Replace ReactiveUI
This commit is contained in:
parent
43b4d50e43
commit
5584ab4ec8
41 changed files with 472 additions and 1013 deletions
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
|||
131
app/InkForge.Desktop/Models/TextDocumentStore.cs
Normal file
131
app/InkForge.Desktop/Models/TextDocumentStore.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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));
|
||||
// }
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue