Skip to content

Commit

Permalink
now we are there
Browse files Browse the repository at this point in the history
  • Loading branch information
rafaellehmkuhl committed Dec 9, 2024
1 parent 595c5d8 commit 5b56d98
Show file tree
Hide file tree
Showing 12 changed files with 255 additions and 65 deletions.
10 changes: 9 additions & 1 deletion electron/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { app, BrowserWindow, protocol, screen } from 'electron'
import { join } from 'path'

import { setupNetworkService } from './services/network'
import { setupFilesystemStorage } from './services/storage'

export const ROOT_PATH = {
dist: join(__dirname, '..'),
Expand Down Expand Up @@ -61,9 +62,16 @@ protocol.registerSchemesAsPrivileged([
},
])

setupFilesystemStorage()
setupNetworkService()

app.whenReady().then(createWindow)
app.whenReady().then(async () => {
console.log('Electron app is ready.')
console.log(`Cockpit version: ${app.getVersion()}`)

console.log('Creating window...')
createWindow()
})

app.on('before-quit', () => {
// @ts-ignore: import.meta.env does not exist in the types
Expand Down
20 changes: 20 additions & 0 deletions electron/preload.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,24 @@ import { contextBridge, ipcRenderer } from 'electron'

contextBridge.exposeInMainWorld('electronAPI', {
getInfoOnSubnets: () => ipcRenderer.invoke('get-info-on-subnets'),
setItem: async (key: string, value: Blob) => {
const arrayBuffer = await value.arrayBuffer()
await ipcRenderer.invoke('setItem', { key, value: new Uint8Array(arrayBuffer) })
},
getItem: async (key: string) => {
const arrayBuffer = await ipcRenderer.invoke('getItem', key)
return arrayBuffer ? new Blob([arrayBuffer]) : null
},
removeItem: async (key: string) => {
await ipcRenderer.invoke('removeItem', key)
},
clear: async () => {
await ipcRenderer.invoke('clear')
},
keys: async () => {
return await ipcRenderer.invoke('keys')
},
iterate: async (callback: (value: Blob, key: string, iterationNumber: number) => void) => {
await ipcRenderer.invoke('iterate', (_, data) => callback(data.value, data.key, data.iterationNumber))
},
})
71 changes: 71 additions & 0 deletions electron/services/storage.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import { ipcMain } from 'electron'
import { app } from 'electron'
import * as fs from 'fs/promises'
import { dirname, join } from 'path'

import { StorageDB } from '../../src/types/general'

// Create a new storage interface for filesystem
const cockpitFolderPath = join(app.getPath('home'), 'Cockpit')
fs.mkdir(cockpitFolderPath, { recursive: true })

export const filesystemStorage: StorageDB = {
async setItem(key: string, value: ArrayBuffer): Promise<void> {
const buffer = Buffer.from(value)
const filePath = join(cockpitFolderPath, key)
await fs.mkdir(dirname(filePath), { recursive: true })
await fs.writeFile(filePath, buffer)
},
async getItem(key: string): Promise<ArrayBuffer | null> {
const filePath = join(cockpitFolderPath, key)
try {
return await fs.readFile(filePath)
} catch (error) {
if (error.code === 'ENOENT') return null
throw error
}
},
async removeItem(key: string): Promise<void> {
const filePath = join(cockpitFolderPath, key)
await fs.unlink(filePath)
},
async clear(): Promise<void> {
throw new Error(
`Clear functionality is not available in the filesystem storage, so we don't risk losing important data. If you
want to clear the storage, please delete the Cockpit folder in your user data directory manually.`
)
},
async keys(): Promise<string[]> {
const dirPath = cockpitFolderPath
try {
return await fs.readdir(dirPath)
} catch (error) {
if (error.code === 'ENOENT') return []
throw error
}
},
async iterate(callback: (value: unknown, key: string, iterationNumber: number) => void): Promise<void> {
throw new Error('Iterate functionality is not available in the filesystem storage.')
},
}

export const setupFilesystemStorage = (): void => {
ipcMain.handle('setItem', async (_, data) => {
await filesystemStorage.setItem(data.key, data.value)
})
ipcMain.handle('getItem', async (_, key) => {
return await filesystemStorage.getItem(key)
})
ipcMain.handle('removeItem', async (_, key) => {
await filesystemStorage.removeItem(key)
})
ipcMain.handle('clear', async () => {
await filesystemStorage.clear()
})
ipcMain.handle('keys', async () => {
return await filesystemStorage.keys()
})
ipcMain.handle('iterate', async (_, callback) => {
await filesystemStorage.iterate(callback)
})
}
9 changes: 5 additions & 4 deletions src/components/VideoLibraryModal.vue
Original file line number Diff line number Diff line change
Expand Up @@ -889,11 +889,12 @@ const fetchVideosAndLogData = async (): Promise<void> => {
const logFileOperations: Promise<VideoLibraryLogFile>[] = []
// Fetch processed videos and logs
await videoStore.videoStoringDB.iterate((value, key) => {
const keys = await videoStore.videoStorage.keys()
for (const key of keys) {
if (videoStore.isVideoFilename(key)) {
videoFilesOperations.push(
(async () => {
const videoBlob = await videoStore.videoStoringDB.getItem<Blob>(key)
const videoBlob = await videoStore.videoStorage.getItem(key)
let url = ''
let isProcessed = true
if (videoBlob instanceof Blob) {
Expand All @@ -910,7 +911,7 @@ const fetchVideosAndLogData = async (): Promise<void> => {
if (key.endsWith('.ass')) {
logFileOperations.push(
(async () => {
const videoBlob = await videoStore.videoStoringDB.getItem<Blob>(key)
const videoBlob = await videoStore.videoStorage.getItem(key)
let url = ''
if (videoBlob instanceof Blob) {
url = URL.createObjectURL(videoBlob)
Expand All @@ -923,7 +924,7 @@ const fetchVideosAndLogData = async (): Promise<void> => {
})()
)
}
})
}
// Fetch unprocessed videos
const unprocessedVideos = await videoStore.unprocessedVideos
Expand Down
3 changes: 2 additions & 1 deletion src/components/mini-widgets/MiniVideoRecorder.vue
Original file line number Diff line number Diff line change
Expand Up @@ -188,7 +188,8 @@ watch(nameSelectedStream, (newName) => {
// Fetch number of temporary videos on storage
const fetchNumberOfTempVideos = async (): Promise<void> => {
const nProcessedVideos = (await videoStore.videoStoringDB.keys()).filter((k) => videoStore.isVideoFilename(k)).length
const keys = await videoStore.videoStorage.keys()
const nProcessedVideos = keys.filter((k) => videoStore.isVideoFilename(k)).length
const nFailedUnprocessedVideos = Object.keys(videoStore.keysFailedUnprocessedVideos).length
numberOfVideosOnDB.value = nProcessedVideos + nFailedUnprocessedVideos
}
Expand Down
31 changes: 30 additions & 1 deletion src/libs/cosmos.ts
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,36 @@ declare global {
registerActionCallback: typeof registerActionCallback
unregisterActionCallback: typeof unregisterActionCallback
executeActionCallback: typeof executeActionCallback
/* eslint-enable jsdoc/require-jsdoc */
}
/**
* Electron API for update management
*/
electronAPI: {
/**
* Set an item in the filesystem storage
*/
setItem: (key: string, value: Blob) => Promise<void>
/**
* Get an item from the filesystem storage
*/
getItem: (key: string) => Promise<ArrayBuffer | null | undefined>
/**
* Remove an item from the filesystem storage
*/
removeItem: (key: string) => Promise<void>
/**
* Clear the filesystem storage
*/
clear: () => Promise<void>
/**
* Get all keys from the filesystem storage
*/
keys: () => Promise<string[]>
/**
* Iterate over the items in the filesystem storage
*/
iterate: (callback: (value: Blob, key: string, iterationNumber: number) => void) => Promise<void>
}
/**
* Electron API exposed through preload script
Expand All @@ -115,7 +145,6 @@ declare global {
getInfoOnSubnets: () => Promise<NetworkInfo[]>
}
}
/* eslint-enable jsdoc/require-jsdoc */
}

// Use global as window when running for browsers
Expand Down
42 changes: 42 additions & 0 deletions src/libs/electron/filesystemStorageRendererAPI.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import type { StorageDB } from '@/types/general'

import { isElectron } from '../utils'

const throwIfNotElectron = (): void => {
if (!isElectron()) {
console.warn('Filesystem storage is only available in Electron.')
return
}
if (!window.electronAPI) {
console.error('electronAPI is not available on window object')
console.debug('Available window properties:', Object.keys(window))
throw new Error('Electron filesystem API is not properly initialized. This is likely a setup issue.')
}
}

export const electronStorage: StorageDB = {
setItem: async (key: string, value: Blob): Promise<void> => {
throwIfNotElectron()
await window.electronAPI.setItem(key, value)
},
getItem: async (key: string): Promise<ArrayBuffer | null | undefined> => {
throwIfNotElectron()
return await window.electronAPI.getItem(key)
},
removeItem: async (key: string): Promise<void> => {
throwIfNotElectron()
await window.electronAPI.removeItem(key)
},
clear: async (): Promise<void> => {
throwIfNotElectron()
await window.electronAPI.clear()
},
keys: async (): Promise<string[]> => {
throwIfNotElectron()
return await window.electronAPI.keys()
},
iterate: async (callback: (value: Blob, key: string, iterationNumber: number) => void): Promise<void> => {
throwIfNotElectron()
await window.electronAPI.iterate(callback)
},
}
9 changes: 2 additions & 7 deletions src/libs/utils.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,5 @@
/* eslint-disable @typescript-eslint/no-explicit-any */

import { useInteractionDialog } from '@/composables/interactionDialog'

const { showDialog } = useInteractionDialog()

export const constrain = (value: number, min: number, max: number): number => {
return Math.max(Math.min(value, max), min)
}
Expand Down Expand Up @@ -135,7 +131,7 @@ export const tryOrAlert = async (tryFunction: () => Promise<void>): Promise<void
try {
await tryFunction()
} catch (error) {
showDialog({ message: error as string, variant: 'error' })
console.error(error as string)
}
}

Expand All @@ -145,8 +141,7 @@ export const tryOrAlert = async (tryFunction: () => Promise<void>): Promise<void
*/
export const reloadCockpit = (timeout = 500): void => {
const restartMessage = `Restarting Cockpit in ${timeout / 1000} seconds...`
console.log(restartMessage)
showDialog({ message: restartMessage, variant: 'info', timer: timeout })
console.info(restartMessage)
setTimeout(() => location.reload(), timeout)
}

Expand Down
25 changes: 25 additions & 0 deletions src/libs/videoStorage.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import localforage from 'localforage'

import { electronStorage } from '@/libs/electron/filesystemStorageRendererAPI'
import { StorageDB } from '@/types/general'

import { isElectron } from './utils'

const tempVideoChunksIndexdedDB = localforage.createInstance({
driver: localforage.INDEXEDDB,
name: 'Cockpit - Temporary Video',
storeName: 'cockpit-temp-video-db',
version: 1.0,
description: 'Database for storing the chunks of an ongoing recording, to be merged afterwards.',
})

const videoStoringIndexedDB = localforage.createInstance({
driver: localforage.INDEXEDDB,
name: 'Cockpit - Video Recovery',
storeName: 'cockpit-video-recovery-db',
version: 1.0,
description: 'Local backups of Cockpit video recordings to be retrieved in case of failure.',
})

export const videoStorage: StorageDB = isElectron() ? electronStorage : videoStoringIndexedDB
export const tempVideoStorage: StorageDB = isElectron() ? electronStorage : tempVideoChunksIndexdedDB
Loading

0 comments on commit 5b56d98

Please sign in to comment.