Merge "[Multimedia] Added InternalsVisibleTo attribute for Radio."
[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.<br/>
486         ///     <br/>
487         ///     The <see cref="MediaDatabase.ScanFile(string)"/> or the <see cref="MediaDatabase.ScanFolderAsync(string)"/> can be used instead.<br/>
488         ///     <br/>
489         ///     If you want to access internal storage, you should add privilege http://tizen.org/privilege/mediastorage.<br/>
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.<br/>
501         ///     -or-<br/>
502         ///     <paramref name="path"/> contains a hidden path that starts with '.'.<br/>
503         ///     -or-<br/>
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.<br/>
573         ///     At most 300 items can be added at once.<br/>
574         ///     <br/>
575         ///     If you want to access internal storage, you should add privilege http://tizen.org/privilege/mediastorage.<br/>
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.<br/>
589         ///     -or-<br/>
590         ///     <paramref name="paths"/> contains the invalid path.<br/>
591         ///     -or-<br/>
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>(TaskCreationOptions.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         /// <summary>
626         /// Updates the media with the favorite value.
627         /// </summary>
628         /// <privilege>http://tizen.org/privilege/content.write</privilege>
629         /// <param name="mediaId">The media ID to update.</param>
630         /// <param name="value">The value indicating whether the media is favorite.</param>
631         /// <returns>true if the matched record was found and updated, otherwise false.</returns>
632         /// <exception cref="InvalidOperationException">The <see cref="MediaDatabase"/> is disconnected.</exception>
633         /// <exception cref="ObjectDisposedException">The <see cref="MediaDatabase"/> has already been disposed of.</exception>
634         /// <exception cref="MediaDatabaseException">An error occurred while executing the command.</exception>
635         /// <exception cref="ArgumentNullException"><paramref name="mediaId"/> is null.</exception>
636         /// <exception cref="ArgumentException"><paramref name="mediaId"/> is a zero-length string, contains only white space.</exception>
637         /// <exception cref="UnauthorizedAccessException">The caller has no required privilege.</exception>
638         public bool UpdateFavorite(string mediaId, bool value)
639         {
640             ValidateDatabase();
641
642             ValidationUtil.ValidateNotNullOrEmpty(mediaId, nameof(mediaId));
643
644             if (CommandHelper.Count(
645                 Interop.MediaInfo.GetMediaCount, $"{MediaInfoColumns.Id}='{mediaId}'") == 0)
646             {
647                 return false;
648             }
649
650             Interop.MediaInfo.GetMediaFromDB(mediaId, out var handle).ThrowIfError("Failed to update");
651
652             if (handle.IsInvalid)
653             {
654                 return false;
655             }
656
657             try
658             {
659                 Interop.MediaInfo.SetFavorite(handle, value).ThrowIfError("Failed to update");
660
661                 Interop.MediaInfo.UpdateToDB(handle).ThrowIfError("Failed to update");
662                 return true;
663             }
664             finally
665             {
666                 handle.Dispose();
667             }
668         }
669
670         /// <summary>
671         /// Updates the path of the media to the specified destination path in the database.
672         /// </summary>
673         /// <privilege>http://tizen.org/privilege/content.write</privilege>
674         /// <privilege>http://tizen.org/privilege/mediastorage</privilege>
675         /// <privilege>http://tizen.org/privilege/externalstorage</privilege>
676         /// <param name="mediaId">The media ID to move.</param>
677         /// <param name="newPath">The path that the media has been moved to.</param>
678         /// <returns>true if the matched record was found and updated, otherwise false.</returns>
679         /// <remarks>
680         ///     Usually, it is used after the media file is moved to the another path.<br/>
681         ///     <br/>
682         ///     If you want to access internal storage, you should add privilege http://tizen.org/privilege/mediastorage.<br/>
683         ///     If you want to access external storage, you should add privilege http://tizen.org/privilege/externalstorage.
684         /// </remarks>
685         /// <exception cref="InvalidOperationException">The <see cref="MediaDatabase"/> is disconnected.</exception>
686         /// <exception cref="ObjectDisposedException">The <see cref="MediaDatabase"/> has already been disposed of.</exception>
687         /// <exception cref="MediaDatabaseException">An error occurred while executing the command.</exception>
688         /// <exception cref="ArgumentNullException">
689         ///     <paramref name="mediaId"/> is null.<br/>
690         ///     -or-<br/>
691         ///     <paramref name="newPath"/> is null.
692         /// </exception>
693         /// <exception cref="ArgumentException">
694         ///     <paramref name="mediaId"/> is a zero-length string, contains only white space.<br/>
695         ///     -or-<br/>
696         ///     <paramref name="newPath"/> is a zero-length string, contains only white space.<br/>
697         ///     -or-<br/>
698         ///     <paramref name="newPath"/> contains a hidden directory that starts with '.'.<br/>
699         ///     -or-<br/>
700         ///     <paramref name="newPath"/> contains a directory containing the ".scan_ignore" file.
701         /// </exception>
702         /// <exception cref="FileNotFoundException"><paramref name="newPath"/> does not exists.</exception>
703         /// <exception cref="UnauthorizedAccessException">The caller has no required privilege.</exception>
704         public bool Move(string mediaId, string newPath)
705         {
706             ValidateDatabase();
707
708             ValidationUtil.ValidateNotNullOrEmpty(mediaId, nameof(mediaId));
709
710             ValidationUtil.ValidateNotNullOrEmpty(newPath, nameof(newPath));
711
712             if (File.Exists(newPath) == false)
713             {
714                 throw new FileNotFoundException("destination is not valid path.", newPath);
715             }
716
717             if (File.GetAttributes(newPath).HasFlag(FileAttributes.Hidden))
718             {
719                 throw new ArgumentException($"{nameof(newPath)} contains a hidden path.", nameof(newPath));
720             }
721
722             //TODO can be improved if MoveToDB supports result value.
723             Interop.MediaInfo.GetMediaFromDB(mediaId, out var handle).
724                 Ignore(MediaContentError.InvalidParameter).ThrowIfError("Failed to move");
725
726             if (handle.IsInvalid)
727             {
728                 return false;
729             }
730
731             try
732             {
733                 Interop.MediaInfo.MoveToDB(handle, newPath).ThrowIfError("Failed to move");
734             }
735             finally
736             {
737                 handle.Dispose();
738             }
739
740             return true;
741         }
742
743         #region CreateThumbnailAsync
744         /// <summary>
745         /// Creates the thumbnail image for the given media.
746         /// If the thumbnail already exists for the given media, the existing path will be returned.
747         /// </summary>
748         /// <privilege>http://tizen.org/privilege/content.write</privilege>
749         /// <param name="mediaId">The media ID to create the thumbnail.</param>
750         /// <returns>A task that represents the asynchronous operation. The task result contains the thumbnail path.</returns>
751         /// <exception cref="InvalidOperationException">
752         ///     The <see cref="MediaDatabase"/> is disconnected.<br/>
753         ///     -or-<br/>
754         ///     An internal error occurred while executing.
755         /// </exception>
756         /// <exception cref="ObjectDisposedException">The <see cref="MediaDatabase"/> has already been disposed of.</exception>
757         /// <exception cref="MediaDatabaseException">An error occurred while executing the command.</exception>
758         /// <exception cref="ArgumentNullException"><paramref name="mediaId"/> is null.</exception>
759         /// <exception cref="RecordNotFoundException"><paramref name="mediaId"/> does not exist in the database.</exception>
760         /// <exception cref="ArgumentException">
761         ///     <paramref name="mediaId"/> is a zero-length string, contains only white space.
762         /// </exception>
763         /// <exception cref="FileNotFoundException">The file of the media does not exists; moved or deleted.</exception>
764         /// <exception cref="UnsupportedContentException">
765         ///     The thumbnail is not available for the given media.<br/>
766         ///     -or-<br/>
767         ///     The media is in the external USB storage (<see cref="MediaInfo.StorageType"/> is <see cref="StorageType.ExternalUsb"/>).
768         /// </exception>
769         public Task<string> CreateThumbnailAsync(string mediaId)
770         {
771             return CreateThumbnailAsync(mediaId, CancellationToken.None);
772         }
773
774         /// <summary>
775         /// Creates the thumbnail image for the given media.
776         /// If the thumbnail already exists for the given media, the existing path will be returned.
777         /// </summary>
778         /// <privilege>http://tizen.org/privilege/content.write</privilege>
779         /// <param name="mediaId">The media ID to create the thumbnail.</param>
780         /// <param name="cancellationToken">The token to cancel the operation.</param>
781         /// <returns>A task that represents the asynchronous operation. The task result contains the thumbnail path.</returns>
782         /// <exception cref="InvalidOperationException">
783         ///     The <see cref="MediaDatabase"/> is disconnected.<br/>
784         ///     -or-<br/>
785         ///     An internal error occurred while executing.
786         /// </exception>
787         /// <exception cref="ObjectDisposedException">The <see cref="MediaDatabase"/> has already been disposed of.</exception>
788         /// <exception cref="MediaDatabaseException">An error occurred while executing the command.</exception>
789         /// <exception cref="ArgumentNullException"><paramref name="mediaId"/> is null.</exception>
790         /// <exception cref="RecordNotFoundException"><paramref name="mediaId"/> does not exist in the database.</exception>
791         /// <exception cref="ArgumentException">
792         ///     <paramref name="mediaId"/> is a zero-length string, contains only white space.
793         /// </exception>
794         /// <exception cref="FileNotFoundException">The file of the media does not exists; moved or deleted.</exception>
795         /// <exception cref="UnsupportedContentException">
796         ///     The thumbnail is not available for the given media.<br/>
797         ///     -or-<br/>
798         ///     The media is in the external USB storage (<see cref="MediaInfo.StorageType"/> is <see cref="StorageType.ExternalUsb"/>).
799         /// </exception>
800         public Task<string> CreateThumbnailAsync(string mediaId, CancellationToken cancellationToken)
801         {
802             ValidateDatabase();
803
804             return cancellationToken.IsCancellationRequested ? Task.FromCanceled<string>(cancellationToken) :
805                 CreateThumbnailAsyncCore(mediaId, cancellationToken);
806         }
807
808         private async Task<string> CreateThumbnailAsyncCore(string mediaId, CancellationToken cancellationToken)
809         {
810             ValidationUtil.ValidateNotNullOrEmpty(mediaId, nameof(mediaId));
811
812             var tcs = new TaskCompletionSource<string>();
813
814             Interop.MediaInfo.GetMediaFromDB(mediaId, out var handle).ThrowIfError("Failed to create thumbnail");
815
816             if (handle.IsInvalid)
817             {
818                 throw new RecordNotFoundException("Media does not exist.");
819             }
820
821             using (handle)
822             {
823                 if (InteropHelper.GetValue<StorageType>(handle, Interop.MediaInfo.GetStorageType) == StorageType.ExternalUsb)
824                 {
825                     throw new UnsupportedContentException("The media is in external usb storage.");
826                 }
827
828                 var path = InteropHelper.GetString(handle, Interop.MediaInfo.GetFilePath);
829
830                 if (File.Exists(path) == false)
831                 {
832                     throw new FileNotFoundException($"The media file does not exist. Path={path}.", path);
833                 }
834
835                 using (RegisterCancelThumbnail(cancellationToken, tcs, handle))
836                 using (var cbKeeper = ObjectKeeper.Get(GetCreateThumbnailCallback(tcs)))
837                 {
838                     Interop.MediaInfo.CreateThumbnail(handle, cbKeeper.Target).ThrowIfError("Failed to create thumbnail");
839
840                     return await tcs.Task;
841                 }
842             }
843         }
844
845         private static Interop.MediaInfo.ThumbnailCompletedCallback GetCreateThumbnailCallback(
846             TaskCompletionSource<string> tcs)
847         {
848             return (error, path, _) =>
849             {
850                 if (error != MediaContentError.None)
851                 {
852                     tcs.TrySetException(error.AsException("Failed to create thumbnail"));
853                 }
854                 else
855                 {
856                     tcs.TrySetResult(path);
857                 }
858             };
859         }
860
861         private static IDisposable RegisterCancelThumbnail(CancellationToken cancellationToken,
862             TaskCompletionSource<string> tcs, Interop.MediaInfoHandle handle)
863         {
864             if (cancellationToken.CanBeCanceled == false)
865             {
866                 return null;
867             }
868
869             return cancellationToken.Register(() =>
870             {
871                 if (tcs.Task.IsCompleted)
872                 {
873                     return;
874                 }
875
876                 Interop.MediaInfo.CancelThumbnail(handle).ThrowIfError("Failed to cancel");
877                 tcs.TrySetCanceled();
878             });
879         }
880         #endregion
881
882         #region DetectFaceAsync
883         /// <summary>
884         /// Detects faces from the given media.
885         /// If the thumbnail already exists for the given media, the existing path will be returned.
886         /// </summary>
887         /// <privilege>http://tizen.org/privilege/content.write</privilege>
888         /// <feature>http://tizen.org/feature/vision.face_recognition</feature>
889         /// <param name="mediaId">The media ID to create the thumbnail.</param>
890         /// <returns>A task that represents the asynchronous add operation. The task result contains the number of faces detected.</returns>
891         /// <exception cref="InvalidOperationException">
892         ///     The <see cref="MediaDatabase"/> is disconnected.<br/>
893         ///     -or-<br/>
894         ///     An internal error occurred while executing.
895         /// </exception>
896         /// <exception cref="ObjectDisposedException">The <see cref="MediaDatabase"/> has already been disposed of.</exception>
897         /// <exception cref="MediaDatabaseException">An error occurred while executing the command.</exception>
898         /// <exception cref="ArgumentNullException"><paramref name="mediaId"/> is null.</exception>
899         /// <exception cref="RecordNotFoundException"><paramref name="mediaId"/> does not exist in the database.</exception>
900         /// <exception cref="ArgumentException">
901         ///     <paramref name="mediaId"/> is a zero-length string, contains only white space.
902         /// </exception>
903         /// <exception cref="FileNotFoundException">The file of the media does not exists; moved or deleted.</exception>
904         /// <exception cref="UnsupportedContentException">Face detection is not available for the given media.</exception>
905         /// <exception cref="NotSupportedException">The required feature is not supported.</exception>
906         /// <exception cref="UnauthorizedAccessException">The caller has no required privilege.</exception>
907         public Task<int> DetectFaceAsync(string mediaId)
908         {
909             return DetectFaceAsync(mediaId, CancellationToken.None);
910         }
911
912         /// <summary>
913         /// Creates the thumbnail image for the given media.
914         /// If the thumbnail already exists for the given media, the existing path will be returned.
915         /// </summary>
916         /// <remarks>
917         ///     Media in the external storage is not supported, with the exception of MMC.
918         /// </remarks>
919         /// <privilege>http://tizen.org/privilege/content.write</privilege>
920         /// <feature>http://tizen.org/feature/vision.face_recognition</feature>
921         /// <param name="mediaId">The media ID to create the thumbnail.</param>
922         /// <param name="cancellationToken">The token to cancel the operation.</param>
923         /// <returns>A task that represents the asynchronous operation. The task result contains the number of faces detected.</returns>
924         /// <exception cref="InvalidOperationException">
925         ///     The <see cref="MediaDatabase"/> is disconnected.<br/>
926         ///     -or-<br/>
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">
938         ///     Face detection is not available for the given media.<br/>
939         ///     -or-<br/>
940         ///     The media is in the external USB storage (<see cref="MediaInfo.StorageType"/> is <see cref="StorageType.ExternalUsb"/>).
941         /// </exception>
942         /// <exception cref="NotSupportedException">The required feature is not supported.</exception>
943         public Task<int> DetectFaceAsync(string mediaId, CancellationToken cancellationToken)
944         {
945             if (Features.IsSupported(Features.FaceRecognition) == false)
946             {
947                 throw new NotSupportedException($"The feature({Features.FaceRecognition}) is not supported.");
948             }
949
950             ValidateDatabase();
951
952             return cancellationToken.IsCancellationRequested ? Task.FromCanceled<int>(cancellationToken) :
953                 DetectFaceAsyncCore(mediaId, cancellationToken);
954         }
955
956         private static async Task<int> DetectFaceAsyncCore(string mediaId, CancellationToken cancellationToken)
957         {
958             ValidationUtil.ValidateNotNullOrEmpty(mediaId, nameof(mediaId));
959
960             var tcs = new TaskCompletionSource<int>();
961
962             Interop.MediaInfo.GetMediaFromDB(mediaId, out var handle).ThrowIfError("Failed to detect faces");
963
964             if (handle.IsInvalid)
965             {
966                 throw new RecordNotFoundException("Media does not exist.");
967             }
968
969             using (handle)
970             {
971                 if (InteropHelper.GetValue<StorageType>(handle, Interop.MediaInfo.GetStorageType) == StorageType.ExternalUsb)
972                 {
973                     throw new UnsupportedContentException("The media is in external usb storage.");
974                 }
975
976                 if (InteropHelper.GetValue<MediaType>(handle, Interop.MediaInfo.GetMediaType) != MediaType.Image)
977                 {
978                     throw new UnsupportedContentException("Only image is supported.");
979                 }
980
981                 var path = InteropHelper.GetString(handle, Interop.MediaInfo.GetFilePath);
982
983                 if (File.Exists(path) == false)
984                 {
985                     throw new FileNotFoundException($"The media file does not exist. Path={path}.", path);
986                 }
987
988                 using (RegisterCancelFaceDetection(cancellationToken, tcs, handle))
989                 using (var cbKeeper = ObjectKeeper.Get(GetFaceDetectionCallback(tcs)))
990                 {
991                     Interop.MediaInfo.StartFaceDetection(handle, cbKeeper.Target).ThrowIfError("Failed to detect faces");
992
993                     return await tcs.Task;
994                 }
995             }
996         }
997
998         private static Interop.MediaInfo.FaceDetectionCompletedCallback GetFaceDetectionCallback(
999             TaskCompletionSource<int> tcs)
1000         {
1001             return (error, count, _) =>
1002             {
1003                 if (error != MediaContentError.None)
1004                 {
1005                     tcs.TrySetException(error.AsException("Failed to detect faces"));
1006                 }
1007                 else
1008                 {
1009                     tcs.TrySetResult(count);
1010                 }
1011             };
1012         }
1013
1014         private static IDisposable RegisterCancelFaceDetection(CancellationToken cancellationToken,
1015             TaskCompletionSource<int> tcs, Interop.MediaInfoHandle handle)
1016         {
1017             if (cancellationToken.CanBeCanceled == false)
1018             {
1019                 return null;
1020             }
1021
1022             return cancellationToken.Register(() =>
1023             {
1024                 if (tcs.Task.IsCompleted)
1025                 {
1026                     return;
1027                 }
1028
1029                 Interop.MediaInfo.CancelFaceDetection(handle).ThrowIfError("Failed to cancel");
1030                 tcs.TrySetCanceled();
1031             });
1032         }
1033         #endregion
1034     }
1035 }