Compare commits

...

24 Commits

Author SHA1 Message Date
Robert McRackan
9abb9e376d null checks 2019-12-31 10:53:15 -05:00
Robert McRackan
f93498bfe3 v3.1.1 release 2019-12-31 10:06:57 -05:00
Robert McRackan
a13e1f27bb clicking on stoplight prompts with option to liberate full library 2019-12-31 10:04:55 -05:00
Robert McRackan
c7c1b4505b improved logging: at startup, config changes, library counts
better starting instructions to liberate library
2019-12-31 09:49:32 -05:00
Robert McRackan
d9e0f1aedf New feature: check if upgrade available on github 2019-12-27 22:08:43 -05:00
Robert McRackan
d8a0124b68 Better error when download service is unavailable 2019-12-26 13:04:56 -05:00
Robert McRackan
79e0a8fba7 Merge branch 'master' of https://github.com/rmcrackan/Libation 2019-12-23 11:24:54 -05:00
Robert McRackan
8497987967 update release notes 2019-12-23 11:24:49 -05:00
rmcrackan
717fefd2c0 Update README.md 2019-12-23 11:17:00 -05:00
rmcrackan
066cae8e33 update readme 2019-12-23 11:15:32 -05:00
rmcrackan
9083574a77 Update README.md 2019-12-23 10:52:50 -05:00
rmcrackan
6b1ab9c777 Update README.md 2019-12-23 10:51:52 -05:00
rmcrackan
3be7c87c8e Update README.md 2019-12-23 10:46:52 -05:00
rmcrackan
8694d3206b Update README.md 2019-12-23 10:45:42 -05:00
Robert McRackan
f67f3805c6 add screenshot 2019-12-23 10:45:27 -05:00
Robert McRackan
612dd41b4b Merge branch 'master' of https://github.com/rmcrackan/Libation 2019-12-23 10:42:00 -05:00
Robert McRackan
13378a482d delete unused screenshots 2019-12-23 10:41:55 -05:00
rmcrackan
352b498c23 Update README.md 2019-12-23 10:41:32 -05:00
rmcrackan
3a652cfb70 Update README.md 2019-12-23 10:38:32 -05:00
Robert McRackan
93e9ce31ba update readme images 2019-12-23 10:37:54 -05:00
Robert McRackan
69ed7767b2 update readme screenshots 2019-12-23 10:29:40 -05:00
Robert McRackan
6fcaa8d551 Improved pdf icons 2019-12-23 10:09:28 -05:00
Robert McRackan
15ece43463 Update icon. Add glow so it can be seen on black background. Not as attractive, but no longer invisible 2019-12-23 09:27:11 -05:00
Robert McRackan
25f5f0ed14 Change liberate text buttons to images 2019-12-20 16:37:50 -05:00
62 changed files with 3076 additions and 2424 deletions

View File

@@ -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

View File

@@ -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");

View File

@@ -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";
}
}

View File

@@ -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");
}

View 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);
}
}
}
}

View File

@@ -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;
}
}

View File

@@ -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;

View File

@@ -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;

View File

@@ -0,0 +1,4 @@
namespace FileLiberator
{
public interface IDownloadableProcessable : IDownloadable, IProcessable { }
}

View File

@@ -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
View File

@@ -0,0 +1,4 @@
{
"//": "https://github.com/BalassaMarton/MSBump",
BumpRevision: true
}

View File

@@ -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>

View File

@@ -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;
}
}

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 92 KiB

After

Width:  |  Height:  |  Size: 100 KiB

View File

@@ -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));
}
}
}
}

View File

@@ -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>

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 100 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.6 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 482 B

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.9 KiB

View File

Binary file not shown.

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

View File

@@ -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

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 92 KiB

After

Width:  |  Height:  |  Size: 100 KiB

View File

Binary file not shown.

View File

Binary file not shown.

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

View File

@@ -0,0 +1,2 @@
[InternetShortcut]
URL=https://www.flaticon.com/free-icon/pdf-file-format-symbol_29099

View File

@@ -0,0 +1,2 @@
[InternetShortcut]
URL=https://www.flaticon.com/free-icon/semaphore_55291

View File

@@ -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

View File

@@ -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)

View File

@@ -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);

View File

@@ -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 &gt; Scan Library</value>
Go to Import &gt; 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>

View File

@@ -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

View File

File diff suppressed because it is too large Load Diff

View File

@@ -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

View File

@@ -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];
}
}

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 92 KiB

After

Width:  |  Height:  |  Size: 100 KiB

View File

@@ -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.
![Liberate book step 1](images/LiberateBook1.png)
Select Liberate > Begin Book Backups
You can also click on the stop light to download only that title and its PDF
![Liberate book step 2](images/LiberateBook2.png)
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
![Liberate book step 5](images/LiberateBook5.png)
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.
![PDF download step 1](images/PdfDownload1.png)
Select Liberate > Begin PDF Backups
![PDF download step 2](images/PdfDownload2.png)
@@ -100,10 +115,6 @@ The downloads work just like with books, only with no additional decryption need
![PDF download step 3](images/PdfDownload3.png)
Ta da!
![PDF download step 4](images/PdfDownload4.png)
### Details of downloaded files
![Post download](images/PostDownload.png)
@@ -156,11 +167,12 @@ If you only want to see Harry Potter
![Search example: "harry potter"](images/SearchExampleHarryPotter.png)
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"
![Search example: "potter NOT harry"](images/SearchExamplePotterNotHarry.png)
![Search example: "potter -harry"](images/SearchExamplePotterNotHarry2.png)
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.)
![Search example: author:gaiman AND authornarrated](images/SearchExampleGaimanAuthorNarrated.png)
@@ -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.
![default filters](images/FiltersDefault.png)
## 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.

View File

@@ -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

View File

@@ -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);

View File

@@ -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 &gt; Scan Library</value>
Go to Import &gt; 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>

View File

File diff suppressed because it is too large Load Diff

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 314 B

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 573 B

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 38 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.8 KiB

After

Width:  |  Height:  |  Size: 6.6 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.2 KiB

After

Width:  |  Height:  |  Size: 35 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.7 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.8 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB