From b3f787a9b26fab5d2212d4f6bc614827a8883a0d Mon Sep 17 00:00:00 2001 From: Maximilian Schiller Date: Thu, 23 Oct 2025 20:10:21 +0200 Subject: [PATCH 1/3] feat: watch for resource file changes and update in surf --- app/package.json | 1 + app/src/main/index.ts | 3 +- app/src/main/ipcHandlers.ts | 11 + app/src/main/sffs.ts | 19 +- app/src/main/surfBackend.ts | 113 +++++- app/src/main/watcher.ts | 373 ++++++++++++++++++ app/src/preload/core.ts | 30 +- app/src/preload/helpers/backend.ts | 12 + .../renderer/Core/handlers/importerEvents.ts | 73 ++++ packages/backend/src/api/message.rs | 1 + packages/backend/src/api/store.rs | 14 + packages/backend/src/store/resources.rs | 16 + .../backend/src/worker/handlers/resource.rs | 27 ++ packages/services/src/lib/ipc/events.ts | 4 +- .../lib/notebooks/notebookManager.svelte.ts | 7 +- .../src/lib/resources/resources.svelte.ts | 26 +- packages/services/src/lib/sffs.ts | 25 ++ packages/types/src/sffs.types.ts | 11 + yarn.lock | 75 +++- 19 files changed, 820 insertions(+), 21 deletions(-) create mode 100644 app/src/main/watcher.ts diff --git a/app/package.json b/app/package.json index d02648319..6b870bdc6 100644 --- a/app/package.json +++ b/app/package.json @@ -37,6 +37,7 @@ "electron-drag-click": "^1.0.5", "electron-updater": "^6.1.7", "iconify-icon": "^2.1.0", + "node-watch": "^0.7.4", "sharp": "^0.33.5", "svelte-dnd-list": "^0.1.8", "svelte-portal": "^2.2.1" diff --git a/app/src/main/index.ts b/app/src/main/index.ts index 90d9c2ffd..4f560b3a6 100644 --- a/app/src/main/index.ts +++ b/app/src/main/index.ts @@ -181,8 +181,7 @@ const setupBackendServer = async (appPath: string, backendRootPath: string, user surfBackendManager.start() await surfBackendManager.waitForStart() - - initializeSFFSMain() + surfBackendManager.initBackend() } const initializeApp = async () => { diff --git a/app/src/main/ipcHandlers.ts b/app/src/main/ipcHandlers.ts index 87004e4a3..20a572bce 100644 --- a/app/src/main/ipcHandlers.ts +++ b/app/src/main/ipcHandlers.ts @@ -8,6 +8,7 @@ import { handleDragStart } from './drag' import { BrowserType, ElectronAppInfo, + ResourceFileChange, RightSidebarTab, SFFSResource, UserSettings @@ -716,5 +717,15 @@ export const ipcSenders = { } IPC_EVENTS_MAIN.updateViewBounds.sendToWebContents(window.webContents, { viewId, bounds }) + }, + + resourceFileChange(data: ResourceFileChange) { + const window = getMainWindow() + if (!window) { + log.error('Main window not found') + return + } + + IPC_EVENTS_MAIN.resourceFileChange.sendToWebContents(window.webContents, data) } } diff --git a/app/src/main/sffs.ts b/app/src/main/sffs.ts index 12c5f9fd9..e89dfa279 100644 --- a/app/src/main/sffs.ts +++ b/app/src/main/sffs.ts @@ -107,9 +107,26 @@ export class SFFSMain { const stringified = JSON.stringify(resource) - const result = this.sffs.js__store_update_resource(stringified) + const result = await this.sffs.js__store_update_resource(stringified) return result } + + async deleteResource(id: string) { + console.debug('deleting resource with id', id) + await this.sffs.js__store_remove_resources([id]) + } + + async getResourceByPath(path: string): Promise { + console.log('getting resource by path', path) + const dataString = await this.sffs.js__store_get_resource_by_path(path) + + const composite = optimisticParseJSON(dataString) + if (!composite) { + return null + } + + return SFFSMain.convertCompositeResourceToResource(composite) + } } let sffsMain: SFFSMain | null = null diff --git a/app/src/main/surfBackend.ts b/app/src/main/surfBackend.ts index bc62a921c..93cf98555 100644 --- a/app/src/main/surfBackend.ts +++ b/app/src/main/surfBackend.ts @@ -2,7 +2,12 @@ import { isWindows } from '@deta/utils' import { spawn, type ChildProcess, execSync } from 'child_process' import EventEmitter from 'events' -import { basename } from 'path' +import path, { basename } from 'path' +import { FileWatcher } from './watcher' +import { app } from 'electron' +import { initializeSFFSMain, SFFSMain } from './sffs' +import { SFFSRawResource } from '@deta/types' +import { ipcSenders } from './ipcHandlers' export class SurfBackendServerManager extends EventEmitter { private process: ChildProcess | null = null @@ -17,6 +22,9 @@ export class SurfBackendServerManager extends EventEmitter { private startResolve: (() => void) | null = null private startReject: ((reason: Error) => void) | null = null + private sffs: SFFSMain | null = null + private watcher: FileWatcher | null = null + constructor( private readonly serverPath: string, private readonly args: string[], @@ -29,6 +37,100 @@ export class SurfBackendServerManager extends EventEmitter { return this.lastKnownHealth } + initWatcher() { + const USER_DATA_PATH = app.getPath('userData') + const BACKEND_ROOT_PATH = path.join(USER_DATA_PATH, 'sffs_backend') + const BACKEND_RESOURCES_PATH = path.join(BACKEND_ROOT_PATH, 'resources') + + this.watcher = new FileWatcher(BACKEND_RESOURCES_PATH) + + this.watcher + .on('create', ({ filename, path }) => { + console.log(`File created: ${filename} at ${path}`) + // this.handleFileCreate({ filename, path }); + ipcSenders.resourceFileChange({ + type: 'create', + data: { newName: filename, newPath: path } + }) + }) + .on('delete', ({ filename, path }) => { + console.log(`File deleted: ${filename} at ${path}`) + // this.handleFileDelete({ filename, path }); + ipcSenders.resourceFileChange({ + type: 'delete', + data: { oldName: filename, oldPath: path } + }) + }) + .on('rename', ({ oldName, newName, oldPath, newPath }) => { + ipcSenders.resourceFileChange({ + type: 'rename', + data: { oldName, newName, oldPath, newPath } + }) + }) + } + + async handleFileRename({ + oldName, + newName, + oldPath, + newPath + }: { + oldName: string + newName: string + oldPath: string + newPath: string + }) { + try { + console.log(`File renamed: ${oldName} -> ${newName}`) + + if (!this.sffs) { + console.warn('SFFS not initialized. Cannot update resource path.') + return + } + + const resource = await this.sffs.getResourceByPath(oldPath) + if (resource) { + console.log( + 'Updating resource path in SFFS for renamed file', + resource.id, + oldPath, + newPath + ) + await this.sffs.updateResource({ + id: resource.id, + resource_path: newPath, + resource_type: resource.type, + created_at: resource.createdAt, + updated_at: new Date().toISOString(), + deleted: resource.deleted ? 1 : 0 + } satisfies SFFSRawResource) + console.log(`Updated resource path in SFFS: ${oldPath} -> ${newPath}`) + } + } catch (error) { + console.error('Error updating resource path in SFFS:', error) + } + } + + async handleFileDelete({ filename, path }: { filename: string; path: string }) { + try { + console.log(`File deleted: ${filename} at ${path}`) + + if (!this.sffs) { + console.warn('SFFS not initialized. Cannot delete resource.') + return + } + + const resource = await this.sffs.getResourceByPath(path) + if (resource) { + console.log('Deleting resource in SFFS for deleted file', resource.id, path) + await this.sffs.deleteResource(resource.id) + console.log(`Deleted resource in SFFS for file: ${path}`) + } + } catch (error) { + console.error('Error deleting resource in SFFS:', error) + } + } + start(): void { if (this.process) { this.emit('warn', 'surf backend server is already running') @@ -41,6 +143,11 @@ export class SurfBackendServerManager extends EventEmitter { this.isShuttingDown = false } + initBackend() { + this.sffs = initializeSFFSMain() + this.initWatcher() + } + waitForStart(): Promise { if (!this.startPromise) throw new Error('server has not been started') return this.startPromise @@ -67,6 +174,10 @@ export class SurfBackendServerManager extends EventEmitter { return } + if (this.watcher) { + this.watcher.close() + } + this.isShuttingDown = true this.process.kill() this.process = null diff --git a/app/src/main/watcher.ts b/app/src/main/watcher.ts new file mode 100644 index 000000000..b5bcc1e05 --- /dev/null +++ b/app/src/main/watcher.ts @@ -0,0 +1,373 @@ +import fs from 'fs' +import path from 'path' + +interface FileWatcherOptions { + debounceDelay?: number + renameTimeout?: number + useInodeTracking?: boolean // Use inode for rename detection (Unix only) +} + +interface CreateEvent { + filename: string + path: string +} + +interface DeleteEvent { + filename: string + path: string +} + +interface RenameEvent { + oldName: string + newName: string + oldPath: string + newPath: string +} + +type EventCallback = (data: T) => void + +interface PendingEvent { + type: string + filename: string + timestamp: number +} + +export class FileWatcher { + private directory: string + private debounceDelay: number + private renameTimeout: number + private listeners: { + create: EventCallback[] + delete: EventCallback[] + rename: EventCallback[] + } + + private files: Map + private pendingEvents: PendingEvent[] + private debounceTimer: NodeJS.Timeout | null + private watcher: fs.FSWatcher | null + private isProcessing: boolean + private useInodeTracking: boolean + private inodeToFilename: Map + + constructor(directory: string, options: FileWatcherOptions = {}) { + this.directory = path.resolve(directory) + this.debounceDelay = options.debounceDelay || 100 + this.renameTimeout = options.renameTimeout || 50 + this.useInodeTracking = options.useInodeTracking !== false // Default true + this.listeners = { + create: [], + delete: [], + rename: [] + } + + this.files = new Map() + this.inodeToFilename = new Map() + this.pendingEvents = [] + this.debounceTimer = null + this.watcher = null + this.isProcessing = false + + this.initialize() + } + + private initialize(): void { + // Validate directory exists + if (!fs.existsSync(this.directory)) { + throw new Error(`Directory does not exist: ${this.directory}`) + } + + if (!fs.statSync(this.directory).isDirectory()) { + throw new Error(`Path is not a directory: ${this.directory}`) + } + + // Build initial file list with metadata + this.scanDirectory() + + // Start watching + try { + this.watcher = fs.watch(this.directory, (eventType, filename) => { + if (!filename) return + + this.pendingEvents.push({ + type: eventType, + filename, + timestamp: Date.now() + }) + + // Debounce event processing + if (this.debounceTimer) { + clearTimeout(this.debounceTimer) + } + + this.debounceTimer = setTimeout(() => { + this.processEvents() + }, this.debounceDelay) + }) + } catch (err) { + throw new Error(`Failed to watch directory: ${err}`) + } + } + + private scanDirectory(): void { + try { + const entries = fs.readdirSync(this.directory) + + for (const file of entries) { + const fullPath = path.join(this.directory, file) + try { + const stats = fs.statSync(fullPath) + if (stats.isFile()) { + const metadata = { + mtime: stats.mtimeMs, + size: stats.size, + ...(this.useInodeTracking && { ino: stats.ino }) + } + this.files.set(file, metadata) + + if (this.useInodeTracking && stats.ino !== undefined) { + this.inodeToFilename.set(stats.ino, file) + } + } + } catch (err) { + // File might have been deleted during scan, skip it + continue + } + } + } catch (err) { + throw new Error(`Error scanning directory: ${err}`) + } + } + + private async processEvents(): Promise { + // Prevent concurrent processing + if (this.isProcessing) { + return + } + + this.isProcessing = true + + try { + const events = [...this.pendingEvents] + this.pendingEvents = [] + + // Small delay to catch related events (e.g., rename operations) + await new Promise((resolve) => setTimeout(resolve, this.renameTimeout)) + + // Get current directory state + const currentFiles = new Map() + const currentInodeToFilename = new Map() + + try { + const entries = fs.readdirSync(this.directory) + + for (const file of entries) { + const fullPath = path.join(this.directory, file) + try { + const stats = fs.statSync(fullPath) + if (stats.isFile()) { + const metadata = { + mtime: stats.mtimeMs, + size: stats.size, + ...(this.useInodeTracking && { ino: stats.ino }) + } + currentFiles.set(file, metadata) + + if (this.useInodeTracking && stats.ino !== undefined) { + currentInodeToFilename.set(stats.ino, file) + } + } + } catch (err) { + // File might have been deleted during scan + continue + } + } + } catch (err) { + console.error('Error reading directory:', err) + this.isProcessing = false + return + } + + // Detect changes + const created: string[] = [] + const deleted: string[] = [] + const renamed: Array<{ oldName: string; newName: string }> = [] + + // Use inode tracking for accurate rename detection (Unix systems) + if (this.useInodeTracking) { + // Find files that changed names but kept same inode (renames) + for (const [oldFilename, oldMetadata] of this.files.entries()) { + if (oldMetadata.ino !== undefined) { + const newFilename = currentInodeToFilename.get(oldMetadata.ino) + + if (newFilename && newFilename !== oldFilename) { + // Same inode, different name = rename + renamed.push({ oldName: oldFilename, newName: newFilename }) + } else if (!newFilename) { + // Inode disappeared = delete + deleted.push(oldFilename) + } + // else: same filename, same inode = no change (not a create/delete) + } else if (!currentFiles.has(oldFilename)) { + // No inode info but file gone = delete + deleted.push(oldFilename) + } + } + + // Find truly new files (new inodes) + for (const [newFilename, newMetadata] of currentFiles.entries()) { + if (newMetadata.ino !== undefined) { + const wasRenamed = renamed.some((r) => r.newName === newFilename) + if (!this.inodeToFilename.has(newMetadata.ino) && !wasRenamed) { + // New inode = new file + created.push(newFilename) + } + } else if (!this.files.has(newFilename)) { + // No inode info but new name = create + created.push(newFilename) + } + } + } else { + // Fallback: name-based detection with size heuristic + // Find created files + for (const [filename, metadata] of currentFiles.entries()) { + if (!this.files.has(filename)) { + created.push(filename) + } + } + + // Find deleted files + for (const filename of this.files.keys()) { + if (!currentFiles.has(filename)) { + deleted.push(filename) + } + } + + // Try to match renames by size (less reliable) + if (created.length > 0 && deleted.length > 0) { + const pairs = this.matchRenames(deleted, created, currentFiles) + renamed.push(...pairs) + + // Remove matched pairs from created/deleted lists + const renamedOld = new Set(pairs.map((p) => p.oldName)) + const renamedNew = new Set(pairs.map((p) => p.newName)) + + created.splice(0, created.length, ...created.filter((f) => !renamedNew.has(f))) + deleted.splice(0, deleted.length, ...deleted.filter((f) => !renamedOld.has(f))) + } + } + + // Emit events + for (const { oldName, newName } of renamed) { + this.emit('rename', { + oldName, + newName, + oldPath: path.join(this.directory, oldName), + newPath: path.join(this.directory, newName) + }) + } + + for (const filename of created) { + this.emitCreate(filename, currentFiles.get(filename)!) + } + + for (const filename of deleted) { + this.emitDelete(filename) + } + + // Update tracked files and inodes + this.files = currentFiles + this.inodeToFilename = currentInodeToFilename + } finally { + this.isProcessing = false + } + } + + private matchRenames( + deleted: string[], + created: string[], + currentFiles: Map + ): Array<{ oldName: string; newName: string }> { + const pairs: Array<{ oldName: string; newName: string }> = [] + const usedDeleted = new Set() + const usedCreated = new Set() + + // Try to match by size (fallback when inode tracking unavailable) + // Note: This is unreliable and can produce false positives + for (const oldName of deleted) { + const oldMetadata = this.files.get(oldName) + if (!oldMetadata) continue + + for (const newName of created) { + if (usedCreated.has(newName)) continue + + const newMetadata = currentFiles.get(newName) + if (!newMetadata) continue + + // Match by size (weak indicator for renames) + // This can fail when: + // 1. Different files have same size + // 2. File is renamed AND content changes + if (oldMetadata.size === newMetadata.size) { + pairs.push({ oldName, newName }) + usedDeleted.add(oldName) + usedCreated.add(newName) + break + } + } + } + + return pairs + } + + private emitCreate(filename: string, metadata: { mtime: number; size: number }): void { + this.emit('create', { + filename, + path: path.join(this.directory, filename) + }) + } + + private emitDelete(filename: string): void { + this.emit('delete', { + filename, + path: path.join(this.directory, filename) + }) + } + + public on(event: 'create', callback: EventCallback): this + public on(event: 'delete', callback: EventCallback): this + public on(event: 'rename', callback: EventCallback): this + public on(event: string, callback: EventCallback): this { + if (this.listeners[event as keyof typeof this.listeners]) { + this.listeners[event as keyof typeof this.listeners].push(callback) + } + return this + } + + private emit(event: keyof typeof this.listeners, data: T): void { + const callbacks = this.listeners[event] + for (const callback of callbacks) { + try { + callback(data as any) + } catch (err) { + console.error(`Error in ${event} listener:`, err) + } + } + } + + public close(): void { + if (this.watcher) { + this.watcher.close() + this.watcher = null + } + + if (this.debounceTimer) { + clearTimeout(this.debounceTimer) + this.debounceTimer = null + } + + this.listeners.create = [] + this.listeners.delete = [] + this.listeners.rename = [] + } +} diff --git a/app/src/preload/core.ts b/app/src/preload/core.ts index 1029c5240..a242dfa7e 100644 --- a/app/src/preload/core.ts +++ b/app/src/preload/core.ts @@ -27,7 +27,8 @@ import { WebContentsViewManagerActionOutputs, WebContentsViewActionOutputs, RendererType, - type ControlWindow + type ControlWindow, + ResourceFileChange } from '@deta/types' import { @@ -480,6 +481,33 @@ const eventHandlers = { }) }, + onResourceFileChange: ( + callback: ( + type: ResourceFileChange['type'], + data: ResourceFileChange['data'], + file?: ResourceFileChange['file'] + ) => void + ) => { + return IPC_EVENTS_RENDERER.resourceFileChange.on(async (_, { type, data }) => { + try { + let file: File | undefined = undefined + + if (type === 'create' && data.newPath) { + const fileBuffer = await fsp.readFile(data.newPath) + const fileName = path.basename(data.newPath) + const fileType = mime.lookup(fileName.toLowerCase()) || 'application/octet-stream' + file = new File([fileBuffer as any], fileName, { + type: fileType + }) + } + + callback(type, data, file) + } catch (error) { + // noop + } + }) + }, + onWebContentsViewEvent: (callback: (event: WebContentsViewEvent) => void) => { return IPC_EVENTS_RENDERER.webContentsViewEvent.on((_, event) => { try { diff --git a/app/src/preload/helpers/backend.ts b/app/src/preload/helpers/backend.ts index 779b74db1..83d42c590 100644 --- a/app/src/preload/helpers/backend.ts +++ b/app/src/preload/helpers/backend.ts @@ -535,6 +535,10 @@ export class ResourceHandle { await this.fd.close() await this.onWriteHappened() } + + async delete(): Promise { + await fsp.unlink(this.filePath) + } } export const initResources = (sffs: ReturnType, opts?: SFFSOptions) => { @@ -590,6 +594,13 @@ export const initResources = (sffs: ReturnType, opts?: SFFSOpti resourceHandles.delete(resourceId) } + async function deleteResource(resourceId: string) { + const resourceHandle = resourceHandles.get(resourceId) + if (!resourceHandle) throw new Error('resource handle is not open') + + await resourceHandle.delete() + } + async function triggerPostProcessing(resourceId: string) { await (sffs as any).js__store_resource_post_process(resourceId) } @@ -620,6 +631,7 @@ export const initResources = (sffs: ReturnType, opts?: SFFSOpti writeResource, flushResource, closeResource, + deleteResource, updateResourceHash, triggerPostProcessing } diff --git a/app/src/renderer/Core/handlers/importerEvents.ts b/app/src/renderer/Core/handlers/importerEvents.ts index 450bbcaec..0c530845d 100644 --- a/app/src/renderer/Core/handlers/importerEvents.ts +++ b/app/src/renderer/Core/handlers/importerEvents.ts @@ -17,4 +17,77 @@ export const setupImportEvents = (events: PreloadEvents) => { log.error('Failed to import', err) } }) + + const ignorePaths = new Set() + + events.onResourceFileChange(async (type, data, file) => { + try { + log.debug('Resource file change detected', type, data, file) + if (type === 'rename') { + const { oldPath, newPath, newName } = data + if (oldPath && newPath) { + const resource = await resourceManager.getResourceByPath(oldPath) + if (resource) { + log.debug('Updating resource path for renamed file', resource.id, oldPath, newPath) + await resourceManager.updateResource(resource.id, { + resource_path: newPath, + updated_at: new Date().toISOString() + }) + + if (!resource.type.startsWith('application/vnd.space')) { + await resourceManager.updateResourceMetadata(resource.id, { + name: newName || resource.metadata.name + }) + } + + log.debug(`Updated resource path: ${oldPath} -> ${newPath}`) + } + } + } else if (type === 'delete') { + const { oldPath } = data + if (oldPath) { + const resource = await resourceManager.getResourceByPath(oldPath) + if (resource) { + log.debug('Deleting resource for deleted file', resource.id, oldPath) + await resourceManager.deleteResource(resource.id) + log.debug(`Deleted resource for file: ${oldPath}`) + } + } + } else if (type === 'create' && file) { + const { newPath } = data + + if (ignorePaths.has(newPath)) { + log.debug('Ignoring resource file change for path', newPath) + ignorePaths.delete(newPath) + return + } + + const existingResource = await resourceManager.getResourceByPath(newPath) + if (existingResource) { + log.debug('Resource already exists for created file, skipping', existingResource) + return + } + + const newResources = await createResourcesFromFiles([file], resourceManager) + const resource = newResources?.[0] + if (!resource) { + log.warn('No resource created for new file', newPath) + return + } + + const oldPath = resource.path + ignorePaths.add(oldPath) + + log.debug('Created resource for new file', resource) + + log.debug('Deleting temporary resource file and updating resource path', oldPath, newPath) + await resource.deleteDataFile() + await resourceManager.updateResource(resource.id, { + resource_path: newPath + }) + } + } catch (error) { + log.error('Error handling resource file change', error) + } + }) } diff --git a/packages/backend/src/api/message.rs b/packages/backend/src/api/message.rs index 5dabdad9d..3f2c41c0c 100644 --- a/packages/backend/src/api/message.rs +++ b/packages/backend/src/api/message.rs @@ -111,6 +111,7 @@ pub enum ResourceMessage { resource_metadata: Option, }, GetResource(String, bool), + GetResourceByPath(String), RemoveResources(Vec), RemoveResourcesByTags(Vec), RecoverResource(String), diff --git a/packages/backend/src/api/store.rs b/packages/backend/src/api/store.rs index 32fe3976c..d2363f4c8 100644 --- a/packages/backend/src/api/store.rs +++ b/packages/backend/src/api/store.rs @@ -6,6 +6,7 @@ use neon::types::JsDate; pub fn register_exported_functions(cx: &mut ModuleContext) -> NeonResult<()> { cx.export_function("js__store_create_resource", js_create_resource)?; cx.export_function("js__store_get_resource", js_get_resource)?; + cx.export_function("js__store_get_resource_by_path", js_get_resource_by_path)?; // cx.export_function("js__store_update_resource", js_update_resource)?; cx.export_function("js__store_remove_resources", js_remove_resources)?; cx.export_function( @@ -409,6 +410,19 @@ fn js_get_resource(mut cx: FunctionContext) -> JsResult { Ok(promise) } +fn js_get_resource_by_path(mut cx: FunctionContext) -> JsResult { + let tunnel = cx.argument::>(0)?; + let resource_path = cx.argument::(1)?.value(&mut cx); + + let (deferred, promise) = cx.promise(); + tunnel.worker_send_js( + WorkerMessage::ResourceMessage(ResourceMessage::GetResourceByPath(resource_path)), + deferred, + ); + + Ok(promise) +} + fn js_remove_resources(mut cx: FunctionContext) -> JsResult { let tunnel = cx.argument::>(0)?; let resource_ids = cx.argument::(1)?.to_vec(&mut cx)?; diff --git a/packages/backend/src/store/resources.rs b/packages/backend/src/store/resources.rs index ee44b2184..228d49f10 100644 --- a/packages/backend/src/store/resources.rs +++ b/packages/backend/src/store/resources.rs @@ -88,6 +88,22 @@ impl Database { .optional()?) } + pub fn get_resource_by_path(&self, path: &str) -> BackendResult> { + let mut stmt = self.conn.prepare("SELECT id, resource_path, resource_type, created_at, updated_at, deleted FROM resources WHERE resource_path = ?1")?; + Ok(stmt + .query_row(rusqlite::params![path], |row| { + Ok(Resource { + id: row.get(0)?, + resource_path: row.get(1)?, + resource_type: row.get(2)?, + created_at: row.get(3)?, + updated_at: row.get(4)?, + deleted: row.get(5)?, + }) + }) + .optional()?) + } + pub fn remove_resources_tx( tx: &mut rusqlite::Transaction, ids: &[String], diff --git a/packages/backend/src/worker/handlers/resource.rs b/packages/backend/src/worker/handlers/resource.rs index cd15596bd..d36055179 100644 --- a/packages/backend/src/worker/handlers/resource.rs +++ b/packages/backend/src/worker/handlers/resource.rs @@ -168,6 +168,29 @@ impl Worker { })) } + #[instrument(level = "trace", skip(self))] + fn read_resource_by_path(&mut self, path: &str) -> BackendResult> { + let resource = match self.db.get_resource_by_path(path)? { + Some(data) => data, + None => return Ok(None), + }; + let metadata = self.db.get_resource_metadata_by_resource_id(&resource.id)?; + let processing_state = self.db.get_resource_processing_state(&resource.id)?; + let space_ids = self.db.list_space_ids_by_resource_id(&resource.id)?; + let resource_tags = self.db.list_resource_tags(&resource.id)?; + let resource_tags = (!resource_tags.is_empty()).then_some(resource_tags); + + Ok(Some(CompositeResource { + resource, + metadata, + text_content: None, + resource_tags, + resource_annotations: None, + post_processing_job: processing_state, + space_ids: Some(space_ids), + })) + } + #[instrument(level = "trace", skip(self))] pub fn remove_resources(&mut self, ids: Vec) -> BackendResult<()> { if ids.is_empty() { @@ -741,6 +764,10 @@ pub fn handle_resource_message( let result = worker.read_resource(&id, include_annotations); send_worker_response(&mut worker.channel, oneshot, result); } + ResourceMessage::GetResourceByPath(path) => { + let result = worker.read_resource_by_path(&path); + send_worker_response(&mut worker.channel, oneshot, result); + } ResourceMessage::RemoveResources(ids) => { let result = worker.remove_resources(ids); send_worker_response(&mut worker.channel, oneshot, result); diff --git a/packages/services/src/lib/ipc/events.ts b/packages/services/src/lib/ipc/events.ts index 852035240..8d9e61451 100644 --- a/packages/services/src/lib/ipc/events.ts +++ b/packages/services/src/lib/ipc/events.ts @@ -13,7 +13,8 @@ import type { WebContentsViewEvent, WebContentsViewManagerActionEvent, WebContentsViewActionEvent, - ControlWindow + ControlWindow, + ResourceFileChange } from '@deta/types' import { createIPCService, type IPCEvent } from './ipc' @@ -180,6 +181,7 @@ const IPC_EVENTS = ipcService.registerEvents({ webContentsViewEvent: ipcService.addEvent('webcontentsview-event'), focusMainRenderer: ipcService.addEvent('focus-main-renderer'), updateViewBounds: ipcService.addEvent('update-view-bounds'), + resourceFileChange: ipcService.addEvent('resource-file-change'), // events that return a value getAdblockerState: ipcService.addEventWithReturn('get-adblocker-state'), diff --git a/packages/services/src/lib/notebooks/notebookManager.svelte.ts b/packages/services/src/lib/notebooks/notebookManager.svelte.ts index d66038979..483dba9c8 100644 --- a/packages/services/src/lib/notebooks/notebookManager.svelte.ts +++ b/packages/services/src/lib/notebooks/notebookManager.svelte.ts @@ -118,7 +118,7 @@ export class NotebookManager extends EventEmitterBase { - this.log.debug('Resource updated, refreshing affected notebooks:', resource.id) + console.log('Resource updated, refreshing affected notebooks:', resource.id) // Find notebooks that contain this resource and refresh their contents for (const notebook of this.notebooks.values()) { @@ -134,11 +134,12 @@ export class NotebookManager extends EventEmitterBase + useViewManager()?.viewsValue.forEach((view) => { + console.log('xxxx-sending resource update to view', view.id, resource.id) this.messagePort.extern_state_resourceUpdated.send(view.id, { resourceId: resource.id }) - ) + }) } else { this.messagePort.extern_state_resourceUpdated.send({ resourceId: resource.id diff --git a/packages/services/src/lib/resources/resources.svelte.ts b/packages/services/src/lib/resources/resources.svelte.ts index 10b0447d0..986f91900 100644 --- a/packages/services/src/lib/resources/resources.svelte.ts +++ b/packages/services/src/lib/resources/resources.svelte.ts @@ -549,6 +549,12 @@ export class Resource extends EventEmitterBase { } } + deleteDataFile() { + this.log.debug('deleting resource data file at', this.path) + + return this.sffs.deleteDataFile(this.id, this.type, this.path) + } + updateExtractionState(state: ResourceState) { this.extractionState.set(state) } @@ -754,7 +760,7 @@ export class ResourceManager extends EventEmitterBase, 'created_at' | 'updated_at' | 'deleted'> + data: Pick, 'created_at' | 'updated_at' | 'deleted' | 'resource_path'> ) { const resource = await this.getResource(id) if (!resource) { @@ -1328,6 +1346,10 @@ export class ResourceManager extends EventEmitterBase { + this.log.debug('getting resource by path', path) + const dataString = await this.backend.js__store_get_resource_by_path(path) + + const composite = this.parseData(dataString) + if (!composite) { + return null + } + + return this.convertCompositeResourceToResource(composite) + } + async updateResource(resource: SFFSRawResource) { this.log.debug('updating resource with id', resource.id, 'data:', resource) @@ -753,6 +765,19 @@ export class SFFS { await this.fs.closeResource(resourceId) } + async deleteDataFile( + resourceId: string, + resourceType: string, + resourcePath: string + ): Promise { + this.log.debug('deleting data file', resourceId, resourceType, resourcePath) + + await this.fs.openResource(resourceId, resourceType, resourcePath, 'w') + + await this.fs.deleteResource(resourceId) + await this.fs.closeResource(resourceId) + } + async createHistoryEntry(entry: HistoryEntry): Promise { this.log.debug('creating history entry', entry) const rawEntry = this.convertHistoryEntryToRawHistoryEntry(entry) diff --git a/packages/types/src/sffs.types.ts b/packages/types/src/sffs.types.ts index 495590de9..c7011028a 100644 --- a/packages/types/src/sffs.types.ts +++ b/packages/types/src/sffs.types.ts @@ -175,3 +175,14 @@ export type SpaceEntrySearchOptions = { order?: 'asc' | 'desc' limit?: number } + +export type ResourceFileChange = { + type: 'create' | 'delete' | 'rename' + data: { + oldName?: string + newName?: string + oldPath?: string + newPath?: string + } + file?: File +} diff --git a/yarn.lock b/yarn.lock index aa89e491f..7b3906614 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3113,7 +3113,15 @@ boolean@^3.0.1: resolved "https://registry.npmjs.org/boolean/-/boolean-3.2.0.tgz" integrity sha512-d0II/GO9uf9lfUHH2BQsjxzRJZBdsjgsBiW4BvhWk/3qoKwQFjIDVN19PfX8F2D/r9PCMTtLWjYVCFrpeYUzsw== -brace-expansion@^1.1.7, brace-expansion@^2.0.1, brace-expansion@^2.0.2: +brace-expansion@^1.1.7: + version "1.1.12" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.12.tgz#ab9b454466e5a8cc3a187beaad580412a9c5b843" + integrity sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg== + dependencies: + balanced-match "^1.0.0" + concat-map "0.0.1" + +brace-expansion@^2.0.1: version "2.0.2" resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-2.0.2.tgz#54fc53237a613d854c7bd37463aad17df87214e7" integrity sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ== @@ -3650,6 +3658,11 @@ component-emitter@^2.0.0: resolved "https://registry.yarnpkg.com/component-emitter/-/component-emitter-2.0.0.tgz#3a137dfe66fcf2efe3eab7cb7d5f51741b3620c6" integrity sha512-4m5s3Me2xxlVKG9PkZpQqHQR7bgpnN7joDMJ4yvVkVXngjoITG76IaZmzmywSeRTeTpc6N6r3H3+KyUurV8OYw== +concat-map@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" + integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg== + concurrently@^7.0.0: version "7.6.0" resolved "https://registry.yarnpkg.com/concurrently/-/concurrently-7.6.0.tgz#531a6f5f30cf616f355a4afb8f8fcb2bba65a49a" @@ -3703,10 +3716,10 @@ convert-source-map@^2.0.0: resolved "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz" integrity sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg== -cookie@^0.6.0, cookie@^0.7.0: - version "0.7.2" - resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.7.2.tgz#556369c472a2ba910f2979891b526b3436237ed7" - integrity sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w== +cookie@^0.6.0: + version "0.6.0" + resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.6.0.tgz#2798b04b071b0ecbff0dbb62a505a8efa4e19051" + integrity sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw== core-util-is@1.0.2: version "1.0.2" @@ -4018,7 +4031,7 @@ detect-node@^2.0.4: resolved "https://registry.npmjs.org/detect-node/-/detect-node-2.1.0.tgz" integrity sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g== -devalue@^5.1.0, devalue@^5.3.2, devalue@^5.4.1: +devalue@^5.1.0, devalue@^5.3.2: version "5.4.1" resolved "https://registry.yarnpkg.com/devalue/-/devalue-5.4.1.tgz#6086910772fa055d707f60a3e5858d26ef9dcf55" integrity sha512-YtoaOfsqjbZQKGIMRYDWKjUmSB4VJ/RElB+bXZawQAQYAo4xu08GKTMVlsZDTF6R2MbAgjcAQRPI5eIyRAT2OQ== @@ -7655,6 +7668,11 @@ node-stdlib-browser@^1.2.0: util "^0.12.4" vm-browserify "^1.0.1" +node-watch@^0.7.4: + version "0.7.4" + resolved "https://registry.yarnpkg.com/node-watch/-/node-watch-0.7.4.tgz#34557106948cd4b8ddff9aa3d284774004548824" + integrity sha512-RinNxoz4W1cep1b928fuFhvAQ5ag/+1UlMDV7rbyGthBIgsiEouS4kvRayvvboxii4m8eolKOIBo3OjDqbc+uQ== + normalize-package-data@^2.5.0: version "2.5.0" resolved "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz" @@ -7827,6 +7845,11 @@ os-browserify@^0.3.0: resolved "https://registry.yarnpkg.com/os-browserify/-/os-browserify-0.3.0.tgz#854373c7f5c2315914fc9bfc6bd8238fdda1ec27" integrity sha512-gjcpUc3clBf9+210TRaDWbf+rZZZEshZ+DlXMRCeAjp0xhTrnQsKHypIy1J3d5hKdUzj69t708EHtU8P6bUn0A== +os-tmpdir@~1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274" + integrity sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g== + p-cancelable@^2.0.0: version "2.1.1" resolved "https://registry.npmjs.org/p-cancelable/-/p-cancelable-2.1.1.tgz" @@ -9554,7 +9577,16 @@ string-argv@~0.3.1: resolved "https://registry.yarnpkg.com/string-argv/-/string-argv-0.3.2.tgz#2b6d0ef24b656274d957d54e0a4bbf6153dc02b6" integrity sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q== -"string-width-cjs@npm:string-width@^4.2.0", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: +"string-width-cjs@npm:string-width@^4.2.0": + version "4.2.3" + resolved "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz" + integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== + dependencies: + emoji-regex "^8.0.0" + is-fullwidth-code-point "^3.0.0" + strip-ansi "^6.0.1" + +string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: version "4.2.3" resolved "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz" integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== @@ -9657,7 +9689,14 @@ stringify-entities@^4.0.0: character-entities-html4 "^2.0.0" character-entities-legacy "^3.0.0" -"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1: +"strip-ansi-cjs@npm:strip-ansi@^6.0.1": + version "6.0.1" + resolved "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz" + integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== + dependencies: + ansi-regex "^5.0.1" + +strip-ansi@^6.0.0, strip-ansi@^6.0.1: version "6.0.1" resolved "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz" integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== @@ -10153,7 +10192,14 @@ tmp-promise@^3.0.2: dependencies: tmp "^0.2.0" -tmp@^0.0.33, tmp@^0.2.0, tmp@^0.2.4: +tmp@^0.0.33: + version "0.0.33" + resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.33.tgz#6d34335889768d21b2bcda0aa277ced3b1bfadf9" + integrity sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw== + dependencies: + os-tmpdir "~1.0.2" + +tmp@^0.2.0: version "0.2.5" resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.2.5.tgz#b06bcd23f0f3c8357b426891726d16015abfd8f8" integrity sha512-voyz6MApa1rQGUxT3E+BK7/ROe8itEx7vD8/HEvt4xwXucvQ5G5oeEiHkmHZJuBO21RpOf+YYm9MOivj709jow== @@ -10843,7 +10889,16 @@ word-wrap@^1.2.5: resolved "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz" integrity sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA== -"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0: +"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0": + version "7.0.0" + resolved "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz" + integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== + dependencies: + ansi-styles "^4.0.0" + string-width "^4.1.0" + strip-ansi "^6.0.0" + +wrap-ansi@^7.0.0: version "7.0.0" resolved "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz" integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== From 1b9aa1be8599f21047a18f683956509d9059a773 Mon Sep 17 00:00:00 2001 From: Maximilian Schiller Date: Thu, 23 Oct 2025 20:13:26 +0200 Subject: [PATCH 2/3] chore: remove unused dependency --- app/package.json | 1 - yarn.lock | 50 ++++++++++-------------------------------------- 2 files changed, 10 insertions(+), 41 deletions(-) diff --git a/app/package.json b/app/package.json index 6b870bdc6..d02648319 100644 --- a/app/package.json +++ b/app/package.json @@ -37,7 +37,6 @@ "electron-drag-click": "^1.0.5", "electron-updater": "^6.1.7", "iconify-icon": "^2.1.0", - "node-watch": "^0.7.4", "sharp": "^0.33.5", "svelte-dnd-list": "^0.1.8", "svelte-portal": "^2.2.1" diff --git a/yarn.lock b/yarn.lock index 7b3906614..14afd8f55 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3113,15 +3113,7 @@ boolean@^3.0.1: resolved "https://registry.npmjs.org/boolean/-/boolean-3.2.0.tgz" integrity sha512-d0II/GO9uf9lfUHH2BQsjxzRJZBdsjgsBiW4BvhWk/3qoKwQFjIDVN19PfX8F2D/r9PCMTtLWjYVCFrpeYUzsw== -brace-expansion@^1.1.7: - version "1.1.12" - resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.12.tgz#ab9b454466e5a8cc3a187beaad580412a9c5b843" - integrity sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg== - dependencies: - balanced-match "^1.0.0" - concat-map "0.0.1" - -brace-expansion@^2.0.1: +brace-expansion@^1.1.7, brace-expansion@^2.0.1, brace-expansion@^2.0.2: version "2.0.2" resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-2.0.2.tgz#54fc53237a613d854c7bd37463aad17df87214e7" integrity sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ== @@ -3658,11 +3650,6 @@ component-emitter@^2.0.0: resolved "https://registry.yarnpkg.com/component-emitter/-/component-emitter-2.0.0.tgz#3a137dfe66fcf2efe3eab7cb7d5f51741b3620c6" integrity sha512-4m5s3Me2xxlVKG9PkZpQqHQR7bgpnN7joDMJ4yvVkVXngjoITG76IaZmzmywSeRTeTpc6N6r3H3+KyUurV8OYw== -concat-map@0.0.1: - version "0.0.1" - resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" - integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg== - concurrently@^7.0.0: version "7.6.0" resolved "https://registry.yarnpkg.com/concurrently/-/concurrently-7.6.0.tgz#531a6f5f30cf616f355a4afb8f8fcb2bba65a49a" @@ -3716,10 +3703,10 @@ convert-source-map@^2.0.0: resolved "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz" integrity sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg== -cookie@^0.6.0: - version "0.6.0" - resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.6.0.tgz#2798b04b071b0ecbff0dbb62a505a8efa4e19051" - integrity sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw== +cookie@^0.6.0, cookie@^0.7.0: + version "0.7.2" + resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.7.2.tgz#556369c472a2ba910f2979891b526b3436237ed7" + integrity sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w== core-util-is@1.0.2: version "1.0.2" @@ -4031,10 +4018,10 @@ detect-node@^2.0.4: resolved "https://registry.npmjs.org/detect-node/-/detect-node-2.1.0.tgz" integrity sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g== -devalue@^5.1.0, devalue@^5.3.2: - version "5.4.1" - resolved "https://registry.yarnpkg.com/devalue/-/devalue-5.4.1.tgz#6086910772fa055d707f60a3e5858d26ef9dcf55" - integrity sha512-YtoaOfsqjbZQKGIMRYDWKjUmSB4VJ/RElB+bXZawQAQYAo4xu08GKTMVlsZDTF6R2MbAgjcAQRPI5eIyRAT2OQ== +devalue@^5.1.0, devalue@^5.3.2, devalue@^5.4.1: + version "5.4.2" + resolved "https://registry.yarnpkg.com/devalue/-/devalue-5.4.2.tgz#d002d836f9e92fc0c3bd9b76ea69129cbf99dca7" + integrity sha512-MwPZTKEPK2k8Qgfmqrd48ZKVvzSQjgW0lXLxiIBA8dQjtf/6mw6pggHNLcyDKyf+fI6eXxlQwPsfaCMTU5U+Bw== devlop@^1.0.0, devlop@^1.1.0: version "1.1.0" @@ -7668,11 +7655,6 @@ node-stdlib-browser@^1.2.0: util "^0.12.4" vm-browserify "^1.0.1" -node-watch@^0.7.4: - version "0.7.4" - resolved "https://registry.yarnpkg.com/node-watch/-/node-watch-0.7.4.tgz#34557106948cd4b8ddff9aa3d284774004548824" - integrity sha512-RinNxoz4W1cep1b928fuFhvAQ5ag/+1UlMDV7rbyGthBIgsiEouS4kvRayvvboxii4m8eolKOIBo3OjDqbc+uQ== - normalize-package-data@^2.5.0: version "2.5.0" resolved "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz" @@ -7845,11 +7827,6 @@ os-browserify@^0.3.0: resolved "https://registry.yarnpkg.com/os-browserify/-/os-browserify-0.3.0.tgz#854373c7f5c2315914fc9bfc6bd8238fdda1ec27" integrity sha512-gjcpUc3clBf9+210TRaDWbf+rZZZEshZ+DlXMRCeAjp0xhTrnQsKHypIy1J3d5hKdUzj69t708EHtU8P6bUn0A== -os-tmpdir@~1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274" - integrity sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g== - p-cancelable@^2.0.0: version "2.1.1" resolved "https://registry.npmjs.org/p-cancelable/-/p-cancelable-2.1.1.tgz" @@ -10192,14 +10169,7 @@ tmp-promise@^3.0.2: dependencies: tmp "^0.2.0" -tmp@^0.0.33: - version "0.0.33" - resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.33.tgz#6d34335889768d21b2bcda0aa277ced3b1bfadf9" - integrity sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw== - dependencies: - os-tmpdir "~1.0.2" - -tmp@^0.2.0: +tmp@^0.0.33, tmp@^0.2.0, tmp@^0.2.4: version "0.2.5" resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.2.5.tgz#b06bcd23f0f3c8357b426891726d16015abfd8f8" integrity sha512-voyz6MApa1rQGUxT3E+BK7/ROe8itEx7vD8/HEvt4xwXucvQ5G5oeEiHkmHZJuBO21RpOf+YYm9MOivj709jow== From 78e66d0a83ca6cb10f464e192ff7f83fb3e9ef7d Mon Sep 17 00:00:00 2001 From: Maximilian Schiller Date: Thu, 23 Oct 2025 20:23:55 +0200 Subject: [PATCH 3/3] chore: cleanup test code --- app/src/main/index.ts | 4 +- app/src/main/surfBackend.ts | 74 ------------------------------------- 2 files changed, 3 insertions(+), 75 deletions(-) diff --git a/app/src/main/index.ts b/app/src/main/index.ts index 4f560b3a6..c0c9bcdfc 100644 --- a/app/src/main/index.ts +++ b/app/src/main/index.ts @@ -181,7 +181,9 @@ const setupBackendServer = async (appPath: string, backendRootPath: string, user surfBackendManager.start() await surfBackendManager.waitForStart() - surfBackendManager.initBackend() + surfBackendManager.initWatcher() + + initializeSFFSMain() } const initializeApp = async () => { diff --git a/app/src/main/surfBackend.ts b/app/src/main/surfBackend.ts index 93cf98555..23513ec54 100644 --- a/app/src/main/surfBackend.ts +++ b/app/src/main/surfBackend.ts @@ -5,8 +5,6 @@ import EventEmitter from 'events' import path, { basename } from 'path' import { FileWatcher } from './watcher' import { app } from 'electron' -import { initializeSFFSMain, SFFSMain } from './sffs' -import { SFFSRawResource } from '@deta/types' import { ipcSenders } from './ipcHandlers' export class SurfBackendServerManager extends EventEmitter { @@ -22,7 +20,6 @@ export class SurfBackendServerManager extends EventEmitter { private startResolve: (() => void) | null = null private startReject: ((reason: Error) => void) | null = null - private sffs: SFFSMain | null = null private watcher: FileWatcher | null = null constructor( @@ -46,16 +43,12 @@ export class SurfBackendServerManager extends EventEmitter { this.watcher .on('create', ({ filename, path }) => { - console.log(`File created: ${filename} at ${path}`) - // this.handleFileCreate({ filename, path }); ipcSenders.resourceFileChange({ type: 'create', data: { newName: filename, newPath: path } }) }) .on('delete', ({ filename, path }) => { - console.log(`File deleted: ${filename} at ${path}`) - // this.handleFileDelete({ filename, path }); ipcSenders.resourceFileChange({ type: 'delete', data: { oldName: filename, oldPath: path } @@ -69,68 +62,6 @@ export class SurfBackendServerManager extends EventEmitter { }) } - async handleFileRename({ - oldName, - newName, - oldPath, - newPath - }: { - oldName: string - newName: string - oldPath: string - newPath: string - }) { - try { - console.log(`File renamed: ${oldName} -> ${newName}`) - - if (!this.sffs) { - console.warn('SFFS not initialized. Cannot update resource path.') - return - } - - const resource = await this.sffs.getResourceByPath(oldPath) - if (resource) { - console.log( - 'Updating resource path in SFFS for renamed file', - resource.id, - oldPath, - newPath - ) - await this.sffs.updateResource({ - id: resource.id, - resource_path: newPath, - resource_type: resource.type, - created_at: resource.createdAt, - updated_at: new Date().toISOString(), - deleted: resource.deleted ? 1 : 0 - } satisfies SFFSRawResource) - console.log(`Updated resource path in SFFS: ${oldPath} -> ${newPath}`) - } - } catch (error) { - console.error('Error updating resource path in SFFS:', error) - } - } - - async handleFileDelete({ filename, path }: { filename: string; path: string }) { - try { - console.log(`File deleted: ${filename} at ${path}`) - - if (!this.sffs) { - console.warn('SFFS not initialized. Cannot delete resource.') - return - } - - const resource = await this.sffs.getResourceByPath(path) - if (resource) { - console.log('Deleting resource in SFFS for deleted file', resource.id, path) - await this.sffs.deleteResource(resource.id) - console.log(`Deleted resource in SFFS for file: ${path}`) - } - } catch (error) { - console.error('Error deleting resource in SFFS:', error) - } - } - start(): void { if (this.process) { this.emit('warn', 'surf backend server is already running') @@ -143,11 +74,6 @@ export class SurfBackendServerManager extends EventEmitter { this.isShuttingDown = false } - initBackend() { - this.sffs = initializeSFFSMain() - this.initWatcher() - } - waitForStart(): Promise { if (!this.startPromise) throw new Error('server has not been started') return this.startPromise