Files
UVtools/UVtools.Core/Network/RemotePrinterRequest.cs
T
Tiago Conceição 816898836b v3.9.0
- **File formats:**
  - (Add) File extension: .gktwo.ctb to `ChituboxFile` to be able to convert files for UniFormation GKtwo under special CTB format
  - (Add) UniFormation GKtwo compatibility under CTB format, if exporting JXS rename to CTB before open
  - (Fix) CTB, CBDDLP, PHOTON, FDG, PHZ: Read and write files larger then 4GB (#608)
- **PCB Exposure:**
  - (Add) Offset X/Y to offset the PCB from it origin
  - (Add) Allow to toggle between "Show preview image cropped by it bounds" and "Show full preview image (The final result)"
  - (Improvement) Use rectangle instead of line for center line primitive (#607)
  - (Fix) Implement rotation to polygon and center line primitives (#607)
  - (Fix) Macros in a single line was not being parsed (#607)
  - (Fix) Invert color per file was not affecting primitives
- **Network printers:**
  - (Add) Socket requests with TCP and UDP
  - (Add) AnyCubic printer preset (However it can't upload a file)
  - (Add) Scripts in request path to allow a first request to fetch data to the final request:
    -  A script starts with **<\?** and ends with **?>**
    -  First parameter is the first request to get response content from
    -  Second parameter is the regex pattern to match content with
    -  Third parameter is the final request that supports a parameter from regex matching group, eg: **{#1}** is match Group[1] value
    -  **Example:** <\? getfiles > {0}\/(\d+\.[\da-zA-Z]+), > printfile,{#1} ?>
  - (Change) Allow to print a filename without send it when upload request path is empty
  - (Fix) Do not show printers with empty requests
- (Change) Default layer compression to Lz4 instead of Png
- (Improvement) Application is now culture aware but set part of `NumberFormat` to the `InvariantCulture.NumberFormat`
- (Improvement) Material cost now show with the current culture currency symbol due previous change
- (Improvement) Better submit of bug reports using sections and forms
- (Improvement) Linux: AppImage now have a help manual with possible arguments and parameters
- (Improvement) macOS: Codesign app on auto-installer and auto-upgrade to bypass arm64 run restriction (#431)
- (Improvement) macOS: Rebuilt arm64 libcvextern.dylib to run with less dependencies (#431)
- (Improvement) macOS: Try to show missing dependencies from openCV (if any) on the error message
- (Fix) UI: layers sorted lexicographically instead of numerically in the issues list view (#611)
- (Fix) PrusaSlicer printer parameters: UniFormation GKtwo
2022-12-01 04:27:44 +00:00

346 lines
11 KiB
C#

/*
* GNU AFFERO GENERAL PUBLIC LICENSE
* Version 3, 19 November 2007
* Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
* Everyone is permitted to copy and distribute verbatim copies
* of this license document, but changing it is not allowed.
*/
using System;
using System.ComponentModel;
using System.IO;
using System.Net;
using System.Net.Http;
using System.Net.Sockets;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
using UVtools.Core.Extensions;
using UVtools.Core.Objects;
using UVtools.Core.Operations;
namespace UVtools.Core.Network;
public class RemotePrinterRequest : BindableBase
{
#region Enums
public enum RequestMethod : byte
{
[Description("HTTP GET")]
GET,
[Description("HTTP POST")]
POST,
[Description("HTTP PUT")]
PUT,
[Description("SOCK TCP")]
TCP,
[Description("SOCK UDP")]
UDP
}
public enum RequestType : byte
{
UploadFile,
PrintFile,
DeleteFile,
PausePrint,
ResumePrint,
StopPrint,
GetFiles,
PrintStatus,
PrinterInfo,
}
#endregion
#region Members
private RequestType _type;
private RequestMethod _method;
private string _path = string.Empty;
#endregion
#region Properties
/// <summary>
/// Gets or sets this request type
/// </summary>
public RequestType Type
{
get => _type;
set => RaiseAndSetIfChanged(ref _type, value);
}
/// <summary>
/// Gets or sets this request method
/// </summary>
public RequestMethod Method
{
get => _method;
set => RaiseAndSetIfChanged(ref _method, value);
}
/// <summary>
/// Gets or sets the request path, eg: print/file/{0}
/// </summary>
public string Path
{
get => _path;
set
{
if (!string.IsNullOrWhiteSpace(value))
{
value = value.Trim();
if (value[0] == '/') value = value.Remove(0, 1);
if (value[^1] == '/') value = value[..^2];
value = value.Trim();
}
if(!RaiseAndSetIfChanged(ref _path, value)) return;
RaisePropertyChanged(nameof(IsValid));
}
}
public bool IsValid => !string.IsNullOrWhiteSpace(_path);
#endregion
#region Constructors
public RemotePrinterRequest() { }
public RemotePrinterRequest(RequestType type, RequestMethod method, string path = "")
{
_type = type;
_method = method;
Path = path;
}
#endregion
#region Methods
/// <summary>
/// Gets the path with formatted arguments
/// </summary>
/// <param name="parameters"></param>
/// <returns></returns>
public string GetFormattedPath(params object?[] parameters) => string.Format(_path, parameters);
public async Task<HttpResponseMessage> SendRequest(string host, ushort port = 0, OperationProgress? progress = null, string? param1 = null, string? uploadFilePath = null)
{
string url = $"http://{host}";
if (port > 0) url += $":{port}";
progress ??= new OperationProgress();
progress.Title = $"Sending {_method} request to: {url}";
progress.ItemName = "Megabyte(s)";
progress.CanCancel = true;
if(_path == "{0}") return new HttpResponseMessage(HttpStatusCode.OK);
string? formattedPathWithParameter = null;
if (!string.IsNullOrWhiteSpace(_path))
{
if (_path.StartsWith("<$") && _path.EndsWith("$>"))
{
var newPath = _path[2..^2].Trim();
var split = newPath.Split('>', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
if (split.Length < 3) throw new ArgumentException($"Path '{_path}' is malformed", nameof(Path));
var firstRequest = Clone();
firstRequest.Path = split[0];
using var response = await firstRequest.SendRequest(host, port, progress, param1);
if (!response.IsSuccessStatusCode) return response;
var content = await response.Content.ReadAsStringAsync(progress.Token);
if (content is null) throw new InvalidDataException($"The request to {firstRequest.Path} returned no data");
if (!string.IsNullOrWhiteSpace(param1)) split[1] = string.Format(split[1], Regex.Escape(param1));
var match = Regex.Match(content, split[1]);
if (!match.Success || match.Groups.Count < 2) throw new InvalidDataException($"Unable to parse any data from the content.\nRegex: {split[1]}\nContent: {content}");
for (int i = 1; i < match.Groups.Count; i++)
{
split[2] = split[2].Replace($"{{#{i}}}", match.Groups[i].Value);
}
formattedPathWithParameter = string.Format(split[2], param1);
}
else
{
formattedPathWithParameter = GetFormattedPath(param1);
}
}
if (!string.IsNullOrWhiteSpace(_path) && !string.IsNullOrWhiteSpace(formattedPathWithParameter)) url += $"/{formattedPathWithParameter}";
switch (_method)
{
case RequestMethod.GET:
{
using var response = await NetworkExtensions.HttpClient.GetAsync(url, progress.Token);
return response;
}
case RequestMethod.POST:
{
if (string.IsNullOrWhiteSpace(uploadFilePath)) return await NetworkExtensions.HttpClient.PostAsync(url, null, progress.Token);
await using var stream = File.OpenRead(uploadFilePath);
using var httpContent = new StreamContent(stream);
progress.ItemCount = (uint) (stream.Length / 1048576);
bool isCopying = true;
try
{
var task = new Task(() =>
{
while (isCopying)
{
progress.ProcessedItems = (uint) (stream.Position / 1048576);
Thread.Sleep(200);
}
});
}
catch (Exception)
{
// ignored
}
using var response = await NetworkExtensions.HttpClient.PostAsync(url, httpContent, progress.Token);
isCopying = false;
return response;
}
case RequestMethod.PUT:
{
if (string.IsNullOrWhiteSpace(uploadFilePath)) return await NetworkExtensions.HttpClient.PutAsync(url, null, progress.Token);
await using var stream = File.OpenRead(uploadFilePath);
using var httpContent = new StreamContent(stream);
progress.ItemCount = (uint)(stream.Length / 1048576);
bool isCopying = true;
try
{
var task = new Task(() =>
{
while (isCopying)
{
progress.ProcessedItems = (uint)(stream.Position / 1048576);
Thread.Sleep(200);
}
});
}
catch (Exception)
{
// ignored
}
using var response = await NetworkExtensions.HttpClient.PutAsync(url, httpContent, progress.Token);
isCopying = false;
return response;
}
case RequestMethod.TCP:
case RequestMethod.UDP:
{
using var socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, _method == RequestMethod.TCP ? ProtocolType.Tcp : ProtocolType.Udp)
{
ReceiveTimeout = 10
};
var ipAd = IPAddress.Parse(host);
await socket.ConnectAsync(ipAd, port);
if (string.IsNullOrWhiteSpace(uploadFilePath))
{
if (string.IsNullOrWhiteSpace(formattedPathWithParameter))
{
await socket.SendAsync(ReadOnlyMemory<byte>.Empty, SocketFlags.None);
}
else
{
var requestBytes = Encoding.UTF8.GetBytes(formattedPathWithParameter);
var bytesSent = 0;
while (bytesSent < requestBytes.Length)
{
bytesSent += await socket.SendAsync(requestBytes.AsMemory(bytesSent), SocketFlags.None);
}
}
}
else // file upload
{
if (string.IsNullOrWhiteSpace(formattedPathWithParameter))
{
await socket.SendFileAsync(uploadFilePath, progress.Token);
}
else
{
var preRequestBytes = Encoding.UTF8.GetBytes(formattedPathWithParameter);
await socket.SendFileAsync(uploadFilePath, preRequestBytes, ReadOnlyMemory<byte>.Empty, TransmitFileOptions.UseDefaultWorkerThread, progress.Token);
}
}
var responseBytes = new byte[1024];
var sb = new StringBuilder();
int bytesReceived; // Receiving 0 bytes means EOF has been reached
while ((bytesReceived = await socket.ReceiveAsync(responseBytes, SocketFlags.None, progress.Token)) > 0)
{
var result = Encoding.UTF8.GetString(responseBytes, 0, bytesReceived);
sb.Append(result);
if (result.EndsWith(",end")) break;
}
socket.Shutdown(SocketShutdown.Both);
return new HttpResponseMessage(HttpStatusCode.OK)
{
Content = new StringContent(sb.ToString()),
};
}
default:
throw new ArgumentOutOfRangeException(nameof(Method));
}
}
public async Task<HttpResponseMessage> SendRequest(RemotePrinter remotePrinter,
OperationProgress? progress = null, string? param1 = null, string? uploadFilePath = null)
=> await SendRequest(remotePrinter.Host, remotePrinter.Port, progress, param1, uploadFilePath);
public RemotePrinterRequest Clone()
{
return (RemotePrinterRequest)MemberwiseClone();
}
public override string ToString()
{
return _path;
}
protected bool Equals(RemotePrinterRequest other)
{
return _path == other._path;
}
public override bool Equals(object? obj)
{
if (ReferenceEquals(null, obj)) return false;
if (ReferenceEquals(this, obj)) return true;
if (obj.GetType() != this.GetType()) return false;
return Equals((RemotePrinterRequest)obj);
}
public override int GetHashCode()
{
return (_path != null ? _path.GetHashCode() : 0);
}
#endregion
}