mirror of
https://github.com/rmcrackan/Libation.git
synced 2026-01-09 22:38:53 -05:00
Compare commits
11 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f4e7cf3418 | ||
|
|
8492a7ea3a | ||
|
|
5589a6cbd5 | ||
|
|
bfbad988c0 | ||
|
|
8db8615713 | ||
|
|
e8fa3f14b3 | ||
|
|
71552f2417 | ||
|
|
5b96f96a80 | ||
|
|
afffeb953c | ||
|
|
f4d8685058 | ||
|
|
39e9f675d2 |
2
.github/workflows/build-linux.yml
vendored
2
.github/workflows/build-linux.yml
vendored
@@ -124,7 +124,7 @@ jobs:
|
||||
echo "artifact=${artifact}" >> "${GITHUB_OUTPUT}"
|
||||
|
||||
- name: Publish bundle
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v5
|
||||
with:
|
||||
name: ${{ steps.bundle.outputs.artifact }}
|
||||
path: ./Source/bin/Publish/bundle/${{ steps.bundle.outputs.artifact }}
|
||||
|
||||
2
.github/workflows/build-windows.yml
vendored
2
.github/workflows/build-windows.yml
vendored
@@ -110,7 +110,7 @@ jobs:
|
||||
Compress-Archive -Path "${bin_dir}*" -DestinationPath "$artifact.zip"
|
||||
|
||||
- name: Publish artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v5
|
||||
with:
|
||||
name: ${{ steps.zip.outputs.artifact }}.zip
|
||||
path: ./Source/bin/Publish/${{ steps.zip.outputs.artifact }}.zip
|
||||
|
||||
2
.github/workflows/release.yml
vendored
2
.github/workflows/release.yml
vendored
@@ -40,7 +40,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Download artifacts
|
||||
uses: actions/download-artifact@v5
|
||||
uses: actions/download-artifact@v6
|
||||
with:
|
||||
path: artifacts
|
||||
pattern: "*(Classic-)Libation.*"
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net9.0</TargetFramework>
|
||||
<Version>12.5.5.1</Version>
|
||||
<Version>12.5.7.1</Version>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Octokit" Version="14.0.0" />
|
||||
|
||||
@@ -9,7 +9,7 @@ namespace ApplicationServices
|
||||
{
|
||||
/// <summary>Use for fully functional context, incl. SaveChanges(). For query-only, use the other method</summary>
|
||||
public static LibationContext GetContext()
|
||||
=> LibationContext.Create(SqliteStorage.ConnectionString);
|
||||
=> InstanceQueue<LibationContext>.WaitToCreateInstance(() => LibationContext.Create(SqliteStorage.ConnectionString));
|
||||
|
||||
/// <summary>Use for full library querying. No lazy loading</summary>
|
||||
public static List<LibraryBook> GetLibrary_Flat_NoTracking(bool includeParents = false)
|
||||
|
||||
103
Source/DataLayer/InstanceQueue.cs
Normal file
103
Source/DataLayer/InstanceQueue.cs
Normal file
@@ -0,0 +1,103 @@
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Threading;
|
||||
|
||||
#nullable enable
|
||||
namespace DataLayer;
|
||||
|
||||
/// <summary> Notifies clients that the object is being disposed. </summary>
|
||||
public interface INotifyDisposed : IDisposable
|
||||
{
|
||||
/// <summary> Event raised when the object is disposed. </summary>
|
||||
event EventHandler? ObjectDisposed;
|
||||
}
|
||||
|
||||
/// <summary> Creates a single instance of <typeparamref name="TDisposable"/> at a time, blocking subsequent creations until the previous creations are disposed. </summary>
|
||||
public static class InstanceQueue<TDisposable> where TDisposable : INotifyDisposed
|
||||
{
|
||||
/// <summary> Synchronization object for access to <see cref="LastInLine"/>"/> </summary>
|
||||
private static Lock Locker { get; } = new();
|
||||
/// <summary> Ticket holder for the last instance creator in line. </summary>
|
||||
private static Ticket? LastInLine { get; set; }
|
||||
|
||||
/// <summary> Waits for all previously created instances of <typeparamref name="TDisposable"/> to be disposed, then creates and returns a new instance of <typeparamref name="TDisposable"/> using the provided <paramref name="creator"/> factory. </summary>
|
||||
public static TDisposable WaitToCreateInstance(Func<TDisposable> creator)
|
||||
{
|
||||
Ticket ticket;
|
||||
lock (Locker)
|
||||
{
|
||||
ticket = LastInLine = new Ticket(creator, LastInLine);
|
||||
}
|
||||
|
||||
return ticket.Fulfill();
|
||||
}
|
||||
|
||||
/// <summary> A queue ticket for an instance creator. </summary>
|
||||
/// <param name="creator">Factory to create a new instance of <typeparamref name="TDisposable"/></param>
|
||||
/// <param name="inFront">The ticket immediately in preceding this new ticket. This new ticket must wait for <paramref name="inFront"/> to signal its instance has been disposed before creating a new instance of <typeparamref name="TDisposable"/></param>
|
||||
private class Ticket(Func<TDisposable> creator, Ticket? inFront) : IDisposable
|
||||
{
|
||||
/// <summary> Factory to create a new instance of <typeparamref name="TDisposable"/> </summary>
|
||||
private Func<TDisposable> Creator { get; } = creator;
|
||||
/// <summary> Ticket immediately in front of this one. This instance must wait for <see cref="InFront"/> to signal its instance has been disposed before creating a new instance of <typeparamref name="TDisposable"/></summary>
|
||||
private Ticket? InFront { get; } = inFront;
|
||||
/// <summary> Wait handle to signal when this ticket's created instance is disposed </summary>
|
||||
private EventWaitHandle WaitHandle { get; } = new(false, EventResetMode.ManualReset);
|
||||
/// <summary> This ticket's created instance of <typeparamref name="TDisposable"/> </summary>
|
||||
private TDisposable? CreatedInstance { get; set; }
|
||||
|
||||
/// <summary> Disposes of this ticket and every ticket queued in front of it. </summary>
|
||||
public void Dispose()
|
||||
{
|
||||
WaitHandle.Dispose();
|
||||
InFront?.Dispose();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Waits for the <see cref="InFront"/> ticket's instance to be disposed, then creates and returns a new instance of <typeparamref name="TDisposable"/> using the <see cref="Creator"/> factory.
|
||||
/// </summary>
|
||||
public TDisposable Fulfill()
|
||||
{
|
||||
#if DEBUG
|
||||
var sw = Stopwatch.StartNew();
|
||||
#endif
|
||||
//Wait for the previous ticket's instance to be disposed, then dispose of the previous ticket.
|
||||
InFront?.WaitHandle.WaitOne(Timeout.Infinite);
|
||||
InFront?.Dispose();
|
||||
#if DEBUG
|
||||
sw.Stop();
|
||||
Debug.WriteLine($"Waited {sw.ElapsedMilliseconds}ms to create instance of {typeof(TDisposable).Name}");
|
||||
#endif
|
||||
CreatedInstance = Creator();
|
||||
CreatedInstance.ObjectDisposed += CreatedInstance_ObjectDisposed;
|
||||
return CreatedInstance;
|
||||
}
|
||||
|
||||
private void CreatedInstance_ObjectDisposed(object? sender, EventArgs e)
|
||||
{
|
||||
Debug.WriteLine($"{typeof(TDisposable).Name} Disposed");
|
||||
if (CreatedInstance is not null)
|
||||
{
|
||||
CreatedInstance.ObjectDisposed -= CreatedInstance_ObjectDisposed;
|
||||
CreatedInstance = default;
|
||||
}
|
||||
|
||||
lock (Locker)
|
||||
{
|
||||
if (this == LastInLine)
|
||||
{
|
||||
//There are no ticket holders waiting after this one.
|
||||
//This ticket is fulfilled and will never be waited on.
|
||||
LastInLine = null;
|
||||
Dispose();
|
||||
}
|
||||
else
|
||||
{
|
||||
//Signal the that this ticket has been fulfilled so that
|
||||
//the next ticket in line may proceed.
|
||||
WaitHandle.Set();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,11 @@
|
||||
using DataLayer.Configurations;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace DataLayer
|
||||
{
|
||||
public class LibationContext : DbContext
|
||||
public class LibationContext : DbContext, INotifyDisposed
|
||||
{
|
||||
// IMPORTANT: USING DbSet<>
|
||||
// ========================
|
||||
@@ -25,6 +27,18 @@ namespace DataLayer
|
||||
public DbSet<Category> Categories { get; private set; }
|
||||
public DbSet<CategoryLadder> CategoryLadders { get; private set; }
|
||||
|
||||
public event EventHandler ObjectDisposed;
|
||||
public override void Dispose()
|
||||
{
|
||||
base.Dispose();
|
||||
ObjectDisposed?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
public override async ValueTask DisposeAsync()
|
||||
{
|
||||
await base.DisposeAsync();
|
||||
ObjectDisposed?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
|
||||
public static LibationContext Create(string connectionString)
|
||||
{
|
||||
var factory = new LibationContextFactory();
|
||||
|
||||
@@ -122,7 +122,8 @@ namespace LibationAvalonia.Dialogs
|
||||
|
||||
private void Reload()
|
||||
{
|
||||
var deletedBooks = DbContexts.GetContext().GetDeletedLibraryBooks();
|
||||
using var context = DbContexts.GetContext();
|
||||
var deletedBooks = context.GetDeletedLibraryBooks();
|
||||
|
||||
DeletedBooks.Clear();
|
||||
DeletedBooks.AddRange(deletedBooks.Select(lb => new CheckBoxViewModel { Item = lb }));
|
||||
|
||||
@@ -12,6 +12,7 @@ namespace LibationAvalonia.ViewModels
|
||||
{
|
||||
public partial class MainVM : ViewModelBase
|
||||
{
|
||||
public Task? BindToGridTask { get; set; }
|
||||
public ProcessQueueViewModel ProcessQueue { get; } = new ProcessQueueViewModel();
|
||||
public ProductsDisplayViewModel ProductsDisplay { get; } = new ProductsDisplayViewModel();
|
||||
|
||||
@@ -43,6 +44,13 @@ namespace LibationAvalonia.ViewModels
|
||||
{
|
||||
try
|
||||
{
|
||||
//Prevent race condition which can occur if an auto-scan
|
||||
//completes before the initial grid binding completes.
|
||||
if (BindToGridTask is null)
|
||||
return;
|
||||
else if (BindToGridTask.IsCompleted is false)
|
||||
await BindToGridTask;
|
||||
|
||||
await Task.WhenAll(
|
||||
SetBackupCountsAsync(fullLibrary),
|
||||
Task.Run(() => ProductsDisplay.UpdateGridAsync(fullLibrary)));
|
||||
|
||||
@@ -165,9 +165,10 @@ namespace LibationAvalonia.Views
|
||||
if (QuickFilters.UseDefault)
|
||||
await vm.PerformFilter(QuickFilters.Filters.FirstOrDefault());
|
||||
|
||||
await Task.WhenAll(
|
||||
ViewModel.BindToGridTask = Task.WhenAll(
|
||||
vm.SetBackupCountsAsync(initialLibrary),
|
||||
Task.Run(() => vm.ProductsDisplay.BindToGridAsync(initialLibrary)));
|
||||
await ViewModel.BindToGridTask;
|
||||
}
|
||||
|
||||
public void ProductsDisplay_LiberateClicked(object _, LibraryBook[] libraryBook) => ViewModel.LiberateClicked(libraryBook);
|
||||
|
||||
@@ -23,7 +23,8 @@ namespace LibationWinForms.Dialogs
|
||||
|
||||
deletedCheckedTemplate = deletedCheckedLbl.Text;
|
||||
|
||||
var deletedBooks = DbContexts.GetContext().GetDeletedLibraryBooks();
|
||||
using var context = DbContexts.GetContext();
|
||||
var deletedBooks = context.GetDeletedLibraryBooks();
|
||||
foreach (var lb in deletedBooks)
|
||||
deletedCbl.Items.Add(lb);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user