Katteker/Katteker/UpdateManager.cs

224 lines
8.5 KiB
C#

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Net;
using System.Reflection;
using System.Threading;
using System.Threading.Tasks;
namespace Katteker
{
/// <summary>
/// Update manager, which handles the updates.
/// </summary>
public class UpdateManager
{
private static readonly string BaseDir = AppDomain.CurrentDomain.BaseDirectory;
private static KattekerConfig _config;
private readonly string _applicationName;
private readonly string _packageDir;
private readonly string _rootAppDirectory;
private Releases _releases;
private UpdateManager(string urlOrPath, string applicationName, string rootDirectory)
{
UrlOrPath = urlOrPath;
_applicationName = applicationName;
_rootAppDirectory = rootDirectory;
var packageDir = Path.Combine(rootDirectory, Constants.PackageFolder);
if (!Directory.Exists(packageDir)) Directory.CreateDirectory(packageDir);
_packageDir = packageDir;
}
/// <summary>
/// Filename of the changelog.
/// </summary>
public string ChangelogFilename => _config.Changelog;
/// <summary>
/// Url or path where the update files are located.
/// </summary>
public string UrlOrPath { get; }
/// <summary>
/// Create the update manager.
/// </summary>
/// <param name="urlOrPath">path to the publishing directory</param>
/// <param name="applicationName">name of the application to update.</param>
/// <param name="rootDirectory">root directory.</param>
/// <returns>the update manager.</returns>
public static UpdateManager Create(string urlOrPath = null, string applicationName = null,
string rootDirectory = null)
{
_config = ReadConfigFile();
urlOrPath = urlOrPath ?? _config.Publish;
var appName = applicationName ?? Utility.GetApplicationName();
var channel = string.IsNullOrWhiteSpace(_config.Channel) ? string.Empty : $"_{_config.Channel}";
var rootAppDirectory = Path.Combine(rootDirectory ?? Utility.GetLocalAppDataDirectory(), appName + channel);
return new UpdateManager(urlOrPath, appName, rootAppDirectory);
}
/// <summary>
/// Try to create the update manager.
/// </summary>
/// <param name="manager">update manager</param>
/// <param name="urlOrPath">path to the publishing directory</param>
/// <param name="applicationName">name of the application to update.</param>
/// <param name="rootDirectory">root directory.</param>
/// <returns>true if the creation success, false otherwise.</returns>
public static bool TryCreate(out UpdateManager manager, string urlOrPath = null, string applicationName = null,
string rootDirectory = null)
{
try
{
manager = Create(urlOrPath, applicationName, rootDirectory);
return true;
}
catch (Exception)
{
manager = default(UpdateManager);
return false;
}
}
/// <summary>
/// Check for updates
/// </summary>
/// <returns></returns>
public async Task<IEnumerable<ReleaseEntry>> CheckForUpdateAsync()
{
try
{
return await CheckForUpdateImplAsync().ConfigureAwait(false);
}
catch (Exception e)
{
throw new KattekerUpdateException("Error at checking for available update.", e);
}
}
private async Task<IEnumerable<ReleaseEntry>> CheckForUpdateImplAsync()
{
_releases = Utility.IsWebUrl(UrlOrPath)
? await DownloadIndexAsync(UrlOrPath).ConfigureAwait(false)
: GetFromFilesystem(UrlOrPath);
var updateInfo = new UpdateInfo(_applicationName, _releases);
return updateInfo.ReleasesToApply;
}
/// <summary>
/// Restart the application.
/// </summary>
/// <param name="exeToStart"></param>
/// <param name="arguments"></param>
public void RestartApp(string exeToStart = null, string arguments = null)
{
exeToStart = exeToStart ?? Path.GetFileName(Assembly.GetEntryAssembly().Location);
var program = Path.Combine(_rootAppDirectory, exeToStart);
if (!File.Exists(program)) throw new FileNotFoundException(program);
Process.Start(program, arguments);
Thread.Sleep(500);
Environment.Exit(0);
}
/// <summary>
/// Update application.
/// </summary>
/// <param name="progress">The updating process.</param>
/// <returns></returns>
public async Task<bool> UpdateAppAsync(IProgress<int> progress = null)
{
try
{
progress?.Report(0);
var updateEntries = (await CheckForUpdateAsync().ConfigureAwait(false)).ToArray();
var update = updateEntries.LastOrDefault();
if (update == null) return false;
progress?.Report(30);
return await UpdateAppImplAsync(update, progress).ConfigureAwait(false);
}
catch (Exception e)
{
throw new KattekerUpdateException("Error at updating application.", e);
}
}
private static async Task<Releases> DownloadIndexAsync(string urlOrPath)
{
var url = urlOrPath.TrimEnd('/');
url += "/" + Constants.Release;
var content = await new WebClient().DownloadStringTaskAsync(url).ConfigureAwait(false);
var lines = content.Split(new[] {'\r', '\n'}, StringSplitOptions.RemoveEmptyEntries);
return new Releases(lines);
}
private static Releases GetFromFilesystem(string urlOrPath) => new Releases(urlOrPath);
private static KattekerConfig ReadConfigFile()
{
var configPath = Path.Combine(BaseDir, Constants.KattekerConfig);
if (!File.Exists(configPath)) throw new FileNotFoundException("Configuration file not found.", configPath);
return KattekerConfig.ReadFromFile(configPath);
}
private static bool VerifyFileChecksum(string targetFile, string lastEntrySha1)
{
var hash = Utility.ComputeFileHash(targetFile);
return lastEntrySha1.Equals(hash);
}
private Task KillAppStubAsync()
{
foreach (var process in Process.GetProcessesByName(_applicationName))
{
var path = Path.GetDirectoryName(process.MainModule.FileName);
if (!_rootAppDirectory.Equals(path)) continue;
process.Kill();
return Task.Delay(100);
}
return Task.FromResult(true);
}
private async Task PutFileInPackageFolderAsync(string filename)
{
var targetFile = Path.Combine(_packageDir, filename);
File.Delete(targetFile);
if (Utility.IsWebUrl(UrlOrPath))
{
var url = UrlOrPath.TrimEnd('/');
url += "/" + filename;
await new WebClient().DownloadFileTaskAsync(new Uri(url), targetFile).ConfigureAwait(false);
}
else
{
File.Copy(Path.Combine(UrlOrPath, filename), targetFile);
}
}
private async Task<bool> UpdateAppImplAsync(ReleaseEntry lastEntry, IProgress<int> progress)
{
if (lastEntry == null) throw new ArgumentNullException(nameof(lastEntry));
var targetFile = Path.Combine(_packageDir, lastEntry.Filename);
//download file.
await PutFileInPackageFolderAsync(lastEntry.Filename).ConfigureAwait(false);
progress?.Report(60);
if (!VerifyFileChecksum(targetFile, lastEntry.SHA1)) throw new FileLoadException("Checksum missmatch.");
progress?.Report(70);
await KillAppStubAsync().ConfigureAwait(false);
progress?.Report(80);
using (var updater = Process.Start(targetFile, "/S"))
{
updater?.WaitForExit();
}
progress?.Report(100);
return true;
}
}
}