From 37c27a26e90b7eff62cec9e2b6a6c003e79fcbe4 Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Sat, 26 Jul 2014 13:30:15 -0400 Subject: added sync job database --- .../FileOrganization/EpisodeFileOrganizer.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'MediaBrowser.Server.Implementations/FileOrganization') diff --git a/MediaBrowser.Server.Implementations/FileOrganization/EpisodeFileOrganizer.cs b/MediaBrowser.Server.Implementations/FileOrganization/EpisodeFileOrganizer.cs index d44811886..594ba9d23 100644 --- a/MediaBrowser.Server.Implementations/FileOrganization/EpisodeFileOrganizer.cs +++ b/MediaBrowser.Server.Implementations/FileOrganization/EpisodeFileOrganizer.cs @@ -248,12 +248,12 @@ namespace MediaBrowser.Server.Implementations.FileOrganization .ToList(); var folder = Path.GetDirectoryName(targetPath); - var targetFileNameWithoutExtension = Path.GetFileNameWithoutExtension(targetPath); - + var targetFileNameWithoutExtension = _fileSystem.GetFileNameWithoutExtension(targetPath); + try { var filesOfOtherExtensions = Directory.EnumerateFiles(folder, "*", SearchOption.TopDirectoryOnly) - .Where(i => EntityResolutionHelper.IsVideoFile(i) && string.Equals(Path.GetFileNameWithoutExtension(i), targetFileNameWithoutExtension, StringComparison.OrdinalIgnoreCase)); + .Where(i => EntityResolutionHelper.IsVideoFile(i) && string.Equals(_fileSystem.GetFileNameWithoutExtension(i), targetFileNameWithoutExtension, StringComparison.OrdinalIgnoreCase)); episodePaths.AddRange(filesOfOtherExtensions); } -- cgit v1.2.3 From a37a11c486350a35cbc9f8763c477dd189e35501 Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Wed, 30 Jul 2014 22:09:23 -0400 Subject: 3.0.5324.37963 --- MediaBrowser.Api/Playback/BaseStreamingService.cs | 3 +- MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs | 30 ++++++------ MediaBrowser.Controller/Library/TVUtils.cs | 22 +++++---- MediaBrowser.Dlna/Didl/DidlBuilder.cs | 55 +++++++++++++--------- MediaBrowser.Dlna/PlayTo/PlayToController.cs | 8 ++-- .../MediaBrowser.Model.Portable.csproj | 15 +++--- .../MediaBrowser.Model.net35.csproj | 15 +++--- MediaBrowser.Model/Configuration/LiveTvOptions.cs | 8 ---- .../Configuration/ServerConfiguration.cs | 22 +-------- .../Configuration/TvFileOrganizationOptions.cs | 40 ---------------- MediaBrowser.Model/Dlna/ContentFeatureBuilder.cs | 38 ++++++++++----- .../FileOrganization/AutoOrganizeOptions.cs | 17 +++++++ .../FileOrganization/TvFileOrganizationOptions.cs | 40 ++++++++++++++++ MediaBrowser.Model/LiveTv/LiveTvOptions.cs | 8 ++++ MediaBrowser.Model/MediaBrowser.Model.csproj | 5 +- .../FileOrganization/EpisodeFileOrganizer.cs | 2 +- .../FileOrganization/Extensions.cs | 29 ++++++++++++ .../FileOrganization/FileOrganizationService.cs | 9 +++- .../FileOrganization/OrganizerScheduledTask.cs | 12 +++-- .../Library/Resolvers/TV/SeriesResolver.cs | 7 ++- .../LiveTv/LiveTvConfigurationFactory.cs | 2 +- .../MediaBrowser.Server.Implementations.csproj | 1 + MediaBrowser.ServerApplication/ApplicationHost.cs | 8 ++++ 23 files changed, 242 insertions(+), 154 deletions(-) delete mode 100644 MediaBrowser.Model/Configuration/LiveTvOptions.cs delete mode 100644 MediaBrowser.Model/Configuration/TvFileOrganizationOptions.cs create mode 100644 MediaBrowser.Model/FileOrganization/AutoOrganizeOptions.cs create mode 100644 MediaBrowser.Model/FileOrganization/TvFileOrganizationOptions.cs create mode 100644 MediaBrowser.Model/LiveTv/LiveTvOptions.cs create mode 100644 MediaBrowser.Server.Implementations/FileOrganization/Extensions.cs (limited to 'MediaBrowser.Server.Implementations/FileOrganization') diff --git a/MediaBrowser.Api/Playback/BaseStreamingService.cs b/MediaBrowser.Api/Playback/BaseStreamingService.cs index df85ca3cb..1963ad10a 100644 --- a/MediaBrowser.Api/Playback/BaseStreamingService.cs +++ b/MediaBrowser.Api/Playback/BaseStreamingService.cs @@ -1917,7 +1917,8 @@ namespace MediaBrowser.Api.Playback state.TargetPacketLength, state.TranscodeSeekInfo, state.IsTargetAnamorphic - ); + + ).FirstOrDefault() ?? string.Empty; } foreach (var item in responseHeaders) diff --git a/MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs b/MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs index ccaa918ec..c00e0c18b 100644 --- a/MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs +++ b/MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs @@ -55,21 +55,6 @@ namespace MediaBrowser.Api.Playback.Hls public object Get(GetMasterHlsVideoStream request) { - if (string.Equals(request.AudioCodec, "copy", StringComparison.OrdinalIgnoreCase)) - { - throw new ArgumentException("Audio codec copy is not allowed here."); - } - - if (string.Equals(request.VideoCodec, "copy", StringComparison.OrdinalIgnoreCase)) - { - throw new ArgumentException("Video codec copy is not allowed here."); - } - - if (string.IsNullOrEmpty(request.MediaSourceId)) - { - throw new ArgumentException("MediaSourceId is required"); - } - var result = GetAsync(request).Result; return result; @@ -332,6 +317,21 @@ namespace MediaBrowser.Api.Playback.Hls { var state = await GetState(request, CancellationToken.None).ConfigureAwait(false); + if (string.Equals(request.AudioCodec, "copy", StringComparison.OrdinalIgnoreCase)) + { + throw new ArgumentException("Audio codec copy is not allowed here."); + } + + if (string.Equals(request.VideoCodec, "copy", StringComparison.OrdinalIgnoreCase)) + { + throw new ArgumentException("Video codec copy is not allowed here."); + } + + if (string.IsNullOrEmpty(request.MediaSourceId)) + { + throw new ArgumentException("MediaSourceId is required"); + } + var audioBitrate = state.OutputAudioBitrate ?? 0; var videoBitrate = state.OutputVideoBitrate ?? 0; diff --git a/MediaBrowser.Controller/Library/TVUtils.cs b/MediaBrowser.Controller/Library/TVUtils.cs index 6ef1fb6f7..5e43eb644 100644 --- a/MediaBrowser.Controller/Library/TVUtils.cs +++ b/MediaBrowser.Controller/Library/TVUtils.cs @@ -223,10 +223,11 @@ namespace MediaBrowser.Controller.Library continue; } - if ((attributes & FileAttributes.System) == FileAttributes.System) - { - continue; - } + // Can't enforce this because files saved by Bitcasa are always marked System + //if ((attributes & FileAttributes.System) == FileAttributes.System) + //{ + // continue; + //} if ((attributes & FileAttributes.Directory) == FileAttributes.Directory) { @@ -268,13 +269,16 @@ namespace MediaBrowser.Controller.Library if ((attributes & FileAttributes.Hidden) == FileAttributes.Hidden) { + logger.Debug("Igoring series file or folder marked hidden: {0}", child.FullName); continue; } - if ((attributes & FileAttributes.System) == FileAttributes.System) - { - continue; - } + // Can't enforce this because files saved by Bitcasa are always marked System + //if ((attributes & FileAttributes.System) == FileAttributes.System) + //{ + // logger.Debug("Igoring series subfolder marked system: {0}", child.FullName); + // continue; + //} if ((attributes & FileAttributes.Directory) == FileAttributes.Directory) { @@ -299,6 +303,8 @@ namespace MediaBrowser.Controller.Library } else { + logger.Debug("Evaluating series file: {0}", child.FullName); + var fullName = child.FullName; if (EntityResolutionHelper.IsVideoFile(fullName) || EntityResolutionHelper.IsVideoPlaceHolder(fullName)) diff --git a/MediaBrowser.Dlna/Didl/DidlBuilder.cs b/MediaBrowser.Dlna/Didl/DidlBuilder.cs index 649ba2c8f..f1377727b 100644 --- a/MediaBrowser.Dlna/Didl/DidlBuilder.cs +++ b/MediaBrowser.Dlna/Didl/DidlBuilder.cs @@ -97,8 +97,6 @@ namespace MediaBrowser.Dlna.Didl private void AddVideoResource(XmlElement container, Video video, string deviceId, Filter filter, StreamInfo streamInfo = null) { - var res = container.OwnerDocument.CreateElement(string.Empty, "res", NS_DIDL); - if (streamInfo == null) { var sources = _user == null ? video.GetMediaSources(true).ToList() : video.GetMediaSources(true, _user).ToList(); @@ -113,6 +111,38 @@ namespace MediaBrowser.Dlna.Didl }); } + var targetWidth = streamInfo.TargetWidth; + var targetHeight = streamInfo.TargetHeight; + + var contentFeatureList = new ContentFeatureBuilder(_profile).BuildVideoHeader(streamInfo.Container, + streamInfo.VideoCodec, + streamInfo.AudioCodec, + targetWidth, + targetHeight, + streamInfo.TargetVideoBitDepth, + streamInfo.TargetVideoBitrate, + streamInfo.TargetAudioChannels, + streamInfo.TargetAudioBitrate, + streamInfo.TargetTimestamp, + streamInfo.IsDirectStream, + streamInfo.RunTimeTicks, + streamInfo.TargetVideoProfile, + streamInfo.TargetVideoLevel, + streamInfo.TargetFramerate, + streamInfo.TargetPacketLength, + streamInfo.TranscodeSeekInfo, + streamInfo.IsTargetAnamorphic); + + foreach (var contentFeature in contentFeatureList) + { + AddVideoResource(container, video, deviceId, filter, contentFeature, streamInfo); + } + } + + private void AddVideoResource(XmlElement container, Video video, string deviceId, Filter filter, string contentFeatures, StreamInfo streamInfo) + { + var res = container.OwnerDocument.CreateElement(string.Empty, "res", NS_DIDL); + var url = streamInfo.ToDlnaUrl(_serverAddress); res.InnerText = url; @@ -189,25 +219,6 @@ namespace MediaBrowser.Dlna.Didl ? MimeTypes.GetMimeType(filename) : mediaProfile.MimeType; - var contentFeatures = new ContentFeatureBuilder(_profile).BuildVideoHeader(streamInfo.Container, - streamInfo.VideoCodec, - streamInfo.AudioCodec, - targetWidth, - targetHeight, - streamInfo.TargetVideoBitDepth, - streamInfo.TargetVideoBitrate, - streamInfo.TargetAudioChannels, - streamInfo.TargetAudioBitrate, - streamInfo.TargetTimestamp, - streamInfo.IsDirectStream, - streamInfo.RunTimeTicks, - streamInfo.TargetVideoProfile, - streamInfo.TargetVideoLevel, - streamInfo.TargetFramerate, - streamInfo.TargetPacketLength, - streamInfo.TranscodeSeekInfo, - streamInfo.IsTargetAnamorphic); - res.SetAttribute("protocolInfo", String.Format( "http-get:*:{0}:{1}", mimeType, @@ -216,7 +227,7 @@ namespace MediaBrowser.Dlna.Didl container.AppendChild(res); } - + private void AddAudioResource(XmlElement container, Audio audio, string deviceId, Filter filter, StreamInfo streamInfo = null) { var res = container.OwnerDocument.CreateElement(string.Empty, "res", NS_DIDL); diff --git a/MediaBrowser.Dlna/PlayTo/PlayToController.cs b/MediaBrowser.Dlna/PlayTo/PlayToController.cs index d81e46049..9e8d8213f 100644 --- a/MediaBrowser.Dlna/PlayTo/PlayToController.cs +++ b/MediaBrowser.Dlna/PlayTo/PlayToController.cs @@ -501,7 +501,7 @@ namespace MediaBrowser.Dlna.PlayTo if (streamInfo.MediaType == DlnaProfileType.Video) { - return new ContentFeatureBuilder(profile) + var list = new ContentFeatureBuilder(profile) .BuildVideoHeader(streamInfo.Container, streamInfo.VideoCodec, streamInfo.AudioCodec, @@ -520,6 +520,8 @@ namespace MediaBrowser.Dlna.PlayTo streamInfo.TargetPacketLength, streamInfo.TranscodeSeekInfo, streamInfo.IsTargetAnamorphic); + + return list.FirstOrDefault(); } return null; @@ -600,9 +602,7 @@ namespace MediaBrowser.Dlna.PlayTo _currentPlaylistIndex = index; var currentitem = Playlist[index]; - var dlnaheaders = GetDlnaHeaders(currentitem); - - await _device.SetAvTransport(currentitem.StreamUrl, dlnaheaders, currentitem.Didl); + await _device.SetAvTransport(currentitem.StreamUrl, GetDlnaHeaders(currentitem), currentitem.Didl); var streamInfo = currentitem.StreamInfo; if (streamInfo.StartPositionTicks > 0 && streamInfo.IsDirectStream) diff --git a/MediaBrowser.Model.Portable/MediaBrowser.Model.Portable.csproj b/MediaBrowser.Model.Portable/MediaBrowser.Model.Portable.csproj index e55d3eec9..f374ed529 100644 --- a/MediaBrowser.Model.Portable/MediaBrowser.Model.Portable.csproj +++ b/MediaBrowser.Model.Portable/MediaBrowser.Model.Portable.csproj @@ -152,9 +152,6 @@ Configuration\ImageSavingConvention.cs - - Configuration\LiveTvOptions.cs - Configuration\MetadataOptions.cs @@ -179,9 +176,6 @@ Configuration\SubtitlePlaybackMode.cs - - Configuration\TvFileOrganizationOptions.cs - Configuration\UnratedItem.cs @@ -473,6 +467,9 @@ Extensions\StringHelper.cs + + FileOrganization\AutoOrganizeOptions.cs + FileOrganization\EpisodeFileOrganizationRequest.cs @@ -488,6 +485,9 @@ FileOrganization\FileSortingStatus.cs + + FileOrganization\TvFileOrganizationOptions.cs + Games\GameSystem.cs @@ -542,6 +542,9 @@ LiveTv\LiveTvInfo.cs + + LiveTv\LiveTvOptions.cs + LiveTv\LiveTvServiceInfo.cs diff --git a/MediaBrowser.Model.net35/MediaBrowser.Model.net35.csproj b/MediaBrowser.Model.net35/MediaBrowser.Model.net35.csproj index 603eecbae..5385ee036 100644 --- a/MediaBrowser.Model.net35/MediaBrowser.Model.net35.csproj +++ b/MediaBrowser.Model.net35/MediaBrowser.Model.net35.csproj @@ -115,9 +115,6 @@ Configuration\ImageSavingConvention.cs - - Configuration\LiveTvOptions.cs - Configuration\MetadataOptions.cs @@ -142,9 +139,6 @@ Configuration\SubtitlePlaybackMode.cs - - Configuration\TvFileOrganizationOptions.cs - Configuration\UnratedItem.cs @@ -436,6 +430,9 @@ Extensions\StringHelper.cs + + FileOrganization\AutoOrganizeOptions.cs + FileOrganization\EpisodeFileOrganizationRequest.cs @@ -451,6 +448,9 @@ FileOrganization\FileSortingStatus.cs + + FileOrganization\TvFileOrganizationOptions.cs + Games\GameSystem.cs @@ -499,6 +499,9 @@ LiveTv\LiveTvInfo.cs + + LiveTv\LiveTvOptions.cs + LiveTv\LiveTvServiceInfo.cs diff --git a/MediaBrowser.Model/Configuration/LiveTvOptions.cs b/MediaBrowser.Model/Configuration/LiveTvOptions.cs deleted file mode 100644 index 575f0b863..000000000 --- a/MediaBrowser.Model/Configuration/LiveTvOptions.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace MediaBrowser.Model.Configuration -{ - public class LiveTvOptions - { - public int? GuideDays { get; set; } - public string ActiveService { get; set; } - } -} \ No newline at end of file diff --git a/MediaBrowser.Model/Configuration/ServerConfiguration.cs b/MediaBrowser.Model/Configuration/ServerConfiguration.cs index 00f7add14..4734e2af7 100644 --- a/MediaBrowser.Model/Configuration/ServerConfiguration.cs +++ b/MediaBrowser.Model/Configuration/ServerConfiguration.cs @@ -1,7 +1,7 @@ using MediaBrowser.Model.Entities; +using MediaBrowser.Model.FileOrganization; +using MediaBrowser.Model.LiveTv; using MediaBrowser.Model.Notifications; -using MediaBrowser.Model.Weather; -using System; namespace MediaBrowser.Model.Configuration { @@ -10,18 +10,6 @@ namespace MediaBrowser.Model.Configuration /// public class ServerConfiguration : BaseApplicationConfiguration { - /// - /// Gets or sets the zip code to use when displaying weather - /// - /// The weather location. - public string WeatherLocation { get; set; } - - /// - /// Gets or sets the weather unit to use when displaying weather - /// - /// The weather unit. - public WeatherUnits WeatherUnit { get; set; } - /// /// Gets or sets a value indicating whether [enable u pn p]. /// @@ -191,9 +179,6 @@ namespace MediaBrowser.Model.Configuration public SubtitleOptions SubtitleOptions { get; set; } - [Obsolete] - public string[] ManualLoginClients { get; set; } - public ChannelOptions ChannelOptions { get; set; } public ChapterOptions ChapterOptions { get; set; } @@ -237,8 +222,6 @@ namespace MediaBrowser.Model.Configuration SortRemoveCharacters = new[] { ",", "&", "-", "{", "}", "'" }; SortRemoveWords = new[] { "the", "a", "an" }; - ManualLoginClients = new string[] { }; - SeasonZeroDisplayName = "Specials"; EnableRealtimeMonitor = true; @@ -304,7 +287,6 @@ namespace MediaBrowser.Model.Configuration }; SubtitleOptions = new SubtitleOptions(); - TvFileOrganizationOptions = new TvFileOrganizationOptions(); } } } diff --git a/MediaBrowser.Model/Configuration/TvFileOrganizationOptions.cs b/MediaBrowser.Model/Configuration/TvFileOrganizationOptions.cs deleted file mode 100644 index fe32d4a80..000000000 --- a/MediaBrowser.Model/Configuration/TvFileOrganizationOptions.cs +++ /dev/null @@ -1,40 +0,0 @@ - -namespace MediaBrowser.Model.Configuration -{ - public class TvFileOrganizationOptions - { - public bool IsEnabled { get; set; } - public int MinFileSizeMb { get; set; } - public string[] LeftOverFileExtensionsToDelete { get; set; } - public string[] WatchLocations { get; set; } - - public string SeasonFolderPattern { get; set; } - - public string SeasonZeroFolderName { get; set; } - - public string EpisodeNamePattern { get; set; } - public string MultiEpisodeNamePattern { get; set; } - - public bool OverwriteExistingEpisodes { get; set; } - - public bool DeleteEmptyFolders { get; set; } - - public bool CopyOriginalFile { get; set; } - - public TvFileOrganizationOptions() - { - MinFileSizeMb = 50; - - LeftOverFileExtensionsToDelete = new string[] { }; - - WatchLocations = new string[] { }; - - EpisodeNamePattern = "%sn - %sx%0e - %en.%ext"; - MultiEpisodeNamePattern = "%sn - %sx%0e-x%0ed - %en.%ext"; - SeasonFolderPattern = "Season %s"; - SeasonZeroFolderName = "Season 0"; - - CopyOriginalFile = false; - } - } -} diff --git a/MediaBrowser.Model/Dlna/ContentFeatureBuilder.cs b/MediaBrowser.Model/Dlna/ContentFeatureBuilder.cs index b6d9d9f77..5417c5b82 100644 --- a/MediaBrowser.Model/Dlna/ContentFeatureBuilder.cs +++ b/MediaBrowser.Model/Dlna/ContentFeatureBuilder.cs @@ -92,7 +92,7 @@ namespace MediaBrowser.Model.Dlna return (contentFeatures + orgOp + orgCi + dlnaflags).Trim(';'); } - public string BuildVideoHeader(string container, + public List BuildVideoHeader(string container, string videoCodec, string audioCodec, int? width, @@ -149,30 +149,42 @@ namespace MediaBrowser.Model.Dlna timestamp, isAnamorphic); - string orgPn = mediaProfile == null ? null : mediaProfile.OrgPn; + List orgPnValues = new List(); - if (string.IsNullOrEmpty(orgPn)) + if (mediaProfile != null && !string.IsNullOrEmpty(mediaProfile.OrgPn)) + { + orgPnValues.Add(mediaProfile.OrgPn); + } + else { foreach (string s in GetVideoOrgPnValue(container, videoCodec, audioCodec, width, height, timestamp)) { - orgPn = s; + orgPnValues.Add(s); break; } } - if (string.IsNullOrEmpty(orgPn)) + List contentFeatureList = new List(); + + foreach (string orgPn in orgPnValues) { - // TODO: Support multiple values and return multiple headers? - foreach (string s in (orgPn ?? string.Empty).Split(',')) - { - orgPn = s; - break; - } + string contentFeatures = string.IsNullOrEmpty(orgPn) ? string.Empty : "DLNA.ORG_PN=" + orgPn; + + var value = (contentFeatures + orgOp + orgCi + dlnaflags).Trim(';'); + + contentFeatureList.Add(value); } - string contentFeatures = string.IsNullOrEmpty(orgPn) ? string.Empty : "DLNA.ORG_PN=" + orgPn; + if (orgPnValues.Count == 0) + { + string contentFeatures = string.Empty; - return (contentFeatures + orgOp + orgCi + dlnaflags).Trim(';'); + var value = (contentFeatures + orgOp + orgCi + dlnaflags).Trim(';'); + + contentFeatureList.Add(value); + } + + return contentFeatureList; } private string GetImageOrgPnValue(string container, int? width, int? height) diff --git a/MediaBrowser.Model/FileOrganization/AutoOrganizeOptions.cs b/MediaBrowser.Model/FileOrganization/AutoOrganizeOptions.cs new file mode 100644 index 000000000..ae701ea68 --- /dev/null +++ b/MediaBrowser.Model/FileOrganization/AutoOrganizeOptions.cs @@ -0,0 +1,17 @@ + +namespace MediaBrowser.Model.FileOrganization +{ + public class AutoOrganizeOptions + { + /// + /// Gets or sets the tv options. + /// + /// The tv options. + public TvFileOrganizationOptions TvOptions { get; set; } + + public AutoOrganizeOptions() + { + TvOptions = new TvFileOrganizationOptions(); + } + } +} diff --git a/MediaBrowser.Model/FileOrganization/TvFileOrganizationOptions.cs b/MediaBrowser.Model/FileOrganization/TvFileOrganizationOptions.cs new file mode 100644 index 000000000..973ecf6e7 --- /dev/null +++ b/MediaBrowser.Model/FileOrganization/TvFileOrganizationOptions.cs @@ -0,0 +1,40 @@ + +namespace MediaBrowser.Model.FileOrganization +{ + public class TvFileOrganizationOptions + { + public bool IsEnabled { get; set; } + public int MinFileSizeMb { get; set; } + public string[] LeftOverFileExtensionsToDelete { get; set; } + public string[] WatchLocations { get; set; } + + public string SeasonFolderPattern { get; set; } + + public string SeasonZeroFolderName { get; set; } + + public string EpisodeNamePattern { get; set; } + public string MultiEpisodeNamePattern { get; set; } + + public bool OverwriteExistingEpisodes { get; set; } + + public bool DeleteEmptyFolders { get; set; } + + public bool CopyOriginalFile { get; set; } + + public TvFileOrganizationOptions() + { + MinFileSizeMb = 50; + + LeftOverFileExtensionsToDelete = new string[] { }; + + WatchLocations = new string[] { }; + + EpisodeNamePattern = "%sn - %sx%0e - %en.%ext"; + MultiEpisodeNamePattern = "%sn - %sx%0e-x%0ed - %en.%ext"; + SeasonFolderPattern = "Season %s"; + SeasonZeroFolderName = "Season 0"; + + CopyOriginalFile = false; + } + } +} diff --git a/MediaBrowser.Model/LiveTv/LiveTvOptions.cs b/MediaBrowser.Model/LiveTv/LiveTvOptions.cs new file mode 100644 index 000000000..05fdc00b1 --- /dev/null +++ b/MediaBrowser.Model/LiveTv/LiveTvOptions.cs @@ -0,0 +1,8 @@ +namespace MediaBrowser.Model.LiveTv +{ + public class LiveTvOptions + { + public int? GuideDays { get; set; } + public string ActiveService { get; set; } + } +} \ No newline at end of file diff --git a/MediaBrowser.Model/MediaBrowser.Model.csproj b/MediaBrowser.Model/MediaBrowser.Model.csproj index f57c3b95d..efd8c2a0a 100644 --- a/MediaBrowser.Model/MediaBrowser.Model.csproj +++ b/MediaBrowser.Model/MediaBrowser.Model.csproj @@ -79,13 +79,14 @@ - + + - + diff --git a/MediaBrowser.Server.Implementations/FileOrganization/EpisodeFileOrganizer.cs b/MediaBrowser.Server.Implementations/FileOrganization/EpisodeFileOrganizer.cs index 594ba9d23..cf9571824 100644 --- a/MediaBrowser.Server.Implementations/FileOrganization/EpisodeFileOrganizer.cs +++ b/MediaBrowser.Server.Implementations/FileOrganization/EpisodeFileOrganizer.cs @@ -126,7 +126,7 @@ namespace MediaBrowser.Server.Implementations.FileOrganization var series = (Series)_libraryManager.GetItemById(new Guid(request.SeriesId)); - await OrganizeEpisode(result.OriginalPath, series, request.SeasonNumber, request.EpisodeNumber, request.EndingEpisodeNumber, _config.Configuration.TvFileOrganizationOptions, true, result, cancellationToken).ConfigureAwait(false); + await OrganizeEpisode(result.OriginalPath, series, request.SeasonNumber, request.EpisodeNumber, request.EndingEpisodeNumber, options, true, result, cancellationToken).ConfigureAwait(false); await _organizationService.SaveResult(result, CancellationToken.None).ConfigureAwait(false); diff --git a/MediaBrowser.Server.Implementations/FileOrganization/Extensions.cs b/MediaBrowser.Server.Implementations/FileOrganization/Extensions.cs new file mode 100644 index 000000000..e43ab3665 --- /dev/null +++ b/MediaBrowser.Server.Implementations/FileOrganization/Extensions.cs @@ -0,0 +1,29 @@ +using MediaBrowser.Common.Configuration; +using MediaBrowser.Model.FileOrganization; +using System.Collections.Generic; + +namespace MediaBrowser.Server.Implementations.FileOrganization +{ + public static class ConfigurationExtension + { + public static AutoOrganizeOptions GetAutoOrganizeOptions(this IConfigurationManager manager) + { + return manager.GetConfiguration("autoorganize"); + } + } + + public class AutoOrganizeOptionsFactory : IConfigurationFactory + { + public IEnumerable GetConfigurations() + { + return new List + { + new ConfigurationStore + { + Key = "autoorganize", + ConfigurationType = typeof (AutoOrganizeOptions) + } + }; + } + } +} diff --git a/MediaBrowser.Server.Implementations/FileOrganization/FileOrganizationService.cs b/MediaBrowser.Server.Implementations/FileOrganization/FileOrganizationService.cs index 7cf38b0a0..7c5269678 100644 --- a/MediaBrowser.Server.Implementations/FileOrganization/FileOrganizationService.cs +++ b/MediaBrowser.Server.Implementations/FileOrganization/FileOrganizationService.cs @@ -95,6 +95,11 @@ namespace MediaBrowser.Server.Implementations.FileOrganization return _repo.Delete(resultId); } + private TvFileOrganizationOptions GetTvOptions() + { + return _config.GetAutoOrganizeOptions().TvOptions; + } + public async Task PerformOrganization(string resultId) { var result = _repo.GetResult(resultId); @@ -107,7 +112,7 @@ namespace MediaBrowser.Server.Implementations.FileOrganization var organizer = new EpisodeFileOrganizer(this, _config, _fileSystem, _logger, _libraryManager, _libraryMonitor, _providerManager); - await organizer.OrganizeEpisodeFile(result.OriginalPath, _config.Configuration.TvFileOrganizationOptions, true, CancellationToken.None) + await organizer.OrganizeEpisodeFile(result.OriginalPath, GetTvOptions(), true, CancellationToken.None) .ConfigureAwait(false); } @@ -121,7 +126,7 @@ namespace MediaBrowser.Server.Implementations.FileOrganization var organizer = new EpisodeFileOrganizer(this, _config, _fileSystem, _logger, _libraryManager, _libraryMonitor, _providerManager); - await organizer.OrganizeWithCorrection(request, _config.Configuration.TvFileOrganizationOptions, CancellationToken.None).ConfigureAwait(false); + await organizer.OrganizeWithCorrection(request, GetTvOptions(), CancellationToken.None).ConfigureAwait(false); } } } diff --git a/MediaBrowser.Server.Implementations/FileOrganization/OrganizerScheduledTask.cs b/MediaBrowser.Server.Implementations/FileOrganization/OrganizerScheduledTask.cs index fbb743f94..4e182ea88 100644 --- a/MediaBrowser.Server.Implementations/FileOrganization/OrganizerScheduledTask.cs +++ b/MediaBrowser.Server.Implementations/FileOrganization/OrganizerScheduledTask.cs @@ -4,6 +4,7 @@ using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.FileOrganization; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Providers; +using MediaBrowser.Model.FileOrganization; using MediaBrowser.Model.Logging; using System; using System.Collections.Generic; @@ -48,10 +49,15 @@ namespace MediaBrowser.Server.Implementations.FileOrganization get { return "Library"; } } + private TvFileOrganizationOptions GetTvOptions() + { + return _config.GetAutoOrganizeOptions().TvOptions; + } + public Task Execute(CancellationToken cancellationToken, IProgress progress) { return new TvFolderOrganizer(_libraryManager, _logger, _fileSystem, _libraryMonitor, _organizationService, _config, _providerManager) - .Organize(_config.Configuration.TvFileOrganizationOptions, cancellationToken, progress); + .Organize(GetTvOptions(), cancellationToken, progress); } public IEnumerable GetDefaultTriggers() @@ -64,12 +70,12 @@ namespace MediaBrowser.Server.Implementations.FileOrganization public bool IsHidden { - get { return !_config.Configuration.TvFileOrganizationOptions.IsEnabled; } + get { return !GetTvOptions().IsEnabled; } } public bool IsEnabled { - get { return _config.Configuration.TvFileOrganizationOptions.IsEnabled; } + get { return !GetTvOptions().IsEnabled; } } } } diff --git a/MediaBrowser.Server.Implementations/Library/Resolvers/TV/SeriesResolver.cs b/MediaBrowser.Server.Implementations/Library/Resolvers/TV/SeriesResolver.cs index a17c33845..d3aad582a 100644 --- a/MediaBrowser.Server.Implementations/Library/Resolvers/TV/SeriesResolver.cs +++ b/MediaBrowser.Server.Implementations/Library/Resolvers/TV/SeriesResolver.cs @@ -60,15 +60,18 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers.TV var collectionType = args.GetCollectionType(); + var isTvShowsFolder = string.Equals(collectionType, CollectionType.TvShows, + StringComparison.OrdinalIgnoreCase); + // If there's a collection type and it's not tv, it can't be a series if (!string.IsNullOrEmpty(collectionType) && - !string.Equals(collectionType, CollectionType.TvShows, StringComparison.OrdinalIgnoreCase) && + !isTvShowsFolder && !string.Equals(collectionType, CollectionType.BoxSets, StringComparison.OrdinalIgnoreCase)) { return null; } - if (TVUtils.IsSeriesFolder(args.Path, string.Equals(collectionType, CollectionType.TvShows, StringComparison.OrdinalIgnoreCase), args.FileSystemChildren, args.DirectoryService, _fileSystem, _logger)) + if (TVUtils.IsSeriesFolder(args.Path, isTvShowsFolder, args.FileSystemChildren, args.DirectoryService, _fileSystem, _logger)) { return new Series(); } diff --git a/MediaBrowser.Server.Implementations/LiveTv/LiveTvConfigurationFactory.cs b/MediaBrowser.Server.Implementations/LiveTv/LiveTvConfigurationFactory.cs index fefe6401d..57d1d79e1 100644 --- a/MediaBrowser.Server.Implementations/LiveTv/LiveTvConfigurationFactory.cs +++ b/MediaBrowser.Server.Implementations/LiveTv/LiveTvConfigurationFactory.cs @@ -1,5 +1,5 @@ using MediaBrowser.Common.Configuration; -using MediaBrowser.Model.Configuration; +using MediaBrowser.Model.LiveTv; using System.Collections.Generic; namespace MediaBrowser.Server.Implementations.LiveTv diff --git a/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj b/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj index 79d15039b..5cc22ac64 100644 --- a/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj +++ b/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj @@ -127,6 +127,7 @@ + diff --git a/MediaBrowser.ServerApplication/ApplicationHost.cs b/MediaBrowser.ServerApplication/ApplicationHost.cs index e584a950c..7dc70627b 100644 --- a/MediaBrowser.ServerApplication/ApplicationHost.cs +++ b/MediaBrowser.ServerApplication/ApplicationHost.cs @@ -43,6 +43,7 @@ using MediaBrowser.LocalMetadata.Providers; using MediaBrowser.MediaEncoding.BdInfo; using MediaBrowser.MediaEncoding.Encoder; using MediaBrowser.MediaEncoding.Subtitles; +using MediaBrowser.Model.FileOrganization; using MediaBrowser.Model.Logging; using MediaBrowser.Model.MediaInfo; using MediaBrowser.Model.System; @@ -333,6 +334,13 @@ namespace MediaBrowser.ServerApplication saveConfig = true; } + if (ServerConfigurationManager.Configuration.TvFileOrganizationOptions != null) + { + ServerConfigurationManager.SaveConfiguration("autoorganize", new AutoOrganizeOptions { TvOptions = ServerConfigurationManager.Configuration.TvFileOrganizationOptions }); + ServerConfigurationManager.Configuration.TvFileOrganizationOptions = null; + saveConfig = true; + } + if (saveConfig) { ServerConfigurationManager.SaveConfiguration(); -- cgit v1.2.3 From 2714127d2b663b735048da6d9def08efa38f2b5f Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Sat, 2 Aug 2014 22:16:37 -0400 Subject: fixes #791 - Support server-side playlists --- MediaBrowser.Api/PlaylistService.cs | 81 +++++++++++++++- MediaBrowser.Api/UserLibrary/BaseItemsRequest.cs | 6 +- MediaBrowser.Controller/Entities/BaseItem.cs | 24 ++++- .../Entities/BasePluginFolder.cs | 5 - MediaBrowser.Controller/Entities/Folder.cs | 8 +- MediaBrowser.Controller/Entities/LinkedChild.cs | 13 +++ MediaBrowser.Controller/Entities/Movies/BoxSet.cs | 12 ++- MediaBrowser.Controller/Entities/TV/Season.cs | 9 ++ MediaBrowser.Controller/Entities/TV/Series.cs | 9 ++ MediaBrowser.Controller/Entities/UserView.cs | 1 - MediaBrowser.Controller/Playlists/Playlist.cs | 77 ++++++++++++++- .../Playlists/PlaylistCreationOptions.cs | 4 + .../Providers/BaseItemXmlParser.cs | 72 ++++++++++++++ .../ContentDirectory/ControlHandler.cs | 2 +- .../MediaBrowser.LocalMetadata.csproj | 1 + .../Parsers/BoxSetXmlParser.cs | 54 ----------- .../Savers/FolderXmlSaver.cs | 14 +-- .../Savers/PlaylistXmlSaver.cs | 68 +++++++++++++ .../Savers/XmlSaverHelpers.cs | 31 ++++-- MediaBrowser.Model/Dto/BaseItemDto.cs | 14 ++- .../Channels/ChannelDownloadScheduledTask.cs | 2 +- .../Collections/CollectionManager.cs | 8 +- .../Collections/ManualCollectionsFolder.cs | 1 + .../Dto/DtoService.cs | 47 ++++++++- .../FileOrganization/OrganizerScheduledTask.cs | 2 +- .../Library/Resolvers/PlaylistResolver.cs | 38 ++++++++ .../Localization/JavaScript/javascript.json | 11 ++- .../Localization/Server/server.json | 6 +- .../MediaBrowser.Server.Implementations.csproj | 1 + .../Playlists/ManualPlaylistsFolder.cs | 17 +++- .../Playlists/PlaylistManager.cs | 105 +++++++++++++++++---- MediaBrowser.WebDashboard/Api/DashboardService.cs | 5 +- .../MediaBrowser.WebDashboard.csproj | 18 +++- 33 files changed, 636 insertions(+), 130 deletions(-) create mode 100644 MediaBrowser.LocalMetadata/Savers/PlaylistXmlSaver.cs create mode 100644 MediaBrowser.Server.Implementations/Library/Resolvers/PlaylistResolver.cs (limited to 'MediaBrowser.Server.Implementations/FileOrganization') diff --git a/MediaBrowser.Api/PlaylistService.cs b/MediaBrowser.Api/PlaylistService.cs index 475183dea..b4d2e2f0f 100644 --- a/MediaBrowser.Api/PlaylistService.cs +++ b/MediaBrowser.Api/PlaylistService.cs @@ -1,9 +1,12 @@ using MediaBrowser.Controller.Dto; +using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Net; using MediaBrowser.Controller.Playlists; +using MediaBrowser.Model.Dto; using MediaBrowser.Model.Playlists; using MediaBrowser.Model.Querying; using ServiceStack; +using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; @@ -18,6 +21,9 @@ namespace MediaBrowser.Api [ApiMember(Name = "Ids", Description = "Item Ids to add to the playlist", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "POST", AllowMultiple = true)] public string Ids { get; set; } + + [ApiMember(Name = "UserId", Description = "User Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] + public string UserId { get; set; } } [Route("/Playlists/{Id}/Items", "POST", Summary = "Adds items to a playlist")] @@ -37,16 +43,55 @@ namespace MediaBrowser.Api public string Id { get; set; } } + [Route("/Playlists/{Id}/Items", "GET", Summary = "Gets the original items of a playlist")] + public class GetPlaylistItems : IReturn>, IHasItemFields + { + [ApiMember(Name = "Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "DELETE")] + public string Id { get; set; } + + /// + /// Gets or sets the user id. + /// + /// The user id. + [ApiMember(Name = "UserId", Description = "User Id", IsRequired = false, DataType = "string", ParameterType = "path", Verb = "GET")] + public Guid? UserId { get; set; } + + /// + /// Skips over a given number of items within the results. Use for paging. + /// + /// The start index. + [ApiMember(Name = "StartIndex", Description = "Optional. The record index to start at. All items with a lower index will be dropped from the results.", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")] + public int? StartIndex { get; set; } + + /// + /// The maximum number of items to return + /// + /// The limit. + [ApiMember(Name = "Limit", Description = "Optional. The maximum number of records to return", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")] + public int? Limit { get; set; } + + /// + /// Fields to return within the items, in addition to basic information + /// + /// The fields. + [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, ParentId, Path, People, ProviderIds, PrimaryImageAspectRatio, Revenue, SortName, Studios, Taglines", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)] + public string Fields { get; set; } + } + [Authenticated] public class PlaylistService : BaseApiService { private readonly IPlaylistManager _playlistManager; private readonly IDtoService _dtoService; + private readonly IUserManager _userManager; + private readonly ILibraryManager _libraryManager; - public PlaylistService(IDtoService dtoService, IPlaylistManager playlistManager) + public PlaylistService(IDtoService dtoService, IPlaylistManager playlistManager, IUserManager userManager, ILibraryManager libraryManager) { _dtoService = dtoService; _playlistManager = playlistManager; + _userManager = userManager; + _libraryManager = libraryManager; } public object Post(CreatePlaylist request) @@ -54,7 +99,8 @@ namespace MediaBrowser.Api var task = _playlistManager.CreatePlaylist(new PlaylistCreationOptions { Name = request.Name, - ItemIdList = (request.Ids ?? string.Empty).Split(',').Where(i => !string.IsNullOrWhiteSpace(i)).ToList() + ItemIdList = (request.Ids ?? string.Empty).Split(',').Where(i => !string.IsNullOrWhiteSpace(i)).ToList(), + UserId = request.UserId }); var item = task.Result; @@ -80,5 +126,36 @@ namespace MediaBrowser.Api //Task.WaitAll(task); } + + public object Get(GetPlaylistItems request) + { + var playlist = (Playlist)_libraryManager.GetItemById(request.Id); + var user = request.UserId.HasValue ? _userManager.GetUserById(request.UserId.Value) : null; + var items = playlist.GetManageableItems().ToArray(); + + var count = items.Length; + + if (request.StartIndex.HasValue) + { + items = items.Skip(request.StartIndex.Value).ToArray(); + } + + if (request.Limit.HasValue) + { + items = items.Take(request.Limit.Value).ToArray(); + } + + var dtos = items + .Select(i => _dtoService.GetBaseItemDto(i, request.GetItemFields().ToList(), user)) + .ToArray(); + + var result = new ItemsResult + { + Items = dtos, + TotalRecordCount = count + }; + + return ToOptimizedResult(result); + } } } diff --git a/MediaBrowser.Api/UserLibrary/BaseItemsRequest.cs b/MediaBrowser.Api/UserLibrary/BaseItemsRequest.cs index 7cd518a18..f23610014 100644 --- a/MediaBrowser.Api/UserLibrary/BaseItemsRequest.cs +++ b/MediaBrowser.Api/UserLibrary/BaseItemsRequest.cs @@ -1,9 +1,9 @@ -using System.Collections.Generic; -using System.Linq; -using MediaBrowser.Model.Entities; +using MediaBrowser.Model.Entities; using MediaBrowser.Model.Querying; using ServiceStack; using System; +using System.Collections.Generic; +using System.Linq; namespace MediaBrowser.Api.UserLibrary { diff --git a/MediaBrowser.Controller/Entities/BaseItem.cs b/MediaBrowser.Controller/Entities/BaseItem.cs index 2bdbab084..36e65f5f5 100644 --- a/MediaBrowser.Controller/Entities/BaseItem.cs +++ b/MediaBrowser.Controller/Entities/BaseItem.cs @@ -1006,6 +1006,18 @@ namespace MediaBrowser.Controller.Entities private BaseItem FindLinkedChild(LinkedChild info) { + if (!string.IsNullOrWhiteSpace(info.ItemName)) + { + if (string.Equals(info.ItemType, "musicgenre", StringComparison.OrdinalIgnoreCase)) + { + return LibraryManager.GetMusicGenre(info.ItemName); + } + if (string.Equals(info.ItemType, "musicartist", StringComparison.OrdinalIgnoreCase)) + { + return LibraryManager.GetArtist(info.ItemName); + } + } + if (!string.IsNullOrEmpty(info.Path)) { var itemByPath = LibraryManager.RootFolder.FindByPath(info.Path); @@ -1028,7 +1040,17 @@ namespace MediaBrowser.Controller.Entities { if (info.ItemYear.HasValue) { - return info.ItemYear.Value == (i.ProductionYear ?? -1); + if (info.ItemYear.Value != (i.ProductionYear ?? -1)) + { + return false; + } + } + if (info.ItemIndexNumber.HasValue) + { + if (info.ItemIndexNumber.Value != (i.IndexNumber ?? -1)) + { + return false; + } } return true; } diff --git a/MediaBrowser.Controller/Entities/BasePluginFolder.cs b/MediaBrowser.Controller/Entities/BasePluginFolder.cs index fa2b49a60..b30bd81b9 100644 --- a/MediaBrowser.Controller/Entities/BasePluginFolder.cs +++ b/MediaBrowser.Controller/Entities/BasePluginFolder.cs @@ -7,11 +7,6 @@ namespace MediaBrowser.Controller.Entities /// public abstract class BasePluginFolder : Folder, ICollectionFolder, IByReferenceItem { - protected BasePluginFolder() - { - DisplayMediaType = "CollectionFolder"; - } - public virtual string CollectionType { get { return null; } diff --git a/MediaBrowser.Controller/Entities/Folder.cs b/MediaBrowser.Controller/Entities/Folder.cs index 12afe26b6..2013b926c 100644 --- a/MediaBrowser.Controller/Entities/Folder.cs +++ b/MediaBrowser.Controller/Entities/Folder.cs @@ -38,6 +38,12 @@ namespace MediaBrowser.Controller.Entities Tags = new List(); } + [IgnoreDataMember] + public virtual bool IsPreSorted + { + get { return false; } + } + /// /// Gets a value indicating whether this instance is folder. /// @@ -855,7 +861,7 @@ namespace MediaBrowser.Controller.Entities /// if set to true [include linked children]. /// IEnumerable{BaseItem}. /// - public IEnumerable GetRecursiveChildren(User user, bool includeLinkedChildren = true) + public virtual IEnumerable GetRecursiveChildren(User user, bool includeLinkedChildren = true) { if (user == null) { diff --git a/MediaBrowser.Controller/Entities/LinkedChild.cs b/MediaBrowser.Controller/Entities/LinkedChild.cs index 1ae04e40f..3fc0ab716 100644 --- a/MediaBrowser.Controller/Entities/LinkedChild.cs +++ b/MediaBrowser.Controller/Entities/LinkedChild.cs @@ -12,12 +12,25 @@ namespace MediaBrowser.Controller.Entities public string ItemName { get; set; } public string ItemType { get; set; } public int? ItemYear { get; set; } + public int? ItemIndexNumber { get; set; } /// /// Serves as a cache /// [IgnoreDataMember] public Guid? ItemId { get; set; } + + public static LinkedChild Create(BaseItem item) + { + return new LinkedChild + { + ItemName = item.Name, + ItemYear = item.ProductionYear, + ItemType = item.GetType().Name, + Type = LinkedChildType.Manual, + ItemIndexNumber = item.IndexNumber + }; + } } public enum LinkedChildType diff --git a/MediaBrowser.Controller/Entities/Movies/BoxSet.cs b/MediaBrowser.Controller/Entities/Movies/BoxSet.cs index 0d2be9f74..5e6bd9707 100644 --- a/MediaBrowser.Controller/Entities/Movies/BoxSet.cs +++ b/MediaBrowser.Controller/Entities/Movies/BoxSet.cs @@ -1,4 +1,5 @@ -using MediaBrowser.Common.Progress; +using System.Runtime.Serialization; +using MediaBrowser.Common.Progress; using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Configuration; using MediaBrowser.Model.Entities; @@ -58,6 +59,15 @@ namespace MediaBrowser.Controller.Entities.Movies return config.BlockUnratedItems.Contains(UnratedItem.Movie); } + [IgnoreDataMember] + public override bool IsPreSorted + { + get + { + return true; + } + } + public override IEnumerable GetChildren(User user, bool includeLinkedChildren) { var children = base.GetChildren(user, includeLinkedChildren); diff --git a/MediaBrowser.Controller/Entities/TV/Season.cs b/MediaBrowser.Controller/Entities/TV/Season.cs index cf39cda89..3977d869c 100644 --- a/MediaBrowser.Controller/Entities/TV/Season.cs +++ b/MediaBrowser.Controller/Entities/TV/Season.cs @@ -29,6 +29,15 @@ namespace MediaBrowser.Controller.Entities.TV } } + [IgnoreDataMember] + public override bool IsPreSorted + { + get + { + return true; + } + } + /// /// We want to group into our Series /// diff --git a/MediaBrowser.Controller/Entities/TV/Series.cs b/MediaBrowser.Controller/Entities/TV/Series.cs index 9c2ed27bb..27ca8b18d 100644 --- a/MediaBrowser.Controller/Entities/TV/Series.cs +++ b/MediaBrowser.Controller/Entities/TV/Series.cs @@ -39,6 +39,15 @@ namespace MediaBrowser.Controller.Entities.TV DisplaySpecialsWithSeasons = true; } + [IgnoreDataMember] + public override bool IsPreSorted + { + get + { + return true; + } + } + public bool DisplaySpecialsWithSeasons { get; set; } public List LocalTrailerIds { get; set; } diff --git a/MediaBrowser.Controller/Entities/UserView.cs b/MediaBrowser.Controller/Entities/UserView.cs index f8ca56fa8..34ca85d1d 100644 --- a/MediaBrowser.Controller/Entities/UserView.cs +++ b/MediaBrowser.Controller/Entities/UserView.cs @@ -1,7 +1,6 @@ using MediaBrowser.Controller.Entities.Movies; using MediaBrowser.Controller.Entities.TV; using MediaBrowser.Model.Entities; -using MoreLinq; using System; using System.Collections.Generic; using System.Linq; diff --git a/MediaBrowser.Controller/Playlists/Playlist.cs b/MediaBrowser.Controller/Playlists/Playlist.cs index e20387eba..5ea535f4d 100644 --- a/MediaBrowser.Controller/Playlists/Playlist.cs +++ b/MediaBrowser.Controller/Playlists/Playlist.cs @@ -1,20 +1,87 @@ using MediaBrowser.Controller.Entities; +using MediaBrowser.Model.Entities; +using MediaBrowser.Model.Querying; +using System; using System.Collections.Generic; +using System.Linq; +using System.Runtime.Serialization; namespace MediaBrowser.Controller.Playlists { public class Playlist : Folder { - public List ItemIds { get; set; } + public string OwnerUserId { get; set; } - public Playlist() + public override IEnumerable GetChildren(User user, bool includeLinkedChildren) { - ItemIds = new List(); + return GetPlayableItems(user); } - public override IEnumerable GetChildren(User user, bool includeLinkedChildren) + public override IEnumerable GetRecursiveChildren(User user, bool includeLinkedChildren = true) + { + return GetPlayableItems(user); + } + + public IEnumerable GetManageableItems() + { + return GetLinkedChildren(); + } + + private IEnumerable GetPlayableItems(User user) + { + return GetPlaylistItems(MediaType, base.GetChildren(user, true), user); + } + + public static IEnumerable GetPlaylistItems(string playlistMediaType, IEnumerable inputItems, User user) + { + return inputItems.SelectMany(i => + { + var folder = i as Folder; + + if (folder != null) + { + var items = folder.GetRecursiveChildren(user, true) + .Where(m => !m.IsFolder && string.Equals(m.MediaType, playlistMediaType, StringComparison.OrdinalIgnoreCase)); + + if (!folder.IsPreSorted) + { + items = LibraryManager.Sort(items, user, new[] { ItemSortBy.SortName }, SortOrder.Ascending); + } + + return items; + } + + return new[] { i }; + }); + } + + [IgnoreDataMember] + public override bool IsPreSorted + { + get + { + return true; + } + } + + public string PlaylistMediaType { get; set; } + + public override string MediaType + { + get + { + return PlaylistMediaType; + } + } + + public void SetMediaType(string value) + { + PlaylistMediaType = value; + } + + public override bool IsVisible(User user) { - return base.GetChildren(user, includeLinkedChildren); + return base.IsVisible(user) && string.Equals(user.Id.ToString("N"), OwnerUserId); } } } diff --git a/MediaBrowser.Controller/Playlists/PlaylistCreationOptions.cs b/MediaBrowser.Controller/Playlists/PlaylistCreationOptions.cs index a62cbe12e..1766ba75c 100644 --- a/MediaBrowser.Controller/Playlists/PlaylistCreationOptions.cs +++ b/MediaBrowser.Controller/Playlists/PlaylistCreationOptions.cs @@ -8,6 +8,10 @@ namespace MediaBrowser.Controller.Playlists public List ItemIdList { get; set; } + public string MediaType { get; set; } + + public string UserId { get; set; } + public PlaylistCreationOptions() { ItemIdList = new List(); diff --git a/MediaBrowser.Controller/Providers/BaseItemXmlParser.cs b/MediaBrowser.Controller/Providers/BaseItemXmlParser.cs index 3cb90d360..7e6895ec5 100644 --- a/MediaBrowser.Controller/Providers/BaseItemXmlParser.cs +++ b/MediaBrowser.Controller/Providers/BaseItemXmlParser.cs @@ -1283,6 +1283,78 @@ namespace MediaBrowser.Controller.Providers return new[] { personInfo }; } + protected LinkedChild GetLinkedChild(XmlReader reader) + { + reader.MoveToContent(); + + var linkedItem = new LinkedChild + { + Type = LinkedChildType.Manual + }; + + while (reader.Read()) + { + if (reader.NodeType == XmlNodeType.Element) + { + switch (reader.Name) + { + case "Name": + { + linkedItem.ItemName = reader.ReadElementContentAsString(); + break; + } + + case "Type": + { + linkedItem.ItemType = reader.ReadElementContentAsString(); + break; + } + + case "Year": + { + var val = reader.ReadElementContentAsString(); + + if (!string.IsNullOrWhiteSpace(val)) + { + int rval; + + if (int.TryParse(val, NumberStyles.Integer, _usCulture, out rval)) + { + linkedItem.ItemYear = rval; + } + } + + break; + } + + case "IndexNumber": + { + var val = reader.ReadElementContentAsString(); + + if (!string.IsNullOrWhiteSpace(val)) + { + int rval; + + if (int.TryParse(val, NumberStyles.Integer, _usCulture, out rval)) + { + linkedItem.ItemIndexNumber = rval; + } + } + + break; + } + + default: + reader.Skip(); + break; + } + } + } + + return string.IsNullOrWhiteSpace(linkedItem.ItemName) || string.IsNullOrWhiteSpace(linkedItem.ItemType) ? null : linkedItem; + } + + /// /// Used to split names of comma or pipe delimeted genres and people /// diff --git a/MediaBrowser.Dlna/ContentDirectory/ControlHandler.cs b/MediaBrowser.Dlna/ContentDirectory/ControlHandler.cs index aced2009d..4eb6baeed 100644 --- a/MediaBrowser.Dlna/ContentDirectory/ControlHandler.cs +++ b/MediaBrowser.Dlna/ContentDirectory/ControlHandler.cs @@ -462,7 +462,7 @@ namespace MediaBrowser.Dlna.ContentDirectory items = FilterUnsupportedContent(items); - if (folder is Series || folder is Season || folder is BoxSet) + if (folder.IsPreSorted) { return items; } diff --git a/MediaBrowser.LocalMetadata/MediaBrowser.LocalMetadata.csproj b/MediaBrowser.LocalMetadata/MediaBrowser.LocalMetadata.csproj index 2cc7e989b..b103d9f5a 100644 --- a/MediaBrowser.LocalMetadata/MediaBrowser.LocalMetadata.csproj +++ b/MediaBrowser.LocalMetadata/MediaBrowser.LocalMetadata.csproj @@ -83,6 +83,7 @@ + diff --git a/MediaBrowser.LocalMetadata/Parsers/BoxSetXmlParser.cs b/MediaBrowser.LocalMetadata/Parsers/BoxSetXmlParser.cs index 51a4684d7..85a72cf28 100644 --- a/MediaBrowser.LocalMetadata/Parsers/BoxSetXmlParser.cs +++ b/MediaBrowser.LocalMetadata/Parsers/BoxSetXmlParser.cs @@ -71,59 +71,5 @@ namespace MediaBrowser.LocalMetadata.Parsers item.LinkedChildren = list; } - - private LinkedChild GetLinkedChild(XmlReader reader) - { - reader.MoveToContent(); - - var linkedItem = new LinkedChild - { - Type = LinkedChildType.Manual - }; - - while (reader.Read()) - { - if (reader.NodeType == XmlNodeType.Element) - { - switch (reader.Name) - { - case "Name": - { - linkedItem.ItemName = reader.ReadElementContentAsString(); - break; - } - - case "Type": - { - linkedItem.ItemType = reader.ReadElementContentAsString(); - break; - } - - case "Year": - { - var val = reader.ReadElementContentAsString(); - - if (!string.IsNullOrWhiteSpace(val)) - { - int rval; - - if (int.TryParse(val, NumberStyles.Integer, UsCulture, out rval)) - { - linkedItem.ItemYear = rval; - } - } - - break; - } - - default: - reader.Skip(); - break; - } - } - } - - return string.IsNullOrWhiteSpace(linkedItem.ItemName) || string.IsNullOrWhiteSpace(linkedItem.ItemType) ? null : linkedItem; - } } } diff --git a/MediaBrowser.LocalMetadata/Savers/FolderXmlSaver.cs b/MediaBrowser.LocalMetadata/Savers/FolderXmlSaver.cs index 6dd65b69c..c38a33c40 100644 --- a/MediaBrowser.LocalMetadata/Savers/FolderXmlSaver.cs +++ b/MediaBrowser.LocalMetadata/Savers/FolderXmlSaver.cs @@ -1,12 +1,13 @@ -using System.Collections.Generic; -using System.IO; -using System.Text; -using System.Threading; -using MediaBrowser.Controller.Entities; +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.Playlists; +using System.Collections.Generic; +using System.IO; +using System.Text; +using System.Threading; namespace MediaBrowser.LocalMetadata.Savers { @@ -37,7 +38,8 @@ namespace MediaBrowser.LocalMetadata.Savers { if (!(item is Series) && !(item is BoxSet) && !(item is MusicArtist) && !(item is MusicAlbum) && !(item is Season) && - !(item is GameSystem)) + !(item is GameSystem) && + !(item is Playlist)) { return updateType >= ItemUpdateType.MetadataDownload; } diff --git a/MediaBrowser.LocalMetadata/Savers/PlaylistXmlSaver.cs b/MediaBrowser.LocalMetadata/Savers/PlaylistXmlSaver.cs new file mode 100644 index 000000000..cdb3a2500 --- /dev/null +++ b/MediaBrowser.LocalMetadata/Savers/PlaylistXmlSaver.cs @@ -0,0 +1,68 @@ +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Entities.Movies; +using MediaBrowser.Controller.Library; +using System.Collections.Generic; +using System.IO; +using System.Text; +using System.Threading; + +namespace MediaBrowser.LocalMetadata.Savers +{ + public class PlaylistXmlSaver : IMetadataFileSaver + { + public string Name + { + get + { + return "Media Browser Xml"; + } + } + + /// + /// Determines whether [is enabled for] [the specified item]. + /// + /// The item. + /// Type of the update. + /// true if [is enabled for] [the specified item]; otherwise, false. + public bool IsEnabledFor(IHasMetadata item, ItemUpdateType updateType) + { + if (!item.SupportsLocalMetadata) + { + return false; + } + + return item is BoxSet && updateType >= ItemUpdateType.MetadataDownload; + } + + /// + /// Saves the specified item. + /// + /// The item. + /// The cancellation token. + /// Task. + public void Save(IHasMetadata item, CancellationToken cancellationToken) + { + var builder = new StringBuilder(); + + builder.Append(""); + + XmlSaverHelpers.AddCommonNodes((BoxSet)item, builder); + + builder.Append(""); + + var xmlFilePath = GetSavePath(item); + + XmlSaverHelpers.Save(builder, xmlFilePath, new List { }); + } + + /// + /// Gets the save path. + /// + /// The item. + /// System.String. + public string GetSavePath(IHasMetadata item) + { + return Path.Combine(item.Path, "playlist.xml"); + } + } +} diff --git a/MediaBrowser.LocalMetadata/Savers/XmlSaverHelpers.cs b/MediaBrowser.LocalMetadata/Savers/XmlSaverHelpers.cs index 1a2c341da..491592989 100644 --- a/MediaBrowser.LocalMetadata/Savers/XmlSaverHelpers.cs +++ b/MediaBrowser.LocalMetadata/Savers/XmlSaverHelpers.cs @@ -10,6 +10,7 @@ using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.Movies; using MediaBrowser.Controller.Entities.TV; using MediaBrowser.Controller.Persistence; +using MediaBrowser.Controller.Playlists; using MediaBrowser.Model.Entities; namespace MediaBrowser.LocalMetadata.Savers @@ -109,7 +110,8 @@ namespace MediaBrowser.LocalMetadata.Savers "VoteCount", "Website", "Zap2ItId", - "CollectionItems" + "CollectionItems", + "PlaylistItems" }.ToDictionary(i => i, StringComparer.OrdinalIgnoreCase); @@ -631,10 +633,16 @@ namespace MediaBrowser.LocalMetadata.Savers builder.Append(""); } - var folder = item as BoxSet; - if (folder != null) + var boxset = item as BoxSet; + if (boxset != null) { - AddCollectionItems(folder, builder); + AddLinkedChildren(boxset, builder, "CollectionItems", "CollectionItem"); + } + + var playlist = item as Playlist; + if (playlist != null) + { + AddLinkedChildren(playlist, builder, "PlaylistItems", "PlaylistItem"); } } @@ -693,7 +701,7 @@ namespace MediaBrowser.LocalMetadata.Savers } } - public static void AddCollectionItems(Folder item, StringBuilder builder) + public static void AddLinkedChildren(Folder item, StringBuilder builder, string pluralNodeName, string singularNodeName) { var items = item.LinkedChildren .Where(i => i.Type == LinkedChildType.Manual && !string.IsNullOrWhiteSpace(i.ItemName)) @@ -704,10 +712,10 @@ namespace MediaBrowser.LocalMetadata.Savers return; } - builder.Append(""); + builder.Append("<" + pluralNodeName + ">"); foreach (var link in items) { - builder.Append(""); + builder.Append("<" + singularNodeName + ">"); builder.Append("" + SecurityElement.Escape(link.ItemName) + ""); builder.Append("" + SecurityElement.Escape(link.ItemType) + ""); @@ -717,9 +725,14 @@ namespace MediaBrowser.LocalMetadata.Savers builder.Append("" + SecurityElement.Escape(link.ItemYear.Value.ToString(UsCulture)) + ""); } - builder.Append(""); + if (link.ItemIndexNumber.HasValue) + { + builder.Append("" + SecurityElement.Escape(link.ItemIndexNumber.Value.ToString(UsCulture)) + ""); + } + + builder.Append(""); } - builder.Append(""); + builder.Append(""); } } } diff --git a/MediaBrowser.Model/Dto/BaseItemDto.cs b/MediaBrowser.Model/Dto/BaseItemDto.cs index d138ddf9b..9581b5740 100644 --- a/MediaBrowser.Model/Dto/BaseItemDto.cs +++ b/MediaBrowser.Model/Dto/BaseItemDto.cs @@ -594,7 +594,19 @@ namespace MediaBrowser.Model.Dto /// /// The parent thumb image tag. public string ParentThumbImageTag { get; set; } - + + /// + /// Gets or sets the parent primary image item identifier. + /// + /// The parent primary image item identifier. + public string ParentPrimaryImageItemId { get; set; } + + /// + /// Gets or sets the parent primary image tag. + /// + /// The parent primary image tag. + public string ParentPrimaryImageTag { get; set; } + /// /// Gets or sets the chapters. /// diff --git a/MediaBrowser.Server.Implementations/Channels/ChannelDownloadScheduledTask.cs b/MediaBrowser.Server.Implementations/Channels/ChannelDownloadScheduledTask.cs index af1bd9427..567092cae 100644 --- a/MediaBrowser.Server.Implementations/Channels/ChannelDownloadScheduledTask.cs +++ b/MediaBrowser.Server.Implementations/Channels/ChannelDownloadScheduledTask.cs @@ -239,7 +239,7 @@ namespace MediaBrowser.Server.Implementations.Channels throw new ApplicationException("Unexpected response type encountered: " + response.ContentType); } - File.Move(response.TempFilePath, destination); + File.Copy(response.TempFilePath, destination, true); await RefreshMediaSourceItem(destination, cancellationToken).ConfigureAwait(false); diff --git a/MediaBrowser.Server.Implementations/Collections/CollectionManager.cs b/MediaBrowser.Server.Implementations/Collections/CollectionManager.cs index 6a87453ab..fe4b16645 100644 --- a/MediaBrowser.Server.Implementations/Collections/CollectionManager.cs +++ b/MediaBrowser.Server.Implementations/Collections/CollectionManager.cs @@ -162,13 +162,7 @@ namespace MediaBrowser.Server.Implementations.Collections throw new ArgumentException("Item already exists in collection"); } - list.Add(new LinkedChild - { - ItemName = item.Name, - ItemYear = item.ProductionYear, - ItemType = item.GetType().Name, - Type = LinkedChildType.Manual - }); + list.Add(LinkedChild.Create(item)); var supportsGrouping = item as ISupportsBoxSetGrouping; diff --git a/MediaBrowser.Server.Implementations/Collections/ManualCollectionsFolder.cs b/MediaBrowser.Server.Implementations/Collections/ManualCollectionsFolder.cs index aaa02c720..b02c52874 100644 --- a/MediaBrowser.Server.Implementations/Collections/ManualCollectionsFolder.cs +++ b/MediaBrowser.Server.Implementations/Collections/ManualCollectionsFolder.cs @@ -8,6 +8,7 @@ namespace MediaBrowser.Server.Implementations.Collections public ManualCollectionsFolder() { Name = "Collections"; + DisplayMediaType = "CollectionFolder"; } public override bool IsVisible(User user) diff --git a/MediaBrowser.Server.Implementations/Dto/DtoService.cs b/MediaBrowser.Server.Implementations/Dto/DtoService.cs index afcfde556..e3a386841 100644 --- a/MediaBrowser.Server.Implementations/Dto/DtoService.cs +++ b/MediaBrowser.Server.Implementations/Dto/DtoService.cs @@ -11,6 +11,7 @@ using MediaBrowser.Controller.Entities.TV; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.LiveTv; using MediaBrowser.Controller.Persistence; +using MediaBrowser.Controller.Playlists; using MediaBrowser.Controller.Providers; using MediaBrowser.Controller.Sync; using MediaBrowser.Model.Drawing; @@ -179,6 +180,11 @@ namespace MediaBrowser.Server.Implementations.Dto } } + if (item is Playlist) + { + AttachLinkedChildImages(dto, (Folder)item, user); + } + return dto; } @@ -819,7 +825,7 @@ namespace MediaBrowser.Server.Implementations.Dto dto.DisplayOrder = hasDisplayOrder.DisplayOrder; } - var collectionFolder = item as CollectionFolder; + var collectionFolder = item as ICollectionFolder; if (collectionFolder != null) { dto.CollectionType = collectionFolder.CollectionType; @@ -1211,6 +1217,45 @@ namespace MediaBrowser.Server.Implementations.Dto } } + private void AttachLinkedChildImages(BaseItemDto dto, Folder folder, User user) + { + List linkedChildren = null; + + if (dto.BackdropImageTags.Count == 0) + { + if (linkedChildren == null) + { + linkedChildren = user == null + ? folder.GetRecursiveChildren().ToList() + : folder.GetRecursiveChildren(user, true).ToList(); + } + var parentWithBackdrop = linkedChildren.FirstOrDefault(i => i.GetImages(ImageType.Backdrop).Any()); + + if (parentWithBackdrop != null) + { + dto.ParentBackdropItemId = GetDtoId(parentWithBackdrop); + dto.ParentBackdropImageTags = GetBackdropImageTags(parentWithBackdrop); + } + } + + if (!dto.ImageTags.ContainsKey(ImageType.Primary)) + { + if (linkedChildren == null) + { + linkedChildren = user == null + ? folder.GetRecursiveChildren().ToList() + : folder.GetRecursiveChildren(user, true).ToList(); + } + var parentWithImage = linkedChildren.FirstOrDefault(i => i.GetImages(ImageType.Primary).Any()); + + if (parentWithImage != null) + { + dto.ParentPrimaryImageItemId = GetDtoId(parentWithImage); + dto.ParentPrimaryImageTag = GetImageCacheTag(parentWithImage, ImageType.Primary); + } + } + } + private string GetMappedPath(IHasMetadata item) { var path = item.Path; diff --git a/MediaBrowser.Server.Implementations/FileOrganization/OrganizerScheduledTask.cs b/MediaBrowser.Server.Implementations/FileOrganization/OrganizerScheduledTask.cs index 4e182ea88..1c4ccb141 100644 --- a/MediaBrowser.Server.Implementations/FileOrganization/OrganizerScheduledTask.cs +++ b/MediaBrowser.Server.Implementations/FileOrganization/OrganizerScheduledTask.cs @@ -75,7 +75,7 @@ namespace MediaBrowser.Server.Implementations.FileOrganization public bool IsEnabled { - get { return !GetTvOptions().IsEnabled; } + get { return GetTvOptions().IsEnabled; } } } } diff --git a/MediaBrowser.Server.Implementations/Library/Resolvers/PlaylistResolver.cs b/MediaBrowser.Server.Implementations/Library/Resolvers/PlaylistResolver.cs new file mode 100644 index 000000000..7eff53ce1 --- /dev/null +++ b/MediaBrowser.Server.Implementations/Library/Resolvers/PlaylistResolver.cs @@ -0,0 +1,38 @@ +using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Playlists; +using System; +using System.IO; + +namespace MediaBrowser.Server.Implementations.Library.Resolvers +{ + public class PlaylistResolver : FolderResolver + { + /// + /// Resolves the specified args. + /// + /// The args. + /// BoxSet. + protected override Playlist Resolve(ItemResolveArgs args) + { + // It's a boxset if all of the following conditions are met: + // Is a Directory + // Contains [playlist] in the path + if (args.IsDirectory) + { + var filename = Path.GetFileName(args.Path); + + if (string.IsNullOrEmpty(filename)) + { + return null; + } + + if (filename.IndexOf("[playlist]", StringComparison.OrdinalIgnoreCase) != -1) + { + return new Playlist { Path = args.Path }; + } + } + + return null; + } + } +} diff --git a/MediaBrowser.Server.Implementations/Localization/JavaScript/javascript.json b/MediaBrowser.Server.Implementations/Localization/JavaScript/javascript.json index 3a5b91abd..3c1748ef1 100644 --- a/MediaBrowser.Server.Implementations/Localization/JavaScript/javascript.json +++ b/MediaBrowser.Server.Implementations/Localization/JavaScript/javascript.json @@ -323,6 +323,13 @@ "HeaderSelectPlayer": "Select Player:", "ButtonSelect": "Select", "ButtonNew": "New", - "MessageInternetExplorerWebm": "For best results with Internet Explorer please install the WebM plugin for IE.", - "HeaderVideoError": "Video Error" + "MessageInternetExplorerWebm": "For best results with Internet Explorer please install the WebM plugin for IE.", + "HeaderVideoError": "Video Error", + "ButtonAddToPlaylist": "Add to playlist", + "HeaderAddToPlaylist": "Add to Playlist", + "LabelName": "Name:", + "ButtonSubmit": "Submit", + "LabelSelectPlaylist": "Playlist:", + "OptionNewPlaylist": "New playlist...", + "MessageAddedToPlaylistSuccess": "Ok" } diff --git a/MediaBrowser.Server.Implementations/Localization/Server/server.json b/MediaBrowser.Server.Implementations/Localization/Server/server.json index caf8860fc..471620948 100644 --- a/MediaBrowser.Server.Implementations/Localization/Server/server.json +++ b/MediaBrowser.Server.Implementations/Localization/Server/server.json @@ -808,6 +808,8 @@ "TabNextUp": "Next Up", "MessageNoMovieSuggestionsAvailable": "No movie suggestions are currently available. Start watching and rating your movies, and then come back to view your recommendations.", "MessageNoCollectionsAvailable": "Collections allow you to enjoy personalized groupings of Movies, Series, Albums, Books and Games. Click the New button to start creating Collections.", + "MessageNoPlaylistsAvailable": "Playlists allow you to create lists of content to play consecutively at a time. To add items to playlists, right click or tap and hold, then select Add to Playlist.", + "MessageNoPlaylistItemsAvailable": "This playlist is currently empty.", "HeaderWelcomeToMediaBrowserWebClient": "Welcome to the Media Browser Web Client", "ButtonDismiss": "Dismiss", "MessageLearnHowToCustomize": "Learn how to customize this page to your own personal tastes. Click your user icon in the top right corner of the screen to view and update your preferences.", @@ -915,5 +917,7 @@ "OptionProtocolHls": "Http Live Streaming", "LabelContext": "Context:", "OptionContextStreaming": "Streaming", - "OptionContextStatic": "Sync" + "OptionContextStatic": "Sync", + "ButtonAddToPlaylist": "Add to playlist", + "TabPlaylists": "Playlists" } diff --git a/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj b/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj index 8bfbc0855..c60835ee6 100644 --- a/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj +++ b/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj @@ -166,6 +166,7 @@ + diff --git a/MediaBrowser.Server.Implementations/Playlists/ManualPlaylistsFolder.cs b/MediaBrowser.Server.Implementations/Playlists/ManualPlaylistsFolder.cs index 3b46191b1..a87edde7b 100644 --- a/MediaBrowser.Server.Implementations/Playlists/ManualPlaylistsFolder.cs +++ b/MediaBrowser.Server.Implementations/Playlists/ManualPlaylistsFolder.cs @@ -1,5 +1,7 @@ -using MediaBrowser.Common.Configuration; +using System.Collections.Generic; +using MediaBrowser.Common.Configuration; using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Playlists; using System.IO; using System.Linq; @@ -14,8 +16,15 @@ namespace MediaBrowser.Server.Implementations.Playlists public override bool IsVisible(User user) { - return GetChildren(user, true).Any() && - base.IsVisible(user); + return base.IsVisible(user) && GetRecursiveChildren(user, false) + .OfType() + .Any(i => string.Equals(i.OwnerUserId, user.Id.ToString("N"))); + } + + protected override IEnumerable GetEligibleChildrenForRecursiveChildren(User user) + { + return RecursiveChildren + .OfType(); } public override bool IsHidden @@ -48,7 +57,7 @@ namespace MediaBrowser.Server.Implementations.Playlists public BasePluginFolder GetFolder() { - var path = Path.Combine(_appPaths.DataPath, "playlists"); + var path = Path.Combine(_appPaths.CachePath, "playlists"); Directory.CreateDirectory(path); diff --git a/MediaBrowser.Server.Implementations/Playlists/PlaylistManager.cs b/MediaBrowser.Server.Implementations/Playlists/PlaylistManager.cs index 92f01305c..79b673283 100644 --- a/MediaBrowser.Server.Implementations/Playlists/PlaylistManager.cs +++ b/MediaBrowser.Server.Implementations/Playlists/PlaylistManager.cs @@ -1,8 +1,10 @@ using MediaBrowser.Common.IO; using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Entities.Audio; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Playlists; using MediaBrowser.Controller.Providers; +using MediaBrowser.Model.Entities; using MediaBrowser.Model.Logging; using System; using System.Collections.Generic; @@ -41,9 +43,6 @@ namespace MediaBrowser.Server.Implementations.Playlists { var name = options.Name; - // Need to use the [boxset] suffix - // If internet metadata is not found, or if xml saving is off there will be no collection.xml - // This could cause it to get re-resolved as a plain folder var folderName = _fileSystem.GetValidFilename(name) + " [playlist]"; var parentFolder = GetPlaylistsFolder(null); @@ -53,7 +52,55 @@ namespace MediaBrowser.Server.Implementations.Playlists throw new ArgumentException(); } + if (string.IsNullOrWhiteSpace(options.MediaType)) + { + foreach (var itemId in options.ItemIdList) + { + var item = _libraryManager.GetItemById(itemId); + + if (item == null) + { + throw new ArgumentException("No item exists with the supplied Id"); + } + + if (!string.IsNullOrWhiteSpace(item.MediaType)) + { + options.MediaType = item.MediaType; + } + else if (item is MusicArtist || item is MusicAlbum || item is MusicGenre) + { + options.MediaType = MediaType.Audio; + } + else if (item is Genre) + { + options.MediaType = MediaType.Video; + } + else + { + var folder = item as Folder; + if (folder != null) + { + options.MediaType = folder.GetRecursiveChildren() + .Where(i => !i.IsFolder) + .Select(i => i.MediaType) + .FirstOrDefault(i => !string.IsNullOrWhiteSpace(i)); + } + } + + if (!string.IsNullOrWhiteSpace(options.MediaType)) + { + break; + } + } + } + + if (string.IsNullOrWhiteSpace(options.MediaType)) + { + throw new ArgumentException("A playlist media type is required."); + } + var path = Path.Combine(parentFolder.Path, folderName); + path = GetTargetPath(path); _iLibraryMonitor.ReportFileSystemChangeBeginning(path); @@ -61,24 +108,27 @@ namespace MediaBrowser.Server.Implementations.Playlists { Directory.CreateDirectory(path); - var collection = new Playlist + var playlist = new Playlist { Name = name, Parent = parentFolder, - Path = path + Path = path, + OwnerUserId = options.UserId }; - await parentFolder.AddChild(collection, CancellationToken.None).ConfigureAwait(false); + playlist.SetMediaType(options.MediaType); + + await parentFolder.AddChild(playlist, CancellationToken.None).ConfigureAwait(false); - await collection.RefreshMetadata(new MetadataRefreshOptions(), CancellationToken.None) + await playlist.RefreshMetadata(new MetadataRefreshOptions { ForceSave = true }, CancellationToken.None) .ConfigureAwait(false); if (options.ItemIdList.Count > 0) { - await AddToPlaylist(collection.Id.ToString("N"), options.ItemIdList); + await AddToPlaylist(playlist.Id.ToString("N"), options.ItemIdList); } - return collection; + return playlist; } finally { @@ -87,11 +137,28 @@ namespace MediaBrowser.Server.Implementations.Playlists } } + private string GetTargetPath(string path) + { + while (Directory.Exists(path)) + { + path += "1"; + } + + return path; + } + + private IEnumerable GetPlaylistItems(IEnumerable itemIds, string playlistMediaType, User user) + { + var items = itemIds.Select(i => _libraryManager.GetItemById(i)).Where(i => i != null); + + return Playlist.GetPlaylistItems(playlistMediaType, items, user); + } + public async Task AddToPlaylist(string playlistId, IEnumerable itemIds) { - var collection = _libraryManager.GetItemById(playlistId) as Playlist; + var playlist = _libraryManager.GetItemById(playlistId) as Playlist; - if (collection == null) + if (playlist == null) { throw new ArgumentException("No Playlist exists with the supplied Id"); } @@ -110,17 +177,17 @@ namespace MediaBrowser.Server.Implementations.Playlists itemList.Add(item); - list.Add(new LinkedChild - { - Type = LinkedChildType.Manual, - ItemId = item.Id - }); + list.Add(LinkedChild.Create(item)); } - collection.LinkedChildren.AddRange(list); + playlist.LinkedChildren.AddRange(list); + + await playlist.UpdateToRepository(ItemUpdateType.MetadataEdit, CancellationToken.None).ConfigureAwait(false); + await playlist.RefreshMetadata(new MetadataRefreshOptions{ + + ForceSave = true - await collection.UpdateToRepository(ItemUpdateType.MetadataEdit, CancellationToken.None).ConfigureAwait(false); - await collection.RefreshMetadata(CancellationToken.None).ConfigureAwait(false); + }, CancellationToken.None).ConfigureAwait(false); } public Task RemoveFromPlaylist(string playlistId, IEnumerable indeces) diff --git a/MediaBrowser.WebDashboard/Api/DashboardService.cs b/MediaBrowser.WebDashboard/Api/DashboardService.cs index 9235beacf..c05819361 100644 --- a/MediaBrowser.WebDashboard/Api/DashboardService.cs +++ b/MediaBrowser.WebDashboard/Api/DashboardService.cs @@ -528,6 +528,7 @@ namespace MediaBrowser.WebDashboard.Api "chromecast.js", "backdrops.js", "sync.js", + "playlistmanager.js", "mediaplayer.js", "mediaplayer-video.js", @@ -621,6 +622,9 @@ namespace MediaBrowser.WebDashboard.Api "notificationsetting.js", "notificationsettings.js", "playlist.js", + "playlists.js", + "playlistedit.js", + "plugincatalogpage.js", "pluginspage.js", "remotecontrol.js", @@ -676,7 +680,6 @@ namespace MediaBrowser.WebDashboard.Api "librarymenu.css", "librarybrowser.css", "detailtable.css", - "posteritem.css", "card.css", "tileitem.css", "metadataeditor.css", diff --git a/MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj b/MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj index 4bc05e0c1..c0fe0261b 100644 --- a/MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj +++ b/MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj @@ -347,6 +347,12 @@ PreserveNewest + + PreserveNewest + + + PreserveNewest + PreserveNewest @@ -527,9 +533,6 @@ PreserveNewest - - PreserveNewest - PreserveNewest @@ -665,6 +668,15 @@ PreserveNewest + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + PreserveNewest -- cgit v1.2.3 From e84ba17b9f48a3bc8811b1a89c54c25bc6607599 Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Sun, 10 Aug 2014 18:13:17 -0400 Subject: add activity log feature --- MediaBrowser.Api/ChannelService.cs | 2 +- MediaBrowser.Api/Images/ImageService.cs | 1 + MediaBrowser.Api/MediaBrowser.Api.csproj | 11 +- .../Session/SessionInfoWebSocketListener.cs | 116 +++++ MediaBrowser.Api/Session/SessionsService.cs | 506 +++++++++++++++++++ MediaBrowser.Api/SessionsService.cs | 506 ------------------- MediaBrowser.Api/System/ActivityLogService.cs | 44 ++ .../System/SystemInfoWebSocketListener.cs | 49 ++ MediaBrowser.Api/System/SystemService.cs | 178 +++++++ MediaBrowser.Api/SystemService.cs | 177 ------- MediaBrowser.Api/UserService.cs | 21 +- .../WebSocket/SessionInfoWebSocketListener.cs | 116 ----- .../WebSocket/SystemInfoWebSocketListener.cs | 49 -- .../Configuration/BaseConfigurationManager.cs | 12 + .../ScheduledTasks/ScheduledTaskWorker.cs | 1 + .../Configuration/IConfigurationManager.cs | 5 + .../ScheduledTasks/IConfigurableScheduledTask.cs | 5 + .../Activity/IActivityManager.cs | 17 + .../Activity/IActivityRepository.cs | 13 + .../Configuration/IServerConfigurationManager.cs | 7 - MediaBrowser.Controller/Entities/Movies/BoxSet.cs | 14 +- MediaBrowser.Controller/Library/IUserManager.cs | 1 + MediaBrowser.Controller/Library/TVUtils.cs | 2 +- .../MediaBrowser.Controller.csproj | 6 +- .../Notifications/INotificationsRepository.cs | 12 - .../Session/AuthenticationRequest.cs | 14 + MediaBrowser.Controller/Session/ISessionManager.cs | 28 +- .../Subtitles/ISubtitleManager.cs | 17 +- .../Subtitles/SubtitleDownloadEventArgs.cs | 27 + MediaBrowser.LocalMetadata/BaseXmlProvider.cs | 4 +- .../MediaBrowser.Model.Portable.csproj | 9 +- .../MediaBrowser.Model.net35.csproj | 9 +- MediaBrowser.Model/Activity/ActivityLogEntry.cs | 62 +++ MediaBrowser.Model/ApiClient/IApiClient.cs | 2 +- MediaBrowser.Model/Channels/ChannelItemQuery.cs | 5 +- .../Configuration/ServerConfiguration.cs | 3 +- .../Configuration/SubtitleOptions.cs | 22 - MediaBrowser.Model/Events/GenericEventArgs.cs | 16 + MediaBrowser.Model/MediaBrowser.Model.csproj | 5 +- MediaBrowser.Model/Providers/SubtitleOptions.cs | 22 + MediaBrowser.Model/Tasks/TaskResult.cs | 6 + .../MediaBrowser.Providers.csproj | 3 +- .../MediaInfo/FFProbeVideoInfo.cs | 19 +- .../MediaInfo/SubtitleScheduledTask.cs | 30 +- .../Subtitles/ConfigurationExtension.cs | 29 ++ .../Subtitles/OpenSubtitleDownloader.cs | 35 +- .../Subtitles/SubtitleManager.cs | 69 ++- .../Activity/ActivityManager.cs | 40 ++ .../Activity/ActivityRepository.cs | 293 +++++++++++ .../EntryPoints/ActivityLogEntryPoint.cs | 543 +++++++++++++++++++++ .../EntryPoints/Notifications/Notifications.cs | 8 +- .../EntryPoints/Notifications/WebSocketNotifier.cs | 9 - .../EntryPoints/ServerEventNotifier.cs | 13 +- .../FileOrganization/OrganizerScheduledTask.cs | 7 +- .../SocketSharp/WebSocketSharpListener.cs | 2 +- .../Library/UserManager.cs | 7 +- .../Localization/Server/server.json | 138 ++++-- .../MediaBrowser.Server.Implementations.csproj | 5 +- .../Notifications/SqliteNotificationsRepository.cs | 66 --- .../ServerManager/ServerManager.cs | 2 + .../Session/SessionManager.cs | 51 +- .../Session/SessionWebSocketListener.cs | 1 - .../Sync/SyncRepository.cs | 46 +- MediaBrowser.ServerApplication/ApplicationHost.cs | 22 + 64 files changed, 2398 insertions(+), 1162 deletions(-) create mode 100644 MediaBrowser.Api/Session/SessionInfoWebSocketListener.cs create mode 100644 MediaBrowser.Api/Session/SessionsService.cs delete mode 100644 MediaBrowser.Api/SessionsService.cs create mode 100644 MediaBrowser.Api/System/ActivityLogService.cs create mode 100644 MediaBrowser.Api/System/SystemInfoWebSocketListener.cs create mode 100644 MediaBrowser.Api/System/SystemService.cs delete mode 100644 MediaBrowser.Api/SystemService.cs delete mode 100644 MediaBrowser.Api/WebSocket/SessionInfoWebSocketListener.cs delete mode 100644 MediaBrowser.Api/WebSocket/SystemInfoWebSocketListener.cs create mode 100644 MediaBrowser.Controller/Activity/IActivityManager.cs create mode 100644 MediaBrowser.Controller/Activity/IActivityRepository.cs create mode 100644 MediaBrowser.Controller/Session/AuthenticationRequest.cs create mode 100644 MediaBrowser.Controller/Subtitles/SubtitleDownloadEventArgs.cs create mode 100644 MediaBrowser.Model/Activity/ActivityLogEntry.cs delete mode 100644 MediaBrowser.Model/Configuration/SubtitleOptions.cs create mode 100644 MediaBrowser.Model/Providers/SubtitleOptions.cs create mode 100644 MediaBrowser.Providers/Subtitles/ConfigurationExtension.cs create mode 100644 MediaBrowser.Server.Implementations/Activity/ActivityManager.cs create mode 100644 MediaBrowser.Server.Implementations/Activity/ActivityRepository.cs create mode 100644 MediaBrowser.Server.Implementations/EntryPoints/ActivityLogEntryPoint.cs (limited to 'MediaBrowser.Server.Implementations/FileOrganization') diff --git a/MediaBrowser.Api/ChannelService.cs b/MediaBrowser.Api/ChannelService.cs index 2cc046f1d..3736814e5 100644 --- a/MediaBrowser.Api/ChannelService.cs +++ b/MediaBrowser.Api/ChannelService.cs @@ -230,7 +230,7 @@ namespace MediaBrowser.Api SortOrder = request.SortOrder, SortBy = (request.SortBy ?? string.Empty).Split(',').Where(i => !string.IsNullOrWhiteSpace(i)).ToArray(), Filters = request.GetFilters().ToArray(), - Fields = request.GetItemFields().ToList() + Fields = request.GetItemFields().ToArray() }, CancellationToken.None).Result; diff --git a/MediaBrowser.Api/Images/ImageService.cs b/MediaBrowser.Api/Images/ImageService.cs index ea7eaa947..43e9ad3ef 100644 --- a/MediaBrowser.Api/Images/ImageService.cs +++ b/MediaBrowser.Api/Images/ImageService.cs @@ -40,6 +40,7 @@ namespace MediaBrowser.Api.Images [Route("/Items/{Id}/Images/{Type}", "GET")] [Route("/Items/{Id}/Images/{Type}/{Index}", "GET")] [Route("/Items/{Id}/Images/{Type}/{Index}/{Tag}/{Format}/{MaxWidth}/{MaxHeight}", "GET")] + [Route("/Items/{Id}/Images/{Type}/{Index}/{Tag}/{Format}/{MaxWidth}/{MaxHeight}", "HEAD")] [Api(Description = "Gets an item image")] public class GetItemImage : ImageRequest { diff --git a/MediaBrowser.Api/MediaBrowser.Api.csproj b/MediaBrowser.Api/MediaBrowser.Api.csproj index 9a7d28ec4..df689cb24 100644 --- a/MediaBrowser.Api/MediaBrowser.Api.csproj +++ b/MediaBrowser.Api/MediaBrowser.Api.csproj @@ -118,10 +118,11 @@ - + - + + @@ -139,8 +140,8 @@ - - + + @@ -173,4 +174,4 @@ --> - + \ No newline at end of file diff --git a/MediaBrowser.Api/Session/SessionInfoWebSocketListener.cs b/MediaBrowser.Api/Session/SessionInfoWebSocketListener.cs new file mode 100644 index 000000000..e6b525e53 --- /dev/null +++ b/MediaBrowser.Api/Session/SessionInfoWebSocketListener.cs @@ -0,0 +1,116 @@ +using MediaBrowser.Common.Net; +using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Session; +using MediaBrowser.Model.Logging; +using MediaBrowser.Model.Session; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace MediaBrowser.Api.Session +{ + /// + /// Class SessionInfoWebSocketListener + /// + class SessionInfoWebSocketListener : BasePeriodicWebSocketListener, WebSocketListenerState> + { + /// + /// Gets the name. + /// + /// The name. + protected override string Name + { + get { return "Sessions"; } + } + + /// + /// The _kernel + /// + private readonly ISessionManager _sessionManager; + + /// + /// Initializes a new instance of the class. + /// + /// The logger. + /// The session manager. + public SessionInfoWebSocketListener(ILogger logger, ISessionManager sessionManager) + : base(logger) + { + _sessionManager = sessionManager; + + _sessionManager.SessionStarted += _sessionManager_SessionStarted; + _sessionManager.SessionEnded += _sessionManager_SessionEnded; + _sessionManager.PlaybackStart += _sessionManager_PlaybackStart; + _sessionManager.PlaybackStopped += _sessionManager_PlaybackStopped; + _sessionManager.PlaybackProgress += _sessionManager_PlaybackProgress; + _sessionManager.CapabilitiesChanged += _sessionManager_CapabilitiesChanged; + _sessionManager.SessionActivity += _sessionManager_SessionActivity; + } + + void _sessionManager_SessionActivity(object sender, SessionEventArgs e) + { + SendData(false); + } + + void _sessionManager_CapabilitiesChanged(object sender, SessionEventArgs e) + { + SendData(true); + } + + void _sessionManager_PlaybackProgress(object sender, PlaybackProgressEventArgs e) + { + SendData(false); + } + + void _sessionManager_PlaybackStopped(object sender, PlaybackStopEventArgs e) + { + SendData(true); + } + + void _sessionManager_PlaybackStart(object sender, PlaybackProgressEventArgs e) + { + SendData(true); + } + + void _sessionManager_SessionEnded(object sender, SessionEventArgs e) + { + SendData(true); + } + + void _sessionManager_SessionStarted(object sender, SessionEventArgs e) + { + SendData(true); + } + + /// + /// Gets the data to send. + /// + /// The state. + /// Task{SystemInfo}. + protected override Task> GetDataToSend(WebSocketListenerState state) + { + return Task.FromResult(_sessionManager.Sessions.Where(i => i.IsActive).Select(_sessionManager.GetSessionInfoDto)); + } + + protected override bool SendOnTimer + { + get + { + return false; + } + } + + protected override void Dispose(bool dispose) + { + _sessionManager.SessionStarted -= _sessionManager_SessionStarted; + _sessionManager.SessionEnded -= _sessionManager_SessionEnded; + _sessionManager.PlaybackStart -= _sessionManager_PlaybackStart; + _sessionManager.PlaybackStopped -= _sessionManager_PlaybackStopped; + _sessionManager.PlaybackProgress -= _sessionManager_PlaybackProgress; + _sessionManager.CapabilitiesChanged -= _sessionManager_CapabilitiesChanged; + _sessionManager.SessionActivity -= _sessionManager_SessionActivity; + + base.Dispose(dispose); + } + } +} diff --git a/MediaBrowser.Api/Session/SessionsService.cs b/MediaBrowser.Api/Session/SessionsService.cs new file mode 100644 index 000000000..e2c95eba9 --- /dev/null +++ b/MediaBrowser.Api/Session/SessionsService.cs @@ -0,0 +1,506 @@ +using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Net; +using MediaBrowser.Controller.Security; +using MediaBrowser.Controller.Session; +using MediaBrowser.Model.Session; +using ServiceStack; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace MediaBrowser.Api.Session +{ + /// + /// Class GetSessions + /// + [Route("/Sessions", "GET", Summary = "Gets a list of sessions")] + [Authenticated] + public class GetSessions : IReturn> + { + [ApiMember(Name = "ControllableByUserId", Description = "Optional. Filter by sessions that a given user is allowed to remote control.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] + public Guid? ControllableByUserId { get; set; } + + [ApiMember(Name = "DeviceId", Description = "Optional. Filter by device id.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] + public string DeviceId { get; set; } + } + + /// + /// Class DisplayContent + /// + [Route("/Sessions/{Id}/Viewing", "POST", Summary = "Instructs a session to browse to an item or view")] + [Authenticated] + public class DisplayContent : IReturnVoid + { + /// + /// Gets or sets the id. + /// + /// The id. + [ApiMember(Name = "Id", Description = "Session Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] + public string Id { get; set; } + + /// + /// Artist, Genre, Studio, Person, or any kind of BaseItem + /// + /// The type of the item. + [ApiMember(Name = "ItemType", Description = "The type of item to browse to.", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "POST")] + public string ItemType { get; set; } + + /// + /// Artist name, genre name, item Id, etc + /// + /// The item identifier. + [ApiMember(Name = "ItemId", Description = "The Id of the item.", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "POST")] + public string ItemId { get; set; } + + /// + /// Gets or sets the name of the item. + /// + /// The name of the item. + [ApiMember(Name = "ItemName", Description = "The name of the item.", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "POST")] + public string ItemName { get; set; } + } + + [Route("/Sessions/{Id}/Playing", "POST", Summary = "Instructs a session to play an item")] + [Authenticated] + public class Play : IReturnVoid + { + /// + /// Gets or sets the id. + /// + /// The id. + [ApiMember(Name = "Id", Description = "Session Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] + public string Id { get; set; } + + /// + /// Artist, Genre, Studio, Person, or any kind of BaseItem + /// + /// The type of the item. + [ApiMember(Name = "ItemIds", Description = "The ids of the items to play, comma delimited", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "POST", AllowMultiple = true)] + public string ItemIds { get; set; } + + /// + /// Gets or sets the start position ticks that the first item should be played at + /// + /// The start position ticks. + [ApiMember(Name = "StartPositionTicks", Description = "The starting position of the first item.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "POST")] + public long? StartPositionTicks { get; set; } + + /// + /// Gets or sets the play command. + /// + /// The play command. + [ApiMember(Name = "PlayCommand", Description = "The type of play command to issue (PlayNow, PlayNext, PlayLast). Clients who have not yet implemented play next and play last may play now.", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "POST")] + public PlayCommand PlayCommand { get; set; } + } + + [Route("/Sessions/{Id}/Playing/{Command}", "POST", Summary = "Issues a playstate command to a client")] + [Authenticated] + public class SendPlaystateCommand : IReturnVoid + { + /// + /// Gets or sets the id. + /// + /// The id. + [ApiMember(Name = "Id", Description = "Session Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] + public string Id { get; set; } + + /// + /// Gets or sets the position to seek to + /// + [ApiMember(Name = "SeekPositionTicks", Description = "The position to seek to.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "POST")] + public long? SeekPositionTicks { get; set; } + + /// + /// Gets or sets the play command. + /// + /// The play command. + [ApiMember(Name = "Command", Description = "The command to send - stop, pause, unpause, nexttrack, previoustrack, seek, fullscreen.", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] + public PlaystateCommand Command { get; set; } + } + + [Route("/Sessions/{Id}/System/{Command}", "POST", Summary = "Issues a system command to a client")] + [Authenticated] + public class SendSystemCommand : IReturnVoid + { + /// + /// Gets or sets the id. + /// + /// The id. + [ApiMember(Name = "Id", Description = "Session Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] + public string Id { get; set; } + + /// + /// Gets or sets the command. + /// + /// The play command. + [ApiMember(Name = "Command", Description = "The command to send.", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] + public string Command { get; set; } + } + + [Route("/Sessions/{Id}/Command/{Command}", "POST", Summary = "Issues a system command to a client")] + [Authenticated] + public class SendGeneralCommand : IReturnVoid + { + /// + /// Gets or sets the id. + /// + /// The id. + [ApiMember(Name = "Id", Description = "Session Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] + public string Id { get; set; } + + /// + /// Gets or sets the command. + /// + /// The play command. + [ApiMember(Name = "Command", Description = "The command to send.", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] + public string Command { get; set; } + } + + [Route("/Sessions/{Id}/Command", "POST", Summary = "Issues a system command to a client")] + [Authenticated] + public class SendFullGeneralCommand : GeneralCommand, IReturnVoid + { + /// + /// Gets or sets the id. + /// + /// The id. + [ApiMember(Name = "Id", Description = "Session Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] + public string Id { get; set; } + } + + [Route("/Sessions/{Id}/Message", "POST", Summary = "Issues a command to a client to display a message to the user")] + [Authenticated] + public class SendMessageCommand : IReturnVoid + { + /// + /// Gets or sets the id. + /// + /// The id. + [ApiMember(Name = "Id", Description = "Session Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] + public string Id { get; set; } + + [ApiMember(Name = "Text", Description = "The message text.", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "POST")] + public string Text { get; set; } + + [ApiMember(Name = "Header", Description = "The message header.", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "POST")] + public string Header { get; set; } + + [ApiMember(Name = "TimeoutMs", Description = "The message timeout. If omitted the user will have to confirm viewing the message.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "POST")] + public long? TimeoutMs { get; set; } + } + + [Route("/Sessions/{Id}/Users/{UserId}", "POST", Summary = "Adds an additional user to a session")] + [Authenticated] + public class AddUserToSession : IReturnVoid + { + [ApiMember(Name = "Id", Description = "Session Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] + public string Id { get; set; } + + [ApiMember(Name = "UserId", Description = "UserId Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] + public Guid UserId { get; set; } + } + + [Route("/Sessions/{Id}/Users/{UserId}", "DELETE", Summary = "Removes an additional user from a session")] + [Authenticated] + public class RemoveUserFromSession : IReturnVoid + { + [ApiMember(Name = "Id", Description = "Session Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] + public string Id { get; set; } + + [ApiMember(Name = "UserId", Description = "UserId Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] + public Guid UserId { get; set; } + } + + [Route("/Sessions/Capabilities", "POST", Summary = "Updates capabilities for a device")] + [Authenticated] + public class PostCapabilities : IReturnVoid + { + /// + /// Gets or sets the id. + /// + /// The id. + [ApiMember(Name = "Id", Description = "Session Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] + public string Id { get; set; } + + [ApiMember(Name = "PlayableMediaTypes", Description = "A list of playable media types, comma delimited. Audio, Video, Book, Game, Photo.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "POST")] + public string PlayableMediaTypes { get; set; } + + [ApiMember(Name = "SupportedCommands", Description = "A list of supported remote control commands, comma delimited", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "POST")] + public string SupportedCommands { get; set; } + + [ApiMember(Name = "MessageCallbackUrl", Description = "A url to post messages to, including remote control commands.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "POST")] + public string MessageCallbackUrl { get; set; } + + [ApiMember(Name = "SupportsMediaControl", Description = "Determines whether media can be played remotely.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "POST")] + public bool SupportsMediaControl { get; set; } + } + + [Route("/Sessions/Logout", "POST", Summary = "Reports that a session has ended")] + public class ReportSessionEnded : IReturnVoid + { + } + + [Route("/Auth/Keys", "GET")] + public class GetApiKeys + { + } + + [Route("/Auth/Keys/{Key}", "DELETE")] + public class RevokeKey + { + [ApiMember(Name = "Key", Description = "Auth Key", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] + public string Key { get; set; } + } + + [Route("/Auth/Keys", "POST")] + public class CreateKey + { + [ApiMember(Name = "App", Description = "App", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "POST")] + public string App { get; set; } + } + + /// + /// Class SessionsService + /// + public class SessionsService : BaseApiService + { + /// + /// The _session manager + /// + private readonly ISessionManager _sessionManager; + + private readonly IUserManager _userManager; + private readonly IAuthorizationContext _authContext; + private readonly IAuthenticationRepository _authRepo; + + /// + /// Initializes a new instance of the class. + /// + /// The session manager. + /// The user manager. + /// The authentication context. + /// The authentication repo. + public SessionsService(ISessionManager sessionManager, IUserManager userManager, IAuthorizationContext authContext, IAuthenticationRepository authRepo) + { + _sessionManager = sessionManager; + _userManager = userManager; + _authContext = authContext; + _authRepo = authRepo; + } + + public void Delete(RevokeKey request) + { + var task = _sessionManager.RevokeToken(request.Key); + + Task.WaitAll(task); + } + + public void Post(CreateKey request) + { + var task = _authRepo.Create(new AuthenticationInfo + { + AppName = request.App, + IsActive = true, + AccessToken = Guid.NewGuid().ToString("N"), + DateCreated = DateTime.UtcNow + + }, CancellationToken.None); + + Task.WaitAll(task); + } + + public void Post(ReportSessionEnded request) + { + var auth = _authContext.GetAuthorizationInfo(Request); + + _sessionManager.Logout(auth.Token); + } + + public object Get(GetApiKeys request) + { + var result = _authRepo.Get(new AuthenticationInfoQuery + { + IsActive = true + }); + + return ToOptimizedResult(result); + } + + /// + /// Gets the specified request. + /// + /// The request. + /// System.Object. + public object Get(GetSessions request) + { + var result = _sessionManager.Sessions.Where(i => i.IsActive); + + if (!string.IsNullOrEmpty(request.DeviceId)) + { + result = result.Where(i => string.Equals(i.DeviceId, request.DeviceId, StringComparison.OrdinalIgnoreCase)); + } + + if (request.ControllableByUserId.HasValue) + { + result = result.Where(i => i.SupportsMediaControl); + + var user = _userManager.GetUserById(request.ControllableByUserId.Value); + + if (!user.Configuration.EnableRemoteControlOfOtherUsers) + { + result = result.Where(i => !i.UserId.HasValue || i.ContainsUser(request.ControllableByUserId.Value)); + } + } + + return ToOptimizedResult(result.Select(_sessionManager.GetSessionInfoDto).ToList()); + } + + public void Post(SendPlaystateCommand request) + { + var command = new PlaystateRequest + { + Command = request.Command, + SeekPositionTicks = request.SeekPositionTicks + }; + + var task = _sessionManager.SendPlaystateCommand(GetSession().Id, request.Id, command, CancellationToken.None); + + Task.WaitAll(task); + } + + /// + /// Posts the specified request. + /// + /// The request. + public void Post(DisplayContent request) + { + var command = new BrowseRequest + { + ItemId = request.ItemId, + ItemName = request.ItemName, + ItemType = request.ItemType + }; + + var task = _sessionManager.SendBrowseCommand(GetSession().Id, request.Id, command, CancellationToken.None); + + Task.WaitAll(task); + } + + /// + /// Posts the specified request. + /// + /// The request. + public void Post(SendSystemCommand request) + { + GeneralCommandType commandType; + + if (Enum.TryParse(request.Command, true, out commandType)) + { + var currentSession = GetSession(); + + var command = new GeneralCommand + { + Name = commandType.ToString(), + ControllingUserId = currentSession.UserId.HasValue ? currentSession.UserId.Value.ToString("N") : null + }; + + var task = _sessionManager.SendGeneralCommand(currentSession.Id, request.Id, command, CancellationToken.None); + + Task.WaitAll(task); + } + } + + /// + /// Posts the specified request. + /// + /// The request. + public void Post(SendMessageCommand request) + { + var command = new MessageCommand + { + Header = string.IsNullOrEmpty(request.Header) ? "Message from Server" : request.Header, + TimeoutMs = request.TimeoutMs, + Text = request.Text + }; + + var task = _sessionManager.SendMessageCommand(GetSession().Id, request.Id, command, CancellationToken.None); + + Task.WaitAll(task); + } + + /// + /// Posts the specified request. + /// + /// The request. + public void Post(Play request) + { + var command = new PlayRequest + { + ItemIds = request.ItemIds.Split(',').ToArray(), + + PlayCommand = request.PlayCommand, + StartPositionTicks = request.StartPositionTicks + }; + + var task = _sessionManager.SendPlayCommand(GetSession().Id, request.Id, command, CancellationToken.None); + + Task.WaitAll(task); + } + + public void Post(SendGeneralCommand request) + { + var currentSession = GetSession(); + + var command = new GeneralCommand + { + Name = request.Command, + ControllingUserId = currentSession.UserId.HasValue ? currentSession.UserId.Value.ToString("N") : null + }; + + var task = _sessionManager.SendGeneralCommand(currentSession.Id, request.Id, command, CancellationToken.None); + + Task.WaitAll(task); + } + + public void Post(SendFullGeneralCommand request) + { + var currentSession = GetSession(); + + request.ControllingUserId = currentSession.UserId.HasValue ? currentSession.UserId.Value.ToString("N") : null; + + var task = _sessionManager.SendGeneralCommand(currentSession.Id, request.Id, request, CancellationToken.None); + + Task.WaitAll(task); + } + + public void Post(AddUserToSession request) + { + _sessionManager.AddAdditionalUser(request.Id, request.UserId); + } + + public void Delete(RemoveUserFromSession request) + { + _sessionManager.RemoveAdditionalUser(request.Id, request.UserId); + } + + public void Post(PostCapabilities request) + { + if (string.IsNullOrWhiteSpace(request.Id)) + { + request.Id = GetSession().Id; + } + _sessionManager.ReportCapabilities(request.Id, new SessionCapabilities + { + PlayableMediaTypes = request.PlayableMediaTypes.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries).ToList(), + + SupportedCommands = request.SupportedCommands.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries).ToList(), + + SupportsMediaControl = request.SupportsMediaControl, + + MessageCallbackUrl = request.MessageCallbackUrl + }); + } + } +} \ No newline at end of file diff --git a/MediaBrowser.Api/SessionsService.cs b/MediaBrowser.Api/SessionsService.cs deleted file mode 100644 index 8017f3523..000000000 --- a/MediaBrowser.Api/SessionsService.cs +++ /dev/null @@ -1,506 +0,0 @@ -using MediaBrowser.Controller.Library; -using MediaBrowser.Controller.Net; -using MediaBrowser.Controller.Security; -using MediaBrowser.Controller.Session; -using MediaBrowser.Model.Session; -using ServiceStack; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; - -namespace MediaBrowser.Api -{ - /// - /// Class GetSessions - /// - [Route("/Sessions", "GET", Summary = "Gets a list of sessions")] - [Authenticated] - public class GetSessions : IReturn> - { - [ApiMember(Name = "ControllableByUserId", Description = "Optional. Filter by sessions that a given user is allowed to remote control.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] - public Guid? ControllableByUserId { get; set; } - - [ApiMember(Name = "DeviceId", Description = "Optional. Filter by device id.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] - public string DeviceId { get; set; } - } - - /// - /// Class DisplayContent - /// - [Route("/Sessions/{Id}/Viewing", "POST", Summary = "Instructs a session to browse to an item or view")] - [Authenticated] - public class DisplayContent : IReturnVoid - { - /// - /// Gets or sets the id. - /// - /// The id. - [ApiMember(Name = "Id", Description = "Session Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] - public string Id { get; set; } - - /// - /// Artist, Genre, Studio, Person, or any kind of BaseItem - /// - /// The type of the item. - [ApiMember(Name = "ItemType", Description = "The type of item to browse to.", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "POST")] - public string ItemType { get; set; } - - /// - /// Artist name, genre name, item Id, etc - /// - /// The item identifier. - [ApiMember(Name = "ItemId", Description = "The Id of the item.", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "POST")] - public string ItemId { get; set; } - - /// - /// Gets or sets the name of the item. - /// - /// The name of the item. - [ApiMember(Name = "ItemName", Description = "The name of the item.", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "POST")] - public string ItemName { get; set; } - } - - [Route("/Sessions/{Id}/Playing", "POST", Summary = "Instructs a session to play an item")] - [Authenticated] - public class Play : IReturnVoid - { - /// - /// Gets or sets the id. - /// - /// The id. - [ApiMember(Name = "Id", Description = "Session Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] - public string Id { get; set; } - - /// - /// Artist, Genre, Studio, Person, or any kind of BaseItem - /// - /// The type of the item. - [ApiMember(Name = "ItemIds", Description = "The ids of the items to play, comma delimited", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "POST", AllowMultiple = true)] - public string ItemIds { get; set; } - - /// - /// Gets or sets the start position ticks that the first item should be played at - /// - /// The start position ticks. - [ApiMember(Name = "StartPositionTicks", Description = "The starting position of the first item.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "POST")] - public long? StartPositionTicks { get; set; } - - /// - /// Gets or sets the play command. - /// - /// The play command. - [ApiMember(Name = "PlayCommand", Description = "The type of play command to issue (PlayNow, PlayNext, PlayLast). Clients who have not yet implemented play next and play last may play now.", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "POST")] - public PlayCommand PlayCommand { get; set; } - } - - [Route("/Sessions/{Id}/Playing/{Command}", "POST", Summary = "Issues a playstate command to a client")] - [Authenticated] - public class SendPlaystateCommand : IReturnVoid - { - /// - /// Gets or sets the id. - /// - /// The id. - [ApiMember(Name = "Id", Description = "Session Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] - public string Id { get; set; } - - /// - /// Gets or sets the position to seek to - /// - [ApiMember(Name = "SeekPositionTicks", Description = "The position to seek to.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "POST")] - public long? SeekPositionTicks { get; set; } - - /// - /// Gets or sets the play command. - /// - /// The play command. - [ApiMember(Name = "Command", Description = "The command to send - stop, pause, unpause, nexttrack, previoustrack, seek, fullscreen.", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] - public PlaystateCommand Command { get; set; } - } - - [Route("/Sessions/{Id}/System/{Command}", "POST", Summary = "Issues a system command to a client")] - [Authenticated] - public class SendSystemCommand : IReturnVoid - { - /// - /// Gets or sets the id. - /// - /// The id. - [ApiMember(Name = "Id", Description = "Session Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] - public string Id { get; set; } - - /// - /// Gets or sets the command. - /// - /// The play command. - [ApiMember(Name = "Command", Description = "The command to send.", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] - public string Command { get; set; } - } - - [Route("/Sessions/{Id}/Command/{Command}", "POST", Summary = "Issues a system command to a client")] - [Authenticated] - public class SendGeneralCommand : IReturnVoid - { - /// - /// Gets or sets the id. - /// - /// The id. - [ApiMember(Name = "Id", Description = "Session Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] - public string Id { get; set; } - - /// - /// Gets or sets the command. - /// - /// The play command. - [ApiMember(Name = "Command", Description = "The command to send.", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] - public string Command { get; set; } - } - - [Route("/Sessions/{Id}/Command", "POST", Summary = "Issues a system command to a client")] - [Authenticated] - public class SendFullGeneralCommand : GeneralCommand, IReturnVoid - { - /// - /// Gets or sets the id. - /// - /// The id. - [ApiMember(Name = "Id", Description = "Session Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] - public string Id { get; set; } - } - - [Route("/Sessions/{Id}/Message", "POST", Summary = "Issues a command to a client to display a message to the user")] - [Authenticated] - public class SendMessageCommand : IReturnVoid - { - /// - /// Gets or sets the id. - /// - /// The id. - [ApiMember(Name = "Id", Description = "Session Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] - public string Id { get; set; } - - [ApiMember(Name = "Text", Description = "The message text.", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "POST")] - public string Text { get; set; } - - [ApiMember(Name = "Header", Description = "The message header.", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "POST")] - public string Header { get; set; } - - [ApiMember(Name = "TimeoutMs", Description = "The message timeout. If omitted the user will have to confirm viewing the message.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "POST")] - public long? TimeoutMs { get; set; } - } - - [Route("/Sessions/{Id}/Users/{UserId}", "POST", Summary = "Adds an additional user to a session")] - [Authenticated] - public class AddUserToSession : IReturnVoid - { - [ApiMember(Name = "Id", Description = "Session Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] - public string Id { get; set; } - - [ApiMember(Name = "UserId", Description = "UserId Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] - public Guid UserId { get; set; } - } - - [Route("/Sessions/{Id}/Users/{UserId}", "DELETE", Summary = "Removes an additional user from a session")] - [Authenticated] - public class RemoveUserFromSession : IReturnVoid - { - [ApiMember(Name = "Id", Description = "Session Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] - public string Id { get; set; } - - [ApiMember(Name = "UserId", Description = "UserId Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] - public Guid UserId { get; set; } - } - - [Route("/Sessions/Capabilities", "POST", Summary = "Updates capabilities for a device")] - [Authenticated] - public class PostCapabilities : IReturnVoid - { - /// - /// Gets or sets the id. - /// - /// The id. - [ApiMember(Name = "Id", Description = "Session Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] - public string Id { get; set; } - - [ApiMember(Name = "PlayableMediaTypes", Description = "A list of playable media types, comma delimited. Audio, Video, Book, Game, Photo.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "POST")] - public string PlayableMediaTypes { get; set; } - - [ApiMember(Name = "SupportedCommands", Description = "A list of supported remote control commands, comma delimited", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "POST")] - public string SupportedCommands { get; set; } - - [ApiMember(Name = "MessageCallbackUrl", Description = "A url to post messages to, including remote control commands.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "POST")] - public string MessageCallbackUrl { get; set; } - - [ApiMember(Name = "SupportsMediaControl", Description = "Determines whether media can be played remotely.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "POST")] - public bool SupportsMediaControl { get; set; } - } - - [Route("/Sessions/Logout", "POST", Summary = "Reports that a session has ended")] - public class ReportSessionEnded : IReturnVoid - { - } - - [Route("/Auth/Keys", "GET")] - public class GetApiKeys - { - } - - [Route("/Auth/Keys/{Key}", "DELETE")] - public class RevokeKey - { - [ApiMember(Name = "Key", Description = "Auth Key", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] - public string Key { get; set; } - } - - [Route("/Auth/Keys", "POST")] - public class CreateKey - { - [ApiMember(Name = "App", Description = "App", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "POST")] - public string App { get; set; } - } - - /// - /// Class SessionsService - /// - public class SessionsService : BaseApiService - { - /// - /// The _session manager - /// - private readonly ISessionManager _sessionManager; - - private readonly IUserManager _userManager; - private readonly IAuthorizationContext _authContext; - private readonly IAuthenticationRepository _authRepo; - - /// - /// Initializes a new instance of the class. - /// - /// The session manager. - /// The user manager. - /// The authentication context. - /// The authentication repo. - public SessionsService(ISessionManager sessionManager, IUserManager userManager, IAuthorizationContext authContext, IAuthenticationRepository authRepo) - { - _sessionManager = sessionManager; - _userManager = userManager; - _authContext = authContext; - _authRepo = authRepo; - } - - public void Delete(RevokeKey request) - { - var task = _sessionManager.RevokeToken(request.Key); - - Task.WaitAll(task); - } - - public void Post(CreateKey request) - { - var task = _authRepo.Create(new AuthenticationInfo - { - AppName = request.App, - IsActive = true, - AccessToken = Guid.NewGuid().ToString("N"), - DateCreated = DateTime.UtcNow - - }, CancellationToken.None); - - Task.WaitAll(task); - } - - public void Post(ReportSessionEnded request) - { - var auth = _authContext.GetAuthorizationInfo(Request); - - _sessionManager.Logout(auth.Token); - } - - public object Get(GetApiKeys request) - { - var result = _authRepo.Get(new AuthenticationInfoQuery - { - IsActive = true - }); - - return ToOptimizedResult(result); - } - - /// - /// Gets the specified request. - /// - /// The request. - /// System.Object. - public object Get(GetSessions request) - { - var result = _sessionManager.Sessions.Where(i => i.IsActive); - - if (!string.IsNullOrEmpty(request.DeviceId)) - { - result = result.Where(i => string.Equals(i.DeviceId, request.DeviceId, StringComparison.OrdinalIgnoreCase)); - } - - if (request.ControllableByUserId.HasValue) - { - result = result.Where(i => i.SupportsMediaControl); - - var user = _userManager.GetUserById(request.ControllableByUserId.Value); - - if (!user.Configuration.EnableRemoteControlOfOtherUsers) - { - result = result.Where(i => !i.UserId.HasValue || i.ContainsUser(request.ControllableByUserId.Value)); - } - } - - return ToOptimizedResult(result.Select(_sessionManager.GetSessionInfoDto).ToList()); - } - - public void Post(SendPlaystateCommand request) - { - var command = new PlaystateRequest - { - Command = request.Command, - SeekPositionTicks = request.SeekPositionTicks - }; - - var task = _sessionManager.SendPlaystateCommand(GetSession().Id, request.Id, command, CancellationToken.None); - - Task.WaitAll(task); - } - - /// - /// Posts the specified request. - /// - /// The request. - public void Post(DisplayContent request) - { - var command = new BrowseRequest - { - ItemId = request.ItemId, - ItemName = request.ItemName, - ItemType = request.ItemType - }; - - var task = _sessionManager.SendBrowseCommand(GetSession().Id, request.Id, command, CancellationToken.None); - - Task.WaitAll(task); - } - - /// - /// Posts the specified request. - /// - /// The request. - public void Post(SendSystemCommand request) - { - GeneralCommandType commandType; - - if (Enum.TryParse(request.Command, true, out commandType)) - { - var currentSession = GetSession(); - - var command = new GeneralCommand - { - Name = commandType.ToString(), - ControllingUserId = currentSession.UserId.HasValue ? currentSession.UserId.Value.ToString("N") : null - }; - - var task = _sessionManager.SendGeneralCommand(currentSession.Id, request.Id, command, CancellationToken.None); - - Task.WaitAll(task); - } - } - - /// - /// Posts the specified request. - /// - /// The request. - public void Post(SendMessageCommand request) - { - var command = new MessageCommand - { - Header = string.IsNullOrEmpty(request.Header) ? "Message from Server" : request.Header, - TimeoutMs = request.TimeoutMs, - Text = request.Text - }; - - var task = _sessionManager.SendMessageCommand(GetSession().Id, request.Id, command, CancellationToken.None); - - Task.WaitAll(task); - } - - /// - /// Posts the specified request. - /// - /// The request. - public void Post(Play request) - { - var command = new PlayRequest - { - ItemIds = request.ItemIds.Split(',').ToArray(), - - PlayCommand = request.PlayCommand, - StartPositionTicks = request.StartPositionTicks - }; - - var task = _sessionManager.SendPlayCommand(GetSession().Id, request.Id, command, CancellationToken.None); - - Task.WaitAll(task); - } - - public void Post(SendGeneralCommand request) - { - var currentSession = GetSession(); - - var command = new GeneralCommand - { - Name = request.Command, - ControllingUserId = currentSession.UserId.HasValue ? currentSession.UserId.Value.ToString("N") : null - }; - - var task = _sessionManager.SendGeneralCommand(currentSession.Id, request.Id, command, CancellationToken.None); - - Task.WaitAll(task); - } - - public void Post(SendFullGeneralCommand request) - { - var currentSession = GetSession(); - - request.ControllingUserId = currentSession.UserId.HasValue ? currentSession.UserId.Value.ToString("N") : null; - - var task = _sessionManager.SendGeneralCommand(currentSession.Id, request.Id, request, CancellationToken.None); - - Task.WaitAll(task); - } - - public void Post(AddUserToSession request) - { - _sessionManager.AddAdditionalUser(request.Id, request.UserId); - } - - public void Delete(RemoveUserFromSession request) - { - _sessionManager.RemoveAdditionalUser(request.Id, request.UserId); - } - - public void Post(PostCapabilities request) - { - if (string.IsNullOrWhiteSpace(request.Id)) - { - request.Id = GetSession().Id; - } - _sessionManager.ReportCapabilities(request.Id, new SessionCapabilities - { - PlayableMediaTypes = request.PlayableMediaTypes.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries).ToList(), - - SupportedCommands = request.SupportedCommands.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries).ToList(), - - SupportsMediaControl = request.SupportsMediaControl, - - MessageCallbackUrl = request.MessageCallbackUrl - }); - } - } -} \ No newline at end of file diff --git a/MediaBrowser.Api/System/ActivityLogService.cs b/MediaBrowser.Api/System/ActivityLogService.cs new file mode 100644 index 000000000..0ccc28c6f --- /dev/null +++ b/MediaBrowser.Api/System/ActivityLogService.cs @@ -0,0 +1,44 @@ +using MediaBrowser.Controller.Activity; +using MediaBrowser.Controller.Net; +using MediaBrowser.Model.Activity; +using MediaBrowser.Model.Querying; +using ServiceStack; + +namespace MediaBrowser.Api.System +{ + [Route("/System/ActivityLog/Entries", "GET", Summary = "Gets activity log entries")] + public class GetActivityLogs : IReturn> + { + /// + /// Skips over a given number of items within the results. Use for paging. + /// + /// The start index. + [ApiMember(Name = "StartIndex", Description = "Optional. The record index to start at. All items with a lower index will be dropped from the results.", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")] + public int? StartIndex { get; set; } + + /// + /// The maximum number of items to return + /// + /// The limit. + [ApiMember(Name = "Limit", Description = "Optional. The maximum number of records to return", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")] + public int? Limit { get; set; } + } + + [Authenticated] + public class ActivityLogService : BaseApiService + { + private readonly IActivityManager _activityManager; + + public ActivityLogService(IActivityManager activityManager) + { + _activityManager = activityManager; + } + + public object Get(GetActivityLogs request) + { + var result = _activityManager.GetActivityLogEntries(request.StartIndex, request.Limit); + + return ToOptimizedResult(result); + } + } +} diff --git a/MediaBrowser.Api/System/SystemInfoWebSocketListener.cs b/MediaBrowser.Api/System/SystemInfoWebSocketListener.cs new file mode 100644 index 000000000..c20cef3b3 --- /dev/null +++ b/MediaBrowser.Api/System/SystemInfoWebSocketListener.cs @@ -0,0 +1,49 @@ +using MediaBrowser.Common.Net; +using MediaBrowser.Controller; +using MediaBrowser.Model.Logging; +using MediaBrowser.Model.System; +using System.Threading.Tasks; + +namespace MediaBrowser.Api.System +{ + /// + /// Class SystemInfoWebSocketListener + /// + public class SystemInfoWebSocketListener : BasePeriodicWebSocketListener + { + /// + /// Gets the name. + /// + /// The name. + protected override string Name + { + get { return "SystemInfo"; } + } + + /// + /// The _kernel + /// + private readonly IServerApplicationHost _appHost; + + /// + /// Initializes a new instance of the class. + /// + /// The logger. + /// The app host. + public SystemInfoWebSocketListener(ILogger logger, IServerApplicationHost appHost) + : base(logger) + { + _appHost = appHost; + } + + /// + /// Gets the data to send. + /// + /// The state. + /// Task{SystemInfo}. + protected override Task GetDataToSend(WebSocketListenerState state) + { + return Task.FromResult(_appHost.GetSystemInfo()); + } + } +} diff --git a/MediaBrowser.Api/System/SystemService.cs b/MediaBrowser.Api/System/SystemService.cs new file mode 100644 index 000000000..3913275ee --- /dev/null +++ b/MediaBrowser.Api/System/SystemService.cs @@ -0,0 +1,178 @@ +using MediaBrowser.Common.Configuration; +using MediaBrowser.Common.IO; +using MediaBrowser.Controller; +using MediaBrowser.Controller.Net; +using MediaBrowser.Model.System; +using ServiceStack; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Threading.Tasks; + +namespace MediaBrowser.Api.System +{ + /// + /// Class GetSystemInfo + /// + [Route("/System/Info", "GET", Summary = "Gets information about the server")] + [Authenticated] + public class GetSystemInfo : IReturn + { + + } + + [Route("/System/Info/Public", "GET", Summary = "Gets public information about the server")] + public class GetPublicSystemInfo : IReturn + { + + } + + /// + /// Class RestartApplication + /// + [Route("/System/Restart", "POST", Summary = "Restarts the application, if needed")] + [Authenticated] + public class RestartApplication + { + } + + [Route("/System/Shutdown", "POST", Summary = "Shuts down the application")] + [Authenticated] + public class ShutdownApplication + { + } + + [Route("/System/Logs", "GET", Summary = "Gets a list of available server log files")] + [Authenticated] + public class GetServerLogs : IReturn> + { + } + + [Route("/System/Logs/Log", "GET", Summary = "Gets a log file")] + public class GetLogFile + { + [ApiMember(Name = "Name", Description = "The log file name.", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)] + public string Name { get; set; } + } + + /// + /// Class SystemInfoService + /// + public class SystemService : BaseApiService + { + /// + /// The _app host + /// + private readonly IServerApplicationHost _appHost; + private readonly IApplicationPaths _appPaths; + private readonly IFileSystem _fileSystem; + + /// + /// Initializes a new instance of the class. + /// + /// The app host. + /// The application paths. + /// The file system. + /// jsonSerializer + public SystemService(IServerApplicationHost appHost, IApplicationPaths appPaths, IFileSystem fileSystem) + { + _appHost = appHost; + _appPaths = appPaths; + _fileSystem = fileSystem; + } + + public object Get(GetServerLogs request) + { + List files; + + try + { + files = new DirectoryInfo(_appPaths.LogDirectoryPath) + .EnumerateFiles("*", SearchOption.AllDirectories) + .Where(i => string.Equals(i.Extension, ".txt", global::System.StringComparison.OrdinalIgnoreCase)) + .ToList(); + } + catch (DirectoryNotFoundException) + { + files = new List(); + } + + var result = files.Select(i => new LogFile + { + DateCreated = _fileSystem.GetCreationTimeUtc(i), + DateModified = _fileSystem.GetLastWriteTimeUtc(i), + Name = i.Name, + Size = i.Length + + }).OrderByDescending(i => i.DateModified) + .ThenByDescending(i => i.DateCreated) + .ThenBy(i => i.Name) + .ToList(); + + return ToOptimizedResult(result); + } + + public object Get(GetLogFile request) + { + var file = new DirectoryInfo(_appPaths.LogDirectoryPath) + .EnumerateFiles("*", SearchOption.AllDirectories) + .First(i => string.Equals(i.Name, request.Name, global::System.StringComparison.OrdinalIgnoreCase)); + + return ResultFactory.GetStaticFileResult(Request, file.FullName, FileShare.ReadWrite); + } + + /// + /// Gets the specified request. + /// + /// The request. + /// System.Object. + public object Get(GetSystemInfo request) + { + var result = _appHost.GetSystemInfo(); + + return ToOptimizedResult(result); + } + + public object Get(GetPublicSystemInfo request) + { + var result = _appHost.GetSystemInfo(); + + var publicInfo = new PublicSystemInfo + { + Id = result.Id, + ServerName = result.ServerName, + Version = result.Version + }; + + return ToOptimizedResult(publicInfo); + } + + /// + /// Posts the specified request. + /// + /// The request. + public void Post(RestartApplication request) + { + Task.Run(async () => + { + await Task.Delay(100).ConfigureAwait(false); + await _appHost.Restart().ConfigureAwait(false); + }); + } + + /// + /// Posts the specified request. + /// + /// The request. + public void Post(ShutdownApplication request) + { + Task.Run(async () => + { + await Task.Delay(100).ConfigureAwait(false); + await _appHost.Shutdown().ConfigureAwait(false); + }); + } + + } +} diff --git a/MediaBrowser.Api/SystemService.cs b/MediaBrowser.Api/SystemService.cs deleted file mode 100644 index 259b1d892..000000000 --- a/MediaBrowser.Api/SystemService.cs +++ /dev/null @@ -1,177 +0,0 @@ -using MediaBrowser.Common.Configuration; -using MediaBrowser.Common.IO; -using MediaBrowser.Controller; -using MediaBrowser.Controller.Net; -using MediaBrowser.Model.System; -using ServiceStack; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Threading.Tasks; - -namespace MediaBrowser.Api -{ - /// - /// Class GetSystemInfo - /// - [Route("/System/Info", "GET", Summary = "Gets information about the server")] - [Authenticated] - public class GetSystemInfo : IReturn - { - - } - - [Route("/System/Info/Public", "GET", Summary = "Gets public information about the server")] - public class GetPublicSystemInfo : IReturn - { - - } - - /// - /// Class RestartApplication - /// - [Route("/System/Restart", "POST", Summary = "Restarts the application, if needed")] - [Authenticated] - public class RestartApplication - { - } - - [Route("/System/Shutdown", "POST", Summary = "Shuts down the application")] - [Authenticated] - public class ShutdownApplication - { - } - - [Route("/System/Logs", "GET", Summary = "Gets a list of available server log files")] - [Authenticated] - public class GetServerLogs : IReturn> - { - } - - [Route("/System/Logs/Log", "GET", Summary = "Gets a log file")] - public class GetLogFile - { - [ApiMember(Name = "Name", Description = "The log file name.", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)] - public string Name { get; set; } - } - - /// - /// Class SystemInfoService - /// - public class SystemService : BaseApiService - { - /// - /// The _app host - /// - private readonly IServerApplicationHost _appHost; - private readonly IApplicationPaths _appPaths; - private readonly IFileSystem _fileSystem; - - /// - /// Initializes a new instance of the class. - /// - /// The app host. - /// The application paths. - /// The file system. - /// jsonSerializer - public SystemService(IServerApplicationHost appHost, IApplicationPaths appPaths, IFileSystem fileSystem) - { - _appHost = appHost; - _appPaths = appPaths; - _fileSystem = fileSystem; - } - - public object Get(GetServerLogs request) - { - List files; - - try - { - files = new DirectoryInfo(_appPaths.LogDirectoryPath) - .EnumerateFiles("*", SearchOption.AllDirectories) - .Where(i => string.Equals(i.Extension, ".txt", System.StringComparison.OrdinalIgnoreCase)) - .ToList(); - } - catch (DirectoryNotFoundException) - { - files = new List(); - } - - var result = files.Select(i => new LogFile - { - DateCreated = _fileSystem.GetCreationTimeUtc(i), - DateModified = _fileSystem.GetLastWriteTimeUtc(i), - Name = i.Name, - Size = i.Length - - }).OrderByDescending(i => i.DateModified) - .ThenByDescending(i => i.DateCreated) - .ThenBy(i => i.Name) - .ToList(); - - return ToOptimizedResult(result); - } - - public object Get(GetLogFile request) - { - var file = new DirectoryInfo(_appPaths.LogDirectoryPath) - .EnumerateFiles("*", SearchOption.AllDirectories) - .First(i => string.Equals(i.Name, request.Name, System.StringComparison.OrdinalIgnoreCase)); - - return ResultFactory.GetStaticFileResult(Request, file.FullName, FileShare.ReadWrite); - } - - /// - /// Gets the specified request. - /// - /// The request. - /// System.Object. - public object Get(GetSystemInfo request) - { - var result = _appHost.GetSystemInfo(); - - return ToOptimizedResult(result); - } - - public object Get(GetPublicSystemInfo request) - { - var result = _appHost.GetSystemInfo(); - - var publicInfo = new PublicSystemInfo - { - Id = result.Id, - ServerName = result.ServerName, - Version = result.Version - }; - - return ToOptimizedResult(publicInfo); - } - - /// - /// Posts the specified request. - /// - /// The request. - public void Post(RestartApplication request) - { - Task.Run(async () => - { - await Task.Delay(100).ConfigureAwait(false); - await _appHost.Restart().ConfigureAwait(false); - }); - } - - /// - /// Posts the specified request. - /// - /// The request. - public void Post(ShutdownApplication request) - { - Task.Run(async () => - { - await Task.Delay(100).ConfigureAwait(false); - await _appHost.Shutdown().ConfigureAwait(false); - }); - } - - } -} diff --git a/MediaBrowser.Api/UserService.cs b/MediaBrowser.Api/UserService.cs index f5a1f54cb..4ffe5b391 100644 --- a/MediaBrowser.Api/UserService.cs +++ b/MediaBrowser.Api/UserService.cs @@ -195,7 +195,7 @@ namespace MediaBrowser.Api var authInfo = AuthorizationContext.GetAuthorizationInfo(Request); var isDashboard = string.Equals(authInfo.Client, "Dashboard", StringComparison.OrdinalIgnoreCase); - if ((Request.IsLocal && isDashboard) || + if ((Request.IsLocal && isDashboard) || !_config.Configuration.IsStartupWizardCompleted) { return Get(new GetUsers @@ -327,7 +327,7 @@ namespace MediaBrowser.Api var revokeTask = _sessionMananger.RevokeUserTokens(user.Id.ToString("N")); Task.WaitAll(revokeTask); - + var task = _userManager.DeleteUser(user); Task.WaitAll(task); @@ -374,8 +374,17 @@ namespace MediaBrowser.Api auth.DeviceId = "Unknown device id"; } - var result = _sessionMananger.AuthenticateNewSession(request.Username, request.Password, auth.Client, auth.Version, - auth.DeviceId, auth.Device, Request.RemoteIp, Request.IsLocal).Result; + var result = _sessionMananger.AuthenticateNewSession(new AuthenticationRequest + { + App = auth.Client, + AppVersion = auth.Version, + DeviceId = auth.DeviceId, + DeviceName = auth.Device, + Password = request.Password, + RemoteEndPoint = Request.RemoteIp, + Username = request.Username + + }, Request.IsLocal).Result; return ToOptimizedResult(result); } @@ -457,8 +466,8 @@ namespace MediaBrowser.Api Task.WaitAll(revokeTask); } - var task = user.Name.Equals(dtoUser.Name, StringComparison.Ordinal) ? - _userManager.UpdateUser(user) : + var task = user.Name.Equals(dtoUser.Name, StringComparison.Ordinal) ? + _userManager.UpdateUser(user) : _userManager.RenameUser(user, dtoUser.Name); Task.WaitAll(task); diff --git a/MediaBrowser.Api/WebSocket/SessionInfoWebSocketListener.cs b/MediaBrowser.Api/WebSocket/SessionInfoWebSocketListener.cs deleted file mode 100644 index 600d9e405..000000000 --- a/MediaBrowser.Api/WebSocket/SessionInfoWebSocketListener.cs +++ /dev/null @@ -1,116 +0,0 @@ -using MediaBrowser.Common.Net; -using MediaBrowser.Controller.Library; -using MediaBrowser.Controller.Session; -using MediaBrowser.Model.Logging; -using MediaBrowser.Model.Session; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; - -namespace MediaBrowser.Api.WebSocket -{ - /// - /// Class SessionInfoWebSocketListener - /// - class SessionInfoWebSocketListener : BasePeriodicWebSocketListener, WebSocketListenerState> - { - /// - /// Gets the name. - /// - /// The name. - protected override string Name - { - get { return "Sessions"; } - } - - /// - /// The _kernel - /// - private readonly ISessionManager _sessionManager; - - /// - /// Initializes a new instance of the class. - /// - /// The logger. - /// The session manager. - public SessionInfoWebSocketListener(ILogger logger, ISessionManager sessionManager) - : base(logger) - { - _sessionManager = sessionManager; - - _sessionManager.SessionStarted += _sessionManager_SessionStarted; - _sessionManager.SessionEnded += _sessionManager_SessionEnded; - _sessionManager.PlaybackStart += _sessionManager_PlaybackStart; - _sessionManager.PlaybackStopped += _sessionManager_PlaybackStopped; - _sessionManager.PlaybackProgress += _sessionManager_PlaybackProgress; - _sessionManager.CapabilitiesChanged += _sessionManager_CapabilitiesChanged; - _sessionManager.SessionActivity += _sessionManager_SessionActivity; - } - - void _sessionManager_SessionActivity(object sender, SessionEventArgs e) - { - SendData(false); - } - - void _sessionManager_CapabilitiesChanged(object sender, SessionEventArgs e) - { - SendData(true); - } - - void _sessionManager_PlaybackProgress(object sender, PlaybackProgressEventArgs e) - { - SendData(false); - } - - void _sessionManager_PlaybackStopped(object sender, PlaybackStopEventArgs e) - { - SendData(true); - } - - void _sessionManager_PlaybackStart(object sender, PlaybackProgressEventArgs e) - { - SendData(true); - } - - void _sessionManager_SessionEnded(object sender, SessionEventArgs e) - { - SendData(true); - } - - void _sessionManager_SessionStarted(object sender, SessionEventArgs e) - { - SendData(true); - } - - /// - /// Gets the data to send. - /// - /// The state. - /// Task{SystemInfo}. - protected override Task> GetDataToSend(WebSocketListenerState state) - { - return Task.FromResult(_sessionManager.Sessions.Where(i => i.IsActive).Select(_sessionManager.GetSessionInfoDto)); - } - - protected override bool SendOnTimer - { - get - { - return false; - } - } - - protected override void Dispose(bool dispose) - { - _sessionManager.SessionStarted -= _sessionManager_SessionStarted; - _sessionManager.SessionEnded -= _sessionManager_SessionEnded; - _sessionManager.PlaybackStart -= _sessionManager_PlaybackStart; - _sessionManager.PlaybackStopped -= _sessionManager_PlaybackStopped; - _sessionManager.PlaybackProgress -= _sessionManager_PlaybackProgress; - _sessionManager.CapabilitiesChanged -= _sessionManager_CapabilitiesChanged; - _sessionManager.SessionActivity -= _sessionManager_SessionActivity; - - base.Dispose(dispose); - } - } -} diff --git a/MediaBrowser.Api/WebSocket/SystemInfoWebSocketListener.cs b/MediaBrowser.Api/WebSocket/SystemInfoWebSocketListener.cs deleted file mode 100644 index 2940bcef0..000000000 --- a/MediaBrowser.Api/WebSocket/SystemInfoWebSocketListener.cs +++ /dev/null @@ -1,49 +0,0 @@ -using MediaBrowser.Common.Net; -using MediaBrowser.Controller; -using MediaBrowser.Model.Logging; -using MediaBrowser.Model.System; -using System.Threading.Tasks; - -namespace MediaBrowser.Api.WebSocket -{ - /// - /// Class SystemInfoWebSocketListener - /// - public class SystemInfoWebSocketListener : BasePeriodicWebSocketListener - { - /// - /// Gets the name. - /// - /// The name. - protected override string Name - { - get { return "SystemInfo"; } - } - - /// - /// The _kernel - /// - private readonly IServerApplicationHost _appHost; - - /// - /// Initializes a new instance of the class. - /// - /// The logger. - /// The app host. - public SystemInfoWebSocketListener(ILogger logger, IServerApplicationHost appHost) - : base(logger) - { - _appHost = appHost; - } - - /// - /// Gets the data to send. - /// - /// The state. - /// Task{SystemInfo}. - protected override Task GetDataToSend(WebSocketListenerState state) - { - return Task.FromResult(_appHost.GetSystemInfo()); - } - } -} diff --git a/MediaBrowser.Common.Implementations/Configuration/BaseConfigurationManager.cs b/MediaBrowser.Common.Implementations/Configuration/BaseConfigurationManager.cs index 60abc14f1..cb6121c9f 100644 --- a/MediaBrowser.Common.Implementations/Configuration/BaseConfigurationManager.cs +++ b/MediaBrowser.Common.Implementations/Configuration/BaseConfigurationManager.cs @@ -28,6 +28,11 @@ namespace MediaBrowser.Common.Implementations.Configuration /// public event EventHandler ConfigurationUpdated; + /// + /// Occurs when [configuration updating]. + /// + public event EventHandler NamedConfigurationUpdating; + /// /// Occurs when [named configuration updated]. /// @@ -217,6 +222,13 @@ namespace MediaBrowser.Common.Implementations.Configuration throw new ArgumentException("Expected configuration type is " + configurationType.Name); } + EventHelper.FireEventIfNotNull(NamedConfigurationUpdating, this, new ConfigurationUpdateEventArgs + { + Key = key, + NewConfiguration = configuration + + }, Logger); + _configurations.AddOrUpdate(key, configuration, (k, v) => configuration); var path = GetConfigurationFile(key); diff --git a/MediaBrowser.Common.Implementations/ScheduledTasks/ScheduledTaskWorker.cs b/MediaBrowser.Common.Implementations/ScheduledTasks/ScheduledTaskWorker.cs index 68222d843..0dc67f8c0 100644 --- a/MediaBrowser.Common.Implementations/ScheduledTasks/ScheduledTaskWorker.cs +++ b/MediaBrowser.Common.Implementations/ScheduledTasks/ScheduledTaskWorker.cs @@ -547,6 +547,7 @@ namespace MediaBrowser.Common.Implementations.ScheduledTasks if (ex != null) { result.ErrorMessage = ex.Message; + result.LongErrorMessage = ex.StackTrace; } var path = GetHistoryFilePath(); diff --git a/MediaBrowser.Common/Configuration/IConfigurationManager.cs b/MediaBrowser.Common/Configuration/IConfigurationManager.cs index 25698d972..d826a3ee7 100644 --- a/MediaBrowser.Common/Configuration/IConfigurationManager.cs +++ b/MediaBrowser.Common/Configuration/IConfigurationManager.cs @@ -6,6 +6,11 @@ namespace MediaBrowser.Common.Configuration { public interface IConfigurationManager { + /// + /// Occurs when [configuration updating]. + /// + event EventHandler NamedConfigurationUpdating; + /// /// Occurs when [configuration updated]. /// diff --git a/MediaBrowser.Common/ScheduledTasks/IConfigurableScheduledTask.cs b/MediaBrowser.Common/ScheduledTasks/IConfigurableScheduledTask.cs index fc6963070..6989dea06 100644 --- a/MediaBrowser.Common/ScheduledTasks/IConfigurableScheduledTask.cs +++ b/MediaBrowser.Common/ScheduledTasks/IConfigurableScheduledTask.cs @@ -13,4 +13,9 @@ /// true if this instance is enabled; otherwise, false. bool IsEnabled { get; } } + + public interface IScheduledTaskActivityLog + { + bool IsActivityLogged { get; } + } } \ No newline at end of file diff --git a/MediaBrowser.Controller/Activity/IActivityManager.cs b/MediaBrowser.Controller/Activity/IActivityManager.cs new file mode 100644 index 000000000..0c565ae36 --- /dev/null +++ b/MediaBrowser.Controller/Activity/IActivityManager.cs @@ -0,0 +1,17 @@ +using MediaBrowser.Model.Activity; +using MediaBrowser.Model.Events; +using MediaBrowser.Model.Querying; +using System; +using System.Threading.Tasks; + +namespace MediaBrowser.Controller.Activity +{ + public interface IActivityManager + { + event EventHandler> EntryCreated; + + Task Create(ActivityLogEntry entry); + + QueryResult GetActivityLogEntries(int? startIndex, int? limit); + } +} diff --git a/MediaBrowser.Controller/Activity/IActivityRepository.cs b/MediaBrowser.Controller/Activity/IActivityRepository.cs new file mode 100644 index 000000000..29e60ff1f --- /dev/null +++ b/MediaBrowser.Controller/Activity/IActivityRepository.cs @@ -0,0 +1,13 @@ +using MediaBrowser.Model.Activity; +using MediaBrowser.Model.Querying; +using System.Threading.Tasks; + +namespace MediaBrowser.Controller.Activity +{ + public interface IActivityRepository + { + Task Create(ActivityLogEntry entry); + + QueryResult GetActivityLogEntries(int? startIndex, int? limit); + } +} diff --git a/MediaBrowser.Controller/Configuration/IServerConfigurationManager.cs b/MediaBrowser.Controller/Configuration/IServerConfigurationManager.cs index aac8cda2e..13c9f8d84 100644 --- a/MediaBrowser.Controller/Configuration/IServerConfigurationManager.cs +++ b/MediaBrowser.Controller/Configuration/IServerConfigurationManager.cs @@ -1,7 +1,5 @@ using MediaBrowser.Common.Configuration; using MediaBrowser.Model.Configuration; -using MediaBrowser.Model.Events; -using System; namespace MediaBrowser.Controller.Configuration { @@ -10,11 +8,6 @@ namespace MediaBrowser.Controller.Configuration /// public interface IServerConfigurationManager : IConfigurationManager { - /// - /// Occurs when [configuration updating]. - /// - event EventHandler> ConfigurationUpdating; - /// /// Gets the application paths. /// diff --git a/MediaBrowser.Controller/Entities/Movies/BoxSet.cs b/MediaBrowser.Controller/Entities/Movies/BoxSet.cs index 5e6bd9707..19c960167 100644 --- a/MediaBrowser.Controller/Entities/Movies/BoxSet.cs +++ b/MediaBrowser.Controller/Entities/Movies/BoxSet.cs @@ -102,17 +102,9 @@ namespace MediaBrowser.Controller.Entities.Movies var totalItems = items.Count; var percentages = new Dictionary(totalItems); - var tasks = new List(); - // Refresh songs foreach (var item in items) { - if (tasks.Count >= 3) - { - await Task.WhenAll(tasks).ConfigureAwait(false); - tasks.Clear(); - } - cancellationToken.ThrowIfCancellationRequested(); var innerProgress = new ActionableProgress(); @@ -132,13 +124,9 @@ namespace MediaBrowser.Controller.Entities.Movies }); // Avoid implicitly captured closure - var taskChild = item; - tasks.Add(Task.Run(async () => await RefreshItem(taskChild, refreshOptions, innerProgress, cancellationToken).ConfigureAwait(false), cancellationToken)); + await RefreshItem(item, refreshOptions, innerProgress, cancellationToken).ConfigureAwait(false); } - await Task.WhenAll(tasks).ConfigureAwait(false); - tasks.Clear(); - // Refresh current item await RefreshMetadata(refreshOptions, cancellationToken).ConfigureAwait(false); diff --git a/MediaBrowser.Controller/Library/IUserManager.cs b/MediaBrowser.Controller/Library/IUserManager.cs index 0da5f9272..c6bbf02ae 100644 --- a/MediaBrowser.Controller/Library/IUserManager.cs +++ b/MediaBrowser.Controller/Library/IUserManager.cs @@ -31,6 +31,7 @@ namespace MediaBrowser.Controller.Library event EventHandler> UserCreated; event EventHandler> UserConfigurationUpdated; + event EventHandler> UserPasswordChanged; /// /// Updates the configuration. diff --git a/MediaBrowser.Controller/Library/TVUtils.cs b/MediaBrowser.Controller/Library/TVUtils.cs index d8d836597..34486182b 100644 --- a/MediaBrowser.Controller/Library/TVUtils.cs +++ b/MediaBrowser.Controller/Library/TVUtils.cs @@ -269,7 +269,7 @@ namespace MediaBrowser.Controller.Library if ((attributes & FileAttributes.Hidden) == FileAttributes.Hidden) { - logger.Debug("Igoring series file or folder marked hidden: {0}", child.FullName); + //logger.Debug("Igoring series file or folder marked hidden: {0}", child.FullName); continue; } diff --git a/MediaBrowser.Controller/MediaBrowser.Controller.csproj b/MediaBrowser.Controller/MediaBrowser.Controller.csproj index 5243e1a2a..28e1ffb1c 100644 --- a/MediaBrowser.Controller/MediaBrowser.Controller.csproj +++ b/MediaBrowser.Controller/MediaBrowser.Controller.csproj @@ -68,6 +68,8 @@ Properties\SharedVersion.cs + + @@ -241,6 +243,7 @@ + @@ -320,6 +323,7 @@ + @@ -360,4 +364,4 @@ xcopy "$(TargetPath)" "$(SolutionDir)\Nuget\dlls\" /y /d /r /i --> - + \ No newline at end of file diff --git a/MediaBrowser.Controller/Notifications/INotificationsRepository.cs b/MediaBrowser.Controller/Notifications/INotificationsRepository.cs index 87b89e79c..254e56e05 100644 --- a/MediaBrowser.Controller/Notifications/INotificationsRepository.cs +++ b/MediaBrowser.Controller/Notifications/INotificationsRepository.cs @@ -16,10 +16,6 @@ namespace MediaBrowser.Controller.Notifications /// event EventHandler NotificationAdded; /// - /// Occurs when [notification updated]. - /// - event EventHandler NotificationUpdated; - /// /// Occurs when [notifications marked read]. /// event EventHandler NotificationsMarkedRead; @@ -37,14 +33,6 @@ namespace MediaBrowser.Controller.Notifications /// NotificationResult. NotificationResult GetNotifications(NotificationQuery query); - /// - /// Gets the notification. - /// - /// The id. - /// The user id. - /// Notification. - Notification GetNotification(string id, string userId); - /// /// Adds the notification. /// diff --git a/MediaBrowser.Controller/Session/AuthenticationRequest.cs b/MediaBrowser.Controller/Session/AuthenticationRequest.cs new file mode 100644 index 000000000..38871e814 --- /dev/null +++ b/MediaBrowser.Controller/Session/AuthenticationRequest.cs @@ -0,0 +1,14 @@ + +namespace MediaBrowser.Controller.Session +{ + public class AuthenticationRequest + { + public string Username { get; set; } + public string Password { get; set; } + public string App { get; set; } + public string AppVersion { get; set; } + public string DeviceId { get; set; } + public string DeviceName { get; set; } + public string RemoteEndPoint { get; set; } + } +} diff --git a/MediaBrowser.Controller/Session/ISessionManager.cs b/MediaBrowser.Controller/Session/ISessionManager.cs index e37a13923..f715ce770 100644 --- a/MediaBrowser.Controller/Session/ISessionManager.cs +++ b/MediaBrowser.Controller/Session/ISessionManager.cs @@ -1,6 +1,7 @@ using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Library; using MediaBrowser.Model.Entities; +using MediaBrowser.Model.Events; using MediaBrowser.Model.Session; using MediaBrowser.Model.Users; using System; @@ -46,6 +47,16 @@ namespace MediaBrowser.Controller.Session /// Occurs when [capabilities changed]. /// event EventHandler CapabilitiesChanged; + + /// + /// Occurs when [authentication failed]. + /// + event EventHandler> AuthenticationFailed; + + /// + /// Occurs when [authentication succeeded]. + /// + event EventHandler> AuthenticationSucceeded; /// /// Gets the sessions. @@ -211,23 +222,10 @@ namespace MediaBrowser.Controller.Session /// /// Authenticates the new session. /// - /// The username. - /// The password. - /// Type of the client. - /// The application version. - /// The device identifier. - /// Name of the device. - /// The remote end point. + /// The request. /// if set to true [is local]. /// Task{SessionInfo}. - Task AuthenticateNewSession(string username, - string password, - string clientType, - string appVersion, - string deviceId, - string deviceName, - string remoteEndPoint, - bool isLocal); + Task AuthenticateNewSession(AuthenticationRequest request, bool isLocal); /// /// Reports the capabilities. diff --git a/MediaBrowser.Controller/Subtitles/ISubtitleManager.cs b/MediaBrowser.Controller/Subtitles/ISubtitleManager.cs index 1d66d1505..0c814c0d4 100644 --- a/MediaBrowser.Controller/Subtitles/ISubtitleManager.cs +++ b/MediaBrowser.Controller/Subtitles/ISubtitleManager.cs @@ -1,5 +1,6 @@ using MediaBrowser.Controller.Entities; using MediaBrowser.Model.Providers; +using System; using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; @@ -8,6 +9,16 @@ namespace MediaBrowser.Controller.Subtitles { public interface ISubtitleManager { + /// + /// Occurs when [subtitle download failure]. + /// + event EventHandler SubtitleDownloadFailure; + + /// + /// Occurs when [subtitles downloaded]. + /// + event EventHandler SubtitlesDownloaded; + /// /// Adds the parts. /// @@ -31,7 +42,7 @@ namespace MediaBrowser.Controller.Subtitles /// The request. /// The cancellation token. /// Task{IEnumerable{RemoteSubtitleInfo}}. - Task> SearchSubtitles(SubtitleSearchRequest request, + Task> SearchSubtitles(SubtitleSearchRequest request, CancellationToken cancellationToken); /// @@ -41,8 +52,8 @@ namespace MediaBrowser.Controller.Subtitles /// The subtitle identifier. /// The cancellation token. /// Task. - Task DownloadSubtitles(Video video, - string subtitleId, + Task DownloadSubtitles(Video video, + string subtitleId, CancellationToken cancellationToken); /// diff --git a/MediaBrowser.Controller/Subtitles/SubtitleDownloadEventArgs.cs b/MediaBrowser.Controller/Subtitles/SubtitleDownloadEventArgs.cs new file mode 100644 index 000000000..1d204f2cb --- /dev/null +++ b/MediaBrowser.Controller/Subtitles/SubtitleDownloadEventArgs.cs @@ -0,0 +1,27 @@ +using System; +using MediaBrowser.Controller.Entities; + +namespace MediaBrowser.Controller.Subtitles +{ + public class SubtitleDownloadEventArgs + { + public BaseItem Item { get; set; } + + public string Format { get; set; } + + public string Language { get; set; } + + public bool IsForced { get; set; } + + public string Provider { get; set; } + } + + public class SubtitleDownloadFailureEventArgs + { + public BaseItem Item { get; set; } + + public string Provider { get; set; } + + public Exception Exception { get; set; } + } +} diff --git a/MediaBrowser.LocalMetadata/BaseXmlProvider.cs b/MediaBrowser.LocalMetadata/BaseXmlProvider.cs index 62aec5ecb..25778d036 100644 --- a/MediaBrowser.LocalMetadata/BaseXmlProvider.cs +++ b/MediaBrowser.LocalMetadata/BaseXmlProvider.cs @@ -27,7 +27,7 @@ namespace MediaBrowser.LocalMetadata var path = file.FullName; - await XmlProviderUtils.XmlParsingResourcePool.WaitAsync(cancellationToken).ConfigureAwait(false); + //await XmlProviderUtils.XmlParsingResourcePool.WaitAsync(cancellationToken).ConfigureAwait(false); try { @@ -46,7 +46,7 @@ namespace MediaBrowser.LocalMetadata } finally { - XmlProviderUtils.XmlParsingResourcePool.Release(); + //XmlProviderUtils.XmlParsingResourcePool.Release(); } return result; diff --git a/MediaBrowser.Model.Portable/MediaBrowser.Model.Portable.csproj b/MediaBrowser.Model.Portable/MediaBrowser.Model.Portable.csproj index ca48b8889..2a99076d4 100644 --- a/MediaBrowser.Model.Portable/MediaBrowser.Model.Portable.csproj +++ b/MediaBrowser.Model.Portable/MediaBrowser.Model.Portable.csproj @@ -83,6 +83,9 @@ + + Activity\ActivityLogEntry.cs + ApiClient\ApiClientExtensions.cs @@ -173,9 +176,6 @@ Configuration\ServerConfiguration.cs - - Configuration\SubtitleOptions.cs - Configuration\SubtitlePlaybackMode.cs @@ -728,6 +728,9 @@ Providers\RemoteSubtitleInfo.cs + + Providers\SubtitleOptions.cs + Querying\AllThemeMediaResult.cs diff --git a/MediaBrowser.Model.net35/MediaBrowser.Model.net35.csproj b/MediaBrowser.Model.net35/MediaBrowser.Model.net35.csproj index 1adf83d36..72414d454 100644 --- a/MediaBrowser.Model.net35/MediaBrowser.Model.net35.csproj +++ b/MediaBrowser.Model.net35/MediaBrowser.Model.net35.csproj @@ -52,6 +52,9 @@ + + Activity\ActivityLogEntry.cs + ApiClient\GeneralCommandEventArgs.cs @@ -136,9 +139,6 @@ Configuration\ServerConfiguration.cs - - Configuration\SubtitleOptions.cs - Configuration\SubtitlePlaybackMode.cs @@ -685,6 +685,9 @@ Providers\RemoteSubtitleInfo.cs + + Providers\SubtitleOptions.cs + Querying\AllThemeMediaResult.cs diff --git a/MediaBrowser.Model/Activity/ActivityLogEntry.cs b/MediaBrowser.Model/Activity/ActivityLogEntry.cs new file mode 100644 index 000000000..8fad57461 --- /dev/null +++ b/MediaBrowser.Model/Activity/ActivityLogEntry.cs @@ -0,0 +1,62 @@ +using MediaBrowser.Model.Logging; +using System; + +namespace MediaBrowser.Model.Activity +{ + public class ActivityLogEntry + { + /// + /// Gets or sets the identifier. + /// + /// The identifier. + public string Id { get; set; } + + /// + /// Gets or sets the name. + /// + /// The name. + public string Name { get; set; } + + /// + /// Gets or sets the overview. + /// + /// The overview. + public string Overview { get; set; } + + /// + /// Gets or sets the short overview. + /// + /// The short overview. + public string ShortOverview { get; set; } + + /// + /// Gets or sets the type. + /// + /// The type. + public string Type { get; set; } + + /// + /// Gets or sets the item identifier. + /// + /// The item identifier. + public string ItemId { get; set; } + + /// + /// Gets or sets the date. + /// + /// The date. + public DateTime Date { get; set; } + + /// + /// Gets or sets the user identifier. + /// + /// The user identifier. + public string UserId { get; set; } + + /// + /// Gets or sets the log severity. + /// + /// The log severity. + public LogSeverity Severity { get; set; } + } +} diff --git a/MediaBrowser.Model/ApiClient/IApiClient.cs b/MediaBrowser.Model/ApiClient/IApiClient.cs index 363500954..98b765e6b 100644 --- a/MediaBrowser.Model/ApiClient/IApiClient.cs +++ b/MediaBrowser.Model/ApiClient/IApiClient.cs @@ -623,7 +623,7 @@ namespace MediaBrowser.Model.ApiClient Task ReportPlaybackStoppedAsync(PlaybackStopInfo info); /// - /// Instructs antoher client to browse to a library item. + /// Instructs another client to browse to a library item. /// /// The session id. /// The id of the item to browse to. diff --git a/MediaBrowser.Model/Channels/ChannelItemQuery.cs b/MediaBrowser.Model/Channels/ChannelItemQuery.cs index a76c6cd2d..4aacc1619 100644 --- a/MediaBrowser.Model/Channels/ChannelItemQuery.cs +++ b/MediaBrowser.Model/Channels/ChannelItemQuery.cs @@ -1,6 +1,5 @@ using MediaBrowser.Model.Entities; using MediaBrowser.Model.Querying; -using System.Collections.Generic; namespace MediaBrowser.Model.Channels { @@ -39,13 +38,13 @@ namespace MediaBrowser.Model.Channels public SortOrder? SortOrder { get; set; } public string[] SortBy { get; set; } public ItemFilter[] Filters { get; set; } - public List Fields { get; set; } + public ItemFields[] Fields { get; set; } public ChannelItemQuery() { Filters = new ItemFilter[] { }; SortBy = new string[] { }; - Fields = new List(); + Fields = new ItemFields[] { }; } } diff --git a/MediaBrowser.Model/Configuration/ServerConfiguration.cs b/MediaBrowser.Model/Configuration/ServerConfiguration.cs index 4734e2af7..6600a3e91 100644 --- a/MediaBrowser.Model/Configuration/ServerConfiguration.cs +++ b/MediaBrowser.Model/Configuration/ServerConfiguration.cs @@ -2,6 +2,7 @@ using MediaBrowser.Model.FileOrganization; using MediaBrowser.Model.LiveTv; using MediaBrowser.Model.Notifications; +using MediaBrowser.Model.Providers; namespace MediaBrowser.Model.Configuration { @@ -285,8 +286,6 @@ namespace MediaBrowser.Model.Configuration new MetadataOptions(0, 1280) {ItemType = "Season"} }; - - SubtitleOptions = new SubtitleOptions(); } } } diff --git a/MediaBrowser.Model/Configuration/SubtitleOptions.cs b/MediaBrowser.Model/Configuration/SubtitleOptions.cs deleted file mode 100644 index d50dba1b2..000000000 --- a/MediaBrowser.Model/Configuration/SubtitleOptions.cs +++ /dev/null @@ -1,22 +0,0 @@ -namespace MediaBrowser.Model.Configuration -{ - public class SubtitleOptions - { - public bool SkipIfGraphicalSubtitlesPresent { get; set; } - public bool SkipIfAudioTrackMatches { get; set; } - public string[] DownloadLanguages { get; set; } - public bool DownloadMovieSubtitles { get; set; } - public bool DownloadEpisodeSubtitles { get; set; } - - public string OpenSubtitlesUsername { get; set; } - public string OpenSubtitlesPasswordHash { get; set; } - public bool IsOpenSubtitleVipAccount { get; set; } - - public SubtitleOptions() - { - DownloadLanguages = new string[] { }; - - SkipIfAudioTrackMatches = true; - } - } -} \ No newline at end of file diff --git a/MediaBrowser.Model/Events/GenericEventArgs.cs b/MediaBrowser.Model/Events/GenericEventArgs.cs index 5a83419e1..3c558577a 100644 --- a/MediaBrowser.Model/Events/GenericEventArgs.cs +++ b/MediaBrowser.Model/Events/GenericEventArgs.cs @@ -13,5 +13,21 @@ namespace MediaBrowser.Model.Events /// /// The argument. public T Argument { get; set; } + + /// + /// Initializes a new instance of the class. + /// + /// The argument. + public GenericEventArgs(T arg) + { + Argument = arg; + } + + /// + /// Initializes a new instance of the class. + /// + public GenericEventArgs() + { + } } } diff --git a/MediaBrowser.Model/MediaBrowser.Model.csproj b/MediaBrowser.Model/MediaBrowser.Model.csproj index 042828887..75694cb04 100644 --- a/MediaBrowser.Model/MediaBrowser.Model.csproj +++ b/MediaBrowser.Model/MediaBrowser.Model.csproj @@ -59,6 +59,7 @@ Properties\SharedVersion.cs + @@ -99,7 +100,7 @@ - + @@ -378,4 +379,4 @@ xcopy "$(TargetPath)" "$(SolutionDir)\Nuget\dlls\net45\" /y /d /r /i --> - + \ No newline at end of file diff --git a/MediaBrowser.Model/Providers/SubtitleOptions.cs b/MediaBrowser.Model/Providers/SubtitleOptions.cs new file mode 100644 index 000000000..84f01e0b7 --- /dev/null +++ b/MediaBrowser.Model/Providers/SubtitleOptions.cs @@ -0,0 +1,22 @@ +namespace MediaBrowser.Model.Providers +{ + public class SubtitleOptions + { + public bool SkipIfGraphicalSubtitlesPresent { get; set; } + public bool SkipIfAudioTrackMatches { get; set; } + public string[] DownloadLanguages { get; set; } + public bool DownloadMovieSubtitles { get; set; } + public bool DownloadEpisodeSubtitles { get; set; } + + public string OpenSubtitlesUsername { get; set; } + public string OpenSubtitlesPasswordHash { get; set; } + public bool IsOpenSubtitleVipAccount { get; set; } + + public SubtitleOptions() + { + DownloadLanguages = new string[] { }; + + SkipIfAudioTrackMatches = true; + } + } +} \ No newline at end of file diff --git a/MediaBrowser.Model/Tasks/TaskResult.cs b/MediaBrowser.Model/Tasks/TaskResult.cs index e73b4c9a1..956d68ae4 100644 --- a/MediaBrowser.Model/Tasks/TaskResult.cs +++ b/MediaBrowser.Model/Tasks/TaskResult.cs @@ -42,5 +42,11 @@ namespace MediaBrowser.Model.Tasks /// /// The error message. public string ErrorMessage { get; set; } + + /// + /// Gets or sets the long error message. + /// + /// The long error message. + public string LongErrorMessage { get; set; } } } diff --git a/MediaBrowser.Providers/MediaBrowser.Providers.csproj b/MediaBrowser.Providers/MediaBrowser.Providers.csproj index 76a1e52f5..66188f796 100644 --- a/MediaBrowser.Providers/MediaBrowser.Providers.csproj +++ b/MediaBrowser.Providers/MediaBrowser.Providers.csproj @@ -152,6 +152,7 @@ + @@ -213,4 +214,4 @@ --> - + \ No newline at end of file diff --git a/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs b/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs index a2e1ba05a..f48707582 100644 --- a/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs +++ b/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs @@ -13,10 +13,12 @@ using MediaBrowser.Controller.MediaEncoding; using MediaBrowser.Controller.Persistence; using MediaBrowser.Controller.Providers; using MediaBrowser.Controller.Subtitles; +using MediaBrowser.Model.Configuration; using MediaBrowser.Model.Entities; using MediaBrowser.Model.IO; using MediaBrowser.Model.Logging; using MediaBrowser.Model.MediaInfo; +using MediaBrowser.Model.Providers; using MediaBrowser.Model.Serialization; using System; using System.Collections.Generic; @@ -464,6 +466,11 @@ namespace MediaBrowser.Providers.MediaInfo } } + private SubtitleOptions GetOptions() + { + return _config.GetConfiguration("subtitles"); + } + /// /// Adds the external subtitles. /// @@ -484,9 +491,11 @@ namespace MediaBrowser.Providers.MediaInfo var enableSubtitleDownloading = options.MetadataRefreshMode == MetadataRefreshMode.Default || options.MetadataRefreshMode == MetadataRefreshMode.FullRefresh; - if (enableSubtitleDownloading && (_config.Configuration.SubtitleOptions.DownloadEpisodeSubtitles && + var subtitleOptions = GetOptions(); + + if (enableSubtitleDownloading && (subtitleOptions.DownloadEpisodeSubtitles && video is Episode) || - (_config.Configuration.SubtitleOptions.DownloadMovieSubtitles && + (subtitleOptions.DownloadMovieSubtitles && video is Movie)) { var downloadedLanguages = await new SubtitleDownloader(_logger, @@ -494,9 +503,9 @@ namespace MediaBrowser.Providers.MediaInfo .DownloadSubtitles(video, currentStreams, externalSubtitleStreams, - _config.Configuration.SubtitleOptions.SkipIfGraphicalSubtitlesPresent, - _config.Configuration.SubtitleOptions.SkipIfAudioTrackMatches, - _config.Configuration.SubtitleOptions.DownloadLanguages, + subtitleOptions.SkipIfGraphicalSubtitlesPresent, + subtitleOptions.SkipIfAudioTrackMatches, + subtitleOptions.DownloadLanguages, cancellationToken).ConfigureAwait(false); // Rescan diff --git a/MediaBrowser.Providers/MediaInfo/SubtitleScheduledTask.cs b/MediaBrowser.Providers/MediaInfo/SubtitleScheduledTask.cs index 361cc317c..63df3f50d 100644 --- a/MediaBrowser.Providers/MediaInfo/SubtitleScheduledTask.cs +++ b/MediaBrowser.Providers/MediaInfo/SubtitleScheduledTask.cs @@ -1,10 +1,12 @@ -using MediaBrowser.Common.ScheduledTasks; +using MediaBrowser.Common.Configuration; +using MediaBrowser.Common.ScheduledTasks; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.Movies; using MediaBrowser.Controller.Entities.TV; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Subtitles; +using MediaBrowser.Model.Configuration; using MediaBrowser.Model.Entities; using MediaBrowser.Model.Logging; using System; @@ -12,6 +14,7 @@ using System.Collections.Generic; using System.Linq; using System.Threading; using System.Threading.Tasks; +using MediaBrowser.Model.Providers; namespace MediaBrowser.Providers.MediaInfo { @@ -45,8 +48,15 @@ namespace MediaBrowser.Providers.MediaInfo get { return "Library"; } } + private SubtitleOptions GetOptions() + { + return _config.GetConfiguration("subtitles"); + } + public async Task Execute(CancellationToken cancellationToken, IProgress progress) { + var options = GetOptions(); + var videos = _libraryManager.RootFolder .RecursiveChildren .OfType