diff --git a/deadlock-plugins/deadlock-extension/src/core/api.service.ts b/deadlock-plugins/deadlock-extension/src/core/api.service.ts index 9d0295643b94a877060bac29b3c4a3ef98896d5d..b362180001e3dcb17848c3649ad430d0fc380c69 100644 --- a/deadlock-plugins/deadlock-extension/src/core/api.service.ts +++ b/deadlock-plugins/deadlock-extension/src/core/api.service.ts @@ -14,7 +14,7 @@ export default class ApiService { private axiosInstance: AxiosInstance; private static _instance: ApiService; - static getInstance(): ApiService { + static get instance(): ApiService { if (!ApiService._instance) { ApiService._instance = new ApiService(); } @@ -33,18 +33,6 @@ export default class ApiService { this.initApiInterceptor(); } - private get controller(): Controller { - return Controller.getInstance(); - } - - private get extensionStore(): ExtensionStore { - return ExtensionStore.getInstance(); - } - - private get keycloackConnection(): KeycloakOAuth2DeviceFlowConnection { - return KeycloakOAuth2DeviceFlowConnection.getInstance(); - } - initApiInterceptor() { this.initRequestInterceptor(); this.initResponseInterceptor(); @@ -53,7 +41,7 @@ export default class ApiService { private initRequestInterceptor() { this.axiosInstance.interceptors.request.use( async (config) => { - const accessToken = await this.extensionStore.getAccessToken(); + const accessToken = await ExtensionStore.instance.getAccessToken(); if (accessToken && config.headers) { config.headers['authorization'] = `BEARER ${accessToken}`; } @@ -98,14 +86,14 @@ export default class ApiService { private async onRetry(originalConfig) { try { - const storedRefreshToken = await this.extensionStore.getRefreshToken(); - const { accessToken, refreshToken } = await this.keycloackConnection.getToken({ + const storedRefreshToken = await ExtensionStore.instance.getRefreshToken(); + const { accessToken, refreshToken } = await KeycloakOAuth2DeviceFlowConnection.instance.getToken({ refreshToken: storedRefreshToken, openLink: Controller.openBrowserWithUrl, }); - await this.extensionStore.setAccessToken(accessToken); - await this.extensionStore.setRefreshToken(refreshToken); + await ExtensionStore.instance.setAccessToken(accessToken); + await ExtensionStore.instance.setRefreshToken(refreshToken); this.axiosInstance.defaults.headers.common['authorization'] = `BEARER ${accessToken}`; return this.axiosInstance(originalConfig); @@ -125,7 +113,7 @@ export default class ApiService { private async onInvalidRefreshToken(originalConfig) { try { - await this.controller.authenticate(); + await Controller.instance.authenticate(); return this.axiosInstance(originalConfig); } catch (_error) { return Promise.reject(_error); @@ -161,7 +149,7 @@ export default class ApiService { async pingUpdateWorkTime(): Promise<MissionUser> { return this.axiosInstance .post<MissionUser>( - `users/${(await this.getCurrentUser()).id}/missions/${UserMission.getInstance().missionId}/worktime`, + `users/${(await this.getCurrentUser()).id}/missions/${UserMission.instance.missionId}/worktime`, { action: 'PING', }, @@ -171,7 +159,7 @@ export default class ApiService { async startUpdateWorkTime() { return this.axiosInstance - .post(`users/${(await this.getCurrentUser()).id}/missions/${UserMission.getInstance().missionId}/worktime`, { + .post(`users/${(await this.getCurrentUser()).id}/missions/${UserMission.instance.missionId}/worktime`, { action: 'START', }) .then((res) => res.data); @@ -180,7 +168,7 @@ export default class ApiService { async submitAttempt(attempt: Attempt): Promise<MissionUser> { return this.axiosInstance .post<MissionUser>( - `/users/${(await this.getCurrentUser()).id}/missions/${UserMission.getInstance().missionId}/solve/desktop`, + `/users/${(await this.getCurrentUser()).id}/missions/${UserMission.instance.missionId}/solve/desktop`, attempt, ) .then((res) => res.data); diff --git a/deadlock-plugins/deadlock-extension/src/core/commandHandler.ts b/deadlock-plugins/deadlock-extension/src/core/commandHandler.ts index 6dde3a57fc7f225dabfdafd7b9cc27ad107ce2c9..b73494fb57264fefeb9b8d2a4e2e0a2f43d975b1 100644 --- a/deadlock-plugins/deadlock-extension/src/core/commandHandler.ts +++ b/deadlock-plugins/deadlock-extension/src/core/commandHandler.ts @@ -1,16 +1,27 @@ import { Command, commands } from 'vscode'; import Controller from './controller'; + export class CommandHandler { - constructor(private controller: Controller) { - this.initCommandHandler(); + private static _instance?: CommandHandler; + public readonly disconnectCommand: Command; + public readonly authenticateCommand: Command = { title: 'Authenticate', command: 'deadlock.authenticate' }; + public readonly openUrlInBrowserCommand: Command = { title: 'Open url in browser', command: 'vscode.open' }; + + public static get instance(): CommandHandler { + if (!this._instance) { + this._instance = new CommandHandler(); + } + return this._instance; } - initCommandHandler() { - commands.registerCommand(disconnectCommand.command, this.controller.disconnect.bind(this.controller)); - commands.registerCommand(authenticateCommand.command, this.controller.authenticate.bind(this.controller)); + private constructor() { + this.disconnectCommand = { title: 'Disconnect', command: 'deadlock.disconnect' }; + this.authenticateCommand = { title: 'Authenticate', command: 'deadlock.authenticate' }; + this.openUrlInBrowserCommand = { title: 'Open url in browser', command: 'vscode.open' }; + commands.registerCommand(this.disconnectCommand.command, Controller.instance.disconnect.bind(Controller.instance)); + commands.registerCommand( + this.authenticateCommand.command, + Controller.instance.authenticate.bind(Controller.instance), + ); } } - -export const disconnectCommand: Command = { title: 'Disconnect', command: 'deadlock.disconnect' }; -export const authenticateCommand: Command = { title: 'Authenticate', command: 'deadlock.authenticate' }; -export const openUrlInBrowserCommand: Command = { title: 'Open url in browser', command: 'vscode.open' }; diff --git a/deadlock-plugins/deadlock-extension/src/core/controller.ts b/deadlock-plugins/deadlock-extension/src/core/controller.ts index 02fca9269d04aec578abad1704fbe1f3478f1a4a..5541b40178643dcac5f90fafa2120fd38a281845 100644 --- a/deadlock-plugins/deadlock-extension/src/core/controller.ts +++ b/deadlock-plugins/deadlock-extension/src/core/controller.ts @@ -1,6 +1,6 @@ import BriefingView from '../view/briefingView'; import QuickSetupView from '../view/quickSetupView'; -import { CommandHandler, openUrlInBrowserCommand } from './commandHandler'; +import { CommandHandler } from './commandHandler'; import ExtensionStore from './extensionStore'; import KeycloakOAuth2DeviceFlowConnection from './keycloakOAuth2DeviceFlowConnection'; import { MissionDevContainer } from './mission/missionDevContainer'; @@ -20,43 +20,25 @@ import Mission from '../model/mission'; import ApiService from './api.service'; export default class Controller { - private static instance: Controller; - private commandHandler: CommandHandler; + private static _instance: Controller; private briefingView?: BriefingView; private quickSetupView: QuickSetupView; - private _extensionStore: ExtensionStore; - public get extensionStore() { - return this._extensionStore; - } - - private get connection(): KeycloakOAuth2DeviceFlowConnection { - return KeycloakOAuth2DeviceFlowConnection.getInstance(); - } - - private get apiService(): ApiService { - return ApiService.getInstance(); - } - - private constructor(public readonly context: ExtensionContext) { - this._extensionStore = ExtensionStore.getInstance(context); + public constructor(public readonly context: ExtensionContext) { + if (Controller._instance) { + throw new Error('Controller is a singleton'); + } + Controller._instance = this; if (isDocker()) { this.briefingView = new BriefingView(); } - this.quickSetupView = new QuickSetupView(context.extensionUri); - this.commandHandler = new CommandHandler(this); + this.quickSetupView = new QuickSetupView(); this.init(); } - public static getInstance(context?: ExtensionContext): Controller { - if (!Controller.instance) { - if (context === undefined) { - throw new Error('Controller needs a context when instantiated'); - } - Controller.instance = new Controller(context); - } - return Controller.instance; + public static get instance(): Controller { + return Controller._instance; } getChallenge(missionId: string): Mission | undefined { @@ -67,7 +49,7 @@ export default class Controller { public async getSolution(missionId: string, reviewedId: string) { try { - const solution = await this.apiService.getSolution(missionId); + const solution = await ApiService.instance.getSolution(missionId); const solutionFolder = join(getReviewedStudentWorkdirPath(reviewedId), missionId, '.solution'); const tarName = 'solution.tar.gz'; const tarPath = join(solutionFolder, tarName); @@ -122,7 +104,7 @@ export default class Controller { }, }); - this.quickSetupView.isAlreadyConnected = (await this._extensionStore.getAccessToken()) !== undefined; + this.quickSetupView.isAlreadyConnected = (await ExtensionStore.instance.getAccessToken()) !== undefined; } public async disconnect() { @@ -139,7 +121,7 @@ export default class Controller { extensionWarn(e); } try { - await this.extensionStore.clear(); + await ExtensionStore.instance.clear(); } catch (e) { extensionWarn('Could not clear extension store'); extensionWarn(e); @@ -148,21 +130,23 @@ export default class Controller { } public async createSshKeyPair() { - const { publicKey, privateKey } = await this.apiService.getUserSshKey(); + const { publicKey, privateKey } = await ApiService.instance.getUserSshKey(); await createSshKeyFiles(publicKey, privateKey); } public async authenticate() { - await this.connection.registerDevice(); - const tokens = await this.connection.getToken({ openLink: Controller.openBrowserWithUrl }); - await this._extensionStore.setAccessToken(tokens.accessToken); - await this._extensionStore.setRefreshToken(tokens.refreshToken); + await KeycloakOAuth2DeviceFlowConnection.instance.registerDevice(); + const tokens = await KeycloakOAuth2DeviceFlowConnection.instance.getToken({ + openLink: Controller.openBrowserWithUrl, + }); + await ExtensionStore.instance.setAccessToken(tokens.accessToken); + await ExtensionStore.instance.setRefreshToken(tokens.refreshToken); await this.createSshKeyPair(); this.quickSetupView.isAlreadyConnected = true; } public static openBrowserWithUrl(url: string) { - commands.executeCommand(openUrlInBrowserCommand.command, Uri.parse(url)); + commands.executeCommand(CommandHandler.instance.openUrlInBrowserCommand.command, Uri.parse(url)); } public async launchMission({ @@ -178,18 +162,18 @@ export default class Controller { }) { window.showInformationMessage(`vous lancez la mission ${missionId}`); - const hadBeenConnected = (await this._extensionStore.getAccessToken()) !== undefined; + const hadBeenConnected = (await ExtensionStore.instance.getAccessToken()) !== undefined; if (!hadBeenConnected) { await this.authenticate(); window.showInformationMessage('Connexion validée'); } - const reviewer = await this.apiService.getCurrentUser(); + const reviewer = await ApiService.instance.getCurrentUser(); if (!!revieweeId && revieweeId !== reviewer.id) { try { - await this.apiService.grantAccessToRepository(revieweeId, missionId); + await ApiService.instance.grantAccessToRepository(revieweeId, missionId); } catch (e) { if (hasStatusNumber(e)) { if (e.status === 403) { @@ -204,7 +188,7 @@ export default class Controller { } } try { - await Controller.getInstance().getSolution(missionId, revieweeId); + await this.getSolution(missionId, revieweeId); } catch (e) { window.showErrorMessage(`Une erreur est survenue lors de la récupération de la solution`); extensionError(e); diff --git a/deadlock-plugins/deadlock-extension/src/core/extensionStore.ts b/deadlock-plugins/deadlock-extension/src/core/extensionStore.ts index 2ccb8749dca4c345316711cd1371ee84113cef88..ff52c9b672f7dd3c7a32462fabd53480c1ff21a4 100644 --- a/deadlock-plugins/deadlock-extension/src/core/extensionStore.ts +++ b/deadlock-plugins/deadlock-extension/src/core/extensionStore.ts @@ -1,12 +1,13 @@ -import { ExtensionContext, SecretStorage } from 'vscode'; +import { SecretStorage } from 'vscode'; import { extensionLog as log } from '../recorder/utils/log'; +import Controller from './controller'; export default class ExtensionStore { - private static instance: ExtensionStore; + private static _instance: ExtensionStore; private secretStorage: SecretStorage; - private constructor(context: ExtensionContext) { - this.secretStorage = context.secrets; + private constructor() { + this.secretStorage = Controller.instance.context.secrets; } public async clear() { @@ -16,13 +17,12 @@ export default class ExtensionStore { this.secretStorage.delete(SecretStoreKey.RefreshTokenKey); } - public static getInstance(context?: ExtensionContext): ExtensionStore { - if (!ExtensionStore.instance) { - if (!context) throw new Error('ExtensionStore should be initiate with a storage first time'); - ExtensionStore.instance = new ExtensionStore(context); + public static get instance(): ExtensionStore { + if (!ExtensionStore._instance) { + ExtensionStore._instance = new ExtensionStore(); } - return ExtensionStore.instance; + return ExtensionStore._instance; } public getAccessToken(): Thenable<string | undefined> { diff --git a/deadlock-plugins/deadlock-extension/src/core/gitMission.ts b/deadlock-plugins/deadlock-extension/src/core/gitMission.ts index 794137e58b1e4dd0571f0632178edb778dd97ac4..dc0db9aa7124c4c5b0b296707da1894dd6068fe6 100644 --- a/deadlock-plugins/deadlock-extension/src/core/gitMission.ts +++ b/deadlock-plugins/deadlock-extension/src/core/gitMission.ts @@ -13,10 +13,18 @@ const defaultRemote = 'origin'; type Branch = 'master' | 'live'; export default class GitMission { + private static _instance: GitMission; + public static get instance(): GitMission { + if (!GitMission._instance) { + GitMission._instance = new GitMission(); + } + return GitMission._instance; + } + private prefix = 'DEADLOCK-RECORDER'; private git: SimpleGit; - constructor() { + private constructor() { const options: Partial<SimpleGitOptions> = { baseDir: GITEA_PATH_IC, binary: 'git', @@ -36,7 +44,7 @@ export default class GitMission { async setupSshAgent() { try { - const gitea = await ApiService.getInstance().getGiteaPublicProperties(); + const gitea = await ApiService.instance.getGiteaPublicProperties(); await exec(`ssh-add /tmp/.ssh/id_rsa`); await exec(`eval "$(ssh-agent -s)" && ssh-keyscan -p ${gitea.sshPort} -H ${gitea.sshHost} >> ~/.ssh/known_hosts`); } catch (err) { @@ -58,7 +66,7 @@ export default class GitMission { } async getAuthor(): Promise<string> { - return (await UserMission.getInstance().getGiteaUser()).username; + return (await UserMission.instance.getGiteaUser()).username; } async init() { @@ -73,10 +81,10 @@ export default class GitMission { return Promise.resolve(this); } - const giteaUser = await UserMission.getInstance().getGiteaUser(); + const giteaUser = await UserMission.instance.getGiteaUser(); await this.git.addRemote(defaultRemote, await this.getRemotePath()); - await this.git.addConfig('user.email', await UserMission.getInstance().getEmail(), false, 'local'); + await this.git.addConfig('user.email', await UserMission.instance.getEmail(), false, 'local'); await this.git.addConfig( 'user.name', `${giteaUser.details.lastName} ${giteaUser.details.firstName}`, @@ -93,10 +101,10 @@ export default class GitMission { } private async getRemotePath() { - const giteaConfig = await ApiService.getInstance().getGiteaPublicProperties(); + const giteaConfig = await ApiService.instance.getGiteaPublicProperties(); return `ssh://git@${giteaConfig.sshHost}:${giteaConfig.sshPort}/${ - (await UserMission.getInstance().getGiteaUser()).username - }/${UserMission.getInstance().missionId}`; + (await UserMission.instance.getGiteaUser()).username + }/${UserMission.instance.missionId}`; } async readRemote() { diff --git a/deadlock-plugins/deadlock-extension/src/core/keycloakOAuth2DeviceFlowConnection.ts b/deadlock-plugins/deadlock-extension/src/core/keycloakOAuth2DeviceFlowConnection.ts index 587f973416f5976fa3af5c2e99b1ba62b31791d3..39ba2e4c019b66b4b701bdbc7415603ec132f67f 100644 --- a/deadlock-plugins/deadlock-extension/src/core/keycloakOAuth2DeviceFlowConnection.ts +++ b/deadlock-plugins/deadlock-extension/src/core/keycloakOAuth2DeviceFlowConnection.ts @@ -35,7 +35,7 @@ export default class KeycloakOAuth2DeviceFlowConnection { this.deviceAuthorizationRequestResponse = {}; } - public static getInstance(): KeycloakOAuth2DeviceFlowConnection { + public static get instance(): KeycloakOAuth2DeviceFlowConnection { if (!KeycloakOAuth2DeviceFlowConnection._instance) { KeycloakOAuth2DeviceFlowConnection._instance = new KeycloakOAuth2DeviceFlowConnection(); } diff --git a/deadlock-plugins/deadlock-extension/src/core/mission/model/userMission.ts b/deadlock-plugins/deadlock-extension/src/core/mission/model/userMission.ts index 359af9cb47dd5242244a0a8567d96af6d6609793..829a829430e584589db3d4d4df4b1eacd08e93b4 100644 --- a/deadlock-plugins/deadlock-extension/src/core/mission/model/userMission.ts +++ b/deadlock-plugins/deadlock-extension/src/core/mission/model/userMission.ts @@ -24,19 +24,15 @@ export default class UserMission implements UserMissionModel { UserMission._instance = this; } - public static getInstance(): UserMission { + public static get instance(): UserMission { if (!UserMission._instance) { return (this._instance = new UserMission(JSON.parse(readFileSync(USER_MISSION_PATH, 'utf8')))); } return UserMission._instance; } - private get apiService(): ApiService { - return ApiService.getInstance(); - } - public async getGiteaUser(): Promise<User & { username: string }> { - const giteaUser: User & { username?: string } = this.reviewee ?? (await this.apiService.getCurrentUser()); + const giteaUser: User & { username?: string } = this.reviewee ?? (await ApiService.instance.getCurrentUser()); return { ...giteaUser, username: giteaUser.id.split('-').join('') }; } @@ -69,7 +65,7 @@ export default class UserMission implements UserMissionModel { missionId, storyId, missionVersion, - reviewee: revieweeId ? await ApiService.getInstance().getUser(revieweeId) : undefined, + reviewee: revieweeId ? await ApiService.instance.getUser(revieweeId) : undefined, } as UserMissionModel, null, 2, diff --git a/deadlock-plugins/deadlock-extension/src/extension.ts b/deadlock-plugins/deadlock-extension/src/extension.ts index ba3d660ebf7525b69a16978c6606fd0e356b26ba..093a4d10fb36a6db2c6dce4c140f0e8ff2089300 100644 --- a/deadlock-plugins/deadlock-extension/src/extension.ts +++ b/deadlock-plugins/deadlock-extension/src/extension.ts @@ -1,21 +1,21 @@ import { window, ExtensionContext, workspace, commands } from 'vscode'; -import Controller from './core/controller'; import isDocker from './core/utils/isdocker'; import Recorder from './recorder/recorder'; import { extensionError as error } from './recorder/utils/log'; import { DepNodeProvider } from './view/deadlockPanel'; import { CommandTreeProvider } from './view/CommandTree'; import StartedMissionsView from './view/startedMissionsView'; +import Controller from './core/controller'; export async function activate(context: ExtensionContext) { + new Controller(context); window.showInformationMessage('Bienvenue sur Deadlock!'); - Controller.getInstance(context); const workspaceFolders = workspace.workspaceFolders?.toString() ?? ''; if (!workspaceFolders) window.showInformationMessage('Pas de répertoires ouverts'); const deadlockPanelProvider = new DepNodeProvider(); window.registerTreeDataProvider('deadlockPanel', deadlockPanelProvider); - window.registerWebviewViewProvider('startedMissions', new StartedMissionsView(context)); + window.registerWebviewViewProvider('startedMissions', new StartedMissionsView()); if (isDocker()) { commands.executeCommand('setContext', 'deadlock.inContainer', true); diff --git a/deadlock-plugins/deadlock-extension/src/recorder/recorder.ts b/deadlock-plugins/deadlock-extension/src/recorder/recorder.ts index 0eaa8e4716a2acc57e019fbc095f4a0956ab7a5e..20b1cd8b1b9fbf7ff8987b117bfac00b198fc9e0 100644 --- a/deadlock-plugins/deadlock-extension/src/recorder/recorder.ts +++ b/deadlock-plugins/deadlock-extension/src/recorder/recorder.ts @@ -8,13 +8,13 @@ import aquirePermissions from './utils/permission'; import UserMission from '../core/mission/model/userMission'; import ApiService from '../core/api.service'; import { window } from 'vscode'; +import { pushOnCommitQueueIfNotReviewing } from './utils/gitea'; export default class Recorder { - private _gitMission?: GitMission; private static _instance?: Recorder; private get userMission(): UserMission { - return UserMission.getInstance(); + return UserMission.instance; } private constructor() { @@ -28,22 +28,14 @@ export default class Recorder { return this._instance; } - public get gitMission(): GitMission { - return this._gitMission!; - } - async setupProject(gitMission: GitMission) { const argsArray: string[] = [`user-git-${(await this.userMission.getGiteaUser()).username}`]; - if(!this.userMission.isReviewing()) { + if (!this.userMission.isReviewing()) { argsArray.push('.git'); } - await copyProjectSources( - GITEA_PATH_IC, - MISSION_PATH_IC, - ...argsArray - ); + await copyProjectSources(GITEA_PATH_IC, MISSION_PATH_IC, ...argsArray); if (!this.userMission.isReviewing()) { renameTempToUserGitFiles(MISSION_PATH_IC, await gitMission.getAuthor()); @@ -67,13 +59,16 @@ export default class Recorder { async run() { try { await aquirePermissions(); - // TODO refactor make it as a sigleton - this._gitMission = await new GitMission().init(); - await this.setupFromRemoteRepo(this._gitMission); - await this.setupProject(this._gitMission); + await GitMission.instance.init(); + const isStarted = await GitMission.instance.isRemoteRepoExist(); + await this.setupFromRemoteRepo(GitMission.instance); + await this.setupProject(GitMission.instance); + if (!isStarted) { + pushOnCommitQueueIfNotReviewing(GitMission.instance, 'Auto'); + } if (!this.userMission.isReviewing()) { if (ENABLE_AUTOMATIC_SAVE) { - new AutomaticSave(MISSION_PATH_IC, this._gitMission); + new AutomaticSave(MISSION_PATH_IC, GitMission.instance); } try { runTimer(); @@ -82,7 +77,7 @@ export default class Recorder { error(e); } } else { - await this.gitMission.forgetSshKeys(); + await GitMission.instance.forgetSshKeys(); } } catch (e) { error('Cannot setup user repo.'); @@ -92,7 +87,7 @@ export default class Recorder { } export async function runTimer() { - const apiService = ApiService.getInstance(); + const apiService = ApiService.instance; try { await apiService.startUpdateWorkTime(); setInterval(async () => { diff --git a/deadlock-plugins/deadlock-extension/src/recorder/utils/gitea.ts b/deadlock-plugins/deadlock-extension/src/recorder/utils/gitea.ts index f307b2b591568691fb4c51a1616f8ff9ae3ea1ef..d3aaa558cfd410a4a87e8ff132c1c38b590b3cf6 100644 --- a/deadlock-plugins/deadlock-extension/src/recorder/utils/gitea.ts +++ b/deadlock-plugins/deadlock-extension/src/recorder/utils/gitea.ts @@ -40,7 +40,7 @@ export async function pushOnCommitQueueIfNotReviewing( from: 'Run' | 'Auto' | 'HttpServer', callback?: (commitResult: CommitResult) => void, ) { - if (!UserMission.getInstance().isReviewing()) { + if (!UserMission.instance.isReviewing()) { await pushOnCommitQueue(gitMission, from, callback); } } diff --git a/deadlock-plugins/deadlock-extension/src/recorder/utils/workdir.ts b/deadlock-plugins/deadlock-extension/src/recorder/utils/workdir.ts index acb35be4f28d154d6ed6db0078c3f4271c70ad80..5bac1dfbe438f148dea7907a3a70c5417f7607fa 100644 --- a/deadlock-plugins/deadlock-extension/src/recorder/utils/workdir.ts +++ b/deadlock-plugins/deadlock-extension/src/recorder/utils/workdir.ts @@ -4,6 +4,7 @@ import { join } from 'path'; import { log } from './log'; import { promisify } from 'util'; import { mkdir, rm } from 'fs/promises'; +import Controller from '../../core/controller'; const exec = promisify(execCallback); const PREFIX = '[DEADLOCK-RECORDER]'; @@ -88,3 +89,7 @@ export async function emptyDirectories(...directoryPaths: string[]) { export async function createDirectories(...directoryPaths: string[]) { return Promise.all(directoryPaths.map((directoryPath) => mkdir(directoryPath, { recursive: true }))); } + +export function getExtensionUri() { + return Controller.instance.context.extensionUri; +} diff --git a/deadlock-plugins/deadlock-extension/src/view/CommandTree.ts b/deadlock-plugins/deadlock-extension/src/view/CommandTree.ts index 8fdfd7b7e4ccc096b1df28430c1d2e452c5b4c44..9c62aacdd0fb13b7918643cd38e77c0ce14a8e14 100644 --- a/deadlock-plugins/deadlock-extension/src/view/CommandTree.ts +++ b/deadlock-plugins/deadlock-extension/src/view/CommandTree.ts @@ -1,12 +1,12 @@ import { MissionCommand, MissionHandler } from '../model/mission'; import { commands, Event, EventEmitter, ThemeColor, ThemeIcon, TreeDataProvider, TreeItem, window } from 'vscode'; -import Recorder from '../recorder/recorder'; import { MISSION_PATH_IC } from '../core/config'; import { ChildProcessWithoutNullStreams, spawn } from 'child_process'; import AttemptBuilder from '../model/attempt'; import ApiService from '../core/api.service'; import UserMission from '../core/mission/model/userMission'; import { pushOnCommitQueueIfNotReviewing } from '../recorder/utils/gitea'; +import GitMission from '../core/gitMission'; export class CommandTreeProvider implements TreeDataProvider<CommandItem> { getTreeItem(element: CommandItem): TreeItem { @@ -103,10 +103,10 @@ class CommandItem extends TreeItem { const output = window.createOutputChannel(`${outputPrefix} - ${this.missionCommand.name}`); output.show(); const attempt = new AttemptBuilder( - (await ApiService.getInstance().getCurrentUser()).id, - UserMission.getInstance().missionId, + (await ApiService.instance.getCurrentUser()).id, + UserMission.instance.missionId, this.missionCommand.type === 'submit', - UserMission.getInstance().storyId, + UserMission.instance.storyId, ); this.spawnHandler = spawn(this.parced.cmd, this.parced.args, { cwd: MISSION_PATH_IC, @@ -123,8 +123,8 @@ class CommandItem extends TreeItem { output.appendLine(`${outputPrefix} - ${this.missionCommand.name} - exited with code ${code}`); attempt.setExitCode(code ?? undefined); if (this.missionCommand.type === 'submit' || this.missionCommand.type === 'run') { - pushOnCommitQueueIfNotReviewing(Recorder.instance.gitMission, 'Run', async (commit) => { - await ApiService.getInstance().submitAttempt(attempt.build(commit?.commit)); + pushOnCommitQueueIfNotReviewing(GitMission.instance, 'Run', async (commit) => { + await ApiService.instance.submitAttempt(attempt.build(commit?.commit)); }); } this.isRunning = false; diff --git a/deadlock-plugins/deadlock-extension/src/view/briefingView.ts b/deadlock-plugins/deadlock-extension/src/view/briefingView.ts index ae6dfaf5a7f4b25dd89388998e0831c778a18274..71db47c9617ece2ea80bbbf8f9ec7136b3c95ba5 100644 --- a/deadlock-plugins/deadlock-extension/src/view/briefingView.ts +++ b/deadlock-plugins/deadlock-extension/src/view/briefingView.ts @@ -69,10 +69,10 @@ export default class BriefingView extends WebviewBase { async render() { let output = ''; - const missionUser = UserMission.getInstance(); + const missionUser = UserMission.instance; if (missionUser.isReviewing()) { - const user = await ApiService.getInstance().getCurrentUser(); + const user = await ApiService.instance.getCurrentUser(); const giteaUser = await missionUser.getGiteaUser(); output += ` <h2>Professeur</h2> diff --git a/deadlock-plugins/deadlock-extension/src/view/quickSetupView.ts b/deadlock-plugins/deadlock-extension/src/view/quickSetupView.ts index 0c3ec6a80c8760db1e4c03eca115fbbe5bc07c81..0f0d4f31d0f07e201999d7198cca20fa308cc354 100644 --- a/deadlock-plugins/deadlock-extension/src/view/quickSetupView.ts +++ b/deadlock-plugins/deadlock-extension/src/view/quickSetupView.ts @@ -1,21 +1,16 @@ -import { commands, Uri } from 'vscode'; -import { authenticateCommand, disconnectCommand } from '../core/commandHandler'; +import { commands } from 'vscode'; +import { CommandHandler } from '../core/commandHandler'; import { openQuickSetupCommand } from '../core/controller'; -import ExtensionStore from '../core/extensionStore'; import { extensionLog as log } from '../recorder/utils/log'; import { WebviewBase } from './webviewBase'; export const quickSetupId = 'quickSetup'; export default class QuickSetupView extends WebviewBase { - private extensionUri: Uri; - private extensionStore: ExtensionStore; private _isAlreadyConnected: boolean; - constructor(extensionUri: Uri) { + constructor() { super(quickSetupId, 'QuickSetup', openQuickSetupCommand); - this.extensionUri = extensionUri; - this.extensionStore = ExtensionStore.getInstance(); this._isAlreadyConnected = false; } @@ -87,7 +82,7 @@ export default class QuickSetupView extends WebviewBase { } renderHeaderHtml() { - const toolkitUri = this.getExternalRessourcePath(this.extensionUri, [ + const toolkitUri = this.getExternalRessourcePath([ 'node_modules', '@vscode', 'webview-ui-toolkit', @@ -95,13 +90,9 @@ export default class QuickSetupView extends WebviewBase { 'toolkit.js', ]); - const customStyle = this.getExternalRessourcePath(this.extensionUri, [ - 'resources', - 'styles', - 'gettingStartedView.css', - ]); + const customStyle = this.getExternalRessourcePath(['resources', 'styles', 'gettingStartedView.css']); - const customScript = this.getExternalRessourcePath(this.extensionUri, ['resources', 'js', 'gettingStartedView.js']); + const customScript = this.getExternalRessourcePath(['resources', 'js', 'gettingStartedView.js']); return ` <meta charset="UTF-8"> @@ -114,10 +105,10 @@ export default class QuickSetupView extends WebviewBase { onMessageReceive(message: any): void { switch (message.command) { case 'openAuthenticationPageAction': - commands.executeCommand(authenticateCommand.command); + commands.executeCommand(CommandHandler.instance.authenticateCommand.command); return; case 'disconnectUserAction': - commands.executeCommand(disconnectCommand.command); + commands.executeCommand(CommandHandler.instance.disconnectCommand.command); return; } } diff --git a/deadlock-plugins/deadlock-extension/src/view/startedMissionsView.ts b/deadlock-plugins/deadlock-extension/src/view/startedMissionsView.ts index 8fac6e7ee6e1bb1504b4980c0cdd47bec1447ed9..dda4a28edbedd42f61f15f8d1c9b1aebe54da196 100644 --- a/deadlock-plugins/deadlock-extension/src/view/startedMissionsView.ts +++ b/deadlock-plugins/deadlock-extension/src/view/startedMissionsView.ts @@ -1,23 +1,18 @@ import { randomBytes } from 'crypto'; import { existsSync, readdirSync, readFileSync } from 'fs'; import { join } from 'path'; -import { ExtensionContext, Webview, WebviewView, WebviewViewProvider } from 'vscode'; +import { Webview, WebviewView, WebviewViewProvider } from 'vscode'; import Controller from '../core/controller'; import { extensionWarn } from '../recorder/utils/log'; import { getUri } from './webviewBase'; import { missionWorkdir } from '../core/config'; import UserMission from '../core/mission/model/userMission'; +import { getExtensionUri } from '../recorder/utils/workdir'; export default class StartedMissionsView implements WebviewViewProvider { - private readonly controller: Controller; - constructor(private context: ExtensionContext) { - this.controller = Controller.getInstance(context); - } - resolveWebviewView(webviewView: WebviewView): void | Thenable<void> { webviewView.webview.options = { enableScripts: true, - - localResourceRoots: [this.context.extensionUri], + localResourceRoots: [getExtensionUri()], }; webviewView.webview.html = this._getHtmlForWebview(webviewView.webview); webviewView.webview.onDidReceiveMessage(this.onMessageReceive.bind(this)); @@ -28,7 +23,7 @@ export default class StartedMissionsView implements WebviewViewProvider { case 'openMission': { const path = join(missionWorkdir, message.mission, '.config', 'user-challenge.json'); const userMission: UserMission = JSON.parse(readFileSync(path, 'utf8')); - Controller.getInstance().launchMission({ + Controller.instance.launchMission({ missionId: userMission.missionId, missionVersion: userMission.missionVersion, storyId: userMission.storyId, @@ -39,17 +34,11 @@ export default class StartedMissionsView implements WebviewViewProvider { } private _getHtmlForWebview(webview: Webview) { - const toolkitUri = getUri(webview, this.context.extensionUri, [ - 'node_modules', - '@vscode', - 'webview-ui-toolkit', - 'dist', - 'toolkit.js', - ]); + const toolkitUri = getUri(webview, ['node_modules', '@vscode', 'webview-ui-toolkit', 'dist', 'toolkit.js']); - const css = getUri(webview, this.context.extensionUri, ['resources', 'styles', 'startedMissionsView.css']); + const css = getUri(webview, ['resources', 'styles', 'startedMissionsView.css']); - const js = getUri(webview, this.context.extensionUri, ['resources', 'js', 'startedMissionsView.js']); + const js = getUri(webview, ['resources', 'js', 'startedMissionsView.js']); // find all folders in mission directory let missionsHtml = ''; diff --git a/deadlock-plugins/deadlock-extension/src/view/webviewBase.ts b/deadlock-plugins/deadlock-extension/src/view/webviewBase.ts index a2a62417a14fafdf86c917acca48502ea06bd0e4..821b96ddd1be98254b3f4d8a40234431f393c4e3 100644 --- a/deadlock-plugins/deadlock-extension/src/view/webviewBase.ts +++ b/deadlock-plugins/deadlock-extension/src/view/webviewBase.ts @@ -1,4 +1,3 @@ -'use strict'; import { Command, commands, @@ -10,6 +9,7 @@ import { WebviewPanelOnDidChangeViewStateEvent, window, } from 'vscode'; +import { getExtensionUri } from '../recorder/utils/workdir'; const emptyCommands: Disposable[] = [ { @@ -19,8 +19,8 @@ const emptyCommands: Disposable[] = [ }, ]; -export function getUri(webview: Webview, extensionUri: Uri, pathList: string[]) { - return webview.asWebviewUri(Uri.joinPath(extensionUri, ...pathList)); +export function getUri(webview: Webview, pathList: string[]) { + return webview.asWebviewUri(Uri.joinPath(getExtensionUri(), ...pathList)); } export abstract class WebviewBase implements Disposable { @@ -33,8 +33,8 @@ export abstract class WebviewBase implements Disposable { this.load(); } - getExternalRessourcePath(extensionUri: Uri, pathList: string[]) { - return getUri(this.panel!.webview, extensionUri, pathList); + getExternalRessourcePath(pathList: string[]) { + return getUri(this.panel!.webview, pathList); } registerCommands(): Disposable[] {