Skip to content
Open
5 changes: 4 additions & 1 deletion src/hooks/sync-sites/use-sync-push.ts
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,10 @@ export function useSyncPush( {
if ( isKeyFailed( statusKey ) || isKeyFinished( statusKey ) || isKeyCancelled( statusKey ) ) {
getIpcApi().clearSyncOperation( generateStateId( selectedSiteId, remoteSiteId ) );
} else {
getIpcApi().addSyncOperation( generateStateId( selectedSiteId, remoteSiteId ) );
getIpcApi().addSyncOperation(
generateStateId( selectedSiteId, remoteSiteId ),
state.status
);
}
},
[ isKeyFailed, isKeyFinished, isKeyCancelled, updateState ]
Expand Down
18 changes: 0 additions & 18 deletions src/hooks/use-sync-states-progress-info.ts
Original file line number Diff line number Diff line change
Expand Up @@ -277,22 +277,6 @@ export function useSyncStatesProgressInfo() {
]
);

const canCancelPull = useCallback( ( key: PullStateProgressInfo[ 'key' ] | undefined ) => {
const cancellableStateKeys: PullStateProgressInfo[ 'key' ][] = [ 'in-progress', 'downloading' ];
if ( ! key ) {
return false;
}
return cancellableStateKeys.includes( key );
}, [] );

const canCancelPush = useCallback( ( key: PushStateProgressInfo[ 'key' ] | undefined ) => {
const cancellableStateKeys: PushStateProgressInfo[ 'key' ][] = [ 'creatingBackup' ];
if ( ! key ) {
return false;
}
return cancellableStateKeys.includes( key );
}, [] );

return {
pullStatesProgressInfo,
pushStatesProgressInfo,
Expand All @@ -305,7 +289,5 @@ export function useSyncStatesProgressInfo() {
getBackupStatusWithProgress,
getPullStatusWithProgress,
getPushStatusWithProgress,
canCancelPull,
canCancelPush,
};
}
24 changes: 20 additions & 4 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,10 @@ import { suppressPunycodeWarning } from 'common/lib/suppress-punycode-warning';
import { StatsGroup } from 'common/types/stats';
import { IPC_VOID_HANDLERS } from 'src/constants';
import * as ipcHandlers from 'src/ipc-handlers';
import { hasActiveSyncOperations } from 'src/lib/active-sync-operations';
import {
hasActiveSyncOperations,
hasCancelableSyncOperations,
} from 'src/lib/active-sync-operations';
import { bumpAggregatedUniqueStat, bumpStat } from 'src/lib/bump-stats';
import { getPlatformMetric } from 'src/lib/bump-stats/lib';
import { getUserLocaleWithFallback } from 'src/lib/locale-node';
Expand Down Expand Up @@ -351,6 +354,19 @@ async function appBoot() {
globalShortcut.unregisterAll();
} );

function getQuitConfirmationMessage(): string {
if ( hasCancelableSyncOperations() ) {
return __(
"There's a sync operation in progress. Quitting the app will abort that operation. Are you sure you want to quit?"
);
}

// Default message for creatingBackup, uploading, or pull operations
return __(
"There's a sync operation in progress. The process will continue on WordPress.com servers even after quitting Studio. We will send you an email when it completes. Are you sure you want to quit?"
);
}

app.on( 'before-quit', ( event ) => {
if ( ! hasActiveSyncOperations() ) {
return;
Expand All @@ -359,11 +375,11 @@ async function appBoot() {
const QUIT_APP_BUTTON_INDEX = 0;
const CANCEL_BUTTON_INDEX = 1;

const detailMessage = getQuitConfirmationMessage();

const clickedButtonIndex = dialog.showMessageBoxSync( {
message: __( 'Sync in progress' ),
detail: __(
'There’s a sync operation in progress. Quitting the app will abort that operation. Are you sure you want to quit?'
),
detail: detailMessage,
buttons: [ __( 'Yes, quit the app' ), __( 'No, take me back' ) ],
cancelId: CANCEL_BUTTON_INDEX,
defaultId: QUIT_APP_BUTTON_INDEX,
Expand Down
12 changes: 10 additions & 2 deletions src/ipc-handlers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,10 @@ import {
unlockAppdata,
updateAppdata,
} from 'src/storage/user-data';
import {
PullStateProgressInfo,
PushStateProgressInfo,
} from './hooks/use-sync-states-progress-info';
import { Blueprint } from './stores/wpcom-api';
import type { SyncSite } from 'src/hooks/use-fetch-wpcom-sites/types';
import type { WpCliResult } from 'src/lib/wp-cli-process';
Expand Down Expand Up @@ -1332,8 +1336,12 @@ export async function isImportExportSupported( _event: IpcMainInvokeEvent, siteI
/**
* Store the ID of a push/pull operation in a deduped set.
*/
export function addSyncOperation( event: IpcMainInvokeEvent, id: string ) {
ACTIVE_SYNC_OPERATIONS.add( id );
export function addSyncOperation(
event: IpcMainInvokeEvent,
id: string,
state?: PullStateProgressInfo | PushStateProgressInfo
) {
ACTIVE_SYNC_OPERATIONS.set( id, state );
}

/**
Expand Down
48 changes: 47 additions & 1 deletion src/lib/active-sync-operations.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,58 @@
import type {
PullStateProgressInfo,
PushStateProgressInfo,
} from 'src/hooks/use-sync-states-progress-info';
/**
* This set is used to store the IDs of active sync operations. It's used to determine if we should
* display a confirmation modal before quitting the app.
*/
export const ACTIVE_SYNC_OPERATIONS = new Set();
export const ACTIVE_SYNC_OPERATIONS = new Map<
string,
PullStateProgressInfo | PushStateProgressInfo | undefined
>();

/**
* Determine if the set of active push/pull operations has any members.
*/
export function hasActiveSyncOperations(): boolean {
return ACTIVE_SYNC_OPERATIONS.size > 0;
}

export function getSyncOperations(): Map<
string,
PullStateProgressInfo | PushStateProgressInfo | undefined
> {
return ACTIVE_SYNC_OPERATIONS;
}

/**
* Check if a pull operation can be cancelled based on its current state.
*/
export function canCancelPull( key: PullStateProgressInfo[ 'key' ] | undefined ): boolean {
const cancellableStateKeys: PullStateProgressInfo[ 'key' ][] = [ 'in-progress', 'downloading' ];
if ( ! key ) {
return false;
}
return cancellableStateKeys.includes( key );
}

/**
* Check if a push operation can be cancelled based on its current state.
*/
export function canCancelPush( key: PushStateProgressInfo[ 'key' ] | undefined ): boolean {
const cancellableStateKeys: PushStateProgressInfo[ 'key' ][] = [ 'creatingBackup' ];
if ( ! key ) {
return false;
}
return cancellableStateKeys.includes( key );
}

export function hasCancelableSyncOperations(): boolean {
// Iterate over all the sites and check if any operation is cancelable
for ( const [ , state ] of ACTIVE_SYNC_OPERATIONS ) {
if ( state && 'key' in state ) {
return canCancelPush( state.key as PushStateProgressInfo[ 'key' ] );
}
}
return true;
}
3 changes: 1 addition & 2 deletions src/modules/sync/components/sync-connected-sites.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import { useSyncSites } from 'src/hooks/sync-sites';
import { useImportExport } from 'src/hooks/use-import-export';
import { useOffline } from 'src/hooks/use-offline';
import { useSyncStatesProgressInfo } from 'src/hooks/use-sync-states-progress-info';
import { canCancelPull, canCancelPush } from 'src/lib/active-sync-operations';
import { cx } from 'src/lib/cx';
import { getIpcApi } from 'src/lib/get-ipc-api';
import { getLocalizedLink } from 'src/lib/get-localized-link';
Expand Down Expand Up @@ -186,8 +187,6 @@ const SyncConnectedSitesSectionItem = ( {
isKeyFailed,
isKeyCancelled,
getPullStatusWithProgress,
canCancelPull,
canCancelPush,
} = useSyncStatesProgressInfo();

const sitePullState = getPullState( selectedSite.id, connectedSite.id );
Expand Down
2 changes: 1 addition & 1 deletion src/preload.ts
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ const api: IpcApi = {
removeSyncBackup: ( remoteSiteId ) => ipcRendererInvoke( 'removeSyncBackup', remoteSiteId ),
getConnectedWpcomSites: ( localSiteId ) =>
ipcRendererInvoke( 'getConnectedWpcomSites', localSiteId ),
addSyncOperation: ( id ) => ipcRendererSend( 'addSyncOperation', id ),
addSyncOperation: ( id, status ) => ipcRendererSend( 'addSyncOperation', id, status ),
clearSyncOperation: ( id ) => ipcRendererSend( 'clearSyncOperation', id ),
cancelSyncOperation: ( id ) => ipcRendererSend( 'cancelSyncOperation', id ),
getDirectorySize: ( id, subdir ) => ipcRendererInvoke( 'getDirectorySize', id, subdir ),
Expand Down