Release 4.0.0-preview1-00172
[platform/core/csapi/tizenfx.git] / src / Tizen.Content.MediaContent / Tizen.Content.MediaContent / MediaInfoCommand.cs
1 /*
2  * Copyright (c) 2016 Samsung Electronics Co., Ltd All Rights Reserved
3  *
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
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
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.
15  */
16
17 using System;
18 using System.Collections.Generic;
19 using System.IO;
20 using System.Linq;
21 using System.Threading;
22 using System.Threading.Tasks;
23
24 namespace Tizen.Content.MediaContent
25 {
26     /// <summary>
27     /// Provides commands to manage the media information and query related items in the database.
28     /// </summary>
29     public class MediaInfoCommand : MediaCommand
30     {
31         /// <summary>
32         /// Initializes a new instance of the <see cref="FolderCommand"/> class with the specified <see cref="MediaDatabase"/>.
33         /// </summary>
34         /// <param name="database">The <see cref="MediaDatabase"/> that the commands run on.</param>
35         /// <exception cref="ArgumentNullException"><paramref name="database"/> is null.</exception>
36         /// <exception cref="ObjectDisposedException"><paramref name="database"/> has already been disposed of.</exception>
37         public MediaInfoCommand(MediaDatabase database) : base(database)
38         {
39         }
40
41         /// <summary>
42         /// Retrieves the number of the bookmarks added to the media.
43         /// </summary>
44         /// <param name="mediaId">The media ID to count the bookmarks added to the media.</param>
45         /// <returns>The number of the bookmarks.</returns>
46         /// <exception cref="InvalidOperationException">The <see cref="MediaDatabase"/> is disconnected.</exception>
47         /// <exception cref="ObjectDisposedException">The <see cref="MediaDatabase"/> has already been disposed of.</exception>
48         /// <exception cref="MediaDatabaseException">An error occurred while executing the command.</exception>
49         /// <exception cref="ArgumentNullException"><paramref name="mediaId"/> is null.</exception>
50         /// <exception cref="ArgumentException"><paramref name="mediaId"/> is a zero-length string, contains only white space.</exception>
51         public int CountBookmark(string mediaId)
52         {
53             return CountBookmark(mediaId, null);
54         }
55
56         /// <summary>
57         /// Retrieves the number of the bookmarks added to the media with the <see cref="CountArguments"/>.
58         /// </summary>
59         /// <param name="mediaId">The media ID to count the bookmarks added to the media.</param>
60         /// <param name="arguments">The criteria to use to filter. This value can be null.</param>
61         /// <returns>The number of the bookmarks.</returns>
62         /// <exception cref="InvalidOperationException">The <see cref="MediaDatabase"/> is disconnected.</exception>
63         /// <exception cref="ObjectDisposedException">The <see cref="MediaDatabase"/> has already been disposed of.</exception>
64         /// <exception cref="MediaDatabaseException">An error occurred while executing the command.</exception>
65         /// <exception cref="ArgumentNullException"><paramref name="mediaId"/> is null.</exception>
66         /// <exception cref="ArgumentException"><paramref name="mediaId"/> is a zero-length string, contains only white space.</exception>
67         public int CountBookmark(string mediaId, CountArguments arguments)
68         {
69             ValidateDatabase();
70
71             ValidationUtil.ValidateNotNullOrEmpty(mediaId, nameof(mediaId));
72
73             return CommandHelper.Count(Interop.MediaInfo.GetBookmarkCount, mediaId, arguments);
74         }
75
76         /// <summary>
77         /// Retrieves the bookmarks added to the media.
78         /// </summary>
79         /// <param name="mediaId">The media ID to select the bookmarks added to the media.</param>
80         /// <returns>The <see cref="MediaDataReader{TRecord}"/> containing the results.</returns>
81         /// <exception cref="InvalidOperationException">The <see cref="MediaDatabase"/> is disconnected.</exception>
82         /// <exception cref="ObjectDisposedException">The <see cref="MediaDatabase"/> has already been disposed of.</exception>
83         /// <exception cref="MediaDatabaseException">An error occurred while executing the command.</exception>
84         /// <exception cref="ArgumentNullException"><paramref name="mediaId"/> is null.</exception>
85         /// <exception cref="ArgumentException"><paramref name="mediaId"/> is a zero-length string, contains only white space.</exception>
86         public MediaDataReader<Bookmark> SelectBookmark(string mediaId)
87         {
88             return SelectBookmark(mediaId, null);
89         }
90
91         /// <summary>
92         /// Retrieves the bookmarks added to the media with the <see cref="SelectArguments"/>.
93         /// </summary>
94         /// <param name="mediaId">The media ID to select the bookmarks added to the media.</param>
95         /// <param name="filter">The criteria to use to filter. This value can be null.</param>
96         /// <returns>The <see cref="MediaDataReader{TRecord}"/> containing the results.</returns>
97         /// <exception cref="InvalidOperationException">The <see cref="MediaDatabase"/> is disconnected.</exception>
98         /// <exception cref="ObjectDisposedException">The <see cref="MediaDatabase"/> has already been disposed of.</exception>
99         /// <exception cref="MediaDatabaseException">An error occurred while executing the command.</exception>
100         /// <exception cref="ArgumentNullException"><paramref name="mediaId"/> is null.</exception>
101         /// <exception cref="ArgumentException"><paramref name="mediaId"/> is a zero-length string, contains only white space.</exception>
102         public MediaDataReader<Bookmark> SelectBookmark(string mediaId, SelectArguments filter)
103         {
104             ValidateDatabase();
105
106             ValidationUtil.ValidateNotNullOrEmpty(mediaId, nameof(mediaId));
107
108             return CommandHelper.SelectMembers(mediaId, filter, Interop.MediaInfo.ForeachBookmarks,
109                 Bookmark.FromHandle);
110         }
111
112
113         /// <summary>
114         /// Retrieves the number of the face information added to or detected from the media.
115         /// </summary>
116         /// <param name="mediaId">The media ID to count face information added to the media.</param>
117         /// <returns>The number of the face information.</returns>
118         /// <exception cref="InvalidOperationException">The <see cref="MediaDatabase"/> is disconnected.</exception>
119         /// <exception cref="ObjectDisposedException">The <see cref="MediaDatabase"/> has already been disposed of.</exception>
120         /// <exception cref="MediaDatabaseException">An error occurred while executing the command.</exception>
121         /// <exception cref="ArgumentNullException"><paramref name="mediaId"/> is null.</exception>
122         /// <exception cref="ArgumentException"><paramref name="mediaId"/> is a zero-length string, contains only white space.</exception>
123         public int CountFaceInfo(string mediaId)
124         {
125             return CountFaceInfo(mediaId, null);
126         }
127
128         /// <summary>
129         /// Retrieves the number of the face information added to or detected from the media with filter.
130         /// </summary>
131         /// <param name="mediaId">The media ID to count the face information added to the media.</param>
132         /// <param name="arguments">The criteria to use to filter. This value can be null.</param>
133         /// <returns>The number of the face information.</returns>
134         /// <exception cref="InvalidOperationException">The <see cref="MediaDatabase"/> is disconnected.</exception>
135         /// <exception cref="ObjectDisposedException">The <see cref="MediaDatabase"/> has already been disposed of.</exception>
136         /// <exception cref="MediaDatabaseException">An error occurred while executing the command.</exception>
137         /// <exception cref="ArgumentNullException"><paramref name="mediaId"/> is null.</exception>
138         /// <exception cref="ArgumentException"><paramref name="mediaId"/> is a zero-length string, contains only white space.</exception>
139         public int CountFaceInfo(string mediaId, CountArguments arguments)
140         {
141             ValidateDatabase();
142
143             ValidationUtil.ValidateNotNullOrEmpty(mediaId, nameof(mediaId));
144
145             return CommandHelper.Count(Interop.MediaInfo.GetFaceCount, mediaId, arguments);
146         }
147
148         /// <summary>
149         /// Retrieves the face information added to or detected from the media.
150         /// </summary>
151         /// <param name="mediaId">The media ID to select face information added to the media.</param>
152         /// <returns>The <see cref="MediaDataReader{TRecord}"/> containing the results.</returns>
153         /// <exception cref="InvalidOperationException">The <see cref="MediaDatabase"/> is disconnected.</exception>
154         /// <exception cref="ObjectDisposedException">The <see cref="MediaDatabase"/> has already been disposed of.</exception>
155         /// <exception cref="MediaDatabaseException">An error occurred while executing the command.</exception>
156         /// <exception cref="ArgumentNullException"><paramref name="mediaId"/> is null.</exception>
157         /// <exception cref="ArgumentException"><paramref name="mediaId"/> is a zero-length string, contains only white space.</exception>
158         public MediaDataReader<FaceInfo> SelectFaceInfo(string mediaId)
159         {
160             return SelectFaceInfo(mediaId, null);
161         }
162
163         /// <summary>
164         /// Retrieves the face information added to or detected from the media with the <see cref="SelectArguments"/>.
165         /// </summary>
166         /// <param name="mediaId">The media ID to select the face information added to the media.</param>
167         /// <param name="arguments">The criteria to use to filter. This value can be null.</param>
168         /// <returns>The <see cref="MediaDataReader{TRecord}"/> containing the results.</returns>
169         /// <exception cref="InvalidOperationException">The <see cref="MediaDatabase"/> is disconnected.</exception>
170         /// <exception cref="ObjectDisposedException">The <see cref="MediaDatabase"/> has already been disposed of.</exception>
171         /// <exception cref="MediaDatabaseException">An error occurred while executing the command.</exception>
172         /// <exception cref="ArgumentNullException"><paramref name="mediaId"/> is null.</exception>
173         /// <exception cref="ArgumentException"><paramref name="mediaId"/> is a zero-length string, contains only white space.</exception>
174         public MediaDataReader<FaceInfo> SelectFaceInfo(string mediaId, SelectArguments arguments)
175         {
176             ValidateDatabase();
177
178             ValidationUtil.ValidateNotNullOrEmpty(mediaId, nameof(mediaId));
179
180             return CommandHelper.SelectMembers(mediaId, arguments, Interop.MediaInfo.ForeachFaces,
181                 FaceInfo.FromHandle);
182         }
183
184         /// <summary>
185         /// Retrieves the number of tags that the media has.
186         /// </summary>
187         /// <returns>The number of tags.</returns>
188         /// <param name="mediaId">The media ID to count tags added to the media.</param>
189         /// <exception cref="InvalidOperationException">The <see cref="MediaDatabase"/> is disconnected.</exception>
190         /// <exception cref="ObjectDisposedException">The <see cref="MediaDatabase"/> has already been disposed of.</exception>
191         /// <exception cref="MediaDatabaseException">An error occurred while executing the command.</exception>
192         /// <exception cref="ArgumentNullException"><paramref name="mediaId"/> is null.</exception>
193         /// <exception cref="ArgumentException"><paramref name="mediaId"/> is a zero-length string, contains only white space.</exception>
194         public int CountTag(string mediaId)
195         {
196             return CountTag(mediaId, null);
197         }
198
199         /// <summary>
200         /// Retrieves the number of tags that the media has with the <see cref="CountArguments"/>.
201         /// </summary>
202         /// <param name="mediaId">The media ID to count tags added to the media.</param>
203         /// <param name="arguments">The criteria to use to filter. This value can be null.</param>
204         /// <returns>The number of tags.</returns>
205         /// <exception cref="InvalidOperationException">The <see cref="MediaDatabase"/> is disconnected.</exception>
206         /// <exception cref="ObjectDisposedException">The <see cref="MediaDatabase"/> has already been disposed of.</exception>
207         /// <exception cref="MediaDatabaseException">An error occurred while executing the command.</exception>
208         /// <exception cref="ArgumentNullException"><paramref name="mediaId"/> is null.</exception>
209         /// <exception cref="ArgumentException"><paramref name="mediaId"/> is a zero-length string, contains only white space.</exception>
210         public int CountTag(string mediaId, CountArguments arguments)
211         {
212             ValidateDatabase();
213
214             ValidationUtil.ValidateNotNullOrEmpty(mediaId, nameof(mediaId));
215
216             return CommandHelper.Count(Interop.MediaInfo.GetTagCount, mediaId, arguments);
217         }
218
219         /// <summary>
220         /// Retrieves the tags that the media has.
221         /// </summary>
222         /// <param name="mediaId">The media ID to select tags added to the media.</param>
223         /// <returns>The <see cref="MediaDataReader{TRecord}"/> containing the results.</returns>
224         /// <exception cref="InvalidOperationException">The <see cref="MediaDatabase"/> is disconnected.</exception>
225         /// <exception cref="ObjectDisposedException">The <see cref="MediaDatabase"/> has already been disposed of.</exception>
226         /// <exception cref="MediaDatabaseException">An error occurred while executing the command.</exception>
227         /// <exception cref="ArgumentNullException"><paramref name="mediaId"/> is null.</exception>
228         /// <exception cref="ArgumentException"><paramref name="mediaId"/> is a zero-length string, contains only white space.</exception>
229         public MediaDataReader<Tag> SelectTag(string mediaId)
230         {
231             return SelectTag(mediaId, null);
232         }
233
234         /// <summary>
235         /// Retrieves the tags that the media has with the <see cref="SelectArguments"/>.
236         /// </summary>
237         /// <param name="mediaId">The media ID to select tags added to the media.</param>
238         /// <param name="filter">The criteria to use to filter. This value can be null.</param>
239         /// <returns>The <see cref="MediaDataReader{TRecord}"/> containing the results.</returns>
240         /// <exception cref="InvalidOperationException">The <see cref="MediaDatabase"/> is disconnected.</exception>
241         /// <exception cref="ObjectDisposedException">The <see cref="MediaDatabase"/> has already been disposed of.</exception>
242         /// <exception cref="MediaDatabaseException">An error occurred while executing the command.</exception>
243         /// <exception cref="ArgumentNullException"><paramref name="mediaId"/> is null.</exception>
244         /// <exception cref="ArgumentException"><paramref name="mediaId"/> is a zero-length string, contains only white space.</exception>
245         public MediaDataReader<Tag> SelectTag(string mediaId, SelectArguments filter)
246         {
247             ValidateDatabase();
248
249             ValidationUtil.ValidateNotNullOrEmpty(mediaId, nameof(mediaId));
250
251             return CommandHelper.SelectMembers(mediaId, filter, Interop.MediaInfo.ForeachTags,
252                 Tag.FromHandle);
253         }
254
255
256         /// <summary>
257         /// Retrieves the number of the media information.
258         /// </summary>
259         /// <returns>The number of the media information.</returns>
260         /// <exception cref="InvalidOperationException">The <see cref="MediaDatabase"/> is disconnected.</exception>
261         /// <exception cref="ObjectDisposedException">The <see cref="MediaDatabase"/> has already been disposed of.</exception>
262         /// <exception cref="MediaDatabaseException">An error occurred while executing the command.</exception>
263         public int CountMedia()
264         {
265             return CountMedia(null);
266         }
267
268         /// <summary>
269         /// Retrieves the number of the media information with the <see cref="SelectArguments"/>.
270         /// </summary>
271         /// <param name="arguments">The criteria to use to filter. This value can be null.</param>
272         /// <returns>The number of media information.</returns>
273         /// <exception cref="InvalidOperationException">The <see cref="MediaDatabase"/> is disconnected.</exception>
274         /// <exception cref="ObjectDisposedException">The <see cref="MediaDatabase"/> has already been disposed of.</exception>
275         /// <exception cref="MediaDatabaseException">An error occurred while executing the command.</exception>
276         public int CountMedia(CountArguments arguments)
277         {
278             ValidateDatabase();
279
280             return CommandHelper.Count(Interop.MediaInfo.GetMediaCount, arguments);
281         }
282
283         /// <summary>
284         /// Retrieves the media.
285         /// </summary>
286         /// <param name="mediaId">The media ID to retrieve.</param>
287         /// <returns>The <see cref="MediaInfo"/> instance if the matched record was found in the database, otherwise null.</returns>
288         /// <exception cref="InvalidOperationException">The <see cref="MediaDatabase"/> is disconnected.</exception>
289         /// <exception cref="ObjectDisposedException">The <see cref="MediaDatabase"/> has already been disposed of.</exception>
290         /// <exception cref="MediaDatabaseException">An error occurred while executing the command.</exception>
291         /// <exception cref="ArgumentNullException"><paramref name="mediaId"/> is null.</exception>
292         /// <exception cref="ArgumentException"><paramref name="mediaId"/> is a zero-length string, contains only white space.</exception>
293         public MediaInfo SelectMedia(string mediaId)
294         {
295             ValidateDatabase();
296
297             ValidationUtil.ValidateNotNullOrEmpty(mediaId, nameof(mediaId));
298
299             Interop.MediaInfo.GetMediaFromDB(mediaId, out var handle).Ignore(MediaContentError.InvalidParameter).
300                 ThrowIfError("Failed to query");
301
302             try
303             {
304                 return MediaInfo.FromHandle(handle);
305             }
306             finally
307             {
308                 handle.Dispose();
309             }
310         }
311
312         /// <summary>
313         /// Retrieves the number of values grouped by the specified column with the <see cref="SelectArguments"/>.
314         /// </summary>
315         /// <param name="columnKey">The column key.</param>
316         /// <returns>The number of groups.</returns>
317         /// <exception cref="InvalidOperationException">The <see cref="MediaDatabase"/> is disconnected.</exception>
318         /// <exception cref="ObjectDisposedException">The <see cref="MediaDatabase"/> has already been disposed of.</exception>
319         /// <exception cref="MediaDatabaseException">An error occurred while executing the command.</exception>
320         /// <exception cref="ArgumentException"><paramref name="columnKey"/> is invalid.</exception>
321         public int CountGroupBy(MediaInfoColumnKey columnKey)
322         {
323             return CountGroupBy(columnKey, null);
324         }
325
326         /// <summary>
327         /// Retrieves the number of values grouped by the specified column with the <see cref="SelectArguments"/>.
328         /// </summary>
329         /// <param name="columnKey">The column key.</param>
330         /// <param name="arguments">The criteria to use to filter. This value can be null.</param>
331         /// <returns>The number of groups.</returns>
332         /// <exception cref="InvalidOperationException">The <see cref="MediaDatabase"/> is disconnected.</exception>
333         /// <exception cref="ObjectDisposedException">The <see cref="MediaDatabase"/> has already been disposed of.</exception>
334         /// <exception cref="MediaDatabaseException">An error occurred while executing the command.</exception>
335         /// <exception cref="ArgumentException"><paramref name="columnKey"/> is invalid.</exception>
336         public int CountGroupBy(MediaInfoColumnKey columnKey, CountArguments arguments)
337         {
338             ValidateDatabase();
339
340             ValidationUtil.ValidateEnum(typeof(MediaInfoColumnKey), columnKey, nameof(columnKey));
341
342             using (var filter = QueryArguments.ToNativeHandle(arguments))
343             {
344                 Interop.Group.GetGroupCount(filter, columnKey, out var count).ThrowIfError("Failed to query count");
345                 return count;
346             }
347         }
348
349         /// <summary>
350         /// Retrieves the group values of the specified column.
351         /// </summary>
352         /// <param name="columnKey">The column key.</param>
353         /// <returns>The <see cref="MediaDataReader{TRecord}"/> containing the results.</returns>
354         /// <exception cref="InvalidOperationException">The <see cref="MediaDatabase"/> is disconnected.</exception>
355         /// <exception cref="ObjectDisposedException">The <see cref="MediaDatabase"/> has already been disposed of.</exception>
356         /// <exception cref="MediaDatabaseException">An error occurred while executing the command.</exception>
357         /// <exception cref="ArgumentException"><paramref name="columnKey"/> is invalid.</exception>
358         public MediaDataReader<string> SelectGroupBy(MediaInfoColumnKey columnKey)
359         {
360             return SelectGroupBy(columnKey, null);
361         }
362
363         /// <summary>
364         /// Retrieves the group values of the specified column with the <see cref="SelectArguments"/>.
365         /// </summary>
366         /// <param name="columnKey">The column key.</param>
367         /// <param name="arguments">The criteria to use to filter. This value can be null.</param>
368         /// <returns>The <see cref="MediaDataReader{TRecord}"/> containing the results.</returns>
369         /// <exception cref="InvalidOperationException">The <see cref="MediaDatabase"/> is disconnected.</exception>
370         /// <exception cref="ObjectDisposedException">The <see cref="MediaDatabase"/> has already been disposed of.</exception>
371         /// <exception cref="MediaDatabaseException">An error occurred while executing the command.</exception>
372         /// <exception cref="ArgumentException"><paramref name="columnKey"/> is invalid.</exception>
373         public MediaDataReader<string> SelectGroupBy(MediaInfoColumnKey columnKey, SelectArguments arguments)
374         {
375             ValidateDatabase();
376
377             ValidationUtil.ValidateEnum(typeof(MediaInfoColumnKey), columnKey, nameof(columnKey));
378
379             List<string> list = new List<string>();
380
381             using (var filter = QueryArguments.ToNativeHandle(arguments))
382             {
383                 Interop.Group.ForeachGroup(filter, columnKey, (name, _) =>
384                 {
385                     list.Add(name);
386
387                     return true;
388                 }).ThrowIfError("Failed to query");
389
390                 return new MediaDataReader<string>(list);
391             }
392         }
393
394         /// <summary>
395         /// Retrieves all the media.
396         /// </summary>
397         /// <returns>The <see cref="MediaDataReader{TRecord}"/> containing the results.</returns>
398         /// <exception cref="InvalidOperationException">The <see cref="MediaDatabase"/> is disconnected.</exception>
399         /// <exception cref="ObjectDisposedException">The <see cref="MediaDatabase"/> has already been disposed of.</exception>
400         /// <exception cref="MediaDatabaseException">An error occurred while executing the command.</exception>
401         public MediaDataReader<MediaInfo> SelectMedia()
402         {
403             return SelectMedia(arguments: null);
404         }
405
406         /// <summary>
407         /// Retrieves the media with the <see cref="SelectArguments"/>.
408         /// </summary>
409         /// <param name="arguments">The criteria to use to filter. This value can be null.</param>
410         /// <returns>The <see cref="MediaDataReader{TRecord}"/> containing the results.</returns>
411         /// <exception cref="InvalidOperationException">The <see cref="MediaDatabase"/> is disconnected.</exception>
412         /// <exception cref="ObjectDisposedException">The <see cref="MediaDatabase"/> has already been disposed of.</exception>
413         /// <exception cref="MediaDatabaseException">An error occurred while executing the command.</exception>
414         public MediaDataReader<MediaInfo> SelectMedia(SelectArguments arguments)
415         {
416             ValidateDatabase();
417
418             return new MediaDataReader<MediaInfo>(QueryMedia(arguments));
419         }
420
421         private static List<MediaInfo> QueryMedia(SelectArguments arguments)
422         {
423             using (var filter = QueryArguments.ToNativeHandle(arguments))
424             {
425                 List<MediaInfo> list = new List<MediaInfo>();
426
427                 Exception caught = null;
428
429                 Interop.MediaInfo.ForeachMedia(filter, (handle, _) =>
430                 {
431                     try
432                     {
433                         list.Add(MediaInfo.FromHandle(handle));
434                         return true;
435                     }
436                     catch (Exception e)
437                     {
438                         caught = e;
439                         return false;
440                     }
441                 });
442
443                 if (caught != null)
444                 {
445                     throw caught;
446                 }
447
448                 return list;
449             }
450         }
451
452         /// <summary>
453         /// Deletes the media from the database.
454         /// </summary>
455         /// <privilege>http://tizen.org/privilege/content.write</privilege>
456         /// <param name="mediaId">The media ID to delete.</param>
457         /// <returns>true if the matched record was found and deleted, otherwise false.</returns>
458         /// <remarks>The <see cref="MediaDatabase.ScanFile(string)"/> or the <see cref="MediaDatabase.ScanFolderAsync(string)"/> can be used instead.</remarks>
459         /// <exception cref="InvalidOperationException">The <see cref="MediaDatabase"/> is disconnected.</exception>
460         /// <exception cref="ObjectDisposedException">The <see cref="MediaDatabase"/> has already been disposed of.</exception>
461         /// <exception cref="MediaDatabaseException">An error occurred while executing the command.</exception>
462         /// <exception cref="ArgumentNullException"><paramref name="mediaId"/> is null.</exception>
463         /// <exception cref="ArgumentException"><paramref name="mediaId"/> is a zero-length string, contains only white space.</exception>
464         /// <exception cref="UnauthorizedAccessException">The caller has no required privilege.</exception>
465         public bool Delete(string mediaId)
466         {
467             ValidateDatabase();
468
469             ValidationUtil.ValidateNotNullOrEmpty(mediaId, nameof(mediaId));
470
471             if (CommandHelper.Count(
472                 Interop.MediaInfo.GetMediaCount, $"{MediaInfoColumns.Id}='{mediaId}'") == 0)
473             {
474                 return false;
475             }
476
477             CommandHelper.Delete(Interop.MediaInfo.Delete, mediaId);
478             return true;
479         }
480
481         /// <summary>
482         /// Adds the media to the database.
483         /// </summary>
484         /// <param name="path">The file path to add.</param>
485         /// <returns>The <see cref="MediaInfo"/> instance that contains the record information in the database.</returns>
486         /// <remarks>
487         ///     If the media already exists in the database, it returns the existing information.\n
488         ///     \n
489         ///     The <see cref="MediaDatabase.ScanFile(string)"/> or the <see cref="MediaDatabase.ScanFolderAsync(string)"/> can be used instead.\n
490         ///     \n
491         ///     If you want to access internal storage, you should add privilege http://tizen.org/privilege/mediastorage.\n
492         ///     If you want to access external storage, you should add privilege http://tizen.org/privilege/externalstorage.
493         /// </remarks>
494         /// <privilege>http://tizen.org/privilege/content.write</privilege>
495         /// <privilege>http://tizen.org/privilege/mediastorage</privilege>
496         /// <privilege>http://tizen.org/privilege/externalstorage</privilege>
497         /// <exception cref="InvalidOperationException">The <see cref="MediaDatabase"/> is disconnected.</exception>
498         /// <exception cref="ObjectDisposedException">The <see cref="MediaDatabase"/> has already been disposed of.</exception>
499         /// <exception cref="MediaDatabaseException">An error occurred while executing the command.</exception>
500         /// <exception cref="ArgumentNullException"><paramref name="path"/> is null.</exception>
501         /// <exception cref="ArgumentException">
502         ///     <paramref name="path"/> is a zero-length string, contains only white space.\n
503         ///     -or-\n
504         ///     <paramref name="path"/> contains a hidden path that starts with '.'.\n
505         ///     -or-\n
506         ///     <paramref name="path"/> contains a directory containing the ".scan_ignore" file.
507         /// </exception>
508         /// <exception cref="FileNotFoundException"><paramref name="path"/> does not exists.</exception>
509         /// <exception cref="UnauthorizedAccessException">The caller has no required privilege.</exception>
510         public MediaInfo Add(string path)
511         {
512             ValidateDatabase();
513
514             ValidationUtil.ValidateNotNullOrEmpty(path, nameof(path));
515
516             if (File.Exists(path) == false)
517             {
518                 throw new FileNotFoundException("destination is not valid path.", path);
519             }
520
521             if (File.GetAttributes(path).HasFlag(FileAttributes.Hidden))
522             {
523                 throw new ArgumentException($"{nameof(path)} contains a hidden path.", nameof(path));
524             }
525
526             Interop.MediaInfoHandle handle = null;
527
528             try
529             {
530                 Interop.MediaInfo.Insert(path, out handle).ThrowIfError("Failed to insert");
531
532                 return MediaInfo.FromHandle(handle);
533             }
534             finally
535             {
536                 if (handle != null)
537                 {
538                     handle.Dispose();
539                 }
540             }
541         }
542
543         private static void ValidatePaths(IEnumerable<string> paths)
544         {
545             if (paths == null)
546             {
547                 throw new ArgumentNullException(nameof(paths));
548             }
549
550             if (paths.Count() > 300)
551             {
552                 throw new ArgumentException("Too many paths to add.");
553             }
554
555             foreach (var path in paths)
556             {
557                 if (path == null)
558                 {
559                     throw new ArgumentException($"{nameof(paths)} contains null.", nameof(paths));
560                 }
561
562                 if (File.Exists(path) == false)
563                 {
564                     throw new FileNotFoundException($"{nameof(paths)} contains a path that does not exist. Path={path}.", path);
565                 }
566             }
567
568         }
569
570         /// <summary>
571         /// Adds media files into the media database.
572         /// </summary>
573         /// <remarks>
574         ///     The paths that already exist in the database will be ignored.\n
575         ///     At most 300 items can be added at once.\n
576         ///     \n
577         ///     If you want to access internal storage, you should add privilege http://tizen.org/privilege/mediastorage.\n
578         ///     If you want to access external storage, you should add privilege http://tizen.org/privilege/externalstorage.
579         /// </remarks>
580         /// <privilege>http://tizen.org/privilege/content.write</privilege>
581         /// <privilege>http://tizen.org/privilege/mediastorage</privilege>
582         /// <privilege>http://tizen.org/privilege/externalstorage</privilege>
583         /// <param name="paths">The paths of the media files to add.</param>
584         /// <returns>A task that represents the asynchronous add operation.</returns>
585         /// <exception cref="InvalidOperationException">The <see cref="MediaDatabase"/> is disconnected.</exception>
586         /// <exception cref="ObjectDisposedException">The <see cref="MediaDatabase"/> has already been disposed of.</exception>
587         /// <exception cref="MediaDatabaseException">An error occurred while executing the command.</exception>
588         /// <exception cref="ArgumentNullException"><paramref name="paths"/> is null.</exception>
589         /// <exception cref="ArgumentException">
590         ///     <paramref name="paths"/> contains null.\n
591         ///     -or-\n
592         ///     <paramref name="paths"/> contains the invalid path.\n
593         ///     -or-\n
594         ///     The number of <paramref name="paths"/> is 300 or more items.
595         /// </exception>
596         /// <exception cref="FileNotFoundException"><paramref name="paths"/> contains a path that does not exist.</exception>
597         /// <exception cref="UnauthorizedAccessException">The caller has no required privilege.</exception>
598         public async Task AddAsync(IEnumerable<string> paths)
599         {
600             ValidateDatabase();
601
602             ValidatePaths(paths);
603
604             var pathArray = paths.ToArray();
605             var tcs = new TaskCompletionSource<bool>();
606
607             Interop.MediaInfo.InsertCompletedCallback callback = (error, _) =>
608             {
609                 if (error == MediaContentError.None)
610                 {
611                     tcs.TrySetResult(true);
612                 }
613                 else
614                 {
615                     tcs.TrySetException(error.AsException("Failed to add"));
616                 }
617             };
618
619             using (ObjectKeeper.Get(callback))
620             {
621                 Interop.MediaInfo.BatchInsert(pathArray, pathArray.Length, callback).ThrowIfError("Failed to add");
622
623                 await tcs.Task;
624             }
625         }
626
627         /// <summary>
628         /// Adds burst shot images into the media database.
629         /// </summary>
630         /// <param name="paths">The paths of the burst shot images to add.</param>
631         /// <returns>A task that represents the asynchronous add operation.</returns>
632         /// <remarks>
633         ///     The paths that already exist in the database.\n
634         ///     At most 300 items can be added at once.
635         ///     \n
636         ///     If you want to access internal storage, you should add privilege http://tizen.org/privilege/mediastorage.\n
637         ///     If you want to access external storage, you should add privilege http://tizen.org/privilege/externalstorage.
638         /// </remarks>
639         /// <privilege>http://tizen.org/privilege/content.write</privilege>
640         /// <privilege>http://tizen.org/privilege/mediastorage</privilege>
641         /// <privilege>http://tizen.org/privilege/externalstorage</privilege>
642         /// <exception cref="InvalidOperationException">The <see cref="MediaDatabase"/> is disconnected.</exception>
643         /// <exception cref="ObjectDisposedException">The <see cref="MediaDatabase"/> has already been disposed of.</exception>
644         /// <exception cref="MediaDatabaseException">An error occurred while executing the command.</exception>
645         /// <exception cref="ArgumentNullException"><paramref name="paths"/> is null.</exception>
646         /// <exception cref="ArgumentException">
647         ///     <paramref name="paths"/> contains null.\n
648         ///     -or-\n
649         ///     <paramref name="paths"/> contains the invalid path.\n
650         ///     -or-\n
651         ///     The number of <paramref name="paths"/> is 300 or more items.
652         /// </exception>
653         /// <exception cref="FileNotFoundException"><paramref name="paths"/> contains a path that does not exist.</exception>
654         /// <exception cref="UnauthorizedAccessException">The caller has no required privilege.</exception>
655         public async Task AddBurstShotImagesAsync(IEnumerable<string> paths)
656         {
657             ValidateDatabase();
658
659             ValidatePaths(paths);
660
661             var tcs = new TaskCompletionSource<bool>();
662             string[] pathArray = paths.ToArray();
663
664             Interop.MediaInfo.InsertBurstShotCompletedCallback callback = (error, _) =>
665             {
666                 if (error == MediaContentError.None)
667                 {
668                     tcs.TrySetResult(true);
669                 }
670                 else
671                 {
672                     tcs.TrySetException(error.AsException("Failed to add burst shot images"));
673                 }
674             };
675
676             using (ObjectKeeper.Get(callback))
677             {
678                 Interop.MediaInfo.BurstShotInsert(pathArray, pathArray.Length, callback).
679                     ThrowIfError("Failed to add burst shots to db");
680
681                 await tcs.Task;
682             }
683         }
684
685         private static void SetUpdateValue<T>(Interop.MediaInfoHandle handle, T value,
686             Func<Interop.MediaInfoHandle, T, MediaContentError> func)
687         {
688             if (value != null)
689             {
690                 func(handle, value).ThrowIfError("Failed to update");
691             }
692         }
693
694
695         private static void SetUpdateValue<T>(Interop.MediaInfoHandle handle, Nullable<T> value,
696             Func<Interop.MediaInfoHandle, T, MediaContentError> func) where T : struct
697         {
698             if (value.HasValue)
699             {
700                 func(handle, value.Value).ThrowIfError("Failed to update");
701             }
702         }
703
704         /// <summary>
705         /// Updates the media with the specified values.
706         /// </summary>
707         /// <privilege>http://tizen.org/privilege/content.write</privilege>
708         /// <param name="mediaId">The media ID to update.</param>
709         /// <param name="values">The values for update.</param>
710         /// <returns>true if the matched record was found and updated, otherwise false.</returns>
711         /// <remarks>Only values set in the <see cref="MediaInfoUpdateValues"/> are updated.</remarks>
712         /// <exception cref="InvalidOperationException">The <see cref="MediaDatabase"/> is disconnected.</exception>
713         /// <exception cref="ObjectDisposedException">The <see cref="MediaDatabase"/> has already been disposed of.</exception>
714         /// <exception cref="MediaDatabaseException">An error occurred while executing the command.</exception>
715         /// <exception cref="ArgumentNullException">
716         ///     <paramref name="mediaId"/> is null.\n
717         ///     -or-\n
718         ///     <paramref name="values"/> is null.
719         /// </exception>
720         /// <exception cref="ArgumentException"><paramref name="mediaId"/> is a zero-length string, contains only white space.</exception>
721         /// <exception cref="UnauthorizedAccessException">The caller has no required privilege.</exception>
722         public bool Update(string mediaId, MediaInfoUpdateValues values)
723         {
724             ValidateDatabase();
725
726             ValidationUtil.ValidateNotNullOrEmpty(mediaId, nameof(mediaId));
727
728             if (values == null)
729             {
730                 throw new ArgumentNullException(nameof(values));
731             }
732
733             if (CommandHelper.Count(
734                 Interop.MediaInfo.GetMediaCount, $"{MediaInfoColumns.Id}='{mediaId}'") == 0)
735             {
736                 return false;
737             }
738
739             Interop.MediaInfo.GetMediaFromDB(mediaId, out var handle).ThrowIfError("Failed to update");
740
741             if (handle.IsInvalid)
742             {
743                 return false;
744             }
745
746             try
747             {
748                 SetUpdateValue(handle, values.Weather, Interop.MediaInfo.SetWeather);
749                 SetUpdateValue(handle, values.IsFavorite, Interop.MediaInfo.SetFavorite);
750                 SetUpdateValue(handle, values.Provider, Interop.MediaInfo.SetProvider);
751                 SetUpdateValue(handle, values.Category, Interop.MediaInfo.SetCategory);
752                 SetUpdateValue(handle, values.LocationTag, Interop.MediaInfo.SetLocationTag);
753                 SetUpdateValue(handle, values.AgeRating, Interop.MediaInfo.SetAgeRating);
754
755                 Interop.MediaInfo.UpdateToDB(handle).ThrowIfError("Failed to update");
756                 return true;
757             }
758             finally
759             {
760                 handle.Dispose();
761             }
762         }
763
764         /// <summary>
765         /// Updates the path of the media to the specified destination path in the database.
766         /// </summary>
767         /// <privilege>http://tizen.org/privilege/content.write</privilege>
768         /// <privilege>http://tizen.org/privilege/mediastorage</privilege>
769         /// <privilege>http://tizen.org/privilege/externalstorage</privilege>
770         /// <param name="mediaId">The media ID to move.</param>
771         /// <param name="newPath">The path that the media has been moved to.</param>
772         /// <returns>true if the matched record was found and updated, otherwise false.</returns>
773         /// <remarks>
774         ///     Usually, it is used after the media file is moved to the another path.\n
775         ///     \n
776         ///     If you want to access internal storage, you should add privilege http://tizen.org/privilege/mediastorage.\n
777         ///     If you want to access external storage, you should add privilege http://tizen.org/privilege/externalstorage.
778         /// </remarks>
779         /// <exception cref="InvalidOperationException">The <see cref="MediaDatabase"/> is disconnected.</exception>
780         /// <exception cref="ObjectDisposedException">The <see cref="MediaDatabase"/> has already been disposed of.</exception>
781         /// <exception cref="MediaDatabaseException">An error occurred while executing the command.</exception>
782         /// <exception cref="ArgumentNullException">
783         ///     <paramref name="mediaId"/> is null.\n
784         ///     -or-\n
785         ///     <paramref name="newPath"/> is null.
786         /// </exception>
787         /// <exception cref="ArgumentException">
788         ///     <paramref name="mediaId"/> is a zero-length string, contains only white space.\n
789         ///     -or-\n
790         ///     <paramref name="newPath"/> is a zero-length string, contains only white space.\n
791         ///     -or-\n
792         ///     <paramref name="newPath"/> contains a hidden directory that starts with '.'.\n
793         ///     -or-\n
794         ///     <paramref name="newPath"/> contains a directory containing the ".scan_ignore" file.
795         /// </exception>
796         /// <exception cref="FileNotFoundException"><paramref name="newPath"/> does not exists.</exception>
797         /// <exception cref="UnauthorizedAccessException">The caller has no required privilege.</exception>
798         public bool Move(string mediaId, string newPath)
799         {
800             ValidateDatabase();
801
802             ValidationUtil.ValidateNotNullOrEmpty(mediaId, nameof(mediaId));
803
804             ValidationUtil.ValidateNotNullOrEmpty(newPath, nameof(newPath));
805
806             if (File.Exists(newPath) == false)
807             {
808                 throw new FileNotFoundException("destination is not valid path.", newPath);
809             }
810
811             if (File.GetAttributes(newPath).HasFlag(FileAttributes.Hidden))
812             {
813                 throw new ArgumentException($"{nameof(newPath)} contains a hidden path.", nameof(newPath));
814             }
815
816             //TODO can be improved if MoveToDB supports result value.
817             Interop.MediaInfo.GetMediaFromDB(mediaId, out var handle).
818                 Ignore(MediaContentError.InvalidParameter).ThrowIfError("Failed to move");
819
820             if (handle.IsInvalid)
821             {
822                 return false;
823             }
824
825             try
826             {
827                 Interop.MediaInfo.MoveToDB(handle, newPath).ThrowIfError("Failed to move");
828             }
829             finally
830             {
831                 handle.Dispose();
832             }
833
834             return true;
835         }
836
837         #region CreateThumbnailAsync
838         /// <summary>
839         /// Creates the thumbnail image for the given media.
840         /// If the thumbnail already exists for the given media, the existing path will be returned.
841         /// </summary>
842         /// <privilege>http://tizen.org/privilege/content.write</privilege>
843         /// <param name="mediaId">The media ID to create the thumbnail.</param>
844         /// <returns>A task that represents the asynchronous operation. The task result contains the thumbnail path.</returns>
845         /// <exception cref="InvalidOperationException">
846         ///     The <see cref="MediaDatabase"/> is disconnected.\n
847         ///     -or-\n
848         ///     An internal error occurred while executing.
849         /// </exception>
850         /// <exception cref="ObjectDisposedException">The <see cref="MediaDatabase"/> has already been disposed of.</exception>
851         /// <exception cref="MediaDatabaseException">An error occurred while executing the command.</exception>
852         /// <exception cref="ArgumentNullException"><paramref name="mediaId"/> is null.</exception>
853         /// <exception cref="RecordNotFoundException"><paramref name="mediaId"/> does not exist in the database.</exception>
854         /// <exception cref="ArgumentException">
855         ///     <paramref name="mediaId"/> is a zero-length string, contains only white space.
856         /// </exception>
857         /// <exception cref="FileNotFoundException">The file of the media does not exists; moved or deleted.</exception>
858         /// <exception cref="UnsupportedContentException">
859         ///     The thumbnail is not available for the given media.\n
860         ///     -or-\n
861         ///     The media is in the external USB storage (<see cref="MediaInfo.StorageType"/> is <see cref="StorageType.ExternalUsb"/>).
862         /// </exception>
863         public Task<string> CreateThumbnailAsync(string mediaId)
864         {
865             return CreateThumbnailAsync(mediaId, CancellationToken.None);
866         }
867
868         /// <summary>
869         /// Creates the thumbnail image for the given media.
870         /// If the thumbnail already exists for the given media, the existing path will be returned.
871         /// </summary>
872         /// <privilege>http://tizen.org/privilege/content.write</privilege>
873         /// <param name="mediaId">The media ID to create the thumbnail.</param>
874         /// <param name="cancellationToken">The token to cancel the operation.</param>
875         /// <returns>A task that represents the asynchronous operation. The task result contains the thumbnail path.</returns>
876         /// <exception cref="InvalidOperationException">
877         ///     The <see cref="MediaDatabase"/> is disconnected.\n
878         ///     -or-\n
879         ///     An internal error occurred while executing.
880         /// </exception>
881         /// <exception cref="ObjectDisposedException">The <see cref="MediaDatabase"/> has already been disposed of.</exception>
882         /// <exception cref="MediaDatabaseException">An error occurred while executing the command.</exception>
883         /// <exception cref="ArgumentNullException"><paramref name="mediaId"/> is null.</exception>
884         /// <exception cref="RecordNotFoundException"><paramref name="mediaId"/> does not exist in the database.</exception>
885         /// <exception cref="ArgumentException">
886         ///     <paramref name="mediaId"/> is a zero-length string, contains only white space.
887         /// </exception>
888         /// <exception cref="FileNotFoundException">The file of the media does not exists; moved or deleted.</exception>
889         /// <exception cref="UnsupportedContentException">
890         ///     The thumbnail is not available for the given media.\n
891         ///     -or-\n
892         ///     The media is in the external USB storage (<see cref="MediaInfo.StorageType"/> is <see cref="StorageType.ExternalUsb"/>).
893         /// </exception>
894         public Task<string> CreateThumbnailAsync(string mediaId, CancellationToken cancellationToken)
895         {
896             ValidateDatabase();
897
898             return cancellationToken.IsCancellationRequested ? Task.FromCanceled<string>(cancellationToken) :
899                 CreateThumbnailAsyncCore(mediaId, cancellationToken);
900         }
901
902         private async Task<string> CreateThumbnailAsyncCore(string mediaId, CancellationToken cancellationToken)
903         {
904             ValidationUtil.ValidateNotNullOrEmpty(mediaId, nameof(mediaId));
905
906             var tcs = new TaskCompletionSource<string>();
907
908             Interop.MediaInfo.GetMediaFromDB(mediaId, out var handle).ThrowIfError("Failed to create thumbnail");
909
910             if (handle.IsInvalid)
911             {
912                 throw new RecordNotFoundException("Media does not exist.");
913             }
914
915             using (handle)
916             {
917                 if (InteropHelper.GetValue<StorageType>(handle, Interop.MediaInfo.GetStorageType) == StorageType.ExternalUsb)
918                 {
919                     throw new UnsupportedContentException("The media is in external usb storage.");
920                 }
921
922                 var path = InteropHelper.GetString(handle, Interop.MediaInfo.GetFilePath);
923
924                 if (File.Exists(path) == false)
925                 {
926                     throw new FileNotFoundException($"The media file does not exist. Path={path}.", path);
927                 }
928
929                 using (RegisterCancelThumbnail(cancellationToken, tcs, handle))
930                 using (var cbKeeper = ObjectKeeper.Get(GetCreateThumbnailCallback(tcs)))
931                 {
932                     Interop.MediaInfo.CreateThumbnail(handle, cbKeeper.Target).ThrowIfError("Failed to create thumbnail");
933
934                     return await tcs.Task;
935                 }
936             }
937         }
938
939         private static Interop.MediaInfo.ThumbnailCompletedCallback GetCreateThumbnailCallback(
940             TaskCompletionSource<string> tcs)
941         {
942             return (error, path, _) =>
943             {
944                 if (error != MediaContentError.None)
945                 {
946                     tcs.TrySetException(error.AsException("Failed to create thumbnail"));
947                 }
948                 else
949                 {
950                     tcs.TrySetResult(path);
951                 }
952             };
953         }
954
955         private static IDisposable RegisterCancelThumbnail(CancellationToken cancellationToken,
956             TaskCompletionSource<string> tcs, Interop.MediaInfoHandle handle)
957         {
958             if (cancellationToken.CanBeCanceled == false)
959             {
960                 return null;
961             }
962
963             return cancellationToken.Register(() =>
964             {
965                 if (tcs.Task.IsCompleted)
966                 {
967                     return;
968                 }
969
970                 Interop.MediaInfo.CancelThumbnail(handle).ThrowIfError("Failed to cancel");
971                 tcs.TrySetCanceled();
972             });
973         }
974         #endregion
975
976         #region DetectFaceAsync
977         /// <summary>
978         /// Detects faces from the given media.
979         /// If the thumbnail already exists for the given media, the existing path will be returned.
980         /// </summary>
981         /// <privilege>http://tizen.org/privilege/content.write</privilege>
982         /// <feature>http://tizen.org/feature/vision.face_recognition</feature>
983         /// <param name="mediaId">The media ID to create the thumbnail.</param>
984         /// <returns>A task that represents the asynchronous add operation. The task result contains the number of faces detected.</returns>
985         /// <exception cref="InvalidOperationException">
986         ///     The <see cref="MediaDatabase"/> is disconnected.\n
987         ///     -or-\n
988         ///     An internal error occurred while executing.
989         /// </exception>
990         /// <exception cref="ObjectDisposedException">The <see cref="MediaDatabase"/> has already been disposed of.</exception>
991         /// <exception cref="MediaDatabaseException">An error occurred while executing the command.</exception>
992         /// <exception cref="ArgumentNullException"><paramref name="mediaId"/> is null.</exception>
993         /// <exception cref="RecordNotFoundException"><paramref name="mediaId"/> does not exist in the database.</exception>
994         /// <exception cref="ArgumentException">
995         ///     <paramref name="mediaId"/> is a zero-length string, contains only white space.
996         /// </exception>
997         /// <exception cref="FileNotFoundException">The file of the media does not exists; moved or deleted.</exception>
998         /// <exception cref="UnsupportedContentException">Face detection is not available for the given media.</exception>
999         /// <exception cref="NotSupportedException">The required feature is not supported.</exception>
1000         /// <exception cref="UnauthorizedAccessException">The caller has no required privilege.</exception>
1001         public Task<int> DetectFaceAsync(string mediaId)
1002         {
1003             return DetectFaceAsync(mediaId, CancellationToken.None);
1004         }
1005
1006         /// <summary>
1007         /// Creates the thumbnail image for the given media.
1008         /// If the thumbnail already exists for the given media, the existing path will be returned.
1009         /// </summary>
1010         /// <remarks>
1011         ///     Media in the external storage is not supported, with the exception of MMC.
1012         /// </remarks>
1013         /// <privilege>http://tizen.org/privilege/content.write</privilege>
1014         /// <feature>http://tizen.org/feature/vision.face_recognition</feature>
1015         /// <param name="mediaId">The media ID to create the thumbnail.</param>
1016         /// <param name="cancellationToken">The token to cancel the operation.</param>
1017         /// <returns>A task that represents the asynchronous operation. The task result contains the number of faces detected.</returns>
1018         /// <exception cref="InvalidOperationException">
1019         ///     The <see cref="MediaDatabase"/> is disconnected.\n
1020         ///     -or-\n
1021         ///     An internal error occurred while executing.
1022         /// </exception>
1023         /// <exception cref="ObjectDisposedException">The <see cref="MediaDatabase"/> has already been disposed of.</exception>
1024         /// <exception cref="MediaDatabaseException">An error occurred while executing the command.</exception>
1025         /// <exception cref="ArgumentNullException"><paramref name="mediaId"/> is null.</exception>
1026         /// <exception cref="RecordNotFoundException"><paramref name="mediaId"/> does not exist in the database.</exception>
1027         /// <exception cref="ArgumentException">
1028         ///     <paramref name="mediaId"/> is a zero-length string, contains only white space.
1029         /// </exception>
1030         /// <exception cref="FileNotFoundException">The file of the media does not exists; moved or deleted.</exception>
1031         /// <exception cref="UnsupportedContentException">
1032         ///     Face detection is not available for the given media.\n
1033         ///     -or-\n
1034         ///     The media is in the external USB storage (<see cref="MediaInfo.StorageType"/> is <see cref="StorageType.ExternalUsb"/>).
1035         /// </exception>
1036         /// <exception cref="NotSupportedException">The required feature is not supported.</exception>
1037         public Task<int> DetectFaceAsync(string mediaId, CancellationToken cancellationToken)
1038         {
1039             if (Features.IsSupported(Features.FaceRecognition) == false)
1040             {
1041                 throw new NotSupportedException($"The feature({Features.FaceRecognition}) is not supported.");
1042             }
1043
1044             ValidateDatabase();
1045
1046             return cancellationToken.IsCancellationRequested ? Task.FromCanceled<int>(cancellationToken) :
1047                 DetectFaceAsyncCore(mediaId, cancellationToken);
1048         }
1049
1050         private static async Task<int> DetectFaceAsyncCore(string mediaId, CancellationToken cancellationToken)
1051         {
1052             ValidationUtil.ValidateNotNullOrEmpty(mediaId, nameof(mediaId));
1053
1054             var tcs = new TaskCompletionSource<int>();
1055
1056             Interop.MediaInfo.GetMediaFromDB(mediaId, out var handle).ThrowIfError("Failed to detect faces");
1057
1058             if (handle.IsInvalid)
1059             {
1060                 throw new RecordNotFoundException("Media does not exist.");
1061             }
1062
1063             using (handle)
1064             {
1065                 if (InteropHelper.GetValue<StorageType>(handle, Interop.MediaInfo.GetStorageType) == StorageType.ExternalUsb)
1066                 {
1067                     throw new UnsupportedContentException("The media is in external usb storage.");
1068                 }
1069
1070                 if (InteropHelper.GetValue<MediaType>(handle, Interop.MediaInfo.GetMediaType) != MediaType.Image)
1071                 {
1072                     throw new UnsupportedContentException("Only image is supported.");
1073                 }
1074
1075                 var path = InteropHelper.GetString(handle, Interop.MediaInfo.GetFilePath);
1076
1077                 if (File.Exists(path) == false)
1078                 {
1079                     throw new FileNotFoundException($"The media file does not exist. Path={path}.", path);
1080                 }
1081
1082                 using (RegisterCancelFaceDetection(cancellationToken, tcs, handle))
1083                 using (var cbKeeper = ObjectKeeper.Get(GetFaceDetectionCallback(tcs)))
1084                 {
1085                     Interop.MediaInfo.StartFaceDetection(handle, cbKeeper.Target).ThrowIfError("Failed to detect faces");
1086
1087                     return await tcs.Task;
1088                 }
1089             }
1090         }
1091
1092         private static Interop.MediaInfo.FaceDetectionCompletedCallback GetFaceDetectionCallback(
1093             TaskCompletionSource<int> tcs)
1094         {
1095             return (error, count, _) =>
1096             {
1097                 if (error != MediaContentError.None)
1098                 {
1099                     tcs.TrySetException(error.AsException("Failed to detect faces"));
1100                 }
1101                 else
1102                 {
1103                     tcs.TrySetResult(count);
1104                 }
1105             };
1106         }
1107
1108         private static IDisposable RegisterCancelFaceDetection(CancellationToken cancellationToken,
1109             TaskCompletionSource<int> tcs, Interop.MediaInfoHandle handle)
1110         {
1111             if (cancellationToken.CanBeCanceled == false)
1112             {
1113                 return null;
1114             }
1115
1116             return cancellationToken.Register(() =>
1117             {
1118                 if (tcs.Task.IsCompleted)
1119                 {
1120                     return;
1121                 }
1122
1123                 Interop.MediaInfo.CancelFaceDetection(handle).ThrowIfError("Failed to cancel");
1124                 tcs.TrySetCanceled();
1125             });
1126         }
1127         #endregion
1128     }
1129 }