refactor: move sync logic to sync service

This commit is contained in:
linonetwo 2023-12-31 19:42:25 +08:00
parent 8bf3fcdc1d
commit 5f41b77da9
15 changed files with 240 additions and 143 deletions

View file

@ -5,8 +5,8 @@
See [this 6aedff4b commit](https://github.com/tiddly-gittly/TidGi-Desktop/commit/6aedff4bb2441e692c95aedc57a586953a641615) for example, you need to modify these files:
- [src/preload/common/services.ts](../../src/preload/common/services.ts) to expose it to renderer side for in-wiki plugin access
- [src/services/libs/bindServiceAndProxy.ts](../../src/services/libs/bindServiceAndProxy.ts) for dependency injection in inversifyjs
- [src/services/serviceIdentifier.ts](../../src/services/serviceIdentifier.ts) for IoC id
- [src/services/libs/bindServiceAndProxy.ts](../../src/services/libs/bindServiceAndProxy.ts) for dependency injection in inversifyjs
## Sync service

View file

@ -148,6 +148,10 @@ export enum MetaDataChannel {
pushViewMetaData = 'pushViewMetaData',
}
export enum SyncChannel {
name = 'SyncChannel',
}
export type Channels =
| MainChannel
| AuthenticationChannel
@ -167,4 +171,5 @@ export type Channels =
| WindowChannel
| ThemeChannel
| I18NChannels
| MetaDataChannel;
| MetaDataChannel
| SyncChannel;

View file

@ -16,6 +16,7 @@ import { INativeService, NativeServiceIPCDescriptor } from '@services/native/int
import { INotificationService, NotificationServiceIPCDescriptor } from '@services/notifications/interface';
import { IPagesService, PagesServiceIPCDescriptor } from '@services/pages/interface';
import { IPreferenceService, PreferenceServiceIPCDescriptor } from '@services/preferences/interface';
import { ISyncService, SyncServiceIPCDescriptor } from '@services/sync/interface';
import { ISystemPreferenceService, SystemPreferenceServiceIPCDescriptor } from '@services/systemPreferences/interface';
import { IThemeService, ThemeServiceIPCDescriptor } from '@services/theme/interface';
import { IUpdaterService, UpdaterServiceIPCDescriptor } from '@services/updater/interface';
@ -35,6 +36,7 @@ export const native = createProxy<INativeService>(NativeServiceIPCDescriptor);
export const notification = createProxy<INotificationService>(NotificationServiceIPCDescriptor);
export const pages = createProxy<IPagesService>(PagesServiceIPCDescriptor);
export const preference = createProxy<IPreferenceService>(PreferenceServiceIPCDescriptor);
export const sync = createProxy<ISyncService>(SyncServiceIPCDescriptor);
export const systemPreference = createProxy<ISystemPreferenceService>(SystemPreferenceServiceIPCDescriptor);
export const theme = createProxy<IThemeService>(ThemeServiceIPCDescriptor);
export const updater = createProxy<IUpdaterService>(UpdaterServiceIPCDescriptor);
@ -55,6 +57,7 @@ export const descriptors = {
notification: NotificationServiceIPCDescriptor,
pages: PagesServiceIPCDescriptor,
preference: PreferenceServiceIPCDescriptor,
sync: SyncServiceIPCDescriptor,
systemPreference: SystemPreferenceServiceIPCDescriptor,
theme: ThemeServiceIPCDescriptor,
updater: UpdaterServiceIPCDescriptor,

View file

@ -16,6 +16,7 @@ import { NativeService } from '@services/native';
import { NotificationService } from '@services/notifications';
import { Pages } from '@services/pages';
import { Preference } from '@services/preferences';
import { Sync } from '@services/sync';
import { SystemPreference } from '@services/systemPreferences';
import { ThemeService } from '@services/theme';
import { Updater } from '@services/updater';
@ -46,6 +47,8 @@ import type { IPagesService } from '@services/pages/interface';
import { PagesServiceIPCDescriptor } from '@services/pages/interface';
import type { IPreferenceService } from '@services/preferences/interface';
import { PreferenceServiceIPCDescriptor } from '@services/preferences/interface';
import type { ISyncService } from '@services/sync/interface';
import { SyncServiceIPCDescriptor } from '@services/sync/interface';
import type { ISystemPreferenceService } from '@services/systemPreferences/interface';
import { SystemPreferenceServiceIPCDescriptor } from '@services/systemPreferences/interface';
import type { IThemeService } from '@services/theme/interface';
@ -80,6 +83,7 @@ export function bindServiceAndProxy(): void {
container.bind<IThemeService>(serviceIdentifier.ThemeService).to(ThemeService).inSingletonScope();
container.bind<IUpdaterService>(serviceIdentifier.Updater).to(Updater).inSingletonScope();
container.bind<IViewService>(serviceIdentifier.View).to(View).inSingletonScope();
container.bind<ISyncService>(serviceIdentifier.Sync).to(Sync).inSingletonScope();
container.bind<IWikiGitWorkspaceService>(serviceIdentifier.WikiGitWorkspace).to(WikiGitWorkspace).inSingletonScope();
container.bind<IWikiService>(serviceIdentifier.Wiki).to(Wiki).inSingletonScope();
container.bind<IWindowService>(serviceIdentifier.Window).to(Window).inSingletonScope();
@ -99,6 +103,7 @@ export function bindServiceAndProxy(): void {
const systemPreferenceService = container.get<ISystemPreferenceService>(serviceIdentifier.SystemPreference);
const themeService = container.get<IThemeService>(serviceIdentifier.ThemeService);
const updaterService = container.get<IUpdaterService>(serviceIdentifier.Updater);
const syncService = container.get<ISyncService>(serviceIdentifier.Sync);
const viewService = container.get<IViewService>(serviceIdentifier.View);
const wikiGitWorkspaceService = container.get<IWikiGitWorkspaceService>(serviceIdentifier.WikiGitWorkspace);
const wikiService = container.get<IWikiService>(serviceIdentifier.Wiki);
@ -115,6 +120,7 @@ export function bindServiceAndProxy(): void {
registerProxy(nativeService, NativeServiceIPCDescriptor);
registerProxy(notificationService, NotificationServiceIPCDescriptor);
registerProxy(pagesService, PagesServiceIPCDescriptor);
registerProxy(syncService, SyncServiceIPCDescriptor);
registerProxy(preferenceService, PreferenceServiceIPCDescriptor);
registerProxy(systemPreferenceService, SystemPreferenceServiceIPCDescriptor);
registerProxy(themeService, ThemeServiceIPCDescriptor);

View file

@ -10,6 +10,7 @@ import type { INativeService } from '@services/native/interface';
import type { IPagesService } from '@services/pages/interface';
import type { IPreferenceService } from '@services/preferences/interface';
import serviceIdentifier from '@services/serviceIdentifier';
import { ISyncService } from '@services/sync/interface';
import type { IUpdaterService } from '@services/updater/interface';
import type { IViewService } from '@services/view/interface';
import type { IWikiService } from '@services/wiki/interface';
@ -70,6 +71,9 @@ export class MenuService implements IMenuService {
@lazyInject(serviceIdentifier.WorkspaceView)
private readonly workspaceViewService!: IWorkspaceViewService;
@lazyInject(serviceIdentifier.Sync)
private readonly syncService!: ISyncService;
#menuTemplate?: DeferredMenuItemConstructorOptions[];
private get menuTemplate(): DeferredMenuItemConstructorOptions[] {
if (this.#menuTemplate === undefined) {
@ -302,6 +306,7 @@ export class MenuService implements IMenuService {
window: this.windowService,
workspace: this.workspaceService,
workspaceView: this.workspaceViewService,
sync: this.syncService,
};
// workspace menus
menu.append(new MenuItem({ type: 'separator' }));

View file

@ -9,6 +9,7 @@ export default {
NotificationService: Symbol.for('NotificationService'),
Pages: Symbol.for('Pages'),
Preference: Symbol.for('Preference'),
Sync: Symbol.for('Sync'),
SystemPreference: Symbol.for('SystemPreference'),
ThemeService: Symbol.for('ThemeService'),
Updater: Symbol.for('Updater'),

137
src/services/sync/index.ts Normal file
View file

@ -0,0 +1,137 @@
import { injectable } from 'inversify';
import { WikiChannel } from '@/constants/channels';
import type { IAuthenticationService } from '@services/auth/interface';
import { lazyInject } from '@services/container';
import { ICommitAndSyncConfigs, IGitService } from '@services/git/interface';
import { logger } from '@services/libs/log';
import type { IPreferenceService } from '@services/preferences/interface';
import serviceIdentifier from '@services/serviceIdentifier';
import { SupportedStorageServices } from '@services/types';
import type { IViewService } from '@services/view/interface';
import type { IWikiService } from '@services/wiki/interface';
import { IWorkspace, IWorkspaceService } from '@services/workspaces/interface';
import { IWorkspaceViewService } from '@services/workspacesView/interface';
import { ISyncService } from './interface';
@injectable()
export class Sync implements ISyncService {
@lazyInject(serviceIdentifier.Authentication)
private readonly authService!: IAuthenticationService;
@lazyInject(serviceIdentifier.Preference)
private readonly preferenceService!: IPreferenceService;
@lazyInject(serviceIdentifier.Wiki)
private readonly wikiService!: IWikiService;
@lazyInject(serviceIdentifier.View)
private readonly viewService!: IViewService;
@lazyInject(serviceIdentifier.Git)
private readonly gitService!: IGitService;
@lazyInject(serviceIdentifier.WorkspaceView)
private readonly workspaceViewService!: IWorkspaceViewService;
@lazyInject(serviceIdentifier.Workspace)
private readonly workspaceService!: IWorkspaceService;
public async syncWikiIfNeeded(workspace: IWorkspace): Promise<void> {
const { gitUrl, storageService, backupOnInterval, id, isSubWiki } = workspace;
const userInfo = await this.authService.getStorageServiceUserInfo(storageService);
if (
storageService !== SupportedStorageServices.local &&
typeof gitUrl === 'string' &&
userInfo !== undefined &&
(await this.checkCanSyncDueToNoDraft(id))
) {
const syncOrForcePullConfigs = { remoteUrl: gitUrl, userInfo } satisfies ICommitAndSyncConfigs;
// sync current workspace first
const hasChanges = await this.gitService.syncOrForcePull(workspace, syncOrForcePullConfigs);
if (isSubWiki) {
// after sync this sub wiki, reload its main workspace
const mainWorkspace = this.workspaceService.getMainWorkspace(workspace);
if (hasChanges && mainWorkspace !== undefined) {
await this.workspaceViewService.restartWorkspaceViewService(mainWorkspace.id);
await this.viewService.reloadViewsWebContents(mainWorkspace.id);
}
} else {
// sync all sub workspace
const subWorkspaces = await this.workspaceService.getSubWorkspacesAsList(id);
const subHasChangesPromise = subWorkspaces.map(async (subWorkspace) => {
const { gitUrl: subGitUrl, storageService: subStorageService } = subWorkspace;
// eslint-disable-next-line @typescript-eslint/strict-boolean-expressions
if (!subGitUrl) return false;
const subUserInfo = await this.authService.getStorageServiceUserInfo(subStorageService);
const hasChanges = await this.gitService.syncOrForcePull(subWorkspace, { remoteUrl: subGitUrl, userInfo: subUserInfo });
return hasChanges;
});
const subHasChange = (await Promise.all(subHasChangesPromise)).some(Boolean);
// any of main or sub has changes, reload main workspace
if (hasChanges || subHasChange) {
await this.workspaceViewService.restartWorkspaceViewService(id);
await this.viewService.reloadViewsWebContents(id);
}
}
} else if (backupOnInterval && (await this.checkCanSyncDueToNoDraft(id))) {
// for local workspace, commitOnly, no sync and no force pull.
await this.gitService.commitAndSync(workspace, { commitOnly: true });
}
}
public async checkCanSyncDueToNoDraft(workspaceID: string): Promise<boolean> {
const syncOnlyWhenNoDraft = await this.preferenceService.get('syncOnlyWhenNoDraft');
if (!syncOnlyWhenNoDraft) {
return true;
}
try {
// TODO: check this, seems not working.
const draftTitles = await this.wikiService.wikiOperationInServer(WikiChannel.runFilter, workspaceID, ['[is[draft]]']);
// eslint-disable-next-line @typescript-eslint/strict-boolean-expressions
if (Array.isArray(draftTitles) && draftTitles.length > 0) {
return false;
}
return true;
} catch (error) {
logger.error(
`${(error as Error).message} when checking draft titles. ${
(error as Error).stack ?? ''
}\n This might because it just will throw error when on Windows and App is at background (BrowserView will disappear and not accessible.)`,
);
// when app is on background, might have no draft, because user won't edit it. So just return true
return true;
}
}
/**
* Record<workspaceID, returnValue<setInterval>>
* Set this in wikiStartup, and clear it when wiki is down.
*/
private wikiSyncIntervals: Record<string, ReturnType<typeof setInterval>> = {};
/**
* Trigger git sync interval if needed in config
*/
public async startIntervalSyncIfNeeded(workspace: IWorkspace): Promise<void> {
const { syncOnInterval, backupOnInterval, id } = workspace;
if (syncOnInterval || backupOnInterval) {
const syncDebounceInterval = await this.preferenceService.get('syncDebounceInterval');
this.wikiSyncIntervals[id] = setInterval(async () => {
await this.syncWikiIfNeeded(workspace);
}, syncDebounceInterval);
}
}
public stopIntervalSync(workspaceID: string): void {
if (typeof this.wikiSyncIntervals[workspaceID] === 'number') {
clearInterval(this.wikiSyncIntervals[workspaceID]);
}
}
public clearAllSyncIntervals(): void {
Object.values(this.wikiSyncIntervals).forEach((interval) => {
clearInterval(interval);
});
}
}

View file

@ -0,0 +1,33 @@
import { SyncChannel } from '@/constants/channels';
import { IWorkspace } from '@services/workspaces/interface';
import { ProxyPropertyType } from 'electron-ipc-cat/common';
/**
* Manage sync process and interval sync.
*/
export interface ISyncService {
/**
* Check if there is any draft in current workspace that block us syncing.
* @returns true if can sync, false if cannot sync due to there is a draft in current workspace.
*/
checkCanSyncDueToNoDraft(workspaceID: string): Promise<boolean>;
clearAllSyncIntervals(): void;
startIntervalSyncIfNeeded(workspace: IWorkspace): Promise<void>;
stopIntervalSync(workspaceID: string): void;
/**
* Trigger git sync for a wiki workspace.
* Simply do some check before calling `gitService.syncOrForcePull`, and after that, restart workspaceViewService if needed.
*/
syncWikiIfNeeded(workspace: IWorkspace): Promise<void>;
}
export const SyncServiceIPCDescriptor = {
channel: SyncChannel.name,
properties: {
clearAllSyncIntervals: ProxyPropertyType.Function,
checkCanSyncDueToNoDraft: ProxyPropertyType.Function,
startIntervalSyncIfNeeded: ProxyPropertyType.Function,
stopIntervalSync: ProxyPropertyType.Function,
syncWikiIfNeeded: ProxyPropertyType.Function,
},
};

View file

@ -18,7 +18,6 @@ import type { IGitService, IGitUserInfos } from '@services/git/interface';
import { i18n } from '@services/libs/i18n';
import { getWikiErrorLogFileName, logger, startWikiLogger } from '@services/libs/log';
import serviceIdentifier from '@services/serviceIdentifier';
import { SupportedStorageServices } from '@services/types';
import type { IViewService } from '@services/view/interface';
import type { IWindowService } from '@services/windows/interface';
import { WindowNames } from '@services/windows/WindowProperties';
@ -42,6 +41,7 @@ import { isHtmlWiki } from '@/constants/fileNames';
import { defaultServerIP } from '@/constants/urls';
import { IDatabaseService } from '@services/database/interface';
import { IPreferenceService } from '@services/preferences/interface';
import { ISyncService } from '@services/sync/interface';
import { mapValues } from 'lodash';
import { wikiWorkerStartedEventName } from './constants';
import { IWorkerWikiOperations } from './wikiOperations/executor/wikiOperationInServer';
@ -73,6 +73,9 @@ export class Wiki implements IWikiService {
@lazyInject(serviceIdentifier.WorkspaceView)
private readonly workspaceViewService!: IWorkspaceViewService;
@lazyInject(serviceIdentifier.Sync)
private readonly syncService!: ISyncService;
public async getSubWikiPluginContent(mainWikiPath: string): Promise<ISubWikiPluginContent[]> {
return await getSubWikiPluginContent(mainWikiPath);
}
@ -329,7 +332,7 @@ export class Wiki implements IWikiService {
});
return;
}
clearInterval(this.wikiSyncIntervals[id]);
this.syncService.stopIntervalSync(id);
try {
logger.debug(`worker.beforeExit for ${id}`);
await worker.beforeExit();
@ -597,84 +600,6 @@ export class Wiki implements IWikiService {
return textResult;
}
/**
* Trigger git sync
* Simply do some check before calling `gitService.syncOrForcePull`
*/
private async syncWikiIfNeeded(workspace: IWorkspace): Promise<void> {
const { gitUrl: githubRepoUrl, storageService, backupOnInterval, id } = workspace;
const checkCanSyncDueToNoDraft = async (): Promise<boolean> => {
const syncOnlyWhenNoDraft = await this.preferenceService.get('syncOnlyWhenNoDraft');
if (!syncOnlyWhenNoDraft) {
return true;
}
try {
const draftTitles = await this.wikiOperationInServer(WikiChannel.runFilter, id, ['[is[draft]]']);
// eslint-disable-next-line @typescript-eslint/strict-boolean-expressions
if (Array.isArray(draftTitles) && draftTitles.length > 0) {
return false;
}
return true;
} catch (error) {
logger.error(
`${(error as Error).message} when checking draft titles. ${
(error as Error).stack ?? ''
}\n This might because it just will throw error when on Windows and App is at background (BrowserView will disappear and not accessible.)`,
);
// when app is on background, might have no draft, because user won't edit it. So just return true
return true;
}
};
const userInfo = await this.authService.getStorageServiceUserInfo(storageService);
if (
storageService !== SupportedStorageServices.local &&
typeof githubRepoUrl === 'string' &&
userInfo !== undefined &&
(await checkCanSyncDueToNoDraft())
) {
const hasChanges = await this.gitService.syncOrForcePull(workspace, { remoteUrl: githubRepoUrl, userInfo });
if (hasChanges) {
await this.workspaceViewService.restartWorkspaceViewService(id);
await this.viewService.reloadViewsWebContents(id);
}
} else if (backupOnInterval && (await checkCanSyncDueToNoDraft())) {
// for local workspace, commitOnly, no sync and no force pull.
await this.gitService.commitAndSync(workspace, { commitOnly: true });
}
}
/**
* Record<workspaceID, returnValue<setInterval>>
* Set this in wikiStartup, and clear it when wiki is down.
*/
private wikiSyncIntervals: Record<string, ReturnType<typeof setInterval>> = {};
/**
* Trigger git sync interval if needed in config
*/
private async startIntervalSyncIfNeeded(workspace: IWorkspace): Promise<void> {
const { syncOnInterval, backupOnInterval, id } = workspace;
if (syncOnInterval || backupOnInterval) {
const syncDebounceInterval = await this.preferenceService.get('syncDebounceInterval');
this.wikiSyncIntervals[id] = setInterval(async () => {
await this.syncWikiIfNeeded(workspace);
}, syncDebounceInterval);
}
}
private stopIntervalSync(workspace: IWorkspace): void {
const { id } = workspace;
if (typeof this.wikiSyncIntervals[id] === 'number') {
clearInterval(this.wikiSyncIntervals[id]);
}
}
public clearAllSyncIntervals(): void {
Object.values(this.wikiSyncIntervals).forEach((interval) => {
clearInterval(interval);
});
}
public async wikiStartup(workspace: IWorkspace): Promise<void> {
const { id, isSubWiki, name, mainWikiID, wikiFolderLocation } = workspace;
@ -722,7 +647,7 @@ export class Wiki implements IWikiService {
}
}
}
await this.startIntervalSyncIfNeeded(workspace);
await this.syncService.startIntervalSyncIfNeeded(workspace);
}
public async restartWiki(workspace: IWorkspace): Promise<void> {
@ -731,12 +656,12 @@ export class Wiki implements IWikiService {
// eslint-disable-next-line @typescript-eslint/strict-boolean-expressions
const userName = await this.authService.getUserName(workspace);
this.stopIntervalSync(workspace);
this.syncService.stopIntervalSync(id);
if (!isSubWiki) {
await this.stopWiki(id);
await this.startWiki(id, userName);
}
await this.startIntervalSyncIfNeeded(workspace);
await this.syncService.startIntervalSyncIfNeeded(workspace);
}
public async updateSubWikiPluginContent(mainWikiPath: string, newConfig?: IWorkspace, oldConfig?: IWorkspace): Promise<void> {

View file

@ -28,7 +28,6 @@ export interface IWikiService {
/** return true if wiki does existed and folder is a valid tiddlywiki folder, return error message (a string) if there is an error checking wiki existence */
checkWikiExist(workspace: IWorkspace, options?: { shouldBeMainWiki?: boolean; showDialog?: boolean }): Promise<string | true>;
checkWikiStartLock(wikiFolderLocation: string): boolean;
clearAllSyncIntervals(): void;
cloneSubWiki(
parentFolderLocation: string,
wikiFolderName: string,
@ -106,7 +105,6 @@ export const WikiServiceIPCDescriptor = {
properties: {
callWikiIpcServerRoute: ProxyPropertyType.Function,
checkWikiExist: ProxyPropertyType.Function,
clearAllSyncIntervals: ProxyPropertyType.Function,
cloneSubWiki: ProxyPropertyType.Function,
cloneWiki: ProxyPropertyType.Function,
copyWikiTemplate: ProxyPropertyType.Function,

View file

@ -16,6 +16,7 @@ import type { IWorkspaceViewService } from '@services/workspacesView/interface';
import { IContextService } from '@services/context/interface';
import { i18n } from '@services/libs/i18n';
import { logger } from '@services/libs/log';
import { ISyncService } from '@services/sync/interface';
import { SupportedStorageServices } from '@services/types';
import { updateGhConfig } from '@services/wiki/plugin/ghPages';
import { hasGit } from 'git-sync-js';
@ -48,6 +49,9 @@ export class WikiGitWorkspace implements IWikiGitWorkspaceService {
@lazyInject(serviceIdentifier.NotificationService)
private readonly notificationService!: INotificationService;
@lazyInject(serviceIdentifier.Sync)
private readonly syncService!: ISyncService;
public registerSyncBeforeShutdown(): void {
const listener = async (event: Event): Promise<void> => {
event.preventDefault();
@ -62,11 +66,7 @@ export class WikiGitWorkspace implements IWikiGitWorkspaceService {
if (workspace.readOnlyMode) {
return;
}
const userInfo = await this.authService.getStorageServiceUserInfo(workspace.storageService);
if (userInfo !== undefined && workspace.gitUrl !== null) {
// TODO: use syncWikiIfNeeded
await this.gitService.commitAndSync(workspace, { remoteUrl: workspace.gitUrl, userInfo });
}
await this.syncService.syncWikiIfNeeded(workspace);
}),
]);
}

View file

@ -5,6 +5,7 @@ import { IContextService } from '@services/context/interface';
import { IGitService } from '@services/git/interface';
import type { INativeService } from '@services/native/interface';
import { IPagesService, PageType } from '@services/pages/interface';
import { ISyncService } from '@services/sync/interface';
import { SupportedStorageServices } from '@services/types';
import type { IViewService } from '@services/view/interface';
import type { IWikiService } from '@services/wiki/interface';
@ -23,6 +24,7 @@ interface IWorkspaceMenuRequiredServices {
git: Pick<IGitService, 'commitAndSync'>;
native: Pick<INativeService, 'openURI' | 'openPath' | 'openInEditor' | 'openInGitGuiApp' | 'getLocalHostUrlWithActualInfo'>;
pages: Pick<IPagesService, 'setActivePage' | 'getActivePage'>;
sync: Pick<ISyncService, 'syncWikiIfNeeded'>;
view: Pick<IViewService, 'reloadViewsWebContents' | 'getViewCurrentUrl'>;
wiki: Pick<IWikiService, 'wikiOperationInBrowser' | 'wikiOperationInServer' | 'requestWikiSendActionMessage'>;
wikiGitWorkspace: Pick<IWikiGitWorkspaceService, 'removeWorkspace'>;
@ -130,37 +132,7 @@ export async function getWorkspaceMenuTemplate(
label: t('ContextMenu.SyncNow') + (isOnline ? '' : `(${t('ContextMenu.NoNetworkConnection')})`),
enabled: isOnline,
click: async () => {
if (isSubWiki) {
// TODO: use syncWikiIfNeeded
const hasChanges = await service.git.commitAndSync(workspace, { remoteUrl: gitUrl, userInfo });
if (hasChanges) {
if (mainWikiID === null) {
await service.workspaceView.restartWorkspaceViewService(id);
await service.view.reloadViewsWebContents(id);
} else {
// reload main workspace to reflect change (do this before watch-fs stable)
await service.workspaceView.restartWorkspaceViewService(mainWikiID);
await service.view.reloadViewsWebContents(mainWikiID);
}
}
} else {
// sync all sub workspace
const mainHasChanges = await service.git.commitAndSync(workspace, { remoteUrl: gitUrl, userInfo });
const subWorkspaces = await service.workspace.getSubWorkspacesAsList(id);
const subHasChangesPromise = subWorkspaces.map(async (subWorkspace) => {
const { gitUrl: subGitUrl } = subWorkspace;
// eslint-disable-next-line @typescript-eslint/strict-boolean-expressions
if (!subGitUrl) return false;
const hasChanges = await service.git.commitAndSync(subWorkspace, { remoteUrl: subGitUrl, userInfo });
return hasChanges;
});
const subHasChange = (await Promise.all(subHasChangesPromise)).some(Boolean);
const hasChange = mainHasChanges || subHasChange;
if (hasChange) {
await service.workspaceView.restartWorkspaceViewService(id);
await service.view.reloadViewsWebContents(id);
}
}
await service.sync.syncWikiIfNeeded(workspace);
},
});
}

View file

@ -13,6 +13,7 @@ import path from 'path';
import { BehaviorSubject, Observable } from 'rxjs';
import { map } from 'rxjs/operators';
import { DELAY_MENU_REGISTER } from '@/constants/parameters';
import { getDefaultTidGiUrl } from '@/constants/urls';
import { fixSettingFileWhenError } from '@/helpers/configSetting';
import { IAuthenticationService } from '@services/auth/interface';
@ -33,7 +34,6 @@ import { debouncedSetSettingFile } from './debouncedSetSettingFile';
import type { INewWorkspaceConfig, IWorkspace, IWorkspaceMetaData, IWorkspaceService, IWorkspaceWithMetadata } from './interface';
import { registerMenu } from './registerMenu';
import { workspaceSorter } from './utils';
import { DELAY_MENU_REGISTER } from '@/constants/parameters';
@injectable()
export class Workspace implements IWorkspaceService {
@ -212,6 +212,16 @@ export class Workspace implements IWorkspaceService {
}
}
public getMainWorkspace(subWorkspace: IWorkspace): IWorkspace | undefined {
const { mainWikiID, isSubWiki, mainWikiToLink } = subWorkspace;
if (!isSubWiki) return undefined;
if (mainWikiID) return this.getSync(mainWikiID);
const mainWorkspace = (this.getWorkspacesAsListSync() ?? []).find(
(workspaceToSearch) => mainWikiToLink === workspaceToSearch.wikiFolderLocation,
);
return mainWorkspace;
}
/**
* Pure function that make sure workspace setting is consistent, or doing migration across updates
* @param workspaceToSanitize User input workspace or loaded workspace, that may contains bad values
@ -226,9 +236,7 @@ export class Workspace implements IWorkspaceService {
const fixingValues: Partial<IWorkspace> = {};
// we add mainWikiID in creation, we fix this value for old existed workspaces
if (workspaceToSanitize.isSubWiki && !workspaceToSanitize.mainWikiID) {
const mainWorkspace = (this.getWorkspacesAsListSync() ?? []).find(
(workspaceToSearch) => workspaceToSanitize.mainWikiToLink === workspaceToSearch.wikiFolderLocation,
);
const mainWorkspace = this.getMainWorkspace(workspaceToSanitize);
if (mainWorkspace !== undefined) {
fixingValues.mainWikiID = mainWorkspace.id;
}

View file

@ -178,6 +178,11 @@ export interface IWorkspaceService {
getAllMetaData: () => Promise<Record<string, Partial<IWorkspaceMetaData>>>;
getByWikiFolderLocation(wikiFolderLocation: string): Promise<IWorkspace | undefined>;
getFirstWorkspace: () => Promise<IWorkspace | undefined>;
/**
* Get parent workspace of a subWorkspace, if the workspace you provided is a main workspace, return undefined.
* @param subWorkspace your workspace object
*/
getMainWorkspace(subWorkspace: IWorkspace): IWorkspace | undefined;
getMetaData: (id: string) => Promise<Partial<IWorkspaceMetaData>>;
getNextWorkspace: (id: string) => Promise<IWorkspace | undefined>;
getPreviousWorkspace: (id: string) => Promise<IWorkspace | undefined>;
@ -206,8 +211,8 @@ export interface IWorkspaceService {
export const WorkspaceServiceIPCDescriptor = {
channel: WorkspaceChannel.name,
properties: {
countWorkspaces: ProxyPropertyType.Function,
clearActiveWorkspace: ProxyPropertyType.Function,
countWorkspaces: ProxyPropertyType.Function,
create: ProxyPropertyType.Function,
get: ProxyPropertyType.Function,
get$: ProxyPropertyType.Function$,
@ -215,6 +220,7 @@ export const WorkspaceServiceIPCDescriptor = {
getAllMetaData: ProxyPropertyType.Function,
getByName: ProxyPropertyType.Function,
getFirstWorkspace: ProxyPropertyType.Function,
getMainWorkspace: ProxyPropertyType.Function,
getMetaData: ProxyPropertyType.Function,
getNextWorkspace: ProxyPropertyType.Function,
getPreviousWorkspace: ProxyPropertyType.Function,

View file

@ -15,7 +15,6 @@ import type { IGitService } from '@services/git/interface';
import { i18n } from '@services/libs/i18n';
import { logger } from '@services/libs/log';
import type { IMenuService } from '@services/menu/interface';
import { INativeService } from '@services/native/interface';
import type { IPreferenceService } from '@services/preferences/interface';
import serviceIdentifier from '@services/serviceIdentifier';
import { SupportedStorageServices } from '@services/types';
@ -26,6 +25,7 @@ import { WindowNames } from '@services/windows/WindowProperties';
import type { IWorkspace, IWorkspaceService } from '@services/workspaces/interface';
import { DELAY_MENU_REGISTER } from '@/constants/parameters';
import { ISyncService } from '@services/sync/interface';
import type { IInitializeWorkspaceOptions, IWorkspaceViewService } from './interface';
import { registerMenu } from './registerMenu';
@ -61,8 +61,8 @@ export class WorkspaceView implements IWorkspaceViewService {
@lazyInject(serviceIdentifier.WorkspaceView)
private readonly workspaceViewService!: IWorkspaceViewService;
@lazyInject(serviceIdentifier.NativeService)
private readonly nativeService!: INativeService;
@lazyInject(serviceIdentifier.Sync)
private readonly syncService!: ISyncService;
constructor() {
setTimeout(() => {
@ -115,8 +115,11 @@ export class WorkspaceView implements IWorkspaceViewService {
}
}
const syncGitWhenInitializeWorkspaceView = async () => {
const { wikiFolderLocation, gitUrl: githubRepoUrl, storageService } = workspace;
const { wikiFolderLocation, gitUrl: githubRepoUrl, storageService, isSubWiki } = workspace;
// we are using syncWikiIfNeeded that handles recursive sync for all subwiki, so we only need to pass main wiki to it in this method.
if (isSubWiki) {
return;
}
// get sync process ready
try {
if (workspace.syncOnStartup && storageService !== SupportedStorageServices.local && syncImmediately) {
@ -129,7 +132,11 @@ export class WorkspaceView implements IWorkspaceViewService {
throw new Error(i18n.t(`Error.MainWindowMissing`));
}
const userInfo = await this.authService.getStorageServiceUserInfo(workspace.storageService);
if (userInfo === undefined) {
// eslint-disable-next-line @typescript-eslint/strict-boolean-expressions
if (userInfo?.accessToken) {
// sync in non-blocking way
void this.syncService.syncWikiIfNeeded(workspace);
} else {
// user not login into Github or something else
void dialog.showMessageBox(mainWindow, {
title: i18n.t('Dialog.StorageServiceUserInfoNoFound'),
@ -138,15 +145,6 @@ export class WorkspaceView implements IWorkspaceViewService {
cancelId: 0,
defaultId: 0,
});
} else {
// sync in non-blocking way
// TODO: use syncWikiIfNeeded
void this.gitService.commitAndSync(workspace, { remoteUrl: githubRepoUrl, userInfo }).then(async (hasChanges) => {
if (hasChanges) {
await this.workspaceViewService.restartWorkspaceViewService(workspace.id);
await this.viewService.reloadViewsWebContents(workspace.id);
}
});
}
}
} catch (error) {