using MediaBrowser.Common; using MediaBrowser.Common.Extensions; using MediaBrowser.Common.Net; using MediaBrowser.Common.Security; using MediaBrowser.Common.Updates; using MediaBrowser.Controller.Devices; using MediaBrowser.Controller.Net; using MediaBrowser.Model.Entities; using MediaBrowser.Model.Plugins; using MediaBrowser.Model.Registration; using MediaBrowser.Model.Serialization; using ServiceStack; using ServiceStack.Web; using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Threading; using System.Threading.Tasks; namespace MediaBrowser.Api { /// /// Class Plugins /// [Route("/Plugins", "GET", Summary = "Gets a list of currently installed plugins")] [Authenticated] public class GetPlugins : IReturn> { public bool? IsAppStoreEnabled { get; set; } } /// /// Class UninstallPlugin /// [Route("/Plugins/{Id}", "DELETE", Summary = "Uninstalls a plugin")] [Authenticated(Roles = "Admin")] public class UninstallPlugin : IReturnVoid { /// /// Gets or sets the id. /// /// The id. [ApiMember(Name = "Id", Description = "Plugin Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "DELETE")] public string Id { get; set; } } /// /// Class GetPluginConfiguration /// [Route("/Plugins/{Id}/Configuration", "GET", Summary = "Gets a plugin's configuration")] [Authenticated] public class GetPluginConfiguration { /// /// Gets or sets the id. /// /// The id. [ApiMember(Name = "Id", Description = "Plugin Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] public string Id { get; set; } } /// /// Class UpdatePluginConfiguration /// [Route("/Plugins/{Id}/Configuration", "POST", Summary = "Updates a plugin's configuration")] [Authenticated] public class UpdatePluginConfiguration : IRequiresRequestStream, IReturnVoid { /// /// Gets or sets the id. /// /// The id. [ApiMember(Name = "Id", Description = "Plugin Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] public string Id { get; set; } /// /// The raw Http Request Input Stream /// /// The request stream. public Stream RequestStream { get; set; } } /// /// Class GetPluginSecurityInfo /// [Route("/Plugins/SecurityInfo", "GET", Summary = "Gets plugin registration information")] [Authenticated] public class GetPluginSecurityInfo : IReturn { } /// /// Class UpdatePluginSecurityInfo /// [Route("/Plugins/SecurityInfo", "POST", Summary = "Updates plugin registration information")] [Authenticated(Roles = "Admin")] public class UpdatePluginSecurityInfo : PluginSecurityInfo, IReturnVoid { } [Route("/Plugins/RegistrationRecords/{Name}", "GET", Summary = "Gets registration status for a feature")] [Authenticated] public class GetRegistrationStatus { [ApiMember(Name = "Name", Description = "Feature Name", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] public string Name { get; set; } [ApiMember(Name = "Mb2Equivalent", Description = "Optional. The equivalent feature name in MB2", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] public string Mb2Equivalent { get; set; } } [Route("/Registrations/{Name}", "GET", Summary = "Gets registration status for a feature")] [Authenticated] public class GetRegistration : IReturn { [ApiMember(Name = "Name", Description = "Feature Name", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] public string Name { get; set; } } [Route("/Appstore/Register", "POST", Summary = "Registers an appstore sale")] [Authenticated] public class RegisterAppstoreSale { [ApiMember(Name = "Store", Description = "Store Name", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "POST")] public string Store { get; set; } [ApiMember(Name = "Application", Description = "Application id", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "POST")] public string Application { get; set; } [ApiMember(Name = "Product", Description = "Product id", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "POST")] public string Product { get; set; } [ApiMember(Name = "Type", Description = "Type of product (Product or Subscription)", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "POST")] public string Type { get; set; } [ApiMember(Name = "StoreId", Description = "Store User Id (if needed)", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "POST")] public string StoreId { get; set; } [ApiMember(Name = "StoreToken", Description = "Unique ID for this purchase in the store", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "POST")] public string StoreToken { get; set; } [ApiMember(Name = "Feature", Description = "Emby Feature Id", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "POST")] public string Feature { get; set; } [ApiMember(Name = "Email", Description = "Email address for purchase", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "POST")] public string Email { get; set; } [ApiMember(Name = "Amount", Description = "String representation of price (can have currency sign)", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "POST")] public string Amount { get; set; } } /// /// Class PluginsService /// public class PluginService : BaseApiService { /// /// The _json serializer /// private readonly IJsonSerializer _jsonSerializer; /// /// The _app host /// private readonly IApplicationHost _appHost; private readonly ISecurityManager _securityManager; private readonly IInstallationManager _installationManager; private readonly INetworkManager _network; private readonly IDeviceManager _deviceManager; public PluginService(IJsonSerializer jsonSerializer, IApplicationHost appHost, ISecurityManager securityManager, IInstallationManager installationManager, INetworkManager network, IDeviceManager deviceManager) : base() { if (jsonSerializer == null) { throw new ArgumentNullException("jsonSerializer"); } _appHost = appHost; _securityManager = securityManager; _installationManager = installationManager; _network = network; _deviceManager = deviceManager; _jsonSerializer = jsonSerializer; } /// /// Gets the specified request. /// /// The request. /// System.Object. public async Task Get(GetRegistrationStatus request) { var result = await _securityManager.GetRegistrationStatus(request.Name, request.Mb2Equivalent).ConfigureAwait(false); return ToOptimizedResult(result); } public async Task Get(GetRegistration request) { var result = await _securityManager.GetRegistrationStatus(request.Name).ConfigureAwait(false); var info = new RegistrationInfo { ExpirationDate = result.ExpirationDate, IsRegistered = result.IsRegistered, IsTrial = result.TrialVersion, Name = request.Name }; return ToOptimizedResult(info); } /// /// Gets the specified request. /// /// The request. /// System.Object. public async Task Get(GetPlugins request) { var result = _appHost.Plugins.OrderBy(p => p.Name).Select(p => p.GetPluginInfo()).ToList(); var requireAppStoreEnabled = request.IsAppStoreEnabled.HasValue && request.IsAppStoreEnabled.Value; // Don't fail just on account of image url's try { var packages = (await _installationManager.GetAvailablePackagesWithoutRegistrationInfo(CancellationToken.None)) .ToList(); foreach (var plugin in result) { var pkg = packages.FirstOrDefault(i => !string.IsNullOrWhiteSpace(i.guid) && new Guid(plugin.Id).Equals(new Guid(i.guid))); if (pkg != null) { plugin.ImageUrl = pkg.thumbImage; } } if (requireAppStoreEnabled) { result = result .Where(plugin => { var pkg = packages.FirstOrDefault(i => !string.IsNullOrWhiteSpace(i.guid) && new Guid(plugin.Id).Equals(new Guid(i.guid))); return pkg != null && pkg.enableInAppStore; }) .ToList(); } } catch { // Play it safe here if (requireAppStoreEnabled) { result = new List(); } } return ToOptimizedSerializedResultUsingCache(result); } /// /// Gets the specified request. /// /// The request. /// System.Object. public object Get(GetPluginConfiguration request) { var guid = new Guid(request.Id); var plugin = _appHost.Plugins.First(p => p.Id == guid); var dateModified = plugin.ConfigurationDateLastModified; var cacheKey = (plugin.Version.ToString() + dateModified.Ticks).GetMD5(); return ToOptimizedResultUsingCache(cacheKey, dateModified, null, () => plugin.Configuration); } /// /// Gets the specified request. /// /// The request. /// System.Object. public object Get(GetPluginSecurityInfo request) { var result = new PluginSecurityInfo { IsMBSupporter = _securityManager.IsMBSupporter, SupporterKey = _securityManager.SupporterKey }; return ToOptimizedSerializedResultUsingCache(result); } public async Task Post(RegisterAppstoreSale request) { var success = await _securityManager.RegisterAppStoreSale(request.Store, request.Application, request.Product, request.Feature, request.Type, request.StoreId, request.StoreToken, request.Email, request.Amount); if (!success) throw new ApplicationException("Error registering store sale"); } /// /// Posts the specified request. /// /// The request. public void Post(UpdatePluginSecurityInfo request) { var info = request; _securityManager.SupporterKey = info.SupporterKey; } /// /// Posts the specified request. /// /// The request. public void Post(UpdatePluginConfiguration request) { // We need to parse this manually because we told service stack not to with IRequiresRequestStream // https://code.google.com/p/servicestack/source/browse/trunk/Common/ServiceStack.Text/ServiceStack.Text/Controller/PathInfo.cs var id = new Guid(GetPathValue(1)); var plugin = _appHost.Plugins.First(p => p.Id == id); var configuration = _jsonSerializer.DeserializeFromStream(request.RequestStream, plugin.ConfigurationType) as BasePluginConfiguration; plugin.UpdateConfiguration(configuration); } /// /// Deletes the specified request. /// /// The request. public void Delete(UninstallPlugin request) { var guid = new Guid(request.Id); var plugin = _appHost.Plugins.First(p => p.Id == guid); _installationManager.UninstallPlugin(plugin); } } }