From 987d57a4dbbd3af45753ab7aeeb3e11c3d5970ea Mon Sep 17 00:00:00 2001 From: morkt Date: Tue, 14 Feb 2017 07:22:48 +0400 Subject: [PATCH] check for updates - initial implementation. --- GUI/AboutBox.xaml | 1 - GUI/AboutBox.xaml.cs | 16 +--- GUI/App.xaml | 3 + GUI/App.xaml.cs | 38 +++++++- GUI/GARbro.GUI.csproj | 8 ++ GUI/GarUpdate.cs | 197 +++++++++++++++++++++++++++++++++++++++ GUI/MainWindow.xaml | 2 + GUI/MainWindow.xaml.cs | 2 + GUI/UpdateDialog.xaml | 61 ++++++++++++ GUI/UpdateDialog.xaml.cs | 31 ++++++ docs/version.xml | 27 ++++++ 11 files changed, 370 insertions(+), 16 deletions(-) create mode 100644 GUI/GarUpdate.cs create mode 100644 GUI/UpdateDialog.xaml create mode 100644 GUI/UpdateDialog.xaml.cs create mode 100644 docs/version.xml diff --git a/GUI/AboutBox.xaml b/GUI/AboutBox.xaml index d3231729..2ad63b3d 100644 --- a/GUI/AboutBox.xaml +++ b/GUI/AboutBox.xaml @@ -33,7 +33,6 @@ IN THE SOFTWARE. Background="{DynamicResource {x:Static SystemColors.ControlBrushKey}}" ShowInTaskbar="False" WindowStartupLocation="CenterOwner"> - https://github.com/morkt/GARbro#readme diff --git a/GUI/AboutBox.xaml.cs b/GUI/AboutBox.xaml.cs index c301dd2c..ba009b91 100644 --- a/GUI/AboutBox.xaml.cs +++ b/GUI/AboutBox.xaml.cs @@ -189,20 +189,8 @@ namespace GARbro.GUI private void Hyperlink_RequestNavigate (object sender, RequestNavigateEventArgs e) { - try - { - if (e.Uri.IsAbsoluteUri) - { - Process.Start (new ProcessStartInfo (e.Uri.AbsoluteUri)); - e.Handled = true; - } - else - throw new ApplicationException ("URI is not absolute"); - } - catch (Exception X) - { - Trace.WriteLine ("Link navigation failed: "+X.Message, e.Uri.ToString()); - } + if (App.NavigateUri (e.Uri)) + e.Handled = true; } } diff --git a/GUI/App.xaml b/GUI/App.xaml index 378f6ff0..c2c55f24 100644 --- a/GUI/App.xaml +++ b/GUI/App.xaml @@ -1,9 +1,12 @@  + https://github.com/morkt/GARbro#readme + https://morkt.github.io/version.xml diff --git a/GUI/App.xaml.cs b/GUI/App.xaml.cs index 632a3abf..04133bf8 100644 --- a/GUI/App.xaml.cs +++ b/GUI/App.xaml.cs @@ -29,6 +29,7 @@ using System.Diagnostics; using GARbro.GUI.Properties; using GameRes; using GameRes.Compression; +using System.Reflection; namespace GARbro.GUI { @@ -79,9 +80,25 @@ namespace GARbro.GUI if (string.IsNullOrEmpty (InitPath)) InitPath = Directory.GetCurrentDirectory(); - string scheme_file = Path.Combine (FormatCatalog.Instance.DataDirectory, "Formats.dat"); + string formats_dat = "Formats.dat"; + DeserializeScheme (Path.Combine (FormatCatalog.Instance.DataDirectory, formats_dat)); + DeserializeScheme (Path.Combine (GetLocalAppDataFolder(), formats_dat)); + } + + string GetLocalAppDataFolder () + { + string local_app_data = Environment.GetFolderPath (Environment.SpecialFolder.LocalApplicationData); + var attribs = Assembly.GetExecutingAssembly().GetCustomAttributes (typeof(AssemblyCompanyAttribute), false); + string company = attribs.Length > 0 ? ((AssemblyCompanyAttribute)attribs[0]).Company : ""; + return Path.Combine (local_app_data, company, Name); + } + + void DeserializeScheme (string scheme_file) + { try { + if (!File.Exists (scheme_file)) + return; using (var file = File.OpenRead (scheme_file)) FormatCatalog.Instance.DeserializeScheme (file); } @@ -116,5 +133,24 @@ namespace GARbro.GUI if (Settings.Default.winState == System.Windows.WindowState.Minimized) Settings.Default.winState = System.Windows.WindowState.Normal; } + + public static bool NavigateUri (Uri uri) + { + try + { + if (uri.IsAbsoluteUri) + { + Process.Start (new ProcessStartInfo (uri.AbsoluteUri)); + return true; + } + else + throw new ApplicationException ("URI is not absolute"); + } + catch (Exception X) + { + Trace.WriteLine ("Link navigation failed: "+X.Message, uri.ToString()); + } + return false; + } } } diff --git a/GUI/GARbro.GUI.csproj b/GUI/GARbro.GUI.csproj index aff20539..cb5b7fc2 100644 --- a/GUI/GARbro.GUI.csproj +++ b/GUI/GARbro.GUI.csproj @@ -154,6 +154,7 @@ + @@ -173,6 +174,9 @@ TextViewer.xaml + + UpdateDialog.xaml + @@ -227,6 +231,10 @@ Designer MSBuild:Compile + + Designer + MSBuild:Compile + diff --git a/GUI/GarUpdate.cs b/GUI/GarUpdate.cs new file mode 100644 index 00000000..570f982b --- /dev/null +++ b/GUI/GarUpdate.cs @@ -0,0 +1,197 @@ +//! \file GarUpdate.cs +//! \date Tue Feb 14 00:02:14 2017 +//! \brief Application update routines. +// +// Copyright (C) 2017 by morkt +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +// + +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Net; +using System.Linq; +using System.Reflection; +using System.Windows; +using System.Windows.Input; +using System.Xml; +using GameRes; + +namespace GARbro.GUI +{ + public partial class MainWindow : Window + { + private readonly BackgroundWorker m_update_checker = new BackgroundWorker(); + + private void InitUpdatesChecker () + { + m_update_checker.DoWork += StartUpdatesCheck; + m_update_checker.RunWorkerCompleted += UpdatesCheckComplete; + } + + /// + /// Handle "Check for updates" command. + /// + private void CheckUpdatesExec (object sender, ExecutedRoutedEventArgs e) + { + if (!m_update_checker.IsBusy) + m_update_checker.RunWorkerAsync(); + } + + private void StartUpdatesCheck (object sender, DoWorkEventArgs e) + { + var url = m_app.Resources["UpdateUrl"] as Uri; + if (null == url) + return; + using (var updater = new GarUpdate (this)) + { + e.Result = updater.Check (url); + } + } + + private void UpdatesCheckComplete (object sender, RunWorkerCompletedEventArgs e) + { + if (e.Error != null) + { + SetStatusText (string.Format ("{0} {1}", "Update failed.", e.Error.Message)); + return; + } + else if (e.Cancelled) + return; + var result = e.Result as GarUpdateInfo; + if (null == result) + { + SetStatusText ("No updates currently available."); + return; + } + var app_version = Assembly.GetExecutingAssembly().GetName().Version; + var db_version = FormatCatalog.Instance.CurrentSchemeVersion; + bool has_app_update = app_version < result.ReleaseVersion; + bool has_db_update = db_version < result.FormatsVersion && CheckAssemblies (result.Assemblies); + if (!has_app_update && !has_db_update) + { + SetStatusText ("GARbro version is up to date."); + return; + } + var dialog = new UpdateDialog (result, has_app_update, has_db_update); + dialog.Owner = this; + dialog.FormatsDownload.Click = FormatsDownloadExec; + dialog.ShowDialog(); + } + + private void FormatsDownloadExec (object sender, RoutedEventArgs e) + { + } + + /// + /// Check if loaded assemblies match required versions. + /// + bool CheckAssemblies (IDictionary assemblies) + { + var loaded = AppDomain.CurrentDomain.GetAssemblies().Select (a => a.GetName()) + .ToDictionary (a => a.Name, a => a.Version); + foreach (var item in assemblies) + { + if (!loaded.ContainsKey (item.Key)) + return false; + if (loaded[item.Key] < item.Value) + return false; + } + return true; + } + } + + public class GarUpdateInfo + { + public Version ReleaseVersion { get; set; } + public Uri ReleaseUrl { get; set; } + public string ReleaseNotes { get; set; } + public int FormatsVersion { get; set; } + public Uri FormatsUrl { get; set; } + public IDictionary Assemblies { get; set; } + } + + internal sealed class GarUpdate : IDisposable + { + Window m_main; + + const int RequestTimeout = 20000; // milliseconds + + public GarUpdate (Window main) + { + m_main = main; + } + + public GarUpdateInfo Check (Uri version_url) + { + var request = WebRequest.Create (version_url); + request.Timeout = RequestTimeout; + var response = (HttpWebResponse)request.GetResponse(); + using (var input = response.GetResponseStream()) + { + var xml = new XmlDocument(); + xml.Load (input); + var root = xml.DocumentElement.SelectSingleNode ("/GARbro"); + if (null == root) + return null; + var info = new GarUpdateInfo + { + ReleaseVersion = Version.Parse (GetInnerText (root.SelectSingleNode ("Release/Version"))), + ReleaseUrl = new Uri (GetInnerText (root.SelectSingleNode ("Release/Url"))), + ReleaseNotes = GetInnerText (root.SelectSingleNode ("Release/Notes")), + + FormatsVersion = Int32.Parse (GetInnerText (root.SelectSingleNode ("FormatsData/FileVersion"))), + FormatsUrl = new Uri (GetInnerText (root.SelectSingleNode ("FormatsData/Url"))), + Assemblies = ParseAssemblies (root.SelectNodes ("FormatsData/Requires/Assembly")), + }; + return info; + } + } + + static string GetInnerText (XmlNode node) + { + return node != null ? node.InnerText : ""; + } + + IDictionary ParseAssemblies (XmlNodeList nodes) + { + var dict = new Dictionary(); + foreach (XmlNode node in nodes) + { + var attr = node.Attributes; + var name = attr["Name"]; + var version = attr["Version"]; + if (name != null && version != null) + dict[name.Value] = Version.Parse (version.Value); + } + return dict; + } + + bool m_disposed = false; + public void Dispose () + { + if (!m_disposed) + { + m_disposed = true; + } + GC.SuppressFinalize (this); + } + } +} diff --git a/GUI/MainWindow.xaml b/GUI/MainWindow.xaml index d900d3a9..7d0cbe3b 100644 --- a/GUI/MainWindow.xaml +++ b/GUI/MainWindow.xaml @@ -151,6 +151,7 @@ + @@ -402,6 +403,7 @@ + diff --git a/GUI/MainWindow.xaml.cs b/GUI/MainWindow.xaml.cs index 3d45960e..436a6622 100644 --- a/GUI/MainWindow.xaml.cs +++ b/GUI/MainWindow.xaml.cs @@ -64,6 +64,7 @@ namespace GARbro.GUI if (this.Left < 0) this.Left = 0; InitDirectoryChangesWatcher(); InitPreviewPane(); + InitUpdatesChecker(); if (null == Settings.Default.appRecentFiles) Settings.Default.appRecentFiles = new StringCollection(); @@ -1478,6 +1479,7 @@ namespace GARbro.GUI public static readonly RoutedCommand SortBy = new RoutedCommand(); public static readonly RoutedCommand Exit = new RoutedCommand(); public static readonly RoutedCommand About = new RoutedCommand(); + public static readonly RoutedCommand CheckUpdates = new RoutedCommand(); public static readonly RoutedCommand GoBack = new RoutedCommand(); public static readonly RoutedCommand GoForward = new RoutedCommand(); public static readonly RoutedCommand DeleteItem = new RoutedCommand(); diff --git a/GUI/UpdateDialog.xaml b/GUI/UpdateDialog.xaml new file mode 100644 index 00000000..d9054837 --- /dev/null +++ b/GUI/UpdateDialog.xaml @@ -0,0 +1,61 @@ + + + + + +