From fd422ef4b4c48ab42e129e867e8a1e221d3d65d2 Mon Sep 17 00:00:00 2001 From: "kenneth.skovhede@gmail.com" Date: Sun, 15 Feb 2009 23:27:14 +0000 Subject: [PATCH] Added option to set the temp folder. Added code to restore the setup from a backup. Removed the unused RDiffWrapper. Added events to the main interface. The beta for 1.0 is now completed, but the installer is missing. git-svn-id: https://duplicati.googlecode.com/svn/trunk@89 59da171f-624f-0410-aa54-27559c288bec --- Duplicati/GUI/ApplicationSetup.Designer.cs | 50 ++- Duplicati/GUI/ApplicationSetup.cs | 28 ++ Duplicati/GUI/ApplicationSetup.resx | 3 + .../GUI/Datamodel/ApplicationSettings.cs | 21 + Duplicati/GUI/Datamodel/Schedule.cs | 176 ++++----- Duplicati/GUI/Datamodel/Task.Extension.cs | 10 + Duplicati/GUI/Duplicati.csproj | 10 + Duplicati/GUI/DuplicatiRunner.cs | 17 +- .../GUI/HelperControls/DurationEditor.cs | 4 +- .../WaitForOperation.Designer.cs | 72 ++++ .../GUI/HelperControls/WaitForOperation.cs | 44 +++ .../GUI/HelperControls/WaitForOperation.resx | 123 ++++++ Duplicati/GUI/TaskPackages.cs | 41 ++ .../Wizard pages/Add backup/FinishedAdd.cs | 22 ++ .../Add backup/PasswordSettings.cs | 23 +- Duplicati/GUI/Wizard pages/FirstLaunch.cs | 2 +- .../Restore/FinishedRestore.Designer.cs | 15 +- .../Wizard pages/Restore/FinishedRestore.cs | 44 +++ .../RestoreSetup/FinishedRestoreSetup.cs | 36 +- .../Wizard pages/SelectBackend.Designer.cs | 20 +- Duplicati/GUI/Wizard pages/SelectBackend.cs | 3 + Duplicati/GUI/WizardHandler.cs | 5 +- Duplicati/Library/Main/BackupStatistics.cs | 2 +- .../Main/Duplicati.Library.Main.csproj | 2 +- Duplicati/Library/Main/Interface.cs | 363 ++++++++++++------ Duplicati/Library/Main/ProgressEvents.cs | 13 + Duplicati/Library/Main/RDiffWrapper.cs | 111 ------ Duplicati/Library/Main/RSyncDir.cs | 1 + Duplicati/Library/Main/RestoreStatistics.cs | 2 +- 29 files changed, 902 insertions(+), 361 deletions(-) create mode 100644 Duplicati/GUI/HelperControls/WaitForOperation.Designer.cs create mode 100644 Duplicati/GUI/HelperControls/WaitForOperation.cs create mode 100644 Duplicati/GUI/HelperControls/WaitForOperation.resx create mode 100644 Duplicati/Library/Main/ProgressEvents.cs delete mode 100644 Duplicati/Library/Main/RDiffWrapper.cs diff --git a/Duplicati/GUI/ApplicationSetup.Designer.cs b/Duplicati/GUI/ApplicationSetup.Designer.cs index 7fa2a302d..ecbddbc14 100644 --- a/Duplicati/GUI/ApplicationSetup.Designer.cs +++ b/Duplicati/GUI/ApplicationSetup.Designer.cs @@ -47,6 +47,10 @@ namespace Duplicati.GUI this.BrowseGPGDialog = new System.Windows.Forms.OpenFileDialog(); this.BrowseSFTPDialog = new System.Windows.Forms.OpenFileDialog(); this.BrowseSCPDialog = new System.Windows.Forms.OpenFileDialog(); + this.TempPathBrowse = new System.Windows.Forms.Button(); + this.TempPath = new System.Windows.Forms.TextBox(); + this.label4 = new System.Windows.Forms.Label(); + this.BrowseTempPath = new System.Windows.Forms.FolderBrowserDialog(); this.groupBox1.SuspendLayout(); this.groupBox2.SuspendLayout(); this.SuspendLayout(); @@ -105,6 +109,9 @@ namespace Duplicati.GUI // this.groupBox2.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) | System.Windows.Forms.AnchorStyles.Right))); + this.groupBox2.Controls.Add(this.TempPathBrowse); + this.groupBox2.Controls.Add(this.TempPath); + this.groupBox2.Controls.Add(this.label4); this.groupBox2.Controls.Add(this.BrowseSCP); this.groupBox2.Controls.Add(this.SCPPath); this.groupBox2.Controls.Add(this.label3); @@ -116,7 +123,7 @@ namespace Duplicati.GUI this.groupBox2.Controls.Add(this.label2); this.groupBox2.Location = new System.Drawing.Point(8, 72); this.groupBox2.Name = "groupBox2"; - this.groupBox2.Size = new System.Drawing.Size(437, 104); + this.groupBox2.Size = new System.Drawing.Size(437, 128); this.groupBox2.TabIndex = 5; this.groupBox2.TabStop = false; this.groupBox2.Text = "Path settings (Advanced)"; @@ -195,7 +202,7 @@ namespace Duplicati.GUI // OKBtn // this.OKBtn.Anchor = System.Windows.Forms.AnchorStyles.Bottom; - this.OKBtn.Location = new System.Drawing.Point(135, 186); + this.OKBtn.Location = new System.Drawing.Point(135, 207); this.OKBtn.Name = "OKBtn"; this.OKBtn.Size = new System.Drawing.Size(80, 24); this.OKBtn.TabIndex = 6; @@ -207,7 +214,7 @@ namespace Duplicati.GUI // this.CancelBtn.Anchor = System.Windows.Forms.AnchorStyles.Bottom; this.CancelBtn.DialogResult = System.Windows.Forms.DialogResult.Cancel; - this.CancelBtn.Location = new System.Drawing.Point(231, 186); + this.CancelBtn.Location = new System.Drawing.Point(231, 207); this.CancelBtn.Name = "CancelBtn"; this.CancelBtn.Size = new System.Drawing.Size(72, 24); this.CancelBtn.TabIndex = 7; @@ -235,12 +242,42 @@ namespace Duplicati.GUI this.BrowseSCPDialog.Filter = "Executables (*.exe)|*.exe|All files (*.*)|*.*"; this.BrowseSCPDialog.Title = "Select the SCP executable"; // + // TempPathBrowse + // + this.TempPathBrowse.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); + this.TempPathBrowse.Location = new System.Drawing.Point(400, 96); + this.TempPathBrowse.Name = "TempPathBrowse"; + this.TempPathBrowse.Size = new System.Drawing.Size(24, 20); + this.TempPathBrowse.TabIndex = 22; + this.TempPathBrowse.Text = "..."; + this.TempPathBrowse.UseVisualStyleBackColor = true; + this.TempPathBrowse.Click += new System.EventHandler(this.TempPathBrowse_Click); + // + // TempPath + // + this.TempPath.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.TempPath.Location = new System.Drawing.Point(128, 96); + this.TempPath.Name = "TempPath"; + this.TempPath.Size = new System.Drawing.Size(272, 20); + this.TempPath.TabIndex = 21; + this.TempPath.TextChanged += new System.EventHandler(this.TempPath_TextChanged); + // + // label4 + // + this.label4.AutoSize = true; + this.label4.Location = new System.Drawing.Point(8, 96); + this.label4.Name = "label4"; + this.label4.Size = new System.Drawing.Size(86, 13); + this.label4.TabIndex = 20; + this.label4.Text = "Temporary folder"; + // // ApplicationSetup // this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; this.CancelButton = this.CancelBtn; - this.ClientSize = new System.Drawing.Size(453, 224); + this.ClientSize = new System.Drawing.Size(453, 245); this.Controls.Add(this.CancelBtn); this.Controls.Add(this.OKBtn); this.Controls.Add(this.groupBox2); @@ -252,6 +289,7 @@ namespace Duplicati.GUI this.Name = "ApplicationSetup"; this.StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen; this.Text = "Duplicati setup"; + this.Load += new System.EventHandler(this.ApplicationSetup_Load); this.groupBox1.ResumeLayout(false); this.groupBox1.PerformLayout(); this.groupBox2.ResumeLayout(false); @@ -280,5 +318,9 @@ namespace Duplicati.GUI private System.Windows.Forms.OpenFileDialog BrowseGPGDialog; private System.Windows.Forms.OpenFileDialog BrowseSFTPDialog; private System.Windows.Forms.OpenFileDialog BrowseSCPDialog; + private System.Windows.Forms.Button TempPathBrowse; + private System.Windows.Forms.TextBox TempPath; + private System.Windows.Forms.Label label4; + private System.Windows.Forms.FolderBrowserDialog BrowseTempPath; } } \ No newline at end of file diff --git a/Duplicati/GUI/ApplicationSetup.cs b/Duplicati/GUI/ApplicationSetup.cs index c473d5fb1..a0bec4c93 100644 --- a/Duplicati/GUI/ApplicationSetup.cs +++ b/Duplicati/GUI/ApplicationSetup.cs @@ -41,6 +41,15 @@ namespace Duplicati.GUI m_connection = new DataFetcherNested(Program.DataConnection); m_settings = new ApplicationSettings(m_connection); + RecentDuration.SetIntervals(new List>( + new KeyValuePair[] + { + new KeyValuePair("One week", "1W"), + new KeyValuePair("Two weeks", "2W"), + new KeyValuePair("One month", "1M"), + new KeyValuePair("Three months", "3M"), + })); + try { m_isUpdating = true; @@ -48,6 +57,7 @@ namespace Duplicati.GUI GPGPath.Text = m_settings.GPGPath; SFTPPath.Text = m_settings.SFtpPath; SCPPath.Text = m_settings.ScpPath; + TempPath.Text = m_settings.TempPath; } finally { @@ -133,5 +143,23 @@ namespace Duplicati.GUI RecentDuration_TextChanged(sender, e); } + private void TempPathBrowse_Click(object sender, EventArgs e) + { + if (BrowseTempPath.ShowDialog(this) == DialogResult.OK) + TempPath.Text = BrowseTempPath.SelectedPath; + } + + private void TempPath_TextChanged(object sender, EventArgs e) + { + if (m_isUpdating) + return; + m_settings.TempPath = TempPath.Text; + } + + private void ApplicationSetup_Load(object sender, EventArgs e) + { + + } + } } \ No newline at end of file diff --git a/Duplicati/GUI/ApplicationSetup.resx b/Duplicati/GUI/ApplicationSetup.resx index 0df775dba..448fa24f7 100644 --- a/Duplicati/GUI/ApplicationSetup.resx +++ b/Duplicati/GUI/ApplicationSetup.resx @@ -126,6 +126,9 @@ 277, 17 + + 415, 17 + diff --git a/Duplicati/GUI/Datamodel/ApplicationSettings.cs b/Duplicati/GUI/Datamodel/ApplicationSettings.cs index 432a6c5eb..147c8a34f 100644 --- a/Duplicati/GUI/Datamodel/ApplicationSettings.cs +++ b/Duplicati/GUI/Datamodel/ApplicationSettings.cs @@ -32,6 +32,7 @@ namespace Duplicati.Datamodel private const string PGP_PATH = "PGP path"; private const string SFTP_PATH = "SFTP Path"; private const string SCP_PATH = "SCP Path"; + private const string TEMP_PATH = "Temp Path"; //TODO: Deal with this on Linux public const string PROGRAM_FILES = "%PROGRAMFILES%"; @@ -108,5 +109,25 @@ namespace Duplicati.Datamodel } set { m_appset[SCP_PATH] = value; } } + + /// + /// Gets or sets the path to store temporary files. May contain environment variables + /// + public string TempPath + { + get + { + if (string.IsNullOrEmpty(m_appset[TEMP_PATH])) + { + if (System.Environment.OSVersion.Platform == PlatformID.Unix || System.Environment.OSVersion.Platform == PlatformID.MacOSX) + return ""; + else + return "%temp%"; + } + return m_appset[TEMP_PATH]; + } + set { m_appset[TEMP_PATH] = value; } + } + } } diff --git a/Duplicati/GUI/Datamodel/Schedule.cs b/Duplicati/GUI/Datamodel/Schedule.cs index d334c772d..d7ee6fa3a 100644 --- a/Duplicati/GUI/Datamodel/Schedule.cs +++ b/Duplicati/GUI/Datamodel/Schedule.cs @@ -17,92 +17,92 @@ // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA // #endregion -/// -/// This class was created by DataClassFileBuilder (LightDatamodel) -/// -/// Table -/// Duplicati.Datamodel -/// Schedule -/// -/// - -using System.Data.LightDatamodel; -using System.Data.LightDatamodel.DataClassAttributes; - -namespace Duplicati.Datamodel -{ - - [DatabaseTable("Schedule")] - public partial class Schedule : DataClassBase - { - -#region " private members " - - [AutoIncrement, PrimaryKey, Relation("TaskSchedule", typeof(Task), "ScheduleID", false), DatabaseField("ID")] - private System.Int64 m_ID = long.MinValue; - [DatabaseField("Name")] - private System.String m_Name = ""; - [DatabaseField("Path")] - private System.String m_Path = ""; - [DatabaseField("When")] - private System.DateTime m_When = new System.DateTime(1, 1, 1); - [DatabaseField("Repeat")] - private System.String m_Repeat = ""; - [DatabaseField("Weekdays")] - private System.String m_Weekdays = ""; -#endregion - -#region " properties " - - public System.Int64 ID - { - get{return m_ID;} - set{object oldvalue = m_ID;OnBeforeDataChange(this, "ID", oldvalue, value);m_ID = value;OnAfterDataChange(this, "ID", oldvalue, value);} - } - - public System.String Name - { - get{return m_Name;} - set{object oldvalue = m_Name;OnBeforeDataChange(this, "Name", oldvalue, value);m_Name = value;OnAfterDataChange(this, "Name", oldvalue, value);} - } - - public System.String Path - { - get{return m_Path;} - set{object oldvalue = m_Path;OnBeforeDataChange(this, "Path", oldvalue, value);m_Path = value;OnAfterDataChange(this, "Path", oldvalue, value);} - } - - public System.DateTime When - { - get{return m_When;} - set{object oldvalue = m_When;OnBeforeDataChange(this, "When", oldvalue, value);m_When = value;OnAfterDataChange(this, "When", oldvalue, value);} - } - - public System.String Repeat - { - get{return m_Repeat;} - set{object oldvalue = m_Repeat;OnBeforeDataChange(this, "Repeat", oldvalue, value);m_Repeat = value;OnAfterDataChange(this, "Repeat", oldvalue, value);} - } - - public System.String Weekdays - { - get{return m_Weekdays;} - set{object oldvalue = m_Weekdays;OnBeforeDataChange(this, "Weekdays", oldvalue, value);m_Weekdays = value;OnAfterDataChange(this, "Weekdays", oldvalue, value);} - } - -#endregion - -#region " referenced properties " - - [Affects(typeof(Task))] - public Task Task - { - get{ return ((DataFetcherWithRelations)m_dataparent).GetRelatedObject("TaskSchedule", this); } - set{ ((DataFetcherWithRelations)m_dataparent).SetRelatedObject("TaskSchedule", this, value); } - } - -#endregion - - } - +/// +/// This class was created by DataClassFileBuilder (LightDatamodel) +/// +/// Table +/// Duplicati.Datamodel +/// Schedule +/// +/// + +using System.Data.LightDatamodel; +using System.Data.LightDatamodel.DataClassAttributes; + +namespace Duplicati.Datamodel +{ + + [DatabaseTable("Schedule")] + public partial class Schedule : DataClassBase + { + +#region " private members " + + [AutoIncrement, PrimaryKey, Relation("TaskSchedule", typeof(Task), "ScheduleID", false), DatabaseField("ID")] + private System.Int64 m_ID = long.MinValue; + [DatabaseField("Name")] + private System.String m_Name = ""; + [DatabaseField("Path")] + private System.String m_Path = ""; + [DatabaseField("When")] + private System.DateTime m_When = new System.DateTime(1, 1, 1); + [DatabaseField("Repeat")] + private System.String m_Repeat = ""; + [DatabaseField("Weekdays")] + private System.String m_Weekdays = ""; +#endregion + +#region " properties " + + public System.Int64 ID + { + get{return m_ID;} + set{object oldvalue = m_ID;OnBeforeDataChange(this, "ID", oldvalue, value);m_ID = value;OnAfterDataChange(this, "ID", oldvalue, value);} + } + + public System.String Name + { + get{return m_Name;} + set{object oldvalue = m_Name;OnBeforeDataChange(this, "Name", oldvalue, value);m_Name = value;OnAfterDataChange(this, "Name", oldvalue, value);} + } + + public System.String Path + { + get{return m_Path;} + set{object oldvalue = m_Path;OnBeforeDataChange(this, "Path", oldvalue, value);m_Path = value;OnAfterDataChange(this, "Path", oldvalue, value);} + } + + public System.DateTime When + { + get{return m_When;} + set{object oldvalue = m_When;OnBeforeDataChange(this, "When", oldvalue, value);m_When = value;OnAfterDataChange(this, "When", oldvalue, value);} + } + + public System.String Repeat + { + get{return m_Repeat;} + set{object oldvalue = m_Repeat;OnBeforeDataChange(this, "Repeat", oldvalue, value);m_Repeat = value;OnAfterDataChange(this, "Repeat", oldvalue, value);} + } + + public System.String Weekdays + { + get{return m_Weekdays;} + set{object oldvalue = m_Weekdays;OnBeforeDataChange(this, "Weekdays", oldvalue, value);m_Weekdays = value;OnAfterDataChange(this, "Weekdays", oldvalue, value);} + } + +#endregion + +#region " referenced properties " + + [Affects(typeof(Task))] + public Task Task + { + get{ return ((DataFetcherWithRelations)m_dataparent).GetRelatedObject("TaskSchedule", this); } + set{ ((DataFetcherWithRelations)m_dataparent).SetRelatedObject("TaskSchedule", this, value); } + } + +#endregion + + } + } \ No newline at end of file diff --git a/Duplicati/GUI/Datamodel/Task.Extension.cs b/Duplicati/GUI/Datamodel/Task.Extension.cs index 422132ce9..49c4c0dd1 100644 --- a/Duplicati/GUI/Datamodel/Task.Extension.cs +++ b/Duplicati/GUI/Datamodel/Task.Extension.cs @@ -89,6 +89,16 @@ namespace Duplicati.Datamodel options["gpg-encryption"] = ""; options["gpg-program-path"] = new ApplicationSettings(this.DataParent).GPGPath; } + + if (string.IsNullOrEmpty(this.Encryptionkey)) + options.Add("no-encryption", ""); + else + options.Add("passphrase", this.Encryptionkey); + + ApplicationSettings set = new ApplicationSettings(this.DataParent); + if (!string.IsNullOrEmpty(set.TempPath)) + options["tempdir"] = System.Environment.ExpandEnvironmentVariables(set.TempPath); + } public string EncodedFilter diff --git a/Duplicati/GUI/Duplicati.csproj b/Duplicati/GUI/Duplicati.csproj index 25a7ce6db..abc473534 100644 --- a/Duplicati/GUI/Duplicati.csproj +++ b/Duplicati/GUI/Duplicati.csproj @@ -85,6 +85,12 @@ FilterEditor.cs + + Form + + + WaitForOperation.cs + Form @@ -386,6 +392,10 @@ Designer FilterEditor.cs + + Designer + WaitForOperation.cs + Designer LogViewer.cs diff --git a/Duplicati/GUI/DuplicatiRunner.cs b/Duplicati/GUI/DuplicatiRunner.cs index 9b495cc9c..1e2917644 100644 --- a/Duplicati/GUI/DuplicatiRunner.cs +++ b/Duplicati/GUI/DuplicatiRunner.cs @@ -33,11 +33,6 @@ namespace Duplicati.GUI public void ExecuteTask(IDuplicityTask task) { Dictionary options = new Dictionary(); - if (string.IsNullOrEmpty(task.Task.Encryptionkey)) - options.Add("no-encryption", ""); - else - options.Add("passphrase", task.Task.Encryptionkey); - task.GetOptions(options); string results = ""; @@ -103,20 +98,24 @@ namespace Duplicati.GUI break; case DuplicityTaskType.ListFiles: (task as ListFilesTask).Files = Interface.ListContent(task.SourcePath, options); - return; + break; case DuplicityTaskType.RemoveAllButNFull: results = Interface.RemoveAllButNFull(task.SourcePath, options); - return; + break; case DuplicityTaskType.RemoveOlderThan: results = Interface.RemoveOlderThan(task.SourcePath, options); - return; + break; case DuplicityTaskType.Restore: options["file-to-restore"] = ((RestoreTask)task).SourceFiles; if (options.ContainsKey("filter")) options.Remove("filter"); results = Interface.Restore(task.SourcePath, task.TargetPath, options); - return; + break; + + case DuplicityTaskType.RestoreSetup: + Interface.RestoreControlFiles(task.SourcePath, task.TargetPath, options); + break; default: return; } diff --git a/Duplicati/GUI/HelperControls/DurationEditor.cs b/Duplicati/GUI/HelperControls/DurationEditor.cs index 728dc6649..dffd3f71c 100644 --- a/Duplicati/GUI/HelperControls/DurationEditor.cs +++ b/Duplicati/GUI/HelperControls/DurationEditor.cs @@ -46,8 +46,8 @@ namespace Duplicati.GUI.HelperControls { EasyDuration.Items.Clear(); - if (values[values.Count - 1].Key != "") - values.Add(new KeyValuePair("", "")); + if (values[values.Count - 1].Value != "") + values.Add(new KeyValuePair("Custom...", "")); m_values = new string[values.Count]; for (int i = 0; i < values.Count; i++) diff --git a/Duplicati/GUI/HelperControls/WaitForOperation.Designer.cs b/Duplicati/GUI/HelperControls/WaitForOperation.Designer.cs new file mode 100644 index 000000000..81ed22ef5 --- /dev/null +++ b/Duplicati/GUI/HelperControls/WaitForOperation.Designer.cs @@ -0,0 +1,72 @@ +namespace Duplicati.GUI.HelperControls +{ + partial class WaitForOperation + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + this.progressBar1 = new System.Windows.Forms.ProgressBar(); + this.backgroundWorker1 = new System.ComponentModel.BackgroundWorker(); + this.SuspendLayout(); + // + // progressBar1 + // + this.progressBar1.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) + | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.progressBar1.Location = new System.Drawing.Point(16, 8); + this.progressBar1.Name = "progressBar1"; + this.progressBar1.Size = new System.Drawing.Size(274, 23); + this.progressBar1.Style = System.Windows.Forms.ProgressBarStyle.Marquee; + this.progressBar1.TabIndex = 0; + // + // backgroundWorker1 + // + this.backgroundWorker1.RunWorkerCompleted += new System.ComponentModel.RunWorkerCompletedEventHandler(this.backgroundWorker1_RunWorkerCompleted); + // + // WaitForOperation + // + this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.ClientSize = new System.Drawing.Size(307, 41); + this.Controls.Add(this.progressBar1); + this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedToolWindow; + this.MaximizeBox = false; + this.MinimizeBox = false; + this.Name = "WaitForOperation"; + this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent; + this.Text = "WaitForOperation"; + this.Load += new System.EventHandler(this.WaitForOperation_Load); + this.ResumeLayout(false); + + } + + #endregion + + private System.Windows.Forms.ProgressBar progressBar1; + private System.ComponentModel.BackgroundWorker backgroundWorker1; + } +} \ No newline at end of file diff --git a/Duplicati/GUI/HelperControls/WaitForOperation.cs b/Duplicati/GUI/HelperControls/WaitForOperation.cs new file mode 100644 index 000000000..456bc4147 --- /dev/null +++ b/Duplicati/GUI/HelperControls/WaitForOperation.cs @@ -0,0 +1,44 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Data; +using System.Drawing; +using System.Text; +using System.Windows.Forms; + +namespace Duplicati.GUI.HelperControls +{ + public partial class WaitForOperation : Form + { + private Exception m_exception; + + public WaitForOperation() + { + InitializeComponent(); + } + + public void Setup(DoWorkEventHandler callback, string title) + { + backgroundWorker1.DoWork += callback; + this.Text = title; + } + + private void backgroundWorker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e) + { + m_exception = e.Error; + if (e.Cancelled || e.Error != null) + this.DialogResult = DialogResult.Cancel; + else + this.DialogResult = DialogResult.OK; + + this.Close(); + } + + private void WaitForOperation_Load(object sender, EventArgs e) + { + backgroundWorker1.RunWorkerAsync(); + } + + public Exception Error { get { return m_exception; } } + } +} \ No newline at end of file diff --git a/Duplicati/GUI/HelperControls/WaitForOperation.resx b/Duplicati/GUI/HelperControls/WaitForOperation.resx new file mode 100644 index 000000000..7aafc39c2 --- /dev/null +++ b/Duplicati/GUI/HelperControls/WaitForOperation.resx @@ -0,0 +1,123 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + 17, 17 + + \ No newline at end of file diff --git a/Duplicati/GUI/TaskPackages.cs b/Duplicati/GUI/TaskPackages.cs index 616f2c21c..fe04b874f 100644 --- a/Duplicati/GUI/TaskPackages.cs +++ b/Duplicati/GUI/TaskPackages.cs @@ -33,6 +33,7 @@ namespace Duplicati.GUI Restore, ListBackups, ListFiles, + RestoreSetup } public delegate void TaskCompletedHandler(IDuplicityTask owner, string output); @@ -340,6 +341,46 @@ namespace Duplicati.GUI } } + public class RestoreSetupTask : BackupTask + { + protected string m_targetdir = null; + + public RestoreSetupTask(Schedule schedule, string targetdir) + : base(schedule) + { + m_targetdir = targetdir; + } + + public override string SourcePath + { + get { return System.Environment.ExpandEnvironmentVariables(this.Task.GetDestinationPath()); } + } + + public override string TargetPath + { + get { return System.Environment.ExpandEnvironmentVariables(this.TargetDir); } + } + + public string TargetDir { get { return m_targetdir; } } + + public override void GetArguments(List args) + { + throw new Exception("The method or operation is not implemented."); + } + + public override void GetOptions(Dictionary options) + { + base.GetOptions(options); + if (options.ContainsKey("filter")) + options.Remove("filter"); + } + + public override DuplicityTaskType TaskType + { + get { return DuplicityTaskType.RestoreSetup; } + } + } + public class RestoreTask : BackupTask { protected DateTime m_when; diff --git a/Duplicati/GUI/Wizard pages/Add backup/FinishedAdd.cs b/Duplicati/GUI/Wizard pages/Add backup/FinishedAdd.cs index 08f79378d..8b396910d 100644 --- a/Duplicati/GUI/Wizard pages/Add backup/FinishedAdd.cs +++ b/Duplicati/GUI/Wizard pages/Add backup/FinishedAdd.cs @@ -108,5 +108,27 @@ namespace Duplicati.GUI.Wizard_pages.Add_backup args.TreatAsLast = true; } + + public override string HelpText + { + get + { + if (m_wrapper.PrimayAction == WizardSettingsWrapper.MainAction.Add) + return base.HelpText; + else + return "The backup modifications are now ready to be saved."; + } + } + + public override string Title + { + get + { + if (m_wrapper.PrimayAction == WizardSettingsWrapper.MainAction.Add) + return base.Title; + else + return "Ready to save modifications"; + } + } } } diff --git a/Duplicati/GUI/Wizard pages/Add backup/PasswordSettings.cs b/Duplicati/GUI/Wizard pages/Add backup/PasswordSettings.cs index 8bdf976f1..73b1df917 100644 --- a/Duplicati/GUI/Wizard pages/Add backup/PasswordSettings.cs +++ b/Duplicati/GUI/Wizard pages/Add backup/PasswordSettings.cs @@ -63,7 +63,7 @@ namespace Duplicati.GUI.Wizard_pages.Add_backup return; } - if (!m_warnedNoPassword && !EnablePassword.Checked) + if (!m_warnedNoPassword && !EnablePassword.Checked && m_wrapper.PrimayAction != WizardSettingsWrapper.MainAction.RestoreSetup) { if (MessageBox.Show(this, "If the backup is stored on machine or device that is not under your direct control,\nit is possible that others may view the files you have stored in the backups.\nIt is highly recomended that you enable encryption.\nDo you want to continue without encryption?", Application.ProductName, MessageBoxButtons.YesNoCancel, MessageBoxIcon.Warning) != DialogResult.Yes) { @@ -118,7 +118,7 @@ namespace Duplicati.GUI.Wizard_pages.Add_backup if (!m_valuesAutoLoaded) { - EnablePassword.Checked = !string.IsNullOrEmpty(m_wrapper.BackupPassword) || m_wrapper.PrimayAction == WizardSettingsWrapper.MainAction.Add; + EnablePassword.Checked = !string.IsNullOrEmpty(m_wrapper.BackupPassword) || (m_wrapper.PrimayAction == WizardSettingsWrapper.MainAction.Add || m_wrapper.PrimayAction == WizardSettingsWrapper.MainAction.RestoreSetup); Password.Text = m_wrapper.BackupPassword; UseGPG.Checked = m_wrapper.GPGEncryption; m_settingsChanged = false; @@ -132,6 +132,14 @@ namespace Duplicati.GUI.Wizard_pages.Add_backup m_warnedChanged = (bool)m_settings["Password:WarnedChanged"]; if (m_settings.ContainsKey("Password:SettingsChanged")) m_settingsChanged = (bool)m_settings["Password:SettingsChanged"]; + + if (m_wrapper.PrimayAction == WizardSettingsWrapper.MainAction.RestoreSetup) + { + PasswordHelptext.Visible = + PasswordGeneratorSettings.Visible = false; + + EnablePassword.Text = "Backup is protected with password"; + } } private void GeneratePassword_Click(object sender, EventArgs e) @@ -158,5 +166,16 @@ namespace Duplicati.GUI.Wizard_pages.Add_backup m_warnedNoGPG = false; } + public override string HelpText + { + get + { + if (m_wrapper.PrimayAction != WizardSettingsWrapper.MainAction.RestoreSetup) + return base.HelpText; + else + return "Enter the encryption settings that were used to create the backup originally"; + } + } + } } diff --git a/Duplicati/GUI/Wizard pages/FirstLaunch.cs b/Duplicati/GUI/Wizard pages/FirstLaunch.cs index d3603a452..cc8244d04 100644 --- a/Duplicati/GUI/Wizard pages/FirstLaunch.cs +++ b/Duplicati/GUI/Wizard pages/FirstLaunch.cs @@ -63,7 +63,7 @@ namespace Duplicati.GUI.Wizard_pages } else { - args.NextPage = new SelectBackend(); + args.NextPage = new Add_backup.PasswordSettings(); wrapper.PrimayAction = WizardSettingsWrapper.MainAction.RestoreSetup; } diff --git a/Duplicati/GUI/Wizard pages/Restore/FinishedRestore.Designer.cs b/Duplicati/GUI/Wizard pages/Restore/FinishedRestore.Designer.cs index b4371aefd..eb3715024 100644 --- a/Duplicati/GUI/Wizard pages/Restore/FinishedRestore.Designer.cs +++ b/Duplicati/GUI/Wizard pages/Restore/FinishedRestore.Designer.cs @@ -30,6 +30,7 @@ namespace Duplicati.GUI.Wizard_pages.Restore { this.Summary = new System.Windows.Forms.TextBox(); this.label1 = new System.Windows.Forms.Label(); + this.RunInBackground = new System.Windows.Forms.CheckBox(); this.SuspendLayout(); // // Summary @@ -40,7 +41,7 @@ namespace Duplicati.GUI.Wizard_pages.Restore this.Summary.Name = "Summary"; this.Summary.ReadOnly = true; this.Summary.ScrollBars = System.Windows.Forms.ScrollBars.Both; - this.Summary.Size = new System.Drawing.Size(456, 152); + this.Summary.Size = new System.Drawing.Size(456, 144); this.Summary.TabIndex = 3; // // label1 @@ -52,10 +53,21 @@ namespace Duplicati.GUI.Wizard_pages.Restore this.label1.TabIndex = 2; this.label1.Text = "Summary"; // + // RunInBackground + // + this.RunInBackground.AutoSize = true; + this.RunInBackground.Location = new System.Drawing.Point(24, 200); + this.RunInBackground.Name = "RunInBackground"; + this.RunInBackground.Size = new System.Drawing.Size(228, 17); + this.RunInBackground.TabIndex = 4; + this.RunInBackground.Text = "Run restore operation as a background job"; + this.RunInBackground.UseVisualStyleBackColor = true; + // // FinishedRestore // this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.Controls.Add(this.RunInBackground); this.Controls.Add(this.Summary); this.Controls.Add(this.label1); this.Name = "FinishedRestore"; @@ -69,5 +81,6 @@ namespace Duplicati.GUI.Wizard_pages.Restore public System.Windows.Forms.TextBox Summary; private System.Windows.Forms.Label label1; + private System.Windows.Forms.CheckBox RunInBackground; } } diff --git a/Duplicati/GUI/Wizard pages/Restore/FinishedRestore.cs b/Duplicati/GUI/Wizard pages/Restore/FinishedRestore.cs index 977436002..8de94e002 100644 --- a/Duplicati/GUI/Wizard pages/Restore/FinishedRestore.cs +++ b/Duplicati/GUI/Wizard pages/Restore/FinishedRestore.cs @@ -32,6 +32,7 @@ namespace Duplicati.GUI.Wizard_pages.Restore public partial class FinishedRestore : WizardControl { private WizardSettingsWrapper m_wrapper; + private HelperControls.WaitForOperation m_waitdlg; public FinishedRestore() : base("Ready to restore files", "Duplicati is now ready to restore your files.") @@ -39,6 +40,24 @@ namespace Duplicati.GUI.Wizard_pages.Restore InitializeComponent(); base.PageEnter += new PageChangeHandler(FinishedRestore_PageEnter); + base.PageLeave += new PageChangeHandler(FinishedRestore_PageLeave); + } + + void FinishedRestore_PageLeave(object sender, PageChangedArgs args) + { + if (args.Direction == PageChangedDirection.Back) + return; + + if (!RunInBackground.Checked) + { + m_waitdlg = new Duplicati.GUI.HelperControls.WaitForOperation(); + m_waitdlg.Setup(new DoWorkEventHandler(Restore), "Restoring files, please wait..."); + m_waitdlg.ShowDialog(); + m_owner.CancelButton.PerformClick(); + m_waitdlg = null; + args.Cancel = true; + return; + } } void FinishedRestore_PageEnter(object sender, PageChangedArgs args) @@ -54,5 +73,30 @@ namespace Duplicati.GUI.Wizard_pages.Restore args.TreatAsLast = true; } + private void Restore(object sender, DoWorkEventArgs args) + { + Schedule s = Program.DataConnection.GetObjectById(m_wrapper.ScheduleID); + + RestoreTask task = new RestoreTask(s, m_wrapper.RestorePath, m_wrapper.RestoreFilter, m_wrapper.RestoreTime); + Dictionary options = new Dictionary(); + task.GetOptions(options); + if (options.ContainsKey("filter")) + options.Remove("filter"); + using (Library.Main.Interface i = new Duplicati.Library.Main.Interface(task.SourcePath, options)) + { + i.OperationProgress += new Duplicati.Library.Main.OperationProgressEvent(i_OperationProgress); + i.Restore(task.TargetPath); + } + } + + void i_OperationProgress(Duplicati.Library.Main.Interface caller, Duplicati.Library.Main.DuplicatiOperation operation, int progress, string message) + { + if (m_waitdlg.InvokeRequired) + m_waitdlg.Invoke(new Duplicati.Library.Main.OperationProgressEvent(i_OperationProgress), caller, operation, progress, message); + else + m_waitdlg.Text = message; + } + + } } diff --git a/Duplicati/GUI/Wizard pages/RestoreSetup/FinishedRestoreSetup.cs b/Duplicati/GUI/Wizard pages/RestoreSetup/FinishedRestoreSetup.cs index 016504a39..61c962fd1 100644 --- a/Duplicati/GUI/Wizard pages/RestoreSetup/FinishedRestoreSetup.cs +++ b/Duplicati/GUI/Wizard pages/RestoreSetup/FinishedRestoreSetup.cs @@ -47,7 +47,16 @@ namespace Duplicati.GUI.Wizard_pages.RestoreSetup if (args.Direction == PageChangedDirection.Back) return; - //TODO: Perform the restore + HelperControls.WaitForOperation dlg = new Duplicati.GUI.HelperControls.WaitForOperation(); + dlg.Setup(new DoWorkEventHandler(Restore), "Restoring setup, please wait..."); + if (dlg.ShowDialog() != DialogResult.OK) + { + if (dlg.Error != null) + MessageBox.Show(this, "Failed to restore setup files, the error message was:\n" + dlg.Error.Message, Application.ProductName, MessageBoxButtons.OK, MessageBoxIcon.Error); + + args.Cancel = true; + return; + } } void FinishedRestoreSetup_PageEnter(object sender, PageChangedArgs args) @@ -96,5 +105,30 @@ namespace Duplicati.GUI.Wizard_pages.RestoreSetup args.TreatAsLast = true; } + + private void Restore(object sender, DoWorkEventArgs args) + { + System.Data.LightDatamodel.DataFetcherNested con = new System.Data.LightDatamodel.DataFetcherNested(Program.DataConnection); + Schedule s = con.Add(); + s.Task = con.Add(); + m_wrapper.UpdateSchedule(s); + + using (Library.Core.TempFolder tf = new Duplicati.Library.Core.TempFolder()) + { + RestoreSetupTask task = new RestoreSetupTask(s, tf); + Dictionary options = new Dictionary(); + task.GetOptions(options); + Library.Main.Interface.RestoreControlFiles(task.SourcePath, task.TargetPath, options); + + string filename = System.IO.Path.Combine(tf, System.IO.Path.GetFileName(Program.DatabasePath)); + if (System.IO.File.Exists(filename)) + System.IO.File.Copy(filename, Program.DatabasePath, true); + else + throw new Exception("Unable to restore setup file from backup"); + + Program.DataConnection.ClearCache(); + Program.Scheduler.Reschedule(); + } + } } } diff --git a/Duplicati/GUI/Wizard pages/SelectBackend.Designer.cs b/Duplicati/GUI/Wizard pages/SelectBackend.Designer.cs index eb5b5bfbb..15b819618 100644 --- a/Duplicati/GUI/Wizard pages/SelectBackend.Designer.cs +++ b/Duplicati/GUI/Wizard pages/SelectBackend.Designer.cs @@ -33,7 +33,7 @@ namespace Duplicati.GUI.Wizard_pages this.SSH = new System.Windows.Forms.RadioButton(); this.WebDAV = new System.Windows.Forms.RadioButton(); this.S3 = new System.Windows.Forms.RadioButton(); - this.label1 = new System.Windows.Forms.Label(); + this.Question = new System.Windows.Forms.Label(); this.SuspendLayout(); // // File @@ -96,20 +96,20 @@ namespace Duplicati.GUI.Wizard_pages this.S3.UseVisualStyleBackColor = true; this.S3.CheckedChanged += new System.EventHandler(this.Item_CheckChanged); // - // label1 + // Question // - this.label1.AutoSize = true; - this.label1.Location = new System.Drawing.Point(40, 16); - this.label1.Name = "label1"; - this.label1.Size = new System.Drawing.Size(206, 13); - this.label1.TabIndex = 5; - this.label1.Text = "Where do you want to store the backups?"; + this.Question.AutoSize = true; + this.Question.Location = new System.Drawing.Point(40, 16); + this.Question.Name = "Question"; + this.Question.Size = new System.Drawing.Size(206, 13); + this.Question.TabIndex = 5; + this.Question.Text = "Where do you want to store the backups?"; // // SelectBackend // this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; - this.Controls.Add(this.label1); + this.Controls.Add(this.Question); this.Controls.Add(this.S3); this.Controls.Add(this.WebDAV); this.Controls.Add(this.SSH); @@ -129,6 +129,6 @@ namespace Duplicati.GUI.Wizard_pages private System.Windows.Forms.RadioButton SSH; private System.Windows.Forms.RadioButton WebDAV; private System.Windows.Forms.RadioButton S3; - private System.Windows.Forms.Label label1; + private System.Windows.Forms.Label Question; } } diff --git a/Duplicati/GUI/Wizard pages/SelectBackend.cs b/Duplicati/GUI/Wizard pages/SelectBackend.cs index e5ac37b66..4ed8821fd 100644 --- a/Duplicati/GUI/Wizard pages/SelectBackend.cs +++ b/Duplicati/GUI/Wizard pages/SelectBackend.cs @@ -130,6 +130,9 @@ namespace Duplicati.GUI.Wizard_pages Item_CheckChanged(null, null); + if (m_wrapper.PrimayAction == WizardSettingsWrapper.MainAction.RestoreSetup) + Question.Text = "Where is the backup stored?"; + } private void Item_CheckChanged(object sender, EventArgs e) diff --git a/Duplicati/GUI/WizardHandler.cs b/Duplicati/GUI/WizardHandler.cs index 24bd0b973..942e84b10 100644 --- a/Duplicati/GUI/WizardHandler.cs +++ b/Duplicati/GUI/WizardHandler.cs @@ -84,7 +84,6 @@ namespace Duplicati.GUI Schedule schedule = Program.DataConnection.GetObjectById(wrapper.ScheduleID); DateTime when = wrapper.RestoreTime; string target = wrapper.RestorePath; - //TODO: Use this string restoreFilter = wrapper.RestoreFilter; if (when.Ticks == 0) @@ -114,6 +113,10 @@ namespace Duplicati.GUI Program.DataConnection.DeleteObject(o); Program.DataConnection.Commit(items.ToArray()); } + else if (m_form.CurrentPage is Wizard_pages.RestoreSetup.FinishedRestoreSetup) + { + MessageBox.Show(m_form as Form, "The previous Duplicati setup is now restored!", Application.ProductName, MessageBoxButtons.OK, MessageBoxIcon.Information); + } } public bool Visible { get { return m_form.Dialog.Visible; } } diff --git a/Duplicati/Library/Main/BackupStatistics.cs b/Duplicati/Library/Main/BackupStatistics.cs index dc93a529c..a058cf6b4 100644 --- a/Duplicati/Library/Main/BackupStatistics.cs +++ b/Duplicati/Library/Main/BackupStatistics.cs @@ -23,7 +23,7 @@ using System.Text; namespace Duplicati.Library.Main { - public class BackupStatistics : CommunicationStatistics + internal class BackupStatistics : CommunicationStatistics { private DateTime m_beginTime; private DateTime m_endTime; diff --git a/Duplicati/Library/Main/Duplicati.Library.Main.csproj b/Duplicati/Library/Main/Duplicati.Library.Main.csproj index 8a5753e83..95c5c4118 100644 --- a/Duplicati/Library/Main/Duplicati.Library.Main.csproj +++ b/Duplicati/Library/Main/Duplicati.Library.Main.csproj @@ -40,8 +40,8 @@ + - diff --git a/Duplicati/Library/Main/Interface.cs b/Duplicati/Library/Main/Interface.cs index 722d17ecd..3cecb23d9 100644 --- a/Duplicati/Library/Main/Interface.cs +++ b/Duplicati/Library/Main/Interface.cs @@ -23,46 +23,73 @@ using System.Text; namespace Duplicati.Library.Main { - public static class Interface + public enum DuplicatiOperation { - public static string Backup(string source, string target, Dictionary options) + Backup, + Restore, + List, + Remove + }; + + public delegate void OperationProgressEvent(Interface caller, DuplicatiOperation operation, int progress, string message); + + public class Interface : IDisposable + { + private string m_backend; + private Dictionary m_options; + + public event OperationProgressEvent OperationStarted; + public event OperationProgressEvent OperationCompleted; + public event OperationProgressEvent OperationProgress; + public event OperationProgressEvent OperationError; + + public Interface(string backend, Dictionary options) + { + m_backend = backend; + m_options = options; + } + + public string Backup(string source) { BackupStatistics bs = new BackupStatistics(); - SetupCommonOptions(options); + SetupCommonOptions(m_options); Backend.IBackend backend = null; - using (new Logging.Timer("Backup from " + source + " to " + target)) + using (new Logging.Timer("Backup from " + source + " to " + m_backend)) { try { - FilenameStrategy fns = new FilenameStrategy(options); - Core.FilenameFilter filter = new Duplicati.Library.Core.FilenameFilter(options); - bool full = options.ContainsKey("full"); + if (OperationStarted != null) + OperationStarted(this, DuplicatiOperation.Backup, 0, "Started"); - int volumesize = (int)Core.Sizeparser.ParseSize(options.ContainsKey("volsize") ? options["volsize"] : "5", "mb"); + FilenameStrategy fns = new FilenameStrategy(m_options); + Core.FilenameFilter filter = new Duplicati.Library.Core.FilenameFilter(m_options); + bool full = m_options.ContainsKey("full"); + + int volumesize = (int)Core.Sizeparser.ParseSize(m_options.ContainsKey("volsize") ? m_options["volsize"] : "5", "mb"); volumesize = Math.Max(1024 * 1024, volumesize); - long totalmax = options.ContainsKey("totalsize") ? Core.Sizeparser.ParseSize(options["totalsize"], "mb") : long.MaxValue; + long totalmax = m_options.ContainsKey("totalsize") ? Core.Sizeparser.ParseSize(m_options["totalsize"], "mb") : long.MaxValue; totalmax = Math.Max(volumesize, totalmax); - backend = Backend.BackendLoader.GetBackend(target, options); + backend = Backend.BackendLoader.GetBackend(m_backend, m_options); if (backend == null) - throw new Exception("Unable to find backend for target: " + target); - backend = new BackendWrapper(bs, backend, options); - backend = Encryption.EncryptedBackendWrapper.WrapWithEncryption(backend, options); + throw new Exception("Unable to find backend for m_backend: " + m_backend); + backend = new BackendWrapper(bs, backend, m_options); + backend = Encryption.EncryptedBackendWrapper.WrapWithEncryption(backend, m_options); - List prev = ParseFileList(backend, options); + List prev = ParseFileList(backend); if (prev.Count == 0) full = true; - if (!full && options.ContainsKey("full-if-older-than")) - full = DateTime.Now > Core.Timeparser.ParseTimeInterval(options["full-if-older-than"], prev[prev.Count - 1].Time); + if (!full && m_options.ContainsKey("full-if-older-than")) + full = DateTime.Now > Core.Timeparser.ParseTimeInterval(m_options["full-if-older-than"], prev[prev.Count - 1].Time); List controlfiles = new List(); - if (options.ContainsKey("signature-control-files")) - controlfiles.AddRange(options["signature-control-files"].Split(System.IO.Path.PathSeparator)); + if (m_options.ContainsKey("signature-control-files")) + controlfiles.AddRange(m_options["signature-control-files"].Split(System.IO.Path.PathSeparator)); using (Core.TempFolder tempfolder = new Duplicati.Library.Core.TempFolder()) { @@ -71,6 +98,9 @@ namespace Duplicati.Library.Main { using (new Logging.Timer("Reading incremental data")) { + if (OperationProgress != null) + OperationProgress(this, DuplicatiOperation.Backup, 0, "Reading incremental data"); + List entries = new List(); entries.Add(prev[prev.Count - 1]); entries.AddRange(prev[prev.Count - 1].Incrementals); @@ -78,6 +108,9 @@ namespace Duplicati.Library.Main foreach (BackupEntry be in entries) foreach (BackupEntry bes in be.SignatureFile) { + if (OperationProgress != null) + OperationProgress(this, DuplicatiOperation.Backup, 0, "Reading incremental file: " + bes.Filename); + string filename = System.IO.Path.Combine(tempfolder, "patch-" + patches.Count.ToString() + ".zip"); using (new Logging.Timer("Get " + bes.Filename)) backend.Get(bes.Filename, filename); @@ -103,6 +136,9 @@ namespace Duplicati.Library.Main { using (new Logging.Timer("Multipass " + (vol + 1).ToString())) { + if (OperationProgress != null) + OperationProgress(this, DuplicatiOperation.Backup, 0, "Creating volume " + (vol + 1).ToString()); + //The backendwrapper will remove these Core.TempFile signaturefile = new Duplicati.Library.Core.TempFile(); Core.TempFile contentfile = new Duplicati.Library.Core.TempFile(); @@ -116,7 +152,7 @@ namespace Duplicati.Library.Main //Add signature files to archive foreach (string s in controlfiles) if (!string.IsNullOrEmpty(s)) - using (System.IO.Stream cs = signaturearchive.CreateFile(System.IO.Path.GetFileName(s))) + using (System.IO.Stream cs = signaturearchive.CreateFile(System.IO.Path.Combine(RSync.RSyncDir.CONTROL_ROOT, System.IO.Path.GetFileName(s)))) using (System.IO.FileStream fs = System.IO.File.OpenRead(s)) Core.Utility.CopyStream(fs, cs); @@ -178,6 +214,9 @@ namespace Duplicati.Library.Main { if (backend != null) backend.Dispose(); + + if (OperationCompleted != null) + OperationCompleted(this, DuplicatiOperation.Backup, 100, "Completed"); } } @@ -185,23 +224,26 @@ namespace Duplicati.Library.Main return bs.ToString(); } - public static string Restore(string source, string target, Dictionary options) + public string Restore(string target) { - SetupCommonOptions(options); + SetupCommonOptions(m_options); RestoreStatistics rs = new RestoreStatistics(); Backend.IBackend backend = null; - using (new Logging.Timer("Restore from " + source + " to " + target)) + using (new Logging.Timer("Restore from " + m_backend + " to " + target)) { try { - string specificfile = options.ContainsKey("file-to-restore") ? options["file-to-restore"] : ""; - string specifictime = options.ContainsKey("restore-time") ? options["restore-time"] : "now"; - Core.FilenameFilter filter = new Duplicati.Library.Core.FilenameFilter(options); + if (OperationStarted != null) + OperationStarted(this, DuplicatiOperation.Restore, 0, "Started"); + + string specificfile = m_options.ContainsKey("file-to-restore") ? m_options["file-to-restore"] : ""; + string specifictime = m_options.ContainsKey("restore-time") ? m_options["restore-time"] : "now"; + Core.FilenameFilter filter = new Duplicati.Library.Core.FilenameFilter(m_options); //Filter is prefered, if both file and filter is specified - if (!options.ContainsKey("filter") && !string.IsNullOrEmpty(specificfile)) + if (!m_options.ContainsKey("filter") && !string.IsNullOrEmpty(specificfile)) { List list = new List(); list.Add(new Core.FilelistFilter(true, specificfile.Split(System.IO.Path.PathSeparator))); @@ -210,14 +252,14 @@ namespace Duplicati.Library.Main filter = new Duplicati.Library.Core.FilenameFilter(list); } - backend = Backend.BackendLoader.GetBackend(source, options); + backend = Backend.BackendLoader.GetBackend(m_backend, m_options); if (backend == null) - throw new Exception("Unable to find backend for target: " + source); - backend = new BackendWrapper(rs, backend, options); + throw new Exception("Unable to find backend for target: " + m_backend); + backend = new BackendWrapper(rs, backend, m_options); - BackupEntry bestFit = FindBestMatch(ParseFileList(backend, options), specifictime); + BackupEntry bestFit = FindBestMatch(ParseFileList(backend), specifictime); if (bestFit.EncryptionMode != null) - backend = Encryption.EncryptedBackendWrapper.WrapWithEncryption(backend, bestFit.EncryptionMode, options); + backend = Encryption.EncryptedBackendWrapper.WrapWithEncryption(backend, bestFit.EncryptionMode, m_options); List entries = new List(); entries.Add(bestFit); @@ -234,6 +276,10 @@ namespace Duplicati.Library.Main using (Core.TempFile patchzip = new Duplicati.Library.Core.TempFile()) { + //TODO: Set better text messages + if (OperationProgress != null) + OperationProgress(this, DuplicatiOperation.Backup, 0, "Patching restore with #" + (patchno + 1).ToString()); + using (new Logging.Timer("Get " + vol.Filename)) backend.Get(vol.Filename, patchzip); @@ -248,6 +294,9 @@ namespace Duplicati.Library.Main { if (backend != null) backend.Dispose(); + + if (OperationCompleted != null) + OperationCompleted(this, DuplicatiOperation.Restore, 100, "Completed"); } } @@ -262,58 +311,28 @@ namespace Duplicati.Library.Main /// /// Restores control files added to a backup. /// - /// The backend to retrieve the control files from /// The folder into which to restore the files - /// Options that affect how the call is performed /// A restore report - public static string RestoreControlFiles(string source, string target, Dictionary options) + public string RestoreControlFiles(string target) { - SetupCommonOptions(options); + SetupCommonOptions(m_options); RestoreStatistics rs = new RestoreStatistics(); Backend.IBackend backend = null; - using (new Logging.Timer("Restore control files from " + source + " to " + target)) + using (new Logging.Timer("Restore control files from " + m_backend + " to " + target)) { try { - backend = Backend.BackendLoader.GetBackend(source, options); + if (OperationStarted != null) + OperationStarted(this, DuplicatiOperation.Restore, 0, "Started"); + + backend = Backend.BackendLoader.GetBackend(m_backend, m_options); if (backend == null) - throw new Exception("Unable to find backend for target: " + source); - backend = new BackendWrapper(rs, backend, options); - - List attempts = ParseFileList(backend, options); - - Core.FilenameFilter filter; - - if (!options.ContainsKey("file-to-restore")) - { - filter = new Duplicati.Library.Core.FilenameFilter( - new List(new Core.IFilenameFilter[] { - //Exclude everything with a path - new Duplicati.Library.Core.RegularExpressionFilter(false, "*." + System.IO.Path.DirectorySeparatorChar.ToString() + "*."), - //Exclude known files - new Duplicati.Library.Core.FilelistFilter(false, new string[] { - RSync.RSyncDir.ADDED_FOLDERS, - RSync.RSyncDir.DELETED_FILES, - RSync.RSyncDir.DELETED_FOLDERS - }) - }) - ); - } - else - { - filter = new Duplicati.Library.Core.FilenameFilter( - new List(new Core.IFilenameFilter[] { - //Include requested items - new Duplicati.Library.Core.FilelistFilter(true, options["file-to-restore"].Split(System.IO.Path.PathSeparator)), - //Exclude everything else - new Duplicati.Library.Core.RegularExpressionFilter(false, "*.") - }) - ); - } - + throw new Exception("Unable to find backend for target: " + m_backend); + backend = new BackendWrapper(rs, backend, m_options); + List attempts = ParseFileList(backend); List flatlist = new List(); foreach (BackupEntry be in attempts) @@ -323,26 +342,32 @@ namespace Duplicati.Library.Main } flatlist.Reverse(); + + string prefix = Core.Utility.AppendDirSeperator(RSync.RSyncDir.CONTROL_ROOT); + foreach (BackupEntry be in flatlist) { Backend.IBackend realbackend = backend; if (be.EncryptionMode != null) - realbackend = Encryption.EncryptedBackendWrapper.WrapWithEncryption(backend, be.EncryptionMode, options); + realbackend = Encryption.EncryptedBackendWrapper.WrapWithEncryption(backend, be.EncryptionMode, m_options); if (be.SignatureFile.Count > 0) using(Core.TempFile z = new Duplicati.Library.Core.TempFile()) { + if (OperationProgress != null) + OperationProgress(this, DuplicatiOperation.Backup, 0, "Reading incremental data from " + be.SignatureFile[0].Filename); + using (new Logging.Timer("Get " + be.SignatureFile[0].Filename)) realbackend.Get(be.SignatureFile[0].Filename, z); using(Compression.FileArchiveZip fz = new Duplicati.Library.Compression.FileArchiveZip(z)) { bool any = false; - foreach (string f in filter.FilterList("", fz.ListFiles(null))) + foreach (string f in fz.ListFiles(prefix)) { any = true; using (System.IO.Stream s1 = fz.OpenRead(f)) - using (System.IO.Stream s2 = System.IO.File.Create(System.IO.Path.Combine(target, f))) + using (System.IO.Stream s2 = System.IO.File.Create(System.IO.Path.Combine(target, f.Substring(prefix.Length)))) Core.Utility.CopyStream(s1, s2); } @@ -359,6 +384,9 @@ namespace Duplicati.Library.Main { if (backend != null) backend.Dispose(); + + if (OperationCompleted != null) + OperationCompleted(this, DuplicatiOperation.Restore, 100, "Completed"); } } @@ -370,21 +398,24 @@ namespace Duplicati.Library.Main return rs.ToString(); } - public static string RemoveAllButNFull(string source, Dictionary options) + public string RemoveAllButNFull() { - if (!options.ContainsKey("remove-all-but-n-full")) + if (!m_options.ContainsKey("remove-all-but-n-full")) throw new Exception("No count given for \"Remove All But N Full\""); - int x = int.Parse(options["remove-all-but-n-full"]); + int x = int.Parse(m_options["remove-all-but-n-full"]); if (x < 0) throw new Exception("Invalid count for remove-all-but-n-full"); - Backend.IBackend backend = Backend.BackendLoader.GetBackend(source, options); + Backend.IBackend backend = Backend.BackendLoader.GetBackend(m_backend, m_options); if (backend == null) - throw new Exception("Unable to find backend for target: " + source); + throw new Exception("Unable to find backend for target: " + m_backend); try { - List entries = ParseFileList(backend, options); + if (OperationStarted != null) + OperationStarted(this, DuplicatiOperation.Remove, 0, "Started"); + + List entries = ParseFileList(backend); List toremove = new List(); @@ -399,30 +430,35 @@ namespace Duplicati.Library.Main toremove.Add(be); } - return RemoveBackupSets(backend, options, toremove); + return RemoveBackupSets(backend, toremove); } finally { if (backend != null) backend.Dispose(); + if (OperationCompleted != null) + OperationCompleted(this, DuplicatiOperation.Remove, 100, "Completed"); } } - public static string RemoveOlderThan(string source, Dictionary options) + public string RemoveOlderThan() { StringBuilder sb = new StringBuilder(); - if (!options.ContainsKey("remove-older-than")) + if (!m_options.ContainsKey("remove-older-than")) throw new Exception("No count given for \"Remove Older Than\""); - Backend.IBackend backend = Backend.BackendLoader.GetBackend(source, options); + Backend.IBackend backend = Backend.BackendLoader.GetBackend(m_backend, m_options); if (backend == null) - throw new Exception("Unable to find backend for target: " + source); + throw new Exception("Unable to find backend for target: " + m_backend); try { - string duration = options["remove-older-than"]; - List entries = ParseFileList(backend, options); + if (OperationStarted != null) + OperationStarted(this, DuplicatiOperation.Remove, 0, "Started"); + + string duration = m_options["remove-older-than"]; + List entries = ParseFileList(backend); DateTime expires = Core.Timeparser.ParseTimeInterval(duration, DateTime.Now, true); @@ -461,18 +497,20 @@ namespace Duplicati.Library.Main } - sb.Append(RemoveBackupSets(backend, options, toremove)); + sb.Append(RemoveBackupSets(backend, toremove)); } finally { if (backend != null) backend.Dispose(); + if (OperationCompleted != null) + OperationCompleted(this, DuplicatiOperation.Remove, 100, "Completed"); } return sb.ToString(); } - private static string RemoveBackupSets(Backend.IBackend backend, Dictionary options, List entries) + private string RemoveBackupSets(Backend.IBackend backend, List entries) { StringBuilder sb = new StringBuilder(); @@ -480,7 +518,7 @@ namespace Duplicati.Library.Main { sb.AppendLine("Deleting backup at " + be.Time.ToString(System.Globalization.CultureInfo.InvariantCulture)); - if (options.ContainsKey("force")) + if (m_options.ContainsKey("force")) { //Delete manifest backend.Delete(be.Filename); @@ -493,46 +531,62 @@ namespace Duplicati.Library.Main } } - if (!options.ContainsKey("force") && entries.Count > 0) + if (!m_options.ContainsKey("force") && entries.Count > 0) sb.AppendLine("Files are not deleted, use the --force command to actually remove files"); return sb.ToString(); } - public static string[] List(string source, Dictionary options) + public string[] List() { - SetupCommonOptions(options); + SetupCommonOptions(m_options); List res = new List(); - Duplicati.Library.Backend.IBackend i = new Duplicati.Library.Backend.BackendLoader(source, options); + Duplicati.Library.Backend.IBackend i = new Duplicati.Library.Backend.BackendLoader(m_backend, m_options); if (i == null) - throw new Exception("Unable to find backend for target: " + source); + throw new Exception("Unable to find backend for target: " + m_backend); + + if (OperationStarted != null) + OperationStarted(this, DuplicatiOperation.List, 0, "Started"); using(i) foreach (Duplicati.Library.Backend.FileEntry fe in i.List()) res.Add(fe.Name); + if (OperationCompleted != null) + OperationCompleted(this, DuplicatiOperation.List, 100, "Completed"); + return res.ToArray(); } - public static List ParseFileList(string backend, Dictionary options) + public List ParseFileList() { - Backend.IBackend obackend = Backend.BackendLoader.GetBackend(backend, options); + if (OperationStarted != null) + OperationStarted(this, DuplicatiOperation.List, 0, "Started"); + + Backend.IBackend obackend = Backend.BackendLoader.GetBackend(m_backend, m_options); if (obackend == null) - throw new Exception("Unable to find backend for target: " + backend); - using(obackend) - return ParseFileList(obackend, options); + throw new Exception("Unable to find backend for target: " + m_backend); + using (obackend) + { + List data = ParseFileList(obackend); + + if (OperationCompleted != null) + OperationCompleted(this, DuplicatiOperation.List, 100, "Completed"); + + return data; + } } - public static List ParseFileList(Duplicati.Library.Backend.IBackend backend, Dictionary options) + private List ParseFileList(Duplicati.Library.Backend.IBackend backend) { - SetupCommonOptions(options); + SetupCommonOptions(m_options); using (new Logging.Timer("Getting and sorting filelist from " + backend.DisplayName)) { - FilenameStrategy fns = new FilenameStrategy(options); + FilenameStrategy fns = new FilenameStrategy(m_options); List incrementals = new List(); List fulls = new List(); @@ -610,22 +664,25 @@ namespace Duplicati.Library.Main } } - public static IList ListContent(string source, Dictionary options) + public IList ListContent() { - SetupCommonOptions(options); + SetupCommonOptions(m_options); RestoreStatistics rs = new RestoreStatistics(); SortedList res = new SortedList(); - Duplicati.Library.Backend.IBackend backend = new Duplicati.Library.Backend.BackendLoader(source, options); + Duplicati.Library.Backend.IBackend backend = new Duplicati.Library.Backend.BackendLoader(m_backend, m_options); if (backend == null) - throw new Exception("Unable to find backend for target: " + source); - backend = new BackendWrapper(rs, backend, options); + throw new Exception("Unable to find backend for target: " + m_backend); + backend = new BackendWrapper(rs, backend, m_options); - string specifictime = options.ContainsKey("restore-time") ? options["restore-time"] : "now"; - BackupEntry bestFit = FindBestMatch(ParseFileList(backend, options), specifictime); + if (OperationStarted != null) + OperationStarted(this, DuplicatiOperation.List, 0, "Started"); + + string specifictime = m_options.ContainsKey("restore-time") ? m_options["restore-time"] : "now"; + BackupEntry bestFit = FindBestMatch(ParseFileList(backend), specifictime); if (bestFit.EncryptionMode != null) - backend = Encryption.EncryptedBackendWrapper.WrapWithEncryption(backend, bestFit.EncryptionMode, options); + backend = Encryption.EncryptedBackendWrapper.WrapWithEncryption(backend, bestFit.EncryptionMode, m_options); using (backend) using (Core.TempFolder basefolder = new Duplicati.Library.Core.TempFolder()) @@ -647,6 +704,9 @@ namespace Duplicati.Library.Main if (be.CompressionMode != "zip") throw new Exception("Unexpected compression mode"); + if (OperationProgress != null) + OperationProgress(this, DuplicatiOperation.Backup, 0, "Reading incremental data: " + be.Filename); + using (new Logging.Timer("Get " + be.Filename)) backend.Get(be.Filename, patchzip); @@ -673,25 +733,13 @@ namespace Duplicati.Library.Main } } + if (OperationCompleted != null) + OperationCompleted(this, DuplicatiOperation.List, 100, "Completed"); + return res.Keys; } - private static string NormalizeZipFileName(string name) - { - if (System.IO.Path.DirectorySeparatorChar != '/') - name = name.Replace(System.IO.Path.DirectorySeparatorChar, '/'); - return name; - } - - private static string NormalizeZipDirName(string name) - { - name = NormalizeZipFileName(name); - if (!name.EndsWith("/")) - name += "/"; - return name; - } - - private static BackupEntry FindBestMatch(List backups, string specifictime) + private BackupEntry FindBestMatch(List backups, string specifictime) { if (string.IsNullOrEmpty(specifictime)) specifictime = "now"; @@ -720,12 +768,71 @@ namespace Duplicati.Library.Main return bestFit; } - private static void SetupCommonOptions(Dictionary options) + private void SetupCommonOptions(Dictionary options) { if (options.ContainsKey("tempdir")) Core.TempFolder.SystemTempPath = options["tempdir"]; if (options.ContainsKey("thread-priority")) System.Threading.Thread.CurrentThread.Priority = Core.Utility.ParsePriority(options["thread-priority"]); } + + //Static helpers + + public static string[] List(string target, Dictionary options) + { + using (Interface i = new Interface(target, options)) + return i.List(); + } + + public static string Backup(string source, string target, Dictionary options) + { + using (Interface i = new Interface(target, options)) + return i.Backup(source); + } + + public static string Restore(string source, string target, Dictionary options) + { + using (Interface i = new Interface(source, options)) + return i.Restore(target); + } + + public static List ParseFileList(string target, Dictionary options) + { + using (Interface i = new Interface(target, options)) + return i.ParseFileList(); + } + + public static IList ListContent(string target, Dictionary options) + { + using (Interface i = new Interface(target, options)) + return i.ListContent(); + } + + public static string RemoveAllButNFull(string target, Dictionary options) + { + using (Interface i = new Interface(target, options)) + return i.RemoveAllButNFull(); + } + + public static string RemoveOlderThan(string target, Dictionary options) + { + using (Interface i = new Interface(target, options)) + return i.RemoveOlderThan(); + } + + public static string RestoreControlFiles(string source, string target, Dictionary options) + { + using (Interface i = new Interface(source, options)) + return i.RestoreControlFiles(target); + } + + + #region IDisposable Members + + public void Dispose() + { + } + + #endregion } } diff --git a/Duplicati/Library/Main/ProgressEvents.cs b/Duplicati/Library/Main/ProgressEvents.cs new file mode 100644 index 000000000..35103a2d5 --- /dev/null +++ b/Duplicati/Library/Main/ProgressEvents.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Duplicati.Library.Main +{ + /// + /// A wrapper for holding multiple events + /// + public class ProgressEvents + { + } +} diff --git a/Duplicati/Library/Main/RDiffWrapper.cs b/Duplicati/Library/Main/RDiffWrapper.cs deleted file mode 100644 index b713086fe..000000000 --- a/Duplicati/Library/Main/RDiffWrapper.cs +++ /dev/null @@ -1,111 +0,0 @@ -#region Disclaimer / License -// Copyright (C) 2009, 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; - -namespace Duplicati.Library.Main.RSync -{ - /// - /// This class wraps the usage of RDiff.exe so the use of RDiff looks like native calls - /// - public class RDiffWrapper - { - /// - /// The name of the RDiff program to call - /// - public static string RDIFF_PROGRAM = "rdiff"; - - /// - /// Generates a signature file - /// - /// The file to create the signature for - /// The file write the signature to - public static void GenerateSignature(string filename, string outputfile) - { - if (System.IO.File.Exists(outputfile)) - System.IO.File.Delete(outputfile); - //System.IO.File.Create(outputfile); - ReadProgramOutput("signature \"" + filename + "\" \"" + outputfile + "\"", null); - } - - /// - /// Generates a delta file - /// - /// The signature for the file - /// The file to create the delta for - /// The delta output file - public static void GenerateDelta(string signaturefile, string filename, string deltafile) - { - if (System.IO.File.Exists(deltafile)) - System.IO.File.Delete(deltafile); - ReadProgramOutput("delta \"" + signaturefile + "\" \"" + filename + "\" \"" + deltafile + "\"", null); - } - - /// - /// Patches a file - /// - /// The most recent full copy of the file - /// The delta file - /// The restored file - public static void PatchFile(string basefile, string deltafile, string outputfile) - { - if (System.IO.File.Exists(outputfile)) - System.IO.File.Delete(outputfile); - ReadProgramOutput("patch \"" + basefile + "\" \"" + deltafile + "\" \"" + outputfile + "\"", null); - } - - /// - /// Internal helper function that wraps the usage of rdiff - /// - /// The command line arguments - /// An optional stream to write output to - /// The data from stdout - private static byte[] ReadProgramOutput(string args, System.IO.Stream output) - { - System.Diagnostics.ProcessStartInfo pi = new System.Diagnostics.ProcessStartInfo(RDIFF_PROGRAM); - pi.Arguments = args; - pi.CreateNoWindow = true; - pi.RedirectStandardOutput = true; - pi.RedirectStandardError = true; - pi.UseShellExecute = false; - pi.WindowStyle = System.Diagnostics.ProcessWindowStyle.Hidden; - - System.Diagnostics.Process p = System.Diagnostics.Process.Start(pi); - System.IO.Stream outstream = output; - if (outstream == null) - outstream = new System.IO.MemoryStream(); - - byte[] buf = new byte[1024]; - int a; - while ((a = p.StandardOutput.BaseStream.Read(buf, 0, buf.Length)) != 0) - outstream.Write(buf, 0, a); - p.WaitForExit(); - - if (p.ExitCode != 0) - throw new Exception(p.StandardError.ReadToEnd()); - else if (output == null) - return (outstream as System.IO.MemoryStream).ToArray(); - else - return null; - - } - } -} diff --git a/Duplicati/Library/Main/RSyncDir.cs b/Duplicati/Library/Main/RSyncDir.cs index 51bae89c2..347328c7c 100644 --- a/Duplicati/Library/Main/RSyncDir.cs +++ b/Duplicati/Library/Main/RSyncDir.cs @@ -33,6 +33,7 @@ namespace Duplicati.Library.Main.RSync internal static readonly string CONTENT_ROOT = "snapshot"; internal static readonly string DELTA_ROOT = "diff"; //private static readonly string DACL_ROOT = "dacl"; + internal static readonly string CONTROL_ROOT = "controlfiles"; internal static readonly string DELETED_FILES = "deleted_files.txt"; internal static readonly string DELETED_FOLDERS = "deleted_folders.txt"; diff --git a/Duplicati/Library/Main/RestoreStatistics.cs b/Duplicati/Library/Main/RestoreStatistics.cs index 344cfe907..85173e1a2 100644 --- a/Duplicati/Library/Main/RestoreStatistics.cs +++ b/Duplicati/Library/Main/RestoreStatistics.cs @@ -23,7 +23,7 @@ using System.Text; namespace Duplicati.Library.Main { - public class RestoreStatistics : CommunicationStatistics + internal class RestoreStatistics : CommunicationStatistics { private DateTime m_beginTime; private DateTime m_endTime;