4 using System.Collections.Generic;
5 using System.Diagnostics;
6 using System.Globalization;
9 using System.Reflection;
10 using System.Threading;
11 using System.Windows.Forms;
13 using SiliconStudio.Assets;
15 namespace SiliconStudio.LauncherApp
19 private const string MainPackageKey =
"mainPackage";
20 private const string MainExecutableKey =
"mainExecutable";
22 private const string LauncherAppCallbackParam =
"LauncherAppCallback";
23 private readonly NugetStore store;
24 private readonly
string mainPackage;
25 private readonly
string mainExecutable;
26 private bool isSynchronous =
false;
27 private readonly List<Thread> downloadThreads;
28 private readonly Stopwatch clock;
29 private int maxPercentage;
30 private bool isInNegativeMode;
32 private bool isDownloading;
34 public bool IsSelfUpdated {
get;
private set; }
36 public bool IsNewPackageAvailable {
get;
private set; }
38 public IntPtr MainWindowHandle {
get; set; }
44 public bool IsDiagnosticMode {
get; set; }
48 internal event EventHandler<NugetLogEventArgs> LogAvailable;
50 public event EventHandler<LoadingEventArgs>
Loading;
58 public event EventHandler<EventArgs>
Running;
60 public bool IsDownloading
68 var previousValue = isDownloading;
69 isDownloading = value;
70 if (previousValue && !isDownloading)
81 var assembly = typeof(
Program).Assembly;
83 var assemblyInformationalVersion = CustomAttributeProviderExtensions.GetCustomAttribute<AssemblyInformationalVersionAttribute>(assembly);
84 Version = assemblyInformationalVersion.InformationalVersion;
89 clock = Stopwatch.StartNew();
94 DebugStep(
"Load store");
97 var thisExeDirectory = Path.GetDirectoryName(typeof(
LauncherApp).Assembly.Location);
99 store =
new NugetStore(thisExeDirectory);
100 store.Manager.Logger =
this;
101 store.SourceRepository.Logger =
this;
103 mainPackage = store.Settings.GetConfigValue(MainPackageKey);
104 if (
string.IsNullOrWhiteSpace(mainPackage))
106 throw new LauncherAppException(
"Invalid configuration. Expecting [{0}] in config", MainPackageKey);
108 store.DefaultPackageId = mainPackage;
110 mainExecutable = store.Settings.GetConfigValue(MainExecutableKey);
111 if (
string.IsNullOrWhiteSpace(mainExecutable))
113 throw new LauncherAppException(
"Invalid configuration. Expecting [{0}] in config", MainExecutableKey);
116 var aggregateRepo = (AggregateRepository)store.Manager.SourceRepository;
117 foreach (var repo in aggregateRepo.Repositories)
120 if (progressProvider != null)
122 progressProvider.ProgressAvailable += OnProgressAvailable;
126 downloadThreads =
new List<Thread>();
130 store.UpdateTargets();
135 if (downloadThreads.Count > 0)
137 foreach (var downloadThread
in downloadThreads)
139 DebugStep(
"Waiting for thread {0} to terminate");
140 downloadThread.Join();
142 downloadThreads.Clear();
146 public int Run(
string[] args)
149 downloadThreads.Add(RunThread(SelfUpdate));
150 DebugStep(
"SelfUpdate launched");
153 var installedPackage = FindLatestInstalledPackage(store.Manager.LocalRepository);
155 DebugStep(
"Find installed package");
158 var cachePackage = FindLatestInstalledPackage(MachineCache.Default);
160 DebugStep(
"Find cache package");
163 if (installedPackage != null)
165 if (cachePackage != null && cachePackage.Version > installedPackage.Version)
167 var processCount = GetProcessCount();
168 bool isSafeToUpdate = processCount <= 1;
173 IsDownloading =
true;
174 Info(
"Preparing installer for new {0} version {1}", mainPackage, cachePackage.Version);
175 PackageUpdate(installedPackage,
false);
176 IsDownloading =
false;
177 DebugStep(
"Update package");
181 ShowInformationDialog(
182 string.Format(
"Cannot update {0} as there are [{1}] instances currently running.\n\nClose all your applications and restart", mainPackage, processCount));
187 DebugStep(
"Start download package in cache");
189 var localPackage = installedPackage;
190 downloadThreads.Add(RunThread(() => PackageUpdate(localPackage,
true)));
195 if (store.CheckSource())
198 IsDownloading =
true;
199 Info(
"Preparing installer for {0}", mainPackage);
201 store.InstallPackage(mainPackage, null);
203 IsDownloading =
false;
204 DebugStep(
"Package installed");
208 ShowErrorDialog(
"Download server not available. Please try again later");
214 installedPackage = FindLatestInstalledPackage(store.Manager.LocalRepository);
216 if (installedPackage == null)
218 ShowErrorDialog(
"No package installed");
223 var fullExePath = GetMainExecutable(installedPackage);
224 Environment.CurrentDirectory = Path.GetDirectoryName(fullExePath);
225 var appDomainSetup =
new AppDomainSetup { ApplicationBase = Path.GetDirectoryName(fullExePath) };
226 var newAppDomain = AppDomain.CreateDomain(
"LauncherAppDomain", null, appDomainSetup);
228 DebugStep(
"Run executable");
229 OnLoading(
new LoadingEventArgs(installedPackage.Id, installedPackage.Version.ToString()));
231 var newArgList =
new List<string> {
"/LauncherWindowHandle", MainWindowHandle.ToInt64().ToString(CultureInfo.InvariantCulture) };
232 newArgList.AddRange(args);
236 return newAppDomain.ExecuteAssembly(fullExePath, newArgList.ToArray());
241 Environment.CurrentDirectory = Path.GetDirectoryName(typeof(
LauncherApp).Assembly.Location);
245 AppDomain.Unload(newAppDomain);
255 EventHandler<EventArgs> handler = Running;
256 if (handler != null) handler(
this,
EventArgs.Empty);
261 var currentModule = Assembly.GetAssembly(typeof(
LauncherApp));
262 return Process.GetProcessesByName(Process.GetCurrentProcess().ProcessName)
263 .Count(x => string.Equals(x.MainModule.FileName, currentModule.Location, StringComparison.OrdinalIgnoreCase));
266 private string GetMainExecutable(IPackage package)
268 var packagePath = store.PathResolver.GetInstallPath(package);
269 return Path.Combine(packagePath, mainExecutable);
272 private IPackage FindLatestInstalledPackage(IPackageRepository packageRepository)
274 return packageRepository.FindPackagesById(mainPackage).OrderByDescending(p => p.Version).FirstOrDefault();
277 private IPackage FindPackageUpdate(IPackage previousPackage)
279 return store.Manager.SourceRepository.GetUpdates(
new[] { previousPackage },
true,
false).FirstOrDefault();
282 private void PackageUpdate(IPackage package,
bool putInMachineCache)
285 DebugStep(
"Find Package Update");
286 var newPackage = FindPackageUpdate(package);
288 if (newPackage != null && newPackage.Version > package.Version)
290 DebugStep(
"Package Update Found {0} {1}", newPackage.Id, newPackage.Version);
292 if (putInMachineCache)
294 IsDownloading =
true;
295 IsNewPackageAvailable =
true;
296 ShowInformationDialog(
"A new version " + newPackage.Version +
" of " + mainPackage +
@" is available.
298 The download will start in the background.
300 The new version will be available on next run after all GameStudio are closed");
302 MachineCache.Default.AddPackage(newPackage);
305 IsDownloading =
false;
309 store.UpdatePackage(newPackage);
314 private Thread RunThread(ThreadStart threadStart)
324 var thread =
new Thread(
333 OnUnhandledException(exception);
335 }) { IsBackground =
true };
341 private void SelfUpdate()
343 var version =
new SemanticVersion(Version);
344 var productAttribute = CustomAttributeProviderExtensions.GetCustomAttribute<AssemblyProductAttribute>(typeof(LauncherApp).Assembly);
345 var packageId = productAttribute.Product;
347 var
package = store.Manager.SourceRepository.GetUpdates(new[] { new PackageName(packageId, version) }, true, false).FirstOrDefault();
350 if (package != null && version < package.Version)
352 var movedFiles =
new List<string>();
355 var inputFiles = package.GetFiles();
356 const string directoryRoot =
"tools\\";
357 foreach (var file
in inputFiles.Where(file => file.Path.StartsWith(directoryRoot)))
359 var fileName = Path.Combine(store.RootDirectory, file.Path.Substring(directoryRoot.Length));
362 string renamedPath = fileName +
".old";
366 if (
File.Exists(fileName))
368 Move(fileName, renamedPath);
369 movedFiles.Add(fileName);
373 UpdateFile(fileName, file);
378 foreach (var oldFile
in movedFiles)
380 renamedPath = oldFile +
".old";
381 Move(renamedPath, oldFile);
389 foreach (var oldFile
in movedFiles)
393 var renamedPath = oldFile +
".old";
395 if (
File.Exists(renamedPath))
397 File.Delete(renamedPath);
406 IsSelfUpdated =
true;
410 private static void EnsureDirectory(
string filePath)
413 var directory = Path.GetDirectoryName(filePath);
414 if (directory != null && !Directory.Exists(directory))
416 Directory.CreateDirectory(directory);
420 private static void UpdateFile(
string newFilePath, IPackageFile file)
422 EnsureDirectory(newFilePath);
423 using (
Stream fromStream = file.GetStream(), toStream =
File.Create(newFilePath))
425 fromStream.CopyTo(toStream);
429 private static void Move(
string oldPath,
string newPath)
431 EnsureDirectory(newPath);
434 if (
File.Exists(newPath))
436 File.Delete(newPath);
439 catch (FileNotFoundException)
444 File.Move(oldPath, newPath);
447 private void OnProgressAvailable(
object sender, ProgressEventArgs e)
451 var percentComplete = e.PercentComplete;
452 if (percentComplete > 0 && !isInNegativeMode)
454 maxPercentage = percentComplete;
457 if (percentComplete < 0 || isInNegativeMode)
459 isInNegativeMode =
true;
460 percentComplete = maxPercentage * 2 + percentComplete + 1;
464 percentComplete = percentComplete < 0 ? 0 : percentComplete > 100 ? 100 : percentComplete;
466 e =
new ProgressEventArgs(e.Operation, percentComplete);
468 var handler = ProgressAvailable;
469 if (handler != null) handler(sender, e);
472 FileConflictResolution IFileConflictResolver.ResolveFileConflict(
string message)
475 return FileConflictResolution.Ignore;
478 void ILogger.Log(MessageLevel level,
string message, params
object[] args)
480 OnLogAvailable(
new NugetLogEventArgs(level, message, args));
483 private void Info(
string message)
485 OnLogAvailable(
new NugetLogEventArgs(MessageLevel.Info, message));
488 private void Info(
string message, params
object[] args)
490 OnLogAvailable(
new NugetLogEventArgs(MessageLevel.Info, message, args));
493 private void Error(
string message)
495 OnLogAvailable(
new NugetLogEventArgs(MessageLevel.Error, message));
498 private void Error(
string message, params
object[] args)
500 OnLogAvailable(
new NugetLogEventArgs(MessageLevel.Error, message, args));
503 private void OnLogAvailable(NugetLogEventArgs e)
505 var handler = LogAvailable;
506 if (handler != null) handler(
this, e);
509 private void OnLoading(LoadingEventArgs e)
512 if (handler != null) handler(
this, e);
515 private void DebugStep(
string step)
517 Console.WriteLine(
"Step {0} ({1}ms)", step, clock.ElapsedMilliseconds);
519 private void DebugStep(
string step, params
object[] args)
521 DebugStep(
string.Format(step, args));
526 var arg =
new DialogEventArgs(text,
"Launcher information", MessageBoxButtons.YesNo, MessageBoxIcon.Information, MessageBoxDefaultButton.Button1, 0);
527 OnDialogAvailable(arg);
533 var arg =
new DialogEventArgs(text,
"Launcher information", MessageBoxButtons.OK, MessageBoxIcon.Information, MessageBoxDefaultButton.Button1, 0);
534 OnDialogAvailable(arg);
538 private void ShowErrorDialog(
string text)
541 OnDialogAvailable(
new DialogEventArgs(text,
"Error in Launcher", MessageBoxButtons.OK, MessageBoxIcon.Error, MessageBoxDefaultButton.Button1, 0));
544 private void OnDialogAvailable(DialogEventArgs e)
546 var handler = DialogAvailable;
547 if (handler != null) handler(
this, e);
550 private void OnDownloadFinished()
552 EventHandler<EventArgs> handler = DownloadFinished;
553 if (handler != null) handler(
this,
EventArgs.Empty);
556 private void OnUnhandledException(
Exception e)
558 EventHandler<Exception> handler = UnhandledException;
559 if (handler != null) handler(
this, e);
static readonly string Version
EventHandler< DialogEventArgs > DialogAvailable
static int GetProcessCount()
EventHandler< EventArgs > DownloadFinished
EventHandler< ProgressEventArgs > ProgressAvailable
DialogResult
An enum representing the result of a dialog invocation.
An error message (level 4).
EventHandler< EventArgs > Running
An regular info message (level 2).
EventHandler< Exception > UnhandledException
EventHandler< LoadingEventArgs > Loading