a11b15e444
TASK 43's alert box grows a real video celebration. Per-alert IAlertClipDecoder
(ffmpeg bgra + f32le pipes, real-time paced, disposed at drain) plays the shipped
Assets/alert-default.mp4 (stamped into the Asset table at startup) unless the
creator picks their own file — path reference only, never stored in the DB; the
six AlertRenderer animations stay the fallback. ~0.3s fade rides the alpha
envelope on straight-source copies (EOF freeze-frames then fades out); audio
forwards to a new AudioMixer alert ring (8s, 48k stereo) drained at unity — no
duck, creator ruling — scaled by volume × fade. An auto-composed marquee ticker
('Funder — Super Chat · $10.00', 140px/s) scrolls top-of-frame via a
FramePump._alertTicker seam through Render/CompositeLayers, mixed into the cache
signature (dynamic overlay, never baked). New Stream Alerts section in LeftPanel.
Derivative-work references (how OBS/Streamlabs alert boxes do per-alert video):
- https://support.streamlabs.com/hc/en-us/articles/217741147-Setting-Up-Your-Streamlabs-Alerts (custom image/video per alert type + variations)
- https://obsproject.com/kb/stream-tutorial-2-alerts (alert overlay as an on-screen zone)
- https://streamlabs.com/content-hub/widgets/alert-box (per-event alert playback)
Good Dog: AlertLayerVideoTests drives a fake IAlertClipDecoder through the whole
lifecycle in one pass (custom path wins, decoder spawns/disposes, fade envelope
0→127→255, audio volume×fade, ticker scrolls, EOF fade-drain to idle). It caught
the clip branch of Advance not clearing _current before AdvanceToNext — the layer
stayed IsPlaying after drain (MyMistakes post-mortem).
Full vstest 319/319; clean build 0 warnings; scope check green.
546 lines
22 KiB
C#
546 lines
22 KiB
C#
using Microsoft.Data.Sqlite;
|
|
using ytLive.Models;
|
|
|
|
namespace ytLive.Services;
|
|
|
|
public partial class LayoutStore : IDisposable
|
|
{
|
|
private void EnsureSchema()
|
|
{
|
|
string[] statements =
|
|
{
|
|
"""
|
|
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,
|
|
HasBackground INTEGER NOT NULL DEFAULT 1,
|
|
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,
|
|
ClipShape TEXT NOT NULL DEFAULT 'Traditional',
|
|
IsMirrored INTEGER NOT NULL DEFAULT 0,
|
|
IsBackground INTEGER NOT NULL DEFAULT 0,
|
|
CaptureKey TEXT,
|
|
SortOrder INTEGER NOT NULL DEFAULT 0
|
|
);
|
|
""",
|
|
"""
|
|
CREATE TABLE IF NOT EXISTS Webcam (
|
|
Id TEXT PRIMARY KEY,
|
|
DeviceId TEXT NOT NULL UNIQUE,
|
|
Name TEXT NOT NULL
|
|
);
|
|
""",
|
|
"""
|
|
CREATE TABLE IF NOT EXISTS WebcamSceneConfig (
|
|
SceneId TEXT NOT NULL REFERENCES Scene(Id) ON DELETE CASCADE,
|
|
WebcamId TEXT NOT NULL REFERENCES Webcam(Id) ON DELETE CASCADE,
|
|
IsVisible 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,
|
|
ClipShape TEXT NOT NULL DEFAULT 'Traditional',
|
|
IsMirrored INTEGER NOT NULL DEFAULT 0,
|
|
RectWidth REAL,
|
|
RectHeight REAL,
|
|
BorderColor TEXT,
|
|
BorderOpacity REAL NOT NULL DEFAULT 1,
|
|
BorderWidth INTEGER NOT NULL DEFAULT 0,
|
|
BorderAnimation TEXT NOT NULL DEFAULT 'None',
|
|
SortOrder INTEGER NOT NULL DEFAULT 0,
|
|
PRIMARY KEY (SceneId, WebcamId)
|
|
);
|
|
""",
|
|
"""
|
|
CREATE TABLE IF NOT EXISTS Socials (
|
|
Id TEXT PRIMARY KEY,
|
|
BarPosition TEXT NOT NULL DEFAULT 'Bottom',
|
|
BarJustify TEXT NOT NULL DEFAULT 'Center'
|
|
);
|
|
""",
|
|
"""
|
|
CREATE TABLE IF NOT EXISTS SocialEntry (
|
|
Id TEXT PRIMARY KEY,
|
|
SocialsId TEXT NOT NULL REFERENCES Socials(Id) ON DELETE CASCADE,
|
|
Service TEXT NOT NULL,
|
|
Handle TEXT NOT NULL,
|
|
ProfileUrl TEXT NOT NULL,
|
|
SortOrder INTEGER NOT NULL DEFAULT 0
|
|
);
|
|
""",
|
|
"""
|
|
CREATE TABLE IF NOT EXISTS Music (
|
|
Id TEXT PRIMARY KEY,
|
|
TrackPath TEXT NOT NULL,
|
|
IsEnabled INTEGER NOT NULL DEFAULT 0
|
|
);
|
|
""",
|
|
"""
|
|
CREATE TABLE IF NOT EXISTS Settings (
|
|
Key TEXT PRIMARY KEY,
|
|
Value TEXT NOT NULL
|
|
);
|
|
""",
|
|
// v9 → v10 (TASK 39): YPP journey-tracker snapshots, appended on every
|
|
// stats refresh. The history is the raw data a later analytics slice
|
|
// reads to compute velocity/(de)acceleration/ETA.
|
|
"""
|
|
CREATE TABLE IF NOT EXISTS YppSnapshot (
|
|
CapturedAtUtc TEXT NOT NULL PRIMARY KEY,
|
|
SubscriberCount INTEGER NOT NULL,
|
|
VideoCount INTEGER NOT NULL,
|
|
TotalViews INTEGER NOT NULL,
|
|
UploadsLast90Days INTEGER NOT NULL,
|
|
CommunityGuidelinesGoodStanding INTEGER NOT NULL,
|
|
CopyrightStrikesGoodStanding INTEGER NOT NULL,
|
|
ContentIdClaimsGoodStanding INTEGER NOT NULL,
|
|
OverallGoodStanding INTEGER NOT NULL
|
|
);
|
|
""",
|
|
};
|
|
foreach (var sql in statements)
|
|
{
|
|
using var cmd = _connection.CreateCommand();
|
|
cmd.CommandText = sql;
|
|
cmd.ExecuteNonQuery();
|
|
}
|
|
MigrateSourceTable();
|
|
MigrateWebcamConfigTable();
|
|
MigrateSceneTable();
|
|
MigrateSceneSocialBarColumn();
|
|
MigrateSocialsTable();
|
|
MigrateSocialEntryTable();
|
|
if (GetUserVersion() < 3)
|
|
MigrateToV3();
|
|
using (var cmd = _connection.CreateCommand())
|
|
{
|
|
cmd.CommandText = "PRAGMA user_version = 10;";
|
|
cmd.ExecuteNonQuery();
|
|
}
|
|
}
|
|
|
|
|
|
// v1 → v2: Source gains ClipShape + IsMirrored (webcam clip/mirror prefs).
|
|
// CREATE TABLE IF NOT EXISTS doesn't touch existing tables, so pre-v2 DBs
|
|
// get the columns here instead.
|
|
private void MigrateSourceTable()
|
|
{
|
|
var columns = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
|
using (var cmd = _connection.CreateCommand())
|
|
{
|
|
cmd.CommandText = "PRAGMA table_info(Source);";
|
|
using var reader = cmd.ExecuteReader();
|
|
while (reader.Read())
|
|
columns.Add(reader.GetString(1));
|
|
}
|
|
|
|
if (!columns.Contains("ClipShape"))
|
|
{
|
|
using var cmd = _connection.CreateCommand();
|
|
cmd.CommandText = "ALTER TABLE Source ADD COLUMN ClipShape TEXT NOT NULL DEFAULT 'Traditional';";
|
|
cmd.ExecuteNonQuery();
|
|
}
|
|
|
|
if (!columns.Contains("IsMirrored"))
|
|
{
|
|
using var cmd = _connection.CreateCommand();
|
|
cmd.CommandText = "ALTER TABLE Source ADD COLUMN IsMirrored INTEGER NOT NULL DEFAULT 0;";
|
|
cmd.ExecuteNonQuery();
|
|
}
|
|
|
|
if (columns.Contains("IsBackdrop"))
|
|
{
|
|
using var cmd = _connection.CreateCommand();
|
|
cmd.CommandText = "ALTER TABLE Source RENAME COLUMN IsBackdrop TO IsBackground;";
|
|
cmd.ExecuteNonQuery();
|
|
}
|
|
else if (!columns.Contains("IsBackground"))
|
|
{
|
|
using var cmd = _connection.CreateCommand();
|
|
cmd.CommandText = "ALTER TABLE Source ADD COLUMN IsBackground INTEGER NOT NULL DEFAULT 0;";
|
|
cmd.ExecuteNonQuery();
|
|
}
|
|
|
|
if (!columns.Contains("CaptureKey"))
|
|
{
|
|
using var cmd = _connection.CreateCommand();
|
|
cmd.CommandText = "ALTER TABLE Source ADD COLUMN CaptureKey TEXT;";
|
|
cmd.ExecuteNonQuery();
|
|
}
|
|
|
|
// TASK 3.18: ChatBox overlay source
|
|
if (!columns.Contains("ChatFontSize"))
|
|
{
|
|
using var cmd = _connection.CreateCommand();
|
|
cmd.CommandText = "ALTER TABLE Source ADD COLUMN ChatFontSize REAL NOT NULL DEFAULT 16;";
|
|
cmd.ExecuteNonQuery();
|
|
}
|
|
if (!columns.Contains("ChatFontColor"))
|
|
{
|
|
using var cmd = _connection.CreateCommand();
|
|
cmd.CommandText = "ALTER TABLE Source ADD COLUMN ChatFontColor TEXT NOT NULL DEFAULT '#FFFFFF';";
|
|
cmd.ExecuteNonQuery();
|
|
}
|
|
if (!columns.Contains("ChatBackgroundColor"))
|
|
{
|
|
using var cmd = _connection.CreateCommand();
|
|
cmd.CommandText = "ALTER TABLE Source ADD COLUMN ChatBackgroundColor TEXT NOT NULL DEFAULT '#80000000';";
|
|
cmd.ExecuteNonQuery();
|
|
}
|
|
if (!columns.Contains("ChatMaxMessages"))
|
|
{
|
|
using var cmd = _connection.CreateCommand();
|
|
cmd.CommandText = "ALTER TABLE Source ADD COLUMN ChatMaxMessages INTEGER NOT NULL DEFAULT 5;";
|
|
cmd.ExecuteNonQuery();
|
|
}
|
|
if (!columns.Contains("ChatShowTimestamps"))
|
|
{
|
|
using var cmd = _connection.CreateCommand();
|
|
cmd.CommandText = "ALTER TABLE Source ADD COLUMN ChatShowTimestamps INTEGER NOT NULL DEFAULT 1;";
|
|
cmd.ExecuteNonQuery();
|
|
}
|
|
if (!columns.Contains("ChatShowBadges"))
|
|
{
|
|
using var cmd = _connection.CreateCommand();
|
|
cmd.CommandText = "ALTER TABLE Source ADD COLUMN ChatShowBadges INTEGER NOT NULL DEFAULT 1;";
|
|
cmd.ExecuteNonQuery();
|
|
}
|
|
if (!columns.Contains("ChatMemberColor"))
|
|
{
|
|
using var cmd = _connection.CreateCommand();
|
|
cmd.CommandText = "ALTER TABLE Source ADD COLUMN ChatMemberColor TEXT NOT NULL DEFAULT '#e94560';";
|
|
cmd.ExecuteNonQuery();
|
|
}
|
|
if (!columns.Contains("ChatSuperChatColor"))
|
|
{
|
|
using var cmd = _connection.CreateCommand();
|
|
cmd.CommandText = "ALTER TABLE Source ADD COLUMN ChatSuperChatColor TEXT NOT NULL DEFAULT '#eab308';";
|
|
cmd.ExecuteNonQuery();
|
|
}
|
|
if (!columns.Contains("ShowDesktop"))
|
|
{
|
|
using var cmd = _connection.CreateCommand();
|
|
cmd.CommandText = "ALTER TABLE Source ADD COLUMN ShowDesktop INTEGER NOT NULL DEFAULT 0;";
|
|
cmd.ExecuteNonQuery();
|
|
}
|
|
|
|
if (!columns.Contains("WebUri"))
|
|
{
|
|
using var cmd = _connection.CreateCommand();
|
|
cmd.CommandText = "ALTER TABLE Source ADD COLUMN WebUri TEXT;";
|
|
cmd.ExecuteNonQuery();
|
|
}
|
|
|
|
// TASK 21: Media source (video/audio file playback)
|
|
if (!columns.Contains("MediaPath"))
|
|
{
|
|
using var cmd = _connection.CreateCommand();
|
|
cmd.CommandText = "ALTER TABLE Source ADD COLUMN MediaPath TEXT;";
|
|
cmd.ExecuteNonQuery();
|
|
}
|
|
if (!columns.Contains("MediaIsLooping"))
|
|
{
|
|
using var cmd = _connection.CreateCommand();
|
|
cmd.CommandText = "ALTER TABLE Source ADD COLUMN MediaIsLooping INTEGER NOT NULL DEFAULT 0;";
|
|
cmd.ExecuteNonQuery();
|
|
}
|
|
if (!columns.Contains("MediaVolume"))
|
|
{
|
|
using var cmd = _connection.CreateCommand();
|
|
cmd.CommandText = "ALTER TABLE Source ADD COLUMN MediaVolume REAL NOT NULL DEFAULT 1;";
|
|
cmd.ExecuteNonQuery();
|
|
}
|
|
if (!columns.Contains("MediaPlaybackState"))
|
|
{
|
|
using var cmd = _connection.CreateCommand();
|
|
cmd.CommandText = "ALTER TABLE Source ADD COLUMN MediaPlaybackState TEXT NOT NULL DEFAULT 'Stopped';";
|
|
cmd.ExecuteNonQuery();
|
|
}
|
|
|
|
// TASK 47: alert video config. The default clip asset id + the creator's
|
|
// replacement path (a FILE reference, never a stored BLOB) + the pill,
|
|
// ticker toggle and volume live on the row.
|
|
if (!columns.Contains("AlertVideoAssetId"))
|
|
{
|
|
using var cmd = _connection.CreateCommand();
|
|
cmd.CommandText = "ALTER TABLE Source ADD COLUMN AlertVideoAssetId TEXT;";
|
|
cmd.ExecuteNonQuery();
|
|
}
|
|
if (!columns.Contains("AlertVideoPath"))
|
|
{
|
|
using var cmd = _connection.CreateCommand();
|
|
cmd.CommandText = "ALTER TABLE Source ADD COLUMN AlertVideoPath TEXT;";
|
|
cmd.ExecuteNonQuery();
|
|
}
|
|
if (!columns.Contains("AlertUseDefaultVideo"))
|
|
{
|
|
using var cmd = _connection.CreateCommand();
|
|
cmd.CommandText = "ALTER TABLE Source ADD COLUMN AlertUseDefaultVideo INTEGER NOT NULL DEFAULT 1;";
|
|
cmd.ExecuteNonQuery();
|
|
}
|
|
if (!columns.Contains("AlertShowTicker"))
|
|
{
|
|
using var cmd = _connection.CreateCommand();
|
|
cmd.CommandText = "ALTER TABLE Source ADD COLUMN AlertShowTicker INTEGER NOT NULL DEFAULT 1;";
|
|
cmd.ExecuteNonQuery();
|
|
}
|
|
if (!columns.Contains("AlertVideoVolume"))
|
|
{
|
|
using var cmd = _connection.CreateCommand();
|
|
cmd.CommandText = "ALTER TABLE Source ADD COLUMN AlertVideoVolume REAL NOT NULL DEFAULT 1;";
|
|
cmd.ExecuteNonQuery();
|
|
}
|
|
|
|
// Rename legacy "Backdrop" display name to "Background" in existing rows
|
|
using (var cmd = _connection.CreateCommand())
|
|
{
|
|
cmd.CommandText = "UPDATE Source SET Name = 'Background' WHERE Name = 'Backdrop';";
|
|
cmd.ExecuteNonQuery();
|
|
}
|
|
}
|
|
|
|
// v3 → v4: WebcamSceneConfig gains RectWidth/RectHeight (the pre-Round rect,
|
|
// so a reloaded Round webcam restores its aspect on toggle-back).
|
|
private void MigrateWebcamConfigTable()
|
|
{
|
|
var columns = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
|
using (var cmd = _connection.CreateCommand())
|
|
{
|
|
cmd.CommandText = "PRAGMA table_info(WebcamSceneConfig);";
|
|
using var reader = cmd.ExecuteReader();
|
|
while (reader.Read())
|
|
columns.Add(reader.GetString(1));
|
|
}
|
|
|
|
if (!columns.Contains("RectWidth"))
|
|
{
|
|
using var cmd = _connection.CreateCommand();
|
|
cmd.CommandText = "ALTER TABLE WebcamSceneConfig ADD COLUMN RectWidth REAL;";
|
|
cmd.ExecuteNonQuery();
|
|
}
|
|
|
|
if (!columns.Contains("RectHeight"))
|
|
{
|
|
using var cmd = _connection.CreateCommand();
|
|
cmd.CommandText = "ALTER TABLE WebcamSceneConfig ADD COLUMN RectHeight REAL;";
|
|
cmd.ExecuteNonQuery();
|
|
}
|
|
}
|
|
|
|
// v2 → v3: the webcam leaves the Source table. Any webcam Source rows become
|
|
// one Webcam identity (first row — one camera app-wide) + a WebcamSceneConfig
|
|
// per scene that had one, then the webcam rows are deleted. No backfill:
|
|
// scenes without the webcam stay webcam-free.
|
|
private void MigrateToV3()
|
|
{
|
|
var rows = new List<(string Id, string SceneId, string Name, double X, double Y, double W, double H, double Opacity, string DeviceId, string ClipShape, bool IsMirrored)>();
|
|
using (var cmd = _connection.CreateCommand())
|
|
{
|
|
cmd.CommandText = """
|
|
SELECT Id, SceneId, Name, X, Y, Width, Height, Opacity, DeviceId, ClipShape, IsMirrored
|
|
FROM Source WHERE Type = 'Webcam' ORDER BY SortOrder;
|
|
""";
|
|
using var reader = cmd.ExecuteReader();
|
|
while (reader.Read())
|
|
{
|
|
rows.Add((
|
|
reader.GetString(0),
|
|
reader.GetString(1),
|
|
reader.GetString(2),
|
|
reader.GetDouble(3),
|
|
reader.GetDouble(4),
|
|
reader.GetDouble(5),
|
|
reader.GetDouble(6),
|
|
reader.GetDouble(7),
|
|
reader.IsDBNull(8) ? string.Empty : reader.GetString(8),
|
|
reader.GetString(9),
|
|
reader.GetInt32(10) != 0));
|
|
}
|
|
}
|
|
if (rows.Count == 0) return;
|
|
|
|
using var tx = _connection.BeginTransaction();
|
|
var webcamId = Guid.NewGuid().ToString();
|
|
|
|
using (var cmd = _connection.CreateCommand())
|
|
{
|
|
cmd.CommandText = "INSERT INTO Webcam (Id, DeviceId, Name) VALUES ($id, $device, $name);";
|
|
cmd.Transaction = tx;
|
|
cmd.Parameters.AddWithValue("$id", webcamId);
|
|
cmd.Parameters.AddWithValue("$device", rows[0].DeviceId);
|
|
cmd.Parameters.AddWithValue("$name", rows[0].Name);
|
|
cmd.ExecuteNonQuery();
|
|
}
|
|
|
|
foreach (var row in rows)
|
|
{
|
|
using var cmd = _connection.CreateCommand();
|
|
cmd.CommandText = """
|
|
INSERT INTO WebcamSceneConfig
|
|
(SceneId, WebcamId, IsVisible, X, Y, Width, Height, Opacity,
|
|
ClipShape, IsMirrored, BorderColor, BorderOpacity, BorderWidth, BorderAnimation, SortOrder)
|
|
VALUES ($sceneId, $webcamId, 1, $x, $y, $w, $h, $opacity,
|
|
$clip, $mirrored, NULL, 1, 0, 'None', 0);
|
|
""";
|
|
cmd.Transaction = tx;
|
|
cmd.Parameters.AddWithValue("$sceneId", row.SceneId);
|
|
cmd.Parameters.AddWithValue("$webcamId", webcamId);
|
|
cmd.Parameters.AddWithValue("$x", row.X);
|
|
cmd.Parameters.AddWithValue("$y", row.Y);
|
|
cmd.Parameters.AddWithValue("$w", row.W);
|
|
cmd.Parameters.AddWithValue("$h", row.H);
|
|
cmd.Parameters.AddWithValue("$opacity", row.Opacity);
|
|
cmd.Parameters.AddWithValue("$clip", row.ClipShape);
|
|
cmd.Parameters.AddWithValue("$mirrored", row.IsMirrored ? 1 : 0);
|
|
cmd.ExecuteNonQuery();
|
|
}
|
|
|
|
using (var cmd = _connection.CreateCommand())
|
|
{
|
|
cmd.CommandText = "DELETE FROM Source WHERE Type = 'Webcam';";
|
|
cmd.Transaction = tx;
|
|
cmd.ExecuteNonQuery();
|
|
}
|
|
tx.Commit();
|
|
}
|
|
|
|
// v5 → v6: Scene gains HasBackground. Added columns default to true; the
|
|
// ONE-TIME backfill (runs only when the column is first added) turns every
|
|
// non-Live canonical scene off and drops their background sources — the fix for
|
|
// DBs saved before the Live-only policy. MainViewModel.NormalizeBackgrounds
|
|
// re-runs the same normalization on every load for DBs that miss the backfill.
|
|
private void MigrateSceneTable()
|
|
{
|
|
var columns = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
|
using (var cmd = _connection.CreateCommand())
|
|
{
|
|
cmd.CommandText = "PRAGMA table_info(Scene);";
|
|
using var reader = cmd.ExecuteReader();
|
|
while (reader.Read())
|
|
columns.Add(reader.GetString(1));
|
|
}
|
|
|
|
if (columns.Contains("HasBackdrop"))
|
|
{
|
|
using var cmd = _connection.CreateCommand();
|
|
cmd.CommandText = "ALTER TABLE Scene RENAME COLUMN HasBackdrop TO HasBackground;";
|
|
cmd.ExecuteNonQuery();
|
|
}
|
|
else if (columns.Contains("HasBackground"))
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (!columns.Contains("HasBackground"))
|
|
{
|
|
using var cmd = _connection.CreateCommand();
|
|
cmd.CommandText = "ALTER TABLE Scene ADD COLUMN HasBackground INTEGER NOT NULL DEFAULT 1;";
|
|
cmd.ExecuteNonQuery();
|
|
}
|
|
|
|
const string noBackground = "('starting', 'brb', 'chat', 'ending')";
|
|
using (var cmd = _connection.CreateCommand())
|
|
{
|
|
cmd.CommandText = $"UPDATE Scene SET HasBackground = 0 WHERE LOWER(TRIM(Name)) IN {noBackground};";
|
|
cmd.ExecuteNonQuery();
|
|
}
|
|
using (var cmd = _connection.CreateCommand())
|
|
{
|
|
cmd.CommandText = $"""
|
|
DELETE FROM Source
|
|
WHERE IsBackground = 1 AND SceneId IN
|
|
(SELECT Id FROM Scene WHERE LOWER(TRIM(Name)) IN {noBackground});
|
|
""";
|
|
cmd.ExecuteNonQuery();
|
|
}
|
|
}
|
|
|
|
// v6 → v7: Scene gains HasSocialBar (the per-scene social-bar toggle).
|
|
// Defaults to 0 (off) — the creator adds it explicitly via the footer [+].
|
|
private void MigrateSceneSocialBarColumn()
|
|
{
|
|
var columns = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
|
using (var cmd = _connection.CreateCommand())
|
|
{
|
|
cmd.CommandText = "PRAGMA table_info(Scene);";
|
|
using var reader = cmd.ExecuteReader();
|
|
while (reader.Read())
|
|
columns.Add(reader.GetString(1));
|
|
}
|
|
|
|
if (columns.Contains("HasSocialBar")) return;
|
|
|
|
using var alter = _connection.CreateCommand();
|
|
alter.CommandText = "ALTER TABLE Scene ADD COLUMN HasSocialBar INTEGER NOT NULL DEFAULT 0;";
|
|
alter.ExecuteNonQuery();
|
|
}
|
|
|
|
// v7 → v8: Socials gains BarEnabled (the dialog's show/hide switch). The old
|
|
// BarJustify column stays for back-compat but is no longer read or written.
|
|
private void MigrateSocialsTable()
|
|
{
|
|
var columns = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
|
using (var cmd = _connection.CreateCommand())
|
|
{
|
|
cmd.CommandText = "PRAGMA table_info(Socials);";
|
|
using var reader = cmd.ExecuteReader();
|
|
while (reader.Read())
|
|
columns.Add(reader.GetString(1));
|
|
}
|
|
|
|
if (columns.Contains("BarEnabled")) return;
|
|
|
|
using var alter = _connection.CreateCommand();
|
|
alter.CommandText = "ALTER TABLE Socials ADD COLUMN BarEnabled INTEGER NOT NULL DEFAULT 1;";
|
|
alter.ExecuteNonQuery();
|
|
}
|
|
|
|
// v8 → v9: SocialEntry gains Software (the fediverse instance's nodeinfo
|
|
// software name) so a reloaded entry still shows the right service logo.
|
|
private void MigrateSocialEntryTable()
|
|
{
|
|
var columns = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
|
using (var cmd = _connection.CreateCommand())
|
|
{
|
|
cmd.CommandText = "PRAGMA table_info(SocialEntry);";
|
|
using var reader = cmd.ExecuteReader();
|
|
while (reader.Read())
|
|
columns.Add(reader.GetString(1));
|
|
}
|
|
|
|
if (columns.Contains("Software")) return;
|
|
|
|
using var alter = _connection.CreateCommand();
|
|
alter.CommandText = "ALTER TABLE SocialEntry ADD COLUMN Software TEXT;";
|
|
alter.ExecuteNonQuery();
|
|
}
|
|
}
|