| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497 |
- using ImageMagick;
- using PicView.Avalonia.ImageHandling;
- using PicView.Avalonia.Input;
- using PicView.Avalonia.UI;
- using PicView.Avalonia.ViewModels;
- using PicView.Core.ArchiveHandling;
- using PicView.Core.DebugTools;
- using PicView.Core.FileHandling;
- using PicView.Core.FileHistory;
- using PicView.Core.Gallery;
- using PicView.Core.Http;
- using PicView.Core.ImageDecoding;
- using PicView.Core.Localization;
- using PicView.Core.Navigation;
- namespace PicView.Avalonia.Navigation;
- public static class ImageLoader
- {
- #region Load Pic From String
- /// <summary>
- /// Loads a picture from a given string source, which can be a file path, directory path, or URL.
- /// </summary>
- public static async Task LoadPicFromStringAsync(string source, MainViewModel vm, ImageIterator imageIterator)
- {
- if (string.IsNullOrWhiteSpace(source) || vm is null)
- {
- return;
- }
- MenuManager.CloseMenus(vm);
- vm.IsLoading = true;
- TitleManager.SetLoadingTitle(vm);
- // Starting in new task makes it more responsive and works better
- await Task.Run(async () =>
- {
- var check = ErrorHelper.CheckIfLoadableString(source);
- if (check == null)
- {
- await ErrorHandling.ReloadAsync(vm).ConfigureAwait(false);
- vm.IsLoading = false;
- ArchiveExtraction.Cleanup();
- return;
- }
- switch (check.Value.Type)
- {
- case ErrorHelper.LoadAbleFileType.File:
- vm.CurrentView = vm.ImageViewer;
- await LoadPicFromFile(check.Value.Data, vm, imageIterator).ConfigureAwait(false);
- vm.IsLoading = false;
- ArchiveExtraction.Cleanup();
- return;
- case ErrorHelper.LoadAbleFileType.Directory:
- vm.CurrentView = vm.ImageViewer;
- await LoadPicFromDirectoryAsync(check.Value.Data, vm).ConfigureAwait(false);
- vm.IsLoading = false;
- ArchiveExtraction.Cleanup();
- return;
- case ErrorHelper.LoadAbleFileType.Web:
- vm.CurrentView = vm.ImageViewer;
- await LoadPicFromUrlAsync(check.Value.Data, vm, imageIterator).ConfigureAwait(false);
- vm.IsLoading = false;
- ArchiveExtraction.Cleanup();
- return;
- case ErrorHelper.LoadAbleFileType.Base64:
- vm.CurrentView = vm.ImageViewer;
- await LoadPicFromBase64Async(check.Value.Data, vm, imageIterator).ConfigureAwait(false);
- vm.IsLoading = false;
- ArchiveExtraction.Cleanup();
- return;
- case ErrorHelper.LoadAbleFileType.Zip:
- vm.CurrentView = vm.ImageViewer;
- await LoadPicFromArchiveAsync(check.Value.Data, vm, imageIterator).ConfigureAwait(false);
- vm.IsLoading = false;
- return;
- default:
- await ErrorHandling.ReloadAsync(vm).ConfigureAwait(false);
- vm.IsLoading = false;
- ArchiveExtraction.Cleanup();
- return;
- }
- });
- }
- #endregion
- #region Load Pic From File
- /// <summary>
- /// Loads an image from a specified file and manages navigation within the directory or recreates the iterator.
- /// </summary>
- /// <param name="fileName">The full path of the file to load.</param>
- /// <param name="vm">The main view model instance associated with the application context.</param>
- /// <param name="imageIterator">An iterator for navigating through images in the directory.</param>
- /// <param name="fileInfo">Optional file information, defaults to a new <c>FileInfo</c> instance for the given file name if not provided.</param>
- public static async Task LoadPicFromFile(string fileName, MainViewModel vm, ImageIterator imageIterator,
- FileInfo? fileInfo = null)
- {
- fileInfo ??= new FileInfo(fileName);
- if (!fileInfo.Exists)
- {
- return;
- }
- await CancelAsync().ConfigureAwait(false);
- if (imageIterator is not null)
- {
- // If image is in same directory as is being browsed, navigate to it. Otherwise, load without iterator.
- if (fileInfo.DirectoryName == imageIterator.InitialFileInfo.DirectoryName)
- {
- var index = imageIterator.ImagePaths.IndexOf(fileInfo.FullName);
- if (index != -1)
- {
- await imageIterator.IterateToIndex(index, _cancellationTokenSource).ConfigureAwait(false);
- await NavigationManager.CheckIfTiffAndUpdate(vm, fileInfo, index);
- if (Settings.Gallery.IsBottomGalleryShown && NavigationManager.GetCount > 0)
- {
- vm.GalleryMode = GalleryMode.ClosedToBottom;
- }
- }
- else
- {
- await LoadWithoutIterator();
- }
- }
- else
- {
- await LoadWithoutIterator();
- }
- }
- else
- {
- await LoadWithoutIterator();
- }
- return;
- async Task LoadWithoutIterator()
- {
- if (Settings.UIProperties.IsTaskbarProgressEnabled)
- {
- vm.PlatformService.StopTaskbarProgress();
- }
- await NavigationManager.LoadWithoutImageIterator(fileInfo, vm).ConfigureAwait(false);
- }
- }
- #endregion
- #region Load Pic From Directory
- /// <summary>
- /// Loads a picture from a directory.
- /// </summary>
- /// <param name="file">The path to the directory containing the picture.</param>
- /// <param name="vm">The main view model instance.</param>
- /// <param name="fileInfo">Optional: FileInfo object for the directory.</param>
- public static async Task LoadPicFromDirectoryAsync(string file, MainViewModel vm, FileInfo? fileInfo = null)
- {
- vm.IsLoading = true;
- TitleManager.SetLoadingTitle(vm);
- if (_cancellationTokenSource is not null)
- {
- await _cancellationTokenSource.CancelAsync().ConfigureAwait(false);
- }
- _cancellationTokenSource = new CancellationTokenSource();
- if (Settings.UIProperties.IsTaskbarProgressEnabled)
- {
- vm.PlatformService.StopTaskbarProgress();
- }
- fileInfo ??= new FileInfo(file);
- var newFileList = await Task.Run(() =>
- {
- var fileList = vm.PlatformService.GetFiles(fileInfo);
- if (fileList.Count > 0)
- {
- return fileList;
- }
- // Attempt to reload with subdirectories and reset the setting
- if (Settings.Sorting.IncludeSubDirectories)
- {
- return null;
- }
- Settings.Sorting.IncludeSubDirectories = true;
- fileList = vm.PlatformService.GetFiles(fileInfo);
- if (fileList.Count <= 0)
- {
- return null;
- }
- Settings.Sorting.IncludeSubDirectories = false;
- return fileList;
- }).ConfigureAwait(false);
- if (newFileList is null)
- {
- await ErrorHandling.ReloadAsync(vm).ConfigureAwait(false);
- return;
- }
- var firstFileInfo = new FileInfo(newFileList[0]);
- await NavigationManager.LoadWithoutImageIterator(firstFileInfo, vm, newFileList);
- }
- #endregion
- #region Load Pic From Archive
- /// <summary>
- /// Asynchronously loads pictures from the specified archive file.
- /// </summary>
- /// <param name="path">The path to the archive file containing the picture(s) to load.</param>
- /// <param name="vm">The main view model instance used to manage UI state and operations.</param>
- /// <param name="imageIterator">The image iterator to use for navigation.</param>
- public static async Task LoadPicFromArchiveAsync(string path, MainViewModel vm, ImageIterator imageIterator)
- {
- if (_cancellationTokenSource is not null)
- {
- await _cancellationTokenSource.CancelAsync().ConfigureAwait(false);
- }
- vm.IsLoading = true;
- TitleManager.SetLoadingTitle(vm);
- string? prevArchiveLocation = null;
- var previousArchiveExist = !string.IsNullOrEmpty(ArchiveExtraction.TempZipDirectory);
- if (previousArchiveExist)
- {
- prevArchiveLocation = ArchiveExtraction.TempZipDirectory;
- }
- var extraction = await ArchiveExtraction
- .ExtractArchiveAsync(path, vm.PlatformService.ExtractWithLocalSoftwareAsync).ConfigureAwait(false);
- if (!extraction)
- {
- await ErrorHandling.ReloadAsync(vm);
- Clean();
- return;
- }
- if (Directory.Exists(ArchiveExtraction.TempZipDirectory))
- {
- var dirInfo = new DirectoryInfo(ArchiveExtraction.TempZipDirectory);
- if (dirInfo.EnumerateDirectories().Any())
- {
- var firstDir = dirInfo.EnumerateDirectories().First();
- var firstFile = firstDir.EnumerateFiles().First();
- await LoadPicFromFile(firstFile.FullName, vm, imageIterator, firstFile).ConfigureAwait(false);
- }
- else
- {
- await LoadPicFromDirectoryAsync(ArchiveExtraction.TempZipDirectory, vm).ConfigureAwait(false);
- }
- FileHistoryManager.Add(path);
- MainKeyboardShortcuts.ClearKeyDownModifiers(); // Fix possible modifier key state issue
- if (previousArchiveExist)
- {
- try
- {
- Directory.Delete(prevArchiveLocation, true);
- }
- catch (Exception e)
- {
- DebugHelper.LogDebug(nameof(ImageLoader), nameof(LoadPicFromArchiveAsync), e);
- }
- }
- }
- else
- {
- await imageIterator.DisposeAsync();
- await ErrorHandling.ReloadAsync(vm);
- Clean();
- }
-
- return;
- void Clean()
- {
- TempFileHelper.DeleteTempFiles();
- ArchiveExtraction.Cleanup();
- }
- }
- #endregion
- #region Load Pic From URL
- /// <summary>
- /// Loads a picture from a given URL.
- /// </summary>
- /// <param name="url">The URL of the picture to load.</param>
- /// <param name="vm">The main view model instance.</param>
- /// <param name="imageIterator">The image iterator to use for navigation.</param>
- public static async Task LoadPicFromUrlAsync(string url, MainViewModel vm, ImageIterator imageIterator)
- {
- var tasks = new List<Task>();
- if (_cancellationTokenSource is not null)
- {
- tasks.Add(_cancellationTokenSource.CancelAsync());
- }
- string destination;
- try
- {
- vm.PlatformService.StopTaskbarProgress();
- var httpDownload = HttpManager.GetDownloadClient(url);
- using var client = httpDownload.Client;
- var fileName = Path.GetFileName(url);
- client.ProgressChanged += (totalFileSize, totalBytesDownloaded, progressPercentage) =>
- {
- if (totalFileSize is null || totalBytesDownloaded is null || progressPercentage is null)
- {
- return;
- }
- var displayProgress = HttpManager.GetProgressDisplay(totalFileSize, totalBytesDownloaded,
- progressPercentage);
- var title = $"{fileName} {TranslationManager.Translation.Downloading} {displayProgress}";
- vm.PicViewer.Title = title;
- vm.PicViewer.TitleTooltip = title;
- vm.PicViewer.WindowTitle = title;
- if (Settings.UIProperties.IsTaskbarProgressEnabled)
- {
- vm.PlatformService.SetTaskbarProgress((ulong)totalBytesDownloaded, (ulong)totalFileSize);
- }
- };
- tasks.Add(client.StartDownloadAsync());
- if (imageIterator is not null)
- {
- tasks.Add(imageIterator.DisposeAsync().AsTask());
- }
- await Task.WhenAll(tasks).ConfigureAwait(false);
- destination = httpDownload.DownloadPath;
- }
- catch (Exception e)
- {
- DebugHelper.LogDebug(nameof(ImageLoader), nameof(LoadPicFromUrlAsync), e);
- await ErrorHandling.ReloadAsync(vm);
- await TooltipHelper.ShowTooltipMessageAsync(e.Message, true);
- return;
- }
- var fileInfo = new FileInfo(destination);
- if (!fileInfo.Exists)
- {
- await ErrorHandling.ReloadAsync(vm);
- return;
- }
- var imageModel = await GetImageModel.GetImageModelAsync(fileInfo).ConfigureAwait(false);
- await UpdateImage.SetSingleImageAsync(imageModel.Image, imageModel.ImageType, url, vm);
- vm.IsLoading = false;
- vm.PicViewer.FileInfo = fileInfo;
- vm.PicViewer.ExifOrientation = imageModel.EXIFOrientation;
- FileHistoryManager.Add(url);
- await NavigationManager.DisposeImageIteratorAsync();
- TempFileHelper.TempFilePath = destination;
- }
- #endregion
- #region Load Pic From Base64
- /// <summary>
- /// Loads a picture from a Base64-encoded string.
- /// </summary>
- /// <param name="base64">The Base64-encoded string representing the picture.</param>
- /// <param name="vm">The main view model instance.</param>
- /// <param name="imageIterator">The image iterator to use for navigation.</param>
- public static async Task LoadPicFromBase64Async(string base64, MainViewModel vm, ImageIterator imageIterator)
- {
- TitleManager.SetLoadingTitle(vm);
- vm.IsLoading = true;
- vm.PicViewer.ImageSource = null;
- vm.PicViewer.FileInfo = null;
- if (_cancellationTokenSource is not null)
- {
- await _cancellationTokenSource.CancelAsync().ConfigureAwait(false);
- }
- await NavigationManager.DisposeImageIteratorAsync().ConfigureAwait(false);
- await Task.Run(async () =>
- {
- try
- {
- var magickImage = ImageDecoder.Base64ToMagickImage(base64);
- magickImage.Format = MagickFormat.Png;
- var bitmap = magickImage.ToWriteableBitmap();
- var imageModel = new ImageModel
- {
- Image = bitmap,
- PixelWidth = bitmap?.PixelSize.Width ?? 0,
- PixelHeight = bitmap?.PixelSize.Height ?? 0,
- ImageType = ImageType.Bitmap
- };
- await UpdateImage.SetSingleImageAsync(imageModel.Image, imageModel.ImageType,
- TranslationManager.Translation.Base64Image, vm);
- }
- catch (Exception e)
- {
- DebugHelper.LogDebug(nameof(ImageLoader), nameof(LoadPicFromBase64Async), e);
- await imageIterator.DisposeAsync();
- await ErrorHandling.ReloadAsync(vm);
- }
- });
- vm.IsLoading = false;
- }
- #endregion
- #region Cancellation
- private static CancellationTokenSource? _cancellationTokenSource;
- public static void Cancel()
- {
- if (_cancellationTokenSource is not null)
- {
- _cancellationTokenSource.Cancel();
- _cancellationTokenSource.Dispose();
- _cancellationTokenSource = null;
- }
- _cancellationTokenSource = new CancellationTokenSource();
- }
- public static async Task CancelAsync()
- {
- if (_cancellationTokenSource is not null)
- {
- await _cancellationTokenSource.CancelAsync().ConfigureAwait(false);
- _cancellationTokenSource.Dispose();
- _cancellationTokenSource = null;
- }
- _cancellationTokenSource = new CancellationTokenSource();
- }
- #endregion
- #region Image Iterator Loading
- /// <inheritdoc cref="ImageIterator.NextIteration(NavigateTo, CancellationTokenSource)" />
- public static async Task LastIterationAsync(ImageIterator imageIterator) =>
- await imageIterator
- .NextIteration(NavigateTo.Last, _cancellationTokenSource)
- .ConfigureAwait(false);
- /// <inheritdoc cref="ImageIterator.NextIteration(NavigateTo, CancellationTokenSource)" />
- public static async Task FirstIterationAsync(ImageIterator imageIterator) =>
- await imageIterator
- .NextIteration(NavigateTo.First, _cancellationTokenSource)
- .ConfigureAwait(false);
- /// <summary>
- /// Checks if the previous iteration has been canceled and starts the iteration at the given index
- /// </summary>
- /// <param name="index">The index to iterate to.</param>
- /// <param name="imageIterator">The ImageIterator instance.</param>
- public static async Task CheckCancellationAndStartIterateToIndex(int index, ImageIterator imageIterator)
- {
- if (_cancellationTokenSource is not null)
- {
- await _cancellationTokenSource.CancelAsync().ConfigureAwait(false);
- }
- // Need to start in a new task. This makes it more responsive, since it can get laggy when loading large images
- await Task.Run(async () =>
- {
- _cancellationTokenSource = new CancellationTokenSource();
- await imageIterator.NextIteration(index, _cancellationTokenSource).ConfigureAwait(false);
- }).ConfigureAwait(false);
- }
- #endregion
- }
|