beginnings of v4.8.0.0
- Minor refactoring - Added an HTTP content cache - Replaced Onova with custom update functionality (work in progress)
This commit is contained in:
parent
52e82e2397
commit
74c5588d64
9 changed files with 219 additions and 249 deletions
|
@ -4,7 +4,7 @@
|
|||
<TargetFramework>net7.0-windows</TargetFramework>
|
||||
<UseWindowsForms>True</UseWindowsForms>
|
||||
<ApplicationIcon>Resources\ini.ico</ApplicationIcon>
|
||||
<Version>4.7.1.3</Version>
|
||||
<Version>4.8.0.0</Version>
|
||||
<Copyright>2021, pointfeev (https://github.com/pointfeev)</Copyright>
|
||||
<Company>CreamInstaller</Company>
|
||||
<Product>Automatic DLC Unlocker Installer & Configuration Generator</Product>
|
||||
|
@ -147,7 +147,6 @@
|
|||
<PackageReference Include="Gameloop.Vdf" Version="0.6.2" />
|
||||
<PackageReference Include="HtmlAgilityPack" Version="1.11.46" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.2" />
|
||||
<PackageReference Include="Onova" Version="2.6.6" />
|
||||
<PackageReference Include="System.ServiceModel.Primitives" Version="4.10.0" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
|
|
|
@ -1,240 +0,0 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Drawing;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Web;
|
||||
using System.Windows.Forms;
|
||||
using CreamInstaller.Components;
|
||||
using CreamInstaller.Utility;
|
||||
using HtmlAgilityPack;
|
||||
using Onova;
|
||||
using Onova.Models;
|
||||
using Onova.Services;
|
||||
|
||||
namespace CreamInstaller.Forms;
|
||||
|
||||
internal sealed partial class MainForm : CustomForm
|
||||
{
|
||||
private CancellationTokenSource cancellationTokenSource;
|
||||
private Version latestVersion;
|
||||
|
||||
private UpdateManager updateManager;
|
||||
private IReadOnlyList<Version> versions;
|
||||
|
||||
internal MainForm()
|
||||
{
|
||||
InitializeComponent();
|
||||
Text = Program.ApplicationNameShort;
|
||||
}
|
||||
|
||||
private void StartProgram()
|
||||
{
|
||||
if (cancellationTokenSource is not null)
|
||||
{
|
||||
cancellationTokenSource.Cancel();
|
||||
cancellationTokenSource.Dispose();
|
||||
cancellationTokenSource = null;
|
||||
}
|
||||
#pragma warning disable CA2000 // Dispose objects before losing scope
|
||||
SelectForm form = new();
|
||||
#pragma warning restore CA2000 // Dispose objects before losing scope
|
||||
form.InheritLocation(this);
|
||||
form.FormClosing += (_, _) => Close();
|
||||
form.Show();
|
||||
Hide();
|
||||
#if DEBUG
|
||||
DebugForm.Current.Attach(form);
|
||||
#endif
|
||||
}
|
||||
|
||||
private async void OnLoad()
|
||||
{
|
||||
progressBar.Visible = false;
|
||||
ignoreButton.Visible = true;
|
||||
updateButton.Text = "Update";
|
||||
updateButton.Click -= OnUpdateCancel;
|
||||
progressLabel.Text = "Checking for updates . . .";
|
||||
changelogTreeView.Visible = false;
|
||||
changelogTreeView.Location = progressLabel.Location with { Y = progressLabel.Location.Y + progressLabel.Size.Height + 13 };
|
||||
Refresh();
|
||||
#if DEBUG
|
||||
DebugForm.Current.Attach(this);
|
||||
#endif
|
||||
GithubPackageResolver resolver = new(Program.RepositoryOwner, Program.RepositoryName, Program.RepositoryPackage);
|
||||
ZipPackageExtractor extractor = new();
|
||||
updateManager = new(AssemblyMetadata.FromAssembly(Program.EntryAssembly, Program.CurrentProcessFilePath), resolver, extractor);
|
||||
if (latestVersion is null)
|
||||
{
|
||||
cancellationTokenSource = new();
|
||||
try
|
||||
{
|
||||
CheckForUpdatesResult checkForUpdatesResult = await updateManager.CheckForUpdatesAsync(cancellationTokenSource.Token);
|
||||
#if !DEBUG
|
||||
if (checkForUpdatesResult.CanUpdate)
|
||||
{
|
||||
#endif
|
||||
latestVersion = checkForUpdatesResult.LastVersion;
|
||||
versions = checkForUpdatesResult.Versions;
|
||||
#if !DEBUG
|
||||
}
|
||||
#endif
|
||||
}
|
||||
#if DEBUG
|
||||
catch (TaskCanceledException) { }
|
||||
catch (Exception e)
|
||||
{
|
||||
DebugForm.Current.Log($"Exception while checking for updates: {e.GetType()} ({e.Message})", LogTextBox.Warning);
|
||||
}
|
||||
#else
|
||||
catch
|
||||
{
|
||||
// ignored
|
||||
}
|
||||
#endif
|
||||
finally
|
||||
{
|
||||
cancellationTokenSource.Dispose();
|
||||
cancellationTokenSource = null;
|
||||
}
|
||||
}
|
||||
if (latestVersion is null)
|
||||
{
|
||||
updateManager.Dispose();
|
||||
updateManager = null;
|
||||
StartProgram();
|
||||
}
|
||||
else
|
||||
{
|
||||
progressLabel.Text = $"An update is available: v{latestVersion}";
|
||||
ignoreButton.Enabled = true;
|
||||
updateButton.Enabled = true;
|
||||
updateButton.Click += OnUpdate;
|
||||
changelogTreeView.Visible = true;
|
||||
Version currentVersion = new(Program.Version);
|
||||
#if DEBUG
|
||||
foreach (Version version in versions.Where(v => (v > currentVersion || v == latestVersion) && !changelogTreeView.Nodes.ContainsKey(v.ToString())))
|
||||
#else
|
||||
foreach (Version version in versions.Where(v => v > currentVersion && !changelogTreeView.Nodes.ContainsKey(v.ToString())))
|
||||
#endif
|
||||
{
|
||||
TreeNode root = new($"v{version}") { Name = version.ToString() };
|
||||
changelogTreeView.Nodes.Add(root);
|
||||
if (changelogTreeView.Nodes.Count > 0)
|
||||
changelogTreeView.Nodes[0].EnsureVisible();
|
||||
_ = Task.Run(async () =>
|
||||
{
|
||||
HtmlNodeCollection nodes = await HttpClientManager.GetDocumentNodes(
|
||||
$"https://github.com/{Program.RepositoryOwner}/{Program.RepositoryName}/releases/tag/v{version}",
|
||||
"//div[@data-test-selector='body-content']/ul/li");
|
||||
if (nodes is null)
|
||||
changelogTreeView.Nodes.Remove(root);
|
||||
else
|
||||
foreach (HtmlNode node in nodes)
|
||||
changelogTreeView.Invoke(delegate
|
||||
{
|
||||
TreeNode change = new() { Text = HttpUtility.HtmlDecode(node.InnerText) ?? string.Empty };
|
||||
root.Nodes.Add(change);
|
||||
root.Expand();
|
||||
if (changelogTreeView.Nodes.Count > 0)
|
||||
changelogTreeView.Nodes[0].EnsureVisible();
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void OnLoad(object sender, EventArgs _)
|
||||
{
|
||||
retry:
|
||||
try
|
||||
{
|
||||
string fileName = Path.GetFileName(Program.CurrentProcessFilePath);
|
||||
if (fileName != Program.ApplicationExecutable)
|
||||
{
|
||||
using DialogForm form = new(this);
|
||||
if (form.Show(SystemIcons.Warning,
|
||||
"WARNING: " + Program.ApplicationExecutable + " was renamed!" + "\n\nThis will cause undesirable behavior when updating the program!",
|
||||
"Ignore", "Abort") == DialogResult.Cancel)
|
||||
{
|
||||
Application.Exit();
|
||||
return;
|
||||
}
|
||||
}
|
||||
OnLoad();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
if (e.HandleException(this))
|
||||
goto retry;
|
||||
Close();
|
||||
}
|
||||
}
|
||||
|
||||
private void OnIgnore(object sender, EventArgs e) => StartProgram();
|
||||
|
||||
private async void OnUpdate(object sender, EventArgs e)
|
||||
{
|
||||
progressBar.Visible = true;
|
||||
ignoreButton.Visible = false;
|
||||
updateButton.Text = "Cancel";
|
||||
updateButton.Click -= OnUpdate;
|
||||
updateButton.Click += OnUpdateCancel;
|
||||
changelogTreeView.Location = progressBar.Location with { Y = progressBar.Location.Y + progressBar.Size.Height + 6 };
|
||||
Refresh();
|
||||
Progress<double> progress = new();
|
||||
progress.ProgressChanged += delegate(object _, double _progress)
|
||||
{
|
||||
progressLabel.Text = $"Updating . . . {(int)_progress}%";
|
||||
progressBar.Value = (int)_progress;
|
||||
};
|
||||
progressLabel.Text = "Updating . . . ";
|
||||
cancellationTokenSource = new();
|
||||
try
|
||||
{
|
||||
await updateManager.PrepareUpdateAsync(latestVersion, progress, cancellationTokenSource.Token);
|
||||
}
|
||||
#if DEBUG
|
||||
catch (TaskCanceledException) { }
|
||||
catch (Exception ex)
|
||||
{
|
||||
DebugForm.Current.Log($"Exception while preparing update: {ex.GetType()} ({ex.Message})", LogTextBox.Warning);
|
||||
}
|
||||
#else
|
||||
catch
|
||||
{
|
||||
// ignored
|
||||
}
|
||||
#endif
|
||||
finally
|
||||
{
|
||||
cancellationTokenSource.Dispose();
|
||||
cancellationTokenSource = null;
|
||||
}
|
||||
if (updateManager is not null && updateManager.IsUpdatePrepared(latestVersion))
|
||||
{
|
||||
updateManager.LaunchUpdater(latestVersion);
|
||||
Application.Exit();
|
||||
return;
|
||||
}
|
||||
OnLoad();
|
||||
}
|
||||
|
||||
private void OnUpdateCancel(object sender, EventArgs e)
|
||||
{
|
||||
cancellationTokenSource?.Cancel();
|
||||
updateManager?.Dispose();
|
||||
updateManager = null;
|
||||
}
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
if (disposing)
|
||||
components?.Dispose();
|
||||
base.Dispose(disposing);
|
||||
cancellationTokenSource?.Dispose();
|
||||
updateManager?.Dispose();
|
||||
}
|
||||
}
|
|
@ -4,7 +4,7 @@ using CreamInstaller.Components;
|
|||
|
||||
namespace CreamInstaller.Forms
|
||||
{
|
||||
partial class MainForm
|
||||
partial class UpdateForm
|
||||
{
|
||||
private IContainer components = null;
|
||||
|
||||
|
@ -78,7 +78,7 @@ namespace CreamInstaller.Forms
|
|||
this.changelogTreeView.Sorted = true;
|
||||
this.changelogTreeView.TabIndex = 5;
|
||||
//
|
||||
// MainForm
|
||||
// UpdateForm
|
||||
//
|
||||
this.AutoScaleDimensions = new System.Drawing.SizeF(7F, 15F);
|
||||
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
|
||||
|
@ -94,9 +94,9 @@ namespace CreamInstaller.Forms
|
|||
this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedSingle;
|
||||
this.MaximizeBox = false;
|
||||
this.MinimizeBox = false;
|
||||
this.Name = "MainForm";
|
||||
this.Name = "UpdateForm";
|
||||
this.StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen;
|
||||
this.Text = "MainForm";
|
||||
this.Text = "UpdateForm";
|
||||
this.Load += new System.EventHandler(this.OnLoad);
|
||||
this.ResumeLayout(false);
|
||||
|
152
CreamInstaller/Forms/UpdateForm.cs
Normal file
152
CreamInstaller/Forms/UpdateForm.cs
Normal file
|
@ -0,0 +1,152 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Drawing;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Windows.Forms;
|
||||
using CreamInstaller.Components;
|
||||
using CreamInstaller.Utility;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace CreamInstaller.Forms;
|
||||
|
||||
internal sealed partial class UpdateForm : CustomForm
|
||||
{
|
||||
private Release latestRelease;
|
||||
|
||||
internal UpdateForm()
|
||||
{
|
||||
InitializeComponent();
|
||||
Text = Program.ApplicationNameShort;
|
||||
}
|
||||
|
||||
private void StartProgram()
|
||||
{
|
||||
SelectForm form = new();
|
||||
form.InheritLocation(this);
|
||||
form.FormClosing += (_, _) => Close();
|
||||
form.Show();
|
||||
Hide();
|
||||
#if DEBUG
|
||||
DebugForm.Current.Attach(form);
|
||||
#endif
|
||||
}
|
||||
|
||||
private async void OnLoad()
|
||||
{
|
||||
progressBar.Visible = false;
|
||||
ignoreButton.Visible = true;
|
||||
updateButton.Text = "Update";
|
||||
updateButton.Click -= OnUpdateCancel;
|
||||
progressLabel.Text = "Checking for updates . . .";
|
||||
changelogTreeView.Visible = false;
|
||||
changelogTreeView.Location = progressLabel.Location with { Y = progressLabel.Location.Y + progressLabel.Size.Height + 13 };
|
||||
Refresh();
|
||||
#if !DEBUG
|
||||
Version currentVersion = new(Program.Version);
|
||||
#endif
|
||||
List<Release> releases = null;
|
||||
string response = await HttpClientManager.EnsureGet($"https://api.github.com/repos/{Program.RepositoryOwner}/{Program.RepositoryName}/releases");
|
||||
if (response is not null)
|
||||
releases = JsonConvert.DeserializeObject<List<Release>>(response)
|
||||
?.Where(release => !release.Draft && !release.Prerelease && release.Assets.Count > 0).ToList();
|
||||
latestRelease = releases?.FirstOrDefault();
|
||||
#if DEBUG
|
||||
if (latestRelease?.Version is not { } latestVersion)
|
||||
#else
|
||||
if (latestRelease?.Version is not { } latestVersion || latestVersion <= currentVersion)
|
||||
#endif
|
||||
StartProgram();
|
||||
else
|
||||
{
|
||||
progressLabel.Text = $"An update is available: v{latestVersion}";
|
||||
ignoreButton.Enabled = true;
|
||||
updateButton.Enabled = true;
|
||||
updateButton.Click += OnUpdate;
|
||||
changelogTreeView.Visible = true;
|
||||
for (int r = releases!.Count - 1; r >= 0; r--)
|
||||
{
|
||||
Release release = releases[r];
|
||||
#if !DEBUG
|
||||
if (release.Version <= currentVersion)
|
||||
continue;
|
||||
#endif
|
||||
TreeNode root = new(release.Name) { Name = release.Name };
|
||||
changelogTreeView.Nodes.Add(root);
|
||||
if (changelogTreeView.Nodes.Count > 0)
|
||||
changelogTreeView.Nodes[0].EnsureVisible();
|
||||
for (int i = release.Changes.Length - 1; i >= 0; i--)
|
||||
changelogTreeView.Invoke(delegate
|
||||
{
|
||||
string change = release.Changes[i];
|
||||
TreeNode changeNode = new() { Text = change };
|
||||
root.Nodes.Add(changeNode);
|
||||
root.Expand();
|
||||
if (changelogTreeView.Nodes.Count > 0)
|
||||
changelogTreeView.Nodes[0].EnsureVisible();
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void OnLoad(object sender, EventArgs _)
|
||||
{
|
||||
retry:
|
||||
try
|
||||
{
|
||||
string fileName = Path.GetFileName(Program.CurrentProcessFilePath);
|
||||
if (fileName != Program.ApplicationExecutable)
|
||||
{
|
||||
using DialogForm form = new(this);
|
||||
if (form.Show(SystemIcons.Warning,
|
||||
"WARNING: " + Program.ApplicationExecutable + " was renamed!" + "\n\nThis will cause undesirable behavior when updating the program!",
|
||||
"Ignore", "Abort") == DialogResult.Cancel)
|
||||
{
|
||||
Application.Exit();
|
||||
return;
|
||||
}
|
||||
}
|
||||
OnLoad();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
if (e.HandleException(this))
|
||||
goto retry;
|
||||
Close();
|
||||
}
|
||||
}
|
||||
|
||||
private void OnIgnore(object sender, EventArgs e) => StartProgram();
|
||||
|
||||
private void OnUpdate(object sender, EventArgs e)
|
||||
{
|
||||
progressBar.Visible = true;
|
||||
ignoreButton.Visible = false;
|
||||
updateButton.Text = "Cancel";
|
||||
updateButton.Click -= OnUpdate;
|
||||
updateButton.Click += OnUpdateCancel;
|
||||
changelogTreeView.Location = progressBar.Location with { Y = progressBar.Location.Y + progressBar.Size.Height + 6 };
|
||||
Refresh();
|
||||
Progress<double> progress = new();
|
||||
progress.ProgressChanged += delegate(object _, double _progress)
|
||||
{
|
||||
progressLabel.Text = $"Updating . . . {(int)_progress}%";
|
||||
progressBar.Value = (int)_progress;
|
||||
};
|
||||
progressLabel.Text = "Updating . . . ";
|
||||
// do update
|
||||
OnLoad();
|
||||
}
|
||||
|
||||
private void OnUpdateCancel(object sender, EventArgs e)
|
||||
{
|
||||
// cancel update
|
||||
}
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
if (disposing)
|
||||
components?.Dispose();
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
}
|
|
@ -40,7 +40,7 @@ internal static class SteamStore
|
|||
string response = await HttpClientManager.EnsureGet($"https://store.steampowered.com/api/appdetails?appids={appId}");
|
||||
if (response is not null)
|
||||
{
|
||||
IDictionary<string, JToken> apps = (IDictionary<string, JToken>)JsonConvert.DeserializeObject(response);
|
||||
Dictionary<string, JToken> apps = JsonConvert.DeserializeObject<Dictionary<string, JToken>>(response);
|
||||
if (apps is not null)
|
||||
foreach (KeyValuePair<string, JToken> app in apps)
|
||||
try
|
||||
|
|
|
@ -86,7 +86,7 @@ internal static class Program
|
|||
try
|
||||
{
|
||||
HttpClientManager.Setup();
|
||||
using MainForm form = new();
|
||||
using UpdateForm form = new();
|
||||
#if DEBUG
|
||||
DebugForm.Current.Attach(form);
|
||||
#endif
|
||||
|
|
52
CreamInstaller/Release.cs
Normal file
52
CreamInstaller/Release.cs
Normal file
|
@ -0,0 +1,52 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace CreamInstaller;
|
||||
|
||||
public class Release
|
||||
{
|
||||
private string[] changes;
|
||||
|
||||
private Version version;
|
||||
|
||||
[JsonProperty("tag_name", NullValueHandling = NullValueHandling.Ignore)]
|
||||
public string TagName { get; set; }
|
||||
|
||||
[JsonProperty("name", NullValueHandling = NullValueHandling.Ignore)]
|
||||
public string Name { get; set; }
|
||||
|
||||
[JsonProperty("draft", NullValueHandling = NullValueHandling.Ignore)]
|
||||
public bool Draft { get; set; }
|
||||
|
||||
[JsonProperty("prerelease", NullValueHandling = NullValueHandling.Ignore)]
|
||||
public bool Prerelease { get; set; }
|
||||
|
||||
[JsonProperty("assets", NullValueHandling = NullValueHandling.Ignore)]
|
||||
public List<Asset> Assets { get; } = new();
|
||||
|
||||
[JsonProperty("body", NullValueHandling = NullValueHandling.Ignore)]
|
||||
public string Body { get; set; }
|
||||
|
||||
public Version Version => version ??= new(TagName[1..]);
|
||||
|
||||
public string[] Changes => changes ??= Body.Replace("- ", "").Split("\r\n");
|
||||
}
|
||||
|
||||
public class Asset
|
||||
{
|
||||
[JsonProperty("name", NullValueHandling = NullValueHandling.Ignore)]
|
||||
public string Name { get; set; }
|
||||
|
||||
[JsonProperty("content_type", NullValueHandling = NullValueHandling.Ignore)]
|
||||
public string ContentType { get; set; }
|
||||
|
||||
[JsonProperty("state", NullValueHandling = NullValueHandling.Ignore)]
|
||||
public string State { get; set; }
|
||||
|
||||
[JsonProperty("size", NullValueHandling = NullValueHandling.Ignore)]
|
||||
public int Size { get; set; }
|
||||
|
||||
[JsonProperty("browser_download_url", NullValueHandling = NullValueHandling.Ignore)]
|
||||
public string BrowserDownloadUrl { get; set; }
|
||||
}
|
|
@ -1,4 +1,5 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Drawing;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
|
@ -14,6 +15,8 @@ internal static class HttpClientManager
|
|||
{
|
||||
internal static HttpClient HttpClient;
|
||||
|
||||
private static readonly Dictionary<string, string> HttpContentCache = new();
|
||||
|
||||
internal static void Setup()
|
||||
{
|
||||
HttpClient = new();
|
||||
|
@ -26,8 +29,12 @@ internal static class HttpClientManager
|
|||
{
|
||||
using HttpRequestMessage request = new(HttpMethod.Get, url);
|
||||
using HttpResponseMessage response = await HttpClient.SendAsync(request, HttpCompletionOption.ResponseHeadersRead);
|
||||
if (response.StatusCode is HttpStatusCode.NotModified && HttpContentCache.TryGetValue(url, out string content))
|
||||
return content;
|
||||
_ = response.EnsureSuccessStatusCode();
|
||||
return await response.Content.ReadAsStringAsync();
|
||||
content = await response.Content.ReadAsStringAsync();
|
||||
HttpContentCache[url] = content;
|
||||
return content;
|
||||
}
|
||||
catch (HttpRequestException e)
|
||||
{
|
||||
|
|
Loading…
Reference in a new issue