import { spawnSync } from 'child_process' import { DriveLsEntry, DriveLogEntry } from './drive_types' /** * Server-only drive functions that use Node.js APIs */ function executeFctDriveCommand(args: string[]): string { const result = spawnSync('fctdrive', args, { encoding: 'utf-8', maxBuffer: 50 * 1024 * 1024 }) if (result.error) { throw new Error(`Failed to execute fctdrive: ${result.error.message}`) } if (result.status !== 0) { throw new Error(`fctdrive exited with code ${result.status}: ${result.stderr}`) } return result.stdout } /// lists the given path on the drive export async function Drive_ls(path: string, recursive: boolean): Promise { const args = ['ls'] if (recursive) { args.push('-r') } if (path) { args.push(path) } const stdout = executeFctDriveCommand(args) const entries = [] for (const line of stdout.split('\n')) { if (line.trim() == "") continue; const parts = line.split('\t'); const path = parts[0]; const type = parts[1]; const lastmod = parseInt(parts[2]); const blobStr = parts[3]; const sizeStr = parts[4]; const author = parts[5]; let blob = null; if (blobStr != "-") blob = blobStr; let size = null; if (sizeStr != "-") size = parseFloat(sizeStr); entries.push({ path, type, lastmod, blob, size, author } as DriveLsEntry); } return entries; } /// import the file at local_path by moving it to drive_path export async function Drive_import(local_path: string, drive_path: string, email: string) { executeFctDriveCommand(['import', local_path, "--mode", "move", "--destination", drive_path, "--email", email]); } /// returns the full filesystem path of the blob given its id export async function Drive_blob_path(blob: string): Promise { const stdout = executeFctDriveCommand(['blob', blob]) return stdout.trim(); } /// removes the file or directory at the given path export async function Drive_remove(path: string, email: string) { executeFctDriveCommand(['remove', '--email', email, path]); } /// creates a directory at the given path export async function Drive_mkdir(path: string, email: string) { executeFctDriveCommand(['mkdir', '--email', email, '--path', path]); } /// renames a file or directory from old path to new path export async function Drive_rename(oldPath: string, newPath: string, email: string) { executeFctDriveCommand(['rename', '--email', email, '--old', oldPath, '--new', newPath]); } /// gets the blob ID for a given path export async function Drive_stat(path: string): Promise { const stdout = executeFctDriveCommand(['stat', path]); return stdout.trim(); } /// lists only directories (recursively) from the given path export async function Drive_ls_directories(path: string = '/'): Promise { // Get all entries recursively and filter for directories const allEntries = await Drive_ls(path, true) return allEntries.filter(entry => entry.type === 'dir') } /// returns the log entries from the drive export async function Drive_log(): Promise { const stdout = executeFctDriveCommand(['log']) const entries: DriveLogEntry[] = [] for (const line of stdout.split('\n')) { if (line.trim() === "") continue; const parts = line.split('\t'); const timestamp = parseInt(parts[0]); const revision = parseInt(parts[1]); const email = parts[2]; const action = parts[3]; // Parse based on action type to create the correct discriminated union switch (action) { case "create_file": { const path = parts[4]; const blob_id = parts[5]; const size = parseInt(parts[6]); entries.push({ timestamp, revision, email, action: "create_file", path, blob_id, size }); break; } case "create_dir": { const path = parts[4]; entries.push({ timestamp, revision, email, action: "create_dir", path }); break; } case "remove": { const path = parts[4]; entries.push({ timestamp, revision, email, action: "remove", path }); break; } case "rename": { const old_path = parts[4]; const new_path = parts[5]; entries.push({ timestamp, revision, email, action: "rename", old_path, new_path }); break; } default: console.warn(`Unknown log action: ${action}`, parts); continue; } } return entries; }