diff --git a/MainWindow.xaml b/MainWindow.xaml
index 67dc56d..ffafa17 100644
--- a/MainWindow.xaml
+++ b/MainWindow.xaml
@@ -7,15 +7,13 @@
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
-
+
-
-
-
-
-
-
+
+
+
+
@@ -35,8 +33,7 @@
-
-
+
@@ -56,7 +53,49 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
diff --git a/MainWindow.xaml.cs b/MainWindow.xaml.cs
index 7236c7d..a724904 100644
--- a/MainWindow.xaml.cs
+++ b/MainWindow.xaml.cs
@@ -34,6 +34,25 @@ namespace TapeCatalog
return mb.ToString("F2");
}
+ private void XmlFilesListBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
+ {
+ if (XmlFilesListBox.SelectedItem is string path)
+ {
+ try
+ {
+ XmlViewerTextBox.Text = File.ReadAllText(path);
+ }
+ catch (Exception ex)
+ {
+ XmlViewerTextBox.Text = $"Error reading file: {ex.Message}";
+ }
+ }
+ else
+ {
+ XmlViewerTextBox.Clear();
+ }
+ }
+
public MainWindow()
{
InitializeComponent();
@@ -153,6 +172,62 @@ namespace TapeCatalog
}
}
#endregion (SET_ARC_DIR)
+ #region (SET_XML_DIR)
+ private void SET_XML_DIR(object sender, RoutedEventArgs e)
+ {
+ // Use the WinForms FolderBrowserDialog to select a folder for XML files
+ using (var dlg = new System.Windows.Forms.FolderBrowserDialog())
+ {
+ dlg.Description = "Select XML folder";
+ dlg.ShowNewFolderButton = true;
+ var result = dlg.ShowDialog();
+ if (result == System.Windows.Forms.DialogResult.OK && !string.IsNullOrWhiteSpace(dlg.SelectedPath))
+ {
+ XmlDirectoryTextBox.Text = dlg.SelectedPath;
+ }
+ }
+ }
+ #endregion (SET_XML_DIR)
+
+ #region (LOAD_XML_FILES)
+ private void LOAD_XML_FILES(object sender, RoutedEventArgs e)
+ {
+ var xmlPath = XmlDirectoryTextBox.Text?.Trim();
+ if (string.IsNullOrWhiteSpace(xmlPath) || !Directory.Exists(xmlPath))
+ {
+ MessageBox.Show(this, "Please select a valid XML directory first.", "Invalid XML Directory", MessageBoxButton.OK, MessageBoxImage.Warning);
+ return;
+ }
+
+ string[] files;
+ try
+ {
+ files = Directory.GetFiles(xmlPath, "*.xml", SearchOption.TopDirectoryOnly);
+ }
+ catch (Exception ex)
+ {
+ MessageBox.Show(this, $"Error enumerating XML files: {ex.Message}", "Error", MessageBoxButton.OK, MessageBoxImage.Error);
+ return;
+ }
+
+ XmlFilesListBox.Items.Clear();
+ XmlViewerTextBox.Clear();
+ foreach (var f in files)
+ {
+ XmlFilesListBox.Items.Add(f);
+ }
+
+ // switch to XML tab
+ foreach (var it in MainTabControl.Items)
+ {
+ if (it is TabItem ti && (ti.Header as string) == "XML")
+ {
+ MainTabControl.SelectedItem = ti;
+ break;
+ }
+ }
+ }
+ #endregion (LOAD_XML_FILES)
#region (SCAN_RES_DIR)
private void SCAN_RES_DIR(object sender, RoutedEventArgs e)
{
@@ -341,6 +416,144 @@ namespace TapeCatalog
}
}
}
+
+ // Populate CopyTreeView with directory structure from SourceTextBox
+ private void POPULATE_COPY_TREE(object sender, RoutedEventArgs e)
+ {
+ BuildCopyTree();
+ }
+
+ private void BuildCopyTree()
+ {
+ CopyTreeView.Items.Clear();
+ var path = SourceTextBox.Text?.Trim();
+ if (string.IsNullOrWhiteSpace(path) || !Directory.Exists(path))
+ {
+ MessageBox.Show(this, "Please select a valid Source directory first.", "Invalid Source", MessageBoxButton.OK, MessageBoxImage.Warning);
+ return;
+ }
+
+ try
+ {
+ var rootDir = new DirectoryInfo(path);
+ var rootItem = CreateDirectoryNode(rootDir);
+ rootItem.IsExpanded = true;
+ CopyTreeView.Items.Add(rootItem);
+ }
+ catch (Exception ex)
+ {
+ MessageBox.Show(this, $"Error building copy tree: {ex.Message}", "Error", MessageBoxButton.OK, MessageBoxImage.Error);
+ }
+ }
+
+ private TreeViewItem CreateDirectoryNode(DirectoryInfo dir)
+ {
+ var item = new TreeViewItem { Header = dir.Name, Tag = dir.FullName };
+ try
+ {
+ foreach (var sub in dir.GetDirectories())
+ {
+ item.Items.Add(CreateDirectoryNode(sub));
+ }
+ 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
+ }
+ return item;
+ }
+
+ private void COPY_SELECTED_TO_ARCHIVE(object sender, RoutedEventArgs e)
+ {
+ var archivePath = ArchiveTextBox.Text?.Trim();
+ if (string.IsNullOrWhiteSpace(archivePath) || !Directory.Exists(archivePath))
+ {
+ MessageBox.Show(this, "Please select a valid Archive directory first.", "Invalid Archive", MessageBoxButton.OK, MessageBoxImage.Warning);
+ return;
+ }
+
+ var sourceRoot = SourceTextBox.Text?.Trim();
+ if (string.IsNullOrWhiteSpace(sourceRoot) || !Directory.Exists(sourceRoot))
+ {
+ MessageBox.Show(this, "Please select a valid Source directory first.", "Invalid Source", MessageBoxButton.OK, MessageBoxImage.Warning);
+ return;
+ }
+
+ var files = new List();
+ foreach (var it in CopyTreeView.Items)
+ {
+ if (it is TreeViewItem tvi)
+ CollectCheckedFiles(tvi, files);
+ }
+
+ if (files.Count == 0)
+ {
+ MessageBox.Show(this, "No files selected.", "Nothing to copy", MessageBoxButton.OK, MessageBoxImage.Information);
+ return;
+ }
+
+ int copied = 0, failed = 0;
+ foreach (var src in files)
+ {
+ try
+ {
+ var fileDir = System.IO.Path.GetDirectoryName(src) ?? string.Empty;
+ string relative;
+ if (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++;
+ }
+ }
+
+ MessageBox.Show(this, $"Copy finished. Success: {copied}, Failed: {failed}", "Copy completed", MessageBoxButton.OK, MessageBoxImage.Information);
+ // no-op edit to preserve patch continuity
+ }
+
+ private void CollectCheckedFiles(TreeViewItem node, List 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);
+ }
+ }
}
#endregion (VERIFY_SIZES)
public class FileEntry : System.ComponentModel.INotifyPropertyChanged
diff --git a/Methods/CopyMethods.cs b/Methods/CopyMethods.cs
new file mode 100644
index 0000000..30c685b
--- /dev/null
+++ b/Methods/CopyMethods.cs
@@ -0,0 +1,51 @@
+using System;
+using System.IO;
+using System.Windows.Controls;
+using System.Windows;
+
+namespace TapeCatalog.Methods
+{
+ // Reusable helper methods for copy-related UI functionality
+ public static class CopyMethods
+ {
+ 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 { Header = dir.Name, Tag = dir.FullName };
+ try
+ {
+ foreach (var sub in dir.GetDirectories())
+ {
+ item.Items.Add(CreateDirectoryNode(sub));
+ }
+ 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
+ }
+ return item;
+ }
+ }
+}
diff --git a/Methods/Settings.cs b/Methods/Settings.cs
new file mode 100644
index 0000000..79d877c
--- /dev/null
+++ b/Methods/Settings.cs
@@ -0,0 +1,184 @@
+using System;
+using System.IO;
+using System.Xml.Serialization;
+using System.Linq;
+using System.Collections.ObjectModel;
+
+namespace TapeCatalog.Methods
+{
+ ///
+ /// Simple settings container and load/save helpers.
+ ///
+ [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 files)
+ {
+ if (string.IsNullOrWhiteSpace(path)) throw new ArgumentException("path");
+ if (!Directory.Exists(path)) throw new DirectoryNotFoundException(path);
+
+ foreach (var file in Directory.EnumerateFiles(path, "*.*", SearchOption.AllDirectories))
+ {
+ try
+ {
+ var fi = new FileInfo(file);
+ var existing = files.FirstOrDefault(f => string.Equals(f.Filename, fi.Name, StringComparison.OrdinalIgnoreCase)
+ && string.Equals(f.FileDir, fi.DirectoryName, 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 = fi.DirectoryName,
+ SizeSrc = FormatBytesToMb(fi.Length),
+ ModSrc = fi.LastWriteTime.ToString(),
+ });
+ }
+ }
+ catch
+ {
+ // ignore inaccessible files
+ }
+ }
+ return files.Count;
+ }
+
+ public static int ScanArchive(string path, ObservableCollection files)
+ {
+ if (string.IsNullOrWhiteSpace(path)) throw new ArgumentException("path");
+ if (!Directory.Exists(path)) throw new DirectoryNotFoundException(path);
+
+ foreach (var file in Directory.EnumerateFiles(path, "*.*", SearchOption.AllDirectories))
+ {
+ try
+ {
+ var fi = new FileInfo(file);
+ var existing = files.FirstOrDefault(f => string.Equals(f.Filename, fi.Name, StringComparison.Ordinal));
+ 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 = fi.DirectoryName,
+ 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");
+ }
+ }
+}
diff --git a/TapeCatalog.csproj b/TapeCatalog.csproj
index bda75f1..d94ce84 100644
--- a/TapeCatalog.csproj
+++ b/TapeCatalog.csproj
@@ -56,6 +56,8 @@
MSBuild:Compile
Designer
+
+
MSBuild:Compile