mirror of
https://github.com/MrUnknownDE/VRCX.git
synced 2026-04-25 01:33:51 +02:00
Electron support for Linux (#1074)
* init * SQLite changes * Move html folder, edit build scripts * AppApi interface * Build flags * AppApi inheritance * Finishing touches * Merge upstream changes * Test CI * Fix class inits * Rename AppApi * Merge upstream changes * Fix SQLiteLegacy on Linux, Add Linux interop, build tools * Linux specific localisation strings * Make it run * Bring back most of Linux functionality * Clean up * Fix TTS voices * Fix UI var * Changes * Electron minimise to tray * Remove separate toggle for WlxOverlay * Fixes * Touchups * Move csproj * Window zoom, Desktop Notifications, VR check on Linux * Fix desktop notifications, VR check spam * Fix building on Linux * Clean up * Fix WebApi headers * Rewrite VRCX updater * Clean up * Linux updater * Add Linux to build action * init * SQLite changes * Move html folder, edit build scripts * AppApi interface * Build flags * AppApi inheritance * Finishing touches * Merge upstream changes * Test CI * Fix class inits * Rename AppApi * Merge upstream changes * Fix SQLiteLegacy on Linux, Add Linux interop, build tools * Linux specific localisation strings * Make it run * Bring back most of Linux functionality * Clean up * Fix TTS voices * Changes * Electron minimise to tray * Remove separate toggle for WlxOverlay * Fixes * Touchups * Move csproj * Window zoom, Desktop Notifications, VR check on Linux * Fix desktop notifications, VR check spam * Fix building on Linux * Clean up * Fix WebApi headers * Rewrite VRCX updater * Clean up * Linux updater * Add Linux to build action * Test updater * Rebase and handle merge conflicts * Fix Linux updater * Fix Linux app restart * Fix friend order * Handle AppImageInstaller, show an install message on Linux * Updates to the AppImage installer * Fix Linux updater, fix set version, check for .NET, copy wine prefix * Handle random errors * Rotate tall prints * try fix Linux restart bug * Final --------- Co-authored-by: rs189 <35667100+rs189@users.noreply.github.com>
This commit is contained in:
274
Dotnet/WebApi.cs
274
Dotnet/WebApi.cs
@@ -1,22 +1,26 @@
|
||||
using CefSharp;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Net;
|
||||
using System.Runtime.Serialization.Formatters.Binary;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using Cookie = System.Net.Cookie;
|
||||
using System.Windows;
|
||||
using NLog;
|
||||
using Timer = System.Threading.Timer;
|
||||
|
||||
#if !LINUX
|
||||
using CefSharp;
|
||||
using System.Windows.Forms;
|
||||
#endif
|
||||
|
||||
namespace VRCX
|
||||
{
|
||||
public class WebApi
|
||||
{
|
||||
public static readonly WebApi Instance;
|
||||
private static readonly Logger Logger = LogManager.GetCurrentClassLogger();
|
||||
public static WebApi Instance;
|
||||
|
||||
public static bool ProxySet;
|
||||
public static string ProxyUrl = "";
|
||||
@@ -35,6 +39,10 @@ namespace VRCX
|
||||
|
||||
public WebApi()
|
||||
{
|
||||
#if LINUX
|
||||
if (Instance == null)
|
||||
Instance = this;
|
||||
#endif
|
||||
_cookieContainer = new CookieContainer();
|
||||
_timer = new Timer(TimerCallback, null, -1, -1);
|
||||
}
|
||||
@@ -45,12 +53,13 @@ namespace VRCX
|
||||
{
|
||||
SaveCookies();
|
||||
}
|
||||
catch
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger.Error($"Failed to save cookies: {e.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
internal void Init()
|
||||
public void Init()
|
||||
{
|
||||
SetProxy();
|
||||
LoadCookies();
|
||||
@@ -80,12 +89,16 @@ namespace VRCX
|
||||
catch (UriFormatException)
|
||||
{
|
||||
VRCXStorage.Instance.Set("VRCX_ProxyServer", string.Empty);
|
||||
MessageBox.Show("The proxy server URI you used is invalid.\nVRCX will close, please correct the proxy URI.", "Invalid Proxy URI", MessageBoxButton.OK);
|
||||
var message = "The proxy server URI you used is invalid.\nVRCX will close, please correct the proxy URI.";
|
||||
#if !LINUX
|
||||
System.Windows.Forms.MessageBox.Show(message, "Invalid Proxy URI", MessageBoxButtons.OK, MessageBoxIcon.Error);
|
||||
#endif
|
||||
Logger.Error(message);
|
||||
Environment.Exit(0);
|
||||
}
|
||||
}
|
||||
|
||||
internal void Exit()
|
||||
public void Exit()
|
||||
{
|
||||
_timer.Change(-1, -1);
|
||||
SaveCookies();
|
||||
@@ -97,32 +110,30 @@ namespace VRCX
|
||||
SaveCookies();
|
||||
}
|
||||
|
||||
internal void LoadCookies()
|
||||
private void LoadCookies()
|
||||
{
|
||||
SQLiteLegacy.Instance.ExecuteNonQuery("CREATE TABLE IF NOT EXISTS `cookies` (`key` TEXT PRIMARY KEY, `value` TEXT)");
|
||||
SQLiteLegacy.Instance.Execute((values) =>
|
||||
{
|
||||
try
|
||||
var values = SQLiteLegacy.Instance.Execute("SELECT `value` FROM `cookies` WHERE `key` = @key",
|
||||
new Dictionary<string, object>
|
||||
{
|
||||
using (var stream = new MemoryStream(Convert.FromBase64String((string)values[0])))
|
||||
{
|
||||
_cookieContainer = new CookieContainer();
|
||||
_cookieContainer.Add(System.Text.Json.JsonSerializer.Deserialize<CookieCollection>(stream));
|
||||
//_cookieContainer = (CookieContainer)new BinaryFormatter().Deserialize(stream);
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
},
|
||||
"SELECT `value` FROM `cookies` WHERE `key` = @key",
|
||||
new Dictionary<string, object>() {
|
||||
{"@key", "default"}
|
||||
{ "@key", "default" }
|
||||
}
|
||||
);
|
||||
try
|
||||
{
|
||||
var item = (object[])values.Item2[0];
|
||||
using var stream = new MemoryStream(Convert.FromBase64String((string)item[0]));
|
||||
_cookieContainer = new CookieContainer();
|
||||
_cookieContainer.Add(System.Text.Json.JsonSerializer.Deserialize<CookieCollection>(stream));
|
||||
// _cookieContainer = (CookieContainer)new BinaryFormatter().Deserialize(stream); // from .NET framework
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger.Error($"Failed to load cookies: {e.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
internal void SaveCookies()
|
||||
public void SaveCookies()
|
||||
{
|
||||
if (_cookieDirty == false)
|
||||
{
|
||||
@@ -148,8 +159,9 @@ namespace VRCX
|
||||
}
|
||||
_cookieDirty = false;
|
||||
}
|
||||
catch
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger.Error($"Failed to save cookies: {e.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -157,19 +169,15 @@ namespace VRCX
|
||||
{
|
||||
_cookieDirty = true; // force cookies to be saved for lastUserLoggedIn
|
||||
|
||||
using (var memoryStream = new MemoryStream())
|
||||
{
|
||||
System.Text.Json.JsonSerializer.Serialize(memoryStream, _cookieContainer.GetAllCookies());
|
||||
//new BinaryFormatter().Serialize(memoryStream, _cookieContainer);
|
||||
return Convert.ToBase64String(memoryStream.ToArray());
|
||||
}
|
||||
using var memoryStream = new MemoryStream();
|
||||
System.Text.Json.JsonSerializer.Serialize(memoryStream, _cookieContainer.GetAllCookies());
|
||||
return Convert.ToBase64String(memoryStream.ToArray());
|
||||
}
|
||||
|
||||
public void SetCookies(string cookies)
|
||||
{
|
||||
using (var stream = new MemoryStream(Convert.FromBase64String(cookies)))
|
||||
{
|
||||
//_cookieContainer = (CookieContainer)new BinaryFormatter().Deserialize(stream);
|
||||
_cookieContainer = new CookieContainer();
|
||||
_cookieContainer.Add(System.Text.Json.JsonSerializer.Deserialize<CookieCollection>(stream));
|
||||
}
|
||||
@@ -200,7 +208,7 @@ namespace VRCX
|
||||
}
|
||||
}
|
||||
var imageData = options["imageData"] as string;
|
||||
byte[] fileToUpload = AppApi.Instance.ResizeImageToFitLimits(Convert.FromBase64String(imageData), false);
|
||||
byte[] fileToUpload = Program.AppApiInstance.ResizeImageToFitLimits(Convert.FromBase64String(imageData), false);
|
||||
string fileFormKey = "image";
|
||||
string fileName = "image.png";
|
||||
string fileMimeType = "image/png";
|
||||
@@ -269,7 +277,7 @@ namespace VRCX
|
||||
}
|
||||
var imageData = options["imageData"] as string;
|
||||
var matchingDimensions = options["matchingDimensions"] as bool? ?? false;
|
||||
byte[] fileToUpload = AppApi.Instance.ResizeImageToFitLimits(Convert.FromBase64String(imageData), matchingDimensions);
|
||||
byte[] fileToUpload = Program.AppApiInstance.ResizeImageToFitLimits(Convert.FromBase64String(imageData), matchingDimensions);
|
||||
|
||||
string fileFormKey = "file";
|
||||
string fileName = "blob";
|
||||
@@ -306,7 +314,7 @@ namespace VRCX
|
||||
request.ContentType = "multipart/form-data; boundary=" + boundary;
|
||||
var requestStream = request.GetRequestStream();
|
||||
var imageData = options["imageData"] as string;
|
||||
var fileToUpload = AppApi.Instance.ResizePrintImage(Convert.FromBase64String(imageData));
|
||||
var fileToUpload = Program.AppApiInstance.ResizePrintImage(Convert.FromBase64String(imageData));
|
||||
const string fileFormKey = "image";
|
||||
const string fileName = "image";
|
||||
const string fileMimeType = "image/png";
|
||||
@@ -347,159 +355,135 @@ namespace VRCX
|
||||
await requestStream.WriteAsync(endBytes);
|
||||
requestStream.Close();
|
||||
}
|
||||
|
||||
public async Task<string> ExecuteJson(string options)
|
||||
{
|
||||
var data = JsonConvert.DeserializeObject<Dictionary<string, object>>(options);
|
||||
Logger.Info(JsonConvert.SerializeObject(data));
|
||||
var result = await Execute(data);
|
||||
return System.Text.Json.JsonSerializer.Serialize(new
|
||||
{
|
||||
status = result.Item1,
|
||||
message = result.Item2
|
||||
});
|
||||
}
|
||||
|
||||
#pragma warning disable CS4014
|
||||
|
||||
public async void Execute(IDictionary<string, object> options, IJavascriptCallback callback)
|
||||
public async Task<Tuple<int, string>> Execute(IDictionary<string, object> options)
|
||||
{
|
||||
try
|
||||
{
|
||||
// TODO: switch to HttpClient
|
||||
#pragma warning disable SYSLIB0014 // Type or member is obsolete
|
||||
var request = WebRequest.CreateHttp((string)options["url"]);
|
||||
#pragma warning restore SYSLIB0014 // Type or member is obsolete
|
||||
if (ProxySet)
|
||||
request.Proxy = Proxy;
|
||||
|
||||
|
||||
request.CookieContainer = _cookieContainer;
|
||||
request.KeepAlive = true;
|
||||
request.UserAgent = Program.Version;
|
||||
request.AutomaticDecompression = DecompressionMethods.All;
|
||||
|
||||
if (options.TryGetValue("headers", out object headers))
|
||||
if (options.TryGetValue("headers", out var headers))
|
||||
{
|
||||
foreach (var header in (IEnumerable<KeyValuePair<string, object>>)headers)
|
||||
Dictionary<string, string> headersDict;
|
||||
if (headers.GetType() == typeof(JObject))
|
||||
{
|
||||
var key = header.Key;
|
||||
var value = header.Value.ToString();
|
||||
headersDict = ((JObject)headers).ToObject<Dictionary<string, string>>();
|
||||
}
|
||||
else
|
||||
{
|
||||
var headersKvp = (IEnumerable<KeyValuePair<string, object>>)headers;
|
||||
headersDict = new Dictionary<string, string>();
|
||||
foreach (var (key, value) in headersKvp)
|
||||
headersDict.Add(key, value.ToString());
|
||||
}
|
||||
|
||||
foreach (var (key, value) in headersDict)
|
||||
{
|
||||
if (string.Compare(key, "Content-Type", StringComparison.OrdinalIgnoreCase) == 0)
|
||||
{
|
||||
request.ContentType = value;
|
||||
}
|
||||
else if (string.Compare(key, "Referer", StringComparison.OrdinalIgnoreCase) == 0)
|
||||
{
|
||||
request.Referer = value;
|
||||
}
|
||||
else
|
||||
{
|
||||
request.Headers.Add(key, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (options.TryGetValue("method", out object method))
|
||||
if (options.TryGetValue("method", out var method))
|
||||
{
|
||||
var _method = (string)method;
|
||||
request.Method = _method;
|
||||
|
||||
if (string.Compare(_method, "GET", StringComparison.OrdinalIgnoreCase) != 0 &&
|
||||
options.TryGetValue("body", out object body) == true)
|
||||
request.Method = (string)method;
|
||||
if (string.Compare(request.Method, "GET", StringComparison.OrdinalIgnoreCase) != 0 &&
|
||||
options.TryGetValue("body", out var body))
|
||||
{
|
||||
using (var stream = await request.GetRequestStreamAsync())
|
||||
using (var streamWriter = new StreamWriter(stream))
|
||||
{
|
||||
await streamWriter.WriteAsync((string)body);
|
||||
}
|
||||
await using var bodyStream = await request.GetRequestStreamAsync();
|
||||
await using var streamWriter = new StreamWriter(bodyStream);
|
||||
await streamWriter.WriteAsync((string)body);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (options.TryGetValue("uploadImage", out _))
|
||||
{
|
||||
await ImageUpload(request, options);
|
||||
}
|
||||
|
||||
|
||||
if (options.TryGetValue("uploadFilePUT", out _))
|
||||
{
|
||||
await UploadFilePut(request, options);
|
||||
}
|
||||
|
||||
if (options.TryGetValue("uploadImageLegacy", out _))
|
||||
{
|
||||
await LegacyImageUpload(request, options);
|
||||
}
|
||||
|
||||
|
||||
if (options.TryGetValue("uploadImagePrint", out _))
|
||||
{
|
||||
await PrintImageUpload(request, options);
|
||||
|
||||
using var response = await request.GetResponseAsync() as HttpWebResponse;
|
||||
if (response?.Headers["Set-Cookie"] != null)
|
||||
_cookieDirty = true;
|
||||
|
||||
await using var imageStream = response.GetResponseStream();
|
||||
using var streamReader = new StreamReader(imageStream);
|
||||
if (response.ContentType.Contains("image/") ||
|
||||
response.ContentType.Contains("application/octet-stream"))
|
||||
{
|
||||
// base64 response data for image
|
||||
using var memoryStream = new MemoryStream();
|
||||
await imageStream.CopyToAsync(memoryStream);
|
||||
return new Tuple<int, string>(
|
||||
(int)response.StatusCode,
|
||||
$"data:image/png;base64,{Convert.ToBase64String(memoryStream.ToArray())}"
|
||||
);
|
||||
}
|
||||
|
||||
try
|
||||
return new Tuple<int, string>(
|
||||
(int)response.StatusCode,
|
||||
await streamReader.ReadToEndAsync()
|
||||
);
|
||||
}
|
||||
catch (WebException webException)
|
||||
{
|
||||
if (webException.Response is HttpWebResponse response)
|
||||
{
|
||||
using (var response = await request.GetResponseAsync() as HttpWebResponse)
|
||||
{
|
||||
if (response.Headers["Set-Cookie"] != null)
|
||||
{
|
||||
_cookieDirty = true;
|
||||
}
|
||||
using (var stream = response.GetResponseStream())
|
||||
using (var streamReader = new StreamReader(stream))
|
||||
{
|
||||
if (callback.CanExecute == true)
|
||||
{
|
||||
if (response.ContentType.Contains("image/") || response.ContentType.Contains("application/octet-stream"))
|
||||
{
|
||||
// base64 response data for image
|
||||
using (var memoryStream = new MemoryStream())
|
||||
{
|
||||
await stream.CopyToAsync(memoryStream);
|
||||
callback.ExecuteAsync(null, new
|
||||
{
|
||||
data = $"data:image/png;base64,{Convert.ToBase64String(memoryStream.ToArray())}",
|
||||
status = response.StatusCode
|
||||
});
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
callback.ExecuteAsync(null, new
|
||||
{
|
||||
data = await streamReader.ReadToEndAsync(),
|
||||
status = response.StatusCode
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (WebException webException)
|
||||
{
|
||||
if (webException.Response is HttpWebResponse response)
|
||||
{
|
||||
if (response.Headers["Set-Cookie"] != null)
|
||||
{
|
||||
_cookieDirty = true;
|
||||
}
|
||||
using (var stream = response.GetResponseStream())
|
||||
using (var streamReader = new StreamReader(stream))
|
||||
{
|
||||
if (callback.CanExecute == true)
|
||||
{
|
||||
callback.ExecuteAsync(null, new
|
||||
{
|
||||
data = await streamReader.ReadToEndAsync(),
|
||||
status = response.StatusCode
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (callback.CanExecute == true)
|
||||
{
|
||||
callback.ExecuteAsync(webException.Message, null);
|
||||
}
|
||||
if (response.Headers["Set-Cookie"] != null)
|
||||
_cookieDirty = true;
|
||||
|
||||
await using var stream = response.GetResponseStream();
|
||||
using var streamReader = new StreamReader(stream);
|
||||
return new Tuple<int, string>(
|
||||
(int)response.StatusCode,
|
||||
await streamReader.ReadToEndAsync()
|
||||
);
|
||||
}
|
||||
|
||||
return new Tuple<int, string>(
|
||||
-1,
|
||||
webException.Message
|
||||
);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
if (callback.CanExecute == true)
|
||||
{
|
||||
// FIXME: 브라우저는 종료되었는데 얘는 이후에 실행되면 터짐
|
||||
callback.ExecuteAsync(e.Message, null);
|
||||
}
|
||||
return new Tuple<int, string>(
|
||||
-1,
|
||||
e.Message
|
||||
);
|
||||
}
|
||||
|
||||
callback.Dispose();
|
||||
}
|
||||
|
||||
#pragma warning restore CS4014
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user