From 1e86cca4179e660a1890023f3162e88e859dcfbf Mon Sep 17 00:00:00 2001 From: Marcel Kloubert Date: Wed, 11 Apr 2018 07:01:32 +0200 Subject: [PATCH] auto deploy buttons --- CHANGELOG.md | 10 ++ package-lock.json | 8 +- package.json | 138 ++++++++++++++++- src/buttons.ts | 50 ++++++- src/clients/ftp.ts | 16 +- src/contracts.ts | 12 ++ src/deploy.ts | 156 ++++++++++++++++---- src/i18.ts | 12 ++ src/lang/de.ts | 12 ++ src/lang/en.ts | 12 ++ src/packages.ts | 188 ++++++++++++++++++----- src/workspaces.ts | 360 ++++++++++++++++++++++++++++++++++++++------- 12 files changed, 830 insertions(+), 144 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ec030c3..b86dcca 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,16 @@ [![Share via Facebook](https://raw.githubusercontent.com/mkloubert/vscode-deploy-reloaded/master/img/share/Facebook.png)](https://www.facebook.com/sharer/sharer.php?u=https%3A%2F%2Fmarketplace.visualstudio.com%2Fitems%3FitemName%3Dmkloubert.vscode-deploy-reloaded"e=vscode-deploy-reloaded) [![Share via Twitter](https://raw.githubusercontent.com/mkloubert/vscode-deploy-reloaded/master/img/share/Twitter.png)](https://twitter.com/intent/tweet?source=https%3A%2F%2Fmarketplace.visualstudio.com%2Fitems%3FitemName%3Dmkloubert.vscode-deploy-reloaded&text=vscode-deploy-reloaded:%20https%3A%2F%2Fmarketplace.visualstudio.com%2Fitems%3FitemName%3Dmkloubert.vscode-deploy-reloaded&via=mjkloubert) [![Share via Google+](https://raw.githubusercontent.com/mkloubert/vscode-deploy-reloaded/master/img/share/Google+.png)](https://plus.google.com/share?url=https%3A%2F%2Fmarketplace.visualstudio.com%2Fitems%3FitemName%3Dmkloubert.vscode-deploy-reloaded) [![Share via Pinterest](https://raw.githubusercontent.com/mkloubert/vscode-deploy-reloaded/master/img/share/Pinterest.png)](https://pinterest.com/pin/create/button/?url=https%3A%2F%2Fmarketplace.visualstudio.com%2Fitems%3FitemName%3Dmkloubert.vscode-deploy-reloaded&media=https://raw.githubusercontent.com/mkloubert/vscode-deploy-reloaded/master/img/demo1.gif&description=Recoded%20version%20of%20Visual%20Studio%20Code%20extension%20%27vs-deploy%27%2C%20which%20provides%20commands%20to%20deploy%20files%20to%20one%20or%20more%20destinations.) [![Share via Reddit](https://raw.githubusercontent.com/mkloubert/vscode-deploy-reloaded/master/img/share/Reddit.png)](https://www.reddit.com/submit?url=https%3A%2F%2Fmarketplace.visualstudio.com%2Fitems%3FitemName%3Dmkloubert.vscode-deploy-reloaded&title=vscode-deploy-reloaded) [![Share via LinkedIn](https://raw.githubusercontent.com/mkloubert/vscode-deploy-reloaded/master/img/share/LinkedIn.png)](https://www.linkedin.com/shareArticle?mini=true&url=https%3A%2F%2Fmarketplace.visualstudio.com%2Fitems%3FitemName%3Dmkloubert.vscode-deploy-reloaded&title=vscode-deploy-reloaded&summary=Recoded%20version%20of%20Visual%20Studio%20Code%20extension%20%27vs-deploy%27%2C%20which%20provides%20commands%20to%20deploy%20files%20to%20one%20or%20more%20destinations.&source=https%3A%2F%2Fmarketplace.visualstudio.com%2Fitems%3FitemName%3Dmkloubert.vscode-deploy-reloaded) [![Share via Wordpress](https://raw.githubusercontent.com/mkloubert/vscode-deploy-reloaded/master/img/share/Wordpress.png)](https://wordpress.com/press-this.php?u=https%3A%2F%2Fmarketplace.visualstudio.com%2Fitems%3FitemName%3Dmkloubert.vscode-deploy-reloaded"e=vscode-deploy-reloaded&s=Recoded%20version%20of%20Visual%20Studio%20Code%20extension%20%27vs-deploy%27%2C%20which%20provides%20commands%20to%20deploy%20files%20to%20one%20or%20more%20destinations.&i=https://raw.githubusercontent.com/mkloubert/vscode-deploy-reloaded/master/img/demo1.gif) [![Share via Email](https://raw.githubusercontent.com/mkloubert/vscode-deploy-reloaded/master/img/share/Email.png)](mailto:?subject=vscode-deploy-reloaded&body=Recoded%20version%20of%20Visual%20Studio%20Code%20extension%20'vs-deploy'%2C%20which%20provides%20commands%20to%20deploy%20files%20to%20one%20or%20more%20destinations.:%20https%3A%2F%2Fmarketplace.visualstudio.com%2Fitems%3FitemName%3Dmkloubert.vscode-deploy-reloaded) +## 0.70.0 (April 11th, 2018; auto deployment) + +* added `pauseFilesFor` setting for [packages](https://github.com/mkloubert/vscode-deploy-reloaded/wiki#packages-), which can define a time, in milliseconds, to wait, before an auto operation can be done for a file again +* added `deployOnChangeButton` [setting](https://github.com/mkloubert/vscode-deploy-reloaded/wiki#settings--), which can setup a [button](https://github.com/mkloubert/vscode-deploy-reloaded/wiki/deploy_on_change#buttons), that is able to activate or deactivate [deploy on change](https://github.com/mkloubert/vscode-deploy-reloaded/wiki/deploy_on_change) feature +* added `deployOnSaveButton` [setting](https://github.com/mkloubert/vscode-deploy-reloaded/wiki#settings--), which can setup a [button](https://github.com/mkloubert/vscode-deploy-reloaded/wiki/deploy_on_save#buttons), that is able to activate or deactivate [deploy on save](https://github.com/mkloubert/vscode-deploy-reloaded/wiki/deploy_on_save) feature +* added `removeOnChangeButton` [setting](https://github.com/mkloubert/vscode-deploy-reloaded/wiki#settings--), which can setup a [button](https://github.com/mkloubert/vscode-deploy-reloaded/wiki/remove_on_change#buttons), that is able to activate or deactivate [remove on change](https://github.com/mkloubert/vscode-deploy-reloaded/wiki/remove_on_change) feature +* improved concurrent auto deployments +* fixed auto creation of directories on [ftp server](https://github.com/mkloubert/vscode-deploy-reloaded/wiki/target_ftp) +* more bugfixes + ## 0.69.1 (April 10th, 2018; log files) * added `Deploy Reloaded: Log files ...` command, which can open an extension's log file now diff --git a/package-lock.json b/package-lock.json index 6eadab3..9383561 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "vscode-deploy-reloaded", - "version": "0.69.1", + "version": "0.70.0", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -5597,9 +5597,9 @@ } }, "vscode-helpers": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/vscode-helpers/-/vscode-helpers-1.7.0.tgz", - "integrity": "sha512-E0vwaAJ3H9Dm55lmjPWtj2bLaWATOVc8i3ProgPDj26jz9GCEyz/Dd/6mEWkZCfB5w7UbvlSUY6wuMAsgRvTow==", + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/vscode-helpers/-/vscode-helpers-1.8.0.tgz", + "integrity": "sha512-xb2Wxde51ox+AcFeOU4tooPPUbDS9Q16O3EMDKRZtYhBLtraiRhPYCxIYuKJnWuQB1+T21dPe777X6C2E0H2zQ==", "requires": { "@types/fs-extra": "5.0.1", "@types/glob": "5.0.35", diff --git a/package.json b/package.json index 072bbe8..ff5b5a9 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "vscode-deploy-reloaded", "displayName": "Deploy (Reloaded)", "description": "Deploys files of a workspace to a destination.", - "version": "0.69.1", + "version": "0.70.0", "publisher": "mkloubert", "engines": { "vscode": "^1.22.0" @@ -511,11 +511,97 @@ "type": "boolean", "default": true }, + "deployOnChangeButton": { + "description": "Show a button that (de-)activate 'deploy on change' feature.", + "oneOf": [ + { + "description": "Show a button that (de-)activate 'deploy on change' feature or not.", + "type": "boolean", + "default": false + }, + { + "description": "Settings for showing a button that (de-)activate 'deploy on change' feature.", + "type": "object", + "properties": { + "color": { + "description": "The (text) color for the button.", + "type": "string", + "default": "button.foreground" + }, + "enabled": { + "description": "Enable button or not.", + "type": "boolean", + "default": true + }, + "isRight": { + "description": "Put button on the right side or not.", + "type": "boolean", + "default": false + }, + "priority": { + "description": "The priority.", + "type": "integer" + }, + "text": { + "description": "The custom display text.", + "type": "string" + }, + "tooltip": { + "description": "The custom tooltip.", + "type": "string" + } + } + } + ] + }, "deployOnSave": { "description": "Activates or deactivates 'deploy on save' feature for all packages.", "type": "boolean", "default": true }, + "deployOnSaveButton": { + "description": "Show a button that (de-)activate 'deploy on save' feature.", + "oneOf": [ + { + "description": "Show a button that (de-)activate 'deploy on save' feature or not.", + "type": "boolean", + "default": false + }, + { + "description": "Settings for showing a button that (de-)activate 'deploy on save' feature.", + "type": "object", + "properties": { + "color": { + "description": "The (text) color for the button.", + "type": "string", + "default": "button.foreground" + }, + "enabled": { + "description": "Enable button or not.", + "type": "boolean", + "default": true + }, + "isRight": { + "description": "Put button on the right side or not.", + "type": "boolean", + "default": false + }, + "priority": { + "description": "The priority.", + "type": "integer" + }, + "text": { + "description": "The custom display text.", + "type": "string" + }, + "tooltip": { + "description": "The custom tooltip.", + "type": "string" + } + } + } + ] + }, "env": { "description": "Settings for the process's environment.", "type": "object", @@ -988,6 +1074,11 @@ "description": "A (display) name for that package.", "type": "string" }, + "pauseFilesFor": { + "description": "The number of milliseconds to wait before an auto operation can be done for a file again.", + "type": "integer", + "minimum": 0 + }, "platforms": { "description": "One or more platform names the package is available for.", "type": "array", @@ -1264,6 +1355,49 @@ "type": "boolean", "default": true }, + "removeOnChangeButton": { + "description": "Show a button that (de-)activate 'remove on change' feature.", + "oneOf": [ + { + "description": "Show a button that (de-)activate 'remove on change' feature or not.", + "type": "boolean", + "default": false + }, + { + "description": "Settings for showing a button that (de-)activate 'remove on change' feature.", + "type": "object", + "properties": { + "color": { + "description": "The (text) color for the button.", + "type": "string", + "default": "button.foreground" + }, + "enabled": { + "description": "Enable button or not.", + "type": "boolean", + "default": true + }, + "isRight": { + "description": "Put button on the right side or not.", + "type": "boolean", + "default": false + }, + "priority": { + "description": "The priority.", + "type": "integer" + }, + "text": { + "description": "The custom display text.", + "type": "string" + }, + "tooltip": { + "description": "The custom tooltip.", + "type": "string" + } + } + } + ] + }, "requiredExtensions": { "description": "Defines one or more Visual Studio Code extensions, which are required.", "type": "object", @@ -71706,6 +71840,6 @@ "tmp": "0.0.33", "uglify-js": "^3.3.20", "uuid": "^3.2.1", - "vscode-helpers": "^1.7.0" + "vscode-helpers": "^1.8.0" } } diff --git a/src/buttons.ts b/src/buttons.ts index 20a1932..fb88aef 100644 --- a/src/buttons.ts +++ b/src/buttons.ts @@ -22,6 +22,12 @@ import * as deploy_workspaces from './workspaces'; import * as vscode from 'vscode'; +/** + * Describes a button, which can (de-)activate an auto deploy operation. + */ +export interface AutoDeployButton extends deploy_contracts.Button { +} + /** * Description for a global button. */ @@ -32,6 +38,28 @@ export interface Button extends deploy_contracts.ButtonWithCustomCommand { readonly arguments?: any[]; } +/** + * Describes a button that should be shown to actiovate or deactivate "deploy on change" feature. + */ +export interface DeployOnChangeButton extends AutoDeployButton { +} + +/** + * A value for a "deploy on change" button. + */ +export type DeployOnChangeButtonValue = boolean | DeployOnChangeButton; + +/** + * Describes a button that should be shown to actiovate or deactivate "deploy on save" feature. + */ +export interface DeployOnSaveButton extends AutoDeployButton { +} + +/** + * A value for a "deploy on save" button. + */ +export type DeployOnSaveButtonValue = boolean | DeployOnSaveButton; + interface FinishedButton extends vscode.Disposable { readonly button: vscode.StatusBarItem; readonly command: vscode.Disposable; @@ -42,6 +70,17 @@ interface GlobalButton extends vscode.Disposable { readonly command: vscode.Disposable; } +/** + * Describes a button that should be shown to actiovate or deactivate "remove on change" feature. + */ +export interface RemoveOnChangeButton extends AutoDeployButton { +} + +/** + * A value for a "remove on change" button. + */ +export type RemoveOnChangeButtonValue = boolean | RemoveOnChangeButton; + const KEY_FINISHED_BTNS = 'finished_buttons'; const KEY_FINISHED_BTN_DELETE = 'finish_delete'; @@ -90,14 +129,10 @@ function createFinishedButton(state: deploy_contracts.KeyValuePairs, key: string deploy_helpers.tryDispose( this.command ); if (BUTTONS) { - delete BUTTONS[ key ]; + delete BUTTONS[key]; } - if (timeouts) { - deploy_helpers.tryDispose( timeouts[key] ); - - delete timeouts[ key ]; - } + deploy_helpers.tryDisposeAndDelete(timeouts, key); } }; } @@ -358,8 +393,7 @@ export function setTimeoutForFinishedButton( } if (false !== key) { - deploy_helpers.tryDispose( timeouts[key] ); - delete timeouts[key]; + deploy_helpers.tryDisposeAndDelete(timeouts, key); const BTN = deploy_helpers.applyFuncFor( getFinishedButton, WORKSPACE diff --git a/src/clients/ftp.ts b/src/clients/ftp.ts index 078da55..d980225 100644 --- a/src/clients/ftp.ts +++ b/src/clients/ftp.ts @@ -261,14 +261,13 @@ export abstract class FTPClientBase extends deploy_clients.AsyncFileListBase { return false; } - // check if remote directory exists + // check if remote directory exists ... if (true === this._existingRemoteDirs[dir]) { - return false; + return false; // seems to exist } try { - // check if exist - await this.list(dir); + await this.cwd(dir); } catch (e) { // no, try to create @@ -1135,13 +1134,8 @@ class JsFTPClient extends FTPClientBase { const COMPLETED = deploy_helpers.createCompletedAction(resolve, reject); try { - ME.connection.list(dir, (err) => { - if (err) { - COMPLETED(err); - } - else { - COMPLETED(null); - } + ME.connection.raw("CWD", [ dir ], (err) => { + COMPLETED(err); }); } catch (e) { diff --git a/src/contracts.ts b/src/contracts.ts index 744f0bb..20e6a63 100644 --- a/src/contracts.ts +++ b/src/contracts.ts @@ -163,10 +163,18 @@ export interface Configuration extends deploy_packages.WithFastFileCheckSettings * Activates or deactivates 'deploy on change' feature for all packages. */ readonly deployOnChange?: boolean; + /** + * Shows a button that (de-)activate "deploy on change" feature. + */ + readonly deployOnChangeButton?: deploy_buttons.DeployOnChangeButtonValue; /** * Activates or deactivates 'deploy on save' feature for all packages. */ readonly deployOnSave?: boolean; + /** + * Shows a button that can (de-)activate "deploy on save" feature. + */ + readonly deployOnSaveButton?: deploy_buttons.DeployOnSaveButtonValue; /** * Settings for the process's environment. */ @@ -249,6 +257,10 @@ export interface Configuration extends deploy_packages.WithFastFileCheckSettings * Activates or deactivates "remove on change" feature for all packages. */ readonly removeOnChange?: boolean; + /** + * Shows a button that (de-)activate "remove on change" feature. + */ + readonly removeOnChangeButton?: deploy_buttons.RemoveOnChangeButtonValue; /** * Run build task on startup or define the wait time, in milliseconds, after * the build task should be run after startup. diff --git a/src/deploy.ts b/src/deploy.ts index a8c9422..2cd59ae 100644 --- a/src/deploy.ts +++ b/src/deploy.ts @@ -986,20 +986,72 @@ export async function deployPackage(pkg: deploy_packages.Package, targetResolver * @param {string} file The file to check. */ export async function deployOnChange(file: string) { - const ME: deploy_workspaces.Workspace = this; + const WORKSPACE: deploy_workspaces.Workspace = this; - return await deploy_helpers.applyFuncFor( - deploy_packages.autoDeployFile, - ME - )(file, - (pkg) => pkg.deployOnChange, - (pkg) => { - return deploy_packages.getFastFileCheckFlag( - pkg, (p) => p.fastCheckOnChange, - ME.config, (c) => c.fastCheckOnChange, - ); - }, - 'deploy.onChange.failed'); + if (WORKSPACE.isInFinalizeState) { + return; + } + + const CFG = WORKSPACE.config; + if (!CFG) { + return; + } + + const STATE = WORKSPACE.workspaceSessionState; + if (!STATE) { + return; + } + + const KEY = Path.resolve(file); + + const FILES_IN_PROGRESS = STATE['auto']['deploy']['on_change_or_save']; + + if (true === FILES_IN_PROGRESS[KEY]) { + return; + } + + FILES_IN_PROGRESS[KEY] = true; + const RESTORE_IN_PROGRESS_STATE = () => { + delete FILES_IN_PROGRESS[KEY]; + }; + + let pauseFilesFor: number; + const FINISHED = async () => { + deploy_helpers.tryDisposeAndDelete(STATE['auto'], deploy_packages.KEY_PAUSE_FILES_FOR); + + if (isNaN(pauseFilesFor)) { + RESTORE_IN_PROGRESS_STATE(); + } + else { + STATE['auto'][ deploy_packages.KEY_PAUSE_FILES_FOR ] = deploy_helpers.createTimeout(() => { + RESTORE_IN_PROGRESS_STATE(); + }, pauseFilesFor); + } + }; + + try { + const OPTS: deploy_packages.AutoDeployFileOptions = { + file: file, + settingsResolver: (pkg) => pkg.deployOnChange, + fastFileCheckFlagResolver: (pkg) => { + return deploy_packages.getFastFileCheckFlag( + pkg, (p) => p.fastCheckOnChange, + CFG, (c) => c.fastCheckOnChange, + ); + }, + errorMsgTemplate: 'deploy.onChange.failed', + prepareForPackage: (pkg) => { + pauseFilesFor = deploy_packages.getPauseFilesForValue(pkg, pauseFilesFor); + }, + }; + + await deploy_helpers.applyFuncFor( + deploy_packages.autoDeployFile, WORKSPACE + )(OPTS); + } + finally { + await FINISHED(); + } } /** @@ -1008,20 +1060,72 @@ export async function deployOnChange(file: string) { * @param {string} file The file to check. */ export async function deployOnSave(file: string) { - const ME: deploy_workspaces.Workspace = this; + const WORKSPACE: deploy_workspaces.Workspace = this; - return await deploy_helpers.applyFuncFor( - deploy_packages.autoDeployFile, - ME - )(file, - (pkg) => pkg.deployOnSave, - (pkg) => { - return deploy_packages.getFastFileCheckFlag( - pkg, (p) => p.fastCheckOnSave, - ME.config, (c) => c.fastCheckOnSave, - ); - }, - 'deploy.onSave.failed'); + if (WORKSPACE.isInFinalizeState) { + return; + } + + const CFG = WORKSPACE.config; + if (!CFG) { + return; + } + + const STATE = WORKSPACE.workspaceSessionState; + if (!STATE) { + return; + } + + const KEY = Path.resolve(file); + + const FILES_IN_PROGRESS = STATE['auto']['deploy']['on_change_or_save']; + + if (true === FILES_IN_PROGRESS[KEY]) { + return; + } + + FILES_IN_PROGRESS[KEY] = true; + const RESTORE_IN_PROGRESS_STATE = () => { + delete FILES_IN_PROGRESS[KEY]; + }; + + let pauseFilesFor: number; + const FINISHED = async () => { + deploy_helpers.tryDisposeAndDelete(STATE['auto'], deploy_packages.KEY_PAUSE_FILES_FOR); + + if (isNaN(pauseFilesFor)) { + RESTORE_IN_PROGRESS_STATE(); + } + else { + STATE['auto'][ deploy_packages.KEY_PAUSE_FILES_FOR ] = deploy_helpers.createTimeout(() => { + RESTORE_IN_PROGRESS_STATE(); + }, pauseFilesFor); + } + }; + + try { + const OPTS: deploy_packages.AutoDeployFileOptions = { + file: file, + settingsResolver: (pkg) => pkg.deployOnSave, + fastFileCheckFlagResolver: (pkg) => { + return deploy_packages.getFastFileCheckFlag( + pkg, (p) => p.fastCheckOnSave, + CFG, (c) => c.fastCheckOnSave, + ); + }, + errorMsgTemplate: 'deploy.onSave.failed', + prepareForPackage: (pkg) => { + pauseFilesFor = deploy_packages.getPauseFilesForValue(pkg, pauseFilesFor); + }, + }; + + await deploy_helpers.applyFuncFor( + deploy_packages.autoDeployFile, WORKSPACE + )(OPTS); + } + finally { + await FINISHED(); + } } /** diff --git a/src/i18.ts b/src/i18.ts index 3b1fd3d..afce037 100644 --- a/src/i18.ts +++ b/src/i18.ts @@ -111,6 +111,10 @@ export interface Translation { }; onChange?: { activated?: string; + button?: { + text?: string; + tooltip?: string; + }; failed?: string; text?: string; waitingBeforeActivate?: string; @@ -199,12 +203,20 @@ export interface Translation { }; }; onChange?: { + button?: { + text?: string; + tooltip?: string; + }; activated?: string; failed?: string; text?: string; waitingBeforeActivate?: string; }; onSave?: { + button?: { + text?: string; + tooltip?: string; + }; failed?: string; text?: string; }; diff --git a/src/lang/de.ts b/src/lang/de.ts index 62053f6..e86662e 100644 --- a/src/lang/de.ts +++ b/src/lang/de.ts @@ -106,6 +106,10 @@ export const translation: Translation = { }, onChange: { activated: "Automatisches Löschen wurde für den Arbeitsbereich{0:trim,surround,leading_space} aktiviert.", + button: { + text: "Automatisches Löschen", + tooltip: "Hier klicken, um 'Automatisches Löschen' zu aktivieren bzw. zu deaktivieren ..." + }, failed: "Das automatische Löschen von{0:trim,surround,leading_space} in{1:trim,surround,leading_space} ist fehlgeschlagen:{2:trim,surround,leading_space}", text: "Automatisches Löschen", waitingBeforeActivate: "Automatisches Löschen wird für ca.{0:trim,leading_space} Sekunden für den Arbeitsbereich{1:trim,surround,leading_space} deaktiviert.", @@ -195,11 +199,19 @@ export const translation: Translation = { }, onChange: { activated: "Das Bereitstellen nach dem Ändern wurde für den Arbeitsbereich{0:trim,surround,leading_space} aktiviert.", + button: { + text: "Automatisches Bereitstellen nach dem Ändern", + tooltip: "Hier klicken, um 'Automatisches Bereitstellen nach dem Ändern' zu aktivieren bzw. zu deaktivieren ..." + }, failed: "Das Bereitstellen nach dem Ändern von{0:trim,surround,leading_space} nach{1:trim,surround,leading_space} ist fehlgeschlagen:{2:trim,surround,leading_space}", text: "Bereitstellen beim Ändern", waitingBeforeActivate: "Das Bereitstellen nach dem Ändern wird für ca.{0:trim,leading_space} Sekunden für den Arbeitsbereich{1:trim,surround,leading_space} deaktiviert.", }, onSave: { + button: { + text: "Automatisches Bereitstellen nach dem Speichern", + tooltip: "Hier klicken, um 'Automatisches Bereitstellen nach dem Speichern' zu aktivieren bzw. zu deaktivieren ..." + }, failed: "Das Bereitstellen nach dem Speichern von{0:trim,surround,leading_space} nach{1:trim,surround,leading_space} ist fehlgeschlagen:{2:trim,surround,leading_space}", text: "Bereitstellen nach dem Speichern", }, diff --git a/src/lang/en.ts b/src/lang/en.ts index 361521e..e71ab60 100644 --- a/src/lang/en.ts +++ b/src/lang/en.ts @@ -106,6 +106,10 @@ export const translation: Translation = { }, onChange: { activated: "Remove on change has been activated for workspace{0:trim,surround,leading_space}.", + button: { + text: "Remove on change", + tooltip: "Click here to activate or deactivate 'remove on change' ..." + }, failed: "Remove on change for{0:trim,surround,leading_space} in{1:trim,surround,leading_space} failed:{2:trim,surround,leading_space}", text: "Remove on change", waitingBeforeActivate: "Remove on change is deactivated for about{0:trim,leading_space} seconds for workspace{1:trim,surround,leading_space}.", @@ -195,11 +199,19 @@ export const translation: Translation = { }, onChange: { activated: "Deploy on change has been activated for workspace{0:trim,surround,leading_space}.", + button: { + text: "Deploy on change", + tooltip: "Click here to activate or deactivate 'deploy on change' ..." + }, failed: "Deploy on change from{0:trim,surround,leading_space} to{1:trim,surround,leading_space} failed:{2:trim,surround,leading_space}", text: "Deploy on change", waitingBeforeActivate: "Deploy on change is deactivated for about{0:trim,leading_space} seconds for workspace{1:trim,surround,leading_space}.", }, onSave: { + button: { + text: "Deploy on save", + tooltip: "Click here to activate or deactivate 'deploy on save' ..." + }, failed: "Deploy on save from{0:trim,surround,leading_space} to{1:trim,surround,leading_space} failed:{2:trim,surround,leading_space}", text: "Deploy on save", }, diff --git a/src/packages.ts b/src/packages.ts index 09dd8e3..f1a6846 100644 --- a/src/packages.ts +++ b/src/packages.ts @@ -34,6 +34,32 @@ import * as UUID from 'uuid'; import * as vscode from 'vscode'; +/** + * Options for 'autoDeployFile()' function. + */ +export interface AutoDeployFileOptions { + /** + * @param {string} errorMsgTemplate The template for an error message. + */ + readonly errorMsgTemplate: string; + /** + * A custom "fast file check" resolver. + */ + readonly fastFileCheckFlagResolver: PackageFastFileCheckFlagResolver; + /** + * The file to check. + */ + readonly file: string; + /** + * Prepares something for a package. + */ + readonly prepareForPackage?: PrepareForPackage; + /** + * The settings resolver. + */ + readonly settingsResolver: PackageDeploySettingsResolver; +} + /** * A package. */ @@ -72,6 +98,10 @@ export interface Package extends deploy_values.Applyable, * Settings for importing files via git. */ readonly git?: PackageDeploySettingValue; + /** + * The number of milliseconds to wait before an auto operation can be done for a file again. + */ + readonly pauseFilesFor?: number; /** * Deletes a file of this package, if it has been deleted from a workspace. */ @@ -162,6 +192,13 @@ export interface PackageGitSettings extends deploy_contracts.FileFilter { */ export type PackageDeploySettingValue = string | PackageGitSettings; +/** + * A function that should be invoked to prepare something for a package. + * + * @param {Package} The underlying package. + */ +export type PrepareForPackage = (pkg: Package) => void | PromiseLike; + /** * Stores settings for 'sync when open' feature. */ @@ -191,43 +228,42 @@ export interface WithFastFileCheckSettings { } const AUTO_DEPLOY_STATES: deploy_contracts.KeyValuePairs = {}; +/** + * The name of a key for 'pauseFilesFor' data. + */ +export const KEY_PAUSE_FILES_FOR = 'pauseFilesFor'; const KEY_PACKAGE_USAGE = 'vscdrLastExecutedPackageActions'; /** * Handles an "auto deploy" of a file. * - * @param {string} file The file to check. - * @param {PackageDeploySettingsResolver} settingsResolver The settings resolver. - * @param {PackageFastFileCheckFlagResolver} fastFileCheckFlagResolver A custom "fast file check" resolver. - * @param {string} errorMsgTemplate The template for an error message. + * @param {AutoDeployFileOptions} opts Options. */ -export async function autoDeployFile(file: string, - settingsResolver: PackageDeploySettingsResolver, - fastFileCheckFlagResolver: PackageFastFileCheckFlagResolver, - errorMsgTemplate: string) { - const ME: deploy_workspaces.Workspace = this; +export async function autoDeployFile(opts: AutoDeployFileOptions) { + const WORKSPACE: deploy_workspaces.Workspace = this; try { const TARGETS = await deploy_helpers.applyFuncFor( - findTargetsForFileOfPackage, ME - )(file, - settingsResolver, - fastFileCheckFlagResolver); + findTargetsForFileOfPackage, WORKSPACE + )(opts.file, + opts.settingsResolver, + opts.fastFileCheckFlagResolver, + opts.prepareForPackage); if (false === TARGETS) { return; } - for (const T of Enumerable.from(TARGETS).distinct(true)) { + for (const T of deploy_helpers.from(TARGETS).distinct(true)) { await invokeForAutoTargetOperation(T, async (target) => { const TARGET_NAME = deploy_targets.getTargetName(target); try { - await ME.deployFileTo(file, target); + await WORKSPACE.deployFileTo(opts.file, target); } catch (e) { - ME.showErrorMessage( - ME.t(errorMsgTemplate, - file, TARGET_NAME, e) + WORKSPACE.showErrorMessage( + WORKSPACE.t(opts.errorMsgTemplate, + opts.file, TARGET_NAME, e) ); } }); @@ -245,6 +281,7 @@ export async function autoDeployFile(file: string, * @param {string} file The path to the file. * @param {PackageDeploySettingsResolver} settingsResolver The resolver for the settings. * @param {PackageFastFileCheckFlagResolver} fastFileCheckFlagResolver A custom "fast file check" resolver. + * @param {PrepareForPackage} [prepareForPackage] A function that prepares something for a package. * * @return {Promise|false} The List of targets or (false) if at least one target name could not be resolved. */ @@ -252,6 +289,7 @@ export async function findTargetsForFileOfPackage( file: string, settingsResolver: PackageDeploySettingsResolver, fastFileCheckFlagResolver: PackageFastFileCheckFlagResolver, + prepareForPackage?: PrepareForPackage, ): Promise { const ME: deploy_workspaces.Workspace = this; @@ -266,11 +304,17 @@ export async function findTargetsForFileOfPackage( const KNOWN_TARGETS = ME.getTargets(); const TARGETS: deploy_targets.Target[] = []; - for (let pkg of ME.getPackages()) { + for (const PKG of ME.getPackages()) { + if (prepareForPackage) { + await Promise.resolve( + prepareForPackage(PKG) + ); + } + let settings: PackageDeploySettings; if (settingsResolver) { settings = await Promise.resolve( - settingsResolver(pkg) + settingsResolver(PKG) ); } @@ -287,17 +331,17 @@ export async function findTargetsForFileOfPackage( targetNames = deploy_helpers.asArray(settings.targets); if (targetNames.length < 1) { // nothing defined => take from package - targetNames = deploy_helpers.asArray(pkg.targets); + targetNames = deploy_helpers.asArray(PKG.targets); } } else if (deploy_helpers.isBool(settings)) { if (true === settings) { - filter = pkg; - targetNames = pkg.targets; + filter = PKG; + targetNames = PKG.targets; } } else { - filter = pkg; + filter = PKG; targetNames = settings; } @@ -322,7 +366,7 @@ export async function findTargetsForFileOfPackage( const FAST_FILE_CHECK = deploy_helpers.toBooleanSafe( await Promise.resolve( - fastFileCheckFlagResolver(pkg) + fastFileCheckFlagResolver(PKG) ) ); @@ -344,8 +388,8 @@ export async function findTargetsForFileOfPackage( } else { fileListResolver = async () => { - let fileList = await ME.findFilesByFilter(pkg); - if (filter !== pkg) { + let fileList = await ME.findFilesByFilter(PKG); + if (filter !== PKG) { fileList = fileList.filter(f => { const REL_PATH = ME.toRelativePath(f); if (false !== REL_PATH) { @@ -468,6 +512,35 @@ export function getPackageName(pkg: Package): string { return name; } +/** + * Returns the (next) 'pauseFilesFor' value by a package. + * + * @param {Package} pkg The current package. + * @param {number} currentValue The current value. + * + * @return {number} The new value. + */ +export function getPauseFilesForValue(pkg: Package, currentValue: number): number { + if (pkg) { + const NEW_VALUE = parseInt( + deploy_helpers.toStringSafe( pkg.pauseFilesFor ).trim() + ); + + if (!isNaN(NEW_VALUE) && NEW_VALUE > 0) { + if (isNaN(currentValue)) { + currentValue = NEW_VALUE; // not set yet + } + else { + if (NEW_VALUE > currentValue) { + currentValue = NEW_VALUE; // only if greater + } + } + } + } + + return currentValue; +} + /** * Returns the targets of a package. * @@ -957,34 +1030,74 @@ export function preparePackageForFileFilter( * @param {string} file The file to check. */ export async function removeOnChange(file: string) { - const ME: deploy_workspaces.Workspace = this; + const WORKSPACE: deploy_workspaces.Workspace = this; - if (ME.isInFinalizeState) { + if (WORKSPACE.isInFinalizeState) { return; } + const CFG = WORKSPACE.config; + if (!CFG) { + return; + } + + const STATE = WORKSPACE.workspaceSessionState; + if (!STATE) { + return; + } + + const KEY = Path.resolve(file); + + const FILES_IN_PROGRESS = STATE['auto']['remove']['on_change']; + + if (true === FILES_IN_PROGRESS[KEY]) { + return; + } + + FILES_IN_PROGRESS[KEY] = true; + const RESTORE_IN_PROGRESS_STATE = () => { + delete FILES_IN_PROGRESS[KEY]; + }; + + let pauseFilesFor: number; + const FINISHED = async () => { + deploy_helpers.tryDisposeAndDelete(STATE['auto'], KEY_PAUSE_FILES_FOR); + + if (isNaN(pauseFilesFor)) { + RESTORE_IN_PROGRESS_STATE(); + } + else { + STATE['auto'][ KEY_PAUSE_FILES_FOR ] = deploy_helpers.createTimeout(() => { + RESTORE_IN_PROGRESS_STATE(); + }, pauseFilesFor); + } + }; + try { const TARGETS = await deploy_helpers.applyFuncFor( - findTargetsForFileOfPackage, ME + findTargetsForFileOfPackage, WORKSPACE )(file, (pkg) => pkg.removeOnChange, - (pkg) => true); + (pkg) => true, + (pkg) => { + pauseFilesFor = getPauseFilesForValue(pkg, pauseFilesFor); + }); if (false === TARGETS) { return; } - for (const T of Enumerable.from(TARGETS).distinct(true)) { + for (const T of deploy_helpers.from(TARGETS).distinct(true)) { await invokeForAutoTargetOperation(T, async (target) => { const TARGET_NAME = deploy_targets.getTargetName(target); try { - await ME.deleteFileIn(file, target, false); + await WORKSPACE.deleteFileIn(file, target, false); } catch (e) { - ME.showErrorMessage( - ME.t('DELETE.onChange.failed', - file, TARGET_NAME, e) + WORKSPACE.showErrorMessage( + WORKSPACE.t('DELETE.onChange.failed', + file, TARGET_NAME, e) ); } }); @@ -994,6 +1107,9 @@ export async function removeOnChange(file: string) { deploy_log.CONSOLE .trace(e, 'delete.removeOnChange()'); } + finally { + await FINISHED(); + } } /** diff --git a/src/workspaces.ts b/src/workspaces.ts index c00bd31..ba1a46f 100644 --- a/src/workspaces.ts +++ b/src/workspaces.ts @@ -56,6 +56,12 @@ import * as Path from 'path'; import * as vscode from 'vscode'; +interface AutoDeployButton extends vscode.Disposable { + button: vscode.StatusBarItem; + command: vscode.Disposable; + settings: deploy_buttons.AutoDeployButton; +} + /** * Options for 'Workspace.deactivateAutoDeployOperationsFor()' method. */ @@ -297,8 +303,16 @@ export interface WorkspaceSettings extends deploy_contracts.Configuration { let activeWorkspaceProvider: WorkspaceProvider; let allWorkspacesProvider: WorkspaceProvider; const FILES_CHANGES: { [path: string]: deploy_contracts.FileChangeType } = {}; +const KEY_AUTO_BTN_DEPLOY_ON_CHANGE = 'deploy_on_change'; +const KEY_AUTO_BTN_DEPLOY_ON_SAVE = 'deploy_on_save'; +const KEY_AUTO_BTN_REMOVE_ON_CHANGE = 'remove_on_change'; +const KEY_TIMEOUT_DEPLOY_ON_CHANGE = 'deploy_on_change'; +const KEY_TIMEOUT_REMOVE_ON_CHANGE = 'remove_on_change'; const KEY_WORKSPACE_USAGE = 'vscdrLastExecutedWorkspaceActions'; +let nextDeployOnChangeButtonId = Number.MIN_SAFE_INTEGER; +let nextDeployOnSaveButtonId = Number.MIN_SAFE_INTEGER; let nextPackageButtonId = Number.MIN_SAFE_INTEGER; +let nextRemoveOnChangeButtonId = Number.MIN_SAFE_INTEGER; let nextTcpProxyButtonId = Number.MIN_SAFE_INTEGER; let nextSwitchButtonId = Number.MIN_SAFE_INTEGER; const SWITCH_STATE_REPO_COLLECTION_KEY = 'SwitchStates'; @@ -318,14 +332,8 @@ export class Workspace extends deploy_helpers.WorkspaceBase implements deploy_co */ protected _configSource: WorkspaceConfigSource; private _gitFolder: string | false; - /** - * Stores if 'deploy on change' feature is freezed or not. - */ - protected _isDeployOnChangeFreezed = false; - /** - * Stores if 'deploy on save' feature is freezed or not. - */ - protected _isDeployOnSaveFreezed = false; + private _isDeployOnChangeFreezed = false; + private _isDeployOnSaveFreezed = false; /** * Stores if workspace has been initialized or not. */ @@ -334,10 +342,7 @@ export class Workspace extends deploy_helpers.WorkspaceBase implements deploy_co * Stores if configuration is currently reloaded or not. */ protected _isReloadingConfig = false; - /** - * Stores if 'remove on change' feature is freezed or not. - */ - protected _isRemoveOnChangeFreezed = false; + private _isRemoveOnChangeFreezed = false; /** * Stores the last timestamp of configuration update. */ @@ -752,7 +757,15 @@ export class Workspace extends deploy_helpers.WorkspaceBase implements deploy_co private createWorkspaceSessionState(newCfg: WorkspaceSettings) { const NEW_SESSION_STATE: deploy_contracts.KeyValuePairs = {}; - + + NEW_SESSION_STATE['auto'] = {}; + NEW_SESSION_STATE['auto']['buttons'] = {}; + NEW_SESSION_STATE['auto']['deploy'] = {}; + NEW_SESSION_STATE['auto']['deploy']['on_change_or_save'] = {}; + NEW_SESSION_STATE['auto']['remove'] = {}; + NEW_SESSION_STATE['auto']['remove']['on_change'] = {}; + NEW_SESSION_STATE['auto']['timeouts'] = {}; + NEW_SESSION_STATE['buttons'] = {}; NEW_SESSION_STATE['commands'] = {}; @@ -816,20 +829,20 @@ export class Workspace extends deploy_helpers.WorkspaceBase implements deploy_co opts = {}; } - let oldIsDeployOnChangeFreezed = this._isDeployOnChangeFreezed; - let oldIsDeployOnSaveFreezed = this._isDeployOnSaveFreezed; - let oldIsRemoveOnChangeFreezed = this._isRemoveOnChangeFreezed; + let oldIsDeployOnChangeFreezed = this.isDeployOnChangeFreezed; + let oldIsDeployOnSaveFreezed = this.isDeployOnSaveFreezed; + let oldIsRemoveOnChangeFreezed = this.isRemoveOnChangeFreezed; try { if (deploy_helpers.toBooleanSafe(opts.noDeployOnChange, true)) { - this._isDeployOnChangeFreezed = true; + this.isDeployOnChangeFreezed = true; } if (deploy_helpers.toBooleanSafe(opts.noRemoveOnChange, true)) { - this._isRemoveOnChangeFreezed = true; + this.isRemoveOnChangeFreezed = true; } if (deploy_helpers.toBooleanSafe(opts.noDeployOnSave, true)) { - this._isDeployOnSaveFreezed = true; + this.isDeployOnSaveFreezed = true; } if (action) { @@ -839,9 +852,9 @@ export class Workspace extends deploy_helpers.WorkspaceBase implements deploy_co } } finally { - this._isDeployOnChangeFreezed = oldIsDeployOnChangeFreezed; - this._isDeployOnSaveFreezed = oldIsDeployOnSaveFreezed; - this._isRemoveOnChangeFreezed = oldIsRemoveOnChangeFreezed; + this.isDeployOnChangeFreezed = oldIsDeployOnChangeFreezed; + this.isDeployOnSaveFreezed = oldIsDeployOnSaveFreezed; + this.isRemoveOnChangeFreezed = oldIsRemoveOnChangeFreezed; } } @@ -992,6 +1005,22 @@ export class Workspace extends deploy_helpers.WorkspaceBase implements deploy_co .apply(this, [ GIT, target ]); } + private disposeAutoDeployButtons() { + const STATE = this.workspaceSessionState; + if (!STATE) { + return; + } + + const TIMEOUTS = STATE['auto']['timeouts']; + deploy_helpers.tryDisposeAndDelete(TIMEOUTS, KEY_TIMEOUT_DEPLOY_ON_CHANGE); + deploy_helpers.tryDisposeAndDelete(TIMEOUTS, KEY_TIMEOUT_REMOVE_ON_CHANGE); + + const BUTTONS = STATE['auto']['buttons']; + deploy_helpers.tryDisposeAndDelete(BUTTONS, KEY_AUTO_BTN_DEPLOY_ON_CHANGE); + deploy_helpers.tryDisposeAndDelete(BUTTONS, KEY_AUTO_BTN_DEPLOY_ON_SAVE); + deploy_helpers.tryDisposeAndDelete(BUTTONS, KEY_AUTO_BTN_REMOVE_ON_CHANGE); + } + private disposeConfigFileWatchers() { while (this._CONFIG_FILE_WATCHERS.length > 0) { deploy_helpers.tryDispose( @@ -2415,18 +2444,28 @@ export class Workspace extends deploy_helpers.WorkspaceBase implements deploy_co } /** - * Gets if 'deploy on change' is currently freezed or not. + * Gets or sets if 'deploy on change' is currently freezed or not. */ public get isDeployOnChangeFreezed() { return this._isDeployOnChangeFreezed; } + public set isDeployOnChangeFreezed(newValue: boolean) { + this._isDeployOnChangeFreezed = deploy_helpers.toBooleanSafe(newValue); + + this.updateAutoDeployButtons(); + } /** - * Gets if 'deploy on change' is currently freezed or not. + * Gets or sets if 'deploy on change' is currently freezed or not. */ public get isDeployOnSaveFreezed() { return this._isDeployOnSaveFreezed; } + public set isDeployOnSaveFreezed(newValue: boolean) { + this._isDeployOnSaveFreezed = deploy_helpers.toBooleanSafe(newValue); + + this.updateAutoDeployButtons(); + } /** * Checks if a file is ignored by that workspace. @@ -2571,11 +2610,16 @@ export class Workspace extends deploy_helpers.WorkspaceBase implements deploy_co } /** - * Gets if 'remove on change' is currently freezed or not. + * Gets or sets if 'remove on change' is currently freezed or not. */ public get isRemoveOnChangeFreezed() { return this._isRemoveOnChangeFreezed; } + public set isRemoveOnChangeFreezed(newValue: boolean) { + this._isRemoveOnChangeFreezed = deploy_helpers.toBooleanSafe(newValue); + + this.updateAutoDeployButtons(); + } /** * Gets the last config update timestamp. @@ -2898,6 +2942,7 @@ export class Workspace extends deploy_helpers.WorkspaceBase implements deploy_co deploy_helpers.applyFuncFor( deploy_buttons.disposeFinishedButtons, this )(); + this.disposeAutoDeployButtons(); // output channel deploy_helpers.tryDispose(this._OUTPUT_CHANNEL); @@ -3055,6 +3100,137 @@ export class Workspace extends deploy_helpers.WorkspaceBase implements deploy_co )(pkg, targetResolver); } + private async reloadAutoDeployButtons() { + const ME = this; + + if (ME.isInFinalizeState) { + return; + } + + const CFG = ME.config; + if (!CFG) { + return; + } + + const STATE = ME.workspaceSessionState; + if (!STATE) { + return; + } + + const BUTTONS = STATE['auto']['buttons']; + + const CREATE_BTN = async ( + settings: deploy_buttons.AutoDeployButton | boolean, + key: string, + idResolver: () => number, + action: (btn: vscode.StatusBarItem) => any, + ) => { + if (_.isNil(settings)) { + return; + } + + if (!deploy_helpers.isObject(settings)) { + settings = { + enabled: settings, + }; + } + + if (!deploy_helpers.toBooleanSafe(settings.enabled, true)) { + return; + } + + let btn: vscode.StatusBarItem; + let cmd: vscode.Disposable; + try { + const ID = idResolver(); + + btn = await deploy_helpers.createButton(settings); + + const CMD_ID = `extension.deploy.reloaded.buttons.autoDeploy.${key}${ID}`; + cmd = vscode.commands.registerCommand(CMD_ID, async () => { + btn.command = undefined; + try { + await Promise.resolve( + action(btn) + ); + } + catch (e) { + deploy_log.CONSOLE + .trace(e, CMD_ID); + } + finally { + ME.updateAutoDeployButtons(); + + btn.command = CMD_ID; + } + }); + + const NEW_BTN: AutoDeployButton = { + button: btn, + command: cmd, + dispose: function() { + deploy_helpers.tryDispose( this.button ); + deploy_helpers.tryDispose( this.command ); + }, + settings: settings, + }; + BUTTONS[key] = NEW_BTN; + + btn.command = CMD_ID; + btn.show(); + } + catch (e) { + deploy_helpers.tryDispose( btn ); + deploy_helpers.tryDispose( cmd ); + + deploy_log.CONSOLE + .trace(e, 'workspaces.Workspace.reloadAutoDeployButtons(2)'); + } + }; + + try { + // deploy on change + await CREATE_BTN( + CFG.deployOnChangeButton, + KEY_AUTO_BTN_DEPLOY_ON_CHANGE, + () => nextDeployOnChangeButtonId++, + () => { + deploy_helpers.tryDisposeAndDelete( STATE['auto']['timeouts'], KEY_TIMEOUT_DEPLOY_ON_CHANGE ); + + ME.isDeployOnChangeFreezed = !ME.isDeployOnChangeFreezed; + } + ); + + // deploy on save + await CREATE_BTN( + CFG.deployOnSaveButton, + KEY_AUTO_BTN_DEPLOY_ON_SAVE, + () => nextDeployOnSaveButtonId++, + () => { + ME.isDeployOnSaveFreezed = !ME.isDeployOnSaveFreezed; + } + ); + + // remove on change + await CREATE_BTN( + CFG.removeOnChangeButton, + KEY_AUTO_BTN_REMOVE_ON_CHANGE, + () => nextRemoveOnChangeButtonId++, + () => { + deploy_helpers.tryDisposeAndDelete( STATE['auto']['timeouts'], KEY_TIMEOUT_REMOVE_ON_CHANGE ); + + ME.isRemoveOnChangeFreezed = !ME.isRemoveOnChangeFreezed; + } + ); + + ME.updateAutoDeployButtons(); + } + catch (e) { + deploy_log.CONSOLE + .trace(e, 'workspaces.Workspace.reloadAutoDeployButtons(1)'); + } + } + /** * Reloads the current configuration for that workspace. * @@ -3079,10 +3255,11 @@ export class Workspace extends deploy_helpers.WorkspaceBase implements deploy_co ME._isReloadingConfig = true; this.disposeConfigFileWatchers(); - // dispose global buttons + // dispose buttons deploy_helpers.applyFuncFor( deploy_buttons.disposeButtons, ME )(); + this.disposeAutoDeployButtons(); const SCOPES = ME.getSettingScopes(); @@ -3091,9 +3268,9 @@ export class Workspace extends deploy_helpers.WorkspaceBase implements deploy_co ME.cleanupTimeouts(); deploy_helpers.applyFuncFor(deploy_commands.cleanupCommands, ME)(); - ME._isDeployOnChangeFreezed = false; - ME._isDeployOnSaveFreezed = false; - ME._isRemoveOnChangeFreezed = false; + ME.isDeployOnChangeFreezed = false; + ME.isDeployOnSaveFreezed = false; + ME.isRemoveOnChangeFreezed = false; const IMPORTED_LOCAL_FILES: string[] = []; let loadedCfg: WorkspaceSettings = vscode.workspace.getConfiguration(ME.configSource.section, @@ -3208,9 +3385,11 @@ export class Workspace extends deploy_helpers.WorkspaceBase implements deploy_co await ME.reloadTargets(loadedCfg); await ME.reloadPackages(loadedCfg); + const NEW_STATE = ME.createWorkspaceSessionState(loadedCfg); + const OLD_CFG = ME._config; ME._config = loadedCfg; - ME._workspaceSessionState = ME.createWorkspaceSessionState(loadedCfg); + ME._workspaceSessionState = NEW_STATE; ME._lastConfigUpdate = Moment(); try { @@ -3263,7 +3442,7 @@ export class Workspace extends deploy_helpers.WorkspaceBase implements deploy_co // deactivate 'deploy on change' // for a while - ME._isDeployOnChangeFreezed = true; + ME.isDeployOnChangeFreezed = true; ME.output.appendLine(''); ME.output.appendLine( @@ -3273,25 +3452,23 @@ export class Workspace extends deploy_helpers.WorkspaceBase implements deploy_co ME.rootPath) ); - ME._TIMEOUTS.push( - setTimeout(() => { - ME._isDeployOnChangeFreezed = false; + NEW_STATE['auto']['timeouts'][ KEY_TIMEOUT_DEPLOY_ON_CHANGE ] = deploy_helpers.createTimeout(() => { + ME.isDeployOnChangeFreezed = false; - ME.output.appendLine(''); - ME.output.appendLine( - `▶️ `+ - ME.t('deploy.onChange.activated', - ME.rootPath) - ); - }, TIME_TO_WAIT_BEFORE_ACTIVATE_DEPLOY_ON_CHANGE) - ); + ME.output.appendLine(''); + ME.output.appendLine( + `▶️ `+ + ME.t('deploy.onChange.activated', + ME.rootPath) + ); + }, TIME_TO_WAIT_BEFORE_ACTIVATE_DEPLOY_ON_CHANGE); } } catch (e) { ME.logger .trace(e, 'workspaces.reloadConfiguration(5)'); - ME._isDeployOnChangeFreezed = false; + ME.isDeployOnChangeFreezed = false; } // timeToWaitBeforeActivateRemoveOnChange @@ -3303,7 +3480,7 @@ export class Workspace extends deploy_helpers.WorkspaceBase implements deploy_co // deactivate 'remove on change' // for a while - ME._isRemoveOnChangeFreezed = true; + ME.isRemoveOnChangeFreezed = true; ME.output.appendLine(''); ME.output.appendLine( @@ -3313,25 +3490,23 @@ export class Workspace extends deploy_helpers.WorkspaceBase implements deploy_co ME.rootPath) ); - ME._TIMEOUTS.push( - setTimeout(() => { - ME._isRemoveOnChangeFreezed = false; + NEW_STATE['auto']['timeouts'][ KEY_TIMEOUT_REMOVE_ON_CHANGE ] = deploy_helpers.createTimeout(() => { + ME.isRemoveOnChangeFreezed = false; - ME.output.appendLine(''); - ME.output.appendLine( - `▶️ `+ - ME.t('DELETE.onChange.activated', - ME.rootPath) - ); - }, TIME_TO_WAIT_BEFORE_ACTIVATE_REMOVE_ON_CHANGE) - ); + ME.output.appendLine(''); + ME.output.appendLine( + `▶️ `+ + ME.t('DELETE.onChange.activated', + ME.rootPath) + ); + }, TIME_TO_WAIT_BEFORE_ACTIVATE_REMOVE_ON_CHANGE); } } catch (e) { ME.logger .trace(e, 'workspaces.reloadConfiguration(6)'); - ME._isRemoveOnChangeFreezed = false; + ME.isRemoveOnChangeFreezed = false; } await ME.reloadPackageButtons(); @@ -3349,10 +3524,11 @@ export class Workspace extends deploy_helpers.WorkspaceBase implements deploy_co await ME.reloadTcpProxies(); - // global buttons + // buttons await deploy_helpers.applyFuncFor( deploy_buttons.reloadButtons, ME, )(); + await ME.reloadAutoDeployButtons(); await ME.initConfigFileWatchers(IMPORTED_LOCAL_FILES); }; @@ -4593,6 +4769,76 @@ export class Workspace extends deploy_helpers.WorkspaceBase implements deploy_co return relativePath; } + private updateAutoDeployButtons() { + const ME = this; + + if (ME.isInFinalizeState) { + return; + } + + const STATE = ME.workspaceSessionState; + if (!STATE) { + return; + } + + const BUTTONS = STATE['auto']['buttons']; + if (!BUTTONS) { + return; + } + + const UPDATE_BTN = ( + btn: AutoDeployButton, + isFreezed: () => boolean, + lang: string, + ) => { + if (!btn) { + return; + } + + try { + const IS_FREEZED = isFreezed(); + + const ICON = IS_FREEZED ? `💤` : `▶️`; + + const LANG_KEY_TEXT = `${lang}.button.text`; + const LANG_KEY_TOOLTIP = `${lang}.button.tooltip`; + + let color: string | vscode.ThemeColor = deploy_helpers.normalizeString(btn.settings.color); + if ('' === color) { + color = new vscode.ThemeColor("button.foreground"); + } + + let text = ME.replaceWithValues(btn.settings.text); + if (deploy_helpers.isEmptyString(text)) { + text = ME.t(LANG_KEY_TEXT); + } + + btn.button.text = `${ICON} ${text}`; + + btn.button.tooltip = ME.replaceWithValues(btn.settings.tooltip); + if (deploy_helpers.isEmptyString(btn.button.tooltip)) { + btn.button.tooltip = ME.t(LANG_KEY_TOOLTIP); + } + + btn.button.color = color; + } + catch (e) { + deploy_log.CONSOLE + .trace(e, 'workspaces.Workspace.updateAutoDeployButtons(2)'); + } + }; + + UPDATE_BTN(BUTTONS[ KEY_AUTO_BTN_DEPLOY_ON_CHANGE ], + () => ME.isDeployOnChangeFreezed, + 'deploy.onChange'); + UPDATE_BTN(BUTTONS[ KEY_AUTO_BTN_DEPLOY_ON_SAVE ], + () => ME.isDeployOnSaveFreezed, + 'deploy.onSave'); + UPDATE_BTN(BUTTONS[ KEY_AUTO_BTN_REMOVE_ON_CHANGE ], + () => ME.isRemoveOnChangeFreezed, + 'DELETE.onChange'); + } + private async updateSwitchButtons() { const ME = this;