diff --git a/src/enums.ts b/src/enums.ts index 6901b632..c283304e 100644 --- a/src/enums.ts +++ b/src/enums.ts @@ -1,7 +1,7 @@ export enum OsType { - Windows = 1, - Linux, - Mac + Windows = "win32", + Linux = "linux", + Mac = "darwin" } export enum SettingType { diff --git a/src/environmentPath.ts b/src/environmentPath.ts index 54fc8951..ca65f81d 100644 --- a/src/environmentPath.ts +++ b/src/environmentPath.ts @@ -1,8 +1,6 @@ "use strict"; -import * as fs from "fs-extra"; -import * as os from "os"; -import * as path from "path"; +import { normalize, resolve } from "path"; import * as vscode from "vscode"; import { OsType } from "./enums"; @@ -28,14 +26,17 @@ export class Environment { ); } - public isInsiders: boolean = false; - public isOss: boolean = false; + // public isInsiders: boolean = false; + // public isOss: boolean = false; + // public isCoderCom: boolean = false; + // public homeDir: string | null = null; + public isPortable: boolean = false; - public isCoderCom: boolean = false; - public homeDir: string | null = null; public USER_FOLDER: string = null; - public ExtensionFolder: string = null; + public CODE_BIN: string; + + public EXTENSION_FOLDER: string = null; public PATH: string = null; public OsType: OsType = null; @@ -64,8 +65,37 @@ export class Environment { public FOLDER_SNIPPETS: string = null; constructor(private context: vscode.ExtensionContext) { + this.context.globalState.update("_", undefined); // Make sure the global state folder exists. This is needed for using this.context.globalStoragePath to access user folder + + this.isPortable = !!process.env.VSCODE_PORTABLE; + + this.OsType = process.platform as OsType; + if (!this.isPortable) { + this.PATH = resolve(this.context.globalStoragePath, "../../..").concat( + normalize("/") + ); + this.USER_FOLDER = resolve(this.PATH, "User").concat(normalize("/")); + this.EXTENSION_FOLDER = resolve( + vscode.extensions.all.filter( + extension => !extension.packageJSON.isBuiltin + )[0].extensionPath, + ".." + ).concat(normalize("/")); // Gets first non-builtin extension's path + } + + if (this.isPortable) { + this.PATH = process.env.VSCODE_PORTABLE; + this.USER_FOLDER = resolve(this.PATH, "user-data/User").concat( + normalize("/") + ); + this.EXTENSION_FOLDER = resolve(this.PATH, "extensions").concat( + normalize("/") + ); + } + + /* Start Legacy Code + this.isInsiders = /insiders/.test(this.context.asAbsolutePath("")); - this.isPortable = process.env.VSCODE_PORTABLE ? true : false; this.isOss = /\boss\b/.test(this.context.asAbsolutePath("")); this.isCoderCom = vscode.extensions.getExtension("coder.coder") !== undefined; @@ -77,8 +107,8 @@ export class Environment { this.homeDir = isXdg ? process.env.XDG_DATA_HOME : process.env[process.platform === "win32" ? "USERPROFILE" : "HOME"]; - const configSuffix = `${isXdg || this.isCoderCom ? "" : "."}vscode${ - this.isInsiders ? "-insiders" : this.isOss ? "-oss" : "" + const configSuffix = `; $; {isXdg || this.isCoderCom ? "" : "."; }vscode$; { + this.isInsiders ? "-insiders" : this.isOss ? "-oss" : ""; }`; if (!this.isPortable) { @@ -148,6 +178,8 @@ export class Environment { this.ExtensionFolder = this.PATH.concat("/extensions/"); } + End Legacy Code */ + this.FILE_EXTENSION = this.USER_FOLDER.concat(this.FILE_EXTENSION_NAME); this.FILE_SETTING = this.USER_FOLDER.concat(this.FILE_SETTING_NAME); this.FILE_LAUNCH = this.USER_FOLDER.concat(this.FILE_LAUNCH_NAME); diff --git a/src/service/pluginService.ts b/src/service/pluginService.ts index 09ade6fc..18e1dd56 100644 --- a/src/service/pluginService.ts +++ b/src/service/pluginService.ts @@ -1,29 +1,29 @@ "use strict"; -import * as fs from "fs-extra"; -import * as path from "path"; import * as vscode from "vscode"; -import { OsType } from "../enums"; - export class ExtensionInformation { public static fromJSON(text: string) { - // TODO: JSON.parse may throw error - // Throw custom error should be more friendly - const obj = JSON.parse(text); - const meta = new ExtensionMetadata( - obj.meta.galleryApiUrl, - obj.meta.id, - obj.meta.downloadUrl, - obj.meta.publisherId, - obj.meta.publisherDisplayName, - obj.meta.date - ); - const item = new ExtensionInformation(); - item.metadata = meta; - item.name = obj.name; - item.publisher = obj.publisher; - item.version = obj.version; - return item; + try { + // TODO: JSON.parse may throw error + // Throw custom error should be more friendly + const obj = JSON.parse(text); + const meta = new ExtensionMetadata( + obj.meta.galleryApiUrl, + obj.meta.id, + obj.meta.downloadUrl, + obj.meta.publisherId, + obj.meta.publisherDisplayName, + obj.meta.date + ); + const item = new ExtensionInformation(); + item.metadata = meta; + item.name = obj.name; + item.publisher = obj.publisher; + item.version = obj.version; + return item; + } catch (err) { + throw new Error(err); + } } public static fromJSONList(text: string) { @@ -52,7 +52,7 @@ export class ExtensionInformation { } }); } catch (err) { - console.error("Sync : Unable to Parse extensions list", err); + throw new Error(err); } return extList; @@ -80,34 +80,19 @@ export class PluginService { remoteExt: string, ignoredExtensions: string[] ) { - const hashset = {}; const remoteList = ExtensionInformation.fromJSONList(remoteExt); const localList = this.CreateExtensionList(); - const missingList: ExtensionInformation[] = []; - for (const ext of localList) { - if (hashset[ext.name] == null) { - hashset[ext.name] = ext; - } - } - - for (const ext of remoteList) { - if ( - hashset[ext.name] == null && - ignoredExtensions.includes(ext.name) === false - ) { - missingList.push(ext); - } - } - return missingList; + return remoteList.filter( + ext => !ignoredExtensions.includes(ext.name) && !localList.includes(ext) + ); } public static GetDeletedExtensions( - remoteList: ExtensionInformation[], + remoteExtensions: ExtensionInformation[], ignoredExtensions: string[] ) { - const localList = this.CreateExtensionList(); - const deletedList: ExtensionInformation[] = []; + const localExtensions = this.CreateExtensionList(); // for (var i = 0; i < remoteList.length; i++) { @@ -127,210 +112,135 @@ export class PluginService { // } - for (const ext of localList) { - let found: boolean = false; - if (ext.name !== "code-settings-sync") { - for (const localExt of remoteList) { - if ( - ext.name === localExt.name || - ignoredExtensions.includes(ext.name) - ) { - found = true; - break; - } - } - if (!found) { - deletedList.push(ext); - } - } - } - return deletedList; + return localExtensions.filter( + ext => + ext.name !== "code-settings-sync" && + !remoteExtensions.map(e => e.name).includes(ext.name) && + !ignoredExtensions.includes(ext.name) + ); } public static CreateExtensionList() { - const list: ExtensionInformation[] = []; - - for (const ext of vscode.extensions.all) { - if (ext.packageJSON.isBuiltin === true) { - continue; - } - - const meta = ext.packageJSON.__metadata || { - id: ext.packageJSON.uuid, - publisherId: ext.id, - publisherDisplayName: ext.packageJSON.publisher - }; - const data = new ExtensionMetadata( - meta.galleryApiUrl, - meta.id, - meta.downloadUrl, - meta.publisherId, - meta.publisherDisplayName, - meta.date - ); - const info = new ExtensionInformation(); - info.metadata = data; - info.name = ext.packageJSON.name; - info.publisher = ext.packageJSON.publisher; - info.version = ext.packageJSON.version; - list.push(info); - } - - return list; + return vscode.extensions.all + .filter(ext => !ext.packageJSON.isBuiltin) + .map(ext => { + const meta = ext.packageJSON.__metadata || { + id: ext.packageJSON.uuid, + publisherId: ext.id, + publisherDisplayName: ext.packageJSON.publisher + }; + const data = new ExtensionMetadata( + meta.galleryApiUrl, + meta.id, + meta.downloadUrl, + meta.publisherId, + meta.publisherDisplayName, + meta.date + ); + const info = new ExtensionInformation(); + info.metadata = data; + info.name = ext.packageJSON.name; + info.publisher = ext.packageJSON.publisher; + info.version = ext.packageJSON.version; + return info; + }); } public static async DeleteExtension( - item: ExtensionInformation, - ExtensionFolder: string + extension: ExtensionInformation ): Promise { - const destination = path.join( - ExtensionFolder, - item.publisher + "." + item.name + "-" + item.version - ); - try { - await fs.remove(destination); + await vscode.commands.executeCommand( + "workbench.extensions.uninstallExtension", + `${extension.publisher}.${extension.name}` + ); return true; } catch (err) { - console.log("Sync : " + "Error in uninstalling Extension."); - console.log(err); - throw err; + throw new Error(err); } } public static async DeleteExtensions( extensionsJson: string, - extensionFolder: string, ignoredExtensions: string[] ): Promise { - const remoteList = ExtensionInformation.fromJSONList(extensionsJson); - const deletedList = PluginService.GetDeletedExtensions( - remoteList, + const remoteExtensions = ExtensionInformation.fromJSONList(extensionsJson); + const toDelete = PluginService.GetDeletedExtensions( + remoteExtensions, ignoredExtensions ); - const deletedExt: ExtensionInformation[] = []; - if (deletedList.length === 0) { - return deletedExt; - } - for (const selectedExtension of deletedList) { - try { - await PluginService.DeleteExtension(selectedExtension, extensionFolder); - deletedExt.push(selectedExtension); - } catch (err) { - console.error( - "Sync : Unable to delete extension " + - selectedExtension.name + - " " + - selectedExtension.version - ); - console.error(err); - throw deletedExt; - } - } - return deletedExt; + return Promise.all( + toDelete.map(async selectedExtension => { + try { + await PluginService.DeleteExtension(selectedExtension); + return selectedExtension; + } catch (err) { + throw new Error( + `Sync : Unable to delete extension ${selectedExtension.name} ${ + selectedExtension.version + }: ${err}` + ); + } + }) + ); } public static async InstallExtensions( extensions: string, ignoredExtensions: string[], - osType: OsType, - insiders: boolean, notificationCallBack: (...data: any[]) => void ): Promise { let addedExtensions: ExtensionInformation[] = []; - const missingList = PluginService.GetMissingExtensions( + const missingExtensions = PluginService.GetMissingExtensions( extensions, ignoredExtensions ); - if (missingList.length === 0) { + if (missingExtensions.length === 0) { notificationCallBack("Sync : No Extensions needs to be installed."); return []; } - addedExtensions = await PluginService.ProcessInstallationCLI( - missingList, - osType, - insiders, + addedExtensions = await PluginService.InstallWithAPI( + missingExtensions, notificationCallBack ); return addedExtensions; } - public static async ProcessInstallationCLI( - missingList: ExtensionInformation[], - osType: OsType, - isInsiders: boolean, + public static async InstallWithAPI( + missingExtensions: ExtensionInformation[], notificationCallBack: (...data: any[]) => void ): Promise { - const addedExtensions: ExtensionInformation[] = []; - const exec = require("child_process").exec; - notificationCallBack("TOTAL EXTENSIONS : " + missingList.length); + let remainingExtensions: ExtensionInformation[] = [...missingExtensions]; + + notificationCallBack("TOTAL EXTENSIONS : " + missingExtensions.length); notificationCallBack(""); notificationCallBack(""); - let myExt: string = process.argv0; - console.log(myExt); - let codeLastFolder = ""; - let codeCliPath = ""; - if (osType === OsType.Windows) { - if (isInsiders) { - codeLastFolder = "Code - Insiders"; - codeCliPath = "bin/code-insiders"; - } else { - codeLastFolder = "Code"; - codeCliPath = "bin/code"; - } - } else if (osType === OsType.Linux) { - if (isInsiders) { - codeLastFolder = "code-insiders"; - codeCliPath = "bin/code-insiders"; - } else { - codeLastFolder = "code"; - codeCliPath = "bin/code"; - } - } else if (osType === OsType.Mac) { - codeLastFolder = "Frameworks"; - codeCliPath = "Resources/app/bin/code"; - } - myExt = - '"' + - myExt.substr(0, myExt.lastIndexOf(codeLastFolder)) + - codeCliPath + - '"'; - for (let i = 0; i < missingList.length; i++) { - const missExt = missingList[i]; - const name = missExt.publisher + "." + missExt.name; - const extensionCli = myExt + " --install-extension " + name; - notificationCallBack(extensionCli); - try { - const installed = await new Promise(res => { - exec(extensionCli, (err, stdout, stderr) => { - if (!stdout && (err || stderr)) { - notificationCallBack(err || stderr); - res(false); - } - notificationCallBack(stdout); - res(true); - }); - }); - if (installed) { + return Promise.all( + missingExtensions.map(async ext => { + const name = ext.publisher + "." + ext.name; + try { + await vscode.commands.executeCommand( + "workbench.extensions.installExtension", + name + ); + remainingExtensions = remainingExtensions.filter( + remExt => remExt.name !== ext.name + ); notificationCallBack(""); notificationCallBack( - "EXTENSION : " + - (i + 1) + - " / " + - missingList.length.toString() + - " INSTALLED.", + `EXTENSION: ${ext.name} INSTALLED. ${ + remainingExtensions.length + } OF ${missingExtensions.length - + remainingExtensions.length} EXTENSIONS REMAINING`, true ); notificationCallBack(""); - notificationCallBack(""); - addedExtensions.push(missExt); + return ext; + } catch (err) { + throw new Error(err); } - } catch (err) { - console.log(err); - } - } - - return addedExtensions; + }) + ); } } diff --git a/src/setting.ts b/src/setting.ts index 88231e61..43b956f0 100644 --- a/src/setting.ts +++ b/src/setting.ts @@ -30,8 +30,7 @@ export class KeyValue { export class CustomSettings { public ignoreUploadFiles: string[] = [ - "state.vscdb", - "state.vscdb.backup", + "state.*", "syncLocalSettings.json", ".DS_Store", "sync.lock", diff --git a/src/sync.ts b/src/sync.ts index aef90aeb..122e81d0 100644 --- a/src/sync.ts +++ b/src/sync.ts @@ -533,7 +533,6 @@ export class Sync { try { deletedExtensions = await PluginService.DeleteExtensions( content, - env.ExtensionFolder, ignoredExtensions ); } catch (uncompletedExtensions) { @@ -545,30 +544,21 @@ export class Sync { } try { - let useCli = true; - const autoUpdate: boolean = vscode.workspace - .getConfiguration("extensions") - .get("autoUpdate"); - useCli = autoUpdate && !env.isCoderCom; - if (useCli) { - if (!syncSetting.quietSync) { - Commons.outputChannel = vscode.window.createOutputChannel( - "Code Settings Sync" - ); - Commons.outputChannel.clear(); - Commons.outputChannel.appendLine( - `COMMAND LINE EXTENSION DOWNLOAD SUMMARY` - ); - Commons.outputChannel.appendLine(`--------------------`); - Commons.outputChannel.show(); - } + if (!syncSetting.quietSync) { + Commons.outputChannel = vscode.window.createOutputChannel( + "Code Settings Sync" + ); + Commons.outputChannel.clear(); + Commons.outputChannel.appendLine( + `Realtime Extension Download Summary` + ); + Commons.outputChannel.appendLine(`--------------------`); + Commons.outputChannel.show(); } addedExtensions = await PluginService.InstallExtensions( content, ignoredExtensions, - env.OsType, - env.isInsiders, (message: string, dispose: boolean) => { if (!syncSetting.quietSync) { Commons.outputChannel.appendLine(message);