2 * Copyright (c) 2016 Samsung Electronics Co., Ltd All Rights Reserved
4 * Licensed under the Apache License, Version 2.0 (the License);
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an AS IS BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
19 using System.Threading;
20 using System.Threading.Tasks;
22 namespace Tizen.Content.MediaContent
25 /// Provides the ability to connect to and manage the database.
27 public class MediaDatabase : IDisposable
30 /// Initializes a new instance of the <see cref="MediaDatabase"/> class.
32 public MediaDatabase()
36 private object _lock = new object();
39 /// Connects to the database.
41 /// <exception cref="InvalidOperationException">The database is already connected.</exception>
42 /// <exception cref="ObjectDisposedException">The <see cref="MediaDatabase"/> has already been disposed of.</exception>
43 /// <exception cref="MediaDatabaseException">An error occurred while connecting.</exception>
46 ValidateNotDisposed();
52 throw new InvalidOperationException("The database is already connected.");
55 Interop.Content.Connect().ThrowIfError("Failed to connect");
62 /// Disconnects from the media database.
64 /// <exception cref="InvalidOperationException">The database is not connected.</exception>
65 /// <exception cref="ObjectDisposedException">The <see cref="MediaDatabase"/> has already been disposed of.</exception>
66 /// <exception cref="MediaDatabaseException">An error occurred while connecting.</exception>
67 public void Disconnect()
69 ValidateNotDisposed();
75 throw new InvalidOperationException("The database is not connected.");
78 Interop.Content.Disconnect().ThrowIfError("Failed to disconnect");
84 private static readonly Interop.Content.MediaContentDBUpdatedCallback _mediaInfoUpdatedCb = (
85 MediaContentError error, int pid, ItemType updateItem, OperationType updateType,
86 MediaType mediaType, string uuid, string filePath, string mimeType, IntPtr _) =>
88 if (updateItem == ItemType.Directory)
93 _mediaInfoUpdated?.Invoke(
94 null, new MediaInfoUpdatedEventArgs(pid, updateType, mediaType, uuid, filePath, mimeType));
97 private static IntPtr _mediaInfoUpdatedHandle = IntPtr.Zero;
98 private static event EventHandler<MediaInfoUpdatedEventArgs> _mediaInfoUpdated;
99 private static readonly object _mediaInfoUpdatedLock = new object();
102 /// Occurs when there is a change for media in the database.
104 public static event EventHandler<MediaInfoUpdatedEventArgs> MediaInfoUpdated
108 lock (_mediaInfoUpdatedLock)
110 if (_mediaInfoUpdated == null)
112 Interop.Content.AddDbUpdatedCb(_mediaInfoUpdatedCb, IntPtr.Zero,
113 out _mediaInfoUpdatedHandle).ThrowIfError("Failed to register an event handler");
116 _mediaInfoUpdated += value;
126 lock (_mediaInfoUpdatedLock)
128 if (_mediaInfoUpdated == value)
130 Interop.Content.RemoveDbUpdatedCb(_mediaInfoUpdatedHandle).ThrowIfError("Failed to unregister");
133 _mediaInfoUpdated -= value;
139 private static readonly Interop.Content.MediaContentDBUpdatedCallback _folderUpdatedCb = (
140 MediaContentError error, int pid, ItemType updateItem, OperationType updateType,
141 MediaType mediaType, string uuid, string filePath, string mimeType, IntPtr _) =>
143 if (updateItem == ItemType.File)
148 _folderUpdated?.Invoke(null, new FolderUpdatedEventArgs(updateType, uuid, filePath));
151 private static IntPtr _folderUpdatedHandle = IntPtr.Zero;
152 private static event EventHandler<FolderUpdatedEventArgs> _folderUpdated;
153 private static readonly object _folderUpdatedLock = new object();
156 /// Occurs when there is a change for the folder in the database.
158 public static event EventHandler<FolderUpdatedEventArgs> FolderUpdated
162 lock (_folderUpdatedLock)
164 if (_folderUpdated == null)
166 Interop.Content.AddDbUpdatedCb(_folderUpdatedCb, IntPtr.Zero,
167 out _folderUpdatedHandle).ThrowIfError("Failed to register an event handler");
170 _folderUpdated += value;
180 lock (_folderUpdatedLock)
182 if (_folderUpdated == value)
184 Interop.Content.RemoveDbUpdatedCb(_folderUpdatedHandle).ThrowIfError("Failed to unregister");
187 _folderUpdated -= value;
193 /// Requests to scan a media file.
195 /// <param name="path">The path of the media to be scanned.</param>
197 /// It requests to scan a media file to the media server.\n
198 /// If the specified file is not registered to the database yet,
199 /// the media file information will be added to the database.\n
200 /// If it is already registered to the database, the media information is refreshed.\n
201 /// If the specified file does not exist,
202 /// the record of the media file will be deleted from the database.\n
204 /// If you want to access internal storage, you should add privilege http://tizen.org/privilege/mediastorage.\n
205 /// If you want to access external storage, you should add privilege http://tizen.org/privilege/externalstorage.
207 /// <privilege>http://tizen.org/privilege/content.write</privilege>
208 /// <privilege>http://tizen.org/privilege/mediastorage</privilege>
209 /// <privilege>http://tizen.org/privilege/externalstorage</privilege>
210 /// <exception cref="InvalidOperationException">The database is not connected.</exception>
211 /// <exception cref="ObjectDisposedException">The <see cref="MediaDatabase"/> has already been disposed of.</exception>
212 /// <exception cref="ArgumentNullException"><paramref name="path"/> is null.</exception>
213 /// <exception cref="ArgumentException">
214 /// <paramref name="path"/> is a zero-length string, contains only white space.\n
216 /// <paramref name="path"/> contains a hidden path that starts with '.'.\n
218 /// <paramref name="path"/> contains a directory containing the ".scan_ignore" file.
220 /// <exception cref="UnauthorizedAccessException">The caller has no required privilege.</exception>
221 public void ScanFile(string path)
225 ValidationUtil.ValidateNotNullOrEmpty(path, nameof(path));
227 Interop.Content.ScanFile(path).Ignore(MediaContentError.InvalidParameter).ThrowIfError("Failed to scan");
231 /// Requests to scan a folder recursively.
234 /// If you want to access internal storage, you should add privilege http://tizen.org/privilege/mediastorage.\n
235 /// If you want to access external storage, you should add privilege http://tizen.org/privilege/externalstorage.
237 /// <privilege>http://tizen.org/privilege/content.write</privilege>
238 /// <privilege>http://tizen.org/privilege/mediastorage</privilege>
239 /// <privilege>http://tizen.org/privilege/externalstorage</privilege>
240 /// <param name="folderPath">The path to scan.</param>
241 /// <remarks>Folders that contains a file named ".scan_ignore" will not be scanned.</remarks>
242 /// <returns>A task that represents the asynchronous scan operation.</returns>
243 /// <exception cref="InvalidOperationException">The database is not connected.</exception>
244 /// <exception cref="ObjectDisposedException">The <see cref="MediaDatabase"/> has already been disposed of.</exception>
245 /// <exception cref="UnauthorizedAccessException">The caller has no required privilege.</exception>
246 /// <exception cref="ArgumentNullException"><paramref name="folderPath"/> is null.</exception>
247 /// <exception cref="ArgumentException">
248 /// <paramref name="folderPath"/> is a zero-length string, contains only white space.\n
250 /// <paramref name="folderPath"/> contains a hidden path that starts with '.'.\n
252 /// <paramref name="folderPath"/> contains a directory containing the ".scan_ignore" file.
254 public Task ScanFolderAsync(string folderPath)
256 return ScanFolderAsync(folderPath, true);
260 /// Requests to scan a folder.
263 /// If you want to access internal storage, you should add privilege http://tizen.org/privilege/mediastorage.\n
264 /// If you want to access external storage, you should add privilege http://tizen.org/privilege/externalstorage.
266 /// <privilege>http://tizen.org/privilege/content.write</privilege>
267 /// <privilege>http://tizen.org/privilege/mediastorage</privilege>
268 /// <privilege>http://tizen.org/privilege/externalstorage</privilege>
269 /// <param name="folderPath">The path to scan.</param>
270 /// <param name="recursive">The value indicating if the folder is to be recursively scanned.</param>
271 /// <remarks>Folders that contains a file named ".scan_ignore" will not be scanned.</remarks>
272 /// <returns>A task that represents the asynchronous scan operation.</returns>
273 /// <exception cref="InvalidOperationException">The database is not connected.</exception>
274 /// <exception cref="ObjectDisposedException">The <see cref="MediaDatabase"/> has already been disposed of.</exception>
275 /// <exception cref="UnauthorizedAccessException">The caller has no required privilege.</exception>
276 /// <exception cref="ArgumentNullException"><paramref name="folderPath"/> is null.</exception>
277 /// <exception cref="ArgumentException">
278 /// <paramref name="folderPath"/> is a zero-length string, contains only white space.\n
280 /// <paramref name="folderPath"/> contains a hidden path that starts with '.'.\n
282 /// <paramref name="folderPath"/> contains a directory containing the ".scan_ignore" file.
284 public Task ScanFolderAsync(string folderPath, bool recursive)
286 return ScanFolderAsync(folderPath, recursive, CancellationToken.None);
290 /// Requests to scan a folder recursively.
293 /// If you want to access internal storage, you should add privilege http://tizen.org/privilege/mediastorage.\n
294 /// If you want to access external storage, you should add privilege http://tizen.org/privilege/externalstorage.
296 /// <privilege>http://tizen.org/privilege/content.write</privilege>
297 /// <privilege>http://tizen.org/privilege/mediastorage</privilege>
298 /// <privilege>http://tizen.org/privilege/externalstorage</privilege>
299 /// <param name="folderPath">The path to scan.</param>
300 /// <param name="cancellationToken">The token to stop scanning.</param>
301 /// <remarks>Folders that contains a file named ".scan_ignore" will not be scanned.</remarks>
302 /// <returns>A task that represents the asynchronous scan operation.</returns>
303 /// <exception cref="InvalidOperationException">The database is not connected.</exception>
304 /// <exception cref="ObjectDisposedException">The <see cref="MediaDatabase"/> has already been disposed of.</exception>
305 /// <exception cref="UnauthorizedAccessException">The caller has no required privilege.</exception>
306 /// <exception cref="ArgumentNullException"><paramref name="folderPath"/> is null.</exception>
307 /// <exception cref="ArgumentException">
308 /// <paramref name="folderPath"/> is a zero-length string, contains only white space.\n
310 /// <paramref name="folderPath"/> contains a hidden path that starts with '.'.\n
312 /// <paramref name="folderPath"/> contains a directory containing the ".scan_ignore" file.
314 public Task ScanFolderAsync(string folderPath, CancellationToken cancellationToken)
316 return ScanFolderAsync(folderPath, true, cancellationToken);
320 /// Requests to scan a folder recursively.
323 /// If you want to access internal storage, you should add privilege http://tizen.org/privilege/mediastorage.\n
324 /// If you want to access external storage, you should add privilege http://tizen.org/privilege/externalstorage.
326 /// <privilege>http://tizen.org/privilege/content.write</privilege>
327 /// <privilege>http://tizen.org/privilege/mediastorage</privilege>
328 /// <privilege>http://tizen.org/privilege/externalstorage</privilege>
329 /// <param name="folderPath">The path to scan.</param>
330 /// <param name="recursive">The value indicating if the folder is to be recursively scanned.</param>
331 /// <param name="cancellationToken">The token to stop scanning.</param>
332 /// <remarks>Folders that contains a file named ".scan_ignore" will not be scanned.</remarks>
333 /// <returns>A task that represents the asynchronous scan operation.</returns>
334 /// <exception cref="InvalidOperationException">The database is not connected.</exception>
335 /// <exception cref="ObjectDisposedException">The <see cref="MediaDatabase"/> has already been disposed of.</exception>
336 /// <exception cref="UnauthorizedAccessException">The caller has no required privilege.</exception>
337 /// <exception cref="ArgumentNullException"><paramref name="folderPath"/> is null.</exception>
338 /// <exception cref="ArgumentException">
339 /// <paramref name="folderPath"/> is a zero-length string, contains only white space.\n
341 /// <paramref name="folderPath"/> contains a hidden path that starts with '.'.\n
343 /// <paramref name="folderPath"/> contains a directory containing the ".scan_ignore" file.
345 public Task ScanFolderAsync(string folderPath, bool recursive, CancellationToken cancellationToken)
349 ValidationUtil.ValidateNotNullOrEmpty(folderPath, nameof(folderPath));
351 return cancellationToken.IsCancellationRequested ? Task.FromCanceled(cancellationToken) :
352 ScanFolderAsyncCore(folderPath, recursive, cancellationToken);
355 private async Task ScanFolderAsyncCore(string folderPath, bool recursive, CancellationToken cancellationToken)
357 var tcs = new TaskCompletionSource<bool>();
359 using (var cbKeeper = ObjectKeeper.Get(GetScanCompletedCallback(tcs)))
360 using (RegisterCancellationAction(tcs, folderPath, cancellationToken))
363 Interop.Content.ScanFolder(folderPath, recursive, cbKeeper.Target)
364 .ThrowIfError("Failed to scan");
370 private static Interop.Content.MediaScanCompletedCallback GetScanCompletedCallback(TaskCompletionSource<bool> tcs)
372 return (scanResult, _) =>
374 if (scanResult == MediaContentError.None)
376 tcs.TrySetResult(true);
380 tcs.TrySetException(scanResult.AsException("Failed to scan"));
385 private static IDisposable RegisterCancellationAction(TaskCompletionSource<bool> tcs,
386 string folderPath, CancellationToken cancellationToken)
388 if (cancellationToken.CanBeCanceled == false)
393 return cancellationToken.Register(() =>
395 if (tcs.Task.IsCompleted)
400 Interop.Content.CancelScanFolder(folderPath).ThrowIfError("Failed to cancel scanning");
401 tcs.TrySetCanceled();
405 internal bool IsConnected { get; set; }
407 internal void ValidateState()
409 ValidateNotDisposed();
411 if (IsConnected == false)
413 throw new InvalidOperationException("Database is not connected.");
417 private void ValidateNotDisposed()
421 throw new ObjectDisposedException(nameof(MediaDatabase));
425 #region IDisposable Support
426 private bool _disposed = false;
429 /// Disposes of the resources (other than memory) used by the MediaDatabase.
431 /// <param name="disposing">true to release both managed and unmanaged resources; false to release only unmanaged resources.</param>
432 protected virtual void Dispose(bool disposing)
438 var disconnectResult = Interop.Content.Disconnect();
440 if (disconnectResult != MediaContentError.None)
442 Log.Warn(nameof(MediaDatabase), $"Failed to disconnect {disconnectResult.ToString()}.");
451 /// Releases all the resources.
453 public void Dispose()
459 /// Gets the value indicating whether the database has been disposed of.
461 /// <value>true if the database has been disposed of; otherwise, false.</value>
462 public bool IsDisposed => _disposed;