mirror of
https://github.com/tiddly-gittly/TidGi-Desktop.git
synced 2025-12-06 02:30:47 -08:00
249 lines
9.5 KiB
TypeScript
249 lines
9.5 KiB
TypeScript
import { ipcMain, dialog } from 'electron';
|
|
import { injectable, inject } from 'inversify';
|
|
import { debounce } from 'lodash';
|
|
import {
|
|
AssumeSyncError,
|
|
CantSyncGitNotInitializedError,
|
|
CantSyncInSpecialGitStateAutoFixFailed,
|
|
GitPullPushError,
|
|
GitStep,
|
|
ModifiedFileList,
|
|
SyncParameterMissingError,
|
|
SyncScriptIsInDeadLoopError,
|
|
hasGit,
|
|
} from 'git-sync-js';
|
|
import { spawn, Worker, ModuleThread } from 'threads';
|
|
|
|
import serviceIdentifier from '@services/serviceIdentifier';
|
|
import type { IViewService } from '@services/view/interface';
|
|
import type { IPreferenceService } from '@services/preferences/interface';
|
|
import { logger } from '@services/libs/log';
|
|
import i18n from '@services/libs/i18n';
|
|
import { IGitLogMessage, IGitService, IGitUserInfos } from './interface';
|
|
import { WikiChannel } from '@/constants/channels';
|
|
import { GitWorker } from './gitWorker';
|
|
import { Observer } from 'rxjs';
|
|
|
|
// @ts-expect-error it don't want .ts
|
|
// eslint-disable-next-line import/no-webpack-loader-syntax
|
|
import workerURL from 'threads-plugin/dist/loader?name=gitWorker!./gitWorker.ts';
|
|
import { LOCAL_GIT_DIRECTORY } from '@/constants/appPaths';
|
|
import { WindowNames } from '@services/windows/WindowProperties';
|
|
import { lazyInject } from '@services/container';
|
|
import { IWindowService } from '@services/windows/interface';
|
|
|
|
@injectable()
|
|
export class Git implements IGitService {
|
|
@lazyInject(serviceIdentifier.Window) private readonly windowService!: IWindowService;
|
|
@lazyInject(serviceIdentifier.View) private readonly viewService!: IViewService;
|
|
|
|
private gitWorker?: ModuleThread<GitWorker>;
|
|
|
|
constructor(@inject(serviceIdentifier.Preference) private readonly preferenceService: IPreferenceService) {
|
|
void this.initWorker();
|
|
this.debounceCommitAndSync = this.commitAndSync.bind(this);
|
|
void this.preferenceService.get('syncDebounceInterval').then((syncDebounceInterval) => {
|
|
this.debounceCommitAndSync = debounce(this.commitAndSync.bind(this), syncDebounceInterval);
|
|
});
|
|
}
|
|
|
|
private async initWorker(): Promise<void> {
|
|
process.env.LOCAL_GIT_DIRECTORY = LOCAL_GIT_DIRECTORY;
|
|
this.gitWorker = await spawn<GitWorker>(new Worker(workerURL));
|
|
}
|
|
|
|
public debounceCommitAndSync: (wikiFolderPath: string, remoteUrl: string, userInfo: IGitUserInfos) => Promise<void> | undefined;
|
|
|
|
public async getWorkspacesRemote(wikiFolderPath: string): Promise<string | undefined> {
|
|
if (await hasGit(wikiFolderPath)) {
|
|
return await this.gitWorker?.getRemoteUrl(wikiFolderPath);
|
|
}
|
|
}
|
|
|
|
public async getModifiedFileList(wikiFolderPath: string): Promise<ModifiedFileList[]> {
|
|
const list = await this.gitWorker?.getModifiedFileList(wikiFolderPath);
|
|
return list ?? [];
|
|
}
|
|
|
|
/**
|
|
*
|
|
* @param {string} githubRepoName similar to "linonetwo/wiki", string after "https://com/"
|
|
*/
|
|
public async updateGitInfoTiddler(githubRepoName: string): Promise<void> {
|
|
const browserView = await this.viewService.getActiveBrowserView();
|
|
if (browserView !== undefined) {
|
|
const tiddlerText = await new Promise((resolve) => {
|
|
browserView.webContents.send(WikiChannel.getTiddlerText, '$:/GitHub/Repo');
|
|
ipcMain.once(WikiChannel.getTiddlerTextDone, (_event, value) => resolve(value));
|
|
});
|
|
if (tiddlerText !== githubRepoName) {
|
|
await new Promise<void>((resolve) => {
|
|
browserView.webContents.send(WikiChannel.addTiddler, '$:/GitHub/Repo', githubRepoName, {
|
|
type: 'text/vnd.tiddlywiki',
|
|
});
|
|
ipcMain.once(WikiChannel.addTiddlerDone, () => resolve());
|
|
});
|
|
}
|
|
return;
|
|
}
|
|
logger.error('no browserView in updateGitInfoTiddler');
|
|
}
|
|
|
|
private translateMessage(message: string): string {
|
|
switch (message) {
|
|
case GitStep.StartGitInitialization: {
|
|
return i18n.t('Log.StartGitInitialization');
|
|
}
|
|
case GitStep.GitRepositoryConfigurationFinished: {
|
|
return i18n.t('Log.GitRepositoryConfigurationFinished');
|
|
}
|
|
case GitStep.StartConfiguringGithubRemoteRepository: {
|
|
return i18n.t('Log.StartConfiguringGithubRemoteRepository');
|
|
}
|
|
case GitStep.StartBackupToGitRemote: {
|
|
return i18n.t('Log.StartBackupToGithubRemote');
|
|
}
|
|
case GitStep.PrepareCloneOnlineWiki: {
|
|
return i18n.t('Log.PrepareCloneOnlineWiki');
|
|
}
|
|
case GitStep.PrepareSync: {
|
|
return i18n.t('Log.PrepareSync');
|
|
}
|
|
case GitStep.HaveThingsToCommit: {
|
|
return i18n.t('Log.HaveThingsToCommit');
|
|
}
|
|
case GitStep.AddingFiles: {
|
|
return i18n.t('Log.AddingFiles');
|
|
}
|
|
case GitStep.AddComplete: {
|
|
return i18n.t('Log.AddComplete');
|
|
}
|
|
case GitStep.CommitComplete: {
|
|
return i18n.t('Log.CommitComplete');
|
|
}
|
|
case GitStep.PreparingUserInfo: {
|
|
return i18n.t('Log.PreparingUserInfo');
|
|
}
|
|
case GitStep.FetchingData: {
|
|
return i18n.t('Log.FetchingData');
|
|
}
|
|
case GitStep.NoNeedToSync: {
|
|
return i18n.t('Log.NoNeedToSync');
|
|
}
|
|
case GitStep.LocalAheadStartUpload: {
|
|
return i18n.t('Log.LocalAheadStartUpload');
|
|
}
|
|
case GitStep.CheckingLocalSyncState: {
|
|
return i18n.t('Log.CheckingLocalSyncState');
|
|
}
|
|
case GitStep.CheckingLocalGitRepoSanity: {
|
|
return i18n.t('Log.CheckingLocalGitRepoSanity');
|
|
}
|
|
case GitStep.LocalStateBehindSync: {
|
|
return i18n.t('Log.LocalStateBehindSync');
|
|
}
|
|
case GitStep.LocalStateDivergeRebase: {
|
|
return i18n.t('Log.LocalStateDivergeRebase');
|
|
}
|
|
case GitStep.RebaseResultChecking: {
|
|
return i18n.t('Log.CheckingRebaseStatus');
|
|
}
|
|
case GitStep.RebaseConflictNeedsResolve: {
|
|
return i18n.t('Log.RebaseConflictNeedsResolve');
|
|
}
|
|
case GitStep.RebaseSucceed: {
|
|
return i18n.t('Log.RebaseSucceed');
|
|
}
|
|
case GitStep.GitPushFailed: {
|
|
return i18n.t('Log.GitPushFailed');
|
|
}
|
|
case GitStep.GitMergeFailed: {
|
|
return i18n.t('Log.GitMergeFailed');
|
|
}
|
|
case GitStep.SyncFailedAlgorithmWrong: {
|
|
return i18n.t('Log.SyncFailedSystemError');
|
|
}
|
|
case GitStep.PerformLastCheckBeforeSynchronizationFinish: {
|
|
return i18n.t('Log.PerformLastCheckBeforeSynchronizationFinish');
|
|
}
|
|
case GitStep.SynchronizationFinish: {
|
|
return i18n.t('Log.SynchronizationFinish');
|
|
}
|
|
case GitStep.StartFetchingFromGithubRemote: {
|
|
return i18n.t('Log.StartFetchingFromGithubRemote');
|
|
}
|
|
case GitStep.CantSyncInSpecialGitStateAutoFixSucceed: {
|
|
return i18n.t('Log.CantSyncInSpecialGitStateAutoFixSucceed');
|
|
}
|
|
default: {
|
|
return message;
|
|
}
|
|
}
|
|
}
|
|
|
|
private translateAndLogErrorMessage(error: Error): void {
|
|
logger.error(error?.message ?? error);
|
|
if (error instanceof AssumeSyncError) {
|
|
error.message = i18n.t('Log.SynchronizationFailed');
|
|
} else if (error instanceof SyncParameterMissingError) {
|
|
error.message = i18n.t('Log.GitTokenMissing') + error.parameterName;
|
|
} else if (error instanceof GitPullPushError) {
|
|
error.message = i18n.t('Log.SyncFailedSystemError');
|
|
} else if (error instanceof CantSyncGitNotInitializedError) {
|
|
error.message = i18n.t('Log.CantSyncGitNotInitialized');
|
|
} else if (error instanceof SyncScriptIsInDeadLoopError) {
|
|
error.message = i18n.t('Log.CantSynchronizeAndSyncScriptIsInDeadLoop');
|
|
} else if (error instanceof CantSyncInSpecialGitStateAutoFixFailed) {
|
|
error.message = i18n.t('Log.CantSyncInSpecialGitStateAutoFixFailed');
|
|
}
|
|
logger.error('↑Translated→: ' + error?.message ?? error);
|
|
}
|
|
|
|
private popGitErrorNotificationToUser(step: GitStep, message: string): void {
|
|
if (step === GitStep.GitPushFailed && message.includes('403')) {
|
|
const mainWindow = this.windowService.get(WindowNames.main);
|
|
if (mainWindow !== undefined) {
|
|
void dialog.showMessageBox(mainWindow, {
|
|
title: i18n.t('Log.GitTokenMissing'),
|
|
message: `${i18n.t('Log.GitTokenExpireOrWrong')} (${message})`,
|
|
buttons: ['OK'],
|
|
cancelId: 0,
|
|
defaultId: 0,
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
private readonly getWorkerObserver = (resolve: () => void, reject: (error: Error) => void): Observer<IGitLogMessage> => ({
|
|
next: (messageObject) => {
|
|
const { message, meta, level } = messageObject;
|
|
if (typeof meta === 'object' && meta !== null && 'step' in meta) {
|
|
this.popGitErrorNotificationToUser((meta as { step: GitStep }).step, message);
|
|
}
|
|
logger.log(level, this.translateMessage(message), meta);
|
|
},
|
|
error: (error) => {
|
|
this.translateAndLogErrorMessage(error);
|
|
reject(error);
|
|
},
|
|
complete: () => resolve(),
|
|
});
|
|
|
|
public async initWikiGit(wikiFolderPath: string, isSyncedWiki?: boolean, remoteUrl?: string, userInfo?: IGitUserInfos): Promise<void> {
|
|
return await new Promise<void>((resolve, reject) => {
|
|
this.gitWorker?.initWikiGit(wikiFolderPath, isSyncedWiki, remoteUrl, userInfo).subscribe(this.getWorkerObserver(resolve, reject));
|
|
});
|
|
}
|
|
|
|
public async commitAndSync(wikiFolderPath: string, remoteUrl: string, userInfo: IGitUserInfos): Promise<void> {
|
|
return await new Promise<void>((resolve, reject) => {
|
|
this.gitWorker?.commitAndSyncWiki(wikiFolderPath, remoteUrl, userInfo).subscribe(this.getWorkerObserver(resolve, reject));
|
|
});
|
|
}
|
|
|
|
public async clone(remoteUrl: string, repoFolderPath: string, userInfo: IGitUserInfos): Promise<void> {
|
|
return await new Promise<void>((resolve, reject) => {
|
|
this.gitWorker?.cloneWiki(repoFolderPath, remoteUrl, userInfo).subscribe(this.getWorkerObserver(resolve, reject));
|
|
});
|
|
}
|
|
}
|