import { ILabShell } from '@jupyterlab/application'; import { IDocumentManager } from '@jupyterlab/docmanager'; import { ISignal, Signal } from '@lumino/signaling'; import { FileTracker, IFile } from './tracker'; import { GitManager } from './git'; /** * * Class in charge of calling necessary functions from * the GitManager and FileTracker classes * */ export class GitSyncService { /* Member Fields */ private _shell: ILabShell; private _manager: IDocumentManager; private _git: GitManager; private _tracker: FileTracker; private _completed = true; private _running: boolean; syncInterval: number = 10 * 1000; private _status: | 'sync' | 'merge' | 'up-to-date' | 'dirty' | 'warning' | 'conflict' = 'up-to-date'; private _blocked = false; private _blockedChange: Signal<this, boolean> = new Signal<this, boolean>( this ); private _runningChange: Signal<this, boolean> = new Signal<this, boolean>( this ); private _statusChange: Signal<this, any> = new Signal<this, any>(this); private _setupChange: Signal<this, any> = new Signal<this, any>(this); constructor(shell: ILabShell, manager: IDocumentManager) { this._shell = shell; this._manager = manager; this._git = new GitManager(); this._tracker = new FileTracker(this); this._addListeners(); } get running(): boolean { return this._running; } get completed(): boolean { return this._completed; } get shell(): ILabShell { return this._shell; } get manager(): IDocumentManager { return this._manager; } get git(): GitManager { return this._git; } get tracker(): FileTracker { return this._tracker; } get status(): string { return this._status; } get blockedChange(): ISignal<this, boolean> { return this._blockedChange; } get runningChange(): ISignal<this, boolean> { return this._runningChange; } get statusChange(): ISignal<this, any> { return this._statusChange; } get setupChange(): ISignal<this, any> { return this._setupChange; } start() { if (!this._blocked && !this.running) { this._running = true; this._run(); this._runningChange.emit(this.running); } } stop() { if (!this._blocked && this.running) { this._running = false; this._runningChange.emit(this.running); } } async setup(file?: IFile, branch?: string) { try { if (file && (!file.repoPath || file.repoPath !== this.git.path)) { const oldRepoPath = this.git.path; const path = file.repoPath ? file.repoPath : file.path.substring(0, file.path.lastIndexOf('/')); await this.git.setup(path); if (!file.repoPath) file.repoPath = this.git.path; if (oldRepoPath !== this.git.path) { const repoPath = this.git.path.substring( this.git.path.lastIndexOf('/') + 1 ); this._setupChange.emit({ status: 'success', attrib: 'path', value: [ 'Changed repository path to ', repoPath, '. Current checked-out branch is ', this.git.branch, '.', ], }); } } if (branch && branch !== this.git.branch) { await this.git.changeBranch(branch); this._setupChange.emit({ status: 'success', attrib: 'branch', value: ['Checked-out to branch ', this.git.branch, '.'], }); } } catch (error) { this._setupChange.emit({ status: 'error', value: error.toString() }); } } async sync(): Promise<void> { try { await this.tracker.saveAll(); this._updateStatus('sync'); await this.git.sync(); this._updateStatus('merge'); await this.tracker.reloadAll(); if (this.tracker.conflict) this._updateStatus('conflict'); else this._updateStatus('up-to-date'); } catch (error) { console.warn(error); this._updateStatus('warning', error.toString()); } } private async _run(): Promise<void> { if (this.running && this.completed && !this._blocked) { this._completed = false; setTimeout(async () => { await this.sync(); this._completed = true; this._run(); }, this.syncInterval); } } private _updateBlocked(blocked: boolean) { if (blocked !== this._blocked) { this._blocked = blocked; this._blockedChange.emit(this._blocked); } } private _updateStatus( status: 'sync' | 'merge' | 'up-to-date' | 'dirty' | 'warning' | 'conflict', error?: string ) { if (status !== this.status) { this._status = status; this._statusChange.emit({ status: status, error: error, }); } } private _addListener(signal: ISignal<any, any>, callback: any) { return signal.connect(callback, this); } private _conflictListener(sender: FileTracker, conflict: boolean) { this._updateBlocked(conflict); if (this.running) { this._run(); this._runningChange.emit(!conflict); } } private _dirtyStateListener(sender: FileTracker, dirty: boolean) { if (this.status === 'up-to-date' && dirty) { this._updateStatus('dirty'); } } private _addListeners(): void { this._addListener(this.tracker.conflictState, this._conflictListener); this._addListener(this.tracker.dirtyState, this._dirtyStateListener); } }