diff --git a/packages/fuse-client/syscalls/flush.ts b/packages/fuse-client/syscalls/flush.ts index 6ac273f..abd4ce8 100644 --- a/packages/fuse-client/syscalls/flush.ts +++ b/packages/fuse-client/syscalls/flush.ts @@ -6,6 +6,10 @@ export const flush: (backend: SQLiteBackend) => MountOptions["flush"] = ( ) => { return async (path, fd, cb) => { console.info("flush(%s, %d)", path, fd); + if (backend.isVirtualFile(path)) { + cb(0); + return; + } await backend.flush(path); cb(0); }; diff --git a/packages/fuse-client/syscalls/getattr.ts b/packages/fuse-client/syscalls/getattr.ts index aa760fc..2f641bf 100644 --- a/packages/fuse-client/syscalls/getattr.ts +++ b/packages/fuse-client/syscalls/getattr.ts @@ -7,6 +7,13 @@ export const getattr: (backend: SQLiteBackend) => MountOptions["getattr"] = ( ) => { return async (path, cb) => { console.info("getattr(%s)", path); + + if (backend.isVirtualFile(path)) { + const virtualFile = backend.getVirtualFile(path); + cb(0, virtualFile.getAttr()); + return; + } + const r = await backend.getFile(path); await match(r) .with({ status: "ok" }, async (r) => { diff --git a/packages/fuse-client/syscalls/open.ts b/packages/fuse-client/syscalls/open.ts index 84daa42..70af2d5 100644 --- a/packages/fuse-client/syscalls/open.ts +++ b/packages/fuse-client/syscalls/open.ts @@ -7,8 +7,14 @@ export const open: (backend: SQLiteBackend) => MountOptions["open"] = ( ) => { return async (path, flags, cb) => { console.info("open(%s, %d)", path, flags); - const r = await backend.getFile(path); + if (backend.isVirtualFile(path)) { + const virtualFile = backend.getVirtualFile(path); + cb(0, virtualFile.fileId); + return; + } + + const r = await backend.getFile(path); match(r) .with({ status: "ok" }, (r) => { cb(0, r.file.id); diff --git a/packages/fuse-client/syscalls/read.ts b/packages/fuse-client/syscalls/read.ts index 8995c23..f26d918 100644 --- a/packages/fuse-client/syscalls/read.ts +++ b/packages/fuse-client/syscalls/read.ts @@ -7,6 +7,15 @@ export const read: (backend: SQLiteBackend) => MountOptions["read"] = ( ) => { return async (path, fd, buf, len, pos, cb) => { console.info("read(%s, %d, %d, %d)", path, fd, len, pos); + + if (backend.isVirtualFile(path)) { + const virtualFile = backend.getVirtualFile(path); + const bufChunk = virtualFile.getBuffer(); + buf.write(bufChunk.toString("binary"), "binary"); + cb(Buffer.byteLength(bufChunk)); + return; + } + const r = await backend.getFileChunks(fd, pos, len); await match(r) .with({ status: "ok" }, async (r) => { diff --git a/packages/fuse-client/syscalls/readdir.ts b/packages/fuse-client/syscalls/readdir.ts index 42770e2..5bdab88 100644 --- a/packages/fuse-client/syscalls/readdir.ts +++ b/packages/fuse-client/syscalls/readdir.ts @@ -7,11 +7,13 @@ export const readdir: (backend: SQLiteBackend) => MountOptions["readdir"] = ( return async (path, cb) => { console.info("readdir(%s)", path); - // TODO: figure out how are these directories in output of ls -la const dotDirs = [".", ".."]; - + const metaFiles: string[] = backend.getVirtualFilePaths(path); const links = await backend.getLinks(path); - const fileNames = dotDirs.concat(links.map((link) => link.name)); + const fileNames = dotDirs + // Note: trim the first / of /.zoid-meta returning .zoid-meta + .concat(metaFiles.map((metaFile) => metaFile.slice(1))) + .concat(links.map((link) => link.name)); return cb(0, fileNames); }; diff --git a/packages/sqlite-backend/SQLiteBackend.ts b/packages/sqlite-backend/SQLiteBackend.ts index dfe739e..f1f9a3d 100644 --- a/packages/sqlite-backend/SQLiteBackend.ts +++ b/packages/sqlite-backend/SQLiteBackend.ts @@ -6,6 +6,8 @@ import { rawCreateMany } from "./prismaRawUtil"; import { WriteBuffer } from "./WriteBuffer"; import mmmagic, { Magic } from "mmmagic"; import { promisify } from "util"; +import { VirtualFiles } from "./VirtualFiles"; +import { VirtualFile } from "./VirtualFile"; export type ContentChunk = { content: Buffer; @@ -19,6 +21,7 @@ export class SQLiteBackend implements Backend { private readonly writeBuffers: Map> = new Map(); private readonly prisma: PrismaClient; + private readonly virtualFiles: VirtualFiles; constructor(prismaOrDbUrl?: PrismaClient | string) { if (prismaOrDbUrl instanceof PrismaClient) { this.prisma = prismaOrDbUrl; @@ -35,6 +38,33 @@ export class SQLiteBackend implements Backend { this.prisma.$connect(); } + + const virtualFile = new VirtualFile( + 999, + "/.zoid-meta", + JSON.stringify( + { + databaseUrl: process.env.DATABASE_URL, + }, + null, + 2 + ) + "\n" + ); + const virtualFileMap = new Map(); + virtualFileMap.set(virtualFile.path, virtualFile); + this.virtualFiles = new VirtualFiles(virtualFileMap); + } + + isVirtualFile(filepath: string) { + return this.virtualFiles.isVirtualFile(filepath); + } + + getVirtualFile(filepath: string) { + return this.virtualFiles.getVirtualFile(filepath)!; + } + + getVirtualFilePaths(filepath: string) { + return this.virtualFiles.filePaths(filepath); } async write(filepath: string, chunk: ContentChunk) { diff --git a/packages/sqlite-backend/VirtualFile.ts b/packages/sqlite-backend/VirtualFile.ts new file mode 100644 index 0000000..e078dee --- /dev/null +++ b/packages/sqlite-backend/VirtualFile.ts @@ -0,0 +1,39 @@ +export class VirtualFile { + private _date = new Date(); + constructor( + private _fileId: number, + private _path: string, + private _content: string + ) {} + + get path() { + return this._path; + } + + get fileId() { + return this._fileId; + } + + getBuffer() { + return Buffer.from(this._content); + } + + getSize() { + return Buffer.byteLength(this.getBuffer()); + } + + getAttr() { + return { + mtime: this._date, + atime: this._date, + ctime: this._date, + blocks: 1, + ino: this.fileId, + nlink: 1, + size: this.getSize(), + mode: 33188, + uid: process.getuid ? process.getuid() : 0, + gid: process.getgid ? process.getgid() : 0, + }; + } +} diff --git a/packages/sqlite-backend/VirtualFiles.ts b/packages/sqlite-backend/VirtualFiles.ts new file mode 100644 index 0000000..9292a90 --- /dev/null +++ b/packages/sqlite-backend/VirtualFiles.ts @@ -0,0 +1,21 @@ +import { VirtualFile } from "./VirtualFile"; + +export class VirtualFiles { + constructor(private _files: Map) {} + + filePaths(path: string) { + if (path === "/") { + return Array.from(this._files.keys()); + } + return []; + } + + isVirtualFile(path: string) { + // TODO: find correct directory + return this._files.has(path); + } + + getVirtualFile(path: string) { + return this._files.get(path); + } +}