mirror of
https://github.com/rmcrackan/Libation.git
synced 2025-12-24 06:28:02 -05:00
Compare commits
115 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
17feca28b9 | ||
|
|
898d38cb6a | ||
|
|
95a99a2f0b | ||
|
|
29a1e8ad34 | ||
|
|
19f3a4f266 | ||
|
|
12ddbc308a | ||
|
|
999bc7604e | ||
|
|
3648de3a8d | ||
|
|
051fa0a28f | ||
|
|
72e667e825 | ||
|
|
5ed59b41b5 | ||
|
|
c7c0d1632e | ||
|
|
2dc73acd20 | ||
|
|
ed71668c48 | ||
|
|
801e154d15 | ||
|
|
a89b07394f | ||
|
|
982f9b7c58 | ||
|
|
789b9207b5 | ||
|
|
133dbb7471 | ||
|
|
5d3ec493cd | ||
|
|
6d7f234497 | ||
|
|
29a50bb640 | ||
|
|
843fddabde | ||
|
|
109ce0dd1f | ||
|
|
42508a82a0 | ||
|
|
d860d39f5f | ||
|
|
15396c611a | ||
|
|
41c4b12ae1 | ||
|
|
e51c30462f | ||
|
|
9b5df99a61 | ||
|
|
3535156ea5 | ||
|
|
577145096d | ||
|
|
89059510fd | ||
|
|
aabc14c639 | ||
|
|
c28872544c | ||
|
|
7b8a4e4d72 | ||
|
|
5dcdf670be | ||
|
|
9721890a3c | ||
|
|
1b9c4cfc23 | ||
|
|
98a552e9af | ||
|
|
e1e265a101 | ||
|
|
b60a854de0 | ||
|
|
d1bddeccc8 | ||
|
|
0a106e64d8 | ||
|
|
91d6181aec | ||
|
|
255c0a3359 | ||
|
|
3a5ef999f0 | ||
|
|
983aa845d6 | ||
|
|
d1779726e6 | ||
|
|
8e23062d0e | ||
|
|
7efbfffd99 | ||
|
|
ff4b2d2ecc | ||
|
|
e079be0ad7 | ||
|
|
a8a54aa443 | ||
|
|
88cbcf6baf | ||
|
|
8d6d26c9d2 | ||
|
|
a490df0f7e | ||
|
|
a46041c958 | ||
|
|
0a6a78bc58 | ||
|
|
c9e850515e | ||
|
|
0ff8da2cf0 | ||
|
|
c0ef3ccbea | ||
|
|
1ab628dee8 | ||
|
|
b24df24b10 | ||
|
|
341678d979 | ||
|
|
49d10273a6 | ||
|
|
5b05c018d5 | ||
|
|
d18d8c0ba4 | ||
|
|
84a8fb0074 | ||
|
|
a40fb7f4bd | ||
|
|
84eb3a3508 | ||
|
|
73a5d76503 | ||
|
|
50c35ed519 | ||
|
|
a7b7e3efea | ||
|
|
88e892196f | ||
|
|
7f08da96bb | ||
|
|
193f24768e | ||
|
|
a8bca3de98 | ||
|
|
9692a802d0 | ||
|
|
28a8b2e685 | ||
|
|
3c9121b4af | ||
|
|
dec1035258 | ||
|
|
9d81c86c1b | ||
|
|
eeb4f4681a | ||
|
|
676af0210b | ||
|
|
77c6a2890b | ||
|
|
c39e748749 | ||
|
|
36e5a6ac8d | ||
|
|
9bdcaa5eaa | ||
|
|
5511004db8 | ||
|
|
0e46cdb514 | ||
|
|
b028899949 | ||
|
|
55285427f1 | ||
|
|
763a6cb31a | ||
|
|
24cb1aa84f | ||
|
|
886aa4938d | ||
|
|
8871651549 | ||
|
|
2ae8ef87d9 | ||
|
|
de4fbe05f7 | ||
|
|
b8abed37c2 | ||
|
|
255e26435c | ||
|
|
9e0550619b | ||
|
|
5c171fd0f0 | ||
|
|
3dd3b710b7 | ||
|
|
3c0485cfa9 | ||
|
|
d5ba405de0 | ||
|
|
71b8bca86d | ||
|
|
c53b9eabd6 | ||
|
|
6d6434b4d4 | ||
|
|
a447e88b86 | ||
|
|
e2d2e00913 | ||
|
|
cbfea37b3a | ||
|
|
d6de647974 | ||
|
|
dad36c73e5 | ||
|
|
936a1d60a0 |
@@ -40,7 +40,7 @@ Libation's advanced searching is built on the powerful Lucene search engine. Sim
|
||||
* Full official guide: https://lucene.apache.org/core/2_9_4/queryparsersyntax.html
|
||||
* Tons of search fields, specific to audiobooks
|
||||
* Synonyms so you don't have to memorize magic words. Eg: author and author**s** will both work
|
||||
* Click [?] button for a full list of search fields and synonyms 
|
||||
* Click [?] button for a full list of search fields and synonyms 
|
||||
* Search by tag like \[this\]
|
||||
* When tags have an underscore you can use part of the tag. This is useful for quick categories. The below examples make this more clear.
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="AAXClean" Version="0.4.6" />
|
||||
<PackageReference Include="AAXClean" Version="0.4.7" />
|
||||
<PackageReference Include="AAXClean.Codecs" Version="0.2.7" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@ namespace AaxDecrypter
|
||||
public event EventHandler<TimeSpan> DecryptTimeRemaining;
|
||||
public event EventHandler<string> FileCreated;
|
||||
|
||||
protected bool IsCanceled { get; set; }
|
||||
public bool IsCanceled { get; set; }
|
||||
|
||||
protected string OutputFileName { get; private set; }
|
||||
protected DownloadOptions DownloadOptions { get; }
|
||||
|
||||
@@ -9,435 +9,435 @@ using System.Threading;
|
||||
|
||||
namespace AaxDecrypter
|
||||
{
|
||||
/// <summary>
|
||||
/// A <see cref="CookieContainer"/> for a single Uri.
|
||||
/// </summary>
|
||||
public class SingleUriCookieContainer : CookieContainer
|
||||
{
|
||||
private Uri baseAddress;
|
||||
public Uri Uri
|
||||
{
|
||||
get => baseAddress;
|
||||
set
|
||||
{
|
||||
baseAddress = new UriBuilder(value.Scheme, value.Host).Uri;
|
||||
}
|
||||
}
|
||||
|
||||
public CookieCollection GetCookies()
|
||||
{
|
||||
return GetCookies(Uri);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A resumable, simultaneous file downloader and reader.
|
||||
/// </summary>
|
||||
public class NetworkFileStream : Stream, IUpdatable
|
||||
{
|
||||
public event EventHandler Updated;
|
||||
|
||||
#region Public Properties
|
||||
|
||||
/// <summary>
|
||||
/// Location to save the downloaded data.
|
||||
/// </summary>
|
||||
[JsonProperty(Required = Required.Always)]
|
||||
public string SaveFilePath { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Http(s) address of the file to download.
|
||||
/// </summary>
|
||||
[JsonProperty(Required = Required.Always)]
|
||||
public Uri Uri { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// All cookies set by caller or by the remote server.
|
||||
/// </summary>
|
||||
[JsonProperty(Required = Required.Always)]
|
||||
public SingleUriCookieContainer CookieContainer { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Http headers to be sent to the server with the request.
|
||||
/// </summary>
|
||||
[JsonProperty(Required = Required.Always)]
|
||||
public WebHeaderCollection RequestHeaders { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// The position in <see cref="SaveFilePath"/> that has been written and flushed to disk.
|
||||
/// </summary>
|
||||
[JsonProperty(Required = Required.Always)]
|
||||
public long WritePosition { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// The total length of the <see cref="Uri"/> file to download.
|
||||
/// </summary>
|
||||
[JsonProperty(Required = Required.Always)]
|
||||
public long ContentLength { get; private set; }
|
||||
|
||||
#endregion
|
||||
|
||||
#region Private Properties
|
||||
private HttpWebRequest HttpRequest { get; set; }
|
||||
private FileStream _writeFile { get; }
|
||||
private FileStream _readFile { get; }
|
||||
private Stream _networkStream { get; set; }
|
||||
private bool hasBegunDownloading { get; set; }
|
||||
public bool IsCancelled { get; private set; }
|
||||
private EventWaitHandle downloadEnded { get; set; }
|
||||
private EventWaitHandle downloadedPiece { get; set; }
|
||||
|
||||
#endregion
|
||||
|
||||
#region Constants
|
||||
|
||||
//Download buffer size
|
||||
private const int DOWNLOAD_BUFF_SZ = 4 * 1024;
|
||||
|
||||
//NetworkFileStream will flush all data in _writeFile to disk after every
|
||||
//DATA_FLUSH_SZ bytes are written to the file stream.
|
||||
private const int DATA_FLUSH_SZ = 1024 * 1024;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Constructor
|
||||
|
||||
/// <summary>
|
||||
/// A resumable, simultaneous file downloader and reader.
|
||||
/// </summary>
|
||||
/// <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="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="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)
|
||||
{
|
||||
ArgumentValidator.EnsureNotNullOrWhiteSpace(saveFilePath, nameof(saveFilePath));
|
||||
ArgumentValidator.EnsureNotNullOrWhiteSpace(uri?.AbsoluteUri, nameof(uri));
|
||||
ArgumentValidator.EnsureGreaterThan(writePosition, nameof(writePosition), -1);
|
||||
|
||||
if (!Directory.Exists(Path.GetDirectoryName(saveFilePath)))
|
||||
throw new ArgumentException($"Specified {nameof(saveFilePath)} directory \"{Path.GetDirectoryName(saveFilePath)}\" does not exist.");
|
||||
|
||||
SaveFilePath = saveFilePath;
|
||||
Uri = uri;
|
||||
WritePosition = writePosition;
|
||||
RequestHeaders = requestHeaders ?? new WebHeaderCollection();
|
||||
CookieContainer = cookies ?? new SingleUriCookieContainer { Uri = uri };
|
||||
|
||||
_writeFile = new FileStream(SaveFilePath, FileMode.OpenOrCreate, FileAccess.Write, FileShare.ReadWrite)
|
||||
{
|
||||
Position = WritePosition
|
||||
};
|
||||
|
||||
_readFile = new FileStream(SaveFilePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
|
||||
|
||||
SetUriForSameFile(uri);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Downloader
|
||||
|
||||
/// <summary>
|
||||
/// Update the <see cref="JsonFilePersister"/>.
|
||||
/// </summary>
|
||||
private void Update()
|
||||
{
|
||||
RequestHeaders = HttpRequest.Headers;
|
||||
Updated?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set a different <see cref="System.Uri"/> to the same file targeted by this instance of <see cref="NetworkFileStream"/>
|
||||
/// </summary>
|
||||
/// <param name="uriToSameFile">New <see cref="System.Uri"/> host must match existing host.</param>
|
||||
public void SetUriForSameFile(Uri uriToSameFile)
|
||||
{
|
||||
ArgumentValidator.EnsureNotNullOrWhiteSpace(uriToSameFile?.AbsoluteUri, nameof(uriToSameFile));
|
||||
|
||||
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}");
|
||||
if (hasBegunDownloading)
|
||||
throw new InvalidOperationException("Cannot change Uri after download has started.");
|
||||
|
||||
Uri = uriToSameFile;
|
||||
HttpRequest = WebRequest.CreateHttp(Uri);
|
||||
|
||||
HttpRequest.CookieContainer = CookieContainer;
|
||||
HttpRequest.Headers = RequestHeaders;
|
||||
//If NetworkFileStream is resuming, Header will already contain a range.
|
||||
HttpRequest.Headers.Remove("Range");
|
||||
HttpRequest.AddRange(WritePosition);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Begins downloading <see cref="Uri"/> to <see cref="SaveFilePath"/> in a background thread.
|
||||
/// </summary>
|
||||
private void BeginDownloading()
|
||||
{
|
||||
downloadEnded = new EventWaitHandle(false, EventResetMode.ManualReset);
|
||||
|
||||
if (ContentLength != 0 && WritePosition == ContentLength)
|
||||
{
|
||||
hasBegunDownloading = true;
|
||||
downloadEnded.Set();
|
||||
return;
|
||||
}
|
||||
|
||||
if (ContentLength != 0 && WritePosition > ContentLength)
|
||||
throw new WebException($"Specified write position (0x{WritePosition:X10}) is larger than {nameof(ContentLength)} (0x{ContentLength:X10}).");
|
||||
|
||||
var response = HttpRequest.GetResponse() as HttpWebResponse;
|
||||
|
||||
if (response.StatusCode != HttpStatusCode.PartialContent)
|
||||
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
|
||||
//to the complete file length if requesting Range: bytes=0-
|
||||
if (WritePosition == 0)
|
||||
ContentLength = response.ContentLength;
|
||||
|
||||
_networkStream = response.GetResponseStream();
|
||||
downloadedPiece = new EventWaitHandle(false, EventResetMode.AutoReset);
|
||||
|
||||
//Download the file in the background.
|
||||
new Thread(() => DownloadFile())
|
||||
{ IsBackground = true }
|
||||
.Start();
|
||||
|
||||
hasBegunDownloading = true;
|
||||
return;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Downlod <see cref="Uri"/> to <see cref="SaveFilePath"/>.
|
||||
/// </summary>
|
||||
private void DownloadFile()
|
||||
{
|
||||
var downloadPosition = WritePosition;
|
||||
var nextFlush = downloadPosition + DATA_FLUSH_SZ;
|
||||
|
||||
var buff = new byte[DOWNLOAD_BUFF_SZ];
|
||||
do
|
||||
{
|
||||
var bytesRead = _networkStream.Read(buff, 0, DOWNLOAD_BUFF_SZ);
|
||||
_writeFile.Write(buff, 0, bytesRead);
|
||||
|
||||
downloadPosition += bytesRead;
|
||||
|
||||
if (downloadPosition > nextFlush)
|
||||
{
|
||||
_writeFile.Flush();
|
||||
WritePosition = downloadPosition;
|
||||
Update();
|
||||
nextFlush = downloadPosition + DATA_FLUSH_SZ;
|
||||
downloadedPiece.Set();
|
||||
}
|
||||
|
||||
} while (downloadPosition < ContentLength && !IsCancelled);
|
||||
|
||||
_writeFile.Close();
|
||||
_networkStream.Close();
|
||||
WritePosition = downloadPosition;
|
||||
Update();
|
||||
|
||||
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 (WritePosition > ContentLength)
|
||||
throw new WebException($"Downloaded size (0x{WritePosition:X10}) is greater than {nameof(ContentLength)} (0x{ContentLength:X10}).");
|
||||
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Json Connverters
|
||||
|
||||
public static JsonSerializerSettings GetJsonSerializerSettings()
|
||||
{
|
||||
var settings = new JsonSerializerSettings();
|
||||
settings.Converters.Add(new CookieContainerConverter());
|
||||
settings.Converters.Add(new WebHeaderCollectionConverter());
|
||||
return settings;
|
||||
}
|
||||
|
||||
internal class CookieContainerConverter : JsonConverter
|
||||
{
|
||||
public override bool CanConvert(Type objectType)
|
||||
=> objectType == typeof(SingleUriCookieContainer);
|
||||
|
||||
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
|
||||
{
|
||||
var jObj = JObject.Load(reader);
|
||||
|
||||
var result = new SingleUriCookieContainer()
|
||||
{
|
||||
Uri = new Uri(jObj["Uri"].Value<string>()),
|
||||
Capacity = jObj["Capacity"].Value<int>(),
|
||||
MaxCookieSize = jObj["MaxCookieSize"].Value<int>(),
|
||||
PerDomainCapacity = jObj["PerDomainCapacity"].Value<int>()
|
||||
};
|
||||
|
||||
var cookieList = jObj["Cookies"].ToList();
|
||||
|
||||
foreach (var cookie in cookieList)
|
||||
{
|
||||
result.Add(
|
||||
new Cookie
|
||||
{
|
||||
Comment = cookie["Comment"].Value<string>(),
|
||||
HttpOnly = cookie["HttpOnly"].Value<bool>(),
|
||||
Discard = cookie["Discard"].Value<bool>(),
|
||||
Domain = cookie["Domain"].Value<string>(),
|
||||
Expired = cookie["Expired"].Value<bool>(),
|
||||
Expires = cookie["Expires"].Value<DateTime>(),
|
||||
Name = cookie["Name"].Value<string>(),
|
||||
Path = cookie["Path"].Value<string>(),
|
||||
Port = cookie["Port"].Value<string>(),
|
||||
Secure = cookie["Secure"].Value<bool>(),
|
||||
Value = cookie["Value"].Value<string>(),
|
||||
Version = cookie["Version"].Value<int>(),
|
||||
});
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public override bool CanWrite => true;
|
||||
|
||||
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
|
||||
{
|
||||
var cookies = value as SingleUriCookieContainer;
|
||||
var obj = (JObject)JToken.FromObject(value);
|
||||
var container = cookies.GetCookies();
|
||||
var propertyNames = container.Select(c => JToken.FromObject(c));
|
||||
obj.AddFirst(new JProperty("Cookies", new JArray(propertyNames)));
|
||||
obj.WriteTo(writer);
|
||||
}
|
||||
}
|
||||
|
||||
internal class WebHeaderCollectionConverter : JsonConverter
|
||||
{
|
||||
public override bool CanConvert(Type objectType)
|
||||
=> objectType == typeof(WebHeaderCollection);
|
||||
|
||||
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
|
||||
{
|
||||
var jObj = JObject.Load(reader);
|
||||
var result = new WebHeaderCollection();
|
||||
|
||||
foreach (var kvp in jObj)
|
||||
result.Add(kvp.Key, kvp.Value.Value<string>());
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public override bool CanWrite => true;
|
||||
|
||||
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
|
||||
{
|
||||
var jObj = new JObject();
|
||||
var type = value.GetType();
|
||||
var headers = value as WebHeaderCollection;
|
||||
var jHeaders = headers.AllKeys.Select(k => new JProperty(k, headers[k]));
|
||||
jObj.Add(jHeaders);
|
||||
jObj.WriteTo(writer);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Download Stream Reader
|
||||
|
||||
[JsonIgnore]
|
||||
public override bool CanRead => true;
|
||||
|
||||
[JsonIgnore]
|
||||
public override bool CanSeek => true;
|
||||
|
||||
[JsonIgnore]
|
||||
public override bool CanWrite => false;
|
||||
|
||||
[JsonIgnore]
|
||||
public override long Length
|
||||
{
|
||||
get
|
||||
{
|
||||
if (!hasBegunDownloading)
|
||||
BeginDownloading();
|
||||
return ContentLength;
|
||||
}
|
||||
}
|
||||
|
||||
[JsonIgnore]
|
||||
public override long Position { get => _readFile.Position; set => Seek(value, SeekOrigin.Begin); }
|
||||
|
||||
[JsonIgnore]
|
||||
public override bool CanTimeout => false;
|
||||
|
||||
[JsonIgnore]
|
||||
public override int ReadTimeout { get => base.ReadTimeout; set => base.ReadTimeout = 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 Write(byte[] buffer, int offset, int count) => throw new NotImplementedException();
|
||||
|
||||
public override int Read(byte[] buffer, int offset, int count)
|
||||
{
|
||||
if (!hasBegunDownloading)
|
||||
BeginDownloading();
|
||||
|
||||
var toRead = Math.Min(count, Length - Position);
|
||||
WaitToPosition(Position + toRead);
|
||||
return _readFile.Read(buffer, offset, count);
|
||||
}
|
||||
|
||||
public override long Seek(long offset, SeekOrigin origin)
|
||||
{
|
||||
var newPosition = origin switch
|
||||
/// <summary>
|
||||
/// A <see cref="CookieContainer"/> for a single Uri.
|
||||
/// </summary>
|
||||
public class SingleUriCookieContainer : CookieContainer
|
||||
{
|
||||
private Uri baseAddress;
|
||||
public Uri Uri
|
||||
{
|
||||
get => baseAddress;
|
||||
set
|
||||
{
|
||||
baseAddress = new UriBuilder(value.Scheme, value.Host).Uri;
|
||||
}
|
||||
}
|
||||
|
||||
public CookieCollection GetCookies()
|
||||
{
|
||||
return GetCookies(Uri);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A resumable, simultaneous file downloader and reader.
|
||||
/// </summary>
|
||||
public class NetworkFileStream : Stream, IUpdatable
|
||||
{
|
||||
public event EventHandler Updated;
|
||||
|
||||
#region Public Properties
|
||||
|
||||
/// <summary>
|
||||
/// Location to save the downloaded data.
|
||||
/// </summary>
|
||||
[JsonProperty(Required = Required.Always)]
|
||||
public string SaveFilePath { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Http(s) address of the file to download.
|
||||
/// </summary>
|
||||
[JsonProperty(Required = Required.Always)]
|
||||
public Uri Uri { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// All cookies set by caller or by the remote server.
|
||||
/// </summary>
|
||||
[JsonProperty(Required = Required.Always)]
|
||||
public SingleUriCookieContainer CookieContainer { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Http headers to be sent to the server with the request.
|
||||
/// </summary>
|
||||
[JsonProperty(Required = Required.Always)]
|
||||
public WebHeaderCollection RequestHeaders { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// The position in <see cref="SaveFilePath"/> that has been written and flushed to disk.
|
||||
/// </summary>
|
||||
[JsonProperty(Required = Required.Always)]
|
||||
public long WritePosition { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// The total length of the <see cref="Uri"/> file to download.
|
||||
/// </summary>
|
||||
[JsonProperty(Required = Required.Always)]
|
||||
public long ContentLength { get; private set; }
|
||||
|
||||
#endregion
|
||||
|
||||
#region Private Properties
|
||||
private HttpWebRequest HttpRequest { get; set; }
|
||||
private FileStream _writeFile { get; }
|
||||
private FileStream _readFile { get; }
|
||||
private Stream _networkStream { get; set; }
|
||||
private bool hasBegunDownloading { get; set; }
|
||||
public bool IsCancelled { get; private set; }
|
||||
private EventWaitHandle downloadEnded { get; set; }
|
||||
private EventWaitHandle downloadedPiece { get; set; }
|
||||
|
||||
#endregion
|
||||
|
||||
#region Constants
|
||||
|
||||
//Download buffer size
|
||||
private const int DOWNLOAD_BUFF_SZ = 32 * 1024;
|
||||
|
||||
//NetworkFileStream will flush all data in _writeFile to disk after every
|
||||
//DATA_FLUSH_SZ bytes are written to the file stream.
|
||||
private const int DATA_FLUSH_SZ = 1024 * 1024;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Constructor
|
||||
|
||||
/// <summary>
|
||||
/// A resumable, simultaneous file downloader and reader.
|
||||
/// </summary>
|
||||
/// <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="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="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)
|
||||
{
|
||||
ArgumentValidator.EnsureNotNullOrWhiteSpace(saveFilePath, nameof(saveFilePath));
|
||||
ArgumentValidator.EnsureNotNullOrWhiteSpace(uri?.AbsoluteUri, nameof(uri));
|
||||
ArgumentValidator.EnsureGreaterThan(writePosition, nameof(writePosition), -1);
|
||||
|
||||
if (!Directory.Exists(Path.GetDirectoryName(saveFilePath)))
|
||||
throw new ArgumentException($"Specified {nameof(saveFilePath)} directory \"{Path.GetDirectoryName(saveFilePath)}\" does not exist.");
|
||||
|
||||
SaveFilePath = saveFilePath;
|
||||
Uri = uri;
|
||||
WritePosition = writePosition;
|
||||
RequestHeaders = requestHeaders ?? new WebHeaderCollection();
|
||||
CookieContainer = cookies ?? new SingleUriCookieContainer { Uri = uri };
|
||||
|
||||
_writeFile = new FileStream(SaveFilePath, FileMode.OpenOrCreate, FileAccess.Write, FileShare.ReadWrite)
|
||||
{
|
||||
Position = WritePosition
|
||||
};
|
||||
|
||||
_readFile = new FileStream(SaveFilePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
|
||||
|
||||
SetUriForSameFile(uri);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Downloader
|
||||
|
||||
/// <summary>
|
||||
/// Update the <see cref="JsonFilePersister"/>.
|
||||
/// </summary>
|
||||
private void Update()
|
||||
{
|
||||
RequestHeaders = HttpRequest.Headers;
|
||||
Updated?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set a different <see cref="System.Uri"/> to the same file targeted by this instance of <see cref="NetworkFileStream"/>
|
||||
/// </summary>
|
||||
/// <param name="uriToSameFile">New <see cref="System.Uri"/> host must match existing host.</param>
|
||||
public void SetUriForSameFile(Uri uriToSameFile)
|
||||
{
|
||||
ArgumentValidator.EnsureNotNullOrWhiteSpace(uriToSameFile?.AbsoluteUri, nameof(uriToSameFile));
|
||||
|
||||
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}");
|
||||
if (hasBegunDownloading)
|
||||
throw new InvalidOperationException("Cannot change Uri after download has started.");
|
||||
|
||||
Uri = uriToSameFile;
|
||||
HttpRequest = WebRequest.CreateHttp(Uri);
|
||||
|
||||
HttpRequest.CookieContainer = CookieContainer;
|
||||
HttpRequest.Headers = RequestHeaders;
|
||||
//If NetworkFileStream is resuming, Header will already contain a range.
|
||||
HttpRequest.Headers.Remove("Range");
|
||||
HttpRequest.AddRange(WritePosition);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Begins downloading <see cref="Uri"/> to <see cref="SaveFilePath"/> in a background thread.
|
||||
/// </summary>
|
||||
private void BeginDownloading()
|
||||
{
|
||||
downloadEnded = new EventWaitHandle(false, EventResetMode.ManualReset);
|
||||
|
||||
if (ContentLength != 0 && WritePosition == ContentLength)
|
||||
{
|
||||
hasBegunDownloading = true;
|
||||
downloadEnded.Set();
|
||||
return;
|
||||
}
|
||||
|
||||
if (ContentLength != 0 && WritePosition > ContentLength)
|
||||
throw new WebException($"Specified write position (0x{WritePosition:X10}) is larger than {nameof(ContentLength)} (0x{ContentLength:X10}).");
|
||||
|
||||
var response = HttpRequest.GetResponse() as HttpWebResponse;
|
||||
|
||||
if (response.StatusCode != HttpStatusCode.PartialContent)
|
||||
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
|
||||
//to the complete file length if requesting Range: bytes=0-
|
||||
if (WritePosition == 0)
|
||||
ContentLength = response.ContentLength;
|
||||
|
||||
_networkStream = response.GetResponseStream();
|
||||
downloadedPiece = new EventWaitHandle(false, EventResetMode.AutoReset);
|
||||
|
||||
//Download the file in the background.
|
||||
new Thread(() => DownloadFile())
|
||||
{ IsBackground = true }
|
||||
.Start();
|
||||
|
||||
hasBegunDownloading = true;
|
||||
return;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Downlod <see cref="Uri"/> to <see cref="SaveFilePath"/>.
|
||||
/// </summary>
|
||||
private void DownloadFile()
|
||||
{
|
||||
var downloadPosition = WritePosition;
|
||||
var nextFlush = downloadPosition + DATA_FLUSH_SZ;
|
||||
|
||||
var buff = new byte[DOWNLOAD_BUFF_SZ];
|
||||
do
|
||||
{
|
||||
var bytesRead = _networkStream.Read(buff, 0, DOWNLOAD_BUFF_SZ);
|
||||
_writeFile.Write(buff, 0, bytesRead);
|
||||
|
||||
downloadPosition += bytesRead;
|
||||
|
||||
if (downloadPosition > nextFlush)
|
||||
{
|
||||
_writeFile.Flush();
|
||||
WritePosition = downloadPosition;
|
||||
Update();
|
||||
nextFlush = downloadPosition + DATA_FLUSH_SZ;
|
||||
downloadedPiece.Set();
|
||||
}
|
||||
|
||||
} while (downloadPosition < ContentLength && !IsCancelled);
|
||||
|
||||
_writeFile.Close();
|
||||
_networkStream.Close();
|
||||
WritePosition = downloadPosition;
|
||||
Update();
|
||||
|
||||
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 (WritePosition > ContentLength)
|
||||
throw new WebException($"Downloaded size (0x{WritePosition:X10}) is greater than {nameof(ContentLength)} (0x{ContentLength:X10}).");
|
||||
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Json Connverters
|
||||
|
||||
public static JsonSerializerSettings GetJsonSerializerSettings()
|
||||
{
|
||||
var settings = new JsonSerializerSettings();
|
||||
settings.Converters.Add(new CookieContainerConverter());
|
||||
settings.Converters.Add(new WebHeaderCollectionConverter());
|
||||
return settings;
|
||||
}
|
||||
|
||||
internal class CookieContainerConverter : JsonConverter
|
||||
{
|
||||
public override bool CanConvert(Type objectType)
|
||||
=> objectType == typeof(SingleUriCookieContainer);
|
||||
|
||||
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
|
||||
{
|
||||
var jObj = JObject.Load(reader);
|
||||
|
||||
var result = new SingleUriCookieContainer()
|
||||
{
|
||||
Uri = new Uri(jObj["Uri"].Value<string>()),
|
||||
Capacity = jObj["Capacity"].Value<int>(),
|
||||
MaxCookieSize = jObj["MaxCookieSize"].Value<int>(),
|
||||
PerDomainCapacity = jObj["PerDomainCapacity"].Value<int>()
|
||||
};
|
||||
|
||||
var cookieList = jObj["Cookies"].ToList();
|
||||
|
||||
foreach (var cookie in cookieList)
|
||||
{
|
||||
result.Add(
|
||||
new Cookie
|
||||
{
|
||||
Comment = cookie["Comment"].Value<string>(),
|
||||
HttpOnly = cookie["HttpOnly"].Value<bool>(),
|
||||
Discard = cookie["Discard"].Value<bool>(),
|
||||
Domain = cookie["Domain"].Value<string>(),
|
||||
Expired = cookie["Expired"].Value<bool>(),
|
||||
Expires = cookie["Expires"].Value<DateTime>(),
|
||||
Name = cookie["Name"].Value<string>(),
|
||||
Path = cookie["Path"].Value<string>(),
|
||||
Port = cookie["Port"].Value<string>(),
|
||||
Secure = cookie["Secure"].Value<bool>(),
|
||||
Value = cookie["Value"].Value<string>(),
|
||||
Version = cookie["Version"].Value<int>(),
|
||||
});
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public override bool CanWrite => true;
|
||||
|
||||
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
|
||||
{
|
||||
var cookies = value as SingleUriCookieContainer;
|
||||
var obj = (JObject)JToken.FromObject(value);
|
||||
var container = cookies.GetCookies();
|
||||
var propertyNames = container.Select(c => JToken.FromObject(c));
|
||||
obj.AddFirst(new JProperty("Cookies", new JArray(propertyNames)));
|
||||
obj.WriteTo(writer);
|
||||
}
|
||||
}
|
||||
|
||||
internal class WebHeaderCollectionConverter : JsonConverter
|
||||
{
|
||||
public override bool CanConvert(Type objectType)
|
||||
=> objectType == typeof(WebHeaderCollection);
|
||||
|
||||
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
|
||||
{
|
||||
var jObj = JObject.Load(reader);
|
||||
var result = new WebHeaderCollection();
|
||||
|
||||
foreach (var kvp in jObj)
|
||||
result.Add(kvp.Key, kvp.Value.Value<string>());
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public override bool CanWrite => true;
|
||||
|
||||
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
|
||||
{
|
||||
var jObj = new JObject();
|
||||
var type = value.GetType();
|
||||
var headers = value as WebHeaderCollection;
|
||||
var jHeaders = headers.AllKeys.Select(k => new JProperty(k, headers[k]));
|
||||
jObj.Add(jHeaders);
|
||||
jObj.WriteTo(writer);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Download Stream Reader
|
||||
|
||||
[JsonIgnore]
|
||||
public override bool CanRead => true;
|
||||
|
||||
[JsonIgnore]
|
||||
public override bool CanSeek => true;
|
||||
|
||||
[JsonIgnore]
|
||||
public override bool CanWrite => false;
|
||||
|
||||
[JsonIgnore]
|
||||
public override long Length
|
||||
{
|
||||
get
|
||||
{
|
||||
if (!hasBegunDownloading)
|
||||
BeginDownloading();
|
||||
return ContentLength;
|
||||
}
|
||||
}
|
||||
|
||||
[JsonIgnore]
|
||||
public override long Position { get => _readFile.Position; set => Seek(value, SeekOrigin.Begin); }
|
||||
|
||||
[JsonIgnore]
|
||||
public override bool CanTimeout => false;
|
||||
|
||||
[JsonIgnore]
|
||||
public override int ReadTimeout { get => base.ReadTimeout; set => base.ReadTimeout = 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 Write(byte[] buffer, int offset, int count) => throw new NotImplementedException();
|
||||
|
||||
public override int Read(byte[] buffer, int offset, int count)
|
||||
{
|
||||
if (!hasBegunDownloading)
|
||||
BeginDownloading();
|
||||
|
||||
var toRead = Math.Min(count, Length - Position);
|
||||
WaitToPosition(Position + toRead);
|
||||
return _readFile.Read(buffer, offset, count);
|
||||
}
|
||||
|
||||
public override long Seek(long offset, SeekOrigin origin)
|
||||
{
|
||||
var newPosition = origin switch
|
||||
{
|
||||
SeekOrigin.Current => Position + offset,
|
||||
SeekOrigin.End => ContentLength + offset,
|
||||
_ => offset,
|
||||
};
|
||||
|
||||
WaitToPosition(newPosition);
|
||||
return _readFile.Position = newPosition;
|
||||
}
|
||||
WaitToPosition(newPosition);
|
||||
return _readFile.Position = newPosition;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Blocks until the file has downloaded to at least <paramref name="requiredPosition"/>, then returns.
|
||||
/// </summary>
|
||||
/// <param name="requiredPosition">The minimum required flished data length in <see cref="SaveFilePath"/>.</param>
|
||||
private void WaitToPosition(long requiredPosition)
|
||||
/// <summary>
|
||||
/// Blocks until the file has downloaded to at least <paramref name="requiredPosition"/>, then returns.
|
||||
/// </summary>
|
||||
/// <param name="requiredPosition">The minimum required flished data length in <see cref="SaveFilePath"/>.</param>
|
||||
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()
|
||||
{
|
||||
IsCancelled = true;
|
||||
public override void Close()
|
||||
{
|
||||
IsCancelled = true;
|
||||
|
||||
while (downloadEnded is not null && !downloadEnded.WaitOne(1000)) ;
|
||||
while (downloadEnded is not null && !downloadEnded.WaitOne(1000)) ;
|
||||
|
||||
_readFile.Close();
|
||||
_writeFile.Close();
|
||||
_networkStream?.Close();
|
||||
Update();
|
||||
}
|
||||
_readFile.Close();
|
||||
_writeFile.Close();
|
||||
_networkStream?.Close();
|
||||
Update();
|
||||
}
|
||||
|
||||
#endregion
|
||||
~NetworkFileStream()
|
||||
{
|
||||
downloadEnded?.Close();
|
||||
downloadedPiece?.Close();
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
~NetworkFileStream()
|
||||
{
|
||||
downloadEnded?.Close();
|
||||
downloadedPiece?.Close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net6.0-windows</TargetFramework>
|
||||
<Version>7.5.0.1</Version>
|
||||
<Version>7.6.2.1</Version>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="AudibleApi" Version="2.8.1.1" />
|
||||
<PackageReference Include="AudibleApi" Version="2.8.3.1" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -14,19 +14,27 @@ namespace FileLiberator
|
||||
{
|
||||
public class ConvertToMp3 : AudioDecodable
|
||||
{
|
||||
public override string Name => "Convert to Mp3";
|
||||
private Mp4File m4bBook;
|
||||
|
||||
private long fileSize;
|
||||
private long fileSize;
|
||||
private static string Mp3FileName(string m4bPath) => Path.ChangeExtension(m4bPath ?? "", ".mp3");
|
||||
|
||||
public override void Cancel() => m4bBook?.Cancel();
|
||||
private bool cancelled = false;
|
||||
public override void Cancel()
|
||||
{
|
||||
m4bBook?.Cancel();
|
||||
cancelled = true;
|
||||
}
|
||||
|
||||
public override bool Validate(LibraryBook libraryBook)
|
||||
{
|
||||
public static bool ValidateMp3(LibraryBook libraryBook)
|
||||
{
|
||||
var path = AudibleFileStorage.Audio.GetPath(libraryBook.Book.AudibleProductId);
|
||||
return path?.ToLower()?.EndsWith(".m4b") == true && !File.Exists(Mp3FileName(path));
|
||||
}
|
||||
|
||||
public override bool Validate(LibraryBook libraryBook) => ValidateMp3(libraryBook);
|
||||
|
||||
public override async Task<StatusHandler> ProcessAsync(LibraryBook libraryBook)
|
||||
{
|
||||
OnBegin(libraryBook);
|
||||
@@ -56,12 +64,12 @@ namespace FileLiberator
|
||||
var realMp3Path = FileUtility.SaferMoveToValidPath(mp3File.Name, proposedMp3Path);
|
||||
OnFileCreated(libraryBook, realMp3Path);
|
||||
|
||||
var statusHandler = new StatusHandler();
|
||||
|
||||
if (result == ConversionResult.Failed)
|
||||
statusHandler.AddError("Conversion failed");
|
||||
|
||||
return statusHandler;
|
||||
return new StatusHandler { "Conversion failed" };
|
||||
else if (result == ConversionResult.Cancelled)
|
||||
return new StatusHandler { "Cancelled" };
|
||||
else
|
||||
return new StatusHandler();
|
||||
}
|
||||
finally
|
||||
{
|
||||
|
||||
@@ -15,6 +15,7 @@ namespace FileLiberator
|
||||
{
|
||||
public class DownloadDecryptBook : AudioDecodable
|
||||
{
|
||||
public override string Name => "Download & Decrypt";
|
||||
private AudiobookDownloadBase abDownloader;
|
||||
|
||||
public override bool Validate(LibraryBook libraryBook) => !libraryBook.Book.Audio_Exists();
|
||||
@@ -65,11 +66,14 @@ namespace FileLiberator
|
||||
foreach (var tmpFile in entries.Where(f => f.FileType != FileType.AAXC))
|
||||
FileUtility.SaferDelete(tmpFile.Path);
|
||||
|
||||
return new StatusHandler { "Decrypt failed" };
|
||||
return abDownloader?.IsCanceled == true ?
|
||||
new StatusHandler { "Cancelled" } :
|
||||
new StatusHandler { "Decrypt failed" };
|
||||
}
|
||||
|
||||
// moves new files from temp dir to final dest
|
||||
var movedAudioFile = moveFilesToBooksDir(libraryBook, entries);
|
||||
// moves new files from temp dir to final dest.
|
||||
// This could take a few seconds if moving hundreds of files.
|
||||
var movedAudioFile = await Task.Run(() => moveFilesToBooksDir(libraryBook, entries));
|
||||
|
||||
// decrypt failed
|
||||
if (!movedAudioFile)
|
||||
|
||||
@@ -14,6 +14,7 @@ namespace FileLiberator
|
||||
{
|
||||
public class DownloadPdf : Processable
|
||||
{
|
||||
public override string Name => "Download Pdf";
|
||||
public override bool Validate(LibraryBook libraryBook)
|
||||
=> !string.IsNullOrWhiteSpace(getdownloadUrl(libraryBook))
|
||||
&& !libraryBook.Book.PDF_Exists();
|
||||
|
||||
@@ -11,6 +11,7 @@ namespace FileLiberator
|
||||
{
|
||||
public abstract class Processable : Streamable
|
||||
{
|
||||
public abstract string Name { get; }
|
||||
public event EventHandler<LibraryBook> Begin;
|
||||
|
||||
/// <summary>General string message to display. DON'T rely on this for success, failure, or control logic</summary>
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Dinah.Core" Version="4.2.0.1" />
|
||||
<PackageReference Include="Dinah.Core" Version="4.4.0.1" />
|
||||
<PackageReference Include="Polly" Version="7.2.3" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "_Solution Items", "_Solutio
|
||||
ProjectSection(SolutionItems) = preProject
|
||||
__README - COLLABORATORS.txt = __README - COLLABORATORS.txt
|
||||
__TODO.txt = __TODO.txt
|
||||
_ARCHITECTURE NOTES.txt = _ARCHITECTURE NOTES.txt
|
||||
_DB_NOTES.txt = _DB_NOTES.txt
|
||||
REFERENCE.txt = REFERENCE.txt
|
||||
EndProjectSection
|
||||
|
||||
@@ -29,7 +29,7 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="CommandLineParser" Version="2.8.0" />
|
||||
<PackageReference Include="CommandLineParser" Version="2.9.1" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -28,16 +28,22 @@ namespace LibationFileManager
|
||||
inMemoryState = JsonConvert.DeserializeObject<FilterState>(File.ReadAllText(JsonFile));
|
||||
}
|
||||
|
||||
public static event EventHandler UseDefaultChanged;
|
||||
public static bool UseDefault
|
||||
{
|
||||
get => inMemoryState.UseDefault;
|
||||
set
|
||||
{
|
||||
if (UseDefault == value)
|
||||
return;
|
||||
|
||||
lock (locker)
|
||||
{
|
||||
inMemoryState.UseDefault = value;
|
||||
save(false);
|
||||
}
|
||||
|
||||
UseDefaultChanged?.Invoke(null, null);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -36,6 +36,7 @@ namespace LibationSearchEngine
|
||||
public const string ALL_TITLE = nameof(Book.Title);
|
||||
public const string ALL_AUTHOR_NAMES = "AuthorNames";
|
||||
public const string ALL_NARRATOR_NAMES = "NarratorNames";
|
||||
public const string ALL_SERIES_NAMES = "SeriesNames";
|
||||
|
||||
private static ReadOnlyDictionary<string, Func<LibraryBook, string>> idIndexRules { get; }
|
||||
= new ReadOnlyDictionary<string, Func<LibraryBook, string>>(
|
||||
@@ -64,16 +65,8 @@ namespace LibationSearchEngine
|
||||
["Narrators"] = lb => lb.Book.NarratorNames(),
|
||||
[nameof(Book.Publisher)] = lb => lb.Book.Publisher,
|
||||
|
||||
["SeriesNames"] = lb => string.Join(
|
||||
", ",
|
||||
lb.Book.SeriesLink
|
||||
.Where(s => !string.IsNullOrWhiteSpace(s.Series.Name))
|
||||
.Select(s => s.Series.AudibleSeriesId)),
|
||||
["Series"] = lb => string.Join(
|
||||
", ",
|
||||
lb.Book.SeriesLink
|
||||
.Where(s => !string.IsNullOrWhiteSpace(s.Series.Name))
|
||||
.Select(s => s.Series.AudibleSeriesId)),
|
||||
[ALL_SERIES_NAMES] = lb => lb.Book.SeriesNames(),
|
||||
["Series"] = lb => lb.Book.SeriesNames(),
|
||||
["SeriesId"] = lb => string.Join(", ", lb.Book.SeriesLink.Select(s => s.Series.AudibleSeriesId)),
|
||||
|
||||
["CategoriesNames"] = lb => lb.Book.CategoriesIds() is null ? null : string.Join(", ", lb.Book.CategoriesIds()),
|
||||
@@ -160,7 +153,8 @@ namespace LibationSearchEngine
|
||||
idIndexRules[ALL_AUDIBLE_PRODUCT_ID],
|
||||
stringIndexRules[ALL_TITLE],
|
||||
stringIndexRules[ALL_AUTHOR_NAMES],
|
||||
stringIndexRules[ALL_NARRATOR_NAMES]
|
||||
stringIndexRules[ALL_NARRATOR_NAMES],
|
||||
stringIndexRules[ALL_SERIES_NAMES]
|
||||
};
|
||||
#endregion
|
||||
|
||||
|
||||
@@ -1,34 +0,0 @@
|
||||
using System;
|
||||
using DataLayer;
|
||||
using LibationFileManager;
|
||||
|
||||
namespace LibationWinForms.BookLiberation
|
||||
{
|
||||
class AudioConvertForm : AudioDecodeForm
|
||||
{
|
||||
public AudioConvertForm()
|
||||
{
|
||||
this.Load += (_, _) => this.RestoreSizeAndLocation(Configuration.Instance);
|
||||
this.FormClosing += (_, _) => this.SaveSizeAndLocation(Configuration.Instance);
|
||||
}
|
||||
|
||||
#region AudioDecodeForm overrides
|
||||
public override string DecodeActionName => "Converting";
|
||||
#endregion
|
||||
|
||||
#region Processable event handler overrides
|
||||
public override void Processable_Begin(object sender, LibraryBook libraryBook)
|
||||
{
|
||||
LogMe.Info($"Convert Step, Begin: {libraryBook.Book}");
|
||||
|
||||
base.Processable_Begin(sender, libraryBook);
|
||||
}
|
||||
public override void Processable_Completed(object sender, LibraryBook libraryBook)
|
||||
{
|
||||
base.Processable_Completed(sender, libraryBook);
|
||||
LogMe.Info($"Convert Step, Completed: {libraryBook.Book}{Environment.NewLine}");
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -1,104 +0,0 @@
|
||||
namespace LibationWinForms.BookLiberation
|
||||
{
|
||||
partial class AudioDecodeForm
|
||||
{
|
||||
/// <summary>
|
||||
/// Required designer variable.
|
||||
/// </summary>
|
||||
private System.ComponentModel.IContainer components = null;
|
||||
|
||||
/// <summary>
|
||||
/// Clean up any resources being used.
|
||||
/// </summary>
|
||||
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
if (disposing && (components != null))
|
||||
{
|
||||
components.Dispose();
|
||||
}
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
|
||||
#region Windows Form Designer generated code
|
||||
|
||||
/// <summary>
|
||||
/// Required method for Designer support - do not modify
|
||||
/// the contents of this method with the code editor.
|
||||
/// </summary>
|
||||
private void InitializeComponent()
|
||||
{
|
||||
this.pictureBox1 = new System.Windows.Forms.PictureBox();
|
||||
this.bookInfoLbl = new System.Windows.Forms.Label();
|
||||
this.progressBar1 = new System.Windows.Forms.ProgressBar();
|
||||
this.remainingTimeLbl = new System.Windows.Forms.Label();
|
||||
((System.ComponentModel.ISupportInitialize)(this.pictureBox1)).BeginInit();
|
||||
this.SuspendLayout();
|
||||
//
|
||||
// pictureBox1
|
||||
//
|
||||
this.pictureBox1.Location = new System.Drawing.Point(14, 14);
|
||||
this.pictureBox1.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3);
|
||||
this.pictureBox1.Name = "pictureBox1";
|
||||
this.pictureBox1.Size = new System.Drawing.Size(117, 115);
|
||||
this.pictureBox1.SizeMode = System.Windows.Forms.PictureBoxSizeMode.StretchImage;
|
||||
this.pictureBox1.TabIndex = 0;
|
||||
this.pictureBox1.TabStop = false;
|
||||
//
|
||||
// bookInfoLbl
|
||||
//
|
||||
this.bookInfoLbl.AutoSize = true;
|
||||
this.bookInfoLbl.Location = new System.Drawing.Point(138, 14);
|
||||
this.bookInfoLbl.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0);
|
||||
this.bookInfoLbl.Name = "bookInfoLbl";
|
||||
this.bookInfoLbl.Size = new System.Drawing.Size(121, 15);
|
||||
this.bookInfoLbl.TabIndex = 0;
|
||||
this.bookInfoLbl.Text = "[multi-line book info]";
|
||||
//
|
||||
// progressBar1
|
||||
//
|
||||
this.progressBar1.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left)
|
||||
| System.Windows.Forms.AnchorStyles.Right)));
|
||||
this.progressBar1.Location = new System.Drawing.Point(14, 143);
|
||||
this.progressBar1.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3);
|
||||
this.progressBar1.Name = "progressBar1";
|
||||
this.progressBar1.Size = new System.Drawing.Size(611, 27);
|
||||
this.progressBar1.TabIndex = 2;
|
||||
//
|
||||
// remainingTimeLbl
|
||||
//
|
||||
this.remainingTimeLbl.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right)));
|
||||
this.remainingTimeLbl.Location = new System.Drawing.Point(632, 143);
|
||||
this.remainingTimeLbl.Name = "remainingTimeLbl";
|
||||
this.remainingTimeLbl.Size = new System.Drawing.Size(60, 31);
|
||||
this.remainingTimeLbl.TabIndex = 3;
|
||||
this.remainingTimeLbl.Text = "ETA:\r\n";
|
||||
this.remainingTimeLbl.TextAlign = System.Drawing.ContentAlignment.TopRight;
|
||||
//
|
||||
// DecryptForm
|
||||
//
|
||||
this.AutoScaleDimensions = new System.Drawing.SizeF(7F, 15F);
|
||||
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
|
||||
this.ClientSize = new System.Drawing.Size(707, 183);
|
||||
this.Controls.Add(this.remainingTimeLbl);
|
||||
this.Controls.Add(this.progressBar1);
|
||||
this.Controls.Add(this.bookInfoLbl);
|
||||
this.Controls.Add(this.pictureBox1);
|
||||
this.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3);
|
||||
this.Name = "DecryptForm";
|
||||
this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent;
|
||||
this.Text = "DecryptForm";
|
||||
((System.ComponentModel.ISupportInitialize)(this.pictureBox1)).EndInit();
|
||||
this.ResumeLayout(false);
|
||||
this.PerformLayout();
|
||||
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
private System.Windows.Forms.PictureBox pictureBox1;
|
||||
private System.Windows.Forms.Label bookInfoLbl;
|
||||
private System.Windows.Forms.ProgressBar progressBar1;
|
||||
private System.Windows.Forms.Label remainingTimeLbl;
|
||||
}
|
||||
}
|
||||
@@ -1,114 +0,0 @@
|
||||
using System;
|
||||
using DataLayer;
|
||||
using Dinah.Core.Net.Http;
|
||||
using Dinah.Core.Threading;
|
||||
using LibationFileManager;
|
||||
using LibationWinForms.BookLiberation.BaseForms;
|
||||
|
||||
namespace LibationWinForms.BookLiberation
|
||||
{
|
||||
public partial class AudioDecodeForm : LiberationBaseForm
|
||||
{
|
||||
public virtual string DecodeActionName { get; } = "Decoding";
|
||||
public AudioDecodeForm() => InitializeComponent();
|
||||
|
||||
private Func<byte[]> GetCoverArtDelegate;
|
||||
|
||||
#region Processable event handler overrides
|
||||
public override void Processable_Begin(object sender, LibraryBook libraryBook)
|
||||
{
|
||||
base.Processable_Begin(sender, libraryBook);
|
||||
|
||||
GetCoverArtDelegate = () => PictureStorage.GetPictureSynchronously(
|
||||
new PictureDefinition(
|
||||
libraryBook.Book.PictureId,
|
||||
PictureSize._500x500));
|
||||
|
||||
//Set default values from library
|
||||
AudioDecodable_TitleDiscovered(sender, libraryBook.Book.Title);
|
||||
AudioDecodable_AuthorsDiscovered(sender, libraryBook.Book.AuthorNames());
|
||||
AudioDecodable_NarratorsDiscovered(sender, libraryBook.Book.NarratorNames());
|
||||
AudioDecodable_CoverImageDiscovered(sender,
|
||||
PictureStorage.GetPicture(
|
||||
new PictureDefinition(
|
||||
libraryBook.Book.PictureId,
|
||||
PictureSize._80x80)).bytes);
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Streamable event handler overrides
|
||||
public override void Streamable_StreamingProgressChanged(object sender, DownloadProgress downloadProgress)
|
||||
{
|
||||
base.Streamable_StreamingProgressChanged(sender, downloadProgress);
|
||||
if (!downloadProgress.ProgressPercentage.HasValue)
|
||||
return;
|
||||
|
||||
if (downloadProgress.ProgressPercentage == 0)
|
||||
updateRemainingTime(0);
|
||||
else
|
||||
progressBar1.UIThreadAsync(() => progressBar1.Value = (int)downloadProgress.ProgressPercentage);
|
||||
}
|
||||
|
||||
public override void Streamable_StreamingTimeRemaining(object sender, TimeSpan timeRemaining)
|
||||
{
|
||||
base.Streamable_StreamingTimeRemaining(sender, timeRemaining);
|
||||
updateRemainingTime((int)timeRemaining.TotalSeconds);
|
||||
}
|
||||
|
||||
private void updateRemainingTime(int remaining)
|
||||
=> remainingTimeLbl.UIThreadAsync(() => remainingTimeLbl.Text = $"ETA:\r\n{formatTime(remaining)}");
|
||||
|
||||
private string formatTime(int seconds)
|
||||
{
|
||||
var timeSpan = new TimeSpan(0, 0, seconds);
|
||||
return
|
||||
timeSpan.TotalHours >= 1 ? $"{timeSpan:%h}h {timeSpan:mm}m {timeSpan:ss}s"
|
||||
: timeSpan.TotalMinutes >= 1 ? $"{timeSpan:%m}m {timeSpan:ss}s"
|
||||
: $"{seconds} sec";
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region AudioDecodable event handlers
|
||||
private string title;
|
||||
private string authorNames;
|
||||
private string narratorNames;
|
||||
|
||||
public override void AudioDecodable_TitleDiscovered(object sender, string title)
|
||||
{
|
||||
base.AudioDecodable_TitleDiscovered(sender, title);
|
||||
this.UIThreadAsync(() => this.Text = DecodeActionName + " " + title);
|
||||
this.title = title;
|
||||
updateBookInfo();
|
||||
}
|
||||
|
||||
public override void AudioDecodable_AuthorsDiscovered(object sender, string authors)
|
||||
{
|
||||
base.AudioDecodable_AuthorsDiscovered(sender, authors);
|
||||
authorNames = authors;
|
||||
updateBookInfo();
|
||||
}
|
||||
|
||||
public override void AudioDecodable_NarratorsDiscovered(object sender, string narrators)
|
||||
{
|
||||
base.AudioDecodable_NarratorsDiscovered(sender, narrators);
|
||||
narratorNames = narrators;
|
||||
updateBookInfo();
|
||||
}
|
||||
|
||||
private void updateBookInfo()
|
||||
=> bookInfoLbl.UIThreadAsync(() => bookInfoLbl.Text = $"{title}\r\nBy {authorNames}\r\nNarrated by {narratorNames}");
|
||||
|
||||
public override void AudioDecodable_RequestCoverArt(object sender, Action<byte[]> setCoverArtDelegate)
|
||||
{
|
||||
base.AudioDecodable_RequestCoverArt(sender, setCoverArtDelegate);
|
||||
setCoverArtDelegate(GetCoverArtDelegate?.Invoke());
|
||||
}
|
||||
|
||||
public override void AudioDecodable_CoverImageDiscovered(object sender, byte[] coverArt)
|
||||
{
|
||||
base.AudioDecodable_CoverImageDiscovered(sender, coverArt);
|
||||
pictureBox1.UIThreadAsync(() => pictureBox1.Image = Dinah.Core.Drawing.ImageReader.ToImage(coverArt));
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
using System;
|
||||
using DataLayer;
|
||||
using LibationFileManager;
|
||||
|
||||
namespace LibationWinForms.BookLiberation
|
||||
{
|
||||
class AudioDecryptForm : AudioDecodeForm
|
||||
{
|
||||
public AudioDecryptForm()
|
||||
{
|
||||
this.Load += (_, _) => this.RestoreSizeAndLocation(Configuration.Instance);
|
||||
this.FormClosing += (_, _) => this.SaveSizeAndLocation(Configuration.Instance);
|
||||
}
|
||||
|
||||
#region AudioDecodeForm overrides
|
||||
public override string DecodeActionName => "Decrypting";
|
||||
#endregion
|
||||
|
||||
#region Processable event handler overrides
|
||||
public override void Processable_Begin(object sender, LibraryBook libraryBook)
|
||||
{
|
||||
LogMe.Info($"Download & Decrypt Step, Begin: {libraryBook.Book}");
|
||||
|
||||
base.Processable_Begin(sender, libraryBook);
|
||||
}
|
||||
public override void Processable_Completed(object sender, LibraryBook libraryBook)
|
||||
{
|
||||
base.Processable_Completed(sender, libraryBook);
|
||||
LogMe.Info($"Download & Decrypt Step, Completed: {libraryBook.Book}{Environment.NewLine}");
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -1,61 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<root>
|
||||
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
|
||||
<xsd:element name="root" msdata:IsDataSet="true">
|
||||
<xsd:complexType>
|
||||
<xsd:choice maxOccurs="unbounded">
|
||||
<xsd:element name="metadata">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" use="required" type="xsd:string" />
|
||||
<xsd:attribute name="type" type="xsd:string" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="assembly">
|
||||
<xsd:complexType>
|
||||
<xsd:attribute name="alias" type="xsd:string" />
|
||||
<xsd:attribute name="name" type="xsd:string" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="data">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
|
||||
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="resheader">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:choice>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:schema>
|
||||
<resheader name="resmimetype">
|
||||
<value>text/microsoft-resx</value>
|
||||
</resheader>
|
||||
<resheader name="version">
|
||||
<value>2.0</value>
|
||||
</resheader>
|
||||
<resheader name="reader">
|
||||
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<resheader name="writer">
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
</root>
|
||||
@@ -1,93 +0,0 @@
|
||||
namespace LibationWinForms.BookLiberation
|
||||
{
|
||||
partial class AutomatedBackupsForm
|
||||
{
|
||||
/// <summary>
|
||||
/// Required designer variable.
|
||||
/// </summary>
|
||||
private System.ComponentModel.IContainer components = null;
|
||||
|
||||
/// <summary>
|
||||
/// Clean up any resources being used.
|
||||
/// </summary>
|
||||
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
if (disposing && (components != null))
|
||||
{
|
||||
components.Dispose();
|
||||
}
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
|
||||
#region Windows Form Designer generated code
|
||||
|
||||
/// <summary>
|
||||
/// Required method for Designer support - do not modify
|
||||
/// the contents of this method with the code editor.
|
||||
/// </summary>
|
||||
private void InitializeComponent()
|
||||
{
|
||||
this.keepGoingCb = new System.Windows.Forms.CheckBox();
|
||||
this.logTb = new System.Windows.Forms.TextBox();
|
||||
this.label1 = new System.Windows.Forms.Label();
|
||||
this.SuspendLayout();
|
||||
//
|
||||
// keepGoingCb
|
||||
//
|
||||
this.keepGoingCb.AutoSize = true;
|
||||
this.keepGoingCb.Checked = true;
|
||||
this.keepGoingCb.CheckState = System.Windows.Forms.CheckState.Checked;
|
||||
this.keepGoingCb.Location = new System.Drawing.Point(12, 12);
|
||||
this.keepGoingCb.Name = "keepGoingCb";
|
||||
this.keepGoingCb.Size = new System.Drawing.Size(325, 17);
|
||||
this.keepGoingCb.TabIndex = 0;
|
||||
this.keepGoingCb.Text = "Keep going. Uncheck to stop when current backup is complete";
|
||||
this.keepGoingCb.UseVisualStyleBackColor = true;
|
||||
//
|
||||
// logTb
|
||||
//
|
||||
this.logTb.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.logTb.Location = new System.Drawing.Point(12, 48);
|
||||
this.logTb.Multiline = true;
|
||||
this.logTb.Name = "logTb";
|
||||
this.logTb.ReadOnly = true;
|
||||
this.logTb.ScrollBars = System.Windows.Forms.ScrollBars.Both;
|
||||
this.logTb.Size = new System.Drawing.Size(960, 202);
|
||||
this.logTb.TabIndex = 1;
|
||||
//
|
||||
// label1
|
||||
//
|
||||
this.label1.AutoSize = true;
|
||||
this.label1.Location = new System.Drawing.Point(9, 32);
|
||||
this.label1.Name = "label1";
|
||||
this.label1.Size = new System.Drawing.Size(501, 13);
|
||||
this.label1.TabIndex = 2;
|
||||
this.label1.Text = "NOTE: if the working directories are inside of Dropbox, some book liberation acti" +
|
||||
"ons may hang indefinitely";
|
||||
//
|
||||
// AutomatedBackupsForm
|
||||
//
|
||||
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
|
||||
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
|
||||
this.ClientSize = new System.Drawing.Size(984, 262);
|
||||
this.Controls.Add(this.label1);
|
||||
this.Controls.Add(this.logTb);
|
||||
this.Controls.Add(this.keepGoingCb);
|
||||
this.Name = "AutomatedBackupsForm";
|
||||
this.Text = "Automated Backups";
|
||||
this.FormClosing += new System.Windows.Forms.FormClosingEventHandler(this.AutomatedBackupsForm_FormClosing);
|
||||
this.ResumeLayout(false);
|
||||
this.PerformLayout();
|
||||
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
private System.Windows.Forms.CheckBox keepGoingCb;
|
||||
private System.Windows.Forms.TextBox logTb;
|
||||
private System.Windows.Forms.Label label1;
|
||||
}
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
using System;
|
||||
using System.Windows.Forms;
|
||||
using Dinah.Core.Threading;
|
||||
|
||||
namespace LibationWinForms.BookLiberation
|
||||
{
|
||||
public partial class AutomatedBackupsForm : Form
|
||||
{
|
||||
public bool KeepGoingChecked => keepGoingCb.Checked;
|
||||
|
||||
public bool KeepGoing => keepGoingCb.Enabled && keepGoingCb.Checked;
|
||||
|
||||
public AutomatedBackupsForm()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
public void WriteLine(string text)
|
||||
{
|
||||
if (!IsDisposed)
|
||||
logTb.UIThreadAsync(() => logTb.AppendText($"{DateTime.Now} {text}{Environment.NewLine}"));
|
||||
}
|
||||
|
||||
public void FinalizeUI()
|
||||
{
|
||||
keepGoingCb.Enabled = false;
|
||||
|
||||
if (!IsDisposed)
|
||||
logTb.AppendText("");
|
||||
}
|
||||
|
||||
private void AutomatedBackupsForm_FormClosing(object sender, FormClosingEventArgs e) => keepGoingCb.Checked = false;
|
||||
}
|
||||
}
|
||||
@@ -1,61 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<root>
|
||||
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
|
||||
<xsd:element name="root" msdata:IsDataSet="true">
|
||||
<xsd:complexType>
|
||||
<xsd:choice maxOccurs="unbounded">
|
||||
<xsd:element name="metadata">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" use="required" type="xsd:string" />
|
||||
<xsd:attribute name="type" type="xsd:string" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="assembly">
|
||||
<xsd:complexType>
|
||||
<xsd:attribute name="alias" type="xsd:string" />
|
||||
<xsd:attribute name="name" type="xsd:string" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="data">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
|
||||
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="resheader">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:choice>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:schema>
|
||||
<resheader name="resmimetype">
|
||||
<value>text/microsoft-resx</value>
|
||||
</resheader>
|
||||
<resheader name="version">
|
||||
<value>2.0</value>
|
||||
</resheader>
|
||||
<resheader name="reader">
|
||||
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<resheader name="writer">
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
</root>
|
||||
@@ -1,164 +0,0 @@
|
||||
using System;
|
||||
using System.Windows.Forms;
|
||||
using DataLayer;
|
||||
using Dinah.Core.Net.Http;
|
||||
using Dinah.Core.Threading;
|
||||
using FileLiberator;
|
||||
|
||||
namespace LibationWinForms.BookLiberation.BaseForms
|
||||
{
|
||||
public class LiberationBaseForm : Form
|
||||
{
|
||||
protected Streamable Streamable { get; private set; }
|
||||
protected LogMe LogMe { get; private set; }
|
||||
private SynchronizeInvoker Invoker { get; init; }
|
||||
|
||||
public LiberationBaseForm()
|
||||
{
|
||||
//SynchronizationContext.Current will be null until the process contains a Form.
|
||||
//If this is the first form created, it will not exist until after execution
|
||||
//reaches inside the constructor (after base class has been initialized).
|
||||
Invoker = new SynchronizeInvoker();
|
||||
this.SetLibationIcon();
|
||||
}
|
||||
|
||||
public void RegisterFileLiberator(Streamable streamable, LogMe logMe = null)
|
||||
{
|
||||
if (streamable is null) return;
|
||||
|
||||
Streamable = streamable;
|
||||
LogMe = logMe;
|
||||
|
||||
Subscribe(streamable);
|
||||
|
||||
if (Streamable is Processable processable)
|
||||
Subscribe(processable);
|
||||
if (Streamable is AudioDecodable audioDecodable)
|
||||
Subscribe(audioDecodable);
|
||||
}
|
||||
|
||||
#region Event Subscribers and Unsubscribers
|
||||
private void Subscribe(Streamable streamable)
|
||||
{
|
||||
UnsubscribeStreamable(this, EventArgs.Empty);
|
||||
|
||||
streamable.StreamingBegin += OnStreamingBeginShow;
|
||||
streamable.StreamingBegin += Streamable_StreamingBegin;
|
||||
streamable.StreamingProgressChanged += Streamable_StreamingProgressChanged;
|
||||
streamable.StreamingTimeRemaining += Streamable_StreamingTimeRemaining;
|
||||
streamable.StreamingCompleted += Streamable_StreamingCompleted;
|
||||
streamable.StreamingCompleted += OnStreamingCompletedClose;
|
||||
|
||||
Disposed += UnsubscribeStreamable;
|
||||
}
|
||||
private void Subscribe(Processable processable)
|
||||
{
|
||||
UnsubscribeProcessable(this, null);
|
||||
|
||||
processable.Begin += Processable_Begin;
|
||||
processable.StatusUpdate += Processable_StatusUpdate;
|
||||
processable.Completed += Processable_Completed;
|
||||
|
||||
//The form is created on Processable.Begin and we
|
||||
//dispose of it on Processable.Completed
|
||||
processable.Completed += OnCompletedDispose;
|
||||
|
||||
//Don't unsubscribe from Dispose because it fires when
|
||||
//Streamable.StreamingCompleted closes the form, and
|
||||
//the Processable events need to live past that event.
|
||||
processable.Completed += UnsubscribeProcessable;
|
||||
}
|
||||
private void Subscribe(AudioDecodable audioDecodable)
|
||||
{
|
||||
UnsubscribeAudioDecodable(this, EventArgs.Empty);
|
||||
|
||||
audioDecodable.RequestCoverArt += AudioDecodable_RequestCoverArt;
|
||||
audioDecodable.TitleDiscovered += AudioDecodable_TitleDiscovered;
|
||||
audioDecodable.AuthorsDiscovered += AudioDecodable_AuthorsDiscovered;
|
||||
audioDecodable.NarratorsDiscovered += AudioDecodable_NarratorsDiscovered;
|
||||
audioDecodable.CoverImageDiscovered += AudioDecodable_CoverImageDiscovered;
|
||||
|
||||
Disposed += UnsubscribeAudioDecodable;
|
||||
}
|
||||
private void UnsubscribeStreamable(object sender, EventArgs e)
|
||||
{
|
||||
Disposed -= UnsubscribeStreamable;
|
||||
|
||||
Streamable.StreamingBegin -= OnStreamingBeginShow;
|
||||
Streamable.StreamingBegin -= Streamable_StreamingBegin;
|
||||
Streamable.StreamingProgressChanged -= Streamable_StreamingProgressChanged;
|
||||
Streamable.StreamingTimeRemaining -= Streamable_StreamingTimeRemaining;
|
||||
Streamable.StreamingCompleted -= Streamable_StreamingCompleted;
|
||||
Streamable.StreamingCompleted -= OnStreamingCompletedClose;
|
||||
}
|
||||
private void UnsubscribeProcessable(object sender, LibraryBook e)
|
||||
{
|
||||
if (Streamable is not Processable processable)
|
||||
return;
|
||||
|
||||
processable.Completed -= UnsubscribeProcessable;
|
||||
processable.Completed -= OnCompletedDispose;
|
||||
processable.Completed -= Processable_Completed;
|
||||
processable.StatusUpdate -= Processable_StatusUpdate;
|
||||
processable.Begin -= Processable_Begin;
|
||||
}
|
||||
private void UnsubscribeAudioDecodable(object sender, EventArgs e)
|
||||
{
|
||||
if (Streamable is not AudioDecodable audioDecodable)
|
||||
return;
|
||||
|
||||
Disposed -= UnsubscribeAudioDecodable;
|
||||
audioDecodable.RequestCoverArt -= AudioDecodable_RequestCoverArt;
|
||||
audioDecodable.TitleDiscovered -= AudioDecodable_TitleDiscovered;
|
||||
audioDecodable.AuthorsDiscovered -= AudioDecodable_AuthorsDiscovered;
|
||||
audioDecodable.NarratorsDiscovered -= AudioDecodable_NarratorsDiscovered;
|
||||
audioDecodable.CoverImageDiscovered -= AudioDecodable_CoverImageDiscovered;
|
||||
|
||||
audioDecodable.Cancel();
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Form creation and disposal handling
|
||||
|
||||
/// <summary>
|
||||
/// If the form was shown using Show (not ShowDialog), Form.Close calls Form.Dispose
|
||||
/// </summary>
|
||||
private void OnStreamingCompletedClose(object sender, string completedString) => this.UIThreadAsync(Close);
|
||||
private void OnCompletedDispose(object sender, LibraryBook e) => this.UIThreadAsync(Dispose);
|
||||
|
||||
/// <summary>
|
||||
/// If StreamingBegin is fired from a worker thread, the window will be created on that
|
||||
/// worker thread. We need to make certain that we show the window on the UI thread (same
|
||||
/// thread that created form), otherwise the renderer will be on a worker thread which
|
||||
/// could cause it to freeze. Form.BeginInvoke won't work until the form is created
|
||||
/// (ie. shown) because Control doesn't get a window handle until it is Shown.
|
||||
/// </summary>
|
||||
private void OnStreamingBeginShow(object sender, string beginString) => Invoker.UIThreadAsync(Show);
|
||||
|
||||
#endregion
|
||||
|
||||
#region Streamable event handlers
|
||||
public virtual void Streamable_StreamingBegin(object sender, string beginString) { }
|
||||
public virtual void Streamable_StreamingProgressChanged(object sender, DownloadProgress downloadProgress) { }
|
||||
public virtual void Streamable_StreamingTimeRemaining(object sender, TimeSpan timeRemaining) { }
|
||||
public virtual void Streamable_StreamingCompleted(object sender, string completedString) { }
|
||||
|
||||
#endregion
|
||||
|
||||
#region Processable event handlers
|
||||
public virtual void Processable_Begin(object sender, LibraryBook libraryBook) { }
|
||||
public virtual void Processable_StatusUpdate(object sender, string statusUpdate) { }
|
||||
public virtual void Processable_Completed(object sender, LibraryBook libraryBook) { }
|
||||
|
||||
#endregion
|
||||
|
||||
#region AudioDecodable event handlers
|
||||
public virtual void AudioDecodable_TitleDiscovered(object sender, string title) { }
|
||||
public virtual void AudioDecodable_AuthorsDiscovered(object sender, string authors) { }
|
||||
public virtual void AudioDecodable_NarratorsDiscovered(object sender, string narrators) { }
|
||||
|
||||
public virtual void AudioDecodable_CoverImageDiscovered(object sender, byte[] coverArt) { }
|
||||
public virtual void AudioDecodable_RequestCoverArt(object sender, Action<byte[]> setCoverArtDelegate) { }
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -2,30 +2,48 @@
|
||||
using System.Windows.Forms;
|
||||
using Dinah.Core.Net.Http;
|
||||
using Dinah.Core.Threading;
|
||||
using LibationWinForms.BookLiberation.BaseForms;
|
||||
using FileLiberator;
|
||||
|
||||
namespace LibationWinForms.BookLiberation
|
||||
{
|
||||
public partial class DownloadForm : LiberationBaseForm
|
||||
public partial class DownloadForm : Form
|
||||
{
|
||||
protected Streamable Streamable { get; private set; }
|
||||
protected LogMe LogMe { get; private set; }
|
||||
private SynchronizeInvoker Invoker { get; init; }
|
||||
|
||||
public DownloadForm()
|
||||
{
|
||||
//SynchronizationContext.Current will be null until the process contains a Form.
|
||||
//If this is the first form created, it will not exist until after execution
|
||||
//reaches inside the constructor (after base class has been initialized).
|
||||
Invoker = new SynchronizeInvoker();
|
||||
InitializeComponent();
|
||||
|
||||
this.SetLibationIcon();
|
||||
progressLbl.Text = "";
|
||||
filenameLbl.Text = "";
|
||||
}
|
||||
|
||||
public void RegisterFileLiberator(Streamable streamable, LogMe logMe = null)
|
||||
{
|
||||
if (streamable is null) return;
|
||||
streamable.StreamingBegin += Streamable_StreamingBegin;
|
||||
streamable.StreamingProgressChanged += Streamable_StreamingProgressChanged;
|
||||
streamable.StreamingCompleted += (_, _) => this.UIThreadAsync(Close);
|
||||
Streamable = streamable;
|
||||
LogMe = logMe;
|
||||
}
|
||||
|
||||
|
||||
#region Streamable event handler overrides
|
||||
public override void Streamable_StreamingBegin(object sender, string beginString)
|
||||
public void Streamable_StreamingBegin(object sender, string beginString)
|
||||
{
|
||||
base.Streamable_StreamingBegin(sender, beginString);
|
||||
Invoker.UIThreadAsync(Show);
|
||||
filenameLbl.UIThreadAsync(() => filenameLbl.Text = beginString);
|
||||
}
|
||||
public override void Streamable_StreamingProgressChanged(object sender, DownloadProgress downloadProgress)
|
||||
public void Streamable_StreamingProgressChanged(object sender, DownloadProgress downloadProgress)
|
||||
{
|
||||
base.Streamable_StreamingProgressChanged(sender, downloadProgress);
|
||||
// this won't happen with download file. it will happen with download string
|
||||
if (!downloadProgress.TotalBytesToReceive.HasValue || downloadProgress.TotalBytesToReceive.Value <= 0)
|
||||
return;
|
||||
|
||||
9
Source/LibationWinForms/BookLiberation/ILogForm.cs
Normal file
9
Source/LibationWinForms/BookLiberation/ILogForm.cs
Normal file
@@ -0,0 +1,9 @@
|
||||
using System;
|
||||
|
||||
namespace LibationWinForms.BookLiberation
|
||||
{
|
||||
public interface ILogForm
|
||||
{
|
||||
void WriteLine(string text);
|
||||
}
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
using DataLayer;
|
||||
|
||||
namespace LibationWinForms.BookLiberation
|
||||
{
|
||||
internal class PdfDownloadForm : DownloadForm
|
||||
{
|
||||
public override void Processable_Begin(object sender, LibraryBook libraryBook)
|
||||
{
|
||||
base.Processable_Begin(sender, libraryBook);
|
||||
LogMe.Info($"PDF Step, Begin: {libraryBook.Book}");
|
||||
}
|
||||
public override void Processable_Completed(object sender, LibraryBook libraryBook)
|
||||
{
|
||||
base.Processable_Completed(sender, libraryBook);
|
||||
LogMe.Info($"PDF Step, Completed: {libraryBook.Book}");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,61 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<root>
|
||||
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
|
||||
<xsd:element name="root" msdata:IsDataSet="true">
|
||||
<xsd:complexType>
|
||||
<xsd:choice maxOccurs="unbounded">
|
||||
<xsd:element name="metadata">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" use="required" type="xsd:string" />
|
||||
<xsd:attribute name="type" type="xsd:string" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="assembly">
|
||||
<xsd:complexType>
|
||||
<xsd:attribute name="alias" type="xsd:string" />
|
||||
<xsd:attribute name="name" type="xsd:string" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="data">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
|
||||
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="resheader">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:choice>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:schema>
|
||||
<resheader name="resmimetype">
|
||||
<value>text/microsoft-resx</value>
|
||||
</resheader>
|
||||
<resheader name="version">
|
||||
<value>2.0</value>
|
||||
</resheader>
|
||||
<resheader name="reader">
|
||||
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<resheader name="writer">
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
</root>
|
||||
@@ -3,11 +3,7 @@ using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Forms;
|
||||
using DataLayer;
|
||||
using Dinah.Core;
|
||||
using FileLiberator;
|
||||
using LibationFileManager;
|
||||
using LibationWinForms.BookLiberation.BaseForms;
|
||||
|
||||
namespace LibationWinForms.BookLiberation
|
||||
{
|
||||
@@ -25,7 +21,7 @@ namespace LibationWinForms.BookLiberation
|
||||
LogError += (_, tuple) => Serilog.Log.Logger.Error(tuple.Item1, tuple.Item2 ?? "Automated backup: error");
|
||||
}
|
||||
|
||||
public static LogMe RegisterForm(AutomatedBackupsForm form = null)
|
||||
public static LogMe RegisterForm<T>(T form) where T : ILogForm
|
||||
{
|
||||
var logMe = new LogMe();
|
||||
|
||||
@@ -52,66 +48,6 @@ namespace LibationWinForms.BookLiberation
|
||||
|
||||
public static class ProcessorAutomationController
|
||||
{
|
||||
public static async Task BackupSingleBookAsync(LibraryBook libraryBook)
|
||||
{
|
||||
Serilog.Log.Logger.Information($"Begin {nameof(BackupSingleBookAsync)} {{@DebugInfo}}", new { libraryBook?.Book?.AudibleProductId });
|
||||
|
||||
var logMe = LogMe.RegisterForm();
|
||||
var backupBook = CreateBackupBook(logMe);
|
||||
|
||||
// continue even if libraryBook is null. we'll display even that in the processing box
|
||||
await new BackupSingle(logMe, backupBook, libraryBook).RunBackupAsync();
|
||||
}
|
||||
|
||||
public static async Task BackupAllBooksAsync(List<LibraryBook> libraryBooks = null)
|
||||
{
|
||||
Serilog.Log.Logger.Information("Begin " + nameof(BackupAllBooksAsync));
|
||||
|
||||
var automatedBackupsForm = new AutomatedBackupsForm();
|
||||
var logMe = LogMe.RegisterForm(automatedBackupsForm);
|
||||
var backupBook = CreateBackupBook(logMe);
|
||||
|
||||
await new BackupLoop(logMe, backupBook, automatedBackupsForm, libraryBooks).RunBackupAsync();
|
||||
}
|
||||
|
||||
public static async Task ConvertAllBooksAsync()
|
||||
{
|
||||
Serilog.Log.Logger.Information("Begin " + nameof(ConvertAllBooksAsync));
|
||||
|
||||
var automatedBackupsForm = new AutomatedBackupsForm();
|
||||
var logMe = LogMe.RegisterForm(automatedBackupsForm);
|
||||
|
||||
var convertBook = CreateProcessable<ConvertToMp3, AudioConvertForm>(logMe);
|
||||
|
||||
await new BackupLoop(logMe, convertBook, automatedBackupsForm).RunBackupAsync();
|
||||
}
|
||||
|
||||
public static async Task BackupAllPdfsAsync()
|
||||
{
|
||||
Serilog.Log.Logger.Information("Begin " + nameof(BackupAllPdfsAsync));
|
||||
|
||||
var automatedBackupsForm = new AutomatedBackupsForm();
|
||||
var logMe = LogMe.RegisterForm(automatedBackupsForm);
|
||||
|
||||
var downloadPdf = CreateProcessable<DownloadPdf, PdfDownloadForm>(logMe);
|
||||
|
||||
await new BackupLoop(logMe, downloadPdf, automatedBackupsForm).RunBackupAsync();
|
||||
}
|
||||
|
||||
private static Processable CreateBackupBook(LogMe logMe)
|
||||
{
|
||||
var downloadPdf = CreateProcessable<DownloadPdf, PdfDownloadForm>(logMe);
|
||||
|
||||
//Chain pdf download on DownloadDecryptBook.Completed
|
||||
async void onDownloadDecryptBookCompleted(object sender, LibraryBook e)
|
||||
{
|
||||
await downloadPdf.TryProcessAsync(e);
|
||||
}
|
||||
|
||||
var downloadDecryptBook = CreateProcessable<DownloadDecryptBook, AudioDecryptForm>(logMe, onDownloadDecryptBookCompleted);
|
||||
return downloadDecryptBook;
|
||||
}
|
||||
|
||||
public static void DownloadFile(string url, string destination, bool showDownloadCompletedDialog = false)
|
||||
{
|
||||
Serilog.Log.Logger.Information($"Begin {nameof(DownloadFile)} for {url}");
|
||||
@@ -132,212 +68,5 @@ namespace LibationWinForms.BookLiberation
|
||||
async void runDownload() => await downloadFile.PerformDownloadFileAsync(url, destination);
|
||||
new Task(runDownload).Start();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a new <see cref="Processable"/> and links it to a new <see cref="LiberationBaseForm"/>.
|
||||
/// </summary>
|
||||
/// <typeparam name="TProcessable">The <see cref="Processable"/> derived type to create.</typeparam>
|
||||
/// <typeparam name="TForm">The <see cref="LiberationBaseForm"/> derived Form to create on <see cref="Processable.Begin"/>, Show on <see cref="Streamable.StreamingBegin"/>, Close on <see cref="Streamable.StreamingCompleted"/>, and Dispose on <see cref="Processable.Completed"/> </typeparam>
|
||||
/// <param name="logMe">The logger</param>
|
||||
/// <param name="completedAction">An additional event handler to handle <see cref="Processable.Completed"/></param>
|
||||
/// <returns>A new <see cref="Processable"/> of type <typeparamref name="TProcessable"/></returns>
|
||||
private static TProcessable CreateProcessable<TProcessable, TForm>(LogMe logMe, EventHandler<LibraryBook> completedAction = null)
|
||||
where TForm : LiberationBaseForm, new()
|
||||
where TProcessable : Processable, new()
|
||||
{
|
||||
var strProc = new TProcessable();
|
||||
|
||||
strProc.Begin += (sender, libraryBook) =>
|
||||
{
|
||||
var processForm = new TForm();
|
||||
processForm.RegisterFileLiberator(strProc, logMe);
|
||||
processForm.Processable_Begin(sender, libraryBook);
|
||||
};
|
||||
|
||||
strProc.Completed += completedAction;
|
||||
|
||||
return strProc;
|
||||
}
|
||||
}
|
||||
|
||||
internal abstract class BackupRunner
|
||||
{
|
||||
protected LogMe LogMe { get; }
|
||||
protected Processable Processable { get; }
|
||||
protected AutomatedBackupsForm AutomatedBackupsForm { get; }
|
||||
|
||||
protected BackupRunner(LogMe logMe, Processable processable, AutomatedBackupsForm automatedBackupsForm = null)
|
||||
{
|
||||
LogMe = logMe;
|
||||
Processable = processable;
|
||||
AutomatedBackupsForm = automatedBackupsForm;
|
||||
}
|
||||
|
||||
protected abstract Task RunAsync();
|
||||
protected abstract string SkipDialogText { get; }
|
||||
protected abstract MessageBoxButtons SkipDialogButtons { get; }
|
||||
protected abstract MessageBoxDefaultButton SkipDialogDefaultButton { get; }
|
||||
protected abstract DialogResult SkipResult { get; }
|
||||
|
||||
public async Task RunBackupAsync()
|
||||
{
|
||||
AutomatedBackupsForm?.Show();
|
||||
|
||||
try
|
||||
{
|
||||
await RunAsync();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogMe.Error(ex);
|
||||
}
|
||||
|
||||
AutomatedBackupsForm?.FinalizeUI();
|
||||
LogMe.Info("DONE");
|
||||
}
|
||||
|
||||
protected async Task<bool> ProcessOneAsync(LibraryBook libraryBook, bool validate)
|
||||
{
|
||||
try
|
||||
{
|
||||
var statusHandler = await Processable.ProcessSingleAsync(libraryBook, validate);
|
||||
|
||||
if (statusHandler.IsSuccess)
|
||||
return true;
|
||||
|
||||
foreach (var errorMessage in statusHandler.Errors)
|
||||
LogMe.Error(errorMessage);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogMe.Error(ex);
|
||||
}
|
||||
|
||||
return showRetry(libraryBook);
|
||||
}
|
||||
|
||||
private bool showRetry(LibraryBook libraryBook)
|
||||
{
|
||||
LogMe.Error("ERROR. All books have not been processed. Most recent book: processing failed");
|
||||
|
||||
DialogResult? dialogResult = Configuration.Instance.BadBook switch
|
||||
{
|
||||
Configuration.BadBookAction.Abort => DialogResult.Abort,
|
||||
Configuration.BadBookAction.Retry => DialogResult.Retry,
|
||||
Configuration.BadBookAction.Ignore => DialogResult.Ignore,
|
||||
Configuration.BadBookAction.Ask => null,
|
||||
_ => null
|
||||
};
|
||||
|
||||
string details;
|
||||
try
|
||||
{
|
||||
static string trunc(string str)
|
||||
=> string.IsNullOrWhiteSpace(str) ? "[empty]"
|
||||
: (str.Length > 50) ? $"{str.Truncate(47)}..."
|
||||
: str;
|
||||
|
||||
details =
|
||||
$@" Title: {libraryBook.Book.Title}
|
||||
ID: {libraryBook.Book.AudibleProductId}
|
||||
Author: {trunc(libraryBook.Book.AuthorNames())}
|
||||
Narr: {trunc(libraryBook.Book.NarratorNames())}";
|
||||
}
|
||||
catch
|
||||
{
|
||||
details = "[Error retrieving details]";
|
||||
}
|
||||
|
||||
// if null then ask user
|
||||
dialogResult ??= MessageBox.Show(string.Format(SkipDialogText + "\r\n\r\nSee Settings to avoid this box in the future.", details), "Skip importing this book?", SkipDialogButtons, MessageBoxIcon.Question, SkipDialogDefaultButton);
|
||||
|
||||
if (dialogResult == DialogResult.Abort)
|
||||
return false;
|
||||
|
||||
if (dialogResult == SkipResult)
|
||||
{
|
||||
libraryBook.Book.UserDefinedItem.BookStatus = LiberatedStatus.Error;
|
||||
ApplicationServices.LibraryCommands.UpdateUserDefinedItem(libraryBook.Book);
|
||||
|
||||
LogMe.Info($"Error. Skip: [{libraryBook.Book.AudibleProductId}] {libraryBook.Book.Title}");
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
internal class BackupSingle : BackupRunner
|
||||
{
|
||||
private LibraryBook _libraryBook { get; }
|
||||
|
||||
protected override string SkipDialogText => @"
|
||||
An error occurred while trying to process this book. Skip this book permanently?
|
||||
{0}
|
||||
|
||||
- Click YES to skip this book permanently.
|
||||
|
||||
- Click NO to skip the book this time only. We'll try again later.
|
||||
".Trim();
|
||||
protected override MessageBoxButtons SkipDialogButtons => MessageBoxButtons.YesNo;
|
||||
protected override MessageBoxDefaultButton SkipDialogDefaultButton => MessageBoxDefaultButton.Button2;
|
||||
protected override DialogResult SkipResult => DialogResult.Yes;
|
||||
|
||||
public BackupSingle(LogMe logMe, Processable processable, LibraryBook libraryBook)
|
||||
: base(logMe, processable)
|
||||
{
|
||||
_libraryBook = libraryBook;
|
||||
}
|
||||
|
||||
protected override async Task RunAsync()
|
||||
{
|
||||
if (_libraryBook is not null)
|
||||
await ProcessOneAsync(_libraryBook, validate: true);
|
||||
}
|
||||
}
|
||||
|
||||
internal class BackupLoop : BackupRunner
|
||||
{
|
||||
protected override string SkipDialogText => @"
|
||||
An error occurred while trying to process this book.
|
||||
{0}
|
||||
|
||||
- ABORT: Stop processing books.
|
||||
|
||||
- RETRY: retry this book later. Just skip it for now. Continue processing books. (Will try this book again later.)
|
||||
|
||||
- IGNORE: Permanently ignore this book. Continue processing books. (Will not try this book again later.)
|
||||
".Trim();
|
||||
protected override MessageBoxButtons SkipDialogButtons => MessageBoxButtons.AbortRetryIgnore;
|
||||
protected override MessageBoxDefaultButton SkipDialogDefaultButton => MessageBoxDefaultButton.Button1;
|
||||
protected override DialogResult SkipResult => DialogResult.Ignore;
|
||||
|
||||
private List<LibraryBook> libraryBooks { get; }
|
||||
|
||||
public BackupLoop(LogMe logMe, Processable processable, AutomatedBackupsForm automatedBackupsForm, List<LibraryBook> libraryBooks = null)
|
||||
: base(logMe, processable, automatedBackupsForm)
|
||||
=> this.libraryBooks = libraryBooks ?? ApplicationServices.DbContexts.GetLibrary_Flat_NoTracking();
|
||||
|
||||
protected override async Task RunAsync()
|
||||
{
|
||||
// support for 'skip this time only' requires state. iterators provide this state for free. therefore: use foreach/iterator here
|
||||
foreach (var libraryBook in Processable.GetValidLibraryBooks(libraryBooks))
|
||||
{
|
||||
var keepGoing = await ProcessOneAsync(libraryBook, validate: false);
|
||||
if (!keepGoing)
|
||||
return;
|
||||
|
||||
if (AutomatedBackupsForm.IsDisposed)
|
||||
break;
|
||||
|
||||
if (!AutomatedBackupsForm.KeepGoing)
|
||||
{
|
||||
if (!AutomatedBackupsForm.KeepGoingChecked)
|
||||
LogMe.Info("'Keep going' is unchecked");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
LogMe.Info("Done. All books have been processed");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,12 +15,8 @@ namespace LibationWinForms.Dialogs
|
||||
private const string COL_AccountName = nameof(AccountName);
|
||||
private const string COL_Locale = nameof(Locale);
|
||||
|
||||
private Form1 _parent { get; }
|
||||
|
||||
public AccountsDialog(Form1 parent)
|
||||
public AccountsDialog()
|
||||
{
|
||||
_parent = parent;
|
||||
|
||||
InitializeComponent();
|
||||
|
||||
dataGridView1.Columns[COL_AccountName].AutoSizeMode = DataGridViewAutoSizeColumnMode.Fill;
|
||||
|
||||
@@ -15,12 +15,8 @@ namespace LibationWinForms.Dialogs
|
||||
private const string COL_MoveUp = nameof(MoveUp);
|
||||
private const string COL_MoveDown = nameof(MoveDown);
|
||||
|
||||
private Form1 _parent { get; }
|
||||
|
||||
public EditQuickFilters(Form1 parent)
|
||||
public EditQuickFilters()
|
||||
{
|
||||
_parent = parent;
|
||||
|
||||
InitializeComponent();
|
||||
|
||||
dataGridView1.Columns[COL_Filter].AutoSizeMode = DataGridViewAutoSizeColumnMode.Fill;
|
||||
|
||||
@@ -136,11 +136,8 @@ namespace LibationWinForms.Dialogs
|
||||
}
|
||||
set
|
||||
{
|
||||
if (_remove != value)
|
||||
{
|
||||
_remove = value;
|
||||
NotifyPropertyChanged();
|
||||
}
|
||||
_remove = value;
|
||||
NotifyPropertyChanged();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -10,12 +10,8 @@ namespace LibationWinForms.Dialogs
|
||||
{
|
||||
public List<Account> CheckedAccounts { get; } = new List<Account>();
|
||||
|
||||
private Form1 _parent { get; }
|
||||
|
||||
public ScanAccountsDialog(Form1 parent)
|
||||
public ScanAccountsDialog()
|
||||
{
|
||||
_parent = parent;
|
||||
|
||||
InitializeComponent();
|
||||
this.SetLibationIcon();
|
||||
}
|
||||
@@ -44,7 +40,7 @@ namespace LibationWinForms.Dialogs
|
||||
|
||||
private void editBtn_Click(object sender, EventArgs e)
|
||||
{
|
||||
if (new AccountsDialog(_parent).ShowDialog() == DialogResult.OK)
|
||||
if (new AccountsDialog().ShowDialog() == DialogResult.OK)
|
||||
{
|
||||
// clear grid
|
||||
this.accountsClb.Items.Clear();
|
||||
|
||||
@@ -21,7 +21,6 @@ namespace LibationWinForms.Dialogs
|
||||
|
||||
private void convertFormatRb_CheckedChanged(object sender, EventArgs e)
|
||||
{
|
||||
lameOptionsGb.Enabled = convertLossyRb.Checked;
|
||||
lameTargetRb_CheckedChanged(sender, e);
|
||||
LameMatchSourceBRCbox_CheckedChanged(sender, e);
|
||||
}
|
||||
|
||||
125
Source/LibationWinForms/Form1.BackupCounts.cs
Normal file
125
Source/LibationWinForms/Form1.BackupCounts.cs
Normal file
@@ -0,0 +1,125 @@
|
||||
using ApplicationServices;
|
||||
using Dinah.Core;
|
||||
using Dinah.Core.Threading;
|
||||
|
||||
namespace LibationWinForms
|
||||
{
|
||||
public partial class Form1
|
||||
{
|
||||
protected void Configure_BackupCounts()
|
||||
{
|
||||
// init formattable
|
||||
beginBookBackupsToolStripMenuItem.Format(0);
|
||||
beginPdfBackupsToolStripMenuItem.Format(0);
|
||||
pdfsCountsLbl.Text = "| [Calculating backed up PDFs]";
|
||||
|
||||
Load += setBackupCounts;
|
||||
LibraryCommands.LibrarySizeChanged += setBackupCounts;
|
||||
LibraryCommands.BookUserDefinedItemCommitted += setBackupCounts;
|
||||
}
|
||||
|
||||
private System.ComponentModel.BackgroundWorker updateCountsBw;
|
||||
private bool runBackupCountsAgain;
|
||||
|
||||
private void setBackupCounts(object _, object __)
|
||||
{
|
||||
runBackupCountsAgain = true;
|
||||
|
||||
if (updateCountsBw is not null)
|
||||
return;
|
||||
|
||||
updateCountsBw = new System.ComponentModel.BackgroundWorker();
|
||||
updateCountsBw.DoWork += UpdateCountsBw_DoWork;
|
||||
updateCountsBw.RunWorkerCompleted += UpdateCountsBw_RunWorkerCompleted;
|
||||
updateCountsBw.RunWorkerAsync();
|
||||
}
|
||||
|
||||
private void UpdateCountsBw_DoWork(object sender, System.ComponentModel.DoWorkEventArgs e)
|
||||
{
|
||||
while (runBackupCountsAgain)
|
||||
{
|
||||
runBackupCountsAgain = false;
|
||||
|
||||
var libraryStats = LibraryCommands.GetCounts();
|
||||
e.Result = libraryStats;
|
||||
}
|
||||
updateCountsBw = null;
|
||||
}
|
||||
|
||||
private void UpdateCountsBw_RunWorkerCompleted(object sender, System.ComponentModel.RunWorkerCompletedEventArgs e)
|
||||
{
|
||||
var libraryStats = e.Result as LibraryCommands.LibraryStats;
|
||||
|
||||
setBookBackupCounts(libraryStats);
|
||||
setPdfBackupCounts(libraryStats);
|
||||
}
|
||||
|
||||
// this cannot be cleanly be FormattableToolStripMenuItem because of the optional "Errors" text
|
||||
private const string backupsCountsLbl_Format = "BACKUPS: No progress: {0} In process: {1} Fully backed up: {2}";
|
||||
|
||||
private void setBookBackupCounts(LibraryCommands.LibraryStats libraryStats)
|
||||
{
|
||||
var pending = libraryStats.booksNoProgress + libraryStats.booksDownloadedOnly;
|
||||
var hasResults = 0 < (libraryStats.booksFullyBackedUp + libraryStats.booksDownloadedOnly + libraryStats.booksNoProgress + libraryStats.booksError);
|
||||
|
||||
// enable/disable export
|
||||
{
|
||||
exportLibraryToolStripMenuItem.Enabled = hasResults;
|
||||
}
|
||||
|
||||
// update bottom numbers
|
||||
{
|
||||
var formatString
|
||||
= !hasResults ? "No books. Begin by importing your library"
|
||||
: libraryStats.booksError > 0 ? backupsCountsLbl_Format + " Errors: {3}"
|
||||
: pending > 0 ? backupsCountsLbl_Format
|
||||
: $"All {"book".PluralizeWithCount(libraryStats.booksFullyBackedUp)} backed up";
|
||||
var statusStripText = string.Format(formatString,
|
||||
libraryStats.booksNoProgress,
|
||||
libraryStats.booksDownloadedOnly,
|
||||
libraryStats.booksFullyBackedUp,
|
||||
libraryStats.booksError);
|
||||
statusStrip1.UIThreadAsync(() => backupsCountsLbl.Text = statusStripText);
|
||||
}
|
||||
|
||||
// update 'begin book backups' menu item
|
||||
{
|
||||
var menuItemText
|
||||
= pending > 0
|
||||
? $"{pending} remaining"
|
||||
: "All books have been liberated";
|
||||
menuStrip1.UIThreadAsync(() =>
|
||||
{
|
||||
beginBookBackupsToolStripMenuItem.Format(menuItemText);
|
||||
beginBookBackupsToolStripMenuItem.Enabled = pending > 0;
|
||||
});
|
||||
}
|
||||
}
|
||||
private void setPdfBackupCounts(LibraryCommands.LibraryStats libraryStats)
|
||||
{
|
||||
// update bottom numbers
|
||||
{
|
||||
var hasResults = 0 < (libraryStats.pdfsNotDownloaded + libraryStats.pdfsDownloaded);
|
||||
// don't need to assign the output of Format(). It just makes this logic cleaner
|
||||
var statusStripText
|
||||
= !hasResults ? ""
|
||||
: libraryStats.pdfsNotDownloaded > 0 ? pdfsCountsLbl.Format(libraryStats.pdfsNotDownloaded, libraryStats.pdfsDownloaded)
|
||||
: $"| All {libraryStats.pdfsDownloaded} PDFs downloaded";
|
||||
statusStrip1.UIThreadAsync(() => pdfsCountsLbl.Text = statusStripText);
|
||||
}
|
||||
|
||||
// update 'begin pdf only backups' menu item
|
||||
{
|
||||
var menuItemText
|
||||
= libraryStats.pdfsNotDownloaded > 0
|
||||
? $"{libraryStats.pdfsNotDownloaded} remaining"
|
||||
: "All PDFs have been downloaded";
|
||||
menuStrip1.UIThreadAsync(() =>
|
||||
{
|
||||
beginPdfBackupsToolStripMenuItem.Format(menuItemText);
|
||||
beginPdfBackupsToolStripMenuItem.Enabled = libraryStats.pdfsNotDownloaded > 0;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
267
Source/LibationWinForms/Form1.Designer.cs
generated
267
Source/LibationWinForms/Form1.Designer.cs
generated
@@ -44,10 +44,10 @@
|
||||
this.removeAllAccountsToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
|
||||
this.removeSomeAccountsToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
|
||||
this.liberateToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
|
||||
this.beginBookBackupsToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
|
||||
this.beginPdfBackupsToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
|
||||
this.beginBookBackupsToolStripMenuItem = new LibationWinForms.FormattableToolStripMenuItem();
|
||||
this.beginPdfBackupsToolStripMenuItem = new LibationWinForms.FormattableToolStripMenuItem();
|
||||
this.convertAllM4bToMp3ToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
|
||||
this.liberateVisible2ToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
|
||||
this.liberateVisible2ToolStripMenuItem = new LibationWinForms.FormattableToolStripMenuItem();
|
||||
this.exportToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
|
||||
this.exportLibraryToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
|
||||
this.quickFiltersToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
|
||||
@@ -55,8 +55,8 @@
|
||||
this.editQuickFiltersToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
|
||||
this.toolStripSeparator1 = new System.Windows.Forms.ToolStripSeparator();
|
||||
this.scanningToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
|
||||
this.visibleBooksToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
|
||||
this.liberateVisibleToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
|
||||
this.visibleBooksToolStripMenuItem = new LibationWinForms.FormattableToolStripMenuItem();
|
||||
this.liberateVisibleToolStripMenuItem = new LibationWinForms.FormattableToolStripMenuItem();
|
||||
this.replaceTagsToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
|
||||
this.setDownloadedToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
|
||||
this.removeToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
|
||||
@@ -66,13 +66,22 @@
|
||||
this.toolStripSeparator2 = new System.Windows.Forms.ToolStripSeparator();
|
||||
this.aboutToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
|
||||
this.statusStrip1 = new System.Windows.Forms.StatusStrip();
|
||||
this.visibleCountLbl = new System.Windows.Forms.ToolStripStatusLabel();
|
||||
this.visibleCountLbl = new LibationWinForms.FormattableToolStripStatusLabel();
|
||||
this.springLbl = new System.Windows.Forms.ToolStripStatusLabel();
|
||||
this.backupsCountsLbl = new System.Windows.Forms.ToolStripStatusLabel();
|
||||
this.pdfsCountsLbl = new System.Windows.Forms.ToolStripStatusLabel();
|
||||
this.addFilterBtn = new System.Windows.Forms.Button();
|
||||
this.pdfsCountsLbl = new LibationWinForms.FormattableToolStripStatusLabel();
|
||||
this.addQuickFilterBtn = new System.Windows.Forms.Button();
|
||||
this.splitContainer1 = new System.Windows.Forms.SplitContainer();
|
||||
this.panel1 = new System.Windows.Forms.Panel();
|
||||
this.toggleQueueHideBtn = new System.Windows.Forms.Button();
|
||||
this.processBookQueue1 = new LibationWinForms.ProcessQueue.ProcessQueueControl();
|
||||
this.menuStrip1.SuspendLayout();
|
||||
this.statusStrip1.SuspendLayout();
|
||||
((System.ComponentModel.ISupportInitialize)(this.splitContainer1)).BeginInit();
|
||||
this.splitContainer1.Panel1.SuspendLayout();
|
||||
this.splitContainer1.Panel2.SuspendLayout();
|
||||
this.splitContainer1.SuspendLayout();
|
||||
this.panel1.SuspendLayout();
|
||||
this.SuspendLayout();
|
||||
//
|
||||
// gridPanel
|
||||
@@ -80,18 +89,18 @@
|
||||
this.gridPanel.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.gridPanel.Location = new System.Drawing.Point(34, 178);
|
||||
this.gridPanel.Margin = new System.Windows.Forms.Padding(10, 8, 10, 8);
|
||||
this.gridPanel.Location = new System.Drawing.Point(15, 33);
|
||||
this.gridPanel.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3);
|
||||
this.gridPanel.Name = "gridPanel";
|
||||
this.gridPanel.Size = new System.Drawing.Size(2378, 1216);
|
||||
this.gridPanel.Size = new System.Drawing.Size(864, 558);
|
||||
this.gridPanel.TabIndex = 5;
|
||||
//
|
||||
// filterHelpBtn
|
||||
//
|
||||
this.filterHelpBtn.Location = new System.Drawing.Point(34, 85);
|
||||
this.filterHelpBtn.Margin = new System.Windows.Forms.Padding(10, 8, 10, 8);
|
||||
this.filterHelpBtn.Location = new System.Drawing.Point(15, 3);
|
||||
this.filterHelpBtn.Margin = new System.Windows.Forms.Padding(15, 3, 4, 3);
|
||||
this.filterHelpBtn.Name = "filterHelpBtn";
|
||||
this.filterHelpBtn.Size = new System.Drawing.Size(63, 74);
|
||||
this.filterHelpBtn.Size = new System.Drawing.Size(26, 27);
|
||||
this.filterHelpBtn.TabIndex = 3;
|
||||
this.filterHelpBtn.Text = "?";
|
||||
this.filterHelpBtn.UseVisualStyleBackColor = true;
|
||||
@@ -100,10 +109,10 @@
|
||||
// filterBtn
|
||||
//
|
||||
this.filterBtn.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right)));
|
||||
this.filterBtn.Location = new System.Drawing.Point(2198, 85);
|
||||
this.filterBtn.Margin = new System.Windows.Forms.Padding(10, 8, 10, 8);
|
||||
this.filterBtn.Location = new System.Drawing.Point(748, 3);
|
||||
this.filterBtn.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3);
|
||||
this.filterBtn.Name = "filterBtn";
|
||||
this.filterBtn.Size = new System.Drawing.Size(214, 74);
|
||||
this.filterBtn.Size = new System.Drawing.Size(88, 27);
|
||||
this.filterBtn.TabIndex = 2;
|
||||
this.filterBtn.Text = "Filter";
|
||||
this.filterBtn.UseVisualStyleBackColor = true;
|
||||
@@ -113,10 +122,10 @@
|
||||
//
|
||||
this.filterSearchTb.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left)
|
||||
| System.Windows.Forms.AnchorStyles.Right)));
|
||||
this.filterSearchTb.Location = new System.Drawing.Point(527, 90);
|
||||
this.filterSearchTb.Margin = new System.Windows.Forms.Padding(10, 8, 10, 8);
|
||||
this.filterSearchTb.Location = new System.Drawing.Point(196, 7);
|
||||
this.filterSearchTb.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3);
|
||||
this.filterSearchTb.Name = "filterSearchTb";
|
||||
this.filterSearchTb.Size = new System.Drawing.Size(1648, 47);
|
||||
this.filterSearchTb.Size = new System.Drawing.Size(544, 23);
|
||||
this.filterSearchTb.TabIndex = 1;
|
||||
this.filterSearchTb.KeyPress += new System.Windows.Forms.KeyPressEventHandler(this.filterSearchTb_KeyPress);
|
||||
//
|
||||
@@ -133,8 +142,8 @@
|
||||
this.settingsToolStripMenuItem});
|
||||
this.menuStrip1.Location = new System.Drawing.Point(0, 0);
|
||||
this.menuStrip1.Name = "menuStrip1";
|
||||
this.menuStrip1.Padding = new System.Windows.Forms.Padding(17, 5, 0, 5);
|
||||
this.menuStrip1.Size = new System.Drawing.Size(2446, 58);
|
||||
this.menuStrip1.Padding = new System.Windows.Forms.Padding(7, 2, 0, 2);
|
||||
this.menuStrip1.Size = new System.Drawing.Size(893, 24);
|
||||
this.menuStrip1.TabIndex = 0;
|
||||
this.menuStrip1.Text = "menuStrip1";
|
||||
//
|
||||
@@ -148,41 +157,42 @@
|
||||
this.scanLibraryOfSomeAccountsToolStripMenuItem,
|
||||
this.removeLibraryBooksToolStripMenuItem});
|
||||
this.importToolStripMenuItem.Name = "importToolStripMenuItem";
|
||||
this.importToolStripMenuItem.Size = new System.Drawing.Size(132, 48);
|
||||
this.importToolStripMenuItem.Size = new System.Drawing.Size(55, 20);
|
||||
this.importToolStripMenuItem.Text = "&Import";
|
||||
//
|
||||
// autoScanLibraryToolStripMenuItem
|
||||
//
|
||||
this.autoScanLibraryToolStripMenuItem.ImageScaling = System.Windows.Forms.ToolStripItemImageScaling.None;
|
||||
this.autoScanLibraryToolStripMenuItem.Name = "autoScanLibraryToolStripMenuItem";
|
||||
this.autoScanLibraryToolStripMenuItem.Size = new System.Drawing.Size(613, 54);
|
||||
this.autoScanLibraryToolStripMenuItem.Size = new System.Drawing.Size(247, 22);
|
||||
this.autoScanLibraryToolStripMenuItem.Text = "A&uto Scan Library";
|
||||
this.autoScanLibraryToolStripMenuItem.Click += new System.EventHandler(this.autoScanLibraryToolStripMenuItem_Click);
|
||||
//
|
||||
// noAccountsYetAddAccountToolStripMenuItem
|
||||
//
|
||||
this.noAccountsYetAddAccountToolStripMenuItem.Name = "noAccountsYetAddAccountToolStripMenuItem";
|
||||
this.noAccountsYetAddAccountToolStripMenuItem.Size = new System.Drawing.Size(613, 54);
|
||||
this.noAccountsYetAddAccountToolStripMenuItem.Size = new System.Drawing.Size(247, 22);
|
||||
this.noAccountsYetAddAccountToolStripMenuItem.Text = "No accounts yet. A&dd Account...";
|
||||
this.noAccountsYetAddAccountToolStripMenuItem.Click += new System.EventHandler(this.noAccountsYetAddAccountToolStripMenuItem_Click);
|
||||
//
|
||||
// scanLibraryToolStripMenuItem
|
||||
//
|
||||
this.scanLibraryToolStripMenuItem.Name = "scanLibraryToolStripMenuItem";
|
||||
this.scanLibraryToolStripMenuItem.Size = new System.Drawing.Size(613, 54);
|
||||
this.scanLibraryToolStripMenuItem.Size = new System.Drawing.Size(247, 22);
|
||||
this.scanLibraryToolStripMenuItem.Text = "Scan &Library";
|
||||
this.scanLibraryToolStripMenuItem.Click += new System.EventHandler(this.scanLibraryToolStripMenuItem_Click);
|
||||
//
|
||||
// scanLibraryOfAllAccountsToolStripMenuItem
|
||||
//
|
||||
this.scanLibraryOfAllAccountsToolStripMenuItem.Name = "scanLibraryOfAllAccountsToolStripMenuItem";
|
||||
this.scanLibraryOfAllAccountsToolStripMenuItem.Size = new System.Drawing.Size(613, 54);
|
||||
this.scanLibraryOfAllAccountsToolStripMenuItem.Size = new System.Drawing.Size(247, 22);
|
||||
this.scanLibraryOfAllAccountsToolStripMenuItem.Text = "Scan Library of &All Accounts";
|
||||
this.scanLibraryOfAllAccountsToolStripMenuItem.Click += new System.EventHandler(this.scanLibraryOfAllAccountsToolStripMenuItem_Click);
|
||||
//
|
||||
// scanLibraryOfSomeAccountsToolStripMenuItem
|
||||
//
|
||||
this.scanLibraryOfSomeAccountsToolStripMenuItem.Name = "scanLibraryOfSomeAccountsToolStripMenuItem";
|
||||
this.scanLibraryOfSomeAccountsToolStripMenuItem.Size = new System.Drawing.Size(613, 54);
|
||||
this.scanLibraryOfSomeAccountsToolStripMenuItem.Size = new System.Drawing.Size(247, 22);
|
||||
this.scanLibraryOfSomeAccountsToolStripMenuItem.Text = "Scan Library of &Some Accounts...";
|
||||
this.scanLibraryOfSomeAccountsToolStripMenuItem.Click += new System.EventHandler(this.scanLibraryOfSomeAccountsToolStripMenuItem_Click);
|
||||
//
|
||||
@@ -192,21 +202,21 @@
|
||||
this.removeAllAccountsToolStripMenuItem,
|
||||
this.removeSomeAccountsToolStripMenuItem});
|
||||
this.removeLibraryBooksToolStripMenuItem.Name = "removeLibraryBooksToolStripMenuItem";
|
||||
this.removeLibraryBooksToolStripMenuItem.Size = new System.Drawing.Size(613, 54);
|
||||
this.removeLibraryBooksToolStripMenuItem.Size = new System.Drawing.Size(247, 22);
|
||||
this.removeLibraryBooksToolStripMenuItem.Text = "Remove Library Books";
|
||||
this.removeLibraryBooksToolStripMenuItem.Click += new System.EventHandler(this.removeLibraryBooksToolStripMenuItem_Click);
|
||||
//
|
||||
// removeAllAccountsToolStripMenuItem
|
||||
//
|
||||
this.removeAllAccountsToolStripMenuItem.Name = "removeAllAccountsToolStripMenuItem";
|
||||
this.removeAllAccountsToolStripMenuItem.Size = new System.Drawing.Size(390, 54);
|
||||
this.removeAllAccountsToolStripMenuItem.Size = new System.Drawing.Size(157, 22);
|
||||
this.removeAllAccountsToolStripMenuItem.Text = "All Accounts";
|
||||
this.removeAllAccountsToolStripMenuItem.Click += new System.EventHandler(this.removeAllAccountsToolStripMenuItem_Click);
|
||||
//
|
||||
// removeSomeAccountsToolStripMenuItem
|
||||
//
|
||||
this.removeSomeAccountsToolStripMenuItem.Name = "removeSomeAccountsToolStripMenuItem";
|
||||
this.removeSomeAccountsToolStripMenuItem.Size = new System.Drawing.Size(390, 54);
|
||||
this.removeSomeAccountsToolStripMenuItem.Size = new System.Drawing.Size(157, 22);
|
||||
this.removeSomeAccountsToolStripMenuItem.Text = "Some Accounts";
|
||||
this.removeSomeAccountsToolStripMenuItem.Click += new System.EventHandler(this.removeSomeAccountsToolStripMenuItem_Click);
|
||||
//
|
||||
@@ -218,34 +228,37 @@
|
||||
this.convertAllM4bToMp3ToolStripMenuItem,
|
||||
this.liberateVisible2ToolStripMenuItem});
|
||||
this.liberateToolStripMenuItem.Name = "liberateToolStripMenuItem";
|
||||
this.liberateToolStripMenuItem.Size = new System.Drawing.Size(148, 48);
|
||||
this.liberateToolStripMenuItem.Size = new System.Drawing.Size(61, 20);
|
||||
this.liberateToolStripMenuItem.Text = "&Liberate";
|
||||
//
|
||||
// beginBookBackupsToolStripMenuItem
|
||||
//
|
||||
this.beginBookBackupsToolStripMenuItem.FormatText = "Begin &Book and PDF Backups: {0}";
|
||||
this.beginBookBackupsToolStripMenuItem.Name = "beginBookBackupsToolStripMenuItem";
|
||||
this.beginBookBackupsToolStripMenuItem.Size = new System.Drawing.Size(728, 54);
|
||||
this.beginBookBackupsToolStripMenuItem.Size = new System.Drawing.Size(293, 22);
|
||||
this.beginBookBackupsToolStripMenuItem.Text = "Begin &Book and PDF Backups: {0}";
|
||||
this.beginBookBackupsToolStripMenuItem.Click += new System.EventHandler(this.beginBookBackupsToolStripMenuItem_Click);
|
||||
//
|
||||
// beginPdfBackupsToolStripMenuItem
|
||||
//
|
||||
this.beginPdfBackupsToolStripMenuItem.FormatText = "Begin &PDF Only Backups: {0}";
|
||||
this.beginPdfBackupsToolStripMenuItem.Name = "beginPdfBackupsToolStripMenuItem";
|
||||
this.beginPdfBackupsToolStripMenuItem.Size = new System.Drawing.Size(728, 54);
|
||||
this.beginPdfBackupsToolStripMenuItem.Size = new System.Drawing.Size(293, 22);
|
||||
this.beginPdfBackupsToolStripMenuItem.Text = "Begin &PDF Only Backups: {0}";
|
||||
this.beginPdfBackupsToolStripMenuItem.Click += new System.EventHandler(this.beginPdfBackupsToolStripMenuItem_Click);
|
||||
//
|
||||
// convertAllM4bToMp3ToolStripMenuItem
|
||||
//
|
||||
this.convertAllM4bToMp3ToolStripMenuItem.Name = "convertAllM4bToMp3ToolStripMenuItem";
|
||||
this.convertAllM4bToMp3ToolStripMenuItem.Size = new System.Drawing.Size(728, 54);
|
||||
this.convertAllM4bToMp3ToolStripMenuItem.Size = new System.Drawing.Size(293, 22);
|
||||
this.convertAllM4bToMp3ToolStripMenuItem.Text = "Convert all &M4b to Mp3 [Long-running]...";
|
||||
this.convertAllM4bToMp3ToolStripMenuItem.Click += new System.EventHandler(this.convertAllM4bToMp3ToolStripMenuItem_Click);
|
||||
//
|
||||
// liberateVisible2ToolStripMenuItem
|
||||
//
|
||||
this.liberateVisible2ToolStripMenuItem.FormatText = "Liberate &Visible Books: {0}";
|
||||
this.liberateVisible2ToolStripMenuItem.Name = "liberateVisible2ToolStripMenuItem";
|
||||
this.liberateVisible2ToolStripMenuItem.Size = new System.Drawing.Size(728, 54);
|
||||
this.liberateVisible2ToolStripMenuItem.Size = new System.Drawing.Size(293, 22);
|
||||
this.liberateVisible2ToolStripMenuItem.Text = "Liberate &Visible Books: {0}";
|
||||
this.liberateVisible2ToolStripMenuItem.Click += new System.EventHandler(this.liberateVisible);
|
||||
//
|
||||
@@ -254,13 +267,13 @@
|
||||
this.exportToolStripMenuItem.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] {
|
||||
this.exportLibraryToolStripMenuItem});
|
||||
this.exportToolStripMenuItem.Name = "exportToolStripMenuItem";
|
||||
this.exportToolStripMenuItem.Size = new System.Drawing.Size(127, 48);
|
||||
this.exportToolStripMenuItem.Size = new System.Drawing.Size(53, 20);
|
||||
this.exportToolStripMenuItem.Text = "E&xport";
|
||||
//
|
||||
// exportLibraryToolStripMenuItem
|
||||
//
|
||||
this.exportLibraryToolStripMenuItem.Name = "exportLibraryToolStripMenuItem";
|
||||
this.exportLibraryToolStripMenuItem.Size = new System.Drawing.Size(387, 54);
|
||||
this.exportLibraryToolStripMenuItem.Size = new System.Drawing.Size(156, 22);
|
||||
this.exportLibraryToolStripMenuItem.Text = "E&xport Library...";
|
||||
this.exportLibraryToolStripMenuItem.Click += new System.EventHandler(this.exportLibraryToolStripMenuItem_Click);
|
||||
//
|
||||
@@ -271,35 +284,37 @@
|
||||
this.editQuickFiltersToolStripMenuItem,
|
||||
this.toolStripSeparator1});
|
||||
this.quickFiltersToolStripMenuItem.Name = "quickFiltersToolStripMenuItem";
|
||||
this.quickFiltersToolStripMenuItem.Size = new System.Drawing.Size(204, 48);
|
||||
this.quickFiltersToolStripMenuItem.Size = new System.Drawing.Size(84, 20);
|
||||
this.quickFiltersToolStripMenuItem.Text = "Quick &Filters";
|
||||
//
|
||||
// firstFilterIsDefaultToolStripMenuItem
|
||||
//
|
||||
this.firstFilterIsDefaultToolStripMenuItem.ImageScaling = System.Windows.Forms.ToolStripItemImageScaling.None;
|
||||
this.firstFilterIsDefaultToolStripMenuItem.Name = "firstFilterIsDefaultToolStripMenuItem";
|
||||
this.firstFilterIsDefaultToolStripMenuItem.Size = new System.Drawing.Size(639, 54);
|
||||
this.firstFilterIsDefaultToolStripMenuItem.Size = new System.Drawing.Size(256, 22);
|
||||
this.firstFilterIsDefaultToolStripMenuItem.Text = "Start Libation with 1st filter &Default";
|
||||
this.firstFilterIsDefaultToolStripMenuItem.Click += new System.EventHandler(this.FirstFilterIsDefaultToolStripMenuItem_Click);
|
||||
this.firstFilterIsDefaultToolStripMenuItem.Click += new System.EventHandler(this.firstFilterIsDefaultToolStripMenuItem_Click);
|
||||
//
|
||||
// editQuickFiltersToolStripMenuItem
|
||||
//
|
||||
this.editQuickFiltersToolStripMenuItem.Name = "editQuickFiltersToolStripMenuItem";
|
||||
this.editQuickFiltersToolStripMenuItem.Size = new System.Drawing.Size(639, 54);
|
||||
this.editQuickFiltersToolStripMenuItem.Size = new System.Drawing.Size(256, 22);
|
||||
this.editQuickFiltersToolStripMenuItem.Text = "&Edit quick filters...";
|
||||
this.editQuickFiltersToolStripMenuItem.Click += new System.EventHandler(this.EditQuickFiltersToolStripMenuItem_Click);
|
||||
this.editQuickFiltersToolStripMenuItem.Click += new System.EventHandler(this.editQuickFiltersToolStripMenuItem_Click);
|
||||
//
|
||||
// toolStripSeparator1
|
||||
//
|
||||
this.toolStripSeparator1.Name = "toolStripSeparator1";
|
||||
this.toolStripSeparator1.Size = new System.Drawing.Size(636, 6);
|
||||
this.toolStripSeparator1.Size = new System.Drawing.Size(253, 6);
|
||||
//
|
||||
// scanningToolStripMenuItem
|
||||
//
|
||||
this.scanningToolStripMenuItem.Alignment = System.Windows.Forms.ToolStripItemAlignment.Right;
|
||||
this.scanningToolStripMenuItem.Enabled = false;
|
||||
this.scanningToolStripMenuItem.Image = global::LibationWinForms.Properties.Resources.import_16x16;
|
||||
this.scanningToolStripMenuItem.ImageScaling = System.Windows.Forms.ToolStripItemImageScaling.None;
|
||||
this.scanningToolStripMenuItem.Name = "scanningToolStripMenuItem";
|
||||
this.scanningToolStripMenuItem.Size = new System.Drawing.Size(224, 48);
|
||||
this.scanningToolStripMenuItem.Size = new System.Drawing.Size(93, 20);
|
||||
this.scanningToolStripMenuItem.Text = "Scanning...";
|
||||
this.scanningToolStripMenuItem.Visible = false;
|
||||
//
|
||||
@@ -310,35 +325,37 @@
|
||||
this.replaceTagsToolStripMenuItem,
|
||||
this.setDownloadedToolStripMenuItem,
|
||||
this.removeToolStripMenuItem});
|
||||
this.visibleBooksToolStripMenuItem.FormatText = "&Visible Books: {0}";
|
||||
this.visibleBooksToolStripMenuItem.Name = "visibleBooksToolStripMenuItem";
|
||||
this.visibleBooksToolStripMenuItem.Size = new System.Drawing.Size(267, 48);
|
||||
this.visibleBooksToolStripMenuItem.Size = new System.Drawing.Size(108, 20);
|
||||
this.visibleBooksToolStripMenuItem.Text = "&Visible Books: {0}";
|
||||
//
|
||||
// liberateVisibleToolStripMenuItem
|
||||
//
|
||||
this.liberateVisibleToolStripMenuItem.FormatText = "&Liberate: {0}";
|
||||
this.liberateVisibleToolStripMenuItem.Name = "liberateVisibleToolStripMenuItem";
|
||||
this.liberateVisibleToolStripMenuItem.Size = new System.Drawing.Size(525, 54);
|
||||
this.liberateVisibleToolStripMenuItem.Size = new System.Drawing.Size(209, 22);
|
||||
this.liberateVisibleToolStripMenuItem.Text = "&Liberate: {0}";
|
||||
this.liberateVisibleToolStripMenuItem.Click += new System.EventHandler(this.liberateVisible);
|
||||
//
|
||||
// replaceTagsToolStripMenuItem
|
||||
//
|
||||
this.replaceTagsToolStripMenuItem.Name = "replaceTagsToolStripMenuItem";
|
||||
this.replaceTagsToolStripMenuItem.Size = new System.Drawing.Size(525, 54);
|
||||
this.replaceTagsToolStripMenuItem.Size = new System.Drawing.Size(209, 22);
|
||||
this.replaceTagsToolStripMenuItem.Text = "Replace &Tags...";
|
||||
this.replaceTagsToolStripMenuItem.Click += new System.EventHandler(this.replaceTagsToolStripMenuItem_Click);
|
||||
//
|
||||
// setDownloadedToolStripMenuItem
|
||||
//
|
||||
this.setDownloadedToolStripMenuItem.Name = "setDownloadedToolStripMenuItem";
|
||||
this.setDownloadedToolStripMenuItem.Size = new System.Drawing.Size(525, 54);
|
||||
this.setDownloadedToolStripMenuItem.Size = new System.Drawing.Size(209, 22);
|
||||
this.setDownloadedToolStripMenuItem.Text = "Set \'&Downloaded\' status...";
|
||||
this.setDownloadedToolStripMenuItem.Click += new System.EventHandler(this.setDownloadedToolStripMenuItem_Click);
|
||||
//
|
||||
// removeToolStripMenuItem
|
||||
//
|
||||
this.removeToolStripMenuItem.Name = "removeToolStripMenuItem";
|
||||
this.removeToolStripMenuItem.Size = new System.Drawing.Size(525, 54);
|
||||
this.removeToolStripMenuItem.Size = new System.Drawing.Size(209, 22);
|
||||
this.removeToolStripMenuItem.Text = "&Remove from library...";
|
||||
this.removeToolStripMenuItem.Click += new System.EventHandler(this.removeToolStripMenuItem_Click);
|
||||
//
|
||||
@@ -350,32 +367,32 @@
|
||||
this.toolStripSeparator2,
|
||||
this.aboutToolStripMenuItem});
|
||||
this.settingsToolStripMenuItem.Name = "settingsToolStripMenuItem";
|
||||
this.settingsToolStripMenuItem.Size = new System.Drawing.Size(149, 48);
|
||||
this.settingsToolStripMenuItem.Size = new System.Drawing.Size(61, 20);
|
||||
this.settingsToolStripMenuItem.Text = "&Settings";
|
||||
//
|
||||
// accountsToolStripMenuItem
|
||||
//
|
||||
this.accountsToolStripMenuItem.Name = "accountsToolStripMenuItem";
|
||||
this.accountsToolStripMenuItem.Size = new System.Drawing.Size(327, 54);
|
||||
this.accountsToolStripMenuItem.Size = new System.Drawing.Size(133, 22);
|
||||
this.accountsToolStripMenuItem.Text = "&Accounts...";
|
||||
this.accountsToolStripMenuItem.Click += new System.EventHandler(this.accountsToolStripMenuItem_Click);
|
||||
//
|
||||
// basicSettingsToolStripMenuItem
|
||||
//
|
||||
this.basicSettingsToolStripMenuItem.Name = "basicSettingsToolStripMenuItem";
|
||||
this.basicSettingsToolStripMenuItem.Size = new System.Drawing.Size(327, 54);
|
||||
this.basicSettingsToolStripMenuItem.Size = new System.Drawing.Size(133, 22);
|
||||
this.basicSettingsToolStripMenuItem.Text = "&Settings...";
|
||||
this.basicSettingsToolStripMenuItem.Click += new System.EventHandler(this.basicSettingsToolStripMenuItem_Click);
|
||||
//
|
||||
// toolStripSeparator2
|
||||
//
|
||||
this.toolStripSeparator2.Name = "toolStripSeparator2";
|
||||
this.toolStripSeparator2.Size = new System.Drawing.Size(324, 6);
|
||||
this.toolStripSeparator2.Size = new System.Drawing.Size(130, 6);
|
||||
//
|
||||
// aboutToolStripMenuItem
|
||||
//
|
||||
this.aboutToolStripMenuItem.Name = "aboutToolStripMenuItem";
|
||||
this.aboutToolStripMenuItem.Size = new System.Drawing.Size(327, 54);
|
||||
this.aboutToolStripMenuItem.Size = new System.Drawing.Size(133, 22);
|
||||
this.aboutToolStripMenuItem.Text = "A&bout...";
|
||||
this.aboutToolStripMenuItem.Click += new System.EventHandler(this.aboutToolStripMenuItem_Click);
|
||||
//
|
||||
@@ -387,63 +404,117 @@
|
||||
this.springLbl,
|
||||
this.backupsCountsLbl,
|
||||
this.pdfsCountsLbl});
|
||||
this.statusStrip1.Location = new System.Drawing.Point(0, 1419);
|
||||
this.statusStrip1.Location = new System.Drawing.Point(0, 618);
|
||||
this.statusStrip1.Name = "statusStrip1";
|
||||
this.statusStrip1.Padding = new System.Windows.Forms.Padding(2, 0, 39, 0);
|
||||
this.statusStrip1.Size = new System.Drawing.Size(2446, 54);
|
||||
this.statusStrip1.Padding = new System.Windows.Forms.Padding(1, 0, 16, 0);
|
||||
this.statusStrip1.Size = new System.Drawing.Size(893, 22);
|
||||
this.statusStrip1.TabIndex = 6;
|
||||
this.statusStrip1.Text = "statusStrip1";
|
||||
//
|
||||
// visibleCountLbl
|
||||
//
|
||||
this.visibleCountLbl.FormatText = "Visible: {0}";
|
||||
this.visibleCountLbl.Name = "visibleCountLbl";
|
||||
this.visibleCountLbl.Size = new System.Drawing.Size(136, 41);
|
||||
this.visibleCountLbl.Text = "Visible: 0";
|
||||
this.visibleCountLbl.Size = new System.Drawing.Size(61, 17);
|
||||
this.visibleCountLbl.Text = "Visible: {0}";
|
||||
//
|
||||
// springLbl
|
||||
//
|
||||
this.springLbl.Name = "springLbl";
|
||||
this.springLbl.Size = new System.Drawing.Size(1299, 41);
|
||||
this.springLbl.Size = new System.Drawing.Size(379, 17);
|
||||
this.springLbl.Spring = true;
|
||||
//
|
||||
// backupsCountsLbl
|
||||
//
|
||||
this.backupsCountsLbl.Name = "backupsCountsLbl";
|
||||
this.backupsCountsLbl.Size = new System.Drawing.Size(544, 41);
|
||||
this.backupsCountsLbl.Size = new System.Drawing.Size(218, 17);
|
||||
this.backupsCountsLbl.Text = "[Calculating backed up book quantities]";
|
||||
//
|
||||
// pdfsCountsLbl
|
||||
//
|
||||
this.pdfsCountsLbl.FormatText = "| PDFs: NOT d/l\'ed: {0} Downloaded: {1}";
|
||||
this.pdfsCountsLbl.Name = "pdfsCountsLbl";
|
||||
this.pdfsCountsLbl.Size = new System.Drawing.Size(426, 41);
|
||||
this.pdfsCountsLbl.Text = "| [Calculating backed up PDFs]";
|
||||
this.pdfsCountsLbl.Size = new System.Drawing.Size(218, 17);
|
||||
this.pdfsCountsLbl.Text = "| PDFs: NOT d/l\'ed: {0} Downloaded: {1}";
|
||||
//
|
||||
// addFilterBtn
|
||||
// addQuickFilterBtn
|
||||
//
|
||||
this.addFilterBtn.Location = new System.Drawing.Point(114, 85);
|
||||
this.addFilterBtn.Margin = new System.Windows.Forms.Padding(10, 8, 10, 8);
|
||||
this.addFilterBtn.Name = "addFilterBtn";
|
||||
this.addFilterBtn.Size = new System.Drawing.Size(396, 74);
|
||||
this.addFilterBtn.TabIndex = 4;
|
||||
this.addFilterBtn.Text = "Add To Quick Filters";
|
||||
this.addFilterBtn.UseVisualStyleBackColor = true;
|
||||
this.addFilterBtn.Click += new System.EventHandler(this.AddFilterBtn_Click);
|
||||
this.addQuickFilterBtn.Location = new System.Drawing.Point(50, 3);
|
||||
this.addQuickFilterBtn.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3);
|
||||
this.addQuickFilterBtn.Name = "addQuickFilterBtn";
|
||||
this.addQuickFilterBtn.Size = new System.Drawing.Size(137, 27);
|
||||
this.addQuickFilterBtn.TabIndex = 4;
|
||||
this.addQuickFilterBtn.Text = "Add To Quick Filters";
|
||||
this.addQuickFilterBtn.UseVisualStyleBackColor = true;
|
||||
this.addQuickFilterBtn.Click += new System.EventHandler(this.addQuickFilterBtn_Click);
|
||||
//
|
||||
// splitContainer1
|
||||
//
|
||||
this.splitContainer1.Dock = System.Windows.Forms.DockStyle.Fill;
|
||||
this.splitContainer1.Location = new System.Drawing.Point(0, 0);
|
||||
this.splitContainer1.Name = "splitContainer1";
|
||||
//
|
||||
// splitContainer1.Panel1
|
||||
//
|
||||
this.splitContainer1.Panel1.Controls.Add(this.panel1);
|
||||
this.splitContainer1.Panel1.Controls.Add(this.menuStrip1);
|
||||
this.splitContainer1.Panel1.Controls.Add(this.statusStrip1);
|
||||
//
|
||||
// splitContainer1.Panel2
|
||||
//
|
||||
this.splitContainer1.Panel2.Controls.Add(this.processBookQueue1);
|
||||
this.splitContainer1.Size = new System.Drawing.Size(1231, 640);
|
||||
this.splitContainer1.SplitterDistance = 893;
|
||||
this.splitContainer1.SplitterWidth = 8;
|
||||
this.splitContainer1.TabIndex = 7;
|
||||
//
|
||||
// panel1
|
||||
//
|
||||
this.panel1.AutoSizeMode = System.Windows.Forms.AutoSizeMode.GrowAndShrink;
|
||||
this.panel1.Controls.Add(this.toggleQueueHideBtn);
|
||||
this.panel1.Controls.Add(this.gridPanel);
|
||||
this.panel1.Controls.Add(this.addQuickFilterBtn);
|
||||
this.panel1.Controls.Add(this.filterHelpBtn);
|
||||
this.panel1.Controls.Add(this.filterSearchTb);
|
||||
this.panel1.Controls.Add(this.filterBtn);
|
||||
this.panel1.Dock = System.Windows.Forms.DockStyle.Fill;
|
||||
this.panel1.Location = new System.Drawing.Point(0, 24);
|
||||
this.panel1.Margin = new System.Windows.Forms.Padding(0);
|
||||
this.panel1.Name = "panel1";
|
||||
this.panel1.Size = new System.Drawing.Size(893, 594);
|
||||
this.panel1.TabIndex = 7;
|
||||
//
|
||||
// toggleQueueHideBtn
|
||||
//
|
||||
this.toggleQueueHideBtn.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right)));
|
||||
this.toggleQueueHideBtn.Location = new System.Drawing.Point(845, 3);
|
||||
this.toggleQueueHideBtn.Margin = new System.Windows.Forms.Padding(4, 3, 15, 3);
|
||||
this.toggleQueueHideBtn.Name = "toggleQueueHideBtn";
|
||||
this.toggleQueueHideBtn.Size = new System.Drawing.Size(33, 27);
|
||||
this.toggleQueueHideBtn.TabIndex = 8;
|
||||
this.toggleQueueHideBtn.Text = "❱❱❱";
|
||||
this.toggleQueueHideBtn.UseVisualStyleBackColor = true;
|
||||
this.toggleQueueHideBtn.Click += new System.EventHandler(this.ToggleQueueHideBtn_Click);
|
||||
//
|
||||
// processBookQueue1
|
||||
//
|
||||
this.processBookQueue1.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle;
|
||||
this.processBookQueue1.Dock = System.Windows.Forms.DockStyle.Fill;
|
||||
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.Size = new System.Drawing.Size(330, 640);
|
||||
this.processBookQueue1.TabIndex = 0;
|
||||
//
|
||||
// Form1
|
||||
//
|
||||
this.AutoScaleDimensions = new System.Drawing.SizeF(17F, 41F);
|
||||
this.AutoScaleDimensions = new System.Drawing.SizeF(7F, 15F);
|
||||
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
|
||||
this.ClientSize = new System.Drawing.Size(2446, 1473);
|
||||
this.Controls.Add(this.filterBtn);
|
||||
this.Controls.Add(this.addFilterBtn);
|
||||
this.Controls.Add(this.filterSearchTb);
|
||||
this.Controls.Add(this.filterHelpBtn);
|
||||
this.Controls.Add(this.statusStrip1);
|
||||
this.Controls.Add(this.gridPanel);
|
||||
this.Controls.Add(this.menuStrip1);
|
||||
this.ClientSize = new System.Drawing.Size(1231, 640);
|
||||
this.Controls.Add(this.splitContainer1);
|
||||
this.Icon = ((System.Drawing.Icon)(resources.GetObject("$this.Icon")));
|
||||
this.MainMenuStrip = this.menuStrip1;
|
||||
this.Margin = new System.Windows.Forms.Padding(10, 8, 10, 8);
|
||||
this.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3);
|
||||
this.Name = "Form1";
|
||||
this.Text = "Libation: Liberate your Library";
|
||||
this.Load += new System.EventHandler(this.Form1_Load);
|
||||
@@ -451,8 +522,14 @@
|
||||
this.menuStrip1.PerformLayout();
|
||||
this.statusStrip1.ResumeLayout(false);
|
||||
this.statusStrip1.PerformLayout();
|
||||
this.splitContainer1.Panel1.ResumeLayout(false);
|
||||
this.splitContainer1.Panel1.PerformLayout();
|
||||
this.splitContainer1.Panel2.ResumeLayout(false);
|
||||
((System.ComponentModel.ISupportInitialize)(this.splitContainer1)).EndInit();
|
||||
this.splitContainer1.ResumeLayout(false);
|
||||
this.panel1.ResumeLayout(false);
|
||||
this.panel1.PerformLayout();
|
||||
this.ResumeLayout(false);
|
||||
this.PerformLayout();
|
||||
|
||||
}
|
||||
|
||||
@@ -463,12 +540,12 @@
|
||||
private System.Windows.Forms.ToolStripMenuItem importToolStripMenuItem;
|
||||
private System.Windows.Forms.StatusStrip statusStrip1;
|
||||
private System.Windows.Forms.ToolStripStatusLabel springLbl;
|
||||
private System.Windows.Forms.ToolStripStatusLabel visibleCountLbl;
|
||||
private LibationWinForms.FormattableToolStripStatusLabel visibleCountLbl;
|
||||
private System.Windows.Forms.ToolStripMenuItem liberateToolStripMenuItem;
|
||||
private System.Windows.Forms.ToolStripStatusLabel backupsCountsLbl;
|
||||
private System.Windows.Forms.ToolStripMenuItem beginBookBackupsToolStripMenuItem;
|
||||
private System.Windows.Forms.ToolStripStatusLabel pdfsCountsLbl;
|
||||
private System.Windows.Forms.ToolStripMenuItem beginPdfBackupsToolStripMenuItem;
|
||||
private LibationWinForms.FormattableToolStripMenuItem beginBookBackupsToolStripMenuItem;
|
||||
private LibationWinForms.FormattableToolStripStatusLabel pdfsCountsLbl;
|
||||
private LibationWinForms.FormattableToolStripMenuItem beginPdfBackupsToolStripMenuItem;
|
||||
private System.Windows.Forms.TextBox filterSearchTb;
|
||||
private System.Windows.Forms.Button filterBtn;
|
||||
private System.Windows.Forms.Button filterHelpBtn;
|
||||
@@ -476,7 +553,7 @@
|
||||
private System.Windows.Forms.ToolStripMenuItem scanLibraryToolStripMenuItem;
|
||||
private System.Windows.Forms.ToolStripMenuItem quickFiltersToolStripMenuItem;
|
||||
private System.Windows.Forms.ToolStripMenuItem firstFilterIsDefaultToolStripMenuItem;
|
||||
private System.Windows.Forms.Button addFilterBtn;
|
||||
private System.Windows.Forms.Button addQuickFilterBtn;
|
||||
private System.Windows.Forms.ToolStripMenuItem editQuickFiltersToolStripMenuItem;
|
||||
private System.Windows.Forms.ToolStripSeparator toolStripSeparator1;
|
||||
private System.Windows.Forms.ToolStripMenuItem basicSettingsToolStripMenuItem;
|
||||
@@ -494,11 +571,15 @@
|
||||
private System.Windows.Forms.ToolStripMenuItem aboutToolStripMenuItem;
|
||||
private System.Windows.Forms.ToolStripMenuItem scanningToolStripMenuItem;
|
||||
private System.Windows.Forms.ToolStripMenuItem autoScanLibraryToolStripMenuItem;
|
||||
private System.Windows.Forms.ToolStripMenuItem visibleBooksToolStripMenuItem;
|
||||
private System.Windows.Forms.ToolStripMenuItem liberateVisibleToolStripMenuItem;
|
||||
private LibationWinForms.FormattableToolStripMenuItem visibleBooksToolStripMenuItem;
|
||||
private LibationWinForms.FormattableToolStripMenuItem liberateVisibleToolStripMenuItem;
|
||||
private System.Windows.Forms.ToolStripMenuItem replaceTagsToolStripMenuItem;
|
||||
private System.Windows.Forms.ToolStripMenuItem setDownloadedToolStripMenuItem;
|
||||
private System.Windows.Forms.ToolStripMenuItem removeToolStripMenuItem;
|
||||
private System.Windows.Forms.ToolStripMenuItem liberateVisible2ToolStripMenuItem;
|
||||
}
|
||||
private LibationWinForms.FormattableToolStripMenuItem liberateVisible2ToolStripMenuItem;
|
||||
private System.Windows.Forms.SplitContainer splitContainer1;
|
||||
private LibationWinForms.ProcessQueue.ProcessQueueControl processBookQueue1;
|
||||
private System.Windows.Forms.Panel panel1;
|
||||
private System.Windows.Forms.Button toggleQueueHideBtn;
|
||||
}
|
||||
}
|
||||
|
||||
47
Source/LibationWinForms/Form1.Export.cs
Normal file
47
Source/LibationWinForms/Form1.Export.cs
Normal file
@@ -0,0 +1,47 @@
|
||||
using System;
|
||||
using System.Windows.Forms;
|
||||
using ApplicationServices;
|
||||
|
||||
namespace LibationWinForms
|
||||
{
|
||||
public partial class Form1
|
||||
{
|
||||
private void Configure_Export() { }
|
||||
|
||||
private void exportLibraryToolStripMenuItem_Click(object sender, EventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
var saveFileDialog = new SaveFileDialog
|
||||
{
|
||||
Title = "Where to export Library",
|
||||
Filter = "Excel Workbook (*.xlsx)|*.xlsx|CSV files (*.csv)|*.csv|JSON files (*.json)|*.json" // + "|All files (*.*)|*.*"
|
||||
};
|
||||
|
||||
if (saveFileDialog.ShowDialog() != DialogResult.OK)
|
||||
return;
|
||||
|
||||
// FilterIndex is 1-based, NOT 0-based
|
||||
switch (saveFileDialog.FilterIndex)
|
||||
{
|
||||
case 1: // xlsx
|
||||
default:
|
||||
LibraryExporter.ToXlsx(saveFileDialog.FileName);
|
||||
break;
|
||||
case 2: // csv
|
||||
LibraryExporter.ToCsv(saveFileDialog.FileName);
|
||||
break;
|
||||
case 3: // json
|
||||
LibraryExporter.ToJson(saveFileDialog.FileName);
|
||||
break;
|
||||
}
|
||||
|
||||
MessageBox.Show("Library exported to:\r\n" + saveFileDialog.FileName);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
MessageBoxLib.ShowAdminAlert("Error attempting to export your library.", "Error exporting", ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
45
Source/LibationWinForms/Form1.Filter.cs
Normal file
45
Source/LibationWinForms/Form1.Filter.cs
Normal file
@@ -0,0 +1,45 @@
|
||||
using System;
|
||||
using System.Windows.Forms;
|
||||
using LibationWinForms.Dialogs;
|
||||
|
||||
namespace LibationWinForms
|
||||
{
|
||||
public partial class Form1
|
||||
{
|
||||
protected void Configure_Filter() { }
|
||||
|
||||
private void filterHelpBtn_Click(object sender, EventArgs e) => new SearchSyntaxDialog().ShowDialog();
|
||||
|
||||
private void filterSearchTb_KeyPress(object sender, KeyPressEventArgs e)
|
||||
{
|
||||
if (e.KeyChar == (char)Keys.Return)
|
||||
{
|
||||
performFilter(this.filterSearchTb.Text);
|
||||
|
||||
// silence the 'ding'
|
||||
e.Handled = true;
|
||||
}
|
||||
}
|
||||
|
||||
private void filterBtn_Click(object sender, EventArgs e) => performFilter(this.filterSearchTb.Text);
|
||||
|
||||
private string lastGoodFilter = "";
|
||||
private void performFilter(string filterString)
|
||||
{
|
||||
this.filterSearchTb.Text = filterString;
|
||||
|
||||
try
|
||||
{
|
||||
productsGrid.Filter(filterString);
|
||||
lastGoodFilter = filterString;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
MessageBox.Show($"Bad filter string:\r\n\r\n{ex.Message}", "Bad filter string", MessageBoxButtons.OK, MessageBoxIcon.Error);
|
||||
|
||||
// re-apply last good filter
|
||||
performFilter(lastGoodFilter);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
46
Source/LibationWinForms/Form1.Liberate.cs
Normal file
46
Source/LibationWinForms/Form1.Liberate.cs
Normal file
@@ -0,0 +1,46 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Forms;
|
||||
|
||||
namespace LibationWinForms
|
||||
{
|
||||
public partial class Form1
|
||||
{
|
||||
private void Configure_Liberate() { }
|
||||
|
||||
//GetLibrary_Flat_NoTracking() may take a long time on a hugh library. so run in new thread
|
||||
private async void beginBookBackupsToolStripMenuItem_Click(object sender, EventArgs e)
|
||||
{
|
||||
SetQueueCollapseState(false);
|
||||
await Task.Run(() => processBookQueue1.AddDownloadDecrypt(ApplicationServices.DbContexts.GetLibrary_Flat_NoTracking()
|
||||
.Where(lb => lb.Book.UserDefinedItem.PdfStatus is DataLayer.LiberatedStatus.NotLiberated || lb.Book.UserDefinedItem.BookStatus is DataLayer.LiberatedStatus.NotLiberated)));
|
||||
}
|
||||
|
||||
private async void beginPdfBackupsToolStripMenuItem_Click(object sender, EventArgs e)
|
||||
{
|
||||
SetQueueCollapseState(false);
|
||||
await Task.Run(() => processBookQueue1.AddDownloadPdf(ApplicationServices.DbContexts.GetLibrary_Flat_NoTracking()
|
||||
.Where(lb => lb.Book.UserDefinedItem.PdfStatus is DataLayer.LiberatedStatus.NotLiberated)));
|
||||
}
|
||||
|
||||
private async void convertAllM4bToMp3ToolStripMenuItem_Click(object sender, EventArgs e)
|
||||
{
|
||||
var result = MessageBox.Show(
|
||||
"This converts all m4b titles in your library to mp3 files. Original files are not deleted."
|
||||
+ "\r\nFor large libraries this will take a long time and will take up more disk space."
|
||||
+ "\r\n\r\nContinue?"
|
||||
+ "\r\n\r\n(To always download titles as mp3 instead of m4b, go to Settings: Download my books as .MP3 files)",
|
||||
"Convert all M4b => Mp3?",
|
||||
MessageBoxButtons.YesNo,
|
||||
MessageBoxIcon.Warning);
|
||||
if (result == DialogResult.Yes)
|
||||
{
|
||||
SetQueueCollapseState(false);
|
||||
await Task.Run(() => processBookQueue1.AddConvertMp3(ApplicationServices.DbContexts.GetLibrary_Flat_NoTracking()
|
||||
.Where(lb => lb.Book.UserDefinedItem.BookStatus is DataLayer.LiberatedStatus.Liberated)));
|
||||
}
|
||||
//Only Queue Liberated books for conversion. This isn't a perfect filter, but it's better than nothing.
|
||||
}
|
||||
}
|
||||
}
|
||||
17
Source/LibationWinForms/Form1.PictureStorage.cs
Normal file
17
Source/LibationWinForms/Form1.PictureStorage.cs
Normal file
@@ -0,0 +1,17 @@
|
||||
using Dinah.Core.Drawing;
|
||||
using LibationFileManager;
|
||||
|
||||
namespace LibationWinForms
|
||||
{
|
||||
public partial class Form1
|
||||
{
|
||||
private void Configure_PictureStorage()
|
||||
{
|
||||
// init default/placeholder cover art
|
||||
var format = System.Drawing.Imaging.ImageFormat.Jpeg;
|
||||
PictureStorage.SetDefaultImage(PictureSize._80x80, Properties.Resources.default_cover_80x80.ToBytes(format));
|
||||
PictureStorage.SetDefaultImage(PictureSize._300x300, Properties.Resources.default_cover_300x300.ToBytes(format));
|
||||
PictureStorage.SetDefaultImage(PictureSize._500x500, Properties.Resources.default_cover_500x500.ToBytes(format));
|
||||
}
|
||||
}
|
||||
}
|
||||
110
Source/LibationWinForms/Form1.ProcessQueue.cs
Normal file
110
Source/LibationWinForms/Form1.ProcessQueue.cs
Normal file
@@ -0,0 +1,110 @@
|
||||
using DataLayer;
|
||||
using Dinah.Core;
|
||||
using LibationFileManager;
|
||||
using LibationWinForms.ProcessQueue;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Windows.Forms;
|
||||
|
||||
namespace LibationWinForms
|
||||
{
|
||||
public partial class Form1
|
||||
{
|
||||
|
||||
int WidthChange = 0;
|
||||
private void Configure_ProcessQueue()
|
||||
{
|
||||
productsGrid.LiberateClicked += ProductsGrid_LiberateClicked;
|
||||
processBookQueue1.popoutBtn.Click += ProcessBookQueue1_PopOut;
|
||||
var coppalseState = Configuration.Instance.GetNonString<bool>(nameof(splitContainer1.Panel2Collapsed));
|
||||
WidthChange = splitContainer1.Panel2.Width + splitContainer1.SplitterWidth;
|
||||
SetQueueCollapseState(coppalseState);
|
||||
}
|
||||
|
||||
private void ProductsGrid_LiberateClicked(object sender, LibraryBook e)
|
||||
{
|
||||
if (e.Book.Audio_Exists())
|
||||
{
|
||||
// liberated: open explorer to file
|
||||
var filePath = AudibleFileStorage.Audio.GetPath(e.Book.AudibleProductId);
|
||||
if (!Go.To.File(filePath))
|
||||
{
|
||||
var suffix = string.IsNullOrWhiteSpace(filePath) ? "" : $":\r\n{filePath}";
|
||||
MessageBox.Show($"File not found" + suffix);
|
||||
}
|
||||
return;
|
||||
}
|
||||
else if (e.Book.UserDefinedItem.BookStatus != LiberatedStatus.Liberated)
|
||||
{
|
||||
SetQueueCollapseState(false);
|
||||
processBookQueue1.AddDownloadDecrypt(e);
|
||||
}
|
||||
else if (e.Book.UserDefinedItem.PdfStatus is not null and LiberatedStatus.NotLiberated)
|
||||
{
|
||||
SetQueueCollapseState(false);
|
||||
processBookQueue1.AddDownloadPdf(e);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private void SetQueueCollapseState(bool collapsed)
|
||||
{
|
||||
if (collapsed && !splitContainer1.Panel2Collapsed)
|
||||
{
|
||||
WidthChange = splitContainer1.Panel2.Width + splitContainer1.SplitterWidth;
|
||||
splitContainer1.Panel2.Controls.Remove(processBookQueue1);
|
||||
splitContainer1.Panel2Collapsed = true;
|
||||
Width -= WidthChange;
|
||||
}
|
||||
else if (!collapsed && splitContainer1.Panel2Collapsed)
|
||||
{
|
||||
Width += WidthChange;
|
||||
splitContainer1.Panel2.Controls.Add(processBookQueue1);
|
||||
splitContainer1.Panel2Collapsed = false;
|
||||
processBookQueue1.popoutBtn.Visible = true;
|
||||
}
|
||||
toggleQueueHideBtn.Text = splitContainer1.Panel2Collapsed ? "❰❰❰" : "❱❱❱";
|
||||
}
|
||||
|
||||
private void ToggleQueueHideBtn_Click(object sender, EventArgs e)
|
||||
{
|
||||
SetQueueCollapseState(!splitContainer1.Panel2Collapsed);
|
||||
Configuration.Instance.SetObject(nameof(splitContainer1.Panel2Collapsed), splitContainer1.Panel2Collapsed);
|
||||
}
|
||||
|
||||
private void ProcessBookQueue1_PopOut(object sender, EventArgs e)
|
||||
{
|
||||
ProcessBookForm dockForm = new();
|
||||
dockForm.WidthChange = splitContainer1.Panel2.Width + splitContainer1.SplitterWidth;
|
||||
dockForm.RestoreSizeAndLocation(Configuration.Instance);
|
||||
dockForm.FormClosing += DockForm_FormClosing;
|
||||
splitContainer1.Panel2.Controls.Remove(processBookQueue1);
|
||||
splitContainer1.Panel2Collapsed = true;
|
||||
processBookQueue1.popoutBtn.Visible = false;
|
||||
dockForm.PassControl(processBookQueue1);
|
||||
dockForm.Show();
|
||||
this.Width -= dockForm.WidthChange;
|
||||
toggleQueueHideBtn.Visible = false;
|
||||
int deltax = filterBtn.Margin.Right + toggleQueueHideBtn.Width + toggleQueueHideBtn.Margin.Left;
|
||||
filterBtn.Location= new System.Drawing.Point(filterBtn.Location.X + deltax, filterBtn.Location.Y);
|
||||
filterSearchTb.Location = new System.Drawing.Point(filterSearchTb.Location.X + deltax, filterSearchTb.Location.Y);
|
||||
}
|
||||
|
||||
private void DockForm_FormClosing(object sender, FormClosingEventArgs e)
|
||||
{
|
||||
if (sender is ProcessBookForm dockForm)
|
||||
{
|
||||
this.Width += dockForm.WidthChange;
|
||||
splitContainer1.Panel2.Controls.Add(dockForm.RegainControl());
|
||||
splitContainer1.Panel2Collapsed = false;
|
||||
processBookQueue1.popoutBtn.Visible = true;
|
||||
dockForm.SaveSizeAndLocation(Configuration.Instance);
|
||||
this.Focus();
|
||||
toggleQueueHideBtn.Visible = true;
|
||||
int deltax = filterBtn.Margin.Right + toggleQueueHideBtn.Width + toggleQueueHideBtn.Margin.Left;
|
||||
filterBtn.Location = new System.Drawing.Point(filterBtn.Location.X - deltax, filterBtn.Location.Y);
|
||||
filterSearchTb.Location = new System.Drawing.Point(filterSearchTb.Location.X - deltax, filterSearchTb.Location.Y);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
60
Source/LibationWinForms/Form1.QuickFilters.cs
Normal file
60
Source/LibationWinForms/Form1.QuickFilters.cs
Normal file
@@ -0,0 +1,60 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Windows.Forms;
|
||||
using LibationFileManager;
|
||||
using LibationWinForms.Dialogs;
|
||||
|
||||
namespace LibationWinForms
|
||||
{
|
||||
public partial class Form1
|
||||
{
|
||||
private void Configure_QuickFilters()
|
||||
{
|
||||
Load += updateFirstFilterIsDefaultToolStripMenuItem;
|
||||
Load += updateFiltersMenu;
|
||||
QuickFilters.UseDefaultChanged += updateFirstFilterIsDefaultToolStripMenuItem;
|
||||
QuickFilters.Updated += updateFiltersMenu;
|
||||
|
||||
productsGrid.InitialLoaded += (_, __) =>
|
||||
{
|
||||
if (QuickFilters.UseDefault)
|
||||
performFilter(QuickFilters.Filters.FirstOrDefault());
|
||||
};
|
||||
}
|
||||
|
||||
private object quickFilterTag { get; } = new();
|
||||
private void updateFiltersMenu(object _ = null, object __ = null)
|
||||
{
|
||||
// remove old
|
||||
var removeUs = quickFiltersToolStripMenuItem.DropDownItems
|
||||
.Cast<ToolStripItem>()
|
||||
.Where(item => item.Tag == quickFilterTag)
|
||||
.ToList();
|
||||
foreach (var item in removeUs)
|
||||
quickFiltersToolStripMenuItem.DropDownItems.Remove(item);
|
||||
|
||||
// re-populate
|
||||
var index = 0;
|
||||
foreach (var filter in QuickFilters.Filters)
|
||||
{
|
||||
var quickFilterMenuItem = new ToolStripMenuItem
|
||||
{
|
||||
Tag = quickFilterTag,
|
||||
Text = $"&{++index}: {filter}"
|
||||
};
|
||||
quickFilterMenuItem.Click += (_, __) => performFilter(filter);
|
||||
quickFiltersToolStripMenuItem.DropDownItems.Add(quickFilterMenuItem);
|
||||
}
|
||||
}
|
||||
|
||||
private void updateFirstFilterIsDefaultToolStripMenuItem(object sender, EventArgs e)
|
||||
=> firstFilterIsDefaultToolStripMenuItem.Checked = QuickFilters.UseDefault;
|
||||
|
||||
private void firstFilterIsDefaultToolStripMenuItem_Click(object sender, EventArgs e)
|
||||
=> QuickFilters.UseDefault = !firstFilterIsDefaultToolStripMenuItem.Checked;
|
||||
|
||||
private void addQuickFilterBtn_Click(object sender, EventArgs e) => QuickFilters.Add(this.filterSearchTb.Text);
|
||||
|
||||
private void editQuickFiltersToolStripMenuItem_Click(object sender, EventArgs e) => new EditQuickFilters().ShowDialog();
|
||||
}
|
||||
}
|
||||
86
Source/LibationWinForms/Form1.ScanAuto.cs
Normal file
86
Source/LibationWinForms/Form1.ScanAuto.cs
Normal file
@@ -0,0 +1,86 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using ApplicationServices;
|
||||
using AudibleUtilities;
|
||||
using Dinah.Core;
|
||||
using LibationFileManager;
|
||||
|
||||
namespace LibationWinForms
|
||||
{
|
||||
// This is for the auto-scanner. It is unrelated to manual scanning/import
|
||||
public partial class Form1
|
||||
{
|
||||
private InterruptableTimer autoScanTimer;
|
||||
|
||||
private void Configure_ScanAuto()
|
||||
{
|
||||
// creating InterruptableTimer inside 'Configure_' is a break from the pattern. As long as no one else needs to access or subscribe to it, this is ok
|
||||
var hours = 0;
|
||||
var minutes = 5;
|
||||
var seconds = 0;
|
||||
var _5_minutes = new TimeSpan(hours, minutes, seconds);
|
||||
autoScanTimer = new InterruptableTimer(_5_minutes);
|
||||
|
||||
// subscribe as async/non-blocking. I'd actually rather prefer blocking but real-world testing found that caused a deadlock in the AudibleAPI
|
||||
autoScanTimer.Elapsed += async (_, __) =>
|
||||
{
|
||||
using var persister = AudibleApiStorage.GetAccountsSettingsPersister();
|
||||
var accounts = persister.AccountsSettings
|
||||
.GetAll()
|
||||
.Where(a => a.LibraryScan)
|
||||
.ToArray();
|
||||
|
||||
// in autoScan, new books SHALL NOT show dialog
|
||||
await Invoke(async () => await LibraryCommands.ImportAccountAsync(Login.WinformLoginChoiceEager.ApiExtendedFunc, accounts));
|
||||
};
|
||||
|
||||
// load init state to menu checkbox
|
||||
Load += updateAutoScanLibraryToolStripMenuItem;
|
||||
// if enabled: begin on load
|
||||
Load += startAutoScan;
|
||||
|
||||
// if new 'default' account is added, run autoscan
|
||||
AccountsSettingsPersister.Saving += accountsPreSave;
|
||||
AccountsSettingsPersister.Saved += accountsPostSave;
|
||||
|
||||
// when autoscan setting is changed, update menu checkbox and run autoscan
|
||||
Configuration.Instance.AutoScanChanged += updateAutoScanLibraryToolStripMenuItem;
|
||||
Configuration.Instance.AutoScanChanged += startAutoScan;
|
||||
}
|
||||
|
||||
private List<(string AccountId, string LocaleName)> preSaveDefaultAccounts;
|
||||
private List<(string AccountId, string LocaleName)> getDefaultAccounts()
|
||||
{
|
||||
using var persister = AudibleApiStorage.GetAccountsSettingsPersister();
|
||||
return persister.AccountsSettings
|
||||
.GetAll()
|
||||
.Where(a => a.LibraryScan)
|
||||
.Select(a => (a.AccountId, a.Locale.Name))
|
||||
.ToList();
|
||||
}
|
||||
private void accountsPreSave(object sender = null, EventArgs e = null)
|
||||
=> preSaveDefaultAccounts = getDefaultAccounts();
|
||||
private void accountsPostSave(object sender = null, EventArgs e = null)
|
||||
{
|
||||
var postSaveDefaultAccounts = getDefaultAccounts();
|
||||
var newDefaultAccounts = postSaveDefaultAccounts.Except(preSaveDefaultAccounts).ToList();
|
||||
|
||||
if (newDefaultAccounts.Any())
|
||||
startAutoScan();
|
||||
}
|
||||
|
||||
private void startAutoScan(object sender = null, EventArgs e = null)
|
||||
{
|
||||
if (Configuration.Instance.AutoScan)
|
||||
autoScanTimer.PerformNow();
|
||||
else
|
||||
autoScanTimer.Stop();
|
||||
}
|
||||
|
||||
private void updateAutoScanLibraryToolStripMenuItem(object sender, EventArgs e) => autoScanLibraryToolStripMenuItem.Checked = Configuration.Instance.AutoScan;
|
||||
|
||||
private void autoScanLibraryToolStripMenuItem_Click(object sender, EventArgs e) => Configuration.Instance.AutoScan = !autoScanLibraryToolStripMenuItem.Checked;
|
||||
}
|
||||
}
|
||||
135
Source/LibationWinForms/Form1.ScanManual.cs
Normal file
135
Source/LibationWinForms/Form1.ScanManual.cs
Normal file
@@ -0,0 +1,135 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Forms;
|
||||
using ApplicationServices;
|
||||
using AudibleUtilities;
|
||||
using LibationFileManager;
|
||||
using LibationWinForms.Dialogs;
|
||||
|
||||
namespace LibationWinForms
|
||||
{
|
||||
// this is for manual scan/import. Unrelated to auto-scan
|
||||
public partial class Form1
|
||||
{
|
||||
private void Configure_ScanManual()
|
||||
{
|
||||
this.Load += refreshImportMenu;
|
||||
AccountsSettingsPersister.Saved += refreshImportMenu;
|
||||
}
|
||||
|
||||
private void refreshImportMenu(object _, EventArgs __)
|
||||
{
|
||||
using var persister = AudibleApiStorage.GetAccountsSettingsPersister();
|
||||
var count = persister.AccountsSettings.Accounts.Count;
|
||||
|
||||
autoScanLibraryToolStripMenuItem.Visible = count > 0;
|
||||
|
||||
noAccountsYetAddAccountToolStripMenuItem.Visible = count == 0;
|
||||
scanLibraryToolStripMenuItem.Visible = count == 1;
|
||||
scanLibraryOfAllAccountsToolStripMenuItem.Visible = count > 1;
|
||||
scanLibraryOfSomeAccountsToolStripMenuItem.Visible = count > 1;
|
||||
|
||||
removeLibraryBooksToolStripMenuItem.Visible = count > 0;
|
||||
removeSomeAccountsToolStripMenuItem.Visible = count > 1;
|
||||
removeAllAccountsToolStripMenuItem.Visible = count > 1;
|
||||
}
|
||||
|
||||
private void noAccountsYetAddAccountToolStripMenuItem_Click(object sender, EventArgs e)
|
||||
{
|
||||
MessageBox.Show("To load your Audible library, come back here to the Import menu after adding your account");
|
||||
new AccountsDialog().ShowDialog();
|
||||
}
|
||||
|
||||
private async void scanLibraryToolStripMenuItem_Click(object sender, EventArgs e)
|
||||
{
|
||||
using var persister = AudibleApiStorage.GetAccountsSettingsPersister();
|
||||
var firstAccount = persister.AccountsSettings.GetAll().FirstOrDefault();
|
||||
await scanLibrariesAsync(firstAccount);
|
||||
}
|
||||
|
||||
private async void scanLibraryOfAllAccountsToolStripMenuItem_Click(object sender, EventArgs e)
|
||||
{
|
||||
using var persister = AudibleApiStorage.GetAccountsSettingsPersister();
|
||||
var allAccounts = persister.AccountsSettings.GetAll();
|
||||
await scanLibrariesAsync(allAccounts);
|
||||
}
|
||||
|
||||
private async void scanLibraryOfSomeAccountsToolStripMenuItem_Click(object sender, EventArgs e)
|
||||
{
|
||||
using var scanAccountsDialog = new ScanAccountsDialog();
|
||||
|
||||
if (scanAccountsDialog.ShowDialog() != DialogResult.OK)
|
||||
return;
|
||||
|
||||
if (!scanAccountsDialog.CheckedAccounts.Any())
|
||||
return;
|
||||
|
||||
await scanLibrariesAsync(scanAccountsDialog.CheckedAccounts);
|
||||
}
|
||||
|
||||
private void removeLibraryBooksToolStripMenuItem_Click(object sender, EventArgs e)
|
||||
{
|
||||
// if 0 accounts, this will not be visible
|
||||
// if 1 account, run scanLibrariesRemovedBooks() on this account
|
||||
// if multiple accounts, another menu set will open. do not run scanLibrariesRemovedBooks()
|
||||
using var persister = AudibleApiStorage.GetAccountsSettingsPersister();
|
||||
var accounts = persister.AccountsSettings.GetAll();
|
||||
|
||||
if (accounts.Count != 1)
|
||||
return;
|
||||
|
||||
var firstAccount = accounts.Single();
|
||||
scanLibrariesRemovedBooks(firstAccount);
|
||||
}
|
||||
|
||||
// selectively remove books from all accounts
|
||||
private void removeAllAccountsToolStripMenuItem_Click(object sender, EventArgs e)
|
||||
{
|
||||
using var persister = AudibleApiStorage.GetAccountsSettingsPersister();
|
||||
var allAccounts = persister.AccountsSettings.GetAll();
|
||||
scanLibrariesRemovedBooks(allAccounts.ToArray());
|
||||
}
|
||||
|
||||
// selectively remove books from some accounts
|
||||
private void removeSomeAccountsToolStripMenuItem_Click(object sender, EventArgs e)
|
||||
{
|
||||
using var scanAccountsDialog = new ScanAccountsDialog();
|
||||
|
||||
if (scanAccountsDialog.ShowDialog() != DialogResult.OK)
|
||||
return;
|
||||
|
||||
if (!scanAccountsDialog.CheckedAccounts.Any())
|
||||
return;
|
||||
|
||||
scanLibrariesRemovedBooks(scanAccountsDialog.CheckedAccounts.ToArray());
|
||||
}
|
||||
|
||||
private void scanLibrariesRemovedBooks(params Account[] accounts)
|
||||
{
|
||||
using var dialog = new RemoveBooksDialog(accounts);
|
||||
dialog.ShowDialog();
|
||||
}
|
||||
|
||||
private async Task scanLibrariesAsync(IEnumerable<Account> accounts) => await scanLibrariesAsync(accounts.ToArray());
|
||||
private async Task scanLibrariesAsync(params Account[] accounts)
|
||||
{
|
||||
try
|
||||
{
|
||||
var (totalProcessed, newAdded) = await LibraryCommands.ImportAccountAsync(Login.WinformLoginChoiceEager.ApiExtendedFunc, accounts);
|
||||
|
||||
// this is here instead of ScanEnd so that the following is only possible when it's user-initiated, not automatic loop
|
||||
if (Configuration.Instance.ShowImportedStats && newAdded > 0)
|
||||
MessageBox.Show($"Total processed: {totalProcessed}\r\nNew: {newAdded}");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
MessageBoxLib.ShowAdminAlert(
|
||||
"Error importing library. Please try again. If this still happens after 2 or 3 tries, stop and contact administrator",
|
||||
"Error importing library",
|
||||
ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
37
Source/LibationWinForms/Form1.ScanNotification.cs
Normal file
37
Source/LibationWinForms/Form1.ScanNotification.cs
Normal file
@@ -0,0 +1,37 @@
|
||||
using System;
|
||||
using ApplicationServices;
|
||||
|
||||
namespace LibationWinForms
|
||||
{
|
||||
// This is for the Scanning notificationin the upper right. This shown for manual scanning and auto-scan
|
||||
public partial class Form1
|
||||
{
|
||||
private void Configure_ScanNotification()
|
||||
{
|
||||
LibraryCommands.ScanBegin += LibraryCommands_ScanBegin;
|
||||
LibraryCommands.ScanEnd += LibraryCommands_ScanEnd;
|
||||
}
|
||||
|
||||
private void LibraryCommands_ScanBegin(object sender, int accountsLength)
|
||||
{
|
||||
scanLibraryToolStripMenuItem.Enabled = false;
|
||||
scanLibraryOfAllAccountsToolStripMenuItem.Enabled = false;
|
||||
scanLibraryOfSomeAccountsToolStripMenuItem.Enabled = false;
|
||||
|
||||
this.scanningToolStripMenuItem.Visible = true;
|
||||
this.scanningToolStripMenuItem.Text
|
||||
= (accountsLength == 1)
|
||||
? "Scanning..."
|
||||
: $"Scanning {accountsLength} accounts...";
|
||||
}
|
||||
|
||||
private void LibraryCommands_ScanEnd(object sender, EventArgs e)
|
||||
{
|
||||
scanLibraryToolStripMenuItem.Enabled = true;
|
||||
scanLibraryOfAllAccountsToolStripMenuItem.Enabled = true;
|
||||
scanLibraryOfSomeAccountsToolStripMenuItem.Enabled = true;
|
||||
|
||||
this.scanningToolStripMenuItem.Visible = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
18
Source/LibationWinForms/Form1.Settings.cs
Normal file
18
Source/LibationWinForms/Form1.Settings.cs
Normal file
@@ -0,0 +1,18 @@
|
||||
using System;
|
||||
using System.Windows.Forms;
|
||||
using LibationWinForms.Dialogs;
|
||||
|
||||
namespace LibationWinForms
|
||||
{
|
||||
public partial class Form1
|
||||
{
|
||||
private void Configure_Settings() { }
|
||||
|
||||
private void accountsToolStripMenuItem_Click(object sender, EventArgs e) => new AccountsDialog().ShowDialog();
|
||||
|
||||
private void basicSettingsToolStripMenuItem_Click(object sender, EventArgs e) => new SettingsDialog().ShowDialog();
|
||||
|
||||
private void aboutToolStripMenuItem_Click(object sender, EventArgs e)
|
||||
=> MessageBox.Show($"Running Libation version {AppScaffolding.LibationScaffolding.BuildVersion}", $"Libation v{AppScaffolding.LibationScaffolding.BuildVersion}");
|
||||
}
|
||||
}
|
||||
129
Source/LibationWinForms/Form1.VisibleBooks.cs
Normal file
129
Source/LibationWinForms/Form1.VisibleBooks.cs
Normal file
@@ -0,0 +1,129 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Forms;
|
||||
using ApplicationServices;
|
||||
using Dinah.Core.Threading;
|
||||
using LibationWinForms.Dialogs;
|
||||
|
||||
namespace LibationWinForms
|
||||
{
|
||||
public partial class Form1
|
||||
{
|
||||
protected void Configure_VisibleBooks()
|
||||
{
|
||||
// init formattable
|
||||
visibleCountLbl.Format(0);
|
||||
liberateVisibleToolStripMenuItem.Format(0);
|
||||
liberateVisible2ToolStripMenuItem.Format(0);
|
||||
|
||||
// bottom-left visible count
|
||||
productsGrid.VisibleCountChanged += (_, qty) => visibleCountLbl.Format(qty);
|
||||
|
||||
// top menu strip
|
||||
visibleBooksToolStripMenuItem.Format(0);
|
||||
productsGrid.VisibleCountChanged += (_, qty) => {
|
||||
visibleBooksToolStripMenuItem.Format(qty);
|
||||
visibleBooksToolStripMenuItem.Enabled = qty > 0;
|
||||
|
||||
var notLiberatedCount = productsGrid.GetVisible().Count(lb => lb.Book.UserDefinedItem.BookStatus == DataLayer.LiberatedStatus.NotLiberated);
|
||||
};
|
||||
|
||||
productsGrid.VisibleCountChanged += setLiberatedVisibleMenuItemAsync;
|
||||
LibraryCommands.BookUserDefinedItemCommitted += setLiberatedVisibleMenuItemAsync;
|
||||
}
|
||||
private async void setLiberatedVisibleMenuItemAsync(object _, int __)
|
||||
=> await Task.Run(setLiberatedVisibleMenuItem);
|
||||
private async void setLiberatedVisibleMenuItemAsync(object _, EventArgs __)
|
||||
=> await Task.Run(setLiberatedVisibleMenuItem);
|
||||
void setLiberatedVisibleMenuItem()
|
||||
{
|
||||
var notLiberated = productsGrid.GetVisible().Count(lb => lb.Book.UserDefinedItem.BookStatus == DataLayer.LiberatedStatus.NotLiberated);
|
||||
this.UIThreadSync(() =>
|
||||
{
|
||||
if (notLiberated > 0)
|
||||
{
|
||||
liberateVisibleToolStripMenuItem.Format(notLiberated);
|
||||
liberateVisibleToolStripMenuItem.Enabled = true;
|
||||
|
||||
liberateVisible2ToolStripMenuItem.Format(notLiberated);
|
||||
liberateVisible2ToolStripMenuItem.Enabled = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
liberateVisibleToolStripMenuItem.Text = "All visible books are liberated";
|
||||
liberateVisibleToolStripMenuItem.Enabled = false;
|
||||
|
||||
liberateVisible2ToolStripMenuItem.Text = "All visible books are liberated";
|
||||
liberateVisible2ToolStripMenuItem.Enabled = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private async void liberateVisible(object sender, EventArgs e)
|
||||
{
|
||||
SetQueueCollapseState(false);
|
||||
await Task.Run(() => processBookQueue1.AddDownloadDecrypt(productsGrid.GetVisible()));
|
||||
}
|
||||
|
||||
private void replaceTagsToolStripMenuItem_Click(object sender, EventArgs e)
|
||||
{
|
||||
var dialog = new TagsBatchDialog();
|
||||
var result = dialog.ShowDialog();
|
||||
if (result != DialogResult.OK)
|
||||
return;
|
||||
|
||||
var visibleLibraryBooks = productsGrid.GetVisible();
|
||||
|
||||
var confirmationResult = MessageBoxLib.ShowConfirmationDialog(
|
||||
visibleLibraryBooks,
|
||||
$"Are you sure you want to replace tags in {0}?",
|
||||
"Replace tags?");
|
||||
|
||||
if (confirmationResult != DialogResult.Yes)
|
||||
return;
|
||||
|
||||
foreach (var libraryBook in visibleLibraryBooks)
|
||||
libraryBook.Book.UserDefinedItem.Tags = dialog.NewTags;
|
||||
LibraryCommands.UpdateUserDefinedItem(visibleLibraryBooks.Select(lb => lb.Book));
|
||||
}
|
||||
|
||||
private void setDownloadedToolStripMenuItem_Click(object sender, EventArgs e)
|
||||
{
|
||||
var dialog = new LiberatedStatusBatchDialog();
|
||||
var result = dialog.ShowDialog();
|
||||
if (result != DialogResult.OK)
|
||||
return;
|
||||
|
||||
var visibleLibraryBooks = productsGrid.GetVisible();
|
||||
|
||||
var confirmationResult = MessageBoxLib.ShowConfirmationDialog(
|
||||
visibleLibraryBooks,
|
||||
$"Are you sure you want to replace downloaded status in {0}?",
|
||||
"Replace downloaded status?");
|
||||
|
||||
if (confirmationResult != DialogResult.Yes)
|
||||
return;
|
||||
|
||||
foreach (var libraryBook in visibleLibraryBooks)
|
||||
libraryBook.Book.UserDefinedItem.BookStatus = dialog.BookLiberatedStatus;
|
||||
LibraryCommands.UpdateUserDefinedItem(visibleLibraryBooks.Select(lb => lb.Book));
|
||||
}
|
||||
|
||||
private async void removeToolStripMenuItem_Click(object sender, EventArgs e)
|
||||
{
|
||||
var visibleLibraryBooks = productsGrid.GetVisible();
|
||||
|
||||
var confirmationResult = MessageBoxLib.ShowConfirmationDialog(
|
||||
visibleLibraryBooks,
|
||||
$"Are you sure you want to remove {0} from Libation's library?",
|
||||
"Remove books from Libation?");
|
||||
|
||||
if (confirmationResult != DialogResult.Yes)
|
||||
return;
|
||||
|
||||
var visibleIds = visibleLibraryBooks.Select(lb => lb.Book.AudibleProductId).ToList();
|
||||
await LibraryCommands.RemoveBooksAsync(visibleIds);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4,24 +4,14 @@ using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Forms;
|
||||
using ApplicationServices;
|
||||
using AudibleUtilities;
|
||||
using Dinah.Core;
|
||||
using Dinah.Core.Drawing;
|
||||
using Dinah.Core.Threading;
|
||||
using LibationFileManager;
|
||||
using LibationWinForms.Dialogs;
|
||||
|
||||
namespace LibationWinForms
|
||||
{
|
||||
public partial class Form1 : Form
|
||||
{
|
||||
private string beginBookBackupsToolStripMenuItem_format { get; }
|
||||
private string beginPdfBackupsToolStripMenuItem_format { get; }
|
||||
|
||||
private string visibleBooksToolStripMenuItem_format { get; }
|
||||
private string liberateVisibleToolStripMenuItem_format { get; }
|
||||
private string liberateVisible2ToolStripMenuItem_format { get; }
|
||||
|
||||
private ProductsGrid productsGrid { get; }
|
||||
|
||||
public Form1()
|
||||
@@ -31,52 +21,55 @@ namespace LibationWinForms
|
||||
if (this.DesignMode)
|
||||
return;
|
||||
|
||||
productsGrid = new ProductsGrid { Dock = DockStyle.Fill };
|
||||
productsGrid.VisibleCountChanged += (_, qty) => visibleCountLbl.Text = string.Format("Visible: {0}", qty);
|
||||
gridPanel.Controls.Add(productsGrid);
|
||||
this.Load += (_, __) =>
|
||||
{
|
||||
productsGrid.Display();
|
||||
// I'd actually like these lines to be handled in the designer, but I'm currently getting this error when I try:
|
||||
// Failed to create component 'ProductsGrid'. The error message follows:
|
||||
// 'Microsoft.DotNet.DesignTools.Client.DesignToolsServerException: Object reference not set to an instance of an object.
|
||||
// Since the designer's choking on it, I'm keeping it below the DesignMode check to be safe
|
||||
productsGrid = new ProductsGrid { Dock = DockStyle.Fill };
|
||||
gridPanel.Controls.Add(productsGrid);
|
||||
}
|
||||
|
||||
// also applies filter. ONLY call AFTER loading grid
|
||||
loadInitialQuickFilterState();
|
||||
};
|
||||
// Pre-requisite:
|
||||
// Before calling anything else, including subscribing to events, ensure database exists. If we wait and let it happen lazily, race conditions and errors are likely during new installs
|
||||
using var _ = DbContexts.GetContext();
|
||||
|
||||
// back up string formats
|
||||
beginBookBackupsToolStripMenuItem_format = beginBookBackupsToolStripMenuItem.Text;
|
||||
beginPdfBackupsToolStripMenuItem_format = beginPdfBackupsToolStripMenuItem.Text;
|
||||
visibleBooksToolStripMenuItem_format = visibleBooksToolStripMenuItem.Text;
|
||||
liberateVisibleToolStripMenuItem_format = liberateVisibleToolStripMenuItem.Text;
|
||||
liberateVisible2ToolStripMenuItem_format = liberateVisible2ToolStripMenuItem.Text;
|
||||
|
||||
// independent UI updates
|
||||
this.Load += (_, _) => this.RestoreSizeAndLocation(Configuration.Instance);
|
||||
this.FormClosing += (_, _) => this.SaveSizeAndLocation(Configuration.Instance);
|
||||
LibraryCommands.LibrarySizeChanged += (_, __) =>
|
||||
|
||||
// this looks like a perfect opportunity to refactor per below.
|
||||
// since this loses design-time tooling and internal access, for now I'm opting for partial classes
|
||||
// var modules = new ConfigurableModuleBase[]
|
||||
// {
|
||||
// new PictureStorageModule(),
|
||||
// new BackupCountsModule(),
|
||||
// new VisibleBooksModule(),
|
||||
// // ...
|
||||
// };
|
||||
// foreach(ConfigurableModuleBase m in modules)
|
||||
// m.Configure(this);
|
||||
|
||||
// these should do nothing interesting yet (storing simple var, subscribe to events) and should never rely on each other for order.
|
||||
// otherwise, order could be an issue.
|
||||
// eg: if one of these init'd productsGrid, then another can't reliably subscribe to it
|
||||
Configure_PictureStorage();
|
||||
Configure_BackupCounts();
|
||||
Configure_ScanAuto();
|
||||
Configure_ScanNotification();
|
||||
Configure_VisibleBooks();
|
||||
Configure_QuickFilters();
|
||||
Configure_ScanManual();
|
||||
Configure_Liberate();
|
||||
Configure_Export();
|
||||
Configure_Settings();
|
||||
Configure_ProcessQueue();
|
||||
Configure_Filter();
|
||||
|
||||
// Configure_Grid(); // since it's just this, can keep here. If it needs more, then give grid it's own 'partial class Form1'
|
||||
{
|
||||
this.UIThreadSync(() => productsGrid.Display());
|
||||
this.UIThreadAsync(() => doFilter(lastGoodFilter));
|
||||
};
|
||||
LibraryCommands.LibrarySizeChanged += setBackupCounts;
|
||||
this.Load += setBackupCounts;
|
||||
LibraryCommands.BookUserDefinedItemCommitted += setBackupCounts;
|
||||
QuickFilters.Updated += updateFiltersMenu;
|
||||
LibraryCommands.ScanBegin += LibraryCommands_ScanBegin;
|
||||
LibraryCommands.ScanEnd += LibraryCommands_ScanEnd;
|
||||
|
||||
// accounts updated
|
||||
this.Load += refreshImportMenu;
|
||||
AccountsSettingsPersister.Saved += refreshImportMenu;
|
||||
|
||||
configAndInitAutoScan();
|
||||
|
||||
configVisibleBooksMenu();
|
||||
|
||||
// init default/placeholder cover art
|
||||
var format = System.Drawing.Imaging.ImageFormat.Jpeg;
|
||||
PictureStorage.SetDefaultImage(PictureSize._80x80, Properties.Resources.default_cover_80x80.ToBytes(format));
|
||||
PictureStorage.SetDefaultImage(PictureSize._300x300, Properties.Resources.default_cover_300x300.ToBytes(format));
|
||||
PictureStorage.SetDefaultImage(PictureSize._500x500, Properties.Resources.default_cover_500x500.ToBytes(format));
|
||||
this.Load += (_, __) => productsGrid.Display();
|
||||
LibraryCommands.LibrarySizeChanged += (_, __) => this.UIThreadAsync(() => productsGrid.Display());
|
||||
}
|
||||
}
|
||||
|
||||
private void Form1_Load(object sender, EventArgs e)
|
||||
@@ -86,566 +79,5 @@ namespace LibationWinForms
|
||||
|
||||
// I'm leaving this empty call here as a reminder that if we use this, it should probably be after DesignMode check
|
||||
}
|
||||
|
||||
#region bottom: backup counts
|
||||
private System.ComponentModel.BackgroundWorker updateCountsBw;
|
||||
private bool runBackupCountsAgain;
|
||||
|
||||
private void setBackupCounts(object _, object __)
|
||||
{
|
||||
runBackupCountsAgain = true;
|
||||
|
||||
if (updateCountsBw is not null)
|
||||
return;
|
||||
|
||||
updateCountsBw = new System.ComponentModel.BackgroundWorker();
|
||||
updateCountsBw.DoWork += UpdateCountsBw_DoWork;
|
||||
updateCountsBw.RunWorkerCompleted += UpdateCountsBw_RunWorkerCompleted;
|
||||
updateCountsBw.RunWorkerAsync();
|
||||
}
|
||||
|
||||
private void UpdateCountsBw_DoWork(object sender, System.ComponentModel.DoWorkEventArgs e)
|
||||
{
|
||||
while (runBackupCountsAgain)
|
||||
{
|
||||
runBackupCountsAgain = false;
|
||||
|
||||
var libraryStats = LibraryCommands.GetCounts();
|
||||
e.Result = libraryStats;
|
||||
}
|
||||
updateCountsBw = null;
|
||||
}
|
||||
|
||||
private void UpdateCountsBw_RunWorkerCompleted(object sender, System.ComponentModel.RunWorkerCompletedEventArgs e)
|
||||
{
|
||||
var libraryStats = e.Result as LibraryCommands.LibraryStats;
|
||||
|
||||
setBookBackupCounts(libraryStats);
|
||||
setPdfBackupCounts(libraryStats);
|
||||
}
|
||||
|
||||
private void setBookBackupCounts(LibraryCommands.LibraryStats libraryStats)
|
||||
{
|
||||
var backupsCountsLbl_Format = "BACKUPS: No progress: {0} In process: {1} Fully backed up: {2}";
|
||||
|
||||
// enable/disable export
|
||||
var hasResults = 0 < (libraryStats.booksFullyBackedUp + libraryStats.booksDownloadedOnly + libraryStats.booksNoProgress + libraryStats.booksError);
|
||||
exportLibraryToolStripMenuItem.Enabled = hasResults;
|
||||
|
||||
// update bottom numbers
|
||||
var pending = libraryStats.booksNoProgress + libraryStats.booksDownloadedOnly;
|
||||
var statusStripText
|
||||
= !hasResults ? "No books. Begin by importing your library"
|
||||
: libraryStats.booksError > 0 ? string.Format(backupsCountsLbl_Format + " Errors: {3}", libraryStats.booksNoProgress, libraryStats.booksDownloadedOnly, libraryStats.booksFullyBackedUp, libraryStats.booksError)
|
||||
: pending > 0 ? string.Format(backupsCountsLbl_Format, libraryStats.booksNoProgress, libraryStats.booksDownloadedOnly, libraryStats.booksFullyBackedUp)
|
||||
: $"All {"book".PluralizeWithCount(libraryStats.booksFullyBackedUp)} backed up";
|
||||
|
||||
// update menu item
|
||||
var menuItemText
|
||||
= pending > 0
|
||||
? $"{pending} remaining"
|
||||
: "All books have been liberated";
|
||||
|
||||
// update UI
|
||||
statusStrip1.UIThreadAsync(() => backupsCountsLbl.Text = statusStripText);
|
||||
menuStrip1.UIThreadAsync(() => beginBookBackupsToolStripMenuItem.Enabled = pending > 0);
|
||||
menuStrip1.UIThreadAsync(() => beginBookBackupsToolStripMenuItem.Text = string.Format(beginBookBackupsToolStripMenuItem_format, menuItemText));
|
||||
}
|
||||
private void setPdfBackupCounts(LibraryCommands.LibraryStats libraryStats)
|
||||
{
|
||||
var pdfsCountsLbl_Format = "| PDFs: NOT d/l\'ed: {0} Downloaded: {1}";
|
||||
|
||||
// update bottom numbers
|
||||
var hasResults = 0 < (libraryStats.pdfsNotDownloaded + libraryStats.pdfsDownloaded);
|
||||
var statusStripText
|
||||
= !hasResults ? ""
|
||||
: libraryStats.pdfsNotDownloaded > 0 ? string.Format(pdfsCountsLbl_Format, libraryStats.pdfsNotDownloaded, libraryStats.pdfsDownloaded)
|
||||
: $"| All {libraryStats.pdfsDownloaded} PDFs downloaded";
|
||||
|
||||
// update menu item
|
||||
var menuItemText
|
||||
= libraryStats.pdfsNotDownloaded > 0
|
||||
? $"{libraryStats.pdfsNotDownloaded} remaining"
|
||||
: "All PDFs have been downloaded";
|
||||
|
||||
// update UI
|
||||
statusStrip1.UIThreadAsync(() => pdfsCountsLbl.Text = statusStripText);
|
||||
menuStrip1.UIThreadAsync(() => beginPdfBackupsToolStripMenuItem.Enabled = libraryStats.pdfsNotDownloaded > 0);
|
||||
menuStrip1.UIThreadAsync(() => beginPdfBackupsToolStripMenuItem.Text = string.Format(beginPdfBackupsToolStripMenuItem_format, menuItemText));
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region filter
|
||||
private void filterHelpBtn_Click(object sender, EventArgs e) => new SearchSyntaxDialog().ShowDialog();
|
||||
|
||||
private void AddFilterBtn_Click(object sender, EventArgs e) => QuickFilters.Add(this.filterSearchTb.Text);
|
||||
|
||||
private void filterSearchTb_KeyPress(object sender, KeyPressEventArgs e)
|
||||
{
|
||||
if (e.KeyChar == (char)Keys.Return)
|
||||
{
|
||||
doFilter();
|
||||
|
||||
// silence the 'ding'
|
||||
e.Handled = true;
|
||||
}
|
||||
}
|
||||
private void filterBtn_Click(object sender, EventArgs e) => doFilter();
|
||||
|
||||
private string lastGoodFilter = "";
|
||||
private void doFilter(string filterString)
|
||||
{
|
||||
this.filterSearchTb.Text = filterString;
|
||||
doFilter();
|
||||
}
|
||||
private void doFilter()
|
||||
{
|
||||
try
|
||||
{
|
||||
productsGrid.Filter(filterSearchTb.Text);
|
||||
lastGoodFilter = filterSearchTb.Text;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
MessageBox.Show($"Bad filter string:\r\n\r\n{ex.Message}", "Bad filter string", MessageBoxButtons.OK, MessageBoxIcon.Error);
|
||||
|
||||
// re-apply last good filter
|
||||
doFilter(lastGoodFilter);
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Auto-scanner
|
||||
private InterruptableTimer autoScanTimer;
|
||||
|
||||
private void configAndInitAutoScan()
|
||||
{
|
||||
var hours = 0;
|
||||
var minutes = 5;
|
||||
var seconds = 0;
|
||||
var _5_minutes = new TimeSpan(hours, minutes, seconds);
|
||||
autoScanTimer = new InterruptableTimer(_5_minutes);
|
||||
|
||||
// subscribe as async/non-blocking. I'd actually rather prefer blocking but real-world testing found that caused a deadlock in the AudibleAPI
|
||||
autoScanTimer.Elapsed += async (_, __) =>
|
||||
{
|
||||
using var persister = AudibleApiStorage.GetAccountsSettingsPersister();
|
||||
var accounts = persister.AccountsSettings
|
||||
.GetAll()
|
||||
.Where(a => a.LibraryScan)
|
||||
.ToArray();
|
||||
|
||||
// in autoScan, new books SHALL NOT show dialog
|
||||
await Invoke(async () => await LibraryCommands.ImportAccountAsync(Login.WinformLoginChoiceEager.ApiExtendedFunc, accounts));
|
||||
};
|
||||
|
||||
// load init state to menu checkbox
|
||||
this.Load += updateAutoScanLibraryToolStripMenuItem;
|
||||
// if enabled: begin on load
|
||||
this.Load += startAutoScan;
|
||||
|
||||
// if new 'default' account is added, run autoscan
|
||||
AccountsSettingsPersister.Saving += accountsPreSave;
|
||||
AccountsSettingsPersister.Saved += accountsPostSave;
|
||||
|
||||
// when autoscan setting is changed, update menu checkbox and run autoscan
|
||||
Configuration.Instance.AutoScanChanged += updateAutoScanLibraryToolStripMenuItem;
|
||||
Configuration.Instance.AutoScanChanged += startAutoScan;
|
||||
}
|
||||
|
||||
private List<(string AccountId, string LocaleName)> preSaveDefaultAccounts;
|
||||
private List<(string AccountId, string LocaleName)> getDefaultAccounts()
|
||||
{
|
||||
using var persister = AudibleApiStorage.GetAccountsSettingsPersister();
|
||||
return persister.AccountsSettings
|
||||
.GetAll()
|
||||
.Where(a => a.LibraryScan)
|
||||
.Select(a => (a.AccountId, a.Locale.Name))
|
||||
.ToList();
|
||||
}
|
||||
private void accountsPreSave(object sender = null, EventArgs e = null)
|
||||
=> preSaveDefaultAccounts = getDefaultAccounts();
|
||||
private void accountsPostSave(object sender = null, EventArgs e = null)
|
||||
{
|
||||
var postSaveDefaultAccounts = getDefaultAccounts();
|
||||
var newDefaultAccounts = postSaveDefaultAccounts.Except(preSaveDefaultAccounts).ToList();
|
||||
|
||||
if (newDefaultAccounts.Any())
|
||||
startAutoScan();
|
||||
}
|
||||
|
||||
private void startAutoScan(object sender = null, EventArgs e = null)
|
||||
{
|
||||
if (Configuration.Instance.AutoScan)
|
||||
autoScanTimer.PerformNow();
|
||||
else
|
||||
autoScanTimer.Stop();
|
||||
}
|
||||
|
||||
private void updateAutoScanLibraryToolStripMenuItem(object sender, EventArgs e) => autoScanLibraryToolStripMenuItem.Checked = Configuration.Instance.AutoScan;
|
||||
#endregion
|
||||
|
||||
#region Import menu
|
||||
private void refreshImportMenu(object _ = null, EventArgs __ = null)
|
||||
{
|
||||
using var persister = AudibleApiStorage.GetAccountsSettingsPersister();
|
||||
var count = persister.AccountsSettings.Accounts.Count;
|
||||
|
||||
autoScanLibraryToolStripMenuItem.Visible = count > 0;
|
||||
|
||||
noAccountsYetAddAccountToolStripMenuItem.Visible = count == 0;
|
||||
scanLibraryToolStripMenuItem.Visible = count == 1;
|
||||
scanLibraryOfAllAccountsToolStripMenuItem.Visible = count > 1;
|
||||
scanLibraryOfSomeAccountsToolStripMenuItem.Visible = count > 1;
|
||||
|
||||
removeLibraryBooksToolStripMenuItem.Visible = count > 0;
|
||||
removeSomeAccountsToolStripMenuItem.Visible = count > 1;
|
||||
removeAllAccountsToolStripMenuItem.Visible = count > 1;
|
||||
}
|
||||
|
||||
private void autoScanLibraryToolStripMenuItem_Click(object sender, EventArgs e) => Configuration.Instance.AutoScan = !autoScanLibraryToolStripMenuItem.Checked;
|
||||
|
||||
private void noAccountsYetAddAccountToolStripMenuItem_Click(object sender, EventArgs e)
|
||||
{
|
||||
MessageBox.Show("To load your Audible library, come back here to the Import menu after adding your account");
|
||||
new AccountsDialog(this).ShowDialog();
|
||||
}
|
||||
|
||||
private async void scanLibraryToolStripMenuItem_Click(object sender, EventArgs e)
|
||||
{
|
||||
using var persister = AudibleApiStorage.GetAccountsSettingsPersister();
|
||||
var firstAccount = persister.AccountsSettings.GetAll().FirstOrDefault();
|
||||
await scanLibrariesAsync(firstAccount);
|
||||
}
|
||||
|
||||
private async void scanLibraryOfAllAccountsToolStripMenuItem_Click(object sender, EventArgs e)
|
||||
{
|
||||
using var persister = AudibleApiStorage.GetAccountsSettingsPersister();
|
||||
var allAccounts = persister.AccountsSettings.GetAll();
|
||||
await scanLibrariesAsync(allAccounts);
|
||||
}
|
||||
|
||||
private async void scanLibraryOfSomeAccountsToolStripMenuItem_Click(object sender, EventArgs e)
|
||||
{
|
||||
using var scanAccountsDialog = new ScanAccountsDialog(this);
|
||||
|
||||
if (scanAccountsDialog.ShowDialog() != DialogResult.OK)
|
||||
return;
|
||||
|
||||
if (!scanAccountsDialog.CheckedAccounts.Any())
|
||||
return;
|
||||
|
||||
await scanLibrariesAsync(scanAccountsDialog.CheckedAccounts);
|
||||
}
|
||||
|
||||
private void removeLibraryBooksToolStripMenuItem_Click(object sender, EventArgs e)
|
||||
{
|
||||
// if 0 accounts, this will not be visible
|
||||
// if 1 account, run scanLibrariesRemovedBooks() on this account
|
||||
// if multiple accounts, another menu set will open. do not run scanLibrariesRemovedBooks()
|
||||
using var persister = AudibleApiStorage.GetAccountsSettingsPersister();
|
||||
var accounts = persister.AccountsSettings.GetAll();
|
||||
|
||||
if (accounts.Count != 1)
|
||||
return;
|
||||
|
||||
var firstAccount = accounts.Single();
|
||||
scanLibrariesRemovedBooks(firstAccount);
|
||||
}
|
||||
|
||||
// selectively remove books from all accounts
|
||||
private void removeAllAccountsToolStripMenuItem_Click(object sender, EventArgs e)
|
||||
{
|
||||
using var persister = AudibleApiStorage.GetAccountsSettingsPersister();
|
||||
var allAccounts = persister.AccountsSettings.GetAll();
|
||||
scanLibrariesRemovedBooks(allAccounts.ToArray());
|
||||
}
|
||||
|
||||
// selectively remove books from some accounts
|
||||
private void removeSomeAccountsToolStripMenuItem_Click(object sender, EventArgs e)
|
||||
{
|
||||
using var scanAccountsDialog = new ScanAccountsDialog(this);
|
||||
|
||||
if (scanAccountsDialog.ShowDialog() != DialogResult.OK)
|
||||
return;
|
||||
|
||||
if (!scanAccountsDialog.CheckedAccounts.Any())
|
||||
return;
|
||||
|
||||
scanLibrariesRemovedBooks(scanAccountsDialog.CheckedAccounts.ToArray());
|
||||
}
|
||||
|
||||
private void scanLibrariesRemovedBooks(params Account[] accounts)
|
||||
{
|
||||
using var dialog = new RemoveBooksDialog(accounts);
|
||||
dialog.ShowDialog();
|
||||
}
|
||||
|
||||
private async Task scanLibrariesAsync(IEnumerable<Account> accounts) => await scanLibrariesAsync(accounts.ToArray());
|
||||
private async Task scanLibrariesAsync(params Account[] accounts)
|
||||
{
|
||||
try
|
||||
{
|
||||
var (totalProcessed, newAdded) = await LibraryCommands.ImportAccountAsync(Login.WinformLoginChoiceEager.ApiExtendedFunc, accounts);
|
||||
|
||||
// this is here instead of ScanEnd so that the following is only possible when it's user-initiated, not automatic loop
|
||||
if (Configuration.Instance.ShowImportedStats && newAdded > 0)
|
||||
MessageBox.Show($"Total processed: {totalProcessed}\r\nNew: {newAdded}");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
MessageBoxLib.ShowAdminAlert(
|
||||
"Error importing library. Please try again. If this still happens after 2 or 3 tries, stop and contact administrator",
|
||||
"Error importing library",
|
||||
ex);
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Liberate menu
|
||||
private async void beginBookBackupsToolStripMenuItem_Click(object sender, EventArgs e)
|
||||
=> await BookLiberation.ProcessorAutomationController.BackupAllBooksAsync();
|
||||
|
||||
private async void beginPdfBackupsToolStripMenuItem_Click(object sender, EventArgs e)
|
||||
=> await BookLiberation.ProcessorAutomationController.BackupAllPdfsAsync();
|
||||
|
||||
private async void convertAllM4bToMp3ToolStripMenuItem_Click(object sender, EventArgs e)
|
||||
{
|
||||
var result = MessageBox.Show(
|
||||
"This converts all m4b titles in your library to mp3 files. Original files are not deleted."
|
||||
+ "\r\nFor large libraries this will take a long time and will take up more disk space."
|
||||
+ "\r\n\r\nContinue?"
|
||||
+ "\r\n\r\n(To always download titles as mp3 instead of m4b, go to Settings: Download my books as .MP3 files)",
|
||||
"Convert all M4b => Mp3?",
|
||||
MessageBoxButtons.YesNo,
|
||||
MessageBoxIcon.Warning);
|
||||
if (result == DialogResult.Yes)
|
||||
await BookLiberation.ProcessorAutomationController.ConvertAllBooksAsync();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Export menu
|
||||
private void exportLibraryToolStripMenuItem_Click(object sender, EventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
var saveFileDialog = new SaveFileDialog
|
||||
{
|
||||
Title = "Where to export Library",
|
||||
Filter = "Excel Workbook (*.xlsx)|*.xlsx|CSV files (*.csv)|*.csv|JSON files (*.json)|*.json" // + "|All files (*.*)|*.*"
|
||||
};
|
||||
|
||||
if (saveFileDialog.ShowDialog() != DialogResult.OK)
|
||||
return;
|
||||
|
||||
// FilterIndex is 1-based, NOT 0-based
|
||||
switch (saveFileDialog.FilterIndex)
|
||||
{
|
||||
case 1: // xlsx
|
||||
default:
|
||||
LibraryExporter.ToXlsx(saveFileDialog.FileName);
|
||||
break;
|
||||
case 2: // csv
|
||||
LibraryExporter.ToCsv(saveFileDialog.FileName);
|
||||
break;
|
||||
case 3: // json
|
||||
LibraryExporter.ToJson(saveFileDialog.FileName);
|
||||
break;
|
||||
}
|
||||
|
||||
MessageBox.Show("Library exported to:\r\n" + saveFileDialog.FileName);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
MessageBoxLib.ShowAdminAlert("Error attempting to export your library.", "Error exporting", ex);
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Quick Filters menu
|
||||
private void FirstFilterIsDefaultToolStripMenuItem_Click(object sender, EventArgs e)
|
||||
{
|
||||
firstFilterIsDefaultToolStripMenuItem.Checked = !firstFilterIsDefaultToolStripMenuItem.Checked;
|
||||
QuickFilters.UseDefault = firstFilterIsDefaultToolStripMenuItem.Checked;
|
||||
}
|
||||
|
||||
private void loadInitialQuickFilterState()
|
||||
{
|
||||
// set inital state. do once only
|
||||
firstFilterIsDefaultToolStripMenuItem.Checked = QuickFilters.UseDefault;
|
||||
|
||||
// load default filter. do once only
|
||||
if (QuickFilters.UseDefault)
|
||||
doFilter(QuickFilters.Filters.FirstOrDefault());
|
||||
|
||||
updateFiltersMenu();
|
||||
}
|
||||
|
||||
private object quickFilterTag { get; } = new object();
|
||||
private void updateFiltersMenu(object _ = null, object __ = null)
|
||||
{
|
||||
// remove old
|
||||
for (var i = quickFiltersToolStripMenuItem.DropDownItems.Count - 1; i >= 0; i--)
|
||||
{
|
||||
var menuItem = quickFiltersToolStripMenuItem.DropDownItems[i];
|
||||
if (menuItem.Tag == quickFilterTag)
|
||||
quickFiltersToolStripMenuItem.DropDownItems.Remove(menuItem);
|
||||
}
|
||||
|
||||
// re-populate
|
||||
var index = 0;
|
||||
foreach (var filter in QuickFilters.Filters)
|
||||
{
|
||||
var menuItem = new ToolStripMenuItem
|
||||
{
|
||||
Tag = quickFilterTag,
|
||||
Text = $"&{++index}: {filter}"
|
||||
};
|
||||
menuItem.Click += (_, __) => doFilter(filter);
|
||||
quickFiltersToolStripMenuItem.DropDownItems.Add(menuItem);
|
||||
}
|
||||
}
|
||||
|
||||
private void EditQuickFiltersToolStripMenuItem_Click(object sender, EventArgs e) => new EditQuickFilters(this).ShowDialog();
|
||||
#endregion
|
||||
|
||||
#region Visible Books menu
|
||||
private void configVisibleBooksMenu()
|
||||
{
|
||||
productsGrid.VisibleCountChanged += (_, qty) => {
|
||||
visibleBooksToolStripMenuItem.Text = string.Format(visibleBooksToolStripMenuItem_format, qty);
|
||||
visibleBooksToolStripMenuItem.Enabled = qty > 0;
|
||||
|
||||
var notLiberatedCount = productsGrid.GetVisible().Count(lb => lb.Book.UserDefinedItem.BookStatus == DataLayer.LiberatedStatus.NotLiberated);
|
||||
};
|
||||
|
||||
productsGrid.VisibleCountChanged += setLiberatedVisibleMenuItemAsync;
|
||||
LibraryCommands.BookUserDefinedItemCommitted += setLiberatedVisibleMenuItemAsync;
|
||||
}
|
||||
private async void setLiberatedVisibleMenuItemAsync(object _, int __)
|
||||
=> await Task.Run(setLiberatedVisibleMenuItem);
|
||||
private async void setLiberatedVisibleMenuItemAsync(object _, EventArgs __)
|
||||
=> await Task.Run(setLiberatedVisibleMenuItem);
|
||||
void setLiberatedVisibleMenuItem()
|
||||
{
|
||||
var notLiberated = productsGrid.GetVisible().Count(lb => lb.Book.UserDefinedItem.BookStatus == DataLayer.LiberatedStatus.NotLiberated);
|
||||
this.UIThreadSync(() =>
|
||||
{
|
||||
if (notLiberated > 0)
|
||||
{
|
||||
liberateVisibleToolStripMenuItem.Text = string.Format(liberateVisibleToolStripMenuItem_format, notLiberated);
|
||||
liberateVisibleToolStripMenuItem.Enabled = true;
|
||||
|
||||
liberateVisible2ToolStripMenuItem.Text = string.Format(liberateVisible2ToolStripMenuItem_format, notLiberated);
|
||||
liberateVisible2ToolStripMenuItem.Enabled = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
liberateVisibleToolStripMenuItem.Text = "All visible books are liberated";
|
||||
liberateVisibleToolStripMenuItem.Enabled = false;
|
||||
|
||||
liberateVisible2ToolStripMenuItem.Text = "All visible books are liberated";
|
||||
liberateVisible2ToolStripMenuItem.Enabled = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private async void liberateVisible(object sender, EventArgs e)
|
||||
=> await BookLiberation.ProcessorAutomationController.BackupAllBooksAsync(productsGrid.GetVisible());
|
||||
|
||||
private void replaceTagsToolStripMenuItem_Click(object sender, EventArgs e)
|
||||
{
|
||||
var dialog = new TagsBatchDialog();
|
||||
var result = dialog.ShowDialog();
|
||||
if (result != DialogResult.OK)
|
||||
return;
|
||||
|
||||
var visibleLibraryBooks = productsGrid.GetVisible();
|
||||
|
||||
var confirmationResult = MessageBoxLib.ShowConfirmationDialog(
|
||||
visibleLibraryBooks,
|
||||
$"Are you sure you want to replace tags in {0}?",
|
||||
"Replace tags?");
|
||||
|
||||
if (confirmationResult != DialogResult.Yes)
|
||||
return;
|
||||
|
||||
foreach (var libraryBook in visibleLibraryBooks)
|
||||
libraryBook.Book.UserDefinedItem.Tags = dialog.NewTags;
|
||||
LibraryCommands.UpdateUserDefinedItem(visibleLibraryBooks.Select(lb => lb.Book));
|
||||
}
|
||||
|
||||
private void setDownloadedToolStripMenuItem_Click(object sender, EventArgs e)
|
||||
{
|
||||
var dialog = new LiberatedStatusBatchDialog();
|
||||
var result = dialog.ShowDialog();
|
||||
if (result != DialogResult.OK)
|
||||
return;
|
||||
|
||||
var visibleLibraryBooks = productsGrid.GetVisible();
|
||||
|
||||
var confirmationResult = MessageBoxLib.ShowConfirmationDialog(
|
||||
visibleLibraryBooks,
|
||||
$"Are you sure you want to replace downloaded status in {0}?",
|
||||
"Replace downloaded status?");
|
||||
|
||||
if (confirmationResult != DialogResult.Yes)
|
||||
return;
|
||||
|
||||
foreach (var libraryBook in visibleLibraryBooks)
|
||||
libraryBook.Book.UserDefinedItem.BookStatus = dialog.BookLiberatedStatus;
|
||||
LibraryCommands.UpdateUserDefinedItem(visibleLibraryBooks.Select(lb => lb.Book));
|
||||
}
|
||||
|
||||
private async void removeToolStripMenuItem_Click(object sender, EventArgs e)
|
||||
{
|
||||
var visibleLibraryBooks = productsGrid.GetVisible();
|
||||
|
||||
var confirmationResult = MessageBoxLib.ShowConfirmationDialog(
|
||||
visibleLibraryBooks,
|
||||
$"Are you sure you want to remove {0} from Libation's library?",
|
||||
"Remove books from Libation?");
|
||||
|
||||
if (confirmationResult != DialogResult.Yes)
|
||||
return;
|
||||
|
||||
var visibleIds = visibleLibraryBooks.Select(lb => lb.Book.AudibleProductId).ToList();
|
||||
await LibraryCommands.RemoveBooksAsync(visibleIds);
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Settings menu
|
||||
private void accountsToolStripMenuItem_Click(object sender, EventArgs e) => new AccountsDialog(this).ShowDialog();
|
||||
|
||||
private void basicSettingsToolStripMenuItem_Click(object sender, EventArgs e) => new SettingsDialog().ShowDialog();
|
||||
|
||||
private void aboutToolStripMenuItem_Click(object sender, EventArgs e)
|
||||
=> MessageBox.Show($"Running Libation version {AppScaffolding.LibationScaffolding.BuildVersion}", $"Libation v{AppScaffolding.LibationScaffolding.BuildVersion}");
|
||||
#endregion
|
||||
|
||||
#region Scanning label
|
||||
private void LibraryCommands_ScanBegin(object sender, int accountsLength)
|
||||
{
|
||||
scanLibraryToolStripMenuItem.Enabled = false;
|
||||
scanLibraryOfAllAccountsToolStripMenuItem.Enabled = false;
|
||||
scanLibraryOfSomeAccountsToolStripMenuItem.Enabled = false;
|
||||
|
||||
this.scanningToolStripMenuItem.Visible = true;
|
||||
this.scanningToolStripMenuItem.Text
|
||||
= (accountsLength == 1)
|
||||
? "Scanning..."
|
||||
: $"Scanning {accountsLength} accounts...";
|
||||
}
|
||||
|
||||
private void LibraryCommands_ScanEnd(object sender, EventArgs e)
|
||||
{
|
||||
scanLibraryToolStripMenuItem.Enabled = true;
|
||||
scanLibraryOfAllAccountsToolStripMenuItem.Enabled = true;
|
||||
scanLibraryOfSomeAccountsToolStripMenuItem.Enabled = true;
|
||||
|
||||
this.scanningToolStripMenuItem.Visible = false;
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
32
Source/LibationWinForms/FormattableLabel.cs
Normal file
32
Source/LibationWinForms/FormattableLabel.cs
Normal file
@@ -0,0 +1,32 @@
|
||||
using System;
|
||||
using System.Drawing;
|
||||
using System.Windows.Forms;
|
||||
|
||||
namespace LibationWinForms
|
||||
{
|
||||
public class FormattableLabel : Label
|
||||
{
|
||||
public string FormatText { get; set; }
|
||||
|
||||
/// <summary>Text set: first non-null, non-whitespace <see cref="Text"/> set is also saved as <see cref="FormatText"/></summary>
|
||||
public override string Text
|
||||
{
|
||||
get => base.Text;
|
||||
set
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(FormatText))
|
||||
FormatText = value;
|
||||
|
||||
base.Text = value;
|
||||
}
|
||||
}
|
||||
|
||||
#region ctor.s
|
||||
public FormattableLabel() : base() { }
|
||||
#endregion
|
||||
|
||||
/// <summary>Replaces the format item in a specified string with the string representation of a corresponding object in a specified array. Returns <see cref="Text"/> for convenience.</summary>
|
||||
/// <param name="args">An object array that contains zero or more objects to format.</param>
|
||||
public string Format(params object[] args) => Text = string.Format(FormatText, args);
|
||||
}
|
||||
}
|
||||
39
Source/LibationWinForms/FormattableToolStripMenuItem.cs
Normal file
39
Source/LibationWinForms/FormattableToolStripMenuItem.cs
Normal file
@@ -0,0 +1,39 @@
|
||||
using System;
|
||||
using System.Drawing;
|
||||
using System.Windows.Forms;
|
||||
|
||||
namespace LibationWinForms
|
||||
{
|
||||
public class FormattableToolStripMenuItem : ToolStripMenuItem
|
||||
{
|
||||
public string FormatText { get; set; }
|
||||
|
||||
/// <summary>Text set: first non-null, non-whitespace <see cref="Text"/> set is also saved as <see cref="FormatText"/></summary>
|
||||
public override string Text
|
||||
{
|
||||
get => base.Text;
|
||||
set
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(FormatText))
|
||||
FormatText = value;
|
||||
|
||||
base.Text = value;
|
||||
}
|
||||
}
|
||||
|
||||
#region ctor.s
|
||||
public FormattableToolStripMenuItem() : base() { }
|
||||
public FormattableToolStripMenuItem(string text) : base(text) => FormatText = text;
|
||||
public FormattableToolStripMenuItem(Image image) : base(image) { }
|
||||
public FormattableToolStripMenuItem(string text, Image image) : base(text, image) => FormatText = text;
|
||||
public FormattableToolStripMenuItem(string text, Image image, EventHandler onClick) : base(text, image, onClick) => FormatText = text;
|
||||
public FormattableToolStripMenuItem(string text, Image image, params ToolStripItem[] dropDownItems) : base(text, image, dropDownItems) => FormatText = text;
|
||||
public FormattableToolStripMenuItem(string text, Image image, EventHandler onClick, Keys shortcutKeys) : base(text, image, onClick, shortcutKeys) => FormatText = text;
|
||||
public FormattableToolStripMenuItem(string text, Image image, EventHandler onClick, string name) : base(text, image, onClick, name) => FormatText = text;
|
||||
#endregion
|
||||
|
||||
/// <summary>Replaces the format item in a specified string with the string representation of a corresponding object in a specified array. Returns <see cref="Text"/> for convenience.</summary>
|
||||
/// <param name="args">An object array that contains zero or more objects to format.</param>
|
||||
public string Format(params object[] args) => Text = string.Format(FormatText, args);
|
||||
}
|
||||
}
|
||||
37
Source/LibationWinForms/FormattableToolStripStatusLabel.cs
Normal file
37
Source/LibationWinForms/FormattableToolStripStatusLabel.cs
Normal file
@@ -0,0 +1,37 @@
|
||||
using System;
|
||||
using System.Drawing;
|
||||
using System.Windows.Forms;
|
||||
|
||||
namespace LibationWinForms
|
||||
{
|
||||
public class FormattableToolStripStatusLabel : ToolStripStatusLabel
|
||||
{
|
||||
public string FormatText { get; set; }
|
||||
|
||||
/// <summary>Text set: first non-null, non-whitespace <see cref="Text"/> set is also saved as <see cref="FormatText"/></summary>
|
||||
public override string Text
|
||||
{
|
||||
get => base.Text;
|
||||
set
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(FormatText))
|
||||
FormatText = value;
|
||||
|
||||
base.Text = value;
|
||||
}
|
||||
}
|
||||
|
||||
#region ctor.s
|
||||
public FormattableToolStripStatusLabel() : base() { }
|
||||
public FormattableToolStripStatusLabel(string text) : base(text) => FormatText = text;
|
||||
public FormattableToolStripStatusLabel(Image image) : base(image) { }
|
||||
public FormattableToolStripStatusLabel(string text, Image image) : base(text, image) => FormatText = text;
|
||||
public FormattableToolStripStatusLabel(string text, Image image, EventHandler onClick) : base(text, image, onClick) => FormatText = text;
|
||||
public FormattableToolStripStatusLabel(string text, Image image, EventHandler onClick, string name) : base(text, image, onClick, name) => FormatText = text;
|
||||
#endregion
|
||||
|
||||
/// <summary>Replaces the format item in a specified string with the string representation of a corresponding object in a specified array. Returns <see cref="Text"/> for convenience.</summary>
|
||||
/// <param name="args">An object array that contains zero or more objects to format.</param>
|
||||
public string Format(params object[] args) => Text = string.Format(FormatText, args);
|
||||
}
|
||||
}
|
||||
@@ -28,7 +28,7 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Dinah.Core.WindowsDesktop" Version="4.2.0.1" />
|
||||
<PackageReference Include="Dinah.Core.WindowsDesktop" Version="4.2.2.1" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
@@ -37,6 +37,12 @@
|
||||
<ProjectReference Include="..\FileLiberator\FileLiberator.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Compile Update="Form1.*.cs">
|
||||
<DependentUpon>Form1.cs</DependentUpon>
|
||||
</Compile>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Compile Update="Properties\Resources.Designer.cs">
|
||||
<DesignTime>True</DesignTime>
|
||||
|
||||
386
Source/LibationWinForms/ProcessQueue/ProcessBook.cs
Normal file
386
Source/LibationWinForms/ProcessQueue/ProcessBook.cs
Normal file
@@ -0,0 +1,386 @@
|
||||
using DataLayer;
|
||||
using Dinah.Core;
|
||||
using FileLiberator;
|
||||
using LibationFileManager;
|
||||
using LibationWinForms.BookLiberation;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Drawing;
|
||||
using System.Linq;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Forms;
|
||||
|
||||
namespace LibationWinForms.ProcessQueue
|
||||
{
|
||||
public enum ProcessBookResult
|
||||
{
|
||||
None,
|
||||
Success,
|
||||
Cancelled,
|
||||
ValidationFail,
|
||||
FailedRetry,
|
||||
FailedSkip,
|
||||
FailedAbort
|
||||
}
|
||||
|
||||
public enum ProcessBookStatus
|
||||
{
|
||||
Queued,
|
||||
Cancelled,
|
||||
Working,
|
||||
Completed,
|
||||
Failed
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This is the viewmodel for queued processables
|
||||
/// </summary>
|
||||
public class ProcessBook : INotifyPropertyChanged
|
||||
{
|
||||
public event EventHandler Completed;
|
||||
public event PropertyChangedEventHandler PropertyChanged;
|
||||
|
||||
private ProcessBookResult _result = ProcessBookResult.None;
|
||||
private ProcessBookStatus _status = ProcessBookStatus.Queued;
|
||||
private string _bookText;
|
||||
private int _progress;
|
||||
private TimeSpan _timeRemaining;
|
||||
private Image _cover;
|
||||
|
||||
public ProcessBookResult Result { get => _result; private set { _result = value; NotifyPropertyChanged(); } }
|
||||
public ProcessBookStatus Status { get => _status; private set { _status = value; NotifyPropertyChanged(); } }
|
||||
public string BookText { get => _bookText; private set { _bookText = value; NotifyPropertyChanged(); } }
|
||||
public int Progress { get => _progress; private set { _progress = value; NotifyPropertyChanged(); } }
|
||||
public TimeSpan TimeRemaining { get => _timeRemaining; private set { _timeRemaining = value; NotifyPropertyChanged(); } }
|
||||
public Image Cover { get => _cover; private set { _cover = value; NotifyPropertyChanged(); } }
|
||||
|
||||
public LibraryBook LibraryBook { get; private set; }
|
||||
private Processable CurrentProcessable => _currentProcessable ??= Processes.Dequeue().Invoke();
|
||||
private Processable NextProcessable() => _currentProcessable = null;
|
||||
private Processable _currentProcessable;
|
||||
private Func<byte[]> GetCoverArtDelegate;
|
||||
private readonly Queue<Func<Processable>> Processes = new();
|
||||
private readonly LogMe Logger;
|
||||
|
||||
public void NotifyPropertyChanged([CallerMemberName] string propertyName = "")
|
||||
=> PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
|
||||
|
||||
public ProcessBook(LibraryBook libraryBook, LogMe logme)
|
||||
{
|
||||
LibraryBook = libraryBook;
|
||||
Logger = logme;
|
||||
|
||||
title = LibraryBook.Book.Title;
|
||||
authorNames = LibraryBook.Book.AuthorNames();
|
||||
narratorNames = LibraryBook.Book.NarratorNames();
|
||||
_bookText = $"{title}\r\nBy {authorNames}\r\nNarrated by {narratorNames}";
|
||||
|
||||
(bool isDefault, byte[] picture) = PictureStorage.GetPicture(new PictureDefinition(LibraryBook.Book.PictureId, PictureSize._80x80));
|
||||
|
||||
if (isDefault)
|
||||
PictureStorage.PictureCached += PictureStorage_PictureCached;
|
||||
_cover = Dinah.Core.Drawing.ImageReader.ToImage(picture);
|
||||
|
||||
}
|
||||
|
||||
private void PictureStorage_PictureCached(object sender, PictureCachedEventArgs e)
|
||||
{
|
||||
if (e.Definition.PictureId == LibraryBook.Book.PictureId)
|
||||
{
|
||||
Cover = Dinah.Core.Drawing.ImageReader.ToImage(e.Picture);
|
||||
PictureStorage.PictureCached -= PictureStorage_PictureCached;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<ProcessBookResult> ProcessOneAsync()
|
||||
{
|
||||
string procName = CurrentProcessable.Name;
|
||||
try
|
||||
{
|
||||
LinkProcessable(CurrentProcessable);
|
||||
|
||||
var statusHandler = await CurrentProcessable.ProcessSingleAsync(LibraryBook, validate: true);
|
||||
|
||||
if (statusHandler.IsSuccess)
|
||||
return Result = ProcessBookResult.Success;
|
||||
else if (statusHandler.Errors.Contains("Cancelled"))
|
||||
{
|
||||
Logger.Info($"{procName}: Process was cancelled {LibraryBook.Book}");
|
||||
return Result = ProcessBookResult.Cancelled;
|
||||
}
|
||||
else if (statusHandler.Errors.Contains("Validation failed"))
|
||||
{
|
||||
Logger.Info($"{procName}: Validation failed {LibraryBook.Book}");
|
||||
return Result = ProcessBookResult.ValidationFail;
|
||||
}
|
||||
|
||||
foreach (var errorMessage in statusHandler.Errors)
|
||||
Logger.Error($"{procName}: {errorMessage}");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.Error(ex, procName);
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (Result == ProcessBookResult.None)
|
||||
Result = showRetry(LibraryBook);
|
||||
|
||||
Status = Result switch
|
||||
{
|
||||
ProcessBookResult.Success => ProcessBookStatus.Completed,
|
||||
ProcessBookResult.Cancelled => ProcessBookStatus.Cancelled,
|
||||
ProcessBookResult.FailedRetry => ProcessBookStatus.Queued,
|
||||
_ => ProcessBookStatus.Failed,
|
||||
};
|
||||
}
|
||||
|
||||
return Result;
|
||||
}
|
||||
|
||||
public async Task Cancel()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (CurrentProcessable is AudioDecodable audioDecodable)
|
||||
{
|
||||
//There's some threadding bug that causes this to hang if executed synchronously.
|
||||
await Task.Run(audioDecodable.Cancel);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.Error(ex, $"{CurrentProcessable.Name}: 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()
|
||||
{
|
||||
Processes.Enqueue(() => new T());
|
||||
}
|
||||
|
||||
public override string ToString() => LibraryBook.ToString();
|
||||
|
||||
#region Subscribers and Unsubscribers
|
||||
|
||||
private void LinkProcessable(Processable processable)
|
||||
{
|
||||
processable.Begin += Processable_Begin;
|
||||
processable.Completed += Processable_Completed;
|
||||
processable.StreamingProgressChanged += Streamable_StreamingProgressChanged;
|
||||
processable.StreamingTimeRemaining += Streamable_StreamingTimeRemaining;
|
||||
|
||||
if (processable 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 processable)
|
||||
{
|
||||
processable.Begin -= Processable_Begin;
|
||||
processable.Completed -= Processable_Completed;
|
||||
processable.StreamingProgressChanged -= Streamable_StreamingProgressChanged;
|
||||
processable.StreamingTimeRemaining -= Streamable_StreamingTimeRemaining;
|
||||
|
||||
if (processable 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}";
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Streamable event handlers
|
||||
private void Streamable_StreamingTimeRemaining(object sender, TimeSpan timeRemaining)
|
||||
{
|
||||
TimeRemaining = timeRemaining;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Processable event handlers
|
||||
|
||||
private void Processable_Begin(object sender, LibraryBook libraryBook)
|
||||
{
|
||||
Status = ProcessBookStatus.Working;
|
||||
|
||||
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();
|
||||
updateBookInfo();
|
||||
}
|
||||
|
||||
private async void Processable_Completed(object sender, LibraryBook libraryBook)
|
||||
{
|
||||
|
||||
Logger.Info($"{((Processable)sender).Name} Step, Completed: {libraryBook.Book}");
|
||||
UnlinkProcessable((Processable)sender);
|
||||
|
||||
if (Processes.Count > 0)
|
||||
{
|
||||
NextProcessable();
|
||||
LinkProcessable(CurrentProcessable);
|
||||
var result = await CurrentProcessable.ProcessSingleAsync(libraryBook, validate: true);
|
||||
|
||||
if (result.HasErrors)
|
||||
{
|
||||
foreach (var errorMessage in result.Errors.Where(e => e != "Validation failed"))
|
||||
Logger.Error(errorMessage);
|
||||
|
||||
Completed?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Completed?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Failure Handler
|
||||
|
||||
private ProcessBookResult showRetry(LibraryBook libraryBook)
|
||||
{
|
||||
Logger.Error("ERROR. All books have not been processed. Most recent book: processing failed");
|
||||
|
||||
DialogResult? dialogResult = Configuration.Instance.BadBook switch
|
||||
{
|
||||
Configuration.BadBookAction.Abort => DialogResult.Abort,
|
||||
Configuration.BadBookAction.Retry => DialogResult.Retry,
|
||||
Configuration.BadBookAction.Ignore => DialogResult.Ignore,
|
||||
Configuration.BadBookAction.Ask => null,
|
||||
_ => null
|
||||
};
|
||||
|
||||
string details;
|
||||
try
|
||||
{
|
||||
static string trunc(string str)
|
||||
=> string.IsNullOrWhiteSpace(str) ? "[empty]"
|
||||
: (str.Length > 50) ? $"{str.Truncate(47)}..."
|
||||
: str;
|
||||
|
||||
details =
|
||||
$@" Title: {libraryBook.Book.Title}
|
||||
ID: {libraryBook.Book.AudibleProductId}
|
||||
Author: {trunc(libraryBook.Book.AuthorNames())}
|
||||
Narr: {trunc(libraryBook.Book.NarratorNames())}";
|
||||
}
|
||||
catch
|
||||
{
|
||||
details = "[Error retrieving details]";
|
||||
}
|
||||
|
||||
// if null then ask user
|
||||
dialogResult ??= MessageBox.Show(string.Format(SkipDialogText + "\r\n\r\nSee Settings to avoid this box in the future.", details), "Skip importing this book?", SkipDialogButtons, MessageBoxIcon.Question, SkipDialogDefaultButton);
|
||||
|
||||
if (dialogResult == DialogResult.Abort)
|
||||
return ProcessBookResult.FailedAbort;
|
||||
|
||||
if (dialogResult == SkipResult)
|
||||
{
|
||||
libraryBook.Book.UserDefinedItem.BookStatus = LiberatedStatus.Error;
|
||||
ApplicationServices.LibraryCommands.UpdateUserDefinedItem(libraryBook.Book);
|
||||
|
||||
Logger.Info($"Error. Skip: [{libraryBook.Book.AudibleProductId}] {libraryBook.Book.Title}");
|
||||
|
||||
return ProcessBookResult.FailedSkip;
|
||||
}
|
||||
|
||||
return ProcessBookResult.FailedRetry;
|
||||
}
|
||||
|
||||
|
||||
private string SkipDialogText => @"
|
||||
An error occurred while trying to process this book.
|
||||
{0}
|
||||
|
||||
- ABORT: Stop processing books.
|
||||
|
||||
- RETRY: retry this book later. Just skip it for now. Continue processing books. (Will try this book again later.)
|
||||
|
||||
- IGNORE: Permanently ignore this book. Continue processing books. (Will not try this book again later.)
|
||||
".Trim();
|
||||
private MessageBoxButtons SkipDialogButtons => MessageBoxButtons.AbortRetryIgnore;
|
||||
private MessageBoxDefaultButton SkipDialogDefaultButton => MessageBoxDefaultButton.Button1;
|
||||
private DialogResult SkipResult => DialogResult.Ignore;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
227
Source/LibationWinForms/ProcessQueue/ProcessBookControl.Designer.cs
generated
Normal file
227
Source/LibationWinForms/ProcessQueue/ProcessBookControl.Designer.cs
generated
Normal file
@@ -0,0 +1,227 @@
|
||||
namespace LibationWinForms.ProcessQueue
|
||||
{
|
||||
partial class ProcessBookControl
|
||||
{
|
||||
/// <summary>
|
||||
/// Required designer variable.
|
||||
/// </summary>
|
||||
private System.ComponentModel.IContainer components = null;
|
||||
|
||||
/// <summary>
|
||||
/// Clean up any resources being used.
|
||||
/// </summary>
|
||||
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
if (disposing && (components != null))
|
||||
{
|
||||
components.Dispose();
|
||||
}
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
|
||||
#region Component Designer generated code
|
||||
|
||||
/// <summary>
|
||||
/// Required method for Designer support - do not modify
|
||||
/// the contents of this method with the code editor.
|
||||
/// </summary>
|
||||
private void InitializeComponent()
|
||||
{
|
||||
System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(ProcessBookControl));
|
||||
this.pictureBox1 = new System.Windows.Forms.PictureBox();
|
||||
this.progressBar1 = new System.Windows.Forms.ProgressBar();
|
||||
this.remainingTimeLbl = new System.Windows.Forms.Label();
|
||||
this.etaLbl = new System.Windows.Forms.Label();
|
||||
this.cancelBtn = new System.Windows.Forms.Button();
|
||||
this.statusLbl = new System.Windows.Forms.Label();
|
||||
this.bookInfoLbl = new System.Windows.Forms.Label();
|
||||
this.moveUpBtn = new System.Windows.Forms.Button();
|
||||
this.moveDownBtn = new System.Windows.Forms.Button();
|
||||
this.moveFirstBtn = new System.Windows.Forms.Button();
|
||||
this.moveLastBtn = new System.Windows.Forms.Button();
|
||||
((System.ComponentModel.ISupportInitialize)(this.pictureBox1)).BeginInit();
|
||||
this.SuspendLayout();
|
||||
//
|
||||
// pictureBox1
|
||||
//
|
||||
this.pictureBox1.Location = new System.Drawing.Point(2, 2);
|
||||
this.pictureBox1.Name = "pictureBox1";
|
||||
this.pictureBox1.Size = new System.Drawing.Size(80, 80);
|
||||
this.pictureBox1.SizeMode = System.Windows.Forms.PictureBoxSizeMode.StretchImage;
|
||||
this.pictureBox1.TabIndex = 0;
|
||||
this.pictureBox1.TabStop = false;
|
||||
//
|
||||
// progressBar1
|
||||
//
|
||||
this.progressBar1.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left)
|
||||
| System.Windows.Forms.AnchorStyles.Right)));
|
||||
this.progressBar1.Location = new System.Drawing.Point(88, 65);
|
||||
this.progressBar1.MarqueeAnimationSpeed = 0;
|
||||
this.progressBar1.Name = "progressBar1";
|
||||
this.progressBar1.Size = new System.Drawing.Size(212, 17);
|
||||
this.progressBar1.TabIndex = 2;
|
||||
//
|
||||
// remainingTimeLbl
|
||||
//
|
||||
this.remainingTimeLbl.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right)));
|
||||
this.remainingTimeLbl.AutoSize = true;
|
||||
this.remainingTimeLbl.Location = new System.Drawing.Point(338, 65);
|
||||
this.remainingTimeLbl.Name = "remainingTimeLbl";
|
||||
this.remainingTimeLbl.Size = new System.Drawing.Size(30, 15);
|
||||
this.remainingTimeLbl.TabIndex = 3;
|
||||
this.remainingTimeLbl.Text = "--:--";
|
||||
this.remainingTimeLbl.TextAlign = System.Drawing.ContentAlignment.TopRight;
|
||||
//
|
||||
// etaLbl
|
||||
//
|
||||
this.etaLbl.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right)));
|
||||
this.etaLbl.AutoSize = true;
|
||||
this.etaLbl.Font = new System.Drawing.Font("Segoe UI", 8F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point);
|
||||
this.etaLbl.Location = new System.Drawing.Point(304, 66);
|
||||
this.etaLbl.Name = "etaLbl";
|
||||
this.etaLbl.Size = new System.Drawing.Size(28, 13);
|
||||
this.etaLbl.TabIndex = 3;
|
||||
this.etaLbl.Text = "ETA:";
|
||||
this.etaLbl.TextAlign = System.Drawing.ContentAlignment.TopRight;
|
||||
//
|
||||
// cancelBtn
|
||||
//
|
||||
this.cancelBtn.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right)));
|
||||
this.cancelBtn.BackColor = System.Drawing.Color.Transparent;
|
||||
this.cancelBtn.BackgroundImage = ((System.Drawing.Image)(resources.GetObject("cancelBtn.BackgroundImage")));
|
||||
this.cancelBtn.BackgroundImageLayout = System.Windows.Forms.ImageLayout.Zoom;
|
||||
this.cancelBtn.FlatStyle = System.Windows.Forms.FlatStyle.Flat;
|
||||
this.cancelBtn.ForeColor = System.Drawing.SystemColors.Control;
|
||||
this.cancelBtn.Location = new System.Drawing.Point(348, 6);
|
||||
this.cancelBtn.Margin = new System.Windows.Forms.Padding(0);
|
||||
this.cancelBtn.Name = "cancelBtn";
|
||||
this.cancelBtn.Size = new System.Drawing.Size(20, 20);
|
||||
this.cancelBtn.TabIndex = 4;
|
||||
this.cancelBtn.UseVisualStyleBackColor = false;
|
||||
//
|
||||
// statusLbl
|
||||
//
|
||||
this.statusLbl.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left)));
|
||||
this.statusLbl.AutoSize = true;
|
||||
this.statusLbl.Font = new System.Drawing.Font("Segoe UI", 8F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point);
|
||||
this.statusLbl.Location = new System.Drawing.Point(89, 66);
|
||||
this.statusLbl.Name = "statusLbl";
|
||||
this.statusLbl.Size = new System.Drawing.Size(50, 13);
|
||||
this.statusLbl.TabIndex = 3;
|
||||
this.statusLbl.Text = "[STATUS]";
|
||||
this.statusLbl.TextAlign = System.Drawing.ContentAlignment.TopRight;
|
||||
//
|
||||
// bookInfoLbl
|
||||
//
|
||||
this.bookInfoLbl.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.bookInfoLbl.Font = new System.Drawing.Font("Segoe UI", 8F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point);
|
||||
this.bookInfoLbl.Location = new System.Drawing.Point(89, 6);
|
||||
this.bookInfoLbl.Name = "bookInfoLbl";
|
||||
this.bookInfoLbl.Size = new System.Drawing.Size(219, 56);
|
||||
this.bookInfoLbl.TabIndex = 1;
|
||||
this.bookInfoLbl.Text = "[multi-\r\nline\r\nbook\r\n info]";
|
||||
//
|
||||
// moveUpBtn
|
||||
//
|
||||
this.moveUpBtn.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom)
|
||||
| System.Windows.Forms.AnchorStyles.Right)));
|
||||
this.moveUpBtn.BackColor = System.Drawing.Color.Transparent;
|
||||
this.moveUpBtn.BackgroundImage = ((System.Drawing.Image)(resources.GetObject("moveUpBtn.BackgroundImage")));
|
||||
this.moveUpBtn.BackgroundImageLayout = System.Windows.Forms.ImageLayout.Zoom;
|
||||
this.moveUpBtn.FlatStyle = System.Windows.Forms.FlatStyle.Flat;
|
||||
this.moveUpBtn.ForeColor = System.Drawing.SystemColors.Control;
|
||||
this.moveUpBtn.Location = new System.Drawing.Point(314, 24);
|
||||
this.moveUpBtn.Name = "moveUpBtn";
|
||||
this.moveUpBtn.Size = new System.Drawing.Size(30, 17);
|
||||
this.moveUpBtn.TabIndex = 5;
|
||||
this.moveUpBtn.UseVisualStyleBackColor = false;
|
||||
//
|
||||
// moveDownBtn
|
||||
//
|
||||
this.moveDownBtn.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom)
|
||||
| System.Windows.Forms.AnchorStyles.Right)));
|
||||
this.moveDownBtn.BackColor = System.Drawing.Color.Transparent;
|
||||
this.moveDownBtn.BackgroundImage = ((System.Drawing.Image)(resources.GetObject("moveDownBtn.BackgroundImage")));
|
||||
this.moveDownBtn.BackgroundImageLayout = System.Windows.Forms.ImageLayout.Zoom;
|
||||
this.moveDownBtn.FlatStyle = System.Windows.Forms.FlatStyle.Flat;
|
||||
this.moveDownBtn.ForeColor = System.Drawing.SystemColors.Control;
|
||||
this.moveDownBtn.Location = new System.Drawing.Point(314, 40);
|
||||
this.moveDownBtn.Name = "moveDownBtn";
|
||||
this.moveDownBtn.Size = new System.Drawing.Size(30, 17);
|
||||
this.moveDownBtn.TabIndex = 5;
|
||||
this.moveDownBtn.UseVisualStyleBackColor = false;
|
||||
//
|
||||
// moveFirstBtn
|
||||
//
|
||||
this.moveFirstBtn.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom)
|
||||
| System.Windows.Forms.AnchorStyles.Right)));
|
||||
this.moveFirstBtn.BackColor = System.Drawing.Color.Transparent;
|
||||
this.moveFirstBtn.BackgroundImage = ((System.Drawing.Image)(resources.GetObject("moveFirstBtn.BackgroundImage")));
|
||||
this.moveFirstBtn.BackgroundImageLayout = System.Windows.Forms.ImageLayout.Zoom;
|
||||
this.moveFirstBtn.FlatStyle = System.Windows.Forms.FlatStyle.Flat;
|
||||
this.moveFirstBtn.ForeColor = System.Drawing.SystemColors.Control;
|
||||
this.moveFirstBtn.Location = new System.Drawing.Point(314, 3);
|
||||
this.moveFirstBtn.Name = "moveFirstBtn";
|
||||
this.moveFirstBtn.Size = new System.Drawing.Size(30, 17);
|
||||
this.moveFirstBtn.TabIndex = 5;
|
||||
this.moveFirstBtn.UseVisualStyleBackColor = false;
|
||||
//
|
||||
// moveLastBtn
|
||||
//
|
||||
this.moveLastBtn.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom)
|
||||
| System.Windows.Forms.AnchorStyles.Right)));
|
||||
this.moveLastBtn.BackColor = System.Drawing.Color.Transparent;
|
||||
this.moveLastBtn.BackgroundImage = ((System.Drawing.Image)(resources.GetObject("moveLastBtn.BackgroundImage")));
|
||||
this.moveLastBtn.BackgroundImageLayout = System.Windows.Forms.ImageLayout.Zoom;
|
||||
this.moveLastBtn.FlatStyle = System.Windows.Forms.FlatStyle.Flat;
|
||||
this.moveLastBtn.ForeColor = System.Drawing.SystemColors.Control;
|
||||
this.moveLastBtn.Location = new System.Drawing.Point(314, 63);
|
||||
this.moveLastBtn.Name = "moveLastBtn";
|
||||
this.moveLastBtn.Size = new System.Drawing.Size(30, 17);
|
||||
this.moveLastBtn.TabIndex = 5;
|
||||
this.moveLastBtn.UseVisualStyleBackColor = false;
|
||||
//
|
||||
// ProcessBookControl
|
||||
//
|
||||
this.AutoScaleDimensions = new System.Drawing.SizeF(7F, 15F);
|
||||
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
|
||||
this.BackColor = System.Drawing.SystemColors.ControlLight;
|
||||
this.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle;
|
||||
this.Controls.Add(this.moveLastBtn);
|
||||
this.Controls.Add(this.moveDownBtn);
|
||||
this.Controls.Add(this.moveFirstBtn);
|
||||
this.Controls.Add(this.moveUpBtn);
|
||||
this.Controls.Add(this.cancelBtn);
|
||||
this.Controls.Add(this.statusLbl);
|
||||
this.Controls.Add(this.etaLbl);
|
||||
this.Controls.Add(this.remainingTimeLbl);
|
||||
this.Controls.Add(this.progressBar1);
|
||||
this.Controls.Add(this.bookInfoLbl);
|
||||
this.Controls.Add(this.pictureBox1);
|
||||
this.Margin = new System.Windows.Forms.Padding(4, 2, 4, 2);
|
||||
this.Name = "ProcessBookControl";
|
||||
this.Size = new System.Drawing.Size(375, 86);
|
||||
((System.ComponentModel.ISupportInitialize)(this.pictureBox1)).EndInit();
|
||||
this.ResumeLayout(false);
|
||||
this.PerformLayout();
|
||||
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
private System.Windows.Forms.PictureBox pictureBox1;
|
||||
private System.Windows.Forms.ProgressBar progressBar1;
|
||||
private System.Windows.Forms.Label remainingTimeLbl;
|
||||
private System.Windows.Forms.Label etaLbl;
|
||||
private System.Windows.Forms.Label statusLbl;
|
||||
private System.Windows.Forms.Label bookInfoLbl;
|
||||
public System.Windows.Forms.Button cancelBtn;
|
||||
public System.Windows.Forms.Button moveUpBtn;
|
||||
public System.Windows.Forms.Button moveDownBtn;
|
||||
public System.Windows.Forms.Button moveFirstBtn;
|
||||
public System.Windows.Forms.Button moveLastBtn;
|
||||
}
|
||||
}
|
||||
175
Source/LibationWinForms/ProcessQueue/ProcessBookControl.cs
Normal file
175
Source/LibationWinForms/ProcessQueue/ProcessBookControl.cs
Normal file
@@ -0,0 +1,175 @@
|
||||
using System;
|
||||
using System.Drawing;
|
||||
using System.Windows.Forms;
|
||||
|
||||
namespace LibationWinForms.ProcessQueue
|
||||
{
|
||||
internal partial class ProcessBookControl : UserControl
|
||||
{
|
||||
private static int ControlNumberCounter = 0;
|
||||
|
||||
/// <summary>
|
||||
/// The contol's position within <see cref="VirtualFlowControl"/>
|
||||
/// </summary>
|
||||
public int ControlNumber { get; }
|
||||
private ProcessBookStatus Status { get; set; } = ProcessBookStatus.Queued;
|
||||
private readonly int CancelBtnDistanceFromEdge;
|
||||
private readonly int ProgressBarDistanceFromEdge;
|
||||
|
||||
public static Color FailedColor = Color.LightCoral;
|
||||
public static Color CancelledColor = Color.Khaki;
|
||||
public static Color QueuedColor = SystemColors.Control;
|
||||
public static Color SuccessColor = Color.PaleGreen;
|
||||
|
||||
public ProcessBookControl()
|
||||
{
|
||||
InitializeComponent();
|
||||
statusLbl.Text = "Queued";
|
||||
remainingTimeLbl.Visible = false;
|
||||
progressBar1.Visible = false;
|
||||
etaLbl.Visible = false;
|
||||
|
||||
CancelBtnDistanceFromEdge = Width - cancelBtn.Location.X;
|
||||
ProgressBarDistanceFromEdge = Width - progressBar1.Location.X - progressBar1.Width;
|
||||
ControlNumber = ControlNumberCounter++;
|
||||
}
|
||||
|
||||
public void SetCover(Image cover)
|
||||
{
|
||||
pictureBox1.Image = cover;
|
||||
}
|
||||
|
||||
public void SetBookInfo(string title)
|
||||
{
|
||||
bookInfoLbl.Text = title;
|
||||
}
|
||||
|
||||
public void SetProgrss(int progress)
|
||||
{
|
||||
//Disable slow fill
|
||||
//https://stackoverflow.com/a/5332770/3335599
|
||||
if (progress < progressBar1.Maximum)
|
||||
progressBar1.Value = progress + 1;
|
||||
progressBar1.Value = progress;
|
||||
}
|
||||
|
||||
public void SetRemainingTime(TimeSpan remaining)
|
||||
{
|
||||
remainingTimeLbl.Text = $"{remaining:mm\\:ss}";
|
||||
}
|
||||
|
||||
public void SetResult(ProcessBookResult result)
|
||||
{
|
||||
string statusText = default;
|
||||
switch (result)
|
||||
{
|
||||
case ProcessBookResult.Success:
|
||||
statusText = "Finished";
|
||||
Status = ProcessBookStatus.Completed;
|
||||
break;
|
||||
case ProcessBookResult.Cancelled:
|
||||
statusText = "Cancelled";
|
||||
Status = ProcessBookStatus.Cancelled;
|
||||
break;
|
||||
case ProcessBookResult.FailedRetry:
|
||||
statusText = "Queued";
|
||||
Status = ProcessBookStatus.Queued;
|
||||
break;
|
||||
case ProcessBookResult.FailedSkip:
|
||||
statusText = "Error, Skippping";
|
||||
Status = ProcessBookStatus.Failed;
|
||||
break;
|
||||
case ProcessBookResult.FailedAbort:
|
||||
statusText = "Error, Abort";
|
||||
Status = ProcessBookStatus.Failed;
|
||||
break;
|
||||
case ProcessBookResult.ValidationFail:
|
||||
statusText = "Validion fail";
|
||||
Status = ProcessBookStatus.Failed;
|
||||
break;
|
||||
case ProcessBookResult.None:
|
||||
statusText = "UNKNOWN";
|
||||
Status = ProcessBookStatus.Failed;
|
||||
break;
|
||||
}
|
||||
|
||||
SetStatus(Status, statusText);
|
||||
}
|
||||
|
||||
public void SetStatus(ProcessBookStatus status, string statusText = null)
|
||||
{
|
||||
Color backColor = default;
|
||||
switch (status)
|
||||
{
|
||||
case ProcessBookStatus.Completed:
|
||||
backColor = SuccessColor;
|
||||
Status = ProcessBookStatus.Completed;
|
||||
break;
|
||||
case ProcessBookStatus.Cancelled:
|
||||
backColor = CancelledColor;
|
||||
Status = ProcessBookStatus.Cancelled;
|
||||
break;
|
||||
case ProcessBookStatus.Queued:
|
||||
backColor = QueuedColor;
|
||||
Status = ProcessBookStatus.Queued;
|
||||
break;
|
||||
case ProcessBookStatus.Working:
|
||||
backColor = QueuedColor;
|
||||
Status = ProcessBookStatus.Working;
|
||||
break;
|
||||
case ProcessBookStatus.Failed:
|
||||
backColor = FailedColor;
|
||||
Status = ProcessBookStatus.Failed;
|
||||
break;
|
||||
}
|
||||
|
||||
SuspendLayout();
|
||||
|
||||
cancelBtn.Visible = Status is ProcessBookStatus.Queued or ProcessBookStatus.Working;
|
||||
moveLastBtn.Visible = Status == ProcessBookStatus.Queued;
|
||||
moveDownBtn.Visible = Status == ProcessBookStatus.Queued;
|
||||
moveUpBtn.Visible = Status == ProcessBookStatus.Queued;
|
||||
moveFirstBtn.Visible = Status == ProcessBookStatus.Queued;
|
||||
remainingTimeLbl.Visible = Status == ProcessBookStatus.Working;
|
||||
progressBar1.Visible = Status == ProcessBookStatus.Working;
|
||||
etaLbl.Visible = Status == ProcessBookStatus.Working;
|
||||
statusLbl.Visible = Status != ProcessBookStatus.Working;
|
||||
statusLbl.Text = statusText ?? Status.ToString();
|
||||
BackColor = backColor;
|
||||
|
||||
int deltaX = Width - cancelBtn.Location.X - CancelBtnDistanceFromEdge;
|
||||
|
||||
if (Status is ProcessBookStatus.Queued or ProcessBookStatus.Working && deltaX != 0)
|
||||
{
|
||||
//If the last book to occupy this control before resizing was not
|
||||
//queued, the buttons were not Visible so the Anchor property was
|
||||
//ignored. Manually resize and reposition everyhting
|
||||
|
||||
cancelBtn.Location = new Point(cancelBtn.Location.X + deltaX, cancelBtn.Location.Y);
|
||||
moveFirstBtn.Location = new Point(moveFirstBtn.Location.X + deltaX, moveFirstBtn.Location.Y);
|
||||
moveUpBtn.Location = new Point(moveUpBtn.Location.X + deltaX, moveUpBtn.Location.Y);
|
||||
moveDownBtn.Location = new Point(moveDownBtn.Location.X + deltaX, moveDownBtn.Location.Y);
|
||||
moveLastBtn.Location = new Point(moveLastBtn.Location.X + deltaX, moveLastBtn.Location.Y);
|
||||
etaLbl.Location = new Point(etaLbl.Location.X + deltaX, etaLbl.Location.Y);
|
||||
remainingTimeLbl.Location = new Point(remainingTimeLbl.Location.X + deltaX, remainingTimeLbl.Location.Y);
|
||||
progressBar1.Width = Width - ProgressBarDistanceFromEdge - progressBar1.Location.X;
|
||||
}
|
||||
|
||||
if (status == ProcessBookStatus.Working)
|
||||
{
|
||||
bookInfoLbl.Width = cancelBtn.Location.X - bookInfoLbl.Location.X - bookInfoLbl.Padding.Left + cancelBtn.Padding.Right;
|
||||
}
|
||||
else
|
||||
{
|
||||
bookInfoLbl.Width = moveLastBtn.Location.X - bookInfoLbl.Location.X - bookInfoLbl.Padding.Left + moveLastBtn.Padding.Right;
|
||||
}
|
||||
|
||||
ResumeLayout();
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return bookInfoLbl.Text ?? "[NO TITLE]";
|
||||
}
|
||||
}
|
||||
}
|
||||
868
Source/LibationWinForms/ProcessQueue/ProcessBookControl.resx
Normal file
868
Source/LibationWinForms/ProcessQueue/ProcessBookControl.resx
Normal file
@@ -0,0 +1,868 @@
|
||||
<root>
|
||||
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
|
||||
<xsd:element name="root" msdata:IsDataSet="true">
|
||||
<xsd:complexType>
|
||||
<xsd:choice maxOccurs="unbounded">
|
||||
<xsd:element name="metadata">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" use="required" type="xsd:string" />
|
||||
<xsd:attribute name="type" type="xsd:string" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="assembly">
|
||||
<xsd:complexType>
|
||||
<xsd:attribute name="alias" type="xsd:string" />
|
||||
<xsd:attribute name="name" type="xsd:string" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="data">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
|
||||
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="resheader">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:choice>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:schema>
|
||||
<resheader name="resmimetype">
|
||||
<value>text/microsoft-resx</value>
|
||||
</resheader>
|
||||
<resheader name="version">
|
||||
<value>2.0</value>
|
||||
</resheader>
|
||||
<resheader name="reader">
|
||||
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<resheader name="writer">
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<metadata name="pictureBox1.Locked" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
|
||||
<value>True</value>
|
||||
</metadata>
|
||||
<metadata name="progressBar1.Locked" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
|
||||
<value>True</value>
|
||||
</metadata>
|
||||
<metadata name="remainingTimeLbl.Locked" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
|
||||
<value>True</value>
|
||||
</metadata>
|
||||
<metadata name="etaLbl.Locked" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
|
||||
<value>True</value>
|
||||
</metadata>
|
||||
<metadata name="cancelBtn.Locked" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
|
||||
<value>True</value>
|
||||
</metadata>
|
||||
<assembly alias="System.Drawing" name="System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" />
|
||||
<data name="cancelBtn.BackgroundImage" type="System.Drawing.Bitmap, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
||||
<value>
|
||||
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
|
||||
/x9W31o+WFcHNAAAAABJRU5ErkJggg==
|
||||
</value>
|
||||
</data>
|
||||
<metadata name="statusLbl.Locked" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
|
||||
<value>True</value>
|
||||
</metadata>
|
||||
<metadata name="bookInfoLbl.Locked" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
|
||||
<value>True</value>
|
||||
</metadata>
|
||||
<metadata name="moveUpBtn.Locked" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
|
||||
<value>True</value>
|
||||
</metadata>
|
||||
<data name="moveUpBtn.BackgroundImage" type="System.Drawing.Bitmap, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
||||
<value>
|
||||
iVBORw0KGgoAAAANSUhEUgAAAMgAAABNCAYAAADjJSv1AAAAIGNIUk0AAHolAACAgwAA+f8AAIDpAAB1
|
||||
MAAA6mAAADqYAAAXb5JfxUYAAAAJcEhZcwAACwwAAAsMAT9AIsgAAATRSURBVHhe7Zlfp6ZVHIaHISIi
|
||||
Oo2I6ANEDDFERx3FMERERN8gIoaIoc8QERERnQ5DREQfICKio4iOYs/vmndmrNlz79nPs55nrWf9uS8u
|
||||
xn0y27vf2/rd9pWzszNr7QXK0Fp7UobW2pMytNaelKG19qQMrbUnZWitPSlDa+1JGaI5jHfCZ07/NDWR
|
||||
PVAhmuq8GP4Y8uH/Fr4amorIHqgQTVXeDv8O+eAf+l/4UWgqIXugQjRVeDb8MkyLcd7vQ14XUxjZAxWi
|
||||
Kc5rIadUWoaL/CvklTEFkT1QIZqifBxyQqUlWOLt0AO+ELIHKkRTBE6lH8L0S79WD/hCyB6oEM3ucCJx
|
||||
KqVf9lw94Asge6BCNLvBScRplH7B99IDfkdkD1SIZhc4hZYO8Vz/DN8KzUZkD1SIZjOcQDlDPNcvQg/4
|
||||
DcgeqBBNNpw8nD7pl7eWv4Ye8JnIHqgQTRacOpw86Ze2trxaH4ZmJbIHKkSzipJDPFcP+JXIHqgQzWJq
|
||||
DPFcPeBXIHugQjSLqD3Ec/WAX4DsgQrRPJUjh3iuHvCXIHugQjQX0sIQz9UD/inIHqgQzRNwonCqpF+4
|
||||
Xv0ufCE0CbIHKkTzGJwmnCjpl6x3eQWvh+YBsgcqRPMITpIehniun4ce8IHsgQrR3D9BOEXSL9Oo/hJO
|
||||
P+BlD1SIk8Pp0esQz3X6AS97oEKcFE4NTo70izOb0w542QMV4oRwYnBqpF+WWZ1ywMseqBAn44Pw3zD9
|
||||
ktjJBrzsgQpxEmYa4rlOM+BlD1SIEzDjEM+V15VXdmhkD1SIA+Mhni+v7fPhkMgeqBAHxUN8u7y6b4bD
|
||||
IXugQhwQD/F9vRUONeBlD1SIA8FJ4CFexp/DYQa87IEKcRA4BTzEyzrMgJc9UCF2Dk8/J0D6i7Rl7X7A
|
||||
yx6oEDvmlZCnP/3l2Tp2PeBlD1SInfJ+6CF+vF0OeNkDFWJn8LR/G6a/JHusvOK85t0ge6BC7Aie9D/C
|
||||
9Jdj25DXnFe9C2QPVIgdcDXkKf8/TH8ptj153Zsf8LIHKsTG8RDvT175pge87IEKsWE8xPuV177ZAS97
|
||||
oEJsEA/xcWxywMseqBAbw0N8PJsb8LIHKsRG8BAf32YGvOyBCrEBXg49xOewiQEve6BCPJj3Qg/xuXw4
|
||||
4LkaDkH2QIV4EDy134TpB2fnkquB66E6sgcqxAO4Fv4eph+WnVOuB66IqsgeqBArwpP6Weghbs/LNVFt
|
||||
wMseqBArwVP6U5h+KNamclVUGfCyByrECtwMPcTtEqsMeNkDFWJBngu/DtMPwNolFh3wsgcqxEK8EXqI
|
||||
2y0WG/CyByrEnfEQt3u7+4CXPVAh7oiHuC0l1wh/HtgF2QMV4k54iNvScpVwnWwe8LIHKsSNeIjb2nKl
|
||||
bBrwsgcqxA14iNuj5FrhaslC9kCFmAFP3Kehh7g9Wq6X1QNe9kCFuBKetrth+kNae6SrB7zsgQpxBTfC
|
||||
f8L0h7O2BVcNeNkDFeICGOJfhekPZG2LLhrwsgcqxEt4PfQQtz156YCXPVAhXgBP1Sehh7jtVQY8188T
|
||||
yB6oEAUvhXfC9D+ztke5fvhzxGPIHqgQz/Fu6CFuR5IriD9LPBrwsgcqxAd4iNvR5c8T9we87IEKMfAQ
|
||||
t7PIdXRT9kCF1lo8u3IPfFOKqVljg2IAAAAASUVORK5CYII=
|
||||
</value>
|
||||
</data>
|
||||
<metadata name="moveDownBtn.Locked" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
|
||||
<value>True</value>
|
||||
</metadata>
|
||||
<data name="moveDownBtn.BackgroundImage" type="System.Drawing.Bitmap, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
||||
<value>
|
||||
iVBORw0KGgoAAAANSUhEUgAAAMgAAABNCAYAAADjJSv1AAAAIGNIUk0AAHolAACAgwAA+f8AAIDpAAB1
|
||||
MAAA6mAAADqYAAAXb5JfxUYAAAAJcEhZcwAACwwAAAsMAT9AIsgAAATWSURBVHhe7Zlfh+1lGIaHiIiI
|
||||
6DQiog8QERHRUacRERHRN4hNRET0GSIiIvoAERERnUZEdBTRUUzP1dLe7559zTtr1vr9/90XF3Uf7D17
|
||||
Zt3e5zYXl5eXMcZr9PDi4o3yr5L/iXHL/lI+bz1ADw88VX5Xtn9YjFvys/LRUnuAHt7jofJO+U/Z/sEx
|
||||
rtk/y9fLu1gP0MMHeaHkKWr/khjX6Lcl19F9WA/QQ+ex8vOy/ctiXItcQe+XXEUPYD1AD/tkwMe1+d8Q
|
||||
L6/FeoAe3gxP1Pdl+0XEuETvDvEe1gP08Dh4qj4oM+DjEuXKuW+I97AeoIe348Xy17L94mKcU3498cAQ
|
||||
72E9QA9vDwP+i7L9ImOcWq4Zfi2hQ7yH9QA9PJ0M+DiXDHF+HXES1gP08Dwy4OPU8usHrpiTsR6gh+eT
|
||||
AR+nkGuFq+VsrAfo4XBkwMex5Eq51RDvYT1AD4clAz4OKVcJ18mth3gP6wF6OA5vlhnw8Ry5RrhKBsd6
|
||||
gB6Ox9NlBnw8xbOHeA/rAXo4Lg+XPJHtPz7G6xxsiPewHqCH08BT+VvZfjNibB10iPewHqCH08GT+WXZ
|
||||
flNiHGWI97AeoIfTkwEf/3e0Id7DeoAezkMGfOTXAaMN8R7WA/RwPjLg9ynXA1fEbFgP0MP5yYDfj1wN
|
||||
XA+zYj1AD5dBBvz25Vrgapgd6wF6uCzeKjPgtyXXweRDvIf1AD1cHs+UP5TtNzmuU66CWYZ4D+sBerhM
|
||||
eIo/LNtvdlyPXAFcA4vEeoAeLpuXygz4dcnrzxWwWKwH6OHyebzMgF+HixniPawH6OF6yIBfrosb4j2s
|
||||
B+jhusiAX56LHOI9rAfo4frIgF+Gix7iPawH6OF6yYCfz8UP8R7WA/Rw3WTATy+v9+KHeA/rAXq4Dd4u
|
||||
/y7bH2QcVl5rXu3VYz1AD7cDT/6PZftDjcPIK81rvQmsB+jhtuDp/6hsf7jxdHmVeZ03hfUAPdwmL5cZ
|
||||
8OfJa7zaId7DeoAebpcM+NPlFV71EO9hPUAPt08G/PHy6vL6bhrrAXq4DzLgb/ar8oly81gP0MP9kAHv
|
||||
bnKI97AeoIf7IwP+npsd4j2sB+jhPuGU4KRoPyx7c9NDvIf1AD3cN++UexvwuxjiPawH6GHgxPipbD9E
|
||||
W3U3Q7yH9QA9DMCp8XHZfpi2JK8kr2UorAfoYWh5pdzagOd13N0Q72E9QA/DVbY04HkVdznEe1gP0MNw
|
||||
HWse8L+XvIZBsB6gh6HHGgf81+Xuh3gP6wF6GG5iLQOe1+7dMtyA9QA9DMfCycLp0n4olyKv3LNlOALr
|
||||
AXoYbgOnCydM++Gc20/KR8pwJNYD9DCcAqfM3AM+Q/xErAfoYTgVTpq5BnyG+BlYD9DDcA6cNlMO+Azx
|
||||
AbAeoIdhCKYY8BniA2E9QA/DUIw54DPEB8R6gB6GoRlywP9RZogPjPUAPQxjMMSA/6bMEB8B6wF6GMaC
|
||||
k4jTqP3QHyOvz3tlGAnrAXoYxoYTiVOpLcF1/lw+V4YRsR6gh2EKOJU4mdoyXPXTMkN8AqwH6GGYEk6n
|
||||
qwOe1+XVMkyE9QA9DFPDCcUpxTefV+XJMkyI9QA9DHPAKfXa4T/D1FgPUMMY40ENY4wHNYwxHtQwxnhQ
|
||||
wxjjQQ1jjAc1jDEe1DDGiJcX/wKcO4zm90rrbQAAAABJRU5ErkJggg==
|
||||
</value>
|
||||
</data>
|
||||
<metadata name="moveFirstBtn.Locked" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
|
||||
<value>True</value>
|
||||
</metadata>
|
||||
<data name="moveFirstBtn.BackgroundImage" type="System.Drawing.Bitmap, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
||||
<value>
|
||||
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
|
||||
jfkqyMbMAAAAAElFTkSuQmCC
|
||||
</value>
|
||||
</data>
|
||||
<metadata name="moveLastBtn.Locked" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
|
||||
<value>True</value>
|
||||
</metadata>
|
||||
<data name="moveLastBtn.BackgroundImage" type="System.Drawing.Bitmap, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
||||
<value>
|
||||
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
|
||||
6l8KOJAbKVKmPQAAAABJRU5ErkJggg==
|
||||
</value>
|
||||
</data>
|
||||
<metadata name="$this.Locked" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
|
||||
<value>True</value>
|
||||
</metadata>
|
||||
</root>
|
||||
47
Source/LibationWinForms/ProcessQueue/ProcessBookForm.Designer.cs
generated
Normal file
47
Source/LibationWinForms/ProcessQueue/ProcessBookForm.Designer.cs
generated
Normal file
@@ -0,0 +1,47 @@
|
||||
namespace LibationWinForms.ProcessQueue
|
||||
{
|
||||
partial class ProcessBookForm
|
||||
{
|
||||
/// <summary>
|
||||
/// Required designer variable.
|
||||
/// </summary>
|
||||
private System.ComponentModel.IContainer components = null;
|
||||
|
||||
/// <summary>
|
||||
/// Clean up any resources being used.
|
||||
/// </summary>
|
||||
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
if (disposing && (components != null))
|
||||
{
|
||||
components.Dispose();
|
||||
}
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
|
||||
#region Windows Form Designer generated code
|
||||
|
||||
/// <summary>
|
||||
/// Required method for Designer support - do not modify
|
||||
/// the contents of this method with the code editor.
|
||||
/// </summary>
|
||||
private void InitializeComponent()
|
||||
{
|
||||
this.SuspendLayout();
|
||||
//
|
||||
// ProcessBookForm
|
||||
//
|
||||
this.AutoScaleDimensions = new System.Drawing.SizeF(7F, 15F);
|
||||
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
|
||||
this.ClientSize = new System.Drawing.Size(522, 638);
|
||||
this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.SizableToolWindow;
|
||||
this.Name = "ProcessBookForm";
|
||||
this.Text = "Book Processing Queue";
|
||||
this.ResumeLayout(false);
|
||||
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
26
Source/LibationWinForms/ProcessQueue/ProcessBookForm.cs
Normal file
26
Source/LibationWinForms/ProcessQueue/ProcessBookForm.cs
Normal file
@@ -0,0 +1,26 @@
|
||||
using System.Windows.Forms;
|
||||
|
||||
namespace LibationWinForms.ProcessQueue
|
||||
{
|
||||
public partial class ProcessBookForm : Form
|
||||
{
|
||||
private Control _dockControl;
|
||||
public int WidthChange { get; set; }
|
||||
public ProcessBookForm()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
public void PassControl(Control dockControl)
|
||||
{
|
||||
_dockControl = dockControl;
|
||||
Controls.Add(_dockControl);
|
||||
}
|
||||
|
||||
public Control RegainControl()
|
||||
{
|
||||
Controls.Remove(_dockControl);
|
||||
return _dockControl;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<root>
|
||||
<root>
|
||||
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
|
||||
<xsd:element name="root" msdata:IsDataSet="true">
|
||||
341
Source/LibationWinForms/ProcessQueue/ProcessQueueControl.Designer.cs
generated
Normal file
341
Source/LibationWinForms/ProcessQueue/ProcessQueueControl.Designer.cs
generated
Normal file
@@ -0,0 +1,341 @@
|
||||
namespace LibationWinForms.ProcessQueue
|
||||
{
|
||||
partial class ProcessQueueControl
|
||||
{
|
||||
/// <summary>
|
||||
/// Required designer variable.
|
||||
/// </summary>
|
||||
private System.ComponentModel.IContainer components = null;
|
||||
|
||||
/// <summary>
|
||||
/// Clean up any resources being used.
|
||||
/// </summary>
|
||||
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
if (disposing && (components != null))
|
||||
{
|
||||
components.Dispose();
|
||||
}
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
|
||||
#region Component Designer generated code
|
||||
|
||||
/// <summary>
|
||||
/// Required method for Designer support - do not modify
|
||||
/// the contents of this method with the code editor.
|
||||
/// </summary>
|
||||
private void InitializeComponent()
|
||||
{
|
||||
this.components = new System.ComponentModel.Container();
|
||||
System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(ProcessQueueControl));
|
||||
System.Windows.Forms.DataGridViewCellStyle dataGridViewCellStyle1 = new System.Windows.Forms.DataGridViewCellStyle();
|
||||
this.statusStrip1 = new System.Windows.Forms.StatusStrip();
|
||||
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.runningTimeLbl = new System.Windows.Forms.ToolStripStatusLabel();
|
||||
this.tabControl1 = new System.Windows.Forms.TabControl();
|
||||
this.tabPage1 = new System.Windows.Forms.TabPage();
|
||||
this.panel3 = new System.Windows.Forms.Panel();
|
||||
this.virtualFlowControl2 = new LibationWinForms.ProcessQueue.VirtualFlowControl();
|
||||
this.panel1 = new System.Windows.Forms.Panel();
|
||||
this.btnCleanFinished = new System.Windows.Forms.Button();
|
||||
this.cancelAllBtn = new System.Windows.Forms.Button();
|
||||
this.tabPage2 = new System.Windows.Forms.TabPage();
|
||||
this.logDGV = new System.Windows.Forms.DataGridView();
|
||||
this.timestampColumn = new System.Windows.Forms.DataGridViewTextBoxColumn();
|
||||
this.logEntryColumn = new System.Windows.Forms.DataGridViewTextBoxColumn();
|
||||
this.panel4 = new System.Windows.Forms.Panel();
|
||||
this.panel2 = new System.Windows.Forms.Panel();
|
||||
this.clearLogBtn = new System.Windows.Forms.Button();
|
||||
this.counterTimer = new System.Windows.Forms.Timer(this.components);
|
||||
this.logCopyBtn = new System.Windows.Forms.Button();
|
||||
this.statusStrip1.SuspendLayout();
|
||||
this.tabControl1.SuspendLayout();
|
||||
this.tabPage1.SuspendLayout();
|
||||
this.panel1.SuspendLayout();
|
||||
this.tabPage2.SuspendLayout();
|
||||
((System.ComponentModel.ISupportInitialize)(this.logDGV)).BeginInit();
|
||||
this.panel2.SuspendLayout();
|
||||
this.SuspendLayout();
|
||||
//
|
||||
// statusStrip1
|
||||
//
|
||||
this.statusStrip1.ImageScalingSize = new System.Drawing.Size(20, 20);
|
||||
this.statusStrip1.Items.AddRange(new System.Windows.Forms.ToolStripItem[] {
|
||||
this.toolStripProgressBar1,
|
||||
this.queueNumberLbl,
|
||||
this.completedNumberLbl,
|
||||
this.errorNumberLbl,
|
||||
this.toolStripStatusLabel1,
|
||||
this.runningTimeLbl});
|
||||
this.statusStrip1.Location = new System.Drawing.Point(0, 483);
|
||||
this.statusStrip1.Name = "statusStrip1";
|
||||
this.statusStrip1.Size = new System.Drawing.Size(404, 25);
|
||||
this.statusStrip1.TabIndex = 1;
|
||||
this.statusStrip1.Text = "baseStatusStrip";
|
||||
//
|
||||
// toolStripProgressBar1
|
||||
//
|
||||
this.toolStripProgressBar1.Name = "toolStripProgressBar1";
|
||||
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
|
||||
//
|
||||
this.toolStripStatusLabel1.Name = "toolStripStatusLabel1";
|
||||
this.toolStripStatusLabel1.Size = new System.Drawing.Size(77, 20);
|
||||
this.toolStripStatusLabel1.Spring = true;
|
||||
//
|
||||
// runningTimeLbl
|
||||
//
|
||||
this.runningTimeLbl.AutoSize = false;
|
||||
this.runningTimeLbl.Name = "runningTimeLbl";
|
||||
this.runningTimeLbl.Size = new System.Drawing.Size(41, 20);
|
||||
this.runningTimeLbl.Text = "[TIME]";
|
||||
//
|
||||
// tabControl1
|
||||
//
|
||||
this.tabControl1.Controls.Add(this.tabPage1);
|
||||
this.tabControl1.Controls.Add(this.tabPage2);
|
||||
this.tabControl1.Dock = System.Windows.Forms.DockStyle.Fill;
|
||||
this.tabControl1.Location = new System.Drawing.Point(0, 0);
|
||||
this.tabControl1.Margin = new System.Windows.Forms.Padding(0);
|
||||
this.tabControl1.Name = "tabControl1";
|
||||
this.tabControl1.SelectedIndex = 0;
|
||||
this.tabControl1.Size = new System.Drawing.Size(404, 483);
|
||||
this.tabControl1.TabIndex = 3;
|
||||
//
|
||||
// tabPage1
|
||||
//
|
||||
this.tabPage1.Controls.Add(this.panel3);
|
||||
this.tabPage1.Controls.Add(this.virtualFlowControl2);
|
||||
this.tabPage1.Controls.Add(this.panel1);
|
||||
this.tabPage1.Location = new System.Drawing.Point(4, 24);
|
||||
this.tabPage1.Name = "tabPage1";
|
||||
this.tabPage1.Padding = new System.Windows.Forms.Padding(3);
|
||||
this.tabPage1.Size = new System.Drawing.Size(396, 455);
|
||||
this.tabPage1.TabIndex = 0;
|
||||
this.tabPage1.Text = "Process Queue";
|
||||
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
|
||||
//
|
||||
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.Location = new System.Drawing.Point(3, 3);
|
||||
this.virtualFlowControl2.Name = "virtualFlowControl2";
|
||||
this.virtualFlowControl2.Size = new System.Drawing.Size(390, 424);
|
||||
this.virtualFlowControl2.TabIndex = 3;
|
||||
this.virtualFlowControl2.VirtualControlCount = 0;
|
||||
//
|
||||
// panel1
|
||||
//
|
||||
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.cancelAllBtn);
|
||||
this.panel1.Dock = System.Windows.Forms.DockStyle.Bottom;
|
||||
this.panel1.Location = new System.Drawing.Point(3, 427);
|
||||
this.panel1.Name = "panel1";
|
||||
this.panel1.Size = new System.Drawing.Size(390, 25);
|
||||
this.panel1.TabIndex = 2;
|
||||
//
|
||||
// btnCleanFinished
|
||||
//
|
||||
this.btnCleanFinished.Dock = System.Windows.Forms.DockStyle.Right;
|
||||
this.btnCleanFinished.Location = new System.Drawing.Point(298, 0);
|
||||
this.btnCleanFinished.Name = "btnCleanFinished";
|
||||
this.btnCleanFinished.Size = new System.Drawing.Size(90, 23);
|
||||
this.btnCleanFinished.TabIndex = 3;
|
||||
this.btnCleanFinished.Text = "Clear Finished";
|
||||
this.btnCleanFinished.UseVisualStyleBackColor = true;
|
||||
this.btnCleanFinished.Click += new System.EventHandler(this.btnClearFinished_Click);
|
||||
//
|
||||
// cancelAllBtn
|
||||
//
|
||||
this.cancelAllBtn.Dock = System.Windows.Forms.DockStyle.Left;
|
||||
this.cancelAllBtn.Location = new System.Drawing.Point(0, 0);
|
||||
this.cancelAllBtn.Name = "cancelAllBtn";
|
||||
this.cancelAllBtn.Size = new System.Drawing.Size(75, 23);
|
||||
this.cancelAllBtn.TabIndex = 2;
|
||||
this.cancelAllBtn.Text = "Cancel All";
|
||||
this.cancelAllBtn.UseVisualStyleBackColor = true;
|
||||
this.cancelAllBtn.Click += new System.EventHandler(this.cancelAllBtn_Click);
|
||||
//
|
||||
// tabPage2
|
||||
//
|
||||
this.tabPage2.Controls.Add(this.logDGV);
|
||||
this.tabPage2.Controls.Add(this.panel4);
|
||||
this.tabPage2.Controls.Add(this.panel2);
|
||||
this.tabPage2.Location = new System.Drawing.Point(4, 24);
|
||||
this.tabPage2.Name = "tabPage2";
|
||||
this.tabPage2.Padding = new System.Windows.Forms.Padding(3);
|
||||
this.tabPage2.Size = new System.Drawing.Size(396, 455);
|
||||
this.tabPage2.TabIndex = 1;
|
||||
this.tabPage2.Text = "Log";
|
||||
this.tabPage2.UseVisualStyleBackColor = true;
|
||||
//
|
||||
// logDGV
|
||||
//
|
||||
this.logDGV.AllowUserToAddRows = false;
|
||||
this.logDGV.AllowUserToDeleteRows = false;
|
||||
this.logDGV.AllowUserToOrderColumns = true;
|
||||
this.logDGV.AutoSizeRowsMode = System.Windows.Forms.DataGridViewAutoSizeRowsMode.AllCells;
|
||||
this.logDGV.ColumnHeadersHeightSizeMode = System.Windows.Forms.DataGridViewColumnHeadersHeightSizeMode.AutoSize;
|
||||
this.logDGV.Columns.AddRange(new System.Windows.Forms.DataGridViewColumn[] {
|
||||
this.timestampColumn,
|
||||
this.logEntryColumn});
|
||||
this.logDGV.Dock = System.Windows.Forms.DockStyle.Fill;
|
||||
this.logDGV.Location = new System.Drawing.Point(3, 3);
|
||||
this.logDGV.Name = "logDGV";
|
||||
this.logDGV.RowHeadersVisible = false;
|
||||
this.logDGV.RowTemplate.Height = 40;
|
||||
this.logDGV.Size = new System.Drawing.Size(390, 419);
|
||||
this.logDGV.TabIndex = 3;
|
||||
this.logDGV.Resize += new System.EventHandler(this.LogDGV_Resize);
|
||||
//
|
||||
// timestampColumn
|
||||
//
|
||||
this.timestampColumn.AutoSizeMode = System.Windows.Forms.DataGridViewAutoSizeColumnMode.DisplayedCells;
|
||||
this.timestampColumn.HeaderText = "Timestamp";
|
||||
this.timestampColumn.Name = "timestampColumn";
|
||||
this.timestampColumn.ReadOnly = true;
|
||||
this.timestampColumn.Width = 91;
|
||||
//
|
||||
// logEntryColumn
|
||||
//
|
||||
dataGridViewCellStyle1.WrapMode = System.Windows.Forms.DataGridViewTriState.True;
|
||||
this.logEntryColumn.DefaultCellStyle = dataGridViewCellStyle1;
|
||||
this.logEntryColumn.HeaderText = "Log";
|
||||
this.logEntryColumn.Name = "logEntryColumn";
|
||||
this.logEntryColumn.ReadOnly = 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
|
||||
//
|
||||
this.panel2.BackColor = System.Drawing.SystemColors.Control;
|
||||
this.panel2.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle;
|
||||
this.panel2.Controls.Add(this.logCopyBtn);
|
||||
this.panel2.Controls.Add(this.clearLogBtn);
|
||||
this.panel2.Dock = System.Windows.Forms.DockStyle.Bottom;
|
||||
this.panel2.Location = new System.Drawing.Point(3, 427);
|
||||
this.panel2.Name = "panel2";
|
||||
this.panel2.Size = new System.Drawing.Size(390, 25);
|
||||
this.panel2.TabIndex = 1;
|
||||
//
|
||||
// clearLogBtn
|
||||
//
|
||||
this.clearLogBtn.Dock = System.Windows.Forms.DockStyle.Left;
|
||||
this.clearLogBtn.Location = new System.Drawing.Point(0, 0);
|
||||
this.clearLogBtn.Name = "clearLogBtn";
|
||||
this.clearLogBtn.Size = new System.Drawing.Size(60, 23);
|
||||
this.clearLogBtn.TabIndex = 0;
|
||||
this.clearLogBtn.Text = "Clear";
|
||||
this.clearLogBtn.UseVisualStyleBackColor = true;
|
||||
this.clearLogBtn.Click += new System.EventHandler(this.clearLogBtn_Click);
|
||||
//
|
||||
// counterTimer
|
||||
//
|
||||
this.counterTimer.Interval = 950;
|
||||
this.counterTimer.Tick += new System.EventHandler(this.CounterTimer_Tick);
|
||||
//
|
||||
// logCopyBtn
|
||||
//
|
||||
this.logCopyBtn.Dock = System.Windows.Forms.DockStyle.Right;
|
||||
this.logCopyBtn.Location = new System.Drawing.Point(331, 0);
|
||||
this.logCopyBtn.Name = "logCopyBtn";
|
||||
this.logCopyBtn.Size = new System.Drawing.Size(57, 23);
|
||||
this.logCopyBtn.TabIndex = 1;
|
||||
this.logCopyBtn.Text = "Copy";
|
||||
this.logCopyBtn.UseVisualStyleBackColor = true;
|
||||
this.logCopyBtn.Click += new System.EventHandler(this.LogCopyBtn_Click);
|
||||
//
|
||||
// ProcessQueueControl
|
||||
//
|
||||
this.AutoScaleDimensions = new System.Drawing.SizeF(7F, 15F);
|
||||
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
|
||||
this.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle;
|
||||
this.Controls.Add(this.tabControl1);
|
||||
this.Controls.Add(this.statusStrip1);
|
||||
this.Name = "ProcessQueueControl";
|
||||
this.Size = new System.Drawing.Size(404, 508);
|
||||
this.statusStrip1.ResumeLayout(false);
|
||||
this.statusStrip1.PerformLayout();
|
||||
this.tabControl1.ResumeLayout(false);
|
||||
this.tabPage1.ResumeLayout(false);
|
||||
this.panel1.ResumeLayout(false);
|
||||
this.tabPage2.ResumeLayout(false);
|
||||
((System.ComponentModel.ISupportInitialize)(this.logDGV)).EndInit();
|
||||
this.panel2.ResumeLayout(false);
|
||||
this.ResumeLayout(false);
|
||||
this.PerformLayout();
|
||||
|
||||
}
|
||||
|
||||
#endregion
|
||||
private System.Windows.Forms.StatusStrip statusStrip1;
|
||||
private System.Windows.Forms.ToolStripProgressBar toolStripProgressBar1;
|
||||
private System.Windows.Forms.TabControl tabControl1;
|
||||
private System.Windows.Forms.TabPage tabPage1;
|
||||
private System.Windows.Forms.Panel panel1;
|
||||
private System.Windows.Forms.TabPage tabPage2;
|
||||
private System.Windows.Forms.Button btnCleanFinished;
|
||||
private System.Windows.Forms.Button cancelAllBtn;
|
||||
private System.Windows.Forms.Panel panel2;
|
||||
private System.Windows.Forms.Button clearLogBtn;
|
||||
private System.Windows.Forms.ToolStripStatusLabel toolStripStatusLabel1;
|
||||
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;
|
||||
private System.Windows.Forms.ToolStripStatusLabel runningTimeLbl;
|
||||
private System.Windows.Forms.Timer counterTimer;
|
||||
private System.Windows.Forms.DataGridView logDGV;
|
||||
private System.Windows.Forms.DataGridViewTextBoxColumn timestampColumn;
|
||||
private System.Windows.Forms.DataGridViewTextBoxColumn logEntryColumn;
|
||||
private System.Windows.Forms.Button logCopyBtn;
|
||||
}
|
||||
}
|
||||
366
Source/LibationWinForms/ProcessQueue/ProcessQueueControl.cs
Normal file
366
Source/LibationWinForms/ProcessQueue/ProcessQueueControl.cs
Normal file
@@ -0,0 +1,366 @@
|
||||
using LibationWinForms.BookLiberation;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Forms;
|
||||
|
||||
namespace LibationWinForms.ProcessQueue
|
||||
{
|
||||
internal partial class ProcessQueueControl : UserControl, ILogForm
|
||||
{
|
||||
private TrackedQueue<ProcessBook> Queue = new();
|
||||
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 bool Running => !QueueRunner?.IsCompleted ?? false;
|
||||
public ToolStripButton popoutBtn = new();
|
||||
|
||||
private System.Threading.SynchronizationContext syncContext { get; } = System.Threading.SynchronizationContext.Current;
|
||||
|
||||
public ProcessQueueControl()
|
||||
{
|
||||
InitializeComponent();
|
||||
Logger = LogMe.RegisterForm(this);
|
||||
|
||||
runningTimeLbl.Text = string.Empty;
|
||||
popoutBtn.DisplayStyle = ToolStripItemDisplayStyle.Text;
|
||||
popoutBtn.Name = "popoutBtn";
|
||||
popoutBtn.Text = "Pop Out";
|
||||
popoutBtn.TextAlign = System.Drawing.ContentAlignment.MiddleCenter;
|
||||
popoutBtn.Alignment = ToolStripItemAlignment.Right;
|
||||
popoutBtn.Anchor = AnchorStyles.Bottom | AnchorStyles.Right;
|
||||
|
||||
statusStrip1.Items.Add(popoutBtn);
|
||||
|
||||
virtualFlowControl2.RequestData += VirtualFlowControl1_RequestData;
|
||||
virtualFlowControl2.ButtonClicked += VirtualFlowControl2_ButtonClicked;
|
||||
|
||||
Queue.QueuededCountChanged += Queue_QueuededCountChanged;
|
||||
Queue.CompletedCountChanged += Queue_CompletedCountChanged;
|
||||
|
||||
QueuedCount = 0;
|
||||
ErrorCount = 0;
|
||||
CompletedCount = 0;
|
||||
}
|
||||
|
||||
public void AddDownloadPdf(IEnumerable<DataLayer.LibraryBook> entries)
|
||||
{
|
||||
foreach (var entry in entries)
|
||||
AddDownloadPdf(entry);
|
||||
}
|
||||
|
||||
public void AddDownloadDecrypt(IEnumerable<DataLayer.LibraryBook> entries)
|
||||
{
|
||||
foreach (var entry in entries)
|
||||
AddDownloadDecrypt(entry);
|
||||
}
|
||||
|
||||
public void AddConvertMp3(IEnumerable<DataLayer.LibraryBook> entries)
|
||||
{
|
||||
foreach (var entry in entries)
|
||||
AddConvertMp3(entry);
|
||||
}
|
||||
|
||||
public void AddDownloadPdf(DataLayer.LibraryBook libraryBook)
|
||||
{
|
||||
if (Queue.Any(b => b?.LibraryBook?.Book?.AudibleProductId == libraryBook.Book.AudibleProductId))
|
||||
return;
|
||||
|
||||
ProcessBook pbook = new(libraryBook, Logger);
|
||||
pbook.PropertyChanged += Pbook_DataAvailable;
|
||||
pbook.AddDownloadPdf();
|
||||
AddToQueue(pbook);
|
||||
}
|
||||
|
||||
public void AddDownloadDecrypt(DataLayer.LibraryBook libraryBook)
|
||||
{
|
||||
if (Queue.Any(b => b?.LibraryBook?.Book?.AudibleProductId == libraryBook.Book.AudibleProductId))
|
||||
return;
|
||||
|
||||
ProcessBook pbook = new(libraryBook, Logger);
|
||||
pbook.PropertyChanged += Pbook_DataAvailable;
|
||||
pbook.AddDownloadDecryptBook();
|
||||
pbook.AddDownloadPdf();
|
||||
AddToQueue(pbook);
|
||||
}
|
||||
|
||||
public void AddConvertMp3(DataLayer.LibraryBook libraryBook)
|
||||
{
|
||||
if (Queue.Any(b => b?.LibraryBook?.Book?.AudibleProductId == libraryBook.Book.AudibleProductId))
|
||||
return;
|
||||
|
||||
ProcessBook pbook = new(libraryBook, Logger);
|
||||
pbook.PropertyChanged += Pbook_DataAvailable;
|
||||
pbook.AddConvertToMp3();
|
||||
AddToQueue(pbook);
|
||||
}
|
||||
|
||||
private void AddToQueue(ProcessBook pbook)
|
||||
{
|
||||
syncContext.Post(_ =>
|
||||
{
|
||||
Queue.Enqueue(pbook);
|
||||
if (!Running)
|
||||
QueueRunner = QueueLoop();
|
||||
},
|
||||
null);
|
||||
}
|
||||
|
||||
DateTime StartintTime;
|
||||
private async Task QueueLoop()
|
||||
{
|
||||
StartintTime = DateTime.Now;
|
||||
counterTimer.Start();
|
||||
|
||||
while (Queue.MoveNext())
|
||||
{
|
||||
var nextBook = Queue.Current;
|
||||
|
||||
var result = await nextBook.ProcessOneAsync();
|
||||
|
||||
if (result == ProcessBookResult.FailedRetry)
|
||||
Queue.Enqueue(nextBook);
|
||||
else if (result == ProcessBookResult.ValidationFail)
|
||||
Queue.ClearCurrent();
|
||||
else if (result == ProcessBookResult.FailedAbort)
|
||||
return;
|
||||
}
|
||||
Queue_CompletedCountChanged(this, 0);
|
||||
counterTimer.Stop();
|
||||
virtualFlowControl2.VirtualControlCount = Queue.Count;
|
||||
UpdateAllControls();
|
||||
}
|
||||
|
||||
public void WriteLine(string text)
|
||||
{
|
||||
if (IsDisposed) return;
|
||||
|
||||
var timeStamp = DateTime.Now;
|
||||
logDGV.Rows.Add(timeStamp, text.Trim());
|
||||
}
|
||||
|
||||
#region Control event handlers
|
||||
|
||||
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 cancelAllBtn_Click(object sender, EventArgs e)
|
||||
{
|
||||
Queue.ClearQueue();
|
||||
Queue.Current?.Cancel();
|
||||
virtualFlowControl2.VirtualControlCount = Queue.Count;
|
||||
UpdateAllControls();
|
||||
}
|
||||
|
||||
private void btnClearFinished_Click(object sender, EventArgs e)
|
||||
{
|
||||
Queue.ClearCompleted();
|
||||
virtualFlowControl2.VirtualControlCount = Queue.Count;
|
||||
UpdateAllControls();
|
||||
|
||||
if (!Running)
|
||||
runningTimeLbl.Text = string.Empty;
|
||||
}
|
||||
|
||||
private void CounterTimer_Tick(object sender, EventArgs e)
|
||||
{
|
||||
string timeToStr(TimeSpan time)
|
||||
{
|
||||
string minsSecs = $"{time:mm\\:ss}";
|
||||
if (time.TotalHours >= 1)
|
||||
return $"{time.TotalHours:F0}:{minsSecs}";
|
||||
return minsSecs;
|
||||
}
|
||||
|
||||
if (Running)
|
||||
runningTimeLbl.Text = timeToStr(DateTime.Now - StartintTime);
|
||||
}
|
||||
|
||||
private void clearLogBtn_Click(object sender, EventArgs e)
|
||||
{
|
||||
logDGV.Rows.Clear();
|
||||
}
|
||||
|
||||
private void LogCopyBtn_Click(object sender, EventArgs e)
|
||||
{
|
||||
string logText = string.Join("\r\n", logDGV.Rows.Cast<DataGridViewRow>().Select(r => $"{r.Cells[0].Value}\t{r.Cells[1].Value}"));
|
||||
Clipboard.SetDataObject(logText, false, 5, 150);
|
||||
}
|
||||
|
||||
private void LogDGV_Resize(object sender, EventArgs e)
|
||||
{
|
||||
logDGV.Columns[1].Width = logDGV.Width - logDGV.Columns[0].Width;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region View-Model update event handling
|
||||
|
||||
/// <summary>
|
||||
/// Index of the first <see cref="ProcessBook"/> visible in the <see cref="VirtualFlowControl"/>
|
||||
/// </summary>
|
||||
private int FirstVisible = 0;
|
||||
/// <summary>
|
||||
/// Number of <see cref="ProcessBook"/> visible in the <see cref="VirtualFlowControl"/>
|
||||
/// </summary>
|
||||
private int NumVisible = 0;
|
||||
/// <summary>
|
||||
/// Controls displaying the <see cref="ProcessBook"/> state, starting with <see cref="FirstVisible"/>
|
||||
/// </summary>
|
||||
private IReadOnlyList<ProcessBookControl> Panels;
|
||||
|
||||
/// <summary>
|
||||
/// Updates the display of a single <see cref="ProcessBookControl"/> at <paramref name="queueIndex"/> within <see cref="Queue"/>
|
||||
/// </summary>
|
||||
/// <param name="queueIndex">index of the <see cref="ProcessBook"/> within the <see cref="Queue"/></param>
|
||||
/// <param name="propertyName">The nme of the property that needs updating. If null, all properties are updated.</param>
|
||||
private void UpdateControl(int queueIndex, string propertyName = null)
|
||||
{
|
||||
int i = queueIndex - FirstVisible;
|
||||
|
||||
if (i > NumVisible || i < 0) return;
|
||||
|
||||
var proc = Queue[queueIndex];
|
||||
|
||||
syncContext.Send(_ =>
|
||||
{
|
||||
Panels[i].SuspendLayout();
|
||||
if (propertyName is null or nameof(proc.Cover))
|
||||
Panels[i].SetCover(proc.Cover);
|
||||
if (propertyName is null or nameof(proc.BookText))
|
||||
Panels[i].SetBookInfo(proc.BookText);
|
||||
|
||||
if (proc.Result != ProcessBookResult.None)
|
||||
{
|
||||
Panels[i].SetResult(proc.Result);
|
||||
return;
|
||||
}
|
||||
|
||||
if (propertyName is null or nameof(proc.Status))
|
||||
Panels[i].SetStatus(proc.Status);
|
||||
if (propertyName is null or nameof(proc.Progress))
|
||||
Panels[i].SetProgrss(proc.Progress);
|
||||
if (propertyName is null or nameof(proc.TimeRemaining))
|
||||
Panels[i].SetRemainingTime(proc.TimeRemaining);
|
||||
Panels[i].ResumeLayout();
|
||||
},
|
||||
null);
|
||||
}
|
||||
|
||||
private void UpdateAllControls()
|
||||
{
|
||||
int numToShow = Math.Min(NumVisible, Queue.Count - FirstVisible);
|
||||
|
||||
for (int i = 0; i < numToShow; i++)
|
||||
UpdateControl(FirstVisible + i);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// View notified the model that a botton was clicked
|
||||
/// </summary>
|
||||
/// <param name="queueIndex">index of the <see cref="ProcessBook"/> within <see cref="Queue"/></param>
|
||||
/// <param name="panelClicked">The clicked control to update</param>
|
||||
private async void VirtualFlowControl2_ButtonClicked(int queueIndex, string buttonName, ProcessBookControl panelClicked)
|
||||
{
|
||||
ProcessBook item = Queue[queueIndex];
|
||||
if (buttonName == nameof(panelClicked.cancelBtn))
|
||||
{
|
||||
await item.Cancel();
|
||||
Queue.RemoveQueued(item);
|
||||
virtualFlowControl2.VirtualControlCount = Queue.Count;
|
||||
}
|
||||
else if (buttonName == nameof(panelClicked.moveFirstBtn))
|
||||
{
|
||||
Queue.MoveQueuePosition(item, QueuePosition.Fisrt);
|
||||
UpdateAllControls();
|
||||
}
|
||||
else if (buttonName == nameof(panelClicked.moveUpBtn))
|
||||
{
|
||||
Queue.MoveQueuePosition(item, QueuePosition.OneUp);
|
||||
UpdateControl(queueIndex);
|
||||
if (queueIndex > 0)
|
||||
UpdateControl(queueIndex - 1);
|
||||
}
|
||||
else if (buttonName == nameof(panelClicked.moveDownBtn))
|
||||
{
|
||||
Queue.MoveQueuePosition(item, QueuePosition.OneDown);
|
||||
UpdateControl(queueIndex);
|
||||
if (queueIndex + 1 < Queue.Count)
|
||||
UpdateControl(queueIndex + 1);
|
||||
}
|
||||
else if (buttonName == nameof(panelClicked.moveLastBtn))
|
||||
{
|
||||
Queue.MoveQueuePosition(item, QueuePosition.Last);
|
||||
UpdateAllControls();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// View needs updating
|
||||
/// </summary>
|
||||
private void VirtualFlowControl1_RequestData(int firstIndex, int numVisible, IReadOnlyList<ProcessBookControl> panelsToFill)
|
||||
{
|
||||
FirstVisible = firstIndex;
|
||||
NumVisible = numVisible;
|
||||
Panels = panelsToFill;
|
||||
UpdateAllControls();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Model updates the view
|
||||
/// </summary>
|
||||
private void Pbook_DataAvailable(object sender, PropertyChangedEventArgs e)
|
||||
{
|
||||
int index = Queue.IndexOf((ProcessBook)sender);
|
||||
UpdateControl(index, e.PropertyName);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
642
Source/LibationWinForms/ProcessQueue/ProcessQueueControl.resx
Normal file
642
Source/LibationWinForms/ProcessQueue/ProcessQueueControl.resx
Normal file
@@ -0,0 +1,642 @@
|
||||
<root>
|
||||
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
|
||||
<xsd:element name="root" msdata:IsDataSet="true">
|
||||
<xsd:complexType>
|
||||
<xsd:choice maxOccurs="unbounded">
|
||||
<xsd:element name="metadata">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" use="required" type="xsd:string" />
|
||||
<xsd:attribute name="type" type="xsd:string" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="assembly">
|
||||
<xsd:complexType>
|
||||
<xsd:attribute name="alias" type="xsd:string" />
|
||||
<xsd:attribute name="name" type="xsd:string" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="data">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
|
||||
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="resheader">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:choice>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:schema>
|
||||
<resheader name="resmimetype">
|
||||
<value>text/microsoft-resx</value>
|
||||
</resheader>
|
||||
<resheader name="version">
|
||||
<value>2.0</value>
|
||||
</resheader>
|
||||
<resheader name="reader">
|
||||
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<resheader name="writer">
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<metadata name="statusStrip1.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
|
||||
<value>17, 17</value>
|
||||
</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
|
||||
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
|
||||
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>
|
||||
<metadata name="timestampColumn.UserAddedColumn" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
|
||||
<value>True</value>
|
||||
</metadata>
|
||||
<metadata name="logEntryColumn.UserAddedColumn" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
|
||||
<value>True</value>
|
||||
</metadata>
|
||||
<metadata name="counterTimer.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
|
||||
<value>133, 17</value>
|
||||
</metadata>
|
||||
</root>
|
||||
230
Source/LibationWinForms/ProcessQueue/TrackedQueue[T].cs
Normal file
230
Source/LibationWinForms/ProcessQueue/TrackedQueue[T].cs
Normal file
@@ -0,0 +1,230 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace LibationWinForms.ProcessQueue
|
||||
{
|
||||
public enum QueuePosition
|
||||
{
|
||||
Fisrt,
|
||||
OneUp,
|
||||
OneDown,
|
||||
Last
|
||||
}
|
||||
|
||||
/*
|
||||
* This data structure is like lifting a metal chain one link at a time.
|
||||
* Each time you grab and lift a new link (MoveNext call):
|
||||
*
|
||||
* 1) you're holding a new link in your hand (Current)
|
||||
* 2) the remaining chain to be lifted shortens by 1 link (Queued)
|
||||
* 3) the pile of chain at your feet grows by 1 link (Completed)
|
||||
*
|
||||
* The index is the link position from the first link you lifted to the
|
||||
* last one in the chain.
|
||||
*/
|
||||
public class TrackedQueue<T> where T : class
|
||||
{
|
||||
public event EventHandler<int> CompletedCountChanged;
|
||||
public event EventHandler<int> QueuededCountChanged;
|
||||
public T Current { get; private set; }
|
||||
|
||||
public IReadOnlyList<T> Queued => _queued;
|
||||
public IReadOnlyList<T> Completed => _completed;
|
||||
|
||||
private readonly List<T> _queued = new();
|
||||
private readonly List<T> _completed = new();
|
||||
private readonly object lockObject = new();
|
||||
|
||||
public T this[int index]
|
||||
{
|
||||
get
|
||||
{
|
||||
lock (lockObject)
|
||||
{
|
||||
if (index < _completed.Count)
|
||||
return _completed[index];
|
||||
index -= _completed.Count;
|
||||
|
||||
if (index == 0 && Current != null) return Current;
|
||||
|
||||
if (Current != null) index--;
|
||||
|
||||
if (index < _queued.Count) return _queued.ElementAt(index);
|
||||
|
||||
throw new IndexOutOfRangeException();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public int Count
|
||||
{
|
||||
get
|
||||
{
|
||||
lock (lockObject)
|
||||
{
|
||||
return _queued.Count + _completed.Count + (Current == null ? 0 : 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public int IndexOf(T item)
|
||||
{
|
||||
lock (lockObject)
|
||||
{
|
||||
if (_completed.Contains(item))
|
||||
return _completed.IndexOf(item);
|
||||
|
||||
if (Current == item) return _completed.Count;
|
||||
|
||||
if (_queued.Contains(item))
|
||||
return _queued.IndexOf(item) + (Current is null ? 0 : 1);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
public bool RemoveQueued(T item)
|
||||
{
|
||||
lock (lockObject)
|
||||
{
|
||||
bool removed = _queued.Remove(item);
|
||||
if (removed)
|
||||
QueuededCountChanged?.Invoke(this, _queued.Count);
|
||||
return removed;
|
||||
}
|
||||
}
|
||||
|
||||
public void ClearCurrent()
|
||||
{
|
||||
lock(lockObject)
|
||||
Current = null;
|
||||
}
|
||||
|
||||
public bool RemoveCompleted(T item)
|
||||
{
|
||||
lock (lockObject)
|
||||
{
|
||||
bool removed = _completed.Remove(item);
|
||||
if (removed)
|
||||
CompletedCountChanged?.Invoke(this, _completed.Count);
|
||||
return removed;
|
||||
}
|
||||
}
|
||||
|
||||
public void ClearQueue()
|
||||
{
|
||||
lock (lockObject)
|
||||
{
|
||||
_queued.Clear();
|
||||
QueuededCountChanged?.Invoke(this, 0);
|
||||
}
|
||||
}
|
||||
|
||||
public void ClearCompleted()
|
||||
{
|
||||
lock (lockObject)
|
||||
{
|
||||
_completed.Clear();
|
||||
CompletedCountChanged?.Invoke(this, 0);
|
||||
}
|
||||
}
|
||||
|
||||
public bool Any(Func<T, bool> predicate)
|
||||
{
|
||||
lock (lockObject)
|
||||
{
|
||||
return (Current != null && predicate(Current)) || _completed.Any(predicate) || _queued.Any(predicate);
|
||||
}
|
||||
}
|
||||
|
||||
public void MoveQueuePosition(T item, QueuePosition requestedPosition)
|
||||
{
|
||||
lock (lockObject)
|
||||
{
|
||||
if (_queued.Count == 0 || !_queued.Contains(item)) return;
|
||||
|
||||
if ((requestedPosition == QueuePosition.Fisrt || requestedPosition == QueuePosition.OneUp) && _queued[0] == item)
|
||||
return;
|
||||
if ((requestedPosition == QueuePosition.Last || requestedPosition == QueuePosition.OneDown) && _queued[^1] == item)
|
||||
return;
|
||||
|
||||
int queueIndex = _queued.IndexOf(item);
|
||||
|
||||
if (requestedPosition == QueuePosition.OneUp)
|
||||
{
|
||||
_queued.RemoveAt(queueIndex);
|
||||
_queued.Insert(queueIndex - 1, item);
|
||||
}
|
||||
else if (requestedPosition == QueuePosition.OneDown)
|
||||
{
|
||||
_queued.RemoveAt(queueIndex);
|
||||
_queued.Insert(queueIndex + 1, item);
|
||||
}
|
||||
else if (requestedPosition == QueuePosition.Fisrt)
|
||||
{
|
||||
_queued.RemoveAt(queueIndex);
|
||||
_queued.Insert(0, item);
|
||||
}
|
||||
else
|
||||
{
|
||||
_queued.RemoveAt(queueIndex);
|
||||
_queued.Insert(_queued.Count, item);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool MoveNext()
|
||||
{
|
||||
lock (lockObject)
|
||||
{
|
||||
if (Current != null)
|
||||
{
|
||||
_completed.Add(Current);
|
||||
CompletedCountChanged?.Invoke(this, _completed.Count);
|
||||
}
|
||||
if (_queued.Count == 0)
|
||||
{
|
||||
Current = null;
|
||||
return false;
|
||||
}
|
||||
Current = _queued[0];
|
||||
_queued.RemoveAt(0);
|
||||
|
||||
QueuededCountChanged?.Invoke(this, _queued.Count);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public bool TryPeek(out T item)
|
||||
{
|
||||
lock (lockObject)
|
||||
{
|
||||
if (_queued.Count == 0)
|
||||
{
|
||||
item = null;
|
||||
return false;
|
||||
}
|
||||
item = _queued[0];
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public T Peek()
|
||||
{
|
||||
lock (lockObject)
|
||||
{
|
||||
if (_queued.Count == 0) throw new InvalidOperationException("Queue empty");
|
||||
return _queued.Count > 0 ? _queued[0] : default;
|
||||
}
|
||||
}
|
||||
|
||||
public void Enqueue(T item)
|
||||
{
|
||||
lock (lockObject)
|
||||
{
|
||||
_queued.Add(item);
|
||||
QueuededCountChanged?.Invoke(this, _queued.Count);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
59
Source/LibationWinForms/ProcessQueue/VirtualFlowControl.Designer.cs
generated
Normal file
59
Source/LibationWinForms/ProcessQueue/VirtualFlowControl.Designer.cs
generated
Normal file
@@ -0,0 +1,59 @@
|
||||
namespace LibationWinForms.ProcessQueue
|
||||
{
|
||||
partial class VirtualFlowControl
|
||||
{
|
||||
/// <summary>
|
||||
/// Required designer variable.
|
||||
/// </summary>
|
||||
private System.ComponentModel.IContainer components = null;
|
||||
|
||||
/// <summary>
|
||||
/// Clean up any resources being used.
|
||||
/// </summary>
|
||||
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
if (disposing && (components != null))
|
||||
{
|
||||
components.Dispose();
|
||||
}
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
|
||||
#region Component Designer generated code
|
||||
|
||||
/// <summary>
|
||||
/// Required method for Designer support - do not modify
|
||||
/// the contents of this method with the code editor.
|
||||
/// </summary>
|
||||
private void InitializeComponent()
|
||||
{
|
||||
this.panel1 = new System.Windows.Forms.Panel();
|
||||
this.SuspendLayout();
|
||||
//
|
||||
// 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.BackColor = System.Drawing.SystemColors.ControlDark;
|
||||
this.panel1.Location = new System.Drawing.Point(0, 0);
|
||||
this.panel1.Name = "panel1";
|
||||
this.panel1.Size = new System.Drawing.Size(377, 505);
|
||||
this.panel1.TabIndex = 0;
|
||||
//
|
||||
// VirtualFlowControl
|
||||
//
|
||||
this.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle;
|
||||
this.Controls.Add(this.panel1);
|
||||
this.Name = "VirtualFlowControl";
|
||||
this.Size = new System.Drawing.Size(377, 505);
|
||||
this.ResumeLayout(false);
|
||||
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
private System.Windows.Forms.Panel panel1;
|
||||
}
|
||||
}
|
||||
267
Source/LibationWinForms/ProcessQueue/VirtualFlowControl.cs
Normal file
267
Source/LibationWinForms/ProcessQueue/VirtualFlowControl.cs
Normal file
@@ -0,0 +1,267 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Drawing;
|
||||
using System.Windows.Forms;
|
||||
|
||||
namespace LibationWinForms.ProcessQueue
|
||||
{
|
||||
|
||||
internal delegate void RequestDataDelegate(int queueIndex, int numVisible, IReadOnlyList<ProcessBookControl> panelsToFill);
|
||||
internal delegate void ControlButtonClickedDelegate(int queueIndex, string buttonName, ProcessBookControl panelClicked);
|
||||
internal partial class VirtualFlowControl : UserControl
|
||||
{
|
||||
/// <summary>
|
||||
/// Triggered when the <see cref="VirtualFlowControl"/> needs to update the displayed <see cref="ProcessBookControl"/>s
|
||||
/// </summary>
|
||||
public event RequestDataDelegate RequestData;
|
||||
/// <summary>
|
||||
/// Triggered when one of the <see cref="ProcessBookControl"/>'s buttons has been clicked
|
||||
/// </summary>
|
||||
public event ControlButtonClickedDelegate ButtonClicked;
|
||||
|
||||
#region Dynamic Properties
|
||||
|
||||
/// <summary>
|
||||
/// The number of virtual <see cref="ProcessBookControl"/>s in the <see cref="VirtualFlowControl"/>
|
||||
/// </summary>
|
||||
public int VirtualControlCount
|
||||
{
|
||||
get => _virtualControlCount;
|
||||
set
|
||||
{
|
||||
if (_virtualControlCount == 0)
|
||||
vScrollBar1.Value = 0;
|
||||
|
||||
_virtualControlCount = value;
|
||||
AdjustScrollBar();
|
||||
DoVirtualScroll();
|
||||
}
|
||||
}
|
||||
|
||||
private int _virtualControlCount;
|
||||
|
||||
int ScrollValue => Math.Max(vScrollBar1.Value, 0);
|
||||
/// <summary>
|
||||
/// Amount the control moves with a small scroll change
|
||||
/// </summary>
|
||||
private int SmallScrollChange => VirtualControlHeight * SMALL_SCROLL_CHANGE_MULTIPLE;
|
||||
/// <summary>
|
||||
/// Amount the control moves with a large scroll change. Equal to the number of whole <see cref="ProcessBookControl"/>s in the panel, less 1.
|
||||
/// </summary>
|
||||
private int LargeScrollChange => Math.Max(DisplayHeight / VirtualControlHeight - 1, SMALL_SCROLL_CHANGE_MULTIPLE) * VirtualControlHeight;
|
||||
/// <summary>
|
||||
/// Virtual height of all virtual controls within this <see cref="VirtualFlowControl"/>
|
||||
/// </summary>
|
||||
private int VirtualHeight => (VirtualControlCount + NUM_BLANK_SPACES_AT_BOTTOM) * VirtualControlHeight - DisplayHeight + 2 * TopMargin;
|
||||
/// <summary>
|
||||
/// Index of the first virtual <see cref="ProcessBookControl"/>
|
||||
/// </summary>
|
||||
private int FirstVisibleVirtualIndex => ScrollValue / VirtualControlHeight;
|
||||
/// <summary>
|
||||
/// The display height of this <see cref="VirtualFlowControl"/>
|
||||
/// </summary>
|
||||
private int DisplayHeight => DisplayRectangle.Height;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Instance variables
|
||||
|
||||
/// <summary>
|
||||
/// The total height, inclusing margins, of the repeated <see cref="ProcessBookControl"/>
|
||||
/// </summary>
|
||||
private readonly int VirtualControlHeight;
|
||||
/// <summary>
|
||||
/// Margin between the top <see cref="ProcessBookControl"/> and the top of the Panel, and the bottom <see cref="ProcessBookControl"/> and the bottom of the panel
|
||||
/// </summary>
|
||||
private readonly int TopMargin;
|
||||
|
||||
private readonly VScrollBar vScrollBar1;
|
||||
private readonly List<ProcessBookControl> BookControls = new();
|
||||
|
||||
#endregion
|
||||
|
||||
#region Global behavior settings
|
||||
|
||||
/// <summary>
|
||||
/// Total number of actual controls added to the panel. 23 is sufficient up to a 4k monitor height.
|
||||
/// </summary>
|
||||
private const int NUM_ACTUAL_CONTROLS = 23;
|
||||
/// <summary>
|
||||
/// Multiple of <see cref="VirtualControlHeight"/> that is moved for each small scroll change
|
||||
/// </summary>
|
||||
private const int SMALL_SCROLL_CHANGE_MULTIPLE = 1;
|
||||
/// <summary>
|
||||
/// Amount of space at the bottom of the <see cref="VirtualFlowControl"/>, in multiples of <see cref="VirtualControlHeight"/>
|
||||
/// </summary>
|
||||
private const int NUM_BLANK_SPACES_AT_BOTTOM = 2;
|
||||
|
||||
#endregion
|
||||
|
||||
public VirtualFlowControl()
|
||||
{
|
||||
InitializeComponent();
|
||||
|
||||
vScrollBar1 = new VScrollBar
|
||||
{
|
||||
Minimum = 0,
|
||||
Value = 0,
|
||||
Dock = DockStyle.Right
|
||||
};
|
||||
Controls.Add(vScrollBar1);
|
||||
|
||||
vScrollBar1.Scroll += (_, s) => SetScrollPosition(s.NewValue);
|
||||
panel1.Width -= vScrollBar1.Width + panel1.Margin.Right;
|
||||
panel1.Resize += (_, _) =>
|
||||
{
|
||||
AdjustScrollBar();
|
||||
DoVirtualScroll();
|
||||
};
|
||||
|
||||
|
||||
var control = InitControl(0);
|
||||
VirtualControlHeight = control.Height + control.Margin.Top + control.Margin.Bottom;
|
||||
TopMargin = control.Margin.Top;
|
||||
|
||||
BookControls.Add(control);
|
||||
panel1.Controls.Add(control);
|
||||
|
||||
if (DesignMode)
|
||||
return;
|
||||
|
||||
for (int i = 1; i < NUM_ACTUAL_CONTROLS; i++)
|
||||
{
|
||||
control = InitControl(VirtualControlHeight * i);
|
||||
BookControls.Add(control);
|
||||
panel1.Controls.Add(control);
|
||||
}
|
||||
|
||||
vScrollBar1.SmallChange = SmallScrollChange;
|
||||
panel1.Height += NUM_BLANK_SPACES_AT_BOTTOM * VirtualControlHeight;
|
||||
}
|
||||
|
||||
private ProcessBookControl InitControl(int locationY)
|
||||
{
|
||||
var control = new ProcessBookControl();
|
||||
control.Location = new Point(control.Margin.Left, locationY + control.Margin.Top);
|
||||
control.Width = panel1.ClientRectangle.Width - control.Margin.Left - control.Margin.Right;
|
||||
control.Anchor = AnchorStyles.Left | AnchorStyles.Right | AnchorStyles.Top;
|
||||
|
||||
control.cancelBtn.Click += ControlButton_Click;
|
||||
control.moveFirstBtn.Click += ControlButton_Click;
|
||||
control.moveUpBtn.Click += ControlButton_Click;
|
||||
control.moveDownBtn.Click += ControlButton_Click;
|
||||
control.moveLastBtn.Click += ControlButton_Click;
|
||||
return control;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handles all button clicks from all <see cref="ProcessBookControl"/>, detects which one sent the click, and fires <see cref="ButtonClicked"/> to notify the model of the click
|
||||
/// </summary>
|
||||
private void ControlButton_Click(object sender, EventArgs e)
|
||||
{
|
||||
Control button = sender as Control;
|
||||
Control form = button.Parent;
|
||||
while (form is not ProcessBookControl)
|
||||
form = form.Parent;
|
||||
|
||||
int clickedIndex = BookControls.IndexOf((ProcessBookControl)form);
|
||||
|
||||
ButtonClicked?.Invoke(FirstVisibleVirtualIndex + clickedIndex, button.Name, BookControls[clickedIndex]);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adjusts the <see cref="vScrollBar1"/> max width and enabled status based on the <see cref="VirtualControlCount"/> and the <see cref="DisplayHeight"/>
|
||||
/// </summary>
|
||||
private void AdjustScrollBar()
|
||||
{
|
||||
int maxFullVisible = DisplayHeight / VirtualControlHeight;
|
||||
|
||||
if (VirtualControlCount <= maxFullVisible)
|
||||
{
|
||||
vScrollBar1.Enabled = false;
|
||||
vScrollBar1.Value = 0;
|
||||
|
||||
for (int i = VirtualControlCount; i < NUM_ACTUAL_CONTROLS; i++)
|
||||
BookControls[i].Visible = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
vScrollBar1.Enabled = true;
|
||||
vScrollBar1.LargeChange = LargeScrollChange;
|
||||
|
||||
//https://stackoverflow.com/a/2882878/3335599
|
||||
int newMaximum = VirtualHeight + vScrollBar1.LargeChange - 1;
|
||||
if (newMaximum < vScrollBar1.Maximum)
|
||||
vScrollBar1.Value = Math.Max(vScrollBar1.Value - (vScrollBar1.Maximum - newMaximum), 0);
|
||||
vScrollBar1.Maximum = newMaximum;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calculated the virtual controls that are in view at the currrent scroll position and windows size,
|
||||
/// positions <see cref="panel1"/> to simulate scroll activity, then fires <see cref="RequestData"/> to notify the model to update all visible controls
|
||||
/// </summary>
|
||||
private void DoVirtualScroll()
|
||||
{
|
||||
int firstVisible = FirstVisibleVirtualIndex;
|
||||
|
||||
int position = ScrollValue % VirtualControlHeight;
|
||||
panel1.Location = new Point(0, -position);
|
||||
|
||||
int numVisible = DisplayHeight / VirtualControlHeight;
|
||||
|
||||
if (DisplayHeight % VirtualControlHeight != 0)
|
||||
numVisible++;
|
||||
|
||||
numVisible = Math.Min(numVisible, VirtualControlCount);
|
||||
numVisible = Math.Min(numVisible, VirtualControlCount - firstVisible);
|
||||
|
||||
RequestData?.Invoke(firstVisible, numVisible, BookControls);
|
||||
|
||||
for (int i = 0; i < BookControls.Count; i++)
|
||||
BookControls[i].Visible = i < numVisible;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set scroll value to an integral multiple of <see cref="SmallScrollChange"/>
|
||||
/// </summary>
|
||||
private void SetScrollPosition(int value)
|
||||
{
|
||||
int newPos = (int)Math.Round((double)value / SmallScrollChange) * SmallScrollChange;
|
||||
if (vScrollBar1.Value != newPos)
|
||||
{
|
||||
//https://stackoverflow.com/a/2882878/3335599
|
||||
vScrollBar1.Value = Math.Min(newPos, vScrollBar1.Maximum - vScrollBar1.LargeChange + 1);
|
||||
DoVirtualScroll();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private const int WM_MOUSEWHEEL = 522;
|
||||
private const int WHEEL_DELTA = 120;
|
||||
protected override void WndProc(ref Message m)
|
||||
{
|
||||
//Capture mouse wheel movement and interpret it as a scroll event
|
||||
if (m.Msg == WM_MOUSEWHEEL)
|
||||
{
|
||||
//https://docs.microsoft.com/en-us/windows/win32/inputdev/wm-mousewheel
|
||||
int wheelDelta = -(short)(((ulong)m.WParam) >> 16 & ushort.MaxValue);
|
||||
|
||||
int numSmallPositionMoves = Math.Abs(wheelDelta) / WHEEL_DELTA;
|
||||
|
||||
int scrollDelta = Math.Sign(wheelDelta) * numSmallPositionMoves * SmallScrollChange;
|
||||
|
||||
int newScrollPosition;
|
||||
|
||||
if (scrollDelta > 0)
|
||||
newScrollPosition = Math.Min(vScrollBar1.Value + scrollDelta, vScrollBar1.Maximum);
|
||||
else
|
||||
newScrollPosition = Math.Max(vScrollBar1.Value + scrollDelta, vScrollBar1.Minimum);
|
||||
|
||||
SetScrollPosition(newScrollPosition);
|
||||
}
|
||||
|
||||
base.WndProc(ref m);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<root>
|
||||
<root>
|
||||
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
|
||||
<xsd:element name="root" msdata:IsDataSet="true">
|
||||
@@ -17,7 +17,9 @@ namespace LibationWinForms
|
||||
public SyncBindingSource(object dataSource, string dataMember) : base(dataSource, dataMember)
|
||||
=> syncContext = SynchronizationContext.Current;
|
||||
|
||||
protected override void OnListChanged(ListChangedEventArgs e)
|
||||
public override bool SupportsFiltering => true;
|
||||
|
||||
protected override void OnListChanged(ListChangedEventArgs e)
|
||||
{
|
||||
if (syncContext is not null)
|
||||
syncContext.Send(_ => base.OnListChanged(e), null);
|
||||
|
||||
@@ -6,8 +6,11 @@ namespace LibationWinForms
|
||||
{
|
||||
public abstract class AsyncNotifyPropertyChanged : SynchronizeInvoker, INotifyPropertyChanged
|
||||
{
|
||||
// see also notes in Libation/Source/_ARCHITECTURE NOTES.txt :: MVVM
|
||||
public event PropertyChangedEventHandler PropertyChanged;
|
||||
|
||||
// per standard INotifyPropertyChanged pattern:
|
||||
// https://docs.microsoft.com/en-us/dotnet/desktop/wpf/data/how-to-implement-property-change-notification
|
||||
public void NotifyPropertyChanged([CallerMemberName] string propertyName = "")
|
||||
=> this.UIThreadAsync(() => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)));
|
||||
}
|
||||
|
||||
@@ -0,0 +1,94 @@
|
||||
using ApplicationServices;
|
||||
using Dinah.Core.DataBinding;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
|
||||
namespace LibationWinForms
|
||||
{
|
||||
/*
|
||||
* Allows filtering of the underlying SortableBindingList<GridEntry>
|
||||
* by implementing IBindingListView and using SearchEngineCommands
|
||||
*
|
||||
* When filtering is applied, the filtered-out items are removed
|
||||
* from the base list and added to the private FilterRemoved list.
|
||||
* When filtering is removed, items in the FilterRemoved list are
|
||||
* added back to the base list.
|
||||
*
|
||||
* Remove is overridden to ensure that removed items are removed from
|
||||
* the base list (visible items) as well as the FilterRemoved list.
|
||||
*/
|
||||
internal class FilterableSortableBindingList : SortableBindingList<GridEntry>, IBindingListView
|
||||
{
|
||||
/// <summary>
|
||||
/// Items that were removed from the base list due to filtering
|
||||
/// </summary>
|
||||
private readonly List<GridEntry> FilterRemoved = new();
|
||||
private string FilterString;
|
||||
public FilterableSortableBindingList(IEnumerable<GridEntry> enumeration) : base(enumeration) { }
|
||||
|
||||
public bool SupportsFiltering => true;
|
||||
public string Filter { get => FilterString; set => ApplyFilter(value); }
|
||||
|
||||
#region Unused - Advanced Filtering
|
||||
public bool SupportsAdvancedSorting => false;
|
||||
|
||||
//This ApplySort overload is only called if SupportsAdvancedSorting is true.
|
||||
//Otherwise BindingList.ApplySort() is used
|
||||
public void ApplySort(ListSortDescriptionCollection sorts) => throw new NotImplementedException();
|
||||
|
||||
public ListSortDescriptionCollection SortDescriptions => throw new NotImplementedException();
|
||||
#endregion
|
||||
|
||||
public new void Remove(GridEntry entry)
|
||||
{
|
||||
FilterRemoved.Remove(entry);
|
||||
base.Remove(entry);
|
||||
}
|
||||
|
||||
/// <returns>All items in the list, including those filtered out.</returns>
|
||||
public List<GridEntry> AllItems() => Items.Concat(FilterRemoved).ToList();
|
||||
|
||||
private void ApplyFilter(string filterString)
|
||||
{
|
||||
if (filterString != FilterString)
|
||||
RemoveFilter();
|
||||
|
||||
FilterString = filterString;
|
||||
|
||||
var searchResults = SearchEngineCommands.Search(filterString);
|
||||
var filteredOut = Items.ExceptBy(searchResults.Docs.Select(d => d.ProductId), ge => ge.AudibleProductId);
|
||||
|
||||
for (int i = Items.Count - 1; i >= 0; i--)
|
||||
{
|
||||
if (filteredOut.Contains(Items[i]))
|
||||
{
|
||||
FilterRemoved.Add(Items[i]);
|
||||
Items.RemoveAt(i);
|
||||
base.OnListChanged(new ListChangedEventArgs(ListChangedType.ItemDeleted, i));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void RemoveFilter()
|
||||
{
|
||||
if (FilterString is null) return;
|
||||
|
||||
int visibleCount = Items.Count;
|
||||
for (int i = 0; i < FilterRemoved.Count; i++)
|
||||
base.InsertItem(i + visibleCount, FilterRemoved[i]);
|
||||
OnListChanged(new ListChangedEventArgs(ListChangedType.Reset, -1));
|
||||
|
||||
FilterRemoved.Clear();
|
||||
|
||||
if (IsSortedCore)
|
||||
Sort();
|
||||
else
|
||||
//No user-defined sort is applied, so do default sorting by date added, descending
|
||||
((List<GridEntry>)Items).Sort((i1, i2) => i2.LibraryBook.DateAdded.CompareTo(i1.LibraryBook.DateAdded));
|
||||
|
||||
FilterString = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -46,7 +46,6 @@ namespace LibationWinForms
|
||||
}
|
||||
}
|
||||
|
||||
public bool DownloadInProgress { get; private set; }
|
||||
public string ProductRating { get; private set; }
|
||||
public string PurchaseDate { get; private set; }
|
||||
public string MyRating { get; private set; }
|
||||
@@ -68,7 +67,8 @@ namespace LibationWinForms
|
||||
//Cache these statuses for faster sorting.
|
||||
if ((DateTime.Now - lastStatusUpdate).TotalSeconds > 2)
|
||||
{
|
||||
UpdateLiberatedStatus(notify: false);
|
||||
_bookStatus = LibraryCommands.Liberated_Status(LibraryBook.Book);
|
||||
_pdfStatus = LibraryCommands.Pdf_Status(LibraryBook.Book);
|
||||
lastStatusUpdate = DateTime.Now;
|
||||
}
|
||||
return (_bookStatus, _pdfStatus);
|
||||
@@ -76,37 +76,19 @@ namespace LibationWinForms
|
||||
}
|
||||
#endregion
|
||||
|
||||
public event EventHandler LibraryBookUpdated;
|
||||
public event EventHandler Committed;
|
||||
|
||||
// alias
|
||||
private Book Book => LibraryBook.Book;
|
||||
|
||||
public GridEntry(LibraryBook libraryBook) => setLibraryBook(libraryBook);
|
||||
|
||||
public async Task DownloadBook()
|
||||
{
|
||||
if (DownloadInProgress)
|
||||
return;
|
||||
|
||||
try
|
||||
{
|
||||
DownloadInProgress = true;
|
||||
await BookLiberation.ProcessorAutomationController.BackupSingleBookAsync(LibraryBook);
|
||||
UpdateLiberatedStatus();
|
||||
}
|
||||
finally
|
||||
{
|
||||
DownloadInProgress = false;
|
||||
}
|
||||
}
|
||||
|
||||
public void UpdateLibraryBook(LibraryBook libraryBook)
|
||||
{
|
||||
if (AudibleProductId != libraryBook.Book.AudibleProductId)
|
||||
throw new Exception("Invalid grid entry update. IDs must match");
|
||||
|
||||
setLibraryBook(libraryBook);
|
||||
|
||||
NotifyPropertyChanged();
|
||||
}
|
||||
|
||||
private void setLibraryBook(LibraryBook libraryBook)
|
||||
@@ -142,9 +124,6 @@ namespace LibationWinForms
|
||||
}
|
||||
|
||||
UserDefinedItem.ItemChanged += UserDefinedItem_ItemChanged;
|
||||
|
||||
// this will never have a value when triggered by ctor b/c nothing can subscribe to the event until after ctor is complete
|
||||
LibraryBookUpdated?.Invoke(this, null);
|
||||
}
|
||||
|
||||
private void PictureStorage_PictureCached(object sender, PictureCachedEventArgs e)
|
||||
@@ -177,10 +156,12 @@ namespace LibationWinForms
|
||||
break;
|
||||
case nameof(udi.BookStatus):
|
||||
Book.UserDefinedItem.BookStatus = udi.BookStatus;
|
||||
_bookStatus = udi.BookStatus;
|
||||
NotifyPropertyChanged(nameof(Liberate));
|
||||
break;
|
||||
case nameof(udi.PdfStatus):
|
||||
Book.UserDefinedItem.PdfStatus = udi.PdfStatus;
|
||||
_pdfStatus = udi.PdfStatus;
|
||||
NotifyPropertyChanged(nameof(Liberate));
|
||||
break;
|
||||
}
|
||||
@@ -204,17 +185,6 @@ namespace LibationWinForms
|
||||
Book.UserDefinedItem.BookStatus = bookStatus;
|
||||
Book.UserDefinedItem.PdfStatus = pdfStatus;
|
||||
LibraryCommands.UpdateUserDefinedItem(Book);
|
||||
|
||||
// notify
|
||||
Committed?.Invoke(this, null);
|
||||
}
|
||||
|
||||
private void UpdateLiberatedStatus(bool notify = true)
|
||||
{
|
||||
_bookStatus = LibraryCommands.Liberated_Status(LibraryBook.Book);
|
||||
_pdfStatus = LibraryCommands.Pdf_Status(LibraryBook.Book);
|
||||
if (notify)
|
||||
NotifyPropertyChanged(nameof(Liberate));
|
||||
}
|
||||
|
||||
#endregion
|
||||
@@ -260,7 +230,7 @@ namespace LibationWinForms
|
||||
#endregion
|
||||
|
||||
#region Static library display functions
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// This information should not change during <see cref="GridEntry"/> lifetime, so call only once.
|
||||
/// </summary>
|
||||
|
||||
@@ -6,9 +6,6 @@ using System.Threading.Tasks;
|
||||
using System.Windows.Forms;
|
||||
using ApplicationServices;
|
||||
using DataLayer;
|
||||
using Dinah.Core;
|
||||
using Dinah.Core.DataBinding;
|
||||
using Dinah.Core.Threading;
|
||||
using Dinah.Core.Windows.Forms;
|
||||
using FileLiberator;
|
||||
using LibationFileManager;
|
||||
@@ -16,6 +13,7 @@ using LibationWinForms.Dialogs;
|
||||
|
||||
namespace LibationWinForms
|
||||
{
|
||||
|
||||
#region // legacy instructions to update data_grid_view
|
||||
// INSTRUCTIONS TO UPDATE DATA_GRID_VIEW
|
||||
// - delete current DataGridView
|
||||
@@ -35,8 +33,10 @@ namespace LibationWinForms
|
||||
// VS has improved since then with .net6+ but I haven't checked again
|
||||
#endregion
|
||||
|
||||
|
||||
public partial class ProductsGrid : UserControl
|
||||
{
|
||||
public event EventHandler<LibraryBook> LiberateClicked;
|
||||
/// <summary>Number of visible rows has changed</summary>
|
||||
public event EventHandler<int> VisibleCountChanged;
|
||||
|
||||
@@ -47,16 +47,17 @@ namespace LibationWinForms
|
||||
{
|
||||
InitializeComponent();
|
||||
|
||||
// sorting breaks filters. must reapply filters after sorting
|
||||
_dataGridView.Sorted += Filter;
|
||||
_dataGridView.CellContentClick += DataGridView_CellContentClick;
|
||||
|
||||
this.Load += ProductsGrid_Load;
|
||||
if (this.DesignMode)
|
||||
return;
|
||||
|
||||
EnableDoubleBuffering();
|
||||
|
||||
_dataGridView.CellContentClick += DataGridView_CellContentClick;
|
||||
|
||||
this.Load += ProductsGrid_Load;
|
||||
}
|
||||
|
||||
private void EnableDoubleBuffering()
|
||||
private void EnableDoubleBuffering()
|
||||
{
|
||||
var propertyInfo = _dataGridView.GetType().GetProperty("DoubleBuffered", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic);
|
||||
|
||||
@@ -72,7 +73,7 @@ namespace LibationWinForms
|
||||
return;
|
||||
|
||||
if (e.ColumnIndex == liberateGVColumn.Index)
|
||||
await Liberate_Click(getGridEntry(e.RowIndex));
|
||||
Liberate_Click(getGridEntry(e.RowIndex));
|
||||
else if (e.ColumnIndex == tagAndDetailsGVColumn.Index)
|
||||
Details_Click(getGridEntry(e.RowIndex));
|
||||
else if (e.ColumnIndex == descriptionGVColumn.Index)
|
||||
@@ -84,7 +85,7 @@ namespace LibationWinForms
|
||||
private ImageDisplay imageDisplay;
|
||||
private async Task Cover_Click(GridEntry liveGridEntry)
|
||||
{
|
||||
var picDefinition = new PictureDefinition(liveGridEntry.LibraryBook.Book.PictureLarge, PictureSize.Native);
|
||||
var picDefinition = new PictureDefinition(liveGridEntry.LibraryBook.Book.PictureLarge ?? liveGridEntry.LibraryBook.Book.PictureId, PictureSize.Native);
|
||||
var picDlTask = Task.Run(() => PictureStorage.GetPictureSynchronously(picDefinition));
|
||||
|
||||
(_, byte[] initialImageBts) = PictureStorage.GetPicture(new PictureDefinition(liveGridEntry.LibraryBook.Book.PictureId, PictureSize._80x80));
|
||||
@@ -124,24 +125,9 @@ namespace LibationWinForms
|
||||
displayWindow.Show(this);
|
||||
}
|
||||
|
||||
private static async Task Liberate_Click(GridEntry liveGridEntry)
|
||||
private void Liberate_Click(GridEntry liveGridEntry)
|
||||
{
|
||||
var libraryBook = liveGridEntry.LibraryBook;
|
||||
|
||||
// liberated: open explorer to file
|
||||
if (libraryBook.Book.Audio_Exists())
|
||||
{
|
||||
var filePath = AudibleFileStorage.Audio.GetPath(libraryBook.Book.AudibleProductId);
|
||||
if (!Go.To.File(filePath))
|
||||
{
|
||||
var suffix = string.IsNullOrWhiteSpace(filePath) ? "" : $":\r\n{filePath}";
|
||||
MessageBox.Show($"File not found" + suffix);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// else: liberate
|
||||
await liveGridEntry.DownloadBook();
|
||||
LiberateClicked?.Invoke(this, liveGridEntry.LibraryBook);
|
||||
}
|
||||
|
||||
private static void Details_Click(GridEntry liveGridEntry)
|
||||
@@ -155,120 +141,100 @@ namespace LibationWinForms
|
||||
|
||||
#region UI display functions
|
||||
|
||||
private SortableBindingList<GridEntry> bindingList;
|
||||
private FilterableSortableBindingList bindingList;
|
||||
|
||||
private bool hasBeenDisplayed;
|
||||
public event EventHandler InitialLoaded;
|
||||
public void Display()
|
||||
{
|
||||
// don't return early if lib size == 0. this will not update correctly if all books are removed
|
||||
var lib = DbContexts.GetLibrary_Flat_NoTracking();
|
||||
|
||||
var orderedBooks = lib
|
||||
// default load order
|
||||
.OrderByDescending(lb => lb.DateAdded)
|
||||
//// more advanced example: sort by author, then series, then title
|
||||
//.OrderBy(lb => lb.Book.AuthorNames)
|
||||
// .ThenBy(lb => lb.Book.SeriesSortable)
|
||||
// .ThenBy(lb => lb.Book.TitleSortable)
|
||||
.ToList();
|
||||
|
||||
// BIND
|
||||
if (bindingList?.Count > 0)
|
||||
updateGrid(orderedBooks);
|
||||
if (!hasBeenDisplayed)
|
||||
{
|
||||
// bind
|
||||
bindToGrid(lib);
|
||||
hasBeenDisplayed = true;
|
||||
InitialLoaded?.Invoke(this, new());
|
||||
VisibleCountChanged?.Invoke(this, bindingList.Count);
|
||||
}
|
||||
else
|
||||
bindToGrid(orderedBooks);
|
||||
updateGrid(lib);
|
||||
|
||||
// FILTER
|
||||
Filter();
|
||||
}
|
||||
|
||||
private void bindToGrid(List<DataLayer.LibraryBook> orderedBooks)
|
||||
private void bindToGrid(List<LibraryBook> dbBooks)
|
||||
{
|
||||
bindingList = new SortableBindingList<GridEntry>(orderedBooks.Select(lb => toGridEntry(lb)));
|
||||
bindingList = new FilterableSortableBindingList(dbBooks.OrderByDescending(lb => lb.DateAdded).Select(lb => new GridEntry(lb)));
|
||||
gridEntryBindingSource.DataSource = bindingList;
|
||||
}
|
||||
|
||||
private void updateGrid(List<DataLayer.LibraryBook> orderedBooks)
|
||||
private void updateGrid(List<LibraryBook> dbBooks)
|
||||
{
|
||||
for (var i = orderedBooks.Count - 1; i >= 0; i--)
|
||||
int visibleCount = bindingList.Count;
|
||||
string existingFilter = gridEntryBindingSource.Filter;
|
||||
|
||||
//Add absent books to grid, or update current books
|
||||
|
||||
var allItmes = bindingList.AllItems();
|
||||
for (var i = dbBooks.Count - 1; i >= 0; i--)
|
||||
{
|
||||
var libraryBook = orderedBooks[i];
|
||||
var existingItem = bindingList.FirstOrDefault(i => i.AudibleProductId == libraryBook.Book.AudibleProductId);
|
||||
var libraryBook = dbBooks[i];
|
||||
var existingItem = allItmes.FirstOrDefault(i => i.AudibleProductId == libraryBook.Book.AudibleProductId);
|
||||
|
||||
// add new to top
|
||||
if (existingItem is null)
|
||||
bindingList.Insert(0, toGridEntry(libraryBook));
|
||||
bindingList.Insert(0, new GridEntry(libraryBook));
|
||||
// update existing
|
||||
else
|
||||
existingItem.UpdateLibraryBook(libraryBook);
|
||||
}
|
||||
|
||||
// remove deleted from grid. note: actual deletion from db must still occur via the RemoveBook feature. deleting from audible will not trigger this
|
||||
var oldIds = bindingList.Select(ge => ge.AudibleProductId).ToList();
|
||||
var newIds = orderedBooks.Select(lb => lb.Book.AudibleProductId).ToList();
|
||||
var remove = oldIds.Except(newIds).ToList();
|
||||
foreach (var id in remove)
|
||||
if (bindingList.Count != visibleCount)
|
||||
{
|
||||
var oldItem = bindingList.FirstOrDefault(ge => ge.AudibleProductId == id);
|
||||
if (oldItem is not null)
|
||||
bindingList.Remove(oldItem);
|
||||
//re-filter for newly added items
|
||||
Filter(null);
|
||||
Filter(existingFilter);
|
||||
}
|
||||
}
|
||||
|
||||
private GridEntry toGridEntry(DataLayer.LibraryBook libraryBook)
|
||||
{
|
||||
var entry = new GridEntry(libraryBook);
|
||||
entry.Committed += Filter;
|
||||
entry.LibraryBookUpdated += (sender, _) => _dataGridView.InvalidateRow(_dataGridView.GetRowIdOfBoundItem((GridEntry)sender));
|
||||
return entry;
|
||||
// remove deleted from grid.
|
||||
// note: actual deletion from db must still occur via the RemoveBook feature. deleting from audible will not trigger this
|
||||
var removedBooks =
|
||||
bindingList
|
||||
.AllItems()
|
||||
.ExceptBy(dbBooks.Select(lb => lb.Book.AudibleProductId), ge => ge.AudibleProductId)
|
||||
.ToList();
|
||||
|
||||
foreach (var removed in removedBooks)
|
||||
//no need to re-filter for removed books
|
||||
bindingList.Remove(removed);
|
||||
|
||||
if (bindingList.Count != visibleCount)
|
||||
VisibleCountChanged?.Invoke(this, bindingList.Count);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Filter
|
||||
|
||||
private string _filterSearchString;
|
||||
private void Filter(object _ = null, EventArgs __ = null) => Filter(_filterSearchString);
|
||||
public void Filter(string searchString)
|
||||
{
|
||||
_filterSearchString = searchString;
|
||||
int visibleCount = bindingList.Count;
|
||||
|
||||
if (_dataGridView.Rows.Count == 0)
|
||||
return;
|
||||
if (string.IsNullOrEmpty(searchString))
|
||||
gridEntryBindingSource.RemoveFilter();
|
||||
else
|
||||
gridEntryBindingSource.Filter = searchString;
|
||||
|
||||
var initVisible = getVisible().Count();
|
||||
|
||||
var searchResults = SearchEngineCommands.Search(searchString);
|
||||
var productIds = searchResults.Docs.Select(d => d.ProductId).ToList();
|
||||
|
||||
// https://stackoverflow.com/a/18942430
|
||||
var bindingContext = BindingContext[_dataGridView.DataSource];
|
||||
bindingContext.SuspendBinding();
|
||||
{
|
||||
this.UIThreadSync(() =>
|
||||
{
|
||||
for (var r = _dataGridView.RowCount - 1; r >= 0; r--)
|
||||
_dataGridView.Rows[r].Visible = productIds.Contains(getGridEntry(r).AudibleProductId);
|
||||
});
|
||||
}
|
||||
|
||||
// Causes repainting of the DataGridView
|
||||
bindingContext.ResumeBinding();
|
||||
|
||||
var endVisible = getVisible().Count();
|
||||
if (initVisible != endVisible)
|
||||
VisibleCountChanged?.Invoke(this, endVisible);
|
||||
if (visibleCount != bindingList.Count)
|
||||
VisibleCountChanged?.Invoke(this, bindingList.Count);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
private IEnumerable<DataGridViewRow> getVisible()
|
||||
=> _dataGridView
|
||||
.AsEnumerable()
|
||||
.Where(row => row.Visible);
|
||||
|
||||
internal List<DataLayer.LibraryBook> GetVisible()
|
||||
=> getVisible()
|
||||
.Select(row => ((GridEntry)row.DataBoundItem).LibraryBook)
|
||||
internal List<LibraryBook> GetVisible()
|
||||
=> bindingList
|
||||
.Select(row => row.LibraryBook)
|
||||
.ToList();
|
||||
|
||||
private GridEntry getGridEntry(int rowIndex) => _dataGridView.GetBoundItem<GridEntry>(rowIndex);
|
||||
@@ -278,6 +244,9 @@ namespace LibationWinForms
|
||||
// to ensure this is only ever called once: Load instead of 'override OnVisibleChanged'
|
||||
private void ProductsGrid_Load(object sender, EventArgs e)
|
||||
{
|
||||
if (this.DesignMode)
|
||||
return;
|
||||
|
||||
contextMenuStrip1.Items.Add(new ToolStripLabel("Show / Hide Columns"));
|
||||
contextMenuStrip1.Items.Add(new ToolStripSeparator());
|
||||
|
||||
|
||||
13
Source/_ARCHITECTURE NOTES.txt
Normal file
13
Source/_ARCHITECTURE NOTES.txt
Normal file
@@ -0,0 +1,13 @@
|
||||
MVVM
|
||||
====
|
||||
Libation is not strictly MVVM. It's not strictly anything. There are however efforts at moving some major components toward this pattern.
|
||||
|
||||
Primary View: ProductsGrid
|
||||
Primary View Model: GridEntry
|
||||
|
||||
see also: https://docs.microsoft.com/en-us/dotnet/desktop/winforms/controls/raise-change-notifications--bindingsource
|
||||
BindingSource + INotifyPropertyChanged + DataGridView is the backbone of our implementation. The SortableBindingList (BindingList/BindingSource) automatically subscribes to each entry's NotifyPropertyChanged -- which is why our AsyncNotifyPropertyChanged.NotifyPropertyChanged is needed even though none of our code calls it.
|
||||
|
||||
- Adding or removing an entry to/from this BindingSource automatically updates the UI. No additional code needed.
|
||||
- Calling NotifyPropertyChanged with the name of a field updates that field in the UI
|
||||
- Calling NotifyPropertyChanged without a field name (or with an invalid field name) updates the whole entry in the UI
|
||||
@@ -8,8 +8,8 @@
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="FluentAssertions" Version="6.6.0" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.1.0" />
|
||||
<PackageReference Include="Moq" Version="4.17.2" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.2.0" />
|
||||
<PackageReference Include="Moq" Version="4.18.1" />
|
||||
<PackageReference Include="MSTest.TestAdapter" Version="2.2.10" />
|
||||
<PackageReference Include="MSTest.TestFramework" Version="2.2.10" />
|
||||
<PackageReference Include="coverlet.collector" Version="3.1.2">
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="FluentAssertions" Version="6.6.0" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.1.0" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.2.0" />
|
||||
<PackageReference Include="MSTest.TestAdapter" Version="2.2.10" />
|
||||
<PackageReference Include="MSTest.TestFramework" Version="2.2.10" />
|
||||
<PackageReference Include="coverlet.collector" Version="3.1.2">
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="FluentAssertions" Version="6.6.0" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.1.0" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.2.0" />
|
||||
<PackageReference Include="MSTest.TestAdapter" Version="2.2.10" />
|
||||
<PackageReference Include="MSTest.TestFramework" Version="2.2.10" />
|
||||
<PackageReference Include="coverlet.collector" Version="3.1.2">
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="FluentAssertions" Version="6.6.0" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.1.0" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.2.0" />
|
||||
<PackageReference Include="MSTest.TestAdapter" Version="2.2.10" />
|
||||
<PackageReference Include="MSTest.TestFramework" Version="2.2.10" />
|
||||
<PackageReference Include="coverlet.collector" Version="3.1.2">
|
||||
|
||||
@@ -8,8 +8,8 @@
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="FluentAssertions" Version="6.6.0" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.1.0" />
|
||||
<PackageReference Include="Moq" Version="4.17.2" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.2.0" />
|
||||
<PackageReference Include="Moq" Version="4.18.1" />
|
||||
<PackageReference Include="MSTest.TestAdapter" Version="2.2.10" />
|
||||
<PackageReference Include="MSTest.TestFramework" Version="2.2.10" />
|
||||
<PackageReference Include="coverlet.collector" Version="3.1.2">
|
||||
|
||||
Reference in New Issue
Block a user