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 /// <since_tizen> 4 </since_tizen>
28 public class MediaDatabase : IDisposable
31 /// Initializes a new instance of the <see cref="MediaDatabase"/> class.
33 /// <since_tizen> 4 </since_tizen>
34 public MediaDatabase()
38 private object _lock = new object();
41 /// Connects to the database.
43 /// <exception cref="InvalidOperationException">The database is already connected.</exception>
44 /// <exception cref="ObjectDisposedException">The <see cref="MediaDatabase"/> has already been disposed.</exception>
45 /// <exception cref="MediaDatabaseException">An error occurred while connecting.</exception>
46 /// <since_tizen> 4 </since_tizen>
49 ValidateNotDisposed();
55 throw new InvalidOperationException("The database is already connected.");
58 Interop.Content.Connect().ThrowIfError("Failed to connect");
65 /// Disconnects from the media database.
67 /// <exception cref="InvalidOperationException">The database is not connected.</exception>
68 /// <exception cref="ObjectDisposedException">The <see cref="MediaDatabase"/> has already been disposed.</exception>
69 /// <exception cref="MediaDatabaseException">An error occurred while connecting.</exception>
70 /// <since_tizen> 4 </since_tizen>
71 public void Disconnect()
73 ValidateNotDisposed();
79 throw new InvalidOperationException("The database is not connected.");
82 Interop.Content.Disconnect().ThrowIfError("Failed to disconnect");
88 private static readonly Interop.Content.MediaContentDBUpdatedCallback _mediaInfoUpdatedCb = (
89 MediaContentError error, int pid, ItemType updateItem, OperationType updateType,
90 MediaType mediaType, string uuid, string filePath, string mimeType, IntPtr _) =>
92 if (updateItem == ItemType.Directory)
97 _mediaInfoUpdated?.Invoke(
98 null, new MediaInfoUpdatedEventArgs(pid, updateType, mediaType, uuid, filePath, mimeType));
101 private static IntPtr _mediaInfoUpdatedHandle = IntPtr.Zero;
102 private static event EventHandler<MediaInfoUpdatedEventArgs> _mediaInfoUpdated;
103 private static readonly object _mediaInfoUpdatedLock = new object();
106 /// Occurs when there is a change for media in the database.
108 /// <since_tizen> 4 </since_tizen>
109 public static event EventHandler<MediaInfoUpdatedEventArgs> MediaInfoUpdated
113 lock (_mediaInfoUpdatedLock)
115 if (_mediaInfoUpdated == null)
117 Interop.Content.AddDbUpdatedCb(_mediaInfoUpdatedCb, IntPtr.Zero,
118 out _mediaInfoUpdatedHandle).ThrowIfError("Failed to register an event handler");
121 _mediaInfoUpdated += value;
131 lock (_mediaInfoUpdatedLock)
133 if (_mediaInfoUpdated == value)
135 Interop.Content.RemoveDbUpdatedCb(_mediaInfoUpdatedHandle).ThrowIfError("Failed to unregister");
138 _mediaInfoUpdated -= value;
143 private static readonly Interop.Content.MediaContentDBUpdatedCallback _folderUpdatedCb = (
144 MediaContentError error, int pid, ItemType updateItem, OperationType updateType,
145 MediaType mediaType, string uuid, string filePath, string mimeType, IntPtr _) =>
147 if (updateItem == ItemType.File)
152 _folderUpdated?.Invoke(null, new FolderUpdatedEventArgs(updateType, uuid, filePath));
155 private static IntPtr _folderUpdatedHandle = IntPtr.Zero;
156 private static event EventHandler<FolderUpdatedEventArgs> _folderUpdated;
157 private static readonly object _folderUpdatedLock = new object();
160 /// Occurs when there is a change for the folder in the database.
162 /// <since_tizen> 4 </since_tizen>
163 public static event EventHandler<FolderUpdatedEventArgs> FolderUpdated
167 lock (_folderUpdatedLock)
169 if (_folderUpdated == null)
171 Interop.Content.AddDbUpdatedCb(_folderUpdatedCb, IntPtr.Zero,
172 out _folderUpdatedHandle).ThrowIfError("Failed to register an event handler");
175 _folderUpdated += value;
185 lock (_folderUpdatedLock)
187 if (_folderUpdated == value)
189 Interop.Content.RemoveDbUpdatedCb(_folderUpdatedHandle).ThrowIfError("Failed to unregister");
192 _folderUpdated -= value;
198 /// Requests to scan a media file.
200 /// <param name="path">The path of the media to be scanned.</param>
202 /// It requests to scan a media file to the media server.<br/>
203 /// If the specified file is not registered to the database yet,
204 /// the media file information will be added to the database.<br/>
205 /// If it is already registered to the database, the media information is refreshed.<br/>
206 /// If the specified file does not exist,
207 /// the record of the media file will be deleted from the database.<br/>
209 /// If you want to access internal storage, you should add privilege http://tizen.org/privilege/mediastorage.<br/>
210 /// If you want to access external storage, you should add privilege http://tizen.org/privilege/externalstorage.<br/>
212 /// If http://tizen.org/feature/content.scanning.others feature is not supported and the specified file is other-type,
213 /// <see cref="NotSupportedException"/> will be thrown.
215 /// <privilege>http://tizen.org/privilege/content.write</privilege>
216 /// <privilege>http://tizen.org/privilege/mediastorage</privilege>
217 /// <privilege>http://tizen.org/privilege/externalstorage</privilege>
218 /// <exception cref="InvalidOperationException">The database is not connected.</exception>
219 /// <exception cref="ObjectDisposedException">The <see cref="MediaDatabase"/> has already been disposed.</exception>
220 /// <exception cref="ArgumentNullException"><paramref name="path"/> is null.</exception>
221 /// <exception cref="ArgumentException">
222 /// <paramref name="path"/> is a zero-length string, contains only white space.<br/>
224 /// <paramref name="path"/> contains a hidden path that starts with '.'.<br/>
226 /// <paramref name="path"/> contains a directory containing the ".scan_ignore" file.
228 /// <exception cref="UnauthorizedAccessException">The caller has no required privilege.</exception>
229 /// <exception cref="NotSupportedException">The required feature is not supported.</exception>
230 /// <since_tizen> 4 </since_tizen>
231 public void ScanFile(string path)
235 ValidationUtil.ValidateNotNullOrEmpty(path, nameof(path));
237 Interop.Content.ScanFile(path).Ignore(MediaContentError.InvalidParameter).ThrowIfError("Failed to scan");
241 /// Requests to scan a folder recursively.
244 /// If you want to access internal storage, you should add privilege http://tizen.org/privilege/mediastorage.<br/>
245 /// If you want to access external storage, you should add privilege http://tizen.org/privilege/externalstorage.
247 /// <privilege>http://tizen.org/privilege/content.write</privilege>
248 /// <privilege>http://tizen.org/privilege/mediastorage</privilege>
249 /// <privilege>http://tizen.org/privilege/externalstorage</privilege>
250 /// <param name="folderPath">The path to scan.</param>
251 /// <remarks>Folders that contains a file named ".scan_ignore" will not be scanned.</remarks>
252 /// <returns>A task that represents the asynchronous scan operation.</returns>
253 /// <exception cref="InvalidOperationException">The database is not connected.</exception>
254 /// <exception cref="ObjectDisposedException">The <see cref="MediaDatabase"/> has already been disposed.</exception>
255 /// <exception cref="UnauthorizedAccessException">The caller has no required privilege.</exception>
256 /// <exception cref="ArgumentNullException"><paramref name="folderPath"/> is null.</exception>
257 /// <exception cref="ArgumentException">
258 /// <paramref name="folderPath"/> is a zero-length string, contains only white space.<br/>
260 /// <paramref name="folderPath"/> contains a hidden path that starts with '.'.<br/>
262 /// <paramref name="folderPath"/> contains a directory containing the ".scan_ignore" file.
264 /// <since_tizen> 4 </since_tizen>
265 public Task ScanFolderAsync(string folderPath)
267 return ScanFolderAsync(folderPath, true);
271 /// Requests to scan a folder.
274 /// If you want to access internal storage, you should add privilege http://tizen.org/privilege/mediastorage.<br/>
275 /// If you want to access external storage, you should add privilege http://tizen.org/privilege/externalstorage.
277 /// <privilege>http://tizen.org/privilege/content.write</privilege>
278 /// <privilege>http://tizen.org/privilege/mediastorage</privilege>
279 /// <privilege>http://tizen.org/privilege/externalstorage</privilege>
280 /// <param name="folderPath">The path to scan.</param>
281 /// <param name="recursive">The value indicating if the folder is to be recursively scanned.</param>
282 /// <remarks>Folders that contains a file named ".scan_ignore" will not be scanned.</remarks>
283 /// <returns>A task that represents the asynchronous scan operation.</returns>
284 /// <exception cref="InvalidOperationException">The database is not connected.</exception>
285 /// <exception cref="ObjectDisposedException">The <see cref="MediaDatabase"/> has already been disposed.</exception>
286 /// <exception cref="UnauthorizedAccessException">The caller has no required privilege.</exception>
287 /// <exception cref="ArgumentNullException"><paramref name="folderPath"/> is null.</exception>
288 /// <exception cref="ArgumentException">
289 /// <paramref name="folderPath"/> is a zero-length string, contains only white space.<br/>
291 /// <paramref name="folderPath"/> contains a hidden path that starts with '.'.<br/>
293 /// <paramref name="folderPath"/> contains a directory containing the ".scan_ignore" file.
295 /// <since_tizen> 4 </since_tizen>
296 public Task ScanFolderAsync(string folderPath, bool recursive)
298 return ScanFolderAsync(folderPath, recursive, CancellationToken.None);
302 /// Requests to scan a folder recursively.
305 /// If you want to access internal storage, you should add privilege http://tizen.org/privilege/mediastorage.<br/>
306 /// If you want to access external storage, you should add privilege http://tizen.org/privilege/externalstorage.
308 /// <privilege>http://tizen.org/privilege/content.write</privilege>
309 /// <privilege>http://tizen.org/privilege/mediastorage</privilege>
310 /// <privilege>http://tizen.org/privilege/externalstorage</privilege>
311 /// <param name="folderPath">The path to scan.</param>
312 /// <param name="cancellationToken">The token to stop scanning.</param>
313 /// <remarks>Folders that contains a file named ".scan_ignore" will not be scanned.</remarks>
314 /// <returns>A task that represents the asynchronous scan operation.</returns>
315 /// <exception cref="InvalidOperationException">The database is not connected.</exception>
316 /// <exception cref="ObjectDisposedException">The <see cref="MediaDatabase"/> has already been disposed.</exception>
317 /// <exception cref="UnauthorizedAccessException">The caller has no required privilege.</exception>
318 /// <exception cref="ArgumentNullException"><paramref name="folderPath"/> is null.</exception>
319 /// <exception cref="ArgumentException">
320 /// <paramref name="folderPath"/> is a zero-length string, contains only white space.<br/>
322 /// <paramref name="folderPath"/> contains a hidden path that starts with '.'.<br/>
324 /// <paramref name="folderPath"/> contains a directory containing the ".scan_ignore" file.
326 /// <since_tizen> 4 </since_tizen>
327 public Task ScanFolderAsync(string folderPath, CancellationToken cancellationToken)
329 return ScanFolderAsync(folderPath, true, cancellationToken);
333 /// Requests to scan a folder.
336 /// If you want to access internal storage, you should add privilege http://tizen.org/privilege/mediastorage.<br/>
337 /// If you want to access external storage, you should add privilege http://tizen.org/privilege/externalstorage.
339 /// <privilege>http://tizen.org/privilege/content.write</privilege>
340 /// <privilege>http://tizen.org/privilege/mediastorage</privilege>
341 /// <privilege>http://tizen.org/privilege/externalstorage</privilege>
342 /// <param name="folderPath">The path to scan.</param>
343 /// <param name="recursive">The value indicating if the folder is to be recursively scanned.</param>
344 /// <param name="cancellationToken">The token to stop scanning.</param>
345 /// <remarks>Folders that contains a file named ".scan_ignore" will not be scanned.</remarks>
346 /// <returns>A task that represents the asynchronous scan operation.</returns>
347 /// <exception cref="InvalidOperationException">The database is not connected.</exception>
348 /// <exception cref="ObjectDisposedException">The <see cref="MediaDatabase"/> has already been disposed.</exception>
349 /// <exception cref="UnauthorizedAccessException">The caller has no required privilege.</exception>
350 /// <exception cref="ArgumentNullException"><paramref name="folderPath"/> is null.</exception>
351 /// <exception cref="ArgumentException">
352 /// <paramref name="folderPath"/> is a zero-length string, contains only white space.<br/>
354 /// <paramref name="folderPath"/> contains a hidden path that starts with '.'.<br/>
356 /// <paramref name="folderPath"/> contains a directory containing the ".scan_ignore" file.
358 /// <since_tizen> 4 </since_tizen>
359 public Task ScanFolderAsync(string folderPath, bool recursive, CancellationToken cancellationToken)
363 ValidationUtil.ValidateNotNullOrEmpty(folderPath, nameof(folderPath));
365 return cancellationToken.IsCancellationRequested ? Task.FromCanceled(cancellationToken) :
366 ScanFolderAsyncCore(folderPath, recursive, cancellationToken);
369 private async Task ScanFolderAsyncCore(string folderPath, bool recursive, CancellationToken cancellationToken)
371 var tcs = new TaskCompletionSource<bool>(TaskCreationOptions.RunContinuationsAsynchronously);
373 using (var cbKeeper = ObjectKeeper.Get(GetScanCompletedCallback(tcs, cancellationToken)))
374 using (RegisterCancellationAction(tcs, folderPath, cancellationToken))
376 Interop.Content.ScanFolder(folderPath, recursive, cbKeeper.Target)
377 .ThrowIfError("Failed to scan");
383 private static Interop.Content.MediaScanCompletedCallback GetScanCompletedCallback(TaskCompletionSource<bool> tcs,
384 CancellationToken cancellationToken)
386 return (scanResult, _) =>
388 if (scanResult == MediaContentError.None)
390 if (cancellationToken.IsCancellationRequested)
392 tcs.TrySetCanceled();
396 tcs.TrySetResult(true);
401 tcs.TrySetException(scanResult.AsException("Failed to scan"));
406 private static IDisposable RegisterCancellationAction(TaskCompletionSource<bool> tcs,
407 string folderPath, CancellationToken cancellationToken)
409 if (cancellationToken.CanBeCanceled == false)
414 return cancellationToken.Register(() =>
416 if (tcs.Task.IsCompleted)
421 Interop.Content.CancelScanFolder(folderPath).ThrowIfError("Failed to cancel scanning");
425 internal bool IsConnected { get; set; }
427 internal void ValidateState()
429 ValidateNotDisposed();
431 if (IsConnected == false)
433 throw new InvalidOperationException("Database is not connected.");
437 private void ValidateNotDisposed()
441 throw new ObjectDisposedException(nameof(MediaDatabase));
445 #region IDisposable Support
446 private bool _disposed = false;
449 /// Disposes of the resources (other than memory) used by the MediaDatabase.
451 /// <param name="disposing">true to release both managed and unmanaged resources; false to release only unmanaged resources.</param>
452 /// <since_tizen> 4 </since_tizen>
453 protected virtual void Dispose(bool disposing)
459 var disconnectResult = Interop.Content.Disconnect();
461 if (disconnectResult != MediaContentError.None)
463 Log.Warn(nameof(MediaDatabase), $"Failed to disconnect {disconnectResult.ToString()}.");
472 /// Releases all the resources.
474 /// <since_tizen> 4 </since_tizen>
475 public void Dispose()
481 /// Gets the value indicating whether the database has been disposed.
483 /// <value>true if the database has been disposed; otherwise, false.</value>
484 /// <since_tizen> 4 </since_tizen>
485 public bool IsDisposed => _disposed;