Browse Source

Use task based loading approach rather than polling.

Ruben 7 months ago
parent
commit
7f0be04d1b

+ 1 - 1
src/PicView.Avalonia/ImageHandling/GetImageModel.cs

@@ -24,7 +24,7 @@ public static class GetImageModel
         { ".b64", new Base64Handler() }
         { ".b64", new Base64Handler() }
     };
     };
 
 
-    public static async Task<ImageModel?> GetImageModelAsync(FileInfo fileInfo)
+    public static async Task<ImageModel> GetImageModelAsync(FileInfo fileInfo)
     {
     {
         if (fileInfo is null)
         if (fileInfo is null)
         {
         {

+ 22 - 29
src/PicView.Avalonia/Navigation/ImageIterator.cs

@@ -131,6 +131,7 @@ public class ImageIterator : IAsyncDisposable
 #endif
 #endif
             });
             });
         };
         };
+        Interlocked.Exchange(ref _isRunningFlag, 0);
     }
     }
 
 
     private async Task OnFileAdded(FileSystemEventArgs e)
     private async Task OnFileAdded(FileSystemEventArgs e)
@@ -142,11 +143,6 @@ public class ImageIterator : IAsyncDisposable
 
 
         try
         try
         {
         {
-            if (e.FullPath.IsSupported() == false)
-            {
-                return;
-            }
-
             var fileInfo = new FileInfo(e.FullPath);
             var fileInfo = new FileInfo(e.FullPath);
             if (fileInfo.Exists == false)
             if (fileInfo.Exists == false)
             {
             {
@@ -211,11 +207,6 @@ public class ImageIterator : IAsyncDisposable
         
         
         try
         try
         {
         {
-            if (e.FullPath.IsSupported() == false)
-            {
-                return;
-            }
-
             if (ImagePaths.Contains(e.FullPath) == false)
             if (ImagePaths.Contains(e.FullPath) == false)
             {
             {
                 return;
                 return;
@@ -595,32 +586,34 @@ public class ImageIterator : IAsyncDisposable
             var preloadValue = GetPreLoadValue(index);
             var preloadValue = GetPreLoadValue(index);
             if (preloadValue is not null)
             if (preloadValue is not null)
             {
             {
-                // Wait for image to load
+                // Wait for image to load if it's still loading
                 if (preloadValue is { IsLoading: true, ImageModel.Image: null })
                 if (preloadValue is { IsLoading: true, ImageModel.Image: null })
                 {
                 {
                     LoadingPreview();
                     LoadingPreview();
 
 
-                    var retries = 0;
-                    do
+                    using var linkedCts = CancellationTokenSource.CreateLinkedTokenSource(cts.Token);
+                    linkedCts.CancelAfter(TimeSpan.FromMinutes(2));
+
+                    try
                     {
                     {
-                        await Task.Delay(20, cts.Token).ConfigureAwait(false);
-                        if (CurrentIndex != index)
+                        // Wait for the loading to complete or timeout
+                        await preloadValue.WaitForLoadingCompleteAsync().WaitAsync(linkedCts.Token);
+                    }
+                    catch (OperationCanceledException) when (!cts.IsCancellationRequested)
+                    {
+                        // This is a timeout, not cancellation from navigation
+                        preloadValue = new PreLoadValue(await GetImageModel.GetImageModelAsync(new FileInfo(ImagePaths[CurrentIndex])))
                         {
                         {
-                            // Skip loading if user went to next value
-                            await cts.CancelAsync();
-                            return;
-                        }
-
-                        retries++;
+                            IsLoading = false
+                        };
+                    }
 
 
-                        if (retries > 50)
-                        {
-                            preloadValue = new PreLoadValue(await GetImageModel.GetImageModelAsync(new FileInfo(ImagePaths[CurrentIndex])))
-                            {
-                                IsLoading = false
-                            };
-                        }
-                    } while (preloadValue.IsLoading);
+                    // Check if user navigated away during loading
+                    if (CurrentIndex != index)
+                    {
+                        await cts.CancelAsync();
+                        return;
+                    }
                 }
                 }
             }
             }
             else
             else

+ 60 - 3
src/PicView.Avalonia/Preloading/PreloadValue.cs

@@ -1,9 +1,66 @@
 using PicView.Avalonia.ImageHandling;
 using PicView.Avalonia.ImageHandling;
 
 
 namespace PicView.Avalonia.Preloading;
 namespace PicView.Avalonia.Preloading;
-public class PreLoadValue(ImageModel? imageModel)
+
+/// <summary>
+/// Represents a preloaded image value.
+/// </summary>
+public class PreLoadValue
 {
 {
-    public ImageModel? ImageModel { get; set; } = imageModel;
+    private TaskCompletionSource<bool>? _loadingCompletionSource;
+    private bool _isLoading;
+
+    /// <summary>
+    /// Initializes a new instance of the <see cref="PreLoadValue"/> class.
+    /// </summary>
+    /// <param name="imageModel">The image model.</param>
+    /// <param name="isLoading">Indicates whether the image is loading.</param>
+    public PreLoadValue(ImageModel imageModel, bool isLoading = false)
+    {
+        ImageModel = imageModel;
+        if (isLoading)
+        {
+            _loadingCompletionSource = new TaskCompletionSource<bool>();
+        }
+        IsLoading = isLoading;
+    }
+
+    /// <summary>
+    /// Gets or sets the image model.
+    /// </summary>
+    public ImageModel ImageModel { get; set; }
+
+    /// <summary>
+    /// Gets or sets a value indicating whether the image is loading.
+    /// </summary>
+    public bool IsLoading
+    {
+        get => _isLoading;
+        set
+        {
+            var wasLoading = _isLoading;
+            _isLoading = value;
+            
+            // Signal completion when loading changes from true to false
+            if (wasLoading && !value && _loadingCompletionSource != null)
+            {
+                _loadingCompletionSource.TrySetResult(true);
+                _loadingCompletionSource = null;
+            }
+            else if (value && !wasLoading)
+            {
+                // If we're starting to load, create a new completion source
+                _loadingCompletionSource = new TaskCompletionSource<bool>();
+            }
+        }
+    }
 
 
-    public bool IsLoading = true;
+    /// <summary>
+    /// Gets a task that completes when loading is finished.
+    /// </summary>
+    /// <returns>A task that completes when IsLoading becomes false.</returns>
+    public Task WaitForLoadingCompleteAsync()
+    {
+        return !_isLoading ? Task.CompletedTask : _loadingCompletionSource?.Task ?? Task.CompletedTask;
+    }
 }
 }

+ 3 - 6
src/PicView.Avalonia/Preloading/Preloader.cs

@@ -59,16 +59,13 @@ public class PreLoader : IAsyncDisposable
         }
         }
 
 
         var imageModel = new ImageModel();
         var imageModel = new ImageModel();
-        var preLoadValue = new PreLoadValue(imageModel)
-        {
-            IsLoading = true,
-        };
+        var preLoadValue = new PreLoadValue(imageModel, true); // Set isLoading to true
 
 
         if (!_preLoadList.TryAdd(index, preLoadValue))
         if (!_preLoadList.TryAdd(index, preLoadValue))
         {
         {
             return false;
             return false;
         }
         }
-        
+    
         try
         try
         {
         {
             var fileInfo = imageModel.FileInfo = new FileInfo(list[index]);
             var fileInfo = imageModel.FileInfo = new FileInfo(list[index]);
@@ -91,7 +88,7 @@ public class PreLoader : IAsyncDisposable
         }
         }
         finally
         finally
         {
         {
-            preLoadValue.IsLoading = false;
+            preLoadValue.IsLoading = false; // This will trigger the TaskCompletionSource
         }
         }
     }
     }