feat: add bounded file byte reads

This commit is contained in:
2026-06-29 03:16:16 +08:00
parent aa4905a097
commit 57677d1b1b
13 changed files with 208 additions and 13 deletions
@@ -242,6 +242,12 @@ export function createPluginAPI(pluginId) {
return App.ReadVaultTextFile(pluginId, relativePath);
});
},
readBytes: function(relativePath) {
assertActive('files.readBytes(' + relativePath + ')');
return callBackend(pluginId, 'files.readBytes(' + relativePath + ')', function() {
return App.ReadVaultFileBytes(pluginId, relativePath);
});
},
writeText: function(relativePath, content, options) {
assertActive('files.writeText(' + relativePath + ')');
return callBackendErrorString(pluginId, 'files.writeText(' + relativePath + ')', function() {
+17
View File
@@ -1287,6 +1287,23 @@
if (node.type !== 'file') return Promise.resolve(['', 'not-regular-file: ' + norm.path]);
return Promise.resolve([node.content || '', '']);
},
ReadVaultFileBytes: function (pluginId, relativePath) {
var err = requirePluginPermission(pluginId, 'files.read');
if (err) return Promise.resolve([{}, err]);
var norm = normalizeVaultPath(relativePath, false);
if (norm.error) return Promise.resolve([{}, norm.error]);
var node = vaultFiles[norm.path];
if (!node) return Promise.resolve([{}, 'not-found: ' + norm.path]);
if (node.type !== 'file') return Promise.resolve([{}, 'not-regular-file: ' + norm.path]);
var content = node.content || '';
var dataBase64 = typeof btoa === 'function' ? btoa(content) : '';
return Promise.resolve([{
relativePath: norm.path,
size: content.length,
mimeHint: norm.path.toLowerCase().endsWith('.png') ? 'image/png' : '',
dataBase64: dataBase64
}, '']);
},
WriteVaultTextFile: function (pluginId, relativePath, content, options) {
var err = requirePluginPermission(pluginId, 'files.write');
if (err) return Promise.resolve(err);
+17 -2
View File
@@ -11,8 +11,12 @@ globalThis.window = {
go: {
api: {
App: {
ReadVaultFileBytes: (pluginId, relativePath) => {
calls.push({ method: 'ReadVaultFileBytes', pluginId, relativePath });
return Promise.resolve([{ relativePath, size: 4, mimeHint: 'image/png', dataBase64: 'iVBORw==' }, '']);
},
RestoreVaultTrash: (pluginId, trashId, options) => {
calls.push({ pluginId, trashId, options });
calls.push({ method: 'RestoreVaultTrash', pluginId, trashId, options });
return Promise.resolve(['Docs/restored.txt', '']);
},
},
@@ -33,12 +37,23 @@ const api = apiModule.createPluginAPI('verstak.files');
if (!api.files || typeof api.files.restoreTrash !== 'function') {
throw new Error('api.files.restoreTrash is missing');
}
if (typeof api.files.readBytes !== 'function') {
throw new Error('api.files.readBytes is missing');
}
const bytes = await api.files.readBytes('Docs/image.png');
if (bytes.dataBase64 !== 'iVBORw==' || bytes.mimeHint !== 'image/png') {
throw new Error(`unexpected readBytes result: ${JSON.stringify(bytes)}`);
}
const restored = await api.files.restoreTrash('trash-1', { overwrite: true });
if (restored !== 'Docs/restored.txt') {
throw new Error(`unexpected restore result: ${JSON.stringify(restored)}`);
}
if (calls.length !== 1 || calls[0].pluginId !== 'verstak.files' || calls[0].trashId !== 'trash-1' || calls[0].options.overwrite !== true) {
if (calls.length !== 2 || calls[0].method !== 'ReadVaultFileBytes' || calls[0].pluginId !== 'verstak.files' || calls[0].relativePath !== 'Docs/image.png') {
throw new Error(`unexpected ReadVaultFileBytes call: ${JSON.stringify(calls)}`);
}
if (calls[1].method !== 'RestoreVaultTrash' || calls[1].pluginId !== 'verstak.files' || calls[1].trashId !== 'trash-1' || calls[1].options.overwrite !== true) {
throw new Error(`unexpected RestoreVaultTrash call: ${JSON.stringify(calls)}`);
}
+4 -2
View File
@@ -102,18 +102,20 @@ export function ReadPluginSetting(arg1:string,arg2:string):Promise<any>;
export function ReadPluginSettings(arg1:string):Promise<Record<string, any>|string>;
export function ReadVaultFileBytes(arg1:string,arg2:string):Promise<files.FileBytes|string>;
export function ReadVaultTextFile(arg1:string,arg2:string):Promise<string|string>;
export function RecordDesiredPlugin(arg1:string,arg2:string,arg3:string):Promise<string>;
export function ReloadPlugins():Promise<number|string>;
export function RestoreVaultTrash(arg1:string,arg2:string,arg3:files.RestoreOptions):Promise<string|string>;
export function RenameWorkspace(arg1:string,arg2:string):Promise<string>;
export function RenameWorkspaceNode(arg1:string,arg2:string):Promise<string>;
export function RestoreVaultTrash(arg1:string,arg2:string,arg3:files.RestoreOptions):Promise<string|string>;
export function SelectDirectory():Promise<string>;
export function SelectVaultForOpen():Promise<string>;
+8 -4
View File
@@ -190,6 +190,10 @@ export function ReadPluginSettings(arg1) {
return window['go']['api']['App']['ReadPluginSettings'](arg1);
}
export function ReadVaultFileBytes(arg1, arg2) {
return window['go']['api']['App']['ReadVaultFileBytes'](arg1, arg2);
}
export function ReadVaultTextFile(arg1, arg2) {
return window['go']['api']['App']['ReadVaultTextFile'](arg1, arg2);
}
@@ -202,10 +206,6 @@ export function ReloadPlugins() {
return window['go']['api']['App']['ReloadPlugins']();
}
export function RestoreVaultTrash(arg1, arg2, arg3) {
return window['go']['api']['App']['RestoreVaultTrash'](arg1, arg2, arg3);
}
export function RenameWorkspace(arg1, arg2) {
return window['go']['api']['App']['RenameWorkspace'](arg1, arg2);
}
@@ -214,6 +214,10 @@ export function RenameWorkspaceNode(arg1, arg2) {
return window['go']['api']['App']['RenameWorkspaceNode'](arg1, arg2);
}
export function RestoreVaultTrash(arg1, arg2, arg3) {
return window['go']['api']['App']['RestoreVaultTrash'](arg1, arg2, arg3);
}
export function SelectDirectory() {
return window['go']['api']['App']['SelectDirectory']();
}
+18
View File
@@ -369,6 +369,24 @@ export namespace capability {
export namespace files {
export class FileBytes {
relativePath: string;
size: number;
mimeHint: string;
dataBase64: string;
static createFrom(source: any = {}) {
return new FileBytes(source);
}
constructor(source: any = {}) {
if ('string' === typeof source) source = JSON.parse(source);
this.relativePath = source["relativePath"];
this.size = source["size"];
this.mimeHint = source["mimeHint"];
this.dataBase64 = source["dataBase64"];
}
}
export class FileEntry {
name: string;
relativePath: string;