/* * 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. /// /// 4 public class MediaDatabase : IDisposable { /// /// Initializes a new instance of the class. /// /// 4 public MediaDatabase() { } private object _lock = new object(); /// /// Connects to the database. /// /// The database is already connected. /// The has already been disposed. /// An error occurred while connecting. /// 4 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. /// An error occurred while connecting. /// 4 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. /// /// 4 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. /// /// 4 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.
/// If the specified file is not registered to the database yet, /// the media file information will be added to the database.
/// If it is already registered to the database, the media information is refreshed.
/// If the specified file does not exist, /// the record of the media file will be deleted from the database.
///
/// If you want to access internal storage, you should add privilege http://tizen.org/privilege/mediastorage.
/// If you want to access external storage, you should add privilege http://tizen.org/privilege/externalstorage.
///
/// If http://tizen.org/feature/content.scanning.others feature is not supported and the specified file is other-type, /// will be thrown. ///
/// 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. /// is null. /// /// is a zero-length string, contains only white space.
/// -or-
/// contains a hidden path that starts with '.'.
/// -or-
/// contains a directory containing the ".scan_ignore" file. ///
/// The caller has no required privilege. /// The required feature is not supported. /// 4 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.
/// 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. /// The caller has no required privilege. /// is null. /// /// is a zero-length string, contains only white space.
/// -or-
/// contains a hidden path that starts with '.'.
/// -or-
/// contains a directory containing the ".scan_ignore" file. ///
/// 4 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.
/// 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. /// The caller has no required privilege. /// is null. /// /// is a zero-length string, contains only white space.
/// -or-
/// contains a hidden path that starts with '.'.
/// -or-
/// contains a directory containing the ".scan_ignore" file. ///
/// 4 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.
/// 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. /// The caller has no required privilege. /// is null. /// /// is a zero-length string, contains only white space.
/// -or-
/// contains a hidden path that starts with '.'.
/// -or-
/// contains a directory containing the ".scan_ignore" file. ///
/// 4 public Task ScanFolderAsync(string folderPath, CancellationToken cancellationToken) { return ScanFolderAsync(folderPath, true, cancellationToken); } /// /// Requests to scan a folder. /// /// /// If you want to access internal storage, you should add privilege http://tizen.org/privilege/mediastorage.
/// 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. /// The caller has no required privilege. /// is null. /// /// is a zero-length string, contains only white space.
/// -or-
/// contains a hidden path that starts with '.'.
/// -or-
/// contains a directory containing the ".scan_ignore" file. ///
/// 4 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(TaskCreationOptions.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. /// 4 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. /// /// 4 public void Dispose() { Dispose(true); } /// /// Gets the value indicating whether the database has been disposed. /// /// true if the database has been disposed; otherwise, false. /// 4 public bool IsDisposed => _disposed; #endregion } }