2020-04-14 22:05:03 +01:00
|
|
|
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;
|
|
|
|
|
|
2020-04-16 20:40:25 +01:00
|
|
|
namespace Aaru.Gui.ViewModels.Panels
|
2020-04-14 22:05:03 +01:00
|
|
|
{
|
|
|
|
|
public class SubdirectoryViewModel
|
|
|
|
|
{
|
|
|
|
|
readonly SubdirectoryModel _model;
|
|
|
|
|
readonly Window _view;
|
|
|
|
|
|
|
|
|
|
public SubdirectoryViewModel(SubdirectoryModel model, Window view)
|
|
|
|
|
{
|
|
|
|
|
Entries = new ObservableCollection<FileModel>();
|
|
|
|
|
SelectedEntries = new List<FileModel>();
|
|
|
|
|
ExtractFilesCommand = ReactiveCommand.Create(ExecuteExtractFilesCommand);
|
|
|
|
|
_model = model;
|
|
|
|
|
_view = view;
|
|
|
|
|
|
|
|
|
|
Errno errno = model.Plugin.ReadDir(model.Path, out List<string> 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<FileModel> Entries { get; }
|
|
|
|
|
public List<FileModel> SelectedEntries { get; }
|
|
|
|
|
public ReactiveCommand<Unit, Unit> 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;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|