From 478c36c5bde5369f164b6e993e2f7d9307606ae1 Mon Sep 17 00:00:00 2001 From: Akos Kitta Date: Wed, 21 Feb 2024 12:32:41 +0100 Subject: [PATCH] fix: nested cloud sketch folder sync Signed-off-by: Akos Kitta --- .../src/browser/create/create-fs-provider.ts | 72 +++++++++++-------- .../cloud-sketchbook/cloud-sketchbook-tree.ts | 22 +++--- 2 files changed, 57 insertions(+), 37 deletions(-) diff --git a/arduino-ide-extension/src/browser/create/create-fs-provider.ts b/arduino-ide-extension/src/browser/create/create-fs-provider.ts index 7908d055..231d5f04 100644 --- a/arduino-ide-extension/src/browser/create/create-fs-provider.ts +++ b/arduino-ide-extension/src/browser/create/create-fs-provider.ts @@ -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 { - Stat, - FileType, - FileChange, - FileWriteOptions, - FileDeleteOptions, - FileOverwriteOptions, - FileSystemProvider, - FileSystemProviderError, - FileSystemProviderErrorCode, - FileSystemProviderCapabilities, - WatchOptions, -} from '@theia/filesystem/lib/common/files'; +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 { + FileChange, + FileDeleteOptions, + FileOverwriteOptions, + FileSystemProvider, + FileSystemProviderCapabilities, + FileSystemProviderError, + FileSystemProviderErrorCode, + FileType, + FileWriteOptions, + Stat, + WatchOptions, + createFileSystemProviderError, +} from '@theia/filesystem/lib/common/files'; +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 @@ -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(err)) { + errToRethrow = createFileSystemProviderError( + err, + FileSystemProviderErrorCode.FileNotFound + ); + } + throw errToRethrow; + } } async mkdir(uri: URI): Promise { diff --git a/arduino-ide-extension/src/browser/widgets/cloud-sketchbook/cloud-sketchbook-tree.ts b/arduino-ide-extension/src/browser/widgets/cloud-sketchbook/cloud-sketchbook-tree.ts index 9a5571da..ddb805a6 100644 --- a/arduino-ide-extension/src/browser/widgets/cloud-sketchbook/cloud-sketchbook-tree.ts +++ b/arduino-ide-extension/src/browser/widgets/cloud-sketchbook/cloud-sketchbook-tree.ts @@ -389,15 +389,21 @@ export class CloudSketchbookTree extends SketchbookTree { private async sync(source: URI, dest: URI): Promise { 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 + filesToWrite.sort( + (left, right) => + left.source.path.toString().length - right.source.path.toString().length ); + 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) =>