From c3f61533f797343d5a324aad21974ca5046e7984 Mon Sep 17 00:00:00 2001 From: fullex <0xfullex@gmail.com> Date: Sun, 10 Aug 2025 01:40:03 +0800 Subject: [PATCH] feat(migration): enhance migration flow with new stages and error handling This commit introduces additional stages to the migration process, including 'backup_required', 'backup_progress', and 'backup_confirmed', improving user guidance during data migration. It also adds new IPC channels for proceeding to backup and retrying migration, along with enhanced error handling and logging throughout the migration flow. The user interface has been updated to reflect these changes, providing clearer feedback and options during the migration process. --- packages/shared/IpcChannel.ts | 4 +- .../DataRefactorMigrateService.ts | 411 ++++++++++++++---- src/main/index.ts | 41 +- .../dataRefactorMigrate/MigrateApp.tsx | 161 ++++--- 4 files changed, 470 insertions(+), 147 deletions(-) diff --git a/packages/shared/IpcChannel.ts b/packages/shared/IpcChannel.ts index 7494b62fc3..035594c5df 100644 --- a/packages/shared/IpcChannel.ts +++ b/packages/shared/IpcChannel.ts @@ -186,13 +186,15 @@ export enum IpcChannel { // data migration DataMigrate_CheckNeeded = 'data-migrate:check-needed', - DataMigrate_StartMigration = 'data-migrate:start-migration', DataMigrate_GetProgress = 'data-migrate:get-progress', DataMigrate_Cancel = 'data-migrate:cancel', DataMigrate_RequireBackup = 'data-migrate:require-backup', DataMigrate_BackupCompleted = 'data-migrate:backup-completed', DataMigrate_ShowBackupDialog = 'data-migrate:show-backup-dialog', DataMigrate_StartFlow = 'data-migrate:start-flow', + DataMigrate_ProceedToBackup = 'data-migrate:proceed-to-backup', + DataMigrate_StartMigration = 'data-migrate:start-migration', + DataMigrate_RetryMigration = 'data-migrate:retry-migration', DataMigrate_RestartApp = 'data-migrate:restart-app', DataMigrate_CloseWindow = 'data-migrate:close-window', diff --git a/src/main/data/migrate/dataRefactor/DataRefactorMigrateService.ts b/src/main/data/migrate/dataRefactor/DataRefactorMigrateService.ts index 0f489b7742..1f4d4f6d46 100644 --- a/src/main/data/migrate/dataRefactor/DataRefactorMigrateService.ts +++ b/src/main/data/migrate/dataRefactor/DataRefactorMigrateService.ts @@ -22,11 +22,21 @@ interface DataRefactorMigrationStatus { version?: string } +type MigrationStage = + | 'introduction' // Introduction phase - user can cancel + | 'backup_required' // Backup required - show backup requirement + | 'backup_progress' // Backup in progress - user is backing up + | 'backup_confirmed' // Backup confirmed - ready to migrate + | 'migration' // Migration in progress - cannot cancel + | 'completed' // Completed - restart app + | 'error' // Error - recovery options + interface MigrationProgress { - stage: string + stage: MigrationStage progress: number total: number message: string + error?: string } interface MigrationResult { @@ -39,13 +49,12 @@ class DataRefactorMigrateService { private static instance: DataRefactorMigrateService | null = null private migrateWindow: BrowserWindow | null = null private backupManager: BackupManager - private backupCompletionResolver: ((value: boolean) => void) | null = null private db = dbService.getDb() private currentProgress: MigrationProgress = { - stage: 'idle', + stage: 'introduction', progress: 0, total: 100, - message: 'Ready to migrate' + message: 'Ready to start data migration' } private isMigrating: boolean = false @@ -77,11 +86,32 @@ class DataRefactorMigrateService { } }) + ipcMain.handle(IpcChannel.DataMigrate_ProceedToBackup, async () => { + try { + await this.proceedToBackup() + return true + } catch (error) { + logger.error('IPC handler error: proceedToBackup', error as Error) + throw error + } + }) + ipcMain.handle(IpcChannel.DataMigrate_StartMigration, async () => { try { - return await this.runMigration() + await this.startMigrationProcess() + return true } catch (error) { - logger.error('IPC handler error: runMigration', error as Error) + logger.error('IPC handler error: startMigrationProcess', error as Error) + throw error + } + }) + + ipcMain.handle(IpcChannel.DataMigrate_RetryMigration, async () => { + try { + await this.retryMigration() + return true + } catch (error) { + logger.error('IPC handler error: retryMigration', error as Error) throw error } }) @@ -104,9 +134,9 @@ class DataRefactorMigrateService { } }) - ipcMain.handle(IpcChannel.DataMigrate_BackupCompleted, () => { + ipcMain.handle(IpcChannel.DataMigrate_BackupCompleted, async () => { try { - this.notifyBackupCompleted() + await this.notifyBackupCompleted() return true } catch (error) { logger.error('IPC handler error: notifyBackupCompleted', error as Error) @@ -114,14 +144,55 @@ class DataRefactorMigrateService { } }) - ipcMain.handle(IpcChannel.DataMigrate_ShowBackupDialog, () => { + ipcMain.handle(IpcChannel.DataMigrate_ShowBackupDialog, async () => { try { - // Show the backup dialog/interface - // This could integrate with existing backup UI or create a new backup interface - logger.info('Backup dialog request received') - return true + logger.info('Opening backup dialog for migration') + + // Update progress to indicate backup dialog is opening + await this.updateProgress('backup_progress', 10, 'Opening backup dialog...') + + // Instead of performing backup automatically, let's open the file dialog + // and let the user choose where to save the backup + const { dialog } = await import('electron') + const result = await dialog.showSaveDialog({ + title: 'Save Migration Backup', + defaultPath: `cherry-studio-migration-backup-${new Date().toISOString().split('T')[0]}.zip`, + filters: [ + { name: 'Backup Files', extensions: ['zip'] }, + { name: 'All Files', extensions: ['*'] } + ] + }) + + if (!result.canceled && result.filePath) { + logger.info('User selected backup location', { filePath: result.filePath }) + await this.updateProgress('backup_progress', 50, 'Creating backup file...') + + // Perform the actual backup to the selected location + const backupResult = await this.performBackupToFile(result.filePath) + + if (backupResult.success) { + await this.updateProgress('backup_progress', 100, 'Backup created successfully!') + // Wait a moment to show the success message, then transition to confirmed state + setTimeout(async () => { + await this.updateProgress( + 'backup_confirmed', + 100, + 'Backup completed! Ready to start migration. Click "Start Migration" to continue.' + ) + }, 1000) + } else { + await this.updateProgress('backup_required', 0, `Backup failed: ${backupResult.error}`) + } + + return backupResult + } else { + logger.info('User cancelled backup dialog') + await this.updateProgress('backup_required', 0, 'Backup cancelled. Please create a backup to continue.') + return { success: false, error: 'Backup cancelled by user' } + } } catch (error) { logger.error('IPC handler error: showBackupDialog', error as Error) + await this.updateProgress('backup_required', 0, 'Backup process failed') throw error } }) @@ -135,9 +206,9 @@ class DataRefactorMigrateService { } }) - ipcMain.handle(IpcChannel.DataMigrate_RestartApp, () => { + ipcMain.handle(IpcChannel.DataMigrate_RestartApp, async () => { try { - this.restartApplication() + await this.restartApplication() return true } catch (error) { logger.error('IPC handler error: restartApplication', error as Error) @@ -167,12 +238,14 @@ class DataRefactorMigrateService { try { ipcMain.removeAllListeners(IpcChannel.DataMigrate_CheckNeeded) - ipcMain.removeAllListeners(IpcChannel.DataMigrate_StartMigration) ipcMain.removeAllListeners(IpcChannel.DataMigrate_GetProgress) ipcMain.removeAllListeners(IpcChannel.DataMigrate_Cancel) ipcMain.removeAllListeners(IpcChannel.DataMigrate_BackupCompleted) ipcMain.removeAllListeners(IpcChannel.DataMigrate_ShowBackupDialog) ipcMain.removeAllListeners(IpcChannel.DataMigrate_StartFlow) + ipcMain.removeAllListeners(IpcChannel.DataMigrate_ProceedToBackup) + ipcMain.removeAllListeners(IpcChannel.DataMigrate_StartMigration) + ipcMain.removeAllListeners(IpcChannel.DataMigrate_RetryMigration) ipcMain.removeAllListeners(IpcChannel.DataMigrate_RestartApp) ipcMain.removeAllListeners(IpcChannel.DataMigrate_CloseWindow) @@ -200,6 +273,7 @@ class DataRefactorMigrateService { return true } + logger.info('Data Refactor Migration is needed') return false } catch (error) { logger.error('Failed to check migration status', error as Error) @@ -212,18 +286,40 @@ class DataRefactorMigrateService { */ private async isMigrationCompleted(): Promise { try { + logger.debug('Checking migration completion status in database') + + // First check if the database is available + if (!this.db) { + logger.warn('Database not initialized, assuming migration not completed') + return false + } + const result = await this.db .select() .from(appStateTable) .where(eq(appStateTable.key, DATA_REFACTOR_MIGRATION_STATUS)) .limit(1) - if (result.length === 0) return false + logger.debug('Migration status query result', { resultCount: result.length }) + + if (result.length === 0) { + logger.info('No migration status record found, migration needed') + return false + } const status = result[0].value as DataRefactorMigrationStatus - return status.completed === true + const isCompleted = status.completed === true + + logger.info('Migration status found', { + completed: isCompleted, + completedAt: status.completedAt, + version: status.version + }) + + return isCompleted } catch (error) { - logger.warn('Failed to check migration state', error as Error) + logger.error('Failed to check migration state - treating as not completed', error as Error) + // In case of database errors, assume migration is needed to be safe return false } } @@ -320,7 +416,7 @@ class DataRefactorMigrateService { } /** - * Run the complete migration process + * Show migration window and initialize introduction stage */ async runMigration(): Promise { if (this.isMigrating) { @@ -332,6 +428,9 @@ class DataRefactorMigrateService { this.isMigrating = true logger.info('Showing migration window') + // Initialize introduction stage + await this.updateProgress('introduction', 0, 'Welcome to Cherry Studio data migration') + // Create migration window const window = this.createMigrateWindow() @@ -345,12 +444,45 @@ class DataRefactorMigrateService { }) } + /** + * Start migration flow - simply ensure we're in introduction stage + * This is called when user first opens the migration window + */ async startMigrationFlow(): Promise { if (!this.isMigrating) { logger.warn('Migration not started, cannot execute flow.') return } - logger.info('Starting migration flow from user action') + + logger.info('Confirming introduction stage for migration flow') + await this.updateProgress('introduction', 0, 'Ready to begin migration process. Please read the information below.') + } + + /** + * Proceed from introduction to backup requirement stage + * This is called when user clicks "Next" in introduction + */ + async proceedToBackup(): Promise { + if (!this.isMigrating) { + logger.warn('Migration not started, cannot proceed to backup.') + return + } + + logger.info('Proceeding from introduction to backup stage') + await this.updateProgress('backup_required', 0, 'Data backup is required before migration can proceed') + } + + /** + * Start the actual migration process + * This is called when user confirms backup and clicks "Start Migration" + */ + async startMigrationProcess(): Promise { + if (!this.isMigrating) { + logger.warn('Migration not started, cannot start migration process.') + return + } + + logger.info('Starting actual migration process') try { await this.executeMigrationFlow() } catch (error) { @@ -360,21 +492,12 @@ class DataRefactorMigrateService { } /** - * Execute the complete migration flow + * Execute the actual migration process + * Called after user has confirmed backup completion */ private async executeMigrationFlow(): Promise { try { - // Step 1: Enforce backup - await this.updateProgress('backup', 0, 'Starting backup process...') - const backupSuccess = await this.enforceBackup() - - if (!backupSuccess) { - throw new Error('Backup process failed or was cancelled by user') - } - - await this.updateProgress('backup', 100, 'Backup completed successfully') - - // Step 2: Execute migration + // Start migration await this.updateProgress('migration', 0, 'Starting data migration...') const migrationResult = await this.executeMigration() @@ -388,16 +511,18 @@ class DataRefactorMigrateService { `Migration completed: ${migrationResult.migratedCount} items migrated` ) - // Step 3: Mark as completed + // Mark as completed await this.markMigrationCompleted() - await this.updateProgress('completed', 100, 'Migration completed! Please restart the app.') + await this.updateProgress('completed', 100, 'Migration completed successfully! Click restart to continue.') } catch (error) { logger.error('Migration flow failed', error as Error) + const errorMessage = error instanceof Error ? error.message : String(error) await this.updateProgress( 'error', 0, - `Migration failed: ${error instanceof Error ? error.message : String(error)}. Please close this window and restart the app to try again.` + 'Migration failed. You can close this window and try again, or continue using the previous version.', + errorMessage ) throw error @@ -405,58 +530,113 @@ class DataRefactorMigrateService { } /** - * Enforce backup before migration + * Perform backup to a specific file location */ - private async enforceBackup(): Promise { + private async performBackupToFile(filePath: string): Promise<{ success: boolean; error?: string }> { try { - logger.info('Enforcing backup before migration') + logger.info('Performing backup to file', { filePath }) - await this.updateProgress('backup', 0, 'Backup is required before migration') + // Get backup data from the current application state + const backupData = await this.getBackupData() - // Send backup requirement to renderer - if (this.migrateWindow && !this.migrateWindow.isDestroyed()) { - this.migrateWindow.webContents.send(IpcChannel.DataMigrate_RequireBackup) - } + // Extract directory and filename from the full path + const path = await import('path') + const destinationDir = path.dirname(filePath) + const fileName = path.basename(filePath) - // Wait for user to complete backup - const backupResult = await this.waitForBackupCompletion() + // Use the existing backup manager to create a backup + const backupPath = await this.backupManager.backup( + null as any, // IpcMainInvokeEvent - we're calling directly so pass null + fileName, + backupData, + destinationDir, + false // Don't skip backup files - full backup for migration safety + ) - if (backupResult) { - await this.updateProgress('backup', 100, 'Backup completed successfully') - return true + if (backupPath) { + logger.info('Backup created successfully', { path: backupPath }) + return { success: true } } else { - await this.updateProgress('backup', 0, 'Backup is required to proceed with migration') - return false + return { + success: false, + error: 'Backup process did not return a file path' + } } } catch (error) { - logger.error('Backup enforcement failed', error as Error) - await this.updateProgress('backup', 0, 'Backup process failed') - return false + const errorMessage = error instanceof Error ? error.message : String(error) + logger.error('Backup failed during migration:', error as Error) + return { + success: false, + error: errorMessage + } } } /** - * Wait for user to complete backup + * Get backup data from the current application + * This creates a minimal backup with essential system information */ - private async waitForBackupCompletion(): Promise { - return new Promise((resolve) => { - // Store resolver for later use - this.backupCompletionResolver = resolve + private async getBackupData(): Promise { + try { + const fs = await import('fs-extra') + const path = await import('path') - // The actual completion will be triggered by notifyBackupCompleted() method - }) + // Gather basic system information + const data = { + backup: { + timestamp: new Date().toISOString(), + version: electronApp.getVersion(), + type: 'pre-migration-backup', + note: 'This is a safety backup created before data migration' + }, + system: { + platform: process.platform, + arch: process.arch, + nodeVersion: process.version + }, + // Include basic configuration files if they exist + configs: {} as Record + } + + // Try to read some basic configuration files (non-critical if they fail) + try { + const { getDataPath } = await import('@main/utils') + const dataPath = getDataPath() + + // Check if there are any config files we should backup + const configFiles = ['config.json', 'settings.json', 'preferences.json'] + for (const configFile of configFiles) { + const configPath = path.join(dataPath, configFile) + if (await fs.pathExists(configPath)) { + try { + const configContent = await fs.readJson(configPath) + data.configs[configFile] = configContent + } catch (err) { + logger.warn(`Could not read config file ${configFile}`, err as Error) + } + } + } + } catch (err) { + logger.warn('Could not access data directory for config backup', err as Error) + } + + return JSON.stringify(data, null, 2) + } catch (error) { + logger.error('Failed to get backup data:', error as Error) + throw error + } } /** * Notify that backup has been completed (called from IPC handler) */ - public notifyBackupCompleted(): void { - if (this.backupCompletionResolver) { - logger.info('Backup completed by user') - - this.backupCompletionResolver(true) - this.backupCompletionResolver = null - } + public async notifyBackupCompleted(): Promise { + logger.info('Backup completed by user') + await this.updateProgress( + 'backup_confirmed', + 100, + 'Backup completed! Ready to start migration. Click "Start Migration" to continue.' + ) } /** @@ -494,12 +674,18 @@ class DataRefactorMigrateService { /** * Update migration progress and broadcast to window */ - private async updateProgress(stage: string, progress: number, message: string): Promise { + private async updateProgress( + stage: MigrationStage, + progress: number, + message: string, + error?: string + ): Promise { this.currentProgress = { stage, progress, total: 100, - message + message, + error } if (this.migrateWindow && !this.migrateWindow.isDestroyed()) { @@ -518,18 +704,36 @@ class DataRefactorMigrateService { /** * Cancel migration process + * Only allowed during introduction and backup phases */ async cancelMigration(): Promise { if (!this.isMigrating) { return } + const currentStage = this.currentProgress.stage + if (currentStage === 'migration') { + logger.warn('Cannot cancel migration during migration process') + return + } + logger.info('Cancelling migration process') this.isMigrating = false - await this.updateProgress('cancelled', 0, 'Migration cancelled by user') this.closeMigrateWindow() } + /** + * Retry migration after error + */ + async retryMigration(): Promise { + logger.info('Retrying migration process') + await this.updateProgress( + 'introduction', + 0, + 'Ready to restart migration process. Please read the information below.' + ) + } + /** * Close migration window */ @@ -547,22 +751,73 @@ class DataRefactorMigrateService { /** * Restart the application after successful migration */ - private restartApplication(): void { + private async restartApplication(): Promise { try { - logger.info('Restarting application after migration completion') + logger.info('Preparing to restart application after migration completion') - // Clean up migration window and handlers before restart - this.closeMigrateWindow() + // Ensure migration status is properly saved before restart + await this.verifyMigrationStatus() - // Restart the app using Electron's relaunch mechanism - app.relaunch() - app.exit(0) + // Give some time for database operations to complete + await new Promise((resolve) => setTimeout(resolve, 500)) + + logger.info('Restarting application now') + + // In development mode, relaunch might not work properly + if (process.env.NODE_ENV === 'development' || !app.isPackaged) { + logger.warn('Development mode detected - showing restart instruction instead of auto-restart') + + const { dialog } = await import('electron') + await dialog.showMessageBox({ + type: 'info', + title: 'Migration Complete - Restart Required', + message: + 'Data migration completed successfully!\n\nSince you are in development mode, please manually restart the application to continue.', + buttons: ['Close App'], + defaultId: 0 + }) + + // Clean up migration window and handlers after showing dialog + this.closeMigrateWindow() + app.quit() + } else { + // Production mode - clean up first, then relaunch + this.closeMigrateWindow() + app.relaunch() + app.exit(0) + } } catch (error) { logger.error('Failed to restart application', error as Error) // Fallback: just close migration window and let user manually restart this.closeMigrateWindow() } } + + /** + * Verify that migration status is properly saved + */ + private async verifyMigrationStatus(): Promise { + try { + const isCompleted = await this.isMigrationCompleted() + if (isCompleted) { + logger.info('Migration status verified as completed') + } else { + logger.warn('Migration status not found as completed, attempting to mark again') + await this.markMigrationCompleted() + + // Double-check + const recheck = await this.isMigrationCompleted() + if (recheck) { + logger.info('Migration status successfully marked as completed on retry') + } else { + logger.error('Failed to mark migration as completed even on retry') + } + } + } catch (error) { + logger.error('Failed to verify migration status', error as Error) + // Don't throw - still allow restart + } + } } // Export singleton instance diff --git a/src/main/index.ts b/src/main/index.ts index 27e0d24ae6..52bc54e78e 100644 --- a/src/main/index.ts +++ b/src/main/index.ts @@ -113,20 +113,43 @@ if (!app.requestSingleInstanceLock()) { // Data Refactor Migration // Check if data migration is needed BEFORE creating any windows try { + logger.info('Checking if data refactor migration is needed') const isMigrated = await dataRefactorMigrateService.isMigrated() + logger.info('Migration status check result', { isMigrated }) + if (!isMigrated) { logger.info('Data Refactor Migration needed, starting migration process') - await dataRefactorMigrateService.runMigration() - logger.info('Migration completed, app will restart automatically') - // Migration service will handle app restart, no need to continue startup - return + + try { + await dataRefactorMigrateService.runMigration() + logger.info('Migration window created successfully') + // Migration service will handle the migration flow, no need to continue startup + return + } catch (migrationError) { + logger.error('Failed to start migration process', migrationError as Error) + + // Migration is required for this version - show error and exit + await dialog.showErrorBox( + 'Migration Required - Application Cannot Start', + `This version of Cherry Studio requires data migration to function properly.\n\nMigration window failed to start: ${(migrationError as Error).message}\n\nThe application will now exit. Please try starting again or contact support if the problem persists.` + ) + + logger.error('Exiting application due to failed migration startup') + app.quit() + return + } } } catch (error) { - logger.error('Migration process failed', error as Error) - dialog.showErrorBox( - 'Fatal Error: Data Refactor Migration Failed', - `The application could not start due to a critical error during data migration.\n\nPlease contact support or try restoring data from a backup.\n\nError details:\n${(error as Error).message}` + logger.error('Migration status check failed', error as Error) + + // If we can't check migration status, this could indicate a serious database issue + // Since migration may be required, it's safer to exit and let user investigate + await dialog.showErrorBox( + 'Migration Status Check Failed - Application Cannot Start', + `Could not determine if data migration is completed.\n\nThis may indicate a database connectivity issue: ${(error as Error).message}\n\nThe application will now exit. Please check your installation and try again.` ) + + logger.error('Exiting application due to migration status check failure') app.quit() return } @@ -140,7 +163,7 @@ if (!app.requestSingleInstanceLock()) { app.dock?.hide() } - // Only create main window if no migration was needed or migration failed + // Create main window - migration has either completed or was not needed const mainWindow = windowService.createMainWindow() new TrayService() diff --git a/src/renderer/src/windows/dataRefactorMigrate/MigrateApp.tsx b/src/renderer/src/windows/dataRefactorMigrate/MigrateApp.tsx index 27798f7761..fdad3fb9b4 100644 --- a/src/renderer/src/windows/dataRefactorMigrate/MigrateApp.tsx +++ b/src/renderer/src/windows/dataRefactorMigrate/MigrateApp.tsx @@ -6,21 +6,30 @@ import styled from 'styled-components' const { Title, Text } = Typography +type MigrationStage = + | 'introduction' // Introduction phase - user can cancel + | 'backup_required' // Backup required - show backup requirement + | 'backup_progress' // Backup in progress - user is backing up + | 'backup_confirmed' // Backup confirmed - ready to migrate + | 'migration' // Migration in progress - cannot cancel + | 'completed' // Completed - restart app + | 'error' // Error - recovery options + interface MigrationProgress { - stage: string + stage: MigrationStage progress: number total: number message: string + error?: string } const MigrateApp: React.FC = () => { const [progress, setProgress] = useState({ - stage: 'idle', + stage: 'introduction', progress: 0, total: 100, - message: '准备开始迁移...' + message: 'Ready to start data migration' }) - const [showBackupRequired, setShowBackupRequired] = useState(false) useEffect(() => { // Listen for progress updates @@ -28,13 +37,7 @@ const MigrateApp: React.FC = () => { setProgress(progressData) } - // Listen for backup requirement - const handleBackupRequired = () => { - setShowBackupRequired(true) - } - window.electron.ipcRenderer.on(IpcChannel.DataMigrateProgress, handleProgress) - window.electron.ipcRenderer.on(IpcChannel.DataMigrate_RequireBackup, handleBackupRequired) // Request initial progress window.electron.ipcRenderer @@ -47,37 +50,41 @@ const MigrateApp: React.FC = () => { return () => { window.electron.ipcRenderer.removeAllListeners(IpcChannel.DataMigrateProgress) - window.electron.ipcRenderer.removeAllListeners(IpcChannel.DataMigrate_RequireBackup) } }, []) const currentStep = useMemo(() => { switch (progress.stage) { - case 'idle': + case 'introduction': return 0 - case 'backup': + case 'backup_required': + case 'backup_progress': + case 'backup_confirmed': return 1 case 'migration': return 2 case 'completed': - return 4 - case 'error': - case 'cancelled': return 3 + case 'error': + return -1 // Error state - will be handled separately default: return 0 } }, [progress.stage]) const stepStatus = useMemo(() => { - if (progress.stage === 'error' || progress.stage === 'cancelled') { + if (progress.stage === 'error') { return 'error' } return 'process' }, [progress.stage]) + const handleProceedToBackup = () => { + window.electron.ipcRenderer.invoke(IpcChannel.DataMigrate_ProceedToBackup) + } + const handleStartMigration = () => { - window.electron.ipcRenderer.invoke(IpcChannel.DataMigrate_StartFlow) + window.electron.ipcRenderer.invoke(IpcChannel.DataMigrate_StartMigration) } const handleRestartApp = () => { @@ -98,18 +105,22 @@ const MigrateApp: React.FC = () => { } const handleBackupCompleted = () => { - setShowBackupRequired(false) // Notify the main process that backup is completed window.electron.ipcRenderer.invoke(IpcChannel.DataMigrate_BackupCompleted) } + const handleRetryMigration = () => { + window.electron.ipcRenderer.invoke(IpcChannel.DataMigrate_RetryMigration) + } + const getProgressColor = () => { switch (progress.stage) { case 'completed': return '#52c41a' case 'error': - case 'cancelled': return '#ff4d4f' + case 'backup_confirmed': + return '#52c41a' default: return '#1890ff' } @@ -117,12 +128,36 @@ const MigrateApp: React.FC = () => { const renderActionButtons = () => { switch (progress.stage) { - case 'idle': + case 'introduction': return ( - + + + + ) + case 'backup_required': + return ( + + + + + + ) + case 'backup_confirmed': + return ( + + + + + ) + case 'migration': + return case 'completed': return ( ) case 'error': - case 'cancelled': return ( - + + ) - case 'backup': - case 'migration': - return ( - - ) default: return null } @@ -167,11 +197,11 @@ const MigrateApp: React.FC = () => { - {progress.stage !== 'idle' && ( + {progress.stage !== 'introduction' && progress.stage !== 'error' && ( { {progress.message} + {progress.stage === 'introduction' && ( + + )} + + {progress.stage === 'backup_required' && ( + + )} + + {progress.stage === 'backup_confirmed' && ( + + )} + {progress.stage === 'error' && ( { {progress.stage === 'completed' && ( )} - {showBackupRequired && ( - - - - - } - /> - )} -
{renderActionButtons()}