From 3db371610132af9cab578c6495ad9423878db756 Mon Sep 17 00:00:00 2001 From: Max Goodhart Date: Sat, 14 Jun 2025 19:14:36 +0000 Subject: [PATCH] Persist wall assignments across restarts --- package-lock.json | 1 + packages/streamwall/package.json | 1 + packages/streamwall/src/main/index.ts | 53 ++++++++++++++++++++++----- 3 files changed, 45 insertions(+), 10 deletions(-) diff --git a/package-lock.json b/package-lock.json index 5979a49..bacd922 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14348,6 +14348,7 @@ "esbuild-register": "^3.6.0", "hls.js": "^1.5.18", "lodash-es": "^4.17.21", + "lowdb": "^7.0.1", "node-fetch": "^3.3.2", "react-hotkeys-hook": "^4.6.1", "react-icons": "^5.4.0", diff --git a/packages/streamwall/package.json b/packages/streamwall/package.json index 61812b8..73d61d8 100644 --- a/packages/streamwall/package.json +++ b/packages/streamwall/package.json @@ -55,6 +55,7 @@ "esbuild-register": "^3.6.0", "hls.js": "^1.5.18", "lodash-es": "^4.17.21", + "lowdb": "^7.0.1", "node-fetch": "^3.3.2", "react-hotkeys-hook": "^4.6.1", "react-icons": "^5.4.0", diff --git a/packages/streamwall/src/main/index.ts b/packages/streamwall/src/main/index.ts index bad2feb..0d99c03 100644 --- a/packages/streamwall/src/main/index.ts +++ b/packages/streamwall/src/main/index.ts @@ -3,6 +3,7 @@ import * as Sentry from '@sentry/electron/main' import { BrowserWindow, app, session } from 'electron' import started from 'electron-squirrel-startup' import fs from 'fs' +import { throttle } from 'lodash-es' import EventEmitter from 'node:events' import { join } from 'node:path' import ReconnectingWebSocket from 'reconnecting-websocket' @@ -22,6 +23,7 @@ import { pollDataURL, watchDataFile, } from './data' +import { loadStorage } from './storage' import StreamdelayClient from './StreamdelayClient' import StreamWindow from './StreamWindow' import TwitchBot from './TwitchBot' @@ -281,16 +283,7 @@ async function main(argv: ReturnType) { streamdelay: null, } - const stateDoc = new Y.Doc() - const viewsState = stateDoc.getMap>('views') - stateDoc.transact(() => { - for (let i = 0; i < argv.grid.count ** 2; i++) { - const data = new Y.Map() - data.set('streamId', undefined) - viewsState.set(String(i), data) - } - }) - viewsState.observeDeep(() => { + function updateViewsFromStateDoc() { try { const viewContentMap = new Map() for (const [key, viewData] of viewsState) { @@ -308,8 +301,47 @@ async function main(argv: ReturnType) { } catch (err) { console.error('Error updating views', err) } + } + + const stateDoc = new Y.Doc() + const viewsState = stateDoc.getMap>('views') + + const db = await loadStorage( + join(app.getPath('userData'), 'streamwall-storage.json'), + ) + if (db.data.stateDoc) { + console.log('Loading stateDoc from storage...') + try { + Y.applyUpdate(stateDoc, Buffer.from(db.data.stateDoc, 'base64')) + } catch (err) { + console.warn('Failed to restore stateDoc', err) + } + } + + stateDoc.on( + 'update', + throttle(() => { + db.update((data) => { + const fullDoc = Y.encodeStateAsUpdate(stateDoc) + data.stateDoc = Buffer.from(fullDoc).toString('base64') + }) + }, 1000), + ) + + stateDoc.transact(() => { + for (let i = 0; i < argv.grid.count ** 2; i++) { + if (viewsState.has(String(i))) { + continue + } + const data = new Y.Map() + data.set('streamId', undefined) + viewsState.set(String(i), data) + } }) + updateViewsFromStateDoc() + viewsState.observeDeep(updateViewsFromStateDoc) + const onCommand = async (msg: ControlCommand) => { console.debug('Received message:', msg) if (msg.type === 'set-listening-view') { @@ -506,6 +538,7 @@ async function main(argv: ReturnType) { console.debug('Processing streams:', rawStreams) const streams = idGen.process(rawStreams) updateState({ streams }) + updateViewsFromStateDoc() } }