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));
}
}