DotN64/DotN64.Desktop/Updater.cs

165 lines
6.0 KiB
C#

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.IO.Compression;
using System.Net;
using System.Reflection;
namespace DotN64.Desktop
{
public static class Updater
{
#region Fields
private const string StagingDirectory = ".update", ProjectName = nameof(DotN64), NewFileExtension = ".new";
#endregion
#region Properties
private static string InstallDirectory => AppDomain.CurrentDomain.BaseDirectory;
private static string PlatformSuffix => string.Join(".", Environment.OSVersion.Platform, Environment.Is64BitOperatingSystem ? "64" : "32");
/// <summary>
/// Gets the available release streams.
/// </summary>
public static IEnumerable<string> Streams
{
get
{
using (var client = new WebClient())
using (var reader = new StreamReader(client.OpenRead(new Uri(Program.Website, $"{ProjectName}/Download/streams"))))
{
while (!reader.EndOfStream)
{
yield return reader.ReadLine();
}
}
}
}
#endregion
#region Methods
/// <summary>
/// Checks for a new update.
/// </summary>
public static Version Check(string releaseStream = null)
{
if (releaseStream == null)
releaseStream = Program.ReleaseStream;
using (var client = new WebClient())
{
var remoteVersion = new Version(client.DownloadString(new Uri(Program.Website, $"{ProjectName}/Download/{releaseStream}/version")));
var currentVersion = Assembly.GetEntryAssembly().GetName().Version;
return remoteVersion > currentVersion ? remoteVersion : null;
}
}
/// <summary>
/// Downloads the latest update.
/// </summary>
public static void Download(string releaseStream = null)
{
if (releaseStream == null)
releaseStream = Program.ReleaseStream;
var updateDirectory = new DirectoryInfo(Path.Combine(InstallDirectory, StagingDirectory));
if (updateDirectory.Exists)
updateDirectory.Delete(true);
updateDirectory.Create();
updateDirectory.Attributes = FileAttributes.Directory | FileAttributes.Hidden;
using (var client = new WebClient())
{
using (var archive = new ZipArchive(client.OpenRead(new Uri(Program.Website, $"{ProjectName}/Download/{releaseStream}/{ProjectName}.zip"))))
{
archive.ExtractToDirectory(updateDirectory.FullName);
}
try
{
using (var archive = new ZipArchive(client.OpenRead(new Uri(Program.Website, $"{ProjectName}/Download/{releaseStream}/{ProjectName}.{PlatformSuffix}.zip"))))
{
archive.ExtractToDirectory(updateDirectory.FullName);
}
}
catch (WebException e) when ((e.Response as HttpWebResponse).StatusCode == HttpStatusCode.NotFound) { }
}
}
/// <summary>
/// Applies the update.
/// </summary>
public static void Apply()
{
var updateDirectory = new DirectoryInfo(Path.Combine(InstallDirectory, StagingDirectory));
var executableName = Path.GetFileName(Assembly.GetEntryAssembly().Location);
var platform = Environment.OSVersion.Platform;
foreach (var file in updateDirectory.GetFiles())
{
var destination = Path.Combine(InstallDirectory, file.Name);
if (file.Name == executableName)
{
switch (platform)
{
case PlatformID.Unix:
case PlatformID.MacOSX:
Process.Start(new ProcessStartInfo("chmod", $"+x {file.FullName}")).WaitForExit(); // ZipArchive does not preserve permission bits, so we fix it.
break;
case PlatformID.Win32NT:
file.MoveTo(destination + NewFileExtension);
continue;
}
}
File.Delete(destination);
file.MoveTo(destination);
}
updateDirectory.Delete(true);
if (platform == PlatformID.Win32NT)
ApplyWindowsUpdate(executableName);
}
private static void ApplyWindowsUpdate(string executableName)
{
var newExecutableName = executableName + NewFileExtension;
var scriptFile = new FileInfo(Path.Combine(InstallDirectory, "CompleteUpdate.cmd"));
var processID = Process.GetCurrentProcess().Id;
using (var writer = scriptFile.CreateText())
{
writer.WriteLine(":CHECK");
writer.WriteLine("timeout /t 1");
writer.WriteLine($"tasklist /fi \"pid eq {processID}\" | find \"{processID}\"");
writer.WriteLine("if errorlevel 1 goto UPDATE");
writer.WriteLine("goto CHECK");
writer.WriteLine(":UPDATE");
writer.WriteLine($"move /Y \"{newExecutableName}\" \"{executableName}\"");
//writer.WriteLine($"start \"\" \"{executableName}\""); // It's pointless to automatically restart the program as it'll crash with no arguments in its current state.
writer.WriteLine($"del /A:H %0");
}
scriptFile.Attributes |= FileAttributes.Hidden;
Process.Start(new ProcessStartInfo(scriptFile.FullName)
{
UseShellExecute = false,
CreateNoWindow = true,
WorkingDirectory = InstallDirectory
});
Environment.Exit(0);
}
#endregion
}
}