Added Cancel method to stop download/decrypt and added estimated time remaining event.

This commit is contained in:
Michael Bucari-Tovo 2021-06-28 15:21:59 -06:00
parent b65f9567e0
commit f0eb57a40b
9 changed files with 146 additions and 93 deletions

View File

@ -3,13 +3,14 @@ using Dinah.Core.Diagnostics;
using Dinah.Core.IO; using Dinah.Core.IO;
using Dinah.Core.StepRunner; using Dinah.Core.StepRunner;
using System; using System;
using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
namespace AaxDecrypter namespace AaxDecrypter
{ {
public interface ISimpleAaxToM4bConverter2 public interface ISimpleAaxToM4bConverter
{ {
event EventHandler<int> DecryptProgressUpdate; event EventHandler<int> DecryptProgressUpdate;
bool Run(); bool Run();
@ -23,8 +24,9 @@ namespace AaxDecrypter
string Narrator { get; } string Narrator { get; }
byte[] CoverArt { get; } byte[] CoverArt { get; }
} }
public interface IAdvancedAaxcToM4bConverter : ISimpleAaxToM4bConverter2 public interface IAdvancedAaxcToM4bConverter : ISimpleAaxToM4bConverter
{ {
void Cancel();
bool Step1_CreateDir(); bool Step1_CreateDir();
bool Step2_DownloadAndCombine(); bool Step2_DownloadAndCombine();
bool Step3_RestoreMetadata(); bool Step3_RestoreMetadata();
@ -34,6 +36,7 @@ namespace AaxDecrypter
public class AaxcDownloadConverter : IAdvancedAaxcToM4bConverter public class AaxcDownloadConverter : IAdvancedAaxcToM4bConverter
{ {
public event EventHandler<int> DecryptProgressUpdate; public event EventHandler<int> DecryptProgressUpdate;
public event EventHandler<TimeSpan> DecryptTimeRemaining;
public string AppName { get; set; } = nameof(AaxcDownloadConverter); public string AppName { get; set; } = nameof(AaxcDownloadConverter);
public string outDir { get; private set; } public string outDir { get; private set; }
public string outputFileName { get; private set; } public string outputFileName { get; private set; }
@ -46,7 +49,7 @@ namespace AaxDecrypter
private TagLib.Mpeg4.File aaxcTagLib { get; set; } private TagLib.Mpeg4.File aaxcTagLib { get; set; }
private StepSequence steps { get; } private StepSequence steps { get; }
private DownloadLicense downloadLicense { get; set; } private DownloadLicense downloadLicense { get; set; }
private FFMpegAaxcProcesser aaxcProcesser;
public static async Task<AaxcDownloadConverter> CreateAsync(string outDirectory, DownloadLicense dlLic, ChapterInfo chapters = null) public static async Task<AaxcDownloadConverter> CreateAsync(string outDirectory, DownloadLicense dlLic, ChapterInfo chapters = null)
{ {
var converter = new AaxcDownloadConverter(outDirectory, dlLic, chapters); var converter = new AaxcDownloadConverter(outDirectory, dlLic, chapters);
@ -132,7 +135,7 @@ namespace AaxDecrypter
public bool Step2_DownloadAndCombine() public bool Step2_DownloadAndCombine()
{ {
var aaxcProcesser = new FFMpegAaxcProcesser(downloadLicense); aaxcProcesser = new FFMpegAaxcProcesser(downloadLicense);
aaxcProcesser.ProgressUpdate += AaxcProcesser_ProgressUpdate; aaxcProcesser.ProgressUpdate += AaxcProcesser_ProgressUpdate;
bool userSuppliedChapters = chapters != null; bool userSuppliedChapters = chapters != null;
@ -166,11 +169,72 @@ namespace AaxDecrypter
private void AaxcProcesser_ProgressUpdate(object sender, TimeSpan e) private void AaxcProcesser_ProgressUpdate(object sender, TimeSpan e)
{ {
double averageRate = getAverageProcessRate(e);
double remainingSecsToProcess = (aaxcTagLib.Properties.Duration - e).TotalSeconds;
double estTimeRemaining = remainingSecsToProcess / averageRate;
if (double.IsNormal(estTimeRemaining))
DecryptTimeRemaining?.Invoke(this, TimeSpan.FromSeconds(estTimeRemaining));
double progressPercent = 100 * e.TotalSeconds / aaxcTagLib.Properties.Duration.TotalSeconds; double progressPercent = 100 * e.TotalSeconds / aaxcTagLib.Properties.Duration.TotalSeconds;
DecryptProgressUpdate?.Invoke(this, (int)progressPercent); DecryptProgressUpdate?.Invoke(this, (int)progressPercent);
} }
/// <summary>
/// Calculates the average processing rate based on the last <see cref="MAX_NUM_AVERAGE"/> samples.
/// </summary>
/// <param name="lastProcessedPosition">Position in the audio file last processed</param>
/// <returns>The average processing rate, in book_duration_seconds / second.</returns>
private double getAverageProcessRate(TimeSpan lastProcessedPosition)
{
streamPositions.Enqueue(new StreamPosition
{
ProcessPosition = lastProcessedPosition,
EventTime = DateTime.Now,
});
if (streamPositions.Count < 2)
return double.PositiveInfinity;
//Calculate the harmonic mean of the last AVERAGE_NUM progress updates
//Units are Book_Duration_Seconds / second
var lastPos = streamPositions.Count > MAX_NUM_AVERAGE ? streamPositions.Dequeue() : null;
double harmonicDenominator = 0;
int harmonicNumerator = 0;
foreach (var pos in streamPositions)
{
if (lastPos is null)
{
lastPos = pos;
continue;
}
double dP = (pos.ProcessPosition - lastPos.ProcessPosition).TotalSeconds;
double dT = (pos.EventTime - lastPos.EventTime).TotalSeconds;
harmonicDenominator += dT / dP;
harmonicNumerator++;
lastPos = pos;
}
double harmonicMean = harmonicNumerator / harmonicDenominator;
return harmonicMean;
}
private const int MAX_NUM_AVERAGE = 15;
private class StreamPosition
{
public TimeSpan ProcessPosition { get; set; }
public DateTime EventTime { get; set; }
}
private Queue<StreamPosition> streamPositions = new Queue<StreamPosition>();
/// <summary> /// <summary>
/// Copy all aacx metadata to m4b file, including cover art. /// Copy all aacx metadata to m4b file, including cover art.
/// </summary> /// </summary>
@ -207,5 +271,10 @@ namespace AaxDecrypter
File.WriteAllText(PathLib.ReplaceExtension(outputFileName, ".nfo"), NFO.CreateContents(AppName, aaxcTagLib, chapters)); File.WriteAllText(PathLib.ReplaceExtension(outputFileName, ".nfo"), NFO.CreateContents(AppName, aaxcTagLib, chapters));
return true; return true;
} }
public void Cancel()
{
aaxcProcesser.Cancel();
}
} }
} }

View File

@ -25,6 +25,8 @@ namespace AaxDecrypter
private StringBuilder remuxerError = new StringBuilder(); private StringBuilder remuxerError = new StringBuilder();
private StringBuilder downloaderError = new StringBuilder(); private StringBuilder downloaderError = new StringBuilder();
private static Regex processedTimeRegex = new Regex("time=(\\d{2}):(\\d{2}):(\\d{2}).\\d{2}", RegexOptions.IgnoreCase | RegexOptions.Compiled); private static Regex processedTimeRegex = new Regex("time=(\\d{2}):(\\d{2}):(\\d{2}).\\d{2}", RegexOptions.IgnoreCase | RegexOptions.Compiled);
private Process downloader;
private Process remuxer;
public FFMpegAaxcProcesser( DownloadLicense downloadLicense) public FFMpegAaxcProcesser( DownloadLicense downloadLicense)
{ {
@ -36,14 +38,14 @@ namespace AaxDecrypter
{ {
//This process gets the aaxc from the url and streams the decrypted //This process gets the aaxc from the url and streams the decrypted
//aac stream to standard output //aac stream to standard output
var downloader = new Process downloader = new Process
{ {
StartInfo = getDownloaderStartInfo() StartInfo = getDownloaderStartInfo()
}; };
//This process retreves an aac stream from standard input and muxes //This process retreves an aac stream from standard input and muxes
// it into an m4b along with the cover art and metadata. // it into an m4b along with the cover art and metadata.
var remuxer = new Process remuxer = new Process
{ {
StartInfo = getRemuxerStartInfo(outputFile, ffmetaChaptersPath) StartInfo = getRemuxerStartInfo(outputFile, ffmetaChaptersPath)
}; };
@ -90,7 +92,11 @@ namespace AaxDecrypter
IsRunning = false; IsRunning = false;
Succeeded = downloader.ExitCode == 0 && remuxer.ExitCode == 0; Succeeded = downloader.ExitCode == 0 && remuxer.ExitCode == 0;
} }
public void Cancel()
{
if (IsRunning && !remuxer.HasExited)
remuxer.Kill();
}
private void Downloader_ErrorDataReceived(object sender, DataReceivedEventArgs e) private void Downloader_ErrorDataReceived(object sender, DataReceivedEventArgs e)
{ {
if (string.IsNullOrEmpty(e.Data)) if (string.IsNullOrEmpty(e.Data))

View File

@ -22,10 +22,12 @@ namespace FileLiberator.AaxcDownloadDecrypt
public event EventHandler<string> NarratorsDiscovered; public event EventHandler<string> NarratorsDiscovered;
public event EventHandler<byte[]> CoverImageFilepathDiscovered; public event EventHandler<byte[]> CoverImageFilepathDiscovered;
public event EventHandler<int> UpdateProgress; public event EventHandler<int> UpdateProgress;
public event EventHandler<TimeSpan> UpdateRemainingTime;
public event EventHandler<string> DecryptCompleted; public event EventHandler<string> DecryptCompleted;
public event EventHandler<LibraryBook> Completed; public event EventHandler<LibraryBook> Completed;
public event EventHandler<string> StatusUpdate; public event EventHandler<string> StatusUpdate;
private AaxcDownloadConverter aaxcDownloader;
public async Task<StatusHandler> ProcessAsync(LibraryBook libraryBook) public async Task<StatusHandler> ProcessAsync(LibraryBook libraryBook)
{ {
Begin?.Invoke(this, libraryBook); Begin?.Invoke(this, libraryBook);
@ -74,7 +76,6 @@ namespace FileLiberator.AaxcDownloadDecrypt
var destinationDirectory = Path.GetDirectoryName(destinationDir); var destinationDirectory = Path.GetDirectoryName(destinationDir);
AaxcDownloadConverter newDownloader;
if (Configuration.Instance.DownloadChapters) if (Configuration.Instance.DownloadChapters)
{ {
var contentMetadata = await api.GetLibraryBookMetadataAsync(libraryBook.Book.AudibleProductId); var contentMetadata = await api.GetLibraryBookMetadataAsync(libraryBook.Book.AudibleProductId);
@ -84,33 +85,34 @@ namespace FileLiberator.AaxcDownloadDecrypt
foreach (var chap in contentMetadata?.ChapterInfo?.Chapters) foreach (var chap in contentMetadata?.ChapterInfo?.Chapters)
aaxcDecryptChapters.AddChapter(new Chapter(chap.Title, chap.StartOffsetMs, chap.LengthMs)); aaxcDecryptChapters.AddChapter(new Chapter(chap.Title, chap.StartOffsetMs, chap.LengthMs));
newDownloader = await AaxcDownloadConverter.CreateAsync(destinationDirectory, aaxcDecryptDlLic, aaxcDecryptChapters); aaxcDownloader = await AaxcDownloadConverter.CreateAsync(destinationDirectory, aaxcDecryptDlLic, aaxcDecryptChapters);
} }
else else
{ {
newDownloader = await AaxcDownloadConverter.CreateAsync(destinationDirectory, aaxcDecryptDlLic); aaxcDownloader = await AaxcDownloadConverter.CreateAsync(destinationDirectory, aaxcDecryptDlLic);
} }
newDownloader.AppName = "Libation"; aaxcDownloader.AppName = "Libation";
TitleDiscovered?.Invoke(this, newDownloader.Title); TitleDiscovered?.Invoke(this, aaxcDownloader.Title);
AuthorsDiscovered?.Invoke(this, newDownloader.Author); AuthorsDiscovered?.Invoke(this, aaxcDownloader.Author);
NarratorsDiscovered?.Invoke(this, newDownloader.Narrator); NarratorsDiscovered?.Invoke(this, aaxcDownloader.Narrator);
CoverImageFilepathDiscovered?.Invoke(this, newDownloader.CoverArt); CoverImageFilepathDiscovered?.Invoke(this, aaxcDownloader.CoverArt);
// override default which was set in CreateAsync // override default which was set in CreateAsync
var proposedOutputFile = Path.Combine(destinationDir, $"{libraryBook.Book.Title} [{libraryBook.Book.AudibleProductId}].m4b"); var proposedOutputFile = Path.Combine(destinationDir, $"{libraryBook.Book.Title} [{libraryBook.Book.AudibleProductId}].m4b");
newDownloader.SetOutputFilename(proposedOutputFile); aaxcDownloader.SetOutputFilename(proposedOutputFile);
newDownloader.DecryptProgressUpdate += (s, progress) => UpdateProgress?.Invoke(this, progress); aaxcDownloader.DecryptProgressUpdate += (s, progress) => UpdateProgress?.Invoke(this, progress);
aaxcDownloader.DecryptTimeRemaining += (s, remaining) => UpdateRemainingTime?.Invoke(this, remaining);
// REAL WORK DONE HERE // REAL WORK DONE HERE
var success = await Task.Run(() => newDownloader.Run()); var success = await Task.Run(() => aaxcDownloader.Run());
// decrypt failed // decrypt failed
if (!success) if (!success)
return null; return null;
return newDownloader.outputFileName; return aaxcDownloader.outputFileName;
} }
finally finally
{ {
@ -195,6 +197,9 @@ namespace FileLiberator.AaxcDownloadDecrypt
=> !AudibleFileStorage.Audio.Exists(libraryBook.Book.AudibleProductId) => !AudibleFileStorage.Audio.Exists(libraryBook.Book.AudibleProductId)
&& !AudibleFileStorage.AAX.Exists(libraryBook.Book.AudibleProductId); && !AudibleFileStorage.AAX.Exists(libraryBook.Book.AudibleProductId);
public void Cancel()
{
aaxcDownloader.Cancel();
}
} }
} }

View File

@ -11,7 +11,9 @@ namespace FileLiberator
event EventHandler<string> NarratorsDiscovered; event EventHandler<string> NarratorsDiscovered;
event EventHandler<byte[]> CoverImageFilepathDiscovered; event EventHandler<byte[]> CoverImageFilepathDiscovered;
event EventHandler<int> UpdateProgress; event EventHandler<int> UpdateProgress;
event EventHandler<TimeSpan> UpdateRemainingTime;
event EventHandler<string> DecryptCompleted; event EventHandler<string> DecryptCompleted;
void Cancel();
} }
} }

View File

@ -13,7 +13,7 @@
<!-- <PublishSingleFile>true</PublishSingleFile> --> <!-- <PublishSingleFile>true</PublishSingleFile> -->
<RuntimeIdentifier>win-x64</RuntimeIdentifier> <RuntimeIdentifier>win-x64</RuntimeIdentifier>
<Version>4.4.0.121</Version> <Version>4.4.0.181</Version>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>

View File

@ -32,14 +32,16 @@
this.bookInfoLbl = new System.Windows.Forms.Label(); this.bookInfoLbl = new System.Windows.Forms.Label();
this.progressBar1 = new System.Windows.Forms.ProgressBar(); this.progressBar1 = new System.Windows.Forms.ProgressBar();
this.rtbLog = new System.Windows.Forms.RichTextBox(); this.rtbLog = new System.Windows.Forms.RichTextBox();
this.remainingTimeLbl = new System.Windows.Forms.Label();
((System.ComponentModel.ISupportInitialize)(this.pictureBox1)).BeginInit(); ((System.ComponentModel.ISupportInitialize)(this.pictureBox1)).BeginInit();
this.SuspendLayout(); this.SuspendLayout();
// //
// pictureBox1 // pictureBox1
// //
this.pictureBox1.Location = new System.Drawing.Point(12, 12); this.pictureBox1.Location = new System.Drawing.Point(14, 14);
this.pictureBox1.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3);
this.pictureBox1.Name = "pictureBox1"; this.pictureBox1.Name = "pictureBox1";
this.pictureBox1.Size = new System.Drawing.Size(100, 100); this.pictureBox1.Size = new System.Drawing.Size(117, 115);
this.pictureBox1.SizeMode = System.Windows.Forms.PictureBoxSizeMode.StretchImage; this.pictureBox1.SizeMode = System.Windows.Forms.PictureBoxSizeMode.StretchImage;
this.pictureBox1.TabIndex = 0; this.pictureBox1.TabIndex = 0;
this.pictureBox1.TabStop = false; this.pictureBox1.TabStop = false;
@ -47,9 +49,10 @@
// bookInfoLbl // bookInfoLbl
// //
this.bookInfoLbl.AutoSize = true; this.bookInfoLbl.AutoSize = true;
this.bookInfoLbl.Location = new System.Drawing.Point(118, 12); this.bookInfoLbl.Location = new System.Drawing.Point(138, 14);
this.bookInfoLbl.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0);
this.bookInfoLbl.Name = "bookInfoLbl"; this.bookInfoLbl.Name = "bookInfoLbl";
this.bookInfoLbl.Size = new System.Drawing.Size(100, 13); this.bookInfoLbl.Size = new System.Drawing.Size(121, 15);
this.bookInfoLbl.TabIndex = 0; this.bookInfoLbl.TabIndex = 0;
this.bookInfoLbl.Text = "[multi-line book info]"; this.bookInfoLbl.Text = "[multi-line book info]";
// //
@ -57,9 +60,10 @@
// //
this.progressBar1.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left) this.progressBar1.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left)
| System.Windows.Forms.AnchorStyles.Right))); | System.Windows.Forms.AnchorStyles.Right)));
this.progressBar1.Location = new System.Drawing.Point(12, 526); this.progressBar1.Location = new System.Drawing.Point(14, 607);
this.progressBar1.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3);
this.progressBar1.Name = "progressBar1"; this.progressBar1.Name = "progressBar1";
this.progressBar1.Size = new System.Drawing.Size(582, 23); this.progressBar1.Size = new System.Drawing.Size(611, 27);
this.progressBar1.TabIndex = 2; this.progressBar1.TabIndex = 2;
// //
// rtbLog // rtbLog
@ -67,21 +71,33 @@
this.rtbLog.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) this.rtbLog.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom)
| System.Windows.Forms.AnchorStyles.Left) | System.Windows.Forms.AnchorStyles.Left)
| System.Windows.Forms.AnchorStyles.Right))); | System.Windows.Forms.AnchorStyles.Right)));
this.rtbLog.Location = new System.Drawing.Point(12, 118); this.rtbLog.Location = new System.Drawing.Point(14, 136);
this.rtbLog.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3);
this.rtbLog.Name = "rtbLog"; this.rtbLog.Name = "rtbLog";
this.rtbLog.Size = new System.Drawing.Size(582, 402); this.rtbLog.Size = new System.Drawing.Size(678, 463);
this.rtbLog.TabIndex = 1; this.rtbLog.TabIndex = 1;
this.rtbLog.Text = ""; this.rtbLog.Text = "";
// //
// remainingTimeLbl
//
this.remainingTimeLbl.Location = new System.Drawing.Point(632, 607);
this.remainingTimeLbl.Name = "remainingTimeLbl";
this.remainingTimeLbl.Size = new System.Drawing.Size(60, 31);
this.remainingTimeLbl.TabIndex = 3;
this.remainingTimeLbl.Text = "ETA:\r\n";
this.remainingTimeLbl.TextAlign = System.Drawing.ContentAlignment.TopRight;
//
// DecryptForm // DecryptForm
// //
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); this.AutoScaleDimensions = new System.Drawing.SizeF(7F, 15F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.ClientSize = new System.Drawing.Size(606, 561); this.ClientSize = new System.Drawing.Size(707, 647);
this.Controls.Add(this.remainingTimeLbl);
this.Controls.Add(this.rtbLog); this.Controls.Add(this.rtbLog);
this.Controls.Add(this.progressBar1); this.Controls.Add(this.progressBar1);
this.Controls.Add(this.bookInfoLbl); this.Controls.Add(this.bookInfoLbl);
this.Controls.Add(this.pictureBox1); this.Controls.Add(this.pictureBox1);
this.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3);
this.Name = "DecryptForm"; this.Name = "DecryptForm";
this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent; this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent;
this.Text = "DecryptForm"; this.Text = "DecryptForm";
@ -99,5 +115,6 @@
private System.Windows.Forms.Label bookInfoLbl; private System.Windows.Forms.Label bookInfoLbl;
private System.Windows.Forms.ProgressBar progressBar1; private System.Windows.Forms.ProgressBar progressBar1;
private System.Windows.Forms.RichTextBox rtbLog; private System.Windows.Forms.RichTextBox rtbLog;
private System.Windows.Forms.Label remainingTimeLbl;
} }
} }

View File

@ -59,6 +59,17 @@ namespace LibationWinForms.BookLiberation
public void SetCoverImage(byte[] coverBytes) public void SetCoverImage(byte[] coverBytes)
=> pictureBox1.UIThread(() => pictureBox1.Image = ImageReader.ToImage(coverBytes)); => pictureBox1.UIThread(() => pictureBox1.Image = ImageReader.ToImage(coverBytes));
public void UpdateProgress(int percentage) => progressBar1.UIThread(() => progressBar1.Value = percentage); public void UpdateProgress(int percentage)
{
if (percentage == 0)
remainingTimeLbl.UIThread(() => remainingTimeLbl.Text = "ETA:\r\n0 sec");
progressBar1.UIThread(() => progressBar1.Value = percentage);
}
public void UpdateRemainingTime(TimeSpan remaining)
{
remainingTimeLbl.UIThread(() => remainingTimeLbl.Text = $"ETA:\r\n{(int)remaining.TotalSeconds} sec");
}
} }
} }

View File

@ -1,64 +1,4 @@
<?xml version="1.0" encoding="utf-8"?> <root>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata"> <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" /> <xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true"> <xsd:element name="root" msdata:IsDataSet="true">

View File

@ -264,6 +264,7 @@ namespace LibationWinForms.BookLiberation
void narratorsDiscovered(object _, string narrators) => decryptDialog.SetNarratorNames(narrators); void narratorsDiscovered(object _, string narrators) => decryptDialog.SetNarratorNames(narrators);
void coverImageFilepathDiscovered(object _, byte[] coverBytes) => decryptDialog.SetCoverImage(coverBytes); void coverImageFilepathDiscovered(object _, byte[] coverBytes) => decryptDialog.SetCoverImage(coverBytes);
void updateProgress(object _, int percentage) => decryptDialog.UpdateProgress(percentage); void updateProgress(object _, int percentage) => decryptDialog.UpdateProgress(percentage);
void updateRemainingTime(object _, TimeSpan remaining) => decryptDialog.UpdateRemainingTime(remaining);
void decryptCompleted(object _, string __) => decryptDialog.Close(); void decryptCompleted(object _, string __) => decryptDialog.Close();
#endregion #endregion
@ -276,6 +277,7 @@ namespace LibationWinForms.BookLiberation
decryptBook.NarratorsDiscovered += narratorsDiscovered; decryptBook.NarratorsDiscovered += narratorsDiscovered;
decryptBook.CoverImageFilepathDiscovered += coverImageFilepathDiscovered; decryptBook.CoverImageFilepathDiscovered += coverImageFilepathDiscovered;
decryptBook.UpdateProgress += updateProgress; decryptBook.UpdateProgress += updateProgress;
decryptBook.UpdateRemainingTime += updateRemainingTime;
decryptBook.DecryptCompleted += decryptCompleted; decryptBook.DecryptCompleted += decryptCompleted;
#endregion #endregion
@ -293,6 +295,7 @@ namespace LibationWinForms.BookLiberation
decryptBook.UpdateProgress -= updateProgress; decryptBook.UpdateProgress -= updateProgress;
decryptBook.DecryptCompleted -= decryptCompleted; decryptBook.DecryptCompleted -= decryptCompleted;
decryptBook.Cancel();
}; };
#endregion #endregion
} }