commit
51aabe5dd4
@ -90,6 +90,7 @@ namespace AppScaffolding
|
||||
Migrations.migrate_to_v6_6_9(config);
|
||||
Migrations.migrate_to_v11_5_0(config);
|
||||
Migrations.migrate_to_v11_6_5(config);
|
||||
Migrations.migrate_to_v12_0_1(config);
|
||||
}
|
||||
|
||||
/// <summary>Initialize logging. Wire-up events. Run after migration</summary>
|
||||
@ -417,6 +418,82 @@ namespace AppScaffolding
|
||||
public List<string> Filters { get; set; } = new();
|
||||
}
|
||||
|
||||
|
||||
public static void migrate_to_v12_0_1(Configuration config)
|
||||
{
|
||||
#nullable enable
|
||||
//Migrate from version 1 file cache to the dictionary-based version 2 cache
|
||||
const string FILENAME_V1 = "FileLocations.json";
|
||||
const string FILENAME_V2 = "FileLocationsV2.json";
|
||||
|
||||
var jsonFileV1 = Path.Combine(Configuration.Instance.LibationFiles, FILENAME_V1);
|
||||
var jsonFileV2 = Path.Combine(Configuration.Instance.LibationFiles, FILENAME_V2);
|
||||
|
||||
if (!File.Exists(jsonFileV2) && File.Exists(jsonFileV1))
|
||||
{
|
||||
try
|
||||
{
|
||||
//FilePathCache loads the cache in its static constructor,
|
||||
//so perform migration without using FilePathCache.CacheEntry
|
||||
if (JArray.Parse(File.ReadAllText(jsonFileV1)) is not JArray v1Cache || v1Cache.Count == 0)
|
||||
return;
|
||||
|
||||
Dictionary<string, JArray> cache = new();
|
||||
|
||||
//Convert to c# objects to speed up searching by ID inside the iterator
|
||||
var allItems
|
||||
= v1Cache
|
||||
.Select(i => new
|
||||
{
|
||||
Id = i["Id"]?.Value<string>(),
|
||||
Path = i["Path"]?["Path"]?.Value<string>()
|
||||
}).Where(i => i.Id != null)
|
||||
.ToArray();
|
||||
|
||||
foreach (var id in allItems.Select(i => i.Id).OfType<string>().Distinct())
|
||||
{
|
||||
//Use this opportunity to purge non-existent files and re-classify file types
|
||||
//(due to *.aax files previously not being classified as FileType.AAXC)
|
||||
var items = allItems
|
||||
.Where(i => i.Id == id && File.Exists(i.Path))
|
||||
.Select(i => new JObject
|
||||
{
|
||||
{ "Id", i.Id },
|
||||
{ "FileType", (int)FileTypes.GetFileTypeFromPath(i.Path) },
|
||||
{ "Path", new JObject{ { "Path", i.Path } } }
|
||||
})
|
||||
.ToArray();
|
||||
|
||||
if (items.Length == 0)
|
||||
continue;
|
||||
|
||||
cache[id] = new JArray(items);
|
||||
}
|
||||
|
||||
var cacheJson = new JObject { { "Dictionary", JObject.FromObject(cache) } };
|
||||
var cacheFileText = cacheJson.ToString(Formatting.Indented);
|
||||
|
||||
void migrate()
|
||||
{
|
||||
File.WriteAllText(jsonFileV2, cacheFileText);
|
||||
File.Delete(jsonFileV1);
|
||||
}
|
||||
|
||||
try { migrate(); }
|
||||
catch (IOException)
|
||||
{
|
||||
try { migrate(); }
|
||||
catch (IOException)
|
||||
{
|
||||
migrate();
|
||||
}
|
||||
}
|
||||
}
|
||||
catch { /* eat */ }
|
||||
}
|
||||
#nullable restore
|
||||
}
|
||||
|
||||
public static void migrate_to_v11_6_5(Configuration config)
|
||||
{
|
||||
//Settings migration for unsupported sample rates (#1116)
|
||||
|
||||
@ -144,7 +144,7 @@ namespace ApplicationServices
|
||||
PictureId = a.Book.PictureId,
|
||||
IsAbridged = a.Book.IsAbridged,
|
||||
DatePublished = a.Book.DatePublished,
|
||||
CategoriesNames = a.Book.LowestCategoryNames().Any() ? a.Book.LowestCategoryNames().Aggregate((a, b) => $"{a}, {b}") : "",
|
||||
CategoriesNames = string.Join("; ", a.Book.LowestCategoryNames()),
|
||||
MyRatingOverall = a.Book.UserDefinedItem.Rating.OverallRating,
|
||||
MyRatingPerformance = a.Book.UserDefinedItem.Rating.PerformanceRating,
|
||||
MyRatingStory = a.Book.UserDefinedItem.Rating.StoryRating,
|
||||
|
||||
@ -2,6 +2,7 @@
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:local="using:LibationAvalonia"
|
||||
xmlns:controls="using:LibationAvalonia.Controls"
|
||||
xmlns:dialogs="using:LibationAvalonia.Dialogs"
|
||||
x:Class="LibationAvalonia.App"
|
||||
Name="Libation">
|
||||
|
||||
@ -12,6 +13,10 @@
|
||||
<Application.Resources>
|
||||
|
||||
<ResourceDictionary>
|
||||
<ControlTheme x:Key="{x:Type TextBlock}" TargetType="TextBlock">
|
||||
<Setter Property="Foreground" Value="{DynamicResource TextControlForeground}" />
|
||||
<Setter Property="Background" Value="Transparent" />
|
||||
</ControlTheme>
|
||||
<ResourceDictionary.ThemeDictionaries>
|
||||
<ResourceDictionary x:Key="Light">
|
||||
<SolidColorBrush x:Key="SeriesEntryGridBackgroundBrush" Opacity="0.3" Color="#abffab" />
|
||||
@ -81,6 +86,60 @@
|
||||
<!-- It's called AutoHide, but this is really the mouseover shrink/expand. -->
|
||||
<Setter Property="AllowAutoHide" Value="false"/>
|
||||
</Style>
|
||||
|
||||
<Style Selector="dialogs|DialogWindow">
|
||||
<Style Selector="^[UseCustomTitleBar=false]">
|
||||
<Setter Property="SystemDecorations" Value="Full"/>
|
||||
<Setter Property="Template">
|
||||
<ControlTemplate>
|
||||
<ContentPresenter Background="{DynamicResource SystemControlBackgroundAltHighBrush}" Content="{TemplateBinding Content}" />
|
||||
</ControlTemplate>
|
||||
</Setter>
|
||||
</Style>
|
||||
<Style Selector="^[UseCustomTitleBar=true]">
|
||||
<Setter Property="SystemDecorations" Value="BorderOnly"/>
|
||||
<Setter Property="Template">
|
||||
<ControlTemplate>
|
||||
<Panel Background="{DynamicResource SystemControlBackgroundAltHighBrush}">
|
||||
<Grid RowDefinitions="30,*">
|
||||
<Border Name="DialogWindowTitleBorder" Margin="5,0" Background="{DynamicResource SystemAltMediumColor}">
|
||||
<Border.Styles>
|
||||
<Style Selector="Button#DialogCloseButton">
|
||||
<Style Selector="^:pointerover">
|
||||
<Style Selector="^ /template/ ContentPresenter#PART_ContentPresenter">
|
||||
<Setter Property="Background" Value="Red" />
|
||||
</Style>
|
||||
<Style Selector="^ Path">
|
||||
<Setter Property="Fill" Value="{DynamicResource IconFill}" />
|
||||
</Style>
|
||||
</Style>
|
||||
<Style Selector="^:not(:pointerover) /template/ ContentPresenter#PART_ContentPresenter">
|
||||
<Setter Property="Background" Value="Transparent" />
|
||||
<Setter Property="BorderBrush" Value="Transparent" />
|
||||
</Style>
|
||||
</Style>
|
||||
</Border.Styles>
|
||||
|
||||
<Grid ColumnDefinitions="Auto,*,Auto">
|
||||
|
||||
<Path Name="DialogWindowTitleIcon" Margin="3,5,0,5" Fill="{DynamicResource IconFill}" Stretch="Uniform" Data="{StaticResource LibationGlassIcon}"/>
|
||||
|
||||
<TextBlock Name="DialogWindowTitleTextBlock" Margin="8,0,0,0" VerticalAlignment="Center" FontWeight="DemiBold" FontSize="12" Grid.Column="1" Text="{TemplateBinding Title}" />
|
||||
|
||||
<Button Name="DialogCloseButton" Grid.Column="2">
|
||||
<Path Fill="{DynamicResource SystemControlBackgroundBaseLowBrush}" VerticalAlignment="Center" Stretch="Uniform" RenderTransform="{StaticResource Rotate45Transform}" Data="{StaticResource CancelButtonIcon}" />
|
||||
</Button>
|
||||
</Grid>
|
||||
</Border>
|
||||
<Path Stroke="{DynamicResource SystemBaseMediumLowColor}" StrokeThickness="1" VerticalAlignment="Bottom" Stretch="Fill" Data="M0,0 L1,0" />
|
||||
<ContentPresenter Grid.Row="1" Content="{TemplateBinding Content}" />
|
||||
</Grid>
|
||||
</Panel>
|
||||
</ControlTemplate>
|
||||
</Setter>
|
||||
</Style>
|
||||
</Style>
|
||||
|
||||
</Application.Styles>
|
||||
|
||||
<NativeMenu.Menu>
|
||||
|
||||
@ -44,7 +44,7 @@ namespace LibationAvalonia
|
||||
|
||||
if (!config.LibationSettingsAreValid)
|
||||
{
|
||||
var defaultLibationFilesDir = Configuration.UserProfile;
|
||||
var defaultLibationFilesDir = Configuration.DefaultLibationFilesDirectory;
|
||||
|
||||
// check for existing settings in default location
|
||||
var defaultSettingsFile = Path.Combine(defaultLibationFilesDir, "Settings.json");
|
||||
@ -82,8 +82,8 @@ namespace LibationAvalonia
|
||||
// - error message, Exit()
|
||||
if (setupDialog.IsNewUser)
|
||||
{
|
||||
Configuration.SetLibationFiles(Configuration.UserProfile);
|
||||
setupDialog.Config.Books = Path.Combine(Configuration.UserProfile, nameof(Configuration.Books));
|
||||
Configuration.SetLibationFiles(Configuration.DefaultLibationFilesDirectory);
|
||||
setupDialog.Config.Books = Configuration.DefaultBooksDirectory;
|
||||
|
||||
if (setupDialog.Config.LibationSettingsAreValid)
|
||||
{
|
||||
@ -174,7 +174,7 @@ namespace LibationAvalonia
|
||||
|
||||
if (continueResult == DialogResult.Yes)
|
||||
{
|
||||
config.Books = Path.Combine(libationFilesDialog.SelectedDirectory, nameof(Configuration.Books));
|
||||
config.Books = Configuration.DefaultBooksDirectory;
|
||||
|
||||
if (config.LibationSettingsAreValid)
|
||||
{
|
||||
|
||||
@ -91,6 +91,32 @@
|
||||
S 192,128 147,147
|
||||
</StreamGeometry>
|
||||
|
||||
<StreamGeometry x:Key="LibationGlassIcon">
|
||||
M262,8
|
||||
h-117
|
||||
a 192,200 0 0 0 -36,82
|
||||
a 222,334 41 0 0 138,236
|
||||
v158
|
||||
h-81
|
||||
a 16,16 0 0 0 0,32
|
||||
h192
|
||||
a 16 16 0 0 0 0,-32
|
||||
h-81
|
||||
v-158
|
||||
a 222,334 -41 0 0 138,-236
|
||||
a 192,200 0 0 0 -36,-82
|
||||
h-117
|
||||
m-99,30
|
||||
a 192,200 0 0 0 -26,95
|
||||
a 187.5,334 35 0 0 125,159
|
||||
a 187.5,334 -35 0 0 125,-159
|
||||
a 192,200 0 0 0 -26,-95
|
||||
h-198
|
||||
M158,136
|
||||
a 168,305 35 0 0 104,136
|
||||
a 168,305 -35 0 0 104,-136
|
||||
</StreamGeometry>
|
||||
|
||||
</ResourceDictionary>
|
||||
</Styles.Resources>
|
||||
</Styles>
|
||||
|
||||
@ -51,7 +51,9 @@ namespace LibationAvalonia.Controls
|
||||
{
|
||||
Configuration.KnownDirectories.WinTemp,
|
||||
Configuration.KnownDirectories.UserProfile,
|
||||
Configuration.KnownDirectories.ApplicationData,
|
||||
Configuration.KnownDirectories.AppDir,
|
||||
Configuration.KnownDirectories.MyMusic,
|
||||
Configuration.KnownDirectories.MyDocs,
|
||||
Configuration.KnownDirectories.LibationFiles
|
||||
};
|
||||
|
||||
@ -166,16 +166,7 @@
|
||||
MinWidth="80"
|
||||
SelectedItem="{CompiledBinding ThemeVariant, Mode=TwoWay}"
|
||||
ItemsSource="{CompiledBinding Themes}"/>
|
||||
|
||||
<TextBlock
|
||||
Grid.Column="2"
|
||||
FontSize="16"
|
||||
FontWeight="Bold"
|
||||
Margin="10,0"
|
||||
VerticalAlignment="Center"
|
||||
IsVisible="{CompiledBinding SelectionChanged}"
|
||||
Text="Theme change takes effect on restart"/>
|
||||
|
||||
|
||||
</Grid>
|
||||
</Grid>
|
||||
</UserControl>
|
||||
|
||||
@ -1,4 +1,6 @@
|
||||
using Avalonia.Controls;
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Controls.Primitives;
|
||||
using LibationFileManager;
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
@ -9,19 +11,98 @@ namespace LibationAvalonia.Dialogs
|
||||
{
|
||||
public bool SaveAndRestorePosition { get; set; } = true;
|
||||
public Control ControlToFocusOnShow { get; set; }
|
||||
protected override Type StyleKeyOverride => typeof(DialogWindow);
|
||||
|
||||
public static readonly StyledProperty<bool> UseCustomTitleBarProperty =
|
||||
AvaloniaProperty.Register<DialogWindow, bool>(nameof(UseCustomTitleBar));
|
||||
|
||||
public bool UseCustomTitleBar
|
||||
{
|
||||
get { return GetValue(UseCustomTitleBarProperty); }
|
||||
set { SetValue(UseCustomTitleBarProperty, value); }
|
||||
}
|
||||
|
||||
public DialogWindow()
|
||||
{
|
||||
this.HideMinMaxBtns();
|
||||
this.KeyDown += DialogWindow_KeyDown;
|
||||
this.Initialized += DialogWindow_Initialized;
|
||||
this.Opened += DialogWindow_Opened;
|
||||
this.Closing += DialogWindow_Closing;
|
||||
KeyDown += DialogWindow_KeyDown;
|
||||
Initialized += DialogWindow_Initialized;
|
||||
Opened += DialogWindow_Opened;
|
||||
Closing += DialogWindow_Closing;
|
||||
|
||||
UseCustomTitleBar = Configuration.IsWindows;
|
||||
}
|
||||
|
||||
private bool fixedMinHeight = false;
|
||||
private bool fixedMaxHeight = false;
|
||||
private bool fixedHeight = false;
|
||||
|
||||
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
|
||||
{
|
||||
const int customTitleBarHeight = 30;
|
||||
if (UseCustomTitleBar)
|
||||
{
|
||||
if (change.Property == MinHeightProperty && !fixedMinHeight)
|
||||
{
|
||||
fixedMinHeight = true;
|
||||
MinHeight += customTitleBarHeight;
|
||||
fixedMinHeight = false;
|
||||
}
|
||||
if (change.Property == MaxHeightProperty && !fixedMaxHeight)
|
||||
{
|
||||
fixedMaxHeight = true;
|
||||
MaxHeight += customTitleBarHeight;
|
||||
fixedMaxHeight = false;
|
||||
}
|
||||
if (change.Property == HeightProperty && !fixedHeight)
|
||||
{
|
||||
fixedHeight = true;
|
||||
Height += customTitleBarHeight;
|
||||
fixedHeight = false;
|
||||
}
|
||||
}
|
||||
base.OnPropertyChanged(change);
|
||||
}
|
||||
|
||||
public DialogWindow(bool saveAndRestorePosition) : this()
|
||||
{
|
||||
SaveAndRestorePosition = saveAndRestorePosition;
|
||||
}
|
||||
|
||||
protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
|
||||
{
|
||||
base.OnApplyTemplate(e);
|
||||
|
||||
if (!UseCustomTitleBar)
|
||||
return;
|
||||
|
||||
var closeButton = e.NameScope.Find<Button>("DialogCloseButton");
|
||||
var border = e.NameScope.Get<Border>("DialogWindowTitleBorder");
|
||||
var titleBlock = e.NameScope.Get<TextBlock>("DialogWindowTitleTextBlock");
|
||||
var icon = e.NameScope.Get<Avalonia.Controls.Shapes.Path>("DialogWindowTitleIcon");
|
||||
|
||||
closeButton.Click += CloseButton_Click;
|
||||
border.PointerPressed += Border_PointerPressed;
|
||||
icon.IsVisible = Icon != null;
|
||||
|
||||
if (MinHeight == MaxHeight && MinWidth == MaxWidth)
|
||||
{
|
||||
CanResize = false;
|
||||
border.Margin = new Thickness(0);
|
||||
icon.Margin = new Thickness(8, 5, 0, 5);
|
||||
}
|
||||
}
|
||||
|
||||
private void Border_PointerPressed(object sender, Avalonia.Input.PointerPressedEventArgs e)
|
||||
{
|
||||
if (e.GetCurrentPoint(null).Properties.IsLeftButtonPressed)
|
||||
BeginMoveDrag(e);
|
||||
}
|
||||
|
||||
private void CloseButton_Click(object sender, Avalonia.Interactivity.RoutedEventArgs e)
|
||||
{
|
||||
CancelAndClose();
|
||||
}
|
||||
|
||||
private void DialogWindow_Initialized(object sender, EventArgs e)
|
||||
{
|
||||
this.WindowStartupLocation = WindowStartupLocation.CenterOwner;
|
||||
|
||||
@ -25,7 +25,6 @@ namespace LibationAvalonia.Dialogs
|
||||
{
|
||||
InitializeComponent();
|
||||
|
||||
this.HideMinMaxBtns();
|
||||
ControlToFocusOnShow = this.FindControl<Button>(nameof(ImportButton));
|
||||
|
||||
LoadAccounts();
|
||||
|
||||
@ -12,8 +12,6 @@ namespace LibationAvalonia.Dialogs
|
||||
{
|
||||
InitializeComponent();
|
||||
|
||||
this.HideMinMaxBtns();
|
||||
|
||||
StringFields = @"
|
||||
Search for wizard of oz:
|
||||
title:oz
|
||||
|
||||
@ -109,23 +109,5 @@ namespace LibationAvalonia
|
||||
public int Width;
|
||||
public bool IsMaximized;
|
||||
}
|
||||
|
||||
public static void HideMinMaxBtns(this Window form)
|
||||
{
|
||||
if (Design.IsDesignMode || !Configuration.IsWindows || form.TryGetPlatformHandle() is not IPlatformHandle handle)
|
||||
return;
|
||||
|
||||
var currentStyle = GetWindowLong(handle.Handle, GWL_STYLE);
|
||||
|
||||
SetWindowLong(handle.Handle, GWL_STYLE, currentStyle & ~WS_MAXIMIZEBOX & ~WS_MINIMIZEBOX);
|
||||
}
|
||||
|
||||
const long WS_MINIMIZEBOX = 0x00020000L;
|
||||
const long WS_MAXIMIZEBOX = 0x10000L;
|
||||
const int GWL_STYLE = -16;
|
||||
[System.Runtime.InteropServices.DllImport("user32.dll", EntryPoint = "GetWindowLong")]
|
||||
static extern long GetWindowLong(IntPtr hWnd, int nIndex);
|
||||
[System.Runtime.InteropServices.DllImport("user32.dll")]
|
||||
static extern int SetWindowLong(IntPtr hWnd, int nIndex, long dwNewLong);
|
||||
}
|
||||
}
|
||||
|
||||
@ -165,8 +165,6 @@ Libation.
|
||||
|
||||
var dialog = new MessageBoxWindow(saveAndRestorePosition);
|
||||
|
||||
dialog.HideMinMaxBtns();
|
||||
|
||||
var vm = new MessageBoxViewModel(message, caption, buttons, icon, defaultButton);
|
||||
dialog.DataContext = vm;
|
||||
dialog.ControlToFocusOnShow = dialog.FindControl<Control>(defaultButton.ToString());
|
||||
@ -190,11 +188,13 @@ Libation.
|
||||
|
||||
tbx.Height = tbx.DesiredSize.Height;
|
||||
tbx.Width = tbx.DesiredSize.Width;
|
||||
dialog.MinHeight = vm.FormHeightFromTboxHeight((int)tbx.DesiredSize.Height);
|
||||
|
||||
var absoluteHeight = vm.FormHeightFromTboxHeight((int)tbx.DesiredSize.Height);
|
||||
dialog.MinHeight = absoluteHeight;
|
||||
dialog.MinWidth = vm.FormWidthFromTboxWidth((int)tbx.DesiredSize.Width);
|
||||
dialog.MaxHeight = dialog.MinHeight;
|
||||
dialog.MaxHeight = absoluteHeight;
|
||||
dialog.MaxWidth = dialog.MinWidth;
|
||||
dialog.Height = dialog.MinHeight;
|
||||
dialog.Height = absoluteHeight;
|
||||
dialog.Width = dialog.MinWidth;
|
||||
return dialog;
|
||||
}
|
||||
|
||||
@ -4,7 +4,6 @@ using DataLayer;
|
||||
using LibationFileManager;
|
||||
using ReactiveUI;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace LibationAvalonia.ViewModels
|
||||
@ -44,9 +43,6 @@ namespace LibationAvalonia.ViewModels
|
||||
|
||||
private void Configure_BackupCounts()
|
||||
{
|
||||
LibraryCommands.LibrarySizeChanged += async (object _, List<LibraryBook> libraryBooks)
|
||||
=> await SetBackupCountsAsync(libraryBooks);
|
||||
|
||||
//Pass null to the setup count to get the whole library.
|
||||
LibraryCommands.BookUserDefinedItemCommitted += async (_, _)
|
||||
=> await SetBackupCountsAsync(null);
|
||||
|
||||
@ -10,8 +10,8 @@ namespace LibationAvalonia.ViewModels
|
||||
{
|
||||
partial class MainVM
|
||||
{
|
||||
private int _visibleNotLiberated = 1;
|
||||
private int _visibleCount = 1;
|
||||
private int _visibleNotLiberated = 0;
|
||||
private int _visibleCount = 0;
|
||||
|
||||
/// <summary> The Bottom-right visible book count status text </summary>
|
||||
public string VisibleCountText => $"Visible: {_visibleCount}";
|
||||
|
||||
@ -4,6 +4,7 @@ using LibationAvalonia.Views;
|
||||
using LibationFileManager;
|
||||
using ReactiveUI;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace LibationAvalonia.ViewModels
|
||||
{
|
||||
@ -38,7 +39,9 @@ namespace LibationAvalonia.ViewModels
|
||||
|
||||
private async void LibraryCommands_LibrarySizeChanged(object sender, List<LibraryBook> fullLibrary)
|
||||
{
|
||||
await ProductsDisplay.UpdateGridAsync(fullLibrary);
|
||||
await Task.WhenAll(
|
||||
SetBackupCountsAsync(fullLibrary),
|
||||
ProductsDisplay.UpdateGridAsync(fullLibrary));
|
||||
}
|
||||
|
||||
private static string menufyText(string header) => Configuration.IsMacOs ? header : $"_{header}";
|
||||
|
||||
@ -118,7 +118,15 @@ namespace LibationAvalonia.ViewModels
|
||||
#region Add Books to Queue
|
||||
|
||||
private bool isBookInQueue(LibraryBook libraryBook)
|
||||
=> Queue.Any(b => b?.LibraryBook?.Book?.AudibleProductId == libraryBook.Book.AudibleProductId);
|
||||
{
|
||||
var entry = Queue.FirstOrDefault(b => b?.LibraryBook?.Book?.AudibleProductId == libraryBook.Book.AudibleProductId);
|
||||
if (entry == null)
|
||||
return false;
|
||||
else if (entry.Status is ProcessBookStatus.Cancelled or ProcessBookStatus.Failed)
|
||||
return !Queue.RemoveCompleted(entry);
|
||||
else
|
||||
return true;
|
||||
}
|
||||
|
||||
public void AddDownloadPdf(LibraryBook libraryBook)
|
||||
=> AddDownloadPdf(new List<LibraryBook>() { libraryBook });
|
||||
|
||||
@ -21,6 +21,7 @@ namespace LibationAvalonia.ViewModels.Settings
|
||||
public List<Configuration.KnownDirectories> KnownDirectories { get; } = new()
|
||||
{
|
||||
Configuration.KnownDirectories.WinTemp,
|
||||
Configuration.KnownDirectories.ApplicationData,
|
||||
Configuration.KnownDirectories.UserProfile,
|
||||
Configuration.KnownDirectories.AppDir,
|
||||
Configuration.KnownDirectories.MyDocs,
|
||||
|
||||
@ -67,7 +67,8 @@ namespace LibationAvalonia.ViewModels.Settings
|
||||
{
|
||||
Configuration.KnownDirectories.UserProfile,
|
||||
Configuration.KnownDirectories.AppDir,
|
||||
Configuration.KnownDirectories.MyDocs
|
||||
Configuration.KnownDirectories.MyDocs,
|
||||
Configuration.KnownDirectories.MyMusic,
|
||||
};
|
||||
|
||||
public string BooksText { get; } = Configuration.GetDescription(nameof(Configuration.Books));
|
||||
@ -100,11 +101,14 @@ namespace LibationAvalonia.ViewModels.Settings
|
||||
set
|
||||
{
|
||||
this.RaiseAndSetIfChanged(ref themeVariant, value);
|
||||
|
||||
SelectionChanged = ThemeVariant != initialThemeVariant;
|
||||
this.RaisePropertyChanged(nameof(SelectionChanged));
|
||||
App.Current.RequestedThemeVariant = themeVariant switch
|
||||
{
|
||||
nameof(Avalonia.Styling.ThemeVariant.Dark) => Avalonia.Styling.ThemeVariant.Dark,
|
||||
nameof(Avalonia.Styling.ThemeVariant.Light) => Avalonia.Styling.ThemeVariant.Light,
|
||||
// "System"
|
||||
_ => Avalonia.Styling.ThemeVariant.Default
|
||||
};
|
||||
}
|
||||
}
|
||||
public bool SelectionChanged { get; private set; }
|
||||
}
|
||||
}
|
||||
|
||||
@ -8,6 +8,7 @@ using ReactiveUI;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace LibationAvalonia.Views
|
||||
{
|
||||
@ -60,13 +61,14 @@ namespace LibationAvalonia.Views
|
||||
filterSearchTb.Focus();
|
||||
}
|
||||
|
||||
public async System.Threading.Tasks.Task OnLibraryLoadedAsync(List<LibraryBook> initialLibrary)
|
||||
public async Task OnLibraryLoadedAsync(List<LibraryBook> initialLibrary)
|
||||
{
|
||||
if (QuickFilters.UseDefault)
|
||||
await ViewModel.PerformFilter(QuickFilters.Filters.FirstOrDefault());
|
||||
|
||||
await ViewModel.SetBackupCountsAsync(initialLibrary);
|
||||
await ViewModel.ProductsDisplay.BindToGridAsync(initialLibrary);
|
||||
await Task.WhenAll(
|
||||
ViewModel.SetBackupCountsAsync(initialLibrary),
|
||||
ViewModel.ProductsDisplay.BindToGridAsync(initialLibrary));
|
||||
}
|
||||
|
||||
public void ProductsDisplay_LiberateClicked(object _, LibraryBook libraryBook) => ViewModel.LiberateClicked(libraryBook);
|
||||
|
||||
@ -6,7 +6,7 @@
|
||||
xmlns:views="clr-namespace:LibationAvalonia.Views"
|
||||
xmlns:viewModels="clr-namespace:LibationAvalonia.ViewModels"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
mc:Ignorable="d" d:DesignWidth="400" d:DesignHeight="850"
|
||||
mc:Ignorable="d" d:DesignWidth="400" d:DesignHeight="650"
|
||||
x:Class="LibationAvalonia.Views.ProcessQueueControl">
|
||||
|
||||
<UserControl.Resources>
|
||||
@ -39,7 +39,8 @@
|
||||
<ScrollViewer
|
||||
Name="scroller"
|
||||
HorizontalScrollBarVisibility="Disabled"
|
||||
VerticalScrollBarVisibility="Auto">
|
||||
VerticalScrollBarVisibility="Auto"
|
||||
AllowAutoHide="False">
|
||||
<ItemsRepeater IsVisible="True"
|
||||
Grid.Column="0"
|
||||
Name="repeater"
|
||||
|
||||
@ -50,7 +50,7 @@ namespace LibationFileManager
|
||||
get
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(Configuration.Instance.Books))
|
||||
Configuration.Instance.Books = Path.Combine(Configuration.UserProfile, "Books");
|
||||
Configuration.Instance.Books = Configuration.DefaultBooksDirectory;
|
||||
return Directory.CreateDirectory(Configuration.Instance.Books).FullName;
|
||||
}
|
||||
}
|
||||
|
||||
@ -14,8 +14,12 @@ namespace LibationFileManager
|
||||
public static string AppDir_Relative => $@".{Path.PathSeparator}{LIBATION_FILES_KEY}";
|
||||
public static string AppDir_Absolute => Path.GetFullPath(Path.Combine(ProcessDirectory, LIBATION_FILES_KEY));
|
||||
public static string MyDocs => Path.GetFullPath(Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments), "Libation"));
|
||||
public static string MyMusic => Path.GetFullPath(Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyMusic), "Libation"));
|
||||
public static string WinTemp => Path.GetFullPath(Path.Combine(Path.GetTempPath(), "Libation"));
|
||||
public static string UserProfile => Path.GetFullPath(Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), "Libation"));
|
||||
public static string LocalAppData => Path.GetFullPath(Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "Libation"));
|
||||
public static string DefaultLibationFilesDirectory => !IsWindows ? LocalAppData : UserProfile;
|
||||
public static string DefaultBooksDirectory => Path.Combine(!IsWindows ? MyMusic : UserProfile, nameof(Books));
|
||||
|
||||
public enum KnownDirectories
|
||||
{
|
||||
@ -34,19 +38,27 @@ namespace LibationFileManager
|
||||
MyDocs = 4,
|
||||
|
||||
[Description("Your settings folder (aka: Libation Files)")]
|
||||
LibationFiles = 5
|
||||
}
|
||||
// use func calls so we always get the latest value of LibationFiles
|
||||
private static List<(KnownDirectories directory, Func<string?> getPathFunc)> directoryOptionsPaths { get; } = new()
|
||||
LibationFiles = 5,
|
||||
|
||||
[Description("User Application Data Folder")]
|
||||
ApplicationData = 6,
|
||||
|
||||
[Description("My Music")]
|
||||
MyMusic = 7,
|
||||
}
|
||||
// use func calls so we always get the latest value of LibationFiles
|
||||
private static List<(KnownDirectories directory, Func<string?> getPathFunc)> directoryOptionsPaths { get; } = new()
|
||||
{
|
||||
(KnownDirectories.None, () => null),
|
||||
(KnownDirectories.ApplicationData, () => LocalAppData),
|
||||
(KnownDirectories.MyMusic, () => MyMusic),
|
||||
(KnownDirectories.UserProfile, () => UserProfile),
|
||||
(KnownDirectories.AppDir, () => AppDir_Relative),
|
||||
(KnownDirectories.WinTemp, () => WinTemp),
|
||||
(KnownDirectories.MyDocs, () => MyDocs),
|
||||
// this is important to not let very early calls try to accidentally load LibationFiles too early.
|
||||
// also, keep this at bottom of this list
|
||||
(KnownDirectories.LibationFiles, () => libationFilesPathCache)
|
||||
(KnownDirectories.LibationFiles, () => LibationSettingsDirectory)
|
||||
};
|
||||
public static string? GetKnownDirectoryPath(KnownDirectories directory)
|
||||
{
|
||||
|
||||
@ -22,11 +22,11 @@ namespace LibationFileManager
|
||||
{
|
||||
get
|
||||
{
|
||||
if (libationFilesPathCache is not null)
|
||||
return libationFilesPathCache;
|
||||
if (LibationSettingsDirectory is not null)
|
||||
return LibationSettingsDirectory;
|
||||
|
||||
// FIRST: must write here before SettingsFilePath in next step reads cache
|
||||
libationFilesPathCache = getLibationFilesSettingFromJson();
|
||||
LibationSettingsDirectory = getLibationFilesSettingFromJson();
|
||||
|
||||
// SECOND. before setting to json file with SetWithJsonPath, PersistentDictionary must exist
|
||||
persistentDictionary = new PersistentDictionary(SettingsFilePath);
|
||||
@ -42,11 +42,14 @@ namespace LibationFileManager
|
||||
|
||||
SetWithJsonPath(jsonpath, "path", logPath, true);
|
||||
|
||||
return libationFilesPathCache;
|
||||
return LibationSettingsDirectory;
|
||||
}
|
||||
}
|
||||
|
||||
private static string? libationFilesPathCache { get; set; }
|
||||
/// <summary>
|
||||
/// Directory pointed to by appsettings.json
|
||||
/// </summary>
|
||||
private static string? LibationSettingsDirectory { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Try to find appsettings.json in the following locations:
|
||||
@ -79,7 +82,7 @@ namespace LibationFileManager
|
||||
string[] possibleAppsettingsDirectories = new[]
|
||||
{
|
||||
ProcessDirectory,
|
||||
Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "Libation"),
|
||||
LocalAppData,
|
||||
UserProfile,
|
||||
Path.Combine(Path.GetTempPath(), "Libation")
|
||||
};
|
||||
@ -106,9 +109,15 @@ namespace LibationFileManager
|
||||
}
|
||||
|
||||
//Valid appsettings.json not found. Try to create it in each folder.
|
||||
var endingContents = new JObject { { LIBATION_FILES_KEY, UserProfile } }.ToString(Formatting.Indented);
|
||||
var endingContents = new JObject { { LIBATION_FILES_KEY, DefaultLibationFilesDirectory } }.ToString(Formatting.Indented);
|
||||
|
||||
foreach (var dir in possibleAppsettingsDirectories)
|
||||
{
|
||||
//Don't try to create appsettings.json in the program files directory on *.nix systems.
|
||||
//However, still _look_ for one there for backwards compatibility with previous installations
|
||||
if (!IsWindows && dir == ProcessDirectory)
|
||||
continue;
|
||||
|
||||
var appsettingsFile = Path.Combine(dir, appsettings_filename);
|
||||
|
||||
try
|
||||
@ -180,7 +189,7 @@ namespace LibationFileManager
|
||||
|
||||
public static void SetLibationFiles(string directory)
|
||||
{
|
||||
libationFilesPathCache = null;
|
||||
LibationSettingsDirectory = null;
|
||||
|
||||
var startingContents = File.ReadAllText(AppsettingsJsonFile);
|
||||
var jObj = JObject.Parse(startingContents);
|
||||
|
||||
@ -18,9 +18,8 @@ namespace LibationFileManager
|
||||
|
||||
var pDic = new PersistentDictionary(settingsFile, isReadOnly: false);
|
||||
|
||||
var booksDir = pDic.GetString(nameof(Books));
|
||||
|
||||
if (booksDir is null) return false;
|
||||
if (pDic.GetString(nameof(Books)) is not string booksDir)
|
||||
return false;
|
||||
|
||||
if (!Directory.Exists(booksDir))
|
||||
{
|
||||
@ -28,17 +27,21 @@ namespace LibationFileManager
|
||||
throw new DirectoryNotFoundException(settingsFile);
|
||||
|
||||
//"Books" is not null, so setup has already been run.
|
||||
//Since Books can't be found, try to create it in Libation settings folder
|
||||
booksDir = Path.Combine(dir, nameof(Books));
|
||||
try
|
||||
//Since Books can't be found, try to create it
|
||||
//and then revert to the default books directory
|
||||
foreach (string d in new string[] { booksDir, DefaultBooksDirectory })
|
||||
{
|
||||
Directory.CreateDirectory(booksDir);
|
||||
try
|
||||
{
|
||||
Directory.CreateDirectory(d);
|
||||
|
||||
pDic.SetString(nameof(Books), booksDir);
|
||||
pDic.SetString(nameof(Books), d);
|
||||
|
||||
return booksDir is not null && Directory.Exists(booksDir);
|
||||
return Directory.Exists(d);
|
||||
}
|
||||
catch { /* Do Nothing */ }
|
||||
}
|
||||
catch { return false; }
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
@ -1,8 +1,8 @@
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using Dinah.Core.Collections.Immutable;
|
||||
using FileManager;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
@ -13,78 +13,96 @@ namespace LibationFileManager
|
||||
{
|
||||
public record CacheEntry(string Id, FileType FileType, LongPath Path);
|
||||
|
||||
private const string FILENAME = "FileLocations.json";
|
||||
private const string FILENAME_V2 = "FileLocationsV2.json";
|
||||
|
||||
public static event EventHandler<CacheEntry>? Inserted;
|
||||
public static event EventHandler<CacheEntry>? Removed;
|
||||
|
||||
private static Cache<CacheEntry> cache { get; } = new Cache<CacheEntry>();
|
||||
private static LongPath jsonFileV2 => Path.Combine(Configuration.Instance.LibationFiles, FILENAME_V2);
|
||||
|
||||
private static LongPath jsonFile => Path.Combine(Configuration.Instance.LibationFiles, FILENAME);
|
||||
private static readonly FileCacheV2<CacheEntry> Cache = new();
|
||||
|
||||
static FilePathCache()
|
||||
{
|
||||
static FilePathCache()
|
||||
{
|
||||
// load json into memory. if file doesn't exist, nothing to do. save() will create if needed
|
||||
if (!File.Exists(jsonFile))
|
||||
if (!File.Exists(jsonFileV2))
|
||||
return;
|
||||
|
||||
try
|
||||
{
|
||||
var list = JsonConvert.DeserializeObject<List<CacheEntry>>(File.ReadAllText(jsonFile));
|
||||
if (list is null)
|
||||
throw new NullReferenceException("File exists but deserialize is null. This will never happen when file is healthy.");
|
||||
|
||||
cache = new Cache<CacheEntry>(list);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Serilog.Log.Logger.Error(ex, "Error deserializing file. Wrong format. Possibly corrupt. Deleting file. {@DebugInfo}", new { jsonFile });
|
||||
lock (locker)
|
||||
File.Delete(jsonFile);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
public static bool Exists(string id, FileType type) => GetFirstPath(id, type) is not null;
|
||||
|
||||
public static List<(FileType fileType, LongPath path)> GetFiles(string id)
|
||||
=> getEntries(entry => entry.Id == id)
|
||||
.Select(entry => (entry.FileType, entry.Path))
|
||||
.ToList();
|
||||
|
||||
public static LongPath? GetFirstPath(string id, FileType type)
|
||||
=> getEntries(entry => entry.Id == id && entry.FileType == type)
|
||||
?.FirstOrDefault()
|
||||
?.Path;
|
||||
|
||||
private static IEnumerable<CacheEntry> getEntries(Func<CacheEntry, bool> predicate)
|
||||
{
|
||||
var entries = cache.Where(predicate).ToList();
|
||||
if (entries is null || !entries.Any())
|
||||
return Enumerable.Empty<CacheEntry>();
|
||||
|
||||
remove(entries.Where(e => !File.Exists(e.Path)).ToList());
|
||||
|
||||
return cache.Where(predicate).ToList();
|
||||
}
|
||||
|
||||
private static void remove(List<CacheEntry> entries)
|
||||
{
|
||||
if (entries is null)
|
||||
return;
|
||||
|
||||
lock (locker)
|
||||
try
|
||||
{
|
||||
foreach (var entry in entries)
|
||||
{
|
||||
cache.Remove(entry);
|
||||
Removed?.Invoke(null, entry);
|
||||
}
|
||||
save();
|
||||
Cache = JsonConvert.DeserializeObject<FileCacheV2<CacheEntry>>(File.ReadAllText(jsonFileV2))
|
||||
?? throw new NullReferenceException("File exists but deserialize is null. This will never happen when file is healthy.");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Serilog.Log.Logger.Error(ex, "Error deserializing file. Wrong format. Possibly corrupt. Deleting file. {@DebugInfo}", new { jsonFileV2 });
|
||||
lock (locker)
|
||||
File.Delete(jsonFileV2);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
public static void Insert(string id, string path)
|
||||
public static bool Exists(string id, FileType type) => GetFirstPath(id, type) is not null;
|
||||
|
||||
public static List<(FileType fileType, LongPath path)> GetFiles(string id)
|
||||
{
|
||||
var matchingFiles = Cache.GetIdEntries(id);
|
||||
|
||||
bool cacheChanged = false;
|
||||
|
||||
//Verify all entries exist
|
||||
for (int i = 0; i < matchingFiles.Count; i++)
|
||||
{
|
||||
if (!File.Exists(matchingFiles[i].Path))
|
||||
{
|
||||
matchingFiles.RemoveAt(i);
|
||||
cacheChanged |= Remove(matchingFiles[i]);
|
||||
}
|
||||
}
|
||||
if (cacheChanged)
|
||||
save();
|
||||
|
||||
return matchingFiles.Select(e => (e.FileType, e.Path)).ToList();
|
||||
}
|
||||
|
||||
public static LongPath? GetFirstPath(string id, FileType type)
|
||||
{
|
||||
var matchingFiles = Cache.GetIdEntries(id).Where(e => e.FileType == type).ToList();
|
||||
|
||||
bool cacheChanged = false;
|
||||
try
|
||||
{
|
||||
//Verify entries exist, but return first matching 'type'
|
||||
for (int i = 0; i < matchingFiles.Count; i++)
|
||||
{
|
||||
if (File.Exists(matchingFiles[i].Path))
|
||||
return matchingFiles[i].Path;
|
||||
else
|
||||
{
|
||||
matchingFiles.RemoveAt(i);
|
||||
cacheChanged |= Remove(matchingFiles[i]);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (cacheChanged)
|
||||
save();
|
||||
}
|
||||
}
|
||||
|
||||
private static bool Remove(CacheEntry entry)
|
||||
{
|
||||
if (Cache.Remove(entry.Id, entry))
|
||||
{
|
||||
Removed?.Invoke(null, entry);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public static void Insert(string id, string path)
|
||||
{
|
||||
var type = FileTypes.GetFileTypeFromPath(path);
|
||||
Insert(new CacheEntry(id, type, path));
|
||||
@ -92,7 +110,7 @@ namespace LibationFileManager
|
||||
|
||||
public static void Insert(CacheEntry entry)
|
||||
{
|
||||
cache.Add(entry);
|
||||
Cache.Add(entry.Id, entry);
|
||||
Inserted?.Invoke(null, entry);
|
||||
save();
|
||||
}
|
||||
@ -102,7 +120,7 @@ namespace LibationFileManager
|
||||
private static void save()
|
||||
{
|
||||
// create json if not exists
|
||||
static void resave() => File.WriteAllText(jsonFile, JsonConvert.SerializeObject(cache.ToList(), Formatting.Indented));
|
||||
static void resave() => File.WriteAllText(jsonFileV2, JsonConvert.SerializeObject(Cache, Formatting.Indented));
|
||||
|
||||
lock (locker)
|
||||
{
|
||||
@ -112,11 +130,41 @@ namespace LibationFileManager
|
||||
try { resave(); }
|
||||
catch (IOException ex)
|
||||
{
|
||||
Serilog.Log.Logger.Error(ex, $"Error saving {FILENAME}");
|
||||
Serilog.Log.Logger.Error(ex, $"Error saving {FILENAME_V2}");
|
||||
throw;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class FileCacheV2<TEntry>
|
||||
{
|
||||
[JsonProperty]
|
||||
private readonly ConcurrentDictionary<string, List<TEntry>> Dictionary = new();
|
||||
|
||||
public List<TEntry> GetIdEntries(string id)
|
||||
{
|
||||
static List<TEntry> empty() => new();
|
||||
|
||||
return Dictionary.TryGetValue(id, out var entries) ? entries.ToList() : empty();
|
||||
}
|
||||
|
||||
public void Add(string id, TEntry entry)
|
||||
{
|
||||
Dictionary.AddOrUpdate(id, [entry], (id, entries) => { entries.Add(entry); return entries; });
|
||||
}
|
||||
|
||||
public void AddRange(string id, IEnumerable<TEntry> entries)
|
||||
{
|
||||
Dictionary.AddOrUpdate(id, entries.ToList(), (id, entries) =>
|
||||
{
|
||||
entries.AddRange(entries);
|
||||
return entries;
|
||||
});
|
||||
}
|
||||
|
||||
public bool Remove(string id, TEntry entry)
|
||||
=> Dictionary.TryGetValue(id, out List<TEntry>? entries) && entries.Remove(entry);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -11,6 +11,7 @@ namespace LibationFileManager
|
||||
{
|
||||
private static Dictionary<string, FileType> dic => new()
|
||||
{
|
||||
["aax"] = FileType.AAXC,
|
||||
["aaxc"] = FileType.AAXC,
|
||||
["cue"] = FileType.Cue,
|
||||
["pdf"] = FileType.PDF,
|
||||
|
||||
@ -169,6 +169,16 @@ namespace LibationUiBase
|
||||
}
|
||||
}
|
||||
|
||||
public T FirstOrDefault(Func<T, bool> predicate)
|
||||
{
|
||||
lock (lockObject)
|
||||
{
|
||||
return Current != null && predicate(Current) ? Current
|
||||
: _completed.FirstOrDefault(predicate) is T completed ? completed
|
||||
: _queued.FirstOrDefault(predicate);
|
||||
}
|
||||
}
|
||||
|
||||
public void MoveQueuePosition(T item, QueuePosition requestedPosition)
|
||||
{
|
||||
lock (lockObject)
|
||||
|
||||
@ -37,6 +37,7 @@ namespace LibationWinForms.Dialogs
|
||||
inProgressSelectControl.SetDirectoryItems(new()
|
||||
{
|
||||
Configuration.KnownDirectories.WinTemp,
|
||||
Configuration.KnownDirectories.ApplicationData,
|
||||
Configuration.KnownDirectories.UserProfile,
|
||||
Configuration.KnownDirectories.AppDir,
|
||||
Configuration.KnownDirectories.MyDocs,
|
||||
|
||||
@ -44,7 +44,8 @@ namespace LibationWinForms.Dialogs
|
||||
{
|
||||
Configuration.KnownDirectories.UserProfile,
|
||||
Configuration.KnownDirectories.AppDir,
|
||||
Configuration.KnownDirectories.MyDocs
|
||||
Configuration.KnownDirectories.MyDocs,
|
||||
Configuration.KnownDirectories.MyMusic,
|
||||
},
|
||||
Configuration.KnownDirectories.UserProfile,
|
||||
"Books");
|
||||
|
||||
@ -105,13 +105,14 @@ namespace LibationWinForms
|
||||
splitContainer1.Panel2Collapsed = false;
|
||||
processBookQueue1.popoutBtn.Visible = true;
|
||||
}
|
||||
|
||||
Configuration.Instance.SetNonString(splitContainer1.Panel2Collapsed, nameof(splitContainer1.Panel2Collapsed));
|
||||
toggleQueueHideBtn.Text = splitContainer1.Panel2Collapsed ? "❰❰❰" : "❱❱❱";
|
||||
}
|
||||
|
||||
private void ToggleQueueHideBtn_Click(object sender, EventArgs e)
|
||||
{
|
||||
SetQueueCollapseState(!splitContainer1.Panel2Collapsed);
|
||||
Configuration.Instance.SetNonString(splitContainer1.Panel2Collapsed, nameof(splitContainer1.Panel2Collapsed));
|
||||
}
|
||||
|
||||
private void ProcessBookQueue1_PopOut(object sender, EventArgs e)
|
||||
|
||||
@ -17,7 +17,7 @@ namespace LibationWinForms
|
||||
//Set this size before restoring form size and position
|
||||
splitContainer1.Panel2MinSize = this.DpiScale(350);
|
||||
this.RestoreSizeAndLocation(Configuration.Instance);
|
||||
this.FormClosing += (_, _) => this.SaveSizeAndLocation(Configuration.Instance);
|
||||
FormClosing += Form1_FormClosing;
|
||||
|
||||
// this looks like a perfect opportunity to refactor per below.
|
||||
// since this loses design-time tooling and internal access, for now I'm opting for partial classes
|
||||
@ -58,6 +58,14 @@ namespace LibationWinForms
|
||||
Shown += Form1_Shown;
|
||||
}
|
||||
|
||||
private void Form1_FormClosing(object sender, FormClosingEventArgs e)
|
||||
{
|
||||
//Always close the queue before saving the form to prevent
|
||||
//Form1 from getting excessively wide when it's restored.
|
||||
SetQueueCollapseState(true);
|
||||
this.SaveSizeAndLocation(Configuration.Instance);
|
||||
}
|
||||
|
||||
private async void Form1_Shown(object sender, EventArgs e)
|
||||
{
|
||||
if (Configuration.Instance.FirstLaunch)
|
||||
|
||||
@ -82,7 +82,15 @@ namespace LibationWinForms.ProcessQueue
|
||||
}
|
||||
|
||||
private bool isBookInQueue(DataLayer.LibraryBook libraryBook)
|
||||
=> Queue.Any(b => b?.LibraryBook?.Book?.AudibleProductId == libraryBook.Book.AudibleProductId);
|
||||
{
|
||||
var entry = Queue.FirstOrDefault(b => b?.LibraryBook?.Book?.AudibleProductId == libraryBook.Book.AudibleProductId);
|
||||
if (entry == null)
|
||||
return false;
|
||||
else if (entry.Status is ProcessBookStatus.Cancelled or ProcessBookStatus.Failed)
|
||||
return !Queue.RemoveCompleted(entry);
|
||||
else
|
||||
return true;
|
||||
}
|
||||
|
||||
public void AddDownloadPdf(DataLayer.LibraryBook libraryBook)
|
||||
=> AddDownloadPdf(new List<DataLayer.LibraryBook>() { libraryBook });
|
||||
|
||||
@ -98,7 +98,7 @@ namespace LibationWinForms
|
||||
if (config.LibationSettingsAreValid)
|
||||
return;
|
||||
|
||||
var defaultLibationFilesDir = Configuration.UserProfile;
|
||||
var defaultLibationFilesDir = Configuration.DefaultLibationFilesDirectory;
|
||||
|
||||
// check for existing settings in default location
|
||||
var defaultSettingsFile = Path.Combine(defaultLibationFilesDir, "Settings.json");
|
||||
@ -154,7 +154,7 @@ namespace LibationWinForms
|
||||
|
||||
// INIT DEFAULT SETTINGS
|
||||
// if 'new user' was clicked, or if 'returning user' chose new install: show basic settings dialog
|
||||
config.Books ??= Path.Combine(defaultLibationFilesDir, "Books");
|
||||
config.Books ??= Configuration.DefaultBooksDirectory;
|
||||
|
||||
if (config.LibationSettingsAreValid)
|
||||
return;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user