refactor: make major git operations run in a worker_threads

This commit is contained in:
tiddlygit-test 2021-05-04 15:22:35 +08:00
parent 5b4a9e0ec5
commit 7c18e0a83b
5 changed files with 126 additions and 70 deletions

View 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);

View file

@ -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));
});
}
}

View file

@ -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>;

View file

@ -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';

View file

@ -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 ?? ''}`;