Compare commits
24 Commits
v3.1-beta.
...
v3.1.2
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9abb9e376d | ||
|
|
f93498bfe3 | ||
|
|
a13e1f27bb | ||
|
|
c7c1b4505b | ||
|
|
d9e0f1aedf | ||
|
|
d8a0124b68 | ||
|
|
79e0a8fba7 | ||
|
|
8497987967 | ||
|
|
717fefd2c0 | ||
|
|
066cae8e33 | ||
|
|
9083574a77 | ||
|
|
6b1ab9c777 | ||
|
|
3be7c87c8e | ||
|
|
8694d3206b | ||
|
|
f67f3805c6 | ||
|
|
612dd41b4b | ||
|
|
13378a482d | ||
|
|
352b498c23 | ||
|
|
3a652cfb70 | ||
|
|
93e9ce31ba | ||
|
|
69ed7767b2 | ||
|
|
6fcaa8d551 | ||
|
|
15ece43463 | ||
|
|
25f5f0ed14 |
@@ -293,14 +293,14 @@ namespace AaxDecrypter
|
||||
|
||||
public bool Step3_Chapterize()
|
||||
{
|
||||
string str1 = "";
|
||||
var str1 = "";
|
||||
if (chapters.FirstChapterStart != 0.0)
|
||||
{
|
||||
str1 = " -ss " + chapters.FirstChapterStart.ToString("0.000", CultureInfo.InvariantCulture) + " -t " + (chapters.LastChapterStart - 1.0).ToString("0.000", CultureInfo.InvariantCulture) + " ";
|
||||
}
|
||||
|
||||
string ffmpegTags = tags.GenerateFfmpegTags();
|
||||
string ffmpegChapters = chapters.GenerateFfmpegChapters();
|
||||
var ffmpegTags = tags.GenerateFfmpegTags();
|
||||
var ffmpegChapters = chapters.GenerateFfmpegChapters();
|
||||
File.WriteAllText(ff_txt_file, ffmpegTags + ffmpegChapters);
|
||||
|
||||
var tagAndChapterInfo = new ProcessStartInfo
|
||||
|
||||
@@ -17,10 +17,10 @@ namespace AaxDecrypter
|
||||
|
||||
public Chapters(string file, double totalTime)
|
||||
{
|
||||
this.markers = getAAXChapters(file);
|
||||
markers = getAAXChapters(file);
|
||||
|
||||
// add end time
|
||||
this.markers.Add(totalTime);
|
||||
markers.Add(totalTime);
|
||||
}
|
||||
|
||||
private static List<double> getAAXChapters(string file)
|
||||
@@ -42,7 +42,7 @@ namespace AaxDecrypter
|
||||
}
|
||||
|
||||
// subtract 1 b/c end time marker is a real entry but isn't a real chapter
|
||||
public int Count() => this.markers.Count - 1;
|
||||
public int Count() => markers.Count - 1;
|
||||
|
||||
public string GetCuefromChapters(string fileName)
|
||||
{
|
||||
@@ -56,7 +56,7 @@ namespace AaxDecrypter
|
||||
{
|
||||
var chapter = i + 1;
|
||||
|
||||
var timeSpan = TimeSpan.FromSeconds(this.markers[i]);
|
||||
var timeSpan = TimeSpan.FromSeconds(markers[i]);
|
||||
var minutes = Math.Floor(timeSpan.TotalMinutes).ToString();
|
||||
var seconds = timeSpan.Seconds.ToString("D2");
|
||||
var milliseconds = (timeSpan.Milliseconds / 10).ToString("D2");
|
||||
@@ -78,8 +78,8 @@ namespace AaxDecrypter
|
||||
{
|
||||
var chapter = i + 1;
|
||||
|
||||
var start = this.markers[i] * 1000.0;
|
||||
var end = this.markers[i + 1] * 1000.0;
|
||||
var start = markers[i] * 1000.0;
|
||||
var end = markers[i + 1] * 1000.0;
|
||||
var chapterName = chapter.ToString("D3");
|
||||
|
||||
stringBuilder.Append("[CHAPTER]\n");
|
||||
|
||||
@@ -1,9 +1,4 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using TagLib;
|
||||
using TagLib.Mpeg4;
|
||||
using Dinah.Core;
|
||||
@@ -23,52 +18,50 @@ namespace AaxDecrypter
|
||||
public string genre { get; }
|
||||
public TimeSpan duration { get; }
|
||||
|
||||
// input file
|
||||
public Tags(string file)
|
||||
{
|
||||
using TagLib.File tagLibFile = TagLib.File.Create(file, "audio/mp4", ReadStyle.Average);
|
||||
this.title = tagLibFile.Tag.Title.Replace(" (Unabridged)", "");
|
||||
this.album = tagLibFile.Tag.Album.Replace(" (Unabridged)", "");
|
||||
this.author = tagLibFile.Tag.FirstPerformer ?? "[unknown]";
|
||||
this.year = tagLibFile.Tag.Year.ToString();
|
||||
this.comments = tagLibFile.Tag.Comment;
|
||||
this.duration = tagLibFile.Properties.Duration;
|
||||
this.genre = tagLibFile.Tag.FirstGenre;
|
||||
using var tagLibFile = TagLib.File.Create(file, "audio/mp4", ReadStyle.Average);
|
||||
title = tagLibFile.Tag.Title.Replace(" (Unabridged)", "");
|
||||
album = tagLibFile.Tag.Album.Replace(" (Unabridged)", "");
|
||||
author = tagLibFile.Tag.FirstPerformer ?? "[unknown]";
|
||||
year = tagLibFile.Tag.Year.ToString();
|
||||
comments = tagLibFile.Tag.Comment ?? "";
|
||||
duration = tagLibFile.Properties.Duration;
|
||||
genre = tagLibFile.Tag.FirstGenre ?? "";
|
||||
|
||||
var tag = tagLibFile.GetTag(TagTypes.Apple, true);
|
||||
this.publisher = tag.Publisher;
|
||||
this.narrator = string.IsNullOrWhiteSpace(tagLibFile.Tag.FirstComposer) ? tag.Narrator : tagLibFile.Tag.FirstComposer;
|
||||
this.comments = !string.IsNullOrWhiteSpace(tag.LongDescription) ? tag.LongDescription : tag.Description;
|
||||
this.id = tag.AudibleCDEK;
|
||||
var tag = tagLibFile.GetTag(TagTypes.Apple, true);
|
||||
publisher = tag.Publisher ?? "";
|
||||
narrator = string.IsNullOrWhiteSpace(tagLibFile.Tag.FirstComposer) ? tag.Narrator : tagLibFile.Tag.FirstComposer;
|
||||
comments = !string.IsNullOrWhiteSpace(tag.LongDescription) ? tag.LongDescription : tag.Description;
|
||||
id = tag.AudibleCDEK;
|
||||
}
|
||||
|
||||
// my best guess of what this step is doing:
|
||||
// re-publish the data we read from the input file => output file
|
||||
public void AddAppleTags(string file)
|
||||
{
|
||||
using var file1 = TagLib.File.Create(file, "audio/mp4", ReadStyle.Average);
|
||||
var tag = (AppleTag)file1.GetTag(TagTypes.Apple, true);
|
||||
tag.Publisher = this.publisher;
|
||||
tag.LongDescription = this.comments;
|
||||
tag.Description = this.comments;
|
||||
file1.Save();
|
||||
using var tagLibFile = TagLib.File.Create(file, "audio/mp4", ReadStyle.Average);
|
||||
var tag = (AppleTag)tagLibFile.GetTag(TagTypes.Apple, true);
|
||||
tag.Publisher = publisher;
|
||||
tag.LongDescription = comments;
|
||||
tag.Description = comments;
|
||||
tagLibFile.Save();
|
||||
}
|
||||
|
||||
public string GenerateFfmpegTags()
|
||||
{
|
||||
StringBuilder stringBuilder = new StringBuilder();
|
||||
|
||||
stringBuilder.Append(";FFMETADATA1\n");
|
||||
stringBuilder.Append("major_brand=aax\n");
|
||||
stringBuilder.Append("minor_version=1\n");
|
||||
stringBuilder.Append("compatible_brands=aax M4B mp42isom\n");
|
||||
stringBuilder.Append("date=" + this.year + "\n");
|
||||
stringBuilder.Append("genre=" + this.genre + "\n");
|
||||
stringBuilder.Append("title=" + this.title + "\n");
|
||||
stringBuilder.Append("artist=" + this.author + "\n");
|
||||
stringBuilder.Append("album=" + this.album + "\n");
|
||||
stringBuilder.Append("composer=" + this.narrator + "\n");
|
||||
stringBuilder.Append("comment=" + this.comments.Truncate(254) + "\n");
|
||||
stringBuilder.Append("description=" + this.comments + "\n");
|
||||
|
||||
return stringBuilder.ToString();
|
||||
}
|
||||
=> $";FFMETADATA1"
|
||||
+ $"\nmajor_brand=aax"
|
||||
+ $"\nminor_version=1"
|
||||
+ $"\ncompatible_brands=aax M4B mp42isom"
|
||||
+ $"\ndate={year}"
|
||||
+ $"\ngenre={genre}"
|
||||
+ $"\ntitle={title}"
|
||||
+ $"\nartist={author}"
|
||||
+ $"\nalbum={album}"
|
||||
+ $"\ncomposer={narrator}"
|
||||
+ $"\ncomment={comments.Truncate(254)}"
|
||||
+ $"\ndescription={comments}"
|
||||
+ $"\n";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
using System.IO;
|
||||
using System.Threading.Tasks;
|
||||
using DataLayer;
|
||||
using Dinah.Core;
|
||||
using Dinah.Core.ErrorHandling;
|
||||
using FileManager;
|
||||
|
||||
@@ -44,11 +45,17 @@ namespace FileLiberator
|
||||
tempAaxFilename,
|
||||
(p) => api.DownloadAaxWorkaroundAsync(libraryBook.Book.AudibleProductId, tempAaxFilename, p));
|
||||
|
||||
// if bad file download, a 0-33 byte file will be created
|
||||
System.Threading.Thread.Sleep(100);
|
||||
// if bad file download, a 0-33 byte file will be created
|
||||
// if service unavailable, a 52 byte string will be saved as file
|
||||
if (new FileInfo(actualFilePath).Length < 100)
|
||||
{
|
||||
var contents = File.ReadAllText(actualFilePath);
|
||||
File.Delete(actualFilePath);
|
||||
|
||||
var unavailable = "Content Delivery Companion Service is not available.";
|
||||
if (contents.StartsWithInsensitive(unavailable))
|
||||
throw new Exception(unavailable);
|
||||
throw new Exception("Error downloading file");
|
||||
}
|
||||
|
||||
|
||||
35
FileLiberator/UNTESTED/DownloadFile.cs
Normal file
@@ -0,0 +1,35 @@
|
||||
using System;
|
||||
using System.Net.Http;
|
||||
using System.Threading.Tasks;
|
||||
using Dinah.Core.Net.Http;
|
||||
|
||||
namespace FileLiberator
|
||||
{
|
||||
// frustratingly copy pasta from DownloadableBase and DownloadPdf
|
||||
public class DownloadFile : IDownloadable
|
||||
{
|
||||
public event EventHandler<string> DownloadBegin;
|
||||
public event EventHandler<DownloadProgress> DownloadProgressChanged;
|
||||
public event EventHandler<string> DownloadCompleted;
|
||||
|
||||
public async Task<string> PerformDownloadFileAsync(string downloadUrl, string proposedDownloadFilePath)
|
||||
{
|
||||
var client = new HttpClient();
|
||||
|
||||
var progress = new Progress<DownloadProgress>();
|
||||
progress.ProgressChanged += (_, e) => DownloadProgressChanged?.Invoke(this, e);
|
||||
|
||||
DownloadBegin?.Invoke(this, proposedDownloadFilePath);
|
||||
|
||||
try
|
||||
{
|
||||
var actualDownloadedFilePath = await client.DownloadFileAsync(downloadUrl, proposedDownloadFilePath, progress);
|
||||
return actualDownloadedFilePath;
|
||||
}
|
||||
finally
|
||||
{
|
||||
DownloadCompleted?.Invoke(this, proposedDownloadFilePath);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -16,9 +16,6 @@ namespace FileLiberator
|
||||
=> !string.IsNullOrWhiteSpace(getdownloadUrl(libraryBook))
|
||||
&& !AudibleFileStorage.PDF.Exists(libraryBook.Book.AudibleProductId);
|
||||
|
||||
private static string getdownloadUrl(LibraryBook libraryBook)
|
||||
=> libraryBook?.Book?.Supplements?.FirstOrDefault()?.Url;
|
||||
|
||||
public override async Task<StatusHandler> ProcessItemAsync(LibraryBook libraryBook)
|
||||
{
|
||||
var proposedDownloadFilePath = getProposedDownloadFilePath(libraryBook);
|
||||
@@ -26,6 +23,11 @@ namespace FileLiberator
|
||||
return verifyDownload(libraryBook);
|
||||
}
|
||||
|
||||
private static StatusHandler verifyDownload(LibraryBook libraryBook)
|
||||
=> !AudibleFileStorage.PDF.Exists(libraryBook.Book.AudibleProductId)
|
||||
? new StatusHandler { "Downloaded PDF cannot be found" }
|
||||
: new StatusHandler();
|
||||
|
||||
private static string getProposedDownloadFilePath(LibraryBook libraryBook)
|
||||
{
|
||||
// if audio file exists, get it's dir. else return base Book dir
|
||||
@@ -39,15 +41,15 @@ namespace FileLiberator
|
||||
|
||||
private async Task downloadPdfAsync(LibraryBook libraryBook, string proposedDownloadFilePath)
|
||||
{
|
||||
var downloadUrl = getdownloadUrl(libraryBook);
|
||||
|
||||
var client = new HttpClient();
|
||||
var actualDownloadedFilePath = await PerformDownloadAsync(
|
||||
proposedDownloadFilePath,
|
||||
(p) => client.DownloadFileAsync(getdownloadUrl(libraryBook), proposedDownloadFilePath, p));
|
||||
(p) => client.DownloadFileAsync(downloadUrl, proposedDownloadFilePath, p));
|
||||
}
|
||||
|
||||
private static StatusHandler verifyDownload(LibraryBook libraryBook)
|
||||
=> !AudibleFileStorage.PDF.Exists(libraryBook.Book.AudibleProductId)
|
||||
? new StatusHandler { "Downloaded PDF cannot be found" }
|
||||
: new StatusHandler();
|
||||
private static string getdownloadUrl(LibraryBook libraryBook)
|
||||
=> libraryBook?.Book?.Supplements?.FirstOrDefault()?.Url;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ using Dinah.Core.Net.Http;
|
||||
|
||||
namespace FileLiberator
|
||||
{
|
||||
public abstract class DownloadableBase : IDownloadable
|
||||
public abstract class DownloadableBase : IDownloadableProcessable
|
||||
{
|
||||
public event EventHandler<LibraryBook> Begin;
|
||||
public event EventHandler<LibraryBook> Completed;
|
||||
|
||||
@@ -3,7 +3,7 @@ using Dinah.Core.Net.Http;
|
||||
|
||||
namespace FileLiberator
|
||||
{
|
||||
public interface IDownloadable : IProcessable
|
||||
public interface IDownloadable
|
||||
{
|
||||
event EventHandler<string> DownloadBegin;
|
||||
event EventHandler<DownloadProgress> DownloadProgressChanged;
|
||||
|
||||
4
FileLiberator/UNTESTED/IDownloadableProcessable.cs
Normal file
@@ -0,0 +1,4 @@
|
||||
namespace FileLiberator
|
||||
{
|
||||
public interface IDownloadableProcessable : IDownloadable, IProcessable { }
|
||||
}
|
||||
@@ -64,7 +64,34 @@ namespace FileManager
|
||||
// set cache
|
||||
stringCache[propertyName] = newValue;
|
||||
|
||||
// set in file
|
||||
writeFile(propertyName, newValue);
|
||||
}
|
||||
|
||||
public void Set(string propertyName, object newValue)
|
||||
{
|
||||
// set cache
|
||||
objectCache[propertyName] = newValue;
|
||||
|
||||
var parsedNewValue = JToken.Parse(JsonConvert.SerializeObject(newValue));
|
||||
writeFile(propertyName, parsedNewValue);
|
||||
}
|
||||
|
||||
private void writeFile(string propertyName, JToken newValue)
|
||||
{
|
||||
try
|
||||
{
|
||||
var str = newValue?.ToString();
|
||||
var formattedValue
|
||||
= str is null ? "[null]"
|
||||
: string.IsNullOrEmpty(str) ? "[empty]"
|
||||
: string.IsNullOrWhiteSpace(str) ? $"[whitespace. Length={str.Length}]"
|
||||
: str.Length > 100 ? $"[Length={str.Length}] {str[0..50]}...{str[^50..^0]}"
|
||||
: str;
|
||||
Serilog.Log.Logger.Information($"Config changed. {propertyName}={formattedValue}");
|
||||
}
|
||||
catch { }
|
||||
|
||||
// write new setting to file
|
||||
lock (locker)
|
||||
{
|
||||
var jObject = readFile();
|
||||
@@ -73,20 +100,7 @@ namespace FileManager
|
||||
}
|
||||
}
|
||||
|
||||
public void Set(string propertyName, object newValue)
|
||||
{
|
||||
// set cache
|
||||
objectCache[propertyName] = newValue;
|
||||
|
||||
// set in file
|
||||
lock (locker)
|
||||
{
|
||||
var jObject = readFile();
|
||||
jObject[propertyName] = JToken.Parse(JsonConvert.SerializeObject(newValue));
|
||||
File.WriteAllText(Filepath, JsonConvert.SerializeObject(jObject, Formatting.Indented));
|
||||
}
|
||||
}
|
||||
|
||||
// special case: no caching. no logging
|
||||
public void SetWithJsonPath(string jsonPath, string propertyName, string newValue)
|
||||
{
|
||||
lock (locker)
|
||||
@@ -94,6 +108,7 @@ namespace FileManager
|
||||
var jObject = readFile();
|
||||
var token = jObject.SelectToken(jsonPath);
|
||||
var debug_oldValue = (string)token[propertyName];
|
||||
|
||||
token[propertyName] = newValue;
|
||||
File.WriteAllText(Filepath, JsonConvert.SerializeObject(jObject, Formatting.Indented));
|
||||
}
|
||||
|
||||
4
LibationLauncher/.msbump
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"//": "https://github.com/BalassaMarton/MSBump",
|
||||
BumpRevision: true
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk.WindowsDesktop">
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project Sdk="Microsoft.NET.Sdk.WindowsDesktop">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>WinExe</OutputType>
|
||||
@@ -11,8 +12,18 @@
|
||||
<PublishReadyToRun>true</PublishReadyToRun>
|
||||
<!-- <PublishSingleFile>true</PublishSingleFile> -->
|
||||
<RuntimeIdentifier>win-x64</RuntimeIdentifier>
|
||||
|
||||
<Version>3.1.2.0</Version>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="MSBump" Version="2.3.2">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Octokit" Version="0.36.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\LibationWinForms\LibationWinForms.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Windows.Forms;
|
||||
using Dinah.Core.Logging;
|
||||
using FileManager;
|
||||
using LibationWinForms;
|
||||
using LibationWinForms.Dialogs;
|
||||
@@ -21,7 +21,12 @@ namespace LibationLauncher
|
||||
Application.SetCompatibleTextRenderingDefault(false);
|
||||
|
||||
createSettings();
|
||||
initLogging();
|
||||
|
||||
ensureLoggingConfig();
|
||||
ensureSerilogConfig();
|
||||
configureLogging();
|
||||
checkForUpdate();
|
||||
logStartupState();
|
||||
|
||||
Application.Run(new Form1());
|
||||
}
|
||||
@@ -71,47 +76,11 @@ namespace LibationLauncher
|
||||
&& !string.IsNullOrWhiteSpace(config.DownloadsInProgressEnum)
|
||||
&& !string.IsNullOrWhiteSpace(config.DecryptInProgressEnum);
|
||||
|
||||
private static void initLogging()
|
||||
private static string defaultLoggingLevel { get; } = "Information";
|
||||
private static void ensureLoggingConfig()
|
||||
{
|
||||
var config = Configuration.Instance;
|
||||
|
||||
ensureLoggingConfig(config);
|
||||
ensureSerilogConfig(config);
|
||||
|
||||
// override path. always use current libation files
|
||||
var logPath = Path.Combine(Configuration.Instance.LibationFiles, "Log.log");
|
||||
config.SetWithJsonPath("Serilog.WriteTo[1].Args", "path", logPath);
|
||||
|
||||
//// hack which achieves the same
|
||||
//configuration["Serilog:WriteTo:1:Args:path"] = logPath;
|
||||
|
||||
// CONFIGURATION-DRIVEN (json)
|
||||
var configuration = new ConfigurationBuilder()
|
||||
.AddJsonFile(config.SettingsJsonPath)
|
||||
.Build();
|
||||
Log.Logger = new LoggerConfiguration()
|
||||
.ReadFrom.Configuration(configuration)
|
||||
.CreateLogger();
|
||||
|
||||
//// MANUAL HARD CODED
|
||||
//Log.Logger = new LoggerConfiguration()
|
||||
// .Enrich.WithCaller()
|
||||
// .MinimumLevel.Information()
|
||||
// .WriteTo.File(logPath,
|
||||
// rollingInterval: RollingInterval.Month,
|
||||
// outputTemplate: code_outputTemplate)
|
||||
// .CreateLogger();
|
||||
|
||||
Log.Logger.Information("Begin Libation");
|
||||
|
||||
// .Here() captures debug info via System.Runtime.CompilerServices attributes. Warning: expensive
|
||||
//var withLineNumbers_outputTemplate = "[{Timestamp:HH:mm:ss} {Level}] {SourceContext}{NewLine}{Message}{NewLine}in method {MemberName} at {FilePath}:{LineNumber}{NewLine}{Exception}{NewLine}";
|
||||
//Log.Logger.Here().Debug("Begin Libation. Debug with line numbers");
|
||||
}
|
||||
|
||||
private static string defaultLoggingLevel { get; } = "Information";
|
||||
private static void ensureLoggingConfig(Configuration config)
|
||||
{
|
||||
if (config.GetObject("Logging") != null)
|
||||
return;
|
||||
|
||||
@@ -129,8 +98,10 @@ namespace LibationLauncher
|
||||
config.SetObject("Logging", loggingObj);
|
||||
}
|
||||
|
||||
private static void ensureSerilogConfig(Configuration config)
|
||||
private static void ensureSerilogConfig()
|
||||
{
|
||||
var config = Configuration.Instance;
|
||||
|
||||
if (config.GetObject("Serilog") != null)
|
||||
return;
|
||||
|
||||
@@ -184,5 +155,101 @@ namespace LibationLauncher
|
||||
};
|
||||
config.SetObject("Serilog", serilogObj);
|
||||
}
|
||||
|
||||
private static void configureLogging()
|
||||
{
|
||||
var config = Configuration.Instance;
|
||||
|
||||
// override path. always use current libation files
|
||||
var logPath = Path.Combine(Configuration.Instance.LibationFiles, "Log.log");
|
||||
config.SetWithJsonPath("Serilog.WriteTo[1].Args", "path", logPath);
|
||||
|
||||
//// hack which achieves the same
|
||||
//configuration["Serilog:WriteTo:1:Args:path"] = logPath;
|
||||
|
||||
// CONFIGURATION-DRIVEN (json)
|
||||
var configuration = new ConfigurationBuilder()
|
||||
.AddJsonFile(config.SettingsJsonPath)
|
||||
.Build();
|
||||
Log.Logger = new LoggerConfiguration()
|
||||
.ReadFrom.Configuration(configuration)
|
||||
.CreateLogger();
|
||||
|
||||
//// MANUAL HARD CODED
|
||||
//Log.Logger = new LoggerConfiguration()
|
||||
// // requires: using Dinah.Core.Logging;
|
||||
// .Enrich.WithCaller()
|
||||
// .MinimumLevel.Information()
|
||||
// .WriteTo.File(logPath,
|
||||
// rollingInterval: RollingInterval.Month,
|
||||
// outputTemplate: code_outputTemplate)
|
||||
// .CreateLogger();
|
||||
|
||||
// .Here() captures debug info via System.Runtime.CompilerServices attributes. Warning: expensive
|
||||
//var withLineNumbers_outputTemplate = "[{Timestamp:HH:mm:ss} {Level}] {SourceContext}{NewLine}{Message}{NewLine}in method {MemberName} at {FilePath}:{LineNumber}{NewLine}{Exception}{NewLine}";
|
||||
//Log.Logger.Here().Debug("Begin Libation. Debug with line numbers");
|
||||
}
|
||||
|
||||
private static void checkForUpdate()
|
||||
{
|
||||
try
|
||||
{
|
||||
var gitHubClient = new Octokit.GitHubClient(new Octokit.ProductHeaderValue("Libation"));
|
||||
|
||||
// https://octokitnet.readthedocs.io/en/latest/releases/
|
||||
var releases = gitHubClient.Repository.Release.GetAll("rmcrackan", "Libation").GetAwaiter().GetResult();
|
||||
var latest = releases.First(r => !r.Draft);
|
||||
|
||||
var latestVersionString = latest.TagName.Trim('v');
|
||||
if (!Version.TryParse(latestVersionString, out var latestRelease))
|
||||
return;
|
||||
|
||||
// we're up to date
|
||||
if (latestRelease <= BuildVersion)
|
||||
return;
|
||||
|
||||
// we have an update
|
||||
var zip = latest.Assets.FirstOrDefault(a => a.BrowserDownloadUrl.EndsWith(".zip"));
|
||||
var zipUrl = zip?.BrowserDownloadUrl;
|
||||
if (zipUrl is null)
|
||||
{
|
||||
MessageBox.Show(latest.HtmlUrl, "New version available");
|
||||
return;
|
||||
}
|
||||
|
||||
var result = MessageBox.Show($"New version available @ {latest.HtmlUrl}\r\nDownload the zip file?", "New version available", MessageBoxButtons.YesNo, MessageBoxIcon.Information);
|
||||
if (result != DialogResult.Yes)
|
||||
return;
|
||||
|
||||
using var fileSelector = new SaveFileDialog { FileName = zip.Name, Filter = "Zip Files (*.zip)|*.zip|All files (*.*)|*.*" };
|
||||
if (fileSelector.ShowDialog() != DialogResult.OK)
|
||||
return;
|
||||
var selectedPath = fileSelector.FileName;
|
||||
|
||||
try
|
||||
{
|
||||
LibationWinForms.BookLiberation.ProcessorAutomationController.DownloadFileAsync(zipUrl, selectedPath).GetAwaiter().GetResult();
|
||||
MessageBox.Show($"File downloaded");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
MessageBox.Show($"ERROR: {ex.Message}\r\n{ex.StackTrace}");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
MessageBox.Show($"Error checking for update. ERROR: {ex.Message}\r\n{ex.StackTrace}");
|
||||
}
|
||||
}
|
||||
|
||||
private static void logStartupState()
|
||||
{
|
||||
Log.Logger.Information("Begin Libation");
|
||||
Log.Logger.Information($"Version: {BuildVersion}");
|
||||
Log.Logger.Information($"LibationFiles: {Configuration.Instance.LibationFiles}");
|
||||
Log.Logger.Information($"Audible locale: {Configuration.Instance.LocaleCountryCode}");
|
||||
}
|
||||
|
||||
private static Version BuildVersion => System.Reflection.Assembly.GetExecutingAssembly().GetName().Version;
|
||||
}
|
||||
}
|
||||
|
Before Width: | Height: | Size: 92 KiB After Width: | Height: | Size: 100 KiB |
90
LibationWinForms/Properties/Resources.Designer.cs
generated
@@ -109,5 +109,95 @@ namespace LibationWinForms.Properties {
|
||||
return ((System.Drawing.Bitmap)(obj));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized resource of type System.Drawing.Bitmap.
|
||||
/// </summary>
|
||||
internal static System.Drawing.Bitmap liberate_green {
|
||||
get {
|
||||
object obj = ResourceManager.GetObject("liberate_green", resourceCulture);
|
||||
return ((System.Drawing.Bitmap)(obj));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized resource of type System.Drawing.Bitmap.
|
||||
/// </summary>
|
||||
internal static System.Drawing.Bitmap liberate_green_pdf_no {
|
||||
get {
|
||||
object obj = ResourceManager.GetObject("liberate_green_pdf_no", resourceCulture);
|
||||
return ((System.Drawing.Bitmap)(obj));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized resource of type System.Drawing.Bitmap.
|
||||
/// </summary>
|
||||
internal static System.Drawing.Bitmap liberate_green_pdf_yes {
|
||||
get {
|
||||
object obj = ResourceManager.GetObject("liberate_green_pdf_yes", resourceCulture);
|
||||
return ((System.Drawing.Bitmap)(obj));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized resource of type System.Drawing.Bitmap.
|
||||
/// </summary>
|
||||
internal static System.Drawing.Bitmap liberate_red {
|
||||
get {
|
||||
object obj = ResourceManager.GetObject("liberate_red", resourceCulture);
|
||||
return ((System.Drawing.Bitmap)(obj));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized resource of type System.Drawing.Bitmap.
|
||||
/// </summary>
|
||||
internal static System.Drawing.Bitmap liberate_red_pdf_no {
|
||||
get {
|
||||
object obj = ResourceManager.GetObject("liberate_red_pdf_no", resourceCulture);
|
||||
return ((System.Drawing.Bitmap)(obj));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized resource of type System.Drawing.Bitmap.
|
||||
/// </summary>
|
||||
internal static System.Drawing.Bitmap liberate_red_pdf_yes {
|
||||
get {
|
||||
object obj = ResourceManager.GetObject("liberate_red_pdf_yes", resourceCulture);
|
||||
return ((System.Drawing.Bitmap)(obj));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized resource of type System.Drawing.Bitmap.
|
||||
/// </summary>
|
||||
internal static System.Drawing.Bitmap liberate_yellow {
|
||||
get {
|
||||
object obj = ResourceManager.GetObject("liberate_yellow", resourceCulture);
|
||||
return ((System.Drawing.Bitmap)(obj));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized resource of type System.Drawing.Bitmap.
|
||||
/// </summary>
|
||||
internal static System.Drawing.Bitmap liberate_yellow_pdf_no {
|
||||
get {
|
||||
object obj = ResourceManager.GetObject("liberate_yellow_pdf_no", resourceCulture);
|
||||
return ((System.Drawing.Bitmap)(obj));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized resource of type System.Drawing.Bitmap.
|
||||
/// </summary>
|
||||
internal static System.Drawing.Bitmap liberate_yellow_pdf_yes {
|
||||
get {
|
||||
object obj = ResourceManager.GetObject("liberate_yellow_pdf_yes", resourceCulture);
|
||||
return ((System.Drawing.Bitmap)(obj));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -133,4 +133,31 @@
|
||||
<data name="edit_tags_50x50" type="System.Resources.ResXFileRef, System.Windows.Forms">
|
||||
<value>..\Resources\edit-tags-50x50.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
|
||||
</data>
|
||||
<data name="liberate_green" type="System.Resources.ResXFileRef, System.Windows.Forms">
|
||||
<value>..\Resources\liberate_green.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
|
||||
</data>
|
||||
<data name="liberate_green_pdf_no" type="System.Resources.ResXFileRef, System.Windows.Forms">
|
||||
<value>..\Resources\liberate_green_pdf_no.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
|
||||
</data>
|
||||
<data name="liberate_green_pdf_yes" type="System.Resources.ResXFileRef, System.Windows.Forms">
|
||||
<value>..\Resources\liberate_green_pdf_yes.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
|
||||
</data>
|
||||
<data name="liberate_red" type="System.Resources.ResXFileRef, System.Windows.Forms">
|
||||
<value>..\Resources\liberate_red.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
|
||||
</data>
|
||||
<data name="liberate_red_pdf_no" type="System.Resources.ResXFileRef, System.Windows.Forms">
|
||||
<value>..\Resources\liberate_red_pdf_no.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
|
||||
</data>
|
||||
<data name="liberate_red_pdf_yes" type="System.Resources.ResXFileRef, System.Windows.Forms">
|
||||
<value>..\Resources\liberate_red_pdf_yes.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
|
||||
</data>
|
||||
<data name="liberate_yellow" type="System.Resources.ResXFileRef, System.Windows.Forms">
|
||||
<value>..\Resources\liberate_yellow.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
|
||||
</data>
|
||||
<data name="liberate_yellow_pdf_no" type="System.Resources.ResXFileRef, System.Windows.Forms">
|
||||
<value>..\Resources\liberate_yellow_pdf_no.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
|
||||
</data>
|
||||
<data name="liberate_yellow_pdf_yes" type="System.Resources.ResXFileRef, System.Windows.Forms">
|
||||
<value>..\Resources\liberate_yellow_pdf_yes.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
|
||||
</data>
|
||||
</root>
|
||||
BIN
LibationWinForms/Resources/Libation icon/glass-with-glow.ico
Normal file
|
After Width: | Height: | Size: 100 KiB |
BIN
LibationWinForms/Resources/Libation icon/glass-with-glow_128.png
Normal file
|
After Width: | Height: | Size: 7.6 KiB |
BIN
LibationWinForms/Resources/Libation icon/glass-with-glow_16.png
Normal file
|
After Width: | Height: | Size: 482 B |
BIN
LibationWinForms/Resources/Libation icon/glass-with-glow_256.png
Normal file
|
After Width: | Height: | Size: 19 KiB |
BIN
LibationWinForms/Resources/Libation icon/glass-with-glow_32.png
Normal file
|
After Width: | Height: | Size: 1.1 KiB |
BIN
LibationWinForms/Resources/Libation icon/glass-with-glow_512.png
Normal file
|
After Width: | Height: | Size: 6.9 KiB |
BIN
LibationWinForms/Resources/Libation icon/glass-with-glow_64.png
Normal file
|
After Width: | Height: | Size: 2.9 KiB |
@@ -1,6 +1,6 @@
|
||||
how to create the app's icon
|
||||
============================
|
||||
https://www.flaticon.com/free-icon/glass-with-wine_33529#term=wine&page=1&position=59
|
||||
orig from (no longer available) https://www.flaticon.com/free-icon/glass-with-wine_33529
|
||||
get this image, color=black, at each of these sizes: 16,32,64,128,256
|
||||
|
||||
from this answer: https://stackoverflow.com/a/16922387
|
||||
|
||||
BIN
LibationWinForms/Resources/download-arrow.png
Normal file
|
After Width: | Height: | Size: 3.9 KiB |
|
Before Width: | Height: | Size: 92 KiB After Width: | Height: | Size: 100 KiB |
BIN
LibationWinForms/Resources/liberate and pdf icons.pdn
Normal file
BIN
LibationWinForms/Resources/liberate icons.pdn
Normal file
BIN
LibationWinForms/Resources/liberate_green.png
Normal file
|
After Width: | Height: | Size: 1.5 KiB |
BIN
LibationWinForms/Resources/liberate_green_pdf_no.png
Normal file
|
After Width: | Height: | Size: 2.8 KiB |
BIN
LibationWinForms/Resources/liberate_green_pdf_yes.png
Normal file
|
After Width: | Height: | Size: 2.3 KiB |
BIN
LibationWinForms/Resources/liberate_red.png
Normal file
|
After Width: | Height: | Size: 1.6 KiB |
BIN
LibationWinForms/Resources/liberate_red_pdf_no.png
Normal file
|
After Width: | Height: | Size: 2.8 KiB |
BIN
LibationWinForms/Resources/liberate_red_pdf_yes.png
Normal file
|
After Width: | Height: | Size: 2.3 KiB |
BIN
LibationWinForms/Resources/liberate_yellow.png
Normal file
|
After Width: | Height: | Size: 1.6 KiB |
BIN
LibationWinForms/Resources/liberate_yellow_pdf_no.png
Normal file
|
After Width: | Height: | Size: 3.0 KiB |
BIN
LibationWinForms/Resources/liberate_yellow_pdf_yes.png
Normal file
|
After Width: | Height: | Size: 2.3 KiB |
2
LibationWinForms/Resources/pdf source.url
Normal file
@@ -0,0 +1,2 @@
|
||||
[InternetShortcut]
|
||||
URL=https://www.flaticon.com/free-icon/pdf-file-format-symbol_29099
|
||||
2
LibationWinForms/Resources/stoplight source.url
Normal file
@@ -0,0 +1,2 @@
|
||||
[InternetShortcut]
|
||||
URL=https://www.flaticon.com/free-icon/semaphore_55291
|
||||
@@ -33,13 +33,13 @@ namespace LibationWinForms.BookLiberation
|
||||
}
|
||||
|
||||
#region timer
|
||||
Timer timer = new Timer { Interval = 1000 };
|
||||
private Timer timer { get; } = new Timer { Interval = 1000 };
|
||||
private void DownloadForm_Load(object sender, EventArgs e)
|
||||
{
|
||||
timer.Tick += new EventHandler(timer_Tick);
|
||||
timer.Start();
|
||||
}
|
||||
DateTime lastDownloadProgress = DateTime.Now;
|
||||
private DateTime lastDownloadProgress = DateTime.Now;
|
||||
private void timer_Tick(object sender, EventArgs e)
|
||||
{
|
||||
// if no update in the last 30 seconds, display frozen label
|
||||
|
||||
@@ -6,15 +6,28 @@ using FileLiberator;
|
||||
|
||||
namespace LibationWinForms.BookLiberation
|
||||
{
|
||||
// matches a file processor with a form
|
||||
public static class ProcessorAutomationController
|
||||
{
|
||||
//
|
||||
// these utility methods ensure proper wiring
|
||||
// 1) we can't forget to do it
|
||||
// 2) we can't accidentally do it mult times becaues we lost track of complexity
|
||||
//
|
||||
public static BackupBook GetWiredUpBackupBook(EventHandler<LibraryBook> completedAction = null)
|
||||
public static async Task BackupSingleBookAsync(string productId, EventHandler<LibraryBook> completedAction = null)
|
||||
{
|
||||
var backupBook = getWiredUpBackupBook(completedAction);
|
||||
|
||||
var automatedBackupsForm = attachToBackupsForm(backupBook);
|
||||
automatedBackupsForm.KeepGoingVisible = false;
|
||||
|
||||
await runSingleBackupAsync(backupBook, automatedBackupsForm, productId);
|
||||
}
|
||||
|
||||
public static async Task BackupAllBooksAsync(EventHandler<LibraryBook> completedAction = null)
|
||||
{
|
||||
var backupBook = getWiredUpBackupBook(completedAction);
|
||||
|
||||
var automatedBackupsForm = attachToBackupsForm(backupBook);
|
||||
|
||||
await runBackupLoopAsync(backupBook, automatedBackupsForm);
|
||||
}
|
||||
|
||||
private static BackupBook getWiredUpBackupBook(EventHandler<LibraryBook> completedAction)
|
||||
{
|
||||
var backupBook = new BackupBook();
|
||||
|
||||
@@ -32,7 +45,65 @@ namespace LibationWinForms.BookLiberation
|
||||
return backupBook;
|
||||
}
|
||||
|
||||
public static DownloadPdf GetWiredUpDownloadPdf(EventHandler<LibraryBook> completedAction = null)
|
||||
private static AutomatedBackupsForm attachToBackupsForm(BackupBook backupBook)
|
||||
{
|
||||
#region create form
|
||||
var automatedBackupsForm = new AutomatedBackupsForm();
|
||||
#endregion
|
||||
|
||||
#region define how model actions will affect form behavior
|
||||
void downloadBookBegin(object _, LibraryBook libraryBook) => automatedBackupsForm.AppendText($"Download Step, Begin: {libraryBook.Book}");
|
||||
void statusUpdate(object _, string str) => automatedBackupsForm.AppendText("- " + str);
|
||||
void downloadBookCompleted(object _, LibraryBook libraryBook) => automatedBackupsForm.AppendText($"Download Step, Completed: {libraryBook.Book}");
|
||||
void decryptBookBegin(object _, LibraryBook libraryBook) => automatedBackupsForm.AppendText($"Decrypt Step, Begin: {libraryBook.Book}");
|
||||
// extra line after book is completely finished
|
||||
void decryptBookCompleted(object _, LibraryBook libraryBook) => automatedBackupsForm.AppendText($"Decrypt Step, Completed: {libraryBook.Book}{Environment.NewLine}");
|
||||
void downloadPdfBegin(object _, LibraryBook libraryBook) => automatedBackupsForm.AppendText($"PDF Step, Begin: {libraryBook.Book}");
|
||||
// extra line after book is completely finished
|
||||
void downloadPdfCompleted(object _, LibraryBook libraryBook) => automatedBackupsForm.AppendText($"PDF Step, Completed: {libraryBook.Book}{Environment.NewLine}");
|
||||
#endregion
|
||||
|
||||
#region subscribe new form to model's events
|
||||
backupBook.DownloadBook.Begin += downloadBookBegin;
|
||||
backupBook.DownloadBook.StatusUpdate += statusUpdate;
|
||||
backupBook.DownloadBook.Completed += downloadBookCompleted;
|
||||
backupBook.DecryptBook.Begin += decryptBookBegin;
|
||||
backupBook.DecryptBook.StatusUpdate += statusUpdate;
|
||||
backupBook.DecryptBook.Completed += decryptBookCompleted;
|
||||
backupBook.DownloadPdf.Begin += downloadPdfBegin;
|
||||
backupBook.DownloadPdf.StatusUpdate += statusUpdate;
|
||||
backupBook.DownloadPdf.Completed += downloadPdfCompleted;
|
||||
#endregion
|
||||
|
||||
#region when form closes, unsubscribe from model's events
|
||||
// unsubscribe so disposed forms aren't still trying to receive notifications
|
||||
automatedBackupsForm.FormClosing += (_, __) =>
|
||||
{
|
||||
backupBook.DownloadBook.Begin -= downloadBookBegin;
|
||||
backupBook.DownloadBook.StatusUpdate -= statusUpdate;
|
||||
backupBook.DownloadBook.Completed -= downloadBookCompleted;
|
||||
backupBook.DecryptBook.Begin -= decryptBookBegin;
|
||||
backupBook.DecryptBook.StatusUpdate -= statusUpdate;
|
||||
backupBook.DecryptBook.Completed -= decryptBookCompleted;
|
||||
backupBook.DownloadPdf.Begin -= downloadPdfBegin;
|
||||
backupBook.DownloadPdf.StatusUpdate -= statusUpdate;
|
||||
backupBook.DownloadPdf.Completed -= downloadPdfCompleted;
|
||||
};
|
||||
#endregion
|
||||
|
||||
return automatedBackupsForm;
|
||||
}
|
||||
|
||||
public static async Task BackupAllPdfsAsync(EventHandler<LibraryBook> completedAction = null)
|
||||
{
|
||||
var downloadPdf = getWiredUpDownloadPdf(completedAction);
|
||||
|
||||
var automatedBackupsForm = attachToBackupsForm(downloadPdf);
|
||||
|
||||
await runBackupLoopAsync(downloadPdf, automatedBackupsForm);
|
||||
}
|
||||
|
||||
private static DownloadPdf getWiredUpDownloadPdf(EventHandler<LibraryBook> completedAction)
|
||||
{
|
||||
var downloadPdf = new DownloadPdf();
|
||||
|
||||
@@ -44,8 +115,25 @@ namespace LibationWinForms.BookLiberation
|
||||
return downloadPdf;
|
||||
}
|
||||
|
||||
public static async Task DownloadFileAsync(string url, string destination)
|
||||
{
|
||||
var downloadFile = new DownloadFile();
|
||||
|
||||
// frustratingly copy pasta from wireUpEvents(IDownloadable downloadable) due to Completed being EventHandler<LibraryBook>
|
||||
var downloadDialog = new DownloadForm();
|
||||
downloadFile.DownloadBegin += (_, str) =>
|
||||
{
|
||||
downloadDialog.UpdateFilename(str);
|
||||
downloadDialog.Show();
|
||||
};
|
||||
downloadFile.DownloadProgressChanged += (_, progress) => downloadDialog.DownloadProgressChanged(progress.BytesReceived, progress.TotalBytesToReceive.Value);
|
||||
downloadFile.DownloadCompleted += (_, __) => downloadDialog.Close();
|
||||
|
||||
await downloadFile.PerformDownloadFileAsync(url, destination);
|
||||
}
|
||||
|
||||
// subscribed to Begin event because a new form should be created+processed+closed on each iteration
|
||||
private static void wireUpEvents(IDownloadable downloadable)
|
||||
private static void wireUpEvents(IDownloadableProcessable downloadable)
|
||||
{
|
||||
#region create form
|
||||
var downloadDialog = new DownloadForm();
|
||||
@@ -156,13 +244,7 @@ namespace LibationWinForms.BookLiberation
|
||||
#endregion
|
||||
}
|
||||
|
||||
public static async Task RunAutomaticDownloadAsync(IDownloadable downloadable)
|
||||
{
|
||||
AutomatedBackupsForm automatedBackupsForm = attachToBackupsForm(downloadable);
|
||||
await runBackupLoopAsync(downloadable, automatedBackupsForm);
|
||||
}
|
||||
|
||||
private static AutomatedBackupsForm attachToBackupsForm(IDownloadable downloadable)
|
||||
private static AutomatedBackupsForm attachToBackupsForm(IDownloadableProcessable downloadable)
|
||||
{
|
||||
#region create form
|
||||
var automatedBackupsForm = new AutomatedBackupsForm();
|
||||
@@ -194,68 +276,6 @@ namespace LibationWinForms.BookLiberation
|
||||
return automatedBackupsForm;
|
||||
}
|
||||
|
||||
public static async Task RunAutomaticBackupAsync(BackupBook backupBook)
|
||||
{
|
||||
var automatedBackupsForm = attachToBackupsForm(backupBook);
|
||||
await runBackupLoopAsync(backupBook, automatedBackupsForm);
|
||||
}
|
||||
|
||||
public static async Task RunSingleBackupAsync(BackupBook backupBook, string productId)
|
||||
{
|
||||
var automatedBackupsForm = attachToBackupsForm(backupBook);
|
||||
automatedBackupsForm.KeepGoingVisible = false;
|
||||
await runSingleBackupAsync(backupBook, automatedBackupsForm, productId);
|
||||
}
|
||||
|
||||
private static AutomatedBackupsForm attachToBackupsForm(BackupBook backupBook)
|
||||
{
|
||||
#region create form
|
||||
var automatedBackupsForm = new AutomatedBackupsForm();
|
||||
#endregion
|
||||
|
||||
#region define how model actions will affect form behavior
|
||||
void downloadBookBegin(object _, LibraryBook libraryBook) => automatedBackupsForm.AppendText($"Download Step, Begin: {libraryBook.Book}");
|
||||
void statusUpdate(object _, string str) => automatedBackupsForm.AppendText("- " + str);
|
||||
void downloadBookCompleted(object _, LibraryBook libraryBook) => automatedBackupsForm.AppendText($"Download Step, Completed: {libraryBook.Book}");
|
||||
void decryptBookBegin(object _, LibraryBook libraryBook) => automatedBackupsForm.AppendText($"Decrypt Step, Begin: {libraryBook.Book}");
|
||||
// extra line after book is completely finished
|
||||
void decryptBookCompleted(object _, LibraryBook libraryBook) => automatedBackupsForm.AppendText($"Decrypt Step, Completed: {libraryBook.Book}{Environment.NewLine}");
|
||||
void downloadPdfBegin(object _, LibraryBook libraryBook) => automatedBackupsForm.AppendText($"PDF Step, Begin: {libraryBook.Book}");
|
||||
// extra line after book is completely finished
|
||||
void downloadPdfCompleted(object _, LibraryBook libraryBook) => automatedBackupsForm.AppendText($"PDF Step, Completed: {libraryBook.Book}{Environment.NewLine}");
|
||||
#endregion
|
||||
|
||||
#region subscribe new form to model's events
|
||||
backupBook.DownloadBook.Begin += downloadBookBegin;
|
||||
backupBook.DownloadBook.StatusUpdate += statusUpdate;
|
||||
backupBook.DownloadBook.Completed += downloadBookCompleted;
|
||||
backupBook.DecryptBook.Begin += decryptBookBegin;
|
||||
backupBook.DecryptBook.StatusUpdate += statusUpdate;
|
||||
backupBook.DecryptBook.Completed += decryptBookCompleted;
|
||||
backupBook.DownloadPdf.Begin += downloadPdfBegin;
|
||||
backupBook.DownloadPdf.StatusUpdate += statusUpdate;
|
||||
backupBook.DownloadPdf.Completed += downloadPdfCompleted;
|
||||
#endregion
|
||||
|
||||
#region when form closes, unsubscribe from model's events
|
||||
// unsubscribe so disposed forms aren't still trying to receive notifications
|
||||
automatedBackupsForm.FormClosing += (_, __) =>
|
||||
{
|
||||
backupBook.DownloadBook.Begin -= downloadBookBegin;
|
||||
backupBook.DownloadBook.StatusUpdate -= statusUpdate;
|
||||
backupBook.DownloadBook.Completed -= downloadBookCompleted;
|
||||
backupBook.DecryptBook.Begin -= decryptBookBegin;
|
||||
backupBook.DecryptBook.StatusUpdate -= statusUpdate;
|
||||
backupBook.DecryptBook.Completed -= decryptBookCompleted;
|
||||
backupBook.DownloadPdf.Begin -= downloadPdfBegin;
|
||||
backupBook.DownloadPdf.StatusUpdate -= statusUpdate;
|
||||
backupBook.DownloadPdf.Completed -= downloadPdfCompleted;
|
||||
};
|
||||
#endregion
|
||||
|
||||
return automatedBackupsForm;
|
||||
}
|
||||
|
||||
// automated backups looper feels like a composible IProcessable: logic, UI, begin + process child + end
|
||||
// however the process step doesn't follow the pattern: Validate(product) + Process(product)
|
||||
private static async Task runBackupLoopAsync(IProcessable processable, AutomatedBackupsForm automatedBackupsForm)
|
||||
|
||||
@@ -40,13 +40,15 @@
|
||||
this.welcomeLbl.AutoSize = true;
|
||||
this.welcomeLbl.Location = new System.Drawing.Point(12, 9);
|
||||
this.welcomeLbl.Name = "welcomeLbl";
|
||||
this.welcomeLbl.Size = new System.Drawing.Size(399, 78);
|
||||
this.welcomeLbl.Size = new System.Drawing.Size(399, 117);
|
||||
this.welcomeLbl.TabIndex = 0;
|
||||
this.welcomeLbl.Text = resources.GetString("welcomeLbl.Text");
|
||||
//
|
||||
// noQuestionsBtn
|
||||
//
|
||||
this.noQuestionsBtn.Location = new System.Drawing.Point(15, 90);
|
||||
this.noQuestionsBtn.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left)
|
||||
| System.Windows.Forms.AnchorStyles.Right)));
|
||||
this.noQuestionsBtn.Location = new System.Drawing.Point(15, 129);
|
||||
this.noQuestionsBtn.Name = "noQuestionsBtn";
|
||||
this.noQuestionsBtn.Size = new System.Drawing.Size(396, 57);
|
||||
this.noQuestionsBtn.TabIndex = 1;
|
||||
@@ -55,7 +57,9 @@
|
||||
//
|
||||
// basicBtn
|
||||
//
|
||||
this.basicBtn.Location = new System.Drawing.Point(15, 153);
|
||||
this.basicBtn.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left)
|
||||
| System.Windows.Forms.AnchorStyles.Right)));
|
||||
this.basicBtn.Location = new System.Drawing.Point(15, 192);
|
||||
this.basicBtn.Name = "basicBtn";
|
||||
this.basicBtn.Size = new System.Drawing.Size(396, 57);
|
||||
this.basicBtn.TabIndex = 2;
|
||||
@@ -64,7 +68,9 @@
|
||||
//
|
||||
// advancedBtn
|
||||
//
|
||||
this.advancedBtn.Location = new System.Drawing.Point(15, 216);
|
||||
this.advancedBtn.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left)
|
||||
| System.Windows.Forms.AnchorStyles.Right)));
|
||||
this.advancedBtn.Location = new System.Drawing.Point(15, 255);
|
||||
this.advancedBtn.Name = "advancedBtn";
|
||||
this.advancedBtn.Size = new System.Drawing.Size(396, 57);
|
||||
this.advancedBtn.TabIndex = 3;
|
||||
@@ -75,7 +81,7 @@
|
||||
//
|
||||
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
|
||||
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
|
||||
this.ClientSize = new System.Drawing.Size(423, 285);
|
||||
this.ClientSize = new System.Drawing.Size(423, 324);
|
||||
this.Controls.Add(this.advancedBtn);
|
||||
this.Controls.Add(this.basicBtn);
|
||||
this.Controls.Add(this.noQuestionsBtn);
|
||||
|
||||
@@ -123,6 +123,9 @@
|
||||
Please fill in a few settings. You can also change these settings later.
|
||||
|
||||
After you make your selections, get started by importing your library.
|
||||
Go to Import > Scan Library</value>
|
||||
Go to Import > Scan Library
|
||||
|
||||
Download your entire library from the "Liberate" tab or
|
||||
liberate your books one at a time by clicking the stoplight</value>
|
||||
</data>
|
||||
</root>
|
||||
@@ -49,12 +49,10 @@ namespace LibationWinForms
|
||||
// also applies filter. ONLY call AFTER loading grid
|
||||
loadInitialQuickFilterState();
|
||||
|
||||
{ // init bottom counts
|
||||
backupsCountsLbl.Text = "[Calculating backed up book quantities]";
|
||||
pdfsCountsLbl.Text = "[Calculating backed up PDFs]";
|
||||
|
||||
setBackupCounts(null, null);
|
||||
}
|
||||
// init bottom counts
|
||||
backupsCountsLbl.Text = "[Calculating backed up book quantities]";
|
||||
pdfsCountsLbl.Text = "[Calculating backed up PDFs]";
|
||||
setBackupCounts(null, null);
|
||||
}
|
||||
|
||||
#region reload grid
|
||||
@@ -112,7 +110,7 @@ namespace LibationWinForms
|
||||
enum AudioFileState { full, aax, none }
|
||||
private void setBookBackupCounts(IEnumerable<Book> books)
|
||||
{
|
||||
AudioFileState getAudioFileState(string productId)
|
||||
static AudioFileState getAudioFileState(string productId)
|
||||
{
|
||||
if (AudibleFileStorage.Audio.Exists(productId))
|
||||
return AudioFileState.full;
|
||||
@@ -142,6 +140,7 @@ namespace LibationWinForms
|
||||
= pending > 0
|
||||
? $"{pending} remaining"
|
||||
: "All books have been liberated";
|
||||
Serilog.Log.Logger.Information(menuItemText);
|
||||
menuStrip1.UIThread(() => beginBookBackupsToolStripMenuItem.Enabled = pending > 0);
|
||||
menuStrip1.UIThread(() => beginBookBackupsToolStripMenuItem.Text = string.Format(beginBookBackupsToolStripMenuItem_format, menuItemText));
|
||||
}
|
||||
@@ -167,6 +166,7 @@ namespace LibationWinForms
|
||||
= notDownloaded > 0
|
||||
? $"{notDownloaded} remaining"
|
||||
: "All PDFs have been downloaded";
|
||||
Serilog.Log.Logger.Information(menuItemText);
|
||||
menuStrip1.UIThread(() => beginPdfBackupsToolStripMenuItem.Enabled = notDownloaded > 0);
|
||||
menuStrip1.UIThread(() => beginPdfBackupsToolStripMenuItem.Text = string.Format(beginPdfBackupsToolStripMenuItem_format, menuItemText));
|
||||
}
|
||||
@@ -237,16 +237,10 @@ namespace LibationWinForms
|
||||
|
||||
#region liberate menu
|
||||
private async void beginBookBackupsToolStripMenuItem_Click(object sender, EventArgs e)
|
||||
{
|
||||
var backupBook = BookLiberation.ProcessorAutomationController.GetWiredUpBackupBook(updateGridRow);
|
||||
await BookLiberation.ProcessorAutomationController.RunAutomaticBackupAsync(backupBook);
|
||||
}
|
||||
=> await BookLiberation.ProcessorAutomationController.BackupAllBooksAsync(updateGridRow);
|
||||
|
||||
private async void beginPdfBackupsToolStripMenuItem_Click(object sender, EventArgs e)
|
||||
{
|
||||
var downloadPdf = BookLiberation.ProcessorAutomationController.GetWiredUpDownloadPdf(updateGridRow);
|
||||
await BookLiberation.ProcessorAutomationController.RunAutomaticDownloadAsync(downloadPdf);
|
||||
}
|
||||
=> await BookLiberation.ProcessorAutomationController.BackupAllPdfsAsync(updateGridRow);
|
||||
|
||||
private void updateGridRow(object _, LibraryBook libraryBook) => currProductsGrid.RefreshRow(libraryBook.Book.AudibleProductId);
|
||||
#endregion
|
||||
|
||||
@@ -26,44 +26,19 @@ namespace LibationWinForms
|
||||
[Browsable(false)]
|
||||
public IEnumerable<string> TagsEnumerated => book.UserDefinedItem.TagsEnumerated;
|
||||
|
||||
public enum LiberatedState { NotDownloaded, DRM, Liberated }
|
||||
[Browsable(false)]
|
||||
public string Download_Status
|
||||
{
|
||||
get
|
||||
{
|
||||
var print
|
||||
= FileManager.AudibleFileStorage.Audio.Exists(book.AudibleProductId) ? "Liberated"
|
||||
: FileManager.AudibleFileStorage.AAX.Exists(book.AudibleProductId) ? "DRM"
|
||||
: "NOT d/l'ed";
|
||||
public LiberatedState Liberated_Status
|
||||
=> FileManager.AudibleFileStorage.Audio.Exists(book.AudibleProductId) ? LiberatedState.Liberated
|
||||
: FileManager.AudibleFileStorage.AAX.Exists(book.AudibleProductId) ? LiberatedState.DRM
|
||||
: LiberatedState.NotDownloaded;
|
||||
|
||||
if (!book.Supplements.Any())
|
||||
return print;
|
||||
|
||||
print += "\r\n";
|
||||
|
||||
var downloadStatuses = book.Supplements
|
||||
.Select(d => FileManager.AudibleFileStorage.PDF.Exists(book.AudibleProductId))
|
||||
// break delayed execution right now!
|
||||
.ToList();
|
||||
var count = downloadStatuses.Count;
|
||||
if (count == 1)
|
||||
{
|
||||
print += downloadStatuses[0]
|
||||
? "PDF d/l'ed"
|
||||
: "PDF NOT d/l'ed";
|
||||
}
|
||||
else
|
||||
{
|
||||
var downloadedCount = downloadStatuses.Count(s => s);
|
||||
print
|
||||
+= downloadedCount == count ? $"{count} PDFs d/l'ed"
|
||||
: downloadedCount == 0 ? $"{count} PDFs NOT d/l'ed"
|
||||
: $"{downloadedCount} of {count} PDFs d/l'ed";
|
||||
}
|
||||
|
||||
return print;
|
||||
}
|
||||
}
|
||||
public enum PdfState { NoPdf, Downloaded, NotDownloaded }
|
||||
[Browsable(false)]
|
||||
public PdfState Pdf_Status
|
||||
=> !book.Supplements.Any() ? PdfState.NoPdf
|
||||
: FileManager.AudibleFileStorage.PDF.Exists(book.AudibleProductId) ? PdfState.Downloaded
|
||||
: PdfState.NotDownloaded;
|
||||
|
||||
// displayValues is what gets displayed
|
||||
// the value that gets returned from the property is the cell's value
|
||||
|
||||
@@ -74,8 +74,16 @@ namespace LibationWinForms
|
||||
private void replaceFormatted(object sender, DataGridViewCellFormattingEventArgs e)
|
||||
{
|
||||
var col = ((DataGridView)sender).Columns[e.ColumnIndex];
|
||||
if (col is DataGridViewTextBoxColumn textCol && GetGridEntry(e.RowIndex).TryDisplayValue(textCol.Name, out string value))
|
||||
if (col is DataGridViewTextBoxColumn textCol && getGridEntry(e.RowIndex).TryDisplayValue(textCol.Name, out string value))
|
||||
{
|
||||
// DO NOT DO THIS: getCell(e).Value = value;
|
||||
// it's the wrong way and will infinitely call CellFormatting on each assign
|
||||
|
||||
// this is the correct way. will actually set FormattedValue (and EditedFormattedValue) while leaving Value as-is for sorting
|
||||
e.Value = value;
|
||||
|
||||
getCell(e).ToolTipText = value;
|
||||
}
|
||||
}
|
||||
|
||||
private void hiddenFormatting(object sender, DataGridViewCellFormattingEventArgs e)
|
||||
@@ -85,9 +93,9 @@ namespace LibationWinForms
|
||||
if (e.RowIndex < 0 || dgv.Columns[e.ColumnIndex] is DataGridViewButtonColumn)
|
||||
return;
|
||||
|
||||
var isHidden = GetGridEntry(e.RowIndex).TagsEnumerated.Contains("hidden");
|
||||
var isHidden = getGridEntry(e.RowIndex).TagsEnumerated.Contains("hidden");
|
||||
|
||||
dgv.Rows[e.RowIndex].Cells[e.ColumnIndex].Style
|
||||
getCell(e).Style
|
||||
= isHidden
|
||||
? new DataGridViewCellStyle { ForeColor = Color.LightGray }
|
||||
: dgv.DefaultCellStyle;
|
||||
@@ -105,41 +113,90 @@ namespace LibationWinForms
|
||||
|
||||
private void liberate_Paint(object sender, DataGridViewCellPaintingEventArgs e)
|
||||
{
|
||||
var dgv = (DataGridView)sender;
|
||||
|
||||
if (!isColumnValid(dgv, e.RowIndex, e.ColumnIndex, LIBERATE))
|
||||
if (!isColumnValid(e, LIBERATE))
|
||||
return;
|
||||
|
||||
dgv.Rows[e.RowIndex].Cells[e.ColumnIndex].Value = GetGridEntry(e.RowIndex).Download_Status;
|
||||
var cell = getCell(e);
|
||||
var gridEntry = getGridEntry(e.RowIndex);
|
||||
var liberatedStatus = gridEntry.Liberated_Status;
|
||||
var pdfStatus = gridEntry.Pdf_Status;
|
||||
|
||||
// mouseover text
|
||||
{
|
||||
var libState = liberatedStatus switch
|
||||
{
|
||||
GridEntry.LiberatedState.Liberated => "Liberated",
|
||||
GridEntry.LiberatedState.DRM => "Downloaded but needs DRM removed",
|
||||
GridEntry.LiberatedState.NotDownloaded => "Book NOT downloaded",
|
||||
_ => throw new Exception("Unexpected liberation state")
|
||||
};
|
||||
|
||||
var pdfState = pdfStatus switch
|
||||
{
|
||||
GridEntry.PdfState.Downloaded => "\r\nPDF downloaded",
|
||||
GridEntry.PdfState.NotDownloaded => "\r\nPDF NOT downloaded",
|
||||
GridEntry.PdfState.NoPdf => "",
|
||||
_ => throw new Exception("Unexpected PDF state")
|
||||
};
|
||||
|
||||
var text = libState + pdfState;
|
||||
|
||||
if (liberatedStatus == GridEntry.LiberatedState.NotDownloaded ||
|
||||
liberatedStatus == GridEntry.LiberatedState.DRM ||
|
||||
pdfStatus == GridEntry.PdfState.NotDownloaded)
|
||||
text += "\r\nClick to complete";
|
||||
|
||||
//DEBUG//cell.Value = text;
|
||||
cell.ToolTipText = text;
|
||||
}
|
||||
|
||||
// draw img
|
||||
{
|
||||
var image_lib
|
||||
= liberatedStatus == GridEntry.LiberatedState.NotDownloaded ? "red"
|
||||
: liberatedStatus == GridEntry.LiberatedState.DRM ? "yellow"
|
||||
: liberatedStatus == GridEntry.LiberatedState.Liberated ? "green"
|
||||
: throw new Exception("Unexpected liberation state");
|
||||
var image_pdf
|
||||
= pdfStatus == GridEntry.PdfState.NoPdf ? ""
|
||||
: pdfStatus == GridEntry.PdfState.NotDownloaded ? "_pdf_no"
|
||||
: pdfStatus == GridEntry.PdfState.Downloaded ? "_pdf_yes"
|
||||
: throw new Exception("Unexpected PDF state");
|
||||
var image = (Bitmap)Properties.Resources.ResourceManager.GetObject($"liberate_{image_lib}{image_pdf}");
|
||||
drawImage(e, image);
|
||||
}
|
||||
}
|
||||
|
||||
private async void liberate_Click(object sender, DataGridViewCellEventArgs e)
|
||||
{
|
||||
var dgv = (DataGridView)sender;
|
||||
|
||||
if (!isColumnValid(dgv, e.RowIndex, e.ColumnIndex, LIBERATE))
|
||||
if (!isColumnValid(e, LIBERATE))
|
||||
return;
|
||||
|
||||
var productId = GetGridEntry(e.RowIndex).GetBook().AudibleProductId;
|
||||
var productId = getGridEntry(e.RowIndex).GetBook().AudibleProductId;
|
||||
|
||||
// if liberated, open explorer to file
|
||||
// liberated: open explorer to file
|
||||
if (FileManager.AudibleFileStorage.Audio.Exists(productId))
|
||||
{
|
||||
var filePath = FileManager.AudibleFileStorage.Audio.GetPath(productId);
|
||||
System.Diagnostics.Process.Start("explorer.exe", $"/select, \"{filePath}\"");
|
||||
return;
|
||||
}
|
||||
// else liberate
|
||||
|
||||
// not liberated: liberate
|
||||
var msg
|
||||
= "Liberate entire library instead?"
|
||||
+ "\r\n\r\nClick Yes to begin liberating your entire library"
|
||||
+ "\r\n\r\nClick No to liberate this book only";
|
||||
if (MessageBox.Show(msg, "Liberate entire library?", MessageBoxButtons.YesNo) == DialogResult.Yes)
|
||||
await BookLiberation.ProcessorAutomationController.BackupAllBooksAsync((_, libraryBook) => RefreshRow(libraryBook.Book.AudibleProductId));
|
||||
else
|
||||
{
|
||||
var backupBook = BookLiberation.ProcessorAutomationController.GetWiredUpBackupBook((_, __) => RefreshRow(productId));
|
||||
await BookLiberation.ProcessorAutomationController.RunSingleBackupAsync(backupBook, productId);
|
||||
}
|
||||
await BookLiberation.ProcessorAutomationController.BackupSingleBookAsync(productId, (_, __) => RefreshRow(productId));
|
||||
}
|
||||
#endregion
|
||||
|
||||
public void RefreshRow(string productId)
|
||||
{
|
||||
var rowId = GetRowId((ge) => ge.GetBook().AudibleProductId == productId);
|
||||
var rowId = getRowId((ge) => ge.GetBook().AudibleProductId == productId);
|
||||
|
||||
// update cells incl Liberate button text
|
||||
dataGridView.InvalidateRow(rowId);
|
||||
@@ -160,33 +217,21 @@ namespace LibationWinForms
|
||||
{
|
||||
// DataGridView Image for Button Column: https://stackoverflow.com/a/36253883
|
||||
|
||||
var dgv = (DataGridView)sender;
|
||||
|
||||
if (!isColumnValid(dgv, e.RowIndex, e.ColumnIndex, EDIT_TAGS))
|
||||
if (!isColumnValid(e, EDIT_TAGS))
|
||||
return;
|
||||
|
||||
var displayTags = GetGridEntry(e.RowIndex).TagsEnumerated.ToList();
|
||||
var cell = getCell(e);
|
||||
var gridEntry = getGridEntry(e.RowIndex);
|
||||
|
||||
var cell = dgv.Rows[e.RowIndex].Cells[e.ColumnIndex];
|
||||
var displayTags = gridEntry.TagsEnumerated.ToList();
|
||||
|
||||
if (displayTags.Any())
|
||||
cell.Value = string.Join("\r\n", displayTags);
|
||||
else // no tags: use image
|
||||
else
|
||||
{
|
||||
// clear tag text
|
||||
// if removing all tags: clear previous tag text
|
||||
cell.Value = "";
|
||||
|
||||
var image = Properties.Resources.edit_tags_25x25;
|
||||
|
||||
e.Paint(e.CellBounds, DataGridViewPaintParts.All);
|
||||
|
||||
var w = image.Width;
|
||||
var h = image.Height;
|
||||
var x = e.CellBounds.Left + (e.CellBounds.Width - w) / 2;
|
||||
var y = e.CellBounds.Top + (e.CellBounds.Height - h) / 2;
|
||||
|
||||
e.Graphics.DrawImage(image, new Rectangle(x, y, w, h));
|
||||
e.Handled = true;
|
||||
drawImage(e, Properties.Resources.edit_tags_25x25);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -196,10 +241,10 @@ namespace LibationWinForms
|
||||
|
||||
var dgv = (DataGridView)sender;
|
||||
|
||||
if (!isColumnValid(dgv, e.RowIndex, e.ColumnIndex, EDIT_TAGS))
|
||||
if (!isColumnValid(e, EDIT_TAGS))
|
||||
return;
|
||||
|
||||
var liveGridEntry = GetGridEntry(e.RowIndex);
|
||||
var liveGridEntry = getGridEntry(e.RowIndex);
|
||||
|
||||
// EditTagsDialog should display better-formatted title
|
||||
liveGridEntry.TryDisplayValue(nameof(liveGridEntry.Title), out string value);
|
||||
@@ -221,10 +266,25 @@ namespace LibationWinForms
|
||||
}
|
||||
#endregion
|
||||
|
||||
private static bool isColumnValid(DataGridView dgv, int rowIndex, int colIndex, string colName)
|
||||
private static void drawImage(DataGridViewCellPaintingEventArgs e, Bitmap image)
|
||||
{
|
||||
var col = dgv.Columns[colIndex];
|
||||
return rowIndex >= 0 && col.HeaderText == colName && col is DataGridViewButtonColumn;
|
||||
e.Paint(e.CellBounds, DataGridViewPaintParts.All);
|
||||
|
||||
var w = image.Width;
|
||||
var h = image.Height;
|
||||
var x = e.CellBounds.Left + (e.CellBounds.Width - w) / 2;
|
||||
var y = e.CellBounds.Top + (e.CellBounds.Height - h) / 2;
|
||||
|
||||
e.Graphics.DrawImage(image, new Rectangle(x, y, w, h));
|
||||
e.Handled = true;
|
||||
}
|
||||
|
||||
private bool isColumnValid(DataGridViewCellEventArgs e, string colName) => isColumnValid(e.RowIndex, e.ColumnIndex, colName);
|
||||
private bool isColumnValid(DataGridViewCellPaintingEventArgs e, string colName) => isColumnValid(e.RowIndex, e.ColumnIndex, colName);
|
||||
private bool isColumnValid(int rowIndex, int colIndex, string colName)
|
||||
{
|
||||
var col = dataGridView.Columns[colIndex];
|
||||
return rowIndex >= 0 && col.Name == colName && col is DataGridViewButtonColumn;
|
||||
}
|
||||
|
||||
private void formatColumns()
|
||||
@@ -243,6 +303,7 @@ namespace LibationWinForms
|
||||
|
||||
col.Width = col.Name switch
|
||||
{
|
||||
LIBERATE => 70,
|
||||
nameof(GridEntry.Cover) => 80,
|
||||
nameof(GridEntry.Title) => col.Width * 2,
|
||||
nameof(GridEntry.Misc) => (int)(col.Width * 1.35),
|
||||
@@ -263,7 +324,7 @@ namespace LibationWinForms
|
||||
=> dataGridView.UIThread(() => updateRowImage(pictureId));
|
||||
private void updateRowImage(string pictureId)
|
||||
{
|
||||
var rowId = GetRowId((ge) => ge.GetBook().PictureId == pictureId);
|
||||
var rowId = getRowId((ge) => ge.GetBook().PictureId == pictureId);
|
||||
if (rowId > -1)
|
||||
dataGridView.InvalidateRow(rowId);
|
||||
}
|
||||
@@ -331,7 +392,7 @@ namespace LibationWinForms
|
||||
currencyManager.SuspendBinding();
|
||||
{
|
||||
for (var r = dataGridView.RowCount - 1; r >= 0; r--)
|
||||
dataGridView.Rows[r].Visible = productIds.Contains(GetGridEntry(r).GetBook().AudibleProductId);
|
||||
dataGridView.Rows[r].Visible = productIds.Contains(getGridEntry(r).GetBook().AudibleProductId);
|
||||
}
|
||||
currencyManager.ResumeBinding();
|
||||
VisibleCountChanged?.Invoke(this, dataGridView.AsEnumerable().Count(r => r.Visible));
|
||||
@@ -340,8 +401,14 @@ namespace LibationWinForms
|
||||
}
|
||||
#endregion
|
||||
|
||||
private int GetRowId(Func<GridEntry, bool> func) => dataGridView.GetRowIdOfBoundItem(func);
|
||||
private int getRowId(Func<GridEntry, bool> func) => dataGridView.GetRowIdOfBoundItem(func);
|
||||
|
||||
private GridEntry GetGridEntry(int rowIndex) => dataGridView.GetBoundItem<GridEntry>(rowIndex);
|
||||
}
|
||||
private GridEntry getGridEntry(int rowIndex) => dataGridView.GetBoundItem<GridEntry>(rowIndex);
|
||||
|
||||
private DataGridViewCell getCell(DataGridViewCellFormattingEventArgs e) => getCell(e.RowIndex, e.ColumnIndex);
|
||||
|
||||
private DataGridViewCell getCell(DataGridViewCellPaintingEventArgs e) => getCell(e.RowIndex, e.ColumnIndex);
|
||||
|
||||
private DataGridViewCell getCell(int rowIndex, int columnIndex) => dataGridView.Rows[rowIndex].Cells[columnIndex];
|
||||
}
|
||||
}
|
||||
|
||||
|
Before Width: | Height: | Size: 92 KiB After Width: | Height: | Size: 100 KiB |
40
README.md
@@ -16,6 +16,8 @@
|
||||
- [Searches](#searches)
|
||||
- [Search examples](#search-examples)
|
||||
- [Filters](#filters)
|
||||
4. [Advanced](#advanced)
|
||||
- [Files and folders](#files-and-folders)
|
||||
|
||||
## Audible audiobook manager
|
||||
|
||||
@@ -36,6 +38,7 @@
|
||||
|
||||
* Windows only
|
||||
* Several known speed/performance issues
|
||||
* Large file size
|
||||
* Made by a programmer, not a designer so the goals are function rather than beauty. And it shows
|
||||
|
||||
### The ugly
|
||||
@@ -68,10 +71,22 @@ Success! We see how many new titles are imported:
|
||||
|
||||
Automatically download some or all of your audible books. This shows you how much of your library is not yet downloaded and decrypted:
|
||||
|
||||
The stoplights will tell you a title's status:
|
||||
|
||||
* Green: downloaded and decrypted
|
||||
* Yellow: downloaded but still encrypted with DRM
|
||||
* Red: not downloaded
|
||||
* PDF icon without arrow: downloaded
|
||||
* PDF with arrow: not downloaded
|
||||
|
||||
Or hover over the button to see the status.
|
||||
|
||||

|
||||
|
||||
Select Liberate > Begin Book Backups
|
||||
|
||||
You can also click on the stop light to download only that title and its PDF
|
||||
|
||||

|
||||
|
||||
First the original book with DRM is downloaded
|
||||
@@ -86,12 +101,12 @@ And voila! If you have multiple books not yet liberated, Libation will automatic
|
||||
|
||||

|
||||
|
||||
The Audible id must be somewhere in the book's file or folder name for Libation to detect your downloaded book.
|
||||
|
||||
### Download PDF attachments
|
||||
|
||||
For books which include PDF downloads, Libation can download these for you as well and will attempt to store them with the book. "Book backup" will already download an available PDF. This additional option is useful when Audible adds a PDF to your book after you've already backed it up.
|
||||
|
||||

|
||||
|
||||
Select Liberate > Begin PDF Backups
|
||||
|
||||

|
||||
@@ -100,10 +115,6 @@ The downloads work just like with books, only with no additional decryption need
|
||||
|
||||

|
||||
|
||||
Ta da!
|
||||
|
||||

|
||||
|
||||
### Details of downloaded files
|
||||
|
||||

|
||||
@@ -156,11 +167,12 @@ If you only want to see Harry Potter
|
||||
|
||||

|
||||
|
||||
If you only want to see potter except for Harry Potter
|
||||
If you only want to see potter except for Harry Potter. You can also use "-" instead of "NOT"
|
||||
|
||||

|
||||

|
||||
|
||||
Only books written by Neil Gaiman where he also narrates his own book. (If you don't include AND, you'll see everything written by Neil Gaiman and also all books in your library which are self-narrated.)
|
||||
To see only books written by Neil Gaiman where he also narrates his own book. (If you don't include AND, you'll see everything written by Neil Gaiman and also all books in your library which are self-narrated.)
|
||||
|
||||

|
||||
|
||||
@@ -178,3 +190,15 @@ To edit this list go to Quick Filters > Edit quick filters. Here you can re-orde
|
||||
Check "Quick Filters > Start Libation with 1st filter Default" to have your top filter automatically applied when Libation starts. In this top example, I want to always start without these: at books I've tagged hidden, books I've tagged as free_audible_originals, and books which I have rated.
|
||||
|
||||

|
||||
|
||||
## Advanced
|
||||
|
||||
### Files and folders
|
||||
|
||||
To make upgrades and reinstalls easier, Libation separates all of its responsibilities to a few different folders. If you don't want to mess with this stuff: ignore it. Read on if you like a little more control over your files.
|
||||
|
||||
* In Libation's initial folder are the files that make up the program. Since nothing else is here, just copy new files here to upgrade the program. Delete this folder to delete Libation.
|
||||
|
||||
* In a separate folder, Libation keeps track of all of the files it creates like settings and downloaded images. After an upgrade, Libation might think that's its being run for the first time. Just click ADVANCED SETUP and point to this folder. Libation will reload your library and settings.
|
||||
|
||||
* The last important folder is the "books location." This is where Libation looks for your downloaded and decrypted books. This is how it knows which books still need to be downloaded. The Audible id must be somewhere in the book's file or folder name for Libation to detect your downloaded book.
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
-- begin VERSIONING ---------------------------------------------------------------------------------------------------------------------
|
||||
https://github.com/rmcrackan/Libation/releases
|
||||
|
||||
v3.1.2 : null checks
|
||||
v3.1.1 : Check if upgrade available on github
|
||||
v3.1.0 : FIRST PUBLIC RELEASE
|
||||
v3.1-beta.11 : Improved configuration and settings file management. Configurable logging
|
||||
v3.1-beta.10 : New feature: clicking Liberate button on a liberated item navigates to that audio file
|
||||
v3.1-beta.9 : New feature: liberate individual book
|
||||
|
||||
16
WinFormsDesigner/Dialogs/SetupDialog.Designer.cs
generated
@@ -40,13 +40,15 @@
|
||||
this.welcomeLbl.AutoSize = true;
|
||||
this.welcomeLbl.Location = new System.Drawing.Point(12, 9);
|
||||
this.welcomeLbl.Name = "welcomeLbl";
|
||||
this.welcomeLbl.Size = new System.Drawing.Size(399, 78);
|
||||
this.welcomeLbl.Size = new System.Drawing.Size(399, 117);
|
||||
this.welcomeLbl.TabIndex = 0;
|
||||
this.welcomeLbl.Text = resources.GetString("welcomeLbl.Text");
|
||||
//
|
||||
// noQuestionsBtn
|
||||
//
|
||||
this.noQuestionsBtn.Location = new System.Drawing.Point(15, 90);
|
||||
this.noQuestionsBtn.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left)
|
||||
| System.Windows.Forms.AnchorStyles.Right)));
|
||||
this.noQuestionsBtn.Location = new System.Drawing.Point(15, 129);
|
||||
this.noQuestionsBtn.Name = "noQuestionsBtn";
|
||||
this.noQuestionsBtn.Size = new System.Drawing.Size(396, 57);
|
||||
this.noQuestionsBtn.TabIndex = 1;
|
||||
@@ -55,7 +57,9 @@
|
||||
//
|
||||
// basicBtn
|
||||
//
|
||||
this.basicBtn.Location = new System.Drawing.Point(15, 153);
|
||||
this.basicBtn.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left)
|
||||
| System.Windows.Forms.AnchorStyles.Right)));
|
||||
this.basicBtn.Location = new System.Drawing.Point(15, 192);
|
||||
this.basicBtn.Name = "basicBtn";
|
||||
this.basicBtn.Size = new System.Drawing.Size(396, 57);
|
||||
this.basicBtn.TabIndex = 2;
|
||||
@@ -64,7 +68,9 @@
|
||||
//
|
||||
// advancedBtn
|
||||
//
|
||||
this.advancedBtn.Location = new System.Drawing.Point(15, 216);
|
||||
this.advancedBtn.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left)
|
||||
| System.Windows.Forms.AnchorStyles.Right)));
|
||||
this.advancedBtn.Location = new System.Drawing.Point(15, 255);
|
||||
this.advancedBtn.Name = "advancedBtn";
|
||||
this.advancedBtn.Size = new System.Drawing.Size(396, 57);
|
||||
this.advancedBtn.TabIndex = 3;
|
||||
@@ -75,7 +81,7 @@
|
||||
//
|
||||
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
|
||||
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
|
||||
this.ClientSize = new System.Drawing.Size(423, 285);
|
||||
this.ClientSize = new System.Drawing.Size(423, 324);
|
||||
this.Controls.Add(this.advancedBtn);
|
||||
this.Controls.Add(this.basicBtn);
|
||||
this.Controls.Add(this.noQuestionsBtn);
|
||||
|
||||
@@ -123,6 +123,9 @@
|
||||
Please fill in a few settings. You can also change these settings later.
|
||||
|
||||
After you make your selections, get started by importing your library.
|
||||
Go to Import > Scan Library</value>
|
||||
Go to Import > Scan Library
|
||||
|
||||
Download your entire library from the "Liberate" tab or
|
||||
liberate your books one at a time by clicking the stoplight</value>
|
||||
</data>
|
||||
</root>
|
||||
|
Before Width: | Height: | Size: 314 B |
|
Before Width: | Height: | Size: 573 B |
|
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 38 KiB |
|
Before Width: | Height: | Size: 5.8 KiB After Width: | Height: | Size: 6.6 KiB |
|
Before Width: | Height: | Size: 8.2 KiB After Width: | Height: | Size: 35 KiB |
|
Before Width: | Height: | Size: 6.7 KiB |
|
Before Width: | Height: | Size: 8.8 KiB |
BIN
images/SearchExamplePotterNotHarry2.png
Normal file
|
After Width: | Height: | Size: 21 KiB |