diff --git a/src/apps/main/background-processes/sync-engine.ts b/src/apps/main/background-processes/sync-engine.ts index 122d927c5..f8d85fada 100644 --- a/src/apps/main/background-processes/sync-engine.ts +++ b/src/apps/main/background-processes/sync-engine.ts @@ -218,6 +218,14 @@ export function updateSyncEngine() { } } +export function fallbackSyncEngine() { + try { + worker?.webContents.send('FALLBACK_SYNC_ENGINE_PROCESS'); + } catch (err) { + Logger.error(err); + } +} + eventBus.on('USER_LOGGED_OUT', stopAndClearSyncEngineWatcher); eventBus.on('USER_WAS_UNAUTHORIZED', stopAndClearSyncEngineWatcher); eventBus.on('INITIAL_SYNC_READY', spawnSyncEngineWorker); diff --git a/src/apps/main/preload.d.ts b/src/apps/main/preload.d.ts index bd484f6ab..63c33789c 100644 --- a/src/apps/main/preload.d.ts +++ b/src/apps/main/preload.d.ts @@ -167,5 +167,7 @@ declare interface Window { startRemoteSync: () => Promise; openUrl: (url: string) => Promise; getPreferredAppLanguage: () => Promise>; + syncManually: () => Promise; + getRecentlywasSyncing: () => Promise; }; } diff --git a/src/apps/main/preload.js b/src/apps/main/preload.js index e988efa87..7c6c6436d 100644 --- a/src/apps/main/preload.js +++ b/src/apps/main/preload.js @@ -296,5 +296,12 @@ contextBridge.exposeInMainWorld('electron', { getPreferredAppLanguage() { return ipcRenderer.invoke('APP:PREFERRED_LANGUAGE'); }, + syncManually() { + return ipcRenderer.invoke('SYNC_MANUALLY'); + }, + getRecentlywasSyncing() { + return ipcRenderer.invoke('CHECK_SYNC_IN_PROGRESS'); + }, + path, }); diff --git a/src/apps/main/remote-sync/RemoteSyncManager.ts b/src/apps/main/remote-sync/RemoteSyncManager.ts index 049056eeb..3e46504f9 100644 --- a/src/apps/main/remote-sync/RemoteSyncManager.ts +++ b/src/apps/main/remote-sync/RemoteSyncManager.ts @@ -6,6 +6,7 @@ import { RemoteSyncedFile, SyncConfig, SYNC_OFFSET_MS, + WAITING_AFTER_SYNCING } from './helpers'; import { reportError } from '../bug-report/service'; @@ -24,6 +25,8 @@ export class RemoteSyncManager { > = []; private totalFilesSynced = 0; private totalFoldersSynced = 0; + private lastSyncingFinishedTimestamp: Date | null = null; + constructor( private db: { files: DatabaseCollectionAdapter; @@ -47,10 +50,15 @@ export class RemoteSyncManager { if (typeof callback !== 'function') return; this.onStatusChangeCallbacks.push(callback); } + getSyncStatus(): RemoteSyncStatus { return this.status; } + private getLastSyncingFinishedTimestamp() { + return this.lastSyncingFinishedTimestamp; + } + /** * Check if the RemoteSyncManager is in SYNCED status * @@ -60,10 +68,22 @@ export class RemoteSyncManager { return this.status === 'SYNCED'; } + /** + * Consult if recently the RemoteSyncManager was syncing + * @returns True if the RemoteSyncManager was syncing recently + * @returns False if the RemoteSyncManager was not syncing recently + */ + recentlyWasSyncing() { + const passedTime = Date.now() - ( this.getLastSyncingFinishedTimestamp()?.getTime() ?? Date.now() ); + return passedTime < WAITING_AFTER_SYNCING; + } + resetRemoteSync() { this.changeStatus('IDLE'); this.filesSyncStatus = 'IDLE'; this.foldersSyncStatus = 'IDLE'; + this._placeholdersStatus = 'IDLE'; + this.lastSyncingFinishedTimestamp = null; this.totalFilesSynced = 0; this.totalFoldersSynced = 0; } @@ -148,6 +168,7 @@ export class RemoteSyncManager { return true; } private changeStatus(newStatus: RemoteSyncStatus) { + this.addLastSyncingFinishedTimestamp(); if (newStatus === this.status) return; Logger.info(`RemoteSyncManager ${this.status} -> ${newStatus}`); this.status = newStatus; @@ -157,6 +178,12 @@ export class RemoteSyncManager { }); } + private addLastSyncingFinishedTimestamp() { + if (this.status !== 'SYNCING') return; + Logger.info('Adding last syncing finished timestamp'); + this.lastSyncingFinishedTimestamp = new Date(); + } + private checkRemoteSyncStatus() { if (this._placeholdersStatus === 'SYNCING') { this.changeStatus('SYNCING'); diff --git a/src/apps/main/remote-sync/handlers.ts b/src/apps/main/remote-sync/handlers.ts index c4462f76c..9dc854c67 100644 --- a/src/apps/main/remote-sync/handlers.ts +++ b/src/apps/main/remote-sync/handlers.ts @@ -2,14 +2,17 @@ import eventBus from '../event-bus'; import { RemoteSyncManager } from './RemoteSyncManager'; import { DriveFilesCollection } from '../database/collections/DriveFileCollection'; import { DriveFoldersCollection } from '../database/collections/DriveFolderCollection'; -import { clearRemoteSyncStore } from './helpers'; +import { clearRemoteSyncStore, RemoteSyncStatus } from './helpers'; import { getNewTokenClient } from '../../shared/HttpClient/main-process-client'; import Logger from 'electron-log'; import { ipcMain } from 'electron'; import { reportError } from '../bug-report/service'; import { sleep } from '../util'; import { broadcastToWindows } from '../windows'; -import { updateSyncEngine } from '../background-processes/sync-engine'; +import { + updateSyncEngine, + fallbackSyncEngine, +} from '../background-processes/sync-engine'; let initialSyncReady = false; const driveFilesCollection = new DriveFilesCollection(); @@ -77,14 +80,27 @@ ipcMain.handle('get-remote-sync-status', () => remoteSyncManager.getSyncStatus() ); -eventBus.on('RECEIVED_REMOTE_CHANGES', async () => { +export async function updateRemoteSync(): Promise { // Wait before checking for updates, could be possible // that we received the notification, but if we check // for new data we don't receive it await sleep(2_000); - await remoteSyncManager.startRemoteSync(); updateSyncEngine(); +} +export async function fallbackRemoteSync(): Promise { + await sleep(2_000); + fallbackSyncEngine(); +} + +ipcMain.handle('SYNC_MANUALLY', async () => { + Logger.info('[Manual Sync] Received manual sync event'); + await updateRemoteSync(); + await fallbackRemoteSync(); +}); + +eventBus.on('RECEIVED_REMOTE_CHANGES', async () => { + await updateRemoteSync(); }); eventBus.on('USER_LOGGED_IN', async () => { @@ -104,16 +120,18 @@ eventBus.on('USER_LOGGED_OUT', () => { ipcMain.on('CHECK_SYNC', (event) => { Logger.info('Checking sync'); - event.sender.send( - 'CHECK_SYNC_ENGINE_RESPONSE', - 'Dato obtenido del proceso de sincronización' - ); + event.sender.send('CHECK_SYNC_ENGINE_RESPONSE', ''); }); ipcMain.on('CHECK_SYNC_CHANGE_STATUS', async (_, placeholderStates) => { - await sleep(2_000); - Logger.info('[SYNC ENGINE] Changing status'); - remoteSyncManager.placeholderStatus = 'SYNCING'; - await sleep(7_00); + Logger.info('[SYNC ENGINE] Changing status', placeholderStates); + await sleep(5_000); remoteSyncManager.placeholderStatus = placeholderStates; }); + +ipcMain.handle('CHECK_SYNC_IN_PROGRESS', async () => { + const syncingStatus: RemoteSyncStatus = 'SYNCING'; + const isSyncing = remoteSyncManager.getSyncStatus() === syncingStatus; + const recentlySyncing = remoteSyncManager.recentlyWasSyncing(); + return isSyncing || recentlySyncing; // If it's syncing or recently was syncing +}); diff --git a/src/apps/main/remote-sync/helpers.ts b/src/apps/main/remote-sync/helpers.ts index ee6b1fa30..e35c0a6e1 100644 --- a/src/apps/main/remote-sync/helpers.ts +++ b/src/apps/main/remote-sync/helpers.ts @@ -99,6 +99,7 @@ export type SyncConfig = { }; export const SYNC_OFFSET_MS = 0; +export const WAITING_AFTER_SYNCING = 1000 * 60 * 3; export const lastSyncedAtIsNewer = ( itemUpdatedAt: Date, diff --git a/src/apps/renderer/localize/locales/en.json b/src/apps/renderer/localize/locales/en.json index 59cfa584c..744a579ff 100644 --- a/src/apps/renderer/localize/locales/en.json +++ b/src/apps/renderer/localize/locales/en.json @@ -130,6 +130,7 @@ }, "dropdown": { "preferences": "Preferences", + "sync": "Sync", "issues": "Issues", "send-feedback": "Send feedback", "support": "Support", diff --git a/src/apps/renderer/localize/locales/es.json b/src/apps/renderer/localize/locales/es.json index 2ce257eaf..aa0fd9de3 100644 --- a/src/apps/renderer/localize/locales/es.json +++ b/src/apps/renderer/localize/locales/es.json @@ -130,6 +130,7 @@ }, "dropdown": { "preferences": "Preferencias", + "sync": "Sincronizar", "issues": "Lista de errores", "send-feedback": "Enviar feedback", "support": "Ayuda", diff --git a/src/apps/renderer/pages/Widget/Header.tsx b/src/apps/renderer/pages/Widget/Header.tsx index 33dca6632..3f7be266b 100644 --- a/src/apps/renderer/pages/Widget/Header.tsx +++ b/src/apps/renderer/pages/Widget/Header.tsx @@ -12,6 +12,7 @@ import useUsage from '../../hooks/useUsage'; import useVirtualDriveStatus from '../../hooks/VirtualDriveStatus'; import { reportError } from '../../utils/errors'; + export default function Header() { const { translate } = useTranslationContext(); const { virtualDriveCanBeOpened } = useVirtualDriveStatus(); @@ -45,6 +46,19 @@ export default function Header() { window.electron.quit(); } + const wasSyncing = () => { + return window.electron.getRecentlywasSyncing(); + }; + + async function onSyncClick() { + const notAllowed = await wasSyncing(); + if (notAllowed) { + return; + } + window.electron.syncManually(); + } + + const handleOpenURL = async (URL: string) => { try { await window.electron.openUrl(URL); @@ -195,6 +209,17 @@ export default function Header() { )} + + {({active}) => { + + return (
+ + {translate('widget.header.dropdown.sync')} + +
); + } + } +
{({ active }) => (
diff --git a/src/apps/sync-engine/BindingManager.ts b/src/apps/sync-engine/BindingManager.ts index 503b8690a..a66fc1f35 100644 --- a/src/apps/sync-engine/BindingManager.ts +++ b/src/apps/sync-engine/BindingManager.ts @@ -244,6 +244,8 @@ export class BindingsManager { await this.container.virtualDrive.connectSyncRoot(); await this.load(); + + await this.polling(); } watch() { @@ -322,16 +324,18 @@ export class BindingsManager { } private async pollingStart() { + Logger.debug('[SYNC ENGINE] Starting polling'); return this.container.pollingMonitorStart.run(this.polling.bind(this)); } - private async polling(): Promise { + async polling(): Promise { try { Logger.info('[SYNC ENGINE] Monitoring polling...'); const fileInPendingPaths = (await this.container.virtualDrive.getPlaceholderWithStatePending()) as Array; Logger.info('[SYNC ENGINE] fileInPendingPaths', fileInPendingPaths); + await this.container.fileSyncOrchestrator.run(fileInPendingPaths); ipcRenderer.send('CHECK_SYNC'); } catch (error) { diff --git a/src/apps/sync-engine/index.ts b/src/apps/sync-engine/index.ts index dfd5d9fb6..1d80d1947 100644 --- a/src/apps/sync-engine/index.ts +++ b/src/apps/sync-engine/index.ts @@ -57,6 +57,14 @@ async function setUp() { Logger.info('[SYNC ENGINE] sync engine updated successfully'); }); + ipcRenderer.on('FALLBACK_SYNC_ENGINE_PROCESS', async () => { + Logger.info('[SYNC ENGINE] Fallback sync engine'); + + await bindings.polling(); + + Logger.info('[SYNC ENGINE] sync engine fallback successfully'); + }); + ipcRenderer.on('STOP_AND_CLEAR_SYNC_ENGINE_PROCESS', async (event) => { Logger.info('[SYNC ENGINE] Stopping and clearing sync engine'); diff --git a/src/context/virtual-drive/files/application/FileCheckerStatusInRoot.ts b/src/context/virtual-drive/files/application/FileCheckerStatusInRoot.ts index 80a10a329..3d4e66c62 100644 --- a/src/context/virtual-drive/files/application/FileCheckerStatusInRoot.ts +++ b/src/context/virtual-drive/files/application/FileCheckerStatusInRoot.ts @@ -27,16 +27,16 @@ export class FileCheckerStatusInRoot { const ps = placeholderStatus.pinState; const ss = placeholderStatus.syncState; - const status = + const notSynced = ps && ss && ps !== PinState.AlwaysLocal && ps !== PinState.OnlineOnly && ss !== SyncState.InSync; - if (status) { + if (notSynced) { Logger.debug( - `[File Checker Status In Root] item ${path} with status: ${status}` + `[File Checker Status In Root] item ${path} with status: ${notSynced}` ); finalStatus = 'SYNC_PENDING'; break; diff --git a/src/context/virtual-drive/shared/application/PollingMonitorStart.ts b/src/context/virtual-drive/shared/application/PollingMonitorStart.ts index 6c0f2918c..5cef0729d 100644 --- a/src/context/virtual-drive/shared/application/PollingMonitorStart.ts +++ b/src/context/virtual-drive/shared/application/PollingMonitorStart.ts @@ -1,8 +1,21 @@ import { MonitorFn, PollingMonitor } from '../domain/PollingMonitor'; +import { ipcRenderer } from 'electron'; +import Logger from 'electron-log'; export class PollingMonitorStart { constructor(private readonly polling: PollingMonitor) {} run(fn: MonitorFn) { - return this.polling.start(fn); + Logger.info('[START FALLBAK] Starting fallback sync...'); + + const permission = this.permissionFn.bind(this); + return this.polling.start(fn, permission); + } + + private async permissionFn() { + const isSyncing = await ipcRenderer.invoke('CHECK_SYNC_IN_PROGRESS'); + Logger.info('[START FALLBAK] Not permitted to start fallback sync: ', isSyncing); + + const isPermitted = !isSyncing; + return isPermitted; } } diff --git a/src/context/virtual-drive/shared/domain/PollingMonitor.ts b/src/context/virtual-drive/shared/domain/PollingMonitor.ts index ef232677a..3972e566c 100644 --- a/src/context/virtual-drive/shared/domain/PollingMonitor.ts +++ b/src/context/virtual-drive/shared/domain/PollingMonitor.ts @@ -1,4 +1,5 @@ export type MonitorFn = () => Promise; +export type PermissionFn = () => Promise; export class PollingMonitor { constructor(private readonly delay: number) {} @@ -11,16 +12,25 @@ export class PollingMonitor { } } - private setTimeout(fn: MonitorFn) { + private setTimeout(fn: MonitorFn, permissionFn: PermissionFn) { this.clearTimeout(); this.timeout = setTimeout(async () => { + if (!(await permissionFn())) { + // wait for the next interval + this.repeatDelay(fn, permissionFn); + return; + } await fn(); - this.setTimeout(fn); + this.repeatDelay(fn, permissionFn); }, this.delay); } - start(fn: MonitorFn) { - this.setTimeout(fn); + private repeatDelay(fn: MonitorFn, runPermissionFn: PermissionFn) { + this.setTimeout(fn, runPermissionFn); + } + + start(fn: MonitorFn, runPermissionFn: PermissionFn) { + this.setTimeout(fn, runPermissionFn); } stop() {