Refactoring and documenting.

This commit is contained in:
Michael Bucari-Tovo 2021-08-11 23:22:45 -06:00
parent 79ed92f303
commit b5d941d479
5 changed files with 47 additions and 66 deletions

View File

@ -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<PictureCachedEventArgs> PictureCached;
private static BlockingCollection<PictureDefinition> DownloadQueue { get; } = new BlockingCollection<PictureDefinition>();
private static Dictionary<PictureDefinition, byte[]> cache { get; } = new Dictionary<PictureDefinition, byte[]>();
private static Dictionary<PictureSize, byte[]> defaultImages { get; } = new Dictionary<PictureSize, byte[]>();
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<PictureSize, byte[]> defaultImages { get; } = new Dictionary<PictureSize, byte[]>();
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);
}
}
}
}

View File

@ -13,7 +13,7 @@
<!-- <PublishSingleFile>true</PublishSingleFile> -->
<RuntimeIdentifier>win-x64</RuntimeIdentifier>
<Version>5.4.9.253</Version>
<Version>5.4.9.258</Version>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">

View File

@ -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;
}

View File

@ -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) { }

View File

@ -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<ConvertToMp3, AudioConvertForm>(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<DownloadPdf, PdfDownloadForm>(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<DownloadFile, DownloadForm>(onDownloadFileStreamingCompleted);
var downloadFile = CreateStreamable<DownloadFile, DownloadForm>(onDownloadFileStreamingCompleted);
new System.Threading.Thread(() => downloadFile.PerformDownloadFileAsync(url, destination).GetAwaiter().GetResult())
{ IsBackground = true }
@ -132,7 +133,7 @@ namespace LibationWinForms.BookLiberation
/// Create a new <see cref="IStreamProcessable"/> and links it to a new <see cref="ProcessBaseForm"/>.
/// </summary>
/// <typeparam name="TStrProc">The <see cref="IStreamProcessable"/> derrived type to create.</typeparam>
/// <typeparam name="TForm">The <see cref="ProcessBaseForm"/> derrived Form to create on <see cref="IProcessable.Begin"/> and and be Shown on <see cref="IStreamable.StreamingBegin"/></typeparam>
/// <typeparam name="TForm">The <see cref="ProcessBaseForm"/> derrived Form to create on <see cref="IProcessable.Begin"/>, Show on <see cref="IStreamable.StreamingBegin"/>, and Close & Dispose on <see cref="IStreamable.StreamingCompleted"/></typeparam>
/// <param name="completedAction">An additional event handler to handle <see cref="IProcessable.Completed"/></param>
/// <returns>A new <see cref="IStreamProcessable"/> of type <typeparamref name="TStrProc"/></returns>
private static TStrProc CreateStreamProcessable<TStrProc, TForm>(EventHandler<LibraryBook> completedAction = null, LogMe logMe = null)
@ -157,14 +158,15 @@ namespace LibationWinForms.BookLiberation
/// <summary>
/// Creates a new <see cref="IStreamable"/> and links it to a new <see cref="StreamBaseForm"/>
/// </summary>
/// <typeparam name="TStrProc">The <see cref="IStreamable"/> derrived type to create.</typeparam>
/// <typeparam name="TForm">The <see cref="StreamBaseForm"/> derrived Form to create, which will be Shown on <see cref="IStreamable.StreamingBegin"/>.</typeparam>
/// <typeparam name="TStr">The <see cref="IStreamable"/> derrived type to create.</typeparam>
/// <typeparam name="TForm">The <see cref="StreamBaseForm"/> derrived Form to create, which will Show on <see cref="IStreamable.StreamingBegin"/> and Close & Dispose on <see cref="IStreamable.StreamingCompleted"/>.</typeparam>
/// <param name="completedAction">An additional event handler to handle <see cref="IStreamable.StreamingCompleted"/></param>
private static TStrProc CreateStreamable<TStrProc, TForm>(EventHandler<string> completedAction = null)
/// <returns>A new <see cref="IStreamable"/> of type <typeparamref name="TStr"/></returns>
private static TStr CreateStreamable<TStr, TForm>(EventHandler<string> 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);