InkForge/app/InkForge.Desktop/Models/TextDocumentStore.cs

132 lines
3.3 KiB
C#
Raw Permalink Normal View History

2024-05-02 21:44:13 +02:00
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;
}
}
}