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 of.</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 of.</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.
212 /// <privilege>http://tizen.org/privilege/content.write</privilege>
213 /// <privilege>http://tizen.org/privilege/mediastorage</privilege>
214 /// <privilege>http://tizen.org/privilege/externalstorage</privilege>
215 /// <exception cref="InvalidOperationException">The database is not connected.</exception>
216 /// <exception cref="ObjectDisposedException">The <see cref="MediaDatabase"/> has already been disposed of.</exception>
217 /// <exception cref="ArgumentNullException"><paramref name="path"/> is null.</exception>
218 /// <exception cref="ArgumentException">
219 /// <paramref name="path"/> is a zero-length string, contains only white space.<br/>
221 /// <paramref name="path"/> contains a hidden path that starts with '.'.<br/>
223 /// <paramref name="path"/> contains a directory containing the ".scan_ignore" file.
225 /// <exception cref="UnauthorizedAccessException">The caller has no required privilege.</exception>
226 /// <since_tizen> 4 </since_tizen>
227 public void ScanFile(string path)
231 ValidationUtil.ValidateNotNullOrEmpty(path, nameof(path));
233 Interop.Content.ScanFile(path).Ignore(MediaContentError.InvalidParameter).ThrowIfError("Failed to scan");
237 /// Requests to scan a folder recursively.
240 /// If you want to access internal storage, you should add privilege http://tizen.org/privilege/mediastorage.<br/>
241 /// If you want to access external storage, you should add privilege http://tizen.org/privilege/externalstorage.
243 /// <privilege>http://tizen.org/privilege/content.write</privilege>
244 /// <privilege>http://tizen.org/privilege/mediastorage</privilege>
245 /// <privilege>http://tizen.org/privilege/externalstorage</privilege>
246 /// <param name="folderPath">The path to scan.</param>
247 /// <remarks>Folders that contains a file named ".scan_ignore" will not be scanned.</remarks>
248 /// <returns>A task that represents the asynchronous scan operation.</returns>
249 /// <exception cref="InvalidOperationException">The database is not connected.</exception>
250 /// <exception cref="ObjectDisposedException">The <see cref="MediaDatabase"/> has already been disposed of.</exception>
251 /// <exception cref="UnauthorizedAccessException">The caller has no required privilege.</exception>
252 /// <exception cref="ArgumentNullException"><paramref name="folderPath"/> is null.</exception>
253 /// <exception cref="ArgumentException">
254 /// <paramref name="folderPath"/> is a zero-length string, contains only white space.<br/>
256 /// <paramref name="folderPath"/> contains a hidden path that starts with '.'.<br/>
258 /// <paramref name="folderPath"/> contains a directory containing the ".scan_ignore" file.
260 /// <since_tizen> 4 </since_tizen>
261 public Task ScanFolderAsync(string folderPath)
263 return ScanFolderAsync(folderPath, true);
267 /// Requests to scan a folder.
270 /// If you want to access internal storage, you should add privilege http://tizen.org/privilege/mediastorage.<br/>
271 /// If you want to access external storage, you should add privilege http://tizen.org/privilege/externalstorage.
273 /// <privilege>http://tizen.org/privilege/content.write</privilege>
274 /// <privilege>http://tizen.org/privilege/mediastorage</privilege>
275 /// <privilege>http://tizen.org/privilege/externalstorage</privilege>
276 /// <param name="folderPath">The path to scan.</param>
277 /// <param name="recursive">The value indicating if the folder is to be recursively scanned.</param>
278 /// <remarks>Folders that contains a file named ".scan_ignore" will not be scanned.</remarks>
279 /// <returns>A task that represents the asynchronous scan operation.</returns>
280 /// <exception cref="InvalidOperationException">The database is not connected.</exception>
281 /// <exception cref="ObjectDisposedException">The <see cref="MediaDatabase"/> has already been disposed of.</exception>
282 /// <exception cref="UnauthorizedAccessException">The caller has no required privilege.</exception>
283 /// <exception cref="ArgumentNullException"><paramref name="folderPath"/> is null.</exception>
284 /// <exception cref="ArgumentException">
285 /// <paramref name="folderPath"/> is a zero-length string, contains only white space.<br/>
287 /// <paramref name="folderPath"/> contains a hidden path that starts with '.'.<br/>
289 /// <paramref name="folderPath"/> contains a directory containing the ".scan_ignore" file.
291 /// <since_tizen> 4 </since_tizen>
292 public Task ScanFolderAsync(string folderPath, bool recursive)
294 return ScanFolderAsync(folderPath, recursive, CancellationToken.None);
298 /// Requests to scan a folder recursively.
301 /// If you want to access internal storage, you should add privilege http://tizen.org/privilege/mediastorage.<br/>
302 /// If you want to access external storage, you should add privilege http://tizen.org/privilege/externalstorage.
304 /// <privilege>http://tizen.org/privilege/content.write</privilege>
305 /// <privilege>http://tizen.org/privilege/mediastorage</privilege>
306 /// <privilege>http://tizen.org/privilege/externalstorage</privilege>
307 /// <param name="folderPath">The path to scan.</param>
308 /// <param name="cancellationToken">The token to stop scanning.</param>
309 /// <remarks>Folders that contains a file named ".scan_ignore" will not be scanned.</remarks>
310 /// <returns>A task that represents the asynchronous scan operation.</returns>
311 /// <exception cref="InvalidOperationException">The database is not connected.</exception>
312 /// <exception cref="ObjectDisposedException">The <see cref="MediaDatabase"/> has already been disposed of.</exception>
313 /// <exception cref="UnauthorizedAccessException">The caller has no required privilege.</exception>
314 /// <exception cref="ArgumentNullException"><paramref name="folderPath"/> is null.</exception>
315 /// <exception cref="ArgumentException">
316 /// <paramref name="folderPath"/> is a zero-length string, contains only white space.<br/>
318 /// <paramref name="folderPath"/> contains a hidden path that starts with '.'.<br/>
320 /// <paramref name="folderPath"/> contains a directory containing the ".scan_ignore" file.
322 /// <since_tizen> 4 </since_tizen>
323 public Task ScanFolderAsync(string folderPath, CancellationToken cancellationToken)
325 return ScanFolderAsync(folderPath, true, cancellationToken);
329 /// Requests to scan a folder.
332 /// If you want to access internal storage, you should add privilege http://tizen.org/privilege/mediastorage.<br/>
333 /// If you want to access external storage, you should add privilege http://tizen.org/privilege/externalstorage.
335 /// <privilege>http://tizen.org/privilege/content.write</privilege>
336 /// <privilege>http://tizen.org/privilege/mediastorage</privilege>
337 /// <privilege>http://tizen.org/privilege/externalstorage</privilege>
338 /// <param name="folderPath">The path to scan.</param>
339 /// <param name="recursive">The value indicating if the folder is to be recursively scanned.</param>
340 /// <param name="cancellationToken">The token to stop scanning.</param>
341 /// <remarks>Folders that contains a file named ".scan_ignore" will not be scanned.</remarks>
342 /// <returns>A task that represents the asynchronous scan operation.</returns>
343 /// <exception cref="InvalidOperationException">The database is not connected.</exception>
344 /// <exception cref="ObjectDisposedException">The <see cref="MediaDatabase"/> has already been disposed of.</exception>
345 /// <exception cref="UnauthorizedAccessException">The caller has no required privilege.</exception>
346 /// <exception cref="ArgumentNullException"><paramref name="folderPath"/> is null.</exception>
347 /// <exception cref="ArgumentException">
348 /// <paramref name="folderPath"/> is a zero-length string, contains only white space.<br/>
350 /// <paramref name="folderPath"/> contains a hidden path that starts with '.'.<br/>
352 /// <paramref name="folderPath"/> contains a directory containing the ".scan_ignore" file.
354 /// <since_tizen> 4 </since_tizen>
355 public Task ScanFolderAsync(string folderPath, bool recursive, CancellationToken cancellationToken)
359 ValidationUtil.ValidateNotNullOrEmpty(folderPath, nameof(folderPath));
361 return cancellationToken.IsCancellationRequested ? Task.FromCanceled(cancellationToken) :
362 ScanFolderAsyncCore(folderPath, recursive, cancellationToken);
365 private async Task ScanFolderAsyncCore(string folderPath, bool recursive, CancellationToken cancellationToken)
367 var tcs = new TaskCompletionSource<bool>(TaskCreationOptions.RunContinuationsAsynchronously);
369 using (var cbKeeper = ObjectKeeper.Get(GetScanCompletedCallback(tcs, cancellationToken)))
370 using (RegisterCancellationAction(tcs, folderPath, cancellationToken))
372 Interop.Content.ScanFolder(folderPath, recursive, cbKeeper.Target)
373 .ThrowIfError("Failed to scan");
379 private static Interop.Content.MediaScanCompletedCallback GetScanCompletedCallback(TaskCompletionSource<bool> tcs,
380 CancellationToken cancellationToken)
382 return (scanResult, _) =>
384 if (scanResult == MediaContentError.None)
386 if (cancellationToken.IsCancellationRequested)
388 tcs.TrySetCanceled();
392 tcs.TrySetResult(true);
397 tcs.TrySetException(scanResult.AsException("Failed to scan"));
402 private static IDisposable RegisterCancellationAction(TaskCompletionSource<bool> tcs,
403 string folderPath, CancellationToken cancellationToken)
405 if (cancellationToken.CanBeCanceled == false)
410 return cancellationToken.Register(() =>
412 if (tcs.Task.IsCompleted)
417 Interop.Content.CancelScanFolder(folderPath).ThrowIfError("Failed to cancel scanning");
421 internal bool IsConnected { get; set; }
423 internal void ValidateState()
425 ValidateNotDisposed();
427 if (IsConnected == false)
429 throw new InvalidOperationException("Database is not connected.");
433 private void ValidateNotDisposed()
437 throw new ObjectDisposedException(nameof(MediaDatabase));
441 #region IDisposable Support
442 private bool _disposed = false;
445 /// Disposes of the resources (other than memory) used by the MediaDatabase.
447 /// <param name="disposing">true to release both managed and unmanaged resources; false to release only unmanaged resources.</param>
448 /// <since_tizen> 4 </since_tizen>
449 protected virtual void Dispose(bool disposing)
455 var disconnectResult = Interop.Content.Disconnect();
457 if (disconnectResult != MediaContentError.None)
459 Log.Warn(nameof(MediaDatabase), $"Failed to disconnect {disconnectResult.ToString()}.");
468 /// Releases all the resources.
470 /// <since_tizen> 4 </since_tizen>
471 public void Dispose()
477 /// Gets the value indicating whether the database has been disposed of.
479 /// <value>true if the database has been disposed of; otherwise, false.</value>
480 /// <since_tizen> 4 </since_tizen>
481 public bool IsDisposed => _disposed;