mirror of
https://github.com/rmcrackan/Libation.git
synced 2026-01-23 13:18:28 -05:00
Compare commits
16 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a43f25db23 | ||
|
|
e1f4168599 | ||
|
|
29501bddf3 | ||
|
|
e9016ace03 | ||
|
|
d498c094bf | ||
|
|
ce92e79cd8 | ||
|
|
f54a789ae8 | ||
|
|
3ef0bce909 | ||
|
|
413da72bf0 | ||
|
|
db9c810c2d | ||
|
|
ae18ae1b8d | ||
|
|
c1298e9ff6 | ||
|
|
647eb8b9d9 | ||
|
|
fd64d394c2 | ||
|
|
f026d415bd | ||
|
|
3948e25c99 |
@@ -13,7 +13,7 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="AAXClean.Codecs" Version="2.1.3.1" />
|
||||
<PackageReference Include="AAXClean.Codecs" Version="2.1.3.2" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<Version>13.1.2.1</Version>
|
||||
<Version>13.1.4.1</Version>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Octokit" Version="14.0.0" />
|
||||
|
||||
@@ -5,8 +5,12 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="CsvHelper" Version="33.1.0" PrivateAssets="all" />
|
||||
<PackageReference Include="ClosedXML" Version="0.105.0" PrivateAssets="all" />
|
||||
<PackageReference Include="CsvHelper" Version="33.1.0">
|
||||
<PrivateAssets>compile;contentFiles;build;buildMultitargeting;buildTransitive;analyzers;native</PrivateAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="ClosedXML" Version="0.105.0">
|
||||
<PrivateAssets>compile;contentFiles;build;buildMultitargeting;buildTransitive;analyzers;native</PrivateAssets>
|
||||
</PackageReference>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -25,6 +25,12 @@ namespace ApplicationServices
|
||||
return context.GetLibrary_Flat_NoTracking(includeParents);
|
||||
}
|
||||
|
||||
public static List<LibraryBook> GetUnliberated_Flat_NoTracking()
|
||||
{
|
||||
using var context = GetContext();
|
||||
return context.GetUnLiberated_Flat_NoTracking();
|
||||
}
|
||||
|
||||
public static List<LibraryBook> GetDeletedLibraryBooks()
|
||||
{
|
||||
using var context = GetContext();
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="AudibleApi" Version="10.1.2.1" />
|
||||
<PackageReference Include="Google.Protobuf" Version="3.33.3" />
|
||||
<PackageReference Include="Google.Protobuf" Version="3.33.4" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -35,10 +35,6 @@ internal class Device
|
||||
|
||||
if (FileVersion != 2)
|
||||
throw new InvalidDataException($"Unknown CDM File Version: '{FileVersion}'");
|
||||
if (Type != DeviceTypes.Android)
|
||||
throw new InvalidDataException($"Unknown CDM Type: '{Type}'");
|
||||
if (SecurityLevel != 3)
|
||||
throw new InvalidDataException($"Unknown CDM Security Level: '{SecurityLevel}'");
|
||||
|
||||
var privateKeyLength = (fileData[7] << 8) | fileData[8];
|
||||
|
||||
|
||||
@@ -9,11 +9,11 @@
|
||||
<OutputType>Library</OutputType>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="10.0.1">
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="10.0.2">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="10.0.1">
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="10.0.2">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
|
||||
@@ -9,11 +9,11 @@
|
||||
<OutputType>Library</OutputType>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="10.0.1">
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="10.0.2">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="10.0.1">
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="10.0.2">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
|
||||
@@ -13,12 +13,12 @@
|
||||
<PackageReference Include="Dinah.Core" Version="10.0.0.1" />
|
||||
<PackageReference Include="Dinah.EntityFrameworkCore" Version="10.0.0.1" />
|
||||
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="10.0.0" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="10.0.1" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="10.0.1">
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="10.0.2" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="10.0.2">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="10.0.1">
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="10.0.2">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
|
||||
@@ -1,116 +1,131 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Dinah.Core;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
#nullable enable
|
||||
namespace DataLayer
|
||||
namespace DataLayer;
|
||||
|
||||
// only library importing should use tracking. All else should be NoTracking.
|
||||
// only library importing should directly query Book. All else should use LibraryBook
|
||||
public static class LibraryBookQueries
|
||||
{
|
||||
// only library importing should use tracking. All else should be NoTracking.
|
||||
// only library importing should directly query Book. All else should use LibraryBook
|
||||
public static class LibraryBookQueries
|
||||
private static System.Linq.Expressions.Expression<System.Func<LibraryBook, bool>> IsUnLiberatedExpression { get; }
|
||||
= lb =>
|
||||
!lb.AbsentFromLastScan &&
|
||||
(lb.Book.ContentType == ContentType.Product || lb.Book.ContentType == ContentType.Episode) &&
|
||||
(lb.Book.UserDefinedItem.PdfStatus == LiberatedStatus.NotLiberated || lb.Book.UserDefinedItem.BookStatus == LiberatedStatus.NotLiberated || lb.Book.UserDefinedItem.BookStatus == LiberatedStatus.PartialDownload);
|
||||
|
||||
extension(LibationContext context)
|
||||
{
|
||||
//// tracking is a bad idea for main grid. it prevents anything else from updating entities unless getting them from the grid
|
||||
//// tracking is a bad idea for main grid. it prevents anything else from updating entities unless getting them from the grid
|
||||
//public static List<LibraryBook> GetLibrary_Flat_WithTracking(this LibationContext context)
|
||||
// => context
|
||||
// .Library
|
||||
// .GetLibrary()
|
||||
// .ToList();
|
||||
|
||||
public static List<LibraryBook> GetLibrary_Flat_NoTracking(this LibationContext context, bool includeParents = false)
|
||||
=> context
|
||||
.LibraryBooks
|
||||
.AsNoTrackingWithIdentityResolution()
|
||||
.GetLibrary()
|
||||
.AsEnumerable()
|
||||
.Where(c => !c.Book.IsEpisodeParent() || includeParents)
|
||||
.ToList();
|
||||
public List<LibraryBook> GetLibrary_Flat_NoTracking(bool includeParents = false)
|
||||
=> context
|
||||
.LibraryBooks
|
||||
.AsNoTrackingWithIdentityResolution()
|
||||
.GetLibrary()
|
||||
.Where(lb => lb.Book.ContentType != ContentType.Parent || includeParents)
|
||||
.AsEnumerable()
|
||||
.ToList();
|
||||
|
||||
public static LibraryBook? GetLibraryBook_Flat_NoTracking(this LibationContext context, string productId, bool caseSensative = true)
|
||||
{
|
||||
var libraryQuery
|
||||
= context
|
||||
.LibraryBooks
|
||||
.AsNoTrackingWithIdentityResolution()
|
||||
.GetLibrary();
|
||||
public LibraryBook? GetLibraryBook_Flat_NoTracking(string productId, bool caseSensative = true)
|
||||
{
|
||||
var libraryQuery
|
||||
= context
|
||||
.LibraryBooks
|
||||
.AsNoTrackingWithIdentityResolution()
|
||||
.GetLibrary();
|
||||
|
||||
return caseSensative ? libraryQuery.SingleOrDefault(lb => lb.Book.AudibleProductId == productId)
|
||||
: libraryQuery.SingleOrDefault(lb => EF.Functions.Collate(lb.Book.AudibleProductId, "NOCASE") == productId);
|
||||
return caseSensative ? libraryQuery.SingleOrDefault(lb => lb.Book.AudibleProductId == productId)
|
||||
: libraryQuery.SingleOrDefault(lb => EF.Functions.Collate(lb.Book.AudibleProductId, "NOCASE") == productId);
|
||||
}
|
||||
|
||||
/// <summary>This is still IQueryable. YOU MUST CALL ToList() YOURSELF</summary>
|
||||
public static IQueryable<LibraryBook> GetLibrary(this IQueryable<LibraryBook> library)
|
||||
=> library
|
||||
.Where(lb => !lb.IsDeleted)
|
||||
.getLibrary();
|
||||
public List<LibraryBook> GetUnLiberated_Flat_NoTracking()
|
||||
=> context
|
||||
.LibraryBooks
|
||||
.AsNoTrackingWithIdentityResolution()
|
||||
.GetLibrary()
|
||||
.Where(IsUnLiberatedExpression)
|
||||
.AsEnumerable()
|
||||
.ToList();
|
||||
|
||||
public static List<LibraryBook> GetDeletedLibraryBooks(this LibationContext context)
|
||||
=> context
|
||||
.LibraryBooks
|
||||
.AsNoTrackingWithIdentityResolution()
|
||||
//Return all parents so the trash bin grid can show podcasts beneath their parents
|
||||
.Where(lb => lb.IsDeleted || lb.Book.ContentType == ContentType.Parent)
|
||||
.getLibrary()
|
||||
.ToList();
|
||||
public List<LibraryBook> GetDeletedLibraryBooks()
|
||||
=> context
|
||||
.LibraryBooks
|
||||
.AsNoTrackingWithIdentityResolution()
|
||||
//Return all parents so the trash bin grid can show podcasts beneath their parents
|
||||
.Where(lb => lb.IsDeleted || lb.Book.ContentType == ContentType.Parent)
|
||||
.getLibrary()
|
||||
.ToList();
|
||||
}
|
||||
|
||||
/// <summary>This is still IQueryable. YOU MUST CALL ToList() YOURSELF</summary>
|
||||
private static IQueryable<LibraryBook> getLibrary(this IQueryable<LibraryBook> library)
|
||||
=> library
|
||||
// owned items are always loaded. eg: book.UserDefinedItem, book.Supplements
|
||||
.Include(le => le.Book).ThenInclude(b => b.SeriesLink).ThenInclude(sb => sb.Series)
|
||||
.Include(le => le.Book).ThenInclude(b => b.ContributorsLink).ThenInclude(c => c.Contributor)
|
||||
.Include(le => le.Book).ThenInclude(b => b.CategoriesLink).ThenInclude(c => c.CategoryLadder).ThenInclude(c => c._categories);
|
||||
extension(IQueryable<LibraryBook> library)
|
||||
{
|
||||
/// <summary>This is still IQueryable. YOU MUST CALL ToList() YOURSELF</summary>
|
||||
public IQueryable<LibraryBook> GetLibrary()
|
||||
=> library.Where(lb => !lb.IsDeleted).getLibrary();
|
||||
|
||||
public static IEnumerable<LibraryBook> ParentedEpisodes(this IEnumerable<LibraryBook> libraryBooks)
|
||||
=> libraryBooks.Where(lb => lb.Book.IsEpisodeParent()).SelectMany(libraryBooks.FindChildren);
|
||||
/// <summary>This is still IQueryable. YOU MUST CALL ToList() YOURSELF</summary>
|
||||
private IQueryable<LibraryBook> getLibrary()
|
||||
=> library
|
||||
// owned items are always loaded. eg: book.UserDefinedItem, book.Supplements
|
||||
.Include(le => le.Book).ThenInclude(b => b.SeriesLink).ThenInclude(sb => sb.Series)
|
||||
.Include(le => le.Book).ThenInclude(b => b.ContributorsLink).ThenInclude(c => c.Contributor)
|
||||
.Include(le => le.Book).ThenInclude(b => b.CategoriesLink).ThenInclude(c => c.CategoryLadder).ThenInclude(c => c._categories);
|
||||
}
|
||||
|
||||
public static IEnumerable<LibraryBook> FindOrphanedEpisodes(this IEnumerable<LibraryBook> libraryBooks)
|
||||
=> libraryBooks
|
||||
.Where(lb => lb.Book.IsEpisodeChild())
|
||||
.ExceptBy(
|
||||
libraryBooks
|
||||
.ParentedEpisodes()
|
||||
.Select(ge => ge.Book.AudibleProductId), ge => ge.Book.AudibleProductId);
|
||||
extension(IEnumerable<LibraryBook> libraryBooks)
|
||||
{
|
||||
public IEnumerable<LibraryBook> UnLiberated()
|
||||
=> libraryBooks.Where(lb => lb.NeedsPdfDownload || lb.NeedsBookDownload);
|
||||
|
||||
#nullable enable
|
||||
public static LibraryBook? FindSeriesParent(this IEnumerable<LibraryBook> libraryBooks, LibraryBook seriesEpisode)
|
||||
{
|
||||
if (seriesEpisode.Book.SeriesLink is null) return null;
|
||||
public IEnumerable<LibraryBook> ParentedEpisodes()
|
||||
=> libraryBooks.Where(lb => lb.Book.IsEpisodeParent()).SelectMany(libraryBooks.FindChildren);
|
||||
|
||||
try
|
||||
{
|
||||
//Parent books will always have exactly 1 SeriesBook due to how
|
||||
//they are imported in ApiExtended.getChildEpisodesAsync()
|
||||
return libraryBooks.FirstOrDefault(
|
||||
lb =>
|
||||
lb.Book.IsEpisodeParent() &&
|
||||
seriesEpisode.Book.SeriesLink.Any(
|
||||
s => s.Series.AudibleSeriesId == lb.Book.SeriesLink.Single().Series.AudibleSeriesId));
|
||||
}
|
||||
catch (System.Exception ex)
|
||||
{
|
||||
Serilog.Log.Error(ex, "Query error in {0}", nameof(FindSeriesParent));
|
||||
return null;
|
||||
}
|
||||
}
|
||||
#nullable disable
|
||||
public IEnumerable<LibraryBook> FindOrphanedEpisodes()
|
||||
=> libraryBooks
|
||||
.Where(lb => lb.Book.IsEpisodeChild())
|
||||
.ExceptBy(
|
||||
libraryBooks
|
||||
.ParentedEpisodes()
|
||||
.Select(ge => ge.Book.AudibleProductId), ge => ge.Book.AudibleProductId);
|
||||
|
||||
public static List<LibraryBook> FindChildren(this IEnumerable<LibraryBook> bookList, LibraryBook parent)
|
||||
=> bookList
|
||||
.Where(
|
||||
lb =>
|
||||
lb.Book.IsEpisodeChild() &&
|
||||
lb.Book.SeriesLink?
|
||||
.Any(
|
||||
s =>
|
||||
s.Series.AudibleSeriesId == parent.Book.AudibleProductId
|
||||
) == true
|
||||
).ToList();
|
||||
public IEnumerable<LibraryBook> FindChildren(LibraryBook parent)
|
||||
=> libraryBooks.Where(lb => lb.Book.IsEpisodeChild() && lb.HasSeriesId(parent.Book.AudibleProductId));
|
||||
|
||||
public static bool NeedsPdfDownload(this LibraryBook libraryBook)
|
||||
=> !libraryBook.AbsentFromLastScan && libraryBook.Book.UserDefinedItem.PdfStatus is LiberatedStatus.NotLiberated;
|
||||
public static bool NeedsBookDownload(this LibraryBook libraryBook)
|
||||
=> !libraryBook.AbsentFromLastScan && libraryBook.Book.UserDefinedItem.BookStatus is LiberatedStatus.NotLiberated or LiberatedStatus.PartialDownload;
|
||||
public static IEnumerable<LibraryBook> UnLiberated(this IEnumerable<LibraryBook> bookList)
|
||||
=> bookList.Where(lb => lb.NeedsPdfDownload() || lb.NeedsBookDownload());
|
||||
}
|
||||
public LibraryBook? FindSeriesParent(LibraryBook seriesEpisode)
|
||||
{
|
||||
if (seriesEpisode.Book.SeriesLink is null) return null;
|
||||
|
||||
try
|
||||
{
|
||||
//Parent books will always have exactly 1 SeriesBook due to how
|
||||
//they are imported in ApiExtended.getChildEpisodesAsync()
|
||||
return libraryBooks.FirstOrDefault(
|
||||
lb =>
|
||||
lb.Book.IsEpisodeParent() &&
|
||||
seriesEpisode.Book.SeriesLink.Any(
|
||||
s => s.Series.AudibleSeriesId == lb.Book.SeriesLink.Single().Series.AudibleSeriesId));
|
||||
}
|
||||
catch (System.Exception ex)
|
||||
{
|
||||
Serilog.Log.Error(ex, "Query error in {0}", nameof(FindSeriesParent));
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension(LibraryBook libraryBook)
|
||||
{
|
||||
public bool HasSeriesId(string audibleSeriesId) => libraryBook.Book.SeriesLink?.Any(s => s.Series.AudibleSeriesId.EqualsInsensitive(audibleSeriesId)) is true;
|
||||
public bool Downloadable => !libraryBook.AbsentFromLastScan && libraryBook.Book.ContentType is ContentType.Product or ContentType.Episode;
|
||||
public bool NeedsPdfDownload => libraryBook.Downloadable && libraryBook.Book.UserDefinedItem.PdfStatus is LiberatedStatus.NotLiberated;
|
||||
public bool NeedsBookDownload => libraryBook.Downloadable && libraryBook.Book.UserDefinedItem.BookStatus is LiberatedStatus.NotLiberated or LiberatedStatus.PartialDownload;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -334,13 +334,15 @@ namespace FileLiberator
|
||||
|
||||
/// <summary>Move new files to 'Books' directory</summary>
|
||||
/// <returns>Return directory if audiobook file(s) were successfully created and can be located on disk. Else null.</returns>
|
||||
private void MoveFilesToBooksDir(LibraryBook libraryBook, LongPath destinationDir, List<TempFile> entries, CancellationToken cancellationToken)
|
||||
private async Task MoveFilesToBooksDir(LibraryBook libraryBook, LongPath destinationDir, List<TempFile> entries, CancellationToken cancellationToken)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
AverageSpeed averageSpeed = new();
|
||||
MoveWithProgress moveWithProgress = new();
|
||||
|
||||
var totalSizeToMove = entries.Sum(f => new FileInfo(f.FilePath).Length);
|
||||
long totalBytesMoved = 0;
|
||||
moveWithProgress.MoveProgress += onMovefileProgress;
|
||||
|
||||
for (var i = 0; i < entries.Count; i++)
|
||||
{
|
||||
@@ -355,28 +357,13 @@ namespace FileLiberator
|
||||
Configuration.OverwriteExisting);
|
||||
|
||||
var realDest
|
||||
= FileUtility.SaferMoveToValidPath(
|
||||
entry.FilePath,
|
||||
= FileUtility.GetValidFilename(
|
||||
destFileName,
|
||||
Configuration.ReplacementCharacters,
|
||||
entry.Extension,
|
||||
Configuration.OverwriteExisting);
|
||||
|
||||
#region File Move Progress
|
||||
totalBytesMoved += new FileInfo(realDest).Length;
|
||||
averageSpeed.AddPosition(totalBytesMoved);
|
||||
var estSecsRemaining = (totalSizeToMove - totalBytesMoved) / averageSpeed.Average;
|
||||
|
||||
if (double.IsNormal(estSecsRemaining))
|
||||
OnStreamingTimeRemaining(TimeSpan.FromSeconds(estSecsRemaining));
|
||||
|
||||
OnStreamingProgressChanged(new DownloadProgress
|
||||
{
|
||||
ProgressPercentage = 100d * totalBytesMoved / totalSizeToMove,
|
||||
BytesReceived = totalBytesMoved,
|
||||
TotalBytesToReceive = totalSizeToMove
|
||||
});
|
||||
#endregion
|
||||
await moveWithProgress.MoveAsync(entry.FilePath, realDest, Configuration.OverwriteExisting, cancellationToken);
|
||||
|
||||
// propagate corrected path for cue file (after this for-loop)
|
||||
entries[i] = entry with { FilePath = realDest };
|
||||
@@ -395,6 +382,23 @@ namespace FileLiberator
|
||||
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
AudibleFileStorage.Audio.Refresh();
|
||||
|
||||
void onMovefileProgress(object? sender, MoveFileProgressEventArgs e)
|
||||
{
|
||||
totalBytesMoved += e.BytesMoved;
|
||||
averageSpeed.AddPosition(totalBytesMoved);
|
||||
var estSecsRemaining = (totalSizeToMove - totalBytesMoved) / averageSpeed.Average;
|
||||
|
||||
if (double.IsNormal(estSecsRemaining))
|
||||
OnStreamingTimeRemaining(TimeSpan.FromSeconds(estSecsRemaining));
|
||||
|
||||
OnStreamingProgressChanged(new DownloadProgress
|
||||
{
|
||||
ProgressPercentage = 100d * totalBytesMoved / totalSizeToMove,
|
||||
BytesReceived = totalBytesMoved,
|
||||
TotalBytesToReceive = totalSizeToMove
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
private void DownloadCoverArt(LongPath destinationDir, DownloadOptions options, CancellationToken cancellationToken)
|
||||
|
||||
@@ -244,27 +244,14 @@ namespace FileManager
|
||||
/// <returns>List of files</returns>
|
||||
public static IEnumerable<LongPath> SaferEnumerateFiles(LongPath path, string searchPattern = "*", SearchOption searchOption = SearchOption.TopDirectoryOnly)
|
||||
{
|
||||
var foundFiles = Enumerable.Empty<LongPath>();
|
||||
|
||||
try
|
||||
{
|
||||
if (searchOption == SearchOption.AllDirectories)
|
||||
{
|
||||
IEnumerable<LongPath> subDirs = Directory.EnumerateDirectories(path).Select(p => (LongPath)p);
|
||||
// Add files in subdirectories recursively to the list
|
||||
foreach (string dir in subDirs)
|
||||
foundFiles = foundFiles.Concat(SaferEnumerateFiles(dir, searchPattern, searchOption));
|
||||
}
|
||||
|
||||
// Add files from the current directory
|
||||
foundFiles = foundFiles.Concat(Directory.EnumerateFiles(path, searchPattern).Select(f => (LongPath)f));
|
||||
}
|
||||
catch (UnauthorizedAccessException) { }
|
||||
catch (PathTooLongException) { }
|
||||
// Symbolic links will result in DirectoryNotFoundException. Ohter logical directories might also. Just skip them. Don't want to risk (or have to handle) infinite recursion
|
||||
catch (DirectoryNotFoundException) { }
|
||||
|
||||
return foundFiles;
|
||||
var enumOptions = new EnumerationOptions
|
||||
{
|
||||
RecurseSubdirectories = searchOption == SearchOption.AllDirectories,
|
||||
IgnoreInaccessible = true,
|
||||
ReturnSpecialDirectories = false,
|
||||
MatchType = MatchType.Simple
|
||||
};
|
||||
return Directory.EnumerateFiles(path.Path, searchPattern, enumOptions).Select(p => (LongPath) p);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
184
Source/FileManager/MoveWithProgress.cs
Normal file
184
Source/FileManager/MoveWithProgress.cs
Normal file
@@ -0,0 +1,184 @@
|
||||
using System;
|
||||
using System.Buffers;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Win32.SafeHandles;
|
||||
using Serilog;
|
||||
|
||||
#nullable enable
|
||||
namespace FileManager;
|
||||
|
||||
public class MoveFileProgressEventArgs : EventArgs
|
||||
{
|
||||
public long TotalFileSize { get; }
|
||||
public long TotalBytesTransferred { get; }
|
||||
public long BytesMoved { get; }
|
||||
public bool Continue { get; set; } = true;
|
||||
|
||||
internal MoveFileProgressEventArgs(long bytesMoved, long totalBytesTransferred, long totalFileSize)
|
||||
{
|
||||
BytesMoved = bytesMoved;
|
||||
TotalBytesTransferred = totalBytesTransferred;
|
||||
TotalFileSize = totalFileSize;
|
||||
}
|
||||
}
|
||||
|
||||
public class MoveWithProgress
|
||||
{
|
||||
public event EventHandler<MoveFileProgressEventArgs>? MoveProgress;
|
||||
|
||||
public async Task<bool> MoveAsync(LongPath source, LongPath destination, bool overwrite = false, CancellationToken cancellation = default)
|
||||
{
|
||||
ArgumentException.ThrowIfNullOrEmpty(source, nameof(source));
|
||||
ArgumentException.ThrowIfNullOrEmpty(destination, nameof(destination));
|
||||
var sourceFileInfo = new FileInfo(source);
|
||||
|
||||
if (!sourceFileInfo.Exists)
|
||||
throw new FileNotFoundException($"Source file '{source}' does not exist.", source);
|
||||
|
||||
var destinationFile = new FileInfo(destination);
|
||||
var sourceDevice = GetDeviceId(sourceFileInfo);
|
||||
var destinationDevice = GetDeviceId(destinationFile.Directory);
|
||||
|
||||
if (sourceDevice == destinationDevice)
|
||||
{
|
||||
File.Move(sourceFileInfo.FullName, destinationFile.FullName, overwrite);
|
||||
MoveProgress?.Invoke(this, new MoveFileProgressEventArgs(destinationFile.Length, destinationFile.Length, sourceFileInfo.Length));
|
||||
return true;
|
||||
}
|
||||
|
||||
if (destinationFile.Exists && !overwrite)
|
||||
throw new IOException("The file exists.");
|
||||
|
||||
bool success = false;
|
||||
try
|
||||
{
|
||||
success = await CopyWithProgressAsync(sourceFileInfo, destinationFile, cancellation);
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (success)
|
||||
FileUtility.SaferDelete(sourceFileInfo.FullName);
|
||||
else
|
||||
FileUtility.SaferDelete(destinationFile.FullName);
|
||||
}
|
||||
return success;
|
||||
}
|
||||
|
||||
private static string? GetDeviceId(FileSystemInfo? fsEntry)
|
||||
=> fsEntry?.FullName is not string path ? null
|
||||
: LongPath.IsWindows ? GetDriveSerialNumber(path)
|
||||
: LongPath.IsOSX ? RunShellCommand("stat -L -f %d \"" + path + "\"")
|
||||
: RunShellCommand("stat -L -f -c %d \"" + path + "\"");
|
||||
|
||||
private async Task<bool> CopyWithProgressAsync(FileInfo sourceFileInfo, FileInfo destinationFile, CancellationToken cancellation)
|
||||
{
|
||||
const int BlockSizeMb = 8;
|
||||
const int BlockSizeBytes = BlockSizeMb * (1 << 20);
|
||||
using FileStream sourceStream = sourceFileInfo.Open(FileMode.Open, FileAccess.Read, FileShare.Read);
|
||||
using FileStream destinationStream = destinationFile.Open(FileMode.OpenOrCreate, FileAccess.Write, FileShare.Read);
|
||||
|
||||
using IMemoryOwner<byte> pool = MemoryPool<byte>.Shared.Rent(2 * BlockSizeBytes);
|
||||
Memory<byte> readBuff = pool.Memory.Slice(0, BlockSizeBytes);
|
||||
Memory<byte> writeBuff = pool.Memory.Slice(BlockSizeBytes, BlockSizeBytes);
|
||||
|
||||
long totalCopied = 0, bytesMovedSinceLastReport = 0;
|
||||
DateTime nextReport = default;
|
||||
int bytesRead = await sourceStream.ReadAsync(writeBuff, cancellation);
|
||||
while (bytesRead > 0)
|
||||
{
|
||||
totalCopied += bytesRead;
|
||||
bytesMovedSinceLastReport += bytesRead;
|
||||
|
||||
var readTask = sourceStream.ReadAsync(readBuff, cancellation);
|
||||
await destinationStream.WriteAsync(writeBuff[..bytesRead], cancellation);
|
||||
|
||||
if (DateTime.UtcNow >= nextReport)
|
||||
{
|
||||
var args = new MoveFileProgressEventArgs(bytesMovedSinceLastReport, totalCopied, sourceFileInfo.Length);
|
||||
bytesMovedSinceLastReport = 0;
|
||||
MoveProgress?.Invoke(this, args);
|
||||
if (!args.Continue)
|
||||
break;
|
||||
nextReport = DateTime.UtcNow.AddMilliseconds(200.0);
|
||||
}
|
||||
bytesRead = await readTask;
|
||||
(readBuff, writeBuff) = (writeBuff, readBuff);
|
||||
}
|
||||
|
||||
destinationStream.SetLength(totalCopied);
|
||||
MoveProgress?.Invoke(this, new MoveFileProgressEventArgs(bytesMovedSinceLastReport, totalCopied, sourceFileInfo.Length));
|
||||
return totalCopied == sourceFileInfo.Length;
|
||||
}
|
||||
|
||||
private static string? RunShellCommand(string command)
|
||||
{
|
||||
var psi = new ProcessStartInfo
|
||||
{
|
||||
FileName = "/bin/sh",
|
||||
RedirectStandardOutput = true,
|
||||
UseShellExecute = false,
|
||||
CreateNoWindow = true,
|
||||
ArgumentList = { "-c", command }
|
||||
};
|
||||
try
|
||||
{
|
||||
var proc = Process.Start(psi);
|
||||
proc?.WaitForExit();
|
||||
return proc?.StandardOutput?.ReadToEnd()?.Trim();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Logger.Error(e, "Failed to run shell command. {@Arguments}", psi.ArgumentList);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static string? GetDriveSerialNumber(string path)
|
||||
{
|
||||
const uint FILE_FLAG_BACKUP_SEMANTICS = 0x02000000;
|
||||
const uint OPEN_EXISTING = 3;
|
||||
var handle = CreateFile(path, FileAccess.Read, FileShare.Read, 0, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, 0);
|
||||
if (handle.IsInvalid)
|
||||
return null;
|
||||
|
||||
try
|
||||
{
|
||||
BY_HANDLE_FILE_INFORMATION info = default;
|
||||
if (!GetFileInformationByHandle(handle, ref info))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
return info.dwVolumeSerialNumber.ToString("x8");
|
||||
}
|
||||
finally
|
||||
{
|
||||
handle.Close();
|
||||
}
|
||||
}
|
||||
|
||||
[DllImport("kernel32.dll", SetLastError = true)]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
private static extern bool GetFileInformationByHandle(SafeFileHandle hFile, ref BY_HANDLE_FILE_INFORMATION lpFileInformation);
|
||||
|
||||
[DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
|
||||
private static extern SafeFileHandle CreateFile(string fileName, FileAccess fileAccess, FileShare fileShare, nint lpSecurityAttributes, uint dwCreationDisposition, uint dwFlagsAndAttributes, nint hTemplateFile);
|
||||
|
||||
[StructLayout(LayoutKind.Sequential, Pack = 4)]
|
||||
private struct BY_HANDLE_FILE_INFORMATION
|
||||
{
|
||||
private uint dwFileAttributes;
|
||||
private long ftCreationTime;
|
||||
private long ftLastAccessTime;
|
||||
private long ftLastWriteTime;
|
||||
public uint dwVolumeSerialNumber;
|
||||
private uint nFileSizeHigh;
|
||||
private uint nFileSizeLow;
|
||||
private uint nNumberOfLinks;
|
||||
private uint nFileIndexHigh;
|
||||
private uint nFileIndexLow;
|
||||
}
|
||||
}
|
||||
@@ -70,11 +70,11 @@
|
||||
<TrimmableAssembly Include="Avalonia.Themes.Default" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Avalonia.Desktop" Version="11.3.10" />
|
||||
<PackageReference Include="Avalonia.Desktop" Version="11.3.11" />
|
||||
<!--Condition below is needed to remove Avalonia.Diagnostics package from build output in Release configuration.-->
|
||||
<PackageReference Condition="'$(Configuration)' == 'Debug'" Include="Avalonia.Diagnostics" Version="11.3.10" />
|
||||
<PackageReference Condition="'$(Configuration)' == 'Debug'" Include="Avalonia.Diagnostics" Version="11.3.11" />
|
||||
<PackageReference Include="ReactiveUI.Avalonia" Version="11.3.8" />
|
||||
<PackageReference Include="Avalonia.Themes.Fluent" Version="11.3.10" />
|
||||
<PackageReference Include="Avalonia.Themes.Fluent" Version="11.3.11" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\HangoverBase\HangoverBase.csproj" />
|
||||
|
||||
@@ -63,7 +63,7 @@
|
||||
<TextBlock Text="{Binding Request_xHE_AACText}" />
|
||||
</CheckBox>
|
||||
</Grid>
|
||||
|
||||
<!--
|
||||
<Grid ColumnDefinitions="*,Auto">
|
||||
<CheckBox
|
||||
ToolTip.Tip="{Binding RequestSpatialTip}"
|
||||
@@ -95,9 +95,9 @@
|
||||
VerticalAlignment="Center"
|
||||
ItemsSource="{Binding SpatialAudioCodecs}"
|
||||
SelectedItem="{Binding SpatialAudioCodec}"/>
|
||||
</Grid>
|
||||
|
||||
</Grid>
|
||||
</Grid>
|
||||
-->
|
||||
|
||||
<CheckBox IsChecked="{Binding CreateCueSheet, Mode=TwoWay}">
|
||||
<TextBlock Text="{Binding CreateCueSheetText}" />
|
||||
|
||||
@@ -73,12 +73,12 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Avalonia.Controls.ColorPicker" Version="11.3.10" />
|
||||
<PackageReference Include="Avalonia.Diagnostics" Version="11.3.10" Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'" />
|
||||
<PackageReference Include="Avalonia.Controls.DataGrid" Version="11.3.10" />
|
||||
<PackageReference Include="Avalonia.Desktop" Version="11.3.10" />
|
||||
<PackageReference Include="Avalonia.Controls.ColorPicker" Version="11.3.11" />
|
||||
<PackageReference Include="Avalonia.Diagnostics" Version="11.3.11" Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'" />
|
||||
<PackageReference Include="Avalonia.Controls.DataGrid" Version="11.3.11" />
|
||||
<PackageReference Include="Avalonia.Desktop" Version="11.3.11" />
|
||||
<PackageReference Include="ReactiveUI.Avalonia" Version="11.3.8" />
|
||||
<PackageReference Include="Avalonia.Themes.Fluent" Version="11.3.10" />
|
||||
<PackageReference Include="Avalonia.Themes.Fluent" Version="11.3.11" />
|
||||
<PackageReference Include="WebViewControlAvaloniaFree" Version="11.3.15" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@ namespace LibationAvalonia.ViewModels
|
||||
/// <summary> This gets called by the "Begin Book and PDF Backups" menu item. </summary>
|
||||
public async Task BackupAllBooks()
|
||||
{
|
||||
var books = await Task.Run(() => DbContexts.GetLibrary_Flat_NoTracking());
|
||||
var books = await Task.Run(DbContexts.GetUnliberated_Flat_NoTracking);
|
||||
BackupAllBooks(books);
|
||||
}
|
||||
|
||||
|
||||
@@ -98,8 +98,8 @@ namespace LibationFileManager
|
||||
Some audiobooks are only delivered in the highest
|
||||
available quality with special, third-party content
|
||||
protection. Enabling this option will allows you to
|
||||
request audiobooks in the xHE-AAC codec and in
|
||||
spatial (Dolby Atmos) audio formats.
|
||||
request audiobooks in the xHE-AAC codec, which is
|
||||
often higher quality than the standard AAC-LC codec.
|
||||
""" },
|
||||
{nameof(Request_xHE_AAC), """
|
||||
If selected, Libation will request audiobooks in the
|
||||
|
||||
@@ -306,10 +306,10 @@ namespace LibationFileManager
|
||||
[Description("Request xHE-AAC codec")]
|
||||
public bool Request_xHE_AAC { get => GetNonString(defaultValue: false); set => SetNonString(value); }
|
||||
|
||||
[Description("Request Spatial Audio")]
|
||||
public bool RequestSpatial { get => GetNonString(defaultValue: true); set => SetNonString(value); }
|
||||
//[Description("Request Spatial Audio")]
|
||||
public bool RequestSpatial { get => false; set { } } // { get => GetNonString(defaultValue: true); set => SetNonString(value); }
|
||||
|
||||
[Description("Spatial audio codec:")]
|
||||
//[Description("Spatial audio codec:")]
|
||||
public SpatialCodec SpatialAudioCodec { get => GetNonString(defaultValue: SpatialCodec.EC_3); set => SetNonString(value); }
|
||||
|
||||
[Description("Audio quality to request from Audible:")]
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="10.0.1" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="10.0.2" />
|
||||
<PackageReference Include="NameParserSharp" Version="1.5.0" />
|
||||
<PackageReference Include="Serilog.Exceptions" Version="8.4.0" />
|
||||
</ItemGroup>
|
||||
|
||||
@@ -221,6 +221,8 @@ namespace LibationUiBase.GridView
|
||||
nameof(Category) => string.IsNullOrWhiteSpace(Category),
|
||||
nameof(Misc) => string.IsNullOrWhiteSpace(Misc),
|
||||
nameof(BookTags) => string.IsNullOrWhiteSpace(BookTags),
|
||||
nameof(IncludedUntil) => string.IsNullOrWhiteSpace(IncludedUntil),
|
||||
nameof(LastDownload) => LastDownload?.IsValid is not true,
|
||||
_ => false
|
||||
};
|
||||
|
||||
|
||||
@@ -151,7 +151,7 @@ public class ProcessBookViewModel : ReactiveObject
|
||||
}
|
||||
catch (ContentLicenseDeniedException ldex)
|
||||
{
|
||||
Serilog.Log.Logger.Error(ldex, "Content license was denied for {#Book}", LibraryBook.LogFriendly());
|
||||
Serilog.Log.Logger.Error(ldex, "Content license was denied for {Book}", LibraryBook.LogFriendly());
|
||||
if (ldex.AYCL?.RejectionReason is null or RejectionReason.GenericError)
|
||||
{
|
||||
LogInfo($"{procName}: Content license was denied, but this error appears to be caused by a temporary interruption of service. - {LibraryBook.Book}");
|
||||
|
||||
@@ -96,7 +96,7 @@ public class ProcessQueueViewModel : ReactiveObject
|
||||
if (!IsBooksDirectoryValid(config))
|
||||
return false;
|
||||
|
||||
var needsPdf = libraryBooks.Where(lb => lb.NeedsPdfDownload()).ToArray();
|
||||
var needsPdf = libraryBooks.Where(lb => lb.NeedsPdfDownload).ToArray();
|
||||
if (needsPdf.Length > 0)
|
||||
{
|
||||
Serilog.Log.Logger.Information("Begin download {count} pdfs", needsPdf.Length);
|
||||
@@ -137,14 +137,14 @@ public class ProcessQueueViewModel : ReactiveObject
|
||||
|
||||
if (item.AbsentFromLastScan)
|
||||
return false;
|
||||
else if (item.NeedsBookDownload())
|
||||
else if (item.NeedsBookDownload)
|
||||
{
|
||||
RemoveCompleted(item);
|
||||
Serilog.Log.Logger.Information("Begin single library book backup of {libraryBook}", item);
|
||||
AddDownloadDecrypt([item], config);
|
||||
return true;
|
||||
}
|
||||
else if (item.NeedsPdfDownload())
|
||||
else if (item.NeedsPdfDownload)
|
||||
{
|
||||
RemoveCompleted(item);
|
||||
Serilog.Log.Logger.Information("Begin single pdf backup of {libraryBook}", item);
|
||||
|
||||
@@ -25,7 +25,6 @@ namespace LibationWinForms.Dialogs
|
||||
this.stripUnabridgedCbox.Text = desc(nameof(config.StripUnabridged));
|
||||
this.moveMoovAtomCbox.Text = desc(nameof(config.MoveMoovToBeginning));
|
||||
this.useWidevineCbox.Text = desc(nameof(config.UseWidevine));
|
||||
this.requestSpatialCbox.Text = desc(nameof(config.RequestSpatial));
|
||||
this.request_xHE_AAC_Cbox.Text = desc(nameof(config.Request_xHE_AAC));
|
||||
|
||||
toolTip.SetToolTip(combineNestedChapterTitlesCbox, Configuration.GetHelpText(nameof(config.CombineNestedChapterTitles)));
|
||||
@@ -38,9 +37,7 @@ namespace LibationWinForms.Dialogs
|
||||
toolTip.SetToolTip(retainAaxFileCbox, Configuration.GetHelpText(nameof(config.RetainAaxFile)));
|
||||
toolTip.SetToolTip(stripAudibleBrandingCbox, Configuration.GetHelpText(nameof(config.StripAudibleBrandAudio)));
|
||||
toolTip.SetToolTip(useWidevineCbox, Configuration.GetHelpText(nameof(config.UseWidevine)));
|
||||
toolTip.SetToolTip(requestSpatialCbox, Configuration.GetHelpText(nameof(config.RequestSpatial)));
|
||||
toolTip.SetToolTip(request_xHE_AAC_Cbox, Configuration.GetHelpText(nameof(config.Request_xHE_AAC)));
|
||||
toolTip.SetToolTip(spatialAudioCodecCb, Configuration.GetHelpText(nameof(config.SpatialAudioCodec)));
|
||||
toolTip.SetToolTip(minFileDurationLbl, Configuration.GetHelpText(nameof(config.SpatialAudioCodec)));
|
||||
toolTip.SetToolTip(minFileDurationNud, Configuration.GetHelpText(nameof(config.SpatialAudioCodec)));
|
||||
|
||||
@@ -50,12 +47,6 @@ namespace LibationWinForms.Dialogs
|
||||
new EnumDisplay<Configuration.DownloadQuality>(Configuration.DownloadQuality.High),
|
||||
]);
|
||||
|
||||
spatialAudioCodecCb.Items.AddRange(
|
||||
[
|
||||
new EnumDisplay<Configuration.SpatialCodec>(Configuration.SpatialCodec.EC_3, "Dolby Digital Plus (E-AC-3)"),
|
||||
new EnumDisplay<Configuration.SpatialCodec>(Configuration.SpatialCodec.AC_4, "Dolby AC-4")
|
||||
]);
|
||||
|
||||
clipsBookmarksFormatCb.Items.AddRange(
|
||||
[
|
||||
Configuration.ClipBookmarkFormat.CSV,
|
||||
@@ -81,10 +72,8 @@ namespace LibationWinForms.Dialogs
|
||||
downloadCoverArtCbox.Checked = config.DownloadCoverArt;
|
||||
downloadClipsBookmarksCbox.Checked = config.DownloadClipsBookmarks;
|
||||
fileDownloadQualityCb.SelectedItem = config.FileDownloadQuality;
|
||||
spatialAudioCodecCb.SelectedItem = config.SpatialAudioCodec;
|
||||
useWidevineCbox.Checked = config.UseWidevine;
|
||||
request_xHE_AAC_Cbox.Checked = config.Request_xHE_AAC;
|
||||
requestSpatialCbox.Checked = config.RequestSpatial;
|
||||
|
||||
clipsBookmarksFormatCb.SelectedItem = config.ClipsBookmarksFileFormat;
|
||||
retainAaxFileCbox.Checked = config.RetainAaxFile;
|
||||
@@ -130,8 +119,6 @@ namespace LibationWinForms.Dialogs
|
||||
config.FileDownloadQuality = ((EnumDisplay<Configuration.DownloadQuality>)fileDownloadQualityCb.SelectedItem).Value;
|
||||
config.UseWidevine = useWidevineCbox.Checked;
|
||||
config.Request_xHE_AAC = request_xHE_AAC_Cbox.Checked;
|
||||
config.RequestSpatial = requestSpatialCbox.Checked;
|
||||
config.SpatialAudioCodec = ((EnumDisplay<Configuration.SpatialCodec>)spatialAudioCodecCb.SelectedItem).Value;
|
||||
config.ClipsBookmarksFileFormat = (Configuration.ClipBookmarkFormat)clipsBookmarksFormatCb.SelectedItem;
|
||||
config.RetainAaxFile = retainAaxFileCbox.Checked;
|
||||
config.CombineNestedChapterTitles = combineNestedChapterTitlesCbox.Checked;
|
||||
@@ -204,11 +191,6 @@ namespace LibationWinForms.Dialogs
|
||||
}
|
||||
}
|
||||
|
||||
private void requestSpatialCbox_CheckedChanged(object sender, EventArgs e)
|
||||
{
|
||||
spatialAudioCodecCb.Enabled = requestSpatialCbox.Checked && useWidevineCbox.Checked;
|
||||
}
|
||||
|
||||
private void useWidevineCbox_CheckedChanged(object sender, EventArgs e)
|
||||
{
|
||||
if (useWidevineCbox.Checked)
|
||||
@@ -246,11 +228,10 @@ namespace LibationWinForms.Dialogs
|
||||
}
|
||||
else
|
||||
{
|
||||
requestSpatialCbox.Checked = request_xHE_AAC_Cbox.Checked = false;
|
||||
request_xHE_AAC_Cbox.Checked = false;
|
||||
}
|
||||
|
||||
requestSpatialCbox.Enabled = request_xHE_AAC_Cbox.Enabled = useWidevineCbox.Checked;
|
||||
requestSpatialCbox_CheckedChanged(sender, e);
|
||||
request_xHE_AAC_Cbox.Enabled = useWidevineCbox.Checked;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -89,9 +89,7 @@
|
||||
folderTemplateLbl = new System.Windows.Forms.Label();
|
||||
tab4AudioFileOptions = new System.Windows.Forms.TabPage();
|
||||
request_xHE_AAC_Cbox = new System.Windows.Forms.CheckBox();
|
||||
requestSpatialCbox = new System.Windows.Forms.CheckBox();
|
||||
useWidevineCbox = new System.Windows.Forms.CheckBox();
|
||||
spatialAudioCodecCb = new System.Windows.Forms.ComboBox();
|
||||
moveMoovAtomCbox = new System.Windows.Forms.CheckBox();
|
||||
fileDownloadQualityCb = new System.Windows.Forms.ComboBox();
|
||||
fileDownloadQualityLbl = new System.Windows.Forms.Label();
|
||||
@@ -315,7 +313,7 @@
|
||||
allowLibationFixupCbox.AutoSize = true;
|
||||
allowLibationFixupCbox.Checked = true;
|
||||
allowLibationFixupCbox.CheckState = System.Windows.Forms.CheckState.Checked;
|
||||
allowLibationFixupCbox.Location = new System.Drawing.Point(19, 230);
|
||||
allowLibationFixupCbox.Location = new System.Drawing.Point(19, 204);
|
||||
allowLibationFixupCbox.Name = "allowLibationFixupCbox";
|
||||
allowLibationFixupCbox.Size = new System.Drawing.Size(162, 19);
|
||||
allowLibationFixupCbox.TabIndex = 13;
|
||||
@@ -826,9 +824,7 @@
|
||||
tab4AudioFileOptions.AutoScroll = true;
|
||||
tab4AudioFileOptions.BackColor = System.Drawing.SystemColors.Window;
|
||||
tab4AudioFileOptions.Controls.Add(request_xHE_AAC_Cbox);
|
||||
tab4AudioFileOptions.Controls.Add(requestSpatialCbox);
|
||||
tab4AudioFileOptions.Controls.Add(useWidevineCbox);
|
||||
tab4AudioFileOptions.Controls.Add(spatialAudioCodecCb);
|
||||
tab4AudioFileOptions.Controls.Add(moveMoovAtomCbox);
|
||||
tab4AudioFileOptions.Controls.Add(fileDownloadQualityCb);
|
||||
tab4AudioFileOptions.Controls.Add(fileDownloadQualityLbl);
|
||||
@@ -865,19 +861,6 @@
|
||||
request_xHE_AAC_Cbox.TextAlign = System.Drawing.ContentAlignment.MiddleRight;
|
||||
request_xHE_AAC_Cbox.UseVisualStyleBackColor = true;
|
||||
//
|
||||
// requestSpatialCbox
|
||||
//
|
||||
requestSpatialCbox.AutoSize = true;
|
||||
requestSpatialCbox.Checked = true;
|
||||
requestSpatialCbox.CheckState = System.Windows.Forms.CheckState.Checked;
|
||||
requestSpatialCbox.Location = new System.Drawing.Point(19, 60);
|
||||
requestSpatialCbox.Name = "requestSpatialCbox";
|
||||
requestSpatialCbox.Size = new System.Drawing.Size(138, 19);
|
||||
requestSpatialCbox.TabIndex = 4;
|
||||
requestSpatialCbox.Text = "[RequestSpatial desc]";
|
||||
requestSpatialCbox.UseVisualStyleBackColor = true;
|
||||
requestSpatialCbox.CheckedChanged += requestSpatialCbox_CheckedChanged;
|
||||
//
|
||||
// useWidevineCbox
|
||||
//
|
||||
useWidevineCbox.AutoSize = true;
|
||||
@@ -891,16 +874,6 @@
|
||||
useWidevineCbox.UseVisualStyleBackColor = true;
|
||||
useWidevineCbox.CheckedChanged += useWidevineCbox_CheckedChanged;
|
||||
//
|
||||
// spatialAudioCodecCb
|
||||
//
|
||||
spatialAudioCodecCb.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList;
|
||||
spatialAudioCodecCb.FormattingEnabled = true;
|
||||
spatialAudioCodecCb.Location = new System.Drawing.Point(249, 60);
|
||||
spatialAudioCodecCb.Margin = new System.Windows.Forms.Padding(3, 3, 5, 3);
|
||||
spatialAudioCodecCb.Name = "spatialAudioCodecCb";
|
||||
spatialAudioCodecCb.Size = new System.Drawing.Size(173, 23);
|
||||
spatialAudioCodecCb.TabIndex = 5;
|
||||
//
|
||||
// moveMoovAtomCbox
|
||||
//
|
||||
moveMoovAtomCbox.AutoSize = true;
|
||||
@@ -934,7 +907,7 @@
|
||||
// combineNestedChapterTitlesCbox
|
||||
//
|
||||
combineNestedChapterTitlesCbox.AutoSize = true;
|
||||
combineNestedChapterTitlesCbox.Location = new System.Drawing.Point(19, 206);
|
||||
combineNestedChapterTitlesCbox.Location = new System.Drawing.Point(19, 180);
|
||||
combineNestedChapterTitlesCbox.Name = "combineNestedChapterTitlesCbox";
|
||||
combineNestedChapterTitlesCbox.Size = new System.Drawing.Size(217, 19);
|
||||
combineNestedChapterTitlesCbox.TabIndex = 12;
|
||||
@@ -945,7 +918,7 @@
|
||||
//
|
||||
clipsBookmarksFormatCb.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList;
|
||||
clipsBookmarksFormatCb.FormattingEnabled = true;
|
||||
clipsBookmarksFormatCb.Location = new System.Drawing.Point(285, 132);
|
||||
clipsBookmarksFormatCb.Location = new System.Drawing.Point(285, 106);
|
||||
clipsBookmarksFormatCb.Name = "clipsBookmarksFormatCb";
|
||||
clipsBookmarksFormatCb.Size = new System.Drawing.Size(67, 23);
|
||||
clipsBookmarksFormatCb.TabIndex = 9;
|
||||
@@ -953,7 +926,7 @@
|
||||
// downloadClipsBookmarksCbox
|
||||
//
|
||||
downloadClipsBookmarksCbox.AutoSize = true;
|
||||
downloadClipsBookmarksCbox.Location = new System.Drawing.Point(19, 134);
|
||||
downloadClipsBookmarksCbox.Location = new System.Drawing.Point(19, 108);
|
||||
downloadClipsBookmarksCbox.Name = "downloadClipsBookmarksCbox";
|
||||
downloadClipsBookmarksCbox.Size = new System.Drawing.Size(248, 19);
|
||||
downloadClipsBookmarksCbox.TabIndex = 8;
|
||||
@@ -968,9 +941,9 @@
|
||||
audiobookFixupsGb.Controls.Add(splitFilesByChapterCbox);
|
||||
audiobookFixupsGb.Controls.Add(stripUnabridgedCbox);
|
||||
audiobookFixupsGb.Controls.Add(stripAudibleBrandingCbox);
|
||||
audiobookFixupsGb.Location = new System.Drawing.Point(6, 254);
|
||||
audiobookFixupsGb.Location = new System.Drawing.Point(6, 229);
|
||||
audiobookFixupsGb.Name = "audiobookFixupsGb";
|
||||
audiobookFixupsGb.Size = new System.Drawing.Size(416, 128);
|
||||
audiobookFixupsGb.Size = new System.Drawing.Size(416, 153);
|
||||
audiobookFixupsGb.TabIndex = 14;
|
||||
audiobookFixupsGb.TabStop = false;
|
||||
audiobookFixupsGb.Text = "Audiobook Fix-ups";
|
||||
@@ -1438,7 +1411,7 @@
|
||||
// mergeOpeningEndCreditsCbox
|
||||
//
|
||||
mergeOpeningEndCreditsCbox.AutoSize = true;
|
||||
mergeOpeningEndCreditsCbox.Location = new System.Drawing.Point(19, 182);
|
||||
mergeOpeningEndCreditsCbox.Location = new System.Drawing.Point(19, 156);
|
||||
mergeOpeningEndCreditsCbox.Name = "mergeOpeningEndCreditsCbox";
|
||||
mergeOpeningEndCreditsCbox.Size = new System.Drawing.Size(198, 19);
|
||||
mergeOpeningEndCreditsCbox.TabIndex = 11;
|
||||
@@ -1448,7 +1421,7 @@
|
||||
// retainAaxFileCbox
|
||||
//
|
||||
retainAaxFileCbox.AutoSize = true;
|
||||
retainAaxFileCbox.Location = new System.Drawing.Point(19, 158);
|
||||
retainAaxFileCbox.Location = new System.Drawing.Point(19, 132);
|
||||
retainAaxFileCbox.Name = "retainAaxFileCbox";
|
||||
retainAaxFileCbox.Size = new System.Drawing.Size(131, 19);
|
||||
retainAaxFileCbox.TabIndex = 10;
|
||||
@@ -1461,7 +1434,7 @@
|
||||
downloadCoverArtCbox.AutoSize = true;
|
||||
downloadCoverArtCbox.Checked = true;
|
||||
downloadCoverArtCbox.CheckState = System.Windows.Forms.CheckState.Checked;
|
||||
downloadCoverArtCbox.Location = new System.Drawing.Point(19, 110);
|
||||
downloadCoverArtCbox.Location = new System.Drawing.Point(19, 84);
|
||||
downloadCoverArtCbox.Name = "downloadCoverArtCbox";
|
||||
downloadCoverArtCbox.Size = new System.Drawing.Size(162, 19);
|
||||
downloadCoverArtCbox.TabIndex = 7;
|
||||
@@ -1474,7 +1447,7 @@
|
||||
createCueSheetCbox.AutoSize = true;
|
||||
createCueSheetCbox.Checked = true;
|
||||
createCueSheetCbox.CheckState = System.Windows.Forms.CheckState.Checked;
|
||||
createCueSheetCbox.Location = new System.Drawing.Point(19, 86);
|
||||
createCueSheetCbox.Location = new System.Drawing.Point(19, 60);
|
||||
createCueSheetCbox.Name = "createCueSheetCbox";
|
||||
createCueSheetCbox.Size = new System.Drawing.Size(145, 19);
|
||||
createCueSheetCbox.TabIndex = 6;
|
||||
@@ -1642,9 +1615,7 @@
|
||||
private System.Windows.Forms.Label gridFontScaleFactorLbl;
|
||||
private System.Windows.Forms.GroupBox groupBox1;
|
||||
private System.Windows.Forms.Button applyDisplaySettingsBtn;
|
||||
private System.Windows.Forms.ComboBox spatialAudioCodecCb;
|
||||
private System.Windows.Forms.CheckBox useWidevineCbox;
|
||||
private System.Windows.Forms.CheckBox requestSpatialCbox;
|
||||
private System.Windows.Forms.CheckBox request_xHE_AAC_Cbox;
|
||||
private DirectoryOrCustomSelectControl inProgressSelectControl;
|
||||
private DirectoryOrCustomSelectControl booksSelectControl;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<root>
|
||||
<!--
|
||||
Microsoft ResX Schema
|
||||
Microsoft ResX Schema
|
||||
|
||||
Version 2.0
|
||||
|
||||
@@ -48,7 +48,7 @@
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
|
||||
mimetype: application/x-microsoft.net.object.soap.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
|
||||
|
||||
@@ -15,7 +15,7 @@ namespace LibationWinForms
|
||||
//GetLibrary_Flat_NoTracking() may take a long time on a hugh library. so run in new thread
|
||||
private async void beginBookBackupsToolStripMenuItem_Click(object _ = null, EventArgs __ = null)
|
||||
{
|
||||
var library = await Task.Run(() => DbContexts.GetLibrary_Flat_NoTracking());
|
||||
var library = await Task.Run(DbContexts.GetUnliberated_Flat_NoTracking);
|
||||
BackupAllBooks(library);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user