131 lines
3.3 KiB
C#
131 lines
3.3 KiB
C#
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;
|
|
}
|
|
}
|
|
}
|