/*
* Copyright (c) 2016 Samsung Electronics Co., Ltd All Rights Reserved
*
* Licensed under the Apache License, Version 2.0 (the License);
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an AS IS BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
namespace Tizen.Content.MediaContent
{
///
/// Provides commands to manage the media information and query related items in the database.
///
public class MediaInfoCommand : MediaCommand
{
///
/// Initializes a new instance of the class with the specified .
///
/// The that the commands run on.
/// is null.
/// has already been disposed of.
public MediaInfoCommand(MediaDatabase database) : base(database)
{
}
///
/// Retrieves the number of the bookmarks added to the media.
///
/// The media ID to count the bookmarks added to the media.
/// The number of the bookmarks.
/// The is disconnected.
/// The has already been disposed of.
/// An error occurred while executing the command.
/// is null.
/// is a zero-length string, contains only white space.
public int CountBookmark(string mediaId)
{
return CountBookmark(mediaId, null);
}
///
/// Retrieves the number of the bookmarks added to the media with the .
///
/// The media ID to count the bookmarks added to the media.
/// The criteria to use to filter. This value can be null.
/// The number of the bookmarks.
/// The is disconnected.
/// The has already been disposed of.
/// An error occurred while executing the command.
/// is null.
/// is a zero-length string, contains only white space.
public int CountBookmark(string mediaId, CountArguments arguments)
{
ValidateDatabase();
ValidationUtil.ValidateNotNullOrEmpty(mediaId, nameof(mediaId));
return CommandHelper.Count(Interop.MediaInfo.GetBookmarkCount, mediaId, arguments);
}
///
/// Retrieves the bookmarks added to the media.
///
/// The media ID to select the bookmarks added to the media.
/// The containing the results.
/// The is disconnected.
/// The has already been disposed of.
/// An error occurred while executing the command.
/// is null.
/// is a zero-length string, contains only white space.
public MediaDataReader SelectBookmark(string mediaId)
{
return SelectBookmark(mediaId, null);
}
///
/// Retrieves the bookmarks added to the media with the .
///
/// The media ID to select the bookmarks added to the media.
/// The criteria to use to filter. This value can be null.
/// The containing the results.
/// The is disconnected.
/// The has already been disposed of.
/// An error occurred while executing the command.
/// is null.
/// is a zero-length string, contains only white space.
public MediaDataReader SelectBookmark(string mediaId, SelectArguments filter)
{
ValidateDatabase();
ValidationUtil.ValidateNotNullOrEmpty(mediaId, nameof(mediaId));
return CommandHelper.SelectMembers(mediaId, filter, Interop.MediaInfo.ForeachBookmarks,
Bookmark.FromHandle);
}
///
/// Retrieves the number of the face information added to or detected from the media.
///
/// The media ID to count face information added to the media.
/// The number of the face information.
/// The is disconnected.
/// The has already been disposed of.
/// An error occurred while executing the command.
/// is null.
/// is a zero-length string, contains only white space.
public int CountFaceInfo(string mediaId)
{
return CountFaceInfo(mediaId, null);
}
///
/// Retrieves the number of the face information added to or detected from the media with filter.
///
/// The media ID to count the face information added to the media.
/// The criteria to use to filter. This value can be null.
/// The number of the face information.
/// The is disconnected.
/// The has already been disposed of.
/// An error occurred while executing the command.
/// is null.
/// is a zero-length string, contains only white space.
public int CountFaceInfo(string mediaId, CountArguments arguments)
{
ValidateDatabase();
ValidationUtil.ValidateNotNullOrEmpty(mediaId, nameof(mediaId));
return CommandHelper.Count(Interop.MediaInfo.GetFaceCount, mediaId, arguments);
}
///
/// Retrieves the face information added to or detected from the media.
///
/// The media ID to select face information added to the media.
/// The containing the results.
/// The is disconnected.
/// The has already been disposed of.
/// An error occurred while executing the command.
/// is null.
/// is a zero-length string, contains only white space.
public MediaDataReader SelectFaceInfo(string mediaId)
{
return SelectFaceInfo(mediaId, null);
}
///
/// Retrieves the face information added to or detected from the media with the .
///
/// The media ID to select the face information added to the media.
/// The criteria to use to filter. This value can be null.
/// The containing the results.
/// The is disconnected.
/// The has already been disposed of.
/// An error occurred while executing the command.
/// is null.
/// is a zero-length string, contains only white space.
public MediaDataReader SelectFaceInfo(string mediaId, SelectArguments arguments)
{
ValidateDatabase();
ValidationUtil.ValidateNotNullOrEmpty(mediaId, nameof(mediaId));
return CommandHelper.SelectMembers(mediaId, arguments, Interop.MediaInfo.ForeachFaces,
FaceInfo.FromHandle);
}
///
/// Retrieves the number of tags that the media has.
///
/// The number of tags.
/// The media ID to count tags added to the media.
/// The is disconnected.
/// The has already been disposed of.
/// An error occurred while executing the command.
/// is null.
/// is a zero-length string, contains only white space.
public int CountTag(string mediaId)
{
return CountTag(mediaId, null);
}
///
/// Retrieves the number of tags that the media has with the .
///
/// The media ID to count tags added to the media.
/// The criteria to use to filter. This value can be null.
/// The number of tags.
/// The is disconnected.
/// The has already been disposed of.
/// An error occurred while executing the command.
/// is null.
/// is a zero-length string, contains only white space.
public int CountTag(string mediaId, CountArguments arguments)
{
ValidateDatabase();
ValidationUtil.ValidateNotNullOrEmpty(mediaId, nameof(mediaId));
return CommandHelper.Count(Interop.MediaInfo.GetTagCount, mediaId, arguments);
}
///
/// Retrieves the tags that the media has.
///
/// The media ID to select tags added to the media.
/// The containing the results.
/// The is disconnected.
/// The has already been disposed of.
/// An error occurred while executing the command.
/// is null.
/// is a zero-length string, contains only white space.
public MediaDataReader SelectTag(string mediaId)
{
return SelectTag(mediaId, null);
}
///
/// Retrieves the tags that the media has with the .
///
/// The media ID to select tags added to the media.
/// The criteria to use to filter. This value can be null.
/// The containing the results.
/// The is disconnected.
/// The has already been disposed of.
/// An error occurred while executing the command.
/// is null.
/// is a zero-length string, contains only white space.
public MediaDataReader SelectTag(string mediaId, SelectArguments filter)
{
ValidateDatabase();
ValidationUtil.ValidateNotNullOrEmpty(mediaId, nameof(mediaId));
return CommandHelper.SelectMembers(mediaId, filter, Interop.MediaInfo.ForeachTags,
Tag.FromHandle);
}
///
/// Retrieves the number of the media information.
///
/// The number of the media information.
/// The is disconnected.
/// The has already been disposed of.
/// An error occurred while executing the command.
public int CountMedia()
{
return CountMedia(null);
}
///
/// Retrieves the number of the media information with the .
///
/// The criteria to use to filter. This value can be null.
/// The number of media information.
/// The is disconnected.
/// The has already been disposed of.
/// An error occurred while executing the command.
public int CountMedia(CountArguments arguments)
{
ValidateDatabase();
return CommandHelper.Count(Interop.MediaInfo.GetMediaCount, arguments);
}
///
/// Retrieves the media.
///
/// The media ID to retrieve.
/// The instance if the matched record was found in the database, otherwise null.
/// The is disconnected.
/// The has already been disposed of.
/// An error occurred while executing the command.
/// is null.
/// is a zero-length string, contains only white space.
public MediaInfo SelectMedia(string mediaId)
{
ValidateDatabase();
ValidationUtil.ValidateNotNullOrEmpty(mediaId, nameof(mediaId));
Interop.MediaInfo.GetMediaFromDB(mediaId, out var handle).Ignore(MediaContentError.InvalidParameter).
ThrowIfError("Failed to query");
try
{
return MediaInfo.FromHandle(handle);
}
finally
{
handle.Dispose();
}
}
///
/// Retrieves the number of values grouped by the specified column with the .
///
/// The column key.
/// The number of groups.
/// The is disconnected.
/// The has already been disposed of.
/// An error occurred while executing the command.
/// is invalid.
public int CountGroupBy(MediaInfoColumnKey columnKey)
{
return CountGroupBy(columnKey, null);
}
///
/// Retrieves the number of values grouped by the specified column with the .
///
/// The column key.
/// The criteria to use to filter. This value can be null.
/// The number of groups.
/// The is disconnected.
/// The has already been disposed of.
/// An error occurred while executing the command.
/// is invalid.
public int CountGroupBy(MediaInfoColumnKey columnKey, CountArguments arguments)
{
ValidateDatabase();
ValidationUtil.ValidateEnum(typeof(MediaInfoColumnKey), columnKey, nameof(columnKey));
using (var filter = QueryArguments.ToNativeHandle(arguments))
{
Interop.Group.GetGroupCount(filter, columnKey, out var count).ThrowIfError("Failed to query count");
return count;
}
}
///
/// Retrieves the group values of the specified column.
///
/// The column key.
/// The containing the results.
/// The is disconnected.
/// The has already been disposed of.
/// An error occurred while executing the command.
/// is invalid.
public MediaDataReader SelectGroupBy(MediaInfoColumnKey columnKey)
{
return SelectGroupBy(columnKey, null);
}
///
/// Retrieves the group values of the specified column with the .
///
/// The column key.
/// The criteria to use to filter. This value can be null.
/// The containing the results.
/// The is disconnected.
/// The has already been disposed of.
/// An error occurred while executing the command.
/// is invalid.
public MediaDataReader SelectGroupBy(MediaInfoColumnKey columnKey, SelectArguments arguments)
{
ValidateDatabase();
ValidationUtil.ValidateEnum(typeof(MediaInfoColumnKey), columnKey, nameof(columnKey));
List list = new List();
using (var filter = QueryArguments.ToNativeHandle(arguments))
{
Interop.Group.ForeachGroup(filter, columnKey, (name, _) =>
{
list.Add(name);
return true;
}).ThrowIfError("Failed to query");
return new MediaDataReader(list);
}
}
///
/// Retrieves all the media.
///
/// The containing the results.
/// The is disconnected.
/// The has already been disposed of.
/// An error occurred while executing the command.
public MediaDataReader SelectMedia()
{
return SelectMedia(arguments: null);
}
///
/// Retrieves the media with the .
///
/// The criteria to use to filter. This value can be null.
/// The containing the results.
/// The is disconnected.
/// The has already been disposed of.
/// An error occurred while executing the command.
public MediaDataReader SelectMedia(SelectArguments arguments)
{
ValidateDatabase();
return new MediaDataReader(QueryMedia(arguments));
}
private static List QueryMedia(SelectArguments arguments)
{
using (var filter = QueryArguments.ToNativeHandle(arguments))
{
List list = new List();
Exception caught = null;
Interop.MediaInfo.ForeachMedia(filter, (handle, _) =>
{
try
{
list.Add(MediaInfo.FromHandle(handle));
return true;
}
catch (Exception e)
{
caught = e;
return false;
}
});
if (caught != null)
{
throw caught;
}
return list;
}
}
///
/// Deletes the media from the database.
///
/// http://tizen.org/privilege/content.write
/// The media ID to delete.
/// true if the matched record was found and deleted, otherwise false.
/// The or the can be used instead.
/// The is disconnected.
/// The has already been disposed of.
/// An error occurred while executing the command.
/// is null.
/// is a zero-length string, contains only white space.
/// The caller has no required privilege.
public bool Delete(string mediaId)
{
ValidateDatabase();
ValidationUtil.ValidateNotNullOrEmpty(mediaId, nameof(mediaId));
if (CommandHelper.Count(
Interop.MediaInfo.GetMediaCount, $"{MediaInfoColumns.Id}='{mediaId}'") == 0)
{
return false;
}
CommandHelper.Delete(Interop.MediaInfo.Delete, mediaId);
return true;
}
///
/// Adds the media to the database.
///
/// The file path to add.
/// The instance that contains the record information in the database.
///
/// If the media already exists in the database, it returns the existing information.
///
/// The or the can be used instead.
///
/// If you want to access internal storage, you should add privilege http://tizen.org/privilege/mediastorage.
/// If you want to access external storage, you should add privilege http://tizen.org/privilege/externalstorage.
///
/// http://tizen.org/privilege/content.write
/// http://tizen.org/privilege/mediastorage
/// http://tizen.org/privilege/externalstorage
/// The is disconnected.
/// The has already been disposed of.
/// An error occurred while executing the command.
/// is null.
///
/// is a zero-length string, contains only white space.
/// -or-
/// contains a hidden path that starts with '.'.
/// -or-
/// contains a directory containing the ".scan_ignore" file.
///
/// does not exists.
/// The caller has no required privilege.
public MediaInfo Add(string path)
{
ValidateDatabase();
ValidationUtil.ValidateNotNullOrEmpty(path, nameof(path));
if (File.Exists(path) == false)
{
throw new FileNotFoundException("destination is not valid path.", path);
}
if (File.GetAttributes(path).HasFlag(FileAttributes.Hidden))
{
throw new ArgumentException($"{nameof(path)} contains a hidden path.", nameof(path));
}
Interop.MediaInfoHandle handle = null;
try
{
Interop.MediaInfo.Insert(path, out handle).ThrowIfError("Failed to insert");
return MediaInfo.FromHandle(handle);
}
finally
{
if (handle != null)
{
handle.Dispose();
}
}
}
private static void ValidatePaths(IEnumerable paths)
{
if (paths == null)
{
throw new ArgumentNullException(nameof(paths));
}
if (paths.Count() > 300)
{
throw new ArgumentException("Too many paths to add.");
}
foreach (var path in paths)
{
if (path == null)
{
throw new ArgumentException($"{nameof(paths)} contains null.", nameof(paths));
}
if (File.Exists(path) == false)
{
throw new FileNotFoundException($"{nameof(paths)} contains a path that does not exist. Path={path}.", path);
}
}
}
///
/// Adds media files into the media database.
///
///
/// The paths that already exist in the database will be ignored.
/// At most 300 items can be added at once.
///
/// If you want to access internal storage, you should add privilege http://tizen.org/privilege/mediastorage.
/// If you want to access external storage, you should add privilege http://tizen.org/privilege/externalstorage.
///
/// http://tizen.org/privilege/content.write
/// http://tizen.org/privilege/mediastorage
/// http://tizen.org/privilege/externalstorage
/// The paths of the media files to add.
/// A task that represents the asynchronous add operation.
/// The is disconnected.
/// The has already been disposed of.
/// An error occurred while executing the command.
/// is null.
///
/// contains null.
/// -or-
/// contains the invalid path.
/// -or-
/// The number of is 300 or more items.
///
/// contains a path that does not exist.
/// The caller has no required privilege.
public async Task AddAsync(IEnumerable paths)
{
ValidateDatabase();
ValidatePaths(paths);
var pathArray = paths.ToArray();
var tcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
Interop.MediaInfo.InsertCompletedCallback callback = (error, _) =>
{
if (error == MediaContentError.None)
{
tcs.TrySetResult(true);
}
else
{
tcs.TrySetException(error.AsException("Failed to add"));
}
};
using (ObjectKeeper.Get(callback))
{
Interop.MediaInfo.BatchInsert(pathArray, pathArray.Length, callback).ThrowIfError("Failed to add");
await tcs.Task;
}
}
///
/// Updates the media with the favorite value.
///
/// http://tizen.org/privilege/content.write
/// The media ID to update.
/// The value indicating whether the media is favorite.
/// true if the matched record was found and updated, otherwise false.
/// The is disconnected.
/// The has already been disposed of.
/// An error occurred while executing the command.
/// is null.
/// is a zero-length string, contains only white space.
/// The caller has no required privilege.
public bool UpdateFavorite(string mediaId, bool value)
{
ValidateDatabase();
ValidationUtil.ValidateNotNullOrEmpty(mediaId, nameof(mediaId));
if (CommandHelper.Count(
Interop.MediaInfo.GetMediaCount, $"{MediaInfoColumns.Id}='{mediaId}'") == 0)
{
return false;
}
Interop.MediaInfo.GetMediaFromDB(mediaId, out var handle).ThrowIfError("Failed to update");
if (handle.IsInvalid)
{
return false;
}
try
{
Interop.MediaInfo.SetFavorite(handle, value).ThrowIfError("Failed to update");
Interop.MediaInfo.UpdateToDB(handle).ThrowIfError("Failed to update");
return true;
}
finally
{
handle.Dispose();
}
}
///
/// Updates the path of the media to the specified destination path in the database.
///
/// http://tizen.org/privilege/content.write
/// http://tizen.org/privilege/mediastorage
/// http://tizen.org/privilege/externalstorage
/// The media ID to move.
/// The path that the media has been moved to.
/// true if the matched record was found and updated, otherwise false.
///
/// Usually, it is used after the media file is moved to the another path.
///
/// If you want to access internal storage, you should add privilege http://tizen.org/privilege/mediastorage.
/// If you want to access external storage, you should add privilege http://tizen.org/privilege/externalstorage.
///
/// The is disconnected.
/// The has already been disposed of.
/// An error occurred while executing the command.
///
/// is null.
/// -or-
/// is null.
///
///
/// is a zero-length string, contains only white space.
/// -or-
/// is a zero-length string, contains only white space.
/// -or-
/// contains a hidden directory that starts with '.'.
/// -or-
/// contains a directory containing the ".scan_ignore" file.
///
/// does not exists.
/// The caller has no required privilege.
public bool Move(string mediaId, string newPath)
{
ValidateDatabase();
ValidationUtil.ValidateNotNullOrEmpty(mediaId, nameof(mediaId));
ValidationUtil.ValidateNotNullOrEmpty(newPath, nameof(newPath));
if (File.Exists(newPath) == false)
{
throw new FileNotFoundException("destination is not valid path.", newPath);
}
if (File.GetAttributes(newPath).HasFlag(FileAttributes.Hidden))
{
throw new ArgumentException($"{nameof(newPath)} contains a hidden path.", nameof(newPath));
}
//TODO can be improved if MoveToDB supports result value.
Interop.MediaInfo.GetMediaFromDB(mediaId, out var handle).
Ignore(MediaContentError.InvalidParameter).ThrowIfError("Failed to move");
if (handle.IsInvalid)
{
return false;
}
try
{
Interop.MediaInfo.MoveToDB(handle, newPath).ThrowIfError("Failed to move");
}
finally
{
handle.Dispose();
}
return true;
}
#region CreateThumbnailAsync
///
/// Creates the thumbnail image for the given media.
/// If the thumbnail already exists for the given media, the existing path will be returned.
///
/// http://tizen.org/privilege/content.write
/// The media ID to create the thumbnail.
/// A task that represents the asynchronous operation. The task result contains the thumbnail path.
///
/// The is disconnected.
/// -or-
/// An internal error occurred while executing.
///
/// The has already been disposed of.
/// An error occurred while executing the command.
/// is null.
/// does not exist in the database.
///
/// is a zero-length string, contains only white space.
///
/// The file of the media does not exists; moved or deleted.
///
/// The thumbnail is not available for the given media.
/// -or-
/// The media is in the external USB storage ( is ).
///
public Task CreateThumbnailAsync(string mediaId)
{
return CreateThumbnailAsync(mediaId, CancellationToken.None);
}
///
/// Creates the thumbnail image for the given media.
/// If the thumbnail already exists for the given media, the existing path will be returned.
///
/// http://tizen.org/privilege/content.write
/// The media ID to create the thumbnail.
/// The token to cancel the operation.
/// A task that represents the asynchronous operation. The task result contains the thumbnail path.
///
/// The is disconnected.
/// -or-
/// An internal error occurred while executing.
///
/// The has already been disposed of.
/// An error occurred while executing the command.
/// is null.
/// does not exist in the database.
///
/// is a zero-length string, contains only white space.
///
/// The file of the media does not exists; moved or deleted.
///
/// The thumbnail is not available for the given media.
/// -or-
/// The media is in the external USB storage ( is ).
///
public Task CreateThumbnailAsync(string mediaId, CancellationToken cancellationToken)
{
ValidateDatabase();
return cancellationToken.IsCancellationRequested ? Task.FromCanceled(cancellationToken) :
CreateThumbnailAsyncCore(mediaId, cancellationToken);
}
private async Task CreateThumbnailAsyncCore(string mediaId, CancellationToken cancellationToken)
{
ValidationUtil.ValidateNotNullOrEmpty(mediaId, nameof(mediaId));
var tcs = new TaskCompletionSource();
Interop.MediaInfo.GetMediaFromDB(mediaId, out var handle).ThrowIfError("Failed to create thumbnail");
if (handle.IsInvalid)
{
throw new RecordNotFoundException("Media does not exist.");
}
using (handle)
{
if (InteropHelper.GetValue(handle, Interop.MediaInfo.GetStorageType) == StorageType.ExternalUsb)
{
throw new UnsupportedContentException("The media is in external usb storage.");
}
var path = InteropHelper.GetString(handle, Interop.MediaInfo.GetFilePath);
if (File.Exists(path) == false)
{
throw new FileNotFoundException($"The media file does not exist. Path={path}.", path);
}
using (RegisterCancelThumbnail(cancellationToken, tcs, handle))
using (var cbKeeper = ObjectKeeper.Get(GetCreateThumbnailCallback(tcs)))
{
Interop.MediaInfo.CreateThumbnail(handle, cbKeeper.Target).ThrowIfError("Failed to create thumbnail");
return await tcs.Task;
}
}
}
private static Interop.MediaInfo.ThumbnailCompletedCallback GetCreateThumbnailCallback(
TaskCompletionSource tcs)
{
return (error, path, _) =>
{
if (error != MediaContentError.None)
{
tcs.TrySetException(error.AsException("Failed to create thumbnail"));
}
else
{
tcs.TrySetResult(path);
}
};
}
private static IDisposable RegisterCancelThumbnail(CancellationToken cancellationToken,
TaskCompletionSource tcs, Interop.MediaInfoHandle handle)
{
if (cancellationToken.CanBeCanceled == false)
{
return null;
}
return cancellationToken.Register(() =>
{
if (tcs.Task.IsCompleted)
{
return;
}
Interop.MediaInfo.CancelThumbnail(handle).ThrowIfError("Failed to cancel");
tcs.TrySetCanceled();
});
}
#endregion
#region DetectFaceAsync
///
/// Detects faces from the given media.
/// If the thumbnail already exists for the given media, the existing path will be returned.
///
/// http://tizen.org/privilege/content.write
/// http://tizen.org/feature/vision.face_recognition
/// The media ID to create the thumbnail.
/// A task that represents the asynchronous add operation. The task result contains the number of faces detected.
///
/// The is disconnected.
/// -or-
/// An internal error occurred while executing.
///
/// The has already been disposed of.
/// An error occurred while executing the command.
/// is null.
/// does not exist in the database.
///
/// is a zero-length string, contains only white space.
///
/// The file of the media does not exists; moved or deleted.
/// Face detection is not available for the given media.
/// The required feature is not supported.
/// The caller has no required privilege.
public Task DetectFaceAsync(string mediaId)
{
return DetectFaceAsync(mediaId, CancellationToken.None);
}
///
/// Creates the thumbnail image for the given media.
/// If the thumbnail already exists for the given media, the existing path will be returned.
///
///
/// Media in the external storage is not supported, with the exception of MMC.
///
/// http://tizen.org/privilege/content.write
/// http://tizen.org/feature/vision.face_recognition
/// The media ID to create the thumbnail.
/// The token to cancel the operation.
/// A task that represents the asynchronous operation. The task result contains the number of faces detected.
///
/// The is disconnected.
/// -or-
/// An internal error occurred while executing.
///
/// The has already been disposed of.
/// An error occurred while executing the command.
/// is null.
/// does not exist in the database.
///
/// is a zero-length string, contains only white space.
///
/// The file of the media does not exists; moved or deleted.
///
/// Face detection is not available for the given media.
/// -or-
/// The media is in the external USB storage ( is ).
///
/// The required feature is not supported.
public Task DetectFaceAsync(string mediaId, CancellationToken cancellationToken)
{
if (Features.IsSupported(Features.FaceRecognition) == false)
{
throw new NotSupportedException($"The feature({Features.FaceRecognition}) is not supported.");
}
ValidateDatabase();
return cancellationToken.IsCancellationRequested ? Task.FromCanceled(cancellationToken) :
DetectFaceAsyncCore(mediaId, cancellationToken);
}
private static async Task DetectFaceAsyncCore(string mediaId, CancellationToken cancellationToken)
{
ValidationUtil.ValidateNotNullOrEmpty(mediaId, nameof(mediaId));
var tcs = new TaskCompletionSource();
Interop.MediaInfo.GetMediaFromDB(mediaId, out var handle).ThrowIfError("Failed to detect faces");
if (handle.IsInvalid)
{
throw new RecordNotFoundException("Media does not exist.");
}
using (handle)
{
if (InteropHelper.GetValue(handle, Interop.MediaInfo.GetStorageType) == StorageType.ExternalUsb)
{
throw new UnsupportedContentException("The media is in external usb storage.");
}
if (InteropHelper.GetValue(handle, Interop.MediaInfo.GetMediaType) != MediaType.Image)
{
throw new UnsupportedContentException("Only image is supported.");
}
var path = InteropHelper.GetString(handle, Interop.MediaInfo.GetFilePath);
if (File.Exists(path) == false)
{
throw new FileNotFoundException($"The media file does not exist. Path={path}.", path);
}
using (RegisterCancelFaceDetection(cancellationToken, tcs, handle))
using (var cbKeeper = ObjectKeeper.Get(GetFaceDetectionCallback(tcs)))
{
Interop.MediaInfo.StartFaceDetection(handle, cbKeeper.Target).ThrowIfError("Failed to detect faces");
return await tcs.Task;
}
}
}
private static Interop.MediaInfo.FaceDetectionCompletedCallback GetFaceDetectionCallback(
TaskCompletionSource tcs)
{
return (error, count, _) =>
{
if (error != MediaContentError.None)
{
tcs.TrySetException(error.AsException("Failed to detect faces"));
}
else
{
tcs.TrySetResult(count);
}
};
}
private static IDisposable RegisterCancelFaceDetection(CancellationToken cancellationToken,
TaskCompletionSource tcs, Interop.MediaInfoHandle handle)
{
if (cancellationToken.CanBeCanceled == false)
{
return null;
}
return cancellationToken.Register(() =>
{
if (tcs.Task.IsCompleted)
{
return;
}
Interop.MediaInfo.CancelFaceDetection(handle).ThrowIfError("Failed to cancel");
tcs.TrySetCanceled();
});
}
#endregion
}
}