public async Task EvaluateImagesLocally(IProgress <int> progress, CancellationToken cancellationToken)
        {
            var evaluated = 0;

            using (new DisposableLogger(GalleryLog.EvaluateLocalBegin, (sw) => GalleryLog.EvaluateLocalEnd(sw, evaluated)))
            {
                IsEvaluating = true;

                await LoadLocalModel();

                for (var i = 0; i < MediaDatas.Count; i++)
                {
                    var data = MediaDatas[i];

                    // Throw if cancellation is requested.
                    cancellationToken.ThrowIfCancellationRequested();

                    // Evaluate media using classifier if it has no tags already.
                    if (data.Meta?.Labels == null || data.Meta?.Labels.Count < 1)
                    {
                        await data.EvaluateLocalAsync(_memeClassifier);
                    }
                    // Update and then report progress.
                    evaluated++;
                    progress.Report(evaluated);
                }
                IsEvaluating = false;
            }

            // Save local results to database in case online image evaluation never finishes.
            await DatabaseUtils.SaveAllMetadatasAsync(MediaDatas);
        }
        private async void Query_ContentsChanged(IStorageQueryResultBase sender, object args)
        {
            // Run on UI thread.
            await App.Current.NavigationService.Frame.Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, async() =>
            {
                var tracker = _folder.TryGetChangeTracker();
                if (tracker != null)
                {
                    var changeReader = tracker.GetChangeReader();
                    var changes      = await changeReader.ReadBatchAsync();

                    foreach (var change in changes)
                    {
                        if (change.ChangeType == StorageLibraryChangeType.ChangeTrackingLost)
                        {
                            // Change tracker is in an invalid state and must be reset
                            // This should be a very rare case, but must be handled
                            tracker.Reset();
                            return;
                        }
                        if (change.IsOfType(StorageItemTypes.File))
                        {
                            // Process file change on UI thread.
                            await ProcessFileChange(change);
                        }
                        else if (change.IsOfType(StorageItemTypes.Folder))
                        {
                            // No-op; not interested in folders
                        }
                        else
                        {
                            if (change.ChangeType == StorageLibraryChangeType.Deleted)
                            {
                                // TODO: The application does not have to support FAT drives at the moment. NTFS is enough.
                                //UnknownItemRemoved(change.Path);
                            }
                        }
                    }

                    // If any changes were recorded, save database.
                    if (changes.Count > 0)
                    {
                        // Log query contens changed saving.
                        GalleryLog.QueryContentsChangedSaving();
                        await DatabaseUtils.SaveAllMetadatasAsync(MediaDatas);
                    }

                    await changeReader.AcceptChangesAsync();
                }
            });
        }
        public Dictionary <MediaData, List <MediaData> > ScanForDuplicates()
        {
            var duplicates = new Dictionary <MediaData, List <MediaData> >();

            // Time and log scanning.
            using (new DisposableLogger(GalleryLog.DuplicateScanBegin, (sw) => GalleryLog.DuplicateScanEnd(sw, MediaDatas.Count, duplicates.Count)))
            {
                foreach (var mediaData in MediaDatas)
                {
                    ScanForDuplicate(duplicates, mediaData);
                }
            }
            return(duplicates);
        }
        public async Task EvaluateImagesOnline(IProgress <int> progress, CancellationToken cancellationToken)
        {
            // Sign into firebase.
            if (await FirebaseClient.Client.SignInPromptUserIfNecessary())
            {
                // Find all media datas without annotation data.
                var mediaDatasWithoutAnnotationData = MediaDatas.Where(data => data.Meta.AnnotationData == null);

                var evaluated = 0;
                using (new DisposableLogger(GalleryLog.EvaluateOnlineBegin, (sw) => GalleryLog.EvaluateOnlineEnd(sw, evaluated)))
                {
                    IsEvaluating = true;
                    await mediaDatasWithoutAnnotationData.ForEachAsyncConcurrent(async media =>
                    {
                        try
                        {
                            // Evaluate media thumbnail online.
                            await media.EvaluateOnlineAsync(FirebaseClient.accessToken);
                        }
                        catch (Exception e)
                        {
                            // Log Exception.
                            LifecycleLog.Exception(e);
                        }
                        finally
                        {
                            // Update and then report progress.
                            evaluated++;
                            progress.Report(evaluated);
                        }
                    }, cancellationToken, 5);

                    IsEvaluating = false;
                }

                // Save online results to database.
                await DatabaseUtils.SaveAllMetadatasAsync(MediaDatas);
            }
        }
        public async Task LoadFolderContents(int imageSize, IProgress <int> progress, ICollection <MediaData> mediaDatas = null)
        {
            if (!StorageApplicationPermissions.FutureAccessList.ContainsItem("gallery"))
            {
                return;
            }

            ImageSize = imageSize;

            _folder = await StorageApplicationPermissions.FutureAccessList.GetFolderAsync("gallery");

            if (_folder != null)
            {
                // Time and log file query.
                using (new DisposableLogger(GalleryLog.FileQueryBegin, (sw) => GalleryLog.FileQueryEnd(sw, FilesFound)))
                {
                    var queryOptions = new QueryOptions(CommonFileQuery.DefaultQuery, FileTypes.Extensions)
                    {
                        FolderDepth   = FolderDepth.Deep,
                        IndexerOption = IndexerOption.UseIndexerWhenAvailable,
                    };

                    // Sort results.
                    queryOptions.SortOrder.Clear();
                    var sortEntry = QueryUtils.GetSortEntryFromSettings();
                    queryOptions.SortOrder.Add(sortEntry);

                    // Prefetch thumbnails.
                    queryOptions.SetThumbnailPrefetch(ThumbnailMode.SingleItem, (uint)imageSize, ThumbnailOptions.UseCurrentScale);
                    queryOptions.SetPropertyPrefetch(PropertyPrefetchOptions.ImageProperties, new[] { "System.DateModified" });

                    // Create query.
                    _query = _folder.CreateFileQueryWithOptions(queryOptions);

                    // Register tracker.
                    _query.ContentsChanged += Query_ContentsChanged;

                    FilesFound = (int)await _query.GetItemCountAsync();
                }

                // Time and log file parsing.
                using (new DisposableLogger(GalleryLog.FileParseBegin, (sw) => GalleryLog.FileParseEnd(sw, FilesFound)))
                {
                    uint index = 0, stepSize = SettingsService.Instance.MediaLoadBatchSize;
                    var  files = await _query.GetFilesAsync(index, stepSize);

                    index += stepSize;
                    while (files.Count != 0)
                    {
                        var fileTask = _query.GetFilesAsync(index, stepSize).AsTask();
                        for (var i = 0; i < files.Count; i++)
                        {
                            var mediaFile = files[i];

                            // Don't bother with files not supported by MIME.
                            if (!FileTypes.IsSupportedExtension(mediaFile.FileType))
                            {
                                continue;
                            }

                            if (mediaDatas == null)
                            {
                                await AddFileAsync(imageSize, mediaFile, false);
                            }
                            else
                            {
                                // Find mediaData in mediaDatas based on path of mediaFile.
                                var matchingMediaDatas = mediaDatas.Where(data => data?.Meta?.MediaFilePath?.Equals(mediaFile?.Path) ?? false).ToList();

                                var mediaData = matchingMediaDatas.FirstOrDefault();

                                if (mediaData is null)
                                {
                                    // Track the lost media files.
                                    _lostMediaFiles.Add(mediaFile);
                                    continue;
                                }
                                if (mediaData.Meta is null)
                                {
                                    continue;
                                }
                                if (string.IsNullOrEmpty(mediaData.Meta.MediaFilePath))
                                {
                                    continue;
                                }

                                // Tell the mediaData which file belongs to it.
                                mediaData.MediaFile = mediaFile;

                                await AddFileAsync(imageSize, mediaFile, false, mediaData);
                            }
                        }
                        files  = await fileTask;
                        index += stepSize;
                        // Report progress to UI.
                        progress?.Report(MediaDatas.Count);
                    }
                }

                // Register tracker.
                RegisterFolderContentTracker();

                // Run tracker once to pick up on any file changes since last application boot.
                Query_ContentsChanged(_query, null);
            }
        }