diff --git a/FileManager/PictureStorage.cs b/FileManager/PictureStorage.cs index 4fdd894b..f9f5a35d 100644 --- a/FileManager/PictureStorage.cs +++ b/FileManager/PictureStorage.cs @@ -1,8 +1,10 @@ using System; +using System.Collections.Concurrent; using System.Collections.Generic; using System.IO; using System.Linq; using System.Net.Http; +using System.Threading.Tasks; namespace FileManager { @@ -32,31 +34,34 @@ namespace FileManager private static string getPath(PictureDefinition def) => Path.Combine(ImagesDirectory, $"{def.PictureId}{def.Size}.jpg"); - private static System.Timers.Timer timer { get; } + static Task backgroundDownloader; + static PictureStorage() { - timer = new System.Timers.Timer(700) - { - AutoReset = true, - Enabled = true - }; - timer.Elapsed += (_, __) => timerDownload(); + backgroundDownloader = new Task(BackgroundDownloader); + backgroundDownloader.Start(); } public static event EventHandler PictureCached; + private static BlockingCollection DownloadQueue { get; } = new BlockingCollection(); private static Dictionary cache { get; } = new Dictionary(); + private static Dictionary defaultImages { get; } = new Dictionary(); public static (bool isDefault, byte[] bytes) GetPicture(PictureDefinition def) { - if (!cache.ContainsKey(def)) + if (cache.ContainsKey(def)) + return (false, cache[def]); + + var path = getPath(def); + + if (File.Exists(path)) { - var path = getPath(def); - cache[def] - = File.Exists(path) - ? File.ReadAllBytes(path) - : null; + cache[def] = File.ReadAllBytes(path); + return (false, cache[def]); } - return (cache[def] == null, cache[def] ?? getDefaultImage(def.Size)); + + DownloadQueue.Add(def); + return (true, getDefaultImage(def.Size)); } public static byte[] GetPictureSynchronously(PictureDefinition def) @@ -79,7 +84,6 @@ namespace FileManager return cache[def]; } - private static Dictionary defaultImages { get; } = new Dictionary(); public static void SetDefaultImage(PictureSize pictureSize, byte[] bytes) => defaultImages[pictureSize] = bytes; private static byte[] getDefaultImage(PictureSize size) @@ -87,28 +91,12 @@ namespace FileManager ? defaultImages[size] : new byte[0]; - // necessary to avoid IO errors. ReadAllBytes and WriteAllBytes can conflict in some cases, esp when debugging - private static bool isProcessing; - private static void timerDownload() + static void BackgroundDownloader() { - // must live outside try-catch, else 'finally' can reset another thread's lock - if (isProcessing) - return; - - try + while (!DownloadQueue.IsCompleted) { - isProcessing = true; - - var def = cache - .Where(kvp => kvp.Value is null) - .Select(kvp => kvp.Key) - // 80x80 should be 1st since it's enum value == 0 - .OrderBy(d => d.PictureId) - .FirstOrDefault(); - - // no more null entries. all requsted images are cached - if (string.IsNullOrWhiteSpace(def.PictureId)) - return; + if (!DownloadQueue.TryTake(out var def, System.Threading.Timeout.InfiniteTimeSpan)) + continue; var bytes = downloadBytes(def); saveFile(def, bytes); @@ -116,10 +104,6 @@ namespace FileManager PictureCached?.Invoke(nameof(PictureStorage), new PictureCachedEventArgs { Definition = def, Picture = bytes }); } - finally - { - isProcessing = false; - } } private static HttpClient imageDownloadClient { get; } = new HttpClient(); @@ -135,4 +119,4 @@ namespace FileManager File.WriteAllBytes(path, bytes); } } -} +} \ No newline at end of file diff --git a/LibationLauncher/LibationLauncher.csproj b/LibationLauncher/LibationLauncher.csproj index 9d263c16..390110a8 100644 --- a/LibationLauncher/LibationLauncher.csproj +++ b/LibationLauncher/LibationLauncher.csproj @@ -13,7 +13,7 @@ win-x64 - 5.4.9.253 + 5.4.9.258 diff --git a/LibationWinForms/BookLiberation/BaseForms/ProcessBaseForm.cs b/LibationWinForms/BookLiberation/BaseForms/ProcessBaseForm.cs index f8f89613..f97825c6 100644 --- a/LibationWinForms/BookLiberation/BaseForms/ProcessBaseForm.cs +++ b/LibationWinForms/BookLiberation/BaseForms/ProcessBaseForm.cs @@ -1,8 +1,6 @@ using DataLayer; -using Dinah.Core.Net.Http; using FileLiberator; using System; -using System.Windows.Forms; namespace LibationWinForms.BookLiberation { @@ -21,6 +19,7 @@ namespace LibationWinForms.BookLiberation processable.Begin += OnBegin; processable.Completed += OnCompleted; processable.StatusUpdate += OnStatusUpdate; + Disposed += OnUnsubscribeAll; } diff --git a/LibationWinForms/BookLiberation/BaseForms/StreamBaseForm.cs b/LibationWinForms/BookLiberation/BaseForms/StreamBaseForm.cs index e6cbde79..13337e13 100644 --- a/LibationWinForms/BookLiberation/BaseForms/StreamBaseForm.cs +++ b/LibationWinForms/BookLiberation/BaseForms/StreamBaseForm.cs @@ -16,7 +16,7 @@ namespace LibationWinForms.BookLiberation public StreamBaseForm() { - //Will not work if set outside constructor. + //Will be null if set outside constructor. SyncContext = SynchronizationContext.Current; } @@ -50,10 +50,11 @@ namespace LibationWinForms.BookLiberation #region IStreamable event handlers public virtual void OnStreamingBegin(object sender, string beginString) { - //If StreamingBegin is fired from a worker thread, the form will be created on - //that UI thread. Form.BeginInvoke won't work until the form is created (ie. shown), - //so we need to make certain that we show the form on the same thread that created - //this StreamBaseForm. + //If StreamingBegin is fired from a worker thread, the window will be created on + //that UI thread. We need to make certain that we show the window on the same + //thread that created form, otherwise the form and the window handle will be on + //different threads. Form.BeginInvoke won't work until the form is created + //(ie. shown) because control doesn't get a window handle until it is Shown. static void sendCallback(object asyncArgs) { @@ -64,16 +65,11 @@ namespace LibationWinForms.BookLiberation Action code = Show; if (InvokeRequired) - { - var args = new AsyncCompletedEventArgs(null, false, code); SyncContext.Send( sendCallback, - args); - } + new AsyncCompletedEventArgs(null, false, code)); else - { code(); - } } public virtual void OnStreamingProgressChanged(object sender, DownloadProgress downloadProgress) { } public virtual void OnStreamingTimeRemaining(object sender, TimeSpan timeRemaining) { } diff --git a/LibationWinForms/BookLiberation/ProcessorAutomationController.cs b/LibationWinForms/BookLiberation/ProcessorAutomationController.cs index 3704289b..1575be89 100644 --- a/LibationWinForms/BookLiberation/ProcessorAutomationController.cs +++ b/LibationWinForms/BookLiberation/ProcessorAutomationController.cs @@ -54,7 +54,7 @@ namespace LibationWinForms.BookLiberation { Serilog.Log.Logger.Information("Begin backup single {@DebugInfo}", new { libraryBook?.Book?.AudibleProductId }); - LogMe logMe = LogMe.RegisterForm(); + var logMe = LogMe.RegisterForm(); var backupBook = CreateBackupBook(completedAction, logMe); // continue even if libraryBook is null. we'll display even that in the processing box @@ -66,8 +66,7 @@ namespace LibationWinForms.BookLiberation Serilog.Log.Logger.Information("Begin " + nameof(BackupAllBooksAsync)); var automatedBackupsForm = new AutomatedBackupsForm(); - LogMe logMe = LogMe.RegisterForm(automatedBackupsForm); - + var logMe = LogMe.RegisterForm(automatedBackupsForm); var backupBook = CreateBackupBook(completedAction, logMe); await new BackupLoop(logMe, backupBook, automatedBackupsForm).RunBackupAsync(); @@ -78,7 +77,7 @@ namespace LibationWinForms.BookLiberation Serilog.Log.Logger.Information("Begin " + nameof(ConvertAllBooksAsync)); var automatedBackupsForm = new AutomatedBackupsForm(); - LogMe logMe = LogMe.RegisterForm(automatedBackupsForm); + var logMe = LogMe.RegisterForm(automatedBackupsForm); var convertBook = CreateStreamProcessable(null, logMe); @@ -90,7 +89,7 @@ namespace LibationWinForms.BookLiberation Serilog.Log.Logger.Information("Begin " + nameof(BackupAllPdfsAsync)); var automatedBackupsForm = new AutomatedBackupsForm(); - LogMe logMe = LogMe.RegisterForm(automatedBackupsForm); + var logMe = LogMe.RegisterForm(automatedBackupsForm); var downloadPdf = CreateStreamProcessable(completedAction, logMe); @@ -99,13 +98,15 @@ namespace LibationWinForms.BookLiberation public static void DownloadFile(string url, string destination, bool showDownloadCompletedDialog = false) { + Serilog.Log.Logger.Information($"Begin {nameof(DownloadFile)} for {url}"); + void onDownloadFileStreamingCompleted(object o, string s) { if (showDownloadCompletedDialog) MessageBox.Show("File downloaded to:\r\n\r\n" + s); } - DownloadFile downloadFile = CreateStreamable(onDownloadFileStreamingCompleted); + var downloadFile = CreateStreamable(onDownloadFileStreamingCompleted); new System.Threading.Thread(() => downloadFile.PerformDownloadFileAsync(url, destination).GetAwaiter().GetResult()) { IsBackground = true } @@ -132,7 +133,7 @@ namespace LibationWinForms.BookLiberation /// Create a new and links it to a new . /// /// The derrived type to create. - /// The derrived Form to create on and and be Shown on + /// The derrived Form to create on , Show on , and Close & Dispose on /// An additional event handler to handle /// A new of type private static TStrProc CreateStreamProcessable(EventHandler completedAction = null, LogMe logMe = null) @@ -157,14 +158,15 @@ namespace LibationWinForms.BookLiberation /// /// Creates a new and links it to a new /// - /// The derrived type to create. - /// The derrived Form to create, which will be Shown on . + /// The derrived type to create. + /// The derrived Form to create, which will Show on and Close & Dispose on . /// An additional event handler to handle - private static TStrProc CreateStreamable(EventHandler completedAction = null) + /// A new of type + private static TStr CreateStreamable(EventHandler completedAction = null) where TForm : StreamBaseForm, new() - where TStrProc : IStreamable, new() + where TStr : IStreamable, new() { - var streamable = new TStrProc(); + var streamable = new TStr(); var streamForm = new TForm(); streamForm.SetStreamable(streamable);