Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found
Select Git revision
Loading items

Target

Select target project
  • deadlock-public/deadlock-desktop
1 result
Select Git revision
Loading items
Show changes

Commits on Source 2

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