Issue #54: new updating mechanism, now based on official CodePlex RSS release feed.

Some code clean-up.
This commit is contained in:
Lorenz Cuno Klopfenstein 2014-01-08 23:15:49 +01:00
parent b75214d5e4
commit 56e47fb7e5
11 changed files with 229 additions and 191 deletions

View file

@ -0,0 +1,135 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="ApplicationWebsite" xml:space="preserve">
<value>http://ontopreplica.codeplex.com</value>
</data>
<data name="AuthorWebsite" xml:space="preserve">
<value>http://lorenz.klopfenstein.net</value>
</data>
<data name="LatestCommitsLink" xml:space="preserve">
<value>http://ontopreplica.codeplex.com/SourceControl/list/changesets</value>
</data>
<data name="MsRlLicenseLink" xml:space="preserve">
<value>http://opensource.org/licenses/ms-rl.html</value>
</data>
<data name="UpdateFeed" xml:space="preserve">
<value>http://ontopreplica.codeplex.com/project/feeds/rss?ProjectRSSFeed=codeplex%3a%2f%2frelease%2fontopreplica</value>
</data>
</root>

View file

@ -102,6 +102,11 @@
</ItemGroup>
<ItemGroup>
<Compile Include="AppPaths.cs" />
<Compile Include="AppStrings.Designer.cs">
<AutoGen>True</AutoGen>
<DesignTime>True</DesignTime>
<DependentUpon>AppStrings.resx</DependentUpon>
</Compile>
<Compile Include="AspectRatioForm.cs">
<SubType>Form</SubType>
</Compile>
@ -154,6 +159,7 @@
<DesignTimeSharedInput>True</DesignTimeSharedInput>
<DependentUpon>Settings.settings</DependentUpon>
</Compile>
<Compile Include="Shell.cs" />
<Compile Include="SidePanelContainer.cs">
<SubType>Form</SubType>
</Compile>
@ -201,6 +207,10 @@
<Compile Include="WindowSeekers\RestoreWindowSeeker.cs" />
<Compile Include="WindowSeekers\TaskWindowSeeker.cs" />
<Compile Include="WindowsFormsExtensions.cs" />
<EmbeddedResource Include="AppStrings.resx">
<Generator>ResXFileCodeGenerator</Generator>
<LastGenOutput>AppStrings.Designer.cs</LastGenOutput>
</EmbeddedResource>
<EmbeddedResource Include="SidePanelContainer.resx">
<DependentUpon>SidePanelContainer.cs</DependentUpon>
</EmbeddedResource>

View file

@ -116,7 +116,7 @@ namespace OnTopReplica {
if (e.Success && e.Information != null) {
Log.Write("Updated check successful (latest version is {0})", e.Information.LatestVersion);
if (e.Information.IsNewVersion) {
if (e.Information.IsNewVersionAvailable) {
Update.ConfirmAndInstall();
}
}

27
OnTopReplica/Shell.cs Normal file
View file

@ -0,0 +1,27 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
namespace OnTopReplica {
static class Shell {
/// <summary>
/// Executes a filename via Windows shell.
/// </summary>
/// <param name="filename">Filename to execute.</param>
public static void Execute(string filename){
if (filename == null)
throw new ArgumentNullException();
Process.Start(new ProcessStartInfo {
FileName = filename,
UseShellExecute = true
});
}
}
}

View file

@ -23,7 +23,7 @@ namespace OnTopReplica {
/// <summary>
/// Gets the panel's parent form.
/// </summary>
protected MainForm ParentForm { get; private set; }
protected MainForm ParentMainForm { get; private set; }
/// <summary>
/// Raised when the side panel requests to be closed.
@ -41,7 +41,7 @@ namespace OnTopReplica {
/// </summary>
/// <param name="form">Parent form that is embedding the side panel.</param>
public virtual void OnFirstShown(MainForm form) {
ParentForm = form;
ParentMainForm = form;
}
/// <summary>

View file

@ -54,18 +54,18 @@ namespace OnTopReplica.SidePanels {
}
private void LinkHomepage_clicked(object sender, LinkLabelLinkClickedEventArgs e) {
Process.Start("http://ontopreplica.codeplex.com");
Shell.Execute(AppStrings.ApplicationWebsite);
}
private void LinkAuthor_clicked(object sender, LinkLabelLinkClickedEventArgs e) {
Process.Start("http://lorenz.klopfenstein.net");
Shell.Execute(AppStrings.AuthorWebsite);
}
private void LinkCredits_click(object sender, LinkLabelLinkClickedEventArgs e) {
var exeDir = Path.GetDirectoryName(Application.ExecutablePath);
var filePath = Path.Combine(exeDir, "CREDITS.txt");
Process.Start(filePath);
Shell.Execute(filePath);
}
void UpdateButton_click(object sender, System.EventArgs e) {
@ -80,7 +80,7 @@ namespace OnTopReplica.SidePanels {
//TODO
MessageBox.Show("Failed to download update info.");
}
else if (!e.Information.IsNewVersion) {
else if (!e.Information.IsNewVersionAvailable) {
Program.Update.DisplayInfo();
}
@ -89,11 +89,11 @@ namespace OnTopReplica.SidePanels {
}
private void LinkLicense_click(object sender, LinkLabelLinkClickedEventArgs e) {
Process.Start("http://opensource.org/licenses/ms-rl.html");
Shell.Execute(AppStrings.MsRlLicenseLink);
}
private void LinkContribute_clicked(object sender, LinkLabelLinkClickedEventArgs e) {
Process.Start("http://ontopreplica.codeplex.com/SourceControl/list/changesets");
Shell.Execute(AppStrings.LatestCommitsLink);
}
}
}

View file

@ -29,7 +29,7 @@ namespace OnTopReplica.SidePanels {
LoadWindowList();
labelStatus.Text = (ParentForm.MessagePumpManager.Get<GroupSwitchManager>().IsActive) ?
labelStatus.Text = (ParentMainForm.MessagePumpManager.Get<GroupSwitchManager>().IsActive) ?
Strings.GroupSwitchModeStatusEnabled :
Strings.GroupSwitchModeStatusDisabled;
}

View file

@ -217,7 +217,7 @@ namespace OnTopReplica.SidePanels {
/// <param name="regionBounds">Region bounds.</param>
protected virtual void OnRegionSet(ThumbnailRegion region) {
//Forward region to thumbnail
ParentForm.SelectedThumbnailRegion = region;
ParentMainForm.SelectedThumbnailRegion = region;
//Have region, allowed to save
buttonSave.Enabled = true;
@ -231,7 +231,7 @@ namespace OnTopReplica.SidePanels {
private void Reset_click(object sender, EventArgs e) {
Reset();
ParentForm.SelectedThumbnailRegion = null;
ParentMainForm.SelectedThumbnailRegion = null;
}
private void Delete_click(object sender, EventArgs e) {
@ -305,9 +305,9 @@ namespace OnTopReplica.SidePanels {
var region = ConstructCurrentRegion();
region.Relative = !region.Relative; //this must be reversed because the GUI has already switched state when calling ConstructCurrentRegion()
if (checkRelative.Checked)
region.SwitchToRelative(ParentForm.ThumbnailPanel.ThumbnailOriginalSize);
region.SwitchToRelative(ParentMainForm.ThumbnailPanel.ThumbnailOriginalSize);
else
region.SwitchToAbsolute(ParentForm.ThumbnailPanel.ThumbnailOriginalSize);
region.SwitchToAbsolute(ParentMainForm.ThumbnailPanel.ThumbnailOriginalSize);
//Update GUI
SetRegion(region);

View file

@ -1,9 +1,5 @@
using System;
using System.Collections.Generic;
using System.Text;
using System.Xml.Serialization;
using System.IO;
using System.Xml;
using System.Globalization;
using System.Reflection;
namespace OnTopReplica.Update {
@ -13,79 +9,62 @@ namespace OnTopReplica.Update {
/// </summary>
public class UpdateInformation {
Version _latestVersion;
/// <summary>
/// Construct update information from raw data.
/// </summary>
/// <param name="latestVersion">Latest available version.</param>
/// <param name="downloadLink">Direct link to the download page (has URL form).</param>
/// <param name="publicationDate">Publication date of latest version, in standard RTF/RSS format.</param>
public UpdateInformation(Version latestVersion, string downloadLink, string publicationDate) {
LatestVersion = latestVersion;
DownloadPage = downloadLink;
//RSS date formatted as in: <pubDate>Thu, 29 Nov 2012 12:55:04 GMT</pubDate>
DateTime parsedPublicationDate;
if (DateTime.TryParseExact(publicationDate, "R", CultureInfo.InvariantCulture, DateTimeStyles.AdjustToUniversal, out parsedPublicationDate)) {
LatestVersionRelease = parsedPublicationDate;
}
}
/// <summary>
/// Gets the latest available version of the software.
/// Gets or sets the latest available version of the software.
/// </summary>
[XmlIgnore]
public Version LatestVersion {
get {
return _latestVersion;
}
set {
_latestVersion = value;
}
}
[XmlElement("latestVersion")]
public string LatestVersionInternal {
get {
return _latestVersion.ToString();
}
set {
_latestVersion = new Version(value);
}
}
public Version LatestVersion { get; private set; }
/// <summary>
/// Returns whether this update information instance represents data about
/// a new available version.
/// </summary>
public bool IsNewVersion {
public bool IsNewVersionAvailable {
get {
var currentVersion = CurrentVersion;
return (LatestVersion > currentVersion);
return (LatestVersion > CurrentVersion);
}
}
private Version _currentVersion = null;
/// <summary>
/// Gets the currently installed version.
/// </summary>
public Version CurrentVersion {
get {
return Assembly.GetExecutingAssembly().GetName().Version;
if (_currentVersion == null) {
_currentVersion = Assembly.GetExecutingAssembly().GetName().Version;
}
return _currentVersion;
}
}
/// <summary>
/// Indicates when the latest version was released.
/// </summary>
[XmlElement("latestVersionRelease")]
public DateTime LatestVersionRelease { get; set; }
public DateTime LatestVersionRelease { get; private set; }
/// <summary>
/// Gets the URL of the page that allows the user to download the updated installer.
/// </summary>
[XmlElement("downloadPage")]
public string DownloadPage { get; set; }
/// <summary>
/// Gets the URL of the installer executable.
/// </summary>
/// <remarks>New after version 3.3.1.</remarks>
[XmlElement("downloadInstaller")]
public string DownloadInstaller { get; set; }
/// <summary>
/// Deserializes an UpdateInformation object from a stream.
/// </summary>
public static UpdateInformation Deserialize(Stream source) {
var serializer = new XmlSerializer(typeof(UpdateInformation));
var info = serializer.Deserialize(source) as UpdateInformation;
return info;
}
public string DownloadPage { get; private set; }
}

View file

@ -34,8 +34,6 @@ namespace OnTopReplica.Update {
#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>
@ -53,7 +51,7 @@ namespace OnTopReplica.Update {
}
//Build web request
_checkRequest = (HttpWebRequest)HttpWebRequest.Create(UpdateFeedUrl);
_checkRequest = (HttpWebRequest)HttpWebRequest.Create(AppStrings.UpdateFeed);
_checkRequest.AllowAutoRedirect = true;
_checkRequest.AutomaticDecompression = DecompressionMethods.Deflate | DecompressionMethods.GZip;
_checkRequest.CachePolicy = new RequestCachePolicy(RequestCacheLevel.BypassCache);
@ -90,11 +88,13 @@ namespace OnTopReplica.Update {
let title = item.Element("title").Value
let match = _versionExtractor.Match(title)
where match.Success
let versionNumber = match.Groups["version"].Value
let versionNumber = new Version(match.Groups["version"].Value)
orderby versionNumber descending
select new { versionNumber, item.Element("link").Value };
select new { Version = versionNumber, Link = item.Element("link").Value, Date = item.Element("pubDate").Value };
return new UpdateInformation();
var lastRelease = releases.FirstOrDefault();
return new UpdateInformation(lastRelease.Version, lastRelease.Link, lastRelease.Date);
}
#endregion
@ -127,15 +127,11 @@ namespace OnTopReplica.Update {
#region Updating
HttpWebRequest _downloadRequest;
TaskDialog _updateDialog;
bool _updateDownloaded = false;
/// <summary>
/// Asks confirmation for an update and installs the update.
/// Asks confirmation for an update and installs the update (if available).
/// </summary>
public void ConfirmAndInstall() {
if (LastInformation == null || !LastInformation.IsNewVersion)
if (LastInformation == null || !LastInformation.IsNewVersionAvailable)
return;
AttachedForm.SafeInvoke(new Action(ConfirmAndInstallCore));
@ -145,7 +141,7 @@ namespace OnTopReplica.Update {
/// Core delegate that asks for update confirmation and installs. Must be called from GUI thread.
/// </summary>
private void ConfirmAndInstallCore() {
_updateDialog = new TaskDialog {
var updateDialog = new TaskDialog {
Title = Strings.UpdateTitle,
Instruction = string.Format(Strings.UpdateAvailableInstruction, LastInformation.LatestVersion),
Content = Strings.UpdateAvailableContent,
@ -157,111 +153,11 @@ namespace OnTopReplica.Update {
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);
if (updateDialog.Show(AttachedForm).CommonButton == Result.OK) {
Shell.Execute(LastInformation.DownloadPage);
}
}
/// <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>
@ -285,8 +181,9 @@ namespace OnTopReplica.Update {
Footer = string.Format(Strings.UpdateInfoFooter, LastInformation.LatestVersionRelease.ToLongDateString())
};
dlg.HyperlinkClick += delegate(object sender, HyperlinkEventArgs args) {
Process.Start("http://ontopreplica.codeplex.com");
Shell.Execute(AppStrings.ApplicationWebsite);
};
dlg.Show(AttachedForm);
}

View file

@ -113,16 +113,6 @@ namespace OnTopReplica {
}
return sb.ToString();
if (string.IsNullOrWhiteSpace(_title)) {
return string.Format("#{0}", _handle.ToInt64());
}
else if (string.IsNullOrWhiteSpace(_class)) {
return string.Format("#{0} ({1})", _handle.ToInt64(), _title);
}
else {
return string.Format("#{0} ({1}, class {2})", _handle.ToInt64(), _title, _class);
}
}
public override bool Equals(object obj) {