/// <summary> /// Get backup directory /// </summary> protected override string GetBackupDirectory(BackupMedia media) { switch (media) { case BackupMedia.Private: return(System.Environment.GetFolderPath(System.Environment.SpecialFolder.LocalApplicationData)); case BackupMedia.Public: var ossec = (ApplicationContext.Current as AndroidApplicationContext).CurrentActivity as IOperatingSystemSecurityService; if (ossec.HasPermission(PermissionType.FileSystem) || ossec.RequestPermission(PermissionType.FileSystem)) { var retVal = System.IO.Path.Combine( A.OS.Environment.ExternalStorageDirectory.AbsolutePath, A.OS.Environment.DirectoryDocuments, (AndroidApplicationContext.Current as AndroidApplicationContext).AndroidApplication.PackageName); if (!System.IO.Directory.Exists(retVal)) { System.IO.Directory.CreateDirectory(retVal); } return(retVal); } else { return(System.Environment.GetFolderPath(System.Environment.SpecialFolder.LocalApplicationData)); } default: throw new PlatformNotSupportedException("Don't support external media on this platform"); } }
/// <summary> /// Rremove t /// </summary> /// <param name="media"></param> /// <param name="backupDescriptor"></param> public void RemoveBackup(BackupMedia media, string backupDescriptor) { // Make a determination that the user is allowed to perform this action if (AuthenticationContext.Current.Principal != AuthenticationContext.SystemPrincipal) { new PolicyPermission(System.Security.Permissions.PermissionState.Unrestricted, PermissionPolicyIdentifiers.ExportClinicalData).Demand(); } var directoryName = this.GetBackupDirectory(media); this.m_tracer.TraceInfo("Removing backup {0}...", backupDescriptor); var expectedFile = Path.ChangeExtension(Path.Combine(directoryName, backupDescriptor), ".sdbk"); if (!File.Exists(expectedFile)) { throw new FileNotFoundException($"Cannot find backup with descriptor {backupDescriptor}"); } else { try { File.Delete(expectedFile); } catch (Exception) { this.m_tracer.TraceError("Error removing backup descriptor {0}", backupDescriptor); throw new Exception($"Error removing backup descriptor {backupDescriptor}"); } } }
/// <summary> /// Get backup directory /// </summary> protected virtual string GetBackupDirectory(BackupMedia media) { String retVal = String.Empty; switch (media) { case BackupMedia.Private: retVal = Path.Combine(ApplicationServiceContext.Current.GetService <IConfigurationPersister>().ApplicationDataDirectory, "backup"); if (!Directory.Exists(retVal)) { Directory.CreateDirectory(retVal); } break; case BackupMedia.Public: retVal = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments); // Sometimes my documents isn't available if (String.IsNullOrEmpty(retVal)) { retVal = Environment.GetFolderPath(Environment.SpecialFolder.CommonDocuments); } break; default: throw new PlatformNotSupportedException("Don't support external media on this platform"); } return(retVal); }
/// <summary> /// Get the list of backup descriptors /// </summary> public IEnumerable <string> GetBackups(BackupMedia media) { var directoryName = this.GetBackupDirectory(media); this.m_tracer.TraceInfo("Getting {0} for backups...", directoryName); return(Directory.GetFiles(directoryName, "*.sdbk").OrderByDescending(o => new FileInfo(o).CreationTime).Select(o => Path.GetFileNameWithoutExtension(o))); }
/// <summary> /// Get the last backup date /// </summary> private string GetLastBackup(BackupMedia media) { var directoryName = this.GetBackupDirectory(media); this.m_tracer.TraceInfo("Checking {0} for backups...", directoryName); System.Diagnostics.Trace.TraceInformation(directoryName); return(Directory.GetFiles(directoryName, "*.sdbk").OrderByDescending(o => o).FirstOrDefault()); }
/// <summary> /// Get backup directory /// </summary> protected virtual string GetBackupDirectory(BackupMedia media) { switch (media) { case BackupMedia.Private: return(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData)); case BackupMedia.Public: return(Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments)); default: throw new PlatformNotSupportedException("Don't support external media on this platform"); } }
/// <summary> /// Returns a default location for tail log backup /// If the first backup media is from Microsoft Azure, a Microsoft Azure url for the Tail log backup file is returned /// </summary> internal string GetDefaultTailLogbackupFile(string databaseName, RestorePlan restorePlan) { if (string.IsNullOrEmpty(databaseName) || restorePlan == null) { return(string.Empty); } if (restorePlan.TailLogBackupOperation != null && restorePlan.TailLogBackupOperation.Devices != null) { restorePlan.TailLogBackupOperation.Devices.Clear(); } string folderpath = string.Empty; BackupMedia firstBackupMedia = this.GetFirstBackupMedia(restorePlan); string filename = this.SanitizeFileName(databaseName) + "_LogBackup_" + this.GetServerCurrentDateTime().ToString("yyyy-MM-dd_HH-mm-ss") + ".bak"; if (firstBackupMedia != null && firstBackupMedia.MediaType == DeviceType.Url) { // the uri will use the same container as the container of the first backup media Uri uri; if (Uri.TryCreate(firstBackupMedia.MediaName, UriKind.Absolute, out uri)) { UriBuilder uriBuilder = new UriBuilder(); uriBuilder.Scheme = uri.Scheme; uriBuilder.Host = uri.Host; if (uri.AbsolutePath.Length > 0) { string[] parts = uri.AbsolutePath.Split('/'); string newPath = string.Join("/", parts, 0, parts.Length - 1); if (newPath.EndsWith("/")) { newPath = newPath.Substring(0, newPath.Length - 1); } uriBuilder.Host = uriBuilder.Host + newPath; } uriBuilder.Path = filename; string urlFilename = uriBuilder.Uri.AbsoluteUri; if (restorePlan.TailLogBackupOperation != null && restorePlan.TailLogBackupOperation.Devices != null) { restorePlan.TailLogBackupOperation.Devices.Add(new BackupDeviceItem(urlFilename, DeviceType.Url)); } return(urlFilename); } } folderpath = this.GetDefaultBackupFolder(); if (restorePlan.TailLogBackupOperation != null && restorePlan.TailLogBackupOperation.Devices != null) { restorePlan.TailLogBackupOperation.Devices.Add(new BackupDeviceItem(PathWrapper.Combine(folderpath, filename), DeviceType.File)); } return(PathWrapper.Combine(folderpath, filename)); }
/// <summary> /// Restore the specified data /// </summary> public void Restore(BackupMedia media, String password = null) { // Get the last backup var lastBackup = this.GetLastBackup(media); if (!ApplicationContext.Current.Confirm(String.Format(Strings.locale_backup_restore_confirm, new FileInfo(lastBackup).CreationTime.ToString("ddd MMM dd, yyyy")))) { return; } try { this.m_tracer.TraceInfo("Beginning restore of {0}...", lastBackup); ApplicationContext.Current?.SetProgress(Strings.locale_backup_restore, 0.0f); var sourceDirectory = XamarinApplicationContext.Current.ConfigurationManager.ApplicationDataDirectory; using (var fs = File.OpenRead(lastBackup)) using (var gzs = new GZipStream(fs, CompressionMode.Decompress)) using (var tr = TarReader.Open(gzs)) { // Move to next entry & copy while (tr.MoveToNextEntry()) { this.m_tracer.TraceVerbose("Extracting : {0}", tr.Entry.Key); if (tr.Entry.Key == "DISCLAIMER.TXT" || tr.Entry.Key == ".appinfo.xml") { continue; } var destDir = Path.Combine(sourceDirectory, tr.Entry.Key.Replace('/', Path.DirectorySeparatorChar)); if (!Directory.Exists(Path.GetDirectoryName(destDir))) { Directory.CreateDirectory(Path.GetDirectoryName(destDir)); } if (!tr.Entry.IsDirectory) { using (var s = tr.OpenEntryStream()) using (var ofs = File.Create(Path.Combine(sourceDirectory, tr.Entry.Key))) s.CopyTo(ofs); } } } } catch (Exception ex) { this.m_tracer.TraceError("Error restoring backup {0}: {1}", lastBackup, ex); throw; } }
/// <summary> /// Restore the specified data /// </summary> public void Restore(BackupMedia media, String backupDescriptor = null, String password = null) { // Get the last backup String backupFile = null; if (String.IsNullOrEmpty(backupDescriptor)) { backupFile = this.GetLastBackup(media); } else { if (!Path.IsPathRooted(backupDescriptor)) { var directoryName = this.GetBackupDirectory(media); backupFile = Path.ChangeExtension(Path.Combine(directoryName, backupDescriptor), ".sdbk"); } else { backupFile = backupDescriptor; } if (!File.Exists(backupFile)) { throw new FileNotFoundException($"Cannot find backup with descriptor {backupDescriptor}"); } } if (ApplicationContext.Current?.Confirm(String.Format(Strings.locale_backup_restore_confirm, new FileInfo(backupFile).CreationTime.ToString("ddd MMM dd, yyyy"))) == false) { return; } try { this.m_tracer.TraceInfo("Beginning restore of {0}...", backupFile); ApplicationContext.Current?.SetProgress(Strings.locale_backup_restore, 0.0f); var sourceDirectory = ApplicationContext.Current.ConfigurationPersister.ApplicationDataDirectory; this.RestoreFiles(backupFile, password, sourceDirectory); } catch (Exception ex) { this.m_tracer.TraceError("Error restoring backup {0}: {1}", backupFile, ex); throw; } }
/// <summary> /// Get backup directory /// </summary> protected override string GetBackupDirectory(BackupMedia media) { switch (media) { case BackupMedia.Private: return(System.Environment.GetFolderPath(System.Environment.SpecialFolder.LocalApplicationData)); case BackupMedia.Public: var retVal = System.IO.Path.Combine( A.OS.Environment.ExternalStorageDirectory.AbsolutePath, A.OS.Environment.DirectoryDocuments, (AndroidApplicationContext.Current as AndroidApplicationContext).AndroidApplication.PackageName); if (!System.IO.Directory.Exists(retVal)) { System.IO.Directory.CreateDirectory(retVal); } return(retVal); default: throw new PlatformNotSupportedException("Don't support external media on this platform"); } }
/// <summary> /// Perform backup on the specified media /// </summary> public void Backup(BackupMedia media, String password = null) { // Make a determination that the user is allowed to perform this action if (AuthenticationContext.Current.Principal != AuthenticationContext.SystemPrincipal) { new PolicyPermission(System.Security.Permissions.PermissionState.Unrestricted, PolicyIdentifiers.ExportClinicalData).Demand(); } // Get the output medium var directoryName = this.GetBackupDirectory(media); string fileName = Path.Combine(directoryName, $"oizdc-{DateTime.Now.ToString("yyyyMMddHHmm")}.oiz.tar"); // Confirm if the user really really wants to backup if (String.IsNullOrEmpty(password) && !ApplicationContext.Current.Confirm(Strings.locale_backup_confirm)) { return; } // TODO: Audit the backup to the data to the central server AuditUtil.AuditDataExport(); // Try to backup the data try { this.m_tracer.TraceInfo("Beginning backup to {0}..", fileName); ApplicationContext.Current?.SetProgress(Strings.locale_backup, 0.25f); // Backup folders first var sourceDirectory = XamarinApplicationContext.Current.ConfigurationManager.ApplicationDataDirectory; using (var fs = File.Create(fileName)) using (var writer = new SharpCompress.Writers.Tar.TarWriter(fs, new SharpCompress.Writers.WriterOptions(SharpCompress.Common.CompressionType.None))) { this.BackupDirectory(writer, sourceDirectory, sourceDirectory); var appInfo = new ApplicationInfo(false).ToDiagnosticReport(); // Output appInfo using (var ms = new MemoryStream()) { XmlSerializer xsz = new XmlSerializer(appInfo.GetType()); xsz.Serialize(ms, appInfo); ms.Flush(); ms.Seek(0, SeekOrigin.Begin); writer.Write(".appinfo.xml", ms, DateTime.Now); } // Output declaration statement using (var ms = new MemoryStream(Encoding.UTF8.GetBytes($"User {(AuthenticationContext.Current?.Principal?.Identity?.Name ?? "SYSTEM")} created this backup on {DateTime.Now}. The end user was asked to confirm this decision to backup and acknolwedges all responsibilities for guarding this file."))) writer.Write("DISCLAIMER.TXT", ms, DateTime.Now); } this.m_tracer.TraceInfo("Beginning compression {0}..", fileName); using (var fs = File.OpenRead(fileName)) using (var gzs = new GZipStream(File.Create(fileName + ".gz"), CompressionMode.Compress)) { int br = 4096; byte[] buffer = new byte[br]; while (br == 4096) { br = fs.Read(buffer, 0, 4096); gzs.Write(buffer, 0, br); ApplicationContext.Current?.SetProgress(Strings.locale_backup_compressing, (float)fs.Position / (float)fs.Length * 0.5f + 0.5f); } } File.Delete(fileName); } catch (Exception ex) { this.m_tracer.TraceError("Error backing up to {0}: {1}", fileName, ex); throw; } }
/// <summary> /// Get the last backup date /// </summary> private string GetLastBackup(BackupMedia media) { var directoryName = this.GetBackupDirectory(media); return(Directory.GetFiles(directoryName, "*.oiz.tar.gz").OrderByDescending(o => o).FirstOrDefault()); }
/// <summary> /// Determine if backup is available on the specified media /// </summary> public bool HasBackup(BackupMedia media) { return(this.GetLastBackup(media) != null); }
/// <summary> /// Perform backup on the specified media /// </summary> public void Backup(BackupMedia media, String password = null) { // Make a determination that the user is allowed to perform this action if (AuthenticationContext.Current.Principal != AuthenticationContext.SystemPrincipal) { new PolicyPermission(System.Security.Permissions.PermissionState.Unrestricted, PermissionPolicyIdentifiers.ExportClinicalData).Demand(); } // Get the output medium var directoryName = this.GetBackupDirectory(media); string fileName = Path.Combine(directoryName, $"sdbdc-{DateTime.Now.ToString("yyyy-MM-dd-HH-mm")}.tar"); // Confirm if the user really really wants to backup if (String.IsNullOrEmpty(password) && !ApplicationContext.Current.Confirm(Strings.locale_backup_confirm)) { return; } // TODO: Audit the backup to the data to the central server AuditUtil.AuditDataExport(); // Try to backup the data try { this.m_tracer.TraceInfo("Beginning backup to {0}..", fileName); ApplicationContext.Current?.SetProgress(Strings.locale_backup, 0.25f); // Backup folders first var sourceDirectory = ApplicationContext.Current.ConfigurationPersister.ApplicationDataDirectory; try { using (var fs = File.Create(fileName)) using (var writer = new SharpCompress.Writers.Tar.TarWriter(fs, new TarWriterOptions(SharpCompress.Common.CompressionType.None, true))) { this.BackupDirectory(writer, sourceDirectory, sourceDirectory); var appInfo = new DiagnosticReport() { ApplicationInfo = new DiagnosticApplicationInfo(AppDomain.CurrentDomain.GetAssemblies().FirstOrDefault(o => o.DefinedTypes.Any(t => t.Name == "Program" || t.Name == "SplashActivity")) ?? typeof(SanteDBConfiguration).Assembly) }; // Output appInfo using (var ms = new MemoryStream()) { XmlSerializer xsz = XmlModelSerializerFactory.Current.CreateSerializer(appInfo.GetType()); xsz.Serialize(ms, appInfo); ms.Flush(); ms.Seek(0, SeekOrigin.Begin); writer.Write(".appinfo.xml", ms, DateTime.Now); } // Output declaration statement using (var ms = new MemoryStream(Encoding.UTF8.GetBytes($"User {(AuthenticationContext.Current?.Principal?.Identity?.Name ?? "SYSTEM")} created this backup on {DateTime.Now}. The end user was asked to confirm this decision to backup and acknolwedges all responsibilities for guarding this file."))) writer.Write("DISCLAIMER.TXT", ms, DateTime.Now); // Output databases var dcc = ApplicationServiceContext.Current.GetService <IDataManagementService>(); var dbBackupLocation = dcc.Backup(""); this.BackupDirectory(writer, dbBackupLocation, dbBackupLocation); try { Directory.Delete(dbBackupLocation, true); } catch { } // Now include the configuration file // HACK: This is a total hack to support restore in place if (!String.IsNullOrEmpty(password)) { using (var ms = new MemoryStream()) { ApplicationServiceContext.Current.GetService <IConfigurationManager>().Configuration.Save(ms); ms.Seek(0, SeekOrigin.Begin); var config = SanteDBConfiguration.Load(ms); /// Load a new copy off the stream // Set the security system not to encrypt the device secret config.GetSection <SecurityConfigurationSection>().PlainTextSecret = true; using (var oms = new MemoryStream()) { config.Save(oms); oms.Flush(); oms.Seek(0, SeekOrigin.Begin); writer.Write("santedb.config", oms, DateTime.Now); } } } } this.m_tracer.TraceInfo("Beginning compression {0}..", fileName); using (var fileStream = File.Create(Path.ChangeExtension(fileName, "sdbk"))) { Stream outStream = fileStream; try { // Write header fileStream.Write(MAGIC, 0, MAGIC.Length); // Encrypt if (!String.IsNullOrEmpty(password)) { fileStream.WriteByte(1); var cryptProvider = AesCryptoServiceProvider.Create(); var passKey = ASCIIEncoding.ASCII.GetBytes(password); passKey = Enumerable.Range(0, 32).Select(o => passKey.Length > o ? passKey[o] : (byte)0).ToArray(); cryptProvider.Key = passKey; cryptProvider.GenerateIV(); fileStream.Write(BitConverter.GetBytes(cryptProvider.IV.Length), 0, 4); fileStream.Write(cryptProvider.IV, 0, cryptProvider.IV.Length); outStream = new CryptoStream(fileStream, cryptProvider.CreateEncryptor(), CryptoStreamMode.Write); } else { fileStream.WriteByte(0); } using (var fs = File.OpenRead(fileName)) using (var gzs = new GZipStream(outStream, CompressionMode.Compress)) { fs.CopyTo(gzs); } } finally { if (outStream != fileStream) { outStream.Close(); } } } } finally { File.Delete(fileName); } } catch (Exception ex) { this.m_tracer.TraceError("Error backing up to {0}: {1}", fileName, ex); throw; } }