mirror of
https://github.com/tiddly-gittly/TidGi-Desktop.git
synced 2025-12-15 15:10:31 -08:00
refactor: make major git operations run in a worker_threads
This commit is contained in:
parent
5b4a9e0ec5
commit
7c18e0a83b
5 changed files with 126 additions and 70 deletions
82
src/services/git/gitWorker.ts
Normal file
82
src/services/git/gitWorker.ts
Normal file
|
|
@ -0,0 +1,82 @@
|
|||
/* eslint-disable @typescript-eslint/no-misused-promises */
|
||||
import { expose } from 'threads/worker';
|
||||
import { Observable } from 'rxjs';
|
||||
import { clone, commitAndSync, GitStep, ILoggerContext, initGit } from 'git-sync-js';
|
||||
import { IGitLogMessage, IGitUserInfos } from './interface';
|
||||
import { defaultGitInfo } from './defaultGitInfo';
|
||||
import { WikiChannel } from '@/constants/channels';
|
||||
|
||||
function initWikiGit(wikiFolderPath: string, isSyncedWiki?: boolean, remoteUrl?: string, userInfo?: IGitUserInfos): Observable<IGitLogMessage> {
|
||||
return new Observable<IGitLogMessage>((observer) => {
|
||||
void initGit({
|
||||
dir: wikiFolderPath,
|
||||
remoteUrl,
|
||||
syncImmediately: isSyncedWiki,
|
||||
userInfo: { ...defaultGitInfo, ...userInfo },
|
||||
logger: {
|
||||
log: (message: string, context: ILoggerContext): unknown =>
|
||||
observer.next({ message, level: 'log', meta: { callerFunction: 'initWikiGit', ...context } }),
|
||||
warn: (message: string, context: ILoggerContext): unknown =>
|
||||
observer.next({ message, level: 'warn', meta: { callerFunction: 'initWikiGit', ...context } }),
|
||||
info: (message: GitStep, context: ILoggerContext): void => {
|
||||
observer.next({ message, level: 'warn', meta: { handler: WikiChannel.syncProgress, callerFunction: 'initWikiGit', ...context } });
|
||||
},
|
||||
},
|
||||
}).then(
|
||||
() => observer.complete(),
|
||||
(error) => observer.error(error),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {string} wikiFolderPath
|
||||
* @param {string} remoteUrl
|
||||
* @param {{ login: string, email: string, accessToken: string }} userInfo
|
||||
*/
|
||||
function commitAndSyncWiki(wikiFolderPath: string, remoteUrl: string, userInfo: IGitUserInfos): Observable<IGitLogMessage> {
|
||||
return new Observable<IGitLogMessage>((observer) => {
|
||||
void commitAndSync({
|
||||
dir: wikiFolderPath,
|
||||
remoteUrl,
|
||||
userInfo: { ...defaultGitInfo, ...userInfo },
|
||||
logger: {
|
||||
log: (message: string, context: ILoggerContext): unknown =>
|
||||
observer.next({ message, level: 'log', meta: { callerFunction: 'commitAndSync', ...context } }),
|
||||
warn: (message: string, context: ILoggerContext): unknown =>
|
||||
observer.next({ message, level: 'warn', meta: { callerFunction: 'commitAndSync', ...context } }),
|
||||
info: (message: GitStep, context: ILoggerContext): void => {
|
||||
observer.next({ message, level: 'warn', meta: { handler: WikiChannel.syncProgress, callerFunction: 'commitAndSync', ...context } });
|
||||
},
|
||||
},
|
||||
}).then(
|
||||
() => observer.complete(),
|
||||
(error) => observer.error(error),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
function cloneWiki(remoteUrl: string, repoFolderPath: string, userInfo: IGitUserInfos): Observable<IGitLogMessage> {
|
||||
return new Observable<IGitLogMessage>((observer) => {
|
||||
void clone({
|
||||
dir: repoFolderPath,
|
||||
remoteUrl,
|
||||
userInfo: { ...defaultGitInfo, ...userInfo },
|
||||
logger: {
|
||||
log: (message: string, context: ILoggerContext): unknown => observer.next({ message, level: 'log', meta: { callerFunction: 'clone', ...context } }),
|
||||
warn: (message: string, context: ILoggerContext): unknown => observer.next({ message, level: 'warn', meta: { callerFunction: 'clone', ...context } }),
|
||||
info: (message: GitStep, context: ILoggerContext): void => {
|
||||
observer.next({ message, level: 'warn', meta: { handler: WikiChannel.syncProgress, callerFunction: 'clone', ...context } });
|
||||
},
|
||||
},
|
||||
}).then(
|
||||
() => observer.complete(),
|
||||
(error) => observer.error(error),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
const gitWorker = { initWikiGit, commitAndSyncWiki, cloneWiki };
|
||||
export type GitWorker = typeof gitWorker;
|
||||
expose(gitWorker);
|
||||
|
|
@ -5,42 +5,45 @@ import {
|
|||
AssumeSyncError,
|
||||
CantSyncGitNotInitializedError,
|
||||
CantSyncInSpecialGitStateAutoFixFailed,
|
||||
clone,
|
||||
commitAndSync,
|
||||
getModifiedFileList,
|
||||
getRemoteUrl,
|
||||
GitPullPushError,
|
||||
GitStep,
|
||||
ILoggerContext,
|
||||
initGit,
|
||||
ModifiedFileList,
|
||||
SyncParameterMissingError,
|
||||
SyncScriptIsInDeadLoopError,
|
||||
} 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 { IGitService, IGitUserInfos } from './interface';
|
||||
import { defaultGitInfo } from './defaultGitInfo';
|
||||
import { IGitLogMessage, IGitService, IGitUserInfos } from './interface';
|
||||
import { WikiChannel } from '@/constants/channels';
|
||||
import { GitWorker } from './gitWorker';
|
||||
import { Observer } from 'rxjs';
|
||||
|
||||
@injectable()
|
||||
export class Git implements IGitService {
|
||||
disableSyncOnDevelopment = true;
|
||||
private gitWorker?: ModuleThread<GitWorker>;
|
||||
|
||||
constructor(
|
||||
@inject(serviceIdentifier.View) private readonly viewService: IViewService,
|
||||
@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> {
|
||||
this.gitWorker = await spawn<GitWorker>(new Worker('./gitWorker.ts'));
|
||||
}
|
||||
|
||||
public debounceCommitAndSync: (wikiFolderPath: string, remoteUrl: string, userInfo: IGitUserInfos) => Promise<void> | undefined;
|
||||
|
||||
public async getWorkspacesRemote(wikiFolderPath: string): Promise<string> {
|
||||
|
|
@ -167,7 +170,7 @@ export class Git implements IGitService {
|
|||
}
|
||||
}
|
||||
|
||||
private translateErrorMessage(error: Error): void {
|
||||
private translateAndLogErrorMessage(error: Error): void {
|
||||
logger.error(error?.message ?? error);
|
||||
if (error instanceof AssumeSyncError) {
|
||||
error.message = i18n.t('Log.SynchronizationFailed');
|
||||
|
|
@ -186,67 +189,32 @@ export class Git implements IGitService {
|
|||
throw error;
|
||||
}
|
||||
|
||||
public async initWikiGit(wikiFolderPath: string, isMainWiki: boolean, isSyncedWiki?: boolean, remoteUrl?: string, userInfo?: IGitUserInfos): Promise<void> {
|
||||
try {
|
||||
await initGit({
|
||||
dir: wikiFolderPath,
|
||||
remoteUrl,
|
||||
syncImmediately: isSyncedWiki,
|
||||
userInfo: { ...defaultGitInfo, ...userInfo },
|
||||
logger: {
|
||||
log: (message: string, context: ILoggerContext): unknown => logger.info(message, { callerFunction: 'initWikiGit', ...context }),
|
||||
warn: (message: string, context: ILoggerContext): unknown => logger.warn(message, { callerFunction: 'initWikiGit', ...context }),
|
||||
info: (message: GitStep, context: ILoggerContext): void => {
|
||||
logger.notice(this.translateMessage(message), { handler: WikiChannel.syncProgress, callerFunction: 'initWikiGit', ...context });
|
||||
},
|
||||
},
|
||||
});
|
||||
} catch (error) {
|
||||
this.translateErrorMessage(error);
|
||||
}
|
||||
private readonly getWorkerObserver = (resolve: () => void, reject: (error: Error) => void): Observer<IGitLogMessage> => ({
|
||||
next: (message) => {
|
||||
logger.log(message.level, this.translateMessage(message.message), 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));
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {string} wikiFolderPath
|
||||
* @param {string} remoteUrl
|
||||
* @param {{ login: string, email: string, accessToken: string }} userInfo
|
||||
*/
|
||||
public async commitAndSync(wikiFolderPath: string, remoteUrl: string, userInfo: IGitUserInfos): Promise<void> {
|
||||
try {
|
||||
await commitAndSync({
|
||||
dir: wikiFolderPath,
|
||||
remoteUrl,
|
||||
userInfo: { ...defaultGitInfo, ...userInfo },
|
||||
logger: {
|
||||
log: (message: string, context: ILoggerContext): unknown => logger.info(message, { callerFunction: 'commitAndSync', ...context }),
|
||||
warn: (message: string, context: ILoggerContext): unknown => logger.warn(message, { callerFunction: 'commitAndSync', ...context }),
|
||||
info: (message: GitStep, context: ILoggerContext): void => {
|
||||
logger.notice(this.translateMessage(message), { handler: WikiChannel.syncProgress, callerFunction: 'commitAndSync', ...context });
|
||||
},
|
||||
},
|
||||
});
|
||||
} catch (error) {
|
||||
this.translateErrorMessage(error);
|
||||
}
|
||||
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> {
|
||||
try {
|
||||
await clone({
|
||||
dir: repoFolderPath,
|
||||
remoteUrl,
|
||||
userInfo: { ...defaultGitInfo, ...userInfo },
|
||||
logger: {
|
||||
log: (message: string, context: ILoggerContext): unknown => logger.info(message, { callerFunction: 'clone', ...context }),
|
||||
warn: (message: string, context: ILoggerContext): unknown => logger.warn(message, { callerFunction: 'clone', ...context }),
|
||||
info: (message: GitStep, context: ILoggerContext): void => {
|
||||
logger.notice(this.translateMessage(message), { handler: WikiChannel.syncProgress, callerFunction: 'clone', ...context });
|
||||
},
|
||||
},
|
||||
});
|
||||
} catch (error) {
|
||||
this.translateErrorMessage(error);
|
||||
}
|
||||
return await new Promise<void>((resolve, reject) => {
|
||||
this.gitWorker?.cloneWiki(repoFolderPath, remoteUrl, userInfo).subscribe(this.getWorkerObserver(resolve, reject));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,6 +14,12 @@ export interface IGitUserInfosWithoutToken {
|
|||
gitUserName: string;
|
||||
}
|
||||
|
||||
export interface IGitLogMessage {
|
||||
level: 'log' | 'warn' | 'notice';
|
||||
message: string;
|
||||
meta: unknown;
|
||||
}
|
||||
|
||||
/**
|
||||
* System Preferences are not stored in storage but stored in macOS Preferences.
|
||||
* It can be retrieved and changed using Electron APIs
|
||||
|
|
@ -28,8 +34,8 @@ export interface IGitService {
|
|||
/**
|
||||
* Run git init in a folder, prepare remote origin if isSyncedWiki
|
||||
*/
|
||||
initWikiGit(wikiFolderPath: string, isMainWiki: boolean, isSyncedWiki: true, remoteUrl: string, userInfo: IGitUserInfos): Promise<void>;
|
||||
initWikiGit(wikiFolderPath: string, isMainWiki: boolean, isSyncedWiki?: false): Promise<void>;
|
||||
initWikiGit(wikiFolderPath: string, isSyncedWiki: true, remoteUrl: string, userInfo: IGitUserInfos): Promise<void>;
|
||||
initWikiGit(wikiFolderPath: string, isSyncedWiki?: false): Promise<void>;
|
||||
commitAndSync(wikiFolderPath: string, remoteUrl: string, userInfo: IGitUserInfos): Promise<void>;
|
||||
/** Inspect git's remote url from folder's .git config */
|
||||
getWorkspacesRemote(wikiFolderPath: string): Promise<string>;
|
||||
|
|
|
|||
|
|
@ -5,8 +5,8 @@ import { injectable } from 'inversify';
|
|||
import { delay } from 'bluebird';
|
||||
import fs from 'fs-extra';
|
||||
import path from 'path';
|
||||
import { spawn, Thread, Worker } from 'threads';
|
||||
import type { ModuleThread, WorkerEvent } from 'threads/dist/types/master';
|
||||
import { spawn, Thread, Worker, ModuleThread } from 'threads';
|
||||
import type { WorkerEvent } from 'threads/dist/types/master';
|
||||
import { dialog } from 'electron';
|
||||
import chokidar from 'chokidar';
|
||||
import { trim, compact, debounce } from 'lodash';
|
||||
|
|
|
|||
|
|
@ -36,14 +36,14 @@ export class WikiGitWorkspace implements IWikiGitWorkspaceService {
|
|||
try {
|
||||
if (isSyncedWiki) {
|
||||
if (typeof gitUrl === 'string' && userInfo !== undefined) {
|
||||
await this.gitService.initWikiGit(wikiFolderLocation, !isSubWiki, isSyncedWiki, gitUrl, userInfo);
|
||||
await this.gitService.initWikiGit(wikiFolderLocation, isSyncedWiki, gitUrl, userInfo);
|
||||
} else {
|
||||
throw new Error(
|
||||
`E-1-1 SyncedWiki gitUrl is ${gitUrl ?? 'undefined'} , userInfo is ${userInfo === undefined ? JSON.stringify(userInfo) : 'undefined'}`,
|
||||
);
|
||||
}
|
||||
} else {
|
||||
await this.gitService.initWikiGit(wikiFolderLocation, !isSubWiki, false);
|
||||
await this.gitService.initWikiGit(wikiFolderLocation, false);
|
||||
}
|
||||
} catch (error) {
|
||||
const errorMessage = `initWikiGitTransaction failed, ${(error as Error).message} ${(error as Error).stack ?? ''}`;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue