SHA256
Update UI
This commit is contained in:
1 parent
abab3612f2
commit
a3b41679c9
6 files changed
+416
-314
No files matched your search
+30
-13
@@ -61,7 +61,15 @@
|
||||
</Grid.ColumnDefinitions>
|
||||
<Button Content="Refresh Copy Tree" HorizontalAlignment="Left" Height="22" Margin="10,10,0,0" VerticalAlignment="Top" Width="140" Click="POPULATE_COPY_TREE"/>
|
||||
<Button Content="Copy Selected to Archive" HorizontalAlignment="Left" Height="22" Margin="160,10,0,0" VerticalAlignment="Top" Width="180" Click="COPY_SELECTED_TO_ARCHIVE" Grid.ColumnSpan="2"/>
|
||||
<TreeView x:Name="CopyTreeView" Margin="10,40,10,10" Grid.ColumnSpan="2" />
|
||||
<StackPanel Orientation="Vertical" HorizontalAlignment="Left" VerticalAlignment="Top" Margin="146,10,0,0" Grid.Column="1" Width="149">
|
||||
<TextBlock Text="Selected files:"/>
|
||||
<TextBlock x:Name="CopySelectedCountText" Text="0" Margin="0,2,0,6"/>
|
||||
<TextBlock Text="Selected size (MB):"/>
|
||||
<TextBlock x:Name="CopySelectedSizeText" Text="0.00" Margin="0,2,0,6"/>
|
||||
<TextBlock Text="Archive free:"/>
|
||||
<TextBlock x:Name="ArchiveFreeText" Text="-" Margin="0,2,0,6"/>
|
||||
</StackPanel>
|
||||
<TreeView x:Name="CopyTreeView" Margin="10,170,10,10" Grid.ColumnSpan="2" />
|
||||
</Grid>
|
||||
</TabItem>
|
||||
<TabItem Header="XML">
|
||||
@@ -71,28 +79,37 @@
|
||||
<ColumnDefinition Width="*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<ListBox x:Name="XmlFilesListBox" Margin="4" SelectionChanged="XmlFilesListBox_SelectionChanged" />
|
||||
<TextBox x:Name="XmlViewerTextBox" Margin="4" Grid.Column="1" IsReadOnly="True" TextWrapping="Wrap" VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Auto" />
|
||||
<ListView x:Name="XmlSummaryListView" Margin="4" Grid.Column="1">
|
||||
<ListView.View>
|
||||
<GridView>
|
||||
<GridViewColumn Header="Filename" DisplayMemberBinding="{Binding Filename}" Width="250" />
|
||||
<GridViewColumn Header="FileDir" DisplayMemberBinding="{Binding FileDir}" Width="350" />
|
||||
<GridViewColumn Header="Status" DisplayMemberBinding="{Binding Status}" Width="100" />
|
||||
</GridView>
|
||||
</ListView.View>
|
||||
</ListView>
|
||||
</Grid>
|
||||
</TabItem>
|
||||
<TabItem Header="Settings" Height="20" VerticalAlignment="Center" RenderTransformOrigin="0.104,0.652" HorizontalAlignment="Left" Width="55">
|
||||
<Grid Background="#FFE5E5E5">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="0*"/>
|
||||
<ColumnDefinition/>
|
||||
<ColumnDefinition Width="96*"/>
|
||||
<ColumnDefinition Width="301*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock Text="Application Settings" FontWeight="Bold" Margin="0,0,0,10" Grid.ColumnSpan="2" />
|
||||
<TextBox x:Name="SourceTextBox" HorizontalAlignment="Left" Height="20" Margin="0,40,0,0" TextWrapping="Wrap" Text="Source Directory" VerticalAlignment="Top" Width="510" Grid.ColumnSpan="2"/>
|
||||
<TextBox x:Name="ArchiveTextBox" HorizontalAlignment="Left" Height="20" Margin="0,80,0,0" TextWrapping="Wrap" Text="Archive Directory" VerticalAlignment="Top" Width="510" Grid.ColumnSpan="2"/>
|
||||
<TextBlock Text="XML Directory:" Height="20" Margin="0,100,101,0" VerticalAlignment="Top" Grid.ColumnSpan="2"/>
|
||||
<TextBox x:Name="XmlDirectoryTextBox" HorizontalAlignment="Left" Height="20" Margin="0,120,0,0" TextWrapping="Wrap" Text="XML Directory" VerticalAlignment="Top" Width="510" Grid.ColumnSpan="2"/>
|
||||
<Button x:Name="XmlDirectorySelect" Content="Select Directory" HorizontalAlignment="Left" Margin="520,120,0,0" VerticalAlignment="Top" Height="20" Click="SET_XML_DIR" Grid.Column="1"/>
|
||||
<Button x:Name="SourceSelect" Content="Select Directory" HorizontalAlignment="Left" Margin="520,40,0,0" VerticalAlignment="Top" Height="20" Click="SET_RES_DIR" Grid.Column="1" Width="87"/>
|
||||
<Button x:Name="ArchiveSelect" Content="Select Directory" HorizontalAlignment="Left" Margin="520,80,0,0" VerticalAlignment="Top" Height="20" Click="SET_ARC_DIR" Grid.Column="1" Width="87"/>
|
||||
<TextBlock Text="Application Settings" FontWeight="Bold" Margin="0,0,0,10" Grid.ColumnSpan="3" />
|
||||
<TextBox x:Name="SourceTextBox" HorizontalAlignment="Left" Height="20" Margin="0,40,0,0" TextWrapping="Wrap" Text="Source Directory" VerticalAlignment="Top" Width="510" Grid.ColumnSpan="3"/>
|
||||
<TextBox x:Name="ArchiveTextBox" HorizontalAlignment="Left" Height="20" Margin="0,80,0,0" TextWrapping="Wrap" Text="Archive Directory" VerticalAlignment="Top" Width="510" Grid.ColumnSpan="3"/>
|
||||
<TextBlock Text="XML Directory:" Height="20" Margin="0,100,101,0" VerticalAlignment="Top" Grid.ColumnSpan="3"/>
|
||||
<TextBlock Text="Source Directory:" Height="20" Margin="0,20,192,0" VerticalAlignment="Top" Grid.ColumnSpan="3"/>
|
||||
<TextBlock Text="Archive Directory:" Height="20" Margin="0,60,192,0" VerticalAlignment="Top" Grid.ColumnSpan="3"/>
|
||||
<TextBox x:Name="XmlDirectoryTextBox" HorizontalAlignment="Left" Height="20" Margin="0,120,0,0" TextWrapping="Wrap" Text="XML Directory" VerticalAlignment="Top" Width="510" Grid.ColumnSpan="3"/>
|
||||
<Button x:Name="XmlDirectorySelect" Content="Select Directory" HorizontalAlignment="Left" Margin="328,120,0,0" VerticalAlignment="Top" Height="20" Click="SET_XML_DIR" Grid.Column="2"/>
|
||||
<Button x:Name="SourceSelect" Content="Select Directory" HorizontalAlignment="Left" Margin="328,40,0,0" VerticalAlignment="Top" Height="20" Click="SET_RES_DIR" Grid.Column="2" Width="87"/>
|
||||
<Button x:Name="ArchiveSelect" Content="Select Directory" HorizontalAlignment="Left" Margin="328,80,0,0" VerticalAlignment="Top" Height="20" Click="SET_ARC_DIR" Grid.Column="2" Width="87"/>
|
||||
<Button Content="Scan Source" HorizontalAlignment="Left" Height="22" Margin="10,160,0,0" VerticalAlignment="Top" Width="120" Click="SCAN_RES_DIR" Grid.Column="1"/>
|
||||
<Button Content="Scan Archive" HorizontalAlignment="Left" Height="22" Margin="10,182,0,0" VerticalAlignment="Top" Width="120" Click="SCAN_ARC_DIR" Grid.Column="1"/>
|
||||
<Button Content="Load XML Files" HorizontalAlignment="Left" Height="22" Margin="10,204,0,0" VerticalAlignment="Top" Width="120" Click="LOAD_XML_FILES" Grid.Column="1"/>
|
||||
<TextBlock TextWrapping="Wrap" Text="Source Directory:" Height="20" Margin="0,20,101,0" VerticalAlignment="Top"/>
|
||||
<TextBlock TextWrapping="Wrap" Text="Archive Directory:" Height="20" Margin="0,60,101,0" VerticalAlignment="Top"/>
|
||||
</Grid>
|
||||
</TabItem>
|
||||
|
||||
|
||||
+116
-249
@@ -15,6 +15,8 @@ using System.Windows.Shapes;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.IO;
|
||||
using XML;
|
||||
using TapeCatalog.Methods;
|
||||
using System.Windows.Threading;
|
||||
|
||||
|
||||
|
||||
@@ -26,30 +28,64 @@ namespace TapeCatalog
|
||||
public partial class MainWindow : Window
|
||||
{
|
||||
private readonly ObservableCollection<FileEntry> _files = new ObservableCollection<FileEntry>();
|
||||
private DispatcherTimer _copySummaryTimer;
|
||||
|
||||
private static string FormatBytesToMb(long bytes)
|
||||
{
|
||||
// convert to MB with two decimal places
|
||||
double mb = bytes / 1024.0 / 1024.0;
|
||||
return mb.ToString("F2");
|
||||
}
|
||||
// NOTE: FormatBytesToMb moved to Methods.SettingsUi; no local implementation kept.
|
||||
|
||||
private void XmlFilesListBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
|
||||
{
|
||||
if (XmlFilesListBox.SelectedItem is string path)
|
||||
if (!(XmlFilesListBox.SelectedItem is string path))
|
||||
{
|
||||
try
|
||||
XmlSummaryListView.Items.Clear();
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
if (!File.Exists(path))
|
||||
{
|
||||
XmlViewerTextBox.Text = File.ReadAllText(path);
|
||||
XmlSummaryListView.Items.Clear();
|
||||
return;
|
||||
}
|
||||
catch (Exception ex)
|
||||
|
||||
var doc = System.Xml.Linq.XDocument.Load(path);
|
||||
var root = doc.Root;
|
||||
if (root == null)
|
||||
{
|
||||
XmlViewerTextBox.Text = $"Error reading file: {ex.Message}";
|
||||
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
|
||||
}
|
||||
}
|
||||
else
|
||||
catch
|
||||
{
|
||||
XmlViewerTextBox.Clear();
|
||||
XmlSummaryListView.Items.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -57,6 +93,10 @@ namespace TapeCatalog
|
||||
{
|
||||
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)
|
||||
@@ -143,49 +183,22 @@ namespace TapeCatalog
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
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)
|
||||
{
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
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)
|
||||
{
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
var sel = SettingsUi.SelectDirectory("Select XML folder");
|
||||
if (!string.IsNullOrWhiteSpace(sel)) XmlDirectoryTextBox.Text = sel;
|
||||
}
|
||||
#endregion (SET_XML_DIR)
|
||||
|
||||
@@ -211,7 +224,7 @@ namespace TapeCatalog
|
||||
}
|
||||
|
||||
XmlFilesListBox.Items.Clear();
|
||||
XmlViewerTextBox.Clear();
|
||||
XmlSummaryListView.Items.Clear();
|
||||
foreach (var f in files)
|
||||
{
|
||||
XmlFilesListBox.Items.Add(f);
|
||||
@@ -232,132 +245,42 @@ namespace TapeCatalog
|
||||
private void SCAN_RES_DIR(object sender, RoutedEventArgs e)
|
||||
{
|
||||
var path = SourceTextBox.Text?.Trim();
|
||||
if (string.IsNullOrWhiteSpace(path))
|
||||
if (string.IsNullOrWhiteSpace(path) || !Directory.Exists(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);
|
||||
MessageBox.Show("Please select a valid Source directory first.", "No directory", MessageBoxButton.OK, MessageBoxImage.Warning);
|
||||
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
|
||||
}
|
||||
}
|
||||
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);
|
||||
}
|
||||
// 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))
|
||||
if (string.IsNullOrWhiteSpace(path) || !Directory.Exists(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);
|
||||
MessageBox.Show("Please select a valid Archive directory first.", "No directory", MessageBoxButton.OK, MessageBoxImage.Warning);
|
||||
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
|
||||
}
|
||||
}
|
||||
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);
|
||||
}
|
||||
// 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)
|
||||
@@ -420,58 +343,7 @@ 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;
|
||||
Copy.PopulateTree(CopyTreeView, SourceTextBox.Text);
|
||||
}
|
||||
|
||||
private void COPY_SELECTED_TO_ARCHIVE(object sender, RoutedEventArgs e)
|
||||
@@ -490,68 +362,63 @@ namespace TapeCatalog
|
||||
return;
|
||||
}
|
||||
|
||||
var files = new List<string>();
|
||||
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
|
||||
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 CollectCheckedFiles(TreeViewItem node, List<string> list)
|
||||
private void UpdateCopySummary()
|
||||
{
|
||||
if (node.Header is StackPanel sp)
|
||||
try
|
||||
{
|
||||
foreach (var child in sp.Children)
|
||||
var files = Copy.GetCheckedFiles(CopyTreeView);
|
||||
CopySelectedCountText.Text = files.Count.ToString();
|
||||
|
||||
double totalMb = 0;
|
||||
foreach (var f in files)
|
||||
{
|
||||
if (child is CheckBox cb && cb.IsChecked == true)
|
||||
try
|
||||
{
|
||||
if (node.Tag is string path && File.Exists(path))
|
||||
{
|
||||
list.Add(path);
|
||||
}
|
||||
break;
|
||||
var fi = new FileInfo(f);
|
||||
totalMb += fi.Length / 1024.0 / 1024.0;
|
||||
}
|
||||
catch
|
||||
{
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
}
|
||||
CopySelectedSizeText.Text = totalMb.ToString("F2");
|
||||
|
||||
foreach (var it in node.Items)
|
||||
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)
|
||||
{
|
||||
if (it is TreeViewItem t)
|
||||
CollectCheckedFiles(t, list);
|
||||
// swallow periodic update errors; avoid UI disruptions
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+185
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,51 +0,0 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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>();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+2
-1
@@ -56,8 +56,9 @@
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
<SubType>Designer</SubType>
|
||||
</ApplicationDefinition>
|
||||
<Compile Include="Methods\CopyMethods.cs" />
|
||||
<Compile Include="Methods\Copy.cs" />
|
||||
<Compile Include="Methods\Settings.cs" />
|
||||
<Compile Include="Methods\xml.cs" />
|
||||
<Compile Include="XML_Methods.cs" />
|
||||
<Page Include="MainWindow.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
|
||||
Reference in new issue
Block a user