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