using System; using System.ComponentModel; using System.Linq; using System.Net; using System.Reflection; using System.Threading; using System.Timers; using System.Xml; using System.Xml.Linq; namespace TestIntelReporter { /// /// Queries the version service to make sure we are running the most /// recent version. /// /// /// The URL must return an XML document with the following format: /// /// /// /// /// /// /// [DefaultEvent("UpdateAvailable"), DefaultProperty("CheckUri")] public class UpdateCheck : Component { private const double intervalSecond = 1000.0; private const double intervalMinute = intervalSecond * 60; private const double intervalHour = intervalMinute * 60; private const double intervalDay = intervalHour * 24; // Default value for CheckInterval: One day private const double defaultUpdateInterval = intervalDay; // Timer object to periodically ping the server private readonly System.Timers.Timer timer; /// /// Initializes a new instance of the /// class. /// public UpdateCheck() : this(null) { } /// /// Initializes a new instance of the /// class with the specified container. /// public UpdateCheck(IContainer container) { this.timer = new System.Timers.Timer(defaultUpdateInterval); this.timer.Elapsed += this.timer_Elapsed; this.timer.AutoReset = true; // Add to the parent container if (container != null) { container.Add(this); } } /// /// Raised after pinging the server an a new version is available. /// public event EventHandler UpdateAvailable; /// /// Gets the name of the currently executing assembly. /// /// /// uses as the /// assembly to perform a version check on. /// public string AssemblyName { get { var assembly = Assembly.GetEntryAssembly(); return (assembly != null) ? assembly.GetName().Name : String.Empty; } } /// /// Gets the file version of the currently executing /// assembly. /// /// /// uses as /// the "current version" when looking for updates. /// public Version AssemblyVersion { get { var assembly = Assembly.GetEntryAssembly(); if (assembly != null) { var attribute = assembly .GetCustomAttributes(typeof(AssemblyFileVersionAttribute), true) .Cast() .SingleOrDefault(); return (attribute != null) ? Version.Parse(attribute.Version) : null; } else { return null; } } } /// /// The URI to use when downloading the list of assembly versions. /// [DefaultValue(null)] public string CheckUri { get; set; } /// /// The URI to use when checking for new versions. /// [DefaultValue(defaultUpdateInterval)] public double CheckInterval { get { return this.timer.Interval; } set { this.timer.Interval = value; } } /// /// Gets or sets the instance of /// to use when raising events. /// [DefaultValue(null)] public ISynchronizeInvoke SynchronizationObject { get; set; } /// /// Starts checking for updates. Begins a background check /// immediately. /// public void Start() { if (!this.timer.Enabled) { ThreadPool.QueueUserWorkItem((state) => this.timer_Elapsed(null, null)); this.timer.Start(); } } /// /// Terminates checking for updates. /// public void Stop() { this.timer.Stop(); } /// protected override void Dispose(bool disposing) { if (disposing) { this.timer.Dispose(); } base.Dispose(disposing); } /// /// Called periodically by to download /// the version list and check for updates. /// private void timer_Elapsed(object sender, ElapsedEventArgs e) { var requestUri = this.CheckUri; if (String.IsNullOrEmpty(requestUri)) { return; } // Grab the assembly information var assemblyName = this.AssemblyName; var assemblyVersion = this.AssemblyVersion; if (assemblyName == null || assemblyVersion == null) { return; } try { // Figure out what the server has available var doc = XDocument.Load(requestUri); var newAssembly = doc.Root .Elements("assembly") .Select(x => new { Name = x.Attribute("name"), Version = x.Attribute("version"), UpdateUri = x.Attribute("update-uri") }) .Where(x => (x.Name != null) && (x.Name.Value == assemblyName) && (x.Version != null)) .Select(x => new { Version = new Version(x.Version.Value), UpdateUri = (x.UpdateUri != null) ? x.UpdateUri.Value : null }) .OrderBy(x => x.Version) .Last(); // Check if we have an update if (newAssembly.Version > assemblyVersion) { var handler = this.UpdateAvailable; if (handler != null) { var sync = this.SynchronizationObject; var args = new UpdateEventArgs( assemblyVersion, newAssembly.Version, newAssembly.UpdateUri); if ((sync != null) && sync.InvokeRequired) { sync.BeginInvoke(new Action(() => handler(this, args)), null); } else { ThreadPool.QueueUserWorkItem((state) => handler(this, args)); } } } } catch (WebException) { // Error downloading the document } catch (FormatException) { // The user's URI is invalid } catch (XmlException) { // Error parsing the XML } catch (InvalidOperationException) { // Our assembly isn't listed } } } }