Skip to content

Commit a36a07d

Browse files
committed
Persist local stream data
1 parent fc42a69 commit a36a07d

File tree

5 files changed

+75
-27
lines changed

5 files changed

+75
-27
lines changed

packages/streamwall-control-ui/src/index.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1297,7 +1297,7 @@ function CustomStreamInput({
12971297
return (
12981298
<div>
12991299
<LazyChangeInput
1300-
value={props.label}
1300+
value={props.label ?? ''}
13011301
onChange={handleChangeLabel}
13021302
placeholder="Label (optional)"
13031303
/>{' '}

packages/streamwall-shared/src/types.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,16 +23,21 @@ export interface ContentViewInfo {
2323

2424
export type ContentKind = 'video' | 'audio' | 'web' | 'background' | 'overlay'
2525

26-
export interface StreamData extends ContentDisplayOptions {
26+
export interface StreamDataContent extends ContentDisplayOptions {
2727
kind: ContentKind
2828
link: string
29-
label: string
29+
label?: string
3030
labelPosition?: 'top-left' | 'top-right' | 'bottom-right' | 'bottom-left'
3131
source?: string
3232
notes?: string
3333
status?: string
3434
city?: string
3535
state?: string
36+
_id?: string
37+
_dataSource?: string
38+
}
39+
40+
export interface StreamData extends StreamDataContent {
3641
_id: string
3742
_dataSource: string
3843
}

packages/streamwall/src/main/data.ts

Lines changed: 36 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -6,20 +6,24 @@ import { promises as fsPromises } from 'fs'
66
import { isArray } from 'lodash-es'
77
import fetch from 'node-fetch'
88
import { promisify } from 'util'
9-
import { StreamData, StreamList } from '../../../streamwall-shared/src/types'
9+
import {
10+
StreamData,
11+
StreamDataContent,
12+
StreamList,
13+
} from '../../../streamwall-shared/src/types'
1014

1115
const sleep = promisify(setTimeout)
1216

13-
type DataSource = AsyncGenerator<StreamData[]>
17+
type DataSource = AsyncGenerator<StreamDataContent[]>
1418

1519
export async function* pollDataURL(url: string, intervalSecs: number) {
1620
const refreshInterval = intervalSecs * 1000
1721
let lastData = []
1822
while (true) {
19-
let data: StreamData[] = []
23+
let data: StreamDataContent[] = []
2024
try {
2125
const resp = await fetch(url)
22-
data = (await resp.json()) as StreamData[]
26+
data = (await resp.json()) as StreamDataContent[]
2327
} catch (err) {
2428
console.warn('error loading stream data', err)
2529
}
@@ -65,33 +69,50 @@ export async function* markDataSource(dataSource: DataSource, name: string) {
6569
}
6670
}
6771

68-
export async function* combineDataSources(dataSources: DataSource[]) {
72+
export async function* combineDataSources(
73+
dataSources: DataSource[],
74+
idGen: StreamIDGenerator,
75+
) {
6976
for await (const streamLists of Repeater.latest(dataSources)) {
7077
const dataByURL = new Map<string, StreamData>()
7178
for (const list of streamLists) {
7279
for (const data of list) {
7380
const existing = dataByURL.get(data.link)
74-
dataByURL.set(data.link, { ...existing, ...data })
81+
dataByURL.set(data.link, { ...existing, ...data } as StreamData)
7582
}
7683
}
77-
const streams: StreamList = [...dataByURL.values()]
84+
85+
const streams = idGen.process([...dataByURL.values()]) as StreamList
86+
7887
// Retain the index to speed up local lookups
7988
streams.byURL = dataByURL
8089
yield streams
8190
}
8291
}
8392

84-
export class LocalStreamData extends EventEmitter {
85-
dataByURL: Map<string, Partial<StreamData>>
93+
interface LocalStreamDataEvents {
94+
update: [StreamDataContent[]]
95+
}
8696

87-
constructor() {
97+
export class LocalStreamData extends EventEmitter<LocalStreamDataEvents> {
98+
dataByURL: Map<string, StreamDataContent>
99+
100+
constructor(entries: StreamDataContent[] = []) {
88101
super()
89102
this.dataByURL = new Map()
103+
for (const entry of entries) {
104+
if (!entry.link) {
105+
continue
106+
}
107+
this.dataByURL.set(entry.link, entry)
108+
}
90109
}
91110

92-
update(url: string, data: Partial<StreamData>) {
111+
update(url: string, data: Partial<StreamDataContent>) {
93112
const existing = this.dataByURL.get(url)
94-
this.dataByURL.set(data.link ?? url, { ...existing, ...data, link: url })
113+
const kind = data.kind ?? existing?.kind ?? 'video'
114+
const updated: StreamDataContent = { ...existing, ...data, kind, link: url }
115+
this.dataByURL.set(data.link ?? url, updated)
95116
if (data.link != null && url !== data.link) {
96117
this.dataByURL.delete(url)
97118
}
@@ -107,9 +128,9 @@ export class LocalStreamData extends EventEmitter {
107128
this.emit('update', [...this.dataByURL.values()])
108129
}
109130

110-
gen(): AsyncGenerator<StreamData[]> {
131+
gen(): AsyncGenerator<StreamDataContent[]> {
111132
return new Repeater(async (push, stop) => {
112-
await push([])
133+
await push([...this.dataByURL.values()])
113134
this.on('update', push)
114135
await stop
115136
this.off('update', push)
@@ -126,7 +147,7 @@ export class StreamIDGenerator {
126147
this.idSet = new Set()
127148
}
128149

129-
process(streams: StreamData[]) {
150+
process(streams: StreamDataContent[]) {
130151
const { idMap, idSet } = this
131152

132153
for (const stream of streams) {

packages/streamwall/src/main/index.ts

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -250,9 +250,20 @@ async function main(argv: ReturnType<typeof parseArgs>) {
250250
callback(false)
251251
})
252252

253+
const db = await loadStorage(
254+
join(app.getPath('userData'), 'streamwall-storage.json'),
255+
)
256+
253257
console.debug('Creating StreamWindow...')
254258
const idGen = new StreamIDGenerator()
255-
const localStreamData = new LocalStreamData()
259+
260+
const localStreamData = new LocalStreamData(db.data.localStreamData)
261+
localStreamData.on('update', (entries) => {
262+
db.update((data) => {
263+
data.localStreamData = entries
264+
})
265+
})
266+
256267
const overlayStreamData = new LocalStreamData()
257268

258269
const streamWindowConfig = {
@@ -306,9 +317,6 @@ async function main(argv: ReturnType<typeof parseArgs>) {
306317
const stateDoc = new Y.Doc()
307318
const viewsState = stateDoc.getMap<Y.Map<string | undefined>>('views')
308319

309-
const db = await loadStorage(
310-
join(app.getPath('userData'), 'streamwall-storage.json'),
311-
)
312320
if (db.data.stateDoc) {
313321
console.log('Loading stateDoc from storage...')
314322
try {
@@ -531,11 +539,10 @@ async function main(argv: ReturnType<typeof parseArgs>) {
531539
return markDataSource(watchDataFile(path), 'toml-file')
532540
}),
533541
markDataSource(localStreamData.gen(), 'custom'),
534-
overlayStreamData.gen(),
542+
markDataSource(overlayStreamData.gen(), 'overlay'),
535543
]
536544

537-
for await (const rawStreams of combineDataSources(dataSources)) {
538-
const streams = idGen.process(rawStreams)
545+
for await (const streams of combineDataSources(dataSources, idGen)) {
539546
updateState({ streams })
540547
updateViewsFromStateDoc()
541548
}
Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,32 @@
1-
import type { Low } from 'lowdb'
1+
import { Low, Memory } from 'lowdb'
22
import { JSONFilePreset } from 'lowdb/node'
3+
import { StreamDataContent } from 'streamwall-shared'
34

45
export interface StreamwallStoredData {
56
stateDoc: string
7+
localStreamData: StreamDataContent[]
68
}
79

810
const defaultData: StreamwallStoredData = {
911
stateDoc: '',
12+
localStreamData: [],
1013
}
1114

1215
export type StorageDB = Low<StreamwallStoredData>
1316

1417
export async function loadStorage(dbPath: string) {
15-
const db = await JSONFilePreset<StreamwallStoredData>(dbPath, defaultData)
18+
let db: StorageDB | undefined = undefined
19+
20+
try {
21+
db = await JSONFilePreset<StreamwallStoredData>(dbPath, defaultData)
22+
} catch (err) {
23+
console.warn(
24+
'Failed to load storage at',
25+
dbPath,
26+
' -- changes will not be persisted',
27+
)
28+
db = new Low<StreamwallStoredData>(new Memory(), defaultData)
29+
}
30+
1631
return db
1732
}

0 commit comments

Comments
 (0)