lantool

ein feines Tool für LANs (damals)
git clone https://git.clttr.info/lantool.git
Log (Feed) | Files | Refs (Tags) | README | LICENSE

MainForm.cs (66309B)


      1 using System;
      2 using System.Collections;
      3 using System.Collections.Generic;
      4 using System.Diagnostics;
      5 using System.Drawing;
      6 using System.IO;
      7 using System.Text.RegularExpressions;
      8 using System.Windows.Forms;
      9 using LanTool.Classes;
     10 
     11 namespace LanTool
     12 {
     13 	public partial class MainForm : Form
     14 	{
     15 		#region Konstanten
     16 
     17 		internal const string DefaultHeader = "LanTool 1.6 - LumpLan #1 Edition";
     18 
     19 		#endregion
     20 
     21 		#region Felder
     22 
     23 		internal StuffItemTree m_MyDisks = new StuffItemTree("Meine Platten", false);
     24 
     25 		internal StuffItemTree m_OtherDisks = new StuffItemTree("Fremde Platten", true);
     26 
     27 		// Zähler für die Anzahl der Kopierthreads
     28 		int copyThreadCount = 0;
     29 
     30 		// Zähler für das Maximum des Fortschrittsbalkens
     31 		float maxProgressMB = 0;
     32 		float currentProgressMB = 0;
     33 
     34 		/// <summary>
     35 		/// Pfad für Config/Exclude-Files etc.
     36 		/// </summary>
     37 		private static string basePath = "c:\\";
     38 
     39 		/// <summary>
     40 		/// Alle Konfigurationsdaten
     41 		/// </summary>
     42 		internal Config m_Config;
     43 
     44 		/// <summary>
     45 		/// Die Caches für das Hauptfenster
     46 		/// </summary>
     47 		internal GlobalCache m_Cache;
     48 
     49 		#endregion
     50 
     51 		#region Hilfsfelder für die Suche
     52 
     53 		/// <summary>
     54 		/// Speichert das zuletzt benutzte TreeView
     55 		/// </summary>
     56 		TreeView lastUsedView = null;
     57 		/// <summary>
     58 		/// Liste mit StuffItems, die bereits angezeigt wurden
     59 		/// </summary>
     60 		List<string> itemsAlreadyFound = new List<string>();
     61         /// <summary>
     62         /// Suchstring, nachdem zuletzt gesucht wurde
     63         /// </summary>
     64         string lastSearchString = string.Empty;
     65 
     66 		#endregion
     67 
     68 		#region Windows 7-Spezialitäten
     69 		/// <summary>
     70 		/// Zugriff auf Windows 7-Superbar
     71 		/// </summary>
     72 		ITaskbarList3 tbl = null;
     73 
     74 		private bool IsWin7 = Environment.OSVersion.Version.ToString().StartsWith("6.1");
     75 
     76 		#endregion
     77 
     78 		/// <summary>
     79 		/// Konstruktor für das Formular
     80 		/// </summary>
     81 		public MainForm()
     82 		{
     83 			InitializeComponent();
     84 		}
     85 
     86 		/// <summary>
     87 		/// On Load Config ausführen
     88 		/// </summary>
     89 		/// <param name="sender"></param>
     90 		/// <param name="e"></param>
     91 		private void MainForm_Load(object sender, EventArgs e)
     92 		{
     93 			// Pfad zur Datei ermitteln
     94 			basePath = Application.ExecutablePath.Substring(0, Application.ExecutablePath.LastIndexOf("\\") + 1);
     95 
     96 			// Konfiguration von Platte einlesen
     97 			m_Config = new Config(basePath);
     98 
     99 			// Programm mit niedriger Priorität ausführen
    100 			if (m_Config.IsLowerPriority)
    101 			{
    102 				Process.GetCurrentProcess().PriorityClass = ProcessPriorityClass.BelowNormal;
    103 			}
    104 
    105 			// Fenstertitel setzen
    106 			if (!string.IsNullOrEmpty(m_Config.HeaderExtension))
    107 			{
    108                 Text = String.Format("[{0}] {1}", m_Config.HeaderExtension, DefaultHeader);
    109 			}
    110 			else
    111 			{
    112                 Text = DefaultHeader;
    113 			}
    114 
    115 			// Daten von der Platte einlesen
    116 			m_Cache = new GlobalCache(basePath);
    117 			
    118 			// Menüs erweitern
    119 			AddFremdePlattenMenu(MainMenuFremdePlatten.DropDownItems);
    120 			AddEigenePlattenMenu(MainMenuEigenePlatten.DropDownItems);
    121 
    122 			// wenn eine DefaultOfflineDatei hinterlegt ist
    123 			if (!String.IsNullOrEmpty(m_Config.DefaultOfflineFile))
    124 			{
    125 				if (File.Exists(m_Config.DefaultOfflineFile))
    126 				{
    127 					m_MyDisks.LoadOfflineCacheFromFile(m_Config.DefaultOfflineFile);
    128 				}
    129 				else
    130 				{
    131 					LogError("DefaultOfflineDatei " + m_Config.DefaultOfflineFile + " existiert nicht.");
    132 				}
    133 			}
    134 
    135 			// Standardeinstellungen für den "Meine Platten"-Tree anlegen
    136 			myDisksTree.AfterCheck += new TreeViewEventHandler(TransferCheckToStuffItem);
    137 			myDisksTree.TreeViewNodeSorter = (IComparer)new StuffItemSorter();
    138 
    139 			// Standardeinstellungen für den "Andere Platten"-Tree anlegen
    140 			otherDisksTree.AfterCheck += new TreeViewEventHandler(TransferCheckToStuffItem);
    141 			otherDisksTree.TreeViewNodeSorter = (IComparer)new StuffItemSorter();
    142 
    143 			StartUpdate();
    144 
    145 			// nur Windows 7/Server 2008 R2
    146 			if (IsWin7)
    147 			{
    148 				tbl = (ITaskbarList3)new TaskbarList();
    149 			}
    150 		}
    151 
    152 		/// <summary>
    153 		/// Kopiert eine Liste von Dateien an den angegebenen Zielort
    154 		/// </summary>
    155 		/// <param name="state"></param>
    156 		public void CopyFiles(object state)
    157 		{
    158 			// Thread-Zähler erhöhen
    159 			copyThreadCount++;
    160 
    161 			// Fehlerzähler
    162 			int errorCount = 0;
    163 			// Zähler für die kopierten Dateien
    164 			int copiedFileCount = 0;
    165 			float copiedFileSizeMB = 0;
    166 
    167 			List<StuffItemPair> copylist = (List<StuffItemPair>)state;
    168 
    169 			if (copylist.Count < 1)
    170 				return;
    171 
    172 			// Größe aller Dateien für die Einrichtung des Fortschrittsbalkens ermitteln
    173 			float allFileSizeMB = 0;
    174 			foreach (StuffItemPair pair in copylist)
    175 			{
    176 				try
    177 				{
    178 					FileInfo sourceInfo = new FileInfo(pair.Source.Path);
    179 					allFileSizeMB += sourceInfo.Length / 1024 / 1024;
    180 				}
    181 				catch { }
    182 				{
    183 					// mit 1 vorbelegen wegen division durch 0
    184 					if (allFileSizeMB == 0)
    185 						allFileSizeMB = 1;
    186 				}
    187 			}
    188 
    189 			// Setzen des Maximalwerts des Fortschrittsbalkens
    190 			maxProgressMB += allFileSizeMB;
    191 
    192 			Progress(copyThreadCount + "|0");
    193 
    194 			DateTime startTime = DateTime.Now;
    195 
    196 			// jede Datei kopieren
    197 			foreach (StuffItemPair sip in copylist)
    198 			{
    199 				float sourceSizeMB = 0;
    200 				try
    201 				{
    202 					// Quelle und Ziel bestimmen
    203 					string source = sip.Source.Path;
    204 					string target = Path.Combine(sip.Target.Path, Path.GetFileName(sip.Source.Path));
    205 
    206 					// Dateigröße der Quelldatei bestimmen
    207 					try
    208 					{
    209 						FileInfo sourceInfo = new FileInfo(source);
    210 						sourceSizeMB = (sourceInfo.Length / 1024 / 1024);
    211 					}
    212 					catch (Exception ex)
    213 					{
    214 						sourceSizeMB = 0;
    215 						LogWarning("Fehler: Größe der Datei " + source + " konnte nicht ermittelt werden: " + ex.Message);
    216 					}
    217 
    218 					// Dateianzahl erhöhen
    219 					copiedFileCount++;
    220 
    221 					Status("Kopiere Datei " + copiedFileCount.ToString() + " von " + copylist.Count.ToString() + ": " + source + " -> " + target + " (" + sourceSizeMB.ToString() + " MB)");
    222 					Log("Kopiere Datei " + copiedFileCount.ToString() + " von " + copylist.Count.ToString() + ": " + source + " -> " + target + " (" + sourceSizeMB.ToString() + " MB)");
    223 					try
    224 					{
    225 						Directory.CreateDirectory(sip.Target.Path);
    226 					}
    227 					catch (Exception ee)
    228 					{
    229 						LogError("Fehler beim Erstellen des Verzeichnisses: " + ee.Message);
    230 					}
    231 					File.Copy(source, target);
    232 					// Markierung entfernen
    233 					sip.Source.Checked = false;
    234 				}
    235 				catch (Exception e)
    236 				{
    237 					LogError("Fehler beim Kopieren der Datei " + sip.Source.Path + " : " + e.Message);
    238 					errorCount++;
    239 				}
    240 				finally
    241 				{
    242 					// Fortschritt aktualisieren
    243 					copiedFileSizeMB += sourceSizeMB;
    244 					Progress(copyThreadCount + "|" + sourceSizeMB);
    245 				}
    246 			}
    247 
    248 			// Thread-Zähler verringern
    249 			copyThreadCount--;
    250 
    251 			// Fortschrittsbalkenmaximum verringern
    252 			maxProgressMB -= allFileSizeMB;
    253 			currentProgressMB -= copiedFileSizeMB;
    254 
    255 			// Fortschrittsbalken zurücksetzen
    256 			Progress(copyThreadCount + "|0");
    257 
    258 			DateTime endTime = DateTime.Now;
    259 
    260 			// Anzeige der eigenen Platten aktualisieren
    261 			m_MyDisks.UpdateChildsBackground();
    262 
    263 			Status("Kopieren abgeschlossen! " + errorCount.ToString() + " von " + copylist.Count.ToString() + " Dateien konnten nicht kopiert werden.");
    264 
    265 			// Meldung über Kopiervorgang anzeigen
    266 			TimeSpan copyTime = endTime - startTime;
    267 			LogHighlight("Kopiervorgang abgeschlossen! Dateien kopiert: " + copiedFileCount + " - Gesamtgröße: " + String.Format("{0:F2}", copiedFileSizeMB) + " MB - Dauer: " + String.Format("{0:F0}:{1:D2}:{2:D2}", copyTime.TotalHours, copyTime.Minutes, copyTime.Seconds) + " - Transferrate: " + String.Format("{0:F2}", copiedFileSizeMB / copyTime.TotalSeconds) + " MB/s");
    268 			Message("Kopiervorgang abgeschlossen!" + Environment.NewLine + Environment.NewLine + "Dateien kopiert:\t" + copiedFileCount + Environment.NewLine + "Gesamtgröße:\t" + String.Format("{0:F2}", copiedFileSizeMB) + " MB" + Environment.NewLine + "Dauer:\t\t" + String.Format("{0:F0}:{1:D2}:{2:D2}", copyTime.TotalHours, copyTime.Minutes, copyTime.Seconds) + Environment.NewLine + "Transferrate:\t" + String.Format("{0:F2}", copiedFileSizeMB / copyTime.TotalSeconds) + " MB/s");
    269 		}
    270 
    271 		#region Klicks auf Menuepunkte
    272 
    273 		/// <summary>
    274 		/// KLick auf Exit
    275 		/// </summary>
    276 		/// <param name="sender"></param>
    277 		/// <param name="e"></param>
    278 		private void exitToolStripMenuItem_Click(object sender, EventArgs e)
    279 		{
    280 			Application.Exit();
    281 		}
    282 		
    283 		/// <summary>
    284 		/// Klick auf "Ordner aktualisieren"
    285 		/// </summary>
    286 		/// <param name="sender"></param>
    287 		/// <param name="e"></param>
    288 		private void UpdateFolderClick(object sender, EventArgs e)
    289 		{
    290 			GetStuffItem(sender).UpdateChildsBackground();
    291 		}
    292 
    293 		/// <summary>
    294 		/// Klick auf "Löschen des Logs"
    295 		/// </summary>
    296 		/// <param name="sender"></param>
    297 		/// <param name="e"></param>
    298 		private void protokollausgabeLöschenToolStripMenuItem_Click(object sender, EventArgs e)
    299 		{
    300             logTextBox.Clear();
    301 		}
    302 
    303 		/// <summary>
    304 		/// Konfiguration aufrufen und bei OK gleich scannen
    305 		/// </summary>
    306 		/// <param name="sender"></param>
    307 		/// <param name="e"></param>
    308 		private void konfigurationToolStripMenuItem_Click(object sender, EventArgs e)
    309 		{
    310 			if (m_Config.EditConfig())
    311 			{
    312 				StartUpdate();
    313 			}
    314 		}
    315 
    316 		/// <summary>
    317 		/// Eigene Platten erneut einlesen (z.zt nur default Konfiguration)
    318 		/// </summary>
    319 		/// <param name="sender"></param>
    320 		/// <param name="e"></param>
    321 		private void eigenePlattenAktualisierenToolStripMenuItem_Click(object sender, EventArgs e)
    322 		{
    323 			m_MyDisks.UpdateChildsBackground();
    324 		}
    325 
    326 		/// <summary>
    327 		/// Erneutes einlesen der fremden Platte
    328 		/// </summary>
    329 		/// <param name="sender"></param>
    330 		/// <param name="e"></param>
    331 		private void fremdePlatteAktualisierenToolStripMenuItem_Click(object sender, EventArgs e)
    332 		{
    333 			Program.MainWindow.m_OtherDisks.UpdateChildsBackground();
    334 		}
    335 
    336 		/// <summary>
    337 		/// Löschen des Hash-Caches (Datei löschen ginge auch ;-)
    338 		/// </summary>
    339 		/// <param name="sender"></param>
    340 		/// <param name="e"></param>
    341 		private void hashCacheLöschenToolStripMenuItem_Click(object sender, EventArgs e)
    342 		{
    343 			m_Cache.Clear();
    344 		}
    345 
    346 		#endregion
    347 
    348 		#region Aktualisierung der Bäume (von Hintergrundthread aufrufbar)
    349 
    350 		/// <summary>
    351 		/// Henkel um von einem Background-Prozess die Trees wieder zu aktualisieren
    352 		/// </summary>
    353 		public void UpdateTrees()
    354 		{
    355 			if ( InvokeRequired )
    356 			{
    357                 Invoke( new MethodInvoker(UpdateTrees));
    358 			}
    359 			else
    360 			{
    361 				UpdateMyDisksTreesInvoked(false);
    362 				UpdateOtherDisksTreesInvoked(false);
    363 			}
    364 		}
    365 
    366 		/// <summary>
    367 		/// Henkel um von einem Background-Prozess die Trees wieder zu aktualisieren
    368 		/// Dabei werden unmarkierte und Zweige ohne Fehler zugeklappt
    369 		/// </summary>
    370 		public void CollapseMyDisks()
    371 		{
    372 			if ( InvokeRequired )
    373 			{
    374                 Invoke( new MethodInvoker(CollapseMyDisks));
    375 			}
    376 			else
    377 			{
    378 				UpdateMyDisksTreesInvoked(true);
    379 			}
    380 		}
    381 
    382 		/// <summary>
    383 		/// Henkel um von einem Background-Prozess die Trees wieder zu aktualisieren
    384 		/// Dabei werden unmarkierte und Zweige ohne Fehler zugeklappt
    385 		/// </summary>
    386 		public void CollapseOtherDisks()
    387 		{
    388 			if ( InvokeRequired )
    389 			{
    390                 Invoke( new MethodInvoker(CollapseOtherDisks));
    391 			}
    392 			else
    393 			{
    394 				UpdateOtherDisksTreesInvoked(true);
    395 			}
    396 		}
    397 
    398 		/// <summary>
    399 		/// Nur im Invoke-Zyklus rufbar!
    400 		/// </summary>
    401 		/// <param name="autoCollapse"></param>
    402 		private void UpdateMyDisksTreesInvoked(bool autoCollapse)
    403 		{
    404 			if ( InvokeRequired )
    405 			{
    406 				throw new Exception("Dieser Aufruf ist nur im Invoke-Zylus erlaubt");
    407 			}
    408 			
    409 			myDisksTree.BeginUpdate();
    410 
    411 			if (myDisksTree.Nodes.Count == 0)
    412 			{
    413 				myDisksTree.Nodes.Add(new TreeNode());
    414 			}
    415 
    416 			UpdateTreeRecursive( myDisksTree.Nodes[ 0 ], m_MyDisks, autoCollapse );
    417 			
    418 			myDisksTree.Nodes[0].Expand();
    419 			myDisksTree.Sort();
    420 	
    421 			myDisksTree.EndUpdate();
    422 			Status("Aktualisierung abgeschlossen!");
    423 		}
    424 
    425 		/// <summary>
    426 		/// Nur im Invoke-Zyklus rufbar!
    427 		/// </summary>
    428 		/// <param name="autoCollapse"></param>
    429 		private void UpdateOtherDisksTreesInvoked(bool autoCollapse)
    430 		{
    431 			if (this.InvokeRequired)
    432 			{
    433 				throw new Exception("Dieser Aufruf ist nur im Invoke-Zylus erlaubt");
    434 			}
    435 			//---Rechter Baum
    436 			
    437 			otherDisksTree.BeginUpdate();
    438 			if (otherDisksTree.Nodes.Count == 0)
    439 			{
    440 				otherDisksTree.Nodes.Add(new TreeNode());
    441 			}
    442 
    443 			UpdateTreeRecursive(otherDisksTree.Nodes[0], Program.MainWindow.m_OtherDisks, autoCollapse);
    444 			
    445 			otherDisksTree.Nodes[0].Expand();
    446 			otherDisksTree.Sort();
    447 			otherDisksTree.EndUpdate();
    448 			Status("Aktualisierung abgeschlossen!");
    449 		}
    450 
    451 		/// <summary>
    452 		/// Checkboxstatus bei Änderungen in StuffItem (im Tag) übertragen
    453 		/// </summary>
    454 		/// <param name="sender"></param>
    455 		/// <param name="e"></param>
    456 		void TransferCheckToStuffItem(object sender, TreeViewEventArgs e)
    457 		{
    458 			StuffItem si = (StuffItem)e.Node.Tag;
    459 
    460 			if (si != null)
    461 			{
    462 				si.Checked = e.Node.Checked;
    463 			}
    464 		}
    465 		
    466 		/// <summary>
    467 		/// TreeNode aus StuffItem aktualisieren (Rekursiv)
    468 		/// </summary>
    469 		/// <param name="tn">Node</param>
    470 		/// <param name="si">Item</param>
    471 		/// <param name="autoCollapse">Automatisch zuklappen</param>
    472 		private void UpdateTreeRecursive(TreeNode tn, StuffItem si, Boolean autoCollapse)
    473 		{
    474 			List<string> delKeys = new List<string>();
    475 
    476 			// StuffItem immer in Tag hängen
    477 			tn.Tag = si;
    478 			// Mit AutoCollapse erst mal alles zuklappen und nur bei Bedarf wieder öffnen
    479 			if (autoCollapse)
    480 			{
    481 				tn.Collapse();
    482 			}
    483 			// Sicherstellen, dass Fehler und Markierungen immer sichtbar sind,
    484 			// erlaubte Dubletten nicht immer sichtbar machen
    485 			if ((si.HasClones && (!si.IsIgnored || m_Config.IsShowIgnoredClones)) || si.IsNeeded || si.Checked)
    486 			{
    487 				TreeNode pn = tn.Parent;
    488 				while (pn != null)
    489 				{
    490 					pn.Expand();
    491 					pn = pn.Parent;
    492 				}
    493 			}
    494 
    495 			tn.Text = si.Text;
    496 			tn.ImageIndex = tn.SelectedImageIndex = (int)si.Icon;
    497 			tn.ToolTipText = si.ToolTip;
    498 
    499 			// Checkboxes aus StuffItems setzen
    500 			tn.Checked = si.Checked;
    501 
    502 			// Offline duch Textfarbe anzeigen
    503 			tn.ForeColor = si.IsOnline ? Color.Black : Color.SteelBlue;
    504 
    505 			// Alle ChildItems durchlaufen, ggf. hinzufügen und rekursiv aktualisieren
    506 			foreach (StuffItem csi in si.ChildItems.ToArray())
    507 			{
    508 				// Wenn die Datei gesperrt/vorhanden ist und keine gesperrten/vorhandenen Dateien
    509 				// angezeigt werden sollen, dann gar nicht erst hinzufügen
    510 				if (!csi.IsVisible)
    511 				{
    512 					if (tn.Nodes.ContainsKey(csi.Path))
    513 					{
    514 						delKeys.Add(csi.Path);
    515 					}
    516 					continue;
    517 				}
    518 
    519 				// neuen Node anlegen, wenn noch nicht bekannt
    520 				if (!tn.Nodes.ContainsKey(csi.Path))
    521 				{
    522 					tn.Nodes.Add(csi.Path, csi.Name);
    523 				}
    524 				TreeNode ntn = tn.Nodes[csi.Path];
    525 				UpdateTreeRecursive(ntn, csi, autoCollapse);
    526 			}
    527 
    528 			// Alle TreeNodes löschen, die nicht mehr in den ChildItems sind (Das TreeNode.Tag zeigt auf das StuffItem)
    529 			foreach (TreeNode ntn in tn.Nodes)
    530 			{
    531 				if (!si.ChildItems.Contains((StuffItem)ntn.Tag))
    532 				{
    533 					delKeys.Add(((StuffItem)ntn.Tag).Path);
    534 				}
    535 			}
    536 
    537 			// Zweistufiges rauswerfen, damit die Liste nicht beim Durchlaufen manipuliert wird
    538 			foreach (string k in delKeys)
    539 			{
    540 				tn.Nodes.RemoveByKey(k);
    541 			}
    542 		}
    543 
    544 		#endregion
    545 
    546 		#region Logging/Messagebox/Status/Progress (auch von Hintergrundthreads nutzbar)
    547 
    548 		private delegate void MethodInvokerWithString(string s);
    549 
    550 		/// <summary>
    551 		/// Logausgabe. Diese Methode kann von Hintergrundthreads gerufen werden
    552 		/// </summary>
    553 		/// <param name="logText"></param>
    554 		public void Log(string logText)
    555 		{
    556 			if (this.InvokeRequired)
    557 			{
    558 				this.Invoke(new MethodInvokerWithString(Log), "*BG* " + logText);
    559 			}
    560 			else
    561 			{
    562 				logTextBox.SelectionColor = Color.Black;
    563 				LogMessage(logText);
    564 			}
    565 		}
    566 
    567 		/// <summary>
    568 		/// Logausgabe in Grün als Highlight. Diese Methode kann von Hintergrundthreads gerufen werden
    569 		/// </summary>
    570 		/// <param name="log"></param>
    571 		public void LogHighlight(string log)
    572 		{
    573 			if (this.InvokeRequired)
    574 			{
    575 				this.Invoke(new MethodInvokerWithString(LogHighlight), "*BG* " + log);
    576 			}
    577 			else
    578 			{
    579 				logTextBox.SelectionColor = Color.DarkGreen;
    580 				LogMessage(log);
    581 			}
    582 		}
    583 
    584 		/// <summary>
    585 		/// Logausgabe in Gelb als Warnung. Diese Methode kann von Hintergrundthreads gerufen werden
    586 		/// </summary>
    587 		/// <param name="log"></param>
    588 		public void LogWarning(string log)
    589 		{
    590 			if (this.InvokeRequired)
    591 			{
    592 				this.Invoke(new MethodInvokerWithString(LogWarning), "*BG* " + log);
    593 			}
    594 			else
    595 			{
    596 				logTextBox.SelectionColor = Color.Orange;
    597 				LogMessage(log);
    598 			}
    599 		}
    600 
    601 		/// <summary>
    602 		/// Logausgabe in Rot als Fehler. Diese Methode kann von Hintergrundthreads gerufen werden
    603 		/// </summary>
    604 		/// <param name="log"></param>
    605 		public void LogError(string log)
    606 		{
    607 			if (this.InvokeRequired)
    608 			{
    609 				this.Invoke(new MethodInvokerWithString(LogError), "*BG* " + log);
    610 			}
    611 			else
    612 			{            
    613 				logTextBox.SelectionColor = Color.Red;
    614 				LogMessage(log);
    615 			}
    616 		}
    617 
    618 		private void LogMessage(string message)
    619 		{
    620 			if (String.IsNullOrEmpty(message))
    621 				logTextBox.AppendText("\r\n");
    622 			else
    623 				logTextBox.AppendText("\r\n" + DateTime.Now.ToString() + " | " + message);
    624 
    625 			logTextBox.ScrollToCaret();
    626 			logTextBox.Refresh();
    627 		}
    628 
    629 		/// <summary>
    630 		/// Statustext ausgeben
    631 		/// </summary>
    632 		/// <param name="message">Nachricht, die in der Statuszeile ausgegeben werden soll</param>
    633 		public void Status(string message)
    634 		{
    635 			if (this.InvokeRequired)
    636 			{
    637 				this.Invoke(new MethodInvokerWithString(Status), message);
    638 			}
    639 			else
    640 			{
    641 				stsStatus.Items[0].Text = message;
    642 			}
    643 		}
    644 
    645 		/// <summary>
    646 		/// Messagebox ausgeben
    647 		/// </summary>
    648 		public void Message(string messageText)
    649 		{
    650 			if (this.InvokeRequired)
    651 			{
    652 				this.Invoke(new MethodInvokerWithString(Message), messageText);
    653 			}
    654 			else
    655 			{
    656 				MessageBox.Show(messageText, "Information", MessageBoxButtons.OK, MessageBoxIcon.Information);
    657 			}
    658 		}
    659 
    660 		/// <summary>
    661 		/// Progress anzeigen
    662 		/// </summary>
    663 		/// <param name="value">|</param>
    664 		public void Progress(string value)
    665 		{
    666 			if (this.InvokeRequired)
    667 			{
    668 				this.Invoke(new MethodInvokerWithString(Progress), value);
    669 			}
    670 			else
    671 			{
    672 				try
    673 				{
    674 					if (value is String)
    675 					{
    676 						String[] args = value.Split("|".ToCharArray());
    677 
    678 						if (args[0].Equals("0"))
    679 						{
    680 							statusProgress.Value = 0;
    681 
    682 							// nur Windows 7/Server 2008 R2
    683 							if (IsWin7)
    684 							{
    685 								tbl.SetProgressState(this.Handle, TBPFLAG.TBPF_NOPROGRESS);
    686 							}
    687 
    688 
    689 						}
    690 						else
    691 						{
    692 							// Fortschrittsbalken
    693 							if (maxProgressMB > 0)
    694 							{
    695 								// neuen Fortschritt an Hand der Dateigröße berechnen
    696 								currentProgressMB += Convert.ToInt32(args[1]);
    697 								int progressValue = (int)((currentProgressMB * 100) / maxProgressMB);
    698 								statusProgress.Value = progressValue;
    699 								statusProgress.ToolTipText = "Fortschritt: " + progressValue + " % (" + currentProgressMB +
    700 									" MB von " + maxProgressMB + " MB";
    701 
    702 								// nur Windows 7/Server 2008 R2
    703 								if (IsWin7)
    704 								{
    705 									tbl.SetProgressValue(this.Handle, (ulong)progressValue, 100);
    706 								}
    707 							}
    708 						}
    709 
    710 						// Anzahl Threads
    711 						statusThreads.Text = "T: " + args[0];
    712 					}
    713 				}
    714 				catch
    715 				{
    716 					// ignore
    717 				}
    718 			}
    719 		}
    720 
    721 		#endregion
    722 
    723 		#region Drag & Drop
    724 
    725 		private void otherDisksTree_ItemDrag(object sender, ItemDragEventArgs e)
    726 		{
    727 			TreeNode tn = (TreeNode)e.Item;
    728 			if (tn != null /*&& ((StuffItem)tn.Tag).IsNeeded*/)
    729 			{
    730 				DoDragDrop(e.Item, DragDropEffects.Move);
    731 			}
    732 		}
    733 
    734 		private void myDisksTree_DragEnter(object sender, DragEventArgs e)
    735 		{
    736 			TreeNode trgt = myDisksTree.GetNodeAt(myDisksTree.PointToClient(new Point(e.X, e.Y)));
    737 			if (trgt != null && trgt.ImageIndex == (int)TreeNodeImage.Folder)
    738 			{
    739 				e.Effect = DragDropEffects.Move;
    740 			}
    741 			else
    742 			{
    743 				e.Effect = DragDropEffects.None;
    744 			}
    745 		}
    746 
    747 		private void myDisksTree_DragDrop(object sender, DragEventArgs e)
    748 		{
    749 			// Source und Target aus dem Drag&Drog-Object ermitteln
    750 			TreeNode src = (TreeNode)e.Data.GetData(typeof(TreeNode));
    751 			TreeNode trgt = myDisksTree.GetNodeAt(myDisksTree.PointToClient(new Point(e.X, e.Y)));
    752 			List<StuffItemPair> copylist = new List<StuffItemPair>();
    753 			copylist.Add(new StuffItemPair());
    754 			copylist[0].Source = ((StuffItem)src.Tag);
    755 			copylist[0].Target = ((StuffItem)trgt.Tag);
    756 
    757 			Program.RunBackgroundTask(this, "CopyFiles", copylist);
    758 		}
    759 
    760 		#endregion
    761 
    762 		#region Funktionen für die Arbeit mit MainMenü / PopupMenü
    763 
    764 		/// <summary>
    765 		/// Einhängen eines Menüpunktes mit Event, Bild (opt) und StuffItem
    766 		/// </summary>
    767 		private void AddToMenuCollection(ToolStripItemCollection menuCollection, string name, string image, EventHandler eventHandler, StuffItem stuffItem)
    768 		{
    769 			ToolStripItem ts = menuCollection.Add(name);
    770 			ts.Click += eventHandler;
    771 			ts.Tag = stuffItem;
    772 			if (image != null && fileSystemImageList.Images.ContainsKey(image))
    773 			{
    774 				ts.Image = fileSystemImageList.Images[image];
    775 			}
    776 		}
    777 
    778 		/// <summary>
    779 		/// StuffItem aus ToolStripItem oder MenuItem extrahieren (damit können Events
    780 		/// von beiden Menüs aus verwendet werden)
    781 		/// </summary>
    782 		/// <param name="menuItem"></param>
    783 		/// <returns></returns>
    784 		private static StuffItem GetStuffItem(object menuItem)
    785 		{
    786 			if (menuItem is MenuItem)
    787 			{
    788 				return (StuffItem)((MenuItem)menuItem).Tag;
    789 			}
    790 
    791 			if (menuItem is ToolStripItem)
    792 			{
    793 				return (StuffItem)((ToolStripItem)menuItem).Tag;
    794 			}
    795 
    796 			return null;
    797 		}
    798 
    799 		/// <summary>
    800 		/// Menü für eigene Platten aufbauen (Hauptmenü und Popup-Menü)
    801 		/// </summary>
    802 		/// <param name="menuCollection"></param>
    803 		private void AddEigenePlattenMenu(ToolStripItemCollection menuCollection)
    804 		{
    805 			// Konfiguration öffnen
    806 			AddToMenuCollection(menuCollection, "Platten hinzufügen oder entfernen...", "Hinzufuegen.png", new EventHandler(konfigurationToolStripMenuItem_Click), m_MyDisks);
    807 			// Menüpunkt für Aktualisieren links
    808 			AddToMenuCollection(menuCollection, "Meine Platten aktualisieren", "Aktualisieren.png", new EventHandler(eigenePlattenAktualisierenToolStripMenuItem_Click), m_MyDisks);
    809 			// Online / Offline Wechsel
    810 			AddToMenuCollection( menuCollection, "Meine Platten " + ( m_MyDisks.IsOnline ? "offline" : "online" ) + " schalten", m_MyDisks.IsOnline ? "DateiWeiss.png" : "DateiGruen.png", new EventHandler( ToggleOnline ), m_MyDisks );
    811 			// Separator
    812 			menuCollection.Add("-");
    813 			// Markieren aller Dateien ab Root, die gelöscht werden können
    814 			AddToMenuCollection(menuCollection, "Dubletten zum Löschen markieren (eine bleibt)", "Markieren.png", new EventHandler(ContextMarkDoubleEntries), m_MyDisks);
    815 			// Allgemeines Menü für alle Platten dazu
    816 			AddAllePlattenMenu( menuCollection, m_MyDisks );
    817 			// Löschen aller links makierten Dateien
    818 			AddToMenuCollection(menuCollection, "Alle markierten Dateien sperren und löschen", "DateiExcludedAndDeleted.png", new EventHandler(ContextExcludeAndDeleteAllMarkedFiles), m_MyDisks);
    819 			AddToMenuCollection(menuCollection, "Alle markierten Dateien löschen", "DateiLoeschen.png", new EventHandler(ContextDeleteAllMarkedFiles), m_MyDisks);
    820 		}
    821 
    822 		/// <summary>
    823 		/// Menü für fremde Platten aufbauen (Hauptmenü und Popup-Menü)
    824 		/// </summary>
    825 		/// <param name="menuCollection"></param>
    826 		private void AddFremdePlattenMenu(ToolStripItemCollection menuCollection)
    827 		{      
    828 			// Konfiguration öffnen
    829 			AddToMenuCollection(menuCollection, "Platten hinzufügen oder entfernen...", "Hinzufuegen.png", new EventHandler(konfigurationToolStripMenuItem_Click), Program.MainWindow.m_OtherDisks);
    830 			// AddToMenuCollection(menuCollection, "Fremde Platte hinzufügen...", "Hinzufuegen.png", new EventHandler(FremdePlatteAuswaehlenClick), Program.MainWindow.m_OtherDisks);
    831 			// Menüpunkt für Aktualisieren rechts
    832 			AddToMenuCollection(menuCollection, "Fremde Platten aktualisieren", "Aktualisieren.png", new EventHandler(fremdePlatteAktualisierenToolStripMenuItem_Click), Program.MainWindow.m_OtherDisks);
    833 			// Online / Offline Wechsel
    834 			AddToMenuCollection(menuCollection, "Fremde Platten " + (Program.MainWindow.m_OtherDisks.IsOnline ? "offline" : "online") + " schalten", Program.MainWindow.m_OtherDisks.IsOnline ? "DateiWeiss.png" : "DateiGruen.png", new EventHandler(ToggleOnline), Program.MainWindow.m_OtherDisks);
    835 			// Separator
    836 			menuCollection.Add("-");
    837 			// Markieren aller zu saugenden Dateien ab Root
    838 			AddToMenuCollection(menuCollection, "Alle fehlenden Dateien markieren", "Markieren.png", new EventHandler(ContextMarkMissingFiles), Program.MainWindow.m_OtherDisks);
    839 			// Allgemeines Menü für alle Platten dazu
    840 			AddAllePlattenMenu(menuCollection, Program.MainWindow.m_OtherDisks);
    841 		}
    842 
    843 		/// <summary>
    844 		/// Menü für beide Platten, wird von AddFremde/EigenenPlattenMenu verwendet
    845 		/// </summary>
    846 		/// <param name="menuCollection"></param>
    847 		/// <param name="stuffItem"></param>
    848 		private void AddAllePlattenMenu(ToolStripItemCollection menuCollection, StuffItem stuffItem)
    849 		{
    850 			// Markieren aller Dateien ab Root
    851 			AddToMenuCollection(menuCollection, "Alle Dateien markieren", "Markieren.png", new EventHandler(ContextMark), stuffItem);
    852 			// Markierung mit Suchfilter setzen
    853 			AddToMenuCollection(menuCollection, "Dateien mit Suchfilter markieren...", "Markieren.png", new EventHandler(ContextMarkByFilter), stuffItem);
    854 			// Markierung entfernen aller Dateien ab Root
    855 			AddToMenuCollection(menuCollection, "Alle Markierungen entfernen", "Demarkieren.png", new EventHandler(ContextRemoveMark), stuffItem);
    856 			// Separator
    857 			menuCollection.Add("-");
    858 			// Unbenötigte Teile zuklappen
    859 			AddToMenuCollection(menuCollection, "Alle Ordner ohne Markierungen/Hinweise zuklappen", "Zuklappen.png", new EventHandler(ContextCollapse), stuffItem);
    860 			// Separator
    861 			menuCollection.Add("-");
    862 			// Markieren/Demarkieren
    863 			AddToMenuCollection(menuCollection, "Alle markierten Dateien entsperren", "DateiFrischGesaugt.png", new EventHandler(ContextIncludeMarkers), stuffItem);
    864 			AddToMenuCollection(menuCollection, "Alle markierten Dateien sperren", "DateiExcluded.png", new EventHandler(ContextExcludeMarkers), stuffItem);
    865 		}
    866 
    867 		/// <summary>
    868 		/// Kontextmenu aufbauen
    869 		/// </summary>
    870 		/// <param name="sender"></param>
    871 		/// <param name="e"></param>
    872 		private void tree_MouseUp( object sender, MouseEventArgs e )
    873 		{
    874 			TreeView tv = ( TreeView )sender;
    875 			if ( e.Button != MouseButtons.Right )
    876 			{
    877 				return;
    878 			}
    879 
    880 			Point clickPoint = new Point( e.X, e.Y );
    881 			TreeNode clickNode = tv.GetNodeAt( clickPoint );
    882 			if ( clickNode == null )
    883 			{
    884 				return;
    885 			}
    886 
    887 			StuffItem clickItem = ( StuffItem )clickNode.Tag;
    888 			bool isRoot = clickItem.Equals( m_MyDisks ) || clickItem.Equals( Program.MainWindow.m_OtherDisks );
    889 			// Convert from Tree coordinates to Screen coordinates
    890 			Point ScreenPoint = tv.PointToScreen( clickPoint );
    891 			// Convert from Screen coordinates to Form coordinates
    892 			Point FormPoint = this.PointToClient( ScreenPoint );
    893 
    894 			// Show context menu
    895 			ContextMenuStrip contextmenu = new ContextMenuStrip();
    896 			contextmenu.Items.Clear();
    897 			// Fremde Platte entfernen
    898 
    899 			// Menüs für die 
    900 			if ( isRoot )
    901 			{
    902 				if ( !clickItem.IsOnForeignDisk )
    903 				{
    904 					AddEigenePlattenMenu( contextmenu.Items );
    905 				}
    906 				else
    907 				{
    908 					AddFremdePlattenMenu( contextmenu.Items );
    909 				}
    910 			}
    911 			else
    912 			{
    913 				if ( !clickItem.IsFile )
    914 				{
    915 					AddToMenuCollection( contextmenu.Items, "Ordner " + clickItem.Path + " aktualisieren" + ( clickItem.IsOnline ? "" : " (offline)" ), "Aktualisieren.png", new EventHandler( UpdateFolderClick ), clickItem );
    916 					AddToMenuCollection( contextmenu.Items, "Ordner " + clickItem.Path + ( clickItem.IsOnline ? " offline" : " online" ) + " schalten", clickItem.IsOnline ? "DateiWeiss.png" : "DateiGruen.png", new EventHandler( ToggleOnline ), clickItem );
    917 					contextmenu.Items.Add( "-" );
    918 				}
    919 				// Menüpunkt für Ansehen
    920 				if ( !clickItem.HasReadError )
    921 				{
    922 					AddToMenuCollection( contextmenu.Items, "Öffnen", "Oeffnen.png", new EventHandler( ContextExecute ), clickItem );
    923 					// Öffnen mit Anbieten bei Dateien
    924 					if ( clickItem.IsFile )
    925 					{
    926 						AddToMenuCollection( contextmenu.Items, "Öffnen mit...", "Oeffnen.png", new EventHandler( ContextExecuteWith ), clickItem );
    927 					}
    928 				}
    929 
    930 				// Ordner mit Unterordnern
    931 				if ( !clickItem.IsFile && clickItem.ChildItems.Count > 0 )
    932 				{
    933 					contextmenu.Items.Add( "-" );
    934 					// Markieren aller Dateien ab Folder
    935 					AddToMenuCollection( contextmenu.Items, "Alle Dateien unterhalb " + clickItem.Path + " markieren", "Markieren.png", new EventHandler( ContextMark ), clickItem );
    936 					// Markieren aller Dateien ab Folder mit Filter
    937 					AddToMenuCollection( contextmenu.Items, "Dateien unterhalb " + clickItem.Path + " mit Suchfilter markieren...", "Markieren.png", new EventHandler( ContextMarkByFilter ), clickItem );
    938 					// Markieren aller zu saugenden Dateien ab Folder
    939 					if ( clickItem.IsOnForeignDisk )
    940 					{
    941 						AddToMenuCollection( contextmenu.Items, "Alle fehlenden Dateien unterhalb " + clickItem.Path + " markieren", "Markieren.png", new EventHandler( ContextMarkMissingFiles ), clickItem );
    942 					}
    943 					// Markierung entfernen aller Dateien ab Folder
    944 					AddToMenuCollection( contextmenu.Items, "Alle Markierungen unterhalb " + clickItem.Path + " entfernen", "Demarkieren.png", new EventHandler( ContextRemoveMark ), clickItem );
    945 				}
    946 
    947 				if ( !clickItem.IsOnForeignDisk && !clickItem.IsFile && !clickItem.HasReadError )
    948 				{
    949 					contextmenu.Items.Add( "-" );
    950 					// Kopieren aller rechts markierten Dateien in einen Folder (flat)
    951 					AddToMenuCollection( contextmenu.Items, "Alle fremden selektierten Dateien direkt in diesen Ordner kopieren (flat)", "DateiSaugen.png", new EventHandler( ContextCopyAllClick ), clickItem );
    952 					// Kopieren aller rechts markierten Dateien in einen Folder (hierarchisch)
    953 					AddToMenuCollection( contextmenu.Items, "Alle fremden selektierten Dateien mit Ordnerstruktur in diesen Ordner kopieren", "DateiSaugen.png", new EventHandler( ContextCopyAllWithFoldersClick ), clickItem );
    954 					// Ordner anlegen
    955 					AddToMenuCollection( contextmenu.Items, "Neuen Ordner unterhalb " + clickItem.Path + " anlegen", "Ordner.png", new EventHandler( ContextNewFolder ), clickItem );
    956 
    957 				}
    958 
    959 				if ( clickItem.IsFile && clickItem.HasClones )
    960 				{
    961 					contextmenu.Items.Add( "-" );
    962 
    963 					List<StuffItem> clones = Program.MainWindow.m_MyDisks.GetItemClones( clickItem );
    964 					for ( int i = 0 ; i < clones.Count ; i++ )
    965 					{
    966 						if ( !clones[ i ].Equals( clickItem ) )
    967 						{
    968 							AddToMenuCollection( contextmenu.Items, "Öffnen Dublette " + clones[ i ].Path, "Oeffnen.png", new EventHandler( ContextExecute ), clones[ i ] );
    969 						}
    970 					}
    971 
    972 					AddToMenuCollection( contextmenu.Items, "Markieren aller Dubletten dieser Datei", "Markieren.png", new EventHandler( ContextMarkClones ), clickItem );
    973 					// Dubletten ignorieren ja/nein
    974 					if ( !clickItem.IsIgnored )
    975 					{
    976 						AddToMenuCollection( contextmenu.Items, "Dubletten dieser Datei erlauben", "", new EventHandler( ContextIgnore ), clickItem );
    977 					}
    978 					else
    979 					{
    980 						AddToMenuCollection( contextmenu.Items, "Dubletten dieser Datei wieder markieren", "", new EventHandler( ContextUnIgnore ), clickItem );
    981 					}
    982 
    983 					// Für Dubletten Menüs anbieten (auf rot)
    984 					for ( int i = 0 ; i < clones.Count ; i++ )
    985 					{
    986 						if ( !clones[ i ].Equals( clickItem ) )
    987 						{
    988 							AddToMenuCollection( contextmenu.Items, "Löschen Dublette " + clones[ i ].Path, "DateiLoeschen.png", new EventHandler( ContextDeleteFile ), clones[ i ] );
    989 						}
    990 					}
    991 
    992 				}
    993 				// Datei in die Sperrliste ein-/austragen
    994 				if ( clickItem.IsFile )
    995 				{
    996 					// Linie nach Markieren
    997 					contextmenu.Items.Add( "-" );
    998 					if ( !clickItem.IsExcluded )
    999 					{
   1000 						AddToMenuCollection( contextmenu.Items, "Sperren der Datei", "DateiExcluded.png", new EventHandler( ContextExclude ), clickItem );
   1001 					}
   1002 					else
   1003 					{
   1004 						AddToMenuCollection( contextmenu.Items, "Entsperren der Datei", "DateiFrischGesaugt.png", new EventHandler( ContextInclude ), clickItem );
   1005 					}
   1006 					// Sperren und zugleich löschen
   1007 					if ( !clickItem.IsExcluded && !clickItem.IsOnForeignDisk )
   1008 					{
   1009 						AddToMenuCollection( contextmenu.Items, "Sperren und Löschen der Datei", "DateiExcludedAndDeleted.png", new EventHandler( ContextExcludeAndDelete ), clickItem );
   1010 					}
   1011 					// Löschen einer Einzeldatei, nur links
   1012 					if ( !clickItem.IsOnForeignDisk )
   1013 					{
   1014 						AddToMenuCollection( contextmenu.Items, "Löschen der Datei", "DateiLoeschen.png", new EventHandler( ContextDeleteFile ), clickItem );
   1015 					}
   1016 				}
   1017 				// Löschen eines (leeren) Ordners
   1018 				if ( !clickItem.HasReadError && !clickItem.IsOnForeignDisk && !clickItem.IsFile && clickItem.ChildItems.Count == 0 )
   1019 				{
   1020 					AddToMenuCollection( contextmenu.Items, "Ordner löschen", "OrdnerLoeschen.png", new EventHandler( ContextDeleteFolder ), clickItem );
   1021 				}
   1022 				// Sperren/Entsperren multi
   1023 				if ( !clickItem.IsFile && clickItem.ChildItems.Count > 0 )
   1024 				{
   1025 					// Linie nach Markieren
   1026 					contextmenu.Items.Add( "-" );
   1027 					AddToMenuCollection( contextmenu.Items, "Alle markierten Dateien entsperren", "DateiFrischGesaugt.png", new EventHandler( ContextIncludeMarkers ), clickItem );
   1028 					AddToMenuCollection( contextmenu.Items, "Alle markierten Dateien sperren", "DateiExcluded.png", new EventHandler( ContextExcludeMarkers ), clickItem );
   1029 					AddToMenuCollection( contextmenu.Items, "Alle markierten Dateien sperren und löschen", "DateiExcludedAndDeleted.png", new EventHandler( ContextExcludeAndDeleteAllMarkedFiles ), clickItem );
   1030 					// Löschen aller links markierten Dateien
   1031 					if ( !clickItem.IsOnForeignDisk )
   1032 					{
   1033 						AddToMenuCollection( contextmenu.Items, "Alle markierten Dateien unterhalb " + clickItem.Path + " löschen", "DateiLoeschen.png", new EventHandler( ContextDeleteAllMarkedFiles ), clickItem );
   1034 					}
   1035 				}
   1036 			}
   1037 			contextmenu.Show( this, FormPoint );
   1038 		}
   1039 
   1040 		#endregion
   1041 
   1042 		#region Popup Menü Aktionen
   1043 
   1044 		/// <summary>
   1045 		/// Teilbaum von Offline auf Online und umgekehrt setzen
   1046 		/// </summary>
   1047 		/// <param name="sender"></param>
   1048 		/// <param name="e"></param>
   1049 		void ToggleOnline(object sender, EventArgs e)
   1050 		{
   1051 			// StuffItem ermitteln
   1052 			StuffItem si = GetStuffItem(sender);
   1053 			if (si == null)
   1054 			{
   1055 				return;
   1056 			}
   1057 			
   1058 			// Onlinestatus umsetzen
   1059 			if (si.IsOnline)
   1060 			{
   1061 				Log("Setze Offline: " + si.Path);
   1062 				si.IsOnline = false;
   1063 			}
   1064 			else
   1065 			{
   1066 				Log("Setze Online: " + si.Path);
   1067 				si.IsOnline = true;
   1068 			}
   1069 
   1070 			// Menüs bauen sich dabei um!
   1071 			MainMenuFremdePlatten.DropDownItems.Clear();
   1072 			AddFremdePlattenMenu(MainMenuFremdePlatten.DropDownItems);
   1073 			MainMenuEigenePlatten.DropDownItems.Clear();
   1074 			AddEigenePlattenMenu(MainMenuEigenePlatten.DropDownItems);
   1075 			
   1076 			UpdateTrees();
   1077 		}
   1078 
   1079 		/// <summary>
   1080 		/// Eine Datei excluden
   1081 		/// </summary>
   1082 		/// <param name="sender"></param>
   1083 		/// <param name="e"></param>
   1084 		void ContextExclude(object sender, EventArgs e)
   1085 		{
   1086 			try
   1087 			{
   1088 				StuffItem si = GetStuffItem(sender);
   1089 				// wenn kein StuffItem ausgewählt wurde, dann tun wir hier nix
   1090 				if (si == null)
   1091 				{
   1092 					return;
   1093 				}
   1094 
   1095 				// Datei sperren
   1096 				si.Exclude();
   1097 				Log("Sperre Datei: " + si.Path);
   1098 
   1099 				UpdateTreeNodeIcon(si);
   1100 			}
   1101 			catch (Exception e2)
   1102 			{
   1103 				LogError("Fehler beim Sperren: " + e2.Message);
   1104 			}
   1105 		}
   1106 
   1107 		/// <summary>
   1108 		/// Eine Datei excluden und löschen
   1109 		/// </summary>
   1110 		/// <param name="sender"></param>
   1111 		/// <param name="e"></param>
   1112 		void ContextExcludeAndDelete( object sender, EventArgs e )
   1113 		{
   1114 			try
   1115 			{
   1116 				StuffItem si = GetStuffItem( sender );
   1117 				if ( si == null )
   1118 				{
   1119 					return;
   1120 				}
   1121 				DialogResult res = MessageBox.Show( "Datei " + si.Name + " wirklich sperren und löschen?", 
   1122 					"Vorsicht, sie trennen sich von Material!", MessageBoxButtons.OKCancel, MessageBoxIcon.Exclamation );
   1123 				if ( res == DialogResult.OK )
   1124 				{
   1125 					Log( "Sperre und lösche Datei: " + si.Path );
   1126 					si.Exclude();
   1127 					File.Delete( si.Path );
   1128 
   1129 					RemoveStuffItemFromMyDisksTree( si );
   1130 				}
   1131 			}
   1132 			catch ( Exception e2 )
   1133 			{
   1134 				LogError( "Fehler beim Sperren und Löschen: " + e2.Message );
   1135 			}
   1136 		}
   1137 
   1138 		/// <summary>
   1139 		/// Eine Datei includen
   1140 		/// </summary>
   1141 		/// <param name="sender"></param>
   1142 		/// <param name="e"></param>
   1143 		void ContextInclude(object sender, EventArgs e)
   1144 		{
   1145 			try
   1146 			{
   1147 				StuffItem si = GetStuffItem(sender);
   1148 				if (si == null)
   1149 				{
   1150 					return;
   1151 				}
   1152 
   1153 				Log("Entsperre Datei: " + si.Path);
   1154 				si.Include();
   1155 
   1156 				UpdateTreeNodeIcon(si);
   1157 			}
   1158 			catch (Exception ex)
   1159 			{
   1160 				LogError("Fehler beim Entsperren: " + ex.Message);
   1161 			}
   1162 		}
   1163 
   1164 
   1165 		/// <summary>
   1166 		/// Eine Datei ignorieren
   1167 		/// </summary>
   1168 		/// <param name="sender"></param>
   1169 		/// <param name="e"></param>
   1170 		void ContextIgnore(object sender, EventArgs e)
   1171 		{
   1172 			try
   1173 			{
   1174 				StuffItem si = GetStuffItem(sender);
   1175 				if (si == null)
   1176 				{
   1177 					return;
   1178 				}
   1179 
   1180 				Log("Erlaube Dubletten der Datei: " + si.Path);
   1181 				si.Ignore();
   1182 
   1183 				// Icon der Datei und möglicher Clones anpassen
   1184 				UpdateTreeNodeIcon(si);
   1185 			}
   1186 			catch (Exception ex)
   1187 			{
   1188 				LogError("Fehler beim Erlauben: " + ex.Message);
   1189 			}
   1190 		}
   1191 
   1192 		/// <summary>
   1193 		/// Eine Datei un-ignorieren
   1194 		/// </summary>
   1195 		/// <param name="sender"></param>
   1196 		/// <param name="e"></param>
   1197 		void ContextUnIgnore(object sender, EventArgs e)
   1198 		{
   1199 			try
   1200 			{
   1201 				StuffItem si = GetStuffItem(sender);
   1202 				if (si == null)
   1203 				{
   1204 					return;
   1205 				}
   1206 
   1207 				Log("Markiere Dubletten der Datei: " + si.Path);
   1208 				si.UnIgnore();
   1209 
   1210 				// Icon der Datei und möglicher Clones anpassen
   1211 				UpdateTreeNodeIcon( si );
   1212 			}
   1213 			catch (Exception ex)
   1214 			{
   1215 				LogError("Fehler beim Markieren: " + ex.Message);
   1216 			}
   1217 		}
   1218 
   1219 		/// <summary>
   1220 		/// Eine Datei ausführen durch Menüeintrag 
   1221 		/// </summary>
   1222 		/// <param name="sender"></param>
   1223 		/// <param name="e"></param>
   1224 		void ContextExecute(object sender, EventArgs e)
   1225 		{
   1226 			try
   1227 			{
   1228 				StuffItem si = GetStuffItem(sender);
   1229 				if ( si == null )
   1230 				{
   1231 					return;
   1232 				}
   1233 				if ( si.IsFile )
   1234 				{
   1235 					Status( "Starte Datei: " + si.Path );
   1236 				}
   1237 				else
   1238 				{
   1239 					Status( "Öffne Verzeichnis: " + si.Path );
   1240 				}
   1241 
   1242 				try
   1243 				{
   1244 					Process.Start(si.Path);
   1245 				}
   1246 				catch
   1247 				{
   1248 					ContextExecuteWith(sender, e);
   1249 				}
   1250 			}
   1251 			catch (Exception e2)
   1252 			{
   1253 				LogError("Fehler beim Ausführen: " + e2.Message);
   1254 			}
   1255 		}
   1256 
   1257 		/// <summary>
   1258 		/// Eine Datei ausführen mit Auswahl durch Programm durch Menüeintrag 
   1259 		/// </summary>
   1260 		/// <param name="sender"></param>
   1261 		/// <param name="e"></param>
   1262 		void ContextExecuteWith(object sender, EventArgs e)
   1263 		{
   1264 			try
   1265 			{
   1266 				StuffItem si = GetStuffItem(sender);
   1267 				if ( si == null )
   1268 				{
   1269 					return;
   1270 				}
   1271 				if ( si.IsFile )
   1272 				{
   1273 					Status( "Starte Datei: " + si.Path );
   1274 				}
   1275 				else
   1276 				{
   1277 					Status( "Öffne Verzeichnis: " + si.Path );
   1278 				}
   1279 
   1280 				using (Process p = new Process())
   1281 				{
   1282 					ProcessStartInfo pi = new ProcessStartInfo("rundll32.exe");
   1283 					pi.UseShellExecute = false;
   1284 					pi.RedirectStandardOutput = true;
   1285 					pi.Arguments = "shell32.dll,OpenAs_RunDLL " + si.Path;
   1286 					p.StartInfo = pi;
   1287 					p.Start();
   1288 				}
   1289 			}
   1290 			catch (Exception e2)
   1291 			{
   1292 				LogError("Fehler beim Ausführen: " + e2.Message);
   1293 			}
   1294 		}
   1295 
   1296 		/// <summary>
   1297 		/// Eine Datei löschen
   1298 		/// </summary>
   1299 		/// <param name="sender"></param>
   1300 		/// <param name="e"></param>
   1301 		void ContextDeleteFile(object sender, EventArgs e)
   1302 		{
   1303 			try
   1304 			{
   1305 				StuffItem si = GetStuffItem(sender);
   1306 				if (si == null)
   1307 				{
   1308 					return;
   1309 				}
   1310 
   1311 				if (MessageBox.Show("Datei " + si.Name + " wirklich löschen?", "Vorsicht, sie trennen sich von Material!", 
   1312 					MessageBoxButtons.OKCancel, MessageBoxIcon.Exclamation) == DialogResult.OK)
   1313 				{
   1314 					// Datei löschen
   1315 					Log("Lösche Datei: " + si.Path);
   1316 					File.Delete(si.Path);
   1317 
   1318 					RemoveStuffItemFromMyDisksTree( si );
   1319 				}
   1320 			}
   1321 			catch (Exception e2)
   1322 			{
   1323 				LogError("Fehler beim Löschen: " + e2.Message);
   1324 			}
   1325 		}
   1326 
   1327 		/// <summary>
   1328 		/// Eine leeren Ordner löschen
   1329 		/// </summary>
   1330 		/// <param name="sender"></param>
   1331 		/// <param name="e"></param>
   1332 		void ContextDeleteFolder(object sender, EventArgs e)
   1333 		{
   1334 			try
   1335 			{
   1336 				StuffItem si = GetStuffItem(sender);
   1337 				if ( si == null )
   1338 				{
   1339 					return;
   1340 				}
   1341 
   1342 				DialogResult res = System.Windows.Forms.MessageBox.Show("Ordner " + si.Name + " wirklich löschen?", "Leeren Ordner löschen", MessageBoxButtons.OKCancel, MessageBoxIcon.Exclamation);
   1343 				if (res == DialogResult.OK)
   1344 				{
   1345 					Log("Lösche Ordner: " + si.Path);
   1346 					Directory.Delete(si.Path);
   1347 
   1348 					RemoveStuffItemFromMyDisksTree( si );
   1349 				}
   1350 			}
   1351 			catch (Exception e2)
   1352 			{
   1353 				LogError("Fehler beim Löschen: " + e2.Message);
   1354 			}
   1355 		}
   1356 		/// <summary>
   1357 		/// Alle markierten unterhalb eines Ordners löschen
   1358 		/// </summary>
   1359 		/// <param name="sender"></param>
   1360 		/// <param name="e"></param>
   1361 		void ContextDeleteAllMarkedFiles(object sender, EventArgs e)
   1362 		{
   1363 			StuffItem si = GetStuffItem(sender);
   1364 			if (si == null || si.IsFile || si.IsOnForeignDisk)
   1365 			{
   1366 				return;
   1367 			}
   1368 
   1369 			string meld = "";
   1370 			if (si.Parent == null)
   1371 			{
   1372 				meld = "Alle markierten Dateien wirklich löschen?";
   1373 			}
   1374 			else
   1375 			{
   1376 				meld = "Alle markierten Dateien unterhalb " + si.Path + " wirklich löschen?";
   1377 			}
   1378 
   1379 			DialogResult res = MessageBox.Show(meld, "Vorsicht, sie trennen sich von Material!", MessageBoxButtons.OKCancel, MessageBoxIcon.Warning);
   1380 			if (res == DialogResult.OK)
   1381 			{
   1382 				DeleteAllMarkedFilesRecursive(si);
   1383 			}
   1384 		}
   1385 
   1386 		/// <summary>
   1387 		/// Alle markierten unterhalb eines Ordners sperren und löschen
   1388 		/// </summary>
   1389 		/// <param name="sender"></param>
   1390 		/// <param name="e"></param>
   1391 		void ContextExcludeAndDeleteAllMarkedFiles(object sender, EventArgs e)
   1392 		{
   1393 			StuffItem si = GetStuffItem(sender);
   1394 			if (si == null || si.IsFile ||si.IsOnForeignDisk)
   1395 			{
   1396 				return;
   1397 			}
   1398 
   1399 			string meld = "";
   1400 			if (si.Parent == null)
   1401 			{
   1402 				meld = "Alle markierten Dateien wirklich sperren und löschen?";
   1403 			}
   1404 			else
   1405 			{
   1406 				meld = "Alle markierten Dateien unterhalb " + si.Path + " wirklich sperren und löschen?";
   1407 			}
   1408 
   1409 			if (MessageBox.Show(meld, "Vorsicht, sie trennen sich von Material!", 
   1410 				MessageBoxButtons.OKCancel, MessageBoxIcon.Warning) == DialogResult.OK)
   1411 			{
   1412 				Recursive_ExcludeMarkers(si, true);
   1413 				DeleteAllMarkedFilesRecursive(si);
   1414 			}
   1415 		}
   1416 
   1417 		/// <summary>
   1418 		/// Hilfsfunktion um alle markierten Dateien rekrusiv zu löschen
   1419 		/// </summary>
   1420 		/// <param name="si"></param>
   1421 		private void DeleteAllMarkedFilesRecursive(StuffItem si)
   1422 		{
   1423 			try
   1424 			{
   1425 				if ( si.IsFile )
   1426 				{
   1427 					if ( !si.Checked )
   1428 					{
   1429 						return;
   1430 					}
   1431 					// Datei löschen
   1432 					Log( "Datei wird gelöscht: " + si.Path );
   1433 					File.Delete( si.Path );
   1434 					
   1435 					// aus der StuffItem-Liste löschen
   1436 					RemoveStuffItemFromMyDisksTree( si );
   1437 				}
   1438 				else
   1439 				{
   1440 					// Rückwärts durchlaufen wegen Löschen
   1441 					for ( int j = si.ChildItems.Count - 1 ; j >= 0 ; j-- )
   1442 					{
   1443 						StuffItem csi = si.ChildItems[ j ];
   1444 						DeleteAllMarkedFilesRecursive( csi );
   1445 					}
   1446 
   1447 					// Leeres Verzeichnis löschen
   1448 					if ( si.ChildItems.Count == 0 && si.Checked )
   1449 					{
   1450 						Log( "Leeres Verzeichnis wird gelöscht: " + si.Path );
   1451 						Directory.Delete( si.Path );
   1452 
   1453 						RemoveStuffItemFromMyDisksTree( si );
   1454 					}
   1455 				}
   1456 			}
   1457 			catch (Exception e2)
   1458 			{
   1459 				LogError("Fehler beim Löschen: " + e2.Message);
   1460 			}
   1461 		}
   1462 
   1463 		private void RemoveStuffItemFromMyDisksTree( StuffItem si )
   1464 		{
   1465 			// Verweis vom Elternteil entfernen
   1466 			si.Parent.ChildItems.Remove( si );
   1467 
   1468 			// Datei aus dem Tree entfernen
   1469 			myDisksTree.FindSingleNode(si.Path).Remove();
   1470 
   1471 			// Einträge auch aus der MyDisksByHash-Liste entfernen
   1472 			m_MyDisks.RemoveStuffItemFromHashes( si );
   1473 
   1474 			// Anzeige aktualisieren
   1475 			UpdateTreeNodeIcon( si );
   1476 		}
   1477 
   1478 		/// <summary>
   1479 		/// Alle markierten unterhalb eines Ordners sperren
   1480 		/// </summary>
   1481 		/// <param name="sender"></param>
   1482 		/// <param name="e"></param>
   1483 		void ContextExcludeMarkers(object sender, EventArgs e)
   1484 		{
   1485 			StuffItem si = GetStuffItem(sender);
   1486 			if (si == null || si.IsFile)
   1487 			{
   1488 				return;
   1489 			}
   1490 			Recursive_ExcludeMarkers(si, true);
   1491 
   1492 			// Markierungen entfernen, falls gewünscht
   1493 			if (m_Config.IsRemoveMarks)
   1494 			{
   1495 				// Markierungen im aktuellen Tree entfernen
   1496 				ContextRemoveMark(sender, e);
   1497 			}
   1498 		}
   1499 
   1500 		/// <summary>
   1501 		/// Alle markierten unterhalb eines Ordners entsperren
   1502 		/// </summary>
   1503 		/// <param name="sender"></param>
   1504 		/// <param name="e"></param>
   1505 		void ContextIncludeMarkers(object sender, EventArgs e)
   1506 		{
   1507 			StuffItem si = GetStuffItem(sender);
   1508 			if (si == null || si.IsFile)
   1509 			{
   1510 				return;
   1511 			}
   1512 			Recursive_ExcludeMarkers(si, false);
   1513 
   1514 			// Markierungen entfernen, falls gewünscht
   1515 			if (m_Config.IsRemoveMarks)
   1516 			{
   1517 				// Markierungen im aktuellen Tree entfernen
   1518 				ContextRemoveMark(sender, e);
   1519 			}
   1520 		}
   1521 
   1522 		/// <summary>
   1523 		/// Hilfsfunktion um alle markierten Dateien rekursiv zu sperren
   1524 		/// </summary>
   1525 		/// <param name="si"></param>
   1526 		/// <param name="exclude">true für Dateien sperren, false wenn die Dateien entsperren</param>
   1527 		private void Recursive_ExcludeMarkers( StuffItem si, bool exclude )
   1528 		{
   1529 			try
   1530 			{
   1531 				if ( si.IsFile )
   1532 				{
   1533 					if ( si.Checked )
   1534 					{
   1535 						if ( exclude )
   1536 						{
   1537 							Log( "Datei gesperrt: " + si.Path );
   1538 							si.Exclude();
   1539 						}
   1540 						else
   1541 						{
   1542 							Log( "Datei entsperrt: " + si.Path );
   1543 							si.Include();
   1544 						}
   1545 
   1546 						// TreeNodeIcon aktualisieren
   1547 						UpdateTreeNodeIcon( si );
   1548 					}
   1549 				}
   1550 				else
   1551 				{
   1552 					foreach ( StuffItem csi in si.ChildItems )
   1553 					{
   1554 						Recursive_ExcludeMarkers( csi, exclude );
   1555 					}
   1556 				}
   1557 			}
   1558 			catch ( Exception e2 )
   1559 			{
   1560 				LogError( "Fehler beim Sperren/Entsperren: " + e2.Message );
   1561 			}
   1562 		}
   1563 
   1564 		/// <summary>
   1565 		/// Alle markierten Items in Kopierliste zusammenfassen und im Hintergrund kopieren
   1566 		/// </summary>
   1567 		/// <param name="sender"></param>
   1568 		/// <param name="e"></param>
   1569 		void ContextCopyAllClick(object sender, EventArgs e)
   1570 		{
   1571 			StuffItem trgt = GetStuffItem(sender);
   1572 			if (trgt == null || trgt.IsFile)
   1573 				return;
   1574 			List<StuffItemPair> copylist = new List<StuffItemPair>();
   1575 			ContextCopyAllClickAddChecked(copylist, otherDisksTree.Nodes[0]);
   1576 
   1577 			// Markierungen entfernen, falls gewünscht
   1578 			// beim Kopieren schon jetzt, denn entfällt das manuelle Demarkieren
   1579 			if (m_Config.IsRemoveMarks)
   1580 			{
   1581 				// Markierung immer im Tree "Fremde Platten" entfernen
   1582 				ContextRemoveMark(otherDisksTree.Nodes[0].Tag, e);
   1583 			}
   1584 
   1585 			foreach (StuffItemPair pair in copylist)
   1586 			{
   1587 				pair.Target = trgt;
   1588 			}
   1589 			// Kopieraktion starten
   1590 			Program.RunBackgroundTask(this, "CopyFiles", copylist);
   1591 		}
   1592 
   1593 		/// <summary>
   1594 		/// Alle markierten Items in Kopierliste zusammenfassen und mit Pfad kopieren
   1595 		/// </summary>
   1596 		/// <param name="sender"></param>
   1597 		/// <param name="e"></param>
   1598 		void ContextCopyAllWithFoldersClick(object sender, EventArgs e)
   1599 		{
   1600 			StuffItem trgt = GetStuffItem(sender);
   1601 			if (trgt == null || trgt.IsFile)
   1602 				return;
   1603 
   1604 			List<StuffItemPair> copylist = new List<StuffItemPair>();
   1605 			ContextCopyAllClickAddChecked(copylist, otherDisksTree.Nodes[0]);
   1606 			foreach (StuffItemPair pair in copylist)
   1607 			{
   1608 				FileInfo fi = new FileInfo(pair.Source.Path);
   1609 				string subpath = fi.DirectoryName.Replace(":", "").Replace("\\\\", "");
   1610 				pair.Target = new StuffItem();
   1611 				pair.Target.Path = trgt.Path + "\\" + subpath;
   1612 			}
   1613 
   1614 			// Markierungen entfernen, falls gewünscht
   1615 			// beim Kopieren schon jetzt, denn entfällt das manuelle Demarkieren
   1616 			// beim Kopieren immer im Tree "Fremde Platten"
   1617 			if (m_Config.IsRemoveMarks)
   1618 			{
   1619 				ContextRemoveMark(otherDisksTree.Nodes[0].Tag, e);
   1620 			}
   1621 
   1622 			// Dateien kopieren
   1623 			Program.RunBackgroundTask(this, "CopyFiles", copylist);
   1624 		}
   1625 
   1626 		/// <summary>
   1627 		/// Hilfsfunktion zum rekursiven Kopieren (ohne Erhalt der Hierarchie)
   1628 		/// </summary>
   1629 		/// <param name="copylist"></param>
   1630 		/// <param name="node"></param>
   1631 		void ContextCopyAllClickAddChecked(List<StuffItemPair> copylist, TreeNode node)
   1632 		{
   1633 			if (node.Checked)
   1634 			{
   1635 				StuffItem item = (StuffItem)node.Tag;
   1636 				if (item != null && item.IsFile)
   1637 				{
   1638 					copylist.Add(new StuffItemPair());
   1639 					copylist[copylist.Count - 1].Source = item;
   1640 				}
   1641 			}
   1642 			foreach (TreeNode n in node.Nodes)
   1643 			{
   1644 				ContextCopyAllClickAddChecked(copylist, n);
   1645 			}
   1646 		}
   1647 
   1648 		/// <summary>
   1649 		/// Aktualisiert das Icon eines bestimmten StuffItems in den TreeViews
   1650 		/// </summary>
   1651 		/// <param name="si">StuffItem, welches es zu aktualisieren gilt</param>
   1652 		void UpdateTreeNodeIcon(StuffItem si)
   1653 		{
   1654 			TreeView currentTree = si.IsOnForeignDisk ? otherDisksTree : myDisksTree;
   1655 			TreeNode tn = currentTree.FindSingleNode( si.Path );
   1656 			if ( tn != null )
   1657 			{
   1658 				tn.ImageIndex = tn.SelectedImageIndex = ( int )si.Icon;
   1659 			}
   1660 
   1661 			// lokale Dateien eines entfernten StuffItems ebenfalls anpassen
   1662 			if ( si.IsOnForeignDisk )
   1663 			{
   1664 				foreach ( String localpath in si.GetLocalFiles )
   1665 				{
   1666 					tn = myDisksTree.FindSingleNode( localpath );
   1667 					tn.ImageIndex = tn.SelectedImageIndex = ( int )( ( StuffItem )tn.Tag ).Icon;
   1668 				}
   1669 			}
   1670 			else
   1671 			{
   1672 				// ggf. den anderen Tree auch anpassen (wg. Exludes)
   1673 				foreach ( String remotepath in si.GetRemoteFiles )
   1674 				{
   1675 					tn = otherDisksTree.FindSingleNode( remotepath );
   1676 					tn.ImageIndex = tn.SelectedImageIndex = ( int )( ( StuffItem )tn.Tag ).Icon;
   1677 				}
   1678 				// TODO: bei Excludes und aktiver Option zum Ausblenden von Items, dann muss das Ding ganz raus
   1679 			}
   1680 		}
   1681 
   1682 		#endregion
   1683 
   1684 		#region Markierungen setzen und entfernen (Kontextmenu)
   1685 
   1686 		/// <summary>
   1687 		/// Alle Zweige ohne Markierung zuklappen
   1688 		/// </summary>
   1689 		/// <param name="sender"></param>
   1690 		/// <param name="e"></param>
   1691 		private void ContextCollapse(object sender, EventArgs e)
   1692 		{
   1693 			StuffItem si = GetStuffItem(sender);
   1694 			if (si == null)
   1695 			{
   1696 				return;
   1697 			}
   1698 			if (si.IsOnForeignDisk)
   1699 			{
   1700 				CollapseOtherDisks();
   1701 			}
   1702 			else
   1703 			{
   1704 				CollapseMyDisks();
   1705 			}
   1706 		}
   1707 
   1708 		/// <summary>
   1709 		/// Alle markieren, die zu saugen sind
   1710 		/// </summary>
   1711 		/// <param name="sender"></param>
   1712 		/// <param name="e"></param>
   1713 		private void ContextMarkMissingFiles(object sender, EventArgs e)
   1714 		{
   1715 			StuffItem si = GetStuffItem(sender);
   1716 			if (si == null)
   1717 			{
   1718 				return;
   1719 			}
   1720 
   1721 			Recursive_UpdateMark( si, true );
   1722 			Recursive_MarkMissingFiles(si);
   1723 		}
   1724 
   1725 		/// <summary>
   1726 		/// Alle Dateien die ein Clone einer anderen sind markieren
   1727 		/// </summary>
   1728 		private void ContextMarkClones(object sender, EventArgs e)
   1729 		{
   1730 			StuffItem si = GetStuffItem(sender);
   1731 			if (si == null)
   1732 			{
   1733 				return;
   1734 			}
   1735 
   1736 			si.Checked = false;
   1737 			foreach ( StuffItem clone in Program.MainWindow.m_MyDisks.GetItemClones( si ) )
   1738 			{
   1739 				TreeNode node = myDisksTree.FindSingleNode( clone.Path );
   1740 				node.Checked = true;
   1741 				node.Expand();
   1742 			}
   1743 		}
   1744 
   1745 		/// <summary>
   1746 		/// Alle Dateien unterhalb eines Knotens markieren
   1747 		/// </summary>
   1748 		/// <param name="sender"></param>
   1749 		/// <param name="e"></param>
   1750 		private void ContextMark(object sender, EventArgs e)
   1751 		{
   1752 			StuffItem si = GetStuffItem(sender);
   1753 			if (si == null)
   1754 			{
   1755 				return;
   1756 			}
   1757 
   1758 			Recursive_UpdateMark( si, true );
   1759 		}
   1760 
   1761 		/// <summary>
   1762 		/// Alle Markierungen entfernen
   1763 		/// </summary>
   1764 		/// <param name="sender"></param>
   1765 		/// <param name="e"></param>
   1766 		private void ContextRemoveMark(object sender, EventArgs e)
   1767 		{
   1768 			StuffItem si = GetStuffItem(sender);
   1769 			if (si == null)
   1770 			{
   1771 				return;
   1772 			}
   1773 
   1774 			Recursive_UpdateMark( si, false );
   1775 		}
   1776 
   1777 		/// <summary>
   1778 		/// Alle markieren, die zu gelöscht werden können, weil doppelt
   1779 		/// Das klappt nur auf dem Root-Node, dehalb nur da anbieten
   1780 		/// </summary>
   1781 		/// <param name="sender"></param>
   1782 		/// <param name="e"></param>
   1783 		private void ContextMarkDoubleEntries(object sender, EventArgs e)
   1784 		{
   1785 			StuffItem se = GetStuffItem(sender);
   1786 			if (se == null)
   1787 			{
   1788 				return;
   1789 			}
   1790 			Recursive_UpdateMark( se, false );
   1791 			Recursive_MarkDoubleEntries(se);
   1792 		}
   1793 
   1794 
   1795 		/// <summary>
   1796 		/// Alle Dateien unterhalb eines Knotens mit einem manuellen Filterstring markieren
   1797 		/// </summary>
   1798 		/// <param name="sender"></param>
   1799 		/// <param name="e"></param>
   1800 		private void ContextMarkByFilter(object sender, EventArgs e)
   1801 		{
   1802 			// Ohne Startitem gehts nicht
   1803 			StuffItem si = GetStuffItem(sender);
   1804 			if (si == null)
   1805 			{
   1806 				return;
   1807 			}
   1808 
   1809 			// Dialog aufbauen / abfragen
   1810 			using (LanTool.FilterDialog filterDlg = new LanTool.FilterDialog())
   1811 			{
   1812 				filterDlg.Text = "Filtern unterhalb \"" + si.Path + "\"";
   1813 
   1814 				if (filterDlg.ShowDialog() == DialogResult.OK)
   1815 				{
   1816 					string filter = filterDlg.Expression;
   1817 					// RegExp-Sonderzeichen per backslash quoten
   1818 					string[] quote = new string[] { "\\", ".", "(", ")", "[", "]" };
   1819 					foreach (string q in quote)
   1820 						filter = filter.Replace(q, "\\" + q);
   1821 
   1822 					// Dos Wildcards durch RegExp ersetzen
   1823 					filter = filter.Replace("*", ".*").Replace("?", ".");
   1824 
   1825 					// Losfiltern ab Item
   1826 					Recursive_MarkByFilter(si, filter);
   1827 				}
   1828 			}
   1829 		}
   1830 
   1831 		/// <summary>
   1832 		/// Rekursion zum Markieren mit einem Filterstring
   1833 		/// </summary>
   1834 		/// <param name="si">aktuellles StuffItem</param>
   1835 		/// <param name="filter">String-Filter</param>
   1836 		private void Recursive_MarkByFilter(StuffItem si, string filter)
   1837 		{
   1838 			TreeView currenttree = si.IsOnForeignDisk ? otherDisksTree : myDisksTree;
   1839 
   1840 			foreach (StuffItem csi in si.ChildItems)
   1841 			{
   1842 				// wenn der Filter zutrifft, dann markieren
   1843 				if (Regex.IsMatch(csi.Name, filter, RegexOptions.IgnoreCase))
   1844 				{
   1845 					currenttree.FindSingleNode(csi.Path).Checked = true;
   1846 				}
   1847 				// wenn Ordner, dann weiter rekursiv durch
   1848 				if (!csi.IsFile)
   1849 				{
   1850 					Recursive_MarkByFilter(csi, filter);
   1851 				}
   1852 			}
   1853 		}
   1854 
   1855 		/// <summary>
   1856 		/// Rekursion zum Markieren fürs Saugen
   1857 		/// </summary>
   1858 		private void Recursive_MarkMissingFiles(StuffItem si)
   1859 		{
   1860 			TreeView currenttree = si.IsOnForeignDisk ? otherDisksTree : myDisksTree;
   1861 	
   1862 			// alle TreeNodes finden
   1863 			foreach ( StuffItem csi in si.ChildItems )
   1864 			{
   1865 				TreeNode node = currenttree.FindSingleNode( csi.Path );
   1866 				node.Checked = csi.IsNeeded;
   1867 				if ( csi.IsNeeded )
   1868 				{
   1869 					node.Expand();
   1870 				}
   1871 
   1872 				if ( !csi.IsFile )
   1873 				{
   1874 					Recursive_MarkMissingFiles( csi );
   1875 				}
   1876 			}
   1877 		}
   1878 
   1879 		/// <summary>
   1880 		/// Rekursiv alle untergeordneten Nodes de-/markieren
   1881 		/// </summary>
   1882 		private void Recursive_UpdateMark( StuffItem si, Boolean setMark )
   1883 		{
   1884 			TreeView currenttree = si.IsOnForeignDisk ? otherDisksTree : myDisksTree;
   1885 
   1886 			// alle TreeNodes finden
   1887 			TreeNode node = currenttree.FindSingleNode( si.Path );
   1888 			node.Checked = setMark;
   1889 			if ( setMark )
   1890 			{
   1891 				node.Expand();
   1892 			}
   1893 
   1894 			foreach ( StuffItem csi in si.ChildItems )
   1895 			{
   1896 				Recursive_UpdateMark( csi, setMark );
   1897 			}
   1898 		}
   1899 
   1900 		/// <summary>
   1901 		/// Die nachfolgenden Duplikate markieren, d.h. das erste bleibt erhalten
   1902 		/// Das kann nur klappen wenn auf RootNode begonnen wird und vorher alle Markierungen
   1903 		/// zurückgesetzt werden!
   1904 		/// </summary>
   1905 		/// <param name="si"></param>
   1906 		private void Recursive_MarkDoubleEntries(StuffItem si)
   1907 		{
   1908 			// nur bei den eigenen Platten
   1909 			if (!si.IsOnForeignDisk)
   1910 			{
   1911 				// nur bei Dateien
   1912 				if (si.IsFile)
   1913 				{
   1914 					if (!si.Checked && si.HasClones)
   1915 					{
   1916 						foreach ( StuffItem csi in Program.MainWindow.m_MyDisks.GetItemClones( si ) )
   1917 						{
   1918 							TreeNode node = myDisksTree.FindSingleNode( csi.Path );
   1919 
   1920 							node.Checked = true;
   1921 							node.Expand();
   1922 
   1923 						}
   1924 					}
   1925 				}
   1926 				else
   1927 				{
   1928 					foreach (StuffItem csi in si.ChildItems)
   1929 					{
   1930 						Recursive_MarkDoubleEntries(csi);
   1931 					}
   1932 				}
   1933 			}
   1934 		}
   1935 
   1936 		#endregion
   1937 
   1938 		#region Suchen
   1939 
   1940 		/// <summary>
   1941 		/// Suche
   1942 		/// </summary>
   1943 		private bool ContextSearchRecursive(StuffItem si, string filter)
   1944 		{
   1945 			foreach (StuffItem se2 in si.ChildItems)
   1946 			{
   1947 				if (Regex.IsMatch(se2.Path, filter, RegexOptions.IgnoreCase) && !itemsAlreadyFound.Contains(se2.Path))
   1948 				{
   1949 					// Item zur Liste "bereits gefunden" hinzufügen
   1950 					itemsAlreadyFound.Add(se2.Path);
   1951 
   1952 					// gesuchtes Item gefunden, dann 
   1953 					TreeNode fn = FindNode(lastUsedView.Nodes[0].Nodes, se2.Path);
   1954 					if (fn == null)
   1955 					{
   1956 						return false;
   1957 					}
   1958 					else
   1959 					{
   1960 						lastUsedView.SelectedNode = fn;
   1961 						return true;
   1962 					}
   1963 				}
   1964 				else
   1965 				{
   1966 					// keine Datei, dann die childs untersuchen
   1967 					if (!se2.IsFile)
   1968 						if (ContextSearchRecursive(se2, filter))
   1969 							return true;
   1970 				}
   1971 			}
   1972 
   1973 			return false;
   1974 		}
   1975 		private TreeNode FindNode(TreeNodeCollection tncoll, String strText)
   1976 		{
   1977 			TreeNode tnFound = null;
   1978 			foreach (TreeNode tnCurr in tncoll)
   1979 			{
   1980 				if (String.Equals(tnCurr.Name, strText, StringComparison.CurrentCultureIgnoreCase))
   1981 				{
   1982 					return tnCurr;
   1983 				}
   1984 				if (tnCurr.Nodes.Count > 0)
   1985 					tnFound = FindNode(tnCurr.Nodes, strText);
   1986 				if (tnFound != null)
   1987 				{
   1988 					return tnFound;
   1989 				}
   1990 			}
   1991 			return null;
   1992 		}
   1993 
   1994 		/// <summary>
   1995 		/// Im Suchfeld wurde eine Taste betätigt
   1996 		/// </summary>
   1997 		/// <param name="sender"></param>
   1998 		/// <param name="e"></param>
   1999 		private void searchField_KeyUp(object sender, KeyEventArgs e)
   2000 		{
   2001 			if (e.KeyCode == Keys.Return)
   2002 			{
   2003 				// wenn es um eine neue Suche handelt, dann die bereits gefunden Liste zurücksetzen
   2004 				if (!String.Equals(lastSearchString, searchField.Text, StringComparison.CurrentCultureIgnoreCase))
   2005 				{
   2006 					itemsAlreadyFound.Clear();
   2007 
   2008 					lastSearchString = searchField.Text;
   2009 				}
   2010 
   2011 				String filter = searchField.Text;
   2012 
   2013 				// wenn kein Suchtext angegeben wurde, dann wird auch nichts gesucht
   2014 				if (!String.IsNullOrEmpty(filter))
   2015 				{
   2016 
   2017 					// RegExp-Sonderzeichen per backslash quoten
   2018 					string[] quote = new string[] { "\\", ".", "(", ")", "[", "]" };
   2019 					foreach (string q in quote)
   2020 						filter = filter.Replace(q, "\\" + q);
   2021 
   2022 					// Dos Wildcards durch RegExp ersetzen
   2023 					filter = filter.Replace("*", ".*").Replace("?", ".");
   2024 
   2025 					StuffItem si = (StuffItem)lastUsedView.Nodes[0].Tag;
   2026 
   2027 					// Suche beim aktuellen Item starten
   2028 					if (!ContextSearchRecursive(si, filter))
   2029 					{
   2030 						Status("Keine (weiteren) Treffer.");
   2031 						// Bereits gefunden Liste zurücksetzen
   2032 						itemsAlreadyFound.Clear();
   2033 					}
   2034 
   2035 					// damit das ganze auch sichtbar wird, den TreeView fokusieren
   2036 					lastUsedView.Focus();
   2037 				}
   2038 				else
   2039 				{
   2040 					searchField.Focus();
   2041 				}
   2042 			}
   2043 		}
   2044 
   2045 		/// <summary>
   2046 		/// Suchen-Button im Menü
   2047 		/// </summary>
   2048 		/// <param name="sender"></param>
   2049 		/// <param name="e"></param>
   2050 		private void suchenToolStripMenuItem_Click(object sender, EventArgs e)
   2051 		{
   2052 			// gleiche Funktion wie Enter im Suchfeld
   2053 			searchField_KeyUp(sender, new KeyEventArgs(Keys.Return));
   2054 		}
   2055 
   2056 		#endregion
   2057 
   2058 		/// <summary>
   2059 		/// Neuen Ordner anlegen
   2060 		/// </summary>
   2061 		/// <param name="sender"></param>
   2062 		/// <param name="e"></param>
   2063 		private void ContextNewFolder(object sender, EventArgs e)
   2064 		{
   2065 			// Ohne Startitem gehts nicht
   2066 			StuffItem si = GetStuffItem(sender);
   2067 			if (si == null)
   2068 			{
   2069 				return;
   2070 			}
   2071 
   2072 			// Dialog aufbauen / abfragen
   2073 			using (NewFolderDialog folderDlg = new NewFolderDialog())
   2074 			{
   2075 				folderDlg.Text = "Neuen Ordner anlegen unterhalb \"" + si.Path + "\":";
   2076 				if (folderDlg.ShowDialog() == DialogResult.OK)
   2077 				{
   2078 					string folder = folderDlg.NewFolderName;
   2079 					// Sonderzeichen durch _ ersetzen
   2080 					foreach ( char q in Path.GetInvalidPathChars() )
   2081 					{
   2082 						folder = folder.Replace( q, '_' );
   2083 					}
   2084 					// Losfiltern ab Item
   2085 					Directory.CreateDirectory(Path.Combine(si.Path, folder));
   2086 
   2087 					si.AddOrGetChildItemByPath( Path.Combine( si.Path, folder ) );
   2088 					
   2089 					UpdateTreeRecursive( myDisksTree.Nodes[si.Path], si, false);
   2090 				}
   2091 			}
   2092 		}
   2093 
   2094 		#region Speichern und laden einer Fileliste als Ersatz für dem MyDisk-Filesystem Scan
   2095 	  
   2096 		/// <summary>
   2097 		/// Verzeichnisse exportieren (werden durch realen Scan aufgebaut)
   2098 		/// </summary>
   2099 		/// <param name="sender"></param>
   2100 		/// <param name="e"></param>
   2101 		private void verzeichnisFürMeinePlattenExportierenToolStripMenuItem_Click(object sender, EventArgs e)
   2102 		{
   2103 			using (SaveFileDialog fd = new SaveFileDialog())
   2104 			{
   2105 				fd.Filter = "LanTool-Verzeichnis-Export (*.files)|*.files|Textdateien (*.txt)|*.txt";
   2106 				fd.DefaultExt = "*.files";
   2107 				fd.FileName = "MeinePlatten.files";
   2108 				fd.InitialDirectory = MainForm.basePath;
   2109 				fd.CheckFileExists = false;
   2110 				fd.CheckPathExists = true;
   2111 				fd.AutoUpgradeEnabled = true;
   2112 				if (fd.ShowDialog(this).Equals(DialogResult.OK))
   2113 				{
   2114                     Log(String.Format("Offline-Datei {0} wurde gespeichert.", fd.FileName));
   2115 					m_MyDisks.SaveOfflineCacheToFile(fd.FileName);
   2116 				}
   2117 			}
   2118 		}
   2119 
   2120 		/// <summary>
   2121 		/// Einlesen einer Cache-Datei für den Offline-Betrieb
   2122 		/// </summary>
   2123 		/// <param name="sender"></param>
   2124 		/// <param name="e"></param>
   2125 		private void verzeichnisFürMeinePlattenImportierenToolStripMenuItem_Click(object sender, EventArgs e)
   2126 		{
   2127 			using (OpenFileDialog fd = new OpenFileDialog())
   2128 			{
   2129 				fd.Filter = "LanTool-Verzeichnis-Export (*.files)|*.files|Textdateien (*.txt)|*.txt";
   2130 				fd.DefaultExt = "*.files";
   2131 				fd.FileName = "MeinePlatten.files";
   2132 				fd.InitialDirectory = MainForm.basePath;
   2133 				fd.CheckFileExists = true;
   2134 				fd.CheckPathExists = true;
   2135 				fd.Multiselect = false;
   2136 				fd.AutoUpgradeEnabled = true;
   2137 				if (fd.ShowDialog(this) == DialogResult.OK)
   2138 				{
   2139 					m_MyDisks.LoadOfflineCacheFromFile(fd.FileName);
   2140 					m_MyDisks.IsOnline = false;
   2141 
   2142                     Log(String.Format("Offline-Datei {0} wurde eingelesen.", fd.FileName));
   2143 
   2144 					UpdateTrees();
   2145 				}
   2146 			}
   2147 		}
   2148 
   2149 		private void offlineCacheLöschenToolStripMenuItem_Click(object sender, EventArgs e)
   2150 		{
   2151 			m_MyDisks.ResetOfflineCaches();
   2152 			m_MyDisks.IsOnline = true;
   2153 			
   2154 			UpdateTrees();
   2155 		}
   2156 
   2157 		#endregion
   2158 
   2159 		/// <summary>
   2160 		/// Speichern welcher TreeView zuletzt aufgerufen wurde
   2161 		/// </summary>
   2162 		/// <param name="sender"></param>
   2163 		/// <param name="e"></param>
   2164 		private void DisksTree_Enter(object sender, EventArgs e)
   2165 		{
   2166 			lastUsedView = (TreeView)sender;
   2167 		}
   2168 
   2169 		/// <summary>
   2170 		/// Ausführen einer Datei durch Doppelklick
   2171 		/// </summary>
   2172 		private void tree_MouseDoubleClick(object sender, MouseEventArgs e)
   2173 		{
   2174 			try
   2175 			{
   2176 				// nur bei Linksklick reagieren
   2177 				if (e.Button.Equals(MouseButtons.Left))
   2178 				{
   2179 					Point clickPoint = new Point(e.X, e.Y);
   2180 
   2181 					// Objekt unter der Maus holen
   2182 					TreeNode clickNode = ((TreeView)sender).GetNodeAt(clickPoint);
   2183 					// ins leere geklickt -> nichts tun
   2184 					if ( clickNode == null || clickNode.Tag == null )
   2185 					{
   2186 						return;
   2187 					}
   2188 
   2189 					StuffItem tn = (StuffItem)clickNode.Tag;
   2190 					// kein StuffItem dran oder keine Datei
   2191 					if ( tn == null || !tn.IsFile )
   2192 					{
   2193 						return;
   2194 					}
   2195 					
   2196 					// Datei ausführen
   2197 					Status("Starte Datei: " + tn.Path);
   2198 					Process.Start(tn.Path);
   2199 				}
   2200 			}
   2201 			catch (Exception e2)
   2202 			{
   2203 				LogError("Fehler beim Ausführen: " + e2.Message);
   2204 			}
   2205 		}
   2206 
   2207 		/// <summary>
   2208 		/// Hotkeys behandeln
   2209 		/// </summary>
   2210 		/// <param name="sender"></param>
   2211 		/// <param name="e"></param>
   2212 		private void MainForm_KeyUp(object sender, KeyEventArgs e)
   2213 		{
   2214 			// ins Suchfeld springen bei Strg+F
   2215 			if (e.KeyCode == Keys.F && Control.ModifierKeys == Keys.Control)
   2216 			{
   2217 				searchField.Focus();
   2218 			}
   2219 			// gleiche Funktion wie Enter im Suchfeld
   2220 			else if (e.KeyCode == Keys.F3)
   2221 			{ 
   2222 				searchField_KeyUp(sender, new KeyEventArgs(Keys.Return));
   2223 			}
   2224 		}
   2225 		
   2226 		private void MainForm_FormClosing(object sender, FormClosingEventArgs e)
   2227 		{
   2228 			// Caches auf die Platte schreiben
   2229 			m_Cache.Dispose();
   2230 		}
   2231 
   2232 		/// <summary>
   2233 		/// Nach Auswahl eines Elementes den Pfad zum Element in der Textbox anzeigen
   2234 		/// </summary>
   2235 		private void myDisksTree_AfterSelect(object sender, TreeViewEventArgs e)
   2236 		{
   2237 			StuffItem selectedStuffItem = (StuffItem)myDisksTree.SelectedNode.Tag;
   2238 			if (selectedStuffItem.IsFile)
   2239 			{
   2240 				myDisksPath.Text = Path.GetDirectoryName(selectedStuffItem.Path);
   2241 			}
   2242 			else
   2243 			{
   2244 				myDisksPath.Text = selectedStuffItem.Path;
   2245 			}
   2246 		}
   2247 
   2248 		/// <summary>
   2249 		/// Nach Auswahl eines Elementes den Pfad zum Element in der Textbox anzeigen
   2250 		/// </summary>
   2251 		private void otherDisksTree_AfterSelect(object sender, TreeViewEventArgs e)
   2252 		{
   2253 			StuffItem selectedStuffItem = (StuffItem)otherDisksTree.SelectedNode.Tag;
   2254 			if (selectedStuffItem.IsFile)
   2255 			{
   2256 				otherDisksPath.Text = Path.GetDirectoryName(selectedStuffItem.Path);
   2257 			}
   2258 			else
   2259 			{ 
   2260 				otherDisksPath.Text = selectedStuffItem.Path;
   2261 			}
   2262 		}
   2263 
   2264 		#region Pfad-Felder über den TreeViews
   2265 
   2266 		private void DisksPath_OpenPath(object sender, MouseEventArgs e)
   2267 		{
   2268 			String path = ((TextBox)sender).Text;
   2269 
   2270 			// Links -> Pfad im Explorer öffnen
   2271 			if (e.Button == MouseButtons.Left)
   2272 			{
   2273 				try
   2274 				{
   2275 					// Nach oben suchen bis ein Text entsteht, der als Verzeichnis existiert
   2276 					while ( !Directory.Exists( path ) && path.Length > 0 )
   2277 					{
   2278 						path = path.Substring( 0, path.LastIndexOf( @"\" ) );
   2279 					}
   2280 
   2281 					// im Explorer öffnen
   2282 					if ( !Directory.Exists( path ) )
   2283 					{
   2284 						return;
   2285 					}
   2286 
   2287 					Status( "Öffne Verzeichnis: " + path );
   2288 					Process.Start( path );
   2289 				}
   2290 				catch { }
   2291 			}
   2292 			// Rechtsklick -> in die Zwischenablage
   2293 			if (e.Button == MouseButtons.Right)
   2294 			{
   2295 				Clipboard.SetDataObject(path, true);
   2296 			}
   2297 		}
   2298 
   2299 		private void DisksPath_KeyUp(object sender, KeyEventArgs e)
   2300 		{
   2301 			if (e.KeyCode == Keys.Enter)
   2302 				DisksPath_OpenPath(sender, new MouseEventArgs(MouseButtons.Left, 2, 0, 0, 0));
   2303 		}
   2304 
   2305 		#endregion
   2306 
   2307 		#region Info-Menü
   2308 
   2309 		private void lanToolForumToolStripMenuItem_Click(object sender, EventArgs e)
   2310 		{
   2311 			Process.Start("http://www.rw-net.de/bbl/index.php?page=Board&boardID=21");
   2312 		}
   2313 
   2314 		private void lanToolTippsTricksToolStripMenuItem_Click(object sender, EventArgs e)
   2315 		{
   2316 			Process.Start("http://www.rw-net.de/LanTool_Tipps.txt");
   2317 		}
   2318 
   2319 		private void lanToolChangeLogToolStripMenuItem_Click(object sender, EventArgs e)
   2320 		{
   2321 			Process.Start("http://www.rw-net.de/LanTool_ChangeLog.txt");
   2322 		}
   2323 
   2324 		private void überToolStripMenuItem_Click(object sender, EventArgs e)
   2325 		{
   2326 			new AboutBox().ShowDialog();
   2327 		}
   2328 
   2329 		private void fTPMitDemLanToolNetDriveToolStripMenuItem_Click(object sender, EventArgs e)
   2330 		{
   2331 			try
   2332 			{
   2333 				if (File.Exists("NetDrive-Setup.exe"))
   2334 					Process.Start("NetDrive-Setup.exe");
   2335 				else
   2336 					Process.Start("http://www.netdrive.net");
   2337 			}
   2338 			catch
   2339 			{ }
   2340 		}
   2341 
   2342 		#endregion
   2343 
   2344 		/// <summary>
   2345 		/// Aktualisierung der beiden TreeViews starten
   2346 		/// </summary>
   2347 		private void StartUpdate()
   2348 		{
   2349 			// Auto Scan anwerfen
   2350 			if (m_Config.IsAutoScanMyDisks)
   2351 			{
   2352 				m_MyDisks.UpdateChildsBackground();
   2353 			}
   2354 			if (m_Config.IsAutoScanOtherDisks)
   2355 			{
   2356 				Program.MainWindow.m_OtherDisks.UpdateChildsBackground();
   2357 			}
   2358 
   2359 			Program.MainWindow.UpdateTrees();
   2360 		}
   2361 	}
   2362 }