diff --git a/src/app/imex/sync/sync-config.service.ts b/src/app/imex/sync/sync-config.service.ts index 6b68aa6c04..859b48dd52 100644 --- a/src/app/imex/sync/sync-config.service.ts +++ b/src/app/imex/sync/sync-config.service.ts @@ -3,7 +3,7 @@ import { SyncProviderManager } from '../../op-log/sync-providers/provider-manage import { GlobalConfigService } from '../../features/config/global-config.service'; import { combineLatest, from, Observable, of } from 'rxjs'; import { SyncConfig } from '../../features/config/global-config.model'; -import { first, switchMap, tap } from 'rxjs/operators'; +import { switchMap, tap } from 'rxjs/operators'; import { CurrentProviderPrivateCfg, PrivateCfgByProviderId, @@ -14,7 +14,6 @@ import { SyncLog } from '../../core/log'; import { clearSessionKeyCache } from '../../op-log/encryption/encryption'; import { SuperSyncPrivateCfg } from '../../op-log/sync-providers/super-sync/super-sync.model'; import { SyncWrapperService } from './sync-wrapper.service'; -import { LocalFileSyncPrivateCfg } from '../../op-log/core/types/sync.types'; // Maps sync providers to their corresponding form field in SyncConfig // Dropbox is null because it doesn't store settings in the form (uses OAuth) @@ -103,19 +102,6 @@ export class SyncConfigService { private _lastSettings: SyncConfig | null = null; - constructor() { - // One-time startup migration: move syncFolderPath from legacy global config to credential - // store. Must run at startup (not only when sync settings UI is open) so that the app - // correctly finds the configured folder after a restart. Fixes: #5455 (Mac restart). - this._globalConfigService.sync$ - .pipe(first((cfg) => !!cfg?.localFileSync?.syncFolderPath)) - .subscribe((cfg) => { - this._migrateLegacyLocalFileSyncPath(cfg.localFileSync!.syncFolderPath!).catch( - (e) => SyncLog.err('SyncConfigService: LocalFile path migration failed', e), - ); - }); - } - private _deriveEncryptionState( baseConfig: SyncConfig, currentProviderCfg: CurrentProviderPrivateCfg | null, @@ -423,47 +409,4 @@ export class SyncConfigService { clearSessionKeyCache(); } } - - /** - * Migrates syncFolderPath from the legacy global config storage to SyncCredentialStore. - * - * Older versions stored the LocalFile sync folder path in globalConfig.sync.localFileSync. - * The current architecture stores it in SyncCredentialStore (IndexedDB 'sup-sync'). - * This one-time migration preserves the user's configured folder across upgrades. - * Fixes: Mac forgets sync folder on restart (#5455). - */ - private async _migrateLegacyLocalFileSyncPath(legacyPath: string): Promise { - SyncLog.normal( - 'SyncConfigService: Checking for legacy LocalFile syncFolderPath migration', - ); - const provider = await this._providerManager.getProviderById( - SyncProviderId.LocalFile, - ); - if (!provider) { - // Provider not registered (e.g. web build). Clear the stale field anyway so - // it never triggers migration again and doesn't silently persist. - SyncLog.warn( - 'SyncConfigService: LocalFile provider not found during migration; clearing legacy field', - ); - } else { - const existingCfg = - (await provider.privateCfg.load()) as LocalFileSyncPrivateCfg | null; - if (!existingCfg?.syncFolderPath) { - SyncLog.normal( - 'SyncConfigService: Migrating LocalFile syncFolderPath from global config to credential store', - ); - await provider.privateCfg.upsertPartial({ - syncFolderPath: legacyPath, - } as LocalFileSyncPrivateCfg); - } - } - // Always clear the legacy path from global config so this migration never re-runs. - // Spread DEFAULT to avoid shallow-merge data loss on any other localFileSync fields. - this._globalConfigService.updateSection('sync', { - localFileSync: { - ...DEFAULT_GLOBAL_CONFIG.sync.localFileSync, - syncFolderPath: null, - }, - }); - } } diff --git a/src/app/imex/sync/sync-trigger.service.ts b/src/app/imex/sync/sync-trigger.service.ts index 06543ceb4f..69db8ea069 100644 --- a/src/app/imex/sync/sync-trigger.service.ts +++ b/src/app/imex/sync/sync-trigger.service.ts @@ -206,7 +206,10 @@ export class SyncTriggerService { shareReplay(1), ); - getSyncTrigger$(syncInterval: number = SYNC_DEFAULT_AUDIT_TIME): Observable { + getSyncTrigger$( + syncInterval: number = SYNC_DEFAULT_AUDIT_TIME, + useIntervalTimer = false, + ): Observable { const _immediateSyncTrigger$: Observable = IS_ANDROID_WEB_VIEW ? // ANDROID ONLY merge( @@ -233,9 +236,11 @@ export class SyncTriggerService { this._onElectronResumeTrigger$, // Periodic interval timer: fires every syncInterval ms to detect external file // changes (e.g. Syncthing on Linux) even without user activity. - // Applies to Electron desktop and web/PWA (not Android, which has its own timer). + // Only for file-based providers — SuperSync uses WebSocket push and doesn't need polling. // Fixes: Linux auto-sync ignoring interval (#4783) - timer(syncInterval, syncInterval).pipe(mapTo('I_INTERVAL_TIMER')), + ...(useIntervalTimer + ? [timer(syncInterval, syncInterval).pipe(mapTo('I_INTERVAL_TIMER'))] + : []), ); return merge( // once immediately diff --git a/src/app/imex/sync/sync.effects.ts b/src/app/imex/sync/sync.effects.ts index 3c0acb7d91..62dc1983b8 100644 --- a/src/app/imex/sync/sync.effects.ts +++ b/src/app/imex/sync/sync.effects.ts @@ -135,10 +135,14 @@ export class SyncEffects { combineLatest([ this._syncWrapperService.isEnabledAndReady$, this._syncWrapperService.syncInterval$, + this._syncWrapperService.syncProviderId$, ]).pipe( - switchMap(([isEnabledAndReady, syncInterval]) => + switchMap(([isEnabledAndReady, syncInterval, providerId]) => isEnabledAndReady && syncInterval - ? this._syncTriggerService.getSyncTrigger$(syncInterval) + ? this._syncTriggerService.getSyncTrigger$( + syncInterval, + providerId !== SyncProviderId.SuperSync, + ) : EMPTY, ), ), diff --git a/src/main.ts b/src/main.ts index 9b4bfc67fe..048c3d4487 100644 --- a/src/main.ts +++ b/src/main.ts @@ -81,7 +81,6 @@ import { Log } from './app/core/log'; import { OperationWriteFlushService } from './app/op-log/sync/operation-write-flush.service'; import { PluginOAuthRedirectHandler } from './app/plugins/oauth/plugin-oauth-redirect.handler'; import { OAuthCallbackHandlerService } from './app/imex/sync/oauth-callback-handler.service'; -import { SyncConfigService } from './app/imex/sync/sync-config.service'; import { GlobalConfigService } from './app/features/config/global-config.service'; import { LocaleDatePipe } from './app/ui/pipes/locale-date.pipe'; import { DateTimeFormatService } from './app/core/date-time-format/date-time-format.service'; @@ -281,17 +280,6 @@ bootstrapApplication(AppComponent, { deps: [OAuthCallbackHandlerService], multi: true, }, - // Ensure SyncConfigService is instantiated at bootstrap so the startup migration - // (syncFolderPath from global config → credential store) runs on every app launch, - // not only when the sync settings UI is opened. Fixes: #5455 (Mac restart). - { - provide: APP_INITIALIZER, - useFactory: (_svc: SyncConfigService) => { - return () => {}; - }, - deps: [SyncConfigService], - multi: true, - }, // Note: ImmediateUploadService now initializes itself in constructor // after DataInitStateService.isAllDataLoadedInitially$ fires to avoid // race condition where upload attempts happen before sync config is loaded