mirror of
https://jihulab.com/DGP-Studio/Snap.Hutao.Deployment.git
synced 2025-11-19 21:08:45 +08:00
Init Commit
This commit is contained in:
6
src/Snap.Hutao.Deployment/.gitignore
vendored
Normal file
6
src/Snap.Hutao.Deployment/.gitignore
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
.vs/
|
||||
bin/
|
||||
obj/
|
||||
|
||||
*.pubxml
|
||||
*.user
|
||||
24
src/Snap.Hutao.Deployment/HttpClientExtension.cs
Normal file
24
src/Snap.Hutao.Deployment/HttpClientExtension.cs
Normal file
@@ -0,0 +1,24 @@
|
||||
using System;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Net.Http;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Snap.Hutao.Deployment;
|
||||
|
||||
internal static class HttpClientExtension
|
||||
{
|
||||
public static Task<HttpResponseMessage> HeadAsync(this HttpClient httpClient, [StringSyntax(StringSyntaxAttribute.Uri)] string? requestUri, HttpCompletionOption completionOption)
|
||||
{
|
||||
return httpClient.SendAsync(PrivateCreateRequestMessage(httpClient, HttpMethod.Get, CreateUri(default!, requestUri)), completionOption, CancellationToken.None);
|
||||
}
|
||||
|
||||
// private HttpRequestMessage CreateRequestMessage(HttpMethod method, Uri? uri)
|
||||
[UnsafeAccessor(UnsafeAccessorKind.Method, Name = "CreateRequestMessage")]
|
||||
private static extern HttpRequestMessage PrivateCreateRequestMessage(HttpClient httpClient, HttpMethod method, Uri? uri);
|
||||
|
||||
// private static Uri? CreateUri(string? uri)
|
||||
[UnsafeAccessor(UnsafeAccessorKind.StaticMethod, Name = "CreateUri")]
|
||||
private static extern Uri? CreateUri(HttpClient discard, string? uri);
|
||||
}
|
||||
168
src/Snap.Hutao.Deployment/HttpShardCopyWorker.cs
Normal file
168
src/Snap.Hutao.Deployment/HttpShardCopyWorker.cs
Normal file
@@ -0,0 +1,168 @@
|
||||
using Microsoft.Win32.SafeHandles;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Net.Http;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Snap.Hutao.Deployment;
|
||||
|
||||
internal sealed class HttpShardCopyWorker<TStatus> : IDisposable
|
||||
{
|
||||
private const int ShardSize = 4 * 1024 * 1024;
|
||||
|
||||
private readonly HttpClient httpClient;
|
||||
private readonly string sourceUrl;
|
||||
private readonly Func<long, long, TStatus> statusFactory;
|
||||
private readonly long contentLength;
|
||||
private readonly int bufferSize;
|
||||
private readonly SafeFileHandle destFileHandle;
|
||||
private readonly List<Shard> shards;
|
||||
|
||||
private HttpShardCopyWorker(HttpShardCopyWorkerOptions<TStatus> options)
|
||||
{
|
||||
httpClient = options.HttpClient;
|
||||
sourceUrl = options.SourceUrl;
|
||||
statusFactory = options.StatusFactory;
|
||||
contentLength = options.ContentLength;
|
||||
bufferSize = options.BufferSize;
|
||||
destFileHandle = options.GetFileHandle();
|
||||
shards = CalculateShards(contentLength);
|
||||
|
||||
static List<Shard> CalculateShards(long contentLength)
|
||||
{
|
||||
List<Shard> shards = [];
|
||||
long currentOffset = 0;
|
||||
|
||||
while (currentOffset < contentLength)
|
||||
{
|
||||
long end = Math.Min(currentOffset + ShardSize, contentLength) - 1;
|
||||
shards.Add(new Shard(currentOffset, end));
|
||||
currentOffset = end + 1;
|
||||
}
|
||||
|
||||
return shards;
|
||||
}
|
||||
}
|
||||
|
||||
public static async ValueTask<HttpShardCopyWorker<TStatus>> CreateAsync(HttpShardCopyWorkerOptions<TStatus> options)
|
||||
{
|
||||
await options.DetectContentLengthAsync().ConfigureAwait(false);
|
||||
return new(options);
|
||||
}
|
||||
|
||||
public Task CopyAsync(IProgress<TStatus> progress, CancellationToken token = default)
|
||||
{
|
||||
ShardProgress shardProgress = new(progress, statusFactory, contentLength);
|
||||
return Parallel.ForEachAsync(shards, token, (shard, token) => CopyShardAsync(shard, shardProgress, token));
|
||||
|
||||
async ValueTask CopyShardAsync(Shard shard, IProgress<ShardStatus> progress, CancellationToken token)
|
||||
{
|
||||
ValueStopwatch stopwatch = ValueStopwatch.StartNew();
|
||||
HttpRequestMessage request = new(HttpMethod.Get, sourceUrl)
|
||||
{
|
||||
Headers = { Range = new(shard.StartOffset, shard.EndOffset), },
|
||||
};
|
||||
|
||||
using (request)
|
||||
{
|
||||
using (HttpResponseMessage response = await httpClient.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, token).ConfigureAwait(false))
|
||||
{
|
||||
response.EnsureSuccessStatusCode();
|
||||
|
||||
Memory<byte> buffer = new byte[bufferSize];
|
||||
using (Stream stream = await response.Content.ReadAsStreamAsync(token).ConfigureAwait(false))
|
||||
{
|
||||
int totalBytesRead = 0;
|
||||
int bytesReadAfterPreviousReport = 0;
|
||||
do
|
||||
{
|
||||
int bytesRead = await stream.ReadAsync(buffer, token).ConfigureAwait(false);
|
||||
if (bytesRead <= 0)
|
||||
{
|
||||
progress.Report(new(bytesReadAfterPreviousReport));
|
||||
bytesReadAfterPreviousReport = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
await RandomAccess.WriteAsync(destFileHandle, buffer[..bytesRead], shard.StartOffset + totalBytesRead, token).ConfigureAwait(false);
|
||||
|
||||
totalBytesRead += bytesRead;
|
||||
bytesReadAfterPreviousReport += bytesRead;
|
||||
if (stopwatch.GetElapsedTime().TotalMilliseconds > 500)
|
||||
{
|
||||
progress.Report(new(bytesReadAfterPreviousReport));
|
||||
bytesReadAfterPreviousReport = 0;
|
||||
stopwatch = ValueStopwatch.StartNew();
|
||||
}
|
||||
}
|
||||
while (true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
destFileHandle.Dispose();
|
||||
}
|
||||
|
||||
private sealed class Shard
|
||||
{
|
||||
public Shard(long startOffset, long endOffset)
|
||||
{
|
||||
StartOffset = startOffset;
|
||||
EndOffset = endOffset;
|
||||
}
|
||||
|
||||
public long StartOffset { get; }
|
||||
|
||||
public long EndOffset { get; }
|
||||
}
|
||||
|
||||
private sealed class ShardStatus
|
||||
{
|
||||
public ShardStatus(int bytesRead)
|
||||
{
|
||||
BytesRead = bytesRead;
|
||||
}
|
||||
|
||||
public int BytesRead { get; }
|
||||
}
|
||||
|
||||
private sealed class ShardProgress : IProgress<ShardStatus>
|
||||
{
|
||||
private readonly IProgress<TStatus> workerProgress;
|
||||
private readonly Func<long, long, TStatus> statusFactory;
|
||||
private readonly long contentLength;
|
||||
|
||||
private readonly object syncRoot = new();
|
||||
private ValueStopwatch stopwatch = ValueStopwatch.StartNew();
|
||||
private long totalBytesRead;
|
||||
|
||||
public ShardProgress(IProgress<TStatus> workerProgress, Func<long, long, TStatus> statusFactory, long contentLength)
|
||||
{
|
||||
this.workerProgress = workerProgress;
|
||||
this.statusFactory = statusFactory;
|
||||
this.contentLength = contentLength;
|
||||
}
|
||||
|
||||
public void Report(ShardStatus value)
|
||||
{
|
||||
Interlocked.Add(ref totalBytesRead, value.BytesRead);
|
||||
if (stopwatch.GetElapsedTime().TotalMilliseconds >= 500 || totalBytesRead == contentLength)
|
||||
{
|
||||
lock (syncRoot)
|
||||
{
|
||||
if (stopwatch.GetElapsedTime().TotalMilliseconds >= 500 || totalBytesRead == contentLength)
|
||||
{
|
||||
workerProgress.Report(statusFactory(totalBytesRead, contentLength));
|
||||
stopwatch = ValueStopwatch.StartNew();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
42
src/Snap.Hutao.Deployment/HttpShardCopyWorkerOptions.cs
Normal file
42
src/Snap.Hutao.Deployment/HttpShardCopyWorkerOptions.cs
Normal file
@@ -0,0 +1,42 @@
|
||||
using Microsoft.Win32.SafeHandles;
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Net.Http;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Snap.Hutao.Deployment;
|
||||
|
||||
internal sealed class HttpShardCopyWorkerOptions<TStatus>
|
||||
{
|
||||
public HttpClient HttpClient { get; set; } = default!;
|
||||
|
||||
public string SourceUrl { get; set; } = default!;
|
||||
|
||||
public string DestinationFilePath { get; set; } = default!;
|
||||
|
||||
public long ContentLength { get; private set; }
|
||||
|
||||
public Func<long, long, TStatus> StatusFactory { get; set; } = default!;
|
||||
|
||||
public int BufferSize { get; set; } = 80 * 1024;
|
||||
|
||||
public SafeFileHandle GetFileHandle()
|
||||
{
|
||||
return File.OpenHandle(DestinationFilePath, FileMode.Create, FileAccess.Write, FileShare.None, FileOptions.RandomAccess | FileOptions.Asynchronous, ContentLength);
|
||||
}
|
||||
|
||||
public async ValueTask DetectContentLengthAsync()
|
||||
{
|
||||
if (ContentLength > 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
HttpResponseMessage response = await HttpClient.HeadAsync(SourceUrl, HttpCompletionOption.ResponseHeadersRead).ConfigureAwait(false);
|
||||
response.EnsureSuccessStatusCode();
|
||||
|
||||
long contentLength = response.Content.Headers.ContentLength ?? 0;
|
||||
ArgumentOutOfRangeException.ThrowIfNegativeOrZero(contentLength);
|
||||
ContentLength = contentLength;
|
||||
}
|
||||
}
|
||||
137
src/Snap.Hutao.Deployment/Invocation.cs
Normal file
137
src/Snap.Hutao.Deployment/Invocation.cs
Normal file
@@ -0,0 +1,137 @@
|
||||
using System;
|
||||
using System.CommandLine.Invocation;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Net.Http;
|
||||
using System.Threading.Tasks;
|
||||
using Windows.Management.Deployment;
|
||||
|
||||
namespace Snap.Hutao.Deployment;
|
||||
|
||||
internal static class Invocation
|
||||
{
|
||||
public static async Task RunDeploymentAsync(InvocationContext context)
|
||||
{
|
||||
string? path = context.ParseResult.GetValueForOption(InvocationOptions.PackagePath);
|
||||
string? name = context.ParseResult.GetValueForOption(InvocationOptions.FamilyName);
|
||||
bool isUpdateMode = context.ParseResult.GetValueForOption(InvocationOptions.UpdateBehavior);
|
||||
|
||||
ArgumentException.ThrowIfNullOrEmpty(path);
|
||||
|
||||
Console.WriteLine($"""
|
||||
PackagePath: {path}
|
||||
FamilyName: {name}
|
||||
------------------------------------------------------------
|
||||
""");
|
||||
|
||||
if (!File.Exists(path))
|
||||
{
|
||||
Console.WriteLine($"Package file not found.");
|
||||
|
||||
if (isUpdateMode)
|
||||
{
|
||||
Console.WriteLine("Exit in 10 seconds...");
|
||||
await Task.Delay(10000);
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.WriteLine("Start downloading package...");
|
||||
await DownloadPackageAsync(path);
|
||||
}
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
Console.WriteLine("Initializing PackageManager...");
|
||||
PackageManager packageManager = new();
|
||||
AddPackageOptions addPackageOptions = new()
|
||||
{
|
||||
ForceAppShutdown = true,
|
||||
RetainFilesOnFailure = true,
|
||||
StageInPlace = true,
|
||||
};
|
||||
|
||||
Console.WriteLine("Start deploying...");
|
||||
IProgress<DeploymentProgress> progress = new Progress<DeploymentProgress>(p =>
|
||||
{
|
||||
Console.WriteLine($"[Deploying]: State: {p.state} Progress: {p.percentage}%");
|
||||
});
|
||||
DeploymentResult result = await packageManager.AddPackageByUriAsync(new Uri(path), addPackageOptions).AsTask(progress);
|
||||
|
||||
if (result.IsRegistered)
|
||||
{
|
||||
Console.WriteLine("Package deployed.");
|
||||
if (string.IsNullOrEmpty(name))
|
||||
{
|
||||
Console.WriteLine("FamilyName not provided, enumerating packages.");
|
||||
|
||||
foreach (Windows.ApplicationModel.Package package in packageManager.FindPackages())
|
||||
{
|
||||
|
||||
if (package is { DisplayName: "Snap Hutao", PublisherDisplayName: "DGP Studio" })
|
||||
{
|
||||
name = package.Id.FamilyName;
|
||||
Console.WriteLine($"Package found: {name}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Console.WriteLine("Starting app...");
|
||||
Process.Start(new ProcessStartInfo()
|
||||
{
|
||||
UseShellExecute = true,
|
||||
FileName = $@"shell:AppsFolder\{name}!App",
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.WriteLine($"""
|
||||
ActivityId: {result.ActivityId}
|
||||
ExtendedErrorCode: {result.ExtendedErrorCode}
|
||||
ErrorText: {result.ErrorText}
|
||||
|
||||
Exit in 10 seconds...
|
||||
""");
|
||||
|
||||
await Task.Delay(10000);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"""
|
||||
Exception occured:
|
||||
{ex}
|
||||
|
||||
Exit in 10 seconds...
|
||||
""");
|
||||
|
||||
await Task.Delay(10000);
|
||||
}
|
||||
}
|
||||
|
||||
private static async Task DownloadPackageAsync(string packagePath)
|
||||
{
|
||||
using (HttpClient httpClient = new())
|
||||
{
|
||||
HttpShardCopyWorkerOptions<PackageDownloadStatus> options = new()
|
||||
{
|
||||
HttpClient = httpClient,
|
||||
SourceUrl = "https://api.snapgenshin.com/patch/hutao/download",
|
||||
DestinationFilePath = packagePath,
|
||||
StatusFactory = (bytesRead, totalBytes) => new PackageDownloadStatus(bytesRead, totalBytes),
|
||||
};
|
||||
|
||||
using (HttpShardCopyWorker<PackageDownloadStatus> worker = await HttpShardCopyWorker<PackageDownloadStatus>.CreateAsync(options).ConfigureAwait(false))
|
||||
{
|
||||
Progress<PackageDownloadStatus> progress = new(ConsoleWriteProgress);
|
||||
await worker.CopyAsync(progress).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
static void ConsoleWriteProgress(PackageDownloadStatus status)
|
||||
{
|
||||
Console.Write($"\r{status.ProgressDescription}");
|
||||
}
|
||||
}
|
||||
}
|
||||
22
src/Snap.Hutao.Deployment/InvocationOptions.cs
Normal file
22
src/Snap.Hutao.Deployment/InvocationOptions.cs
Normal file
@@ -0,0 +1,22 @@
|
||||
using System;
|
||||
using System.CommandLine;
|
||||
using System.IO;
|
||||
|
||||
namespace Snap.Hutao.Deployment;
|
||||
|
||||
internal static class InvocationOptions
|
||||
{
|
||||
public static readonly Option<string> PackagePath = new(
|
||||
"--package-path",
|
||||
() => Path.Combine(AppContext.BaseDirectory, "Snap.Hutao.msix"),
|
||||
"The path of the package to be deployed.");
|
||||
|
||||
public static readonly Option<string> FamilyName = new(
|
||||
"--family-name",
|
||||
"The family name of the app to be updated.");
|
||||
|
||||
public static readonly Option<bool> UpdateBehavior = new(
|
||||
"--update-behavior",
|
||||
() => false,
|
||||
"Change behavior of the tool into update mode");
|
||||
}
|
||||
45
src/Snap.Hutao.Deployment/PackageDownloadStatus.cs
Normal file
45
src/Snap.Hutao.Deployment/PackageDownloadStatus.cs
Normal file
@@ -0,0 +1,45 @@
|
||||
namespace Snap.Hutao.Deployment;
|
||||
|
||||
internal sealed class PackageDownloadStatus
|
||||
{
|
||||
public PackageDownloadStatus(long bytesRead, long totalBytes)
|
||||
{
|
||||
ProgressDescription = bytesRead != totalBytes
|
||||
? $"Download Progress: {ToFileSizeString(bytesRead),8}/{ToFileSizeString(totalBytes),8} | {(double)bytesRead / totalBytes,8:P3}"
|
||||
: "Download Completed\n";
|
||||
}
|
||||
|
||||
public string ProgressDescription { get; }
|
||||
|
||||
private static string ToFileSizeString(long size)
|
||||
{
|
||||
if (size < 1024)
|
||||
{
|
||||
return size.ToString("F0") + " bytes";
|
||||
}
|
||||
else if ((size >> 10) < 1024)
|
||||
{
|
||||
return (size / 1024F).ToString("F1") + " KB";
|
||||
}
|
||||
else if ((size >> 20) < 1024)
|
||||
{
|
||||
return ((size >> 10) / 1024F).ToString("F1") + " MB";
|
||||
}
|
||||
else if ((size >> 30) < 1024)
|
||||
{
|
||||
return ((size >> 20) / 1024F).ToString("F1") + " GB";
|
||||
}
|
||||
else if ((size >> 40) < 1024)
|
||||
{
|
||||
return ((size >> 30) / 1024F).ToString("F1") + " TB";
|
||||
}
|
||||
else if ((size >> 50) < 1024)
|
||||
{
|
||||
return ((size >> 40) / 1024F).ToString("F1") + " PB";
|
||||
}
|
||||
else
|
||||
{
|
||||
return ((size >> 50) / 1024F).ToString("F1") + " EB";
|
||||
}
|
||||
}
|
||||
}
|
||||
23
src/Snap.Hutao.Deployment/Program.cs
Normal file
23
src/Snap.Hutao.Deployment/Program.cs
Normal file
@@ -0,0 +1,23 @@
|
||||
using System.CommandLine;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Snap.Hutao.Deployment;
|
||||
|
||||
internal static class Program
|
||||
{
|
||||
internal static async Task<int> Main(string[] args)
|
||||
{
|
||||
string description = $@"
|
||||
Snap Hutao Updater
|
||||
Copyright (c) DGP Studio. All rights reserved.
|
||||
";
|
||||
RootCommand root = new(description);
|
||||
root.AddOption(InvocationOptions.PackagePath);
|
||||
root.AddOption(InvocationOptions.FamilyName);
|
||||
root.AddOption(InvocationOptions.UpdateBehavior);
|
||||
|
||||
root.SetHandler(Invocation.RunDeploymentAsync);
|
||||
|
||||
return await root.InvokeAsync(args);
|
||||
}
|
||||
}
|
||||
25
src/Snap.Hutao.Deployment/Snap.Hutao.Deployment.csproj
Normal file
25
src/Snap.Hutao.Deployment/Snap.Hutao.Deployment.csproj
Normal file
@@ -0,0 +1,25 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net8.0-windows10.0.22621.0</TargetFramework>
|
||||
<ImplicitUsings>disable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<Platforms>x64</Platforms>
|
||||
<PublishSingleFile>true</PublishSingleFile>
|
||||
<PublishTrimmed>true</PublishTrimmed>
|
||||
<RuntimeIdentifier>win-x64</RuntimeIdentifier>
|
||||
<SelfContained>true</SelfContained>
|
||||
<DebugType>embedded</DebugType>
|
||||
<ApplicationManifest>app.manifest</ApplicationManifest>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Remove=".gitignore" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="System.CommandLine" Version="2.0.0-beta4.22272.1" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
31
src/Snap.Hutao.Deployment/Snap.Hutao.Deployment.sln
Normal file
31
src/Snap.Hutao.Deployment/Snap.Hutao.Deployment.sln
Normal file
@@ -0,0 +1,31 @@
|
||||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio Version 17
|
||||
VisualStudioVersion = 17.8.34330.188
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Snap.Hutao.Deployment", "Snap.Hutao.Deployment.csproj", "{B457BB36-A85E-4C2B-91C1-9453949445E3}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
Debug|x64 = Debug|x64
|
||||
Release|Any CPU = Release|Any CPU
|
||||
Release|x64 = Release|x64
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{B457BB36-A85E-4C2B-91C1-9453949445E3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{B457BB36-A85E-4C2B-91C1-9453949445E3}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{B457BB36-A85E-4C2B-91C1-9453949445E3}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{B457BB36-A85E-4C2B-91C1-9453949445E3}.Debug|x64.Build.0 = Debug|x64
|
||||
{B457BB36-A85E-4C2B-91C1-9453949445E3}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{B457BB36-A85E-4C2B-91C1-9453949445E3}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{B457BB36-A85E-4C2B-91C1-9453949445E3}.Release|x64.ActiveCfg = Release|x64
|
||||
{B457BB36-A85E-4C2B-91C1-9453949445E3}.Release|x64.Build.0 = Release|x64
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {A7AE25C3-D7A1-4F58-809B-786CC4CA6951}
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
29
src/Snap.Hutao.Deployment/ValueStopwatch.cs
Normal file
29
src/Snap.Hutao.Deployment/ValueStopwatch.cs
Normal file
@@ -0,0 +1,29 @@
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace Snap.Hutao.Deployment;
|
||||
|
||||
internal readonly struct ValueStopwatch
|
||||
{
|
||||
private readonly long startTimestamp;
|
||||
|
||||
private ValueStopwatch(long startTimestamp)
|
||||
{
|
||||
this.startTimestamp = startTimestamp;
|
||||
}
|
||||
|
||||
public bool IsActive
|
||||
{
|
||||
get => startTimestamp != 0;
|
||||
}
|
||||
|
||||
public static ValueStopwatch StartNew()
|
||||
{
|
||||
return new(Stopwatch.GetTimestamp());
|
||||
}
|
||||
|
||||
public TimeSpan GetElapsedTime()
|
||||
{
|
||||
return Stopwatch.GetElapsedTime(startTimestamp);
|
||||
}
|
||||
}
|
||||
25
src/Snap.Hutao.Deployment/app.manifest
Normal file
25
src/Snap.Hutao.Deployment/app.manifest
Normal file
@@ -0,0 +1,25 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1">
|
||||
<assemblyIdentity version="1.0.0.0" name="MyApplication.app"/>
|
||||
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v2">
|
||||
<security>
|
||||
<requestedPrivileges xmlns="urn:schemas-microsoft-com:asm.v3">
|
||||
<!--https://learn.microsoft.com/en-us/previous-versions/bb756929(v=msdn.10)-->
|
||||
<requestedExecutionLevel level="requireAdministrator" uiAccess="false" />
|
||||
</requestedPrivileges>
|
||||
</security>
|
||||
</trustInfo>
|
||||
|
||||
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
|
||||
<application>
|
||||
<!-- Windows 10 -->
|
||||
<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}" />
|
||||
</application>
|
||||
</compatibility>
|
||||
<application xmlns="urn:schemas-microsoft-com:asm.v3">
|
||||
<windowsSettings>
|
||||
<!--https://docs.microsoft.com/windows/win32/fileio/maximum-file-path-limitation-->
|
||||
<longPathAware xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">true</longPathAware>
|
||||
</windowsSettings>
|
||||
</application>
|
||||
</assembly>
|
||||
Reference in New Issue
Block a user