Created
September 24, 2012 19:57
-
-
Save anaisbetts/3777989 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.Collections.Generic; | |
using System.ComponentModel; | |
using System.ComponentModel.Composition; | |
using System.Deployment.Application; | |
using System.Globalization; | |
using System.Linq; | |
using System.Reactive.Linq; | |
using System.Timers; | |
using System.Windows; | |
using GitHub.Helpers; | |
using GitHub.Models; | |
using NLog; | |
using ReactiveUI; | |
namespace GitHub.ViewModels | |
{ | |
[Export(typeof(SoftwareUpdateViewModel))] | |
public class SoftwareUpdateViewModel : AboutViewModel, IDisposable | |
{ | |
static readonly Logger log = LogManager.GetCurrentClassLogger(); | |
readonly IChangeLogFetcher changeLogFetcher; | |
readonly IAppInstanceCommunicator instanceCommunicator; | |
readonly ObservableAsPropertyHelper<List<ChangeLogEntry>> latestReleases; | |
readonly ObservableAsPropertyHelper<bool> retrievingChangeLogFailed; | |
ChangeLog changeLog; | |
bool updateAvailableAndDownloaded; | |
string updateVersion; | |
volatile bool isUpdateCheckInProgress; | |
string updateCheckStatus; | |
readonly Timer timer; | |
[ImportingConstructor] | |
public SoftwareUpdateViewModel(IServiceProvider serviceProvider) : base(serviceProvider) | |
{ | |
Ensure.ArgumentNotNull(serviceProvider, "serviceProvider"); | |
changeLogFetcher = serviceProvider.Get<IChangeLogFetcher>(); | |
instanceCommunicator = serviceProvider.Get<IAppInstanceCommunicator>(); | |
CurrentVersionString = string.Format(CultureInfo.InvariantCulture, "You are currently running: {0} ({1}).", Version, ShortSha); | |
if (ApplicationDeployment.IsNetworkDeployed) | |
ClickOnceAppIconHelper.SetAddRemoveProgramsIcon(); | |
else | |
log.Info("ApplicationDeployment.IsNetworkDeployed = false. We are not going to try and update the Add/Remove Programs Icon."); | |
this.WhenAny(x => x.UpdateVersion, x => x.Value) | |
.Where(x => !string.IsNullOrWhiteSpace(x)) | |
.Subscribe(_ => FetchLatestChangeLog()); | |
retrievingChangeLogFailed = this.WhenAny(x => x.ChangeLog, x => x.Value == null) | |
.ToProperty(this, x => x.RetrievingChangeLogFailed); | |
latestReleases = this.WhenAny(x => x.ChangeLog, x => x.Value) | |
.Where(x => x != null) | |
.Select(x => FilterChangeLog(x.Releases)) | |
.ToProperty(this, x => x.LatestReleases); | |
timer = new Timer(10*1000); // first check in 10 seconds | |
timer.Elapsed += CheckForUpdates; | |
if (!App.IsInDesignMode()) | |
timer.Start(); | |
} | |
/// <summary> | |
/// Filters a full change log to just the releases between the current version and | |
/// the version to be updated to. | |
/// | |
/// Example: | |
/// Application is running 1.0.0.105 | |
/// There have been two subsequent releases (1.1.0 and 1.2.0) | |
/// Change log should show changes for 1.1.0, 1.2.0 | |
/// Change log should not show changes for 1.0.0 | |
/// | |
/// </summary> | |
/// <param name="fullChangeLog"></param> | |
/// <returns></returns> | |
List<ChangeLogEntry> FilterChangeLog(IEnumerable<ChangeLogEntry> fullChangeLog) | |
{ | |
return fullChangeLog.Where(x => | |
{ | |
var entryVersion = new Version(x.Version); | |
var latestVersion = new Version(UpdateVersion); | |
var currentVersion = new Version(Version); | |
return entryVersion <= latestVersion && entryVersion > currentVersion; | |
}) | |
.ToList(); | |
} | |
public void CheckForUpdates(object sender, EventArgs args) | |
{ | |
if (IsUpdateCheckInProgress) return; | |
timer.Stop(); | |
if (!ApplicationDeployment.IsNetworkDeployed) | |
{ | |
UpdateCheckStatus = "This isn't a networked deployed app."; | |
UpdateVersion = Version; | |
return; | |
} | |
if (!instanceCommunicator.IsMasterInstance) | |
{ | |
UpdateCheckStatus = "There is another instance running that will check for updates."; | |
UpdateVersion = Version; | |
return; | |
} | |
timer.Interval = 2*60*60*1000; // from now on check every 2 hrs | |
// todo: checking for a clickone version causes memory to leak ea. time you check. | |
// We want to do this only once, so this should be replace with a call to central | |
// to see if a new version is available. Only at that point will we call the clickone | |
// update* methods. | |
UpdateCheckStatus = "Checking for update…"; | |
IsUpdateCheckInProgress = true; | |
log.Info("Checking for new version"); | |
var deployment = ApplicationDeployment.CurrentDeployment; | |
try | |
{ | |
deployment.CheckForUpdateCompleted -= CheckForUpdateCompleted; | |
deployment.CheckForUpdateCompleted += CheckForUpdateCompleted; | |
deployment.CheckForUpdateAsync(); | |
} | |
catch (Exception ex) | |
{ | |
log.ErrorException("Failed to check for sw update", ex); | |
deployment.CheckForUpdateCompleted -= CheckForUpdateCompleted; | |
} | |
timer.Start(); | |
} | |
public string CurrentVersionString { get; private set; } | |
/// <summary> | |
/// Forces an immediate check for updates instead of waiting for the timer. | |
/// </summary> | |
public void ForceCheckForUpdates() | |
{ | |
CheckForUpdates(null, null); | |
} | |
/// <summary> | |
/// Tells if we are already curerntly checking for or downloading an update. | |
/// </summary> | |
public bool IsUpdateCheckInProgress | |
{ | |
get { return isUpdateCheckInProgress; } | |
set { this.RaiseAndSetIfChanged(x => x.IsUpdateCheckInProgress, value); } | |
} | |
/// <summary> | |
/// User friendly status of current state of checking for an update. | |
/// </summary> | |
public string UpdateCheckStatus | |
{ | |
get { return updateCheckStatus; } | |
set { this.RaiseAndSetIfChanged(x => x.UpdateCheckStatus, value); } | |
} | |
/// <summary> | |
/// The full changelog as served by windows.github.com | |
/// </summary> | |
public ChangeLog ChangeLog | |
{ | |
get { return changeLog; } | |
set { this.RaiseAndSetIfChanged(x => x.ChangeLog, value); } | |
} | |
/// <summary> | |
/// The latest release is the release that matches the Version ClickOnce wants to download. | |
/// </summary> | |
public List<ChangeLogEntry> LatestReleases | |
{ | |
get { return latestReleases.Value; } | |
} | |
/// <summary> | |
/// Tells if fetching the changelog from the server has failed. | |
/// </summary> | |
public bool RetrievingChangeLogFailed | |
{ | |
get { return retrievingChangeLogFailed.Value; } | |
} | |
/// <summary> | |
/// Tells if an updated is available and has been downloaded. | |
/// </summary> | |
public bool UpdateAvailableAndDownloaded | |
{ | |
get { return updateAvailableAndDownloaded; } | |
set { this.RaiseAndSetIfChanged(x => x.UpdateAvailableAndDownloaded, value); } | |
} | |
/// <summary> | |
/// The latest version of the application according to ClickOnce. | |
/// </summary> | |
public string UpdateVersion | |
{ | |
get { return updateVersion; } | |
set { this.RaiseAndSetIfChanged(x => x.UpdateVersion, value); } | |
} | |
/// <summary> | |
/// Restart the application immediately. | |
/// </summary> | |
public void Restart() | |
{ | |
if (UpdateAvailableAndDownloaded) | |
{ | |
System.Windows.Forms.Application.Restart(); | |
Application.Current.Shutdown(); | |
} | |
Cancel(); | |
} | |
void CheckForUpdateCompleted(object sender, CheckForUpdateCompletedEventArgs e) | |
{ | |
if (e.Error != null) | |
{ | |
IsUpdateCheckInProgress = false; | |
log.ErrorException("Checking for application update failed.", e.Error); | |
UpdateCheckStatus = "Checking for update failed."; | |
return; | |
} | |
if (e.Cancelled || !e.UpdateAvailable) | |
{ | |
IsUpdateCheckInProgress = false; | |
UpdateCheckStatus = "GitHub for Windows is up to date."; | |
UpdateVersion = Version; | |
return; | |
} | |
log.Info("New version available: {0}", e.AvailableVersion); | |
UpdateCheckStatus = "Downloading update…"; | |
UpdateVersion = e.AvailableVersion.ToString(); | |
var deployment = ApplicationDeployment.CurrentDeployment; | |
try | |
{ | |
deployment.UpdateCompleted -= UpdateCompleted; | |
deployment.UpdateCompleted += UpdateCompleted; | |
deployment.UpdateAsync(); | |
} | |
catch (Exception ex) | |
{ | |
log.ErrorException("Failed to update", ex); | |
deployment.UpdateCompleted -= UpdateCompleted; | |
} | |
} | |
/// <summary> | |
/// Grab the latest changelog from the server (windows.github.com) | |
/// </summary> | |
void FetchLatestChangeLog() | |
{ | |
changeLogFetcher.FetchChangeLog() | |
.Catch<ChangeLog, Exception>(ex => | |
{ | |
log.WarnException("Couldn't fetch ChangeLog", ex); | |
return Observable.Return<ChangeLog>(null); | |
}) | |
.Subscribe(x => ChangeLog = x); | |
} | |
void UpdateCompleted(object sender, AsyncCompletedEventArgs e) | |
{ | |
IsUpdateCheckInProgress = false; | |
if (e.Error != null) | |
{ | |
log.ErrorException("Downloading application update failed.", e.Error); | |
UpdateCheckStatus = "Downloading update failed."; | |
return; | |
} | |
if (e.Cancelled) return; | |
UpdateCheckStatus = "New updates available."; | |
UpdateAvailableAndDownloaded = true; | |
} | |
public void Dispose() | |
{ | |
Dispose(true); | |
GC.SuppressFinalize(this); | |
} | |
protected virtual void Dispose(bool disposing) | |
{ | |
timer.Dispose(); | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment