static bool Run(List<string> args, Dictionary<string, string> options, bool first) { string allowedChars = ValidFilenameChars; if (options.ContainsKey("extended-chars")) allowedChars += options["extended-chars"]; else allowedChars += ExtendedChars; bool autoCreateFolders = Library.Utility.Utility.ParseBoolOption(options, "auto-create-folder"); Library.Interface.IBackend backend = Library.DynamicLoader.BackendLoader.GetBackend(args[0], options); if (backend == null) { Console.WriteLine("Unsupported backend"); Console.WriteLine(); Console.WriteLine("Supported backends: " + string.Join(",", Duplicati.Library.DynamicLoader.BackendLoader.Keys)); return false; } string disabledModulesValue; string enabledModulesValue; options.TryGetValue("enable-module", out enabledModulesValue); options.TryGetValue("disable-module", out disabledModulesValue); string[] enabledModules = enabledModulesValue == null ? new string[0] : enabledModulesValue.Trim().ToLower().Split(','); string[] disabledModules = disabledModulesValue == null ? new string[0] : disabledModulesValue.Trim().ToLower().Split(','); List<Library.Interface.IGenericModule> loadedModules = new List<IGenericModule>(); foreach (Library.Interface.IGenericModule m in Library.DynamicLoader.GenericLoader.Modules) if (Array.IndexOf<string>(disabledModules, m.Key.ToLower()) < 0 && (m.LoadAsDefault || Array.IndexOf<string>(enabledModules, m.Key.ToLower()) >= 0)) { m.Configure(options); loadedModules.Add(m); } try { List<Library.Interface.IFileEntry> curlist = null; try { curlist = backend.List(); } catch (FolderMissingException fex) { if (autoCreateFolders) { try { backend.CreateFolder(); curlist = backend.List(); } catch (Exception ex) { Console.WriteLine("Autocreate folder failed with message: " + ex.Message); } } if (curlist == null) throw fex; } foreach (Library.Interface.IFileEntry fe in curlist) if (!fe.IsFolder) { if (Library.Utility.Utility.ParseBoolOption(options, "auto-clean") && first) if (Library.Utility.Utility.ParseBoolOption(options, "force")) { Console.WriteLine("Auto clean, removing file: {0}", fe.Name); backend.Delete(fe.Name); continue; } else Console.WriteLine("Specify the --force flag to actually delete files"); Console.WriteLine("*** Remote folder is not empty, aborting"); return false; } int number_of_files = 10; int min_file_size = 1024; int max_file_size = 1024 * 1024 * 50; int min_filename_size = 5; int max_filename_size = 80; bool disableStreaming = Library.Utility.Utility.ParseBoolOption(options, "disable-streaming-transfers"); bool skipOverwriteTest = Library.Utility.Utility.ParseBoolOption(options, "skip-overwrite-test"); if (options.ContainsKey("number-of-files")) number_of_files = int.Parse(options["number-of-files"]); if (options.ContainsKey("min-file-size")) min_file_size = (int)Duplicati.Library.Utility.Sizeparser.ParseSize(options["min-file-size"], "mb"); if (options.ContainsKey("max-file-size")) max_file_size = (int)Duplicati.Library.Utility.Sizeparser.ParseSize(options["max-file-size"], "mb"); if (options.ContainsKey("min-filename-length")) min_filename_size = int.Parse(options["min-filename-length"]); if (options.ContainsKey("max-filename-length")) max_filename_size = int.Parse(options["max-filename-length"]); Random rnd = new Random(); System.Security.Cryptography.SHA256 sha = System.Security.Cryptography.SHA256.Create(); //Create random files using (Library.Utility.TempFolder tf = new Duplicati.Library.Utility.TempFolder()) { List<TempFile> files = new List<TempFile>(); for (int i = 0; i < number_of_files; i++) { StringBuilder filename = new StringBuilder(); int filenamelen = rnd.Next(min_filename_size, max_filename_size); for (int j = 0; j < filenamelen; j++) filename.Append(allowedChars[rnd.Next(0, allowedChars.Length)]); string localfilename = CreateRandomFile(tf, i, min_file_size, max_file_size, rnd); //Calculate local hash and length using (System.IO.FileStream fs = new System.IO.FileStream(localfilename, System.IO.FileMode.Open, System.IO.FileAccess.Read)) files.Add(new TempFile(filename.ToString(), localfilename, sha.ComputeHash(fs), fs.Length)); } byte[] dummyFileHash = null; if (!skipOverwriteTest) { Console.WriteLine("Uploading wrong files ..."); using (Library.Utility.TempFile dummy = Library.Utility.TempFile.WrapExistingFile(CreateRandomFile(tf, files.Count, 1024, 2048, rnd))) { using (System.IO.FileStream fs = new System.IO.FileStream(dummy, System.IO.FileMode.Open, System.IO.FileAccess.Read)) dummyFileHash = sha.ComputeHash(fs); //Upload a dummy file for entry 0 and the last one, they will be replaced by the real files afterwards //We upload entry 0 twice just to try to freak any internal cache list Uploadfile(dummy, 0, files[0].remotefilename, backend, disableStreaming); Uploadfile(dummy, 0, files[0].remotefilename, backend, disableStreaming); Uploadfile(dummy, files.Count - 1, files[files.Count - 1].remotefilename, backend, disableStreaming); } } Console.WriteLine("Uploading files ..."); for (int i = 0; i < files.Count; i++) Uploadfile(files[i].localfilename, i, files[i].remotefilename, backend, disableStreaming); Console.WriteLine("Verifying file list ..."); curlist = backend.List(); foreach (Library.Interface.IFileEntry fe in curlist) if (!fe.IsFolder) { bool found = false; foreach (TempFile tx in files) if (tx.remotefilename == fe.Name) { if (tx.found) Console.WriteLine("*** File with name {0} was found more than once", tx.remotefilename); found = true; tx.found = true; if (fe.Size > 0 && tx.length != fe.Size) Console.WriteLine("*** File with name {0} has size {1} but the size was reported as {2}", tx.remotefilename, tx.length, fe.Size); break; } if (!found) Console.WriteLine("*** File with name {0} was found on server but not uploaded!", fe.Name); } foreach (TempFile tx in files) if (!tx.found) Console.WriteLine("*** File with name {0} was uploaded but not found afterwards", tx.remotefilename); Console.WriteLine("Downloading files"); for (int i = 0; i < files.Count; i++) { using (Duplicati.Library.Utility.TempFile cf = new Duplicati.Library.Utility.TempFile()) { Exception e = null; Console.Write("Downloading file {0} ... ", i); try { if (backend is Library.Interface.IStreamingBackend && !disableStreaming) { using (System.IO.FileStream fs = new System.IO.FileStream(cf, System.IO.FileMode.Create, System.IO.FileAccess.Write, System.IO.FileShare.None)) using (NonSeekableStream nss = new NonSeekableStream(fs)) (backend as Library.Interface.IStreamingBackend).Get(files[i].remotefilename, nss); } else backend.Get(files[i].remotefilename, cf); e = null; } catch (Exception ex) { e = ex; } if (e != null) Console.WriteLine("failed\n*** Error: {0}", e.ToString()); else Console.WriteLine("done"); Console.Write("Checking hash ... "); using (System.IO.FileStream fs = new System.IO.FileStream(cf, System.IO.FileMode.Open, System.IO.FileAccess.Read)) if (Convert.ToBase64String(sha.ComputeHash(fs)) != Convert.ToBase64String(files[i].hash)) { if (dummyFileHash != null && Convert.ToBase64String(sha.ComputeHash(fs)) == Convert.ToBase64String(dummyFileHash)) Console.WriteLine("failed\n*** Downloaded file was the dummy file"); else Console.WriteLine("failed\n*** Downloaded file was corrupt"); } else Console.WriteLine("done"); } } Console.WriteLine("Deleting files..."); foreach (TempFile tx in files) try { backend.Delete(tx.remotefilename); } catch (Exception ex) { Console.WriteLine("*** Failed to delete file {0}, message: {1}", tx.remotefilename, ex.ToString()); } curlist = backend.List(); foreach (Library.Interface.IFileEntry fe in curlist) if (!fe.IsFolder) { Console.WriteLine("*** Remote folder contains {0} after cleanup", fe.Name); } } } finally { foreach (Library.Interface.IGenericModule m in loadedModules) if (m is IDisposable) ((IDisposable)m).Dispose(); } return true; }
public void ExecuteTask(IDuplicityTask task) { Dictionary<string, string> options = new Dictionary<string,string>(); //Set the log level to be that of the GUI options["log-level"] = Duplicati.Library.Logging.Log.LogLevel.ToString(); string destination = task.GetConfiguration(options); string results = ""; string parsedMessage = ""; m_isAborted = false; try { //TODO: Its a bit dirty to set the options after creating the instance using (Interface i = new Interface(destination, options)) { lock (m_lock) { m_stopReason = System.Windows.Forms.CloseReason.None; m_currentBackupControlInterface = i; } SetupControlInterface(); i.OperationProgress += new OperationProgressEvent(Duplicati_OperationProgress); switch (task.TaskType) { case DuplicityTaskType.FullBackup: case DuplicityTaskType.IncrementalBackup: { //Activate auto-cleanup options["auto-cleanup"] = ""; options["force"] = ""; if (task.Schedule.Task.KeepFull > 0) m_extraOperations++; if (!string.IsNullOrEmpty(task.Schedule.Task.KeepTime)) m_extraOperations++; Library.Utility.TempFolder tf = null; try { if (ProgressEvent != null) ProgressEvent(DuplicatiOperation.Backup, RunnerState.Started, task.Schedule.Name, "", 0, -1); if (task.Task.IncludeSetup) { //Make a copy of the current database tf = new Duplicati.Library.Utility.TempFolder(); string filename = System.IO.Path.Combine(tf, System.IO.Path.GetFileName(Program.DatabasePath)); System.IO.File.Copy(Program.DatabasePath, filename, true); using (System.Data.IDbConnection con = (System.Data.IDbConnection)Activator.CreateInstance(SQLiteLoader.SQLiteConnectionType)) { con.ConnectionString = "Data Source=" + filename; //Open the database, handle any encryption issues automatically Program.OpenDatabase(con); using (System.Data.IDbCommand cmd = con.CreateCommand()) { //Remove all log data to minimize the size of the database cmd.CommandText = "DELETE FROM CommandQueue;"; cmd.ExecuteNonQuery(); cmd.CommandText = "DELETE FROM Log;"; cmd.ExecuteNonQuery(); cmd.CommandText = "DELETE FROM LogBlob;"; cmd.ExecuteNonQuery(); //Free up unused space cmd.CommandText = "VACUUM;"; cmd.ExecuteNonQuery(); } } options["signature-control-files"] = filename; } options["full-if-sourcefolder-changed"] = ""; List<KeyValuePair<bool, string>> filters = new List<KeyValuePair<bool, string>>(); string[] sourceFolders = DynamicSetupHelper.GetSourceFolders(task.Task, new ApplicationSettings(task.Task.DataParent), filters); if (options.ContainsKey("filter")) filters.AddRange(Library.Utility.FilenameFilter.DecodeFilter(options["filter"])); options["filter"] = Library.Utility.FilenameFilter.EncodeAsFilter(filters); //At this point we register the backup as being in progress ((FullOrIncrementalTask)task).WriteBackupInProgress(Strings.DuplicatiRunner.ShutdownWhileBackupInprogress); results = i.Backup(sourceFolders); } finally { if (tf != null) tf.Dispose(); if (ProgressEvent != null) ProgressEvent(DuplicatiOperation.Backup, RunnerState.Stopped, task.Schedule.Name, "", 100, -1); } break; } case DuplicityTaskType.ListBackups: List<string> res = new List<string>(); foreach (ManifestEntry be in i.GetBackupSets()) { res.Add(be.Time.ToString()); foreach (ManifestEntry bei in be.Incrementals) res.Add(bei.Time.ToString()); } (task as ListBackupsTask).Backups = res.ToArray(); break; case DuplicityTaskType.ListBackupEntries: (task as ListBackupEntriesTask).Backups = i.GetBackupSets(); break; case DuplicityTaskType.ListFiles: (task as ListFilesTask).Files = i.ListCurrentFiles(); break; case DuplicityTaskType.ListSourceFolders: (task as ListSourceFoldersTask).Files = new List<string>(i.ListSourceFolders() ?? new string[0]); break; case DuplicityTaskType.ListActualFiles: (task as ListActualFilesTask).Files = i.ListActualSignatureFiles(); break; case DuplicityTaskType.RemoveAllButNFull: results = i.DeleteAllButNFull(); break; case DuplicityTaskType.RemoveOlderThan: results = i.DeleteOlderThan(); break; case DuplicityTaskType.Restore: options["file-to-restore"] = ((RestoreTask)task).SourceFiles; if (options.ContainsKey("filter")) options.Remove("filter"); try { if (ProgressEvent != null) ProgressEvent(DuplicatiOperation.Restore, RunnerState.Started, task.Schedule.Name, "", 0, -1); results = i.Restore(task.LocalPath.Split(System.IO.Path.PathSeparator)); } finally { if (ProgressEvent != null) ProgressEvent(DuplicatiOperation.Restore, RunnerState.Stopped, task.Schedule.Name, "", 100, -1); } break; case DuplicityTaskType.RestoreSetup: i.RestoreControlFiles(task.LocalPath); break; default: return; } } } catch (Exception ex) { while (ex is System.Reflection.TargetInvocationException && ex.InnerException != null) ex = ex.InnerException; if (ex is System.Threading.ThreadAbortException) { m_isAborted = true; System.Threading.Thread.ResetAbort(); } else if (ex is Library.Main.LiveControl.ExecutionStoppedException) m_isAborted = true; if (m_isAborted && m_stopReason != System.Windows.Forms.CloseReason.None) { //If the user has stopped the backup for some reason, write a nicer message switch (m_stopReason) { case System.Windows.Forms.CloseReason.ApplicationExitCall: parsedMessage = Strings.DuplicatiRunner.ApplicationExitLogMesssage; break; case System.Windows.Forms.CloseReason.TaskManagerClosing: parsedMessage = Strings.DuplicatiRunner.TaskManagerCloseMessage; break; case System.Windows.Forms.CloseReason.UserClosing: parsedMessage = Strings.DuplicatiRunner.UserClosingMessage; break; case System.Windows.Forms.CloseReason.WindowsShutDown: parsedMessage = Strings.DuplicatiRunner.WindowsShutdownMessage; break; default: parsedMessage = string.Format(Strings.DuplicatiRunner.OtherAbortMessage, m_stopReason); break; } if (task.Schedule != null) { //If the application is going down, the backup should resume on next launch switch (m_stopReason) { case System.Windows.Forms.CloseReason.ApplicationExitCall: case System.Windows.Forms.CloseReason.TaskManagerClosing: case System.Windows.Forms.CloseReason.WindowsShutDown: task.Schedule.ScheduledRunFailed(); break; } } } else parsedMessage = string.Format(Strings.DuplicatiRunner.ErrorMessage, ex.Message); results = "Error: " + ex.ToString(); //Don't localize while (ex.InnerException != null) { ex = ex.InnerException; results += Environment.NewLine + "InnerError: " + ex.ToString(); //Don't localize } } finally { lock (m_lock) m_currentBackupControlInterface = null; } try { if (!m_isAborted && (task.TaskType == DuplicityTaskType.FullBackup || task.TaskType == DuplicityTaskType.IncrementalBackup)) { if (task.Schedule.Task.KeepFull > 0) { m_lastPGProgress = 100; m_lastPGmessage = Strings.DuplicatiRunner.CleaningUpMessage; m_lastPGSubmessage = ""; m_lastPGSubprogress = -1; ReinvokeLastProgressEvent(); m_extraOperations--; RemoveAllButNFullTask tmpTask = new RemoveAllButNFullTask(task.Schedule, (int)task.Schedule.Task.KeepFull); ExecuteTask(tmpTask); results += Environment.NewLine + Strings.DuplicatiRunner.CleanupLogdataHeader + Environment.NewLine + tmpTask.Result; } if (!string.IsNullOrEmpty(task.Schedule.Task.KeepTime)) { m_lastPGProgress = 100; m_lastPGmessage = Strings.DuplicatiRunner.CleaningUpMessage; m_lastPGSubmessage = ""; m_lastPGSubprogress = -1; ReinvokeLastProgressEvent(); m_extraOperations--; RemoveOlderThanTask tmpTask = new RemoveOlderThanTask(task.Schedule, task.Schedule.Task.KeepTime); ExecuteTask(tmpTask); results += Environment.NewLine + Strings.DuplicatiRunner.CleanupLogdataHeader + Environment.NewLine + tmpTask.Result; } if (task.Schedule.Task.KeepFull > 0 || !string.IsNullOrEmpty(task.Schedule.Task.KeepTime)) ReinvokeLastProgressEvent(); if (ProgressEvent != null) ProgressEvent(DuplicatiOperation.Backup, RunnerState.Stopped, task.Schedule.Name, "", 100, -1); } } catch (Exception ex) { results += Environment.NewLine + string.Format(Strings.DuplicatiRunner.CleanupError, ex.Message); } task.IsAborted = m_isAborted; task.Result = results; task.RaiseTaskCompleted(results, parsedMessage); if (ResultEvent != null && task is FullBackupTask || task is IncrementalBackupTask) { Log[] logs = Program.DataConnection.GetObjects<Log>("TaskID = ? AND SubAction LIKE ? ORDER BY EndTime DESC", task.Task.ID, "Primary"); if (logs != null && logs.Length > 0) { Datamodel.Log l = logs[0]; RunnerResult r = RunnerResult.Error; if (l.ParsedStatus == DuplicatiOutputParser.ErrorStatus) r = RunnerResult.Error; else if (l.ParsedStatus == DuplicatiOutputParser.OKStatus || l.ParsedStatus == DuplicatiOutputParser.NoChangedFiles) r = RunnerResult.OK; else if (l.ParsedStatus == DuplicatiOutputParser.PartialStatus) r = RunnerResult.Partial; else if (l.ParsedStatus == DuplicatiOutputParser.WarningStatus) r = RunnerResult.Warning; ResultEvent(r, parsedMessage, results); } } if (task.Schedule != null && !m_isAborted) task.Schedule.ScheduledRunCompleted(); //Register as completed if not aborted }
public static bool DownloadAndUnpackUpdate(UpdateInfo version, Action<double> progress = null) { if (INSTALLDIR == null) return false; var updates = version.RemoteURLS.ToList(); // If alternate update URLs are specified, // we look for packages there as well if (AutoUpdateSettings.UsesAlternateURLs) { var packagepath = new Library.Utility.Uri(updates[0]).Path; var packagename = packagepath.Split('/').Last(); foreach(var alt_url in AutoUpdateSettings.URLs.Reverse()) { var alt_uri = new Library.Utility.Uri(alt_url); var path_components = alt_uri.Path.Split('/'); var path = string.Join("/", path_components.Take(path_components.Count() - 1).Union(new string[] { packagename})); var new_path = alt_uri.SetPath(path); updates.Insert(0, new_path.ToString()); } } using(var tempfile = new Library.Utility.TempFile()) { foreach(var url in updates) { try { Action<long> cb = null; if (progress != null) cb = (s) => { progress(Math.Min(1.0, Math.Max(0.0, (double)s / version.CompressedSize))); }; var wreq = (System.Net.HttpWebRequest)System.Net.WebRequest.Create(url); wreq.UserAgent = string.Format("{0} v{1}", APPNAME, SelfVersion.Version); wreq.Headers.Add("X-Install-ID", InstallID); using(var resp = wreq.GetResponse()) using(var rss = resp.GetResponseStream()) using(var pgs = new Duplicati.Library.Utility.ProgressReportingStream(rss, version.CompressedSize, cb)) using(var fs = System.IO.File.Open(tempfile, System.IO.FileMode.Create)) Duplicati.Library.Utility.Utility.CopyStream(pgs, fs); var sha256 = System.Security.Cryptography.SHA256.Create(); var md5 = System.Security.Cryptography.MD5.Create(); using(var s = System.IO.File.OpenRead(tempfile)) { if (s.Length != version.CompressedSize) throw new Exception(string.Format("Invalid file size {0}, expected {1} for {2}", s.Length, version.CompressedSize, url)); var sha256hash = Convert.ToBase64String(sha256.ComputeHash(s)); if (sha256hash != version.SHA256) throw new Exception(string.Format("Damaged or corrupted file, sha256 mismatch for {0}", url)); } using(var s = System.IO.File.OpenRead(tempfile)) { var md5hash = Convert.ToBase64String(md5.ComputeHash(s)); if (md5hash != version.MD5) throw new Exception(string.Format("Damaged or corrupted file, md5 mismatch for {0}", url)); } using(var tempfolder = new Duplicati.Library.Utility.TempFolder()) using(var zip = new Duplicati.Library.Compression.FileArchiveZip(tempfile, new Dictionary<string, string>())) { foreach(var file in zip.ListFilesWithSize("")) { if (System.IO.Path.IsPathRooted(file.Key) || file.Key.Trim().StartsWith("..", StringComparison.InvariantCultureIgnoreCase)) throw new Exception(string.Format("Out-of-place file path detected: {0}", file.Key)); var targetpath = System.IO.Path.Combine(tempfolder, file.Key); var targetfolder = System.IO.Path.GetDirectoryName(targetpath); if (!System.IO.Directory.Exists(targetfolder)) System.IO.Directory.CreateDirectory(targetfolder); using(var zs = zip.OpenRead(file.Key)) using(var fs = System.IO.File.Create(targetpath)) zs.CopyTo(fs); } if (VerifyUnpackedFolder(tempfolder, version)) { var versionstring = TryParseVersion(version.Version).ToString(); var targetfolder = System.IO.Path.Combine(INSTALLDIR, versionstring); if (System.IO.Directory.Exists(targetfolder)) System.IO.Directory.Delete(targetfolder, true); System.IO.Directory.CreateDirectory(targetfolder); var tempfolderpath = Duplicati.Library.Utility.Utility.AppendDirSeparator(tempfolder); var tempfolderlength = tempfolderpath.Length; // Would be nice, but does not work :( //System.IO.Directory.Move(tempfolder, targetfolder); foreach(var e in Duplicati.Library.Utility.Utility.EnumerateFileSystemEntries(tempfolder)) { var relpath = e.Substring(tempfolderlength); if (string.IsNullOrWhiteSpace(relpath)) continue; var fullpath = System.IO.Path.Combine(targetfolder, relpath); if (relpath.EndsWith(System.IO.Path.DirectorySeparatorChar.ToString())) System.IO.Directory.CreateDirectory(fullpath); else System.IO.File.Copy(e, fullpath); } // Verification will kick in when we list the installed updates //VerifyUnpackedFolder(targetfolder, version); System.IO.File.WriteAllText(System.IO.Path.Combine(INSTALLDIR, CURRENT_FILE), versionstring); m_hasUpdateInstalled = null; var obsolete = (from n in FindInstalledVersions() where n.Value.Version != version.Version && n.Value.Version != SelfVersion.Version let x = TryParseVersion(n.Value.Version) orderby x descending select n).Skip(1).ToArray(); foreach(var f in obsolete) try { System.IO.Directory.Delete(f.Key, true); } catch { } return true; } else { throw new Exception(string.Format("Unable to verify unpacked folder for url: {0}", url)); } } } catch(Exception ex) { if (OnError != null) OnError(ex); } } } return false; }
public static Duplicati.Library.Interface.IBasicResults Run(IRunnerData data, bool fromQueue) { var backup = data.Backup; Duplicati.Library.Utility.TempFolder tempfolder = null; if (backup.Metadata == null) backup.Metadata = new Dictionary<string, string>(); try { var options = ApplyOptions(backup, data.Operation, GetCommonOptions(backup, data.Operation)); var sink = new MessageSink(data.TaskID, backup.ID); if (fromQueue) { Program.GenerateProgressState = () => sink.Copy(); Program.StatusEventNotifyer.SignalNewEvent(); } if (data.ExtraOptions != null) foreach(var k in data.ExtraOptions) options[k.Key] = k.Value; // Log file is using the internal log-handler // so we can display output in the GUI as well as log // into the given file if (options.ContainsKey("log-file")) { var file = options["log-file"]; string o; Library.Logging.LogMessageType level; options.TryGetValue("log-level", out o); Enum.TryParse<Library.Logging.LogMessageType>(o, true, out level); options.Remove("log-file"); options.Remove("log-level"); Program.LogHandler.SetOperationFile(file, level); } // Pack in the system or task config for easy restore if (data.Operation == DuplicatiOperation.Backup && options.ContainsKey("store-task-config")) { var all_tasks = string.Equals(options["store-task-config"], "all", StringComparison.InvariantCultureIgnoreCase) || string.Equals(options["store-task-config"], "*", StringComparison.InvariantCultureIgnoreCase); var this_task = Duplicati.Library.Utility.Utility.ParseBool(options["store-task-config"], false); options.Remove("store-task-config"); if (all_tasks || this_task) { if (tempfolder == null) tempfolder = new Duplicati.Library.Utility.TempFolder(); var temppath = System.IO.Path.Combine(tempfolder, "task-setup.json"); using(var tempfile = Duplicati.Library.Utility.TempFile.WrapExistingFile(temppath)) { object taskdata = null; if (all_tasks) taskdata = Program.DataConnection.Backups.Where(x => !x.IsTemporary).Select(x => Program.DataConnection.PrepareBackupForExport(Program.DataConnection.GetBackup(x.ID))); else taskdata = new [] { Program.DataConnection.PrepareBackupForExport(data.Backup) }; using(var fs = System.IO.File.OpenWrite(tempfile)) using(var sw = new System.IO.StreamWriter(fs, System.Text.Encoding.UTF8)) Serializer.SerializeJson(sw, taskdata, true); tempfile.Protected = true; string controlfiles = null; options.TryGetValue("control-files", out controlfiles); if (string.IsNullOrWhiteSpace(controlfiles)) controlfiles = tempfile; else controlfiles += System.IO.Path.PathSeparator + tempfile; options["control-files"] = controlfiles; } } } using(tempfolder) using(var controller = new Duplicati.Library.Main.Controller(backup.TargetURL, options, sink)) { ((RunnerData)data).Controller = controller; switch (data.Operation) { case DuplicatiOperation.Backup: { var filter = ApplyFilter(backup, data.Operation, GetCommonFilter(backup, data.Operation)); var sources = (from n in backup.Sources let p = SpecialFolders.ExpandEnvironmentVariables(n) where !string.IsNullOrWhiteSpace(p) select p).ToArray(); var r = controller.Backup(sources, filter); UpdateMetadata(backup, r); return r; } case DuplicatiOperation.List: { var r = controller.List(data.FilterStrings); UpdateMetadata(backup, r); return r; } case DuplicatiOperation.Repair: { var r = controller.Repair(data.FilterStrings == null ? null : new Library.Utility.FilterExpression(data.FilterStrings)); UpdateMetadata(backup, r); return r; } case DuplicatiOperation.RepairUpdate: { var r = controller.UpdateDatabaseWithVersions(); UpdateMetadata(backup, r); return r; } case DuplicatiOperation.Remove: { var r = controller.Delete(); UpdateMetadata(backup, r); return r; } case DuplicatiOperation.Restore: { var r = controller.Restore(data.FilterStrings); UpdateMetadata(backup, r); return r; } case DuplicatiOperation.Verify: { var r = controller.Test(); UpdateMetadata(backup, r); return r; } case DuplicatiOperation.CreateReport: { using(var tf = new Duplicati.Library.Utility.TempFile()) { var r = controller.CreateLogDatabase(tf); var tempid = Program.DataConnection.RegisterTempFile("create-bug-report", r.TargetPath, DateTime.Now.AddDays(3)); if (string.Equals(tf, r.TargetPath, Library.Utility.Utility.ClientFilenameStringComparision)) tf.Protected = true; Program.DataConnection.RegisterNotification( NotificationType.Information, "Bugreport ready", "Bugreport is ready for download", null, null, "bug-report:created:" + tempid, (n, a) => n ); return r; } } default: //TODO: Log this return null; } } } catch (Exception ex) { Program.DataConnection.LogError(data.Backup.ID, string.Format("Failed while executing \"{0}\" with id: {1}", data.Operation, data.Backup.ID), ex); UpdateMetadataError(data.Backup, ex); Library.UsageReporter.Reporter.Report(ex); if (!fromQueue) throw; return null; } finally { ((RunnerData)data).Controller = null; Program.LogHandler.RemoveOperationFile(); } }
public static Duplicati.Library.Interface.IBasicResults Run(IRunnerData data, bool fromQueue) { if (data is CustomRunnerTask) { try { var sink = new MessageSink(data.TaskID, null); Program.GenerateProgressState = sink.Copy; Program.StatusEventNotifyer.SignalNewEvent(); ((CustomRunnerTask)data).Run(sink); } catch (Exception ex) { Program.DataConnection.LogError(string.Empty, "Failed while executing custom task", ex); } return(null); } var backup = data.Backup; if (backup.Metadata == null) { backup.Metadata = new Dictionary <string, string>(); } Duplicati.Library.Utility.TempFolder tempfolder = null; try { var sink = new MessageSink(data.TaskID, backup.ID); if (fromQueue) { Program.GenerateProgressState = () => sink.Copy(); Program.StatusEventNotifyer.SignalNewEvent(); } var options = ApplyOptions(backup, GetCommonOptions()); if (data.ExtraOptions != null) { foreach (var k in data.ExtraOptions) { options[k.Key] = k.Value; } } // Pack in the system or task config for easy restore if (data.Operation == DuplicatiOperation.Backup && options.ContainsKey("store-task-config")) { tempfolder = StoreTaskConfigAndGetTempFolder(data, options); } // Attach a log scope that tags all messages to relay the TaskID and BackupID using (Library.Logging.Log.StartScope(log => { log[LogWriteHandler.LOG_EXTRA_TASKID] = data.TaskID.ToString(); log[LogWriteHandler.LOG_EXTRA_BACKUPID] = data.BackupID; })) using (tempfolder) using (var controller = new Duplicati.Library.Main.Controller(backup.TargetURL, options, sink)) { try { if (options.ContainsKey("throttle-upload")) { ((RunnerData)data).OriginalUploadSpeed = Duplicati.Library.Utility.Sizeparser.ParseSize(options["throttle-upload"], "kb"); } } catch { } try { if (options.ContainsKey("throttle-download")) { ((RunnerData)data).OriginalDownloadSpeed = Duplicati.Library.Utility.Sizeparser.ParseSize(options["throttle-download"], "kb"); } } catch { } ((RunnerData)data).Controller = controller; data.UpdateThrottleSpeed(); switch (data.Operation) { case DuplicatiOperation.Backup: { var filter = ApplyFilter(backup, GetCommonFilter()); var sources = (from n in backup.Sources let p = SpecialFolders.ExpandEnvironmentVariables(n) where !string.IsNullOrWhiteSpace(p) select p).ToArray(); var r = controller.Backup(sources, filter); UpdateMetadata(backup, r); return(r); } case DuplicatiOperation.List: { var r = controller.List(data.FilterStrings, null); UpdateMetadata(backup, r); return(r); } case DuplicatiOperation.Repair: { var r = controller.Repair(data.FilterStrings == null ? null : new Library.Utility.FilterExpression(data.FilterStrings)); UpdateMetadata(backup, r); return(r); } case DuplicatiOperation.RepairUpdate: { var r = controller.UpdateDatabaseWithVersions(); UpdateMetadata(backup, r); return(r); } case DuplicatiOperation.Remove: { var r = controller.Delete(); UpdateMetadata(backup, r); return(r); } case DuplicatiOperation.Restore: { var r = controller.Restore(data.FilterStrings); UpdateMetadata(backup, r); return(r); } case DuplicatiOperation.Verify: { var r = controller.Test(); UpdateMetadata(backup, r); return(r); } case DuplicatiOperation.Compact: { var r = controller.Compact(); UpdateMetadata(backup, r); return(r); } case DuplicatiOperation.CreateReport: { using (var tf = new Duplicati.Library.Utility.TempFile()) { var r = controller.CreateLogDatabase(tf); var tempid = Program.DataConnection.RegisterTempFile("create-bug-report", r.TargetPath, DateTime.Now.AddDays(3)); if (string.Equals(tf, r.TargetPath, Library.Utility.Utility.ClientFilenameStringComparison)) { tf.Protected = true; } Program.DataConnection.RegisterNotification( NotificationType.Information, "Bugreport ready", "Bugreport is ready for download", null, null, "bug-report:created:" + tempid, null, "BugreportCreatedReady", "", (n, a) => n ); return(r); } } case DuplicatiOperation.ListRemote: { var r = controller.ListRemote(); UpdateMetadata(backup, r); return(r); } case DuplicatiOperation.Delete: { if (Library.Utility.Utility.ParseBoolOption(data.ExtraOptions, "delete-remote-files")) { controller.DeleteAllRemoteFiles(); } if (Library.Utility.Utility.ParseBoolOption(data.ExtraOptions, "delete-local-db")) { string dbpath; options.TryGetValue("dbpath", out dbpath); if (!string.IsNullOrWhiteSpace(dbpath) && System.IO.File.Exists(dbpath)) { System.IO.File.Delete(dbpath); } } Program.DataConnection.DeleteBackup(backup); Program.Scheduler.Reschedule(); return(null); } case DuplicatiOperation.Vacuum: { var r = controller.Vacuum(); UpdateMetadata(backup, r); return(r); } default: //TODO: Log this return(null); } } } catch (Exception ex) { Program.DataConnection.LogError(data.Backup.ID, string.Format("Failed while executing \"{0}\" with id: {1}", data.Operation, data.Backup.ID), ex); UpdateMetadataError(data.Backup, ex); Library.UsageReporter.Reporter.Report(ex); if (!fromQueue) { throw; } return(null); } finally { ((RunnerData)data).Controller = null; } }
private void Restore(object sender, DoWorkEventArgs args) { System.Data.LightDatamodel.DataFetcherNested con = new System.Data.LightDatamodel.DataFetcherNested(Program.DataConnection); Schedule s = con.Add<Schedule>(); s.Task = con.Add<Task>(); m_wrapper.UpdateSchedule(s); using (Library.Utility.TempFolder tf = new Duplicati.Library.Utility.TempFolder()) { RestoreSetupTask task = new RestoreSetupTask(s, tf); Dictionary<string, string> options = new Dictionary<string, string>(); string destination = task.GetConfiguration(options); Library.Main.Interface.RestoreControlFiles(destination, task.LocalPath, options); string filename = System.IO.Path.Combine(tf, System.IO.Path.GetFileName(Program.DatabasePath)); if (System.IO.File.Exists(filename)) { //Connect to the downloaded database using (System.Data.IDbConnection scon = (System.Data.IDbConnection)Activator.CreateInstance(SQLiteLoader.SQLiteConnectionType)) { scon.ConnectionString = "Data Source=" + filename; //Make sure encryption etc is handled correctly Program.OpenDatabase(scon); //Upgrade the database to the current version DatabaseUpgrader.UpgradeDatabase(scon, filename); } //Shut down this connection Program.LiveControl.Pause(); Program.DataConnection.ClearCache(); //We also need to remove any dirty objects as the ClearCache maintains those foreach (System.Data.LightDatamodel.IDataClass o in Program.DataConnection.LocalCache.GetAllChanged()) Program.DataConnection.DiscardObject(o); Program.DataConnection.Provider.Connection.Close(); //Replace the existing database with this one System.IO.File.Copy(filename, Program.DatabasePath, true); //Re-start the connection, using the new file Program.DataConnection.Provider.Connection = (System.Data.IDbConnection)Activator.CreateInstance(SQLiteLoader.SQLiteConnectionType); Program.DataConnection.Provider.Connection.ConnectionString = "Data Source=" + Program.DatabasePath; Program.OpenDatabase(Program.DataConnection.Provider.Connection); //Remove the downloaded database try { System.IO.File.Delete(filename); } catch { } } else throw new Exception(Strings.FinishedRestoreSetup.SetupFileMissingError); NormalizeApplicationSettings(); Program.Scheduler.Reschedule(); } }
public static Duplicati.Library.Interface.IBasicResults Run(IRunnerData data, bool fromQueue) { var backup = data.Backup; Duplicati.Library.Utility.TempFolder tempfolder = null; if (backup.Metadata == null) { backup.Metadata = new Dictionary <string, string>(); } try { var options = ApplyOptions(backup, data.Operation, GetCommonOptions(backup, data.Operation)); var sink = new MessageSink(data.TaskID, backup.ID); if (fromQueue) { Program.GenerateProgressState = () => sink.Copy(); Program.StatusEventNotifyer.SignalNewEvent(); } if (data.ExtraOptions != null) { foreach (var k in data.ExtraOptions) { options[k.Key] = k.Value; } } // Log file is using the internal log-handler // so we can display output in the GUI as well as log // into the given file if (options.ContainsKey("log-file")) { var file = options["log-file"]; string o; Library.Logging.LogMessageType level; options.TryGetValue("log-level", out o); Enum.TryParse <Library.Logging.LogMessageType>(o, true, out level); options.Remove("log-file"); options.Remove("log-level"); Program.LogHandler.SetOperationFile(file, level); } // Pack in the system or task config for easy restore if (data.Operation == DuplicatiOperation.Backup && options.ContainsKey("store-task-config")) { var all_tasks = string.Equals(options["store-task-config"], "all", StringComparison.InvariantCultureIgnoreCase) || string.Equals(options["store-task-config"], "*", StringComparison.InvariantCultureIgnoreCase); var this_task = Duplicati.Library.Utility.Utility.ParseBool(options["store-task-config"], false); options.Remove("store-task-config"); if (all_tasks || this_task) { if (tempfolder == null) { tempfolder = new Duplicati.Library.Utility.TempFolder(); } var temppath = System.IO.Path.Combine(tempfolder, "task-setup.json"); using (var tempfile = Duplicati.Library.Utility.TempFile.WrapExistingFile(temppath)) { object taskdata = null; if (all_tasks) { taskdata = Program.DataConnection.Backups.Where(x => !x.IsTemporary).Select(x => Program.DataConnection.PrepareBackupForExport(Program.DataConnection.GetBackup(x.ID))); } else { taskdata = new [] { Program.DataConnection.PrepareBackupForExport(data.Backup) } }; using (var fs = System.IO.File.OpenWrite(tempfile)) using (var sw = new System.IO.StreamWriter(fs, System.Text.Encoding.UTF8)) Serializer.SerializeJson(sw, taskdata, true); tempfile.Protected = true; string controlfiles = null; options.TryGetValue("control-files", out controlfiles); if (string.IsNullOrWhiteSpace(controlfiles)) { controlfiles = tempfile; } else { controlfiles += System.IO.Path.PathSeparator + tempfile; } options["control-files"] = controlfiles; } } } using (tempfolder) using (var controller = new Duplicati.Library.Main.Controller(backup.TargetURL, options, sink)) { ((RunnerData)data).Controller = controller; switch (data.Operation) { case DuplicatiOperation.Backup: { var filter = ApplyFilter(backup, data.Operation, GetCommonFilter(backup, data.Operation)); var sources = (from n in backup.Sources let p = SpecialFolders.ExpandEnvironmentVariables(n) where !string.IsNullOrWhiteSpace(p) select p).ToArray(); var r = controller.Backup(sources, filter); UpdateMetadata(backup, r); return(r); } case DuplicatiOperation.List: { var r = controller.List(data.FilterStrings); UpdateMetadata(backup, r); return(r); } case DuplicatiOperation.Repair: { var r = controller.Repair(data.FilterStrings == null ? null : new Library.Utility.FilterExpression(data.FilterStrings)); UpdateMetadata(backup, r); return(r); } case DuplicatiOperation.RepairUpdate: { var r = controller.UpdateDatabaseWithVersions(); UpdateMetadata(backup, r); return(r); } case DuplicatiOperation.Remove: { var r = controller.Delete(); UpdateMetadata(backup, r); return(r); } case DuplicatiOperation.Restore: { var r = controller.Restore(data.FilterStrings); UpdateMetadata(backup, r); return(r); } case DuplicatiOperation.Verify: { var r = controller.Test(); UpdateMetadata(backup, r); return(r); } case DuplicatiOperation.Compact: { var r = controller.Compact(); UpdateMetadata(backup, r); return(r); } case DuplicatiOperation.CreateReport: { using (var tf = new Duplicati.Library.Utility.TempFile()) { var r = controller.CreateLogDatabase(tf); var tempid = Program.DataConnection.RegisterTempFile("create-bug-report", r.TargetPath, DateTime.Now.AddDays(3)); if (string.Equals(tf, r.TargetPath, Library.Utility.Utility.ClientFilenameStringComparision)) { tf.Protected = true; } Program.DataConnection.RegisterNotification( NotificationType.Information, "Bugreport ready", "Bugreport is ready for download", null, null, "bug-report:created:" + tempid, (n, a) => n ); return(r); } } case DuplicatiOperation.ListRemote: { var r = controller.ListRemote(); UpdateMetadata(backup, r); return(r); } case DuplicatiOperation.Delete: { if (Library.Utility.Utility.ParseBoolOption(data.ExtraOptions, "delete-remote-files")) { controller.DeleteAllRemoteFiles(); } if (Library.Utility.Utility.ParseBoolOption(data.ExtraOptions, "delete-local-db")) { string dbpath; options.TryGetValue("db-path", out dbpath); if (!string.IsNullOrWhiteSpace(dbpath) && System.IO.File.Exists(dbpath)) { System.IO.File.Delete(dbpath); } } Program.DataConnection.DeleteBackup(backup); Program.Scheduler.Reschedule(); return(null); } default: //TODO: Log this return(null); } } } catch (Exception ex) { Program.DataConnection.LogError(data.Backup.ID, string.Format("Failed while executing \"{0}\" with id: {1}", data.Operation, data.Backup.ID), ex); UpdateMetadataError(data.Backup, ex); Library.UsageReporter.Reporter.Report(ex); if (!fromQueue) { throw; } return(null); } finally { ((RunnerData)data).Controller = null; Program.LogHandler.RemoveOperationFile(); } }
public static bool DownloadAndUnpackUpdate(UpdateInfo version, Action <double> progress = null) { if (INSTALLDIR == null) { return(false); } var updates = version.RemoteURLS.ToList(); // If alternate update URLs are specified, // we look for packages there as well if (AutoUpdateSettings.UsesAlternateURLs) { var packagepath = new Library.Utility.Uri(updates[0]).Path; var packagename = packagepath.Split('/').Last(); foreach (var alt_url in AutoUpdateSettings.URLs.Reverse()) { var alt_uri = new Library.Utility.Uri(alt_url); var path_components = alt_uri.Path.Split('/'); var path = string.Join("/", path_components.Take(path_components.Count() - 1).Union(new string[] { packagename })); var new_path = alt_uri.SetPath(path); updates.Insert(0, new_path.ToString()); } } using (var tempfile = new Library.Utility.TempFile()) { foreach (var url in updates) { try { Action <long> cb = null; if (progress != null) { cb = (s) => { progress(Math.Min(1.0, Math.Max(0.0, (double)s / version.CompressedSize))); } } ; var wreq = (System.Net.HttpWebRequest)System.Net.WebRequest.Create(url); wreq.UserAgent = string.Format("{0} v{1}", APPNAME, SelfVersion.Version); wreq.Headers.Add("X-Install-ID", InstallID); using (var resp = wreq.GetResponse()) using (var rss = resp.GetResponseStream()) using (var pgs = new Duplicati.Library.Utility.ProgressReportingStream(rss, version.CompressedSize, cb)) using (var fs = System.IO.File.Open(tempfile, System.IO.FileMode.Create)) Duplicati.Library.Utility.Utility.CopyStream(pgs, fs); var sha256 = System.Security.Cryptography.SHA256.Create(); var md5 = System.Security.Cryptography.MD5.Create(); using (var s = System.IO.File.OpenRead(tempfile)) { if (s.Length != version.CompressedSize) { throw new Exception(string.Format("Invalid file size {0}, expected {1} for {2}", s.Length, version.CompressedSize, url)); } var sha256hash = Convert.ToBase64String(sha256.ComputeHash(s)); if (sha256hash != version.SHA256) { throw new Exception(string.Format("Damaged or corrupted file, sha256 mismatch for {0}", url)); } } using (var s = System.IO.File.OpenRead(tempfile)) { var md5hash = Convert.ToBase64String(md5.ComputeHash(s)); if (md5hash != version.MD5) { throw new Exception(string.Format("Damaged or corrupted file, md5 mismatch for {0}", url)); } } using (var tempfolder = new Duplicati.Library.Utility.TempFolder()) using (var zip = new Duplicati.Library.Compression.FileArchiveZip(tempfile, new Dictionary <string, string>())) { foreach (var file in zip.ListFilesWithSize("")) { if (System.IO.Path.IsPathRooted(file.Key) || file.Key.Trim().StartsWith("..", StringComparison.InvariantCultureIgnoreCase)) { throw new Exception(string.Format("Out-of-place file path detected: {0}", file.Key)); } var targetpath = System.IO.Path.Combine(tempfolder, file.Key); var targetfolder = System.IO.Path.GetDirectoryName(targetpath); if (!System.IO.Directory.Exists(targetfolder)) { System.IO.Directory.CreateDirectory(targetfolder); } using (var zs = zip.OpenRead(file.Key)) using (var fs = System.IO.File.Create(targetpath)) zs.CopyTo(fs); } if (VerifyUnpackedFolder(tempfolder, version)) { var versionstring = TryParseVersion(version.Version).ToString(); var targetfolder = System.IO.Path.Combine(INSTALLDIR, versionstring); if (System.IO.Directory.Exists(targetfolder)) { System.IO.Directory.Delete(targetfolder, true); } System.IO.Directory.CreateDirectory(targetfolder); var tempfolderpath = Duplicati.Library.Utility.Utility.AppendDirSeparator(tempfolder); var tempfolderlength = tempfolderpath.Length; // Would be nice, but does not work :( //System.IO.Directory.Move(tempfolder, targetfolder); foreach (var e in Duplicati.Library.Utility.Utility.EnumerateFileSystemEntries(tempfolder)) { var relpath = e.Substring(tempfolderlength); if (string.IsNullOrWhiteSpace(relpath)) { continue; } var fullpath = System.IO.Path.Combine(targetfolder, relpath); if (relpath.EndsWith(System.IO.Path.DirectorySeparatorChar.ToString())) { System.IO.Directory.CreateDirectory(fullpath); } else { System.IO.File.Copy(e, fullpath); } } // Verification will kick in when we list the installed updates //VerifyUnpackedFolder(targetfolder, version); System.IO.File.WriteAllText(System.IO.Path.Combine(INSTALLDIR, CURRENT_FILE), versionstring); m_hasUpdateInstalled = null; var obsolete = (from n in FindInstalledVersions() where n.Value.Version != version.Version && n.Value.Version != SelfVersion.Version let x = TryParseVersion(n.Value.Version) orderby x descending select n).Skip(1).ToArray(); foreach (var f in obsolete) { try { System.IO.Directory.Delete(f.Key, true); } catch { } } return(true); } else { throw new Exception(string.Format("Unable to verify unpacked folder for url: {0}", url)); } } } catch (Exception ex) { if (OnError != null) { OnError(ex); } } } } return(false); }
static bool Run(List <string> args, Dictionary <string, string> options, bool first) { string allowedChars = ValidFilenameChars; if (options.ContainsKey("extended-chars")) { allowedChars += options["extended-chars"]; } else { allowedChars += ExtendedChars; } bool autoCreateFolders = Library.Utility.Utility.ParseBoolOption(options, "auto-create-folder"); Library.Interface.IBackend backend = Library.DynamicLoader.BackendLoader.GetBackend(args[0], options); if (backend == null) { Console.WriteLine("Unsupported backend"); Console.WriteLine(); Console.WriteLine("Supported backends: " + string.Join(",", Duplicati.Library.DynamicLoader.BackendLoader.Keys)); return(false); } string disabledModulesValue; string enabledModulesValue; options.TryGetValue("enable-module", out enabledModulesValue); options.TryGetValue("disable-module", out disabledModulesValue); string[] enabledModules = enabledModulesValue == null ? new string[0] : enabledModulesValue.Trim().ToLower().Split(','); string[] disabledModules = disabledModulesValue == null ? new string[0] : disabledModulesValue.Trim().ToLower().Split(','); List <Library.Interface.IGenericModule> loadedModules = new List <IGenericModule>(); foreach (Library.Interface.IGenericModule m in Library.DynamicLoader.GenericLoader.Modules) { if (Array.IndexOf <string>(disabledModules, m.Key.ToLower()) < 0 && (m.LoadAsDefault || Array.IndexOf <string>(enabledModules, m.Key.ToLower()) >= 0)) { m.Configure(options); loadedModules.Add(m); } } try { List <Library.Interface.IFileEntry> curlist = null; try { curlist = backend.List(); } catch (FolderMissingException fex) { if (autoCreateFolders) { try { backend.CreateFolder(); curlist = backend.List(); } catch (Exception ex) { Console.WriteLine("Autocreate folder failed with message: " + ex.Message); } } if (curlist == null) { throw fex; } } foreach (Library.Interface.IFileEntry fe in curlist) { if (!fe.IsFolder) { if (Library.Utility.Utility.ParseBoolOption(options, "auto-clean") && first) { if (Library.Utility.Utility.ParseBoolOption(options, "force")) { Console.WriteLine("Auto clean, removing file: {0}", fe.Name); backend.Delete(fe.Name); continue; } else { Console.WriteLine("Specify the --force flag to actually delete files"); } } Console.WriteLine("*** Remote folder is not empty, aborting"); return(false); } } int number_of_files = 10; int min_file_size = 1024; int max_file_size = 1024 * 1024 * 50; int min_filename_size = 5; int max_filename_size = 80; bool disableStreaming = Library.Utility.Utility.ParseBoolOption(options, "disable-streaming-transfers"); bool skipOverwriteTest = Library.Utility.Utility.ParseBoolOption(options, "skip-overwrite-test"); if (options.ContainsKey("number-of-files")) { number_of_files = int.Parse(options["number-of-files"]); } if (options.ContainsKey("min-file-size")) { min_file_size = (int)Duplicati.Library.Utility.Sizeparser.ParseSize(options["min-file-size"], "mb"); } if (options.ContainsKey("max-file-size")) { max_file_size = (int)Duplicati.Library.Utility.Sizeparser.ParseSize(options["max-file-size"], "mb"); } if (options.ContainsKey("min-filename-length")) { min_filename_size = int.Parse(options["min-filename-length"]); } if (options.ContainsKey("max-filename-length")) { max_filename_size = int.Parse(options["max-filename-length"]); } Random rnd = new Random(); System.Security.Cryptography.SHA256 sha = System.Security.Cryptography.SHA256.Create(); //Create random files using (Library.Utility.TempFolder tf = new Duplicati.Library.Utility.TempFolder()) { List <TempFile> files = new List <TempFile>(); for (int i = 0; i < number_of_files; i++) { StringBuilder filename = new StringBuilder(); int filenamelen = rnd.Next(min_filename_size, max_filename_size); for (int j = 0; j < filenamelen; j++) { filename.Append(allowedChars[rnd.Next(0, allowedChars.Length)]); } string localfilename = CreateRandomFile(tf, i, min_file_size, max_file_size, rnd); //Calculate local hash and length using (System.IO.FileStream fs = new System.IO.FileStream(localfilename, System.IO.FileMode.Open, System.IO.FileAccess.Read)) files.Add(new TempFile(filename.ToString(), localfilename, sha.ComputeHash(fs), fs.Length)); } byte[] dummyFileHash = null; if (!skipOverwriteTest) { Console.WriteLine("Uploading wrong files ..."); using (Library.Utility.TempFile dummy = Library.Utility.TempFile.WrapExistingFile(CreateRandomFile(tf, files.Count, 1024, 2048, rnd))) { using (System.IO.FileStream fs = new System.IO.FileStream(dummy, System.IO.FileMode.Open, System.IO.FileAccess.Read)) dummyFileHash = sha.ComputeHash(fs); //Upload a dummy file for entry 0 and the last one, they will be replaced by the real files afterwards //We upload entry 0 twice just to try to freak any internal cache list Uploadfile(dummy, 0, files[0].remotefilename, backend, disableStreaming); Uploadfile(dummy, 0, files[0].remotefilename, backend, disableStreaming); Uploadfile(dummy, files.Count - 1, files[files.Count - 1].remotefilename, backend, disableStreaming); } } Console.WriteLine("Uploading files ..."); for (int i = 0; i < files.Count; i++) { Uploadfile(files[i].localfilename, i, files[i].remotefilename, backend, disableStreaming); } Console.WriteLine("Verifying file list ..."); curlist = backend.List(); foreach (Library.Interface.IFileEntry fe in curlist) { if (!fe.IsFolder) { bool found = false; foreach (TempFile tx in files) { if (tx.remotefilename == fe.Name) { if (tx.found) { Console.WriteLine("*** File with name {0} was found more than once", tx.remotefilename); } found = true; tx.found = true; if (fe.Size > 0 && tx.length != fe.Size) { Console.WriteLine("*** File with name {0} has size {1} but the size was reported as {2}", tx.remotefilename, tx.length, fe.Size); } break; } } if (!found) { Console.WriteLine("*** File with name {0} was found on server but not uploaded!", fe.Name); } } } foreach (TempFile tx in files) { if (!tx.found) { Console.WriteLine("*** File with name {0} was uploaded but not found afterwards", tx.remotefilename); } } Console.WriteLine("Downloading files"); for (int i = 0; i < files.Count; i++) { using (Duplicati.Library.Utility.TempFile cf = new Duplicati.Library.Utility.TempFile()) { Exception e = null; Console.Write("Downloading file {0} ... ", i); try { if (backend is Library.Interface.IStreamingBackend && !disableStreaming) { using (System.IO.FileStream fs = new System.IO.FileStream(cf, System.IO.FileMode.Create, System.IO.FileAccess.Write, System.IO.FileShare.None)) using (NonSeekableStream nss = new NonSeekableStream(fs)) (backend as Library.Interface.IStreamingBackend).Get(files[i].remotefilename, nss); } else { backend.Get(files[i].remotefilename, cf); } e = null; } catch (Exception ex) { e = ex; } if (e != null) { Console.WriteLine("failed\n*** Error: {0}", e.ToString()); } else { Console.WriteLine("done"); } Console.Write("Checking hash ... "); using (System.IO.FileStream fs = new System.IO.FileStream(cf, System.IO.FileMode.Open, System.IO.FileAccess.Read)) if (Convert.ToBase64String(sha.ComputeHash(fs)) != Convert.ToBase64String(files[i].hash)) { if (dummyFileHash != null && Convert.ToBase64String(sha.ComputeHash(fs)) == Convert.ToBase64String(dummyFileHash)) { Console.WriteLine("failed\n*** Downloaded file was the dummy file"); } else { Console.WriteLine("failed\n*** Downloaded file was corrupt"); } } else { Console.WriteLine("done"); } } } Console.WriteLine("Deleting files..."); foreach (TempFile tx in files) { try { backend.Delete(tx.remotefilename); } catch (Exception ex) { Console.WriteLine("*** Failed to delete file {0}, message: {1}", tx.remotefilename, ex.ToString()); } } curlist = backend.List(); foreach (Library.Interface.IFileEntry fe in curlist) { if (!fe.IsFolder) { Console.WriteLine("*** Remote folder contains {0} after cleanup", fe.Name); } } } } finally { foreach (Library.Interface.IGenericModule m in loadedModules) { if (m is IDisposable) { ((IDisposable)m).Dispose(); } } } return(true); }
static bool Run(List <string> args, Dictionary <string, string> options, bool first) { Library.Interface.IBackend backend = Library.DynamicLoader.BackendLoader.GetBackend(args[0], options); if (backend == null) { Console.WriteLine("Unsupported backend"); Console.WriteLine(); Console.WriteLine("Supported backends: " + string.Join(",", Duplicati.Library.DynamicLoader.BackendLoader.Keys)); return(false); } string allowedChars = ValidFilenameChars; if (options.ContainsKey("extended-chars")) { allowedChars += String.IsNullOrEmpty(options["extended-chars"]) ? ExtendedChars : options["extended-chars"]; } bool autoCreateFolders = Library.Utility.Utility.ParseBoolOption(options, "auto-create-folder"); string disabledModulesValue; string enabledModulesValue; options.TryGetValue("enable-module", out enabledModulesValue); options.TryGetValue("disable-module", out disabledModulesValue); string[] enabledModules = enabledModulesValue == null ? new string[0] : enabledModulesValue.Trim().ToLower().Split(','); string[] disabledModules = disabledModulesValue == null ? new string[0] : disabledModulesValue.Trim().ToLower().Split(','); List <Library.Interface.IGenericModule> loadedModules = new List <IGenericModule>(); foreach (Library.Interface.IGenericModule m in Library.DynamicLoader.GenericLoader.Modules) { if (!disabledModules.Contains(m.Key, StringComparer.OrdinalIgnoreCase) && (m.LoadAsDefault || enabledModules.Contains(m.Key, StringComparer.OrdinalIgnoreCase))) { m.Configure(options); loadedModules.Add(m); } } try { IEnumerable <Library.Interface.IFileEntry> curlist = null; try { backend.Test(); curlist = backend.List(); } catch (FolderMissingException fex) { if (autoCreateFolders) { try { backend.CreateFolder(); curlist = backend.List(); } catch (Exception ex) { Console.WriteLine("Autocreate folder failed with message: " + ex.Message); } } if (curlist == null) { throw fex; } } foreach (Library.Interface.IFileEntry fe in curlist) { if (!fe.IsFolder) { if (Library.Utility.Utility.ParseBoolOption(options, "auto-clean") && first) { if (Library.Utility.Utility.ParseBoolOption(options, "force")) { Console.WriteLine("Auto clean, removing file: {0}", fe.Name); backend.Delete(fe.Name); continue; } else { Console.WriteLine("Specify the --force flag to actually delete files"); } } Console.WriteLine("*** Remote folder is not empty, aborting"); return(false); } } int number_of_files = 10; int min_file_size = 1024; int max_file_size = 1024 * 1024 * 50; int min_filename_size = 5; int max_filename_size = 80; bool disableStreaming = Library.Utility.Utility.ParseBoolOption(options, "disable-streaming-transfers"); bool skipOverwriteTest = Library.Utility.Utility.ParseBoolOption(options, "skip-overwrite-test"); bool trimFilenameSpaces = Library.Utility.Utility.ParseBoolOption(options, "trim-filename-spaces"); if (options.ContainsKey("number-of-files")) { number_of_files = int.Parse(options["number-of-files"]); } if (options.ContainsKey("min-file-size")) { min_file_size = (int)Duplicati.Library.Utility.Sizeparser.ParseSize(options["min-file-size"], "mb"); } if (options.ContainsKey("max-file-size")) { max_file_size = (int)Duplicati.Library.Utility.Sizeparser.ParseSize(options["max-file-size"], "mb"); } if (options.ContainsKey("min-filename-length")) { min_filename_size = int.Parse(options["min-filename-length"]); } if (options.ContainsKey("max-filename-length")) { max_filename_size = int.Parse(options["max-filename-length"]); } Random rnd = new Random(); System.Security.Cryptography.SHA256 sha = System.Security.Cryptography.SHA256.Create(); //Create random files using (Library.Utility.TempFolder tf = new Duplicati.Library.Utility.TempFolder()) { List <TempFile> files = new List <TempFile>(); for (int i = 0; i < number_of_files; i++) { string filename = CreateRandomRemoteFileName(min_filename_size, max_filename_size, allowedChars, trimFilenameSpaces, rnd); string localfilename = CreateRandomFile(tf, i, min_file_size, max_file_size, rnd); //Calculate local hash and length using (System.IO.FileStream fs = new System.IO.FileStream(localfilename, System.IO.FileMode.Open, System.IO.FileAccess.Read)) files.Add(new TempFile(filename, localfilename, sha.ComputeHash(fs), fs.Length)); } byte[] dummyFileHash = null; if (!skipOverwriteTest) { Console.WriteLine("Uploading wrong files ..."); using (Library.Utility.TempFile dummy = Library.Utility.TempFile.WrapExistingFile(CreateRandomFile(tf, files.Count, 1024, 2048, rnd))) { using (System.IO.FileStream fs = new System.IO.FileStream(dummy, System.IO.FileMode.Open, System.IO.FileAccess.Read)) dummyFileHash = sha.ComputeHash(fs); //Upload a dummy file for entry 0 and the last one, they will be replaced by the real files afterwards //We upload entry 0 twice just to try to freak any internal cache list Uploadfile(dummy, 0, files[0].remotefilename, backend, disableStreaming); Uploadfile(dummy, 0, files[0].remotefilename, backend, disableStreaming); Uploadfile(dummy, files.Count - 1, files[files.Count - 1].remotefilename, backend, disableStreaming); } } Console.WriteLine("Uploading files ..."); for (int i = 0; i < files.Count; i++) { Uploadfile(files[i].localfilename, i, files[i].remotefilename, backend, disableStreaming); } TempFile originalRenamedFile = null; string renamedFileNewName = null; IRenameEnabledBackend renameEnabledBackend = backend as IRenameEnabledBackend; if (renameEnabledBackend != null) { // Rename the second file in the list, if there are more than one. If not, just do the first one. int renameIndex = files.Count > 1 ? 1 : 0; originalRenamedFile = files[renameIndex]; renamedFileNewName = CreateRandomRemoteFileName(min_filename_size, max_filename_size, allowedChars, trimFilenameSpaces, rnd); Console.WriteLine("Renaming file {0} from {1} to {2}", renameIndex, originalRenamedFile.remotefilename, renamedFileNewName); renameEnabledBackend.Rename(originalRenamedFile.remotefilename, renamedFileNewName); files[renameIndex] = new TempFile(renamedFileNewName, originalRenamedFile.localfilename, originalRenamedFile.hash, originalRenamedFile.length); } Console.WriteLine("Verifying file list ..."); curlist = backend.List(); foreach (Library.Interface.IFileEntry fe in curlist) { if (!fe.IsFolder) { bool found = false; foreach (TempFile tx in files) { if (tx.remotefilename == fe.Name) { if (tx.found) { Console.WriteLine("*** File with name {0} was found more than once", tx.remotefilename); } found = true; tx.found = true; if (fe.Size > 0 && tx.length != fe.Size) { Console.WriteLine("*** File with name {0} has size {1} but the size was reported as {2}", tx.remotefilename, tx.length, fe.Size); } break; } } if (!found) { if (originalRenamedFile != null && renamedFileNewName != null && originalRenamedFile.remotefilename == fe.Name) { Console.WriteLine("*** File with name {0} was found on server but was supposed to have been renamed to {1}!", fe.Name, renamedFileNewName); } else { Console.WriteLine("*** File with name {0} was found on server but not uploaded!", fe.Name); } } } } foreach (TempFile tx in files) { if (!tx.found) { Console.WriteLine("*** File with name {0} was uploaded but not found afterwards", tx.remotefilename); } } Console.WriteLine("Downloading files"); for (int i = 0; i < files.Count; i++) { using (Duplicati.Library.Utility.TempFile cf = new Duplicati.Library.Utility.TempFile()) { Exception e = null; Console.Write("Downloading file {0} ... ", i); try { if (backend is Library.Interface.IStreamingBackend && !disableStreaming) { using (System.IO.FileStream fs = new System.IO.FileStream(cf, System.IO.FileMode.Create, System.IO.FileAccess.Write, System.IO.FileShare.None)) using (NonSeekableStream nss = new NonSeekableStream(fs)) (backend as Library.Interface.IStreamingBackend).Get(files[i].remotefilename, nss); } else { backend.Get(files[i].remotefilename, cf); } e = null; } catch (Exception ex) { e = ex; } if (e != null) { Console.WriteLine("failed\n*** Error: {0}", e); } else { Console.WriteLine("done"); } Console.Write("Checking hash ... "); using (System.IO.FileStream fs = new System.IO.FileStream(cf, System.IO.FileMode.Open, System.IO.FileAccess.Read)) if (Convert.ToBase64String(sha.ComputeHash(fs)) != Convert.ToBase64String(files[i].hash)) { if (dummyFileHash != null && Convert.ToBase64String(sha.ComputeHash(fs)) == Convert.ToBase64String(dummyFileHash)) { Console.WriteLine("failed\n*** Downloaded file was the dummy file"); } else { Console.WriteLine("failed\n*** Downloaded file was corrupt"); } } else { Console.WriteLine("done"); } } } Console.WriteLine("Deleting files..."); foreach (TempFile tx in files) { try { backend.Delete(tx.remotefilename); } catch (Exception ex) { Console.WriteLine("*** Failed to delete file {0}, message: {1}", tx.remotefilename, ex); } } curlist = backend.List(); foreach (Library.Interface.IFileEntry fe in curlist) { if (!fe.IsFolder) { Console.WriteLine("*** Remote folder contains {0} after cleanup", fe.Name); } } } // Test quota retrieval IQuotaEnabledBackend quotaEnabledBackend = backend as IQuotaEnabledBackend; if (quotaEnabledBackend != null) { Console.WriteLine("Checking quota..."); IQuotaInfo quota = null; bool noException; try { quota = quotaEnabledBackend.Quota; noException = true; } catch (Exception ex) { Console.WriteLine("*** Checking quota information failed: {0}", ex); noException = false; } if (noException) { if (quota != null) { Console.WriteLine("Free Space: {0}", Library.Utility.Utility.FormatSizeString(quota.FreeQuotaSpace)); Console.WriteLine("Total Space: {0}", Library.Utility.Utility.FormatSizeString(quota.TotalQuotaSpace)); } else { Console.WriteLine("Unable to retrieve quota information"); } } } // Test DNSName lookup Console.WriteLine("Checking DNS names used by this backend..."); try { string[] dnsNames = backend.DNSName; if (dnsNames != null) { foreach (string dnsName in dnsNames) { Console.WriteLine(dnsName); } } else { Console.WriteLine("No DNS names reported"); } } catch (Exception ex) { Console.WriteLine("*** Checking DNSName failed: {0}", ex); } } finally { foreach (Library.Interface.IGenericModule m in loadedModules) { if (m is IDisposable) { ((IDisposable)m).Dispose(); } } } return(true); }
public void ExecuteTask(IDuplicityTask task) { Dictionary <string, string> options = new Dictionary <string, string>(); //Set the log level to be that of the GUI options["log-level"] = Duplicati.Library.Logging.Log.LogLevel.ToString(); string destination = task.GetConfiguration(options); string results = ""; string parsedMessage = ""; m_isAborted = false; try { //TODO: Its a bit dirty to set the options after creating the instance using (Interface i = new Interface(destination, options)) { lock (m_lock) { m_stopReason = System.Windows.Forms.CloseReason.None; m_currentBackupControlInterface = i; } SetupControlInterface(); i.OperationProgress += new OperationProgressEvent(Duplicati_OperationProgress); switch (task.TaskType) { case DuplicityTaskType.FullBackup: case DuplicityTaskType.IncrementalBackup: { //Activate auto-cleanup options["auto-cleanup"] = ""; options["force"] = ""; if (task.Schedule.Task.KeepFull > 0) { m_extraOperations++; } if (!string.IsNullOrEmpty(task.Schedule.Task.KeepTime)) { m_extraOperations++; } Library.Utility.TempFolder tf = null; try { if (ProgressEvent != null) { ProgressEvent(DuplicatiOperation.Backup, RunnerState.Started, task.Schedule.Name, "", 0, -1); } if (task.Task.IncludeSetup) { //Make a copy of the current database tf = new Duplicati.Library.Utility.TempFolder(); string filename = System.IO.Path.Combine(tf, System.IO.Path.GetFileName(Program.DatabasePath)); System.IO.File.Copy(Program.DatabasePath, filename, true); using (System.Data.IDbConnection con = (System.Data.IDbConnection)Activator.CreateInstance(SQLiteLoader.SQLiteConnectionType)) { con.ConnectionString = "Data Source=" + filename; //Open the database, handle any encryption issues automatically Program.OpenDatabase(con); using (System.Data.IDbCommand cmd = con.CreateCommand()) { //Remove all log data to minimize the size of the database cmd.CommandText = "DELETE FROM CommandQueue;"; cmd.ExecuteNonQuery(); cmd.CommandText = "DELETE FROM Log;"; cmd.ExecuteNonQuery(); cmd.CommandText = "DELETE FROM LogBlob;"; cmd.ExecuteNonQuery(); //Free up unused space cmd.CommandText = "VACUUM;"; cmd.ExecuteNonQuery(); } } options["signature-control-files"] = filename; } options["full-if-sourcefolder-changed"] = ""; List <KeyValuePair <bool, string> > filters = new List <KeyValuePair <bool, string> >(); string[] sourceFolders = DynamicSetupHelper.GetSourceFolders(task.Task, new ApplicationSettings(task.Task.DataParent), filters); if (options.ContainsKey("filter")) { filters.AddRange(Library.Utility.FilenameFilter.DecodeFilter(options["filter"])); } options["filter"] = Library.Utility.FilenameFilter.EncodeAsFilter(filters); //At this point we register the backup as being in progress ((FullOrIncrementalTask)task).WriteBackupInProgress(Strings.DuplicatiRunner.ShutdownWhileBackupInprogress); results = i.Backup(sourceFolders); } finally { if (tf != null) { tf.Dispose(); } if (ProgressEvent != null) { ProgressEvent(DuplicatiOperation.Backup, RunnerState.Stopped, task.Schedule.Name, "", 100, -1); } } break; } case DuplicityTaskType.ListBackups: List <string> res = new List <string>(); foreach (ManifestEntry be in i.GetBackupSets()) { res.Add(be.Time.ToString()); foreach (ManifestEntry bei in be.Incrementals) { res.Add(bei.Time.ToString()); } } (task as ListBackupsTask).Backups = res.ToArray(); break; case DuplicityTaskType.ListBackupEntries: (task as ListBackupEntriesTask).Backups = i.GetBackupSets(); break; case DuplicityTaskType.ListFiles: (task as ListFilesTask).Files = i.ListCurrentFiles(); break; case DuplicityTaskType.ListSourceFolders: (task as ListSourceFoldersTask).Files = new List <string>(i.ListSourceFolders() ?? new string[0]); break; case DuplicityTaskType.ListActualFiles: (task as ListActualFilesTask).Files = i.ListActualSignatureFiles(); break; case DuplicityTaskType.RemoveAllButNFull: results = i.DeleteAllButNFull(); break; case DuplicityTaskType.RemoveOlderThan: results = i.DeleteOlderThan(); break; case DuplicityTaskType.Restore: options["file-to-restore"] = ((RestoreTask)task).SourceFiles; if (options.ContainsKey("filter")) { options.Remove("filter"); } try { if (ProgressEvent != null) { ProgressEvent(DuplicatiOperation.Restore, RunnerState.Started, task.Schedule.Name, "", 0, -1); } results = i.Restore(task.LocalPath.Split(System.IO.Path.PathSeparator)); } finally { if (ProgressEvent != null) { ProgressEvent(DuplicatiOperation.Restore, RunnerState.Stopped, task.Schedule.Name, "", 100, -1); } } break; case DuplicityTaskType.RestoreSetup: i.RestoreControlFiles(task.LocalPath); break; default: return; } } } catch (Exception ex) { while (ex is System.Reflection.TargetInvocationException && ex.InnerException != null) { ex = ex.InnerException; } if (ex is System.Threading.ThreadAbortException) { m_isAborted = true; System.Threading.Thread.ResetAbort(); } else if (ex is Library.Main.LiveControl.ExecutionStoppedException) { m_isAborted = true; } if (m_isAborted && m_stopReason != System.Windows.Forms.CloseReason.None) { //If the user has stopped the backup for some reason, write a nicer message switch (m_stopReason) { case System.Windows.Forms.CloseReason.ApplicationExitCall: parsedMessage = Strings.DuplicatiRunner.ApplicationExitLogMesssage; break; case System.Windows.Forms.CloseReason.TaskManagerClosing: parsedMessage = Strings.DuplicatiRunner.TaskManagerCloseMessage; break; case System.Windows.Forms.CloseReason.UserClosing: parsedMessage = Strings.DuplicatiRunner.UserClosingMessage; break; case System.Windows.Forms.CloseReason.WindowsShutDown: parsedMessage = Strings.DuplicatiRunner.WindowsShutdownMessage; break; default: parsedMessage = string.Format(Strings.DuplicatiRunner.OtherAbortMessage, m_stopReason); break; } if (task.Schedule != null) { //If the application is going down, the backup should resume on next launch switch (m_stopReason) { case System.Windows.Forms.CloseReason.ApplicationExitCall: case System.Windows.Forms.CloseReason.TaskManagerClosing: case System.Windows.Forms.CloseReason.WindowsShutDown: task.Schedule.ScheduledRunFailed(); break; } } } else { parsedMessage = string.Format(Strings.DuplicatiRunner.ErrorMessage, ex.Message); } results = "Error: " + ex.ToString(); //Don't localize while (ex.InnerException != null) { ex = ex.InnerException; results += Environment.NewLine + "InnerError: " + ex.ToString(); //Don't localize } } finally { lock (m_lock) m_currentBackupControlInterface = null; } try { if (!m_isAborted && (task.TaskType == DuplicityTaskType.FullBackup || task.TaskType == DuplicityTaskType.IncrementalBackup)) { if (task.Schedule.Task.KeepFull > 0) { m_lastPGProgress = 100; m_lastPGmessage = Strings.DuplicatiRunner.CleaningUpMessage; m_lastPGSubmessage = ""; m_lastPGSubprogress = -1; ReinvokeLastProgressEvent(); m_extraOperations--; RemoveAllButNFullTask tmpTask = new RemoveAllButNFullTask(task.Schedule, (int)task.Schedule.Task.KeepFull); ExecuteTask(tmpTask); results += Environment.NewLine + Strings.DuplicatiRunner.CleanupLogdataHeader + Environment.NewLine + tmpTask.Result; } if (!string.IsNullOrEmpty(task.Schedule.Task.KeepTime)) { m_lastPGProgress = 100; m_lastPGmessage = Strings.DuplicatiRunner.CleaningUpMessage; m_lastPGSubmessage = ""; m_lastPGSubprogress = -1; ReinvokeLastProgressEvent(); m_extraOperations--; RemoveOlderThanTask tmpTask = new RemoveOlderThanTask(task.Schedule, task.Schedule.Task.KeepTime); ExecuteTask(tmpTask); results += Environment.NewLine + Strings.DuplicatiRunner.CleanupLogdataHeader + Environment.NewLine + tmpTask.Result; } if (task.Schedule.Task.KeepFull > 0 || !string.IsNullOrEmpty(task.Schedule.Task.KeepTime)) { ReinvokeLastProgressEvent(); } if (ProgressEvent != null) { ProgressEvent(DuplicatiOperation.Backup, RunnerState.Stopped, task.Schedule.Name, "", 100, -1); } } } catch (Exception ex) { results += Environment.NewLine + string.Format(Strings.DuplicatiRunner.CleanupError, ex.Message); } task.IsAborted = m_isAborted; task.Result = results; task.RaiseTaskCompleted(results, parsedMessage); if (ResultEvent != null && task is FullBackupTask || task is IncrementalBackupTask) { Log[] logs = Program.DataConnection.GetObjects <Log>("TaskID = ? AND SubAction LIKE ? ORDER BY EndTime DESC", task.Task.ID, "Primary"); if (logs != null && logs.Length > 0) { Datamodel.Log l = logs[0]; RunnerResult r = RunnerResult.Error; if (l.ParsedStatus == DuplicatiOutputParser.ErrorStatus) { r = RunnerResult.Error; } else if (l.ParsedStatus == DuplicatiOutputParser.OKStatus || l.ParsedStatus == DuplicatiOutputParser.NoChangedFiles) { r = RunnerResult.OK; } else if (l.ParsedStatus == DuplicatiOutputParser.PartialStatus) { r = RunnerResult.Partial; } else if (l.ParsedStatus == DuplicatiOutputParser.WarningStatus) { r = RunnerResult.Warning; } ResultEvent(r, parsedMessage, results); } } if (task.Schedule != null && !m_isAborted) { task.Schedule.ScheduledRunCompleted(); //Register as completed if not aborted } }