/* * Copyright (c) 2016 Samsung Electronics Co., Ltd All Rights Reserved * * Licensed under the Apache License, Version 2.0 (the License); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an AS IS BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ using System; using System.Linq; using System.Threading; using System.Threading.Tasks; namespace Tizen.Content.MediaContent { /// /// Provides the ability to connect to and manage the database. /// public class MediaDatabase : IDisposable { /// /// Initializes a new instance of the class. /// public MediaDatabase() { } private object _lock = new object(); /// /// Connects to the database. /// /// The database is already connected. /// The has already been disposed of. /// An error occurred while connecting. public void Connect() { ValidateNotDisposed(); lock (_lock) { if (IsConnected) { throw new InvalidOperationException("The database is already connected."); } Interop.Content.Connect().ThrowIfError("Failed to connect"); IsConnected = true; } } /// /// Disconnects from the media database. /// /// The database is not connected. /// The has already been disposed of. /// An error occurred while connecting. public void Disconnect() { ValidateNotDisposed(); lock (_lock) { if (!IsConnected) { throw new InvalidOperationException("The database is not connected."); } Interop.Content.Disconnect().ThrowIfError("Failed to disconnect"); IsConnected = false; } } private static readonly Interop.Content.MediaContentDBUpdatedCallback _mediaInfoUpdatedCb = ( MediaContentError error, int pid, ItemType updateItem, OperationType updateType, MediaType mediaType, string uuid, string filePath, string mimeType, IntPtr _) => { if (updateItem == ItemType.Directory) { return; } _mediaInfoUpdated?.Invoke( null, new MediaInfoUpdatedEventArgs(pid, updateType, mediaType, uuid, filePath, mimeType)); }; private static IntPtr _mediaInfoUpdatedHandle = IntPtr.Zero; private static event EventHandler _mediaInfoUpdated; private static readonly object _mediaInfoUpdatedLock = new object(); /// /// Occurs when there is a change for media in the database. /// public static event EventHandler MediaInfoUpdated { add { lock (_mediaInfoUpdatedLock) { if (_mediaInfoUpdated == null) { Interop.Content.AddDbUpdatedCb(_mediaInfoUpdatedCb, IntPtr.Zero, out _mediaInfoUpdatedHandle).ThrowIfError("Failed to register an event handler"); } _mediaInfoUpdated += value; } } remove { if (value == null) { return; } lock (_mediaInfoUpdatedLock) { if (_mediaInfoUpdated == value) { Interop.Content.RemoveDbUpdatedCb(_mediaInfoUpdatedHandle).ThrowIfError("Failed to unregister"); } _mediaInfoUpdated -= value; } } } private static readonly Interop.Content.MediaContentDBUpdatedCallback _folderUpdatedCb = ( MediaContentError error, int pid, ItemType updateItem, OperationType updateType, MediaType mediaType, string uuid, string filePath, string mimeType, IntPtr _) => { if (updateItem == ItemType.File) { return; } _folderUpdated?.Invoke(null, new FolderUpdatedEventArgs(updateType, uuid, filePath)); }; private static IntPtr _folderUpdatedHandle = IntPtr.Zero; private static event EventHandler _folderUpdated; private static readonly object _folderUpdatedLock = new object(); /// /// Occurs when there is a change for the folder in the database. /// public static event EventHandler FolderUpdated { add { lock (_folderUpdatedLock) { if (_folderUpdated == null) { Interop.Content.AddDbUpdatedCb(_folderUpdatedCb, IntPtr.Zero, out _folderUpdatedHandle).ThrowIfError("Failed to register an event handler"); } _folderUpdated += value; } } remove { if (value == null) { return; } lock (_folderUpdatedLock) { if (_folderUpdated == value) { Interop.Content.RemoveDbUpdatedCb(_folderUpdatedHandle).ThrowIfError("Failed to unregister"); } _folderUpdated -= value; } } } /// /// Requests to scan a media file. /// /// The path of the media to be scanned. /// /// It requests to scan a media file to the media server.\n /// If the specified file is not registered to the database yet, /// the media file information will be added to the database.\n /// If it is already registered to the database, the media information is refreshed.\n /// If the specified file does not exist, /// the record of the media file will be deleted from the database.\n /// \n /// If you want to access internal storage, you should add privilege http://tizen.org/privilege/mediastorage.\n /// If you want to access external storage, you should add privilege http://tizen.org/privilege/externalstorage. /// /// http://tizen.org/privilege/content.write /// http://tizen.org/privilege/mediastorage /// http://tizen.org/privilege/externalstorage /// The database is not connected. /// The has already been disposed of. /// is null. /// /// is a zero-length string, contains only white space.\n /// -or-\n /// contains a hidden path that starts with '.'.\n /// -or-\n /// contains a directory containing the ".scan_ignore" file. /// /// The caller has no required privilege. public void ScanFile(string path) { ValidateState(); ValidationUtil.ValidateNotNullOrEmpty(path, nameof(path)); Interop.Content.ScanFile(path).Ignore(MediaContentError.InvalidParameter).ThrowIfError("Failed to scan"); } /// /// Requests to scan a folder recursively. /// /// /// If you want to access internal storage, you should add privilege http://tizen.org/privilege/mediastorage.\n /// If you want to access external storage, you should add privilege http://tizen.org/privilege/externalstorage. /// /// http://tizen.org/privilege/content.write /// http://tizen.org/privilege/mediastorage /// http://tizen.org/privilege/externalstorage /// The path to scan. /// Folders that contains a file named ".scan_ignore" will not be scanned. /// A task that represents the asynchronous scan operation. /// The database is not connected. /// The has already been disposed of. /// The caller has no required privilege. /// is null. /// /// is a zero-length string, contains only white space.\n /// -or-\n /// contains a hidden path that starts with '.'.\n /// -or-\n /// contains a directory containing the ".scan_ignore" file. /// public Task ScanFolderAsync(string folderPath) { return ScanFolderAsync(folderPath, true); } /// /// Requests to scan a folder. /// /// /// If you want to access internal storage, you should add privilege http://tizen.org/privilege/mediastorage.\n /// If you want to access external storage, you should add privilege http://tizen.org/privilege/externalstorage. /// /// http://tizen.org/privilege/content.write /// http://tizen.org/privilege/mediastorage /// http://tizen.org/privilege/externalstorage /// The path to scan. /// The value indicating if the folder is to be recursively scanned. /// Folders that contains a file named ".scan_ignore" will not be scanned. /// A task that represents the asynchronous scan operation. /// The database is not connected. /// The has already been disposed of. /// The caller has no required privilege. /// is null. /// /// is a zero-length string, contains only white space.\n /// -or-\n /// contains a hidden path that starts with '.'.\n /// -or-\n /// contains a directory containing the ".scan_ignore" file. /// public Task ScanFolderAsync(string folderPath, bool recursive) { return ScanFolderAsync(folderPath, recursive, CancellationToken.None); } /// /// Requests to scan a folder recursively. /// /// /// If you want to access internal storage, you should add privilege http://tizen.org/privilege/mediastorage.\n /// If you want to access external storage, you should add privilege http://tizen.org/privilege/externalstorage. /// /// http://tizen.org/privilege/content.write /// http://tizen.org/privilege/mediastorage /// http://tizen.org/privilege/externalstorage /// The path to scan. /// The token to stop scanning. /// Folders that contains a file named ".scan_ignore" will not be scanned. /// A task that represents the asynchronous scan operation. /// The database is not connected. /// The has already been disposed of. /// The caller has no required privilege. /// is null. /// /// is a zero-length string, contains only white space.\n /// -or-\n /// contains a hidden path that starts with '.'.\n /// -or-\n /// contains a directory containing the ".scan_ignore" file. /// public Task ScanFolderAsync(string folderPath, CancellationToken cancellationToken) { return ScanFolderAsync(folderPath, true, cancellationToken); } /// /// Requests to scan a folder recursively. /// /// /// If you want to access internal storage, you should add privilege http://tizen.org/privilege/mediastorage.\n /// If you want to access external storage, you should add privilege http://tizen.org/privilege/externalstorage. /// /// http://tizen.org/privilege/content.write /// http://tizen.org/privilege/mediastorage /// http://tizen.org/privilege/externalstorage /// The path to scan. /// The value indicating if the folder is to be recursively scanned. /// The token to stop scanning. /// Folders that contains a file named ".scan_ignore" will not be scanned. /// A task that represents the asynchronous scan operation. /// The database is not connected. /// The has already been disposed of. /// The caller has no required privilege. /// is null. /// /// is a zero-length string, contains only white space.\n /// -or-\n /// contains a hidden path that starts with '.'.\n /// -or-\n /// contains a directory containing the ".scan_ignore" file. /// public Task ScanFolderAsync(string folderPath, bool recursive, CancellationToken cancellationToken) { ValidateState(); ValidationUtil.ValidateNotNullOrEmpty(folderPath, nameof(folderPath)); return cancellationToken.IsCancellationRequested ? Task.FromCanceled(cancellationToken) : ScanFolderAsyncCore(folderPath, recursive, cancellationToken); } private async Task ScanFolderAsyncCore(string folderPath, bool recursive, CancellationToken cancellationToken) { var tcs = new TaskCompletionSource(TaskContinuationOptions.RunContinuationsAsynchronously); using (var cbKeeper = ObjectKeeper.Get(GetScanCompletedCallback(tcs, cancellationToken))) using (RegisterCancellationAction(tcs, folderPath, cancellationToken)) { Interop.Content.ScanFolder(folderPath, recursive, cbKeeper.Target) .ThrowIfError("Failed to scan"); await tcs.Task; } } private static Interop.Content.MediaScanCompletedCallback GetScanCompletedCallback(TaskCompletionSource tcs, CancellationToken cancellationToken) { return (scanResult, _) => { if (scanResult == MediaContentError.None) { if (cancellationToken.IsCancellationRequested) { tcs.TrySetCanceled(); } else { tcs.TrySetResult(true); } } else { tcs.TrySetException(scanResult.AsException("Failed to scan")); } }; } private static IDisposable RegisterCancellationAction(TaskCompletionSource tcs, string folderPath, CancellationToken cancellationToken) { if (cancellationToken.CanBeCanceled == false) { return null; } return cancellationToken.Register(() => { if (tcs.Task.IsCompleted) { return; } Interop.Content.CancelScanFolder(folderPath).ThrowIfError("Failed to cancel scanning"); }); } internal bool IsConnected { get; set; } internal void ValidateState() { ValidateNotDisposed(); if (IsConnected == false) { throw new InvalidOperationException("Database is not connected."); } } private void ValidateNotDisposed() { if (IsDisposed) { throw new ObjectDisposedException(nameof(MediaDatabase)); } } #region IDisposable Support private bool _disposed = false; /// /// Disposes of the resources (other than memory) used by the MediaDatabase. /// /// true to release both managed and unmanaged resources; false to release only unmanaged resources. protected virtual void Dispose(bool disposing) { if (!_disposed) { if (IsConnected) { var disconnectResult = Interop.Content.Disconnect(); if (disconnectResult != MediaContentError.None) { Log.Warn(nameof(MediaDatabase), $"Failed to disconnect {disconnectResult.ToString()}."); } } _disposed = true; } } /// /// Releases all the resources. /// public void Dispose() { Dispose(true); } /// /// Gets the value indicating whether the database has been disposed of. /// /// true if the database has been disposed of; otherwise, false. public bool IsDisposed => _disposed; #endregion } }