Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: nested cloud sketch folder sync #2388

Draft
wants to merge 4 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
62 changes: 38 additions & 24 deletions arduino-ide-extension/src/browser/create/create-fs-provider.ts
Original file line number Diff line number Diff line change
@@ -1,35 +1,36 @@
import { inject, injectable } from '@theia/core/shared/inversify';
import URI from '@theia/core/lib/common/uri';
import { Event } from '@theia/core/lib/common/event';
import { FrontendApplicationContribution } from '@theia/core/lib/browser/frontend-application';
import {
Disposable,
DisposableCollection,
} from '@theia/core/lib/common/disposable';
import { FrontendApplicationContribution } from '@theia/core/lib/browser/frontend-application';
import { Event } from '@theia/core/lib/common/event';
import URI from '@theia/core/lib/common/uri';
import { inject, injectable } from '@theia/core/shared/inversify';
import {
FileService,
FileServiceContribution,
} from '@theia/filesystem/lib/browser/file-service';
import {
Stat,
FileType,
FileChange,
FileWriteOptions,
FileDeleteOptions,
FileOverwriteOptions,
FileSystemProvider,
FileSystemProviderCapabilities,
FileSystemProviderError,
FileSystemProviderErrorCode,
FileSystemProviderCapabilities,
FileType,
FileWriteOptions,
Stat,
WatchOptions,
createFileSystemProviderError,
} from '@theia/filesystem/lib/common/files';
import {
FileService,
FileServiceContribution,
} from '@theia/filesystem/lib/browser/file-service';
import { SketchesService } from '../../common/protocol';
import { stringToUint8Array } from '../../common/utils';
import { ArduinoPreferences } from '../arduino-preferences';
import { AuthenticationClientService } from '../auth/authentication-client-service';
import { CreateApi } from './create-api';
import { CreateUri } from './create-uri';
import { SketchesService } from '../../common/protocol';
import { ArduinoPreferences } from '../arduino-preferences';
import { Create } from './typings';
import { stringToUint8Array } from '../../common/utils';
import { Create, isNotFound } from './typings';

@injectable()
export class CreateFsProvider
Expand Down Expand Up @@ -90,14 +91,27 @@ export class CreateFsProvider
size: 0,
};
}
const resource = await this.getCreateApi.stat(uri.path.toString());
const mtime = Date.parse(resource.modified_at);
return {
type: this.toFileType(resource.type),
ctime: mtime,
mtime,
size: 0,
};
try {
const resource = await this.getCreateApi.stat(uri.path.toString());
const mtime = Date.parse(resource.modified_at);
return {
type: this.toFileType(resource.type),
ctime: mtime,
mtime,
size: 0,
};
} catch (err) {
let errToRethrow = err;
// Not Found (Create API) errors must be remapped to VS Code filesystem provider specific errors
// https://code.visualstudio.com/api/references/vscode-api#FileSystemError
if (isNotFound(errToRethrow)) {
errToRethrow = createFileSystemProviderError(
errToRethrow,
FileSystemProviderErrorCode.FileNotFound
);
}
throw errToRethrow;
}
}

async mkdir(uri: URI): Promise<void> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,13 @@ export class WorkspaceCommandContribution extends TheiaWorkspaceCommandContribut
execute: (uri) => this.newFile(uri),
})
);
registry.unregisterCommand(WorkspaceCommands.NEW_FOLDER);
registry.registerCommand(
WorkspaceCommands.NEW_FOLDER,
this.newWorkspaceRootUriAwareCommandHandler({
execute: (uri) => this.newFolder(uri),
})
);
registry.unregisterCommand(WorkspaceCommands.FILE_RENAME);
registry.registerCommand(
WorkspaceCommands.FILE_RENAME,
Expand All @@ -72,6 +79,37 @@ export class WorkspaceCommandContribution extends TheiaWorkspaceCommandContribut
);
}

private async newFolder(uri: URI | undefined): Promise<void> {
if (!uri) {
return;
}

const parent = await this.getDirectory(uri);
if (!parent) {
return;
}

const dialog = new WorkspaceInputDialog(
{
title: nls.localizeByDefault('New Folder...'),
parentUri: uri,
placeholder: nls.localize(
'theia/workspace/newFolderPlaceholder',
'Folder Name'
),
validate: (name) => this.validateFileName(name, parent, true),
},
this.labelProvider
);
const name = await this.openDialog(dialog, uri);
if (!name) {
return;
}
const folderUri = uri.resolve(name);
await this.fileService.createFolder(folderUri);
this.fireCreateNewFile({ parent: uri, uri: folderUri });
}

private async newFile(uri: URI | undefined): Promise<void> {
if (!uri) {
return;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -389,21 +389,28 @@ export class CloudSketchbookTree extends SketchbookTree {

private async sync(source: URI, dest: URI): Promise<void> {
const { filesToWrite, filesToDelete } = await this.treeDiff(source, dest);
await Promise.all(
filesToWrite.map(async ({ source, dest }) => {
if ((await this.fileService.resolve(source)).isFile) {
const content = await this.fileService.read(source);
return this.fileService.write(dest, content.value);
}
return this.fileService.createFolder(dest);
})
// Sort by the URIs. The shortest comes first. It's to ensure creating the parent folder for nested resources, for example.
// When sorting the URIs, it does not matter whether on source or dest, only the URI path and its length matters; they're the same for a source+dest pair
const uriPathLengthComparator = (left: URI, right: URI) =>
left.path.toString().length - right.path.toString().length;
filesToWrite.sort((left, right) =>
uriPathLengthComparator(left.source, right.source)
);
for (const { source, dest } of filesToWrite) {
const stat = await this.fileService.resolve(source);
if (stat.isFile) {
const content = await this.fileService.read(source);
await this.fileService.write(dest, content.value);
} else {
await this.fileService.createFolder(dest);
}
}

await Promise.all(
filesToDelete.map((file) =>
this.fileService.delete(file, { recursive: true })
)
);
// Longes URI paths come first to delete the most nested ones first.
filesToDelete.sort(uriPathLengthComparator).reverse();
for (const resource of filesToDelete) {
await this.fileService.delete(resource, { recursive: true });
}
}

override async resolveChildren(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,14 @@ export namespace SketchbookCommands {
'arduino/sketch/openFolder'
);

export const NEW_FOLDER = Command.toLocalizedCommand(
{
id: 'arduino-sketchbook--new-folder',
label: 'New Folder',
},
'arduino/sketch/newFolder'
);

export const OPEN_SKETCHBOOK_CONTEXT_MENU: Command = {
id: 'arduino-sketchbook--open-sketch-context-menu',
iconClass: 'sketchbook-tree__opts',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,10 @@ import {
} from '../../sketches-service-client-impl';
import { FileService } from '@theia/filesystem/lib/browser/file-service';
import { URI } from '../../contributions/contribution';
import { WorkspaceInput } from '@theia/workspace/lib/browser';
import {
WorkspaceCommands,
WorkspaceInput,
} from '@theia/workspace/lib/browser';

export const SKETCHBOOK__CONTEXT = ['arduino-sketchbook--context'];

Expand Down Expand Up @@ -130,6 +133,21 @@ export class SketchbookWidgetContribution
!!arg && 'node' in arg && SketchbookTree.SketchDirNode.is(arg.node),
});

registry.registerCommand(SketchbookCommands.NEW_FOLDER, {
execute: async (arg) => {
if (arg.node.uri) {
return registry.executeCommand(
WorkspaceCommands.NEW_FOLDER.id,
arg.node.uri
);
}
},
isEnabled: (arg) =>
!!arg && 'node' in arg && SketchbookTree.SketchDirNode.is(arg.node),
isVisible: (arg) =>
!!arg && 'node' in arg && SketchbookTree.SketchDirNode.is(arg.node),
});

registry.registerCommand(SketchbookCommands.OPEN_SKETCHBOOK_CONTEXT_MENU, {
isEnabled: (arg) =>
!!arg && 'node' in arg && SketchbookTree.SketchDirNode.is(arg.node),
Expand Down Expand Up @@ -206,6 +224,12 @@ export class SketchbookWidgetContribution
label: SketchbookCommands.REVEAL_IN_FINDER.label,
order: '0',
});

registry.registerMenuAction(SKETCHBOOK__CONTEXT__MAIN_GROUP, {
commandId: SketchbookCommands.NEW_FOLDER.id,
label: SketchbookCommands.NEW_FOLDER.label,
order: '1',
});
}

private openNewWindow(
Expand Down
4 changes: 3 additions & 1 deletion i18n/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -455,6 +455,7 @@
"moving": "Moving",
"movingMsg": "The file \"{0}\" needs to be inside a sketch folder named \"{1}\".\nCreate this folder, move the file, and continue?",
"new": "New Sketch",
"newFolder": "New Folder",
"noTrailingPeriod": "A filename cannot end with a dot",
"openFolder": "Open Folder",
"openRecent": "Open Recent",
Expand Down Expand Up @@ -545,7 +546,8 @@
"deleteCurrentSketch": "The sketch '{0}' will be permanently deleted. This action is irreversible. Do you want to delete the current sketch?",
"fileNewName": "Name for new file",
"invalidExtension": ".{0} is not a valid extension",
"newFileName": "New name for file"
"newFileName": "New name for file",
"newFolderPlaceholder": "Folder Name"
}
}
}
Loading