/* * 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 } }