This commit is contained in:
Michael Bucari-Tovo 2022-05-14 03:16:48 -06:00
parent 676af0210b
commit eeb4f4681a
10 changed files with 1502 additions and 752 deletions

View File

@ -9,435 +9,436 @@ using System.Threading;
namespace AaxDecrypter namespace AaxDecrypter
{ {
/// <summary> /// <summary>
/// A <see cref="CookieContainer"/> for a single Uri. /// A <see cref="CookieContainer"/> for a single Uri.
/// </summary> /// </summary>
public class SingleUriCookieContainer : CookieContainer public class SingleUriCookieContainer : CookieContainer
{ {
private Uri baseAddress; private Uri baseAddress;
public Uri Uri public Uri Uri
{ {
get => baseAddress; get => baseAddress;
set set
{ {
baseAddress = new UriBuilder(value.Scheme, value.Host).Uri; baseAddress = new UriBuilder(value.Scheme, value.Host).Uri;
} }
} }
public CookieCollection GetCookies() public CookieCollection GetCookies()
{ {
return GetCookies(Uri); return GetCookies(Uri);
} }
} }
/// <summary> /// <summary>
/// A resumable, simultaneous file downloader and reader. /// A resumable, simultaneous file downloader and reader.
/// </summary> /// </summary>
public class NetworkFileStream : Stream, IUpdatable public class NetworkFileStream : Stream, IUpdatable
{ {
public event EventHandler Updated; public event EventHandler Updated;
#region Public Properties #region Public Properties
/// <summary> /// <summary>
/// Location to save the downloaded data. /// Location to save the downloaded data.
/// </summary> /// </summary>
[JsonProperty(Required = Required.Always)] [JsonProperty(Required = Required.Always)]
public string SaveFilePath { get; } public string SaveFilePath { get; }
/// <summary> /// <summary>
/// Http(s) address of the file to download. /// Http(s) address of the file to download.
/// </summary> /// </summary>
[JsonProperty(Required = Required.Always)] [JsonProperty(Required = Required.Always)]
public Uri Uri { get; private set; } public Uri Uri { get; private set; }
/// <summary> /// <summary>
/// All cookies set by caller or by the remote server. /// All cookies set by caller or by the remote server.
/// </summary> /// </summary>
[JsonProperty(Required = Required.Always)] [JsonProperty(Required = Required.Always)]
public SingleUriCookieContainer CookieContainer { get; } public SingleUriCookieContainer CookieContainer { get; }
/// <summary> /// <summary>
/// Http headers to be sent to the server with the request. /// Http headers to be sent to the server with the request.
/// </summary> /// </summary>
[JsonProperty(Required = Required.Always)] [JsonProperty(Required = Required.Always)]
public WebHeaderCollection RequestHeaders { get; private set; } public WebHeaderCollection RequestHeaders { get; private set; }
/// <summary> /// <summary>
/// The position in <see cref="SaveFilePath"/> that has been written and flushed to disk. /// The position in <see cref="SaveFilePath"/> that has been written and flushed to disk.
/// </summary> /// </summary>
[JsonProperty(Required = Required.Always)] [JsonProperty(Required = Required.Always)]
public long WritePosition { get; private set; } public long WritePosition { get; private set; }
/// <summary> /// <summary>
/// The total length of the <see cref="Uri"/> file to download. /// The total length of the <see cref="Uri"/> file to download.
/// </summary> /// </summary>
[JsonProperty(Required = Required.Always)] [JsonProperty(Required = Required.Always)]
public long ContentLength { get; private set; } public long ContentLength { get; private set; }
#endregion #endregion
#region Private Properties #region Private Properties
private HttpWebRequest HttpRequest { get; set; } private HttpWebRequest HttpRequest { get; set; }
private FileStream _writeFile { get; } private FileStream _writeFile { get; }
private FileStream _readFile { get; } private FileStream _readFile { get; }
private Stream _networkStream { get; set; } private Stream _networkStream { get; set; }
private bool hasBegunDownloading { get; set; } private bool hasBegunDownloading { get; set; }
public bool IsCancelled { get; private set; } public bool IsCancelled { get; private set; }
private EventWaitHandle downloadEnded { get; set; } private EventWaitHandle downloadEnded { get; set; }
private EventWaitHandle downloadedPiece { get; set; } private EventWaitHandle downloadedPiece { get; set; }
#endregion #endregion
#region Constants #region Constants
//Download buffer size //Download buffer size
private const int DOWNLOAD_BUFF_SZ = 4 * 1024; private const int DOWNLOAD_BUFF_SZ = 32 * 1024;
//NetworkFileStream will flush all data in _writeFile to disk after every //NetworkFileStream will flush all data in _writeFile to disk after every
//DATA_FLUSH_SZ bytes are written to the file stream. //DATA_FLUSH_SZ bytes are written to the file stream.
private const int DATA_FLUSH_SZ = 1024 * 1024; private const int DATA_FLUSH_SZ = 1024 * 1024;
#endregion #endregion
#region Constructor #region Constructor
/// <summary> /// <summary>
/// A resumable, simultaneous file downloader and reader. /// A resumable, simultaneous file downloader and reader.
/// </summary> /// </summary>
/// <param name="saveFilePath">Path to a location on disk to save the downloaded data from <paramref name="uri"/></param> /// <param name="saveFilePath">Path to a location on disk to save the downloaded data from <paramref name="uri"/></param>
/// <param name="uri">Http(s) address of the file to download.</param> /// <param name="uri">Http(s) address of the file to download.</param>
/// <param name="writePosition">The position in <paramref name="uri"/> to begin downloading.</param> /// <param name="writePosition">The position in <paramref name="uri"/> to begin downloading.</param>
/// <param name="requestHeaders">Http headers to be sent to the server with the <see cref="HttpWebRequest"/>.</param> /// <param name="requestHeaders">Http headers to be sent to the server with the <see cref="HttpWebRequest"/>.</param>
/// <param name="cookies">A <see cref="SingleUriCookieContainer"/> with cookies to send with the <see cref="HttpWebRequest"/>. It will also be populated with any cookies set by the server. </param> /// <param name="cookies">A <see cref="SingleUriCookieContainer"/> with cookies to send with the <see cref="HttpWebRequest"/>. It will also be populated with any cookies set by the server. </param>
public NetworkFileStream(string saveFilePath, Uri uri, long writePosition = 0, WebHeaderCollection requestHeaders = null, SingleUriCookieContainer cookies = null) public NetworkFileStream(string saveFilePath, Uri uri, long writePosition = 0, WebHeaderCollection requestHeaders = null, SingleUriCookieContainer cookies = null)
{ {
ArgumentValidator.EnsureNotNullOrWhiteSpace(saveFilePath, nameof(saveFilePath)); ArgumentValidator.EnsureNotNullOrWhiteSpace(saveFilePath, nameof(saveFilePath));
ArgumentValidator.EnsureNotNullOrWhiteSpace(uri?.AbsoluteUri, nameof(uri)); ArgumentValidator.EnsureNotNullOrWhiteSpace(uri?.AbsoluteUri, nameof(uri));
ArgumentValidator.EnsureGreaterThan(writePosition, nameof(writePosition), -1); ArgumentValidator.EnsureGreaterThan(writePosition, nameof(writePosition), -1);
if (!Directory.Exists(Path.GetDirectoryName(saveFilePath))) if (!Directory.Exists(Path.GetDirectoryName(saveFilePath)))
throw new ArgumentException($"Specified {nameof(saveFilePath)} directory \"{Path.GetDirectoryName(saveFilePath)}\" does not exist."); throw new ArgumentException($"Specified {nameof(saveFilePath)} directory \"{Path.GetDirectoryName(saveFilePath)}\" does not exist.");
SaveFilePath = saveFilePath; SaveFilePath = saveFilePath;
Uri = uri; Uri = uri;
WritePosition = writePosition; WritePosition = writePosition;
RequestHeaders = requestHeaders ?? new WebHeaderCollection(); RequestHeaders = requestHeaders ?? new WebHeaderCollection();
CookieContainer = cookies ?? new SingleUriCookieContainer { Uri = uri }; CookieContainer = cookies ?? new SingleUriCookieContainer { Uri = uri };
_writeFile = new FileStream(SaveFilePath, FileMode.OpenOrCreate, FileAccess.Write, FileShare.ReadWrite) _writeFile = new FileStream(SaveFilePath, FileMode.OpenOrCreate, FileAccess.Write, FileShare.ReadWrite)
{ {
Position = WritePosition Position = WritePosition
}; };
_readFile = new FileStream(SaveFilePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite); _readFile = new FileStream(SaveFilePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
SetUriForSameFile(uri); SetUriForSameFile(uri);
} }
#endregion #endregion
#region Downloader #region Downloader
/// <summary> /// <summary>
/// Update the <see cref="JsonFilePersister"/>. /// Update the <see cref="JsonFilePersister"/>.
/// </summary> /// </summary>
private void Update() private void Update()
{ {
RequestHeaders = HttpRequest.Headers; RequestHeaders = HttpRequest.Headers;
Updated?.Invoke(this, EventArgs.Empty); Updated?.Invoke(this, EventArgs.Empty);
} }
/// <summary> /// <summary>
/// Set a different <see cref="System.Uri"/> to the same file targeted by this instance of <see cref="NetworkFileStream"/> /// Set a different <see cref="System.Uri"/> to the same file targeted by this instance of <see cref="NetworkFileStream"/>
/// </summary> /// </summary>
/// <param name="uriToSameFile">New <see cref="System.Uri"/> host must match existing host.</param> /// <param name="uriToSameFile">New <see cref="System.Uri"/> host must match existing host.</param>
public void SetUriForSameFile(Uri uriToSameFile) public void SetUriForSameFile(Uri uriToSameFile)
{ {
ArgumentValidator.EnsureNotNullOrWhiteSpace(uriToSameFile?.AbsoluteUri, nameof(uriToSameFile)); ArgumentValidator.EnsureNotNullOrWhiteSpace(uriToSameFile?.AbsoluteUri, nameof(uriToSameFile));
if (uriToSameFile.Host != Uri.Host) if (uriToSameFile.Host != Uri.Host)
throw new ArgumentException($"New uri to the same file must have the same host.\r\n Old Host :{Uri.Host}\r\nNew Host: {uriToSameFile.Host}"); throw new ArgumentException($"New uri to the same file must have the same host.\r\n Old Host :{Uri.Host}\r\nNew Host: {uriToSameFile.Host}");
if (hasBegunDownloading) if (hasBegunDownloading)
throw new InvalidOperationException("Cannot change Uri after download has started."); throw new InvalidOperationException("Cannot change Uri after download has started.");
Uri = uriToSameFile; Uri = uriToSameFile;
HttpRequest = WebRequest.CreateHttp(Uri); HttpRequest = WebRequest.CreateHttp(Uri);
HttpRequest.CookieContainer = CookieContainer; HttpRequest.CookieContainer = CookieContainer;
HttpRequest.Headers = RequestHeaders; HttpRequest.Headers = RequestHeaders;
//If NetworkFileStream is resuming, Header will already contain a range. //If NetworkFileStream is resuming, Header will already contain a range.
HttpRequest.Headers.Remove("Range"); HttpRequest.Headers.Remove("Range");
HttpRequest.AddRange(WritePosition); HttpRequest.AddRange(WritePosition);
} }
/// <summary> /// <summary>
/// Begins downloading <see cref="Uri"/> to <see cref="SaveFilePath"/> in a background thread. /// Begins downloading <see cref="Uri"/> to <see cref="SaveFilePath"/> in a background thread.
/// </summary> /// </summary>
private void BeginDownloading() private void BeginDownloading()
{ {
downloadEnded = new EventWaitHandle(false, EventResetMode.ManualReset); downloadEnded = new EventWaitHandle(false, EventResetMode.ManualReset);
if (ContentLength != 0 && WritePosition == ContentLength) if (ContentLength != 0 && WritePosition == ContentLength)
{ {
hasBegunDownloading = true; hasBegunDownloading = true;
downloadEnded.Set(); downloadEnded.Set();
return; return;
} }
if (ContentLength != 0 && WritePosition > ContentLength) if (ContentLength != 0 && WritePosition > ContentLength)
throw new WebException($"Specified write position (0x{WritePosition:X10}) is larger than {nameof(ContentLength)} (0x{ContentLength:X10})."); throw new WebException($"Specified write position (0x{WritePosition:X10}) is larger than {nameof(ContentLength)} (0x{ContentLength:X10}).");
var response = HttpRequest.GetResponse() as HttpWebResponse; var response = HttpRequest.GetResponse() as HttpWebResponse;
if (response.StatusCode != HttpStatusCode.PartialContent) if (response.StatusCode != HttpStatusCode.PartialContent)
throw new WebException($"Server at {Uri.Host} responded with unexpected status code: {response.StatusCode}."); throw new WebException($"Server at {Uri.Host} responded with unexpected status code: {response.StatusCode}.");
//Content length is the length of the range request, and it is only equal //Content length is the length of the range request, and it is only equal
//to the complete file length if requesting Range: bytes=0- //to the complete file length if requesting Range: bytes=0-
if (WritePosition == 0) if (WritePosition == 0)
ContentLength = response.ContentLength; ContentLength = response.ContentLength;
_networkStream = response.GetResponseStream(); _networkStream = response.GetResponseStream();
downloadedPiece = new EventWaitHandle(false, EventResetMode.AutoReset); downloadedPiece = new EventWaitHandle(false, EventResetMode.AutoReset);
//Download the file in the background. //Download the file in the background.
new Thread(() => DownloadFile()) new Thread(() => DownloadFile())
{ IsBackground = true } { IsBackground = true }
.Start(); .Start();
hasBegunDownloading = true; hasBegunDownloading = true;
return; return;
} }
/// <summary> /// <summary>
/// Downlod <see cref="Uri"/> to <see cref="SaveFilePath"/>. /// Downlod <see cref="Uri"/> to <see cref="SaveFilePath"/>.
/// </summary> /// </summary>
private void DownloadFile() private void DownloadFile()
{ {
var downloadPosition = WritePosition; var downloadPosition = WritePosition;
var nextFlush = downloadPosition + DATA_FLUSH_SZ; var nextFlush = downloadPosition + DATA_FLUSH_SZ;
var buff = new byte[DOWNLOAD_BUFF_SZ]; var buff = new byte[DOWNLOAD_BUFF_SZ];
do do
{ {
var bytesRead = _networkStream.Read(buff, 0, DOWNLOAD_BUFF_SZ); Thread.Sleep(10);
_writeFile.Write(buff, 0, bytesRead); var bytesRead = _networkStream.Read(buff, 0, DOWNLOAD_BUFF_SZ);
_writeFile.Write(buff, 0, bytesRead);
downloadPosition += bytesRead;
downloadPosition += bytesRead;
if (downloadPosition > nextFlush)
{ if (downloadPosition > nextFlush)
_writeFile.Flush(); {
WritePosition = downloadPosition; _writeFile.Flush();
Update(); WritePosition = downloadPosition;
nextFlush = downloadPosition + DATA_FLUSH_SZ; Update();
downloadedPiece.Set(); nextFlush = downloadPosition + DATA_FLUSH_SZ;
} downloadedPiece.Set();
}
} while (downloadPosition < ContentLength && !IsCancelled);
} while (downloadPosition < ContentLength && !IsCancelled);
_writeFile.Close();
_networkStream.Close(); _writeFile.Close();
WritePosition = downloadPosition; _networkStream.Close();
Update(); WritePosition = downloadPosition;
Update();
downloadedPiece.Set();
downloadEnded.Set(); downloadedPiece.Set();
downloadEnded.Set();
if (!IsCancelled && WritePosition < ContentLength)
throw new WebException($"Downloaded size (0x{WritePosition:X10}) is less than {nameof(ContentLength)} (0x{ContentLength:X10})."); if (!IsCancelled && WritePosition < ContentLength)
throw new WebException($"Downloaded size (0x{WritePosition:X10}) is less than {nameof(ContentLength)} (0x{ContentLength:X10}).");
if (WritePosition > ContentLength)
throw new WebException($"Downloaded size (0x{WritePosition:X10}) is greater than {nameof(ContentLength)} (0x{ContentLength:X10})."); if (WritePosition > ContentLength)
throw new WebException($"Downloaded size (0x{WritePosition:X10}) is greater than {nameof(ContentLength)} (0x{ContentLength:X10}).");
}
}
#endregion
#endregion
#region Json Connverters
#region Json Connverters
public static JsonSerializerSettings GetJsonSerializerSettings()
{ public static JsonSerializerSettings GetJsonSerializerSettings()
var settings = new JsonSerializerSettings(); {
settings.Converters.Add(new CookieContainerConverter()); var settings = new JsonSerializerSettings();
settings.Converters.Add(new WebHeaderCollectionConverter()); settings.Converters.Add(new CookieContainerConverter());
return settings; settings.Converters.Add(new WebHeaderCollectionConverter());
} return settings;
}
internal class CookieContainerConverter : JsonConverter
{ internal class CookieContainerConverter : JsonConverter
public override bool CanConvert(Type objectType) {
=> objectType == typeof(SingleUriCookieContainer); public override bool CanConvert(Type objectType)
=> objectType == typeof(SingleUriCookieContainer);
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{ public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
var jObj = JObject.Load(reader); {
var jObj = JObject.Load(reader);
var result = new SingleUriCookieContainer()
{ var result = new SingleUriCookieContainer()
Uri = new Uri(jObj["Uri"].Value<string>()), {
Capacity = jObj["Capacity"].Value<int>(), Uri = new Uri(jObj["Uri"].Value<string>()),
MaxCookieSize = jObj["MaxCookieSize"].Value<int>(), Capacity = jObj["Capacity"].Value<int>(),
PerDomainCapacity = jObj["PerDomainCapacity"].Value<int>() MaxCookieSize = jObj["MaxCookieSize"].Value<int>(),
}; PerDomainCapacity = jObj["PerDomainCapacity"].Value<int>()
};
var cookieList = jObj["Cookies"].ToList();
var cookieList = jObj["Cookies"].ToList();
foreach (var cookie in cookieList)
{ foreach (var cookie in cookieList)
result.Add( {
new Cookie result.Add(
{ new Cookie
Comment = cookie["Comment"].Value<string>(), {
HttpOnly = cookie["HttpOnly"].Value<bool>(), Comment = cookie["Comment"].Value<string>(),
Discard = cookie["Discard"].Value<bool>(), HttpOnly = cookie["HttpOnly"].Value<bool>(),
Domain = cookie["Domain"].Value<string>(), Discard = cookie["Discard"].Value<bool>(),
Expired = cookie["Expired"].Value<bool>(), Domain = cookie["Domain"].Value<string>(),
Expires = cookie["Expires"].Value<DateTime>(), Expired = cookie["Expired"].Value<bool>(),
Name = cookie["Name"].Value<string>(), Expires = cookie["Expires"].Value<DateTime>(),
Path = cookie["Path"].Value<string>(), Name = cookie["Name"].Value<string>(),
Port = cookie["Port"].Value<string>(), Path = cookie["Path"].Value<string>(),
Secure = cookie["Secure"].Value<bool>(), Port = cookie["Port"].Value<string>(),
Value = cookie["Value"].Value<string>(), Secure = cookie["Secure"].Value<bool>(),
Version = cookie["Version"].Value<int>(), Value = cookie["Value"].Value<string>(),
}); Version = cookie["Version"].Value<int>(),
} });
}
return result;
} return result;
}
public override bool CanWrite => true;
public override bool CanWrite => true;
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{ public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
var cookies = value as SingleUriCookieContainer; {
var obj = (JObject)JToken.FromObject(value); var cookies = value as SingleUriCookieContainer;
var container = cookies.GetCookies(); var obj = (JObject)JToken.FromObject(value);
var propertyNames = container.Select(c => JToken.FromObject(c)); var container = cookies.GetCookies();
obj.AddFirst(new JProperty("Cookies", new JArray(propertyNames))); var propertyNames = container.Select(c => JToken.FromObject(c));
obj.WriteTo(writer); obj.AddFirst(new JProperty("Cookies", new JArray(propertyNames)));
} obj.WriteTo(writer);
} }
}
internal class WebHeaderCollectionConverter : JsonConverter
{ internal class WebHeaderCollectionConverter : JsonConverter
public override bool CanConvert(Type objectType) {
=> objectType == typeof(WebHeaderCollection); public override bool CanConvert(Type objectType)
=> objectType == typeof(WebHeaderCollection);
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{ public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
var jObj = JObject.Load(reader); {
var result = new WebHeaderCollection(); var jObj = JObject.Load(reader);
var result = new WebHeaderCollection();
foreach (var kvp in jObj)
result.Add(kvp.Key, kvp.Value.Value<string>()); foreach (var kvp in jObj)
result.Add(kvp.Key, kvp.Value.Value<string>());
return result;
} return result;
}
public override bool CanWrite => true;
public override bool CanWrite => true;
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{ public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
var jObj = new JObject(); {
var type = value.GetType(); var jObj = new JObject();
var headers = value as WebHeaderCollection; var type = value.GetType();
var jHeaders = headers.AllKeys.Select(k => new JProperty(k, headers[k])); var headers = value as WebHeaderCollection;
jObj.Add(jHeaders); var jHeaders = headers.AllKeys.Select(k => new JProperty(k, headers[k]));
jObj.WriteTo(writer); jObj.Add(jHeaders);
} jObj.WriteTo(writer);
} }
}
#endregion
#endregion
#region Download Stream Reader
#region Download Stream Reader
[JsonIgnore]
public override bool CanRead => true; [JsonIgnore]
public override bool CanRead => true;
[JsonIgnore]
public override bool CanSeek => true; [JsonIgnore]
public override bool CanSeek => true;
[JsonIgnore]
public override bool CanWrite => false; [JsonIgnore]
public override bool CanWrite => false;
[JsonIgnore]
public override long Length [JsonIgnore]
{ public override long Length
get {
{ get
if (!hasBegunDownloading) {
BeginDownloading(); if (!hasBegunDownloading)
return ContentLength; BeginDownloading();
} return ContentLength;
} }
}
[JsonIgnore]
public override long Position { get => _readFile.Position; set => Seek(value, SeekOrigin.Begin); } [JsonIgnore]
public override long Position { get => _readFile.Position; set => Seek(value, SeekOrigin.Begin); }
[JsonIgnore]
public override bool CanTimeout => false; [JsonIgnore]
public override bool CanTimeout => false;
[JsonIgnore]
public override int ReadTimeout { get => base.ReadTimeout; set => base.ReadTimeout = value; } [JsonIgnore]
public override int ReadTimeout { get => base.ReadTimeout; set => base.ReadTimeout = value; }
[JsonIgnore]
public override int WriteTimeout { get => base.WriteTimeout; set => base.WriteTimeout = value; } [JsonIgnore]
public override int WriteTimeout { get => base.WriteTimeout; set => base.WriteTimeout = value; }
public override void Flush() => throw new NotImplementedException();
public override void SetLength(long value) => throw new NotImplementedException(); public override void Flush() => throw new NotImplementedException();
public override void Write(byte[] buffer, int offset, int count) => throw new NotImplementedException(); public override void SetLength(long value) => throw new NotImplementedException();
public override void Write(byte[] buffer, int offset, int count) => throw new NotImplementedException();
public override int Read(byte[] buffer, int offset, int count)
{ public override int Read(byte[] buffer, int offset, int count)
if (!hasBegunDownloading) {
BeginDownloading(); if (!hasBegunDownloading)
BeginDownloading();
var toRead = Math.Min(count, Length - Position);
WaitToPosition(Position + toRead); var toRead = Math.Min(count, Length - Position);
return _readFile.Read(buffer, offset, count); WaitToPosition(Position + toRead);
} return _readFile.Read(buffer, offset, count);
}
public override long Seek(long offset, SeekOrigin origin)
{ public override long Seek(long offset, SeekOrigin origin)
var newPosition = origin switch {
var newPosition = origin switch
{ {
SeekOrigin.Current => Position + offset, SeekOrigin.Current => Position + offset,
SeekOrigin.End => ContentLength + offset, SeekOrigin.End => ContentLength + offset,
_ => offset, _ => offset,
}; };
WaitToPosition(newPosition); WaitToPosition(newPosition);
return _readFile.Position = newPosition; return _readFile.Position = newPosition;
} }
/// <summary> /// <summary>
/// Blocks until the file has downloaded to at least <paramref name="requiredPosition"/>, then returns. /// Blocks until the file has downloaded to at least <paramref name="requiredPosition"/>, then returns.
/// </summary> /// </summary>
/// <param name="requiredPosition">The minimum required flished data length in <see cref="SaveFilePath"/>.</param> /// <param name="requiredPosition">The minimum required flished data length in <see cref="SaveFilePath"/>.</param>
private void WaitToPosition(long requiredPosition) private void WaitToPosition(long requiredPosition)
{ {
while (requiredPosition > WritePosition && !IsCancelled && hasBegunDownloading && !downloadedPiece.WaitOne(1000)) ; while (requiredPosition > WritePosition && !IsCancelled && hasBegunDownloading && !downloadedPiece.WaitOne(1000)) ;
} }
public override void Close() public override void Close()
{ {
IsCancelled = true; IsCancelled = true;
while (downloadEnded is not null && !downloadEnded.WaitOne(1000)) ; while (downloadEnded is not null && !downloadEnded.WaitOne(1000)) ;
_readFile.Close(); _readFile.Close();
_writeFile.Close(); _writeFile.Close();
_networkStream?.Close(); _networkStream?.Close();
Update(); Update();
} }
#endregion #endregion
~NetworkFileStream() ~NetworkFileStream()
{ {
downloadEnded?.Close(); downloadEnded?.Close();
downloadedPiece?.Close(); downloadedPiece?.Close();
} }
} }
} }

View File

@ -72,6 +72,8 @@
this.pdfsCountsLbl = new System.Windows.Forms.ToolStripStatusLabel(); this.pdfsCountsLbl = new System.Windows.Forms.ToolStripStatusLabel();
this.addQuickFilterBtn = new System.Windows.Forms.Button(); this.addQuickFilterBtn = new System.Windows.Forms.Button();
this.splitContainer1 = new System.Windows.Forms.SplitContainer(); this.splitContainer1 = new System.Windows.Forms.SplitContainer();
this.button1 = new System.Windows.Forms.Button();
this.panel1 = new System.Windows.Forms.Panel();
this.processBookQueue1 = new LibationWinForms.ProcessQueue.ProcessBookQueue(); this.processBookQueue1 = new LibationWinForms.ProcessQueue.ProcessBookQueue();
this.menuStrip1.SuspendLayout(); this.menuStrip1.SuspendLayout();
this.statusStrip1.SuspendLayout(); this.statusStrip1.SuspendLayout();
@ -79,17 +81,16 @@
this.splitContainer1.Panel1.SuspendLayout(); this.splitContainer1.Panel1.SuspendLayout();
this.splitContainer1.Panel2.SuspendLayout(); this.splitContainer1.Panel2.SuspendLayout();
this.splitContainer1.SuspendLayout(); this.splitContainer1.SuspendLayout();
this.panel1.SuspendLayout();
this.SuspendLayout(); this.SuspendLayout();
// //
// gridPanel // gridPanel
// //
this.gridPanel.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) this.gridPanel.Dock = System.Windows.Forms.DockStyle.Fill;
| System.Windows.Forms.AnchorStyles.Left) this.gridPanel.Location = new System.Drawing.Point(0, 0);
| System.Windows.Forms.AnchorStyles.Right)));
this.gridPanel.Location = new System.Drawing.Point(15, 60);
this.gridPanel.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3); this.gridPanel.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3);
this.gridPanel.Name = "gridPanel"; this.gridPanel.Name = "gridPanel";
this.gridPanel.Size = new System.Drawing.Size(865, 556); this.gridPanel.Size = new System.Drawing.Size(864, 560);
this.gridPanel.TabIndex = 5; this.gridPanel.TabIndex = 5;
// //
// filterHelpBtn // filterHelpBtn
@ -106,7 +107,7 @@
// filterBtn // filterBtn
// //
this.filterBtn.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); this.filterBtn.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right)));
this.filterBtn.Location = new System.Drawing.Point(792, 27); this.filterBtn.Location = new System.Drawing.Point(791, 27);
this.filterBtn.Margin = new System.Windows.Forms.Padding(4, 3, 15, 3); this.filterBtn.Margin = new System.Windows.Forms.Padding(4, 3, 15, 3);
this.filterBtn.Name = "filterBtn"; this.filterBtn.Name = "filterBtn";
this.filterBtn.Size = new System.Drawing.Size(88, 27); this.filterBtn.Size = new System.Drawing.Size(88, 27);
@ -122,7 +123,7 @@
this.filterSearchTb.Location = new System.Drawing.Point(220, 30); this.filterSearchTb.Location = new System.Drawing.Point(220, 30);
this.filterSearchTb.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3); this.filterSearchTb.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3);
this.filterSearchTb.Name = "filterSearchTb"; this.filterSearchTb.Name = "filterSearchTb";
this.filterSearchTb.Size = new System.Drawing.Size(564, 23); this.filterSearchTb.Size = new System.Drawing.Size(563, 23);
this.filterSearchTb.TabIndex = 1; this.filterSearchTb.TabIndex = 1;
this.filterSearchTb.KeyPress += new System.Windows.Forms.KeyPressEventHandler(this.filterSearchTb_KeyPress); this.filterSearchTb.KeyPress += new System.Windows.Forms.KeyPressEventHandler(this.filterSearchTb_KeyPress);
// //
@ -140,7 +141,7 @@
this.menuStrip1.Location = new System.Drawing.Point(0, 0); this.menuStrip1.Location = new System.Drawing.Point(0, 0);
this.menuStrip1.Name = "menuStrip1"; this.menuStrip1.Name = "menuStrip1";
this.menuStrip1.Padding = new System.Windows.Forms.Padding(7, 2, 0, 2); this.menuStrip1.Padding = new System.Windows.Forms.Padding(7, 2, 0, 2);
this.menuStrip1.Size = new System.Drawing.Size(895, 24); this.menuStrip1.Size = new System.Drawing.Size(894, 24);
this.menuStrip1.TabIndex = 0; this.menuStrip1.TabIndex = 0;
this.menuStrip1.Text = "menuStrip1"; this.menuStrip1.Text = "menuStrip1";
// //
@ -396,7 +397,7 @@
this.statusStrip1.Location = new System.Drawing.Point(0, 619); this.statusStrip1.Location = new System.Drawing.Point(0, 619);
this.statusStrip1.Name = "statusStrip1"; this.statusStrip1.Name = "statusStrip1";
this.statusStrip1.Padding = new System.Windows.Forms.Padding(1, 0, 16, 0); this.statusStrip1.Padding = new System.Windows.Forms.Padding(1, 0, 16, 0);
this.statusStrip1.Size = new System.Drawing.Size(895, 22); this.statusStrip1.Size = new System.Drawing.Size(894, 22);
this.statusStrip1.TabIndex = 6; this.statusStrip1.TabIndex = 6;
this.statusStrip1.Text = "statusStrip1"; this.statusStrip1.Text = "statusStrip1";
// //
@ -409,7 +410,7 @@
// springLbl // springLbl
// //
this.springLbl.Name = "springLbl"; this.springLbl.Name = "springLbl";
this.springLbl.Size = new System.Drawing.Size(436, 17); this.springLbl.Size = new System.Drawing.Size(435, 17);
this.springLbl.Spring = true; this.springLbl.Spring = true;
// //
// backupsCountsLbl // backupsCountsLbl
@ -429,7 +430,7 @@
this.addQuickFilterBtn.Location = new System.Drawing.Point(49, 27); this.addQuickFilterBtn.Location = new System.Drawing.Point(49, 27);
this.addQuickFilterBtn.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3); this.addQuickFilterBtn.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3);
this.addQuickFilterBtn.Name = "addQuickFilterBtn"; this.addQuickFilterBtn.Name = "addQuickFilterBtn";
this.addQuickFilterBtn.Size = new System.Drawing.Size(163, 27); this.addQuickFilterBtn.Size = new System.Drawing.Size(99, 27);
this.addQuickFilterBtn.TabIndex = 4; this.addQuickFilterBtn.TabIndex = 4;
this.addQuickFilterBtn.Text = "Add To Quick Filters"; this.addQuickFilterBtn.Text = "Add To Quick Filters";
this.addQuickFilterBtn.UseVisualStyleBackColor = true; this.addQuickFilterBtn.UseVisualStyleBackColor = true;
@ -443,8 +444,9 @@
// //
// splitContainer1.Panel1 // splitContainer1.Panel1
// //
this.splitContainer1.Panel1.Controls.Add(this.button1);
this.splitContainer1.Panel1.Controls.Add(this.panel1);
this.splitContainer1.Panel1.Controls.Add(this.menuStrip1); this.splitContainer1.Panel1.Controls.Add(this.menuStrip1);
this.splitContainer1.Panel1.Controls.Add(this.gridPanel);
this.splitContainer1.Panel1.Controls.Add(this.filterSearchTb); this.splitContainer1.Panel1.Controls.Add(this.filterSearchTb);
this.splitContainer1.Panel1.Controls.Add(this.addQuickFilterBtn); this.splitContainer1.Panel1.Controls.Add(this.addQuickFilterBtn);
this.splitContainer1.Panel1.Controls.Add(this.filterBtn); this.splitContainer1.Panel1.Controls.Add(this.filterBtn);
@ -455,17 +457,41 @@
// //
this.splitContainer1.Panel2.Controls.Add(this.processBookQueue1); this.splitContainer1.Panel2.Controls.Add(this.processBookQueue1);
this.splitContainer1.Size = new System.Drawing.Size(1231, 641); this.splitContainer1.Size = new System.Drawing.Size(1231, 641);
this.splitContainer1.SplitterDistance = 895; this.splitContainer1.SplitterDistance = 894;
this.splitContainer1.SplitterWidth = 8; this.splitContainer1.SplitterWidth = 8;
this.splitContainer1.TabIndex = 7; this.splitContainer1.TabIndex = 7;
// //
// button1
//
this.button1.Location = new System.Drawing.Point(155, 27);
this.button1.Margin = new System.Windows.Forms.Padding(3, 2, 3, 2);
this.button1.Name = "button1";
this.button1.Size = new System.Drawing.Size(58, 22);
this.button1.TabIndex = 8;
this.button1.Text = "button1";
this.button1.UseVisualStyleBackColor = true;
this.button1.Click += new System.EventHandler(this.button1_Click);
//
// panel1
//
this.panel1.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom)
| System.Windows.Forms.AnchorStyles.Left)
| System.Windows.Forms.AnchorStyles.Right)));
this.panel1.Controls.Add(this.gridPanel);
this.panel1.Location = new System.Drawing.Point(15, 59);
this.panel1.Margin = new System.Windows.Forms.Padding(3, 2, 3, 2);
this.panel1.Name = "panel1";
this.panel1.Size = new System.Drawing.Size(864, 560);
this.panel1.TabIndex = 7;
//
// processBookQueue1 // processBookQueue1
// //
this.processBookQueue1.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle; this.processBookQueue1.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle;
this.processBookQueue1.Dock = System.Windows.Forms.DockStyle.Fill; this.processBookQueue1.Dock = System.Windows.Forms.DockStyle.Fill;
this.processBookQueue1.Location = new System.Drawing.Point(0, 0); this.processBookQueue1.Location = new System.Drawing.Point(0, 0);
this.processBookQueue1.Margin = new System.Windows.Forms.Padding(3, 4, 3, 4);
this.processBookQueue1.Name = "processBookQueue1"; this.processBookQueue1.Name = "processBookQueue1";
this.processBookQueue1.Size = new System.Drawing.Size(328, 641); this.processBookQueue1.Size = new System.Drawing.Size(329, 641);
this.processBookQueue1.TabIndex = 0; this.processBookQueue1.TabIndex = 0;
// //
// Form1 // Form1
@ -489,6 +515,7 @@
this.splitContainer1.Panel2.ResumeLayout(false); this.splitContainer1.Panel2.ResumeLayout(false);
((System.ComponentModel.ISupportInitialize)(this.splitContainer1)).EndInit(); ((System.ComponentModel.ISupportInitialize)(this.splitContainer1)).EndInit();
this.splitContainer1.ResumeLayout(false); this.splitContainer1.ResumeLayout(false);
this.panel1.ResumeLayout(false);
this.ResumeLayout(false); this.ResumeLayout(false);
} }
@ -539,5 +566,7 @@
private System.Windows.Forms.ToolStripMenuItem liberateVisible2ToolStripMenuItem; private System.Windows.Forms.ToolStripMenuItem liberateVisible2ToolStripMenuItem;
private System.Windows.Forms.SplitContainer splitContainer1; private System.Windows.Forms.SplitContainer splitContainer1;
private LibationWinForms.ProcessQueue.ProcessBookQueue processBookQueue1; private LibationWinForms.ProcessQueue.ProcessBookQueue processBookQueue1;
private System.Windows.Forms.Panel panel1;
private System.Windows.Forms.Button button1;
} }
} }

View File

@ -1,19 +1,16 @@
using System; using LibationFileManager;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System.Windows.Forms;
using LibationFileManager;
using LibationWinForms.ProcessQueue; using LibationWinForms.ProcessQueue;
using System;
using System.Windows.Forms;
namespace LibationWinForms namespace LibationWinForms
{ {
public partial class Form1 public partial class Form1
{ {
private void Configure_ProcessQueue() private void Configure_ProcessQueue()
{ {
//splitContainer1.Panel2Collapsed = true; //splitContainer1.Panel2Collapsed = true;
processBookQueue1.popoutBtn.Click += ProcessBookQueue1_PopOut; processBookQueue1.popoutBtn.Click += ProcessBookQueue1_PopOut;
} }
private void ProcessBookQueue1_PopOut(object sender, EventArgs e) private void ProcessBookQueue1_PopOut(object sender, EventArgs e)
@ -42,5 +39,10 @@ namespace LibationWinForms
this.Focus(); this.Focus();
} }
} }
private void button1_Click(object sender, EventArgs e)
{
processBookQueue1.AddDownloadDecrypt(productsGrid.List);
}
} }
} }

View File

@ -5,6 +5,7 @@ using LibationFileManager;
using LibationWinForms.BookLiberation; using LibationWinForms.BookLiberation;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Drawing;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using System.Windows.Forms; using System.Windows.Forms;
@ -21,6 +22,7 @@ namespace LibationWinForms.ProcessQueue
FailedSkip, FailedSkip,
FailedAbort FailedAbort
} }
public enum ProcessBookStatus public enum ProcessBookStatus
{ {
Queued, Queued,
@ -30,98 +32,56 @@ namespace LibationWinForms.ProcessQueue
Failed Failed
} }
internal enum QueuePosition public class ProcessBook
{
Absent,
Completed,
Current,
Fisrt,
OneUp,
OneDown,
Last
}
internal enum QueuePositionRequest
{
Fisrt,
OneUp,
OneDown,
Last
}
internal delegate QueuePosition ProcessControlReorderHandler(ProcessBook sender, QueuePositionRequest arg);
internal delegate void ProcessControlEventArgs<T>(ProcessBook sender, T arg);
internal delegate void ProcessControlEventArgs(ProcessBook sender, EventArgs arg);
internal class ProcessBook
{ {
public event EventHandler Completed; public event EventHandler Completed;
public event ProcessControlEventArgs Cancelled; public event EventHandler DataAvailable;
public event ProcessControlReorderHandler RequestMove;
public GridEntry Entry { get; }
//public ProcessBookControl BookControl { get; }
private Func<Processable> _makeFirstProc; public Processable CurrentProcessable => _currentProcessable ??= Processes.Dequeue().Invoke();
private Processable _firstProcessable; public ProcessBookResult Result { get; private set; } = ProcessBookResult.None;
private bool cancelled = false; public ProcessBookStatus Status { get; private set; } = ProcessBookStatus.Queued;
private bool running = false; public string BookText { get; private set; }
public Processable FirstProcessable => _firstProcessable ??= _makeFirstProc?.Invoke(); public Image Cover { get; private set; }
public int Progress { get; private set; }
public TimeSpan TimeRemaining { get; private set; }
public LibraryBook LibraryBook { get; }
private Processable _currentProcessable;
private Func<byte[]> GetCoverArtDelegate;
private readonly Queue<Func<Processable>> Processes = new(); private readonly Queue<Func<Processable>> Processes = new();
private readonly LogMe Logger;
LogMe Logger; public ProcessBook(LibraryBook libraryBook, Image coverImage, LogMe logme)
public ProcessBook(GridEntry entry, LogMe logme)
{ {
Entry = entry; LibraryBook = libraryBook;
//BookControl = new ProcessBookControl(Entry.Title, Entry.Cover); Cover = coverImage;
//BookControl.CancelAction = Cancel;
//BookControl.RequestMoveAction = MoveRequested;
Logger = logme; Logger = logme;
}
public QueuePosition? MoveRequested(QueuePositionRequest requestedPosition) title = LibraryBook.Book.Title;
{ authorNames = LibraryBook.Book.AuthorNames();
return RequestMove?.Invoke(this, requestedPosition); narratorNames = LibraryBook.Book.NarratorNames();
} BookText = $"{title}\r\nBy {authorNames}\r\nNarrated by {narratorNames}";
public void Cancel()
{
cancelled = true;
try
{
if (FirstProcessable is AudioDecodable audioDecodable)
audioDecodable.Cancel();
}
catch(Exception ex)
{
Logger.Error(ex, "Error while cancelling");
}
if (!running)
Cancelled?.Invoke(this, EventArgs.Empty);
} }
public async Task<ProcessBookResult> ProcessOneAsync() public async Task<ProcessBookResult> ProcessOneAsync()
{ {
running = true;
ProcessBookResult result = ProcessBookResult.None;
try try
{ {
LinkProcessable(FirstProcessable); LinkProcessable(CurrentProcessable);
var statusHandler = await FirstProcessable.ProcessSingleAsync(Entry.LibraryBook, validate: true);
var statusHandler = await CurrentProcessable.ProcessSingleAsync(LibraryBook, validate: true);
if (statusHandler.IsSuccess) if (statusHandler.IsSuccess)
return result = ProcessBookResult.Success; return Result = ProcessBookResult.Success;
else if (cancelled) else if (statusHandler.Errors.Contains("Cancelled"))
{ {
Logger.Info($"Process was cancelled {Entry.LibraryBook.Book}"); Logger.Info($"Process was cancelled {LibraryBook.Book}");
return result = ProcessBookResult.Cancelled; return Result = ProcessBookResult.Cancelled;
} }
else if (statusHandler.Errors.Contains("Validation failed")) else if (statusHandler.Errors.Contains("Validation failed"))
{ {
Logger.Info($"Validation failed {Entry.LibraryBook.Book}"); Logger.Info($"Validation failed {LibraryBook.Book}");
return result = ProcessBookResult.ValidationFail; return Result = ProcessBookResult.ValidationFail;
} }
foreach (var errorMessage in statusHandler.Errors) foreach (var errorMessage in statusHandler.Errors)
@ -133,51 +93,189 @@ namespace LibationWinForms.ProcessQueue
} }
finally finally
{ {
if (result == ProcessBookResult.None) if (Result == ProcessBookResult.None)
result = showRetry(Entry.LibraryBook); Result = showRetry(LibraryBook);
//BookControl.SetResult(result); Status = Result switch
{
ProcessBookResult.Success => ProcessBookStatus.Completed,
ProcessBookResult.Cancelled => ProcessBookStatus.Cancelled,
ProcessBookResult.FailedRetry => ProcessBookStatus.Queued,
_ => ProcessBookStatus.Failed,
};
DataAvailable?.Invoke(this, EventArgs.Empty);
} }
return result; return Result;
} }
public void AddPdfProcessable() => AddProcessable<DownloadPdf>(); public void Cancel()
public void AddDownloadDecryptProcessable() => AddProcessable<DownloadDecryptBook>(); {
public void AddConvertMp3Processable() => AddProcessable<ConvertToMp3>(); try
{
if (CurrentProcessable is AudioDecodable audioDecodable)
{
//There's some threadding bug that causes this to hang if executed synchronously.
Task.Run(audioDecodable.Cancel);
DataAvailable?.Invoke(this, EventArgs.Empty);
}
}
catch (Exception ex)
{
Logger.Error(ex, "Error while cancelling");
}
}
public void AddDownloadPdf() => AddProcessable<DownloadPdf>();
public void AddDownloadDecryptBook() => AddProcessable<DownloadDecryptBook>();
public void AddConvertToMp3() => AddProcessable<ConvertToMp3>();
private void AddProcessable<T>() where T : Processable, new() private void AddProcessable<T>() where T : Processable, new()
{ {
if (FirstProcessable == null) Processes.Enqueue(() => new T());
{
_makeFirstProc = () => new T();
}
else
Processes.Enqueue(() => new T());
} }
public override string ToString() => LibraryBook.ToString();
#region Subscribers and Unsubscribers
private void LinkProcessable(Processable strProc) private void LinkProcessable(Processable strProc)
{ {
strProc.Begin += Processable_Begin; strProc.Begin += Processable_Begin;
strProc.Completed += Processable_Completed; strProc.Completed += Processable_Completed;
strProc.StreamingProgressChanged += Streamable_StreamingProgressChanged;
strProc.StreamingTimeRemaining += Streamable_StreamingTimeRemaining;
if (strProc is AudioDecodable audioDecodable)
{
audioDecodable.RequestCoverArt += AudioDecodable_RequestCoverArt;
audioDecodable.TitleDiscovered += AudioDecodable_TitleDiscovered;
audioDecodable.AuthorsDiscovered += AudioDecodable_AuthorsDiscovered;
audioDecodable.NarratorsDiscovered += AudioDecodable_NarratorsDiscovered;
audioDecodable.CoverImageDiscovered += AudioDecodable_CoverImageDiscovered;
}
} }
private void UnlinkProcessable(Processable strProc)
{
strProc.Begin -= Processable_Begin;
strProc.Completed -= Processable_Completed;
strProc.StreamingProgressChanged -= Streamable_StreamingProgressChanged;
strProc.StreamingTimeRemaining -= Streamable_StreamingTimeRemaining;
if (strProc is AudioDecodable audioDecodable)
{
audioDecodable.RequestCoverArt -= AudioDecodable_RequestCoverArt;
audioDecodable.TitleDiscovered -= AudioDecodable_TitleDiscovered;
audioDecodable.AuthorsDiscovered -= AudioDecodable_AuthorsDiscovered;
audioDecodable.NarratorsDiscovered -= AudioDecodable_NarratorsDiscovered;
audioDecodable.CoverImageDiscovered -= AudioDecodable_CoverImageDiscovered;
}
}
#endregion
#region AudioDecodable event handlers
private string title;
private string authorNames;
private string narratorNames;
private void AudioDecodable_TitleDiscovered(object sender, string title)
{
this.title = title;
updateBookInfo();
}
private void AudioDecodable_AuthorsDiscovered(object sender, string authors)
{
authorNames = authors;
updateBookInfo();
}
private void AudioDecodable_NarratorsDiscovered(object sender, string narrators)
{
narratorNames = narrators;
updateBookInfo();
}
private void updateBookInfo()
{
BookText = $"{title}\r\nBy {authorNames}\r\nNarrated by {narratorNames}";
DataAvailable?.Invoke(this, EventArgs.Empty);
}
public void AudioDecodable_RequestCoverArt(object sender, Action<byte[]> setCoverArtDelegate)
{
byte[] coverData = GetCoverArtDelegate();
setCoverArtDelegate(coverData);
AudioDecodable_CoverImageDiscovered(this, coverData);
}
private void AudioDecodable_CoverImageDiscovered(object sender, byte[] coverArt)
{
Cover = Dinah.Core.Drawing.ImageReader.ToImage(coverArt);
DataAvailable?.Invoke(this, EventArgs.Empty);
}
#endregion
#region Streamable event handlers
private void Streamable_StreamingTimeRemaining(object sender, TimeSpan timeRemaining)
{
TimeRemaining = timeRemaining;
DataAvailable?.Invoke(this, EventArgs.Empty);
}
private void Streamable_StreamingProgressChanged(object sender, Dinah.Core.Net.Http.DownloadProgress downloadProgress)
{
if (!downloadProgress.ProgressPercentage.HasValue)
return;
if (downloadProgress.ProgressPercentage == 0)
TimeRemaining = TimeSpan.Zero;
else
Progress = (int)downloadProgress.ProgressPercentage;
DataAvailable?.Invoke(this, EventArgs.Empty);
}
#endregion
#region Processable event handlers
private void Processable_Begin(object sender, LibraryBook libraryBook) private void Processable_Begin(object sender, LibraryBook libraryBook)
{ {
//BookControl.RegisterFileLiberator((Processable)sender, Logger); Status = ProcessBookStatus.Working;
//BookControl.Processable_Begin(sender, libraryBook);
Logger.Info($"{Environment.NewLine}{((Processable)sender).Name} Step, Begin: {libraryBook.Book}");
GetCoverArtDelegate = () => PictureStorage.GetPictureSynchronously(
new PictureDefinition(
libraryBook.Book.PictureId,
PictureSize._500x500));
title = libraryBook.Book.Title;
authorNames = libraryBook.Book.AuthorNames();
narratorNames = libraryBook.Book.NarratorNames();
Cover = Dinah.Core.Drawing.ImageReader.ToImage(PictureStorage.GetPicture(
new PictureDefinition(
libraryBook.Book.PictureId,
PictureSize._80x80)).bytes);
updateBookInfo();
} }
private async void Processable_Completed(object sender, LibraryBook e) private async void Processable_Completed(object sender, LibraryBook libraryBook)
{ {
((Processable)sender).Begin -= Processable_Begin;
Logger.Info($"{((Processable)sender).Name} Step, Completed: {libraryBook.Book}");
UnlinkProcessable((Processable)sender);
if (Processes.Count > 0) if (Processes.Count > 0)
{ {
var nextProcessFunc = Processes.Dequeue(); _currentProcessable = null;
var nextProcess = nextProcessFunc(); LinkProcessable(CurrentProcessable);
LinkProcessable(nextProcess); var result = await CurrentProcessable.ProcessSingleAsync(libraryBook, validate: true);
var result = await nextProcess.ProcessSingleAsync(e, true);
if (result.HasErrors) if (result.HasErrors)
{ {
@ -185,16 +283,18 @@ namespace LibationWinForms.ProcessQueue
Logger.Error(errorMessage); Logger.Error(errorMessage);
Completed?.Invoke(this, EventArgs.Empty); Completed?.Invoke(this, EventArgs.Empty);
running = false;
} }
} }
else else
{ {
Completed?.Invoke(this, EventArgs.Empty); Completed?.Invoke(this, EventArgs.Empty);
running = false;
} }
} }
#endregion
#region Failure Handler
private ProcessBookResult showRetry(LibraryBook libraryBook) private ProcessBookResult showRetry(LibraryBook libraryBook)
{ {
Logger.Error("ERROR. All books have not been processed. Most recent book: processing failed"); Logger.Error("ERROR. All books have not been processed. Most recent book: processing failed");
@ -247,7 +347,7 @@ $@" Title: {libraryBook.Book.Title}
} }
protected string SkipDialogText => @" private string SkipDialogText => @"
An error occurred while trying to process this book. An error occurred while trying to process this book.
{0} {0}
@ -257,8 +357,10 @@ An error occurred while trying to process this book.
- IGNORE: Permanently ignore this book. Continue processing books. (Will not try this book again later.) - IGNORE: Permanently ignore this book. Continue processing books. (Will not try this book again later.)
".Trim(); ".Trim();
protected MessageBoxButtons SkipDialogButtons => MessageBoxButtons.AbortRetryIgnore; private MessageBoxButtons SkipDialogButtons => MessageBoxButtons.AbortRetryIgnore;
protected MessageBoxDefaultButton SkipDialogDefaultButton => MessageBoxDefaultButton.Button1; private MessageBoxDefaultButton SkipDialogDefaultButton => MessageBoxDefaultButton.Button1;
protected DialogResult SkipResult => DialogResult.Ignore; private DialogResult SkipResult => DialogResult.Ignore;
} }
#endregion
} }

View File

@ -1,12 +1,4 @@
using System; using System.Windows.Forms;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace LibationWinForms.ProcessQueue namespace LibationWinForms.ProcessQueue
{ {

View File

@ -28,20 +28,25 @@
/// </summary> /// </summary>
private void InitializeComponent() private void InitializeComponent()
{ {
System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(ProcessBookQueue));
this.statusStrip1 = new System.Windows.Forms.StatusStrip(); this.statusStrip1 = new System.Windows.Forms.StatusStrip();
this.toolStripProgressBar1 = new System.Windows.Forms.ToolStripProgressBar(); this.toolStripProgressBar1 = new System.Windows.Forms.ToolStripProgressBar();
this.queueNumberLbl = new System.Windows.Forms.ToolStripStatusLabel();
this.completedNumberLbl = new System.Windows.Forms.ToolStripStatusLabel();
this.errorNumberLbl = new System.Windows.Forms.ToolStripStatusLabel();
this.toolStripStatusLabel1 = new System.Windows.Forms.ToolStripStatusLabel(); this.toolStripStatusLabel1 = new System.Windows.Forms.ToolStripStatusLabel();
this.tabControl1 = new System.Windows.Forms.TabControl(); this.tabControl1 = new System.Windows.Forms.TabControl();
this.tabPage1 = new System.Windows.Forms.TabPage(); this.tabPage1 = new System.Windows.Forms.TabPage();
this.panel3 = new System.Windows.Forms.Panel();
this.virtualFlowControl2 = new LibationWinForms.ProcessQueue.VirtualFlowControl(); this.virtualFlowControl2 = new LibationWinForms.ProcessQueue.VirtualFlowControl();
this.panel1 = new System.Windows.Forms.Panel(); this.panel1 = new System.Windows.Forms.Panel();
this.btnCleanFinished = new System.Windows.Forms.Button(); this.btnCleanFinished = new System.Windows.Forms.Button();
this.cancelAllBtn = new System.Windows.Forms.Button(); this.cancelAllBtn = new System.Windows.Forms.Button();
this.tabPage2 = new System.Windows.Forms.TabPage(); this.tabPage2 = new System.Windows.Forms.TabPage();
this.panel4 = new System.Windows.Forms.Panel();
this.panel2 = new System.Windows.Forms.Panel(); this.panel2 = new System.Windows.Forms.Panel();
this.clearLogBtn = new System.Windows.Forms.Button(); this.clearLogBtn = new System.Windows.Forms.Button();
this.logMeTbox = new System.Windows.Forms.TextBox(); this.logMeTbox = new System.Windows.Forms.TextBox();
this.tabPage3 = new System.Windows.Forms.TabPage();
this.statusStrip1.SuspendLayout(); this.statusStrip1.SuspendLayout();
this.tabControl1.SuspendLayout(); this.tabControl1.SuspendLayout();
this.tabPage1.SuspendLayout(); this.tabPage1.SuspendLayout();
@ -52,78 +57,111 @@
// //
// statusStrip1 // statusStrip1
// //
this.statusStrip1.ImageScalingSize = new System.Drawing.Size(20, 20);
this.statusStrip1.Items.AddRange(new System.Windows.Forms.ToolStripItem[] { this.statusStrip1.Items.AddRange(new System.Windows.Forms.ToolStripItem[] {
this.toolStripProgressBar1, this.toolStripProgressBar1,
this.queueNumberLbl,
this.completedNumberLbl,
this.errorNumberLbl,
this.toolStripStatusLabel1}); this.toolStripStatusLabel1});
this.statusStrip1.Location = new System.Drawing.Point(0, 486); this.statusStrip1.Location = new System.Drawing.Point(0, 483);
this.statusStrip1.Name = "statusStrip1"; this.statusStrip1.Name = "statusStrip1";
this.statusStrip1.Size = new System.Drawing.Size(404, 22); this.statusStrip1.Size = new System.Drawing.Size(404, 25);
this.statusStrip1.TabIndex = 1; this.statusStrip1.TabIndex = 1;
this.statusStrip1.Text = "statusStrip1"; this.statusStrip1.Text = "statusStrip1";
// //
// toolStripProgressBar1 // toolStripProgressBar1
// //
this.toolStripProgressBar1.Name = "toolStripProgressBar1"; this.toolStripProgressBar1.Name = "toolStripProgressBar1";
this.toolStripProgressBar1.Size = new System.Drawing.Size(100, 16); this.toolStripProgressBar1.Size = new System.Drawing.Size(100, 19);
//
// queueNumberLbl
//
this.queueNumberLbl.Image = ((System.Drawing.Image)(resources.GetObject("queueNumberLbl.Image")));
this.queueNumberLbl.Name = "queueNumberLbl";
this.queueNumberLbl.Size = new System.Drawing.Size(51, 20);
this.queueNumberLbl.Text = "[Q#]";
//
// completedNumberLbl
//
this.completedNumberLbl.Image = ((System.Drawing.Image)(resources.GetObject("completedNumberLbl.Image")));
this.completedNumberLbl.Name = "completedNumberLbl";
this.completedNumberLbl.Size = new System.Drawing.Size(56, 20);
this.completedNumberLbl.Text = "[DL#]";
//
// errorNumberLbl
//
this.errorNumberLbl.Image = ((System.Drawing.Image)(resources.GetObject("errorNumberLbl.Image")));
this.errorNumberLbl.Name = "errorNumberLbl";
this.errorNumberLbl.Size = new System.Drawing.Size(62, 20);
this.errorNumberLbl.Text = "[ERR#]";
// //
// toolStripStatusLabel1 // toolStripStatusLabel1
// //
this.toolStripStatusLabel1.Name = "toolStripStatusLabel1"; this.toolStripStatusLabel1.Name = "toolStripStatusLabel1";
this.toolStripStatusLabel1.Size = new System.Drawing.Size(287, 17); this.toolStripStatusLabel1.Size = new System.Drawing.Size(118, 20);
this.toolStripStatusLabel1.Spring = true; this.toolStripStatusLabel1.Spring = true;
// //
// tabControl1 // tabControl1
// //
this.tabControl1.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom)
| System.Windows.Forms.AnchorStyles.Left)
| System.Windows.Forms.AnchorStyles.Right)));
this.tabControl1.Controls.Add(this.tabPage1); this.tabControl1.Controls.Add(this.tabPage1);
this.tabControl1.Controls.Add(this.tabPage2); this.tabControl1.Controls.Add(this.tabPage2);
this.tabControl1.Controls.Add(this.tabPage3); this.tabControl1.Dock = System.Windows.Forms.DockStyle.Fill;
this.tabControl1.Location = new System.Drawing.Point(0, 0); this.tabControl1.Location = new System.Drawing.Point(0, 0);
this.tabControl1.Margin = new System.Windows.Forms.Padding(0); this.tabControl1.Margin = new System.Windows.Forms.Padding(0);
this.tabControl1.Name = "tabControl1"; this.tabControl1.Name = "tabControl1";
this.tabControl1.SelectedIndex = 0; this.tabControl1.SelectedIndex = 0;
this.tabControl1.Size = new System.Drawing.Size(405, 486); this.tabControl1.Size = new System.Drawing.Size(404, 483);
this.tabControl1.TabIndex = 3; this.tabControl1.TabIndex = 3;
// //
// tabPage1 // tabPage1
// //
this.tabPage1.Controls.Add(this.panel3);
this.tabPage1.Controls.Add(this.virtualFlowControl2); this.tabPage1.Controls.Add(this.virtualFlowControl2);
this.tabPage1.Controls.Add(this.panel1); this.tabPage1.Controls.Add(this.panel1);
this.tabPage1.Location = new System.Drawing.Point(4, 24); this.tabPage1.Location = new System.Drawing.Point(4, 24);
this.tabPage1.Name = "tabPage1"; this.tabPage1.Name = "tabPage1";
this.tabPage1.Padding = new System.Windows.Forms.Padding(3); this.tabPage1.Padding = new System.Windows.Forms.Padding(3);
this.tabPage1.Size = new System.Drawing.Size(397, 458); this.tabPage1.Size = new System.Drawing.Size(396, 455);
this.tabPage1.TabIndex = 0; this.tabPage1.TabIndex = 0;
this.tabPage1.Text = "Process Queue"; this.tabPage1.Text = "Process Queue";
this.tabPage1.UseVisualStyleBackColor = true; this.tabPage1.UseVisualStyleBackColor = true;
// //
// panel3
//
this.panel3.Dock = System.Windows.Forms.DockStyle.Bottom;
this.panel3.Location = new System.Drawing.Point(3, 422);
this.panel3.Name = "panel3";
this.panel3.Size = new System.Drawing.Size(390, 5);
this.panel3.TabIndex = 4;
//
// virtualFlowControl2 // virtualFlowControl2
// //
this.virtualFlowControl2.AccessibleRole = System.Windows.Forms.AccessibleRole.None;
this.virtualFlowControl2.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle;
this.virtualFlowControl2.Dock = System.Windows.Forms.DockStyle.Fill; this.virtualFlowControl2.Dock = System.Windows.Forms.DockStyle.Fill;
this.virtualFlowControl2.Location = new System.Drawing.Point(3, 3); this.virtualFlowControl2.Location = new System.Drawing.Point(3, 3);
this.virtualFlowControl2.Name = "virtualFlowControl2"; this.virtualFlowControl2.Name = "virtualFlowControl2";
this.virtualFlowControl2.Size = new System.Drawing.Size(391, 422); this.virtualFlowControl2.Size = new System.Drawing.Size(390, 424);
this.virtualFlowControl2.TabIndex = 3; this.virtualFlowControl2.TabIndex = 3;
this.virtualFlowControl2.VirtualControlCount = 0; this.virtualFlowControl2.VirtualControlCount = 0;
// //
// panel1 // panel1
// //
this.panel1.BackColor = System.Drawing.SystemColors.ControlDark; this.panel1.BackColor = System.Drawing.SystemColors.Control;
this.panel1.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle;
this.panel1.Controls.Add(this.btnCleanFinished); this.panel1.Controls.Add(this.btnCleanFinished);
this.panel1.Controls.Add(this.cancelAllBtn); this.panel1.Controls.Add(this.cancelAllBtn);
this.panel1.Dock = System.Windows.Forms.DockStyle.Bottom; this.panel1.Dock = System.Windows.Forms.DockStyle.Bottom;
this.panel1.Location = new System.Drawing.Point(3, 425); this.panel1.Location = new System.Drawing.Point(3, 427);
this.panel1.Name = "panel1"; this.panel1.Name = "panel1";
this.panel1.Size = new System.Drawing.Size(391, 30); this.panel1.Size = new System.Drawing.Size(390, 25);
this.panel1.TabIndex = 2; this.panel1.TabIndex = 2;
// //
// btnCleanFinished // btnCleanFinished
// //
this.btnCleanFinished.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) this.btnCleanFinished.Dock = System.Windows.Forms.DockStyle.Right;
| System.Windows.Forms.AnchorStyles.Right))); this.btnCleanFinished.Location = new System.Drawing.Point(298, 0);
this.btnCleanFinished.Location = new System.Drawing.Point(298, 3);
this.btnCleanFinished.Name = "btnCleanFinished"; this.btnCleanFinished.Name = "btnCleanFinished";
this.btnCleanFinished.Size = new System.Drawing.Size(90, 23); this.btnCleanFinished.Size = new System.Drawing.Size(90, 23);
this.btnCleanFinished.TabIndex = 3; this.btnCleanFinished.TabIndex = 3;
@ -133,9 +171,8 @@
// //
// cancelAllBtn // cancelAllBtn
// //
this.cancelAllBtn.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) this.cancelAllBtn.Dock = System.Windows.Forms.DockStyle.Left;
| System.Windows.Forms.AnchorStyles.Left))); this.cancelAllBtn.Location = new System.Drawing.Point(0, 0);
this.cancelAllBtn.Location = new System.Drawing.Point(3, 3);
this.cancelAllBtn.Name = "cancelAllBtn"; this.cancelAllBtn.Name = "cancelAllBtn";
this.cancelAllBtn.Size = new System.Drawing.Size(75, 23); this.cancelAllBtn.Size = new System.Drawing.Size(75, 23);
this.cancelAllBtn.TabIndex = 2; this.cancelAllBtn.TabIndex = 2;
@ -145,31 +182,40 @@
// //
// tabPage2 // tabPage2
// //
this.tabPage2.Controls.Add(this.panel4);
this.tabPage2.Controls.Add(this.panel2); this.tabPage2.Controls.Add(this.panel2);
this.tabPage2.Controls.Add(this.logMeTbox); this.tabPage2.Controls.Add(this.logMeTbox);
this.tabPage2.Location = new System.Drawing.Point(4, 24); this.tabPage2.Location = new System.Drawing.Point(4, 24);
this.tabPage2.Name = "tabPage2"; this.tabPage2.Name = "tabPage2";
this.tabPage2.Padding = new System.Windows.Forms.Padding(3); this.tabPage2.Padding = new System.Windows.Forms.Padding(3);
this.tabPage2.Size = new System.Drawing.Size(397, 458); this.tabPage2.Size = new System.Drawing.Size(396, 455);
this.tabPage2.TabIndex = 1; this.tabPage2.TabIndex = 1;
this.tabPage2.Text = "Log"; this.tabPage2.Text = "Log";
this.tabPage2.UseVisualStyleBackColor = true; this.tabPage2.UseVisualStyleBackColor = true;
// //
// panel4
//
this.panel4.Dock = System.Windows.Forms.DockStyle.Bottom;
this.panel4.Location = new System.Drawing.Point(3, 422);
this.panel4.Name = "panel4";
this.panel4.Size = new System.Drawing.Size(390, 5);
this.panel4.TabIndex = 2;
//
// panel2 // panel2
// //
this.panel2.BackColor = System.Drawing.SystemColors.ControlDark; this.panel2.BackColor = System.Drawing.SystemColors.Control;
this.panel2.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle;
this.panel2.Controls.Add(this.clearLogBtn); this.panel2.Controls.Add(this.clearLogBtn);
this.panel2.Dock = System.Windows.Forms.DockStyle.Bottom; this.panel2.Dock = System.Windows.Forms.DockStyle.Bottom;
this.panel2.Location = new System.Drawing.Point(3, 425); this.panel2.Location = new System.Drawing.Point(3, 427);
this.panel2.Name = "panel2"; this.panel2.Name = "panel2";
this.panel2.Size = new System.Drawing.Size(391, 30); this.panel2.Size = new System.Drawing.Size(390, 25);
this.panel2.TabIndex = 1; this.panel2.TabIndex = 1;
// //
// clearLogBtn // clearLogBtn
// //
this.clearLogBtn.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) this.clearLogBtn.Dock = System.Windows.Forms.DockStyle.Left;
| System.Windows.Forms.AnchorStyles.Left))); this.clearLogBtn.Location = new System.Drawing.Point(0, 0);
this.clearLogBtn.Location = new System.Drawing.Point(3, 3);
this.clearLogBtn.Name = "clearLogBtn"; this.clearLogBtn.Name = "clearLogBtn";
this.clearLogBtn.Size = new System.Drawing.Size(75, 23); this.clearLogBtn.Size = new System.Drawing.Size(75, 23);
this.clearLogBtn.TabIndex = 0; this.clearLogBtn.TabIndex = 0;
@ -179,9 +225,7 @@
// //
// logMeTbox // logMeTbox
// //
this.logMeTbox.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) this.logMeTbox.Dock = System.Windows.Forms.DockStyle.Fill;
| System.Windows.Forms.AnchorStyles.Left)
| System.Windows.Forms.AnchorStyles.Right)));
this.logMeTbox.Location = new System.Drawing.Point(3, 3); this.logMeTbox.Location = new System.Drawing.Point(3, 3);
this.logMeTbox.Margin = new System.Windows.Forms.Padding(3, 3, 3, 0); this.logMeTbox.Margin = new System.Windows.Forms.Padding(3, 3, 3, 0);
this.logMeTbox.MaxLength = 10000000; this.logMeTbox.MaxLength = 10000000;
@ -189,19 +233,9 @@
this.logMeTbox.Name = "logMeTbox"; this.logMeTbox.Name = "logMeTbox";
this.logMeTbox.ReadOnly = true; this.logMeTbox.ReadOnly = true;
this.logMeTbox.ScrollBars = System.Windows.Forms.ScrollBars.Both; this.logMeTbox.ScrollBars = System.Windows.Forms.ScrollBars.Both;
this.logMeTbox.Size = new System.Drawing.Size(346, 419); this.logMeTbox.Size = new System.Drawing.Size(390, 449);
this.logMeTbox.TabIndex = 0; this.logMeTbox.TabIndex = 0;
// //
// tabPage3
//
this.tabPage3.Location = new System.Drawing.Point(4, 24);
this.tabPage3.Name = "tabPage3";
this.tabPage3.Padding = new System.Windows.Forms.Padding(3);
this.tabPage3.Size = new System.Drawing.Size(397, 458);
this.tabPage3.TabIndex = 2;
this.tabPage3.Text = "tabPage3";
this.tabPage3.UseVisualStyleBackColor = true;
//
// ProcessBookQueue // ProcessBookQueue
// //
this.AutoScaleDimensions = new System.Drawing.SizeF(7F, 15F); this.AutoScaleDimensions = new System.Drawing.SizeF(7F, 15F);
@ -237,7 +271,11 @@
private System.Windows.Forms.Panel panel2; private System.Windows.Forms.Panel panel2;
private System.Windows.Forms.Button clearLogBtn; private System.Windows.Forms.Button clearLogBtn;
private System.Windows.Forms.ToolStripStatusLabel toolStripStatusLabel1; private System.Windows.Forms.ToolStripStatusLabel toolStripStatusLabel1;
private System.Windows.Forms.TabPage tabPage3;
private VirtualFlowControl virtualFlowControl2; private VirtualFlowControl virtualFlowControl2;
private System.Windows.Forms.ToolStripStatusLabel queueNumberLbl;
private System.Windows.Forms.ToolStripStatusLabel completedNumberLbl;
private System.Windows.Forms.ToolStripStatusLabel errorNumberLbl;
private System.Windows.Forms.Panel panel3;
private System.Windows.Forms.Panel panel4;
} }
} }

View File

@ -1,10 +1,8 @@
using DataLayer; using Dinah.Core.Threading;
using Dinah.Core.Threading;
using LibationWinForms.BookLiberation; using LibationWinForms.BookLiberation;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Runtime.InteropServices;
using System.Threading.Tasks; using System.Threading.Tasks;
using System.Windows.Forms; using System.Windows.Forms;
@ -12,131 +10,196 @@ namespace LibationWinForms.ProcessQueue
{ {
internal partial class ProcessBookQueue : UserControl, ILogForm internal partial class ProcessBookQueue : UserControl, ILogForm
{ {
TrackedQueue<ProcessBook> Queue = new(); private TrackedQueue<ProcessBook> Queue = new();
private readonly LogMe Logger; private readonly LogMe Logger;
private int QueuedCount
{
set
{
queueNumberLbl.Text = value.ToString();
queueNumberLbl.Visible = value > 0;
}
}
private int ErrorCount
{
set
{
errorNumberLbl.Text = value.ToString();
errorNumberLbl.Visible = value > 0;
}
}
private int CompletedCount
{
set
{
completedNumberLbl.Text = value.ToString();
completedNumberLbl.Visible = value > 0;
}
}
public Task QueueRunner { get; private set; } public Task QueueRunner { get; private set; }
public bool Running => !QueueRunner?.IsCompleted ?? false; public bool Running => !QueueRunner?.IsCompleted ?? false;
public ToolStripButton popoutBtn = new(); public ToolStripButton popoutBtn = new();
private int FirstVisible = 0;
private int NumVisible = 0;
private IReadOnlyList<ProcessBookControl> Panels;
public ProcessBookQueue() public ProcessBookQueue()
{ {
InitializeComponent(); InitializeComponent();
Logger = LogMe.RegisterForm(this); Logger = LogMe.RegisterForm(this);
this.popoutBtn.DisplayStyle = ToolStripItemDisplayStyle.Text; popoutBtn.DisplayStyle = ToolStripItemDisplayStyle.Text;
this.popoutBtn.Name = "popoutBtn"; popoutBtn.Name = "popoutBtn";
this.popoutBtn.Text = "Pop Out"; popoutBtn.Text = "Pop Out";
this.popoutBtn.TextAlign = System.Drawing.ContentAlignment.MiddleCenter; popoutBtn.TextAlign = System.Drawing.ContentAlignment.MiddleCenter;
this.popoutBtn.Alignment = ToolStripItemAlignment.Right; popoutBtn.Alignment = ToolStripItemAlignment.Right;
this.popoutBtn.Anchor = AnchorStyles.Bottom | AnchorStyles.Right; popoutBtn.Anchor = AnchorStyles.Bottom | AnchorStyles.Right;
statusStrip1.Items.Add(popoutBtn); statusStrip1.Items.Add(popoutBtn);
virtualFlowControl2.RequestData += VirtualFlowControl1_RequestData; virtualFlowControl2.RequestData += VirtualFlowControl1_RequestData;
virtualFlowControl2.ButtonClicked += VirtualFlowControl2_ButtonClicked;
Queue.QueuededCountChanged += Queue_QueuededCountChanged;
Queue.CompletedCountChanged += Queue_CompletedCountChanged;
QueuedCount = 0;
ErrorCount = 0;
CompletedCount = 0;
}
private void Queue_CompletedCountChanged(object sender, int e)
{
int errCount = Queue.Completed.Count(p => p.Result is ProcessBookResult.FailedAbort or ProcessBookResult.FailedSkip or ProcessBookResult.ValidationFail);
int completeCount = Queue.Completed.Count(p => p.Result is ProcessBookResult.Success);
ErrorCount = errCount;
CompletedCount = completeCount;
UpdateProgressBar();
}
private void Queue_QueuededCountChanged(object sender, int cueCount)
{
QueuedCount = cueCount;
virtualFlowControl2.VirtualControlCount = Queue.Count;
UpdateProgressBar();
}
private void UpdateProgressBar()
{
toolStripProgressBar1.Maximum = Queue.Count;
toolStripProgressBar1.Value = Queue.Completed.Count;
}
private void VirtualFlowControl2_ButtonClicked(int itemIndex, string buttonName, ProcessBookControl panelClicked)
{
ProcessBook item = Queue[itemIndex];
if (buttonName == "cancelBtn")
{
item.Cancel();
Queue.RemoveQueued(item);
virtualFlowControl2.VirtualControlCount = Queue.Count;
UpdateControl(itemIndex);
}
else if (buttonName == "moveFirstBtn")
{
Queue.MoveQueuePosition(item, QueuePosition.Fisrt);
UpdateAllControls();
}
else if (buttonName == "moveUpBtn")
{
Queue.MoveQueuePosition(item, QueuePosition.OneUp);
UpdateControl(itemIndex - 1);
UpdateControl(itemIndex);
}
else if (buttonName == "moveDownBtn")
{
Queue.MoveQueuePosition(item, QueuePosition.OneDown);
UpdateControl(itemIndex + 1);
UpdateControl(itemIndex);
}
else if (buttonName == "moveLastBtn")
{
Queue.MoveQueuePosition(item, QueuePosition.Last);
UpdateAllControls();
}
}
private void UpdateControl(int queueIndex)
{
int i = queueIndex - FirstVisible;
if (i < 0 || i > NumVisible) return;
var proc = Queue[queueIndex];
Panels[i].Invoke(() =>
{
Panels[i].SuspendLayout();
Panels[i].SetCover(proc.Cover);
Panels[i].SetBookInfo(proc.BookText);
if (proc.Result != ProcessBookResult.None)
{
Panels[i].SetResult(proc.Result);
return;
}
Panels[i].SetStatus(proc.Status);
Panels[i].SetProgrss(proc.Progress);
Panels[i].SetRemainingTime(proc.TimeRemaining);
Panels[i].ResumeLayout();
});
}
private void UpdateAllControls()
{
int numToShow = Math.Min(NumVisible, Queue.Count - FirstVisible);
for (int i = 0; i < numToShow; i++)
UpdateControl(FirstVisible + i);
} }
private void VirtualFlowControl1_RequestData(int firstIndex, int numVisible, IReadOnlyList<ProcessBookControl> panelsToFill) private void VirtualFlowControl1_RequestData(int firstIndex, int numVisible, IReadOnlyList<ProcessBookControl> panelsToFill)
{ {
int numToShow = Math.Min(numVisible, Queue.Count - firstIndex); FirstVisible = firstIndex;
for (int i = 0; i < numToShow; i++) NumVisible = numVisible;
{ Panels = panelsToFill;
var proc = Queue[firstIndex + i]; UpdateAllControls();
panelsToFill[i].SetCover(proc.Entry.Cover);
panelsToFill[i].SetTitle(proc.Entry.Title);
}
} }
public async Task AddDownloadDecrypt(IEnumerable<GridEntry> entries) public void AddDownloadDecrypt(IEnumerable<GridEntry> entries)
{ {
SuspendLayout();
foreach (var entry in entries) foreach (var entry in entries)
await AddDownloadDecryptAsync(entry); AddDownloadDecryptAsync(entry);
ResumeLayout();
} }
int count = 0;
public async Task AddDownloadDecryptAsync(GridEntry gridEntry) public void AddDownloadDecryptAsync(GridEntry gridEntry)
{ {
//if (Queue.Any(b=> b?.Entry?.AudibleProductId == gridEntry.AudibleProductId)) if (Queue.Any(b => b?.LibraryBook?.Book?.AudibleProductId == gridEntry.AudibleProductId))
//return; return;
ProcessBook pbook = new ProcessBook(gridEntry, Logger); ProcessBook pbook = new(gridEntry.LibraryBook, gridEntry.Cover, Logger);
pbook.Completed += Pbook_Completed; pbook.DataAvailable += Pbook_DataAvailable;
pbook.Cancelled += Pbook_Cancelled;
pbook.RequestMove += (o,d) => RequestMove(o, d);
var libStatus = gridEntry.Liberate; pbook.AddDownloadDecryptBook();
pbook.AddDownloadPdf();
if (libStatus.BookStatus != LiberatedStatus.Liberated) Queue.Enqueue(pbook);
pbook.AddDownloadDecryptProcessable();
if (libStatus.PdfStatus != LiberatedStatus.Liberated)
pbook.AddPdfProcessable();
Queue.EnqueueBook(pbook);
//await AddBookControlAsync(pbook.BookControl);
count++;
virtualFlowControl2.VirtualControlCount = count;
if (!Running) if (!Running)
{ {
//QueueRunner = QueueLoop(); QueueRunner = QueueLoop();
} }
toolStripStatusLabel1.Text = count.ToString();
} }
private async void Pbook_Cancelled(ProcessBook sender, EventArgs e) private void Pbook_DataAvailable(object sender, EventArgs e)
{ {
Queue.Remove(sender); int index = Queue.IndexOf((ProcessBook)sender);
//await RemoveBookControlAsync(sender.BookControl); UpdateControl(index);
}
/// <summary>
/// Handles requests by <see cref="ProcessBook"/> to change its order in the queue
/// </summary>
/// <param name="sender">The requesting <see cref="ProcessBook"/></param>
/// <param name="direction">The requested position</param>
/// <returns>The resultant position</returns>
private QueuePosition RequestMove(ProcessBook sender, QueuePositionRequest requested)
{
var direction = Queue.MoveQueuePosition(sender, requested);
if (direction is QueuePosition.Absent or QueuePosition.Current or QueuePosition.Completed)
return direction;
return direction;
/*
var firstQueue = autosizeFlowLayout1.Controls.Cast<ProcessBookControl>().FirstOrDefault(c => c.Status == ProcessBookStatus.Queued);
if (firstQueue is null) return QueuePosition.Current;
int firstQueueIndex = autosizeFlowLayout1.Controls.IndexOf(firstQueue);
var index = autosizeFlowLayout1.Controls.IndexOf(sender.BookControl);
int newIndex = direction switch
{
QueuePosition.Fisrt => firstQueueIndex,
QueuePosition.OneUp => index - 1,
QueuePosition.OneDown => index + 1,
QueuePosition.Last => autosizeFlowLayout1.Controls.Count - 1,
_ => -1,
};
if (newIndex < 0) return direction;
autosizeFlowLayout1.Controls.SetChildIndex(sender.BookControl, newIndex);
return direction;
*/
} }
private async Task QueueLoop() private async Task QueueLoop()
@ -147,80 +210,28 @@ namespace LibationWinForms.ProcessQueue
var result = await nextBook.ProcessOneAsync(); var result = await nextBook.ProcessOneAsync();
switch (result) if (result == ProcessBookResult.FailedRetry)
{ Queue.Enqueue(nextBook);
case ProcessBookResult.FailedRetry: else if (result == ProcessBookResult.FailedAbort)
Queue.EnqueueBook(nextBook); return;
break;
case ProcessBookResult.FailedAbort:
return;
}
} }
Queue_CompletedCountChanged(this, 0);
} }
private void cancelAllBtn_Click(object sender, EventArgs e)
private void Pbook_Completed(object sender, EventArgs e)
{ {
}
private async void cancelAllBtn_Click(object sender, EventArgs e)
{
List<ProcessBook> l1 = Queue.QueuedItems();
Queue.ClearQueue(); Queue.ClearQueue();
Queue.Current?.Cancel(); Queue.Current?.Cancel();
virtualFlowControl2.VirtualControlCount = Queue.Count;
//await RemoveBookControlsAsync(l1.Select(l => l.BookControl)); UpdateAllControls();
} }
private async void btnCleanFinished_Click(object sender, EventArgs e) private void btnCleanFinished_Click(object sender, EventArgs e)
{ {
List<ProcessBook> l1 = Queue.CompletedItems();
Queue.ClearCompleted(); Queue.ClearCompleted();
//await RemoveBookControlsAsync(l1.Select(l => l.BookControl)); virtualFlowControl2.VirtualControlCount = Queue.Count;
} UpdateAllControls();
private async Task AddBookControlAsync(ProcessBookControl control)
{
await Task.Run(() => Invoke(() =>
{
/*
control.Width = autosizeFlowLayout1.DesiredBookControlWidth;
autosizeFlowLayout1.Controls.Add(control);
autosizeFlowLayout1.SetFlowBreak(control, true);
*/
//Refresh();
//System.Threading.Thread.Sleep(1000);
}));
}
private async Task RemoveBookControlAsync(ProcessBookControl control)
{
await Task.Run(() => Invoke(() =>
{
//autosizeFlowLayout1.Controls.Remove(control);
}));
}
private async Task RemoveBookControlsAsync(IEnumerable<ProcessBookControl> control)
{
await Task.Run(() => Invoke(() =>
{
/*
SuspendLayout();
foreach (var l in control)
autosizeFlowLayout1.Controls.Remove(l);
ResumeLayout();
*/
}));
}
public void WriteLine(string text)
{
if (!IsDisposed)
logMeTbox.UIThreadAsync(() => logMeTbox.AppendText($"{DateTime.Now} {text}{Environment.NewLine}"));
} }
private void clearLogBtn_Click(object sender, EventArgs e) private void clearLogBtn_Click(object sender, EventArgs e)
@ -228,5 +239,10 @@ namespace LibationWinForms.ProcessQueue
logMeTbox.Clear(); logMeTbox.Clear();
} }
public void WriteLine(string text)
{
if (!IsDisposed)
logMeTbox.UIThreadAsync(() => logMeTbox.AppendText($"{DateTime.Now} {text}{Environment.NewLine}"));
}
} }
} }

View File

@ -60,4 +60,574 @@
<metadata name="statusStrip1.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"> <metadata name="statusStrip1.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
<value>17, 17</value> <value>17, 17</value>
</metadata> </metadata>
<assembly alias="System.Drawing" name="System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" />
<data name="queueNumberLbl.Image" type="System.Drawing.Bitmap, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>
iVBORw0KGgoAAAANSUhEUgAAAm4AAAJuCAYAAAAJqI4TAAAAIGNIUk0AAHolAACAgwAA+f8AAIDpAAB1
MAAA6mAAADqYAAAXb5JfxUYAAAAJcEhZcwAACwwAAAsMAT9AIsgAACyySURBVHhe7d0/zL5tXd9xwQik
okkDuEhs0kGj3URxFYOmSYeq0a1xdDUdmq4MHVwZuzqadCLibCNOTVAcUTb/LCKDYIJBepziHeTg+zzH
735+93Xen+9xvD7JK8Hv4lM5r+d850pz3T/w7W9/GwCABsojAAB5yiMAAHnKIwAAecojAAB5yiMAAHnK
IwAAecojAAB5yiMAAHnKIwAAecojAAB5yiMAAHnKIwAAecojAAB5yiMAAHnKIwAAecojAAB5yiMAAHnK
IwAAecojAAB5yiMAAHnKIwAAecojAAB5yiMAAHnKIwAAecojAAB5yiMAAHnKIwAAecojAAB5yiMAAHnK
IwAAecojAAB5yiMAAHnKIwAAecojAAB5yiMAAHnKIwAAecojAAB5yiMAAHnKIwAAecojAAB5yiMAAHnK
IwAAecojAAB5yiMAAHnKIwAAecojAAB5yiMAAHnKIwAAecojAAB5yiMAAHnKIwAAecojAAB5yiMAAHnK
IwAAecojAAB5yiMAAHnKIwAAecojAAB5yiMAAHnKIwAAecojAAB5yiMAAHnKIwAAecojAAB5yiMAAHnK
IwAAecojAAB5yiMAAHnKIwAAecojAAB5yiMAAHnKIwAAecojAAB5ymMnts3++3D9F8o7+2/D3ftfQ/XP
wnf95+Hu/buh+mfhu/7vYButaoATlcdObJsJtzXhlkm4ZRJum61qgBOVx05smwm3NeGWSbhlEm6brWqA
E5XHTmybCbc14ZZJuGUSbputaoATlcdObJsJtzXhlkm4ZRJum61qgBOVx05smwm3NeGWSbhlEm6brWqA
E5XHTmybCbc14ZZJuGUSbputaoATlcdObJsJtzXhlkm4ZRJum61qgBOVx05smwm3NeGWSbhlEm6brWqA
E5XHTmybCbc14ZZJuGUSbputaoATlcdObJsJtzXhlkm4ZRJum61qgBOVx05smwm3NeGWSbhlEm6brWqA
E5XHTmybCbc14ZZJuGUSbputaoATlcdObJsJtzXhlkm4ZRJum61qgBOVx05smwm3NeGWSbhlEm6brWqA
E5XHTmybCbc14ZZJuGUSbputaoATlcdObJsJtzXhlkm4ZRJum61qgBOVx05smwm3NeGWSbhlEm6brWqA
E5XHTmybCbc14ZZJuGUSbputaoATlcdObJsJtzXhlkm4ZRJum61qgBOVx05smwm3NeGWSbhlEm6brWqA
E5XHTmybCbc14ZZJuGUSbputaoATlcdObJsJtzXhlkm4ZRJum61qgBOVx05smwm3NeGWSbhlEm6brWqA
E5XHTmybCbc14ZZJuGUSbputaoATlcdObJsJtzXhlkm4ZRJum61qgBOVx05smwm3NeGWSbhlEm6brWqA
E5XHTmybCbc14ZZJuGUSbputaoATlcdObJsJtzXhlkm4ZRJum61qgBOVx05smwm3NeGWSbhlEm6brWqA
E5XHTmybCbc14ZZJuGUSbputaoATlcdObJsJtzXhlkm4ZRJum61qgBOVx05smwm3NeGWSbhlEm6brWqA
E5XHTmybCbc14ZZJuGUSbputaoATlcdObJsJtzXhlkm4ZRJum61qgBOVx05smwm3NeGWSbhlEm6brWqA
E5XHTmybCbc14ZZJuGUSbputaoATlcdObJsJtzXhlkm4ZRJum61qgBOVx05smwm3NeGWSbhlEm6brWqA
E5XHTmybCbc14ZZJuGUSbputaoATlcdObJsJtzXhlkm4ZRJum61qgBOVx05smwm3NeGWSbhlEm6brWqA
E5XHTmybCbc14ZZJuGUSbputaoATlcdObJsJtzXhlkm4ZRJum61qgBOVx05smwm3NeGWSbhlEm6brWqA
E5XHTmybCbc14ZZJuGUSbputaoATlcdObJsJtzXhlkm4ZRJum61qgBOVx05smwm3NeGWSbhlEm6brWqA
E5XHTmybCbc14ZZJuGUSbputaoATlcdObJsJtzXhlkm4ZRJum61qgBOVx05smwm3NeGWSbhlEm6brWqA
E5XHTmybCbc14ZZJuGUSbputaoATlcdObJsJtzXhlkm4ZRJum61qgBOVx05smwm3NeGWSbhlEm6brWqA
E5XHTmybCbc14ZZJuGUSbputaoATlcdObJsJtzXhlkm4ZRJum61qgBOVx05smwm3NeGWSbhlEm6brWqA
E5XHTmybCbc14ZZJuGUSbputaoATlcdObJsJtzXhlkm4ZRJum61qgBOVx05smwm3NeGWSbhlEm6brWqA
E5XHTmybCbc14ZZJuGUSbputaoATlcdObJsJtzXhlkm4ZRJum61qgBOVx05smwm3NeGWSbhlEm6brWqA
E5XHTmybCbc14ZZJuGUSbputaoATlcdObJsJtzXhlkm4ZRJum61qgBOVx05smwm3NeGWSbhlEm6brWqA
E5XHTmybCbc14ZZJuGUSbputaoATlcdObJsJtzXhlkm4ZRJum61qgBOVx05smwm3NeGWSbhlEm6brWqA
E5XHTmybCbc14ZZJuGUSbputaoATlcdObJsJtzXhlkm4ZRJum61qgBOVx05smwm3NeGWSbhlEm6brWqA
E5XHTmybCbc14ZZJuGUSbputaoATlcdObJsJtzXhlkm4ZRJum61qgBOVx05smwm3NeGWSbhlEm6brWqA
E5XHTmybCbc14ZZJuGUSbputaoATlcdObJsJtzXhlkm4ZRJum61qgBOVx05smwm3NeGWSbhlEm6brWqA
E5XHTmybCbc14ZZJuGUSbputaoATlcdObJsJtzXhlkm4ZRJum61qgBOVx05smwm3NeGWSbhlEm6brWqA
E5XHTmybCbc14ZZJuGUSbputaoATlcdObJsJtzXhlkm4ZRJum61qgBOVx05smwm3NeGWSbhlEm6brWqA
E5XHTmybCbc14ZZJuGUSbputaoATlcdObJsJtzXhlkm4ZRJum61qgBOVx05smwm3NeGWSbhlEm6brWqA
E5XHTmybCbc14ZZJuGUSbputaoATlcdObJsJtzXhlkm4ZRJum61qgBOVx05smwm3NeGWSbhlEm6brWqA
E5XHTmybCbc14ZZJuGUSbputaoATlcdObJsJtzXhlkm4ZRJum61qgBOVx05smwm3NeGWSbhlEm6brWqA
E5XHTmybCbc14ZZJuGUSbputaoATlcdObJsJtzXhlkm4ZRJum61qgBOVx05smwm3NeGWSbhlEm6brWqA
E5XHTmybCbc14ZZJuGUSbputaoATlcdObJsJtzXhlkm4ZRJum61qgBOVx05smwm3NeGWSbhlEm6brWqA
E5XHTmybCbc14ZZJuGUSbputaoATlcdObJsJtzXhlkm4ZRJum61qgBOVx05smwm3NeGWSbhlEm6brWqA
E5XHTmybCbc14ZZJuGUSbputaoATlcdObJsJtzXhlkm4ZRJum61qgBOVx05smwm3NeGWSbhlEm6brWqA
E5XHTmybCbc14ZZJuGUSbputaoATlcdObJsJtzXhlkm4ZRJum61qgBOVx05smwm3NeGWSbhlEm6brWqA
E5XHTmybCbc14ZZJuGUSbputaoATlcdObJsJtzXhlkm4ZRJum61qgBOVx05smwm3NeGWSbhlEm6brWqA
E5XHTmybCbc14ZZJuGUSbputaoATlcdObJsJtzXhlkm4ZRJum61qgBOVx05smwm3NeGWSbhlEm6brWqA
E5XHTmybCbc14ZZJuGUSbputaoATlcdObJsJtzXhlkm4ZRJum61qgBOVx05smwm3NeGWSbhlEm6brWqA
E5XHTmybCbc14ZZJuGUSbputaoATlcdObJsJtzXhlkm4ZRJum61qgBOVx05smwm3NeGWSbhlEm6brWqA
E5XHTmybCbc14ZZJuGUSbputaoATlcdObJsJtzXhlkm4ZRJum61qgBOVx05smwm3NeGWSbhlEm6brWqA
E5XHTmybCbc14ZZJuGUSbputaoATlcdObJsJtzXhlkm4ZRJum61qgBOVx05smwm3NeGWSbhlEm6brWqA
E5XHTmybCbc14ZZJuGUSbputaoATlcdObJsJtzXhlkm4ZRJum61qgBOVx05smwm3NeGWSbhlEm6brWqA
E5XHTmybCbc14ZZJuGUSbputaoATlcdObJsJtzXhlkm4ZRJum61qgBOVx05smwm3NeGWSbhlEm6brWqA
E5XHTmybCbc14ZZJuGUSbputaoATlcdObJsJtzXhlkm4ZRJum61qgBOVx07Gfmn4oes/WOsJtzXhlkm4
ZXqNcPv3wye/8x/tpVc1wInKYydj/2f40uDD0nvCbU24ZRJumV4j3P7D8K3hfw8fvg72cqsa4ETlsZOx
K9yu/+DD0nvCbU24ZRJumV4r3J7+939l+JXBXmhVA5yoPHYy9hRu//bD8suD9ZpwWxNumYRbptcOtye/
P3x0sLdc1QAnKo+djM3h9uT6sHxksB4TbmvCLZNwy5QSbpe/HX5rsLdY1QAnKo+djL1TuF18WPpMuK0J
t0zCLVNSuD353PDxwd7DqgY4UXnsZOzdwu2JD0v+hNuacMsk3DIlhtvla8PvDO8f7BmrGuBE5bGTsTcJ
t8v1Yfnt4X2D5U24rQm3TMItU2q4Pbn++X5qsDdc1QAnKo+djL1puD3xYcmccFsTbpmEW6b0cLt8Y/jM
4LdI32BVA5yoPHYy9txwu1wflv85/OBgGRNua8Itk3DL1CHcnvzp8HODvcuqBjhReexk7L2E25MvDp8Y
7PUn3NaEWybhlqlTuF3+afjscP13a8WqBjhReexk7G3C7XJ9WH53+NBgrzfhtibcMgm3TN3C7clfDNef
crRpVQOcqDx2Mva24fbky8OnBnudCbc14ZZJuGXqGm6Xfx6uvwT0o4P966oGOFF57GTspcLt4sPyehNu
a8Itk3DL1Dncnvz18OuDjVUNcKLy2MnYS4bbk+vD8muD3TfhtibcMgm3TDuE25PrLwH92HD0qgY4UXns
ZOwR4fbk+rB8bLDHT7itCbdMwi3TTuF2+epw/Rbpsasa4ETlsZOxR4bb5fgPy00TbmvCLZNwy7RbuD35
w+H633PcqgY4UXnsZOzR4fbk88ORH5abJtzWhFsm4ZZp13C7fH047rdIqwY4UXnsZOyucLsc+WG5acJt
TbhlEm6Zdg63J38y/MxwxKoGOFF57GTsznB78oXhmA/LTRNua8Itk3DLdEK4Xb45XL9F+sFh61UNcKLy
2MnYa4Tb5enD8oHB3n7CbU24ZRJumU4Jtyd/PvzCsO2qBjhReexk7LXC7cmXhk8O9nYTbmvCLZNwy3Ra
uF2+NVy/RfrhYbtVDXCi8tjJ2GuH22XrD8tNE25rwi2TcMt0Yrg9+crwK8NWqxrgROWxk7GEcHtyfVh+
ebDnT7itCbdMwi3TyeH25Pot0o8OW6xqgBOVx07GksLtyfVh+chgbz7htibcMgm3TMLtO/52+K2h/aoG
OFF57GQsMdwu23xYbppwWxNumYRbJuH2vT43fHxou6oBTlQeOxlLDbcn7T8sN024rQm3TMItk3D7fl8b
fmd4/9BuVQOcqDx2MpYebpfrw3L92az3DVZPuK0Jt0zCLZNwe2fX/21+ami1qgFOVB47GesQbk9aflhu
mnBbE26ZhFsm4fbuvjF8ZvihocWqBjhReexkrFO4Xa4Piz+b9f0TbmvCLZNwyyTc3syfDj83xK9qgBOV
x07GuoXbk+vD8onBvjPhtibcMgm3TMLtzf3T8Nnheq5iVzXAicpjJ2Ndw+1yfViuP5v1oeH0Cbc14ZZJ
uGUSbs/3F8MvDZGrGuBE5bGTsc7h9uTLw6eGkyfc1oRbJuGWSbi9N/88XH8J6EeHqFUNcKLy2MnYDuF2
if2w3DThtibcMgm3TMLt7fz18OtDzKoGOFF57GRsl3B7cn1Yfm04bcJtTbhlEm6ZhNvLuP4S0I8Nr76q
AU5UHjsZ2y3cnlwflo8Np0y4rQm3TMItk3B7OV8drt8ifdVVDXCi8tjJ2K7hdon4sNw04bYm3DIJt0zC
7eX94XD9v/FVVjXAicpjJ2M7h9uTzw+v9mG5acJtTbhlEm6ZhNtjfH14ld8irRrgROWxk7ETwu3yah+W
mybc1oRbJuGWSbg91p8MPzPctqoBTlQeOxk7JdyefGG49cNy04TbmnDLJNwyCbfH++Zw/RbpB4eHr2qA
E5XHTsZOC7fL04flA8MuE25rwi2TcMsk3O7z58MvDA9d1QAnKo+djJ0Ybk++NHxy2GHCbU24ZRJumYTb
vb41XL9F+uHhIasa4ETlsZOxk8Pt8vAPy00TbmvCLZNwyyTcXsdXhl8ZXnxVA5yoPHYydnq4Pbk+LL88
dJ1wWxNumYRbJuH2uq7fIv3o8GKrGuBE5bGTMeH2va4Py0eGbhNua8Itk3DLJNxe398OvzW8yKoGOFF5
7GRMuH2/F/2w3DThtibcMgm3TMItx+eGjw9vtaoBTlQeOxkTbu/sRT4sN024rQm3TMItk3DL8rXhd4b3
D+9pVQOcqDx2Mibc3t1bf1humnBbE26ZhFsm4Zbp+u/lp4Znr2qAE5XHTsaE25t5zx+Wmybc1oRbJuGW
Sbjl+sbwmeGHhjde1QAnKo+djAm3N3d9WFL/bJZwWxNumYRbJuGW70+HnxveaFUDnKg8djIm3J7v+rB8
YkiacFsTbpmEWybh1sM/DZ8drmf6XVc1wInKYydjwu29uT4s15/N+tCQMOG2JtwyCbdMwq2Xvxh+aXjH
VQ1wovLYyZhweztfHj41vPaE25pwyyTcMgm3fv55uP4S0I8O37eqAU5UHjsZE25v710/LDdNuK0Jt0zC
LZNw6+uvh18fvmdVA5yoPHYyJtxezvVh+bXhNSbc1oRbJuGWSbj1d/0loB8b/mVVA5yoPHYyJtxe3vVh
+dhw54TbmnDLJNwyCbc9fHX47aFsgBOVx07GhNtj/MFw54/2Crc14ZZJuGUSbvv45vBfqgY4UXnsZEy4
vazX+q034bYm3DIJt0zCbQ9fHP7l56uqBjhReexkTLi9nD8afnJ4jQm3NeGWSbhlEm69fd+XCFUDnKg8
djIm3N7e3w/X/x+C9w2vNeG2JtwyCbdMwq2v8kuEqgFOVB47GRNub+dzw48Prz3htibcMgm3TMKtn68N
7/glQtUAJyqPnYwJt/fmb4bfGFIm3NaEWybhlkm49XJ9ifDx4R1XNcCJymMnY8Ltea4f2/294SND0oTb
mnDLJNwyCbceri8RfnNYrmqAE5XHTsaE25v7y+HTQ+KE25pwyyTcMgm3fNdvhr7xlwhVA5yoPHYyJtzW
rj8o/9nhh4fUCbc14ZZJuGUSbrm+Mjz7S4SqAU5UHjsZE27v7kvDzw/pE25rwi2TcMsk3PJ8a7j+LvaH
h2evaoATlcdOxoRb7R+HzwwfGDpMuK0Jt0zCLZNwy3J9ifDJ4T2vaoATlcdOxoTb9/vj4aeHThNua8It
k3DLJNwyXH+u6neHt/4SoWqAE5XHTsaE23f9w3D90vSdf2P0pSbc1oRbJuGWSbi9vi8ML/YlQtUAJyqP
nYwJt++4/ij8TwxdJ9zWhFsm4ZZJuL2erw8v/iVC1QAnKo+djJ0ebl8drl+a7j7htibcMgm3TMLtdXx+
eMiXCFUDnKg8djJ2crhdv4HzsWGHCbc14ZZJuGUSbvd6+JcIVQOcqDx2MnZiuP3V8KvDThNua8Itk3DL
JNzuc8uXCFUDnKg8djJ2Urhdf67q+g2cHxl2m3BbE26ZhFsm4fZ4t36JUDXAicpjJ2OnhNuXh18cdp1w
WxNumYRbJuH2OK/yJULVACcqj52M7R5u15+run4D54PDzhNua8Itk3DLJNwe4/oS4VPD7asa4ETlsZOx
ncPti8PPDidMuK0Jt0zCLZNwe1lPXyJ8aHiVVQ1wovLYydiO4faN4foNnB8cTplwWxNumYRbJuH2cq4v
ET4xvOqqBjhReexkbLdw+6PhJ4fTJtzWhFsm4ZZJuL29qC8RqgY4UXnsZGyXcPv74foNnPcNJ064rQm3
TMItk3B7O3FfIlQNcKLy2MnYDuH2ueHHh5Mn3NaEWybhlkm4vTdfGyK/RKga4ETlsZOxzuH2N8NvDCbc
3oRwyyTcMgm357u+RPj4ELmqAU5UHjsZ6xhu12/g/N7wkcG+M+G2JtwyCbdMwu3NXV8i/OYQvaoBTlQe
OxnrFm5/OXx6sO+dcFsTbpmEWybh9mauP1fV4kuEqgFOVB47GesSbtdv4Hx2+OHBvn/CbU24ZRJumYTb
u/vK0OpLhKoBTlQeOxnrEG5/Nvz8YO884bYm3DIJt0zCrfat4fpzVR8eWq1qgBOVx07GksPtH4fPDB8Y
7N0n3NaEWybhlkm4fb8vDZ8cWq5qgBOVx07GUsPtj4efHuzNJtzWhFsm4ZZJuH3XN4frz1W1/hKhaoAT
lcdOxtLC7R+G65em3z/Ym0+4rQm3TMItk3D7ji8MW3yJUDXAicpjJ2NJ4fYHw08M9vwJtzXhlkm4ZTo9
3L4+bPUlQtUAJyqPnYwlhNvfDdcvTdt7n3BbE26ZhFumk8Pt88N2XyJUDXCi8tjJ2GuH2/UbOB8b7O0m
3NaEWybhlunEcPvqsO2XCFUDnKg8djL2WuH2V8OvDvYyE25rwi2TcMt0Wrht/yVC1QAnKo+djN0dbtef
q7p+A+dHBnu5Cbc14ZZJuGU6JdyO+RKhaoATlcdOxu4Mty8PvzjYy0+4rQm3TMIt0+7hdtyXCFUDnKg8
djJ2R7g9/QbOBwd7zITbmnDLJNwy7Rxu15cInxqOWtUAJyqPnYw9Oty+OPzsYI+dcFsTbpmEW6Ydw+36
m9fHfolQNcCJymMnY48Kt28M12/g/OBgj59wWxNumYRbpt3C7foS4RPDsasa4ETlsZOxR4TbHw0/Odh9
E25rwi2TcMu0S7j5EuFfVzXAicpjJ2MvGW5/P1y/gfO+we6dcFsTbpmEW6Ydws2XCP9mVQOcqDx2MvZS
4fa54ccHe50JtzXhlkm4Zeocbr5EKFY1wInKYydjbxtufzP8xmCvO+G2JtwyCbdMXcPt+hLh44NNqxrg
ROWxk7H3Gm7Xb+D83vCRwV5/wm1NuGUSbpm6hdv1JcJvDvYOqxrgROWxk7H3Em5/OXx6sJwJtzXhlkm4
ZeoSbr5EeMNVDXCi8tjJ2HPC7foNnM8OPzxY1oTbmnDLJNwydQi3rwy+RHjDVQ1wovLYydibhtufDT8/
WOaE25pwyyTcMiWH27eG689VfXiwN1zVACcqj52MrcLtH4fPDB8YLHfCbU24ZRJumVLD7UvDJwd75qoG
OFF57GTs3cLtj4efHix/wm1NuGUSbpnSwu3pb177EuE9rmqAE5XHTsaqcPuH4fql6fcP1mPCbU24ZRJu
mZLC7QuDLxHeclUDnKg8djI2h9sfDD8xWK8JtzXhlkm4ZUoIt68PvkR4oVUNcKLy2MnYU7j93XD90rT1
nHBbE26ZhFum1w63zw++RHjBVQ1wovLYydgVbr8/fOz6H6zthNuacMsk3DK9Vrh9dfAlwgNWNcCJymMn
Y9cHxfpPuK0Jt0zCLdNrhNv1G6Ef/c5/tJde1QAnKo+d2DYTbmvCLZNwy/Qa4WYPXNUAJyqPndg2E25r
wi2TcMsk3DZb1QAnKo+d2DYTbmvCLZNwyyTcNlvVACcqj53YNhNua8Itk3DLJNw2W9UAJyqPndg2E25r
wi2TcMsk3DZb1QAnKo+d2DYTbmvCLZNwyyTcNlvVACcqj53YNhNua8Itk3DLJNw2W9UAJyqPndg2E25r
wi2TcMsk3DZb1QAnKo+d2DYTbmvCLZNwyyTcNlvVACcqj53YNhNua8Itk3DLJNw2W9UAJyqPndg2E25r
wi2TcMsk3DZb1QAnKo+d2DYTbmvCLZNwyyTcNlvVACcqj53YNhNua8Itk3DLJNw2W9UAJyqPndg2E25r
wi2TcMsk3DZb1QAnKo+d2DYTbmvCLZNwyyTcNlvVACcqj53YNhNua8Itk3DLJNw2W9UAJyqPndg2E25r
wi2TcMsk3DZb1QAnKo+d2DYTbmvCLZNwyyTcNlvVACcqj53YNhNua8Itk3DLJNw2W9UAJyqPndg2E25r
wi2TcMsk3DZb1QAnKo+d2DYTbmvCLZNwyyTcNlvVACcqj53YNhNua8Itk3DLJNw2W9UAJyqPndg2E25r
wi2TcMsk3DZb1QAnKo+d2DYTbmvCLZNwyyTcNlvVACcqj53YNhNua8Itk3DLJNw2W9UAJyqPndg2E25r
wi2TcMsk3DZb1QAnKo+d2DYTbmvCLZNwyyTcNlvVACcqj53YNhNua8Itk3DLJNw2W9UAJyqPndg2E25r
wi2TcMsk3DZb1QAnKo+d2DYTbmvCLZNwyyTcNlvVACcqj53YNhNua8Itk3DLJNw2W9UAJyqPndg2E25r
wi2TcMsk3DZb1QAnKo+d2DYTbmvCLZNwyyTcNlvVACcqj53YNhNua8Itk3DLJNw2W9UAJyqPndg2E25r
wi2TcMsk3DZb1QAnKo+d2DYTbmvCLZNwyyTcNlvVACcqj53YNhNua8Itk3DLJNw2W9UAJyqPndg2E25r
wi2TcMsk3DZb1QAnKo+d2DYTbmvCLZNwyyTcNlvVACcqj53YNhNua8Itk3DLJNw2W9UAJyqPndg2E25r
wi2TcMsk3DZb1QAnKo+d2DYTbmvCLZNwyyTcNlvVACcqj53YNhNua8Itk3DLJNw2W9UAJyqPndg2E25r
wi2TcMsk3DZb1QAnKo+d2DYTbmvCLZNwyyTcNlvVACcqj6zZi0+4rQm3TMItk3B70Kp3Ivcpj6zZi0+4
rQm3TMItk3B70Kp3Ivcpj6zZi0+4rQm3TMItk3B70Kp3Ivcpj6zZi0+4rQm3TMItk3B70Kp3Ivcpj6zZ
i0+4rQm3TMItk3B70Kp3Ivcpj6zZi0+4rQm3TMItk3B70Kp3Ivcpj6zZi0+4rQm3TMItk3B70Kp3Ivcp
j6zZi0+4rQm3TMItk3B70Kp3Ivcpj6zZi0+4rQm3TMItk3B70Kp3Ivcpj6zZi0+4rQm3TMItk3B70Kp3
Ivcpj6zZi0+4rQm3TMItk3B70Kp3Ivcpj6zZi0+4rQm3TMItk3B70Kp3Ivcpj6zZi0+4rQm3TMItk3B7
0Kp3Ivcpj6zZi0+4rQm3TMItk3B70Kp3Ivcpj6zZi0+4rQm3TMItk3B70Kp3Ivcpj6zZi0+4rQm3TMIt
k3B70Kp3Ivcpj6zZi0+4rQm3TMItk3B70Kp3Ivcpj6zZi0+4rQm3TMItk3B70Kp3Ivcpj6zZi0+4rQm3
TMItk3B70Kp3Ivcpj6zZi0+4rQm3TMItk3B70Kp3Ivcpj6zZi0+4rQm3TMItk3B70Kp3Ivcpj6zZi0+4
rQm3TMItk3B70Kp3Ivcpj6yNfbqZTw7JE25rwi2TcMvUJdyufzdX/86OVb0TuU95ZG3s3/4LooP/NyRP
uK0Jt0zCLVOXcLv+3Vz988ea34fcqzyyNj/IDQi3/oRbJuGWSbg9yPw+5F7lkbX5QW5AuPUn3DIJt0zC
7UHm9yH3Ko+szQ9yA8KtP+GWSbhlEm4PMr8PuVd5ZG1+kBsQbv0Jt0zCLZNwe5D5fci9yiNr84PcgHDr
T7hlEm6ZhNuDzO9D7lUeWZsf5AaEW3/CLZNwyyTcHmR+H3Kv8sja/CA3INz6E26ZhFsm4fYg8/uQe5VH
1uYHuQHh1p9wyyTcMgm3B5nfh9yrPLI2P8gNCLf+hFsm4ZZJuD3I/D7kXuWRtflBbkC49SfcMgm3TMLt
Qeb3Ifcqj6zND3IDwq0/4ZZJuGUSbg8yvw+5V3lkbX6QGxBu/Qm3TMItk3B7kPl9yL3KI2vzg9yAcOtP
uGUSbpmE24PM70PuVR5Zmx/kBoRbf8Itk3DLJNweZH4fcq/yyNr8IDcg3PoTbpmEWybh9iDz+5B7lUfW
5ge5AeHWn3DLJNwyCbcHmd+H3Ks8sjY/yA0It/6EWybhlkm4Pcj8PuRe5ZG1+UFuQLj1J9wyCbdMwu1B
5vch9yqPrM0PcgPCrT/hlkm4ZRJuDzK/D7lXeWRtfpAbEG79CbdMwi2TcHuQ+X3Ivcoja/OD3IBw60+4
ZRJumYTbg8zvQ+5VHlmbH+QGhFt/wi2TcMsk3B5kfh9yr/LI2vwgNyDc+hNumYRbJuH2IPP7kHuVR9bm
B7kB4dafcMsk3DIJtweZ34fcqzyyNj/IDQi3/oRbJuGWSbg9yPw+5F7lkbX5QW5AuPUn3DIJt0zC7UHm
9yH3Ko+szQ9yA8KtP+GWSbhlEm4PMr8PuVd5ZG1+kBsQbv0Jt0zCLZNwe5D5fci9yiNr84PcgHDrT7hl
Em6ZhNuDzO9D7lUeWZsf5AaEW3/CLZNwyyTcHmR+H3Kv8sja/CA3INz6E26ZhFsm4fYg8/uQe5VH1uYH
uQHh1p9wyyTcMgm3B5nfh9yrPLI2P8gNCLf+hFsm4ZZJuD3I/D7kXuWRtflBbkC49SfcMgm3TMLtQeb3
Ifcqj6zND3IDwq0/4ZZJuGUSbg8yvw+5V3lkbX6QGxBu/Qm3TMItk3B7kPl9yL3KI2vzg9yAcOtPuGUS
bpmE24PM70PuVR5Zmx/kBoRbf8Itk3DLJNweZH4fcq/yyNr8IDcg3PoTbpmEWybh9iDz+5B7lUfW5ge5
AeHWn3DLJNwyCbcHmd+H3Ks8sjY/yA0It/6EWybhlkm4Pcj8PuRe5ZG1+UFuQLj1J9wyCbdMwu1B5vch
9yqPrM0PcgPCrT/hlkm4ZRJuDzK/D7lXeWRtfpAbEG79CbdMwi2TcHuQ+X3Ivcoja/OD3IBw60+4ZRJu
mYTbg8zvQ+5VHlmbH+QGhFt/wi2TcMsk3B5kfh9yr/LI2vwgNyDc+hNumYRbJuH2IPP7kHuVR9bmB7kB
4dafcMsk3DIJtweZ34fcqzyyNj/IDQi3/oRbJuGWSbg9yPw+5F7lkbX5QW5AuPUn3DIJt0zC7UHm9yH3
Ko+szQ9yA8KtP+GWSbhlEm4PMr8PuVd5ZG1+kBsQbv0Jt0zCLZNwe5D5fci9yiNr84PcgHDrT7hlEm6Z
hNuDzO9D7lUeWZsf5AbSw+3Tw//mXf3CcPf+61D9s/Bd/2m4ex8Yqn8Wvut/DB0m3HiW8sja/CA3kB5u
ZmYnTrjxLOWRtflBbkC4mZnlTbjxLOWRtflBbkC4mZnlTbjxLOWRtflBbkC4mZnlTbjxLOWRtflBbkC4
mZnlTbjxLOWRtflBbkC4mZnlTbjxLOWRtflBbkC4mZnlTbjxLOWRtflBbkC4mZnlTbjxLOWRtflBbkC4
mZnlTbjxLOWRtflBbkC4mZnlTbjxLOWRtflBbkC4mZnlTbjxLOWRtflBbkC4mZnlTbjxLOWRtflBbkC4
mZnlTbjxLOWRtflBbkC4mZnlTbjxLOWRtflBbkC4mZnlTbjxLOWRtflBbkC4mZnlTbjxLOWRtflBbkC4
mZnlTbjxLOWRtflBbkC4mZnlTbjxLOWRtflBbkC4mZnlTbjxLOWRtflBbkC4mZnlTbjxLOWRtflBbkC4
mZnlTbjxLOWRtflBbkC4mZnlTbjxLOWRtflBbkC4mZnlTbjxLOWRtflBbkC4mZnlTbjxLOWRtflBbkC4
mZnlTbjxLOWRtflBbkC4mZnlTbjxLOWRtflBbkC4mZnlTbjxLOWRtflBbkC4mZnlTbjxLOWRtflBbkC4
mZnlTbjxLOWRtflBbkC4mZnlTbjxLOWRtflBbkC4mZnlTbjxLOWRtflBbkC4mZnlTbjxLOWRtflBbkC4
mZnlTbjxLOWRtflBbkC4mZnlTbjxLOWRtflBbuCrw+8DEOX6d3P17+xY8/uQe5VH1uYHGQBOML8PuVd5
ZG1+kAHgBPP7kHuVR9bmBxkATjC/D7lXeWRtfpAB4ATz+5B7lUfW5gcZAE4wvw+5V3lkbX6QAeAE8/uQ
e5VH1uYHGQBOML8PuVd5ZG1+kAHgBPP7kHuVR9bmBxkATjC/D7lXeWRtfpAB4ATz+5B7lUfW5gcZAE4w
vw+5V3lkbX6QAeAE8/uQe5VH1uYHGQBOML8PuVd5ZG1+kAHgBPP7kHuVR9bmBxkATjC/D7lXeWRtfpAB
4ATz+5B7lUfW5gcZAE4wvw+5V3lkbX6QAeAE8/uQe5VH1uYHGQBOML8PuVd5ZG1+kAHgBPP7kHuVR9bm
BxkATjC/D7lXeWRtfpAB4ATz+5B7lUfW5gcZAE4wvw+5V3lkbX6QAeAE8/uQe5VH1uYHGQBOML8PuVd5
ZG1+kAHgBPP7kHuVR9bmBxkATjC/D7lXeWRtfpAB4ATz+5B7lUfW5gcZAE4wvw+5V3lkbX6QAeAE8/uQ
e5VH1uYHGQBOML8PuVd5ZG1+kAHgBPP7kHuVR9bmBxkATjC/D7lXeWRtfpAB4ATz+5B7lUfW5gcZAE4w
vw+5V3lkbX6QAeAE8/uQe5VH1uYHGQBOML8PuVd5ZG1+kAHgBPP7kHuVR9bmBxkATjC/D7lXeWRtfpAB
4ATz+5B7lUfW5gcZAE4wvw+5V3lkbX6QAeAE8/uQe5VH1uYHGQBOML8PuVd5ZG1+kAHgBPP7kHuVR9bG
/iMAnKZ6J3Kf8ggAQJ7yCABAnvIIAECe8ggAQJ7yCABAnvIIAECe8ggAQJ7yCABAnvIIAECe8ggAQJ7y
CABAnvIIAECe8ggAQJ7yCABAnvIIAECe8ggAQJ7yCABAnvIIAECe8ggAQJ7yCABAnvIIAECe8ggAQJ7y
CABAnvIIAECe8ggAQJ7yCABAnvIIAECe8ggAQJ7yCABAnvIIAECe8ggAQJ7yCABAnvIIAECe8ggAQJ7y
CABAnvIIAECe8ggAQJ7yCABAnvIIAECe8ggAQJ7yCABAnvIIAECe8ggAQJ7yCABAnvIIAECe8ggAQJ7y
CABAnvIIAECe8ggAQJ7yCABAnvIIAECe8ggAQJ7yCABAnvIIAECe8ggAQJ7yCABAnvIIAECe8ggAQJ7y
CABAnvIIAECe8ggAQJ7yCABAnvIIAECe8ggAQJ7yCABAnvIIAECe8ggAQJ7yCABAnvIIAECe8ggAQJ7y
CABAnvIIAECe8ggAQJ7yCABAnvIIAECe8ggAQJ7yCABAnvIIAECe8ggAQJ7yCABAnvIIAECe8ggAQJ7y
CABAnvIIAECe8ggAQJ7yCABAnvIIAECe8ggAQJ7yCABAnvIIAECe8ggAQJ7yCABAnvIIAECe8ggAQJ7y
CABAnvIIAECe8ggAQJ7yCABAmm//wP8HTmEikkRXgigAAAAASUVORK5CYII=
</value>
</data>
<data name="completedNumberLbl.Image" type="System.Drawing.Bitmap, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>
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
gg==
</value>
</data>
<data name="errorNumberLbl.Image" type="System.Drawing.Bitmap, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>
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
w1EmAAAAAElFTkSuQmCC
</value>
</data>
</root> </root>

View File

@ -36,7 +36,7 @@
this.panel1.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) this.panel1.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.panel1.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle; this.panel1.BackColor = System.Drawing.SystemColors.ControlDark;
this.panel1.Location = new System.Drawing.Point(0, 0); this.panel1.Location = new System.Drawing.Point(0, 0);
this.panel1.Name = "panel1"; this.panel1.Name = "panel1";
this.panel1.Size = new System.Drawing.Size(377, 505); this.panel1.Size = new System.Drawing.Size(377, 505);

View File

@ -117,7 +117,7 @@ namespace LibationWinForms.ProcessQueue
vScrollBar1.SmallChange = VirtualControlHeight; vScrollBar1.SmallChange = VirtualControlHeight;
vScrollBar1.LargeChange = 3 * VirtualControlHeight; vScrollBar1.LargeChange = 3 * VirtualControlHeight;
panel1.Height += 2*VirtualControlHeight; panel1.Height += 2 * VirtualControlHeight;
} }
private ProcessBookControl InitControl(int locationY) private ProcessBookControl InitControl(int locationY)