diff options
199 files changed, 5435 insertions, 2111 deletions
diff --git a/MediaBrowser.Api/BaseApiService.cs b/MediaBrowser.Api/BaseApiService.cs index d01e96a5a..ee0721d5e 100644 --- a/MediaBrowser.Api/BaseApiService.cs +++ b/MediaBrowser.Api/BaseApiService.cs @@ -92,7 +92,7 @@ namespace MediaBrowser.Api private readonly char[] _dashReplaceChars = new[] { '?', '/' }; private const char SlugChar = '-'; - protected Artist GetArtist(string name, ILibraryManager libraryManager) + protected MusicArtist GetArtist(string name, ILibraryManager libraryManager) { return libraryManager.GetArtist(DeSlugArtistName(name, libraryManager)); } @@ -147,21 +147,7 @@ namespace MediaBrowser.Api return name; } - return libraryManager.RootFolder.GetRecursiveChildren() - .OfType<Audio>() - .SelectMany(i => - { - var list = new List<string>(); - - if (!string.IsNullOrEmpty(i.AlbumArtist)) - { - list.Add(i.AlbumArtist); - } - list.AddRange(i.Artists); - - return list; - }) - .Distinct(StringComparer.OrdinalIgnoreCase) + return libraryManager.GetAllArtists() .FirstOrDefault(i => { i = _dashReplaceChars.Aggregate(i, (current, c) => current.Replace(c, SlugChar)); diff --git a/MediaBrowser.Api/DefaultTheme/DefaultThemeService.cs b/MediaBrowser.Api/DefaultTheme/DefaultThemeService.cs index 6c1aa04cf..4e8864644 100644 --- a/MediaBrowser.Api/DefaultTheme/DefaultThemeService.cs +++ b/MediaBrowser.Api/DefaultTheme/DefaultThemeService.cs @@ -194,20 +194,7 @@ namespace MediaBrowser.Api.DefaultTheme .Select(i => _dtoService.GetBaseItemDto(i, fields, user)) .ToList(); - var artists = allItems.OfType<Audio>() - .SelectMany(i => - { - var list = new List<string>(); - - if (!string.IsNullOrEmpty(i.AlbumArtist)) - { - list.Add(i.AlbumArtist); - } - list.AddRange(i.Artists); - - return list; - }) - .Distinct(StringComparer.OrdinalIgnoreCase) + var artists = _libraryManager.GetAllArtists(allItems) .Randomize() .Select(i => { @@ -650,7 +637,7 @@ namespace MediaBrowser.Api.DefaultTheme public static IEnumerable<T> Randomize<T>(this IEnumerable<T> sequence, string type = "none") where T : BaseItem { - var hour = DateTime.Now.Hour + 2; + var hour = DateTime.Now.Hour + DateTime.Now.Day + 2; var typeCode = type.GetHashCode(); diff --git a/MediaBrowser.Api/IHasItemFields.cs b/MediaBrowser.Api/IHasItemFields.cs new file mode 100644 index 000000000..36303c889 --- /dev/null +++ b/MediaBrowser.Api/IHasItemFields.cs @@ -0,0 +1,52 @@ +using MediaBrowser.Model.Querying; +using System; +using System.Collections.Generic; +using System.Linq; + +namespace MediaBrowser.Api +{ + /// <summary> + /// Interface IHasItemFields + /// </summary> + public interface IHasItemFields + { + /// <summary> + /// Gets or sets the fields. + /// </summary> + /// <value>The fields.</value> + string Fields { get; set; } + } + + /// <summary> + /// Class ItemFieldsExtensions. + /// </summary> + public static class ItemFieldsExtensions + { + /// <summary> + /// Gets the item fields. + /// </summary> + /// <param name="request">The request.</param> + /// <returns>IEnumerable{ItemFields}.</returns> + public static IEnumerable<ItemFields> GetItemFields(this IHasItemFields request) + { + var val = request.Fields; + + if (string.IsNullOrEmpty(val)) + { + return new ItemFields[] { }; + } + + return val.Split(',').Select(v => + { + ItemFields value; + + if (Enum.TryParse(v, true, out value)) + { + return (ItemFields?)value; + } + return null; + + }).Where(i => i.HasValue).Select(i => i.Value); + } + } +} diff --git a/MediaBrowser.Api/Images/ImageService.cs b/MediaBrowser.Api/Images/ImageService.cs index 632230be3..27881d12b 100644 --- a/MediaBrowser.Api/Images/ImageService.cs +++ b/MediaBrowser.Api/Images/ImageService.cs @@ -5,6 +5,7 @@ using MediaBrowser.Controller.Drawing; using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.LiveTv; using MediaBrowser.Controller.Persistence; using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Drawing; @@ -37,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<List<ImageInfo>> + { + /// <summary> + /// Gets or sets the id. + /// </summary> + /// <value>The id.</value> + [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")] @@ -67,6 +80,19 @@ namespace MediaBrowser.Api.Images public string Id { get; set; } } + [Route("/LiveTv/Channels/{Id}/Images/{Type}", "GET")] + [Route("/LiveTv/Channels/{Id}/Images/{Type}/{Index}", "GET")] + [Api(Description = "Gets an item image")] + public class GetChannelImage : ImageRequest + { + /// <summary> + /// Gets or sets the id. + /// </summary> + /// <value>The id.</value> + [ApiMember(Name = "Id", Description = "Channel Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] + public string Id { get; set; } + } + /// <summary> /// Class UpdateItemImageIndex /// </summary> @@ -243,6 +269,19 @@ namespace MediaBrowser.Api.Images public Guid Id { get; set; } } + [Route("/LiveTv/Channels/{Id}/Images/{Type}", "DELETE")] + [Route("/LiveTv/Channels/{Id}/Images/{Type}/{Index}", "DELETE")] + [Api(Description = "Deletes an item image")] + public class DeleteChannelImage : DeleteImageRequest, IReturnVoid + { + /// <summary> + /// Gets or sets the id. + /// </summary> + /// <value>The id.</value> + [ApiMember(Name = "Id", Description = "Channel Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "DELETE")] + public string Id { get; set; } + } + /// <summary> /// Class PostUserImage /// </summary> @@ -318,6 +357,25 @@ namespace MediaBrowser.Api.Images public Stream RequestStream { get; set; } } + [Route("/LiveTv/Channels/{Id}/Images/{Type}", "POST")] + [Route("/LiveTv/Channels/{Id}/Images/{Type}/{Index}", "POST")] + [Api(Description = "Posts an item image")] + public class PostChannelImage : DeleteImageRequest, IRequiresRequestStream, IReturnVoid + { + /// <summary> + /// Gets or sets the id. + /// </summary> + /// <value>The id.</value> + [ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] + public string Id { get; set; } + + /// <summary> + /// The raw Http Request Input Stream + /// </summary> + /// <value>The request stream.</value> + public Stream RequestStream { get; set; } + } + /// <summary> /// Class ImageService /// </summary> @@ -341,10 +399,12 @@ namespace MediaBrowser.Api.Images private readonly IDtoService _dtoService; private readonly IImageProcessor _imageProcessor; + private readonly ILiveTvManager _liveTv; + /// <summary> /// Initializes a new instance of the <see cref="ImageService" /> class. /// </summary> - public ImageService(IUserManager userManager, ILibraryManager libraryManager, IApplicationPaths appPaths, IProviderManager providerManager, IItemRepository itemRepo, IDtoService dtoService, IImageProcessor imageProcessor) + public ImageService(IUserManager userManager, ILibraryManager libraryManager, IApplicationPaths appPaths, IProviderManager providerManager, IItemRepository itemRepo, IDtoService dtoService, IImageProcessor imageProcessor, ILiveTvManager liveTv) { _userManager = userManager; _libraryManager = libraryManager; @@ -353,6 +413,7 @@ namespace MediaBrowser.Api.Images _itemRepo = itemRepo; _dtoService = dtoService; _imageProcessor = imageProcessor; + _liveTv = liveTv; } /// <summary> @@ -369,6 +430,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); @@ -484,7 +554,7 @@ namespace MediaBrowser.Api.Images Height = Convert.ToInt32(size.Height) }; } - catch (IOException ex) + catch (Exception ex) { Logger.ErrorException("Error getting image information for {0}", ex, path); @@ -492,6 +562,13 @@ namespace MediaBrowser.Api.Images } } + public object Get(GetChannelImage request) + { + var item = _liveTv.GetChannel(request.Id); + + return GetImage(request, item); + } + /// <summary> /// Gets the specified request. /// </summary> @@ -577,6 +654,20 @@ namespace MediaBrowser.Api.Images Task.WaitAll(task); } + public void Post(PostChannelImage request) + { + var pathInfo = PathInfo.Parse(RequestContext.PathInfo); + var id = pathInfo.GetArgumentValue<string>(2); + + request.Type = (ImageType)Enum.Parse(typeof(ImageType), pathInfo.GetArgumentValue<string>(4), true); + + var item = _liveTv.GetChannel(id); + + var task = PostImage(item, request.RequestStream, request.Type, RequestContext.ContentType); + + Task.WaitAll(task); + } + /// <summary> /// Deletes the specified request. /// </summary> @@ -603,6 +694,15 @@ namespace MediaBrowser.Api.Images Task.WaitAll(task); } + public void Delete(DeleteChannelImage request) + { + var item = _liveTv.GetChannel(request.Id); + + var task = item.DeleteImage(request.Type, request.Index); + + Task.WaitAll(task); + } + /// <summary> /// Deletes the specified request. /// </summary> diff --git a/MediaBrowser.Api/ItemRefreshService.cs b/MediaBrowser.Api/ItemRefreshService.cs index 260d93860..8fca3f338 100644 --- a/MediaBrowser.Api/ItemRefreshService.cs +++ b/MediaBrowser.Api/ItemRefreshService.cs @@ -264,16 +264,14 @@ namespace MediaBrowser.Api { var item = _dtoService.GetItemByDtoId(request.Id); - var folder = item as Folder; - try { await item.RefreshMetadata(CancellationToken.None, forceRefresh: request.Forced).ConfigureAwait(false); - if (folder != null) + if (item.IsFolder) { // Collection folders don't validate their children so we'll have to simulate that here - var collectionFolder = folder as CollectionFolder; + var collectionFolder = item as CollectionFolder; if (collectionFolder != null) { @@ -281,6 +279,8 @@ namespace MediaBrowser.Api } else { + var folder = (Folder)item; + await folder.ValidateChildren(new Progress<double>(), CancellationToken.None, request.Recursive, request.Forced).ConfigureAwait(false); } } @@ -303,10 +303,10 @@ namespace MediaBrowser.Api { await child.RefreshMetadata(CancellationToken.None, forceRefresh: request.Forced).ConfigureAwait(false); - var folder = child as Folder; - - if (folder != null) + if (child.IsFolder) { + var folder = (Folder)child; + await folder.ValidateChildren(new Progress<double>(), CancellationToken.None, request.Recursive, request.Forced).ConfigureAwait(false); } } diff --git a/MediaBrowser.Api/ItemUpdateService.cs b/MediaBrowser.Api/ItemUpdateService.cs index 6e1dbc08b..90fe11129 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,13 +98,34 @@ namespace MediaBrowser.Api Task.WaitAll(task); } - private Task UpdateItem(UpdateItem request) + public void Post(UpdateChannel request) + { + var task = UpdateItem(request); + + Task.WaitAll(task); + } + + private async Task UpdateItem(UpdateItem request) { var item = _dtoService.GetItemByDtoId(request.ItemId); + var newEnableInternetProviders = request.EnableInternetProviders ?? true; + var dontFetchMetaChanged = item.DontFetchMeta != !newEnableInternetProviders; + UpdateItem(request, item); - return _libraryManager.UpdateItem(item, ItemUpdateType.MetadataEdit, CancellationToken.None); + await _libraryManager.UpdateItem(item, ItemUpdateType.MetadataEdit, CancellationToken.None).ConfigureAwait(false); + + if (dontFetchMetaChanged && item.IsFolder) + { + var folder = (Folder)item; + + foreach (var child in folder.RecursiveChildren.ToList()) + { + child.DontFetchMeta = !newEnableInternetProviders; + await _libraryManager.UpdateItem(child, ItemUpdateType.MetadataEdit, CancellationToken.None).ConfigureAwait(false); + } + } } public void Post(UpdatePerson request) @@ -112,6 +144,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); @@ -126,15 +167,6 @@ namespace MediaBrowser.Api UpdateItem(request, item); await _libraryManager.UpdateItem(item, ItemUpdateType.MetadataEdit, CancellationToken.None).ConfigureAwait(false); - - var musicArtist = Artist.FindMusicArtist(item, _libraryManager); - - if (musicArtist != null) - { - UpdateItem(request, musicArtist); - - await _libraryManager.UpdateItem(musicArtist, ItemUpdateType.MetadataEdit, CancellationToken.None).ConfigureAwait(false); - } } public void Post(UpdateStudio request) @@ -216,8 +248,12 @@ namespace MediaBrowser.Api item.ForcedSortName = request.SortName; } - item.Budget = request.Budget; - item.Revenue = request.Revenue; + var hasBudget = item as IHasBudget; + if (hasBudget != null) + { + hasBudget.Budget = request.Budget; + hasBudget.Revenue = request.Revenue; + } var hasCriticRating = item as IHasCriticRating; if (hasCriticRating != null) @@ -235,8 +271,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) { @@ -247,11 +291,16 @@ namespace MediaBrowser.Api item.PremiereDate = request.PremiereDate.HasValue ? request.PremiereDate.Value.ToUniversalTime() : (DateTime?)null; item.ProductionYear = request.ProductionYear; item.ProductionLocations = request.ProductionLocations; - item.AspectRatio = request.AspectRatio; item.Language = request.Language; item.OfficialRating = request.OfficialRating; item.CustomRating = request.CustomRating; + var hasAspectRatio = item as IHasAspectRatio; + if (hasAspectRatio != null) + { + hasAspectRatio.AspectRatio = request.AspectRatio; + } + item.DontFetchMeta = !(request.EnableInternetProviders ?? true); if (request.EnableInternetProviders ?? true) { diff --git a/MediaBrowser.Api/Library/LibraryStructureService.cs b/MediaBrowser.Api/Library/LibraryStructureService.cs index f3306bb63..198bec1a0 100644 --- a/MediaBrowser.Api/Library/LibraryStructureService.cs +++ b/MediaBrowser.Api/Library/LibraryStructureService.cs @@ -286,7 +286,12 @@ namespace MediaBrowser.Api.Library } finally { - _directoryWatchers.Start(); + // No need to start if scanning the library because it will handle it + if (!request.RefreshLibrary) + { + _directoryWatchers.Start(); + } + _directoryWatchers.RemoveTempIgnore(virtualFolderPath); } @@ -353,7 +358,12 @@ namespace MediaBrowser.Api.Library } finally { - _directoryWatchers.Start(); + // No need to start if scanning the library because it will handle it + if (!request.RefreshLibrary) + { + _directoryWatchers.Start(); + } + _directoryWatchers.RemoveTempIgnore(currentPath); _directoryWatchers.RemoveTempIgnore(newPath); } @@ -404,7 +414,12 @@ namespace MediaBrowser.Api.Library } finally { - _directoryWatchers.Start(); + // No need to start if scanning the library because it will handle it + if (!request.RefreshLibrary) + { + _directoryWatchers.Start(); + } + _directoryWatchers.RemoveTempIgnore(path); } @@ -442,7 +457,11 @@ namespace MediaBrowser.Api.Library } finally { - _directoryWatchers.Start(); + // No need to start if scanning the library because it will handle it + if (!request.RefreshLibrary) + { + _directoryWatchers.Start(); + } } if (request.RefreshLibrary) @@ -479,7 +498,11 @@ namespace MediaBrowser.Api.Library } finally { - _directoryWatchers.Start(); + // No need to start if scanning the library because it will handle it + if (!request.RefreshLibrary) + { + _directoryWatchers.Start(); + } } if (request.RefreshLibrary) diff --git a/MediaBrowser.Api/LibraryService.cs b/MediaBrowser.Api/LibraryService.cs index 582eb6f49..cf62e42ba 100644 --- a/MediaBrowser.Api/LibraryService.cs +++ b/MediaBrowser.Api/LibraryService.cs @@ -37,7 +37,7 @@ namespace MediaBrowser.Api /// </summary> [Route("/Items/{Id}/CriticReviews", "GET")] [Api(Description = "Gets critic reviews for an item")] - public class GetCriticReviews : IReturn<ItemReviewsResult> + public class GetCriticReviews : IReturn<QueryResult<ItemReview>> { /// <summary> /// Gets or sets the id. @@ -367,7 +367,7 @@ namespace MediaBrowser.Api BoxSetCount = boxsets.Count, BookCount = books.Count, - UniqueTypes = items.Select(i => i.GetType().Name).Distinct().ToList() + UniqueTypes = items.Select(i => i.GetClientTypeName()).Distinct().ToList() }; var people = items.SelectMany(i => i.People) @@ -390,19 +390,7 @@ namespace MediaBrowser.Api people = request.UserId.HasValue ? FilterItems(people, request, request.UserId.Value).ToList() : people; counts.PersonCount = people.Count; - var artists = items.OfType<Audio>().SelectMany(i => - { - var list = new List<string>(); - - if (!string.IsNullOrEmpty(i.AlbumArtist)) - { - list.Add(i.AlbumArtist); - } - list.AddRange(i.Artists); - - return list; - }) - .Distinct(StringComparer.OrdinalIgnoreCase) + var artists = _libraryManager.GetAllArtists(items) .Select(i => { try @@ -477,13 +465,16 @@ namespace MediaBrowser.Api if (item.LocationType == LocationType.FileSystem) { - if (Directory.Exists(item.Path)) - { - Directory.Delete(item.Path, true); - } - else if (File.Exists(item.Path)) + foreach (var path in item.GetDeletePaths().ToList()) { - File.Delete(item.Path); + if (Directory.Exists(path)) + { + Directory.Delete(path, true); + } + else if (File.Exists(path)) + { + File.Delete(path); + } } if (parent != null) @@ -521,16 +512,16 @@ namespace MediaBrowser.Api /// </summary> /// <param name="request">The request.</param> /// <returns>Task{ItemReviewsResult}.</returns> - private ItemReviewsResult GetCriticReviews(GetCriticReviews request) + private QueryResult<ItemReview> GetCriticReviews(GetCriticReviews request) { var reviews = _itemRepo.GetCriticReviews(new Guid(request.Id)); var reviewsArray = reviews.ToArray(); - var result = new ItemReviewsResult - { - TotalRecordCount = reviewsArray.Length - }; + var result = new QueryResult<ItemReview> + { + TotalRecordCount = reviewsArray.Length + }; if (request.StartIndex.HasValue) { @@ -541,7 +532,7 @@ namespace MediaBrowser.Api reviewsArray = reviewsArray.Take(request.Limit.Value).ToArray(); } - result.ItemReviews = reviewsArray; + result.Items = reviewsArray; return result; } @@ -681,6 +672,11 @@ namespace MediaBrowser.Api { var album = originalItem as MusicAlbum; + if (album == null) + { + album = originalItem.Parents.OfType<MusicAlbum>().FirstOrDefault(); + } + if (album != null) { var linkedItemWithThemes = album.SoundtrackIds @@ -744,17 +740,12 @@ namespace MediaBrowser.Api : (Folder)_libraryManager.RootFolder) : _dtoService.GetItemByDtoId(id, userId); - while (GetSoundtrackSongIds(item).Count == 0 && inheritFromParent && item.Parent != null) - { - item = item.Parent; - } - // Get everything var fields = Enum.GetNames(typeof(ItemFields)) .Select(i => (ItemFields)Enum.Parse(typeof(ItemFields), i, true)) .ToList(); - var dtos = GetSoundtrackSongIds(item) + var dtos = GetSoundtrackSongIds(item, inheritFromParent) .Select(_libraryManager.GetItemById) .OfType<MusicAlbum>() .SelectMany(i => i.RecursiveChildren) @@ -772,7 +763,7 @@ namespace MediaBrowser.Api }; } - private List<Guid> GetSoundtrackSongIds(BaseItem item) + private IEnumerable<Guid> GetSoundtrackSongIds(BaseItem item, bool inherit) { var hasSoundtracks = item as IHasSoundtracks; @@ -781,7 +772,14 @@ namespace MediaBrowser.Api return hasSoundtracks.SoundtrackIds; } - return new List<Guid>(); + if (!inherit) + { + return null; + } + + hasSoundtracks = item.Parents.OfType<IHasSoundtracks>().FirstOrDefault(); + + return hasSoundtracks != null ? hasSoundtracks.SoundtrackIds : new List<Guid>(); } } } diff --git a/MediaBrowser.Api/LiveTv/LiveTvService.cs b/MediaBrowser.Api/LiveTv/LiveTvService.cs index 72e5eee92..0e40db58c 100644 --- a/MediaBrowser.Api/LiveTv/LiveTvService.cs +++ b/MediaBrowser.Api/LiveTv/LiveTvService.cs @@ -1,6 +1,8 @@ using MediaBrowser.Controller.LiveTv; using MediaBrowser.Model.LiveTv; +using MediaBrowser.Model.Querying; using ServiceStack.ServiceHost; +using System; using System.Collections.Generic; using System.Linq; using System.Threading; @@ -18,31 +20,101 @@ namespace MediaBrowser.Api.LiveTv [Route("/LiveTv/Channels", "GET")] [Api(Description = "Gets available live tv channels.")] - public class GetChannels : IReturn<List<ChannelInfoDto>> + public class GetChannels : IReturn<QueryResult<ChannelInfoDto>> { [ApiMember(Name = "ServiceName", Description = "Optional filter by service.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] public string ServiceName { get; set; } + + [ApiMember(Name = "Type", Description = "Optional filter by channel type.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] + public ChannelType? Type { get; set; } + + [ApiMember(Name = "UserId", Description = "Optional filter by user id.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] + public string UserId { get; set; } + } + + [Route("/LiveTv/Channels/{Id}", "GET")] + [Api(Description = "Gets a live tv channel")] + public class GetChannel : IReturn<ChannelInfoDto> + { + /// <summary> + /// Gets or sets the id. + /// </summary> + /// <value>The id.</value> + [ApiMember(Name = "Id", Description = "Channel Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] + public string Id { get; set; } + + [ApiMember(Name = "UserId", Description = "Optional user id.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] + public string UserId { get; set; } } [Route("/LiveTv/Recordings", "GET")] - [Api(Description = "Gets available live tv recordings.")] - public class GetRecordings : IReturn<List<RecordingInfo>> + [Api(Description = "Gets live tv recordings")] + public class GetRecordings : IReturn<QueryResult<RecordingInfoDto>> { [ApiMember(Name = "ServiceName", Description = "Optional filter by service.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] public string ServiceName { get; set; } + + [ApiMember(Name = "ChannelId", Description = "Optional filter by channel id.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] + public string ChannelId { get; set; } } - [Route("/LiveTv/Guide", "GET")] + [Route("/LiveTv/Recordings/{Id}", "GET")] + [Api(Description = "Gets a live tv recording")] + public class GetRecording : IReturn<RecordingInfoDto> + { + [ApiMember(Name = "Id", Description = "Recording Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] + public string Id { get; set; } + } + + [Route("/LiveTv/Timers/{Id}", "GET")] + [Api(Description = "Gets a live tv timer")] + public class GetTimer : IReturn<TimerInfoDto> + { + [ApiMember(Name = "Id", Description = "Timer Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] + public string Id { get; set; } + } + + [Route("/LiveTv/Timers", "GET")] + [Api(Description = "Gets live tv timers")] + public class GetTimers : IReturn<QueryResult<TimerInfoDto>> + { + [ApiMember(Name = "ServiceName", Description = "Optional filter by service.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] + public string ServiceName { get; set; } + + [ApiMember(Name = "ChannelId", Description = "Optional filter by channel id.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] + public string ChannelId { get; set; } + } + + [Route("/LiveTv/Programs", "GET")] [Api(Description = "Gets available live tv epgs..")] - public class GetGuide : IReturn<List<ChannelGuide>> + public class GetPrograms : IReturn<QueryResult<ProgramInfoDto>> { - [ApiMember(Name = "ServiceName", Description = "Live tv service name", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "GET")] + [ApiMember(Name = "ServiceName", Description = "Live tv service name", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] public string ServiceName { get; set; } - [ApiMember(Name = "ChannelIds", Description = "The channels to return guide information for.", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "GET")] + [ApiMember(Name = "ChannelIds", Description = "The channels to return guide information for.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] public string ChannelIds { get; set; } + + [ApiMember(Name = "UserId", Description = "Optional filter by user id.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] + public string UserId { get; set; } } + [Route("/LiveTv/Recordings/{Id}", "DELETE")] + [Api(Description = "Deletes a live tv recording")] + public class DeleteRecording : IReturnVoid + { + [ApiMember(Name = "Id", Description = "Recording Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] + public string Id { get; set; } + } + + [Route("/LiveTv/Timers/{Id}", "DELETE")] + [Api(Description = "Cancels a live tv timer")] + public class CancelTimer : IReturnVoid + { + [ApiMember(Name = "Id", Description = "Timer Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] + public string Id { get; set; } + } + public class LiveTvService : BaseApiService { private readonly ILiveTvManager _liveTvManager; @@ -58,7 +130,7 @@ namespace MediaBrowser.Api.LiveTv if (!string.IsNullOrEmpty(serviceName)) { - services = services.Where(i => string.Equals(i.Name, serviceName, System.StringComparison.OrdinalIgnoreCase)); + services = services.Where(i => string.Equals(i.Name, serviceName, StringComparison.OrdinalIgnoreCase)); } return services; @@ -83,62 +155,87 @@ namespace MediaBrowser.Api.LiveTv public object Get(GetChannels request) { - var result = GetChannelsAsync(request).Result; + var result = _liveTvManager.GetChannels(new ChannelQuery + { + ChannelType = request.Type, + ServiceName = request.ServiceName, + UserId = request.UserId - return ToOptimizedResult(result.ToList()); + }); + + return ToOptimizedResult(result); } - private async Task<IEnumerable<ChannelInfoDto>> GetChannelsAsync(GetChannels request) + public object Get(GetChannel request) { - var services = GetServices(request.ServiceName); + var result = _liveTvManager.GetChannelInfoDto(request.Id, request.UserId); + + return ToOptimizedResult(result); + } - var tasks = services.Select(i => i.GetChannelsAsync(CancellationToken.None)); + public object Get(GetPrograms request) + { + var result = _liveTvManager.GetPrograms(new ProgramQuery + { + ServiceName = request.ServiceName, + ChannelIdList = (request.ChannelIds ?? string.Empty).Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries).ToArray(), + UserId = request.UserId - var channelLists = await Task.WhenAll(tasks).ConfigureAwait(false); + }, CancellationToken.None).Result; - // Aggregate all channels from all services - return channelLists.SelectMany(i => i) - .Select(_liveTvManager.GetChannelInfoDto); + return ToOptimizedResult(result); } public object Get(GetRecordings request) { - var result = GetRecordingsAsync(request).Result; + var result = _liveTvManager.GetRecordings(new RecordingQuery + { + ChannelId = request.ChannelId, + ServiceName = request.ServiceName - return ToOptimizedResult(result.ToList()); + }, CancellationToken.None).Result; + + return ToOptimizedResult(result); } - private async Task<IEnumerable<RecordingInfo>> GetRecordingsAsync(GetRecordings request) + public object Get(GetRecording request) { - var services = GetServices(request.ServiceName); - - var query = new RecordingQuery - { - - }; + var result = _liveTvManager.GetRecording(request.Id, CancellationToken.None).Result; - var tasks = services.Select(i => i.GetRecordingsAsync(query, CancellationToken.None)); + return ToOptimizedResult(result); + } - var recordings = await Task.WhenAll(tasks).ConfigureAwait(false); + public object Get(GetTimer request) + { + var result = _liveTvManager.GetTimer(request.Id, CancellationToken.None).Result; - return recordings.SelectMany(i => i); + return ToOptimizedResult(result); } - public object Get(GetGuide request) + public object Get(GetTimers request) { - var result = GetGuideAsync(request).Result; + var result = _liveTvManager.GetTimers(new TimerQuery + { + ChannelId = request.ChannelId, + ServiceName = request.ServiceName + + }, CancellationToken.None).Result; return ToOptimizedResult(result); } - private async Task<IEnumerable<ChannelGuide>> GetGuideAsync(GetGuide request) + public void Delete(DeleteRecording request) { - var service = GetServices(request.ServiceName) - .First(); + var task = _liveTvManager.DeleteRecording(request.Id); - var channels = request.ChannelIds.Split(','); + Task.WaitAll(task); + } + + public void Delete(CancelTimer request) + { + var task = _liveTvManager.CancelTimer(request.Id); - return await service.GetChannelGuidesAsync(channels, CancellationToken.None).ConfigureAwait(false); + Task.WaitAll(task); } } }
\ No newline at end of file diff --git a/MediaBrowser.Api/MediaBrowser.Api.csproj b/MediaBrowser.Api/MediaBrowser.Api.csproj index 7a9c6c8ac..706117fc2 100644 --- a/MediaBrowser.Api/MediaBrowser.Api.csproj +++ b/MediaBrowser.Api/MediaBrowser.Api.csproj @@ -38,6 +38,18 @@ <RunPostBuildEvent>Always</RunPostBuildEvent> </PropertyGroup> <ItemGroup> + <Reference Include="ServiceStack.Common, Version=3.9.70.0, Culture=neutral, processorArchitecture=MSIL"> + <SpecificVersion>False</SpecificVersion> + <HintPath>..\packages\ServiceStack.Common.3.9.70\lib\net35\ServiceStack.Common.dll</HintPath> + </Reference> + <Reference Include="ServiceStack.Interfaces, Version=1.0.0.0, Culture=neutral, processorArchitecture=MSIL"> + <SpecificVersion>False</SpecificVersion> + <HintPath>..\packages\ServiceStack.Common.3.9.70\lib\net35\ServiceStack.Interfaces.dll</HintPath> + </Reference> + <Reference Include="ServiceStack.Text, Version=3.9.70.0, Culture=neutral, processorArchitecture=MSIL"> + <SpecificVersion>False</SpecificVersion> + <HintPath>..\packages\ServiceStack.Text.3.9.70\lib\net35\ServiceStack.Text.dll</HintPath> + </Reference> <Reference Include="System" /> <Reference Include="System.Core" /> <Reference Include="Microsoft.CSharp" /> @@ -47,15 +59,6 @@ <Reference Include="MoreLinq"> <HintPath>..\packages\morelinq.1.0.16006\lib\net35\MoreLinq.dll</HintPath> </Reference> - <Reference Include="ServiceStack.Common"> - <HintPath>..\packages\ServiceStack.Common.3.9.62\lib\net35\ServiceStack.Common.dll</HintPath> - </Reference> - <Reference Include="ServiceStack.Interfaces"> - <HintPath>..\packages\ServiceStack.Common.3.9.62\lib\net35\ServiceStack.Interfaces.dll</HintPath> - </Reference> - <Reference Include="ServiceStack.Text"> - <HintPath>..\packages\ServiceStack.Text.3.9.62\lib\net35\ServiceStack.Text.dll</HintPath> - </Reference> <Reference Include="System.Xml" /> </ItemGroup> <ItemGroup> @@ -70,6 +73,7 @@ <Compile Include="EnvironmentService.cs" /> <Compile Include="AuthorizationRequestFilterAttribute.cs" /> <Compile Include="GamesService.cs" /> + <Compile Include="IHasItemFields.cs" /> <Compile Include="Images\ImageByNameService.cs" /> <Compile Include="Images\ImageRequest.cs" /> <Compile Include="Images\ImageService.cs" /> diff --git a/MediaBrowser.Api/Playback/BaseStreamingService.cs b/MediaBrowser.Api/Playback/BaseStreamingService.cs index 4a767b088..9ad0703ae 100644 --- a/MediaBrowser.Api/Playback/BaseStreamingService.cs +++ b/MediaBrowser.Api/Playback/BaseStreamingService.cs @@ -1,5 +1,4 @@ -using System.Globalization; -using MediaBrowser.Common.Extensions; +using MediaBrowser.Common.Extensions; using MediaBrowser.Common.IO; using MediaBrowser.Common.MediaInfo; using MediaBrowser.Controller; @@ -11,15 +10,15 @@ using MediaBrowser.Controller.MediaInfo; using MediaBrowser.Model.Drawing; using MediaBrowser.Model.Dto; using MediaBrowser.Model.Entities; +using MediaBrowser.Model.IO; using System; using System.Collections.Generic; -using System.ComponentModel; using System.Diagnostics; +using System.Globalization; using System.IO; using System.Linq; using System.Threading; using System.Threading.Tasks; -using MediaBrowser.Model.IO; namespace MediaBrowser.Api.Playback { @@ -696,7 +695,7 @@ namespace MediaBrowser.Api.Playback // This is arbitrary, but add a little buffer time when internet streaming if (state.Item.LocationType == LocationType.Remote) { - await Task.Delay(2000).ConfigureAwait(false); + await Task.Delay(4000).ConfigureAwait(false); } } diff --git a/MediaBrowser.Api/Playback/Hls/VideoHlsService.cs b/MediaBrowser.Api/Playback/Hls/VideoHlsService.cs index 080ab9c7e..dfd18ab15 100644 --- a/MediaBrowser.Api/Playback/Hls/VideoHlsService.cs +++ b/MediaBrowser.Api/Playback/Hls/VideoHlsService.cs @@ -88,7 +88,7 @@ namespace MediaBrowser.Api.Playback.Hls } var volParam = string.Empty; - var AudioSampleRate = string.Empty; + var audioSampleRate = string.Empty; // Boost volume to 200% when downsampling from 6ch to 2ch if (channels.HasValue && channels.Value <= 2 && state.AudioStream.Channels.HasValue && state.AudioStream.Channels.Value > 5) @@ -98,10 +98,10 @@ namespace MediaBrowser.Api.Playback.Hls if (state.Request.AudioSampleRate.HasValue) { - AudioSampleRate= state.Request.AudioSampleRate.Value + ":"; + audioSampleRate= state.Request.AudioSampleRate.Value + ":"; } - args += string.Format(" -af \"adelay=1,aresample={0}async=1000{1}\"",AudioSampleRate, volParam); + args += string.Format(" -af \"adelay=1,aresample={0}async=1000{1}\"",audioSampleRate, volParam); return args; } @@ -127,6 +127,10 @@ namespace MediaBrowser.Api.Playback.Hls const string keyFrameArg = " -force_key_frames expr:if(isnan(prev_forced_t),gte(t,.1),gte(t,prev_forced_t+5))"; + var hasGraphicalSubs = state.SubtitleStream != null && !state.SubtitleStream.IsExternal && + (state.SubtitleStream.Codec.IndexOf("pgs", StringComparison.OrdinalIgnoreCase) != -1 || + state.SubtitleStream.Codec.IndexOf("dvd", StringComparison.OrdinalIgnoreCase) != -1); + var args = "-codec:v:0 " + codec + " -preset superfast" + keyFrameArg; var bitrate = GetVideoBitrateParam(state); @@ -137,9 +141,12 @@ namespace MediaBrowser.Api.Playback.Hls } // Add resolution params, if specified - if (state.VideoRequest.Width.HasValue || state.VideoRequest.Height.HasValue || state.VideoRequest.MaxHeight.HasValue || state.VideoRequest.MaxWidth.HasValue) + if (!hasGraphicalSubs) { - args += GetOutputSizeParam(state, codec, performSubtitleConversion); + if (state.VideoRequest.Width.HasValue || state.VideoRequest.Height.HasValue || state.VideoRequest.MaxHeight.HasValue || state.VideoRequest.MaxWidth.HasValue) + { + args += GetOutputSizeParam(state, codec, performSubtitleConversion); + } } if (state.VideoRequest.Framerate.HasValue) @@ -158,14 +165,11 @@ namespace MediaBrowser.Api.Playback.Hls { args += " -level " + state.VideoRequest.Level; } - - if (state.SubtitleStream != null) + + // This is for internal graphical subs + if (hasGraphicalSubs) { - // This is for internal graphical subs - if (!state.SubtitleStream.IsExternal && (state.SubtitleStream.Codec.IndexOf("pgs", StringComparison.OrdinalIgnoreCase) != -1 || state.SubtitleStream.Codec.IndexOf("dvd", StringComparison.OrdinalIgnoreCase) != -1)) - { - args += GetInternalGraphicalSubtitleParam(state, codec); - } + args += GetInternalGraphicalSubtitleParam(state, codec); } return args; diff --git a/MediaBrowser.Api/Playback/Progressive/BaseProgressiveStreamingService.cs b/MediaBrowser.Api/Playback/Progressive/BaseProgressiveStreamingService.cs index a31b6af0e..1f0853e08 100644 --- a/MediaBrowser.Api/Playback/Progressive/BaseProgressiveStreamingService.cs +++ b/MediaBrowser.Api/Playback/Progressive/BaseProgressiveStreamingService.cs @@ -127,44 +127,44 @@ namespace MediaBrowser.Api.Playback.Progressive const string dlnaflags = ";DLNA.ORG_FLAGS=01500000000000000000000000000000"; - if (string.Equals(extension, ".mp3", StringComparison.OrdinalIgnoreCase)) - { - contentFeatures = "DLNA.ORG_PN=MP3"; - } - else if (string.Equals(extension, ".aac", StringComparison.OrdinalIgnoreCase)) - { - contentFeatures = "DLNA.ORG_PN=AAC_ISO"; - } - else if (string.Equals(extension, ".wma", StringComparison.OrdinalIgnoreCase)) - { - contentFeatures = "DLNA.ORG_PN=WMABASE"; - } - else if (string.Equals(extension, ".avi", StringComparison.OrdinalIgnoreCase)) - { - contentFeatures = "DLNA.ORG_PN=AVI"; - } - else if (string.Equals(extension, ".mp4", StringComparison.OrdinalIgnoreCase)) - { - contentFeatures = "DLNA.ORG_PN=MPEG4_P2_SP_AAC"; - } - else if (string.Equals(extension, ".mpeg", StringComparison.OrdinalIgnoreCase)) - { - contentFeatures = "DLNA.ORG_PN=MPEG_PS_PAL"; - } - else if (string.Equals(extension, ".wmv", StringComparison.OrdinalIgnoreCase)) - { - contentFeatures = "DLNA.ORG_PN=WMVHIGH_BASE"; - } - else if (string.Equals(extension, ".asf", StringComparison.OrdinalIgnoreCase)) - { - // ?? - contentFeatures = "DLNA.ORG_PN=WMVHIGH_BASE"; - } - else if (string.Equals(extension, ".mkv", StringComparison.OrdinalIgnoreCase)) - { - // ?? - contentFeatures = ""; - } + //if (string.Equals(extension, ".mp3", StringComparison.OrdinalIgnoreCase)) + //{ + // contentFeatures = "DLNA.ORG_PN=MP3"; + //} + //else if (string.Equals(extension, ".aac", StringComparison.OrdinalIgnoreCase)) + //{ + // contentFeatures = "DLNA.ORG_PN=AAC_ISO"; + //} + //else if (string.Equals(extension, ".wma", StringComparison.OrdinalIgnoreCase)) + //{ + // contentFeatures = "DLNA.ORG_PN=WMABASE"; + //} + //else if (string.Equals(extension, ".avi", StringComparison.OrdinalIgnoreCase)) + //{ + // contentFeatures = "DLNA.ORG_PN=AVI"; + //} + //else if (string.Equals(extension, ".mp4", StringComparison.OrdinalIgnoreCase)) + //{ + // contentFeatures = "DLNA.ORG_PN=MPEG4_P2_SP_AAC"; + //} + //else if (string.Equals(extension, ".mpeg", StringComparison.OrdinalIgnoreCase)) + //{ + // contentFeatures = "DLNA.ORG_PN=MPEG_PS_PAL"; + //} + //else if (string.Equals(extension, ".wmv", StringComparison.OrdinalIgnoreCase)) + //{ + // contentFeatures = "DLNA.ORG_PN=WMVHIGH_BASE"; + //} + //else if (string.Equals(extension, ".asf", StringComparison.OrdinalIgnoreCase)) + //{ + // // ?? + // contentFeatures = "DLNA.ORG_PN=WMVHIGH_BASE"; + //} + //else if (string.Equals(extension, ".mkv", StringComparison.OrdinalIgnoreCase)) + //{ + // // ?? + // contentFeatures = ""; + //} if (!string.IsNullOrEmpty(contentFeatures)) { @@ -206,10 +206,10 @@ namespace MediaBrowser.Api.Playback.Progressive var outputPath = GetOutputFilePath(state); var outputPathExists = File.Exists(outputPath); - //var isStatic = request.Static || - // (outputPathExists && !ApiEntryPoint.Instance.HasActiveTranscodingJob(outputPath, TranscodingJobType.Progressive)); + var isStatic = request.Static || + (outputPathExists && !ApiEntryPoint.Instance.HasActiveTranscodingJob(outputPath, TranscodingJobType.Progressive)); - //AddDlnaHeaders(state, responseHeaders, isStatic); + AddDlnaHeaders(state, responseHeaders, isStatic); if (request.Static) { @@ -307,7 +307,7 @@ namespace MediaBrowser.Api.Playback.Progressive } } - return new ImageService(UserManager, LibraryManager, ApplicationPaths, null, ItemRepository, DtoService, ImageProcessor) + return new ImageService(UserManager, LibraryManager, ApplicationPaths, null, ItemRepository, DtoService, ImageProcessor, null) { Logger = Logger, RequestContext = RequestContext, diff --git a/MediaBrowser.Api/Playback/Progressive/VideoService.cs b/MediaBrowser.Api/Playback/Progressive/VideoService.cs index 0ef6d13cc..97b808b86 100644 --- a/MediaBrowser.Api/Playback/Progressive/VideoService.cs +++ b/MediaBrowser.Api/Playback/Progressive/VideoService.cs @@ -143,12 +143,19 @@ namespace MediaBrowser.Api.Playback.Progressive args += keyFrameArg; + var hasGraphicalSubs = state.SubtitleStream != null && !state.SubtitleStream.IsExternal && + (state.SubtitleStream.Codec.IndexOf("pgs", StringComparison.OrdinalIgnoreCase) != -1 || + state.SubtitleStream.Codec.IndexOf("dvd", StringComparison.OrdinalIgnoreCase) != -1); + var request = state.VideoRequest; // Add resolution params, if specified - if (request.Width.HasValue || request.Height.HasValue || request.MaxHeight.HasValue || request.MaxWidth.HasValue) + if (!hasGraphicalSubs) { - args += GetOutputSizeParam(state, codec, performSubtitleConversion); + if (request.Width.HasValue || request.Height.HasValue || request.MaxHeight.HasValue || request.MaxWidth.HasValue) + { + args += GetOutputSizeParam(state, codec, performSubtitleConversion); + } } if (request.Framerate.HasValue) @@ -175,13 +182,10 @@ namespace MediaBrowser.Api.Playback.Progressive args += " -level " + state.VideoRequest.Level; } - if (state.SubtitleStream != null) + // This is for internal graphical subs + if (hasGraphicalSubs) { - // This is for internal graphical subs - if (!state.SubtitleStream.IsExternal && (state.SubtitleStream.Codec.IndexOf("pgs", StringComparison.OrdinalIgnoreCase) != -1 || state.SubtitleStream.Codec.IndexOf("dvd", StringComparison.OrdinalIgnoreCase) != -1)) - { - args += GetInternalGraphicalSubtitleParam(state, codec); - } + args += GetInternalGraphicalSubtitleParam(state, codec); } return args; diff --git a/MediaBrowser.Api/ScheduledTasks/ScheduledTaskService.cs b/MediaBrowser.Api/ScheduledTasks/ScheduledTaskService.cs index 2674529e5..d17a38e07 100644 --- a/MediaBrowser.Api/ScheduledTasks/ScheduledTaskService.cs +++ b/MediaBrowser.Api/ScheduledTasks/ScheduledTaskService.cs @@ -31,7 +31,8 @@ namespace MediaBrowser.Api.ScheduledTasks [Api(Description = "Gets scheduled tasks")] public class GetScheduledTasks : IReturn<List<TaskInfo>> { - + [ApiMember(Name = "IsHidden", Description = "Optional filter tasks that are hidden, or not.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")] + public bool? IsHidden { get; set; } } /// <summary> @@ -112,10 +113,33 @@ namespace MediaBrowser.Api.ScheduledTasks /// <returns>IEnumerable{TaskInfo}.</returns> public object Get(GetScheduledTasks request) { - var result = TaskManager.ScheduledTasks.OrderBy(i => i.Name) - .Select(ScheduledTaskHelpers.GetTaskInfo).ToList(); + IEnumerable<IScheduledTaskWorker> result = TaskManager.ScheduledTasks + .OrderBy(i => i.Name); - return ToOptimizedResult(result); + if (request.IsHidden.HasValue) + { + var val = request.IsHidden.Value; + + result = result.Where(i => + { + var isHidden = false; + + var configurableTask = i.ScheduledTask as IConfigurableScheduledTask; + + if (configurableTask != null) + { + isHidden = configurableTask.IsHidden; + } + + return isHidden == val; + }); + } + + var infos = result + .Select(ScheduledTaskHelpers.GetTaskInfo) + .ToList(); + + return ToOptimizedResult(infos); } /// <summary> diff --git a/MediaBrowser.Api/ScheduledTasks/ScheduledTasksWebSocketListener.cs b/MediaBrowser.Api/ScheduledTasks/ScheduledTasksWebSocketListener.cs index 20634301a..c143635bf 100644 --- a/MediaBrowser.Api/ScheduledTasks/ScheduledTasksWebSocketListener.cs +++ b/MediaBrowser.Api/ScheduledTasks/ScheduledTasksWebSocketListener.cs @@ -46,8 +46,10 @@ namespace MediaBrowser.Api.ScheduledTasks /// <returns>Task{IEnumerable{TaskInfo}}.</returns> protected override Task<IEnumerable<TaskInfo>> GetDataToSend(object state) { - return Task.FromResult(TaskManager.ScheduledTasks.OrderBy(i => i.Name) - .Select(ScheduledTaskHelpers.GetTaskInfo)); + return Task.FromResult(TaskManager.ScheduledTasks + .OrderBy(i => i.Name) + .Select(ScheduledTaskHelpers.GetTaskInfo) + .Where(i => !i.IsHidden)); } } } diff --git a/MediaBrowser.Api/SearchService.cs b/MediaBrowser.Api/SearchService.cs index f87434e97..113df4e38 100644 --- a/MediaBrowser.Api/SearchService.cs +++ b/MediaBrowser.Api/SearchService.cs @@ -144,7 +144,7 @@ namespace MediaBrowser.Api IndexNumber = item.IndexNumber, ParentIndexNumber = item.ParentIndexNumber, ItemId = _dtoService.GetDtoId(item), - Type = item.GetType().Name, + Type = item.GetClientTypeName(), MediaType = item.MediaType, MatchedTerm = hintInfo.MatchedTerm, DisplayMediaType = item.DisplayMediaType, @@ -187,9 +187,7 @@ namespace MediaBrowser.Api result.SongCount = songs.Count; - result.Artists = songs - .SelectMany(i => i.Artists) - .Distinct(StringComparer.OrdinalIgnoreCase) + result.Artists = _libraryManager.GetAllArtists(songs) .ToArray(); result.AlbumArtist = songs.Select(i => i.AlbumArtist).FirstOrDefault(i => !string.IsNullOrEmpty(i)); diff --git a/MediaBrowser.Api/SimilarItemsHelper.cs b/MediaBrowser.Api/SimilarItemsHelper.cs index 587965b3f..a160ce666 100644 --- a/MediaBrowser.Api/SimilarItemsHelper.cs +++ b/MediaBrowser.Api/SimilarItemsHelper.cs @@ -25,7 +25,7 @@ namespace MediaBrowser.Api public string Id { get; set; } } - public class BaseGetSimilarItems : IReturn<ItemsResult> + public class BaseGetSimilarItems : IReturn<ItemsResult>, IHasItemFields { /// <summary> /// Gets or sets the user id. @@ -47,32 +47,6 @@ namespace MediaBrowser.Api /// <value>The fields.</value> [ApiMember(Name = "Fields", Description = "Optional. Specify additional fields of information to return in the output. This allows multiple, comma delimeted. Options: Budget, Chapters, CriticRatingSummary, DateCreated, Genres, HomePageUrl, IndexOptions, MediaStreams, Overview, OverviewHtml, ParentId, Path, People, ProviderIds, PrimaryImageAspectRatio, Revenue, SortName, Studios, Taglines, TrailerUrls", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)] public string Fields { get; set; } - - /// <summary> - /// Gets the item fields. - /// </summary> - /// <returns>IEnumerable{ItemFields}.</returns> - public IEnumerable<ItemFields> GetItemFields() - { - var val = Fields; - - if (string.IsNullOrEmpty(val)) - { - return new ItemFields[] { }; - } - - return val.Split(',').Select(v => - { - ItemFields value; - - if (Enum.TryParse(v, true, out value)) - { - return (ItemFields?)value; - } - return null; - - }).Where(i => i.HasValue).Select(i => i.Value); - } } /// <summary> diff --git a/MediaBrowser.Api/TvShowsService.cs b/MediaBrowser.Api/TvShowsService.cs index 6cae379d2..68ebd60c5 100644 --- a/MediaBrowser.Api/TvShowsService.cs +++ b/MediaBrowser.Api/TvShowsService.cs @@ -1,4 +1,5 @@ -using MediaBrowser.Controller.Dto; +using MediaBrowser.Api.UserLibrary; +using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.TV; using MediaBrowser.Controller.Library; @@ -17,7 +18,7 @@ namespace MediaBrowser.Api /// </summary> [Route("/Shows/NextUp", "GET")] [Api(("Gets a list of currently installed plugins"))] - public class GetNextUpEpisodes : IReturn<ItemsResult> + public class GetNextUpEpisodes : IReturn<ItemsResult>, IHasItemFields { /// <summary> /// Gets or sets the user id. @@ -49,38 +50,83 @@ namespace MediaBrowser.Api [ApiMember(Name = "SeriesId", Description = "Optional. Filter by series id", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] public string SeriesId { get; set; } + } + + [Route("/Shows/{Id}/Similar", "GET")] + [Api(Description = "Finds tv shows similar to a given one.")] + public class GetSimilarShows : BaseGetSimilarItemsFromItem + { + } + [Route("/Shows/{Id}/Episodes", "GET")] + [Api(Description = "Gets episodes for a tv season")] + public class GetEpisodes : IReturn<ItemsResult>, IHasItemFields + { /// <summary> - /// Gets the item fields. + /// Gets or sets the user id. /// </summary> - /// <returns>IEnumerable{ItemFields}.</returns> - public IEnumerable<ItemFields> GetItemFields() - { - var val = Fields; + /// <value>The user id.</value> + [ApiMember(Name = "UserId", Description = "User Id", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "GET")] + public Guid UserId { get; set; } - if (string.IsNullOrEmpty(val)) - { - return new ItemFields[] { }; - } + /// <summary> + /// Fields to return within the items, in addition to basic information + /// </summary> + /// <value>The fields.</value> + [ApiMember(Name = "Fields", Description = "Optional. Specify additional fields of information to return in the output. This allows multiple, comma delimeted. Options: Budget, Chapters, CriticRatingSummary, DateCreated, Genres, HomePageUrl, IndexOptions, MediaStreams, Overview, OverviewHtml, ParentId, Path, People, ProviderIds, PrimaryImageAspectRatio, Revenue, SortName, Studios, Taglines, TrailerUrls", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)] + public string Fields { get; set; } - return val.Split(',').Select(v => - { - ItemFields value; + [ApiMember(Name = "Id", Description = "The series id", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "GET")] + public Guid Id { get; set; } - if (Enum.TryParse(v, true, out value)) - { - return (ItemFields?)value; - } - return null; + [ApiMember(Name = "Season", Description = "Optional filter by season number.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] + public int? Season { get; set; } - }).Where(i => i.HasValue).Select(i => i.Value); - } + [ApiMember(Name = "SeasonId", Description = "Optional. Filter by season id", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] + public string SeasonId { get; set; } + + [ApiMember(Name = "IsMissing", Description = "Optional filter by items that are missing episodes or not.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")] + public bool? IsMissing { get; set; } + + [ApiMember(Name = "IsVirtualUnaired", Description = "Optional filter by items that are virtual unaired episodes or not.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")] + public bool? IsVirtualUnaired { get; set; } + + [ApiMember(Name = "AdjacentTo", Description = "Optional. Return items that are siblings of a supplied item.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] + public string AdjacentTo { get; set; } } - [Route("/Shows/{Id}/Similar", "GET")] - [Api(Description = "Finds tv shows similar to a given one.")] - public class GetSimilarShows : BaseGetSimilarItemsFromItem + [Route("/Shows/{Id}/Seasons", "GET")] + [Api(Description = "Gets seasons for a tv series")] + public class GetSeasons : IReturn<ItemsResult>, IHasItemFields { + /// <summary> + /// Gets or sets the user id. + /// </summary> + /// <value>The user id.</value> + [ApiMember(Name = "UserId", Description = "User Id", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "GET")] + public Guid UserId { get; set; } + + /// <summary> + /// Fields to return within the items, in addition to basic information + /// </summary> + /// <value>The fields.</value> + [ApiMember(Name = "Fields", Description = "Optional. Specify additional fields of information to return in the output. This allows multiple, comma delimeted. Options: Budget, Chapters, CriticRatingSummary, DateCreated, Genres, HomePageUrl, IndexOptions, MediaStreams, Overview, OverviewHtml, ParentId, Path, People, ProviderIds, PrimaryImageAspectRatio, Revenue, SortName, Studios, Taglines, TrailerUrls", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)] + public string Fields { get; set; } + + [ApiMember(Name = "Id", Description = "The series id", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "GET")] + public Guid Id { get; set; } + + [ApiMember(Name = "IsSpecialSeason", Description = "Optional. Filter by special season.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")] + public bool? IsSpecialSeason { get; set; } + + [ApiMember(Name = "IsMissing", Description = "Optional filter by items that are missing episodes or not.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")] + public bool? IsMissing { get; set; } + + [ApiMember(Name = "IsVirtualUnaired", Description = "Optional filter by items that are virtual unaired episodes or not.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")] + public bool? IsVirtualUnaired { get; set; } + + [ApiMember(Name = "AdjacentTo", Description = "Optional. Return items that are siblings of a supplied item.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] + public string AdjacentTo { get; set; } } /// <summary> @@ -311,5 +357,196 @@ namespace MediaBrowser.Api return items; } + + public object Get(GetSeasons request) + { + var user = _userManager.GetUserById(request.UserId); + + var series = _libraryManager.GetItemById(request.Id) as Series; + + var fields = request.GetItemFields().ToList(); + + var seasons = series.GetChildren(user, true) + .OfType<Season>(); + + var sortOrder = ItemSortBy.SortName; + + if (request.IsSpecialSeason.HasValue) + { + var val = request.IsSpecialSeason.Value; + + seasons = seasons.Where(i => i.IsSpecialSeason == val); + } + + var config = user.Configuration; + + if (!config.DisplayMissingEpisodes && !config.DisplayUnairedEpisodes) + { + seasons = seasons.Where(i => !i.IsMissingOrVirtualUnaired); + } + else + { + if (!config.DisplayMissingEpisodes) + { + seasons = seasons.Where(i => !i.IsMissingSeason); + } + if (!config.DisplayUnairedEpisodes) + { + seasons = seasons.Where(i => !i.IsVirtualUnaired); + } + } + + seasons = FilterVirtualSeasons(request, seasons); + + seasons = _libraryManager.Sort(seasons, user, new[] { sortOrder }, SortOrder.Ascending) + .Cast<Season>(); + + // This must be the last filter + if (!string.IsNullOrEmpty(request.AdjacentTo)) + { + seasons = ItemsService.FilterForAdjacency(seasons, request.AdjacentTo) + .Cast<Season>(); + } + + var returnItems = seasons.Select(i => _dtoService.GetBaseItemDto(i, fields, user)) + .ToArray(); + + return new ItemsResult + { + TotalRecordCount = returnItems.Length, + Items = returnItems + }; + } + + private IEnumerable<Season> FilterVirtualSeasons(GetSeasons request, IEnumerable<Season> items) + { + if (request.IsMissing.HasValue && request.IsVirtualUnaired.HasValue) + { + var isMissing = request.IsMissing.Value; + var isVirtualUnaired = request.IsVirtualUnaired.Value; + + if (!isMissing && !isVirtualUnaired) + { + return items.Where(i => !i.IsMissingOrVirtualUnaired); + } + } + + if (request.IsMissing.HasValue) + { + var val = request.IsMissing.Value; + items = items.Where(i => i.IsMissingSeason == val); + } + + if (request.IsVirtualUnaired.HasValue) + { + var val = request.IsVirtualUnaired.Value; + items = items.Where(i => i.IsVirtualUnaired == val); + } + + return items; + } + + public object Get(GetEpisodes request) + { + var user = _userManager.GetUserById(request.UserId); + + var series = _libraryManager.GetItemById(request.Id) as Series; + + var fields = request.GetItemFields().ToList(); + + var episodes = series.GetRecursiveChildren(user) + .OfType<Episode>(); + + var sortOrder = ItemSortBy.SortName; + + if (!string.IsNullOrEmpty(request.SeasonId)) + { + var season = _libraryManager.GetItemById(new Guid(request.SeasonId)) as Season; + + if (season.IndexNumber.HasValue) + { + episodes = FilterEpisodesBySeason(episodes, season.IndexNumber.Value, true); + + sortOrder = ItemSortBy.AiredEpisodeOrder; + } + else + { + episodes = season.RecursiveChildren.OfType<Episode>(); + + sortOrder = ItemSortBy.SortName; + } + } + + else if (request.Season.HasValue) + { + episodes = FilterEpisodesBySeason(episodes, request.Season.Value, true); + + sortOrder = ItemSortBy.AiredEpisodeOrder; + } + + var config = user.Configuration; + + if (!config.DisplayMissingEpisodes) + { + episodes = episodes.Where(i => !i.IsMissingEpisode); + } + if (!config.DisplayUnairedEpisodes) + { + episodes = episodes.Where(i => !i.IsVirtualUnaired); + } + + if (request.IsMissing.HasValue) + { + var val = request.IsMissing.Value; + episodes = episodes.Where(i => i.IsMissingEpisode == val); + } + + if (request.IsVirtualUnaired.HasValue) + { + var val = request.IsVirtualUnaired.Value; + episodes = episodes.Where(i => i.IsVirtualUnaired == val); + } + + episodes = _libraryManager.Sort(episodes, user, new[] { sortOrder }, SortOrder.Ascending) + .Cast<Episode>(); + + // This must be the last filter + if (!string.IsNullOrEmpty(request.AdjacentTo)) + { + episodes = ItemsService.FilterForAdjacency(episodes, request.AdjacentTo) + .Cast<Episode>(); + } + + var returnItems = episodes.Select(i => _dtoService.GetBaseItemDto(i, fields, user)) + .ToArray(); + + return new ItemsResult + { + TotalRecordCount = returnItems.Length, + Items = returnItems + }; + } + + internal static IEnumerable<Episode> FilterEpisodesBySeason(IEnumerable<Episode> episodes, int seasonNumber, bool includeSpecials) + { + if (!includeSpecials || seasonNumber < 1) + { + return episodes.Where(i => (i.PhysicalSeasonNumber ?? -1) == seasonNumber); + } + + return episodes.Where(i => + { + var episode = i; + + if (episode != null) + { + var currentSeasonNumber = episode.AiredSeasonNumber; + + return currentSeasonNumber.HasValue && currentSeasonNumber.Value == seasonNumber; + } + + return false; + }); + } } } diff --git a/MediaBrowser.Api/UserLibrary/ArtistsService.cs b/MediaBrowser.Api/UserLibrary/ArtistsService.cs index ca626116a..4dd3d744f 100644 --- a/MediaBrowser.Api/UserLibrary/ArtistsService.cs +++ b/MediaBrowser.Api/UserLibrary/ArtistsService.cs @@ -43,7 +43,7 @@ namespace MediaBrowser.Api.UserLibrary /// <summary> /// Class ArtistsService /// </summary> - public class ArtistsService : BaseItemsByNameService<Artist> + public class ArtistsService : BaseItemsByNameService<MusicArtist> { /// <summary> /// Initializes a new instance of the <see cref="ArtistsService" /> class. @@ -109,24 +109,9 @@ namespace MediaBrowser.Api.UserLibrary /// <param name="request">The request.</param> /// <param name="items">The items.</param> /// <returns>IEnumerable{Tuple{System.StringFunc{System.Int32}}}.</returns> - protected override IEnumerable<Artist> GetAllItems(GetItemsByName request, IEnumerable<BaseItem> items) + protected override IEnumerable<MusicArtist> GetAllItems(GetItemsByName request, IEnumerable<BaseItem> items) { - var itemsList = items.OfType<Audio>().ToList(); - - return itemsList - .SelectMany(i => - { - var list = new List<string>(); - - if (!string.IsNullOrEmpty(i.AlbumArtist)) - { - list.Add(i.AlbumArtist); - } - list.AddRange(i.Artists); - - return list; - }) - .Distinct(StringComparer.OrdinalIgnoreCase) + return LibraryManager.GetAllArtists(items) .Select(name => LibraryManager.GetArtist(name)); } } diff --git a/MediaBrowser.Api/UserLibrary/BaseItemsRequest.cs b/MediaBrowser.Api/UserLibrary/BaseItemsRequest.cs index eba36d856..6aa87e499 100644 --- a/MediaBrowser.Api/UserLibrary/BaseItemsRequest.cs +++ b/MediaBrowser.Api/UserLibrary/BaseItemsRequest.cs @@ -7,7 +7,7 @@ using System; namespace MediaBrowser.Api.UserLibrary { - public abstract class BaseItemsRequest + public abstract class BaseItemsRequest : IHasItemFields { /// <summary> /// Skips over a given number of items within the results. Use for paging. @@ -110,32 +110,6 @@ namespace MediaBrowser.Api.UserLibrary } /// <summary> - /// Gets the item fields. - /// </summary> - /// <returns>IEnumerable{ItemFields}.</returns> - public IEnumerable<ItemFields> GetItemFields() - { - var val = Fields; - - if (string.IsNullOrEmpty(val)) - { - return new ItemFields[] { }; - } - - return val.Split(',').Select(v => - { - ItemFields value; - - if (Enum.TryParse(v, true, out value)) - { - return (ItemFields?)value; - } - return null; - - }).Where(i => i.HasValue).Select(i => i.Value); - } - - /// <summary> /// Gets the image types. /// </summary> /// <returns>IEnumerable{ImageType}.</returns> diff --git a/MediaBrowser.Api/UserLibrary/ItemsService.cs b/MediaBrowser.Api/UserLibrary/ItemsService.cs index 1c7722e58..3a7ea5ddd 100644 --- a/MediaBrowser.Api/UserLibrary/ItemsService.cs +++ b/MediaBrowser.Api/UserLibrary/ItemsService.cs @@ -1,10 +1,13 @@ -using MediaBrowser.Controller.Dto; +using System.Globalization; +using System.IO; +using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.Audio; using MediaBrowser.Controller.Entities.Movies; using MediaBrowser.Controller.Entities.TV; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Localization; +using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Entities; using MediaBrowser.Model.Querying; using ServiceStack.ServiceHost; @@ -205,6 +208,27 @@ namespace MediaBrowser.Api.UserLibrary [ApiMember(Name = "AiredDuringSeason", Description = "Gets all episodes that aired during a season, including specials.", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")] public int? AiredDuringSeason { get; set; } + + [ApiMember(Name = "MinPremiereDate", Description = "Optional. The minimum premiere date. Format = yyyyMMddHHmmss", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "POST")] + public string MinPremiereDate { get; set; } + + [ApiMember(Name = "MaxPremiereDate", Description = "Optional. The maximum premiere date. Format = yyyyMMddHHmmss", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "POST")] + public string MaxPremiereDate { get; set; } + + [ApiMember(Name = "HasOverview", Description = "Optional filter by items that have an overview or not.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")] + public bool? HasOverview { get; set; } + + [ApiMember(Name = "HasImdbId", Description = "Optional filter by items that have an imdb id or not.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")] + public bool? HasImdbId { get; set; } + + [ApiMember(Name = "HasTmdbId", Description = "Optional filter by items that have a tmdb id or not.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")] + public bool? HasTmdbId { get; set; } + + [ApiMember(Name = "HasTvdbId", Description = "Optional filter by items that have a tvdb id or not.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")] + public bool? HasTvdbId { get; set; } + + [ApiMember(Name = "IsYearMismatched", Description = "Optional filter by items that are potentially misidentified.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")] + public bool? IsYearMismatched { get; set; } } /// <summary> @@ -286,6 +310,12 @@ namespace MediaBrowser.Api.UserLibrary items = ApplySortOrder(request, items, user, _libraryManager); + // This must be the last filter + if (!string.IsNullOrEmpty(request.AdjacentTo)) + { + items = FilterForAdjacency(items, request.AdjacentTo); + } + var itemsArray = items.ToList(); var pagedItems = ApplyPaging(request, itemsArray); @@ -642,30 +672,6 @@ namespace MediaBrowser.Api.UserLibrary }); } - if (!string.IsNullOrEmpty(request.AdjacentTo)) - { - var item = _dtoService.GetItemByDtoId(request.AdjacentTo); - - var allSiblings = item.Parent.GetChildren(user, true).OrderBy(i => i.SortName).ToList(); - - var index = allSiblings.IndexOf(item); - - var previousId = Guid.Empty; - var nextId = Guid.Empty; - - if (index > 0) - { - previousId = allSiblings[index - 1].Id; - } - - if (index < allSiblings.Count - 1) - { - nextId = allSiblings[index + 1].Id; - } - - items = items.Where(i => i.Id == previousId || i.Id == nextId); - } - // Min index number if (request.MinIndexNumber.HasValue) { @@ -861,7 +867,19 @@ namespace MediaBrowser.Api.UserLibrary if (request.HasTrailer.HasValue) { - items = items.Where(i => request.HasTrailer.Value ? i.LocalTrailerIds.Count > 0 : i.LocalTrailerIds.Count == 0); + var val = request.HasTrailer.Value; + items = items.Where(i => + { + var trailerCount = 0; + + var hasTrailers = i as IHasTrailers; + if (hasTrailers != null) + { + trailerCount = hasTrailers.LocalTrailerIds.Count; + } + + return val ? trailerCount > 0 : trailerCount == 0; + }); } if (request.HasThemeSong.HasValue) @@ -1005,26 +1023,134 @@ namespace MediaBrowser.Api.UserLibrary if (request.AiredDuringSeason.HasValue) { - var val = request.AiredDuringSeason.Value; + items = TvShowsService.FilterEpisodesBySeason(items.OfType<Episode>(), request.AiredDuringSeason.Value, true); + } + + if (!string.IsNullOrEmpty(request.MinPremiereDate)) + { + var date = DateTime.ParseExact(request.MinPremiereDate, "yyyyMMddHHmmss", CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal); + + items = items.Where(i => i.PremiereDate.HasValue && i.PremiereDate.Value >= date); + } + + if (!string.IsNullOrEmpty(request.MaxPremiereDate)) + { + var date = DateTime.ParseExact(request.MaxPremiereDate, "yyyyMMddHHmmss", CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal); + + items = items.Where(i => i.PremiereDate.HasValue && i.PremiereDate.Value <= date); + } + + if (request.HasOverview.HasValue) + { + var filterValue = request.HasOverview.Value; items = items.Where(i => { - var episode = i as Episode; + var hasValue = !string.IsNullOrEmpty(i.Overview); - if (episode != null) - { - var seasonNumber = episode.AirsAfterSeasonNumber ?? episode.AirsBeforeEpisodeNumber ?? episode.ParentIndexNumber; + return hasValue == filterValue; + }); + } - return episode.PremiereDate.HasValue && seasonNumber.HasValue && seasonNumber.Value == val; - } + if (request.HasImdbId.HasValue) + { + var filterValue = request.HasImdbId.Value; - return false; + items = items.Where(i => + { + var hasValue = !string.IsNullOrEmpty(i.GetProviderId(MetadataProviders.Imdb)); + + return hasValue == filterValue; }); } + if (request.HasTmdbId.HasValue) + { + var filterValue = request.HasTmdbId.Value; + + items = items.Where(i => + { + var hasValue = !string.IsNullOrEmpty(i.GetProviderId(MetadataProviders.Tmdb)); + + return hasValue == filterValue; + }); + } + + if (request.HasTvdbId.HasValue) + { + var filterValue = request.HasTvdbId.Value; + + items = items.Where(i => + { + var hasValue = !string.IsNullOrEmpty(i.GetProviderId(MetadataProviders.Tvdb)); + + return hasValue == filterValue; + }); + } + + if (request.IsYearMismatched.HasValue) + { + var filterValue = request.IsYearMismatched.Value; + + items = items.Where(i => IsYearMismatched(i) == filterValue); + } + return items; } + private bool IsYearMismatched(BaseItem item) + { + if (item.ProductionYear.HasValue) + { + var path = item.Path; + + if (!string.IsNullOrEmpty(path)) + { + int? yearInName; + string name; + NameParser.ParseName(Path.GetFileName(path), out name, out yearInName); + + // Go up a level if we didn't get a year + if (!yearInName.HasValue) + { + NameParser.ParseName(Path.GetFileName(Path.GetDirectoryName(path)), out name, out yearInName); + } + + if (yearInName.HasValue) + { + return yearInName.Value != item.ProductionYear.Value; + } + } + } + + return false; + } + + internal static IEnumerable<BaseItem> FilterForAdjacency(IEnumerable<BaseItem> items, string adjacentToId) + { + var list = items.ToList(); + + var adjacentToIdGuid = new Guid(adjacentToId); + var adjacentToItem = list.FirstOrDefault(i => i.Id == adjacentToIdGuid); + + var index = list.IndexOf(adjacentToItem); + + var previousId = Guid.Empty; + var nextId = Guid.Empty; + + if (index > 0) + { + previousId = list[index - 1].Id; + } + + if (index < list.Count - 1) + { + nextId = list[index + 1].Id; + } + + return list.Where(i => i.Id == previousId || i.Id == nextId); + } + /// <summary> /// Determines whether the specified item has image. /// </summary> diff --git a/MediaBrowser.Api/UserLibrary/PersonsService.cs b/MediaBrowser.Api/UserLibrary/PersonsService.cs index 885a4a3e6..09b5ef09f 100644 --- a/MediaBrowser.Api/UserLibrary/PersonsService.cs +++ b/MediaBrowser.Api/UserLibrary/PersonsService.cs @@ -155,7 +155,7 @@ namespace MediaBrowser.Api.UserLibrary /// <returns>IEnumerable{PersonInfo}.</returns> private IEnumerable<PersonInfo> GetAllPeople(IEnumerable<BaseItem> itemsList, string[] personTypes) { - var people = itemsList.SelectMany(i => i.People.OrderBy(p => p.Type)); + var people = itemsList.SelectMany(i => i.People.OrderBy(p => p.SortOrder ?? int.MaxValue).ThenBy(p => p.Type)); return personTypes.Length == 0 ? diff --git a/MediaBrowser.Api/UserLibrary/UserLibraryService.cs b/MediaBrowser.Api/UserLibrary/UserLibraryService.cs index 6b7980b1f..e9fc52468 100644 --- a/MediaBrowser.Api/UserLibrary/UserLibraryService.cs +++ b/MediaBrowser.Api/UserLibrary/UserLibraryService.cs @@ -489,7 +489,15 @@ namespace MediaBrowser.Api.UserLibrary // Get everything var fields = Enum.GetNames(typeof(ItemFields)).Select(i => (ItemFields)Enum.Parse(typeof(ItemFields), i, true)).ToList(); - var dtos = item.LocalTrailerIds + var trailerIds = new List<Guid>(); + + var hasTrailers = item as IHasTrailers; + if (hasTrailers != null) + { + trailerIds = hasTrailers.LocalTrailerIds; + } + + var dtos = trailerIds .Select(_libraryManager.GetItemById) .OrderBy(i => i.SortName) .Select(i => _dtoService.GetBaseItemDto(i, fields, user, item)); diff --git a/MediaBrowser.Api/packages.config b/MediaBrowser.Api/packages.config index c9fec8100..e9a27e8ad 100644 --- a/MediaBrowser.Api/packages.config +++ b/MediaBrowser.Api/packages.config @@ -1,6 +1,6 @@ <?xml version="1.0" encoding="utf-8"?> <packages> <package id="morelinq" version="1.0.16006" targetFramework="net45" /> - <package id="ServiceStack.Common" version="3.9.62" targetFramework="net45" /> - <package id="ServiceStack.Text" version="3.9.62" targetFramework="net45" /> + <package id="ServiceStack.Common" version="3.9.70" targetFramework="net45" /> + <package id="ServiceStack.Text" version="3.9.70" targetFramework="net45" /> </packages>
\ No newline at end of file diff --git a/MediaBrowser.Common.Implementations/BaseApplicationHost.cs b/MediaBrowser.Common.Implementations/BaseApplicationHost.cs index ee22b7baa..e8f4d51eb 100644 --- a/MediaBrowser.Common.Implementations/BaseApplicationHost.cs +++ b/MediaBrowser.Common.Implementations/BaseApplicationHost.cs @@ -22,7 +22,6 @@ using System.Collections.Generic; using System.IO; using System.Linq; using System.Net; -using System.Net.Http; using System.Reflection; using System.Threading; using System.Threading.Tasks; @@ -34,7 +33,7 @@ namespace MediaBrowser.Common.Implementations /// </summary> /// <typeparam name="TApplicationPathsType">The type of the T application paths type.</typeparam> public abstract class BaseApplicationHost<TApplicationPathsType> : IApplicationHost - where TApplicationPathsType : class, IApplicationPaths, new() + where TApplicationPathsType : class, IApplicationPaths { /// <summary> /// Occurs when [has pending restart changed]. @@ -84,7 +83,7 @@ namespace MediaBrowser.Common.Implementations /// <summary> /// The json serializer /// </summary> - public readonly IJsonSerializer JsonSerializer = new JsonSerializer(); + public IJsonSerializer JsonSerializer { get; private set; } /// <summary> /// The _XML serializer @@ -154,7 +153,7 @@ namespace MediaBrowser.Common.Implementations protected IInstallationManager InstallationManager { get; private set; } protected IFileSystem FileSystemManager { get; private set; } - + /// <summary> /// Gets or sets the zip client. /// </summary> @@ -182,6 +181,8 @@ namespace MediaBrowser.Common.Implementations /// <returns>Task.</returns> public virtual async Task Init() { + JsonSerializer = CreateJsonSerializer(); + IsFirstRun = !ConfigurationManager.CommonConfiguration.IsStartupWizardCompleted; Logger = LogManager.GetLogger("App"); @@ -213,19 +214,24 @@ namespace MediaBrowser.Common.Implementations } + protected virtual IJsonSerializer CreateJsonSerializer() + { + return new JsonSerializer(); + } + private void SetHttpLimit() { try { // Increase the max http request limit - ServicePointManager.DefaultConnectionLimit = Math.Max(48, ServicePointManager.DefaultConnectionLimit); + ServicePointManager.DefaultConnectionLimit = Math.Max(96, ServicePointManager.DefaultConnectionLimit); } catch (Exception ex) { Logger.ErrorException("Error setting http limit", ex); } } - + /// <summary> /// Installs the iso mounters. /// </summary> @@ -353,7 +359,7 @@ namespace MediaBrowser.Common.Implementations FileSystemManager = CreateFileSystemManager(); RegisterSingleInstance(FileSystemManager); - HttpClient = new HttpClientManager.HttpClientManager(ApplicationPaths, Logger, CreateHttpClient, FileSystemManager); + HttpClient = new HttpClientManager.HttpClientManager(ApplicationPaths, Logger, FileSystemManager); RegisterSingleInstance(HttpClient); NetworkManager = CreateNetworkManager(); @@ -378,8 +384,6 @@ namespace MediaBrowser.Common.Implementations return new CommonFileSystem(Logger, true); } - protected abstract HttpClient CreateHttpClient(bool enableHttpCompression); - /// <summary> /// Gets a list of types within an assembly /// This will handle situations that would normally throw an exception - such as a type within the assembly that depends on some other non-existant reference diff --git a/MediaBrowser.Common.Implementations/BaseApplicationPaths.cs b/MediaBrowser.Common.Implementations/BaseApplicationPaths.cs index bee5e5dc4..ba68d1f5a 100644 --- a/MediaBrowser.Common.Implementations/BaseApplicationPaths.cs +++ b/MediaBrowser.Common.Implementations/BaseApplicationPaths.cs @@ -20,21 +20,23 @@ namespace MediaBrowser.Common.Implementations /// <summary> /// Initializes a new instance of the <see cref="BaseApplicationPaths" /> class. /// </summary> - /// <param name="useDebugPath">if set to <c>true</c> [use debug paths].</param> - protected BaseApplicationPaths(bool useDebugPath) + protected BaseApplicationPaths(bool useDebugPath, string applicationPath) { _useDebugPath = useDebugPath; + ApplicationPath = applicationPath; } /// <summary> /// Initializes a new instance of the <see cref="BaseApplicationPaths"/> class. /// </summary> - /// <param name="programDataPath">The program data path.</param> - protected BaseApplicationPaths(string programDataPath) + protected BaseApplicationPaths(string programDataPath, string applicationPath) { _programDataPath = programDataPath; + ApplicationPath = applicationPath; } + public string ApplicationPath { get; private set; } + /// <summary> /// The _program data path /// </summary> diff --git a/MediaBrowser.Common.Implementations/HttpClientManager/HttpClientInfo.cs b/MediaBrowser.Common.Implementations/HttpClientManager/HttpClientInfo.cs index 33f7079df..8af6ef6c6 100644 --- a/MediaBrowser.Common.Implementations/HttpClientManager/HttpClientInfo.cs +++ b/MediaBrowser.Common.Implementations/HttpClientManager/HttpClientInfo.cs @@ -1,5 +1,4 @@ using System; -using System.Net.Http; namespace MediaBrowser.Common.Implementations.HttpClientManager { @@ -9,11 +8,6 @@ namespace MediaBrowser.Common.Implementations.HttpClientManager public class HttpClientInfo { /// <summary> - /// Gets or sets the HTTP client. - /// </summary> - /// <value>The HTTP client.</value> - public HttpClient HttpClient { get; set; } - /// <summary> /// Gets or sets the last timeout. /// </summary> /// <value>The last timeout.</value> diff --git a/MediaBrowser.Common.Implementations/HttpClientManager/HttpClientManager.cs b/MediaBrowser.Common.Implementations/HttpClientManager/HttpClientManager.cs index 0d6ba5c1d..214ed106d 100644 --- a/MediaBrowser.Common.Implementations/HttpClientManager/HttpClientManager.cs +++ b/MediaBrowser.Common.Implementations/HttpClientManager/HttpClientManager.cs @@ -9,7 +9,10 @@ using System.Collections.Generic; using System.Globalization; using System.IO; using System.Linq; +using System.Net; +using System.Net.Cache; using System.Net.Http; +using System.Reflection; using System.Text; using System.Threading; using System.Threading.Tasks; @@ -22,6 +25,11 @@ namespace MediaBrowser.Common.Implementations.HttpClientManager public class HttpClientManager : IHttpClient { /// <summary> + /// When one request to a host times out, we'll ban all other requests for this period of time, to prevent scans from stalling + /// </summary> + private const int TimeoutSeconds = 30; + + /// <summary> /// The _logger /// </summary> private readonly ILogger _logger; @@ -31,23 +39,18 @@ namespace MediaBrowser.Common.Implementations.HttpClientManager /// </summary> private readonly IApplicationPaths _appPaths; - public delegate HttpClient GetHttpClientHandler(bool enableHttpCompression); - - private readonly GetHttpClientHandler _getHttpClientHandler; private readonly IFileSystem _fileSystem; /// <summary> - /// Initializes a new instance of the <see cref="HttpClientManager"/> class. + /// Initializes a new instance of the <see cref="HttpClientManager" /> class. /// </summary> /// <param name="appPaths">The app paths.</param> /// <param name="logger">The logger.</param> - /// <param name="getHttpClientHandler">The get HTTP client handler.</param> - /// <exception cref="System.ArgumentNullException"> - /// appPaths + /// <param name="fileSystem">The file system.</param> + /// <exception cref="System.ArgumentNullException">appPaths /// or - /// logger - /// </exception> - public HttpClientManager(IApplicationPaths appPaths, ILogger logger, GetHttpClientHandler getHttpClientHandler, IFileSystem fileSystem) + /// logger</exception> + public HttpClientManager(IApplicationPaths appPaths, ILogger logger, IFileSystem fileSystem) { if (appPaths == null) { @@ -59,7 +62,6 @@ namespace MediaBrowser.Common.Implementations.HttpClientManager } _logger = logger; - _getHttpClientHandler = getHttpClientHandler; _fileSystem = fileSystem; _appPaths = appPaths; } @@ -91,111 +93,83 @@ namespace MediaBrowser.Common.Implementations.HttpClientManager if (!_httpClients.TryGetValue(key, out client)) { - client = new HttpClientInfo - { + client = new HttpClientInfo(); - HttpClient = _getHttpClientHandler(enableHttpCompression) - }; _httpClients.TryAdd(key, client); } return client; } - public async Task<HttpResponseInfo> GetResponse(HttpRequestOptions options) + private WebRequest GetMonoRequest(HttpRequestOptions options, string method, bool enableHttpCompression) { - ValidateParams(options.Url, options.CancellationToken); + var request = WebRequest.Create(options.Url); - options.CancellationToken.ThrowIfCancellationRequested(); - - var client = GetHttpClient(GetHostFromUrl(options.Url), options.EnableHttpCompression); - - if ((DateTime.UtcNow - client.LastTimeout).TotalSeconds < 30) + if (!string.IsNullOrEmpty(options.AcceptHeader)) { - throw new HttpException(string.Format("Cancelling connection to {0} due to a previous timeout.", options.Url)) { IsTimedOut = true }; + request.Headers.Add("Accept", options.AcceptHeader); } - using (var message = GetHttpRequestMessage(options)) - { - if (options.ResourcePool != null) - { - await options.ResourcePool.WaitAsync(options.CancellationToken).ConfigureAwait(false); - } - - if ((DateTime.UtcNow - client.LastTimeout).TotalSeconds < 30) - { - if (options.ResourcePool != null) - { - options.ResourcePool.Release(); - } - - throw new HttpException(string.Format("Connection to {0} timed out", options.Url)) { IsTimedOut = true }; - } - - _logger.Info("HttpClientManager.Get url: {0}", options.Url); - - try - { - options.CancellationToken.ThrowIfCancellationRequested(); - - var response = await client.HttpClient.SendAsync(message, HttpCompletionOption.ResponseContentRead, options.CancellationToken).ConfigureAwait(false); - - EnsureSuccessStatusCode(response); - - options.CancellationToken.ThrowIfCancellationRequested(); - - return new HttpResponseInfo - { - Content = await response.Content.ReadAsStreamAsync().ConfigureAwait(false), + request.CachePolicy = new RequestCachePolicy(RequestCacheLevel.Revalidate); + request.ConnectionGroupName = GetHostFromUrl(options.Url); + request.Method = method; + request.Timeout = 20000; - StatusCode = response.StatusCode, + if (!string.IsNullOrEmpty(options.UserAgent)) + { + request.Headers.Add("User-Agent", options.UserAgent); + } - ContentType = response.Content.Headers.ContentType.MediaType - }; - } - catch (OperationCanceledException ex) - { - var exception = GetCancellationException(options.Url, options.CancellationToken, ex); + return request; + } - var httpException = exception as HttpException; + private PropertyInfo _httpBehaviorPropertyInfo; + private WebRequest GetRequest(HttpRequestOptions options, string method, bool enableHttpCompression) + { +#if __MonoCS__ + return GetMonoRequest(options, method, enableHttpCompression); +#endif + + var request = HttpWebRequest.CreateHttp(options.Url); - if (httpException != null && httpException.IsTimedOut) - { - client.LastTimeout = DateTime.UtcNow; - } + if (!string.IsNullOrEmpty(options.AcceptHeader)) + { + request.Accept = options.AcceptHeader; + } - throw exception; - } - catch (HttpRequestException ex) - { - _logger.ErrorException("Error getting response from " + options.Url, ex); + request.AutomaticDecompression = enableHttpCompression ? DecompressionMethods.Deflate : DecompressionMethods.None; + request.CachePolicy = new RequestCachePolicy(RequestCacheLevel.Revalidate); + request.ConnectionGroupName = GetHostFromUrl(options.Url); + request.KeepAlive = true; + request.Method = method; + request.Pipelined = true; + request.Timeout = 20000; - throw new HttpException(ex.Message, ex); - } - catch (Exception ex) - { - _logger.ErrorException("Error getting response from " + options.Url, ex); + if (!string.IsNullOrEmpty(options.UserAgent)) + { + request.UserAgent = options.UserAgent; + } - throw; - } - finally - { - if (options.ResourcePool != null) - { - options.ResourcePool.Release(); - } - } + // This is a hack to prevent KeepAlive from getting disabled internally by the HttpWebRequest + // May need to remove this for mono + var sp = request.ServicePoint; + if (_httpBehaviorPropertyInfo == null) + { + _httpBehaviorPropertyInfo = sp.GetType().GetProperty("HttpBehaviour", BindingFlags.Instance | BindingFlags.NonPublic); } + _httpBehaviorPropertyInfo.SetValue(sp, (byte)0, null); + + return request; } /// <summary> - /// Performs a GET request and returns the resulting stream + /// Gets the response internal. /// </summary> /// <param name="options">The options.</param> - /// <returns>Task{Stream}.</returns> - /// <exception cref="HttpException"></exception> - /// <exception cref="MediaBrowser.Model.Net.HttpException"></exception> - public async Task<Stream> Get(HttpRequestOptions options) + /// <returns>Task{HttpResponseInfo}.</returns> + /// <exception cref="HttpException"> + /// </exception> + public async Task<HttpResponseInfo> GetResponse(HttpRequestOptions options) { ValidateParams(options.Url, options.CancellationToken); @@ -203,73 +177,97 @@ namespace MediaBrowser.Common.Implementations.HttpClientManager var client = GetHttpClient(GetHostFromUrl(options.Url), options.EnableHttpCompression); - if ((DateTime.UtcNow - client.LastTimeout).TotalSeconds < 30) + if ((DateTime.UtcNow - client.LastTimeout).TotalSeconds < TimeoutSeconds) { throw new HttpException(string.Format("Cancelling connection to {0} due to a previous timeout.", options.Url)) { IsTimedOut = true }; } - using (var message = GetHttpRequestMessage(options)) + var httpWebRequest = GetRequest(options, "GET", options.EnableHttpCompression); + + if (options.ResourcePool != null) + { + await options.ResourcePool.WaitAsync(options.CancellationToken).ConfigureAwait(false); + } + + if ((DateTime.UtcNow - client.LastTimeout).TotalSeconds < TimeoutSeconds) { if (options.ResourcePool != null) { - await options.ResourcePool.WaitAsync(options.CancellationToken).ConfigureAwait(false); + options.ResourcePool.Release(); } - if ((DateTime.UtcNow - client.LastTimeout).TotalSeconds < 30) - { - if (options.ResourcePool != null) - { - options.ResourcePool.Release(); - } + throw new HttpException(string.Format("Connection to {0} timed out", options.Url)) { IsTimedOut = true }; + } - throw new HttpException(string.Format("Connection to {0} timed out", options.Url)) { IsTimedOut = true }; - } + _logger.Info("HttpClientManager.GET url: {0}", options.Url); - _logger.Info("HttpClientManager.Get url: {0}", options.Url); + try + { + options.CancellationToken.ThrowIfCancellationRequested(); - try + using (var response = await httpWebRequest.GetResponseAsync().ConfigureAwait(false)) { + var httpResponse = (HttpWebResponse)response; + + EnsureSuccessStatusCode(httpResponse); + options.CancellationToken.ThrowIfCancellationRequested(); - var response = await client.HttpClient.SendAsync(message, HttpCompletionOption.ResponseContentRead, options.CancellationToken).ConfigureAwait(false); + using (var stream = httpResponse.GetResponseStream()) + { + var memoryStream = new MemoryStream(); - EnsureSuccessStatusCode(response); + await stream.CopyToAsync(memoryStream).ConfigureAwait(false); - options.CancellationToken.ThrowIfCancellationRequested(); + memoryStream.Position = 0; - return await response.Content.ReadAsStreamAsync().ConfigureAwait(false); - } - catch (OperationCanceledException ex) - { - var exception = GetCancellationException(options.Url, options.CancellationToken, ex); + return new HttpResponseInfo + { + Content = memoryStream, - var httpException = exception as HttpException; + StatusCode = httpResponse.StatusCode, - if (httpException != null && httpException.IsTimedOut) - { - client.LastTimeout = DateTime.UtcNow; + ContentType = httpResponse.ContentType + }; } - - throw exception; } - catch (HttpRequestException ex) - { - _logger.ErrorException("Error getting response from " + options.Url, ex); + } + catch (OperationCanceledException ex) + { + var exception = GetCancellationException(options.Url, options.CancellationToken, ex); - throw new HttpException(ex.Message, ex); - } - catch (Exception ex) - { - _logger.ErrorException("Error getting response from " + options.Url, ex); + var httpException = exception as HttpException; - throw; + if (httpException != null && httpException.IsTimedOut) + { + client.LastTimeout = DateTime.UtcNow; } - finally + + throw exception; + } + catch (HttpRequestException ex) + { + _logger.ErrorException("Error getting response from " + options.Url, ex); + + throw new HttpException(ex.Message, ex); + } + catch (WebException ex) + { + _logger.ErrorException("Error getting response from " + options.Url, ex); + + throw new HttpException(ex.Message, ex); + } + catch (Exception ex) + { + _logger.ErrorException("Error getting response from " + options.Url, ex); + + throw; + } + finally + { + if (options.ResourcePool != null) { - if (options.ResourcePool != null) - { - options.ResourcePool.Release(); - } + options.ResourcePool.Release(); } } } @@ -277,6 +275,20 @@ namespace MediaBrowser.Common.Implementations.HttpClientManager /// <summary> /// Performs a GET request and returns the resulting stream /// </summary> + /// <param name="options">The options.</param> + /// <returns>Task{Stream}.</returns> + /// <exception cref="HttpException"></exception> + /// <exception cref="MediaBrowser.Model.Net.HttpException"></exception> + public async Task<Stream> Get(HttpRequestOptions options) + { + var response = await GetResponse(options).ConfigureAwait(false); + + return response.Content; + } + + /// <summary> + /// Performs a GET request and returns the resulting stream + /// </summary> /// <param name="url">The URL.</param> /// <param name="resourcePool">The resource pool.</param> /// <param name="cancellationToken">The cancellation token.</param> @@ -305,65 +317,113 @@ namespace MediaBrowser.Common.Implementations.HttpClientManager /// <summary> /// Performs a POST request /// </summary> - /// <param name="url">The URL.</param> + /// <param name="options">The options.</param> /// <param name="postData">Params to add to the POST data.</param> - /// <param name="resourcePool">The resource pool.</param> - /// <param name="cancellationToken">The cancellation token.</param> /// <returns>stream on success, null on failure</returns> + /// <exception cref="HttpException"> + /// </exception> /// <exception cref="System.ArgumentNullException">postData</exception> /// <exception cref="MediaBrowser.Model.Net.HttpException"></exception> - public async Task<Stream> Post(string url, Dictionary<string, string> postData, SemaphoreSlim resourcePool, CancellationToken cancellationToken) + public async Task<Stream> Post(HttpRequestOptions options, Dictionary<string, string> postData) { - ValidateParams(url, cancellationToken); + ValidateParams(options.Url, options.CancellationToken); - if (postData == null) - { - throw new ArgumentNullException("postData"); - } + options.CancellationToken.ThrowIfCancellationRequested(); - cancellationToken.ThrowIfCancellationRequested(); + var httpWebRequest = GetRequest(options, "POST", options.EnableHttpCompression); var strings = postData.Keys.Select(key => string.Format("{0}={1}", key, postData[key])); var postContent = string.Join("&", strings.ToArray()); - var content = new StringContent(postContent, Encoding.UTF8, "application/x-www-form-urlencoded"); + var bytes = Encoding.UTF8.GetBytes(postContent); - if (resourcePool != null) + httpWebRequest.ContentType = "application/x-www-form-urlencoded"; + httpWebRequest.ContentLength = bytes.Length; + httpWebRequest.GetRequestStream().Write(bytes, 0, bytes.Length); + + if (options.ResourcePool != null) { - await resourcePool.WaitAsync(cancellationToken).ConfigureAwait(false); + await options.ResourcePool.WaitAsync(options.CancellationToken).ConfigureAwait(false); } - _logger.Info("HttpClientManager.Post url: {0}", url); + _logger.Info("HttpClientManager.POST url: {0}", options.Url); try { - cancellationToken.ThrowIfCancellationRequested(); + options.CancellationToken.ThrowIfCancellationRequested(); - var msg = await GetHttpClient(GetHostFromUrl(url), true).HttpClient.PostAsync(url, content, cancellationToken).ConfigureAwait(false); + using (var response = await httpWebRequest.GetResponseAsync().ConfigureAwait(false)) + { + var httpResponse = (HttpWebResponse)response; + + EnsureSuccessStatusCode(httpResponse); - EnsureSuccessStatusCode(msg); + options.CancellationToken.ThrowIfCancellationRequested(); - return await msg.Content.ReadAsStreamAsync().ConfigureAwait(false); + using (var stream = httpResponse.GetResponseStream()) + { + var memoryStream = new MemoryStream(); + + await stream.CopyToAsync(memoryStream).ConfigureAwait(false); + + memoryStream.Position = 0; + + return memoryStream; + } + } } catch (OperationCanceledException ex) { - throw GetCancellationException(url, cancellationToken, ex); + var exception = GetCancellationException(options.Url, options.CancellationToken, ex); + + throw exception; } catch (HttpRequestException ex) { - _logger.ErrorException("Error getting response from " + url, ex); + _logger.ErrorException("Error getting response from " + options.Url, ex); + + throw new HttpException(ex.Message, ex); + } + catch (WebException ex) + { + _logger.ErrorException("Error getting response from " + options.Url, ex); throw new HttpException(ex.Message, ex); } + catch (Exception ex) + { + _logger.ErrorException("Error getting response from " + options.Url, ex); + + throw; + } finally { - if (resourcePool != null) + if (options.ResourcePool != null) { - resourcePool.Release(); + options.ResourcePool.Release(); } } } /// <summary> + /// Performs a POST request + /// </summary> + /// <param name="url">The URL.</param> + /// <param name="postData">Params to add to the POST data.</param> + /// <param name="resourcePool">The resource pool.</param> + /// <param name="cancellationToken">The cancellation token.</param> + /// <returns>stream on success, null on failure</returns> + public Task<Stream> Post(string url, Dictionary<string, string> postData, SemaphoreSlim resourcePool, CancellationToken cancellationToken) + { + return Post(new HttpRequestOptions + { + Url = url, + ResourcePool = resourcePool, + CancellationToken = cancellationToken + + }, postData); + } + + /// <summary> /// Downloads the contents of a given url into a temporary location /// </summary> /// <param name="options">The options.</param> @@ -391,6 +451,8 @@ namespace MediaBrowser.Common.Implementations.HttpClientManager options.CancellationToken.ThrowIfCancellationRequested(); + var httpWebRequest = GetRequest(options, "GET", options.EnableHttpCompression); + if (options.ResourcePool != null) { await options.ResourcePool.WaitAsync(options.CancellationToken).ConfigureAwait(false); @@ -398,57 +460,68 @@ namespace MediaBrowser.Common.Implementations.HttpClientManager options.Progress.Report(0); - _logger.Info("HttpClientManager.GetTempFile url: {0}, temp file: {1}", options.Url, tempFile); + _logger.Info("HttpClientManager.GetTempFileResponse url: {0}", options.Url); try { options.CancellationToken.ThrowIfCancellationRequested(); - using (var message = GetHttpRequestMessage(options)) + using (var response = await httpWebRequest.GetResponseAsync().ConfigureAwait(false)) { - using (var response = await GetHttpClient(GetHostFromUrl(options.Url), options.EnableHttpCompression).HttpClient.SendAsync(message, HttpCompletionOption.ResponseHeadersRead, options.CancellationToken).ConfigureAwait(false)) - { - EnsureSuccessStatusCode(response); + var httpResponse = (HttpWebResponse)response; - options.CancellationToken.ThrowIfCancellationRequested(); + EnsureSuccessStatusCode(httpResponse); + + options.CancellationToken.ThrowIfCancellationRequested(); - var contentLength = GetContentLength(response); + var contentLength = GetContentLength(httpResponse); - if (!contentLength.HasValue) + if (!contentLength.HasValue) + { + // We're not able to track progress + using (var stream = httpResponse.GetResponseStream()) { - // We're not able to track progress - using (var stream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false)) + using (var fs = _fileSystem.GetFileStream(tempFile, FileMode.Create, FileAccess.Write, FileShare.Read, true)) { - using (var fs = _fileSystem.GetFileStream(tempFile, FileMode.Create, FileAccess.Write, FileShare.Read, true)) - { - await stream.CopyToAsync(fs, StreamDefaults.DefaultCopyToBufferSize, options.CancellationToken).ConfigureAwait(false); - } + await stream.CopyToAsync(fs, StreamDefaults.DefaultCopyToBufferSize, options.CancellationToken).ConfigureAwait(false); } } - else + } + else + { + using (var stream = ProgressStream.CreateReadProgressStream(httpResponse.GetResponseStream(), options.Progress.Report, contentLength.Value)) { - using (var stream = ProgressStream.CreateReadProgressStream(await response.Content.ReadAsStreamAsync().ConfigureAwait(false), options.Progress.Report, contentLength.Value)) + using (var fs = _fileSystem.GetFileStream(tempFile, FileMode.Create, FileAccess.Write, FileShare.Read, true)) { - using (var fs = _fileSystem.GetFileStream(tempFile, FileMode.Create, FileAccess.Write, FileShare.Read, true)) - { - await stream.CopyToAsync(fs, StreamDefaults.DefaultCopyToBufferSize, options.CancellationToken).ConfigureAwait(false); - } + await stream.CopyToAsync(fs, StreamDefaults.DefaultCopyToBufferSize, options.CancellationToken).ConfigureAwait(false); } } + } - options.Progress.Report(100); + options.Progress.Report(100); - return new HttpResponseInfo - { - TempFilePath = tempFile, + return new HttpResponseInfo + { + TempFilePath = tempFile, - StatusCode = response.StatusCode, + StatusCode = httpResponse.StatusCode, - ContentType = response.Content.Headers.ContentType.MediaType - }; - } + ContentType = httpResponse.ContentType + }; } } + catch (OperationCanceledException ex) + { + throw GetTempFileException(ex, options, tempFile); + } + catch (HttpRequestException ex) + { + throw GetTempFileException(ex, options, tempFile); + } + catch (WebException ex) + { + throw GetTempFileException(ex, options, tempFile); + } catch (Exception ex) { throw GetTempFileException(ex, options, tempFile); @@ -462,63 +535,16 @@ namespace MediaBrowser.Common.Implementations.HttpClientManager } } - /// <summary> - /// Gets the message. - /// </summary> - /// <param name="options">The options.</param> - /// <returns>HttpResponseMessage.</returns> - private HttpRequestMessage GetHttpRequestMessage(HttpRequestOptions options) + private long? GetContentLength(HttpWebResponse response) { - var message = new HttpRequestMessage(HttpMethod.Get, options.Url); + var length = response.ContentLength; - foreach (var pair in options.RequestHeaders.ToList()) - { - if (!message.Headers.TryAddWithoutValidation(pair.Key, pair.Value)) - { - _logger.Error("Unable to add request header {0} with value {1}", pair.Key, pair.Value); - } - } - - return message; - } - - /// <summary> - /// Gets the length of the content. - /// </summary> - /// <param name="response">The response.</param> - /// <returns>System.Nullable{System.Int64}.</returns> - private long? GetContentLength(HttpResponseMessage response) - { - IEnumerable<string> lengthValues = null; - - // Seeing some InvalidOperationException here under mono - try - { - response.Headers.TryGetValues("content-length", out lengthValues); - } - catch (InvalidOperationException ex) - { - _logger.ErrorException("Error accessing response.Headers.TryGetValues Content-Length", ex); - } - - if (lengthValues == null) - { - try - { - response.Content.Headers.TryGetValues("content-length", out lengthValues); - } - catch (InvalidOperationException ex) - { - _logger.ErrorException("Error accessing response.Content.Headers.TryGetValues Content-Length", ex); - } - } - - if (lengthValues == null) + if (length == 0) { return null; } - return long.Parse(string.Join(string.Empty, lengthValues.ToArray()), UsCulture); + return length; } protected static readonly CultureInfo UsCulture = new CultureInfo("en-US"); @@ -545,16 +571,23 @@ namespace MediaBrowser.Common.Implementations.HttpClientManager _logger.ErrorException("Error getting response from " + options.Url, ex); - var httpRequestException = ex as HttpRequestException; - // Cleanup DeleteTempFile(tempFile); + var httpRequestException = ex as HttpRequestException; + if (httpRequestException != null) { return new HttpException(ex.Message, ex); } + var webException = ex as WebException; + + if (webException != null) + { + return new HttpException(ex.Message, ex); + } + return ex; } @@ -613,11 +646,6 @@ namespace MediaBrowser.Common.Implementations.HttpClientManager { if (dispose) { - foreach (var client in _httpClients.Values.ToList()) - { - client.HttpClient.Dispose(); - } - _httpClients.Clear(); } } @@ -645,16 +673,14 @@ namespace MediaBrowser.Common.Implementations.HttpClientManager return exception; } - /// <summary> - /// Ensures the success status code. - /// </summary> - /// <param name="response">The response.</param> - /// <exception cref="MediaBrowser.Model.Net.HttpException"></exception> - private void EnsureSuccessStatusCode(HttpResponseMessage response) + private void EnsureSuccessStatusCode(HttpWebResponse response) { - if (!response.IsSuccessStatusCode) + var statusCode = response.StatusCode; + var isSuccessful = statusCode >= HttpStatusCode.OK && statusCode <= (HttpStatusCode)299; + + if (!isSuccessful) { - throw new HttpException(response.ReasonPhrase) { StatusCode = response.StatusCode }; + throw new HttpException(response.StatusDescription) { StatusCode = response.StatusCode }; } } diff --git a/MediaBrowser.Common.Implementations/MediaBrowser.Common.Implementations.csproj b/MediaBrowser.Common.Implementations/MediaBrowser.Common.Implementations.csproj index 9e48f3b3e..948785575 100644 --- a/MediaBrowser.Common.Implementations/MediaBrowser.Common.Implementations.csproj +++ b/MediaBrowser.Common.Implementations/MediaBrowser.Common.Implementations.csproj @@ -41,6 +41,10 @@ <SpecificVersion>False</SpecificVersion> <HintPath>..\packages\NLog.2.1.0\lib\net45\NLog.dll</HintPath> </Reference> + <Reference Include="ServiceStack.Text, Version=3.9.70.0, Culture=neutral, processorArchitecture=MSIL"> + <SpecificVersion>False</SpecificVersion> + <HintPath>..\packages\ServiceStack.Text.3.9.70\lib\net35\ServiceStack.Text.dll</HintPath> + </Reference> <Reference Include="SharpCompress"> <HintPath>..\packages\sharpcompress.0.10.1.3\lib\net40\SharpCompress.dll</HintPath> </Reference> @@ -55,9 +59,6 @@ <Reference Include="System.Net" /> <Reference Include="System.Net.Http" /> <Reference Include="System.Xml" /> - <Reference Include="ServiceStack.Text"> - <HintPath>..\packages\ServiceStack.Text.3.9.62\lib\net35\ServiceStack.Text.dll</HintPath> - </Reference> </ItemGroup> <ItemGroup> <Compile Include="..\SharedVersion.cs"> diff --git a/MediaBrowser.Common.Implementations/ScheduledTasks/Tasks/DeleteCacheFileTask.cs b/MediaBrowser.Common.Implementations/ScheduledTasks/Tasks/DeleteCacheFileTask.cs index e04cadfc5..6d886bc69 100644 --- a/MediaBrowser.Common.Implementations/ScheduledTasks/Tasks/DeleteCacheFileTask.cs +++ b/MediaBrowser.Common.Implementations/ScheduledTasks/Tasks/DeleteCacheFileTask.cs @@ -29,7 +29,6 @@ namespace MediaBrowser.Common.Implementations.ScheduledTasks.Tasks /// <summary> /// Initializes a new instance of the <see cref="DeleteCacheFileTask" /> class. /// </summary> - /// <param name="appPaths">The app paths.</param> public DeleteCacheFileTask(IApplicationPaths appPaths, ILogger logger, IFileSystem fileSystem) { ApplicationPaths = appPaths; diff --git a/MediaBrowser.Common.Implementations/Serialization/JsonSerializer.cs b/MediaBrowser.Common.Implementations/Serialization/JsonSerializer.cs index 4a6b9255c..9c2412b22 100644 --- a/MediaBrowser.Common.Implementations/Serialization/JsonSerializer.cs +++ b/MediaBrowser.Common.Implementations/Serialization/JsonSerializer.cs @@ -80,7 +80,7 @@ namespace MediaBrowser.Common.Implementations.Serialization using (Stream stream = File.OpenRead(file)) { - return ServiceStack.Text.JsonSerializer.DeserializeFromStream(type, stream); + return DeserializeFromStream(stream, type); } } @@ -101,7 +101,7 @@ namespace MediaBrowser.Common.Implementations.Serialization using (Stream stream = File.OpenRead(file)) { - return ServiceStack.Text.JsonSerializer.DeserializeFromStream<T>(stream); + return DeserializeFromStream<T>(stream); } } @@ -169,6 +169,8 @@ namespace MediaBrowser.Common.Implementations.Serialization ServiceStack.Text.JsConfig.DateHandler = ServiceStack.Text.JsonDateHandler.ISO8601; ServiceStack.Text.JsConfig.ExcludeTypeInfo = true; ServiceStack.Text.JsConfig.IncludeNullValues = false; + ServiceStack.Text.JsConfig.AlwaysUseUtc = true; + ServiceStack.Text.JsConfig.AssumeUtc = true; } /// <summary> diff --git a/MediaBrowser.Common.Implementations/Updates/InstallationManager.cs b/MediaBrowser.Common.Implementations/Updates/InstallationManager.cs index 56641296f..0581343d3 100644 --- a/MediaBrowser.Common.Implementations/Updates/InstallationManager.cs +++ b/MediaBrowser.Common.Implementations/Updates/InstallationManager.cs @@ -168,7 +168,7 @@ namespace MediaBrowser.Common.Implementations.Updates { // Let dev users get results more often for testing purposes var cacheLength = _config.CommonConfiguration.SystemUpdateLevel == PackageVersionClass.Dev - ? TimeSpan.FromMinutes(15) + ? TimeSpan.FromMinutes(5) : TimeSpan.FromHours(12); if ((DateTime.UtcNow - _lastPackageListResult.Item2) < cacheLength) diff --git a/MediaBrowser.Common.Implementations/packages.config b/MediaBrowser.Common.Implementations/packages.config index f2fe48830..269ac0e56 100644 --- a/MediaBrowser.Common.Implementations/packages.config +++ b/MediaBrowser.Common.Implementations/packages.config @@ -1,7 +1,7 @@ <?xml version="1.0" encoding="utf-8"?> <packages> <package id="NLog" version="2.1.0" targetFramework="net45" /> - <package id="ServiceStack.Text" version="3.9.62" targetFramework="net45" /> + <package id="ServiceStack.Text" version="3.9.70" targetFramework="net45" /> <package id="sharpcompress" version="0.10.1.3" targetFramework="net45" /> <package id="SimpleInjector" version="2.3.6" targetFramework="net45" /> </packages>
\ No newline at end of file diff --git a/MediaBrowser.Common/Configuration/IApplicationPaths.cs b/MediaBrowser.Common/Configuration/IApplicationPaths.cs index d2446ce46..d3bf03302 100644 --- a/MediaBrowser.Common/Configuration/IApplicationPaths.cs +++ b/MediaBrowser.Common/Configuration/IApplicationPaths.cs @@ -7,6 +7,12 @@ namespace MediaBrowser.Common.Configuration public interface IApplicationPaths { /// <summary> + /// Gets the application path. + /// </summary> + /// <value>The application path.</value> + string ApplicationPath { get; } + + /// <summary> /// Gets the path to the program data folder /// </summary> /// <value>The program data path.</value> diff --git a/MediaBrowser.Common/MediaBrowser.Common.csproj b/MediaBrowser.Common/MediaBrowser.Common.csproj index f4d759a4d..a9499dedd 100644 --- a/MediaBrowser.Common/MediaBrowser.Common.csproj +++ b/MediaBrowser.Common/MediaBrowser.Common.csproj @@ -35,18 +35,21 @@ <WarningLevel>4</WarningLevel> </PropertyGroup> <ItemGroup> - <Reference Include="System" /> - <Reference Include="System.Core" /> - <Reference Include="Microsoft.CSharp" /> - <Reference Include="ServiceStack.Common"> - <HintPath>..\packages\ServiceStack.Common.3.9.62\lib\net35\ServiceStack.Common.dll</HintPath> + <Reference Include="ServiceStack.Common, Version=3.9.70.0, Culture=neutral, processorArchitecture=MSIL"> + <SpecificVersion>False</SpecificVersion> + <HintPath>..\packages\ServiceStack.Common.3.9.70\lib\net35\ServiceStack.Common.dll</HintPath> </Reference> - <Reference Include="ServiceStack.Interfaces"> - <HintPath>..\packages\ServiceStack.Common.3.9.62\lib\net35\ServiceStack.Interfaces.dll</HintPath> + <Reference Include="ServiceStack.Interfaces, Version=1.0.0.0, Culture=neutral, processorArchitecture=MSIL"> + <SpecificVersion>False</SpecificVersion> + <HintPath>..\packages\ServiceStack.Common.3.9.70\lib\net35\ServiceStack.Interfaces.dll</HintPath> </Reference> - <Reference Include="ServiceStack.Text"> - <HintPath>..\packages\ServiceStack.Text.3.9.62\lib\net35\ServiceStack.Text.dll</HintPath> + <Reference Include="ServiceStack.Text, Version=3.9.70.0, Culture=neutral, processorArchitecture=MSIL"> + <SpecificVersion>False</SpecificVersion> + <HintPath>..\packages\ServiceStack.Text.3.9.70\lib\net35\ServiceStack.Text.dll</HintPath> </Reference> + <Reference Include="System" /> + <Reference Include="System.Core" /> + <Reference Include="Microsoft.CSharp" /> </ItemGroup> <ItemGroup> <Compile Include="..\SharedVersion.cs"> diff --git a/MediaBrowser.Common/Net/IHttpClient.cs b/MediaBrowser.Common/Net/IHttpClient.cs index 93348d4ca..54d6665e2 100644 --- a/MediaBrowser.Common/Net/IHttpClient.cs +++ b/MediaBrowser.Common/Net/IHttpClient.cs @@ -73,6 +73,11 @@ namespace MediaBrowser.Common.Net /// <exception cref="MediaBrowser.Model.Net.HttpException"></exception> Task<string> GetTempFile(HttpRequestOptions options); + /// <summary> + /// Gets the temporary file response. + /// </summary> + /// <param name="options">The options.</param> + /// <returns>Task{HttpResponseInfo}.</returns> Task<HttpResponseInfo> GetTempFileResponse(HttpRequestOptions options); } }
\ No newline at end of file diff --git a/MediaBrowser.Common/ScheduledTasks/IScheduledTask.cs b/MediaBrowser.Common/ScheduledTasks/IScheduledTask.cs index 351e96c7d..2ee4fb4b5 100644 --- a/MediaBrowser.Common/ScheduledTasks/IScheduledTask.cs +++ b/MediaBrowser.Common/ScheduledTasks/IScheduledTask.cs @@ -42,4 +42,9 @@ namespace MediaBrowser.Common.ScheduledTasks /// <returns>IEnumerable{BaseTaskTrigger}.</returns> IEnumerable<ITaskTrigger> GetDefaultTriggers(); } + + public interface IConfigurableScheduledTask + { + bool IsHidden { get; } + } } diff --git a/MediaBrowser.Common/ScheduledTasks/ScheduledTaskHelpers.cs b/MediaBrowser.Common/ScheduledTasks/ScheduledTaskHelpers.cs index f31650986..39148166b 100644 --- a/MediaBrowser.Common/ScheduledTasks/ScheduledTaskHelpers.cs +++ b/MediaBrowser.Common/ScheduledTasks/ScheduledTaskHelpers.cs @@ -16,6 +16,15 @@ namespace MediaBrowser.Common.ScheduledTasks /// <returns>TaskInfo.</returns> public static TaskInfo GetTaskInfo(IScheduledTaskWorker task) { + var isHidden = false; + + var configurableTask = task.ScheduledTask as IConfigurableScheduledTask; + + if (configurableTask != null) + { + isHidden = configurableTask.IsHidden; + } + return new TaskInfo { Name = task.Name, @@ -25,7 +34,8 @@ namespace MediaBrowser.Common.ScheduledTasks LastExecutionResult = task.LastExecutionResult, Triggers = task.Triggers.Select(GetTriggerInfo).ToList(), Description = task.Description, - Category = task.Category + Category = task.Category, + IsHidden = isHidden }; } diff --git a/MediaBrowser.Common/packages.config b/MediaBrowser.Common/packages.config index 6969b43c5..7411e313c 100644 --- a/MediaBrowser.Common/packages.config +++ b/MediaBrowser.Common/packages.config @@ -1,5 +1,5 @@ <?xml version="1.0" encoding="utf-8"?> <packages> - <package id="ServiceStack.Common" version="3.9.62" targetFramework="net45" /> - <package id="ServiceStack.Text" version="3.9.62" targetFramework="net45" /> + <package id="ServiceStack.Common" version="3.9.70" targetFramework="net45" /> + <package id="ServiceStack.Text" version="3.9.70" targetFramework="net45" /> </packages>
\ No newline at end of file diff --git a/MediaBrowser.Controller/Entities/Audio/Artist.cs b/MediaBrowser.Controller/Entities/Audio/Artist.cs deleted file mode 100644 index 947ee1122..000000000 --- a/MediaBrowser.Controller/Entities/Audio/Artist.cs +++ /dev/null @@ -1,86 +0,0 @@ -using MediaBrowser.Controller.Library; -using MediaBrowser.Model.Dto; -using MediaBrowser.Model.Entities; -using System; -using System.Collections.Generic; -using System.Globalization; -using System.Linq; -using System.Runtime.Serialization; - -namespace MediaBrowser.Controller.Entities.Audio -{ - /// <summary> - /// Class Artist - /// </summary> - public class Artist : BaseItem, IItemByName, IHasMusicGenres - { - public Artist() - { - UserItemCounts = new Dictionary<Guid, ItemByNameCounts>(); - } - - public string LastFmImageUrl { get; set; } - public string LastFmImageSize { get; set; } - - /// <summary> - /// Gets the user data key. - /// </summary> - /// <returns>System.String.</returns> - public override string GetUserDataKey() - { - return GetUserDataKey(this); - } - - [IgnoreDataMember] - public Dictionary<Guid, ItemByNameCounts> UserItemCounts { get; set; } - - /// <summary> - /// Finds the music artist. - /// </summary> - /// <param name="artist">The artist.</param> - /// <param name="libraryManager">The library manager.</param> - /// <returns>MusicArtist.</returns> - public static MusicArtist FindMusicArtist(Artist artist, ILibraryManager libraryManager) - { - return FindMusicArtist(artist, libraryManager.RootFolder.RecursiveChildren.OfType<MusicArtist>()); - } - - /// <summary> - /// Finds the music artist. - /// </summary> - /// <param name="artist">The artist.</param> - /// <param name="allMusicArtists">All music artists.</param> - /// <returns>MusicArtist.</returns> - public static MusicArtist FindMusicArtist(Artist artist, IEnumerable<MusicArtist> allMusicArtists) - { - var musicBrainzId = artist.GetProviderId(MetadataProviders.Musicbrainz); - - return allMusicArtists.FirstOrDefault(i => - { - if (!string.IsNullOrWhiteSpace(musicBrainzId) && string.Equals(musicBrainzId, i.GetProviderId(MetadataProviders.Musicbrainz), StringComparison.OrdinalIgnoreCase)) - { - return true; - } - - return string.Compare(i.Name, artist.Name, CultureInfo.CurrentCulture, CompareOptions.IgnoreNonSpace | CompareOptions.IgnoreCase | CompareOptions.IgnoreSymbols) == 0; - }); - } - - /// <summary> - /// Gets the user data key. - /// </summary> - /// <param name="item">The item.</param> - /// <returns>System.String.</returns> - public static string GetUserDataKey(BaseItem item) - { - var id = item.GetProviderId(MetadataProviders.Musicbrainz); - - if (!string.IsNullOrEmpty(id)) - { - return "Artist-Musicbrainz-" + id; - } - - return "Artist-" + item.Name; - } - } -} diff --git a/MediaBrowser.Controller/Entities/Audio/MusicArtist.cs b/MediaBrowser.Controller/Entities/Audio/MusicArtist.cs index d791c92ae..d5572b9a5 100644 --- a/MediaBrowser.Controller/Entities/Audio/MusicArtist.cs +++ b/MediaBrowser.Controller/Entities/Audio/MusicArtist.cs @@ -1,11 +1,65 @@ - +using MediaBrowser.Model.Dto; +using MediaBrowser.Model.Entities; +using System; +using System.Collections.Generic; +using System.Runtime.Serialization; +using System.Threading; +using System.Threading.Tasks; + namespace MediaBrowser.Controller.Entities.Audio { /// <summary> /// Class MusicArtist /// </summary> - public class MusicArtist : Folder + public class MusicArtist : Folder, IItemByName, IHasMusicGenres, IHasDualAccess { + [IgnoreDataMember] + public List<ItemByNameCounts> UserItemCountList { get; set; } + + public bool IsAccessedByName { get; set; } + + public override bool IsFolder + { + get + { + return !IsAccessedByName; + } + } + + protected override IEnumerable<BaseItem> ActualChildren + { + get + { + if (IsAccessedByName) + { + return new List<BaseItem>(); + } + + return base.ActualChildren; + } + } + + protected override Task ValidateChildrenInternal(IProgress<double> progress, CancellationToken cancellationToken, bool? recursive = null, bool forceRefreshMetadata = false) + { + if (IsAccessedByName) + { + // Should never get in here anyway + return Task.FromResult(true); + } + + return base.ValidateChildrenInternal(progress, cancellationToken, recursive, forceRefreshMetadata); + } + + public override string GetClientTypeName() + { + if (IsAccessedByName) + { + //return "Artist"; + } + + return base.GetClientTypeName(); + } + /// <summary> /// Gets or sets the last fm image URL. /// </summary> @@ -13,13 +67,35 @@ namespace MediaBrowser.Controller.Entities.Audio public string LastFmImageUrl { get; set; } public string LastFmImageSize { get; set; } + public MusicArtist() + { + UserItemCountList = new List<ItemByNameCounts>(); + } + /// <summary> /// Gets the user data key. /// </summary> /// <returns>System.String.</returns> public override string GetUserDataKey() { - return Artist.GetUserDataKey(this); + return GetUserDataKey(this); + } + + /// <summary> + /// Gets the user data key. + /// </summary> + /// <param name="item">The item.</param> + /// <returns>System.String.</returns> + public static string GetUserDataKey(BaseItem item) + { + var id = item.GetProviderId(MetadataProviders.Musicbrainz); + + if (!string.IsNullOrEmpty(id)) + { + return "Artist-Musicbrainz-" + id; + } + + return "Artist-" + item.Name; } } } diff --git a/MediaBrowser.Controller/Entities/Audio/MusicGenre.cs b/MediaBrowser.Controller/Entities/Audio/MusicGenre.cs index ec2995fb2..b54e14f2d 100644 --- a/MediaBrowser.Controller/Entities/Audio/MusicGenre.cs +++ b/MediaBrowser.Controller/Entities/Audio/MusicGenre.cs @@ -12,7 +12,7 @@ namespace MediaBrowser.Controller.Entities.Audio { public MusicGenre() { - UserItemCounts = new Dictionary<Guid, ItemByNameCounts>(); + UserItemCountList = new List<ItemByNameCounts>(); } /// <summary> @@ -25,6 +25,6 @@ namespace MediaBrowser.Controller.Entities.Audio } [IgnoreDataMember] - public Dictionary<Guid, ItemByNameCounts> UserItemCounts { get; set; } + public List<ItemByNameCounts> UserItemCountList { get; set; } } } diff --git a/MediaBrowser.Controller/Entities/BaseItem.cs b/MediaBrowser.Controller/Entities/BaseItem.cs index a6178536c..4f7889f97 100644 --- a/MediaBrowser.Controller/Entities/BaseItem.cs +++ b/MediaBrowser.Controller/Entities/BaseItem.cs @@ -1,7 +1,6 @@ using MediaBrowser.Common.Extensions; using MediaBrowser.Common.IO; using MediaBrowser.Controller.Configuration; -using MediaBrowser.Controller.Entities.TV; using MediaBrowser.Controller.IO; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Localization; @@ -38,10 +37,8 @@ namespace MediaBrowser.Controller.Entities Tags = new List<string>(); ThemeSongIds = new List<Guid>(); ThemeVideoIds = new List<Guid>(); - LocalTrailerIds = new List<Guid>(); LockedFields = new List<MetadataFields>(); Taglines = new List<string>(); - RemoteTrailers = new List<MediaUrl>(); ImageSources = new List<ImageSourceInfo>(); } @@ -88,30 +85,12 @@ namespace MediaBrowser.Controller.Entities public Guid Id { get; set; } /// <summary> - /// Gets or sets the budget. - /// </summary> - /// <value>The budget.</value> - public double? Budget { get; set; } - - /// <summary> /// Gets or sets the taglines. /// </summary> /// <value>The taglines.</value> public List<string> Taglines { get; set; } /// <summary> - /// Gets or sets the revenue. - /// </summary> - /// <value>The revenue.</value> - public double? Revenue { get; set; } - - /// <summary> - /// Gets or sets the trailer URL. - /// </summary> - /// <value>The trailer URL.</value> - public List<MediaUrl> RemoteTrailers { get; set; } - - /// <summary> /// Return the id that should be used to key display prefs for this item. /// Default is based on the type for everything except actual generic folders. /// </summary> @@ -139,6 +118,7 @@ namespace MediaBrowser.Controller.Entities /// Gets or sets the type of the location. /// </summary> /// <value>The type of the location.</value> + [IgnoreDataMember] public virtual LocationType LocationType { get @@ -483,6 +463,22 @@ namespace MediaBrowser.Controller.Entities [IgnoreDataMember] public Folder Parent { get; set; } + [IgnoreDataMember] + public IEnumerable<Folder> Parents + { + get + { + var parent = Parent; + + while (parent != null) + { + yield return parent; + + parent = parent.Parent; + } + } + } + /// <summary> /// When the item first debuted. For movies this could be premiere date, episodes would be first aired /// </summary> @@ -630,11 +626,6 @@ namespace MediaBrowser.Controller.Entities /// <value>The original run time ticks.</value> public long? OriginalRunTimeTicks { get; set; } /// <summary> - /// Gets or sets the aspect ratio. - /// </summary> - /// <value>The aspect ratio.</value> - public string AspectRatio { get; set; } - /// <summary> /// Gets or sets the production year. /// </summary> /// <value>The production year.</value> @@ -655,7 +646,6 @@ namespace MediaBrowser.Controller.Entities public List<Guid> ThemeSongIds { get; set; } public List<Guid> ThemeVideoIds { get; set; } - public List<Guid> LocalTrailerIds { get; set; } [IgnoreDataMember] public virtual string OfficialRatingForComparison @@ -898,7 +888,11 @@ namespace MediaBrowser.Controller.Entities themeVideosChanged = await RefreshThemeVideos(cancellationToken, forceSave, forceRefresh, allowSlowProviders).ConfigureAwait(false); - localTrailersChanged = await RefreshLocalTrailers(cancellationToken, forceSave, forceRefresh, allowSlowProviders).ConfigureAwait(false); + var hasTrailers = this as IHasTrailers; + if (hasTrailers != null) + { + localTrailersChanged = await RefreshLocalTrailers(hasTrailers, cancellationToken, forceSave, forceRefresh, allowSlowProviders).ConfigureAwait(false); + } } cancellationToken.ThrowIfCancellationRequested(); @@ -918,18 +912,18 @@ namespace MediaBrowser.Controller.Entities return changed; } - private async Task<bool> RefreshLocalTrailers(CancellationToken cancellationToken, bool forceSave = false, bool forceRefresh = false, bool allowSlowProviders = true) + private async Task<bool> RefreshLocalTrailers(IHasTrailers item, CancellationToken cancellationToken, bool forceSave = false, bool forceRefresh = false, bool allowSlowProviders = true) { var newItems = LoadLocalTrailers().ToList(); var newItemIds = newItems.Select(i => i.Id).ToList(); - var itemsChanged = !LocalTrailerIds.SequenceEqual(newItemIds); + var itemsChanged = !item.LocalTrailerIds.SequenceEqual(newItemIds); var tasks = newItems.Select(i => i.RefreshMetadata(cancellationToken, forceSave, forceRefresh, allowSlowProviders, resetResolveArgs: false)); var results = await Task.WhenAll(tasks).ConfigureAwait(false); - LocalTrailerIds = newItemIds; + item.LocalTrailerIds = newItemIds; return itemsChanged || results.Contains(true); } @@ -1134,6 +1128,11 @@ namespace MediaBrowser.Controller.Entities return changed; } + public virtual string GetClientTypeName() + { + return GetType().Name; + } + /// <summary> /// Determines if the item is considered new based on user settings /// </summary> @@ -1187,6 +1186,7 @@ namespace MediaBrowser.Controller.Entities if (existing != null) { existing.Type = PersonType.GuestStar; + existing.SortOrder = person.SortOrder ?? existing.SortOrder; return; } } @@ -1203,23 +1203,35 @@ namespace MediaBrowser.Controller.Entities else { // Was there, if no role and we have one - fill it in - if (string.IsNullOrWhiteSpace(existing.Role) && !string.IsNullOrWhiteSpace(person.Role)) existing.Role = person.Role; + if (string.IsNullOrWhiteSpace(existing.Role) && !string.IsNullOrWhiteSpace(person.Role)) + { + existing.Role = person.Role; + } + + existing.SortOrder = person.SortOrder ?? existing.SortOrder; } } else { + var existing = People.FirstOrDefault(p => + string.Equals(p.Name, person.Name, StringComparison.OrdinalIgnoreCase) && + string.Equals(p.Type, person.Type, StringComparison.OrdinalIgnoreCase)); + // Check for dupes based on the combination of Name and Type - if (!People.Any(p => string.Equals(p.Name, person.Name, StringComparison.OrdinalIgnoreCase) && string.Equals(p.Type, person.Type, StringComparison.OrdinalIgnoreCase))) + if (existing == null) { People.Add(person); } + else + { + existing.SortOrder = person.SortOrder ?? existing.SortOrder; + } } } /// <summary> /// Adds the tagline. /// </summary> - /// <param name="item">The item.</param> /// <param name="tagline">The tagline.</param> /// <exception cref="System.ArgumentNullException">tagline</exception> public void AddTagline(string tagline) @@ -1737,5 +1749,14 @@ namespace MediaBrowser.Controller.Entities // See if we can avoid a file system lookup by looking for the file in ResolveArgs return metaFileEntry == null ? FileSystem.GetLastWriteTimeUtc(imagePath) : FileSystem.GetLastWriteTimeUtc(metaFileEntry); } + + /// <summary> + /// Gets the file system path to delete when the item is to be deleted + /// </summary> + /// <returns></returns> + public virtual IEnumerable<string> GetDeletePaths() + { + return new[] { Path }; + } } } diff --git a/MediaBrowser.Controller/Entities/Extensions.cs b/MediaBrowser.Controller/Entities/Extensions.cs index d189f4e71..2a64bd3a4 100644 --- a/MediaBrowser.Controller/Entities/Extensions.cs +++ b/MediaBrowser.Controller/Entities/Extensions.cs @@ -16,7 +16,7 @@ namespace MediaBrowser.Controller.Entities /// <param name="url">The URL.</param> /// <param name="isDirectLink">if set to <c>true</c> [is direct link].</param> /// <exception cref="System.ArgumentNullException">url</exception> - public static void AddTrailerUrl(this BaseItem item, string url, bool isDirectLink) + public static void AddTrailerUrl(this IHasTrailers item, string url, bool isDirectLink) { if (string.IsNullOrWhiteSpace(url)) { diff --git a/MediaBrowser.Controller/Entities/Folder.cs b/MediaBrowser.Controller/Entities/Folder.cs index 8dbc98193..e8b583181 100644 --- a/MediaBrowser.Controller/Entities/Folder.cs +++ b/MediaBrowser.Controller/Entities/Folder.cs @@ -90,7 +90,7 @@ namespace MediaBrowser.Controller.Entities item.Id = item.Path.GetMBId(item.GetType()); } - if (_children.Any(i => i.Id == item.Id)) + if (ActualChildren.Any(i => i.Id == item.Id)) { throw new ArgumentException(string.Format("A child with the Id {0} already exists.", item.Id)); } @@ -108,14 +108,14 @@ namespace MediaBrowser.Controller.Entities await LibraryManager.CreateItem(item, cancellationToken).ConfigureAwait(false); - await ItemRepository.SaveChildren(Id, _children.Select(i => i.Id).ToList(), cancellationToken).ConfigureAwait(false); + await ItemRepository.SaveChildren(Id, ActualChildren.Select(i => i.Id).ToList(), cancellationToken).ConfigureAwait(false); } protected void AddChildrenInternal(IEnumerable<BaseItem> children) { lock (_childrenSyncLock) { - var newChildren = _children.ToList(); + var newChildren = ActualChildren.ToList(); newChildren.AddRange(children); _children = newChildren; } @@ -124,7 +124,7 @@ namespace MediaBrowser.Controller.Entities { lock (_childrenSyncLock) { - var newChildren = _children.ToList(); + var newChildren = ActualChildren.ToList(); newChildren.Add(child); _children = newChildren; } @@ -134,7 +134,7 @@ namespace MediaBrowser.Controller.Entities { lock (_childrenSyncLock) { - _children = _children.Except(children).ToList(); + _children = ActualChildren.Except(children).ToList(); } } @@ -519,7 +519,7 @@ namespace MediaBrowser.Controller.Entities /// <summary> /// The children /// </summary> - private IReadOnlyList<BaseItem> _children = new List<BaseItem>(); + private IReadOnlyList<BaseItem> _children; /// <summary> /// The _children sync lock /// </summary> @@ -532,15 +532,10 @@ namespace MediaBrowser.Controller.Entities { get { - return _children; + return _children ?? (_children = LoadChildrenInternal()); } } - public void LoadSavedChildren() - { - _children = LoadChildrenInternal(); - } - /// <summary> /// thread-safe access to the actual children of this folder - without regard to user /// </summary> @@ -758,7 +753,7 @@ namespace MediaBrowser.Controller.Entities AddChildrenInternal(newItems); - await ItemRepository.SaveChildren(Id, _children.Select(i => i.Id).ToList(), cancellationToken).ConfigureAwait(false); + await ItemRepository.SaveChildren(Id, ActualChildren.Select(i => i.Id).ToList(), cancellationToken).ConfigureAwait(false); //force the indexes to rebuild next time if (IndexCache != null) @@ -1007,8 +1002,7 @@ namespace MediaBrowser.Controller.Entities return result; } - var initialCount = _children.Count; - var list = new List<BaseItem>(initialCount); + var list = new List<BaseItem>(); AddChildrenToList(user, includeLinkedChildren, list, false, null); @@ -1038,16 +1032,13 @@ namespace MediaBrowser.Controller.Entities } } - if (recursive) + if (recursive && child.IsFolder) { - var folder = child as Folder; + var folder = (Folder)child; - if (folder != null) + if (folder.AddChildrenToList(user, includeLinkedChildren, list, true, filter)) { - if (folder.AddChildrenToList(user, includeLinkedChildren, list, true, filter)) - { - hasLinkedChildren = true; - } + hasLinkedChildren = true; } } } @@ -1073,7 +1064,6 @@ namespace MediaBrowser.Controller.Entities return hasLinkedChildren; } - private int _lastRecursiveCount; /// <summary> /// Gets allowed recursive children of an item /// </summary> @@ -1101,13 +1091,10 @@ namespace MediaBrowser.Controller.Entities throw new ArgumentNullException("user"); } - var initialCount = _lastRecursiveCount == 0 ? _children.Count : _lastRecursiveCount; - var list = new List<BaseItem>(initialCount); + var list = new List<BaseItem>(); var hasLinkedChildren = AddChildrenToList(user, includeLinkedChildren, list, true, filter); - _lastRecursiveCount = list.Count; - return hasLinkedChildren ? list.DistinctBy(i => i.Id).ToList() : list; } @@ -1127,8 +1114,7 @@ namespace MediaBrowser.Controller.Entities /// <returns>IEnumerable{BaseItem}.</returns> public IList<BaseItem> GetRecursiveChildren(Func<BaseItem, bool> filter) { - var initialCount = _lastRecursiveCount == 0 ? _children.Count : _lastRecursiveCount; - var list = new List<BaseItem>(initialCount); + var list = new List<BaseItem>(); AddChildrenToList(list, true, filter); @@ -1150,14 +1136,11 @@ namespace MediaBrowser.Controller.Entities list.Add(child); } - if (recursive) + if (recursive && child.IsFolder) { - var folder = child as Folder; + var folder = (Folder)child; - if (folder != null) - { - folder.AddChildrenToList(list, true, filter); - } + folder.AddChildrenToList(list, true, filter); } } } diff --git a/MediaBrowser.Controller/Entities/Game.cs b/MediaBrowser.Controller/Entities/Game.cs index e8374c274..e15b7e4c9 100644 --- a/MediaBrowser.Controller/Entities/Game.cs +++ b/MediaBrowser.Controller/Entities/Game.cs @@ -4,16 +4,26 @@ using System.Collections.Generic; namespace MediaBrowser.Controller.Entities { - public class Game : BaseItem, IHasSoundtracks + public class Game : BaseItem, IHasSoundtracks, IHasTrailers { public List<Guid> SoundtrackIds { get; set; } - + public Game() { MultiPartGameFiles = new List<string>(); SoundtrackIds = new List<Guid>(); + RemoteTrailers = new List<MediaUrl>(); + LocalTrailerIds = new List<Guid>(); } + public List<Guid> LocalTrailerIds { get; set; } + + /// <summary> + /// Gets or sets the remote trailers. + /// </summary> + /// <value>The remote trailers.</value> + public List<MediaUrl> RemoteTrailers { get; set; } + /// <summary> /// Gets the type of the media. /// </summary> @@ -84,5 +94,15 @@ namespace MediaBrowser.Controller.Entities } return base.GetUserDataKey(); } + + public override IEnumerable<string> GetDeletePaths() + { + if (!IsInMixedFolder) + { + return new[] { System.IO.Path.GetDirectoryName(Path) }; + } + + return base.GetDeletePaths(); + } } } diff --git a/MediaBrowser.Controller/Entities/GameGenre.cs b/MediaBrowser.Controller/Entities/GameGenre.cs index 0c877782e..ffe62ba03 100644 --- a/MediaBrowser.Controller/Entities/GameGenre.cs +++ b/MediaBrowser.Controller/Entities/GameGenre.cs @@ -9,7 +9,7 @@ namespace MediaBrowser.Controller.Entities { public GameGenre() { - UserItemCounts = new Dictionary<Guid, ItemByNameCounts>(); + UserItemCountList = new List<ItemByNameCounts>(); } /// <summary> @@ -22,6 +22,6 @@ namespace MediaBrowser.Controller.Entities } [IgnoreDataMember] - public Dictionary<Guid, ItemByNameCounts> UserItemCounts { get; set; } + public List<ItemByNameCounts> UserItemCountList { get; set; } } } diff --git a/MediaBrowser.Controller/Entities/Genre.cs b/MediaBrowser.Controller/Entities/Genre.cs index 71fa05720..0fa49639b 100644 --- a/MediaBrowser.Controller/Entities/Genre.cs +++ b/MediaBrowser.Controller/Entities/Genre.cs @@ -1,7 +1,7 @@ -using System.Runtime.Serialization; -using MediaBrowser.Model.Dto; +using MediaBrowser.Model.Dto; using System; using System.Collections.Generic; +using System.Runtime.Serialization; namespace MediaBrowser.Controller.Entities { @@ -12,7 +12,7 @@ namespace MediaBrowser.Controller.Entities { public Genre() { - UserItemCounts = new Dictionary<Guid, ItemByNameCounts>(); + UserItemCountList = new List<ItemByNameCounts>(); } /// <summary> @@ -25,6 +25,6 @@ namespace MediaBrowser.Controller.Entities } [IgnoreDataMember] - public Dictionary<Guid, ItemByNameCounts> UserItemCounts { get; set; } + public List<ItemByNameCounts> UserItemCountList { get; set; } } } diff --git a/MediaBrowser.Controller/Entities/IHasAspectRatio.cs b/MediaBrowser.Controller/Entities/IHasAspectRatio.cs new file mode 100644 index 000000000..5aecf4eac --- /dev/null +++ b/MediaBrowser.Controller/Entities/IHasAspectRatio.cs @@ -0,0 +1,14 @@ +namespace MediaBrowser.Controller.Entities +{ + /// <summary> + /// Interface IHasAspectRatio + /// </summary> + public interface IHasAspectRatio + { + /// <summary> + /// Gets or sets the aspect ratio. + /// </summary> + /// <value>The aspect ratio.</value> + string AspectRatio { get; set; } + } +} diff --git a/MediaBrowser.Controller/Entities/IHasBudget.cs b/MediaBrowser.Controller/Entities/IHasBudget.cs new file mode 100644 index 000000000..f697715c1 --- /dev/null +++ b/MediaBrowser.Controller/Entities/IHasBudget.cs @@ -0,0 +1,18 @@ + +namespace MediaBrowser.Controller.Entities +{ + public interface IHasBudget + { + /// <summary> + /// Gets or sets the budget. + /// </summary> + /// <value>The budget.</value> + double? Budget { get; set; } + + /// <summary> + /// Gets or sets the revenue. + /// </summary> + /// <value>The revenue.</value> + double? Revenue { get; set; } + } +} diff --git a/MediaBrowser.Controller/Entities/IHasTrailers.cs b/MediaBrowser.Controller/Entities/IHasTrailers.cs new file mode 100644 index 000000000..47779064b --- /dev/null +++ b/MediaBrowser.Controller/Entities/IHasTrailers.cs @@ -0,0 +1,21 @@ +using MediaBrowser.Model.Entities; +using System; +using System.Collections.Generic; + +namespace MediaBrowser.Controller.Entities +{ + public interface IHasTrailers + { + /// <summary> + /// Gets or sets the remote trailers. + /// </summary> + /// <value>The remote trailers.</value> + List<MediaUrl> RemoteTrailers { get; set; } + + /// <summary> + /// Gets or sets the local trailer ids. + /// </summary> + /// <value>The local trailer ids.</value> + List<Guid> LocalTrailerIds { get; set; } + } +} diff --git a/MediaBrowser.Controller/Entities/IItemByName.cs b/MediaBrowser.Controller/Entities/IItemByName.cs index 7284bf101..1e83c7466 100644 --- a/MediaBrowser.Controller/Entities/IItemByName.cs +++ b/MediaBrowser.Controller/Entities/IItemByName.cs @@ -1,6 +1,7 @@ using MediaBrowser.Model.Dto; using System; using System.Collections.Generic; +using System.Linq; namespace MediaBrowser.Controller.Entities { @@ -9,26 +10,37 @@ namespace MediaBrowser.Controller.Entities /// </summary> public interface IItemByName { - Dictionary<Guid, ItemByNameCounts> UserItemCounts { get; set; } + List<ItemByNameCounts> UserItemCountList { get; set; } } - public static class IItemByNameExtensions + public interface IHasDualAccess : IItemByName { - public static ItemByNameCounts GetItemByNameCounts(this IItemByName item, User user) + bool IsAccessedByName { get; } + } + + public static class ItemByNameExtensions + { + public static ItemByNameCounts GetItemByNameCounts(this IItemByName item, Guid userId) { - if (user == null) + if (userId == Guid.Empty) { - throw new ArgumentNullException("user"); + throw new ArgumentNullException("userId"); } - ItemByNameCounts counts; + return item.UserItemCountList.FirstOrDefault(i => i.UserId == userId); + } + + public static void SetItemByNameCounts(this IItemByName item, Guid userId, ItemByNameCounts counts) + { + var current = item.UserItemCountList.FirstOrDefault(i => i.UserId == userId); - if (item.UserItemCounts.TryGetValue(user.Id, out counts)) + if (current != null) { - return counts; + item.UserItemCountList.Remove(current); } - return null; + counts.UserId = userId; + item.UserItemCountList.Add(counts); } } } diff --git a/MediaBrowser.Controller/Entities/IndexFolder.cs b/MediaBrowser.Controller/Entities/IndexFolder.cs index 35c11ef5c..57e4a35d3 100644 --- a/MediaBrowser.Controller/Entities/IndexFolder.cs +++ b/MediaBrowser.Controller/Entities/IndexFolder.cs @@ -40,7 +40,6 @@ namespace MediaBrowser.Controller.Entities IndexName = indexName; Parent = parent; - LoadSavedChildren(); } /// <summary> diff --git a/MediaBrowser.Controller/Entities/Movies/BoxSet.cs b/MediaBrowser.Controller/Entities/Movies/BoxSet.cs index e52ece502..4a6221ee9 100644 --- a/MediaBrowser.Controller/Entities/Movies/BoxSet.cs +++ b/MediaBrowser.Controller/Entities/Movies/BoxSet.cs @@ -1,10 +1,26 @@ - +using System; +using MediaBrowser.Model.Entities; +using System.Collections.Generic; + namespace MediaBrowser.Controller.Entities.Movies { /// <summary> /// Class BoxSet /// </summary> - public class BoxSet : Folder + public class BoxSet : Folder, IHasTrailers { + public BoxSet() + { + RemoteTrailers = new List<MediaUrl>(); + LocalTrailerIds = new List<Guid>(); + } + + public List<Guid> LocalTrailerIds { get; set; } + + /// <summary> + /// Gets or sets the remote trailers. + /// </summary> + /// <value>The remote trailers.</value> + public List<MediaUrl> RemoteTrailers { get; set; } } } diff --git a/MediaBrowser.Controller/Entities/Movies/Movie.cs b/MediaBrowser.Controller/Entities/Movies/Movie.cs index eef348f61..473ea4996 100644 --- a/MediaBrowser.Controller/Entities/Movies/Movie.cs +++ b/MediaBrowser.Controller/Entities/Movies/Movie.cs @@ -11,7 +11,7 @@ namespace MediaBrowser.Controller.Entities.Movies /// <summary> /// Class Movie /// </summary> - public class Movie : Video, IHasCriticRating, IHasSoundtracks + public class Movie : Video, IHasCriticRating, IHasSoundtracks, IHasBudget, IHasTrailers { public List<Guid> SpecialFeatureIds { get; set; } @@ -21,8 +21,26 @@ namespace MediaBrowser.Controller.Entities.Movies { SpecialFeatureIds = new List<Guid>(); SoundtrackIds = new List<Guid>(); + RemoteTrailers = new List<MediaUrl>(); + LocalTrailerIds = new List<Guid>(); } + public List<Guid> LocalTrailerIds { get; set; } + + public List<MediaUrl> RemoteTrailers { get; set; } + + /// <summary> + /// Gets or sets the budget. + /// </summary> + /// <value>The budget.</value> + public double? Budget { get; set; } + + /// <summary> + /// Gets or sets the revenue. + /// </summary> + /// <value>The revenue.</value> + public double? Revenue { get; set; } + /// <summary> /// Gets or sets the critic rating. /// </summary> diff --git a/MediaBrowser.Controller/Entities/MusicVideo.cs b/MediaBrowser.Controller/Entities/MusicVideo.cs index 207f76efd..68ad4630a 100644 --- a/MediaBrowser.Controller/Entities/MusicVideo.cs +++ b/MediaBrowser.Controller/Entities/MusicVideo.cs @@ -4,7 +4,7 @@ using System; namespace MediaBrowser.Controller.Entities { - public class MusicVideo : Video, IHasArtist, IHasMusicGenres + public class MusicVideo : Video, IHasArtist, IHasMusicGenres, IHasBudget { /// <summary> /// Gets or sets the artist. @@ -19,6 +19,18 @@ namespace MediaBrowser.Controller.Entities public string Album { get; set; } /// <summary> + /// Gets or sets the budget. + /// </summary> + /// <value>The budget.</value> + public double? Budget { get; set; } + + /// <summary> + /// Gets or sets the revenue. + /// </summary> + /// <value>The revenue.</value> + public double? Revenue { get; set; } + + /// <summary> /// Determines whether the specified name has artist. /// </summary> /// <param name="name">The name.</param> diff --git a/MediaBrowser.Controller/Entities/Person.cs b/MediaBrowser.Controller/Entities/Person.cs index e5cf48ad0..243861da7 100644 --- a/MediaBrowser.Controller/Entities/Person.cs +++ b/MediaBrowser.Controller/Entities/Person.cs @@ -1,7 +1,7 @@ -using System.Runtime.Serialization; -using MediaBrowser.Model.Dto; +using MediaBrowser.Model.Dto; using System; using System.Collections.Generic; +using System.Runtime.Serialization; namespace MediaBrowser.Controller.Entities { @@ -12,11 +12,11 @@ namespace MediaBrowser.Controller.Entities { public Person() { - UserItemCounts = new Dictionary<Guid, ItemByNameCounts>(); + UserItemCountList = new List<ItemByNameCounts>(); } [IgnoreDataMember] - public Dictionary<Guid, ItemByNameCounts> UserItemCounts { get; set; } + public List<ItemByNameCounts> UserItemCountList { get; set; } /// <summary> /// Gets the user data key. @@ -50,6 +50,12 @@ namespace MediaBrowser.Controller.Entities public string Type { get; set; } /// <summary> + /// Gets or sets the sort order - ascending + /// </summary> + /// <value>The sort order.</value> + public int? SortOrder { get; set; } + + /// <summary> /// Returns a <see cref="System.String" /> that represents this instance. /// </summary> /// <returns>A <see cref="System.String" /> that represents this instance.</returns> diff --git a/MediaBrowser.Controller/Entities/Studio.cs b/MediaBrowser.Controller/Entities/Studio.cs index bbe96a88b..7bc17549f 100644 --- a/MediaBrowser.Controller/Entities/Studio.cs +++ b/MediaBrowser.Controller/Entities/Studio.cs @@ -12,7 +12,7 @@ namespace MediaBrowser.Controller.Entities { public Studio() { - UserItemCounts = new Dictionary<Guid, ItemByNameCounts>(); + UserItemCountList = new List<ItemByNameCounts>(); } /// <summary> @@ -25,6 +25,6 @@ namespace MediaBrowser.Controller.Entities } [IgnoreDataMember] - public Dictionary<Guid, ItemByNameCounts> UserItemCounts { get; set; } + public List<ItemByNameCounts> UserItemCountList { get; set; } } } diff --git a/MediaBrowser.Controller/Entities/TV/Episode.cs b/MediaBrowser.Controller/Entities/TV/Episode.cs index c68ba0ad1..e9f250d2a 100644 --- a/MediaBrowser.Controller/Entities/TV/Episode.cs +++ b/MediaBrowser.Controller/Entities/TV/Episode.cs @@ -50,6 +50,33 @@ namespace MediaBrowser.Controller.Entities.TV get { return true; } } + [IgnoreDataMember] + public int? AiredSeasonNumber + { + get + { + return AirsAfterSeasonNumber ?? AirsBeforeSeasonNumber ?? PhysicalSeasonNumber; + } + } + + [IgnoreDataMember] + public int? PhysicalSeasonNumber + { + get + { + var value = ParentIndexNumber; + + if (value.HasValue) + { + return value; + } + + var season = Parent as Season; + + return season != null ? season.IndexNumber : null; + } + } + /// <summary> /// We roll up into series /// </summary> @@ -59,7 +86,7 @@ namespace MediaBrowser.Controller.Entities.TV { get { - return Season; + return FindParent<Season>(); } } @@ -152,20 +179,6 @@ namespace MediaBrowser.Controller.Entities.TV } /// <summary> - /// The _season - /// </summary> - private Season _season; - /// <summary> - /// This Episode's Season Instance - /// </summary> - /// <value>The season.</value> - [IgnoreDataMember] - public Season Season - { - get { return _season ?? (_season = FindParent<Season>()); } - } - - /// <summary> /// This is the ending episode number for double episodes. /// </summary> /// <value>The index number.</value> @@ -221,5 +234,46 @@ namespace MediaBrowser.Controller.Entities.TV { get { return LocationType == Model.Entities.LocationType.Virtual && IsUnaired; } } + + [IgnoreDataMember] + public Guid? SeasonId + { + get + { + // First see if the parent is a Season + var season = Parent as Season; + + if (season != null) + { + return season.Id; + } + + var seasonNumber = ParentIndexNumber; + + // Parent is a Series + if (seasonNumber.HasValue) + { + var series = Parent as Series; + + if (series != null) + { + season = series.Children.OfType<Season>() + .FirstOrDefault(i => i.IndexNumber.HasValue && i.IndexNumber.Value == seasonNumber.Value); + + if (season != null) + { + return season.Id; + } + } + } + + return null; + } + } + + public override IEnumerable<string> GetDeletePaths() + { + return new[] { Path }; + } } } diff --git a/MediaBrowser.Controller/Entities/TV/Season.cs b/MediaBrowser.Controller/Entities/TV/Season.cs index 97a09b7b5..78e0b8bc4 100644 --- a/MediaBrowser.Controller/Entities/TV/Season.cs +++ b/MediaBrowser.Controller/Entities/TV/Season.cs @@ -1,9 +1,9 @@ -using System.Linq; -using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Localization; using System; using System.Collections.Generic; using System.IO; +using System.Linq; using System.Runtime.Serialization; namespace MediaBrowser.Controller.Entities.TV @@ -94,6 +94,22 @@ namespace MediaBrowser.Controller.Entities.TV get { return _series ?? (_series = FindParent<Series>()); } } + [IgnoreDataMember] + public string SeriesPath + { + get + { + var series = Series; + + if (series != null) + { + return series.Path; + } + + return System.IO.Path.GetDirectoryName(Path); + } + } + /// <summary> /// Our rating comes from our series /// </summary> @@ -149,16 +165,34 @@ namespace MediaBrowser.Controller.Entities.TV return IndexNumber != null ? IndexNumber.Value.ToString("0000") : Name; } + private IEnumerable<Episode> GetEpisodes() + { + var series = Series; + + if (series != null && series.ContainsEpisodesWithoutSeasonFolders) + { + var seasonNumber = IndexNumber; + + if (seasonNumber.HasValue) + { + return series.RecursiveChildren.OfType<Episode>() + .Where(i => i.ParentIndexNumber.HasValue && i.ParentIndexNumber.Value == seasonNumber.Value); + } + } + + return Children.OfType<Episode>(); + } + [IgnoreDataMember] public bool IsMissingSeason { - get { return LocationType == Model.Entities.LocationType.Virtual && Children.OfType<Episode>().All(i => i.IsMissingEpisode); } + get { return LocationType == Model.Entities.LocationType.Virtual && GetEpisodes().All(i => i.IsMissingEpisode); } } [IgnoreDataMember] public bool IsUnaired { - get { return Children.OfType<Episode>().All(i => i.IsUnaired); } + get { return GetEpisodes().All(i => i.IsUnaired); } } [IgnoreDataMember] @@ -170,7 +204,13 @@ namespace MediaBrowser.Controller.Entities.TV [IgnoreDataMember] public bool IsMissingOrVirtualUnaired { - get { return LocationType == Model.Entities.LocationType.Virtual && Children.OfType<Episode>().All(i => i.IsVirtualUnaired || i.IsMissingEpisode); } + get { return LocationType == Model.Entities.LocationType.Virtual && GetEpisodes().All(i => i.IsVirtualUnaired || i.IsMissingEpisode); } + } + + [IgnoreDataMember] + public bool IsSpecialSeason + { + get { return (IndexNumber ?? -1) == 0; } } } } diff --git a/MediaBrowser.Controller/Entities/TV/Series.cs b/MediaBrowser.Controller/Entities/TV/Series.cs index 02ea50c6b..b4a3fc811 100644 --- a/MediaBrowser.Controller/Entities/TV/Series.cs +++ b/MediaBrowser.Controller/Entities/TV/Series.cs @@ -4,6 +4,7 @@ using MediaBrowser.Model.Entities; using System; using System.Collections.Generic; using System.IO; +using System.Linq; using System.Runtime.Serialization; namespace MediaBrowser.Controller.Entities.TV @@ -11,7 +12,7 @@ namespace MediaBrowser.Controller.Entities.TV /// <summary> /// Class Series /// </summary> - public class Series : Folder, IHasSoundtracks + public class Series : Folder, IHasSoundtracks, IHasTrailers { public List<Guid> SpecialFeatureIds { get; set; } public List<Guid> SoundtrackIds { get; set; } @@ -24,8 +25,14 @@ namespace MediaBrowser.Controller.Entities.TV SpecialFeatureIds = new List<Guid>(); SoundtrackIds = new List<Guid>(); + RemoteTrailers = new List<MediaUrl>(); + LocalTrailerIds = new List<Guid>(); } + public List<Guid> LocalTrailerIds { get; set; } + + public List<MediaUrl> RemoteTrailers { get; set; } + /// <summary> /// Gets or sets the status. /// </summary> @@ -94,5 +101,14 @@ namespace MediaBrowser.Controller.Entities.TV return args; } + + [IgnoreDataMember] + public bool ContainsEpisodesWithoutSeasonFolders + { + get + { + return Children.OfType<Video>().Any(); + } + } } } diff --git a/MediaBrowser.Controller/Entities/Trailer.cs b/MediaBrowser.Controller/Entities/Trailer.cs index 26814ad40..77efe8e8c 100644 --- a/MediaBrowser.Controller/Entities/Trailer.cs +++ b/MediaBrowser.Controller/Entities/Trailer.cs @@ -8,7 +8,7 @@ namespace MediaBrowser.Controller.Entities /// <summary> /// Class Trailer /// </summary> - public class Trailer : Video, IHasCriticRating, IHasSoundtracks + public class Trailer : Video, IHasCriticRating, IHasSoundtracks, IHasBudget, IHasTrailers { public List<Guid> SoundtrackIds { get; set; } @@ -17,8 +17,25 @@ namespace MediaBrowser.Controller.Entities RemoteTrailers = new List<MediaUrl>(); Taglines = new List<string>(); SoundtrackIds = new List<Guid>(); + LocalTrailerIds = new List<Guid>(); } + public List<Guid> LocalTrailerIds { get; set; } + + public List<MediaUrl> RemoteTrailers { get; set; } + + /// <summary> + /// Gets or sets the budget. + /// </summary> + /// <value>The budget.</value> + public double? Budget { get; set; } + + /// <summary> + /// Gets or sets the revenue. + /// </summary> + /// <value>The revenue.</value> + public double? Revenue { get; set; } + /// <summary> /// Gets or sets the critic rating. /// </summary> diff --git a/MediaBrowser.Controller/Entities/Video.cs b/MediaBrowser.Controller/Entities/Video.cs index e900dd77e..9b02571b0 100644 --- a/MediaBrowser.Controller/Entities/Video.cs +++ b/MediaBrowser.Controller/Entities/Video.cs @@ -14,7 +14,7 @@ namespace MediaBrowser.Controller.Entities /// <summary> /// Class Video /// </summary> - public class Video : BaseItem, IHasMediaStreams + public class Video : BaseItem, IHasMediaStreams, IHasAspectRatio { public bool IsMultiPart { get; set; } @@ -66,6 +66,12 @@ namespace MediaBrowser.Controller.Entities } /// <summary> + /// Gets or sets the aspect ratio. + /// </summary> + /// <value>The aspect ratio.</value> + public string AspectRatio { get; set; } + + /// <summary> /// Should be overridden to return the proper folder where metadata lives /// </summary> /// <value>The meta location.</value> @@ -252,5 +258,17 @@ namespace MediaBrowser.Controller.Entities }).ToList(); } + public override IEnumerable<string> GetDeletePaths() + { + if (!IsInMixedFolder) + { + if (VideoType == VideoType.VideoFile || VideoType == VideoType.Iso) + { + return new[] { System.IO.Path.GetDirectoryName(Path) }; + } + } + + return base.GetDeletePaths(); + } } } diff --git a/MediaBrowser.Controller/Entities/Year.cs b/MediaBrowser.Controller/Entities/Year.cs index d0f457718..cd50a1c60 100644 --- a/MediaBrowser.Controller/Entities/Year.cs +++ b/MediaBrowser.Controller/Entities/Year.cs @@ -12,11 +12,11 @@ namespace MediaBrowser.Controller.Entities { public Year() { - UserItemCounts = new Dictionary<Guid, ItemByNameCounts>(); + UserItemCountList = new List<ItemByNameCounts>(); } [IgnoreDataMember] - public Dictionary<Guid, ItemByNameCounts> UserItemCounts { get; set; } + public List<ItemByNameCounts> UserItemCountList { get; set; } /// <summary> /// Gets the user data key. diff --git a/MediaBrowser.Controller/Library/ILibraryManager.cs b/MediaBrowser.Controller/Library/ILibraryManager.cs index 0a89c0df8..338edd568 100644 --- a/MediaBrowser.Controller/Library/ILibraryManager.cs +++ b/MediaBrowser.Controller/Library/ILibraryManager.cs @@ -60,7 +60,7 @@ namespace MediaBrowser.Controller.Library /// </summary> /// <param name="name">The name.</param> /// <returns>Task{Artist}.</returns> - Artist GetArtist(string name); + MusicArtist GetArtist(string name); /// <summary> /// Gets a Studio @@ -302,5 +302,18 @@ namespace MediaBrowser.Controller.Library /// <param name="updateType">Type of the update.</param> /// <returns>Task.</returns> Task SaveMetadata(BaseItem item, ItemUpdateType updateType); + + /// <summary> + /// Gets all artists. + /// </summary> + /// <returns>IEnumerable{System.String}.</returns> + IEnumerable<string> GetAllArtists(); + + /// <summary> + /// Gets all artists. + /// </summary> + /// <param name="items">The items.</param> + /// <returns>IEnumerable{System.String}.</returns> + IEnumerable<string> GetAllArtists(IEnumerable<BaseItem> items); } }
\ No newline at end of file diff --git a/MediaBrowser.Controller/Library/TVUtils.cs b/MediaBrowser.Controller/Library/TVUtils.cs index 7a5722746..65308bd10 100644 --- a/MediaBrowser.Controller/Library/TVUtils.cs +++ b/MediaBrowser.Controller/Library/TVUtils.cs @@ -257,7 +257,7 @@ namespace MediaBrowser.Controller.Library if (match != null) { - return ParseEpisodeNumber(match.Value); + return ParseEpisodeNumber(match.Groups["epnumber"].Value); } } diff --git a/MediaBrowser.Controller/LiveTv/Channel.cs b/MediaBrowser.Controller/LiveTv/Channel.cs new file mode 100644 index 000000000..8097cea1d --- /dev/null +++ b/MediaBrowser.Controller/LiveTv/Channel.cs @@ -0,0 +1,73 @@ +using MediaBrowser.Controller.Entities; +using MediaBrowser.Model.Dto; +using MediaBrowser.Model.LiveTv; +using System; +using System.Collections.Generic; +using System.Runtime.Serialization; + +namespace MediaBrowser.Controller.LiveTv +{ + public class Channel : BaseItem, IItemByName + { + public Channel() + { + UserItemCountList = new List<ItemByNameCounts>(); + } + + /// <summary> + /// Gets the user data key. + /// </summary> + /// <returns>System.String.</returns> + public override string GetUserDataKey() + { + return "Channel-" + Name; + } + + [IgnoreDataMember] + public List<ItemByNameCounts> UserItemCountList { get; set; } + + /// <summary> + /// Gets or sets the number. + /// </summary> + /// <value>The number.</value> + public string ChannelNumber { get; set; } + + /// <summary> + /// Get or sets the Id. + /// </summary> + /// <value>The id of the channel.</value> + public string ChannelId { get; set; } + + /// <summary> + /// Gets or sets the name of the service. + /// </summary> + /// <value>The name of the service.</value> + public string ServiceName { get; set; } + + /// <summary> + /// Gets or sets the type of the channel. + /// </summary> + /// <value>The type of the channel.</value> + public ChannelType ChannelType { get; set; } + + protected override string CreateSortName() + { + double number = 0; + + if (!string.IsNullOrEmpty(ChannelNumber)) + { + double.TryParse(ChannelNumber, out number); + } + + 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.Controller/LiveTv/ChannelInfo.cs b/MediaBrowser.Controller/LiveTv/ChannelInfo.cs index 721c1e40a..27fc59630 100644 --- a/MediaBrowser.Controller/LiveTv/ChannelInfo.cs +++ b/MediaBrowser.Controller/LiveTv/ChannelInfo.cs @@ -26,12 +26,6 @@ namespace MediaBrowser.Controller.LiveTv public string Id { get; set; } /// <summary> - /// Gets or sets the name of the service. - /// </summary> - /// <value>The name of the service.</value> - public string ServiceName { get; set; } - - /// <summary> /// Gets or sets the type of the channel. /// </summary> /// <value>The type of the channel.</value> diff --git a/MediaBrowser.Controller/LiveTv/ILiveTvManager.cs b/MediaBrowser.Controller/LiveTv/ILiveTvManager.cs index 62bfdf3e5..4e73fc109 100644 --- a/MediaBrowser.Controller/LiveTv/ILiveTvManager.cs +++ b/MediaBrowser.Controller/LiveTv/ILiveTvManager.cs @@ -1,6 +1,8 @@ -using System.Threading.Tasks; -using MediaBrowser.Model.LiveTv; +using MediaBrowser.Model.LiveTv; +using MediaBrowser.Model.Querying; using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; namespace MediaBrowser.Controller.LiveTv { @@ -16,16 +18,92 @@ namespace MediaBrowser.Controller.LiveTv IReadOnlyList<ILiveTvService> Services { get; } /// <summary> + /// Schedules the recording. + /// </summary> + /// <param name="programId">The program identifier.</param> + /// <returns>Task.</returns> + Task ScheduleRecording(string programId); + + /// <summary> + /// Deletes the recording. + /// </summary> + /// <param name="id">The identifier.</param> + /// <returns>Task.</returns> + Task DeleteRecording(string id); + + /// <summary> + /// Cancels the timer. + /// </summary> + /// <param name="id">The identifier.</param> + /// <returns>Task.</returns> + Task CancelTimer(string id); + + /// <summary> /// Adds the parts. /// </summary> /// <param name="services">The services.</param> void AddParts(IEnumerable<ILiveTvService> services); /// <summary> - /// Gets the channel info dto. + /// Gets the channels. + /// </summary> + /// <param name="query">The query.</param> + /// <returns>IEnumerable{Channel}.</returns> + QueryResult<ChannelInfoDto> GetChannels(ChannelQuery query); + + /// <summary> + /// Gets the recording. + /// </summary> + /// <param name="id">The identifier.</param> + /// <param name="cancellationToken">The cancellation token.</param> + /// <returns>Task{RecordingInfoDto}.</returns> + Task<RecordingInfoDto> GetRecording(string id, CancellationToken cancellationToken); + + /// <summary> + /// Gets the timer. + /// </summary> + /// <param name="id">The identifier.</param> + /// <param name="cancellationToken">The cancellation token.</param> + /// <returns>Task{TimerInfoDto}.</returns> + Task<TimerInfoDto> GetTimer(string id, CancellationToken cancellationToken); + + /// <summary> + /// Gets the recordings. + /// </summary> + /// <param name="query">The query.</param> + /// <param name="cancellationToken">The cancellation token.</param> + /// <returns>QueryResult{RecordingInfoDto}.</returns> + Task<QueryResult<RecordingInfoDto>> GetRecordings(RecordingQuery query, CancellationToken cancellationToken); + + /// <summary> + /// Gets the timers. + /// </summary> + /// <param name="query">The query.</param> + /// <param name="cancellationToken">The cancellation token.</param> + /// <returns>Task{QueryResult{TimerInfoDto}}.</returns> + Task<QueryResult<TimerInfoDto>> GetTimers(TimerQuery query, CancellationToken cancellationToken); + + /// <summary> + /// Gets the channel. + /// </summary> + /// <param name="id">The identifier.</param> + /// <returns>Channel.</returns> + Channel GetChannel(string id); + + /// <summary> + /// Gets the channel. + /// </summary> + /// <param name="id">The identifier.</param> + /// <param name="userId">The user identifier.</param> + /// <returns>Channel.</returns> + ChannelInfoDto GetChannelInfoDto(string id, string userId); + + /// <summary> + /// Gets the programs. /// </summary> - /// <param name="info">The info.</param> - /// <returns>ChannelInfoDto.</returns> - ChannelInfoDto GetChannelInfoDto(ChannelInfo info); + /// <param name="query">The query.</param> + /// <param name="cancellationToken">The cancellation token.</param> + /// <returns>IEnumerable{ProgramInfo}.</returns> + Task<QueryResult<ProgramInfoDto>> GetPrograms(ProgramQuery query, CancellationToken cancellationToken); } } diff --git a/MediaBrowser.Controller/LiveTv/ILiveTvService.cs b/MediaBrowser.Controller/LiveTv/ILiveTvService.cs index 5c019ae8c..a6c60d468 100644 --- a/MediaBrowser.Controller/LiveTv/ILiveTvService.cs +++ b/MediaBrowser.Controller/LiveTv/ILiveTvService.cs @@ -1,4 +1,4 @@ -using MediaBrowser.Model.LiveTv; +using MediaBrowser.Common.Net; using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; @@ -24,19 +24,88 @@ namespace MediaBrowser.Controller.LiveTv Task<IEnumerable<ChannelInfo>> GetChannelsAsync(CancellationToken cancellationToken); /// <summary> + /// Cancels the timer asynchronous. + /// </summary> + /// <param name="timerId">The timer identifier.</param> + /// <param name="cancellationToken">The cancellation token.</param> + /// <returns>Task.</returns> + Task CancelTimerAsync(string timerId, CancellationToken cancellationToken); + + /// <summary> + /// Deletes the recording asynchronous. + /// </summary> + /// <param name="recordingId">The recording identifier.</param> + /// <param name="cancellationToken">The cancellation token.</param> + /// <returns>Task.</returns> + Task DeleteRecordingAsync(string recordingId, CancellationToken cancellationToken); + + /// <summary> + /// Creates the timer asynchronous. + /// </summary> + /// <param name="info">The information.</param> + /// <param name="cancellationToken">The cancellation token.</param> + /// <returns>Task.</returns> + Task CreateTimerAsync(TimerInfo info, CancellationToken cancellationToken); + + /// <summary> + /// Creates the series timer asynchronous. + /// </summary> + /// <param name="info">The information.</param> + /// <param name="cancellationToken">The cancellation token.</param> + /// <returns>Task.</returns> + Task CreateSeriesTimerAsync(SeriesTimerInfo info, CancellationToken cancellationToken); + + /// <summary> + /// Updates the series timer asynchronous. + /// </summary> + /// <param name="info">The information.</param> + /// <param name="cancellationToken">The cancellation token.</param> + /// <returns>Task.</returns> + Task UpdateSeriesTimerAsync(SeriesTimerInfo info, CancellationToken cancellationToken); + + /// <summary> + /// Gets the channel image asynchronous. + /// </summary> + /// <param name="channelId">The channel identifier.</param> + /// <param name="cancellationToken">The cancellation token.</param> + /// <returns>Task{Stream}.</returns> + Task<ImageResponseInfo> GetChannelImageAsync(string channelId, CancellationToken cancellationToken); + + /// <summary> + /// Gets the program image asynchronous. + /// </summary> + /// <param name="programId">The program identifier.</param> + /// <param name="cancellationToken">The cancellation token.</param> + /// <returns>Task{ImageResponseInfo}.</returns> + Task<ImageResponseInfo> GetProgramImageAsync(string programId, CancellationToken cancellationToken); + + /// <summary> /// Gets the recordings asynchronous. /// </summary> - /// <param name="query">The query.</param> /// <param name="cancellationToken">The cancellation token.</param> /// <returns>Task{IEnumerable{RecordingInfo}}.</returns> - Task<IEnumerable<RecordingInfo>> GetRecordingsAsync(RecordingQuery query, CancellationToken cancellationToken); + Task<IEnumerable<RecordingInfo>> GetRecordingsAsync(CancellationToken cancellationToken); /// <summary> - /// Gets the channel guides. + /// Gets the recordings asynchronous. + /// </summary> + /// <param name="cancellationToken">The cancellation token.</param> + /// <returns>Task{IEnumerable{RecordingInfo}}.</returns> + Task<IEnumerable<TimerInfo>> GetTimersAsync(CancellationToken cancellationToken); + + /// <summary> + /// Gets the series timers asynchronous. + /// </summary> + /// <param name="cancellationToken">The cancellation token.</param> + /// <returns>Task{IEnumerable{SeriesTimerInfo}}.</returns> + Task<IEnumerable<SeriesTimerInfo>> GetSeriesTimersAsync(CancellationToken cancellationToken); + + /// <summary> + /// Gets the programs asynchronous. /// </summary> - /// <param name="channelIdList">The channel identifier list.</param> + /// <param name="channelId">The channel identifier.</param> /// <param name="cancellationToken">The cancellation token.</param> - /// <returns>Task{IEnumerable{ChannelGuide}}.</returns> - Task<IEnumerable<ChannelGuide>> GetChannelGuidesAsync(IEnumerable<string> channelIdList, CancellationToken cancellationToken); + /// <returns>Task{IEnumerable{ProgramInfo}}.</returns> + Task<IEnumerable<ProgramInfo>> GetProgramsAsync(string channelId, CancellationToken cancellationToken); } } diff --git a/MediaBrowser.Controller/LiveTv/ImageResponseInfo.cs b/MediaBrowser.Controller/LiveTv/ImageResponseInfo.cs new file mode 100644 index 000000000..d454a1ef8 --- /dev/null +++ b/MediaBrowser.Controller/LiveTv/ImageResponseInfo.cs @@ -0,0 +1,19 @@ +using System.IO; + +namespace MediaBrowser.Controller.LiveTv +{ + public class ImageResponseInfo + { + /// <summary> + /// Gets or sets the stream. + /// </summary> + /// <value>The stream.</value> + public Stream Stream { get; set; } + + /// <summary> + /// Gets or sets the type of the MIME. + /// </summary> + /// <value>The type of the MIME.</value> + public string MimeType { get; set; } + } +} diff --git a/MediaBrowser.Controller/LiveTv/ProgramInfo.cs b/MediaBrowser.Controller/LiveTv/ProgramInfo.cs new file mode 100644 index 000000000..cf5cdb94c --- /dev/null +++ b/MediaBrowser.Controller/LiveTv/ProgramInfo.cs @@ -0,0 +1,98 @@ +using MediaBrowser.Model.LiveTv; +using System; +using System.Collections.Generic; + +namespace MediaBrowser.Controller.LiveTv +{ + public class ProgramInfo + { + /// <summary> + /// Id of the program. + /// </summary> + public string Id { get; set; } + + /// <summary> + /// Gets or sets the channel identifier. + /// </summary> + /// <value>The channel identifier.</value> + public string ChannelId { get; set; } + + /// <summary> + /// Name of the program + /// </summary> + public string Name { get; set; } + + /// <summary> + /// Gets or sets the official rating. + /// </summary> + /// <value>The official rating.</value> + public string OfficialRating { get; set; } + + /// <summary> + /// Description of the progam. + /// </summary> + public string Description { get; set; } + + /// <summary> + /// The start date of the program, in UTC. + /// </summary> + public DateTime StartDate { get; set; } + + /// <summary> + /// The end date of the program, in UTC. + /// </summary> + public DateTime EndDate { get; set; } + + /// <summary> + /// Gets or sets the aspect ratio. + /// </summary> + /// <value>The aspect ratio.</value> + public string AspectRatio { get; set; } + + /// <summary> + /// Genre of the program. + /// </summary> + public List<string> Genres { get; set; } + + /// <summary> + /// Gets or sets the quality. + /// </summary> + /// <value>The quality.</value> + public ProgramVideoQuality Quality { get; set; } + + /// <summary> + /// Gets or sets the original air date. + /// </summary> + /// <value>The original air date.</value> + public DateTime? OriginalAirDate { get; set; } + + /// <summary> + /// Gets or sets the audio. + /// </summary> + /// <value>The audio.</value> + public ProgramAudio Audio { get; set; } + + /// <summary> + /// Gets or sets the community rating. + /// </summary> + /// <value>The community rating.</value> + public float? CommunityRating { get; set; } + + /// <summary> + /// Gets or sets a value indicating whether this instance is repeat. + /// </summary> + /// <value><c>true</c> if this instance is repeat; otherwise, <c>false</c>.</value> + public bool IsRepeat { get; set; } + + /// <summary> + /// Gets or sets the episode title. + /// </summary> + /// <value>The episode title.</value> + public string EpisodeTitle { get; set; } + + public ProgramInfo() + { + Genres = new List<string>(); + } + } +} diff --git a/MediaBrowser.Controller/LiveTv/RecordingInfo.cs b/MediaBrowser.Controller/LiveTv/RecordingInfo.cs new file mode 100644 index 000000000..2c8e8cb46 --- /dev/null +++ b/MediaBrowser.Controller/LiveTv/RecordingInfo.cs @@ -0,0 +1,102 @@ +using MediaBrowser.Model.LiveTv; +using System; +using System.Collections.Generic; + +namespace MediaBrowser.Controller.LiveTv +{ + public class RecordingInfo + { + /// <summary> + /// Id of the recording. + /// </summary> + public string Id { get; set; } + + /// <summary> + /// ChannelId of the recording. + /// </summary> + public string ChannelId { get; set; } + + /// <summary> + /// ChannelName of the recording. + /// </summary> + public string ChannelName { get; set; } + + /// <summary> + /// Gets or sets the type of the channel. + /// </summary> + /// <value>The type of the channel.</value> + public ChannelType ChannelType { get; set; } + + /// <summary> + /// Name of the recording. + /// </summary> + public string Name { get; set; } + + /// <summary> + /// Gets or sets the path. + /// </summary> + /// <value>The path.</value> + public string Path { get; set; } + + /// <summary> + /// Description of the recording. + /// </summary> + public string Description { get; set; } + + /// <summary> + /// The start date of the recording, in UTC. + /// </summary> + public DateTime StartDate { get; set; } + + /// <summary> + /// The end date of the recording, in UTC. + /// </summary> + public DateTime EndDate { get; set; } + + /// <summary> + /// Gets or sets the program identifier. + /// </summary> + /// <value>The program identifier.</value> + public string ProgramId { get; set; } + + /// <summary> + /// Gets or sets the status. + /// </summary> + /// <value>The status.</value> + public RecordingStatus Status { get; set; } + + /// <summary> + /// Genre of the program. + /// </summary> + public List<string> Genres { get; set; } + + /// <summary> + /// Gets or sets a value indicating whether this instance is repeat. + /// </summary> + /// <value><c>true</c> if this instance is repeat; otherwise, <c>false</c>.</value> + public bool IsRepeat { get; set; } + + /// <summary> + /// Gets or sets the episode title. + /// </summary> + /// <value>The episode title.</value> + public string EpisodeTitle { get; set; } + + /// <summary> + /// Gets or sets the official rating. + /// </summary> + /// <value>The official rating.</value> + public string OfficialRating { get; set; } + + /// <summary> + /// Gets or sets the community rating. + /// </summary> + /// <value>The community rating.</value> + public float? CommunityRating { get; set; } + + public RecordingInfo() + { + Genres = new List<string>(); + } + } +} diff --git a/MediaBrowser.Controller/LiveTv/SeriesTimerInfo.cs b/MediaBrowser.Controller/LiveTv/SeriesTimerInfo.cs new file mode 100644 index 000000000..44594882c --- /dev/null +++ b/MediaBrowser.Controller/LiveTv/SeriesTimerInfo.cs @@ -0,0 +1,85 @@ +using MediaBrowser.Model.LiveTv; +using System; +using System.Collections.Generic; + +namespace MediaBrowser.Controller.LiveTv +{ + public class SeriesTimerInfo + { + /// <summary> + /// Id of the recording. + /// </summary> + public string Id { get; set; } + + /// <summary> + /// ChannelId of the recording. + /// </summary> + public string ChannelId { get; set; } + + /// <summary> + /// ChannelName of the recording. + /// </summary> + public string ChannelName { get; set; } + + /// <summary> + /// Gets or sets the program identifier. + /// </summary> + /// <value>The program identifier.</value> + public string ProgramId { get; set; } + + /// <summary> + /// Name of the recording. + /// </summary> + public string Name { get; set; } + + /// <summary> + /// Description of the recording. + /// </summary> + public string Description { get; set; } + + /// <summary> + /// The start date of the recording, in UTC. + /// </summary> + public DateTime StartDate { get; set; } + + /// <summary> + /// The end date of the recording, in UTC. + /// </summary> + public DateTime EndDate { get; set; } + + /// <summary> + /// Gets or sets the pre padding seconds. + /// </summary> + /// <value>The pre padding seconds.</value> + public int PrePaddingSeconds { get; set; } + + /// <summary> + /// Gets or sets the post padding seconds. + /// </summary> + /// <value>The post padding seconds.</value> + public int PostPaddingSeconds { get; set; } + + /// <summary> + /// Gets or sets the type of the recurrence. + /// </summary> + /// <value>The type of the recurrence.</value> + public RecurrenceType RecurrenceType { get; set; } + + /// <summary> + /// Gets or sets the days. + /// </summary> + /// <value>The days.</value> + public List<DayOfWeek> Days { get; set; } + + /// <summary> + /// Gets or sets the priority. + /// </summary> + /// <value>The priority.</value> + public int Priority { get; set; } + + public SeriesTimerInfo() + { + Days = new List<DayOfWeek>(); + } + } +} diff --git a/MediaBrowser.Controller/LiveTv/TimerInfo.cs b/MediaBrowser.Controller/LiveTv/TimerInfo.cs new file mode 100644 index 000000000..3df0b8cca --- /dev/null +++ b/MediaBrowser.Controller/LiveTv/TimerInfo.cs @@ -0,0 +1,73 @@ +using MediaBrowser.Model.LiveTv; +using System; + +namespace MediaBrowser.Controller.LiveTv +{ + public class TimerInfo + { + /// <summary> + /// Id of the recording. + /// </summary> + public string Id { get; set; } + + /// <summary> + /// Gets or sets the series timer identifier. + /// </summary> + /// <value>The series timer identifier.</value> + public string SeriesTimerId { get; set; } + + /// <summary> + /// ChannelId of the recording. + /// </summary> + public string ChannelId { get; set; } + + /// <summary> + /// ChannelName of the recording. + /// </summary> + public string ChannelName { get; set; } + + /// <summary> + /// Gets or sets the program identifier. + /// </summary> + /// <value>The program identifier.</value> + public string ProgramId { get; set; } + + /// <summary> + /// Name of the recording. + /// </summary> + public string Name { get; set; } + + /// <summary> + /// Description of the recording. + /// </summary> + public string Description { get; set; } + + /// <summary> + /// The start date of the recording, in UTC. + /// </summary> + public DateTime StartDate { get; set; } + + /// <summary> + /// The end date of the recording, in UTC. + /// </summary> + public DateTime EndDate { get; set; } + + /// <summary> + /// Gets or sets the status. + /// </summary> + /// <value>The status.</value> + public RecordingStatus Status { get; set; } + + /// <summary> + /// Gets or sets the pre padding seconds. + /// </summary> + /// <value>The pre padding seconds.</value> + public int PrePaddingSeconds { get; set; } + + /// <summary> + /// Gets or sets the post padding seconds. + /// </summary> + /// <value>The post padding seconds.</value> + public int PostPaddingSeconds { get; set; } + } +} diff --git a/MediaBrowser.Controller/MediaBrowser.Controller.csproj b/MediaBrowser.Controller/MediaBrowser.Controller.csproj index f2837a1f1..2beb3588e 100644 --- a/MediaBrowser.Controller/MediaBrowser.Controller.csproj +++ b/MediaBrowser.Controller/MediaBrowser.Controller.csproj @@ -89,8 +89,11 @@ <Compile Include="Entities\GameGenre.cs" /> <Compile Include="Entities\GameSystem.cs" /> <Compile Include="Entities\IByReferenceItem.cs" /> + <Compile Include="Entities\IHasAspectRatio.cs" /> + <Compile Include="Entities\IHasBudget.cs" /> <Compile Include="Entities\IHasCriticRating.cs" /> <Compile Include="Entities\IHasSoundtracks.cs" /> + <Compile Include="Entities\IHasTrailers.cs" /> <Compile Include="Entities\IItemByName.cs" /> <Compile Include="Entities\ILibraryItem.cs" /> <Compile Include="Entities\ImageSourceInfo.cs" /> @@ -103,18 +106,24 @@ <Compile Include="Library\ItemUpdateType.cs" /> <Compile Include="Library\IUserDataManager.cs" /> <Compile Include="Library\UserDataSaveEventArgs.cs" /> + <Compile Include="LiveTv\Channel.cs" /> <Compile Include="LiveTv\ChannelInfo.cs" /> <Compile Include="LiveTv\ILiveTvManager.cs" /> <Compile Include="LiveTv\ILiveTvService.cs" /> + <Compile Include="LiveTv\ImageResponseInfo.cs" /> + <Compile Include="LiveTv\ProgramInfo.cs" /> + <Compile Include="LiveTv\RecordingInfo.cs" /> + <Compile Include="LiveTv\SeriesTimerInfo.cs" /> + <Compile Include="LiveTv\TimerInfo.cs" /> <Compile Include="Localization\ILocalizationManager.cs" /> <Compile Include="Notifications\INotificationsRepository.cs" /> <Compile Include="Notifications\NotificationUpdateEventArgs.cs" /> <Compile Include="Providers\IDynamicInfoProvider.cs" /> <Compile Include="Providers\IImageProvider.cs" /> + <Compile Include="Providers\NameParser.cs" /> <Compile Include="Session\ISessionManager.cs" /> <Compile Include="Drawing\ImageExtensions.cs" /> <Compile Include="Entities\AggregateFolder.cs" /> - <Compile Include="Entities\Audio\Artist.cs" /> <Compile Include="Entities\Audio\Audio.cs" /> <Compile Include="Entities\Audio\MusicAlbum.cs" /> <Compile Include="Entities\Audio\MusicArtist.cs" /> diff --git a/MediaBrowser.Controller/Providers/BaseItemXmlParser.cs b/MediaBrowser.Controller/Providers/BaseItemXmlParser.cs index 9fdbbf3b7..617e4fd81 100644 --- a/MediaBrowser.Controller/Providers/BaseItemXmlParser.cs +++ b/MediaBrowser.Controller/Providers/BaseItemXmlParser.cs @@ -69,6 +69,12 @@ namespace MediaBrowser.Controller.Providers item.People.Clear(); item.Tags.Clear(); + var hasTrailers = item as IHasTrailers; + if (hasTrailers != null) + { + hasTrailers.RemoteTrailers.Clear(); + } + //Fetch(item, metadataFile, settings, Encoding.GetEncoding("ISO-8859-1"), cancellationToken); Fetch(item, metadataFile, settings, Encoding.UTF8, cancellationToken); } @@ -161,10 +167,14 @@ namespace MediaBrowser.Controller.Providers case "Budget": { var text = reader.ReadElementContentAsString(); - double value; - if (double.TryParse(text, NumberStyles.Any, _usCulture, out value)) + var hasBudget = item as IHasBudget; + if (hasBudget != null) { - item.Budget = value; + double value; + if (double.TryParse(text, NumberStyles.Any, _usCulture, out value)) + { + hasBudget.Budget = value; + } } break; @@ -173,10 +183,14 @@ namespace MediaBrowser.Controller.Providers case "Revenue": { var text = reader.ReadElementContentAsString(); - double value; - if (double.TryParse(text, NumberStyles.Any, _usCulture, out value)) + var hasBudget = item as IHasBudget; + if (hasBudget != null) { - item.Revenue = value; + double value; + if (double.TryParse(text, NumberStyles.Any, _usCulture, out value)) + { + hasBudget.Revenue = value; + } } break; @@ -375,9 +389,10 @@ namespace MediaBrowser.Controller.Providers { var val = reader.ReadElementContentAsString(); - if (!string.IsNullOrWhiteSpace(val)) + var hasAspectRatio = item as IHasAspectRatio; + if (!string.IsNullOrWhiteSpace(val) && hasAspectRatio != null) { - item.AspectRatio = val; + hasAspectRatio.AspectRatio = val; } break; } @@ -474,9 +489,26 @@ namespace MediaBrowser.Controller.Providers { var val = reader.ReadElementContentAsString(); - if (!string.IsNullOrWhiteSpace(val)) + var hasTrailers = item as IHasTrailers; + if (hasTrailers != null) { - item.AddTrailerUrl(val, false); + if (!string.IsNullOrWhiteSpace(val)) + { + hasTrailers.AddTrailerUrl(val, false); + } + } + break; + } + + case "Trailers": + { + using (var subtree = reader.ReadSubtree()) + { + var hasTrailers = item as IHasTrailers; + if (hasTrailers != null) + { + FetchDataFromTrailersNode(subtree, hasTrailers); + } } break; } @@ -921,6 +953,35 @@ namespace MediaBrowser.Controller.Providers } } + private void FetchDataFromTrailersNode(XmlReader reader, IHasTrailers item) + { + reader.MoveToContent(); + + while (reader.Read()) + { + if (reader.NodeType == XmlNodeType.Element) + { + switch (reader.Name) + { + case "Trailer": + { + var val = reader.ReadElementContentAsString(); + + if (!string.IsNullOrWhiteSpace(val)) + { + item.AddTrailerUrl(val, false); + } + break; + } + + default: + reader.Skip(); + break; + } + } + } + } + protected async Task FetchChaptersFromXmlNode(BaseItem item, XmlReader reader, IItemRepository repository, CancellationToken cancellationToken) { var runtime = item.RunTimeTicks ?? 0; @@ -1071,9 +1132,10 @@ namespace MediaBrowser.Controller.Providers /// <returns>IEnumerable{PersonInfo}.</returns> private IEnumerable<PersonInfo> GetPersonsFromXmlNode(XmlReader reader) { - var names = new List<string>(); + var name = string.Empty; var type = "Actor"; // If type is not specified assume actor var role = string.Empty; + int? sortOrder = null; reader.MoveToContent(); @@ -1084,7 +1146,7 @@ namespace MediaBrowser.Controller.Providers switch (reader.Name) { case "Name": - names.AddRange(SplitNames(reader.ReadElementContentAsString())); + name = reader.ReadElementContentAsString() ?? string.Empty; break; case "Type": @@ -1108,6 +1170,20 @@ namespace MediaBrowser.Controller.Providers } break; } + case "SortOrder": + { + var val = reader.ReadElementContentAsString(); + + if (!string.IsNullOrWhiteSpace(val)) + { + int intVal; + if (int.TryParse(val, NumberStyles.Integer, _usCulture, out intVal)) + { + sortOrder = intVal; + } + } + break; + } default: reader.Skip(); @@ -1116,7 +1192,15 @@ namespace MediaBrowser.Controller.Providers } } - return names.Select(n => new PersonInfo { Name = n.Trim(), Role = role, Type = type }); + var personInfo = new PersonInfo + { + Name = name.Trim(), + Role = role, + Type = type, + SortOrder = sortOrder + }; + + return new[] { personInfo }; } /// <summary> diff --git a/MediaBrowser.Controller/Providers/NameParser.cs b/MediaBrowser.Controller/Providers/NameParser.cs new file mode 100644 index 000000000..726f0e60e --- /dev/null +++ b/MediaBrowser.Controller/Providers/NameParser.cs @@ -0,0 +1,39 @@ +using System; +using System.Text.RegularExpressions; + +namespace MediaBrowser.Controller.Providers +{ + public static class NameParser + { + static readonly Regex[] NameMatches = new[] { + new Regex(@"(?<name>.*)\((?<year>\d{4})\)"), // matches "My Movie (2001)" and gives us the name and the year + new Regex(@"(?<name>.*)(\.(?<year>\d{4})(\.|$)).*$"), + new Regex(@"(?<name>.*)") // last resort matches the whole string as the name + }; + + + /// <summary> + /// Parses the name. + /// </summary> + /// <param name="name">The name.</param> + /// <param name="justName">Name of the just.</param> + /// <param name="year">The year.</param> + public static void ParseName(string name, out string justName, out int? year) + { + justName = null; + year = null; + foreach (var re in NameMatches) + { + Match m = re.Match(name); + if (m.Success) + { + justName = m.Groups["name"].Value.Trim(); + string y = m.Groups["year"] != null ? m.Groups["year"].Value : null; + int temp; + year = Int32.TryParse(y, out temp) ? temp : (int?)null; + break; + } + } + } + } +} diff --git a/MediaBrowser.Model.Portable/MediaBrowser.Model.Portable.csproj b/MediaBrowser.Model.Portable/MediaBrowser.Model.Portable.csproj index 726df1eb5..67888aa46 100644 --- a/MediaBrowser.Model.Portable/MediaBrowser.Model.Portable.csproj +++ b/MediaBrowser.Model.Portable/MediaBrowser.Model.Portable.csproj @@ -224,27 +224,36 @@ <Compile Include="..\MediaBrowser.Model\IO\IZipClient.cs"> <Link>IO\IZipClient.cs</Link> </Compile> - <Compile Include="..\MediaBrowser.Model\LiveTv\ChannelGuide.cs"> - <Link>LiveTv\ChannelGuide.cs</Link> - </Compile> <Compile Include="..\MediaBrowser.Model\LiveTv\ChannelInfoDto.cs"> <Link>LiveTv\ChannelInfoDto.cs</Link> </Compile> + <Compile Include="..\MediaBrowser.Model\LiveTv\ChannelQuery.cs"> + <Link>LiveTv\ChannelQuery.cs</Link> + </Compile> <Compile Include="..\MediaBrowser.Model\LiveTv\ChannelType.cs"> <Link>LiveTv\ChannelType.cs</Link> </Compile> <Compile Include="..\MediaBrowser.Model\LiveTv\LiveTvServiceInfo.cs"> <Link>LiveTv\LiveTvServiceInfo.cs</Link> </Compile> - <Compile Include="..\MediaBrowser.Model\LiveTv\ProgramInfo.cs"> - <Link>LiveTv\ProgramInfo.cs</Link> + <Compile Include="..\MediaBrowser.Model\LiveTv\ProgramInfoDto.cs"> + <Link>LiveTv\ProgramInfoDto.cs</Link> </Compile> - <Compile Include="..\MediaBrowser.Model\LiveTv\RecordingInfo.cs"> - <Link>LiveTv\RecordingInfo.cs</Link> + <Compile Include="..\MediaBrowser.Model\LiveTv\ProgramQuery.cs"> + <Link>LiveTv\ProgramQuery.cs</Link> + </Compile> + <Compile Include="..\MediaBrowser.Model\LiveTv\RecordingInfoDto.cs"> + <Link>LiveTv\RecordingInfoDto.cs</Link> </Compile> <Compile Include="..\MediaBrowser.Model\LiveTv\RecordingQuery.cs"> <Link>LiveTv\RecordingQuery.cs</Link> </Compile> + <Compile Include="..\MediaBrowser.Model\LiveTv\RecordingStatus.cs"> + <Link>LiveTv\RecordingStatus.cs</Link> + </Compile> + <Compile Include="..\MediaBrowser.Model\LiveTv\TimerInfoDto.cs"> + <Link>LiveTv\TimerInfoDto.cs</Link> + </Compile> <Compile Include="..\MediaBrowser.Model\Logging\ILogger.cs"> <Link>Logging\ILogger.cs</Link> </Compile> @@ -311,6 +320,9 @@ <Compile Include="..\MediaBrowser.Model\Querying\ArtistsQuery.cs"> <Link>Querying\ArtistsQuery.cs</Link> </Compile> + <Compile Include="..\MediaBrowser.Model\Querying\EpisodeQuery.cs"> + <Link>Querying\EpisodeQuery.cs</Link> + </Compile> <Compile Include="..\MediaBrowser.Model\Querying\ItemCountsQuery.cs"> <Link>Querying\ItemCountsQuery.cs</Link> </Compile> @@ -323,9 +335,6 @@ <Compile Include="..\MediaBrowser.Model\Querying\ItemQuery.cs"> <Link>Querying\ItemQuery.cs</Link> </Compile> - <Compile Include="..\MediaBrowser.Model\Querying\ItemReviewsResult.cs"> - <Link>Querying\ItemReviewsResult.cs</Link> - </Compile> <Compile Include="..\MediaBrowser.Model\Querying\ItemsByNameQuery.cs"> <Link>Querying\ItemsByNameQuery.cs</Link> </Compile> @@ -341,6 +350,9 @@ <Compile Include="..\MediaBrowser.Model\Querying\PersonsQuery.cs"> <Link>Querying\PersonsQuery.cs</Link> </Compile> + <Compile Include="..\MediaBrowser.Model\Querying\QueryResult.cs"> + <Link>Querying\QueryResult.cs</Link> + </Compile> <Compile Include="..\MediaBrowser.Model\Querying\SessionQuery.cs"> <Link>Querying\SessionQuery.cs</Link> </Compile> diff --git a/MediaBrowser.Model.net35/MediaBrowser.Model.net35.csproj b/MediaBrowser.Model.net35/MediaBrowser.Model.net35.csproj index 471db6f25..cfe4a5462 100644 --- a/MediaBrowser.Model.net35/MediaBrowser.Model.net35.csproj +++ b/MediaBrowser.Model.net35/MediaBrowser.Model.net35.csproj @@ -211,27 +211,36 @@ <Compile Include="..\MediaBrowser.Model\IO\IZipClient.cs"> <Link>IO\IZipClient.cs</Link> </Compile> - <Compile Include="..\MediaBrowser.Model\LiveTv\ChannelGuide.cs"> - <Link>LiveTv\ChannelGuide.cs</Link> - </Compile> <Compile Include="..\MediaBrowser.Model\LiveTv\ChannelInfoDto.cs"> <Link>LiveTv\ChannelInfoDto.cs</Link> </Compile> + <Compile Include="..\MediaBrowser.Model\LiveTv\ChannelQuery.cs"> + <Link>LiveTv\ChannelQuery.cs</Link> + </Compile> <Compile Include="..\MediaBrowser.Model\LiveTv\ChannelType.cs"> <Link>LiveTv\ChannelType.cs</Link> </Compile> <Compile Include="..\MediaBrowser.Model\LiveTv\LiveTvServiceInfo.cs"> <Link>LiveTv\LiveTvServiceInfo.cs</Link> </Compile> - <Compile Include="..\MediaBrowser.Model\LiveTv\ProgramInfo.cs"> - <Link>LiveTv\ProgramInfo.cs</Link> + <Compile Include="..\MediaBrowser.Model\LiveTv\ProgramInfoDto.cs"> + <Link>LiveTv\ProgramInfoDto.cs</Link> </Compile> - <Compile Include="..\MediaBrowser.Model\LiveTv\RecordingInfo.cs"> - <Link>LiveTv\RecordingInfo.cs</Link> + <Compile Include="..\MediaBrowser.Model\LiveTv\ProgramQuery.cs"> + <Link>LiveTv\ProgramQuery.cs</Link> + </Compile> + <Compile Include="..\MediaBrowser.Model\LiveTv\RecordingInfoDto.cs"> + <Link>LiveTv\RecordingInfoDto.cs</Link> </Compile> <Compile Include="..\MediaBrowser.Model\LiveTv\RecordingQuery.cs"> <Link>LiveTv\RecordingQuery.cs</Link> </Compile> + <Compile Include="..\MediaBrowser.Model\LiveTv\RecordingStatus.cs"> + <Link>LiveTv\RecordingStatus.cs</Link> + </Compile> + <Compile Include="..\MediaBrowser.Model\LiveTv\TimerInfoDto.cs"> + <Link>LiveTv\TimerInfoDto.cs</Link> + </Compile> <Compile Include="..\MediaBrowser.Model\Logging\ILogger.cs"> <Link>Logging\ILogger.cs</Link> </Compile> @@ -298,6 +307,9 @@ <Compile Include="..\MediaBrowser.Model\Querying\ArtistsQuery.cs"> <Link>Querying\ArtistsQuery.cs</Link> </Compile> + <Compile Include="..\MediaBrowser.Model\Querying\EpisodeQuery.cs"> + <Link>Querying\EpisodeQuery.cs</Link> + </Compile> <Compile Include="..\MediaBrowser.Model\Querying\ItemCountsQuery.cs"> <Link>Querying\ItemCountsQuery.cs</Link> </Compile> @@ -310,9 +322,6 @@ <Compile Include="..\MediaBrowser.Model\Querying\ItemQuery.cs"> <Link>Querying\ItemQuery.cs</Link> </Compile> - <Compile Include="..\MediaBrowser.Model\Querying\ItemReviewsResult.cs"> - <Link>Querying\ItemReviewsResult.cs</Link> - </Compile> <Compile Include="..\MediaBrowser.Model\Querying\ItemsByNameQuery.cs"> <Link>Querying\ItemsByNameQuery.cs</Link> </Compile> @@ -328,6 +337,9 @@ <Compile Include="..\MediaBrowser.Model\Querying\PersonsQuery.cs"> <Link>Querying\PersonsQuery.cs</Link> </Compile> + <Compile Include="..\MediaBrowser.Model\Querying\QueryResult.cs"> + <Link>Querying\QueryResult.cs</Link> + </Compile> <Compile Include="..\MediaBrowser.Model\Querying\SessionQuery.cs"> <Link>Querying\SessionQuery.cs</Link> </Compile> diff --git a/MediaBrowser.Model/ApiClient/IApiClient.cs b/MediaBrowser.Model/ApiClient/IApiClient.cs index 3f66f195e..16b3543eb 100644 --- a/MediaBrowser.Model/ApiClient/IApiClient.cs +++ b/MediaBrowser.Model/ApiClient/IApiClient.cs @@ -83,7 +83,7 @@ namespace MediaBrowser.Model.ApiClient /// <param name="startIndex">The start index.</param> /// <param name="limit">The limit.</param> /// <returns>Task{ItemReviewsResult}.</returns> - Task<ItemReviewsResult> GetCriticReviews(string itemId, CancellationToken cancellationToken, int? startIndex = null, int? limit = null); + Task<QueryResult<ItemReview>> GetCriticReviews(string itemId, CancellationToken cancellationToken, int? startIndex = null, int? limit = null); /// <summary> /// Gets the theme songs async. @@ -226,6 +226,20 @@ namespace MediaBrowser.Model.ApiClient Task<ItemCounts> GetItemCountsAsync(ItemCountsQuery query); /// <summary> + /// Gets the episodes asynchronous. + /// </summary> + /// <param name="query">The query.</param> + /// <returns>Task{ItemsResult}.</returns> + Task<ItemsResult> GetEpisodesAsync(EpisodeQuery query); + + /// <summary> + /// Gets the seasons asynchronous. + /// </summary> + /// <param name="query">The query.</param> + /// <returns>Task{ItemsResult}.</returns> + Task<ItemsResult> GetSeasonsAsync(SeasonQuery query); + + /// <summary> /// Queries for items /// </summary> /// <param name="query">The query.</param> diff --git a/MediaBrowser.Model/Dto/BaseItemDto.cs b/MediaBrowser.Model/Dto/BaseItemDto.cs index bfa751756..20f60586c 100644 --- a/MediaBrowser.Model/Dto/BaseItemDto.cs +++ b/MediaBrowser.Model/Dto/BaseItemDto.cs @@ -313,6 +313,12 @@ namespace MediaBrowser.Model.Dto public string SeriesId { get; set; } /// <summary> + /// Gets or sets the season identifier. + /// </summary> + /// <value>The season identifier.</value> + public string SeasonId { get; set; } + + /// <summary> /// Gets or sets the special feature count. /// </summary> /// <value>The special feature count.</value> diff --git a/MediaBrowser.Model/Dto/ItemByNameCounts.cs b/MediaBrowser.Model/Dto/ItemByNameCounts.cs index ae801e196..31b6d2da0 100644 --- a/MediaBrowser.Model/Dto/ItemByNameCounts.cs +++ b/MediaBrowser.Model/Dto/ItemByNameCounts.cs @@ -1,4 +1,5 @@ - +using System; + namespace MediaBrowser.Model.Dto { /// <summary> @@ -6,6 +7,8 @@ namespace MediaBrowser.Model.Dto /// </summary> public class ItemByNameCounts { + public Guid UserId { get; set; } + /// <summary> /// Gets or sets the total count. /// </summary> diff --git a/MediaBrowser.Model/LiveTv/ChannelGuide.cs b/MediaBrowser.Model/LiveTv/ChannelGuide.cs deleted file mode 100644 index d2bebac18..000000000 --- a/MediaBrowser.Model/LiveTv/ChannelGuide.cs +++ /dev/null @@ -1,23 +0,0 @@ -using System.Collections.Generic; - -namespace MediaBrowser.Model.LiveTv -{ - public class ChannelGuide - { - /// <summary> - /// Gets or sets the name of the service. - /// </summary> - /// <value>The name of the service.</value> - public string ServiceName { get; set; } - - /// <summary> - /// ChannelId for the EPG. - /// </summary> - public string ChannelId { get; set; } - - /// <summary> - /// List of all the programs for a specific channel - /// </summary> - public List<ProgramInfo> Programs { get; set; } - } -} diff --git a/MediaBrowser.Model/LiveTv/ChannelInfoDto.cs b/MediaBrowser.Model/LiveTv/ChannelInfoDto.cs index 8daaa75ca..020771e5e 100644 --- a/MediaBrowser.Model/LiveTv/ChannelInfoDto.cs +++ b/MediaBrowser.Model/LiveTv/ChannelInfoDto.cs @@ -1,4 +1,8 @@ - +using MediaBrowser.Model.Dto; +using MediaBrowser.Model.Entities; +using System; +using System.Collections.Generic; + namespace MediaBrowser.Model.LiveTv { /// <summary> @@ -19,6 +23,12 @@ namespace MediaBrowser.Model.LiveTv public string Id { get; set; } /// <summary> + /// Gets or sets the image tags. + /// </summary> + /// <value>The image tags.</value> + public Dictionary<ImageType, Guid> ImageTags { get; set; } + + /// <summary> /// Gets or sets the number. /// </summary> /// <value>The number.</value> @@ -35,5 +45,28 @@ namespace MediaBrowser.Model.LiveTv /// </summary> /// <value>The type of the channel.</value> public ChannelType ChannelType { get; set; } + + /// <summary> + /// Gets or sets the type. + /// </summary> + /// <value>The type.</value> + public string Type { get; set; } + + /// <summary> + /// Gets or sets the type of the media. + /// </summary> + /// <value>The type of the media.</value> + public string MediaType { get; set; } + + /// <summary> + /// Gets or sets the user data. + /// </summary> + /// <value>The user data.</value> + public UserItemDataDto UserData { get; set; } + + public ChannelInfoDto() + { + ImageTags = new Dictionary<ImageType, Guid>(); + } } } diff --git a/MediaBrowser.Model/LiveTv/ChannelQuery.cs b/MediaBrowser.Model/LiveTv/ChannelQuery.cs new file mode 100644 index 000000000..9fe74502f --- /dev/null +++ b/MediaBrowser.Model/LiveTv/ChannelQuery.cs @@ -0,0 +1,27 @@ + +namespace MediaBrowser.Model.LiveTv +{ + /// <summary> + /// Class ChannelQuery. + /// </summary> + public class ChannelQuery + { + /// <summary> + /// Gets or sets the name of the service. + /// </summary> + /// <value>The name of the service.</value> + public string ServiceName { get; set; } + + /// <summary> + /// Gets or sets the type of the channel. + /// </summary> + /// <value>The type of the channel.</value> + public ChannelType? ChannelType { get; set; } + + /// <summary> + /// Gets or sets the user identifier. + /// </summary> + /// <value>The user identifier.</value> + public string UserId { get; set; } + } +} diff --git a/MediaBrowser.Model/LiveTv/ProgramInfo.cs b/MediaBrowser.Model/LiveTv/ProgramInfo.cs deleted file mode 100644 index 6bf0e383f..000000000 --- a/MediaBrowser.Model/LiveTv/ProgramInfo.cs +++ /dev/null @@ -1,37 +0,0 @@ -using System; - -namespace MediaBrowser.Model.LiveTv -{ - public class ProgramInfo - { - /// <summary> - /// Id of the program. - /// </summary> - public string Id { get; set; } - - /// <summary> - /// Name of the program - /// </summary> - public string Name { get; set; } - - /// <summary> - /// Description of the progam. - /// </summary> - public string Description { get; set; } - - /// <summary> - /// The start date of the program, in UTC. - /// </summary> - public DateTime StartDate { get; set; } - - /// <summary> - /// The end date of the program, in UTC. - /// </summary> - public DateTime EndDate { get; set; } - - /// <summary> - /// Genre of the program. - /// </summary> - public string Genre { get; set; } - } -}
\ No newline at end of file diff --git a/MediaBrowser.Model/LiveTv/ProgramInfoDto.cs b/MediaBrowser.Model/LiveTv/ProgramInfoDto.cs new file mode 100644 index 000000000..6884d355d --- /dev/null +++ b/MediaBrowser.Model/LiveTv/ProgramInfoDto.cs @@ -0,0 +1,120 @@ +using System; +using System.Collections.Generic; + +namespace MediaBrowser.Model.LiveTv +{ + public class ProgramInfoDto + { + /// <summary> + /// Id of the program. + /// </summary> + public string Id { get; set; } + + /// <summary> + /// Gets or sets the external identifier. + /// </summary> + /// <value>The external identifier.</value> + public string ExternalId { get; set; } + + /// <summary> + /// Gets or sets the channel identifier. + /// </summary> + /// <value>The channel identifier.</value> + public string ChannelId { get; set; } + + /// <summary> + /// Gets or sets the community rating. + /// </summary> + /// <value>The community rating.</value> + public float? CommunityRating { get; set; } + + /// <summary> + /// Gets or sets the aspect ratio. + /// </summary> + /// <value>The aspect ratio.</value> + public string AspectRatio { get; set; } + + /// <summary> + /// Gets or sets the official rating. + /// </summary> + /// <value>The official rating.</value> + public string OfficialRating { get; set; } + + /// <summary> + /// Gets or sets the name of the service. + /// </summary> + /// <value>The name of the service.</value> + public string ServiceName { get; set; } + + /// <summary> + /// Name of the program + /// </summary> + public string Name { get; set; } + + /// <summary> + /// Description of the progam. + /// </summary> + public string Description { get; set; } + + /// <summary> + /// The start date of the program, in UTC. + /// </summary> + public DateTime StartDate { get; set; } + + /// <summary> + /// The end date of the program, in UTC. + /// </summary> + public DateTime EndDate { get; set; } + + /// <summary> + /// Genre of the program. + /// </summary> + public List<string> Genres { get; set; } + + /// <summary> + /// Gets or sets the quality. + /// </summary> + /// <value>The quality.</value> + public ProgramVideoQuality Quality { get; set; } + + /// <summary> + /// Gets or sets the audio. + /// </summary> + /// <value>The audio.</value> + public ProgramAudio Audio { get; set; } + + /// <summary> + /// Gets or sets the original air date. + /// </summary> + /// <value>The original air date.</value> + public DateTime? OriginalAirDate { get; set; } + + /// <summary> + /// Gets or sets a value indicating whether this instance is repeat. + /// </summary> + /// <value><c>true</c> if this instance is repeat; otherwise, <c>false</c>.</value> + public bool IsRepeat { get; set; } + + /// <summary> + /// Gets or sets the episode title. + /// </summary> + /// <value>The episode title.</value> + public string EpisodeTitle { get; set; } + + public ProgramInfoDto() + { + Genres = new List<string>(); + } + } + + public enum ProgramVideoQuality + { + StandardDefinition, + HighDefinition + } + + public enum ProgramAudio + { + Stereo + } +}
\ No newline at end of file diff --git a/MediaBrowser.Model/LiveTv/ProgramQuery.cs b/MediaBrowser.Model/LiveTv/ProgramQuery.cs new file mode 100644 index 000000000..ce0639aa0 --- /dev/null +++ b/MediaBrowser.Model/LiveTv/ProgramQuery.cs @@ -0,0 +1,31 @@ +namespace MediaBrowser.Model.LiveTv +{ + /// <summary> + /// Class ProgramQuery. + /// </summary> + public class ProgramQuery + { + /// <summary> + /// Gets or sets the name of the service. + /// </summary> + /// <value>The name of the service.</value> + public string ServiceName { get; set; } + + /// <summary> + /// Gets or sets the channel identifier. + /// </summary> + /// <value>The channel identifier.</value> + public string[] ChannelIdList { get; set; } + + /// <summary> + /// Gets or sets the user identifier. + /// </summary> + /// <value>The user identifier.</value> + public string UserId { get; set; } + + public ProgramQuery() + { + ChannelIdList = new string[] { }; + } + } +} diff --git a/MediaBrowser.Model/LiveTv/RecordingInfoDto.cs b/MediaBrowser.Model/LiveTv/RecordingInfoDto.cs new file mode 100644 index 000000000..86e82b562 --- /dev/null +++ b/MediaBrowser.Model/LiveTv/RecordingInfoDto.cs @@ -0,0 +1,119 @@ +using System; +using System.Collections.Generic; + +namespace MediaBrowser.Model.LiveTv +{ + public class RecordingInfoDto + { + /// <summary> + /// Id of the recording. + /// </summary> + public string Id { get; set; } + + /// <summary> + /// Gets or sets the external identifier. + /// </summary> + /// <value>The external identifier.</value> + public string ExternalId { get; set; } + + /// <summary> + /// Gets or sets the program identifier. + /// </summary> + /// <value>The program identifier.</value> + public string ProgramId { get; set; } + + /// <summary> + /// ChannelId of the recording. + /// </summary> + public string ChannelId { get; set; } + + /// <summary> + /// ChannelName of the recording. + /// </summary> + public string ChannelName { get; set; } + + /// <summary> + /// Name of the recording. + /// </summary> + public string Name { get; set; } + + /// <summary> + /// Gets or sets the path. + /// </summary> + /// <value>The path.</value> + public string Path { get; set; } + + /// <summary> + /// Description of the recording. + /// </summary> + public string Description { get; set; } + + /// <summary> + /// The start date of the recording, in UTC. + /// </summary> + public DateTime StartDate { get; set; } + + /// <summary> + /// The end date of the recording, in UTC. + /// </summary> + public DateTime EndDate { get; set; } + + /// <summary> + /// Gets or sets the status. + /// </summary> + /// <value>The status.</value> + public RecordingStatus Status { get; set; } + + /// <summary> + /// Genre of the program. + /// </summary> + public List<string> Genres { get; set; } + + /// <summary> + /// Gets or sets a value indicating whether this instance is repeat. + /// </summary> + /// <value><c>true</c> if this instance is repeat; otherwise, <c>false</c>.</value> + public bool IsRepeat { get; set; } + + /// <summary> + /// Gets or sets the episode title. + /// </summary> + /// <value>The episode title.</value> + public string EpisodeTitle { get; set; } + + /// <summary> + /// Gets or sets the duration ms. + /// </summary> + /// <value>The duration ms.</value> + public int DurationMs { get; set; } + + /// <summary> + /// Gets or sets the type of the media. + /// </summary> + /// <value>The type of the media.</value> + public string MediaType { get; set; } + + /// <summary> + /// Gets or sets the type of the channel. + /// </summary> + /// <value>The type of the channel.</value> + public ChannelType ChannelType { get; set; } + + /// <summary> + /// Gets or sets the official rating. + /// </summary> + /// <value>The official rating.</value> + public string OfficialRating { get; set; } + + /// <summary> + /// Gets or sets the community rating. + /// </summary> + /// <value>The community rating.</value> + public float? CommunityRating { get; set; } + + public RecordingInfoDto() + { + Genres = new List<string>(); + } + } +}
\ No newline at end of file diff --git a/MediaBrowser.Model/LiveTv/RecordingQuery.cs b/MediaBrowser.Model/LiveTv/RecordingQuery.cs index e7a91f4d5..0820c7785 100644 --- a/MediaBrowser.Model/LiveTv/RecordingQuery.cs +++ b/MediaBrowser.Model/LiveTv/RecordingQuery.cs @@ -6,9 +6,30 @@ public class RecordingQuery { /// <summary> - /// Gets or sets a value indicating whether this instance has recorded. + /// Gets or sets the channel identifier. /// </summary> - /// <value><c>null</c> if [has recorded] contains no value, <c>true</c> if [has recorded]; otherwise, <c>false</c>.</value> - public bool? HasRecorded { get; set; } + /// <value>The channel identifier.</value> + public string ChannelId { get; set; } + + /// <summary> + /// Gets or sets the name of the service. + /// </summary> + /// <value>The name of the service.</value> + public string ServiceName { get; set; } + } + + public class TimerQuery + { + /// <summary> + /// Gets or sets the channel identifier. + /// </summary> + /// <value>The channel identifier.</value> + public string ChannelId { get; set; } + + /// <summary> + /// Gets or sets the name of the service. + /// </summary> + /// <value>The name of the service.</value> + public string ServiceName { get; set; } } } diff --git a/MediaBrowser.Model/LiveTv/RecordingStatus.cs b/MediaBrowser.Model/LiveTv/RecordingStatus.cs new file mode 100644 index 000000000..08a7cfb0c --- /dev/null +++ b/MediaBrowser.Model/LiveTv/RecordingStatus.cs @@ -0,0 +1,22 @@ + +namespace MediaBrowser.Model.LiveTv +{ + public enum RecordingStatus + { + Pending, + InProgress, + Completed, + CompletedWithError, + Conflicted, + Deleted + } + + public enum RecurrenceType + { + Manual, + NewProgramEventsOneChannel, + AllProgramEventsOneChannel, + NewProgramEventsAllChannels, + AllProgramEventsAllChannels + } +} diff --git a/MediaBrowser.Model/LiveTv/RecordingInfo.cs b/MediaBrowser.Model/LiveTv/TimerInfoDto.cs index 55a30a4b3..9ebc2314d 100644 --- a/MediaBrowser.Model/LiveTv/RecordingInfo.cs +++ b/MediaBrowser.Model/LiveTv/TimerInfoDto.cs @@ -1,9 +1,8 @@ using System; -using System.Collections.Generic; namespace MediaBrowser.Model.LiveTv { - public class RecordingInfo + public class TimerInfoDto { /// <summary> /// Id of the recording. @@ -11,6 +10,12 @@ namespace MediaBrowser.Model.LiveTv public string Id { get; set; } /// <summary> + /// Gets or sets the external identifier. + /// </summary> + /// <value>The external identifier.</value> + public string ExternalId { get; set; } + + /// <summary> /// ChannelId of the recording. /// </summary> public string ChannelId { get; set; } @@ -21,6 +26,12 @@ namespace MediaBrowser.Model.LiveTv public string ChannelName { get; set; } /// <summary> + /// Gets or sets the program identifier. + /// </summary> + /// <value>The program identifier.</value> + public string ProgramId { get; set; } + + /// <summary> /// Name of the recording. /// </summary> public string Name { get; set; } @@ -41,38 +52,33 @@ namespace MediaBrowser.Model.LiveTv public DateTime EndDate { get; set; } /// <summary> - /// Status of the recording. - /// </summary> - public string Status { get; set; } //TODO: Enum for status?? Difference NextPvr,Argus,... - - /// <summary> - /// Quality of the Recording. - /// </summary> - public string Quality { get; set; } // TODO: Enum for quality?? Difference NextPvr,Argus,... - - /// <summary> - /// Recurring recording? + /// Gets or sets the status. /// </summary> - public bool Recurring { get; set; } + /// <value>The status.</value> + public RecordingStatus Status { get; set; } /// <summary> - /// Parent recurring. + /// Gets or sets the series timer identifier. /// </summary> - public string RecurringParent { get; set; } + /// <value>The series timer identifier.</value> + public string SeriesTimerId { get; set; } /// <summary> - /// Start date for the recurring, in UTC. + /// Gets or sets the pre padding seconds. /// </summary> - public DateTime RecurrringStartDate { get; set; } + /// <value>The pre padding seconds.</value> + public int PrePaddingSeconds { get; set; } /// <summary> - /// End date for the recurring, in UTC + /// Gets or sets the post padding seconds. /// </summary> - public DateTime RecurringEndDate { get; set; } + /// <value>The post padding seconds.</value> + public int PostPaddingSeconds { get; set; } /// <summary> - /// When do we need the recording? + /// Gets or sets the duration ms. /// </summary> - public List<string> DayMask { get; set; } + /// <value>The duration ms.</value> + public int DurationMs { get; set; } } -}
\ No newline at end of file +} diff --git a/MediaBrowser.Model/MediaBrowser.Model.csproj b/MediaBrowser.Model/MediaBrowser.Model.csproj index 6ff6fcf73..103e583ae 100644 --- a/MediaBrowser.Model/MediaBrowser.Model.csproj +++ b/MediaBrowser.Model/MediaBrowser.Model.csproj @@ -60,9 +60,13 @@ <Compile Include="Dto\ItemCounts.cs" /> <Compile Include="Dto\ItemIndex.cs" /> <Compile Include="Entities\PackageReviewInfo.cs" /> - <Compile Include="LiveTv\ChannelGuide.cs" /> - <Compile Include="LiveTv\ProgramInfo.cs" /> + <Compile Include="LiveTv\ChannelInfoDto.cs" /> + <Compile Include="LiveTv\ChannelQuery.cs" /> + <Compile Include="LiveTv\ProgramInfoDto.cs" /> + <Compile Include="LiveTv\ProgramQuery.cs" /> <Compile Include="LiveTv\RecordingQuery.cs" /> + <Compile Include="LiveTv\RecordingStatus.cs" /> + <Compile Include="LiveTv\TimerInfoDto.cs" /> <Compile Include="Providers\ImageProviderInfo.cs" /> <Compile Include="Providers\RemoteImageInfo.cs" /> <Compile Include="Dto\StudioDto.cs" /> @@ -76,10 +80,9 @@ <Compile Include="IO\IIsoManager.cs" /> <Compile Include="IO\IIsoMount.cs" /> <Compile Include="IO\IIsoMounter.cs" /> - <Compile Include="LiveTv\ChannelInfoDto.cs" /> <Compile Include="LiveTv\ChannelType.cs" /> <Compile Include="LiveTv\LiveTvServiceInfo.cs" /> - <Compile Include="LiveTv\RecordingInfo.cs" /> + <Compile Include="LiveTv\RecordingInfoDto.cs" /> <Compile Include="Net\WebSocketMessage.cs" /> <Compile Include="Net\WebSocketMessageType.cs" /> <Compile Include="Net\WebSocketState.cs" /> @@ -90,11 +93,12 @@ <Compile Include="Notifications\NotificationsSummary.cs" /> <Compile Include="Providers\RemoteImageResult.cs" /> <Compile Include="Querying\ArtistsQuery.cs" /> + <Compile Include="Querying\EpisodeQuery.cs" /> <Compile Include="Querying\ItemCountsQuery.cs" /> - <Compile Include="Querying\ItemReviewsResult.cs" /> <Compile Include="Querying\ItemsByNameQuery.cs" /> <Compile Include="Entities\BaseItemInfo.cs" /> <Compile Include="Querying\NextUpQuery.cs" /> + <Compile Include="Querying\QueryResult.cs" /> <Compile Include="Querying\SessionQuery.cs" /> <Compile Include="Querying\SimilarItemsQuery.cs" /> <Compile Include="Querying\UserQuery.cs" /> diff --git a/MediaBrowser.Model/Querying/EpisodeQuery.cs b/MediaBrowser.Model/Querying/EpisodeQuery.cs new file mode 100644 index 000000000..589b46433 --- /dev/null +++ b/MediaBrowser.Model/Querying/EpisodeQuery.cs @@ -0,0 +1,45 @@ + +namespace MediaBrowser.Model.Querying +{ + public class EpisodeQuery + { + public string UserId { get; set; } + + public string SeasonId { get; set; } + + public string SeriesId { get; set; } + + public bool? IsMissing { get; set; } + + public bool? IsVirtualUnaired { get; set; } + + public int? SeasonNumber { get; set; } + + public ItemFields[] Fields { get; set; } + + public EpisodeQuery() + { + Fields = new ItemFields[] { }; + } + } + + public class SeasonQuery + { + public string UserId { get; set; } + + public string SeriesId { get; set; } + + public bool? IsMissing { get; set; } + + public bool? IsVirtualUnaired { get; set; } + + public ItemFields[] Fields { get; set; } + + public bool? IsSpecialSeason { get; set; } + + public SeasonQuery() + { + Fields = new ItemFields[] { }; + } + } +} diff --git a/MediaBrowser.Model/Querying/ItemQuery.cs b/MediaBrowser.Model/Querying/ItemQuery.cs index 14c946ba1..6602e031f 100644 --- a/MediaBrowser.Model/Querying/ItemQuery.cs +++ b/MediaBrowser.Model/Querying/ItemQuery.cs @@ -266,6 +266,10 @@ namespace MediaBrowser.Model.Querying public double? MinCriticRating { get; set; } public int? AiredDuringSeason { get; set; } + + public DateTime? MinPremiereDate { get; set; } + + public DateTime? MaxPremiereDate { get; set; } /// <summary> /// Initializes a new instance of the <see cref="ItemQuery" /> class. diff --git a/MediaBrowser.Model/Querying/ItemSortBy.cs b/MediaBrowser.Model/Querying/ItemSortBy.cs index 12dfa9626..57e09d724 100644 --- a/MediaBrowser.Model/Querying/ItemSortBy.cs +++ b/MediaBrowser.Model/Querying/ItemSortBy.cs @@ -6,6 +6,7 @@ namespace MediaBrowser.Model.Querying /// </summary> public static class ItemSortBy { + public const string AiredEpisodeOrder = "AiredEpisodeOrder"; /// <summary> /// The album /// </summary> diff --git a/MediaBrowser.Model/Querying/ItemReviewsResult.cs b/MediaBrowser.Model/Querying/QueryResult.cs index 6a29827a9..1ecc1de6c 100644 --- a/MediaBrowser.Model/Querying/ItemReviewsResult.cs +++ b/MediaBrowser.Model/Querying/QueryResult.cs @@ -1,17 +1,13 @@ -using MediaBrowser.Model.Entities; - + namespace MediaBrowser.Model.Querying { - /// <summary> - /// Class ItemReviewsResult - /// </summary> - public class ItemReviewsResult + public class QueryResult<T> { /// <summary> - /// Gets or sets the item reviews. + /// Gets or sets the items. /// </summary> - /// <value>The item reviews.</value> - public ItemReview[] ItemReviews { get; set; } + /// <value>The items.</value> + public T[] Items { get; set; } /// <summary> /// The total number of records available @@ -22,9 +18,9 @@ namespace MediaBrowser.Model.Querying /// <summary> /// Initializes a new instance of the <see cref="ItemsResult" /> class. /// </summary> - public ItemReviewsResult() + public QueryResult() { - ItemReviews = new ItemReview[] { }; + Items = new T[] { }; } - } + } } diff --git a/MediaBrowser.Model/System/SystemInfo.cs b/MediaBrowser.Model/System/SystemInfo.cs index 9491139db..6a17ad133 100644 --- a/MediaBrowser.Model/System/SystemInfo.cs +++ b/MediaBrowser.Model/System/SystemInfo.cs @@ -93,6 +93,18 @@ namespace MediaBrowser.Model.System public string ProgramDataPath { get; set; } /// <summary> + /// Gets or sets the items by name path. + /// </summary> + /// <value>The items by name path.</value> + public string ItemsByNamePath { get; set; } + + /// <summary> + /// Gets or sets the log path. + /// </summary> + /// <value>The log path.</value> + public string LogPath { get; set; } + + /// <summary> /// Gets or sets the HTTP server port number. /// </summary> /// <value>The HTTP server port number.</value> diff --git a/MediaBrowser.Model/Tasks/TaskInfo.cs b/MediaBrowser.Model/Tasks/TaskInfo.cs index dee4fea7f..a7d500303 100644 --- a/MediaBrowser.Model/Tasks/TaskInfo.cs +++ b/MediaBrowser.Model/Tasks/TaskInfo.cs @@ -57,6 +57,12 @@ namespace MediaBrowser.Model.Tasks public string Category { get; set; } /// <summary> + /// Gets or sets a value indicating whether this instance is hidden. + /// </summary> + /// <value><c>true</c> if this instance is hidden; otherwise, <c>false</c>.</value> + public bool IsHidden { get; set; } + + /// <summary> /// Initializes a new instance of the <see cref="TaskInfo"/> class. /// </summary> public TaskInfo() diff --git a/MediaBrowser.Mono.userprefs b/MediaBrowser.Mono.userprefs index f1260b1da..b3c4d6d23 100644 --- a/MediaBrowser.Mono.userprefs +++ b/MediaBrowser.Mono.userprefs @@ -1,10 +1,14 @@ <Properties> <MonoDevelop.Ide.Workspace ActiveConfiguration="Debug|x86" /> - <MonoDevelop.Ide.Workbench ActiveDocument="MediaBrowser.Server.Mono\Program.cs"> + <MonoDevelop.Ide.Workbench ActiveDocument="MediaBrowser.Server.Implementations\Persistence\SqliteExtensions.cs"> <Files> - <File FileName="MediaBrowser.Server.Mono\Program.cs" Line="259" Column="4" /> - <File FileName="MediaBrowser.ServerApplication\ApplicationHost.cs" Line="58" Column="12" /> - <File FileName="MediaBrowser.Server.Mono\IO\FileSystemFactory.cs" Line="22" Column="1" /> + <File FileName="MediaBrowser.Server.Mono\Program.cs" Line="1" Column="1" /> + <File FileName="MediaBrowser.Server.Mono\IO\FileSystemFactory.cs" Line="1" Column="1" /> + <File FileName="MediaBrowser.Server.Implementations\Persistence\SqliteExtensions.cs" Line="22" Column="29" /> + <File FileName="MediaBrowser.Server.Mono\Native\ServerAuthorization.cs" Line="1" Column="1" /> + <File FileName="MediaBrowser.Server.Mono\Native\NativeApp.cs" Line="1" Column="1" /> + <File FileName="MediaBrowser.Server.Mono\Native\Assemblies.cs" Line="1" Column="1" /> + <File FileName="MediaBrowser.Server.Mono\Native\Autorun.cs" Line="1" Column="1" /> </Files> <Pads> <Pad Id="ProjectPad"> @@ -16,10 +20,13 @@ <Node name="References" expanded="True" /> <Node name="Web" expanded="True" /> </Node> - <Node name="MediaBrowser.Server.Implementations" expanded="True" /> + <Node name="MediaBrowser.Server.Implementations" expanded="True"> + <Node name="Persistence" expanded="True"> + <Node name="SqliteExtensions.cs" selected="True" /> + </Node> + </Node> <Node name="MediaBrowser.Server.Mono" expanded="True"> - <Node name="IO" expanded="True" /> - <Node name="Program.cs" selected="True" /> + <Node name="References" expanded="True" /> </Node> </State> </Pad> diff --git a/MediaBrowser.Providers/FolderProviderFromXml.cs b/MediaBrowser.Providers/FolderProviderFromXml.cs index 449de7450..8459fceef 100644 --- a/MediaBrowser.Providers/FolderProviderFromXml.cs +++ b/MediaBrowser.Providers/FolderProviderFromXml.cs @@ -34,7 +34,7 @@ namespace MediaBrowser.Providers /// <returns><c>true</c> if XXXX, <c>false</c> otherwise</returns> public override bool Supports(BaseItem item) { - return item is Folder && item.LocationType == LocationType.FileSystem; + return item.IsFolder && item.LocationType == LocationType.FileSystem; } /// <summary> diff --git a/MediaBrowser.Providers/ImageFromMediaLocationProvider.cs b/MediaBrowser.Providers/ImageFromMediaLocationProvider.cs index 65eee0f3a..f278a9089 100644 --- a/MediaBrowser.Providers/ImageFromMediaLocationProvider.cs +++ b/MediaBrowser.Providers/ImageFromMediaLocationProvider.cs @@ -101,8 +101,6 @@ namespace MediaBrowser.Providers { cancellationToken.ThrowIfCancellationRequested(); - var args = GetResolveArgsContainingImages(item); - // Make sure current image paths still exist item.ValidateImages(); @@ -114,6 +112,8 @@ namespace MediaBrowser.Providers cancellationToken.ThrowIfCancellationRequested(); + var args = GetResolveArgsContainingImages(item); + PopulateBaseItemImages(item, args); SetLastRefreshed(item, DateTime.UtcNow); diff --git a/MediaBrowser.Providers/LiveTv/ChannelProviderFromXml.cs b/MediaBrowser.Providers/LiveTv/ChannelProviderFromXml.cs new file mode 100644 index 000000000..b166750b5 --- /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; + } + + /// <summary> + /// Supportses the specified item. + /// </summary> + /// <param name="item">The item.</param> + /// <returns><c>true</c> if XXXX, <c>false</c> otherwise</returns> + public override bool Supports(BaseItem item) + { + return item is Channel; + } + + /// <summary> + /// Gets the priority. + /// </summary> + /// <value>The priority.</value> + 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; + } + + /// <summary> + /// Fetches metadata and returns true or false indicating if any work that requires persistence was done + /// </summary> + /// <param name="item">The item.</param> + /// <param name="force">if set to <c>true</c> [force].</param> + /// <param name="cancellationToken">The cancellation token.</param> + /// <returns>Task{System.Boolean}.</returns> + public override Task<bool> FetchAsync(BaseItem item, bool force, CancellationToken cancellationToken) + { + return Fetch(item, cancellationToken); + } + + /// <summary> + /// Fetches the specified item. + /// </summary> + /// <param name="item">The item.</param> + /// <param name="cancellationToken">The cancellation token.</param> + /// <returns><c>true</c> if XXXX, <c>false</c> otherwise</returns> + private async Task<bool> 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<Channel>(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 00924675a..b5b41c6d3 100644 --- a/MediaBrowser.Providers/MediaBrowser.Providers.csproj +++ b/MediaBrowser.Providers/MediaBrowser.Providers.csproj @@ -55,6 +55,7 @@ <Compile Include="Games\GameSystemProviderFromXml.cs" /> <Compile Include="ImageFromMediaLocationProvider.cs" /> <Compile Include="ImagesByNameProvider.cs" /> + <Compile Include="LiveTv\ChannelProviderFromXml.cs" /> <Compile Include="MediaInfo\AudioImageProvider.cs" /> <Compile Include="MediaInfo\BaseFFProbeProvider.cs" /> <Compile Include="MediaInfo\FFProbeAudioInfoProvider.cs" /> @@ -81,16 +82,13 @@ <Compile Include="Music\ArtistInfoFromSongProvider.cs" /> <Compile Include="Music\ArtistProviderFromXml.cs" /> <Compile Include="Music\FanArtAlbumProvider.cs" /> - <Compile Include="Music\FanArtArtistByNameProvider.cs" /> <Compile Include="Music\FanArtArtistProvider.cs" /> <Compile Include="Music\FanArtUpdatesPrescanTask.cs" /> <Compile Include="Music\LastfmAlbumProvider.cs" /> - <Compile Include="Music\LastfmArtistByNameProvider.cs" /> <Compile Include="Music\LastFmImageProvider.cs" /> <Compile Include="Music\LastfmArtistProvider.cs" /> <Compile Include="Music\LastfmBaseProvider.cs" /> <Compile Include="Music\LastfmHelper.cs" /> - <Compile Include="Music\AlbumDynamicInfoProvider.cs" /> <Compile Include="Music\ManualFanartAlbumProvider.cs" /> <Compile Include="Music\ManualFanartArtistProvider.cs" /> <Compile Include="Music\ManualLastFmImageProvider.cs" /> @@ -102,6 +100,7 @@ <Compile Include="Savers\AlbumXmlSaver.cs" /> <Compile Include="Savers\ArtistXmlSaver.cs" /> <Compile Include="Savers\BoxSetXmlSaver.cs" /> + <Compile Include="Savers\ChannelXmlSaver.cs" /> <Compile Include="Savers\EpisodeXmlSaver.cs" /> <Compile Include="Savers\FolderXmlSaver.cs" /> <Compile Include="Savers\GameSystemXmlSaver.cs" /> @@ -135,6 +134,7 @@ <Compile Include="TV\TvdbPersonImageProvider.cs" /> <Compile Include="TV\TvdbPrescanTask.cs" /> <Compile Include="TV\TvdbSeriesImageProvider.cs" /> + <Compile Include="UserRootFolderNameProvider.cs" /> </ItemGroup> <ItemGroup> <ProjectReference Include="..\MediaBrowser.Common\MediaBrowser.Common.csproj"> diff --git a/MediaBrowser.Providers/MediaInfo/AudioImageProvider.cs b/MediaBrowser.Providers/MediaInfo/AudioImageProvider.cs index f61057fe6..683dd4231 100644 --- a/MediaBrowser.Providers/MediaInfo/AudioImageProvider.cs +++ b/MediaBrowser.Providers/MediaInfo/AudioImageProvider.cs @@ -123,6 +123,8 @@ namespace MediaBrowser.Providers.MediaInfo /// <returns>Task{System.Boolean}.</returns> public override async Task<bool> FetchAsync(BaseItem item, bool force, CancellationToken cancellationToken) { + item.ValidateImages(); + var audio = (Audio)item; if (string.IsNullOrEmpty(audio.PrimaryImagePath) && audio.MediaStreams.Any(s => s.Type == MediaStreamType.Video)) diff --git a/MediaBrowser.Providers/MediaInfo/BaseFFProbeProvider.cs b/MediaBrowser.Providers/MediaInfo/BaseFFProbeProvider.cs index a84310310..5f285e6d8 100644 --- a/MediaBrowser.Providers/MediaInfo/BaseFFProbeProvider.cs +++ b/MediaBrowser.Providers/MediaInfo/BaseFFProbeProvider.cs @@ -1,5 +1,4 @@ -using System.Linq; -using MediaBrowser.Common.MediaInfo; +using MediaBrowser.Common.MediaInfo; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.MediaInfo; @@ -11,6 +10,7 @@ using MediaBrowser.Model.Serialization; using System; using System.Collections.Generic; using System.Globalization; +using System.Linq; using System.Threading; using System.Threading.Tasks; @@ -228,14 +228,21 @@ namespace MediaBrowser.Providers.MediaInfo // Get stream bitrate if (stream.Type != MediaStreamType.Subtitle) { + var bitrate = 0; + if (!string.IsNullOrEmpty(streamInfo.bit_rate)) { - stream.BitRate = int.Parse(streamInfo.bit_rate, UsCulture); + bitrate = int.Parse(streamInfo.bit_rate, UsCulture); } else if (formatInfo != null && !string.IsNullOrEmpty(formatInfo.bit_rate)) { // If the stream info doesn't have a bitrate get the value from the media format info - stream.BitRate = int.Parse(formatInfo.bit_rate, UsCulture); + bitrate = int.Parse(formatInfo.bit_rate, UsCulture); + } + + if (bitrate > 0) + { + stream.BitRate = bitrate; } } diff --git a/MediaBrowser.Providers/MediaInfo/VideoImageProvider.cs b/MediaBrowser.Providers/MediaInfo/VideoImageProvider.cs index 7aea484fd..b57742042 100644 --- a/MediaBrowser.Providers/MediaInfo/VideoImageProvider.cs +++ b/MediaBrowser.Providers/MediaInfo/VideoImageProvider.cs @@ -173,6 +173,8 @@ namespace MediaBrowser.Providers.MediaInfo /// <returns>Task{System.Boolean}.</returns> public override async Task<bool> FetchAsync(BaseItem item, bool force, CancellationToken cancellationToken) { + item.ValidateImages(); + var video = (Video)item; // Double check this here in case force was used diff --git a/MediaBrowser.Providers/Movies/FanArtMovieProvider.cs b/MediaBrowser.Providers/Movies/FanArtMovieProvider.cs index 39908c3ee..be195d6d2 100644 --- a/MediaBrowser.Providers/Movies/FanArtMovieProvider.cs +++ b/MediaBrowser.Providers/Movies/FanArtMovieProvider.cs @@ -15,6 +15,8 @@ using System.IO; using System.Linq; using System.Threading; using System.Threading.Tasks; +using MediaBrowser.Model.Net; +using System.Net; namespace MediaBrowser.Providers.Movies { @@ -253,72 +255,42 @@ namespace MediaBrowser.Providers.Movies if (ConfigurationManager.Configuration.DownloadMovieImages.Primary && !item.HasImage(ImageType.Primary)) { - var image = images.FirstOrDefault(i => i.Type == ImageType.Primary); - - if (image != null) - { - await _providerManager.SaveImage(item, image.Url, FanArtResourcePool, ImageType.Primary, null, cancellationToken).ConfigureAwait(false); - } + await SaveImage(item, images, ImageType.Primary, cancellationToken).ConfigureAwait(false); } cancellationToken.ThrowIfCancellationRequested(); if (ConfigurationManager.Configuration.DownloadMovieImages.Logo && !item.HasImage(ImageType.Logo)) { - var image = images.FirstOrDefault(i => i.Type == ImageType.Logo); - - if (image != null) - { - await _providerManager.SaveImage(item, image.Url, FanArtResourcePool, ImageType.Logo, null, cancellationToken).ConfigureAwait(false); - } + await SaveImage(item, images, ImageType.Logo, cancellationToken).ConfigureAwait(false); } cancellationToken.ThrowIfCancellationRequested(); if (ConfigurationManager.Configuration.DownloadMovieImages.Art && !item.HasImage(ImageType.Art)) { - var image = images.FirstOrDefault(i => i.Type == ImageType.Art); - - if (image != null) - { - await _providerManager.SaveImage(item, image.Url, FanArtResourcePool, ImageType.Art, null, cancellationToken).ConfigureAwait(false); - } + await SaveImage(item, images, ImageType.Art, cancellationToken).ConfigureAwait(false); } cancellationToken.ThrowIfCancellationRequested(); if (ConfigurationManager.Configuration.DownloadMovieImages.Disc && !item.HasImage(ImageType.Disc)) { - var image = images.FirstOrDefault(i => i.Type == ImageType.Disc); - - if (image != null) - { - await _providerManager.SaveImage(item, image.Url, FanArtResourcePool, ImageType.Disc, null, cancellationToken).ConfigureAwait(false); - } + await SaveImage(item, images, ImageType.Disc, cancellationToken).ConfigureAwait(false); } cancellationToken.ThrowIfCancellationRequested(); if (ConfigurationManager.Configuration.DownloadMovieImages.Banner && !item.HasImage(ImageType.Banner)) { - var image = images.FirstOrDefault(i => i.Type == ImageType.Banner); - - if (image != null) - { - await _providerManager.SaveImage(item, image.Url, FanArtResourcePool, ImageType.Banner, null, cancellationToken).ConfigureAwait(false); - } + await SaveImage(item, images, ImageType.Banner, cancellationToken).ConfigureAwait(false); } cancellationToken.ThrowIfCancellationRequested(); if (ConfigurationManager.Configuration.DownloadMovieImages.Thumb && !item.HasImage(ImageType.Thumb)) { - var image = images.FirstOrDefault(i => i.Type == ImageType.Thumb); - - if (image != null) - { - await _providerManager.SaveImage(item, image.Url, FanArtResourcePool, ImageType.Thumb, null, cancellationToken).ConfigureAwait(false); - } + await SaveImage(item, images, ImageType.Thumb, cancellationToken).ConfigureAwait(false); } cancellationToken.ThrowIfCancellationRequested(); @@ -336,5 +308,25 @@ namespace MediaBrowser.Providers.Movies } } } + + private async Task SaveImage(BaseItem item, List<RemoteImageInfo> images, ImageType type, CancellationToken cancellationToken) + { + foreach (var image in images.Where(i => i.Type == type)) + { + try + { + await _providerManager.SaveImage(item, image.Url, FanArtResourcePool, type, null, cancellationToken).ConfigureAwait(false); + break; + } + catch (HttpException ex) + { + // Sometimes fanart has bad url's in their xml + if (ex.StatusCode.HasValue && ex.StatusCode.Value == HttpStatusCode.NotFound) + { + continue; + } + } + } + } } } diff --git a/MediaBrowser.Providers/Movies/FanArtMovieUpdatesPrescanTask.cs b/MediaBrowser.Providers/Movies/FanArtMovieUpdatesPrescanTask.cs index cc2293968..aff71c6db 100644 --- a/MediaBrowser.Providers/Movies/FanArtMovieUpdatesPrescanTask.cs +++ b/MediaBrowser.Providers/Movies/FanArtMovieUpdatesPrescanTask.cs @@ -68,8 +68,8 @@ namespace MediaBrowser.Providers.Movies var timestampFileInfo = new FileInfo(timestampFile); - // Don't check for tvdb updates anymore frequently than 24 hours - if (timestampFileInfo.Exists && (DateTime.UtcNow - _fileSystem.GetLastWriteTimeUtc(timestampFileInfo)).TotalDays < 1) + // Don't check for updates every single time + if (timestampFileInfo.Exists && (DateTime.UtcNow - _fileSystem.GetLastWriteTimeUtc(timestampFileInfo)).TotalDays < 3) { return; } diff --git a/MediaBrowser.Providers/Movies/ManualMovieDbPersonImageProvider.cs b/MediaBrowser.Providers/Movies/ManualMovieDbPersonImageProvider.cs index ecfae9d5c..b381de332 100644 --- a/MediaBrowser.Providers/Movies/ManualMovieDbPersonImageProvider.cs +++ b/MediaBrowser.Providers/Movies/ManualMovieDbPersonImageProvider.cs @@ -46,7 +46,12 @@ namespace MediaBrowser.Providers.Movies return images.Where(i => i.Type == imageType); } - public async Task<IEnumerable<RemoteImageInfo>> GetAllImages(BaseItem item, CancellationToken cancellationToken) + public Task<IEnumerable<RemoteImageInfo>> GetAllImages(BaseItem item, CancellationToken cancellationToken) + { + return GetAllImagesInternal(item, true, cancellationToken); + } + + public async Task<IEnumerable<RemoteImageInfo>> GetAllImagesInternal(BaseItem item, bool retryOnMissingData, CancellationToken cancellationToken) { var id = item.GetProviderId(MetadataProviders.Tmdb); @@ -70,11 +75,18 @@ namespace MediaBrowser.Providers.Movies { } + + if (retryOnMissingData) + { + await MovieDbPersonProvider.Current.DownloadPersonInfo(id, cancellationToken).ConfigureAwait(false); + + return await GetAllImagesInternal(item, false, cancellationToken).ConfigureAwait(false); + } } return new List<RemoteImageInfo>(); } - + private IEnumerable<RemoteImageInfo> GetImages(MovieDbPersonProvider.Images images, string baseImageUrl) { var list = new List<RemoteImageInfo>(); diff --git a/MediaBrowser.Providers/Movies/MovieDbProvider.cs b/MediaBrowser.Providers/Movies/MovieDbProvider.cs index cc6e07d62..51a2362c0 100644 --- a/MediaBrowser.Providers/Movies/MovieDbProvider.cs +++ b/MediaBrowser.Providers/Movies/MovieDbProvider.cs @@ -15,7 +15,6 @@ using System.Globalization; using System.IO; using System.Linq; using System.Net; -using System.Text.RegularExpressions; using System.Threading; using System.Threading.Tasks; @@ -190,12 +189,6 @@ namespace MediaBrowser.Providers.Movies internal static string ApiKey = "f6bd687ffa63cd282b6ff2c6877f2669"; internal static string AcceptHeader = "application/json,image/*"; - static readonly Regex[] NameMatches = new[] { - new Regex(@"(?<name>.*)\((?<year>\d{4})\)"), // matches "My Movie (2001)" and gives us the name and the year - new Regex(@"(?<name>.*)(\.(?<year>\d{4})(\.|$)).*$"), - new Regex(@"(?<name>.*)") // last resort matches the whole string as the name - }; - protected override bool NeedsRefreshInternal(BaseItem item, BaseProviderInfo providerInfo) { if (string.IsNullOrEmpty(item.GetProviderId(MetadataProviders.Tmdb))) @@ -273,11 +266,6 @@ namespace MediaBrowser.Providers.Movies if (string.IsNullOrEmpty(id)) { id = await FindId(item, cancellationToken).ConfigureAwait(false); - - if (!string.IsNullOrEmpty(id)) - { - item.SetProviderId(MetadataProviders.Tmdb, id); - } } if (!string.IsNullOrEmpty(id)) @@ -315,30 +303,6 @@ namespace MediaBrowser.Providers.Movies } /// <summary> - /// Parses the name. - /// </summary> - /// <param name="name">The name.</param> - /// <param name="justName">Name of the just.</param> - /// <param name="year">The year.</param> - public static void ParseName(string name, out string justName, out int? year) - { - justName = null; - year = null; - foreach (var re in NameMatches) - { - Match m = re.Match(name); - if (m.Success) - { - justName = m.Groups["name"].Value.Trim(); - string y = m.Groups["year"] != null ? m.Groups["year"].Value : null; - int temp; - year = Int32.TryParse(y, out temp) ? temp : (int?)null; - break; - } - } - } - - /// <summary> /// Finds the id. /// </summary> /// <param name="item">The item.</param> @@ -348,7 +312,7 @@ namespace MediaBrowser.Providers.Movies { int? yearInName; string name = item.Name; - ParseName(name, out name, out yearInName); + NameParser.ParseName(name, out name, out yearInName); var year = item.ProductionYear ?? yearInName; @@ -446,36 +410,71 @@ namespace MediaBrowser.Providers.Movies if (searchResult != null) { - foreach (var possible in searchResult.results) - { - string matchedName = possible.title ?? possible.name; - string id = possible.id.ToString(CultureInfo.InvariantCulture); + return FindIdOfBestResult(searchResult.results, name, year); + } + + return null; + } - if (matchedName != null) + private string FindIdOfBestResult(List<TmdbMovieSearchResult> results, string name, int? year) + { + if (year.HasValue) + { + // Take the first result from the same year + var id = results.Where(i => + { + // Make sure it has a name + if (!string.IsNullOrEmpty(i.title ?? i.name)) { - Logger.Debug("Match " + matchedName + " for " + name); - if (year != null) + DateTime r; + + // These dates are always in this exact format + if (DateTime.TryParseExact(i.release_date, "yyyy-MM-dd", EnUs, DateTimeStyles.None, out r)) { - DateTime r; - - //These dates are always in this exact format - if (DateTime.TryParseExact(possible.release_date, "yyyy-MM-dd", EnUs, DateTimeStyles.None, out r)) - { - if (Math.Abs(r.Year - year.Value) > 1) // allow a 1 year tolerance on release date - { - Logger.Debug("Result " + matchedName + " released on " + r + " did not match year " + year); - continue; - } - } + return r.Year == year.Value; } - //matched name and year - return id; } + return false; + }) + .Select(i => i.id.ToString(CultureInfo.InvariantCulture)) + .FirstOrDefault(); + + if (!string.IsNullOrEmpty(id)) + { + return id; + } + + // Take the first result within one year + id = results.Where(i => + { + // Make sure it has a name + if (!string.IsNullOrEmpty(i.title ?? i.name)) + { + DateTime r; + + // These dates are always in this exact format + if (DateTime.TryParseExact(i.release_date, "yyyy-MM-dd", EnUs, DateTimeStyles.None, out r)) + { + return Math.Abs(r.Year - year.Value) <= 1; + } + } + + return false; + }) + .Select(i => i.id.ToString(CultureInfo.InvariantCulture)) + .FirstOrDefault(); + + if (!string.IsNullOrEmpty(id)) + { + return id; } } - return null; + // Just take the first one + return results.Where(i => !string.IsNullOrEmpty(i.title ?? i.name)) + .Select(i => i.id.ToString(CultureInfo.InvariantCulture)) + .FirstOrDefault(); } /// <summary> @@ -506,6 +505,8 @@ namespace MediaBrowser.Providers.Movies var dataFilePath = GetDataFilePath(item); + var tmdbId = item.GetProviderId(MetadataProviders.Tmdb); + if (string.IsNullOrEmpty(dataFilePath) || !File.Exists(dataFilePath) || !File.Exists(GetImagesDataFilePath(item))) { var isBoxSet = item is BoxSet; @@ -514,7 +515,9 @@ namespace MediaBrowser.Providers.Movies if (mainResult == null) return; - var movieDataPath = GetMovieDataPath(ConfigurationManager.ApplicationPaths, isBoxSet, mainResult.id.ToString(_usCulture)); + tmdbId = mainResult.id.ToString(_usCulture); + + var movieDataPath = GetMovieDataPath(ConfigurationManager.ApplicationPaths, isBoxSet, tmdbId); dataFilePath = Path.Combine(movieDataPath, language + ".json"); @@ -534,7 +537,7 @@ namespace MediaBrowser.Providers.Movies if (isForcedRefresh || ConfigurationManager.Configuration.EnableTmdbUpdates || !HasAltMeta(item)) { - dataFilePath = GetDataFilePath(item); + dataFilePath = GetDataFilePath(item, tmdbId); if (!string.IsNullOrEmpty(dataFilePath)) { @@ -582,8 +585,6 @@ namespace MediaBrowser.Providers.Movies /// <returns>System.String.</returns> internal string GetDataFilePath(BaseItem item) { - var language = ConfigurationManager.Configuration.PreferredMetadataLanguage; - var id = item.GetProviderId(MetadataProviders.Tmdb); if (string.IsNullOrEmpty(id)) @@ -591,7 +592,14 @@ namespace MediaBrowser.Providers.Movies return null; } - var path = GetMovieDataPath(ConfigurationManager.ApplicationPaths, item is BoxSet, id); + return GetDataFilePath(item, id); + } + + internal string GetDataFilePath(BaseItem item, string tmdbId) + { + var language = ConfigurationManager.Configuration.PreferredMetadataLanguage; + + var path = GetMovieDataPath(ConfigurationManager.ApplicationPaths, item is BoxSet, tmdbId); path = Path.Combine(path, language + ".json"); @@ -682,188 +690,194 @@ namespace MediaBrowser.Providers.Movies /// <param name="movieData">The movie data.</param> private void ProcessMainInfo(BaseItem movie, CompleteMovieData movieData) { - if (movie != null && movieData != null) + if (!movie.LockedFields.Contains(MetadataFields.Name)) { - if (!movie.LockedFields.Contains(MetadataFields.Name)) - { - movie.Name = movieData.title ?? movieData.original_title ?? movieData.name ?? movie.Name; - } - if (!movie.LockedFields.Contains(MetadataFields.Overview)) - { - movie.Overview = WebUtility.HtmlDecode(movieData.overview); - movie.Overview = movie.Overview != null ? movie.Overview.Replace("\n\n", "\n") : null; - } - movie.HomePageUrl = movieData.homepage; + movie.Name = movieData.title ?? movieData.original_title ?? movieData.name ?? movie.Name; + } + if (!movie.LockedFields.Contains(MetadataFields.Overview)) + { + movie.Overview = WebUtility.HtmlDecode(movieData.overview); + movie.Overview = movie.Overview != null ? movie.Overview.Replace("\n\n", "\n") : null; + } + movie.HomePageUrl = movieData.homepage; - movie.Budget = movieData.budget; - movie.Revenue = movieData.revenue; + var hasBudget = movie as IHasBudget; + if (hasBudget != null) + { + hasBudget.Budget = movieData.budget; + hasBudget.Revenue = movieData.revenue; + } - if (!string.IsNullOrEmpty(movieData.tagline)) - { - movie.Taglines.Clear(); - movie.AddTagline(movieData.tagline); - } + if (!string.IsNullOrEmpty(movieData.tagline)) + { + movie.Taglines.Clear(); + movie.AddTagline(movieData.tagline); + } - movie.SetProviderId(MetadataProviders.Imdb, movieData.imdb_id); + movie.SetProviderId(MetadataProviders.Tmdb, movieData.id.ToString(_usCulture)); + movie.SetProviderId(MetadataProviders.Imdb, movieData.imdb_id); - if (movieData.belongs_to_collection != null) - { - movie.SetProviderId(MetadataProviders.TmdbCollection, - movieData.belongs_to_collection.id.ToString(CultureInfo.InvariantCulture)); + if (movieData.belongs_to_collection != null) + { + movie.SetProviderId(MetadataProviders.TmdbCollection, + movieData.belongs_to_collection.id.ToString(CultureInfo.InvariantCulture)); - var movieItem = movie as Movie; + var movieItem = movie as Movie; - if (movieItem != null) - { - movieItem.TmdbCollectionName = movieData.belongs_to_collection.name; - } - } - else + if (movieItem != null) { - movie.SetProviderId(MetadataProviders.TmdbCollection, null); // clear out any old entry + movieItem.TmdbCollectionName = movieData.belongs_to_collection.name; } + } + else + { + movie.SetProviderId(MetadataProviders.TmdbCollection, null); // clear out any old entry + } - float rating; - string voteAvg = movieData.vote_average.ToString(CultureInfo.InvariantCulture); + float rating; + string voteAvg = movieData.vote_average.ToString(CultureInfo.InvariantCulture); - // tmdb appears to have unified their numbers to always report "7.3" regardless of country - // so I removed the culture-specific processing here because it was not working for other countries -ebr - // Movies get this from imdb - if (movie is BoxSet && float.TryParse(voteAvg, NumberStyles.AllowDecimalPoint, CultureInfo.InvariantCulture, out rating)) - { - movie.CommunityRating = rating; - } + // tmdb appears to have unified their numbers to always report "7.3" regardless of country + // so I removed the culture-specific processing here because it was not working for other countries -ebr + // Movies get this from imdb + if (movie is BoxSet && float.TryParse(voteAvg, NumberStyles.AllowDecimalPoint, CultureInfo.InvariantCulture, out rating)) + { + movie.CommunityRating = rating; + } + + // Movies get this from imdb + if (movie is BoxSet) + { + movie.VoteCount = movieData.vote_count; + } + + //release date and certification are retrieved based on configured country and we fall back on US if not there and to minimun release date if still no match + if (movieData.releases != null && movieData.releases.countries != null) + { + var ourRelease = movieData.releases.countries.FirstOrDefault(c => c.iso_3166_1.Equals(ConfigurationManager.Configuration.MetadataCountryCode, StringComparison.OrdinalIgnoreCase)) ?? new Country(); + var usRelease = movieData.releases.countries.FirstOrDefault(c => c.iso_3166_1.Equals("US", StringComparison.OrdinalIgnoreCase)) ?? new Country(); + var minimunRelease = movieData.releases.countries.OrderBy(c => c.release_date).FirstOrDefault() ?? new Country(); - // Movies get this from imdb - if (movie is BoxSet) + if (!movie.LockedFields.Contains(MetadataFields.OfficialRating)) { - movie.VoteCount = movieData.vote_count; + var ratingPrefix = ConfigurationManager.Configuration.MetadataCountryCode.Equals("us", StringComparison.OrdinalIgnoreCase) ? "" : ConfigurationManager.Configuration.MetadataCountryCode + "-"; + movie.OfficialRating = !string.IsNullOrEmpty(ourRelease.certification) + ? ratingPrefix + ourRelease.certification + : !string.IsNullOrEmpty(usRelease.certification) + ? usRelease.certification + : !string.IsNullOrEmpty(minimunRelease.certification) + ? minimunRelease.iso_3166_1 + "-" + minimunRelease.certification + : null; } - //release date and certification are retrieved based on configured country and we fall back on US if not there and to minimun release date if still no match - if (movieData.releases != null && movieData.releases.countries != null) + if (ourRelease.release_date != default(DateTime)) { - var ourRelease = movieData.releases.countries.FirstOrDefault(c => c.iso_3166_1.Equals(ConfigurationManager.Configuration.MetadataCountryCode, StringComparison.OrdinalIgnoreCase)) ?? new Country(); - var usRelease = movieData.releases.countries.FirstOrDefault(c => c.iso_3166_1.Equals("US", StringComparison.OrdinalIgnoreCase)) ?? new Country(); - var minimunRelease = movieData.releases.countries.OrderBy(c => c.release_date).FirstOrDefault() ?? new Country(); - - if (!movie.LockedFields.Contains(MetadataFields.OfficialRating)) + if (ourRelease.release_date.Year != 1) { - var ratingPrefix = ConfigurationManager.Configuration.MetadataCountryCode.Equals("us", StringComparison.OrdinalIgnoreCase) ? "" : ConfigurationManager.Configuration.MetadataCountryCode + "-"; - movie.OfficialRating = !string.IsNullOrEmpty(ourRelease.certification) - ? ratingPrefix + ourRelease.certification - : !string.IsNullOrEmpty(usRelease.certification) - ? usRelease.certification - : !string.IsNullOrEmpty(minimunRelease.certification) - ? minimunRelease.iso_3166_1 + "-" + minimunRelease.certification - : null; + movie.PremiereDate = ourRelease.release_date.ToUniversalTime(); + movie.ProductionYear = ourRelease.release_date.Year; } - - if (ourRelease.release_date != default(DateTime)) - { - if (ourRelease.release_date.Year != 1) - { - movie.PremiereDate = ourRelease.release_date.ToUniversalTime(); - movie.ProductionYear = ourRelease.release_date.Year; - } - } - else if (usRelease.release_date != default(DateTime)) + } + else if (usRelease.release_date != default(DateTime)) + { + if (usRelease.release_date.Year != 1) { - if (usRelease.release_date.Year != 1) - { - movie.PremiereDate = usRelease.release_date.ToUniversalTime(); - movie.ProductionYear = usRelease.release_date.Year; - } - } - else if (minimunRelease.release_date != default(DateTime)) - { - if (minimunRelease.release_date.Year != 1) - { - - movie.PremiereDate = minimunRelease.release_date.ToUniversalTime(); - movie.ProductionYear = minimunRelease.release_date.Year; - } + movie.PremiereDate = usRelease.release_date.ToUniversalTime(); + movie.ProductionYear = usRelease.release_date.Year; } } - else + else if (minimunRelease.release_date != default(DateTime)) { - if (movieData.release_date.Year != 1) + if (minimunRelease.release_date.Year != 1) { - //no specific country release info at all - movie.PremiereDate = movieData.release_date.ToUniversalTime(); - movie.ProductionYear = movieData.release_date.Year; + + movie.PremiereDate = minimunRelease.release_date.ToUniversalTime(); + movie.ProductionYear = minimunRelease.release_date.Year; } } - - //if that didn't find a rating and we are a boxset, use the one from our first child - if (movie.OfficialRating == null && movie is BoxSet && !movie.LockedFields.Contains(MetadataFields.OfficialRating)) + } + else + { + if (movieData.release_date.Year != 1) { - var boxset = movie as BoxSet; - Logger.Info("MovieDbProvider - Using rating of first child of boxset..."); + //no specific country release info at all + movie.PremiereDate = movieData.release_date.ToUniversalTime(); + movie.ProductionYear = movieData.release_date.Year; + } + } - var firstChild = boxset.Children.Concat(boxset.GetLinkedChildren()).FirstOrDefault(); + //if that didn't find a rating and we are a boxset, use the one from our first child + if (movie.OfficialRating == null && movie is BoxSet && !movie.LockedFields.Contains(MetadataFields.OfficialRating)) + { + var boxset = movie as BoxSet; + Logger.Info("MovieDbProvider - Using rating of first child of boxset..."); - boxset.OfficialRating = firstChild != null ? firstChild.OfficialRating : null; - } + var firstChild = boxset.Children.Concat(boxset.GetLinkedChildren()).FirstOrDefault(); - if (movieData.runtime > 0) - movie.OriginalRunTimeTicks = TimeSpan.FromMinutes(movieData.runtime).Ticks; + boxset.OfficialRating = firstChild != null ? firstChild.OfficialRating : null; + } - //studios - if (movieData.production_companies != null && !movie.LockedFields.Contains(MetadataFields.Studios)) - { - movie.Studios.Clear(); + if (movieData.runtime > 0) + movie.OriginalRunTimeTicks = TimeSpan.FromMinutes(movieData.runtime).Ticks; - foreach (var studio in movieData.production_companies.Select(c => c.name)) - { - movie.AddStudio(studio); - } - } + //studios + if (movieData.production_companies != null && !movie.LockedFields.Contains(MetadataFields.Studios)) + { + movie.Studios.Clear(); - // genres - // Movies get this from imdb - if (movieData.genres != null && !movie.LockedFields.Contains(MetadataFields.Genres)) + foreach (var studio in movieData.production_companies.Select(c => c.name)) { - // Only grab them if a boxset or there are no genres. - // For movies and trailers we'll use imdb via omdb - if (movie is BoxSet || movie.Genres.Count == 0) - { - movie.Genres.Clear(); - - foreach (var genre in movieData.genres.Select(g => g.name)) - { - movie.AddGenre(genre); - } - } + movie.AddStudio(studio); } + } - if (!movie.LockedFields.Contains(MetadataFields.Cast)) + // genres + // Movies get this from imdb + if (movieData.genres != null && !movie.LockedFields.Contains(MetadataFields.Genres)) + { + // Only grab them if a boxset or there are no genres. + // For movies and trailers we'll use imdb via omdb + if (movie is BoxSet || movie.Genres.Count == 0) { - movie.People.Clear(); + movie.Genres.Clear(); - //Actors, Directors, Writers - all in People - //actors come from cast - if (movieData.casts != null && movieData.casts.cast != null) + foreach (var genre in movieData.genres.Select(g => g.name)) { - foreach (var actor in movieData.casts.cast.OrderBy(a => a.order)) movie.AddPerson(new PersonInfo { Name = actor.name.Trim(), Role = actor.character, Type = PersonType.Actor }); + movie.AddGenre(genre); } + } + } - //and the rest from crew - if (movieData.casts != null && movieData.casts.crew != null) - { - foreach (var person in movieData.casts.crew) movie.AddPerson(new PersonInfo { Name = person.name.Trim(), Role = person.job, Type = person.department }); - } + if (!movie.LockedFields.Contains(MetadataFields.Cast)) + { + movie.People.Clear(); + + //Actors, Directors, Writers - all in People + //actors come from cast + if (movieData.casts != null && movieData.casts.cast != null) + { + foreach (var actor in movieData.casts.cast.OrderBy(a => a.order)) movie.AddPerson(new PersonInfo { Name = actor.name.Trim(), Role = actor.character, Type = PersonType.Actor, SortOrder = actor.order }); } - if (movieData.keywords != null && movieData.keywords.keywords != null && !movie.LockedFields.Contains(MetadataFields.Tags)) + //and the rest from crew + if (movieData.casts != null && movieData.casts.crew != null) { - movie.Tags = movieData.keywords.keywords.Select(i => i.name).ToList(); + foreach (var person in movieData.casts.crew) movie.AddPerson(new PersonInfo { Name = person.name.Trim(), Role = person.job, Type = person.department }); } + } - if (movieData.trailers != null && movieData.trailers.youtube != null && - movieData.trailers.youtube.Count > 0) + if (movieData.keywords != null && movieData.keywords.keywords != null && !movie.LockedFields.Contains(MetadataFields.Tags)) + { + movie.Tags = movieData.keywords.keywords.Select(i => i.name).ToList(); + } + + if (movieData.trailers != null && movieData.trailers.youtube != null && + movieData.trailers.youtube.Count > 0) + { + var hasTrailers = movie as IHasTrailers; + if (hasTrailers != null) { - movie.RemoteTrailers = movieData.trailers.youtube.Select(i => new MediaUrl + hasTrailers.RemoteTrailers = movieData.trailers.youtube.Select(i => new MediaUrl { Url = string.Format("http://www.youtube.com/watch?v={0}", i.source), IsDirectLink = false, @@ -873,7 +887,6 @@ namespace MediaBrowser.Providers.Movies }).ToList(); } } - } private DateTime _lastRequestDate = DateTime.MinValue; diff --git a/MediaBrowser.Providers/Movies/MovieUpdatesPrescanTask.cs b/MediaBrowser.Providers/Movies/MovieUpdatesPrescanTask.cs index 50e04a9af..f8fb133c6 100644 --- a/MediaBrowser.Providers/Movies/MovieUpdatesPrescanTask.cs +++ b/MediaBrowser.Providers/Movies/MovieUpdatesPrescanTask.cs @@ -100,8 +100,8 @@ namespace MediaBrowser.Providers.Movies var timestampFileInfo = new FileInfo(timestampFile); - // Don't check for tvdb updates anymore frequently than 24 hours - if (timestampFileInfo.Exists && (DateTime.UtcNow - _fileSystem.GetLastWriteTimeUtc(timestampFileInfo)).TotalDays < 1) + // Don't check for updates every single time + if (timestampFileInfo.Exists && (DateTime.UtcNow - _fileSystem.GetLastWriteTimeUtc(timestampFileInfo)).TotalDays < 3) { return; } diff --git a/MediaBrowser.Providers/Movies/OpenMovieDatabaseProvider.cs b/MediaBrowser.Providers/Movies/OpenMovieDatabaseProvider.cs index 6550396e5..d881859c6 100644 --- a/MediaBrowser.Providers/Movies/OpenMovieDatabaseProvider.cs +++ b/MediaBrowser.Providers/Movies/OpenMovieDatabaseProvider.cs @@ -109,19 +109,11 @@ namespace MediaBrowser.Providers.Movies public override async Task<bool> FetchAsync(BaseItem item, bool force, CancellationToken cancellationToken) { - BaseProviderInfo data; - - if (!item.ProviderData.TryGetValue(Id, out data)) - { - data = new BaseProviderInfo(); - item.ProviderData[Id] = data; - } - var imdbId = item.GetProviderId(MetadataProviders.Imdb); if (string.IsNullOrEmpty(imdbId)) { - data.LastRefreshStatus = ProviderRefreshStatus.Success; + SetLastRefreshed(item, DateTime.UtcNow); return true; } @@ -182,9 +174,7 @@ namespace MediaBrowser.Providers.Movies ParseAdditionalMetadata(item, result); } - data.LastRefreshStatus = ProviderRefreshStatus.Success; SetLastRefreshed(item, DateTime.UtcNow); - return true; } diff --git a/MediaBrowser.Providers/Movies/PersonUpdatesPreScanTask.cs b/MediaBrowser.Providers/Movies/PersonUpdatesPreScanTask.cs index 2264ccd3f..489b0ad09 100644 --- a/MediaBrowser.Providers/Movies/PersonUpdatesPreScanTask.cs +++ b/MediaBrowser.Providers/Movies/PersonUpdatesPreScanTask.cs @@ -76,8 +76,8 @@ namespace MediaBrowser.Providers.Movies var timestampFileInfo = new FileInfo(timestampFile); - // Don't check for tvdb updates anymore frequently than 24 hours - if (timestampFileInfo.Exists && (DateTime.UtcNow - _fileSystem.GetLastWriteTimeUtc(timestampFileInfo)).TotalDays < 1) + // Don't check for updates every single time + if (timestampFileInfo.Exists && (DateTime.UtcNow - _fileSystem.GetLastWriteTimeUtc(timestampFileInfo)).TotalDays < 3) { return; } diff --git a/MediaBrowser.Providers/Music/AlbumDynamicInfoProvider.cs b/MediaBrowser.Providers/Music/AlbumDynamicInfoProvider.cs deleted file mode 100644 index 5c14a2f56..000000000 --- a/MediaBrowser.Providers/Music/AlbumDynamicInfoProvider.cs +++ /dev/null @@ -1,103 +0,0 @@ -using MediaBrowser.Controller.Configuration; -using MediaBrowser.Controller.Entities; -using MediaBrowser.Controller.Entities.Audio; -using MediaBrowser.Controller.Providers; -using MediaBrowser.Model.Logging; -using System; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; - -namespace MediaBrowser.Providers.Music -{ - /// <summary> - /// Class MusicAlbumDynamicInfoProvider - /// </summary> - public class AlbumDynamicInfoProvider : BaseMetadataProvider, IDynamicInfoProvider - { - /// <summary> - /// Initializes a new instance of the <see cref="BaseMetadataProvider" /> class. - /// </summary> - /// <param name="logManager">The log manager.</param> - /// <param name="configurationManager">The configuration manager.</param> - public AlbumDynamicInfoProvider(ILogManager logManager, IServerConfigurationManager configurationManager) - : base(logManager, configurationManager) - { - } - - /// <summary> - /// Supportses the specified item. - /// </summary> - /// <param name="item">The item.</param> - /// <returns><c>true</c> if XXXX, <c>false</c> otherwise</returns> - public override bool Supports(BaseItem item) - { - return item is MusicAlbum; - } - - /// <summary> - /// Needses the refresh internal. - /// </summary> - /// <param name="item">The item.</param> - /// <param name="providerInfo">The provider info.</param> - /// <returns><c>true</c> if XXXX, <c>false</c> otherwise</returns> - protected override bool NeedsRefreshInternal(BaseItem item, BaseProviderInfo providerInfo) - { - return true; - } - - /// <summary> - /// Fetches metadata and returns true or false indicating if any work that requires persistence was done - /// </summary> - /// <param name="item">The item.</param> - /// <param name="force">if set to <c>true</c> [force].</param> - /// <param name="cancellationToken">The cancellation token.</param> - /// <returns>Task{System.Boolean}.</returns> - public override Task<bool> FetchAsync(BaseItem item, bool force, CancellationToken cancellationToken) - { - var album = (MusicAlbum)item; - - var songs = album.RecursiveChildren - .OfType<Audio>() - .ToList(); - - album.AlbumArtist = songs - .Select(i => i.AlbumArtist) - .FirstOrDefault(i => !string.IsNullOrEmpty(i)); - - album.Artists = songs.SelectMany(i => i.Artists) - .Distinct(StringComparer.OrdinalIgnoreCase) - .ToList(); - - var date = songs.Select(i => i.PremiereDate) - .FirstOrDefault(i => i.HasValue); - - if (date.HasValue) - { - album.PremiereDate = date.Value; - album.ProductionYear = date.Value.Year; - } - else - { - var year = songs.Select(i => i.ProductionYear ?? 1800).FirstOrDefault(i => i != 1800); - - if (year != 1800) - { - album.ProductionYear = year; - } - } - - // Don't save to the db - return FalseTaskResult; - } - - /// <summary> - /// Gets the priority. - /// </summary> - /// <value>The priority.</value> - public override MetadataProviderPriority Priority - { - get { return MetadataProviderPriority.Last; } - } - } -} diff --git a/MediaBrowser.Providers/Music/AlbumInfoFromSongProvider.cs b/MediaBrowser.Providers/Music/AlbumInfoFromSongProvider.cs index 9606279f7..da49d36ed 100644 --- a/MediaBrowser.Providers/Music/AlbumInfoFromSongProvider.cs +++ b/MediaBrowser.Providers/Music/AlbumInfoFromSongProvider.cs @@ -37,7 +37,7 @@ namespace MediaBrowser.Providers.Music { get { - return "1"; + return "2"; } } @@ -65,7 +65,7 @@ namespace MediaBrowser.Providers.Music private Guid GetComparisonData(List<Audio> songs) { - var albumNames = songs.Select(i => i.AlbumArtist) + var albumArtistNames = songs.Select(i => i.AlbumArtist) .Where(i => !string.IsNullOrEmpty(i)) .Distinct(StringComparer.OrdinalIgnoreCase) .ToList(); @@ -78,10 +78,10 @@ namespace MediaBrowser.Providers.Music .Distinct(StringComparer.OrdinalIgnoreCase) .ToList(); - albumNames.AddRange(studios); - albumNames.AddRange(genres); + albumArtistNames.AddRange(studios); + albumArtistNames.AddRange(genres); - return string.Join(string.Empty, albumNames.OrderBy(i => i).ToArray()).GetMD5(); + return string.Join(string.Empty, albumArtistNames.OrderBy(i => i).ToArray()).GetMD5(); } public override Task<bool> FetchAsync(BaseItem item, bool force, CancellationToken cancellationToken) @@ -119,7 +119,34 @@ namespace MediaBrowser.Providers.Music album.Genres = songs.SelectMany(i => i.Genres) .Distinct(StringComparer.OrdinalIgnoreCase) .ToList(); + } + + album.AlbumArtist = songs + .Select(i => i.AlbumArtist) + .FirstOrDefault(i => !string.IsNullOrEmpty(i)); + + album.Artists = songs.SelectMany(i => i.Artists) + .Distinct(StringComparer.OrdinalIgnoreCase) + .ToList(); + + var date = songs.Select(i => i.PremiereDate) + .FirstOrDefault(i => i.HasValue); + + if (date.HasValue) + { + album.PremiereDate = date.Value; + album.ProductionYear = date.Value.Year; } + else + { + var year = songs.Select(i => i.ProductionYear ?? 1800).FirstOrDefault(i => i != 1800); + + if (year != 1800) + { + album.ProductionYear = year; + } + } + data.FileStamp = GetComparisonData(songs); diff --git a/MediaBrowser.Providers/Music/ArtistInfoFromSongProvider.cs b/MediaBrowser.Providers/Music/ArtistInfoFromSongProvider.cs index b5a3b92f9..7069e65ef 100644 --- a/MediaBrowser.Providers/Music/ArtistInfoFromSongProvider.cs +++ b/MediaBrowser.Providers/Music/ArtistInfoFromSongProvider.cs @@ -27,10 +27,15 @@ namespace MediaBrowser.Providers.Music protected override bool NeedsRefreshInternal(BaseItem item, BaseProviderInfo providerInfo) { - // If song metadata has changed - if (GetComparisonData((MusicArtist)item) != providerInfo.FileStamp) + var artist = (MusicArtist)item; + + if (!artist.IsAccessedByName) { - return true; + // If song metadata has changed + if (GetComparisonData(artist) != providerInfo.FileStamp) + { + return true; + } } return base.NeedsRefreshInternal(item, providerInfo); @@ -47,7 +52,7 @@ namespace MediaBrowser.Providers.Music return GetComparisonData(songs); } - private Guid GetComparisonData(List<Audio> songs) + private Guid GetComparisonData(IEnumerable<Audio> songs) { var genres = songs.SelectMany(i => i.Genres) .Distinct(StringComparer.OrdinalIgnoreCase) @@ -60,23 +65,26 @@ namespace MediaBrowser.Providers.Music { var artist = (MusicArtist)item; - BaseProviderInfo data; - if (!item.ProviderData.TryGetValue(Id, out data)) + if (!artist.IsAccessedByName) { - data = new BaseProviderInfo(); - item.ProviderData[Id] = data; - } + BaseProviderInfo data; + if (!item.ProviderData.TryGetValue(Id, out data)) + { + data = new BaseProviderInfo(); + item.ProviderData[Id] = data; + } - var songs = artist.RecursiveChildren.OfType<Audio>().ToList(); + var songs = artist.RecursiveChildren.OfType<Audio>().ToList(); - if (!item.LockedFields.Contains(MetadataFields.Genres)) - { - artist.Genres = songs.SelectMany(i => i.Genres) - .Distinct(StringComparer.OrdinalIgnoreCase) - .ToList(); - } + if (!item.LockedFields.Contains(MetadataFields.Genres)) + { + artist.Genres = songs.SelectMany(i => i.Genres) + .Distinct(StringComparer.OrdinalIgnoreCase) + .ToList(); + } - data.FileStamp = GetComparisonData(songs); + data.FileStamp = GetComparisonData(songs); + } SetLastRefreshed(item, DateTime.UtcNow); return TrueTaskResult; diff --git a/MediaBrowser.Providers/Music/ArtistProviderFromXml.cs b/MediaBrowser.Providers/Music/ArtistProviderFromXml.cs index 8cc49ac39..b7e846353 100644 --- a/MediaBrowser.Providers/Music/ArtistProviderFromXml.cs +++ b/MediaBrowser.Providers/Music/ArtistProviderFromXml.cs @@ -31,7 +31,7 @@ namespace MediaBrowser.Providers.Music /// <returns><c>true</c> if XXXX, <c>false</c> otherwise</returns> public override bool Supports(BaseItem item) { - return (item is Artist || item is MusicArtist) && item.LocationType == LocationType.FileSystem; + return (item is MusicArtist) && item.LocationType == LocationType.FileSystem; } /// <summary> @@ -88,16 +88,7 @@ namespace MediaBrowser.Providers.Music try { - var artist = item as Artist; - - if (artist != null) - { - new BaseItemXmlParser<Artist>(Logger).Fetch(artist, path, cancellationToken); - } - else - { - new BaseItemXmlParser<MusicArtist>(Logger).Fetch((MusicArtist)item, path, cancellationToken); - } + new BaseItemXmlParser<MusicArtist>(Logger).Fetch((MusicArtist)item, path, cancellationToken); } finally { diff --git a/MediaBrowser.Providers/Music/FanArtAlbumProvider.cs b/MediaBrowser.Providers/Music/FanArtAlbumProvider.cs index f67e2b509..a49de9a29 100644 --- a/MediaBrowser.Providers/Music/FanArtAlbumProvider.cs +++ b/MediaBrowser.Providers/Music/FanArtAlbumProvider.cs @@ -14,6 +14,8 @@ using System.IO; using System.Linq; using System.Threading; using System.Threading.Tasks; +using MediaBrowser.Model.Net; +using System.Net; namespace MediaBrowser.Providers.Music { @@ -174,23 +176,33 @@ namespace MediaBrowser.Providers.Music if (ConfigurationManager.Configuration.DownloadMusicAlbumImages.Primary && !item.HasImage(ImageType.Primary)) { - var image = images.FirstOrDefault(i => i.Type == ImageType.Primary); - - if (image != null) - { - await _providerManager.SaveImage(item, image.Url, FanArtResourcePool, ImageType.Primary, null, cancellationToken).ConfigureAwait(false); - } + await SaveImage(item, images, ImageType.Primary, cancellationToken).ConfigureAwait(false); } cancellationToken.ThrowIfCancellationRequested(); if (ConfigurationManager.Configuration.DownloadMusicAlbumImages.Disc && !item.HasImage(ImageType.Disc)) { - var image = images.FirstOrDefault(i => i.Type == ImageType.Disc); + await SaveImage(item, images, ImageType.Disc, cancellationToken).ConfigureAwait(false); + } + } - if (image != null) + private async Task SaveImage(BaseItem item, List<RemoteImageInfo> images, ImageType type, CancellationToken cancellationToken) + { + foreach (var image in images.Where(i => i.Type == type)) + { + try + { + await _providerManager.SaveImage(item, image.Url, FanArtResourcePool, type, null, cancellationToken).ConfigureAwait(false); + break; + } + catch (HttpException ex) { - await _providerManager.SaveImage(item, image.Url, FanArtResourcePool, ImageType.Disc, null, cancellationToken).ConfigureAwait(false); + // Sometimes fanart has bad url's in their xml + if (ex.StatusCode.HasValue && ex.StatusCode.Value == HttpStatusCode.NotFound) + { + continue; + } } } } diff --git a/MediaBrowser.Providers/Music/FanArtArtistByNameProvider.cs b/MediaBrowser.Providers/Music/FanArtArtistByNameProvider.cs deleted file mode 100644 index 5d18f16ff..000000000 --- a/MediaBrowser.Providers/Music/FanArtArtistByNameProvider.cs +++ /dev/null @@ -1,47 +0,0 @@ -using MediaBrowser.Common.IO; -using MediaBrowser.Common.Net; -using MediaBrowser.Controller.Configuration; -using MediaBrowser.Controller.Entities; -using MediaBrowser.Controller.Entities.Audio; -using MediaBrowser.Controller.IO; -using MediaBrowser.Controller.Providers; -using MediaBrowser.Model.Logging; - -namespace MediaBrowser.Providers.Music -{ - /// <summary> - /// Class FanArtArtistByNameProvider - /// </summary> - public class FanArtArtistByNameProvider : FanArtArtistProvider - { - /// <summary> - /// Initializes a new instance of the <see cref="FanArtArtistByNameProvider" /> class. - /// </summary> - public FanArtArtistByNameProvider(IHttpClient httpClient, ILogManager logManager, IServerConfigurationManager configurationManager, IProviderManager providerManager, IFileSystem fileSystem) - : base(httpClient, logManager, configurationManager, providerManager, fileSystem) - { - } - - /// <summary> - /// Supportses the specified item. - /// </summary> - /// <param name="item">The item.</param> - /// <returns><c>true</c> if XXXX, <c>false</c> otherwise</returns> - public override bool Supports(BaseItem item) - { - return item is Artist; - } - - /// <summary> - /// Gets a value indicating whether [save local meta]. - /// </summary> - /// <value><c>true</c> if [save local meta]; otherwise, <c>false</c>.</value> - protected override bool SaveLocalMeta - { - get - { - return true; - } - } - } -} diff --git a/MediaBrowser.Providers/Music/FanArtArtistProvider.cs b/MediaBrowser.Providers/Music/FanArtArtistProvider.cs index 6df21b61d..1b0a80d9a 100644 --- a/MediaBrowser.Providers/Music/FanArtArtistProvider.cs +++ b/MediaBrowser.Providers/Music/FanArtArtistProvider.cs @@ -16,6 +16,8 @@ using System.IO; using System.Linq; using System.Threading; using System.Threading.Tasks; +using MediaBrowser.Model.Net; +using System.Net; namespace MediaBrowser.Providers.Music { @@ -270,48 +272,28 @@ namespace MediaBrowser.Providers.Music if (ConfigurationManager.Configuration.DownloadMusicArtistImages.Primary && !item.HasImage(ImageType.Primary)) { - var image = images.FirstOrDefault(i => i.Type == ImageType.Primary); - - if (image != null) - { - await _providerManager.SaveImage(item, image.Url, FanArtResourcePool, ImageType.Primary, null, cancellationToken).ConfigureAwait(false); - } + await SaveImage(item, images, ImageType.Primary, cancellationToken).ConfigureAwait(false); } cancellationToken.ThrowIfCancellationRequested(); if (ConfigurationManager.Configuration.DownloadMusicArtistImages.Logo && !item.HasImage(ImageType.Logo)) { - var image = images.FirstOrDefault(i => i.Type == ImageType.Logo); - - if (image != null) - { - await _providerManager.SaveImage(item, image.Url, FanArtResourcePool, ImageType.Logo, null, cancellationToken).ConfigureAwait(false); - } + await SaveImage(item, images, ImageType.Logo, cancellationToken).ConfigureAwait(false); } cancellationToken.ThrowIfCancellationRequested(); if (ConfigurationManager.Configuration.DownloadMusicArtistImages.Art && !item.HasImage(ImageType.Art)) { - var image = images.FirstOrDefault(i => i.Type == ImageType.Art); - - if (image != null) - { - await _providerManager.SaveImage(item, image.Url, FanArtResourcePool, ImageType.Art, null, cancellationToken).ConfigureAwait(false); - } + await SaveImage(item, images, ImageType.Art, cancellationToken).ConfigureAwait(false); } cancellationToken.ThrowIfCancellationRequested(); if (ConfigurationManager.Configuration.DownloadMusicArtistImages.Banner && !item.HasImage(ImageType.Banner)) { - var image = images.FirstOrDefault(i => i.Type == ImageType.Banner); - - if (image != null) - { - await _providerManager.SaveImage(item, image.Url, FanArtResourcePool, ImageType.Banner, null, cancellationToken).ConfigureAwait(false); - } + await SaveImage(item, images, ImageType.Banner, cancellationToken).ConfigureAwait(false); } cancellationToken.ThrowIfCancellationRequested(); @@ -329,5 +311,25 @@ namespace MediaBrowser.Providers.Music } } } + + private async Task SaveImage(BaseItem item, List<RemoteImageInfo> images, ImageType type, CancellationToken cancellationToken) + { + foreach (var image in images.Where(i => i.Type == type)) + { + try + { + await _providerManager.SaveImage(item, image.Url, FanArtResourcePool, type, null, cancellationToken).ConfigureAwait(false); + break; + } + catch (HttpException ex) + { + // Sometimes fanart has bad url's in their xml + if (ex.StatusCode.HasValue && ex.StatusCode.Value == HttpStatusCode.NotFound) + { + continue; + } + } + } + } } } diff --git a/MediaBrowser.Providers/Music/FanArtUpdatesPrescanTask.cs b/MediaBrowser.Providers/Music/FanArtUpdatesPrescanTask.cs index 6d9a16e87..ddf212179 100644 --- a/MediaBrowser.Providers/Music/FanArtUpdatesPrescanTask.cs +++ b/MediaBrowser.Providers/Music/FanArtUpdatesPrescanTask.cs @@ -67,8 +67,8 @@ namespace MediaBrowser.Providers.Music var timestampFileInfo = new FileInfo(timestampFile); - // Don't check for tvdb updates anymore frequently than 24 hours - if (timestampFileInfo.Exists && (DateTime.UtcNow - _fileSystem.GetLastWriteTimeUtc(timestampFileInfo)).TotalDays < 1) + // Don't check for updates every single time + if (timestampFileInfo.Exists && (DateTime.UtcNow - _fileSystem.GetLastWriteTimeUtc(timestampFileInfo)).TotalDays < 3) { return; } diff --git a/MediaBrowser.Providers/Music/LastFmImageProvider.cs b/MediaBrowser.Providers/Music/LastFmImageProvider.cs index cd4005223..a2ae597d7 100644 --- a/MediaBrowser.Providers/Music/LastFmImageProvider.cs +++ b/MediaBrowser.Providers/Music/LastFmImageProvider.cs @@ -42,7 +42,7 @@ namespace MediaBrowser.Providers.Music /// <returns><c>true</c> if XXXX, <c>false</c> otherwise</returns> public override bool Supports(BaseItem item) { - return item is Artist || item is MusicArtist || item is MusicAlbum; + return item is MusicArtist || item is MusicAlbum; } /// <summary> diff --git a/MediaBrowser.Providers/Music/LastfmArtistByNameProvider.cs b/MediaBrowser.Providers/Music/LastfmArtistByNameProvider.cs deleted file mode 100644 index 26ac68c3d..000000000 --- a/MediaBrowser.Providers/Music/LastfmArtistByNameProvider.cs +++ /dev/null @@ -1,89 +0,0 @@ -using MediaBrowser.Common.Net; -using MediaBrowser.Controller.Configuration; -using MediaBrowser.Controller.Entities; -using MediaBrowser.Controller.Entities.Audio; -using MediaBrowser.Controller.Library; -using MediaBrowser.Model.Logging; -using MediaBrowser.Model.Serialization; -using System.Threading; -using System.Threading.Tasks; - -namespace MediaBrowser.Providers.Music -{ - /// <summary> - /// Class LastfmArtistByNameProvider - /// </summary> - public class LastfmArtistByNameProvider : LastfmArtistProvider - { - /// <summary> - /// Initializes a new instance of the <see cref="LastfmArtistByNameProvider" /> class. - /// </summary> - /// <param name="jsonSerializer">The json serializer.</param> - /// <param name="httpClient">The HTTP client.</param> - /// <param name="logManager">The log manager.</param> - /// <param name="configurationManager">The configuration manager.</param> - /// <param name="libraryManager">The library manager.</param> - public LastfmArtistByNameProvider(IJsonSerializer jsonSerializer, IHttpClient httpClient, ILogManager logManager, IServerConfigurationManager configurationManager, ILibraryManager libraryManager) - : base(jsonSerializer, httpClient, logManager, configurationManager, libraryManager) - { - } - - /// <summary> - /// Gets a value indicating whether [save local meta]. - /// </summary> - /// <value><c>true</c> if [save local meta]; otherwise, <c>false</c>.</value> - protected override bool SaveLocalMeta - { - get - { - return true; - } - } - - /// <summary> - /// Supportses the specified item. - /// </summary> - /// <param name="item">The item.</param> - /// <returns><c>true</c> if XXXX, <c>false</c> otherwise</returns> - public override bool Supports(BaseItem item) - { - return item is Artist; - } - - /// <summary> - /// Gets the provider version. - /// </summary> - /// <value>The provider version.</value> - protected override string ProviderVersion - { - get - { - return "7"; - } - } - - /// <summary> - /// Fetches the lastfm data. - /// </summary> - /// <param name="item">The item.</param> - /// <param name="musicBrainzId">The music brainz id.</param> - /// <param name="cancellationToken">The cancellation token.</param> - /// <returns>Task.</returns> - protected override async Task FetchLastfmData(BaseItem item, string musicBrainzId, bool force, CancellationToken cancellationToken) - { - var artist = (Artist)item; - - // See if we can avoid an http request by finding the matching MusicArtist entity - var musicArtist = Artist.FindMusicArtist(artist, LibraryManager); - - if (musicArtist != null && !force) - { - LastfmHelper.ProcessArtistData(musicArtist, artist); - } - else - { - await base.FetchLastfmData(item, musicBrainzId, force, cancellationToken).ConfigureAwait(false); - } - } - } -} diff --git a/MediaBrowser.Providers/Music/LastfmArtistProvider.cs b/MediaBrowser.Providers/Music/LastfmArtistProvider.cs index 3113d2541..2a7977fa6 100644 --- a/MediaBrowser.Providers/Music/LastfmArtistProvider.cs +++ b/MediaBrowser.Providers/Music/LastfmArtistProvider.cs @@ -84,17 +84,6 @@ namespace MediaBrowser.Providers.Music /// <returns>Task{System.String}.</returns> private async Task<string> FindId(BaseItem item, CancellationToken cancellationToken) { - if (item is Artist) - { - // Since MusicArtists are refreshed first, try to find it from one of them - var id = FindIdFromMusicArtistEntity(item); - - if (!string.IsNullOrEmpty(id)) - { - return id; - } - } - try { // If we don't get anything, go directly to music brainz diff --git a/MediaBrowser.Providers/Music/LastfmHelper.cs b/MediaBrowser.Providers/Music/LastfmHelper.cs index 800fdc438..8e2aed50e 100644 --- a/MediaBrowser.Providers/Music/LastfmHelper.cs +++ b/MediaBrowser.Providers/Music/LastfmHelper.cs @@ -39,21 +39,12 @@ namespace MediaBrowser.Providers.Music var musicArtist = artist as MusicArtist; - string imageSize; - if (musicArtist != null) { + string imageSize; musicArtist.LastFmImageUrl = GetImageUrl(data, out imageSize); musicArtist.LastFmImageSize = imageSize; } - - var artistByName = artist as Artist; - - if (artistByName != null) - { - artistByName.LastFmImageUrl = GetImageUrl(data, out imageSize); - artistByName.LastFmImageSize = imageSize; - } } private static string GetImageUrl(IHasLastFmImages data, out string size) @@ -85,15 +76,6 @@ namespace MediaBrowser.Providers.Music return null; } - public static void ProcessArtistData(MusicArtist source, Artist target) - { - target.PremiereDate = source.PremiereDate; - target.ProductionYear = source.ProductionYear; - target.Tags = source.Tags.ToList(); - target.Overview = source.Overview; - target.ProductionLocations = source.ProductionLocations.ToList(); - } - public static void ProcessAlbumData(BaseItem item, LastfmAlbum data) { var overview = data.wiki != null ? data.wiki.content : null; diff --git a/MediaBrowser.Providers/Music/ManualFanartArtistProvider.cs b/MediaBrowser.Providers/Music/ManualFanartArtistProvider.cs index 6ff891f60..cdb07d3d7 100644 --- a/MediaBrowser.Providers/Music/ManualFanartArtistProvider.cs +++ b/MediaBrowser.Providers/Music/ManualFanartArtistProvider.cs @@ -39,7 +39,7 @@ namespace MediaBrowser.Providers.Music public bool Supports(BaseItem item) { - return item is MusicArtist || item is Artist; + return item is MusicArtist; } public async Task<IEnumerable<RemoteImageInfo>> GetImages(BaseItem item, ImageType imageType, CancellationToken cancellationToken) diff --git a/MediaBrowser.Providers/Music/ManualLastFmImageProvider.cs b/MediaBrowser.Providers/Music/ManualLastFmImageProvider.cs index 0b1b95b1e..72e8c6f6b 100644 --- a/MediaBrowser.Providers/Music/ManualLastFmImageProvider.cs +++ b/MediaBrowser.Providers/Music/ManualLastFmImageProvider.cs @@ -25,7 +25,7 @@ namespace MediaBrowser.Providers.Music public bool Supports(BaseItem item) { - return item is MusicAlbum || item is MusicArtist || item is Artist; + return item is MusicAlbum || item is MusicArtist; } public async Task<IEnumerable<RemoteImageInfo>> GetImages(BaseItem item, ImageType imageType, CancellationToken cancellationToken) @@ -41,13 +41,6 @@ namespace MediaBrowser.Providers.Music RemoteImageInfo info = null; - var artist = item as Artist; - - if (artist != null) - { - info = GetInfo(artist.LastFmImageUrl, artist.LastFmImageSize); - } - var album = item as MusicAlbum; if (album != null) { diff --git a/MediaBrowser.Providers/Savers/ArtistXmlSaver.cs b/MediaBrowser.Providers/Savers/ArtistXmlSaver.cs index 55331db93..81f092a46 100644 --- a/MediaBrowser.Providers/Savers/ArtistXmlSaver.cs +++ b/MediaBrowser.Providers/Savers/ArtistXmlSaver.cs @@ -43,7 +43,8 @@ namespace MediaBrowser.Providers.Savers // If new metadata has been downloaded or metadata was manually edited, proceed if (wasMetadataDownloaded || wasMetadataEdited) { - if (item is Artist) + var artist = item as MusicArtist; + if (artist != null && artist.IsAccessedByName) { return true; } diff --git a/MediaBrowser.Providers/Savers/ChannelXmlSaver.cs b/MediaBrowser.Providers/Savers/ChannelXmlSaver.cs new file mode 100644 index 000000000..c6ac220a9 --- /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 +{ + /// <summary> + /// Class PersonXmlSaver + /// </summary> + public class ChannelXmlSaver : IMetadataSaver + { + /// <summary> + /// Determines whether [is enabled for] [the specified item]. + /// </summary> + /// <param name="item">The item.</param> + /// <param name="updateType">Type of the update.</param> + /// <returns><c>true</c> if [is enabled for] [the specified item]; otherwise, <c>false</c>.</returns> + 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; + } + + /// <summary> + /// Saves the specified item. + /// </summary> + /// <param name="item">The item.</param> + /// <param name="cancellationToken">The cancellation token.</param> + /// <returns>Task.</returns> + public void Save(BaseItem item, CancellationToken cancellationToken) + { + var builder = new StringBuilder(); + + builder.Append("<Item>"); + + XmlSaverHelpers.AddCommonNodes(item, builder); + + builder.Append("</Item>"); + + var xmlFilePath = GetSavePath(item); + + XmlSaverHelpers.Save(builder, xmlFilePath, new List<string> + { + }); + + // Set last refreshed so that the provider doesn't trigger after the file save + ChannelProviderFromXml.Current.SetLastRefreshed(item, DateTime.UtcNow); + } + + /// <summary> + /// Gets the save path. + /// </summary> + /// <param name="item">The item.</param> + /// <returns>System.String.</returns> + public string GetSavePath(BaseItem item) + { + return Path.Combine(item.Path, "channel.xml"); + } + } +} diff --git a/MediaBrowser.Providers/Savers/FolderXmlSaver.cs b/MediaBrowser.Providers/Savers/FolderXmlSaver.cs index 8c19e2c28..319f6c7d0 100644 --- a/MediaBrowser.Providers/Savers/FolderXmlSaver.cs +++ b/MediaBrowser.Providers/Savers/FolderXmlSaver.cs @@ -29,7 +29,7 @@ namespace MediaBrowser.Providers.Savers /// <returns><c>true</c> if [is enabled for] [the specified item]; otherwise, <c>false</c>.</returns> public bool IsEnabledFor(BaseItem item, ItemUpdateType updateType) { - if (!(item is Folder)) + if (!item.IsFolder) { return false; } diff --git a/MediaBrowser.Providers/Savers/MovieXmlSaver.cs b/MediaBrowser.Providers/Savers/MovieXmlSaver.cs index a974fc13e..1a287a918 100644 --- a/MediaBrowser.Providers/Savers/MovieXmlSaver.cs +++ b/MediaBrowser.Providers/Savers/MovieXmlSaver.cs @@ -119,7 +119,8 @@ namespace MediaBrowser.Providers.Savers "IMDBrating", "Description", "Artist", - "Album" + "Album", + "TmdbCollectionName" }); // Set last refreshed so that the provider doesn't trigger after the file save diff --git a/MediaBrowser.Providers/Savers/XmlSaverHelpers.cs b/MediaBrowser.Providers/Savers/XmlSaverHelpers.cs index 69276e0b8..186941988 100644 --- a/MediaBrowser.Providers/Savers/XmlSaverHelpers.cs +++ b/MediaBrowser.Providers/Savers/XmlSaverHelpers.cs @@ -63,6 +63,7 @@ namespace MediaBrowser.Providers.Savers "TMDbCollectionId", "TMDbId", "Trailer", + "Trailers", "TVcomId", "TvDbId", "Type", @@ -177,7 +178,7 @@ namespace MediaBrowser.Providers.Savers } } } - + return builder.ToString(); } @@ -267,19 +268,34 @@ namespace MediaBrowser.Providers.Savers } } - if (item.RemoteTrailers.Count > 0) + var hasTrailers = item as IHasTrailers; + if (hasTrailers != null) { - builder.Append("<Trailer>" + SecurityElement.Escape(item.RemoteTrailers[0].Url) + "</Trailer>"); - } + if (hasTrailers.RemoteTrailers.Count > 0) + { + builder.Append("<Trailers>"); - if (item.Budget.HasValue) - { - builder.Append("<Budget>" + SecurityElement.Escape(item.Budget.Value.ToString(UsCulture)) + "</Budget>"); + foreach (var trailer in hasTrailers.RemoteTrailers) + { + builder.Append("<Trailer>" + SecurityElement.Escape(trailer.Url) + "</Trailer>"); + } + + builder.Append("</Trailers>"); + } } - if (item.Revenue.HasValue) + var hasBudget = item as IHasBudget; + if (hasBudget != null) { - builder.Append("<Revenue>" + SecurityElement.Escape(item.Revenue.Value.ToString(UsCulture)) + "</Revenue>"); + if (hasBudget.Budget.HasValue) + { + builder.Append("<Budget>" + SecurityElement.Escape(hasBudget.Budget.Value.ToString(UsCulture)) + "</Budget>"); + } + + if (hasBudget.Revenue.HasValue) + { + builder.Append("<Revenue>" + SecurityElement.Escape(hasBudget.Revenue.Value.ToString(UsCulture)) + "</Revenue>"); + } } if (item.CommunityRating.HasValue) @@ -301,9 +317,13 @@ namespace MediaBrowser.Providers.Savers builder.Append("<Website>" + SecurityElement.Escape(item.HomePageUrl) + "</Website>"); } - if (!string.IsNullOrEmpty(item.AspectRatio)) + var hasAspectRatio = item as IHasAspectRatio; + if (hasAspectRatio != null) { - builder.Append("<AspectRatio>" + SecurityElement.Escape(item.AspectRatio) + "</AspectRatio>"); + if (!string.IsNullOrEmpty(hasAspectRatio.AspectRatio)) + { + builder.Append("<AspectRatio>" + SecurityElement.Escape(hasAspectRatio.AspectRatio) + "</AspectRatio>"); + } } if (!string.IsNullOrEmpty(item.Language)) @@ -459,6 +479,12 @@ namespace MediaBrowser.Providers.Savers builder.Append("<Name>" + SecurityElement.Escape(person.Name) + "</Name>"); builder.Append("<Type>" + SecurityElement.Escape(person.Type) + "</Type>"); builder.Append("<Role>" + SecurityElement.Escape(person.Role) + "</Role>"); + + if (person.SortOrder.HasValue) + { + builder.Append("<SortOrder>" + SecurityElement.Escape(person.SortOrder.Value.ToString(UsCulture)) + "</SortOrder>"); + } + builder.Append("</Person>"); } diff --git a/MediaBrowser.Providers/TV/EpisodeIndexNumberProvider.cs b/MediaBrowser.Providers/TV/EpisodeIndexNumberProvider.cs index fc8f55ae1..592c5dcac 100644 --- a/MediaBrowser.Providers/TV/EpisodeIndexNumberProvider.cs +++ b/MediaBrowser.Providers/TV/EpisodeIndexNumberProvider.cs @@ -27,6 +27,22 @@ namespace MediaBrowser.Providers.TV { } + protected override bool RefreshOnVersionChange + { + get + { + return true; + } + } + + protected override string ProviderVersion + { + get + { + return "2"; + } + } + /// <summary> /// Supportses the specified item. /// </summary> @@ -51,6 +67,16 @@ namespace MediaBrowser.Providers.TV episode.IndexNumber = TVUtils.GetEpisodeNumberFromFile(item.Path, item.Parent is Season); episode.IndexNumberEnd = TVUtils.GetEndingEpisodeNumberFromFile(item.Path); + if (!episode.ParentIndexNumber.HasValue) + { + var season = episode.Parent as Season; + + if (season != null) + { + episode.ParentIndexNumber = season.IndexNumber; + } + } + SetLastRefreshed(item, DateTime.UtcNow); return TrueTaskResult; diff --git a/MediaBrowser.Providers/TV/EpisodeProviderFromXml.cs b/MediaBrowser.Providers/TV/EpisodeProviderFromXml.cs index b6fdaaa83..7ddf421b9 100644 --- a/MediaBrowser.Providers/TV/EpisodeProviderFromXml.cs +++ b/MediaBrowser.Providers/TV/EpisodeProviderFromXml.cs @@ -2,7 +2,6 @@ using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.TV; -using MediaBrowser.Controller.IO; using MediaBrowser.Controller.Persistence; using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Entities; diff --git a/MediaBrowser.Providers/TV/FanArtSeasonProvider.cs b/MediaBrowser.Providers/TV/FanArtSeasonProvider.cs index 2a5b16bef..bf52ca349 100644 --- a/MediaBrowser.Providers/TV/FanArtSeasonProvider.cs +++ b/MediaBrowser.Providers/TV/FanArtSeasonProvider.cs @@ -13,6 +13,8 @@ using System.IO; using System.Linq; using System.Threading; using System.Threading.Tasks; +using MediaBrowser.Model.Net; +using System.Net; namespace MediaBrowser.Providers.TV { @@ -121,11 +123,26 @@ namespace MediaBrowser.Providers.TV { if (ConfigurationManager.Configuration.DownloadSeasonImages.Thumb && !season.HasImage(ImageType.Thumb)) { - var image = images.FirstOrDefault(i => i.Type == ImageType.Thumb); + await SaveImage(season, images, ImageType.Thumb, cancellationToken).ConfigureAwait(false); + } + } - if (image != null) + private async Task SaveImage(BaseItem item, List<RemoteImageInfo> images, ImageType type, CancellationToken cancellationToken) + { + foreach (var image in images.Where(i => i.Type == type)) + { + try + { + await _providerManager.SaveImage(item, image.Url, FanArtResourcePool, type, null, cancellationToken).ConfigureAwait(false); + break; + } + catch (HttpException ex) { - await _providerManager.SaveImage(season, image.Url, FanArtResourcePool, ImageType.Thumb, null, cancellationToken).ConfigureAwait(false); + // Sometimes fanart has bad url's in their xml + if (ex.StatusCode.HasValue && ex.StatusCode.Value == HttpStatusCode.NotFound) + { + continue; + } } } } diff --git a/MediaBrowser.Providers/TV/FanArtTVProvider.cs b/MediaBrowser.Providers/TV/FanArtTVProvider.cs index 1f20140c5..037e0b5bc 100644 --- a/MediaBrowser.Providers/TV/FanArtTVProvider.cs +++ b/MediaBrowser.Providers/TV/FanArtTVProvider.cs @@ -16,6 +16,8 @@ using System.IO; using System.Linq; using System.Threading; using System.Threading.Tasks; +using MediaBrowser.Model.Net; +using System.Net; namespace MediaBrowser.Providers.TV { @@ -198,60 +200,35 @@ namespace MediaBrowser.Providers.TV if (ConfigurationManager.Configuration.DownloadSeriesImages.Primary && !item.HasImage(ImageType.Primary)) { - var image = images.FirstOrDefault(i => i.Type == ImageType.Primary); - - if (image != null) - { - await _providerManager.SaveImage(item, image.Url, FanArtResourcePool, ImageType.Primary, null, cancellationToken).ConfigureAwait(false); - } + await SaveImage(item, images, ImageType.Primary, cancellationToken).ConfigureAwait(false); } cancellationToken.ThrowIfCancellationRequested(); if (ConfigurationManager.Configuration.DownloadSeriesImages.Logo && !item.HasImage(ImageType.Logo)) { - var image = images.FirstOrDefault(i => i.Type == ImageType.Logo); - - if (image != null) - { - await _providerManager.SaveImage(item, image.Url, FanArtResourcePool, ImageType.Logo, null, cancellationToken).ConfigureAwait(false); - } + await SaveImage(item, images, ImageType.Logo, cancellationToken).ConfigureAwait(false); } cancellationToken.ThrowIfCancellationRequested(); if (ConfigurationManager.Configuration.DownloadSeriesImages.Art && !item.HasImage(ImageType.Art)) { - var image = images.FirstOrDefault(i => i.Type == ImageType.Art); - - if (image != null) - { - await _providerManager.SaveImage(item, image.Url, FanArtResourcePool, ImageType.Art, null, cancellationToken).ConfigureAwait(false); - } + await SaveImage(item, images, ImageType.Art, cancellationToken).ConfigureAwait(false); } cancellationToken.ThrowIfCancellationRequested(); if (ConfigurationManager.Configuration.DownloadSeriesImages.Thumb && !item.HasImage(ImageType.Thumb)) { - var image = images.FirstOrDefault(i => i.Type == ImageType.Thumb); - - if (image != null) - { - await _providerManager.SaveImage(item, image.Url, FanArtResourcePool, ImageType.Thumb, null, cancellationToken).ConfigureAwait(false); - } + await SaveImage(item, images, ImageType.Thumb, cancellationToken).ConfigureAwait(false); } cancellationToken.ThrowIfCancellationRequested(); if (ConfigurationManager.Configuration.DownloadSeriesImages.Banner && !item.HasImage(ImageType.Banner)) { - var image = images.FirstOrDefault(i => i.Type == ImageType.Banner); - - if (image != null) - { - await _providerManager.SaveImage(item, image.Url, FanArtResourcePool, ImageType.Banner, null, cancellationToken).ConfigureAwait(false); - } + await SaveImage(item, images, ImageType.Banner, cancellationToken).ConfigureAwait(false); } cancellationToken.ThrowIfCancellationRequested(); @@ -271,6 +248,26 @@ namespace MediaBrowser.Providers.TV } + private async Task SaveImage(BaseItem item, List<RemoteImageInfo> images, ImageType type, CancellationToken cancellationToken) + { + foreach (var image in images.Where(i => i.Type == type)) + { + try + { + await _providerManager.SaveImage(item, image.Url, FanArtResourcePool, type, null, cancellationToken).ConfigureAwait(false); + break; + } + catch (HttpException ex) + { + // Sometimes fanart has bad url's in their xml + if (ex.StatusCode.HasValue && ex.StatusCode.Value == HttpStatusCode.NotFound) + { + continue; + } + } + } + } + /// <summary> /// Downloads the series XML. /// </summary> diff --git a/MediaBrowser.Providers/TV/FanArtTvUpdatesPrescanTask.cs b/MediaBrowser.Providers/TV/FanArtTvUpdatesPrescanTask.cs index 4bc7c3c4f..8ef04ed11 100644 --- a/MediaBrowser.Providers/TV/FanArtTvUpdatesPrescanTask.cs +++ b/MediaBrowser.Providers/TV/FanArtTvUpdatesPrescanTask.cs @@ -68,8 +68,8 @@ namespace MediaBrowser.Providers.TV var timestampFileInfo = new FileInfo(timestampFile); - // Don't check for tvdb updates anymore frequently than 24 hours - if (timestampFileInfo.Exists && (DateTime.UtcNow - _fileSystem.GetLastWriteTimeUtc(timestampFileInfo)).TotalDays < 1) + // Don't check for updates every single time + if (timestampFileInfo.Exists && (DateTime.UtcNow - _fileSystem.GetLastWriteTimeUtc(timestampFileInfo)).TotalDays < 3) { return; } diff --git a/MediaBrowser.Providers/TV/SeriesPostScanTask.cs b/MediaBrowser.Providers/TV/SeriesPostScanTask.cs index 7ad564d0f..56b06f490 100644 --- a/MediaBrowser.Providers/TV/SeriesPostScanTask.cs +++ b/MediaBrowser.Providers/TV/SeriesPostScanTask.cs @@ -150,6 +150,12 @@ namespace MediaBrowser.Providers.TV .ConfigureAwait(false); var hasNewEpisodes = false; + var hasNewSeasons = false; + + if (series.ContainsEpisodesWithoutSeasonFolders) + { + hasNewSeasons = await AddDummySeasonFolders(series, cancellationToken).ConfigureAwait(false); + } if (_config.Configuration.EnableInternetProviders) { @@ -157,7 +163,7 @@ namespace MediaBrowser.Providers.TV .ConfigureAwait(false); } - if (hasNewEpisodes || anySeasonsRemoved || anyEpisodesRemoved) + if (hasNewSeasons || hasNewEpisodes || anySeasonsRemoved || anyEpisodesRemoved) { await series.RefreshMetadata(cancellationToken, true) .ConfigureAwait(false); @@ -168,6 +174,40 @@ namespace MediaBrowser.Providers.TV } /// <summary> + /// For series with episodes directly under the series folder, this adds dummy seasons to enable regular browsing and metadata + /// </summary> + /// <param name="series"></param> + /// <param name="cancellationToken"></param> + /// <returns></returns> + private async Task<bool> AddDummySeasonFolders(Series series, CancellationToken cancellationToken) + { + var existingEpisodes = series.RecursiveChildren + .OfType<Episode>() + .ToList(); + + var hasChanges = false; + + // Loop through the unique season numbers + foreach (var seasonNumber in existingEpisodes.Select(i => i.ParentIndexNumber ?? -1) + .Where(i => i >= 0) + .Distinct() + .ToList()) + { + var hasSeason = series.Children.OfType<Season>() + .Any(i => i.IndexNumber.HasValue && i.IndexNumber.Value == seasonNumber); + + if (!hasSeason) + { + await AddSeason(series, seasonNumber, cancellationToken).ConfigureAwait(false); + + hasChanges = true; + } + } + + return hasChanges; + } + + /// <summary> /// Adds the missing episodes. /// </summary> /// <param name="series">The series.</param> @@ -355,7 +395,7 @@ namespace MediaBrowser.Providers.TV return hasChanges; } - + /// <summary> /// Adds the episode. /// </summary> diff --git a/MediaBrowser.Providers/TV/TvdbSeriesProvider.cs b/MediaBrowser.Providers/TV/TvdbSeriesProvider.cs index a22f4f1c3..29e191a59 100644 --- a/MediaBrowser.Providers/TV/TvdbSeriesProvider.cs +++ b/MediaBrowser.Providers/TV/TvdbSeriesProvider.cs @@ -1056,6 +1056,23 @@ namespace MediaBrowser.Providers.TV break; } + case "SortOrder": + { + var val = reader.ReadElementContentAsString(); + + if (!string.IsNullOrWhiteSpace(val)) + { + int rval; + + // int.TryParse is local aware, so it can be probamatic, force us culture + if (int.TryParse(val, NumberStyles.Integer, UsCulture, out rval)) + { + personInfo.SortOrder = rval; + } + } + break; + } + default: reader.Skip(); break; diff --git a/MediaBrowser.Providers/UserRootFolderNameProvider.cs b/MediaBrowser.Providers/UserRootFolderNameProvider.cs new file mode 100644 index 000000000..513020213 --- /dev/null +++ b/MediaBrowser.Providers/UserRootFolderNameProvider.cs @@ -0,0 +1,42 @@ +using MediaBrowser.Controller.Configuration; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Providers; +using MediaBrowser.Model.Logging; +using System; +using System.IO; +using System.Threading; +using System.Threading.Tasks; + +namespace MediaBrowser.Providers +{ + public class UserRootFolderNameProvider : BaseMetadataProvider + { + public UserRootFolderNameProvider(ILogManager logManager, IServerConfigurationManager configurationManager) + : base(logManager, configurationManager) + { + } + + public override bool Supports(BaseItem item) + { + return item is UserRootFolder; + } + + public override Task<bool> FetchAsync(BaseItem item, bool force, CancellationToken cancellationToken) + { + var parentName = Path.GetFileNameWithoutExtension(item.Path); + + if (string.Equals(parentName, "default", StringComparison.OrdinalIgnoreCase)) + { + item.Name = "Media Library"; + } + + SetLastRefreshed(item, DateTime.UtcNow); + return TrueTaskResult; + } + + public override MetadataProviderPriority Priority + { + get { return MetadataProviderPriority.First; } + } + } +} diff --git a/MediaBrowser.Server.Implementations/BdInfo/BdInfoExaminer.cs b/MediaBrowser.Server.Implementations/BdInfo/BdInfoExaminer.cs index 219b76cd5..06768f353 100644 --- a/MediaBrowser.Server.Implementations/BdInfo/BdInfoExaminer.cs +++ b/MediaBrowser.Server.Implementations/BdInfo/BdInfoExaminer.cs @@ -127,7 +127,6 @@ namespace MediaBrowser.Server.Implementations.BdInfo { var stream = new MediaStream { - BitRate = Convert.ToInt32(audioStream.BitRate), Codec = audioStream.CodecShortName, Language = audioStream.LanguageCode, Channels = audioStream.ChannelCount, @@ -136,6 +135,13 @@ namespace MediaBrowser.Server.Implementations.BdInfo Index = streams.Count }; + var bitrate = Convert.ToInt32(audioStream.BitRate); + + if (bitrate > 0) + { + stream.BitRate = bitrate; + } + if (audioStream.LFE > 0) { stream.Channels = audioStream.ChannelCount + 1; diff --git a/MediaBrowser.Server.Implementations/Dto/DtoService.cs b/MediaBrowser.Server.Implementations/Dto/DtoService.cs index 1f0e7d1e1..f298c2d4d 100644 --- a/MediaBrowser.Server.Implementations/Dto/DtoService.cs +++ b/MediaBrowser.Server.Implementations/Dto/DtoService.cs @@ -129,17 +129,13 @@ namespace MediaBrowser.Server.Implementations.Dto /// <param name="user">The user.</param> private void AttachItemByNameCounts(BaseItemDto dto, IItemByName item, User user) { - ItemByNameCounts counts; - if (user == null) { //counts = item.ItemCounts; return; } - if (!item.UserItemCounts.TryGetValue(user.Id, out counts)) - { - counts = new ItemByNameCounts(); - } + + ItemByNameCounts counts = item.GetItemByNameCounts(user.Id) ?? new ItemByNameCounts(); dto.ChildCount = counts.TotalCount; @@ -207,7 +203,7 @@ namespace MediaBrowser.Server.Implementations.Dto if (!string.IsNullOrEmpty(image)) { - dto.PrimaryImageTag = _imageProcessor.GetImageCacheTag(user, ImageType.Primary, image); + dto.PrimaryImageTag = GetImageCacheTag(user, ImageType.Primary, image); try { @@ -277,7 +273,7 @@ namespace MediaBrowser.Server.Implementations.Dto Id = GetDtoId(item), Name = item.Name, MediaType = item.MediaType, - Type = item.GetType().Name, + Type = item.GetClientTypeName(), RunTimeTicks = item.RunTimeTicks }; @@ -285,13 +281,7 @@ namespace MediaBrowser.Server.Implementations.Dto if (!string.IsNullOrEmpty(imagePath)) { - try - { - info.PrimaryImageTag = _imageProcessor.GetImageCacheTag(item, ImageType.Primary, imagePath); - } - catch (IOException) - { - } + info.PrimaryImageTag = GetImageCacheTag(item, ImageType.Primary, imagePath); } return info; @@ -433,7 +423,7 @@ namespace MediaBrowser.Server.Implementations.Dto // Ordering by person type to ensure actors and artists are at the front. // This is taking advantage of the fact that they both begin with A // This should be improved in the future - var people = item.People.OrderBy(i => i.Type).ToList(); + var people = item.People.OrderBy(i => i.SortOrder ?? int.MaxValue).ThenBy(i => i.Type).ToList(); // Attach People by transforming them into BaseItemPerson (DTO) dto.People = new BaseItemPerson[people.Count]; @@ -733,14 +723,18 @@ namespace MediaBrowser.Server.Implementations.Dto dto.EnableInternetProviders = !item.DontFetchMeta; } - if (fields.Contains(ItemFields.Budget)) + var hasBudget = item as IHasBudget; + if (hasBudget != null) { - dto.Budget = item.Budget; - } + if (fields.Contains(ItemFields.Budget)) + { + dto.Budget = hasBudget.Budget; + } - if (fields.Contains(ItemFields.Revenue)) - { - dto.Revenue = item.Revenue; + if (fields.Contains(ItemFields.Revenue)) + { + dto.Revenue = hasBudget.Revenue; + } } dto.EndDate = item.EndDate; @@ -760,7 +754,11 @@ namespace MediaBrowser.Server.Implementations.Dto dto.ProductionLocations = item.ProductionLocations; } - dto.AspectRatio = item.AspectRatio; + var hasAspectRatio = item as IHasAspectRatio; + if (hasAspectRatio != null) + { + dto.AspectRatio = hasAspectRatio.AspectRatio; + } dto.BackdropImageTags = GetBackdropImageTags(item); @@ -806,11 +804,17 @@ namespace MediaBrowser.Server.Implementations.Dto } } - var localTrailerCount = item.LocalTrailerIds.Count; + var hasTrailers = item as IHasTrailers; + if (hasTrailers != null) + { + dto.LocalTrailerCount = hasTrailers.LocalTrailerIds.Count; + } - if (localTrailerCount > 0) + if (fields.Contains(ItemFields.RemoteTrailers)) { - dto.LocalTrailerCount = localTrailerCount; + dto.RemoteTrailers = hasTrailers != null ? + hasTrailers.RemoteTrailers : + new List<MediaUrl>(); } dto.Name = item.Name; @@ -923,12 +927,7 @@ namespace MediaBrowser.Server.Implementations.Dto dto.Taglines = item.Taglines; } - if (fields.Contains(ItemFields.RemoteTrailers)) - { - dto.RemoteTrailers = item.RemoteTrailers; - } - - dto.Type = item.GetType().Name; + dto.Type = item.GetClientTypeName(); dto.CommunityRating = item.CommunityRating; dto.VoteCount = item.VoteCount; @@ -1030,6 +1029,12 @@ namespace MediaBrowser.Server.Implementations.Dto { dto.IndexNumberEnd = episode.IndexNumberEnd; dto.SpecialSeasonNumber = episode.AirsAfterSeasonNumber ?? episode.AirsBeforeSeasonNumber; + + var seasonId = episode.SeasonId; + if (seasonId.HasValue) + { + dto.SeasonId = seasonId.Value.ToString("N"); + } } // Add SeriesInfo diff --git a/MediaBrowser.Server.Implementations/HttpServer/HttpResultFactory.cs b/MediaBrowser.Server.Implementations/HttpServer/HttpResultFactory.cs index e6942fae6..5a5a2dd04 100644 --- a/MediaBrowser.Server.Implementations/HttpServer/HttpResultFactory.cs +++ b/MediaBrowser.Server.Implementations/HttpServer/HttpResultFactory.cs @@ -99,6 +99,14 @@ namespace MediaBrowser.Server.Implementations.HttpServer return result; } + private bool SupportsCompression + { + get + { + return true; + } + } + /// <summary> /// Gets the optimized result. /// </summary> @@ -116,7 +124,7 @@ namespace MediaBrowser.Server.Implementations.HttpServer throw new ArgumentNullException("result"); } - var optimizedResult = requestContext.ToOptimizedResult(result); + var optimizedResult = SupportsCompression ? requestContext.ToOptimizedResult(result) : result; if (responseHeaders != null) { @@ -458,6 +466,11 @@ namespace MediaBrowser.Server.Implementations.HttpServer } } + if (!SupportsCompression) + { + return new HttpResult(content, contentType); + } + var contents = content.Compress(requestContext.CompressionType); return new CompressedResult(contents, requestContext.CompressionType, contentType); diff --git a/MediaBrowser.Server.Implementations/HttpServer/SwaggerService.cs b/MediaBrowser.Server.Implementations/HttpServer/SwaggerService.cs index 20728a30c..904b6799b 100644 --- a/MediaBrowser.Server.Implementations/HttpServer/SwaggerService.cs +++ b/MediaBrowser.Server.Implementations/HttpServer/SwaggerService.cs @@ -1,6 +1,6 @@ -using MediaBrowser.Common.Net; +using MediaBrowser.Common.Configuration; +using MediaBrowser.Common.Net; using ServiceStack.ServiceHost; -using System.Diagnostics; using System.IO; namespace MediaBrowser.Server.Implementations.HttpServer @@ -20,6 +20,13 @@ namespace MediaBrowser.Server.Implementations.HttpServer public class SwaggerService : IHasResultFactory, IRestfulService { + private readonly IApplicationPaths _appPaths; + + public SwaggerService(IApplicationPaths appPaths) + { + _appPaths = appPaths; + } + /// <summary> /// Gets the specified request. /// </summary> @@ -27,7 +34,7 @@ namespace MediaBrowser.Server.Implementations.HttpServer /// <returns>System.Object.</returns> public object Get(GetSwaggerResource request) { - var runningDirectory = Path.GetDirectoryName(Process.GetCurrentProcess().MainModule.FileName); + var runningDirectory = Path.GetDirectoryName(_appPaths.ApplicationPath); var swaggerDirectory = Path.Combine(runningDirectory, "swagger-ui"); diff --git a/MediaBrowser.Server.Implementations/IO/DirectoryWatchers.cs b/MediaBrowser.Server.Implementations/IO/DirectoryWatchers.cs index ffb351222..870a14bd8 100644 --- a/MediaBrowser.Server.Implementations/IO/DirectoryWatchers.cs +++ b/MediaBrowser.Server.Implementations/IO/DirectoryWatchers.cs @@ -68,7 +68,8 @@ namespace MediaBrowser.Server.Implementations.IO { // This is an arbitraty amount of time, but delay it because file system writes often trigger events after RemoveTempIgnore has been called. // Seeing long delays in some situations, especially over the network. - await Task.Delay(40000).ConfigureAwait(false); + // Seeing delays up to 40 seconds, but not going to ignore changes for that long. + await Task.Delay(1500).ConfigureAwait(false); string val; _tempIgnoredPaths.TryRemove(path, out val); diff --git a/MediaBrowser.Server.Implementations/Library/LibraryManager.cs b/MediaBrowser.Server.Implementations/Library/LibraryManager.cs index 6a2df70b1..3b6a5ea25 100644 --- a/MediaBrowser.Server.Implementations/Library/LibraryManager.cs +++ b/MediaBrowser.Server.Implementations/Library/LibraryManager.cs @@ -391,10 +391,23 @@ namespace MediaBrowser.Server.Implementations.Library /// <param name="item">The item.</param> private void UpdateItemInLibraryCache(BaseItem item) { - if (!(item is IItemByName)) + if (item is IItemByName) { - LibraryItemsCache.AddOrUpdate(item.Id, item, delegate { return item; }); + var hasDualAccess = item as IHasDualAccess; + if (hasDualAccess != null) + { + if (hasDualAccess.IsAccessedByName) + { + return; + } + } + else + { + return; + } } + + LibraryItemsCache.AddOrUpdate(item.Id, item, delegate { return item; }); } /// <summary> @@ -657,16 +670,6 @@ namespace MediaBrowser.Server.Implementations.Library } /// <summary> - /// Gets a Genre - /// </summary> - /// <param name="name">The name.</param> - /// <returns>Task{Genre}.</returns> - public Artist GetArtist(string name) - { - return GetItemByName<Artist>(ConfigurationManager.ApplicationPaths.ArtistsPath, name); - } - - /// <summary> /// The us culture /// </summary> private static readonly CultureInfo UsCulture = new CultureInfo("en-US"); @@ -688,6 +691,16 @@ namespace MediaBrowser.Server.Implementations.Library } /// <summary> + /// Gets a Genre + /// </summary> + /// <param name="name">The name.</param> + /// <returns>Task{Genre}.</returns> + public MusicArtist GetArtist(string name) + { + return GetItemByName<MusicArtist>(ConfigurationManager.ApplicationPaths.ArtistsPath, name); + } + + /// <summary> /// The images by name item cache /// </summary> private readonly ConcurrentDictionary<string, BaseItem> _itemsByName = new ConcurrentDictionary<string, BaseItem>(StringComparer.OrdinalIgnoreCase); @@ -697,12 +710,12 @@ namespace MediaBrowser.Server.Implementations.Library { if (string.IsNullOrEmpty(path)) { - throw new ArgumentNullException(); + throw new ArgumentNullException("path"); } if (string.IsNullOrEmpty(name)) { - throw new ArgumentNullException(); + throw new ArgumentNullException("name"); } var validFilename = _fileSystem.GetValidFilename(name).Trim(); @@ -743,6 +756,20 @@ namespace MediaBrowser.Server.Implementations.Library private Tuple<bool, T> CreateItemByName<T>(string path, string name) where T : BaseItem, new() { + var isArtist = typeof(T) == typeof(MusicArtist); + + if (isArtist) + { + var existing = RootFolder.RecursiveChildren + .OfType<T>() + .FirstOrDefault(i => string.Equals(i.Name, name, StringComparison.OrdinalIgnoreCase)); + + if (existing != null) + { + return new Tuple<bool, T>(false, existing); + } + } + var fileInfo = new DirectoryInfo(path); var isNew = false; @@ -779,6 +806,11 @@ namespace MediaBrowser.Server.Implementations.Library isNew = true; } + if (isArtist) + { + (item as MusicArtist).IsAccessedByName = true; + } + // Set this now so we don't cause additional file system access during provider executions item.ResetResolveArgs(fileInfo); @@ -874,6 +906,20 @@ namespace MediaBrowser.Server.Implementations.Library /// <returns>Task.</returns> public async Task ValidateMediaLibraryInternal(IProgress<double> progress, CancellationToken cancellationToken) { + _directoryWatchersFactory().Stop(); + + try + { + await PerformLibraryValidation(progress, cancellationToken).ConfigureAwait(false); + } + finally + { + _directoryWatchersFactory().Start(); + } + } + + private async Task PerformLibraryValidation(IProgress<double> progress, CancellationToken cancellationToken) + { _logger.Info("Validating media library"); await RootFolder.RefreshMetadata(cancellationToken).ConfigureAwait(false); @@ -1361,16 +1407,7 @@ namespace MediaBrowser.Server.Implementations.Library /// <returns>BaseItem.</returns> public BaseItem RetrieveItem(Guid id) { - var item = ItemRepository.RetrieveItem(id); - - var folder = item as Folder; - - if (folder != null) - { - folder.LoadSavedChildren(); - } - - return item; + return ItemRepository.RetrieveItem(id); } private readonly ConcurrentDictionary<string, SemaphoreSlim> _fileLocks = new ConcurrentDictionary<string, SemaphoreSlim>(); @@ -1470,5 +1507,30 @@ namespace MediaBrowser.Server.Implementations.Library return collectionTypes.Count == 1 ? collectionTypes[0] : null; } + + + public IEnumerable<string> GetAllArtists() + { + return GetAllArtists(RootFolder.RecursiveChildren); + } + + public IEnumerable<string> GetAllArtists(IEnumerable<BaseItem> items) + { + return items + .OfType<Audio>() + .SelectMany(i => + { + var list = new List<string>(); + + if (!string.IsNullOrEmpty(i.AlbumArtist)) + { + list.Add(i.AlbumArtist); + } + list.AddRange(i.Artists); + + return list; + }) + .Distinct(StringComparer.OrdinalIgnoreCase); + } } } diff --git a/MediaBrowser.Server.Implementations/Library/LuceneSearchEngine.cs b/MediaBrowser.Server.Implementations/Library/LuceneSearchEngine.cs index d5fccc6fd..b194b2e94 100644 --- a/MediaBrowser.Server.Implementations/Library/LuceneSearchEngine.cs +++ b/MediaBrowser.Server.Implementations/Library/LuceneSearchEngine.cs @@ -74,21 +74,7 @@ namespace MediaBrowser.Server.Implementations.Library })); // Find artists - var artists = items.OfType<Audio>() - .SelectMany(i => - { - var list = new List<string>(); - - if (!string.IsNullOrEmpty(i.AlbumArtist)) - { - list.Add(i.AlbumArtist); - } - list.AddRange(i.Artists); - - return list; - }) - .Where(i => !string.IsNullOrEmpty(i)) - .Distinct(StringComparer.OrdinalIgnoreCase) + var artists = _libraryManager.GetAllArtists(items) .ToList(); foreach (var item in artists) diff --git a/MediaBrowser.Server.Implementations/Library/ResolverHelper.cs b/MediaBrowser.Server.Implementations/Library/ResolverHelper.cs index 620bcaee4..e32fcd627 100644 --- a/MediaBrowser.Server.Implementations/Library/ResolverHelper.cs +++ b/MediaBrowser.Server.Implementations/Library/ResolverHelper.cs @@ -1,11 +1,11 @@ using MediaBrowser.Common.Extensions; using MediaBrowser.Common.IO; using MediaBrowser.Controller.Entities; -using MediaBrowser.Controller.IO; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Resolvers; using System; using System.IO; +using System.Linq; using System.Text.RegularExpressions; namespace MediaBrowser.Server.Implementations.Library @@ -48,7 +48,8 @@ namespace MediaBrowser.Server.Implementations.Library // Make sure the item has a name EnsureName(item); - item.DontFetchMeta = item.Path.IndexOf("[dontfetchmeta]", StringComparison.OrdinalIgnoreCase) != -1; + item.DontFetchMeta = item.Path.IndexOf("[dontfetchmeta]", StringComparison.OrdinalIgnoreCase) != -1 || + item.Parents.Any(i => i.DontFetchMeta); // Make sure DateCreated and DateModified have values EntityResolutionHelper.EnsureDates(fileSystem, item, args, true); diff --git a/MediaBrowser.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs b/MediaBrowser.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs index 0f87b9d33..03e29dd38 100644 --- a/MediaBrowser.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs +++ b/MediaBrowser.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs @@ -172,7 +172,7 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers.Movies private void SetProviderIdFromPath(Video item) { //we need to only look at the name of this actual item (not parents) - var justName = Path.GetFileName(item.Path); + var justName = item.IsInMixedFolder ? Path.GetFileName(item.Path) : Path.GetFileName(Path.GetDirectoryName(item.Path)); var id = justName.GetAttributeValue("tmdbid"); @@ -345,26 +345,17 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers.Movies /// <returns><c>true</c> if [is DVD directory] [the specified directory name]; otherwise, <c>false</c>.</returns> private bool IsDvdDirectory(string directoryName) { - return directoryName.Equals("video_ts", StringComparison.OrdinalIgnoreCase); + return string.Equals(directoryName, "video_ts", StringComparison.OrdinalIgnoreCase); } /// <summary> - /// Determines whether [is hd DVD directory] [the specified directory name]. - /// </summary> - /// <param name="directoryName">Name of the directory.</param> - /// <returns><c>true</c> if [is hd DVD directory] [the specified directory name]; otherwise, <c>false</c>.</returns> - private bool IsHdDvdDirectory(string directoryName) - { - return directoryName.Equals("hvdvd_ts", StringComparison.OrdinalIgnoreCase); - } - /// <summary> /// Determines whether [is blu ray directory] [the specified directory name]. /// </summary> /// <param name="directoryName">Name of the directory.</param> /// <returns><c>true</c> if [is blu ray directory] [the specified directory name]; otherwise, <c>false</c>.</returns> private bool IsBluRayDirectory(string directoryName) { - return directoryName.Equals("bdmv", StringComparison.OrdinalIgnoreCase); + return string.Equals(directoryName, "bdmv", StringComparison.OrdinalIgnoreCase); } } } diff --git a/MediaBrowser.Server.Implementations/Library/Resolvers/TV/EpisodeResolver.cs b/MediaBrowser.Server.Implementations/Library/Resolvers/TV/EpisodeResolver.cs index 0a6a72fc1..693594a20 100644 --- a/MediaBrowser.Server.Implementations/Library/Resolvers/TV/EpisodeResolver.cs +++ b/MediaBrowser.Server.Implementations/Library/Resolvers/TV/EpisodeResolver.cs @@ -20,7 +20,7 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers.TV var season = args.Parent as Season; // If the parent is a Season or Series, then this is an Episode if the VideoResolver returns something - if (season != null) + if (season != null || args.Parent is Series) { Episode episode = null; @@ -51,8 +51,11 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers.TV if (episode != null) { - episode.ParentIndexNumber = season.IndexNumber; - + if (season != null) + { + episode.ParentIndexNumber = season.IndexNumber; + } + if (episode.ParentIndexNumber == null) { episode.ParentIndexNumber = TVUtils.GetSeasonNumberFromEpisodeFile(args.Path); diff --git a/MediaBrowser.Server.Implementations/Library/Validators/ArtistsValidator.cs b/MediaBrowser.Server.Implementations/Library/Validators/ArtistsValidator.cs index 7d46d7060..40ef5304c 100644 --- a/MediaBrowser.Server.Implementations/Library/Validators/ArtistsValidator.cs +++ b/MediaBrowser.Server.Implementations/Library/Validators/ArtistsValidator.cs @@ -57,7 +57,6 @@ namespace MediaBrowser.Server.Implementations.Library.Validators { var allItems = _libraryManager.RootFolder.GetRecursiveChildren(); - var allMusicArtists = allItems.OfType<MusicArtist>().ToList(); var allSongs = allItems.OfType<Audio>().ToList(); var innerProgress = new ActionableProgress<double>(); @@ -80,36 +79,8 @@ namespace MediaBrowser.Server.Implementations.Library.Validators { cancellationToken.ThrowIfCancellationRequested(); - artist.ValidateImages(); - artist.ValidateBackdrops(); - - var musicArtist = Artist.FindMusicArtist(artist, allMusicArtists); - - if (musicArtist != null) - { - MergeImages(musicArtist.Images, artist.Images); - - // Merge backdrops - var additionalBackdrops = musicArtist - .BackdropImagePaths - .Except(artist.BackdropImagePaths) - .ToList(); - - var sources = additionalBackdrops - .Select(musicArtist.GetImageSourceInfo) - .Where(i => i != null) - .ToList(); - - foreach (var path in additionalBackdrops) - { - artist.RemoveImageSourceForPath(path); - } - - artist.BackdropImagePaths.AddRange(additionalBackdrops); - artist.ImageSources.AddRange(sources); - } - - if (!artist.LockedFields.Contains(MetadataFields.Genres)) + // Only do this for artists accessed by name. Folder-based artists use ArtistInfoFromSongsProvider + if (artist.IsAccessedByName && !artist.LockedFields.Contains(MetadataFields.Genres)) { // Avoid implicitly captured closure var artist1 = artist; @@ -145,7 +116,7 @@ namespace MediaBrowser.Server.Implementations.Library.Validators /// <param name="artist">The artist.</param> /// <param name="userId">The user id.</param> /// <param name="allItems">All items.</param> - private void SetItemCounts(Artist artist, Guid? userId, IEnumerable<IHasArtist> allItems) + private void SetItemCounts(MusicArtist artist, Guid? userId, IEnumerable<IHasArtist> allItems) { var name = artist.Name; @@ -166,26 +137,7 @@ namespace MediaBrowser.Server.Implementations.Library.Validators if (userId.HasValue) { - artist.UserItemCounts[userId.Value] = counts; - } - } - - /// <summary> - /// Merges the images. - /// </summary> - /// <param name="source">The source.</param> - /// <param name="target">The target.</param> - private void MergeImages(Dictionary<ImageType, string> source, Dictionary<ImageType, string> target) - { - foreach (var key in source.Keys - .Where(k => !target.ContainsKey(k))) - { - string path; - - if (source.TryGetValue(key, out path)) - { - target[key] = path; - } + artist.SetItemByNameCounts(userId.Value, counts); } } @@ -196,25 +148,12 @@ namespace MediaBrowser.Server.Implementations.Library.Validators /// <param name="cancellationToken">The cancellation token.</param> /// <param name="progress">The progress.</param> /// <returns>Task{Artist[]}.</returns> - private async Task<List<Artist>> GetAllArtists(IEnumerable<Audio> allSongs, CancellationToken cancellationToken, IProgress<double> progress) + private async Task<List<MusicArtist>> GetAllArtists(IEnumerable<Audio> allSongs, CancellationToken cancellationToken, IProgress<double> progress) { - var allArtists = allSongs - .SelectMany(i => - { - var list = new List<string>(); - - if (!string.IsNullOrEmpty(i.AlbumArtist)) - { - list.Add(i.AlbumArtist); - } - list.AddRange(i.Artists); - - return list; - }) - .Distinct(StringComparer.OrdinalIgnoreCase) + var allArtists = _libraryManager.GetAllArtists(allSongs) .ToList(); - var returnArtists = new List<Artist>(allArtists.Count); + var returnArtists = new List<MusicArtist>(allArtists.Count); var numComplete = 0; var numArtists = allArtists.Count; diff --git a/MediaBrowser.Server.Implementations/Library/Validators/GameGenresValidator.cs b/MediaBrowser.Server.Implementations/Library/Validators/GameGenresValidator.cs index d21a123c0..c7af7a238 100644 --- a/MediaBrowser.Server.Implementations/Library/Validators/GameGenresValidator.cs +++ b/MediaBrowser.Server.Implementations/Library/Validators/GameGenresValidator.cs @@ -106,7 +106,7 @@ namespace MediaBrowser.Server.Implementations.Library.Validators { var itemCounts = CountHelpers.GetCounts(counts[libraryId]); - itemByName.UserItemCounts[libraryId] = itemCounts; + itemByName.SetItemByNameCounts(libraryId, itemCounts); } await itemByName.RefreshMetadata(cancellationToken).ConfigureAwait(false); diff --git a/MediaBrowser.Server.Implementations/Library/Validators/GenresValidator.cs b/MediaBrowser.Server.Implementations/Library/Validators/GenresValidator.cs index 0670e1a85..cb1253df0 100644 --- a/MediaBrowser.Server.Implementations/Library/Validators/GenresValidator.cs +++ b/MediaBrowser.Server.Implementations/Library/Validators/GenresValidator.cs @@ -107,7 +107,7 @@ namespace MediaBrowser.Server.Implementations.Library.Validators { var itemCounts = CountHelpers.GetCounts(counts[libraryId]); - itemByName.UserItemCounts[libraryId] = itemCounts; + itemByName.SetItemByNameCounts(libraryId, itemCounts); } await itemByName.RefreshMetadata(cancellationToken).ConfigureAwait(false); diff --git a/MediaBrowser.Server.Implementations/Library/Validators/MusicGenresValidator.cs b/MediaBrowser.Server.Implementations/Library/Validators/MusicGenresValidator.cs index 166f557cf..57a6a612b 100644 --- a/MediaBrowser.Server.Implementations/Library/Validators/MusicGenresValidator.cs +++ b/MediaBrowser.Server.Implementations/Library/Validators/MusicGenresValidator.cs @@ -107,7 +107,7 @@ namespace MediaBrowser.Server.Implementations.Library.Validators { var itemCounts = CountHelpers.GetCounts(counts[libraryId]); - itemByName.UserItemCounts[libraryId] = itemCounts; + itemByName.SetItemByNameCounts(libraryId, itemCounts); } await itemByName.RefreshMetadata(cancellationToken).ConfigureAwait(false); diff --git a/MediaBrowser.Server.Implementations/Library/Validators/PeoplePostScanTask.cs b/MediaBrowser.Server.Implementations/Library/Validators/PeoplePostScanTask.cs index cfc7f4310..0104b2b7e 100644 --- a/MediaBrowser.Server.Implementations/Library/Validators/PeoplePostScanTask.cs +++ b/MediaBrowser.Server.Implementations/Library/Validators/PeoplePostScanTask.cs @@ -94,7 +94,7 @@ namespace MediaBrowser.Server.Implementations.Library.Validators { var itemCounts = CountHelpers.GetCounts(counts[libraryId]); - itemByName.UserItemCounts[libraryId] = itemCounts; + itemByName.SetItemByNameCounts(libraryId, itemCounts); } } catch (Exception ex) diff --git a/MediaBrowser.Server.Implementations/Library/Validators/StudiosValidator.cs b/MediaBrowser.Server.Implementations/Library/Validators/StudiosValidator.cs index 02c7a94b4..0f4ff562e 100644 --- a/MediaBrowser.Server.Implementations/Library/Validators/StudiosValidator.cs +++ b/MediaBrowser.Server.Implementations/Library/Validators/StudiosValidator.cs @@ -106,7 +106,7 @@ namespace MediaBrowser.Server.Implementations.Library.Validators { var itemCounts = CountHelpers.GetCounts(counts[libraryId]); - itemByName.UserItemCounts[libraryId] = itemCounts; + itemByName.SetItemByNameCounts(libraryId, itemCounts); } await itemByName.RefreshMetadata(cancellationToken).ConfigureAwait(false); diff --git a/MediaBrowser.Server.Implementations/LiveTv/ChannelImageProvider.cs b/MediaBrowser.Server.Implementations/LiveTv/ChannelImageProvider.cs new file mode 100644 index 000000000..322948bad --- /dev/null +++ b/MediaBrowser.Server.Implementations/LiveTv/ChannelImageProvider.cs @@ -0,0 +1,95 @@ +using MediaBrowser.Controller.Configuration; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.LiveTv; +using MediaBrowser.Controller.Providers; +using MediaBrowser.Model.Entities; +using MediaBrowser.Model.Logging; +using MediaBrowser.Model.Net; +using System; +using System.Linq; +using System.Net; +using System.Threading; +using System.Threading.Tasks; + +namespace MediaBrowser.Server.Implementations.LiveTv +{ + public class ChannelImageProvider : BaseMetadataProvider + { + private readonly ILiveTvManager _liveTvManager; + private readonly IProviderManager _providerManager; + + public ChannelImageProvider(ILogManager logManager, IServerConfigurationManager configurationManager, ILiveTvManager liveTvManager, IProviderManager providerManager) + : base(logManager, configurationManager) + { + _liveTvManager = liveTvManager; + _providerManager = providerManager; + } + + public override bool Supports(BaseItem item) + { + return item is Channel; + } + + protected override bool NeedsRefreshInternal(BaseItem item, BaseProviderInfo providerInfo) + { + return !item.HasImage(ImageType.Primary); + } + + public override async Task<bool> FetchAsync(BaseItem item, bool force, CancellationToken cancellationToken) + { + if (item.HasImage(ImageType.Primary)) + { + SetLastRefreshed(item, DateTime.UtcNow); + return true; + } + + try + { + await DownloadImage(item, cancellationToken).ConfigureAwait(false); + } + catch (HttpException ex) + { + // Don't fail the provider on a 404 + if (!ex.StatusCode.HasValue || ex.StatusCode.Value != HttpStatusCode.NotFound) + { + throw; + } + } + + + SetLastRefreshed(item, DateTime.UtcNow); + return true; + } + + private async Task DownloadImage(BaseItem item, CancellationToken cancellationToken) + { + var channel = (Channel)item; + + var service = _liveTvManager.Services.FirstOrDefault(i => string.Equals(i.Name, channel.ServiceName, StringComparison.OrdinalIgnoreCase)); + + if (service != null) + { + var response = await service.GetChannelImageAsync(channel.ChannelId, cancellationToken).ConfigureAwait(false); + + // Dummy up the original url + var url = channel.ServiceName + channel.ChannelId; + + await _providerManager.SaveImage(channel, response.Stream, response.MimeType, ImageType.Primary, null, url, cancellationToken).ConfigureAwait(false); + } + } + + public override MetadataProviderPriority Priority + { + get { return MetadataProviderPriority.Second; } + } + + public override ItemUpdateType ItemUpdateType + { + get + { + return ItemUpdateType.ImageUpdate; + } + } + } +} diff --git a/MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs b/MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs index 05bac17c3..4fd1f0e43 100644 --- a/MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs +++ b/MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs @@ -1,6 +1,23 @@ -using MediaBrowser.Controller.LiveTv; +using MediaBrowser.Common.Extensions; +using MediaBrowser.Common.IO; +using MediaBrowser.Controller; +using MediaBrowser.Controller.Drawing; +using MediaBrowser.Controller.Dto; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.LiveTv; +using MediaBrowser.Controller.Localization; +using MediaBrowser.Controller.Persistence; +using MediaBrowser.Model.Entities; using MediaBrowser.Model.LiveTv; +using MediaBrowser.Model.Logging; +using MediaBrowser.Model.Querying; +using System; using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; namespace MediaBrowser.Server.Implementations.LiveTv { @@ -9,7 +26,35 @@ namespace MediaBrowser.Server.Implementations.LiveTv /// </summary> public class LiveTvManager : ILiveTvManager { + private readonly IServerApplicationPaths _appPaths; + private readonly IFileSystem _fileSystem; + private readonly ILogger _logger; + private readonly IItemRepository _itemRepo; + private readonly IImageProcessor _imageProcessor; + + private readonly IUserManager _userManager; + private readonly ILocalizationManager _localization; + private readonly IUserDataManager _userDataManager; + private readonly IDtoService _dtoService; + private readonly List<ILiveTvService> _services = new List<ILiveTvService>(); + + private List<Channel> _channels = new List<Channel>(); + private List<ProgramInfoDto> _programs = new List<ProgramInfoDto>(); + + public LiveTvManager(IServerApplicationPaths appPaths, IFileSystem fileSystem, ILogger logger, IItemRepository itemRepo, IImageProcessor imageProcessor, IUserManager userManager, ILocalizationManager localization, IUserDataManager userDataManager, IDtoService dtoService) + { + _appPaths = appPaths; + _fileSystem = fileSystem; + _logger = logger; + _itemRepo = itemRepo; + _imageProcessor = imageProcessor; + _userManager = userManager; + _localization = localization; + _userDataManager = userDataManager; + _dtoService = dtoService; + } + /// <summary> /// Gets the services. /// </summary> @@ -32,17 +77,515 @@ namespace MediaBrowser.Server.Implementations.LiveTv /// Gets the channel info dto. /// </summary> /// <param name="info">The info.</param> + /// <param name="user">The user.</param> /// <returns>ChannelInfoDto.</returns> - public ChannelInfoDto GetChannelInfoDto(ChannelInfo info) + public ChannelInfoDto GetChannelInfoDto(Channel info, User user) { - return new ChannelInfoDto + var dto = new ChannelInfoDto { Name = info.Name, ServiceName = info.ServiceName, ChannelType = info.ChannelType, - Id = info.Id, - Number = info.Number + Number = info.ChannelNumber, + Type = info.GetType().Name, + Id = info.Id.ToString("N"), + MediaType = info.MediaType + }; + + if (user != null) + { + dto.UserData = _dtoService.GetUserItemDataDto(_userDataManager.GetUserData(user.Id, info.GetUserDataKey())); + } + + var imageTag = GetLogoImageTag(info); + + if (imageTag.HasValue) + { + dto.ImageTags[ImageType.Primary] = imageTag.Value; + } + + return dto; + } + + private Guid? GetLogoImageTag(Channel info) + { + var path = info.PrimaryImagePath; + + if (string.IsNullOrEmpty(path)) + { + return null; + } + + try + { + return _imageProcessor.GetImageCacheTag(info, ImageType.Primary, path); + } + catch (Exception ex) + { + _logger.ErrorException("Error getting channel image info for {0}", ex, info.Name); + } + + return null; + } + + public QueryResult<ChannelInfoDto> GetChannels(ChannelQuery query) + { + var user = string.IsNullOrEmpty(query.UserId) ? null : _userManager.GetUserById(new Guid(query.UserId)); + + IEnumerable<Channel> channels = _channels; + + if (user != null) + { + channels = channels.Where(i => i.IsParentalAllowed(user, _localization)) + .OrderBy(i => + { + double number = 0; + + if (!string.IsNullOrEmpty(i.ChannelNumber)) + { + double.TryParse(i.ChannelNumber, out number); + } + + return number; + + }); + } + + var returnChannels = channels.OrderBy(i => + { + double number = 0; + + if (!string.IsNullOrEmpty(i.ChannelNumber)) + { + double.TryParse(i.ChannelNumber, out number); + } + + return number; + + }).ThenBy(i => i.Name) + .Select(i => GetChannelInfoDto(i, user)) + .ToArray(); + + return new QueryResult<ChannelInfoDto> + { + Items = returnChannels, + TotalRecordCount = returnChannels.Length + }; + } + + public Channel GetChannel(string id) + { + var guid = new Guid(id); + + return _channels.FirstOrDefault(i => i.Id == guid); + } + + public ChannelInfoDto GetChannelInfoDto(string id, string userId) + { + var channel = GetChannel(id); + + var user = string.IsNullOrEmpty(userId) ? null : _userManager.GetUserById(new Guid(userId)); + + return channel == null ? null : GetChannelInfoDto(channel, user); + } + + private ProgramInfoDto GetProgramInfoDto(ProgramInfo program, Channel channel) + { + var id = GetInternalProgramIdId(channel.ServiceName, program.Id).ToString("N"); + + return new ProgramInfoDto + { + ChannelId = channel.Id.ToString("N"), + Description = program.Description, + EndDate = program.EndDate, + Genres = program.Genres, + ExternalId = program.Id, + Id = id, + Name = program.Name, + ServiceName = channel.ServiceName, + StartDate = program.StartDate, + OfficialRating = program.OfficialRating, + Quality = program.Quality, + OriginalAirDate = program.OriginalAirDate, + Audio = program.Audio, + CommunityRating = program.CommunityRating, + AspectRatio = program.AspectRatio, + IsRepeat = program.IsRepeat, + EpisodeTitle = program.EpisodeTitle + }; + } + + private Guid GetInternalChannelId(string serviceName, string externalChannelId, string channelName) + { + var name = serviceName + externalChannelId + channelName; + + return name.ToLower().GetMBId(typeof(Channel)); + } + + private Guid GetInternalProgramIdId(string serviceName, string externalProgramId) + { + var name = serviceName + externalProgramId; + + return name.ToLower().GetMD5(); + } + + private async Task<Channel> GetChannel(ChannelInfo channelInfo, string serviceName, CancellationToken cancellationToken) + { + var path = Path.Combine(_appPaths.ItemsByNamePath, "channels", _fileSystem.GetValidFilename(serviceName), _fileSystem.GetValidFilename(channelInfo.Name)); + + var fileInfo = new DirectoryInfo(path); + + var isNew = false; + + if (!fileInfo.Exists) + { + Directory.CreateDirectory(path); + fileInfo = new DirectoryInfo(path); + + if (!fileInfo.Exists) + { + throw new IOException("Path not created: " + path); + } + + isNew = true; + } + + var id = GetInternalChannelId(serviceName, channelInfo.Id, channelInfo.Name); + + var item = _itemRepo.RetrieveItem(id) as Channel; + + if (item == null) + { + item = new Channel + { + Name = channelInfo.Name, + Id = id, + DateCreated = _fileSystem.GetCreationTimeUtc(fileInfo), + DateModified = _fileSystem.GetLastWriteTimeUtc(fileInfo), + Path = path, + ChannelId = channelInfo.Id, + ChannelNumber = channelInfo.Number, + ServiceName = serviceName + }; + + isNew = true; + } + + // Set this now so we don't cause additional file system access during provider executions + item.ResetResolveArgs(fileInfo); + + await item.RefreshMetadata(cancellationToken, forceSave: isNew, resetResolveArgs: false); + + return item; + } + + public async Task<QueryResult<ProgramInfoDto>> GetPrograms(ProgramQuery query, CancellationToken cancellationToken) + { + IEnumerable<ProgramInfoDto> programs = _programs + .OrderBy(i => i.StartDate) + .ThenBy(i => i.EndDate); + + if (!string.IsNullOrEmpty(query.ServiceName)) + { + programs = programs.Where(i => string.Equals(i.ServiceName, query.ServiceName, StringComparison.OrdinalIgnoreCase)); + } + + if (query.ChannelIdList.Length > 0) + { + var guids = query.ChannelIdList.Select(i => new Guid(i)).ToList(); + + programs = programs.Where(i => guids.Contains(new Guid(i.ChannelId))); + } + + var returnArray = programs.ToArray(); + + return new QueryResult<ProgramInfoDto> + { + Items = returnArray, + TotalRecordCount = returnArray.Length + }; + } + + internal async Task RefreshChannels(IProgress<double> progress, CancellationToken cancellationToken) + { + // Avoid implicitly captured closure + var currentCancellationToken = cancellationToken; + + var channelTasks = _services.Select(i => GetChannels(i, currentCancellationToken)); + + progress.Report(10); + + var results = await Task.WhenAll(channelTasks).ConfigureAwait(false); + + var allChannels = results.SelectMany(i => i).ToList(); + + var list = new List<Channel>(); + var programs = new List<ProgramInfoDto>(); + + var numComplete = 0; + + foreach (var channelInfo in allChannels) + { + try + { + var item = await GetChannel(channelInfo.Item2, channelInfo.Item1, cancellationToken).ConfigureAwait(false); + + var service = _services.First(i => string.Equals(channelInfo.Item1, i.Name, StringComparison.OrdinalIgnoreCase)); + + var channelPrograms = await service.GetProgramsAsync(channelInfo.Item2.Id, cancellationToken).ConfigureAwait(false); + + programs.AddRange(channelPrograms.Select(program => GetProgramInfoDto(program, item))); + + list.Add(item); + } + catch (OperationCanceledException) + { + throw; + } + catch (Exception ex) + { + _logger.ErrorException("Error getting channel information for {0}", ex, channelInfo.Item2.Name); + } + + numComplete++; + double percent = numComplete; + percent /= allChannels.Count; + + progress.Report(90 * percent + 10); + } + + _programs = programs; + _channels = list; + } + + private async Task<IEnumerable<Tuple<string, ChannelInfo>>> GetChannels(ILiveTvService service, CancellationToken cancellationToken) + { + var channels = await service.GetChannelsAsync(cancellationToken).ConfigureAwait(false); + + return channels.Select(i => new Tuple<string, ChannelInfo>(service.Name, i)); + } + + private async Task<IEnumerable<RecordingInfoDto>> GetRecordings(ILiveTvService service, CancellationToken cancellationToken) + { + var recordings = await service.GetRecordingsAsync(cancellationToken).ConfigureAwait(false); + + return recordings.Select(i => GetRecordingInfoDto(i, service)); + } + + private RecordingInfoDto GetRecordingInfoDto(RecordingInfo info, ILiveTvService service) + { + var id = service.Name + info.ChannelId + info.Id; + id = id.GetMD5().ToString("N"); + + var dto = new RecordingInfoDto + { + ChannelName = info.ChannelName, + Description = info.Description, + EndDate = info.EndDate, + Name = info.Name, + StartDate = info.StartDate, + Id = id, + ExternalId = info.Id, + ChannelId = GetInternalChannelId(service.Name, info.ChannelId, info.ChannelName).ToString("N"), + Status = info.Status, + Path = info.Path, + Genres = info.Genres, + IsRepeat = info.IsRepeat, + EpisodeTitle = info.EpisodeTitle, + ChannelType = info.ChannelType, + MediaType = info.ChannelType == ChannelType.Radio ? MediaType.Audio : MediaType.Video, + CommunityRating = info.CommunityRating, + OfficialRating = info.OfficialRating }; + + var duration = info.EndDate - info.StartDate; + dto.DurationMs = Convert.ToInt32(duration.TotalMilliseconds); + + if (!string.IsNullOrEmpty(info.ProgramId)) + { + dto.ProgramId = GetInternalProgramIdId(service.Name, info.ProgramId).ToString("N"); + } + + return dto; + } + + public async Task<QueryResult<RecordingInfoDto>> GetRecordings(RecordingQuery query, CancellationToken cancellationToken) + { + var list = new List<RecordingInfoDto>(); + + foreach (var service in GetServices(query.ServiceName, query.ChannelId)) + { + var recordings = await GetRecordings(service, cancellationToken).ConfigureAwait(false); + + list.AddRange(recordings); + } + + if (!string.IsNullOrEmpty(query.ChannelId)) + { + list = list.Where(i => string.Equals(i.ChannelId, query.ChannelId)) + .ToList(); + } + + var returnArray = list.OrderByDescending(i => i.StartDate) + .ToArray(); + + return new QueryResult<RecordingInfoDto> + { + Items = returnArray, + TotalRecordCount = returnArray.Length + }; + } + + private IEnumerable<ILiveTvService> GetServices(string serviceName, string channelId) + { + IEnumerable<ILiveTvService> services = _services; + + if (string.IsNullOrEmpty(serviceName) && !string.IsNullOrEmpty(channelId)) + { + var channelIdGuid = new Guid(channelId); + + serviceName = _channels.Where(i => i.Id == channelIdGuid) + .Select(i => i.ServiceName) + .FirstOrDefault(); + } + + if (!string.IsNullOrEmpty(serviceName)) + { + services = services.Where(i => string.Equals(i.Name, serviceName, StringComparison.OrdinalIgnoreCase)); + } + + return services; + } + + public Task ScheduleRecording(string programId) + { + throw new NotImplementedException(); + } + + public async Task<QueryResult<TimerInfoDto>> GetTimers(TimerQuery query, CancellationToken cancellationToken) + { + var list = new List<TimerInfoDto>(); + + foreach (var service in GetServices(query.ServiceName, query.ChannelId)) + { + var timers = await GetTimers(service, cancellationToken).ConfigureAwait(false); + + list.AddRange(timers); + } + + if (!string.IsNullOrEmpty(query.ChannelId)) + { + list = list.Where(i => string.Equals(i.ChannelId, query.ChannelId)) + .ToList(); + } + + var returnArray = list.OrderByDescending(i => i.StartDate) + .ToArray(); + + return new QueryResult<TimerInfoDto> + { + Items = returnArray, + TotalRecordCount = returnArray.Length + }; + } + + private async Task<IEnumerable<TimerInfoDto>> GetTimers(ILiveTvService service, CancellationToken cancellationToken) + { + var timers = await service.GetTimersAsync(cancellationToken).ConfigureAwait(false); + + return timers.Select(i => GetTimerInfoDto(i, service)); + } + + private TimerInfoDto GetTimerInfoDto(TimerInfo info, ILiveTvService service) + { + var id = service.Name + info.ChannelId + info.Id; + id = id.GetMD5().ToString("N"); + + var dto = new TimerInfoDto + { + ChannelName = info.ChannelName, + Description = info.Description, + EndDate = info.EndDate, + Name = info.Name, + StartDate = info.StartDate, + Id = id, + ExternalId = info.Id, + ChannelId = GetInternalChannelId(service.Name, info.ChannelId, info.ChannelName).ToString("N"), + Status = info.Status, + SeriesTimerId = info.SeriesTimerId, + PrePaddingSeconds = info.PrePaddingSeconds, + PostPaddingSeconds = info.PostPaddingSeconds + }; + + var duration = info.EndDate - info.StartDate; + dto.DurationMs = Convert.ToInt32(duration.TotalMilliseconds); + + if (!string.IsNullOrEmpty(info.ProgramId)) + { + dto.ProgramId = GetInternalProgramIdId(service.Name, info.ProgramId).ToString("N"); + } + + return dto; + } + + public async Task DeleteRecording(string recordingId) + { + var recordings = await GetRecordings(new RecordingQuery + { + + }, CancellationToken.None).ConfigureAwait(false); + + var recording = recordings.Items + .FirstOrDefault(i => string.Equals(recordingId, i.Id, StringComparison.OrdinalIgnoreCase)); + + if (recording == null) + { + throw new ResourceNotFoundException(string.Format("Recording with Id {0} not found", recordingId)); + } + + var channel = GetChannel(recording.ChannelId); + + var service = GetServices(channel.ServiceName, null) + .First(); + + await service.DeleteRecordingAsync(recording.ExternalId, CancellationToken.None).ConfigureAwait(false); + } + + public async Task CancelTimer(string id) + { + var timers = await GetTimers(new TimerQuery + { + + }, CancellationToken.None).ConfigureAwait(false); + + var timer = timers.Items + .FirstOrDefault(i => string.Equals(id, i.Id, StringComparison.OrdinalIgnoreCase)); + + if (timer == null) + { + throw new ResourceNotFoundException(string.Format("Timer with Id {0} not found", id)); + } + + var channel = GetChannel(timer.ChannelId); + + var service = GetServices(channel.ServiceName, null) + .First(); + + await service.CancelTimerAsync(timer.ExternalId, CancellationToken.None).ConfigureAwait(false); + } + + public async Task<RecordingInfoDto> GetRecording(string id, CancellationToken cancellationToken) + { + var results = await GetRecordings(new RecordingQuery(), cancellationToken).ConfigureAwait(false); + + return results.Items.FirstOrDefault(i => string.Equals(i.Id, id, StringComparison.CurrentCulture)); + } + + public async Task<TimerInfoDto> GetTimer(string id, CancellationToken cancellationToken) + { + var results = await GetTimers(new TimerQuery(), cancellationToken).ConfigureAwait(false); + + return results.Items.FirstOrDefault(i => string.Equals(i.Id, id, StringComparison.CurrentCulture)); } } } diff --git a/MediaBrowser.Server.Implementations/LiveTv/RefreshChannelsScheduledTask.cs b/MediaBrowser.Server.Implementations/LiveTv/RefreshChannelsScheduledTask.cs new file mode 100644 index 000000000..00bf9e55b --- /dev/null +++ b/MediaBrowser.Server.Implementations/LiveTv/RefreshChannelsScheduledTask.cs @@ -0,0 +1,59 @@ +using MediaBrowser.Common.ScheduledTasks; +using MediaBrowser.Controller.LiveTv; +using MediaBrowser.Model.Tasks; +using System; +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace MediaBrowser.Server.Implementations.LiveTv +{ + class RefreshChannelsScheduledTask : IScheduledTask, IConfigurableScheduledTask + { + private readonly ILiveTvManager _liveTvManager; + + public RefreshChannelsScheduledTask(ILiveTvManager liveTvManager) + { + _liveTvManager = liveTvManager; + } + + public string Name + { + get { return "Refresh Guide"; } + } + + public string Description + { + get { return "Downloads channel information from live tv services."; } + } + + public string Category + { + get { return "Live TV"; } + } + + public Task Execute(System.Threading.CancellationToken cancellationToken, IProgress<double> progress) + { + var manager = (LiveTvManager)_liveTvManager; + + return manager.RefreshChannels(progress, cancellationToken); + } + + public IEnumerable<ITaskTrigger> GetDefaultTriggers() + { + return new ITaskTrigger[] + { + + new StartupTrigger(), + + new SystemEventTrigger{ SystemEvent = SystemEvent.WakeFromSleep}, + + new IntervalTrigger{ Interval = TimeSpan.FromHours(2)} + }; + } + + public bool IsHidden + { + get { return _liveTvManager.Services.Count == 0; } + } + } +} diff --git a/MediaBrowser.Server.Implementations/Localization/Ratings/ca.txt b/MediaBrowser.Server.Implementations/Localization/Ratings/ca.txt new file mode 100644 index 000000000..5a110648c --- /dev/null +++ b/MediaBrowser.Server.Implementations/Localization/Ratings/ca.txt @@ -0,0 +1,6 @@ +CA-G,1 +CA-PG,5 +CA-14A,7 +CA-A,8 +CA-18A,9 +CA-R,10
\ No newline at end of file diff --git a/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj b/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj index ac451e1eb..3bfbdea3e 100644 --- a/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj +++ b/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj @@ -41,8 +41,39 @@ <SpecificVersion>False</SpecificVersion> <HintPath>..\packages\MediaBrowser.BdInfo.1.0.0.5\lib\net20\BDInfo.dll</HintPath> </Reference> - <Reference Include="ServiceStack.OrmLite.SqliteNET"> - <HintPath>..\packages\ServiceStack.OrmLite.Sqlite32.3.9.63\lib\net40\ServiceStack.OrmLite.SqliteNET.dll</HintPath> + <Reference Include="Mono.Data.Sqlite"> + <HintPath>..\packages\ServiceStack.OrmLite.Sqlite.Mono.3.9.70\lib\net35\Mono.Data.Sqlite.dll</HintPath> + </Reference> + <Reference Include="ServiceStack, Version=3.9.70.0, Culture=neutral, processorArchitecture=MSIL"> + <SpecificVersion>False</SpecificVersion> + <HintPath>..\packages\ServiceStack.3.9.70\lib\net35\ServiceStack.dll</HintPath> + </Reference> + <Reference Include="ServiceStack.Api.Swagger, Version=3.9.70.0, Culture=neutral, processorArchitecture=MSIL"> + <SpecificVersion>False</SpecificVersion> + <HintPath>..\packages\ServiceStack.Api.Swagger.3.9.70\lib\net35\ServiceStack.Api.Swagger.dll</HintPath> + </Reference> + <Reference Include="ServiceStack.Common, Version=3.9.70.0, Culture=neutral, processorArchitecture=MSIL"> + <SpecificVersion>False</SpecificVersion> + <HintPath>..\packages\ServiceStack.Common.3.9.70\lib\net35\ServiceStack.Common.dll</HintPath> + </Reference> + <Reference Include="ServiceStack.Interfaces, Version=1.0.0.0, Culture=neutral, processorArchitecture=MSIL"> + <SpecificVersion>False</SpecificVersion> + <HintPath>..\packages\ServiceStack.Common.3.9.70\lib\net35\ServiceStack.Interfaces.dll</HintPath> + </Reference> + <Reference Include="ServiceStack.OrmLite, Version=3.9.70.0, Culture=neutral, processorArchitecture=MSIL"> + <SpecificVersion>False</SpecificVersion> + <HintPath>..\packages\ServiceStack.OrmLite.Sqlite.Mono.3.9.70\lib\net35\ServiceStack.OrmLite.dll</HintPath> + </Reference> + <Reference Include="ServiceStack.OrmLite.Sqlite"> + <HintPath>..\packages\ServiceStack.OrmLite.Sqlite.Mono.3.9.70\lib\net35\ServiceStack.OrmLite.Sqlite.dll</HintPath> + </Reference> + <Reference Include="ServiceStack.ServiceInterface, Version=3.9.70.0, Culture=neutral, processorArchitecture=MSIL"> + <SpecificVersion>False</SpecificVersion> + <HintPath>..\packages\ServiceStack.3.9.70\lib\net35\ServiceStack.ServiceInterface.dll</HintPath> + </Reference> + <Reference Include="ServiceStack.Text, Version=3.9.70.0, Culture=neutral, processorArchitecture=MSIL"> + <SpecificVersion>False</SpecificVersion> + <HintPath>..\packages\ServiceStack.Text.3.9.70\lib\net35\ServiceStack.Text.dll</HintPath> </Reference> <Reference Include="System" /> <Reference Include="System.Core" /> @@ -72,39 +103,12 @@ <Reference Include="MoreLinq"> <HintPath>..\packages\morelinq.1.0.16006\lib\net35\MoreLinq.dll</HintPath> </Reference> - <Reference Include="ServiceStack"> - <HintPath>..\packages\ServiceStack.3.9.62\lib\net35\ServiceStack.dll</HintPath> - </Reference> - <Reference Include="ServiceStack.Api.Swagger"> - <HintPath>..\packages\ServiceStack.Api.Swagger.3.9.59\lib\net35\ServiceStack.Api.Swagger.dll</HintPath> - </Reference> - <Reference Include="ServiceStack.Common"> - <HintPath>..\packages\ServiceStack.Common.3.9.62\lib\net35\ServiceStack.Common.dll</HintPath> - </Reference> - <Reference Include="ServiceStack.Interfaces"> - <HintPath>..\packages\ServiceStack.Common.3.9.62\lib\net35\ServiceStack.Interfaces.dll</HintPath> - </Reference> <Reference Include="ServiceStack.OrmLite.SqlServer"> <HintPath>..\packages\ServiceStack.OrmLite.SqlServer.3.9.43\lib\ServiceStack.OrmLite.SqlServer.dll</HintPath> </Reference> <Reference Include="ServiceStack.Redis"> <HintPath>..\packages\ServiceStack.Redis.3.9.43\lib\net35\ServiceStack.Redis.dll</HintPath> </Reference> - <Reference Include="ServiceStack.ServiceInterface"> - <HintPath>..\packages\ServiceStack.3.9.62\lib\net35\ServiceStack.ServiceInterface.dll</HintPath> - </Reference> - <Reference Include="ServiceStack.Text"> - <HintPath>..\packages\ServiceStack.Text.3.9.62\lib\net35\ServiceStack.Text.dll</HintPath> - </Reference> - <Reference Include="Mono.Data.Sqlite"> - <HintPath>..\packages\ServiceStack.OrmLite.Sqlite.Mono.3.9.64\lib\net35\Mono.Data.Sqlite.dll</HintPath> - </Reference> - <Reference Include="ServiceStack.OrmLite"> - <HintPath>..\packages\ServiceStack.OrmLite.Sqlite.Mono.3.9.64\lib\net35\ServiceStack.OrmLite.dll</HintPath> - </Reference> - <Reference Include="ServiceStack.OrmLite.Sqlite"> - <HintPath>..\packages\ServiceStack.OrmLite.Sqlite.Mono.3.9.64\lib\net35\ServiceStack.OrmLite.Sqlite.dll</HintPath> - </Reference> </ItemGroup> <ItemGroup> <Compile Include="..\SharedVersion.cs"> @@ -167,7 +171,9 @@ <Compile Include="Library\Validators\StudiosPostScanTask.cs" /> <Compile Include="Library\Validators\StudiosValidator.cs" /> <Compile Include="Library\Validators\YearsPostScanTask.cs" /> + <Compile Include="LiveTv\ChannelImageProvider.cs" /> <Compile Include="LiveTv\LiveTvManager.cs" /> + <Compile Include="LiveTv\RefreshChannelsScheduledTask.cs" /> <Compile Include="Localization\LocalizationManager.cs" /> <Compile Include="MediaEncoder\MediaEncoder.cs" /> <Compile Include="Persistence\SqliteChapterRepository.cs" /> @@ -188,6 +194,7 @@ </Compile> <Compile Include="Session\SessionWebSocketListener.cs" /> <Compile Include="Session\WebSocketController.cs" /> + <Compile Include="Sorting\AiredEpisodeOrderComparer.cs" /> <Compile Include="Sorting\AirTimeComparer.cs" /> <Compile Include="Sorting\AlbumArtistComparer.cs" /> <Compile Include="Sorting\AlbumComparer.cs" /> @@ -265,6 +272,7 @@ <None Include="packages.config" /> </ItemGroup> <ItemGroup> + <Content Include="sqlite3.dll" /> <Content Include="swagger-ui\css\hightlight.default.css"> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> </Content> @@ -283,9 +291,6 @@ <Content Include="swagger-ui\images\wordnik_api.png"> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> </Content> - <Content Include="swagger-ui\index.html"> - <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> - </Content> <Content Include="swagger-ui\lib\backbone-min.js"> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> </Content> @@ -319,6 +324,10 @@ <Content Include="swagger-ui\swagger-ui.min.js"> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> </Content> + <EmbeddedResource Include="Localization\Ratings\ca.txt" /> + <Content Include="swagger-ui\index.html"> + <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> + </Content> </ItemGroup> <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" /> <Import Project="$(SolutionDir)\.nuget\nuget.targets" /> diff --git a/MediaBrowser.Server.Implementations/MediaEncoder/MediaEncoder.cs b/MediaBrowser.Server.Implementations/MediaEncoder/MediaEncoder.cs index b8874fb54..2224c657f 100644 --- a/MediaBrowser.Server.Implementations/MediaEncoder/MediaEncoder.cs +++ b/MediaBrowser.Server.Implementations/MediaEncoder/MediaEncoder.cs @@ -864,25 +864,33 @@ namespace MediaBrowser.Server.Implementations.MediaEncoder throw new ArgumentNullException("outputPath"); } - var vf = "scale=iw*sar:ih, scale=600:-1"; - + var vf = "crop=min(iw\\,ih*dar):min(ih\\,iw/dar):(iw-min(iw\\,iw*sar))/2:(ih - min (ih\\,ih/sar))/2,scale=600:600/dar"; + // apply some filters to thumbnail extracted below (below) crop any black lines that we made and get the correct ar then scale to width 600. This filter chain may have adverse effects on recorded tv thumbnails if ar changes during presentation ex. commercials @ diff ar if (threedFormat.HasValue) { switch (threedFormat.Value) { case Video3DFormat.HalfSideBySide: + vf = "crop=iw/2:ih:0:0,scale=(iw*2):ih,setdar=dar=a,crop=min(iw\\,ih*dar):min(ih\\,iw/dar):(iw-min(iw\\,iw*sar))/2:(ih - min (ih\\,ih/sar))/2,scale=600:600/dar"; + // hsbs crop width in half,scale to correct size, set the display aspect,crop out any black bars we may have made the scale width to 600. Work out the correct height based on the display aspect it will maintain the aspect where -1 in this case (3d) may not. + break; case Video3DFormat.FullSideBySide: - vf = "crop=iw/2:ih:0:0,scale=(iw*2):ih,scale=600:-1"; + vf = "crop=iw/2:ih:0:0,setdar=dar=a,,crop=min(iw\\,ih*dar):min(ih\\,iw/dar):(iw-min(iw\\,iw*sar))/2:(ih - min (ih\\,ih/sar))/2,scale=600:600/dar"; + //fsbs crop width in half,set the display aspect,crop out any black bars we may have made the scale width to 600. break; case Video3DFormat.HalfTopAndBottom: + vf = "crop=iw:ih/2:0:0,scale=(iw*2):ih),setdar=dar=a,crop=min(iw\\,ih*dar):min(ih\\,iw/dar):(iw-min(iw\\,iw*sar))/2:(ih - min (ih\\,ih/sar))/2,scale=600:600/dar"; + //htab crop heigh in half,scale to correct size, set the display aspect,crop out any black bars we may have made the scale width to 600 + break; case Video3DFormat.FullTopAndBottom: - vf = "crop=iw:ih/2:0:0,scale=iw:(ih*2),scale=600:-1"; + vf = "crop=iw:ih/2:0:0,setdar=dar=a,crop=min(iw\\,ih*dar):min(ih\\,iw/dar):(iw-min(iw\\,iw*sar))/2:(ih - min (ih\\,ih/sar))/2,scale=600:600/dar"; + // ftab crop heigt in half, set the display aspect,crop out any black bars we may have made the scale width to 600 break; } } - var args = useIFrame ? string.Format("-i {0} -threads 0 -v quiet -vframes 1 -filter:v select=\"eq(pict_type\\,I)\" -vf \"{2}\" -f image2 \"{1}\"", inputPath, outputPath, vf) : - string.Format("-i {0} -threads 0 -v quiet -vframes 1 -vf \"{2}\" -f image2 \"{1}\"", inputPath, outputPath, vf); + var args = useIFrame ? string.Format("-i {0} -threads 0 -v quiet -vframes 1 -vf \"thumbnail,{2}\" -f image2 \"{1}\"", inputPath, outputPath, vf) : + string.Format("-i {0} -threads 0 -v quiet -vframes 1 -vf \"{2}\" -f image2 \"{1}\"", inputPath, outputPath, vf); //use ffmpeg to sample 100 (we can drop this if required using thumbnail=50 for 50 frames) frames and pick the best thumbnail. Have a fall back jic var probeSize = GetProbeSizeArgument(type); diff --git a/MediaBrowser.Server.Implementations/Providers/ImageSaver.cs b/MediaBrowser.Server.Implementations/Providers/ImageSaver.cs index ff11b9a2b..e2192535c 100644 --- a/MediaBrowser.Server.Implementations/Providers/ImageSaver.cs +++ b/MediaBrowser.Server.Implementations/Providers/ImageSaver.cs @@ -6,6 +6,7 @@ using MediaBrowser.Controller.Entities.TV; using MediaBrowser.Controller.IO; using MediaBrowser.Model.Configuration; using MediaBrowser.Model.Entities; +using MediaBrowser.Model.Logging; using System; using System.Collections.Generic; using System.Globalization; @@ -13,7 +14,6 @@ using System.IO; using System.Linq; using System.Threading; using System.Threading.Tasks; -using MediaBrowser.Model.Logging; namespace MediaBrowser.Server.Implementations.Providers { @@ -89,6 +89,23 @@ namespace MediaBrowser.Server.Implementations.Providers if (locationType == LocationType.Remote || locationType == LocationType.Virtual) { saveLocally = false; + + var season = item as Season; + + // If season is virtual under a physical series, save locally if using compatible convention + if (season != null && _config.Configuration.ImageSavingConvention == ImageSavingConvention.Compatible) + { + var series = season.Series; + + if (series != null) + { + var seriesLocationType = series.LocationType; + if (seriesLocationType == LocationType.FileSystem || seriesLocationType == LocationType.Offline) + { + saveLocally = true; + } + } + } } if (type == ImageType.Backdrop && imageIndex == null) @@ -344,6 +361,9 @@ namespace MediaBrowser.Server.Implementations.Providers case ImageType.Art: filename = "clearart"; break; + case ImageType.Disc: + filename = item is MusicAlbum ? "cdart" : "disc"; + break; case ImageType.Primary: filename = item is Episode ? Path.GetFileNameWithoutExtension(item.Path) : "folder"; break; @@ -399,7 +419,7 @@ namespace MediaBrowser.Server.Implementations.Providers return path; } - private string GetBackdropSaveFilename(List<string> images, string zeroIndexFilename, string numberedIndexPrefix, int index) + private string GetBackdropSaveFilename(IEnumerable<string> images, string zeroIndexFilename, string numberedIndexPrefix, int index) { if (index == 0) { @@ -428,6 +448,8 @@ namespace MediaBrowser.Server.Implementations.Providers /// <exception cref="System.ArgumentNullException">imageIndex</exception> private string[] GetCompatibleSavePaths(BaseItem item, ImageType type, int? imageIndex, string mimeType) { + var season = item as Season; + var extension = mimeType.Split('/').Last(); if (string.Equals(extension, "jpeg", StringComparison.OrdinalIgnoreCase)) @@ -446,9 +468,9 @@ namespace MediaBrowser.Server.Implementations.Providers if (imageIndex.Value == 0) { - if (item is Season && item.IndexNumber.HasValue) + if (season != null && item.IndexNumber.HasValue) { - var seriesFolder = Path.GetDirectoryName(item.Path); + var seriesFolder = season.SeriesPath; var seasonMarker = item.IndexNumber.Value == 0 ? "-specials" @@ -478,9 +500,9 @@ namespace MediaBrowser.Server.Implementations.Providers if (type == ImageType.Primary) { - if (item is Season && item.IndexNumber.HasValue) + if (season != null && item.IndexNumber.HasValue) { - var seriesFolder = Path.GetDirectoryName(item.Path); + var seriesFolder = season.SeriesPath; var seasonMarker = item.IndexNumber.Value == 0 ? "-specials" @@ -505,15 +527,19 @@ namespace MediaBrowser.Server.Implementations.Providers return new[] { GetSavePathForItemInMixedFolder(item, type, string.Empty, extension) }; } - var filename = "poster" + extension; - return new[] { Path.Combine(item.MetaLocation, filename) }; + if (item is MusicAlbum || item is MusicArtist) + { + return new[] { Path.Combine(item.MetaLocation, "folder" + extension) }; + } + + return new[] { Path.Combine(item.MetaLocation, "poster" + extension) }; } if (type == ImageType.Banner) { - if (item is Season && item.IndexNumber.HasValue) + if (season != null && item.IndexNumber.HasValue) { - var seriesFolder = Path.GetDirectoryName(item.Path); + var seriesFolder = season.SeriesPath; var seasonMarker = item.IndexNumber.Value == 0 ? "-specials" @@ -527,9 +553,9 @@ namespace MediaBrowser.Server.Implementations.Providers if (type == ImageType.Thumb) { - if (item is Season && item.IndexNumber.HasValue) + if (season != null && item.IndexNumber.HasValue) { - var seriesFolder = Path.GetDirectoryName(item.Path); + var seriesFolder = season.SeriesPath; var seasonMarker = item.IndexNumber.Value == 0 ? "-specials" diff --git a/MediaBrowser.Server.Implementations/ServerApplicationPaths.cs b/MediaBrowser.Server.Implementations/ServerApplicationPaths.cs index fb3c5aec8..db538f8dd 100644 --- a/MediaBrowser.Server.Implementations/ServerApplicationPaths.cs +++ b/MediaBrowser.Server.Implementations/ServerApplicationPaths.cs @@ -13,16 +13,16 @@ namespace MediaBrowser.Server.Implementations /// <summary> /// Initializes a new instance of the <see cref="ServerApplicationPaths" /> class. /// </summary> - public ServerApplicationPaths() - : base(true) + public ServerApplicationPaths(string applicationPath) + : base(true, applicationPath) { } #else /// <summary> /// Initializes a new instance of the <see cref="ServerApplicationPaths"/> class. /// </summary> - public ServerApplicationPaths() - : base(false) + public ServerApplicationPaths(string applicationPath) + : base(false, applicationPath) { } #endif @@ -30,9 +30,8 @@ namespace MediaBrowser.Server.Implementations /// <summary> /// Initializes a new instance of the <see cref="BaseApplicationPaths" /> class. /// </summary> - /// <param name="programDataPath">The program data path.</param> - public ServerApplicationPaths(string programDataPath) - : base(programDataPath) + public ServerApplicationPaths(string programDataPath, string applicationPath) + : base(programDataPath, applicationPath) { } diff --git a/MediaBrowser.Server.Implementations/Sorting/AiredEpisodeOrderComparer.cs b/MediaBrowser.Server.Implementations/Sorting/AiredEpisodeOrderComparer.cs new file mode 100644 index 000000000..76971342a --- /dev/null +++ b/MediaBrowser.Server.Implementations/Sorting/AiredEpisodeOrderComparer.cs @@ -0,0 +1,138 @@ +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Entities.TV; +using MediaBrowser.Controller.Sorting; +using MediaBrowser.Model.Querying; +using System; + +namespace MediaBrowser.Server.Implementations.Sorting +{ + class AiredEpisodeOrderComparer : IBaseItemComparer + { + /// <summary> + /// Compares the specified x. + /// </summary> + /// <param name="x">The x.</param> + /// <param name="y">The y.</param> + /// <returns>System.Int32.</returns> + public int Compare(BaseItem x, BaseItem y) + { + if (x.PremiereDate.HasValue && y.PremiereDate.HasValue) + { + var val = DateTime.Compare(x.PremiereDate.Value, y.PremiereDate.Value); + + if (val != 0) + { + //return val; + } + } + + var episode1 = x as Episode; + var episode2 = y as Episode; + + if (episode1 == null) + { + if (episode2 == null) + { + return 0; + } + + return 1; + } + + if (episode2 == null) + { + return -1; + } + + return Compare(episode1, episode2); + } + + private int Compare(Episode x, Episode y) + { + var isXSpecial = (x.PhysicalSeasonNumber ?? -1) == 0; + var isYSpecial = (y.PhysicalSeasonNumber ?? -1) == 0; + + if (isXSpecial && isYSpecial) + { + return CompareSpecials(x, y); + } + + if (!isXSpecial && !isYSpecial) + { + return CompareEpisodes(x, y); + } + + if (!isXSpecial && isYSpecial) + { + return CompareEpisodeToSpecial(x, y); + } + + return CompareEpisodeToSpecial(y, x) * -1; + } + + private int CompareEpisodeToSpecial(Episode x, Episode y) + { + var xSeason = x.PhysicalSeasonNumber ?? -1; + var ySeason = y.AirsAfterSeasonNumber ?? y.AirsBeforeSeasonNumber ?? -1; + + if (xSeason != ySeason) + { + return xSeason.CompareTo(ySeason); + } + + // Now we know they have the same season + + // Compare episode number + + // Add 1 to to non-specials to account for AirsBeforeEpisodeNumber + var xEpisode = x.IndexNumber ?? -1; + xEpisode++; + var yEpisode = y.AirsBeforeEpisodeNumber ?? 10000; + + return xEpisode.CompareTo(yEpisode); + } + + private int CompareSpecials(Episode x, Episode y) + { + return GetSpecialCompareValue(x).CompareTo(GetSpecialCompareValue(y)); + } + + private int GetSpecialCompareValue(Episode item) + { + // First sort by season number + // Since there are three sort orders, pad with 9 digits (3 for each, figure 1000 episode buffer should be enough) + var val = (item.AirsAfterSeasonNumber ?? item.AirsBeforeSeasonNumber ?? 0) * 1000000000; + + // Second sort order is if it airs after the season + if (item.AirsAfterSeasonNumber.HasValue) + { + val += 1000000; + } + + // Third level is the episode number + val += (item.AirsBeforeEpisodeNumber ?? 0) * 1000; + + // Finally, if that's still the same, last resort is the special number itself + val += item.IndexNumber ?? 0; + + return val; + } + + private int CompareEpisodes(Episode x, Episode y) + { + var xValue = ((x.PhysicalSeasonNumber ?? -1) * 1000) + (x.IndexNumber ?? -1); + var yValue = ((y.PhysicalSeasonNumber ?? -1) * 1000) + (y.IndexNumber ?? -1); + + return xValue.CompareTo(yValue); + } + + /// <summary> + /// Gets the name. + /// </summary> + /// <value>The name.</value> + public string Name + { + get { return ItemSortBy.AiredEpisodeOrder; } + } + } +} diff --git a/MediaBrowser.Server.Implementations/Sorting/AlbumCountComparer.cs b/MediaBrowser.Server.Implementations/Sorting/AlbumCountComparer.cs index 8e24bc52d..e35ba00f2 100644 --- a/MediaBrowser.Server.Implementations/Sorting/AlbumCountComparer.cs +++ b/MediaBrowser.Server.Implementations/Sorting/AlbumCountComparer.cs @@ -48,7 +48,7 @@ namespace MediaBrowser.Server.Implementations.Sorting if (itemByName != null) { - var counts = itemByName.GetItemByNameCounts(User); + var counts = itemByName.GetItemByNameCounts(User.Id); if (counts != null) { diff --git a/MediaBrowser.Server.Implementations/Sorting/BudgetComparer.cs b/MediaBrowser.Server.Implementations/Sorting/BudgetComparer.cs index d2dac6549..87a7325c6 100644 --- a/MediaBrowser.Server.Implementations/Sorting/BudgetComparer.cs +++ b/MediaBrowser.Server.Implementations/Sorting/BudgetComparer.cs @@ -19,7 +19,12 @@ namespace MediaBrowser.Server.Implementations.Sorting private double GetValue(BaseItem x) { - return x.Budget ?? 0; + var hasBudget = x as IHasBudget; + if (hasBudget != null) + { + return hasBudget.Budget ?? 0; + } + return 0; } /// <summary> diff --git a/MediaBrowser.Server.Implementations/Sorting/EpisodeCountComparer.cs b/MediaBrowser.Server.Implementations/Sorting/EpisodeCountComparer.cs index 7731e59d2..b3fd8a023 100644 --- a/MediaBrowser.Server.Implementations/Sorting/EpisodeCountComparer.cs +++ b/MediaBrowser.Server.Implementations/Sorting/EpisodeCountComparer.cs @@ -48,7 +48,7 @@ namespace MediaBrowser.Server.Implementations.Sorting if (itemByName != null) { - var counts = itemByName.GetItemByNameCounts(User); + var counts = itemByName.GetItemByNameCounts(User.Id); if (counts != null) { diff --git a/MediaBrowser.Server.Implementations/Sorting/MovieCountComparer.cs b/MediaBrowser.Server.Implementations/Sorting/MovieCountComparer.cs index 51f39a02f..605f4d1af 100644 --- a/MediaBrowser.Server.Implementations/Sorting/MovieCountComparer.cs +++ b/MediaBrowser.Server.Implementations/Sorting/MovieCountComparer.cs @@ -48,7 +48,7 @@ namespace MediaBrowser.Server.Implementations.Sorting if (itemByName != null) { - var counts = itemByName.GetItemByNameCounts(User); + var counts = itemByName.GetItemByNameCounts(User.Id); if (counts != null) { diff --git a/MediaBrowser.Server.Implementations/Sorting/MusicVideoCountComparer.cs b/MediaBrowser.Server.Implementations/Sorting/MusicVideoCountComparer.cs index 889658459..6c9c5534d 100644 --- a/MediaBrowser.Server.Implementations/Sorting/MusicVideoCountComparer.cs +++ b/MediaBrowser.Server.Implementations/Sorting/MusicVideoCountComparer.cs @@ -48,7 +48,7 @@ namespace MediaBrowser.Server.Implementations.Sorting if (itemByName != null) { - var counts = itemByName.GetItemByNameCounts(User); + var counts = itemByName.GetItemByNameCounts(User.Id); if (counts != null) { diff --git a/MediaBrowser.Server.Implementations/Sorting/RevenueComparer.cs b/MediaBrowser.Server.Implementations/Sorting/RevenueComparer.cs index e9d7912a1..6caa27ac3 100644 --- a/MediaBrowser.Server.Implementations/Sorting/RevenueComparer.cs +++ b/MediaBrowser.Server.Implementations/Sorting/RevenueComparer.cs @@ -19,7 +19,12 @@ namespace MediaBrowser.Server.Implementations.Sorting private double GetValue(BaseItem x) { - return x.Revenue ?? 0; + var hasBudget = x as IHasBudget; + if (hasBudget != null) + { + return hasBudget.Revenue ?? 0; + } + return 0; } /// <summary> diff --git a/MediaBrowser.Server.Implementations/Sorting/SeriesCountComparer.cs b/MediaBrowser.Server.Implementations/Sorting/SeriesCountComparer.cs index 13d2932cb..8567e400c 100644 --- a/MediaBrowser.Server.Implementations/Sorting/SeriesCountComparer.cs +++ b/MediaBrowser.Server.Implementations/Sorting/SeriesCountComparer.cs @@ -48,7 +48,7 @@ namespace MediaBrowser.Server.Implementations.Sorting if (itemByName != null) { - var counts = itemByName.GetItemByNameCounts(User); + var counts = itemByName.GetItemByNameCounts(User.Id); if (counts != null) { diff --git a/MediaBrowser.Server.Implementations/Sorting/SongCountComparer.cs b/MediaBrowser.Server.Implementations/Sorting/SongCountComparer.cs index b12e1322a..85b849a21 100644 --- a/MediaBrowser.Server.Implementations/Sorting/SongCountComparer.cs +++ b/MediaBrowser.Server.Implementations/Sorting/SongCountComparer.cs @@ -48,7 +48,7 @@ namespace MediaBrowser.Server.Implementations.Sorting if (itemByName != null) { - var counts = itemByName.GetItemByNameCounts(User); + var counts = itemByName.GetItemByNameCounts(User.Id); if (counts != null) { diff --git a/MediaBrowser.Server.Implementations/Sorting/TrailerCountComparer.cs b/MediaBrowser.Server.Implementations/Sorting/TrailerCountComparer.cs index b6f67410a..a13875674 100644 --- a/MediaBrowser.Server.Implementations/Sorting/TrailerCountComparer.cs +++ b/MediaBrowser.Server.Implementations/Sorting/TrailerCountComparer.cs @@ -48,7 +48,7 @@ namespace MediaBrowser.Server.Implementations.Sorting if (itemByName != null) { - var counts = itemByName.GetItemByNameCounts(User); + var counts = itemByName.GetItemByNameCounts(User.Id); if (counts != null) { diff --git a/MediaBrowser.Server.Implementations/packages.config b/MediaBrowser.Server.Implementations/packages.config index eeeedfe36..488dbc1ae 100644 --- a/MediaBrowser.Server.Implementations/packages.config +++ b/MediaBrowser.Server.Implementations/packages.config @@ -6,13 +6,12 @@ <package id="Rx-Core" version="2.1.30214.0" targetFramework="net45" />
<package id="Rx-Interfaces" version="2.1.30214.0" targetFramework="net45" />
<package id="Rx-Linq" version="2.1.30214.0" targetFramework="net45" />
- <package id="ServiceStack" version="3.9.62" targetFramework="net45" />
- <package id="ServiceStack.Api.Swagger" version="3.9.59" targetFramework="net45" />
- <package id="ServiceStack.Common" version="3.9.62" targetFramework="net45" />
- <package id="ServiceStack.OrmLite.Sqlite.Mono" version="3.9.64" targetFramework="net45" />
- <package id="ServiceStack.OrmLite.Sqlite32" version="3.9.63" targetFramework="net45" />
+ <package id="ServiceStack" version="3.9.70" targetFramework="net45" />
+ <package id="ServiceStack.Api.Swagger" version="3.9.70" targetFramework="net45" />
+ <package id="ServiceStack.Common" version="3.9.70" targetFramework="net45" />
+ <package id="ServiceStack.OrmLite.Sqlite.Mono" version="3.9.70" targetFramework="net45" />
<package id="ServiceStack.OrmLite.SqlServer" version="3.9.43" targetFramework="net45" />
<package id="ServiceStack.Redis" version="3.9.43" targetFramework="net45" />
- <package id="ServiceStack.Text" version="3.9.62" targetFramework="net45" />
+ <package id="ServiceStack.Text" version="3.9.70" targetFramework="net45" />
<package id="System.Data.SQLite.x86" version="1.0.89.0" targetFramework="net45" />
</packages>
\ No newline at end of file diff --git a/MediaBrowser.Server.Implementations/swagger-ui/index.html b/MediaBrowser.Server.Implementations/swagger-ui/index.html index 0fcc06959..49f983a72 100644 --- a/MediaBrowser.Server.Implementations/swagger-ui/index.html +++ b/MediaBrowser.Server.Implementations/swagger-ui/index.html @@ -20,7 +20,7 @@ $(function () { window.swaggerUi = new SwaggerUi({ discoveryUrl: "../resources", - apiKey:"special-key", + apiKey: "special-key", dom_id:"swagger-ui-container", supportHeaderParams: false, supportedSubmitMethods: ['get', 'post', 'put'], diff --git a/MediaBrowser.Server.Mono/MediaBrowser.Server.Mono.csproj b/MediaBrowser.Server.Mono/MediaBrowser.Server.Mono.csproj index 900169c70..e32dad8d7 100644 --- a/MediaBrowser.Server.Mono/MediaBrowser.Server.Mono.csproj +++ b/MediaBrowser.Server.Mono/MediaBrowser.Server.Mono.csproj @@ -44,13 +44,13 @@ <Reference Include="System.Windows.Forms" /> <Reference Include="System.Net.Http" /> <Reference Include="System.Data" /> + <Reference Include="Mono.Posix" /> <Reference Include="ServiceStack.Common"> - <HintPath>..\packages\ServiceStack.Common.3.9.62\lib\net35\ServiceStack.Common.dll</HintPath> + <HintPath>..\packages\ServiceStack.Common.3.9.70\lib\net35\ServiceStack.Common.dll</HintPath> </Reference> <Reference Include="ServiceStack.Interfaces"> - <HintPath>..\packages\ServiceStack.Common.3.9.62\lib\net35\ServiceStack.Interfaces.dll</HintPath> + <HintPath>..\packages\ServiceStack.Common.3.9.70\lib\net35\ServiceStack.Interfaces.dll</HintPath> </Reference> - <Reference Include="Mono.Posix" /> </ItemGroup> <ItemGroup> <EmbeddedResource Include="gtk-gui\gui.stetic"> @@ -82,7 +82,6 @@ </Compile> <Compile Include="Native\Assemblies.cs" /> <Compile Include="Native\NativeApp.cs" /> - <Compile Include="Native\HttpClientFactory.cs" /> <Compile Include="Networking\NetworkManager.cs" /> <Compile Include="..\MediaBrowser.ServerApplication\FFMpeg\FFMpegDownloader.cs"> <Link>FFMpeg\FFMpegDownloader.cs</Link> diff --git a/MediaBrowser.Server.Mono/Native/HttpClientFactory.cs b/MediaBrowser.Server.Mono/Native/HttpClientFactory.cs deleted file mode 100644 index 0fceab060..000000000 --- a/MediaBrowser.Server.Mono/Native/HttpClientFactory.cs +++ /dev/null @@ -1,24 +0,0 @@ -using System; -using System.Net.Http; - -namespace MediaBrowser.ServerApplication.Native -{ - /// <summary> - /// Class HttpClientFactory - /// </summary> - public static class HttpClientFactory - { - /// <summary> - /// Gets the HTTP client. - /// </summary> - /// <param name="enableHttpCompression">if set to <c>true</c> [enable HTTP compression].</param> - /// <returns>HttpClient.</returns> - public static HttpClient GetHttpClient(bool enableHttpCompression) - { - return new HttpClient() - { - Timeout = TimeSpan.FromSeconds(20) - }; - } - } -} diff --git a/MediaBrowser.Server.Mono/Program.cs b/MediaBrowser.Server.Mono/Program.cs index 057a2456f..d423017fc 100644 --- a/MediaBrowser.Server.Mono/Program.cs +++ b/MediaBrowser.Server.Mono/Program.cs @@ -36,7 +36,9 @@ namespace MediaBrowser.Server.Mono { Application.Init (); - var appPaths = CreateApplicationPaths(); + var applicationPath = Assembly.GetEntryAssembly ().Location; + + var appPaths = CreateApplicationPaths(applicationPath); var logManager = new NlogManager(appPaths.LogDirectoryPath, "server"); logManager.ReloadLogger(LogSeverity.Info); @@ -65,9 +67,9 @@ namespace MediaBrowser.Server.Mono } } - private static ServerApplicationPaths CreateApplicationPaths() + private static ServerApplicationPaths CreateApplicationPaths(string applicationPath) { - return new ServerApplicationPaths(); + return new ServerApplicationPaths(applicationPath); } /// <summary> diff --git a/MediaBrowser.ServerApplication/ApplicationHost.cs b/MediaBrowser.ServerApplication/ApplicationHost.cs index 03819ff91..f51238152 100644 --- a/MediaBrowser.ServerApplication/ApplicationHost.cs +++ b/MediaBrowser.ServerApplication/ApplicationHost.cs @@ -27,6 +27,7 @@ using MediaBrowser.Controller.Session; using MediaBrowser.Controller.Sorting; using MediaBrowser.Model.Logging; using MediaBrowser.Model.MediaInfo; +using MediaBrowser.Model.Serialization; using MediaBrowser.Model.System; using MediaBrowser.Model.Updates; using MediaBrowser.Providers; @@ -199,8 +200,6 @@ namespace MediaBrowser.ServerApplication { await base.RunStartupTasks().ConfigureAwait(false); - DirectoryWatchers.Start(); - Logger.Info("Core startup complete"); Parallel.ForEach(GetExports<IServerEntryPoint>(), entryPoint => @@ -294,7 +293,7 @@ namespace MediaBrowser.ServerApplication DtoService = new DtoService(Logger, LibraryManager, UserManager, UserDataManager, ItemRepository, ImageProcessor); RegisterSingleInstance(DtoService); - LiveTvManager = new LiveTvManager(); + LiveTvManager = new LiveTvManager(ApplicationPaths, FileSystemManager, Logger, ItemRepository, ImageProcessor, UserManager, LocalizationManager, UserDataManager, DtoService); RegisterSingleInstance(LiveTvManager); var displayPreferencesTask = Task.Run(async () => await ConfigureDisplayPreferencesRepositories().ConfigureAwait(false)); @@ -611,6 +610,8 @@ namespace MediaBrowser.ServerApplication CompletedInstallations = InstallationManager.CompletedInstallations.ToList(), Id = _systemId, ProgramDataPath = ApplicationPaths.ProgramDataPath, + LogPath = ApplicationPaths.LogDirectoryPath, + ItemsByNamePath = ApplicationPaths.ItemsByNamePath, MacAddress = GetMacAddress(), HttpServerPortNumber = ServerConfigurationManager.Configuration.HttpServerPortNumber, OperatingSystem = Environment.OSVersion.ToString(), @@ -704,16 +705,6 @@ namespace MediaBrowser.ServerApplication OnApplicationUpdated(package.version); } - /// <summary> - /// Creates the HTTP client. - /// </summary> - /// <param name="enableHttpCompression">if set to <c>true</c> [enable HTTP compression].</param> - /// <returns>HttpClient.</returns> - protected override HttpClient CreateHttpClient(bool enableHttpCompression) - { - return HttpClientFactory.GetHttpClient(enableHttpCompression); - } - protected override void ConfigureAutoRunAtStartup(bool autorun) { Autorun.Configure(autorun); diff --git a/MediaBrowser.ServerApplication/FFMpeg/FFMpegDownloader.cs b/MediaBrowser.ServerApplication/FFMpeg/FFMpegDownloader.cs index e8af0a13e..1f329446e 100644 --- a/MediaBrowser.ServerApplication/FFMpeg/FFMpegDownloader.cs +++ b/MediaBrowser.ServerApplication/FFMpeg/FFMpegDownloader.cs @@ -90,11 +90,7 @@ namespace MediaBrowser.ServerApplication.FFMpeg { Url = url, CancellationToken = CancellationToken.None, - Progress = new Progress<double>(), - - // Make it look like a browser - // Try to hide that we're direct linking - UserAgent = "Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/30.0.1599.47 Safari/537.36" + Progress = new Progress<double>() }); } diff --git a/MediaBrowser.ServerApplication/MainStartup.cs b/MediaBrowser.ServerApplication/MainStartup.cs index d3eed5c48..349cbbc61 100644 --- a/MediaBrowser.ServerApplication/MainStartup.cs +++ b/MediaBrowser.ServerApplication/MainStartup.cs @@ -158,7 +158,9 @@ namespace MediaBrowser.ServerApplication return new ServerApplicationPaths(programDataPath); } - return new ServerApplicationPaths(); + var applicationPath = Process.GetCurrentProcess().MainModule.FileName; + + return new ServerApplicationPaths(applicationPath); } /// <summary> diff --git a/MediaBrowser.ServerApplication/MediaBrowser.ServerApplication.csproj b/MediaBrowser.ServerApplication/MediaBrowser.ServerApplication.csproj index f24283e70..5f05bc787 100644 --- a/MediaBrowser.ServerApplication/MediaBrowser.ServerApplication.csproj +++ b/MediaBrowser.ServerApplication/MediaBrowser.ServerApplication.csproj @@ -119,9 +119,9 @@ <Reference Include="Hardcodet.Wpf.TaskbarNotification"> <HintPath>..\packages\Hardcodet.Wpf.TaskbarNotification.1.0.4.0\lib\net40\Hardcodet.Wpf.TaskbarNotification.dll</HintPath> </Reference> - <Reference Include="MediaBrowser.IsoMounter, Version=1.0.5025.12100, Culture=neutral, processorArchitecture=MSIL"> + <Reference Include="MediaBrowser.IsoMounter, Version=1.0.5079.1480, Culture=neutral, processorArchitecture=MSIL"> <SpecificVersion>False</SpecificVersion> - <HintPath>..\packages\MediaBrowser.IsoMounting.3.0.61\lib\net45\MediaBrowser.IsoMounter.dll</HintPath> + <HintPath>..\packages\MediaBrowser.IsoMounting.3.0.65\lib\net45\MediaBrowser.IsoMounter.dll</HintPath> </Reference> <Reference Include="NLog, Version=2.1.0.0, Culture=neutral, PublicKeyToken=5120e14c03d0593c, processorArchitecture=MSIL"> <SpecificVersion>False</SpecificVersion> @@ -129,19 +129,19 @@ </Reference> <Reference Include="pfmclrapi, Version=0.0.0.0, Culture=neutral, processorArchitecture=x86"> <SpecificVersion>False</SpecificVersion> - <HintPath>..\packages\MediaBrowser.IsoMounting.3.0.61\lib\net45\pfmclrapi.dll</HintPath> + <HintPath>..\packages\MediaBrowser.IsoMounting.3.0.65\lib\net45\pfmclrapi.dll</HintPath> </Reference> - <Reference Include="ServiceStack, Version=3.9.60.0, Culture=neutral, processorArchitecture=MSIL"> + <Reference Include="ServiceStack, Version=3.9.70.0, Culture=neutral, processorArchitecture=MSIL"> <SpecificVersion>False</SpecificVersion> - <HintPath>..\packages\ServiceStack.3.9.62\lib\net35\ServiceStack.dll</HintPath> + <HintPath>..\packages\ServiceStack.3.9.70\lib\net35\ServiceStack.dll</HintPath> </Reference> - <Reference Include="ServiceStack.Common, Version=3.9.60.0, Culture=neutral, processorArchitecture=MSIL"> + <Reference Include="ServiceStack.Common, Version=3.9.70.0, Culture=neutral, processorArchitecture=MSIL"> <SpecificVersion>False</SpecificVersion> - <HintPath>..\packages\ServiceStack.Common.3.9.62\lib\net35\ServiceStack.Common.dll</HintPath> + <HintPath>..\packages\ServiceStack.Common.3.9.70\lib\net35\ServiceStack.Common.dll</HintPath> </Reference> - <Reference Include="ServiceStack.Interfaces, Version=3.9.60.0, Culture=neutral, processorArchitecture=MSIL"> + <Reference Include="ServiceStack.Interfaces, Version=1.0.0.0, Culture=neutral, processorArchitecture=MSIL"> <SpecificVersion>False</SpecificVersion> - <HintPath>..\packages\ServiceStack.Common.3.9.62\lib\net35\ServiceStack.Interfaces.dll</HintPath> + <HintPath>..\packages\ServiceStack.Common.3.9.70\lib\net35\ServiceStack.Interfaces.dll</HintPath> </Reference> <Reference Include="ServiceStack.OrmLite.SqlServer"> <HintPath>..\packages\ServiceStack.OrmLite.SqlServer.3.9.44\lib\ServiceStack.OrmLite.SqlServer.dll</HintPath> @@ -149,13 +149,13 @@ <Reference Include="ServiceStack.Redis"> <HintPath>..\packages\ServiceStack.Redis.3.9.44\lib\net35\ServiceStack.Redis.dll</HintPath> </Reference> - <Reference Include="ServiceStack.ServiceInterface, Version=3.9.60.0, Culture=neutral, processorArchitecture=MSIL"> + <Reference Include="ServiceStack.ServiceInterface, Version=3.9.70.0, Culture=neutral, processorArchitecture=MSIL"> <SpecificVersion>False</SpecificVersion> - <HintPath>..\packages\ServiceStack.3.9.62\lib\net35\ServiceStack.ServiceInterface.dll</HintPath> + <HintPath>..\packages\ServiceStack.3.9.70\lib\net35\ServiceStack.ServiceInterface.dll</HintPath> </Reference> - <Reference Include="ServiceStack.Text, Version=3.9.59.0, Culture=neutral, processorArchitecture=MSIL"> + <Reference Include="ServiceStack.Text, Version=3.9.70.0, Culture=neutral, processorArchitecture=MSIL"> <SpecificVersion>False</SpecificVersion> - <HintPath>..\packages\ServiceStack.Text.3.9.62\lib\net35\ServiceStack.Text.dll</HintPath> + <HintPath>..\packages\ServiceStack.Text.3.9.70\lib\net35\ServiceStack.Text.dll</HintPath> </Reference> <Reference Include="SimpleInjector, Version=2.3.6.0, Culture=neutral, PublicKeyToken=984cb50dea722e99, processorArchitecture=MSIL"> <SpecificVersion>False</SpecificVersion> @@ -190,7 +190,6 @@ <Compile Include="FFMpeg\FFMpegInfo.cs" /> <Compile Include="IO\FileSystemFactory.cs" /> <Compile Include="Native\Assemblies.cs" /> - <Compile Include="Native\HttpClientFactory.cs" /> <Compile Include="Native\NativeApp.cs" /> <Compile Include="IO\NativeFileSystem.cs" /> <Compile Include="Native\ServerAuthorization.cs" /> diff --git a/MediaBrowser.ServerApplication/Native/HttpClientFactory.cs b/MediaBrowser.ServerApplication/Native/HttpClientFactory.cs deleted file mode 100644 index 57f00ba03..000000000 --- a/MediaBrowser.ServerApplication/Native/HttpClientFactory.cs +++ /dev/null @@ -1,30 +0,0 @@ -using System; -using System.Net; -using System.Net.Cache; -using System.Net.Http; - -namespace MediaBrowser.ServerApplication.Native -{ - /// <summary> - /// Class HttpClientFactory - /// </summary> - public static class HttpClientFactory - { - /// <summary> - /// Gets the HTTP client. - /// </summary> - /// <param name="enableHttpCompression">if set to <c>true</c> [enable HTTP compression].</param> - /// <returns>HttpClient.</returns> - public static HttpClient GetHttpClient(bool enableHttpCompression) - { - return new HttpClient(new WebRequestHandler - { - CachePolicy = new RequestCachePolicy(RequestCacheLevel.Revalidate), - AutomaticDecompression = enableHttpCompression ? DecompressionMethods.Deflate : DecompressionMethods.None - }) - { - Timeout = TimeSpan.FromSeconds(20) - }; - } - } -} diff --git a/MediaBrowser.ServerApplication/packages.config b/MediaBrowser.ServerApplication/packages.config index 0893a1b38..5d7c3265f 100644 --- a/MediaBrowser.ServerApplication/packages.config +++ b/MediaBrowser.ServerApplication/packages.config @@ -1,12 +1,12 @@ <?xml version="1.0" encoding="utf-8"?> <packages> <package id="Hardcodet.Wpf.TaskbarNotification" version="1.0.4.0" targetFramework="net45" /> - <package id="MediaBrowser.IsoMounting" version="3.0.61" targetFramework="net45" /> + <package id="MediaBrowser.IsoMounting" version="3.0.65" targetFramework="net45" /> <package id="NLog" version="2.1.0" targetFramework="net45" /> - <package id="ServiceStack" version="3.9.62" targetFramework="net45" /> - <package id="ServiceStack.Common" version="3.9.62" targetFramework="net45" /> + <package id="ServiceStack" version="3.9.70" targetFramework="net45" /> + <package id="ServiceStack.Common" version="3.9.70" targetFramework="net45" /> <package id="ServiceStack.OrmLite.SqlServer" version="3.9.44" targetFramework="net45" /> <package id="ServiceStack.Redis" version="3.9.44" targetFramework="net45" /> - <package id="ServiceStack.Text" version="3.9.62" targetFramework="net45" /> + <package id="ServiceStack.Text" version="3.9.70" targetFramework="net45" /> <package id="SimpleInjector" version="2.3.6" targetFramework="net45" /> </packages>
\ No newline at end of file diff --git a/MediaBrowser.Tests/Providers/MovieDbProviderTests.cs b/MediaBrowser.Tests/Providers/MovieDbProviderTests.cs index f7a87c9d4..8f5dcc034 100644 --- a/MediaBrowser.Tests/Providers/MovieDbProviderTests.cs +++ b/MediaBrowser.Tests/Providers/MovieDbProviderTests.cs @@ -1,4 +1,5 @@ -using MediaBrowser.Providers.Movies; +using MediaBrowser.Controller.Providers; +using MediaBrowser.Providers.Movies; using Microsoft.VisualStudio.TestTools.UnitTesting; namespace MediaBrowser.Tests.Providers { @@ -8,27 +9,27 @@ namespace MediaBrowser.Tests.Providers { public void TestNameMatches() { var name = string.Empty; int? year = null; - MovieDbProvider.ParseName("My Movie (2013)", out name, out year); + NameParser.ParseName("My Movie (2013)", out name, out year); Assert.AreEqual("My Movie", name); Assert.AreEqual(2013, year); name = string.Empty; year = null; - MovieDbProvider.ParseName("My Movie 2 (2013)", out name, out year); + NameParser.ParseName("My Movie 2 (2013)", out name, out year); Assert.AreEqual("My Movie 2", name); Assert.AreEqual(2013, year); name = string.Empty; year = null; - MovieDbProvider.ParseName("My Movie 2001 (2013)", out name, out year); + NameParser.ParseName("My Movie 2001 (2013)", out name, out year); Assert.AreEqual("My Movie 2001", name); Assert.AreEqual(2013, year); name = string.Empty; year = null; - MovieDbProvider.ParseName("My Movie - 2 (2013)", out name, out year); + NameParser.ParseName("My Movie - 2 (2013)", out name, out year); Assert.AreEqual("My Movie - 2", name); Assert.AreEqual(2013, year); name = string.Empty; year = null; - MovieDbProvider.ParseName("curse.of.chucky.2013.stv.unrated.multi.1080p.bluray.x264-rough", out name, out year); + NameParser.ParseName("curse.of.chucky.2013.stv.unrated.multi.1080p.bluray.x264-rough", out name, out year); Assert.AreEqual("curse.of.chucky", name); Assert.AreEqual(2013, year); } diff --git a/MediaBrowser.Tests/Resolvers/TvUtilTests.cs b/MediaBrowser.Tests/Resolvers/TvUtilTests.cs index 1aaff6384..69a6be329 100644 --- a/MediaBrowser.Tests/Resolvers/TvUtilTests.cs +++ b/MediaBrowser.Tests/Resolvers/TvUtilTests.cs @@ -51,6 +51,12 @@ namespace MediaBrowser.Tests.Resolvers Assert.AreEqual(03, TVUtils.GetEpisodeNumberFromFile(@"Season 2009\Elementary - 2009x03x04x15 - Ep Name.ext", true)); Assert.AreEqual(23, TVUtils.GetEpisodeNumberFromFile(@"Season 2009\Elementary - S2009E23-E24-E26 - The Woman.mp4", true)); Assert.AreEqual(23, TVUtils.GetEpisodeNumberFromFile(@"Season 2009\S2009E23-E24-E26 - The Woman.mp4", true)); + + //Without season number + Assert.AreEqual(02, TVUtils.GetEpisodeNumberFromFile(@"Season 1\02 - blah.avi", true)); + Assert.AreEqual(02, TVUtils.GetEpisodeNumberFromFile(@"Season 2\02 - blah 14 blah.avi", true)); + Assert.AreEqual(02, TVUtils.GetEpisodeNumberFromFile(@"Season 1\02 - blah-02 a.avi", true)); + Assert.AreEqual(02, TVUtils.GetEpisodeNumberFromFile(@"Season 2\02.avi", true)); } [TestMethod] @@ -104,6 +110,11 @@ namespace MediaBrowser.Tests.Resolvers Assert.AreEqual(26, TVUtils.GetEndingEpisodeNumberFromFile(@"Season 2009\Elementary - S2009E23-E24-E26 - The Woman.mp4")); Assert.AreEqual(26, TVUtils.GetEndingEpisodeNumberFromFile(@"Season 2009\S2009E23-E24-E26 - The Woman.mp4")); + //Without season number + Assert.AreEqual(null, TVUtils.GetEndingEpisodeNumberFromFile(@"Season 1\02 - blah.avi")); + Assert.AreEqual(null, TVUtils.GetEndingEpisodeNumberFromFile(@"Season 2\02 - blah 14 blah.avi")); + Assert.AreEqual(null, TVUtils.GetEndingEpisodeNumberFromFile(@"Season 1\02 - blah-02 a.avi")); + Assert.AreEqual(null, TVUtils.GetEndingEpisodeNumberFromFile(@"Season 2\02.avi")); } [TestMethod] diff --git a/MediaBrowser.WebDashboard/Api/DashboardService.cs b/MediaBrowser.WebDashboard/Api/DashboardService.cs index 1e867ecef..ee893c613 100644 --- a/MediaBrowser.WebDashboard/Api/DashboardService.cs +++ b/MediaBrowser.WebDashboard/Api/DashboardService.cs @@ -5,7 +5,6 @@ using MediaBrowser.Common.ScheduledTasks; using MediaBrowser.Controller; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Dto; -using MediaBrowser.Controller.IO; using MediaBrowser.Controller.Plugins; using MediaBrowser.Controller.Session; using MediaBrowser.Model.Logging; @@ -13,7 +12,6 @@ using MediaBrowser.Model.Tasks; using ServiceStack.ServiceHost; using System; using System.Collections.Generic; -using System.Diagnostics; using System.IO; using System.Linq; using System.Reflection; @@ -151,7 +149,7 @@ namespace MediaBrowser.WebDashboard.Api return _serverConfigurationManager.Configuration.DashboardSourcePath; } - var runningDirectory = Path.GetDirectoryName(Process.GetCurrentProcess().MainModule.FileName); + var runningDirectory = Path.GetDirectoryName(_serverConfigurationManager.ApplicationPaths.ApplicationPath); return Path.Combine(runningDirectory, "dashboard-ui"); } @@ -430,7 +428,7 @@ namespace MediaBrowser.WebDashboard.Api "http://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js", "http://code.jquery.com/mobile/1.3.2/jquery.mobile-1.3.2.min.js", "scripts/all.js" + versionString, - "thirdparty/jstree1.0fix2/jquery.jstree.js" + "thirdparty/jstree1.0/jquery.jstree.js" }; var tags = files.Select(s => string.Format("<script src=\"{0}\"></script>", s)).ToArray(); @@ -479,9 +477,13 @@ namespace MediaBrowser.WebDashboard.Api "itemgallery.js", "itemlistpage.js", "librarysettings.js", + "livetvchannel.js", "livetvchannels.js", "livetvguide.js", + "livetvrecording.js", "livetvrecordings.js", + "livetvtimer.js", + "livetvtimers.js", "loginpage.js", "logpage.js", "medialibrarypage.js", @@ -495,6 +497,7 @@ namespace MediaBrowser.WebDashboard.Api "moviestudios.js", "movietrailers.js", "musicalbums.js", + "musicalbumartists.js", "musicartists.js", "musicgenres.js", "musicrecommended.js", diff --git a/MediaBrowser.WebDashboard/ApiClient.js b/MediaBrowser.WebDashboard/ApiClient.js index d6f82184c..16e2ae3b9 100644 --- a/MediaBrowser.WebDashboard/ApiClient.js +++ b/MediaBrowser.WebDashboard/ApiClient.js @@ -380,7 +380,22 @@ MediaBrowser.ApiClient = function ($, navigator, JSON, WebSocket, setTimeout, wi self.getLiveTvServices = function (options) { - var url = self.getUrl("/LiveTv/Services", options || {}); + var url = self.getUrl("LiveTv/Services", options || {}); + + return self.ajax({ + type: "GET", + url: url, + dataType: "json" + }); + }; + + self.getLiveTvChannel = function (id) { + + if (!id) { + throw new Error("null id"); + } + + var url = self.getUrl("LiveTv/Channels/" + id); return self.ajax({ type: "GET", @@ -391,7 +406,18 @@ MediaBrowser.ApiClient = function ($, navigator, JSON, WebSocket, setTimeout, wi self.getLiveTvChannels = function (options) { - var url = self.getUrl("/LiveTv/Channels", options || {}); + var url = self.getUrl("LiveTv/Channels", options || {}); + + return self.ajax({ + type: "GET", + url: url, + dataType: "json" + }); + }; + + self.getLiveTvPrograms = function (options) { + + var url = self.getUrl("LiveTv/Programs", options || {}); return self.ajax({ type: "GET", @@ -402,7 +428,22 @@ MediaBrowser.ApiClient = function ($, navigator, JSON, WebSocket, setTimeout, wi self.getLiveTvRecordings = function (options) { - var url = self.getUrl("/LiveTv/Recordings", options || {}); + var url = self.getUrl("LiveTv/Recordings", options || {}); + + return self.ajax({ + type: "GET", + url: url, + dataType: "json" + }); + }; + + self.getLiveTvRecording = function (id) { + + if (!id) { + throw new Error("null id"); + } + + var url = self.getUrl("LiveTv/Recordings/" + id); return self.ajax({ type: "GET", @@ -411,6 +452,74 @@ MediaBrowser.ApiClient = function ($, navigator, JSON, WebSocket, setTimeout, wi }); }; + self.deleteLiveTvRecording = function (id) { + + if (!id) { + throw new Error("null id"); + } + + var url = self.getUrl("LiveTv/Recordings/" + id); + + return self.ajax({ + type: "DELETE", + url: url + }); + }; + + self.cancelLiveTvTimer = function (id) { + + if (!id) { + throw new Error("null id"); + } + + var url = self.getUrl("LiveTv/Timers/" + id); + + return self.ajax({ + type: "DELETE", + url: url + }); + }; + + self.getLiveTvTimers = function (options) { + + var url = self.getUrl("LiveTv/Timers", options || {}); + + return self.ajax({ + type: "GET", + url: url, + dataType: "json" + }); + }; + + self.getLiveTvTimer = function (id) { + + if (!id) { + throw new Error("null id"); + } + + var url = self.getUrl("LiveTv/Timers/" + id); + + return self.ajax({ + type: "GET", + url: url, + dataType: "json" + }); + }; + + self.createLiveTvTimer = function (options) { + + if (!options) { + throw new Error("null options"); + } + + var url = self.getUrl("LiveTv/Timers", options); + + return self.ajax({ + type: "POST", + url: url + }); + }; + /** * Gets the current server status */ @@ -469,6 +578,28 @@ MediaBrowser.ApiClient = function ($, navigator, JSON, WebSocket, setTimeout, wi }); }; + self.getEpisodes = function (itemId, options) { + + var url = self.getUrl("Shows/" + itemId + "/Episodes", options); + + return self.ajax({ + type: "GET", + url: url, + dataType: "json" + }); + }; + + self.getSeasons = function (itemId, options) { + + var url = self.getUrl("Shows/" + itemId + "/Seasons", options); + + return self.ajax({ + type: "GET", + url: url, + dataType: "json" + }); + }; + self.getSimilarMovies = function (itemId, options) { var url = self.getUrl("Movies/" + itemId + "/Similar", options); @@ -902,9 +1033,11 @@ MediaBrowser.ApiClient = function ($, navigator, JSON, WebSocket, setTimeout, wi /** * Gets the server's scheduled tasks */ - self.getScheduledTasks = function () { + self.getScheduledTasks = function (options) { - var url = self.getUrl("ScheduledTasks"); + options = options || {}; + + var url = self.getUrl("ScheduledTasks", options); return self.ajax({ type: "GET", @@ -1225,7 +1358,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"); @@ -1265,6 +1402,19 @@ MediaBrowser.ApiClient = function ($, navigator, JSON, WebSocket, setTimeout, wi }); }; + self.stopActiveEncodings = function () { + + var url = self.getUrl("Videos/ActiveEncodings", { + + deviceId: deviceId + }); + + return self.ajax({ + type: "DELETE", + url: url + }); + }; + self.updateItemImageIndex = function (itemId, itemType, itemName, imageType, imageIndex, newIndex) { if (!imageType) { @@ -1281,7 +1431,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); @@ -1311,7 +1465,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"); @@ -1444,7 +1602,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"); @@ -2307,6 +2469,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 071120ee0..3b2714b2d 100644 --- a/MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj +++ b/MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj @@ -37,21 +37,24 @@ <RunPostBuildEvent>Always</RunPostBuildEvent>
</PropertyGroup>
<ItemGroup>
+ <Reference Include="ServiceStack.Common, Version=3.9.70.0, Culture=neutral, processorArchitecture=MSIL">
+ <SpecificVersion>False</SpecificVersion>
+ <HintPath>..\packages\ServiceStack.Common.3.9.70\lib\net35\ServiceStack.Common.dll</HintPath>
+ </Reference>
+ <Reference Include="ServiceStack.Interfaces, Version=1.0.0.0, Culture=neutral, processorArchitecture=MSIL">
+ <SpecificVersion>False</SpecificVersion>
+ <HintPath>..\packages\ServiceStack.Common.3.9.70\lib\net35\ServiceStack.Interfaces.dll</HintPath>
+ </Reference>
+ <Reference Include="ServiceStack.Text, Version=3.9.70.0, Culture=neutral, processorArchitecture=MSIL">
+ <SpecificVersion>False</SpecificVersion>
+ <HintPath>..\packages\ServiceStack.Text.3.9.70\lib\net35\ServiceStack.Text.dll</HintPath>
+ </Reference>
<Reference Include="System" />
<Reference Include="System.Core" />
<Reference Include="System.Data.DataSetExtensions" />
<Reference Include="Microsoft.CSharp" />
<Reference Include="System.Data" />
<Reference Include="System.Xml" />
- <Reference Include="ServiceStack.Common">
- <HintPath>..\packages\ServiceStack.Common.3.9.62\lib\net35\ServiceStack.Common.dll</HintPath>
- </Reference>
- <Reference Include="ServiceStack.Interfaces">
- <HintPath>..\packages\ServiceStack.Common.3.9.62\lib\net35\ServiceStack.Interfaces.dll</HintPath>
- </Reference>
- <Reference Include="ServiceStack.Text">
- <HintPath>..\packages\ServiceStack.Text.3.9.62\lib\net35\ServiceStack.Text.dll</HintPath>
- </Reference>
</ItemGroup>
<ItemGroup>
<Compile Include="..\SharedVersion.cs">
@@ -80,6 +83,24 @@ </ItemGroup>
<ItemGroup>
<EmbeddedResource Include="ApiClient.js" />
+ <Content Include="dashboard-ui\css\images\editor.png">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\css\images\items\detail\tv.png">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\livetvchannel.html">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\livetvrecording.html">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\livetvtimers.html">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\musicalbumartists.html">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
<Content Include="dashboard-ui\allusersettings.html">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
@@ -269,22 +290,10 @@ <Content Include="dashboard-ui\css\images\userdata\thumbs_up_on.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
- <Content Include="dashboard-ui\css\images\userdata\playedoff.png">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\css\images\userdata\playedon.png">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\css\images\views\games.png">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\css\images\views\movies.png">
+ <Content Include="dashboard-ui\css\images\userdata\checkedoff.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
- <Content Include="dashboard-ui\css\images\views\music.png">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\css\images\views\tvshows.png">
+ <Content Include="dashboard-ui\css\images\userdata\checkedon.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="dashboard-ui\css\librarybrowser.css">
@@ -341,6 +350,24 @@ <Content Include="dashboard-ui\livetvrecordings.html">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
+ <Content Include="dashboard-ui\scripts\livetvchannel.js">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\livetvtimer.html">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\scripts\livetvrecording.js">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\scripts\livetvtimer.js">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\scripts\livetvtimers.js">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\scripts\musicalbumartists.js">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
<Content Include="dashboard-ui\scripts\livetvchannels.js">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
@@ -583,76 +610,76 @@ <Content Include="dashboard-ui\thirdparty\html5slider.js">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
- <Content Include="dashboard-ui\thirdparty\jstree1.0fix2\jquery.jstree.js">
+ <Content Include="dashboard-ui\thirdparty\jstree1.0\jquery.jstree.js">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
- <Content Include="dashboard-ui\thirdparty\jstree1.0fix2\themes\apple\bg.jpg">
+ <Content Include="dashboard-ui\thirdparty\jstree1.0\themes\apple\bg.jpg">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
- <Content Include="dashboard-ui\thirdparty\jstree1.0fix2\themes\apple\d.png">
+ <Content Include="dashboard-ui\thirdparty\jstree1.0\themes\apple\d.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
- <Content Include="dashboard-ui\thirdparty\jstree1.0fix2\themes\apple\dot_for_ie.gif">
+ <Content Include="dashboard-ui\thirdparty\jstree1.0\themes\apple\dot_for_ie.gif">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
- <Content Include="dashboard-ui\thirdparty\jstree1.0fix2\themes\apple\style.css">
+ <Content Include="dashboard-ui\thirdparty\jstree1.0\themes\apple\style.css">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
- <Content Include="dashboard-ui\thirdparty\jstree1.0fix2\themes\apple\throbber.gif">
+ <Content Include="dashboard-ui\thirdparty\jstree1.0\themes\apple\throbber.gif">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
- <Content Include="dashboard-ui\thirdparty\jstree1.0fix2\themes\classic\d.gif">
+ <Content Include="dashboard-ui\thirdparty\jstree1.0\themes\classic\d.gif">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
- <Content Include="dashboard-ui\thirdparty\jstree1.0fix2\themes\classic\d.png">
+ <Content Include="dashboard-ui\thirdparty\jstree1.0\themes\classic\d.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
- <Content Include="dashboard-ui\thirdparty\jstree1.0fix2\themes\classic\dot_for_ie.gif">
+ <Content Include="dashboard-ui\thirdparty\jstree1.0\themes\classic\dot_for_ie.gif">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
- <Content Include="dashboard-ui\thirdparty\jstree1.0fix2\themes\classic\style.css">
+ <Content Include="dashboard-ui\thirdparty\jstree1.0\themes\classic\style.css">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
- <Content Include="dashboard-ui\thirdparty\jstree1.0fix2\themes\classic\throbber.gif">
+ <Content Include="dashboard-ui\thirdparty\jstree1.0\themes\classic\throbber.gif">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
- <Content Include="dashboard-ui\thirdparty\jstree1.0fix2\themes\default-rtl\d.gif">
+ <Content Include="dashboard-ui\thirdparty\jstree1.0\themes\default-rtl\d.gif">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
- <Content Include="dashboard-ui\thirdparty\jstree1.0fix2\themes\default-rtl\d.png">
+ <Content Include="dashboard-ui\thirdparty\jstree1.0\themes\default-rtl\d.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
- <Content Include="dashboard-ui\thirdparty\jstree1.0fix2\themes\default-rtl\dots.gif">
+ <Content Include="dashboard-ui\thirdparty\jstree1.0\themes\default-rtl\dots.gif">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
- <Content Include="dashboard-ui\thirdparty\jstree1.0fix2\themes\default-rtl\style.css">
+ <Content Include="dashboard-ui\thirdparty\jstree1.0\themes\default-rtl\style.css">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
- <Content Include="dashboard-ui\thirdparty\jstree1.0fix2\themes\default-rtl\throbber.gif">
+ <Content Include="dashboard-ui\thirdparty\jstree1.0\themes\default-rtl\throbber.gif">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
- <Content Include="dashboard-ui\thirdparty\jstree1.0fix2\themes\default\d.gif">
+ <Content Include="dashboard-ui\thirdparty\jstree1.0\themes\default\d.gif">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
- <Content Include="dashboard-ui\thirdparty\jstree1.0fix2\themes\default\d.png">
+ <Content Include="dashboard-ui\thirdparty\jstree1.0\themes\default\d.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
- <Content Include="dashboard-ui\thirdparty\jstree1.0fix2\themes\default\style.css">
+ <Content Include="dashboard-ui\thirdparty\jstree1.0\themes\default\style.css">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
- <Content Include="dashboard-ui\thirdparty\jstree1.0fix2\themes\default\throbber.gif">
+ <Content Include="dashboard-ui\thirdparty\jstree1.0\themes\default\throbber.gif">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
- <Content Include="dashboard-ui\thirdparty\jstree1.0fix2\themes\mb3\d.gif">
+ <Content Include="dashboard-ui\thirdparty\jstree1.0\themes\mb3\d.gif">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
- <Content Include="dashboard-ui\thirdparty\jstree1.0fix2\themes\mb3\d.png">
+ <Content Include="dashboard-ui\thirdparty\jstree1.0\themes\mb3\d.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
- <Content Include="dashboard-ui\thirdparty\jstree1.0fix2\themes\mb3\style.css">
+ <Content Include="dashboard-ui\thirdparty\jstree1.0\themes\mb3\style.css">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
- <Content Include="dashboard-ui\thirdparty\jstree1.0fix2\themes\mb3\throbber.gif">
+ <Content Include="dashboard-ui\thirdparty\jstree1.0\themes\mb3\throbber.gif">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="dashboard-ui\tvgenres.html">
@@ -1191,9 +1218,8 @@ </Content>
</ItemGroup>
<ItemGroup>
- <None Include="packages.config" />
+ <EmbeddedResource Include="packages.config" />
</ItemGroup>
- <ItemGroup />
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<PropertyGroup>
<PostBuildEvent>
diff --git a/MediaBrowser.WebDashboard/packages.config b/MediaBrowser.WebDashboard/packages.config index d431a43a5..f13a431e3 100644 --- a/MediaBrowser.WebDashboard/packages.config +++ b/MediaBrowser.WebDashboard/packages.config @@ -1,6 +1,6 @@ <?xml version="1.0" encoding="utf-8"?> <packages> - <package id="MediaBrowser.ApiClient.Javascript" version="3.0.193" targetFramework="net45" /> - <package id="ServiceStack.Common" version="3.9.62" targetFramework="net45" /> - <package id="ServiceStack.Text" version="3.9.62" targetFramework="net45" /> + <package id="MediaBrowser.ApiClient.Javascript" version="3.0.204" targetFramework="net45" /> + <package id="ServiceStack.Common" version="3.9.70" targetFramework="net45" /> + <package id="ServiceStack.Text" version="3.9.70" targetFramework="net45" /> </packages>
\ No newline at end of file diff --git a/MediaBrowser.sln b/MediaBrowser.sln index 0c5360b49..744debbcd 100644 --- a/MediaBrowser.sln +++ b/MediaBrowser.sln @@ -237,7 +237,4 @@ Global GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection - GlobalSection(Performance) = preSolution - HasPerformanceSessions = true - EndGlobalSection EndGlobal diff --git a/Nuget/MediaBrowser.Common.Internal.nuspec b/Nuget/MediaBrowser.Common.Internal.nuspec index 4e6f0903c..c3998aed7 100644 --- a/Nuget/MediaBrowser.Common.Internal.nuspec +++ b/Nuget/MediaBrowser.Common.Internal.nuspec @@ -2,7 +2,7 @@ <package xmlns="http://schemas.microsoft.com/packaging/2011/08/nuspec.xsd"> <metadata> <id>MediaBrowser.Common.Internal</id> - <version>3.0.243</version> + <version>3.0.257</version> <title>MediaBrowser.Common.Internal</title> <authors>Luke</authors> <owners>ebr,Luke,scottisafool</owners> @@ -12,7 +12,7 @@ <description>Contains common components shared by Media Browser Theater and Media Browser Server. Not intended for plugin developer consumption.</description> <copyright>Copyright © Media Browser 2013</copyright> <dependencies> - <dependency id="MediaBrowser.Common" version="3.0.243" /> + <dependency id="MediaBrowser.Common" version="3.0.257" /> <dependency id="NLog" version="2.1.0" /> <dependency id="ServiceStack.Text" version="3.9.58" /> <dependency id="SimpleInjector" version="2.3.6" /> diff --git a/Nuget/MediaBrowser.Common.nuspec b/Nuget/MediaBrowser.Common.nuspec index e7c30692c..aea3230fd 100644 --- a/Nuget/MediaBrowser.Common.nuspec +++ b/Nuget/MediaBrowser.Common.nuspec @@ -2,7 +2,7 @@ <package xmlns="http://schemas.microsoft.com/packaging/2011/08/nuspec.xsd"> <metadata> <id>MediaBrowser.Common</id> - <version>3.0.243</version> + <version>3.0.257</version> <title>MediaBrowser.Common</title> <authors>Media Browser Team</authors> <owners>ebr,Luke,scottisafool</owners> diff --git a/Nuget/MediaBrowser.Server.Core.nuspec b/Nuget/MediaBrowser.Server.Core.nuspec index 9cd0bf547..2a80896c0 100644 --- a/Nuget/MediaBrowser.Server.Core.nuspec +++ b/Nuget/MediaBrowser.Server.Core.nuspec @@ -2,7 +2,7 @@ <package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd"> <metadata> <id>MediaBrowser.Server.Core</id> - <version>3.0.243</version> + <version>3.0.257</version> <title>Media Browser.Server.Core</title> <authors>Media Browser Team</authors> <owners>ebr,Luke,scottisafool</owners> @@ -12,7 +12,7 @@ <description>Contains core components required to build plugins for Media Browser Server.</description> <copyright>Copyright © Media Browser 2013</copyright> <dependencies> - <dependency id="MediaBrowser.Common" version="3.0.243" /> + <dependency id="MediaBrowser.Common" version="3.0.257" /> </dependencies> </metadata> <files> @@ -7,7 +7,7 @@ It features a REST-based api with built-in documention to facilitate client deve We have several client apps released and in production: -- Android +- [Android](https://play.google.com/store/apps/details?id=com.mb.android "Android") - Html5 - [iOS](https://itunes.apple.com/us/app/media-browser-for-ios/id705058087 "iOS") - [Media Portal](http://www.team-mediaportal.com/ "Media Portal") @@ -15,7 +15,7 @@ We have several client apps released and in production: - Windows 7/8 Desktop - Windows Media Center - [Windows Phone](http://www.windowsphone.com/s?appid=f4971ed9-f651-4bf6-84bb-94fd98613b86 "Windows Phone") -- Windows RT +- [Windows 8](http://apps.microsoft.com/windows/en-us/app/media-browser/ad55a2f0-9897-47bd-8944-bed3aefd5d06 "Windows 8.1") #### [Now with 60+ active contributors!](https://github.com/MediaBrowser/MediaBrowser/blob/master/CONTRIBUTORS.md "Now with 60+ active contributors!") @@ -47,7 +47,7 @@ Beta: 3.0.5028.39800<br/>    - +    |
