(hopefully) complete minimum viable product with stateful is-liberated status

This commit is contained in:
Robert McRackan 2021-08-20 21:22:52 -04:00
parent aa56bb74a1
commit c9727f84ab
15 changed files with 432 additions and 54 deletions

View File

@ -202,7 +202,7 @@ namespace ApplicationServices
} }
} }
public static int UpdateBook(LibraryBook libraryBook, LiberatedStatus liberatedStatus, string finalAudioPath) public static int UpdateBook(LibraryBook libraryBook, LiberatedStatus liberatedStatus)
{ {
try try
{ {
@ -210,12 +210,11 @@ namespace ApplicationServices
var udi = libraryBook.Book.UserDefinedItem; var udi = libraryBook.Book.UserDefinedItem;
if (udi.BookStatus == liberatedStatus && udi.BookLocation == finalAudioPath) if (udi.BookStatus == liberatedStatus)
return 0; return 0;
// Attach() NoTracking entities before SaveChanges() // Attach() NoTracking entities before SaveChanges()
udi.BookStatus = liberatedStatus; udi.BookStatus = liberatedStatus;
udi.BookLocation = finalAudioPath;
context.Attach(udi).State = Microsoft.EntityFrameworkCore.EntityState.Modified; context.Attach(udi).State = Microsoft.EntityFrameworkCore.EntityState.Modified;
var qtyChanges = context.SaveChanges(); var qtyChanges = context.SaveChanges();
if (qtyChanges > 0) if (qtyChanges > 0)

View File

@ -92,9 +92,6 @@ namespace ApplicationServices
[Name("Book Liberation Status")] [Name("Book Liberation Status")]
public string BookStatus { get; set; } public string BookStatus { get; set; }
[Name("Book File Location")]
public string BookLocation { get; set; }
[Name("PDF Liberation Status")] [Name("PDF Liberation Status")]
public string PdfStatus { get; set; } public string PdfStatus { get; set; }
} }
@ -127,7 +124,6 @@ namespace ApplicationServices
MyRatingStory = a.Book.UserDefinedItem.Rating.StoryRating, MyRatingStory = a.Book.UserDefinedItem.Rating.StoryRating,
MyLibationTags = a.Book.UserDefinedItem.Tags, MyLibationTags = a.Book.UserDefinedItem.Tags,
BookStatus = a.Book.UserDefinedItem.BookStatus.ToString(), BookStatus = a.Book.UserDefinedItem.BookStatus.ToString(),
BookLocation = a.Book.UserDefinedItem.BookLocation,
PdfStatus = a.Book.UserDefinedItem.PdfStatus.ToString() PdfStatus = a.Book.UserDefinedItem.PdfStatus.ToString()
}).ToList(); }).ToList();
} }
@ -201,7 +197,6 @@ namespace ApplicationServices
nameof (ExportDto.MyRatingStory), nameof (ExportDto.MyRatingStory),
nameof (ExportDto.MyLibationTags), nameof (ExportDto.MyLibationTags),
nameof (ExportDto.BookStatus), nameof (ExportDto.BookStatus),
nameof (ExportDto.BookLocation),
nameof (ExportDto.PdfStatus) nameof (ExportDto.PdfStatus)
}; };
var col = 0; var col = 0;
@ -265,7 +260,6 @@ namespace ApplicationServices
row.CreateCell(col++).SetCellValue(dto.MyLibationTags); row.CreateCell(col++).SetCellValue(dto.MyLibationTags);
row.CreateCell(col++).SetCellValue(dto.BookStatus); row.CreateCell(col++).SetCellValue(dto.BookStatus);
row.CreateCell(col++).SetCellValue(dto.BookLocation);
row.CreateCell(col++).SetCellValue(dto.PdfStatus); row.CreateCell(col++).SetCellValue(dto.PdfStatus);
rowIndex++; rowIndex++;

View File

@ -1,21 +0,0 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using DataLayer;
using FileManager;
namespace ApplicationServices
{
public static class TransitionalFileLocator
{
public static string Audio_GetPath(Book book)
{
var loc = book?.UserDefinedItem?.BookLocation ?? "";
if (File.Exists(loc))
return loc;
return AudibleFileStorage.Audio.GetPath(book.AudibleProductId);
}
}
}

View File

@ -94,9 +94,8 @@ namespace DataLayer
=> Rating.Update(overallRating, performanceRating, storyRating); => Rating.Update(overallRating, performanceRating, storyRating);
#endregion #endregion
#region LiberatedStatuses and book file location #region LiberatedStatuses
public LiberatedStatus BookStatus { get; set; } public LiberatedStatus BookStatus { get; set; }
public string BookLocation { get; set; }
public LiberatedStatus? PdfStatus { get; set; } public LiberatedStatus? PdfStatus { get; set; }
#endregion #endregion

View File

@ -0,0 +1,387 @@
// <auto-generated />
using System;
using DataLayer;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
namespace DataLayer.Migrations
{
[DbContext(typeof(LibationContext))]
[Migration("20210821012137_RemoveUdiBookLocation")]
partial class RemoveUdiBookLocation
{
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "5.0.5");
modelBuilder.Entity("DataLayer.Book", b =>
{
b.Property<int>("BookId")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<string>("AudibleProductId")
.HasColumnType("TEXT");
b.Property<int>("CategoryId")
.HasColumnType("INTEGER");
b.Property<DateTime?>("DatePublished")
.HasColumnType("TEXT");
b.Property<string>("Description")
.HasColumnType("TEXT");
b.Property<bool>("IsAbridged")
.HasColumnType("INTEGER");
b.Property<int>("LengthInMinutes")
.HasColumnType("INTEGER");
b.Property<string>("Locale")
.HasColumnType("TEXT");
b.Property<string>("PictureId")
.HasColumnType("TEXT");
b.Property<string>("Title")
.HasColumnType("TEXT");
b.HasKey("BookId");
b.HasIndex("AudibleProductId");
b.HasIndex("CategoryId");
b.ToTable("Books");
});
modelBuilder.Entity("DataLayer.BookContributor", b =>
{
b.Property<int>("BookId")
.HasColumnType("INTEGER");
b.Property<int>("ContributorId")
.HasColumnType("INTEGER");
b.Property<int>("Role")
.HasColumnType("INTEGER");
b.Property<byte>("Order")
.HasColumnType("INTEGER");
b.HasKey("BookId", "ContributorId", "Role");
b.HasIndex("BookId");
b.HasIndex("ContributorId");
b.ToTable("BookContributor");
});
modelBuilder.Entity("DataLayer.Category", b =>
{
b.Property<int>("CategoryId")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<string>("AudibleCategoryId")
.HasColumnType("TEXT");
b.Property<string>("Name")
.HasColumnType("TEXT");
b.Property<int?>("ParentCategoryCategoryId")
.HasColumnType("INTEGER");
b.HasKey("CategoryId");
b.HasIndex("AudibleCategoryId");
b.HasIndex("ParentCategoryCategoryId");
b.ToTable("Categories");
b.HasData(
new
{
CategoryId = -1,
AudibleCategoryId = "",
Name = ""
});
});
modelBuilder.Entity("DataLayer.Contributor", b =>
{
b.Property<int>("ContributorId")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<string>("AudibleContributorId")
.HasColumnType("TEXT");
b.Property<string>("Name")
.HasColumnType("TEXT");
b.HasKey("ContributorId");
b.HasIndex("Name");
b.ToTable("Contributors");
b.HasData(
new
{
ContributorId = -1,
Name = ""
});
});
modelBuilder.Entity("DataLayer.LibraryBook", b =>
{
b.Property<int>("BookId")
.HasColumnType("INTEGER");
b.Property<string>("Account")
.HasColumnType("TEXT");
b.Property<DateTime>("DateAdded")
.HasColumnType("TEXT");
b.HasKey("BookId");
b.ToTable("Library");
});
modelBuilder.Entity("DataLayer.Series", b =>
{
b.Property<int>("SeriesId")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<string>("AudibleSeriesId")
.HasColumnType("TEXT");
b.Property<string>("Name")
.HasColumnType("TEXT");
b.HasKey("SeriesId");
b.HasIndex("AudibleSeriesId");
b.ToTable("Series");
});
modelBuilder.Entity("DataLayer.SeriesBook", b =>
{
b.Property<int>("SeriesId")
.HasColumnType("INTEGER");
b.Property<int>("BookId")
.HasColumnType("INTEGER");
b.Property<float?>("Index")
.HasColumnType("REAL");
b.HasKey("SeriesId", "BookId");
b.HasIndex("BookId");
b.HasIndex("SeriesId");
b.ToTable("SeriesBook");
});
modelBuilder.Entity("DataLayer.Book", b =>
{
b.HasOne("DataLayer.Category", "Category")
.WithMany()
.HasForeignKey("CategoryId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.OwnsOne("DataLayer.Rating", "Rating", b1 =>
{
b1.Property<int>("BookId")
.HasColumnType("INTEGER");
b1.Property<float>("OverallRating")
.HasColumnType("REAL");
b1.Property<float>("PerformanceRating")
.HasColumnType("REAL");
b1.Property<float>("StoryRating")
.HasColumnType("REAL");
b1.HasKey("BookId");
b1.ToTable("Books");
b1.WithOwner()
.HasForeignKey("BookId");
});
b.OwnsMany("DataLayer.Supplement", "Supplements", b1 =>
{
b1.Property<int>("SupplementId")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b1.Property<int>("BookId")
.HasColumnType("INTEGER");
b1.Property<string>("Url")
.HasColumnType("TEXT");
b1.HasKey("SupplementId");
b1.HasIndex("BookId");
b1.ToTable("Supplement");
b1.WithOwner("Book")
.HasForeignKey("BookId");
b1.Navigation("Book");
});
b.OwnsOne("DataLayer.UserDefinedItem", "UserDefinedItem", b1 =>
{
b1.Property<int>("BookId")
.HasColumnType("INTEGER");
b1.Property<int>("BookStatus")
.HasColumnType("INTEGER");
b1.Property<int?>("PdfStatus")
.HasColumnType("INTEGER");
b1.Property<string>("Tags")
.HasColumnType("TEXT");
b1.HasKey("BookId");
b1.ToTable("UserDefinedItem");
b1.WithOwner("Book")
.HasForeignKey("BookId");
b1.OwnsOne("DataLayer.Rating", "Rating", b2 =>
{
b2.Property<int>("UserDefinedItemBookId")
.HasColumnType("INTEGER");
b2.Property<float>("OverallRating")
.HasColumnType("REAL");
b2.Property<float>("PerformanceRating")
.HasColumnType("REAL");
b2.Property<float>("StoryRating")
.HasColumnType("REAL");
b2.HasKey("UserDefinedItemBookId");
b2.ToTable("UserDefinedItem");
b2.WithOwner()
.HasForeignKey("UserDefinedItemBookId");
});
b1.Navigation("Book");
b1.Navigation("Rating");
});
b.Navigation("Category");
b.Navigation("Rating");
b.Navigation("Supplements");
b.Navigation("UserDefinedItem");
});
modelBuilder.Entity("DataLayer.BookContributor", b =>
{
b.HasOne("DataLayer.Book", "Book")
.WithMany("ContributorsLink")
.HasForeignKey("BookId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("DataLayer.Contributor", "Contributor")
.WithMany("BooksLink")
.HasForeignKey("ContributorId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Book");
b.Navigation("Contributor");
});
modelBuilder.Entity("DataLayer.Category", b =>
{
b.HasOne("DataLayer.Category", "ParentCategory")
.WithMany()
.HasForeignKey("ParentCategoryCategoryId");
b.Navigation("ParentCategory");
});
modelBuilder.Entity("DataLayer.LibraryBook", b =>
{
b.HasOne("DataLayer.Book", "Book")
.WithOne()
.HasForeignKey("DataLayer.LibraryBook", "BookId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Book");
});
modelBuilder.Entity("DataLayer.SeriesBook", b =>
{
b.HasOne("DataLayer.Book", "Book")
.WithMany("SeriesLink")
.HasForeignKey("BookId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("DataLayer.Series", "Series")
.WithMany("BooksLink")
.HasForeignKey("SeriesId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Book");
b.Navigation("Series");
});
modelBuilder.Entity("DataLayer.Book", b =>
{
b.Navigation("ContributorsLink");
b.Navigation("SeriesLink");
});
modelBuilder.Entity("DataLayer.Contributor", b =>
{
b.Navigation("BooksLink");
});
modelBuilder.Entity("DataLayer.Series", b =>
{
b.Navigation("BooksLink");
});
#pragma warning restore 612, 618
}
}
}

View File

@ -0,0 +1,23 @@
using Microsoft.EntityFrameworkCore.Migrations;
namespace DataLayer.Migrations
{
public partial class RemoveUdiBookLocation : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "BookLocation",
table: "UserDefinedItem");
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<string>(
name: "BookLocation",
table: "UserDefinedItem",
type: "TEXT",
nullable: true);
}
}
}

View File

@ -253,9 +253,6 @@ namespace DataLayer.Migrations
b1.Property<int>("BookId") b1.Property<int>("BookId")
.HasColumnType("INTEGER"); .HasColumnType("INTEGER");
b1.Property<string>("BookLocation")
.HasColumnType("TEXT");
b1.Property<int>("BookStatus") b1.Property<int>("BookStatus")
.HasColumnType("INTEGER"); .HasColumnType("INTEGER");

View File

@ -37,7 +37,7 @@ namespace FileLiberator
public bool Validate(LibraryBook libraryBook) public bool Validate(LibraryBook libraryBook)
{ {
var path = ApplicationServices.TransitionalFileLocator.Audio_GetPath(libraryBook.Book); var path = AudibleFileStorage.Audio.GetPath(libraryBook.Book.AudibleProductId);
return path?.ToLower()?.EndsWith(".m4b") == true && !File.Exists(Mp3FileName(path)); return path?.ToLower()?.EndsWith(".m4b") == true && !File.Exists(Mp3FileName(path));
} }
@ -49,7 +49,7 @@ namespace FileLiberator
try try
{ {
var m4bPath = ApplicationServices.TransitionalFileLocator.Audio_GetPath(libraryBook.Book); var m4bPath = AudibleFileStorage.Audio.GetPath(libraryBook.Book.AudibleProductId);
m4bBook = new Mp4File(m4bPath, FileAccess.Read); m4bBook = new Mp4File(m4bPath, FileAccess.Read);
m4bBook.ConversionProgressUpdate += M4bBook_ConversionProgressUpdate; m4bBook.ConversionProgressUpdate += M4bBook_ConversionProgressUpdate;

View File

@ -53,7 +53,7 @@ namespace FileLiberator
return new StatusHandler { "Cannot find final audio file after decryption" }; return new StatusHandler { "Cannot find final audio file after decryption" };
// only need to update if success. if failure, it will remain at 0 == NotLiberated // only need to update if success. if failure, it will remain at 0 == NotLiberated
ApplicationServices.LibraryCommands.UpdateBook(libraryBook, LiberatedStatus.Liberated, outputAudioFilename); ApplicationServices.LibraryCommands.UpdateBook(libraryBook, LiberatedStatus.Liberated);
return new StatusHandler(); return new StatusHandler();
} }

View File

@ -32,7 +32,7 @@ namespace FileLiberator
private static string getProposedDownloadFilePath(LibraryBook libraryBook) private static string getProposedDownloadFilePath(LibraryBook libraryBook)
{ {
// if audio file exists, get it's dir. else return base Book dir // if audio file exists, get it's dir. else return base Book dir
var existingPath = Path.GetDirectoryName(ApplicationServices.TransitionalFileLocator.Audio_GetPath(libraryBook.Book)); var existingPath = Path.GetDirectoryName(AudibleFileStorage.Audio.GetPath(libraryBook.Book.AudibleProductId));
var file = getdownloadUrl(libraryBook); var file = getdownloadUrl(libraryBook);
if (existingPath != null) if (existingPath != null)

View File

@ -17,12 +17,14 @@ namespace FileManager
public static string DownloadsInProgress => Directory.CreateDirectory(Path.Combine(Configuration.Instance.InProgress, "DownloadsInProgress")).FullName; public static string DownloadsInProgress => Directory.CreateDirectory(Path.Combine(Configuration.Instance.InProgress, "DownloadsInProgress")).FullName;
public static string DecryptInProgress => Directory.CreateDirectory(Path.Combine(Configuration.Instance.InProgress, "DecryptInProgress")).FullName; public static string DecryptInProgress => Directory.CreateDirectory(Path.Combine(Configuration.Instance.InProgress, "DecryptInProgress")).FullName;
public static string PdfStorageDirectory => BooksDirectory; public static string PdfStorageDirectory => BooksDirectory;
private static AaxcFileStorage AAXC { get; } = new AaxcFileStorage();
public static bool AaxcExists(string productId) => AAXC.Exists(productId); public static bool AaxcExists(string productId) => AAXC.Exists(productId);
#region static #region static
public static AudioFileStorage Audio { get; } = new AudioFileStorage(); public static AudioFileStorage Audio { get; } = new AudioFileStorage();
public static AaxcFileStorage AAXC { get; } = new AaxcFileStorage();
public static string BooksDirectory public static string BooksDirectory
{ {
@ -50,26 +52,25 @@ namespace FileManager
BookDirectoryFiles ??= new BackgroundFileSystem(BooksDirectory, "*.*", SearchOption.AllDirectories); BookDirectoryFiles ??= new BackgroundFileSystem(BooksDirectory, "*.*", SearchOption.AllDirectories);
} }
public string GetPath(string productId) protected string GetFilePath(string productId)
{ {
var cachedFile = FilePathCache.GetPath(productId, FileType); var cachedFile = FilePathCache.GetPath(productId, FileType);
if (cachedFile != null) if (cachedFile != null)
return cachedFile; return cachedFile;
string storageDir = StorageDirectory;
string regexPattern = $@"{productId}.*?\.({extAggr})$"; string regexPattern = $@"{productId}.*?\.({extAggr})$";
string firstOrNull; string firstOrNull;
if (storageDir == BooksDirectory) if (StorageDirectory == BooksDirectory)
{ {
//If user changed the BooksDirectory, reinitialize. //If user changed the BooksDirectory, reinitialize.
if (storageDir != BookDirectoryFiles.RootDirectory) if (StorageDirectory != BookDirectoryFiles.RootDirectory)
{ {
lock (BookDirectoryFiles) lock (BookDirectoryFiles)
{ {
if (storageDir != BookDirectoryFiles.RootDirectory) if (StorageDirectory != BookDirectoryFiles.RootDirectory)
{ {
BookDirectoryFiles = new BackgroundFileSystem(storageDir, "*.*", SearchOption.AllDirectories); BookDirectoryFiles = new BackgroundFileSystem(StorageDirectory, "*.*", SearchOption.AllDirectories);
} }
} }
} }
@ -80,7 +81,7 @@ namespace FileManager
{ {
firstOrNull = firstOrNull =
Directory Directory
.EnumerateFiles(storageDir, "*.*", SearchOption.AllDirectories) .EnumerateFiles(StorageDirectory, "*.*", SearchOption.AllDirectories)
.FirstOrDefault(s => Regex.IsMatch(s, regexPattern, RegexOptions.IgnoreCase)); .FirstOrDefault(s => Regex.IsMatch(s, regexPattern, RegexOptions.IgnoreCase));
} }
@ -133,6 +134,8 @@ namespace FileManager
public bool IsFileTypeMatch(FileInfo fileInfo) public bool IsFileTypeMatch(FileInfo fileInfo)
=> extensions_noDots.ContainsInsensative(fileInfo.Extension.Trim('.')); => extensions_noDots.ContainsInsensative(fileInfo.Extension.Trim('.'));
public string GetPath(string productId) => GetFilePath(productId);
} }
public class AaxcFileStorage : AudibleFileStorage public class AaxcFileStorage : AudibleFileStorage
@ -152,6 +155,6 @@ namespace FileManager
/// - a directory name has the product id and an audio file is immediately inside /// - a directory name has the product id and an audio file is immediately inside
/// - any audio filename contains the product id /// - any audio filename contains the product id
/// </summary> /// </summary>
public bool Exists(string productId) => GetPath(productId) != null; public bool Exists(string productId) => GetFilePath(productId) != null;
} }
} }

View File

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

View File

@ -274,10 +274,7 @@ namespace LibationLauncher
book.UserDefinedItem.PdfStatus = LiberatedStatus.Liberated; book.UserDefinedItem.PdfStatus = LiberatedStatus.Liberated;
if (fileType == FileType.Audio) if (fileType == FileType.Audio)
{
book.UserDefinedItem.BookStatus = LiberatedStatus.Liberated; book.UserDefinedItem.BookStatus = LiberatedStatus.Liberated;
book.UserDefinedItem.BookLocation = path;
}
} }
context.SaveChanges(); context.SaveChanges();

View File

@ -246,7 +246,7 @@ $@" Title: {libraryBook.Book.Title}
if (dialogResult == CreateSkipFileResult) if (dialogResult == CreateSkipFileResult)
{ {
ApplicationServices.LibraryCommands.UpdateBook(libraryBook, LiberatedStatus.Error, null); ApplicationServices.LibraryCommands.UpdateBook(libraryBook, LiberatedStatus.Error);
var path = FileManager.AudibleFileStorage.Audio.CreateSkipFile(libraryBook.Book.Title, libraryBook.Book.AudibleProductId, logMessage); var path = FileManager.AudibleFileStorage.Audio.CreateSkipFile(libraryBook.Book.Title, libraryBook.Book.AudibleProductId, logMessage);
LogMe.Info($@" LogMe.Info($@"
Created new 'skip' file Created new 'skip' file

View File

@ -79,7 +79,7 @@ namespace LibationWinForms
// liberated: open explorer to file // liberated: open explorer to file
if (libraryBook.Book.Audio_Exists) if (libraryBook.Book.Audio_Exists)
{ {
var filePath = TransitionalFileLocator.Audio_GetPath(libraryBook.Book); var filePath = FileManager.AudibleFileStorage.Audio.GetPath(libraryBook.Book.AudibleProductId);
if (!Go.To.File(filePath)) if (!Go.To.File(filePath))
MessageBox.Show($"File not found:\r\n{filePath}"); MessageBox.Show($"File not found:\r\n{filePath}");
return; return;