mirror of
https://github.com/oMaN-Rod/nxDumpFuse.git
synced 2024-11-08 11:51:49 +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
|
||||
{
|
||||
Invalid,
|
||||
XciNumeric, // .xci.00
|
||||
NspNumeric, // .nsp.00
|
||||
Xci, // .xc0
|
||||
|
|
|
@ -2,23 +2,23 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using nxDumpFuse.Events;
|
||||
using nxDumpFuse.Extensions;
|
||||
using nxDumpFuse.Model.Enums;
|
||||
|
||||
namespace nxDumpFuse.Model
|
||||
{
|
||||
public class Fuse
|
||||
{
|
||||
private const string XciExt = "xci";
|
||||
private const string NspExt = "nsp";
|
||||
|
||||
private readonly CancellationTokenSource _cts;
|
||||
private readonly string _inputFilePath;
|
||||
private readonly string _outputDir;
|
||||
private string _outputFilePath = string.Empty;
|
||||
private FileCase _fileCase;
|
||||
private readonly Stopwatch _sw = new();
|
||||
|
||||
public Fuse(string inputFilePath, string outputDir)
|
||||
{
|
||||
|
@ -35,14 +35,16 @@ namespace nxDumpFuse.Model
|
|||
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
|
||||
{
|
||||
Part = part,
|
||||
Parts = parts,
|
||||
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));
|
||||
}
|
||||
|
||||
public void FuseDump()
|
||||
public void Start()
|
||||
{
|
||||
if (string.IsNullOrEmpty(_inputFilePath))
|
||||
{
|
||||
|
@ -69,14 +71,14 @@ namespace nxDumpFuse.Model
|
|||
return;
|
||||
}
|
||||
|
||||
GetOutputFilePath();
|
||||
if (string.IsNullOrEmpty(_outputFilePath))
|
||||
(_outputFilePath, _fileCase) = _inputFilePath.GetOutputFilePath(_outputDir);
|
||||
if (string.IsNullOrEmpty(_outputFilePath) || _fileCase == FileCase.Invalid)
|
||||
{
|
||||
Log(FuseSimpleLogType.Error, "Output path was null");
|
||||
return;
|
||||
}
|
||||
|
||||
var inputFiles = GetInputFiles();
|
||||
var inputFiles = _inputFilePath.GetInputFiles(_fileCase);
|
||||
if (inputFiles.Count == 0)
|
||||
{
|
||||
Log(FuseSimpleLogType.Error, "No input files found");
|
||||
|
@ -86,77 +88,58 @@ namespace nxDumpFuse.Model
|
|||
FuseFiles(inputFiles);
|
||||
}
|
||||
|
||||
private void GetOutputFilePath()
|
||||
public void Stop()
|
||||
{
|
||||
_cts.Cancel();
|
||||
_sw.Stop();
|
||||
|
||||
var fileName = Path.GetFileName(_inputFilePath);
|
||||
if (Path.HasExtension(fileName))
|
||||
{
|
||||
var ext = Path.GetExtension(fileName).Replace(".", string.Empty);
|
||||
var split = fileName.Split(".").ToList();
|
||||
Log(FuseSimpleLogType.Information, "Fuse Stopped");
|
||||
|
||||
if (int.TryParse(ext, out _) && split.Count >= 3 && split[^2] == XciExt) // .xci.00
|
||||
if (File.Exists(_outputFilePath))
|
||||
{
|
||||
_outputFilePath = Path.Join(_outputDir, $"{string.Join("", split.Take(split.Count - 2))}.{XciExt}");
|
||||
_fileCase = FileCase.XciNumeric;
|
||||
}
|
||||
else if (int.TryParse(ext, out _) && split.Count >= 3 && split[^2] == NspExt) // .nsp.00
|
||||
Task.Run((() =>
|
||||
{
|
||||
_outputFilePath = Path.Join(_outputDir, $"{string.Join("", split.Take(split.Count - 2))}.{NspExt}");
|
||||
_fileCase = FileCase.NspNumeric;
|
||||
}
|
||||
else switch (ext[..2])
|
||||
const int retries = 5;
|
||||
for (var i = 0; i <= retries; i++)
|
||||
{
|
||||
// .xc0
|
||||
case "xc" when int.TryParse(ext.Substring(ext.Length - 1, 1), out _):
|
||||
_outputFilePath = Path.Join(_outputDir, $"{Path.GetFileNameWithoutExtension(fileName)}.{XciExt}");
|
||||
_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;
|
||||
try
|
||||
{
|
||||
File.Delete(_outputFilePath);
|
||||
Avalonia.Threading.Dispatcher.UIThread.InvokeAsync(() => Log(FuseSimpleLogType.Information, $"Deleted {_outputFilePath}"));
|
||||
Update(0, 0, 0, 0, 0);
|
||||
break;
|
||||
}
|
||||
}
|
||||
else // dir/00
|
||||
catch (IOException)
|
||||
{
|
||||
_fileCase = FileCase.Numeric;
|
||||
var inputDir = new FileInfo(_inputFilePath).Directory?.Name;
|
||||
if (string.IsNullOrEmpty(inputDir))
|
||||
{
|
||||
inputDir = Path.GetPathRoot(_inputFilePath);
|
||||
_outputFilePath = $"{inputDir}.{NspExt}";
|
||||
return;
|
||||
Thread.Sleep(1000);
|
||||
}
|
||||
|
||||
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 count = 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);
|
||||
foreach (var inputFilePath in inputFiles)
|
||||
{
|
||||
if (_cts.Token.IsCancellationRequested) return;
|
||||
long currentBytes = 0;
|
||||
int currentBlockSize;
|
||||
long copySpeed = 0;
|
||||
|
||||
await using var inputStream = File.OpenRead(inputFilePath);
|
||||
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)
|
||||
{
|
||||
|
@ -172,84 +155,21 @@ namespace nxDumpFuse.Model
|
|||
catch (TaskCanceledException e)
|
||||
{
|
||||
Log(FuseSimpleLogType.Error, e.Message);
|
||||
_sw.Stop();
|
||||
Update(0,0,0,0,0,true);
|
||||
return;
|
||||
}
|
||||
|
||||
var progress = totalBytes * 100.0 / totalFileLength;
|
||||
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");
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}));
|
||||
}
|
||||
Log(FuseSimpleLogType.Information, $"Fuse Completed in {_sw.ElapsedMilliseconds.ToSeconds()}s");
|
||||
_sw.Stop();
|
||||
Update(0, 0, 0, 0, 0, true);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -6,8 +6,12 @@
|
|||
|
||||
public double ProgressPart { get; set; }
|
||||
|
||||
public long Speed { get; set; }
|
||||
|
||||
public int Part { get; set; }
|
||||
|
||||
public int Parts { get; set; }
|
||||
|
||||
public bool Complete { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Diagnostics;
|
||||
using System.Reactive;
|
||||
using Avalonia.Controls;
|
||||
using nxDumpFuse.Interfaces;
|
||||
|
@ -14,6 +15,8 @@ namespace nxDumpFuse.ViewModels
|
|||
{
|
||||
private readonly IDialogService _dialogService;
|
||||
private Fuse? _fuse;
|
||||
private readonly Stopwatch _sw = new();
|
||||
private TimeSpan _elapsed;
|
||||
|
||||
public FuseViewModel(IDialogService dialogService)
|
||||
{
|
||||
|
@ -37,7 +40,6 @@ namespace nxDumpFuse.ViewModels
|
|||
public ReactiveCommand<Unit, Unit> StopCommand { get; }
|
||||
|
||||
private string _inputFilePath = string.Empty;
|
||||
|
||||
public string InputFilePath
|
||||
{
|
||||
get => _inputFilePath;
|
||||
|
@ -45,7 +47,6 @@ namespace nxDumpFuse.ViewModels
|
|||
}
|
||||
|
||||
private string _outputDir = string.Empty;
|
||||
|
||||
public string OutputDir
|
||||
{
|
||||
get => _outputDir;
|
||||
|
@ -53,7 +54,6 @@ namespace nxDumpFuse.ViewModels
|
|||
}
|
||||
|
||||
private string _progressPartText = string.Empty;
|
||||
|
||||
public string ProgressPartText
|
||||
{
|
||||
get => _progressPartText;
|
||||
|
@ -61,7 +61,6 @@ namespace nxDumpFuse.ViewModels
|
|||
}
|
||||
|
||||
private double _progressPart;
|
||||
|
||||
public double ProgressPart
|
||||
{
|
||||
get => _progressPart;
|
||||
|
@ -69,15 +68,20 @@ namespace nxDumpFuse.ViewModels
|
|||
}
|
||||
|
||||
private double _progress;
|
||||
|
||||
public double Progress
|
||||
{
|
||||
get => _progress;
|
||||
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
|
||||
{
|
||||
get => _logItems;
|
||||
|
@ -99,18 +103,22 @@ namespace nxDumpFuse.ViewModels
|
|||
_fuse = new Fuse(InputFilePath, OutputDir);
|
||||
_fuse.FuseUpdateEvent += OnFuseUpdate;
|
||||
_fuse.FuseSimpleLogEvent += OnFuseSimpleLogEvent;
|
||||
_sw.Start();
|
||||
try
|
||||
{
|
||||
_fuse.FuseDump();
|
||||
_fuse.Start();
|
||||
}
|
||||
catch (Exception e) {
|
||||
_sw.Stop();
|
||||
OnFuseSimpleLogEvent(new FuseSimpleLog(FuseSimpleLogType.Error, DateTime.Now, e.Message));
|
||||
}
|
||||
}
|
||||
|
||||
private void StopDump()
|
||||
{
|
||||
_fuse?.StopFuse();
|
||||
_sw.Stop();
|
||||
_fuse?.Stop();
|
||||
ProgressText = string.Empty;
|
||||
}
|
||||
|
||||
private void ClearLog()
|
||||
|
@ -120,9 +128,20 @@ namespace nxDumpFuse.ViewModels
|
|||
|
||||
private void OnFuseUpdate(FuseUpdateInfo fuseUpdateInfo)
|
||||
{
|
||||
if (fuseUpdateInfo.Complete)
|
||||
{
|
||||
_sw.Stop();
|
||||
ProgressText = string.Empty;
|
||||
return;
|
||||
}
|
||||
ProgressPart = fuseUpdateInfo.ProgressPart;
|
||||
Progress = fuseUpdateInfo.Progress;
|
||||
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)
|
||||
|
|
|
@ -8,40 +8,41 @@
|
|||
x:DataType="vm:FuseViewModel">
|
||||
<DockPanel LastChildFill="True">
|
||||
<Grid Margin="20" DockPanel.Dock="Top"
|
||||
RowDefinitions="Auto,Auto,Auto,Auto,Auto,Auto"
|
||||
ColumnDefinitions="Auto,*">
|
||||
RowDefinitions="Auto,Auto,Auto,Auto,Auto,Auto,Auto"
|
||||
ColumnDefinitions="Auto,*,50">
|
||||
|
||||
<Button Grid.Row="0" Grid.Column="0" Command="{Binding SelectInputFileCommand}" Content="Input"
|
||||
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" />
|
||||
|
||||
<Button Grid.Row="1" Grid.Column="0" Command="{Binding SelectOutputFolderCommand}" Content="Output"
|
||||
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"
|
||||
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" />
|
||||
<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"
|
||||
HorizontalContentAlignment="Center" Margin="2" />
|
||||
<Button Command="{Binding StopCommand}" Content="Stop" HorizontalAlignment="Stretch"
|
||||
HorizontalContentAlignment="Center" Margin="2" />
|
||||
</StackPanel>
|
||||
|
||||
<Expander Grid.Row="5" Grid.Column="0" Grid.ColumnSpan="2"
|
||||
<Expander Grid.Row="5" Grid.Column="0" Grid.ColumnSpan="3"
|
||||
Header="Log"
|
||||
Margin="2">
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<Grid ColumnDefinitions="*,Auto" >
|
||||
<DataGrid Grid.Column="0"
|
||||
x:Name="FuseSimpleLog"
|
||||
Items="{Binding LogItems}"
|
||||
|
@ -60,12 +61,16 @@
|
|||
<DataGridTextColumn Header="Message" Binding="{Binding Message}" Width="Auto" FontSize="12" />
|
||||
</DataGrid.Columns>
|
||||
</DataGrid>
|
||||
<Button Grid.Column="1" Command="{Binding ClearLogCommand}" Content="Clear" HorizontalAlignment="Stretch"
|
||||
<Button Grid.Column="1" Command="{Binding ClearLogCommand}" Content="Clear"
|
||||
HorizontalAlignment="Stretch"
|
||||
HorizontalContentAlignment="Center" VerticalAlignment="Top" Margin="4 0 0 0" />
|
||||
</Grid>
|
||||
|
||||
</Expander>
|
||||
|
||||
</Grid>
|
||||
|
||||
<TextBlock DockPanel.Dock="Bottom" Text="{Binding ProgressText}" HorizontalAlignment="Right" VerticalAlignment="Bottom" FontSize="12" Margin="0 0 20 0"/>
|
||||
</DockPanel>
|
||||
|
||||
</UserControl>
|
Loading…
Reference in a new issue