Files
duplicati/Duplicati/GUI/Wizard pages/Add backup/SelectFiles.cs
T
kenneth.skovhede@gmail.com b81d63ea73 Fixed a bug with async uploads that caused some files to no be uploaded.
Fixed a protential race condition with warning messages and file counts (cosmetic only) when using async transfers.
Spelling correction, Seperator -> Separator.
Changed so a restore operation can also supply advanced options.
Changed the format of timestamps in filenames to follow the Duplicity convention, which removes the need for --time-separator and --use-short-filenames.
Moved the URL utility into Core, to avoid compilation errors with Monodevelop.
Reorganized the runner to better support the LiveControls on all operations rather than only for backup/restore.

Update issue #210
Status: Fixed
I have now changed the time format to follow the Duplicity naming convention.
This means that all filenames are now generated with the time set as "yyyyMMddTHHmmssZ".
This format is more portable across all platforms, and easily sortable/readable.
The drawback is that the Z indicates UTC time, so if one looks at the filenames, 
the times are not local. The motivation for this is to avoid trouble
with daylight savings time offsets.

There is full backwards compatibility as long as backups have not used more than
one character for --time-separator (only possible via commandline).
This new format means that the options --time-separator and --use-short-filenames
are now deprecated and removed from the user interface. It is possible to
access them from the advanced options page, along with the --use-old-filenames option.

If anyone needs these options for some reason, let me know, otherwise they will be removed
in a later version.



git-svn-id: https://duplicati.googlecode.com/svn/trunk@523 59da171f-624f-0410-aa54-27559c288bec
2010-09-25 12:02:14 +00:00

613 lines
26 KiB
C#

#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.ComponentModel;
using System.Drawing;
using System.Data;
using System.Text;
using System.Windows.Forms;
using System.Windows.Forms.Wizard;
using Duplicati.Datamodel;
namespace Duplicati.GUI.Wizard_pages.Add_backup
{
public partial class SelectFiles : WizardControl
{
private const int COLLAPSED_GROUP_SIZE = 24;
private const int GRID_SPACING = 8;
private WorkerThread<string> m_calculator;
private object m_lock = new object();
private Dictionary<string, long> m_sizes;
private IButtonControl m_acceptButton;
private WizardSettingsWrapper m_wrapper;
private string m_myPictures;
private string m_myMusic;
private string m_desktop;
private string m_appData;
private string m_myDocuments;
private string[] m_specialFolders;
public SelectFiles()
: base(Strings.SelectFiles.PageTitle, Strings.SelectFiles.PageDescription)
{
InitializeComponent();
m_sizes = new Dictionary<string, long>(Library.Core.Utility.IsFSCaseSensitive ? StringComparer.CurrentCulture : StringComparer.CurrentCultureIgnoreCase );
m_myPictures = Library.Core.Utility.AppendDirSeparator(System.Environment.GetFolderPath(Environment.SpecialFolder.MyPictures));
m_myMusic = Library.Core.Utility.AppendDirSeparator(System.Environment.GetFolderPath(Environment.SpecialFolder.MyMusic));
m_desktop = Library.Core.Utility.AppendDirSeparator(System.Environment.GetFolderPath(Environment.SpecialFolder.Desktop));
m_appData = Library.Core.Utility.AppendDirSeparator(System.Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData));
m_myDocuments = Library.Core.Utility.AppendDirSeparator(System.Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments));
FolderTooltip.SetToolTip(IncludeDocuments, m_myDocuments);
FolderTooltip.SetToolTip(IncludeMusic, m_myMusic);
FolderTooltip.SetToolTip(IncludeImages, m_myPictures);
FolderTooltip.SetToolTip(IncludeDesktop, m_desktop);
FolderTooltip.SetToolTip(IncludeSettings, m_appData);
m_specialFolders = new string[] {
m_myDocuments,
m_myMusic,
m_myPictures,
m_appData,
m_desktop
};
base.PageEnter += new PageChangeHandler(SelectFiles_PageEnter);
base.PageLeave += new PageChangeHandler(SelectFiles_PageLeave);
}
void m_owner_Cancelled(object sender, CancelEventArgs e)
{
StopCalculator();
}
private void SaveSettings()
{
m_settings["Files:Sizes"] = m_sizes;
m_wrapper.SelectFilesUI.Version = 2;
m_wrapper.SelectFilesUI.UseSimpleMode = DocumentsRadio.Checked;
m_wrapper.SelectFilesUI.IncludeDocuments = IncludeDocuments.Checked;
m_wrapper.SelectFilesUI.IncludeMusic = IncludeMusic.Checked;
m_wrapper.SelectFilesUI.IncludeImages = IncludeImages.Checked;
m_wrapper.SelectFilesUI.IncludeDesktop = IncludeDesktop.Checked;
m_wrapper.SelectFilesUI.IncludeSettings = IncludeSettings.Checked;
List<string> folders = new List<string>();
foreach (HelperControls.FolderPathEntry entry in InnerControlContainer.Controls)
{
string path = entry.SelectedPath.Trim();
if (!string.IsNullOrEmpty(path))
folders.Add(Library.Core.Utility.AppendDirSeparator(path));
}
folders.Reverse();
m_wrapper.SourcePath = string.Join(System.IO.Path.PathSeparator.ToString(), folders.ToArray());
}
void SelectFiles_PageLeave(object sender, PageChangedArgs args)
{
m_owner.Cancelled -= new CancelEventHandler(m_owner_Cancelled);
SaveSettings();
if (args.Direction == PageChangedDirection.Back)
{
StopCalculator();
return;
}
if (DocumentsRadio.Checked)
{
if (!(IncludeDesktop.Checked || IncludeDocuments.Checked || IncludeImages.Checked || IncludeMusic.Checked || IncludeSettings.Checked))
{
MessageBox.Show(this, Strings.SelectFiles.NoFilesSelectedError, Application.ProductName, MessageBoxButtons.OK, MessageBoxIcon.Information);
args.Cancel = true;
return;
}
//Ensure that the right path is inserted at backup time
m_wrapper.SourcePath = "";
}
else
{
List<string> folders = new List<string>();
foreach (HelperControls.FolderPathEntry entry in InnerControlContainer.Controls)
{
string path = entry.SelectedPath.Trim();
if (!string.IsNullOrEmpty(path))
folders.Add(Library.Core.Utility.AppendDirSeparator(path));
}
for (int i = 0; i < folders.Count; i++)
{
while (true)
if (!System.IO.Directory.Exists(folders[i]))
{
DialogResult res = MessageBox.Show(this, string.Format(Strings.SelectFiles.FolderDoesNotExistWarning, folders[i]), Application.ProductName, MessageBoxButtons.AbortRetryIgnore, MessageBoxIcon.Warning);
if (res == DialogResult.Abort)
{
args.Cancel = true;
return;
}
else if (res == DialogResult.Ignore)
break;
}
else
break;
for (int j = i + 1; j < folders.Count; j++)
{
if (folders[i].Equals(folders[j], Library.Core.Utility.ClientFilenameStringComparision))
{
MessageBox.Show(this, string.Format(Strings.SelectFiles.DuplicateFolderError, folders[i]), Application.ProductName, MessageBoxButtons.OK, MessageBoxIcon.Information);
args.Cancel = true;
return;
}
if (folders[i].StartsWith(folders[j]))
{
MessageBox.Show(this, string.Format(Strings.SelectFiles.RelatedFoldersError, folders[i], folders[j]), Application.ProductName, MessageBoxButtons.OK, MessageBoxIcon.Information);
args.Cancel = true;
return;
}
if (folders[j].StartsWith(folders[i]))
{
MessageBox.Show(this, string.Format(Strings.SelectFiles.RelatedFoldersError, folders[j], folders[i]), Application.ProductName, MessageBoxButtons.OK, MessageBoxIcon.Information);
args.Cancel = true;
return;
}
}
}
if (folders.Count == 0)
{
MessageBox.Show(this, Strings.SelectFiles.NoFilesSelectedError, Application.ProductName, MessageBoxButtons.OK, MessageBoxIcon.Information);
args.Cancel = true;
return;
}
folders.Reverse();
m_wrapper.SourcePath = string.Join(System.IO.Path.PathSeparator.ToString(), folders.ToArray());
}
StopCalculator();
args.NextPage = new PasswordSettings();
}
private void UpgradeFromVersionOne()
{
//Upgrade from the previous design with one source folder and multiple filters
MessageBox.Show(this, Strings.SelectFiles.UpgradeWarning, Application.ProductName, MessageBoxButtons.OK, MessageBoxIcon.Information);
string p = Library.Core.Utility.AppendDirSeparator(m_wrapper.SourcePath);
List<KeyValuePair<bool, string>> filters = Library.Core.FilenameFilter.DecodeFilter(m_wrapper.EncodedFilters);
//See what folders are included with the current setup
Library.Core.FilenameFilter filter = new Duplicati.Library.Core.FilenameFilter(filters);
IncludeDocuments.Checked = filter.ShouldInclude(p, m_myDocuments);
IncludeImages.Checked = filter.ShouldInclude(p, m_myPictures);
IncludeMusic.Checked = filter.ShouldInclude(p, m_myMusic);
IncludeDesktop.Checked = filter.ShouldInclude(p, m_desktop);
IncludeSettings.Checked = filter.ShouldInclude(p, m_appData);
//Remove any filters relating to the special folders
for (int i = 0; i < filters.Count; i++)
foreach (string s in m_specialFolders)
if (s.StartsWith(p))
{
if (filters[i].Value == Library.Core.FilenameFilter.ConvertGlobbingToRegExp(s.Substring(p.Length - 1) + "*"))
{
filters.RemoveAt(i);
i--;
break;
}
}
//Remove any "exclude all" filters
for (int i = 0; i < filters.Count; i++)
if (filters[i].Key == false && filters[i].Value == ".*")
{
filters.RemoveAt(i);
i--;
}
//See if there are extra filters that are not supported
bool unsupported = false;
foreach (KeyValuePair<bool, string> f in filters)
if (f.Value.StartsWith(Library.Core.FilenameFilter.ConvertGlobbingToRegExp(System.IO.Path.DirectorySeparatorChar.ToString())))
{
unsupported = true;
break;
}
//If none of the special folders are included, we don't support the setup with simple mode
unsupported |= !(IncludeDocuments.Checked | IncludeImages.Checked | IncludeMusic.Checked | IncludeDesktop.Checked | IncludeSettings.Checked);
InnerControlContainer.Controls.Clear();
AddFolderControl().SelectedPath = m_wrapper.SourcePath;
//Make sure the extra filters are not included
if (!unsupported)
m_wrapper.EncodedFilters = Library.Core.FilenameFilter.EncodeAsFilter(filters);
if (unsupported)
FolderRadio.Checked = true;
else
DocumentsRadio.Checked = true;
}
void SelectFiles_PageEnter(object sender, PageChangedArgs args)
{
m_wrapper = new WizardSettingsWrapper(m_settings);
m_owner.Cancelled += new CancelEventHandler(m_owner_Cancelled);
if (m_settings.ContainsKey("Files:Sizes"))
m_sizes = (Dictionary<string, long>)m_settings["Files:Sizes"];
if (!m_valuesAutoLoaded)
{
if (m_wrapper.SelectFilesUI.Version < 2)
{
//Either upgrade or fresh copy
if (string.IsNullOrEmpty(m_wrapper.SourcePath))
{
//Set defaults
DocumentsRadio.Checked = true;
IncludeDocuments.Checked = true;
IncludeImages.Checked = true;
IncludeMusic.Checked = false;
IncludeDesktop.Checked = true;
IncludeSettings.Checked = false;
}
else
UpgradeFromVersionOne();
}
else
{
//Multifolder version
if (m_wrapper.SelectFilesUI.UseSimpleMode)
{
IncludeDocuments.Checked = m_wrapper.SelectFilesUI.IncludeDocuments;
IncludeImages.Checked = m_wrapper.SelectFilesUI.IncludeImages;
IncludeMusic.Checked = m_wrapper.SelectFilesUI.IncludeMusic;
IncludeDesktop.Checked = m_wrapper.SelectFilesUI.IncludeDesktop;
IncludeSettings.Checked = m_wrapper.SelectFilesUI.IncludeSettings;
DocumentsRadio.Checked = true;
}
else
{
FolderRadio.Checked = true;
}
}
}
//Always populate the list
InnerControlContainer.Controls.Clear();
if (!string.IsNullOrEmpty(m_wrapper.SourcePath))
foreach (string s in m_wrapper.SourcePath.Split(System.IO.Path.PathSeparator))
if (!string.IsNullOrEmpty(s))
AddFolderControl().SelectedPath = s;
Rescan();
//Make sure we resize correctly
TargetType_CheckedChanged(null, null);
}
private void StartCalculator()
{
if (m_calculator == null)
{
totalSize.Text = Strings.SelectFiles.CalculatingSize;
m_calculator = new WorkerThread<string>(CalculateFolderSize, false);
m_calculator.CompletedWork += new EventHandler(m_calculator_CompletedWork);
}
}
private void StopCalculator()
{
if (m_calculator != null)
{
m_calculator.ClearQueue(true);
m_calculator.CompletedWork -= new EventHandler(m_calculator_CompletedWork);
m_calculator.Terminate(false);
m_calculator = null;
}
}
void m_calculator_CompletedWork(object sender, EventArgs e)
{
if (this.InvokeRequired)
this.Invoke(new EventHandler(m_calculator_CompletedWork), sender, e);
else
{
if (m_calculator == null)
return;
long s = 0;
if (DocumentsRadio.Checked)
{
List<string> inclFolders = new List<string>();
lock (m_lock)
{
if (m_sizes.ContainsKey(m_myDocuments) && IncludeDocuments.Checked)
s += FindActualSize(m_myDocuments);
if (m_sizes.ContainsKey(m_myMusic) && IncludeMusic.Checked)
s += FindActualSize(m_myMusic);
if (m_sizes.ContainsKey(m_myPictures) && IncludeImages.Checked)
s += FindActualSize(m_myPictures);
if (m_sizes.ContainsKey(m_appData) && IncludeSettings.Checked)
s += FindActualSize(m_appData);
if (m_sizes.ContainsKey(m_desktop) && IncludeDesktop.Checked)
s += FindActualSize(m_desktop);
}
}
else
{
lock(m_lock)
foreach (HelperControls.FolderPathEntry entry in InnerControlContainer.Controls)
{
string path = entry.SelectedPath.Trim();
if (m_sizes.ContainsKey(path))
{
s += m_sizes[path];
entry.FolderSize = Library.Core.Utility.FormatSizeString(m_sizes[path]);
}
}
}
if (m_calculator.CurrentTasks.Count == 0 && !m_calculator.Active)
totalSize.Text = string.Format(Strings.SelectFiles.FinalSizeCalculated, Library.Core.Utility.FormatSizeString(s));
else
totalSize.Text = string.Format(Strings.SelectFiles.PartialSizeCalculated, Library.Core.Utility.FormatSizeString(s));
lock(m_lock)
{
if (m_sizes.ContainsKey(m_myMusic))
myMusicSize.Text = Library.Core.Utility.FormatSizeString(FindActualSize(m_myMusic));
if (m_sizes.ContainsKey(m_myPictures))
myPicturesSize.Text = Library.Core.Utility.FormatSizeString(FindActualSize(m_myPictures));
if (m_sizes.ContainsKey(m_desktop))
desktopSize.Text = Library.Core.Utility.FormatSizeString(FindActualSize(m_desktop));
if (m_sizes.ContainsKey(m_appData))
appdataSize.Text = Library.Core.Utility.FormatSizeString(FindActualSize(m_appData));
if (m_sizes.ContainsKey(m_myDocuments))
myDocumentsSize.Text = Library.Core.Utility.FormatSizeString(FindActualSize(m_myDocuments));
}
}
}
/// <summary>
/// Calculates the size of a folder, excluding sizes of excluded folders.
/// This function is always called with the lock held
/// </summary>
/// <param name="path">The path to find the size for</param>
/// <param name="exclFolders">The list of excluded folders</param>
/// <returns>The size of the folder</returns>
private long FindActualSize(string path)
{
long size = m_sizes[path];
foreach (string f in m_specialFolders)
if (f != path && f.StartsWith(path) && m_sizes.ContainsKey(f))
size -= m_sizes[f];
return size;
}
private void Rescan()
{
lock (m_lock)
{
StartCalculator();
m_calculator.ClearQueue(true);
if (DocumentsRadio.Checked)
{
if (!m_sizes.ContainsKey(m_myMusic))
m_calculator.AddTask(m_myMusic);
if (!m_sizes.ContainsKey(m_myPictures))
m_calculator.AddTask(m_myPictures);
if (!m_sizes.ContainsKey(m_desktop))
m_calculator.AddTask(m_desktop);
if (!m_sizes.ContainsKey(m_appData))
m_calculator.AddTask(m_appData);
if (!m_sizes.ContainsKey(m_myDocuments))
m_calculator.AddTask(m_myDocuments);
}
else
{
foreach (HelperControls.FolderPathEntry e in InnerControlContainer.Controls)
{
string s = e.SelectedPath.Trim();
if (s.Length > 0 && !m_sizes.ContainsKey(s))
m_calculator.AddTask(s);
}
}
m_calculator_CompletedWork(null, null);
}
}
private void TargetType_CheckedChanged(object sender, EventArgs e)
{
DocumentGroup.Enabled = DocumentsRadio.Checked;
FolderGroup.Enabled = FolderRadio.Checked;
if (DocumentsRadio.Checked)
{
FolderGroup.Height = COLLAPSED_GROUP_SIZE;
FolderGroup.Top = FolderRadio.Top = LayoutControlPanel.Height - FolderGroup.Height;
DocumentGroup.Height = LayoutControlPanel.Height - FolderGroup.Height - GRID_SPACING;
if (m_acceptButton != null)
{
m_owner.Dialog.AcceptButton = m_acceptButton;
m_acceptButton = null;
}
}
else
{
DocumentGroup.Height = COLLAPSED_GROUP_SIZE;
FolderGroup.Top = FolderRadio.Top = DocumentGroup.Height + GRID_SPACING;
FolderGroup.Height = LayoutControlPanel.Height - FolderGroup.Top;
if (m_acceptButton == null)
{
m_acceptButton = m_owner.Dialog.AcceptButton;
m_owner.Dialog.AcceptButton = null;
}
}
Rescan();
EnsureLastFolderEntryIsEmpty();
}
private void CalculateFolderSize(string folder)
{
lock (m_lock)
if (m_sizes.ContainsKey(folder))
return;
//Calculate outside lock
long size = Duplicati.Library.Core.Utility.GetDirectorySize(folder, null);
lock (m_lock)
m_sizes[folder] = size;
}
private void FolderPathEntry_Leave(object sender, EventArgs e)
{
lock (m_lock)
{
HelperControls.FolderPathEntry entry = (HelperControls.FolderPathEntry)sender;
string path = entry.SelectedPath.Trim();
if (path.Length > 0 && !m_sizes.ContainsKey(path))
{
StartCalculator();
m_calculator.AddTask(path);
m_calculator_CompletedWork(null, null);
}
else if (entry.SelectedPath.Trim() == "")
entry.FolderSize = "";
else if (m_sizes.ContainsKey(path))
m_calculator_CompletedWork(null, null);
}
}
private void SelectFiles_VisibleChanged(object sender, EventArgs e)
{
if (!this.Visible)
StopCalculator();
if (!this.Visible && m_acceptButton != null && m_owner != null)
{
m_owner.Dialog.AcceptButton = m_acceptButton;
m_acceptButton = null;
}
}
private HelperControls.FolderPathEntry AddFolderControl()
{
//Just use the last one if it is empty anyway
if (InnerControlContainer.Controls.Count > 0)
{
HelperControls.FolderPathEntry lastEntry = (HelperControls.FolderPathEntry)InnerControlContainer.Controls[0];
if (lastEntry.SelectedPath.Trim().Length == 0)
return lastEntry;
}
try
{
InnerControlContainer.SuspendLayout();
HelperControls.FolderPathEntry entry = new Duplicati.GUI.HelperControls.FolderPathEntry();
entry.FolderBrowserDialog = folderBrowserDialog;
entry.SelectedPathChanged += new EventHandler(FolderPathEntry_SelectedPathChanged);
entry.DeleteButton_Clicked += new EventHandler(FolderPathEntry_DeleteButtonClicked);
entry.SelectedPathLeave += new EventHandler(FolderPathEntry_Leave);
entry.Height = entry.MinimumSize.Height + 2;
InnerControlContainer.Controls.Add(entry);
entry.Dock = DockStyle.Top;
entry.BringToFront();
return entry;
}
finally
{
InnerControlContainer.ResumeLayout();
}
}
void FolderPathEntry_SelectedPathChanged(object sender, EventArgs e)
{
((HelperControls.FolderPathEntry)sender).FolderSize = Strings.SelectFiles.CalculatingSizeMarker;
EnsureLastFolderEntryIsEmpty();
}
void FolderPathEntry_DeleteButtonClicked(object sender, EventArgs e)
{
HelperControls.FolderPathEntry entry = (HelperControls.FolderPathEntry)sender;
InnerControlContainer.Controls.Remove(entry);
entry.SelectedPathChanged -= new EventHandler(FolderPathEntry_SelectedPathChanged);
entry.DeleteButton_Clicked -= new EventHandler(FolderPathEntry_DeleteButtonClicked);
entry.SelectedPathLeave -= new EventHandler(FolderPathEntry_Leave);
EnsureLastFolderEntryIsEmpty();
}
/// <summary>
/// Helper function that ensures that the last folder entry is always empty so the user may enter text in it
/// </summary>
private void EnsureLastFolderEntryIsEmpty()
{
if (InnerControlContainer.Controls.Count == 0)
AddFolderControl().Focus();
else
{
HelperControls.FolderPathEntry lastEntry = (HelperControls.FolderPathEntry)InnerControlContainer.Controls[0];
if (lastEntry.SelectedPath.Trim().Length == 0)
{
//If we have a duplicate empty, remove the non-focused one
if (InnerControlContainer.Controls.Count > 1 && ((HelperControls.FolderPathEntry)InnerControlContainer.Controls[1]).SelectedPath.Trim().Length == 0)
FolderPathEntry_DeleteButtonClicked(InnerControlContainer.Controls[(lastEntry.Focused ? 0 : 1)], null);
}
else
AddFolderControl();
}
}
}
}