From dee2fd5f880622ff11177fd2f251ed7e80155192 Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Sun, 24 Nov 2013 18:37:38 -0500 Subject: [PATCH] allow editing of channel images in the web client --- MediaBrowser.Api/Images/ImageService.cs | 21 ++++ MediaBrowser.Api/ItemUpdateService.cs | 41 ++++++- MediaBrowser.Controller/LiveTv/Channel.cs | 8 ++ MediaBrowser.Model/LiveTv/ChannelInfoDto.cs | 6 + .../LiveTv/ChannelProviderFromXml.cs | 104 ++++++++++++++++++ .../MediaBrowser.Providers.csproj | 2 + .../Savers/ChannelXmlSaver.cs | 74 +++++++++++++ .../LiveTv/LiveTvManager.cs | 3 +- .../Api/DashboardService.cs | 1 + MediaBrowser.WebDashboard/ApiClient.js | 55 ++++++++- .../MediaBrowser.WebDashboard.csproj | 8 +- MediaBrowser.WebDashboard/packages.config | 2 +- 12 files changed, 315 insertions(+), 10 deletions(-) create mode 100644 MediaBrowser.Providers/LiveTv/ChannelProviderFromXml.cs create mode 100644 MediaBrowser.Providers/Savers/ChannelXmlSaver.cs diff --git a/MediaBrowser.Api/Images/ImageService.cs b/MediaBrowser.Api/Images/ImageService.cs index 5234dda187..e5603967b1 100644 --- a/MediaBrowser.Api/Images/ImageService.cs +++ b/MediaBrowser.Api/Images/ImageService.cs @@ -38,6 +38,18 @@ namespace MediaBrowser.Api.Images public string Id { get; set; } } + [Route("/LiveTv/Channels/{Id}/Images", "GET")] + [Api(Description = "Gets information about an item's images")] + public class GetChannelImageInfos : IReturn> + { + /// + /// Gets or sets the id. + /// + /// The id. + [ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] + public string Id { get; set; } + } + [Route("/Artists/{Name}/Images", "GET")] [Route("/Genres/{Name}/Images", "GET")] [Route("/GameGenres/{Name}/Images", "GET")] @@ -386,6 +398,15 @@ namespace MediaBrowser.Api.Images return ToOptimizedResult(result); } + public object Get(GetChannelImageInfos request) + { + var item = _liveTv.GetChannel(request.Id); + + var result = GetItemImageInfos(item); + + return ToOptimizedResult(result); + } + public object Get(GetItemByNameImageInfos request) { var result = GetItemByNameImageInfos(request); diff --git a/MediaBrowser.Api/ItemUpdateService.cs b/MediaBrowser.Api/ItemUpdateService.cs index 10149906c9..95876f1a5b 100644 --- a/MediaBrowser.Api/ItemUpdateService.cs +++ b/MediaBrowser.Api/ItemUpdateService.cs @@ -3,6 +3,7 @@ using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.Audio; using MediaBrowser.Controller.Entities.TV; using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.LiveTv; using MediaBrowser.Model.Dto; using MediaBrowser.Model.Entities; using ServiceStack.ServiceHost; @@ -13,6 +14,14 @@ using System.Threading.Tasks; namespace MediaBrowser.Api { + [Route("/LiveTv/Channels/{ChannelId}", "POST")] + [Api(("Updates an item"))] + public class UpdateChannel : BaseItemDto, IReturnVoid + { + [ApiMember(Name = "ChannelId", Description = "The id of the channel", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] + public string ChannelId { get; set; } + } + [Route("/Items/{ItemId}", "POST")] [Api(("Updates an item"))] public class UpdateItem : BaseItemDto, IReturnVoid @@ -73,11 +82,13 @@ namespace MediaBrowser.Api { private readonly ILibraryManager _libraryManager; private readonly IDtoService _dtoService; + private readonly ILiveTvManager _liveTv; - public ItemUpdateService(ILibraryManager libraryManager, IDtoService dtoService) + public ItemUpdateService(ILibraryManager libraryManager, IDtoService dtoService, ILiveTvManager liveTv) { _libraryManager = libraryManager; _dtoService = dtoService; + _liveTv = liveTv; } public void Post(UpdateItem request) @@ -87,6 +98,13 @@ namespace MediaBrowser.Api Task.WaitAll(task); } + public void Post(UpdateChannel request) + { + var task = UpdateItem(request); + + Task.WaitAll(task); + } + private Task UpdateItem(UpdateItem request) { var item = _dtoService.GetItemByDtoId(request.ItemId); @@ -112,6 +130,15 @@ namespace MediaBrowser.Api await _libraryManager.UpdateItem(item, ItemUpdateType.MetadataEdit, CancellationToken.None).ConfigureAwait(false); } + private async Task UpdateItem(UpdateChannel request) + { + var item = _liveTv.GetChannel(request.Id); + + UpdateItem(request, item); + + await _libraryManager.UpdateItem(item, ItemUpdateType.MetadataEdit, CancellationToken.None).ConfigureAwait(false); + } + public void Post(UpdateArtist request) { var task = UpdateItem(request); @@ -226,8 +253,16 @@ namespace MediaBrowser.Api item.Overview = request.Overview; item.Genres = request.Genres; item.Tags = request.Tags; - item.Studios = request.Studios.Select(x => x.Name).ToList(); - item.People = request.People.Select(x => new PersonInfo { Name = x.Name, Role = x.Role, Type = x.Type }).ToList(); + + if (request.Studios != null) + { + item.Studios = request.Studios.Select(x => x.Name).ToList(); + } + + if (request.People != null) + { + item.People = request.People.Select(x => new PersonInfo { Name = x.Name, Role = x.Role, Type = x.Type }).ToList(); + } if (request.DateCreated.HasValue) { diff --git a/MediaBrowser.Controller/LiveTv/Channel.cs b/MediaBrowser.Controller/LiveTv/Channel.cs index 26245e6fb7..94d76971c3 100644 --- a/MediaBrowser.Controller/LiveTv/Channel.cs +++ b/MediaBrowser.Controller/LiveTv/Channel.cs @@ -61,5 +61,13 @@ namespace MediaBrowser.Controller.LiveTv return number.ToString("000-") + (Name ?? string.Empty); } + + public override string MediaType + { + get + { + return ChannelType == ChannelType.Radio ? Model.Entities.MediaType.Audio : Model.Entities.MediaType.Video; + } + } } } diff --git a/MediaBrowser.Model/LiveTv/ChannelInfoDto.cs b/MediaBrowser.Model/LiveTv/ChannelInfoDto.cs index 8e4c958701..fe242b66cd 100644 --- a/MediaBrowser.Model/LiveTv/ChannelInfoDto.cs +++ b/MediaBrowser.Model/LiveTv/ChannelInfoDto.cs @@ -54,5 +54,11 @@ namespace MediaBrowser.Model.LiveTv /// /// The type. public string Type { get; set; } + + /// + /// Gets or sets the type of the media. + /// + /// The type of the media. + public string MediaType { get; set; } } } diff --git a/MediaBrowser.Providers/LiveTv/ChannelProviderFromXml.cs b/MediaBrowser.Providers/LiveTv/ChannelProviderFromXml.cs new file mode 100644 index 0000000000..b166750b56 --- /dev/null +++ b/MediaBrowser.Providers/LiveTv/ChannelProviderFromXml.cs @@ -0,0 +1,104 @@ +using MediaBrowser.Common.IO; +using MediaBrowser.Controller.Configuration; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.LiveTv; +using MediaBrowser.Controller.Providers; +using MediaBrowser.Model.Logging; +using System; +using System.IO; +using System.Threading; +using System.Threading.Tasks; + +namespace MediaBrowser.Providers.LiveTv +{ + class ChannelProviderFromXml : BaseMetadataProvider + { + internal static ChannelProviderFromXml Current { get; private set; } + private readonly IFileSystem _fileSystem; + + public ChannelProviderFromXml(ILogManager logManager, IServerConfigurationManager configurationManager, IFileSystem fileSystem) + : base(logManager, configurationManager) + { + _fileSystem = fileSystem; + Current = this; + } + + /// + /// Supportses the specified item. + /// + /// The item. + /// true if XXXX, false otherwise + public override bool Supports(BaseItem item) + { + return item is Channel; + } + + /// + /// Gets the priority. + /// + /// The priority. + public override MetadataProviderPriority Priority + { + get { return MetadataProviderPriority.Second; } + } + + private const string XmlFileName = "channel.xml"; + protected override bool NeedsRefreshBasedOnCompareDate(BaseItem item, BaseProviderInfo providerInfo) + { + var xml = item.ResolveArgs.GetMetaFileByPath(Path.Combine(item.MetaLocation, XmlFileName)); + + if (xml == null) + { + return false; + } + + return _fileSystem.GetLastWriteTimeUtc(xml) > providerInfo.LastRefreshed; + } + + /// + /// Fetches metadata and returns true or false indicating if any work that requires persistence was done + /// + /// The item. + /// if set to true [force]. + /// The cancellation token. + /// Task{System.Boolean}. + public override Task FetchAsync(BaseItem item, bool force, CancellationToken cancellationToken) + { + return Fetch(item, cancellationToken); + } + + /// + /// Fetches the specified item. + /// + /// The item. + /// The cancellation token. + /// true if XXXX, false otherwise + private async Task Fetch(BaseItem item, CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + + var metadataFile = item.ResolveArgs.GetMetaFileByPath(Path.Combine(item.MetaLocation, XmlFileName)); + + if (metadataFile != null) + { + var path = metadataFile.FullName; + + await XmlParsingResourcePool.WaitAsync(cancellationToken).ConfigureAwait(false); + + try + { + new BaseItemXmlParser(Logger).Fetch((Channel)item, path, cancellationToken); + } + finally + { + XmlParsingResourcePool.Release(); + } + + SetLastRefreshed(item, DateTime.UtcNow); + return true; + } + + return false; + } + } +} diff --git a/MediaBrowser.Providers/MediaBrowser.Providers.csproj b/MediaBrowser.Providers/MediaBrowser.Providers.csproj index 46bce4909a..83e0246c53 100644 --- a/MediaBrowser.Providers/MediaBrowser.Providers.csproj +++ b/MediaBrowser.Providers/MediaBrowser.Providers.csproj @@ -55,6 +55,7 @@ + @@ -100,6 +101,7 @@ + diff --git a/MediaBrowser.Providers/Savers/ChannelXmlSaver.cs b/MediaBrowser.Providers/Savers/ChannelXmlSaver.cs new file mode 100644 index 0000000000..c6ac220a9e --- /dev/null +++ b/MediaBrowser.Providers/Savers/ChannelXmlSaver.cs @@ -0,0 +1,74 @@ +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.LiveTv; +using MediaBrowser.Providers.LiveTv; +using System; +using System.Collections.Generic; +using System.IO; +using System.Text; +using System.Threading; + +namespace MediaBrowser.Providers.Savers +{ + /// + /// Class PersonXmlSaver + /// + public class ChannelXmlSaver : IMetadataSaver + { + /// + /// Determines whether [is enabled for] [the specified item]. + /// + /// The item. + /// Type of the update. + /// true if [is enabled for] [the specified item]; otherwise, false. + public bool IsEnabledFor(BaseItem item, ItemUpdateType updateType) + { + var wasMetadataEdited = (updateType & ItemUpdateType.MetadataEdit) == ItemUpdateType.MetadataEdit; + var wasMetadataDownloaded = (updateType & ItemUpdateType.MetadataDownload) == ItemUpdateType.MetadataDownload; + + // If new metadata has been downloaded or metadata was manually edited, proceed + if ((wasMetadataEdited || wasMetadataDownloaded)) + { + return item is Channel; + } + + return false; + } + + /// + /// Saves the specified item. + /// + /// The item. + /// The cancellation token. + /// Task. + public void Save(BaseItem item, CancellationToken cancellationToken) + { + var builder = new StringBuilder(); + + builder.Append(""); + + XmlSaverHelpers.AddCommonNodes(item, builder); + + builder.Append(""); + + var xmlFilePath = GetSavePath(item); + + XmlSaverHelpers.Save(builder, xmlFilePath, new List + { + }); + + // Set last refreshed so that the provider doesn't trigger after the file save + ChannelProviderFromXml.Current.SetLastRefreshed(item, DateTime.UtcNow); + } + + /// + /// Gets the save path. + /// + /// The item. + /// System.String. + public string GetSavePath(BaseItem item) + { + return Path.Combine(item.Path, "channel.xml"); + } + } +} diff --git a/MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs b/MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs index 8ea4ff1d3f..973fa54099 100644 --- a/MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs +++ b/MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs @@ -74,7 +74,8 @@ namespace MediaBrowser.Server.Implementations.LiveTv Number = info.ChannelNumber, PrimaryImageTag = GetLogoImageTag(info), Type = info.GetType().Name, - Id = info.Id.ToString("N") + Id = info.Id.ToString("N"), + MediaType = info.MediaType }; } diff --git a/MediaBrowser.WebDashboard/Api/DashboardService.cs b/MediaBrowser.WebDashboard/Api/DashboardService.cs index 681479eb78..2b74eebb0c 100644 --- a/MediaBrowser.WebDashboard/Api/DashboardService.cs +++ b/MediaBrowser.WebDashboard/Api/DashboardService.cs @@ -479,6 +479,7 @@ namespace MediaBrowser.WebDashboard.Api "itemgallery.js", "itemlistpage.js", "librarysettings.js", + "livetvchannel.js", "livetvchannels.js", "livetvguide.js", "livetvrecordings.js", diff --git a/MediaBrowser.WebDashboard/ApiClient.js b/MediaBrowser.WebDashboard/ApiClient.js index 387a849707..f2c0987236 100644 --- a/MediaBrowser.WebDashboard/ApiClient.js +++ b/MediaBrowser.WebDashboard/ApiClient.js @@ -389,6 +389,21 @@ MediaBrowser.ApiClient = function ($, navigator, JSON, WebSocket, setTimeout, wi }); }; + self.getLiveTvChannel = function (id) { + + if (!id) { + throw new Error("null id"); + } + + var url = self.getUrl("/LiveTv/Channels/" + id); + + return self.ajax({ + type: "GET", + url: url, + dataType: "json" + }); + }; + self.getLiveTvChannels = function (options) { var url = self.getUrl("/LiveTv/Channels", options || {}); @@ -1236,7 +1251,11 @@ MediaBrowser.ApiClient = function ($, navigator, JSON, WebSocket, setTimeout, wi if (itemType == "Artist") { url = self.getUrl("Artists/" + self.encodeName(itemName) + "/Images"); - } else if (itemType == "Genre") { + } + else if (itemType == "Channel") { + url = self.getUrl("LiveTv/Channels/" + itemId + "/Images"); + } + else if (itemType == "Genre") { url = self.getUrl("Genres/" + self.encodeName(itemName) + "/Images"); } else if (itemType == "GameGenre") { url = self.getUrl("GameGenres/" + self.encodeName(itemName) + "/Images"); @@ -1292,7 +1311,11 @@ MediaBrowser.ApiClient = function ($, navigator, JSON, WebSocket, setTimeout, wi if (itemType == "Artist") { url = self.getUrl("Artists/" + self.encodeName(itemName) + "/Images/" + imageType + "/" + imageIndex + "/Index", options); - } else if (itemType == "Genre") { + } + else if (itemType == "Channel") { + url = self.getUrl("LiveTv/Channels/" + itemId + "/Images/" + imageType + "/" + imageIndex + "/Index", options); + } + else if (itemType == "Genre") { url = self.getUrl("Genres/" + self.encodeName(itemName) + "/Images/" + imageType + "/" + imageIndex + "/Index", options); } else if (itemType == "GameGenre") { url = self.getUrl("GameGenres/" + self.encodeName(itemName) + "/Images/" + imageType + "/" + imageIndex + "/Index", options); @@ -1322,7 +1345,11 @@ MediaBrowser.ApiClient = function ($, navigator, JSON, WebSocket, setTimeout, wi if (itemType == "Artist") { url = self.getUrl("Artists/" + self.encodeName(itemName) + "/Images"); - } else if (itemType == "Genre") { + } + else if (itemType == "Channel") { + url = self.getUrl("LiveTv/Channels/" + itemId + "/Images"); + } + else if (itemType == "Genre") { url = self.getUrl("Genres/" + self.encodeName(itemName) + "/Images"); } else if (itemType == "GameGenre") { url = self.getUrl("GameGenres/" + self.encodeName(itemName) + "/Images"); @@ -1455,7 +1482,11 @@ MediaBrowser.ApiClient = function ($, navigator, JSON, WebSocket, setTimeout, wi if (itemType == "Artist") { url = self.getUrl("Artists/" + self.encodeName(itemName) + "/Images"); - } else if (itemType == "Genre") { + } + else if (itemType == "Channel") { + url = self.getUrl("LiveTv/Channels/" + itemId + "/Images"); + } + else if (itemType == "Genre") { url = self.getUrl("Genres/" + self.encodeName(itemName) + "/Images"); } else if (itemType == "GameGenre") { url = self.getUrl("GameGenres/" + self.encodeName(itemName) + "/Images"); @@ -2318,6 +2349,22 @@ MediaBrowser.ApiClient = function ($, navigator, JSON, WebSocket, setTimeout, wi }); }; + self.updateLiveTvChannel = function (item) { + + if (!item) { + throw new Error("null item"); + } + + var url = self.getUrl("LiveTv/Channels/" + item.Id); + + return self.ajax({ + type: "POST", + url: url, + data: JSON.stringify(item), + contentType: "application/json" + }); + }; + self.updateArtist = function (item) { if (!item) { diff --git a/MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj b/MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj index 135c15072f..54e70cb9ab 100644 --- a/MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj +++ b/MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj @@ -80,6 +80,9 @@ + + PreserveNewest + PreserveNewest @@ -344,6 +347,9 @@ PreserveNewest + + PreserveNewest + PreserveNewest @@ -1197,7 +1203,7 @@ - + diff --git a/MediaBrowser.WebDashboard/packages.config b/MediaBrowser.WebDashboard/packages.config index 6e250aae3b..75cba28646 100644 --- a/MediaBrowser.WebDashboard/packages.config +++ b/MediaBrowser.WebDashboard/packages.config @@ -1,6 +1,6 @@  - + \ No newline at end of file