Katteker/Katteker/UpdateManager.cs
2018-04-04 20:28:52 +02:00

205 lines
7.7 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 readonly string _urlOrPath;
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 => _urlOrPath;
/// <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 rootAppDirectory = Path.Combine(rootDirectory ?? Utility.GetLocalAppDataDirectory(), appName);
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;
}
}
public async Task<IEnumerable<ReleaseEntry>> CheckForUpdateAsync()
{
_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))
{
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)
{
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);
}
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))
{
process.Kill();
return Task.Delay(500);
}
}
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)
{
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();
progress?.Report(70);
await KillAppStubAsync().ConfigureAwait(false);
progress?.Report(80);
using (var updater = new Process())
{
updater.StartInfo = new ProcessStartInfo(targetFile, "/S");
updater.Start();
updater.WaitForExit();
}
progress?.Report(100);
return true;
}
}
}