#region Disclaimer / License // Copyright (C) 2010, Kenneth Skovhede // http://www.hexad.dk, opensource@hexad.dk // // This library is free software; you can redistribute it and/or // modify it under the terms of the GNU Lesser General Public // License as published by the Free Software Foundation; either // version 2.1 of the License, or (at your option) any later version. // // This library is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU // Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with this library; if not, write to the Free Software // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA // #endregion using System; using System.Collections.Generic; using System.Text; using Duplicati.Datamodel; namespace Duplicati.GUI { public enum DuplicityTaskType { IncrementalBackup, FullBackup, RemoveAllButNFull, RemoveOlderThan, Restore, ListBackups, ListFiles, ListSourceFolders, RestoreSetup, ListActualFiles, ListBackupEntries } public delegate void TaskCompletedHandler(IDuplicityTask owner, string output); public interface IDuplicityTask { DuplicityTaskType TaskType { get; } event TaskCompletedHandler TaskCompleted; void RaiseTaskCompleted(string output); DateTime BeginTime { get; set; } Task Task { get; } Schedule Schedule { get; } string GetConfiguration(Dictionary options); string LocalPath { get; } string Result { get; set; } } public abstract class BackupTask : IDuplicityTask { protected Schedule m_schedule; protected DateTime m_beginTime; protected string m_result; public abstract DuplicityTaskType TaskType { get; } public Schedule Schedule { get { return m_schedule; } } public Task Task { get { return m_schedule.Task; } } public event TaskCompletedHandler TaskCompleted; public virtual DateTime BeginTime { get { return m_beginTime; } set { m_beginTime = value; } } public BackupTask(Schedule schedule) { m_schedule = schedule; } public void RaiseTaskCompleted(string output) { if (TaskCompleted != null) TaskCompleted(this, output); } public abstract string LocalPath { get; } public virtual string GetConfiguration(Dictionary options) { //Schedule settings have lowest priority, because there is currently no setup SetupSchedule(options); //Now setup the environment ApplicationSettings appSet = new ApplicationSettings(this.Task.DataParent); if (appSet.SignatureCacheEnabled && !string.IsNullOrEmpty(appSet.SignatureCachePath)) options["signature-cache-path"] = System.IO.Path.Combine(System.Environment.ExpandEnvironmentVariables(appSet.SignatureCachePath), this.Task.Schedule.ID.ToString()); if (!string.IsNullOrEmpty(appSet.TempPath)) { string tempdir = System.Environment.ExpandEnvironmentVariables(appSet.TempPath); if (!System.IO.Directory.Exists(tempdir)) System.IO.Directory.CreateDirectory(tempdir); options["tempdir"] = tempdir; } Dictionary env = appSet.CreateDetachedCopy(); //If there are any control extensions, let them modify the environement foreach (Library.Interface.ISettingsControl ic in Library.DynamicLoader.SettingsControlLoader.Modules) ic.GetConfiguration(env, SettingExtension.GetExtensions(this.Task.Schedule.DataParent, ic.Key), options); //Setup encryption module SetupEncryptionModule(env, this.Task.EncryptionSettingsLookup, options); //Setup compression module SetupCompressionModule(env, this.Task.CompressionSettingsLookup, options); //Next is the actual backend setup string destination = SetupBackend(env, options); //Setup any task options SetupTask(options); //Setup any task extension options SetupTaskExtensions(options); //Override everything set in the overrides, this is placed last so it cannot be overriden elsewhere foreach (TaskOverride ov in this.Task.TaskOverrides) options[ov.Name] = ov.Value; return destination; } protected virtual void SetupEncryptionModule(Dictionary env, IDictionary guiOptions, Dictionary options) { foreach (Library.Interface.IEncryption e in Library.DynamicLoader.EncryptionLoader.Modules) if (e.FilenameExtension == this.Task.EncryptionModule) { if (e is Library.Interface.IEncryptionGUI) { (e as Library.Interface.IEncryptionGUI).GetConfiguration(env, guiOptions, options); } else { foreach (string k in guiOptions.Keys) if (k.StartsWith("--")) options[k.Substring(2)] = guiOptions[k]; } return; } } protected virtual void SetupCompressionModule(Dictionary env, IDictionary guiOptions, Dictionary options) { foreach (Library.Interface.ICompression e in Library.DynamicLoader.CompressionLoader.Modules) if (e.FilenameExtension == this.Task.CompressionModule) { if (e is Library.Interface.ICompressionGUI) { (e as Library.Interface.ICompressionGUI).GetConfiguration(env, guiOptions, options); } else { foreach (string k in guiOptions.Keys) if (k.StartsWith("--")) options[k.Substring(2)] = guiOptions[k]; } return; } } protected virtual void SetupSchedule(Dictionary options) { } protected virtual void SetupTask(Dictionary options) { if (this.Task.Filters.Count > 0) options["filter"] = this.Task.EncodedFilter; if (!string.IsNullOrEmpty(this.Task.EncryptionModule)) options["encryption-module"] = this.Task.EncryptionModule; if (string.IsNullOrEmpty(this.Task.Encryptionkey)) options.Add("no-encryption", ""); else options.Add("passphrase", this.Task.Encryptionkey); } protected virtual void SetupTaskExtensions(Dictionary options) { Datamodel.Task.TaskExtensionWrapper ext = this.Task.Extensions; if (!string.IsNullOrEmpty(ext.MaxUploadSize)) options["totalsize"] = ext.MaxUploadSize; if (!string.IsNullOrEmpty(ext.VolumeSize)) options["volsize"] = ext.VolumeSize; if (!string.IsNullOrEmpty(ext.DownloadBandwidth)) options["max-download-pr-second"] = ext.DownloadBandwidth; if (!string.IsNullOrEmpty(ext.UploadBandwidth)) options["max-upload-pr-second"] = ext.UploadBandwidth; if (!string.IsNullOrEmpty(ext.ThreadPriority)) options["thread-priority"] = ext.ThreadPriority; if (ext.AsyncTransfer) options["asynchronous-upload"] = ""; if (ext.IgnoreTimestamps) options["disable-filetime-check"] = ""; if (!string.IsNullOrEmpty(ext.FileTimeSeperator)) options["time-separator"] = ext.FileTimeSeperator; if (!string.IsNullOrEmpty(ext.FilenamePrefix)) options["backup-prefix"] = ext.FilenamePrefix; if (ext.ShortFilenames) options["short-filenames"] = ""; if (!string.IsNullOrEmpty(ext.FileSizeLimit)) options["skip-files-larger-than"] = ext.FileSizeLimit; } protected virtual string SetupBackend(Dictionary environment, Dictionary options) { Library.Interface.IBackend selectedBackend = null; foreach (Library.Interface.IBackend b in Library.DynamicLoader.BackendLoader.Backends) if (b.ProtocolKey == this.Task.Service) { selectedBackend = b; break; } if (selectedBackend == null) throw new Exception("Missing backend"); string destination; if (selectedBackend is Library.Interface.IBackendGUI) { //Simply invoke the backends setup function destination = ((Library.Interface.IBackendGUI)selectedBackend).GetConfiguration(environment, this.Task.BackendSettingsLookup, options); } else { //We store destination with the key "Destination" and other options with the -- prefix if (!options.ContainsKey(Duplicati.GUI.Wizard_pages.GridContainer.DESTINATION_EXTENSION_KEY)) throw new Exception("Invalid configuration"); destination = options[Duplicati.GUI.Wizard_pages.GridContainer.DESTINATION_EXTENSION_KEY]; foreach (KeyValuePair k in this.Task.BackendSettingsLookup) if (k.Key.StartsWith("--")) //All options are prefixed with this options[k.Key.Substring(2)] = k.Value; } return destination; } public string Result { get { return m_result; } set { m_result = value; } } protected virtual void WriteLogMessage(string action, string subaction, string data, bool commit) { lock (Program.MainLock) { System.Data.LightDatamodel.IDataFetcher con = this.Task.DataParent; Log l = con.Add(); LogBlob lb = con.Add(); lb.StringData = data; l.Blob = lb; this.Task.Logs.Add(l); //Keep some of the data in an easy to read manner DuplicatiOutputParser.ParseData(l); l.Action = action; l.SubAction = subaction; l.BeginTime = m_beginTime; l.EndTime = DateTime.Now; //Find logs that are no longer displayed, and delete them foreach (Log x in con.GetObjects("EndTime < ?", Library.Core.Timeparser.ParseTimeInterval(new ApplicationSettings(con).RecentBackupDuration, DateTime.Now, true))) { if (x.Blob != null) //Load the blob part if required con.DeleteObject(x.Blob); con.DeleteObject(x); } if (commit) (con as System.Data.LightDatamodel.IDataFetcherWithRelations).CommitAllRecursive(); } } } public abstract class FullOrIncrementalTask : BackupTask { protected FullOrIncrementalTask(Schedule schedule) : base(schedule) { base.TaskCompleted += new TaskCompletedHandler(DoneEvent); } protected void DoneEvent(IDuplicityTask task, string output) { WriteLogMessage("Backup", "Primary", output, true); } public override string GetConfiguration(Dictionary options) { return base.GetConfiguration(options); } public override string LocalPath { get { return System.Environment.ExpandEnvironmentVariables(this.Task.SourcePath); } } } public class FullBackupTask : FullOrIncrementalTask { public override DuplicityTaskType TaskType { get { return DuplicityTaskType.FullBackup; } } public FullBackupTask(Schedule schedule) : base(schedule) { } public override string GetConfiguration(Dictionary options) { string destination = base.GetConfiguration(options); options["full"] = ""; return destination; } } public class IncrementalBackupTask : FullOrIncrementalTask { private string m_fullAfter = null; public override DuplicityTaskType TaskType { get { return DuplicityTaskType.IncrementalBackup; } } public string FullAfter { get { return m_fullAfter == null ? m_schedule.Task.FullAfter : m_fullAfter; } } public IncrementalBackupTask(Schedule schedule, string fullAfter) : this(schedule) { m_fullAfter = fullAfter; } public IncrementalBackupTask(Schedule schedule) : base(schedule) { } public override string GetConfiguration(Dictionary options) { string destination = base.GetConfiguration(options); if (!string.IsNullOrEmpty(this.FullAfter)) options.Add("full-if-older-than", this.FullAfter); return destination; } } public class ListBackupEntriesTask : BackupTask { private List m_backups = null; public override DuplicityTaskType TaskType { get { return DuplicityTaskType.ListBackupEntries; } } public List Backups { get { return m_backups; } set { m_backups = value; } } public ListBackupEntriesTask(Schedule schedule) : base(schedule) { } public override string LocalPath { get { throw new NotImplementedException(); } } } public class ListBackupsTask : BackupTask { private string[] m_backups = null; public override DuplicityTaskType TaskType { get { return DuplicityTaskType.ListBackups; } } public string[] Backups { get { return m_backups; } set { m_backups = value; } } public ListBackupsTask(Schedule schedule) : base(schedule) { this.TaskCompleted += new TaskCompletedHandler(ListBackupsTask_TaskCompleted); } void ListBackupsTask_TaskCompleted(IDuplicityTask owner, string output) { if (string.IsNullOrEmpty(output)) return; const string DIVIDER = "-------------------------\r\n"; const string HEADERS = " Type of backup set: Time: Num volumes:"; List res = new List(); foreach (string part in output.Split(new string[] { DIVIDER }, StringSplitOptions.RemoveEmptyEntries)) if (part.IndexOf(HEADERS) >= 0) { bool passedHeader = false; foreach (string line in part.Split(new string[] { "\r\n" }, StringSplitOptions.RemoveEmptyEntries)) { if (line.StartsWith(HEADERS)) passedHeader = true; else if (passedHeader) { string tmp = line.Trim(); if (tmp.StartsWith("Full")) tmp = tmp.Substring("Full".Length).Trim(); else if (tmp.StartsWith("Incremental")) tmp = tmp.Substring("Incremental".Length).Trim(); string datetime = tmp.Substring(0, tmp.LastIndexOf(' ')).Trim(); /*DateTime dt; if (DateTime.TryParse(datetime, System.Globalization.CultureInfo.InvariantCulture, System.Globalization.DateTimeStyles.None, out dt))*/ res.Add(datetime); } } } //System.Text.RegularExpressions.Regex regexp = new System.Text.RegularExpressions.Regex("---------------- m_backups = res.ToArray(); } public override string LocalPath { get { throw new NotImplementedException(); } } } public class ListFilesTask : BackupTask { protected IList m_files = null; public override DuplicityTaskType TaskType { get { return DuplicityTaskType.ListFiles; } } public IList Files { get { return m_files; } set { m_files = value; } } protected DateTime m_when; public ListFilesTask(Schedule schedule, DateTime when) : base(schedule) { m_when = when; } public string When { get { if (m_when.Year < 10) return null; else return m_when.ToString("s"); } } public override string GetConfiguration(Dictionary options) { string destination = base.GetConfiguration(options); if (!string.IsNullOrEmpty(When)) options["restore-time"] = this.When; return destination; } public override string LocalPath { get { throw new NotImplementedException(); } } } public class ListSourceFoldersTask : ListFilesTask { public override DuplicityTaskType TaskType { get { return DuplicityTaskType.ListSourceFolders; } } public ListSourceFoldersTask(Schedule schedule, DateTime when) : base(schedule, when) { } } public class ListActualFilesTask : BackupTask { protected List> m_files = null; public override DuplicityTaskType TaskType { get { return DuplicityTaskType.ListActualFiles; } } public List> Files { get { return m_files; } set { m_files = value; } } protected DateTime m_when; public ListActualFilesTask(Schedule schedule, DateTime when) : base(schedule) { m_when = when; } public string When { get { if (m_when.Year < 10) return null; else return m_when.ToString("s"); } } public override string GetConfiguration(Dictionary options) { string destination = base.GetConfiguration(options); if (!string.IsNullOrEmpty(When)) options["restore-time"] = this.When; return destination; } public override string LocalPath { get { throw new NotImplementedException(); } } } public class RestoreSetupTask : BackupTask { protected string m_targetdir = null; public RestoreSetupTask(Schedule schedule, string targetdir) : base(schedule) { m_targetdir = targetdir; } public override string LocalPath { get { return System.Environment.ExpandEnvironmentVariables(this.TargetDir); } } public string TargetDir { get { return m_targetdir; } } public override string GetConfiguration(Dictionary options) { string destination = base.GetConfiguration(options); if (options.ContainsKey("filter")) options.Remove("filter"); return destination; } public override DuplicityTaskType TaskType { get { return DuplicityTaskType.RestoreSetup; } } } public class RestoreTask : BackupTask { protected DateTime m_when; protected string m_targetdir = null; protected string m_sourceFiles = null; public string When { get { if (m_when.Year < 10) return null; else return m_when.ToString("s"); } } public string TargetDir { get { return m_targetdir; } } public string SourceFiles { get { return m_sourceFiles; } } public override DuplicityTaskType TaskType { get { return DuplicityTaskType.Restore; } } public RestoreTask(Schedule schedule, string targetdir, string sourcefiles) : this(schedule, targetdir, sourcefiles, new DateTime()) { } public RestoreTask(Schedule schedule, string targetdir, DateTime when) : this(schedule, targetdir, null, when) { } public RestoreTask(Schedule schedule, string targetdir) : this(schedule, targetdir, null, new DateTime()) { } public RestoreTask(Schedule schedule, string targetdir, string sourcefiles, DateTime when) : base(schedule) { m_targetdir = targetdir; m_sourceFiles = sourcefiles; m_when = when; base.TaskCompleted += new TaskCompletedHandler(DoneEvent); } void DoneEvent(IDuplicityTask owner, string output) { WriteLogMessage("Restore", "Primary", output, true); } public override string LocalPath { get { return System.Environment.ExpandEnvironmentVariables(this.TargetDir); } } public override string GetConfiguration(Dictionary options) { string destination = base.GetConfiguration(options); options.Add("force", ""); if (!string.IsNullOrEmpty(When)) options.Add("restore-time", this.When); if (!string.IsNullOrEmpty(this.SourceFiles)) options.Add("file-to-restore", this.SourceFiles); return destination; } } public class RemoveAllButNFullTask : BackupTask { public override DuplicityTaskType TaskType { get { return DuplicityTaskType.RemoveAllButNFull; } } private int m_fullCount; public int FullCount { get { return m_fullCount; } } public RemoveAllButNFullTask(Schedule schedule, int fullcount) : base(schedule) { m_fullCount = fullcount; base.TaskCompleted += new TaskCompletedHandler(DoneEvent); } void DoneEvent(IDuplicityTask owner, string output) { WriteLogMessage("Backup", "Cleanup", output, false); } public override string LocalPath { get { throw new MissingMethodException(); } } public override string GetConfiguration(Dictionary options) { string destination = base.GetConfiguration(options); options.Add("delete-all-but-n-full", this.FullCount.ToString()); options.Add("force", ""); if (!options.ContainsKey("number-of-retries")) options["number-of-retries"] = "2"; return destination; } } public class RemoveOlderThanTask : BackupTask { public override DuplicityTaskType TaskType { get { return DuplicityTaskType.RemoveOlderThan; } } private string m_older; public string Older { get { return m_older; } } public RemoveOlderThanTask(Schedule schedule, string older) : base(schedule) { m_older = older; base.TaskCompleted += new TaskCompletedHandler(DoneEvent); } void DoneEvent(IDuplicityTask owner, string output) { WriteLogMessage("Backup", "Cleanup", output, false); } public override string GetConfiguration(Dictionary options) { string destination = base.GetConfiguration(options); options.Add("delete-older-than", this.Older); options.Add("force", ""); if (!options.ContainsKey("number-of-retries")) options["number-of-retries"] = "2"; return destination; } public override string LocalPath { get { throw new NotImplementedException(); } } } }