/// <summary> /// Default constructor /// </summary> /// <param name="game">The game</param> /// <param name="backupInfo">The backup info</param> public GameBackupItemViewModel(Games game, IBackupInfo backupInfo) { // Get the info var gameInfo = game.GetGameInfo(); LatestAvailableBackupVersion = backupInfo.LatestAvailableBackupVersion; BackupInfo = backupInfo; IconSource = gameInfo.IconSource; DisplayName = backupInfo.GameDisplayName; // If the type if DOSBox, check if GOG cloud sync is being used if (game.GetGameType() == GameType.DosBox) { try { var cloudSyncDir = game.GetInstallDir(false).Parent + "cloud_saves"; IsGOGCloudSyncUsed = cloudSyncDir.DirectoryExists && Directory.GetFileSystemEntries(cloudSyncDir).Any(); } catch (Exception ex) { ex.HandleError("Getting if DOSBox game is using GOG cloud sync"); IsGOGCloudSyncUsed = false; } } else { IsGOGCloudSyncUsed = false; } RestoreCommand = new AsyncRelayCommand(RestoreAsync); BackupCommand = new AsyncRelayCommand(BackupAsync); }
/// ------------------------------------------------------------------------------------ /// <summary> /// Constructor /// </summary> /// <param name="cache">The cache.</param> /// <param name="backupInfo">The backup info.</param> /// <param name="destFolder">The destination folder.</param> /// <param name="fwVersion">The FieldWorks version.</param> /// ------------------------------------------------------------------------------------ public BackupProjectSettings(LcmCache cache, IBackupInfo backupInfo, string destFolder, string fwVersion) : this(Path.GetDirectoryName(cache.ProjectId.ProjectFolder), cache.ProjectId.Name, cache.LanguageProject.LinkedFilesRootDir, cache.ProjectId.Type, destFolder, fwVersion) { if (backupInfo != null) { Comment = backupInfo.Comment; IncludeConfigurationSettings = backupInfo.IncludeConfigurationSettings; IncludeLinkedFiles = backupInfo.IncludeLinkedFiles; IncludeSupportingFiles = backupInfo.IncludeSupportingFiles; IncludeSpellCheckAdditions = backupInfo.IncludeSpellCheckAdditions; } }
/// ------------------------------------------------------------------------------------ /// <summary> /// Constructor /// </summary> /// <param name="cache">The cache.</param> /// <param name="backupInfo">The backup info.</param> /// <param name="destFolder">The destination folder.</param> /// ------------------------------------------------------------------------------------ public BackupProjectSettings(FdoCache cache, IBackupInfo backupInfo, string destFolder) : this(Path.GetDirectoryName(cache.ProjectId.ProjectFolder), cache.ProjectId.Name, cache.LanguageProject.LinkedFilesRootDir, cache.ProjectId.SharedProjectFolder, cache.ProjectId.Type, destFolder) { if (backupInfo != null) { Comment = backupInfo.Comment; IncludeConfigurationSettings = backupInfo.IncludeConfigurationSettings; IncludeLinkedFiles = backupInfo.IncludeLinkedFiles; IncludeSupportingFiles = backupInfo.IncludeSupportingFiles; IncludeSpellCheckAdditions = backupInfo.IncludeSpellCheckAdditions; } }
/// <summary> /// Restores a backup on the game /// </summary> /// <param name="backupInformation">The backup information</param> /// <returns>True if the backup was successful</returns> public async Task <bool> RestoreAsync(IBackupInfo backupInformation) { using (await AsyncLock.LockAsync()) { RL.Logger?.LogInformationSource($"A backup restore has been requested for {backupInformation.GameDisplayName}"); try { // Get the backup directory var existingBackup = backupInformation.ExistingBackups.FirstOrDefault(); // Make sure a backup exists if (existingBackup == null) { RL.Logger?.LogInformationSource($"Restore failed - the input location could not be found"); await Services.MessageUI.DisplayMessageAsync(String.Format(Resources.Restore_MissingBackup, backupInformation.GameDisplayName), Resources.Restore_FailedHeader, MessageType.Error); return(false); } // Get the backup information var backupInfo = backupInformation.RestoreDirectories; // Make sure we have write access to the restore destinations if (backupInfo.Any(x => !FileManager.CheckDirectoryWriteAccess(x.DirPath))) { RL.Logger?.LogInformationSource($"Restore failed - one or more restore destinations lack write access"); // Request to restart as admin await RCPServices.App.RequestRestartAsAdminAsync(); return(false); } using (var tempDir = new TempDirectory(true)) { using var archiveTempDir = new TempDirectory(true); bool hasCreatedTempBackup = false; try { // If the backup is an archive, extract it to temp if (existingBackup.IsCompressed) { using var file = File.OpenRead(existingBackup.Path); using var zip = new ZipArchive(file, ZipArchiveMode.Read); zip.ExtractToDirectory(archiveTempDir.TempPath); } // Move existing files to temp in case the restore fails foreach (BackupDir item in backupInfo) { // Make sure the directory exists if (!item.DirPath.DirectoryExists) { continue; } // Get the destination directory var destDir = tempDir.TempPath + item.ID; // Check if the entire directory should be moved if (item.IsEntireDir()) { // Move the directory FileManager.MoveDirectory(item.DirPath, destDir, true, true); } else { FileManager.MoveFiles(item, destDir, true); } } hasCreatedTempBackup = true; // Restore each backup directory foreach (BackupDir item in backupInfo) { // Get the combined directory path var dirPath = (existingBackup.IsCompressed ? archiveTempDir.TempPath : existingBackup.Path) + item.ID; // Restore the backup if (dirPath.DirectoryExists) { FileManager.CopyDirectory(dirPath, item.DirPath, false, true); } } } catch { // Delete restored files if restore began if (hasCreatedTempBackup) { foreach (BackupDir item in backupInfo) { // Make sure the directory exists if (!item.DirPath.DirectoryExists) { continue; } // Check if the entire directory should be deleted if (item.IsEntireDir()) { // Delete the directory FileManager.DeleteDirectory(item.DirPath); } else { // Delete each file foreach (FileSystemPath file in Directory.GetFiles(item.DirPath, item.SearchPattern, item.SearchOption)) { // Delete the file FileManager.DeleteFile(file); } } } } // Restore temp backup foreach (BackupDir item in backupInfo) { // Get the combined directory path var dirPath = tempDir.TempPath + item.ID; // Make sure there is a directory to restore if (!dirPath.DirectoryExists) { continue; } // Restore FileManager.MoveDirectory(dirPath, item.DirPath, false, false); } RL.Logger?.LogInformationSource($"Restore failed - clean up succeeded"); throw; } } RL.Logger?.LogInformationSource($"Restore complete"); return(true); } catch (Exception ex) { ex.HandleError("Restoring game", backupInformation); await Services.MessageUI.DisplayExceptionMessageAsync(ex, String.Format(Resources.Restore_Failed, backupInformation.GameDisplayName), Resources.Restore_FailedHeader); return(false); } } }
/// <summary> /// Performs a backup on the game /// </summary> /// <param name="backupInformation">The backup information</param> /// <returns>True if the backup was successful</returns> public async Task <bool> BackupAsync(IBackupInfo backupInformation) { using (await AsyncLock.LockAsync()) { RL.Logger?.LogInformationSource($"A backup has been requested for {backupInformation.GameDisplayName}"); try { // Make sure we have write access to the backup location if (!FileManager.CheckDirectoryWriteAccess(RCPServices.Data.BackupLocation + AppViewModel.BackupFamily)) { RL.Logger?.LogInformationSource($"Backup failed - backup location lacks write access"); // Request to restart as admin await RCPServices.App.RequestRestartAsAdminAsync(); return(false); } // Get the backup information and group items by ID var backupInfoByID = backupInformation.BackupDirectories.GroupBy(x => x.ID).ToList(); RL.Logger?.LogDebugSource($"{backupInfoByID.Count} backup directory ID groups were found"); // Get the backup info var backupInfo = new List <BackupDir>(); // Get the latest save info from each group foreach (var group in backupInfoByID) { if (group.Count() == 1) { backupInfo.Add(group.First()); continue; } RL.Logger?.LogDebugSource($"ID {group.Key} has multiple items"); // Find which group is the latest one var groupItems = new Dictionary <BackupDir, DateTime>(); foreach (BackupDir item in group) { // ReSharper disable once ConvertIfStatementToConditionalTernaryExpression if (!item.DirPath.DirectoryExists) { groupItems.Add(item, DateTime.MinValue); } else { groupItems.Add(item, Directory.GetFiles(item.DirPath, item.SearchPattern, item.SearchOption).Select(x => new FileInfo(x).LastWriteTime).OrderByDescending(x => x).FirstOrDefault()); } } // Get the latest directory var latestDir = groupItems.OrderByDescending(x => x.Value).First().Key; // Add the latest directory backupInfo.Add(latestDir); RL.Logger?.LogDebugSource($"The most recent backup directory was found under {latestDir.DirPath}"); } // Make sure all the directories to back up exist if (!backupInfo.Select(x => x.DirPath).DirectoriesExist()) { RL.Logger?.LogInformationSource($"Backup failed - the input directories could not be found"); await Services.MessageUI.DisplayMessageAsync(String.Format(Resources.Backup_MissingDirectoriesError, backupInformation.GameDisplayName), Resources.Backup_FailedHeader, MessageType.Error); return(false); } // Check if the backup should be compressed bool compress = RCPServices.Data.CompressBackups; RL.Logger?.LogDebugSource(compress ? $"The backup will be compressed" : $"The backup will not be compressed"); // Perform the backup and keep track if it succeeded bool success = await(compress ? PerformCompressedBackupAsync(backupInfo, backupInformation.CompressedBackupLocation, backupInformation.GameDisplayName) : PerformBackupAsync(backupInfo, backupInformation.BackupLocation, backupInformation.GameDisplayName)); if (!success) { return(false); } // Remove old backups for the game try { var newBackup = compress ? backupInformation.CompressedBackupLocation : backupInformation.BackupLocation; foreach (RCPBackup existingBackup in backupInformation.ExistingBackups) { // Ignore the newly created backup if (existingBackup.Path.CorrectPathCasing().Equals(newBackup.CorrectPathCasing())) { continue; } if (existingBackup.IsCompressed) { // Delete the file FileManager.DeleteFile(existingBackup.Path); RL.Logger?.LogInformationSource("Compressed leftover backup was deleted"); } else { // Delete the directory FileManager.DeleteDirectory(existingBackup.Path); RL.Logger?.LogInformationSource("Non-compressed leftover backup was deleted"); } } } catch (Exception ex) { ex.HandleError("Deleting leftover backups"); } return(true); } catch (Exception ex) { // Handle error ex.HandleError("Backing up game", backupInformation); // Display message to user await Services.MessageUI.DisplayExceptionMessageAsync(ex, String.Format(Resources.Backup_Failed, backupInformation.GameDisplayName), Resources.Backup_FailedHeader); // Return that backup did not succeed return(false); } } }