Replace ReactiveUI

This commit is contained in:
Jöran Malek 2024-05-02 21:44:13 +02:00
parent 43b4d50e43
commit 5584ab4ec8
41 changed files with 472 additions and 1013 deletions

View file

@ -1,61 +1,12 @@
using System.Reactive.Linq;
using System.Reactive.Subjects;
using ReactiveUI;
using CommunityToolkit.Mvvm.ComponentModel;
namespace InkForge.Desktop.Models;
public class Note : ReactiveObject
public partial class Note : ObservableObject
{
private readonly ObservableAsPropertyHelper<Note?> _parent;
private readonly BehaviorSubject<int?> _parentId = new(default);
private DateTimeOffset _createdTime;
private int _id;
private string _name = default!;
private DateTimeOffset _updatedTime;
public DateTimeOffset CreatedTime
{
get => _createdTime;
set => this.RaiseAndSetIfChanged(ref _createdTime, value);
}
public int Id
{
get => _id;
set => this.RaiseAndSetIfChanged(ref _id, value);
}
public string Name
{
get => _name;
set => this.RaiseAndSetIfChanged(ref _name, value);
}
public DateTimeOffset UpdatedTime
{
get => _updatedTime;
set => this.RaiseAndSetIfChanged(ref _updatedTime, value);
}
public Note? Parent
{
get => _parent.Value;
set => ParentId = value?.Id;
}
public int? ParentId
{
get => _parentId.Value;
set => _parentId.OnNext(value);
}
public Note(NoteStore noteStore)
{
_parent = _parentId.Select(id => id switch
{
{ } => noteStore.Watch((int)id),
_ => Observable.Empty<Note?>(),
}).Switch(null).ToProperty(this, nameof(Parent), deferSubscription: true);
}
[ObservableProperty] private DateTimeOffset _createdTime;
[ObservableProperty] private int _id;
[ObservableProperty] private string _name = default!;
[ObservableProperty] private int? _parentId;
[ObservableProperty] private DateTimeOffset _updatedTime;
}

View file

@ -1,7 +1,4 @@
using System.Collections.ObjectModel;
using System.Reactive.Linq;
using DynamicData;
using Avalonia.Collections;
using InkForge.Data;
@ -9,51 +6,46 @@ using Microsoft.EntityFrameworkCore;
namespace InkForge.Desktop.Models;
public class NoteStore
public class NoteStore(IDbContextFactory<NoteDbContext> dbContextFactory)
{
private readonly IDbContextFactory<NoteDbContext> _dbContextFactory;
private readonly SourceCache<Note, int> _notesCache = new(m => m.Id);
public AvaloniaDictionary<int, Note> Notes { get; } = [];
public ReadOnlyObservableCollection<Note> Notes { get; }
public NoteStore(IDbContextFactory<NoteDbContext> dbContextFactory)
public async ValueTask Load()
{
_dbContextFactory = dbContextFactory;
await using var dbContext = await dbContextFactory.CreateDbContextAsync().ConfigureAwait(false);
Notes.Clear();
await foreach (var note in dbContext.Notes.AsAsyncEnumerable().ConfigureAwait(false))
{
Notes.Add(note.Id, Map(note));
}
}
public void AddNote(Note note)
{
using var dbContext = _dbContextFactory.CreateDbContext();
var entity = ToEntity(note);
using var dbContext = dbContextFactory.CreateDbContext();
var entity = Map(note);
var entry = dbContext.Notes.Add(entity);
dbContext.SaveChanges();
}
public Note CreateNote() => new(this);
public Note? GetById(int id)
{
if (((Note?)_notesCache.Lookup(id)) is not Note note)
if (!Notes.TryGetValue(id, out var note))
{
using var dbContext = _dbContextFactory.CreateDbContext();
using var dbContext = dbContextFactory.CreateDbContext();
if (dbContext.Notes.Find(id) is not { } dbNote)
{
return null;
}
note = ToNote(dbNote);
Notes.Add(id, note = Map(dbNote));
}
return note;
}
public IObservable<Note?> Watch(int id)
{
return _notesCache.WatchValue(id);
}
private NoteEntity ToEntity(Note note)
private NoteEntity Map(Note note)
{
return new()
{
@ -64,25 +56,26 @@ public class NoteStore
Created = note.CreatedTime,
Updated = note.UpdatedTime,
},
Parent = note.Parent switch
Parent = note.ParentId switch
{
{ Id: { } parentId } => new()
{ } parentId => new()
{
Id = parentId
},
_ => null,
}
_ => null
},
};
}
private Note ToNote(NoteEntity entity)
private Note Map(NoteEntity entity)
{
var note = CreateNote();
note.Id = entity.Id;
note.Name = entity.Value.Name;
note.CreatedTime = entity.Value.Created;
note.UpdatedTime = entity.Value.Updated;
note.ParentId = entity.Parent?.Id;
return note;
return new()
{
Id = entity.Id,
Name = entity.Value.Name,
CreatedTime = entity.Value.Created,
UpdatedTime = entity.Value.Updated,
ParentId = entity.Parent?.Id
};
}
}

View file

@ -0,0 +1,131 @@
using System.Buffers;
using System.Text;
using Avalonia.Collections;
using AvaloniaEdit.Document;
using InkForge.Data;
using Microsoft.Data.Sqlite;
using Microsoft.EntityFrameworkCore;
namespace InkForge.Desktop.Models;
public class TextDocumentStore(IDbContextFactory<NoteDbContext> dbContextFactory)
{
private readonly AvaloniaDictionary<int, TextDocument> _cache = [];
public void Close(int id)
{
if (_cache.Remove(id, out var document))
{
document.Remove(0, document.TextLength);
}
}
public TextDocument Get(int id)
{
if (!_cache.TryGetValue(id, out var document))
{
using var dbContext = dbContextFactory.CreateDbContext();
var connection = dbContext.Database.GetDbConnection();
using var command = connection.CreateCommand();
command.CommandText = $"SELECT rowid FROM Blobs WHERE Id={id}";
using var commandReader = command.ExecuteReader();
document = new();
if (commandReader.Read())
{
using SqliteBlob stream = new((SqliteConnection)connection, "Blob", "Value", commandReader.GetInt64(0), true);
CopyDocumentContent(stream, document);
}
_cache.Add(id, document);
}
return document;
static void CopyDocumentContent(Stream stream, TextDocument document)
{
DocumentTextWriter writer = new(document, 0);
StreamReader reader = new(stream, Encoding.UTF8);
Span<char> buffer = stackalloc char[128];
while (!reader.EndOfStream)
{
var read = reader.Read(buffer);
writer.Write(buffer[..read]);
}
}
}
public int? Save(int? id, TextDocument textDocument)
{
var state = textDocument.CreateSnapshot();
var bytes = TextBytes(state);
using var dbContext = dbContextFactory.CreateDbContext();
var connection = dbContext.Database.GetDbConnection();
using var transaction = dbContext.Database.BeginTransaction();
using var command = connection.CreateCommand();
command.CommandText = id switch
{
null => $"INSERT INTO Blobs(Value) VALUES(zeroblob({bytes})) RETURNING (Id, rowid)",
_ => $"UPDATE Blobs SET Value=zeroblob({bytes}) WHERE Id={id} RETURNING (Id, rowid)"
};
using var commandReader = command.ExecuteReader();
if (!commandReader.Read())
{
return null;
}
id = commandReader.GetInt32(0);
using (SqliteBlob stream = new((SqliteConnection)connection, "Blobs", "Value", commandReader.GetInt64(1)))
{
using StreamWriter writer = new(stream, Encoding.UTF8);
state.WriteTextTo(writer);
}
transaction.Commit();
_cache.TryAdd((int)id, textDocument);
return id;
static int TextBytes(ITextSource text)
{
int length = 0;
Span<byte> byteBuffer = stackalloc byte[128];
using var reader = text.CreateReader();
ArrayBufferWriter<char> buffer = new();
var encoder = Encoding.UTF8.GetEncoder();
while (reader.Peek() != -1)
{
var memory = buffer.GetMemory();
buffer.Advance(reader.Read(memory.Span));
ReadOnlySpan<char> chars = buffer.WrittenSpan;
convert:
encoder.Convert(buffer.WrittenSpan, byteBuffer, false, out var charsUsed, out var bytesUsed, out _);
chars = chars[charsUsed..];
if (bytesUsed > 0)
{
length += bytesUsed;
goto convert;
}
Span<char> remaining = [];
if (!chars.IsEmpty)
{
remaining = buffer.GetSpan(chars.Length);
chars.CopyTo(remaining);
}
buffer.Clear();
if (!remaining.IsEmpty)
{
buffer.Write(remaining);
}
}
return length;
}
}
}

View file

@ -4,21 +4,16 @@ using Microsoft.Extensions.DependencyInjection;
namespace InkForge.Desktop.Models;
public sealed class Workspace : IDisposable
public sealed class Workspace(IServiceScope scope) : IDisposable
{
private bool _disposedValue;
private IServiceScope? _scope;
private IServiceScope? _scope = scope;
public string Name { get; set; } = default!;
public LocalWorkspaceOptions Options { get; set; } = default!;
public IServiceProvider Services => _scope!.ServiceProvider;
public Workspace(IServiceScope scope)
{
_scope = scope;
}
public IServiceProvider Services { get; } = scope.ServiceProvider;
public void Dispose()
{
@ -33,14 +28,4 @@ public sealed class Workspace : IDisposable
_disposedValue = true;
}
}
// private async Task LoadNotes()
// {
// await using var dbContext = await _dbContextFactory.CreateDbContextAsync().ConfigureAwait(false);
// await foreach (var asdf in dbContext.Notes.AsAsyncEnumerable().ConfigureAwait(false))
// {
// }
// _ = (await dbContext.Notes.ToListAsync().ConfigureAwait(false));
// }
}