Files
duplicati/Duplicati/Library/AutoUpdater/UpdaterManager.cs
T

987 lines
42 KiB
C#

// Copyright (C) 2014, Kenneth Skovhede
// http://www.hexad.dk, opensource@hexad.dk
//
// This library is free software; you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as
// published by the Free Software Foundation; either version 2.1 of the
// License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
using System;
using System.Linq;
using System.Collections.Generic;
namespace Duplicati.Library.AutoUpdater
{
public enum AutoUpdateStrategy
{
CheckBefore,
CheckDuring,
CheckAfter,
InstallBefore,
InstallDuring,
InstallAfter,
Never
}
public static class UpdaterManager
{
private static readonly System.Security.Cryptography.RSACryptoServiceProvider SIGN_KEY = AutoUpdateSettings.SignKey;
private static readonly string[] MANIFEST_URLS = AutoUpdateSettings.URLs;
private static readonly string APPNAME = AutoUpdateSettings.AppName;
private static readonly string INSTALLDIR;
private static readonly string INSTALLED_BASE_DIR = string.IsNullOrWhiteSpace(System.Environment.GetEnvironmentVariable(string.Format(INSTALLDIR_ENVNAME_TEMPLATE, APPNAME))) ?
System.IO.Path.GetDirectoryName(System.Reflection.Assembly.GetEntryAssembly().Location)
: System.Environment.GetEnvironmentVariable(string.Format(INSTALLDIR_ENVNAME_TEMPLATE, APPNAME));
private static readonly bool DISABLE_UPDATE_DOMAIN = !string.IsNullOrWhiteSpace(System.Environment.GetEnvironmentVariable(string.Format(SKIPUPDATE_ENVNAME_TEMPLATE, APPNAME)));
public static bool RequiresRespawn { get; set; }
private static KeyValuePair<string, UpdateInfo>? m_hasUpdateInstalled;
private static readonly UpdateInfo SelfVersion;
public static event Action<Exception> OnError;
private const string DATETIME_FORMAT = "yyyymmddhhMMss";
private const string INSTALLDIR_ENVNAME_TEMPLATE = "AUTOUPDATER_{0}_INSTALL_ROOT";
private const string SKIPUPDATE_ENVNAME_TEMPLATE = "AUTOUPDATER_{0}_SKIP_UPDATE";
private const string RUN_UPDATED_FOLDER_PATH = "AUTOUPDATER_LOAD_UPDATE";
private const string SLEEP_ENVNAME_TEMPLATE = "AUTOUPDATER_{0}_SLEEP";
private const string UPDATE_STRATEGY_ENVNAME_TEMPLATE = "AUTOUPDATER_{0}_POLICY";
private const string UPDATE_MANIFEST_FILENAME = "autoupdate.manifest";
private const string README_FILE = "README.txt";
private const string INSTALL_FILE = "installation.txt";
private const string CURRENT_FILE = "current";
/// <summary>
/// Gets the original directory that this application was installed into
/// </summary>
/// <value>The original directory that this application was installed into</value>
public static string InstalledBaseDir { get { return INSTALLED_BASE_DIR; } }
static UpdaterManager()
{
string installdir = null;
var attempts = new string[] {
System.IO.Path.Combine(InstalledBaseDir, "updates"),
System.IO.Path.Combine(System.Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles), APPNAME, "updates"),
System.IO.Path.Combine(System.Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), APPNAME, "updates"),
System.IO.Path.Combine(System.Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), APPNAME, "updates"),
};
foreach(var p in attempts)
if (TestDirectoryIsWriteable(p))
{
installdir = p;
break;
}
INSTALLDIR = installdir;
if (INSTALLDIR != null)
{
if (!System.IO.File.Exists(System.IO.Path.Combine(INSTALLDIR, README_FILE)))
System.IO.File.WriteAllText(System.IO.Path.Combine(INSTALLDIR, README_FILE), AutoUpdateSettings.UpdateFolderReadme);
if (!System.IO.File.Exists(System.IO.Path.Combine(INSTALLDIR, INSTALL_FILE)))
System.IO.File.WriteAllText(System.IO.Path.Combine(INSTALLDIR, INSTALL_FILE), AutoUpdateSettings.UpdateInstallFileText);
}
UpdateInfo selfVersion = null;
try
{
selfVersion = ReadInstalledManifest(System.IO.Path.GetDirectoryName(System.Reflection.Assembly.GetEntryAssembly().Location));
}
catch
{
}
if (selfVersion == null)
selfVersion = new UpdateInfo() {
Displayname = "Current",
Version = System.Reflection.Assembly.GetExecutingAssembly().GetName().Version.ToString(),
ReleaseTime = new DateTime(0),
ReleaseType =
#if DEBUG
"Debug"
#else
"Nightly"
#endif
};
SelfVersion = selfVersion;
}
private static Version TryParseVersion(string str)
{
Version v;
if (Version.TryParse(str, out v))
return v;
else
return new Version(0, 0);
}
public static bool HasUpdateInstalled
{
get
{
if (!m_hasUpdateInstalled.HasValue)
{
var selfversion = TryParseVersion(SelfVersion.Version);
m_hasUpdateInstalled =
(from n in FindInstalledVersions()
let nversion = TryParseVersion(n.Value.Version)
let newerVersion = selfversion < nversion
where newerVersion && VerifyUnpackedFolder(n.Key, n.Value)
orderby nversion descending
select n)
.FirstOrDefault();
}
return m_hasUpdateInstalled.Value.Value != null;
}
}
private static bool TestDirectoryIsWriteable(string path)
{
var p2 = System.IO.Path.Combine(path, "test-" + DateTime.UtcNow.ToString(DATETIME_FORMAT));
var probe = System.IO.Directory.Exists(path) ? p2 : path;
if (!System.IO.Directory.Exists(probe))
{
try
{
System.IO.Directory.CreateDirectory(probe);
if (probe != path)
System.IO.Directory.Delete(probe);
return true;
}
catch
{
}
}
return false;
}
private static string InstallID
{
get
{
try { return System.IO.File.ReadAllText(System.IO.Path.Combine(INSTALLDIR, INSTALL_FILE)).Replace('\r', '\n').Split(new char[] { '\n' }).FirstOrDefault().Trim() ?? ""; }
catch { }
return "";
}
}
public static UpdateInfo CheckForUpdate()
{
foreach(var url in MANIFEST_URLS)
{
try
{
using(var tmpfile = new Library.Utility.TempFile())
{
System.Net.WebClient wc = new System.Net.WebClient();
wc.Headers.Add(System.Net.HttpRequestHeader.UserAgent, string.Format("{0} v{1}", APPNAME, SelfVersion.Version));
wc.Headers.Add("X-Install-ID", InstallID);
wc.DownloadFile(url, tmpfile);
using(var fs = System.IO.File.OpenRead(tmpfile))
using(var ss = new SignatureReadingStream(fs, SIGN_KEY))
using(var tr = new System.IO.StreamReader(ss))
using(var jr = new Newtonsoft.Json.JsonTextReader(tr))
{
var update = new Newtonsoft.Json.JsonSerializer().Deserialize<UpdateInfo>(jr);
if (TryParseVersion(update.Version) <= TryParseVersion(SelfVersion.Version))
return null;
if (string.Equals(SelfVersion.ReleaseType, "Debug", StringComparison.InvariantCultureIgnoreCase) && !string.Equals(update.ReleaseType, SelfVersion.ReleaseType, StringComparison.CurrentCultureIgnoreCase))
return null;
return update;
}
}
}
catch (Exception ex)
{
if (OnError != null)
OnError(ex);
}
}
return null;
}
private static UpdateInfo ReadInstalledManifest(string folder)
{
var manifest = System.IO.Path.Combine(folder, UPDATE_MANIFEST_FILENAME);
if (System.IO.File.Exists(manifest))
{
try
{
using(var fs = System.IO.File.OpenRead(manifest))
using(var ss = new SignatureReadingStream(fs, SIGN_KEY))
using(var tr = new System.IO.StreamReader(ss))
using(var jr = new Newtonsoft.Json.JsonTextReader(tr))
return new Newtonsoft.Json.JsonSerializer().Deserialize<UpdateInfo>(jr);
}
catch (Exception ex)
{
if (OnError != null)
OnError(ex);
}
}
return null;
}
public static IEnumerable<KeyValuePair<string, UpdateInfo>> FindInstalledVersions()
{
var res = new List<KeyValuePair<string, UpdateInfo>>();
if (INSTALLDIR != null)
foreach(var folder in System.IO.Directory.GetDirectories(INSTALLDIR))
{
var r = ReadInstalledManifest(folder);
if (r != null)
res.Add(new KeyValuePair<string, UpdateInfo>(folder, r));
}
return res;
}
public static bool DownloadAndUnpackUpdate(UpdateInfo version, Action<double> progress = null)
{
if (INSTALLDIR == null)
return false;
var updates = version.RemoteURLS.ToList();
// If alternate update URLs are specified,
// we look for packages there as well
if (AutoUpdateSettings.UsesAlternateURLs)
{
var packagepath = new Library.Utility.Uri(updates[0]).Path;
var packagename = packagepath.Split('/').Last();
foreach(var alt_url in AutoUpdateSettings.URLs.Reverse())
{
var alt_uri = new Library.Utility.Uri(alt_url);
var path_components = alt_uri.Path.Split('/');
var path = string.Join("/", path_components.Take(path_components.Count() - 1).Union(new string[] { packagename}));
var new_path = alt_uri.SetPath(path);
updates.Insert(0, new_path.ToString());
}
}
using(var tempfile = new Library.Utility.TempFile())
{
foreach(var url in updates)
{
try
{
Action<long> cb = null;
if (progress != null)
cb = (s) => { progress(Math.Min(1.0, Math.Max(0.0, (double)s / version.CompressedSize))); };
var wreq = (System.Net.HttpWebRequest)System.Net.WebRequest.Create(url);
wreq.UserAgent = string.Format("{0} v{1}", APPNAME, SelfVersion.Version);
wreq.Headers.Add("X-Install-ID", InstallID);
using(var resp = wreq.GetResponse())
using(var rss = resp.GetResponseStream())
using(var pgs = new Duplicati.Library.Utility.ProgressReportingStream(rss, version.CompressedSize, cb))
using(var fs = System.IO.File.Open(tempfile, System.IO.FileMode.Create))
Duplicati.Library.Utility.Utility.CopyStream(pgs, fs);
var sha256 = System.Security.Cryptography.SHA256.Create();
var md5 = System.Security.Cryptography.MD5.Create();
using(var s = System.IO.File.OpenRead(tempfile))
{
if (s.Length != version.CompressedSize)
throw new Exception(string.Format("Invalid file size {0}, expected {1} for {2}", s.Length, version.CompressedSize, url));
var sha256hash = Convert.ToBase64String(sha256.ComputeHash(s));
if (sha256hash != version.SHA256)
throw new Exception(string.Format("Damaged or corrupted file, sha256 mismatch for {0}", url));
}
using(var s = System.IO.File.OpenRead(tempfile))
{
var md5hash = Convert.ToBase64String(md5.ComputeHash(s));
if (md5hash != version.MD5)
throw new Exception(string.Format("Damaged or corrupted file, md5 mismatch for {0}", url));
}
using(var tempfolder = new Duplicati.Library.Utility.TempFolder())
using(var zip = new Duplicati.Library.Compression.FileArchiveZip(tempfile, new Dictionary<string, string>()))
{
foreach(var file in zip.ListFilesWithSize(""))
{
if (System.IO.Path.IsPathRooted(file.Key) || file.Key.Trim().StartsWith("..", StringComparison.InvariantCultureIgnoreCase))
throw new Exception(string.Format("Out-of-place file path detected: {0}", file.Key));
var targetpath = System.IO.Path.Combine(tempfolder, file.Key);
var targetfolder = System.IO.Path.GetDirectoryName(targetpath);
if (!System.IO.Directory.Exists(targetfolder))
System.IO.Directory.CreateDirectory(targetfolder);
using(var zs = zip.OpenRead(file.Key))
using(var fs = System.IO.File.Create(targetpath))
zs.CopyTo(fs);
}
if (VerifyUnpackedFolder(tempfolder, version))
{
var versionstring = TryParseVersion(version.Version).ToString();
var targetfolder = System.IO.Path.Combine(INSTALLDIR, versionstring);
if (System.IO.Directory.Exists(targetfolder))
System.IO.Directory.Delete(targetfolder, true);
System.IO.Directory.CreateDirectory(targetfolder);
var tempfolderpath = Duplicati.Library.Utility.Utility.AppendDirSeparator(tempfolder);
var tempfolderlength = tempfolderpath.Length;
// Would be nice, but does not work :(
//System.IO.Directory.Move(tempfolder, targetfolder);
foreach(var e in Duplicati.Library.Utility.Utility.EnumerateFileSystemEntries(tempfolder))
{
var relpath = e.Substring(tempfolderlength);
if (string.IsNullOrWhiteSpace(relpath))
continue;
var fullpath = System.IO.Path.Combine(targetfolder, relpath);
if (relpath.EndsWith(System.IO.Path.DirectorySeparatorChar.ToString()))
System.IO.Directory.CreateDirectory(fullpath);
else
System.IO.File.Copy(e, fullpath);
}
// Verification will kick in when we list the installed updates
//VerifyUnpackedFolder(targetfolder, version);
System.IO.File.WriteAllText(System.IO.Path.Combine(INSTALLDIR, CURRENT_FILE), versionstring);
m_hasUpdateInstalled = null;
var obsolete = (from n in FindInstalledVersions()
where n.Value.Version != version.Version && n.Value.Version != SelfVersion.Version
let x = TryParseVersion(n.Value.Version)
orderby x descending
select n).Skip(1).ToArray();
foreach(var f in obsolete)
try { System.IO.Directory.Delete(f.Key, true); }
catch { }
return true;
}
else
{
throw new Exception(string.Format("Unable to verify unpacked folder for url: {0}", url));
}
}
}
catch(Exception ex)
{
if (OnError != null)
OnError(ex);
}
}
}
return false;
}
public static bool VerifyUnpackedFolder(string folder, UpdateInfo version = null)
{
try
{
UpdateInfo update;
FileEntry manifest;
var sha256 = System.Security.Cryptography.SHA256.Create();
var md5 = System.Security.Cryptography.MD5.Create();
using(var fs = System.IO.File.OpenRead(System.IO.Path.Combine(folder, UPDATE_MANIFEST_FILENAME)))
{
using(var ss = new SignatureReadingStream(fs, SIGN_KEY))
using(var tr = new System.IO.StreamReader(ss))
using(var jr = new Newtonsoft.Json.JsonTextReader(tr))
update = new Newtonsoft.Json.JsonSerializer().Deserialize<UpdateInfo>(jr);
sha256.Initialize();
md5.Initialize();
fs.Position = 0;
var h1 = Convert.ToBase64String(sha256.ComputeHash(fs));
fs.Position = 0;
var h2 = Convert.ToBase64String(md5.ComputeHash(fs));
manifest = new FileEntry() {
Path = UPDATE_MANIFEST_FILENAME,
Ignore = false,
LastWriteTime = update.ReleaseTime,
SHA256 = h1,
MD5 = h2
};
}
if (version != null && (update.Displayname != version.Displayname || update.ReleaseTime != version.ReleaseTime))
throw new Exception("The found version was not the expected version");
var paths = update.Files.Where(x => !x.Ignore).ToDictionary(x => x.Path.Replace('/', System.IO.Path.DirectorySeparatorChar), Library.Utility.Utility.ClientFilenameStringComparer);
paths.Add(manifest.Path, manifest);
var ignores = (from x in update.Files where x.Ignore select Library.Utility.Utility.AppendDirSeparator(x.Path.Replace('/', System.IO.Path.DirectorySeparatorChar))).ToList();
folder = Library.Utility.Utility.AppendDirSeparator(folder);
var baselen = folder.Length;
foreach(var file in Library.Utility.Utility.EnumerateFileSystemEntries(folder))
{
var relpath = file.Substring(baselen);
if (string.IsNullOrWhiteSpace(relpath))
continue;
FileEntry fe;
if (!paths.TryGetValue(relpath, out fe))
{
var ignore = false;
foreach(var c in ignores)
if (ignore = relpath.StartsWith(c))
break;
if (ignore)
continue;
throw new Exception(string.Format("Found unexpected file: {0}", file));
}
paths.Remove(relpath);
if (fe.Path.EndsWith("/"))
continue;
sha256.Initialize();
md5.Initialize();
using(var fs = System.IO.File.OpenRead(file))
{
if (Convert.ToBase64String(sha256.ComputeHash(fs)) != fe.SHA256)
throw new Exception(string.Format("Invalid sha256 hash for file: {0}", file));
fs.Position = 0;
if (Convert.ToBase64String(md5.ComputeHash(fs)) != fe.MD5)
throw new Exception(string.Format("Invalid md5 hash for file: {0}", file));
}
}
var filteredpaths = (from p in paths
where !string.IsNullOrWhiteSpace(p.Key) && !p.Key.EndsWith("/")
select p.Key).ToList();
if (filteredpaths.Count == 1)
throw new Exception(string.Format("Folder {0} is missing: {1}", folder, filteredpaths.First()));
else if (filteredpaths.Count > 0)
throw new Exception(string.Format("Folder {0} is missing {1} and {2} other file(s)", folder, filteredpaths.First(), filteredpaths.Count - 1));
return true;
}
catch (Exception ex)
{
if (OnError != null)
OnError(ex);
}
return false;
}
public static bool SetRunUpdate()
{
if (HasUpdateInstalled)
{
AppDomain.CurrentDomain.SetData(RUN_UPDATED_FOLDER_PATH, m_hasUpdateInstalled.Value.Key);
return true;
}
return false;
}
public static void CreateUpdatePackage(System.Security.Cryptography.RSACryptoServiceProvider key, string inputfolder, string outputfolder, string manifest = null)
{
// Read the existing manifest
UpdateInfo remoteManifest;
var manifestpath = manifest ?? System.IO.Path.Combine(inputfolder, UPDATE_MANIFEST_FILENAME);
using(var s = System.IO.File.OpenRead(manifestpath))
using(var sr = new System.IO.StreamReader(s))
using(var jr = new Newtonsoft.Json.JsonTextReader(sr))
remoteManifest = new Newtonsoft.Json.JsonSerializer().Deserialize<UpdateInfo>(jr);
if (remoteManifest.Files == null)
remoteManifest.Files = new FileEntry[0];
if (remoteManifest.ReleaseTime.Ticks == 0)
remoteManifest.ReleaseTime = DateTime.UtcNow;
var ignoreFiles = (from n in remoteManifest.Files
where n.Ignore
select n).ToArray();
var ignoreMap = ignoreFiles.ToDictionary(k => k.Path, k => "", Duplicati.Library.Utility.Utility.ClientFilenameStringComparer);
remoteManifest.MD5 = null;
remoteManifest.SHA256 = null;
remoteManifest.Files = null;
remoteManifest.UncompressedSize = 0;
var localManifest = remoteManifest.Clone();
localManifest.RemoteURLS = null;
inputfolder = Duplicati.Library.Utility.Utility.AppendDirSeparator(inputfolder);
var baselen = inputfolder.Length;
var dirsep = System.IO.Path.DirectorySeparatorChar.ToString();
ignoreMap.Add(UPDATE_MANIFEST_FILENAME, "");
var md5 = System.Security.Cryptography.MD5.Create();
var sha256 = System.Security.Cryptography.SHA256.Create();
Func<string, string> computeMD5 = (path) =>
{
md5.Initialize();
using(var fs = System.IO.File.OpenRead(path))
return Convert.ToBase64String(md5.ComputeHash(fs));
};
Func<string, string> computeSHA256 = (path) =>
{
sha256.Initialize();
using(var fs = System.IO.File.OpenRead(path))
return Convert.ToBase64String(sha256.ComputeHash(fs));
};
// Build a zip
using (var archive_temp = new Duplicati.Library.Utility.TempFile())
{
using (var zipfile = new Duplicati.Library.Compression.FileArchiveZip(archive_temp, new Dictionary<string, string>()))
{
Func<string, string, bool> addToArchive = (path, relpath) =>
{
if (ignoreMap.ContainsKey(relpath))
return false;
if (path.EndsWith(dirsep))
return true;
using (var source = System.IO.File.OpenRead(path))
using (var target = zipfile.CreateFile(relpath,
Duplicati.Library.Interface.CompressionHint.Compressible,
System.IO.File.GetLastAccessTimeUtc(path)))
{
source.CopyTo(target);
remoteManifest.UncompressedSize += source.Length;
}
return true;
};
// Build the update manifest
localManifest.Files =
(from fse in Duplicati.Library.Utility.Utility.EnumerateFileSystemEntries(inputfolder)
let relpath = fse.Substring(baselen)
where addToArchive(fse, relpath)
select new FileEntry() {
Path = relpath,
LastWriteTime = System.IO.File.GetLastAccessTimeUtc(fse),
MD5 = fse.EndsWith(dirsep) ? null : computeMD5(fse),
SHA256 = fse.EndsWith(dirsep) ? null : computeSHA256(fse)
})
.Union(ignoreFiles).ToArray();
// Write a signed manifest with the files
using (var ms = new System.IO.MemoryStream())
using (var sw = new System.IO.StreamWriter(ms))
{
new Newtonsoft.Json.JsonSerializer().Serialize(sw, localManifest);
sw.Flush();
using (var ms2 = new System.IO.MemoryStream())
{
SignatureReadingStream.CreateSignedStream(ms, ms2, key);
ms2.Position = 0;
using (var sigfile = zipfile.CreateFile(UPDATE_MANIFEST_FILENAME,
Duplicati.Library.Interface.CompressionHint.Compressible,
DateTime.UtcNow))
ms2.CopyTo(sigfile);
}
}
}
remoteManifest.CompressedSize = new System.IO.FileInfo(archive_temp).Length;
remoteManifest.MD5 = computeMD5(archive_temp);
remoteManifest.SHA256 = computeSHA256(archive_temp);
System.IO.File.Move(archive_temp, System.IO.Path.Combine(outputfolder, "package.zip"));
}
// Write a signed manifest for upload
using(var tf = new Duplicati.Library.Utility.TempFile())
{
using (var ms = new System.IO.MemoryStream())
using (var sw = new System.IO.StreamWriter(ms))
{
new Newtonsoft.Json.JsonSerializer().Serialize(sw, remoteManifest);
sw.Flush();
using (var fs = System.IO.File.Create(tf))
SignatureReadingStream.CreateSignedStream(ms, fs, key);
}
System.IO.File.Move(tf, System.IO.Path.Combine(outputfolder, UPDATE_MANIFEST_FILENAME));
}
}
private static void WrapWithUpdater(AutoUpdateStrategy defaultstrategy, Action wrappedFunction)
{
string optstr = Environment.GetEnvironmentVariable(string.Format(UPDATE_STRATEGY_ENVNAME_TEMPLATE, APPNAME));
AutoUpdateStrategy strategy;
if (string.IsNullOrWhiteSpace(optstr) || !Enum.TryParse(optstr, out strategy))
strategy = defaultstrategy;
System.Threading.Thread backgroundChecker = null;
UpdateInfo updateDetected = null;
bool updateInstalled = false;
bool checkForUpdate;
bool downloadUpdate;
bool runAfter;
bool runDuring;
bool runBefore;
switch (strategy)
{
case AutoUpdateStrategy.CheckBefore:
case AutoUpdateStrategy.CheckDuring:
case AutoUpdateStrategy.CheckAfter:
checkForUpdate = true;
downloadUpdate = false;
break;
case AutoUpdateStrategy.InstallBefore:
case AutoUpdateStrategy.InstallDuring:
case AutoUpdateStrategy.InstallAfter:
checkForUpdate = true;
downloadUpdate = true;
break;
default:
checkForUpdate = false;
downloadUpdate = false;
break;
}
switch (strategy)
{
case AutoUpdateStrategy.CheckBefore:
case AutoUpdateStrategy.InstallBefore:
runBefore = true;
runDuring = false;
runAfter = false;
break;
case AutoUpdateStrategy.CheckAfter:
case AutoUpdateStrategy.InstallAfter:
runBefore = false;
runDuring = false;
runAfter = true;
break;
case AutoUpdateStrategy.CheckDuring:
case AutoUpdateStrategy.InstallDuring:
runBefore = false;
runDuring = true;
runAfter = false;
break;
default:
runBefore = false;
runDuring = false;
runAfter = false;
break;
}
if (checkForUpdate)
{
backgroundChecker = new System.Threading.Thread(() =>
{
// Don't run "during" if the task is short
if (runDuring)
System.Threading.Thread.Sleep(TimeSpan.FromSeconds(10));
updateDetected = CheckForUpdate();
if (updateDetected != null && downloadUpdate)
{
if (!runDuring)
Console.WriteLine("Update to {0} detected, installing...", updateDetected.Displayname);
updateInstalled = DownloadAndUnpackUpdate(updateDetected);
}
});
backgroundChecker.IsBackground = true;
backgroundChecker.Name = "BackgroundUpdateChecker";
if (!runAfter)
backgroundChecker.Start();
if (runBefore)
{
Console.WriteLine("Checking for update ...");
backgroundChecker.Join();
if (downloadUpdate)
{
if (updateInstalled)
Console.WriteLine("Install succeeded, running updated version");
else
Console.WriteLine("Install or download failed, using current version");
}
else if (updateDetected != null)
{
Console.WriteLine("Update \"{0}\" detected", updateDetected.Displayname);
}
backgroundChecker = null;
}
}
wrappedFunction();
if (backgroundChecker != null && runAfter)
{
Console.WriteLine("Checking for update ...");
backgroundChecker.Start();
backgroundChecker.Join();
}
if (backgroundChecker != null && updateDetected != null)
{
if (backgroundChecker.IsAlive)
{
Console.WriteLine("Waiting for update \"{0}\" to complete", updateDetected.Displayname);
backgroundChecker.Join();
}
if (downloadUpdate)
{
if (updateInstalled)
Console.WriteLine("Install succeeded, running updated version on next launch");
else
Console.WriteLine("Install or download failed, using current version on next launch");
}
else
{
Console.WriteLine("Update \"{0}\" detected", updateDetected.Displayname);
}
}
}
private static int RunMethod(System.Reflection.MethodInfo method, string[] args)
{
try
{
var n = method.Invoke(null, new object[] { args });
if (method.ReturnType == typeof(int))
return (int)n;
return 0;
}
catch (System.Reflection.TargetInvocationException tex)
{
try
{
Console.WriteLine("Crash! {0}{1}", Environment.NewLine, tex.ToString());
}
catch
{
}
try
{
var report_file = System.IO.Path.Combine(
string.IsNullOrEmpty(INSTALLDIR) ? Library.Utility.TempFolder.SystemTempPath : INSTALLDIR,
string.Format("{0}-crashlog.txt", AutoUpdateSettings.AppName)
);
System.IO.File.WriteAllText(report_file, tex.ToString());
}
catch
{
}
if (tex.InnerException != null)
throw tex.InnerException;
else
throw;
}
}
public static int RunFromMostRecent(System.Reflection.MethodInfo method, string[] cmdargs, AutoUpdateStrategy defaultstrategy = AutoUpdateStrategy.InstallDuring)
{
// If the update is disabled, go straight in
if (DISABLE_UPDATE_DOMAIN)
return RunMethod(method, cmdargs);
// If we are not the primary domain, just execute
if (!AppDomain.CurrentDomain.IsDefaultAppDomain())
{
int r = 0;
WrapWithUpdater(defaultstrategy, () => {
r = RunMethod(method, cmdargs);
});
return r;
}
// If we are a re-launch, wait briefly for the other process to exit
var sleepmarker = System.Environment.GetEnvironmentVariable(string.Format(SLEEP_ENVNAME_TEMPLATE, APPNAME));
if (!string.IsNullOrWhiteSpace(sleepmarker))
{
System.Environment.SetEnvironmentVariable(string.Format(SLEEP_ENVNAME_TEMPLATE, APPNAME), null);
System.Threading.Thread.Sleep(TimeSpan.FromSeconds(10));
}
// Check if there are updates installed, otherwise use current
KeyValuePair<string, UpdateInfo> best = new KeyValuePair<string, UpdateInfo>(AppDomain.CurrentDomain.SetupInformation.ApplicationBase, SelfVersion);
if (HasUpdateInstalled)
best = m_hasUpdateInstalled.Value;
if (INSTALLDIR != null && System.IO.File.Exists(System.IO.Path.Combine(INSTALLDIR, CURRENT_FILE)))
{
try
{
var current = System.IO.File.ReadAllText(System.IO.Path.Combine(INSTALLDIR, CURRENT_FILE)).Trim();
if (!string.IsNullOrWhiteSpace(current))
{
var targetfolder = System.IO.Path.Combine(INSTALLDIR, current);
var currentmanifest = ReadInstalledManifest(targetfolder);
if (currentmanifest != null && VerifyUnpackedFolder(targetfolder, currentmanifest))
best = new KeyValuePair<string, UpdateInfo>(targetfolder, currentmanifest);
}
}
catch (Exception ex)
{
if (OnError != null)
OnError(ex);
}
}
Environment.SetEnvironmentVariable(string.Format(INSTALLDIR_ENVNAME_TEMPLATE, APPNAME), InstalledBaseDir);
var folder = best.Key;
// Basic idea with the loop is that the running AppDomain can use
// RUN_UPDATED_ENVNAME_TEMPLATE to signal that a new version is ready
// when the caller exits, the new update is executed
//
// This allows more or less seamless updates
//
int result = 0;
while (!string.IsNullOrWhiteSpace(folder) && System.IO.Directory.Exists(folder))
{
var prevfolder = folder;
// Create the new domain
var domain = AppDomain.CreateDomain(
"UpdateDomain",
null,
folder,
"",
false
);
result = domain.ExecuteAssemblyByName(method.DeclaringType.Assembly.GetName().Name, cmdargs);
folder = (string)domain.GetData(RUN_UPDATED_FOLDER_PATH);
try { AppDomain.Unload(domain); }
catch (Exception ex)
{
Console.WriteLine("Appdomain unload error: {0}", ex);
}
if (!string.IsNullOrWhiteSpace(folder))
{
if (!VerifyUnpackedFolder(folder))
folder = prevfolder; //Go back and run the previous version
else if (RequiresRespawn)
{
// We have a valid update, and the current instance is terminated.
// But due to external libraries, we need to re-spawn the original process
try
{
var args = Environment.CommandLine;
var app = Environment.GetCommandLineArgs().First();
args = args.Substring(app.Length);
if (!System.IO.Path.IsPathRooted(app))
app = System.IO.Path.Combine(InstalledBaseDir, app);
// Re-launch but give the OS a little time to fully unload all open handles, etc.
var si = new System.Diagnostics.ProcessStartInfo(app, args);
si.UseShellExecute = false;
si.EnvironmentVariables.Add(string.Format(SLEEP_ENVNAME_TEMPLATE, APPNAME), "1");
System.Diagnostics.Process.Start(si);
return 0;
}
catch (Exception ex)
{
if (OnError != null)
OnError(ex);
folder = prevfolder;
}
}
}
}
return result;
}
}
}