Feature/ copy fnc (#1)

Copy Funktion integriert

---------

Co-authored-by: Black_Betty <Black_Betty@DESKTOP-GA9LVM3>
Reviewed-on: #1
This commit was merged in pull request #1.
This commit is contained in:
Black_BettyandBlack_Betty committed 2026-10-06 23:02:38 +02:00
1 parent 976b651e4b
commit 841ba9ed7a
6 files changed
+884 -143

No files matched your search

+185
View File
@@ -0,0 +1,185 @@
using System;
using System.IO;
using System.Windows.Controls;
using System.Windows;
using System.Collections.Generic;
using System.Linq;
namespace TapeCatalog.Methods
{
// Reusable helper methods for copy-related UI functionality
public static class Copy
{
// guard to prevent recursive Checked/Unchecked handling while we programmatically change states
private static bool _suppressCheckEvents = false;
public static void PopulateTree(TreeView tree, string path)
{
if (tree == null) return;
tree.Items.Clear();
if (string.IsNullOrWhiteSpace(path) || !Directory.Exists(path)) return;
var rootDir = new DirectoryInfo(path);
var rootItem = CreateDirectoryNode(rootDir);
rootItem.IsExpanded = true;
tree.Items.Add(rootItem);
}
public static TreeViewItem CreateDirectoryNode(DirectoryInfo dir)
{
var item = new TreeViewItem { Tag = dir.FullName };
// Create header with a checkbox for the directory as well
var headerPanel = new StackPanel { Orientation = Orientation.Horizontal };
var dirCb = new CheckBox { VerticalAlignment = VerticalAlignment.Center, Margin = new Thickness(0, 0, 4, 0) };
var dirTb = new TextBlock { Text = dir.Name, VerticalAlignment = VerticalAlignment.Center };
headerPanel.Children.Add(dirCb);
headerPanel.Children.Add(dirTb);
item.Header = headerPanel;
try
{
// add subdirectories
foreach (var sub in dir.GetDirectories())
{
item.Items.Add(CreateDirectoryNode(sub));
}
// add files
foreach (var f in dir.GetFiles())
{
var fileItem = new TreeViewItem { Tag = f.FullName };
var sp = new StackPanel { Orientation = Orientation.Horizontal };
var cb = new CheckBox { VerticalAlignment = VerticalAlignment.Center, Margin = new Thickness(0, 0, 4, 0) };
var tb = new TextBlock { Text = f.Name, VerticalAlignment = VerticalAlignment.Center };
sp.Children.Add(cb);
sp.Children.Add(tb);
fileItem.Header = sp;
item.Items.Add(fileItem);
}
}
catch
{
// ignore access exceptions
}
// Wire events to propagate state to children when directory checkbox is toggled
dirCb.Checked += (s, e) =>
{
if (_suppressCheckEvents) return;
try { _suppressCheckEvents = true; SetCheckedState(item, true); }
finally { _suppressCheckEvents = false; }
};
dirCb.Unchecked += (s, e) =>
{
if (_suppressCheckEvents) return;
try { _suppressCheckEvents = true; SetCheckedState(item, false); }
finally { _suppressCheckEvents = false; }
};
return item;
}
private static void SetCheckedState(TreeViewItem node, bool isChecked)
{
foreach (var it in node.Items)
{
if (it is TreeViewItem child)
{
// child header expected to be a StackPanel with a CheckBox as first child
if (child.Header is StackPanel sp && sp.Children.Count > 0)
{
foreach (var ch in sp.Children)
{
if (ch is CheckBox childCb)
{
childCb.IsChecked = isChecked;
break;
}
}
}
// recurse into grandchildren
if (child.Items != null && child.Items.Count > 0)
{
SetCheckedState(child, isChecked);
}
}
}
}
// Collect checked files and copy them to archive preserving relative folders
public static Tuple<int,int> CopySelectedToArchive(TreeView tree, string sourceRoot, string archivePath)
{
if (tree == null) return Tuple.Create(0, 0);
var files = new List<string>();
foreach (var it in tree.Items)
{
if (it is TreeViewItem tvi)
CollectCheckedFiles(tvi, files);
}
int copied = 0, failed = 0;
foreach (var src in files)
{
try
{
var fileDir = System.IO.Path.GetDirectoryName(src) ?? string.Empty;
string relative;
if (!string.IsNullOrWhiteSpace(sourceRoot) && fileDir.StartsWith(sourceRoot, StringComparison.OrdinalIgnoreCase))
relative = fileDir.Substring(sourceRoot.Length).TrimStart(System.IO.Path.DirectorySeparatorChar, System.IO.Path.AltDirectorySeparatorChar);
else
relative = System.IO.Path.GetFileName(fileDir);
var destDir = System.IO.Path.Combine(archivePath, relative);
Directory.CreateDirectory(destDir);
var destFile = System.IO.Path.Combine(destDir, System.IO.Path.GetFileName(src));
File.Copy(src, destFile, true);
copied++;
}
catch
{
failed++;
}
}
return Tuple.Create(copied, failed);
}
// Return a list of all checked file paths in the tree
public static List<string> GetCheckedFiles(TreeView tree)
{
var files = new List<string>();
if (tree == null) return files;
foreach (var it in tree.Items)
{
if (it is TreeViewItem tvi)
CollectCheckedFiles(tvi, files);
}
return files;
}
private static void CollectCheckedFiles(TreeViewItem node, List<string> list)
{
if (node.Header is StackPanel sp)
{
foreach (var child in sp.Children)
{
if (child is CheckBox cb && cb.IsChecked == true)
{
if (node.Tag is string path && File.Exists(path))
{
list.Add(path);
}
break;
}
}
}
foreach (var it in node.Items)
{
if (it is TreeViewItem t)
CollectCheckedFiles(t, list);
}
}
}
}
+206
View File
@@ -0,0 +1,206 @@
using System;
using System.IO;
using System.Xml.Serialization;
using System.Linq;
using System.Collections.ObjectModel;
namespace TapeCatalog.Methods
{
/// <summary>
/// Simple settings container and load/save helpers.
/// </summary>
[Serializable]
public class SettingsData
{
public string SourceDirectory { get; set; }
public string ArchiveDirectory { get; set; }
public string XmlDirectory { get; set; }
public static SettingsData Default => new SettingsData
{
SourceDirectory = string.Empty,
ArchiveDirectory = string.Empty,
XmlDirectory = string.Empty
};
}
public static class SettingsManager
{
private const string DefaultFileName = "appsettings.xml";
public static void Save(SettingsData data, string filePath = null)
{
if (data == null) throw new ArgumentNullException(nameof(data));
var path = string.IsNullOrWhiteSpace(filePath) ? DefaultFileName : filePath;
try
{
using (var fs = File.Create(path))
{
var ser = new XmlSerializer(typeof(SettingsData));
ser.Serialize(fs, data);
}
}
catch
{
// swallow errors; callers can handle existence/permission issues
}
}
public static SettingsData Load(string filePath = null)
{
var path = string.IsNullOrWhiteSpace(filePath) ? DefaultFileName : filePath;
if (!File.Exists(path)) return SettingsData.Default;
try
{
using (var fs = File.OpenRead(path))
{
var ser = new XmlSerializer(typeof(SettingsData));
return ser.Deserialize(fs) as SettingsData ?? SettingsData.Default;
}
}
catch
{
return SettingsData.Default;
}
}
}
// UI-related helpers used by the Settings tab. These methods avoid direct UI manipulation
// and return data so the MainWindow can update controls.
public static class SettingsUi
{
public static string SelectDirectory(string description)
{
using (var dlg = new System.Windows.Forms.FolderBrowserDialog())
{
dlg.Description = description ?? "Select a folder";
dlg.ShowNewFolderButton = true;
var result = dlg.ShowDialog();
if (result == System.Windows.Forms.DialogResult.OK && !string.IsNullOrWhiteSpace(dlg.SelectedPath))
return dlg.SelectedPath;
}
return null;
}
public static int ScanSource(string path, ObservableCollection<TapeCatalog.FileEntry> files)
{
if (string.IsNullOrWhiteSpace(path)) throw new ArgumentException("path");
if (!Directory.Exists(path)) throw new DirectoryNotFoundException(path);
// normalize source root for reliable relative path calculation
var rootFull = System.IO.Path.GetFullPath(path).TrimEnd(System.IO.Path.DirectorySeparatorChar, System.IO.Path.AltDirectorySeparatorChar);
foreach (var file in Directory.EnumerateFiles(path, "*.*", SearchOption.AllDirectories))
{
try
{
var fi = new FileInfo(file);
// compute directory relative to source root (remove the source root prefix)
var dirFull = System.IO.Path.GetFullPath(fi.DirectoryName).TrimEnd(System.IO.Path.DirectorySeparatorChar, System.IO.Path.AltDirectorySeparatorChar);
string relativeDir = string.Empty;
if (dirFull.StartsWith(rootFull, StringComparison.OrdinalIgnoreCase))
relativeDir = dirFull.Length == rootFull.Length ? string.Empty : dirFull.Substring(rootFull.Length).TrimStart(System.IO.Path.DirectorySeparatorChar, System.IO.Path.AltDirectorySeparatorChar);
else
relativeDir = fi.DirectoryName;
var existing = files.FirstOrDefault(f => string.Equals(f.Filename, fi.Name, StringComparison.OrdinalIgnoreCase)
&& string.Equals(f.FileDir, relativeDir, StringComparison.OrdinalIgnoreCase));
if (existing != null)
{
var newSizeMb = FormatBytesToMb(fi.Length);
if (existing.SizeSrc != newSizeMb)
{
existing.SizeSrc = newSizeMb;
existing.ModSrc = fi.LastWriteTime.ToString();
existing.Status = string.Empty;
existing.ArcDrive = string.Empty;
}
}
else
{
files.Add(new TapeCatalog.FileEntry
{
Filename = fi.Name,
FileDir = relativeDir,
SizeSrc = FormatBytesToMb(fi.Length),
ModSrc = fi.LastWriteTime.ToString(),
});
}
}
catch
{
// ignore inaccessible files
}
}
return files.Count;
}
public static int ScanArchive(string path, ObservableCollection<TapeCatalog.FileEntry> files)
{
if (string.IsNullOrWhiteSpace(path)) throw new ArgumentException("path");
if (!Directory.Exists(path)) throw new DirectoryNotFoundException(path);
// normalize archive root for reliable relative path calculation
var rootFull = System.IO.Path.GetFullPath(path).TrimEnd(System.IO.Path.DirectorySeparatorChar, System.IO.Path.AltDirectorySeparatorChar);
foreach (var file in Directory.EnumerateFiles(path, "*.*", SearchOption.AllDirectories))
{
try
{
var fi = new FileInfo(file);
var dirFull = System.IO.Path.GetFullPath(fi.DirectoryName).TrimEnd(System.IO.Path.DirectorySeparatorChar, System.IO.Path.AltDirectorySeparatorChar);
string relativeDir = string.Empty;
if (dirFull.StartsWith(rootFull, StringComparison.OrdinalIgnoreCase))
relativeDir = dirFull.Length == rootFull.Length ? string.Empty : dirFull.Substring(rootFull.Length).TrimStart(System.IO.Path.DirectorySeparatorChar, System.IO.Path.AltDirectorySeparatorChar);
else
relativeDir = fi.DirectoryName;
var existing = files.FirstOrDefault(f => string.Equals(f.Filename, fi.Name, StringComparison.Ordinal)
&& string.Equals(f.FileDir, relativeDir, StringComparison.OrdinalIgnoreCase));
if (existing != null)
{
existing.SizeArc = FormatBytesToMb(fi.Length);
DateTime prevMod;
if (!DateTime.TryParse(existing.ModArc, out prevMod)) prevMod = DateTime.MinValue;
var mod = fi.LastWriteTime > prevMod ? fi.LastWriteTime : prevMod;
existing.ModArc = mod.ToString();
}
else
{
files.Add(new TapeCatalog.FileEntry
{
Filename = fi.Name,
FileDir = relativeDir,
SizeArc = FormatBytesToMb(fi.Length),
ModArc = fi.LastWriteTime.ToString()
});
}
}
catch
{
// ignore inaccessible files
}
}
return files.Count;
}
public static string[] GetXmlFiles(string xmlPath)
{
if (string.IsNullOrWhiteSpace(xmlPath) || !Directory.Exists(xmlPath)) return new string[0];
try
{
return Directory.GetFiles(xmlPath, "*.xml", SearchOption.TopDirectoryOnly);
}
catch
{
return new string[0];
}
}
private static string FormatBytesToMb(long bytes)
{
double mb = bytes / 1024.0 / 1024.0;
return mb.ToString("F2");
}
}
}
+83
View File
@@ -0,0 +1,83 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Xml.Linq;
namespace TapeCatalog.Methods
{
/// <summary>
/// Helpers to read XML files and produce a compact summary used by the UI.
/// </summary>
public static class Xml
{
public static List<TapeCatalog.FileEntry> ParseSummary(string filePath)
{
var result = new List<TapeCatalog.FileEntry>();
if (string.IsNullOrWhiteSpace(filePath) || !File.Exists(filePath))
return result;
try
{
var doc = XDocument.Load(filePath);
var root = doc.Root;
if (root == null) return result;
if (string.Equals(root.Name.LocalName, "Files", StringComparison.OrdinalIgnoreCase))
{
foreach (var fe in root.Elements("File"))
{
try
{
var fn = fe.Element("Filename")?.Value ?? string.Empty;
var fd = fe.Element("FileDir")?.Value ?? string.Empty;
var st = fe.Element("Status")?.Value ?? string.Empty;
result.Add(new TapeCatalog.FileEntry { Filename = fn, FileDir = fd, Status = st });
}
catch
{
// ignore malformed entries
}
}
}
else if (string.Equals(root.Name.LocalName, "TapeEntries", StringComparison.OrdinalIgnoreCase))
{
foreach (var entry in root.Elements("Entry"))
{
try
{
var fn = entry.Element("Filename")?.Value ?? string.Empty;
var med = entry.Element("Medium")?.Value ?? string.Empty;
var mod = entry.Element("Modified")?.Value ?? string.Empty;
result.Add(new TapeCatalog.FileEntry { Filename = fn, FileDir = med, Status = mod });
}
catch
{
// ignore malformed entries
}
}
}
}
catch
{
// ignore parse errors and return empty list
}
return result;
}
public static string[] GetXmlFiles(string xmlDirectory)
{
if (string.IsNullOrWhiteSpace(xmlDirectory) || !Directory.Exists(xmlDirectory))
return Array.Empty<string>();
try
{
return Directory.GetFiles(xmlDirectory, "*.xml", SearchOption.TopDirectoryOnly);
}
catch
{
return Array.Empty<string>();
}
}
}
}