Skip to content

Commit

Permalink
feat(memfs): finish importFromZip logic
Browse files Browse the repository at this point in the history
  • Loading branch information
JiyuShao committed Jun 13, 2024
1 parent ff97ec1 commit e4a1c56
Show file tree
Hide file tree
Showing 6 changed files with 146 additions and 63 deletions.
Binary file modified packages/extensions/js-runner-and-debugger/assets/demo.zip
Binary file not shown.
4 changes: 0 additions & 4 deletions packages/extensions/js-runner-and-debugger/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,6 @@
"command": "js-runner-and-debugger.memfs.importDemoWorkspace",
"title": "JS Runner & Debugger: MemFS Init Demo Workspace"
},
{
"command": "js-runner-and-debugger.memfs.reset",
"title": "JS Runner & Debugger: MemFS Reset"
},
{
"command": "js-runner-and-debugger.memfs.importWorkspace",
"title": "JS Runner & Debugger: MemFS Import Workspace"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,17 @@ import * as vscode from 'vscode';
import { MemFS } from '../utils/memfs';
import { executeCommand, registerCommand } from '../utils/commands';
import { FS_SCHEME } from '../config';
import { importFile, exportFile } from '../utils/file';
import { importFile } from '../utils/file';

export async function registerFileSystem(context: vscode.ExtensionContext) {
const memFs = new MemFS();
// init memFs
const workspaceFolders = vscode.workspace.workspaceFolders || [];
const matchedFolder = workspaceFolders.find(
folder => folder.uri.scheme === FS_SCHEME
);
const memFs = new MemFS(
matchedFolder ? matchedFolder.uri : vscode.Uri.parse(`${FS_SCHEME}:/`)
);

// register a file system provider
context.subscriptions.push(
Expand All @@ -15,19 +22,15 @@ export async function registerFileSystem(context: vscode.ExtensionContext) {
);

registerCommand(context, `${FS_SCHEME}.importDemoWorkspace`, async () => {
const folderUri = vscode.Uri.parse(`${FS_SCHEME}:/`);
await executeCommand(`${FS_SCHEME}.importWorkspace`, {
isDemo: true,
});
await vscode.commands.executeCommand('vscode.openFolder', folderUri, {
forceReuseWindow: true,
uri: vscode.Uri.joinPath(context.extensionUri, 'assets/demo.zip'),
});
});

registerCommand(
context,
`${FS_SCHEME}.importWorkspace`,
async (options?: { isDemo: boolean }) => {
async (options?: { uri: vscode.Uri }) => {
// 展示信息消息框,含有Yes和No选项
const userChoice = await vscode.window.showInformationMessage(
'Importing the workspace will delete all data in the current workspace, continue?',
Expand All @@ -37,29 +40,26 @@ export async function registerFileSystem(context: vscode.ExtensionContext) {
if (userChoice !== 'Yes') {
return;
}
const { isDemo = false } = options || {};
let zipFileContent: Uint8Array | void = undefined;
if (!isDemo) {
zipFileContent = await importFile();
} else {
zipFileContent = await vscode.workspace.fs.readFile(
vscode.Uri.joinPath(context.extensionUri, 'assets/demo.zip')
);
const { uri } = options || {};
const finalUri: vscode.Uri | void = uri || (await importFile());
if (!finalUri) {
return;
}
if (zipFileContent) {
// reset workspace before run import logic
await memFs.reset();
await memFs.importFromZip(zipFileContent);
// reset workspace before run import logic
await memFs.reset();
// import and folder to memfs(persistence)
const newUri = await memFs.importFromZip(finalUri);
if (newUri) {
// open single-folder workspace
await vscode.commands.executeCommand('vscode.openFolder', newUri, {
forceReuseWindow: true,
});
vscode.window.showInformationMessage('File imported successfully');
}
}
);

registerCommand(context, `${FS_SCHEME}.exportWorkspace`, async () => {
const content = await memFs.exportToZip();
const path = await exportFile(content, 'memfs.zip');
if (path) {
vscode.window.showInformationMessage(`File saved successfully: ${path}`);
}
await memFs.exportToZip();
});
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import vscode from 'vscode';

export async function importFile(): Promise<Uint8Array | void> {
export async function importFile(): Promise<vscode.Uri | void> {
const uris = await vscode.window.showOpenDialog({
canSelectMany: false,
canSelectFiles: true,
Expand All @@ -15,7 +15,7 @@ export async function importFile(): Promise<Uint8Array | void> {
if (!uris || uris.length === 0) {
return;
}
return vscode.workspace.fs.readFile(uris[0]);
return uris[0];
}

export async function exportFile(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
export class IndexedDBWrapper {
private dbName: string;
private storeName: string;
private db: IDBDatabase | null = null;

constructor(dbName: string, storeName: string) {
this.dbName = dbName;
this.storeName = storeName;
}

async open(): Promise<void> {
return new Promise((resolve, reject) => {
const request = indexedDB.open(this.dbName);
request.onupgradeneeded = () => {
const db = request.result;
if (!db.objectStoreNames.contains(this.storeName)) {
db.createObjectStore(this.storeName);
}
};
request.onsuccess = () => {
this.db = request.result;
resolve();
};
request.onerror = () => {
reject(request.error);
};
});
}

async put(key: string, value: any): Promise<void> {
return new Promise((resolve, reject) => {
if (!this.db) throw new Error('Database not opened yet');
const transaction = this.db.transaction(this.storeName, 'readwrite');
const store = transaction.objectStore(this.storeName);
const request = store.put(value, key);
request.onsuccess = () => resolve();
request.onerror = () => reject(request.error);
});
}

async get(key: string): Promise<any> {
return new Promise((resolve, reject) => {
if (!this.db) throw new Error('Database not opened yet');
const transaction = this.db.transaction(this.storeName, 'readonly');
const store = transaction.objectStore(this.storeName);
const request = store.get(key);
request.onsuccess = () => resolve(request.result);
request.onerror = () => reject(request.error);
});
}
}
100 changes: 68 additions & 32 deletions packages/extensions/js-runner-and-debugger/src/web/utils/memfs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import * as vscode from 'vscode';
import JSZip from 'jszip';
import { logger } from './logger';
import { FS_SCHEME } from '../config';
import { IndexedDBWrapper } from './indexed-db';
import { exportFile } from './file';

export class File implements vscode.FileStat {
type: vscode.FileType;
Expand Down Expand Up @@ -50,7 +52,13 @@ export class Directory implements vscode.FileStat {
export type Entry = File | Directory;

export class MemFS implements vscode.FileSystemProvider {
root = new Directory('/');
private root: Directory;
private dbWrapper: IndexedDBWrapper;

constructor(rootUri: vscode.Uri) {
this.root = new Directory(rootUri.path);
this.dbWrapper = new IndexedDBWrapper(FS_SCHEME, rootUri.path);
}

// --- manage file metadata

Expand Down Expand Up @@ -240,14 +248,20 @@ export class MemFS implements vscode.FileSystemProvider {
}

// Custom Method
public async reset(uri: vscode.Uri = vscode.Uri.parse(`${FS_SCHEME}:/`)) {
public async init() {
return this.dbWrapper.open();
}

public async reset(
uri: vscode.Uri = vscode.Uri.parse(`${FS_SCHEME}:${this.root.name}`)
) {
for (const [name] of this.readDirectory(uri)) {
this.delete(vscode.Uri.parse(`${FS_SCHEME}:/${name}`));
}
}

public async exportToZip(): Promise<Uint8Array> {
const zip = new JSZip();
public async exportToZip(): Promise<void> {
const rootZip = new JSZip();
const stack: {
value: Directory | File;
path: string;
Expand All @@ -256,7 +270,7 @@ export class MemFS implements vscode.FileSystemProvider {
{
value: this.root,
path: '',
parentZip: zip,
parentZip: rootZip,
},
];
// preorder DFS
Expand Down Expand Up @@ -288,37 +302,59 @@ export class MemFS implements vscode.FileSystemProvider {
}
logger.debug(`Creating ${currentItem.path} Succeed!`);
}
return zip
.generateAsync({ type: 'uint8array' })
.then(function (content) {
logger.info(`Export to zip Succeed:`, content);
return content;
})
.catch(error => {
logger.error(`Export to zip Failed!`, error);
throw error;
});
try {
const content = await rootZip.generateAsync({ type: 'uint8array' });
const path = await exportFile(content, 'memfs.zip');
vscode.window.showInformationMessage(`File saved successfully: ${path}`);
} catch (error) {
const errMsg = `exportToZip failed:${(error as Error).message}`;
logger.info(errMsg);
throw new Error(errMsg);
}
}

public async importFromZip(content: Uint8Array) {
const zipFileContent = await JSZip.loadAsync(content);
Object.values(zipFileContent.files).forEach(async currentFileMeta => {
if (currentFileMeta.dir) {
this.createDirectory(
vscode.Uri.parse(`${FS_SCHEME}:/${currentFileMeta.name}`)
);
} else {
const currentFileContent = await zipFileContent
.file(currentFileMeta.name)
?.async('uint8array');
if (currentFileContent) {
this.writeFile(
vscode.Uri.parse(`${FS_SCHEME}:/${currentFileMeta.name}`),
currentFileContent,
{ create: true, overwrite: true }
public async importFromZip(uri: vscode.Uri): Promise<vscode.Uri | void> {
const rawContent = await vscode.workspace.fs.readFile(uri);
if (!rawContent) {
return;
}
const rootZip = await JSZip.loadAsync(rawContent);
const stack: {
value: Directory | File;
path: string;
zip: JSZip;
}[] = [
{
value: this.root,
path: '',
zip: rootZip,
},
];
while (stack.length > 0) {
const currentItem = stack.pop()!;
for (const currentFileMeta of Object.values(currentItem.zip.files)) {
if (currentFileMeta.dir) {
this.createDirectory(
vscode.Uri.parse(
`${FS_SCHEME}:${currentItem.path}/${currentFileMeta.name}`
)
);
} else {
const currentFileContent = await currentItem.zip
.file(currentFileMeta.name)
?.async('uint8array');
if (currentFileContent) {
this.writeFile(
vscode.Uri.parse(
`${FS_SCHEME}:${currentItem.path}${currentFileMeta.name}`
),
currentFileContent,
{ create: true, overwrite: true }
);
}
}
}
});
}
return vscode.Uri.parse(`${FS_SCHEME}:/`);
}
}

0 comments on commit e4a1c56

Please # to comment.