diff --git a/arduino-ide-extension/src/browser/arduino-ide-frontend-module.ts b/arduino-ide-extension/src/browser/arduino-ide-frontend-module.ts index 1854a09f..0ef4fb5d 100644 --- a/arduino-ide-extension/src/browser/arduino-ide-frontend-module.ts +++ b/arduino-ide-extension/src/browser/arduino-ide-frontend-module.ts @@ -139,6 +139,7 @@ import { Help } from './contributions/help'; import { bindArduinoPreferences } from './arduino-preferences' import { SettingsService, SettingsDialog, SettingsWidget, SettingsDialogProps } from './settings'; import { AddFile } from './contributions/add-file'; +import { ArchiveSketch } from './contributions/archive-sketch'; const ElementQueries = require('css-element-queries/src/ElementQueries'); @@ -346,6 +347,7 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => { Contribution.configure(bind, OpenRecentSketch); Contribution.configure(bind, Help); Contribution.configure(bind, AddFile); + Contribution.configure(bind, ArchiveSketch); bind(OutputServiceImpl).toSelf().inSingletonScope().onActivation(({ container }, outputService) => { WebSocketConnectionProvider.createProxy(container, OutputServicePath, outputService); diff --git a/arduino-ide-extension/src/browser/contributions/archive-sketch.ts b/arduino-ide-extension/src/browser/contributions/archive-sketch.ts new file mode 100644 index 00000000..d4f8da0d --- /dev/null +++ b/arduino-ide-extension/src/browser/contributions/archive-sketch.ts @@ -0,0 +1,55 @@ +import { injectable } from 'inversify'; +import { remote } from 'electron'; +import * as dateFormat from 'dateformat'; +import URI from '@theia/core/lib/common/uri'; +import { ArduinoMenus } from '../menu/arduino-menus'; +import { SketchContribution, Command, CommandRegistry, MenuModelRegistry } from './contribution'; + +@injectable() +export class ArchiveSketch extends SketchContribution { + + registerCommands(registry: CommandRegistry): void { + registry.registerCommand(ArchiveSketch.Commands.ARCHIVE_SKETCH, { + execute: () => this.archiveSketch() + }); + } + + registerMenus(registry: MenuModelRegistry): void { + registry.registerMenuAction(ArduinoMenus.TOOLS__MAIN_GROUP, { + commandId: ArchiveSketch.Commands.ARCHIVE_SKETCH.id, + label: 'Archive Sketch', + order: '1' + }); + } + + protected async archiveSketch(): Promise { + const [sketch, config] = await Promise.all([ + this.sketchServiceClient.currentSketch(), + this.configService.getConfiguration() + ]); + if (!sketch) { + return; + } + const archiveBasename = `${sketch.name}-${dateFormat(new Date(), 'yymmdd')}a.zip`; + const defaultPath = await this.fileService.fsPath(new URI(config.sketchDirUri).resolve(archiveBasename)); + const { filePath, canceled } = await remote.dialog.showSaveDialog({ title: 'Save sketch folder as...', defaultPath }); + if (!filePath || canceled) { + return; + } + const destinationUri = await this.fileSystemExt.getUri(filePath); + if (!destinationUri) { + return; + } + await this.sketchService.archive(sketch, destinationUri.toString()); + this.messageService.info(`Created archive '${archiveBasename}'.`, { timeout: 2000 }); + } + +} + +export namespace ArchiveSketch { + export namespace Commands { + export const ARCHIVE_SKETCH: Command = { + id: 'arduino-archive-sketch' + }; + } +} diff --git a/arduino-ide-extension/src/common/protocol/sketches-service.ts b/arduino-ide-extension/src/common/protocol/sketches-service.ts index d4bddf9d..de79ccf0 100644 --- a/arduino-ide-extension/src/common/protocol/sketches-service.ts +++ b/arduino-ide-extension/src/common/protocol/sketches-service.ts @@ -58,6 +58,11 @@ export interface SketchesService { */ recentlyOpenedSketches(): Promise; + /** + * Archives the sketch, resolves to the archive URI. + */ + archive(sketch: Sketch, destinationUri: string): Promise; + } export interface Sketch { diff --git a/arduino-ide-extension/src/node/sketches-service-impl.ts b/arduino-ide-extension/src/node/sketches-service-impl.ts index d5d664a2..f1f3135b 100644 --- a/arduino-ide-extension/src/node/sketches-service-impl.ts +++ b/arduino-ide-extension/src/node/sketches-service-impl.ts @@ -14,7 +14,7 @@ import { firstToLowerCase } from '../common/utils'; import { NotificationServiceServerImpl } from './notification-service-server'; import { EnvVariablesServer } from '@theia/core/lib/common/env-variables'; import { CoreClientProvider } from './core-client-provider'; -import { LoadSketchReq } from './cli-protocol/commands/commands_pb'; +import { LoadSketchReq, ArchiveSketchReq } from './cli-protocol/commands/commands_pb'; const WIN32_DRIVE_REGEXP = /^[a-zA-Z]:\\/; @@ -324,6 +324,29 @@ void loop() { return FileUri.create(destination).toString(); } + async archive(sketch: Sketch, destinationUri: string): Promise { + await this.loadSketch(sketch.uri); // sanity check + const { client } = await this.coreClient(); + const archivePath = FileUri.fsPath(destinationUri); + // The CLI cannot override existing archives, so we have to wipe it manually: https://github.com/arduino/arduino-cli/issues/1160 + if (await fs.exists(archivePath)) { + await fs.unlink(archivePath); + } + const req = new ArchiveSketchReq(); + req.setSketchPath(FileUri.fsPath(sketch.uri)); + req.setArchivePath(archivePath); + await new Promise((resolve, reject) => { + client.archiveSketch(req, err => { + if (err) { + reject(err); + return; + } + resolve(destinationUri); + }); + }); + return destinationUri; + } + private async coreClient(): Promise { const coreClient = await new Promise(async resolve => { const client = await this.coreClientProvider.client();