All audible-related file naming terminates at FileUtility
File extensions: Dinah.Core => Libation FileUtility
This commit is contained in:
parent
6a81b9b02d
commit
648b84ee55
@ -6,7 +6,11 @@
|
|||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="AAXClean" Version="0.1.9" />
|
<PackageReference Include="AAXClean" Version="0.1.9" />
|
||||||
<PackageReference Include="Dinah.Core" Version="1.1.1.2" />
|
<PackageReference Include="Dinah.Core" Version="2.0.0.1" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\FileManager\FileManager.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
@ -3,10 +3,9 @@ using System.Collections.Generic;
|
|||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using AAXClean;
|
using AAXClean;
|
||||||
using Dinah.Core;
|
|
||||||
using Dinah.Core.IO;
|
|
||||||
using Dinah.Core.Net.Http;
|
using Dinah.Core.Net.Http;
|
||||||
using Dinah.Core.StepRunner;
|
using Dinah.Core.StepRunner;
|
||||||
|
using FileManager;
|
||||||
|
|
||||||
namespace AaxDecrypter
|
namespace AaxDecrypter
|
||||||
{
|
{
|
||||||
@ -66,8 +65,7 @@ namespace AaxDecrypter
|
|||||||
{
|
{
|
||||||
var zeroProgress = Step2_Start();
|
var zeroProgress = Step2_Start();
|
||||||
|
|
||||||
if (File.Exists(OutputFileName))
|
FileUtility.SafeDelete(OutputFileName);
|
||||||
FileExt.SafeDelete(OutputFileName);
|
|
||||||
|
|
||||||
var outputFile = File.Open(OutputFileName, FileMode.OpenOrCreate, FileAccess.ReadWrite);
|
var outputFile = File.Open(OutputFileName, FileMode.OpenOrCreate, FileAccess.ReadWrite);
|
||||||
|
|
||||||
@ -185,12 +183,8 @@ That naming may not be desirable for everyone, but it's an easy change to instea
|
|||||||
{
|
{
|
||||||
var chapterCount = 0;
|
var chapterCount = 0;
|
||||||
aaxFile.ConvertToMultiMp4a(splitChapters, newSplitCallback =>
|
aaxFile.ConvertToMultiMp4a(splitChapters, newSplitCallback =>
|
||||||
{
|
createOutputFileStream(++chapterCount, splitChapters, newSplitCallback)
|
||||||
var fileName = GetMultipartFileName(++chapterCount, splitChapters.Count, newSplitCallback.Chapter.Title);
|
);
|
||||||
if (File.Exists(fileName))
|
|
||||||
FileExt.SafeDelete(fileName);
|
|
||||||
newSplitCallback.OutputFile = File.Open(fileName, FileMode.OpenOrCreate);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ConvertToMultiMp3(ChapterInfo splitChapters)
|
private void ConvertToMultiMp3(ChapterInfo splitChapters)
|
||||||
@ -198,37 +192,22 @@ That naming may not be desirable for everyone, but it's an easy change to instea
|
|||||||
var chapterCount = 0;
|
var chapterCount = 0;
|
||||||
aaxFile.ConvertToMultiMp3(splitChapters, newSplitCallback =>
|
aaxFile.ConvertToMultiMp3(splitChapters, newSplitCallback =>
|
||||||
{
|
{
|
||||||
var fileName = GetMultipartFileName(++chapterCount, splitChapters.Count, newSplitCallback.Chapter.Title);
|
createOutputFileStream(++chapterCount, splitChapters, newSplitCallback);
|
||||||
if (File.Exists(fileName))
|
|
||||||
FileExt.SafeDelete(fileName);
|
|
||||||
newSplitCallback.OutputFile = File.Open(fileName, FileMode.OpenOrCreate);
|
|
||||||
newSplitCallback.LameConfig.ID3.Track = chapterCount.ToString();
|
newSplitCallback.LameConfig.ID3.Track = chapterCount.ToString();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private string GetMultipartFileName(int chapterCount, int chaptersTotal, string chapterTitle)
|
private void createOutputFileStream(int currentChapter, ChapterInfo splitChapters, NewSplitCallback newSplitCallback)
|
||||||
{
|
{
|
||||||
const int MAX_FILENAME_LENGTH = 255;
|
var fileName = FileUtility.GetMultipartFileName(OutputFileName, currentChapter, splitChapters.Count, newSplitCallback.Chapter.Title);
|
||||||
|
multiPartFilePaths.Add(fileName);
|
||||||
|
|
||||||
// 1-9 => 1-9
|
FileUtility.SafeDelete(fileName);
|
||||||
// 10-99 => 01-99
|
|
||||||
// 100-999 => 001-999
|
|
||||||
var chapterCountLeadingZeros = chapterCount.ToString().PadLeft(chaptersTotal.ToString().Length, '0');
|
|
||||||
|
|
||||||
string extension = Path.GetExtension(OutputFileName);
|
newSplitCallback.OutputFile = File.Open(fileName, FileMode.OpenOrCreate);
|
||||||
|
}
|
||||||
|
|
||||||
var filenameBase = $"{Path.GetFileNameWithoutExtension(OutputFileName)} - {chapterCountLeadingZeros} - {chapterTitle}";
|
private void AaxFile_ConversionProgressUpdate(object sender, ConversionProgressEventArgs e)
|
||||||
// Replace illegal path characters with spaces
|
|
||||||
var filenameBaseSafe = string.Join(" ", filenameBase.Split(Path.GetInvalidFileNameChars()));
|
|
||||||
var fileName = filenameBaseSafe.Truncate(MAX_FILENAME_LENGTH - extension.Length);
|
|
||||||
var path = Path.Combine(Path.GetDirectoryName(OutputFileName), fileName + extension);
|
|
||||||
|
|
||||||
multiPartFilePaths.Add(path);
|
|
||||||
|
|
||||||
return path;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void AaxFile_ConversionProgressUpdate(object sender, ConversionProgressEventArgs e)
|
|
||||||
{
|
{
|
||||||
var duration = aaxFile.Duration;
|
var duration = aaxFile.Duration;
|
||||||
double remainingSecsToProcess = (duration - e.ProcessPosition).TotalSeconds;
|
double remainingSecsToProcess = (duration - e.ProcessPosition).TotalSeconds;
|
||||||
|
|||||||
@ -1,13 +1,9 @@
|
|||||||
using Dinah.Core;
|
using System;
|
||||||
using Dinah.Core.IO;
|
using System.IO;
|
||||||
|
using Dinah.Core;
|
||||||
using Dinah.Core.Net.Http;
|
using Dinah.Core.Net.Http;
|
||||||
using Dinah.Core.StepRunner;
|
using Dinah.Core.StepRunner;
|
||||||
using System;
|
using FileManager;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.IO;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace AaxDecrypter
|
namespace AaxDecrypter
|
||||||
{
|
{
|
||||||
@ -24,11 +20,13 @@ namespace AaxDecrypter
|
|||||||
public event EventHandler<string> FileCreated;
|
public event EventHandler<string> FileCreated;
|
||||||
|
|
||||||
protected bool IsCanceled { get; set; }
|
protected bool IsCanceled { get; set; }
|
||||||
protected string OutputFileName { get; }
|
protected string OutputFileName { get; private set; }
|
||||||
protected string CacheDir { get; }
|
protected string CacheDir { get; }
|
||||||
protected DownloadLicense DownloadLicense { get; }
|
protected DownloadLicense DownloadLicense { get; }
|
||||||
protected NetworkFileStream InputFileStream => (nfsPersister ??= OpenNetworkFileStream()).NetworkFileStream;
|
protected NetworkFileStream InputFileStream => (nfsPersister ??= OpenNetworkFileStream()).NetworkFileStream;
|
||||||
|
|
||||||
|
// Don't give the property a 'set'. This should have to be an obvious choice; not accidental
|
||||||
|
protected void SetOutputFileName(string newOutputFileName) => OutputFileName = newOutputFileName;
|
||||||
|
|
||||||
protected abstract StepSequence Steps { get; }
|
protected abstract StepSequence Steps { get; }
|
||||||
private NetworkFileStreamPersister nfsPersister;
|
private NetworkFileStreamPersister nfsPersister;
|
||||||
@ -42,14 +40,14 @@ namespace AaxDecrypter
|
|||||||
|
|
||||||
var outDir = Path.GetDirectoryName(OutputFileName);
|
var outDir = Path.GetDirectoryName(OutputFileName);
|
||||||
if (!Directory.Exists(outDir))
|
if (!Directory.Exists(outDir))
|
||||||
throw new ArgumentNullException(nameof(outDir), "Directory does not exist");
|
throw new DirectoryNotFoundException($"Directory does not exist: {nameof(outDir)}");
|
||||||
if (File.Exists(OutputFileName))
|
|
||||||
File.Delete(OutputFileName);
|
|
||||||
|
|
||||||
if (!Directory.Exists(cacheDirectory))
|
if (!Directory.Exists(cacheDirectory))
|
||||||
throw new ArgumentNullException(nameof(cacheDirectory), "Directory does not exist");
|
throw new DirectoryNotFoundException($"Directory does not exist: {nameof(cacheDirectory)}");
|
||||||
CacheDir = cacheDirectory;
|
CacheDir = cacheDirectory;
|
||||||
|
|
||||||
|
// delete file after validation is complete
|
||||||
|
FileUtility.SafeDelete(OutputFileName);
|
||||||
DownloadLicense = ArgumentValidator.EnsureNotNull(dlLic, nameof(dlLic));
|
DownloadLicense = ArgumentValidator.EnsureNotNull(dlLic, nameof(dlLic));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -105,6 +103,7 @@ namespace AaxDecrypter
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
var path = PathLib.ReplaceExtension(OutputFileName, ".cue");
|
var path = PathLib.ReplaceExtension(OutputFileName, ".cue");
|
||||||
|
path = FileUtility.GetValidFilename(path);
|
||||||
File.WriteAllText(path, Cue.CreateContents(Path.GetFileName(OutputFileName), DownloadLicense.ChapterInfo));
|
File.WriteAllText(path, Cue.CreateContents(Path.GetFileName(OutputFileName), DownloadLicense.ChapterInfo));
|
||||||
OnFileCreated(path);
|
OnFileCreated(path);
|
||||||
}
|
}
|
||||||
@ -117,8 +116,8 @@ namespace AaxDecrypter
|
|||||||
|
|
||||||
protected bool Step4_Cleanup()
|
protected bool Step4_Cleanup()
|
||||||
{
|
{
|
||||||
FileExt.SafeDelete(jsonDownloadState);
|
FileUtility.SafeDelete(jsonDownloadState);
|
||||||
FileExt.SafeDelete(tempFile);
|
FileUtility.SafeDelete(tempFile);
|
||||||
return !IsCanceled;
|
return !IsCanceled;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -137,8 +136,8 @@ namespace AaxDecrypter
|
|||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
{
|
{
|
||||||
FileExt.SafeDelete(jsonDownloadState);
|
FileUtility.SafeDelete(jsonDownloadState);
|
||||||
FileExt.SafeDelete(tempFile);
|
FileUtility.SafeDelete(tempFile);
|
||||||
return NewNetworkFilePersister();
|
return NewNetworkFilePersister();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,10 +1,8 @@
|
|||||||
using Dinah.Core.IO;
|
using System;
|
||||||
|
using System.Threading;
|
||||||
using Dinah.Core.Net.Http;
|
using Dinah.Core.Net.Http;
|
||||||
using Dinah.Core.StepRunner;
|
using Dinah.Core.StepRunner;
|
||||||
using System;
|
using FileManager;
|
||||||
using System.IO;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Threading;
|
|
||||||
|
|
||||||
namespace AaxDecrypter
|
namespace AaxDecrypter
|
||||||
{
|
{
|
||||||
@ -68,10 +66,8 @@ namespace AaxDecrypter
|
|||||||
|
|
||||||
CloseInputFileStream();
|
CloseInputFileStream();
|
||||||
|
|
||||||
if (File.Exists(OutputFileName))
|
var realOutputFileName = FileUtility.Move(InputFileStream.SaveFilePath, OutputFileName);
|
||||||
FileExt.SafeDelete(OutputFileName);
|
SetOutputFileName(realOutputFileName);
|
||||||
|
|
||||||
FileExt.SafeMove(InputFileStream.SaveFilePath, OutputFileName);
|
|
||||||
OnFileCreated(OutputFileName);
|
OnFileCreated(OutputFileName);
|
||||||
|
|
||||||
return !IsCanceled;
|
return !IsCanceled;
|
||||||
|
|||||||
@ -3,7 +3,7 @@
|
|||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net5.0</TargetFramework>
|
<TargetFramework>net5.0</TargetFramework>
|
||||||
<Version>6.2.6.4</Version>
|
<Version>6.2.6.7</Version>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|||||||
@ -12,13 +12,13 @@
|
|||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Dinah.EntityFrameworkCore" Version="1.0.5.2" />
|
<PackageReference Include="Dinah.EntityFrameworkCore" Version="1.0.6.1" />
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="5.0.10">
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="5.0.11">
|
||||||
<PrivateAssets>all</PrivateAssets>
|
<PrivateAssets>all</PrivateAssets>
|
||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="5.0.10" />
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="5.0.11" />
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="5.0.10">
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="5.0.11">
|
||||||
<PrivateAssets>all</PrivateAssets>
|
<PrivateAssets>all</PrivateAssets>
|
||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
|
|||||||
@ -1,13 +1,12 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using AAXClean;
|
using AAXClean;
|
||||||
using DataLayer;
|
using DataLayer;
|
||||||
using Dinah.Core;
|
using Dinah.Core;
|
||||||
using Dinah.Core.ErrorHandling;
|
using Dinah.Core.ErrorHandling;
|
||||||
using Dinah.Core.IO;
|
|
||||||
using Dinah.Core.Net.Http;
|
using Dinah.Core.Net.Http;
|
||||||
|
using FileManager;
|
||||||
using LibationFileManager;
|
using LibationFileManager;
|
||||||
|
|
||||||
namespace FileLiberator
|
namespace FileLiberator
|
||||||
@ -52,10 +51,9 @@ namespace FileLiberator
|
|||||||
m4bBook.InputStream.Close();
|
m4bBook.InputStream.Close();
|
||||||
mp3File.Close();
|
mp3File.Close();
|
||||||
|
|
||||||
var mp3Path = Mp3FileName(m4bPath);
|
var proposedMp3Path = Mp3FileName(m4bPath);
|
||||||
|
var realMp3Path = FileUtility.Move(mp3File.Name, proposedMp3Path);
|
||||||
FileExt.SafeMove(mp3File.Name, mp3Path);
|
OnFileCreated(libraryBook, realMp3Path);
|
||||||
OnFileCreated(libraryBook.Book.AudibleProductId, mp3Path);
|
|
||||||
|
|
||||||
var statusHandler = new StatusHandler();
|
var statusHandler = new StatusHandler();
|
||||||
|
|
||||||
|
|||||||
@ -129,7 +129,7 @@ namespace FileLiberator
|
|||||||
abDownloader.RetrievedAuthors += (_, authors) => OnAuthorsDiscovered(authors);
|
abDownloader.RetrievedAuthors += (_, authors) => OnAuthorsDiscovered(authors);
|
||||||
abDownloader.RetrievedNarrators += (_, narrators) => OnNarratorsDiscovered(narrators);
|
abDownloader.RetrievedNarrators += (_, narrators) => OnNarratorsDiscovered(narrators);
|
||||||
abDownloader.RetrievedCoverArt += AaxcDownloader_RetrievedCoverArt;
|
abDownloader.RetrievedCoverArt += AaxcDownloader_RetrievedCoverArt;
|
||||||
abDownloader.FileCreated += (_, path) => OnFileCreated(libraryBook.Book.AudibleProductId, path);
|
abDownloader.FileCreated += (_, path) => OnFileCreated(libraryBook, path);
|
||||||
|
|
||||||
// REAL WORK DONE HERE
|
// REAL WORK DONE HERE
|
||||||
var success = await Task.Run(abDownloader.Run);
|
var success = await Task.Run(abDownloader.Run);
|
||||||
@ -189,31 +189,26 @@ namespace FileLiberator
|
|||||||
var destinationDir = FileUtility.GetValidFilename(AudibleFileStorage.BooksDirectory, titleDir, null, asin);
|
var destinationDir = FileUtility.GetValidFilename(AudibleFileStorage.BooksDirectory, titleDir, null, asin);
|
||||||
Directory.CreateDirectory(destinationDir);
|
Directory.CreateDirectory(destinationDir);
|
||||||
|
|
||||||
var music = entries.FirstOrDefault(f => f.FileType == FileType.Audio);
|
FilePathCache.CacheEntry getFirstAudio() => entries.FirstOrDefault(f => f.FileType == FileType.Audio);
|
||||||
|
|
||||||
if (music == default)
|
if (getFirstAudio() == default)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
var musicFileExt = Path.GetExtension(music.Path).Trim('.');
|
for (var i = 0; i < entries.Count; i++)
|
||||||
|
|
||||||
var audioFileName = FileUtility.GetValidFilename(destinationDir, book.Title, musicFileExt, book.AudibleProductId);
|
|
||||||
|
|
||||||
foreach (var entry in entries)
|
|
||||||
{
|
{
|
||||||
var fileInfo = new FileInfo(entry.Path);
|
var entry = entries[i];
|
||||||
|
|
||||||
var dest
|
var realDest = FileUtility.Move(entry.Path, Path.Combine(destinationDir, Path.GetFileName(entry.Path)));
|
||||||
= entry.FileType == FileType.Audio
|
FilePathCache.Insert(book.AudibleProductId, realDest);
|
||||||
? Path.Join(destinationDir, fileInfo.Name)
|
|
||||||
: FileUtility.GetValidFilename(destinationDir, book.Title, fileInfo.Extension, book.AudibleProductId, musicFileExt);
|
|
||||||
|
|
||||||
if (Path.GetExtension(dest).Trim('.').ToLower() == "cue")
|
// propogate corrected path. Must update cache with corrected path. Also want updated path for cue file (after this for-loop)
|
||||||
Cue.UpdateFileName(fileInfo, audioFileName);
|
entries[i] = entry with { Path = realDest };
|
||||||
|
|
||||||
File.Move(fileInfo.FullName, dest);
|
|
||||||
FilePathCache.Insert(book.AudibleProductId, dest);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
|||||||
@ -73,7 +73,7 @@ namespace FileLiberator
|
|||||||
var client = new HttpClient();
|
var client = new HttpClient();
|
||||||
|
|
||||||
var actualDownloadedFilePath = await client.DownloadFileAsync(downloadUrl, proposedDownloadFilePath, progress);
|
var actualDownloadedFilePath = await client.DownloadFileAsync(downloadUrl, proposedDownloadFilePath, progress);
|
||||||
OnFileCreated(libraryBook.Book.AudibleProductId, actualDownloadedFilePath);
|
OnFileCreated(libraryBook, actualDownloadedFilePath);
|
||||||
|
|
||||||
OnStatusUpdate(actualDownloadedFilePath);
|
OnStatusUpdate(actualDownloadedFilePath);
|
||||||
return actualDownloadedFilePath;
|
return actualDownloadedFilePath;
|
||||||
|
|||||||
@ -34,6 +34,7 @@ namespace FileLiberator
|
|||||||
StreamingCompleted?.Invoke(this, filePath);
|
StreamingCompleted?.Invoke(this, filePath);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected void OnFileCreated(DataLayer.LibraryBook libraryBook, string path) => OnFileCreated(libraryBook.Book.AudibleProductId, path);
|
||||||
protected void OnFileCreated(string id, string path)
|
protected void OnFileCreated(string id, string path)
|
||||||
{
|
{
|
||||||
Serilog.Log.Logger.Information("File created {@DebugInfo}", new { Name = nameof(FileCreated), id, path });
|
Serilog.Log.Logger.Information("File created {@DebugInfo}", new { Name = nameof(FileCreated), id, path });
|
||||||
|
|||||||
@ -7,10 +7,13 @@
|
|||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.9.4" />
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.11.0" />
|
||||||
<PackageReference Include="MSTest.TestAdapter" Version="2.2.3" />
|
<PackageReference Include="MSTest.TestAdapter" Version="2.2.7" />
|
||||||
<PackageReference Include="MSTest.TestFramework" Version="2.2.3" />
|
<PackageReference Include="MSTest.TestFramework" Version="2.2.7" />
|
||||||
<PackageReference Include="coverlet.collector" Version="3.0.2" />
|
<PackageReference Include="coverlet.collector" Version="3.1.0">
|
||||||
|
<PrivateAssets>all</PrivateAssets>
|
||||||
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
|
</PackageReference>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|||||||
@ -5,7 +5,7 @@
|
|||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Dinah.Core" Version="1.1.1.2" />
|
<PackageReference Include="Dinah.Core" Version="2.0.0.1" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
@ -2,11 +2,15 @@
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using Dinah.Core;
|
||||||
|
|
||||||
namespace FileManager
|
namespace FileManager
|
||||||
{
|
{
|
||||||
public static class FileUtility
|
public static class FileUtility
|
||||||
{
|
{
|
||||||
|
private const int MAX_FILENAME_LENGTH = 255;
|
||||||
|
private const int MAX_DIRECTORY_LENGTH = 247;
|
||||||
|
|
||||||
//public static string GetValidFilename(string template, Dictionary<string, object> parameters) { }
|
//public static string GetValidFilename(string template, Dictionary<string, object> parameters) { }
|
||||||
public static string GetValidFilename(string dirFullPath, string filename, string extension, params string[] metadataSuffixes)
|
public static string GetValidFilename(string dirFullPath, string filename, string extension, params string[] metadataSuffixes)
|
||||||
{
|
{
|
||||||
@ -15,11 +19,9 @@ namespace FileManager
|
|||||||
|
|
||||||
filename ??= "";
|
filename ??= "";
|
||||||
|
|
||||||
// file max length = 255. dir max len = 247
|
|
||||||
|
|
||||||
// sanitize. omit invalid characters. exception: colon => underscore
|
// sanitize. omit invalid characters. exception: colon => underscore
|
||||||
filename = filename.Replace(':', '_');
|
filename = filename.Replace(':', '_');
|
||||||
filename = Dinah.Core.PathLib.ToPathSafeString(filename);
|
filename = PathLib.ToPathSafeString(filename);
|
||||||
|
|
||||||
// manage length
|
// manage length
|
||||||
if (filename.Length > 50)
|
if (filename.Length > 50)
|
||||||
@ -41,5 +43,89 @@ namespace FileManager
|
|||||||
|
|
||||||
return fullfilename;
|
return fullfilename;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static string GetMultipartFileName(string originalPath, int partsPosition, int partsTotal, string suffix)
|
||||||
|
{
|
||||||
|
// 1-9 => 1-9
|
||||||
|
// 10-99 => 01-99
|
||||||
|
// 100-999 => 001-999
|
||||||
|
var chapterCountLeadingZeros = partsPosition.ToString().PadLeft(partsTotal.ToString().Length, '0');
|
||||||
|
|
||||||
|
string extension = Path.GetExtension(originalPath);
|
||||||
|
|
||||||
|
var filenameBase = $"{Path.GetFileNameWithoutExtension(originalPath)} - {chapterCountLeadingZeros} - {suffix}";
|
||||||
|
|
||||||
|
// Replace illegal path characters with spaces
|
||||||
|
var filenameBaseSafe = PathLib.ToPathSafeString(filenameBase, " ");
|
||||||
|
var fileName = filenameBaseSafe.Truncate(MAX_FILENAME_LENGTH - extension.Length);
|
||||||
|
var path = Path.Combine(Path.GetDirectoryName(originalPath), fileName + extension);
|
||||||
|
return path;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string Move(string source, string destination)
|
||||||
|
{
|
||||||
|
// TODO: destination must be valid path. Use: " (#)" when needed
|
||||||
|
SafeMove(source, destination);
|
||||||
|
return destination;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string GetValidFilename(string path)
|
||||||
|
{
|
||||||
|
// TODO: destination must be valid path. Use: " (#)" when needed
|
||||||
|
return path;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Delete file. No error when file does not exist.
|
||||||
|
/// Exceptions are logged, not thrown.</summary>
|
||||||
|
/// <param name="source">File to delete</param>
|
||||||
|
public static void SafeDelete(string source)
|
||||||
|
{
|
||||||
|
if (!File.Exists(source))
|
||||||
|
return;
|
||||||
|
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
File.Delete(source);
|
||||||
|
Serilog.Log.Logger.Information($"File successfully deleted: {source}");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
System.Threading.Thread.Sleep(100);
|
||||||
|
Serilog.Log.Logger.Error(e, $"Failed to delete: {source}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Moves a specified file to a new location, providing the option to specify a newfile name.
|
||||||
|
/// Exceptions are logged, not thrown.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="source">The name of the file to move. Can include a relative or absolute path.</param>
|
||||||
|
/// <param name="target">The new path and name for the file.</param>
|
||||||
|
public static void SafeMove(string source, string target)
|
||||||
|
{
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (File.Exists(source))
|
||||||
|
{
|
||||||
|
SafeDelete(target);
|
||||||
|
File.Move(source, target);
|
||||||
|
Serilog.Log.Logger.Information($"File successfully moved from '{source}' to '{target}'");
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
System.Threading.Thread.Sleep(100);
|
||||||
|
Serilog.Log.Logger.Error(e, $"Failed to move '{source}' to '{target}'");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -5,7 +5,7 @@
|
|||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="AudibleApi" Version="2.2.0.1" />
|
<PackageReference Include="AudibleApi" Version="2.3.0.1" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|||||||
@ -29,7 +29,7 @@
|
|||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Dinah.Core.WindowsDesktop" Version="1.1.1.1" />
|
<PackageReference Include="Dinah.Core.WindowsDesktop" Version="2.0.0.1" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user