mirror of
https://github.com/LorenzCK/OnTopReplica.git
synced 2024-05-21 04:43:55 +12:00
298 lines
11 KiB
C#
298 lines
11 KiB
C#
using System;
|
|
using System.Diagnostics;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using System.Net;
|
|
using System.Net.Cache;
|
|
using System.Text.RegularExpressions;
|
|
using System.Windows.Forms;
|
|
using System.Xml.Linq;
|
|
using WindowsFormsAero.TaskDialog;
|
|
|
|
namespace OnTopReplica.Update {
|
|
|
|
/// <summary>
|
|
/// Handles update checking and information display.
|
|
/// </summary>
|
|
class UpdateManager {
|
|
|
|
/// <summary>
|
|
/// Constructs a new update manager with an attached form.
|
|
/// </summary>
|
|
/// <param name="attachedForm">Form through which all GUI calls are made. Closing this form should terminate the application.</param>
|
|
public UpdateManager(Form attachedForm) {
|
|
if (attachedForm == null)
|
|
throw new ArgumentNullException();
|
|
|
|
AttachedForm = attachedForm;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets or sets the attached form (through which all GUI calls are made).
|
|
/// </summary>
|
|
protected Form AttachedForm { get; private set; }
|
|
|
|
#region Checking
|
|
|
|
const string UpdateFeedUrl = "https://ontopreplica.codeplex.com/project/feeds/rss?ProjectRSSFeed=codeplex%3a%2f%2frelease%2fontopreplica";
|
|
|
|
/// <summary>
|
|
/// Gets the latest update information available.
|
|
/// </summary>
|
|
public UpdateInformation LastInformation { get; private set; }
|
|
|
|
HttpWebRequest _checkRequest;
|
|
|
|
/// <summary>
|
|
/// Checks for update asynchronously, updating update information.
|
|
/// When check is completed, raises update events.
|
|
/// </summary>
|
|
public void CheckForUpdate() {
|
|
if (_checkRequest != null) {
|
|
_checkRequest.Abort();
|
|
}
|
|
|
|
//Build web request
|
|
_checkRequest = (HttpWebRequest)HttpWebRequest.Create(UpdateFeedUrl);
|
|
_checkRequest.AllowAutoRedirect = true;
|
|
_checkRequest.AutomaticDecompression = DecompressionMethods.Deflate | DecompressionMethods.GZip;
|
|
_checkRequest.CachePolicy = new RequestCachePolicy(RequestCacheLevel.BypassCache);
|
|
|
|
_checkRequest.BeginGetResponse(CheckForUpdateCallback, null);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Asynchronous callback that handles the update check request.
|
|
/// </summary>
|
|
private void CheckForUpdateCallback(IAsyncResult result) {
|
|
if (_checkRequest == null)
|
|
return;
|
|
|
|
try {
|
|
var response = _checkRequest.EndGetResponse(result);
|
|
LastInformation = ParseUpdateCheckResponse(response.GetResponseStream());
|
|
|
|
OnUpdateCheckSuccess(LastInformation);
|
|
}
|
|
catch (Exception ex) {
|
|
OnUpdateCheckError(ex);
|
|
}
|
|
|
|
_checkRequest = null;
|
|
}
|
|
|
|
private Regex _versionExtractor = new Regex(@"^Released: Release (?<version>([0-9]\.){0,3}[0-9]?)", RegexOptions.Compiled | RegexOptions.Singleline);
|
|
|
|
private UpdateInformation ParseUpdateCheckResponse(Stream stream) {
|
|
var xdoc = XDocument.Load(stream);
|
|
|
|
var releases = from item in xdoc.Descendants("item")
|
|
let title = item.Element("title").Value
|
|
let match = _versionExtractor.Match(title)
|
|
where match.Success
|
|
let versionNumber = match.Groups["version"].Value
|
|
orderby versionNumber descending
|
|
select new { versionNumber, item.Element("link").Value };
|
|
|
|
return new UpdateInformation();
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Eventing
|
|
|
|
public event EventHandler<UpdateCheckCompletedEventArgs> UpdateCheckCompleted;
|
|
|
|
protected virtual void OnUpdateCheckError(Exception ex) {
|
|
var evt = UpdateCheckCompleted;
|
|
if (evt != null) {
|
|
evt(this, new UpdateCheckCompletedEventArgs {
|
|
Success = false,
|
|
Error = ex
|
|
});
|
|
}
|
|
}
|
|
|
|
protected virtual void OnUpdateCheckSuccess(UpdateInformation information) {
|
|
var evt = UpdateCheckCompleted;
|
|
if (evt != null) {
|
|
evt(this, new UpdateCheckCompletedEventArgs {
|
|
Success = true,
|
|
Information = information
|
|
});
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Updating
|
|
|
|
HttpWebRequest _downloadRequest;
|
|
TaskDialog _updateDialog;
|
|
bool _updateDownloaded = false;
|
|
|
|
/// <summary>
|
|
/// Asks confirmation for an update and installs the update.
|
|
/// </summary>
|
|
public void ConfirmAndInstall() {
|
|
if (LastInformation == null || !LastInformation.IsNewVersion)
|
|
return;
|
|
|
|
AttachedForm.SafeInvoke(new Action(ConfirmAndInstallCore));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Core delegate that asks for update confirmation and installs. Must be called from GUI thread.
|
|
/// </summary>
|
|
private void ConfirmAndInstallCore() {
|
|
_updateDialog = new TaskDialog {
|
|
Title = Strings.UpdateTitle,
|
|
Instruction = string.Format(Strings.UpdateAvailableInstruction, LastInformation.LatestVersion),
|
|
Content = Strings.UpdateAvailableContent,
|
|
CustomButtons = new CustomButton[] {
|
|
new CustomButton(Result.OK, string.Format(Strings.UpdateAvailableCommandOk, LastInformation.LatestVersion)),
|
|
new CustomButton(Result.Cancel, Strings.UpdateAvailableCommandCancel)
|
|
},
|
|
UseCommandLinks = true,
|
|
CommonIcon = TaskDialogIcon.Information,
|
|
ExpandedInformation = string.Format(Strings.UpdateAvailableExpanded, LastInformation.CurrentVersion, LastInformation.LatestVersion),
|
|
};
|
|
_updateDialog.ButtonClick += delegate(object sender, ClickEventArgs args) {
|
|
if (args.ButtonID == (int)Result.OK) {
|
|
args.PreventClosing = true;
|
|
|
|
if (_updateDownloaded) {
|
|
//Terminate application
|
|
AttachedForm.Close();
|
|
|
|
//Launch updater
|
|
Process.Start(UpdateInstallerPath);
|
|
}
|
|
else {
|
|
var downDlg = new TaskDialog {
|
|
Title = Strings.UpdateTitle,
|
|
Instruction = Strings.UpdateDownloadingInstruction,
|
|
ShowProgressBar = true,
|
|
ProgressBarMinRange = 0,
|
|
ProgressBarMaxRange = 100,
|
|
ProgressBarPosition = 0,
|
|
CommonButtons = TaskDialogButton.Cancel
|
|
};
|
|
_updateDialog.Navigate(downDlg);
|
|
|
|
_downloadRequest = (HttpWebRequest)HttpWebRequest.Create(LastInformation.DownloadInstaller);
|
|
_downloadRequest.BeginGetResponse(DownloadAsyncCallback, null);
|
|
}
|
|
}
|
|
};
|
|
|
|
_updateDialog.Show(AttachedForm);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the target filename used when downloading the update from the Internet.
|
|
/// </summary>
|
|
private string UpdateInstallerPath {
|
|
get {
|
|
var downloadPath = Native.FilesystemMethods.DownloadsPath;
|
|
|
|
string versionName = (LastInformation != null) ?
|
|
LastInformation.LatestVersion.ToString() : string.Empty;
|
|
string filename = string.Format("OnTopReplica-Update-{0}.exe", versionName);
|
|
|
|
return Path.Combine(downloadPath, filename);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Handles background downloading.
|
|
/// </summary>
|
|
private void DownloadAsyncCallback(IAsyncResult result) {
|
|
if (_downloadRequest == null || _updateDialog == null)
|
|
return;
|
|
|
|
try {
|
|
var response = _downloadRequest.EndGetResponse(result);
|
|
var responseStream = response.GetResponseStream();
|
|
long total = response.ContentLength;
|
|
|
|
byte[] buffer = new byte[1024];
|
|
|
|
using (var stream = new FileStream(UpdateInstallerPath, FileMode.Create)) {
|
|
int readTotal = 0;
|
|
while (true) {
|
|
int read = responseStream.Read(buffer, 0, buffer.Length);
|
|
readTotal += read;
|
|
|
|
if (read <= 0) //EOF
|
|
break;
|
|
|
|
stream.Write(buffer, 0, read);
|
|
|
|
_updateDialog.Content = string.Format(Strings.UpdateDownloadingContent, readTotal, total);
|
|
_updateDialog.ProgressBarPosition = (int)((readTotal * 100.0) / total);
|
|
}
|
|
}
|
|
}
|
|
catch (Exception ex) {
|
|
DownloadShowError(ex.Message);
|
|
return;
|
|
}
|
|
|
|
_updateDownloaded = true;
|
|
|
|
var okDlg = new TaskDialog {
|
|
Title = Strings.UpdateTitle,
|
|
Instruction = Strings.UpdateReadyInstruction,
|
|
Content = string.Format(Strings.UpdateReadyContent, LastInformation.LatestVersion),
|
|
UseCommandLinks = true,
|
|
CommonButtons = TaskDialogButton.Cancel,
|
|
CustomButtons = new CustomButton[] {
|
|
new CustomButton(Result.OK, Strings.UpdateReadyCommandOk)
|
|
}
|
|
};
|
|
_updateDialog.Navigate(okDlg);
|
|
}
|
|
|
|
private void DownloadShowError(string msg) {
|
|
if (_updateDialog == null)
|
|
return;
|
|
|
|
_updateDialog.ProgressBarState = WindowsFormsAero.ProgressBar.States.Error;
|
|
_updateDialog.Content = msg;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Displays some information about the current installation and available updates.
|
|
/// </summary>
|
|
public void DisplayInfo() {
|
|
AttachedForm.SafeInvoke(new Action(DisplayInfoCore));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Displays info. Called from GUI thread.
|
|
/// </summary>
|
|
private void DisplayInfoCore() {
|
|
//No updates, but need to inform the user
|
|
var dlg = new TaskDialog {
|
|
Title = Strings.UpdateTitle,
|
|
Instruction = Strings.UpdateInfoInstruction,
|
|
Content = Strings.UpdateInfoContent,
|
|
EnableHyperlinks = true,
|
|
CommonButtons = TaskDialogButton.Close,
|
|
AllowDialogCancellation = true,
|
|
CommonIcon = TaskDialogIcon.Information,
|
|
Footer = string.Format(Strings.UpdateInfoFooter, LastInformation.LatestVersionRelease.ToLongDateString())
|
|
};
|
|
dlg.HyperlinkClick += delegate(object sender, HyperlinkEventArgs args) {
|
|
Process.Start("http://ontopreplica.codeplex.com");
|
|
};
|
|
dlg.Show(AttachedForm);
|
|
}
|
|
|
|
#endregion
|
|
|
|
}
|
|
|
|
}
|