Skip to content

Commit a79e916

Browse files
committed
Update mechanism to use velopack
1 parent ae9da1b commit a79e916

File tree

10 files changed

+219
-63
lines changed

10 files changed

+219
-63
lines changed

ApplyUpdate-Core/ApplyUpdate-Core.csproj

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
<Project Sdk="Microsoft.NET.Sdk">
22
<PropertyGroup>
3-
<TargetFramework>net8.0</TargetFramework>
3+
<TargetFramework>net9.0</TargetFramework>
44
<Nullable>disable</Nullable>
55
<LangVersion>latest</LangVersion>
66
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
@@ -25,6 +25,6 @@
2525
<PackageReference Include="Avalonia.Themes.Fluent" Version="11.0.11" />
2626
<!--Condition below is needed to remove Avalonia.Diagnostics package from build output in Release configuration.-->
2727
<PackageReference Condition="'$(Configuration)' == 'Debug'" Include="Avalonia.Diagnostics" Version="11.0.11" />
28-
<PackageReference Include="System.IO.Hashing" Version="8.0.0" />
28+
<PackageReference Include="System.IO.Hashing" Version="9.0.0-rc.1.24431.7" />
2929
</ItemGroup>
3030
</Project>

ApplyUpdate-Core/FallbackCDNUtil.cs

Lines changed: 166 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
1-
using Hi3Helper.Http;
1+
using Hi3Helper;
2+
using Hi3Helper.Http;
3+
using Hi3Helper.Http.Legacy;
24
using System;
35
using System.Collections.Generic;
46
using System.IO;
57
using System.Linq;
8+
using System.Net;
69
using System.Text;
710
using System.Threading;
811
using System.Threading.Tasks;
@@ -23,27 +26,27 @@ public static class FallbackCDNUtil
2326
public static List<CDNURLProperty> CDNList = new List<CDNURLProperty>
2427
{
2528
new CDNURLProperty
26-
{
27-
Name = "GitHub",
28-
URLPrefix = "https://github.com/neon-nyan/CollapseLauncher-ReleaseRepo/raw/main",
29-
PartialDownloadSupport = true
30-
},
31-
new CDNURLProperty
32-
{
33-
Name = "Cloudflare",
34-
URLPrefix = "https://r2.bagelnl.my.id/cl-cdn",
35-
PartialDownloadSupport = true
36-
},
37-
new CDNURLProperty
38-
{
39-
Name = "Bitbucket",
40-
URLPrefix = "https://bitbucket.org/neon-nyan/collapselauncher-releaserepo/raw/main"
41-
},
42-
new CDNURLProperty
43-
{
44-
Name = "GitLab",
45-
URLPrefix = "https://gitlab.com/bagusnl/CollapseLauncher-ReleaseRepo/-/raw/main/"
46-
}
29+
{
30+
Name = "GitHub",
31+
URLPrefix = "https://github.com/CollapseLauncher/CollapseLauncher-ReleaseRepo/raw/main",
32+
PartialDownloadSupport = true
33+
},
34+
new CDNURLProperty
35+
{
36+
Name = "Cloudflare",
37+
URLPrefix = "https://r2.bagelnl.my.id/cl-cdn",
38+
PartialDownloadSupport = true
39+
},
40+
new CDNURLProperty
41+
{
42+
Name = "GitLab",
43+
URLPrefix = "https://gitlab.com/bagusnl/CollapseLauncher-ReleaseRepo/-/raw/main/"
44+
},
45+
new CDNURLProperty
46+
{
47+
Name = "Coding",
48+
URLPrefix = "https://ohly-generic.pkg.coding.net/collapse/release/"
49+
},
4750
};
4851

4952
public static event EventHandler<DownloadEvent> DownloadProgress;
@@ -76,6 +79,34 @@ public static async Task DownloadCDNFallbackContent(Http httpInstance, string ou
7679
}
7780
}
7881

82+
public static async Task DownloadCDNFallbackContent(DownloadClient downloadClient, string outputPath, int parallelThread, string relativeURL, CancellationToken token)
83+
{
84+
// Get the preferred CDN first and try get the content
85+
CDNURLProperty preferredCDN = GetPreferredCDN();
86+
bool isSuccess = await TryGetCDNContent(preferredCDN, downloadClient, outputPath, relativeURL, parallelThread, token);
87+
88+
// If successful, then return
89+
if (isSuccess) return;
90+
91+
// If the fail return code occurred by the token, then throw cancellation exception
92+
token.ThrowIfCancellationRequested();
93+
94+
// If not, then continue to get the content from another CDN
95+
foreach (CDNURLProperty fallbackCDN in CDNList.Where(x => !x.Equals(preferredCDN)))
96+
{
97+
isSuccess = await TryGetCDNContent(fallbackCDN, downloadClient, outputPath, relativeURL, parallelThread, token);
98+
99+
// If successful, then return
100+
if (isSuccess) return;
101+
}
102+
103+
// If all of them failed, then throw an exception
104+
if (!isSuccess)
105+
{
106+
throw new AggregateException($"All available CDNs aren't reachable for your network while getting content: {relativeURL}. Please check your internet!");
107+
}
108+
}
109+
79110
public static async Task DownloadCDNFallbackContent(Http httpInstance, Stream outputStream, string relativeURL, CancellationToken token)
80111
{
81112
// Argument check
@@ -147,6 +178,88 @@ private static async Task<bool> TryGetCDNContent(CDNURLProperty cdnProp, Http ht
147178
}
148179
}
149180

181+
public static async Task DownloadCDNFallbackContent(DownloadClient downloadClient, Stream outputStream, string relativeURL, CancellationToken token)
182+
{
183+
// Argument check
184+
PerformStreamCheckAndSeek(outputStream);
185+
186+
// Get the preferred CDN first and try get the content
187+
CDNURLProperty preferredCDN = GetPreferredCDN();
188+
bool isSuccess = await TryGetCDNContent(preferredCDN, downloadClient, outputStream, relativeURL, token);
189+
190+
// If successful, then return
191+
if (isSuccess) return;
192+
193+
// If the fail return code occurred by the token, then throw cancellation exception
194+
token.ThrowIfCancellationRequested();
195+
196+
// If not, then continue to get the content from another CDN
197+
foreach (CDNURLProperty fallbackCDN in CDNList.Where(x => !x.Equals(preferredCDN)))
198+
{
199+
isSuccess = await TryGetCDNContent(fallbackCDN, downloadClient, outputStream, relativeURL, token);
200+
201+
// If successful, then return
202+
if (isSuccess) return;
203+
}
204+
205+
// If all of them failed, then throw an exception
206+
if (!isSuccess)
207+
{
208+
throw new AggregateException($"All available CDNs aren't reachable for your network while getting content: {relativeURL}. Please check your internet!");
209+
}
210+
}
211+
212+
private static async ValueTask<bool> TryGetCDNContent(CDNURLProperty cdnProp, DownloadClient downloadClient, string outputPath, string relativeURL, int parallelThread, CancellationToken token)
213+
{
214+
try
215+
{
216+
// Get the URL Status then return boolean and and URLStatus
217+
(bool, string) urlStatus = await TryGetURLStatus(cdnProp, downloadClient, relativeURL, token);
218+
219+
// If URL status is false, then return false
220+
if (!urlStatus.Item1) return false;
221+
222+
// Continue to get the content and return true if successful
223+
if (!cdnProp.PartialDownloadSupport)
224+
{
225+
// If the CDN marked to not supporting the partial download, then use single thread mode download.
226+
using FileStream stream = File.Create(outputPath);
227+
await downloadClient.DownloadAsync(urlStatus.Item2, stream, false, HttpInstance_DownloadProgressAdapter, null, null, cancelToken: token);
228+
return true;
229+
}
230+
await downloadClient.DownloadAsync(urlStatus.Item2, outputPath, true, progressDelegateAsync: HttpInstance_DownloadProgressAdapter, maxConnectionSessions: parallelThread, cancelToken: token);
231+
return true;
232+
}
233+
// Handle the error and log it. If fails, then log it and return false
234+
catch (Exception ex)
235+
{
236+
LogWriteLine($"Failed while getting CDN content from: {cdnProp.Name} (prefix: {cdnProp.URLPrefix}) (relPath: {relativeURL})\r\n{ex}", LogType.Error, true);
237+
return false;
238+
}
239+
}
240+
241+
private static async ValueTask<bool> TryGetCDNContent(CDNURLProperty cdnProp, DownloadClient downloadClient, Stream outputStream, string relativeURL, CancellationToken token)
242+
{
243+
try
244+
{
245+
// Get the URL Status then return boolean and and URLStatus
246+
(bool, string) urlStatus = await TryGetURLStatus(cdnProp, downloadClient, relativeURL, token);
247+
248+
// If URL status is false, then return false
249+
if (!urlStatus.Item1) return false;
250+
251+
// Continue to get the content and return true if successful
252+
await downloadClient.DownloadAsync(urlStatus.Item2, outputStream, false, HttpInstance_DownloadProgressAdapter, null, null, cancelToken: token);
253+
return true;
254+
}
255+
// Handle the error and log it. If fails, then log it and return false
256+
catch (Exception ex)
257+
{
258+
LogWriteLine($"Failed while getting CDN content from: {cdnProp.Name} (prefix: {cdnProp.URLPrefix}) (relPath: {relativeURL})\r\n{ex}", LogType.Error, true);
259+
return false;
260+
}
261+
}
262+
150263
private static async Task<bool> TryGetCDNContent(CDNURLProperty cdnProp, Http httpInstance, string outputPath, string relativeURL, int parallelThread, CancellationToken token)
151264
{
152265
try
@@ -205,6 +318,27 @@ private static async Task<bool> TryGetCDNContent(CDNURLProperty cdnProp, Http ht
205318
return (true, absoluteURL);
206319
}
207320

321+
private static async Task<(bool, string)> TryGetURLStatus(CDNURLProperty cdnProp, DownloadClient downloadClient, string relativeURL, CancellationToken token)
322+
{
323+
// Concat the URL Prefix and Relative URL
324+
string absoluteURL = CombineURLFromString(cdnProp.URLPrefix, relativeURL);
325+
326+
LogWriteLine($"Getting CDN Content from: {cdnProp.Name} at URL: {absoluteURL}", LogType.Default, true);
327+
328+
// Try check the status of the URL
329+
(HttpStatusCode, bool) returnCode = await downloadClient.GetURLStatus(absoluteURL, token);
330+
331+
// If it's not a successful code, then return false
332+
if (!returnCode.Item2)
333+
{
334+
LogWriteLine($"CDN content from: {cdnProp.Name} (prefix: {cdnProp.URLPrefix}) (relPath: {relativeURL}) has returned error code: {returnCode.Item1} ({(int)returnCode.Item1})", LogType.Error, true);
335+
return (false, absoluteURL);
336+
}
337+
338+
// Otherwise, return true
339+
return (true, absoluteURL);
340+
}
341+
208342
public static string CombineURLFromString(string baseURL, params string[] segments)
209343
{
210344
StringBuilder builder = new StringBuilder().Append(baseURL.TrimEnd('/'));
@@ -229,5 +363,15 @@ public static string CombineURLFromString(string baseURL, params string[] segmen
229363

230364
// Re-send the events to the static DownloadProgress
231365
private static void HttpInstance_DownloadProgressAdapter(object sender, DownloadEvent e) => DownloadProgress?.Invoke(sender, e);
366+
367+
private static DownloadEvent DownloadClientAdapter = new DownloadEvent();
368+
369+
private static void HttpInstance_DownloadProgressAdapter(int read, DownloadProgress downloadProgress)
370+
{
371+
DownloadClientAdapter.SizeToBeDownloaded = downloadProgress.BytesTotal;
372+
DownloadClientAdapter.SizeDownloaded = downloadProgress.BytesDownloaded;
373+
DownloadClientAdapter.Read = read;
374+
DownloadProgress?.Invoke(null, DownloadClientAdapter);
375+
}
232376
}
233377
}

ApplyUpdate-Core/Statics.cs

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,6 @@
55

66
namespace ApplyUpdate
77
{
8-
internal enum LogType
9-
{
10-
Scheme, Default, Warning, Error, Debug, NoTag
11-
}
12-
138
[JsonSerializable(typeof(LocalizationParams))]
149
internal partial class CoreLibraryFieldsJSONContext : JsonSerializerContext { }
1510

ApplyUpdate-Core/UpdateTask.cs

Lines changed: 23 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -262,29 +262,32 @@ internal static async Task DownloadPackage(string stamp)
262262
{
263263
if (!File.Exists(zipPath))
264264
{
265-
string packageURL = FallbackCDNUtil.CombineURLFromString("squirrel", stamp, "latest");
266-
using (Http httpClient = new Http(true))
265+
string packageURL = FallbackCDNUtil.CombineURLFromString("velopack", stamp, "latest");
266+
DownloadClient downloadClient = DownloadClient.CreateInstance(MainWindow.GlobalHttpClient);
267+
268+
_propStatus.ActivityStatus = Lang._UpdatePage.ApplyUpdateTaskDownloadingPkgTitle;
269+
InvokeStatus();
270+
Stopwatch localStopwatch = Stopwatch.StartNew();
271+
FallbackCDNUtil.DownloadProgress += (_, progress) =>
267272
{
268-
_propStatus.ActivityStatus = Lang._UpdatePage.ApplyUpdateTaskDownloadingPkgTitle;
273+
_propProgress.SizeProcessed = progress.SizeDownloaded;
274+
_propProgress.SizeToBeProcessed = progress.SizeToBeDownloaded;
275+
_propProgress.Read = progress.Read;
276+
277+
long speed = (long)(progress.SizeDownloaded / localStopwatch.Elapsed.TotalSeconds);
278+
279+
_propProgress.Speed = speed;
280+
281+
_propStatus.IsProgressIndetermined = false;
282+
_propStatus.ActivitySubStatus = string.Format("{0} / {1}", SummarizeSizeSimple(progress.SizeDownloaded), SummarizeSizeSimple(progress.SizeToBeDownloaded));
283+
InvokeProgress();
269284
InvokeStatus();
270-
FallbackCDNUtil.DownloadProgress += (_, progress) =>
271-
{
272-
_propProgress.SizeProcessed = progress.SizeDownloaded;
273-
_propProgress.SizeToBeProcessed = progress.SizeToBeDownloaded;
274-
_propProgress.Read = progress.Read;
275-
_propProgress.Speed = progress.Speed;
276-
277-
_propStatus.IsProgressIndetermined = false;
278-
_propStatus.ActivitySubStatus = string.Format("{0} / {1}", SummarizeSizeSimple(progress.SizeDownloaded), SummarizeSizeSimple(progress.SizeToBeDownloaded));
279-
InvokeProgress();
280-
InvokeStatus();
281285

282-
string print = GetBothAlignedString($"Downloading package: {Math.Round(progress.ProgressPercentage, 2)}% [{SummarizeSizeSimple(progress.Speed)}/s]...", $"[{SummarizeSizeSimple(progress.SizeDownloaded)} / {SummarizeSizeSimple(progress.SizeToBeDownloaded)}]");
283-
Console.Write($"\r{print}");
284-
};
285-
await FallbackCDNUtil.DownloadCDNFallbackContent(httpClient, zipPath, (byte)(Environment.ProcessorCount >= 4 ? 4 : Environment.ProcessorCount), packageURL, default);
286-
Console.WriteLine();
287-
}
286+
string print = GetBothAlignedString($"Downloading package: {Math.Round(progress.ProgressPercentage, 2)}% [{SummarizeSizeSimple(progress.Speed)}/s]...", $"[{SummarizeSizeSimple(progress.SizeDownloaded)} / {SummarizeSizeSimple(progress.SizeToBeDownloaded)}]");
287+
Console.Write($"\r{print}");
288+
};
289+
await FallbackCDNUtil.DownloadCDNFallbackContent(downloadClient, zipPath, (byte)(Environment.ProcessorCount >= 4 ? 4 : Environment.ProcessorCount), packageURL, default);
290+
Console.WriteLine();
288291
}
289292
}
290293

ApplyUpdate-Core/Views/MainWindow.axaml.cs

Lines changed: 21 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@
1212
using System.Diagnostics;
1313
using System.IO;
1414
using System.Linq;
15+
using System.Net;
16+
using System.Net.Http;
1517
using System.Text.Json;
1618
using System.Text.Json.Serialization;
1719
using System.Threading;
@@ -27,6 +29,20 @@ public partial class JSONContext : JsonSerializerContext { }
2729

2830
public partial class MainWindow : Window
2931
{
32+
private static SocketsHttpHandler GlobalSocketHttpHandler = new SocketsHttpHandler
33+
{
34+
AutomaticDecompression = DecompressionMethods.None,
35+
AllowAutoRedirect = true,
36+
EnableMultipleHttp3Connections = true,
37+
EnableMultipleHttp2Connections = true,
38+
MaxConnectionsPerServer = 256
39+
};
40+
public static HttpClient GlobalHttpClient = new HttpClient(GlobalSocketHttpHandler, false)
41+
{
42+
DefaultRequestVersion = HttpVersion.Version30,
43+
DefaultVersionPolicy = HttpVersionPolicy.RequestVersionOrLower
44+
};
45+
3046
private CancellationTokenSource CountDownToken { get; set; }
3147
private string Stamp { get; set; }
3248
private bool IsStampFromFile { get; set; }
@@ -468,14 +484,11 @@ private void SpawnError(string message)
468484

469485
private async Task<AppUpdateVersionProp> FetchMetadata(string stamp)
470486
{
471-
using (MemoryStream ms = new MemoryStream())
472-
using (Http _httpClient = new Http())
473-
{
474-
await FallbackCDNUtil.DownloadCDNFallbackContent(_httpClient, ms, $"{stamp}/fileindex.json", default);
475-
ms.Position = 0;
476-
AppUpdateVersionProp updateInfo = (AppUpdateVersionProp)await JsonSerializer.DeserializeAsync(ms, typeof(AppUpdateVersionProp), JSONContext.Default)!;
477-
return updateInfo;
478-
}
487+
CDNURLProperty preferredUrl = FallbackCDNUtil.GetPreferredCDN();
488+
string stampUrl = FallbackCDNUtil.CombineURLFromString(preferredUrl.URLPrefix, stamp, "fileindex.json");
489+
await using Stream stream = await HttpResponseInputStream.CreateStreamAsync(GlobalHttpClient, stampUrl, 0, null, null, null, null, default);
490+
AppUpdateVersionProp updateInfo = await JsonSerializer.DeserializeAsync(stream, JSONContext.Default.AppUpdateVersionProp)!;
491+
return updateInfo;
479492
}
480493

481494
private async void UpdateTask_UpdateStatus(object sender, UpdateStatus e)

ApplyUpdate-Desktop/ApplyUpdate-Desktop.csproj

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,13 @@
33
<OutputType>Exe</OutputType>
44
<!--If you are willing to use Windows/MacOS native APIs you will need to create 3 projects.
55
One for Windows with net7.0-windows TFM, one for MacOS with net7.0-macos and one with net7.0 TFM for Linux.-->
6-
<TargetFramework>net8.0</TargetFramework>
6+
<TargetFramework>net9.0</TargetFramework>
77
<Nullable>enable</Nullable>
88
<BuiltInComInteropSupport>true</BuiltInComInteropSupport>
99
<ApplicationManifest>app.manifest</ApplicationManifest>
1010
<ApplicationIcon>..\ApplyUpdate-Core\Assets\icon.ico</ApplicationIcon>
1111
<Platforms>x64</Platforms>
12-
<Version>2.1.0</Version>
12+
<Version>2.2.0</Version>
1313
</PropertyGroup>
1414

1515
<ItemGroup>

ApplyUpdate-Desktop/Properties/PublishProfiles/NativeAOT_win-x64.pubxml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ https://go.microsoft.com/fwlink/?LinkID=208121.
99
<PublishDir>build</PublishDir>
1010
<PublishProtocol>FileSystem</PublishProtocol>
1111
<_TargetId>Folder</_TargetId>
12-
<TargetFramework>net8.0</TargetFramework>
12+
<TargetFramework>net9.0</TargetFramework>
1313
<RuntimeIdentifier>win-x64</RuntimeIdentifier>
1414
<Optimize>True</Optimize>
1515
<PublishAot>true</PublishAot>
@@ -27,5 +27,6 @@ https://go.microsoft.com/fwlink/?LinkID=208121.
2727
<HttpActivityPropagationSupport>false</HttpActivityPropagationSupport>
2828
<UseSystemResourceKeys>true</UseSystemResourceKeys>
2929
<DebuggerSupport>false</DebuggerSupport>
30+
<IlcOptimizationPreference>Speed</IlcOptimizationPreference>
3031
</PropertyGroup>
3132
</Project>

0 commit comments

Comments
 (0)