- tweak: remove dependency from Nebula.Shared
This commit is contained in:
3
.gitignore
vendored
3
.gitignore
vendored
@@ -3,4 +3,5 @@ obj/
|
||||
/packages/
|
||||
riderModule.iml
|
||||
/_ReSharper.Caches/
|
||||
release/
|
||||
release/
|
||||
publish/
|
||||
@@ -1,8 +1,6 @@
|
||||
using Avalonia;
|
||||
using Avalonia.Controls.ApplicationLifetimes;
|
||||
using Avalonia.Markup.Xaml;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Nebula.Shared;
|
||||
|
||||
namespace Nebula.UpdateResolver;
|
||||
|
||||
@@ -15,15 +13,11 @@ public partial class App : Application
|
||||
|
||||
public override void OnFrameworkInitializationCompleted()
|
||||
{
|
||||
var services = new ServiceCollection();
|
||||
services.AddServices();
|
||||
services.AddTransient<MainWindow>();
|
||||
|
||||
var serviceProvider = services.BuildServiceProvider();
|
||||
|
||||
|
||||
if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
|
||||
{
|
||||
desktop.MainWindow = serviceProvider.GetService<MainWindow>();
|
||||
desktop.MainWindow = new MainWindow();
|
||||
}
|
||||
|
||||
base.OnFrameworkInitializationCompleted();
|
||||
|
||||
16
Nebula.UpdateResolver/Configuration/ConVar.cs
Normal file
16
Nebula.UpdateResolver/Configuration/ConVar.cs
Normal file
@@ -0,0 +1,16 @@
|
||||
using System;
|
||||
|
||||
namespace Nebula.UpdateResolver.Configuration;
|
||||
|
||||
public class ConVar<T>
|
||||
{
|
||||
public ConVar(string name, T? defaultValue = default)
|
||||
{
|
||||
Name = name ?? throw new ArgumentNullException(nameof(name));
|
||||
DefaultValue = defaultValue;
|
||||
}
|
||||
|
||||
public string Name { get; }
|
||||
public Type Type => typeof(T);
|
||||
public T? DefaultValue { get; }
|
||||
}
|
||||
14
Nebula.UpdateResolver/Configuration/ConVarBuilder.cs
Normal file
14
Nebula.UpdateResolver/Configuration/ConVarBuilder.cs
Normal file
@@ -0,0 +1,14 @@
|
||||
using System;
|
||||
|
||||
namespace Nebula.UpdateResolver.Configuration;
|
||||
|
||||
public static class ConVarBuilder
|
||||
{
|
||||
public static ConVar<T> Build<T>(string name, T? defaultValue = default)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(name))
|
||||
throw new ArgumentException("ConVar name cannot be null or whitespace.", nameof(name));
|
||||
|
||||
return new ConVar<T>(name, defaultValue);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
using System;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.IO;
|
||||
using System.Text.Json;
|
||||
|
||||
namespace Nebula.UpdateResolver.Configuration;
|
||||
|
||||
public static class ConfigurationStandalone
|
||||
{
|
||||
private static FileApi _fileApi = new FileApi(Path.Join(MainWindow.RootPath, "config"));
|
||||
|
||||
public static T? GetConfigValue<T>(ConVar<T> conVar)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(conVar);
|
||||
|
||||
try
|
||||
{
|
||||
if (_fileApi.TryOpen(GetFileName(conVar), out var stream))
|
||||
using (stream)
|
||||
{
|
||||
var obj = JsonSerializer.Deserialize<T>(stream);
|
||||
if (obj != null)
|
||||
{
|
||||
Console.WriteLine($"Successfully loaded config: {conVar.Name}");
|
||||
return obj;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Console.WriteLine($"Error loading config for {conVar.Name}: {e.Message}");
|
||||
}
|
||||
|
||||
Console.WriteLine($"Using default value for config: {conVar.Name}");
|
||||
return conVar.DefaultValue;
|
||||
}
|
||||
|
||||
public static bool TryGetConfigValue<T>(ConVar<T> conVar,
|
||||
[NotNullWhen(true)] out T? value)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(conVar);
|
||||
value = default;
|
||||
try
|
||||
{
|
||||
if (_fileApi.TryOpen(GetFileName(conVar), out var stream))
|
||||
using (stream)
|
||||
{
|
||||
var obj = JsonSerializer.Deserialize<T>(stream);
|
||||
if (obj != null)
|
||||
{
|
||||
Console.WriteLine($"Successfully loaded config: {conVar.Name}");
|
||||
value = obj;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Console.WriteLine($"Error loading config for {conVar.Name}: {e.Message}");
|
||||
}
|
||||
|
||||
Console.WriteLine($"Using default value for config: {conVar.Name}");
|
||||
return false;
|
||||
}
|
||||
|
||||
public static void SetConfigValue<T>(ConVar<T> conVar, T value)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(conVar);
|
||||
if (value == null) throw new ArgumentNullException(nameof(value));
|
||||
|
||||
if (!conVar.Type.IsInstanceOfType(value))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var serializedData = JsonSerializer.Serialize(value);
|
||||
|
||||
using var stream = new MemoryStream();
|
||||
using var writer = new StreamWriter(stream);
|
||||
writer.Write(serializedData);
|
||||
writer.Flush();
|
||||
stream.Seek(0, SeekOrigin.Begin);
|
||||
|
||||
_fileApi.Save(GetFileName(conVar), stream);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
private static string GetFileName<T>(ConVar<T> conVar)
|
||||
{
|
||||
return $"{conVar.Name}.json";
|
||||
}
|
||||
}
|
||||
88
Nebula.UpdateResolver/FileApi.cs
Normal file
88
Nebula.UpdateResolver/FileApi.cs
Normal file
@@ -0,0 +1,88 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
|
||||
namespace Nebula.UpdateResolver;
|
||||
|
||||
public sealed class FileApi
|
||||
{
|
||||
public string RootPath;
|
||||
|
||||
public FileApi(string rootPath)
|
||||
{
|
||||
RootPath = rootPath;
|
||||
}
|
||||
|
||||
public bool TryOpen(string path,[NotNullWhen(true)] out Stream? stream)
|
||||
{
|
||||
var fullPath = Path.Join(RootPath, path);
|
||||
if (File.Exists(fullPath))
|
||||
try
|
||||
{
|
||||
stream = new FileStream(fullPath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
|
||||
return true;
|
||||
}
|
||||
catch
|
||||
{
|
||||
stream = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
stream = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
public bool Save(string path, Stream input)
|
||||
{
|
||||
var currPath = Path.Join(RootPath, path);
|
||||
|
||||
try
|
||||
{
|
||||
var dirInfo = new DirectoryInfo(Path.GetDirectoryName(currPath) ?? throw new InvalidOperationException());
|
||||
if (!dirInfo.Exists) dirInfo.Create();
|
||||
|
||||
using var stream = new FileStream(currPath, FileMode.Create, FileAccess.Write, FileShare.None);
|
||||
input.CopyTo(stream);
|
||||
return true;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public bool Remove(string path)
|
||||
{
|
||||
var fullPath = Path.Join(RootPath, path);
|
||||
try
|
||||
{
|
||||
if (File.Exists(fullPath))
|
||||
{
|
||||
File.Delete(fullPath);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Log exception if necessary
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public bool Has(string path)
|
||||
{
|
||||
var fullPath = Path.Join(RootPath, path);
|
||||
return File.Exists(fullPath);
|
||||
}
|
||||
|
||||
private IEnumerable<string> GetAllFiles(){
|
||||
|
||||
if(!Directory.Exists(RootPath)) return [];
|
||||
return Directory.EnumerateFiles(RootPath, "*.*", SearchOption.AllDirectories).Select(p=>p.Replace(RootPath,"").Substring(1));
|
||||
}
|
||||
|
||||
public IEnumerable<string> AllFiles => GetAllFiles();
|
||||
}
|
||||
@@ -7,75 +7,77 @@ using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Avalonia.Controls;
|
||||
using Nebula.Shared.FileApis;
|
||||
using Nebula.Shared.FileApis.Interfaces;
|
||||
using Nebula.Shared.Models;
|
||||
using Nebula.Shared.Services;
|
||||
using Tmds.DBus.Protocol;
|
||||
using Nebula.UpdateResolver.Configuration;
|
||||
using Nebula.UpdateResolver.Rest;
|
||||
|
||||
namespace Nebula.UpdateResolver;
|
||||
|
||||
public partial class MainWindow : Window
|
||||
{
|
||||
private readonly ConfigurationService _configurationService;
|
||||
private readonly RestService _restService;
|
||||
private readonly HttpClient _httpClient = new HttpClient();
|
||||
public FileApi FileApi { get; set; }
|
||||
public static readonly string RootPath = Path.Join(Environment.GetFolderPath(
|
||||
Environment.SpecialFolder.ApplicationData), "Datum");
|
||||
|
||||
public MainWindow(FileService fileService, ConfigurationService configurationService, RestService restService)
|
||||
private readonly HttpClient _httpClient = new HttpClient();
|
||||
public readonly FileApi FileApi = new FileApi(Path.Join(RootPath,"app"));
|
||||
|
||||
public MainWindow()
|
||||
{
|
||||
_configurationService = configurationService;
|
||||
_restService = restService;
|
||||
InitializeComponent();
|
||||
FileApi = (FileApi)fileService.CreateFileApi("app");
|
||||
|
||||
Start();
|
||||
}
|
||||
|
||||
private async Task Start()
|
||||
{
|
||||
var info = await EnsureFiles();
|
||||
Log("Downloading files...");
|
||||
|
||||
foreach (var file in info.ToDelete)
|
||||
try
|
||||
{
|
||||
Log("Deleting " + file.Path);
|
||||
FileApi.Remove(file.Path);
|
||||
}
|
||||
var info = await EnsureFiles();
|
||||
Log("Downloading files...");
|
||||
|
||||
var loadedManifest = info.FilesExist;
|
||||
Save(loadedManifest);
|
||||
foreach (var file in info.ToDelete)
|
||||
{
|
||||
Log("Deleting " + file.Path);
|
||||
FileApi.Remove(file.Path);
|
||||
}
|
||||
|
||||
var count = info.ToDownload.Count;
|
||||
var resolved = 0;
|
||||
|
||||
foreach (var file in info.ToDownload)
|
||||
{
|
||||
using var response = await _httpClient.GetAsync(
|
||||
_configurationService.GetConfigValue(UpdateConVars.UpdateCacheUrl)
|
||||
+ "/" + file.Hash);
|
||||
|
||||
response.EnsureSuccessStatusCode();
|
||||
await using var stream = await response.Content.ReadAsStreamAsync();
|
||||
FileApi.Save(file.Path, stream);
|
||||
resolved++;
|
||||
Log("Saving " + file.Path, (int)(resolved/(float)count*100f));
|
||||
|
||||
loadedManifest.Add(file);
|
||||
var loadedManifest = info.FilesExist;
|
||||
Save(loadedManifest);
|
||||
|
||||
var count = info.ToDownload.Count;
|
||||
var resolved = 0;
|
||||
|
||||
foreach (var file in info.ToDownload)
|
||||
{
|
||||
using var response = await _httpClient.GetAsync(
|
||||
ConfigurationStandalone.GetConfigValue(UpdateConVars.UpdateCacheUrl)
|
||||
+ "/" + file.Hash);
|
||||
|
||||
response.EnsureSuccessStatusCode();
|
||||
await using var stream = await response.Content.ReadAsStreamAsync();
|
||||
FileApi.Save(file.Path, stream);
|
||||
resolved++;
|
||||
Log("Saving " + file.Path, (int)(resolved / (float)count * 100f));
|
||||
|
||||
loadedManifest.Add(file);
|
||||
Save(loadedManifest);
|
||||
}
|
||||
|
||||
Log("Download finished. Running launcher...");
|
||||
|
||||
var process = Process.Start(new ProcessStartInfo
|
||||
{
|
||||
FileName = "dotnet.exe",
|
||||
Arguments = Path.Join(FileApi.RootPath, "Nebula.Launcher.dll"),
|
||||
CreateNoWindow = true,
|
||||
UseShellExecute = false,
|
||||
RedirectStandardOutput = true,
|
||||
RedirectStandardError = true,
|
||||
StandardOutputEncoding = Encoding.UTF8
|
||||
});
|
||||
}
|
||||
Log("Download finished. Running launcher...");
|
||||
|
||||
var process = Process.Start(new ProcessStartInfo
|
||||
catch (Exception e)
|
||||
{
|
||||
FileName = "dotnet.exe",
|
||||
Arguments = Path.Join(FileApi.RootPath,"Nebula.Launcher.dll"),
|
||||
CreateNoWindow = true,
|
||||
UseShellExecute = false,
|
||||
RedirectStandardOutput = true,
|
||||
RedirectStandardError = true,
|
||||
StandardOutputEncoding = Encoding.UTF8
|
||||
});
|
||||
Log("Error! " + e.Message);
|
||||
}
|
||||
|
||||
Thread.Sleep(2000);
|
||||
|
||||
@@ -85,29 +87,29 @@ public partial class MainWindow : Window
|
||||
private async Task<ManifestEnsureInfo> EnsureFiles()
|
||||
{
|
||||
Log("Ensuring launcher manifest...");
|
||||
var manifest = await _restService.GetAsync<LauncherManifest>(
|
||||
new Uri(_configurationService.GetConfigValue(UpdateConVars.UpdateCacheUrl)! + "/manifest.json"), CancellationToken.None);
|
||||
var manifest = await RestStandalone.GetAsync<LauncherManifest>(
|
||||
new Uri(ConfigurationStandalone.GetConfigValue(UpdateConVars.UpdateCacheUrl)! + "/manifest.json"), CancellationToken.None);
|
||||
|
||||
var toDownload = new HashSet<LauncherManifestEntry>();
|
||||
var toDelete = new HashSet<LauncherManifestEntry>();
|
||||
var filesExist = new HashSet<LauncherManifestEntry>();
|
||||
|
||||
Log("Manifest loaded!");
|
||||
if (_configurationService.TryGetConfigValue(UpdateConVars.CurrentLauncherManifest, out var currentManifest))
|
||||
if (ConfigurationStandalone.TryGetConfigValue(UpdateConVars.CurrentLauncherManifest, out var currentManifest))
|
||||
{
|
||||
Log("Delta manifest loaded!");
|
||||
foreach (var file in currentManifest.Entries)
|
||||
{
|
||||
if (!manifest.Entries.Contains(file))
|
||||
toDelete.Add(file);
|
||||
toDelete.Add(EnsurePath(file));
|
||||
else
|
||||
filesExist.Add(file);
|
||||
filesExist.Add(EnsurePath(file));
|
||||
}
|
||||
|
||||
foreach (var file in manifest.Entries)
|
||||
{
|
||||
if(!currentManifest.Entries.Contains(file))
|
||||
toDownload.Add(file);
|
||||
toDownload.Add(EnsurePath(file));
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -133,8 +135,43 @@ public partial class MainWindow : Window
|
||||
|
||||
private void Save(HashSet<LauncherManifestEntry> entries)
|
||||
{
|
||||
_configurationService.SetConfigValue(UpdateConVars.CurrentLauncherManifest, new LauncherManifest(entries));
|
||||
ConfigurationStandalone.SetConfigValue(UpdateConVars.CurrentLauncherManifest, new LauncherManifest(entries));
|
||||
}
|
||||
|
||||
private LauncherManifestEntry EnsurePath(LauncherManifestEntry entry)
|
||||
{
|
||||
if(!PathValidator.IsSafePath(FileApi.RootPath, entry.Path))
|
||||
throw new ArgumentException("Path contains invalid characters. Manifest hash: " + entry.Hash);
|
||||
|
||||
return entry;
|
||||
}
|
||||
}
|
||||
|
||||
public record struct ManifestEnsureInfo(HashSet<LauncherManifestEntry> ToDownload, HashSet<LauncherManifestEntry> ToDelete, HashSet<LauncherManifestEntry> FilesExist);
|
||||
|
||||
public static class PathValidator
|
||||
{
|
||||
public static bool IsSafePath(string baseDirectory, string relativePath)
|
||||
{
|
||||
if (Path.IsPathRooted(relativePath))
|
||||
return false;
|
||||
|
||||
var fullBase = Path.GetFullPath(baseDirectory);
|
||||
|
||||
|
||||
var combinedPath = Path.Combine(fullBase, relativePath);
|
||||
var fullPath = Path.GetFullPath(combinedPath);
|
||||
|
||||
|
||||
if (!fullPath.StartsWith(fullBase, StringComparison.Ordinal))
|
||||
return false;
|
||||
|
||||
if (File.Exists(fullPath) || Directory.Exists(fullPath))
|
||||
{
|
||||
FileInfo fileInfo = new FileInfo(fullPath);
|
||||
if (fileInfo.Attributes.HasFlag(FileAttributes.ReparsePoint))
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
5
Nebula.UpdateResolver/ManifestEnsureInfo.cs
Normal file
5
Nebula.UpdateResolver/ManifestEnsureInfo.cs
Normal file
@@ -0,0 +1,5 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Nebula.UpdateResolver;
|
||||
|
||||
public record struct ManifestEnsureInfo(HashSet<LauncherManifestEntry> ToDownload, HashSet<LauncherManifestEntry> ToDelete, HashSet<LauncherManifestEntry> FilesExist);
|
||||
@@ -6,6 +6,9 @@
|
||||
<BuiltInComInteropSupport>true</BuiltInComInteropSupport>
|
||||
<ApplicationManifest>app.manifest</ApplicationManifest>
|
||||
<AvaloniaUseCompiledBindingsByDefault>true</AvaloniaUseCompiledBindingsByDefault>
|
||||
<PublishSingleFile>true</PublishSingleFile>
|
||||
<!--<PublishTrimmed>true</PublishTrimmed>-->
|
||||
<SelfContained>true</SelfContained>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
@@ -22,10 +25,5 @@
|
||||
<IncludeAssets Condition="'$(Configuration)' != 'Debug'">None</IncludeAssets>
|
||||
<PrivateAssets Condition="'$(Configuration)' != 'Debug'">All</PrivateAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="9.0.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Nebula.Shared\Nebula.Shared.csproj" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
||||
39
Nebula.UpdateResolver/Rest/Helper.cs
Normal file
39
Nebula.UpdateResolver/Rest/Helper.cs
Normal file
@@ -0,0 +1,39 @@
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Net.Http;
|
||||
using System.Text.Json;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Nebula.UpdateResolver.Rest;
|
||||
|
||||
public static class Helper
|
||||
{
|
||||
public static readonly JsonSerializerOptions JsonWebOptions = new(JsonSerializerDefaults.Web);
|
||||
public static void SafeOpenBrowser(string uri)
|
||||
{
|
||||
if (!Uri.TryCreate(uri, UriKind.Absolute, out var parsedUri))
|
||||
{
|
||||
Console.WriteLine("Unable to parse URI in server-provided link: {Link}", uri);
|
||||
return;
|
||||
}
|
||||
|
||||
if (parsedUri.Scheme is not ("http" or "https"))
|
||||
{
|
||||
Console.WriteLine("Refusing to open server-provided link {Link}, only http/https are allowed", parsedUri);
|
||||
return;
|
||||
}
|
||||
|
||||
OpenBrowser(parsedUri.ToString());
|
||||
}
|
||||
public static void OpenBrowser(string url)
|
||||
{
|
||||
Process.Start(new ProcessStartInfo(url) { UseShellExecute = true });
|
||||
}
|
||||
|
||||
public static async Task<T> AsJson<T>(this HttpContent content) where T : notnull
|
||||
{
|
||||
var str = await content.ReadAsStringAsync();
|
||||
return JsonSerializer.Deserialize<T>(str, JsonWebOptions) ??
|
||||
throw new JsonException("AsJson: did not expect null response");
|
||||
}
|
||||
}
|
||||
11
Nebula.UpdateResolver/Rest/RestRequestException.cs
Normal file
11
Nebula.UpdateResolver/Rest/RestRequestException.cs
Normal file
@@ -0,0 +1,11 @@
|
||||
using System;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
|
||||
namespace Nebula.UpdateResolver.Rest;
|
||||
|
||||
public sealed class RestRequestException(HttpContent content, HttpStatusCode statusCode) : Exception
|
||||
{
|
||||
public HttpStatusCode StatusCode { get; } = statusCode;
|
||||
public HttpContent Content { get; } = content;
|
||||
}
|
||||
77
Nebula.UpdateResolver/Rest/RestStandalone.cs
Normal file
77
Nebula.UpdateResolver/Rest/RestStandalone.cs
Normal file
@@ -0,0 +1,77 @@
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Net.Http;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Nebula.UpdateResolver.Rest;
|
||||
|
||||
public static class RestStandalone
|
||||
{
|
||||
private static readonly HttpClient _client = new();
|
||||
|
||||
private static readonly JsonSerializerOptions _serializerOptions = new()
|
||||
{
|
||||
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
|
||||
WriteIndented = true
|
||||
};
|
||||
|
||||
public static async Task<T> GetAsync<T>(Uri uri, CancellationToken cancellationToken) where T : notnull
|
||||
{
|
||||
var response = await _client.GetAsync(uri, cancellationToken);
|
||||
return await ReadResult<T>(response, cancellationToken);
|
||||
}
|
||||
|
||||
public static async Task<T> GetAsyncDefault<T>(Uri uri, T defaultValue, CancellationToken cancellationToken) where T : notnull
|
||||
{
|
||||
try
|
||||
{
|
||||
return await GetAsync<T>(uri, cancellationToken);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
return defaultValue;
|
||||
}
|
||||
}
|
||||
|
||||
public static async Task<K> PostAsync<K, T>(T information, Uri uri, CancellationToken cancellationToken) where K : notnull
|
||||
{
|
||||
var json = JsonSerializer.Serialize(information, _serializerOptions);
|
||||
var content = new StringContent(json, Encoding.UTF8, "application/json");
|
||||
var response = await _client.PostAsync(uri, content, cancellationToken);
|
||||
return await ReadResult<K>(response, cancellationToken);
|
||||
}
|
||||
|
||||
public static async Task<T> PostAsync<T>(Stream stream, Uri uri, CancellationToken cancellationToken) where T : notnull
|
||||
{
|
||||
using var multipartFormContent =
|
||||
new MultipartFormDataContent("Upload----" + DateTime.Now.ToString(CultureInfo.InvariantCulture));
|
||||
multipartFormContent.Add(new StreamContent(stream), "formFile", "image.png");
|
||||
var response = await _client.PostAsync(uri, multipartFormContent, cancellationToken);
|
||||
return await ReadResult<T>(response, cancellationToken);
|
||||
}
|
||||
|
||||
public static async Task<T> DeleteAsync<T>(Uri uri, CancellationToken cancellationToken) where T : notnull
|
||||
{
|
||||
var response = await _client.DeleteAsync(uri, cancellationToken);
|
||||
return await ReadResult<T>(response, cancellationToken);
|
||||
}
|
||||
|
||||
private static async Task<T> ReadResult<T>(HttpResponseMessage response, CancellationToken cancellationToken) where T : notnull
|
||||
{
|
||||
var content = await response.Content.ReadAsStringAsync(cancellationToken);
|
||||
|
||||
if (typeof(T) == typeof(string) && content is T t)
|
||||
return t;
|
||||
|
||||
if (response.IsSuccessStatusCode)
|
||||
{
|
||||
return await response.Content.AsJson<T>();
|
||||
}
|
||||
|
||||
throw new RestRequestException(response.Content, response.StatusCode);
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,4 @@
|
||||
using System;
|
||||
using Nebula.Shared.Models;
|
||||
using Nebula.Shared.Services;
|
||||
using Nebula.UpdateResolver.Configuration;
|
||||
|
||||
namespace Nebula.UpdateResolver;
|
||||
|
||||
|
||||
@@ -1,4 +1,13 @@
|
||||
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
|
||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ACancellationToken_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003FUsers_003FCinka_003FAppData_003FRoaming_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FSourcesCache_003F2565b9d99fdde488bc7801b84387b2cc864959cfb63212e1ff576fc9c6bb7e_003FCancellationToken_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AConsole_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003FUsers_003FCinka_003FAppData_003FRoaming_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FSourcesCache_003Ffd57398b7dc3a8ce7da2786f2c67289c3d974658a9e90d0c1e84db3d965fbf1_003FConsole_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AFrozenDictionary_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003FUsers_003FCinka_003FAppData_003FRoaming_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FSourcesCache_003F89dff9063ddb01ff8125b579122b88bf4de94526490d77bcbbef7d0ee662a_003FFrozenDictionary_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AFuture_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003FUsers_003FCinka_003FAppData_003FRoaming_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FSourcesCache_003Fb3575a2f41d7c2dbfaa36e866b8a361e11dd7223ff82bc574c1d5d4b7522f735_003FFuture_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AHttpClient_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003FUsers_003FCinka_003FAppData_003FRoaming_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FSourcesCache_003Fc439425da351c75ac7d966a1cc8324b51a9c471865af79d2f2f3fcb65e392_003FHttpClient_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AHttpContent_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003FUsers_003FCinka_003FAppData_003FRoaming_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FSourcesCache_003F9657cc383c70851dc2bdcf91eff27f21196844abfe552fc9c3243ff36974cd_003FHttpContent_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AHttpResponseMessage_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003FUsers_003FCinka_003FAppData_003FRoaming_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FSourcesCache_003F4cfeb8b377bc81e1fbb5f7d7a02492cb6ac23e88c8c9d7155944f0716f3d4b_003FHttpResponseMessage_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AJsonSerializer_002ERead_002EString_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003FUsers_003FCinka_003FAppData_003FRoaming_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FSourcesCache_003F27c4858128168eda568c1334d70d5241efb9461e2a3209258a04deee5d9c367_003FJsonSerializer_002ERead_002EString_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AParallel_002EForEachAsync_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003FUsers_003FCinka_003FAppData_003FRoaming_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FSourcesCache_003Fc1d1ed6be2d5d4de542b4af5b36e82f6d1d1a389a35a4e4f9748d137d1c651_003FParallel_002EForEachAsync_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AServiceCollectionContainerBuilderExtensions_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003FUsers_003FCinka_003FAppData_003FRoaming_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fa8ceca48b7b645dd875a40ee6d28725416d08_003F1b_003F6cd78dc8_003FServiceCollectionContainerBuilderExtensions_002Ecs/@EntryIndexedValue">ForceIncluded</s:String></wpf:ResourceDictionary>
|
||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AServiceCollectionContainerBuilderExtensions_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003FUsers_003FCinka_003FAppData_003FRoaming_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fa8ceca48b7b645dd875a40ee6d28725416d08_003F1b_003F6cd78dc8_003FServiceCollectionContainerBuilderExtensions_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AString_002EManipulation_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003FUsers_003FCinka_003FAppData_003FRoaming_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FSourcesCache_003Fe75a5575ba872c8ea754c015cb363850e6c661f39569712d5b74aaca67263c_003FString_002EManipulation_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AUri_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003FUsers_003FCinka_003FAppData_003FRoaming_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FSourcesCache_003F6a1fb5a19c4883d19f63515be2d0cce5e0e9929bb30469a912a58ad2e1e6152_003FUri_002Ecs/@EntryIndexedValue">ForceIncluded</s:String></wpf:ResourceDictionary>
|
||||
Reference in New Issue
Block a user