From 56e47fb7e55c2d45673e938f829b1fa1e27d85c9 Mon Sep 17 00:00:00 2001 From: Lorenz Cuno Klopfenstein Date: Wed, 8 Jan 2014 23:15:49 +0100 Subject: [PATCH] Issue #54: new updating mechanism, now based on official CodePlex RSS release feed. Some code clean-up. --- OnTopReplica/AppStrings.resx | 135 ++++++++++++++++++ OnTopReplica/OnTopReplica.csproj | 10 ++ OnTopReplica/Program.cs | 2 +- OnTopReplica/Shell.cs | 27 ++++ OnTopReplica/SidePanel.cs | 4 +- OnTopReplica/SidePanels/AboutPanelContents.cs | 12 +- OnTopReplica/SidePanels/GroupSwitchPanel.cs | 2 +- OnTopReplica/SidePanels/RegionPanel.cs | 8 +- OnTopReplica/Update/UpdateInformation.cs | 81 ++++------- OnTopReplica/Update/UpdateManager.cs | 129 ++--------------- OnTopReplica/WindowHandle.cs | 10 -- 11 files changed, 229 insertions(+), 191 deletions(-) create mode 100644 OnTopReplica/AppStrings.resx create mode 100644 OnTopReplica/Shell.cs diff --git a/OnTopReplica/AppStrings.resx b/OnTopReplica/AppStrings.resx new file mode 100644 index 0000000..376870a --- /dev/null +++ b/OnTopReplica/AppStrings.resx @@ -0,0 +1,135 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + http://ontopreplica.codeplex.com + + + http://lorenz.klopfenstein.net + + + http://ontopreplica.codeplex.com/SourceControl/list/changesets + + + http://opensource.org/licenses/ms-rl.html + + + http://ontopreplica.codeplex.com/project/feeds/rss?ProjectRSSFeed=codeplex%3a%2f%2frelease%2fontopreplica + + \ No newline at end of file diff --git a/OnTopReplica/OnTopReplica.csproj b/OnTopReplica/OnTopReplica.csproj index 6205031..971ba9a 100644 --- a/OnTopReplica/OnTopReplica.csproj +++ b/OnTopReplica/OnTopReplica.csproj @@ -102,6 +102,11 @@ + + True + True + AppStrings.resx + Form @@ -154,6 +159,7 @@ True Settings.settings + Form @@ -201,6 +207,10 @@ + + ResXFileCodeGenerator + AppStrings.Designer.cs + SidePanelContainer.cs diff --git a/OnTopReplica/Program.cs b/OnTopReplica/Program.cs index ad90306..73b0846 100644 --- a/OnTopReplica/Program.cs +++ b/OnTopReplica/Program.cs @@ -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(); } } diff --git a/OnTopReplica/Shell.cs b/OnTopReplica/Shell.cs new file mode 100644 index 0000000..7ace07c --- /dev/null +++ b/OnTopReplica/Shell.cs @@ -0,0 +1,27 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Text; + +namespace OnTopReplica { + + static class Shell { + + /// + /// Executes a filename via Windows shell. + /// + /// Filename to execute. + public static void Execute(string filename){ + if (filename == null) + throw new ArgumentNullException(); + + Process.Start(new ProcessStartInfo { + FileName = filename, + UseShellExecute = true + }); + } + + } + +} diff --git a/OnTopReplica/SidePanel.cs b/OnTopReplica/SidePanel.cs index d66f010..296cff2 100644 --- a/OnTopReplica/SidePanel.cs +++ b/OnTopReplica/SidePanel.cs @@ -23,7 +23,7 @@ namespace OnTopReplica { /// /// Gets the panel's parent form. /// - protected MainForm ParentForm { get; private set; } + protected MainForm ParentMainForm { get; private set; } /// /// Raised when the side panel requests to be closed. @@ -41,7 +41,7 @@ namespace OnTopReplica { /// /// Parent form that is embedding the side panel. public virtual void OnFirstShown(MainForm form) { - ParentForm = form; + ParentMainForm = form; } /// diff --git a/OnTopReplica/SidePanels/AboutPanelContents.cs b/OnTopReplica/SidePanels/AboutPanelContents.cs index 84738b2..1ffccc7 100644 --- a/OnTopReplica/SidePanels/AboutPanelContents.cs +++ b/OnTopReplica/SidePanels/AboutPanelContents.cs @@ -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); } } } diff --git a/OnTopReplica/SidePanels/GroupSwitchPanel.cs b/OnTopReplica/SidePanels/GroupSwitchPanel.cs index 66183f5..a168d53 100644 --- a/OnTopReplica/SidePanels/GroupSwitchPanel.cs +++ b/OnTopReplica/SidePanels/GroupSwitchPanel.cs @@ -29,7 +29,7 @@ namespace OnTopReplica.SidePanels { LoadWindowList(); - labelStatus.Text = (ParentForm.MessagePumpManager.Get().IsActive) ? + labelStatus.Text = (ParentMainForm.MessagePumpManager.Get().IsActive) ? Strings.GroupSwitchModeStatusEnabled : Strings.GroupSwitchModeStatusDisabled; } diff --git a/OnTopReplica/SidePanels/RegionPanel.cs b/OnTopReplica/SidePanels/RegionPanel.cs index 3d4dcbd..9ef4983 100644 --- a/OnTopReplica/SidePanels/RegionPanel.cs +++ b/OnTopReplica/SidePanels/RegionPanel.cs @@ -217,7 +217,7 @@ namespace OnTopReplica.SidePanels { /// Region bounds. 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); diff --git a/OnTopReplica/Update/UpdateInformation.cs b/OnTopReplica/Update/UpdateInformation.cs index de46168..5ab3165 100644 --- a/OnTopReplica/Update/UpdateInformation.cs +++ b/OnTopReplica/Update/UpdateInformation.cs @@ -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 { /// public class UpdateInformation { - Version _latestVersion; + /// + /// Construct update information from raw data. + /// + /// Latest available version. + /// Direct link to the download page (has URL form). + /// Publication date of latest version, in standard RTF/RSS format. + public UpdateInformation(Version latestVersion, string downloadLink, string publicationDate) { + LatestVersion = latestVersion; + DownloadPage = downloadLink; + + //RSS date formatted as in: Thu, 29 Nov 2012 12:55:04 GMT + DateTime parsedPublicationDate; + if (DateTime.TryParseExact(publicationDate, "R", CultureInfo.InvariantCulture, DateTimeStyles.AdjustToUniversal, out parsedPublicationDate)) { + LatestVersionRelease = parsedPublicationDate; + } + } /// - /// Gets the latest available version of the software. + /// Gets or sets the latest available version of the software. /// - [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; } /// /// Returns whether this update information instance represents data about /// a new available version. /// - public bool IsNewVersion { + public bool IsNewVersionAvailable { get { - var currentVersion = CurrentVersion; - - return (LatestVersion > currentVersion); + return (LatestVersion > CurrentVersion); } } + private Version _currentVersion = null; + /// /// Gets the currently installed version. /// public Version CurrentVersion { get { - return Assembly.GetExecutingAssembly().GetName().Version; + if (_currentVersion == null) { + _currentVersion = Assembly.GetExecutingAssembly().GetName().Version; + } + + return _currentVersion; } } /// /// Indicates when the latest version was released. /// - [XmlElement("latestVersionRelease")] - public DateTime LatestVersionRelease { get; set; } + public DateTime LatestVersionRelease { get; private set; } /// /// Gets the URL of the page that allows the user to download the updated installer. /// - [XmlElement("downloadPage")] - public string DownloadPage { get; set; } - - /// - /// Gets the URL of the installer executable. - /// - /// New after version 3.3.1. - [XmlElement("downloadInstaller")] - public string DownloadInstaller { get; set; } - - /// - /// Deserializes an UpdateInformation object from a stream. - /// - 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; } } diff --git a/OnTopReplica/Update/UpdateManager.cs b/OnTopReplica/Update/UpdateManager.cs index a2fcd9b..de14199 100644 --- a/OnTopReplica/Update/UpdateManager.cs +++ b/OnTopReplica/Update/UpdateManager.cs @@ -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"; - /// /// Gets the latest update information available. /// @@ -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; - /// - /// Asks confirmation for an update and installs the update. + /// Asks confirmation for an update and installs the update (if available). /// 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. /// 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); - } - - /// - /// Gets the target filename used when downloading the update from the Internet. - /// - 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); } } - /// - /// Handles background downloading. - /// - 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; - } - /// /// Displays some information about the current installation and available updates. /// @@ -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); } diff --git a/OnTopReplica/WindowHandle.cs b/OnTopReplica/WindowHandle.cs index d2b2cc6..34720b3 100644 --- a/OnTopReplica/WindowHandle.cs +++ b/OnTopReplica/WindowHandle.cs @@ -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) {