diff --git a/.idea/.idea.Aaru/.idea/contentModel.xml b/.idea/.idea.Aaru/.idea/contentModel.xml
index 7ec2970e5..58d148504 100644
--- a/.idea/.idea.Aaru/.idea/contentModel.xml
+++ b/.idea/.idea.Aaru/.idea/contentModel.xml
@@ -1234,6 +1234,7 @@
+
@@ -1249,6 +1250,7 @@
+
@@ -1258,10 +1260,10 @@
+
+
-
-
@@ -1314,6 +1316,7 @@
+
diff --git a/Aaru.Gui/Forms/frmMain.xeto.cs b/Aaru.Gui/Forms/frmMain.xeto.cs
index 9fa64bd66..38260a644 100644
--- a/Aaru.Gui/Forms/frmMain.xeto.cs
+++ b/Aaru.Gui/Forms/frmMain.xeto.cs
@@ -362,46 +362,6 @@ namespace Aaru.Gui.Forms
lblError.Text = devErrorMessage;
splMain.Panel2 = lblError;
- break;
- case Dictionary files:
- splMain.Panel2 = new pnlListFiles(selectedItem.Values[2] as IReadOnlyFilesystem, files,
- selectedItem.Values[1] as string == "/" ? "/"
- : selectedItem.Values[4] as string);
-
- break;
- case null when selectedItem.Values.Length >= 5 && selectedItem.Values[4] is string dirPath &&
- selectedItem.Values[2] is IReadOnlyFilesystem fsPlugin:
- Errno errno = fsPlugin.ReadDir(dirPath, out List dirents);
-
- if(errno != Errno.NoError)
- {
- Eto.Forms.MessageBox.Show($"Error {errno} trying to read \"{dirPath}\" of chosen filesystem",
- MessageBoxType.Error);
-
- break;
- }
-
- Dictionary filesNew = new Dictionary();
-
- foreach(string dirent in dirents)
- {
- errno = fsPlugin.Stat(dirPath + "/" + dirent, out FileEntryInfo stat);
-
- if(errno != Errno.NoError)
- {
- AaruConsole.
- ErrorWriteLine($"Error {errno} trying to get information about filesystem entry named {dirent}");
-
- continue;
- }
-
- if(!stat.Attributes.HasFlag(FileAttributes.Directory))
- filesNew.Add(dirent, stat);
- }
-
- selectedItem.Values[3] = filesNew;
- splMain.Panel2 = new pnlListFiles(fsPlugin, filesNew, dirPath);
-
break;
}
}
diff --git a/Aaru.Gui/Models/FileModel.cs b/Aaru.Gui/Models/FileModel.cs
new file mode 100644
index 000000000..057a8b0db
--- /dev/null
+++ b/Aaru.Gui/Models/FileModel.cs
@@ -0,0 +1,26 @@
+using System;
+using Aaru.CommonTypes.Structs;
+
+namespace Aaru.Gui.Models
+{
+ public class FileModel
+ {
+ public string Name { get; set; }
+ public string Size => $"{Stat.Length}";
+ public string CreationTime =>
+ Stat.CreationTime == default(DateTime) ? "" : $"{Stat.CreationTime:G}";
+ public string LastAccessTime => Stat.AccessTime == default(DateTime) ? "" : $"{Stat.AccessTime:G}";
+ public string ChangedTime =>
+ Stat.StatusChangeTime == default(DateTime) ? "" : $"{Stat.StatusChangeTime:G}";
+ public string LastBackupTime => Stat.BackupTime == default(DateTime) ? "" : $"{Stat.BackupTime:G}";
+ public string LastWriteTime =>
+ Stat.LastWriteTime == default(DateTime) ? "" : $"{Stat.LastWriteTime:G}";
+ public string Attributes => $"{Stat.Attributes}";
+ public string Gid => $"{Stat.GID}";
+ public string Uid => $"{Stat.UID}";
+ public string Inode => $"{Stat.Inode}";
+ public string Links => $"{Stat.Links}";
+ public string Mode => $"{Stat.Mode}";
+ public FileEntryInfo Stat { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/Aaru.Gui/Models/FileSystemModel.cs b/Aaru.Gui/Models/FileSystemModel.cs
index e15362c61..f85758c3f 100644
--- a/Aaru.Gui/Models/FileSystemModel.cs
+++ b/Aaru.Gui/Models/FileSystemModel.cs
@@ -1,3 +1,4 @@
+using System.Collections.ObjectModel;
using Aaru.CommonTypes.Interfaces;
using Aaru.Gui.ViewModels;
@@ -5,9 +6,12 @@ namespace Aaru.Gui.Models
{
public class FileSystemModel : RootModel
{
- public string VolumeName { get; set; }
- public IFilesystem Filesystem { get; set; }
- public IReadOnlyFilesystem ReadOnlyFilesystem { get; set; }
- public FileSystemViewModel ViewModel { get; set; }
+ public FileSystemModel() => Roots = new ObservableCollection();
+
+ public string VolumeName { get; set; }
+ public IFilesystem Filesystem { get; set; }
+ public IReadOnlyFilesystem ReadOnlyFilesystem { get; set; }
+ public FileSystemViewModel ViewModel { get; set; }
+ public ObservableCollection Roots { get; set; }
}
}
\ No newline at end of file
diff --git a/Aaru.Gui/Models/SubdirectoryModel.cs b/Aaru.Gui/Models/SubdirectoryModel.cs
new file mode 100644
index 000000000..30fbc939c
--- /dev/null
+++ b/Aaru.Gui/Models/SubdirectoryModel.cs
@@ -0,0 +1,16 @@
+using System.Collections.ObjectModel;
+using Aaru.CommonTypes.Interfaces;
+
+namespace Aaru.Gui.Models
+{
+ public class SubdirectoryModel
+ {
+ public SubdirectoryModel() => Subdirectories = new ObservableCollection();
+
+ public string Name { get; set; }
+ public string Path { get; set; }
+ public ObservableCollection Subdirectories { get; set; }
+ public IReadOnlyFilesystem Plugin { get; set; }
+ public bool Listed { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/Aaru.Gui/Panels/SubdirectoryPanel.xaml b/Aaru.Gui/Panels/SubdirectoryPanel.xaml
new file mode 100644
index 000000000..26264a097
--- /dev/null
+++ b/Aaru.Gui/Panels/SubdirectoryPanel.xaml
@@ -0,0 +1,33 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Aaru.Gui/Panels/SubdirectoryPanel.xaml.cs b/Aaru.Gui/Panels/SubdirectoryPanel.xaml.cs
new file mode 100644
index 000000000..5c71f18fe
--- /dev/null
+++ b/Aaru.Gui/Panels/SubdirectoryPanel.xaml.cs
@@ -0,0 +1,12 @@
+using Avalonia.Controls;
+using Avalonia.Markup.Xaml;
+
+namespace Aaru.Gui.Panels
+{
+ public class SubdirectoryPanel : UserControl
+ {
+ public SubdirectoryPanel() => InitializeComponent();
+
+ void InitializeComponent() => AvaloniaXamlLoader.Load(this);
+ }
+}
\ No newline at end of file
diff --git a/Aaru.Gui/Panels/pnlListFiles.xeto b/Aaru.Gui/Panels/pnlListFiles.xeto
deleted file mode 100644
index ca1431716..000000000
--- a/Aaru.Gui/Panels/pnlListFiles.xeto
+++ /dev/null
@@ -1,36 +0,0 @@
-
-
-
-
\ No newline at end of file
diff --git a/Aaru.Gui/Panels/pnlListFiles.xeto.cs b/Aaru.Gui/Panels/pnlListFiles.xeto.cs
deleted file mode 100644
index 3eeaa0d44..000000000
--- a/Aaru.Gui/Panels/pnlListFiles.xeto.cs
+++ /dev/null
@@ -1,552 +0,0 @@
-// /***************************************************************************
-// Aaru Data Preservation Suite
-// ----------------------------------------------------------------------------
-//
-// Filename : pnlListFiles.xeto.cs
-// Author(s) : Natalia Portillo
-//
-// Component : List files panel.
-//
-// --[ Description ] ----------------------------------------------------------
-//
-// Implements the list files panel.
-//
-// --[ License ] --------------------------------------------------------------
-//
-// This program is free software: you can redistribute it and/or modify
-// it under the terms of the GNU General public License as
-// published by the Free Software Foundation, either version 3 of the
-// License, or (at your option) any later version.
-//
-// This program is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-// GNU General public License for more details.
-//
-// You should have received a copy of the GNU General public License
-// along with this program. If not, see .
-//
-// ----------------------------------------------------------------------------
-// Copyright © 2011-2020 Natalia Portillo
-// ****************************************************************************/
-
-using System;
-using System.Collections.Generic;
-using System.Collections.ObjectModel;
-using System.IO;
-using System.Linq;
-using Aaru.CommonTypes.Interfaces;
-using Aaru.CommonTypes.Interop;
-using Aaru.CommonTypes.Structs;
-using Aaru.Core;
-using Eto.Forms;
-using Eto.Serialization.Xaml;
-
-namespace Aaru.Gui.Panels
-{
- // TODO: Resize columns
- // TODO: File icons?
- // TODO: Show xattrs
- public class pnlListFiles : Panel
- {
- readonly GridColumn accessColumn;
- readonly GridColumn attributesColumn;
- readonly GridColumn backupColumn;
- readonly GridColumn changedColumn;
- readonly GridColumn createdColumn;
- readonly ObservableCollection entries;
- readonly IReadOnlyFilesystem filesystem;
- readonly GridColumn gidColumn;
-
- readonly GridColumn inodeColumn;
- readonly GridColumn linksColumn;
- readonly GridColumn modeColumn;
- readonly GridColumn nameColumn;
- readonly ButtonMenuItem saveFilesMenuItem;
- readonly GridColumn sizeColumn;
- readonly GridColumn uidColumn;
- readonly GridColumn writeColumn;
- bool ascendingSort;
-
- #region XAML controls
- #pragma warning disable 169
- #pragma warning disable 649
- GridView grdFiles;
- #pragma warning restore 169
- #pragma warning restore 649
- #endregion
- GridColumn sortedColumn;
-
- public pnlListFiles(IReadOnlyFilesystem filesystem, Dictionary files, string parentPath)
- {
- this.filesystem = filesystem;
- XamlReader.Load(this);
-
- entries = new ObservableCollection();
-
- nameColumn = new GridColumn
- {
- DataCell = new TextBoxCell
- {
- Binding = Binding.Property(r => r.Name)
- },
- HeaderText = "Name", Sortable = true
- };
-
- sizeColumn = new GridColumn
- {
- DataCell = new TextBoxCell
- {
- Binding = Binding.Property(r => $"{r.Stat.Length}")
- },
- HeaderText = "Size", Sortable = true
- };
-
- createdColumn = new GridColumn
- {
- DataCell = new TextBoxCell
- {
- Binding = Binding.Property(r => r.Stat.CreationTime == default(DateTime) ? ""
- : $"{r.Stat.CreationTime:G}")
- },
- HeaderText = "Created", Sortable = true
- };
-
- accessColumn = new GridColumn
- {
- DataCell = new TextBoxCell
- {
- Binding = Binding.Property(r => r.Stat.AccessTime == default(DateTime) ? ""
- : $"{r.Stat.AccessTime:G}")
- },
- HeaderText = "Last access", Sortable = true
- };
-
- changedColumn = new GridColumn
- {
- DataCell = new TextBoxCell
- {
- Binding = Binding.Property(r => r.Stat.StatusChangeTime == default(DateTime)
- ? "" : $"{r.Stat.StatusChangeTime:G}")
- },
- HeaderText = "Changed", Sortable = true
- };
-
- backupColumn = new GridColumn
- {
- DataCell = new TextBoxCell
- {
- Binding = Binding.Property(r => r.Stat.BackupTime == default(DateTime) ? ""
- : $"{r.Stat.BackupTime:G}")
- },
- HeaderText = "Last backup", Sortable = true
- };
-
- writeColumn = new GridColumn
- {
- DataCell = new TextBoxCell
- {
- Binding = Binding.Property(r => r.Stat.LastWriteTime == default(DateTime) ? ""
- : $"{r.Stat.LastWriteTime:G}")
- },
- HeaderText = "Last write", Sortable = true
- };
-
- attributesColumn = new GridColumn
- {
- DataCell = new TextBoxCell
- {
- Binding = Binding.Property(r => $"{r.Stat.Attributes}")
- },
- HeaderText = "Attributes", Sortable = true
- };
-
- gidColumn = new GridColumn
- {
- DataCell = new TextBoxCell
- {
- Binding = Binding.Property(r => $"{r.Stat.GID}")
- },
- HeaderText = "GID", Sortable = true
- };
-
- uidColumn = new GridColumn
- {
- DataCell = new TextBoxCell
- {
- Binding = Binding.Property(r => $"{r.Stat.UID}")
- },
- HeaderText = "UID", Sortable = true
- };
-
- inodeColumn = new GridColumn
- {
- DataCell = new TextBoxCell
- {
- Binding = Binding.Property(r => $"{r.Stat.Inode}")
- },
- HeaderText = "Inode", Sortable = true
- };
-
- linksColumn = new GridColumn
- {
- DataCell = new TextBoxCell
- {
- Binding = Binding.Property(r => $"{r.Stat.Links}")
- },
- HeaderText = "Links", Sortable = true
- };
-
- modeColumn = new GridColumn
- {
- DataCell = new TextBoxCell
- {
- Binding = Binding.Property(r => $"{r.Stat.Mode}")
- },
- HeaderText = "Mode", Sortable = true
- };
-
- grdFiles.Columns.Add(nameColumn);
- grdFiles.Columns.Add(sizeColumn);
- grdFiles.Columns.Add(createdColumn);
- grdFiles.Columns.Add(accessColumn);
- grdFiles.Columns.Add(changedColumn);
- grdFiles.Columns.Add(backupColumn);
- grdFiles.Columns.Add(writeColumn);
- grdFiles.Columns.Add(attributesColumn);
- grdFiles.Columns.Add(gidColumn);
- grdFiles.Columns.Add(uidColumn);
- grdFiles.Columns.Add(inodeColumn);
- grdFiles.Columns.Add(linksColumn);
- grdFiles.Columns.Add(modeColumn);
-
- grdFiles.AllowColumnReordering = true;
- grdFiles.AllowDrop = false;
- grdFiles.AllowMultipleSelection = true;
- grdFiles.ShowHeader = true;
-
- foreach(KeyValuePair file in files)
- entries.Add(new EntryForGrid
- {
- Name = file.Key, Stat = file.Value, ParentPath = parentPath
- });
-
- grdFiles.DataStore = entries;
- sortedColumn = null;
- grdFiles.ColumnHeaderClick += OnGrdFilesOnColumnHeaderClick;
- ascendingSort = true;
-
- grdFiles.ContextMenu = new ContextMenu();
-
- saveFilesMenuItem = new ButtonMenuItem
- {
- Text = "Extract to...", Enabled = false
- };
-
- saveFilesMenuItem.Click += OnSaveFilesMenuItemClick;
-
- grdFiles.ContextMenu.Items.Add(saveFilesMenuItem);
-
- grdFiles.SelectionChanged += OnGrdFilesSelectionChanged;
- }
-
- void OnGrdFilesSelectionChanged(object sender, EventArgs e) =>
- saveFilesMenuItem.Enabled = grdFiles.SelectedItems.Any();
-
- void OnSaveFilesMenuItemClick(object sender, EventArgs e)
- {
- if(!grdFiles.SelectedItems.Any())
- return;
-
- var saveFilesFolderDialog = new SelectFolderDialog
- {
- Title = "Choose destination folder..."
- };
-
- DialogResult result = saveFilesFolderDialog.ShowDialog(this);
-
- if(result != DialogResult.Ok)
- return;
-
- Statistics.AddCommand("extract-files");
-
- string folder = saveFilesFolderDialog.Directory;
-
- foreach(EntryForGrid file in grdFiles.SelectedItems)
- {
- string filename = file.Name;
-
- if(DetectOS.IsWindows)
- if(filename.Contains('<') ||
- filename.Contains('>') ||
- filename.Contains(':') ||
- filename.Contains('\\') ||
- filename.Contains('/') ||
- filename.Contains('|') ||
- filename.Contains('?') ||
- filename.Contains('*') ||
- filename.Any(c => c < 32) ||
- filename.ToUpperInvariant() == "CON" ||
- filename.ToUpperInvariant() == "PRN" ||
- filename.ToUpperInvariant() == "AUX" ||
- filename.ToUpperInvariant() == "COM1" ||
- filename.ToUpperInvariant() == "COM2" ||
- filename.ToUpperInvariant() == "COM3" ||
- filename.ToUpperInvariant() == "COM4" ||
- filename.ToUpperInvariant() == "COM5" ||
- filename.ToUpperInvariant() == "COM6" ||
- filename.ToUpperInvariant() == "COM7" ||
- filename.ToUpperInvariant() == "COM8" ||
- filename.ToUpperInvariant() == "COM9" ||
- filename.ToUpperInvariant() == "LPT1" ||
- filename.ToUpperInvariant() == "LPT2" ||
- filename.ToUpperInvariant() == "LPT3" ||
- filename.ToUpperInvariant() == "LPT4" ||
- filename.ToUpperInvariant() == "LPT5" ||
- filename.ToUpperInvariant() == "LPT6" ||
- filename.ToUpperInvariant() == "LPT7" ||
- filename.ToUpperInvariant() == "LPT8" ||
- filename.ToUpperInvariant() == "LPT9" ||
- filename.Last() == '.' ||
- filename.Last() == ' ')
- {
- char[] chars;
-
- if(filename.Last() == '.' ||
- filename.Last() == ' ')
- chars = new char[filename.Length - 1];
- else
- chars = new char[filename.Length];
-
- for(int ci = 0; ci < chars.Length; ci++)
- switch(filename[ci])
- {
- case '<':
- case '>':
- case ':':
- case '\\':
- case '/':
- case '|':
- case '?':
- case '*':
- case '\u0000':
- case '\u0001':
- case '\u0002':
- case '\u0003':
- case '\u0004':
- case '\u0005':
- case '\u0006':
- case '\u0007':
- case '\u0008':
- case '\u0009':
- case '\u000A':
- case '\u000B':
- case '\u000C':
- case '\u000D':
- case '\u000E':
- case '\u000F':
- case '\u0010':
- case '\u0011':
- case '\u0012':
- case '\u0013':
- case '\u0014':
- case '\u0015':
- case '\u0016':
- case '\u0017':
- case '\u0018':
- case '\u0019':
- case '\u001A':
- case '\u001B':
- case '\u001C':
- case '\u001D':
- case '\u001E':
- case '\u001F':
- chars[ci] = '_';
-
- break;
- default:
- chars[ci] = filename[ci];
-
- break;
- }
-
- if(filename.StartsWith("CON", StringComparison.InvariantCultureIgnoreCase) ||
- filename.StartsWith("PRN", StringComparison.InvariantCultureIgnoreCase) ||
- filename.StartsWith("AUX", StringComparison.InvariantCultureIgnoreCase) ||
- filename.StartsWith("COM", StringComparison.InvariantCultureIgnoreCase) ||
- filename.StartsWith("LPT", StringComparison.InvariantCultureIgnoreCase))
- {
- chars[0] = '_';
- chars[1] = '_';
- chars[2] = '_';
- }
-
- string corrected = new string(chars);
-
- result = Eto.Forms.MessageBox.Show(this, "Unsupported filename",
- $"The file name {filename} is not supported on this platform.\nDo you want to rename it to {corrected}?",
- MessageBoxButtons.YesNoCancel, MessageBoxType.Warning);
-
- if(result == DialogResult.Cancel)
- return;
-
- if(result == DialogResult.No)
- continue;
-
- filename = corrected;
- }
-
- string outputPath = Path.Combine(folder, filename);
-
- if(File.Exists(outputPath))
- {
- result = Eto.Forms.MessageBox.Show(this, "Existing file",
- $"A file named {filename} already exists on the destination folder.\nDo you want to overwrite it?",
- MessageBoxButtons.YesNoCancel, MessageBoxType.Question);
-
- if(result == DialogResult.Cancel)
- return;
-
- if(result == DialogResult.No)
- continue;
-
- try
- {
- File.Delete(outputPath);
- }
- catch(IOException)
- {
- result = Eto.Forms.MessageBox.Show(this, "Cannot delete",
- "Could not delete existing file.\nDo you want to continue?",
- MessageBoxButtons.YesNo, MessageBoxType.Warning);
-
- if(result == DialogResult.No)
- return;
- }
- }
-
- try
- {
- byte[] outBuf = new byte[0];
-
- Errno error = filesystem.Read(file.ParentPath + file.Name, 0, file.Stat.Length, ref outBuf);
-
- if(error != Errno.NoError)
- {
- result = Eto.Forms.MessageBox.Show(this, "Error reading file",
- $"Error {error} reading file.\nDo you want to continue?",
- MessageBoxButtons.YesNo, MessageBoxType.Warning);
-
- if(result == DialogResult.No)
- return;
-
- continue;
- }
-
- var fs = new FileStream(outputPath, FileMode.CreateNew, FileAccess.ReadWrite, FileShare.None);
-
- fs.Write(outBuf, 0, outBuf.Length);
- fs.Close();
- var fi = new FileInfo(outputPath);
- #pragma warning disable RECS0022 // A catch clause that catches System.Exception and has an empty body
- try
- {
- if(file.Stat.CreationTimeUtc.HasValue)
- fi.CreationTimeUtc = file.Stat.CreationTimeUtc.Value;
- }
- catch
- {
- // ignored
- }
-
- try
- {
- if(file.Stat.LastWriteTimeUtc.HasValue)
- fi.LastWriteTimeUtc = file.Stat.LastWriteTimeUtc.Value;
- }
- catch
- {
- // ignored
- }
-
- try
- {
- if(file.Stat.AccessTimeUtc.HasValue)
- fi.LastAccessTimeUtc = file.Stat.AccessTimeUtc.Value;
- }
- catch
- {
- // ignored
- }
- #pragma warning restore RECS0022 // A catch clause that catches System.Exception and has an empty body
- }
- catch(IOException)
- {
- result = Eto.Forms.MessageBox.Show(this, "Cannot create file",
- "Could not create destination file.\nDo you want to continue?",
- MessageBoxButtons.YesNo, MessageBoxType.Warning);
-
- if(result == DialogResult.No)
- return;
- }
- }
- }
-
- void OnGrdFilesOnColumnHeaderClick(object sender, GridColumnEventArgs gridColumnEventArgs)
- {
- if(sortedColumn == gridColumnEventArgs.Column)
- ascendingSort = !ascendingSort;
- else
- ascendingSort = true;
-
- sortedColumn = gridColumnEventArgs.Column;
-
- if(sortedColumn == nameColumn)
- grdFiles.DataStore = ascendingSort ? entries.OrderBy(t => t.Name)
- : entries.OrderByDescending(t => t.Name);
- else if(sortedColumn == sizeColumn)
- grdFiles.DataStore = ascendingSort ? entries.OrderBy(t => t.Stat.Length)
- : entries.OrderByDescending(t => t.Stat.Length);
- else if(sortedColumn == createdColumn)
- grdFiles.DataStore = ascendingSort ? entries.OrderBy(t => t.Stat.CreationTime)
- : entries.OrderByDescending(t => t.Stat.CreationTime);
- else if(sortedColumn == accessColumn)
- grdFiles.DataStore = ascendingSort ? entries.OrderBy(t => t.Stat.AccessTime)
- : entries.OrderByDescending(t => t.Stat.AccessTime);
- else if(sortedColumn == changedColumn)
- grdFiles.DataStore = ascendingSort ? entries.OrderBy(t => t.Stat.StatusChangeTime)
- : entries.OrderByDescending(t => t.Stat.StatusChangeTime);
- else if(sortedColumn == backupColumn)
- grdFiles.DataStore = ascendingSort ? entries.OrderBy(t => t.Stat.BackupTime)
- : entries.OrderByDescending(t => t.Stat.BackupTime);
- else if(sortedColumn == writeColumn)
- grdFiles.DataStore = ascendingSort ? entries.OrderBy(t => t.Stat.LastWriteTime)
- : entries.OrderByDescending(t => t.Stat.LastWriteTime);
- else if(sortedColumn == attributesColumn)
- grdFiles.DataStore = ascendingSort ? entries.OrderBy(t => t.Stat.Attributes)
- : entries.OrderByDescending(t => t.Stat.Attributes);
- else if(sortedColumn == gidColumn)
- grdFiles.DataStore = ascendingSort ? entries.OrderBy(t => t.Stat.GID)
- : entries.OrderByDescending(t => t.Stat.GID);
- else if(sortedColumn == uidColumn)
- grdFiles.DataStore = ascendingSort ? entries.OrderBy(t => t.Stat.UID)
- : entries.OrderByDescending(t => t.Stat.UID);
- else if(sortedColumn == inodeColumn)
- grdFiles.DataStore = ascendingSort ? entries.OrderBy(t => t.Stat.Inode)
- : entries.OrderByDescending(t => t.Stat.Inode);
- else if(sortedColumn == linksColumn)
- grdFiles.DataStore = ascendingSort ? entries.OrderBy(t => t.Stat.Links)
- : entries.OrderByDescending(t => t.Stat.Links);
- else if(sortedColumn == modeColumn)
- grdFiles.DataStore = ascendingSort ? entries.OrderBy(t => t.Stat.Mode)
- : entries.OrderByDescending(t => t.Stat.Mode);
- }
-
- class EntryForGrid
- {
- public string ParentPath;
- public FileEntryInfo Stat;
- public string Name { get; set; }
- }
- }
-}
\ No newline at end of file
diff --git a/Aaru.Gui/ViewModels/MainWindowViewModel.cs b/Aaru.Gui/ViewModels/MainWindowViewModel.cs
index ebd4c997d..5ebbf7431 100644
--- a/Aaru.Gui/ViewModels/MainWindowViewModel.cs
+++ b/Aaru.Gui/ViewModels/MainWindowViewModel.cs
@@ -160,6 +160,14 @@ namespace Aaru.Gui.ViewModels
DataContext = fileSystemModel.ViewModel
};
+ if(value is SubdirectoryModel subdirectoryModel)
+ {
+ ContentPanel = new SubdirectoryPanel
+ {
+ DataContext = new SubdirectoryViewModel(subdirectoryModel, _view)
+ };
+ }
+
this.RaiseAndSetIfChanged(ref _treeViewSelectedItem, value);
}
}
@@ -474,10 +482,16 @@ namespace Aaru.Gui.ViewModels
ViewModel = new FileSystemViewModel(plugin.XmlFsType, information)
};
- /* TODO: Trap expanding item
+ // TODO: Trap expanding item
if(fsPlugin != null)
+ {
+ filesystemModel.Roots.Add(new SubdirectoryModel
+ {
+ Name = "/", Path = "", Plugin = fsPlugin
+ });
+
Statistics.AddCommand("ls");
- */
+ }
Statistics.AddFilesystem(plugin.XmlFsType.Type);
partitionModel.FileSystems.Add(filesystemModel);
@@ -531,10 +545,16 @@ namespace Aaru.Gui.ViewModels
ViewModel = new FileSystemViewModel(plugin.XmlFsType, information)
};
- /* TODO: Trap expanding item
+ // TODO: Trap expanding item
if(fsPlugin != null)
+ {
+ filesystemModel.Roots.Add(new SubdirectoryModel
+ {
+ Name = "/", Path = "", Plugin = fsPlugin
+ });
+
Statistics.AddCommand("ls");
- */
+ }
Statistics.AddFilesystem(plugin.XmlFsType.Type);
imageModel.PartitionSchemesOrFileSystems.Add(filesystemModel);
diff --git a/Aaru.Gui/ViewModels/SubdirectoryViewModel.cs b/Aaru.Gui/ViewModels/SubdirectoryViewModel.cs
new file mode 100644
index 000000000..5da889f95
--- /dev/null
+++ b/Aaru.Gui/ViewModels/SubdirectoryViewModel.cs
@@ -0,0 +1,326 @@
+using System;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.IO;
+using System.Linq;
+using System.Reactive;
+using Aaru.CommonTypes.Interop;
+using Aaru.CommonTypes.Structs;
+using Aaru.Console;
+using Aaru.Core;
+using Aaru.Gui.Models;
+using Avalonia.Controls;
+using MessageBox.Avalonia;
+using MessageBox.Avalonia.Enums;
+using ReactiveUI;
+using FileAttributes = Aaru.CommonTypes.Structs.FileAttributes;
+
+namespace Aaru.Gui.ViewModels
+{
+ public class SubdirectoryViewModel
+ {
+ readonly SubdirectoryModel _model;
+ readonly Window _view;
+
+ public SubdirectoryViewModel(SubdirectoryModel model, Window view)
+ {
+ Entries = new ObservableCollection();
+ SelectedEntries = new List();
+ ExtractFilesCommand = ReactiveCommand.Create(ExecuteExtractFilesCommand);
+ _model = model;
+ _view = view;
+
+ Errno errno = model.Plugin.ReadDir(model.Path, out List dirents);
+
+ if(errno != Errno.NoError)
+ {
+ MessageBoxManager.
+ GetMessageBoxStandardWindow("Error",
+ $"Error {errno} trying to read \"{model.Path}\" of chosen filesystem",
+ ButtonEnum.Ok, Icon.Error).ShowDialog(view);
+
+ return;
+ }
+
+ foreach(string dirent in dirents)
+ {
+ errno = model.Plugin.Stat(model.Path + "/" + dirent, out FileEntryInfo stat);
+
+ if(errno != Errno.NoError)
+ {
+ AaruConsole.
+ ErrorWriteLine($"Error {errno} trying to get information about filesystem entry named {dirent}");
+
+ continue;
+ }
+
+ if(stat.Attributes.HasFlag(FileAttributes.Directory) &&
+ !model.Listed)
+ {
+ model.Subdirectories.Add(new SubdirectoryModel
+ {
+ Name = dirent, Path = model.Path + "/" + dirent, Plugin = model.Plugin
+ });
+
+ continue;
+ }
+
+ Entries.Add(new FileModel
+ {
+ Name = dirent, Stat = stat
+ });
+ }
+ }
+
+ public ObservableCollection Entries { get; }
+ public List SelectedEntries { get; }
+ public ReactiveCommand ExtractFilesCommand { get; }
+
+ async void ExecuteExtractFilesCommand()
+ {
+ if(SelectedEntries.Count == 0)
+ return;
+
+ ButtonResult mboxResult;
+
+ var saveFilesFolderDialog = new OpenFolderDialog
+ {
+ Title = "Choose destination folder..."
+ };
+
+ string result = await saveFilesFolderDialog.ShowAsync(_view);
+
+ if(result is null)
+ return;
+
+ Statistics.AddCommand("extract-files");
+
+ string folder = saveFilesFolderDialog.Directory;
+
+ foreach(FileModel file in SelectedEntries)
+ {
+ string filename = file.Name;
+
+ if(DetectOS.IsWindows)
+ if(filename.Contains('<') ||
+ filename.Contains('>') ||
+ filename.Contains(':') ||
+ filename.Contains('\\') ||
+ filename.Contains('/') ||
+ filename.Contains('|') ||
+ filename.Contains('?') ||
+ filename.Contains('*') ||
+ filename.Any(c => c < 32) ||
+ filename.ToUpperInvariant() == "CON" ||
+ filename.ToUpperInvariant() == "PRN" ||
+ filename.ToUpperInvariant() == "AUX" ||
+ filename.ToUpperInvariant() == "COM1" ||
+ filename.ToUpperInvariant() == "COM2" ||
+ filename.ToUpperInvariant() == "COM3" ||
+ filename.ToUpperInvariant() == "COM4" ||
+ filename.ToUpperInvariant() == "COM5" ||
+ filename.ToUpperInvariant() == "COM6" ||
+ filename.ToUpperInvariant() == "COM7" ||
+ filename.ToUpperInvariant() == "COM8" ||
+ filename.ToUpperInvariant() == "COM9" ||
+ filename.ToUpperInvariant() == "LPT1" ||
+ filename.ToUpperInvariant() == "LPT2" ||
+ filename.ToUpperInvariant() == "LPT3" ||
+ filename.ToUpperInvariant() == "LPT4" ||
+ filename.ToUpperInvariant() == "LPT5" ||
+ filename.ToUpperInvariant() == "LPT6" ||
+ filename.ToUpperInvariant() == "LPT7" ||
+ filename.ToUpperInvariant() == "LPT8" ||
+ filename.ToUpperInvariant() == "LPT9" ||
+ filename.Last() == '.' ||
+ filename.Last() == ' ')
+ {
+ char[] chars;
+
+ if(filename.Last() == '.' ||
+ filename.Last() == ' ')
+ chars = new char[filename.Length - 1];
+ else
+ chars = new char[filename.Length];
+
+ for(int ci = 0; ci < chars.Length; ci++)
+ switch(filename[ci])
+ {
+ case '<':
+ case '>':
+ case ':':
+ case '\\':
+ case '/':
+ case '|':
+ case '?':
+ case '*':
+ case '\u0000':
+ case '\u0001':
+ case '\u0002':
+ case '\u0003':
+ case '\u0004':
+ case '\u0005':
+ case '\u0006':
+ case '\u0007':
+ case '\u0008':
+ case '\u0009':
+ case '\u000A':
+ case '\u000B':
+ case '\u000C':
+ case '\u000D':
+ case '\u000E':
+ case '\u000F':
+ case '\u0010':
+ case '\u0011':
+ case '\u0012':
+ case '\u0013':
+ case '\u0014':
+ case '\u0015':
+ case '\u0016':
+ case '\u0017':
+ case '\u0018':
+ case '\u0019':
+ case '\u001A':
+ case '\u001B':
+ case '\u001C':
+ case '\u001D':
+ case '\u001E':
+ case '\u001F':
+ chars[ci] = '_';
+
+ break;
+ default:
+ chars[ci] = filename[ci];
+
+ break;
+ }
+
+ if(filename.StartsWith("CON", StringComparison.InvariantCultureIgnoreCase) ||
+ filename.StartsWith("PRN", StringComparison.InvariantCultureIgnoreCase) ||
+ filename.StartsWith("AUX", StringComparison.InvariantCultureIgnoreCase) ||
+ filename.StartsWith("COM", StringComparison.InvariantCultureIgnoreCase) ||
+ filename.StartsWith("LPT", StringComparison.InvariantCultureIgnoreCase))
+ {
+ chars[0] = '_';
+ chars[1] = '_';
+ chars[2] = '_';
+ }
+
+ string corrected = new string(chars);
+
+ mboxResult = await MessageBoxManager.GetMessageBoxStandardWindow("Unsupported filename",
+ $"The file name {filename} is not supported on this platform.\nDo you want to rename it to {corrected}?",
+ ButtonEnum.YesNoCancel,
+ Icon.Warning).
+ ShowDialog(_view);
+
+ if(mboxResult == ButtonResult.Cancel)
+ return;
+
+ if(mboxResult == ButtonResult.No)
+ continue;
+
+ filename = corrected;
+ }
+
+ string outputPath = Path.Combine(folder, filename);
+
+ if(File.Exists(outputPath))
+ {
+ mboxResult = await MessageBoxManager.GetMessageBoxStandardWindow("Existing file",
+ $"A file named {filename} already exists on the destination folder.\nDo you want to overwrite it?",
+ ButtonEnum.YesNoCancel,
+ Icon.Warning).ShowDialog(_view);
+
+ if(mboxResult == ButtonResult.Cancel)
+ return;
+
+ if(mboxResult == ButtonResult.No)
+ continue;
+
+ try
+ {
+ File.Delete(outputPath);
+ }
+ catch(IOException)
+ {
+ mboxResult = await MessageBoxManager.GetMessageBoxStandardWindow("Cannot delete",
+ "Could not delete existing file.\nDo you want to continue?",
+ ButtonEnum.YesNo, Icon.Error).
+ ShowDialog(_view);
+
+ if(mboxResult == ButtonResult.No)
+ return;
+ }
+ }
+
+ try
+ {
+ byte[] outBuf = new byte[0];
+
+ Errno error = _model.Plugin.Read(_model.Path + "/" + file.Name, 0, file.Stat.Length, ref outBuf);
+
+ if(error != Errno.NoError)
+ {
+ mboxResult = await MessageBoxManager.GetMessageBoxStandardWindow("Error reading file",
+ $"Error {error} reading file.\nDo you want to continue?",
+ ButtonEnum.YesNo, Icon.Error).
+ ShowDialog(_view);
+
+ if(mboxResult == ButtonResult.No)
+ return;
+
+ continue;
+ }
+
+ var fs = new FileStream(outputPath, FileMode.CreateNew, FileAccess.ReadWrite, FileShare.None);
+
+ fs.Write(outBuf, 0, outBuf.Length);
+ fs.Close();
+ var fi = new FileInfo(outputPath);
+ #pragma warning disable RECS0022 // A catch clause that catches System.Exception and has an empty body
+ try
+ {
+ if(file.Stat.CreationTimeUtc.HasValue)
+ fi.CreationTimeUtc = file.Stat.CreationTimeUtc.Value;
+ }
+ catch
+ {
+ // ignored
+ }
+
+ try
+ {
+ if(file.Stat.LastWriteTimeUtc.HasValue)
+ fi.LastWriteTimeUtc = file.Stat.LastWriteTimeUtc.Value;
+ }
+ catch
+ {
+ // ignored
+ }
+
+ try
+ {
+ if(file.Stat.AccessTimeUtc.HasValue)
+ fi.LastAccessTimeUtc = file.Stat.AccessTimeUtc.Value;
+ }
+ catch
+ {
+ // ignored
+ }
+ #pragma warning restore RECS0022 // A catch clause that catches System.Exception and has an empty body
+ }
+ catch(IOException)
+ {
+ mboxResult = await MessageBoxManager.GetMessageBoxStandardWindow("Cannot create file",
+ "Could not create destination file.\nDo you want to continue?",
+ ButtonEnum.YesNo, Icon.Error).
+ ShowDialog(_view);
+
+ if(mboxResult == ButtonResult.No)
+ return;
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/Aaru.Gui/Views/MainWindow.xaml b/Aaru.Gui/Views/MainWindow.xaml
index fe72ad2a4..0d858d4fe 100644
--- a/Aaru.Gui/Views/MainWindow.xaml
+++ b/Aaru.Gui/Views/MainWindow.xaml
@@ -54,13 +54,27 @@
-
-
-
-
-
-
-
+
+
+
+
+
+
+
@@ -79,12 +93,18 @@
-
+
+
+
+
+
+
+
diff --git a/Aaru.sln.DotSettings b/Aaru.sln.DotSettings
index 25ca023e1..1c167c52e 100644
--- a/Aaru.sln.DotSettings
+++ b/Aaru.sln.DotSettings
@@ -212,6 +212,7 @@
True
True
True
+ True
True
True
True