/*
* GNU AFFERO GENERAL PUBLIC LICENSE
* Version 3, 19 November 2007
* Copyright (C) 2007 Free Software Foundation, Inc.
* 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
///
/// Gets or sets this request type
///
public RequestType Type
{
get => _type;
set => RaiseAndSetIfChanged(ref _type, value);
}
///
/// Gets or sets this request method
///
public RequestMethod Method
{
get => _method;
set => RaiseAndSetIfChanged(ref _method, value);
}
///
/// Gets or sets the request path, eg: print/file/{0}
///
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
///
/// Gets the path with formatted arguments
///
///
///
public string GetFormattedPath(params object?[] parameters) => string.Format(_path, parameters);
public async Task 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.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.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 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
}