diff --git a/.idea/.idea.DiscImageChef/.idea/contentModel.xml b/.idea/.idea.DiscImageChef/.idea/contentModel.xml index 89977601f..7fbf5a66e 100644 --- a/.idea/.idea.DiscImageChef/.idea/contentModel.xml +++ b/.idea/.idea.DiscImageChef/.idea/contentModel.xml @@ -1641,6 +1641,8 @@ + + diff --git a/DiscImageChef.Gui/Forms/frmMain.xeto.cs b/DiscImageChef.Gui/Forms/frmMain.xeto.cs index b6b363243..0f962de37 100644 --- a/DiscImageChef.Gui/Forms/frmMain.xeto.cs +++ b/DiscImageChef.Gui/Forms/frmMain.xeto.cs @@ -37,6 +37,7 @@ using System.IO; using System.Linq; using DiscImageChef.CommonTypes; using DiscImageChef.CommonTypes.Interfaces; +using DiscImageChef.CommonTypes.Structs; using DiscImageChef.Console; using DiscImageChef.Core; using DiscImageChef.Core.Media.Info; @@ -47,6 +48,7 @@ using DiscImageChef.Gui.Panels; using Eto.Drawing; using Eto.Forms; using Eto.Serialization.Xaml; +using FileAttributes = DiscImageChef.CommonTypes.Structs.FileAttributes; using ImageFormat = DiscImageChef.Core.ImageFormat; namespace DiscImageChef.Gui.Forms @@ -252,6 +254,17 @@ namespace DiscImageChef.Gui.Forms { plugin.GetInformation(imageFormat, partition, out string information, null); + IReadOnlyFilesystem fsPlugin = plugin as IReadOnlyFilesystem; + + if(fsPlugin != null) + { + Errno error = + fsPlugin.Mount(imageFormat, partition, null, + new Dictionary()); + + if(error != Errno.NoError) fsPlugin = null; + } + TreeGridItem filesystemGridItem = new TreeGridItem { Values = new object[] @@ -260,10 +273,16 @@ namespace DiscImageChef.Gui.Forms plugin.XmlFsType.VolumeName is null ? $"{plugin.XmlFsType.Type}" : $"{plugin.XmlFsType.VolumeName} ({plugin.XmlFsType.Type})", - null, new pnlFilesystem(plugin.XmlFsType, information) + fsPlugin, new pnlFilesystem(plugin.XmlFsType, information) } }; + if(fsPlugin != null) + { + Core.Statistics.AddCommand("ls"); + filesystemGridItem.Children.Add(placeholderItem); + } + Statistics.AddFilesystem(plugin.XmlFsType.Type); partitionGridItem.Children.Add(filesystemGridItem); } @@ -296,6 +315,17 @@ namespace DiscImageChef.Gui.Forms { plugin.GetInformation(imageFormat, wholePart, out string information, null); + IReadOnlyFilesystem fsPlugin = plugin as IReadOnlyFilesystem; + + if(fsPlugin != null) + { + Errno error = + fsPlugin.Mount(imageFormat, wholePart, null, + new Dictionary()); + + if(error != Errno.NoError) fsPlugin = null; + } + TreeGridItem filesystemGridItem = new TreeGridItem { Values = new object[] @@ -304,10 +334,16 @@ namespace DiscImageChef.Gui.Forms plugin.XmlFsType.VolumeName is null ? $"{plugin.XmlFsType.Type}" : $"{plugin.XmlFsType.VolumeName} ({plugin.XmlFsType.Type})", - null, new pnlFilesystem(plugin.XmlFsType, information) + fsPlugin, new pnlFilesystem(plugin.XmlFsType, information) } }; + if(fsPlugin != null) + { + Core.Statistics.AddCommand("ls"); + filesystemGridItem.Children.Add(placeholderItem); + } + Statistics.AddFilesystem(plugin.XmlFsType.Type); imageGridItem.Children.Add(filesystemGridItem); } @@ -503,11 +539,11 @@ namespace DiscImageChef.Gui.Forms return; } - if(selectedItem.Parent != devicesRoot) return; + if(selectedItem.Values.Length < 4) return; switch(selectedItem.Values[3]) { - case null: + case null when selectedItem.Parent == devicesRoot: try { Device dev = new Device((string)selectedItem.Values[2]); @@ -533,19 +569,26 @@ namespace DiscImageChef.Gui.Forms } break; - case string devErrorMessage: + case string devErrorMessage when selectedItem.Parent == devicesRoot: 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; } } protected void OnTreeImagesItemExpanding(object sender, TreeGridViewItemCancelEventArgs e) { // First expansion of a device - if((e.Item as TreeGridItem)?.Children?.Count == 1 && - ((TreeGridItem)e.Item).Children[0] == placeholderItem && - ((TreeGridItem)e.Item).Parent == devicesRoot) + if((e.Item as TreeGridItem)?.Children?.Count != 1 || + ((TreeGridItem)e.Item).Children[0] != placeholderItem) return; + + if(((TreeGridItem)e.Item).Parent == devicesRoot) { TreeGridItem deviceItem = (TreeGridItem)e.Item; @@ -613,6 +656,104 @@ namespace DiscImageChef.Gui.Forms dev.Close(); } + else if(((TreeGridItem)e.Item).Values[2] is IReadOnlyFilesystem fsPlugin) + { + TreeGridItem fsItem = (TreeGridItem)e.Item; + + fsItem.Children.Clear(); + + if(fsItem.Values.Length == 5 && fsItem.Values[4] is string dirPath) + { + Errno errno = fsPlugin.ReadDir(dirPath, out List dirents); + + if(errno != Errno.NoError) + { + MessageBox.Show($"Error {errno} trying to read \"{dirPath}\" of chosen filesystem", + MessageBoxType.Error); + return; + } + + Dictionary files = new Dictionary(); + List directories = new List(); + + foreach(string dirent in dirents) + { + errno = fsPlugin.Stat(dirPath + "/" + dirent, out FileEntryInfo stat); + + if(errno != Errno.NoError) + { + DicConsole + .ErrorWriteLine($"Error {errno} trying to get information about filesystem entry named {dirent}"); + continue; + } + + if(stat.Attributes.HasFlag(FileAttributes.Directory)) directories.Add(dirent); + else files.Add(dirent, stat); + } + + foreach(string directory in directories) + { + TreeGridItem dirItem = new TreeGridItem + { + Values = new object[] {imagesIcon, directory, fsPlugin, null, dirPath + "/" + directory} + }; + + dirItem.Children.Add(placeholderItem); + fsItem.Children.Add(dirItem); + } + } + else + { + Errno errno = fsPlugin.ReadDir("/", out List dirents); + + if(errno != Errno.NoError) + { + MessageBox.Show($"Error {errno} trying to read root directory of chosen filesystem", + MessageBoxType.Error); + return; + } + + Dictionary files = new Dictionary(); + List directories = new List(); + + foreach(string dirent in dirents) + { + errno = fsPlugin.Stat("/" + dirent, out FileEntryInfo stat); + + if(errno != Errno.NoError) + { + DicConsole + .ErrorWriteLine($"Error {errno} trying to get information about filesystem entry named {dirent}"); + continue; + } + + if(stat.Attributes.HasFlag(FileAttributes.Directory)) directories.Add(dirent); + else files.Add(dirent, stat); + } + + TreeGridItem rootDirectoryItem = new TreeGridItem + { + Values = new object[] + { + nullImage, // TODO: Get icon from volume + "/", fsPlugin, files + } + }; + + foreach(string directory in directories) + { + TreeGridItem dirItem = new TreeGridItem + { + Values = new object[] {imagesIcon, directory, fsPlugin, null, "/" + directory} + }; + + dirItem.Children.Add(placeholderItem); + rootDirectoryItem.Children.Add(dirItem); + } + + fsItem.Children.Add(rootDirectoryItem); + } + } } #region XAML IDs diff --git a/DiscImageChef.Gui/Panels/pnlListFiles.xeto b/DiscImageChef.Gui/Panels/pnlListFiles.xeto new file mode 100644 index 000000000..cf56d312d --- /dev/null +++ b/DiscImageChef.Gui/Panels/pnlListFiles.xeto @@ -0,0 +1,36 @@ + + + + \ No newline at end of file diff --git a/DiscImageChef.Gui/Panels/pnlListFiles.xeto.cs b/DiscImageChef.Gui/Panels/pnlListFiles.xeto.cs new file mode 100644 index 000000000..a90547ed3 --- /dev/null +++ b/DiscImageChef.Gui/Panels/pnlListFiles.xeto.cs @@ -0,0 +1,298 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : pnlListFiles.xeto.cs +// Author(s) : Natalia Portillo +// +// Component : List files. +// +// --[ 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-2018 Natalia Portillo +// ****************************************************************************/ + +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Linq; +using DiscImageChef.CommonTypes.Interfaces; +using DiscImageChef.CommonTypes.Structs; +using Eto.Forms; +using Eto.Serialization.Xaml; + +namespace DiscImageChef.Gui.Panels +{ + // TODO: Resize columns + // TODO: File icons? + // TODO: Show xattrs + public class pnlListFiles : Panel + { + GridColumn accessColumn; + bool ascendingSort; + GridColumn attributesColumn; + GridColumn backupColumn; + GridColumn changedColumn; + GridColumn createdColumn; + ObservableCollection entries; + IReadOnlyFilesystem filesystem; + GridColumn gidColumn; + + #region XAML controls + #pragma warning disable 169 + #pragma warning disable 649 + GridView grdFiles; + #pragma warning restore 169 + #pragma warning restore 649 + #endregion + + GridColumn inodeColumn; + GridColumn linksColumn; + GridColumn modeColumn; + GridColumn nameColumn; + GridColumn sizeColumn; + GridColumn sortedColumn; + GridColumn uidColumn; + GridColumn writeColumn; + + 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; + } + + 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