diff --git a/ApplicationServices/LibraryCommands.cs b/ApplicationServices/LibraryCommands.cs
index b27d5693..52a8d3d2 100644
--- a/ApplicationServices/LibraryCommands.cs
+++ b/ApplicationServices/LibraryCommands.cs
@@ -259,13 +259,13 @@ namespace ApplicationServices
// 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)
- => TransitionalFileLocator.Audio_Exists(book) ? LiberatedState.Liberated
- : TransitionalFileLocator.AAXC_Exists(book) ? LiberatedState.PartialDownload
+ => 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
- : TransitionalFileLocator.PDF_Exists(book) ? PdfState.Downloaded
+ : book.PDF_Exists ? PdfState.Downloaded
: PdfState.NotDownloaded;
public record LibraryStats(int booksFullyBackedUp, int booksDownloadedOnly, int booksNoProgress, int pdfsDownloaded, int pdfsNotDownloaded) { }
diff --git a/ApplicationServices/TransitionalFileLocator.cs b/ApplicationServices/TransitionalFileLocator.cs
index e560e56b..aabf6189 100644
--- a/ApplicationServices/TransitionalFileLocator.cs
+++ b/ApplicationServices/TransitionalFileLocator.cs
@@ -17,30 +17,5 @@ namespace ApplicationServices
return AudibleFileStorage.Audio.GetPath(book.AudibleProductId);
}
-
- public static bool PDF_Exists(Book book)
- {
- var status = book?.UserDefinedItem?.PdfStatus;
- if (status.HasValue && status.Value == LiberatedStatus.Liberated)
- return true;
-
- return AudibleFileStorage.PDF.Exists(book.AudibleProductId);
- }
-
- public static bool Audio_Exists(Book book)
- {
- var status = book?.UserDefinedItem?.BookStatus;
- // true since Error == libhack
- if (status.HasValue && status.Value != LiberatedStatus.NotLiberated)
- return true;
-
- return AudibleFileStorage.Audio.Exists(book.AudibleProductId);
- }
-
- public static bool AAXC_Exists(Book book)
- {
- // this one will actually stay the same. centralizing helps with organization in the interim though
- return AudibleFileStorage.AAXC.Exists(book.AudibleProductId);
- }
}
}
diff --git a/DataLayer/EfClasses/Book.cs b/DataLayer/EfClasses/Book.cs
index 3a566870..c9f87c5b 100644
--- a/DataLayer/EfClasses/Book.cs
+++ b/DataLayer/EfClasses/Book.cs
@@ -51,6 +51,25 @@ namespace DataLayer
// is owned, not optional 1:1
public UserDefinedItem UserDefinedItem { get; private set; }
+ // UserDefinedItem convenience properties
+ public bool Audio_Exists
+ {
+ get
+ {
+ var status = UserDefinedItem?.BookStatus;
+ // true since Error == libhack
+ return status.HasValue && status.Value != LiberatedStatus.NotLiberated;
+ }
+ }
+ public bool PDF_Exists
+ {
+ get
+ {
+ var status = UserDefinedItem?.PdfStatus;
+ return (status.HasValue && status.Value == LiberatedStatus.Liberated);
+ }
+ }
+
// is owned, not optional 1:1
/// The product's aggregate community rating
public Rating Rating { get; private set; } = new Rating(0, 0, 0);
diff --git a/FileLiberator/DownloadDecryptBook.cs b/FileLiberator/DownloadDecryptBook.cs
index d8596ae3..1dfcf3c2 100644
--- a/FileLiberator/DownloadDecryptBook.cs
+++ b/FileLiberator/DownloadDecryptBook.cs
@@ -37,7 +37,7 @@ namespace FileLiberator
try
{
- if (ApplicationServices.TransitionalFileLocator.Audio_Exists(libraryBook.Book))
+ if (libraryBook.Book.Audio_Exists)
return new StatusHandler { "Cannot find decrypt. Final audio file already exists" };
var outputAudioFilename = await aaxToM4bConverterDecryptAsync(AudibleFileStorage.DownloadsInProgress, AudibleFileStorage.DecryptInProgress, libraryBook);
@@ -49,8 +49,7 @@ namespace FileLiberator
// moves files and returns dest dir
_ = moveFilesToBooksDir(libraryBook.Book, outputAudioFilename);
- var finalAudioExists = ApplicationServices.TransitionalFileLocator.Audio_Exists(libraryBook.Book);
- if (!finalAudioExists)
+ if (!libraryBook.Book.Audio_Exists)
return new StatusHandler { "Cannot find final audio file after decryption" };
// only need to update if success. if failure, it will remain at 0 == NotLiberated
@@ -222,8 +221,7 @@ namespace FileLiberator
throw new Exception(errorString("Locale"));
}
- public bool Validate(LibraryBook libraryBook)
- => !ApplicationServices.TransitionalFileLocator.Audio_Exists(libraryBook.Book);
+ public bool Validate(LibraryBook libraryBook) => !libraryBook.Book.Audio_Exists;
public void Cancel()
{
diff --git a/FileLiberator/DownloadPdf.cs b/FileLiberator/DownloadPdf.cs
index 8b496011..809775d8 100644
--- a/FileLiberator/DownloadPdf.cs
+++ b/FileLiberator/DownloadPdf.cs
@@ -15,7 +15,7 @@ namespace FileLiberator
{
public override bool Validate(LibraryBook libraryBook)
=> !string.IsNullOrWhiteSpace(getdownloadUrl(libraryBook))
- && !ApplicationServices.TransitionalFileLocator.PDF_Exists(libraryBook.Book);
+ && !libraryBook.Book.PDF_Exists;
public override async Task ProcessItemAsync(LibraryBook libraryBook)
{
@@ -39,7 +39,7 @@ namespace FileLiberator
return Path.Combine(existingPath, Path.GetFileName(file));
var full = FileUtility.GetValidFilename(
- AudibleFileStorage.PDF.StorageDirectory,
+ AudibleFileStorage.PdfStorageDirectory,
libraryBook.Book.Title,
Path.GetExtension(file),
libraryBook.Book.AudibleProductId);
@@ -61,7 +61,7 @@ namespace FileLiberator
}
private static StatusHandler verifyDownload(LibraryBook libraryBook)
- => !ApplicationServices.TransitionalFileLocator.PDF_Exists(libraryBook.Book)
+ => !libraryBook.Book.PDF_Exists
? new StatusHandler { "Downloaded PDF cannot be found" }
: new StatusHandler();
}
diff --git a/FileManager/AudibleFileStorage.cs b/FileManager/AudibleFileStorage.cs
index 6af95a17..1c20b78c 100644
--- a/FileManager/AudibleFileStorage.cs
+++ b/FileManager/AudibleFileStorage.cs
@@ -15,14 +15,14 @@ namespace FileManager
protected abstract string[] Extensions { get; }
public abstract string StorageDirectory { get; }
+ 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 PdfStorageDirectory => BooksDirectory;
+ public static bool AaxcExists(string productId) => AAXC.Exists(productId);
+
#region static
public static AudioFileStorage Audio { get; } = new AudioFileStorage();
- public static AudibleFileStorage AAXC { get; } = new AaxcFileStorage();
- public static AudibleFileStorage PDF { get; } = new PdfFileStorage();
-
- 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 AaxcFileStorage AAXC { get; } = new AaxcFileStorage();
public static string BooksDirectory
{
@@ -34,13 +34,13 @@ namespace FileManager
}
}
- private static BackgroundFileSystem BookDirectoryFiles { get; set; }
+ internal static BackgroundFileSystem BookDirectoryFiles { get; set; }
#endregion
#region instance
public FileType FileType => (FileType)Value;
- private IEnumerable extensions_noDots { get; }
+ protected IEnumerable extensions_noDots { get; }
private string extAggr { get; }
protected AudibleFileStorage(FileType fileType) : base((int)fileType, fileType.ToString())
@@ -50,19 +50,6 @@ namespace FileManager
BookDirectoryFiles ??= new BackgroundFileSystem(BooksDirectory, "*.*", SearchOption.AllDirectories);
}
- public void Refresh()
- {
- BookDirectoryFiles.RefreshFiles();
- }
-
- ///
- /// Example for full books:
- /// Search recursively in _books directory. Full book exists if either are true
- /// - a directory name has the product id and an audio file is immediately inside
- /// - any audio filename contains the product id
- ///
- public bool Exists(string productId) => GetPath(productId) != null;
-
public string GetPath(string productId)
{
var cachedFile = FilePathCache.GetPath(productId, FileType);
@@ -103,21 +90,6 @@ namespace FileManager
FilePathCache.Upsert(productId, FileType, firstOrNull);
return firstOrNull;
}
-
- public string GetDestDir(string title, string asin)
- {
- // to prevent the paths from getting too long, we don't need after the 1st ":" for the folder
- var underscoreIndex = title.IndexOf(':');
- var titleDir
- = underscoreIndex < 4
- ? title
- : title.Substring(0, underscoreIndex);
- var finalDir = FileUtility.GetValidFilename(StorageDirectory, titleDir, null, asin);
- return finalDir;
- }
-
- public bool IsFileTypeMatch(FileInfo fileInfo)
- => extensions_noDots.ContainsInsensative(fileInfo.Extension.Trim('.'));
#endregion
}
@@ -134,6 +106,8 @@ namespace FileManager
public AudioFileStorage() : base(FileType.Audio) { }
+ public void Refresh() => BookDirectoryFiles.RefreshFiles();
+
public string CreateSkipFile(string title, string asin, string contents = null)
{
var destinationDir = GetDestDir(title, asin);
@@ -144,6 +118,21 @@ namespace FileManager
return path;
}
+
+ public string GetDestDir(string title, string asin)
+ {
+ // to prevent the paths from getting too long, we don't need after the 1st ":" for the folder
+ var underscoreIndex = title.IndexOf(':');
+ var titleDir
+ = underscoreIndex < 4
+ ? title
+ : title.Substring(0, underscoreIndex);
+ var finalDir = FileUtility.GetValidFilename(StorageDirectory, titleDir, null, asin);
+ return finalDir;
+ }
+
+ public bool IsFileTypeMatch(FileInfo fileInfo)
+ => extensions_noDots.ContainsInsensative(fileInfo.Extension.Trim('.'));
}
public class AaxcFileStorage : AudibleFileStorage
@@ -156,17 +145,13 @@ namespace FileManager
public override string StorageDirectory => DownloadsInProgress;
public AaxcFileStorage() : base(FileType.AAXC) { }
- }
- public class PdfFileStorage : AudibleFileStorage
- {
- protected override string[] Extensions { get; } = new[] { "pdf", "zip" };
-
- // we always want to use the latest config value, therefore
- // - DO use 'get' arrow "=>"
- // - do NOT use assign "="
- public override string StorageDirectory => BooksDirectory;
-
- public PdfFileStorage() : base(FileType.PDF) { }
+ ///
+ /// Example for full books:
+ /// Search recursively in _books directory. Full book exists if either are true
+ /// - a directory name has the product id and an audio file is immediately inside
+ /// - any audio filename contains the product id
+ ///
+ public bool Exists(string productId) => GetPath(productId) != null;
}
}
diff --git a/FileManager/BackgroundFileSystem.cs b/FileManager/BackgroundFileSystem.cs
index 1ed670ac..f4a84167 100644
--- a/FileManager/BackgroundFileSystem.cs
+++ b/FileManager/BackgroundFileSystem.cs
@@ -9,7 +9,14 @@ using System.Threading.Tasks;
namespace FileManager
{
- class BackgroundFileSystem
+ ///
+ /// Tracks actual locations of files. This is especially useful for clicking button to navigate to the book's files.
+ ///
+ /// Note: this is no longer how Libation manages "Liberated" state. That is not statefully managed in the database.
+ /// This paradigm is what allows users to manually choose to not download books. Also allows them to manually toggle
+ /// this state and download again.
+ ///
+ internal class BackgroundFileSystem
{
public string RootDirectory { get; private set; }
public string SearchPattern { get; private set; }
diff --git a/FileManager/FilePathCache.cs b/FileManager/FilePathCache.cs
index bfd175a2..ea4070d5 100644
--- a/FileManager/FilePathCache.cs
+++ b/FileManager/FilePathCache.cs
@@ -9,6 +9,7 @@ namespace FileManager
{
public static class FilePathCache
{
+ private const string FILENAME = "FileLocations.json";
internal class CacheEntry
{
public string Id { get; set; }
@@ -18,7 +19,7 @@ namespace FileManager
private static Cache cache { get; } = new Cache();
- private static string jsonFile => Path.Combine(Configuration.Instance.LibationFiles, "FilePaths.json");
+ private static string jsonFile => Path.Combine(Configuration.Instance.LibationFiles, FILENAME);
static FilePathCache()
{
@@ -84,7 +85,7 @@ namespace FileManager
try { resave(); }
catch (IOException ex)
{
- Serilog.Log.Logger.Error(ex, "Error saving FilePaths.json");
+ Serilog.Log.Logger.Error(ex, $"Error saving {FILENAME}");
throw;
}
}
diff --git a/LibationLauncher/LibationLauncher.csproj b/LibationLauncher/LibationLauncher.csproj
index ee621d18..a9921d8b 100644
--- a/LibationLauncher/LibationLauncher.csproj
+++ b/LibationLauncher/LibationLauncher.csproj
@@ -13,7 +13,7 @@
win-x64
- 5.5.1.5
+ 5.5.1.11
diff --git a/LibationLauncher/Program.cs b/LibationLauncher/Program.cs
index bcfb1f4d..07897927 100644
--- a/LibationLauncher/Program.cs
+++ b/LibationLauncher/Program.cs
@@ -52,7 +52,7 @@ namespace LibationLauncher
migrate_to_v5_0_0(config);
migrate_to_v5_2_0__post_config(config);
- //migrate_to_v5_4_1(config);// comment out until after vacation
+ migrate_to_v5_5_0(config);
ensureSerilogConfig(config);
configureLogging(config);
@@ -233,19 +233,11 @@ namespace LibationLauncher
}
#endregion
- #region migrate to v5.4.1 see comment
- // this 'migration' is a bit different. it intentionally runs each time Libation is started. its job will be fulfilled when I eventually
- // implement the portion which removes FilePaths.json, at which time this method will be a proper migration
- //
- // I'm iterating through safe steps toward getting rid of the live scanner except to track audiobook files as a convenience
- // such as clicking the stop light to open its location. live scanning will be replaced with state tracking in the database.
-
- // FilePaths.json => db. long running. fire and forget
- private static void migrate_to_v5_4_1(Configuration config)
- => new System.Threading.Thread(() => migrate_to_v5_4_1_thread(config)) { IsBackground = true }.Start();
- private static void migrate_to_v5_4_1_thread(Configuration config)
+ #region migrate to v5.5.0. FilePaths.json => db. long running. fire and forget
+ private static void migrate_to_v5_5_0(Configuration config)
+ => new System.Threading.Thread(() => migrate_to_v5_5_0_thread(config)) { IsBackground = true }.Start();
+ private static void migrate_to_v5_5_0_thread(Configuration config)
{
- var debugStopwatch = System.Diagnostics.Stopwatch.StartNew();
try
{
var filePaths = Path.Combine(config.LibationFiles, "FilePaths.json");
@@ -289,13 +281,13 @@ namespace LibationLauncher
}
context.SaveChanges();
+
+ File.Delete(filePaths);
}
catch (Exception ex)
{
Log.Logger.Error(ex, "Error attempting to insert FilePaths into db");
}
- debugStopwatch.Stop();
- var debugTotal = debugStopwatch.Elapsed;
}
#endregion
diff --git a/LibationSearchEngine/SearchEngine.cs b/LibationSearchEngine/SearchEngine.cs
index 4df7956f..550ef809 100644
--- a/LibationSearchEngine/SearchEngine.cs
+++ b/LibationSearchEngine/SearchEngine.cs
@@ -139,9 +139,7 @@ namespace LibationSearchEngine
return authors.Intersect(narrators).Any();
}
- private static bool isLiberated(Book book)
- => book.UserDefinedItem.BookStatus == LiberatedStatus.Liberated
- || AudibleFileStorage.Audio.Exists(book.AudibleProductId);
+ private static bool isLiberated(Book book) => book.UserDefinedItem.BookStatus == LiberatedStatus.Liberated;
private static bool liberatedError(Book book) => book.UserDefinedItem.BookStatus == LiberatedStatus.Error;
// use these common fields in the "all" default search field
diff --git a/LibationWinForms/ProductsGrid.cs b/LibationWinForms/ProductsGrid.cs
index 1aea5520..1c156020 100644
--- a/LibationWinForms/ProductsGrid.cs
+++ b/LibationWinForms/ProductsGrid.cs
@@ -77,7 +77,7 @@ namespace LibationWinForms
var libraryBook = liveGridEntry.LibraryBook;
// liberated: open explorer to file
- if (TransitionalFileLocator.Audio_Exists(libraryBook.Book))
+ if (libraryBook.Book.Audio_Exists)
{
var filePath = TransitionalFileLocator.Audio_GetPath(libraryBook.Book);
if (!Go.To.File(filePath))
diff --git a/REFERENCE.txt b/REFERENCE.txt
index d66c5a9c..0b5edb65 100644
--- a/REFERENCE.txt
+++ b/REFERENCE.txt
@@ -67,7 +67,7 @@ alternate book id (eg BK_RAND_006061) is called 'sku' , 'sku_lite' , 'prod_id' ,
-- begin SOLUTION LAYOUT ---------------------------------------------------------------------------------------------------------------------
do NOT combine jsons for
- audible-scraped persistence: library, book details
-- libation-generated persistence: FilePaths.json
+- libation-generated persistence: FileLocations.json
- user-defined persistence: BookTags.json
-- end SOLUTION LAYOUT ---------------------------------------------------------------------------------------------------------------------