Compare commits

..

45 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
Robert McRackan
de66e5b405 Clean up for 3.1 beta 11 2019-12-18 14:27:27 -05:00
Robert McRackan
73c671b7c0 Removed temp fix of hard-coded logging level. Now is correctly set via config file 2019-12-18 10:50:46 -05:00
Robert McRackan
4994684690 Improved configuration management 2019-12-17 10:09:33 -05:00
Robert McRackan
6c757773f7 Minor string change 2019-12-16 13:40:54 -05:00
Robert McRackan
2d0af587d5 Add new settings options to main form 2019-12-16 11:18:50 -05:00
Robert McRackan
c7891dc448 2 bug fixes which prevented saving to db 2019-12-16 10:32:38 -05:00
Robert McRackan
95ae8335a1 Improved settings 2019-12-13 16:11:55 -05:00
Robert McRackan
2fa5170f28 Pluralize LibationWinForms 2019-12-10 10:45:14 -05:00
Robert McRackan
123a32ff9b Move application logic from view to Launcher 2019-12-10 10:33:51 -05:00
Robert McRackan
41620352e8 Add appsettings.json to main application. This will overwrite the one previously inherited from DataLayer 2019-12-09 15:24:25 -05:00
Robert McRackan
54eea8ddae DataLayer appsettings.json is only used for Migrations. It is not directly used by application. Make notes 2019-12-09 14:51:33 -05:00
Robert McRackan
bcc237c693 sqlite db file should live in LibationFiles dir, not in app dir 2019-12-09 14:44:46 -05:00
Robert McRackan
65dc273e12 update release notes 2019-12-06 10:23:05 -05:00
Robert McRackan
7bb4853903 Clicking Liberate button on a liberated item navigates to that audio file 2019-12-06 09:53:07 -05:00
Robert McRackan
f9917d4064 edit comments 2019-12-05 16:14:39 -05:00
Robert McRackan
0f9f0d9eae New feature: liberate individual book 2019-12-05 15:55:46 -05:00
Robert McRackan
498aeaac3a Change "Download Status" column to "Liberate" button column. Displays text status. No functionality added yet 2019-12-05 12:39:38 -05:00
Robert McRackan
9534969c2d Series should sort irrespective of initial the/a/an (like Title already does) 2019-12-04 13:32:25 -05:00
Robert McRackan
b120bb8a66 Replace custom FileLogger with Serilog 2019-12-04 09:58:31 -05:00
Robert McRackan
f8a51f0882 Upgrade to Core 3.1 2019-12-03 16:47:53 -05:00
Robert McRackan
7529fdf878 Add logging 2019-12-02 15:14:19 -05:00
168 changed files with 7155 additions and 5271 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

@@ -0,0 +1,16 @@
using System;
using DataLayer;
using FileManager;
namespace ApplicationServices
{
public static class DbContexts
{
//// idea for future command/query separation
// public static LibationContext GetCommandContext() { }
// public static LibationContext GetQueryContext() { }
public static LibationContext GetContext()
=> LibationContext.Create(SqliteStorage.ConnectionString);
}
}

View File

@@ -16,14 +16,16 @@ namespace ApplicationServices
var audibleApiActions = new AudibleApiActions();
var items = await audibleApiActions.GetAllLibraryItemsAsync(callback);
var totalCount = items.Count;
Serilog.Log.Logger.Debug($"GetAllLibraryItems: Total count {totalCount}");
Serilog.Log.Logger.Information($"GetAllLibraryItems: Total count {totalCount}");
var libImporter = new LibraryImporter();
using var context = DbContexts.GetContext();
var libImporter = new LibraryImporter(context);
var newCount = await Task.Run(() => libImporter.Import(items));
Serilog.Log.Logger.Debug($"Import: New count {newCount}");
context.SaveChanges();
Serilog.Log.Logger.Information($"Import: New count {newCount}");
await Task.Run(() => SearchEngineCommands.FullReIndex());
Serilog.Log.Logger.Debug("FullReIndex: success");
Serilog.Log.Logger.Information("FullReIndex: success");
return (totalCount, newCount);
}

View File

@@ -1,4 +1,5 @@
using DataLayer;
using System.IO;
using DataLayer;
using LibationSearchEngine;
namespace ApplicationServices
@@ -7,18 +8,18 @@ namespace ApplicationServices
{
public static void FullReIndex()
{
var engine = new SearchEngine();
var engine = new SearchEngine(DbContexts.GetContext());
engine.CreateNewIndex();
}
public static SearchResultSet Search(string searchString)
{
var engine = new SearchEngine();
var engine = new SearchEngine(DbContexts.GetContext());
try
{
return engine.Search(searchString);
}
catch (System.IO.FileNotFoundException)
catch (FileNotFoundException)
{
FullReIndex();
return engine.Search(searchString);
@@ -27,12 +28,12 @@ namespace ApplicationServices
public static void UpdateBookTags(Book book)
{
var engine = new SearchEngine();
var engine = new SearchEngine(DbContexts.GetContext());
try
{
engine.UpdateTags(book.AudibleProductId, book.UserDefinedItem.Tags);
}
catch (System.IO.FileNotFoundException)
catch (FileNotFoundException)
{
FullReIndex();
engine.UpdateTags(book.AudibleProductId, book.UserDefinedItem.Tags);

View File

@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>netcoreapp3.0;netstandard2.1</TargetFrameworks>
<TargetFrameworks>netcoreapp3.1;netstandard2.1</TargetFrameworks>
</PropertyGroup>
<PropertyGroup>
@@ -12,13 +12,13 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="3.0.1">
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="3.1.0">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="3.0.1" />
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="3.0.1" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="3.0.1">
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="3.1.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="3.1.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="3.1.0">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
@@ -32,7 +32,6 @@
<ItemGroup>
<None Update="appsettings.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
<ExcludeFromSingleFile>true</ExcludeFromSingleFile>
</None>
</ItemGroup>

View File

@@ -25,10 +25,10 @@ namespace DataLayer
public DbSet<Series> Series { get; private set; }
public DbSet<Category> Categories { get; private set; }
public static LibationContext Create()
public static LibationContext Create(string connectionString)
{
var factory = new LibationContextFactory();
var context = factory.Create();
var context = factory.Create(connectionString);
return context;
}

View File

@@ -8,14 +8,11 @@ namespace DataLayer
{
public static class BookQueries
{
public static Book GetBook_Flat_NoTracking(string productId)
{
using var context = LibationContext.Create();
return context
public static Book GetBook_Flat_NoTracking(this LibationContext context, string productId)
=> context
.Books
.AsNoTracking()
.GetBook(productId);
}
public static Book GetBook(this IQueryable<Book> books, string productId)
=> books

View File

@@ -12,24 +12,18 @@ namespace DataLayer
.GetLibrary()
.ToList();
public static List<LibraryBook> GetLibrary_Flat_NoTracking()
{
using var context = LibationContext.Create();
return context
public static List<LibraryBook> GetLibrary_Flat_NoTracking(this LibationContext context)
=> context
.Library
.AsNoTracking()
.GetLibrary()
.ToList();
}
public static LibraryBook GetLibraryBook_Flat_NoTracking(string productId)
{
using var context = LibationContext.Create();
return context
public static LibraryBook GetLibraryBook_Flat_NoTracking(this LibationContext context, string productId)
=> context
.Library
.AsNoTracking()
.GetLibraryBook(productId);
}
.GetLibraryBook(productId);
/// <summary>This is still IQueryable. YOU MUST CALL ToList() YOURSELF</summary>
public static IQueryable<LibraryBook> GetLibrary(this IQueryable<LibraryBook> library)

View File

@@ -22,7 +22,7 @@ TO USE MIGRATIONS AS *BOTH* CORE AND STANDARD
to: TargetFrameworks
inside of TargetFrameworks
from: netstandard2.1
to: netcoreapp3.0;netstandard2.1
to: netcoreapp3.1;netstandard2.1
run. error
SQLite Error 1: 'no such table: Blogs'.

View File

@@ -1,6 +1,8 @@
{
"ConnectionStrings": {
"LibationContext_sqlserver": "Server=(LocalDb)\\MSSQLLocalDB;Database=DataLayer.LibationContext;Integrated Security=true;",
"// this connection string is ONLY used for DataLayer's Migrations. this appsettings.json file is NOT used at all by application; it is overwritten": "",
"LibationContext": "Data Source=LibationContext.db;Foreign Keys=False;",
"// sqlite notes": "",

View File

@@ -9,29 +9,31 @@ namespace DtoImporterService
{
public class BookImporter : ItemsImporterBase
{
public BookImporter(LibationContext context) : base(context) { }
public override IEnumerable<Exception> Validate(IEnumerable<Item> items) => new BookValidator().Validate(items);
protected override int DoImport(IEnumerable<Item> items, LibationContext context)
protected override int DoImport(IEnumerable<Item> items)
{
// pre-req.s
new ContributorImporter().Import(items, context);
new SeriesImporter().Import(items, context);
new CategoryImporter().Import(items, context);
new ContributorImporter(DbContext).Import(items);
new SeriesImporter(DbContext).Import(items);
new CategoryImporter(DbContext).Import(items);
// get distinct
var productIds = items.Select(i => i.ProductId).ToList();
// load db existing => .Local
loadLocal_books(productIds, context);
loadLocal_books(productIds);
// upsert
var qtyNew = upsertBooks(items, context);
var qtyNew = upsertBooks(items);
return qtyNew;
}
private void loadLocal_books(List<string> productIds, LibationContext context)
private void loadLocal_books(List<string> productIds)
{
var localProductIds = context.Books.Local.Select(b => b.AudibleProductId);
var localProductIds = DbContext.Books.Local.Select(b => b.AudibleProductId);
var remainingProductIds = productIds
.Distinct()
.Except(localProductIds)
@@ -39,29 +41,29 @@ namespace DtoImporterService
// GetBooks() eager loads Series, category, et al
if (remainingProductIds.Any())
context.Books.GetBooks(b => remainingProductIds.Contains(b.AudibleProductId)).ToList();
DbContext.Books.GetBooks(b => remainingProductIds.Contains(b.AudibleProductId)).ToList();
}
private int upsertBooks(IEnumerable<Item> items, LibationContext context)
private int upsertBooks(IEnumerable<Item> items)
{
var qtyNew = 0;
foreach (var item in items)
{
var book = context.Books.Local.SingleOrDefault(p => p.AudibleProductId == item.ProductId);
var book = DbContext.Books.Local.SingleOrDefault(p => p.AudibleProductId == item.ProductId);
if (book is null)
{
book = createNewBook(item, context);
book = createNewBook(item);
qtyNew++;
}
updateBook(item, book, context);
updateBook(item, book);
}
return qtyNew;
}
private static Book createNewBook(Item item, LibationContext context)
private Book createNewBook(Item item)
{
// absence of authors is very rare, but possible
if (!item.Authors?.Any() ?? true)
@@ -70,7 +72,7 @@ namespace DtoImporterService
// nested logic is required so order of names is retained. else, contributors may appear in the order they were inserted into the db
var authors = item
.Authors
.Select(a => context.Contributors.Local.Single(c => a.Name == c.Name))
.Select(a => DbContext.Contributors.Local.Single(c => a.Name == c.Name))
.ToList();
var narrators
@@ -80,15 +82,15 @@ namespace DtoImporterService
// nested logic is required so order of names is retained. else, contributors may appear in the order they were inserted into the db
: item
.Narrators
.Select(n => context.Contributors.Local.Single(c => n.Name == c.Name))
.Select(n => DbContext.Contributors.Local.Single(c => n.Name == c.Name))
.ToList();
// categories are laid out for a breadcrumb. category is 1st, subcategory is 2nd
// absence of categories is very rare, but possible
var lastCategory = item.Categories.LastOrDefault()?.CategoryId ?? "";
var category = context.Categories.Local.SingleOrDefault(c => c.AudibleCategoryId == lastCategory);
var category = DbContext.Categories.Local.SingleOrDefault(c => c.AudibleCategoryId == lastCategory);
var book = context.Books.Add(new Book(
var book = DbContext.Books.Add(new Book(
new AudibleProductId(item.ProductId),
item.Title,
item.Description,
@@ -101,7 +103,7 @@ namespace DtoImporterService
var publisherName = item.Publisher;
if (!string.IsNullOrWhiteSpace(publisherName))
{
var publisher = context.Contributors.Local.Single(c => publisherName == c.Name);
var publisher = DbContext.Contributors.Local.Single(c => publisherName == c.Name);
book.ReplacePublisher(publisher);
}
@@ -113,7 +115,7 @@ namespace DtoImporterService
return book;
}
private static void updateBook(Item item, Book book, LibationContext context)
private void updateBook(Item item, Book book)
{
// set/update book-specific info which may have changed
book.PictureId = item.PictureId;
@@ -128,7 +130,7 @@ namespace DtoImporterService
{
foreach (var seriesEntry in item.Series)
{
var series = context.Series.Local.Single(s => seriesEntry.SeriesId == s.AudibleSeriesId);
var series = DbContext.Series.Local.Single(s => seriesEntry.SeriesId == s.AudibleSeriesId);
book.UpsertSeries(series, seriesEntry.Index);
}
}

View File

@@ -9,25 +9,27 @@ namespace DtoImporterService
{
public class CategoryImporter : ItemsImporterBase
{
public CategoryImporter(LibationContext context) : base(context) { }
public override IEnumerable<Exception> Validate(IEnumerable<Item> items) => new CategoryValidator().Validate(items);
protected override int DoImport(IEnumerable<Item> items, LibationContext context)
protected override int DoImport(IEnumerable<Item> items)
{
// get distinct
var categoryIds = items.GetCategoriesDistinct().Select(c => c.CategoryId).ToList();
// load db existing => .Local
loadLocal_categories(categoryIds, context);
loadLocal_categories(categoryIds);
// upsert
var categoryPairs = items.GetCategoryPairsDistinct().ToList();
var qtyNew = upsertCategories(categoryPairs, context);
var qtyNew = upsertCategories(categoryPairs);
return qtyNew;
}
private void loadLocal_categories(List<string> categoryIds, LibationContext context)
private void loadLocal_categories(List<string> categoryIds)
{
var localIds = context.Categories.Local.Select(c => c.AudibleCategoryId);
var localIds = DbContext.Categories.Local.Select(c => c.AudibleCategoryId);
var remainingCategoryIds = categoryIds
.Distinct()
.Except(localIds)
@@ -37,11 +39,11 @@ namespace DtoImporterService
// remember to include default/empty/missing
var emptyName = Contributor.GetEmpty().Name;
if (remainingCategoryIds.Any())
context.Categories.Where(c => remainingCategoryIds.Contains(c.AudibleCategoryId) || c.Name == emptyName).ToList();
DbContext.Categories.Where(c => remainingCategoryIds.Contains(c.AudibleCategoryId) || c.Name == emptyName).ToList();
}
// only use after loading contributors => local
private int upsertCategories(List<Ladder[]> categoryPairs, LibationContext context)
private int upsertCategories(List<Ladder[]> categoryPairs)
{
var qtyNew = 0;
@@ -54,12 +56,12 @@ namespace DtoImporterService
Category parentCategory = null;
if (i == 1)
parentCategory = context.Categories.Local.Single(c => c.AudibleCategoryId == pair[0].CategoryId);
parentCategory = DbContext.Categories.Local.Single(c => c.AudibleCategoryId == pair[0].CategoryId);
var category = context.Categories.Local.SingleOrDefault(c => c.AudibleCategoryId == id);
var category = DbContext.Categories.Local.SingleOrDefault(c => c.AudibleCategoryId == id);
if (category is null)
{
category = context.Categories.Add(new Category(new AudibleCategoryId(id), name)).Entity;
category = DbContext.Categories.Add(new Category(new AudibleCategoryId(id), name)).Entity;
qtyNew++;
}

View File

@@ -9,9 +9,11 @@ namespace DtoImporterService
{
public class ContributorImporter : ItemsImporterBase
{
public ContributorImporter(LibationContext context) : base(context) { }
public override IEnumerable<Exception> Validate(IEnumerable<Item> items) => new ContributorValidator().Validate(items);
protected override int DoImport(IEnumerable<Item> items, LibationContext context)
protected override int DoImport(IEnumerable<Item> items)
{
// get distinct
var authors = items.GetAuthorsDistinct().ToList();
@@ -24,23 +26,23 @@ namespace DtoImporterService
.Union(narrators.Select(n => n.Name))
.Where(name => !string.IsNullOrWhiteSpace(name))
.ToList();
loadLocal_contributors(allNames, context);
loadLocal_contributors(allNames);
// upsert
var qtyNew = 0;
qtyNew += upsertPeople(authors, context);
qtyNew += upsertPeople(narrators, context);
qtyNew += upsertPublishers(publishers, context);
qtyNew += upsertPeople(authors);
qtyNew += upsertPeople(narrators);
qtyNew += upsertPublishers(publishers);
return qtyNew;
}
private void loadLocal_contributors(List<string> contributorNames, LibationContext context)
private void loadLocal_contributors(List<string> contributorNames)
{
//// BAD: very inefficient
// var x = context.Contributors.Local.Where(c => !contribNames.Contains(c.Name));
// GOOD: Except() is efficient. Due to hashing, it's close to O(n)
var localNames = context.Contributors.Local.Select(c => c.Name);
var localNames = DbContext.Contributors.Local.Select(c => c.Name);
var remainingContribNames = contributorNames
.Distinct()
.Except(localNames)
@@ -50,20 +52,20 @@ namespace DtoImporterService
// remember to include default/empty/missing
var emptyName = Contributor.GetEmpty().Name;
if (remainingContribNames.Any())
context.Contributors.Where(c => remainingContribNames.Contains(c.Name) || c.Name == emptyName).ToList();
DbContext.Contributors.Where(c => remainingContribNames.Contains(c.Name) || c.Name == emptyName).ToList();
}
// only use after loading contributors => local
private int upsertPeople(List<Person> people, LibationContext context)
private int upsertPeople(List<Person> people)
{
var qtyNew = 0;
foreach (var p in people)
{
var person = context.Contributors.Local.SingleOrDefault(c => c.Name == p.Name);
var person = DbContext.Contributors.Local.SingleOrDefault(c => c.Name == p.Name);
if (person == null)
{
person = context.Contributors.Add(new Contributor(p.Name, p.Asin)).Entity;
person = DbContext.Contributors.Add(new Contributor(p.Name, p.Asin)).Entity;
qtyNew++;
}
}
@@ -72,15 +74,15 @@ namespace DtoImporterService
}
// only use after loading contributors => local
private int upsertPublishers(List<string> publishers, LibationContext context)
private int upsertPublishers(List<string> publishers)
{
var qtyNew = 0;
foreach (var publisherName in publishers)
{
if (context.Contributors.Local.SingleOrDefault(c => c.Name == publisherName) == null)
if (DbContext.Contributors.Local.SingleOrDefault(c => c.Name == publisherName) == null)
{
context.Contributors.Add(new Contributor(publisherName));
DbContext.Contributors.Add(new Contributor(publisherName));
qtyNew++;
}
}

View File

@@ -3,23 +3,25 @@ using System.Collections.Generic;
using System.Linq;
using AudibleApiDTOs;
using DataLayer;
using Dinah.Core;
namespace DtoImporterService
{
public interface IContextRunner<T>
public abstract class ImporterBase<T>
{
public TResult Run<TResult>(Func<T, LibationContext, TResult> func, T param, LibationContext context = null)
{
if (context is null)
{
using (context = LibationContext.Create())
{
var r = Run(func, param, context);
context.SaveChanges();
return r;
}
}
protected LibationContext DbContext { get; }
public ImporterBase(LibationContext context)
{
ArgumentValidator.EnsureNotNull(context, nameof(context));
DbContext = context;
}
/// <summary>LONG RUNNING. call with await Task.Run</summary>
public int Import(T param) => Run(DoImport, param);
public TResult Run<TResult>(Func<T, TResult> func, T param)
{
try
{
var exceptions = Validate(param);
@@ -34,7 +36,7 @@ namespace DtoImporterService
try
{
var result = func(param, context);
var result = func(param);
return result;
}
catch (Exception ex)
@@ -43,18 +45,13 @@ namespace DtoImporterService
throw;
}
}
IEnumerable<Exception> Validate(T param);
}
public abstract class ImporterBase<T> : IContextRunner<T>
{
/// <summary>LONG RUNNING. call with await Task.Run</summary>
public int Import(T param, LibationContext context = null)
=> ((IContextRunner<T>)this).Run(DoImport, param, context);
protected abstract int DoImport(T elements, LibationContext context);
protected abstract int DoImport(T elements);
public abstract IEnumerable<Exception> Validate(T param);
}
public abstract class ItemsImporterBase : ImporterBase<IEnumerable<Item>> { }
public abstract class ItemsImporterBase : ImporterBase<IEnumerable<Item>>
{
public ItemsImporterBase(LibationContext context) : base(context) { }
}
}

View File

@@ -9,27 +9,29 @@ namespace DtoImporterService
{
public class LibraryImporter : ItemsImporterBase
{
public LibraryImporter(LibationContext context) : base(context) { }
public override IEnumerable<Exception> Validate(IEnumerable<Item> items) => new LibraryValidator().Validate(items);
protected override int DoImport(IEnumerable<Item> items, LibationContext context)
protected override int DoImport(IEnumerable<Item> items)
{
new BookImporter().Import(items, context);
new BookImporter(DbContext).Import(items);
var qtyNew = upsertLibraryBooks(items, context);
var qtyNew = upsertLibraryBooks(items);
return qtyNew;
}
private int upsertLibraryBooks(IEnumerable<Item> items, LibationContext context)
private int upsertLibraryBooks(IEnumerable<Item> items)
{
var currentLibraryProductIds = context.Library.Select(l => l.Book.AudibleProductId).ToList();
var currentLibraryProductIds = DbContext.Library.Select(l => l.Book.AudibleProductId).ToList();
var newItems = items.Where(dto => !currentLibraryProductIds.Contains(dto.ProductId)).ToList();
foreach (var newItem in newItems)
{
var libraryBook = new LibraryBook(
context.Books.Local.Single(b => b.AudibleProductId == newItem.ProductId),
DbContext.Books.Local.Single(b => b.AudibleProductId == newItem.ProductId),
newItem.DateAdded);
context.Library.Add(libraryBook);
DbContext.Library.Add(libraryBook);
}
var qtyNew = newItems.Count;

View File

@@ -9,44 +9,46 @@ namespace DtoImporterService
{
public class SeriesImporter : ItemsImporterBase
{
public SeriesImporter(LibationContext context) : base(context) { }
public override IEnumerable<Exception> Validate(IEnumerable<Item> items) => new SeriesValidator().Validate(items);
protected override int DoImport(IEnumerable<Item> items, LibationContext context)
protected override int DoImport(IEnumerable<Item> items)
{
// get distinct
var series = items.GetSeriesDistinct().ToList();
// load db existing => .Local
loadLocal_series(series, context);
loadLocal_series(series);
// upsert
var qtyNew = upsertSeries(series, context);
var qtyNew = upsertSeries(series);
return qtyNew;
}
private void loadLocal_series(List<AudibleApiDTOs.Series> series, LibationContext context)
private void loadLocal_series(List<AudibleApiDTOs.Series> series)
{
var seriesIds = series.Select(s => s.SeriesId).ToList();
var localIds = context.Series.Local.Select(s => s.AudibleSeriesId).ToList();
var localIds = DbContext.Series.Local.Select(s => s.AudibleSeriesId).ToList();
var remainingSeriesIds = seriesIds
.Distinct()
.Except(localIds)
.ToList();
if (remainingSeriesIds.Any())
context.Series.Where(s => remainingSeriesIds.Contains(s.AudibleSeriesId)).ToList();
DbContext.Series.Where(s => remainingSeriesIds.Contains(s.AudibleSeriesId)).ToList();
}
private int upsertSeries(List<AudibleApiDTOs.Series> requestedSeries, LibationContext context)
private int upsertSeries(List<AudibleApiDTOs.Series> requestedSeries)
{
var qtyNew = 0;
foreach (var s in requestedSeries)
{
var series = context.Series.Local.SingleOrDefault(c => c.AudibleSeriesId == s.SeriesId);
var series = DbContext.Series.Local.SingleOrDefault(c => c.AudibleSeriesId == s.SeriesId);
if (series is null)
{
series = context.Series.Add(new DataLayer.Series(new AudibleSeriesId(s.SeriesId))).Entity;
series = DbContext.Series.Add(new DataLayer.Series(new AudibleSeriesId(s.SeriesId))).Entity;
qtyNew++;
}
series.UpdateName(s.SeriesName);

View File

@@ -17,9 +17,9 @@ namespace FileLiberator
/// </summary>
public class BackupBook : IProcessable
{
public event EventHandler<string> Begin;
public event EventHandler<LibraryBook> Begin;
public event EventHandler<string> StatusUpdate;
public event EventHandler<string> Completed;
public event EventHandler<LibraryBook> Completed;
public DownloadBook DownloadBook { get; } = new DownloadBook();
public DecryptBook DecryptBook { get; } = new DecryptBook();
@@ -32,10 +32,7 @@ namespace FileLiberator
// often calls events which prints to forms in the UI context
public async Task<StatusHandler> ProcessAsync(LibraryBook libraryBook)
{
var productId = libraryBook.Book.AudibleProductId;
var displayMessage = $"[{productId}] {libraryBook.Book.Title}";
Begin?.Invoke(this, displayMessage);
Begin?.Invoke(this, libraryBook);
try
{
@@ -61,7 +58,7 @@ namespace FileLiberator
}
finally
{
Completed?.Invoke(this, displayMessage);
Completed?.Invoke(this, libraryBook);
}
}
}

View File

@@ -21,7 +21,7 @@ namespace FileLiberator
/// </summary>
public class DecryptBook : IDecryptable
{
public event EventHandler<string> Begin;
public event EventHandler<LibraryBook> Begin;
public event EventHandler<string> StatusUpdate;
public event EventHandler<string> DecryptBegin;
@@ -32,7 +32,7 @@ namespace FileLiberator
public event EventHandler<int> UpdateProgress;
public event EventHandler<string> DecryptCompleted;
public event EventHandler<string> Completed;
public event EventHandler<LibraryBook> Completed;
public bool Validate(LibraryBook libraryBook)
=> AudibleFileStorage.AAX.Exists(libraryBook.Book.AudibleProductId)
@@ -42,9 +42,7 @@ namespace FileLiberator
// often calls events which prints to forms in the UI context
public async Task<StatusHandler> ProcessAsync(LibraryBook libraryBook)
{
var displayMessage = $"[{libraryBook.Book.AudibleProductId}] {libraryBook.Book.Title}";
Begin?.Invoke(this, displayMessage);
Begin?.Invoke(this, libraryBook);
try
{
@@ -52,7 +50,7 @@ namespace FileLiberator
if (aaxFilename == null)
return new StatusHandler { "aaxFilename parameter is null" };
if (!FileUtility.FileExists(aaxFilename))
if (!File.Exists(aaxFilename))
return new StatusHandler { $"Cannot find AAX file: {aaxFilename}" };
if (AudibleFileStorage.Audio.Exists(libraryBook.Book.AudibleProductId))
return new StatusHandler { "Cannot find decrypt. Final audio file already exists" };
@@ -76,7 +74,7 @@ namespace FileLiberator
}
finally
{
Completed?.Invoke(this, displayMessage);
Completed?.Invoke(this, libraryBook);
}
}
@@ -101,11 +99,9 @@ namespace FileLiberator
// REAL WORK DONE HERE
var success = await Task.Run(() => converter.Run());
// decrypt failed
if (!success)
{
Console.WriteLine("decrypt failed");
return null;
}
Configuration.Instance.DecryptKey = converter.decryptKey;

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,10 +6,10 @@ using Dinah.Core.Net.Http;
namespace FileLiberator
{
public abstract class DownloadableBase : IDownloadable
public abstract class DownloadableBase : IDownloadableProcessable
{
public event EventHandler<string> Begin;
public event EventHandler<string> Completed;
public event EventHandler<LibraryBook> Begin;
public event EventHandler<LibraryBook> Completed;
public event EventHandler<string> DownloadBegin;
public event EventHandler<DownloadProgress> DownloadProgressChanged;
@@ -26,9 +26,7 @@ namespace FileLiberator
// often calls events which prints to forms in the UI context
public async Task<StatusHandler> ProcessAsync(LibraryBook libraryBook)
{
var displayMessage = $"[{libraryBook.Book.AudibleProductId}] {libraryBook.Book.Title}";
Begin?.Invoke(this, displayMessage);
Begin?.Invoke(this, libraryBook);
try
{
@@ -36,7 +34,7 @@ namespace FileLiberator
}
finally
{
Completed?.Invoke(this, displayMessage);
Completed?.Invoke(this, libraryBook);
}
}

View File

@@ -1,11 +1,12 @@
using System;
using Dinah.Core.Net.Http;
namespace FileLiberator
{
public interface IDownloadable : IProcessable
public interface IDownloadable
{
event EventHandler<string> DownloadBegin;
event EventHandler<Dinah.Core.Net.Http.DownloadProgress> DownloadProgressChanged;
event EventHandler<DownloadProgress> DownloadProgressChanged;
event EventHandler<string> DownloadCompleted;
}
}

View File

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

View File

@@ -7,12 +7,12 @@ namespace FileLiberator
{
public interface IProcessable
{
event EventHandler<string> Begin;
event EventHandler<LibraryBook> Begin;
/// <summary>General string message to display. DON'T rely on this for success, failure, or control logic</summary>
event EventHandler<string> StatusUpdate;
event EventHandler<string> Completed;
event EventHandler<LibraryBook> Completed;
/// <returns>True == Valid</returns>
bool Validate(LibraryBook libraryBook);

View File

@@ -1,5 +1,7 @@
using System;
using System.Linq;
using System.Threading.Tasks;
using ApplicationServices;
using DataLayer;
using Dinah.Core.ErrorHandling;
@@ -17,28 +19,51 @@ namespace FileLiberator
/// <returns>Returns either the status handler from the process, or null if all books have been processed</returns>
public static async Task<StatusHandler> ProcessFirstValidAsync(this IProcessable processable)
{
var libraryBook = processable.GetNextValid();
var libraryBook = processable.getNextValidBook();
if (libraryBook == null)
return null;
// this should never happen. check anyway. ProcessFirstValidAsync returning null is the signal that we're done. we can't let another IProcessable accidentally send this commans
var status = await processable.ProcessAsync(libraryBook);
return await processBookAsync(processable, libraryBook);
}
/// <summary>Process the first valid product. Create default context</summary>
/// <returns>Returns either the status handler from the process, or null if all books have been processed</returns>
public static async Task<StatusHandler> ProcessSingleAsync(this IProcessable processable, string productId)
{
using var context = DbContexts.GetContext();
var libraryBook = context
.Library
.GetLibrary()
.SingleOrDefault(lb => lb.Book.AudibleProductId == productId);
if (libraryBook == null)
return null;
if (!processable.Validate(libraryBook))
return new StatusHandler { "Validation failed" };
return await processBookAsync(processable, libraryBook);
}
private static async Task<StatusHandler> processBookAsync(IProcessable processable, LibraryBook libraryBook)
{
// this should never happen. check anyway. ProcessFirstValidAsync returning null is the signal that we're done. we can't let another IProcessable accidentally send this command
var status = await processable.ProcessAsync(libraryBook);
if (status == null)
throw new Exception("Processable should never return a null status");
return status;
}
public static LibraryBook GetNextValid(this IProcessable processable)
private static LibraryBook getNextValidBook(this IProcessable processable)
{
var libraryBooks = LibraryQueries.GetLibrary_Flat_NoTracking();
var libraryBooks = DbContexts.GetContext().GetLibrary_Flat_NoTracking();
foreach (var libraryBook in libraryBooks)
if (processable.Validate(libraryBook))
return libraryBook;
return null;
}
}
public static async Task<StatusHandler> TryProcessAsync(this IProcessable processable, LibraryBook libraryBook)
=> processable.Validate(libraryBook)

View File

@@ -5,7 +5,7 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Polly" Version="7.1.1" />
<PackageReference Include="Polly" Version="7.2.0" />
</ItemGroup>
<ItemGroup>

View File

@@ -39,7 +39,7 @@ namespace FileManager
Configuration.Instance.DecryptInProgressEnum = "WinTemp";
var M4bRootDir
= Configuration.Instance.DecryptInProgressEnum == "WinTemp" // else "LibationFiles"
? Configuration.Instance.WinTemp
? Configuration.WinTemp
: Configuration.Instance.LibationFiles;
DecryptInProgress = Path.Combine(M4bRootDir, "DecryptInProgress");
Directory.CreateDirectory(DecryptInProgress);
@@ -50,7 +50,7 @@ namespace FileManager
Configuration.Instance.DownloadsInProgressEnum = "WinTemp";
var AaxRootDir
= Configuration.Instance.DownloadsInProgressEnum == "WinTemp" // else "LibationFiles"
? Configuration.Instance.WinTemp
? Configuration.WinTemp
: Configuration.Instance.LibationFiles;
DownloadsInProgress = Path.Combine(AaxRootDir, "DownloadsInProgress");
Directory.CreateDirectory(DownloadsInProgress);

View File

@@ -4,6 +4,8 @@ using System.ComponentModel;
using System.IO;
using System.Linq;
using Dinah.Core;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
namespace FileManager
{
@@ -31,162 +33,164 @@ namespace FileManager
*/
#endregion
private const string configFilename = "LibationSettings.json";
private PersistentDictionary persistentDictionary;
private PersistentDictionary persistentDictionary { get; }
public bool FilesExist
=> File.Exists(APPSETTINGS_JSON)
&& File.Exists(SettingsJsonPath)
&& Directory.Exists(LibationFiles)
&& Directory.Exists(Books);
[Description("Location of the configuration file where these settings are saved. Please do not edit this file directly while Libation is running.")]
public string Filepath { get; }
public string SettingsJsonPath => Path.Combine(LibationFiles, "Settings.json");
[Description("[Advanced. Leave alone in most cases.] Your user-specific key used to decrypt your audible files (*.aax) into audio files you can use anywhere (*.m4b)")]
[Description("Your user-specific key used to decrypt your audible files (*.aax) into audio files you can use anywhere (*.m4b). Leave alone in most cases")]
public string DecryptKey
{
get => persistentDictionary[nameof(DecryptKey)];
set => persistentDictionary[nameof(DecryptKey)] = value;
get => persistentDictionary.GetString(nameof(DecryptKey));
set => persistentDictionary.Set(nameof(DecryptKey), value);
}
[Description("Location for book storage. Includes destination of newly liberated books")]
public string Books
{
get => persistentDictionary[nameof(Books)];
set => persistentDictionary[nameof(Books)] = value;
get => persistentDictionary.GetString(nameof(Books));
set => persistentDictionary.Set(nameof(Books), value);
}
public string WinTemp { get; } = Path.Combine(Path.GetTempPath(), "Libation");
private const string APP_DIR = "AppDir";
public static string AppDir { get; } = Path.GetFullPath(Path.Combine(Path.GetDirectoryName(Exe.FileLocationOnDisk), LIBATION_FILES));
public static string MyDocs { get; } = Path.GetFullPath(Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments), LIBATION_FILES));
public static string WinTemp { get; } = Path.GetFullPath(Path.Combine(Path.GetTempPath(), "Libation"));
[Description("Location for storage of program-created files")]
public string LibationFiles
private Dictionary<string, string> wellKnownPaths { get; } = new Dictionary<string, string>
{
get => persistentDictionary[nameof(LibationFiles)];
set => persistentDictionary[nameof(LibationFiles)] = value;
}
[APP_DIR] = AppDir,
["MyDocs"] = MyDocs,
["WinTemp"] = WinTemp
};
private string libationFilesPathCache;
// default setting and directory creation occur in class responsible for files.
// config class is only responsible for path. not responsible for setting defaults, dir validation, or dir creation
// exceptions: appsettings.json, LibationFiles dir, Settings.json
// temp/working dir(s) should be outside of dropbox
[Description("Temporary location of files while they're in process of being downloaded.\r\nWhen download is complete, the final file will be in [LibationFiles]\\DownloadsFinal")]
public string DownloadsInProgressEnum
{
get => persistentDictionary[nameof(DownloadsInProgressEnum)];
set => persistentDictionary[nameof(DownloadsInProgressEnum)] = value;
get => persistentDictionary.GetString(nameof(DownloadsInProgressEnum));
set => persistentDictionary.Set(nameof(DownloadsInProgressEnum), value);
}
// temp/working dir(s) should be outside of dropbox
[Description("Temporary location of files while they're in process of being decrypted.\r\nWhen decryption is complete, the final file will be in Books location")]
public string DecryptInProgressEnum
{
get => persistentDictionary[nameof(DecryptInProgressEnum)];
set => persistentDictionary[nameof(DecryptInProgressEnum)] = value;
get => persistentDictionary.GetString(nameof(DecryptInProgressEnum));
set => persistentDictionary.Set(nameof(DecryptInProgressEnum), value);
}
public string LocaleCountryCode
{
get => persistentDictionary[nameof(LocaleCountryCode)];
set => persistentDictionary[nameof(LocaleCountryCode)] = value;
}
public string LocaleCountryCode
{
get => persistentDictionary.GetString(nameof(LocaleCountryCode));
set => persistentDictionary.Set(nameof(LocaleCountryCode), value);
}
// note: any potential file manager static ctors can't compensate if storage dir is changed at run time via settings. this is partly bad architecture. but the side effect is desirable. if changing LibationFiles location: restart app
// singleton stuff
public static Configuration Instance { get; } = new Configuration();
private Configuration()
private Configuration() { }
private const string APPSETTINGS_JSON = "appsettings.json";
private const string LIBATION_FILES = "LibationFiles";
[Description("Location for storage of program-created files")]
public string LibationFiles => libationFilesPathCache ?? getLibationFiles();
private string getLibationFiles()
{
Filepath = getPath();
var value = getLiberationFilesSettingFromJson();
// load json values into memory
persistentDictionary = new PersistentDictionary(Filepath);
ensureDictionaryEntries();
// this looks weird but is correct for translating wellKnownPaths
if (wellKnownPaths.ContainsKey(value))
value = wellKnownPaths[value];
// setUserFilesDirectoryDefault
// don't create dir. dir creation is the responsibility of places that use the dir
if (string.IsNullOrWhiteSpace(LibationFiles))
LibationFiles = Path.Combine(Path.GetDirectoryName(Exe.FileLocationOnDisk), "Libation");
// must write here before SettingsJsonPath in next step reads cache
libationFilesPathCache = value;
// load json values into memory. create if not exists
persistentDictionary = new PersistentDictionary(SettingsJsonPath);
return libationFilesPathCache;
}
private string getLiberationFilesSettingFromJson()
{
try
{
if (File.Exists(APPSETTINGS_JSON))
{
var appSettingsContents = File.ReadAllText(APPSETTINGS_JSON);
var jObj = JObject.Parse(appSettingsContents);
if (jObj.ContainsKey(LIBATION_FILES))
{
var value = jObj[LIBATION_FILES].Value<string>();
// do not check whether directory exists. special/meta directory (eg: AppDir) is valid
if (!string.IsNullOrWhiteSpace(value))
return value;
}
}
}
catch { }
File.WriteAllText(APPSETTINGS_JSON, new JObject { { LIBATION_FILES, APP_DIR } }.ToString(Formatting.Indented));
return APP_DIR;
}
public object GetObject(string propertyName) => persistentDictionary.GetObject(propertyName);
public void SetObject(string propertyName, object newValue) => persistentDictionary.Set(propertyName, newValue);
public void SetWithJsonPath(string jsonPath, string propertyName, string newValue) => persistentDictionary.SetWithJsonPath(jsonPath, propertyName, newValue);
public static string GetDescription(string propertyName)
{
var attribute = typeof(Configuration)
.GetProperty(propertyName)
?.GetCustomAttributes(typeof(DescriptionAttribute), true)
.SingleOrDefault()
as DescriptionAttribute;
var attribute = typeof(Configuration)
.GetProperty(propertyName)
?.GetCustomAttributes(typeof(DescriptionAttribute), true)
.SingleOrDefault()
as DescriptionAttribute;
return attribute?.Description;
}
return attribute?.Description;
}
private static string getPath()
public bool TrySetLibationFiles(string directory)
{
// search folders for config file. accept the first match
var defaultdir = Path.GetDirectoryName(Exe.FileLocationOnDisk);
if (!Directory.Exists(directory) && !wellKnownPaths.ContainsKey(directory))
return false;
var baseDirs = new HashSet<string>
// if moving from default, delete old settings file and dir (if empty)
if (LibationFiles.EqualsInsensitive(AppDir))
{
defaultdir,
getNonDevelopmentDir(defaultdir),
Environment.GetFolderPath(Environment.SpecialFolder.Personal)
};
var subDirs = baseDirs.Select(dir => Path.Combine(dir, "Libation"));
var dirs = baseDirs.Concat(subDirs).ToList();
foreach (var dir in dirs)
{
var f = Path.Combine(dir, configFilename);
if (File.Exists(f))
return f;
File.Delete(SettingsJsonPath);
System.Threading.Thread.Sleep(100);
if (!Directory.EnumerateDirectories(AppDir).Any() && !Directory.EnumerateFiles(AppDir).Any())
Directory.Delete(AppDir);
}
return Path.Combine(defaultdir, configFilename);
}
private static string getNonDevelopmentDir(string path)
{
// examples:
// \Libation\Core2_0\bin\Debug\netcoreapp3.0
// \Libation\StndLib\bin\Debug\netstandard2.1
// \Libation\MyWnfrm\bin\Debug
// \Libation\Core2_0\bin\Release\netcoreapp3.0
// \Libation\StndLib\bin\Release\netstandard2.1
// \Libation\MyWnfrm\bin\Release
libationFilesPathCache = null;
var curr = new DirectoryInfo(path);
if (!curr.Name.EqualsInsensitive("debug") && !curr.Name.EqualsInsensitive("release") && !curr.Name.StartsWithInsensitive("netcoreapp") && !curr.Name.StartsWithInsensitive("netstandard"))
return path;
var contents = File.ReadAllText(APPSETTINGS_JSON);
var jObj = JObject.Parse(contents);
// get out of netcore/standard dir => debug
if (curr.Name.StartsWithInsensitive("netcoreapp") || curr.Name.StartsWithInsensitive("netstandard"))
curr = curr.Parent;
jObj[LIBATION_FILES] = directory;
if (!curr.Name.EqualsInsensitive("debug") && !curr.Name.EqualsInsensitive("release"))
return path;
var output = JsonConvert.SerializeObject(jObj, Formatting.Indented);
File.WriteAllText(APPSETTINGS_JSON, output);
// get out of debug => bin
curr = curr.Parent;
if (!curr.Name.EqualsInsensitive("bin"))
return path;
// get out of bin
curr = curr.Parent;
// get out of csproj-level dir
curr = curr.Parent;
// curr should now be sln-level dir
return curr.FullName;
}
private void ensureDictionaryEntries()
{
var stringProperties = getDictionaryProperties().Select(p => p.Name).ToList();
var missingKeys = stringProperties.Except(persistentDictionary.Keys).ToArray();
persistentDictionary.AddKeys(missingKeys);
}
private IEnumerable<System.Reflection.PropertyInfo> dicPropertiesCache;
private IEnumerable<System.Reflection.PropertyInfo> getDictionaryProperties()
{
if (dicPropertiesCache == null)
dicPropertiesCache = PersistentDictionary.GetPropertiesToPersist(this.GetType());
return dicPropertiesCache;
return true;
}
}
}

View File

@@ -23,7 +23,7 @@ namespace FileManager
static FilePathCache()
{
// load json into memory. if file doesn't exist, nothing to do. save() will create if needed
if (FileUtility.FileExists(JsonFile))
if (File.Exists(JsonFile))
{
var list = JsonConvert.DeserializeObject<List<CacheEntry>>(File.ReadAllText(JsonFile));
cache = new Cache<CacheEntry>(list);
@@ -39,7 +39,7 @@ namespace FileManager
if (entry == null)
return null;
if (!FileUtility.FileExists(entry.Path))
if (!File.Exists(entry.Path))
{
remove(entry);
return null;
@@ -56,7 +56,7 @@ namespace FileManager
public static void Upsert(string id, FileType type, string path)
{
if (!FileUtility.FileExists(path))
if (!File.Exists(path))
throw new FileNotFoundException("Cannot add path to cache. File not found");
var entry = cache.SingleOrDefault(i => i.Id == id && i.FileType == type);

View File

@@ -1,32 +1,10 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
namespace FileManager
{
public static class FileUtility
{
// a replacement for File.Exists() which allows long paths
// not needed in .net-core
public static bool FileExists(string path)
{
var basic = File.Exists(path);
if (basic)
return true;
// character cutoff is usually 269 but this isn't a hard number. there are edgecases which shorted the threshold
if (path.Length < 260)
return false;
// try long name prefix:
// \\?\
// https://blogs.msdn.microsoft.com/jeremykuhne/2016/06/21/more-on-new-net-path-handling/
path = @"\\?\" + path;
return File.Exists(path);
}
public static string GetValidFilename(string dirFullPath, string filename, string extension, params string[] metadataSuffixes)
{
if (string.IsNullOrWhiteSpace(dirFullPath))
@@ -51,7 +29,7 @@ namespace FileManager
// ensure uniqueness
var fullfilename = Path.Combine(dirFullPath, filename + extension);
var i = 0;
while (FileExists(fullfilename))
while (File.Exists(fullfilename))
fullfilename = Path.Combine(dirFullPath, filename + $" ({++i})" + extension);
return fullfilename;

View File

@@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.IO;
using System.Linq;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
namespace FileManager
{
@@ -10,70 +11,114 @@ namespace FileManager
{
public string Filepath { get; }
// forgiving -- doesn't drop settings. old entries will continue to be persisted even if not publicly visible
private Dictionary<string, string> settingsDic { get; }
public string this[string key]
{
get => settingsDic[key];
set
{
if (settingsDic.ContainsKey(key) && settingsDic[key] == value)
return;
settingsDic[key] = value;
// auto-save to file
save();
}
}
// optimize for strings. expectation is most settings will be strings and a rare exception will be something else
private Dictionary<string, string> stringCache { get; } = new Dictionary<string, string>();
private Dictionary<string, object> objectCache { get; } = new Dictionary<string, object>();
public PersistentDictionary(string filepath)
{
Filepath = filepath;
// not found. create blank file
if (!File.Exists(Filepath))
{
File.WriteAllText(Filepath, "{}");
// give system time to create file before first use
System.Threading.Thread.Sleep(100);
}
settingsDic = JsonConvert.DeserializeObject<Dictionary<string, string>>(File.ReadAllText(Filepath));
}
public IEnumerable<string> Keys => settingsDic.Keys.Cast<string>();
public void AddKeys(params string[] keys)
{
if (keys == null || keys.Length == 0)
if (File.Exists(Filepath))
return;
foreach (var key in keys)
settingsDic.Add(key, null);
save();
// will create any missing directories, incl subdirectories. if all already exist: no action
Directory.CreateDirectory(Path.GetDirectoryName(filepath));
File.WriteAllText(Filepath, "{}");
System.Threading.Thread.Sleep(100);
}
public string GetString(string propertyName)
{
if (!stringCache.ContainsKey(propertyName))
{
var jObject = readFile();
stringCache[propertyName] = jObject.ContainsKey(propertyName) ? jObject[propertyName].Value<string>() : null;
}
return stringCache[propertyName];
}
public T Get<T>(string propertyName) where T : class
=> GetObject(propertyName) is T obj ? obj : default;
public object GetObject(string propertyName)
{
if (!objectCache.ContainsKey(propertyName))
{
var jObject = readFile();
objectCache[propertyName] = jObject.ContainsKey(propertyName) ? jObject[propertyName].Value<object>() : null;
}
return objectCache[propertyName];
}
private object locker { get; } = new object();
private void save()
public void Set(string propertyName, string newValue)
{
lock (locker)
File.WriteAllText(Filepath, JsonConvert.SerializeObject(settingsDic, Formatting.Indented));
// only do this check in string cache, NOT object cache
if (stringCache[propertyName] == newValue)
return;
// set cache
stringCache[propertyName] = newValue;
writeFile(propertyName, newValue);
}
public static IEnumerable<System.Reflection.PropertyInfo> GetPropertiesToPersist(Type type)
=> type
.GetProperties(System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance)
.Where(p =>
// string properties only
p.PropertyType == typeof(string)
// exclude indexer
&& p.GetIndexParameters().Length == 0
// exclude read-only, write-only
&& p.GetGetMethod(false) != null
&& p.GetSetMethod(false) != null
).ToList();
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();
jObject[propertyName] = 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)
{
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));
}
}
private JObject readFile()
{
var settingsJsonContents = File.ReadAllText(Filepath);
var jObject = JsonConvert.DeserializeObject<JObject>(settingsJsonContents);
return jObject;
}
}
}

View File

@@ -47,7 +47,7 @@ namespace FileManager
{
var path = getPath(def);
cache[def]
= FileUtility.FileExists(path)
= File.Exists(path)
? File.ReadAllBytes(path)
: null;
}

View File

@@ -22,7 +22,7 @@ namespace FileManager
static QuickFilters()
{
// load json into memory. if file doesn't exist, nothing to do. save() will create if needed
if (FileUtility.FileExists(JsonFile))
if (File.Exists(JsonFile))
inMemoryState = JsonConvert.DeserializeObject<FilterState>(File.ReadAllText(JsonFile));
}

View File

@@ -0,0 +1,11 @@
using System.IO;
namespace FileManager
{
public static class SqliteStorage
{
// not customizable. don't move to config
private static string databasePath => Path.Combine(Configuration.Instance.LibationFiles, "LibationContext.db");
public static string ConnectionString => $"Data Source={databasePath};Foreign Keys=False;";
}
}

View File

@@ -53,7 +53,7 @@ namespace FileManager
{
if (cache is null)
lock (locker)
cache = !FileUtility.FileExists(TagsFile)
cache = !File.Exists(TagsFile)
? new Dictionary<string, string>()
: JsonConvert.DeserializeObject<Dictionary<string, string>>(File.ReadAllText(TagsFile));
}

View File

@@ -44,7 +44,7 @@ namespace InternalUtilities
if (!libResult.Items.Any())
break;
else
Serilog.Log.Logger.Debug($"Page {i}: {libResult.Items.Length} results");
Serilog.Log.Logger.Information($"Page {i}: {libResult.Items.Length} results");
allItems.AddRange(libResult.Items);
}

View File

@@ -50,7 +50,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AudibleApi", "..\audible ap
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AudibleApi.Tests", "..\audible api\AudibleApi\_Tests\AudibleApi.Tests\AudibleApi.Tests.csproj", "{111420E2-D4F0-4068-B46A-C4B6DCC823DC}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LibationWinForm", "LibationWinForm\LibationWinForm.csproj", "{635F00E1-AAD1-45F7-BEB7-D909AD33B9F6}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LibationWinForms", "LibationWinForms\LibationWinForms.csproj", "{635F00E1-AAD1-45F7-BEB7-D909AD33B9F6}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WinFormsDesigner", "WinFormsDesigner\WinFormsDesigner.csproj", "{0807616A-A77A-4B08-A65A-1582B09E114B}"
EndProject
@@ -78,7 +78,9 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ApplicationServices", "Appl
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Dinah.Core.WindowsDesktop", "..\Dinah.Core\Dinah.Core.WindowsDesktop\Dinah.Core.WindowsDesktop.csproj", "{059CE32C-9AD6-45E9-A166-790DFFB0B730}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WindowsDesktopUtilities", "WindowsDesktopUtilities\WindowsDesktopUtilities.csproj", "{E7EFD64D-6630-4426-B09C-B6862A92E3FD}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WindowsDesktopUtilities", "WindowsDesktopUtilities\WindowsDesktopUtilities.csproj", "{E7EFD64D-6630-4426-B09C-B6862A92E3FD}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LibationLauncher", "LibationLauncher\LibationLauncher.csproj", "{F3B04A3A-20C8-4582-A54A-715AF6A5D859}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
@@ -194,6 +196,10 @@ Global
{E7EFD64D-6630-4426-B09C-B6862A92E3FD}.Debug|Any CPU.Build.0 = Debug|Any CPU
{E7EFD64D-6630-4426-B09C-B6862A92E3FD}.Release|Any CPU.ActiveCfg = Release|Any CPU
{E7EFD64D-6630-4426-B09C-B6862A92E3FD}.Release|Any CPU.Build.0 = Release|Any CPU
{F3B04A3A-20C8-4582-A54A-715AF6A5D859}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{F3B04A3A-20C8-4582-A54A-715AF6A5D859}.Debug|Any CPU.Build.0 = Debug|Any CPU
{F3B04A3A-20C8-4582-A54A-715AF6A5D859}.Release|Any CPU.ActiveCfg = Release|Any CPU
{F3B04A3A-20C8-4582-A54A-715AF6A5D859}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -226,6 +232,7 @@ Global
{B95650EA-25F0-449E-BA5D-99126BC5D730} = {41CDCC73-9B81-49DD-9570-C54406E852AF}
{059CE32C-9AD6-45E9-A166-790DFFB0B730} = {43E3ACB3-E0BC-4370-8DBB-E3720C8C8FD1}
{E7EFD64D-6630-4426-B09C-B6862A92E3FD} = {F0CBB7A7-D3FB-41FF-8F47-CF3F6A592249}
{F3B04A3A-20C8-4582-A54A-715AF6A5D859} = {8679CAC8-9164-4007-BDD2-F004810EDA14}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {615E00ED-BAEF-4E8E-A92A-9B82D87942A9}

4
LibationLauncher/.msbump Normal file
View File

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

View File

@@ -0,0 +1,31 @@
<?xml version="1.0" encoding="utf-8"?>
<Project Sdk="Microsoft.NET.Sdk.WindowsDesktop">
<PropertyGroup>
<OutputType>WinExe</OutputType>
<TargetFramework>netcoreapp3.1</TargetFramework>
<UseWindowsForms>true</UseWindowsForms>
<ApplicationIcon>libation.ico</ApplicationIcon>
<AssemblyName>Libation</AssemblyName>
<PublishTrimmed>true</PublishTrimmed>
<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>
</Project>

View File

@@ -0,0 +1,255 @@
using System;
using System.IO;
using System.Linq;
using System.Windows.Forms;
using FileManager;
using LibationWinForms;
using LibationWinForms.Dialogs;
using Microsoft.Extensions.Configuration;
using Newtonsoft.Json.Linq;
using Serilog;
namespace LibationLauncher
{
static class Program
{
[STAThread]
static void Main()
{
Application.SetHighDpiMode(HighDpiMode.SystemAware);
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
createSettings();
ensureLoggingConfig();
ensureSerilogConfig();
configureLogging();
checkForUpdate();
logStartupState();
Application.Run(new Form1());
}
private static void createSettings()
{
var config = Configuration.Instance;
if (configSetupIsComplete(config))
return;
var isAdvanced = false;
var setupDialog = new SetupDialog();
setupDialog.NoQuestionsBtn_Click += (_, __) =>
{
config.DecryptKey ??= "";
config.LocaleCountryCode ??= "us";
config.DownloadsInProgressEnum ??= "WinTemp";
config.DecryptInProgressEnum ??= "WinTemp";
config.Books ??= Configuration.AppDir;
};
// setupDialog.BasicBtn_Click += (_, __) => // no action needed
setupDialog.AdvancedBtn_Click += (_, __) => isAdvanced = true;
setupDialog.ShowDialog();
if (isAdvanced)
{
var dialog = new LibationFilesDialog();
if (dialog.ShowDialog() != DialogResult.OK)
MessageBox.Show("Libation Files location not changed");
}
if (configSetupIsComplete(config))
return;
if (new SettingsDialog().ShowDialog() == DialogResult.OK)
return;
MessageBox.Show("Initial set up cancelled.", "Cancelled", MessageBoxButtons.OK, MessageBoxIcon.Warning);
Application.Exit();
Environment.Exit(0);
}
private static bool configSetupIsComplete(Configuration config)
=> config.FilesExist
&& !string.IsNullOrWhiteSpace(config.LocaleCountryCode)
&& !string.IsNullOrWhiteSpace(config.DownloadsInProgressEnum)
&& !string.IsNullOrWhiteSpace(config.DecryptInProgressEnum);
private static string defaultLoggingLevel { get; } = "Information";
private static void ensureLoggingConfig()
{
var config = Configuration.Instance;
if (config.GetObject("Logging") != null)
return;
// "Logging": {
// "LogLevel": {
// "Default": "Debug"
// }
// }
var loggingObj = new JObject
{
{
"LogLevel", new JObject { { "Default", defaultLoggingLevel } }
}
};
config.SetObject("Logging", loggingObj);
}
private static void ensureSerilogConfig()
{
var config = Configuration.Instance;
if (config.GetObject("Serilog") != null)
return;
// default. for reference. output example:
// 2019-11-26 08:48:40.224 -05:00 [DBG] Begin Libation
var default_outputTemplate = "{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} [{Level:u3}] {Message:lj}{NewLine}{Exception}";
// with class and method info. output example:
// 2019-11-26 08:48:40.224 -05:00 [DBG] (at LibationWinForms.Program.init()) Begin Libation
var code_outputTemplate = "{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} [{Level:u3}] (at {Caller}) {Message:lj}{NewLine}{Exception}";
// "Serilog": {
// "MinimumLevel": "Information"
// "WriteTo": [
// {
// "Name": "Console"
// },
// {
// "Name": "File",
// "Args": {
// "rollingInterval": "Day",
// "outputTemplate": ...
// }
// }
// ],
// "Using": [ "Dinah.Core" ],
// "Enrich": [ "WithCaller" ]
// }
var serilogObj = new JObject
{
{ "MinimumLevel", defaultLoggingLevel },
{ "WriteTo", new JArray
{
new JObject { {"Name", "Console" } },
new JObject
{
{ "Name", "File" },
{ "Args",
new JObject
{
// for this sink to work, a path must be provided. we override this below
{ "path", Path.Combine(Configuration.Instance.LibationFiles, "_Log.log") },
{ "rollingInterval", "Month" },
{ "outputTemplate", code_outputTemplate }
}
}
}
}
},
{ "Using", new JArray{ "Dinah.Core" } }, // dll's name, NOT namespace
{ "Enrich", new JArray{ "WithCaller" } },
};
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.

After

Width:  |  Height:  |  Size: 100 KiB

View File

@@ -3,7 +3,6 @@ using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using DataLayer;
using Dinah.Core;
using FileManager;
@@ -19,6 +18,8 @@ namespace LibationSearchEngine
{
public const Lucene.Net.Util.Version Version = Lucene.Net.Util.Version.LUCENE_30;
private LibationContext context { get; }
// not customizable. don't move to config
private static string SearchEngineDirectory { get; }
= new System.IO.DirectoryInfo(Configuration.Instance.LibationFiles).CreateSubdirectory("SearchEngine").FullName;
@@ -160,7 +161,9 @@ namespace LibationSearchEngine
private Directory getIndex() => FSDirectory.Open(SearchEngineDirectory);
public void CreateNewIndex(bool overwrite = true)
public SearchEngine(LibationContext context) => this.context = context;
public void CreateNewIndex(bool overwrite = true)
{
// 300 products
// 1st run after app is started: 400ms
@@ -172,7 +175,7 @@ namespace LibationSearchEngine
log();
var library = LibraryQueries.GetLibrary_Flat_NoTracking();
var library = context.GetLibrary_Flat_NoTracking();
log();
@@ -233,7 +236,7 @@ namespace LibationSearchEngine
/// <summary>Long running. Use await Task.Run(() => UpdateBook(productId))</summary>
public void UpdateBook(string productId)
{
var libraryBook = LibraryQueries.GetLibraryBook_Flat_NoTracking(productId);
var libraryBook = context.GetLibraryBook_Flat_NoTracking(productId);
var term = new Term(_ID_, productId);
var document = createBookIndexDocument(libraryBook);

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 92 KiB

View File

@@ -1,65 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using DataLayer;
using Dinah.Core.ErrorHandling;
using FileLiberator;
namespace LibationWinForm.BookLiberation
{
public class BookLiberatorControllerExamples
{
async Task BackupBookAsync(string productId)
{
using var context = LibationContext.Create();
var libraryBook = context
.Library
.GetLibrary()
.SingleOrDefault(lb => lb.Book.AudibleProductId == productId);
if (libraryBook == null)
return;
var backupBook = new BackupBook();
backupBook.DownloadBook.Completed += SetBackupCountsAsync;
backupBook.DecryptBook.Completed += SetBackupCountsAsync;
await ProcessValidateLibraryBookAsync(backupBook, libraryBook);
}
static async Task<StatusHandler> ProcessValidateLibraryBookAsync(IProcessable processable, LibraryBook libraryBook)
{
if (!processable.Validate(libraryBook))
return new StatusHandler { "Validation failed" };
return await processable.ProcessAsync(libraryBook);
}
// Download First Book (Download encrypted/DRM file)
async Task DownloadFirstBookAsync()
{
var downloadBook = ProcessorAutomationController.GetWiredUpDownloadBook();
downloadBook.Completed += SetBackupCountsAsync;
await downloadBook.ProcessFirstValidAsync();
}
// Decrypt First Book (Remove DRM from downloaded file)
async Task DecryptFirstBookAsync()
{
var decryptBook = ProcessorAutomationController.GetWiredUpDecryptBook();
decryptBook.Completed += SetBackupCountsAsync;
await decryptBook.ProcessFirstValidAsync();
}
// Backup First Book (Decrypt a non-liberated book. Download if needed)
async Task BackupFirstBookAsync()
{
var backupBook = ProcessorAutomationController.GetWiredUpBackupBook();
backupBook.DownloadBook.Completed += SetBackupCountsAsync;
backupBook.DecryptBook.Completed += SetBackupCountsAsync;
await backupBook.ProcessFirstValidAsync();
}
async void SetBackupCountsAsync(object obj, string str) => throw new NotImplementedException();
}
}

View File

@@ -1,441 +0,0 @@
namespace LibationWinForm
{
partial class SettingsDialog
{
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
#region Windows Form Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
this.settingsFileLbl = new System.Windows.Forms.Label();
this.settingsFileTb = new System.Windows.Forms.TextBox();
this.decryptKeyLbl = new System.Windows.Forms.Label();
this.decryptKeyTb = new System.Windows.Forms.TextBox();
this.booksLocationLbl = new System.Windows.Forms.Label();
this.booksLocationTb = new System.Windows.Forms.TextBox();
this.booksLocationSearchBtn = new System.Windows.Forms.Button();
this.settingsFileDescLbl = new System.Windows.Forms.Label();
this.decryptKeyDescLbl = new System.Windows.Forms.Label();
this.booksLocationDescLbl = new System.Windows.Forms.Label();
this.libationFilesGb = new System.Windows.Forms.GroupBox();
this.libationFilesDescLbl = new System.Windows.Forms.Label();
this.libationFilesCustomBtn = new System.Windows.Forms.Button();
this.libationFilesCustomTb = new System.Windows.Forms.TextBox();
this.libationFilesCustomRb = new System.Windows.Forms.RadioButton();
this.libationFilesMyDocsRb = new System.Windows.Forms.RadioButton();
this.libationFilesRootRb = new System.Windows.Forms.RadioButton();
this.downloadsInProgressGb = new System.Windows.Forms.GroupBox();
this.downloadsInProgressLibationFilesRb = new System.Windows.Forms.RadioButton();
this.downloadsInProgressWinTempRb = new System.Windows.Forms.RadioButton();
this.downloadsInProgressDescLbl = new System.Windows.Forms.Label();
this.decryptInProgressGb = new System.Windows.Forms.GroupBox();
this.decryptInProgressLibationFilesRb = new System.Windows.Forms.RadioButton();
this.decryptInProgressWinTempRb = new System.Windows.Forms.RadioButton();
this.decryptInProgressDescLbl = new System.Windows.Forms.Label();
this.saveBtn = new System.Windows.Forms.Button();
this.cancelBtn = new System.Windows.Forms.Button();
this.audibleLocaleLbl = new System.Windows.Forms.Label();
this.audibleLocaleCb = new System.Windows.Forms.ComboBox();
this.libationFilesGb.SuspendLayout();
this.downloadsInProgressGb.SuspendLayout();
this.decryptInProgressGb.SuspendLayout();
this.SuspendLayout();
//
// settingsFileLbl
//
this.settingsFileLbl.AutoSize = true;
this.settingsFileLbl.Location = new System.Drawing.Point(7, 15);
this.settingsFileLbl.Name = "settingsFileLbl";
this.settingsFileLbl.Size = new System.Drawing.Size(61, 13);
this.settingsFileLbl.TabIndex = 0;
this.settingsFileLbl.Text = "Settings file";
//
// settingsFileTb
//
this.settingsFileTb.Location = new System.Drawing.Point(90, 12);
this.settingsFileTb.Name = "settingsFileTb";
this.settingsFileTb.ReadOnly = true;
this.settingsFileTb.Size = new System.Drawing.Size(698, 20);
this.settingsFileTb.TabIndex = 1;
//
// decryptKeyLbl
//
this.decryptKeyLbl.AutoSize = true;
this.decryptKeyLbl.Location = new System.Drawing.Point(7, 59);
this.decryptKeyLbl.Name = "decryptKeyLbl";
this.decryptKeyLbl.Size = new System.Drawing.Size(64, 13);
this.decryptKeyLbl.TabIndex = 3;
this.decryptKeyLbl.Text = "Decrypt key";
//
// decryptKeyTb
//
this.decryptKeyTb.Location = new System.Drawing.Point(90, 56);
this.decryptKeyTb.Name = "decryptKeyTb";
this.decryptKeyTb.Size = new System.Drawing.Size(100, 20);
this.decryptKeyTb.TabIndex = 4;
//
// booksLocationLbl
//
this.booksLocationLbl.AutoSize = true;
this.booksLocationLbl.Location = new System.Drawing.Point(7, 125);
this.booksLocationLbl.Name = "booksLocationLbl";
this.booksLocationLbl.Size = new System.Drawing.Size(77, 13);
this.booksLocationLbl.TabIndex = 8;
this.booksLocationLbl.Text = "Books location";
//
// booksLocationTb
//
this.booksLocationTb.Location = new System.Drawing.Point(90, 122);
this.booksLocationTb.Name = "booksLocationTb";
this.booksLocationTb.Size = new System.Drawing.Size(657, 20);
this.booksLocationTb.TabIndex = 9;
//
// booksLocationSearchBtn
//
this.booksLocationSearchBtn.Location = new System.Drawing.Point(753, 120);
this.booksLocationSearchBtn.Name = "booksLocationSearchBtn";
this.booksLocationSearchBtn.Size = new System.Drawing.Size(35, 23);
this.booksLocationSearchBtn.TabIndex = 10;
this.booksLocationSearchBtn.Text = "...";
this.booksLocationSearchBtn.UseVisualStyleBackColor = true;
this.booksLocationSearchBtn.Click += new System.EventHandler(this.booksLocationSearchBtn_Click);
//
// settingsFileDescLbl
//
this.settingsFileDescLbl.AutoSize = true;
this.settingsFileDescLbl.Location = new System.Drawing.Point(87, 35);
this.settingsFileDescLbl.Name = "settingsFileDescLbl";
this.settingsFileDescLbl.Size = new System.Drawing.Size(36, 13);
this.settingsFileDescLbl.TabIndex = 2;
this.settingsFileDescLbl.Text = "[desc]";
//
// decryptKeyDescLbl
//
this.decryptKeyDescLbl.AutoSize = true;
this.decryptKeyDescLbl.Location = new System.Drawing.Point(87, 79);
this.decryptKeyDescLbl.Name = "decryptKeyDescLbl";
this.decryptKeyDescLbl.Size = new System.Drawing.Size(36, 13);
this.decryptKeyDescLbl.TabIndex = 5;
this.decryptKeyDescLbl.Text = "[desc]";
//
// booksLocationDescLbl
//
this.booksLocationDescLbl.AutoSize = true;
this.booksLocationDescLbl.Location = new System.Drawing.Point(87, 145);
this.booksLocationDescLbl.Name = "booksLocationDescLbl";
this.booksLocationDescLbl.Size = new System.Drawing.Size(36, 13);
this.booksLocationDescLbl.TabIndex = 11;
this.booksLocationDescLbl.Text = "[desc]";
//
// libationFilesGb
//
this.libationFilesGb.Controls.Add(this.libationFilesDescLbl);
this.libationFilesGb.Controls.Add(this.libationFilesCustomBtn);
this.libationFilesGb.Controls.Add(this.libationFilesCustomTb);
this.libationFilesGb.Controls.Add(this.libationFilesCustomRb);
this.libationFilesGb.Controls.Add(this.libationFilesMyDocsRb);
this.libationFilesGb.Controls.Add(this.libationFilesRootRb);
this.libationFilesGb.Location = new System.Drawing.Point(12, 161);
this.libationFilesGb.Name = "libationFilesGb";
this.libationFilesGb.Size = new System.Drawing.Size(776, 131);
this.libationFilesGb.TabIndex = 12;
this.libationFilesGb.TabStop = false;
this.libationFilesGb.Text = "Libation files";
//
// libationFilesDescLbl
//
this.libationFilesDescLbl.AutoSize = true;
this.libationFilesDescLbl.Location = new System.Drawing.Point(6, 16);
this.libationFilesDescLbl.Name = "libationFilesDescLbl";
this.libationFilesDescLbl.Size = new System.Drawing.Size(36, 13);
this.libationFilesDescLbl.TabIndex = 0;
this.libationFilesDescLbl.Text = "[desc]";
//
// libationFilesCustomBtn
//
this.libationFilesCustomBtn.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right)));
this.libationFilesCustomBtn.Location = new System.Drawing.Point(741, 102);
this.libationFilesCustomBtn.Name = "libationFilesCustomBtn";
this.libationFilesCustomBtn.Size = new System.Drawing.Size(35, 23);
this.libationFilesCustomBtn.TabIndex = 5;
this.libationFilesCustomBtn.Text = "...";
this.libationFilesCustomBtn.UseVisualStyleBackColor = true;
this.libationFilesCustomBtn.Click += new System.EventHandler(this.libationFilesCustomBtn_Click);
//
// libationFilesCustomTb
//
this.libationFilesCustomTb.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left)
| System.Windows.Forms.AnchorStyles.Right)));
this.libationFilesCustomTb.Location = new System.Drawing.Point(29, 104);
this.libationFilesCustomTb.Name = "libationFilesCustomTb";
this.libationFilesCustomTb.Size = new System.Drawing.Size(706, 20);
this.libationFilesCustomTb.TabIndex = 4;
this.libationFilesCustomTb.TextChanged += new System.EventHandler(this.libationFiles_Changed);
//
// libationFilesCustomRb
//
this.libationFilesCustomRb.AutoSize = true;
this.libationFilesCustomRb.Location = new System.Drawing.Point(9, 107);
this.libationFilesCustomRb.Name = "libationFilesCustomRb";
this.libationFilesCustomRb.Size = new System.Drawing.Size(14, 13);
this.libationFilesCustomRb.TabIndex = 3;
this.libationFilesCustomRb.TabStop = true;
this.libationFilesCustomRb.UseVisualStyleBackColor = true;
//
// libationFilesMyDocsRb
//
this.libationFilesMyDocsRb.AutoSize = true;
this.libationFilesMyDocsRb.CheckAlign = System.Drawing.ContentAlignment.TopLeft;
this.libationFilesMyDocsRb.Location = new System.Drawing.Point(9, 68);
this.libationFilesMyDocsRb.Name = "libationFilesMyDocsRb";
this.libationFilesMyDocsRb.Size = new System.Drawing.Size(111, 30);
this.libationFilesMyDocsRb.TabIndex = 2;
this.libationFilesMyDocsRb.TabStop = true;
this.libationFilesMyDocsRb.Text = "[desc]\r\n[myDocs\\Libation]";
this.libationFilesMyDocsRb.UseVisualStyleBackColor = true;
this.libationFilesMyDocsRb.CheckedChanged += new System.EventHandler(this.libationFiles_Changed);
//
// libationFilesRootRb
//
this.libationFilesRootRb.AutoSize = true;
this.libationFilesRootRb.CheckAlign = System.Drawing.ContentAlignment.TopLeft;
this.libationFilesRootRb.Location = new System.Drawing.Point(9, 32);
this.libationFilesRootRb.Name = "libationFilesRootRb";
this.libationFilesRootRb.Size = new System.Drawing.Size(113, 30);
this.libationFilesRootRb.TabIndex = 1;
this.libationFilesRootRb.TabStop = true;
this.libationFilesRootRb.Text = "[desc]\r\n[exeRoot\\Libation]";
this.libationFilesRootRb.UseVisualStyleBackColor = true;
this.libationFilesRootRb.CheckedChanged += new System.EventHandler(this.libationFiles_Changed);
//
// downloadsInProgressGb
//
this.downloadsInProgressGb.Controls.Add(this.downloadsInProgressLibationFilesRb);
this.downloadsInProgressGb.Controls.Add(this.downloadsInProgressWinTempRb);
this.downloadsInProgressGb.Controls.Add(this.downloadsInProgressDescLbl);
this.downloadsInProgressGb.Location = new System.Drawing.Point(12, 298);
this.downloadsInProgressGb.Name = "downloadsInProgressGb";
this.downloadsInProgressGb.Size = new System.Drawing.Size(776, 117);
this.downloadsInProgressGb.TabIndex = 13;
this.downloadsInProgressGb.TabStop = false;
this.downloadsInProgressGb.Text = "Downloads in progress";
//
// downloadsInProgressLibationFilesRb
//
this.downloadsInProgressLibationFilesRb.AutoSize = true;
this.downloadsInProgressLibationFilesRb.CheckAlign = System.Drawing.ContentAlignment.TopLeft;
this.downloadsInProgressLibationFilesRb.Location = new System.Drawing.Point(9, 81);
this.downloadsInProgressLibationFilesRb.Name = "downloadsInProgressLibationFilesRb";
this.downloadsInProgressLibationFilesRb.Size = new System.Drawing.Size(193, 30);
this.downloadsInProgressLibationFilesRb.TabIndex = 2;
this.downloadsInProgressLibationFilesRb.TabStop = true;
this.downloadsInProgressLibationFilesRb.Text = "[desc]\r\n[libationFiles\\DownloadsInProgress]";
this.downloadsInProgressLibationFilesRb.UseVisualStyleBackColor = true;
//
// downloadsInProgressWinTempRb
//
this.downloadsInProgressWinTempRb.AutoSize = true;
this.downloadsInProgressWinTempRb.CheckAlign = System.Drawing.ContentAlignment.TopLeft;
this.downloadsInProgressWinTempRb.Location = new System.Drawing.Point(9, 45);
this.downloadsInProgressWinTempRb.Name = "downloadsInProgressWinTempRb";
this.downloadsInProgressWinTempRb.Size = new System.Drawing.Size(182, 30);
this.downloadsInProgressWinTempRb.TabIndex = 1;
this.downloadsInProgressWinTempRb.TabStop = true;
this.downloadsInProgressWinTempRb.Text = "[desc]\r\n[winTemp\\DownloadsInProgress]";
this.downloadsInProgressWinTempRb.UseVisualStyleBackColor = true;
//
// downloadsInProgressDescLbl
//
this.downloadsInProgressDescLbl.AutoSize = true;
this.downloadsInProgressDescLbl.Location = new System.Drawing.Point(6, 16);
this.downloadsInProgressDescLbl.Name = "downloadsInProgressDescLbl";
this.downloadsInProgressDescLbl.Size = new System.Drawing.Size(38, 26);
this.downloadsInProgressDescLbl.TabIndex = 0;
this.downloadsInProgressDescLbl.Text = "[desc]\r\n[line 2]";
//
// decryptInProgressGb
//
this.decryptInProgressGb.Controls.Add(this.decryptInProgressLibationFilesRb);
this.decryptInProgressGb.Controls.Add(this.decryptInProgressWinTempRb);
this.decryptInProgressGb.Controls.Add(this.decryptInProgressDescLbl);
this.decryptInProgressGb.Location = new System.Drawing.Point(12, 421);
this.decryptInProgressGb.Name = "decryptInProgressGb";
this.decryptInProgressGb.Size = new System.Drawing.Size(776, 117);
this.decryptInProgressGb.TabIndex = 14;
this.decryptInProgressGb.TabStop = false;
this.decryptInProgressGb.Text = "Decrypt in progress";
//
// decryptInProgressLibationFilesRb
//
this.decryptInProgressLibationFilesRb.AutoSize = true;
this.decryptInProgressLibationFilesRb.CheckAlign = System.Drawing.ContentAlignment.TopLeft;
this.decryptInProgressLibationFilesRb.Location = new System.Drawing.Point(6, 81);
this.decryptInProgressLibationFilesRb.Name = "decryptInProgressLibationFilesRb";
this.decryptInProgressLibationFilesRb.Size = new System.Drawing.Size(177, 30);
this.decryptInProgressLibationFilesRb.TabIndex = 2;
this.decryptInProgressLibationFilesRb.TabStop = true;
this.decryptInProgressLibationFilesRb.Text = "[desc]\r\n[libationFiles\\DecryptInProgress]";
this.decryptInProgressLibationFilesRb.UseVisualStyleBackColor = true;
//
// decryptInProgressWinTempRb
//
this.decryptInProgressWinTempRb.AutoSize = true;
this.decryptInProgressWinTempRb.CheckAlign = System.Drawing.ContentAlignment.TopLeft;
this.decryptInProgressWinTempRb.Location = new System.Drawing.Point(6, 45);
this.decryptInProgressWinTempRb.Name = "decryptInProgressWinTempRb";
this.decryptInProgressWinTempRb.Size = new System.Drawing.Size(166, 30);
this.decryptInProgressWinTempRb.TabIndex = 1;
this.decryptInProgressWinTempRb.TabStop = true;
this.decryptInProgressWinTempRb.Text = "[desc]\r\n[winTemp\\DecryptInProgress]";
this.decryptInProgressWinTempRb.UseVisualStyleBackColor = true;
//
// decryptInProgressDescLbl
//
this.decryptInProgressDescLbl.AutoSize = true;
this.decryptInProgressDescLbl.Location = new System.Drawing.Point(6, 16);
this.decryptInProgressDescLbl.Name = "decryptInProgressDescLbl";
this.decryptInProgressDescLbl.Size = new System.Drawing.Size(38, 26);
this.decryptInProgressDescLbl.TabIndex = 0;
this.decryptInProgressDescLbl.Text = "[desc]\r\n[line 2]";
//
// saveBtn
//
this.saveBtn.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right)));
this.saveBtn.Location = new System.Drawing.Point(612, 544);
this.saveBtn.Name = "saveBtn";
this.saveBtn.Size = new System.Drawing.Size(75, 23);
this.saveBtn.TabIndex = 15;
this.saveBtn.Text = "Save";
this.saveBtn.UseVisualStyleBackColor = true;
this.saveBtn.Click += new System.EventHandler(this.saveBtn_Click);
//
// cancelBtn
//
this.cancelBtn.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right)));
this.cancelBtn.DialogResult = System.Windows.Forms.DialogResult.Cancel;
this.cancelBtn.Location = new System.Drawing.Point(713, 544);
this.cancelBtn.Name = "cancelBtn";
this.cancelBtn.Size = new System.Drawing.Size(75, 23);
this.cancelBtn.TabIndex = 16;
this.cancelBtn.Text = "Cancel";
this.cancelBtn.UseVisualStyleBackColor = true;
this.cancelBtn.Click += new System.EventHandler(this.cancelBtn_Click);
//
// audibleLocaleLbl
//
this.audibleLocaleLbl.AutoSize = true;
this.audibleLocaleLbl.Location = new System.Drawing.Point(7, 98);
this.audibleLocaleLbl.Name = "audibleLocaleLbl";
this.audibleLocaleLbl.Size = new System.Drawing.Size(77, 13);
this.audibleLocaleLbl.TabIndex = 6;
this.audibleLocaleLbl.Text = "Audible Locale";
//
// audibleLocaleCb
//
this.audibleLocaleCb.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList;
this.audibleLocaleCb.FormattingEnabled = true;
this.audibleLocaleCb.Items.AddRange(new object[] {
"us",
"uk",
"germany",
"france",
"canada"});
this.audibleLocaleCb.Location = new System.Drawing.Point(90, 95);
this.audibleLocaleCb.Name = "audibleLocaleCb";
this.audibleLocaleCb.Size = new System.Drawing.Size(121, 21);
this.audibleLocaleCb.TabIndex = 7;
//
// SettingsDialog
//
this.AcceptButton = this.saveBtn;
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.CancelButton = this.cancelBtn;
this.ClientSize = new System.Drawing.Size(800, 579);
this.Controls.Add(this.audibleLocaleCb);
this.Controls.Add(this.audibleLocaleLbl);
this.Controls.Add(this.cancelBtn);
this.Controls.Add(this.saveBtn);
this.Controls.Add(this.decryptInProgressGb);
this.Controls.Add(this.downloadsInProgressGb);
this.Controls.Add(this.libationFilesGb);
this.Controls.Add(this.booksLocationDescLbl);
this.Controls.Add(this.decryptKeyDescLbl);
this.Controls.Add(this.settingsFileDescLbl);
this.Controls.Add(this.booksLocationSearchBtn);
this.Controls.Add(this.booksLocationTb);
this.Controls.Add(this.booksLocationLbl);
this.Controls.Add(this.decryptKeyTb);
this.Controls.Add(this.decryptKeyLbl);
this.Controls.Add(this.settingsFileTb);
this.Controls.Add(this.settingsFileLbl);
this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedToolWindow;
this.Name = "SettingsDialog";
this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent;
this.Text = "Edit Settings";
this.Load += new System.EventHandler(this.SettingsDialog_Load);
this.libationFilesGb.ResumeLayout(false);
this.libationFilesGb.PerformLayout();
this.downloadsInProgressGb.ResumeLayout(false);
this.downloadsInProgressGb.PerformLayout();
this.decryptInProgressGb.ResumeLayout(false);
this.decryptInProgressGb.PerformLayout();
this.ResumeLayout(false);
this.PerformLayout();
}
#endregion
private System.Windows.Forms.Label settingsFileLbl;
private System.Windows.Forms.TextBox settingsFileTb;
private System.Windows.Forms.Label decryptKeyLbl;
private System.Windows.Forms.TextBox decryptKeyTb;
private System.Windows.Forms.Label booksLocationLbl;
private System.Windows.Forms.TextBox booksLocationTb;
private System.Windows.Forms.Button booksLocationSearchBtn;
private System.Windows.Forms.Label settingsFileDescLbl;
private System.Windows.Forms.Label decryptKeyDescLbl;
private System.Windows.Forms.Label booksLocationDescLbl;
private System.Windows.Forms.GroupBox libationFilesGb;
private System.Windows.Forms.Button libationFilesCustomBtn;
private System.Windows.Forms.TextBox libationFilesCustomTb;
private System.Windows.Forms.RadioButton libationFilesCustomRb;
private System.Windows.Forms.RadioButton libationFilesMyDocsRb;
private System.Windows.Forms.RadioButton libationFilesRootRb;
private System.Windows.Forms.Label libationFilesDescLbl;
private System.Windows.Forms.GroupBox downloadsInProgressGb;
private System.Windows.Forms.Label downloadsInProgressDescLbl;
private System.Windows.Forms.RadioButton downloadsInProgressWinTempRb;
private System.Windows.Forms.RadioButton downloadsInProgressLibationFilesRb;
private System.Windows.Forms.GroupBox decryptInProgressGb;
private System.Windows.Forms.Label decryptInProgressDescLbl;
private System.Windows.Forms.RadioButton decryptInProgressLibationFilesRb;
private System.Windows.Forms.RadioButton decryptInProgressWinTempRb;
private System.Windows.Forms.Button saveBtn;
private System.Windows.Forms.Button cancelBtn;
private System.Windows.Forms.Label audibleLocaleLbl;
private System.Windows.Forms.ComboBox audibleLocaleCb;
}
}

View File

@@ -1,175 +0,0 @@
using System;
using System.IO;
using System.Windows.Forms;
using Dinah.Core;
using FileManager;
namespace LibationWinForm
{
public partial class SettingsDialog : Form
{
Configuration config { get; } = Configuration.Instance;
Func<string, string> desc { get; } = Configuration.GetDescription;
string exeRoot { get; }
string myDocs { get; }
bool isFirstLoad;
public SettingsDialog()
{
InitializeComponent();
this.libationFilesCustomTb.TextChanged += (_, __) =>
{
if (!string.IsNullOrWhiteSpace(libationFilesCustomTb.Text))
this.libationFilesCustomRb.Checked = true;
};
exeRoot = Path.GetFullPath(Path.Combine(Path.GetDirectoryName(Exe.FileLocationOnDisk), "Libation"));
myDocs = Path.GetFullPath(Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments), "Libation"));
}
private void SettingsDialog_Load(object sender, EventArgs e)
{
isFirstLoad = string.IsNullOrWhiteSpace(config.Books);
this.settingsFileTb.Text = config.Filepath;
this.settingsFileDescLbl.Text = desc(nameof(config.Filepath));
this.decryptKeyTb.Text = config.DecryptKey;
this.decryptKeyDescLbl.Text = desc(nameof(config.DecryptKey));
this.booksLocationTb.Text
= !string.IsNullOrWhiteSpace(config.Books)
? config.Books
: Path.GetDirectoryName(Exe.FileLocationOnDisk);
this.booksLocationDescLbl.Text = desc(nameof(config.Books));
this.audibleLocaleCb.Text
= !string.IsNullOrWhiteSpace(config.LocaleCountryCode)
? config.LocaleCountryCode
: "us";
libationFilesDescLbl.Text = desc(nameof(config.LibationFiles));
this.libationFilesRootRb.Text = "In the same folder that Libation is running from\r\n" + exeRoot;
this.libationFilesMyDocsRb.Text = "In My Documents\r\n" + myDocs;
if (config.LibationFiles == exeRoot)
libationFilesRootRb.Checked = true;
else if (config.LibationFiles == myDocs)
libationFilesMyDocsRb.Checked = true;
else
{
libationFilesCustomRb.Checked = true;
libationFilesCustomTb.Text = config.LibationFiles;
}
this.downloadsInProgressDescLbl.Text = desc(nameof(config.DownloadsInProgressEnum));
var winTempDownloadsInProgress = Path.Combine(config.WinTemp, "DownloadsInProgress");
this.downloadsInProgressWinTempRb.Text = "In your Windows temporary folder\r\n" + winTempDownloadsInProgress;
switch (config.DownloadsInProgressEnum)
{
case "LibationFiles":
downloadsInProgressLibationFilesRb.Checked = true;
break;
case "WinTemp":
default:
downloadsInProgressWinTempRb.Checked = true;
break;
}
this.decryptInProgressDescLbl.Text = desc(nameof(config.DecryptInProgressEnum));
var winTempDecryptInProgress = Path.Combine(config.WinTemp, "DecryptInProgress");
this.decryptInProgressWinTempRb.Text = "In your Windows temporary folder\r\n" + winTempDecryptInProgress;
switch (config.DecryptInProgressEnum)
{
case "LibationFiles":
decryptInProgressLibationFilesRb.Checked = true;
break;
case "WinTemp":
default:
decryptInProgressWinTempRb.Checked = true;
break;
}
libationFiles_Changed(this, null);
}
private void libationFiles_Changed(object sender, EventArgs e)
{
var libationFilesDir
= libationFilesRootRb.Checked ? exeRoot
: libationFilesMyDocsRb.Checked ? myDocs
: libationFilesCustomTb.Text;
var downloadsInProgress = Path.Combine(libationFilesDir, "DownloadsInProgress");
this.downloadsInProgressLibationFilesRb.Text = $"In your Libation Files (ie: program-created files)\r\n{downloadsInProgress}";
var decryptInProgress = Path.Combine(libationFilesDir, "DecryptInProgress");
this.decryptInProgressLibationFilesRb.Text = $"In your Libation Files (ie: program-created files)\r\n{decryptInProgress}";
}
private void booksLocationSearchBtn_Click(object sender, EventArgs e) => selectFolder("Search for books location", this.booksLocationTb);
private void libationFilesCustomBtn_Click(object sender, EventArgs e) => selectFolder("Search for Libation Files location", this.libationFilesCustomTb);
private static void selectFolder(string desc, TextBox textbox)
{
using var dialog = new FolderBrowserDialog { Description = desc, SelectedPath = "" };
dialog.ShowDialog();
if (!string.IsNullOrWhiteSpace(dialog.SelectedPath))
textbox.Text = dialog.SelectedPath;
}
private void saveBtn_Click(object sender, EventArgs e)
{
config.DecryptKey = this.decryptKeyTb.Text;
var pathsChanged = false;
if (!Directory.Exists(this.booksLocationTb.Text))
MessageBox.Show("Not saving change to Books location. This folder does not exist:\r\n" + this.booksLocationTb.Text);
else if (config.Books != this.booksLocationTb.Text)
{
pathsChanged = true;
config.Books = this.booksLocationTb.Text;
}
config.LocaleCountryCode = this.audibleLocaleCb.Text;
var libationDir
= libationFilesRootRb.Checked ? exeRoot
: libationFilesMyDocsRb.Checked ? myDocs
: libationFilesCustomTb.Text;
if (!Directory.Exists(libationDir))
MessageBox.Show("Not saving change to Libation Files location. This folder does not exist:\r\n" + libationDir);
else if (config.LibationFiles != libationDir)
{
pathsChanged = true;
config.LibationFiles = libationDir;
}
config.DownloadsInProgressEnum = downloadsInProgressLibationFilesRb.Checked ? "LibationFiles" : "WinTemp";
config.DecryptInProgressEnum = decryptInProgressLibationFilesRb.Checked ? "LibationFiles" : "WinTemp";
if (!isFirstLoad && pathsChanged)
{
var shutdownResult = MessageBox.Show(
"You have changed a file path important for this program. All files will remain in their original location; nothing will be moved. It is highly recommended that you restart this program so these changes are handled correctly."
+ "\r\n"
+ "\r\nClose program?",
"Restart program",
MessageBoxButtons.YesNo,
MessageBoxIcon.Exclamation,
MessageBoxDefaultButton.Button1);
if (shutdownResult == DialogResult.Yes)
{
Application.Exit();
}
}
this.DialogResult = DialogResult.OK;
this.Close();
}
private void cancelBtn_Click(object sender, EventArgs e) => this.Close();
}
}

View File

@@ -1,282 +0,0 @@
namespace LibationWinForm
{
partial class Form1
{
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
#region Windows Form Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(Form1));
this.gridPanel = new System.Windows.Forms.Panel();
this.filterHelpBtn = new System.Windows.Forms.Button();
this.filterBtn = new System.Windows.Forms.Button();
this.filterSearchTb = new System.Windows.Forms.TextBox();
this.menuStrip1 = new System.Windows.Forms.MenuStrip();
this.importToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
this.scanLibraryToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
this.liberateToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
this.beginBookBackupsToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
this.beginPdfBackupsToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
this.quickFiltersToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
this.firstFilterIsDefaultToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
this.editQuickFiltersToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
this.toolStripSeparator1 = new System.Windows.Forms.ToolStripSeparator();
this.settingsToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
this.statusStrip1 = new System.Windows.Forms.StatusStrip();
this.visibleCountLbl = new System.Windows.Forms.ToolStripStatusLabel();
this.springLbl = new System.Windows.Forms.ToolStripStatusLabel();
this.backupsCountsLbl = new System.Windows.Forms.ToolStripStatusLabel();
this.pdfsCountsLbl = new System.Windows.Forms.ToolStripStatusLabel();
this.addFilterBtn = new System.Windows.Forms.Button();
this.menuStrip1.SuspendLayout();
this.statusStrip1.SuspendLayout();
this.SuspendLayout();
//
// gridPanel
//
this.gridPanel.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom)
| System.Windows.Forms.AnchorStyles.Left)
| System.Windows.Forms.AnchorStyles.Right)));
this.gridPanel.Location = new System.Drawing.Point(12, 56);
this.gridPanel.Name = "gridPanel";
this.gridPanel.Size = new System.Drawing.Size(839, 386);
this.gridPanel.TabIndex = 5;
//
// filterHelpBtn
//
this.filterHelpBtn.Location = new System.Drawing.Point(12, 27);
this.filterHelpBtn.Name = "filterHelpBtn";
this.filterHelpBtn.Size = new System.Drawing.Size(22, 23);
this.filterHelpBtn.TabIndex = 3;
this.filterHelpBtn.Text = "?";
this.filterHelpBtn.UseVisualStyleBackColor = true;
this.filterHelpBtn.Click += new System.EventHandler(this.filterHelpBtn_Click);
//
// filterBtn
//
this.filterBtn.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right)));
this.filterBtn.Location = new System.Drawing.Point(776, 27);
this.filterBtn.Name = "filterBtn";
this.filterBtn.Size = new System.Drawing.Size(75, 23);
this.filterBtn.TabIndex = 2;
this.filterBtn.Text = "Filter";
this.filterBtn.UseVisualStyleBackColor = true;
this.filterBtn.Click += new System.EventHandler(this.filterBtn_Click);
//
// filterSearchTb
//
this.filterSearchTb.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left)
| System.Windows.Forms.AnchorStyles.Right)));
this.filterSearchTb.Location = new System.Drawing.Point(186, 29);
this.filterSearchTb.Name = "filterSearchTb";
this.filterSearchTb.Size = new System.Drawing.Size(584, 20);
this.filterSearchTb.TabIndex = 1;
this.filterSearchTb.KeyPress += new System.Windows.Forms.KeyPressEventHandler(this.filterSearchTb_KeyPress);
//
// menuStrip1
//
this.menuStrip1.Items.AddRange(new System.Windows.Forms.ToolStripItem[] {
this.importToolStripMenuItem,
this.liberateToolStripMenuItem,
this.quickFiltersToolStripMenuItem,
this.settingsToolStripMenuItem});
this.menuStrip1.Location = new System.Drawing.Point(0, 0);
this.menuStrip1.Name = "menuStrip1";
this.menuStrip1.Size = new System.Drawing.Size(863, 24);
this.menuStrip1.TabIndex = 0;
this.menuStrip1.Text = "menuStrip1";
//
// importToolStripMenuItem
//
this.importToolStripMenuItem.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] {
this.scanLibraryToolStripMenuItem});
this.importToolStripMenuItem.Name = "importToolStripMenuItem";
this.importToolStripMenuItem.Size = new System.Drawing.Size(55, 20);
this.importToolStripMenuItem.Text = "&Import";
//
// scanLibraryToolStripMenuItem
//
this.scanLibraryToolStripMenuItem.Name = "scanLibraryToolStripMenuItem";
this.scanLibraryToolStripMenuItem.Size = new System.Drawing.Size(138, 22);
this.scanLibraryToolStripMenuItem.Text = "Scan &Library";
this.scanLibraryToolStripMenuItem.Click += new System.EventHandler(this.scanLibraryToolStripMenuItem_Click);
//
// liberateToolStripMenuItem
//
this.liberateToolStripMenuItem.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] {
this.beginBookBackupsToolStripMenuItem,
this.beginPdfBackupsToolStripMenuItem});
this.liberateToolStripMenuItem.Name = "liberateToolStripMenuItem";
this.liberateToolStripMenuItem.Size = new System.Drawing.Size(61, 20);
this.liberateToolStripMenuItem.Text = "&Liberate";
//
// beginBookBackupsToolStripMenuItem
//
this.beginBookBackupsToolStripMenuItem.Name = "beginBookBackupsToolStripMenuItem";
this.beginBookBackupsToolStripMenuItem.Size = new System.Drawing.Size(248, 22);
this.beginBookBackupsToolStripMenuItem.Text = "Begin &Book and PDF Backups: {0}";
this.beginBookBackupsToolStripMenuItem.Click += new System.EventHandler(this.beginBookBackupsToolStripMenuItem_Click);
//
// beginPdfBackupsToolStripMenuItem
//
this.beginPdfBackupsToolStripMenuItem.Name = "beginPdfBackupsToolStripMenuItem";
this.beginPdfBackupsToolStripMenuItem.Size = new System.Drawing.Size(248, 22);
this.beginPdfBackupsToolStripMenuItem.Text = "Begin &PDF Only Backups: {0}";
this.beginPdfBackupsToolStripMenuItem.Click += new System.EventHandler(this.beginPdfBackupsToolStripMenuItem_Click);
//
// quickFiltersToolStripMenuItem
//
this.quickFiltersToolStripMenuItem.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] {
this.firstFilterIsDefaultToolStripMenuItem,
this.editQuickFiltersToolStripMenuItem,
this.toolStripSeparator1});
this.quickFiltersToolStripMenuItem.Name = "quickFiltersToolStripMenuItem";
this.quickFiltersToolStripMenuItem.Size = new System.Drawing.Size(84, 20);
this.quickFiltersToolStripMenuItem.Text = "Quick &Filters";
//
// firstFilterIsDefaultToolStripMenuItem
//
this.firstFilterIsDefaultToolStripMenuItem.Name = "firstFilterIsDefaultToolStripMenuItem";
this.firstFilterIsDefaultToolStripMenuItem.Size = new System.Drawing.Size(256, 22);
this.firstFilterIsDefaultToolStripMenuItem.Text = "Start Libation with 1st filter &Default";
this.firstFilterIsDefaultToolStripMenuItem.Click += new System.EventHandler(this.FirstFilterIsDefaultToolStripMenuItem_Click);
//
// editQuickFiltersToolStripMenuItem
//
this.editQuickFiltersToolStripMenuItem.Name = "editQuickFiltersToolStripMenuItem";
this.editQuickFiltersToolStripMenuItem.Size = new System.Drawing.Size(256, 22);
this.editQuickFiltersToolStripMenuItem.Text = "&Edit quick filters";
this.editQuickFiltersToolStripMenuItem.Click += new System.EventHandler(this.EditQuickFiltersToolStripMenuItem_Click);
//
// toolStripSeparator1
//
this.toolStripSeparator1.Name = "toolStripSeparator1";
this.toolStripSeparator1.Size = new System.Drawing.Size(253, 6);
//
// settingsToolStripMenuItem
//
this.settingsToolStripMenuItem.Name = "settingsToolStripMenuItem";
this.settingsToolStripMenuItem.Size = new System.Drawing.Size(61, 20);
this.settingsToolStripMenuItem.Text = "&Settings";
this.settingsToolStripMenuItem.Click += new System.EventHandler(this.settingsToolStripMenuItem_Click);
//
// statusStrip1
//
this.statusStrip1.Items.AddRange(new System.Windows.Forms.ToolStripItem[] {
this.visibleCountLbl,
this.springLbl,
this.backupsCountsLbl,
this.pdfsCountsLbl});
this.statusStrip1.Location = new System.Drawing.Point(0, 445);
this.statusStrip1.Name = "statusStrip1";
this.statusStrip1.Size = new System.Drawing.Size(863, 22);
this.statusStrip1.TabIndex = 6;
this.statusStrip1.Text = "statusStrip1";
//
// visibleCountLbl
//
this.visibleCountLbl.Name = "visibleCountLbl";
this.visibleCountLbl.Size = new System.Drawing.Size(61, 17);
this.visibleCountLbl.Text = "Visible: {0}";
//
// springLbl
//
this.springLbl.Name = "springLbl";
this.springLbl.Size = new System.Drawing.Size(232, 17);
this.springLbl.Spring = true;
//
// backupsCountsLbl
//
this.backupsCountsLbl.Name = "backupsCountsLbl";
this.backupsCountsLbl.Size = new System.Drawing.Size(336, 17);
this.backupsCountsLbl.Text = "BACKUPS: No progress: {0} Encrypted: {1} Fully backed up: {2}";
//
// pdfsCountsLbl
//
this.pdfsCountsLbl.Name = "pdfsCountsLbl";
this.pdfsCountsLbl.Size = new System.Drawing.Size(219, 17);
this.pdfsCountsLbl.Text = "| PDFs: NOT d/l\'ed: {0} Downloaded: {1}";
//
// addFilterBtn
//
this.addFilterBtn.Location = new System.Drawing.Point(40, 27);
this.addFilterBtn.Name = "addFilterBtn";
this.addFilterBtn.Size = new System.Drawing.Size(140, 23);
this.addFilterBtn.TabIndex = 4;
this.addFilterBtn.Text = "Add To Quick Filters";
this.addFilterBtn.UseVisualStyleBackColor = true;
this.addFilterBtn.Click += new System.EventHandler(this.AddFilterBtn_Click);
//
// Form1
//
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.ClientSize = new System.Drawing.Size(863, 467);
this.Controls.Add(this.filterBtn);
this.Controls.Add(this.addFilterBtn);
this.Controls.Add(this.filterSearchTb);
this.Controls.Add(this.filterHelpBtn);
this.Controls.Add(this.statusStrip1);
this.Controls.Add(this.gridPanel);
this.Controls.Add(this.menuStrip1);
this.Icon = ((System.Drawing.Icon)(resources.GetObject("$this.Icon")));
this.MainMenuStrip = this.menuStrip1;
this.Name = "Form1";
this.Text = "Libation: Liberate your Library";
this.Load += new System.EventHandler(this.Form1_Load);
this.menuStrip1.ResumeLayout(false);
this.menuStrip1.PerformLayout();
this.statusStrip1.ResumeLayout(false);
this.statusStrip1.PerformLayout();
this.ResumeLayout(false);
this.PerformLayout();
}
#endregion
private System.Windows.Forms.Panel gridPanel;
private System.Windows.Forms.MenuStrip menuStrip1;
private System.Windows.Forms.ToolStripMenuItem importToolStripMenuItem;
private System.Windows.Forms.StatusStrip statusStrip1;
private System.Windows.Forms.ToolStripStatusLabel springLbl;
private System.Windows.Forms.ToolStripStatusLabel visibleCountLbl;
private System.Windows.Forms.ToolStripMenuItem liberateToolStripMenuItem;
private System.Windows.Forms.ToolStripStatusLabel backupsCountsLbl;
private System.Windows.Forms.ToolStripMenuItem beginBookBackupsToolStripMenuItem;
private System.Windows.Forms.ToolStripStatusLabel pdfsCountsLbl;
private System.Windows.Forms.ToolStripMenuItem beginPdfBackupsToolStripMenuItem;
private System.Windows.Forms.TextBox filterSearchTb;
private System.Windows.Forms.Button filterBtn;
private System.Windows.Forms.Button filterHelpBtn;
private System.Windows.Forms.ToolStripMenuItem settingsToolStripMenuItem;
private System.Windows.Forms.ToolStripMenuItem scanLibraryToolStripMenuItem;
private System.Windows.Forms.ToolStripMenuItem quickFiltersToolStripMenuItem;
private System.Windows.Forms.ToolStripMenuItem firstFilterIsDefaultToolStripMenuItem;
private System.Windows.Forms.Button addFilterBtn;
private System.Windows.Forms.ToolStripMenuItem editQuickFiltersToolStripMenuItem;
private System.Windows.Forms.ToolStripSeparator toolStripSeparator1;
}
}

View File

File diff suppressed because it is too large Load Diff

View File

@@ -1,201 +0,0 @@
namespace LibationWinForm
{
partial class ProductsGrid
{
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
#region Component Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
this.components = new System.ComponentModel.Container();
this.gridEntryBindingSource = new System.Windows.Forms.BindingSource(this.components);
this.gridEntryDataGridView = new System.Windows.Forms.DataGridView();
this.dataGridViewImageColumn1 = new System.Windows.Forms.DataGridViewImageColumn();
this.dataGridViewTextBoxColumn1 = new System.Windows.Forms.DataGridViewTextBoxColumn();
this.dataGridViewTextBoxColumn2 = new System.Windows.Forms.DataGridViewTextBoxColumn();
this.dataGridViewTextBoxColumn3 = new System.Windows.Forms.DataGridViewTextBoxColumn();
this.dataGridViewTextBoxColumn4 = new System.Windows.Forms.DataGridViewTextBoxColumn();
this.dataGridViewTextBoxColumn5 = new System.Windows.Forms.DataGridViewTextBoxColumn();
this.dataGridViewTextBoxColumn6 = new System.Windows.Forms.DataGridViewTextBoxColumn();
this.dataGridViewTextBoxColumn7 = new System.Windows.Forms.DataGridViewTextBoxColumn();
this.dataGridViewTextBoxColumn8 = new System.Windows.Forms.DataGridViewTextBoxColumn();
this.dataGridViewTextBoxColumn9 = new System.Windows.Forms.DataGridViewTextBoxColumn();
this.dataGridViewTextBoxColumn10 = new System.Windows.Forms.DataGridViewTextBoxColumn();
this.dataGridViewTextBoxColumn11 = new System.Windows.Forms.DataGridViewTextBoxColumn();
this.dataGridViewTextBoxColumn12 = new System.Windows.Forms.DataGridViewTextBoxColumn();
((System.ComponentModel.ISupportInitialize)(this.gridEntryBindingSource)).BeginInit();
((System.ComponentModel.ISupportInitialize)(this.gridEntryDataGridView)).BeginInit();
this.SuspendLayout();
//
// gridEntryBindingSource
//
this.gridEntryBindingSource.DataSource = typeof(LibationWinForm.GridEntry);
//
// gridEntryDataGridView
//
this.gridEntryDataGridView.AutoGenerateColumns = false;
this.gridEntryDataGridView.ColumnHeadersHeightSizeMode = System.Windows.Forms.DataGridViewColumnHeadersHeightSizeMode.AutoSize;
this.gridEntryDataGridView.Columns.AddRange(new System.Windows.Forms.DataGridViewColumn[] {
this.dataGridViewImageColumn1,
this.dataGridViewTextBoxColumn1,
this.dataGridViewTextBoxColumn2,
this.dataGridViewTextBoxColumn3,
this.dataGridViewTextBoxColumn4,
this.dataGridViewTextBoxColumn5,
this.dataGridViewTextBoxColumn6,
this.dataGridViewTextBoxColumn7,
this.dataGridViewTextBoxColumn8,
this.dataGridViewTextBoxColumn9,
this.dataGridViewTextBoxColumn10,
this.dataGridViewTextBoxColumn11,
this.dataGridViewTextBoxColumn12});
this.gridEntryDataGridView.DataSource = this.gridEntryBindingSource;
this.gridEntryDataGridView.Location = new System.Drawing.Point(54, 58);
this.gridEntryDataGridView.Name = "gridEntryDataGridView";
this.gridEntryDataGridView.Size = new System.Drawing.Size(300, 220);
this.gridEntryDataGridView.TabIndex = 0;
//
// dataGridViewImageColumn1
//
this.dataGridViewImageColumn1.DataPropertyName = "Cover";
this.dataGridViewImageColumn1.HeaderText = "Cover";
this.dataGridViewImageColumn1.Name = "dataGridViewImageColumn1";
this.dataGridViewImageColumn1.ReadOnly = true;
//
// dataGridViewTextBoxColumn1
//
this.dataGridViewTextBoxColumn1.DataPropertyName = "Title";
this.dataGridViewTextBoxColumn1.HeaderText = "Title";
this.dataGridViewTextBoxColumn1.Name = "dataGridViewTextBoxColumn1";
this.dataGridViewTextBoxColumn1.ReadOnly = true;
//
// dataGridViewTextBoxColumn2
//
this.dataGridViewTextBoxColumn2.DataPropertyName = "Authors";
this.dataGridViewTextBoxColumn2.HeaderText = "Authors";
this.dataGridViewTextBoxColumn2.Name = "dataGridViewTextBoxColumn2";
this.dataGridViewTextBoxColumn2.ReadOnly = true;
//
// dataGridViewTextBoxColumn3
//
this.dataGridViewTextBoxColumn3.DataPropertyName = "Narrators";
this.dataGridViewTextBoxColumn3.HeaderText = "Narrators";
this.dataGridViewTextBoxColumn3.Name = "dataGridViewTextBoxColumn3";
this.dataGridViewTextBoxColumn3.ReadOnly = true;
//
// dataGridViewTextBoxColumn4
//
this.dataGridViewTextBoxColumn4.DataPropertyName = "Length";
this.dataGridViewTextBoxColumn4.HeaderText = "Length";
this.dataGridViewTextBoxColumn4.Name = "dataGridViewTextBoxColumn4";
this.dataGridViewTextBoxColumn4.ReadOnly = true;
//
// dataGridViewTextBoxColumn5
//
this.dataGridViewTextBoxColumn5.DataPropertyName = "Series";
this.dataGridViewTextBoxColumn5.HeaderText = "Series";
this.dataGridViewTextBoxColumn5.Name = "dataGridViewTextBoxColumn5";
this.dataGridViewTextBoxColumn5.ReadOnly = true;
//
// dataGridViewTextBoxColumn6
//
this.dataGridViewTextBoxColumn6.DataPropertyName = "Description";
this.dataGridViewTextBoxColumn6.HeaderText = "Description";
this.dataGridViewTextBoxColumn6.Name = "dataGridViewTextBoxColumn6";
this.dataGridViewTextBoxColumn6.ReadOnly = true;
//
// dataGridViewTextBoxColumn7
//
this.dataGridViewTextBoxColumn7.DataPropertyName = "Category";
this.dataGridViewTextBoxColumn7.HeaderText = "Category";
this.dataGridViewTextBoxColumn7.Name = "dataGridViewTextBoxColumn7";
this.dataGridViewTextBoxColumn7.ReadOnly = true;
//
// dataGridViewTextBoxColumn8
//
this.dataGridViewTextBoxColumn8.DataPropertyName = "Product_Rating";
this.dataGridViewTextBoxColumn8.HeaderText = "Product_Rating";
this.dataGridViewTextBoxColumn8.Name = "dataGridViewTextBoxColumn8";
this.dataGridViewTextBoxColumn8.ReadOnly = true;
//
// dataGridViewTextBoxColumn9
//
this.dataGridViewTextBoxColumn9.DataPropertyName = "Purchase_Date";
this.dataGridViewTextBoxColumn9.HeaderText = "Purchase_Date";
this.dataGridViewTextBoxColumn9.Name = "dataGridViewTextBoxColumn9";
this.dataGridViewTextBoxColumn9.ReadOnly = true;
//
// dataGridViewTextBoxColumn10
//
this.dataGridViewTextBoxColumn10.DataPropertyName = "My_Rating";
this.dataGridViewTextBoxColumn10.HeaderText = "My_Rating";
this.dataGridViewTextBoxColumn10.Name = "dataGridViewTextBoxColumn10";
this.dataGridViewTextBoxColumn10.ReadOnly = true;
//
// dataGridViewTextBoxColumn11
//
this.dataGridViewTextBoxColumn11.DataPropertyName = "Misc";
this.dataGridViewTextBoxColumn11.HeaderText = "Misc";
this.dataGridViewTextBoxColumn11.Name = "dataGridViewTextBoxColumn11";
this.dataGridViewTextBoxColumn11.ReadOnly = true;
//
// dataGridViewTextBoxColumn12
//
this.dataGridViewTextBoxColumn12.DataPropertyName = "Download_Status";
this.dataGridViewTextBoxColumn12.HeaderText = "Download_Status";
this.dataGridViewTextBoxColumn12.Name = "dataGridViewTextBoxColumn12";
this.dataGridViewTextBoxColumn12.ReadOnly = true;
//
// ProductsGrid
//
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.Controls.Add(this.gridEntryDataGridView);
this.Name = "ProductsGrid";
this.Size = new System.Drawing.Size(434, 329);
((System.ComponentModel.ISupportInitialize)(this.gridEntryBindingSource)).EndInit();
((System.ComponentModel.ISupportInitialize)(this.gridEntryDataGridView)).EndInit();
this.ResumeLayout(false);
}
#endregion
private System.Windows.Forms.BindingSource gridEntryBindingSource;
private System.Windows.Forms.DataGridView gridEntryDataGridView;
private System.Windows.Forms.DataGridViewImageColumn dataGridViewImageColumn1;
private System.Windows.Forms.DataGridViewTextBoxColumn dataGridViewTextBoxColumn1;
private System.Windows.Forms.DataGridViewTextBoxColumn dataGridViewTextBoxColumn2;
private System.Windows.Forms.DataGridViewTextBoxColumn dataGridViewTextBoxColumn3;
private System.Windows.Forms.DataGridViewTextBoxColumn dataGridViewTextBoxColumn4;
private System.Windows.Forms.DataGridViewTextBoxColumn dataGridViewTextBoxColumn5;
private System.Windows.Forms.DataGridViewTextBoxColumn dataGridViewTextBoxColumn6;
private System.Windows.Forms.DataGridViewTextBoxColumn dataGridViewTextBoxColumn7;
private System.Windows.Forms.DataGridViewTextBoxColumn dataGridViewTextBoxColumn8;
private System.Windows.Forms.DataGridViewTextBoxColumn dataGridViewTextBoxColumn9;
private System.Windows.Forms.DataGridViewTextBoxColumn dataGridViewTextBoxColumn10;
private System.Windows.Forms.DataGridViewTextBoxColumn dataGridViewTextBoxColumn11;
private System.Windows.Forms.DataGridViewTextBoxColumn dataGridViewTextBoxColumn12;
}
}

View File

@@ -1,274 +0,0 @@
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using System.Windows.Forms;
using ApplicationServices;
using DataLayer;
using Dinah.Core.Collections.Generic;
using Dinah.Core.DataBinding;
using Dinah.Core.Windows.Forms;
namespace LibationWinForm
{
// INSTRUCTIONS TO UPDATE DATA_GRID_VIEW
// - delete current DataGridView
// - view > other windows > data sources
// - refresh
// OR
// - Add New Data Source
// Object. Next
// LibationWinForm
// AudibleDTO
// GridEntry
// - go to Design view
// - click on Data Sources > ProductItem. drowdown: DataGridView
// - drag/drop ProductItem on design surface
public partial class ProductsGrid : UserControl
{
public event EventHandler<int> VisibleCountChanged;
private DataGridView dataGridView;
private LibationContext context;
public ProductsGrid()
{
InitializeComponent();
Disposed += (_, __) => context?.Dispose();
manageLiveImageUpdateSubscriptions();
}
private bool hasBeenDisplayed = false;
public void Display()
{
if (hasBeenDisplayed)
return;
hasBeenDisplayed = true;
dataGridView = gridEntryDataGridView;
dataGridView.Dock = DockStyle.Fill;
dataGridView.AllowUserToAddRows = false;
dataGridView.AllowUserToDeleteRows = false;
dataGridView.AutoGenerateColumns = false;
dataGridView.ColumnHeadersHeightSizeMode = DataGridViewColumnHeadersHeightSizeMode.AutoSize;
dataGridView.DefaultCellStyle.WrapMode = DataGridViewTriState.True;
dataGridView.ReadOnly = true;
dataGridView.RowHeadersVisible = false;
// adjust height for 80x80 pictures.
// this must be done before databinding. or can alter later by iterating through rows
dataGridView.RowTemplate.Height = 82;
dataGridView.CellFormatting += replaceFormatted;
dataGridView.CellFormatting += hiddenFormatting;
// sorting breaks filters. must reapply filters after sorting
dataGridView.Sorted += (_, __) => filter();
{ // add tag buttons
var editUserTagsButton = new DataGridViewButtonColumn { HeaderText = "Edit Tags" };
dataGridView.Columns.Add(editUserTagsButton);
// add image and handle click
dataGridView.CellPainting += paintEditTag_TextAndImage;
dataGridView.CellContentClick += dataGridView_GridButtonClick;
}
for (var i = dataGridView.ColumnCount - 1; i >= 0; i--)
{
DataGridViewColumn col = dataGridView.Columns[i];
// initial HeaderText is the lookup name from GridEntry class. any formatting below won't change this
col.Name = col.HeaderText;
if (!(col is DataGridViewImageColumn || col is DataGridViewButtonColumn))
col.SortMode = DataGridViewColumnSortMode.Automatic;
col.HeaderText = col.HeaderText.Replace("_", " ");
col.Width = col.Name switch
{
nameof(GridEntry.Cover) => 80,
nameof(GridEntry.Title) => col.Width * 2,
nameof(GridEntry.Misc) => (int)(col.Width * 1.35),
var n when n.In(nameof(GridEntry.My_Rating), nameof(GridEntry.Product_Rating)) => col.Width + 8,
_ => col.Width
};
}
//
// transform into sorted GridEntry.s BEFORE binding
//
context = LibationContext.Create();
var lib = context.GetLibrary_Flat_WithTracking();
// if no data. hide all columns. return
if (!lib.Any())
{
for (var i = dataGridView.ColumnCount - 1; i >= 0; i--)
dataGridView.Columns.RemoveAt(i);
return;
}
var orderedGridEntries = lib
.Select(lb => new GridEntry(lb)).ToList()
// default load order
.OrderByDescending(ge => ge.Purchase_Date)
//// more advanced example: sort by author, then series, then title
//.OrderBy(ge => ge.Authors)
// .ThenBy(ge => ge.Series)
// .ThenBy(ge => ge.Title)
.ToList();
//
// BIND
//
gridEntryBindingSource.DataSource = orderedGridEntries.ToSortableBindingList();
//
// FILTER
//
filter();
}
private void paintEditTag_TextAndImage(object sender, DataGridViewCellPaintingEventArgs e)
{
// DataGridView Image for Button Column: https://stackoverflow.com/a/36253883
if (e.RowIndex < 0 || !(((DataGridView)sender).Columns[e.ColumnIndex] is DataGridViewButtonColumn))
return;
var gridEntry = getGridEntry(e.RowIndex);
var displayTags = gridEntry.TagsEnumerated.ToList();
if (displayTags.Any())
dataGridView.Rows[e.RowIndex].Cells[e.ColumnIndex].Value = string.Join("\r\n", displayTags);
else // no tags: use image
{
// clear tag text
dataGridView.Rows[e.RowIndex].Cells[e.ColumnIndex].Value = "";
// images from: icons8.com -- search: tags
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;
}
}
private void dataGridView_GridButtonClick(object sender, DataGridViewCellEventArgs e)
{
// handle grid button click: https://stackoverflow.com/a/13687844
if (e.RowIndex < 0)
return;
if (sender != dataGridView)
throw new Exception($"{nameof(dataGridView_GridButtonClick)} has incorrect sender ...somehow");
if (!(dataGridView.Columns[e.ColumnIndex] is DataGridViewButtonColumn))
return;
var liveGridEntry = getGridEntry(e.RowIndex);
// EditTagsDialog should display better-formatted title
liveGridEntry.TryGetFormatted(nameof(liveGridEntry.Title), out string value);
var editTagsForm = new EditTagsDialog(value, liveGridEntry.Tags);
if (editTagsForm.ShowDialog() != DialogResult.OK)
return;
var qtyChanges = context.UpdateTags(liveGridEntry.GetBook(), editTagsForm.NewTags);
if (qtyChanges == 0)
return;
// force a re-draw, and re-apply filters
// needed to update text colors
dataGridView.InvalidateRow(e.RowIndex);
filter();
}
#region Cell Formatting
private void replaceFormatted(object sender, DataGridViewCellFormattingEventArgs e)
{
var col = ((DataGridView)sender).Columns[e.ColumnIndex];
if (col is DataGridViewTextBoxColumn textCol && getGridEntry(e.RowIndex).TryGetFormatted(textCol.Name, out string value))
e.Value = value;
}
private void hiddenFormatting(object sender, DataGridViewCellFormattingEventArgs e)
{
var isHidden = getGridEntry(e.RowIndex).TagsEnumerated.Contains("hidden");
dataGridView.Rows[e.RowIndex].Cells[e.ColumnIndex].Style
= isHidden
? new DataGridViewCellStyle { ForeColor = Color.LightGray }
: dataGridView.DefaultCellStyle;
}
#endregion
#region filter
string _filterSearchString;
private void filter() => Filter(_filterSearchString);
public void Filter(string searchString)
{
_filterSearchString = searchString;
if (dataGridView.Rows.Count == 0)
return;
var searchResults = SearchEngineCommands.Search(searchString);
var productIds = searchResults.Docs.Select(d => d.ProductId).ToList();
// https://stackoverflow.com/a/18942430
var currencyManager = (CurrencyManager)BindingContext[dataGridView.DataSource];
currencyManager.SuspendBinding();
{
for (var r = dataGridView.RowCount - 1; r >= 0; r--)
dataGridView.Rows[r].Visible = productIds.Contains(getGridEntry(r).GetBook().AudibleProductId);
}
currencyManager.ResumeBinding();
VisibleCountChanged?.Invoke(this, dataGridView.AsEnumerable().Count(r => r.Visible));
var luceneSearchString_debug = searchResults.SearchString;
}
#endregion
#region live update newly downloaded and cached images
private void manageLiveImageUpdateSubscriptions()
{
FileManager.PictureStorage.PictureCached += crossThreadImageUpdate;
Disposed += (_, __) => FileManager.PictureStorage.PictureCached -= crossThreadImageUpdate;
}
private void crossThreadImageUpdate(object _, string pictureId)
=> dataGridView.UIThread(() => updateRowImage(pictureId));
private void updateRowImage(string pictureId)
{
var rowId = getRowId((ge) => ge.GetBook().PictureId == pictureId);
if (rowId > -1)
dataGridView.InvalidateRow(rowId);
}
#endregion
private GridEntry getGridEntry(int rowIndex) => (GridEntry)dataGridView.Rows[rowIndex].DataBoundItem;
private int getRowId(Func<GridEntry, bool> func)
{
for (var r = 0; r < dataGridView.RowCount; r++)
if (func(getGridEntry(r)))
return r;
return -1;
}
}
}

View File

@@ -1,74 +0,0 @@
using System;
using System.Windows.Forms;
using Dinah.Core.Logging;
using Serilog;
namespace LibationWinForm
{
static class Program
{
[STAThread]
static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
if (!createSettings())
return;
init();
Application.Run(new Form1());
}
private static bool createSettings()
{
if (!string.IsNullOrWhiteSpace(FileManager.Configuration.Instance.Books))
return true;
var welcomeText = @"
This appears to be your first time using Libation. Welcome.
Please fill in a few settings on the following page. You can also change these settings later.
After you make your selections, get started by importing your library.
Go to Import > Scan Library
".Trim();
MessageBox.Show(welcomeText, "Welcome to Libation", MessageBoxButtons.OK);
var dialogResult = new SettingsDialog().ShowDialog();
if (dialogResult != DialogResult.OK)
{
MessageBox.Show("Initial set up cancelled.", "Cancelled", MessageBoxButtons.OK, MessageBoxIcon.Warning);
return false;
}
return true;
}
private static void init()
{
// default. for reference. output example:
// 2019-11-26 08:48:40.224 -05:00 [DBG] Begin Libation
var default_outputTemplate = "{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} [{Level:u3}] {Message:lj}{NewLine}{Exception}";
// with class and method info. output example:
// 2019-11-26 08:48:40.224 -05:00 [DBG] (at LibationWinForm.Program.init()) Begin Libation
var code_outputTemplate = "{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} [{Level:u3}] (at {Caller}) {Message:lj}{NewLine}{Exception}";
var logPath = System.IO.Path.Combine(FileManager.Configuration.Instance.LibationFiles, "Log.log");
Log.Logger = new LoggerConfiguration()
.Enrich.WithCaller()
.MinimumLevel.Debug()
.WriteTo.File(logPath,
rollingInterval: RollingInterval.Month,
outputTemplate: code_outputTemplate)
.CreateLogger();
Log.Logger.Debug("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");
}
}
}

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 92 KiB

View File

@@ -1,11 +1,10 @@
<Project Sdk="Microsoft.NET.Sdk.WindowsDesktop">
<PropertyGroup>
<OutputType>WinExe</OutputType>
<TargetFramework>netcoreapp3.0</TargetFramework>
<OutputType>Library</OutputType>
<TargetFramework>netcoreapp3.1</TargetFramework>
<UseWindowsForms>true</UseWindowsForms>
<ApplicationIcon>libation.ico</ApplicationIcon>
<AssemblyName>Libation</AssemblyName>
<PublishTrimmed>true</PublishTrimmed>
<PublishReadyToRun>true</PublishReadyToRun>
@@ -24,12 +23,30 @@
<AutoGen>True</AutoGen>
<DependentUpon>Resources.resx</DependentUpon>
</Compile>
<Compile Update="UNTESTED\Dialogs\LibationFilesDialog.cs">
<SubType>Form</SubType>
</Compile>
<Compile Update="UNTESTED\Dialogs\LibationFilesDialog.Designer.cs">
<DependentUpon>LibationFilesDialog.cs</DependentUpon>
</Compile>
<Compile Update="UNTESTED\Dialogs\SettingsDialog.cs">
<SubType>Form</SubType>
</Compile>
<Compile Update="UNTESTED\Dialogs\SettingsDialog.Designer.cs">
<DependentUpon>SettingsDialog.cs</DependentUpon>
</Compile>
<Compile Update="UNTESTED\Dialogs\IndexLibraryDialog.cs">
<SubType>Form</SubType>
</Compile>
<Compile Update="UNTESTED\Dialogs\IndexLibraryDialog.Designer.cs">
<DependentUpon>IndexLibraryDialog.cs</DependentUpon>
</Compile>
<Compile Update="UNTESTED\Dialogs\SetupDialog.cs">
<SubType>Form</SubType>
</Compile>
<Compile Update="UNTESTED\Dialogs\SetupDialog.Designer.cs">
<DependentUpon>SetupDialog.cs</DependentUpon>
</Compile>
</ItemGroup>
<ItemGroup>
@@ -37,6 +54,15 @@
<Generator>ResXFileCodeGenerator</Generator>
<LastGenOutput>Resources.Designer.cs</LastGenOutput>
</EmbeddedResource>
<EmbeddedResource Update="UNTESTED\Dialogs\LibationFilesDialog.resx">
<DependentUpon>LibationFilesDialog.cs</DependentUpon>
</EmbeddedResource>
<EmbeddedResource Update="UNTESTED\Dialogs\SettingsDialog.resx">
<DependentUpon>SettingsDialog.cs</DependentUpon>
</EmbeddedResource>
<EmbeddedResource Update="UNTESTED\Dialogs\SetupDialog.resx">
<DependentUpon>SetupDialog.cs</DependentUpon>
</EmbeddedResource>
</ItemGroup>
</Project>

View File

@@ -6,5 +6,5 @@
cause the file to be unrecognizable by the program.
-->
<GenericObjectDataSource DisplayName="GridEntry" Version="1.0" xmlns="urn:schemas-microsoft-com:xml-msdatasource">
<TypeInfo>WinFormsDesigner.GridEntry, LibationWinForm, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null</TypeInfo>
<TypeInfo>LibationWinForms.GridEntry, LibationWinForms, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null</TypeInfo>
</GenericObjectDataSource>

View File

@@ -8,7 +8,7 @@
// </auto-generated>
//------------------------------------------------------------------------------
namespace LibationWinForm.Properties {
namespace LibationWinForms.Properties {
using System;
@@ -39,7 +39,7 @@ namespace LibationWinForm.Properties {
internal static global::System.Resources.ResourceManager ResourceManager {
get {
if (object.ReferenceEquals(resourceMan, null)) {
global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("LibationWinForm.Properties.Resources", typeof(Resources).Assembly);
global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("LibationWinForms.Properties.Resources", typeof(Resources).Assembly);
resourceMan = temp;
}
return resourceMan;
@@ -109,5 +109,95 @@ namespace LibationWinForm.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

Before

Width:  |  Height:  |  Size: 314 B

After

Width:  |  Height:  |  Size: 314 B

View File

Before

Width:  |  Height:  |  Size: 573 B

After

Width:  |  Height:  |  Size: 573 B

View File

Before

Width:  |  Height:  |  Size: 6.0 KiB

After

Width:  |  Height:  |  Size: 6.0 KiB

View File

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 11 KiB

View File

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

Binary file not shown.

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

@@ -1,4 +1,4 @@
namespace LibationWinForm.BookLiberation
namespace LibationWinForms.BookLiberation
{
partial class AutomatedBackupsForm
{

View File

@@ -2,11 +2,22 @@
using System.Windows.Forms;
using Dinah.Core.Windows.Forms;
namespace LibationWinForm.BookLiberation
namespace LibationWinForms.BookLiberation
{
public partial class AutomatedBackupsForm : Form
{
public bool KeepGoingIsChecked => keepGoingCb.Checked;
public bool KeepGoingVisible
{
get => keepGoingCb.Visible;
set => keepGoingCb.Visible = value;
}
public bool KeepGoingChecked => keepGoingCb.Checked;
public bool KeepGoing
=> keepGoingCb.Visible
&& keepGoingCb.Enabled
&& keepGoingCb.Checked;
public AutomatedBackupsForm()
{
@@ -20,7 +31,7 @@ namespace LibationWinForm.BookLiberation
}
public void AppendText(string text)
{
Serilog.Log.Logger.Debug($"Automated backup: {text}");
Serilog.Log.Logger.Information($"Automated backup: {text}");
appendText(text);
}
private void appendText(string text)

View File

@@ -1,4 +1,4 @@
namespace LibationWinForm.BookLiberation
namespace LibationWinForms.BookLiberation
{
partial class DecryptForm
{

View File

@@ -4,16 +4,10 @@ using Dinah.Core.Drawing;
using Dinah.Core.IO;
using Dinah.Core.Windows.Forms;
namespace LibationWinForm.BookLiberation
namespace LibationWinForms.BookLiberation
{
public partial class DecryptForm : Form
{
class SerilogTextWriter : System.IO.TextWriter
{
public override System.Text.Encoding Encoding => System.Text.Encoding.ASCII;
public override void WriteLine(string value) => Serilog.Log.Logger.Debug(value);
}
public DecryptForm()
{
InitializeComponent();
@@ -23,8 +17,10 @@ namespace LibationWinForm.BookLiberation
private void DecryptForm_Load(object sender, EventArgs e)
{
// redirect Console.WriteLine to console, textbox
var controlWriter = new RichTextBoxTextWriter(this.rtbLog);
var multiLogger = new MultiTextWriter(origOut, controlWriter, new SerilogTextWriter());
var multiLogger = new MultiTextWriter(
origOut,
new RichTextBoxTextWriter(this.rtbLog),
new SerilogTextWriter());
Console.SetOut(multiLogger);
}

View File

@@ -1,4 +1,4 @@
namespace LibationWinForm.BookLiberation
namespace LibationWinForms.BookLiberation
{
partial class DownloadForm
{

View File

@@ -2,7 +2,7 @@
using System.Windows.Forms;
using Dinah.Core.Windows.Forms;
namespace LibationWinForm.BookLiberation
namespace LibationWinForms.BookLiberation
{
public partial class DownloadForm : Form
{
@@ -33,13 +33,13 @@ namespace LibationWinForm.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

@@ -1,48 +1,139 @@
using System;
using System.Threading.Tasks;
using DataLayer;
using Dinah.Core.ErrorHandling;
using FileLiberator;
namespace LibationWinForm.BookLiberation
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()
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();
backupBook.DownloadBook.Begin += (_, __) => wireUpDownloadable(backupBook.DownloadBook);
backupBook.DecryptBook.Begin += (_, __) => wireUpDecryptable(backupBook.DecryptBook);
backupBook.DownloadPdf.Begin += (_, __) => wireUpDecryptable(backupBook.DecryptBook);
backupBook.DownloadBook.Begin += (_, __) => wireUpEvents(backupBook.DownloadBook);
backupBook.DecryptBook.Begin += (_, __) => wireUpEvents(backupBook.DecryptBook);
backupBook.DownloadPdf.Begin += (_, __) => wireUpEvents(backupBook.DownloadPdf);
return backupBook;
if (completedAction != null)
{
backupBook.DownloadBook.Completed += completedAction;
backupBook.DecryptBook.Completed += completedAction;
backupBook.DownloadPdf.Completed += completedAction;
}
return backupBook;
}
public static DecryptBook GetWiredUpDecryptBook()
private static AutomatedBackupsForm attachToBackupsForm(BackupBook backupBook)
{
var decryptBook = new DecryptBook();
decryptBook.Begin += (_, __) => wireUpDecryptable(decryptBook);
return decryptBook;
#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 DownloadBook GetWiredUpDownloadBook()
public static async Task BackupAllPdfsAsync(EventHandler<LibraryBook> completedAction = null)
{
var downloadBook = new DownloadBook();
downloadBook.Begin += (_, __) => wireUpDownloadable(downloadBook);
return downloadBook;
var downloadPdf = getWiredUpDownloadPdf(completedAction);
var automatedBackupsForm = attachToBackupsForm(downloadPdf);
await runBackupLoopAsync(downloadPdf, automatedBackupsForm);
}
public static DownloadPdf GetWiredUpDownloadPdf()
private static DownloadPdf getWiredUpDownloadPdf(EventHandler<LibraryBook> completedAction)
{
var downloadPdf = new DownloadPdf();
downloadPdf.Begin += (_, __) => wireUpDownloadable(downloadPdf);
downloadPdf.Begin += (_, __) => wireUpEvents(downloadPdf);
if (completedAction != null)
downloadPdf.Completed += completedAction;
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 wireUpDownloadable(IDownloadable downloadable)
private static void wireUpEvents(IDownloadableProcessable downloadable)
{
#region create form
var downloadDialog = new DownloadForm();
@@ -82,7 +173,7 @@ namespace LibationWinForm.BookLiberation
// unless we dispose, if the form is created but un-used/never-shown then weird UI stuff can happen
// also, since event unsubscribe occurs on FormClosing and an unused form is never closed, then the events will never be unsubscribed
void dialogDispose(object _, string __)
void dialogDispose(object _, object __)
{
if (!downloadDialog.IsDisposed)
downloadDialog.Dispose();
@@ -106,7 +197,7 @@ namespace LibationWinForm.BookLiberation
}
// subscribed to Begin event because a new form should be created+processed+closed on each iteration
private static void wireUpDecryptable(IDecryptable decryptBook)
private static void wireUpEvents(IDecryptable decryptBook)
{
#region create form
var decryptDialog = new DecryptForm();
@@ -153,17 +244,17 @@ namespace LibationWinForm.BookLiberation
#endregion
}
public static async Task RunAutomaticDownload(IDownloadable downloadable)
private static AutomatedBackupsForm attachToBackupsForm(IDownloadableProcessable downloadable)
{
#region create form
var automatedBackupsForm = new AutomatedBackupsForm();
#endregion
#region define how model actions will affect form behavior
void begin(object _, string str) => automatedBackupsForm.AppendText("Begin: " + str);
void begin(object _, LibraryBook libraryBook) => automatedBackupsForm.AppendText($"Begin: {libraryBook.Book}");
void statusUpdate(object _, string str) => automatedBackupsForm.AppendText("- " + str);
// extra line after book is completely finished
void completed(object _, string str) => automatedBackupsForm.AppendText("Completed: " + str + Environment.NewLine);
void completed(object _, LibraryBook libraryBook) => automatedBackupsForm.AppendText($"Completed: {libraryBook.Book}{Environment.NewLine}");
#endregion
#region subscribe new form to model's events
@@ -182,91 +273,23 @@ namespace LibationWinForm.BookLiberation
};
#endregion
await runBackupLoop(downloadable, automatedBackupsForm);
}
public static async Task RunAutomaticBackup(BackupBook backupBook)
{
#region create form
var automatedBackupsForm = new AutomatedBackupsForm();
#endregion
#region define how model actions will affect form behavior
void downloadBookBegin(object _, string str) => automatedBackupsForm.AppendText("DownloadStep_Begin: " + str);
void statusUpdate(object _, string str) => automatedBackupsForm.AppendText("- " + str);
void downloadBookCompleted(object _, string str) => automatedBackupsForm.AppendText("DownloadStep_Completed: " + str);
void decryptBookBegin(object _, string str) => automatedBackupsForm.AppendText("DecryptStep_Begin: " + str);
// extra line after book is completely finished
void decryptBookCompleted(object _, string str) => automatedBackupsForm.AppendText("DecryptStep_Completed: " + str + Environment.NewLine);
void downloadPdfBegin(object _, string str) => automatedBackupsForm.AppendText("PdfStep_Begin: " + str);
// extra line after book is completely finished
void downloadPdfCompleted(object _, string str) => automatedBackupsForm.AppendText("PdfStep_Completed: " + str + 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
await runBackupLoop(backupBook, automatedBackupsForm);
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 runBackupLoop(IProcessable processable, AutomatedBackupsForm automatedBackupsForm)
private static async Task runBackupLoopAsync(IProcessable processable, AutomatedBackupsForm automatedBackupsForm)
{
automatedBackupsForm.Show();
try
{
do
var shouldContinue = true;
while (shouldContinue)
{
var statusHandler = await processable.ProcessFirstValidAsync();
if (statusHandler == null)
{
automatedBackupsForm.AppendText("Done. All books have been processed");
break;
}
if (statusHandler.HasErrors)
{
automatedBackupsForm.AppendText("ERROR. All books have not been processed. Most recent valid book: processing failed");
foreach (var errorMessage in statusHandler.Errors)
automatedBackupsForm.AppendText(errorMessage);
break;
}
if (!automatedBackupsForm.KeepGoingIsChecked)
{
automatedBackupsForm.AppendText("'Keep going' is unchecked");
break;
}
shouldContinue = validateStatus(statusHandler, automatedBackupsForm);
}
while (automatedBackupsForm.KeepGoingIsChecked);
}
catch (Exception ex)
{
@@ -275,5 +298,48 @@ namespace LibationWinForm.BookLiberation
automatedBackupsForm.FinalizeUI();
}
private static async Task runSingleBackupAsync(IProcessable processable, AutomatedBackupsForm automatedBackupsForm, string productId)
{
automatedBackupsForm.Show();
try
{
var statusHandler = await processable.ProcessSingleAsync(productId);
validateStatus(statusHandler, automatedBackupsForm);
}
catch (Exception ex)
{
automatedBackupsForm.AppendError(ex);
}
automatedBackupsForm.FinalizeUI();
}
private static bool validateStatus(StatusHandler statusHandler, AutomatedBackupsForm automatedBackupsForm)
{
if (statusHandler == null)
{
automatedBackupsForm.AppendText("Done. All books have been processed");
return false;
}
if (statusHandler.HasErrors)
{
automatedBackupsForm.AppendText("ERROR. All books have not been processed. Most recent valid book: processing failed");
foreach (var errorMessage in statusHandler.Errors)
automatedBackupsForm.AppendText(errorMessage);
return false;
}
if (!automatedBackupsForm.KeepGoing)
{
if (automatedBackupsForm.KeepGoingVisible && !automatedBackupsForm.KeepGoingChecked)
automatedBackupsForm.AppendText("'Keep going' is unchecked");
return false;
}
return true;
}
}
}

View File

@@ -1,4 +1,4 @@
namespace LibationWinForm.Dialogs
namespace LibationWinForms.Dialogs
{
partial class EditQuickFilters
{

View File

@@ -9,7 +9,7 @@ using System.Threading.Tasks;
using System.Windows.Forms;
using FileManager;
namespace LibationWinForm.Dialogs
namespace LibationWinForms.Dialogs
{
public partial class EditQuickFilters : Form
{

Some files were not shown because too many files have changed in this diff Show More