277 lines
10 KiB
C#
277 lines
10 KiB
C#
using System.IO;
|
|
using System.Security.Cryptography;
|
|
using Microsoft.Data.Sqlite;
|
|
using ytLive.Models;
|
|
|
|
namespace ytLive.Services;
|
|
|
|
public class LayoutStore : IDisposable
|
|
{
|
|
public static LayoutStore? Instance { get; private set; }
|
|
|
|
private readonly SqliteConnection _connection;
|
|
public string ActivePath { get; }
|
|
|
|
public LayoutStore(string path)
|
|
{
|
|
ActivePath = path;
|
|
Directory.CreateDirectory(Path.GetDirectoryName(path)!);
|
|
_connection = new SqliteConnection($"Data Source={path}");
|
|
_connection.Open();
|
|
using (var pragma = _connection.CreateCommand())
|
|
{
|
|
pragma.CommandText = "PRAGMA foreign_keys = ON;";
|
|
pragma.ExecuteNonQuery();
|
|
}
|
|
EnsureSchema();
|
|
Instance = this;
|
|
}
|
|
|
|
private void EnsureSchema()
|
|
{
|
|
string[] statements =
|
|
{
|
|
"PRAGMA user_version = 1;",
|
|
"""
|
|
CREATE TABLE IF NOT EXISTS Scene (
|
|
Id TEXT PRIMARY KEY,
|
|
Name TEXT NOT NULL,
|
|
IsHidden INTEGER NOT NULL DEFAULT 0,
|
|
IsChatScene INTEGER NOT NULL DEFAULT 0,
|
|
SortOrder INTEGER NOT NULL DEFAULT 0
|
|
);
|
|
""",
|
|
"""
|
|
CREATE TABLE IF NOT EXISTS Asset (
|
|
Id TEXT PRIMARY KEY,
|
|
Hash TEXT NOT NULL UNIQUE,
|
|
Data BLOB NOT NULL,
|
|
PixelWidth INTEGER NOT NULL DEFAULT 0,
|
|
PixelHeight INTEGER NOT NULL DEFAULT 0
|
|
);
|
|
""",
|
|
"""
|
|
CREATE TABLE IF NOT EXISTS Source (
|
|
Id TEXT PRIMARY KEY,
|
|
SceneId TEXT NOT NULL REFERENCES Scene(Id) ON DELETE CASCADE,
|
|
AssetId TEXT REFERENCES Asset(Id),
|
|
Type TEXT NOT NULL,
|
|
Name TEXT NOT NULL,
|
|
IsEnabled INTEGER NOT NULL DEFAULT 1,
|
|
X REAL NOT NULL DEFAULT 0,
|
|
Y REAL NOT NULL DEFAULT 0,
|
|
Width REAL NOT NULL DEFAULT 0,
|
|
Height REAL NOT NULL DEFAULT 0,
|
|
Opacity REAL NOT NULL DEFAULT 1,
|
|
MonitorIndex INTEGER,
|
|
DeviceId TEXT,
|
|
SortOrder INTEGER NOT NULL DEFAULT 0
|
|
);
|
|
""",
|
|
};
|
|
foreach (var sql in statements)
|
|
{
|
|
using var cmd = _connection.CreateCommand();
|
|
cmd.CommandText = sql;
|
|
cmd.ExecuteNonQuery();
|
|
}
|
|
}
|
|
|
|
public List<Scene> Load()
|
|
{
|
|
var scenes = new List<Scene>();
|
|
var sourcesByScene = new Dictionary<string, List<Source>>();
|
|
|
|
using (var cmd = _connection.CreateCommand())
|
|
{
|
|
cmd.CommandText = "SELECT Id, Name, IsHidden, IsChatScene FROM Scene ORDER BY SortOrder";
|
|
using var reader = cmd.ExecuteReader();
|
|
while (reader.Read())
|
|
{
|
|
scenes.Add(new Scene
|
|
{
|
|
Id = reader.GetString(0),
|
|
Name = reader.GetString(1),
|
|
IsHidden = reader.GetInt32(2) != 0,
|
|
IsChatScene = reader.GetInt32(3) != 0,
|
|
});
|
|
}
|
|
}
|
|
|
|
using (var cmd = _connection.CreateCommand())
|
|
{
|
|
cmd.CommandText = """
|
|
SELECT Id, SceneId, AssetId, Type, Name, IsEnabled,
|
|
X, Y, Width, Height, Opacity, MonitorIndex, DeviceId
|
|
FROM Source ORDER BY SortOrder
|
|
""";
|
|
using var reader = cmd.ExecuteReader();
|
|
while (reader.Read())
|
|
{
|
|
var sceneId = reader.GetString(1);
|
|
var source = new Source
|
|
{
|
|
Id = reader.GetString(0),
|
|
AssetId = reader.IsDBNull(2) ? null : reader.GetString(2),
|
|
Type = Enum.Parse<SourceType>(reader.GetString(3)),
|
|
Name = reader.GetString(4),
|
|
IsEnabled = reader.GetInt32(5) != 0,
|
|
X = reader.GetDouble(6),
|
|
Y = reader.GetDouble(7),
|
|
Width = reader.GetDouble(8),
|
|
Height = reader.GetDouble(9),
|
|
Opacity = reader.GetDouble(10),
|
|
MonitorIndex = reader.IsDBNull(11) ? null : reader.GetInt32(11),
|
|
DeviceId = reader.IsDBNull(12) ? null : reader.GetString(12),
|
|
};
|
|
if (!sourcesByScene.TryGetValue(sceneId, out var list))
|
|
sourcesByScene[sceneId] = list = new List<Source>();
|
|
list.Add(source);
|
|
}
|
|
}
|
|
|
|
foreach (var scene in scenes)
|
|
{
|
|
if (sourcesByScene.TryGetValue(scene.Id, out var list))
|
|
foreach (var source in list)
|
|
scene.Sources.Add(source);
|
|
}
|
|
|
|
return scenes;
|
|
}
|
|
|
|
public void Save(IEnumerable<Scene> scenes)
|
|
{
|
|
using var tx = _connection.BeginTransaction();
|
|
using (var cmd = _connection.CreateCommand())
|
|
{
|
|
cmd.CommandText = "DELETE FROM Source;";
|
|
cmd.Transaction = tx;
|
|
cmd.ExecuteNonQuery();
|
|
}
|
|
using (var cmd = _connection.CreateCommand())
|
|
{
|
|
cmd.CommandText = "DELETE FROM Scene;";
|
|
cmd.Transaction = tx;
|
|
cmd.ExecuteNonQuery();
|
|
}
|
|
|
|
using (var cmd = _connection.CreateCommand())
|
|
{
|
|
cmd.CommandText = """
|
|
INSERT INTO Scene (Id, Name, IsHidden, IsChatScene, SortOrder)
|
|
VALUES ($id, $name, $isHidden, $isChat, $sort)
|
|
""";
|
|
cmd.Transaction = tx;
|
|
var idP = cmd.Parameters.Add("$id", SqliteType.Text);
|
|
var nameP = cmd.Parameters.Add("$name", SqliteType.Text);
|
|
var hiddenP = cmd.Parameters.Add("$isHidden", SqliteType.Integer);
|
|
var chatP = cmd.Parameters.Add("$isChat", SqliteType.Integer);
|
|
var sortP = cmd.Parameters.Add("$sort", SqliteType.Integer);
|
|
|
|
var sort = 0;
|
|
foreach (var scene in scenes)
|
|
{
|
|
idP.Value = scene.Id;
|
|
nameP.Value = scene.Name;
|
|
hiddenP.Value = scene.IsHidden ? 1 : 0;
|
|
chatP.Value = scene.IsChatScene ? 1 : 0;
|
|
sortP.Value = sort++;
|
|
cmd.ExecuteNonQuery();
|
|
}
|
|
}
|
|
|
|
using (var cmd = _connection.CreateCommand())
|
|
{
|
|
cmd.CommandText = """
|
|
INSERT INTO Source (Id, SceneId, AssetId, Type, Name, IsEnabled,
|
|
X, Y, Width, Height, Opacity, MonitorIndex, DeviceId, SortOrder)
|
|
VALUES ($id, $sceneId, $assetId, $type, $name, $isEnabled,
|
|
$x, $y, $w, $h, $opacity, $monitor, $device, $sort)
|
|
""";
|
|
cmd.Transaction = tx;
|
|
var idP = cmd.Parameters.Add("$id", SqliteType.Text);
|
|
var sceneIdP = cmd.Parameters.Add("$sceneId", SqliteType.Text);
|
|
var assetIdP = cmd.Parameters.Add("$assetId", SqliteType.Text);
|
|
var typeP = cmd.Parameters.Add("$type", SqliteType.Text);
|
|
var nameP = cmd.Parameters.Add("$name", SqliteType.Text);
|
|
var enabledP = cmd.Parameters.Add("$isEnabled", SqliteType.Integer);
|
|
var xP = cmd.Parameters.Add("$x", SqliteType.Real);
|
|
var yP = cmd.Parameters.Add("$y", SqliteType.Real);
|
|
var wP = cmd.Parameters.Add("$w", SqliteType.Real);
|
|
var hP = cmd.Parameters.Add("$h", SqliteType.Real);
|
|
var opacityP = cmd.Parameters.Add("$opacity", SqliteType.Real);
|
|
var monitorP = cmd.Parameters.Add("$monitor", SqliteType.Integer);
|
|
var deviceP = cmd.Parameters.Add("$device", SqliteType.Text);
|
|
var sortP = cmd.Parameters.Add("$sort", SqliteType.Integer);
|
|
|
|
foreach (var scene in scenes)
|
|
{
|
|
var sort = 0;
|
|
foreach (var source in scene.Sources)
|
|
{
|
|
idP.Value = source.Id;
|
|
sceneIdP.Value = scene.Id;
|
|
assetIdP.Value = (object?)source.AssetId ?? DBNull.Value;
|
|
typeP.Value = source.Type.ToString();
|
|
nameP.Value = source.Name;
|
|
enabledP.Value = source.IsEnabled ? 1 : 0;
|
|
xP.Value = source.X;
|
|
yP.Value = source.Y;
|
|
wP.Value = source.Width;
|
|
hP.Value = source.Height;
|
|
opacityP.Value = source.Opacity;
|
|
monitorP.Value = (object?)source.MonitorIndex ?? DBNull.Value;
|
|
deviceP.Value = (object?)source.DeviceId ?? DBNull.Value;
|
|
sortP.Value = sort++;
|
|
cmd.ExecuteNonQuery();
|
|
}
|
|
}
|
|
}
|
|
|
|
using (var cmd = _connection.CreateCommand())
|
|
{
|
|
cmd.CommandText = "DELETE FROM Asset WHERE Id NOT IN (SELECT AssetId FROM Source WHERE AssetId IS NOT NULL);";
|
|
cmd.Transaction = tx;
|
|
cmd.ExecuteNonQuery();
|
|
}
|
|
|
|
tx.Commit();
|
|
}
|
|
|
|
public byte[]? GetAssetBytes(string assetId)
|
|
{
|
|
using var cmd = _connection.CreateCommand();
|
|
cmd.CommandText = "SELECT Data FROM Asset WHERE Id = $id";
|
|
cmd.Parameters.AddWithValue("$id", assetId);
|
|
return cmd.ExecuteScalar() as byte[];
|
|
}
|
|
|
|
public string UpsertAsset(byte[] data, int pixelWidth, int pixelHeight)
|
|
{
|
|
var hash = Convert.ToHexString(SHA256.HashData(data));
|
|
var id = Guid.NewGuid().ToString();
|
|
|
|
using var cmd = _connection.CreateCommand();
|
|
cmd.CommandText = """
|
|
INSERT INTO Asset (Id, Hash, Data, PixelWidth, PixelHeight)
|
|
VALUES ($id, $hash, $data, $w, $h)
|
|
ON CONFLICT(Hash) DO NOTHING;
|
|
""";
|
|
cmd.Parameters.AddWithValue("$id", id);
|
|
cmd.Parameters.AddWithValue("$hash", hash);
|
|
cmd.Parameters.AddWithValue("$data", data);
|
|
cmd.Parameters.AddWithValue("$w", pixelWidth);
|
|
cmd.Parameters.AddWithValue("$h", pixelHeight);
|
|
cmd.ExecuteNonQuery();
|
|
|
|
cmd.CommandText = "SELECT Id FROM Asset WHERE Hash = $hash;";
|
|
return (string)cmd.ExecuteScalar()!;
|
|
}
|
|
|
|
public void Dispose()
|
|
{
|
|
_connection.Dispose();
|
|
}
|
|
}
|