From bf9e24502c26bc130960cebc6b1be16635070bc8 Mon Sep 17 00:00:00 2001 From: LukePulverenti Date: Mon, 4 Mar 2013 23:25:27 -0500 Subject: extracted an installation manager interface --- MediaBrowser.Api/PackageService.cs | 32 +- MediaBrowser.Api/PluginService.cs | 8 +- .../BaseApplicationHost.cs | 12 +- MediaBrowser.Controller/Kernel.cs | 14 +- .../MediaBrowser.Controller.csproj | 2 +- .../Updates/IInstallationManager.cs | 106 +++++ .../Updates/InstallationManager.cs | 494 --------------------- .../MediaBrowser.Server.Implementations.csproj | 1 + .../ScheduledTasks/PluginUpdateTask.cs | 10 +- .../Updates/InstallationManager.cs | 490 ++++++++++++++++++++ MediaBrowser.ServerApplication/ApplicationHost.cs | 31 +- MediaBrowser.ServerApplication/WebSocketEvents.cs | 22 +- Nuget/MediaBrowser.Common.Internal.nuspec | 4 +- Nuget/MediaBrowser.Common.nuspec | 2 +- Nuget/MediaBrowser.Server.Core.nuspec | 4 +- 15 files changed, 663 insertions(+), 569 deletions(-) create mode 100644 MediaBrowser.Controller/Updates/IInstallationManager.cs delete mode 100644 MediaBrowser.Controller/Updates/InstallationManager.cs create mode 100644 MediaBrowser.Server.Implementations/Updates/InstallationManager.cs diff --git a/MediaBrowser.Api/PackageService.cs b/MediaBrowser.Api/PackageService.cs index 75dffc67b..1c0508c6d 100644 --- a/MediaBrowser.Api/PackageService.cs +++ b/MediaBrowser.Api/PackageService.cs @@ -1,8 +1,7 @@ using MediaBrowser.Common; using MediaBrowser.Common.Extensions; using MediaBrowser.Common.Implementations.HttpServer; -using MediaBrowser.Common.Kernel; -using MediaBrowser.Controller; +using MediaBrowser.Controller.Updates; using MediaBrowser.Model.Updates; using ServiceStack.ServiceHost; using System; @@ -95,11 +94,14 @@ namespace MediaBrowser.Api /// public class PackageService : BaseRestService { - /// - /// Gets or sets the application host. - /// - /// The application host. - public IApplicationHost ApplicationHost { get; set; } + private readonly IInstallationManager _installationManager; + private readonly IApplicationHost _appHost; + + public PackageService(IInstallationManager installationManager, IApplicationHost appHost) + { + _installationManager = installationManager; + _appHost = appHost; + } /// /// Gets the specified request. @@ -113,12 +115,12 @@ namespace MediaBrowser.Api if (request.PackageType == PackageType.UserInstalled || request.PackageType == PackageType.All) { - result.AddRange(Kernel.Instance.InstallationManager.GetAvailablePluginUpdates(false, CancellationToken.None).Result.ToList()); + result.AddRange(_installationManager.GetAvailablePluginUpdates(false, CancellationToken.None).Result.ToList()); } else if (request.PackageType == PackageType.System || request.PackageType == PackageType.All) { - var updateCheckResult = ApplicationHost.CheckForApplicationUpdate(CancellationToken.None, new Progress { }).Result; + var updateCheckResult = _appHost.CheckForApplicationUpdate(CancellationToken.None, new Progress { }).Result; if (updateCheckResult.IsUpdateAvailable) { @@ -136,7 +138,7 @@ namespace MediaBrowser.Api /// System.Object. public object Get(GetPackage request) { - var packages = Kernel.Instance.InstallationManager.GetAvailablePackages(CancellationToken.None, applicationVersion: ApplicationHost.ApplicationVersion).Result; + var packages = _installationManager.GetAvailablePackages(CancellationToken.None, applicationVersion: _appHost.ApplicationVersion).Result; var result = packages.FirstOrDefault(p => p.name.Equals(request.Name, StringComparison.OrdinalIgnoreCase)); @@ -150,7 +152,7 @@ namespace MediaBrowser.Api /// System.Object. public object Get(GetPackages request) { - var packages = Kernel.Instance.InstallationManager.GetAvailablePackages(CancellationToken.None, request.PackageType, ApplicationHost.ApplicationVersion).Result; + var packages = _installationManager.GetAvailablePackages(CancellationToken.None, request.PackageType, _appHost.ApplicationVersion).Result; return ToOptimizedResult(packages.ToList()); } @@ -163,15 +165,15 @@ namespace MediaBrowser.Api public void Post(InstallPackage request) { var package = string.IsNullOrEmpty(request.Version) ? - Kernel.Instance.InstallationManager.GetLatestCompatibleVersion(request.Name, request.UpdateClass).Result : - Kernel.Instance.InstallationManager.GetPackage(request.Name, request.UpdateClass, Version.Parse(request.Version)).Result; + _installationManager.GetLatestCompatibleVersion(request.Name, request.UpdateClass).Result : + _installationManager.GetPackage(request.Name, request.UpdateClass, Version.Parse(request.Version)).Result; if (package == null) { throw new ResourceNotFoundException(string.Format("Package not found: {0}", request.Name)); } - Task.Run(() => Kernel.Instance.InstallationManager.InstallPackage(package, new Progress { }, CancellationToken.None)); + Task.Run(() => _installationManager.InstallPackage(package, new Progress { }, CancellationToken.None)); } /// @@ -180,7 +182,7 @@ namespace MediaBrowser.Api /// The request. public void Delete(CancelPackageInstallation request) { - var info = Kernel.Instance.InstallationManager.CurrentInstallations.FirstOrDefault(i => i.Item1.Id == request.Id); + var info = _installationManager.CurrentInstallations.FirstOrDefault(i => i.Item1.Id == request.Id); if (info != null) { diff --git a/MediaBrowser.Api/PluginService.cs b/MediaBrowser.Api/PluginService.cs index 044f867ab..9409b9d0e 100644 --- a/MediaBrowser.Api/PluginService.cs +++ b/MediaBrowser.Api/PluginService.cs @@ -3,6 +3,7 @@ using MediaBrowser.Common.Extensions; using MediaBrowser.Common.Implementations.HttpServer; using MediaBrowser.Common.Security; using MediaBrowser.Controller; +using MediaBrowser.Controller.Updates; using MediaBrowser.Model.Entities; using MediaBrowser.Model.Plugins; using MediaBrowser.Model.Serialization; @@ -127,6 +128,8 @@ namespace MediaBrowser.Api private readonly ISecurityManager _securityManager; + private readonly IInstallationManager _installationManager; + /// /// Initializes a new instance of the class. /// @@ -134,7 +137,7 @@ namespace MediaBrowser.Api /// The app host. /// The security manager. /// jsonSerializer - public PluginService(IJsonSerializer jsonSerializer, IApplicationHost appHost, ISecurityManager securityManager) + public PluginService(IJsonSerializer jsonSerializer, IApplicationHost appHost, ISecurityManager securityManager, IInstallationManager installationManager) : base() { if (jsonSerializer == null) @@ -144,6 +147,7 @@ namespace MediaBrowser.Api _appHost = appHost; _securityManager = securityManager; + _installationManager = installationManager; _jsonSerializer = jsonSerializer; } @@ -254,7 +258,7 @@ namespace MediaBrowser.Api { var plugin = _appHost.Plugins.First(p => p.Id == request.Id); - Kernel.Instance.InstallationManager.UninstallPlugin(plugin); + _installationManager.UninstallPlugin(plugin); } } } diff --git a/MediaBrowser.Common.Implementations/BaseApplicationHost.cs b/MediaBrowser.Common.Implementations/BaseApplicationHost.cs index 769cac713..24283e998 100644 --- a/MediaBrowser.Common.Implementations/BaseApplicationHost.cs +++ b/MediaBrowser.Common.Implementations/BaseApplicationHost.cs @@ -135,6 +135,8 @@ namespace MediaBrowser.Common.Implementations protected IKernel Kernel { get; private set; } protected ITaskManager TaskManager { get; private set; } protected ISecurityManager SecurityManager { get; private set; } + protected IPackageManager PackageManager { get; private set; } + protected IHttpClient HttpClient { get; private set; } protected IConfigurationManager ConfigurationManager { get; private set; } @@ -249,18 +251,20 @@ namespace MediaBrowser.Common.Implementations RegisterSingleInstance(ProtobufSerializer); RegisterSingleInstance(new UdpServer(Logger), false); - var httpClient = new HttpClientManager.HttpClientManager(ApplicationPaths, Logger); + HttpClient = new HttpClientManager.HttpClientManager(ApplicationPaths, Logger); - RegisterSingleInstance(httpClient); + RegisterSingleInstance(HttpClient); RegisterSingleInstance(networkManager); RegisterSingleInstance(serverManager); - SecurityManager = new PluginSecurityManager(Kernel, httpClient, JsonSerializer, ApplicationPaths); + SecurityManager = new PluginSecurityManager(Kernel, HttpClient, JsonSerializer, ApplicationPaths); RegisterSingleInstance(SecurityManager); - RegisterSingleInstance(new PackageManager(SecurityManager, networkManager, httpClient, ApplicationPaths, JsonSerializer, Logger)); + PackageManager = new PackageManager(SecurityManager, networkManager, HttpClient, ApplicationPaths, JsonSerializer, Logger); + + RegisterSingleInstance(PackageManager); }); } diff --git a/MediaBrowser.Controller/Kernel.cs b/MediaBrowser.Controller/Kernel.cs index 9f97b1719..947fb8e6d 100644 --- a/MediaBrowser.Controller/Kernel.cs +++ b/MediaBrowser.Controller/Kernel.cs @@ -47,12 +47,6 @@ namespace MediaBrowser.Controller /// The FFMPEG controller. public FFMpegManager FFMpegManager { get; private set; } - /// - /// Gets the installation manager. - /// - /// The installation manager. - public InstallationManager InstallationManager { get; set; } - /// /// Gets or sets the file system manager. /// @@ -331,10 +325,12 @@ namespace MediaBrowser.Controller { var info = base.GetSystemInfo(); - if (InstallationManager != null) + var installationManager = ApplicationHost.Resolve(); + + if (installationManager != null) { - info.InProgressInstallations = InstallationManager.CurrentInstallations.Select(i => i.Item1).ToArray(); - info.CompletedInstallations = InstallationManager.CompletedInstallations.ToArray(); + info.InProgressInstallations = installationManager.CurrentInstallations.Select(i => i.Item1).ToArray(); + info.CompletedInstallations = installationManager.CompletedInstallations.ToArray(); } return info; diff --git a/MediaBrowser.Controller/MediaBrowser.Controller.csproj b/MediaBrowser.Controller/MediaBrowser.Controller.csproj index 0fc97b049..bdf464bbf 100644 --- a/MediaBrowser.Controller/MediaBrowser.Controller.csproj +++ b/MediaBrowser.Controller/MediaBrowser.Controller.csproj @@ -185,7 +185,7 @@ - + diff --git a/MediaBrowser.Controller/Updates/IInstallationManager.cs b/MediaBrowser.Controller/Updates/IInstallationManager.cs new file mode 100644 index 000000000..dc6aaf0c9 --- /dev/null +++ b/MediaBrowser.Controller/Updates/IInstallationManager.cs @@ -0,0 +1,106 @@ +using MediaBrowser.Common.Events; +using MediaBrowser.Common.Plugins; +using MediaBrowser.Model.Updates; +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; + +namespace MediaBrowser.Controller.Updates +{ + public interface IInstallationManager : IDisposable + { + event EventHandler> PackageInstalling; + event EventHandler> PackageInstallationCompleted; + event EventHandler> PackageInstallationFailed; + event EventHandler> PackageInstallationCancelled; + + /// + /// The current installations + /// + List> CurrentInstallations { get; set; } + + /// + /// The completed installations + /// + ConcurrentBag CompletedInstallations { get; set; } + + /// + /// Occurs when [plugin uninstalled]. + /// + event EventHandler> PluginUninstalled; + + /// + /// Occurs when [plugin updated]. + /// + event EventHandler>> PluginUpdated; + + /// + /// Occurs when [plugin updated]. + /// + event EventHandler> PluginInstalled; + + /// + /// Gets all available packages. + /// + /// The cancellation token. + /// Type of the package. + /// The application version. + /// Task{List{PackageInfo}}. + Task> GetAvailablePackages(CancellationToken cancellationToken, + PackageType? packageType = null, + Version applicationVersion = null); + + /// + /// Gets the package. + /// + /// The name. + /// The classification. + /// The version. + /// Task{PackageVersionInfo}. + Task GetPackage(string name, PackageVersionClass classification, Version version); + + /// + /// Gets the latest compatible version. + /// + /// The name. + /// The classification. + /// Task{PackageVersionInfo}. + Task GetLatestCompatibleVersion(string name, PackageVersionClass classification = PackageVersionClass.Release); + + /// + /// Gets the latest compatible version. + /// + /// The available packages. + /// The name. + /// The classification. + /// PackageVersionInfo. + PackageVersionInfo GetLatestCompatibleVersion(IEnumerable availablePackages, string name, PackageVersionClass classification = PackageVersionClass.Release); + + /// + /// Gets the available plugin updates. + /// + /// if set to true [with auto update enabled]. + /// The cancellation token. + /// Task{IEnumerable{PackageVersionInfo}}. + Task> GetAvailablePluginUpdates(bool withAutoUpdateEnabled, CancellationToken cancellationToken); + + /// + /// Installs the package. + /// + /// The package. + /// The progress. + /// The cancellation token. + /// Task. + /// package + Task InstallPackage(PackageVersionInfo package, IProgress progress, CancellationToken cancellationToken); + + /// + /// Uninstalls a plugin + /// + /// The plugin. + /// + void UninstallPlugin(IPlugin plugin); + } +} \ No newline at end of file diff --git a/MediaBrowser.Controller/Updates/InstallationManager.cs b/MediaBrowser.Controller/Updates/InstallationManager.cs deleted file mode 100644 index 9a2733ce0..000000000 --- a/MediaBrowser.Controller/Updates/InstallationManager.cs +++ /dev/null @@ -1,494 +0,0 @@ -using MediaBrowser.Common; -using MediaBrowser.Common.Events; -using MediaBrowser.Common.Kernel; -using MediaBrowser.Common.Net; -using MediaBrowser.Common.Plugins; -using MediaBrowser.Common.Progress; -using MediaBrowser.Common.Updates; -using MediaBrowser.Model.IO; -using MediaBrowser.Model.Logging; -using MediaBrowser.Model.Serialization; -using MediaBrowser.Model.Updates; -using System; -using System.Collections.Concurrent; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; - -namespace MediaBrowser.Controller.Updates -{ - /// - /// Manages all install, uninstall and update operations (both plugins and system) - /// - public class InstallationManager : BaseManager - { - public event EventHandler> PackageInstalling; - public event EventHandler> PackageInstallationCompleted; - public event EventHandler> PackageInstallationFailed; - public event EventHandler> PackageInstallationCancelled; - - /// - /// The current installations - /// - public List> CurrentInstallations { get; set; } - - /// - /// The completed installations - /// - public ConcurrentBag CompletedInstallations { get; set; } - - #region PluginUninstalled Event - /// - /// Occurs when [plugin uninstalled]. - /// - public event EventHandler> PluginUninstalled; - - /// - /// Called when [plugin uninstalled]. - /// - /// The plugin. - private void OnPluginUninstalled(IPlugin plugin) - { - EventHelper.QueueEventIfNotNull(PluginUninstalled, this, new GenericEventArgs { Argument = plugin }, _logger); - } - #endregion - - #region PluginUpdated Event - /// - /// Occurs when [plugin updated]. - /// - public event EventHandler>> PluginUpdated; - /// - /// Called when [plugin updated]. - /// - /// The plugin. - /// The new version. - public void OnPluginUpdated(IPlugin plugin, PackageVersionInfo newVersion) - { - _logger.Info("Plugin updated: {0} {1} {2}", newVersion.name, newVersion.version, newVersion.classification); - - EventHelper.QueueEventIfNotNull(PluginUpdated, this, new GenericEventArgs> { Argument = new Tuple(plugin, newVersion) }, _logger); - - Kernel.NotifyPendingRestart(); - } - #endregion - - #region PluginInstalled Event - /// - /// Occurs when [plugin updated]. - /// - public event EventHandler> PluginInstalled; - /// - /// Called when [plugin installed]. - /// - /// The package. - public void OnPluginInstalled(PackageVersionInfo package) - { - _logger.Info("New plugin installed: {0} {1} {2}", package.name, package.version, package.classification); - - EventHelper.QueueEventIfNotNull(PluginInstalled, this, new GenericEventArgs { Argument = package }, _logger); - - Kernel.NotifyPendingRestart(); - } - #endregion - - /// - /// The _logger - /// - private readonly ILogger _logger; - - /// - /// The _network manager - /// - private readonly INetworkManager _networkManager; - - /// - /// The package manager - /// - private readonly IPackageManager _packageManager; - - /// - /// Gets the json serializer. - /// - /// The json serializer. - protected IJsonSerializer JsonSerializer { get; private set; } - - /// - /// Gets the HTTP client. - /// - /// The HTTP client. - protected IHttpClient HttpClient { get; private set; } - - /// - /// Gets the application host. - /// - /// The application host. - protected IApplicationHost ApplicationHost { get; private set; } - - /// - /// Initializes a new instance of the class. - /// - /// The kernel. - /// The HTTP client. - /// The network manager. - /// The package manager. - /// The json serializer. - /// The logger. - /// The app host. - /// zipClient - public InstallationManager(Kernel kernel, IHttpClient httpClient, INetworkManager networkManager, IPackageManager packageManager, IJsonSerializer jsonSerializer, ILogger logger, IApplicationHost appHost) - : base(kernel) - { - if (networkManager == null) - { - throw new ArgumentNullException("networkManager"); - } - if (packageManager == null) - { - throw new ArgumentNullException("packageManager"); - } - if (logger == null) - { - throw new ArgumentNullException("logger"); - } - if (jsonSerializer == null) - { - throw new ArgumentNullException("jsonSerializer"); - } - if (httpClient == null) - { - throw new ArgumentNullException("httpClient"); - } - - CurrentInstallations = new List>(); - CompletedInstallations = new ConcurrentBag(); - JsonSerializer = jsonSerializer; - HttpClient = httpClient; - ApplicationHost = appHost; - _networkManager = networkManager; - _packageManager = packageManager; - _logger = logger; - } - - /// - /// Gets all available packages. - /// - /// The cancellation token. - /// Type of the package. - /// The application version. - /// Task{List{PackageInfo}}. - public async Task> GetAvailablePackages(CancellationToken cancellationToken, - PackageType? packageType = null, - Version applicationVersion = null) - { - var packages = (await _packageManager.GetAvailablePackages(cancellationToken).ConfigureAwait(false)).ToList(); - - if (packageType.HasValue) - { - packages = packages.Where(p => p.type == packageType.Value).ToList(); - } - - // If an app version was supplied, filter the versions for each package to only include supported versions - if (applicationVersion != null) - { - foreach (var package in packages) - { - package.versions = package.versions.Where(v => IsPackageVersionUpToDate(v, applicationVersion)).ToList(); - } - } - - // Remove packages with no versions - packages = packages.Where(p => p.versions.Any()).ToList(); - - return packages; - } - - /// - /// Determines whether [is package version up to date] [the specified package version info]. - /// - /// The package version info. - /// The application version. - /// true if [is package version up to date] [the specified package version info]; otherwise, false. - private bool IsPackageVersionUpToDate(PackageVersionInfo packageVersionInfo, Version applicationVersion) - { - if (string.IsNullOrEmpty(packageVersionInfo.requiredVersionStr)) - { - return true; - } - - Version requiredVersion; - - return Version.TryParse(packageVersionInfo.requiredVersionStr, out requiredVersion) && applicationVersion >= requiredVersion; - } - - /// - /// Gets the package. - /// - /// The name. - /// The classification. - /// The version. - /// Task{PackageVersionInfo}. - public async Task GetPackage(string name, PackageVersionClass classification, Version version) - { - var packages = await GetAvailablePackages(CancellationToken.None).ConfigureAwait(false); - - var package = packages.FirstOrDefault(p => p.name.Equals(name, StringComparison.OrdinalIgnoreCase)); - - if (package == null) - { - return null; - } - - return package.versions.FirstOrDefault(v => v.version.Equals(version) && v.classification == classification); - } - - /// - /// Gets the latest compatible version. - /// - /// The name. - /// The classification. - /// Task{PackageVersionInfo}. - public async Task GetLatestCompatibleVersion(string name, PackageVersionClass classification = PackageVersionClass.Release) - { - var packages = await GetAvailablePackages(CancellationToken.None).ConfigureAwait(false); - - return GetLatestCompatibleVersion(packages, name, classification); - } - - /// - /// Gets the latest compatible version. - /// - /// The available packages. - /// The name. - /// The classification. - /// PackageVersionInfo. - public PackageVersionInfo GetLatestCompatibleVersion(IEnumerable availablePackages, string name, PackageVersionClass classification = PackageVersionClass.Release) - { - var package = availablePackages.FirstOrDefault(p => p.name.Equals(name, StringComparison.OrdinalIgnoreCase)); - - if (package == null) - { - return null; - } - - return package.versions - .OrderByDescending(v => v.version) - .FirstOrDefault(v => v.classification <= classification && IsPackageVersionUpToDate(v, ApplicationHost.ApplicationVersion)); - } - - /// - /// Gets the available plugin updates. - /// - /// if set to true [with auto update enabled]. - /// The cancellation token. - /// Task{IEnumerable{PackageVersionInfo}}. - public async Task> GetAvailablePluginUpdates(bool withAutoUpdateEnabled, CancellationToken cancellationToken) - { - var catalog = await GetAvailablePackages(cancellationToken).ConfigureAwait(false); - - var plugins = ApplicationHost.Plugins; - - if (withAutoUpdateEnabled) - { - plugins = plugins.Where(p => p.Configuration.EnableAutoUpdate); - } - - // Figure out what needs to be installed - return plugins.Select(p => - { - var latestPluginInfo = GetLatestCompatibleVersion(catalog, p.Name, p.Configuration.UpdateClass); - - return latestPluginInfo != null && latestPluginInfo.version > p.Version ? latestPluginInfo : null; - - }).Where(p => !CompletedInstallations.Any(i => i.Name.Equals(p.name, StringComparison.OrdinalIgnoreCase))) - .Where(p => p != null && !string.IsNullOrWhiteSpace(p.sourceUrl)); - } - - /// - /// Installs the package. - /// - /// The package. - /// The progress. - /// The cancellation token. - /// Task. - /// package - public async Task InstallPackage(PackageVersionInfo package, IProgress progress, CancellationToken cancellationToken) - { - if (package == null) - { - throw new ArgumentNullException("package"); - } - - if (progress == null) - { - throw new ArgumentNullException("progress"); - } - - if (cancellationToken == null) - { - throw new ArgumentNullException("cancellationToken"); - } - - var installationInfo = new InstallationInfo - { - Id = Guid.NewGuid(), - Name = package.name, - UpdateClass = package.classification, - Version = package.versionStr - }; - - var innerCancellationTokenSource = new CancellationTokenSource(); - - var tuple = new Tuple(installationInfo, innerCancellationTokenSource); - - // Add it to the in-progress list - lock (CurrentInstallations) - { - CurrentInstallations.Add(tuple); - } - - var innerProgress = new ActionableProgress { }; - - // Whenever the progress updates, update the outer progress object and InstallationInfo - innerProgress.RegisterAction(percent => - { - progress.Report(percent); - - installationInfo.PercentComplete = percent; - }); - - var linkedToken = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, innerCancellationTokenSource.Token).Token; - - EventHelper.QueueEventIfNotNull(PackageInstalling, this, new GenericEventArgs() { Argument = installationInfo }, _logger); - - try - { - await InstallPackageInternal(package, innerProgress, linkedToken).ConfigureAwait(false); - - lock (CurrentInstallations) - { - CurrentInstallations.Remove(tuple); - } - - CompletedInstallations.Add(installationInfo); - - EventHelper.QueueEventIfNotNull(PackageInstallationCompleted, this, new GenericEventArgs() { Argument = installationInfo }, _logger); - } - catch (OperationCanceledException) - { - lock (CurrentInstallations) - { - CurrentInstallations.Remove(tuple); - } - - _logger.Info("Package installation cancelled: {0} {1}", package.name, package.versionStr); - - EventHelper.QueueEventIfNotNull(PackageInstallationCancelled, this, new GenericEventArgs() { Argument = installationInfo }, _logger); - - throw; - } - catch - { - lock (CurrentInstallations) - { - CurrentInstallations.Remove(tuple); - } - - EventHelper.QueueEventIfNotNull(PackageInstallationFailed, this, new GenericEventArgs() { Argument = installationInfo }, _logger); - - throw; - } - finally - { - // Dispose the progress object and remove the installation from the in-progress list - - innerProgress.Dispose(); - tuple.Item2.Dispose(); - } - } - - /// - /// Installs the package internal. - /// - /// The package. - /// The progress. - /// The cancellation token. - /// Task. - private async Task InstallPackageInternal(PackageVersionInfo package, IProgress progress, CancellationToken cancellationToken) - { - // Do the install - await _packageManager.InstallPackage(progress, package, cancellationToken).ConfigureAwait(false); - - // Do plugin-specific processing - if (!(Path.GetExtension(package.targetFilename) ?? "").Equals(".zip", StringComparison.OrdinalIgnoreCase)) - { - // Set last update time if we were installed before - var plugin = ApplicationHost.Plugins.FirstOrDefault(p => p.Name.Equals(package.name, StringComparison.OrdinalIgnoreCase)); - - if (plugin != null) - { - // Synchronize the UpdateClass value - if (plugin.Configuration.UpdateClass != package.classification) - { - plugin.Configuration.UpdateClass = package.classification; - plugin.SaveConfiguration(); - } - - OnPluginUpdated(plugin, package); - } - else - { - OnPluginInstalled(package); - } - - } - } - - /// - /// Uninstalls a plugin - /// - /// The plugin. - /// - public void UninstallPlugin(IPlugin plugin) - { - if (plugin.IsCorePlugin) - { - throw new ArgumentException(string.Format("{0} cannot be uninstalled because it is a core plugin.", plugin.Name)); - } - - plugin.OnUninstalling(); - - // Remove it the quick way for now - ApplicationHost.RemovePlugin(plugin); - - File.Delete(plugin.AssemblyFilePath); - - OnPluginUninstalled(plugin); - - Kernel.NotifyPendingRestart(); - } - - /// - /// Releases unmanaged and - optionally - managed resources. - /// - /// true to release both managed and unmanaged resources; false to release only unmanaged resources. - protected override void Dispose(bool dispose) - { - if (dispose) - { - lock (CurrentInstallations) - { - foreach (var tuple in CurrentInstallations) - { - tuple.Item2.Dispose(); - } - - CurrentInstallations.Clear(); - } - } - base.Dispose(dispose); - } - } -} diff --git a/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj b/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj index fbd6a96b2..45aa3e530 100644 --- a/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj +++ b/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj @@ -88,6 +88,7 @@ + diff --git a/MediaBrowser.Server.Implementations/ScheduledTasks/PluginUpdateTask.cs b/MediaBrowser.Server.Implementations/ScheduledTasks/PluginUpdateTask.cs index 3ae3a727b..42e7fea01 100644 --- a/MediaBrowser.Server.Implementations/ScheduledTasks/PluginUpdateTask.cs +++ b/MediaBrowser.Server.Implementations/ScheduledTasks/PluginUpdateTask.cs @@ -1,5 +1,6 @@ using MediaBrowser.Common.ScheduledTasks; using MediaBrowser.Controller; +using MediaBrowser.Controller.Updates; using MediaBrowser.Model.Logging; using MediaBrowser.Model.Net; using System; @@ -25,15 +26,18 @@ namespace MediaBrowser.Server.Implementations.ScheduledTasks /// private readonly ILogger _logger; + private IInstallationManager _installationManager; + /// /// Initializes a new instance of the class. /// /// The kernel. /// The logger. - public PluginUpdateTask(Kernel kernel, ILogger logger) + public PluginUpdateTask(Kernel kernel, ILogger logger, IInstallationManager installationManager) { _kernel = kernel; _logger = logger; + _installationManager = installationManager; } /// @@ -61,7 +65,7 @@ namespace MediaBrowser.Server.Implementations.ScheduledTasks { progress.Report(0); - var packagesToInstall = (await _kernel.InstallationManager.GetAvailablePluginUpdates(true, cancellationToken).ConfigureAwait(false)).ToList(); + var packagesToInstall = (await _installationManager.GetAvailablePluginUpdates(true, cancellationToken).ConfigureAwait(false)).ToList(); progress.Report(10); @@ -74,7 +78,7 @@ namespace MediaBrowser.Server.Implementations.ScheduledTasks try { - await _kernel.InstallationManager.InstallPackage(i, new Progress { }, cancellationToken).ConfigureAwait(false); + await _installationManager.InstallPackage(i, new Progress { }, cancellationToken).ConfigureAwait(false); } catch (OperationCanceledException) { diff --git a/MediaBrowser.Server.Implementations/Updates/InstallationManager.cs b/MediaBrowser.Server.Implementations/Updates/InstallationManager.cs new file mode 100644 index 000000000..bfba00a8c --- /dev/null +++ b/MediaBrowser.Server.Implementations/Updates/InstallationManager.cs @@ -0,0 +1,490 @@ +using MediaBrowser.Common; +using MediaBrowser.Common.Events; +using MediaBrowser.Common.Kernel; +using MediaBrowser.Common.Net; +using MediaBrowser.Common.Plugins; +using MediaBrowser.Common.Progress; +using MediaBrowser.Common.Updates; +using MediaBrowser.Controller; +using MediaBrowser.Controller.Updates; +using MediaBrowser.Model.Logging; +using MediaBrowser.Model.Serialization; +using MediaBrowser.Model.Updates; +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace MediaBrowser.Server.Implementations.Updates +{ + /// + /// Manages all install, uninstall and update operations (both plugins and system) + /// + public class InstallationManager : IInstallationManager + { + public event EventHandler> PackageInstalling; + public event EventHandler> PackageInstallationCompleted; + public event EventHandler> PackageInstallationFailed; + public event EventHandler> PackageInstallationCancelled; + + /// + /// The current installations + /// + public List> CurrentInstallations { get; set; } + + /// + /// The completed installations + /// + public ConcurrentBag CompletedInstallations { get; set; } + + #region PluginUninstalled Event + /// + /// Occurs when [plugin uninstalled]. + /// + public event EventHandler> PluginUninstalled; + + /// + /// Called when [plugin uninstalled]. + /// + /// The plugin. + private void OnPluginUninstalled(IPlugin plugin) + { + EventHelper.QueueEventIfNotNull(PluginUninstalled, this, new GenericEventArgs { Argument = plugin }, _logger); + } + #endregion + + #region PluginUpdated Event + /// + /// Occurs when [plugin updated]. + /// + public event EventHandler>> PluginUpdated; + /// + /// Called when [plugin updated]. + /// + /// The plugin. + /// The new version. + private void OnPluginUpdated(IPlugin plugin, PackageVersionInfo newVersion) + { + _logger.Info("Plugin updated: {0} {1} {2}", newVersion.name, newVersion.version, newVersion.classification); + + EventHelper.QueueEventIfNotNull(PluginUpdated, this, new GenericEventArgs> { Argument = new Tuple(plugin, newVersion) }, _logger); + + Kernel.NotifyPendingRestart(); + } + #endregion + + #region PluginInstalled Event + /// + /// Occurs when [plugin updated]. + /// + public event EventHandler> PluginInstalled; + /// + /// Called when [plugin installed]. + /// + /// The package. + private void OnPluginInstalled(PackageVersionInfo package) + { + _logger.Info("New plugin installed: {0} {1} {2}", package.name, package.version, package.classification); + + EventHelper.QueueEventIfNotNull(PluginInstalled, this, new GenericEventArgs { Argument = package }, _logger); + + Kernel.NotifyPendingRestart(); + } + #endregion + + /// + /// The _logger + /// + private readonly ILogger _logger; + + /// + /// The package manager + /// + private readonly IPackageManager _packageManager; + + /// + /// Gets the json serializer. + /// + /// The json serializer. + protected IJsonSerializer JsonSerializer { get; private set; } + + /// + /// Gets the HTTP client. + /// + /// The HTTP client. + protected IHttpClient HttpClient { get; private set; } + + /// + /// Gets the application host. + /// + /// The application host. + protected IApplicationHost ApplicationHost { get; private set; } + + private IKernel Kernel { get; set; } + + /// + /// Initializes a new instance of the class. + /// + /// The kernel. + /// The HTTP client. + /// The package manager. + /// The json serializer. + /// The logger. + /// The app host. + /// zipClient + public InstallationManager(IKernel kernel, IHttpClient httpClient, IPackageManager packageManager, IJsonSerializer jsonSerializer, ILogger logger, IApplicationHost appHost) + { + if (packageManager == null) + { + throw new ArgumentNullException("packageManager"); + } + if (logger == null) + { + throw new ArgumentNullException("logger"); + } + if (jsonSerializer == null) + { + throw new ArgumentNullException("jsonSerializer"); + } + if (httpClient == null) + { + throw new ArgumentNullException("httpClient"); + } + + CurrentInstallations = new List>(); + CompletedInstallations = new ConcurrentBag(); + JsonSerializer = jsonSerializer; + HttpClient = httpClient; + ApplicationHost = appHost; + _packageManager = packageManager; + _logger = logger; + Kernel = kernel; + } + + /// + /// Gets all available packages. + /// + /// The cancellation token. + /// Type of the package. + /// The application version. + /// Task{List{PackageInfo}}. + public async Task> GetAvailablePackages(CancellationToken cancellationToken, + PackageType? packageType = null, + Version applicationVersion = null) + { + var packages = (await _packageManager.GetAvailablePackages(cancellationToken).ConfigureAwait(false)).ToList(); + + if (packageType.HasValue) + { + packages = packages.Where(p => p.type == packageType.Value).ToList(); + } + + // If an app version was supplied, filter the versions for each package to only include supported versions + if (applicationVersion != null) + { + foreach (var package in packages) + { + package.versions = package.versions.Where(v => IsPackageVersionUpToDate(v, applicationVersion)).ToList(); + } + } + + // Remove packages with no versions + packages = packages.Where(p => p.versions.Any()).ToList(); + + return packages; + } + + /// + /// Determines whether [is package version up to date] [the specified package version info]. + /// + /// The package version info. + /// The application version. + /// true if [is package version up to date] [the specified package version info]; otherwise, false. + private bool IsPackageVersionUpToDate(PackageVersionInfo packageVersionInfo, Version applicationVersion) + { + if (string.IsNullOrEmpty(packageVersionInfo.requiredVersionStr)) + { + return true; + } + + Version requiredVersion; + + return Version.TryParse(packageVersionInfo.requiredVersionStr, out requiredVersion) && applicationVersion >= requiredVersion; + } + + /// + /// Gets the package. + /// + /// The name. + /// The classification. + /// The version. + /// Task{PackageVersionInfo}. + public async Task GetPackage(string name, PackageVersionClass classification, Version version) + { + var packages = await GetAvailablePackages(CancellationToken.None).ConfigureAwait(false); + + var package = packages.FirstOrDefault(p => p.name.Equals(name, StringComparison.OrdinalIgnoreCase)); + + if (package == null) + { + return null; + } + + return package.versions.FirstOrDefault(v => v.version.Equals(version) && v.classification == classification); + } + + /// + /// Gets the latest compatible version. + /// + /// The name. + /// The classification. + /// Task{PackageVersionInfo}. + public async Task GetLatestCompatibleVersion(string name, PackageVersionClass classification = PackageVersionClass.Release) + { + var packages = await GetAvailablePackages(CancellationToken.None).ConfigureAwait(false); + + return GetLatestCompatibleVersion(packages, name, classification); + } + + /// + /// Gets the latest compatible version. + /// + /// The available packages. + /// The name. + /// The classification. + /// PackageVersionInfo. + public PackageVersionInfo GetLatestCompatibleVersion(IEnumerable availablePackages, string name, PackageVersionClass classification = PackageVersionClass.Release) + { + var package = availablePackages.FirstOrDefault(p => p.name.Equals(name, StringComparison.OrdinalIgnoreCase)); + + if (package == null) + { + return null; + } + + return package.versions + .OrderByDescending(v => v.version) + .FirstOrDefault(v => v.classification <= classification && IsPackageVersionUpToDate(v, ApplicationHost.ApplicationVersion)); + } + + /// + /// Gets the available plugin updates. + /// + /// if set to true [with auto update enabled]. + /// The cancellation token. + /// Task{IEnumerable{PackageVersionInfo}}. + public async Task> GetAvailablePluginUpdates(bool withAutoUpdateEnabled, CancellationToken cancellationToken) + { + var catalog = await GetAvailablePackages(cancellationToken).ConfigureAwait(false); + + var plugins = ApplicationHost.Plugins; + + if (withAutoUpdateEnabled) + { + plugins = plugins.Where(p => p.Configuration.EnableAutoUpdate); + } + + // Figure out what needs to be installed + return plugins.Select(p => + { + var latestPluginInfo = GetLatestCompatibleVersion(catalog, p.Name, p.Configuration.UpdateClass); + + return latestPluginInfo != null && latestPluginInfo.version > p.Version ? latestPluginInfo : null; + + }).Where(p => !CompletedInstallations.Any(i => i.Name.Equals(p.name, StringComparison.OrdinalIgnoreCase))) + .Where(p => p != null && !string.IsNullOrWhiteSpace(p.sourceUrl)); + } + + /// + /// Installs the package. + /// + /// The package. + /// The progress. + /// The cancellation token. + /// Task. + /// package + public async Task InstallPackage(PackageVersionInfo package, IProgress progress, CancellationToken cancellationToken) + { + if (package == null) + { + throw new ArgumentNullException("package"); + } + + if (progress == null) + { + throw new ArgumentNullException("progress"); + } + + if (cancellationToken == null) + { + throw new ArgumentNullException("cancellationToken"); + } + + var installationInfo = new InstallationInfo + { + Id = Guid.NewGuid(), + Name = package.name, + UpdateClass = package.classification, + Version = package.versionStr + }; + + var innerCancellationTokenSource = new CancellationTokenSource(); + + var tuple = new Tuple(installationInfo, innerCancellationTokenSource); + + // Add it to the in-progress list + lock (CurrentInstallations) + { + CurrentInstallations.Add(tuple); + } + + var innerProgress = new ActionableProgress { }; + + // Whenever the progress updates, update the outer progress object and InstallationInfo + innerProgress.RegisterAction(percent => + { + progress.Report(percent); + + installationInfo.PercentComplete = percent; + }); + + var linkedToken = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, innerCancellationTokenSource.Token).Token; + + EventHelper.QueueEventIfNotNull(PackageInstalling, this, new GenericEventArgs() { Argument = installationInfo }, _logger); + + try + { + await InstallPackageInternal(package, innerProgress, linkedToken).ConfigureAwait(false); + + lock (CurrentInstallations) + { + CurrentInstallations.Remove(tuple); + } + + CompletedInstallations.Add(installationInfo); + + EventHelper.QueueEventIfNotNull(PackageInstallationCompleted, this, new GenericEventArgs() { Argument = installationInfo }, _logger); + } + catch (OperationCanceledException) + { + lock (CurrentInstallations) + { + CurrentInstallations.Remove(tuple); + } + + _logger.Info("Package installation cancelled: {0} {1}", package.name, package.versionStr); + + EventHelper.QueueEventIfNotNull(PackageInstallationCancelled, this, new GenericEventArgs() { Argument = installationInfo }, _logger); + + throw; + } + catch + { + lock (CurrentInstallations) + { + CurrentInstallations.Remove(tuple); + } + + EventHelper.QueueEventIfNotNull(PackageInstallationFailed, this, new GenericEventArgs() { Argument = installationInfo }, _logger); + + throw; + } + finally + { + // Dispose the progress object and remove the installation from the in-progress list + + innerProgress.Dispose(); + tuple.Item2.Dispose(); + } + } + + /// + /// Installs the package internal. + /// + /// The package. + /// The progress. + /// The cancellation token. + /// Task. + private async Task InstallPackageInternal(PackageVersionInfo package, IProgress progress, CancellationToken cancellationToken) + { + // Do the install + await _packageManager.InstallPackage(progress, package, cancellationToken).ConfigureAwait(false); + + // Do plugin-specific processing + if (!(Path.GetExtension(package.targetFilename) ?? "").Equals(".zip", StringComparison.OrdinalIgnoreCase)) + { + // Set last update time if we were installed before + var plugin = ApplicationHost.Plugins.FirstOrDefault(p => p.Name.Equals(package.name, StringComparison.OrdinalIgnoreCase)); + + if (plugin != null) + { + // Synchronize the UpdateClass value + if (plugin.Configuration.UpdateClass != package.classification) + { + plugin.Configuration.UpdateClass = package.classification; + plugin.SaveConfiguration(); + } + + OnPluginUpdated(plugin, package); + } + else + { + OnPluginInstalled(package); + } + + } + } + + /// + /// Uninstalls a plugin + /// + /// The plugin. + /// + public void UninstallPlugin(IPlugin plugin) + { + if (plugin.IsCorePlugin) + { + throw new ArgumentException(string.Format("{0} cannot be uninstalled because it is a core plugin.", plugin.Name)); + } + + plugin.OnUninstalling(); + + // Remove it the quick way for now + ApplicationHost.RemovePlugin(plugin); + + File.Delete(plugin.AssemblyFilePath); + + OnPluginUninstalled(plugin); + + Kernel.NotifyPendingRestart(); + } + + /// + /// Releases unmanaged and - optionally - managed resources. + /// + /// true to release both managed and unmanaged resources; false to release only unmanaged resources. + protected virtual void Dispose(bool dispose) + { + if (dispose) + { + lock (CurrentInstallations) + { + foreach (var tuple in CurrentInstallations) + { + tuple.Item2.Dispose(); + } + + CurrentInstallations.Clear(); + } + } + } + + public void Dispose() + { + Dispose(true); + } + } +} diff --git a/MediaBrowser.ServerApplication/ApplicationHost.cs b/MediaBrowser.ServerApplication/ApplicationHost.cs index 80ed5bea7..d22c522e7 100644 --- a/MediaBrowser.ServerApplication/ApplicationHost.cs +++ b/MediaBrowser.ServerApplication/ApplicationHost.cs @@ -25,6 +25,7 @@ using MediaBrowser.Server.Implementations; using MediaBrowser.Server.Implementations.BdInfo; using MediaBrowser.Server.Implementations.Configuration; using MediaBrowser.Server.Implementations.Library; +using MediaBrowser.Server.Implementations.Updates; using MediaBrowser.ServerApplication.Implementations; using MediaBrowser.WebDashboard.Api; using System; @@ -42,11 +43,6 @@ namespace MediaBrowser.ServerApplication /// public class ApplicationHost : BaseApplicationHost { - /// - /// The _web socket events - /// - private WebSocketEvents _webSocketEvents; - /// /// Gets the server kernel. /// @@ -114,6 +110,8 @@ namespace MediaBrowser.ServerApplication RegisterSingleInstance(userManager); RegisterSingleInstance(new LibraryManager(ServerKernel, Logger, TaskManager, userManager, ServerConfigurationManager)); + + RegisterSingleInstance(new InstallationManager(Kernel, HttpClient, PackageManager, JsonSerializer, Logger, this)); } /// @@ -124,10 +122,6 @@ namespace MediaBrowser.ServerApplication base.FindParts(); Resolve().AddParts(GetExports(), GetExports(), GetExports(), GetExports()); - - ServerKernel.InstallationManager = (InstallationManager)CreateInstance(typeof(InstallationManager)); - - _webSocketEvents = new WebSocketEvents(Resolve(), Resolve(), Resolve(), Resolve(), Resolve(), ServerKernel.InstallationManager); } /// @@ -157,7 +151,7 @@ namespace MediaBrowser.ServerApplication { var pkgManager = Resolve(); var availablePackages = await pkgManager.GetAvailablePackages(CancellationToken.None).ConfigureAwait(false); - var version = ServerKernel.InstallationManager.GetLatestCompatibleVersion(availablePackages, Constants.MBServerPkgName, ConfigurationManager.CommonConfiguration.SystemUpdateLevel); + var version = Resolve().GetLatestCompatibleVersion(availablePackages, Constants.MBServerPkgName, ConfigurationManager.CommonConfiguration.SystemUpdateLevel); return version != null ? new CheckForUpdateResult { AvailableVersion = version.version, IsUpdateAvailable = version.version > ApplicationVersion, Package = version } : new CheckForUpdateResult { AvailableVersion = ApplicationVersion, IsUpdateAvailable = false }; @@ -223,22 +217,5 @@ namespace MediaBrowser.ServerApplication { App.Instance.Dispatcher.Invoke(App.Instance.Shutdown); } - - /// - /// Releases unmanaged and - optionally - managed resources. - /// - /// true to release both managed and unmanaged resources; false to release only unmanaged resources. - protected override void Dispose(bool dispose) - { - if (dispose) - { - if (_webSocketEvents != null) - { - _webSocketEvents.Dispose(); - } - } - - base.Dispose(dispose); - } } } diff --git a/MediaBrowser.ServerApplication/WebSocketEvents.cs b/MediaBrowser.ServerApplication/WebSocketEvents.cs index 6ffd077f8..4d6720869 100644 --- a/MediaBrowser.ServerApplication/WebSocketEvents.cs +++ b/MediaBrowser.ServerApplication/WebSocketEvents.cs @@ -3,6 +3,7 @@ using MediaBrowser.Common.Kernel; using MediaBrowser.Common.Plugins; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Plugins; using MediaBrowser.Controller.Updates; using MediaBrowser.Model.Logging; using MediaBrowser.Model.Updates; @@ -13,7 +14,7 @@ namespace MediaBrowser.ServerApplication /// /// Class WebSocketEvents /// - public class WebSocketEvents : IDisposable + public class WebSocketEvents : IServerEntryPoint, IDisposable { /// /// The _server manager @@ -37,7 +38,7 @@ namespace MediaBrowser.ServerApplication /// /// The _installation manager /// - private readonly InstallationManager _installationManager; + private readonly IInstallationManager _installationManager; /// /// The _kernel @@ -50,7 +51,7 @@ namespace MediaBrowser.ServerApplication /// The server manager. /// The logger. /// The user manager. - public WebSocketEvents(IServerManager serverManager, IKernel kernel, ILogger logger, IUserManager userManager, ILibraryManager libraryManager, InstallationManager installationManager) + public WebSocketEvents(IServerManager serverManager, IKernel kernel, ILogger logger, IUserManager userManager, ILibraryManager libraryManager, IInstallationManager installationManager) { _serverManager = serverManager; _logger = logger; @@ -58,19 +59,22 @@ namespace MediaBrowser.ServerApplication _libraryManager = libraryManager; _installationManager = installationManager; _kernel = kernel; + } + public void Run() + { _userManager.UserDeleted += userManager_UserDeleted; _userManager.UserUpdated += userManager_UserUpdated; _libraryManager.LibraryChanged += libraryManager_LibraryChanged; - kernel.HasPendingRestartChanged += kernel_HasPendingRestartChanged; + _kernel.HasPendingRestartChanged += kernel_HasPendingRestartChanged; - installationManager.PluginUninstalled += InstallationManager_PluginUninstalled; - installationManager.PackageInstalling += installationManager_PackageInstalling; - installationManager.PackageInstallationCancelled += installationManager_PackageInstallationCancelled; - installationManager.PackageInstallationCompleted += installationManager_PackageInstallationCompleted; - installationManager.PackageInstallationFailed += installationManager_PackageInstallationFailed; + _installationManager.PluginUninstalled += InstallationManager_PluginUninstalled; + _installationManager.PackageInstalling += installationManager_PackageInstalling; + _installationManager.PackageInstallationCancelled += installationManager_PackageInstallationCancelled; + _installationManager.PackageInstallationCompleted += installationManager_PackageInstallationCompleted; + _installationManager.PackageInstallationFailed += installationManager_PackageInstallationFailed; } /// diff --git a/Nuget/MediaBrowser.Common.Internal.nuspec b/Nuget/MediaBrowser.Common.Internal.nuspec index f7253b72e..4976c7421 100644 --- a/Nuget/MediaBrowser.Common.Internal.nuspec +++ b/Nuget/MediaBrowser.Common.Internal.nuspec @@ -2,7 +2,7 @@ MediaBrowser.Common.Internal - 3.0.31 + 3.0.32 MediaBrowser.Common.Internal Luke ebr,Luke,scottisafool @@ -12,7 +12,7 @@ Contains common components shared by Media Browser Theatre and Media Browser Server. Not intended for plugin developer consumption. Copyright © Media Browser 2013 - + diff --git a/Nuget/MediaBrowser.Common.nuspec b/Nuget/MediaBrowser.Common.nuspec index 01b27a0cb..0858500bf 100644 --- a/Nuget/MediaBrowser.Common.nuspec +++ b/Nuget/MediaBrowser.Common.nuspec @@ -2,7 +2,7 @@ MediaBrowser.Common - 3.0.31 + 3.0.32 MediaBrowser.Common Media Browser Team ebr,Luke,scottisafool diff --git a/Nuget/MediaBrowser.Server.Core.nuspec b/Nuget/MediaBrowser.Server.Core.nuspec index ffcd539f5..84c2cd69b 100644 --- a/Nuget/MediaBrowser.Server.Core.nuspec +++ b/Nuget/MediaBrowser.Server.Core.nuspec @@ -2,7 +2,7 @@ MediaBrowser.Server.Core - 3.0.31 + 3.0.32 Media Browser.Server.Core Media Browser Team ebr,Luke,scottisafool @@ -12,7 +12,7 @@ Contains core components required to build plugins for Media Browser Server. Copyright © Media Browser 2013 - + -- cgit v1.2.3