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; using TapeCatalog.Methods; using System.Windows.Threading; namespace TapeCatalog { /// /// Interaction logic for MainWindow.xaml /// public partial class MainWindow : Window { private readonly ObservableCollection _files = new ObservableCollection(); private DispatcherTimer _copySummaryTimer; // NOTE: FormatBytesToMb moved to Methods.SettingsUi; no local implementation kept. private void XmlFilesListBox_SelectionChanged(object sender, SelectionChangedEventArgs e) { if (!(XmlFilesListBox.SelectedItem is string path)) { XmlSummaryListView.Items.Clear(); return; } try { if (!File.Exists(path)) { XmlSummaryListView.Items.Clear(); return; } var doc = System.Xml.Linq.XDocument.Load(path); var root = doc.Root; if (root == null) { XmlSummaryListView.Items.Clear(); return; } XmlSummaryListView.Items.Clear(); if (string.Equals(root.Name.LocalName, "Files", StringComparison.OrdinalIgnoreCase)) { foreach (var fe in root.Elements("File")) { var fn = fe.Element("Filename")?.Value ?? string.Empty; var fd = fe.Element("FileDir")?.Value ?? string.Empty; var st = fe.Element("Status")?.Value ?? string.Empty; XmlSummaryListView.Items.Add(new FileEntry { Filename = fn, FileDir = fd, Status = st }); } } else if (string.Equals(root.Name.LocalName, "TapeEntries", StringComparison.OrdinalIgnoreCase)) { foreach (var entry in root.Elements("Entry")) { var fn = entry.Element("Filename")?.Value ?? string.Empty; var med = entry.Element("Medium")?.Value ?? string.Empty; var mod = entry.Element("Modified")?.Value ?? string.Empty; XmlSummaryListView.Items.Add(new FileEntry { Filename = fn, FileDir = med, Status = mod }); } } else { // Unknown format - show nothing } } catch { XmlSummaryListView.Items.Clear(); } } public MainWindow() { InitializeComponent(); FilesListView.ItemsSource = _files; // start periodic update of copy summary _copySummaryTimer = new DispatcherTimer { Interval = TimeSpan.FromMilliseconds(500) }; _copySummaryTimer.Tick += (s, e) => UpdateCopySummary(); _copySummaryTimer.Start(); } #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) { // Only copy entries where the verification status indicates OK (checked) try { var okEntries = _files.Where(f => string.Equals(f.Status, "✔", StringComparison.Ordinal)).ToList(); if (okEntries.Count == 0) { MessageBox.Show(this, "No verified entries to copy to TapeCatalog.", "Nothing to copy", MessageBoxButton.OK, MessageBoxImage.Information); return; } XML.Methods.CopyToTapeCatalog(this, okEntries, TapeCatalogListView); } catch (Exception ex) { MessageBox.Show(this, $"Error copying to TapeCatalog: {ex.Message}", "Error", MessageBoxButton.OK, MessageBoxImage.Error); } } #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) { var sel = SettingsUi.SelectDirectory("Select Source folder"); if (!string.IsNullOrWhiteSpace(sel)) SourceTextBox.Text = sel; } #endregion (EXPORT_LIST) #region (SET_ARC_DIR) private void SET_ARC_DIR(object sender, RoutedEventArgs e) { var sel = SettingsUi.SelectDirectory("Select Archive folder"); if (!string.IsNullOrWhiteSpace(sel)) ArchiveTextBox.Text = sel; } #endregion (SET_ARC_DIR) #region (SET_XML_DIR) private void SET_XML_DIR(object sender, RoutedEventArgs e) { var sel = SettingsUi.SelectDirectory("Select XML folder"); if (!string.IsNullOrWhiteSpace(sel)) XmlDirectoryTextBox.Text = sel; } #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(); XmlSummaryListView.Items.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) || !Directory.Exists(path)) { MessageBox.Show("Please select a valid Source directory first.", "No directory", MessageBoxButton.OK, MessageBoxImage.Warning); return; } try { var count = SettingsUi.ScanSource(path, _files); MessageBox.Show($"Scan abgeschlossen. Einträge: {count}", "Scan beendet", MessageBoxButton.OK, MessageBoxImage.Information); } catch (Exception ex) { MessageBox.Show($"Error scanning directory: {ex.Message}", "Error", MessageBoxButton.OK, MessageBoxImage.Error); } } #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) || !Directory.Exists(path)) { MessageBox.Show("Please select a valid Archive directory first.", "No directory", MessageBoxButton.OK, MessageBoxImage.Warning); return; } try { var count = SettingsUi.ScanArchive(path, _files); MessageBox.Show($"Scan abgeschlossen. Einträge: {count}", "Scan beendet", MessageBoxButton.OK, MessageBoxImage.Information); } catch (Exception ex) { MessageBox.Show($"Error scanning archive directory: {ex.Message}", "Error", MessageBoxButton.OK, MessageBoxImage.Error); } } #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) { Copy.PopulateTree(CopyTreeView, SourceTextBox.Text); } 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 result = Copy.CopySelectedToArchive(CopyTreeView, sourceRoot, archivePath); MessageBox.Show(this, $"Copy finished. Success: {result.Item1}, Failed: {result.Item2}", "Copy completed", MessageBoxButton.OK, MessageBoxImage.Information); } private void UpdateCopySummary() { try { var files = Copy.GetCheckedFiles(CopyTreeView); CopySelectedCountText.Text = files.Count.ToString(); double totalMb = 0; foreach (var f in files) { try { var fi = new FileInfo(f); totalMb += fi.Length / 1024.0 / 1024.0; } catch { // ignore } } CopySelectedSizeText.Text = totalMb.ToString("F2"); var archivePath = ArchiveTextBox.Text?.Trim(); if (!string.IsNullOrWhiteSpace(archivePath)) { try { var root = System.IO.Path.GetPathRoot(archivePath); if (!string.IsNullOrWhiteSpace(root)) { var di = new DriveInfo(root); // AvailableFreeSpace in bytes double freeMb = di.AvailableFreeSpace / 1024.0 / 1024.0; ArchiveFreeText.Text = freeMb.ToString("F2") + " MB"; } else { ArchiveFreeText.Text = "N/A"; } } catch { ArchiveFreeText.Text = "N/A"; } } else { ArchiveFreeText.Text = "N/A"; } } catch (Exception) { // swallow periodic update errors; avoid UI disruptions } } } #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)); } }