u
This commit is contained in:
121
src/tools/vfs.js
121
src/tools/vfs.js
@@ -4,14 +4,6 @@ import { getStore } from '../db/store.js';
|
||||
import env from '../config/env.js';
|
||||
import { getDefaultSkills } from './skill.js';
|
||||
import { getDefaultApplets, DEFAULT_APPLETS_VERSION, APPLET_EXT } from './applet.js';
|
||||
import {
|
||||
parseDraftPath,
|
||||
requestDraftRead,
|
||||
notifyDraftUpdate,
|
||||
notifyDraftUpdateSync,
|
||||
getCachedDraft,
|
||||
DRAFT_NAMES,
|
||||
} from '../services/draftManager.js';
|
||||
|
||||
const FULL_HASH_REGEX = /^[a-zA-Z0-9]{8}$/;
|
||||
const VIRTUAL_IMAGE_FOLDERS = new Set(['image', 'images']);
|
||||
@@ -362,16 +354,6 @@ export function createVfs(vfsRoot, options = {}) {
|
||||
|
||||
const vfs = {
|
||||
readFileSync: (p, opt) => {
|
||||
const draftInfo = parseDraftPath(p);
|
||||
if (draftInfo.isDraft) {
|
||||
const cached = getCachedDraft({ userId: googleAuthId, name: draftInfo.name });
|
||||
if (cached) {
|
||||
const str = typeof cached.content === 'string' ? cached.content : JSON.stringify(cached.content ?? {}, null, 2);
|
||||
const encoding = typeof opt === 'string' ? opt : opt?.encoding;
|
||||
return encoding ? str : Buffer.from(str, 'utf8');
|
||||
}
|
||||
throw new Error(`Virtual draft "${draftInfo.name}" is not cached in memory. Use vfs.promises.readFile() to fetch it asynchronously.`);
|
||||
}
|
||||
const hash = extractFullHash(p);
|
||||
if (hash) {
|
||||
const buf = syncFetchImage(hash);
|
||||
@@ -414,19 +396,6 @@ export function createVfs(vfsRoot, options = {}) {
|
||||
},
|
||||
|
||||
writeFileSync: (p, data, opt) => {
|
||||
const draftInfo = parseDraftPath(p);
|
||||
if (draftInfo.isDraft) {
|
||||
let str = typeof data === 'string' ? data : Buffer.isBuffer(data) ? data.toString('utf8') : JSON.stringify(data);
|
||||
let parsed = str;
|
||||
try { parsed = JSON.parse(str); } catch {}
|
||||
notifyDraftUpdateSync({
|
||||
googleAuthId,
|
||||
clientId: options.clientId,
|
||||
name: draftInfo.name,
|
||||
content: parsed,
|
||||
});
|
||||
return;
|
||||
}
|
||||
assertNotReadOnlyDatabasePath(p);
|
||||
const diskPath = safeResolve(p);
|
||||
fs.mkdirSync(path.dirname(diskPath), { recursive: true });
|
||||
@@ -467,8 +436,6 @@ export function createVfs(vfsRoot, options = {}) {
|
||||
}
|
||||
},
|
||||
existsSync: (p) => {
|
||||
const draftInfo = parseDraftPath(p);
|
||||
if (draftInfo.isFolder || draftInfo.isDraft) return true;
|
||||
const hash = extractFullHash(p);
|
||||
if (hash) {
|
||||
const buf = syncFetchImage(hash);
|
||||
@@ -483,31 +450,6 @@ export function createVfs(vfsRoot, options = {}) {
|
||||
},
|
||||
|
||||
statSync: (p, opt) => {
|
||||
const draftInfo = parseDraftPath(p);
|
||||
if (draftInfo.isFolder) {
|
||||
const now = new Date();
|
||||
return {
|
||||
isFile: () => false,
|
||||
isDirectory: () => true,
|
||||
size: 0,
|
||||
mtime: now,
|
||||
ctime: now,
|
||||
atime: now,
|
||||
};
|
||||
}
|
||||
if (draftInfo.isDraft) {
|
||||
const cached = getCachedDraft({ userId: googleAuthId, name: draftInfo.name });
|
||||
const now = cached?.updatedAt ? new Date(cached.updatedAt) : new Date();
|
||||
const size = cached?.size ?? 0;
|
||||
return {
|
||||
isFile: () => true,
|
||||
isDirectory: () => false,
|
||||
size,
|
||||
mtime: now,
|
||||
ctime: now,
|
||||
atime: now,
|
||||
};
|
||||
}
|
||||
const hash = extractFullHash(p);
|
||||
if (hash) {
|
||||
const buf = syncFetchImage(hash);
|
||||
@@ -530,29 +472,15 @@ export function createVfs(vfsRoot, options = {}) {
|
||||
lstatSync: (p, opt) => vfs.statSync(p, opt),
|
||||
|
||||
readdirSync: (p = '.', opt) => {
|
||||
const draftInfo = parseDraftPath(p);
|
||||
if (draftInfo.isFolder) {
|
||||
return Array.from(DRAFT_NAMES);
|
||||
}
|
||||
const clean = typeof p === 'string' ? p.trim().replace(/^([a-zA-Z]:|\/)+/, '') : '';
|
||||
if (VIRTUAL_IMAGE_FOLDERS.has(clean.toLowerCase())) {
|
||||
return [];
|
||||
}
|
||||
const diskPath = safeResolve(p);
|
||||
const entries = fs.readdirSync(diskPath, opt);
|
||||
if ((clean === '' || clean === '.') && Array.isArray(entries)) {
|
||||
if (!entries.some((e) => (typeof e === 'string' ? e : e.name) === 'drafts')) {
|
||||
entries.push('drafts');
|
||||
}
|
||||
}
|
||||
return entries;
|
||||
return fs.readdirSync(diskPath, opt);
|
||||
},
|
||||
|
||||
mkdirSync: (p, opt) => {
|
||||
const draftInfo = parseDraftPath(p);
|
||||
if (draftInfo.isFolder || draftInfo.isDraft) {
|
||||
throw new Error(`Access denied: "${p}" is a virtual draft and cannot be created on disk.`);
|
||||
}
|
||||
assertNotReadOnlyDatabasePath(p);
|
||||
const diskPath = safeResolve(p);
|
||||
try {
|
||||
@@ -562,30 +490,18 @@ export function createVfs(vfsRoot, options = {}) {
|
||||
}
|
||||
},
|
||||
unlinkSync: (p) => {
|
||||
const draftInfo = parseDraftPath(p);
|
||||
if (draftInfo.isFolder || draftInfo.isDraft) {
|
||||
throw new Error(`Access denied: "${p}" is a virtual draft and cannot be unlinked on disk.`);
|
||||
}
|
||||
assertNotReadOnlyDatabasePath(p);
|
||||
const diskPath = safeResolve(p);
|
||||
return fs.unlinkSync(diskPath);
|
||||
},
|
||||
|
||||
rmdirSync: (p, opt) => {
|
||||
const draftInfo = parseDraftPath(p);
|
||||
if (draftInfo.isFolder || draftInfo.isDraft) {
|
||||
throw new Error(`Access denied: "${p}" is a virtual draft and cannot be removed on disk.`);
|
||||
}
|
||||
assertNotReadOnlyDatabasePath(p);
|
||||
const diskPath = safeResolve(p);
|
||||
return fs.rmdirSync(diskPath, opt);
|
||||
},
|
||||
|
||||
rmSync: (p, opt) => {
|
||||
const draftInfo = parseDraftPath(p);
|
||||
if (draftInfo.isFolder || draftInfo.isDraft) {
|
||||
throw new Error(`Access denied: "${p}" is a virtual draft and cannot be removed on disk.`);
|
||||
}
|
||||
assertNotReadOnlyDatabasePath(p);
|
||||
const diskPath = safeResolve(p);
|
||||
return fs.rmSync(diskPath, opt);
|
||||
@@ -593,17 +509,6 @@ export function createVfs(vfsRoot, options = {}) {
|
||||
|
||||
promises: {
|
||||
readFile: async (p, opt) => {
|
||||
const draftInfo = parseDraftPath(p);
|
||||
if (draftInfo.isDraft) {
|
||||
const content = await requestDraftRead({
|
||||
userId: googleAuthId,
|
||||
clientId: options.clientId,
|
||||
name: draftInfo.name,
|
||||
});
|
||||
const str = typeof content === 'string' ? content : JSON.stringify(content ?? {}, null, 2);
|
||||
const encoding = typeof opt === 'string' ? opt : opt?.encoding;
|
||||
return encoding ? str : Buffer.from(str, 'utf8');
|
||||
}
|
||||
const hash = extractFullHash(p);
|
||||
if (hash) {
|
||||
const buf = syncFetchImage(hash);
|
||||
@@ -631,29 +536,7 @@ export function createVfs(vfsRoot, options = {}) {
|
||||
}
|
||||
return vfs.readFileSync(p, opt);
|
||||
},
|
||||
writeFile: async (p, data, opt) => {
|
||||
const draftInfo = parseDraftPath(p);
|
||||
if (draftInfo.isDraft) {
|
||||
let str = typeof data === 'string' ? data : Buffer.isBuffer(data) ? data.toString('utf8') : JSON.stringify(data);
|
||||
let parsed = str;
|
||||
try { parsed = JSON.parse(str); } catch {}
|
||||
let companyId = options.companyId;
|
||||
if (companyId == null && googleAuthId) {
|
||||
const store = await getStore();
|
||||
const row = await store.google_auth.getById(googleAuthId);
|
||||
companyId = row?.selectedCompanyId;
|
||||
}
|
||||
notifyDraftUpdate({
|
||||
companyId,
|
||||
userId: googleAuthId,
|
||||
clientId: options.clientId,
|
||||
name: draftInfo.name,
|
||||
content: parsed,
|
||||
});
|
||||
return;
|
||||
}
|
||||
return vfs.writeFileSync(p, data, opt);
|
||||
},
|
||||
writeFile: async (p, data, opt) => vfs.writeFileSync(p, data, opt),
|
||||
appendFile: async (p, data, opt) => vfs.appendFileSync(p, data, opt),
|
||||
stat: async (p, opt) => vfs.statSync(p, opt),
|
||||
readdir: async (p, opt) => vfs.readdirSync(p, opt),
|
||||
|
||||
Reference in New Issue
Block a user