Skip to content

Commit

Permalink
feat: local replica bulky sync with progress and cancelable (#155)
Browse files Browse the repository at this point in the history
  • Loading branch information
iamhyc authored Jun 30, 2024
1 parent 7e9dc2b commit df2e8b1
Show file tree
Hide file tree
Showing 4 changed files with 86 additions and 25 deletions.
5 changes: 4 additions & 1 deletion l10n/bundle.l10n.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,14 @@
"Click to configure.": "Click to configure.",
"Disabled": "Disabled",
"Synced": "Synced",
"\"{scm}\" creation failed.": "\"{scm}\" creation failed.",
"Create Source Control: {scm}": "Create Source Control: {scm}",
"\"{scm}\" created: {uri}.": "\"{scm}\" created: {uri}.",
"\"{scm}\" creation failed.": "\"{scm}\" creation failed.",
"Project Source Control Management": "Project Source Control Management",
"Remove": "Remove",
"Local Replica": "Local Replica",
"Invalid Path. Please make sure the absolute path to a folder with read/write permissions is used.": "Invalid Path. Please make sure the absolute path to a folder with read/write permissions is used.",
"Sync Files": "Sync Files",
"e.g., /home/user/empty/local/folder": "e.g., /home/user/empty/local/folder",
"Configure sync ignore patterns ...": "Configure sync ignore patterns ...",
"Press Enter to add a new pattern, or click the trash icon to remove a pattern.": "Press Enter to add a new pattern, or click the trash icon to remove a pattern.",
Expand Down Expand Up @@ -73,7 +74,9 @@
"Remove project \"{label}\" from tag \"{name}\" ?": "Remove project \"{label}\" from tag \"{name}\" ?",
"Please close the open remote overleaf folder firstly.": "Please close the open remote overleaf folder firstly.",
"No local replica found, create one for project \"{label}\" ?": "No local replica found, create one for project \"{label}\" ?",
"Remove local replica": "Remove local replica",
"Select the local replica below.": "Select the local replica below.",
"Remove local replica \"{label}\" ?": "Remove local replica \"{label}\" ?",
"Compile Checker": "Compile Checker",
"Compile Success": "Compile Success",
"Compiling": "Compiling",
Expand Down
46 changes: 40 additions & 6 deletions src/core/projectManagerProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -599,16 +599,50 @@ export class ProjectManagerProvider implements vscode.TreeDataProvider<DataItem>
// open local replica
const replicasPath = replicas.map(scmPersist => vscode.Uri.parse(scmPersist.baseUri).fsPath);
if (replicasPath.length===0) { return; }
const path = await vscode.window.showQuickPick(replicasPath, {
canPickMany:false,
placeHolder:vscode.l10n.t('Select the local replica below.')
const quickPickItems = replicasPath.map(path => {
let label = path;
let buttons = [{
id: "removal",
iconPath: new vscode.ThemeIcon('trash'),
tooltip: vscode.l10n.t('Remove local replica'),
}];
return {label, buttons};
});
if (path) {
const uri = vscode.Uri.file(path);

// select local replica via quick pick
new Promise(resolve => {
const quickPick = vscode.window.createQuickPick();
quickPick.placeholder = vscode.l10n.t('Select the local replica below.');
quickPick.items = quickPickItems;
quickPick.onDidTriggerItemButton(({button,item}) => {
if ((button as any).id === "removal") {
vscode.window.showWarningMessage( vscode.l10n.t('Remove local replica "{label}" ?', {label:item.label}), 'Yes', 'No')
.then(answer => {
if (answer === 'Yes') {
// remove local replica from scm persists
const scmKey = Object.keys(scmPersists).find(key => vscode.Uri.parse(scmPersists[key].baseUri).fsPath===item.label)!;
GlobalStateManager.updateServerProjectSCMPersist(this.context, serverName, projectId, scmKey);
// remove entry from quick pick
quickPick.items = quickPick.items.filter(item => item.label!==item.label);
}
});
}
});
quickPick.onDidAccept(() => {
const path = quickPick.selectedItems[0]?.label;
if (path) {
quickPick.dispose();
resolve(path);
}
});
quickPick.show();
})
.then(path => {
const uri = vscode.Uri.file(path as string);
// always open in current window
vscode.commands.executeCommand('vscode.openFolder', uri, false);
vscode.commands.executeCommand('workbench.view.explorer');
}
});
}

get triggers() {
Expand Down
57 changes: 40 additions & 17 deletions src/scm/localReplicaSCM.ts
Original file line number Diff line number Diff line change
Expand Up @@ -175,24 +175,45 @@ export class LocalReplicaSCMProvider extends BaseSCM {
return true;
}

private async overwrite(root: string='/') {
const vfsUri = this.vfs.pathToUri(root);
const files = await vscode.workspace.fs.readDirectory(vfsUri);

for (const [name, type] of files) {
const relPath = root + name;
// bypass ignore files
if (this.matchIgnorePatterns(relPath)) {
continue;
private async overwrite(root: string='/'): Promise<boolean|undefined> {
return await vscode.window.withProgress({
location: vscode.ProgressLocation.Notification,
title: vscode.l10n.t('Sync Files'),
cancellable: true,
}, async (progress, token) => {
// breadth-first search for the files
const files: [string,string][] = [];
const queue: string[] = [root];
while (queue.length!==0) {
const nextRoot = queue.shift();
const vfsUri = this.vfs.pathToUri(nextRoot!);
const items = await vscode.workspace.fs.readDirectory(vfsUri);
if (token.isCancellationRequested) { return undefined; }
//
for (const [name, type] of items) {
const relPath = nextRoot + name;
if (this.matchIgnorePatterns(relPath)) {
continue;
}
if (type === vscode.FileType.Directory) {
queue.push(relPath+'/');
} else {
files.push([name, relPath]);
}
}
}
// recursively overwrite
if (type === vscode.FileType.Directory) {
const nextRoot = relPath+'/';
await this.overwrite(nextRoot);
} else {

// sync the files
const total = files.length;
for (let i=0; i<total; i++) {
const [name, relPath] = files[i];
const vfsUri = this.vfs.pathToUri(relPath);
if (token.isCancellationRequested) { return false; }
progress.report({increment: 100/total, message: relPath});
//
const baseContent = this.baseCache[relPath];
const localContent = await this.readFile(relPath);
const remoteContent = await vscode.workspace.fs.readFile(vscode.Uri.joinPath(vfsUri, name));
const remoteContent = await vscode.workspace.fs.readFile(vfsUri);
if (baseContent===undefined || localContent===undefined) {
this.setBypassCache(relPath, remoteContent);
await this.writeFile(relPath, remoteContent);
Expand All @@ -210,11 +231,13 @@ export class LocalReplicaSCMProvider extends BaseSCM {
await this.writeFile(relPath, mergedContent);
// write the merged content to remote
if (localPatches.length!==0) {
await vscode.workspace.fs.writeFile(vscode.Uri.joinPath(vfsUri, name), mergedContent);
await vscode.workspace.fs.writeFile(vfsUri, mergedContent);
}
}
}
}

return true;
});
}

private bypassSync(action:'push'|'pull', type:'update'|'delete', relPath: string, content?: Uint8Array): boolean {
Expand Down
3 changes: 2 additions & 1 deletion src/scm/scmCollectionProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,8 @@ export class SCMCollectionProvider extends vscode.Disposable {
return scm;
} catch (error) {
// permanently remove failed scm
this.vfs.setProjectSCMPersist(scm.scmKey, undefined);
// this.vfs.setProjectSCMPersist(scm.scmKey, undefined);
vscode.window.showErrorMessage( vscode.l10n.t('"{scm}" creation failed.', {scm:scmProto.label}) );
return undefined;
}
}
Expand Down

0 comments on commit df2e8b1

Please sign in to comment.