diff options
| author | Roy Ben-Shabat <Roy@Twine-s.com> | 2019-11-26 17:33:02 +0200 |
|---|---|---|
| committer | Roy Ben-Shabat <Roy@Twine-s.com> | 2019-11-26 17:33:02 +0200 |
| commit | ca29510e1e336c4d68aaa926cfea6eb72ce42779 (patch) | |
| tree | 298c10a1567df22cf594054271dd5ce656f09c12 /Software/Visual_Studio/PPC/Tango.PPC.Common | |
| parent | 6c43f97559613443e781917a827c3b644db03490 (diff) | |
| download | Tango-ca29510e1e336c4d68aaa926cfea6eb72ce42779.tar.gz Tango-ca29510e1e336c4d68aaa926cfea6eb72ce42779.zip | |
Working on backup/restore...
Diffstat (limited to 'Software/Visual_Studio/PPC/Tango.PPC.Common')
7 files changed, 426 insertions, 14 deletions
diff --git a/Software/Visual_Studio/PPC/Tango.PPC.Common/BackupRestore/BackupFile.cs b/Software/Visual_Studio/PPC/Tango.PPC.Common/BackupRestore/BackupFile.cs index ebb7b9fcd..c687377a6 100644 --- a/Software/Visual_Studio/PPC/Tango.PPC.Common/BackupRestore/BackupFile.cs +++ b/Software/Visual_Studio/PPC/Tango.PPC.Common/BackupRestore/BackupFile.cs @@ -1,4 +1,5 @@ -using System; +using Newtonsoft.Json; +using System; using System.Collections.Generic; using System.Linq; using System.Text; @@ -14,6 +15,11 @@ namespace Tango.PPC.Common.BackupRestore public class BackupFile { /// <summary> + /// Gets or sets the backup file version. + /// </summary> + public int Version { get; set; } + + /// <summary> /// Gets or sets the backup name. /// </summary> public String Name { get; set; } @@ -61,5 +67,15 @@ namespace Tango.PPC.Common.BackupRestore Settings = new BackupSettings(); JobFiles = new List<JobFile>(); } + + public String ToJson() + { + return JsonConvert.SerializeObject(this); + } + + public static BackupFile FromJson(String json) + { + return JsonConvert.DeserializeObject<BackupFile>(json); + } } } diff --git a/Software/Visual_Studio/PPC/Tango.PPC.Common/BackupRestore/BackupRestoreStage.cs b/Software/Visual_Studio/PPC/Tango.PPC.Common/BackupRestore/BackupRestoreStage.cs index 3d5de1122..27fc06ec4 100644 --- a/Software/Visual_Studio/PPC/Tango.PPC.Common/BackupRestore/BackupRestoreStage.cs +++ b/Software/Visual_Studio/PPC/Tango.PPC.Common/BackupRestore/BackupRestoreStage.cs @@ -30,12 +30,22 @@ namespace Tango.PPC.Common.BackupRestore CompressingFiles, //Restore - [Description("Validating backup...")] - ValidatingBackup, + [Description("Extracting backup configuration...")] + ExtractingBackupConfiguration, [Description("Extracting content...")] ExtractingContent, + [Description("Restoring user settings...")] + RestoringSettings, + [Description("Restoring jobs...")] + RestoringJobs, [Description("Restoring data...")] RestoringDatabase, + [Description("Removing temporary files...")] + RemovingTemporaryFiles, + [Description("Restoring firmware version...")] + RestoringFirmware, + [Description("Rolling back changes...")] + RollingBackChanges, [Description("Done")] diff --git a/Software/Visual_Studio/PPC/Tango.PPC.Common/BackupRestore/DefaultBackupManager.cs b/Software/Visual_Studio/PPC/Tango.PPC.Common/BackupRestore/DefaultBackupManager.cs index 31bdefd5d..05693c794 100644 --- a/Software/Visual_Studio/PPC/Tango.PPC.Common/BackupRestore/DefaultBackupManager.cs +++ b/Software/Visual_Studio/PPC/Tango.PPC.Common/BackupRestore/DefaultBackupManager.cs @@ -16,6 +16,9 @@ using Tango.Settings; using Tango.Core.DB; using System.Data.SqlClient; using Ionic.Zip; +using Tango.BL.Entities; +using Tango.PPC.Common.Authentication; +using Tango.Integration.Upgrade; namespace Tango.PPC.Common.BackupRestore { @@ -23,6 +26,7 @@ namespace Tango.PPC.Common.BackupRestore { private const string BACKUP_FILE_NAME = "Backup.json"; private const string DATABASE_FILE_NAME = "Tango.bak"; + private const int VERSION = 1; [TangoInject(TangoInjectMode.WhenAvailable)] private IPPCApplicationManager _applicationManager; @@ -30,6 +34,9 @@ namespace Tango.PPC.Common.BackupRestore [TangoInject(TangoInjectMode.WhenAvailable)] private IMachineProvider _machineProvider; + [TangoInject(TangoInjectMode.WhenAvailable)] + private IAuthenticationProvider _authenticationProvider; + public DefaultBackupManager() { TangoIOC.Default.Inject(this); @@ -41,17 +48,19 @@ namespace Tango.PPC.Common.BackupRestore { return Task.Factory.StartNew(() => { + var tempFolder = TemporaryManager.CreateFolder(); + try { //Basic - LogManager.Log($"Starting backup operation to file {filePath}."); + LogManager.Log($"Starting backup operation to file '{filePath}'..."); LogManager.Log($"Backup settings:\n{settings.ToJsonString()}"); OnProgress(BackupRestoreStage.Initializing); - var tempFolder = TemporaryManager.CreateFolder(); LogManager.Log($"Temporary folder created on {tempFolder.Path}."); BackupFile backupFile = new BackupFile(); + backupFile.Version = VERSION; backupFile.Date = DateTime.Now; backupFile.Settings = settings; backupFile.Name = name; @@ -134,7 +143,14 @@ namespace Tango.PPC.Common.BackupRestore var dataSource = ObservablesContext.GetActualDataSource(); using (var dbManager = DbManager.FromDataSource(dataSource)) { - dbManager.Backup(dataSource.Catalog, Path.Combine(tempFolder, DATABASE_FILE_NAME)); + Directory.CreateDirectory("C:\\Backups"); + var dbBackupFile = $"C:\\Backups\\{DATABASE_FILE_NAME}"; + if (File.Exists(dbBackupFile)) + { + File.Delete(dbBackupFile); + } + dbManager.Backup(dataSource.Catalog, dbBackupFile); + File.Move(dbBackupFile, Path.Combine(tempFolder, DATABASE_FILE_NAME)); } } catch (Exception ex) @@ -145,7 +161,7 @@ namespace Tango.PPC.Common.BackupRestore LogManager.Log("Database backup completed."); } - //Compression + //Backup.json try { OnProgress(BackupRestoreStage.WritingConfiguration); @@ -158,6 +174,7 @@ namespace Tango.PPC.Common.BackupRestore throw new IOException("Error writing backup configuration file.", ex); } + //Compression LogManager.Log($"Generating {filePath}..."); using (ZipFile zip = new ZipFile()) { @@ -176,22 +193,361 @@ namespace Tango.PPC.Common.BackupRestore zip.Save(filePath); } + tempFolder.Delete(); + + //Done LogManager.Log("Backup operation completed!!!"); - OnProgress(BackupRestoreStage.Done); + OnProgress(BackupRestoreStage.Done, 100, 100, false); } catch (Exception ex) { - OnProgress(BackupRestoreStage.Error); + tempFolder.Delete(); + + OnProgress(BackupRestoreStage.Error, 100, 100, false); LogManager.Log(ex, "Could not complete the backup operation."); throw ex; } }); } - public Task Restore(string filePath) + /// <summary> + /// Extracts the backup configuration from the specified backup file. + /// </summary> + /// <param name="filePath">The file path.</param> + /// <returns></returns> + public Task<BackupFile> ExtractBackupConfiguration(string filePath) + { + return Task.Factory.StartNew<BackupFile>(() => + { + using (ZipFile zip = ZipFile.Read(filePath)) + { + var reader = zip.Entries.SingleOrDefault(x => x.FileName == BACKUP_FILE_NAME).OpenReader(); + String json = String.Empty; + + using (StreamReader stReader = new StreamReader(reader)) + { + json = stReader.ReadToEnd(); + } + + var backupFile = BackupFile.FromJson(json); + reader.Close(); + reader.Dispose(); + return backupFile; + } + }); + } + + public Task<RestoreResult> Restore(string filePath, RestoreSettings settings) { - throw new NotImplementedException(); + TaskCompletionSource<RestoreResult> completionSource = new TaskCompletionSource<RestoreResult>(); + + String dbRollbackFile = null; + + Task.Factory.StartNew(() => + { + LogManager.Log($"Starting restore operation from file '{filePath}'..."); + OnProgress(BackupRestoreStage.Initializing); + + var tempFolder = TemporaryManager.CreateFolder(); + + var restoreResult = new RestoreResult() { FolderPath = tempFolder }; + + try + { + try + { + LogManager.Log("Creating database rollback file..."); + var dataSource = ObservablesContext.GetActualDataSource(); + using (var dbManager = DbManager.FromDataSource(dataSource)) + { + Directory.CreateDirectory("C:\\Backups"); + dbRollbackFile = $"C:\\Backups\\{Path.GetRandomFileName()}.bak"; + LogManager.Log($"Creating database rollback to '{dbRollbackFile}'..."); + dbManager.Backup(dataSource.Catalog, dbRollbackFile); + LogManager.Log("Database rollback created successfully."); + } + } + catch (Exception ex) + { + throw new InvalidDataException("Error creating database rollback file.", ex); + } + + //Basic + LogManager.Log("Extracting backup file configuration..."); + + BackupFile backupFile = null; + + //Extract Configuration + try + { + OnProgress(BackupRestoreStage.ExtractingBackupConfiguration); + backupFile = ExtractBackupConfiguration(filePath).Result; + } + catch (Exception ex) + { + throw new IOException("Error extracting backup configuration.", ex); + } + + //Validate Version + if (backupFile.Version > VERSION) + { + throw new NotSupportedException($"Backup file version {backupFile} is not supported."); + } + + LogManager.Log($"Backup settings:\n{backupFile.Settings.ToJsonString()}"); + + if (backupFile.Settings.Mode == BackupMode.Jobs) + { + //Restore Jobs + OnProgress(BackupRestoreStage.RestoringJobs); + LogManager.Log("Starting jobs restore..."); + + using (ObservablesContext db = ObservablesContext.CreateDefault()) + { + var jobs = db.Jobs.ToList(); + var jobFiles = backupFile.JobFiles; + + if (settings.AllowDeleteJobs) + { + try + { + LogManager.Log("Removing existing jobs..."); + foreach (var job in jobs.ToList()) + { + LogManager.Log($"Removing job '{job.Name}'..."); + job.Delete(db); + jobs.Remove(job); + } + + db.SaveChanges(); + } + catch (Exception ex) + { + throw new Exception("Error removing existing jobs from database.", ex); + } + } + + foreach (var jobFile in jobFiles) + { + LogManager.Log($"Importing job '{jobFile.Name}'..."); + + try + { + var existingJob = jobs.FirstOrDefault(x => x.Name == jobFile.Name); + + if (existingJob != null) + { + if (settings.OverwriteExistingJobs) + { + try + { + LogManager.Log("Job already exist, overwriting..."); + + var newJob = Job.FromJobFile(jobFile, _machineProvider.Machine.Guid, _authenticationProvider.CurrentUser.Guid).Result; + newJob.Guid = existingJob.Guid; + + existingJob.Delete(db); + jobs.Remove(existingJob); + + db.SaveChanges(); + db.Jobs.Add(newJob); + db.SaveChanges(); + } + catch (Exception ex) + { + throw new InvalidOperationException("Error overwriting job.", ex); + } + } + } + else + { + var newJob = Job.FromJobFile(jobFile, _machineProvider.Machine.Guid, _authenticationProvider.CurrentUser.Guid).Result; + db.Jobs.Add(newJob); + } + } + catch (Exception ex) + { + throw new InvalidOperationException("Error importing job.", ex); + } + + OnProgress(BackupRestoreStage.RestoringJobs, jobFiles.IndexOf(jobFile) + 1, jobFiles.Count, false); + } + + OnProgress(BackupRestoreStage.RestoringJobs); + db.SaveChanges(); + } + } + else + { + //Extract zip file + LogManager.Log("Starting backup file extraction..."); + OnProgress(BackupRestoreStage.ExtractingContent); + try + { + using (ZipFile zip = new ZipFile(filePath)) + { + zip.ExtractProgress += (x, e) => + { + if (e.EventType == ZipProgressEventType.Extracting_AfterExtractEntry) + { + LogManager.Log($"Extracting '{e.CurrentEntry.FileName}'..."); + OnProgress(BackupRestoreStage.ExtractingContent, e.EntriesExtracted + 1, e.EntriesTotal, false); + } + }; + + zip.ParallelDeflateThreshold = -1; + zip.ExtractAll(tempFolder); + } + } + catch (Exception ex) + { + throw new IOException("Error extracting backup content.", ex); + } + + //Overwrite settings + LogManager.Log("Validating user settings..."); + if (backupFile.SettingsFile != null) + { + try + { + LogManager.Log("Overwriting settings file..."); + OnProgress(BackupRestoreStage.RestoringSettings); + File.WriteAllText(SettingsManager.Default.FilePath, backupFile.SettingsFile); + } + catch (Exception ex) + { + throw new IOException("Error overwriting user settings.", ex); + } + } + else + { + LogManager.Log("No user settings, skipping..."); + } + + //Restore database + var backupFilePath = Path.Combine(tempFolder, DATABASE_FILE_NAME); + LogManager.Log($"Looking for file database backup on '{backupFilePath}'..."); + if (File.Exists(backupFilePath)) + { + LogManager.Log("Restoring database..."); + OnProgress(BackupRestoreStage.RestoringDatabase); + try + { + var dataSource = ObservablesContext.GetActualDataSource(); + using (var dbManager = DbManager.FromDataSource(dataSource)) + { + Directory.CreateDirectory("C:\\Backups"); + var dbBackupFile = $"C:\\Backups\\{DATABASE_FILE_NAME}"; + File.Copy(backupFilePath, dbBackupFile, true); + dbManager.Restore(dataSource.Catalog, dbBackupFile); + File.Delete(dbBackupFile); + } + } + catch (Exception ex) + { + throw new IOException("Error restoring database backup", ex); + } + + LogManager.Log("Database backup completed."); + } + else + { + LogManager.Log("Database backup file not found, skipping..."); + } + + //Remove extra files from application temp folder + OnProgress(BackupRestoreStage.RemovingTemporaryFiles); + LogManager.Log("Removing redundant files from temp folder..."); + try + { + File.Delete(backupFilePath); + } + catch { } + try + { + File.Delete(Path.Combine(tempFolder, BACKUP_FILE_NAME)); + } + catch { } + + //Update firmware + var tfpFile = Path.Combine(tempFolder, "firmware_package.tfp"); + LogManager.Log($"Looking for tfp file on '{tfpFile}'..."); + if (File.Exists(tfpFile)) + { + OnProgress(BackupRestoreStage.RestoringFirmware); + LogManager.Log("Restoring firmware version..."); + + var stream = new FileStream(tfpFile, FileMode.Open); + + _machineProvider.MachineOperator.FirmwareUpgradeMode = FirmwareUpgradeModes.DFU | FirmwareUpgradeModes.TFP_PACKAGE; + + var handler = _machineProvider.MachineOperator.UpgradeFirmware(stream).Result; + handler.Failed += (_, ex) => + { + stream.Dispose(); + throw ex; + }; + handler.Completed += (_, __) => + { + OnProgress(BackupRestoreStage.RestoringFirmware, 100, 100, false); + stream.Dispose(); + OnProgress(BackupRestoreStage.Done, 100, 100, false); + completionSource.SetResult(restoreResult); + }; + handler.Canceled += (_, __) => + { + stream.Dispose(); + throw new Exception("The operation has been canceled."); + }; + handler.Progress += (_, e) => + { + OnProgress(BackupRestoreStage.RestoringFirmware, e.Current, e.Total, false); + }; + } + else + { + LogManager.Log("Firmware package file not found, skipping..."); + OnProgress(BackupRestoreStage.Done, 100, 100, false); + completionSource.SetResult(restoreResult); + } + } + } + catch (Exception ex) + { + LogManager.Log("Rolling back database changes..."); + + var dataSource = ObservablesContext.GetActualDataSource(); + using (var dbManager = DbManager.FromDataSource(dataSource)) + { + try + { + OnProgress(BackupRestoreStage.RollingBackChanges); + dbManager.Restore(dataSource.Catalog, dbRollbackFile); + LogManager.Log("Database restored successfully."); + } + catch (Exception e) + { + LogManager.Log(e, "Error rolling back database."); + } + finally + { + try + { + File.Delete(dbRollbackFile); + } + catch { } + } + } + + tempFolder.Delete(); + OnProgress(BackupRestoreStage.Error, 100, 100, false); + LogManager.Log(ex, "Could not complete the restore operation."); + completionSource.SetException(ex); + } + }); + + return completionSource.Task; } protected virtual void OnProgress(BackupRestoreStage stage, double progress = 0, double maxProgress = 100, bool isIntermediate = true) diff --git a/Software/Visual_Studio/PPC/Tango.PPC.Common/BackupRestore/IBackupManager.cs b/Software/Visual_Studio/PPC/Tango.PPC.Common/BackupRestore/IBackupManager.cs index 8ff8a434c..ae1884677 100644 --- a/Software/Visual_Studio/PPC/Tango.PPC.Common/BackupRestore/IBackupManager.cs +++ b/Software/Visual_Studio/PPC/Tango.PPC.Common/BackupRestore/IBackupManager.cs @@ -29,7 +29,15 @@ namespace Tango.PPC.Common.BackupRestore /// Restores a backup located in the specified file path. /// </summary> /// <param name="filePath">The file path.</param> + /// <param name="settings">The restore settings</param> /// <returns></returns> - Task Restore(String filePath); + Task<RestoreResult> Restore(String filePath, RestoreSettings settings); + + /// <summary> + /// Extracts the backup configuration from the specified backup file. + /// </summary> + /// <param name="filePath">The file path.</param> + /// <returns></returns> + Task<BackupFile> ExtractBackupConfiguration(String filePath); } } diff --git a/Software/Visual_Studio/PPC/Tango.PPC.Common/BackupRestore/RestoreResult.cs b/Software/Visual_Studio/PPC/Tango.PPC.Common/BackupRestore/RestoreResult.cs index 0e1ac1a13..34d7c6298 100644 --- a/Software/Visual_Studio/PPC/Tango.PPC.Common/BackupRestore/RestoreResult.cs +++ b/Software/Visual_Studio/PPC/Tango.PPC.Common/BackupRestore/RestoreResult.cs @@ -6,7 +6,8 @@ using System.Threading.Tasks; namespace Tango.PPC.Common.BackupRestore { - class RestoreResult + public class RestoreResult { + public String FolderPath { get; set; } } } diff --git a/Software/Visual_Studio/PPC/Tango.PPC.Common/BackupRestore/RestoreSettings.cs b/Software/Visual_Studio/PPC/Tango.PPC.Common/BackupRestore/RestoreSettings.cs index 4e7bb2a55..a5b343302 100644 --- a/Software/Visual_Studio/PPC/Tango.PPC.Common/BackupRestore/RestoreSettings.cs +++ b/Software/Visual_Studio/PPC/Tango.PPC.Common/BackupRestore/RestoreSettings.cs @@ -3,10 +3,29 @@ using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; +using Tango.Core; namespace Tango.PPC.Common.BackupRestore { - class RestoreSettings + public class RestoreSettings : ExtendedObject { + private bool _allowDeleteJobs; + public bool AllowDeleteJobs + { + get { return _allowDeleteJobs; } + set { _allowDeleteJobs = value; RaisePropertyChangedAuto(); } + } + + private bool _overwriteExistingJobs; + public bool OverwriteExistingJobs + { + get { return _overwriteExistingJobs; } + set { _overwriteExistingJobs = value; RaisePropertyChangedAuto(); } + } + + public RestoreSettings() + { + OverwriteExistingJobs = true; + } } } diff --git a/Software/Visual_Studio/PPC/Tango.PPC.Common/Tango.PPC.Common.csproj b/Software/Visual_Studio/PPC/Tango.PPC.Common/Tango.PPC.Common.csproj index cff1e5a71..6375140d7 100644 --- a/Software/Visual_Studio/PPC/Tango.PPC.Common/Tango.PPC.Common.csproj +++ b/Software/Visual_Studio/PPC/Tango.PPC.Common/Tango.PPC.Common.csproj @@ -127,6 +127,8 @@ <Compile Include="BackupRestore\BackupRestoreStage.cs" /> <Compile Include="BackupRestore\DefaultBackupManager.cs" /> <Compile Include="BackupRestore\IBackupManager.cs" /> + <Compile Include="BackupRestore\RestoreResult.cs" /> + <Compile Include="BackupRestore\RestoreSettings.cs" /> <Compile Include="Connection\DefaultMachineProvider.cs" /> <Compile Include="Connection\IMachineProvider.cs" /> <Compile Include="Connection\MachineOperatorChangedEventArgs.cs" /> |
