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