mirror of
https://github.com/oMaN-Rod/nxDumpFuse.git
synced 2024-11-22 18:26:40 +00:00
Add speed, progress, and clean up Fuse
This commit is contained in:
parent
dbfff7fabe
commit
a13cf7c091
8 changed files with 251 additions and 175 deletions
16
src/nxDumpFuse/Extensions/ListExtensions.cs
Normal file
16
src/nxDumpFuse/Extensions/ListExtensions.cs
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
|
namespace nxDumpFuse.Extensions
|
||||||
|
{
|
||||||
|
public static class ListExtensions
|
||||||
|
{
|
||||||
|
public static long GetOutputFileSize(this List<string> files)
|
||||||
|
{
|
||||||
|
long totalFileSize = 0;
|
||||||
|
files.Select(f => f).ToList().ForEach(f => totalFileSize += new FileInfo(f).Length);
|
||||||
|
return totalFileSize;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
16
src/nxDumpFuse/Extensions/LongExtensions.cs
Normal file
16
src/nxDumpFuse/Extensions/LongExtensions.cs
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
namespace nxDumpFuse.Extensions
|
||||||
|
{
|
||||||
|
public static class LongExtensions
|
||||||
|
{
|
||||||
|
public static long ToMb(this long bytes)
|
||||||
|
{
|
||||||
|
return bytes / (1024 * 1024);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static long ToSeconds(this long milliseconds)
|
||||||
|
{
|
||||||
|
return milliseconds / 1000;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
95
src/nxDumpFuse/Extensions/StringExtensions.cs
Normal file
95
src/nxDumpFuse/Extensions/StringExtensions.cs
Normal file
|
@ -0,0 +1,95 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using nxDumpFuse.Model.Enums;
|
||||||
|
|
||||||
|
namespace nxDumpFuse.Extensions
|
||||||
|
{
|
||||||
|
public static class StringExtensions
|
||||||
|
{
|
||||||
|
public static List<string> GetInputFiles(this string inputFilePath, FileCase fileCase)
|
||||||
|
{
|
||||||
|
var inputDir = Path.GetDirectoryName(inputFilePath);
|
||||||
|
if (string.IsNullOrEmpty(inputDir)) return new List<string>();
|
||||||
|
var files = new List<string>();
|
||||||
|
switch (fileCase)
|
||||||
|
{
|
||||||
|
case FileCase.XciNumeric: // .xci.00
|
||||||
|
case FileCase.NspNumeric: // .nsp.00
|
||||||
|
files = Directory.GetFiles(inputDir)
|
||||||
|
.Where(f => int.TryParse(Path.GetExtension(f).Replace(".", ""), out _))
|
||||||
|
.ToList();
|
||||||
|
break;
|
||||||
|
case FileCase.Xci: // .xc0
|
||||||
|
case FileCase.Nsp: // .ns0
|
||||||
|
files = Directory.GetFiles(inputDir, $"{Path.GetFileNameWithoutExtension(inputFilePath)}*")
|
||||||
|
.ToList();
|
||||||
|
break;
|
||||||
|
case FileCase.Numeric: // dir/00
|
||||||
|
files = Directory.GetFiles(inputDir)
|
||||||
|
.Where(f => int.TryParse(Path.GetFileName(f), out _))
|
||||||
|
.ToList();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
files.Sort();
|
||||||
|
return files;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Tuple<string,FileCase> GetOutputFilePath(this string inputFilePath, string outputDir)
|
||||||
|
{
|
||||||
|
var fileName = Path.GetFileName(inputFilePath);
|
||||||
|
string outputFilePath;
|
||||||
|
const string xciExt = "xci";
|
||||||
|
const string nspExt = "nsp";
|
||||||
|
|
||||||
|
if (Path.HasExtension(fileName))
|
||||||
|
{
|
||||||
|
var ext = Path.GetExtension(fileName).Replace(".", string.Empty);
|
||||||
|
var split = fileName.Split(".").ToList();
|
||||||
|
|
||||||
|
if (int.TryParse(ext, out _) && split.Count >= 3 && split[^2] == xciExt) // .xci.00
|
||||||
|
{
|
||||||
|
outputFilePath = Path.Join(outputDir, $"{string.Join("", split.Take(split.Count - 2))}.{xciExt}");
|
||||||
|
return new Tuple<string, FileCase>(outputFilePath, FileCase.XciNumeric);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (int.TryParse(ext, out _) && split.Count >= 3 && split[^2] == nspExt) // .nsp.00
|
||||||
|
{
|
||||||
|
outputFilePath = Path.Join(outputDir, $"{string.Join("", split.Take(split.Count - 2))}.{nspExt}");
|
||||||
|
return new Tuple<string, FileCase>(outputFilePath, FileCase.NspNumeric);
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (ext[..2])
|
||||||
|
{
|
||||||
|
// .xc0
|
||||||
|
case "xc" when int.TryParse(ext.Substring(ext.Length - 1, 1), out _):
|
||||||
|
outputFilePath = Path.Join(outputDir, $"{Path.GetFileNameWithoutExtension(fileName)}.{xciExt}");
|
||||||
|
return new Tuple<string, FileCase>(outputFilePath, FileCase.Xci);
|
||||||
|
// .ns0
|
||||||
|
case "ns" when int.TryParse(ext.Substring(ext.Length - 1, 1), out _):
|
||||||
|
outputFilePath = Path.Join(outputDir, $"{Path.GetFileNameWithoutExtension(fileName)}.{nspExt}");
|
||||||
|
return new Tuple<string, FileCase>(outputFilePath, FileCase.Nsp);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else // dir/00
|
||||||
|
{
|
||||||
|
var inputDir = new FileInfo(inputFilePath).Directory?.Name;
|
||||||
|
if (string.IsNullOrEmpty(inputDir))
|
||||||
|
{
|
||||||
|
inputDir = Path.GetPathRoot(inputFilePath);
|
||||||
|
outputFilePath = $"{inputDir}.{nspExt}";
|
||||||
|
return new Tuple<string, FileCase>(outputFilePath, FileCase.Numeric);
|
||||||
|
}
|
||||||
|
|
||||||
|
var inputDirSplit = inputDir.Split(".");
|
||||||
|
outputFilePath = Path.Join(outputDir, inputDirSplit.Length == 1
|
||||||
|
? $"{inputDir}.{nspExt}"
|
||||||
|
: $"{string.Join("", (inputDirSplit).Take(inputDirSplit.Length - 1))}.{nspExt}");
|
||||||
|
return new Tuple<string, FileCase>(outputFilePath, FileCase.Numeric);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Tuple<string, FileCase>(string.Empty, FileCase.Invalid);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -2,6 +2,7 @@
|
||||||
{
|
{
|
||||||
public enum FileCase
|
public enum FileCase
|
||||||
{
|
{
|
||||||
|
Invalid,
|
||||||
XciNumeric, // .xci.00
|
XciNumeric, // .xci.00
|
||||||
NspNumeric, // .nsp.00
|
NspNumeric, // .nsp.00
|
||||||
Xci, // .xc0
|
Xci, // .xc0
|
||||||
|
|
|
@ -2,23 +2,23 @@
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using nxDumpFuse.Events;
|
using nxDumpFuse.Events;
|
||||||
|
using nxDumpFuse.Extensions;
|
||||||
using nxDumpFuse.Model.Enums;
|
using nxDumpFuse.Model.Enums;
|
||||||
|
|
||||||
namespace nxDumpFuse.Model
|
namespace nxDumpFuse.Model
|
||||||
{
|
{
|
||||||
public class Fuse
|
public class Fuse
|
||||||
{
|
{
|
||||||
private const string XciExt = "xci";
|
|
||||||
private const string NspExt = "nsp";
|
|
||||||
private readonly CancellationTokenSource _cts;
|
private readonly CancellationTokenSource _cts;
|
||||||
private readonly string _inputFilePath;
|
private readonly string _inputFilePath;
|
||||||
private readonly string _outputDir;
|
private readonly string _outputDir;
|
||||||
private string _outputFilePath = string.Empty;
|
private string _outputFilePath = string.Empty;
|
||||||
private FileCase _fileCase;
|
private FileCase _fileCase;
|
||||||
|
private readonly Stopwatch _sw = new();
|
||||||
|
|
||||||
public Fuse(string inputFilePath, string outputDir)
|
public Fuse(string inputFilePath, string outputDir)
|
||||||
{
|
{
|
||||||
|
@ -35,14 +35,16 @@ namespace nxDumpFuse.Model
|
||||||
FuseUpdateEvent?.Invoke(fuseUpdateInfo);
|
FuseUpdateEvent?.Invoke(fuseUpdateInfo);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void Update(int part, int parts, double progress, double progressPart)
|
private void Update(int part, int parts, double progress, double progressPart, long speed, bool complete = false)
|
||||||
{
|
{
|
||||||
OnFuseUpdate(new FuseUpdateInfo
|
OnFuseUpdate(new FuseUpdateInfo
|
||||||
{
|
{
|
||||||
Part = part,
|
Part = part,
|
||||||
Parts = parts,
|
Parts = parts,
|
||||||
Progress = progress,
|
Progress = progress,
|
||||||
ProgressPart = progressPart
|
ProgressPart = progressPart,
|
||||||
|
Speed = speed.ToMb(),
|
||||||
|
Complete = complete
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -56,7 +58,7 @@ namespace nxDumpFuse.Model
|
||||||
OnFuseSimpleLogEvent(new FuseSimpleLog(type, DateTime.Now, message));
|
OnFuseSimpleLogEvent(new FuseSimpleLog(type, DateTime.Now, message));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void FuseDump()
|
public void Start()
|
||||||
{
|
{
|
||||||
if (string.IsNullOrEmpty(_inputFilePath))
|
if (string.IsNullOrEmpty(_inputFilePath))
|
||||||
{
|
{
|
||||||
|
@ -69,14 +71,14 @@ namespace nxDumpFuse.Model
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
GetOutputFilePath();
|
(_outputFilePath, _fileCase) = _inputFilePath.GetOutputFilePath(_outputDir);
|
||||||
if (string.IsNullOrEmpty(_outputFilePath))
|
if (string.IsNullOrEmpty(_outputFilePath) || _fileCase == FileCase.Invalid)
|
||||||
{
|
{
|
||||||
Log(FuseSimpleLogType.Error, "Output path was null");
|
Log(FuseSimpleLogType.Error, "Output path was null");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var inputFiles = GetInputFiles();
|
var inputFiles = _inputFilePath.GetInputFiles(_fileCase);
|
||||||
if (inputFiles.Count == 0)
|
if (inputFiles.Count == 0)
|
||||||
{
|
{
|
||||||
Log(FuseSimpleLogType.Error, "No input files found");
|
Log(FuseSimpleLogType.Error, "No input files found");
|
||||||
|
@ -86,77 +88,58 @@ namespace nxDumpFuse.Model
|
||||||
FuseFiles(inputFiles);
|
FuseFiles(inputFiles);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void GetOutputFilePath()
|
public void Stop()
|
||||||
{
|
{
|
||||||
|
_cts.Cancel();
|
||||||
|
_sw.Stop();
|
||||||
|
|
||||||
var fileName = Path.GetFileName(_inputFilePath);
|
Log(FuseSimpleLogType.Information, "Fuse Stopped");
|
||||||
if (Path.HasExtension(fileName))
|
|
||||||
|
if (File.Exists(_outputFilePath))
|
||||||
{
|
{
|
||||||
var ext = Path.GetExtension(fileName).Replace(".", string.Empty);
|
Task.Run((() =>
|
||||||
var split = fileName.Split(".").ToList();
|
|
||||||
|
|
||||||
if (int.TryParse(ext, out _) && split.Count >= 3 && split[^2] == XciExt) // .xci.00
|
|
||||||
{
|
{
|
||||||
_outputFilePath = Path.Join(_outputDir, $"{string.Join("", split.Take(split.Count - 2))}.{XciExt}");
|
const int retries = 5;
|
||||||
_fileCase = FileCase.XciNumeric;
|
for (var i = 0; i <= retries; i++)
|
||||||
}
|
{
|
||||||
else if (int.TryParse(ext, out _) && split.Count >= 3 && split[^2] == NspExt) // .nsp.00
|
try
|
||||||
{
|
{
|
||||||
_outputFilePath = Path.Join(_outputDir, $"{string.Join("", split.Take(split.Count - 2))}.{NspExt}");
|
File.Delete(_outputFilePath);
|
||||||
_fileCase = FileCase.NspNumeric;
|
Avalonia.Threading.Dispatcher.UIThread.InvokeAsync(() => Log(FuseSimpleLogType.Information, $"Deleted {_outputFilePath}"));
|
||||||
}
|
Update(0, 0, 0, 0, 0);
|
||||||
else switch (ext[..2])
|
break;
|
||||||
{
|
}
|
||||||
// .xc0
|
catch (IOException)
|
||||||
case "xc" when int.TryParse(ext.Substring(ext.Length - 1, 1), out _):
|
{
|
||||||
_outputFilePath = Path.Join(_outputDir, $"{Path.GetFileNameWithoutExtension(fileName)}.{XciExt}");
|
Thread.Sleep(1000);
|
||||||
_fileCase = FileCase.Xci;
|
}
|
||||||
break;
|
}
|
||||||
// .ns0
|
}));
|
||||||
case "ns" when int.TryParse(ext.Substring(ext.Length - 1, 1), out _):
|
|
||||||
_outputFilePath = Path.Join(_outputDir, $"{Path.GetFileNameWithoutExtension(fileName)}.{NspExt}");
|
|
||||||
_fileCase = FileCase.Nsp;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else // dir/00
|
|
||||||
{
|
|
||||||
_fileCase = FileCase.Numeric;
|
|
||||||
var inputDir = new FileInfo(_inputFilePath).Directory?.Name;
|
|
||||||
if (string.IsNullOrEmpty(inputDir))
|
|
||||||
{
|
|
||||||
inputDir = Path.GetPathRoot(_inputFilePath);
|
|
||||||
_outputFilePath = $"{inputDir}.{NspExt}";
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var inputDirSplit = inputDir.Split(".");
|
|
||||||
_outputFilePath = Path.Join(_outputDir, inputDirSplit.Length == 1
|
|
||||||
? $"{inputDir}.{NspExt}"
|
|
||||||
: $"{string.Join("", (inputDirSplit).Take(inputDirSplit.Length - 1))}.{NspExt}");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async void FuseFiles(IReadOnlyCollection<string> inputFiles)
|
private async void FuseFiles(List<string> inputFiles)
|
||||||
{
|
{
|
||||||
var buffer = new byte[1024 * 1024];
|
var buffer = new byte[1024 * 1024];
|
||||||
var count = 0;
|
var count = 0;
|
||||||
long totalBytes = 0;
|
long totalBytes = 0;
|
||||||
var totalFileLength = GetTotalFileSize(inputFiles);
|
var totalFileLength = inputFiles.GetOutputFileSize();
|
||||||
|
|
||||||
Log(FuseSimpleLogType.Information, $"Fusing {inputFiles.Count} parts to {_outputFilePath} ({ToMb(totalFileLength)}MB)");
|
Log(FuseSimpleLogType.Information, $"Fusing {inputFiles.Count} parts to {_outputFilePath} ({totalFileLength.ToMb()}MB)");
|
||||||
|
|
||||||
|
_sw.Start();
|
||||||
await using var outputStream = File.Create(_outputFilePath);
|
await using var outputStream = File.Create(_outputFilePath);
|
||||||
foreach (var inputFilePath in inputFiles)
|
foreach (var inputFilePath in inputFiles)
|
||||||
{
|
{
|
||||||
if (_cts.Token.IsCancellationRequested) return;
|
if (_cts.Token.IsCancellationRequested) return;
|
||||||
long currentBytes = 0;
|
long currentBytes = 0;
|
||||||
int currentBlockSize;
|
int currentBlockSize;
|
||||||
|
long copySpeed = 0;
|
||||||
|
|
||||||
await using var inputStream = File.OpenRead(inputFilePath);
|
await using var inputStream = File.OpenRead(inputFilePath);
|
||||||
var fileLength = inputStream.Length;
|
var fileLength = inputStream.Length;
|
||||||
|
|
||||||
Log(FuseSimpleLogType.Information, $"Fusing file part {++count}-> {inputFilePath} ({ToMb(fileLength)}MB)");
|
Log(FuseSimpleLogType.Information, $"Fusing file part {++count}-> {inputFilePath} ({fileLength.ToMb()}MB)");
|
||||||
|
|
||||||
while ((currentBlockSize = inputStream.Read(buffer, 0, buffer.Length)) > 0)
|
while ((currentBlockSize = inputStream.Read(buffer, 0, buffer.Length)) > 0)
|
||||||
{
|
{
|
||||||
|
@ -172,84 +155,21 @@ namespace nxDumpFuse.Model
|
||||||
catch (TaskCanceledException e)
|
catch (TaskCanceledException e)
|
||||||
{
|
{
|
||||||
Log(FuseSimpleLogType.Error, e.Message);
|
Log(FuseSimpleLogType.Error, e.Message);
|
||||||
|
_sw.Stop();
|
||||||
|
Update(0,0,0,0,0,true);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var progress = totalBytes * 100.0 / totalFileLength;
|
var progress = totalBytes * 100.0 / totalFileLength;
|
||||||
var progressPart = currentBytes * 100.0 / fileLength;
|
var progressPart = currentBytes * 100.0 / fileLength;
|
||||||
Update(count, inputFiles.Count, progress, progressPart);
|
if(_sw.ElapsedMilliseconds >= 1000) copySpeed = totalBytes / _sw.ElapsedMilliseconds.ToSeconds();
|
||||||
|
Update(count, inputFiles.Count, progress, progressPart, copySpeed);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Log(FuseSimpleLogType.Information, "Fuse Complete");
|
Log(FuseSimpleLogType.Information, $"Fuse Completed in {_sw.ElapsedMilliseconds.ToSeconds()}s");
|
||||||
}
|
_sw.Stop();
|
||||||
|
Update(0, 0, 0, 0, 0, true);
|
||||||
private static long ToMb(long bytes)
|
|
||||||
{
|
|
||||||
return bytes / 1000000;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static long GetTotalFileSize(IEnumerable<string> inputFiles)
|
|
||||||
{
|
|
||||||
long totalFileSize = 0;
|
|
||||||
inputFiles.Select(f => f).ToList().ForEach(f => totalFileSize += new FileInfo(f).Length);
|
|
||||||
return totalFileSize;
|
|
||||||
}
|
|
||||||
|
|
||||||
private List<string> GetInputFiles()
|
|
||||||
{
|
|
||||||
var inputDir = Path.GetDirectoryName(_inputFilePath);
|
|
||||||
if (string.IsNullOrEmpty(inputDir)) return new List<string>();
|
|
||||||
var files = new List<string>();
|
|
||||||
switch (_fileCase)
|
|
||||||
{
|
|
||||||
case FileCase.XciNumeric: // .xci.00
|
|
||||||
case FileCase.NspNumeric: // .nsp.00
|
|
||||||
files = Directory.GetFiles(inputDir)
|
|
||||||
.Where(f => int.TryParse(Path.GetExtension(f).Replace(".", ""), out _))
|
|
||||||
.ToList();
|
|
||||||
break;
|
|
||||||
case FileCase.Xci: // .xc0
|
|
||||||
case FileCase.Nsp: // .ns0
|
|
||||||
files = Directory.GetFiles(inputDir, $"{Path.GetFileNameWithoutExtension(_inputFilePath)}*")
|
|
||||||
.ToList();
|
|
||||||
break;
|
|
||||||
case FileCase.Numeric: // dir/00
|
|
||||||
files = Directory.GetFiles(inputDir)
|
|
||||||
.Where(f => int.TryParse(Path.GetFileName(f), out _))
|
|
||||||
.ToList();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
files.Sort();
|
|
||||||
return files;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void StopFuse()
|
|
||||||
{
|
|
||||||
_cts.Cancel();
|
|
||||||
|
|
||||||
Log(FuseSimpleLogType.Information, "Fuse Stopped");
|
|
||||||
|
|
||||||
if (File.Exists(_outputFilePath))
|
|
||||||
{
|
|
||||||
Task.Run((() =>
|
|
||||||
{
|
|
||||||
const int retries = 5;
|
|
||||||
for (var i = 0; i <= retries; i++)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
File.Delete(_outputFilePath);
|
|
||||||
Avalonia.Threading.Dispatcher.UIThread.InvokeAsync(() => Log(FuseSimpleLogType.Information, $"Deleted {_outputFilePath}"));
|
|
||||||
Update(0, 0, 0, 0);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
catch (IOException)
|
|
||||||
{
|
|
||||||
Thread.Sleep(1000);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -6,8 +6,12 @@
|
||||||
|
|
||||||
public double ProgressPart { get; set; }
|
public double ProgressPart { get; set; }
|
||||||
|
|
||||||
|
public long Speed { get; set; }
|
||||||
|
|
||||||
public int Part { get; set; }
|
public int Part { get; set; }
|
||||||
|
|
||||||
public int Parts { get; set; }
|
public int Parts { get; set; }
|
||||||
|
|
||||||
|
public bool Complete { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Collections.ObjectModel;
|
using System.Collections.ObjectModel;
|
||||||
|
using System.Diagnostics;
|
||||||
using System.Reactive;
|
using System.Reactive;
|
||||||
using Avalonia.Controls;
|
using Avalonia.Controls;
|
||||||
using nxDumpFuse.Interfaces;
|
using nxDumpFuse.Interfaces;
|
||||||
|
@ -14,6 +15,8 @@ namespace nxDumpFuse.ViewModels
|
||||||
{
|
{
|
||||||
private readonly IDialogService _dialogService;
|
private readonly IDialogService _dialogService;
|
||||||
private Fuse? _fuse;
|
private Fuse? _fuse;
|
||||||
|
private readonly Stopwatch _sw = new();
|
||||||
|
private TimeSpan _elapsed;
|
||||||
|
|
||||||
public FuseViewModel(IDialogService dialogService)
|
public FuseViewModel(IDialogService dialogService)
|
||||||
{
|
{
|
||||||
|
@ -37,7 +40,6 @@ namespace nxDumpFuse.ViewModels
|
||||||
public ReactiveCommand<Unit, Unit> StopCommand { get; }
|
public ReactiveCommand<Unit, Unit> StopCommand { get; }
|
||||||
|
|
||||||
private string _inputFilePath = string.Empty;
|
private string _inputFilePath = string.Empty;
|
||||||
|
|
||||||
public string InputFilePath
|
public string InputFilePath
|
||||||
{
|
{
|
||||||
get => _inputFilePath;
|
get => _inputFilePath;
|
||||||
|
@ -45,7 +47,6 @@ namespace nxDumpFuse.ViewModels
|
||||||
}
|
}
|
||||||
|
|
||||||
private string _outputDir = string.Empty;
|
private string _outputDir = string.Empty;
|
||||||
|
|
||||||
public string OutputDir
|
public string OutputDir
|
||||||
{
|
{
|
||||||
get => _outputDir;
|
get => _outputDir;
|
||||||
|
@ -53,7 +54,6 @@ namespace nxDumpFuse.ViewModels
|
||||||
}
|
}
|
||||||
|
|
||||||
private string _progressPartText = string.Empty;
|
private string _progressPartText = string.Empty;
|
||||||
|
|
||||||
public string ProgressPartText
|
public string ProgressPartText
|
||||||
{
|
{
|
||||||
get => _progressPartText;
|
get => _progressPartText;
|
||||||
|
@ -61,7 +61,6 @@ namespace nxDumpFuse.ViewModels
|
||||||
}
|
}
|
||||||
|
|
||||||
private double _progressPart;
|
private double _progressPart;
|
||||||
|
|
||||||
public double ProgressPart
|
public double ProgressPart
|
||||||
{
|
{
|
||||||
get => _progressPart;
|
get => _progressPart;
|
||||||
|
@ -69,15 +68,20 @@ namespace nxDumpFuse.ViewModels
|
||||||
}
|
}
|
||||||
|
|
||||||
private double _progress;
|
private double _progress;
|
||||||
|
|
||||||
public double Progress
|
public double Progress
|
||||||
{
|
{
|
||||||
get => _progress;
|
get => _progress;
|
||||||
set => this.RaiseAndSetIfChanged(ref _progress, value);
|
set => this.RaiseAndSetIfChanged(ref _progress, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
private ObservableCollection<FuseSimpleLog> _logItems = new();
|
private string _progressText = string.Empty;
|
||||||
|
public string ProgressText
|
||||||
|
{
|
||||||
|
get => _progressText;
|
||||||
|
set => this.RaiseAndSetIfChanged(ref _progressText, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private ObservableCollection<FuseSimpleLog> _logItems = new();
|
||||||
public ObservableCollection<FuseSimpleLog> LogItems
|
public ObservableCollection<FuseSimpleLog> LogItems
|
||||||
{
|
{
|
||||||
get => _logItems;
|
get => _logItems;
|
||||||
|
@ -99,18 +103,22 @@ namespace nxDumpFuse.ViewModels
|
||||||
_fuse = new Fuse(InputFilePath, OutputDir);
|
_fuse = new Fuse(InputFilePath, OutputDir);
|
||||||
_fuse.FuseUpdateEvent += OnFuseUpdate;
|
_fuse.FuseUpdateEvent += OnFuseUpdate;
|
||||||
_fuse.FuseSimpleLogEvent += OnFuseSimpleLogEvent;
|
_fuse.FuseSimpleLogEvent += OnFuseSimpleLogEvent;
|
||||||
|
_sw.Start();
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
_fuse.FuseDump();
|
_fuse.Start();
|
||||||
}
|
}
|
||||||
catch (Exception e) {
|
catch (Exception e) {
|
||||||
|
_sw.Stop();
|
||||||
OnFuseSimpleLogEvent(new FuseSimpleLog(FuseSimpleLogType.Error, DateTime.Now, e.Message));
|
OnFuseSimpleLogEvent(new FuseSimpleLog(FuseSimpleLogType.Error, DateTime.Now, e.Message));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void StopDump()
|
private void StopDump()
|
||||||
{
|
{
|
||||||
_fuse?.StopFuse();
|
_sw.Stop();
|
||||||
|
_fuse?.Stop();
|
||||||
|
ProgressText = string.Empty;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ClearLog()
|
private void ClearLog()
|
||||||
|
@ -120,9 +128,20 @@ namespace nxDumpFuse.ViewModels
|
||||||
|
|
||||||
private void OnFuseUpdate(FuseUpdateInfo fuseUpdateInfo)
|
private void OnFuseUpdate(FuseUpdateInfo fuseUpdateInfo)
|
||||||
{
|
{
|
||||||
|
if (fuseUpdateInfo.Complete)
|
||||||
|
{
|
||||||
|
_sw.Stop();
|
||||||
|
ProgressText = string.Empty;
|
||||||
|
return;
|
||||||
|
}
|
||||||
ProgressPart = fuseUpdateInfo.ProgressPart;
|
ProgressPart = fuseUpdateInfo.ProgressPart;
|
||||||
Progress = fuseUpdateInfo.Progress;
|
|
||||||
ProgressPartText = $"Part {fuseUpdateInfo.Part}/{fuseUpdateInfo.Parts}";
|
ProgressPartText = $"Part {fuseUpdateInfo.Part}/{fuseUpdateInfo.Parts}";
|
||||||
|
Progress = fuseUpdateInfo.Progress;
|
||||||
|
|
||||||
|
if (!(_sw.Elapsed.TotalSeconds >= 0.5 &&
|
||||||
|
_sw.Elapsed.TotalSeconds - _elapsed.TotalSeconds >= 0.5)) return;
|
||||||
|
_elapsed = _sw.Elapsed;
|
||||||
|
ProgressText = $"({fuseUpdateInfo.Speed:0}MB/s) {Progress:0}% ";
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnFuseSimpleLogEvent(FuseSimpleLog log)
|
private void OnFuseSimpleLogEvent(FuseSimpleLog log)
|
||||||
|
|
|
@ -8,64 +8,69 @@
|
||||||
x:DataType="vm:FuseViewModel">
|
x:DataType="vm:FuseViewModel">
|
||||||
<DockPanel LastChildFill="True">
|
<DockPanel LastChildFill="True">
|
||||||
<Grid Margin="20" DockPanel.Dock="Top"
|
<Grid Margin="20" DockPanel.Dock="Top"
|
||||||
RowDefinitions="Auto,Auto,Auto,Auto,Auto,Auto"
|
RowDefinitions="Auto,Auto,Auto,Auto,Auto,Auto,Auto"
|
||||||
ColumnDefinitions="Auto,*">
|
ColumnDefinitions="Auto,*,50">
|
||||||
|
|
||||||
<Button Grid.Row="0" Grid.Column="0" Command="{Binding SelectInputFileCommand}" Content="Input"
|
<Button Grid.Row="0" Grid.Column="0" Command="{Binding SelectInputFileCommand}" Content="Input"
|
||||||
HorizontalAlignment="Stretch" HorizontalContentAlignment="Center" />
|
HorizontalAlignment="Stretch" HorizontalContentAlignment="Center" />
|
||||||
<TextBox Grid.Row="0" Grid.Column="1" Margin="5" VerticalAlignment="Center" Text="{Binding InputFilePath}"
|
<TextBox Grid.Row="0" Grid.Column="1" Grid.ColumnSpan="2" Margin="5" VerticalAlignment="Center"
|
||||||
|
Text="{Binding InputFilePath}"
|
||||||
Name="InputFileTextBox" />
|
Name="InputFileTextBox" />
|
||||||
|
|
||||||
<Button Grid.Row="1" Grid.Column="0" Command="{Binding SelectOutputFolderCommand}" Content="Output"
|
<Button Grid.Row="1" Grid.Column="0" Command="{Binding SelectOutputFolderCommand}" Content="Output"
|
||||||
HorizontalAlignment="Stretch" HorizontalContentAlignment="Center" />
|
HorizontalAlignment="Stretch" HorizontalContentAlignment="Center" />
|
||||||
<TextBox Grid.Row="1" Grid.Column="1" Margin="5" VerticalAlignment="Center" Text="{Binding OutputDir}" />
|
<TextBox Grid.Row="1" Grid.Column="1" Grid.ColumnSpan="2" Margin="5" VerticalAlignment="Center"
|
||||||
|
Text="{Binding OutputDir}" />
|
||||||
|
|
||||||
<TextBlock Grid.Row="2" Grid.Column="0" Text="{Binding ProgressPartText}" HorizontalAlignment="Right"
|
<TextBlock Grid.Row="2" Grid.Column="0" Text="{Binding ProgressPartText}" HorizontalAlignment="Right"
|
||||||
Margin="2" />
|
Margin="2" />
|
||||||
<ProgressBar Grid.Row="2" Grid.Column="1" Margin="2" Height="10" Value="{Binding ProgressPart}" />
|
<ProgressBar Grid.Row="2" Grid.Column="1" Grid.ColumnSpan="2" Margin="2" Height="10" Value="{Binding ProgressPart}" HorizontalAlignment="Stretch"/>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<TextBlock Grid.Row="3" Grid.Column="0" Text="Total" HorizontalAlignment="Right" Margin="2" />
|
<TextBlock Grid.Row="3" Grid.Column="0" Text="Total" HorizontalAlignment="Right" Margin="2" />
|
||||||
<ProgressBar Grid.Row="3" Grid.Column="1" Margin="2" Height="10" Value="{Binding Progress}" />
|
<ProgressBar Grid.Row="3" Grid.Column="1" Grid.ColumnSpan="2" Margin="2" Height="10" Value="{Binding Progress}" HorizontalAlignment="Stretch"/>
|
||||||
|
|
||||||
<StackPanel Grid.Row="4" Grid.Column="1" Orientation="Horizontal" HorizontalAlignment="Right">
|
|
||||||
|
<StackPanel Grid.Row="4" Grid.Column="1" Grid.ColumnSpan="2" Orientation="Horizontal" HorizontalAlignment="Right">
|
||||||
<Button Command="{Binding FuseCommand}" Content="Fuse" HorizontalAlignment="Stretch"
|
<Button Command="{Binding FuseCommand}" Content="Fuse" HorizontalAlignment="Stretch"
|
||||||
HorizontalContentAlignment="Center" Margin="2" />
|
HorizontalContentAlignment="Center" Margin="2" />
|
||||||
<Button Command="{Binding StopCommand}" Content="Stop" HorizontalAlignment="Stretch"
|
<Button Command="{Binding StopCommand}" Content="Stop" HorizontalAlignment="Stretch"
|
||||||
HorizontalContentAlignment="Center" Margin="2" />
|
HorizontalContentAlignment="Center" Margin="2" />
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
|
|
||||||
<Expander Grid.Row="5" Grid.Column="0" Grid.ColumnSpan="2"
|
<Expander Grid.Row="5" Grid.Column="0" Grid.ColumnSpan="3"
|
||||||
Header="Log"
|
Header="Log"
|
||||||
Margin="2">
|
Margin="2">
|
||||||
<Grid>
|
<Grid ColumnDefinitions="*,Auto" >
|
||||||
<Grid.ColumnDefinitions>
|
<DataGrid Grid.Column="0"
|
||||||
<ColumnDefinition Width="*"/>
|
x:Name="FuseSimpleLog"
|
||||||
<ColumnDefinition Width="Auto"/>
|
Items="{Binding LogItems}"
|
||||||
</Grid.ColumnDefinitions>
|
CanUserSortColumns="False"
|
||||||
<DataGrid Grid.Column="0"
|
CanUserResizeColumns="True"
|
||||||
x:Name="FuseSimpleLog"
|
CanUserReorderColumns="True"
|
||||||
Items="{Binding LogItems}"
|
VerticalScrollBarVisibility="Auto"
|
||||||
CanUserSortColumns="False"
|
HorizontalScrollBarVisibility="Auto"
|
||||||
CanUserResizeColumns="True"
|
VerticalAlignment="Top"
|
||||||
CanUserReorderColumns="True"
|
Height="300"
|
||||||
VerticalScrollBarVisibility="Auto"
|
LoadingRow="FuseSimpleLog_OnLoadingRow">
|
||||||
HorizontalScrollBarVisibility="Auto"
|
<DataGrid.Columns>
|
||||||
VerticalAlignment="Top"
|
<DataGridTextColumn Header="Type" Binding="{Binding Type}" Width="Auto"
|
||||||
Height="300"
|
Foreground="{Binding Color}" FontSize="12" />
|
||||||
LoadingRow="FuseSimpleLog_OnLoadingRow">
|
<DataGridTextColumn Header="Time" Binding="{Binding Time}" Width="Auto" FontSize="12" />
|
||||||
<DataGrid.Columns>
|
<DataGridTextColumn Header="Message" Binding="{Binding Message}" Width="Auto" FontSize="12" />
|
||||||
<DataGridTextColumn Header="Type" Binding="{Binding Type}" Width="Auto"
|
</DataGrid.Columns>
|
||||||
Foreground="{Binding Color}" FontSize="12" />
|
</DataGrid>
|
||||||
<DataGridTextColumn Header="Time" Binding="{Binding Time}" Width="Auto" FontSize="12" />
|
<Button Grid.Column="1" Command="{Binding ClearLogCommand}" Content="Clear"
|
||||||
<DataGridTextColumn Header="Message" Binding="{Binding Message}" Width="Auto" FontSize="12" />
|
HorizontalAlignment="Stretch"
|
||||||
</DataGrid.Columns>
|
HorizontalContentAlignment="Center" VerticalAlignment="Top" Margin="4 0 0 0" />
|
||||||
</DataGrid>
|
</Grid>
|
||||||
<Button Grid.Column="1" Command="{Binding ClearLogCommand}" Content="Clear" HorizontalAlignment="Stretch"
|
|
||||||
HorizontalContentAlignment="Center" VerticalAlignment="Top" Margin=" 4 0 0 0" />
|
</Expander>
|
||||||
</Grid>
|
|
||||||
|
|
||||||
</Expander>
|
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|
||||||
|
<TextBlock DockPanel.Dock="Bottom" Text="{Binding ProgressText}" HorizontalAlignment="Right" VerticalAlignment="Bottom" FontSize="12" Margin="0 0 20 0"/>
|
||||||
</DockPanel>
|
</DockPanel>
|
||||||
|
|
||||||
</UserControl>
|
</UserControl>
|
Loading…
Reference in a new issue