Compare commits

...

4 Commits

Author SHA1 Message Date
Robert McRackan
0a986238bc Defensive FirstOrDefault 2021-10-21 15:39:53 -04:00
Robert McRackan
d636ceed8e File naming stuff is (finally) centralized under AudioFileStorageExt 2021-10-21 14:38:59 -04:00
Robert McRackan
e4fc104afe Naming logic for all new files can now originate from domian logic 2021-10-20 10:56:07 -04:00
Robert McRackan
87e3075fb3 Rename InternalUtilities to AudibleUtilities 2021-10-19 10:22:42 -04:00
49 changed files with 306 additions and 251 deletions

View File

@@ -12,10 +12,23 @@ namespace AaxDecrypter
{
protected override StepSequence Steps { get; }
private static TimeSpan minChapterLength { get; } = TimeSpan.FromSeconds(3);
private Func<string, int, int, NewSplitCallback, string> multipartFileNameCallback { get; }
private static string DefaultMultipartFilename(string outputFileName, int partsPosition, int partsTotal, NewSplitCallback newSplitCallback)
{
var template = Path.ChangeExtension(outputFileName, null) + " - <chapter> - <title>" + Path.GetExtension(outputFileName);
var fileTemplate = new FileTemplate(template) { IllegalCharacterReplacements = " " };
fileTemplate.AddParameterReplacement("chapter", FileUtility.GetSequenceFormatted(partsPosition, partsTotal));
fileTemplate.AddParameterReplacement("title", newSplitCallback?.Chapter?.Title ?? "");
return fileTemplate.GetFilePath();
}
private static TimeSpan minChapterLength { get; } = TimeSpan.FromSeconds(3);
private List<string> multiPartFilePaths { get; } = new List<string>();
public AaxcDownloadMultiConverter(string outFileName, string cacheDirectory, DownloadLicense dlLic, OutputFormat outputFormat)
public AaxcDownloadMultiConverter(string outFileName, string cacheDirectory, DownloadLicense dlLic, OutputFormat outputFormat,
Func<string, int, int, NewSplitCallback, string> multipartFileNameCallback = null)
: base(outFileName, cacheDirectory, dlLic, outputFormat)
{
Steps = new StepSequence
@@ -26,6 +39,7 @@ namespace AaxDecrypter
["Step 2: Download Decrypted Audiobook"] = Step_DownloadAudiobookAsMultipleFilesPerChapter,
["Step 3: Cleanup"] = Step_Cleanup,
};
this.multipartFileNameCallback = multipartFileNameCallback ?? DefaultMultipartFilename;
}
/*
@@ -119,7 +133,9 @@ That naming may not be desirable for everyone, but it's an easy change to instea
private void createOutputFileStream(int currentChapter, ChapterInfo splitChapters, NewSplitCallback newSplitCallback)
{
var fileName = FileUtility.GetMultipartFileName(OutputFileName, currentChapter, splitChapters.Count, newSplitCallback.Chapter.Title);
var fileName = multipartFileNameCallback(OutputFileName, currentChapter, splitChapters.Count, newSplitCallback);
fileName = FileUtility.GetValidFilename(fileName);
multiPartFilePaths.Add(fileName);
FileUtility.SaferDelete(fileName);

View File

@@ -30,8 +30,7 @@ namespace AaxDecrypter
protected abstract StepSequence Steps { get; }
private NetworkFileStreamPersister nfsPersister;
private string cacheDir { get; }
private string jsonDownloadState => Path.Combine(cacheDir, Path.ChangeExtension(OutputFileName, ".json"));
private string jsonDownloadState { get; }
private string tempFile => Path.ChangeExtension(jsonDownloadState, ".tmp");
protected AudiobookDownloadBase(string outFileName, string cacheDirectory, DownloadLicense dlLic)
@@ -44,11 +43,12 @@ namespace AaxDecrypter
if (!Directory.Exists(cacheDirectory))
throw new DirectoryNotFoundException($"Directory does not exist: {nameof(cacheDirectory)}");
cacheDir = cacheDirectory;
jsonDownloadState = Path.Combine(cacheDirectory, Path.ChangeExtension(OutputFileName, ".json"));
DownloadLicense = ArgumentValidator.EnsureNotNull(dlLic, nameof(dlLic));
// delete file after validation is complete
FileUtility.SaferDelete(OutputFileName);
DownloadLicense = ArgumentValidator.EnsureNotNull(dlLic, nameof(dlLic));
}
public abstract void Cancel();

View File

@@ -3,7 +3,7 @@
<PropertyGroup>
<TargetFramework>net5.0</TargetFramework>
<Version>6.2.7.1</Version>
<Version>6.2.8.1</Version>
</PropertyGroup>
<ItemGroup>
@@ -15,7 +15,7 @@
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\InternalUtilities\InternalUtilities.csproj" />
<ProjectReference Include="..\AudibleUtilities\AudibleUtilities.csproj" />
</ItemGroup>
</Project>

View File

@@ -3,10 +3,10 @@ using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using AudibleUtilities;
using Dinah.Core;
using Dinah.Core.IO;
using Dinah.Core.Logging;
using InternalUtilities;
using LibationFileManager;
using Newtonsoft.Json.Linq;
using Serilog;
@@ -211,10 +211,10 @@ namespace AppScaffolding
config.InProgress,
DownloadsInProgressDirectory = AudibleFileStorage.DownloadsInProgressDirectory,
AudibleFileStorage.DownloadsInProgressDirectory,
DownloadsInProgressFiles = Directory.EnumerateFiles(AudibleFileStorage.DownloadsInProgressDirectory).Count(),
DecryptInProgressDirectory = AudibleFileStorage.DecryptInProgressDirectory,
AudibleFileStorage.DecryptInProgressDirectory,
DecryptInProgressFiles = Directory.EnumerateFiles(AudibleFileStorage.DecryptInProgressDirectory).Count(),
});
}

View File

@@ -3,10 +3,10 @@ using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using AudibleApi;
using AudibleUtilities;
using DataLayer;
using Dinah.Core;
using DtoImporterService;
using InternalUtilities;
using LibationFileManager;
using Serilog;
using static DtoImporterService.PerfLogger;

View File

@@ -6,7 +6,7 @@ using AudibleApi.Authorization;
using Dinah.Core;
using Newtonsoft.Json;
namespace InternalUtilities
namespace AudibleUtilities
{
public class Account : IUpdatable
{

View File

@@ -6,7 +6,7 @@ using AudibleApi.Authorization;
using Dinah.Core;
using Newtonsoft.Json;
namespace InternalUtilities
namespace AudibleUtilities
{
// 'AccountsSettings' is intentionally NOT IEnumerable<> so that properties can be added/extended
// from newtonsoft (https://www.newtonsoft.com/json/help/html/SerializationGuide.htm):

View File

@@ -3,7 +3,7 @@ using AudibleApi.Authorization;
using Dinah.Core.IO;
using Newtonsoft.Json;
namespace InternalUtilities
namespace AudibleUtilities
{
public class AccountsSettingsPersister : JsonFilePersister<AccountsSettings>
{

View File

@@ -8,7 +8,7 @@ using Dinah.Core;
using Polly;
using Polly.Retry;
namespace InternalUtilities
namespace AudibleUtilities
{
/// <summary>USE THIS from within Libation. It wraps the call with correct JSONPath</summary>
public class ApiExtended

View File

@@ -3,7 +3,7 @@ using System.IO;
using LibationFileManager;
using Newtonsoft.Json;
namespace InternalUtilities
namespace AudibleUtilities
{
public static class AudibleApiStorage
{

View File

@@ -3,7 +3,7 @@ using System.Collections.Generic;
using System.Linq;
using AudibleApi.Common;
namespace InternalUtilities
namespace AudibleUtilities
{
public interface IValidator
{

View File

@@ -1,38 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
namespace DataLayer.Utilities
{
public static class LocalDatabaseInfo
{
public static List<string> GetLocalDBInstances()
{
// Start the child process.
using var p = new System.Diagnostics.Process
{
StartInfo = new System.Diagnostics.ProcessStartInfo
{
UseShellExecute = false,
RedirectStandardOutput = true,
FileName = "cmd.exe",
Arguments = "/C sqllocaldb info",
CreateNoWindow = true,
WindowStyle = System.Diagnostics.ProcessWindowStyle.Hidden
}
};
p.Start();
var output = p.StandardOutput.ReadToEnd();
p.WaitForExit();
// if LocalDb is not installed then it will return that 'sqllocaldb' is not recognized as an internal or external command operable program or batch file
return string.IsNullOrWhiteSpace(output) || output.Contains("not recognized")
? new List<string>()
: output
.Split(new string[] { Environment.NewLine }, StringSplitOptions.None)
.Select(i => i.Trim())
.Where(i => !string.IsNullOrEmpty(i))
.ToList();
}
}
}

View File

@@ -2,8 +2,8 @@
using System.Collections.Generic;
using System.Linq;
using AudibleApi.Common;
using AudibleUtilities;
using DataLayer;
using InternalUtilities;
namespace DtoImporterService
{
@@ -68,7 +68,7 @@ namespace DtoImporterService
foreach (var item in importItems)
{
var book = DbContext.Books.Local.SingleOrDefault(p => p.AudibleProductId == item.DtoItem.ProductId);
var book = DbContext.Books.Local.FirstOrDefault(p => p.AudibleProductId == item.DtoItem.ProductId);
if (book is null)
{
book = createNewBook(item);

View File

@@ -2,8 +2,8 @@
using System.Collections.Generic;
using System.Linq;
using AudibleApi.Common;
using AudibleUtilities;
using DataLayer;
using InternalUtilities;
namespace DtoImporterService
{

View File

@@ -2,8 +2,8 @@
using System.Collections.Generic;
using System.Linq;
using AudibleApi.Common;
using AudibleUtilities;
using DataLayer;
using InternalUtilities;
namespace DtoImporterService
{

View File

@@ -5,8 +5,8 @@
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\AudibleUtilities\AudibleUtilities.csproj" />
<ProjectReference Include="..\DataLayer\DataLayer.csproj" />
<ProjectReference Include="..\InternalUtilities\InternalUtilities.csproj" />
</ItemGroup>
</Project>

View File

@@ -1,9 +1,9 @@
using System;
using System.Collections.Generic;
using System.Linq;
using AudibleUtilities;
using DataLayer;
using Dinah.Core;
using InternalUtilities;
namespace DtoImporterService
{

View File

@@ -1,8 +1,8 @@
using System;
using System.Collections.Generic;
using System.Linq;
using AudibleUtilities;
using DataLayer;
using InternalUtilities;
namespace DtoImporterService
{

View File

@@ -2,8 +2,8 @@
using System.Collections.Generic;
using System.Linq;
using AudibleApi.Common;
using AudibleUtilities;
using DataLayer;
using InternalUtilities;
namespace DtoImporterService
{

View File

@@ -0,0 +1,69 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using DataLayer;
using Dinah.Core;
using FileManager;
using LibationFileManager;
namespace FileLiberator
{
public static class AudioFileStorageExt
{
public static string MultipartFilename(this AudioFileStorage _, string outputFileName, int partsPosition, int partsTotal, AAXClean.NewSplitCallback newSplitCallback)
=> MultipartFilename(outputFileName, partsPosition, partsTotal, newSplitCallback);
public static string MultipartFilename(string outputFileName, int partsPosition, int partsTotal, AAXClean.NewSplitCallback newSplitCallback)
{
var template = Path.ChangeExtension(outputFileName, null) + " - <chapter> - <title>" + Path.GetExtension(outputFileName);
var fileTemplate = new FileTemplate(template) { IllegalCharacterReplacements = " " };
fileTemplate.AddParameterReplacement("chapter", FileUtility.GetSequenceFormatted(partsPosition, partsTotal));
fileTemplate.AddParameterReplacement("title", newSplitCallback?.Chapter?.Title ?? "");
return fileTemplate.GetFilePath();
}
public static string GetInProgressFilename(this AudioFileStorage _, LibraryBook libraryBook, string extension)
=> GetInProgressFilename(libraryBook, extension);
public static string GetInProgressFilename(LibraryBook libraryBook, string extension)
=> GetValidFilename(AudibleFileStorage.DecryptInProgressDirectory, libraryBook.Book.Title, extension, libraryBook);
public static string GetBooksDirectoryFilename(this AudioFileStorage _, LibraryBook libraryBook, string extension)
=> GetBooksDirectoryFilename(libraryBook, extension);
public static string GetBooksDirectoryFilename(LibraryBook libraryBook, string extension)
=> GetValidFilename(AudibleFileStorage.BooksDirectory, libraryBook.Book.Title, extension, libraryBook);
public static string CreateDestinationDirectory(this AudioFileStorage _, LibraryBook libraryBook)
=> CreateDestinationDirectory(libraryBook);
public static string CreateDestinationDirectory(LibraryBook libraryBook)
{
var title = libraryBook.Book.Title;
// 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 destinationDir = GetValidFilename(AudibleFileStorage.BooksDirectory, titleDir, null, libraryBook);
Directory.CreateDirectory(destinationDir);
return destinationDir;
}
public static string GetValidFilename(string dirFullPath, string filename, string extension, LibraryBook libraryBook)
{
ArgumentValidator.EnsureNotNullOrWhiteSpace(dirFullPath, nameof(dirFullPath));
ArgumentValidator.EnsureNotNullOrWhiteSpace(filename, nameof(filename));
var template = $"<title> [<id>]";
var fullfilename = Path.Combine(dirFullPath, template + FileUtility.GetStandardizedExtension(extension));
var fileTemplate = new FileTemplate(fullfilename) { IllegalCharacterReplacements = "_" };
fileTemplate.AddParameterReplacement("title", filename);
fileTemplate.AddParameterReplacement("id", libraryBook.Book.AudibleProductId);
return fileTemplate.GetFilePath();
}
}
}

View File

@@ -59,13 +59,12 @@ namespace FileLiberator
FilePathCache.Removed -= FilePathCache_Removed;
}
// decrypt failed
if (!success)
return new StatusHandler { "Decrypt failed" };
// moves new files from temp dir to final dest
var movedAudioFile = moveFilesToBooksDir(libraryBook.Book, entries);
var movedAudioFile = moveFilesToBooksDir(libraryBook, entries);
// decrypt failed
if (!movedAudioFile)
@@ -115,14 +114,14 @@ namespace FileLiberator
foreach (var chap in contentLic.ContentMetadata?.ChapterInfo?.Chapters)
audiobookDlLic.ChapterInfo.AddChapter(chap.Title, TimeSpan.FromMilliseconds(chap.LengthMs));
}
var outFileName = FileUtility.GetValidFilename(AudibleFileStorage.DecryptInProgressDirectory, libraryBook.Book.Title, outputFormat.ToString().ToLower(), libraryBook.Book.AudibleProductId);
var outFileName = AudibleFileStorage.Audio.GetInProgressFilename(libraryBook, outputFormat.ToString().ToLower());
var cacheDir = AudibleFileStorage.DownloadsInProgressDirectory;
abDownloader
= contentLic.DrmType != AudibleApi.Common.DrmType.Adrm ? new UnencryptedAudiobookDownloader(outFileName, cacheDir, audiobookDlLic)
: Configuration.Instance.SplitFilesByChapter ? new AaxcDownloadMultiConverter(outFileName, cacheDir, audiobookDlLic, outputFormat)
: Configuration.Instance.SplitFilesByChapter ? new AaxcDownloadMultiConverter(outFileName, cacheDir, audiobookDlLic, outputFormat, AudibleFileStorage.Audio.MultipartFilename)
: new AaxcDownloadSingleConverter(outFileName, cacheDir, audiobookDlLic, outputFormat);
abDownloader.DecryptProgressUpdate += (_, progress) => OnStreamingProgressChanged(progress);
abDownloader.DecryptTimeRemaining += (_, remaining) => OnStreamingTimeRemaining(remaining);
@@ -134,7 +133,6 @@ namespace FileLiberator
// REAL WORK DONE HERE
var success = await Task.Run(abDownloader.Run);
return success;
}
finally
@@ -175,43 +173,34 @@ namespace FileLiberator
/// <summary>Move new files to 'Books' directory</summary>
/// <returns>True if audiobook file(s) were successfully created and can be located on disk. Else false.</returns>
private static bool moveFilesToBooksDir(Book book, List<FilePathCache.CacheEntry> entries)
private static bool moveFilesToBooksDir(LibraryBook libraryBook, List<FilePathCache.CacheEntry> entries)
{
// create final directory. move each file into it
var title = book.Title;
var asin = book.AudibleProductId;
// 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 destinationDir = FileUtility.GetValidFilename(AudibleFileStorage.BooksDirectory, titleDir, null, asin);
Directory.CreateDirectory(destinationDir);
var destinationDir = AudibleFileStorage.Audio.CreateDestinationDirectory(libraryBook);
FilePathCache.CacheEntry getFirstAudio() => entries.FirstOrDefault(f => f.FileType == FileType.Audio);
FilePathCache.CacheEntry getFirstAudio() => entries.FirstOrDefault(f => f.FileType == FileType.Audio);
if (getFirstAudio() == default)
return false;
if (getFirstAudio() == default)
return false;
for (var i = 0; i < entries.Count; i++)
{
var entry = entries[i];
for (var i = 0; i < entries.Count; i++)
{
var entry = entries[i];
var realDest = FileUtility.SaferMoveToValidPath(entry.Path, Path.Combine(destinationDir, Path.GetFileName(entry.Path)));
FilePathCache.Insert(book.AudibleProductId, realDest);
var realDest = FileUtility.SaferMoveToValidPath(entry.Path, Path.Combine(destinationDir, Path.GetFileName(entry.Path)));
FilePathCache.Insert(libraryBook.Book.AudibleProductId, realDest);
// propogate corrected path. Must update cache with corrected path. Also want updated path for cue file (after this for-loop)
entries[i] = entry with { Path = realDest };
}
// propogate corrected path. Must update cache with corrected path. Also want updated path for cue file (after this for-loop)
entries[i] = entry with { Path = realDest };
}
var cue = entries.FirstOrDefault(f => f.FileType == FileType.Cue);
if (cue != default)
Cue.UpdateFileName(cue.Path, getFirstAudio().Path);
var cue = entries.FirstOrDefault(f => f.FileType == FileType.Cue);
if (cue != default)
Cue.UpdateFileName(cue.Path, getFirstAudio().Path);
AudibleFileStorage.Audio.Refresh();
AudibleFileStorage.Audio.Refresh();
return true;
}
}
return true;
}
}
}

View File

@@ -47,7 +47,7 @@ namespace FileLiberator
if (existingPath != null)
return Path.Combine(existingPath, Path.GetFileName(file));
return FileUtility.GetValidFilename(AudibleFileStorage.BooksDirectory, libraryBook.Book.Title, Path.GetExtension(file), libraryBook.Book.AudibleProductId);
return AudibleFileStorage.Audio.GetBooksDirectoryFilename(libraryBook, Path.GetExtension(file));
}
private static string getdownloadUrl(LibraryBook libraryBook)

View File

@@ -7,7 +7,7 @@
<ItemGroup>
<ProjectReference Include="..\AaxDecrypter\AaxDecrypter.csproj" />
<ProjectReference Include="..\DataLayer\DataLayer.csproj" />
<ProjectReference Include="..\InternalUtilities\InternalUtilities.csproj" />
<ProjectReference Include="..\AudibleUtilities\AudibleUtilities.csproj" />
</ItemGroup>
</Project>

View File

@@ -19,7 +19,7 @@ namespace FileLiberator
public static async Task<AudibleApi.Api> GetApiAsync(this LibraryBook libraryBook)
{
var apiExtended = await InternalUtilities.ApiExtended.CreateAsync(libraryBook.Account, libraryBook.Book.Locale);
var apiExtended = await AudibleUtilities.ApiExtended.CreateAsync(libraryBook.Account, libraryBook.Book.Locale);
return apiExtended.Api;
}
}

View File

@@ -28,7 +28,7 @@ namespace FileManager
public string IllegalCharacterReplacements { get; set; }
/// <summary>Generate a valid path for this file or directory</summary>
public string GetFilename()
public string GetFilePath()
{
var filename = Template;

View File

@@ -20,41 +20,22 @@ namespace FileManager
? (extension ?? "")?.Trim()
: '.' + extension.Trim('.');
public static string GetValidFilename(string dirFullPath, string filename, string extension, string metadataSuffix)
/// <summary>
/// Return position with correct number of leading zeros.
/// <br />- 2 of 9 => "2"
/// <br />- 2 of 90 => "02"
/// <br />- 2 of 900 => "002"
/// </summary>
/// <param name="position">position in sequence. The 'x' in 'x of y'</param>
/// <param name="total">total qty in sequence. The 'y' in 'x of y'</param>
public static string GetSequenceFormatted(int position, int total)
{
ArgumentValidator.EnsureNotNullOrWhiteSpace(dirFullPath, nameof(dirFullPath));
ArgumentValidator.EnsureNotNullOrWhiteSpace(filename, nameof(filename));
ArgumentValidator.EnsureGreaterThan(position, nameof(position), 0);
ArgumentValidator.EnsureGreaterThan(total, nameof(total), 0);
if (position > total)
throw new ArgumentException($"{position} may not be greater than {total}");
var template = $"<title> [<id>]";
var fullfilename = Path.Combine(dirFullPath, template + GetStandardizedExtension(extension));
var fileTemplate = new FileTemplate(fullfilename) { IllegalCharacterReplacements = "_" };
fileTemplate.AddParameterReplacement("title", filename);
fileTemplate.AddParameterReplacement("id", metadataSuffix);
return fileTemplate.GetFilename();
}
public static string GetMultipartFileName(string originalPath, int partsPosition, int partsTotal, string suffix)
{
ArgumentValidator.EnsureNotNull(originalPath, nameof(originalPath));
ArgumentValidator.EnsureGreaterThan(partsPosition, nameof(partsPosition), 0);
ArgumentValidator.EnsureGreaterThan(partsTotal, nameof(partsTotal), 0);
if (partsPosition > partsTotal)
throw new ArgumentException($"{partsPosition} may not be greater than {partsTotal}");
// 1-9 => 1-9
// 10-99 => 01-99
// 100-999 => 001-999
var chapterCountLeadingZeros = partsPosition.ToString().PadLeft(partsTotal.ToString().Length, '0');
var template = Path.ChangeExtension(originalPath, null) + " - <chapter> - <title>" + Path.GetExtension(originalPath);
var fileTemplate = new FileTemplate(template) { IllegalCharacterReplacements = " " };
fileTemplate.AddParameterReplacement("chapter", chapterCountLeadingZeros);
fileTemplate.AddParameterReplacement("title", suffix);
return fileTemplate.GetFilename();
return position.ToString().PadLeft(total.ToString().Length, '0');
}
private const int MAX_FILENAME_LENGTH = 255;

View File

@@ -29,7 +29,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DataLayer", "DataLayer\Data
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FileLiberator", "FileLiberator\FileLiberator.csproj", "{393B5B27-D15C-4F77-9457-FA14BA8F3C73}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "InternalUtilities", "InternalUtilities\InternalUtilities.csproj", "{06882742-27A6-4347-97D9-56162CEC9C11}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AudibleUtilities", "AudibleUtilities\AudibleUtilities.csproj", "{06882742-27A6-4347-97D9-56162CEC9C11}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "3 Domain Internal Utilities (db ignorant)", "3 Domain Internal Utilities (db ignorant)", "{F0CBB7A7-D3FB-41FF-8F47-CF3F6A592249}"
EndProject
@@ -46,8 +46,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ApplicationServices", "Appl
EndProject
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}") = "Hoopla", "Hoopla\Hoopla.csproj", "{D8F56E5A-3E65-41A6-B7E7-C4515A264B1F}"
@@ -56,9 +54,13 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LibationCli", "LibationCli\
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AppScaffolding", "AppScaffolding\AppScaffolding.csproj", "{595E7C4D-506D-486D-98B7-5FDDF398D033}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FileManager", "FileManager\FileManager.csproj", "{E86014F9-E4B3-4CD4-A210-2B3DB571DD86}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FileManager", "FileManager\FileManager.csproj", "{E86014F9-E4B3-4CD4-A210-2B3DB571DD86}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FileManager.Tests", "FileManager.Tests\FileManager.Tests.csproj", "{3B58450C-FBDA-4D48-8418-A3C750596D7D}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AudibleUtilities.Tests", "_Tests\AudibleUtilities.Tests\AudibleUtilities.Tests.csproj", "{788294BE-0D8E-40D4-9CEE-67896FBB52CE}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FileLiberator.Tests", "_Tests\FileLiberator.Tests\FileLiberator.Tests.csproj", "{5B8FC827-BF58-4CB1-A59E-BDEB9C62A05E}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FileManager.Tests", "_Tests\FileManager.Tests\FileManager.Tests.csproj", "{F2E04270-4551-41C4-99FF-E7125BED708C}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
@@ -102,10 +104,6 @@ Global
{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
{8447C956-B03E-4F59-9DD4-877793B849D9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{8447C956-B03E-4F59-9DD4-877793B849D9}.Debug|Any CPU.Build.0 = Debug|Any CPU
{8447C956-B03E-4F59-9DD4-877793B849D9}.Release|Any CPU.ActiveCfg = Release|Any CPU
{8447C956-B03E-4F59-9DD4-877793B849D9}.Release|Any CPU.Build.0 = Release|Any CPU
{C5B21768-C7C9-4FCB-AC1E-187B223D5A98}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{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
@@ -126,10 +124,18 @@ Global
{E86014F9-E4B3-4CD4-A210-2B3DB571DD86}.Debug|Any CPU.Build.0 = Debug|Any CPU
{E86014F9-E4B3-4CD4-A210-2B3DB571DD86}.Release|Any CPU.ActiveCfg = Release|Any CPU
{E86014F9-E4B3-4CD4-A210-2B3DB571DD86}.Release|Any CPU.Build.0 = Release|Any CPU
{3B58450C-FBDA-4D48-8418-A3C750596D7D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{3B58450C-FBDA-4D48-8418-A3C750596D7D}.Debug|Any CPU.Build.0 = Debug|Any CPU
{3B58450C-FBDA-4D48-8418-A3C750596D7D}.Release|Any CPU.ActiveCfg = Release|Any CPU
{3B58450C-FBDA-4D48-8418-A3C750596D7D}.Release|Any CPU.Build.0 = Release|Any CPU
{788294BE-0D8E-40D4-9CEE-67896FBB52CE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{788294BE-0D8E-40D4-9CEE-67896FBB52CE}.Debug|Any CPU.Build.0 = Debug|Any CPU
{788294BE-0D8E-40D4-9CEE-67896FBB52CE}.Release|Any CPU.ActiveCfg = Release|Any CPU
{788294BE-0D8E-40D4-9CEE-67896FBB52CE}.Release|Any CPU.Build.0 = Release|Any CPU
{5B8FC827-BF58-4CB1-A59E-BDEB9C62A05E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{5B8FC827-BF58-4CB1-A59E-BDEB9C62A05E}.Debug|Any CPU.Build.0 = Debug|Any CPU
{5B8FC827-BF58-4CB1-A59E-BDEB9C62A05E}.Release|Any CPU.ActiveCfg = Release|Any CPU
{5B8FC827-BF58-4CB1-A59E-BDEB9C62A05E}.Release|Any CPU.Build.0 = Release|Any CPU
{F2E04270-4551-41C4-99FF-E7125BED708C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{F2E04270-4551-41C4-99FF-E7125BED708C}.Debug|Any CPU.Build.0 = Debug|Any CPU
{F2E04270-4551-41C4-99FF-E7125BED708C}.Release|Any CPU.ActiveCfg = Release|Any CPU
{F2E04270-4551-41C4-99FF-E7125BED708C}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -144,13 +150,14 @@ Global
{635F00E1-AAD1-45F7-BEB7-D909AD33B9F6} = {8679CAC8-9164-4007-BDD2-F004810EDA14}
{401865F5-1942-4713-B230-04544C0A97B0} = {41CDCC73-9B81-49DD-9570-C54406E852AF}
{B95650EA-25F0-449E-BA5D-99126BC5D730} = {41CDCC73-9B81-49DD-9570-C54406E852AF}
{8447C956-B03E-4F59-9DD4-877793B849D9} = {67E66E82-5532-4440-AFB3-9FB1DF9DEF53}
{C5B21768-C7C9-4FCB-AC1E-187B223D5A98} = {67E66E82-5532-4440-AFB3-9FB1DF9DEF53}
{D8F56E5A-3E65-41A6-B7E7-C4515A264B1F} = {7FBBB086-0807-4998-85BF-6D1A49C8AD05}
{428163C3-D558-4914-B570-A92069521877} = {8679CAC8-9164-4007-BDD2-F004810EDA14}
{595E7C4D-506D-486D-98B7-5FDDF398D033} = {8679CAC8-9164-4007-BDD2-F004810EDA14}
{E86014F9-E4B3-4CD4-A210-2B3DB571DD86} = {43E3ACB3-E0BC-4370-8DBB-E3720C8C8FD1}
{3B58450C-FBDA-4D48-8418-A3C750596D7D} = {67E66E82-5532-4440-AFB3-9FB1DF9DEF53}
{788294BE-0D8E-40D4-9CEE-67896FBB52CE} = {67E66E82-5532-4440-AFB3-9FB1DF9DEF53}
{5B8FC827-BF58-4CB1-A59E-BDEB9C62A05E} = {67E66E82-5532-4440-AFB3-9FB1DF9DEF53}
{F2E04270-4551-41C4-99FF-E7125BED708C} = {67E66E82-5532-4440-AFB3-9FB1DF9DEF53}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {615E00ED-BAEF-4E8E-A92A-9B82D87942A9}

View File

@@ -3,8 +3,8 @@ using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using ApplicationServices;
using AudibleUtilities;
using CommandLine;
using InternalUtilities;
namespace LibationCli
{

View File

@@ -3,8 +3,8 @@ using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using ApplicationServices;
using AudibleUtilities;
using CommandLine;
using InternalUtilities;
namespace LibationCli
{

View File

@@ -66,8 +66,26 @@ namespace LibationFileManager
#endregion
}
internal class AaxcFileStorage : AudibleFileStorage
{
internal AaxcFileStorage() : base(FileType.AAXC) { }
protected override string GetFilePathCustom(string productId)
{
var regex = GetBookSearchRegex(productId);
return Directory
.EnumerateFiles(DownloadsInProgressDirectory, "*.*", SearchOption.AllDirectories)
.FirstOrDefault(s => regex.IsMatch(s));
}
public bool Exists(string productId) => GetFilePath(productId) != null;
}
public class AudioFileStorage : AudibleFileStorage
{
internal AudioFileStorage() : base(FileType.Audio)
=> BookDirectoryFiles ??= new BackgroundFileSystem(BooksDirectory, "*.*", SearchOption.AllDirectories);
private static BackgroundFileSystem BookDirectoryFiles { get; set; }
private static object bookDirectoryFilesLocker { get; } = new();
protected override string GetFilePathCustom(string productId)
@@ -81,26 +99,8 @@ namespace LibationFileManager
return BookDirectoryFiles.FindFile(regex);
}
internal AudioFileStorage() : base(FileType.Audio)
=> BookDirectoryFiles ??= new BackgroundFileSystem(BooksDirectory, "*.*", SearchOption.AllDirectories);
public void Refresh() => BookDirectoryFiles.RefreshFiles();
public string GetPath(string productId) => GetFilePath(productId);
}
internal class AaxcFileStorage : AudibleFileStorage
{
protected override string GetFilePathCustom(string productId)
{
var regex = GetBookSearchRegex(productId);
return Directory
.EnumerateFiles(DownloadsInProgressDirectory, "*.*", SearchOption.AllDirectories)
.FirstOrDefault(s => regex.IsMatch(s));
}
internal AaxcFileStorage() : base(FileType.AAXC) { }
public bool Exists(string productId) => GetFilePath(productId) != null;
}
}

View File

@@ -1,9 +1,9 @@
using AudibleApi;
using InternalUtilities;
using System;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows.Forms;
using AudibleApi;
using AudibleUtilities;
namespace LibationWinForms.Dialogs
{

View File

@@ -1,9 +1,8 @@
using ApplicationServices;
using InternalUtilities;
using LibationWinForms.Login;
using System;
using System.Linq;
using System;
using System.Windows.Forms;
using ApplicationServices;
using AudibleUtilities;
using LibationWinForms.Login;
namespace LibationWinForms.Dialogs
{

View File

@@ -1,7 +1,7 @@
using System;
using System.Windows.Forms;
using AudibleUtilities;
using Dinah.Core;
using InternalUtilities;
namespace LibationWinForms.Dialogs.Login
{

View File

@@ -1,7 +1,7 @@
using System;
using System.Windows.Forms;
using AudibleUtilities;
using Dinah.Core;
using InternalUtilities;
namespace LibationWinForms.Dialogs.Login
{

View File

@@ -1,7 +1,7 @@
using System;
using System.Windows.Forms;
using AudibleUtilities;
using Dinah.Core;
using InternalUtilities;
namespace LibationWinForms.Dialogs.Login
{

View File

@@ -1,6 +1,6 @@
using System;
using AudibleApi;
using InternalUtilities;
using AudibleUtilities;
using LibationWinForms.Dialogs.Login;
namespace LibationWinForms.Login

View File

@@ -1,6 +1,6 @@
using System;
using AudibleApi;
using InternalUtilities;
using AudibleUtilities;
using LibationWinForms.Dialogs.Login;
namespace LibationWinForms.Login

View File

@@ -1,16 +1,16 @@
using ApplicationServices;
using DataLayer;
using Dinah.Core.DataBinding;
using InternalUtilities;
using LibationFileManager;
using LibationWinForms.Login;
using System;
using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Linq;
using System.Windows.Forms;
using ApplicationServices;
using AudibleUtilities;
using DataLayer;
using Dinah.Core.DataBinding;
using LibationFileManager;
using LibationWinForms.Login;
namespace LibationWinForms.Dialogs
{

View File

@@ -1,8 +1,8 @@
using InternalUtilities;
using System;
using System;
using System.Linq;
using System.Collections.Generic;
using System.Windows.Forms;
using AudibleUtilities;
namespace LibationWinForms.Dialogs
{

View File

@@ -3,10 +3,10 @@ using System.Collections.Generic;
using System.Linq;
using System.Windows.Forms;
using ApplicationServices;
using AudibleUtilities;
using Dinah.Core;
using Dinah.Core.Drawing;
using Dinah.Core.Threading;
using InternalUtilities;
using LibationFileManager;
using LibationWinForms.Dialogs;

View File

@@ -4,9 +4,9 @@ using System.IO;
using System.Linq;
using System.Windows.Forms;
using AudibleApi.Authorization;
using AudibleUtilities;
using DataLayer;
using Dinah.Core;
using InternalUtilities;
using LibationFileManager;
using LibationWinForms.Dialogs;
using Microsoft.EntityFrameworkCore;

View File

@@ -8,10 +8,10 @@ using System.Threading;
using System.Threading.Tasks;
using AudibleApi;
using AudibleApi.Authorization;
using AudibleUtilities;
using Dinah.Core;
using FluentAssertions;
using FluentAssertions.Common;
using InternalUtilities;
using Microsoft.VisualStudio.TestPlatform.Common.Filtering;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Moq;

View File

@@ -19,7 +19,7 @@
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\InternalUtilities\InternalUtilities.csproj" />
<ProjectReference Include="..\..\AudibleUtilities\AudibleUtilities.csproj" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,45 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Dinah.Core;
using FileLiberator;
using FluentAssertions;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace AudioFileStorageExtTests
{
[TestClass]
public class GetValidFilename
{
private DataLayer.LibraryBook GetLibraryBook(string asin)
{
var book = new DataLayer.Book(new DataLayer.AudibleProductId(asin), "title", "desc", 1, DataLayer.ContentType.Product, new List<DataLayer.Contributor> { new DataLayer.Contributor("author") }, new List<DataLayer.Contributor> { new DataLayer.Contributor("narrator") }, new DataLayer.Category(new DataLayer.AudibleCategoryId("seriesId") , "name"), "us");
var libraryBook = new DataLayer.LibraryBook(book, DateTime.Now, "my us");
return libraryBook;
}
[TestMethod]
[DataRow(null, "name", "ext", "suffix")]
[DataRow(@"C:\", null, "ext", "suffix")]
[ExpectedException(typeof(ArgumentNullException))]
public void arg_null_exception(string dirFullPath, string filename, string extension, string metadataSuffix)
=> AudioFileStorageExt.GetValidFilename(dirFullPath, filename, extension, GetLibraryBook(metadataSuffix));
[TestMethod]
[DataRow("", "name", "ext", "suffix")]
[DataRow(" ", "name", "ext", "suffix")]
[DataRow(@"C:\", "", "ext", "suffix")]
[DataRow(@"C:\", " ", "ext", "suffix")]
[ExpectedException(typeof(ArgumentException))]
public void arg_exception(string dirFullPath, string filename, string extension, string metadataSuffix)
=> AudioFileStorageExt.GetValidFilename(dirFullPath, filename, extension, GetLibraryBook(metadataSuffix));
[TestMethod]
public void null_extension() => Tests(@"C:\foo\bar", "my file", null, "meta", @"C:\foo\bar\my file [meta]");
[TestMethod]
[DataRow(@"C:\foo\bar", "my file", "txt", "my id", @"C:\foo\bar\my file [my id].txt")]
public void Tests(string dirFullPath, string filename, string extension, string metadataSuffix, string expected)
=> AudioFileStorageExt.GetValidFilename(dirFullPath, filename, extension, GetLibraryBook(metadataSuffix)).Should().Be(expected);
}
}

View File

@@ -0,0 +1,24 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net5.0</TargetFramework>
<IsPackable>false</IsPackable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="FluentAssertions" Version="6.1.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.11.0" />
<PackageReference Include="MSTest.TestAdapter" Version="2.2.7" />
<PackageReference Include="MSTest.TestFramework" Version="2.2.7" />
<PackageReference Include="coverlet.collector" Version="3.1.0">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\FileLiberator\FileLiberator.csproj" />
</ItemGroup>
</Project>

View File

@@ -18,7 +18,7 @@
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\FileManager\FileManager.csproj" />
<ProjectReference Include="..\..\FileManager\FileManager.csproj" />
</ItemGroup>
</Project>

View File

@@ -10,7 +10,7 @@ using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace FileTemplateTests
{
[TestClass]
public class GetFilename
public class GetFilePath
{
[TestMethod]
public void equiv_GetValidFilename()
@@ -58,7 +58,7 @@ namespace FileTemplateTests
var fileTemplate = new FileTemplate(fullfilename) { IllegalCharacterReplacements = "_" };
fileTemplate.AddParameterReplacement("title", filename);
fileTemplate.AddParameterReplacement("id", metadataSuffix);
return fileTemplate.GetFilename();
return fileTemplate.GetFilePath();
}
[TestMethod]
@@ -102,7 +102,7 @@ namespace FileTemplateTests
fileTemplate.AddParameterReplacement("chapter", chapterCountLeadingZeros);
fileTemplate.AddParameterReplacement("title", suffix);
return fileTemplate.GetFilename();
return fileTemplate.GetFilePath();
}
}
}

View File

@@ -55,76 +55,39 @@ namespace FileUtilityTests
}
[TestClass]
public class GetValidFilename
public class GetSequenceFormatted
{
[TestMethod]
[DataRow(null, "name", "ext", "suffix")]
[DataRow(@"C:\", null, "ext", "suffix")]
[ExpectedException(typeof(ArgumentNullException))]
public void arg_null_exception(string dirFullPath, string filename, string extension, string metadataSuffix)
=> FileUtility.GetValidFilename(dirFullPath, filename, extension, metadataSuffix);
[TestMethod]
[DataRow("", "name", "ext", "suffix")]
[DataRow(" ", "name", "ext", "suffix")]
[DataRow(@"C:\", "", "ext", "suffix")]
[DataRow(@"C:\", " ", "ext", "suffix")]
[ExpectedException(typeof(ArgumentException))]
public void arg_exception(string dirFullPath, string filename, string extension, string metadataSuffix)
=> FileUtility.GetValidFilename(dirFullPath, filename, extension, metadataSuffix);
[TestMethod]
public void null_extension() => Tests(@"C:\foo\bar", "my file", null, "meta", @"C:\foo\bar\my file [meta]");
[TestMethod]
public void null_metadataSuffix() => Tests(@"C:\foo\bar", "my file", "txt", null, @"C:\foo\bar\my file [].txt");
[TestMethod]
[DataRow(@"C:\foo\bar", "my file", "txt", "my id", @"C:\foo\bar\my file [my id].txt")]
[DataRow(@"C:\foo\bar", "my file", "txt", "", @"C:\foo\bar\my file [].txt")]
public void Tests(string dirFullPath, string filename, string extension, string metadataSuffix, string expected)
=> FileUtility.GetValidFilename(dirFullPath, filename, extension, metadataSuffix).Should().Be(expected);
}
[TestClass]
public class GetMultipartFileName
{
[TestMethod]
public void null_path() => Assert.ThrowsException<ArgumentNullException>(() => FileUtility.GetMultipartFileName(null, 1, 1, ""));
[TestMethod]
public void null_suffix() => Tests(@"C:\foo\bar\my file.txt", 2, 100, null, @"C:\foo\bar\my file - 002 - .txt");
[TestMethod]
public void negative_partsPosition() => Assert.ThrowsException<ArgumentException>(()
=> FileUtility.GetMultipartFileName("foo", -1, 2, "")
=> FileUtility.GetSequenceFormatted(-1, 2)
);
[TestMethod]
public void zero_partsPosition() => Assert.ThrowsException<ArgumentException>(()
=> FileUtility.GetMultipartFileName("foo", 0, 2, "")
=> FileUtility.GetSequenceFormatted(0, 2)
);
[TestMethod]
public void negative_partsTotal() => Assert.ThrowsException<ArgumentException>(()
=> FileUtility.GetMultipartFileName("foo", 2, -1, "")
=> FileUtility.GetSequenceFormatted(2, -1)
);
[TestMethod]
public void zero_partsTotal() => Assert.ThrowsException<ArgumentException>(()
=> FileUtility.GetMultipartFileName("foo", 2, 0, "")
=> FileUtility.GetSequenceFormatted(2, 0)
);
[TestMethod]
public void partsPosition_greater_than_partsTotal() => Assert.ThrowsException<ArgumentException>(()
=> FileUtility.GetMultipartFileName("foo", 2, 1, "")
=> FileUtility.GetSequenceFormatted(2, 1)
);
[TestMethod]
// only part
[DataRow(@"C:\foo\bar\my file.txt", 1, 1, "title", @"C:\foo\bar\my file - 1 - title.txt")]
[DataRow(1, 1, "1")]
// 2 digits
[DataRow(2, 90, "02")]
// 3 digits
[DataRow(@"C:\foo\bar\my file.txt", 2, 100, "title", @"C:\foo\bar\my file - 002 - title.txt")]
// no suffix
[DataRow(@"C:\foo\bar\my file.txt", 2, 100, "", @"C:\foo\bar\my file - 002 - .txt")]
public void Tests(string originalPath, int partsPosition, int partsTotal, string suffix, string expected)
=> FileUtility.GetMultipartFileName(originalPath, partsPosition, partsTotal, suffix).Should().Be(expected);
[DataRow(2, 900, "002")]
public void Tests(int partsPosition, int partsTotal, string expected)
=> FileUtility.GetSequenceFormatted(partsPosition, partsTotal).Should().Be(expected);
}
}