diff options
| author | Eric Reed <ebr@mediabrowser3.com> | 2013-12-04 15:07:56 -0500 |
|---|---|---|
| committer | Eric Reed <ebr@mediabrowser3.com> | 2013-12-04 15:07:56 -0500 |
| commit | 6819be81601f6a95a60ce2735474ae0015d19bff (patch) | |
| tree | 7e2743455e53d4a028fae789f2fc74a7c5ae87b9 | |
| parent | 190be6311fbdf3a73f9c8e330f44edafe7764284 (diff) | |
| parent | cb882a4b48e9cf03cd363c54d93338ad62153e7e (diff) | |
Merge branch 'master' of https://github.com/MediaBrowser/MediaBrowser
199 files changed, 5435 insertions, 2111 deletions
diff --git a/MediaBrowser.Api/BaseApiService.cs b/MediaBrowser.Api/BaseApiService.cs index d01e96a5ad..ee0721d5eb 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 6c1aa04cfb..4e8864644e 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 0000000000..36303c889d --- /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 632230be3d..27881d12ba 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 260d938604..8fca3f338f 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 6e1dbc08b9..90fe111292 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 f3306bb63c..198bec1a0d 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 582eb6f497..cf62e42ba7 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 72e5eee92f..0e40db58cd 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 7a9c6c8ac2..706117fc26 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 4a767b0889..9ad0703ae6 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 080ab9c7ed..dfd18ab154 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 a31b6af0e2..1f0853e08b 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 0ef6d13ccc..97b808b867 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 2674529e51..d17a38e073 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 20634301a3..c143635bfa 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 f87434e976..113df4e38c 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 587965b3f8..a160ce6665 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 6cae379d2a..68ebd60c54 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 ca626116a1..4dd3d744fa 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 eba36d8568..6aa87e499d 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 1c7722e587..3a7ea5ddd6 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 885a4a3e67..09b5ef09fe 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 6b7980b1f7..e9fc524680 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 c9fec81005..e9a27e8ad8 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 ee22b7baa2..e8f4d51eb1 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 bee5e5dc48..ba68d1f5ad 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 33f7079df3..8af6ef6c6e 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 0d6ba5c1da..214ed106d9 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 9e48f3b3e9..9487855756 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 e04cadfc5a..6d886bc696 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 4a6b9255c1..9c2412b227 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 56641296f6..0581343d39 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 f2fe488309..269ac0e561 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 d2446ce46d..d3bf033029 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 f4d759a4d1..a9499dedda 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 93348d4ca0..54d6665e23 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 351e96c7d5..2ee4fb4b50 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 f316509869..39148166bc 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 6969b43c54..7411e313cd 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 947ee11227..0000000000 --- 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 d791c92ae9..d5572b9a5e 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 ec2995fb2f..b54e14f2da 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 a6178536c6..4f7889f975 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 d189f4e71b..2a64bd3a41 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 8dbc981938..e8b5831813 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 e8374c2743..e15b7e4c9d 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 0c877782e6..ffe62ba03f 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 71fa057206..0fa49639bf 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 0000000000..5aecf4eac1 --- /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 0000000000..f697715c16 --- /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 0000000000..47779064b4 --- /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 7284bf101d..1e83c7466e 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 35c11ef5ca..57e4a35d3f 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 e52ece502b..4a6221ee98 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 eef348f61c..473ea4996d 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 207f76efda..68ad4630a5 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 e5cf48ad08..243861da76 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 bbe96a88b9..7bc17549f3 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 c68ba0ad1f..e9f250d2a4 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 97a09b7b55..78e0b8bc4a 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 02ea50c6b5..b4a3fc811f 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 26814ad40b..77efe8e8c1 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 e900dd77e9..9b02571b00 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 d0f4577183..cd50a1c60c 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 0a89c0df88..338edd5684 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 7a5722746e..65308bd100 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 0000000000..8097cea1de --- /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 721c1e40a8..27fc596303 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 62bfdf3e58..4e73fc109f 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 5c019ae8c4..a6c60d468d 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 0000000000..d454a1ef8d --- /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 0000000000..cf5cdb94c9 --- /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 0000000000..2c8e8cb464 --- /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 0000000000..44594882cd --- /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 0000000000..3df0b8ccab --- /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 f2837a1f1d..2beb3588ed 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 9fdbbf3b7e..617e4fd819 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 0000000000..726f0e60e3 --- /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 726df1eb54..67888aa46d 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 471db6f25d..cfe4a5462f 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 3f66f195e6..16b3543ebd 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 bfa7517561..20f60586c1 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 ae801e1962..31b6d2da0e 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 d2bebac182..0000000000 --- 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 8daaa75cad..020771e5ee 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 0000000000..9fe74502fd --- /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 6bf0e383fa..0000000000 --- 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 0000000000..6884d355d1 --- /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 0000000000..ce0639aa0e --- /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 0000000000..86e82b562d --- /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 e7a91f4d55..0820c7785e 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 0000000000..08a7cfb0c2 --- /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 55a30a4b38..9ebc2314d4 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 6ff6fcf736..103e583aed 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 0000000000..589b46433a --- /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 14c946ba18..6602e031f5 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 12dfa96261..57e09d724c 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 6a29827a91..1ecc1de6c4 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 9491139dbc..6a17ad133a 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 dee4fea7fc..a7d500303d 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 f1260b1dad..b3c4d6d230 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 449de7450d..8459fceefb 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 65eee0f3a6..f278a90893 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 0000000000..b166750b56 --- /dev/null +++ b/MediaBrowser.Providers/LiveTv/ChannelProviderFromXml.cs @@ -0,0 +1,104 @@ +using MediaBrowser.Common.IO; +using MediaBrowser.Controller.Configuration; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.LiveTv; +using MediaBrowser.Controller.Providers; +using MediaBrowser.Model.Logging; +using System; +using System.IO; +using System.Threading; +using System.Threading.Tasks; + +namespace MediaBrowser.Providers.LiveTv +{ + class ChannelProviderFromXml : BaseMetadataProvider + { + internal static ChannelProviderFromXml Current { get; private set; } + private readonly IFileSystem _fileSystem; + + public ChannelProviderFromXml(ILogManager logManager, IServerConfigurationManager configurationManager, IFileSystem fileSystem) + : base(logManager, configurationManager) + { + _fileSystem = fileSystem; + Current = this; + } + + /// <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 00924675a7..b5b41c6d3d 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 f61057fe6c..683dd42310 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 a843103106..5f285e6d83 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 7aea484fd8..b577420428 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 39908c3eef..be195d6d2e 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 cc22939689..aff71c6dbb 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 ecfae9d5c0..b381de3322 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 cc6e07d622..51a2362c05 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 50e04a9afd..f8fb133c60 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 6550396e51..d881859c6e 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 2264ccd3fb..489b0ad09d 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 5c14a2f56c..0000000000 --- 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 9606279f7d..da49d36ed6 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 b5a3b92f91..7069e65ef4 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 8cc49ac393..b7e8463531 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 f67e2b5091..a49de9a29e 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 5d18f16ffa..0000000000 --- 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 6df21b61d9..1b0a80d9a9 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 6d9a16e874..ddf2121792 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 cd40052235..a2ae597d78 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 26ac68c3d4..0000000000 --- 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 3113d2541f..2a7977fa62 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 800fdc4383..8e2aed50ed 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 6ff891f608..cdb07d3d7f 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 0b1b95b1e5..72e8c6f6ba 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 55331db936..81f092a46f 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 0000000000..c6ac220a9e --- /dev/null +++ b/MediaBrowser.Providers/Savers/ChannelXmlSaver.cs @@ -0,0 +1,74 @@ +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.LiveTv; +using MediaBrowser.Providers.LiveTv; +using System; +using System.Collections.Generic; +using System.IO; +using System.Text; +using System.Threading; + +namespace MediaBrowser.Providers.Savers +{ + /// <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 8c19e2c283..319f6c7d0a 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 a974fc13e9..1a287a918f 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 69276e0b8e..186941988c 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 fc8f55ae14..592c5dcac3 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 b6fdaaa831..7ddf421b9b 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 2a5b16bef4..bf52ca349c 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 1f20140c50..037e0b5bc5 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 4bc7c3c4f5..8ef04ed118 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 7ad564d0fe..56b06f4906 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 a22f4f1c32..29e191a594 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 0000000000..5130202139 --- /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 219b76cd53..06768f353b 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 1f0e7d1e1e..f298c2d4d1 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 e6942fae6a..5a5a2dd040 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 20728a30ca..904b6799b4 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 ffb3512220..870a14bd80 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 6a2df70b17..3b6a5ea25d 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 d5fccc6fd1..b194b2e946 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 620bcaee4a..e32fcd627b 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 0f87b9d337..03e29dd38f 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 0a6a72fc10..693594a20b 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 7d46d7060d..40ef5304c6 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 d21a123c07..c7af7a238f 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 0670e1a851..cb1253df07 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 166f557cf0..57a6a612bc 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 cfc7f4310d..0104b2b7ec 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 02c7a94b4b..0f4ff562ef 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 0000000000..322948bade --- /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 05bac17c3b..4fd1f0e43b 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 0000000000..00bf9e55ba --- /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 0000000000..5a110648cd --- /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 ac451e1ebd..3bfbdea3ea 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 b8874fb548..2224c657ff 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 ff11b9a2be..e2192535c8 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 fb3c5aec86..db538f8dd0 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 0000000000..76971342a0 --- /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 8e24bc52d6..e35ba00f2e 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 d2dac65499..87a7325c63 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 7731e59d2b..b3fd8a023d 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 51f39a02f2..605f4d1af4 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 889658459c..6c9c5534d6 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 e9d7912a16..6caa27ac39 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 13d2932cbc..8567e400cd 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 b12e1322a0..85b849a217 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 b6f67410a0..a13875674d 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 eeeedfe362..488dbc1ae8 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 0fcc069596..49f983a723 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 900169c707..e32dad8d7b 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 0fceab0608..0000000000 --- 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 057a2456f5..d423017fc9 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 03819ff912..f512381528 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 e8af0a13e7..1f329446ec 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 d3eed5c484..349cbbc619 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 f24283e70d..5f05bc7875 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 57f00ba03b..0000000000 --- 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 0893a1b38a..5d7c3265f3 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 f7a87c9d47..8f5dcc034a 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 1aaff6384b..69a6be3291 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 1e867eceff..ee893c613e 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 d6f82184c1..16e2ae3b96 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 071120ee06..3b2714b2d9 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 d431a43a56..f13a431e3a 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 0c5360b494..744debbcd2 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 4e6f0903c9..c3998aed7a 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 e7c30692ca..aea3230fdb 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 9cd0bf5471..2a80896c06 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/>    - +    |
