using Newtonsoft.Json; using Newtonsoft.Json.Converters; using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; using System.Reflection; using System.Text; using System.Threading; using System.Threading.Tasks; using Tango.Core; using Tango.Core.DI; using Tango.Core.Helpers; using Tango.PPC.Common.Application; using Tango.PPC.Common.Connection; using Tango.PPC.Common.Notifications; using Tango.PPC.Shared.Updates; namespace Tango.PPC.Common.UpdatePackages { public class DefaultPackageRunner : ExtendedObject, IPackageRunner { private JsonSerializerSettings _jsonSettings; private String _configFile; private PackagesFile _packagesFile; public event EventHandler PackageStateChanged; public event EventHandler PackageProgress; public DefaultPackageRunner() { _jsonSettings = new JsonSerializerSettings { Formatting = Formatting.Indented, Error = (sender, args) => { args.ErrorContext.Handled = true; LogManager.Log(args.ErrorContext.Error.Message); } }; _jsonSettings.Converters.Add(new StringEnumConverter(false)); _configFile = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "Twine", "Tango", "Packages", "packages.json"); } public Task GetPackagesFile() { return Task.Factory.StartNew(() => { if (_packagesFile != null) { return _packagesFile; } else { Directory.CreateDirectory(Path.GetDirectoryName(_configFile)); _packagesFile = new PackagesFile(); try { if (File.Exists(_configFile)) { LogManager.Log("Loading packages config from " + _configFile + "..."); _packagesFile = JsonConvert.DeserializeObject(File.ReadAllText(_configFile), _jsonSettings); } } catch (Exception ex) { LogManager.Log(ex, "Error loading packages file."); } return _packagesFile; } }); } private void SavePackagesConfig() { Directory.CreateDirectory(Path.GetDirectoryName(_configFile)); String json = String.Empty; if (_packagesFile != null) { json = JsonConvert.SerializeObject(_packagesFile, _jsonSettings); } else { json = JsonConvert.SerializeObject(new PackagesFile(), _jsonSettings); } File.WriteAllText(_configFile, json); } public Task Run(PackageType type, Version deltaVersion, String packagesFolder) { return Task.Factory.StartNew(() => { PackageRunnerResult result = new PackageRunnerResult(); PackageContext context = new PackageContext(); context.ApplicationManager = TangoIOC.Default.GetInstance(); context.MachineProvider = TangoIOC.Default.GetInstance(); context.NotificationProvider = TangoIOC.Default.GetInstance(); context.InstalledVersion = context.ApplicationManager.Version; context.DeltaVersion = deltaVersion; LogManager.Log($"Running {type}-update packages..."); //Get installed packages. _packagesFile = GetPackagesFile().Result; LogManager.Log($"Installed packages file:\n{_packagesFile}"); if (Debugger.IsAttached) { LogManager.Log("Debugger attached detected. switching packages folder to main application path..."); //TO DEBUG PACKAGES -- // //On DEBUG build the packages assemblies are copied to the main application output folder using post-build events. //Then, if a debugger is attached, we change the packages folder to the main output folder so they can be debugged easily. packagesFolder = AssemblyHelper.GetCurrentAssemblyFolder(); } LogManager.Log($"Scanning for packages on '{packagesFolder}'..."); //Get all packages in folder. foreach (var packageFile in Directory.GetFiles(packagesFolder, "*.dll")) { LogManager.Log($"Loading assembly '{Path.GetFileName(packageFile)}'..."); Assembly asm; //Load assembly and investigate for types based on package type. try { asm = Assembly.LoadFile(packageFile); } catch (Exception ex) { LogManager.Log(ex, "Error loading assembly!"); continue; } try { foreach (var packageType in asm.GetTypes().Where( x => typeof(IPPCPackage).IsAssignableFrom(x) && x.GetCustomAttribute() != null && x.GetCustomAttribute().Type == type)) { LogManager.Log($"Checking package '{packageType.FullName}'..."); try { //Getting installed package from file. var installedPackage = _packagesFile.PackageInstallations.SingleOrDefault(x => x.PackageName == packageType.FullName); //Check if requires installation. if (installedPackage == null || installedPackage.State != PackageInstallationState.Installed) { if (installedPackage == null) { LogManager.Log("Package was never installed."); installedPackage = new PackageInstallation(); installedPackage.State = PackageInstallationState.NotInstalled; installedPackage.PackageName = packageType.FullName; installedPackage.Type = type; _packagesFile.PackageInstallations.Add(installedPackage); } else { LogManager.Log($"Package installation state is '{installedPackage.State}' due to {installedPackage.FailedReason}"); } LogManager.Log("Installing package..."); //Install package... var att = packageType.GetCustomAttribute(); var packageInstance = Activator.CreateInstance(packageType) as IPPCPackage; if (packageInstance != null) { try { OnPackageRuns(att.Name, installedPackage.State, installedPackage.Type); installedPackage.InstallationDate = DateTime.Now; context.ProgressAction = (message, isIntermediate, progress, total) => { PackageProgress?.Invoke(this, new PackageProgressEventArgs() { PackageName = att.Name, Message = message, IsIntermediate = isIntermediate, Progress = progress, Total = total }); }; PackageProgress?.Invoke(this, new PackageProgressEventArgs() { PackageName = type == PackageType.Pre ? "Preparing" : "Finalizing", Message = att.Name, IsIntermediate = true, Progress = 0, Total = 100 }); packageInstance.Run(context).GetAwaiter().GetResult(); installedPackage.State = PackageInstallationState.Installed; installedPackage.FailedReason = null; OnPackageRuns(att.Name, installedPackage.State, installedPackage.Type); LogManager.Log("Package installed successfully."); if (att.RequiresRestart) { result.RestartRequired = true; } Thread.Sleep(2000); } catch (Exception ex) { LogManager.Log(ex, "Package installation failed."); installedPackage.State = PackageInstallationState.Failed; installedPackage.FailedReason = ex.FlattenMessage(); OnPackageRuns(att.Name, installedPackage.State, installedPackage.Type); continue; } } } else { LogManager.Log("Package is already installed."); } } catch (Exception ex) { LogManager.Log(ex, "Error in handling the package!"); continue; } } } catch (Exception ex) { LogManager.Log(ex, "Error investigating assembly!"); continue; } } //Save package file. LogManager.Log("Running packages has completed. Saving packages config file."); try { SavePackagesConfig(); } catch (Exception ex) { LogManager.Log(ex, "Error saving packages file!"); } return result; }); } public Task IsPackageInstallationRequired(PackageType type, String packagesFolder) { return Task.Factory.StartNew(() => { LogManager.Log("Checking if any package installation is required..."); _packagesFile = GetPackagesFile().Result; //Get all packages in folder. foreach (var packageFile in Directory.GetFiles(packagesFolder, "*.dll")) { LogManager.Log($"Loading assembly '{Path.GetFileName(packageFile)}'..."); Assembly asm; //Load assembly and investigate for types based on package type. try { asm = Assembly.LoadFile(packageFile); } catch (Exception ex) { LogManager.Log(ex, "Error loading assembly!"); continue; } try { foreach (var packageType in asm.GetTypes().Where( x => typeof(IPPCPackage).IsAssignableFrom(x) && x.GetCustomAttribute() != null && x.GetCustomAttribute().Type == type)) { LogManager.Log($"Checking package '{packageType.FullName}'..."); try { //Getting installed package from file. var installedPackage = _packagesFile.PackageInstallations.SingleOrDefault(x => x.PackageName == packageType.FullName); //Check if requires installation. if (installedPackage == null || installedPackage.State != PackageInstallationState.Installed) { if (installedPackage == null) { LogManager.Log("Package was never installed."); return true; } else { LogManager.Log($"Package installation state is '{installedPackage.State}' due to {installedPackage.FailedReason}"); return true; } } else { LogManager.Log("Package is already installed."); } } catch (Exception ex) { LogManager.Log(ex, "Error in handling the package!"); continue; } } } catch (Exception ex) { LogManager.Log(ex, "Error investigating assembly!"); continue; } } LogManager.Log("No packages to install."); return false; }); } protected virtual void OnPackageRuns(String packageName, PackageInstallationState state, PackageType type) { PackageStateChanged?.Invoke(this, new PackageStateChangedEventArgs() { PackageName = packageName, State = state, PackageType = type, }); } } }