// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. namespace WixToolset.WixBA { using System; using System.ComponentModel; using System.Windows.Input; using WixToolset.BootstrapperApplicationApi; /// <summary> /// The states of the update view model. /// </summary> public enum UpdateState { Unknown, Initializing, Checking, Current, Available, Failed, } /// <summary> /// The model of the update view. /// </summary> public class UpdateViewModel : PropertyNotifyBase { private RootViewModel root; private UpdateState state; private ICommand updateCommand; private string updateVersion; private string updateChanges; public UpdateViewModel(RootViewModel root) { this.root = root; WixBA.Model.Bootstrapper.DetectUpdateBegin += this.DetectUpdateBegin; WixBA.Model.Bootstrapper.DetectUpdate += this.DetectUpdate; WixBA.Model.Bootstrapper.DetectUpdateComplete += this.DetectUpdateComplete; WixBA.Model.Bootstrapper.DetectComplete += this.DetectComplete; this.root.PropertyChanged += new PropertyChangedEventHandler(this.RootPropertyChanged); this.State = UpdateState.Initializing; } void RootPropertyChanged(object sender, PropertyChangedEventArgs e) { if ("InstallState" == e.PropertyName) { base.OnPropertyChanged("CanUpdate"); } } public bool CheckingEnabled { get { return this.State == UpdateState.Initializing || this.State == UpdateState.Checking; } } public bool CanUpdate { get { switch(this.root.InstallState) { case InstallationState.Waiting: case InstallationState.Applied: case InstallationState.Failed: return this.IsUpdateAvailable; default: return false; } } } public ICommand UpdateCommand { get { if (this.updateCommand == null) { this.updateCommand = new RelayCommand(param => WixBA.Plan(LaunchAction.UpdateReplace), param => this.CanUpdate); } return this.updateCommand; } } public bool IsUpdateAvailable { get { return this.State == UpdateState.Available; } } /// <summary> /// Gets and sets the state of the update view model. /// </summary> public UpdateState State { get { return this.state; } set { if (this.state != value) { this.state = value; base.OnPropertyChanged("State"); base.OnPropertyChanged("CanUpdate"); base.OnPropertyChanged("CheckingEnabled"); base.OnPropertyChanged("IsUpdateAvailable"); } } } /// <summary> /// The version of an available update. /// </summary> public string UpdateVersion { get { return updateVersion; } set { if (this.updateVersion != value) { this.updateVersion = value; base.OnPropertyChanged("UpdateVersion"); } } } /// <summary> /// The changes in the available update. /// </summary> public string UpdateChanges { get { return updateChanges; } set { if (this.updateChanges != value) { this.updateChanges = value; base.OnPropertyChanged("UpdateChanges"); } } } private void DetectUpdateBegin(object sender, DetectUpdateBeginEventArgs e) { // Don't check for updates if: // the first check failed (no retry) // if we are being run as an uninstall // if we are not under a full UI. if ((UpdateState.Failed != this.State) && (LaunchAction.Uninstall != WixBA.Model.Command.Action) && (Display.Full == WixBA.Model.Command.Display)) { this.State = UpdateState.Checking; e.Skip = false; } } private void DetectUpdate(object sender, DetectUpdateEventArgs e) { // The list of updates is sorted in descending version, so the first callback should be the largest update available. // This update should be either larger than ours (so we are out of date), the same as ours (so we are current) // or smaller than ours (we have a private build). If we really wanted to, we could leave the e.StopProcessingUpdates alone and // enumerate all of the updates. WixBA.Model.Engine.Log(LogLevel.Verbose, String.Format("Potential update v{0} from '{1}'; current version: v{2}", e.Version, e.UpdateLocation, WixBA.Model.Version)); if (WixBA.Model.Engine.CompareVersions(e.Version, WixBA.Model.Version) > 0) { var updatePackageId = Guid.NewGuid().ToString("N"); WixBA.Model.Engine.SetUpdate(null, e.UpdateLocation, e.Size, UpdateHashType.None, null, updatePackageId); if (!WixBA.Model.BAManifest.Bundle.Packages.ContainsKey(updatePackageId)) { WixBA.Model.BAManifest.Bundle.AddUpdateBundleAsPackage(updatePackageId); } this.UpdateVersion = String.Concat("v", e.Version.ToString()); string changesFormat = @"<body style='overflow: auto;'>{0}</body>"; this.UpdateChanges = String.Format(changesFormat, e.Content); this.State = UpdateState.Available; } else { this.State = UpdateState.Current; } e.StopProcessingUpdates = true; } private void DetectUpdateComplete(object sender, DetectUpdateCompleteEventArgs e) { // Failed to process an update, allow the existing bundle to still install. if ((UpdateState.Failed != this.State) && !Hresult.Succeeded(e.Status)) { this.State = UpdateState.Failed; WixBA.Model.Engine.Log(LogLevel.Verbose, String.Format("Failed to locate an update, status of 0x{0:X8}, updates disabled.", e.Status)); e.IgnoreError = true; } // If we are uninstalling, we don't want to check or show an update // If we are checking, then the feed didn't find any valid enclosures // If we are initializing, we're either uninstalling or not a full UI else if ((LaunchAction.Uninstall == WixBA.Model.Command.Action) || (UpdateState.Initializing == this.State) || (UpdateState.Checking == this.State)) { this.State = UpdateState.Unknown; } } private void DetectComplete(object sender, DetectCompleteEventArgs e) { if (this.State == UpdateState.Initializing || this.State == UpdateState.Checking) { this.State = UpdateState.Unknown; } } } }