diff --git a/MediaBrowser.Api/Movies/CollectionService.cs b/MediaBrowser.Api/Movies/CollectionService.cs index f09992e00f..b9e3888fda 100644 --- a/MediaBrowser.Api/Movies/CollectionService.cs +++ b/MediaBrowser.Api/Movies/CollectionService.cs @@ -1,13 +1,16 @@ using MediaBrowser.Controller.Collections; +using MediaBrowser.Controller.Dto; +using MediaBrowser.Model.Querying; using ServiceStack; using System; +using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; namespace MediaBrowser.Api.Movies { [Route("/Collections", "POST", Summary = "Creates a new collection")] - public class CreateCollection : IReturnVoid + public class CreateCollection : IReturn { [ApiMember(Name = "IsLocked", Description = "Whether or not to lock the new collection.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "POST")] public bool IsLocked { get; set; } @@ -17,6 +20,9 @@ namespace MediaBrowser.Api.Movies [ApiMember(Name = "ParentId", Description = "Optional - create the collection within a specific folder", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "POST")] public Guid? ParentId { get; set; } + + [ApiMember(Name = "Ids", Description = "Item Ids to add to the collection", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "POST", AllowMultiple = true)] + public string Ids { get; set; } } [Route("/Collections/{Id}/Items", "POST", Summary = "Adds items to a collection")] @@ -42,22 +48,32 @@ namespace MediaBrowser.Api.Movies public class CollectionService : BaseApiService { private readonly ICollectionManager _collectionManager; + private readonly IDtoService _dtoService; - public CollectionService(ICollectionManager collectionManager) + public CollectionService(ICollectionManager collectionManager, IDtoService dtoService) { _collectionManager = collectionManager; + _dtoService = dtoService; } - public void Post(CreateCollection request) + public object Post(CreateCollection request) { var task = _collectionManager.CreateCollection(new CollectionCreationOptions { IsLocked = request.IsLocked, Name = request.Name, - ParentId = request.ParentId + ParentId = request.ParentId, + ItemIdList = (request.Ids ?? string.Empty).Split(',').Where(i => !string.IsNullOrWhiteSpace(i)).Select(i => new Guid(i)).ToList() }); - Task.WaitAll(task); + var item = task.Result; + + var dto = _dtoService.GetBaseItemDto(item, new List()); + + return ToOptimizedResult(new CollectionCreationResult + { + Id = dto.Id + }); } public void Post(AddToCollection request) @@ -74,4 +90,9 @@ namespace MediaBrowser.Api.Movies Task.WaitAll(task); } } + + public class CollectionCreationResult + { + public string Id { get; set; } + } } diff --git a/MediaBrowser.Api/Playback/BaseStreamingService.cs b/MediaBrowser.Api/Playback/BaseStreamingService.cs index 9f7c1a6c4f..89d50e9a3d 100644 --- a/MediaBrowser.Api/Playback/BaseStreamingService.cs +++ b/MediaBrowser.Api/Playback/BaseStreamingService.cs @@ -1065,8 +1065,12 @@ namespace MediaBrowser.Api.Playback /// System.String. private string GetUserAgentParam(StreamState state) { - string useragent; - state.RemoteHttpHeaders.TryGetValue("User-Agent", out useragent); + string useragent = null; + + if (state.RemoteHttpHeaders != null) + { + state.RemoteHttpHeaders.TryGetValue("User-Agent", out useragent); + } if (string.IsNullOrWhiteSpace(useragent)) { diff --git a/MediaBrowser.Common.Implementations/HttpClientManager/HttpClientManager.cs b/MediaBrowser.Common.Implementations/HttpClientManager/HttpClientManager.cs index a4337bab46..5efb39cbc0 100644 --- a/MediaBrowser.Common.Implementations/HttpClientManager/HttpClientManager.cs +++ b/MediaBrowser.Common.Implementations/HttpClientManager/HttpClientManager.cs @@ -128,6 +128,11 @@ namespace MediaBrowser.Common.Implementations.HttpClientManager request.Host = options.Host; } + if (!string.IsNullOrEmpty(options.Referer)) + { + request.Referer = options.Referer; + } + #if !__MonoCS__ if (options.EnableKeepAlive) { diff --git a/MediaBrowser.Common/Net/HttpRequestOptions.cs b/MediaBrowser.Common/Net/HttpRequestOptions.cs index c3601f8e99..3cba84334d 100644 --- a/MediaBrowser.Common/Net/HttpRequestOptions.cs +++ b/MediaBrowser.Common/Net/HttpRequestOptions.cs @@ -52,6 +52,12 @@ namespace MediaBrowser.Common.Net } } + /// + /// Gets or sets the referrer. + /// + /// The referrer. + public string Referer { get; set; } + /// /// Gets or sets the host. /// diff --git a/MediaBrowser.Controller/Entities/CollectionFolder.cs b/MediaBrowser.Controller/Entities/CollectionFolder.cs index 8eecebdbc3..4b90741c09 100644 --- a/MediaBrowser.Controller/Entities/CollectionFolder.cs +++ b/MediaBrowser.Controller/Entities/CollectionFolder.cs @@ -121,11 +121,16 @@ namespace MediaBrowser.Controller.Entities /// The refresh options. /// The directory service. /// Task. - protected override Task ValidateChildrenInternal(IProgress progress, CancellationToken cancellationToken, bool recursive, bool refreshChildMetadata, MetadataRefreshOptions refreshOptions, IDirectoryService directoryService) + protected override async Task ValidateChildrenInternal(IProgress progress, CancellationToken cancellationToken, bool recursive, bool refreshChildMetadata, MetadataRefreshOptions refreshOptions, IDirectoryService directoryService) { + var list = PhysicalLocationsList.ToList(); + CreateResolveArgs(directoryService); - return NullTaskResult; + if (!list.SequenceEqual(PhysicalLocationsList)) + { + await UpdateToRepository(ItemUpdateType.MetadataImport, cancellationToken).ConfigureAwait(false); + } } /// @@ -164,8 +169,7 @@ namespace MediaBrowser.Controller.Entities LibraryManager.RootFolder.Children .OfType() .Where(i => i.Path != null && PhysicalLocations.Contains(i.Path, StringComparer.OrdinalIgnoreCase)) - .SelectMany(c => c.Children) - .ToList(); + .SelectMany(c => c.Children); } } } diff --git a/MediaBrowser.Controller/Entities/Folder.cs b/MediaBrowser.Controller/Entities/Folder.cs index e4d9355d28..5b5747b8f1 100644 --- a/MediaBrowser.Controller/Entities/Folder.cs +++ b/MediaBrowser.Controller/Entities/Folder.cs @@ -445,6 +445,11 @@ namespace MediaBrowser.Controller.Entities cancellationToken.ThrowIfCancellationRequested(); + if (this is UserRootFolder) + { + var b = true; + } + foreach (var child in nonCachedChildren) { BaseItem currentChild; diff --git a/MediaBrowser.Controller/Entities/UserRootFolder.cs b/MediaBrowser.Controller/Entities/UserRootFolder.cs index 0290fa39ad..e5a8135c2d 100644 --- a/MediaBrowser.Controller/Entities/UserRootFolder.cs +++ b/MediaBrowser.Controller/Entities/UserRootFolder.cs @@ -2,6 +2,8 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Threading; +using System.Threading.Tasks; namespace MediaBrowser.Controller.Entities { @@ -32,5 +34,22 @@ namespace MediaBrowser.Controller.Entities return hasChanges; } + + protected override async Task ValidateChildrenInternal(IProgress progress, CancellationToken cancellationToken, bool recursive, bool refreshChildMetadata, MetadataRefreshOptions refreshOptions, IDirectoryService directoryService) + { + await base.ValidateChildrenInternal(progress, cancellationToken, recursive, refreshChildMetadata, refreshOptions, directoryService) + .ConfigureAwait(false); + + // Not the best way to handle this, but it solves an issue + // CollectionFolders aren't always getting saved after changes + // This means that grabbing the item by Id may end up returning the old one + // Fix is in two places - make sure the folder gets saved + // And here to remedy it for affected users. + // In theory this can be removed eventually. + foreach (var item in Children) + { + LibraryManager.RegisterItem(item); + } + } } } diff --git a/MediaBrowser.Dlna/Ssdp/SsdpHandler.cs b/MediaBrowser.Dlna/Ssdp/SsdpHandler.cs index 083ec8c82e..231024a0a4 100644 --- a/MediaBrowser.Dlna/Ssdp/SsdpHandler.cs +++ b/MediaBrowser.Dlna/Ssdp/SsdpHandler.cs @@ -147,7 +147,10 @@ namespace MediaBrowser.Dlna.Ssdp SendDatagram(header, values, endpoint, null); - _logger.Debug("{1} - Responded to a {0} request to {2}", d.Type, endpoint, d.Address.ToString()); + if (_config.Configuration.DlnaOptions.EnableDebugLogging) + { + _logger.Debug("{1} - Responded to a {0} request to {2}", d.Type, endpoint, d.Address.ToString()); + } } } } diff --git a/MediaBrowser.Providers/Manager/ProviderManager.cs b/MediaBrowser.Providers/Manager/ProviderManager.cs index 414d7f064d..864435e404 100644 --- a/MediaBrowser.Providers/Manager/ProviderManager.cs +++ b/MediaBrowser.Providers/Manager/ProviderManager.cs @@ -611,13 +611,15 @@ namespace MediaBrowser.Providers.Manager try { + var isEnabledFor = saver.IsEnabledFor(item, updateType); + if (!includeDisabled) { if (options.DisabledMetadataSavers.Contains(saver.Name, StringComparer.OrdinalIgnoreCase)) { return false; } - + if (!item.IsSaveLocalMetadataEnabled()) { if (updateType >= ItemUpdateType.MetadataEdit) @@ -626,7 +628,7 @@ namespace MediaBrowser.Providers.Manager // Manual edit occurred // Even if save local is off, save locally anyway if the metadata file already exists - if (fileSaver == null || !File.Exists(fileSaver.GetSavePath(item))) + if (fileSaver == null || !isEnabledFor || !File.Exists(fileSaver.GetSavePath(item))) { return false; } @@ -640,7 +642,7 @@ namespace MediaBrowser.Providers.Manager } } - return saver.IsEnabledFor(item, updateType); + return isEnabledFor; } catch (Exception ex) { diff --git a/MediaBrowser.Server.Implementations/Library/LibraryManager.cs b/MediaBrowser.Server.Implementations/Library/LibraryManager.cs index 227e7bdfc0..6ba2ac9502 100644 --- a/MediaBrowser.Server.Implementations/Library/LibraryManager.cs +++ b/MediaBrowser.Server.Implementations/Library/LibraryManager.cs @@ -674,14 +674,19 @@ namespace MediaBrowser.Server.Implementations.Library { var folder = child.GetFolder(); - if (folder.Id == Guid.Empty) + if (folder != null) { - folder.Id = (folder.Path ?? folder.GetType().Name).GetMBId(folder.GetType()); - } + if (folder.Id == Guid.Empty) + { + folder.Id = (folder.Path ?? folder.GetType().Name).GetMBId(folder.GetType()); + } - rootFolder.AddVirtualChild(folder); + folder = GetItemById(folder.Id) as BasePluginFolder ?? folder; - RegisterItem(folder); + rootFolder.AddVirtualChild(folder); + + RegisterItem(folder); + } } return rootFolder; diff --git a/MediaBrowser.Server.Implementations/Localization/JavaScript/javascript.json b/MediaBrowser.Server.Implementations/Localization/JavaScript/javascript.json index cf827d26de..330fca0829 100644 --- a/MediaBrowser.Server.Implementations/Localization/JavaScript/javascript.json +++ b/MediaBrowser.Server.Implementations/Localization/JavaScript/javascript.json @@ -115,7 +115,8 @@ "HeaderSplitMedia": "Split Media Apart", "MessageConfirmSplitMedia": "Are you sure you wish to split the media sources into separate items?", "HeaderError": "Error", - "MessagePleaseSelectItemsToGroup": "Please select two or more items to group together.", + "MessagePleaseSelectOneItem": "Please select at least one item.", + "MessagePleaseSelectTwoItems": "Please select at least two items.", "MessageTheFollowingItemsWillBeGrouped": "The following titles will be grouped into one item:", "MessageConfirmItemGrouping": "Media Browser clients will automatically choose the optimal version to play based on device and network performance. Are you sure you wish to continue?", "HeaderResume": "Resume", @@ -138,5 +139,6 @@ "HeaderSelectImagesByNamePathHelp": "Browse or enter the path to your items by name folder. The folder must be writeable.", "HeaderSelectMetadataPathHelp": "Browse or enter the path you'd like to store metadata within. The folder must be writeable.", "HeaderSelectChannelDownloadPath": "Select Channel Download Path", - "HeaderSelectChannelDownloadPathHelp": "Browse or enter the path to use for storing channel cache files. The folder must be writeable." + "HeaderSelectChannelDownloadPathHelp": "Browse or enter the path to use for storing channel cache files. The folder must be writeable.", + "OptionNewCollection": "New..." } \ No newline at end of file diff --git a/MediaBrowser.Server.Implementations/Localization/Server/server.json b/MediaBrowser.Server.Implementations/Localization/Server/server.json index b2ebcdb9fc..465896eb91 100644 --- a/MediaBrowser.Server.Implementations/Localization/Server/server.json +++ b/MediaBrowser.Server.Implementations/Localization/Server/server.json @@ -234,6 +234,7 @@ "ButtonSelect": "Select", "ButtonSearch": "Search", "ButtonGroupVersions": "Group Versions", + "ButtonAddToCollection": "Add to Collection", "PismoMessage": "Utilizing Pismo File Mount through a donated license.", "TangibleSoftwareMessage": "Utilizing Tangible Solutions Java/C# converters through a donated license.", "HeaderCredits": "Credits", @@ -452,6 +453,8 @@ "LabelPreferredDisplayLanguageHelp": "Translating Media Browser is an ongoing project and is not yet complete.", "LabelReadHowYouCanContribute": "Read about how you can contribute.", "HeaderNewCollection": "New Collection", + "HeaderAddToCollection": "Add to Collection", + "ButtonSubmit": "Submit", "NewCollectionNameExample": "Example: Star Wars Collection", "OptionSearchForInternetMetadata": "Search the internet for artwork and metadata", "ButtonCreate": "Create", @@ -807,5 +810,6 @@ "LabelChannelDownloadPathHelp": "Specify a custom download path if desired. Leave empty to download to an internal program data folder.", "LabelChannelDownloadAge": "Delete content after: (days)", "LabelChannelDownloadAgeHelp": "Downloaded content older than this will be deleted. It will remain playable via internet streaming.", - "ChannelSettingsFormHelp": "Install channels such as Trailers and Vimeo in the plugin catalog." + "ChannelSettingsFormHelp": "Install channels such as Trailers and Vimeo in the plugin catalog.", + "LabelSelectCollection": "Select collection:" } \ No newline at end of file diff --git a/MediaBrowser.Server.Implementations/Session/SessionManager.cs b/MediaBrowser.Server.Implementations/Session/SessionManager.cs index ec3e636aec..cbf14076ba 100644 --- a/MediaBrowser.Server.Implementations/Session/SessionManager.cs +++ b/MediaBrowser.Server.Implementations/Session/SessionManager.cs @@ -1,4 +1,5 @@ -using MediaBrowser.Common.Events; +using System.IO; +using MediaBrowser.Common.Events; using MediaBrowser.Common.Extensions; using MediaBrowser.Common.Net; using MediaBrowser.Controller; @@ -142,6 +143,16 @@ namespace MediaBrowser.Server.Implementations.Session SessionInfo = info }, _logger); + + if (!string.IsNullOrWhiteSpace(info.DeviceId)) + { + var capabilities = GetSavedCapabilities(info.DeviceId); + + if (capabilities != null) + { + ReportCapabilities(info, capabilities, false); + } + } } private async void OnSessionEnded(SessionInfo info) @@ -237,7 +248,7 @@ namespace MediaBrowser.Server.Implementations.Session { return session; } - + // Save this directly. No need to fire off all the events for this. await _userRepository.SaveUser(user, CancellationToken.None).ConfigureAwait(false); @@ -1148,6 +1159,13 @@ namespace MediaBrowser.Server.Implementations.Session { var session = GetSession(sessionId); + ReportCapabilities(session, capabilities, true); + } + + private async void ReportCapabilities(SessionInfo session, + SessionCapabilities capabilities, + bool saveCapabilities) + { session.PlayableMediaTypes = capabilities.PlayableMediaTypes; session.SupportedCommands = capabilities.SupportedCommands; @@ -1168,6 +1186,59 @@ namespace MediaBrowser.Server.Implementations.Session SessionInfo = session }, _logger); + + if (saveCapabilities) + { + await SaveCapabilities(session.DeviceId, capabilities).ConfigureAwait(false); + } + } + + private string GetCapabilitiesFilePath(string deviceId) + { + var filename = deviceId.GetMD5().ToString("N") + ".json"; + + return Path.Combine(_configurationManager.ApplicationPaths.CachePath, "devices", filename); + } + + private SessionCapabilities GetSavedCapabilities(string deviceId) + { + var path = GetCapabilitiesFilePath(deviceId); + + try + { + return _jsonSerializer.DeserializeFromFile(path); + } + catch (DirectoryNotFoundException) + { + return null; + } + catch (FileNotFoundException) + { + return null; + } + catch (Exception ex) + { + _logger.ErrorException("Error getting saved capabilities", ex); + return null; + } + } + + private readonly SemaphoreSlim _capabilitiesLock = new SemaphoreSlim(1, 1); + private async Task SaveCapabilities(string deviceId, SessionCapabilities capabilities) + { + var path = GetCapabilitiesFilePath(deviceId); + Directory.CreateDirectory(Path.GetDirectoryName(path)); + + await _capabilitiesLock.WaitAsync().ConfigureAwait(false); + + try + { + _jsonSerializer.SerializeToFile(capabilities, path); + } + finally + { + _capabilitiesLock.Release(); + } } public SessionInfoDto GetSessionInfoDto(SessionInfo session) diff --git a/Nuget/MediaBrowser.Common.Internal.nuspec b/Nuget/MediaBrowser.Common.Internal.nuspec index cffbf8b07a..9306462050 100644 --- a/Nuget/MediaBrowser.Common.Internal.nuspec +++ b/Nuget/MediaBrowser.Common.Internal.nuspec @@ -2,7 +2,7 @@ MediaBrowser.Common.Internal - 3.0.393 + 3.0.394 MediaBrowser.Common.Internal Luke ebr,Luke,scottisafool @@ -12,7 +12,7 @@ Contains common components shared by Media Browser Theater and Media Browser Server. Not intended for plugin developer consumption. Copyright © Media Browser 2013 - + diff --git a/Nuget/MediaBrowser.Common.nuspec b/Nuget/MediaBrowser.Common.nuspec index c2a74a1db0..95acf20cf1 100644 --- a/Nuget/MediaBrowser.Common.nuspec +++ b/Nuget/MediaBrowser.Common.nuspec @@ -2,7 +2,7 @@ MediaBrowser.Common - 3.0.393 + 3.0.394 MediaBrowser.Common Media Browser Team ebr,Luke,scottisafool diff --git a/Nuget/MediaBrowser.Server.Core.nuspec b/Nuget/MediaBrowser.Server.Core.nuspec index 6e12914ae4..9dcdac5155 100644 --- a/Nuget/MediaBrowser.Server.Core.nuspec +++ b/Nuget/MediaBrowser.Server.Core.nuspec @@ -2,7 +2,7 @@ MediaBrowser.Server.Core - 3.0.393 + 3.0.394 Media Browser.Server.Core Media Browser Team ebr,Luke,scottisafool @@ -12,7 +12,7 @@ Contains core components required to build plugins for Media Browser Server. Copyright © Media Browser 2013 - +