Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/sn4k3/UVtools.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTiago Conceição <Tiago_caza@hotmail.com>2021-11-14 22:57:01 +0300
committerTiago Conceição <Tiago_caza@hotmail.com>2021-11-14 22:57:01 +0300
commit48c2c10cfd77005d9eb77a2042c23e916a700c16 (patch)
tree9f0f0cf3592f7bab92e9a3646e75a5279d04f551
parent956e235d17165a6bbc518ff8a7c62edceb7a6514 (diff)
Improvements on File -> Send to
- **File -> Send to -> Device** - (Add) Progress with the transfered megabyte(s) and allow to cancel the transfer - (Add) It will prompt for drive ejection [Configurable - On by default] [Windows only] (#340)
-rw-r--r--UVtools.Core/Extensions/NetworkExtensions.cs2
-rw-r--r--UVtools.Core/Extensions/StreamExtensions.cs10
-rw-r--r--UVtools.Core/Operations/OperationProgress.cs6
-rw-r--r--UVtools.WPF/MainWindow.axaml.cs130
-rw-r--r--UVtools.WPF/SystemOS/Windows/USB.cs130
-rw-r--r--UVtools.WPF/UserSettings.cs7
-rw-r--r--UVtools.WPF/Windows/SettingsWindow.axaml7
7 files changed, 272 insertions, 20 deletions
diff --git a/UVtools.Core/Extensions/NetworkExtensions.cs b/UVtools.Core/Extensions/NetworkExtensions.cs
index cbe4844..d508705 100644
--- a/UVtools.Core/Extensions/NetworkExtensions.cs
+++ b/UVtools.Core/Extensions/NetworkExtensions.cs
@@ -40,7 +40,7 @@ namespace UVtools.Core.Extensions
// Convert absolute progress (bytes downloaded) into relative progress (0% - 100%)
var relativeProgress = new Progress<long>(downloadedBytes => progress.Report(new (contentLength.Value, downloadedBytes)));
// Use extension method to report progress while downloading
- await download.CopyToAsync(destination, 81920, relativeProgress, cancellationToken);
+ await download.CopyToAsync(destination, relativeProgress, cancellationToken);
progress.Report(new(contentLength.Value, contentLength.Value));
return response;
}
diff --git a/UVtools.Core/Extensions/StreamExtensions.cs b/UVtools.Core/Extensions/StreamExtensions.cs
index 2ef9fd6..985c8ae 100644
--- a/UVtools.Core/Extensions/StreamExtensions.cs
+++ b/UVtools.Core/Extensions/StreamExtensions.cs
@@ -15,6 +15,10 @@ namespace UVtools.Core.Extensions
{
public static class StreamExtensions
{
+ //public const int DefaultCopyBufferSize = 81920; // 81.92 kilobytes, .NET default
+ //public const int DefaultCopyBufferSize = 512000; // 512 kilobytes
+ public const int DefaultCopyBufferSize = 1048576; // 1 MB
+
/// <summary>
/// Converts stream into byte array
/// </summary>
@@ -39,6 +43,7 @@ namespace UVtools.Core.Extensions
throw new ArgumentException("Has to be writable", nameof(destination));
if (bufferSize < 0)
throw new ArgumentOutOfRangeException(nameof(bufferSize));
+ if (bufferSize == 0) bufferSize = DefaultCopyBufferSize;
var buffer = new byte[bufferSize];
long totalBytesRead = 0;
@@ -50,5 +55,10 @@ namespace UVtools.Core.Extensions
progress?.Report(totalBytesRead);
}
}
+
+ public static async Task CopyToAsync(this Stream source, Stream destination, IProgress<long> progress = null, CancellationToken cancellationToken = default)
+ {
+ await CopyToAsync(source, destination, DefaultCopyBufferSize, progress, cancellationToken);
+ }
}
}
diff --git a/UVtools.Core/Operations/OperationProgress.cs b/UVtools.Core/Operations/OperationProgress.cs
index f96e1d0..a1a418f 100644
--- a/UVtools.Core/Operations/OperationProgress.cs
+++ b/UVtools.Core/Operations/OperationProgress.cs
@@ -179,6 +179,12 @@ namespace UVtools.Core.Operations
RaisePropertyChanged(nameof(CanCancel));
}
+ public void ResetAll(string title, string name = "", uint itemCount = 0, uint items = 0)
+ {
+ Title = title;
+ Reset(name, itemCount, items);
+ }
+
public void Reset(string name = "", uint itemCount = 0, uint items = 0)
{
ItemName = name;
diff --git a/UVtools.WPF/MainWindow.axaml.cs b/UVtools.WPF/MainWindow.axaml.cs
index 64306f2..131f48e 100644
--- a/UVtools.WPF/MainWindow.axaml.cs
+++ b/UVtools.WPF/MainWindow.axaml.cs
@@ -625,7 +625,7 @@ namespace UVtools.WPF
{
if(drive.DriveType != DriveType.Removable || !drive.IsReady) continue; // Not our target, skip
if (SlicerFile.FileFullPath.StartsWith(drive.Name)) continue; // File already on this device, skip
-
+
var header = drive.Name;
if (!string.IsNullOrWhiteSpace(drive.VolumeLabel))
{
@@ -758,7 +758,7 @@ namespace UVtools.WPF
}
}
- ShowProgressWindow($"Copying: {SlicerFile.Filename} to {path}", false);
+ ShowProgressWindow($"Copying: {SlicerFile.Filename} to {path}", true);
Progress.ItemName = "Copying";
@@ -783,17 +783,17 @@ namespace UVtools.WPF
Progress.ItemCount = (uint)(stream.Length / 1000000);
- using var token = new CancellationTokenSource();
+ bool isCopying = true;
try
{
- new Task(() =>
+ var task = new Task(() =>
{
- while (!token.IsCancellationRequested)
+ while (isCopying)
{
Progress.ProcessedItems = (uint)(stream.Position / 1000000);
Thread.Sleep(200);
}
- }, token.Token);
+ });
}
catch (Exception)
{
@@ -801,7 +801,7 @@ namespace UVtools.WPF
}
response = await remotePrinter.RequestUploadFile.SendRequest(remotePrinter, Progress, SlicerFile.Filename, httpContent);
- token.Cancel();
+ isCopying = false;
if (!response.IsSuccessStatusCode)
{
await this.MessageBoxError(response.ToString(), "Send to printer");
@@ -842,26 +842,120 @@ namespace UVtools.WPF
}
else
{
- await Task.Factory.StartNew(() =>
+ /*var copyResult = await Task.Factory.StartNew(() =>
{
try
{
- File.Copy(SlicerFile.FileFullPath, $"{Path.Combine(path, SlicerFile.Filename)}", true);
- return true;
+ var fileDest = Path.Combine(path, SlicerFile.Filename);
+ //File.Copy(SlicerFile.FileFullPath, $"{Path.Combine(path, SlicerFile.Filename)}", true);
+ var buffer = new byte[1024 * 1024]; // 1MB buffer
+
+ using var source = File.OpenRead(SlicerFile.FileFullPath);
+ using var dest = new FileStream(fileDest, FileMode.Create, FileAccess.Write);
+ //long totalBytes = 0;
+ //int currentBlockSize;
+
+ Progress.Reset("Megabyte(s)", (uint)(source.Length / 1000000));
+ var copyProgress = new Progress<long>(copiedBytes => Progress.ProcessedItems = (uint)(copiedBytes / 1000000));
+ source.CopyToAsync(dest, 0, copyProgress, Progress.Token).ConfigureAwait(false);
+
+ /*while ((currentBlockSize = source.Read(buffer)) > 0)
+ {
+ totalBytes += currentBlockSize;
+
+ dest.Write(buffer, 0, currentBlockSize);
+
+ if (Progress.Token.IsCancellationRequested)
+ {
+ // Delete dest file here
+ dest.Dispose();
+ File.Delete(fileDest);
+ return false;
+ }
+
+ Progress.ProcessedItems = (uint)(totalBytes / 1000000);
+ }*/
+
+ /* return true;
+ }
+ catch (OperationCanceledException) { }
+ catch (Exception exception)
+ {
+ Dispatcher.UIThread.InvokeAsync(async () =>
+ await this.MessageBoxError(exception.ToString(), "Unable to copy the file"));
+ }
+
+ return false;
+ });*/
+
+ bool copyResult = false;
+ var fileDest = Path.Combine(path, SlicerFile.Filename);
+ try
+ {
+ await using var source = File.OpenRead(SlicerFile.FileFullPath);
+ await using var dest = new FileStream(fileDest, FileMode.Create, FileAccess.Write);
+
+ Progress.Reset("Megabyte(s)", (uint)(source.Length / 1000000));
+ var copyProgress = new Progress<long>(copiedBytes => Progress.ProcessedItems = (uint)(copiedBytes / 1000000));
+ await source.CopyToAsync(dest, copyProgress, Progress.Token);
+
+ copyResult = true;
+ }
+ catch (OperationCanceledException)
+ {
+ try
+ {
+ if (File.Exists(fileDest)) File.Delete(fileDest);
}
- catch (OperationCanceledException) { }
- catch (Exception exception)
+ catch (Exception ex)
{
- Dispatcher.UIThread.InvokeAsync(async () =>
- await this.MessageBoxError(exception.ToString(), "Unable to copy the file"));
+ Debug.WriteLine(ex);
}
+
+ }
+ catch (Exception exception)
+ {
+ await this.MessageBoxError(exception.ToString(), "Unable to copy the file");
+ }
- return false;
- });
+ if(copyResult && menuItem.Tag is DriveInfo removableDrive && OperatingSystem.IsWindows() && Settings.General.SendToPromptForRemovableDeviceEject)
+ {
+ if (await this.MessageBoxQuestion(
+ $"File '{SlicerFile.Filename}' has copied successfully into {removableDrive.Name}\n" +
+ $"Do you want to eject the {removableDrive.Name} drive now?", "Copied ok, eject the drive?") == ButtonResult.Yes)
+ {
+ Progress.ResetAll($"Ejecting {removableDrive.Name}");
+ var ejectResult = await Task.Factory.StartNew(() =>
+ {
+ try
+ {
+ return SystemOS.Windows.USB.USBEject(removableDrive.Name);
+ }
+ catch (OperationCanceledException) { }
+ catch (Exception exception)
+ {
+ Dispatcher.UIThread.InvokeAsync(async () =>
+ await this.MessageBoxError(exception.ToString(), $"Unable to eject the drive {removableDrive.Name}"));
+ }
+
+ return false;
+ });
+
+ if (!ejectResult)
+ {
+ await this.MessageBoxError($"Unable to eject the drive {removableDrive.Name}\n\n" +
+ "Possible causes:\n" +
+ "- Drive may be busy or locked\n" +
+ "- Drive was already ejected\n" +
+ "- No permission to eject the drive\n" +
+ "- Another error while trying to eject the drive\n\n" +
+ "Please try to eject the drive manually.", $"Unable to eject the drive {removableDrive.Name}");
+ }
+ }
+ }
}
-
IsGUIEnabled = true;
}
@@ -917,7 +1011,7 @@ namespace UVtools.WPF
ShowLayer();
return;
}*/
- }
+ }
private void InitializeComponent()
{
diff --git a/UVtools.WPF/SystemOS/Windows/USB.cs b/UVtools.WPF/SystemOS/Windows/USB.cs
new file mode 100644
index 0000000..4c96e59
--- /dev/null
+++ b/UVtools.WPF/SystemOS/Windows/USB.cs
@@ -0,0 +1,130 @@
+using System;
+using System.Runtime.InteropServices;
+using System.Threading;
+
+namespace UVtools.WPF.SystemOS.Windows
+{
+ public static class USB
+ {
+ [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Auto)]
+ private static extern IntPtr CreateFile(
+ string lpFileName,
+ uint dwDesiredAccess,
+ uint dwShareMode,
+ IntPtr SecurityAttributes,
+ uint dwCreationDisposition,
+ uint dwFlagsAndAttributes,
+ IntPtr hTemplateFile
+ );
+
+ [DllImport("kernel32.dll", ExactSpelling = true, SetLastError = true, CharSet = CharSet.Auto)]
+ private static extern bool DeviceIoControl(
+ IntPtr hDevice,
+ uint dwIoControlCode,
+ IntPtr lpInBuffer,
+ uint nInBufferSize,
+ IntPtr lpOutBuffer,
+ uint nOutBufferSize,
+ out uint lpBytesReturned,
+ IntPtr lpOverlapped
+ );
+
+ [DllImport("kernel32.dll", ExactSpelling = true, SetLastError = true, CharSet = CharSet.Auto)]
+ private static extern bool DeviceIoControl(
+ IntPtr hDevice,
+ uint dwIoControlCode,
+ byte[] lpInBuffer,
+ uint nInBufferSize,
+ IntPtr lpOutBuffer,
+ uint nOutBufferSize,
+ out uint lpBytesReturned,
+ IntPtr lpOverlapped
+ );
+
+ [DllImport("kernel32.dll", SetLastError = true)]
+ [return: MarshalAs(UnmanagedType.Bool)]
+ private static extern bool CloseHandle(IntPtr hObject);
+
+ const uint GENERIC_READ = 0x80000000;
+ const uint GENERIC_WRITE = 0x40000000;
+ const int FILE_SHARE_READ = 0x1;
+ const int FILE_SHARE_WRITE = 0x2;
+ const int FSCTL_LOCK_VOLUME = 0x00090018;
+ const int FSCTL_DISMOUNT_VOLUME = 0x00090020;
+ const int IOCTL_STORAGE_EJECT_MEDIA = 0x2D4808;
+ const int IOCTL_STORAGE_MEDIA_REMOVAL = 0x002D4804;
+
+ /// <summary>
+ /// Get the USB handler
+ /// </summary>
+ /// <param name="driveLetter">This should be the drive letter. Format: F:/, C:/..</param>
+ public static IntPtr GetUSBHandler(string driveLetter)
+ {
+ var filename = @"\\.\" + driveLetter[0] + ":";
+ return CreateFile(filename, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, IntPtr.Zero, 0x3, 0, IntPtr.Zero);
+ }
+
+ /// <summary>
+ /// Eject an USB drive
+ /// </summary>
+ /// <param name="driveLetter">This should be the drive letter. Format: F:/, C:/..</param>
+ public static bool USBEject(string driveLetter)
+ {
+ return Eject(GetUSBHandler(driveLetter));
+ }
+
+ public static bool Eject(IntPtr handle)
+ {
+ var result = false;
+
+ if (LockVolume(handle) && DismountVolume(handle))
+ {
+ PreventRemovalOfVolume(handle, false);
+ result = AutoEjectVolume(handle);
+ }
+ CloseHandle(handle);
+ return result;
+ }
+
+ private static bool LockVolume(IntPtr handle, uint attempts = 10)
+ {
+ uint byteReturned;
+
+ for (uint i = 0; i < attempts; i++)
+ {
+ if (DeviceIoControl(handle, FSCTL_LOCK_VOLUME, IntPtr.Zero, 0, IntPtr.Zero, 0, out byteReturned, IntPtr.Zero))
+ {
+ return true;
+ }
+ Thread.Sleep(500);
+ }
+ return false;
+ }
+
+ private static bool PreventRemovalOfVolume(IntPtr handle, bool prevent)
+ {
+ byte[] buf = new byte[1];
+ uint retVal;
+
+ buf[0] = (prevent) ? (byte)1 : (byte)0;
+ return DeviceIoControl(handle, IOCTL_STORAGE_MEDIA_REMOVAL, buf, 1, IntPtr.Zero, 0, out retVal, IntPtr.Zero);
+ }
+
+ private static bool DismountVolume(IntPtr handle)
+ {
+ uint byteReturned;
+ return DeviceIoControl(handle, FSCTL_DISMOUNT_VOLUME, IntPtr.Zero, 0, IntPtr.Zero, 0, out byteReturned, IntPtr.Zero);
+ }
+
+ private static bool AutoEjectVolume(IntPtr handle)
+ {
+ uint byteReturned;
+ return DeviceIoControl(handle, IOCTL_STORAGE_EJECT_MEDIA, IntPtr.Zero, 0, IntPtr.Zero, 0, out byteReturned, IntPtr.Zero);
+ }
+
+ private static bool CloseVolume(IntPtr handle)
+ {
+ return CloseHandle(handle);
+ }
+ }
+}
diff --git a/UVtools.WPF/UserSettings.cs b/UVtools.WPF/UserSettings.cs
index 7e62c94..4933ec6 100644
--- a/UVtools.WPF/UserSettings.cs
+++ b/UVtools.WPF/UserSettings.cs
@@ -54,6 +54,7 @@ namespace UVtools.WPF
private bool _promptOverwriteFileSave = true;
private string _fileSaveNamePrefix;
private string _fileSaveNameSuffix = "_copy";
+ private bool _sendToPromptForRemovableDeviceEject = true;
private RangeObservableCollection<MappedDevice> _sendToCustomLocations = new();
@@ -175,6 +176,12 @@ namespace UVtools.WPF
set => RaiseAndSetIfChanged(ref _fileSaveNameSuffix, value);
}
+ public bool SendToPromptForRemovableDeviceEject
+ {
+ get => _sendToPromptForRemovableDeviceEject;
+ set => RaiseAndSetIfChanged(ref _sendToPromptForRemovableDeviceEject, value);
+ }
+
public RangeObservableCollection<MappedDevice> SendToCustomLocations
{
get => _sendToCustomLocations;
diff --git a/UVtools.WPF/Windows/SettingsWindow.axaml b/UVtools.WPF/Windows/SettingsWindow.axaml
index 16c3252..f7babc1 100644
--- a/UVtools.WPF/Windows/SettingsWindow.axaml
+++ b/UVtools.WPF/Windows/SettingsWindow.axaml
@@ -351,7 +351,12 @@
</Grid>
<StackPanel Margin="10" Orientation="Vertical" Spacing="10">
- <DataGrid
+
+ <CheckBox
+ IsChecked="{Binding Settings.General.SendToPromptForRemovableDeviceEject}"
+ Content="When send to a removable device, prompt for device ejection after copied [Windows only]"/>
+
+ <DataGrid
Name="SendToCustomLocationsGrid"
CanUserReorderColumns="True"
CanUserResizeColumns="True"