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