2009-02-10 23:36:15 +00:00
#region Disclaimer / License
2011-05-09 19:52:28 +00:00
// Copyright (C) 2011, Kenneth Skovhede
2009-02-10 23:36:15 +00:00
// 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
2009-02-09 06:47:00 +00:00
using System ;
using System.Collections.Generic ;
using System.ComponentModel ;
using System.Drawing ;
using System.Data ;
using System.Text ;
using System.Windows.Forms ;
using Duplicati.Datamodel ;
2009-02-09 16:29:30 +00:00
using System.Collections ;
2012-05-14 18:53:07 +00:00
using Duplicati.Server ;
2009-02-09 06:47:00 +00:00
namespace Duplicati.GUI.HelperControls
{
public partial class BackupFileList : UserControl
{
private DateTime m_when ;
2009-02-09 16:29:30 +00:00
private List < string > m_files ;
2010-05-12 08:41:54 +00:00
private List < string > m_sourcefolders ;
private List < string > m_targetfolders ;
private string m_defaultTarget ;
private TreeNode [] m_rootnodes ;
2009-02-09 06:47:00 +00:00
private Schedule m_schedule ;
2009-02-09 16:29:30 +00:00
private bool m_isInCheck = false ;
2009-02-09 06:47:00 +00:00
2009-06-11 17:22:38 +00:00
private string m_localizedLoadingText ;
2010-05-12 08:41:54 +00:00
private object m_lock = new object ();
private System . Threading . Thread m_workerThread = null ;
public event EventHandler FileListLoaded ;
2009-02-09 06:47:00 +00:00
public BackupFileList ()
{
InitializeComponent ();
2009-06-11 17:22:38 +00:00
m_localizedLoadingText = LoadingIndicator . Text ;
2009-02-09 06:47:00 +00:00
}
2010-05-12 08:41:54 +00:00
public void LoadFileList ( Schedule schedule , DateTime when , List < string > filelist , List < string > targetFolders , string defaultTarget )
2009-02-09 06:47:00 +00:00
{
2009-02-09 16:29:30 +00:00
//backgroundWorker.CancelAsync();
2009-02-09 06:47:00 +00:00
LoadingIndicator . Visible = true ;
progressBar . Visible = true ;
treeView . Visible = false ;
2009-02-09 16:29:30 +00:00
treeView . TreeViewNodeSorter = new NodeSorter ();
2009-06-11 17:22:38 +00:00
LoadingIndicator . Text = m_localizedLoadingText ;
2009-02-09 06:47:00 +00:00
m_files = filelist ;
m_when = when ;
m_schedule = schedule ;
2010-05-12 08:41:54 +00:00
m_defaultTarget = defaultTarget ;
m_targetfolders = targetFolders ;
m_rootnodes = null ;
2009-02-09 06:47:00 +00:00
2010-05-12 08:41:54 +00:00
if (! backgroundWorker . IsBusy )
2009-02-09 06:47:00 +00:00
backgroundWorker . RunWorkerAsync ();
}
private void backgroundWorker_DoWork ( object sender , DoWorkEventArgs e )
{
2010-05-12 08:41:54 +00:00
try
2009-02-09 06:47:00 +00:00
{
2010-05-12 08:41:54 +00:00
lock ( m_lock )
m_workerThread = System . Threading . Thread . CurrentThread ;
DuplicatiRunner r = new DuplicatiRunner ();
//TODO: Speed up by returning the source folders from ListFiles somehow?
IList < string > sourcefolders = r . ListSourceFolders ( m_schedule , m_when );
2010-10-15 16:50:38 +00:00
if ( r . IsAborted || backgroundWorker . CancellationPending )
2010-05-12 08:41:54 +00:00
{
e . Cancel = true ;
return ;
}
if ( m_files == null || m_files . Count == 0 )
{
IList < string > files = r . ListFiles ( m_schedule , m_when );
2010-10-15 16:50:38 +00:00
if ( backgroundWorker . CancellationPending || r . IsAborted )
2010-05-12 08:41:54 +00:00
{
e . Cancel = true ;
return ;
}
if ( m_files != null )
{
m_files . Clear ();
m_files . AddRange ( files );
}
else
m_files = new List < string >( files );
}
if ( m_sourcefolders != null )
{
m_sourcefolders . Clear ();
m_sourcefolders . AddRange ( sourcefolders );
}
else
m_sourcefolders = new List < string >( sourcefolders );
if ( m_targetfolders == null )
m_targetfolders = new List < string >();
if ( m_targetfolders . Count != m_sourcefolders . Count )
{
m_targetfolders . Clear ();
for ( int i = 0 ; i < m_sourcefolders . Count ; i ++)
m_targetfolders . Add ( null );
}
}
catch ( System . Threading . ThreadAbortException )
{
System . Threading . Thread . ResetAbort ();
2010-01-15 23:20:33 +00:00
e . Cancel = true ;
2009-02-09 06:47:00 +00:00
}
2010-05-12 08:41:54 +00:00
finally
2009-02-09 06:47:00 +00:00
{
2010-05-12 08:41:54 +00:00
lock ( m_lock )
m_workerThread = null ;
2009-02-09 06:47:00 +00:00
}
}
private void backgroundWorker_RunWorkerCompleted ( object sender , RunWorkerCompletedEventArgs e )
{
treeView . Nodes . Clear ();
2010-01-15 23:20:33 +00:00
if ( e != null && e . Error != null )
2009-02-09 06:47:00 +00:00
{
LoadingIndicator . Visible = true ;
treeView . Visible = false ;
progressBar . Visible = false ;
2010-01-15 23:20:33 +00:00
LoadingIndicator . Text = e . Error . Message ;
2009-04-07 21:11:53 +00:00
return ;
2009-02-09 06:47:00 +00:00
}
if ( e != null && e . Cancelled )
return ;
try
{
treeView . BeginUpdate ();
2009-02-09 16:29:30 +00:00
bool supported = OSGeo . MapGuide . Maestro . ResourceEditors . ShellIcons . Supported ;
if ( supported )
treeView . ImageList = OSGeo . MapGuide . Maestro . ResourceEditors . ShellIcons . ImageList ;
2009-02-09 06:47:00 +00:00
foreach ( string s in m_files )
{
TreeNodeCollection c = treeView . Nodes ;
2009-02-12 21:52:58 +00:00
string [] parts = s . Split ( System . IO . Path . DirectorySeparatorChar );
2009-02-09 16:29:30 +00:00
for ( int i = 0 ; i < parts . Length ; i ++)
2009-02-09 06:47:00 +00:00
{
2009-02-09 16:29:30 +00:00
if ( parts [ i ] != "" )
2009-02-09 06:47:00 +00:00
{
2009-02-09 16:29:30 +00:00
TreeNode t = FindNode ( parts [ i ], c );
2009-02-09 06:47:00 +00:00
if ( t == null )
{
2009-02-09 16:29:30 +00:00
t = new TreeNode ( parts [ i ]);
if ( supported )
{
if ( i == parts . Length - 1 )
t . ImageIndex = t . SelectedImageIndex = OSGeo . MapGuide . Maestro . ResourceEditors . ShellIcons . GetShellIcon ( s );
else
t . ImageIndex = t . SelectedImageIndex = OSGeo . MapGuide . Maestro . ResourceEditors . ShellIcons . GetFolderIcon ( false );
}
//tag = IsFolder
t . Tag = !( i == parts . Length - 1 );
2009-02-09 06:47:00 +00:00
c . Add ( t );
}
c = t . Nodes ;
}
}
}
2009-02-09 16:29:30 +00:00
2010-05-12 08:41:54 +00:00
//At this point the folders are named 0..n if there are multiple
if ( m_sourcefolders . Count > 1 )
{
2012-10-08 18:48:36 +00:00
m_rootnodes = new TreeNode [ m_sourcefolders . Count ];
2010-05-12 08:41:54 +00:00
foreach ( TreeNode tn in treeView . Nodes )
2012-10-08 18:48:36 +00:00
{
int ix ;
if (! int . TryParse ( tn . Text , out ix ))
ix = - 1 ;
if ( ix < 0 || ix >= m_rootnodes . Length )
{
//TODO: Translate
MessageBox . Show ( this , string . Format ( "A source folder with index {0} ({1}) was found, but the list of source folders have the following {2} entries: {3}{4}" , ix , tn . Text , m_sourcefolders . Count , Environment . NewLine , string . Join ( Environment . NewLine , m_sourcefolders . ToArray ())), "Internal Error" , MessageBoxButtons . OK , MessageBoxIcon . Error );
}
else
m_rootnodes [ ix ] = tn ;
}
for ( int i = 0 ; i < m_rootnodes . Length ; i ++)
if ( m_rootnodes [ i ] == null )
{
TreeNode tn = new TreeNode ( i . ToString ());
treeView . Nodes . Add ( tn );
m_rootnodes [ i ] = tn ;
}
2010-05-12 08:41:54 +00:00
}
//This call updates the names of the nodes to fit their source/target
2011-02-09 16:15:57 +00:00
RefreshRootDisplay ( true );
2010-05-12 08:41:54 +00:00
2009-04-07 22:02:51 +00:00
//TODO: Apparently it is much faster to sort nodes when the tree is detached
2009-02-09 16:29:30 +00:00
treeView . Sort ();
2009-02-09 06:47:00 +00:00
}
finally
{
treeView . EndUpdate ();
}
2010-05-12 08:41:54 +00:00
if ( FileListLoaded != null )
FileListLoaded ( this , null );
2009-02-09 16:29:30 +00:00
2009-02-09 06:47:00 +00:00
LoadingIndicator . Visible = false ;
treeView . Visible = true ;
}
private TreeNode FindNode ( string name , TreeNodeCollection items )
{
foreach ( TreeNode t in items )
if ( t . Text == name )
return t ;
return null ;
}
2009-02-09 16:29:30 +00:00
private class NodeSorter : IComparer
{
#region IComparer Members
public int Compare ( object x , object y )
{
if (!( x is TreeNode ) || !( y is TreeNode ))
return 0 ;
if (( bool )(( TreeNode ) x ). Tag == ( bool )(( TreeNode ) y ). Tag )
return string . Compare ((( TreeNode ) x ). Text , (( TreeNode ) y ). Text );
else
return ( bool )(( TreeNode ) x ). Tag ? - 1 : 1 ;
}
#endregion
}
private void treeView_AfterCheck ( object sender , TreeViewEventArgs e )
{
if (! m_isInCheck )
{
try
{
m_isInCheck = true ;
Queue < TreeNode > nodes = new Queue < TreeNode >();
nodes . Enqueue ( e . Node );
while ( nodes . Count > 0 )
{
TreeNode n = nodes . Dequeue ();
foreach ( TreeNode nx in n . Nodes )
nodes . Enqueue ( nx );
n . Checked = e . Node . Checked ;
}
}
finally
{
m_isInCheck = false ;
}
}
}
private void treeView_AfterCollapse ( object sender , TreeViewEventArgs e )
{
if ( e . Node != null && e . Node . Tag != null && e . Node . Tag is bool )
e . Node . ImageIndex = e . Node . SelectedImageIndex = OSGeo . MapGuide . Maestro . ResourceEditors . ShellIcons . GetFolderIcon ( false );
}
private void treeView_AfterExpand ( object sender , TreeViewEventArgs e )
{
if ( e . Node != null && e . Node . Tag != null && e . Node . Tag is bool )
e . Node . ImageIndex = e . Node . SelectedImageIndex = OSGeo . MapGuide . Maestro . ResourceEditors . ShellIcons . GetFolderIcon ( true );
}
2010-05-12 08:41:54 +00:00
public List < string > TargetFolders { get { return m_targetfolders ; } }
public string DefaultTarget
{
get { return m_defaultTarget ; }
set
{
m_defaultTarget = value ;
try
{
treeView . BeginUpdate ();
2011-02-09 16:15:57 +00:00
RefreshRootDisplay ( false );
2010-05-12 08:41:54 +00:00
}
finally
{
treeView . EndUpdate ();
}
}
}
2011-02-09 18:14:15 +00:00
public List < string > LoadedFileList { get { return m_files ; } }
2009-04-07 22:02:51 +00:00
public List < string > CheckedFiles
2009-02-12 21:52:58 +00:00
{
get
{
2010-05-12 08:41:54 +00:00
try
2009-02-12 21:52:58 +00:00
{
2010-05-12 08:41:54 +00:00
treeView . BeginUpdate ();
//HACK: Re-name the root nodes to match their real path
if ( m_sourcefolders != null && m_sourcefolders . Count > 1 )
for ( int i = 0 ; i < m_rootnodes . Length ; i ++)
m_rootnodes [ i ]. Text = i . ToString ();
List < string > files = new List < string >();
Queue < TreeNode > items = new Queue < TreeNode >();
foreach ( TreeNode t in treeView . Nodes )
items . Enqueue ( t );
treeView . PathSeparator = System . IO . Path . DirectorySeparatorChar . ToString ();
while ( items . Count > 0 )
{
TreeNode t = items . Dequeue ();
2009-02-12 21:52:58 +00:00
2010-05-12 08:41:54 +00:00
foreach ( TreeNode tn in t . Nodes )
items . Enqueue ( tn );
2009-02-12 21:52:58 +00:00
2010-05-12 08:41:54 +00:00
if ( t . Checked )
{
if ( t . Tag != null && ( bool ) t . Tag == true )
2010-11-21 11:08:15 +00:00
files . Add ( Library . Utility . Utility . AppendDirSeparator ( t . FullPath ));
2010-05-12 08:41:54 +00:00
else
files . Add ( t . FullPath );
}
2009-04-07 22:02:51 +00:00
}
2010-05-12 08:41:54 +00:00
return files ;
}
finally
{
//HACK: Rename them back
2011-02-09 16:15:57 +00:00
RefreshRootDisplay ( false );
2010-05-12 08:41:54 +00:00
treeView . EndUpdate ();
2009-02-12 21:52:58 +00:00
}
2010-05-12 08:41:54 +00:00
}
set
{
try
{
treeView . BeginUpdate ();
//HACK: Re-name the root nodes to match their real path
if ( m_sourcefolders != null && m_sourcefolders . Count > 1 )
for ( int i = 0 ; i < m_rootnodes . Length ; i ++)
m_rootnodes [ i ]. Text = i . ToString ();
if ( value != null )
foreach ( string s in value )
{
TreeNodeCollection col = treeView . Nodes ;
TreeNode p = null ;
foreach ( string e in s . Split ( System . IO . Path . DirectorySeparatorChar ))
{
foreach ( TreeNode n in col )
2010-11-21 11:08:15 +00:00
if ( n . Text . Equals ( e , Library . Utility . Utility . ClientFilenameStringComparision ))
2010-05-12 08:41:54 +00:00
{
p = n ;
col = n . Nodes ;
break ;
}
}
2009-04-07 22:02:51 +00:00
2010-05-12 08:41:54 +00:00
if ( p != null )
{
p . Checked = true ;
while ( p . Parent != null )
{
p . Parent . Expand ();
p = p . Parent ;
}
}
}
}
finally
{
//HACK: Rename them back
2011-02-09 16:15:57 +00:00
RefreshRootDisplay ( false );
2010-05-12 08:41:54 +00:00
treeView . EndUpdate ();
}
2009-02-12 21:52:58 +00:00
}
}
2010-05-12 08:41:54 +00:00
2009-02-09 16:29:30 +00:00
public string CheckedAsFilter
{
get
{
List < KeyValuePair < bool , string >> filter = new List < KeyValuePair < bool , string >>();
treeView . PathSeparator = System . IO . Path . DirectorySeparatorChar . ToString ();
2009-04-07 22:02:51 +00:00
foreach ( string path in this . CheckedFiles )
2010-11-21 11:08:15 +00:00
filter . Add ( new KeyValuePair < bool , string >( true , Library . Utility . FilenameFilter . ConvertGlobbingToRegExp ( treeView . PathSeparator + path )));
2009-02-09 16:29:30 +00:00
2009-04-07 22:02:51 +00:00
//Exclude everything else
filter . Add ( new KeyValuePair < bool , string >( false , ".*" ));
2009-02-09 16:29:30 +00:00
2010-11-21 11:08:15 +00:00
return Library . Utility . FilenameFilter . EncodeAsFilter ( filter );
2009-02-09 16:29:30 +00:00
}
}
2009-04-07 22:02:51 +00:00
public int CheckedCount { get { return this . CheckedFiles . Count ; } }
2010-05-12 08:41:54 +00:00
2011-02-09 16:15:57 +00:00
private void RefreshRootDisplay ( bool sort )
2010-05-12 08:41:54 +00:00
{
2011-02-15 19:13:34 +00:00
if ( m_sourcefolders == null || m_sourcefolders . Count <= 1 || m_rootnodes == null )
2010-05-12 08:41:54 +00:00
return ;
2010-11-02 19:51:22 +00:00
string [] targets = this . TargetSuggestions ;
for ( int index = 0 ; index < m_rootnodes . Length ; index ++)
2010-10-12 22:22:29 +00:00
{
2010-11-02 19:51:22 +00:00
m_rootnodes [ index ]. Text = m_sourcefolders [ index ] + " => " + (! string . IsNullOrEmpty ( m_targetfolders [ index ]) ? m_targetfolders [ index ] : targets [ index ]);
m_rootnodes [ index ]. ToolTipText = string . Format ( Strings . BackupFileList . RootNodeTooltip , m_sourcefolders [ index ], string . IsNullOrEmpty ( m_targetfolders [ index ]) ? targets [ index ] : m_targetfolders [ index ]);
}
2010-10-12 22:22:29 +00:00
2011-02-09 16:15:57 +00:00
if ( sort )
treeView . Sort ();
2010-11-02 19:51:22 +00:00
}
2010-10-12 22:22:29 +00:00
2010-11-02 19:51:22 +00:00
public string [] TargetSuggestions
{
get
{
if ( m_sourcefolders == null || m_sourcefolders . Count <= 1 )
return null ;
List < string > suggestions = new List < string >();
for ( int i = 0 ; i < m_sourcefolders . Count ; i ++)
{
string s = m_sourcefolders [ i ];
//HACK: We use a leading / in the path name to detect source OS
// all paths are absolute, so this detects all unix like systems
string dirSepChar = m_sourcefolders [ i ]. StartsWith ( "/" ) ? "/" : "\\" ;
2010-10-12 22:22:29 +00:00
2010-11-02 19:51:22 +00:00
if ( s . EndsWith ( dirSepChar ))
s = s . Substring ( 0 , s . Length - 1 );
2010-10-12 22:22:29 +00:00
2010-11-02 19:51:22 +00:00
int lix = s . LastIndexOf ( dirSepChar );
if ( lix < 0 || lix + 1 >= s . Length )
s = i . ToString ();
else
s = s . Substring ( lix + 1 );
2010-10-12 22:22:29 +00:00
2010-11-02 19:51:22 +00:00
foreach ( char c in System . IO . Path . GetInvalidFileNameChars ())
s = s . Replace ( c , '_' );
2010-05-12 08:41:54 +00:00
2010-11-02 19:51:22 +00:00
suggestions . Add ( s );
}
2010-05-12 08:41:54 +00:00
2010-11-21 11:08:15 +00:00
Dictionary < string , int > duplicates = new Dictionary < string , int >( Library . Utility . Utility . ClientFilenameStringComparer );
2010-11-02 19:51:22 +00:00
for ( int i = 0 ; i < suggestions . Count ; i ++)
if ( duplicates . ContainsKey ( suggestions [ i ]))
duplicates [ suggestions [ i ]]++;
else
duplicates [ suggestions [ i ]] = 1 ;
string [] targets = new string [ m_rootnodes . Length ];
for ( int index = 0 ; index < m_rootnodes . Length ; index ++)
{
string suffix = duplicates [ suggestions [ index ]] > 1 ? index . ToString () : suggestions [ index ];
2010-11-21 11:08:15 +00:00
targets [ index ] = string . IsNullOrEmpty ( m_defaultTarget ) ? "" : Library . Utility . Utility . AppendDirSeparator ( m_defaultTarget ) + suffix ;
2010-11-02 19:51:22 +00:00
}
return targets ;
}
2010-05-12 08:41:54 +00:00
}
private void treeView_BeforeLabelEdit ( object sender , NodeLabelEditEventArgs e )
{
e . CancelEdit = true ;
if ( m_sourcefolders != null && m_sourcefolders . Count > 1 && Array . IndexOf < TreeNode >( m_rootnodes , e . Node ) >= 0 )
browseForTargetFolder ( e . Node );
}
private void changeDestinationToolStripMenuItem_Click ( object sender , EventArgs e )
{
browseForTargetFolder ( changeDestinationToolStripMenuItem . Tag as TreeNode );
}
private void browseForTargetFolder ( TreeNode node )
{
if ( folderBrowserDialog . ShowDialog ( this ) == DialogResult . OK )
{
m_targetfolders [ Array . IndexOf < TreeNode >( m_rootnodes , node )] = folderBrowserDialog . SelectedPath ;
try
{
treeView . BeginUpdate ();
2011-02-09 16:15:57 +00:00
RefreshRootDisplay ( false );
2010-05-12 08:41:54 +00:00
}
finally
{
treeView . EndUpdate ();
}
}
}
private void contextMenuStrip_Opening ( object sender , CancelEventArgs e )
{
TreeNode node = treeView . GetNodeAt ( this . PointToClient ( Cursor . Position ));
if ( node == null || m_sourcefolders == null || m_sourcefolders . Count <= 1 || Array . IndexOf < TreeNode >( m_rootnodes , node ) < 0 )
e . Cancel = true ;
else
changeDestinationToolStripMenuItem . Tag = node ;
}
private void treeView_KeyUp ( object sender , KeyEventArgs e )
{
if ( e . KeyCode == Keys . F2 )
treeView_BeforeLabelEdit ( treeView , new NodeLabelEditEventArgs ( treeView . SelectedNode ));
}
private void treeView_DoubleClick ( object sender , EventArgs e )
{
TreeNode node = treeView . GetNodeAt ( this . PointToClient ( Cursor . Position ));
if ( node != null )
treeView_BeforeLabelEdit ( treeView , new NodeLabelEditEventArgs ( treeView . SelectedNode ));
}
2009-02-09 06:47:00 +00:00
}
}