mirror of
https://github.com/rmcrackan/Libation.git
synced 2025-12-31 01:48:39 -05:00
Compare commits
211 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f7a482659c | ||
|
|
99527453a7 | ||
|
|
3408b4637c | ||
|
|
3f2899e97e | ||
|
|
7987dfb819 | ||
|
|
1b101106e7 | ||
|
|
7b75955aec | ||
|
|
8f5467e6ca | ||
|
|
28764f92b9 | ||
|
|
777dfe4c62 | ||
|
|
0878a704d9 | ||
|
|
f880897542 | ||
|
|
b37472a954 | ||
|
|
68735a45dd | ||
|
|
e26deb9092 | ||
|
|
43d6ea82cd | ||
|
|
db1aa495ac | ||
|
|
ee62d9ae8d | ||
|
|
4001124cfa | ||
|
|
43a4d0d1d7 | ||
|
|
632b432b7c | ||
|
|
e778c7a59d | ||
|
|
d71cdecd35 | ||
|
|
4a82541ffd | ||
|
|
f29dff3386 | ||
|
|
718d21f6cb | ||
|
|
440550ded9 | ||
|
|
593fe57ea1 | ||
|
|
e8a320dac9 | ||
|
|
3cb43e5d3e | ||
|
|
f86bdba3c3 | ||
|
|
98c3940297 | ||
|
|
b9e789bbcf | ||
|
|
a108846731 | ||
|
|
0b4ce8d6e7 | ||
|
|
42df61b7dd | ||
|
|
6b46fa4cbc | ||
|
|
c0762eba18 | ||
|
|
036fb848e1 | ||
|
|
7198ae9025 | ||
|
|
d2822b06aa | ||
|
|
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 | ||
|
|
bce3bdba7e | ||
|
|
360f077da3 | ||
|
|
75c5f662dc | ||
|
|
3c0485cfa9 | ||
|
|
d5ba405de0 | ||
|
|
71b8bca86d | ||
|
|
c53b9eabd6 | ||
|
|
6d6434b4d4 | ||
|
|
a447e88b86 | ||
|
|
e2d2e00913 | ||
|
|
cbfea37b3a | ||
|
|
d6de647974 | ||
|
|
b784bd6b8d | ||
|
|
00df6da366 | ||
|
|
dad36c73e5 | ||
|
|
936a1d60a0 | ||
|
|
e0248c2d8e | ||
|
|
b12731e3d5 | ||
|
|
9636aca47c | ||
|
|
4138183352 | ||
|
|
c3871d3bca | ||
|
|
dd8b0783a9 | ||
|
|
9a50aa4c7c | ||
|
|
c40185030f | ||
|
|
7cba28019c | ||
|
|
926f8a957e | ||
|
|
59aeaf24e4 | ||
|
|
64eaa157e5 | ||
|
|
9a5d9f3867 | ||
|
|
e368e4669b | ||
|
|
c6ce814e1c | ||
|
|
dd5e162c10 | ||
|
|
7af890d897 | ||
|
|
0faeeea25f | ||
|
|
de9b3fd6ec | ||
|
|
22e5c8746c | ||
|
|
0091245734 | ||
|
|
448c231cfa | ||
|
|
b0d1f692a3 | ||
|
|
a5ff890ea1 | ||
|
|
df4739cbf4 | ||
|
|
9559109aa8 | ||
|
|
d848c1a499 | ||
|
|
48ffc40abb | ||
|
|
82b5daa809 | ||
|
|
b320276926 | ||
|
|
6ccb8d612f | ||
|
|
23460e0137 | ||
|
|
7723de7284 | ||
|
|
138f94594f | ||
|
|
81c152ddcb | ||
|
|
04665fea36 | ||
|
|
803eef3825 | ||
|
|
e2a05761a6 | ||
|
|
b1968caa0f | ||
|
|
6474ef98f5 | ||
|
|
8763d63a93 | ||
|
|
201ecebda9 | ||
|
|
1c9ea0a710 | ||
|
|
30feb42ed8 | ||
|
|
cfe2eac351 | ||
|
|
725979afb0 | ||
|
|
19262bceac | ||
|
|
5ad1e45c65 | ||
|
|
9fe95bbddc | ||
|
|
aecc54401d |
@@ -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; }
|
||||
@@ -140,11 +140,6 @@ namespace AaxDecrypter
|
||||
else
|
||||
FileUtility.SaferDelete(TempFilePath);
|
||||
}
|
||||
else
|
||||
{
|
||||
FileUtility.SaferDelete(OutputFileName);
|
||||
}
|
||||
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
@@ -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.3.0.1</Version>
|
||||
<Version>7.9.0.1</Version>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
@@ -15,6 +15,7 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\ApplicationServices\ApplicationServices.csproj" />
|
||||
<ProjectReference Include="..\AudibleUtilities\AudibleUtilities.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using ApplicationServices;
|
||||
using AudibleUtilities;
|
||||
using Dinah.Core;
|
||||
using Dinah.Core.IO;
|
||||
@@ -125,14 +126,31 @@ namespace AppScaffolding
|
||||
|
||||
if (!config.Exists(nameof(config.AutoScan)))
|
||||
config.AutoScan = true;
|
||||
|
||||
if (!config.Exists(nameof(config.GridColumnsVisibilities)))
|
||||
config.GridColumnsVisibilities = new Dictionary<string, bool>();
|
||||
|
||||
if (!config.Exists(nameof(config.GridColumnsDisplayIndices)))
|
||||
config.GridColumnsDisplayIndices = new Dictionary<string, int>();
|
||||
|
||||
if (!config.Exists(nameof(config.GridColumnsWidths)))
|
||||
config.GridColumnsWidths = new Dictionary<string, int>();
|
||||
|
||||
if (!config.Exists(nameof(config.DownloadCoverArt)))
|
||||
config.DownloadCoverArt = true;
|
||||
|
||||
if (!config.Exists(nameof(config.AutoDownloadEpisodes)))
|
||||
config.AutoDownloadEpisodes = false;
|
||||
}
|
||||
|
||||
/// <summary>Initialize logging. Run after migration</summary>
|
||||
/// <summary>Initialize logging. Wire-up events. Run after migration</summary>
|
||||
public static void RunPostMigrationScaffolding(Configuration config)
|
||||
{
|
||||
ensureSerilogConfig(config);
|
||||
configureLogging(config);
|
||||
logStartupState(config);
|
||||
|
||||
wireUpSystemEvents(config);
|
||||
}
|
||||
|
||||
private static void ensureSerilogConfig(Configuration config)
|
||||
@@ -270,22 +288,26 @@ namespace AppScaffolding
|
||||
});
|
||||
}
|
||||
|
||||
public static (bool hasUpgrade, string zipUrl, string htmlUrl, string zipName) GetLatestRelease()
|
||||
private static void wireUpSystemEvents(Configuration configuration)
|
||||
{
|
||||
(bool, string, string, string) isFalse = (false, null, null, null);
|
||||
LibraryCommands.LibrarySizeChanged += (_, __) => SearchEngineCommands.FullReIndex();
|
||||
LibraryCommands.BookUserDefinedItemCommitted += (_, books) => SearchEngineCommands.UpdateBooks(books);
|
||||
}
|
||||
|
||||
public static UpgradeProperties GetLatestRelease()
|
||||
{
|
||||
// timed out
|
||||
var latest = getLatestRelease(TimeSpan.FromSeconds(10));
|
||||
if (latest is null)
|
||||
return isFalse;
|
||||
return null;
|
||||
|
||||
var latestVersionString = latest.TagName.Trim('v');
|
||||
if (!Version.TryParse(latestVersionString, out var latestRelease))
|
||||
return isFalse;
|
||||
return null;
|
||||
|
||||
// we're up to date
|
||||
if (latestRelease <= BuildVersion)
|
||||
return isFalse;
|
||||
return null;
|
||||
|
||||
// we have an update
|
||||
var zip = latest.Assets.FirstOrDefault(a => a.BrowserDownloadUrl.EndsWith(".zip"));
|
||||
@@ -298,7 +320,7 @@ namespace AppScaffolding
|
||||
zipUrl
|
||||
});
|
||||
|
||||
return (true, zipUrl, latest.HtmlUrl, zip.Name);
|
||||
return new(zipUrl, latest.HtmlUrl, zip.Name, latestRelease);
|
||||
}
|
||||
private static Octokit.Release getLatestRelease(TimeSpan timeout)
|
||||
{
|
||||
|
||||
6
Source/AppScaffolding/UpgradeProperties.cs
Normal file
6
Source/AppScaffolding/UpgradeProperties.cs
Normal file
@@ -0,0 +1,6 @@
|
||||
using System;
|
||||
|
||||
namespace AppScaffolding
|
||||
{
|
||||
public record UpgradeProperties(string ZipUrl, string HtmlUrl, string ZipName, Version LatestRelease);
|
||||
}
|
||||
@@ -33,18 +33,20 @@ namespace ApplicationServices
|
||||
|
||||
//These are the minimum response groups required for the
|
||||
//library scanner to pass all validation and filtering.
|
||||
var libraryResponseGroups =
|
||||
LibraryOptions.ResponseGroupOptions.ProductAttrs |
|
||||
LibraryOptions.ResponseGroupOptions.ProductDesc |
|
||||
LibraryOptions.ResponseGroupOptions.Relationships;
|
||||
|
||||
if (accounts is null || accounts.Length == 0)
|
||||
var libraryOptions = new LibraryOptions
|
||||
{
|
||||
ResponseGroups
|
||||
= LibraryOptions.ResponseGroupOptions.ProductAttrs
|
||||
| LibraryOptions.ResponseGroupOptions.ProductDesc
|
||||
| LibraryOptions.ResponseGroupOptions.Relationships
|
||||
};
|
||||
if (accounts is null || accounts.Length == 0)
|
||||
return new List<LibraryBook>();
|
||||
|
||||
try
|
||||
{
|
||||
logTime($"pre {nameof(scanAccountsAsync)} all");
|
||||
var libraryItems = await scanAccountsAsync(apiExtendedfunc, accounts, libraryResponseGroups);
|
||||
var libraryItems = await scanAccountsAsync(apiExtendedfunc, accounts, libraryOptions);
|
||||
logTime($"post {nameof(scanAccountsAsync)} all");
|
||||
|
||||
var totalCount = libraryItems.Count;
|
||||
@@ -102,7 +104,12 @@ namespace ApplicationServices
|
||||
}
|
||||
|
||||
logTime($"pre {nameof(scanAccountsAsync)} all");
|
||||
var importItems = await scanAccountsAsync(apiExtendedfunc, accounts, LibraryOptions.ResponseGroupOptions.ALL_OPTIONS);
|
||||
var libraryOptions = new LibraryOptions
|
||||
{
|
||||
ResponseGroups = LibraryOptions.ResponseGroupOptions.ALL_OPTIONS,
|
||||
ImageSizes = LibraryOptions.ImageSizeOptions._500 | LibraryOptions.ImageSizeOptions._1215
|
||||
};
|
||||
var importItems = await scanAccountsAsync(apiExtendedfunc, accounts, libraryOptions);
|
||||
logTime($"post {nameof(scanAccountsAsync)} all");
|
||||
|
||||
var totalCount = importItems.Count;
|
||||
@@ -150,7 +157,7 @@ namespace ApplicationServices
|
||||
}
|
||||
}
|
||||
|
||||
private static async Task<List<ImportItem>> scanAccountsAsync(Func<Account, Task<ApiExtended>> apiExtendedfunc, Account[] accounts, LibraryOptions.ResponseGroupOptions libraryResponseGroups)
|
||||
private static async Task<List<ImportItem>> scanAccountsAsync(Func<Account, Task<ApiExtended>> apiExtendedfunc, Account[] accounts, LibraryOptions libraryOptions)
|
||||
{
|
||||
var tasks = new List<Task<List<ImportItem>>>();
|
||||
foreach (var account in accounts)
|
||||
@@ -159,7 +166,7 @@ namespace ApplicationServices
|
||||
var apiExtended = await apiExtendedfunc(account);
|
||||
|
||||
// add scanAccountAsync as a TASK: do not await
|
||||
tasks.Add(scanAccountAsync(apiExtended, account, libraryResponseGroups));
|
||||
tasks.Add(scanAccountAsync(apiExtended, account, libraryOptions));
|
||||
}
|
||||
|
||||
// import library in parallel
|
||||
@@ -168,7 +175,7 @@ namespace ApplicationServices
|
||||
return importItems;
|
||||
}
|
||||
|
||||
private static async Task<List<ImportItem>> scanAccountAsync(ApiExtended apiExtended, Account account, LibraryOptions.ResponseGroupOptions libraryResponseGroups)
|
||||
private static async Task<List<ImportItem>> scanAccountAsync(ApiExtended apiExtended, Account account, LibraryOptions libraryOptions)
|
||||
{
|
||||
ArgumentValidator.EnsureNotNull(account, nameof(account));
|
||||
|
||||
@@ -179,7 +186,7 @@ namespace ApplicationServices
|
||||
|
||||
logTime($"pre scanAccountAsync {account.AccountName}");
|
||||
|
||||
var dtoItems = await apiExtended.GetLibraryValidatedAsync(libraryResponseGroups, Configuration.Instance.ImportEpisodes);
|
||||
var dtoItems = await apiExtended.GetLibraryValidatedAsync(libraryOptions, Configuration.Instance.ImportEpisodes);
|
||||
|
||||
logTime($"post scanAccountAsync {account.AccountName} qty: {dtoItems.Count}");
|
||||
|
||||
@@ -246,43 +253,78 @@ namespace ApplicationServices
|
||||
#endregion
|
||||
|
||||
// call this whenever books are added or removed from library
|
||||
private static void finalizeLibrarySizeChange()
|
||||
{
|
||||
SearchEngineCommands.FullReIndex();
|
||||
LibrarySizeChanged?.Invoke(null, null);
|
||||
}
|
||||
private static void finalizeLibrarySizeChange() => LibrarySizeChanged?.Invoke(null, null);
|
||||
|
||||
/// <summary>Occurs when books are added or removed from library</summary>
|
||||
/// <summary>Occurs when the size of the library changes. ie: books are added or removed</summary>
|
||||
public static event EventHandler LibrarySizeChanged;
|
||||
|
||||
/// <summary>
|
||||
/// Occurs when <see cref="UserDefinedItem.Tags"/>, <see cref="UserDefinedItem.BookStatus"/>, or <see cref="UserDefinedItem.PdfStatus"/>
|
||||
/// changed values are successfully persisted.
|
||||
/// Occurs when the size of the library does not change but book(s) details do. Especially when <see cref="UserDefinedItem.Tags"/>, <see cref="UserDefinedItem.BookStatus"/>, or <see cref="UserDefinedItem.PdfStatus"/> changed values are successfully persisted.
|
||||
/// </summary>
|
||||
public static event EventHandler<string> BookUserDefinedItemCommitted;
|
||||
public static event EventHandler<IEnumerable<Book>> BookUserDefinedItemCommitted;
|
||||
|
||||
#region Update book details
|
||||
public static int UpdateUserDefinedItem(Book book)
|
||||
public static int UpdateBookStatus(this Book book, LiberatedStatus bookStatus)
|
||||
{
|
||||
book.UserDefinedItem.BookStatus = bookStatus;
|
||||
return UpdateUserDefinedItem(book);
|
||||
}
|
||||
public static int UpdatePdfStatus(this Book book, LiberatedStatus pdfStatus)
|
||||
{
|
||||
book.UserDefinedItem.PdfStatus = pdfStatus;
|
||||
return UpdateUserDefinedItem(book);
|
||||
}
|
||||
public static int UpdateBook(
|
||||
this Book book,
|
||||
string tags = null,
|
||||
LiberatedStatus? bookStatus = null,
|
||||
LiberatedStatus? pdfStatus = null)
|
||||
=> UpdateBooks(tags, bookStatus, pdfStatus, book);
|
||||
public static int UpdateBooks(
|
||||
string tags = null,
|
||||
LiberatedStatus? bookStatus = null,
|
||||
LiberatedStatus? pdfStatus = null,
|
||||
params Book[] books)
|
||||
{
|
||||
foreach (var book in books)
|
||||
{
|
||||
// blank tags are expected. null tags are not
|
||||
if (tags is not null && book.UserDefinedItem.Tags != tags)
|
||||
book.UserDefinedItem.Tags = tags;
|
||||
|
||||
if (bookStatus is not null && book.UserDefinedItem.BookStatus != bookStatus.Value)
|
||||
book.UserDefinedItem.BookStatus = bookStatus.Value;
|
||||
|
||||
// even though PdfStatus is nullable, there's no case where we'd actually overwrite with null
|
||||
if (pdfStatus is not null && book.UserDefinedItem.PdfStatus != pdfStatus.Value)
|
||||
book.UserDefinedItem.PdfStatus = pdfStatus.Value;
|
||||
}
|
||||
|
||||
return UpdateUserDefinedItem(books);
|
||||
}
|
||||
public static int UpdateUserDefinedItem(params Book[] books) => UpdateUserDefinedItem(books.ToList());
|
||||
public static int UpdateUserDefinedItem(IEnumerable<Book> books)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (books is null || !books.Any())
|
||||
return 0;
|
||||
|
||||
using var context = DbContexts.GetContext();
|
||||
|
||||
// Attach() NoTracking entities before SaveChanges()
|
||||
context.Attach(book.UserDefinedItem).State = Microsoft.EntityFrameworkCore.EntityState.Modified;
|
||||
foreach (var book in books)
|
||||
context.Attach(book.UserDefinedItem).State = Microsoft.EntityFrameworkCore.EntityState.Modified;
|
||||
|
||||
var qtyChanges = context.SaveChanges();
|
||||
if (qtyChanges > 0)
|
||||
{
|
||||
SearchEngineCommands.UpdateLiberatedStatus(book);
|
||||
SearchEngineCommands.UpdateBookTags(book);
|
||||
BookUserDefinedItemCommitted?.Invoke(null, book.AudibleProductId);
|
||||
}
|
||||
BookUserDefinedItemCommitted?.Invoke(null, books);
|
||||
|
||||
return qtyChanges;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Logger.Error(ex, $"Error updating {nameof(book.UserDefinedItem)}");
|
||||
Log.Logger.Error(ex, $"Error updating {nameof(Book.UserDefinedItem)}");
|
||||
throw;
|
||||
}
|
||||
}
|
||||
@@ -290,7 +332,7 @@ namespace ApplicationServices
|
||||
|
||||
// must be here instead of in db layer due to AaxcExists
|
||||
public static LiberatedStatus Liberated_Status(Book book)
|
||||
=> book.Audio_Exists ? book.UserDefinedItem.BookStatus
|
||||
=> book.Audio_Exists() ? book.UserDefinedItem.BookStatus
|
||||
: AudibleFileStorage.AaxcExists(book.AudibleProductId) ? LiberatedStatus.PartialDownload
|
||||
: LiberatedStatus.NotLiberated;
|
||||
|
||||
@@ -299,7 +341,14 @@ namespace ApplicationServices
|
||||
|
||||
// below are queries, not commands. maybe I should make a LibraryQueries. except there's already one of those...
|
||||
|
||||
public record LibraryStats(int booksFullyBackedUp, int booksDownloadedOnly, int booksNoProgress, int booksError, int pdfsDownloaded, int pdfsNotDownloaded) { }
|
||||
public record LibraryStats(int booksFullyBackedUp, int booksDownloadedOnly, int booksNoProgress, int booksError, int pdfsDownloaded, int pdfsNotDownloaded)
|
||||
{
|
||||
public int PendingBooks => booksNoProgress + booksDownloadedOnly;
|
||||
public bool HasPendingBooks => PendingBooks > 0;
|
||||
|
||||
public bool HasBookResults => 0 < (booksFullyBackedUp + booksDownloadedOnly + booksNoProgress + booksError);
|
||||
public bool HasPdfResults => 0 < (pdfsNotDownloaded + pdfsDownloaded);
|
||||
}
|
||||
public static LibraryStats GetCounts()
|
||||
{
|
||||
var libraryBooks = DbContexts.GetLibrary_Flat_NoTracking();
|
||||
@@ -317,7 +366,7 @@ namespace ApplicationServices
|
||||
|
||||
var boolResults = libraryBooks
|
||||
.AsParallel()
|
||||
.Where(lb => lb.Book.HasPdf)
|
||||
.Where(lb => lb.Book.HasPdf())
|
||||
.Select(lb => Pdf_Status(lb.Book))
|
||||
.ToList();
|
||||
var pdfsDownloaded = boolResults.Count(r => r == LiberatedStatus.Liberated);
|
||||
|
||||
@@ -111,13 +111,13 @@ namespace ApplicationServices
|
||||
AudibleProductId = a.Book.AudibleProductId,
|
||||
Locale = a.Book.Locale,
|
||||
Title = a.Book.Title,
|
||||
AuthorNames = a.Book.AuthorNames,
|
||||
NarratorNames = a.Book.NarratorNames,
|
||||
AuthorNames = a.Book.AuthorNames(),
|
||||
NarratorNames = a.Book.NarratorNames(),
|
||||
LengthInMinutes = a.Book.LengthInMinutes,
|
||||
Description = a.Book.Description,
|
||||
Publisher = a.Book.Publisher,
|
||||
HasPdf = a.Book.HasPdf,
|
||||
SeriesNames = a.Book.SeriesNames,
|
||||
HasPdf = a.Book.HasPdf(),
|
||||
SeriesNames = a.Book.SeriesNames(),
|
||||
SeriesOrder = a.Book.SeriesLink.Any() ? a.Book.SeriesLink?.Select(sl => $"{sl.Order} : {sl.Series.Name}").Aggregate((a, b) => $"{a}, {b}") : "",
|
||||
CommunityRatingOverall = a.Book.Rating?.OverallRating,
|
||||
CommunityRatingPerformance = a.Book.Rating?.PerformanceRating,
|
||||
@@ -125,7 +125,7 @@ namespace ApplicationServices
|
||||
PictureId = a.Book.PictureId,
|
||||
IsAbridged = a.Book.IsAbridged,
|
||||
DatePublished = a.Book.DatePublished,
|
||||
CategoriesNames = a.Book.CategoriesNames.Any() ? a.Book.CategoriesNames.Aggregate((a, b) => $"{a}, {b}") : "",
|
||||
CategoriesNames = a.Book.CategoriesNames().Any() ? a.Book.CategoriesNames().Aggregate((a, b) => $"{a}, {b}") : "",
|
||||
MyRatingOverall = a.Book.UserDefinedItem.Rating.OverallRating,
|
||||
MyRatingPerformance = a.Book.UserDefinedItem.Rating.PerformanceRating,
|
||||
MyRatingStory = a.Book.UserDefinedItem.Rating.StoryRating,
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using DataLayer;
|
||||
using LibationSearchEngine;
|
||||
|
||||
@@ -7,51 +9,99 @@ namespace ApplicationServices
|
||||
{
|
||||
public static class SearchEngineCommands
|
||||
{
|
||||
public static void FullReIndex(SearchEngine engine = null)
|
||||
{
|
||||
engine ??= new SearchEngine();
|
||||
var library = DbContexts.GetLibrary_Flat_NoTracking();
|
||||
engine.CreateNewIndex(library);
|
||||
}
|
||||
|
||||
public static SearchResultSet Search(string searchString) => performSearchEngineFunc_safe(e =>
|
||||
#region Search
|
||||
public static SearchResultSet Search(string searchString) => performSafeQuery(e =>
|
||||
e.Search(searchString)
|
||||
);
|
||||
|
||||
public static void UpdateBookTags(Book book) => performSearchEngineAction_safe(e =>
|
||||
e.UpdateTags(book.AudibleProductId, book.UserDefinedItem.Tags)
|
||||
private static T performSafeQuery<T>(Func<SearchEngine, T> func)
|
||||
{
|
||||
var engine = new SearchEngine();
|
||||
try
|
||||
{
|
||||
return func(engine);
|
||||
}
|
||||
catch (FileNotFoundException)
|
||||
{
|
||||
fullReIndex(engine);
|
||||
return func(engine);
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
public static EventHandler SearchEngineUpdated;
|
||||
|
||||
#region Update
|
||||
private static bool isUpdating;
|
||||
|
||||
public static void UpdateBooks(IEnumerable<Book> books)
|
||||
{
|
||||
// Semi-arbitrary. At some point it's more worth it to do a full re-index than to do one offs.
|
||||
// I did not benchmark before choosing the number here
|
||||
if (books.Count() > 15)
|
||||
FullReIndex();
|
||||
else
|
||||
{
|
||||
foreach (var book in books)
|
||||
{
|
||||
UpdateLiberatedStatus(book);
|
||||
UpdateBookTags(book);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void FullReIndex() => performSafeCommand(e =>
|
||||
fullReIndex(e)
|
||||
);
|
||||
|
||||
public static void UpdateLiberatedStatus(Book book) => performSearchEngineAction_safe(e =>
|
||||
internal static void UpdateLiberatedStatus(Book book) => performSafeCommand(e =>
|
||||
e.UpdateLiberatedStatus(book)
|
||||
);
|
||||
|
||||
private static void performSearchEngineAction_safe(Action<SearchEngine> action)
|
||||
internal static void UpdateBookTags(Book book) => performSafeCommand(e =>
|
||||
e.UpdateTags(book.AudibleProductId, book.UserDefinedItem.Tags)
|
||||
);
|
||||
|
||||
private static void performSafeCommand(Action<SearchEngine> action)
|
||||
{
|
||||
var engine = new SearchEngine();
|
||||
try
|
||||
{
|
||||
action(engine);
|
||||
update(action);
|
||||
}
|
||||
catch (FileNotFoundException)
|
||||
{
|
||||
FullReIndex(engine);
|
||||
action(engine);
|
||||
fullReIndex(new SearchEngine());
|
||||
update(action);
|
||||
}
|
||||
}
|
||||
|
||||
private static T performSearchEngineFunc_safe<T>(Func<SearchEngine, T> func)
|
||||
private static void update(Action<SearchEngine> action)
|
||||
{
|
||||
var engine = new SearchEngine();
|
||||
if (action is null)
|
||||
return;
|
||||
|
||||
// support nesting incl recursion
|
||||
var prevIsUpdating = isUpdating;
|
||||
try
|
||||
{
|
||||
return func(engine);
|
||||
isUpdating = true;
|
||||
|
||||
action(new SearchEngine());
|
||||
|
||||
if (!prevIsUpdating)
|
||||
SearchEngineUpdated?.Invoke(null, null);
|
||||
}
|
||||
catch (FileNotFoundException)
|
||||
finally
|
||||
{
|
||||
FullReIndex(engine);
|
||||
return func(engine);
|
||||
isUpdating = prevIsUpdating;
|
||||
}
|
||||
}
|
||||
|
||||
private static void fullReIndex(SearchEngine engine)
|
||||
{
|
||||
var library = DbContexts.GetLibrary_Flat_NoTracking();
|
||||
engine.CreateNewIndex(library);
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
@@ -106,16 +106,16 @@ namespace AudibleUtilities
|
||||
// 2 retries == 3 total
|
||||
.RetryAsync(2);
|
||||
|
||||
public Task<List<Item>> GetLibraryValidatedAsync(LibraryOptions.ResponseGroupOptions responseGroups = LibraryOptions.ResponseGroupOptions.ALL_OPTIONS, bool importEpisodes = true)
|
||||
public Task<List<Item>> GetLibraryValidatedAsync(LibraryOptions libraryOptions, bool importEpisodes = true)
|
||||
{
|
||||
// bug on audible's side. the 1st time after a long absence, a query to get library will return without titles or authors. a subsequent identical query will be successful. this is true whether or not tokens are refreshed
|
||||
// worse, this 1st dummy call doesn't seem to help:
|
||||
// var page = await api.GetLibraryAsync(new AudibleApi.LibraryOptions { NumberOfResultPerPage = 1, PageNumber = 1, PurchasedAfter = DateTime.Now.AddYears(-20), ResponseGroups = AudibleApi.LibraryOptions.ResponseGroupOptions.ALL_OPTIONS });
|
||||
// i don't want to incur the cost of making a full dummy call every time because it fails sometimes
|
||||
return policy.ExecuteAsync(() => getItemsAsync(responseGroups, importEpisodes));
|
||||
return policy.ExecuteAsync(() => getItemsAsync(libraryOptions, importEpisodes));
|
||||
}
|
||||
|
||||
private async Task<List<Item>> getItemsAsync(LibraryOptions.ResponseGroupOptions responseGroups, bool importEpisodes)
|
||||
private async Task<List<Item>> getItemsAsync(LibraryOptions libraryOptions, bool importEpisodes)
|
||||
{
|
||||
var items = new List<Item>();
|
||||
#if DEBUG
|
||||
@@ -131,7 +131,7 @@ namespace AudibleUtilities
|
||||
Serilog.Log.Logger.Debug("Begin initial library scan");
|
||||
|
||||
if (!items.Any())
|
||||
items = await Api.GetAllLibraryItemsAsync(responseGroups);
|
||||
items = await Api.GetAllLibraryItemsAsync(libraryOptions);
|
||||
|
||||
Serilog.Log.Logger.Debug("Initial library scan complete. Begin episode scan");
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="AudibleApi" Version="2.7.6.1" />
|
||||
<PackageReference Include="AudibleApi" Version="2.8.4.1" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -12,13 +12,13 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Dinah.EntityFrameworkCore" Version="4.0.0.3" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="6.0.4">
|
||||
<PackageReference Include="Dinah.EntityFrameworkCore" Version="4.1.0.1" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="6.0.5">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="6.0.4" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="6.0.4">
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="6.0.5" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="6.0.5">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
@@ -29,7 +29,7 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Update="appsettings.json">
|
||||
<None Update="migrate.json">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
|
||||
@@ -34,6 +34,7 @@ namespace DataLayer
|
||||
|
||||
// mutable
|
||||
public string PictureId { get; set; }
|
||||
public string PictureLarge { get; set; }
|
||||
|
||||
// book details
|
||||
public bool IsAbridged { get; private set; }
|
||||
@@ -42,27 +43,10 @@ namespace DataLayer
|
||||
// non-null. use "empty pattern"
|
||||
internal int CategoryId { get; private set; }
|
||||
public Category Category { get; private set; }
|
||||
public string[] CategoriesNames
|
||||
=> Category is null ? new string[0]
|
||||
: Category.ParentCategory is null ? new[] { Category.Name }
|
||||
: new[] { Category.ParentCategory.Name, Category.Name };
|
||||
public string[] CategoriesIds
|
||||
=> Category is null ? null
|
||||
: Category.ParentCategory is null ? new[] { Category.AudibleCategoryId }
|
||||
: new[] { Category.ParentCategory.AudibleCategoryId, Category.AudibleCategoryId };
|
||||
|
||||
public string TitleSortable => Formatters.GetSortName(Title);
|
||||
public string SeriesSortable => Formatters.GetSortName(SeriesNames);
|
||||
|
||||
// is owned, not optional 1:1
|
||||
public UserDefinedItem UserDefinedItem { get; private set; }
|
||||
|
||||
// UserDefinedItem convenience properties
|
||||
/// <summary>True if IsLiberated or Error. False if NotLiberated</summary>
|
||||
public bool Audio_Exists => UserDefinedItem.BookStatus != LiberatedStatus.NotLiberated;
|
||||
/// <summary>True if exists and IsLiberated. Else false</summary>
|
||||
public bool PDF_Exists => UserDefinedItem.PdfStatus == LiberatedStatus.Liberated;
|
||||
|
||||
// is owned, not optional 1:1
|
||||
/// <summary>The product's aggregate community rating</summary>
|
||||
public Rating Rating { get; private set; } = new Rating(0, 0, 0);
|
||||
@@ -124,11 +108,7 @@ namespace DataLayer
|
||||
.ToList();
|
||||
|
||||
public IEnumerable<Contributor> Authors => getContributions(Role.Author).Select(bc => bc.Contributor).ToList();
|
||||
public string AuthorNames => string.Join(", ", Authors.Select(a => a.Name));
|
||||
|
||||
public IEnumerable<Contributor> Narrators => getContributions(Role.Narrator).Select(bc => bc.Contributor).ToList();
|
||||
public string NarratorNames => string.Join(", ", Narrators.Select(n => n.Name));
|
||||
|
||||
public string Publisher => getContributions(Role.Publisher).SingleOrDefault()?.Contributor.Name;
|
||||
|
||||
public void ReplaceAuthors(IEnumerable<Contributor> authors, DbContext context = null)
|
||||
@@ -184,30 +164,6 @@ namespace DataLayer
|
||||
#region series
|
||||
private HashSet<SeriesBook> _seriesLink;
|
||||
public IEnumerable<SeriesBook> SeriesLink => _seriesLink?.ToList();
|
||||
public string SeriesNames
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_seriesLink is null)
|
||||
return "";
|
||||
|
||||
// first: alphabetical by name
|
||||
var withNames = _seriesLink
|
||||
.Where(s => !string.IsNullOrWhiteSpace(s.Series.Name))
|
||||
.Select(s => s.Series.Name)
|
||||
.OrderBy(a => a)
|
||||
.ToList();
|
||||
// then un-named are alpha by series id
|
||||
var nullNames = _seriesLink
|
||||
.Where(s => string.IsNullOrWhiteSpace(s.Series.Name))
|
||||
.Select(s => s.Series.AudibleSeriesId)
|
||||
.OrderBy(a => a)
|
||||
.ToList();
|
||||
|
||||
var all = withNames.Union(nullNames).ToList();
|
||||
return string.Join(", ", all);
|
||||
}
|
||||
}
|
||||
|
||||
public void UpsertSeries(Series series, string order, DbContext context = null)
|
||||
{
|
||||
@@ -229,7 +185,6 @@ namespace DataLayer
|
||||
#region supplements
|
||||
private HashSet<Supplement> _supplements;
|
||||
public IEnumerable<Supplement> Supplements => _supplements?.ToList();
|
||||
public bool HasPdf => Supplements.Any();
|
||||
|
||||
public void AddSupplementDownloadUrl(string url)
|
||||
{
|
||||
|
||||
@@ -12,7 +12,7 @@ namespace DataLayer
|
||||
public float StoryRating { get; private set; }
|
||||
|
||||
private Rating() { }
|
||||
internal Rating(float overallRating, float performanceRating, float storyRating)
|
||||
public Rating(float overallRating, float performanceRating, float storyRating)
|
||||
{
|
||||
OverallRating = overallRating;
|
||||
PerformanceRating = performanceRating;
|
||||
@@ -38,41 +38,6 @@ namespace DataLayer
|
||||
yield return StoryRating;
|
||||
}
|
||||
|
||||
public float FirstScore
|
||||
=> OverallRating > 0 ? OverallRating
|
||||
: PerformanceRating > 0 ? PerformanceRating
|
||||
: StoryRating;
|
||||
|
||||
/// <summary>character: ★</summary>
|
||||
const char STAR = '\u2605';
|
||||
/// <summary>character: ½</summary>
|
||||
const char HALF = '\u00BD';
|
||||
string getStars(float score)
|
||||
{
|
||||
var fullStars = (int)Math.Floor(score);
|
||||
|
||||
var starString = "".PadLeft(fullStars, STAR);
|
||||
|
||||
if (score - fullStars == 0.5f)
|
||||
starString += HALF;
|
||||
|
||||
return starString;
|
||||
}
|
||||
|
||||
public string ToStarString()
|
||||
{
|
||||
var items = new List<string>();
|
||||
|
||||
if (OverallRating > 0)
|
||||
items.Add($"Overall: {getStars(OverallRating)}");
|
||||
if (PerformanceRating > 0)
|
||||
items.Add($"Perform: {getStars(PerformanceRating)}");
|
||||
if (StoryRating > 0)
|
||||
items.Add($"Story: {getStars(StoryRating)}");
|
||||
|
||||
return string.Join("\r\n", items);
|
||||
}
|
||||
|
||||
public override string ToString() => $"Overall={OverallRating} Perf={PerformanceRating} Story={StoryRating}";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -141,9 +141,11 @@ namespace DataLayer
|
||||
get => _bookStatus;
|
||||
set
|
||||
{
|
||||
if (_bookStatus != value)
|
||||
{
|
||||
_bookStatus = value;
|
||||
// PartialDownload is a live/ephemeral status, not a persistent one. Do not store
|
||||
var displayStatus = value == LiberatedStatus.PartialDownload ? LiberatedStatus.NotLiberated : value;
|
||||
if (_bookStatus != displayStatus)
|
||||
{
|
||||
_bookStatus = displayStatus;
|
||||
OnItemChanged(nameof(BookStatus));
|
||||
}
|
||||
}
|
||||
|
||||
102
Source/DataLayer/EntityExtensions.cs
Normal file
102
Source/DataLayer/EntityExtensions.cs
Normal file
@@ -0,0 +1,102 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace DataLayer
|
||||
{
|
||||
public static class EntityExtensions
|
||||
{
|
||||
public static string TitleSortable(this Book book) => Formatters.GetSortName(book.Title);
|
||||
|
||||
public static string AuthorNames(this Book book) => string.Join(", ", book.Authors.Select(a => a.Name));
|
||||
public static string NarratorNames(this Book book) => string.Join(", ", book.Narrators.Select(n => n.Name));
|
||||
|
||||
/// <summary>True if IsLiberated or Error. False if NotLiberated</summary>
|
||||
public static bool Audio_Exists(this Book book) => book.UserDefinedItem.BookStatus != LiberatedStatus.NotLiberated;
|
||||
/// <summary>True if exists and IsLiberated. Else false</summary>
|
||||
public static bool PDF_Exists(this Book book) => book.UserDefinedItem.PdfStatus == LiberatedStatus.Liberated;
|
||||
|
||||
public static string SeriesSortable(this Book book) => Formatters.GetSortName(book.SeriesNames());
|
||||
public static bool HasPdf(this Book book) => book.Supplements.Any();
|
||||
public static string SeriesNames(this Book book)
|
||||
{
|
||||
if (book.SeriesLink is null)
|
||||
return "";
|
||||
|
||||
// first: alphabetical by name
|
||||
var withNames = book.SeriesLink
|
||||
.Where(s => !string.IsNullOrWhiteSpace(s.Series.Name))
|
||||
.Select(s => s.Series.Name)
|
||||
.OrderBy(a => a)
|
||||
.ToList();
|
||||
// then un-named are alpha by series id
|
||||
var nullNames = book.SeriesLink
|
||||
.Where(s => string.IsNullOrWhiteSpace(s.Series.Name))
|
||||
.Select(s => s.Series.AudibleSeriesId)
|
||||
.OrderBy(a => a)
|
||||
.ToList();
|
||||
|
||||
var all = withNames.Union(nullNames).ToList();
|
||||
return string.Join(", ", all);
|
||||
}
|
||||
public static string[] CategoriesNames(this Book book)
|
||||
=> book.Category is null ? new string[0]
|
||||
: book.Category.ParentCategory is null ? new[] { book.Category.Name }
|
||||
: new[] { book.Category.ParentCategory.Name, book.Category.Name };
|
||||
public static string[] CategoriesIds(this Book book)
|
||||
=> book.Category is null ? null
|
||||
: book.Category.ParentCategory is null ? new[] { book.Category.AudibleCategoryId }
|
||||
: new[] { book.Category.ParentCategory.AudibleCategoryId, book.Category.AudibleCategoryId };
|
||||
|
||||
public static string AggregateTitles(this IEnumerable<LibraryBook> libraryBooks, int max = 5)
|
||||
{
|
||||
if (libraryBooks is null || !libraryBooks.Any())
|
||||
return "";
|
||||
|
||||
max = Math.Max(max, 1);
|
||||
|
||||
var titles = libraryBooks.Select(lb => "- " + lb.Book.Title).ToList();
|
||||
var titlesAgg = titles.Take(max).Aggregate((a, b) => $"{a}\r\n{b}");
|
||||
if (titles.Count == max + 1)
|
||||
titlesAgg += $"\r\n\r\nand 1 other";
|
||||
else if (titles.Count > max + 1)
|
||||
titlesAgg += $"\r\n\r\nand {titles.Count - max } others";
|
||||
return titlesAgg;
|
||||
}
|
||||
|
||||
public static float FirstScore(this Rating rating)
|
||||
=> rating.OverallRating > 0 ? rating.OverallRating
|
||||
: rating.PerformanceRating > 0 ? rating.PerformanceRating
|
||||
: rating.StoryRating;
|
||||
public static string ToStarString(this Rating rating)
|
||||
{
|
||||
var items = new List<string>();
|
||||
|
||||
if (rating.OverallRating > 0)
|
||||
items.Add($"Overall: {getStars(rating.OverallRating)}");
|
||||
if (rating.PerformanceRating > 0)
|
||||
items.Add($"Perform: {getStars(rating.PerformanceRating)}");
|
||||
if (rating.StoryRating > 0)
|
||||
items.Add($"Story: {getStars(rating.StoryRating)}");
|
||||
|
||||
return string.Join("\r\n", items);
|
||||
}
|
||||
/// <summary>character: ★</summary>
|
||||
const char STAR = '\u2605';
|
||||
/// <summary>character: ½</summary>
|
||||
const char HALF = '\u00BD';
|
||||
private static string getStars(float score)
|
||||
{
|
||||
var fullStars = (int)Math.Floor(score);
|
||||
|
||||
var starString = new string(STAR, fullStars);
|
||||
|
||||
if (score - fullStars >= 0.25f)
|
||||
starString += HALF;
|
||||
|
||||
return starString;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -10,7 +10,7 @@ namespace DataLayer
|
||||
// ========================
|
||||
// these run against the db. linq queries against these MUST be translatable to sql. primatives only. no POCOs or this error occurs:
|
||||
// Unable to create a constant value of type 'DataLayer.Contributor'. Only primitive types or enumeration types are supported in this context.
|
||||
// to use full object-linq, load and use local
|
||||
// to use full object-linq, load and use Local. HOWEVER, Local is only hashed/indexed on PK. All other searches are very slow
|
||||
// load full table:
|
||||
// List<Contributor> contributors = ...;
|
||||
// Contributors.Load();
|
||||
|
||||
Binary file not shown.
394
Source/DataLayer/Migrations/20220510175257_AddPictureIDLargeMigration.Designer.cs
generated
Normal file
394
Source/DataLayer/Migrations/20220510175257_AddPictureIDLargeMigration.Designer.cs
generated
Normal file
@@ -0,0 +1,394 @@
|
||||
// <auto-generated />
|
||||
using System;
|
||||
using DataLayer;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace DataLayer.Migrations
|
||||
{
|
||||
[DbContext(typeof(LibationContext))]
|
||||
[Migration("20220510175257_AddPictureIDLargeMigration")]
|
||||
partial class AddPictureIDLargeMigration
|
||||
{
|
||||
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder.HasAnnotation("ProductVersion", "6.0.4");
|
||||
|
||||
modelBuilder.Entity("DataLayer.Book", b =>
|
||||
{
|
||||
b.Property<int>("BookId")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("AudibleProductId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int>("CategoryId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("ContentType")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<DateTime?>("DatePublished")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Description")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<bool>("IsAbridged")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("LengthInMinutes")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("Locale")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("PictureId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("PictureLarge")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Title")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("BookId");
|
||||
|
||||
b.HasIndex("AudibleProductId");
|
||||
|
||||
b.HasIndex("CategoryId");
|
||||
|
||||
b.ToTable("Books");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("DataLayer.BookContributor", b =>
|
||||
{
|
||||
b.Property<int>("BookId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("ContributorId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("Role")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<byte>("Order")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.HasKey("BookId", "ContributorId", "Role");
|
||||
|
||||
b.HasIndex("BookId");
|
||||
|
||||
b.HasIndex("ContributorId");
|
||||
|
||||
b.ToTable("BookContributor");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("DataLayer.Category", b =>
|
||||
{
|
||||
b.Property<int>("CategoryId")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("AudibleCategoryId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int?>("ParentCategoryCategoryId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.HasKey("CategoryId");
|
||||
|
||||
b.HasIndex("AudibleCategoryId");
|
||||
|
||||
b.HasIndex("ParentCategoryCategoryId");
|
||||
|
||||
b.ToTable("Categories");
|
||||
|
||||
b.HasData(
|
||||
new
|
||||
{
|
||||
CategoryId = -1,
|
||||
AudibleCategoryId = "",
|
||||
Name = ""
|
||||
});
|
||||
});
|
||||
|
||||
modelBuilder.Entity("DataLayer.Contributor", b =>
|
||||
{
|
||||
b.Property<int>("ContributorId")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("AudibleContributorId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("ContributorId");
|
||||
|
||||
b.HasIndex("Name");
|
||||
|
||||
b.ToTable("Contributors");
|
||||
|
||||
b.HasData(
|
||||
new
|
||||
{
|
||||
ContributorId = -1,
|
||||
Name = ""
|
||||
});
|
||||
});
|
||||
|
||||
modelBuilder.Entity("DataLayer.LibraryBook", b =>
|
||||
{
|
||||
b.Property<int>("BookId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("Account")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<DateTime>("DateAdded")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("BookId");
|
||||
|
||||
b.ToTable("LibraryBooks");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("DataLayer.Series", b =>
|
||||
{
|
||||
b.Property<int>("SeriesId")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("AudibleSeriesId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("SeriesId");
|
||||
|
||||
b.HasIndex("AudibleSeriesId");
|
||||
|
||||
b.ToTable("Series");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("DataLayer.SeriesBook", b =>
|
||||
{
|
||||
b.Property<int>("SeriesId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("BookId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("Order")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("SeriesId", "BookId");
|
||||
|
||||
b.HasIndex("BookId");
|
||||
|
||||
b.HasIndex("SeriesId");
|
||||
|
||||
b.ToTable("SeriesBook");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("DataLayer.Book", b =>
|
||||
{
|
||||
b.HasOne("DataLayer.Category", "Category")
|
||||
.WithMany()
|
||||
.HasForeignKey("CategoryId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.OwnsOne("DataLayer.Rating", "Rating", b1 =>
|
||||
{
|
||||
b1.Property<int>("BookId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b1.Property<float>("OverallRating")
|
||||
.HasColumnType("REAL");
|
||||
|
||||
b1.Property<float>("PerformanceRating")
|
||||
.HasColumnType("REAL");
|
||||
|
||||
b1.Property<float>("StoryRating")
|
||||
.HasColumnType("REAL");
|
||||
|
||||
b1.HasKey("BookId");
|
||||
|
||||
b1.ToTable("Books");
|
||||
|
||||
b1.WithOwner()
|
||||
.HasForeignKey("BookId");
|
||||
});
|
||||
|
||||
b.OwnsMany("DataLayer.Supplement", "Supplements", b1 =>
|
||||
{
|
||||
b1.Property<int>("SupplementId")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b1.Property<int>("BookId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b1.Property<string>("Url")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b1.HasKey("SupplementId");
|
||||
|
||||
b1.HasIndex("BookId");
|
||||
|
||||
b1.ToTable("Supplement");
|
||||
|
||||
b1.WithOwner("Book")
|
||||
.HasForeignKey("BookId");
|
||||
|
||||
b1.Navigation("Book");
|
||||
});
|
||||
|
||||
b.OwnsOne("DataLayer.UserDefinedItem", "UserDefinedItem", b1 =>
|
||||
{
|
||||
b1.Property<int>("BookId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b1.Property<int>("BookStatus")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b1.Property<int?>("PdfStatus")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b1.Property<string>("Tags")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b1.HasKey("BookId");
|
||||
|
||||
b1.ToTable("UserDefinedItem", (string)null);
|
||||
|
||||
b1.WithOwner("Book")
|
||||
.HasForeignKey("BookId");
|
||||
|
||||
b1.OwnsOne("DataLayer.Rating", "Rating", b2 =>
|
||||
{
|
||||
b2.Property<int>("UserDefinedItemBookId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b2.Property<float>("OverallRating")
|
||||
.HasColumnType("REAL");
|
||||
|
||||
b2.Property<float>("PerformanceRating")
|
||||
.HasColumnType("REAL");
|
||||
|
||||
b2.Property<float>("StoryRating")
|
||||
.HasColumnType("REAL");
|
||||
|
||||
b2.HasKey("UserDefinedItemBookId");
|
||||
|
||||
b2.ToTable("UserDefinedItem");
|
||||
|
||||
b2.WithOwner()
|
||||
.HasForeignKey("UserDefinedItemBookId");
|
||||
});
|
||||
|
||||
b1.Navigation("Book");
|
||||
|
||||
b1.Navigation("Rating");
|
||||
});
|
||||
|
||||
b.Navigation("Category");
|
||||
|
||||
b.Navigation("Rating");
|
||||
|
||||
b.Navigation("Supplements");
|
||||
|
||||
b.Navigation("UserDefinedItem");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("DataLayer.BookContributor", b =>
|
||||
{
|
||||
b.HasOne("DataLayer.Book", "Book")
|
||||
.WithMany("ContributorsLink")
|
||||
.HasForeignKey("BookId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("DataLayer.Contributor", "Contributor")
|
||||
.WithMany("BooksLink")
|
||||
.HasForeignKey("ContributorId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Book");
|
||||
|
||||
b.Navigation("Contributor");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("DataLayer.Category", b =>
|
||||
{
|
||||
b.HasOne("DataLayer.Category", "ParentCategory")
|
||||
.WithMany()
|
||||
.HasForeignKey("ParentCategoryCategoryId");
|
||||
|
||||
b.Navigation("ParentCategory");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("DataLayer.LibraryBook", b =>
|
||||
{
|
||||
b.HasOne("DataLayer.Book", "Book")
|
||||
.WithOne()
|
||||
.HasForeignKey("DataLayer.LibraryBook", "BookId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Book");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("DataLayer.SeriesBook", b =>
|
||||
{
|
||||
b.HasOne("DataLayer.Book", "Book")
|
||||
.WithMany("SeriesLink")
|
||||
.HasForeignKey("BookId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("DataLayer.Series", "Series")
|
||||
.WithMany("BooksLink")
|
||||
.HasForeignKey("SeriesId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Book");
|
||||
|
||||
b.Navigation("Series");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("DataLayer.Book", b =>
|
||||
{
|
||||
b.Navigation("ContributorsLink");
|
||||
|
||||
b.Navigation("SeriesLink");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("DataLayer.Contributor", b =>
|
||||
{
|
||||
b.Navigation("BooksLink");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("DataLayer.Series", b =>
|
||||
{
|
||||
b.Navigation("BooksLink");
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace DataLayer.Migrations
|
||||
{
|
||||
public partial class AddPictureIDLargeMigration : Migration
|
||||
{
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AddColumn<string>(
|
||||
name: "PictureLarge",
|
||||
table: "Books",
|
||||
type: "TEXT",
|
||||
nullable: true);
|
||||
}
|
||||
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropColumn(
|
||||
name: "PictureLarge",
|
||||
table: "Books");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,8 @@ using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace DataLayer.Migrations
|
||||
{
|
||||
[DbContext(typeof(LibationContext))]
|
||||
@@ -13,8 +15,7 @@ namespace DataLayer.Migrations
|
||||
protected override void BuildModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder
|
||||
.HasAnnotation("ProductVersion", "5.0.10");
|
||||
modelBuilder.HasAnnotation("ProductVersion", "6.0.4");
|
||||
|
||||
modelBuilder.Entity("DataLayer.Book", b =>
|
||||
{
|
||||
@@ -49,6 +50,9 @@ namespace DataLayer.Migrations
|
||||
b.Property<string>("PictureId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("PictureLarge")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Title")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
@@ -267,7 +271,7 @@ namespace DataLayer.Migrations
|
||||
|
||||
b1.HasKey("BookId");
|
||||
|
||||
b1.ToTable("UserDefinedItem");
|
||||
b1.ToTable("UserDefinedItem", (string)null);
|
||||
|
||||
b1.WithOwner("Book")
|
||||
.HasForeignKey("BookId");
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
{
|
||||
"ConnectionStrings": {
|
||||
"// this connection string is ONLY used for DataLayer's Migrations. this appsettings.json file is NOT used at all by application; it is overwritten": "",
|
||||
"LibationContext": "Data Source=LibationContext.db;Foreign Keys=False;"
|
||||
}
|
||||
}
|
||||
5
Source/DataLayer/migrate.json
Normal file
5
Source/DataLayer/migrate.json
Normal file
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"ConnectionStrings": {
|
||||
"LibationContext": "Data Source=LibationContext.db;Foreign Keys=False;"
|
||||
}
|
||||
}
|
||||
@@ -164,6 +164,9 @@ namespace DtoImporterService
|
||||
// set/update book-specific info which may have changed
|
||||
if (item.PictureId is not null)
|
||||
book.PictureId = item.PictureId;
|
||||
|
||||
if (item.PictureLarge is not null)
|
||||
book.PictureLarge = item.PictureLarge;
|
||||
|
||||
book.UpdateProductRating(item.Product_OverallStars, item.Product_PerformanceStars, item.Product_StoryStars);
|
||||
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -4,6 +4,7 @@ using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using AaxDecrypter;
|
||||
using ApplicationServices;
|
||||
using AudibleApi;
|
||||
using DataLayer;
|
||||
using Dinah.Core;
|
||||
@@ -15,9 +16,10 @@ 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;
|
||||
public override bool Validate(LibraryBook libraryBook) => !libraryBook.Book.Audio_Exists();
|
||||
|
||||
public override void Cancel() => abDownloader?.Cancel();
|
||||
|
||||
@@ -42,7 +44,7 @@ namespace FileLiberator
|
||||
|
||||
try
|
||||
{
|
||||
if (libraryBook.Book.Audio_Exists)
|
||||
if (libraryBook.Book.Audio_Exists())
|
||||
return new StatusHandler { "Cannot find decrypt. Final audio file already exists" };
|
||||
|
||||
bool success = false;
|
||||
@@ -65,17 +67,23 @@ 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)
|
||||
return new StatusHandler { "Cannot find final audio file after decryption" };
|
||||
|
||||
libraryBook.Book.UserDefinedItem.BookStatus = LiberatedStatus.Liberated;
|
||||
if (Configuration.Instance.DownloadCoverArt)
|
||||
DownloadCoverArt(libraryBook);
|
||||
|
||||
libraryBook.Book.UpdateBookStatus(LiberatedStatus.Liberated);
|
||||
|
||||
return new StatusHandler();
|
||||
}
|
||||
@@ -113,7 +121,7 @@ namespace FileLiberator
|
||||
: new AaxcDownloadSingleConverter(outFileName, cacheDir, audiobookDlLic);
|
||||
|
||||
if (config.AllowLibationFixup)
|
||||
converter.RetrievedMetadata += (_, tags) => tags.Generes = string.Join(", ", libraryBook.Book.CategoriesNames);
|
||||
converter.RetrievedMetadata += (_, tags) => tags.Generes = string.Join(", ", libraryBook.Book.CategoriesNames());
|
||||
|
||||
abDownloader = converter;
|
||||
}
|
||||
@@ -128,6 +136,7 @@ namespace FileLiberator
|
||||
|
||||
// REAL WORK DONE HERE
|
||||
var success = await Task.Run(abDownloader.Run);
|
||||
|
||||
return success;
|
||||
}
|
||||
finally
|
||||
@@ -187,31 +196,27 @@ namespace FileLiberator
|
||||
}
|
||||
}
|
||||
|
||||
NAudio.Lame.LameConfig lameConfig = new();
|
||||
|
||||
|
||||
lameConfig.Mode = NAudio.Lame.MPEGMode.Mono;
|
||||
audiobookDlLic.LameConfig = new();
|
||||
audiobookDlLic.LameConfig.Mode = NAudio.Lame.MPEGMode.Mono;
|
||||
|
||||
if (config.LameTargetBitrate)
|
||||
{
|
||||
if (config.LameConstantBitrate)
|
||||
lameConfig.BitRate = config.LameBitrate;
|
||||
audiobookDlLic.LameConfig.BitRate = config.LameBitrate;
|
||||
else
|
||||
{
|
||||
lameConfig.ABRRateKbps = config.LameBitrate;
|
||||
lameConfig.VBR = NAudio.Lame.VBRMode.ABR;
|
||||
lameConfig.WriteVBRTag = true;
|
||||
audiobookDlLic.LameConfig.ABRRateKbps = config.LameBitrate;
|
||||
audiobookDlLic.LameConfig.VBR = NAudio.Lame.VBRMode.ABR;
|
||||
audiobookDlLic.LameConfig.WriteVBRTag = true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
lameConfig.VBR = NAudio.Lame.VBRMode.Default;
|
||||
lameConfig.VBRQuality = config.LameVBRQuality;
|
||||
lameConfig.WriteVBRTag = true;
|
||||
audiobookDlLic.LameConfig.VBR = NAudio.Lame.VBRMode.Default;
|
||||
audiobookDlLic.LameConfig.VBRQuality = config.LameVBRQuality;
|
||||
audiobookDlLic.LameConfig.WriteVBRTag = true;
|
||||
}
|
||||
|
||||
audiobookDlLic.LameConfig = lameConfig;
|
||||
|
||||
return audiobookDlLic;
|
||||
}
|
||||
|
||||
@@ -277,5 +282,34 @@ namespace FileLiberator
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private void DownloadCoverArt(LibraryBook libraryBook)
|
||||
{
|
||||
var destinationDir = AudibleFileStorage.Audio.GetDestinationDirectory(libraryBook);
|
||||
var coverPath = AudibleFileStorage.Audio.GetBooksDirectoryFilename(libraryBook, ".jpg");
|
||||
coverPath = Path.Combine(destinationDir, Path.GetFileName(coverPath));
|
||||
|
||||
try
|
||||
{
|
||||
if (File.Exists(coverPath))
|
||||
FileUtility.SaferDelete(coverPath);
|
||||
|
||||
(string picId, PictureSize size) = libraryBook.Book.PictureLarge is null ?
|
||||
(libraryBook.Book.PictureId, PictureSize.Native) :
|
||||
(libraryBook.Book.PictureLarge, PictureSize.Native);
|
||||
|
||||
var picBytes = PictureStorage.GetPictureSynchronously(new PictureDefinition(picId, size));
|
||||
|
||||
if (picBytes.Length > 0)
|
||||
File.WriteAllBytes(coverPath, picBytes);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
//Failure to download cover art should not be
|
||||
//considered a failure to download the book
|
||||
Serilog.Log.Logger.Error(ex, $"Error downloading cover art of {libraryBook.Book.AudibleProductId} to {coverPath} catalog product.");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,31 +0,0 @@
|
||||
using System;
|
||||
using System.Net.Http;
|
||||
using System.Threading.Tasks;
|
||||
using Dinah.Core.Net.Http;
|
||||
|
||||
namespace FileLiberator
|
||||
{
|
||||
// currently only used to download the .zip flies for upgrade
|
||||
public class DownloadFile : Streamable
|
||||
{
|
||||
public async Task<string> PerformDownloadFileAsync(string downloadUrl, string proposedDownloadFilePath)
|
||||
{
|
||||
var client = new HttpClient();
|
||||
|
||||
var progress = new Progress<DownloadProgress>(OnStreamingProgressChanged);
|
||||
|
||||
OnStreamingBegin(proposedDownloadFilePath);
|
||||
|
||||
try
|
||||
{
|
||||
var actualDownloadedFilePath = await client.DownloadFileAsync(downloadUrl, proposedDownloadFilePath, progress);
|
||||
OnFileCreated("Upgrade", actualDownloadedFilePath);
|
||||
return actualDownloadedFilePath;
|
||||
}
|
||||
finally
|
||||
{
|
||||
OnStreamingCompleted(proposedDownloadFilePath);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,7 @@ using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Threading.Tasks;
|
||||
using ApplicationServices;
|
||||
using DataLayer;
|
||||
using Dinah.Core.ErrorHandling;
|
||||
using Dinah.Core.Net.Http;
|
||||
@@ -14,9 +15,10 @@ 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;
|
||||
&& !libraryBook.Book.PDF_Exists();
|
||||
|
||||
public override async Task<StatusHandler> ProcessAsync(LibraryBook libraryBook)
|
||||
{
|
||||
@@ -28,7 +30,7 @@ namespace FileLiberator
|
||||
var actualDownloadedFilePath = await downloadPdfAsync(libraryBook, proposedDownloadFilePath);
|
||||
var result = verifyDownload(actualDownloadedFilePath);
|
||||
|
||||
libraryBook.Book.UserDefinedItem.PdfStatus = result.IsSuccess ? LiberatedStatus.Liberated : LiberatedStatus.NotLiberated;
|
||||
libraryBook.Book.UpdatePdfStatus(result.IsSuccess ? LiberatedStatus.Liberated : LiberatedStatus.NotLiberated);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\AaxDecrypter\AaxDecrypter.csproj" />
|
||||
<ProjectReference Include="..\ApplicationServices\ApplicationServices.csproj" />
|
||||
<ProjectReference Include="..\DataLayer\DataLayer.csproj" />
|
||||
<ProjectReference Include="..\AudibleUtilities\AudibleUtilities.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
@@ -5,11 +5,13 @@ using System.Threading.Tasks;
|
||||
using DataLayer;
|
||||
using Dinah.Core;
|
||||
using Dinah.Core.ErrorHandling;
|
||||
using LibationFileManager;
|
||||
|
||||
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>
|
||||
@@ -49,7 +51,7 @@ namespace FileLiberator
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
|
||||
public async Task<StatusHandler> TryProcessAsync(LibraryBook libraryBook)
|
||||
=> Validate(libraryBook)
|
||||
? await ProcessAsync(libraryBook)
|
||||
|
||||
@@ -134,6 +134,7 @@ namespace FileManager
|
||||
|
||||
private void AddPath(string path)
|
||||
{
|
||||
if (!File.Exists(path)) return;
|
||||
if (File.GetAttributes(path).HasFlag(FileAttributes.Directory))
|
||||
AddUniqueFiles(FileUtility.SaferEnumerateFiles(path, SearchPattern, SearchOption));
|
||||
else
|
||||
|
||||
@@ -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,11 +29,10 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="CommandLineParser" Version="2.8.0" />
|
||||
<PackageReference Include="CommandLineParser" Version="2.9.1" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\ApplicationServices\ApplicationServices.csproj" />
|
||||
<ProjectReference Include="..\AppScaffolding\AppScaffolding.csproj" />
|
||||
<ProjectReference Include="..\FileLiberator\FileLiberator.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
@@ -9,7 +9,6 @@ using FileLiberator;
|
||||
|
||||
namespace LibationCli
|
||||
{
|
||||
// streamlined, non-Forms copy of ProcessorAutomationController
|
||||
public abstract class ProcessableOptionsBase : OptionsBase
|
||||
{
|
||||
protected static TProcessable CreateProcessable<TProcessable>(EventHandler<LibraryBook> completedAction = null)
|
||||
|
||||
@@ -27,7 +27,6 @@ namespace LibationCli
|
||||
// //
|
||||
//***********************************************//
|
||||
Setup.Initialize();
|
||||
Setup.SubscribeToDatabaseEvents();
|
||||
|
||||
var types = Setup.LoadVerbs();
|
||||
|
||||
|
||||
@@ -34,15 +34,15 @@ namespace LibationCli
|
||||
|
||||
private static void checkForUpdate()
|
||||
{
|
||||
var (hasUpgrade, zipUrl, htmlUrl, zipName) = LibationScaffolding.GetLatestRelease();
|
||||
if (!hasUpgrade)
|
||||
var upgradeProperties = LibationScaffolding.GetLatestRelease();
|
||||
if (upgradeProperties is null)
|
||||
return;
|
||||
|
||||
var origColor = Console.ForegroundColor;
|
||||
try
|
||||
{
|
||||
Console.ForegroundColor = ConsoleColor.Red;
|
||||
Console.WriteLine($"UPDATE AVAILABLE @ {zipUrl}");
|
||||
Console.WriteLine($"UPDATE AVAILABLE @ {upgradeProperties.ZipUrl}");
|
||||
}
|
||||
finally
|
||||
{
|
||||
@@ -50,11 +50,6 @@ namespace LibationCli
|
||||
}
|
||||
}
|
||||
|
||||
public static void SubscribeToDatabaseEvents()
|
||||
{
|
||||
DataLayer.UserDefinedItem.ItemChanged += (sender, e) => ApplicationServices.LibraryCommands.UpdateUserDefinedItem(((DataLayer.UserDefinedItem)sender).Book);
|
||||
}
|
||||
|
||||
public static Type[] LoadVerbs() => Assembly.GetExecutingAssembly()
|
||||
.GetTypes()
|
||||
.Where(t => t.GetCustomAttribute<VerbAttribute>() is not null)
|
||||
|
||||
@@ -179,6 +179,34 @@ namespace LibationFileManager
|
||||
get => persistentDictionary.GetNonString<int>(nameof(LameVBRQuality));
|
||||
set => persistentDictionary.SetNonString(nameof(LameVBRQuality), value);
|
||||
}
|
||||
|
||||
[Description("A Dictionary of GridView data property names and bool indicating its column's visibility in ProductsGrid")]
|
||||
public Dictionary<string, bool> GridColumnsVisibilities
|
||||
{
|
||||
get => persistentDictionary.GetNonString<Dictionary<string, bool>>(nameof(GridColumnsVisibilities));
|
||||
set => persistentDictionary.SetNonString(nameof(GridColumnsVisibilities), value);
|
||||
}
|
||||
|
||||
[Description("A Dictionary of GridView data property names and int indicating its column's display index in ProductsGrid")]
|
||||
public Dictionary<string, int> GridColumnsDisplayIndices
|
||||
{
|
||||
get => persistentDictionary.GetNonString<Dictionary<string,int>>(nameof(GridColumnsDisplayIndices));
|
||||
set => persistentDictionary.SetNonString(nameof(GridColumnsDisplayIndices), value);
|
||||
}
|
||||
|
||||
[Description("A Dictionary of GridView data property names and int indicating its column's width in ProductsGrid")]
|
||||
public Dictionary<string, int> GridColumnsWidths
|
||||
{
|
||||
get => persistentDictionary.GetNonString<Dictionary<string,int>>(nameof(GridColumnsWidths));
|
||||
set => persistentDictionary.SetNonString(nameof(GridColumnsWidths), value);
|
||||
}
|
||||
|
||||
[Description("Save cover image alongside audiobook?")]
|
||||
public bool DownloadCoverArt
|
||||
{
|
||||
get => persistentDictionary.GetNonString<bool>(nameof(DownloadCoverArt));
|
||||
set => persistentDictionary.SetNonString(nameof(DownloadCoverArt), value);
|
||||
}
|
||||
|
||||
public enum BadBookAction
|
||||
{
|
||||
@@ -240,6 +268,13 @@ namespace LibationFileManager
|
||||
}
|
||||
}
|
||||
|
||||
[Description("Auto download episodes? Efter scan, download new books in 'checked' accounts.")]
|
||||
public bool AutoDownloadEpisodes
|
||||
{
|
||||
get => persistentDictionary.GetNonString<bool>(nameof(AutoDownloadEpisodes));
|
||||
set => persistentDictionary.SetNonString(nameof(AutoDownloadEpisodes), value);
|
||||
}
|
||||
|
||||
#region templates: custom file naming
|
||||
|
||||
[Description("How to format the folders in which files will be saved")]
|
||||
@@ -393,7 +428,7 @@ namespace LibationFileManager
|
||||
return libationFilesPathCache;
|
||||
|
||||
// FIRST: must write here before SettingsFilePath in next step reads cache
|
||||
libationFilesPathCache = getLiberationFilesSettingFromJson();
|
||||
libationFilesPathCache = getLibationFilesSettingFromJson();
|
||||
|
||||
// SECOND. before setting to json file with SetWithJsonPath, PersistentDictionary must exist
|
||||
persistentDictionary = new PersistentDictionary(SettingsFilePath);
|
||||
@@ -415,7 +450,7 @@ namespace LibationFileManager
|
||||
|
||||
private static string libationFilesPathCache;
|
||||
|
||||
private string getLiberationFilesSettingFromJson()
|
||||
private string getLibationFilesSettingFromJson()
|
||||
{
|
||||
string startingContents = null;
|
||||
try
|
||||
@@ -454,6 +489,14 @@ namespace LibationFileManager
|
||||
{
|
||||
libationFilesPathCache = null;
|
||||
|
||||
// ensure exists
|
||||
if (!File.Exists(APPSETTINGS_JSON))
|
||||
{
|
||||
// getter creates new file, loads PersistentDictionary
|
||||
var _ = LibationFiles;
|
||||
System.Threading.Thread.Sleep(100);
|
||||
}
|
||||
|
||||
var startingContents = File.ReadAllText(APPSETTINGS_JSON);
|
||||
var jObj = JObject.Parse(startingContents);
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="6.0.0" />
|
||||
<PackageReference Include="Serilog.Exceptions" Version="8.1.0" />
|
||||
<PackageReference Include="Serilog.Exceptions" Version="8.2.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -8,13 +8,13 @@ using System.Threading.Tasks;
|
||||
|
||||
namespace LibationFileManager
|
||||
{
|
||||
public enum PictureSize { _80x80 = 80, _300x300 = 300, _500x500 = 500 }
|
||||
public enum PictureSize { Native, _80x80 = 80, _300x300 = 300, _500x500 = 500 }
|
||||
public class PictureCachedEventArgs : EventArgs
|
||||
{
|
||||
public PictureDefinition Definition { get; internal set; }
|
||||
public byte[] Picture { get; internal set; }
|
||||
}
|
||||
public struct PictureDefinition
|
||||
public struct PictureDefinition : IEquatable<PictureDefinition>
|
||||
{
|
||||
public string PictureId { get; }
|
||||
public PictureSize Size { get; }
|
||||
@@ -24,6 +24,11 @@ namespace LibationFileManager
|
||||
PictureId = pictureId;
|
||||
Size = pictureSize;
|
||||
}
|
||||
|
||||
public bool Equals(PictureDefinition other)
|
||||
{
|
||||
return PictureId == other.PictureId && Size == other.Size;
|
||||
}
|
||||
}
|
||||
public static class PictureStorage
|
||||
{
|
||||
@@ -113,8 +118,8 @@ namespace LibationFileManager
|
||||
|
||||
try
|
||||
{
|
||||
var sz = (int)def.Size;
|
||||
var bytes = imageDownloadClient.GetByteArrayAsync("ht" + $"tps://images-na.ssl-images-amazon.com/images/I/{def.PictureId}._SL{sz}_.jpg").Result;
|
||||
var sizeStr = def.Size == PictureSize.Native ? "" : $"._SL{(int)def.Size}_";
|
||||
var bytes = imageDownloadClient.GetByteArrayAsync("ht" + $"tps://images-na.ssl-images-amazon.com/images/I/{def.PictureId}{sizeStr}.jpg").Result;
|
||||
|
||||
// save image file. make sure to not save default image
|
||||
var path = getPath(def);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -18,20 +18,20 @@ namespace LibationSearchEngine
|
||||
{
|
||||
public const Lucene.Net.Util.Version Version = Lucene.Net.Util.Version.LUCENE_30;
|
||||
|
||||
// not customizable. don't move to config
|
||||
private static string SearchEngineDirectory { get; }
|
||||
= new System.IO.DirectoryInfo(Configuration.Instance.LibationFiles).CreateSubdirectory("SearchEngine").FullName;
|
||||
|
||||
public const string _ID_ = "_ID_";
|
||||
public const string TAGS = "tags";
|
||||
// special field for each book which includes all major parts of the book's metadata. enables non-targetting searching
|
||||
public const string ALL = "all";
|
||||
|
||||
// the workaround which allows displaying all books when query is empty
|
||||
public const string ALL_QUERY = "*:*";
|
||||
#region index rules
|
||||
// common fields used in the "all" default search field
|
||||
public const string ALL_AUDIBLE_PRODUCT_ID = nameof(Book.AudibleProductId);
|
||||
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";
|
||||
|
||||
#region index rules
|
||||
private static ReadOnlyDictionary<string, Func<LibraryBook, string>> idIndexRules { get; }
|
||||
private static ReadOnlyDictionary<string, Func<LibraryBook, string>> idIndexRules { get; }
|
||||
= new ReadOnlyDictionary<string, Func<LibraryBook, string>>(
|
||||
new Dictionary<string, Func<LibraryBook, string>>
|
||||
{
|
||||
@@ -50,31 +50,23 @@ namespace LibationSearchEngine
|
||||
[nameof(Book.DatePublished)] = lb => lb.Book.DatePublished?.ToLuceneString(),
|
||||
|
||||
[nameof(Book.Title)] = lb => lb.Book.Title,
|
||||
[nameof(Book.AuthorNames)] = lb => lb.Book.AuthorNames,
|
||||
["Author"] = lb => lb.Book.AuthorNames,
|
||||
["Authors"] = lb => lb.Book.AuthorNames,
|
||||
[nameof(Book.NarratorNames)] = lb => lb.Book.NarratorNames,
|
||||
["Narrator"] = lb => lb.Book.NarratorNames,
|
||||
["Narrators"] = lb => lb.Book.NarratorNames,
|
||||
[ALL_AUTHOR_NAMES] = lb => lb.Book.AuthorNames(),
|
||||
["Author"] = lb => lb.Book.AuthorNames(),
|
||||
["Authors"] = lb => lb.Book.AuthorNames(),
|
||||
[ALL_NARRATOR_NAMES] = lb => lb.Book.NarratorNames(),
|
||||
["Narrator"] = lb => lb.Book.NarratorNames(),
|
||||
["Narrators"] = lb => lb.Book.NarratorNames(),
|
||||
[nameof(Book.Publisher)] = lb => lb.Book.Publisher,
|
||||
|
||||
[nameof(Book.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)),
|
||||
|
||||
[nameof(Book.CategoriesNames)] = lb => lb.Book.CategoriesIds is null ? null : string.Join(", ", lb.Book.CategoriesIds),
|
||||
[nameof(Book.Category)] = lb => lb.Book.CategoriesIds is null ? null : string.Join(", ", lb.Book.CategoriesIds),
|
||||
["Categories"] = lb => lb.Book.CategoriesIds is null ? null : string.Join(", ", lb.Book.CategoriesIds),
|
||||
["CategoriesId"] = lb => lb.Book.CategoriesIds is null ? null : string.Join(", ", lb.Book.CategoriesIds),
|
||||
["CategoryId"] = lb => lb.Book.CategoriesIds is null ? null : string.Join(", ", lb.Book.CategoriesIds),
|
||||
["CategoriesNames"] = lb => lb.Book.CategoriesIds() is null ? null : string.Join(", ", lb.Book.CategoriesIds()),
|
||||
[nameof(Book.Category)] = lb => lb.Book.CategoriesIds() is null ? null : string.Join(", ", lb.Book.CategoriesIds()),
|
||||
["Categories"] = lb => lb.Book.CategoriesIds() is null ? null : string.Join(", ", lb.Book.CategoriesIds()),
|
||||
["CategoriesId"] = lb => lb.Book.CategoriesIds() is null ? null : string.Join(", ", lb.Book.CategoriesIds()),
|
||||
["CategoryId"] = lb => lb.Book.CategoriesIds() is null ? null : string.Join(", ", lb.Book.CategoriesIds()),
|
||||
|
||||
[TAGS.FirstCharToUpper()] = lb => lb.Book.UserDefinedItem.Tags,
|
||||
|
||||
@@ -107,14 +99,14 @@ namespace LibationSearchEngine
|
||||
= new ReadOnlyDictionary<string, Func<LibraryBook, bool>>(
|
||||
new Dictionary<string, Func<LibraryBook, bool>>
|
||||
{
|
||||
["HasDownloads"] = lb => lb.Book.HasPdf,
|
||||
["HasDownload"] = lb => lb.Book.HasPdf,
|
||||
["Downloads"] = lb => lb.Book.HasPdf,
|
||||
["Download"] = lb => lb.Book.HasPdf,
|
||||
["HasPDFs"] = lb => lb.Book.HasPdf,
|
||||
["HasPDF"] = lb => lb.Book.HasPdf,
|
||||
["PDFs"] = lb => lb.Book.HasPdf,
|
||||
["PDF"] = lb => lb.Book.HasPdf,
|
||||
["HasDownloads"] = lb => lb.Book.HasPdf(),
|
||||
["HasDownload"] = lb => lb.Book.HasPdf(),
|
||||
["Downloads"] = lb => lb.Book.HasPdf(),
|
||||
["Download"] = lb => lb.Book.HasPdf(),
|
||||
["HasPDFs"] = lb => lb.Book.HasPdf(),
|
||||
["HasPDF"] = lb => lb.Book.HasPdf(),
|
||||
["PDFs"] = lb => lb.Book.HasPdf(),
|
||||
["PDF"] = lb => lb.Book.HasPdf(),
|
||||
|
||||
["IsRated"] = lb => lb.Book.UserDefinedItem.Rating.OverallRating > 0f,
|
||||
["Rated"] = lb => lb.Book.UserDefinedItem.Rating.OverallRating > 0f,
|
||||
@@ -125,14 +117,15 @@ namespace LibationSearchEngine
|
||||
[nameof(Book.IsAbridged)] = lb => lb.Book.IsAbridged,
|
||||
["Abridged"] = lb => lb.Book.IsAbridged,
|
||||
|
||||
// this will only be evaluated at time of re-index. ie: state of files moved later will be out of sync until next re-index
|
||||
["IsLiberated"] = lb => isLiberated(lb.Book),
|
||||
["Liberated"] = lb => isLiberated(lb.Book),
|
||||
["LiberatedError"] = lb => liberatedError(lb.Book),
|
||||
|
||||
["Podcast"] = lb => lb.Book.ContentType == ContentType.Episode,
|
||||
["Podcasts"] = lb => lb.Book.ContentType == ContentType.Episode,
|
||||
["IsPodcast"] = lb => lb.Book.ContentType == ContentType.Episode,
|
||||
["Episode"] = lb => lb.Book.ContentType == ContentType.Episode,
|
||||
["Episodes"] = lb => lb.Book.ContentType == ContentType.Episode,
|
||||
["IsEpisode"] = lb => lb.Book.ContentType == ContentType.Episode,
|
||||
}
|
||||
);
|
||||
@@ -151,10 +144,11 @@ namespace LibationSearchEngine
|
||||
private static IEnumerable<Func<LibraryBook, string>> allFieldIndexRules { get; }
|
||||
= new List<Func<LibraryBook, string>>
|
||||
{
|
||||
idIndexRules[nameof(Book.AudibleProductId)],
|
||||
stringIndexRules[nameof(Book.Title)],
|
||||
stringIndexRules[nameof(Book.AuthorNames)],
|
||||
stringIndexRules[nameof(Book.NarratorNames)]
|
||||
idIndexRules[ALL_AUDIBLE_PRODUCT_ID],
|
||||
stringIndexRules[ALL_TITLE],
|
||||
stringIndexRules[ALL_AUTHOR_NAMES],
|
||||
stringIndexRules[ALL_NARRATOR_NAMES],
|
||||
stringIndexRules[ALL_SERIES_NAMES]
|
||||
};
|
||||
#endregion
|
||||
|
||||
@@ -182,18 +176,6 @@ namespace LibationSearchEngine
|
||||
foreach (var key in numberIndexRules.Keys)
|
||||
yield return key;
|
||||
}
|
||||
|
||||
public static IEnumerable<string> GetSearchFields()
|
||||
{
|
||||
foreach (var key in idIndexRules.Keys)
|
||||
yield return key;
|
||||
foreach (var key in stringIndexRules.Keys)
|
||||
yield return key;
|
||||
foreach (var key in boolIndexRules.Keys)
|
||||
yield return key;
|
||||
foreach (var key in numberIndexRules.Keys)
|
||||
yield return key;
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region create and update index
|
||||
@@ -290,6 +272,10 @@ namespace LibationSearchEngine
|
||||
book.AudibleProductId,
|
||||
d =>
|
||||
{
|
||||
//
|
||||
// TODO: better synonym handling. This is too easy to mess up
|
||||
//
|
||||
|
||||
// fields are key value pairs. MULTIPLE FIELDS CAN POTENTIALLY HAVE THE SAME KEY.
|
||||
// ie: must remove old before adding new else will create unwanted duplicates.
|
||||
var v1 = isLiberated(book);
|
||||
@@ -331,6 +317,9 @@ namespace LibationSearchEngine
|
||||
}
|
||||
#endregion
|
||||
|
||||
// the workaround which allows displaying all books when query is empty
|
||||
public const string ALL_QUERY = "*:*";
|
||||
|
||||
#region search
|
||||
public SearchResultSet Search(string searchString)
|
||||
{
|
||||
@@ -345,7 +334,7 @@ namespace LibationSearchEngine
|
||||
return results;
|
||||
}
|
||||
|
||||
public static string FormatSearchQuery(string searchString)
|
||||
internal static string FormatSearchQuery(string searchString)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(searchString))
|
||||
return ALL_QUERY;
|
||||
@@ -491,5 +480,9 @@ namespace LibationSearchEngine
|
||||
#endregion
|
||||
|
||||
private static Directory getIndex() => FSDirectory.Open(SearchEngineDirectory);
|
||||
|
||||
// not customizable. don't move to config
|
||||
private static string SearchEngineDirectory { get; }
|
||||
= new System.IO.DirectoryInfo(Configuration.Instance.LibationFiles).CreateSubdirectory("SearchEngine").FullName;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,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,163 +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();
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
@@ -1,109 +0,0 @@
|
||||
using DataLayer;
|
||||
using System;
|
||||
|
||||
namespace LibationWinForms.BookLiberation
|
||||
{
|
||||
partial class DownloadForm
|
||||
{
|
||||
/// <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.filenameLbl = new System.Windows.Forms.Label();
|
||||
this.progressBar1 = new System.Windows.Forms.ProgressBar();
|
||||
this.progressLbl = new System.Windows.Forms.Label();
|
||||
this.lastUpdateLbl = new System.Windows.Forms.Label();
|
||||
this.SuspendLayout();
|
||||
//
|
||||
// filenameLbl
|
||||
//
|
||||
this.filenameLbl.AutoSize = true;
|
||||
this.filenameLbl.Location = new System.Drawing.Point(12, 9);
|
||||
this.filenameLbl.Name = "filenameLbl";
|
||||
this.filenameLbl.Size = new System.Drawing.Size(52, 13);
|
||||
this.filenameLbl.TabIndex = 0;
|
||||
this.filenameLbl.Text = "[filename]";
|
||||
//
|
||||
// progressBar1
|
||||
//
|
||||
this.progressBar1.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left)
|
||||
| System.Windows.Forms.AnchorStyles.Right)));
|
||||
this.progressBar1.Location = new System.Drawing.Point(15, 67);
|
||||
this.progressBar1.Name = "progressBar1";
|
||||
this.progressBar1.Size = new System.Drawing.Size(877, 23);
|
||||
this.progressBar1.TabIndex = 4;
|
||||
//
|
||||
// progressLbl
|
||||
//
|
||||
this.progressLbl.Location = new System.Drawing.Point(12, 36);
|
||||
this.progressLbl.Name = "progressLbl";
|
||||
this.progressLbl.Size = new System.Drawing.Size(173, 13);
|
||||
this.progressLbl.TabIndex = 5;
|
||||
this.progressLbl.Text = "[2,999,999,999] of [2,999,999,999]";
|
||||
this.progressLbl.TextAlign = System.Drawing.ContentAlignment.TopRight;
|
||||
//
|
||||
// lastUpdateLbl
|
||||
//
|
||||
this.lastUpdateLbl.AutoSize = true;
|
||||
this.lastUpdateLbl.Font = new System.Drawing.Font("Microsoft Sans Serif", 8.25F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
|
||||
this.lastUpdateLbl.ForeColor = System.Drawing.Color.DarkRed;
|
||||
this.lastUpdateLbl.Location = new System.Drawing.Point(361, 36);
|
||||
this.lastUpdateLbl.Name = "lastUpdateLbl";
|
||||
this.lastUpdateLbl.Size = new System.Drawing.Size(81, 13);
|
||||
this.lastUpdateLbl.TabIndex = 6;
|
||||
this.lastUpdateLbl.Text = "Last updated";
|
||||
this.lastUpdateLbl.Visible = false;
|
||||
//
|
||||
// DownloadForm
|
||||
//
|
||||
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
|
||||
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
|
||||
this.ClientSize = new System.Drawing.Size(904, 102);
|
||||
this.Controls.Add(this.lastUpdateLbl);
|
||||
this.Controls.Add(this.progressLbl);
|
||||
this.Controls.Add(this.progressBar1);
|
||||
this.Controls.Add(this.filenameLbl);
|
||||
this.MaximizeBox = false;
|
||||
this.MinimizeBox = false;
|
||||
this.Name = "DownloadForm";
|
||||
this.ShowIcon = false;
|
||||
this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent;
|
||||
this.Text = "Downloading";
|
||||
this.FormClosing += new System.Windows.Forms.FormClosingEventHandler(this.DownloadForm_FormClosing);
|
||||
this.Load += new System.EventHandler(this.DownloadForm_Load);
|
||||
this.ResumeLayout(false);
|
||||
this.PerformLayout();
|
||||
|
||||
}
|
||||
|
||||
|
||||
#endregion
|
||||
|
||||
private System.Windows.Forms.Label filenameLbl;
|
||||
private System.Windows.Forms.ProgressBar progressBar1;
|
||||
private System.Windows.Forms.Label progressLbl;
|
||||
private System.Windows.Forms.Label lastUpdateLbl;
|
||||
}
|
||||
}
|
||||
@@ -1,67 +0,0 @@
|
||||
using System;
|
||||
using System.Windows.Forms;
|
||||
using Dinah.Core.Net.Http;
|
||||
using Dinah.Core.Threading;
|
||||
using LibationWinForms.BookLiberation.BaseForms;
|
||||
|
||||
namespace LibationWinForms.BookLiberation
|
||||
{
|
||||
public partial class DownloadForm : LiberationBaseForm
|
||||
{
|
||||
public DownloadForm()
|
||||
{
|
||||
InitializeComponent();
|
||||
|
||||
progressLbl.Text = "";
|
||||
filenameLbl.Text = "";
|
||||
}
|
||||
|
||||
|
||||
#region Streamable event handler overrides
|
||||
public override void Streamable_StreamingBegin(object sender, string beginString)
|
||||
{
|
||||
base.Streamable_StreamingBegin(sender, beginString);
|
||||
filenameLbl.UIThreadAsync(() => filenameLbl.Text = beginString);
|
||||
}
|
||||
public override 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;
|
||||
|
||||
progressLbl.UIThreadAsync(() => progressLbl.Text = $"{downloadProgress.BytesReceived:#,##0} of {downloadProgress.TotalBytesToReceive.Value:#,##0}");
|
||||
|
||||
var d = double.Parse(downloadProgress.BytesReceived.ToString()) / double.Parse(downloadProgress.TotalBytesToReceive.Value.ToString()) * 100.0;
|
||||
var i = int.Parse(Math.Truncate(d).ToString());
|
||||
progressBar1.UIThreadAsync(() => progressBar1.Value = i);
|
||||
|
||||
lastDownloadProgress = DateTime.Now;
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region timer
|
||||
private Timer timer { get; } = new Timer { Interval = 1000 };
|
||||
private void DownloadForm_Load(object sender, EventArgs e)
|
||||
{
|
||||
timer.Tick += new EventHandler(timer_Tick);
|
||||
timer.Start();
|
||||
}
|
||||
private DateTime lastDownloadProgress = DateTime.Now;
|
||||
private void timer_Tick(object sender, EventArgs e)
|
||||
{
|
||||
// if no update in the last 30 seconds, display frozen label
|
||||
lastUpdateLbl.UIThreadAsync(() => lastUpdateLbl.Visible = lastDownloadProgress.AddSeconds(30) < DateTime.Now);
|
||||
if (lastUpdateLbl.Visible)
|
||||
{
|
||||
var diff = DateTime.Now - lastDownloadProgress;
|
||||
var min = (int)diff.TotalMinutes;
|
||||
var minText = min > 0 ? $"{min}min " : "";
|
||||
|
||||
lastUpdateLbl.UIThreadAsync(() => lastUpdateLbl.Text = $"Frozen? Last download activity: {minText}{diff.Seconds}sec ago");
|
||||
}
|
||||
}
|
||||
private void DownloadForm_FormClosing(object sender, FormClosingEventArgs e) => timer.Stop();
|
||||
#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,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>
|
||||
@@ -1,336 +0,0 @@
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Forms;
|
||||
using DataLayer;
|
||||
using Dinah.Core;
|
||||
using FileLiberator;
|
||||
using LibationFileManager;
|
||||
using LibationWinForms.BookLiberation.BaseForms;
|
||||
|
||||
namespace LibationWinForms.BookLiberation
|
||||
{
|
||||
// decouple serilog and form. include convenience factory method
|
||||
public class LogMe
|
||||
{
|
||||
public event EventHandler<string> LogInfo;
|
||||
public event EventHandler<string> LogErrorString;
|
||||
public event EventHandler<(Exception, string)> LogError;
|
||||
|
||||
private LogMe()
|
||||
{
|
||||
LogInfo += (_, text) => Serilog.Log.Logger.Information($"Automated backup: {text}");
|
||||
LogErrorString += (_, text) => Serilog.Log.Logger.Error(text);
|
||||
LogError += (_, tuple) => Serilog.Log.Logger.Error(tuple.Item1, tuple.Item2 ?? "Automated backup: error");
|
||||
}
|
||||
|
||||
public static LogMe RegisterForm(AutomatedBackupsForm form = null)
|
||||
{
|
||||
var logMe = new LogMe();
|
||||
|
||||
if (form is null)
|
||||
return logMe;
|
||||
|
||||
logMe.LogInfo += (_, text) => form?.WriteLine(text);
|
||||
|
||||
logMe.LogErrorString += (_, text) => form?.WriteLine(text);
|
||||
|
||||
logMe.LogError += (_, tuple) =>
|
||||
{
|
||||
form?.WriteLine(tuple.Item2 ?? "Automated backup: error");
|
||||
form?.WriteLine("ERROR: " + tuple.Item1.Message);
|
||||
};
|
||||
|
||||
return logMe;
|
||||
}
|
||||
|
||||
public void Info(string text) => LogInfo?.Invoke(this, text);
|
||||
public void Error(string text) => LogErrorString?.Invoke(this, text);
|
||||
public void Error(Exception ex, string text = null) => LogError?.Invoke(this, (ex, text));
|
||||
}
|
||||
|
||||
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()
|
||||
{
|
||||
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).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}");
|
||||
|
||||
void onDownloadFileStreamingCompleted(object sender, string savedFile)
|
||||
{
|
||||
Serilog.Log.Logger.Information($"Completed {nameof(DownloadFile)} for {url}. Saved to {savedFile}");
|
||||
|
||||
if (showDownloadCompletedDialog)
|
||||
MessageBox.Show($"File downloaded to:{Environment.NewLine}{Environment.NewLine}{savedFile}");
|
||||
}
|
||||
|
||||
var downloadFile = new DownloadFile();
|
||||
var downloadForm = new DownloadForm();
|
||||
downloadForm.RegisterFileLiberator(downloadFile);
|
||||
downloadFile.StreamingCompleted += onDownloadFileStreamingCompleted;
|
||||
|
||||
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;
|
||||
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;
|
||||
|
||||
public BackupLoop(LogMe logMe, Processable processable, AutomatedBackupsForm automatedBackupsForm)
|
||||
: base(logMe, processable, automatedBackupsForm) { }
|
||||
|
||||
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(ApplicationServices.DbContexts.GetLibrary_Flat_NoTracking()))
|
||||
{
|
||||
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;
|
||||
@@ -28,6 +24,7 @@ namespace LibationWinForms.Dialogs
|
||||
populateDropDown();
|
||||
|
||||
populateGridValues();
|
||||
this.SetLibationIcon();
|
||||
}
|
||||
|
||||
private void populateDropDown()
|
||||
@@ -127,7 +124,7 @@ namespace LibationWinForms.Dialogs
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
MessageBoxAlertAdmin.Show("Error attempting to save accounts", "Error saving accounts", ex);
|
||||
MessageBoxLib.ShowAdminAlert("Error attempting to save accounts", "Error saving accounts", ex);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -205,7 +205,6 @@
|
||||
this.MaximizeBox = false;
|
||||
this.MinimizeBox = false;
|
||||
this.Name = "BookDetailsDialog";
|
||||
this.ShowIcon = false;
|
||||
this.ShowInTaskbar = false;
|
||||
this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent;
|
||||
this.Text = "Book Details";
|
||||
|
||||
@@ -27,6 +27,7 @@ namespace LibationWinForms.Dialogs
|
||||
public BookDetailsDialog()
|
||||
{
|
||||
InitializeComponent();
|
||||
this.SetLibationIcon();
|
||||
}
|
||||
public BookDetailsDialog(LibraryBook libraryBook) : this()
|
||||
{
|
||||
@@ -45,15 +46,16 @@ namespace LibationWinForms.Dialogs
|
||||
|
||||
var t = @$"
|
||||
Title: {Book.Title}
|
||||
Author(s): {Book.AuthorNames}
|
||||
Narrator(s): {Book.NarratorNames}
|
||||
Author(s): {Book.AuthorNames()}
|
||||
Narrator(s): {Book.NarratorNames()}
|
||||
Length: {(Book.LengthInMinutes == 0 ? "" : $"{Book.LengthInMinutes / 60} hr {Book.LengthInMinutes % 60} min")}
|
||||
Category: {string.Join(" > ", Book.CategoriesNames)}
|
||||
Category: {string.Join(" > ", Book.CategoriesNames())}
|
||||
Purchase Date: {_libraryBook.DateAdded.ToString("d")}
|
||||
".Trim();
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(Book.SeriesNames))
|
||||
t += $"\r\nSeries: {Book.SeriesNames}";
|
||||
var seriesNames = Book.SeriesNames();
|
||||
if (!string.IsNullOrWhiteSpace(seriesNames))
|
||||
t += $"\r\nSeries: {seriesNames}";
|
||||
|
||||
var bookRating = Book.Rating?.ToStarString();
|
||||
if (!string.IsNullOrWhiteSpace(bookRating))
|
||||
|
||||
@@ -15,17 +15,14 @@ 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;
|
||||
|
||||
populateGridValues();
|
||||
this.SetLibationIcon();
|
||||
}
|
||||
|
||||
private void populateGridValues()
|
||||
|
||||
@@ -170,8 +170,10 @@
|
||||
this.Controls.Add(this.templateTb);
|
||||
this.Controls.Add(this.cancelBtn);
|
||||
this.Controls.Add(this.saveBtn);
|
||||
this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedToolWindow;
|
||||
this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedDialog;
|
||||
this.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3);
|
||||
this.MaximizeBox = false;
|
||||
this.MinimizeBox = false;
|
||||
this.Name = "EditTemplateDialog";
|
||||
this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent;
|
||||
this.Text = "Edit Template";
|
||||
|
||||
@@ -28,7 +28,11 @@ namespace LibationWinForms.Dialogs
|
||||
private Templates template { get; }
|
||||
private string inputTemplateText { get; }
|
||||
|
||||
public EditTemplateDialog() => InitializeComponent();
|
||||
public EditTemplateDialog()
|
||||
{
|
||||
InitializeComponent();
|
||||
this.SetLibationIcon();
|
||||
}
|
||||
public EditTemplateDialog(Templates template, string inputTemplateText) : this()
|
||||
{
|
||||
this.template = ArgumentValidator.EnsureNotNull(template, nameof(template));
|
||||
@@ -42,7 +46,7 @@ namespace LibationWinForms.Dialogs
|
||||
|
||||
if (template is null)
|
||||
{
|
||||
MessageBoxAlertAdmin.Show($"Programming error. {nameof(EditTemplateDialog)} was not created correctly", "Edit template error", new NullReferenceException($"{nameof(template)} is null"));
|
||||
MessageBoxLib.ShowAdminAlert($"Programming error. {nameof(EditTemplateDialog)} was not created correctly", "Edit template error", new NullReferenceException($"{nameof(template)} is null"));
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
120
Source/LibationWinForms/Dialogs/LiberatedStatusBatchDialog.Designer.cs
generated
Normal file
120
Source/LibationWinForms/Dialogs/LiberatedStatusBatchDialog.Designer.cs
generated
Normal file
@@ -0,0 +1,120 @@
|
||||
namespace LibationWinForms.Dialogs
|
||||
{
|
||||
partial class LiberatedStatusBatchDialog
|
||||
{
|
||||
/// <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.bookLiberatedCb = new System.Windows.Forms.ComboBox();
|
||||
this.bookLiberatedLbl = new System.Windows.Forms.Label();
|
||||
this.liberatedDescLbl = new System.Windows.Forms.Label();
|
||||
this.cancelBtn = new System.Windows.Forms.Button();
|
||||
this.saveBtn = new System.Windows.Forms.Button();
|
||||
this.SuspendLayout();
|
||||
//
|
||||
// bookLiberatedCb
|
||||
//
|
||||
this.bookLiberatedCb.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList;
|
||||
this.bookLiberatedCb.FormattingEnabled = true;
|
||||
this.bookLiberatedCb.Location = new System.Drawing.Point(52, 54);
|
||||
this.bookLiberatedCb.Name = "bookLiberatedCb";
|
||||
this.bookLiberatedCb.Size = new System.Drawing.Size(121, 23);
|
||||
this.bookLiberatedCb.TabIndex = 7;
|
||||
//
|
||||
// bookLiberatedLbl
|
||||
//
|
||||
this.bookLiberatedLbl.AutoSize = true;
|
||||
this.bookLiberatedLbl.Location = new System.Drawing.Point(12, 57);
|
||||
this.bookLiberatedLbl.Name = "bookLiberatedLbl";
|
||||
this.bookLiberatedLbl.Size = new System.Drawing.Size(34, 15);
|
||||
this.bookLiberatedLbl.TabIndex = 6;
|
||||
this.bookLiberatedLbl.Text = "Book";
|
||||
//
|
||||
// liberatedDescLbl
|
||||
//
|
||||
this.liberatedDescLbl.AutoSize = true;
|
||||
this.liberatedDescLbl.Location = new System.Drawing.Point(12, 9);
|
||||
this.liberatedDescLbl.Name = "liberatedDescLbl";
|
||||
this.liberatedDescLbl.Size = new System.Drawing.Size(312, 30);
|
||||
this.liberatedDescLbl.TabIndex = 5;
|
||||
this.liberatedDescLbl.Text = "To download again next time: change to Not Downloaded\r\nTo not download: change to" +
|
||||
" Downloaded";
|
||||
//
|
||||
// cancelBtn
|
||||
//
|
||||
this.cancelBtn.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right)));
|
||||
this.cancelBtn.Location = new System.Drawing.Point(464, 79);
|
||||
this.cancelBtn.Name = "cancelBtn";
|
||||
this.cancelBtn.Size = new System.Drawing.Size(88, 27);
|
||||
this.cancelBtn.TabIndex = 9;
|
||||
this.cancelBtn.Text = "Cancel";
|
||||
this.cancelBtn.UseVisualStyleBackColor = true;
|
||||
this.cancelBtn.Click += new System.EventHandler(this.cancelBtn_Click);
|
||||
//
|
||||
// saveBtn
|
||||
//
|
||||
this.saveBtn.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right)));
|
||||
this.saveBtn.Location = new System.Drawing.Point(346, 79);
|
||||
this.saveBtn.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3);
|
||||
this.saveBtn.Name = "saveBtn";
|
||||
this.saveBtn.Size = new System.Drawing.Size(88, 27);
|
||||
this.saveBtn.TabIndex = 8;
|
||||
this.saveBtn.Text = "Save";
|
||||
this.saveBtn.UseVisualStyleBackColor = true;
|
||||
this.saveBtn.Click += new System.EventHandler(this.saveBtn_Click);
|
||||
//
|
||||
// LiberatedStatusBatchDialog
|
||||
//
|
||||
this.AcceptButton = this.saveBtn;
|
||||
this.AutoScaleDimensions = new System.Drawing.SizeF(7F, 15F);
|
||||
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
|
||||
this.CancelButton = this.cancelBtn;
|
||||
this.ClientSize = new System.Drawing.Size(564, 118);
|
||||
this.Controls.Add(this.cancelBtn);
|
||||
this.Controls.Add(this.saveBtn);
|
||||
this.Controls.Add(this.bookLiberatedCb);
|
||||
this.Controls.Add(this.bookLiberatedLbl);
|
||||
this.Controls.Add(this.liberatedDescLbl);
|
||||
this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedDialog;
|
||||
this.MaximizeBox = false;
|
||||
this.MinimizeBox = false;
|
||||
this.Name = "LiberatedStatusBatchDialog";
|
||||
this.ShowInTaskbar = false;
|
||||
this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent;
|
||||
this.Text = "Liberated status: Whether the book has been downloaded";
|
||||
this.ResumeLayout(false);
|
||||
this.PerformLayout();
|
||||
|
||||
}
|
||||
|
||||
#endregion
|
||||
private System.Windows.Forms.ComboBox bookLiberatedCb;
|
||||
private System.Windows.Forms.Label bookLiberatedLbl;
|
||||
private System.Windows.Forms.Label liberatedDescLbl;
|
||||
private System.Windows.Forms.Button cancelBtn;
|
||||
private System.Windows.Forms.Button saveBtn;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Windows.Forms;
|
||||
using DataLayer;
|
||||
using Dinah.Core;
|
||||
using LibationFileManager;
|
||||
|
||||
namespace LibationWinForms.Dialogs
|
||||
{
|
||||
public partial class LiberatedStatusBatchDialog : Form
|
||||
{
|
||||
public LiberatedStatus BookLiberatedStatus { get; private set; }
|
||||
|
||||
public class liberatedComboBoxItem
|
||||
{
|
||||
public LiberatedStatus Status { get; set; }
|
||||
public string Text { get; set; }
|
||||
public override string ToString() => Text;
|
||||
}
|
||||
|
||||
public LiberatedStatusBatchDialog()
|
||||
{
|
||||
InitializeComponent();
|
||||
this.SetLibationIcon();
|
||||
|
||||
this.bookLiberatedCb.Items.Add(new liberatedComboBoxItem { Status = LiberatedStatus.Liberated, Text = "Downloaded" });
|
||||
this.bookLiberatedCb.Items.Add(new liberatedComboBoxItem { Status = LiberatedStatus.NotLiberated, Text = "Not Downloaded" });
|
||||
|
||||
this.bookLiberatedCb.SelectedIndex = 0;
|
||||
}
|
||||
|
||||
private void saveBtn_Click(object sender, EventArgs e)
|
||||
{
|
||||
BookLiberatedStatus = ((liberatedComboBoxItem)this.bookLiberatedCb.SelectedItem).Status;
|
||||
this.DialogResult = DialogResult.OK;
|
||||
}
|
||||
|
||||
private void cancelBtn_Click(object sender, EventArgs e)
|
||||
{
|
||||
this.DialogResult = DialogResult.Cancel;
|
||||
this.Close();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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">
|
||||
@@ -38,7 +38,7 @@ namespace LibationWinForms.Dialogs
|
||||
this.authorsDataGridViewTextBoxColumn = new System.Windows.Forms.DataGridViewTextBoxColumn();
|
||||
this.miscDataGridViewTextBoxColumn = new System.Windows.Forms.DataGridViewTextBoxColumn();
|
||||
this.purchaseDateGridViewTextBoxColumn = new System.Windows.Forms.DataGridViewTextBoxColumn();
|
||||
this.gridEntryBindingSource = new LibationWinForms.SyncBindingSource(this.components);
|
||||
this.gridEntryBindingSource = new LibationWinForms.GridView.SyncBindingSource(this.components);
|
||||
this.btnRemoveBooks = new System.Windows.Forms.Button();
|
||||
this.label1 = new System.Windows.Forms.Label();
|
||||
((System.ComponentModel.ISupportInitialize)(this._dataGridView)).BeginInit();
|
||||
@@ -164,7 +164,7 @@ namespace LibationWinForms.Dialogs
|
||||
this.Controls.Add(this.btnRemoveBooks);
|
||||
this.Controls.Add(this._dataGridView);
|
||||
this.Name = "RemoveBooksDialog";
|
||||
this.Text = "RemoveBooksDialog";
|
||||
this.Text = "Remove Books from Libation's Database";
|
||||
this.Shown += new System.EventHandler(this.RemoveBooksDialog_Shown);
|
||||
((System.ComponentModel.ISupportInitialize)(this._dataGridView)).EndInit();
|
||||
((System.ComponentModel.ISupportInitialize)(this.gridEntryBindingSource)).EndInit();
|
||||
@@ -176,7 +176,7 @@ namespace LibationWinForms.Dialogs
|
||||
#endregion
|
||||
|
||||
private System.Windows.Forms.DataGridView _dataGridView;
|
||||
private LibationWinForms.SyncBindingSource gridEntryBindingSource;
|
||||
private LibationWinForms.GridView.SyncBindingSource gridEntryBindingSource;
|
||||
private System.Windows.Forms.Button btnRemoveBooks;
|
||||
private System.Windows.Forms.Label label1;
|
||||
private System.Windows.Forms.DataGridViewCheckBoxColumn removeDataGridViewCheckBoxColumn;
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Data;
|
||||
@@ -48,6 +47,7 @@ namespace LibationWinForms.Dialogs
|
||||
gridEntryBindingSource.DataSource = _removableGridEntries;
|
||||
|
||||
_dataGridView.Enabled = false;
|
||||
this.SetLibationIcon();
|
||||
}
|
||||
|
||||
private void _dataGridView_BindingContextChanged(object sender, EventArgs e)
|
||||
@@ -76,7 +76,7 @@ namespace LibationWinForms.Dialogs
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
MessageBoxAlertAdmin.Show(
|
||||
MessageBoxLib.ShowAdminAlert(
|
||||
"Error scanning library. You may still manually select books to remove from Libation's library.",
|
||||
"Error scanning library",
|
||||
ex);
|
||||
@@ -94,34 +94,22 @@ namespace LibationWinForms.Dialogs
|
||||
if (selectedBooks.Count == 0)
|
||||
return;
|
||||
|
||||
var titles = selectedBooks.Select(rge => "- " + rge.Title).ToList();
|
||||
var titlesAgg = titles.Take(5).Aggregate((a, b) => $"{a}\r\n{b}");
|
||||
if (titles.Count == 6)
|
||||
titlesAgg += $"\r\n\r\nand 1 other";
|
||||
else if (titles.Count > 6)
|
||||
titlesAgg += $"\r\n\r\nand {titles.Count - 5} others";
|
||||
var libraryBooks = selectedBooks.Select(rge => rge.LibraryBook).ToList();
|
||||
var result = MessageBoxLib.ShowConfirmationDialog(
|
||||
libraryBooks,
|
||||
$"Are you sure you want to remove {0} from Libation's library?",
|
||||
"Remove books from Libation?");
|
||||
|
||||
string thisThese = selectedBooks.Count > 1 ? "these" : "this";
|
||||
string bookBooks = selectedBooks.Count > 1 ? "books" : "book";
|
||||
if (result != DialogResult.Yes)
|
||||
return;
|
||||
|
||||
var result = MessageBox.Show(
|
||||
this,
|
||||
$"Are you sure you want to remove {thisThese} {selectedBooks.Count} {bookBooks} from Libation's library?\r\n\r\n{titlesAgg}",
|
||||
"Remove books from Libation?",
|
||||
MessageBoxButtons.YesNo,
|
||||
MessageBoxIcon.Question,
|
||||
MessageBoxDefaultButton.Button1);
|
||||
var idsToRemove = libraryBooks.Select(lb => lb.Book.AudibleProductId).ToList();
|
||||
var removeLibraryBooks = await LibraryCommands.RemoveBooksAsync(idsToRemove);
|
||||
|
||||
if (result == DialogResult.Yes)
|
||||
{
|
||||
var idsToRemove = selectedBooks.Select(rge => rge.AudibleProductId).ToList();
|
||||
var removeLibraryBooks = await LibraryCommands.RemoveBooksAsync(idsToRemove);
|
||||
foreach (var rEntry in selectedBooks)
|
||||
_removableGridEntries.Remove(rEntry);
|
||||
|
||||
foreach (var rEntry in selectedBooks)
|
||||
_removableGridEntries.Remove(rEntry);
|
||||
|
||||
UpdateSelection();
|
||||
}
|
||||
UpdateSelection();
|
||||
}
|
||||
|
||||
private void UpdateSelection()
|
||||
@@ -132,10 +120,8 @@ namespace LibationWinForms.Dialogs
|
||||
}
|
||||
}
|
||||
|
||||
internal class RemovableGridEntry : GridEntry
|
||||
internal class RemovableGridEntry : GridView.LibraryBookEntry
|
||||
{
|
||||
private static readonly IComparer BoolComparer = new ObjectComparer<bool>();
|
||||
|
||||
private bool _remove = false;
|
||||
public RemovableGridEntry(LibraryBook libraryBook) : base(libraryBook) { }
|
||||
|
||||
@@ -147,11 +133,8 @@ namespace LibationWinForms.Dialogs
|
||||
}
|
||||
set
|
||||
{
|
||||
if (_remove != value)
|
||||
{
|
||||
_remove = value;
|
||||
NotifyPropertyChanged();
|
||||
}
|
||||
_remove = value;
|
||||
NotifyPropertyChanged();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -161,12 +144,5 @@ namespace LibationWinForms.Dialogs
|
||||
return Remove;
|
||||
return base.GetMemberValue(memberName);
|
||||
}
|
||||
|
||||
public override IComparer GetMemberComparer(Type memberType)
|
||||
{
|
||||
if (memberType == typeof(bool))
|
||||
return BoolComparer;
|
||||
return base.GetMemberComparer(memberType);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,13 +10,10 @@ 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();
|
||||
}
|
||||
|
||||
private class listItem
|
||||
@@ -43,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();
|
||||
|
||||
@@ -113,7 +113,6 @@
|
||||
this.MaximizeBox = false;
|
||||
this.MinimizeBox = false;
|
||||
this.Name = "SearchSyntaxDialog";
|
||||
this.ShowIcon = false;
|
||||
this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent;
|
||||
this.Text = "Filter options";
|
||||
this.ResumeLayout(false);
|
||||
|
||||
@@ -14,6 +14,7 @@ namespace LibationWinForms.Dialogs
|
||||
label3.Text += "\r\n\r\n" + string.Join("\r\n", LibationSearchEngine.SearchEngine.GetSearchNumberFields());
|
||||
label4.Text += "\r\n\r\n" + string.Join("\r\n", LibationSearchEngine.SearchEngine.GetSearchBoolFields());
|
||||
label5.Text += "\r\n\r\n" + string.Join("\r\n", LibationSearchEngine.SearchEngine.GetSearchIdFields());
|
||||
this.SetLibationIcon();
|
||||
}
|
||||
|
||||
private void CloseBtn_Click(object sender, EventArgs e) => this.Close();
|
||||
|
||||
@@ -17,12 +17,10 @@ namespace LibationWinForms.Dialogs
|
||||
private void LameMatchSourceBRCbox_CheckedChanged(object sender, EventArgs e)
|
||||
{
|
||||
lameBitrateTb.Enabled = !LameMatchSourceBRCbox.Checked;
|
||||
lameConstantBitrateCbox.Enabled = !LameMatchSourceBRCbox.Checked;
|
||||
}
|
||||
|
||||
private void convertFormatRb_CheckedChanged(object sender, EventArgs e)
|
||||
{
|
||||
lameOptionsGb.Enabled = convertLossyRb.Checked;
|
||||
lameTargetRb_CheckedChanged(sender, e);
|
||||
LameMatchSourceBRCbox_CheckedChanged(sender, e);
|
||||
}
|
||||
|
||||
@@ -53,6 +53,8 @@
|
||||
this.tab1ImportantSettings = new System.Windows.Forms.TabPage();
|
||||
this.booksGb = new System.Windows.Forms.GroupBox();
|
||||
this.tab2ImportLibrary = new System.Windows.Forms.TabPage();
|
||||
this.autoDownloadEpisodesCb = new System.Windows.Forms.CheckBox();
|
||||
this.autoScanCb = new System.Windows.Forms.CheckBox();
|
||||
this.showImportedStatsCb = new System.Windows.Forms.CheckBox();
|
||||
this.tab3DownloadDecrypt = new System.Windows.Forms.TabPage();
|
||||
this.inProgressFilesGb = new System.Windows.Forms.GroupBox();
|
||||
@@ -99,8 +101,8 @@
|
||||
this.lameTargetBitrateRb = new System.Windows.Forms.RadioButton();
|
||||
this.stripUnabridgedCbox = new System.Windows.Forms.CheckBox();
|
||||
this.retainAaxFileCbox = new System.Windows.Forms.CheckBox();
|
||||
this.downloadCoverArtCbox = new System.Windows.Forms.CheckBox();
|
||||
this.createCueSheetCbox = new System.Windows.Forms.CheckBox();
|
||||
this.autoScanCb = new System.Windows.Forms.CheckBox();
|
||||
this.badBookGb.SuspendLayout();
|
||||
this.tabControl.SuspendLayout();
|
||||
this.tab1ImportantSettings.SuspendLayout();
|
||||
@@ -243,7 +245,7 @@
|
||||
// stripAudibleBrandingCbox
|
||||
//
|
||||
this.stripAudibleBrandingCbox.AutoSize = true;
|
||||
this.stripAudibleBrandingCbox.Location = new System.Drawing.Point(19, 143);
|
||||
this.stripAudibleBrandingCbox.Location = new System.Drawing.Point(19, 168);
|
||||
this.stripAudibleBrandingCbox.Name = "stripAudibleBrandingCbox";
|
||||
this.stripAudibleBrandingCbox.Size = new System.Drawing.Size(143, 34);
|
||||
this.stripAudibleBrandingCbox.TabIndex = 13;
|
||||
@@ -253,7 +255,7 @@
|
||||
// splitFilesByChapterCbox
|
||||
//
|
||||
this.splitFilesByChapterCbox.AutoSize = true;
|
||||
this.splitFilesByChapterCbox.Location = new System.Drawing.Point(19, 93);
|
||||
this.splitFilesByChapterCbox.Location = new System.Drawing.Point(19, 118);
|
||||
this.splitFilesByChapterCbox.Name = "splitFilesByChapterCbox";
|
||||
this.splitFilesByChapterCbox.Size = new System.Drawing.Size(162, 19);
|
||||
this.splitFilesByChapterCbox.TabIndex = 13;
|
||||
@@ -276,7 +278,7 @@
|
||||
// convertLossyRb
|
||||
//
|
||||
this.convertLossyRb.AutoSize = true;
|
||||
this.convertLossyRb.Location = new System.Drawing.Point(19, 207);
|
||||
this.convertLossyRb.Location = new System.Drawing.Point(19, 232);
|
||||
this.convertLossyRb.Name = "convertLossyRb";
|
||||
this.convertLossyRb.Size = new System.Drawing.Size(329, 19);
|
||||
this.convertLossyRb.TabIndex = 12;
|
||||
@@ -288,7 +290,7 @@
|
||||
//
|
||||
this.convertLosslessRb.AutoSize = true;
|
||||
this.convertLosslessRb.Checked = true;
|
||||
this.convertLosslessRb.Location = new System.Drawing.Point(19, 182);
|
||||
this.convertLosslessRb.Location = new System.Drawing.Point(19, 207);
|
||||
this.convertLosslessRb.Name = "convertLosslessRb";
|
||||
this.convertLosslessRb.Size = new System.Drawing.Size(335, 19);
|
||||
this.convertLosslessRb.TabIndex = 11;
|
||||
@@ -389,6 +391,7 @@
|
||||
//
|
||||
// tab2ImportLibrary
|
||||
//
|
||||
this.tab2ImportLibrary.Controls.Add(this.autoDownloadEpisodesCb);
|
||||
this.tab2ImportLibrary.Controls.Add(this.autoScanCb);
|
||||
this.tab2ImportLibrary.Controls.Add(this.showImportedStatsCb);
|
||||
this.tab2ImportLibrary.Controls.Add(this.importEpisodesCb);
|
||||
@@ -401,6 +404,26 @@
|
||||
this.tab2ImportLibrary.Text = "Import library";
|
||||
this.tab2ImportLibrary.UseVisualStyleBackColor = true;
|
||||
//
|
||||
// autoDownloadEpisodesCb
|
||||
//
|
||||
this.autoDownloadEpisodesCb.AutoSize = true;
|
||||
this.autoDownloadEpisodesCb.Location = new System.Drawing.Point(6, 106);
|
||||
this.autoDownloadEpisodesCb.Name = "autoDownloadEpisodesCb";
|
||||
this.autoDownloadEpisodesCb.Size = new System.Drawing.Size(190, 19);
|
||||
this.autoDownloadEpisodesCb.TabIndex = 5;
|
||||
this.autoDownloadEpisodesCb.Text = "[auto download episodes desc]";
|
||||
this.autoDownloadEpisodesCb.UseVisualStyleBackColor = true;
|
||||
//
|
||||
// autoScanCb
|
||||
//
|
||||
this.autoScanCb.AutoSize = true;
|
||||
this.autoScanCb.Location = new System.Drawing.Point(6, 6);
|
||||
this.autoScanCb.Name = "autoScanCb";
|
||||
this.autoScanCb.Size = new System.Drawing.Size(112, 19);
|
||||
this.autoScanCb.TabIndex = 1;
|
||||
this.autoScanCb.Text = "[auto scan desc]";
|
||||
this.autoScanCb.UseVisualStyleBackColor = true;
|
||||
//
|
||||
// showImportedStatsCb
|
||||
//
|
||||
this.showImportedStatsCb.AutoSize = true;
|
||||
@@ -556,6 +579,7 @@
|
||||
this.tab4AudioFileOptions.Controls.Add(this.stripUnabridgedCbox);
|
||||
this.tab4AudioFileOptions.Controls.Add(this.splitFilesByChapterCbox);
|
||||
this.tab4AudioFileOptions.Controls.Add(this.retainAaxFileCbox);
|
||||
this.tab4AudioFileOptions.Controls.Add(this.downloadCoverArtCbox);
|
||||
this.tab4AudioFileOptions.Controls.Add(this.createCueSheetCbox);
|
||||
this.tab4AudioFileOptions.Controls.Add(this.allowLibationFixupCbox);
|
||||
this.tab4AudioFileOptions.Location = new System.Drawing.Point(4, 24);
|
||||
@@ -893,7 +917,7 @@
|
||||
// stripUnabridgedCbox
|
||||
//
|
||||
this.stripUnabridgedCbox.AutoSize = true;
|
||||
this.stripUnabridgedCbox.Location = new System.Drawing.Point(19, 118);
|
||||
this.stripUnabridgedCbox.Location = new System.Drawing.Point(19, 143);
|
||||
this.stripUnabridgedCbox.Name = "stripUnabridgedCbox";
|
||||
this.stripUnabridgedCbox.Size = new System.Drawing.Size(147, 19);
|
||||
this.stripUnabridgedCbox.TabIndex = 13;
|
||||
@@ -903,7 +927,7 @@
|
||||
// retainAaxFileCbox
|
||||
//
|
||||
this.retainAaxFileCbox.AutoSize = true;
|
||||
this.retainAaxFileCbox.Location = new System.Drawing.Point(19, 68);
|
||||
this.retainAaxFileCbox.Location = new System.Drawing.Point(19, 93);
|
||||
this.retainAaxFileCbox.Name = "retainAaxFileCbox";
|
||||
this.retainAaxFileCbox.Size = new System.Drawing.Size(132, 19);
|
||||
this.retainAaxFileCbox.TabIndex = 10;
|
||||
@@ -911,6 +935,19 @@
|
||||
this.retainAaxFileCbox.UseVisualStyleBackColor = true;
|
||||
this.retainAaxFileCbox.CheckedChanged += new System.EventHandler(this.allowLibationFixupCbox_CheckedChanged);
|
||||
//
|
||||
// downloadCoverArtCbox
|
||||
//
|
||||
this.downloadCoverArtCbox.AutoSize = true;
|
||||
this.downloadCoverArtCbox.Checked = true;
|
||||
this.downloadCoverArtCbox.CheckState = System.Windows.Forms.CheckState.Checked;
|
||||
this.downloadCoverArtCbox.Location = new System.Drawing.Point(19, 68);
|
||||
this.downloadCoverArtCbox.Name = "downloadCoverArtCbox";
|
||||
this.downloadCoverArtCbox.Size = new System.Drawing.Size(162, 19);
|
||||
this.downloadCoverArtCbox.TabIndex = 10;
|
||||
this.downloadCoverArtCbox.Text = "[DownloadCoverArt desc]";
|
||||
this.downloadCoverArtCbox.UseVisualStyleBackColor = true;
|
||||
this.downloadCoverArtCbox.CheckedChanged += new System.EventHandler(this.allowLibationFixupCbox_CheckedChanged);
|
||||
//
|
||||
// createCueSheetCbox
|
||||
//
|
||||
this.createCueSheetCbox.AutoSize = true;
|
||||
@@ -924,16 +961,6 @@
|
||||
this.createCueSheetCbox.UseVisualStyleBackColor = true;
|
||||
this.createCueSheetCbox.CheckedChanged += new System.EventHandler(this.allowLibationFixupCbox_CheckedChanged);
|
||||
//
|
||||
// autoScanCb
|
||||
//
|
||||
this.autoScanCb.AutoSize = true;
|
||||
this.autoScanCb.Location = new System.Drawing.Point(6, 6);
|
||||
this.autoScanCb.Name = "autoScanCb";
|
||||
this.autoScanCb.Size = new System.Drawing.Size(112, 19);
|
||||
this.autoScanCb.TabIndex = 1;
|
||||
this.autoScanCb.Text = "[auto scan desc]";
|
||||
this.autoScanCb.UseVisualStyleBackColor = true;
|
||||
//
|
||||
// SettingsDialog
|
||||
//
|
||||
this.AcceptButton = this.saveBtn;
|
||||
@@ -944,8 +971,10 @@
|
||||
this.Controls.Add(this.tabControl);
|
||||
this.Controls.Add(this.cancelBtn);
|
||||
this.Controls.Add(this.saveBtn);
|
||||
this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedToolWindow;
|
||||
this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedDialog;
|
||||
this.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3);
|
||||
this.MaximizeBox = false;
|
||||
this.MinimizeBox = false;
|
||||
this.Name = "SettingsDialog";
|
||||
this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent;
|
||||
this.Text = "Edit Settings";
|
||||
@@ -1054,5 +1083,7 @@
|
||||
private System.Windows.Forms.Label label16;
|
||||
private System.Windows.Forms.CheckBox createCueSheetCbox;
|
||||
private System.Windows.Forms.CheckBox autoScanCb;
|
||||
private System.Windows.Forms.CheckBox downloadCoverArtCbox;
|
||||
private System.Windows.Forms.CheckBox autoDownloadEpisodesCb;
|
||||
}
|
||||
}
|
||||
@@ -13,7 +13,11 @@ namespace LibationWinForms.Dialogs
|
||||
private Configuration config { get; } = Configuration.Instance;
|
||||
private Func<string, string> desc { get; } = Configuration.GetDescription;
|
||||
|
||||
public SettingsDialog() => InitializeComponent();
|
||||
public SettingsDialog()
|
||||
{
|
||||
InitializeComponent();
|
||||
this.SetLibationIcon();
|
||||
}
|
||||
|
||||
private void SettingsDialog_Load(object sender, EventArgs e)
|
||||
{
|
||||
@@ -31,6 +35,7 @@ namespace LibationWinForms.Dialogs
|
||||
this.showImportedStatsCb.Text = desc(nameof(config.ShowImportedStats));
|
||||
this.importEpisodesCb.Text = desc(nameof(config.ImportEpisodes));
|
||||
this.downloadEpisodesCb.Text = desc(nameof(config.DownloadEpisodes));
|
||||
this.autoDownloadEpisodesCb.Text = desc(nameof(config.AutoDownloadEpisodes));
|
||||
|
||||
this.booksLocationDescLbl.Text = desc(nameof(config.Books));
|
||||
this.inProgressDescLbl.Text = desc(nameof(config.InProgress));
|
||||
@@ -40,6 +45,7 @@ namespace LibationWinForms.Dialogs
|
||||
this.retainAaxFileCbox.Text = desc(nameof(config.RetainAaxFile));
|
||||
this.stripUnabridgedCbox.Text = desc(nameof(config.StripUnabridged));
|
||||
this.createCueSheetCbox.Text = desc(nameof(config.CreateCueSheet));
|
||||
this.downloadCoverArtCbox.Text = desc(nameof(config.DownloadCoverArt));
|
||||
|
||||
booksSelectControl.SetSearchTitle("books location");
|
||||
booksSelectControl.SetDirectoryItems(
|
||||
@@ -69,11 +75,13 @@ namespace LibationWinForms.Dialogs
|
||||
lameConstantBitrateCbox.Checked = config.LameConstantBitrate;
|
||||
LameMatchSourceBRCbox.Checked = config.LameMatchSourceBR;
|
||||
lameVBRQualityTb.Value = config.LameVBRQuality;
|
||||
downloadCoverArtCbox.Checked = config.DownloadCoverArt;
|
||||
|
||||
autoScanCb.Checked = config.AutoScan;
|
||||
showImportedStatsCb.Checked = config.ShowImportedStats;
|
||||
importEpisodesCb.Checked = config.ImportEpisodes;
|
||||
downloadEpisodesCb.Checked = config.DownloadEpisodes;
|
||||
autoDownloadEpisodesCb.Checked = config.AutoDownloadEpisodes;
|
||||
|
||||
lameTargetRb_CheckedChanged(this, e);
|
||||
LameMatchSourceBRCbox_CheckedChanged(this, e);
|
||||
@@ -175,7 +183,7 @@ namespace LibationWinForms.Dialogs
|
||||
|
||||
// only warn if changed during this time. don't want to warn every time user happens to change settings while level is verbose
|
||||
if (logLevelOld != logLevelNew)
|
||||
MessageBoxVerboseLoggingWarning.ShowIfTrue();
|
||||
MessageBoxLib.VerboseLoggingWarning_ShowIfTrue();
|
||||
}
|
||||
|
||||
config.AllowLibationFixup = allowLibationFixupCbox.Checked;
|
||||
@@ -192,11 +200,13 @@ namespace LibationWinForms.Dialogs
|
||||
config.LameConstantBitrate = lameConstantBitrateCbox.Checked;
|
||||
config.LameMatchSourceBR = LameMatchSourceBRCbox.Checked;
|
||||
config.LameVBRQuality = lameVBRQualityTb.Value;
|
||||
config.DownloadCoverArt = downloadCoverArtCbox.Checked;
|
||||
|
||||
config.AutoScan = autoScanCb.Checked;
|
||||
config.ShowImportedStats = showImportedStatsCb.Checked;
|
||||
config.ImportEpisodes = importEpisodesCb.Checked;
|
||||
config.DownloadEpisodes = downloadEpisodesCb.Checked;
|
||||
config.AutoDownloadEpisodes = autoDownloadEpisodesCb.Checked;
|
||||
|
||||
config.InProgress = inProgressSelectControl.SelectedDirectory;
|
||||
|
||||
|
||||
112
Source/LibationWinForms/Dialogs/TagsBatchDialog.Designer.cs
generated
Normal file
112
Source/LibationWinForms/Dialogs/TagsBatchDialog.Designer.cs
generated
Normal file
@@ -0,0 +1,112 @@
|
||||
namespace LibationWinForms.Dialogs
|
||||
{
|
||||
partial class TagsBatchDialog
|
||||
{
|
||||
/// <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.tagsDescLbl = new System.Windows.Forms.Label();
|
||||
this.newTagsTb = new System.Windows.Forms.TextBox();
|
||||
this.cancelBtn = new System.Windows.Forms.Button();
|
||||
this.saveBtn = new System.Windows.Forms.Button();
|
||||
this.SuspendLayout();
|
||||
//
|
||||
// tagsDescLbl
|
||||
//
|
||||
this.tagsDescLbl.AutoSize = true;
|
||||
this.tagsDescLbl.Location = new System.Drawing.Point(13, 9);
|
||||
this.tagsDescLbl.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0);
|
||||
this.tagsDescLbl.Name = "tagsDescLbl";
|
||||
this.tagsDescLbl.Size = new System.Drawing.Size(458, 15);
|
||||
this.tagsDescLbl.TabIndex = 2;
|
||||
this.tagsDescLbl.Text = "Tags are separated by a space. Each tag can contain letters, numbers, and undersc" +
|
||||
"ores";
|
||||
//
|
||||
// newTagsTb
|
||||
//
|
||||
this.newTagsTb.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left)
|
||||
| System.Windows.Forms.AnchorStyles.Right)));
|
||||
this.newTagsTb.Location = new System.Drawing.Point(13, 30);
|
||||
this.newTagsTb.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3);
|
||||
this.newTagsTb.Name = "newTagsTb";
|
||||
this.newTagsTb.ScrollBars = System.Windows.Forms.ScrollBars.Both;
|
||||
this.newTagsTb.Size = new System.Drawing.Size(591, 23);
|
||||
this.newTagsTb.TabIndex = 3;
|
||||
//
|
||||
// cancelBtn
|
||||
//
|
||||
this.cancelBtn.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right)));
|
||||
this.cancelBtn.Location = new System.Drawing.Point(517, 71);
|
||||
this.cancelBtn.Name = "cancelBtn";
|
||||
this.cancelBtn.Size = new System.Drawing.Size(88, 27);
|
||||
this.cancelBtn.TabIndex = 6;
|
||||
this.cancelBtn.Text = "Cancel";
|
||||
this.cancelBtn.UseVisualStyleBackColor = true;
|
||||
this.cancelBtn.Click += new System.EventHandler(this.cancelBtn_Click);
|
||||
//
|
||||
// saveBtn
|
||||
//
|
||||
this.saveBtn.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right)));
|
||||
this.saveBtn.Location = new System.Drawing.Point(399, 71);
|
||||
this.saveBtn.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3);
|
||||
this.saveBtn.Name = "saveBtn";
|
||||
this.saveBtn.Size = new System.Drawing.Size(88, 27);
|
||||
this.saveBtn.TabIndex = 5;
|
||||
this.saveBtn.Text = "Save";
|
||||
this.saveBtn.UseVisualStyleBackColor = true;
|
||||
this.saveBtn.Click += new System.EventHandler(this.saveBtn_Click);
|
||||
//
|
||||
// TagsBatchDialog
|
||||
//
|
||||
this.AcceptButton = this.saveBtn;
|
||||
this.AutoScaleDimensions = new System.Drawing.SizeF(7F, 15F);
|
||||
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
|
||||
this.CancelButton = this.cancelBtn;
|
||||
this.ClientSize = new System.Drawing.Size(617, 110);
|
||||
this.Controls.Add(this.cancelBtn);
|
||||
this.Controls.Add(this.saveBtn);
|
||||
this.Controls.Add(this.tagsDescLbl);
|
||||
this.Controls.Add(this.newTagsTb);
|
||||
this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedDialog;
|
||||
this.MaximizeBox = false;
|
||||
this.MinimizeBox = false;
|
||||
this.Name = "TagsBatchDialog";
|
||||
this.ShowInTaskbar = false;
|
||||
this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent;
|
||||
this.Text = "Replace Tags";
|
||||
this.ResumeLayout(false);
|
||||
this.PerformLayout();
|
||||
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
private System.Windows.Forms.Label tagsDescLbl;
|
||||
private System.Windows.Forms.TextBox newTagsTb;
|
||||
private System.Windows.Forms.Button cancelBtn;
|
||||
private System.Windows.Forms.Button saveBtn;
|
||||
}
|
||||
}
|
||||
35
Source/LibationWinForms/Dialogs/TagsBatchDialog.cs
Normal file
35
Source/LibationWinForms/Dialogs/TagsBatchDialog.cs
Normal file
@@ -0,0 +1,35 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Data;
|
||||
using System.Drawing;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Forms;
|
||||
|
||||
namespace LibationWinForms.Dialogs
|
||||
{
|
||||
public partial class TagsBatchDialog : Form
|
||||
{
|
||||
public string NewTags { get; private set; }
|
||||
|
||||
public TagsBatchDialog()
|
||||
{
|
||||
InitializeComponent();
|
||||
this.SetLibationIcon();
|
||||
}
|
||||
|
||||
private void saveBtn_Click(object sender, EventArgs e)
|
||||
{
|
||||
NewTags = this.newTagsTb.Text;
|
||||
this.DialogResult = DialogResult.OK;
|
||||
}
|
||||
|
||||
private void cancelBtn_Click(object sender, EventArgs e)
|
||||
{
|
||||
this.DialogResult = DialogResult.Cancel;
|
||||
this.Close();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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">
|
||||
119
Source/LibationWinForms/Form1.BackupCounts.cs
Normal file
119
Source/LibationWinForms/Form1.BackupCounts.cs
Normal file
@@ -0,0 +1,119 @@
|
||||
using ApplicationServices;
|
||||
using Dinah.Core;
|
||||
using Dinah.Core.Threading;
|
||||
|
||||
namespace LibationWinForms
|
||||
{
|
||||
public partial class Form1
|
||||
{
|
||||
private System.ComponentModel.BackgroundWorker updateCountsBw = new();
|
||||
|
||||
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;
|
||||
|
||||
updateCountsBw.DoWork += UpdateCountsBw_DoWork;
|
||||
updateCountsBw.RunWorkerCompleted += exportMenuEnable;
|
||||
updateCountsBw.RunWorkerCompleted += updateBottomBookNumbers;
|
||||
updateCountsBw.RunWorkerCompleted += update_BeginBookBackups_menuItem;
|
||||
updateCountsBw.RunWorkerCompleted += updateBottomPdfNumbers;
|
||||
updateCountsBw.RunWorkerCompleted += udpate_BeginPdfOnlyBackups_menuItem;
|
||||
}
|
||||
|
||||
private bool runBackupCountsAgain;
|
||||
|
||||
private void setBackupCounts(object _, object __)
|
||||
{
|
||||
runBackupCountsAgain = true;
|
||||
|
||||
if (!updateCountsBw.IsBusy)
|
||||
updateCountsBw.RunWorkerAsync();
|
||||
}
|
||||
|
||||
private void UpdateCountsBw_DoWork(object sender, System.ComponentModel.DoWorkEventArgs e)
|
||||
{
|
||||
while (runBackupCountsAgain)
|
||||
{
|
||||
runBackupCountsAgain = false;
|
||||
e.Result = LibraryCommands.GetCounts();
|
||||
}
|
||||
}
|
||||
|
||||
private void exportMenuEnable(object _, System.ComponentModel.RunWorkerCompletedEventArgs e)
|
||||
{
|
||||
var libraryStats = e.Result as LibraryCommands.LibraryStats;
|
||||
exportLibraryToolStripMenuItem.Enabled = libraryStats.HasBookResults;
|
||||
}
|
||||
|
||||
// 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 updateBottomBookNumbers(object _, System.ComponentModel.RunWorkerCompletedEventArgs e)
|
||||
{
|
||||
var libraryStats = e.Result as LibraryCommands.LibraryStats;
|
||||
|
||||
var formatString
|
||||
= !libraryStats.HasBookResults ? "No books. Begin by importing your library"
|
||||
: libraryStats.booksError > 0 ? backupsCountsLbl_Format + " Errors: {3}"
|
||||
: libraryStats.HasPendingBooks ? 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
|
||||
private void update_BeginBookBackups_menuItem(object _, System.ComponentModel.RunWorkerCompletedEventArgs e)
|
||||
{
|
||||
var libraryStats = e.Result as LibraryCommands.LibraryStats;
|
||||
|
||||
var menuItemText
|
||||
= libraryStats.HasPendingBooks
|
||||
? $"{libraryStats.PendingBooks} remaining"
|
||||
: "All books have been liberated";
|
||||
menuStrip1.UIThreadAsync(() =>
|
||||
{
|
||||
beginBookBackupsToolStripMenuItem.Format(menuItemText);
|
||||
beginBookBackupsToolStripMenuItem.Enabled = libraryStats.HasPendingBooks;
|
||||
});
|
||||
}
|
||||
|
||||
private void updateBottomPdfNumbers(object _, System.ComponentModel.RunWorkerCompletedEventArgs e)
|
||||
{
|
||||
var libraryStats = e.Result as LibraryCommands.LibraryStats;
|
||||
|
||||
// don't need to assign the output of Format(). It just makes this logic cleaner
|
||||
var statusStripText
|
||||
= !libraryStats.HasPdfResults ? ""
|
||||
: 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
|
||||
private void udpate_BeginPdfOnlyBackups_menuItem(object _, System.ComponentModel.RunWorkerCompletedEventArgs e)
|
||||
{
|
||||
var libraryStats = e.Result as LibraryCommands.LibraryStats;
|
||||
|
||||
var menuItemText
|
||||
= libraryStats.pdfsNotDownloaded > 0
|
||||
? $"{libraryStats.pdfsNotDownloaded} remaining"
|
||||
: "All PDFs have been downloaded";
|
||||
menuStrip1.UIThreadAsync(() =>
|
||||
{
|
||||
beginPdfBackupsToolStripMenuItem.Format(menuItemText);
|
||||
beginPdfBackupsToolStripMenuItem.Enabled = libraryStats.pdfsNotDownloaded > 0;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
846
Source/LibationWinForms/Form1.Designer.cs
generated
846
Source/LibationWinForms/Form1.Designer.cs
generated
@@ -28,391 +28,526 @@
|
||||
/// </summary>
|
||||
private void InitializeComponent()
|
||||
{
|
||||
System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(Form1));
|
||||
this.gridPanel = new System.Windows.Forms.Panel();
|
||||
this.filterHelpBtn = new System.Windows.Forms.Button();
|
||||
this.filterBtn = new System.Windows.Forms.Button();
|
||||
this.filterSearchTb = new System.Windows.Forms.TextBox();
|
||||
this.menuStrip1 = new System.Windows.Forms.MenuStrip();
|
||||
this.importToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
|
||||
this.noAccountsYetAddAccountToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
|
||||
this.autoScanLibraryToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
|
||||
this.scanLibraryToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
|
||||
this.scanLibraryOfAllAccountsToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
|
||||
this.scanLibraryOfSomeAccountsToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
|
||||
this.removeLibraryBooksToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
|
||||
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.convertAllM4bToMp3ToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
|
||||
this.exportToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
|
||||
this.exportLibraryToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
|
||||
this.quickFiltersToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
|
||||
this.firstFilterIsDefaultToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
|
||||
this.editQuickFiltersToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
|
||||
this.toolStripSeparator1 = new System.Windows.Forms.ToolStripSeparator();
|
||||
this.settingsToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
|
||||
this.accountsToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
|
||||
this.basicSettingsToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
|
||||
this.toolStripSeparator2 = new System.Windows.Forms.ToolStripSeparator();
|
||||
this.aboutToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
|
||||
this.scanningToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
|
||||
this.statusStrip1 = new System.Windows.Forms.StatusStrip();
|
||||
this.visibleCountLbl = new System.Windows.Forms.ToolStripStatusLabel();
|
||||
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.menuStrip1.SuspendLayout();
|
||||
this.statusStrip1.SuspendLayout();
|
||||
this.SuspendLayout();
|
||||
//
|
||||
// gridPanel
|
||||
//
|
||||
this.gridPanel.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom)
|
||||
| System.Windows.Forms.AnchorStyles.Left)
|
||||
System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(Form1));
|
||||
this.filterHelpBtn = new System.Windows.Forms.Button();
|
||||
this.filterBtn = new System.Windows.Forms.Button();
|
||||
this.filterSearchTb = new System.Windows.Forms.TextBox();
|
||||
this.menuStrip1 = new System.Windows.Forms.MenuStrip();
|
||||
this.importToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
|
||||
this.autoScanLibraryToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
|
||||
this.noAccountsYetAddAccountToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
|
||||
this.scanLibraryToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
|
||||
this.scanLibraryOfAllAccountsToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
|
||||
this.scanLibraryOfSomeAccountsToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
|
||||
this.removeLibraryBooksToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
|
||||
this.removeAllAccountsToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
|
||||
this.removeSomeAccountsToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
|
||||
this.liberateToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
|
||||
this.beginBookBackupsToolStripMenuItem = new LibationWinForms.FormattableToolStripMenuItem();
|
||||
this.beginPdfBackupsToolStripMenuItem = new LibationWinForms.FormattableToolStripMenuItem();
|
||||
this.convertAllM4bToMp3ToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
|
||||
this.liberateVisibleToolStripMenuItem_LiberateMenu = new LibationWinForms.FormattableToolStripMenuItem();
|
||||
this.exportToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
|
||||
this.exportLibraryToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
|
||||
this.quickFiltersToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
|
||||
this.firstFilterIsDefaultToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
|
||||
this.editQuickFiltersToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
|
||||
this.toolStripSeparator1 = new System.Windows.Forms.ToolStripSeparator();
|
||||
this.scanningToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
|
||||
this.visibleBooksToolStripMenuItem = new LibationWinForms.FormattableToolStripMenuItem();
|
||||
this.liberateVisibleToolStripMenuItem_VisibleBooksMenu = new LibationWinForms.FormattableToolStripMenuItem();
|
||||
this.replaceTagsToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
|
||||
this.setDownloadedToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
|
||||
this.removeToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
|
||||
this.settingsToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
|
||||
this.accountsToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
|
||||
this.basicSettingsToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
|
||||
this.toolStripSeparator2 = new System.Windows.Forms.ToolStripSeparator();
|
||||
this.aboutToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
|
||||
this.statusStrip1 = new System.Windows.Forms.StatusStrip();
|
||||
this.visibleCountLbl = new LibationWinForms.FormattableToolStripStatusLabel();
|
||||
this.springLbl = new System.Windows.Forms.ToolStripStatusLabel();
|
||||
this.backupsCountsLbl = new System.Windows.Forms.ToolStripStatusLabel();
|
||||
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.productsDisplay = new LibationWinForms.GridView.ProductsDisplay();
|
||||
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();
|
||||
//
|
||||
// filterHelpBtn
|
||||
//
|
||||
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(26, 27);
|
||||
this.filterHelpBtn.TabIndex = 3;
|
||||
this.filterHelpBtn.Text = "?";
|
||||
this.filterHelpBtn.UseVisualStyleBackColor = true;
|
||||
this.filterHelpBtn.Click += new System.EventHandler(this.filterHelpBtn_Click);
|
||||
//
|
||||
// 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(916, 3);
|
||||
this.filterBtn.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3);
|
||||
this.filterBtn.Name = "filterBtn";
|
||||
this.filterBtn.Size = new System.Drawing.Size(88, 27);
|
||||
this.filterBtn.TabIndex = 2;
|
||||
this.filterBtn.Text = "Filter";
|
||||
this.filterBtn.UseVisualStyleBackColor = true;
|
||||
this.filterBtn.Click += new System.EventHandler(this.filterBtn_Click);
|
||||
//
|
||||
// filterSearchTb
|
||||
//
|
||||
this.filterSearchTb.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left)
|
||||
| System.Windows.Forms.AnchorStyles.Right)));
|
||||
this.gridPanel.Location = new System.Drawing.Point(14, 65);
|
||||
this.gridPanel.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3);
|
||||
this.gridPanel.Name = "gridPanel";
|
||||
this.gridPanel.Size = new System.Drawing.Size(979, 445);
|
||||
this.gridPanel.TabIndex = 5;
|
||||
//
|
||||
// filterHelpBtn
|
||||
//
|
||||
this.filterHelpBtn.Location = new System.Drawing.Point(14, 31);
|
||||
this.filterHelpBtn.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3);
|
||||
this.filterHelpBtn.Name = "filterHelpBtn";
|
||||
this.filterHelpBtn.Size = new System.Drawing.Size(26, 27);
|
||||
this.filterHelpBtn.TabIndex = 3;
|
||||
this.filterHelpBtn.Text = "?";
|
||||
this.filterHelpBtn.UseVisualStyleBackColor = true;
|
||||
this.filterHelpBtn.Click += new System.EventHandler(this.filterHelpBtn_Click);
|
||||
//
|
||||
// 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(905, 31);
|
||||
this.filterBtn.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3);
|
||||
this.filterBtn.Name = "filterBtn";
|
||||
this.filterBtn.Size = new System.Drawing.Size(88, 27);
|
||||
this.filterBtn.TabIndex = 2;
|
||||
this.filterBtn.Text = "Filter";
|
||||
this.filterBtn.UseVisualStyleBackColor = true;
|
||||
this.filterBtn.Click += new System.EventHandler(this.filterBtn_Click);
|
||||
//
|
||||
// filterSearchTb
|
||||
//
|
||||
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(217, 33);
|
||||
this.filterSearchTb.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3);
|
||||
this.filterSearchTb.Name = "filterSearchTb";
|
||||
this.filterSearchTb.Size = new System.Drawing.Size(681, 23);
|
||||
this.filterSearchTb.TabIndex = 1;
|
||||
this.filterSearchTb.KeyPress += new System.Windows.Forms.KeyPressEventHandler(this.filterSearchTb_KeyPress);
|
||||
//
|
||||
// menuStrip1
|
||||
//
|
||||
this.menuStrip1.Items.AddRange(new System.Windows.Forms.ToolStripItem[] {
|
||||
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(712, 23);
|
||||
this.filterSearchTb.TabIndex = 1;
|
||||
this.filterSearchTb.KeyPress += new System.Windows.Forms.KeyPressEventHandler(this.filterSearchTb_KeyPress);
|
||||
//
|
||||
// menuStrip1
|
||||
//
|
||||
this.menuStrip1.ImageScalingSize = new System.Drawing.Size(40, 40);
|
||||
this.menuStrip1.Items.AddRange(new System.Windows.Forms.ToolStripItem[] {
|
||||
this.importToolStripMenuItem,
|
||||
this.liberateToolStripMenuItem,
|
||||
this.exportToolStripMenuItem,
|
||||
this.quickFiltersToolStripMenuItem,
|
||||
this.settingsToolStripMenuItem,
|
||||
this.scanningToolStripMenuItem});
|
||||
this.menuStrip1.Location = new System.Drawing.Point(0, 0);
|
||||
this.menuStrip1.Name = "menuStrip1";
|
||||
this.menuStrip1.Padding = new System.Windows.Forms.Padding(7, 2, 0, 2);
|
||||
this.menuStrip1.Size = new System.Drawing.Size(1007, 24);
|
||||
this.menuStrip1.TabIndex = 0;
|
||||
this.menuStrip1.Text = "menuStrip1";
|
||||
//
|
||||
// importToolStripMenuItem
|
||||
//
|
||||
this.importToolStripMenuItem.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] {
|
||||
this.scanningToolStripMenuItem,
|
||||
this.visibleBooksToolStripMenuItem,
|
||||
this.settingsToolStripMenuItem});
|
||||
this.menuStrip1.Location = new System.Drawing.Point(0, 0);
|
||||
this.menuStrip1.Name = "menuStrip1";
|
||||
this.menuStrip1.Padding = new System.Windows.Forms.Padding(7, 2, 0, 2);
|
||||
this.menuStrip1.Size = new System.Drawing.Size(1061, 24);
|
||||
this.menuStrip1.TabIndex = 0;
|
||||
this.menuStrip1.Text = "menuStrip1";
|
||||
//
|
||||
// importToolStripMenuItem
|
||||
//
|
||||
this.importToolStripMenuItem.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] {
|
||||
this.autoScanLibraryToolStripMenuItem,
|
||||
this.noAccountsYetAddAccountToolStripMenuItem,
|
||||
this.scanLibraryToolStripMenuItem,
|
||||
this.scanLibraryOfAllAccountsToolStripMenuItem,
|
||||
this.scanLibraryOfSomeAccountsToolStripMenuItem,
|
||||
this.removeLibraryBooksToolStripMenuItem});
|
||||
this.importToolStripMenuItem.Name = "importToolStripMenuItem";
|
||||
this.importToolStripMenuItem.Size = new System.Drawing.Size(55, 20);
|
||||
this.importToolStripMenuItem.Text = "&Import";
|
||||
//
|
||||
// noAccountsYetAddAccountToolStripMenuItem
|
||||
//
|
||||
this.noAccountsYetAddAccountToolStripMenuItem.Name = "noAccountsYetAddAccountToolStripMenuItem";
|
||||
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);
|
||||
//
|
||||
// autoScanLibraryToolStripMenuItem
|
||||
//
|
||||
this.autoScanLibraryToolStripMenuItem.Name = "autoScanLibraryToolStripMenuItem";
|
||||
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);
|
||||
//
|
||||
// scanLibraryToolStripMenuItem
|
||||
//
|
||||
this.scanLibraryToolStripMenuItem.Name = "scanLibraryToolStripMenuItem";
|
||||
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(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(247, 22);
|
||||
this.scanLibraryOfSomeAccountsToolStripMenuItem.Text = "Scan Library of &Some Accounts...";
|
||||
this.scanLibraryOfSomeAccountsToolStripMenuItem.Click += new System.EventHandler(this.scanLibraryOfSomeAccountsToolStripMenuItem_Click);
|
||||
//
|
||||
// removeLibraryBooksToolStripMenuItem
|
||||
//
|
||||
this.removeLibraryBooksToolStripMenuItem.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] {
|
||||
this.importToolStripMenuItem.Name = "importToolStripMenuItem";
|
||||
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(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(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(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(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(247, 22);
|
||||
this.scanLibraryOfSomeAccountsToolStripMenuItem.Text = "Scan Library of &Some Accounts...";
|
||||
this.scanLibraryOfSomeAccountsToolStripMenuItem.Click += new System.EventHandler(this.scanLibraryOfSomeAccountsToolStripMenuItem_Click);
|
||||
//
|
||||
// removeLibraryBooksToolStripMenuItem
|
||||
//
|
||||
this.removeLibraryBooksToolStripMenuItem.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] {
|
||||
this.removeAllAccountsToolStripMenuItem,
|
||||
this.removeSomeAccountsToolStripMenuItem});
|
||||
this.removeLibraryBooksToolStripMenuItem.Name = "removeLibraryBooksToolStripMenuItem";
|
||||
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(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(157, 22);
|
||||
this.removeSomeAccountsToolStripMenuItem.Text = "Some Accounts";
|
||||
this.removeSomeAccountsToolStripMenuItem.Click += new System.EventHandler(this.removeSomeAccountsToolStripMenuItem_Click);
|
||||
//
|
||||
// liberateToolStripMenuItem
|
||||
//
|
||||
this.liberateToolStripMenuItem.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] {
|
||||
this.removeLibraryBooksToolStripMenuItem.Name = "removeLibraryBooksToolStripMenuItem";
|
||||
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(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(157, 22);
|
||||
this.removeSomeAccountsToolStripMenuItem.Text = "Some Accounts";
|
||||
this.removeSomeAccountsToolStripMenuItem.Click += new System.EventHandler(this.removeSomeAccountsToolStripMenuItem_Click);
|
||||
//
|
||||
// liberateToolStripMenuItem
|
||||
//
|
||||
this.liberateToolStripMenuItem.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] {
|
||||
this.beginBookBackupsToolStripMenuItem,
|
||||
this.beginPdfBackupsToolStripMenuItem,
|
||||
this.convertAllM4bToMp3ToolStripMenuItem});
|
||||
this.liberateToolStripMenuItem.Name = "liberateToolStripMenuItem";
|
||||
this.liberateToolStripMenuItem.Size = new System.Drawing.Size(61, 20);
|
||||
this.liberateToolStripMenuItem.Text = "&Liberate";
|
||||
//
|
||||
// beginBookBackupsToolStripMenuItem
|
||||
//
|
||||
this.beginBookBackupsToolStripMenuItem.Name = "beginBookBackupsToolStripMenuItem";
|
||||
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.Name = "beginPdfBackupsToolStripMenuItem";
|
||||
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(293, 22);
|
||||
this.convertAllM4bToMp3ToolStripMenuItem.Text = "Convert all M4b to Mp3 [Long-running]...";
|
||||
this.convertAllM4bToMp3ToolStripMenuItem.Click += new System.EventHandler(this.convertAllM4bToMp3ToolStripMenuItem_Click);
|
||||
//
|
||||
// exportToolStripMenuItem
|
||||
//
|
||||
this.exportToolStripMenuItem.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] {
|
||||
this.convertAllM4bToMp3ToolStripMenuItem,
|
||||
this.liberateVisibleToolStripMenuItem_LiberateMenu});
|
||||
this.liberateToolStripMenuItem.Name = "liberateToolStripMenuItem";
|
||||
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(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(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(293, 22);
|
||||
this.convertAllM4bToMp3ToolStripMenuItem.Text = "Convert all &M4b to Mp3 [Long-running]...";
|
||||
this.convertAllM4bToMp3ToolStripMenuItem.Click += new System.EventHandler(this.convertAllM4bToMp3ToolStripMenuItem_Click);
|
||||
//
|
||||
// liberateVisibleToolStripMenuItem_LiberateMenu
|
||||
//
|
||||
this.liberateVisibleToolStripMenuItem_LiberateMenu.FormatText = "Liberate &Visible Books: {0}";
|
||||
this.liberateVisibleToolStripMenuItem_LiberateMenu.Name = "liberateVisibleToolStripMenuItem_LiberateMenu";
|
||||
this.liberateVisibleToolStripMenuItem_LiberateMenu.Size = new System.Drawing.Size(293, 22);
|
||||
this.liberateVisibleToolStripMenuItem_LiberateMenu.Text = "Liberate &Visible Books: {0}";
|
||||
this.liberateVisibleToolStripMenuItem_LiberateMenu.Click += new System.EventHandler(this.liberateVisible);
|
||||
//
|
||||
// exportToolStripMenuItem
|
||||
//
|
||||
this.exportToolStripMenuItem.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] {
|
||||
this.exportLibraryToolStripMenuItem});
|
||||
this.exportToolStripMenuItem.Name = "exportToolStripMenuItem";
|
||||
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(156, 22);
|
||||
this.exportLibraryToolStripMenuItem.Text = "E&xport Library...";
|
||||
this.exportLibraryToolStripMenuItem.Click += new System.EventHandler(this.exportLibraryToolStripMenuItem_Click);
|
||||
//
|
||||
// quickFiltersToolStripMenuItem
|
||||
//
|
||||
this.quickFiltersToolStripMenuItem.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] {
|
||||
this.exportToolStripMenuItem.Name = "exportToolStripMenuItem";
|
||||
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(156, 22);
|
||||
this.exportLibraryToolStripMenuItem.Text = "E&xport Library...";
|
||||
this.exportLibraryToolStripMenuItem.Click += new System.EventHandler(this.exportLibraryToolStripMenuItem_Click);
|
||||
//
|
||||
// quickFiltersToolStripMenuItem
|
||||
//
|
||||
this.quickFiltersToolStripMenuItem.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] {
|
||||
this.firstFilterIsDefaultToolStripMenuItem,
|
||||
this.editQuickFiltersToolStripMenuItem,
|
||||
this.toolStripSeparator1});
|
||||
this.quickFiltersToolStripMenuItem.Name = "quickFiltersToolStripMenuItem";
|
||||
this.quickFiltersToolStripMenuItem.Size = new System.Drawing.Size(84, 20);
|
||||
this.quickFiltersToolStripMenuItem.Text = "Quick &Filters";
|
||||
//
|
||||
// firstFilterIsDefaultToolStripMenuItem
|
||||
//
|
||||
this.firstFilterIsDefaultToolStripMenuItem.Name = "firstFilterIsDefaultToolStripMenuItem";
|
||||
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);
|
||||
//
|
||||
// editQuickFiltersToolStripMenuItem
|
||||
//
|
||||
this.editQuickFiltersToolStripMenuItem.Name = "editQuickFiltersToolStripMenuItem";
|
||||
this.editQuickFiltersToolStripMenuItem.Size = new System.Drawing.Size(256, 22);
|
||||
this.editQuickFiltersToolStripMenuItem.Text = "&Edit quick filters...";
|
||||
this.editQuickFiltersToolStripMenuItem.Click += new System.EventHandler(this.EditQuickFiltersToolStripMenuItem_Click);
|
||||
//
|
||||
// toolStripSeparator1
|
||||
//
|
||||
this.toolStripSeparator1.Name = "toolStripSeparator1";
|
||||
this.toolStripSeparator1.Size = new System.Drawing.Size(253, 6);
|
||||
//
|
||||
// settingsToolStripMenuItem
|
||||
//
|
||||
this.settingsToolStripMenuItem.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] {
|
||||
this.quickFiltersToolStripMenuItem.Name = "quickFiltersToolStripMenuItem";
|
||||
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(256, 22);
|
||||
this.firstFilterIsDefaultToolStripMenuItem.Text = "Start Libation with 1st filter &Default";
|
||||
this.firstFilterIsDefaultToolStripMenuItem.Click += new System.EventHandler(this.firstFilterIsDefaultToolStripMenuItem_Click);
|
||||
//
|
||||
// editQuickFiltersToolStripMenuItem
|
||||
//
|
||||
this.editQuickFiltersToolStripMenuItem.Name = "editQuickFiltersToolStripMenuItem";
|
||||
this.editQuickFiltersToolStripMenuItem.Size = new System.Drawing.Size(256, 22);
|
||||
this.editQuickFiltersToolStripMenuItem.Text = "&Edit quick filters...";
|
||||
this.editQuickFiltersToolStripMenuItem.Click += new System.EventHandler(this.editQuickFiltersToolStripMenuItem_Click);
|
||||
//
|
||||
// toolStripSeparator1
|
||||
//
|
||||
this.toolStripSeparator1.Name = "toolStripSeparator1";
|
||||
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(93, 20);
|
||||
this.scanningToolStripMenuItem.Text = "Scanning...";
|
||||
this.scanningToolStripMenuItem.Visible = false;
|
||||
//
|
||||
// visibleBooksToolStripMenuItem
|
||||
//
|
||||
this.visibleBooksToolStripMenuItem.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] {
|
||||
this.liberateVisibleToolStripMenuItem_VisibleBooksMenu,
|
||||
this.replaceTagsToolStripMenuItem,
|
||||
this.setDownloadedToolStripMenuItem,
|
||||
this.removeToolStripMenuItem});
|
||||
this.visibleBooksToolStripMenuItem.FormatText = "&Visible Books: {0}";
|
||||
this.visibleBooksToolStripMenuItem.Name = "visibleBooksToolStripMenuItem";
|
||||
this.visibleBooksToolStripMenuItem.Size = new System.Drawing.Size(108, 20);
|
||||
this.visibleBooksToolStripMenuItem.Text = "&Visible Books: {0}";
|
||||
//
|
||||
// liberateVisibleToolStripMenuItem_VisibleBooksMenu
|
||||
//
|
||||
this.liberateVisibleToolStripMenuItem_VisibleBooksMenu.FormatText = "&Liberate: {0}";
|
||||
this.liberateVisibleToolStripMenuItem_VisibleBooksMenu.Name = "liberateVisibleToolStripMenuItem_VisibleBooksMenu";
|
||||
this.liberateVisibleToolStripMenuItem_VisibleBooksMenu.Size = new System.Drawing.Size(209, 22);
|
||||
this.liberateVisibleToolStripMenuItem_VisibleBooksMenu.Text = "&Liberate: {0}";
|
||||
this.liberateVisibleToolStripMenuItem_VisibleBooksMenu.Click += new System.EventHandler(this.liberateVisible);
|
||||
//
|
||||
// replaceTagsToolStripMenuItem
|
||||
//
|
||||
this.replaceTagsToolStripMenuItem.Name = "replaceTagsToolStripMenuItem";
|
||||
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(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(209, 22);
|
||||
this.removeToolStripMenuItem.Text = "&Remove from library...";
|
||||
this.removeToolStripMenuItem.Click += new System.EventHandler(this.removeToolStripMenuItem_Click);
|
||||
//
|
||||
// settingsToolStripMenuItem
|
||||
//
|
||||
this.settingsToolStripMenuItem.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] {
|
||||
this.accountsToolStripMenuItem,
|
||||
this.basicSettingsToolStripMenuItem,
|
||||
this.toolStripSeparator2,
|
||||
this.aboutToolStripMenuItem});
|
||||
this.settingsToolStripMenuItem.Name = "settingsToolStripMenuItem";
|
||||
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(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(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(130, 6);
|
||||
//
|
||||
// aboutToolStripMenuItem
|
||||
//
|
||||
this.aboutToolStripMenuItem.Name = "aboutToolStripMenuItem";
|
||||
this.aboutToolStripMenuItem.Size = new System.Drawing.Size(133, 22);
|
||||
this.aboutToolStripMenuItem.Text = "A&bout...";
|
||||
this.aboutToolStripMenuItem.Click += new System.EventHandler(this.aboutToolStripMenuItem_Click);
|
||||
//
|
||||
// scanningToolStripMenuItem
|
||||
//
|
||||
this.scanningToolStripMenuItem.Alignment = System.Windows.Forms.ToolStripItemAlignment.Right;
|
||||
this.scanningToolStripMenuItem.Enabled = false;
|
||||
this.scanningToolStripMenuItem.Image = global::LibationWinForms.Properties.Resources.import_16x16;
|
||||
this.scanningToolStripMenuItem.Name = "scanningToolStripMenuItem";
|
||||
this.scanningToolStripMenuItem.Size = new System.Drawing.Size(93, 20);
|
||||
this.scanningToolStripMenuItem.Text = "Scanning...";
|
||||
this.scanningToolStripMenuItem.Visible = false;
|
||||
//
|
||||
// statusStrip1
|
||||
//
|
||||
this.statusStrip1.Items.AddRange(new System.Windows.Forms.ToolStripItem[] {
|
||||
this.settingsToolStripMenuItem.Name = "settingsToolStripMenuItem";
|
||||
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(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(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(130, 6);
|
||||
//
|
||||
// aboutToolStripMenuItem
|
||||
//
|
||||
this.aboutToolStripMenuItem.Name = "aboutToolStripMenuItem";
|
||||
this.aboutToolStripMenuItem.Size = new System.Drawing.Size(133, 22);
|
||||
this.aboutToolStripMenuItem.Text = "A&bout...";
|
||||
this.aboutToolStripMenuItem.Click += new System.EventHandler(this.aboutToolStripMenuItem_Click);
|
||||
//
|
||||
// statusStrip1
|
||||
//
|
||||
this.statusStrip1.ImageScalingSize = new System.Drawing.Size(40, 40);
|
||||
this.statusStrip1.Items.AddRange(new System.Windows.Forms.ToolStripItem[] {
|
||||
this.visibleCountLbl,
|
||||
this.springLbl,
|
||||
this.backupsCountsLbl,
|
||||
this.pdfsCountsLbl});
|
||||
this.statusStrip1.Location = new System.Drawing.Point(0, 517);
|
||||
this.statusStrip1.Name = "statusStrip1";
|
||||
this.statusStrip1.Padding = new System.Windows.Forms.Padding(1, 0, 16, 0);
|
||||
this.statusStrip1.Size = new System.Drawing.Size(1007, 22);
|
||||
this.statusStrip1.TabIndex = 6;
|
||||
this.statusStrip1.Text = "statusStrip1";
|
||||
//
|
||||
// visibleCountLbl
|
||||
//
|
||||
this.visibleCountLbl.Name = "visibleCountLbl";
|
||||
this.visibleCountLbl.Size = new System.Drawing.Size(53, 17);
|
||||
this.visibleCountLbl.Text = "Visible: 0";
|
||||
//
|
||||
// springLbl
|
||||
//
|
||||
this.springLbl.Name = "springLbl";
|
||||
this.springLbl.Size = new System.Drawing.Size(548, 17);
|
||||
this.springLbl.Spring = true;
|
||||
//
|
||||
// backupsCountsLbl
|
||||
//
|
||||
this.backupsCountsLbl.Name = "backupsCountsLbl";
|
||||
this.backupsCountsLbl.Size = new System.Drawing.Size(218, 17);
|
||||
this.backupsCountsLbl.Text = "[Calculating backed up book quantities]";
|
||||
//
|
||||
// pdfsCountsLbl
|
||||
//
|
||||
this.pdfsCountsLbl.Name = "pdfsCountsLbl";
|
||||
this.pdfsCountsLbl.Size = new System.Drawing.Size(171, 17);
|
||||
this.pdfsCountsLbl.Text = "| [Calculating backed up PDFs]";
|
||||
//
|
||||
// addFilterBtn
|
||||
//
|
||||
this.addFilterBtn.Location = new System.Drawing.Point(47, 31);
|
||||
this.addFilterBtn.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3);
|
||||
this.addFilterBtn.Name = "addFilterBtn";
|
||||
this.addFilterBtn.Size = new System.Drawing.Size(163, 27);
|
||||
this.addFilterBtn.TabIndex = 4;
|
||||
this.addFilterBtn.Text = "Add To Quick Filters";
|
||||
this.addFilterBtn.UseVisualStyleBackColor = true;
|
||||
this.addFilterBtn.Click += new System.EventHandler(this.AddFilterBtn_Click);
|
||||
//
|
||||
// Form1
|
||||
//
|
||||
this.AutoScaleDimensions = new System.Drawing.SizeF(7F, 15F);
|
||||
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
|
||||
this.ClientSize = new System.Drawing.Size(1007, 539);
|
||||
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.Icon = ((System.Drawing.Icon)(resources.GetObject("$this.Icon")));
|
||||
this.MainMenuStrip = this.menuStrip1;
|
||||
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);
|
||||
this.menuStrip1.ResumeLayout(false);
|
||||
this.menuStrip1.PerformLayout();
|
||||
this.statusStrip1.ResumeLayout(false);
|
||||
this.statusStrip1.PerformLayout();
|
||||
this.ResumeLayout(false);
|
||||
this.PerformLayout();
|
||||
this.statusStrip1.Location = new System.Drawing.Point(0, 618);
|
||||
this.statusStrip1.Name = "statusStrip1";
|
||||
this.statusStrip1.Padding = new System.Windows.Forms.Padding(1, 0, 16, 0);
|
||||
this.statusStrip1.Size = new System.Drawing.Size(1061, 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(61, 17);
|
||||
this.visibleCountLbl.Text = "Visible: {0}";
|
||||
//
|
||||
// springLbl
|
||||
//
|
||||
this.springLbl.Name = "springLbl";
|
||||
this.springLbl.Size = new System.Drawing.Size(547, 17);
|
||||
this.springLbl.Spring = true;
|
||||
//
|
||||
// backupsCountsLbl
|
||||
//
|
||||
this.backupsCountsLbl.Name = "backupsCountsLbl";
|
||||
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(218, 17);
|
||||
this.pdfsCountsLbl.Text = "| PDFs: NOT d/l\'ed: {0} Downloaded: {1}";
|
||||
//
|
||||
// addQuickFilterBtn
|
||||
//
|
||||
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(1463, 640);
|
||||
this.splitContainer1.SplitterDistance = 1061;
|
||||
this.splitContainer1.SplitterWidth = 8;
|
||||
this.splitContainer1.TabIndex = 7;
|
||||
//
|
||||
// panel1
|
||||
//
|
||||
this.panel1.AutoSizeMode = System.Windows.Forms.AutoSizeMode.GrowAndShrink;
|
||||
this.panel1.Controls.Add(this.productsDisplay);
|
||||
this.panel1.Controls.Add(this.toggleQueueHideBtn);
|
||||
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(1061, 594);
|
||||
this.panel1.TabIndex = 7;
|
||||
//
|
||||
// productsDisplay
|
||||
//
|
||||
this.productsDisplay.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.productsDisplay.AutoScroll = true;
|
||||
this.productsDisplay.Location = new System.Drawing.Point(15, 36);
|
||||
this.productsDisplay.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3);
|
||||
this.productsDisplay.Name = "productsDisplay";
|
||||
this.productsDisplay.Size = new System.Drawing.Size(1031, 555);
|
||||
this.productsDisplay.TabIndex = 9;
|
||||
this.productsDisplay.VisibleCountChanged += new System.EventHandler<int>(this.productsDisplay_VisibleCountChanged);
|
||||
this.productsDisplay.LiberateClicked += new System.EventHandler<DataLayer.LibraryBook>(this.ProductsDisplay_LiberateClicked);
|
||||
this.productsDisplay.InitialLoaded += new System.EventHandler(this.productsDisplay_InitialLoaded);
|
||||
//
|
||||
// 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(1013, 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(394, 640);
|
||||
this.processBookQueue1.TabIndex = 0;
|
||||
//
|
||||
// Form1
|
||||
//
|
||||
this.AutoScaleDimensions = new System.Drawing.SizeF(7F, 15F);
|
||||
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
|
||||
this.ClientSize = new System.Drawing.Size(1463, 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(4, 3, 4, 3);
|
||||
this.Name = "Form1";
|
||||
this.Text = "Libation: Liberate your Library";
|
||||
this.Load += new System.EventHandler(this.Form1_Load);
|
||||
this.menuStrip1.ResumeLayout(false);
|
||||
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);
|
||||
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
private System.Windows.Forms.Panel gridPanel;
|
||||
private System.Windows.Forms.MenuStrip menuStrip1;
|
||||
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;
|
||||
@@ -420,7 +555,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;
|
||||
@@ -438,5 +573,16 @@
|
||||
private System.Windows.Forms.ToolStripMenuItem aboutToolStripMenuItem;
|
||||
private System.Windows.Forms.ToolStripMenuItem scanningToolStripMenuItem;
|
||||
private System.Windows.Forms.ToolStripMenuItem autoScanLibraryToolStripMenuItem;
|
||||
}
|
||||
private LibationWinForms.FormattableToolStripMenuItem visibleBooksToolStripMenuItem;
|
||||
private LibationWinForms.FormattableToolStripMenuItem liberateVisibleToolStripMenuItem_VisibleBooksMenu;
|
||||
private System.Windows.Forms.ToolStripMenuItem replaceTagsToolStripMenuItem;
|
||||
private System.Windows.Forms.ToolStripMenuItem setDownloadedToolStripMenuItem;
|
||||
private System.Windows.Forms.ToolStripMenuItem removeToolStripMenuItem;
|
||||
private LibationWinForms.FormattableToolStripMenuItem liberateVisibleToolStripMenuItem_LiberateMenu;
|
||||
private System.Windows.Forms.SplitContainer splitContainer1;
|
||||
private LibationWinForms.ProcessQueue.ProcessQueueControl processBookQueue1;
|
||||
private System.Windows.Forms.Panel panel1;
|
||||
private System.Windows.Forms.Button toggleQueueHideBtn;
|
||||
private LibationWinForms.GridView.ProductsDisplay productsDisplay;
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
{
|
||||
productsDisplay.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 _ = null, EventArgs __ = null)
|
||||
{
|
||||
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.
|
||||
}
|
||||
}
|
||||
}
|
||||
107
Source/LibationWinForms/Form1.ProcessQueue.cs
Normal file
107
Source/LibationWinForms/Form1.ProcessQueue.cs
Normal file
@@ -0,0 +1,107 @@
|
||||
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()
|
||||
{
|
||||
processBookQueue1.popoutBtn.Click += ProcessBookQueue1_PopOut;
|
||||
var coppalseState = Configuration.Instance.GetNonString<bool>(nameof(splitContainer1.Panel2Collapsed));
|
||||
WidthChange = splitContainer1.Panel2.Width + splitContainer1.SplitterWidth;
|
||||
SetQueueCollapseState(coppalseState);
|
||||
}
|
||||
|
||||
private void ProductsDisplay_LiberateClicked(object sender, LibraryBook e)
|
||||
{
|
||||
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);
|
||||
}
|
||||
else 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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();
|
||||
|
||||
private void productsDisplay_InitialLoaded(object sender, EventArgs e)
|
||||
{
|
||||
if (QuickFilters.UseDefault)
|
||||
performFilter(QuickFilters.Filters.FirstOrDefault());
|
||||
}
|
||||
}
|
||||
}
|
||||
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}");
|
||||
}
|
||||
}
|
||||
132
Source/LibationWinForms/Form1.VisibleBooks.cs
Normal file
132
Source/LibationWinForms/Form1.VisibleBooks.cs
Normal file
@@ -0,0 +1,132 @@
|
||||
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_VisibleBooksMenu.Format(0);
|
||||
liberateVisibleToolStripMenuItem_LiberateMenu.Format(0);
|
||||
|
||||
// top menu strip
|
||||
visibleBooksToolStripMenuItem.Format(0);
|
||||
|
||||
LibraryCommands.BookUserDefinedItemCommitted += setLiberatedVisibleMenuItemAsync;
|
||||
}
|
||||
private async void setLiberatedVisibleMenuItemAsync(object _, object __)
|
||||
=> await Task.Run(setLiberatedVisibleMenuItem);
|
||||
void setLiberatedVisibleMenuItem()
|
||||
{
|
||||
var notLiberated = productsDisplay.GetVisible().Count(lb => lb.Book.UserDefinedItem.BookStatus == DataLayer.LiberatedStatus.NotLiberated);
|
||||
this.UIThreadSync(() =>
|
||||
{
|
||||
if (notLiberated > 0)
|
||||
{
|
||||
liberateVisibleToolStripMenuItem_VisibleBooksMenu.Format(notLiberated);
|
||||
liberateVisibleToolStripMenuItem_VisibleBooksMenu.Enabled = true;
|
||||
|
||||
liberateVisibleToolStripMenuItem_LiberateMenu.Format(notLiberated);
|
||||
liberateVisibleToolStripMenuItem_LiberateMenu.Enabled = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
liberateVisibleToolStripMenuItem_VisibleBooksMenu.Text = "All visible books are liberated";
|
||||
liberateVisibleToolStripMenuItem_VisibleBooksMenu.Enabled = false;
|
||||
|
||||
liberateVisibleToolStripMenuItem_LiberateMenu.Text = "All visible books are liberated";
|
||||
liberateVisibleToolStripMenuItem_LiberateMenu.Enabled = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private async void liberateVisible(object sender, EventArgs e)
|
||||
{
|
||||
SetQueueCollapseState(false);
|
||||
await Task.Run(() => processBookQueue1.AddDownloadDecrypt(productsDisplay.GetVisible()));
|
||||
}
|
||||
|
||||
private void replaceTagsToolStripMenuItem_Click(object sender, EventArgs e)
|
||||
{
|
||||
var dialog = new TagsBatchDialog();
|
||||
var result = dialog.ShowDialog();
|
||||
if (result != DialogResult.OK)
|
||||
return;
|
||||
|
||||
var visibleLibraryBooks = productsDisplay.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 = productsDisplay.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 = productsDisplay.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);
|
||||
}
|
||||
|
||||
private async void productsDisplay_VisibleCountChanged(object sender, int qty)
|
||||
{
|
||||
// bottom-left visible count
|
||||
visibleCountLbl.Format(qty);
|
||||
|
||||
// top menu strip
|
||||
visibleBooksToolStripMenuItem.Format(qty);
|
||||
visibleBooksToolStripMenuItem.Enabled = qty > 0;
|
||||
|
||||
//Not used for anything?
|
||||
var notLiberatedCount = productsDisplay.GetVisible().Count(lb => lb.Book.UserDefinedItem.BookStatus == DataLayer.LiberatedStatus.NotLiberated);
|
||||
|
||||
await Task.Run(setLiberatedVisibleMenuItem);
|
||||
}
|
||||
}
|
||||
}
|
||||
37
Source/LibationWinForms/Form1._NonUI.cs
Normal file
37
Source/LibationWinForms/Form1._NonUI.cs
Normal file
@@ -0,0 +1,37 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using ApplicationServices;
|
||||
using Dinah.Core.Drawing;
|
||||
using LibationFileManager;
|
||||
|
||||
namespace LibationWinForms
|
||||
{
|
||||
public partial class Form1
|
||||
{
|
||||
private void Configure_NonUI()
|
||||
{
|
||||
// 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));
|
||||
PictureStorage.SetDefaultImage(PictureSize.Native, Properties.Resources.default_cover_500x500.ToBytes(format));
|
||||
|
||||
// wire-up event to automatically download after scan.
|
||||
// winforms only. this should NOT be allowed in cli
|
||||
updateCountsBw.RunWorkerCompleted += (object sender, System.ComponentModel.RunWorkerCompletedEventArgs e) =>
|
||||
{
|
||||
if (!Configuration.Instance.AutoDownloadEpisodes)
|
||||
return;
|
||||
|
||||
var libraryStats = e.Result as LibraryCommands.LibraryStats;
|
||||
|
||||
if ((libraryStats.booksNoProgress + libraryStats.pdfsNotDownloaded) > 0)
|
||||
beginBookBackupsToolStripMenuItem_Click();
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4,51 +4,59 @@ 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; }
|
||||
|
||||
public Form1()
|
||||
{
|
||||
InitializeComponent();
|
||||
|
||||
// back up string formats
|
||||
beginBookBackupsToolStripMenuItem_format = beginBookBackupsToolStripMenuItem.Text;
|
||||
beginPdfBackupsToolStripMenuItem_format = beginPdfBackupsToolStripMenuItem.Text;
|
||||
// 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();
|
||||
|
||||
if (this.DesignMode)
|
||||
return;
|
||||
|
||||
// independent UI updates
|
||||
this.Load += (_, _) => this.RestoreSizeAndLocation(Configuration.Instance);
|
||||
this.FormClosing += (_, _) => this.SaveSizeAndLocation(Configuration.Instance);
|
||||
LibraryCommands.LibrarySizeChanged += reloadGridAndUpdateBottomNumbers;
|
||||
LibraryCommands.BookUserDefinedItemCommitted += setBackupCounts;
|
||||
QuickFilters.Updated += updateFiltersMenu;
|
||||
LibraryCommands.ScanBegin += LibraryCommands_ScanBegin;
|
||||
LibraryCommands.ScanEnd += LibraryCommands_ScanEnd;
|
||||
|
||||
// accounts updated
|
||||
this.Load += refreshImportMenu;
|
||||
AccountsSettingsPersister.Saved += refreshImportMenu;
|
||||
// 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);
|
||||
|
||||
configAndInitAutoScan();
|
||||
// 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_BackupCounts();
|
||||
Configure_ScanAuto();
|
||||
Configure_ScanNotification();
|
||||
Configure_VisibleBooks();
|
||||
Configure_QuickFilters();
|
||||
Configure_ScanManual();
|
||||
Configure_Liberate();
|
||||
Configure_Export();
|
||||
Configure_Settings();
|
||||
Configure_ProcessQueue();
|
||||
Configure_Filter();
|
||||
// misc which belongs in winforms app but doesn't have a UI element
|
||||
Configure_NonUI();
|
||||
|
||||
// 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));
|
||||
// Configure_Grid(); // since it's just this, can keep here. If it needs more, then give grid it's own 'partial class Form1'
|
||||
{
|
||||
this.Load += (_, __) => productsDisplay.Display();
|
||||
LibraryCommands.LibrarySizeChanged += (_, __) => this.UIThreadAsync(() => productsDisplay.Display());
|
||||
}
|
||||
}
|
||||
|
||||
private void Form1_Load(object sender, EventArgs e)
|
||||
@@ -56,518 +64,7 @@ namespace LibationWinForms
|
||||
if (this.DesignMode)
|
||||
return;
|
||||
|
||||
// can't refactor into "this.Load => reloadGridAndUpdateBottomNumbers"
|
||||
// because loadInitialQuickFilterState must follow it
|
||||
reloadGridAndUpdateBottomNumbers();
|
||||
|
||||
// also applies filter. ONLY call AFTER loading grid
|
||||
loadInitialQuickFilterState();
|
||||
// I'm leaving this empty call here as a reminder that if we use this, it should probably be after DesignMode check
|
||||
}
|
||||
|
||||
private void reloadGridAndUpdateBottomNumbers(object _ = null, object __ = null)
|
||||
{
|
||||
// suppressed filter while init'ing UI
|
||||
var prev_isProcessingGridSelect = isProcessingGridSelect;
|
||||
isProcessingGridSelect = true;
|
||||
this.UIThreadSync(setGrid);
|
||||
isProcessingGridSelect = prev_isProcessingGridSelect;
|
||||
|
||||
// UI init complete. now we can apply filter
|
||||
this.UIThreadAsync(() => doFilter(lastGoodFilter));
|
||||
|
||||
setBackupCounts(null, null);
|
||||
}
|
||||
|
||||
#region reload grid
|
||||
private ProductsGrid productsGrid;
|
||||
private void setGrid()
|
||||
{
|
||||
SuspendLayout();
|
||||
{
|
||||
// previous non-null grid with zero-count removes columns. remove/re-add grid to get columns back
|
||||
if (productsGrid?.Count == 0)
|
||||
{
|
||||
gridPanel.Controls.Remove(productsGrid);
|
||||
productsGrid.VisibleCountChanged -= setVisibleCount;
|
||||
productsGrid.Dispose();
|
||||
productsGrid = null;
|
||||
}
|
||||
|
||||
if (productsGrid is null)
|
||||
{
|
||||
productsGrid = new ProductsGrid { Dock = DockStyle.Fill };
|
||||
productsGrid.VisibleCountChanged += setVisibleCount;
|
||||
gridPanel.UIThreadSync(() => gridPanel.Controls.Add(productsGrid));
|
||||
}
|
||||
|
||||
productsGrid.Display();
|
||||
}
|
||||
ResumeLayout();
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region bottom: qty books visible
|
||||
private void setVisibleCount(object _, int qty) => visibleCountLbl.Text = string.Format("Visible: {0}", qty);
|
||||
#endregion
|
||||
|
||||
#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 bool isProcessingGridSelect = false;
|
||||
private string lastGoodFilter = "";
|
||||
private void doFilter(string filterString)
|
||||
{
|
||||
this.filterSearchTb.Text = filterString;
|
||||
doFilter();
|
||||
}
|
||||
private void doFilter()
|
||||
{
|
||||
if (isProcessingGridSelect || productsGrid is null)
|
||||
return;
|
||||
|
||||
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 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)
|
||||
{
|
||||
MessageBoxAlertAdmin.Show(
|
||||
"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)
|
||||
{
|
||||
MessageBoxAlertAdmin.Show("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 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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -57,12 +57,48 @@
|
||||
<resheader name="writer">
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<metadata name="filterHelpBtn.Locked" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
|
||||
<value>True</value>
|
||||
</metadata>
|
||||
<metadata name="filterBtn.Locked" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
|
||||
<value>True</value>
|
||||
</metadata>
|
||||
<metadata name="filterSearchTb.Locked" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
|
||||
<value>True</value>
|
||||
</metadata>
|
||||
<metadata name="menuStrip1.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
|
||||
<value>17, 17</value>
|
||||
</metadata>
|
||||
<metadata name="menuStrip1.Locked" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
|
||||
<value>True</value>
|
||||
</metadata>
|
||||
<metadata name="statusStrip1.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
|
||||
<value>132, 17</value>
|
||||
</metadata>
|
||||
<metadata name="statusStrip1.Locked" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
|
||||
<value>True</value>
|
||||
</metadata>
|
||||
<metadata name="addQuickFilterBtn.Locked" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
|
||||
<value>True</value>
|
||||
</metadata>
|
||||
<metadata name="splitContainer1.Locked" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
|
||||
<value>True</value>
|
||||
</metadata>
|
||||
<metadata name="panel1.Locked" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
|
||||
<value>True</value>
|
||||
</metadata>
|
||||
<metadata name="productsDisplay.Locked" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
|
||||
<value>True</value>
|
||||
</metadata>
|
||||
<metadata name="toggleQueueHideBtn.Locked" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
|
||||
<value>True</value>
|
||||
</metadata>
|
||||
<metadata name="processBookQueue1.Locked" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
|
||||
<value>True</value>
|
||||
</metadata>
|
||||
<metadata name="$this.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="$this.Icon" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
||||
<value>
|
||||
|
||||
@@ -7,6 +7,18 @@ namespace LibationWinForms
|
||||
{
|
||||
public static class FormSaveExtension
|
||||
{
|
||||
static readonly Icon libationIcon;
|
||||
static FormSaveExtension()
|
||||
{
|
||||
var resources = new System.ComponentModel.ComponentResourceManager(typeof(Form1));
|
||||
libationIcon = (Icon)resources.GetObject("$this.Icon");
|
||||
}
|
||||
|
||||
public static void SetLibationIcon(this Form form)
|
||||
{
|
||||
form.Icon = libationIcon;
|
||||
}
|
||||
|
||||
public static void RestoreSizeAndLocation(this Form form, Configuration config)
|
||||
{
|
||||
FormSizeAndPosition savedState = config.GetNonString<FormSizeAndPosition>(form.Name);
|
||||
@@ -77,6 +89,7 @@ namespace LibationWinForms
|
||||
|
||||
config.SetObject(form.Name, saveState);
|
||||
}
|
||||
|
||||
}
|
||||
class FormSizeAndPosition
|
||||
{
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -2,12 +2,15 @@
|
||||
using System.ComponentModel;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace LibationWinForms
|
||||
namespace LibationWinForms.GridView
|
||||
{
|
||||
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)));
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
using System.Drawing;
|
||||
using System.Windows.Forms;
|
||||
|
||||
namespace LibationWinForms
|
||||
namespace LibationWinForms.GridView
|
||||
{
|
||||
public class DataGridViewImageButtonCell : DataGridViewButtonCell
|
||||
{
|
||||
69
Source/LibationWinForms/GridView/DescriptionDisplay.Designer.cs
generated
Normal file
69
Source/LibationWinForms/GridView/DescriptionDisplay.Designer.cs
generated
Normal file
@@ -0,0 +1,69 @@
|
||||
namespace LibationWinForms.GridView
|
||||
{
|
||||
partial class DescriptionDisplay
|
||||
{
|
||||
/// <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.textBox1 = new System.Windows.Forms.TextBox();
|
||||
this.SuspendLayout();
|
||||
//
|
||||
// textBox1
|
||||
//
|
||||
this.textBox1.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.textBox1.BackColor = System.Drawing.SystemColors.ControlLightLight;
|
||||
this.textBox1.Location = new System.Drawing.Point(5, 5);
|
||||
this.textBox1.Multiline = true;
|
||||
this.textBox1.Name = "textBox1";
|
||||
this.textBox1.ReadOnly = true;
|
||||
this.textBox1.Size = new System.Drawing.Size(540, 140);
|
||||
this.textBox1.TabIndex = 0;
|
||||
//
|
||||
// DescriptionDisplay
|
||||
//
|
||||
this.AutoScaleDimensions = new System.Drawing.SizeF(7F, 15F);
|
||||
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
|
||||
this.BackColor = System.Drawing.SystemColors.Highlight;
|
||||
this.ClientSize = new System.Drawing.Size(550, 150);
|
||||
this.Controls.Add(this.textBox1);
|
||||
this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.None;
|
||||
this.MaximizeBox = false;
|
||||
this.MinimizeBox = false;
|
||||
this.Name = "DescriptionDisplay";
|
||||
this.ShowInTaskbar = false;
|
||||
this.Text = "DescriptionDisplay";
|
||||
this.ResumeLayout(false);
|
||||
this.PerformLayout();
|
||||
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
private System.Windows.Forms.TextBox textBox1;
|
||||
}
|
||||
}
|
||||
49
Source/LibationWinForms/GridView/DescriptionDisplay.cs
Normal file
49
Source/LibationWinForms/GridView/DescriptionDisplay.cs
Normal file
@@ -0,0 +1,49 @@
|
||||
using System;
|
||||
using System.Drawing;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Windows.Forms;
|
||||
|
||||
namespace LibationWinForms.GridView
|
||||
{
|
||||
public partial class DescriptionDisplay : Form
|
||||
{
|
||||
private int borderThickness = 5;
|
||||
|
||||
public int BorderThickness
|
||||
{
|
||||
get => borderThickness;
|
||||
set
|
||||
{
|
||||
borderThickness = value;
|
||||
textBox1.Location = new Point(borderThickness, borderThickness);
|
||||
textBox1.Size = new Size(Width - 2 * borderThickness, Height - 2 * borderThickness);
|
||||
}
|
||||
}
|
||||
public string DescriptionText { get => textBox1.Text; set => textBox1.Text = value; }
|
||||
public Point SpawnLocation { get; set; }
|
||||
public DescriptionDisplay()
|
||||
{
|
||||
InitializeComponent();
|
||||
textBox1.LostFocus += (_, _) => Close();
|
||||
Shown += DescriptionDisplay_Shown;
|
||||
}
|
||||
|
||||
private void DescriptionDisplay_Shown(object sender, EventArgs e)
|
||||
{
|
||||
textBox1.DeselectAll();
|
||||
HideCaret(textBox1.Handle);
|
||||
}
|
||||
|
||||
protected override void OnLoad(EventArgs e)
|
||||
{
|
||||
base.OnLoad(e);
|
||||
int lineCount = textBox1.GetLineFromCharIndex(int.MaxValue) + 2;
|
||||
Height = Height - textBox1.Height + lineCount * TextRenderer.MeasureText("X", textBox1.Font).Height;
|
||||
|
||||
Location = new Point(SpawnLocation.X, Math.Min(SpawnLocation.Y, Screen.PrimaryScreen.WorkingArea.Height - Height));
|
||||
}
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
static extern bool HideCaret(IntPtr hWnd);
|
||||
}
|
||||
}
|
||||
@@ -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">
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user