Compare commits

...

37 Commits

Author SHA1 Message Date
Robert McRackan
e723467ca6 book liberation status Error:
* show system error icon in grid instead of stoplight
* list error count in bottom right #s
* SearchEngine bool: LiberatedError
2021-08-27 17:01:00 -04:00
Robert McRackan
722c33bf61 Add readme to help/annoy collaborators 2021-08-27 15:14:42 -04:00
Robert McRackan
f080215cbb Book details dialog: tags should get initial focus 2021-08-27 14:07:06 -04:00
Robert McRackan
d5c74d629f update dependencies 2021-08-27 11:16:13 -04:00
Robert McRackan
d12c246f6d version increm 2021-08-26 16:09:37 -04:00
Robert McRackan
8969c216af comments 2021-08-26 16:08:26 -04:00
Robert McRackan
9a4903f0dd Bug fix: after successful pdf download, this state wasn't being saved 2021-08-26 15:53:33 -04:00
Robert McRackan
3eda498a5e new AudibleApi nuget no longer relies on external json and js files which caused issues 2021-08-26 12:51:55 -04:00
Robert McRackan
8af7f28f04 (hopefully) final nuget pkg: Dinah.Core.WindowsDesktop 2021-08-26 12:49:37 -04:00
Robert McRackan
d9d7dfe1f7 update depandecies 2021-08-26 12:48:05 -04:00
Robert McRackan
b9c4d11946 remove TestCommon 2021-08-25 17:07:26 -04:00
Robert McRackan
68a5d7a58d nuget. done until I can figure out how to build .net5-windows nuget from github actions 2021-08-25 16:24:02 -04:00
Robert McRackan
4d69b222c5 nuget: Dinah.EntityFrameworkCore 2021-08-25 15:55:31 -04:00
Robert McRackan
42f94e7f6c more nuget migration 2021-08-25 15:33:09 -04:00
Robert McRackan
381d52be72 Better audible api to reduce captcha occurances 2021-08-24 13:42:14 -04:00
Robert McRackan
f16ad30891 bug fix from my last bug fix :( 2021-08-24 09:28:13 -04:00
Robert McRackan
ef53a6a8cb Bug fix: issue #92 2021-08-23 16:29:23 -04:00
Robert McRackan
9a37d434f1 FileLiberator is not db ignorant. It doesn't make context calls but still heavily uses the classes defined in the domain. Also uses internal util.s 2021-08-23 16:27:22 -04:00
Robert McRackan
d7eb190f69 reduce use of Book.Supplements 2021-08-23 16:16:08 -04:00
Robert McRackan
f19c46ee45 Book: add pdf url as is, not Absolute 2021-08-23 16:07:19 -04:00
rmcrackan
343c3b62d6 Merge pull request #90 from Mbucari/master
Fully implemented the MVVM pattern
2021-08-22 21:06:00 -04:00
Michael Bucari-Tovo
b1de10a71a Fix filtering. 2021-08-22 13:29:01 -06:00
Michael Bucari-Tovo
6beb5cc74a Made changes discussed. 2021-08-22 13:27:39 -06:00
Michael Bucari-Tovo
3767c3574a Merge branch 'master' of https://github.com/Mbucari/Libation 2021-08-21 22:09:27 -06:00
Michael Bucari-Tovo
4ceb4f9c03 Change back. 2021-08-21 22:09:13 -06:00
Mbucari
0f5149f7b4 Merge pull request #2 from rmcrackan/master
bug fix. race condition. can't check for book-exists-state before set…
2021-08-21 22:08:51 -06:00
Michael Bucari-Tovo
673451dc11 Git resolve 2021-08-21 22:08:35 -06:00
Michael Bucari-Tovo
e4257afc14 Version Num 2021-08-21 22:06:54 -06:00
Michael Bucari-Tovo
2a7e185dc3 Finish MVVM conversion 2021-08-21 22:03:16 -06:00
Michael Bucari-Tovo
9e06c343c1 Don't check if values have changed when updating the database. 2021-08-21 21:15:25 -06:00
Michael Bucari-Tovo
40b3a9990d FileLiberator is now DB ignorant. IProcessables update UserDaefinedData which notifies the view model. 2021-08-21 20:49:54 -06:00
Robert McRackan
d66c112a1e bug fix. race condition. can't check for book-exists-state before setting this state 2021-08-21 22:32:45 -04:00
Michael Bucari-Tovo
d826885728 Fix display for new LiberatedStatus values. 2021-08-21 18:37:07 -06:00
Michael Bucari-Tovo
263222d8cc Changed method signature. 2021-08-21 18:21:22 -06:00
Michael Bucari-Tovo
f25734334d Add separate command for updating tags 2021-08-21 18:16:24 -06:00
Michael Bucari-Tovo
ede8397f13 Needed to add check for actual file since Audio_Exists is now an application state. 2021-08-21 18:15:39 -06:00
Michael Bucari-Tovo
1369ee575a Replaced LiberatedState with LiberatedStatus and PdfState with LiberatedStatus? 2021-08-21 16:29:16 -06:00
40 changed files with 421 additions and 422 deletions

View File

@@ -4,9 +4,12 @@
<TargetFramework>net5.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Dinah.Core" Version="1.0.5.1" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\AAXClean\AAXClean.csproj" />
<ProjectReference Include="..\..\Dinah.Core\Dinah.Core\Dinah.Core.csproj" />
</ItemGroup>
</Project>

View File

@@ -6,14 +6,11 @@
<ItemGroup>
<PackageReference Include="CsvHelper" Version="27.1.1" />
<PackageReference Include="NPOI" Version="2.5.3" />
<PackageReference Include="NPOI" Version="2.5.4" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\audible api\AudibleApi\AudibleApiDTOs\AudibleApiDTOs.csproj" />
<ProjectReference Include="..\..\audible api\AudibleApi\AudibleApi\AudibleApi.csproj" />
<ProjectReference Include="..\DtoImporterService\DtoImporterService.csproj" />
<ProjectReference Include="..\InternalUtilities\InternalUtilities.csproj" />
<ProjectReference Include="..\LibationSearchEngine\LibationSearchEngine.csproj" />
</ItemGroup>

View File

@@ -11,13 +11,6 @@ using Serilog;
namespace ApplicationServices
{
// subtly different from DataLayer.LiberatedStatus
// - DataLayer.LiberatedStatus: has no concept of partially downloaded
// - ApplicationServices.LiberatedState: has no concept of Error/skipped
public enum LiberatedState { NotDownloaded, PartialDownload, Liberated }
public enum PdfState { NoPdf, Downloaded, NotDownloaded }
public static class LibraryCommands
{
private static LibraryOptions.ResponseGroupOptions LibraryResponseGroups = LibraryOptions.ResponseGroupOptions.ALL_OPTIONS;
@@ -162,112 +155,42 @@ namespace ApplicationServices
#endregion
#region Update book details
public static int UpdateUserDefinedItem(Book book, string newTags, LiberatedStatus bookStatus, LiberatedStatus? pdfStatus)
public static int UpdateUserDefinedItem(Book book)
{
try
{
using var context = DbContexts.GetContext();
var udi = book.UserDefinedItem;
var tagsChanged = udi.Tags != newTags;
var bookStatusChanged = udi.BookStatus != bookStatus;
var pdfStatusChanged = udi.PdfStatus != pdfStatus;
if (!tagsChanged && !bookStatusChanged && !pdfStatusChanged)
return 0;
udi.Tags = newTags;
udi.BookStatus = bookStatus;
udi.PdfStatus = pdfStatus;
// Attach() NoTracking entities before SaveChanges()
context.Attach(udi).State = Microsoft.EntityFrameworkCore.EntityState.Modified;
context.Attach(book.UserDefinedItem).State = Microsoft.EntityFrameworkCore.EntityState.Modified;
var qtyChanges = context.SaveChanges();
if (qtyChanges == 0)
return 0;
if (tagsChanged)
SearchEngineCommands.UpdateBookTags(book);
if (bookStatusChanged || pdfStatusChanged)
if (qtyChanges > 0)
SearchEngineCommands.UpdateLiberatedStatus(book);
return qtyChanges;
}
catch (Exception ex)
{
Log.Logger.Error(ex, "Error updating tags");
throw;
}
}
public static int UpdateBook(LibraryBook libraryBook, LiberatedStatus liberatedStatus)
{
try
{
using var context = DbContexts.GetContext();
var udi = libraryBook.Book.UserDefinedItem;
if (udi.BookStatus == liberatedStatus)
return 0;
// Attach() NoTracking entities before SaveChanges()
udi.BookStatus = liberatedStatus;
context.Attach(udi).State = Microsoft.EntityFrameworkCore.EntityState.Modified;
var qtyChanges = context.SaveChanges();
if (qtyChanges > 0)
SearchEngineCommands.UpdateLiberatedStatus(libraryBook.Book);
return qtyChanges;
}
catch (Exception ex)
{
Log.Logger.Error(ex, "Error updating tags");
throw;
}
}
public static int UpdatePdf(LibraryBook libraryBook, LiberatedStatus liberatedStatus)
{
try
{
using var context = DbContexts.GetContext();
var udi = libraryBook.Book.UserDefinedItem;
if (udi.PdfStatus == liberatedStatus)
return 0;
// Attach() NoTracking entities before SaveChanges()
udi.PdfStatus = liberatedStatus;
context.Attach(udi).State = Microsoft.EntityFrameworkCore.EntityState.Modified;
var qtyChanges = context.SaveChanges();
return qtyChanges;
}
catch (Exception ex)
{
Log.Logger.Error(ex, "Error updating tags");
Log.Logger.Error(ex, $"Error updating {nameof(book.UserDefinedItem)}");
throw;
}
}
#endregion
public static LiberatedStatus Liberated_Status(Book book)
=> book.Audio_Exists ? LiberatedStatus.Liberated
: FileManager.AudibleFileStorage.AaxcExists(book.AudibleProductId) ? LiberatedStatus.PartialDownload
: LiberatedStatus.NotLiberated;
public static LiberatedStatus? Pdf_Status(Book book)
=> !book.HasPdf ? null
: book.PDF_Exists ? LiberatedStatus.Liberated
: LiberatedStatus.NotLiberated;
// below are queries, not commands. maybe I should make a LibraryQueries. except there's already one of those...
public static LiberatedState Liberated_Status(Book book)
=> book.Audio_Exists ? LiberatedState.Liberated
: FileManager.AudibleFileStorage.AaxcExists(book.AudibleProductId) ? LiberatedState.PartialDownload
: LiberatedState.NotDownloaded;
public static PdfState Pdf_Status(Book book)
=> !book.Supplements.Any() ? PdfState.NoPdf
: book.PDF_Exists ? PdfState.Downloaded
: PdfState.NotDownloaded;
public record LibraryStats(int booksFullyBackedUp, int booksDownloadedOnly, int booksNoProgress, int pdfsDownloaded, int pdfsNotDownloaded) { }
public record LibraryStats(int booksFullyBackedUp, int booksDownloadedOnly, int booksNoProgress, int booksError, int pdfsDownloaded, int pdfsNotDownloaded) { }
public static LibraryStats GetCounts()
{
var libraryBooks = DbContexts.GetContext().GetLibrary_Flat_NoTracking();
@@ -276,23 +199,24 @@ namespace ApplicationServices
.AsParallel()
.Select(lb => Liberated_Status(lb.Book))
.ToList();
var booksFullyBackedUp = results.Count(r => r == LiberatedState.Liberated);
var booksDownloadedOnly = results.Count(r => r == LiberatedState.PartialDownload);
var booksNoProgress = results.Count(r => r == LiberatedState.NotDownloaded);
var booksFullyBackedUp = results.Count(r => r == LiberatedStatus.Liberated);
var booksDownloadedOnly = results.Count(r => r == LiberatedStatus.PartialDownload);
var booksNoProgress = results.Count(r => r == LiberatedStatus.NotLiberated);
var booksError = results.Count(r => r == LiberatedStatus.Error);
Log.Logger.Information("Book counts. {@DebugInfo}", new { total = results.Count, booksFullyBackedUp, booksDownloadedOnly, booksNoProgress });
Log.Logger.Information("Book counts. {@DebugInfo}", new { total = results.Count, booksFullyBackedUp, booksDownloadedOnly, booksNoProgress, booksError });
var boolResults = libraryBooks
.AsParallel()
.Where(lb => lb.Book.Supplements.Any())
.Where(lb => lb.Book.HasPdf)
.Select(lb => Pdf_Status(lb.Book))
.ToList();
var pdfsDownloaded = boolResults.Count(r => r == PdfState.Downloaded);
var pdfsNotDownloaded = boolResults.Count(r => r == PdfState.NotDownloaded);
var pdfsDownloaded = boolResults.Count(r => r == LiberatedStatus.Liberated);
var pdfsNotDownloaded = boolResults.Count(r => r == LiberatedStatus.NotLiberated);
Log.Logger.Information("PDF counts. {@DebugInfo}", new { total = boolResults.Count, pdfsDownloaded, pdfsNotDownloaded });
return new(booksFullyBackedUp, booksDownloadedOnly, booksNoProgress, pdfsDownloaded, pdfsNotDownloaded);
return new(booksFullyBackedUp, booksDownloadedOnly, booksNoProgress, booksError, pdfsDownloaded, pdfsNotDownloaded);
}
}
}

View File

@@ -47,8 +47,8 @@ namespace ApplicationServices
[Name("Publisher")]
public string Publisher { get; set; }
[Name("Pdf url")]
public string PdfUrl { get; set; }
[Name("Has PDF")]
public bool HasPdf { get; set; }
[Name("Series Names")]
public string SeriesNames { get; set; }
@@ -109,7 +109,7 @@ namespace ApplicationServices
NarratorNames = a.Book.NarratorNames,
LengthInMinutes = a.Book.LengthInMinutes,
Publisher = a.Book.Publisher,
PdfUrl = a.Book.Supplements?.FirstOrDefault()?.Url,
HasPdf = a.Book.HasPdf,
SeriesNames = a.Book.SeriesNames,
SeriesOrder = a.Book.SeriesLink.Any() ? a.Book.SeriesLink?.Select(sl => $"{sl.Index} : {sl.Series.Name}").Aggregate((a, b) => $"{a}, {b}") : "",
CommunityRatingOverall = a.Book.Rating?.OverallRating,
@@ -182,7 +182,7 @@ namespace ApplicationServices
nameof (ExportDto.NarratorNames),
nameof (ExportDto.LengthInMinutes),
nameof (ExportDto.Publisher),
nameof (ExportDto.PdfUrl),
nameof (ExportDto.HasPdf),
nameof (ExportDto.SeriesNames),
nameof (ExportDto.SeriesOrder),
nameof (ExportDto.CommunityRatingOverall),
@@ -234,7 +234,7 @@ namespace ApplicationServices
row.CreateCell(col++).SetCellValue(dto.NarratorNames);
row.CreateCell(col++).SetCellValue(dto.LengthInMinutes);
row.CreateCell(col++).SetCellValue(dto.Publisher);
row.CreateCell(col++).SetCellValue(dto.PdfUrl);
row.CreateCell(col++).SetCellValue(dto.HasPdf);
row.CreateCell(col++).SetCellValue(dto.SeriesNames);
row.CreateCell(col++).SetCellValue(dto.SeriesOrder);

View File

@@ -12,19 +12,19 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="5.0.5">
<PackageReference Include="Dinah.EntityFrameworkCore" Version="1.0.2.2" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="5.0.9">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="5.0.5" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="5.0.5">
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="5.0.9" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="5.0.9">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\Dinah.Core\Dinah.EntityFrameworkCore\Dinah.EntityFrameworkCore.csproj" />
<ProjectReference Include="..\FileManager\FileManager.csproj" />
</ItemGroup>

View File

@@ -14,7 +14,11 @@ namespace DataLayer
NotLiberated = 0,
Liberated = 1,
/// <summary>Error occurred during liberation. Don't retry</summary>
Error = 2
Error = 2,
/// <summary>Application-state only. Not a valid persistence state.</summary>
PartialDownload = 0x1000
}
public class UserDefinedItem
@@ -38,7 +42,15 @@ namespace DataLayer
public string Tags
{
get => _tags;
set => _tags = sanitize(value);
set
{
var newTags = sanitize(value);
if (_tags != newTags)
{
_tags = newTags;
ItemChanged?.Invoke(this, nameof(Tags));
}
}
}
public IEnumerable<string> TagsEnumerated => Tags == "" ? new string[0] : Tags.Split(null as char[], StringSplitOptions.RemoveEmptyEntries);
@@ -95,10 +107,38 @@ namespace DataLayer
#endregion
#region LiberatedStatuses
public LiberatedStatus BookStatus { get; set; }
public LiberatedStatus? PdfStatus { get; set; }
#endregion
private LiberatedStatus _bookStatus;
private LiberatedStatus? _pdfStatus;
public LiberatedStatus BookStatus
{
get => _bookStatus;
set
{
if (_bookStatus != value)
{
_bookStatus = value;
ItemChanged?.Invoke(this, nameof(BookStatus));
}
}
}
public LiberatedStatus? PdfStatus
{
get => _pdfStatus;
set
{
if (_pdfStatus != value)
{
_pdfStatus = value;
ItemChanged?.Invoke(this, nameof(PdfStatus));
}
}
}
#endregion
/// <summary>
/// Occurs when <see cref="Tags"/>, <see cref="BookStatus"/>, or <see cref="PdfStatus"/> values change.
/// </summary>
public static event EventHandler<string> ItemChanged;
public override string ToString() => $"{Book} {Rating} {Tags}";
}
}

View File

@@ -1,7 +1,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using AudibleApiDTOs;
using AudibleApi.Common;
using DataLayer;
using InternalUtilities;
@@ -120,8 +120,8 @@ namespace DtoImporterService
book.UpdateBookDetails(item.IsAbridged, item.DatePublished);
if (!string.IsNullOrWhiteSpace(item.SupplementUrl))
book.AddSupplementDownloadUrl(item.SupplementUrl);
if (item.PdfUrl is not null)
book.AddSupplementDownloadUrl(item.PdfUrl.ToString());
return book;
}

View File

@@ -1,7 +1,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using AudibleApiDTOs;
using AudibleApi.Common;
using DataLayer;
using InternalUtilities;

View File

@@ -1,7 +1,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using AudibleApiDTOs;
using AudibleApi.Common;
using DataLayer;
using InternalUtilities;

View File

@@ -5,7 +5,6 @@
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\audible api\AudibleApi\AudibleApiDTOs\AudibleApiDTOs.csproj" />
<ProjectReference Include="..\DataLayer\DataLayer.csproj" />
<ProjectReference Include="..\InternalUtilities\InternalUtilities.csproj" />
</ItemGroup>

View File

@@ -1,7 +1,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using AudibleApiDTOs;
using AudibleApi.Common;
using DataLayer;
using InternalUtilities;

View File

@@ -1,7 +1,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using AudibleApiDTOs;
using AudibleApi.Common;
using DataLayer;
using InternalUtilities;
@@ -29,7 +29,7 @@ namespace DtoImporterService
return qtyNew;
}
private void loadLocal_series(List<AudibleApiDTOs.Series> series)
private void loadLocal_series(List<AudibleApi.Common.Series> series)
{
var seriesIds = series.Select(s => s.SeriesId).ToList();
var localIds = DbContext.Series.Local.Select(s => s.AudibleSeriesId).ToList();
@@ -42,7 +42,7 @@ namespace DtoImporterService
DbContext.Series.Where(s => remainingSeriesIds.Contains(s.AudibleSeriesId)).ToList();
}
private int upsertSeries(List<AudibleApiDTOs.Series> requestedSeries)
private int upsertSeries(List<AudibleApi.Common.Series> requestedSeries)
{
var qtyNew = 0;

View File

@@ -16,22 +16,22 @@ namespace FileLiberator
public class DownloadDecryptBook : IAudioDecodable
{
private AaxcDownloadConverter aaxcDownloader;
private AaxcDownloadConverter aaxcDownloader;
public event EventHandler<TimeSpan> StreamingTimeRemaining;
public event EventHandler<Action<byte[]>> RequestCoverArt;
public event EventHandler<string> TitleDiscovered;
public event EventHandler<string> AuthorsDiscovered;
public event EventHandler<string> NarratorsDiscovered;
public event EventHandler<byte[]> CoverImageDiscovered;
public event EventHandler<string> StreamingBegin;
public event EventHandler<DownloadProgress> StreamingProgressChanged;
public event EventHandler<string> StreamingCompleted;
public event EventHandler<LibraryBook> Begin;
public event EventHandler<string> StatusUpdate;
public event EventHandler<LibraryBook> Completed;
public event EventHandler<TimeSpan> StreamingTimeRemaining;
public event EventHandler<Action<byte[]>> RequestCoverArt;
public event EventHandler<string> TitleDiscovered;
public event EventHandler<string> AuthorsDiscovered;
public event EventHandler<string> NarratorsDiscovered;
public event EventHandler<byte[]> CoverImageDiscovered;
public event EventHandler<string> StreamingBegin;
public event EventHandler<DownloadProgress> StreamingProgressChanged;
public event EventHandler<string> StreamingCompleted;
public event EventHandler<LibraryBook> Begin;
public event EventHandler<string> StatusUpdate;
public event EventHandler<LibraryBook> Completed;
public async Task<StatusHandler> ProcessAsync(LibraryBook libraryBook)
public async Task<StatusHandler> ProcessAsync(LibraryBook libraryBook)
{
Begin?.Invoke(this, libraryBook);
@@ -47,13 +47,12 @@ namespace FileLiberator
return new StatusHandler { "Decrypt failed" };
// moves files and returns dest dir
_ = moveFilesToBooksDir(libraryBook.Book, outputAudioFilename);
var moveResults = MoveFilesToBooksDir(libraryBook.Book, outputAudioFilename);
if (!libraryBook.Book.Audio_Exists)
if (!moveResults.movedAudioFile)
return new StatusHandler { "Cannot find final audio file after decryption" };
// only need to update if success. if failure, it will remain at 0 == NotLiberated
ApplicationServices.LibraryCommands.UpdateBook(libraryBook, LiberatedStatus.Liberated);
libraryBook.Book.UserDefinedItem.BookStatus = LiberatedStatus.Liberated;
return new StatusHandler();
}
@@ -126,7 +125,7 @@ namespace FileLiberator
}
private void AaxcDownloader_RetrievedCoverArt(object sender, byte[] e)
private void AaxcDownloader_RetrievedCoverArt(object sender, byte[] e)
{
if (e is null && Configuration.Instance.AllowLibationFixup)
{
@@ -143,10 +142,10 @@ namespace FileLiberator
{
TitleDiscovered?.Invoke(this, e.TitleSansUnabridged);
AuthorsDiscovered?.Invoke(this, e.FirstAuthor ?? "[unknown]");
NarratorsDiscovered?.Invoke(this, e.Narrator ?? "[unknown]");
NarratorsDiscovered?.Invoke(this, e.Narrator ?? "[unknown]");
}
private static string moveFilesToBooksDir(Book product, string outputAudioFilename)
private static (string destinationDir, bool movedAudioFile) MoveFilesToBooksDir(Book product, string outputAudioFilename)
{
// create final directory. move each file into it. MOVE AUDIO FILE LAST
// new dir: safetitle_limit50char + " [" + productId + "]"
@@ -161,6 +160,7 @@ namespace FileLiberator
// audio filename: safetitle_limit50char + " [" + productId + "]." + audio_ext
var audioFileName = FileUtility.GetValidFilename(destinationDir, product.Title, musicFileExt, product.AudibleProductId);
bool movedAudioFile = false;
foreach (var f in sortedFiles)
{
var dest
@@ -173,11 +173,13 @@ namespace FileLiberator
Cue.UpdateFileName(f, audioFileName);
File.Move(f.FullName, dest);
movedAudioFile |= AudibleFileStorage.Audio.IsFileTypeMatch(f);
}
AudibleFileStorage.Audio.Refresh();
return destinationDir;
return (destinationDir, movedAudioFile);
}
private static List<FileInfo> getProductFilesSorted(Book product, string outputAudioFilename)
@@ -200,26 +202,26 @@ namespace FileLiberator
}
private static void validate(LibraryBook libraryBook)
{
string errorString(string field)
=> $"{errorTitle()}\r\nCannot download book. {field} is not known. Try re-importing the account which owns this book.";
{
string errorString(string field)
=> $"{errorTitle()}\r\nCannot download book. {field} is not known. Try re-importing the account which owns this book.";
string errorTitle()
{
var title
= (libraryBook.Book.Title.Length > 53)
? $"{libraryBook.Book.Title.Truncate(50)}..."
: libraryBook.Book.Title;
var errorBookTitle = $"{title} [{libraryBook.Book.AudibleProductId}]";
return errorBookTitle;
};
string errorTitle()
{
var title
= (libraryBook.Book.Title.Length > 53)
? $"{libraryBook.Book.Title.Truncate(50)}..."
: libraryBook.Book.Title;
var errorBookTitle = $"{title} [{libraryBook.Book.AudibleProductId}]";
return errorBookTitle;
};
if (string.IsNullOrWhiteSpace(libraryBook.Account))
throw new Exception(errorString("Account"));
if (string.IsNullOrWhiteSpace(libraryBook.Account))
throw new Exception(errorString("Account"));
if (string.IsNullOrWhiteSpace(libraryBook.Book.Locale))
throw new Exception(errorString("Locale"));
}
if (string.IsNullOrWhiteSpace(libraryBook.Book.Locale))
throw new Exception(errorString("Locale"));
}
public bool Validate(LibraryBook libraryBook) => !libraryBook.Book.Audio_Exists;

View File

@@ -20,11 +20,10 @@ namespace FileLiberator
public override async Task<StatusHandler> ProcessItemAsync(LibraryBook libraryBook)
{
var proposedDownloadFilePath = getProposedDownloadFilePath(libraryBook);
await downloadPdfAsync(libraryBook, proposedDownloadFilePath);
var result = verifyDownload(libraryBook);
var actualDownloadedFilePath = await downloadPdfAsync(libraryBook, proposedDownloadFilePath);
var result = verifyDownload(actualDownloadedFilePath);
var liberatedStatus = result.IsSuccess ? LiberatedStatus.Liberated : LiberatedStatus.NotLiberated;
ApplicationServices.LibraryCommands.UpdatePdf(libraryBook, liberatedStatus);
libraryBook.Book.UserDefinedItem.PdfStatus = result.IsSuccess ? LiberatedStatus.Liberated : LiberatedStatus.NotLiberated;
return result;
}
@@ -49,7 +48,7 @@ namespace FileLiberator
private static string getdownloadUrl(LibraryBook libraryBook)
=> libraryBook?.Book?.Supplements?.FirstOrDefault()?.Url;
private async Task downloadPdfAsync(LibraryBook libraryBook, string proposedDownloadFilePath)
private async Task<string> downloadPdfAsync(LibraryBook libraryBook, string proposedDownloadFilePath)
{
var api = await GetApiAsync(libraryBook);
var downloadUrl = await api.GetPdfDownloadLinkAsync(libraryBook.Book.AudibleProductId);
@@ -58,10 +57,12 @@ namespace FileLiberator
var actualDownloadedFilePath = await PerformDownloadAsync(
proposedDownloadFilePath,
(p) => client.DownloadFileAsync(downloadUrl, proposedDownloadFilePath, p));
return actualDownloadedFilePath;
}
private static StatusHandler verifyDownload(LibraryBook libraryBook)
=> !libraryBook.Book.PDF_Exists
private static StatusHandler verifyDownload(string actualDownloadedFilePath)
=> !File.Exists(actualDownloadedFilePath)
? new StatusHandler { "Downloaded PDF cannot be found" }
: new StatusHandler();
}

View File

@@ -5,9 +5,7 @@
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\Dinah.Core\Dinah.Core\Dinah.Core.csproj" />
<ProjectReference Include="..\AaxDecrypter\AaxDecrypter.csproj" />
<ProjectReference Include="..\ApplicationServices\ApplicationServices.csproj" />
<ProjectReference Include="..\DataLayer\DataLayer.csproj" />
<ProjectReference Include="..\FileManager\FileManager.csproj" />
<ProjectReference Include="..\InternalUtilities\InternalUtilities.csproj" />

View File

@@ -2,7 +2,6 @@
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using ApplicationServices;
using DataLayer;
using Dinah.Core;
using Dinah.Core.ErrorHandling;
@@ -18,10 +17,8 @@ namespace FileLiberator
// when used in foreach: stateful. deferred execution
public static IEnumerable<LibraryBook> GetValidLibraryBooks(this IProcessable processable)
=> DbContexts.GetContext()
.GetLibrary_Flat_NoTracking()
.Where(libraryBook => processable.Validate(libraryBook));
public static IEnumerable<LibraryBook> GetValidLibraryBooks(this IProcessable processable, IEnumerable<LibraryBook> library)
=> library.Where(libraryBook => processable.Validate(libraryBook));
public static async Task<StatusHandler> ProcessSingleAsync(this IProcessable processable, LibraryBook libraryBook, bool validate)
{

View File

@@ -5,13 +5,10 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Dinah.Core" Version="1.0.5.1" />
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="5.0.0" />
<PackageReference Include="Octokit" Version="0.50.0" />
<PackageReference Include="Polly" Version="7.2.2" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\Dinah.Core\Dinah.Core\Dinah.Core.csproj" />
</ItemGroup>
</Project>

View File

@@ -3,7 +3,7 @@ using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using AudibleApi;
using AudibleApiDTOs;
using AudibleApi.Common;
using Dinah.Core;
using Polly;
using Polly.Retry;
@@ -68,13 +68,13 @@ namespace InternalUtilities
// var childIds = items
// .Where(i => i.Episodes)
// .SelectMany(ep => ep.Relationships)
// .Where(r => r.RelationshipToProduct == AudibleApiDTOs.RelationshipToProduct.Child && r.RelationshipType == AudibleApiDTOs.RelationshipType.Episode)
// .Where(r => r.RelationshipToProduct == AudibleApi.Common.RelationshipToProduct.Child && r.RelationshipType == AudibleApi.Common.RelationshipType.Episode)
// .Select(c => c.Asin)
// .ToList();
// foreach (var childId in childIds)
// {
// var bookResult = await api.GetLibraryBookAsync(childId, AudibleApi.LibraryOptions.ResponseGroupOptions.ALL_OPTIONS);
// var bookItem = AudibleApiDTOs.LibraryDtoV10.FromJson(bookResult.ToString()).Item;
// var bookItem = AudibleApi.Common.LibraryDtoV10.FromJson(bookResult.ToString()).Item;
// items.Add(bookItem);
// }
#endregion

View File

@@ -1,7 +1,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using AudibleApiDTOs;
using AudibleApi.Common;
namespace InternalUtilities
{
@@ -76,9 +76,9 @@ namespace InternalUtilities
var distinct = items.GetSeriesDistinct();
if (distinct.Any(s => s.SeriesId is null))
exceptions.Add(new ArgumentException($"Collection contains {nameof(Item.Series)} with null {nameof(AudibleApiDTOs.Series.SeriesId)}", nameof(items)));
exceptions.Add(new ArgumentException($"Collection contains {nameof(Item.Series)} with null {nameof(AudibleApi.Common.Series.SeriesId)}", nameof(items)));
if (distinct.Any(s => s.SeriesName is null))
exceptions.Add(new ArgumentException($"Collection contains {nameof(Item.Series)} with null {nameof(AudibleApiDTOs.Series.SeriesName)}", nameof(items)));
exceptions.Add(new ArgumentException($"Collection contains {nameof(Item.Series)} with null {nameof(AudibleApi.Common.Series.SeriesName)}", nameof(items)));
return exceptions;
}

View File

@@ -1,5 +1,5 @@
using System;
using AudibleApiDTOs;
using AudibleApi.Common;
namespace InternalUtilities
{

View File

@@ -5,7 +5,10 @@
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\audible api\AudibleApi\AudibleApi\AudibleApi.csproj" />
<PackageReference Include="AudibleApi" Version="1.0.6.2" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\FileManager\FileManager.csproj" />
</ItemGroup>

View File

@@ -5,6 +5,7 @@ VisualStudioVersion = 16.0.28803.156
MinimumVisualStudioVersion = 10.0.40219.1
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "_Solution Items", "_Solution Items", "{03C8835F-936C-4AF7-87AE-FF92BDBE8B9B}"
ProjectSection(SolutionItems) = preProject
__README - COLLABORATORS.txt = __README - COLLABORATORS.txt
__TODO.txt = __TODO.txt
_DB_NOTES.txt = _DB_NOTES.txt
REFERENCE.txt = REFERENCE.txt
@@ -34,59 +35,25 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "3 Domain Internal Utilities
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LibationSearchEngine", "LibationSearchEngine\LibationSearchEngine.csproj", "{2E1F5DB4-40CC-4804-A893-5DCE0193E598}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "0 Tests", "0 Tests", "{38E6C6D9-963A-4C5B-89F4-F2F14885ADFD}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "0 Demos and Examples", "0 Demos and Examples", "{F61184E7-2426-4A13-ACEF-5689928E2CE2}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Dinah.Core.Demos", "..\Dinah.Core\_Demos\Dinah.Core.Demos\Dinah.Core.Demos.csproj", "{9F1AA3DE-962F-469B-82B2-46F93491389B}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Dinah.Core.Tests", "..\Dinah.Core\_Tests\Dinah.Core.Tests\Dinah.Core.Tests.csproj", "{E874D000-AD3A-4629-AC65-7219C2C7C1F0}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TestCommon", "..\Dinah.Core\_Tests\TestCommon\TestCommon.csproj", "{FF12ADA0-8975-4E67-B6EA-4AC82E0C8994}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GitAllRepos", "..\GitAllRepos\GitAllRepos\GitAllRepos.csproj", "{AD1FDDC9-8D2A-436A-8EED-91FD74E7C7B4}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AudibleApi", "..\audible api\AudibleApi\AudibleApi\AudibleApi.csproj", "{7EA01F9C-E579-4B01-A3B9-733B49DD0B60}"
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}") = "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
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Dinah.Core", "..\Dinah.Core\Dinah.Core\Dinah.Core.csproj", "{9E951521-2587-4FC6-AD26-FAA9179FB6C4}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Dinah.EntityFrameworkCore", "..\Dinah.Core\Dinah.EntityFrameworkCore\Dinah.EntityFrameworkCore.csproj", "{1255D9BA-CE6E-42E4-A253-6376540B9661}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LuceneNet303r2", "..\LuceneNet303r2\LuceneNet303r2\LuceneNet303r2.csproj", "{35803735-B669-4090-9681-CC7F7FABDC71}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LuceneNet303r2.Tests", "..\LuceneNet303r2\LuceneNet303r2.Tests\LuceneNet303r2.Tests.csproj", "{5A7681A5-60D9-480B-9AC7-63E0812A2548}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DtoImporterService", "DtoImporterService\DtoImporterService.csproj", "{401865F5-1942-4713-B230-04544C0A97B0}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AudibleApiDTOs", "..\audible api\AudibleApi\AudibleApiDTOs\AudibleApiDTOs.csproj", "{C03C5D65-3B7F-453B-972F-23950B7E0604}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AudibleApiDTOs.Tests", "..\audible api\AudibleApi\_Tests\AudibleApiDTOs.Tests\AudibleApiDTOs.Tests.csproj", "{6069D7F6-BEA0-4917-AFD4-4EB680CB0EDD}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AudibleApiClientExample", "..\audible api\AudibleApi\_Demos\AudibleApiClientExample\AudibleApiClientExample.csproj", "{282EEE16-F569-47E1-992F-C6DB8AEC7AA6}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ApplicationServices", "ApplicationServices\ApplicationServices.csproj", "{B95650EA-25F0-449E-BA5D-99126BC5D730}"
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("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LibationLauncher", "LibationLauncher\LibationLauncher.csproj", "{F3B04A3A-20C8-4582-A54A-715AF6A5D859}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "0 Libation Tests", "0 Libation Tests", "{67E66E82-5532-4440-AFB3-9FB1DF9DEF53}"
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "_Tests", "_Tests", "{67E66E82-5532-4440-AFB3-9FB1DF9DEF53}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "InternalUtilities.Tests", "_Tests\InternalUtilities.Tests\InternalUtilities.Tests.csproj", "{8447C956-B03E-4F59-9DD4-877793B849D9}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LibationSearchEngine.Tests", "_Tests\LibationSearchEngine.Tests\LibationSearchEngine.Tests.csproj", "{C5B21768-C7C9-4FCB-AC1E-187B223D5A98}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Dinah.EntityFrameworkCore.Tests", "..\Dinah.Core\_Tests\Dinah.EntityFrameworkCore.Tests\Dinah.EntityFrameworkCore.Tests.csproj", "{6F5131A0-09AE-4707-B82B-5E53CB74688E}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AAXClean", "..\AAXClean\AAXClean.csproj", "{94BEB7CC-511D-45AB-9F09-09BE858EE486}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Hoopla", "Hoopla\Hoopla.csproj", "{D8F56E5A-3E65-41A6-B7E7-C4515A264B1F}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Hoopla", "Hoopla\Hoopla.csproj", "{D8F56E5A-3E65-41A6-B7E7-C4515A264B1F}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
@@ -118,30 +85,6 @@ Global
{2E1F5DB4-40CC-4804-A893-5DCE0193E598}.Debug|Any CPU.Build.0 = Debug|Any CPU
{2E1F5DB4-40CC-4804-A893-5DCE0193E598}.Release|Any CPU.ActiveCfg = Release|Any CPU
{2E1F5DB4-40CC-4804-A893-5DCE0193E598}.Release|Any CPU.Build.0 = Release|Any CPU
{9F1AA3DE-962F-469B-82B2-46F93491389B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{9F1AA3DE-962F-469B-82B2-46F93491389B}.Debug|Any CPU.Build.0 = Debug|Any CPU
{9F1AA3DE-962F-469B-82B2-46F93491389B}.Release|Any CPU.ActiveCfg = Release|Any CPU
{9F1AA3DE-962F-469B-82B2-46F93491389B}.Release|Any CPU.Build.0 = Release|Any CPU
{E874D000-AD3A-4629-AC65-7219C2C7C1F0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{E874D000-AD3A-4629-AC65-7219C2C7C1F0}.Debug|Any CPU.Build.0 = Debug|Any CPU
{E874D000-AD3A-4629-AC65-7219C2C7C1F0}.Release|Any CPU.ActiveCfg = Release|Any CPU
{E874D000-AD3A-4629-AC65-7219C2C7C1F0}.Release|Any CPU.Build.0 = Release|Any CPU
{FF12ADA0-8975-4E67-B6EA-4AC82E0C8994}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{FF12ADA0-8975-4E67-B6EA-4AC82E0C8994}.Debug|Any CPU.Build.0 = Debug|Any CPU
{FF12ADA0-8975-4E67-B6EA-4AC82E0C8994}.Release|Any CPU.ActiveCfg = Release|Any CPU
{FF12ADA0-8975-4E67-B6EA-4AC82E0C8994}.Release|Any CPU.Build.0 = Release|Any CPU
{AD1FDDC9-8D2A-436A-8EED-91FD74E7C7B4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{AD1FDDC9-8D2A-436A-8EED-91FD74E7C7B4}.Debug|Any CPU.Build.0 = Debug|Any CPU
{AD1FDDC9-8D2A-436A-8EED-91FD74E7C7B4}.Release|Any CPU.ActiveCfg = Release|Any CPU
{AD1FDDC9-8D2A-436A-8EED-91FD74E7C7B4}.Release|Any CPU.Build.0 = Release|Any CPU
{7EA01F9C-E579-4B01-A3B9-733B49DD0B60}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{7EA01F9C-E579-4B01-A3B9-733B49DD0B60}.Debug|Any CPU.Build.0 = Debug|Any CPU
{7EA01F9C-E579-4B01-A3B9-733B49DD0B60}.Release|Any CPU.ActiveCfg = Release|Any CPU
{7EA01F9C-E579-4B01-A3B9-733B49DD0B60}.Release|Any CPU.Build.0 = Release|Any CPU
{111420E2-D4F0-4068-B46A-C4B6DCC823DC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{111420E2-D4F0-4068-B46A-C4B6DCC823DC}.Debug|Any CPU.Build.0 = Debug|Any CPU
{111420E2-D4F0-4068-B46A-C4B6DCC823DC}.Release|Any CPU.ActiveCfg = Release|Any CPU
{111420E2-D4F0-4068-B46A-C4B6DCC823DC}.Release|Any CPU.Build.0 = Release|Any CPU
{635F00E1-AAD1-45F7-BEB7-D909AD33B9F6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{635F00E1-AAD1-45F7-BEB7-D909AD33B9F6}.Debug|Any CPU.Build.0 = Debug|Any CPU
{635F00E1-AAD1-45F7-BEB7-D909AD33B9F6}.Release|Any CPU.ActiveCfg = Release|Any CPU
@@ -150,46 +93,14 @@ Global
{0807616A-A77A-4B08-A65A-1582B09E114B}.Debug|Any CPU.Build.0 = Debug|Any CPU
{0807616A-A77A-4B08-A65A-1582B09E114B}.Release|Any CPU.ActiveCfg = Release|Any CPU
{0807616A-A77A-4B08-A65A-1582B09E114B}.Release|Any CPU.Build.0 = Release|Any CPU
{9E951521-2587-4FC6-AD26-FAA9179FB6C4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{9E951521-2587-4FC6-AD26-FAA9179FB6C4}.Debug|Any CPU.Build.0 = Debug|Any CPU
{9E951521-2587-4FC6-AD26-FAA9179FB6C4}.Release|Any CPU.ActiveCfg = Release|Any CPU
{9E951521-2587-4FC6-AD26-FAA9179FB6C4}.Release|Any CPU.Build.0 = Release|Any CPU
{1255D9BA-CE6E-42E4-A253-6376540B9661}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{1255D9BA-CE6E-42E4-A253-6376540B9661}.Debug|Any CPU.Build.0 = Debug|Any CPU
{1255D9BA-CE6E-42E4-A253-6376540B9661}.Release|Any CPU.ActiveCfg = Release|Any CPU
{1255D9BA-CE6E-42E4-A253-6376540B9661}.Release|Any CPU.Build.0 = Release|Any CPU
{35803735-B669-4090-9681-CC7F7FABDC71}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{35803735-B669-4090-9681-CC7F7FABDC71}.Debug|Any CPU.Build.0 = Debug|Any CPU
{35803735-B669-4090-9681-CC7F7FABDC71}.Release|Any CPU.ActiveCfg = Release|Any CPU
{35803735-B669-4090-9681-CC7F7FABDC71}.Release|Any CPU.Build.0 = Release|Any CPU
{5A7681A5-60D9-480B-9AC7-63E0812A2548}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{5A7681A5-60D9-480B-9AC7-63E0812A2548}.Debug|Any CPU.Build.0 = Debug|Any CPU
{5A7681A5-60D9-480B-9AC7-63E0812A2548}.Release|Any CPU.ActiveCfg = Release|Any CPU
{5A7681A5-60D9-480B-9AC7-63E0812A2548}.Release|Any CPU.Build.0 = Release|Any CPU
{401865F5-1942-4713-B230-04544C0A97B0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{401865F5-1942-4713-B230-04544C0A97B0}.Debug|Any CPU.Build.0 = Debug|Any CPU
{401865F5-1942-4713-B230-04544C0A97B0}.Release|Any CPU.ActiveCfg = Release|Any CPU
{401865F5-1942-4713-B230-04544C0A97B0}.Release|Any CPU.Build.0 = Release|Any CPU
{C03C5D65-3B7F-453B-972F-23950B7E0604}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{C03C5D65-3B7F-453B-972F-23950B7E0604}.Debug|Any CPU.Build.0 = Debug|Any CPU
{C03C5D65-3B7F-453B-972F-23950B7E0604}.Release|Any CPU.ActiveCfg = Release|Any CPU
{C03C5D65-3B7F-453B-972F-23950B7E0604}.Release|Any CPU.Build.0 = Release|Any CPU
{6069D7F6-BEA0-4917-AFD4-4EB680CB0EDD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{6069D7F6-BEA0-4917-AFD4-4EB680CB0EDD}.Debug|Any CPU.Build.0 = Debug|Any CPU
{6069D7F6-BEA0-4917-AFD4-4EB680CB0EDD}.Release|Any CPU.ActiveCfg = Release|Any CPU
{6069D7F6-BEA0-4917-AFD4-4EB680CB0EDD}.Release|Any CPU.Build.0 = Release|Any CPU
{282EEE16-F569-47E1-992F-C6DB8AEC7AA6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{282EEE16-F569-47E1-992F-C6DB8AEC7AA6}.Debug|Any CPU.Build.0 = Debug|Any CPU
{282EEE16-F569-47E1-992F-C6DB8AEC7AA6}.Release|Any CPU.ActiveCfg = Release|Any CPU
{282EEE16-F569-47E1-992F-C6DB8AEC7AA6}.Release|Any CPU.Build.0 = Release|Any CPU
{B95650EA-25F0-449E-BA5D-99126BC5D730}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{B95650EA-25F0-449E-BA5D-99126BC5D730}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B95650EA-25F0-449E-BA5D-99126BC5D730}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B95650EA-25F0-449E-BA5D-99126BC5D730}.Release|Any CPU.Build.0 = Release|Any CPU
{059CE32C-9AD6-45E9-A166-790DFFB0B730}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{059CE32C-9AD6-45E9-A166-790DFFB0B730}.Debug|Any CPU.Build.0 = Debug|Any CPU
{059CE32C-9AD6-45E9-A166-790DFFB0B730}.Release|Any CPU.ActiveCfg = Release|Any CPU
{059CE32C-9AD6-45E9-A166-790DFFB0B730}.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
@@ -202,10 +113,6 @@ Global
{C5B21768-C7C9-4FCB-AC1E-187B223D5A98}.Debug|Any CPU.Build.0 = Debug|Any CPU
{C5B21768-C7C9-4FCB-AC1E-187B223D5A98}.Release|Any CPU.ActiveCfg = Release|Any CPU
{C5B21768-C7C9-4FCB-AC1E-187B223D5A98}.Release|Any CPU.Build.0 = Release|Any CPU
{6F5131A0-09AE-4707-B82B-5E53CB74688E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{6F5131A0-09AE-4707-B82B-5E53CB74688E}.Debug|Any CPU.Build.0 = Debug|Any CPU
{6F5131A0-09AE-4707-B82B-5E53CB74688E}.Release|Any CPU.ActiveCfg = Release|Any CPU
{6F5131A0-09AE-4707-B82B-5E53CB74688E}.Release|Any CPU.Build.0 = Release|Any CPU
{94BEB7CC-511D-45AB-9F09-09BE858EE486}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{94BEB7CC-511D-45AB-9F09-09BE858EE486}.Debug|Any CPU.Build.0 = Debug|Any CPU
{94BEB7CC-511D-45AB-9F09-09BE858EE486}.Release|Any CPU.ActiveCfg = Release|Any CPU
@@ -220,33 +127,18 @@ Global
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{8BD8E012-F44F-4EE2-A234-D66C14D5FE4B} = {7FBBB086-0807-4998-85BF-6D1A49C8AD05}
{1AE65B61-9C05-4C80-ABFF-48F16E22FDF1} = {7FBBB086-0807-4998-85BF-6D1A49C8AD05}
{1AE65B61-9C05-4C80-ABFF-48F16E22FDF1} = {F0CBB7A7-D3FB-41FF-8F47-CF3F6A592249}
{59A10DF3-63EC-43F1-A3BF-4000CFA118D2} = {751093DD-5DBA-463E-ADBE-E05FAFB6983E}
{393B5B27-D15C-4F77-9457-FA14BA8F3C73} = {41CDCC73-9B81-49DD-9570-C54406E852AF}
{06882742-27A6-4347-97D9-56162CEC9C11} = {F0CBB7A7-D3FB-41FF-8F47-CF3F6A592249}
{2E1F5DB4-40CC-4804-A893-5DCE0193E598} = {41CDCC73-9B81-49DD-9570-C54406E852AF}
{9F1AA3DE-962F-469B-82B2-46F93491389B} = {F61184E7-2426-4A13-ACEF-5689928E2CE2}
{E874D000-AD3A-4629-AC65-7219C2C7C1F0} = {38E6C6D9-963A-4C5B-89F4-F2F14885ADFD}
{FF12ADA0-8975-4E67-B6EA-4AC82E0C8994} = {38E6C6D9-963A-4C5B-89F4-F2F14885ADFD}
{AD1FDDC9-8D2A-436A-8EED-91FD74E7C7B4} = {38E6C6D9-963A-4C5B-89F4-F2F14885ADFD}
{7EA01F9C-E579-4B01-A3B9-733B49DD0B60} = {7FBBB086-0807-4998-85BF-6D1A49C8AD05}
{111420E2-D4F0-4068-B46A-C4B6DCC823DC} = {38E6C6D9-963A-4C5B-89F4-F2F14885ADFD}
{635F00E1-AAD1-45F7-BEB7-D909AD33B9F6} = {8679CAC8-9164-4007-BDD2-F004810EDA14}
{0807616A-A77A-4B08-A65A-1582B09E114B} = {8679CAC8-9164-4007-BDD2-F004810EDA14}
{9E951521-2587-4FC6-AD26-FAA9179FB6C4} = {43E3ACB3-E0BC-4370-8DBB-E3720C8C8FD1}
{1255D9BA-CE6E-42E4-A253-6376540B9661} = {43E3ACB3-E0BC-4370-8DBB-E3720C8C8FD1}
{35803735-B669-4090-9681-CC7F7FABDC71} = {7FBBB086-0807-4998-85BF-6D1A49C8AD05}
{5A7681A5-60D9-480B-9AC7-63E0812A2548} = {38E6C6D9-963A-4C5B-89F4-F2F14885ADFD}
{401865F5-1942-4713-B230-04544C0A97B0} = {41CDCC73-9B81-49DD-9570-C54406E852AF}
{C03C5D65-3B7F-453B-972F-23950B7E0604} = {7FBBB086-0807-4998-85BF-6D1A49C8AD05}
{6069D7F6-BEA0-4917-AFD4-4EB680CB0EDD} = {38E6C6D9-963A-4C5B-89F4-F2F14885ADFD}
{282EEE16-F569-47E1-992F-C6DB8AEC7AA6} = {F61184E7-2426-4A13-ACEF-5689928E2CE2}
{B95650EA-25F0-449E-BA5D-99126BC5D730} = {41CDCC73-9B81-49DD-9570-C54406E852AF}
{059CE32C-9AD6-45E9-A166-790DFFB0B730} = {43E3ACB3-E0BC-4370-8DBB-E3720C8C8FD1}
{F3B04A3A-20C8-4582-A54A-715AF6A5D859} = {8679CAC8-9164-4007-BDD2-F004810EDA14}
{8447C956-B03E-4F59-9DD4-877793B849D9} = {67E66E82-5532-4440-AFB3-9FB1DF9DEF53}
{C5B21768-C7C9-4FCB-AC1E-187B223D5A98} = {67E66E82-5532-4440-AFB3-9FB1DF9DEF53}
{6F5131A0-09AE-4707-B82B-5E53CB74688E} = {38E6C6D9-963A-4C5B-89F4-F2F14885ADFD}
{94BEB7CC-511D-45AB-9F09-09BE858EE486} = {7FBBB086-0807-4998-85BF-6D1A49C8AD05}
{D8F56E5A-3E65-41A6-B7E7-C4515A264B1F} = {7FBBB086-0807-4998-85BF-6D1A49C8AD05}
EndGlobalSection

View File

@@ -13,7 +13,7 @@
<!-- <PublishSingleFile>true</PublishSingleFile> -->
<RuntimeIdentifier>win-x64</RuntimeIdentifier>
<Version>5.5.2.1</Version>
<Version>5.5.9.1</Version>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">

View File

@@ -8,7 +8,15 @@ using Newtonsoft.Json.Linq;
namespace LibationLauncher
{
/// <summary>for migrations only. directly manipulatings settings files without going through domain logic</summary>
/// <summary>
///
///
/// directly manipulates settings files without going through domain logic.
///
/// for migrations only. use with caution.
///
///
/// </summary>
internal static class UNSAFE_MigrationHelper
{
#region appsettings.json

View File

@@ -9,7 +9,10 @@
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\LuceneNet303r2\LuceneNet303r2\LuceneNet303r2.csproj" />
<PackageReference Include="LuceneNet303r2" Version="3.0.3.2" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\DataLayer\DataLayer.csproj" />
<ProjectReference Include="..\FileManager\FileManager.csproj" />
</ItemGroup>

View File

@@ -9,24 +9,24 @@ using Lucene.Net.Search;
namespace LibationSearchEngine
{
// field names are case specific and, due to StandardAnalyzer, content is case INspecific
public static class LuceneExtensions
internal static class LuceneExtensions
{
public static void AddRaw(this Document document, string name, string value)
internal static void AddRaw(this Document document, string name, string value)
=> document.Add(new Field(name, value, Field.Store.YES, Field.Index.NOT_ANALYZED));
public static void AddAnalyzed(this Document document, string name, string value)
internal static void AddAnalyzed(this Document document, string name, string value)
{
if (value != null)
document.Add(new Field(name.ToLowerInvariant(), value, Field.Store.YES, Field.Index.ANALYZED));
}
public static void AddNotAnalyzed(this Document document, string name, string value)
internal static void AddNotAnalyzed(this Document document, string name, string value)
=> document.Add(new Field(name.ToLowerInvariant(), value, Field.Store.YES, Field.Index.NOT_ANALYZED));
public static void AddBool(this Document document, string name, bool value)
internal static void AddBool(this Document document, string name, bool value)
=> document.Add(new Field(name.ToLowerInvariant(), value.ToString(), Field.Store.YES, Field.Index.ANALYZED_NO_NORMS));
public static Query GetQuery(this Analyzer analyzer, string defaultField, string searchString)
internal static Query GetQuery(this Analyzer analyzer, string defaultField, string searchString)
=> new QueryParser(SearchEngine.Version, defaultField.ToLowerInvariant(), analyzer).Parse(searchString);
// put all numbers, including dates, into this format:

View File

@@ -107,14 +107,14 @@ namespace LibationSearchEngine
= new ReadOnlyDictionary<string, Func<LibraryBook, bool>>(
new Dictionary<string, Func<LibraryBook, bool>>
{
["HasDownloads"] = lb => lb.Book.Supplements.Any(),
["HasDownload"] = lb => lb.Book.Supplements.Any(),
["Downloads"] = lb => lb.Book.Supplements.Any(),
["Download"] = lb => lb.Book.Supplements.Any(),
["HasPDFs"] = lb => lb.Book.Supplements.Any(),
["HasPDF"] = lb => lb.Book.Supplements.Any(),
["PDFs"] = lb => lb.Book.Supplements.Any(),
["PDF"] = lb => lb.Book.Supplements.Any(),
["HasDownloads"] = lb => lb.Book.HasPdf,
["HasDownload"] = lb => lb.Book.HasPdf,
["Downloads"] = lb => lb.Book.HasPdf,
["Download"] = lb => lb.Book.HasPdf,
["HasPDFs"] = lb => lb.Book.HasPdf,
["HasPDF"] = lb => lb.Book.HasPdf,
["PDFs"] = lb => lb.Book.HasPdf,
["PDF"] = lb => lb.Book.HasPdf,
["IsRated"] = lb => lb.Book.UserDefinedItem.Rating.OverallRating > 0f,
["Rated"] = lb => lb.Book.UserDefinedItem.Rating.OverallRating > 0f,

View File

@@ -50,24 +50,24 @@ namespace LibationWinForms.BookLiberation
public static class ProcessorAutomationController
{
public static async Task BackupSingleBookAsync(LibraryBook libraryBook, EventHandler<LibraryBook> completedAction = null)
public static async Task BackupSingleBookAsync(LibraryBook libraryBook)
{
Serilog.Log.Logger.Information($"Begin {nameof(BackupSingleBookAsync)} {{@DebugInfo}}", new { libraryBook?.Book?.AudibleProductId });
var logMe = LogMe.RegisterForm();
var backupBook = CreateBackupBook(completedAction, logMe);
var backupBook = CreateBackupBook(logMe);
// continue even if libraryBook is null. we'll display even that in the processing box
await new BackupSingle(logMe, backupBook, libraryBook).RunBackupAsync();
}
public static async Task BackupAllBooksAsync(EventHandler<LibraryBook> completedAction = null)
public static async Task BackupAllBooksAsync()
{
Serilog.Log.Logger.Information("Begin " + nameof(BackupAllBooksAsync));
var automatedBackupsForm = new AutomatedBackupsForm();
var logMe = LogMe.RegisterForm(automatedBackupsForm);
var backupBook = CreateBackupBook(completedAction, logMe);
var backupBook = CreateBackupBook(logMe);
await new BackupLoop(logMe, backupBook, automatedBackupsForm).RunBackupAsync();
}
@@ -84,19 +84,19 @@ namespace LibationWinForms.BookLiberation
await new BackupLoop(logMe, convertBook, automatedBackupsForm).RunBackupAsync();
}
public static async Task BackupAllPdfsAsync(EventHandler<LibraryBook> completedAction = null)
public static async Task BackupAllPdfsAsync()
{
Serilog.Log.Logger.Information("Begin " + nameof(BackupAllPdfsAsync));
var automatedBackupsForm = new AutomatedBackupsForm();
var logMe = LogMe.RegisterForm(automatedBackupsForm);
var downloadPdf = CreateProcessable<DownloadPdf, PdfDownloadForm>(logMe, completedAction);
var downloadPdf = CreateProcessable<DownloadPdf, PdfDownloadForm>(logMe);
await new BackupLoop(logMe, downloadPdf, automatedBackupsForm).RunBackupAsync();
}
private static IProcessable CreateBackupBook(EventHandler<LibraryBook> completedAction, LogMe logMe)
private static IProcessable CreateBackupBook(LogMe logMe)
{
var downloadPdf = CreateProcessable<DownloadPdf, PdfDownloadForm>(logMe);
@@ -104,7 +104,6 @@ namespace LibationWinForms.BookLiberation
async void onDownloadDecryptBookCompleted(object sender, LibraryBook e)
{
await downloadPdf.TryProcessAsync(e);
completedAction(sender, e);
}
var downloadDecryptBook = CreateProcessable<DownloadDecryptBook, AudioDecryptForm>(logMe, onDownloadDecryptBookCompleted);
@@ -246,7 +245,7 @@ $@" Title: {libraryBook.Book.Title}
if (dialogResult == SkipResult)
{
ApplicationServices.LibraryCommands.UpdateBook(libraryBook, LiberatedStatus.Error);
libraryBook.Book.UserDefinedItem.BookStatus = LiberatedStatus.Error;
LogMe.Info($"Error. Skip: [{libraryBook.Book.AudibleProductId}] {libraryBook.Book.Title}");
}
@@ -305,7 +304,7 @@ An error occurred while trying to process this book.
protected override async Task RunAsync()
{
// support for 'skip this time only' requires state. iterators provide this state for free. therefore: use foreach/iterator here
foreach (var libraryBook in Processable.GetValidLibraryBooks())
foreach (var libraryBook in Processable.GetValidLibraryBooks(ApplicationServices.DbContexts.GetContext().GetLibrary_Flat_NoTracking()))
{
var keepGoing = await ProcessOneAsync(libraryBook, validate: false);
if (!keepGoing)

View File

@@ -99,7 +99,7 @@
this.detailsTb.ReadOnly = true;
this.detailsTb.ScrollBars = System.Windows.Forms.ScrollBars.Both;
this.detailsTb.Size = new System.Drawing.Size(484, 202);
this.detailsTb.TabIndex = 0;
this.detailsTb.TabIndex = 1;
//
// tagsGb
//
@@ -110,7 +110,7 @@
this.tagsGb.Location = new System.Drawing.Point(12, 220);
this.tagsGb.Name = "tagsGb";
this.tagsGb.Size = new System.Drawing.Size(570, 73);
this.tagsGb.TabIndex = 1;
this.tagsGb.TabIndex = 0;
this.tagsGb.TabStop = false;
this.tagsGb.Text = "Edit Tags";
//

View File

@@ -77,7 +77,6 @@ Purchase Date: {_libraryBook.DateAdded.ToString("d")}
if (status == LiberatedStatus.Error)
this.bookLiberatedCb.Items.Add(new liberatedComboBoxItem { Status = LiberatedStatus.Error, Text = "Error" });
setDefaultComboBox(this.bookLiberatedCb, status);
}

View File

@@ -50,7 +50,6 @@ namespace LibationWinForms
// also applies filter. ONLY call AFTER loading grid
loadInitialQuickFilterState();
}
private void Form1_FormClosing(object sender, FormClosingEventArgs e)
@@ -172,26 +171,26 @@ namespace LibationWinForms
#region bottom: backup counts
private async void setBackupCountsAsync(object _, object __)
{
LibraryCommands.LibraryStats libraryStats = null;
await Task.Run(() => libraryStats = LibraryCommands.GetCounts());
var libraryStats = await Task.Run(() => LibraryCommands.GetCounts());
setBookBackupCounts(libraryStats.booksFullyBackedUp, libraryStats.booksDownloadedOnly, libraryStats.booksNoProgress);
setPdfBackupCounts(libraryStats.pdfsDownloaded, libraryStats.pdfsNotDownloaded);
setBookBackupCounts(libraryStats);
setPdfBackupCounts(libraryStats);
}
private void setBookBackupCounts(int booksFullyBackedUp, int booksDownloadedOnly, int booksNoProgress)
private void setBookBackupCounts(LibraryCommands.LibraryStats libraryStats)
{
var backupsCountsLbl_Format = "BACKUPS: No progress: {0} Encrypted: {1} Fully backed up: {2}";
// enable/disable export
var hasResults = 0 < (booksFullyBackedUp + booksDownloadedOnly + booksNoProgress);
var hasResults = 0 < (libraryStats.booksFullyBackedUp + libraryStats.booksDownloadedOnly + libraryStats.booksNoProgress + libraryStats.booksError);
exportLibraryToolStripMenuItem.Enabled = hasResults;
// update bottom numbers
var pending = booksNoProgress + booksDownloadedOnly;
var pending = libraryStats.booksNoProgress + libraryStats.booksDownloadedOnly;
var statusStripText
= !hasResults ? "No books. Begin by importing your library"
: pending > 0 ? string.Format(backupsCountsLbl_Format, booksNoProgress, booksDownloadedOnly, booksFullyBackedUp)
: $"All {"book".PluralizeWithCount(booksFullyBackedUp)} backed up";
: libraryStats.booksError > 0 ? string.Format(backupsCountsLbl_Format + " Errors: {3}", libraryStats.booksNoProgress, libraryStats.booksDownloadedOnly, libraryStats.booksFullyBackedUp, libraryStats.booksError)
: pending > 0 ? string.Format(backupsCountsLbl_Format, libraryStats.booksNoProgress, libraryStats.booksDownloadedOnly, libraryStats.booksFullyBackedUp)
: $"All {"book".PluralizeWithCount(libraryStats.booksFullyBackedUp)} backed up";
// update menu item
var menuItemText
@@ -204,26 +203,26 @@ namespace LibationWinForms
menuStrip1.UIThread(() => beginBookBackupsToolStripMenuItem.Enabled = pending > 0);
menuStrip1.UIThread(() => beginBookBackupsToolStripMenuItem.Text = string.Format(beginBookBackupsToolStripMenuItem_format, menuItemText));
}
private void setPdfBackupCounts(int pdfsDownloaded, int pdfsNotDownloaded)
private void setPdfBackupCounts(LibraryCommands.LibraryStats libraryStats)
{
var pdfsCountsLbl_Format = "| PDFs: NOT d/l\'ed: {0} Downloaded: {1}";
// update bottom numbers
var hasResults = 0 < (pdfsNotDownloaded + pdfsDownloaded);
var hasResults = 0 < (libraryStats.pdfsNotDownloaded + libraryStats.pdfsDownloaded);
var statusStripText
= !hasResults ? ""
: pdfsNotDownloaded > 0 ? string.Format(pdfsCountsLbl_Format, pdfsNotDownloaded, pdfsDownloaded)
: $"| All {pdfsDownloaded} PDFs downloaded";
: libraryStats.pdfsNotDownloaded > 0 ? string.Format(pdfsCountsLbl_Format, libraryStats.pdfsNotDownloaded, libraryStats.pdfsDownloaded)
: $"| All {libraryStats.pdfsDownloaded} PDFs downloaded";
// update menu item
var menuItemText
= pdfsNotDownloaded > 0
? $"{pdfsNotDownloaded} remaining"
= libraryStats.pdfsNotDownloaded > 0
? $"{libraryStats.pdfsNotDownloaded} remaining"
: "All PDFs have been downloaded";
// update UI
statusStrip1.UIThread(() => pdfsCountsLbl.Text = statusStripText);
menuStrip1.UIThread(() => beginPdfBackupsToolStripMenuItem.Enabled = pdfsNotDownloaded > 0);
menuStrip1.UIThread(() => beginPdfBackupsToolStripMenuItem.Enabled = libraryStats.pdfsNotDownloaded > 0);
menuStrip1.UIThread(() => beginPdfBackupsToolStripMenuItem.Text = string.Format(beginPdfBackupsToolStripMenuItem_format, menuItemText));
}
#endregion
@@ -384,15 +383,14 @@ namespace LibationWinForms
#region liberate menu
private async void beginBookBackupsToolStripMenuItem_Click(object sender, EventArgs e)
=> await BookLiberation.ProcessorAutomationController.BackupAllBooksAsync(updateGridRow);
=> await BookLiberation.ProcessorAutomationController.BackupAllBooksAsync();
private async void beginPdfBackupsToolStripMenuItem_Click(object sender, EventArgs e)
=> await BookLiberation.ProcessorAutomationController.BackupAllPdfsAsync(updateGridRow);
=> await BookLiberation.ProcessorAutomationController.BackupAllPdfsAsync();
private async void convertAllM4bToMp3ToolStripMenuItem_Click(object sender, EventArgs e)
=> await BookLiberation.ProcessorAutomationController.ConvertAllBooksAsync();
private void updateGridRow(object _, LibraryBook libraryBook) => currProductsGrid.RefreshRow(libraryBook.Book.AudibleProductId);
#endregion
#region Export menu

View File

@@ -12,6 +12,9 @@ using Dinah.Core.Drawing;
namespace LibationWinForms
{
/// <summary>
/// The View Model for a LibraryBook
/// </summary>
internal class GridEntry : AsyncNotifyPropertyChanged, IMemberComparable
{
#region implementation properties
@@ -26,12 +29,15 @@ namespace LibationWinForms
private Book Book => LibraryBook.Book;
private Image _cover;
private Action Refilter { get; }
public GridEntry(LibraryBook libraryBook)
public GridEntry(LibraryBook libraryBook, Action refilterOnChanged = null)
{
LibraryBook = libraryBook;
Refilter = refilterOnChanged;
_memberValues = CreateMemberValueDictionary();
//Get cover art. If it's default, subscribe to PictureCached
{
(bool isDefault, byte[] picture) = FileManager.PictureStorage.GetPicture(new FileManager.PictureDefinition(Book.PictureId, FileManager.PictureSize._80x80));
@@ -58,7 +64,7 @@ namespace LibationWinForms
Description = GetDescriptionDisplay(Book);
}
//DisplayTags and Liberate properties are live.
UserDefinedItem.ItemChanged += UserDefinedItem_ItemChanged;
}
private void PictureStorage_PictureCached(object sender, FileManager.PictureCachedEventArgs e)
@@ -70,8 +76,78 @@ namespace LibationWinForms
}
}
#region Data Source properties
#region detect changes to the model, update the view, and save to database.
/// <summary>
/// This event handler receives notifications from the model that it has changed.
/// Save to the database and notify the view that it's changed.
/// </summary>
private void UserDefinedItem_ItemChanged(object sender, string itemName)
{
var udi = sender as UserDefinedItem;
if (udi.Book.AudibleProductId != Book.AudibleProductId)
return;
switch (itemName)
{
case nameof(udi.Tags):
{
Book.UserDefinedItem.Tags = udi.Tags;
NotifyPropertyChanged(nameof(DisplayTags));
}
break;
case nameof(udi.BookStatus):
{
Book.UserDefinedItem.BookStatus = udi.BookStatus;
NotifyPropertyChanged(nameof(Liberate));
}
break;
case nameof(udi.PdfStatus):
{
Book.UserDefinedItem.PdfStatus = udi.PdfStatus;
NotifyPropertyChanged(nameof(Liberate));
}
break;
}
if (!suspendCommit)
Commit();
}
private bool suspendCommit = false;
/// <summary>
/// Begin editing the model, suspending commits until <see cref="EndEdit"/> is called.
/// </summary>
public void BeginEdit() => suspendCommit = true;
/// <summary>
/// Save all edits to the database.
/// </summary>
public void EndEdit()
{
Commit();
suspendCommit = false;
}
private void Commit()
{
// We don't want LiberatedStatus.PartialDownload to be a persistent status.
// If display/icon status is PartialDownload then save NotLiberated to db then restore PartialDownload for display
var displayStatus = Book.UserDefinedItem.BookStatus;
var saveStatus = displayStatus == LiberatedStatus.PartialDownload ? LiberatedStatus.NotLiberated : displayStatus;
Book.UserDefinedItem.BookStatus = saveStatus;
LibraryCommands.UpdateUserDefinedItem(Book);
Book.UserDefinedItem.BookStatus = displayStatus;
Refilter?.Invoke();
}
#endregion
#region Model properties exposed to the view
public Image Cover
{
get
@@ -96,8 +172,23 @@ namespace LibationWinForms
public string Category { get; }
public string Misc { get; }
public string Description { get; }
public string DisplayTags => string.Join("\r\n", Book.UserDefinedItem.TagsEnumerated);
public (LiberatedState, PdfState) Liberate => (LibraryCommands.Liberated_Status(Book), LibraryCommands.Pdf_Status(Book));
public string DisplayTags
{
get => string.Join("\r\n", Book.UserDefinedItem.TagsEnumerated);
set => Book.UserDefinedItem.Tags = value;
}
// these 2 values being in 1 field is the trick behind getting the liberated+pdf 'stoplight' icon to draw. See: LiberateDataGridViewImageButtonCell.Paint
public (LiberatedStatus BookStatus, LiberatedStatus? PdfStatus) Liberate
{
get => (LibraryCommands.Liberated_Status(LibraryBook.Book), LibraryCommands.Pdf_Status(LibraryBook.Book));
set
{
LibraryBook.Book.UserDefinedItem.BookStatus = value.BookStatus;
LibraryBook.Book.UserDefinedItem.PdfStatus = value.PdfStatus;
}
}
#endregion
#region Data Sorting
@@ -121,7 +212,7 @@ namespace LibationWinForms
{ nameof(Category), () => Category },
{ nameof(Misc), () => Misc },
{ nameof(DisplayTags), () => DisplayTags },
{ nameof(Liberate), () => Liberate.Item1 }
{ nameof(Liberate), () => Liberate.BookStatus }
};
// Instantiate comparers for every exposed member object type.
@@ -131,7 +222,7 @@ namespace LibationWinForms
{ typeof(int), new ObjectComparer<int>() },
{ typeof(float), new ObjectComparer<float>() },
{ typeof(DateTime), new ObjectComparer<DateTime>() },
{ typeof(LiberatedState), new ObjectComparer<LiberatedState>() },
{ typeof(LiberatedStatus), new ObjectComparer<LiberatedStatus>() },
};
public virtual object GetMemberValue(string memberName) => _memberValues[memberName]();
@@ -200,5 +291,11 @@ namespace LibationWinForms
}
#endregion
~GridEntry()
{
UserDefinedItem.ItemChanged -= UserDefinedItem_ItemChanged;
FileManager.PictureStorage.PictureCached -= PictureStorage_PictureCached;
}
}
}

View File

@@ -13,7 +13,11 @@
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\Dinah.Core\Dinah.Core.WindowsDesktop\Dinah.Core.WindowsDesktop.csproj" />
<PackageReference Include="Dinah.Core.WindowsDesktop" Version="1.0.2.1" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\ApplicationServices\ApplicationServices.csproj" />
<ProjectReference Include="..\FileLiberator\FileLiberator.csproj" />
</ItemGroup>

View File

@@ -3,6 +3,7 @@ using System;
using System.Drawing;
using System.Windows.Forms;
using System.Linq;
using DataLayer;
namespace LibationWinForms
{
@@ -20,9 +21,11 @@ namespace LibationWinForms
{
base.Paint(graphics, clipBounds, cellBounds, rowIndex, elementState, null, null, null, cellStyle, advancedBorderStyle, paintParts);
if (value is (LiberatedState liberatedState, PdfState pdfState))
if (value is (LiberatedStatus, LiberatedStatus) or (LiberatedStatus, null))
{
(string mouseoverText, Bitmap buttonImage) = GetLiberateDisplay(liberatedState, pdfState);
var (bookState, pdfState) = ((LiberatedStatus bookState, LiberatedStatus? pdfState))value;
(string mouseoverText, Bitmap buttonImage) = GetLiberateDisplay(bookState, pdfState);
DrawButtonImage(graphics, buttonImage, cellBounds);
@@ -30,29 +33,33 @@ namespace LibationWinForms
}
}
private static (string mouseoverText, Bitmap buttonImage) GetLiberateDisplay(LiberatedState liberatedStatus, PdfState pdfStatus)
private static (string mouseoverText, Bitmap buttonImage) GetLiberateDisplay(LiberatedStatus liberatedStatus, LiberatedStatus? pdfStatus)
{
if (liberatedStatus == LiberatedStatus.Error)
return ("Book downloaded ERROR", SystemIcons.Error.ToBitmap());
(string libState, string image_lib) = liberatedStatus switch
{
LiberatedState.Liberated => ("Liberated", "green"),
LiberatedState.PartialDownload => ("File has been at least\r\npartially downloaded", "yellow"),
LiberatedState.NotDownloaded => ("Book NOT downloaded", "red"),
LiberatedStatus.Liberated => ("Liberated", "green"),
LiberatedStatus.PartialDownload => ("File has been at least\r\npartially downloaded", "yellow"),
LiberatedStatus.NotLiberated => ("Book NOT downloaded", "red"),
_ => throw new Exception("Unexpected liberation state")
};
(string pdfState, string image_pdf) = pdfStatus switch
{
PdfState.Downloaded => ("\r\nPDF downloaded", "_pdf_yes"),
PdfState.NotDownloaded => ("\r\nPDF NOT downloaded", "_pdf_no"),
PdfState.NoPdf => ("", ""),
LiberatedStatus.Liberated => ("\r\nPDF downloaded", "_pdf_yes"),
LiberatedStatus.NotLiberated => ("\r\nPDF NOT downloaded", "_pdf_no"),
LiberatedStatus.Error => ("\r\nPDF downloaded ERROR", "_pdf_no"),
null => ("", ""),
_ => throw new Exception("Unexpected PDF state")
};
var mouseoverText = libState + pdfState;
if (liberatedStatus == LiberatedState.NotDownloaded ||
liberatedStatus == LiberatedState.PartialDownload ||
pdfStatus == PdfState.NotDownloaded)
if (liberatedStatus == LiberatedStatus.NotLiberated ||
liberatedStatus == LiberatedStatus.PartialDownload ||
pdfStatus == LiberatedStatus.NotLiberated)
mouseoverText += "\r\nClick to complete";
var buttonImage = (Bitmap)Properties.Resources.ResourceManager.GetObject($"liberate_{image_lib}{image_pdf}");

View File

@@ -86,7 +86,7 @@ namespace LibationWinForms
}
// else: liberate
await BookLiberation.ProcessorAutomationController.BackupSingleBookAsync(libraryBook, (_, __) => RefreshRow(libraryBook.Book.AudibleProductId));
await BookLiberation.ProcessorAutomationController.BackupSingleBookAsync(libraryBook);
}
private void Details_Click(GridEntry liveGridEntry)
@@ -95,15 +95,14 @@ namespace LibationWinForms
if (bookDetailsForm.ShowDialog() != DialogResult.OK)
return;
var qtyChanges = LibraryCommands.UpdateUserDefinedItem(liveGridEntry.LibraryBook.Book, bookDetailsForm.NewTags, bookDetailsForm.BookLiberatedStatus, bookDetailsForm.PdfLiberatedStatus);
if (qtyChanges == 0)
return;
liveGridEntry.BeginEdit();
//Re-apply filters
Filter();
liveGridEntry.DisplayTags = bookDetailsForm.NewTags;
liveGridEntry.Liberate = (bookDetailsForm.BookLiberatedStatus, bookDetailsForm.PdfLiberatedStatus);
//Update whole GridEntry row
liveGridEntry.NotifyPropertyChanged();
liveGridEntry.EndEdit();
BackupCountsChanged?.Invoke(this, EventArgs.Empty);
}
#endregion
@@ -132,7 +131,7 @@ namespace LibationWinForms
}
var orderedGridEntries = lib
.Select(lb => new GridEntry(lb)).ToList()
.Select(lb => new GridEntry(lb, Filter)).ToList()
// default load order
.OrderByDescending(ge => (DateTime)ge.GetMemberValue(nameof(ge.PurchaseDate)))
//// more advanced example: sort by author, then series, then title
@@ -150,19 +149,6 @@ namespace LibationWinForms
BackupCountsChanged?.Invoke(this, EventArgs.Empty);
}
public void RefreshRow(string productId)
{
var liveGridEntry = getGridEntry((ge) => ge.AudibleProductId == productId);
// update GridEntry Liberate cell
liveGridEntry?.NotifyPropertyChanged(nameof(liveGridEntry.Liberate));
// needed in case filtering by -IsLiberated and it gets changed to Liberated. want to immediately show the change
Filter();
BackupCountsChanged?.Invoke(this, EventArgs.Empty);
}
#endregion
#region Filter
@@ -195,12 +181,7 @@ namespace LibationWinForms
#endregion
#region DataGridView Macro
private GridEntry getGridEntry(Func<GridEntry, bool> predicate)
=> ((SortableBindingList<GridEntry>)gridEntryBindingSource.DataSource).FirstOrDefault(predicate);
private GridEntry getGridEntry(int rowIndex) => _dataGridView.GetBoundItem<GridEntry>(rowIndex);
#endregion
}
}

View File

@@ -18,11 +18,6 @@ using Moq;
using Moq.Protected;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using TestAudibleApiCommon;
using TestCommon;
using static AuthorizationShared.Shared;
using static AuthorizationShared.Shared.AccessTokenTemporality;
using static TestAudibleApiCommon.ComputedTestValues;
namespace AccountsTests
{
@@ -429,7 +424,7 @@ namespace AccountsTests
var exists = accountsSettings.Upsert("cng", "us");
exists.AccountName.Should().Be("foo");
orig.Should().IsSameOrEqualTo(exists);
orig.Should().BeSameAs(exists);
}
}

View File

@@ -7,18 +7,18 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.9.4" />
<PackageReference Include="MSTest.TestAdapter" Version="2.2.3" />
<PackageReference Include="MSTest.TestFramework" Version="2.2.3" />
<PackageReference Include="coverlet.collector" Version="3.0.3">
<PackageReference Include="FluentAssertions" Version="6.1.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.11.0" />
<PackageReference Include="Moq" Version="4.16.1" />
<PackageReference Include="MSTest.TestAdapter" Version="2.2.6" />
<PackageReference Include="MSTest.TestFramework" Version="2.2.6" />
<PackageReference Include="coverlet.collector" Version="3.1.0">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\audible api\AudibleApi\_Tests\AudibleApi.Tests\AudibleApi.Tests.csproj" />
<ProjectReference Include="..\..\..\Dinah.Core\_Tests\TestCommon\TestCommon.csproj" />
<ProjectReference Include="..\..\InternalUtilities\InternalUtilities.csproj" />
</ItemGroup>

View File

@@ -7,17 +7,18 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.9.4" />
<PackageReference Include="MSTest.TestAdapter" Version="2.2.3" />
<PackageReference Include="MSTest.TestFramework" Version="2.2.3" />
<PackageReference Include="coverlet.collector" Version="3.0.3">
<PackageReference Include="FluentAssertions" Version="6.1.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.11.0" />
<PackageReference Include="Moq" Version="4.16.1" />
<PackageReference Include="MSTest.TestAdapter" Version="2.2.6" />
<PackageReference Include="MSTest.TestFramework" Version="2.2.6" />
<PackageReference Include="coverlet.collector" Version="3.1.0">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\Dinah.Core\_Tests\TestCommon\TestCommon.csproj" />
<ProjectReference Include="..\..\LibationSearchEngine\LibationSearchEngine.csproj" />
</ItemGroup>

View File

@@ -16,7 +16,6 @@ using Moq;
using Moq.Protected;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using TestCommon;
namespace SearchEngineTests
{

View File

@@ -0,0 +1,55 @@
Thank you for looking at the code for Libation. Here are some guidelines and expectations for contributors.
IDEALS
* Libation has always been and will always be free and open source.
* I work on this as time permits; this is part of the reason Libation is free. I work as much or as little as I feel like. You should feel empowered to do likewise.
* I welcome collaboration, ideas, and helpful criticism. At the end of the day though, the final call is mine. If you disagree, you're welcome to fork the repo. (git terminology is weird. I didn't want to tell you to go fork yourself, but here we are.)
* I believe deeply in respecting user privacy.
- As little personally identifiable info is retained as possible. Log files even contain masked user names.
- Passwords are never stored; only tokens are.
- Passwords are never displayed or logged. Metadata is allowed. Eg: logging debug info like password length can be useful to identify nulls or blank fields.
- Nothing is transmitted back to me unless explicitly initiated by the user. So far this isn't done at all, but I could imagine a future feature where the user could choose to transmit log files.
- The rules are looser for Verbose mode logging and the user is appropriately warned.
* Elephant in the room: piracy. It's impossible to remove DRM without addressing this. I 100% do not endorse or condone piracy. I believe in personal choice and personal responsibility. The user should have the choice of how to access, use, store, manipulate, and backup the content they have purchased. It is their responsibility how they handle this freedom.
STRUCTURE
* Folders in the solution are numbered. Eg: "4 Domain (db)"
* All projects should only refer to other projects in the same folder or to projects in folders with smaller numbers.
* 1 Core Libraries
This is code which has roughly equivilent priority and knowledge as the BCL. In practice, if code is this universal then it doesn't live here long and is instead moved into Dinah.Core.
* 2 Utilities (domain ignorant)
Stand-alone libraries with no knowledge of anything having to do with Libation or other programs. In theory any of these should be able to one day be converted to a nuget pkg
* 3 Domain Internal Utilities (db ignorant)
Can have knowledge of Libation concepts. Cannot access the database.
* 4 Domain (db)
All database access
* 5 Domain Utilities (db aware)
This is often where database, domain-ignorant util.s, and non-database capabilities come together to provide the domain-conscious access points
* 6 Application
UI and application launcher
CODING GUIDELINES
* Follow existing coding style when possible
* No changing things just to be 'cleaner'. You can clean up as you go but 'style wars' serve no one and do not enhance the product. If you go through and change all the namespaces for consistency, I'm going to reject the PR
* I have no tabs vs spaces preference. Do whichever one you want
IDIOSYNCRASIES
* Libation has been a labor of love for years. This passion project has also been a 'forever project' and thus a place to test out new technologies, styles, and ideas. Consistency is not guaranteed. Legacy code is a certainty.
* WinFormsDesigner is not used directly and you will likely have no reason to bother with it. Even though all win forms _functionality_ has been re-implemented in .netcore, last I checked there were still a small number of edge cases not yet implemented in the .netcore based _designer_. WinFormsDesigner is .NET Framework which supports all old designer features.
BEING NOTIFIED OF CHANGES
If you're interested in being notified of changes, I recommend subscribing to the github rss feeds:
https://github.com/rmcrackan/Dinah.Core/commits/master.atom
https://github.com/rmcrackan/AudibleApi/commits/master.atom
https://github.com/rmcrackan/Libation/commits/master.atom