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