Răsfoiți Sursa

Refactor and use interface for platform specific file deletion

Ruben 5 luni în urmă
părinte
comite
fc694837ce

+ 5 - 0
src/PicView.Avalonia.MacOS/App.axaml.cs

@@ -219,6 +219,11 @@ public class App : Application, IPlatformSpecificService, IPlatformWindowService
         var iIFileAssociationService = new MacFileAssociationService();
         FileAssociationManager.Initialize(iIFileAssociationService);
     }
+
+    public Task<bool> DeleteFile(string path, bool recycle)
+    {
+         throw new NotImplementedException(); 
+    }
     
     #endregion
     

+ 3 - 0
src/PicView.Avalonia.Win32/App.axaml.cs

@@ -94,6 +94,9 @@ public class App : Application, IPlatformSpecificService, IPlatformWindowService
     public int CombinedTitleButtonsWidth { get; set; } = 215;
 
     #region Interface Implementations
+    
+    public Task<bool> DeleteFile(string path, bool recycle) =>
+        Task.Run(() => WinFileHelper.DeleteFile(path, recycle));
 
     public void SetTaskbarProgress(ulong progress, ulong maximum)
     {

+ 4 - 4
src/PicView.Avalonia/Converters/ConversionHelper.cs

@@ -1,8 +1,8 @@
 using ImageMagick;
+using PicView.Avalonia.Interfaces;
 using PicView.Avalonia.Navigation;
 using PicView.Avalonia.UI;
 using PicView.Avalonia.ViewModels;
-using PicView.Core.FileHandling;
 using PicView.Core.ImageDecoding;
 
 namespace PicView.Avalonia.Converters;
@@ -56,7 +56,7 @@ internal static class ConversionHelper
         return await SaveImageFileHelper.ResizeImageAsync(fileInfo, 0, (uint)height).ConfigureAwait(false);
     }
 
-    public static async Task<string> ConvertTask(FileInfo fileInfo, int selectedIndex)
+    public static async Task<string> ConvertTask(FileInfo fileInfo, int selectedIndex, IPlatformSpecificService platform)
     {
         var currentExtension = fileInfo.Extension.ToLower();
         var newExtension = selectedIndex switch
@@ -83,7 +83,7 @@ internal static class ConversionHelper
             return string.Empty;
         }
 
-        FileDeletionHelper.DeleteFileWithErrorMsg(oldPath, false);
+        await platform.DeleteFile(oldPath, true);
         return newPath;
     }
     
@@ -94,7 +94,7 @@ internal static class ConversionHelper
             return;
         }
 
-        var newPath = await ConvertTask(vm.PicViewer.FileInfo, index);
+        var newPath = await ConvertTask(vm.PicViewer.FileInfo, index, vm.PlatformService);
         if (!string.IsNullOrWhiteSpace(newPath))
         {
             await NavigationManager.LoadPicFromStringAsync(newPath, vm);

+ 10 - 15
src/PicView.Avalonia/FileSystem/FileManager.cs

@@ -1,10 +1,10 @@
 using System.Diagnostics;
 using Avalonia.Threading;
 using PicView.Avalonia.ImageHandling;
+using PicView.Avalonia.Interfaces;
 using PicView.Avalonia.UI;
 using PicView.Avalonia.ViewModels;
 using PicView.Avalonia.Views.UC.PopUps;
-using PicView.Core.FileHandling;
 using PicView.Core.Localization;
 
 namespace PicView.Avalonia.FileSystem;
@@ -14,7 +14,7 @@ public static class FileManager
     /// <summary>
     ///     Deletes the current file, either permanently or by moving to recycle bin
     /// </summary>
-    public static async Task DeleteFileWithOptionalDialog(bool recycle, string path)
+    public static async Task DeleteFileWithOptionalDialog(bool recycle, string path, IPlatformSpecificService platformService)
     {
         if (string.IsNullOrWhiteSpace(path))
         {
@@ -23,31 +23,26 @@ public static class FileManager
 
         try
         {
-            if (recycle && Settings.UIProperties.ShowRecycleConfirmation)
+            if (recycle && Settings.UIProperties.ShowRecycleConfirmation ||
+                !recycle && Settings.UIProperties.ShowPermanentDeletionConfirmation)
             {
                await Dispatcher.UIThread.InvokeAsync(ShowDeleteDialog);
             }
-            else if (!recycle && Settings.UIProperties.ShowPermanentDeletionConfirmation)
-            {
-                await Dispatcher.UIThread.InvokeAsync(ShowDeleteDialog);
-            }
             else
             {
-                var errorMsg =
-                    await Task.FromResult(
-                        FileDeletionHelper.DeleteFileWithErrorMsg(path, recycle));
+                var success = await platformService.DeleteFile(path, recycle);
 
-                if (!string.IsNullOrEmpty(errorMsg))
-                {
-                    await TooltipHelper.ShowTooltipMessageAsync(errorMsg, true);
-                }
-                else
+                if (success)
                 {
                     var msg = recycle
                         ? TranslationManager.Translation.SentFileToRecycleBin
                         : TranslationManager.Translation.DeletedFile;
                     await TooltipHelper.ShowTooltipMessageAsync(msg + Environment.NewLine + Path.GetFileName(path));
                 }
+                else
+                {
+                    await TooltipHelper.ShowTooltipMessageAsync(TranslationManager.Translation.UnexpectedError, true);
+                }
             }
         }
         catch (Exception ex)

+ 2 - 2
src/PicView.Avalonia/Functions/FunctionsMapper.cs

@@ -558,11 +558,11 @@ public static class FunctionsMapper
     
     /// <inheritdoc cref="FileManager.DeleteFileWithOptionalDialog" />
     public static async Task DeleteFile() =>
-        await FileManager.DeleteFileWithOptionalDialog(true, Vm.PicViewer?.FileInfo?.FullName).ConfigureAwait(false);
+        await FileManager.DeleteFileWithOptionalDialog(true, Vm.PicViewer?.FileInfo?.FullName, Vm.PlatformService).ConfigureAwait(false);
     
     /// <inheritdoc cref="FileManager.DeleteFileWithOptionalDialog" />
     public static async Task DeleteFilePermanently() =>
-        await FileManager.DeleteFileWithOptionalDialog(false, Vm.PicViewer?.FileInfo?.FullName).ConfigureAwait(false);
+        await FileManager.DeleteFileWithOptionalDialog(false, Vm.PicViewer?.FileInfo?.FullName, Vm.PlatformService).ConfigureAwait(false);
 
     public static async Task Rename()
     {

+ 2 - 0
src/PicView.Avalonia/Interfaces/IPlatformSpecificService.cs

@@ -40,4 +40,6 @@ public interface IPlatformSpecificService
     string DefaultJsonKeyMap();
 
     void InitiateFileAssociationService();
+    
+    Task<bool> DeleteFile(string path, bool recycle);
 }

+ 4 - 4
src/PicView.Avalonia/ViewModels/MainViewModel.cs

@@ -1103,11 +1103,11 @@ public class MainViewModel : ReactiveObject
     private async Task CutFileTask(string path) =>
         await ClipboardFileOperations.CutFile(path, this).ConfigureAwait(false);
 
-    private static async Task DeleteFileTask(string path) =>
-        await Task.Run(() => FileManager.DeleteFileWithOptionalDialog(false, path)).ConfigureAwait(false);
+    private async Task DeleteFileTask(string path) =>
+        await Task.Run(() => FileManager.DeleteFileWithOptionalDialog(false, path, PlatformService)).ConfigureAwait(false);
 
-    private static async Task RecycleFileTask(string path) =>
-        await Task.Run(() => FileManager.DeleteFileWithOptionalDialog(true, path)).ConfigureAwait(false);
+    private async Task RecycleFileTask(string path) =>
+        await Task.Run(() => FileManager.DeleteFileWithOptionalDialog(true, path, PlatformService)).ConfigureAwait(false);
 
     private async Task DuplicateFileTask(string path) =>
         await ClipboardFileOperations.Duplicate(path, this).ConfigureAwait(false);

+ 1 - 2
src/PicView.Avalonia/Views/SingleImageResizeView.axaml.cs

@@ -9,7 +9,6 @@ using PicView.Avalonia.Navigation;
 using PicView.Avalonia.Resizing;
 using PicView.Avalonia.UI;
 using PicView.Avalonia.ViewModels;
-using PicView.Core.FileHandling;
 using PicView.Core.ImageDecoding;
 using PicView.Core.Localization;
 using ReactiveUI;
@@ -215,7 +214,7 @@ public partial class SingleImageResizeView : UserControl
 
         if (Path.GetExtension(file) != ext)
         {
-            FileDeletionHelper.DeleteFileWithErrorMsg(file, true);
+            await vm.PlatformService.DeleteFile(file, true); 
         }
     }
 

+ 3 - 4
src/PicView.Avalonia/Views/UC/EditableTitlebar.axaml.cs

@@ -172,11 +172,10 @@ public partial class EditableTitlebar : UserControl
 
             if (saved)
             {
-                var deleteMsg = FileDeletionHelper.DeleteFileWithErrorMsg(oldPath, false);
-                if (!string.IsNullOrWhiteSpace(deleteMsg))
+                var success = await vm.PlatformService.DeleteFile(oldPath, true);
+                if (!success)
                 {
-                    // Show error message to user
-                    await TooltipHelper.ShowTooltipMessageAsync(deleteMsg);
+                    await TooltipHelper.ShowTooltipMessageAsync(TranslationManager.Translation.UnexpectedError);
                     vm.IsLoading = false;
                     return;
                 }

+ 20 - 11
src/PicView.Avalonia/Views/UC/PopUps/DeleteDialog.axaml.cs

@@ -2,7 +2,8 @@
 using Avalonia.Input;
 using Avalonia.Interactivity;
 using PicView.Avalonia.CustomControls;
-using PicView.Core.FileHandling;
+using PicView.Avalonia.UI;
+using PicView.Avalonia.ViewModels;
 using PicView.Core.Localization;
 
 namespace PicView.Avalonia.Views.UC.PopUps;
@@ -12,14 +13,10 @@ public partial class DeleteDialog : AnimatedPopUp
     public DeleteDialog(string prompt, string file, bool recycle)
     {
         InitializeComponent();
-        if (recycle)
-        {
-            ConfirmButtonText.Text = TranslationManager.Translation.MoveToRecycleBin;
-        }
-        else
-        {
-            ConfirmButtonText.Text = TranslationManager.Translation.DeleteFile;
-        }
+        ConfirmButtonText.Text = recycle ?
+            TranslationManager.Translation.MoveToRecycleBin :
+            TranslationManager.Translation.DeleteFile;
+        
         Loaded += delegate
         {
             PromptText.Text = prompt;
@@ -27,8 +24,20 @@ public partial class DeleteDialog : AnimatedPopUp
             CancelButton.Click += async delegate { await AnimatedClosing(); };
             ConfirmButton.Click += async delegate
             {
-                FileDeletionHelper.DeleteFileWithErrorMsg(file, recycle);
-                await AnimatedClosing();
+                if (DataContext is not MainViewModel vm)
+                {
+                    return;
+                }
+                var tasks = new List<Task>();
+                var success = vm.PlatformService.DeleteFile(file, true);
+                tasks.Add(success);
+                var animatedClosing = AnimatedClosing();
+                tasks.Add(animatedClosing);
+                await Task.WhenAll(tasks);
+                if (!success.Result)
+                {
+                    await TooltipHelper.ShowTooltipMessageAsync(TranslationManager.Translation.UnexpectedError);
+                }
             };
 
             Focus();

+ 1 - 1
src/PicView.Avalonia/WindowBehavior/WindowFunctions.cs

@@ -62,7 +62,7 @@ public static class WindowFunctions
         Settings.StartUp.LastFile = lastFile;
         await SaveSettingsAsync();
         await KeybindingManager.UpdateKeyBindingsFile(); // Save keybindings
-        FileDeletionHelper.DeleteTempFiles();
+        TempFileHelper.DeleteTempFiles();
         FileHistoryManager.SaveToFile();
         ArchiveExtraction.Cleanup();
         Environment.Exit(0);

+ 13 - 0
src/PicView.Core.WindowsNT/FileHandling/WinFileHelper.cs

@@ -0,0 +1,13 @@
+using Microsoft.VisualBasic.FileIO;
+
+namespace PicView.Core.WindowsNT.FileHandling;
+
+public static class WinFileHelper
+{
+    public static bool DeleteFile(string filePath, bool moveToRecycleBin)
+    {
+        var recycleOption = moveToRecycleBin ? RecycleOption.SendToRecycleBin : RecycleOption.DeletePermanently;
+        FileSystem.DeleteFile(filePath, UIOption.AllDialogs, recycleOption);
+        return File.Exists(filePath);
+    }
+}

+ 33 - 35
src/PicView.Core/Config/SettingsManager.cs

@@ -1,8 +1,8 @@
-using System.Diagnostics;
-using System.Globalization;
+using System.Globalization;
 using System.Runtime.InteropServices;
 using System.Text.Json;
 using System.Text.Json.Serialization;
+using PicView.Core.DebugTools;
 
 namespace PicView.Core.Config;
 
@@ -28,6 +28,7 @@ public static class SettingsManager
             {
                 return await LoadFromPathAsync(GetRoamingSettingsPath()).ConfigureAwait(false);
             }
+
             var path = GetUserSettingsPath();
             if (!string.IsNullOrEmpty(path))
             {
@@ -39,7 +40,7 @@ public static class SettingsManager
         }
         catch (Exception ex)
         {
-            LogError(nameof(LoadSettingsAsync), ex);
+            DebugHelper.LogDebug(nameof(SettingsManager), nameof(LoadSettingsAsync), ex);
             SetDefaults();
             return false;
         }
@@ -52,38 +53,44 @@ public static class SettingsManager
     public static string GetUserSettingsPath()
     {
         var roamingPath = GetRoamingSettingsPath();
-        if (File.Exists(roamingPath))
-        {
-            return roamingPath;
-        }
-
-        return GetLocalSettingsPath();
+        return File.Exists(roamingPath) ? roamingPath : GetLocalSettingsPath();
     }
 
     /// <summary>
     ///     Gets the path to the roaming settings file
     /// </summary>
-    private static string GetRoamingSettingsPath()
-    {
-        return Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), SettingsConfiguration.RoamingConfigPath);
-    }
+    private static string GetRoamingSettingsPath() =>
+        Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),
+            SettingsConfiguration.RoamingConfigPath);
 
     /// <summary>
     ///     Gets the path to the local settings file
     /// </summary>
-    private static string GetLocalSettingsPath()
-    {
-        return Path.Combine(AppDomain.CurrentDomain.BaseDirectory, SettingsConfiguration.LocalConfigFilePath);
-    }
+    private static string GetLocalSettingsPath() =>
+        Path.Combine(AppDomain.CurrentDomain.BaseDirectory, SettingsConfiguration.LocalConfigFilePath);
 
     /// <summary>
     ///     Sets default settings values
     /// </summary>
     public static void SetDefaults()
     {
+        UIProperties uiProperties;
+        if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
+        {
+            uiProperties = new UIProperties
+            {
+                IsTaskbarProgressEnabled = false,
+                OpenInSameWindow = true
+            };
+        }
+        else
+        {
+            uiProperties = new UIProperties();
+        }
+
         Settings = new AppSettings
         {
-            UIProperties = new UIProperties(),
+            UIProperties = uiProperties,
             Gallery = new Gallery(),
             ImageScaling = new ImageScaling(),
             Sorting = new Sorting(),
@@ -93,6 +100,7 @@ public static class SettingsManager
             StartUp = new StartUp(),
             Version = SettingsConfiguration.CurrentSettingsVersion
         };
+
         // Get the default culture from the OS
         Settings.UIProperties.UserLanguage = CultureInfo.CurrentCulture.Name;
     }
@@ -109,7 +117,7 @@ public static class SettingsManager
         }
         catch (Exception ex)
         {
-            LogError(nameof(DeleteSettingFiles), ex);
+            DebugHelper.LogDebug(nameof(SettingsManager), nameof(DeleteSettingFiles), ex);
         }
     }
 
@@ -220,13 +228,13 @@ public static class SettingsManager
             }
             catch (Exception ex)
             {
-                LogError(nameof(SaveSettingsAsync), ex);
+                DebugHelper.LogDebug(nameof(SettingsManager), nameof(SaveSettingsAsync), ex);
                 return false;
             }
         }
         catch (Exception ex)
         {
-            LogError(nameof(SaveSettingsAsync), ex);
+            DebugHelper.LogDebug(nameof(SettingsManager), nameof(SaveSettingsAsync), ex);
             return false;
         }
     }
@@ -289,7 +297,8 @@ public static class SettingsManager
             var jsonString = await File.ReadAllTextAsync(localPath).ConfigureAwait(false);
 
             if (JsonSerializer.Deserialize(
-                    jsonString, typeof(AppSettings), SettingsGenerationContext.Default) is not AppSettings existingSettings)
+                    jsonString, typeof(AppSettings),
+                    SettingsGenerationContext.Default) is not AppSettings existingSettings)
             {
                 return;
             }
@@ -303,7 +312,7 @@ public static class SettingsManager
         }
         catch (Exception ex)
         {
-            LogError(nameof(SynchronizeSettingsAsync), ex);
+            DebugHelper.LogDebug(nameof(SettingsManager), nameof(SynchronizeSettingsAsync), ex);
         }
     }
 
@@ -321,7 +330,7 @@ public static class SettingsManager
         existingSettings.WindowProperties ??= newSettings.WindowProperties;
         existingSettings.Zoom ??= newSettings.Zoom;
         existingSettings.StartUp ??= newSettings.StartUp;
-    
+
         // Fallback for any properties missing in older versions
         foreach (var property in typeof(AppSettings).GetProperties())
         {
@@ -335,15 +344,4 @@ public static class SettingsManager
             property.SetValue(existingSettings, newValue);
         }
     }
-
-    /// <summary>
-    ///     Logs an error message
-    /// </summary>
-    private static void LogError(string methodName, Exception ex)
-    {
-#if DEBUG
-        Trace.WriteLine($"{nameof(SettingsManager)}: {methodName} error: {ex.Message}");
-        Trace.WriteLine(ex.StackTrace);
-#endif
-    }
 }

+ 3 - 4
src/PicView.Core/Config/VersionHelper.cs

@@ -1,4 +1,5 @@
 using System.Reflection;
+using PicView.Core.DebugTools;
 
 namespace PicView.Core.Config;
 
@@ -30,11 +31,9 @@ public static class VersionHelper
             var assembly = Assembly.GetExecutingAssembly();
             return assembly.GetName().Version;
         }
-        catch (Exception e)
+        catch (Exception ex)
         {
-#if DEBUG
-            Console.WriteLine(e);
-#endif
+            DebugHelper.LogDebug(nameof(VersionHelper), nameof(GetAssemblyVersion), ex);
             return null;
         }
     }

+ 19 - 7
src/PicView.Core/FileHandling/FileAssociationManager.cs

@@ -1,4 +1,6 @@
-namespace PicView.Core.FileHandling;
+using PicView.Core.DebugTools;
+
+namespace PicView.Core.FileHandling;
 
 /// <summary>
 /// Platform-agnostic manager for file associations that delegates to platform-specific implementations
@@ -24,7 +26,10 @@ public static class FileAssociationManager
     /// <returns>True if successful, false otherwise</returns>
     public static async Task<bool> AssociateFile(string fileExtension, string? description = null)
     {
-        EnsureInitialized();
+        if (!EnsureInitialized())
+        {
+            return false;
+        }
         // Use provided description or generate a default one
         var fileDescription = description ?? $"{fileExtension.TrimStart('.')} Image File";
         return await _service.RegisterFileAssociation(fileExtension, fileDescription);
@@ -49,16 +54,23 @@ public static class FileAssociationManager
     /// </summary>
     public static async Task<bool> IsFileAssociated(string fileExtension)
     {
-        EnsureInitialized();
+        if (!EnsureInitialized())
+        {
+            return false;
+        }
         return await _service.IsFileAssociated(fileExtension);
     }
     
-    private static void EnsureInitialized()
+    private static bool EnsureInitialized()
     {
-        if (_service == null)
+        if (_service != null)
         {
-            throw new InvalidOperationException(
-                "FileAssociationManager has not been initialized. Call Initialize() with an appropriate implementation before using this class.");
+            return true;
         }
+
+        var ex = new InvalidOperationException(
+            "FileAssociationManager has not been initialized. Call Initialize() with an appropriate implementation before using this class.");
+        DebugHelper.LogDebug(nameof(FileAssociationManager), nameof(EnsureInitialized), ex);
+        return false;
     }
 }

+ 0 - 73
src/PicView.Core/FileHandling/FileDeletionHelper.cs

@@ -1,73 +0,0 @@
-using System.Diagnostics;
-using System.Runtime.InteropServices;
-using Microsoft.VisualBasic.FileIO;
-
-namespace PicView.Core.FileHandling;
-
-public static class FileDeletionHelper
-{
-    public static string DeleteFileWithErrorMsg(string file, bool recycle)
-    {
-        if (string.IsNullOrWhiteSpace(file))
-        {
-            return string.Empty;
-        }
-        if ( File.Exists(file) == false)
-        {
-            return string.Empty;
-        }
-
-        try
-        {
-            var toRecycleOption = recycle ? RecycleOption.SendToRecycleBin : RecycleOption.DeletePermanently;
-            
-            if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
-            {
-                FileSystem.DeleteFile(file, UIOption.OnlyErrorDialogs, toRecycleOption);
-            }
-            else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
-            {
-                File.Delete(file);
-            }
-        }
-        catch (Exception e)
-        {
-#if DEBUG
-            Trace.WriteLine("Delete exception \n" + e.Message);
-#endif
-            return e.Message;
-        }
-
-        return string.Empty;
-    }
-
-    /// <summary>
-    /// Deletes the temporary files when an archived file has been opened
-    /// </summary>
-    public static void DeleteTempFiles()
-    {
-        if (!Directory.Exists(TempFileHelper.TempFilePath))
-        {
-            return;
-        }
-
-        try
-        {
-            Array.ForEach(Directory.GetFiles(TempFileHelper.TempFilePath), File.Delete);
-#if DEBUG
-            Trace.WriteLine("Temp zip files deleted");
-#endif
-
-            Directory.Delete(TempFileHelper.TempFilePath);
-#if DEBUG
-            Trace.WriteLine("Temp zip folder " + TempFileHelper.TempFilePath + " deleted");
-#endif
-        }
-        catch (Exception exception)
-        {
-#if DEBUG
-            Trace.WriteLine($"{nameof(DeleteTempFiles)} caught exception:\n{exception.Message}");
-#endif
-        }
-    }
-}

+ 32 - 1
src/PicView.Core/FileHandling/TempFileHelper.cs

@@ -1,4 +1,7 @@
-namespace PicView.Core.FileHandling;
+using System.Diagnostics;
+using PicView.Core.DebugTools;
+
+namespace PicView.Core.FileHandling;
 
 public static class TempFileHelper
 {
@@ -14,4 +17,32 @@ public static class TempFileHelper
 
         return Directory.Exists(TempFilePath);
     }
+    
+    /// <summary>
+    /// Deletes the temporary files when an archived file has been opened
+    /// </summary>
+    public static void DeleteTempFiles()
+    {
+        if (!Directory.Exists(TempFilePath))
+        {
+            return;
+        }
+
+        try
+        {
+            Array.ForEach(Directory.GetFiles(TempFilePath), File.Delete);
+#if DEBUG
+            Trace.WriteLine("Temp zip files deleted");
+#endif
+
+            Directory.Delete(TempFilePath);
+#if DEBUG
+            Trace.WriteLine("Temp zip folder " + TempFilePath + " deleted");
+#endif
+        }
+        catch (Exception ex)
+        {
+            DebugHelper.LogDebug(nameof(TempFileHelper), nameof(DeleteTempFiles), ex);
+        }
+    }
 }

+ 1 - 1
src/PicView.Tests/FileFunctionTest.cs

@@ -11,7 +11,7 @@ public class FileFunctionTest
         var path = TempFileHelper.TempFilePath;
         Assert.True(result);
         Assert.True(Directory.Exists(TempFileHelper.TempFilePath));
-        FileDeletionHelper.DeleteTempFiles();
+        TempFileHelper.DeleteTempFiles();
         Assert.False(Directory.Exists(path));
         Assert.False(Directory.Exists(TempFileHelper.TempFilePath));
     }