using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Windows; using System.Windows.Controls; using System.Windows.Data; using System.Windows.Documents; using System.Windows.Input; using System.Windows.Media; using System.Windows.Media.Imaging; using System.Windows.Navigation; using System.Windows.Shapes; using System.Collections.ObjectModel; using System.IO; using XML; namespace TapeCatalog { /// /// Interaction logic for MainWindow.xaml /// public partial class MainWindow : Window { private readonly ObservableCollection _files = new ObservableCollection(); private static string FormatBytesToMb(long bytes) { // convert to MB with two decimal places double mb = bytes / 1024.0 / 1024.0; 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(); FilesListView.ItemsSource = _files; } #region (export Medium) private void EXPORT_MEDIUM(object sender, RoutedEventArgs e) { var selected = MediumComboBox.SelectedItem as string; if (string.IsNullOrWhiteSpace(selected)) { MessageBox.Show(this, "Bitte ein Medium im Dropdown auswählen.", "Keine Auswahl", MessageBoxButton.OK, MessageBoxImage.Warning); return; } // collect matching entries from TapeCatalogListView var matches = new List(); foreach (var item in TapeCatalogListView.Items) { var te = item as dynamic; if (te == null) continue; string m = te.Medium as string; if (string.Equals(m, selected, StringComparison.OrdinalIgnoreCase)) { matches.Add(new XML.Methods.TapeEntry { Medium = te.Medium, Filename = te.Filename, Modified = te.Modified }); } } if (matches.Count == 0) { MessageBox.Show(this, "Keine Einträge für das gewählte Medium gefunden.", "Keine Einträge", MessageBoxButton.OK, MessageBoxImage.Information); return; } // delegate export to XML helper XML.Methods.ExportTapeEntries(this, selected, matches); } #endregion #region (Import List) private void IMPORT_LIST(object sender, RoutedEventArgs e) { // delegate to XML import helper XML.Methods.ImportList(this, _files, SourceTextBox, ArchiveTextBox); } #endregion (Import List) #region (COPY_TO_TAPECATALOG) private void COPY_TO_TAPECATALOG(object sender, RoutedEventArgs e) { // delegate to XML helper to copy entries to TapeCatalog list view XML.Methods.CopyToTapeCatalog(this, _files, TapeCatalogListView); } #endregion (COPY_TO_TAPECATALOG) #region (COPY_FROM_SRCARC) private void COPY_FROM_SRCARC(object sender, RoutedEventArgs e) { XML.Methods.CopyToTapeCatalog(this, _files, TapeCatalogListView); } #endregion (COPY_FROM_SRCARC) #region (REFRESH_MEDIUMS) private void REFRESH_MEDIUMS(object sender, RoutedEventArgs e) { // collect distinct mediums from TapeCatalogListView items try { var mediums = new HashSet(StringComparer.OrdinalIgnoreCase); foreach (var item in TapeCatalogListView.Items) { var te = item as dynamic; if (te == null) continue; string m = te.Medium as string; if (!string.IsNullOrWhiteSpace(m)) mediums.Add(m); } MediumComboBox.ItemsSource = mediums.OrderBy(s => s).ToList(); } catch (Exception ex) { MessageBox.Show(this, $"Fehler beim Aktualisieren der Medium-Liste: {ex.Message}", "Fehler", MessageBoxButton.OK, MessageBoxImage.Error); } } #endregion (REFRESH_MEDIUMS) #region (EXPORT_LIST) private void EXPORT_LIST(object sender, RoutedEventArgs e) { // Delegate export to XML helper XML.Methods.ExportList(this, SourceTextBox.Text, ArchiveTextBox.Text, _files); } private void SET_RES_DIR(object sender, RoutedEventArgs e) { // Use the WinForms FolderBrowserDialog to select a folder using (var dlg = new System.Windows.Forms.FolderBrowserDialog()) { dlg.Description = "Select a folder"; dlg.ShowNewFolderButton = true; var result = dlg.ShowDialog(); if (result == System.Windows.Forms.DialogResult.OK && !string.IsNullOrWhiteSpace(dlg.SelectedPath)) { SourceTextBox.Text = dlg.SelectedPath; } } } #endregion (EXPORT_LIST) #region (SET_ARC_DIR) private void SET_ARC_DIR(object sender, RoutedEventArgs e) { // Use the WinForms FolderBrowserDialog to select a folder using (var dlg = new System.Windows.Forms.FolderBrowserDialog()) { dlg.Description = "Select a folder"; dlg.ShowNewFolderButton = true; var result = dlg.ShowDialog(); if (result == System.Windows.Forms.DialogResult.OK && !string.IsNullOrWhiteSpace(dlg.SelectedPath)) { ArchiveTextBox.Text = dlg.SelectedPath; } } } #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) { var path = SourceTextBox.Text?.Trim(); if (string.IsNullOrWhiteSpace(path)) { MessageBox.Show("Please select a directory first.", "No directory", MessageBoxButton.OK, MessageBoxImage.Warning); return; } if (!Directory.Exists(path)) { MessageBox.Show("The specified directory does not exist.", "Directory not found", MessageBoxButton.OK, MessageBoxImage.Error); return; } try { foreach (var file in Directory.EnumerateFiles(path, "*.*", SearchOption.AllDirectories)) { try { var fi = new FileInfo(file); // check if an entry with same filename and same source directory already exists 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) { // update size and modification date, do not add a new entry existing.SizeSrc = newSizeMb; existing.ModSrc = fi.LastWriteTime.ToString(); // reset verification status and archive drive info existing.Status = string.Empty; existing.ArcDrive = string.Empty; } } else { _files.Add(new FileEntry { Filename = fi.Name, FileDir = fi.DirectoryName, SizeSrc = FormatBytesToMb(fi.Length), //SizeArc = string.Empty, ModSrc = fi.LastWriteTime.ToString(), //ModArc = string.Empty }); } } catch { // ignore files we can't access } } } catch (Exception ex) { MessageBox.Show($"Error scanning directory: {ex.Message}", "Error", MessageBoxButton.OK, MessageBoxImage.Error); } // inform user that scan finished MessageBox.Show($"Scan abgeschlossen. Einträge: {_files.Count}", "Scan beendet", MessageBoxButton.OK, MessageBoxImage.Information); } #endregion (SCAN_RES_DIR) #region(SCAN_ARC_DIR) private void SCAN_ARC_DIR(object sender, RoutedEventArgs e) { var path = ArchiveTextBox.Text?.Trim(); if (string.IsNullOrWhiteSpace(path)) { MessageBox.Show("Please select an archive directory first.", "No directory", MessageBoxButton.OK, MessageBoxImage.Warning); return; } if (!Directory.Exists(path)) { MessageBox.Show("The specified archive directory does not exist.", "Directory not found", MessageBoxButton.OK, MessageBoxImage.Error); return; } try { foreach (var file in Directory.EnumerateFiles(path, "*.*", SearchOption.AllDirectories)) { try { var fi = new FileInfo(file); // look for existing entry by filename (case-insensitive) var existing = _files.FirstOrDefault(f => string.Equals(f.Filename, fi.Name, StringComparison.Ordinal)); if (existing != null) { // update SizeArc existing.SizeArc = FormatBytesToMb(fi.Length); // update ModArc to the most recent modified date 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 FileEntry { Filename = fi.Name, FileDir = fi.DirectoryName, //SizeSrc = string.Empty, SizeArc = FormatBytesToMb(fi.Length), //ModSrc = string.Empty, ModArc = fi.LastWriteTime.ToString() }); } } catch { // ignore inaccessible files } } } catch (Exception ex) { MessageBox.Show($"Error scanning archive directory: {ex.Message}", "Error", MessageBoxButton.OK, MessageBoxImage.Error); } // inform user that archive scan finished MessageBox.Show($"Scan abgeschlossen. Einträge: {_files.Count}", "Scan beendet", MessageBoxButton.OK, MessageBoxImage.Information); } #endregion(SCAN_ARC_DIR) #region (CLEAR_FILES) private void CLEAR_FILES(object sender, RoutedEventArgs e) { _files.Clear(); } #endregion (CLEAR_FILES) #region (VERIFY_SIZES) private void VERIFY_SIZES(object sender, RoutedEventArgs e) { // determine archive drive name (volume label or drive root) string archivePath = ArchiveTextBox.Text?.Trim(); string archiveDriveName = string.Empty; if (!string.IsNullOrWhiteSpace(archivePath)) { try { var root = System.IO.Path.GetPathRoot(archivePath); if (!string.IsNullOrWhiteSpace(root)) { try { var di = new DriveInfo(root); archiveDriveName = string.IsNullOrWhiteSpace(di.VolumeLabel) ? root.TrimEnd('\\') : di.VolumeLabel; } catch { archiveDriveName = root.TrimEnd('\\'); } } } catch { archiveDriveName = string.Empty; } } foreach (var entry in _files) { // Sizes are stored in MB with two decimals; compare as doubles double src = 0; double arc = 0; double.TryParse(entry.SizeSrc, out src); double.TryParse(entry.SizeArc, out arc); if (src > 0 && arc > 0 && Math.Abs(src - arc) < 0.01) { entry.Status = "✔"; entry.ArcDrive = archiveDriveName; } else { entry.Status = "✖"; entry.ArcDrive = string.Empty; } } } // 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 { public string Filename { get; set; } public string FileDir { get; set; } private string _sizeSrc; public string SizeSrc { get => _sizeSrc; set { _sizeSrc = value; OnPropertyChanged(nameof(SizeSrc)); } } private string _sizeArc; public string SizeArc { get => _sizeArc; set { _sizeArc = value; OnPropertyChanged(nameof(SizeArc)); } } public string ModSrc { get; set; } public string ModArc { get; set; } private string _status; public string Status { get => _status; set { _status = value; OnPropertyChanged(nameof(Status)); } } private string _arcDrive; public string ArcDrive { get => _arcDrive; set { _arcDrive = value; OnPropertyChanged(nameof(ArcDrive)); } } public event System.ComponentModel.PropertyChangedEventHandler PropertyChanged; private void OnPropertyChanged(string propName) => PropertyChanged?.Invoke(this, new System.ComponentModel.PropertyChangedEventArgs(propName)); } }