import { StorageAdapter, SavedPath } from '../types'; import { pipeline } from 'stream/promises'; import * as fs from 'fs'; import { mkdir, rename, unlink } from 'fs/promises'; import * as path from 'path'; const DEFAULT_LOCAL_ROOT = '/data/media'; export class LocalStorageAdapter implements StorageAdapter { constructor(private readonly localRoot: string = DEFAULT_LOCAL_ROOT) {} private resolveAbsolutePath( localStoragePrefix: string, relativePath: string, ) { return path.resolve(this.localRoot, localStoragePrefix, relativePath); } private tempPath(targetPath: string) { return `${targetPath}.part`; } async put( relativePath: string, localStoragePrefix: string, stream: NodeJS.ReadableStream, ): Promise<{ savedPath: SavedPath }> { const targetPath = this.resolveAbsolutePath( localStoragePrefix, relativePath, ); const targetDir = path.dirname(targetPath); await mkdir(targetDir, { recursive: true }); const stagingPath = this.tempPath(targetPath); const writeStream = fs.createWriteStream(stagingPath, { flags: 'w' }); try { await pipeline(stream, writeStream); await rename(stagingPath, targetPath); return { savedPath: targetPath }; } catch (err) { await unlink(stagingPath).catch(() => undefined); throw err; } } async delete( relativePath: string[], localStoragePrefix: string, ): Promise { for (const relative of relativePath) { const targetPath = this.resolveAbsolutePath(localStoragePrefix, relative); await unlink(targetPath).catch((err) => { if (err.code !== 'ENOENT') { throw err; } }); } } }