Remove RecoveryStorageManager as a dependency of UiImageList

This commit is contained in:
Ben Olden-Cooligan 2022-06-27 21:46:22 -07:00
parent 1572dfd095
commit 8de8a4b9df
8 changed files with 77 additions and 40 deletions

View File

@ -10,6 +10,7 @@ public class ThumbnailRenderQueue : IDisposable
private readonly ScanningContext _scanningContext;
private readonly ThumbnailRenderer _thumbnailRenderer;
private readonly AutoResetEvent _renderThumbnailsWaitHandle = new(false);
private readonly ManualResetEvent _renderThumbnailsCompleteHandle = new(false);
private int _thumbnailSize;
private UiImageList? _imageList;
private bool _started;
@ -28,7 +29,7 @@ public class ThumbnailRenderQueue : IDisposable
{
_thumbnailSize = thumbnailSize;
}
_renderThumbnailsWaitHandle.Set();
BumpRenderThread();
}
public void StartRendering(UiImageList imageList)
@ -51,9 +52,24 @@ public class ThumbnailRenderQueue : IDisposable
private void ImageListUpdated(object? sender, EventArgs args)
{
BumpRenderThread();
}
private void BumpRenderThread()
{
_renderThumbnailsCompleteHandle.Reset();
_renderThumbnailsWaitHandle.Set();
}
/// <summary>
/// For testing. Waits for pending renders to complete.
/// </summary>
public void WaitForRendering()
{
// TODO: Could also check GetNextThumbnailToRender in a loop (though that has some locking issues)
_renderThumbnailsCompleteHandle.WaitOne();
}
private void RenderThumbnails()
{
// TODO: Make this run as async?
@ -107,6 +123,7 @@ public class ThumbnailRenderQueue : IDisposable
fallback.Increase();
continue;
}
_renderThumbnailsCompleteHandle.Set();
_renderThumbnailsWaitHandle.WaitOne();
}
}
@ -172,7 +189,7 @@ public class ThumbnailRenderQueue : IDisposable
{
if (_disposed) return;
_disposed = true;
_renderThumbnailsWaitHandle.Set();
BumpRenderThread();
if (_imageList != null)
{
lock (_imageList)

View File

@ -10,6 +10,7 @@ public class RecoveryModule : NinjectModule
{
string recoveryFolderPath = Path.Combine(Paths.Recovery, Path.GetRandomFileName());
var recoveryStorageManager = RecoveryStorageManager.CreateFolder(recoveryFolderPath);
recoveryStorageManager.RegisterForChanges(Kernel.Get<UiImageList>());
var fileStorageManager = new FileStorageManager(recoveryFolderPath);
Kernel.Bind<RecoveryStorageManager>().ToConstant(recoveryStorageManager);
Kernel.Bind<FileStorageManager>().ToConstant(fileStorageManager);

View File

@ -217,7 +217,8 @@ public class AutomatedScanning
foreach (var scan in _scanList)
{
var imageList = new UiImageList(_recoveryStorageManager, scan.Select(x => new UiImage(x)).ToList());
// TODO: The result doesn't get propagated back, i.e. this does nothing
var imageList = new UiImageList(scan.Select(x => new UiImage(x)).ToList());
if (_options.AltDeinterleave)
{

View File

@ -5,17 +5,13 @@ namespace NAPS2.Lib.Tests.Images;
public class ThumbnailRenderQueueTests : ContextualTexts
{
private readonly RecoveryStorageManager _recoveryStorageManager;
private readonly UiImageList _uiImageList;
private readonly ThumbnailRenderer _thumbnailRenderer;
private readonly ThumbnailRenderQueue _thumbnailRenderQueue;
public ThumbnailRenderQueueTests()
{
_recoveryStorageManager = RecoveryStorageManager.CreateFolder(Path.Combine(FolderPath, "recovery"));
_uiImageList = new UiImageList(_recoveryStorageManager);
_thumbnailRenderer = new ThumbnailRenderer(ImageContext);
_thumbnailRenderQueue = new ThumbnailRenderQueue(ScanningContext, _thumbnailRenderer);
_uiImageList = new UiImageList();
_thumbnailRenderQueue = new ThumbnailRenderQueue(ScanningContext, new ThumbnailRenderer(ImageContext));
}
public override void Dispose()
@ -47,7 +43,7 @@ public class ThumbnailRenderQueueTests : ContextualTexts
}
[Fact]
public async Task StartRendering_WithThumbnailNeeded_Renders()
public void StartRendering_WithThumbnailNeeded_Renders()
{
AppendToImageList(CreateScannedImage());
Assert.Null(_uiImageList.Images[0].GetThumbnailClone());
@ -55,7 +51,7 @@ public class ThumbnailRenderQueueTests : ContextualTexts
_thumbnailRenderQueue.SetThumbnailSize(128);
_thumbnailRenderQueue.StartRendering(_uiImageList);
await Task.Delay(200);
_thumbnailRenderQueue.WaitForRendering();
Assert.NotNull(_uiImageList.Images[0].GetThumbnailClone());
Assert.False(_uiImageList.Images[0].IsThumbnailDirty);

View File

@ -37,7 +37,7 @@ public class DesktopControllerTests : ContextualTexts
ScanningContext.RecoveryPath = Path.Combine(FolderPath, "recovery");
ScanningContext.FileStorageManager = new FileStorageManager(ScanningContext.RecoveryPath);
_recoveryStorageManager = RecoveryStorageManager.CreateFolder(ScanningContext.RecoveryPath);
_imageList = new UiImageList(_recoveryStorageManager);
_imageList = new UiImageList();
_thumbnailRenderQueue = new ThumbnailRenderQueue(ScanningContext, new ThumbnailRenderer(ImageContext));
_operationProgress = new Mock<OperationProgress>();
_config = Naps2Config.Stub();
@ -154,7 +154,7 @@ public class DesktopControllerTests : ContextualTexts
public async Task Initialize_WithUpdateChecksDisabled_DoesntCheckForUpdate()
{
await _desktopController.Initialize();
await Task.Delay(10);
await Task.Delay(50);
Assert.False(_config.Get(c => c.HasCheckedForUpdates));
Assert.Null(_config.Get(c => c.LastUpdateCheckDate));
@ -167,7 +167,7 @@ public class DesktopControllerTests : ContextualTexts
_config.User.Set(c => c.CheckForUpdates, true);
await _desktopController.Initialize();
await Task.Delay(10);
await Task.Delay(50);
Assert.True(_config.Get(c => c.HasCheckedForUpdates));
DateAsserts.Recent(TimeSpan.FromMilliseconds(100), _config.Get(c => c.LastUpdateCheckDate));
@ -185,7 +185,7 @@ public class DesktopControllerTests : ContextualTexts
_updateChecker.Setup(x => x.CheckForUpdates()).ReturnsAsync(mockUpdateInfo);
await _desktopController.Initialize();
await Task.Delay(10);
await Task.Delay(50);
Assert.True(_config.Get(c => c.HasCheckedForUpdates));
DateAsserts.Recent(TimeSpan.FromMilliseconds(100), _config.Get(c => c.LastUpdateCheckDate));
@ -202,7 +202,7 @@ public class DesktopControllerTests : ContextualTexts
_config.User.Set(c => c.CheckForUpdates, true);
await _desktopController.Initialize();
await Task.Delay(10);
await Task.Delay(50);
Assert.False(_config.Get(c => c.HasCheckedForUpdates));
Assert.Null(_config.Get(c => c.LastUpdateCheckDate));
@ -219,7 +219,7 @@ public class DesktopControllerTests : ContextualTexts
_config.User.Set(c => c.LastUpdateCheckDate, updateCheckDate);
await _desktopController.Initialize();
await Task.Delay(10);
await Task.Delay(50);
Assert.True(_config.Get(c => c.HasCheckedForUpdates));
Assert.Equal(updateCheckDate, _config.Get(c => c.LastUpdateCheckDate));
@ -239,7 +239,7 @@ public class DesktopControllerTests : ContextualTexts
_updateChecker.Setup(x => x.CheckForUpdates()).ReturnsAsync(mockUpdateInfo);
await _desktopController.Initialize();
await Task.Delay(10);
await Task.Delay(50);
Assert.True(_config.Get(c => c.HasCheckedForUpdates));
DateAsserts.Recent(TimeSpan.FromMilliseconds(100), _config.Get(c => c.LastUpdateCheckDate));

View File

@ -81,7 +81,7 @@ namespace NAPS2.WinForms
Shown += FDesktop_Shown;
FormClosing += FDesktop_FormClosing;
Closed += FDesktop_Closed;
imageList.ImagesUpdated += (_, _) =>
imageList.SelectionChanged += (_, _) =>
{
SafeInvoke(() =>
{
@ -89,6 +89,7 @@ namespace NAPS2.WinForms
_listView!.Selection = _imageList.Selection;
});
};
imageList.ImagesUpdated += (_, _) => SafeInvoke(UpdateToolbar);
_profileManager.ProfilesUpdated += (_, _) => UpdateScanButton();
_desktopFormProvider.DesktopForm = this;
}

View File

@ -22,11 +22,14 @@ namespace NAPS2.Images;
public class RecoveryStorageManager : IDisposable
{
public const string LOCK_FILE_NAME = ".lock";
private static readonly TimeSpan WriteThrottleInterval = TimeSpan.FromMilliseconds(100);
private readonly ISerializer<RecoveryIndex> _serializer = new XmlSerializer<RecoveryIndex>();
private readonly DirectoryInfo _folder;
private readonly FileInfo _folderLockFile;
private readonly Stream _folderLock;
private readonly TimedThrottle _writeThrottle;
private UiImageList _imageList;
private bool _disposed;
@ -42,10 +45,38 @@ public class RecoveryStorageManager : IDisposable
_folder.Create();
_folderLockFile = new FileInfo(Path.Combine(RecoveryFolderPath, LOCK_FILE_NAME));
_folderLock = _folderLockFile.Open(FileMode.CreateNew, FileAccess.Write, FileShare.None);
_writeThrottle = new TimedThrottle(WriteIndexFromImageList, WriteThrottleInterval);
}
public string RecoveryFolderPath { get; }
// TODO: Maybe just make UiImageList part of the constructor?
public void RegisterForChanges(UiImageList imageList)
{
lock (this)
{
if (_disposed) throw new ObjectDisposedException(nameof(RecoveryStorageManager));
if (_imageList != null) throw new InvalidOperationException();
_imageList = imageList;
imageList.ImagesUpdated += ImageListUpdated;
}
}
private void ImageListUpdated(object? sender, EventArgs args)
{
_writeThrottle.RunAction(null);
}
private void WriteIndexFromImageList()
{
List<UiImage> images;
lock (_imageList)
{
images = _imageList.Images.ToList();
}
WriteIndex(images);
}
public void WriteIndex(IEnumerable<UiImage> images)
{
lock (this)
@ -82,6 +113,10 @@ public class RecoveryStorageManager : IDisposable
_folderLock.Close();
_folderLockFile.Delete();
_folder.Delete(true);
if (_imageList != null)
{
_imageList.ImagesUpdated -= ImageListUpdated;
}
_disposed = true;
}
}

View File

@ -5,20 +5,15 @@ namespace NAPS2.Images;
public class UiImageList
{
private readonly RecoveryStorageManager _recoveryStorageManager;
private readonly TimedThrottle _runUpdateEventsThrottle;
private StateToken _savedState = new(ImmutableList<ProcessedImage.WeakReference>.Empty);
private ListSelection<UiImage> _selection;
public UiImageList(RecoveryStorageManager recoveryStorageManager)
: this(recoveryStorageManager, new List<UiImage>())
public UiImageList() : this(new List<UiImage>())
{
}
public UiImageList(RecoveryStorageManager recoveryStorageManager, List<UiImage> images)
public UiImageList(List<UiImage> images)
{
_recoveryStorageManager = recoveryStorageManager;
_runUpdateEventsThrottle = new TimedThrottle(RunUpdateEvents, TimeSpan.FromMilliseconds(100));
Images = images;
_selection = ListSelection.Empty<UiImage>();
}
@ -44,6 +39,8 @@ public class UiImageList
private set => _selection = value ?? throw new ArgumentNullException(nameof(value));
}
public event EventHandler? SelectionChanged;
public event EventHandler? ImagesUpdated;
public event EventHandler? ImagesThumbnailChanged;
@ -53,19 +50,17 @@ public class UiImageList
public void UpdateSelection(ListSelection<UiImage> newSelection)
{
Selection = newSelection;
ImagesUpdated?.Invoke(this, EventArgs.Empty);
SelectionChanged?.Invoke(this, EventArgs.Empty);
}
public void Mutate(ListMutation<UiImage> mutation, ListSelection<UiImage>? selectionToMutate = null)
{
MutateInternal(mutation, selectionToMutate);
_runUpdateEventsThrottle.RunAction(SynchronizationContext.Current);
}
public async Task MutateAsync(ListMutation<UiImage> mutation, ListSelection<UiImage>? selectionToMutate = null)
{
await Task.Run(() => MutateInternal(mutation, selectionToMutate));
_runUpdateEventsThrottle.RunAction(SynchronizationContext.Current);
}
private void MutateInternal(ListMutation<UiImage> mutation, ListSelection<UiImage>? selectionToMutate)
@ -110,11 +105,11 @@ public class UiImageList
var syncContext = SynchronizationContext.Current;
if (syncContext != null)
{
syncContext.Post(_ => _selection = currentSelection, null);
syncContext.Post(_ => UpdateSelection(currentSelection), null);
}
else
{
_selection = currentSelection;
UpdateSelection(currentSelection);
}
}
@ -128,15 +123,6 @@ public class UiImageList
ImagesThumbnailInvalidated?.Invoke(this, EventArgs.Empty);
}
private void RunUpdateEvents()
{
// TODO: Maybe move this out of this class to an event handler?
lock (this)
{
_recoveryStorageManager.WriteIndex(Images);
}
}
/// <summary>
/// A token that stores the current state of the image list for use in comparisons to determine if changes have been
/// made since the last save. No disposal is needed.