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