Spaces:
Sleeping
Sleeping
Bot
feat: implement zero-config local filesystem storage driver for out-of-the-box file uploads on Hugging Face Spaces
a375c71 | import { FileStorage, UploadContent, UploadOptions, UploadResult, FileMetadata } from "./file-storage.interface"; | |
| import fs from "fs/promises"; | |
| import path from "path"; | |
| import { BASE_URL } from "lib/const"; | |
| const UPLOADS_DIR = "/tmp/uploads"; | |
| export class LocalFileStorage implements FileStorage { | |
| constructor() { | |
| fs.mkdir(UPLOADS_DIR, { recursive: true }).catch(() => {}); | |
| } | |
| async upload(content: UploadContent, options?: UploadOptions): Promise<UploadResult> { | |
| const filename = options?.filename || "file"; | |
| const contentType = options?.contentType || "application/octet-stream"; | |
| const key = `${Date.now()}-${Math.random().toString(36).slice(2, 8)}-${filename}`; | |
| const filePath = path.join(UPLOADS_DIR, key); | |
| let buffer: Buffer; | |
| if (Buffer.isBuffer(content)) { | |
| buffer = content; | |
| } else if (content instanceof ArrayBuffer) { | |
| buffer = Buffer.from(content); | |
| } else if (ArrayBuffer.isView(content)) { | |
| buffer = Buffer.from(content.buffer, content.byteOffset, content.byteLength); | |
| } else if (content instanceof Blob) { | |
| buffer = Buffer.from(await content.arrayBuffer()); | |
| } else if (typeof (content as any).read === "function" || typeof (content as any).on === "function") { | |
| const chunks: any[] = []; | |
| for await (const chunk of content as any) { | |
| chunks.push(chunk); | |
| } | |
| buffer = Buffer.concat(chunks); | |
| } else { | |
| throw new Error("Unsupported upload content type"); | |
| } | |
| await fs.writeFile(filePath, buffer); | |
| const metadata: FileMetadata = { | |
| key, | |
| filename, | |
| contentType, | |
| size: buffer.length, | |
| uploadedAt: new Date(), | |
| }; | |
| return { | |
| key, | |
| sourceUrl: `${BASE_URL}/api/storage/file/${key}`, | |
| metadata, | |
| }; | |
| } | |
| async download(key: string): Promise<Buffer> { | |
| const filePath = path.join(UPLOADS_DIR, key); | |
| return fs.readFile(filePath); | |
| } | |
| async delete(key: string): Promise<void> { | |
| const filePath = path.join(UPLOADS_DIR, key); | |
| await fs.unlink(filePath).catch(() => {}); | |
| } | |
| async exists(key: string): Promise<boolean> { | |
| const filePath = path.join(UPLOADS_DIR, key); | |
| try { | |
| await fs.access(filePath); | |
| return true; | |
| } catch { | |
| return false; | |
| } | |
| } | |
| async getMetadata(key: string): Promise<FileMetadata | null> { | |
| const filePath = path.join(UPLOADS_DIR, key); | |
| try { | |
| const stat = await fs.stat(filePath); | |
| return { | |
| key, | |
| filename: key.split("-").slice(2).join("-") || "file", | |
| contentType: "application/octet-stream", | |
| size: stat.size, | |
| uploadedAt: stat.mtime, | |
| }; | |
| } catch { | |
| return null; | |
| } | |
| } | |
| async getSourceUrl(key: string): Promise<string | null> { | |
| return `${BASE_URL}/api/storage/file/${key}`; | |
| } | |
| } | |
| export const createLocalFileStorage = () => new LocalFileStorage(); | |