using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Net;
using System.Net.Http;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using System.Windows;
using Newtonsoft.Json.Linq;
using NLog;
using Shadowsocks.Localization;
using Shadowsocks.Model;
using Shadowsocks.Util;
using Shadowsocks.Views;
namespace Shadowsocks.Controller
{
public class UpdateChecker
{
private readonly Logger logger;
private readonly HttpClient httpClient;
// https://developer.github.com/v3/repos/releases/
private const string UpdateURL = "https://api.github.com/repos/shadowsocks/shadowsocks-windows/releases";
private Configuration _config;
private Window versionUpdatePromptWindow;
private JToken _releaseObject;
public string NewReleaseVersion { get; private set; }
public string NewReleaseZipFilename { get; private set; }
public event EventHandler CheckUpdateCompleted;
public const string Version = "4.3.2.0";
private readonly Version _version;
public UpdateChecker()
{
logger = LogManager.GetCurrentClassLogger();
httpClient = Program.MainController.GetHttpClient();
_version = new Version(Version);
_config = Program.MainController.GetCurrentConfiguration();
}
///
/// Checks for updates and asks the user if updates are found.
///
/// A delay in milliseconds before checking.
///
public async Task CheckForVersionUpdate(int millisecondsDelay = 0)
{
// delay
logger.Info($"Waiting for {millisecondsDelay}ms before checking for version update.");
await Task.Delay(millisecondsDelay);
// update _config so we would know if the user checked or unchecked pre-release checks
_config = Program.MainController.GetCurrentConfiguration();
// start
logger.Info($"Checking for version update.");
try
{
// list releases via API
var releasesListJsonString = await httpClient.GetStringAsync(UpdateURL);
// parse
var releasesJArray = JArray.Parse(releasesListJsonString);
foreach (var releaseObject in releasesJArray)
{
var releaseTagName = (string)releaseObject["tag_name"];
var releaseVersion = new Version(releaseTagName);
if (releaseTagName == _config.skippedUpdateVersion) // finished checking
break;
if (releaseVersion.CompareTo(_version) > 0 &&
(!(bool)releaseObject["prerelease"] || _config.checkPreRelease && (bool)releaseObject["prerelease"])) // selected
{
logger.Info($"Found new version {releaseTagName}.");
_releaseObject = releaseObject;
NewReleaseVersion = releaseTagName;
AskToUpdate(releaseObject);
return;
}
}
logger.Info($"No new versions found.");
CheckUpdateCompleted?.Invoke(this, new EventArgs());
}
catch (Exception e)
{
logger.LogUsefulException(e);
}
}
///
/// Opens a window to show the update's information.
///
/// The update release object.
private void AskToUpdate(JToken releaseObject)
{
if (versionUpdatePromptWindow == null)
{
versionUpdatePromptWindow = new Window()
{
Title = LocalizationProvider.GetLocalizedValue("VersionUpdate"),
Height = 480,
Width = 640,
MinHeight = 480,
MinWidth = 640,
Content = new VersionUpdatePromptView(releaseObject)
};
versionUpdatePromptWindow.Closed += VersionUpdatePromptWindow_Closed;
versionUpdatePromptWindow.Show();
}
versionUpdatePromptWindow.Activate();
}
private void VersionUpdatePromptWindow_Closed(object sender, EventArgs e)
{
versionUpdatePromptWindow = null;
}
///
/// Downloads the selected update and notifies the user.
///
///
public async Task DoUpdate()
{
try
{
var assets = (JArray)_releaseObject["assets"];
// download all assets
foreach (JObject asset in assets)
{
var filename = (string)asset["name"];
var browser_download_url = (string)asset["browser_download_url"];
var response = await httpClient.GetAsync(browser_download_url);
using (var downloadedFileStream = File.Create(Utils.GetTempPath(filename)))
await response.Content.CopyToAsync(downloadedFileStream);
logger.Info($"Downloaded {filename}.");
// store .zip filename
if (filename.EndsWith(".zip"))
NewReleaseZipFilename = filename;
}
logger.Info("Finished downloading.");
// notify user
CloseVersionUpdatePromptWindow();
Process.Start("explorer.exe", $"/select, \"{Utils.GetTempPath(NewReleaseZipFilename)}\"");
}
catch (Exception e)
{
logger.LogUsefulException(e);
}
}
///
/// Saves the skipped update version.
///
public void SkipUpdate()
{
var version = (string)_releaseObject["tag_name"] ?? "";
_config.skippedUpdateVersion = version;
Program.MainController.SaveSkippedUpdateVerion(version);
logger.Info($"The update {version} has been skipped and will be ignored next time.");
CloseVersionUpdatePromptWindow();
}
///
/// Closes the update prompt window.
///
public void CloseVersionUpdatePromptWindow()
{
if (versionUpdatePromptWindow != null)
{
versionUpdatePromptWindow.Close();
versionUpdatePromptWindow = null;
}
}
}
}