aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--MediaBrowser.Api/ApiEntryPoint.cs42
-rw-r--r--MediaBrowser.Api/AuthorizationRequestFilterAttribute.cs4
-rw-r--r--MediaBrowser.Api/BaseApiService.cs8
-rw-r--r--MediaBrowser.Api/Images/ImageService.cs186
-rw-r--r--MediaBrowser.Api/Images/ImageWriter.cs5
-rw-r--r--MediaBrowser.Api/ItemUpdateService.cs26
-rw-r--r--MediaBrowser.Api/Library/LibraryService.cs25
-rw-r--r--MediaBrowser.Api/Library/LibraryStructureService.cs50
-rw-r--r--MediaBrowser.Api/LibraryService.cs37
-rw-r--r--MediaBrowser.Api/LiveTv/LiveTvImageService.cs195
-rw-r--r--MediaBrowser.Api/LiveTv/LiveTvService.cs234
-rw-r--r--MediaBrowser.Api/MediaBrowser.Api.csproj30
-rw-r--r--MediaBrowser.Api/Playback/BaseStreamingService.cs308
-rw-r--r--MediaBrowser.Api/Playback/Hls/AudioHlsService.cs7
-rw-r--r--MediaBrowser.Api/Playback/Hls/BaseHlsService.cs29
-rw-r--r--MediaBrowser.Api/Playback/Hls/HlsSegmentService.cs9
-rw-r--r--MediaBrowser.Api/Playback/Hls/VideoHlsService.cs13
-rw-r--r--MediaBrowser.Api/Playback/Progressive/AudioService.cs18
-rw-r--r--MediaBrowser.Api/Playback/Progressive/BaseProgressiveStreamingService.cs84
-rw-r--r--MediaBrowser.Api/Playback/Progressive/VideoService.cs90
-rw-r--r--MediaBrowser.Api/Playback/StreamRequest.cs6
-rw-r--r--MediaBrowser.Api/Playback/StreamState.cs25
-rw-r--r--MediaBrowser.Api/SearchService.cs5
-rw-r--r--MediaBrowser.Api/SystemService.cs6
-rw-r--r--MediaBrowser.Api/TvShowsService.cs151
-rw-r--r--MediaBrowser.Api/UserLibrary/ItemsService.cs16
-rw-r--r--MediaBrowser.Api/UserService.cs12
-rw-r--r--MediaBrowser.Common.Implementations/BaseApplicationHost.cs39
-rw-r--r--MediaBrowser.Common.Implementations/BaseApplicationPaths.cs90
-rw-r--r--MediaBrowser.Common.Implementations/Configuration/BaseConfigurationManager.cs45
-rw-r--r--MediaBrowser.Common.Implementations/HttpClientManager/HttpClientManager.cs212
-rw-r--r--MediaBrowser.Common.Implementations/IO/CommonFileSystem.cs42
-rw-r--r--MediaBrowser.Common.Implementations/Logging/NlogManager.cs2
-rw-r--r--MediaBrowser.Common.Implementations/MediaBrowser.Common.Implementations.csproj34
-rw-r--r--MediaBrowser.Common.Implementations/ScheduledTasks/ScheduledTaskWorker.cs40
-rw-r--r--MediaBrowser.Common.Implementations/ScheduledTasks/Tasks/DeleteCacheFileTask.cs11
-rw-r--r--MediaBrowser.Common.Implementations/ScheduledTasks/Tasks/DeleteLogFileTask.cs11
-rw-r--r--MediaBrowser.Common.Implementations/ScheduledTasks/Tasks/ReloadLoggerTask.cs7
-rw-r--r--MediaBrowser.Common.Implementations/Security/MBLicenseFile.cs20
-rw-r--r--MediaBrowser.Common.Implementations/Updates/InstallationManager.cs1
-rw-r--r--MediaBrowser.Common.Implementations/packages.config2
-rw-r--r--MediaBrowser.Common/Configuration/ConfigurationHelper.cs2
-rw-r--r--MediaBrowser.Common/IApplicationHost.cs3
-rw-r--r--MediaBrowser.Common/IO/IFileSystem.cs7
-rw-r--r--MediaBrowser.Common/MediaBrowser.Common.csproj17
-rw-r--r--MediaBrowser.Common/Net/HttpRequestOptions.cs4
-rw-r--r--MediaBrowser.Common/Net/IHttpClient.cs7
-rw-r--r--MediaBrowser.Common/Net/MimeTypes.cs5
-rw-r--r--MediaBrowser.Common/Plugins/BasePlugin.cs2
-rw-r--r--MediaBrowser.Controller/Drawing/IImageProcessor.cs8
-rw-r--r--MediaBrowser.Controller/Drawing/ImageProcessingOptions.cs2
-rw-r--r--MediaBrowser.Controller/Entities/AdultVideo.cs9
-rw-r--r--MediaBrowser.Controller/Entities/Audio/Audio.cs16
-rw-r--r--MediaBrowser.Controller/Entities/Audio/MusicAlbum.cs8
-rw-r--r--MediaBrowser.Controller/Entities/Audio/MusicArtist.cs10
-rw-r--r--MediaBrowser.Controller/Entities/BaseItem.cs147
-rw-r--r--MediaBrowser.Controller/Entities/Book.cs19
-rw-r--r--MediaBrowser.Controller/Entities/CollectionFolder.cs7
-rw-r--r--MediaBrowser.Controller/Entities/Folder.cs503
-rw-r--r--MediaBrowser.Controller/Entities/Game.cs24
-rw-r--r--MediaBrowser.Controller/Entities/GameSystem.cs9
-rw-r--r--MediaBrowser.Controller/Entities/Genre.cs1
-rw-r--r--MediaBrowser.Controller/Entities/IHasImages.cs115
-rw-r--r--MediaBrowser.Controller/Entities/IHasLanguage.cs15
-rw-r--r--MediaBrowser.Controller/Entities/IHasPreferredMetadataLanguage.cs21
-rw-r--r--MediaBrowser.Controller/Entities/IHasUserData.cs15
-rw-r--r--MediaBrowser.Controller/Entities/IndexFolder.cs206
-rw-r--r--MediaBrowser.Controller/Entities/Movies/BoxSet.cs18
-rw-r--r--MediaBrowser.Controller/Entities/Movies/Movie.cs17
-rw-r--r--MediaBrowser.Controller/Entities/MusicVideo.cs6
-rw-r--r--MediaBrowser.Controller/Entities/TV/Episode.cs23
-rw-r--r--MediaBrowser.Controller/Entities/TV/Season.cs73
-rw-r--r--MediaBrowser.Controller/Entities/TV/Series.cs128
-rw-r--r--MediaBrowser.Controller/Entities/Trailer.cs18
-rw-r--r--MediaBrowser.Controller/Entities/User.cs70
-rw-r--r--MediaBrowser.Controller/IServerApplicationPaths.cs8
-rw-r--r--MediaBrowser.Controller/Kernel.cs30
-rw-r--r--MediaBrowser.Controller/Library/ILibraryManager.cs5
-rw-r--r--MediaBrowser.Controller/Library/IUserDataManager.cs2
-rw-r--r--MediaBrowser.Controller/Library/ItemResolveArgs.cs2
-rw-r--r--MediaBrowser.Controller/Library/TVUtils.cs9
-rw-r--r--MediaBrowser.Controller/Library/UserDataSaveEventArgs.cs2
-rw-r--r--MediaBrowser.Controller/LiveTv/Channel.cs73
-rw-r--r--MediaBrowser.Controller/LiveTv/ChannelInfo.cs19
-rw-r--r--MediaBrowser.Controller/LiveTv/ILiveTvManager.cs140
-rw-r--r--MediaBrowser.Controller/LiveTv/ILiveTvService.cs59
-rw-r--r--MediaBrowser.Controller/LiveTv/LiveTvChannel.cs57
-rw-r--r--MediaBrowser.Controller/LiveTv/LiveTvException.cs18
-rw-r--r--MediaBrowser.Controller/LiveTv/LiveTvProgram.cs36
-rw-r--r--MediaBrowser.Controller/LiveTv/LiveTvRecording.cs43
-rw-r--r--MediaBrowser.Controller/LiveTv/ProgramInfo.cs68
-rw-r--r--MediaBrowser.Controller/LiveTv/RecordingInfo.cs78
-rw-r--r--MediaBrowser.Controller/LiveTv/SeriesTimerInfo.cs58
-rw-r--r--MediaBrowser.Controller/LiveTv/StreamResponseInfo.cs (renamed from MediaBrowser.Controller/LiveTv/ImageResponseInfo.cs)2
-rw-r--r--MediaBrowser.Controller/LiveTv/TimerInfo.cs37
-rw-r--r--MediaBrowser.Controller/MediaBrowser.Controller.csproj51
-rw-r--r--MediaBrowser.Controller/MediaInfo/FFMpegManager.cs169
-rw-r--r--MediaBrowser.Controller/MediaInfo/MediaEncoderHelpers.cs40
-rw-r--r--MediaBrowser.Controller/Providers/BaseItemXmlParser.cs113
-rw-r--r--MediaBrowser.Controller/Providers/BaseMetadataProvider.cs72
-rw-r--r--MediaBrowser.Controller/Providers/IImageEnhancer.cs8
-rw-r--r--MediaBrowser.Controller/Providers/IImageProvider.cs6
-rw-r--r--MediaBrowser.Controller/Resolvers/EntityResolutionHelper.cs13
-rw-r--r--MediaBrowser.Controller/Session/ISessionControllerFactory.cs16
-rw-r--r--MediaBrowser.Controller/Session/ISessionManager.cs9
-rw-r--r--MediaBrowser.Controller/Session/SessionInfo.cs6
-rw-r--r--MediaBrowser.Model.Portable/MediaBrowser.Model.Portable.csproj14
-rw-r--r--MediaBrowser.Model.Portable/packages.config2
-rw-r--r--MediaBrowser.Model.net35/MediaBrowser.Model.net35.csproj12
-rw-r--r--MediaBrowser.Model/Configuration/BaseApplicationConfiguration.cs8
-rw-r--r--MediaBrowser.Model/Configuration/ImageDownloadOptions.cs (renamed from MediaBrowser.Model/Entities/ImageDownloadOptions.cs)18
-rw-r--r--MediaBrowser.Model/Configuration/ServerConfiguration.cs79
-rw-r--r--MediaBrowser.Model/Configuration/UserConfiguration.cs7
-rw-r--r--MediaBrowser.Model/Dto/BaseItemDto.cs28
-rw-r--r--MediaBrowser.Model/Entities/MetadataFields.cs14
-rw-r--r--MediaBrowser.Model/LiveTv/ChannelInfoDto.cs6
-rw-r--r--MediaBrowser.Model/LiveTv/ProgramInfoDto.cs101
-rw-r--r--MediaBrowser.Model/LiveTv/RecordingGroupDto.cs27
-rw-r--r--MediaBrowser.Model/LiveTv/RecordingInfoDto.cs93
-rw-r--r--MediaBrowser.Model/LiveTv/RecordingQuery.cs43
-rw-r--r--MediaBrowser.Model/LiveTv/RecordingStatus.cs21
-rw-r--r--MediaBrowser.Model/LiveTv/SeriesTimerInfoDto.cs138
-rw-r--r--MediaBrowser.Model/LiveTv/TimerInfoDto.cs68
-rw-r--r--MediaBrowser.Model/MediaBrowser.Model.csproj23
-rw-r--r--MediaBrowser.Model/Querying/ItemFields.cs2
-rw-r--r--MediaBrowser.Model/Querying/ItemQuery.cs6
-rw-r--r--MediaBrowser.Model/Search/SearchHint.cs6
-rw-r--r--MediaBrowser.Model/Session/SessionInfoDto.cs6
-rw-r--r--MediaBrowser.Model/System/SystemInfo.cs12
-rw-r--r--MediaBrowser.Model/packages.config2
-rw-r--r--MediaBrowser.Mono.sln25
-rw-r--r--MediaBrowser.Mono.userprefs17
-rw-r--r--MediaBrowser.Providers/CollectionFolderImageProvider.cs7
-rw-r--r--MediaBrowser.Providers/ImageFromMediaLocationProvider.cs36
-rw-r--r--MediaBrowser.Providers/ImagesByNameProvider.cs7
-rw-r--r--MediaBrowser.Providers/LiveTv/ChannelProviderFromXml.cs4
-rw-r--r--MediaBrowser.Providers/MediaBrowser.Providers.csproj26
-rw-r--r--MediaBrowser.Providers/MediaInfo/AudioImageProvider.cs53
-rw-r--r--MediaBrowser.Providers/MediaInfo/BaseFFProbeProvider.cs2
-rw-r--r--MediaBrowser.Providers/MediaInfo/FFProbeAudioInfoProvider.cs2
-rw-r--r--MediaBrowser.Providers/MediaInfo/FFProbeVideoInfoProvider.cs6
-rw-r--r--MediaBrowser.Providers/MediaInfo/VideoImageProvider.cs46
-rw-r--r--MediaBrowser.Providers/Movies/FanArtMovieProvider.cs8
-rw-r--r--MediaBrowser.Providers/Movies/FanArtMovieUpdatesPrescanTask.cs2
-rw-r--r--MediaBrowser.Providers/Movies/ManualFanartMovieImageProvider.cs15
-rw-r--r--MediaBrowser.Providers/Movies/ManualMovieDbImageProvider.cs20
-rw-r--r--MediaBrowser.Providers/Movies/ManualMovieDbPersonImageProvider.cs47
-rw-r--r--MediaBrowser.Providers/Movies/MovieDbImagesProvider.cs20
-rw-r--r--MediaBrowser.Providers/Movies/MovieDbPersonImageProvider.cs3
-rw-r--r--MediaBrowser.Providers/Movies/MovieDbPersonProvider.cs22
-rw-r--r--MediaBrowser.Providers/Movies/MovieDbProvider.cs146
-rw-r--r--MediaBrowser.Providers/Movies/MovieProviderFromXml.cs5
-rw-r--r--MediaBrowser.Providers/Movies/MovieUpdatesPrescanTask.cs50
-rw-r--r--MediaBrowser.Providers/Movies/OpenMovieDatabaseProvider.cs10
-rw-r--r--MediaBrowser.Providers/Movies/PersonUpdatesPreScanTask.cs236
-rw-r--r--MediaBrowser.Providers/Music/AlbumInfoFromSongProvider.cs1
-rw-r--r--MediaBrowser.Providers/Music/FanArtAlbumProvider.cs9
-rw-r--r--MediaBrowser.Providers/Music/FanArtArtistProvider.cs72
-rw-r--r--MediaBrowser.Providers/Music/FanArtUpdatesPrescanTask.cs2
-rw-r--r--MediaBrowser.Providers/Music/LastFmImageProvider.cs2
-rw-r--r--MediaBrowser.Providers/Music/LastfmHelper.cs16
-rw-r--r--MediaBrowser.Providers/Music/ManualFanartAlbumProvider.cs16
-rw-r--r--MediaBrowser.Providers/Music/ManualFanartArtistProvider.cs12
-rw-r--r--MediaBrowser.Providers/Music/ManualLastFmImageProvider.cs6
-rw-r--r--MediaBrowser.Providers/Savers/ChannelXmlSaver.cs2
-rw-r--r--MediaBrowser.Providers/Savers/EpisodeXmlSaver.cs20
-rw-r--r--MediaBrowser.Providers/Savers/MovieXmlSaver.cs6
-rw-r--r--MediaBrowser.Providers/Savers/XmlSaverHelpers.cs28
-rw-r--r--MediaBrowser.Providers/Studios/StudioImageProvider.cs154
-rw-r--r--MediaBrowser.Providers/Studios/StudiosManualImageProvider.cs135
-rw-r--r--MediaBrowser.Providers/Studios/posters.txt0
-rw-r--r--MediaBrowser.Providers/Studios/thumbs.txt493
-rw-r--r--MediaBrowser.Providers/TV/EpisodeIndexNumberProvider.cs7
-rw-r--r--MediaBrowser.Providers/TV/EpisodeXmlParser.cs54
-rw-r--r--MediaBrowser.Providers/TV/FanArtSeasonProvider.cs6
-rw-r--r--MediaBrowser.Providers/TV/FanArtTVProvider.cs74
-rw-r--r--MediaBrowser.Providers/TV/FanArtTvUpdatesPrescanTask.cs2
-rw-r--r--MediaBrowser.Providers/TV/ManualFanartSeasonProvider.cs15
-rw-r--r--MediaBrowser.Providers/TV/ManualFanartSeriesProvider.cs8
-rw-r--r--MediaBrowser.Providers/TV/ManualTvdbEpisodeImageProvider.cs6
-rw-r--r--MediaBrowser.Providers/TV/ManualTvdbPersonImageProvider.cs6
-rw-r--r--MediaBrowser.Providers/TV/ManualTvdbSeasonImageProvider.cs16
-rw-r--r--MediaBrowser.Providers/TV/ManualTvdbSeriesImageProvider.cs19
-rw-r--r--MediaBrowser.Providers/TV/SeasonIndexNumberProvider.cs83
-rw-r--r--MediaBrowser.Providers/TV/SeasonProviderFromXml.cs2
-rw-r--r--MediaBrowser.Providers/TV/SeriesPostScanTask.cs5
-rw-r--r--MediaBrowser.Providers/TV/TvdbEpisodeProvider.cs52
-rw-r--r--MediaBrowser.Providers/TV/TvdbPersonImageProvider.cs3
-rw-r--r--MediaBrowser.Providers/TV/TvdbPrescanTask.cs50
-rw-r--r--MediaBrowser.Providers/TV/TvdbSeasonProvider.cs46
-rw-r--r--MediaBrowser.Providers/TV/TvdbSeriesImageProvider.cs39
-rw-r--r--MediaBrowser.Providers/TV/TvdbSeriesProvider.cs14
-rw-r--r--MediaBrowser.Providers/VirtualItemImageValidator.cs48
-rw-r--r--MediaBrowser.Server.Implementations/Configuration/ServerConfigurationManager.cs31
-rw-r--r--MediaBrowser.Server.Implementations/Drawing/ImageHeader.cs3
-rw-r--r--MediaBrowser.Server.Implementations/Drawing/ImageProcessor.cs60
-rw-r--r--MediaBrowser.Server.Implementations/Dto/DtoService.cs136
-rw-r--r--MediaBrowser.Server.Implementations/HttpServer/HttpListenerHost.cs8
-rw-r--r--MediaBrowser.Server.Implementations/HttpServer/ServerFactory.cs3
-rw-r--r--MediaBrowser.Server.Implementations/IO/DirectoryWatchers.cs13
-rw-r--r--MediaBrowser.Server.Implementations/Library/CoreResolutionIgnoreRule.cs6
-rw-r--r--MediaBrowser.Server.Implementations/Library/LibraryManager.cs14
-rw-r--r--MediaBrowser.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs75
-rw-r--r--MediaBrowser.Server.Implementations/Library/UserDataManager.cs2
-rw-r--r--MediaBrowser.Server.Implementations/Library/UserManager.cs57
-rw-r--r--MediaBrowser.Server.Implementations/LiveTv/ChannelImageProvider.cs87
-rw-r--r--MediaBrowser.Server.Implementations/LiveTv/LiveTvDtoService.cs503
-rw-r--r--MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs799
-rw-r--r--MediaBrowser.Server.Implementations/LiveTv/ProgramImageProvider.cs156
-rw-r--r--MediaBrowser.Server.Implementations/LiveTv/RecordingImageProvider.cs156
-rw-r--r--MediaBrowser.Server.Implementations/Localization/Ratings/au.txt4
-rw-r--r--MediaBrowser.Server.Implementations/Localization/Ratings/be.txt6
-rw-r--r--MediaBrowser.Server.Implementations/Localization/Ratings/de.txt1
-rw-r--r--MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj161
-rw-r--r--MediaBrowser.Server.Implementations/MediaEncoder/MediaEncoder.cs19
-rw-r--r--MediaBrowser.Server.Implementations/Persistence/SqliteChapterRepository.cs10
-rw-r--r--MediaBrowser.Server.Implementations/Persistence/SqliteExtensions.cs18
-rw-r--r--MediaBrowser.Server.Implementations/Persistence/SqliteProviderInfoRepository.cs10
-rw-r--r--MediaBrowser.Server.Implementations/Persistence/SqliteUserDataRepository.cs10
-rw-r--r--MediaBrowser.Server.Implementations/Providers/ImageSaver.cs51
-rw-r--r--MediaBrowser.Server.Implementations/Providers/ProviderManager.cs67
-rw-r--r--MediaBrowser.Server.Implementations/Roku/RokuControllerFactory.cs32
-rw-r--r--MediaBrowser.Server.Implementations/Roku/RokuSessionController.cs149
-rw-r--r--MediaBrowser.Server.Implementations/ScheduledTasks/ChapterImagesTask.cs21
-rw-r--r--MediaBrowser.Server.Implementations/ServerManager/ServerManager.cs2
-rw-r--r--MediaBrowser.Server.Implementations/Session/SessionManager.cs41
-rw-r--r--MediaBrowser.Server.Implementations/Session/SessionWebSocketListener.cs10
-rw-r--r--MediaBrowser.Server.Implementations/Sorting/AiredEpisodeOrderComparer.cs34
-rw-r--r--MediaBrowser.Server.Implementations/Sorting/AlphanumComparator.cs119
-rw-r--r--MediaBrowser.Server.Implementations/Sorting/NameComparer.cs3
-rw-r--r--MediaBrowser.Server.Implementations/Sorting/SeriesSortNameComparer.cs2
-rw-r--r--MediaBrowser.Server.Implementations/Sorting/SortNameComparer.cs3
-rw-r--r--MediaBrowser.Server.Implementations/WebSocket/AlchemyServer.cs17
-rw-r--r--MediaBrowser.Server.Implementations/packages.config2
-rw-r--r--MediaBrowser.Server.Mono/FFMpeg/FFMpegDownloadInfo.cs57
-rw-r--r--MediaBrowser.Server.Mono/MediaBrowser.Server.Mono.csproj24
-rw-r--r--MediaBrowser.Server.Mono/Program.cs4
-rw-r--r--MediaBrowser.Server.Mono/app.config3
-rw-r--r--MediaBrowser.ServerApplication/App.config7
-rw-r--r--MediaBrowser.ServerApplication/App.xaml.cs13
-rw-r--r--MediaBrowser.ServerApplication/ApplicationHost.cs95
-rw-r--r--MediaBrowser.ServerApplication/EntryPoints/WanAddressEntryPoint.cs55
-rw-r--r--MediaBrowser.ServerApplication/FFMpeg/FFMpegDownloadInfo.cs8
-rw-r--r--MediaBrowser.ServerApplication/FFMpeg/FFMpegDownloader.cs85
-rw-r--r--MediaBrowser.ServerApplication/LibraryExplorer.xaml.cs47
-rw-r--r--MediaBrowser.ServerApplication/MainStartup.cs78
-rw-r--r--MediaBrowser.ServerApplication/MainWindow.xaml.cs18
-rw-r--r--MediaBrowser.ServerApplication/MediaBrowser.ServerApplication.csproj8
-rw-r--r--MediaBrowser.ServerApplication/Native/ServerAuthorization.cs2
-rw-r--r--MediaBrowser.ServerApplication/Splash/SplashWindow.xaml13
-rw-r--r--MediaBrowser.ServerApplication/Splash/SplashWindow.xaml.cs37
-rw-r--r--MediaBrowser.ServerApplication/packages.config2
-rw-r--r--MediaBrowser.WebDashboard/Api/DashboardService.cs62
-rw-r--r--MediaBrowser.WebDashboard/ApiClient.js191
-rw-r--r--MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj893
-rw-r--r--MediaBrowser.WebDashboard/packages.config2
-rw-r--r--Nuget/MediaBrowser.Common.Internal.nuspec7
-rw-r--r--Nuget/MediaBrowser.Common.nuspec2
-rw-r--r--Nuget/MediaBrowser.Server.Core.nuspec4
-rw-r--r--README.md6
260 files changed, 8809 insertions, 3856 deletions
diff --git a/MediaBrowser.Api/ApiEntryPoint.cs b/MediaBrowser.Api/ApiEntryPoint.cs
index 5cd0aae80..785cc395c 100644
--- a/MediaBrowser.Api/ApiEntryPoint.cs
+++ b/MediaBrowser.Api/ApiEntryPoint.cs
@@ -1,4 +1,6 @@
-using MediaBrowser.Controller.Plugins;
+using MediaBrowser.Controller;
+using MediaBrowser.Controller.Plugins;
+using MediaBrowser.Controller.Resolvers;
using MediaBrowser.Model.Logging;
using System;
using System.Collections.Generic;
@@ -28,12 +30,19 @@ namespace MediaBrowser.Api
private ILogger Logger { get; set; }
/// <summary>
+ /// The application paths
+ /// </summary>
+ private readonly IServerApplicationPaths AppPaths;
+
+ /// <summary>
/// Initializes a new instance of the <see cref="ApiEntryPoint" /> class.
/// </summary>
/// <param name="logger">The logger.</param>
- public ApiEntryPoint(ILogger logger)
+ /// <param name="appPaths">The application paths.</param>
+ public ApiEntryPoint(ILogger logger, IServerApplicationPaths appPaths)
{
Logger = logger;
+ AppPaths = appPaths;
Instance = this;
}
@@ -43,6 +52,31 @@ namespace MediaBrowser.Api
/// </summary>
public void Run()
{
+ try
+ {
+ DeleteEncodedMediaCache();
+ }
+ catch (DirectoryNotFoundException)
+ {
+ // Don't clutter the log
+ }
+ catch (IOException ex)
+ {
+ Logger.ErrorException("Error deleting encoded media cache", ex);
+ }
+ }
+
+ /// <summary>
+ /// Deletes the encoded media cache.
+ /// </summary>
+ private void DeleteEncodedMediaCache()
+ {
+ foreach (var file in Directory.EnumerateFiles(AppPaths.EncodedMediaCachePath)
+ .Where(i => EntityResolutionHelper.VideoFileExtensions.Contains(Path.GetExtension(i)))
+ .ToList())
+ {
+ File.Delete(file);
+ }
}
/// <summary>
@@ -62,7 +96,7 @@ namespace MediaBrowser.Api
{
var jobCount = _activeTranscodingJobs.Count;
- Parallel.ForEach(_activeTranscodingJobs, KillTranscodingJob);
+ Parallel.ForEach(_activeTranscodingJobs.ToList(), KillTranscodingJob);
// Try to allow for some time to kill the ffmpeg processes and delete the partial stream files
if (jobCount > 0)
@@ -317,7 +351,7 @@ namespace MediaBrowser.Api
{
Logger.Info("Deleting partial stream file(s) {0}", job.Path);
- await Task.Delay(1000).ConfigureAwait(false);
+ await Task.Delay(1500).ConfigureAwait(false);
try
{
diff --git a/MediaBrowser.Api/AuthorizationRequestFilterAttribute.cs b/MediaBrowser.Api/AuthorizationRequestFilterAttribute.cs
index a8b34b8bd..8f9babd06 100644
--- a/MediaBrowser.Api/AuthorizationRequestFilterAttribute.cs
+++ b/MediaBrowser.Api/AuthorizationRequestFilterAttribute.cs
@@ -63,7 +63,9 @@ namespace MediaBrowser.Api
if (!string.IsNullOrEmpty(client) && !string.IsNullOrEmpty(deviceId) && !string.IsNullOrEmpty(device) && !string.IsNullOrEmpty(version))
{
- SessionManager.LogSessionActivity(client, version, deviceId, device, user);
+ var remoteEndPoint = request.RemoteIp;
+
+ SessionManager.LogSessionActivity(client, version, deviceId, device, remoteEndPoint, user);
}
}
}
diff --git a/MediaBrowser.Api/BaseApiService.cs b/MediaBrowser.Api/BaseApiService.cs
index ddce1ddcd..62fcbd280 100644
--- a/MediaBrowser.Api/BaseApiService.cs
+++ b/MediaBrowser.Api/BaseApiService.cs
@@ -1,4 +1,5 @@
-using MediaBrowser.Controller.Entities;
+using System.IO;
+using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.Audio;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Net;
@@ -51,6 +52,11 @@ namespace MediaBrowser.Api
return ResultFactory.GetOptimizedResult(Request, result);
}
+ protected object ToStreamResult(Stream stream, string contentType)
+ {
+ return ResultFactory.GetResult(stream, contentType);
+ }
+
/// <summary>
/// To the optimized result using cache.
/// </summary>
diff --git a/MediaBrowser.Api/Images/ImageService.cs b/MediaBrowser.Api/Images/ImageService.cs
index 2de78d75b..663e1be28 100644
--- a/MediaBrowser.Api/Images/ImageService.cs
+++ b/MediaBrowser.Api/Images/ImageService.cs
@@ -1,17 +1,17 @@
using MediaBrowser.Common.Configuration;
using MediaBrowser.Common.Extensions;
-using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Drawing;
using MediaBrowser.Controller.Dto;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library;
-using MediaBrowser.Controller.LiveTv;
using MediaBrowser.Controller.Persistence;
using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Drawing;
using MediaBrowser.Model.Dto;
using MediaBrowser.Model.Entities;
using ServiceStack;
+using ServiceStack.Text.Controller;
+using ServiceStack.Web;
using System;
using System.Collections.Generic;
using System.Drawing;
@@ -19,8 +19,6 @@ using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
-using ServiceStack.Text.Controller;
-using ServiceStack.Web;
namespace MediaBrowser.Api.Images
{
@@ -39,18 +37,6 @@ namespace MediaBrowser.Api.Images
public string Id { get; set; }
}
- [Route("/LiveTv/Channels/{Id}/Images", "GET")]
- [Api(Description = "Gets information about an item's images")]
- public class GetChannelImageInfos : IReturn<List<ImageInfo>>
- {
- /// <summary>
- /// Gets or sets the id.
- /// </summary>
- /// <value>The id.</value>
- [ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")]
- public string Id { get; set; }
- }
-
[Route("/Artists/{Name}/Images", "GET")]
[Route("/Genres/{Name}/Images", "GET")]
[Route("/GameGenres/{Name}/Images", "GET")]
@@ -80,20 +66,7 @@ namespace MediaBrowser.Api.Images
[ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")]
public string Id { get; set; }
}
-
- [Route("/LiveTv/Channels/{Id}/Images/{Type}", "GET")]
- [Route("/LiveTv/Channels/{Id}/Images/{Type}/{Index}", "GET")]
- [Api(Description = "Gets an item image")]
- public class GetChannelImage : ImageRequest
- {
- /// <summary>
- /// Gets or sets the id.
- /// </summary>
- /// <value>The id.</value>
- [ApiMember(Name = "Id", Description = "Channel Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")]
- public string Id { get; set; }
- }
-
+
/// <summary>
/// Class UpdateItemImageIndex
/// </summary>
@@ -270,19 +243,6 @@ namespace MediaBrowser.Api.Images
public Guid Id { get; set; }
}
- [Route("/LiveTv/Channels/{Id}/Images/{Type}", "DELETE")]
- [Route("/LiveTv/Channels/{Id}/Images/{Type}/{Index}", "DELETE")]
- [Api(Description = "Deletes an item image")]
- public class DeleteChannelImage : DeleteImageRequest, IReturnVoid
- {
- /// <summary>
- /// Gets or sets the id.
- /// </summary>
- /// <value>The id.</value>
- [ApiMember(Name = "Id", Description = "Channel Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "DELETE")]
- public string Id { get; set; }
- }
-
/// <summary>
/// Class PostUserImage
/// </summary>
@@ -358,38 +318,13 @@ namespace MediaBrowser.Api.Images
public Stream RequestStream { get; set; }
}
- [Route("/LiveTv/Channels/{Id}/Images/{Type}", "POST")]
- [Route("/LiveTv/Channels/{Id}/Images/{Type}/{Index}", "POST")]
- [Api(Description = "Posts an item image")]
- public class PostChannelImage : DeleteImageRequest, IRequiresRequestStream, IReturnVoid
- {
- /// <summary>
- /// Gets or sets the id.
- /// </summary>
- /// <value>The id.</value>
- [ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")]
- public string Id { get; set; }
-
- /// <summary>
- /// The raw Http Request Input Stream
- /// </summary>
- /// <value>The request stream.</value>
- public Stream RequestStream { get; set; }
- }
-
/// <summary>
/// Class ImageService
/// </summary>
public class ImageService : BaseApiService
{
- /// <summary>
- /// The _user manager
- /// </summary>
private readonly IUserManager _userManager;
- /// <summary>
- /// The _library manager
- /// </summary>
private readonly ILibraryManager _libraryManager;
private readonly IApplicationPaths _appPaths;
@@ -400,12 +335,11 @@ namespace MediaBrowser.Api.Images
private readonly IDtoService _dtoService;
private readonly IImageProcessor _imageProcessor;
- private readonly ILiveTvManager _liveTv;
/// <summary>
/// Initializes a new instance of the <see cref="ImageService" /> class.
/// </summary>
- public ImageService(IUserManager userManager, ILibraryManager libraryManager, IApplicationPaths appPaths, IProviderManager providerManager, IItemRepository itemRepo, IDtoService dtoService, IImageProcessor imageProcessor, ILiveTvManager liveTv)
+ public ImageService(IUserManager userManager, ILibraryManager libraryManager, IApplicationPaths appPaths, IProviderManager providerManager, IItemRepository itemRepo, IDtoService dtoService, IImageProcessor imageProcessor)
{
_userManager = userManager;
_libraryManager = libraryManager;
@@ -414,7 +348,6 @@ namespace MediaBrowser.Api.Images
_itemRepo = itemRepo;
_dtoService = dtoService;
_imageProcessor = imageProcessor;
- _liveTv = liveTv;
}
/// <summary>
@@ -431,15 +364,6 @@ namespace MediaBrowser.Api.Images
return ToOptimizedResult(result);
}
- public object Get(GetChannelImageInfos request)
- {
- var item = _liveTv.GetChannel(request.Id);
-
- var result = GetItemImageInfos(item);
-
- return ToOptimizedResult(result);
- }
-
public object Get(GetItemByNameImageInfos request)
{
var result = GetItemByNameImageInfos(request);
@@ -540,7 +464,7 @@ namespace MediaBrowser.Api.Images
return list;
}
- private ImageInfo GetImageInfo(string path, BaseItem item, int? imageIndex, ImageType type)
+ private ImageInfo GetImageInfo(string path, IHasImages item, int? imageIndex, ImageType type)
{
try
{
@@ -567,13 +491,6 @@ namespace MediaBrowser.Api.Images
}
}
- public object Get(GetChannelImage request)
- {
- var item = _liveTv.GetChannel(request.Id);
-
- return GetImage(request, item);
- }
-
/// <summary>
/// Gets the specified request.
/// </summary>
@@ -659,20 +576,6 @@ namespace MediaBrowser.Api.Images
Task.WaitAll(task);
}
- public void Post(PostChannelImage request)
- {
- var pathInfo = PathInfo.Parse(Request.PathInfo);
- var id = pathInfo.GetArgumentValue<string>(2);
-
- request.Type = (ImageType)Enum.Parse(typeof(ImageType), pathInfo.GetArgumentValue<string>(4), true);
-
- var item = _liveTv.GetChannel(id);
-
- var task = PostImage(item, request.RequestStream, request.Type, Request.ContentType);
-
- Task.WaitAll(task);
- }
-
/// <summary>
/// Deletes the specified request.
/// </summary>
@@ -699,15 +602,6 @@ namespace MediaBrowser.Api.Images
Task.WaitAll(task);
}
- public void Delete(DeleteChannelImage request)
- {
- var item = _liveTv.GetChannel(request.Id);
-
- var task = item.DeleteImage(request.Type, request.Index);
-
- Task.WaitAll(task);
- }
-
/// <summary>
/// Deletes the specified request.
/// </summary>
@@ -762,69 +656,9 @@ namespace MediaBrowser.Api.Images
/// <param name="newIndex">The new index.</param>
/// <returns>Task.</returns>
/// <exception cref="System.ArgumentException">The change index operation is only applicable to backdrops and screenshots</exception>
- private Task UpdateItemIndex(BaseItem item, ImageType type, int currentIndex, int newIndex)
+ private Task UpdateItemIndex(IHasImages item, ImageType type, int currentIndex, int newIndex)
{
- string file1;
- string file2;
-
- if (type == ImageType.Screenshot)
- {
- var hasScreenshots = (IHasScreenshots)item;
- file1 = hasScreenshots.ScreenshotImagePaths[currentIndex];
- file2 = hasScreenshots.ScreenshotImagePaths[newIndex];
- }
- else if (type == ImageType.Backdrop)
- {
- file1 = item.BackdropImagePaths[currentIndex];
- file2 = item.BackdropImagePaths[newIndex];
- }
- else
- {
- throw new ArgumentException("The change index operation is only applicable to backdrops and screenshots");
- }
-
- SwapFiles(file1, file2);
-
- // Directory watchers should repeat this, but do a quick refresh first
- return item.RefreshMetadata(CancellationToken.None, forceSave: true, allowSlowProviders: false);
- }
-
- /// <summary>
- /// Swaps the files.
- /// </summary>
- /// <param name="file1">The file1.</param>
- /// <param name="file2">The file2.</param>
- private void SwapFiles(string file1, string file2)
- {
- var temp1 = Path.Combine(_appPaths.TempDirectory, Guid.NewGuid() + ".tmp");
- var temp2 = Path.Combine(_appPaths.TempDirectory, Guid.NewGuid() + ".tmp");
-
- // Copying over will fail against hidden files
- RemoveHiddenAttribute(file1);
- RemoveHiddenAttribute(file2);
-
- File.Copy(file1, temp1);
- File.Copy(file2, temp2);
-
- File.Copy(temp1, file2, true);
- File.Copy(temp2, file1, true);
-
- File.Delete(temp1);
- File.Delete(temp2);
- }
-
- private void RemoveHiddenAttribute(string path)
- {
- var currentFile = new FileInfo(path);
-
- // This will fail if the file is hidden
- if (currentFile.Exists)
- {
- if ((currentFile.Attributes & FileAttributes.Hidden) == FileAttributes.Hidden)
- {
- currentFile.Attributes &= ~FileAttributes.Hidden;
- }
- }
+ return item.SwapImages(type, currentIndex, newIndex);
}
/// <summary>
@@ -835,7 +669,7 @@ namespace MediaBrowser.Api.Images
/// <returns>System.Object.</returns>
/// <exception cref="ResourceNotFoundException">
/// </exception>
- private object GetImage(ImageRequest request, BaseItem item)
+ public object GetImage(ImageRequest request, IHasImages item)
{
var imagePath = GetImagePath(request, item);
@@ -924,7 +758,7 @@ namespace MediaBrowser.Api.Images
/// <param name="request">The request.</param>
/// <param name="item">The item.</param>
/// <returns>System.String.</returns>
- private string GetImagePath(ImageRequest request, BaseItem item)
+ private string GetImagePath(ImageRequest request, IHasImages item)
{
var index = request.Index ?? 0;
@@ -939,7 +773,7 @@ namespace MediaBrowser.Api.Images
/// <param name="imageType">Type of the image.</param>
/// <param name="mimeType">Type of the MIME.</param>
/// <returns>Task.</returns>
- private async Task PostImage(BaseItem entity, Stream inputStream, ImageType imageType, string mimeType)
+ public async Task PostImage(BaseItem entity, Stream inputStream, ImageType imageType, string mimeType)
{
using (var reader = new StreamReader(inputStream))
{
diff --git a/MediaBrowser.Api/Images/ImageWriter.cs b/MediaBrowser.Api/Images/ImageWriter.cs
index 5d1ee140d..2ace05125 100644
--- a/MediaBrowser.Api/Images/ImageWriter.cs
+++ b/MediaBrowser.Api/Images/ImageWriter.cs
@@ -2,12 +2,11 @@
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Entities;
-using ServiceStack;
+using ServiceStack.Web;
using System;
using System.Collections.Generic;
using System.IO;
using System.Threading.Tasks;
-using ServiceStack.Web;
namespace MediaBrowser.Api.Images
{
@@ -27,7 +26,7 @@ namespace MediaBrowser.Api.Images
/// Gets or sets the item.
/// </summary>
/// <value>The item.</value>
- public BaseItem Item { get; set; }
+ public IHasImages Item { get; set; }
/// <summary>
/// The original image date modified
/// </summary>
diff --git a/MediaBrowser.Api/ItemUpdateService.cs b/MediaBrowser.Api/ItemUpdateService.cs
index 9fc4bb3d0..6c5b279d0 100644
--- a/MediaBrowser.Api/ItemUpdateService.cs
+++ b/MediaBrowser.Api/ItemUpdateService.cs
@@ -146,7 +146,7 @@ namespace MediaBrowser.Api
private async Task UpdateItem(UpdateChannel request)
{
- var item = _liveTv.GetChannel(request.Id);
+ var item = _liveTv.GetInternalChannel(request.Id);
UpdateItem(request, item);
@@ -271,6 +271,17 @@ namespace MediaBrowser.Api
item.Overview = request.Overview;
item.Genres = request.Genres;
+ var episode = item as Episode;
+ if (episode != null)
+ {
+ episode.DvdSeasonNumber = request.DvdSeasonNumber;
+ episode.DvdEpisodeNumber = request.DvdEpisodeNumber;
+ episode.AirsAfterSeasonNumber = request.AirsAfterSeasonNumber;
+ episode.AirsBeforeEpisodeNumber = request.AirsBeforeEpisodeNumber;
+ episode.AirsBeforeSeasonNumber = request.AirsBeforeSeasonNumber;
+ episode.AbsoluteEpisodeNumber = request.AbsoluteEpisodeNumber;
+ }
+
var hasTags = item as IHasTags;
if (hasTags != null)
{
@@ -300,10 +311,12 @@ namespace MediaBrowser.Api
SetProductionLocations(item, request);
- var hasLanguage = item as IHasLanguage;
- if (hasLanguage != null)
+ var hasLang = item as IHasPreferredMetadataLanguage;
+
+ if (hasLang != null)
{
- hasLanguage.Language = request.Language;
+ hasLang.PreferredMetadataCountryCode = request.PreferredMetadataCountryCode;
+ hasLang.PreferredMetadataLanguage = request.PreferredMetadataLanguage;
}
var hasAspectRatio = item as IHasAspectRatio;
@@ -374,6 +387,11 @@ namespace MediaBrowser.Api
series.Status = request.Status;
series.AirDays = request.AirDays;
series.AirTime = request.AirTime;
+
+ if (request.DisplaySpecialsWithSeasons.HasValue)
+ {
+ series.DisplaySpecialsWithSeasons = request.DisplaySpecialsWithSeasons.Value;
+ }
}
}
diff --git a/MediaBrowser.Api/Library/LibraryService.cs b/MediaBrowser.Api/Library/LibraryService.cs
index bef0ba1e1..f3d5824da 100644
--- a/MediaBrowser.Api/Library/LibraryService.cs
+++ b/MediaBrowser.Api/Library/LibraryService.cs
@@ -1,6 +1,7 @@
using MediaBrowser.Common;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library;
+using MediaBrowser.Model.Entities;
using ServiceStack;
using System;
using System.Collections.Generic;
@@ -67,7 +68,27 @@ namespace MediaBrowser.Api.Library
/// <returns>System.Object.</returns>
public object Get(GetPhyscialPaths request)
{
- var result = _libraryManager.RootFolder.Children.SelectMany(c => c.ResolveArgs.PhysicalLocations).ToList();
+ var result = _libraryManager.RootFolder.Children
+ .SelectMany(c =>
+ {
+ var locationType = c.LocationType;
+
+ if (locationType != LocationType.Remote && locationType != LocationType.Virtual)
+ {
+ try
+ {
+ return c.ResolveArgs.PhysicalLocations;
+ }
+ catch (Exception ex)
+ {
+ Logger.ErrorException("Error getting ResolveArgs for {0}", ex, c.Path);
+ }
+
+ }
+
+ return new List<string>();
+ })
+ .ToList();
return ToOptimizedResult(result);
}
@@ -85,7 +106,7 @@ namespace MediaBrowser.Api.Library
{
allTypes = allTypes.Where(t =>
{
- if (t == typeof(UserRootFolder) || t == typeof(AggregateFolder) || t == typeof(Folder) || t == typeof(IndexFolder) || t == typeof(CollectionFolder) || t == typeof(Year))
+ if (t == typeof(UserRootFolder) || t == typeof(AggregateFolder) || t == typeof(Folder) || t == typeof(CollectionFolder) || t == typeof(Year))
{
return false;
}
diff --git a/MediaBrowser.Api/Library/LibraryStructureService.cs b/MediaBrowser.Api/Library/LibraryStructureService.cs
index c964b5517..775907379 100644
--- a/MediaBrowser.Api/Library/LibraryStructureService.cs
+++ b/MediaBrowser.Api/Library/LibraryStructureService.cs
@@ -28,8 +28,8 @@ namespace MediaBrowser.Api.Library
public string UserId { get; set; }
}
- [Route("/Library/VirtualFolders/{Name}", "POST")]
- [Route("/Users/{UserId}/VirtualFolders/{Name}", "POST")]
+ [Route("/Library/VirtualFolders", "POST")]
+ [Route("/Users/{UserId}/VirtualFolders", "POST")]
public class AddVirtualFolder : IReturnVoid
{
/// <summary>
@@ -57,8 +57,8 @@ namespace MediaBrowser.Api.Library
public bool RefreshLibrary { get; set; }
}
- [Route("/Library/VirtualFolders/{Name}", "DELETE")]
- [Route("/Users/{UserId}/VirtualFolders/{Name}", "DELETE")]
+ [Route("/Library/VirtualFolders", "DELETE")]
+ [Route("/Users/{UserId}/VirtualFolders", "DELETE")]
public class RemoveVirtualFolder : IReturnVoid
{
/// <summary>
@@ -80,8 +80,8 @@ namespace MediaBrowser.Api.Library
public bool RefreshLibrary { get; set; }
}
- [Route("/Library/VirtualFolders/{Name}/Name", "POST")]
- [Route("/Users/{UserId}/VirtualFolders/{Name}/Name", "POST")]
+ [Route("/Library/VirtualFolders/Name", "POST")]
+ [Route("/Users/{UserId}/VirtualFolders/Name", "POST")]
public class RenameVirtualFolder : IReturnVoid
{
/// <summary>
@@ -109,8 +109,8 @@ namespace MediaBrowser.Api.Library
public bool RefreshLibrary { get; set; }
}
- [Route("/Library/VirtualFolders/{Name}/Paths", "POST")]
- [Route("/Users/{UserId}/VirtualFolders/{Name}/Paths", "POST")]
+ [Route("/Library/VirtualFolders/Paths", "POST")]
+ [Route("/Users/{UserId}/VirtualFolders/Paths", "POST")]
public class AddMediaPath : IReturnVoid
{
/// <summary>
@@ -138,8 +138,8 @@ namespace MediaBrowser.Api.Library
public bool RefreshLibrary { get; set; }
}
- [Route("/Library/VirtualFolders/{Name}/Paths", "DELETE")]
- [Route("/Users/{UserId}/VirtualFolders/{Name}/Paths", "DELETE")]
+ [Route("/Library/VirtualFolders/Paths", "DELETE")]
+ [Route("/Users/{UserId}/VirtualFolders/Paths", "DELETE")]
public class RemoveMediaPath : IReturnVoid
{
/// <summary>
@@ -243,6 +243,11 @@ namespace MediaBrowser.Api.Library
/// <param name="request">The request.</param>
public void Post(AddVirtualFolder request)
{
+ if (string.IsNullOrWhiteSpace(request.Name))
+ {
+ throw new ArgumentNullException("request");
+ }
+
var name = _fileSystem.GetValidFilename(request.Name);
string rootFolderPath;
@@ -307,6 +312,16 @@ namespace MediaBrowser.Api.Library
/// <param name="request">The request.</param>
public void Post(RenameVirtualFolder request)
{
+ if (string.IsNullOrWhiteSpace(request.Name))
+ {
+ throw new ArgumentNullException("request");
+ }
+
+ if (string.IsNullOrWhiteSpace(request.NewName))
+ {
+ throw new ArgumentNullException("request");
+ }
+
string rootFolderPath;
if (string.IsNullOrEmpty(request.UserId))
@@ -380,6 +395,11 @@ namespace MediaBrowser.Api.Library
/// <param name="request">The request.</param>
public void Delete(RemoveVirtualFolder request)
{
+ if (string.IsNullOrWhiteSpace(request.Name))
+ {
+ throw new ArgumentNullException("request");
+ }
+
string rootFolderPath;
if (string.IsNullOrEmpty(request.UserId))
@@ -435,6 +455,11 @@ namespace MediaBrowser.Api.Library
/// <param name="request">The request.</param>
public void Post(AddMediaPath request)
{
+ if (string.IsNullOrWhiteSpace(request.Name))
+ {
+ throw new ArgumentNullException("request");
+ }
+
_directoryWatchers.Stop();
try
@@ -476,6 +501,11 @@ namespace MediaBrowser.Api.Library
/// <param name="request">The request.</param>
public void Delete(RemoveMediaPath request)
{
+ if (string.IsNullOrWhiteSpace(request.Name))
+ {
+ throw new ArgumentNullException("request");
+ }
+
_directoryWatchers.Stop();
try
diff --git a/MediaBrowser.Api/LibraryService.cs b/MediaBrowser.Api/LibraryService.cs
index 7dc8301fe..d9442b63d 100644
--- a/MediaBrowser.Api/LibraryService.cs
+++ b/MediaBrowser.Api/LibraryService.cs
@@ -1,4 +1,5 @@
-using MediaBrowser.Controller.Dto;
+using MediaBrowser.Common.Extensions;
+using MediaBrowser.Controller.Dto;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.Audio;
using MediaBrowser.Controller.Entities.Movies;
@@ -32,6 +33,21 @@ namespace MediaBrowser.Api
public string Id { get; set; }
}
+ [Route("/Videos/{Id}/Subtitle/{Index}", "GET")]
+ [Api(Description = "Gets an external subtitle file")]
+ public class GetSubtitle
+ {
+ /// <summary>
+ /// Gets or sets the id.
+ /// </summary>
+ /// <value>The id.</value>
+ [ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")]
+ public string Id { get; set; }
+
+ [ApiMember(Name = "Index", Description = "The subtitle stream index", IsRequired = true, DataType = "int", ParameterType = "path", Verb = "GET")]
+ public int Index { get; set; }
+ }
+
/// <summary>
/// Class GetCriticReviews
/// </summary>
@@ -240,6 +256,25 @@ namespace MediaBrowser.Api
return ToStaticFileResult(item.Path);
}
+ public object Get(GetSubtitle request)
+ {
+ var subtitleStream = _itemRepo.GetMediaStreams(new MediaStreamQuery
+ {
+
+ Index = request.Index,
+ ItemId = new Guid(request.Id),
+ Type = MediaStreamType.Subtitle
+
+ }).FirstOrDefault();
+
+ if (subtitleStream == null)
+ {
+ throw new ResourceNotFoundException();
+ }
+
+ return ToStaticFileResult(subtitleStream.Path);
+ }
+
/// <summary>
/// Gets the specified request.
/// </summary>
diff --git a/MediaBrowser.Api/LiveTv/LiveTvImageService.cs b/MediaBrowser.Api/LiveTv/LiveTvImageService.cs
new file mode 100644
index 000000000..65c4e5e23
--- /dev/null
+++ b/MediaBrowser.Api/LiveTv/LiveTvImageService.cs
@@ -0,0 +1,195 @@
+using MediaBrowser.Api.Images;
+using MediaBrowser.Common.Configuration;
+using MediaBrowser.Controller.Drawing;
+using MediaBrowser.Controller.Dto;
+using MediaBrowser.Controller.Library;
+using MediaBrowser.Controller.LiveTv;
+using MediaBrowser.Controller.Persistence;
+using MediaBrowser.Controller.Providers;
+using MediaBrowser.Model.Dto;
+using MediaBrowser.Model.Entities;
+using ServiceStack;
+using ServiceStack.Text.Controller;
+using ServiceStack.Web;
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace MediaBrowser.Api.LiveTv
+{
+ [Route("/LiveTv/Channels/{Id}/Images/{Type}", "POST")]
+ [Route("/LiveTv/Channels/{Id}/Images/{Type}/{Index}", "POST")]
+ [Api(Description = "Posts an item image")]
+ public class PostChannelImage : DeleteImageRequest, IRequiresRequestStream, IReturnVoid
+ {
+ /// <summary>
+ /// Gets or sets the id.
+ /// </summary>
+ /// <value>The id.</value>
+ [ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")]
+ public string Id { get; set; }
+
+ /// <summary>
+ /// The raw Http Request Input Stream
+ /// </summary>
+ /// <value>The request stream.</value>
+ public Stream RequestStream { get; set; }
+ }
+
+ [Route("/LiveTv/Channels/{Id}/Images/{Type}", "DELETE")]
+ [Route("/LiveTv/Channels/{Id}/Images/{Type}/{Index}", "DELETE")]
+ [Api(Description = "Deletes an item image")]
+ public class DeleteChannelImage : DeleteImageRequest, IReturnVoid
+ {
+ /// <summary>
+ /// Gets or sets the id.
+ /// </summary>
+ /// <value>The id.</value>
+ [ApiMember(Name = "Id", Description = "Channel Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "DELETE")]
+ public string Id { get; set; }
+ }
+ [Route("/LiveTv/Channels/{Id}/Images/{Type}", "GET")]
+ [Route("/LiveTv/Channels/{Id}/Images/{Type}/{Index}", "GET")]
+ [Api(Description = "Gets an item image")]
+ public class GetChannelImage : ImageRequest
+ {
+ /// <summary>
+ /// Gets or sets the id.
+ /// </summary>
+ /// <value>The id.</value>
+ [ApiMember(Name = "Id", Description = "Channel Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")]
+ public string Id { get; set; }
+ }
+
+ [Route("/LiveTv/Recordings/{Id}/Images/{Type}", "GET")]
+ [Route("/LiveTv/Recordings/{Id}/Images/{Type}/{Index}", "GET")]
+ [Api(Description = "Gets an item image")]
+ public class GetRecordingImage : ImageRequest
+ {
+ /// <summary>
+ /// Gets or sets the id.
+ /// </summary>
+ /// <value>The id.</value>
+ [ApiMember(Name = "Id", Description = "Recording Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")]
+ public string Id { get; set; }
+ }
+
+ [Route("/LiveTv/Programs/{Id}/Images/{Type}", "GET")]
+ [Route("/LiveTv/Programs/{Id}/Images/{Type}/{Index}", "GET")]
+ [Api(Description = "Gets an item image")]
+ public class GetProgramImage : ImageRequest
+ {
+ /// <summary>
+ /// Gets or sets the id.
+ /// </summary>
+ /// <value>The id.</value>
+ [ApiMember(Name = "Id", Description = "Program Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")]
+ public string Id { get; set; }
+ }
+
+ [Route("/LiveTv/Channels/{Id}/Images", "GET")]
+ [Api(Description = "Gets information about an item's images")]
+ public class GetChannelImageInfos : IReturn<List<ImageInfo>>
+ {
+ /// <summary>
+ /// Gets or sets the id.
+ /// </summary>
+ /// <value>The id.</value>
+ [ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")]
+ public string Id { get; set; }
+ }
+
+ public class LiveTvImageService : BaseApiService
+ {
+ private readonly ILiveTvManager _liveTv;
+
+ private readonly IUserManager _userManager;
+
+ private readonly ILibraryManager _libraryManager;
+
+ private readonly IApplicationPaths _appPaths;
+
+ private readonly IProviderManager _providerManager;
+
+ private readonly IItemRepository _itemRepo;
+ private readonly IDtoService _dtoService;
+ private readonly IImageProcessor _imageProcessor;
+
+ public LiveTvImageService(ILiveTvManager liveTv, IUserManager userManager, ILibraryManager libraryManager, IApplicationPaths appPaths, IProviderManager providerManager, IItemRepository itemRepo, IDtoService dtoService, IImageProcessor imageProcessor)
+ {
+ _liveTv = liveTv;
+ _userManager = userManager;
+ _libraryManager = libraryManager;
+ _appPaths = appPaths;
+ _providerManager = providerManager;
+ _itemRepo = itemRepo;
+ _dtoService = dtoService;
+ _imageProcessor = imageProcessor;
+ }
+
+ public object Get(GetChannelImageInfos request)
+ {
+ var item = _liveTv.GetInternalChannel(request.Id);
+
+ var result = GetImageService().GetItemImageInfos(item);
+
+ return ToOptimizedResult(result);
+ }
+
+ public object Get(GetChannelImage request)
+ {
+ var item = _liveTv.GetInternalChannel(request.Id);
+
+ return GetImageService().GetImage(request, item);
+ }
+
+ public object Get(GetRecordingImage request)
+ {
+ var item = _liveTv.GetInternalRecording(request.Id, CancellationToken.None).Result;
+
+ return GetImageService().GetImage(request, item);
+ }
+
+ public object Get(GetProgramImage request)
+ {
+ var item = _liveTv.GetInternalProgram(request.Id);
+
+ return GetImageService().GetImage(request, item);
+ }
+
+ public void Post(PostChannelImage request)
+ {
+ var pathInfo = PathInfo.Parse(Request.PathInfo);
+ var id = pathInfo.GetArgumentValue<string>(2);
+
+ request.Type = (ImageType)Enum.Parse(typeof(ImageType), pathInfo.GetArgumentValue<string>(4), true);
+
+ var item = _liveTv.GetInternalChannel(id);
+
+ var task = GetImageService().PostImage(item, request.RequestStream, request.Type, Request.ContentType);
+
+ Task.WaitAll(task);
+ }
+
+ public void Delete(DeleteChannelImage request)
+ {
+ var item = _liveTv.GetInternalChannel(request.Id);
+
+ var task = item.DeleteImage(request.Type, request.Index);
+
+ Task.WaitAll(task);
+ }
+
+ private ImageService GetImageService()
+ {
+ return new ImageService(_userManager, _libraryManager, _appPaths, _providerManager, _itemRepo, _dtoService,
+ _imageProcessor)
+ {
+ ResultFactory = ResultFactory,
+ Request = Request
+ };
+ }
+ }
+}
diff --git a/MediaBrowser.Api/LiveTv/LiveTvService.cs b/MediaBrowser.Api/LiveTv/LiveTvService.cs
index 9e83a56de..61883ddaa 100644
--- a/MediaBrowser.Api/LiveTv/LiveTvService.cs
+++ b/MediaBrowser.Api/LiveTv/LiveTvService.cs
@@ -1,4 +1,5 @@
-using MediaBrowser.Controller.LiveTv;
+using MediaBrowser.Controller.Library;
+using MediaBrowser.Controller.LiveTv;
using MediaBrowser.Model.LiveTv;
using MediaBrowser.Model.Querying;
using ServiceStack;
@@ -23,7 +24,7 @@ namespace MediaBrowser.Api.LiveTv
[ApiMember(Name = "Type", Description = "Optional filter by channel type.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
public ChannelType? Type { get; set; }
- [ApiMember(Name = "UserId", Description = "Optional filter by user id.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
+ [ApiMember(Name = "UserId", Description = "Optional filter by user and attach user data.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
public string UserId { get; set; }
}
@@ -38,7 +39,7 @@ namespace MediaBrowser.Api.LiveTv
[ApiMember(Name = "Id", Description = "Channel Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")]
public string Id { get; set; }
- [ApiMember(Name = "UserId", Description = "Optional user id.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
+ [ApiMember(Name = "UserId", Description = "Optional attach user data.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
public string UserId { get; set; }
}
@@ -48,6 +49,26 @@ namespace MediaBrowser.Api.LiveTv
{
[ApiMember(Name = "ChannelId", Description = "Optional filter by channel id.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
public string ChannelId { get; set; }
+
+ [ApiMember(Name = "UserId", Description = "Optional filter by user and attach user data.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
+ public string UserId { get; set; }
+
+ [ApiMember(Name = "GroupId", Description = "Optional filter by recording group.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
+ public string GroupId { get; set; }
+
+ [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; }
+
+ [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; }
+ }
+
+ [Route("/LiveTv/Recordings/Groups", "GET")]
+ [Api(Description = "Gets live tv recording groups")]
+ public class GetRecordingGroups : IReturn<QueryResult<RecordingInfoDto>>
+ {
+ [ApiMember(Name = "UserId", Description = "Optional filter by user and attach user data.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
+ public string UserId { get; set; }
}
[Route("/LiveTv/Recordings/{Id}", "GET")]
@@ -56,6 +77,9 @@ namespace MediaBrowser.Api.LiveTv
{
[ApiMember(Name = "Id", Description = "Recording Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")]
public string Id { get; set; }
+
+ [ApiMember(Name = "UserId", Description = "Optional attach user data.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
+ public string UserId { get; set; }
}
[Route("/LiveTv/Timers/{Id}", "GET")]
@@ -66,6 +90,14 @@ namespace MediaBrowser.Api.LiveTv
public string Id { get; set; }
}
+ [Route("/LiveTv/Timers/Defaults", "GET")]
+ [Api(Description = "Gets default values for a new timer")]
+ public class GetDefaultTimer : IReturn<SeriesTimerInfoDto>
+ {
+ [ApiMember(Name = "ProgramId", Description = "Optional, to attach default values based on a program.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
+ public string ProgramId { get; set; }
+ }
+
[Route("/LiveTv/Timers", "GET")]
[Api(Description = "Gets live tv timers")]
public class GetTimers : IReturn<QueryResult<TimerInfoDto>>
@@ -85,6 +117,18 @@ namespace MediaBrowser.Api.LiveTv
public string UserId { get; set; }
}
+ [Route("/LiveTv/Programs/{Id}", "GET")]
+ [Api(Description = "Gets a live tv program")]
+ public class GetProgram : IReturn<ProgramInfoDto>
+ {
+ [ApiMember(Name = "Id", Description = "Program Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")]
+ public string Id { get; set; }
+
+ [ApiMember(Name = "UserId", Description = "Optional attach user data.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
+ public string UserId { get; set; }
+ }
+
+
[Route("/LiveTv/Recordings/{Id}", "DELETE")]
[Api(Description = "Deletes a live tv recording")]
public class DeleteRecording : IReturnVoid
@@ -100,14 +144,76 @@ namespace MediaBrowser.Api.LiveTv
[ApiMember(Name = "Id", Description = "Timer Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")]
public string Id { get; set; }
}
-
+
+ [Route("/LiveTv/Timers/{Id}", "POST")]
+ [Api(Description = "Updates a live tv timer")]
+ public class UpdateTimer : TimerInfoDto, IReturnVoid
+ {
+ }
+
+ [Route("/LiveTv/Timers", "POST")]
+ [Api(Description = "Creates a live tv timer")]
+ public class CreateTimer : TimerInfoDto, IReturnVoid
+ {
+ }
+
+ [Route("/LiveTv/SeriesTimers/{Id}", "GET")]
+ [Api(Description = "Gets a live tv series timer")]
+ public class GetSeriesTimer : IReturn<TimerInfoDto>
+ {
+ [ApiMember(Name = "Id", Description = "Timer Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")]
+ public string Id { get; set; }
+ }
+
+ [Route("/LiveTv/SeriesTimers", "GET")]
+ [Api(Description = "Gets live tv series timers")]
+ public class GetSeriesTimers : IReturn<QueryResult<SeriesTimerInfoDto>>
+ {
+ }
+
+ [Route("/LiveTv/SeriesTimers/{Id}", "DELETE")]
+ [Api(Description = "Cancels a live tv series timer")]
+ public class CancelSeriesTimer : IReturnVoid
+ {
+ [ApiMember(Name = "Id", Description = "Timer Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")]
+ public string Id { get; set; }
+ }
+
+ [Route("/LiveTv/SeriesTimers/{Id}", "POST")]
+ [Api(Description = "Updates a live tv series timer")]
+ public class UpdateSeriesTimer : SeriesTimerInfoDto, IReturnVoid
+ {
+ }
+
+ [Route("/LiveTv/SeriesTimers", "POST")]
+ [Api(Description = "Creates a live tv series timer")]
+ public class CreateSeriesTimer : SeriesTimerInfoDto, IReturnVoid
+ {
+ }
+
+ [Route("/LiveTv/Recordings/{Id}/Stream", "GET")]
+ public class GetInternalRecordingStream
+ {
+ [ApiMember(Name = "Id", Description = "Recording Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")]
+ public string Id { get; set; }
+ }
+
+ [Route("/LiveTv/Channels/{Id}/Stream", "GET")]
+ public class GetInternalChannelStream
+ {
+ [ApiMember(Name = "Id", Description = "Channel Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")]
+ public string Id { get; set; }
+ }
+
public class LiveTvService : BaseApiService
{
private readonly ILiveTvManager _liveTvManager;
+ private readonly IUserManager _userManager;
- public LiveTvService(ILiveTvManager liveTvManager)
+ public LiveTvService(ILiveTvManager liveTvManager, IUserManager userManager)
{
_liveTvManager = liveTvManager;
+ _userManager = userManager;
}
public object Get(GetServices request)
@@ -134,14 +240,16 @@ namespace MediaBrowser.Api.LiveTv
ChannelType = request.Type,
UserId = request.UserId
- });
+ }, CancellationToken.None).Result;
return ToOptimizedResult(result);
}
public object Get(GetChannel request)
{
- var result = _liveTvManager.GetChannelInfoDto(request.Id, request.UserId);
+ var user = string.IsNullOrEmpty(request.UserId) ? null : _userManager.GetUserById(new Guid(request.UserId));
+
+ var result = _liveTvManager.GetChannel(request.Id, CancellationToken.None, user).Result;
return ToOptimizedResult(result);
}
@@ -162,7 +270,11 @@ namespace MediaBrowser.Api.LiveTv
{
var result = _liveTvManager.GetRecordings(new RecordingQuery
{
- ChannelId = request.ChannelId
+ ChannelId = request.ChannelId,
+ UserId = request.UserId,
+ GroupId = request.GroupId,
+ StartIndex = request.StartIndex,
+ Limit = request.Limit
}, CancellationToken.None).Result;
@@ -171,7 +283,9 @@ namespace MediaBrowser.Api.LiveTv
public object Get(GetRecording request)
{
- var result = _liveTvManager.GetRecording(request.Id, CancellationToken.None).Result;
+ var user = string.IsNullOrEmpty(request.UserId) ? null : _userManager.GetUserById(new Guid(request.UserId));
+
+ var result = _liveTvManager.GetRecording(request.Id, CancellationToken.None, user).Result;
return ToOptimizedResult(result);
}
@@ -207,5 +321,107 @@ namespace MediaBrowser.Api.LiveTv
Task.WaitAll(task);
}
+
+ public void Post(UpdateTimer request)
+ {
+ var task = _liveTvManager.UpdateTimer(request, CancellationToken.None);
+
+ Task.WaitAll(task);
+ }
+
+ public object Get(GetSeriesTimers request)
+ {
+ var result = _liveTvManager.GetSeriesTimers(new SeriesTimerQuery
+ {
+
+ }, CancellationToken.None).Result;
+
+ return ToOptimizedResult(result);
+ }
+
+ public object Get(GetSeriesTimer request)
+ {
+ var result = _liveTvManager.GetSeriesTimer(request.Id, CancellationToken.None).Result;
+
+ return ToOptimizedResult(result);
+ }
+
+ public void Delete(CancelSeriesTimer request)
+ {
+ var task = _liveTvManager.CancelSeriesTimer(request.Id);
+
+ Task.WaitAll(task);
+ }
+
+ public void Post(UpdateSeriesTimer request)
+ {
+ var task = _liveTvManager.UpdateSeriesTimer(request, CancellationToken.None);
+
+ Task.WaitAll(task);
+ }
+
+ public object Get(GetDefaultTimer request)
+ {
+ if (string.IsNullOrEmpty(request.ProgramId))
+ {
+ var result = _liveTvManager.GetNewTimerDefaults(CancellationToken.None).Result;
+
+ return ToOptimizedResult(result);
+ }
+ else
+ {
+ var result = _liveTvManager.GetNewTimerDefaults(request.ProgramId, CancellationToken.None).Result;
+
+ return ToOptimizedResult(result);
+ }
+ }
+
+ public object Get(GetProgram request)
+ {
+ var user = string.IsNullOrEmpty(request.UserId) ? null : _userManager.GetUserById(new Guid(request.UserId));
+
+ var result = _liveTvManager.GetProgram(request.Id, CancellationToken.None, user).Result;
+
+ return ToOptimizedResult(result);
+ }
+
+ public void Post(CreateSeriesTimer request)
+ {
+ var task = _liveTvManager.CreateSeriesTimer(request, CancellationToken.None);
+
+ Task.WaitAll(task);
+ }
+
+ public void Post(CreateTimer request)
+ {
+ var task = _liveTvManager.CreateTimer(request, CancellationToken.None);
+
+ Task.WaitAll(task);
+ }
+
+ public object Get(GetInternalRecordingStream request)
+ {
+ var stream = _liveTvManager.GetRecordingStream(request.Id, CancellationToken.None).Result;
+
+ return ToStreamResult(stream.Stream, stream.MimeType);
+ }
+
+ public object Get(GetInternalChannelStream request)
+ {
+ var stream = _liveTvManager.GetChannelStream(request.Id, CancellationToken.None).Result;
+
+ return ToStreamResult(stream.Stream, stream.MimeType);
+ }
+
+ public object Get(GetRecordingGroups request)
+ {
+ var result = _liveTvManager.GetRecordingGroups(new RecordingGroupQuery
+ {
+ UserId = request.UserId
+
+ }, CancellationToken.None).Result;
+
+ return ToOptimizedResult(result);
+ }
}
} \ No newline at end of file
diff --git a/MediaBrowser.Api/MediaBrowser.Api.csproj b/MediaBrowser.Api/MediaBrowser.Api.csproj
index b1e0339fc..0732ee00c 100644
--- a/MediaBrowser.Api/MediaBrowser.Api.csproj
+++ b/MediaBrowser.Api/MediaBrowser.Api.csproj
@@ -9,12 +9,12 @@
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>MediaBrowser.Api</RootNamespace>
<AssemblyName>MediaBrowser.Api</AssemblyName>
- <TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<SolutionDir Condition="$(SolutionDir) == '' Or $(SolutionDir) == '*Undefined*'">..\</SolutionDir>
<RestorePackages>true</RestorePackages>
<ProductVersion>10.0.0</ProductVersion>
<SchemaVersion>2.0</SchemaVersion>
+ <TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
@@ -25,6 +25,7 @@
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<PlatformTarget>AnyCPU</PlatformTarget>
+ <TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
@@ -33,19 +34,21 @@
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
+ <TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
+ </PropertyGroup>
+ <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release Mono|AnyCPU' ">
+ <DebugType>pdbonly</DebugType>
+ <Optimize>true</Optimize>
+ <OutputPath>bin\Release\</OutputPath>
+ <DefineConstants>TRACE</DefineConstants>
+ <ErrorReport>prompt</ErrorReport>
+ <WarningLevel>4</WarningLevel>
+ <TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
</PropertyGroup>
<PropertyGroup>
<RunPostBuildEvent>Always</RunPostBuildEvent>
</PropertyGroup>
<ItemGroup>
- <Reference Include="ServiceStack.Interfaces, Version=1.0.0.0, Culture=neutral, processorArchitecture=MSIL">
- <SpecificVersion>False</SpecificVersion>
- <HintPath>..\ThirdParty\ServiceStack\ServiceStack.Interfaces.dll</HintPath>
- </Reference>
- <Reference Include="ServiceStack.Text, Version=3.9.70.0, Culture=neutral, processorArchitecture=MSIL">
- <SpecificVersion>False</SpecificVersion>
- <HintPath>..\ThirdParty\ServiceStack\ServiceStack.Text.dll</HintPath>
- </Reference>
<Reference Include="System" />
<Reference Include="System.Core" />
<Reference Include="Microsoft.CSharp" />
@@ -56,6 +59,12 @@
<HintPath>..\packages\morelinq.1.0.16006\lib\net35\MoreLinq.dll</HintPath>
</Reference>
<Reference Include="System.Xml" />
+ <Reference Include="ServiceStack.Interfaces">
+ <HintPath>..\ThirdParty\ServiceStack\ServiceStack.Interfaces.dll</HintPath>
+ </Reference>
+ <Reference Include="ServiceStack.Text">
+ <HintPath>..\ThirdParty\ServiceStack.Text\ServiceStack.Text.dll</HintPath>
+ </Reference>
</ItemGroup>
<ItemGroup>
<Compile Include="..\SharedVersion.cs">
@@ -81,6 +90,7 @@
<Compile Include="Library\LibraryHelpers.cs" />
<Compile Include="Library\LibraryService.cs" />
<Compile Include="Library\LibraryStructureService.cs" />
+ <Compile Include="LiveTv\LiveTvImageService.cs" />
<Compile Include="LiveTv\LiveTvService.cs" />
<Compile Include="LocalizationService.cs" />
<Compile Include="MoviesService.cs" />
@@ -153,7 +163,7 @@
<PostBuildEvent>
</PostBuildEvent>
</PropertyGroup>
- <Import Project="$(SolutionDir)\.nuget\nuget.targets" />
+ <Import Project="$(SolutionDir)\.nuget\nuget.targets" Condition=" '$(ConfigurationName)' != 'Release Mono' " />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.
<Target Name="BeforeBuild">
diff --git a/MediaBrowser.Api/Playback/BaseStreamingService.cs b/MediaBrowser.Api/Playback/BaseStreamingService.cs
index 90996296d..1e2ae58b2 100644
--- a/MediaBrowser.Api/Playback/BaseStreamingService.cs
+++ b/MediaBrowser.Api/Playback/BaseStreamingService.cs
@@ -1,13 +1,14 @@
using MediaBrowser.Common.Extensions;
using MediaBrowser.Common.IO;
using MediaBrowser.Common.MediaInfo;
-using MediaBrowser.Controller;
+using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Dto;
using MediaBrowser.Controller.Entities;
-using MediaBrowser.Controller.Entities.Audio;
using MediaBrowser.Controller.Library;
+using MediaBrowser.Controller.LiveTv;
using MediaBrowser.Controller.MediaInfo;
using MediaBrowser.Controller.Persistence;
+using MediaBrowser.Model.Configuration;
using MediaBrowser.Model.Drawing;
using MediaBrowser.Model.Dto;
using MediaBrowser.Model.Entities;
@@ -32,7 +33,7 @@ namespace MediaBrowser.Api.Playback
/// Gets or sets the application paths.
/// </summary>
/// <value>The application paths.</value>
- protected IServerApplicationPaths ApplicationPaths { get; private set; }
+ protected IServerConfigurationManager ServerConfigurationManager { get; private set; }
/// <summary>
/// Gets or sets the user manager.
@@ -62,21 +63,26 @@ namespace MediaBrowser.Api.Playback
protected IFileSystem FileSystem { get; private set; }
protected IItemRepository ItemRepository { get; private set; }
+ protected ILiveTvManager LiveTvManager { get; private set; }
/// <summary>
/// Initializes a new instance of the <see cref="BaseStreamingService" /> class.
/// </summary>
- /// <param name="appPaths">The app paths.</param>
+ /// <param name="serverConfig">The server configuration.</param>
/// <param name="userManager">The user manager.</param>
/// <param name="libraryManager">The library manager.</param>
/// <param name="isoManager">The iso manager.</param>
/// <param name="mediaEncoder">The media encoder.</param>
- protected BaseStreamingService(IServerApplicationPaths appPaths, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IDtoService dtoService, IFileSystem fileSystem, IItemRepository itemRepository)
+ /// <param name="dtoService">The dto service.</param>
+ /// <param name="fileSystem">The file system.</param>
+ /// <param name="itemRepository">The item repository.</param>
+ protected BaseStreamingService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IDtoService dtoService, IFileSystem fileSystem, IItemRepository itemRepository, ILiveTvManager liveTvManager)
{
+ LiveTvManager = liveTvManager;
ItemRepository = itemRepository;
FileSystem = fileSystem;
DtoService = dtoService;
- ApplicationPaths = appPaths;
+ ServerConfigurationManager = serverConfig;
UserManager = userManager;
LibraryManager = libraryManager;
IsoManager = isoManager;
@@ -105,7 +111,7 @@ namespace MediaBrowser.Api.Playback
/// <returns>System.String.</returns>
protected virtual string GetOutputFileExtension(StreamState state)
{
- return Path.GetExtension(state.Url);
+ return Path.GetExtension(state.RequestedUrl);
}
/// <summary>
@@ -115,7 +121,7 @@ namespace MediaBrowser.Api.Playback
/// <returns>System.String.</returns>
protected virtual string GetOutputFilePath(StreamState state)
{
- var folder = ApplicationPaths.EncodedMediaCachePath;
+ var folder = ServerConfigurationManager.ApplicationPaths.EncodedMediaCachePath;
var outputFileExtension = GetOutputFileExtension(state);
@@ -182,7 +188,7 @@ namespace MediaBrowser.Api.Playback
{
var args = string.Empty;
- if (state.Item.LocationType == LocationType.Remote)
+ if (state.IsRemote || !state.HasMediaStreams)
{
return string.Empty;
}
@@ -191,6 +197,10 @@ namespace MediaBrowser.Api.Playback
{
args += string.Format("-map 0:{0}", state.VideoStream.Index);
}
+ else if (!state.HasMediaStreams)
+ {
+ args += string.Format("-map 0:{0}", 0);
+ }
else
{
args += "-map -0:v";
@@ -200,6 +210,10 @@ namespace MediaBrowser.Api.Playback
{
args += string.Format(" -map 0:{0}", state.AudioStream.Index);
}
+ else if (!state.HasMediaStreams)
+ {
+ args += string.Format(" -map 0:{0}", 1);
+ }
else
{
@@ -247,6 +261,64 @@ namespace MediaBrowser.Api.Playback
}
/// <summary>
+ /// Gets the number of threads.
+ /// </summary>
+ /// <returns>System.Int32.</returns>
+ /// <exception cref="System.Exception">Unrecognized EncodingQuality value.</exception>
+ protected int GetNumberOfThreads()
+ {
+ var quality = ServerConfigurationManager.Configuration.EncodingQuality;
+
+ switch (quality)
+ {
+ case EncodingQuality.Auto:
+ return 0;
+ case EncodingQuality.HighSpeed:
+ return 2;
+ case EncodingQuality.HighQuality:
+ return 2;
+ case EncodingQuality.MaxQuality:
+ return 0;
+ default:
+ throw new Exception("Unrecognized EncodingQuality value.");
+ }
+ }
+
+ /// <summary>
+ /// Gets the video bitrate to specify on the command line
+ /// </summary>
+ /// <param name="state">The state.</param>
+ /// <param name="videoCodec">The video codec.</param>
+ /// <returns>System.String.</returns>
+ protected string GetVideoQualityParam(StreamState state, string videoCodec)
+ {
+ var args = string.Empty;
+
+ // webm
+ if (videoCodec.Equals("libvpx", StringComparison.OrdinalIgnoreCase))
+ {
+ args = "-speed 16 -quality good -profile:v 0 -slices 8";
+ }
+
+ // asf/wmv
+ else if (videoCodec.Equals("wmv2", StringComparison.OrdinalIgnoreCase))
+ {
+ args = "-g 100 -qmax 15";
+ }
+
+ else if (videoCodec.Equals("libx264", StringComparison.OrdinalIgnoreCase))
+ {
+ args = "-preset superfast";
+ }
+ else if (videoCodec.Equals("mpeg4", StringComparison.OrdinalIgnoreCase))
+ {
+ args = "-mbd rd -flags +mv4+aic -trellis 2 -cmp 2 -subcmp 2 -bf 2";
+ }
+
+ return args.Trim();
+ }
+
+ /// <summary>
/// If we're going to put a fixed size on the command line, this will calculate it
/// </summary>
/// <param name="state">The state.</param>
@@ -268,14 +340,17 @@ namespace MediaBrowser.Api.Playback
string.Equals(state.SubtitleStream.Codec, "ass", StringComparison.OrdinalIgnoreCase) ||
string.Equals(state.SubtitleStream.Codec, "ssa", StringComparison.OrdinalIgnoreCase))
{
- assSubtitleParam = GetTextSubtitleParam((Video)state.Item, state.SubtitleStream, request.StartTimeTicks, performTextSubtitleConversion);
+ assSubtitleParam = GetTextSubtitleParam(state, request.StartTimeTicks, performTextSubtitleConversion);
}
}
// If fixed dimensions were supplied
if (request.Width.HasValue && request.Height.HasValue)
{
- return string.Format(" -vf \"scale=trunc({0}/2)*2:trunc({1}/2)*2{2}\"", request.Width.Value, request.Height.Value, assSubtitleParam);
+ var widthParam = request.Width.Value.ToString(UsCulture);
+ var heightParam = request.Height.Value.ToString(UsCulture);
+
+ return string.Format(" -vf \"scale=trunc({0}/2)*2:trunc({1}/2)*2{2}\"", widthParam, heightParam, assSubtitleParam);
}
var isH264Output = outputVideoCodec.Equals("libx264", StringComparison.OrdinalIgnoreCase);
@@ -283,33 +358,41 @@ namespace MediaBrowser.Api.Playback
// If a fixed width was requested
if (request.Width.HasValue)
{
+ var widthParam = request.Width.Value.ToString(UsCulture);
+
return isH264Output ?
- string.Format(" -vf \"scale={0}:trunc(ow/a/2)*2{1}\"", request.Width.Value, assSubtitleParam) :
- string.Format(" -vf \"scale={0}:-1{1}\"", request.Width.Value, assSubtitleParam);
+ string.Format(" -vf \"scale={0}:trunc(ow/a/2)*2{1}\"", widthParam, assSubtitleParam) :
+ string.Format(" -vf \"scale={0}:-1{1}\"", widthParam, assSubtitleParam);
}
// If a fixed height was requested
if (request.Height.HasValue)
{
+ var heightParam = request.Height.Value.ToString(UsCulture);
+
return isH264Output ?
- string.Format(" -vf \"scale=trunc(oh*a*2)/2:{0}{1}\"", request.Height.Value, assSubtitleParam) :
- string.Format(" -vf \"scale=-1:{0}{1}\"", request.Height.Value, assSubtitleParam);
+ string.Format(" -vf \"scale=trunc(oh*a*2)/2:{0}{1}\"", heightParam, assSubtitleParam) :
+ string.Format(" -vf \"scale=-1:{0}{1}\"", heightParam, assSubtitleParam);
}
// If a max width was requested
if (request.MaxWidth.HasValue && (!request.MaxHeight.HasValue || state.VideoStream == null))
{
+ var maxWidthParam = request.MaxWidth.Value.ToString(UsCulture);
+
return isH264Output ?
- string.Format(" -vf \"scale=min(iw\\,{0}):trunc(ow/a/2)*2{1}\"", request.MaxWidth.Value, assSubtitleParam) :
- string.Format(" -vf \"scale=min(iw\\,{0}):-1{1}\"", request.MaxWidth.Value, assSubtitleParam);
+ string.Format(" -vf \"scale=min(iw\\,{0}):trunc(ow/a/2)*2{1}\"", maxWidthParam, assSubtitleParam) :
+ string.Format(" -vf \"scale=min(iw\\,{0}):-1{1}\"", maxWidthParam, assSubtitleParam);
}
// If a max height was requested
if (request.MaxHeight.HasValue && (!request.MaxWidth.HasValue || state.VideoStream == null))
{
+ var maxHeightParam = request.MaxHeight.Value.ToString(UsCulture);
+
return isH264Output ?
- string.Format(" -vf \"scale=trunc(oh*a*2)/2:min(ih\\,{0}){1}\"", request.MaxHeight.Value, assSubtitleParam) :
- string.Format(" -vf \"scale=-1:min(ih\\,{0}){1}\"", request.MaxHeight.Value, assSubtitleParam);
+ string.Format(" -vf \"scale=trunc(oh*a*2)/2:min(ih\\,{0}){1}\"", maxHeightParam, assSubtitleParam) :
+ string.Format(" -vf \"scale=-1:min(ih\\,{0}){1}\"", maxHeightParam, assSubtitleParam);
}
if (state.VideoStream == null)
@@ -329,7 +412,10 @@ namespace MediaBrowser.Api.Playback
// If we're encoding with libx264, it can't handle odd numbered widths or heights, so we'll have to fix that
if (isH264Output)
{
- return string.Format(" -vf \"scale=trunc({0}/2)*2:trunc({1}/2)*2{2}\"", outputSize.Width, outputSize.Height, assSubtitleParam);
+ var widthParam = outputSize.Width.ToString(UsCulture);
+ var heightParam = outputSize.Height.ToString(UsCulture);
+
+ return string.Format(" -vf \"scale=trunc({0}/2)*2:trunc({1}/2)*2{2}\"", widthParam, heightParam, assSubtitleParam);
}
// Otherwise use -vf scale since ffmpeg will ensure internally that the aspect ratio is preserved
@@ -339,14 +425,14 @@ namespace MediaBrowser.Api.Playback
/// <summary>
/// Gets the text subtitle param.
/// </summary>
- /// <param name="video">The video.</param>
- /// <param name="subtitleStream">The subtitle stream.</param>
+ /// <param name="state">The state.</param>
/// <param name="startTimeTicks">The start time ticks.</param>
/// <param name="performConversion">if set to <c>true</c> [perform conversion].</param>
/// <returns>System.String.</returns>
- protected string GetTextSubtitleParam(Video video, MediaStream subtitleStream, long? startTimeTicks, bool performConversion)
+ protected string GetTextSubtitleParam(StreamState state, long? startTimeTicks, bool performConversion)
{
- var path = subtitleStream.IsExternal ? GetConvertedAssPath(video, subtitleStream, startTimeTicks, performConversion) : GetExtractedAssPath(video, subtitleStream, startTimeTicks, performConversion);
+ var path = state.SubtitleStream.IsExternal ? GetConvertedAssPath(state.MediaPath, state.SubtitleStream, startTimeTicks, performConversion) :
+ GetExtractedAssPath(state, startTimeTicks, performConversion);
if (string.IsNullOrEmpty(path))
{
@@ -359,22 +445,21 @@ namespace MediaBrowser.Api.Playback
/// <summary>
/// Gets the extracted ass path.
/// </summary>
- /// <param name="video">The video.</param>
- /// <param name="subtitleStream">The subtitle stream.</param>
+ /// <param name="state">The state.</param>
/// <param name="startTimeTicks">The start time ticks.</param>
/// <param name="performConversion">if set to <c>true</c> [perform conversion].</param>
/// <returns>System.String.</returns>
- private string GetExtractedAssPath(Video video, MediaStream subtitleStream, long? startTimeTicks, bool performConversion)
+ private string GetExtractedAssPath(StreamState state, long? startTimeTicks, bool performConversion)
{
var offset = TimeSpan.FromTicks(startTimeTicks ?? 0);
- var path = Kernel.Instance.FFMpegManager.GetSubtitleCachePath(video, subtitleStream.Index, offset, ".ass");
+ var path = FFMpegManager.Instance.GetSubtitleCachePath(state.MediaPath, state.SubtitleStream, offset, ".ass");
if (performConversion)
{
InputType type;
- var inputPath = MediaEncoderHelpers.GetInputArgument(video, null, out type);
+ var inputPath = MediaEncoderHelpers.GetInputArgument(state.MediaPath, state.IsRemote, state.VideoType, state.IsoType, null, state.PlayableStreamFileNames, out type);
try
{
@@ -382,7 +467,7 @@ namespace MediaBrowser.Api.Playback
Directory.CreateDirectory(parentPath);
- var task = MediaEncoder.ExtractTextSubtitle(inputPath, type, subtitleStream.Index, offset, path, CancellationToken.None);
+ var task = MediaEncoder.ExtractTextSubtitle(inputPath, type, state.SubtitleStream.Index, offset, path, CancellationToken.None);
Task.WaitAll(task);
}
@@ -398,22 +483,16 @@ namespace MediaBrowser.Api.Playback
/// <summary>
/// Gets the converted ass path.
/// </summary>
- /// <param name="video">The video.</param>
+ /// <param name="mediaPath">The media path.</param>
/// <param name="subtitleStream">The subtitle stream.</param>
/// <param name="startTimeTicks">The start time ticks.</param>
/// <param name="performConversion">if set to <c>true</c> [perform conversion].</param>
/// <returns>System.String.</returns>
- private string GetConvertedAssPath(Video video, MediaStream subtitleStream, long? startTimeTicks, bool performConversion)
+ private string GetConvertedAssPath(string mediaPath, MediaStream subtitleStream, long? startTimeTicks, bool performConversion)
{
- // If it's already ass, no conversion neccessary
- //if (string.Equals(Path.GetExtension(subtitleStream.Path), ".ass", StringComparison.OrdinalIgnoreCase))
- //{
- // return subtitleStream.Path;
- //}
-
var offset = TimeSpan.FromTicks(startTimeTicks ?? 0);
- var path = Kernel.Instance.FFMpegManager.GetSubtitleCachePath(video, subtitleStream.Index, offset, ".ass");
+ var path = FFMpegManager.Instance.GetSubtitleCachePath(mediaPath, subtitleStream, offset, ".ass");
if (performConversion)
{
@@ -461,25 +540,15 @@ namespace MediaBrowser.Api.Playback
/// <summary>
/// Gets the probe size argument.
/// </summary>
- /// <param name="item">The item.</param>
+ /// <param name="mediaPath">The media path.</param>
+ /// <param name="isVideo">if set to <c>true</c> [is video].</param>
+ /// <param name="videoType">Type of the video.</param>
+ /// <param name="isoType">Type of the iso.</param>
/// <returns>System.String.</returns>
- protected string GetProbeSizeArgument(BaseItem item)
+ protected string GetProbeSizeArgument(string mediaPath, bool isVideo, VideoType? videoType, IsoType? isoType)
{
- var type = InputType.AudioFile;
-
- if (item is Audio)
- {
- type = MediaEncoderHelpers.GetInputType(item.Path, null, null);
- }
- else
- {
- var video = item as Video;
-
- if (video != null)
- {
- type = MediaEncoderHelpers.GetInputType(item.Path, video.VideoType, video.IsoType);
- }
- }
+ var type = !isVideo ? MediaEncoderHelpers.GetInputType(mediaPath, null, null) :
+ MediaEncoderHelpers.GetInputType(mediaPath, videoType, isoType);
return MediaEncoder.GetProbeSizeArgument(type);
}
@@ -589,22 +658,19 @@ namespace MediaBrowser.Api.Playback
/// <summary>
/// Gets the input argument.
/// </summary>
- /// <param name="item">The item.</param>
- /// <param name="isoMount">The iso mount.</param>
+ /// <param name="state">The state.</param>
/// <returns>System.String.</returns>
- protected string GetInputArgument(BaseItem item, IIsoMount isoMount)
+ protected string GetInputArgument(StreamState state)
{
var type = InputType.AudioFile;
- var inputPath = new[] { item.Path };
-
- var video = item as Video;
+ var inputPath = new[] { state.MediaPath };
- if (video != null)
+ if (state.IsInputVideo)
{
- if (!(video.VideoType == VideoType.Iso && isoMount == null))
+ if (!(state.VideoType == VideoType.Iso && state.IsoMount == null))
{
- inputPath = MediaEncoderHelpers.GetInputArgument(video, isoMount, out type);
+ inputPath = MediaEncoderHelpers.GetInputArgument(state.MediaPath, state.IsRemote, state.VideoType, state.IsoType, state.IsoMount, state.PlayableStreamFileNames, out type);
}
}
@@ -623,11 +689,9 @@ namespace MediaBrowser.Api.Playback
Directory.CreateDirectory(parentPath);
- var video = state.Item as Video;
-
- if (video != null && video.VideoType == VideoType.Iso && video.IsoType.HasValue && IsoManager.CanMount(video.Path))
+ if (state.IsInputVideo && state.VideoType == VideoType.Iso && state.IsoType.HasValue && IsoManager.CanMount(state.MediaPath))
{
- state.IsoMount = await IsoManager.Mount(video.Path, CancellationToken.None).ConfigureAwait(false);
+ state.IsoMount = await IsoManager.Mount(state.MediaPath, CancellationToken.None).ConfigureAwait(false);
}
var process = new Process
@@ -652,11 +716,12 @@ namespace MediaBrowser.Api.Playback
EnableRaisingEvents = true
};
- ApiEntryPoint.Instance.OnTranscodeBeginning(outputPath, TranscodingJobType, process, video != null, state.Request.StartTimeTicks, state.Item.Path, state.Request.DeviceId);
+ ApiEntryPoint.Instance.OnTranscodeBeginning(outputPath, TranscodingJobType, process, state.IsInputVideo, state.Request.StartTimeTicks, state.MediaPath, state.Request.DeviceId);
Logger.Info(process.StartInfo.FileName + " " + process.StartInfo.Arguments);
- var logFilePath = Path.Combine(ApplicationPaths.LogDirectoryPath, "ffmpeg-" + Guid.NewGuid() + ".txt");
+ var logFilePath = Path.Combine(ServerConfigurationManager.ApplicationPaths.LogDirectoryPath, "ffmpeg-" + Guid.NewGuid() + ".txt");
+ Directory.CreateDirectory(Path.GetDirectoryName(logFilePath));
// FFMpeg writes debug/error info to stderr. This is useful when debugging so let's put it in the log directory.
state.LogFileStream = FileSystem.GetFileStream(logFilePath, FileMode.Create, FileAccess.Write, FileShare.Read, true);
@@ -691,13 +756,13 @@ namespace MediaBrowser.Api.Playback
}
// Allow a small amount of time to buffer a little
- if (state.Item is Video)
+ if (state.IsInputVideo)
{
await Task.Delay(500).ConfigureAwait(false);
}
// This is arbitrary, but add a little buffer time when internet streaming
- if (state.Item.LocationType == LocationType.Remote)
+ if (state.IsRemote)
{
await Task.Delay(4000).ConfigureAwait(false);
}
@@ -724,11 +789,11 @@ namespace MediaBrowser.Api.Playback
/// <summary>
/// Gets the user agent param.
/// </summary>
- /// <param name="item">The item.</param>
+ /// <param name="path">The path.</param>
/// <returns>System.String.</returns>
- protected string GetUserAgentParam(BaseItem item)
+ protected string GetUserAgentParam(string path)
{
- var useragent = GetUserAgent(item);
+ var useragent = GetUserAgent(path);
if (!string.IsNullOrEmpty(useragent))
{
@@ -741,11 +806,16 @@ namespace MediaBrowser.Api.Playback
/// <summary>
/// Gets the user agent.
/// </summary>
- /// <param name="item">The item.</param>
+ /// <param name="path">The path.</param>
/// <returns>System.String.</returns>
- protected string GetUserAgent(BaseItem item)
+ protected string GetUserAgent(string path)
{
- if (item.Path.IndexOf("apple.com", StringComparison.OrdinalIgnoreCase) != -1)
+ if (string.IsNullOrEmpty(path))
+ {
+ throw new ArgumentNullException("path");
+
+ }
+ if (path.IndexOf("apple.com", StringComparison.OrdinalIgnoreCase) != -1)
{
return "QuickTime/7.7.4";
}
@@ -784,13 +854,10 @@ namespace MediaBrowser.Api.Playback
/// Gets the state.
/// </summary>
/// <param name="request">The request.</param>
+ /// <param name="cancellationToken">The cancellation token.</param>
/// <returns>StreamState.</returns>
- protected StreamState GetState(StreamRequest request)
+ protected async Task<StreamState> GetState(StreamRequest request, CancellationToken cancellationToken)
{
- var item = DtoService.GetItemByDtoId(request.Id);
-
- var media = (IHasMediaStreams)item;
-
var url = Request.PathInfo;
if (!request.AudioCodec.HasValue)
@@ -800,11 +867,78 @@ namespace MediaBrowser.Api.Playback
var state = new StreamState
{
- Item = item,
Request = request,
- Url = url
+ RequestedUrl = url
};
+ BaseItem item;
+
+ if (string.Equals(request.Type, "Recording", StringComparison.OrdinalIgnoreCase))
+ {
+ var recording = await LiveTvManager.GetInternalRecording(request.Id, cancellationToken).ConfigureAwait(false);
+
+ state.VideoType = VideoType.VideoFile;
+ state.IsInputVideo = string.Equals(recording.MediaType, MediaType.Video, StringComparison.OrdinalIgnoreCase);
+ state.PlayableStreamFileNames = new List<string>();
+
+ if (!string.IsNullOrEmpty(recording.RecordingInfo.Path) && File.Exists(recording.RecordingInfo.Path))
+ {
+ state.MediaPath = recording.RecordingInfo.Path;
+ state.IsRemote = false;
+ }
+ else if (!string.IsNullOrEmpty(recording.RecordingInfo.Url))
+ {
+ state.MediaPath = recording.RecordingInfo.Url;
+ state.IsRemote = true;
+ }
+ else
+ {
+ state.MediaPath = string.Format("http://localhost:{0}/mediabrowser/LiveTv/Recordings/{1}/Stream",
+ ServerConfigurationManager.Configuration.HttpServerPortNumber,
+ request.Id);
+
+ state.IsRemote = true;
+ }
+
+ item = recording;
+ }
+ else if (string.Equals(request.Type, "Channel", StringComparison.OrdinalIgnoreCase))
+ {
+ var channel = LiveTvManager.GetInternalChannel(request.Id);
+
+ state.VideoType = VideoType.VideoFile;
+ state.IsInputVideo = string.Equals(channel.MediaType, MediaType.Video, StringComparison.OrdinalIgnoreCase);
+ state.PlayableStreamFileNames = new List<string>();
+
+ state.MediaPath = string.Format("http://localhost:{0}/mediabrowser/LiveTv/Channels/{1}/Stream",
+ ServerConfigurationManager.Configuration.HttpServerPortNumber,
+ request.Id);
+
+ state.IsRemote = true;
+
+ item = channel;
+ }
+ else
+ {
+ item = DtoService.GetItemByDtoId(request.Id);
+
+ state.MediaPath = item.Path;
+ state.IsRemote = item.LocationType == LocationType.Remote;
+
+ var video = item as Video;
+
+ if (video != null)
+ {
+ state.IsInputVideo = true;
+ state.VideoType = video.VideoType;
+ state.IsoType = video.IsoType;
+
+ state.PlayableStreamFileNames = video.PlayableStreamFileNames == null
+ ? new List<string>()
+ : video.PlayableStreamFileNames.ToList();
+ }
+ }
+
var videoRequest = request as VideoStreamRequest;
var mediaStreams = ItemRepository.GetMediaStreams(new MediaStreamQuery
@@ -829,6 +963,8 @@ namespace MediaBrowser.Api.Playback
state.AudioStream = GetMediaStream(mediaStreams, null, MediaStreamType.Audio, true);
}
+ state.HasMediaStreams = mediaStreams.Count > 0;
+
return state;
}
diff --git a/MediaBrowser.Api/Playback/Hls/AudioHlsService.cs b/MediaBrowser.Api/Playback/Hls/AudioHlsService.cs
index efcc3f07a..d5bf22362 100644
--- a/MediaBrowser.Api/Playback/Hls/AudioHlsService.cs
+++ b/MediaBrowser.Api/Playback/Hls/AudioHlsService.cs
@@ -1,8 +1,9 @@
using MediaBrowser.Common.IO;
using MediaBrowser.Common.MediaInfo;
-using MediaBrowser.Controller;
+using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Dto;
using MediaBrowser.Controller.Library;
+using MediaBrowser.Controller.LiveTv;
using MediaBrowser.Controller.Persistence;
using MediaBrowser.Model.Dto;
using MediaBrowser.Model.IO;
@@ -26,8 +27,8 @@ namespace MediaBrowser.Api.Playback.Hls
/// </summary>
public class AudioHlsService : BaseHlsService
{
- public AudioHlsService(IServerApplicationPaths appPaths, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IDtoService dtoService, IFileSystem fileSystem, IItemRepository itemRepository)
- : base(appPaths, userManager, libraryManager, isoManager, mediaEncoder, dtoService, fileSystem, itemRepository)
+ public AudioHlsService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IDtoService dtoService, IFileSystem fileSystem, IItemRepository itemRepository, ILiveTvManager liveTvManager)
+ : base(serverConfig, userManager, libraryManager, isoManager, mediaEncoder, dtoService, fileSystem, itemRepository, liveTvManager)
{
}
diff --git a/MediaBrowser.Api/Playback/Hls/BaseHlsService.cs b/MediaBrowser.Api/Playback/Hls/BaseHlsService.cs
index 1e5e8b82d..68342e91d 100644
--- a/MediaBrowser.Api/Playback/Hls/BaseHlsService.cs
+++ b/MediaBrowser.Api/Playback/Hls/BaseHlsService.cs
@@ -2,10 +2,10 @@
using MediaBrowser.Common.IO;
using MediaBrowser.Common.MediaInfo;
using MediaBrowser.Common.Net;
-using MediaBrowser.Controller;
+using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Dto;
-using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library;
+using MediaBrowser.Controller.LiveTv;
using MediaBrowser.Controller.Persistence;
using MediaBrowser.Model.Dto;
using MediaBrowser.Model.IO;
@@ -13,6 +13,7 @@ using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
+using System.Threading;
using System.Threading.Tasks;
namespace MediaBrowser.Api.Playback.Hls
@@ -22,14 +23,14 @@ namespace MediaBrowser.Api.Playback.Hls
/// </summary>
public abstract class BaseHlsService : BaseStreamingService
{
- protected BaseHlsService(IServerApplicationPaths appPaths, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IDtoService dtoService, IFileSystem fileSystem, IItemRepository itemRepository)
- : base(appPaths, userManager, libraryManager, isoManager, mediaEncoder, dtoService, fileSystem, itemRepository)
+ protected BaseHlsService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IDtoService dtoService, IFileSystem fileSystem, IItemRepository itemRepository, ILiveTvManager liveTvManager)
+ : base(serverConfig, userManager, libraryManager, isoManager, mediaEncoder, dtoService, fileSystem, itemRepository, liveTvManager)
{
}
protected override string GetOutputFilePath(StreamState state)
{
- var folder = ApplicationPaths.EncodedMediaCachePath;
+ var folder = ServerConfigurationManager.ApplicationPaths.EncodedMediaCachePath;
var outputFileExtension = GetOutputFileExtension(state);
@@ -73,7 +74,7 @@ namespace MediaBrowser.Api.Playback.Hls
/// <returns>System.Object.</returns>
protected object ProcessRequest(StreamRequest request)
{
- var state = GetState(request);
+ var state = GetState(request, CancellationToken.None).Result;
return ProcessRequestAsync(state).Result;
}
@@ -247,7 +248,7 @@ namespace MediaBrowser.Api.Playback.Hls
/// <returns>System.String.</returns>
protected override string GetCommandLineArguments(string outputPath, StreamState state, bool performSubtitleConversions)
{
- var probeSize = GetProbeSizeArgument(state.Item);
+ var probeSize = GetProbeSizeArgument(state.MediaPath, state.IsInputVideo, state.VideoType, state.IsoType);
var hlsVideoRequest = state.VideoRequest as GetHlsVideoStream;
@@ -257,13 +258,16 @@ namespace MediaBrowser.Api.Playback.Hls
var itsOffset = itsOffsetMs == 0 ? string.Empty : string.Format("-itsoffset {0} ", TimeSpan.FromMilliseconds(itsOffsetMs).TotalSeconds);
- var args = string.Format("{0}{1} {2} {3} -i {4}{5} -threads 0 {6} {7} -sc_threshold 0 {8} -hls_time 10 -start_number 0 -hls_list_size 1440 \"{9}\"",
+ var threads = GetNumberOfThreads();
+
+ var args = string.Format("{0}{1} {2} {3} -i {4}{5} -threads {6} {7} {8} -sc_threshold 0 {9} -hls_time 10 -start_number 0 -hls_list_size 1440 \"{10}\"",
itsOffset,
probeSize,
- GetUserAgentParam(state.Item),
+ GetUserAgentParam(state.MediaPath),
GetFastSeekCommandLineParameter(state.Request),
- GetInputArgument(state.Item, state.IsoMount),
+ GetInputArgument(state),
GetSlowSeekCommandLineParameter(state.Request),
+ threads,
GetMapArgs(state),
GetVideoArguments(state, performSubtitleConversions),
GetAudioArguments(state),
@@ -272,13 +276,14 @@ namespace MediaBrowser.Api.Playback.Hls
if (hlsVideoRequest != null)
{
- if (hlsVideoRequest.AppendBaselineStream && state.Item is Video)
+ if (hlsVideoRequest.AppendBaselineStream && state.IsInputVideo)
{
var lowBitratePath = Path.Combine(Path.GetDirectoryName(outputPath), Path.GetFileNameWithoutExtension(outputPath) + "-low.m3u8");
var bitrate = hlsVideoRequest.BaselineStreamAudioBitRate ?? 64000;
- var lowBitrateParams = string.Format(" -threads 0 -vn -codec:a:0 libmp3lame -ac 2 -ab {1} -hls_time 10 -start_number 0 -hls_list_size 1440 \"{0}\"",
+ var lowBitrateParams = string.Format(" -threads {0} -vn -codec:a:0 libmp3lame -ac 2 -ab {2} -hls_time 10 -start_number 0 -hls_list_size 1440 \"{1}\"",
+ threads,
lowBitratePath,
bitrate / 2);
diff --git a/MediaBrowser.Api/Playback/Hls/HlsSegmentService.cs b/MediaBrowser.Api/Playback/Hls/HlsSegmentService.cs
index 02a632694..93e1a06a0 100644
--- a/MediaBrowser.Api/Playback/Hls/HlsSegmentService.cs
+++ b/MediaBrowser.Api/Playback/Hls/HlsSegmentService.cs
@@ -145,16 +145,13 @@ namespace MediaBrowser.Api.Playback.Hls
}
}
- private void ExtendPlaylistTimer(string playlist)
+ private async void ExtendPlaylistTimer(string playlist)
{
ApiEntryPoint.Instance.OnTranscodeBeginRequest(playlist, TranscodingJobType.Hls);
- Task.Run(async () =>
- {
- await Task.Delay(20000).ConfigureAwait(false);
+ await Task.Delay(20000).ConfigureAwait(false);
- ApiEntryPoint.Instance.OnTranscodeEndRequest(playlist, TranscodingJobType.Hls);
- });
+ ApiEntryPoint.Instance.OnTranscodeEndRequest(playlist, TranscodingJobType.Hls);
}
}
}
diff --git a/MediaBrowser.Api/Playback/Hls/VideoHlsService.cs b/MediaBrowser.Api/Playback/Hls/VideoHlsService.cs
index fe863c862..583082500 100644
--- a/MediaBrowser.Api/Playback/Hls/VideoHlsService.cs
+++ b/MediaBrowser.Api/Playback/Hls/VideoHlsService.cs
@@ -1,8 +1,9 @@
using MediaBrowser.Common.IO;
using MediaBrowser.Common.MediaInfo;
-using MediaBrowser.Controller;
+using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Dto;
using MediaBrowser.Controller.Library;
+using MediaBrowser.Controller.LiveTv;
using MediaBrowser.Controller.Persistence;
using MediaBrowser.Model.IO;
using ServiceStack;
@@ -32,8 +33,8 @@ namespace MediaBrowser.Api.Playback.Hls
/// </summary>
public class VideoHlsService : BaseHlsService
{
- public VideoHlsService(IServerApplicationPaths appPaths, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IDtoService dtoService, IFileSystem fileSystem, IItemRepository itemRepository)
- : base(appPaths, userManager, libraryManager, isoManager, mediaEncoder, dtoService, fileSystem, itemRepository)
+ public VideoHlsService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IDtoService dtoService, IFileSystem fileSystem, IItemRepository itemRepository, ILiveTvManager liveTvManager)
+ : base(serverConfig, userManager, libraryManager, isoManager, mediaEncoder, dtoService, fileSystem, itemRepository, liveTvManager)
{
}
@@ -93,7 +94,7 @@ namespace MediaBrowser.Api.Playback.Hls
audioSampleRate = state.Request.AudioSampleRate.Value + ":";
}
- args += string.Format(" -af \"adelay=1,aresample={0}async=1000{1}\"", audioSampleRate, volParam);
+ args += string.Format(" -af \"adelay=1,aresample={0}async=1{1}\"", audioSampleRate, volParam);
return args;
}
@@ -123,13 +124,13 @@ namespace MediaBrowser.Api.Playback.Hls
(state.SubtitleStream.Codec.IndexOf("pgs", StringComparison.OrdinalIgnoreCase) != -1 ||
state.SubtitleStream.Codec.IndexOf("dvd", StringComparison.OrdinalIgnoreCase) != -1);
- var args = "-codec:v:0 " + codec + " -preset superfast" + keyFrameArg;
+ var args = "-codec:v:0 " + codec + " " + GetVideoQualityParam(state, "libx264") + keyFrameArg;
var bitrate = GetVideoBitrateParam(state);
if (bitrate.HasValue)
{
- args += string.Format(" -b:v {0} -maxrate ({0}*.85) -bufsize {0}", bitrate.Value.ToString(UsCulture));
+ args += string.Format(" -b:v {0} -maxrate ({0}*.80) -bufsize {0}", bitrate.Value.ToString(UsCulture));
}
// Add resolution params, if specified
diff --git a/MediaBrowser.Api/Playback/Progressive/AudioService.cs b/MediaBrowser.Api/Playback/Progressive/AudioService.cs
index 86ab498f6..baf7f48fe 100644
--- a/MediaBrowser.Api/Playback/Progressive/AudioService.cs
+++ b/MediaBrowser.Api/Playback/Progressive/AudioService.cs
@@ -1,9 +1,10 @@
using MediaBrowser.Common.IO;
using MediaBrowser.Common.MediaInfo;
-using MediaBrowser.Controller;
+using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Drawing;
using MediaBrowser.Controller.Dto;
using MediaBrowser.Controller.Library;
+using MediaBrowser.Controller.LiveTv;
using MediaBrowser.Controller.Persistence;
using MediaBrowser.Model.IO;
using ServiceStack;
@@ -41,8 +42,8 @@ namespace MediaBrowser.Api.Playback.Progressive
/// </summary>
public class AudioService : BaseProgressiveStreamingService
{
- public AudioService(IServerApplicationPaths appPaths, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IItemRepository itemRepo, IDtoService dtoService, IImageProcessor imageProcessor, IFileSystem fileSystem)
- : base(appPaths, userManager, libraryManager, isoManager, mediaEncoder, itemRepo, dtoService, imageProcessor, fileSystem)
+ public AudioService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IDtoService dtoService, IFileSystem fileSystem, IItemRepository itemRepository, ILiveTvManager liveTvManager, IImageProcessor imageProcessor)
+ : base(serverConfig, userManager, libraryManager, isoManager, mediaEncoder, dtoService, fileSystem, itemRepository, liveTvManager, imageProcessor)
{
}
@@ -101,13 +102,16 @@ namespace MediaBrowser.Api.Playback.Progressive
const string vn = " -vn";
- return string.Format("{0} -i {1}{2} -threads 0{5} {3} -id3v2_version 3 -write_id3v1 1 \"{4}\"",
+ var threads = GetNumberOfThreads();
+
+ return string.Format("{0} -i {1}{2} -threads {3}{4} {5} -id3v2_version 3 -write_id3v1 1 \"{6}\"",
GetFastSeekCommandLineParameter(request),
- GetInputArgument(state.Item, state.IsoMount),
+ GetInputArgument(state),
GetSlowSeekCommandLineParameter(request),
+ threads,
+ vn,
string.Join(" ", audioTranscodeParams.ToArray()),
- outputPath,
- vn).Trim();
+ outputPath).Trim();
}
}
}
diff --git a/MediaBrowser.Api/Playback/Progressive/BaseProgressiveStreamingService.cs b/MediaBrowser.Api/Playback/Progressive/BaseProgressiveStreamingService.cs
index 1fea32219..e367801d2 100644
--- a/MediaBrowser.Api/Playback/Progressive/BaseProgressiveStreamingService.cs
+++ b/MediaBrowser.Api/Playback/Progressive/BaseProgressiveStreamingService.cs
@@ -1,20 +1,18 @@
-using MediaBrowser.Api.Images;
-using MediaBrowser.Common.IO;
+using MediaBrowser.Common.IO;
using MediaBrowser.Common.MediaInfo;
using MediaBrowser.Common.Net;
-using MediaBrowser.Controller;
+using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Drawing;
using MediaBrowser.Controller.Dto;
-using MediaBrowser.Controller.Entities;
-using MediaBrowser.Controller.Entities.Audio;
using MediaBrowser.Controller.Library;
+using MediaBrowser.Controller.LiveTv;
using MediaBrowser.Controller.Persistence;
using MediaBrowser.Model.Dto;
-using MediaBrowser.Model.Entities;
using MediaBrowser.Model.IO;
using System.Collections.Generic;
using System.IO;
using System.Net.Http;
+using System.Threading;
using System.Threading.Tasks;
namespace MediaBrowser.Api.Playback.Progressive
@@ -26,8 +24,8 @@ namespace MediaBrowser.Api.Playback.Progressive
{
protected readonly IImageProcessor ImageProcessor;
- protected BaseProgressiveStreamingService(IServerApplicationPaths appPaths, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IItemRepository itemRepository, IDtoService dtoService, IImageProcessor imageProcessor, IFileSystem fileSystem) :
- base(appPaths, userManager, libraryManager, isoManager, mediaEncoder, dtoService, fileSystem, itemRepository)
+ protected BaseProgressiveStreamingService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IDtoService dtoService, IFileSystem fileSystem, IItemRepository itemRepository, ILiveTvManager liveTvManager, IImageProcessor imageProcessor)
+ : base(serverConfig, userManager, libraryManager, isoManager, mediaEncoder, dtoService, fileSystem, itemRepository, liveTvManager)
{
ImageProcessor = imageProcessor;
}
@@ -51,9 +49,7 @@ namespace MediaBrowser.Api.Playback.Progressive
// Try to infer based on the desired video codec
if (videoRequest != null && videoRequest.VideoCodec.HasValue)
{
- var video = state.Item as Video;
-
- if (video != null)
+ if (state.IsInputVideo)
{
switch (videoRequest.VideoCodec.Value)
{
@@ -72,9 +68,7 @@ namespace MediaBrowser.Api.Playback.Progressive
// Try to infer based on the desired audio codec
if (state.Request.AudioCodec.HasValue)
{
- var audio = state.Item as Audio;
-
- if (audio != null)
+ if (!state.IsInputVideo)
{
switch (state.Request.AudioCodec.Value)
{
@@ -186,18 +180,13 @@ namespace MediaBrowser.Api.Playback.Progressive
/// <returns>Task.</returns>
protected object ProcessRequest(StreamRequest request, bool isHeadRequest)
{
- var state = GetState(request);
-
- if (request.AlbumArt)
- {
- return GetAlbumArtResponse(state);
- }
+ var state = GetState(request, CancellationToken.None).Result;
var responseHeaders = new Dictionary<string, string>();
- if (request.Static && state.Item.LocationType == LocationType.Remote)
+ if (request.Static && state.IsRemote)
{
- return GetStaticRemoteStreamResult(state.Item, responseHeaders, isHeadRequest).Result;
+ return GetStaticRemoteStreamResult(state.MediaPath, responseHeaders, isHeadRequest).Result;
}
var outputPath = GetOutputFilePath(state);
@@ -210,7 +199,7 @@ namespace MediaBrowser.Api.Playback.Progressive
if (request.Static)
{
- return ResultFactory.GetStaticFileResult(Request, state.Item.Path, FileShare.Read, responseHeaders, isHeadRequest);
+ return ResultFactory.GetStaticFileResult(Request, state.MediaPath, FileShare.Read, responseHeaders, isHeadRequest);
}
if (outputPathExists && !ApiEntryPoint.Instance.HasActiveTranscodingJob(outputPath, TranscodingJobType.Progressive))
@@ -224,19 +213,19 @@ namespace MediaBrowser.Api.Playback.Progressive
/// <summary>
/// Gets the static remote stream result.
/// </summary>
- /// <param name="item">The item.</param>
+ /// <param name="mediaPath">The media path.</param>
/// <param name="responseHeaders">The response headers.</param>
/// <param name="isHeadRequest">if set to <c>true</c> [is head request].</param>
/// <returns>Task{System.Object}.</returns>
- private async Task<object> GetStaticRemoteStreamResult(BaseItem item, Dictionary<string, string> responseHeaders, bool isHeadRequest)
+ private async Task<object> GetStaticRemoteStreamResult(string mediaPath, Dictionary<string, string> responseHeaders, bool isHeadRequest)
{
responseHeaders["Accept-Ranges"] = "none";
var httpClient = new HttpClient();
- using (var message = new HttpRequestMessage(HttpMethod.Get, item.Path))
+ using (var message = new HttpRequestMessage(HttpMethod.Get, mediaPath))
{
- var useragent = GetUserAgent(item);
+ var useragent = GetUserAgent(mediaPath);
if (!string.IsNullOrEmpty(useragent))
{
@@ -273,47 +262,6 @@ namespace MediaBrowser.Api.Playback.Progressive
}
/// <summary>
- /// Gets the album art response.
- /// </summary>
- /// <param name="state">The state.</param>
- /// <returns>System.Object.</returns>
- private object GetAlbumArtResponse(StreamState state)
- {
- var request = new GetItemImage
- {
- MaxWidth = 800,
- MaxHeight = 800,
- Type = ImageType.Primary,
- Id = state.Item.Id.ToString()
- };
-
- // Try and find some image to return
- if (!state.Item.HasImage(ImageType.Primary))
- {
- if (state.Item.HasImage(ImageType.Backdrop))
- {
- request.Type = ImageType.Backdrop;
- }
- else if (state.Item.HasImage(ImageType.Thumb))
- {
- request.Type = ImageType.Thumb;
- }
- else if (state.Item.HasImage(ImageType.Logo))
- {
- request.Type = ImageType.Logo;
- }
- }
-
- return new ImageService(UserManager, LibraryManager, ApplicationPaths, null, ItemRepository, DtoService, ImageProcessor, null)
- {
- Logger = Logger,
- Request = Request,
- ResultFactory = ResultFactory
-
- }.Get(request);
- }
-
- /// <summary>
/// Gets the stream result.
/// </summary>
/// <param name="state">The state.</param>
diff --git a/MediaBrowser.Api/Playback/Progressive/VideoService.cs b/MediaBrowser.Api/Playback/Progressive/VideoService.cs
index 40c7492ff..f4e4019f6 100644
--- a/MediaBrowser.Api/Playback/Progressive/VideoService.cs
+++ b/MediaBrowser.Api/Playback/Progressive/VideoService.cs
@@ -1,10 +1,10 @@
using MediaBrowser.Common.IO;
using MediaBrowser.Common.MediaInfo;
-using MediaBrowser.Controller;
+using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Drawing;
using MediaBrowser.Controller.Dto;
-using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library;
+using MediaBrowser.Controller.LiveTv;
using MediaBrowser.Controller.Persistence;
using MediaBrowser.Model.IO;
using ServiceStack;
@@ -55,8 +55,8 @@ namespace MediaBrowser.Api.Playback.Progressive
/// </summary>
public class VideoService : BaseProgressiveStreamingService
{
- public VideoService(IServerApplicationPaths appPaths, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IItemRepository itemRepo, IDtoService dtoService, IImageProcessor imageProcessor, IFileSystem fileSystem)
- : base(appPaths, userManager, libraryManager, isoManager, mediaEncoder, itemRepo, dtoService, imageProcessor, fileSystem)
+ public VideoService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IDtoService dtoService, IFileSystem fileSystem, IItemRepository itemRepository, ILiveTvManager liveTvManager, IImageProcessor imageProcessor)
+ : base(serverConfig, userManager, libraryManager, isoManager, mediaEncoder, dtoService, fileSystem, itemRepository, liveTvManager, imageProcessor)
{
}
@@ -89,9 +89,7 @@ namespace MediaBrowser.Api.Playback.Progressive
/// <returns>System.String.</returns>
protected override string GetCommandLineArguments(string outputPath, StreamState state, bool performSubtitleConversions)
{
- var video = (Video)state.Item;
-
- var probeSize = GetProbeSizeArgument(state.Item);
+ var probeSize = GetProbeSizeArgument(state.MediaPath, state.IsInputVideo, state.VideoType, state.IsoType);
// Get the output codec name
var videoCodec = GetVideoCodec(state.VideoRequest);
@@ -104,13 +102,13 @@ namespace MediaBrowser.Api.Playback.Progressive
format = " -f mp4 -movflags frag_keyframe+empty_moov";
}
- var threads = string.Equals(videoCodec, "libvpx", StringComparison.OrdinalIgnoreCase) ? 2 : 0;
+ var threads = string.Equals(videoCodec, "libvpx", StringComparison.OrdinalIgnoreCase) ? 2 : GetNumberOfThreads();
return string.Format("{0} {1} {2} -i {3}{4}{5} {6} {7} -threads {8} {9}{10} \"{11}\"",
probeSize,
- GetUserAgentParam(state.Item),
+ GetUserAgentParam(state.MediaPath),
GetFastSeekCommandLineParameter(state.Request),
- GetInputArgument(video, state.IsoMount),
+ GetInputArgument(state),
GetSlowSeekCommandLineParameter(state.Request),
keyFrame,
GetMapArgs(state),
@@ -165,9 +163,16 @@ namespace MediaBrowser.Api.Playback.Progressive
var qualityParam = GetVideoQualityParam(state, codec);
+ var bitrate = GetVideoBitrateParam(state);
+
+ if (bitrate.HasValue)
+ {
+ qualityParam += string.Format(" -b:v {0}", bitrate.Value.ToString(UsCulture));
+ }
+
if (!string.IsNullOrEmpty(qualityParam))
{
- args += " " + qualityParam;
+ args += " " + qualityParam.Trim();
}
args += " -vsync vfr";
@@ -213,9 +218,9 @@ namespace MediaBrowser.Api.Playback.Progressive
{
return "-acodec copy";
}
-
+
var args = "-acodec " + codec;
-
+
// Add the number of audio channels
var channels = GetNumAudioChannelsParam(request, state.AudioStream);
@@ -231,64 +236,23 @@ namespace MediaBrowser.Api.Playback.Progressive
args += " -ab " + bitrate.Value.ToString(UsCulture);
}
- var volParam = string.Empty;
- var AudioSampleRate = string.Empty;
-
- // Boost volume to 200% when downsampling from 6ch to 2ch
- if (channels.HasValue && channels.Value <= 2 && state.AudioStream.Channels.HasValue && state.AudioStream.Channels.Value > 5)
- {
- volParam = ",volume=2.000000";
- }
-
- if (state.Request.AudioSampleRate.HasValue)
- {
- AudioSampleRate= state.Request.AudioSampleRate.Value + ":";
- }
-
- args += string.Format(" -af \"aresample={0}async=1000{1}\"",AudioSampleRate, volParam);
-
- return args;
- }
-
- /// <summary>
- /// Gets the video bitrate to specify on the command line
- /// </summary>
- /// <param name="state">The state.</param>
- /// <param name="videoCodec">The video codec.</param>
- /// <returns>System.String.</returns>
- private string GetVideoQualityParam(StreamState state, string videoCodec)
- {
- var args = string.Empty;
+ var volParam = string.Empty;
+ var AudioSampleRate = string.Empty;
- // webm
- if (videoCodec.Equals("libvpx", StringComparison.OrdinalIgnoreCase))
+ // Boost volume to 200% when downsampling from 6ch to 2ch
+ if (channels.HasValue && channels.Value <= 2 && state.AudioStream.Channels.HasValue && state.AudioStream.Channels.Value > 5)
{
- args = "-speed 16 -quality good -profile:v 0 -slices 8";
+ volParam = ",volume=2.000000";
}
- // asf/wmv
- else if (videoCodec.Equals("wmv2", StringComparison.OrdinalIgnoreCase))
+ if (state.Request.AudioSampleRate.HasValue)
{
- args = "-g 100 -qmax 15";
+ AudioSampleRate = state.Request.AudioSampleRate.Value + ":";
}
- else if (videoCodec.Equals("libx264", StringComparison.OrdinalIgnoreCase))
- {
- args = "-preset superfast";
- }
- else if (videoCodec.Equals("mpeg4", StringComparison.OrdinalIgnoreCase))
- {
- args = "-mbd rd -flags +mv4+aic -trellis 2 -cmp 2 -subcmp 2 -bf 2";
- }
-
- var bitrate = GetVideoBitrateParam(state);
-
- if (bitrate.HasValue)
- {
- args += string.Format(" -b:v {0}", bitrate.Value.ToString(UsCulture));
- }
+ args += string.Format(" -af \"aresample={0}async=1{1}\"", AudioSampleRate, volParam);
- return args.Trim();
+ return args;
}
}
}
diff --git a/MediaBrowser.Api/Playback/StreamRequest.cs b/MediaBrowser.Api/Playback/StreamRequest.cs
index 1486c0de7..454cc411c 100644
--- a/MediaBrowser.Api/Playback/StreamRequest.cs
+++ b/MediaBrowser.Api/Playback/StreamRequest.cs
@@ -65,6 +65,12 @@ namespace MediaBrowser.Api.Playback
/// No need to put this in api docs since it's dlna only
/// </summary>
public bool AlbumArt { get; set; }
+
+ /// <summary>
+ /// Gets or sets the type.
+ /// </summary>
+ /// <value>The type.</value>
+ public string Type { get; set; }
}
public class VideoStreamRequest : StreamRequest
diff --git a/MediaBrowser.Api/Playback/StreamState.cs b/MediaBrowser.Api/Playback/StreamState.cs
index 3c2ea5a13..be1ad85eb 100644
--- a/MediaBrowser.Api/Playback/StreamState.cs
+++ b/MediaBrowser.Api/Playback/StreamState.cs
@@ -1,14 +1,13 @@
-using System.IO;
-using MediaBrowser.Common.IO;
-using MediaBrowser.Controller.Entities;
-using MediaBrowser.Model.Entities;
+using MediaBrowser.Model.Entities;
using MediaBrowser.Model.IO;
+using System.Collections.Generic;
+using System.IO;
namespace MediaBrowser.Api.Playback
{
public class StreamState
{
- public string Url { get; set; }
+ public string RequestedUrl { get; set; }
public StreamRequest Request { get; set; }
@@ -29,12 +28,24 @@ namespace MediaBrowser.Api.Playback
public MediaStream SubtitleStream { get; set; }
- public BaseItem Item { get; set; }
-
/// <summary>
/// Gets or sets the iso mount.
/// </summary>
/// <value>The iso mount.</value>
public IIsoMount IsoMount { get; set; }
+
+ public string MediaPath { get; set; }
+
+ public bool IsRemote { get; set; }
+
+ public bool IsInputVideo { get; set; }
+
+ public VideoType VideoType { get; set; }
+
+ public IsoType? IsoType { get; set; }
+
+ public List<string> PlayableStreamFileNames { get; set; }
+
+ public bool HasMediaStreams { get; set; }
}
}
diff --git a/MediaBrowser.Api/SearchService.cs b/MediaBrowser.Api/SearchService.cs
index 25e22ab59..78ff1bc07 100644
--- a/MediaBrowser.Api/SearchService.cs
+++ b/MediaBrowser.Api/SearchService.cs
@@ -148,12 +148,13 @@ namespace MediaBrowser.Api
MediaType = item.MediaType,
MatchedTerm = hintInfo.MatchedTerm,
DisplayMediaType = item.DisplayMediaType,
- RunTimeTicks = item.RunTimeTicks
+ RunTimeTicks = item.RunTimeTicks,
+ ProductionYear = item.ProductionYear
};
if (item.HasImage(ImageType.Primary))
{
- result.PrimaryImageTag = _imageProcessor.GetImageCacheTag(item, ImageType.Primary, item.GetImage(ImageType.Primary));
+ result.PrimaryImageTag = _imageProcessor.GetImageCacheTag(item, ImageType.Primary, item.GetImagePath(ImageType.Primary));
}
var episode = item as Episode;
diff --git a/MediaBrowser.Api/SystemService.cs b/MediaBrowser.Api/SystemService.cs
index dae603dc8..23d64e89b 100644
--- a/MediaBrowser.Api/SystemService.cs
+++ b/MediaBrowser.Api/SystemService.cs
@@ -124,9 +124,11 @@ namespace MediaBrowser.Api
/// <returns>System.Object.</returns>
public object Get(GetConfiguration request)
{
- var dateModified = _fileSystem.GetLastWriteTimeUtc(_configurationManager.ApplicationPaths.SystemConfigurationFilePath);
+ var configPath = _configurationManager.ApplicationPaths.SystemConfigurationFilePath;
- var cacheKey = (_configurationManager.ApplicationPaths.SystemConfigurationFilePath + dateModified.Ticks).GetMD5();
+ var dateModified = _fileSystem.GetLastWriteTimeUtc(configPath);
+
+ var cacheKey = (configPath + dateModified.Ticks).GetMD5();
return ToOptimizedResultUsingCache(cacheKey, dateModified, null, () => _configurationManager.Configuration);
}
diff --git a/MediaBrowser.Api/TvShowsService.cs b/MediaBrowser.Api/TvShowsService.cs
index b45d4dfb0..9521f82cc 100644
--- a/MediaBrowser.Api/TvShowsService.cs
+++ b/MediaBrowser.Api/TvShowsService.cs
@@ -1,4 +1,5 @@
using MediaBrowser.Api.UserLibrary;
+using MediaBrowser.Common.Extensions;
using MediaBrowser.Controller.Dto;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.TV;
@@ -192,18 +193,6 @@ namespace MediaBrowser.Api
/// <returns>System.Object.</returns>
public object Get(GetNextUpEpisodes request)
{
- var result = GetNextUpEpisodeItemsResult(request);
-
- return ToOptimizedResult(result);
- }
-
- /// <summary>
- /// Gets the next up episodes.
- /// </summary>
- /// <param name="request">The request.</param>
- /// <returns>Task{ItemsResult}.</returns>
- private ItemsResult GetNextUpEpisodeItemsResult(GetNextUpEpisodes request)
- {
var user = _userManager.GetUserById(request.UserId);
var itemsList = GetNextUpEpisodes(request)
@@ -215,11 +204,13 @@ namespace MediaBrowser.Api
var returnItems = pagedItems.Select(i => _dtoService.GetBaseItemDto(i, fields, user)).ToArray();
- return new ItemsResult
+ var result = new ItemsResult
{
TotalRecordCount = itemsList.Count,
Items = returnItems
};
+
+ return ToOptimizedResult(result);
}
public IEnumerable<Episode> GetNextUpEpisodes(GetNextUpEpisodes request)
@@ -273,14 +264,12 @@ namespace MediaBrowser.Api
/// <returns>Task{Episode}.</returns>
private Tuple<Episode, DateTime> GetNextUp(Series series, User user, GetNextUpEpisodes request)
{
- var allEpisodes = series.GetRecursiveChildren(user)
- .OfType<Episode>()
- .OrderByDescending(i => i.PremiereDate ?? DateTime.MinValue)
- .ThenByDescending(i => i.IndexNumber ?? 0)
+ // Get them in display order, then reverse
+ var allEpisodes = series.GetSeasons(user, true, true)
+ .SelectMany(i => i.GetEpisodes(user, true, true))
+ .Reverse()
.ToList();
- allEpisodes = FilterItems(request, allEpisodes).ToList();
-
Episode lastWatched = null;
var lastWatchedDate = DateTime.MinValue;
Episode nextUp = null;
@@ -302,7 +291,10 @@ namespace MediaBrowser.Api
}
else
{
- nextUp = episode;
+ if (episode.LocationType != LocationType.Virtual)
+ {
+ nextUp = episode;
+ }
}
}
@@ -314,15 +306,6 @@ namespace MediaBrowser.Api
return new Tuple<Episode, DateTime>(null, lastWatchedDate);
}
-
- private IEnumerable<Episode> FilterItems(GetNextUpEpisodes request, IEnumerable<Episode> items)
- {
- // Make this configurable when needed
- items = items.Where(i => i.LocationType != LocationType.Virtual);
-
- return items;
- }
-
private IEnumerable<Series> FilterSeries(GetNextUpEpisodes request, IEnumerable<Series> items)
{
if (!string.IsNullOrWhiteSpace(request.SeriesId))
@@ -364,12 +347,12 @@ namespace MediaBrowser.Api
var series = _libraryManager.GetItemById(request.Id) as Series;
- var fields = request.GetItemFields().ToList();
-
- var seasons = series.GetChildren(user, true)
- .OfType<Season>();
+ if (series == null)
+ {
+ throw new ResourceNotFoundException("No series exists with Id " + request.Id);
+ }
- var sortOrder = ItemSortBy.SortName;
+ var seasons = series.GetSeasons(user);
if (request.IsSpecialSeason.HasValue)
{
@@ -378,29 +361,8 @@ namespace MediaBrowser.Api
seasons = seasons.Where(i => i.IsSpecialSeason == val);
}
- var config = user.Configuration;
-
- if (!config.DisplayMissingEpisodes && !config.DisplayUnairedEpisodes)
- {
- seasons = seasons.Where(i => !i.IsMissingOrVirtualUnaired);
- }
- else
- {
- if (!config.DisplayMissingEpisodes)
- {
- seasons = seasons.Where(i => !i.IsMissingSeason);
- }
- if (!config.DisplayUnairedEpisodes)
- {
- seasons = seasons.Where(i => !i.IsVirtualUnaired);
- }
- }
-
seasons = FilterVirtualSeasons(request, seasons);
- seasons = _libraryManager.Sort(seasons, user, new[] { sortOrder }, SortOrder.Ascending)
- .Cast<Season>();
-
// This must be the last filter
if (!string.IsNullOrEmpty(request.AdjacentTo))
{
@@ -408,6 +370,8 @@ namespace MediaBrowser.Api
.Cast<Season>();
}
+ var fields = request.GetItemFields().ToList();
+
var returnItems = seasons.Select(i => _dtoService.GetBaseItemDto(i, fields, user))
.ToArray();
@@ -450,66 +414,45 @@ namespace MediaBrowser.Api
{
var user = _userManager.GetUserById(request.UserId);
- var series = _libraryManager.GetItemById(request.Id) as Series;
-
- var fields = request.GetItemFields().ToList();
-
- var episodes = series.GetRecursiveChildren(user)
- .OfType<Episode>();
+ IEnumerable<Episode> episodes;
- var sortOrder = ItemSortBy.SortName;
-
- if (!string.IsNullOrEmpty(request.SeasonId))
+ if (string.IsNullOrEmpty(request.SeasonId))
{
- var season = _libraryManager.GetItemById(new Guid(request.SeasonId)) as Season;
+ var series = _libraryManager.GetItemById(request.Id) as Series;
- if (season.IndexNumber.HasValue)
+ if (series == null)
{
- episodes = FilterEpisodesBySeason(episodes, season.IndexNumber.Value, true);
-
- sortOrder = ItemSortBy.AiredEpisodeOrder;
+ throw new ResourceNotFoundException("No series exists with Id " + request.Id);
}
- else
- {
- episodes = season.RecursiveChildren.OfType<Episode>();
- sortOrder = ItemSortBy.SortName;
- }
+ episodes = series.GetEpisodes(user, request.Season.Value);
}
-
- else if (request.Season.HasValue)
+ else
{
- episodes = FilterEpisodesBySeason(episodes, request.Season.Value, true);
-
- sortOrder = ItemSortBy.AiredEpisodeOrder;
- }
-
- var config = user.Configuration;
+ var season = _libraryManager.GetItemById(new Guid(request.SeasonId)) as Season;
- if (!config.DisplayMissingEpisodes)
- {
- episodes = episodes.Where(i => !i.IsMissingEpisode);
- }
- if (!config.DisplayUnairedEpisodes)
- {
- episodes = episodes.Where(i => !i.IsVirtualUnaired);
+ if (season == null)
+ {
+ throw new ResourceNotFoundException("No season exists with Id " + request.SeasonId);
+ }
+
+ episodes = season.GetEpisodes(user);
}
+ // Filter after the fact in case the ui doesn't want them
if (request.IsMissing.HasValue)
{
var val = request.IsMissing.Value;
episodes = episodes.Where(i => i.IsMissingEpisode == val);
}
+ // Filter after the fact in case the ui doesn't want them
if (request.IsVirtualUnaired.HasValue)
{
var val = request.IsVirtualUnaired.Value;
episodes = episodes.Where(i => i.IsVirtualUnaired == val);
}
- episodes = _libraryManager.Sort(episodes, user, new[] { sortOrder }, SortOrder.Ascending)
- .Cast<Episode>();
-
// This must be the last filter
if (!string.IsNullOrEmpty(request.AdjacentTo))
{
@@ -517,6 +460,8 @@ namespace MediaBrowser.Api
.Cast<Episode>();
}
+ var fields = request.GetItemFields().ToList();
+
var returnItems = episodes.Select(i => _dtoService.GetBaseItemDto(i, fields, user))
.ToArray();
@@ -526,27 +471,5 @@ namespace MediaBrowser.Api
Items = returnItems
};
}
-
- internal static IEnumerable<Episode> FilterEpisodesBySeason(IEnumerable<Episode> episodes, int seasonNumber, bool includeSpecials)
- {
- if (!includeSpecials || seasonNumber < 1)
- {
- return episodes.Where(i => (i.PhysicalSeasonNumber ?? -1) == seasonNumber);
- }
-
- return episodes.Where(i =>
- {
- var episode = i;
-
- if (episode != null)
- {
- var currentSeasonNumber = episode.AiredSeasonNumber;
-
- return currentSeasonNumber.HasValue && currentSeasonNumber.Value == seasonNumber;
- }
-
- return false;
- });
- }
}
}
diff --git a/MediaBrowser.Api/UserLibrary/ItemsService.cs b/MediaBrowser.Api/UserLibrary/ItemsService.cs
index 6fcff545f..8ea225186 100644
--- a/MediaBrowser.Api/UserLibrary/ItemsService.cs
+++ b/MediaBrowser.Api/UserLibrary/ItemsService.cs
@@ -1,6 +1,4 @@
-using System.Globalization;
-using System.IO;
-using MediaBrowser.Controller.Dto;
+using MediaBrowser.Controller.Dto;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.Audio;
using MediaBrowser.Controller.Entities.Movies;
@@ -13,6 +11,8 @@ using MediaBrowser.Model.Querying;
using ServiceStack;
using System;
using System.Collections.Generic;
+using System.Globalization;
+using System.IO;
using System.Linq;
namespace MediaBrowser.Api.UserLibrary
@@ -53,12 +53,6 @@ namespace MediaBrowser.Api.UserLibrary
public string SearchTerm { get; set; }
/// <summary>
- /// The dynamic, localized index function name
- /// </summary>
- /// <value>The index by.</value>
- public string IndexBy { get; set; }
-
- /// <summary>
/// Limit results to items containing specific genres
/// </summary>
/// <value>The genres.</value>
@@ -358,7 +352,7 @@ namespace MediaBrowser.Api.UserLibrary
}
else
{
- items = ((Folder)item).GetChildren(user, true, request.IndexBy);
+ items = ((Folder)item).GetChildren(user, true);
}
if (request.IncludeIndexContainers)
@@ -1041,7 +1035,7 @@ namespace MediaBrowser.Api.UserLibrary
if (request.AiredDuringSeason.HasValue)
{
- items = TvShowsService.FilterEpisodesBySeason(items.OfType<Episode>(), request.AiredDuringSeason.Value, true);
+ items = Series.FilterEpisodesBySeason(items.OfType<Episode>(), request.AiredDuringSeason.Value, true);
}
if (!string.IsNullOrEmpty(request.MinPremiereDate))
diff --git a/MediaBrowser.Api/UserService.cs b/MediaBrowser.Api/UserService.cs
index 3b7808ba7..254fa6ff1 100644
--- a/MediaBrowser.Api/UserService.cs
+++ b/MediaBrowser.Api/UserService.cs
@@ -5,11 +5,11 @@ using MediaBrowser.Model.Dto;
using MediaBrowser.Model.Serialization;
using MediaBrowser.Model.Users;
using ServiceStack;
+using ServiceStack.Text.Controller;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
-using ServiceStack.Text.Controller;
namespace MediaBrowser.Api
{
@@ -269,13 +269,15 @@ namespace MediaBrowser.Api
/// Posts the specified request.
/// </summary>
/// <param name="request">The request.</param>
- public async Task Post(AuthenticateUser request)
+ public object Post(AuthenticateUser request)
{
// No response needed. Will throw an exception on failure.
- await AuthenticateUser(request).ConfigureAwait(false);
+ var result = AuthenticateUser(request).Result;
+
+ return result;
}
- public async Task<object> Post(AuthenticateUserByName request)
+ public object Post(AuthenticateUserByName request)
{
var user = _userManager.Users.FirstOrDefault(i => string.Equals(request.Username, i.Name, StringComparison.OrdinalIgnoreCase));
@@ -284,7 +286,7 @@ namespace MediaBrowser.Api
throw new ArgumentException(string.Format("User {0} not found.", request.Username));
}
- var result = await AuthenticateUser(new AuthenticateUser { Id = user.Id, Password = request.Password }).ConfigureAwait(false);
+ var result = AuthenticateUser(new AuthenticateUser { Id = user.Id, Password = request.Password }).Result;
return ToOptimizedResult(result);
}
diff --git a/MediaBrowser.Common.Implementations/BaseApplicationHost.cs b/MediaBrowser.Common.Implementations/BaseApplicationHost.cs
index bfbbc06ee..929745a94 100644
--- a/MediaBrowser.Common.Implementations/BaseApplicationHost.cs
+++ b/MediaBrowser.Common.Implementations/BaseApplicationHost.cs
@@ -9,6 +9,7 @@ using MediaBrowser.Common.Implementations.Updates;
using MediaBrowser.Common.IO;
using MediaBrowser.Common.Net;
using MediaBrowser.Common.Plugins;
+using MediaBrowser.Common.Progress;
using MediaBrowser.Common.ScheduledTasks;
using MediaBrowser.Common.Security;
using MediaBrowser.Common.Updates;
@@ -180,42 +181,50 @@ namespace MediaBrowser.Common.Implementations
/// Inits this instance.
/// </summary>
/// <returns>Task.</returns>
- public virtual async Task Init()
+ public virtual async Task Init(IProgress<double> progress)
{
- // https://github.com/ServiceStack/ServiceStack/blob/master/tests/ServiceStack.WebHost.IntegrationTests/Web.config#L4
- Licensing.RegisterLicense("1001-e1JlZjoxMDAxLE5hbWU6VGVzdCBCdXNpbmVzcyxUeXBlOkJ1c2luZXNzLEhhc2g6UHVNTVRPclhvT2ZIbjQ5MG5LZE1mUTd5RUMzQnBucTFEbTE3TDczVEF4QUNMT1FhNXJMOWkzVjFGL2ZkVTE3Q2pDNENqTkQyUktRWmhvUVBhYTBiekJGUUZ3ZE5aZHFDYm9hL3lydGlwUHI5K1JsaTBYbzNsUC85cjVJNHE5QVhldDN6QkE4aTlvdldrdTgyTk1relY2eis2dFFqTThYN2lmc0JveHgycFdjPSxFeHBpcnk6MjAxMy0wMS0wMX0=");
-
+ try
+ {
+ // https://github.com/ServiceStack/ServiceStack/blob/master/tests/ServiceStack.WebHost.IntegrationTests/Web.config#L4
+ Licensing.RegisterLicense("1001-e1JlZjoxMDAxLE5hbWU6VGVzdCBCdXNpbmVzcyxUeXBlOkJ1c2luZXNzLEhhc2g6UHVNTVRPclhvT2ZIbjQ5MG5LZE1mUTd5RUMzQnBucTFEbTE3TDczVEF4QUNMT1FhNXJMOWkzVjFGL2ZkVTE3Q2pDNENqTkQyUktRWmhvUVBhYTBiekJGUUZ3ZE5aZHFDYm9hL3lydGlwUHI5K1JsaTBYbzNsUC85cjVJNHE5QVhldDN6QkE4aTlvdldrdTgyTk1relY2eis2dFFqTThYN2lmc0JveHgycFdjPSxFeHBpcnk6MjAxMy0wMS0wMX0=");
+ }
+ catch
+ {
+ // Failing under mono
+ }
+ progress.Report(1);
+
JsonSerializer = CreateJsonSerializer();
IsFirstRun = !ConfigurationManager.CommonConfiguration.IsStartupWizardCompleted;
+ progress.Report(2);
Logger = LogManager.GetLogger("App");
LogManager.LogSeverity = ConfigurationManager.CommonConfiguration.EnableDebugLevelLogging
? LogSeverity.Debug
: LogSeverity.Info;
-
- OnLoggerLoaded();
+ progress.Report(3);
DiscoverTypes();
+ progress.Report(14);
Logger.Info("Version {0} initializing", ApplicationVersion);
SetHttpLimit();
+ progress.Report(15);
- await RegisterResources().ConfigureAwait(false);
+ var innerProgress = new ActionableProgress<double>();
+ innerProgress.RegisterAction(p => progress.Report((.8 * p) + 15));
+
+ await RegisterResources(innerProgress).ConfigureAwait(false);
FindParts();
+ progress.Report(95);
await InstallIsoMounters(CancellationToken.None).ConfigureAwait(false);
- }
-
- /// <summary>
- /// Called when [logger loaded].
- /// </summary>
- protected virtual void OnLoggerLoaded()
- {
+ progress.Report(100);
}
protected virtual IJsonSerializer CreateJsonSerializer()
@@ -341,7 +350,7 @@ namespace MediaBrowser.Common.Implementations
/// Registers resources that classes will depend on
/// </summary>
/// <returns>Task.</returns>
- protected virtual Task RegisterResources()
+ protected virtual Task RegisterResources(IProgress<double> progress)
{
return Task.Run(() =>
{
diff --git a/MediaBrowser.Common.Implementations/BaseApplicationPaths.cs b/MediaBrowser.Common.Implementations/BaseApplicationPaths.cs
index 6acaac5c9..668b1395d 100644
--- a/MediaBrowser.Common.Implementations/BaseApplicationPaths.cs
+++ b/MediaBrowser.Common.Implementations/BaseApplicationPaths.cs
@@ -2,7 +2,6 @@
using System;
using System.Configuration;
using System.IO;
-using System.Reflection;
namespace MediaBrowser.Common.Implementations
{
@@ -82,10 +81,6 @@ namespace MediaBrowser.Common.Implementations
}
/// <summary>
- /// The _image cache path
- /// </summary>
- private string _imageCachePath;
- /// <summary>
/// Gets the image cache path.
/// </summary>
/// <value>The image cache path.</value>
@@ -93,22 +88,11 @@ namespace MediaBrowser.Common.Implementations
{
get
{
- if (_imageCachePath == null)
- {
- _imageCachePath = Path.Combine(CachePath, "images");
-
- Directory.CreateDirectory(_imageCachePath);
- }
-
- return _imageCachePath;
+ return Path.Combine(CachePath, "images");
}
}
/// <summary>
- /// The _plugins path
- /// </summary>
- private string _pluginsPath;
- /// <summary>
/// Gets the path to the plugin directory
/// </summary>
/// <value>The plugins path.</value>
@@ -116,21 +100,11 @@ namespace MediaBrowser.Common.Implementations
{
get
{
- if (_pluginsPath == null)
- {
- _pluginsPath = Path.Combine(ProgramDataPath, "plugins");
- Directory.CreateDirectory(_pluginsPath);
- }
-
- return _pluginsPath;
+ return Path.Combine(ProgramDataPath, "plugins");
}
}
/// <summary>
- /// The _plugin configurations path
- /// </summary>
- private string _pluginConfigurationsPath;
- /// <summary>
/// Gets the path to the plugin configurations directory
/// </summary>
/// <value>The plugin configurations path.</value>
@@ -138,17 +112,10 @@ namespace MediaBrowser.Common.Implementations
{
get
{
- if (_pluginConfigurationsPath == null)
- {
- _pluginConfigurationsPath = Path.Combine(PluginsPath, "configurations");
- Directory.CreateDirectory(_pluginConfigurationsPath);
- }
-
- return _pluginConfigurationsPath;
+ return Path.Combine(PluginsPath, "configurations");
}
}
- private string _tempUpdatePath;
/// <summary>
/// Gets the path to where temporary update files will be stored
/// </summary>
@@ -157,21 +124,11 @@ namespace MediaBrowser.Common.Implementations
{
get
{
- if (_tempUpdatePath == null)
- {
- _tempUpdatePath = Path.Combine(ProgramDataPath, "updates");
- Directory.CreateDirectory(_tempUpdatePath);
- }
-
- return _tempUpdatePath;
+ return Path.Combine(ProgramDataPath, "updates");
}
}
/// <summary>
- /// The _log directory path
- /// </summary>
- private string _logDirectoryPath;
- /// <summary>
/// Gets the path to the log directory
/// </summary>
/// <value>The log directory path.</value>
@@ -179,20 +136,11 @@ namespace MediaBrowser.Common.Implementations
{
get
{
- if (_logDirectoryPath == null)
- {
- _logDirectoryPath = Path.Combine(ProgramDataPath, "logs");
- Directory.CreateDirectory(_logDirectoryPath);
- }
- return _logDirectoryPath;
+ return Path.Combine(ProgramDataPath, "logs");
}
}
/// <summary>
- /// The _configuration directory path
- /// </summary>
- private string _configurationDirectoryPath;
- /// <summary>
/// Gets the path to the application configuration root directory
/// </summary>
/// <value>The configuration directory path.</value>
@@ -200,12 +148,7 @@ namespace MediaBrowser.Common.Implementations
{
get
{
- if (_configurationDirectoryPath == null)
- {
- _configurationDirectoryPath = Path.Combine(ProgramDataPath, "config");
- Directory.CreateDirectory(_configurationDirectoryPath);
- }
- return _configurationDirectoryPath;
+ return Path.Combine(ProgramDataPath, "config");
}
}
@@ -233,7 +176,7 @@ namespace MediaBrowser.Common.Implementations
{
get
{
- if (_cachePath == null)
+ if (string.IsNullOrEmpty(_cachePath))
{
_cachePath = Path.Combine(ProgramDataPath, "cache");
@@ -242,13 +185,13 @@ namespace MediaBrowser.Common.Implementations
return _cachePath;
}
+ set
+ {
+ _cachePath = value;
+ }
}
/// <summary>
- /// The _temp directory
- /// </summary>
- private string _tempDirectory;
- /// <summary>
/// Gets the folder path to the temp directory within the cache folder
/// </summary>
/// <value>The temp directory.</value>
@@ -256,14 +199,7 @@ namespace MediaBrowser.Common.Implementations
{
get
{
- if (_tempDirectory == null)
- {
- _tempDirectory = Path.Combine(CachePath, "temp");
-
- Directory.CreateDirectory(_tempDirectory);
- }
-
- return _tempDirectory;
+ return Path.Combine(CachePath, "temp");
}
}
@@ -273,7 +209,7 @@ namespace MediaBrowser.Common.Implementations
/// <returns>System.String.</returns>
private string GetProgramDataPath()
{
- var programDataPath = _useDebugPath ? ConfigurationManager.AppSettings["DebugProgramDataPath"] : Path.Combine(ConfigurationManager.AppSettings["ReleaseProgramDataPath"], ConfigurationManager.AppSettings["ProgramDataFolderName"]);
+ var programDataPath = _useDebugPath ? ConfigurationManager.AppSettings["DebugProgramDataPath"] : ConfigurationManager.AppSettings["ReleaseProgramDataPath"];
programDataPath = programDataPath.Replace("%ApplicationData%", Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData));
diff --git a/MediaBrowser.Common.Implementations/Configuration/BaseConfigurationManager.cs b/MediaBrowser.Common.Implementations/Configuration/BaseConfigurationManager.cs
index 317a288ff..8c4840ea7 100644
--- a/MediaBrowser.Common.Implementations/Configuration/BaseConfigurationManager.cs
+++ b/MediaBrowser.Common.Implementations/Configuration/BaseConfigurationManager.cs
@@ -1,4 +1,5 @@
-using MediaBrowser.Common.Configuration;
+using System.IO;
+using MediaBrowser.Common.Configuration;
using MediaBrowser.Common.Events;
using MediaBrowser.Model.Configuration;
using MediaBrowser.Model.Logging;
@@ -84,6 +85,8 @@ namespace MediaBrowser.Common.Implementations.Configuration
CommonApplicationPaths = applicationPaths;
XmlSerializer = xmlSerializer;
Logger = logManager.GetLogger(GetType().Name);
+
+ UpdateCachePath();
}
/// <summary>
@@ -96,9 +99,13 @@ namespace MediaBrowser.Common.Implementations.Configuration
/// </summary>
public void SaveConfiguration()
{
+ var path = CommonApplicationPaths.SystemConfigurationFilePath;
+
+ Directory.CreateDirectory(Path.GetDirectoryName(path));
+
lock (_configurationSaveLock)
{
- XmlSerializer.SerializeToFile(CommonConfiguration, CommonApplicationPaths.SystemConfigurationFilePath);
+ XmlSerializer.SerializeToFile(CommonConfiguration, path);
}
OnConfigurationUpdated();
@@ -109,6 +116,8 @@ namespace MediaBrowser.Common.Implementations.Configuration
/// </summary>
protected virtual void OnConfigurationUpdated()
{
+ UpdateCachePath();
+
EventHelper.QueueEventIfNotNull(ConfigurationUpdated, this, EventArgs.Empty, Logger);
}
@@ -124,8 +133,40 @@ namespace MediaBrowser.Common.Implementations.Configuration
throw new ArgumentNullException("newConfiguration");
}
+ ValidateCachePath(newConfiguration);
+
CommonConfiguration = newConfiguration;
SaveConfiguration();
}
+
+ /// <summary>
+ /// Updates the items by name path.
+ /// </summary>
+ private void UpdateCachePath()
+ {
+ ((BaseApplicationPaths)CommonApplicationPaths).CachePath = string.IsNullOrEmpty(CommonConfiguration.CachePath) ?
+ null :
+ CommonConfiguration.CachePath;
+ }
+
+ /// <summary>
+ /// Replaces the cache path.
+ /// </summary>
+ /// <param name="newConfig">The new configuration.</param>
+ /// <exception cref="System.IO.DirectoryNotFoundException"></exception>
+ private void ValidateCachePath(BaseApplicationConfiguration newConfig)
+ {
+ var newPath = newConfig.CachePath;
+
+ if (!string.IsNullOrWhiteSpace(newPath)
+ && !string.Equals(CommonConfiguration.CachePath ?? string.Empty, newPath))
+ {
+ // Validate
+ if (!Directory.Exists(newPath))
+ {
+ throw new DirectoryNotFoundException(string.Format("{0} does not exist.", newPath));
+ }
+ }
+ }
}
}
diff --git a/MediaBrowser.Common.Implementations/HttpClientManager/HttpClientManager.cs b/MediaBrowser.Common.Implementations/HttpClientManager/HttpClientManager.cs
index 19091885d..a5b241b4b 100644
--- a/MediaBrowser.Common.Implementations/HttpClientManager/HttpClientManager.cs
+++ b/MediaBrowser.Common.Implementations/HttpClientManager/HttpClientManager.cs
@@ -64,6 +64,9 @@ namespace MediaBrowser.Common.Implementations.HttpClientManager
_logger = logger;
_fileSystem = fileSystem;
_appPaths = appPaths;
+
+ // http://stackoverflow.com/questions/566437/http-post-returns-the-error-417-expectation-failed-c
+ ServicePointManager.Expect100Continue = false;
}
/// <summary>
@@ -132,7 +135,7 @@ namespace MediaBrowser.Common.Implementations.HttpClientManager
#if __MonoCS__
return GetMonoRequest(options, method, enableHttpCompression);
#endif
-
+
var request = HttpWebRequest.CreateHttp(options.Url);
if (!string.IsNullOrEmpty(options.AcceptHeader))
@@ -172,9 +175,64 @@ namespace MediaBrowser.Common.Implementations.HttpClientManager
/// <returns>Task{HttpResponseInfo}.</returns>
/// <exception cref="HttpException">
/// </exception>
- public async Task<HttpResponseInfo> GetResponse(HttpRequestOptions options)
+ public Task<HttpResponseInfo> GetResponse(HttpRequestOptions options)
+ {
+ return SendAsync(options, "GET");
+ }
+
+ /// <summary>
+ /// Performs a GET request and returns the resulting stream
+ /// </summary>
+ /// <param name="options">The options.</param>
+ /// <returns>Task{Stream}.</returns>
+ /// <exception cref="HttpException"></exception>
+ /// <exception cref="MediaBrowser.Model.Net.HttpException"></exception>
+ public async Task<Stream> Get(HttpRequestOptions options)
+ {
+ var response = await GetResponse(options).ConfigureAwait(false);
+
+ return response.Content;
+ }
+
+ /// <summary>
+ /// Performs a GET request and returns the resulting stream
+ /// </summary>
+ /// <param name="url">The URL.</param>
+ /// <param name="resourcePool">The resource pool.</param>
+ /// <param name="cancellationToken">The cancellation token.</param>
+ /// <returns>Task{Stream}.</returns>
+ public Task<Stream> Get(string url, SemaphoreSlim resourcePool, CancellationToken cancellationToken)
+ {
+ return Get(new HttpRequestOptions
+ {
+ Url = url,
+ ResourcePool = resourcePool,
+ CancellationToken = cancellationToken,
+ });
+ }
+
+ /// <summary>
+ /// Gets the specified URL.
+ /// </summary>
+ /// <param name="url">The URL.</param>
+ /// <param name="cancellationToken">The cancellation token.</param>
+ /// <returns>Task{Stream}.</returns>
+ public Task<Stream> Get(string url, CancellationToken cancellationToken)
+ {
+ return Get(url, null, cancellationToken);
+ }
+
+ /// <summary>
+ /// send as an asynchronous operation.
+ /// </summary>
+ /// <param name="options">The options.</param>
+ /// <param name="httpMethod">The HTTP method.</param>
+ /// <returns>Task{HttpResponseInfo}.</returns>
+ /// <exception cref="HttpException">
+ /// </exception>
+ private async Task<HttpResponseInfo> SendAsync(HttpRequestOptions options, string httpMethod)
{
- ValidateParams(options.Url, options.CancellationToken);
+ ValidateParams(options);
options.CancellationToken.ThrowIfCancellationRequested();
@@ -185,7 +243,17 @@ namespace MediaBrowser.Common.Implementations.HttpClientManager
throw new HttpException(string.Format("Cancelling connection to {0} due to a previous timeout.", options.Url)) { IsTimedOut = true };
}
- var httpWebRequest = GetRequest(options, "GET", options.EnableHttpCompression);
+ var httpWebRequest = GetRequest(options, httpMethod, options.EnableHttpCompression);
+
+ if (!string.IsNullOrEmpty(options.RequestContent) || string.Equals(httpMethod, "post", StringComparison.OrdinalIgnoreCase))
+ {
+ var content = options.RequestContent ?? string.Empty;
+ var bytes = Encoding.UTF8.GetBytes(content);
+
+ httpWebRequest.ContentType = options.RequestContentType ?? "application/x-www-form-urlencoded";
+ httpWebRequest.ContentLength = bytes.Length;
+ httpWebRequest.GetRequestStream().Write(bytes, 0, bytes.Length);
+ }
if (options.ResourcePool != null)
{
@@ -202,7 +270,7 @@ namespace MediaBrowser.Common.Implementations.HttpClientManager
throw new HttpException(string.Format("Connection to {0} timed out", options.Url)) { IsTimedOut = true };
}
- _logger.Info("HttpClientManager.GET url: {0}", options.Url);
+ _logger.Info("HttpClientManager {0}: {1}", httpMethod.ToUpper(), options.Url);
try
{
@@ -275,46 +343,9 @@ namespace MediaBrowser.Common.Implementations.HttpClientManager
}
}
- /// <summary>
- /// Performs a GET request and returns the resulting stream
- /// </summary>
- /// <param name="options">The options.</param>
- /// <returns>Task{Stream}.</returns>
- /// <exception cref="HttpException"></exception>
- /// <exception cref="MediaBrowser.Model.Net.HttpException"></exception>
- public async Task<Stream> Get(HttpRequestOptions options)
+ public Task<HttpResponseInfo> Post(HttpRequestOptions options)
{
- var response = await GetResponse(options).ConfigureAwait(false);
-
- return response.Content;
- }
-
- /// <summary>
- /// Performs a GET request and returns the resulting stream
- /// </summary>
- /// <param name="url">The URL.</param>
- /// <param name="resourcePool">The resource pool.</param>
- /// <param name="cancellationToken">The cancellation token.</param>
- /// <returns>Task{Stream}.</returns>
- public Task<Stream> Get(string url, SemaphoreSlim resourcePool, CancellationToken cancellationToken)
- {
- return Get(new HttpRequestOptions
- {
- Url = url,
- ResourcePool = resourcePool,
- CancellationToken = cancellationToken,
- });
- }
-
- /// <summary>
- /// Gets the specified URL.
- /// </summary>
- /// <param name="url">The URL.</param>
- /// <param name="cancellationToken">The cancellation token.</param>
- /// <returns>Task{Stream}.</returns>
- public Task<Stream> Get(string url, CancellationToken cancellationToken)
- {
- return Get(url, null, cancellationToken);
+ return SendAsync(options, "POST");
}
/// <summary>
@@ -329,82 +360,15 @@ namespace MediaBrowser.Common.Implementations.HttpClientManager
/// <exception cref="MediaBrowser.Model.Net.HttpException"></exception>
public async Task<Stream> Post(HttpRequestOptions options, Dictionary<string, string> postData)
{
- ValidateParams(options.Url, options.CancellationToken);
-
- options.CancellationToken.ThrowIfCancellationRequested();
-
- var httpWebRequest = GetRequest(options, "POST", options.EnableHttpCompression);
-
var strings = postData.Keys.Select(key => string.Format("{0}={1}", key, postData[key]));
var postContent = string.Join("&", strings.ToArray());
- var bytes = Encoding.UTF8.GetBytes(postContent);
-
- httpWebRequest.ContentType = "application/x-www-form-urlencoded";
- httpWebRequest.ContentLength = bytes.Length;
- httpWebRequest.GetRequestStream().Write(bytes, 0, bytes.Length);
-
- if (options.ResourcePool != null)
- {
- await options.ResourcePool.WaitAsync(options.CancellationToken).ConfigureAwait(false);
- }
-
- _logger.Info("HttpClientManager.POST url: {0}", options.Url);
- try
- {
- options.CancellationToken.ThrowIfCancellationRequested();
+ options.RequestContent = postContent;
+ options.RequestContentType = "application/x-www-form-urlencoded";
- using (var response = await httpWebRequest.GetResponseAsync().ConfigureAwait(false))
- {
- var httpResponse = (HttpWebResponse)response;
-
- EnsureSuccessStatusCode(httpResponse);
-
- options.CancellationToken.ThrowIfCancellationRequested();
-
- using (var stream = httpResponse.GetResponseStream())
- {
- var memoryStream = new MemoryStream();
-
- await stream.CopyToAsync(memoryStream).ConfigureAwait(false);
-
- memoryStream.Position = 0;
+ var response = await Post(options).ConfigureAwait(false);
- return memoryStream;
- }
- }
- }
- catch (OperationCanceledException ex)
- {
- var exception = GetCancellationException(options.Url, options.CancellationToken, ex);
-
- throw exception;
- }
- catch (HttpRequestException ex)
- {
- _logger.ErrorException("Error getting response from " + options.Url, ex);
-
- throw new HttpException(ex.Message, ex);
- }
- catch (WebException ex)
- {
- _logger.ErrorException("Error getting response from " + options.Url, ex);
-
- throw new HttpException(ex.Message, ex);
- }
- catch (Exception ex)
- {
- _logger.ErrorException("Error getting response from " + options.Url, ex);
-
- throw;
- }
- finally
- {
- if (options.ResourcePool != null)
- {
- options.ResourcePool.Release();
- }
- }
+ return response.Content;
}
/// <summary>
@@ -443,7 +407,9 @@ namespace MediaBrowser.Common.Implementations.HttpClientManager
public async Task<HttpResponseInfo> GetTempFileResponse(HttpRequestOptions options)
{
- ValidateParams(options.Url, options.CancellationToken);
+ ValidateParams(options);
+
+ Directory.CreateDirectory(_appPaths.TempDirectory);
var tempFile = Path.Combine(_appPaths.TempDirectory, Guid.NewGuid() + ".tmp");
@@ -590,7 +556,7 @@ namespace MediaBrowser.Common.Implementations.HttpClientManager
{
return new HttpException(ex.Message, ex);
}
-
+
return ex;
}
@@ -606,17 +572,11 @@ namespace MediaBrowser.Common.Implementations.HttpClientManager
}
}
- /// <summary>
- /// Validates the params.
- /// </summary>
- /// <param name="url">The URL.</param>
- /// <param name="cancellationToken">The cancellation token.</param>
- /// <exception cref="System.ArgumentNullException">url</exception>
- private void ValidateParams(string url, CancellationToken cancellationToken)
+ private void ValidateParams(HttpRequestOptions options)
{
- if (string.IsNullOrEmpty(url))
+ if (string.IsNullOrEmpty(options.Url))
{
- throw new ArgumentNullException("url");
+ throw new ArgumentNullException("options");
}
}
diff --git a/MediaBrowser.Common.Implementations/IO/CommonFileSystem.cs b/MediaBrowser.Common.Implementations/IO/CommonFileSystem.cs
index ed9baf3b2..616981d50 100644
--- a/MediaBrowser.Common.Implementations/IO/CommonFileSystem.cs
+++ b/MediaBrowser.Common.Implementations/IO/CommonFileSystem.cs
@@ -216,6 +216,48 @@ namespace MediaBrowser.Common.Implementations.IO
return new FileStream(path, mode, access, share);
}
+
+ /// <summary>
+ /// Swaps the files.
+ /// </summary>
+ /// <param name="file1">The file1.</param>
+ /// <param name="file2">The file2.</param>
+ public void SwapFiles(string file1, string file2)
+ {
+ var temp1 = Path.GetTempFileName();
+ var temp2 = Path.GetTempFileName();
+
+ // Copying over will fail against hidden files
+ RemoveHiddenAttribute(file1);
+ RemoveHiddenAttribute(file2);
+
+ File.Copy(file1, temp1, true);
+ File.Copy(file2, temp2, true);
+
+ File.Copy(temp1, file2, true);
+ File.Copy(temp2, file1, true);
+
+ File.Delete(temp1);
+ File.Delete(temp2);
+ }
+
+ /// <summary>
+ /// Removes the hidden attribute.
+ /// </summary>
+ /// <param name="path">The path.</param>
+ private void RemoveHiddenAttribute(string path)
+ {
+ var currentFile = new FileInfo(path);
+
+ // This will fail if the file is hidden
+ if (currentFile.Exists)
+ {
+ if ((currentFile.Attributes & FileAttributes.Hidden) == FileAttributes.Hidden)
+ {
+ currentFile.Attributes &= ~FileAttributes.Hidden;
+ }
+ }
+ }
}
/// <summary>
diff --git a/MediaBrowser.Common.Implementations/Logging/NlogManager.cs b/MediaBrowser.Common.Implementations/Logging/NlogManager.cs
index 56f2b5e29..fb7fd1698 100644
--- a/MediaBrowser.Common.Implementations/Logging/NlogManager.cs
+++ b/MediaBrowser.Common.Implementations/Logging/NlogManager.cs
@@ -186,6 +186,8 @@ namespace MediaBrowser.Common.Implementations.Logging
{
LogFilePath = Path.Combine(LogDirectory, LogFilePrefix + "-" + decimal.Round(DateTime.Now.Ticks / 10000000) + ".log");
+ Directory.CreateDirectory(Path.GetDirectoryName(LogFilePath));
+
AddFileTarget(LogFilePath, level);
LogSeverity = level;
diff --git a/MediaBrowser.Common.Implementations/MediaBrowser.Common.Implementations.csproj b/MediaBrowser.Common.Implementations/MediaBrowser.Common.Implementations.csproj
index 7d75cfd3d..66567fc16 100644
--- a/MediaBrowser.Common.Implementations/MediaBrowser.Common.Implementations.csproj
+++ b/MediaBrowser.Common.Implementations/MediaBrowser.Common.Implementations.csproj
@@ -9,12 +9,12 @@
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>MediaBrowser.Common.Implementations</RootNamespace>
<AssemblyName>MediaBrowser.Common.Implementations</AssemblyName>
- <TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<SolutionDir Condition="$(SolutionDir) == '' Or $(SolutionDir) == '*Undefined*'">..\</SolutionDir>
<RestorePackages>true</RestorePackages>
<ProductVersion>10.0.0</ProductVersion>
<SchemaVersion>2.0</SchemaVersion>
+ <TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
@@ -24,6 +24,7 @@
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
+ <TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
@@ -32,18 +33,23 @@
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
+ <TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
+ </PropertyGroup>
+ <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release Mono|AnyCPU' ">
+ <DebugType>pdbonly</DebugType>
+ <Optimize>true</Optimize>
+ <OutputPath>bin\Release Mono\</OutputPath>
+ <DefineConstants>TRACE</DefineConstants>
+ <ErrorReport>prompt</ErrorReport>
+ <WarningLevel>4</WarningLevel>
+ <TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
</PropertyGroup>
<PropertyGroup>
<RunPostBuildEvent>Always</RunPostBuildEvent>
</PropertyGroup>
<ItemGroup>
- <Reference Include="ServiceStack.Text, Version=3.9.70.0, Culture=neutral, processorArchitecture=MSIL">
- <SpecificVersion>False</SpecificVersion>
- <HintPath>..\ThirdParty\ServiceStack\ServiceStack.Text.dll</HintPath>
- </Reference>
- <Reference Include="SharpCompress, Version=0.10.2.0, Culture=neutral, PublicKeyToken=beaf6f427e128133, processorArchitecture=MSIL">
- <SpecificVersion>False</SpecificVersion>
- <HintPath>..\packages\sharpcompress.0.10.2\lib\net40\SharpCompress.dll</HintPath>
+ <Reference Include="SimpleInjector.Diagnostics">
+ <HintPath>..\packages\SimpleInjector.2.4.0\lib\net45\SimpleInjector.Diagnostics.dll</HintPath>
</Reference>
<Reference Include="System" />
<Reference Include="System.Configuration" />
@@ -55,8 +61,14 @@
<Reference Include="NLog">
<HintPath>..\packages\NLog.2.1.0\lib\net45\NLog.dll</HintPath>
</Reference>
+ <Reference Include="SharpCompress">
+ <HintPath>..\packages\sharpcompress.0.10.2\lib\net40\SharpCompress.dll</HintPath>
+ </Reference>
+ <Reference Include="ServiceStack.Text">
+ <HintPath>..\ThirdParty\ServiceStack.Text\ServiceStack.Text.dll</HintPath>
+ </Reference>
<Reference Include="SimpleInjector">
- <HintPath>..\packages\SimpleInjector.2.3.6\lib\net40-client\SimpleInjector.dll</HintPath>
+ <HintPath>..\packages\SimpleInjector.2.4.0\lib\net45\SimpleInjector.dll</HintPath>
</Reference>
</ItemGroup>
<ItemGroup>
@@ -106,9 +118,9 @@
</ItemGroup>
<ItemGroup />
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
- <Import Project="$(SolutionDir)\.nuget\nuget.targets" />
+ <Import Project="$(SolutionDir)\.nuget\nuget.targets" Condition=" '$(ConfigurationName)' != 'Release Mono' " />
<PropertyGroup>
- <PostBuildEvent>if $(ConfigurationName) == Release (
+ <PostBuildEvent Condition=" '$(ConfigurationName)' != 'Release Mono' ">if '$(ConfigurationName)' == 'Release' (
xcopy "$(TargetPath)" "$(SolutionDir)\Nuget\dlls\" /y /d /r /i
)</PostBuildEvent>
</PropertyGroup>
diff --git a/MediaBrowser.Common.Implementations/ScheduledTasks/ScheduledTaskWorker.cs b/MediaBrowser.Common.Implementations/ScheduledTasks/ScheduledTaskWorker.cs
index 2406d0470..477dc4aee 100644
--- a/MediaBrowser.Common.Implementations/ScheduledTasks/ScheduledTaskWorker.cs
+++ b/MediaBrowser.Common.Implementations/ScheduledTasks/ScheduledTaskWorker.cs
@@ -121,7 +121,7 @@ namespace MediaBrowser.Common.Implementations.ScheduledTasks
{
LazyInitializer.EnsureInitialized(ref _lastExecutionResult, ref _lastExecutionResultinitialized, ref _lastExecutionResultSyncLock, () =>
{
- var path = GetHistoryFilePath(false);
+ var path = GetHistoryFilePath();
try
{
@@ -432,43 +432,28 @@ namespace MediaBrowser.Common.Implementations.ScheduledTasks
/// <summary>
/// Gets the scheduled tasks configuration directory.
/// </summary>
- /// <param name="create">if set to <c>true</c> [create].</param>
/// <returns>System.String.</returns>
- private string GetScheduledTasksConfigurationDirectory(bool create)
+ private string GetScheduledTasksConfigurationDirectory()
{
- var path = Path.Combine(ApplicationPaths.ConfigurationDirectoryPath, "ScheduledTasks");
-
- if (create)
- {
- Directory.CreateDirectory(path);
- }
-
- return path;
+ return Path.Combine(ApplicationPaths.ConfigurationDirectoryPath, "ScheduledTasks");
}
/// <summary>
/// Gets the scheduled tasks data directory.
/// </summary>
- /// <param name="create">if set to <c>true</c> [create].</param>
/// <returns>System.String.</returns>
- private string GetScheduledTasksDataDirectory(bool create)
+ private string GetScheduledTasksDataDirectory()
{
- var path = Path.Combine(ApplicationPaths.DataPath, "ScheduledTasks");
-
- if (create)
- {
- Directory.CreateDirectory(path);
- }
- return path;
+ return Path.Combine(ApplicationPaths.DataPath, "ScheduledTasks");
}
/// <summary>
/// Gets the history file path.
/// </summary>
/// <value>The history file path.</value>
- private string GetHistoryFilePath(bool createDirectory)
+ private string GetHistoryFilePath()
{
- return Path.Combine(GetScheduledTasksDataDirectory(createDirectory), Id + ".js");
+ return Path.Combine(GetScheduledTasksDataDirectory(), Id + ".js");
}
/// <summary>
@@ -477,7 +462,7 @@ namespace MediaBrowser.Common.Implementations.ScheduledTasks
/// <returns>System.String.</returns>
private string GetConfigurationFilePath()
{
- return Path.Combine(GetScheduledTasksConfigurationDirectory(false), Id + ".js");
+ return Path.Combine(GetScheduledTasksConfigurationDirectory(), Id + ".js");
}
/// <summary>
@@ -512,9 +497,7 @@ namespace MediaBrowser.Common.Implementations.ScheduledTasks
{
var path = GetConfigurationFilePath();
- var parentPath = Path.GetDirectoryName(path);
-
- Directory.CreateDirectory(parentPath);
+ Directory.CreateDirectory(Path.GetDirectoryName(path));
JsonSerializer.SerializeToFile(triggers.Select(ScheduledTaskHelpers.GetTriggerInfo), path);
}
@@ -545,7 +528,10 @@ namespace MediaBrowser.Common.Implementations.ScheduledTasks
result.ErrorMessage = ex.Message;
}
- JsonSerializer.SerializeToFile(result, GetHistoryFilePath(true));
+ var path = GetHistoryFilePath();
+ Directory.CreateDirectory(Path.GetDirectoryName(path));
+
+ JsonSerializer.SerializeToFile(result, path);
LastExecutionResult = result;
diff --git a/MediaBrowser.Common.Implementations/ScheduledTasks/Tasks/DeleteCacheFileTask.cs b/MediaBrowser.Common.Implementations/ScheduledTasks/Tasks/DeleteCacheFileTask.cs
index 6d886bc69..d02984bad 100644
--- a/MediaBrowser.Common.Implementations/ScheduledTasks/Tasks/DeleteCacheFileTask.cs
+++ b/MediaBrowser.Common.Implementations/ScheduledTasks/Tasks/DeleteCacheFileTask.cs
@@ -14,7 +14,7 @@ namespace MediaBrowser.Common.Implementations.ScheduledTasks.Tasks
/// <summary>
/// Deletes old cache files
/// </summary>
- public class DeleteCacheFileTask : IScheduledTask
+ public class DeleteCacheFileTask : IScheduledTask, IConfigurableScheduledTask
{
/// <summary>
/// Gets or sets the application paths.
@@ -160,5 +160,14 @@ namespace MediaBrowser.Common.Implementations.ScheduledTasks.Tasks
return "Maintenance";
}
}
+
+ /// <summary>
+ /// Gets a value indicating whether this instance is hidden.
+ /// </summary>
+ /// <value><c>true</c> if this instance is hidden; otherwise, <c>false</c>.</value>
+ public bool IsHidden
+ {
+ get { return true; }
+ }
}
}
diff --git a/MediaBrowser.Common.Implementations/ScheduledTasks/Tasks/DeleteLogFileTask.cs b/MediaBrowser.Common.Implementations/ScheduledTasks/Tasks/DeleteLogFileTask.cs
index 7c7833ae6..e5cb7aa10 100644
--- a/MediaBrowser.Common.Implementations/ScheduledTasks/Tasks/DeleteLogFileTask.cs
+++ b/MediaBrowser.Common.Implementations/ScheduledTasks/Tasks/DeleteLogFileTask.cs
@@ -13,7 +13,7 @@ namespace MediaBrowser.Common.Implementations.ScheduledTasks.Tasks
/// <summary>
/// Deletes old log files
/// </summary>
- public class DeleteLogFileTask : IScheduledTask
+ public class DeleteLogFileTask : IScheduledTask, IConfigurableScheduledTask
{
/// <summary>
/// Gets or sets the configuration manager.
@@ -115,5 +115,14 @@ namespace MediaBrowser.Common.Implementations.ScheduledTasks.Tasks
return "Maintenance";
}
}
+
+ /// <summary>
+ /// Gets a value indicating whether this instance is hidden.
+ /// </summary>
+ /// <value><c>true</c> if this instance is hidden; otherwise, <c>false</c>.</value>
+ public bool IsHidden
+ {
+ get { return true; }
+ }
}
}
diff --git a/MediaBrowser.Common.Implementations/ScheduledTasks/Tasks/ReloadLoggerTask.cs b/MediaBrowser.Common.Implementations/ScheduledTasks/Tasks/ReloadLoggerTask.cs
index 00928255c..9a65046bf 100644
--- a/MediaBrowser.Common.Implementations/ScheduledTasks/Tasks/ReloadLoggerTask.cs
+++ b/MediaBrowser.Common.Implementations/ScheduledTasks/Tasks/ReloadLoggerTask.cs
@@ -11,7 +11,7 @@ namespace MediaBrowser.Common.Implementations.ScheduledTasks.Tasks
/// <summary>
/// Class ReloadLoggerFileTask
/// </summary>
- public class ReloadLoggerFileTask : IScheduledTask
+ public class ReloadLoggerFileTask : IScheduledTask, IConfigurableScheduledTask
{
/// <summary>
/// Gets or sets the log manager.
@@ -91,5 +91,10 @@ namespace MediaBrowser.Common.Implementations.ScheduledTasks.Tasks
{
get { return "Application"; }
}
+
+ public bool IsHidden
+ {
+ get { return true; }
+ }
}
}
diff --git a/MediaBrowser.Common.Implementations/Security/MBLicenseFile.cs b/MediaBrowser.Common.Implementations/Security/MBLicenseFile.cs
index 163a368bf..c5d5f28d6 100644
--- a/MediaBrowser.Common.Implementations/Security/MBLicenseFile.cs
+++ b/MediaBrowser.Common.Implementations/Security/MBLicenseFile.cs
@@ -11,7 +11,6 @@ namespace MediaBrowser.Common.Implementations.Security
{
private readonly IApplicationPaths _appPaths;
- private readonly string _filename;
public string RegKey
{
get { return _regKey; }
@@ -26,6 +25,14 @@ namespace MediaBrowser.Common.Implementations.Security
}
}
+ private string Filename
+ {
+ get
+ {
+ return Path.Combine(_appPaths.ConfigurationDirectoryPath, "mb.lic");
+ }
+ }
+
public string LegacyKey { get; set; }
private Dictionary<Guid, DateTime> UpdateRecords { get; set; }
private readonly object _lck = new object();
@@ -35,8 +42,6 @@ namespace MediaBrowser.Common.Implementations.Security
{
_appPaths = appPaths;
- _filename = Path.Combine(_appPaths.ConfigurationDirectoryPath, "mb.lic");
-
UpdateRecords = new Dictionary<Guid, DateTime>();
Load();
}
@@ -64,15 +69,16 @@ namespace MediaBrowser.Common.Implementations.Security
private void Load()
{
string[] contents = null;
+ var licenseFile = Filename;
lock (_lck)
{
try
{
- contents = File.ReadAllLines(_filename);
+ contents = File.ReadAllLines(licenseFile);
}
catch (FileNotFoundException)
{
- (File.Create(_filename)).Close();
+ (File.Create(licenseFile)).Close();
}
}
if (contents != null && contents.Length > 0)
@@ -100,7 +106,9 @@ namespace MediaBrowser.Common.Implementations.Security
lines.Add(pair.Value.Ticks.ToString());
}
- lock(_lck) File.WriteAllLines(_filename, lines);
+ var licenseFile = Filename;
+ Directory.CreateDirectory(Path.GetDirectoryName(licenseFile));
+ lock (_lck) File.WriteAllLines(licenseFile, lines);
}
}
}
diff --git a/MediaBrowser.Common.Implementations/Updates/InstallationManager.cs b/MediaBrowser.Common.Implementations/Updates/InstallationManager.cs
index 0581343d3..18462ba9b 100644
--- a/MediaBrowser.Common.Implementations/Updates/InstallationManager.cs
+++ b/MediaBrowser.Common.Implementations/Updates/InstallationManager.cs
@@ -528,6 +528,7 @@ namespace MediaBrowser.Common.Implementations.Updates
// Success - move it to the real target
try
{
+ Directory.CreateDirectory(Path.GetDirectoryName(target));
File.Copy(tempFile, target, true);
//If it is an archive - write out a version file so we know what it is
if (isArchive)
diff --git a/MediaBrowser.Common.Implementations/packages.config b/MediaBrowser.Common.Implementations/packages.config
index 16324ab90..81647c114 100644
--- a/MediaBrowser.Common.Implementations/packages.config
+++ b/MediaBrowser.Common.Implementations/packages.config
@@ -2,5 +2,5 @@
<packages>
<package id="NLog" version="2.1.0" targetFramework="net45" />
<package id="sharpcompress" version="0.10.2" targetFramework="net45" />
- <package id="SimpleInjector" version="2.3.6" targetFramework="net45" />
+ <package id="SimpleInjector" version="2.4.0" targetFramework="net45" />
</packages> \ No newline at end of file
diff --git a/MediaBrowser.Common/Configuration/ConfigurationHelper.cs b/MediaBrowser.Common/Configuration/ConfigurationHelper.cs
index 1f86c5c02..64c2e87de 100644
--- a/MediaBrowser.Common/Configuration/ConfigurationHelper.cs
+++ b/MediaBrowser.Common/Configuration/ConfigurationHelper.cs
@@ -42,6 +42,8 @@ namespace MediaBrowser.Common.Configuration
// If the file didn't exist before, or if something has changed, re-save
if (buffer == null || !buffer.SequenceEqual(newBytes))
{
+ Directory.CreateDirectory(Path.GetDirectoryName(path));
+
// Save it after load in case we got new items
File.WriteAllBytes(path, newBytes);
}
diff --git a/MediaBrowser.Common/IApplicationHost.cs b/MediaBrowser.Common/IApplicationHost.cs
index 56b86a3c1..1c7ffe424 100644
--- a/MediaBrowser.Common/IApplicationHost.cs
+++ b/MediaBrowser.Common/IApplicationHost.cs
@@ -129,8 +129,9 @@ namespace MediaBrowser.Common
/// <summary>
/// Inits this instance.
/// </summary>
+ /// <param name="progress">The progress.</param>
/// <returns>Task.</returns>
- Task Init();
+ Task Init(IProgress<double> progress);
/// <summary>
/// Creates the instance.
diff --git a/MediaBrowser.Common/IO/IFileSystem.cs b/MediaBrowser.Common/IO/IFileSystem.cs
index d307b74e5..8fba63195 100644
--- a/MediaBrowser.Common/IO/IFileSystem.cs
+++ b/MediaBrowser.Common/IO/IFileSystem.cs
@@ -74,5 +74,12 @@ namespace MediaBrowser.Common.IO
/// <param name="isAsync">if set to <c>true</c> [is asynchronous].</param>
/// <returns>FileStream.</returns>
FileStream GetFileStream(string path, FileMode mode, FileAccess access, FileShare share, bool isAsync = false);
+
+ /// <summary>
+ /// Swaps the files.
+ /// </summary>
+ /// <param name="file1">The file1.</param>
+ /// <param name="file2">The file2.</param>
+ void SwapFiles(string file1, string file2);
}
}
diff --git a/MediaBrowser.Common/MediaBrowser.Common.csproj b/MediaBrowser.Common/MediaBrowser.Common.csproj
index fce9c18cf..6098c4bb3 100644
--- a/MediaBrowser.Common/MediaBrowser.Common.csproj
+++ b/MediaBrowser.Common/MediaBrowser.Common.csproj
@@ -9,12 +9,12 @@
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>MediaBrowser.Common</RootNamespace>
<AssemblyName>MediaBrowser.Common</AssemblyName>
- <TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<SolutionDir Condition="$(SolutionDir) == '' Or $(SolutionDir) == '*Undefined*'">..\</SolutionDir>
<RestorePackages>true</RestorePackages>
<ProductVersion>10.0.0</ProductVersion>
<SchemaVersion>2.0</SchemaVersion>
+ <TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
@@ -25,6 +25,7 @@
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<PlatformTarget>AnyCPU</PlatformTarget>
+ <TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
@@ -33,6 +34,16 @@
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
+ <TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
+ </PropertyGroup>
+ <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release Mono|AnyCPU' ">
+ <DebugType>pdbonly</DebugType>
+ <Optimize>true</Optimize>
+ <OutputPath>bin\Release Mono\</OutputPath>
+ <DefineConstants>TRACE</DefineConstants>
+ <ErrorReport>prompt</ErrorReport>
+ <WarningLevel>4</WarningLevel>
+ <TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
</PropertyGroup>
<ItemGroup>
<Reference Include="System" />
@@ -101,9 +112,9 @@
</ItemGroup>
<ItemGroup />
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
- <Import Project="$(SolutionDir)\.nuget\nuget.targets" />
+ <Import Project="$(SolutionDir)\.nuget\nuget.targets" Condition=" '$(ConfigurationName)' != 'Release Mono' " />
<PropertyGroup>
- <PostBuildEvent>if $(ConfigurationName) == Release (
+ <PostBuildEvent Condition=" '$(ConfigurationName)' != 'Release Mono' ">if '$(ConfigurationName)' == 'Release' (
xcopy "$(TargetPath)" "$(SolutionDir)\Nuget\dlls\" /y /d /r /i
)</PostBuildEvent>
</PropertyGroup>
diff --git a/MediaBrowser.Common/Net/HttpRequestOptions.cs b/MediaBrowser.Common/Net/HttpRequestOptions.cs
index 977a6aabe..db78fc927 100644
--- a/MediaBrowser.Common/Net/HttpRequestOptions.cs
+++ b/MediaBrowser.Common/Net/HttpRequestOptions.cs
@@ -66,6 +66,10 @@ namespace MediaBrowser.Common.Net
public Dictionary<string, string> RequestHeaders { get; private set; }
+ public string RequestContentType { get; set; }
+
+ public string RequestContent { get; set; }
+
private string GetHeaderValue(string name)
{
string value;
diff --git a/MediaBrowser.Common/Net/IHttpClient.cs b/MediaBrowser.Common/Net/IHttpClient.cs
index 54d6665e2..a3d90cb0d 100644
--- a/MediaBrowser.Common/Net/IHttpClient.cs
+++ b/MediaBrowser.Common/Net/IHttpClient.cs
@@ -65,6 +65,13 @@ namespace MediaBrowser.Common.Net
Task<Stream> Post(string url, Dictionary<string, string> postData, CancellationToken cancellationToken);
/// <summary>
+ /// Posts the specified options.
+ /// </summary>
+ /// <param name="options">The options.</param>
+ /// <returns>Task{HttpResponseInfo}.</returns>
+ Task<HttpResponseInfo> Post(HttpRequestOptions options);
+
+ /// <summary>
/// Downloads the contents of a given url into a temporary location
/// </summary>
/// <param name="options">The options.</param>
diff --git a/MediaBrowser.Common/Net/MimeTypes.cs b/MediaBrowser.Common/Net/MimeTypes.cs
index c11ff59d5..47536a341 100644
--- a/MediaBrowser.Common/Net/MimeTypes.cs
+++ b/MediaBrowser.Common/Net/MimeTypes.cs
@@ -218,6 +218,11 @@ namespace MediaBrowser.Common.Net
return "image/svg+xml";
}
+ if (ext.Equals(".srt", StringComparison.OrdinalIgnoreCase))
+ {
+ return "text/plain";
+ }
+
throw new ArgumentException("Argument not supported: " + path);
}
}
diff --git a/MediaBrowser.Common/Plugins/BasePlugin.cs b/MediaBrowser.Common/Plugins/BasePlugin.cs
index 795d22100..aa1369c4e 100644
--- a/MediaBrowser.Common/Plugins/BasePlugin.cs
+++ b/MediaBrowser.Common/Plugins/BasePlugin.cs
@@ -267,6 +267,8 @@ namespace MediaBrowser.Common.Plugins
{
lock (_configurationSaveLock)
{
+ Directory.CreateDirectory(Path.GetDirectoryName(ConfigurationFilePath));
+
XmlSerializer.SerializeToFile(Configuration, ConfigurationFilePath);
}
}
diff --git a/MediaBrowser.Controller/Drawing/IImageProcessor.cs b/MediaBrowser.Controller/Drawing/IImageProcessor.cs
index 1a8583489..2ecf3ec9a 100644
--- a/MediaBrowser.Controller/Drawing/IImageProcessor.cs
+++ b/MediaBrowser.Controller/Drawing/IImageProcessor.cs
@@ -47,7 +47,7 @@ namespace MediaBrowser.Controller.Drawing
/// <param name="item">The item.</param>
/// <param name="imageType">Type of the image.</param>
/// <returns>IEnumerable{IImageEnhancer}.</returns>
- IEnumerable<IImageEnhancer> GetSupportedEnhancers(BaseItem item, ImageType imageType);
+ IEnumerable<IImageEnhancer> GetSupportedEnhancers(IHasImages item, ImageType imageType);
/// <summary>
/// Gets the image cache tag.
@@ -56,7 +56,7 @@ namespace MediaBrowser.Controller.Drawing
/// <param name="imageType">Type of the image.</param>
/// <param name="imagePath">The image path.</param>
/// <returns>Guid.</returns>
- Guid GetImageCacheTag(BaseItem item, ImageType imageType, string imagePath);
+ Guid GetImageCacheTag(IHasImages item, ImageType imageType, string imagePath);
/// <summary>
/// Gets the image cache tag.
@@ -67,7 +67,7 @@ namespace MediaBrowser.Controller.Drawing
/// <param name="dateModified">The date modified.</param>
/// <param name="imageEnhancers">The image enhancers.</param>
/// <returns>Guid.</returns>
- Guid GetImageCacheTag(BaseItem item, ImageType imageType, string originalImagePath, DateTime dateModified,
+ Guid GetImageCacheTag(IHasImages item, ImageType imageType, string originalImagePath, DateTime dateModified,
List<IImageEnhancer> imageEnhancers);
/// <summary>
@@ -85,6 +85,6 @@ namespace MediaBrowser.Controller.Drawing
/// <param name="imageType">Type of the image.</param>
/// <param name="imageIndex">Index of the image.</param>
/// <returns>Task{System.String}.</returns>
- Task<string> GetEnhancedImage(BaseItem item, ImageType imageType, int imageIndex);
+ Task<string> GetEnhancedImage(IHasImages item, ImageType imageType, int imageIndex);
}
}
diff --git a/MediaBrowser.Controller/Drawing/ImageProcessingOptions.cs b/MediaBrowser.Controller/Drawing/ImageProcessingOptions.cs
index ce4bf6c32..506d6fd3d 100644
--- a/MediaBrowser.Controller/Drawing/ImageProcessingOptions.cs
+++ b/MediaBrowser.Controller/Drawing/ImageProcessingOptions.cs
@@ -9,7 +9,7 @@ namespace MediaBrowser.Controller.Drawing
{
public class ImageProcessingOptions
{
- public BaseItem Item { get; set; }
+ public IHasImages Item { get; set; }
public ImageType ImageType { get; set; }
diff --git a/MediaBrowser.Controller/Entities/AdultVideo.cs b/MediaBrowser.Controller/Entities/AdultVideo.cs
index 9bb0f8355..f81cfa1f6 100644
--- a/MediaBrowser.Controller/Entities/AdultVideo.cs
+++ b/MediaBrowser.Controller/Entities/AdultVideo.cs
@@ -1,7 +1,14 @@

namespace MediaBrowser.Controller.Entities
{
- public class AdultVideo : Video
+ public class AdultVideo : Video, IHasPreferredMetadataLanguage
{
+ public string PreferredMetadataLanguage { get; set; }
+
+ /// <summary>
+ /// Gets or sets the preferred metadata country code.
+ /// </summary>
+ /// <value>The preferred metadata country code.</value>
+ public string PreferredMetadataCountryCode { get; set; }
}
}
diff --git a/MediaBrowser.Controller/Entities/Audio/Audio.cs b/MediaBrowser.Controller/Entities/Audio/Audio.cs
index 63c907c1f..028fc964d 100644
--- a/MediaBrowser.Controller/Entities/Audio/Audio.cs
+++ b/MediaBrowser.Controller/Entities/Audio/Audio.cs
@@ -1,4 +1,5 @@
-using System;
+using MediaBrowser.Model.Configuration;
+using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
@@ -8,7 +9,7 @@ namespace MediaBrowser.Controller.Entities.Audio
/// <summary>
/// Class Audio
/// </summary>
- public class Audio : BaseItem, IHasMediaStreams, IHasAlbumArtist, IHasArtist, IHasMusicGenres, IHasLanguage
+ public class Audio : BaseItem, IHasMediaStreams, IHasAlbumArtist, IHasArtist, IHasMusicGenres
{
public Audio()
{
@@ -16,12 +17,6 @@ namespace MediaBrowser.Controller.Entities.Audio
}
/// <summary>
- /// Gets or sets the language.
- /// </summary>
- /// <value>The language.</value>
- public string Language { get; set; }
-
- /// <summary>
/// Gets or sets a value indicating whether this instance has embedded image.
/// </summary>
/// <value><c>true</c> if this instance has embedded image; otherwise, <c>false</c>.</value>
@@ -131,5 +126,10 @@ namespace MediaBrowser.Controller.Entities.Audio
return base.GetUserDataKey();
}
+
+ protected override bool GetBlockUnratedValue(UserConfiguration config)
+ {
+ return config.BlockUnratedMusic;
+ }
}
}
diff --git a/MediaBrowser.Controller/Entities/Audio/MusicAlbum.cs b/MediaBrowser.Controller/Entities/Audio/MusicAlbum.cs
index 3facccec1..203e6dc43 100644
--- a/MediaBrowser.Controller/Entities/Audio/MusicAlbum.cs
+++ b/MediaBrowser.Controller/Entities/Audio/MusicAlbum.cs
@@ -1,4 +1,5 @@
-using MediaBrowser.Model.Entities;
+using MediaBrowser.Model.Configuration;
+using MediaBrowser.Model.Entities;
using System;
using System.Collections.Generic;
using System.Linq;
@@ -109,6 +110,11 @@ namespace MediaBrowser.Controller.Entities.Audio
return base.GetUserDataKey();
}
+
+ protected override bool GetBlockUnratedValue(UserConfiguration config)
+ {
+ return config.BlockUnratedMusic;
+ }
}
public class MusicAlbumDisc : Folder
diff --git a/MediaBrowser.Controller/Entities/Audio/MusicArtist.cs b/MediaBrowser.Controller/Entities/Audio/MusicArtist.cs
index 3be555f49..860d34fd8 100644
--- a/MediaBrowser.Controller/Entities/Audio/MusicArtist.cs
+++ b/MediaBrowser.Controller/Entities/Audio/MusicArtist.cs
@@ -1,4 +1,5 @@
-using MediaBrowser.Model.Dto;
+using MediaBrowser.Model.Configuration;
+using MediaBrowser.Model.Dto;
using MediaBrowser.Model.Entities;
using System;
using System.Collections.Generic;
@@ -96,7 +97,7 @@ namespace MediaBrowser.Controller.Entities.Audio
/// </summary>
/// <param name="item">The item.</param>
/// <returns>System.String.</returns>
- public static string GetUserDataKey(BaseItem item)
+ private static string GetUserDataKey(MusicArtist item)
{
var id = item.GetProviderId(MetadataProviders.Musicbrainz);
@@ -107,5 +108,10 @@ namespace MediaBrowser.Controller.Entities.Audio
return "Artist-" + item.Name;
}
+
+ protected override bool GetBlockUnratedValue(UserConfiguration config)
+ {
+ return config.BlockUnratedMusic;
+ }
}
}
diff --git a/MediaBrowser.Controller/Entities/BaseItem.cs b/MediaBrowser.Controller/Entities/BaseItem.cs
index 541887598..11562b3d7 100644
--- a/MediaBrowser.Controller/Entities/BaseItem.cs
+++ b/MediaBrowser.Controller/Entities/BaseItem.cs
@@ -7,6 +7,7 @@ using MediaBrowser.Controller.Localization;
using MediaBrowser.Controller.Persistence;
using MediaBrowser.Controller.Providers;
using MediaBrowser.Controller.Resolvers;
+using MediaBrowser.Model.Configuration;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Logging;
using System;
@@ -22,7 +23,7 @@ namespace MediaBrowser.Controller.Entities
/// <summary>
/// Class BaseItem
/// </summary>
- public abstract class BaseItem : IHasProviderIds, ILibraryItem
+ public abstract class BaseItem : IHasProviderIds, ILibraryItem, IHasImages, IHasUserData
{
protected BaseItem()
{
@@ -132,8 +133,8 @@ namespace MediaBrowser.Controller.Entities
[IgnoreDataMember]
public string PrimaryImagePath
{
- get { return GetImage(ImageType.Primary); }
- set { SetImage(ImageType.Primary, value); }
+ get { return this.GetImagePath(ImageType.Primary); }
+ set { this.SetImagePath(ImageType.Primary, value); }
}
/// <summary>
@@ -956,6 +957,66 @@ namespace MediaBrowser.Controller.Entities
}
/// <summary>
+ /// Gets the preferred metadata language.
+ /// </summary>
+ /// <returns>System.String.</returns>
+ public string GetPreferredMetadataLanguage()
+ {
+ string lang = null;
+
+ var hasLang = this as IHasPreferredMetadataLanguage;
+
+ if (hasLang != null)
+ {
+ lang = hasLang.PreferredMetadataLanguage;
+ }
+
+ if (string.IsNullOrEmpty(lang))
+ {
+ lang = Parents.OfType<IHasPreferredMetadataLanguage>()
+ .Select(i => i.PreferredMetadataLanguage)
+ .FirstOrDefault(i => !string.IsNullOrEmpty(i));
+ }
+
+ if (string.IsNullOrEmpty(lang))
+ {
+ lang = ConfigurationManager.Configuration.PreferredMetadataLanguage;
+ }
+
+ return lang;
+ }
+
+ /// <summary>
+ /// Gets the preferred metadata language.
+ /// </summary>
+ /// <returns>System.String.</returns>
+ public string GetPreferredMetadataCountryCode()
+ {
+ string lang = null;
+
+ var hasLang = this as IHasPreferredMetadataLanguage;
+
+ if (hasLang != null)
+ {
+ lang = hasLang.PreferredMetadataCountryCode;
+ }
+
+ if (string.IsNullOrEmpty(lang))
+ {
+ lang = Parents.OfType<IHasPreferredMetadataLanguage>()
+ .Select(i => i.PreferredMetadataCountryCode)
+ .FirstOrDefault(i => !string.IsNullOrEmpty(i));
+ }
+
+ if (string.IsNullOrEmpty(lang))
+ {
+ lang = ConfigurationManager.Configuration.MetadataCountryCode;
+ }
+
+ return lang;
+ }
+
+ /// <summary>
/// Determines if a given user has access to this item
/// </summary>
/// <param name="user">The user.</param>
@@ -985,7 +1046,7 @@ namespace MediaBrowser.Controller.Entities
if (string.IsNullOrEmpty(rating))
{
- return !user.Configuration.BlockNotRated;
+ return !GetBlockUnratedValue(user.Configuration);
}
var value = localizationManager.GetRatingLevel(rating);
@@ -1000,6 +1061,16 @@ namespace MediaBrowser.Controller.Entities
}
/// <summary>
+ /// Gets the block unrated value.
+ /// </summary>
+ /// <param name="config">The configuration.</param>
+ /// <returns><c>true</c> if XXXX, <c>false</c> otherwise.</returns>
+ protected virtual bool GetBlockUnratedValue(UserConfiguration config)
+ {
+ return config.BlockNotRated;
+ }
+
+ /// <summary>
/// Determines if this folder should be visible to a given user.
/// Default is just parental allowed. Can be overridden for more functionality.
/// </summary>
@@ -1064,6 +1135,11 @@ namespace MediaBrowser.Controller.Entities
{
throw new ArgumentNullException();
}
+ if (IsInMixedFolder != copy.IsInMixedFolder)
+ {
+ Logger.Debug(Name + " changed due to different value for IsInMixedFolder.");
+ return true;
+ }
var changed = copy.DateModified != DateModified;
if (changed)
@@ -1310,31 +1386,10 @@ namespace MediaBrowser.Controller.Entities
/// Gets an image
/// </summary>
/// <param name="type">The type.</param>
- /// <returns>System.String.</returns>
- /// <exception cref="System.ArgumentException">Backdrops should be accessed using Item.Backdrops</exception>
- public string GetImage(ImageType type)
- {
- if (type == ImageType.Backdrop)
- {
- throw new ArgumentException("Backdrops should be accessed using Item.Backdrops");
- }
- if (type == ImageType.Screenshot)
- {
- throw new ArgumentException("Screenshots should be accessed using Item.Screenshots");
- }
-
- string val;
- Images.TryGetValue(type, out val);
- return val;
- }
-
- /// <summary>
- /// Gets an image
- /// </summary>
- /// <param name="type">The type.</param>
+ /// <param name="imageIndex">Index of the image.</param>
/// <returns><c>true</c> if the specified type has image; otherwise, <c>false</c>.</returns>
/// <exception cref="System.ArgumentException">Backdrops should be accessed using Item.Backdrops</exception>
- public bool HasImage(ImageType type)
+ public bool HasImage(ImageType type, int imageIndex)
{
if (type == ImageType.Backdrop)
{
@@ -1345,16 +1400,10 @@ namespace MediaBrowser.Controller.Entities
throw new ArgumentException("Screenshots should be accessed using Item.Screenshots");
}
- return !string.IsNullOrEmpty(GetImage(type));
+ return !string.IsNullOrEmpty(this.GetImagePath(type));
}
- /// <summary>
- /// Sets an image
- /// </summary>
- /// <param name="type">The type.</param>
- /// <param name="path">The path.</param>
- /// <exception cref="System.ArgumentException">Backdrops should be accessed using Item.Backdrops</exception>
- public void SetImage(ImageType type, string path)
+ public void SetImagePath(ImageType type, int index, string path)
{
if (type == ImageType.Backdrop)
{
@@ -1423,10 +1472,10 @@ namespace MediaBrowser.Controller.Entities
else
{
// Delete the source file
- DeleteImagePath(GetImage(type));
+ DeleteImagePath(this.GetImagePath(type));
// Remove it from the item
- SetImage(type, null);
+ this.SetImagePath(type, null);
}
// Refresh metadata
@@ -1597,13 +1646,13 @@ namespace MediaBrowser.Controller.Entities
{
if (imageType == ImageType.Backdrop)
{
- return BackdropImagePaths[imageIndex];
+ return BackdropImagePaths.Count > imageIndex ? BackdropImagePaths[imageIndex] : null;
}
if (imageType == ImageType.Screenshot)
{
var hasScreenshots = (IHasScreenshots)this;
- return hasScreenshots.ScreenshotImagePaths[imageIndex];
+ return hasScreenshots.ScreenshotImagePaths.Count > imageIndex ? hasScreenshots.ScreenshotImagePaths[imageIndex] : null;
}
if (imageType == ImageType.Chapter)
@@ -1611,7 +1660,9 @@ namespace MediaBrowser.Controller.Entities
return ItemRepository.GetChapter(Id, imageIndex).ImagePath;
}
- return GetImage(imageType);
+ string val;
+ Images.TryGetValue(imageType, out val);
+ return val;
}
/// <summary>
@@ -1658,5 +1709,21 @@ namespace MediaBrowser.Controller.Entities
{
return new[] { Path };
}
+
+ public Task SwapImages(ImageType type, int index1, int index2)
+ {
+ if (type != ImageType.Screenshot && type != ImageType.Backdrop)
+ {
+ throw new ArgumentException("The change index operation is only applicable to backdrops and screenshots");
+ }
+
+ var file1 = GetImagePath(type, index1);
+ var file2 = GetImagePath(type, index2);
+
+ FileSystem.SwapFiles(file1, file2);
+
+ // Directory watchers should repeat this, but do a quick refresh first
+ return RefreshMetadata(CancellationToken.None, forceSave: true, allowSlowProviders: false);
+ }
}
}
diff --git a/MediaBrowser.Controller/Entities/Book.cs b/MediaBrowser.Controller/Entities/Book.cs
index 87b90b824..298941378 100644
--- a/MediaBrowser.Controller/Entities/Book.cs
+++ b/MediaBrowser.Controller/Entities/Book.cs
@@ -1,8 +1,9 @@
-using System.Collections.Generic;
+using MediaBrowser.Model.Configuration;
+using System.Collections.Generic;
namespace MediaBrowser.Controller.Entities
{
- public class Book : BaseItem, IHasTags
+ public class Book : BaseItem, IHasTags, IHasPreferredMetadataLanguage
{
public override string MediaType
{
@@ -11,6 +12,7 @@ namespace MediaBrowser.Controller.Entities
return Model.Entities.MediaType.Book;
}
}
+
/// <summary>
/// Gets or sets the tags.
/// </summary>
@@ -19,6 +21,14 @@ namespace MediaBrowser.Controller.Entities
public string SeriesName { get; set; }
+ public string PreferredMetadataLanguage { get; set; }
+
+ /// <summary>
+ /// Gets or sets the preferred metadata country code.
+ /// </summary>
+ /// <value>The preferred metadata country code.</value>
+ public string PreferredMetadataCountryCode { get; set; }
+
/// <summary>
///
/// </summary>
@@ -42,5 +52,10 @@ namespace MediaBrowser.Controller.Entities
{
Tags = new List<string>();
}
+
+ protected override bool GetBlockUnratedValue(UserConfiguration config)
+ {
+ return config.BlockUnratedBooks;
+ }
}
}
diff --git a/MediaBrowser.Controller/Entities/CollectionFolder.cs b/MediaBrowser.Controller/Entities/CollectionFolder.cs
index f032d9318..351385533 100644
--- a/MediaBrowser.Controller/Entities/CollectionFolder.cs
+++ b/MediaBrowser.Controller/Entities/CollectionFolder.cs
@@ -58,13 +58,6 @@ namespace MediaBrowser.Controller.Entities
/// <returns>Task.</returns>
protected override Task ValidateChildrenInternal(IProgress<double> progress, CancellationToken cancellationToken, bool? recursive = null, bool forceRefreshMetadata = false)
{
- //we don't directly validate our children
- //but we do need to clear out the index cache...
- if (IndexCache != null)
- {
- IndexCache.Clear();
- }
-
ResetDynamicChildren();
return NullTaskResult;
diff --git a/MediaBrowser.Controller/Entities/Folder.cs b/MediaBrowser.Controller/Entities/Folder.cs
index e7593b075..912b8fa93 100644
--- a/MediaBrowser.Controller/Entities/Folder.cs
+++ b/MediaBrowser.Controller/Entities/Folder.cs
@@ -8,7 +8,6 @@ using MediaBrowser.Model.Entities;
using MoreLinq;
using System;
using System.Collections;
-using System.Collections.Concurrent;
using System.Collections.Generic;
using System.IO;
using System.Linq;
@@ -209,315 +208,32 @@ namespace MediaBrowser.Controller.Entities
#region Indexing
/// <summary>
- /// The _index by options
- /// </summary>
- private Dictionary<string, Func<User, IEnumerable<BaseItem>>> _indexByOptions;
- /// <summary>
- /// Dictionary of index options - consists of a display value and an indexing function
- /// which takes User as a parameter and returns an IEnum of BaseItem
- /// </summary>
- /// <value>The index by options.</value>
- [IgnoreDataMember]
- public Dictionary<string, Func<User, IEnumerable<BaseItem>>> IndexByOptions
- {
- get { return _indexByOptions ?? (_indexByOptions = GetIndexByOptions()); }
- }
-
- /// <summary>
/// Returns the valid set of index by options for this folder type.
/// Override or extend to modify.
/// </summary>
/// <returns>Dictionary{System.StringFunc{UserIEnumerable{BaseItem}}}.</returns>
- protected virtual Dictionary<string, Func<User, IEnumerable<BaseItem>>> GetIndexByOptions()
+ protected virtual IEnumerable<string> GetIndexByOptions()
{
- return new Dictionary<string, Func<User, IEnumerable<BaseItem>>> {
- {LocalizedStrings.Instance.GetString("NoneDispPref"), null},
- {LocalizedStrings.Instance.GetString("PerformerDispPref"), GetIndexByPerformer},
- {LocalizedStrings.Instance.GetString("GenreDispPref"), GetIndexByGenre},
- {LocalizedStrings.Instance.GetString("DirectorDispPref"), GetIndexByDirector},
- {LocalizedStrings.Instance.GetString("YearDispPref"), GetIndexByYear},
+ return new List<string> {
+ {LocalizedStrings.Instance.GetString("NoneDispPref")},
+ {LocalizedStrings.Instance.GetString("PerformerDispPref")},
+ {LocalizedStrings.Instance.GetString("GenreDispPref")},
+ {LocalizedStrings.Instance.GetString("DirectorDispPref")},
+ {LocalizedStrings.Instance.GetString("YearDispPref")},
//{LocalizedStrings.Instance.GetString("OfficialRatingDispPref"), null},
- {LocalizedStrings.Instance.GetString("StudioDispPref"), GetIndexByStudio}
+ {LocalizedStrings.Instance.GetString("StudioDispPref")}
};
}
/// <summary>
- /// Gets the index by actor.
- /// </summary>
- /// <param name="user">The user.</param>
- /// <returns>IEnumerable{BaseItem}.</returns>
- protected IEnumerable<BaseItem> GetIndexByPerformer(User user)
- {
- return GetIndexByPerson(user, new List<string> { PersonType.Actor, PersonType.GuestStar }, true, LocalizedStrings.Instance.GetString("PerformerDispPref"));
- }
-
- /// <summary>
- /// Gets the index by director.
- /// </summary>
- /// <param name="user">The user.</param>
- /// <returns>IEnumerable{BaseItem}.</returns>
- protected IEnumerable<BaseItem> GetIndexByDirector(User user)
- {
- return GetIndexByPerson(user, new List<string> { PersonType.Director }, false, LocalizedStrings.Instance.GetString("DirectorDispPref"));
- }
-
- /// <summary>
- /// Gets the index by person.
- /// </summary>
- /// <param name="user">The user.</param>
- /// <param name="personTypes">The person types we should match on</param>
- /// <param name="includeAudio">if set to <c>true</c> [include audio].</param>
- /// <param name="indexName">Name of the index.</param>
- /// <returns>IEnumerable{BaseItem}.</returns>
- private IEnumerable<BaseItem> GetIndexByPerson(User user, List<string> personTypes, bool includeAudio, string indexName)
- {
- // Even though this implementation means multiple iterations over the target list - it allows us to defer
- // the retrieval of the individual children for each index value until they are requested.
- using (new Profiler(indexName + " Index Build for " + Name, Logger))
- {
- // Put this in a local variable to avoid an implicitly captured closure
- var currentIndexName = indexName;
-
- var us = this;
- var recursiveChildren = GetRecursiveChildren(user).Where(i => i.IncludeInIndex).ToList();
-
- // Get the candidates, but handle audio separately
- var candidates = recursiveChildren.Where(i => i.AllPeople != null && !(i is Audio.Audio)).ToList();
-
- var indexFolders = candidates.AsParallel().SelectMany(i => i.AllPeople.Where(p => personTypes.Contains(p.Type))
- .Select(a => a.Name))
- .Distinct()
- .Select(i =>
- {
- try
- {
- return LibraryManager.GetPerson(i);
- }
- catch (IOException ex)
- {
- Logger.ErrorException("Error getting person {0}", ex, i);
- return null;
- }
- catch (AggregateException ex)
- {
- Logger.ErrorException("Error getting person {0}", ex, i);
- return null;
- }
- })
- .Where(i => i != null)
- .Select(a => new IndexFolder(us, a,
- candidates.Where(i => i.AllPeople.Any(p => personTypes.Contains(p.Type) && p.Name.Equals(a.Name, StringComparison.OrdinalIgnoreCase))
- ), currentIndexName)).AsEnumerable();
-
- if (includeAudio)
- {
- var songs = recursiveChildren.OfType<Audio.Audio>().ToList();
-
- indexFolders = songs.SelectMany(i => i.Artists)
- .Distinct(StringComparer.OrdinalIgnoreCase)
- .Select(i =>
- {
- try
- {
- return LibraryManager.GetArtist(i);
- }
- catch (IOException ex)
- {
- Logger.ErrorException("Error getting artist {0}", ex, i);
- return null;
- }
- catch (AggregateException ex)
- {
- Logger.ErrorException("Error getting artist {0}", ex, i);
- return null;
- }
- })
- .Where(i => i != null)
- .Select(a => new IndexFolder(us, a,
- songs.Where(i => i.Artists.Contains(a.Name, StringComparer.OrdinalIgnoreCase)
- ), currentIndexName)).Concat(indexFolders);
- }
-
- return indexFolders;
- }
- }
-
- /// <summary>
- /// Gets the index by studio.
- /// </summary>
- /// <param name="user">The user.</param>
- /// <returns>IEnumerable{BaseItem}.</returns>
- protected IEnumerable<BaseItem> GetIndexByStudio(User user)
- {
- // Even though this implementation means multiple iterations over the target list - it allows us to defer
- // the retrieval of the individual children for each index value until they are requested.
- using (new Profiler("Studio Index Build for " + Name, Logger))
- {
- var indexName = LocalizedStrings.Instance.GetString("StudioDispPref");
-
- var candidates = GetRecursiveChildren(user).Where(i => i.IncludeInIndex).ToList();
-
- return candidates.AsParallel().SelectMany(i => i.AllStudios)
- .Distinct()
- .Select(i =>
- {
- try
- {
- return LibraryManager.GetStudio(i);
- }
- catch (IOException ex)
- {
- Logger.ErrorException("Error getting studio {0}", ex, i);
- return null;
- }
- catch (AggregateException ex)
- {
- Logger.ErrorException("Error getting studio {0}", ex, i);
- return null;
- }
- })
- .Where(i => i != null)
- .Select(ndx => new IndexFolder(this, ndx, candidates.Where(i => i.AllStudios.Any(s => s.Equals(ndx.Name, StringComparison.OrdinalIgnoreCase))), indexName));
- }
- }
-
- /// <summary>
- /// Gets the index by genre.
- /// </summary>
- /// <param name="user">The user.</param>
- /// <returns>IEnumerable{BaseItem}.</returns>
- protected IEnumerable<BaseItem> GetIndexByGenre(User user)
- {
- // Even though this implementation means multiple iterations over the target list - it allows us to defer
- // the retrieval of the individual children for each index value until they are requested.
- using (new Profiler("Genre Index Build for " + Name, Logger))
- {
- var indexName = LocalizedStrings.Instance.GetString("GenreDispPref");
-
- //we need a copy of this so we don't double-recurse
- var candidates = GetRecursiveChildren(user).Where(i => i.IncludeInIndex).ToList();
-
- return candidates.AsParallel().SelectMany(i => i.AllGenres)
- .Distinct(StringComparer.OrdinalIgnoreCase)
- .Select(i =>
- {
- try
- {
- return LibraryManager.GetGenre(i);
- }
- catch (Exception ex)
- {
- Logger.ErrorException("Error getting genre {0}", ex, i);
- return null;
- }
- })
- .Where(i => i != null)
- .Select(genre => new IndexFolder(this, genre, candidates.Where(i => i.AllGenres.Any(g => g.Equals(genre.Name, StringComparison.OrdinalIgnoreCase))), indexName)
- );
- }
- }
-
- /// <summary>
- /// Gets the index by year.
- /// </summary>
- /// <param name="user">The user.</param>
- /// <returns>IEnumerable{BaseItem}.</returns>
- protected IEnumerable<BaseItem> GetIndexByYear(User user)
- {
- // Even though this implementation means multiple iterations over the target list - it allows us to defer
- // the retrieval of the individual children for each index value until they are requested.
- using (new Profiler("Production Year Index Build for " + Name, Logger))
- {
- var indexName = LocalizedStrings.Instance.GetString("YearDispPref");
-
- //we need a copy of this so we don't double-recurse
- var candidates = GetRecursiveChildren(user).Where(i => i.IncludeInIndex && i.ProductionYear.HasValue).ToList();
-
- return candidates.AsParallel().Select(i => i.ProductionYear.Value)
- .Distinct()
- .Select(i =>
- {
- try
- {
- return LibraryManager.GetYear(i);
- }
- catch (IOException ex)
- {
- Logger.ErrorException("Error getting year {0}", ex, i);
- return null;
- }
- catch (AggregateException ex)
- {
- Logger.ErrorException("Error getting year {0}", ex, i);
- return null;
- }
- })
- .Where(i => i != null)
-
- .Select(ndx => new IndexFolder(this, ndx, candidates.Where(i => i.ProductionYear == int.Parse(ndx.Name)), indexName));
-
- }
- }
-
- /// <summary>
- /// Returns the indexed children for this user from the cache. Caches them if not already there.
- /// </summary>
- /// <param name="user">The user.</param>
- /// <param name="indexBy">The index by.</param>
- /// <returns>IEnumerable{BaseItem}.</returns>
- private IEnumerable<BaseItem> GetIndexedChildren(User user, string indexBy)
- {
- List<BaseItem> result = null;
- var cacheKey = user.Name + indexBy;
-
- if (IndexCache != null)
- {
- IndexCache.TryGetValue(cacheKey, out result);
- }
-
- if (result == null)
- {
- //not cached - cache it
- Func<User, IEnumerable<BaseItem>> indexing;
- IndexByOptions.TryGetValue(indexBy, out indexing);
- result = BuildIndex(indexBy, indexing, user);
- }
- return result;
- }
-
- /// <summary>
/// Get the list of indexy by choices for this folder (localized).
/// </summary>
/// <value>The index by option strings.</value>
[IgnoreDataMember]
public IEnumerable<string> IndexByOptionStrings
{
- get { return IndexByOptions.Keys; }
- }
-
- /// <summary>
- /// The index cache
- /// </summary>
- protected ConcurrentDictionary<string, List<BaseItem>> IndexCache;
-
- /// <summary>
- /// Builds the index.
- /// </summary>
- /// <param name="indexKey">The index key.</param>
- /// <param name="indexFunction">The index function.</param>
- /// <param name="user">The user.</param>
- /// <returns>List{BaseItem}.</returns>
- protected virtual List<BaseItem> BuildIndex(string indexKey, Func<User, IEnumerable<BaseItem>> indexFunction, User user)
- {
- if (IndexCache == null)
- {
- IndexCache = new ConcurrentDictionary<string, List<BaseItem>>();
- }
-
- return indexFunction != null
- ? IndexCache[user.Name + indexKey] = indexFunction(user).ToList()
- : null;
+ get { return GetIndexByOptions(); }
}
#endregion
@@ -648,130 +364,127 @@ namespace MediaBrowser.Controller.Entities
{
var locationType = LocationType;
- // Nothing to do here
- if (locationType == LocationType.Remote || locationType == LocationType.Virtual)
- {
- return;
- }
-
cancellationToken.ThrowIfCancellationRequested();
- IEnumerable<BaseItem> nonCachedChildren;
+ var validChildren = new List<Tuple<BaseItem, bool>>();
- try
- {
- nonCachedChildren = GetNonCachedChildren();
- }
- catch (IOException ex)
+ if (locationType != LocationType.Remote && locationType != LocationType.Virtual)
{
- nonCachedChildren = new BaseItem[] { };
+ IEnumerable<BaseItem> nonCachedChildren;
- Logger.ErrorException("Error getting file system entries for {0}", ex, Path);
- }
+ try
+ {
+ nonCachedChildren = GetNonCachedChildren();
+ }
+ catch (IOException ex)
+ {
+ nonCachedChildren = new BaseItem[] {};
- if (nonCachedChildren == null) return; //nothing to validate
+ Logger.ErrorException("Error getting file system entries for {0}", ex, Path);
+ }
- progress.Report(5);
+ if (nonCachedChildren == null) return; //nothing to validate
- //build a dictionary of the current children we have now by Id so we can compare quickly and easily
- var currentChildren = ActualChildren.ToDictionary(i => i.Id);
+ progress.Report(5);
- //create a list for our validated children
- var validChildren = new List<Tuple<BaseItem, bool>>();
- var newItems = new List<BaseItem>();
+ //build a dictionary of the current children we have now by Id so we can compare quickly and easily
+ var currentChildren = ActualChildren.ToDictionary(i => i.Id);
- cancellationToken.ThrowIfCancellationRequested();
+ //create a list for our validated children
+ var newItems = new List<BaseItem>();
- foreach (var child in nonCachedChildren)
- {
- BaseItem currentChild;
+ cancellationToken.ThrowIfCancellationRequested();
- if (currentChildren.TryGetValue(child.Id, out currentChild))
+ foreach (var child in nonCachedChildren)
{
- currentChild.ResetResolveArgs(child.ResolveArgs);
+ BaseItem currentChild;
- //existing item - check if it has changed
- if (currentChild.HasChanged(child))
+ if (currentChildren.TryGetValue(child.Id, out currentChild))
{
- var currentChildLocationType = currentChild.LocationType;
- if (currentChildLocationType != LocationType.Remote &&
- currentChildLocationType != LocationType.Virtual)
+ currentChild.ResetResolveArgs(child.ResolveArgs);
+
+ //existing item - check if it has changed
+ if (currentChild.HasChanged(child))
{
- EntityResolutionHelper.EnsureDates(FileSystem, currentChild, child.ResolveArgs, false);
+ var currentChildLocationType = currentChild.LocationType;
+ if (currentChildLocationType != LocationType.Remote &&
+ currentChildLocationType != LocationType.Virtual)
+ {
+ EntityResolutionHelper.EnsureDates(FileSystem, currentChild, child.ResolveArgs, false);
+ }
+
+ currentChild.IsInMixedFolder = child.IsInMixedFolder;
+ validChildren.Add(new Tuple<BaseItem, bool>(currentChild, true));
+ }
+ else
+ {
+ validChildren.Add(new Tuple<BaseItem, bool>(currentChild, false));
}
- validChildren.Add(new Tuple<BaseItem, bool>(currentChild, true));
+ currentChild.IsOffline = false;
}
else
{
- validChildren.Add(new Tuple<BaseItem, bool>(currentChild, false));
- }
+ //brand new item - needs to be added
+ newItems.Add(child);
- currentChild.IsOffline = false;
+ validChildren.Add(new Tuple<BaseItem, bool>(child, true));
+ }
}
- else
+
+ // If any items were added or removed....
+ if (newItems.Count > 0 || currentChildren.Count != validChildren.Count)
{
- //brand new item - needs to be added
- newItems.Add(child);
+ var newChildren = validChildren.Select(c => c.Item1).ToList();
- validChildren.Add(new Tuple<BaseItem, bool>(child, true));
- }
- }
+ // That's all the new and changed ones - now see if there are any that are missing
+ var itemsRemoved = currentChildren.Values.Except(newChildren).ToList();
- // If any items were added or removed....
- if (newItems.Count > 0 || currentChildren.Count != validChildren.Count)
- {
- var newChildren = validChildren.Select(c => c.Item1).ToList();
+ var actualRemovals = new List<BaseItem>();
- // That's all the new and changed ones - now see if there are any that are missing
- var itemsRemoved = currentChildren.Values.Except(newChildren).ToList();
+ foreach (var item in itemsRemoved)
+ {
+ if (item.LocationType == LocationType.Virtual ||
+ item.LocationType == LocationType.Remote)
+ {
+ // Don't remove these because there's no way to accurately validate them.
+ validChildren.Add(new Tuple<BaseItem, bool>(item, false));
+ }
- var actualRemovals = new List<BaseItem>();
+ else if (!string.IsNullOrEmpty(item.Path) && IsPathOffline(item.Path))
+ {
+ item.IsOffline = true;
- foreach (var item in itemsRemoved)
- {
- if (item.LocationType == LocationType.Virtual ||
- item.LocationType == LocationType.Remote)
- {
- // Don't remove these because there's no way to accurately validate them.
- continue;
+ validChildren.Add(new Tuple<BaseItem, bool>(item, false));
+ }
+ else
+ {
+ item.IsOffline = false;
+ actualRemovals.Add(item);
+ }
}
-
- if (!string.IsNullOrEmpty(item.Path) && IsPathOffline(item.Path))
- {
- item.IsOffline = true;
- validChildren.Add(new Tuple<BaseItem, bool>(item, false));
- }
- else
+ if (actualRemovals.Count > 0)
{
- item.IsOffline = false;
- actualRemovals.Add(item);
- }
- }
+ RemoveChildrenInternal(actualRemovals);
- if (actualRemovals.Count > 0)
- {
- RemoveChildrenInternal(actualRemovals);
-
- foreach (var item in actualRemovals)
- {
- LibraryManager.ReportItemRemoved(item);
+ foreach (var item in actualRemovals)
+ {
+ LibraryManager.ReportItemRemoved(item);
+ }
}
- }
-
- await LibraryManager.CreateItems(newItems, cancellationToken).ConfigureAwait(false);
- AddChildrenInternal(newItems);
+ await LibraryManager.CreateItems(newItems, cancellationToken).ConfigureAwait(false);
- await ItemRepository.SaveChildren(Id, ActualChildren.Select(i => i.Id).ToList(), cancellationToken).ConfigureAwait(false);
+ AddChildrenInternal(newItems);
- //force the indexes to rebuild next time
- if (IndexCache != null)
- {
- IndexCache.Clear();
+ await ItemRepository.SaveChildren(Id, ActualChildren.Select(i => i.Id).ToList(), cancellationToken).ConfigureAwait(false);
}
}
+ else
+ {
+ validChildren.AddRange(ActualChildren.Select(i => new Tuple<BaseItem, bool>(i, false)));
+ }
progress.Report(10);
@@ -988,10 +701,9 @@ namespace MediaBrowser.Controller.Entities
/// </summary>
/// <param name="user">The user.</param>
/// <param name="includeLinkedChildren">if set to <c>true</c> [include linked children].</param>
- /// <param name="indexBy">The index by.</param>
/// <returns>IEnumerable{BaseItem}.</returns>
/// <exception cref="System.ArgumentNullException"></exception>
- public virtual IEnumerable<BaseItem> GetChildren(User user, bool includeLinkedChildren, string indexBy = null)
+ public virtual IEnumerable<BaseItem> GetChildren(User user, bool includeLinkedChildren)
{
if (user == null)
{
@@ -999,19 +711,7 @@ namespace MediaBrowser.Controller.Entities
}
//the true root should return our users root folder children
- if (IsPhysicalRoot) return user.RootFolder.GetChildren(user, includeLinkedChildren, indexBy);
-
- IEnumerable<BaseItem> result = null;
-
- if (!string.IsNullOrEmpty(indexBy))
- {
- result = GetIndexedChildren(user, indexBy);
- }
-
- if (result != null)
- {
- return result;
- }
+ if (IsPhysicalRoot) return user.RootFolder.GetChildren(user, includeLinkedChildren);
var list = new List<BaseItem>();
@@ -1359,13 +1059,24 @@ namespace MediaBrowser.Controller.Entities
Logger.ErrorException("Error getting ResolveArgs for {0}", ex, Path);
}
- //this should be functionally equivilent to what was here since it is IEnum and works on a thread-safe copy
return RecursiveChildren.Where(i => i.LocationType != LocationType.Virtual).FirstOrDefault(i =>
{
try
{
- return string.Equals(i.Path, path, StringComparison.OrdinalIgnoreCase)
- || i.ResolveArgs.PhysicalLocations.Contains(path, StringComparer.OrdinalIgnoreCase);
+ if (string.Equals(i.Path, path, StringComparison.OrdinalIgnoreCase))
+ {
+ return true;
+ }
+
+ if (i.LocationType != LocationType.Remote)
+ {
+ if (i.ResolveArgs.PhysicalLocations.Contains(path, StringComparer.OrdinalIgnoreCase))
+ {
+ return true;
+ }
+ }
+
+ return false;
}
catch (IOException ex)
{
diff --git a/MediaBrowser.Controller/Entities/Game.cs b/MediaBrowser.Controller/Entities/Game.cs
index c15a31dd3..da95b7c44 100644
--- a/MediaBrowser.Controller/Entities/Game.cs
+++ b/MediaBrowser.Controller/Entities/Game.cs
@@ -1,16 +1,25 @@
-using MediaBrowser.Model.Entities;
+using MediaBrowser.Model.Configuration;
+using MediaBrowser.Model.Entities;
using System;
using System.Collections.Generic;
namespace MediaBrowser.Controller.Entities
{
- public class Game : BaseItem, IHasSoundtracks, IHasTrailers, IHasThemeMedia, IHasTags, IHasLanguage, IHasScreenshots
+ public class Game : BaseItem, IHasSoundtracks, IHasTrailers, IHasThemeMedia, IHasTags, IHasScreenshots, IHasPreferredMetadataLanguage
{
public List<Guid> SoundtrackIds { get; set; }
public List<Guid> ThemeSongIds { get; set; }
public List<Guid> ThemeVideoIds { get; set; }
+ public string PreferredMetadataLanguage { get; set; }
+
+ /// <summary>
+ /// Gets or sets the preferred metadata country code.
+ /// </summary>
+ /// <value>The preferred metadata country code.</value>
+ public string PreferredMetadataCountryCode { get; set; }
+
public Game()
{
MultiPartGameFiles = new List<string>();
@@ -23,12 +32,6 @@ namespace MediaBrowser.Controller.Entities
ScreenshotImagePaths = new List<string>();
}
- /// <summary>
- /// Gets or sets the language.
- /// </summary>
- /// <value>The language.</value>
- public string Language { get; set; }
-
public List<Guid> LocalTrailerIds { get; set; }
/// <summary>
@@ -129,5 +132,10 @@ namespace MediaBrowser.Controller.Entities
return base.GetDeletePaths();
}
+
+ protected override bool GetBlockUnratedValue(UserConfiguration config)
+ {
+ return config.BlockUnratedGames;
+ }
}
}
diff --git a/MediaBrowser.Controller/Entities/GameSystem.cs b/MediaBrowser.Controller/Entities/GameSystem.cs
index 054071b35..63af8082a 100644
--- a/MediaBrowser.Controller/Entities/GameSystem.cs
+++ b/MediaBrowser.Controller/Entities/GameSystem.cs
@@ -1,4 +1,5 @@
-using System;
+using MediaBrowser.Model.Configuration;
+using System;
namespace MediaBrowser.Controller.Entities
{
@@ -38,5 +39,11 @@ namespace MediaBrowser.Controller.Entities
}
return base.GetUserDataKey();
}
+
+ protected override bool GetBlockUnratedValue(UserConfiguration config)
+ {
+ // Don't block. Determine by game
+ return false;
+ }
}
}
diff --git a/MediaBrowser.Controller/Entities/Genre.cs b/MediaBrowser.Controller/Entities/Genre.cs
index 0fa49639b..53bc64194 100644
--- a/MediaBrowser.Controller/Entities/Genre.cs
+++ b/MediaBrowser.Controller/Entities/Genre.cs
@@ -1,5 +1,4 @@
using MediaBrowser.Model.Dto;
-using System;
using System.Collections.Generic;
using System.Runtime.Serialization;
diff --git a/MediaBrowser.Controller/Entities/IHasImages.cs b/MediaBrowser.Controller/Entities/IHasImages.cs
new file mode 100644
index 000000000..d800acd9b
--- /dev/null
+++ b/MediaBrowser.Controller/Entities/IHasImages.cs
@@ -0,0 +1,115 @@
+using MediaBrowser.Model.Entities;
+using System;
+using System.Threading.Tasks;
+
+namespace MediaBrowser.Controller.Entities
+{
+ public interface IHasImages
+ {
+ /// <summary>
+ /// Gets the name.
+ /// </summary>
+ /// <value>The name.</value>
+ string Name { get; }
+
+ /// <summary>
+ /// Gets the path.
+ /// </summary>
+ /// <value>The path.</value>
+ string Path { get; }
+
+ /// <summary>
+ /// Gets the identifier.
+ /// </summary>
+ /// <value>The identifier.</value>
+ Guid Id { get; }
+
+ /// <summary>
+ /// Gets the image path.
+ /// </summary>
+ /// <param name="imageType">Type of the image.</param>
+ /// <param name="imageIndex">Index of the image.</param>
+ /// <returns>System.String.</returns>
+ string GetImagePath(ImageType imageType, int imageIndex);
+
+ /// <summary>
+ /// Gets the image date modified.
+ /// </summary>
+ /// <param name="imagePath">The image path.</param>
+ /// <returns>DateTime.</returns>
+ DateTime GetImageDateModified(string imagePath);
+
+ /// <summary>
+ /// Sets the image.
+ /// </summary>
+ /// <param name="type">The type.</param>
+ /// <param name="index">The index.</param>
+ /// <param name="path">The path.</param>
+ void SetImagePath(ImageType type, int index, string path);
+
+ /// <summary>
+ /// Determines whether the specified type has image.
+ /// </summary>
+ /// <param name="type">The type.</param>
+ /// <param name="imageIndex">Index of the image.</param>
+ /// <returns><c>true</c> if the specified type has image; otherwise, <c>false</c>.</returns>
+ bool HasImage(ImageType type, int imageIndex);
+
+ /// <summary>
+ /// Swaps the images.
+ /// </summary>
+ /// <param name="type">The type.</param>
+ /// <param name="index1">The index1.</param>
+ /// <param name="index2">The index2.</param>
+ /// <returns>Task.</returns>
+ Task SwapImages(ImageType type, int index1, int index2);
+
+ /// <summary>
+ /// Gets the display type of the media.
+ /// </summary>
+ /// <value>The display type of the media.</value>
+ string DisplayMediaType { get; set; }
+
+ /// <summary>
+ /// Gets or sets the primary image path.
+ /// </summary>
+ /// <value>The primary image path.</value>
+ string PrimaryImagePath { get; set; }
+
+ /// <summary>
+ /// Gets the preferred metadata language.
+ /// </summary>
+ /// <returns>System.String.</returns>
+ string GetPreferredMetadataLanguage();
+ }
+
+ public static class HasImagesExtensions
+ {
+ /// <summary>
+ /// Gets the image path.
+ /// </summary>
+ /// <param name="item">The item.</param>
+ /// <param name="imageType">Type of the image.</param>
+ /// <returns>System.String.</returns>
+ public static string GetImagePath(this IHasImages item, ImageType imageType)
+ {
+ return item.GetImagePath(imageType, 0);
+ }
+
+ public static bool HasImage(this IHasImages item, ImageType imageType)
+ {
+ return item.HasImage(imageType, 0);
+ }
+
+ /// <summary>
+ /// Sets the image path.
+ /// </summary>
+ /// <param name="item">The item.</param>
+ /// <param name="imageType">Type of the image.</param>
+ /// <param name="path">The path.</param>
+ public static void SetImagePath(this IHasImages item, ImageType imageType, string path)
+ {
+ item.SetImagePath(imageType, 0, path);
+ }
+ }
+}
diff --git a/MediaBrowser.Controller/Entities/IHasLanguage.cs b/MediaBrowser.Controller/Entities/IHasLanguage.cs
deleted file mode 100644
index a1bb80098..000000000
--- a/MediaBrowser.Controller/Entities/IHasLanguage.cs
+++ /dev/null
@@ -1,15 +0,0 @@
-
-namespace MediaBrowser.Controller.Entities
-{
- /// <summary>
- /// Interface IHasLanguage
- /// </summary>
- public interface IHasLanguage
- {
- /// <summary>
- /// Gets or sets the language.
- /// </summary>
- /// <value>The language.</value>
- string Language { get; set; }
- }
-}
diff --git a/MediaBrowser.Controller/Entities/IHasPreferredMetadataLanguage.cs b/MediaBrowser.Controller/Entities/IHasPreferredMetadataLanguage.cs
new file mode 100644
index 000000000..e3a233e49
--- /dev/null
+++ b/MediaBrowser.Controller/Entities/IHasPreferredMetadataLanguage.cs
@@ -0,0 +1,21 @@
+
+namespace MediaBrowser.Controller.Entities
+{
+ /// <summary>
+ /// Interface IHasPreferredMetadataLanguage
+ /// </summary>
+ public interface IHasPreferredMetadataLanguage
+ {
+ /// <summary>
+ /// Gets or sets the preferred metadata language.
+ /// </summary>
+ /// <value>The preferred metadata language.</value>
+ string PreferredMetadataLanguage { get; set; }
+
+ /// <summary>
+ /// Gets or sets the preferred metadata country code.
+ /// </summary>
+ /// <value>The preferred metadata country code.</value>
+ string PreferredMetadataCountryCode { get; set; }
+ }
+}
diff --git a/MediaBrowser.Controller/Entities/IHasUserData.cs b/MediaBrowser.Controller/Entities/IHasUserData.cs
new file mode 100644
index 000000000..780181a61
--- /dev/null
+++ b/MediaBrowser.Controller/Entities/IHasUserData.cs
@@ -0,0 +1,15 @@
+
+namespace MediaBrowser.Controller.Entities
+{
+ /// <summary>
+ /// Interface IHasUserData
+ /// </summary>
+ public interface IHasUserData
+ {
+ /// <summary>
+ /// Gets the user data key.
+ /// </summary>
+ /// <returns>System.String.</returns>
+ string GetUserDataKey();
+ }
+}
diff --git a/MediaBrowser.Controller/Entities/IndexFolder.cs b/MediaBrowser.Controller/Entities/IndexFolder.cs
deleted file mode 100644
index 57e4a35d3..000000000
--- a/MediaBrowser.Controller/Entities/IndexFolder.cs
+++ /dev/null
@@ -1,206 +0,0 @@
-using MediaBrowser.Common.Extensions;
-using MediaBrowser.Model.Entities;
-using MoreLinq;
-using System.Collections.Concurrent;
-using System.Collections.Generic;
-using System.Linq;
-using System.Runtime.Serialization;
-using System.Threading;
-using System.Threading.Tasks;
-
-namespace MediaBrowser.Controller.Entities
-{
- /// <summary>
- /// Class IndexFolder
- /// </summary>
- public class IndexFolder : Folder
- {
- /// <summary>
- /// Initializes a new instance of the <see cref="IndexFolder" /> class.
- /// </summary>
- /// <param name="parent">The parent.</param>
- /// <param name="shadow">The shadow.</param>
- /// <param name="children">The children.</param>
- /// <param name="indexName">Name of the index.</param>
- /// <param name="groupContents">if set to <c>true</c> [group contents].</param>
- public IndexFolder(Folder parent, BaseItem shadow, IEnumerable<BaseItem> children, string indexName, bool groupContents = true)
- {
- ChildSource = children;
- ShadowItem = shadow;
- GroupContents = groupContents;
- if (shadow == null)
- {
- Name = ForcedSortName = "<Unknown>";
- }
- else
- {
- SetShadowValues();
- }
- Id = (parent.Id.ToString() + Name).GetMBId(typeof(IndexFolder));
-
- IndexName = indexName;
- Parent = parent;
- }
-
- /// <summary>
- /// Resets the parent.
- /// </summary>
- /// <param name="parent">The parent.</param>
- public void ResetParent(Folder parent)
- {
- Parent = parent;
- Id = (parent.Id.ToString() + Name).GetMBId(typeof(IndexFolder));
- }
-
- /// <summary>
- /// Override this to true if class should be grouped under a container in indicies
- /// The container class should be defined via IndexContainer
- /// </summary>
- /// <value><c>true</c> if [group in index]; otherwise, <c>false</c>.</value>
- [IgnoreDataMember]
- public override bool GroupInIndex
- {
- get
- {
- return ShadowItem != null && ShadowItem.GroupInIndex;
- }
- }
-
- public override LocationType LocationType
- {
- get
- {
- return LocationType.Virtual;
- }
- }
-
- /// <summary>
- /// Override this to return the folder that should be used to construct a container
- /// for this item in an index. GroupInIndex should be true as well.
- /// </summary>
- /// <value>The index container.</value>
- [IgnoreDataMember]
- public override Folder IndexContainer
- {
- get { return ShadowItem != null ? ShadowItem.IndexContainer : new IndexFolder(this, null, null, "<Unknown>", false); }
- }
-
- /// <summary>
- /// Gets or sets a value indicating whether [group contents].
- /// </summary>
- /// <value><c>true</c> if [group contents]; otherwise, <c>false</c>.</value>
- protected bool GroupContents { get; set; }
- /// <summary>
- /// Gets or sets the child source.
- /// </summary>
- /// <value>The child source.</value>
- protected IEnumerable<BaseItem> ChildSource { get; set; }
- /// <summary>
- /// Gets or sets our children.
- /// </summary>
- /// <value>Our children.</value>
- protected ConcurrentBag<BaseItem> OurChildren { get; set; }
- /// <summary>
- /// Gets the name of the index.
- /// </summary>
- /// <value>The name of the index.</value>
- public string IndexName { get; private set; }
-
- /// <summary>
- /// Override to return the children defined to us when we were created
- /// </summary>
- /// <value>The actual children.</value>
- protected override IEnumerable<BaseItem> LoadChildren()
- {
- var originalChildSource = ChildSource.ToList();
-
- var kids = originalChildSource;
- if (GroupContents)
- {
- // Recursively group up the chain
- var group = true;
- var isSubsequentLoop = false;
-
- while (group)
- {
- kids = isSubsequentLoop || kids.Any(i => i.GroupInIndex)
- ? GroupedSource(kids).ToList()
- : originalChildSource;
-
- group = kids.Any(i => i.GroupInIndex);
- isSubsequentLoop = true;
- }
- }
-
- // Now - since we built the index grouping from the bottom up - we now need to properly set Parents from the top down
- SetParents(this, kids.OfType<IndexFolder>());
-
- return kids.DistinctBy(i => i.Id);
- }
-
- /// <summary>
- /// Sets the parents.
- /// </summary>
- /// <param name="parent">The parent.</param>
- /// <param name="kids">The kids.</param>
- private void SetParents(Folder parent, IEnumerable<IndexFolder> kids)
- {
- foreach (var child in kids)
- {
- child.ResetParent(parent);
- child.SetParents(child, child.Children.OfType<IndexFolder>());
- }
- }
-
- /// <summary>
- /// Groupeds the source.
- /// </summary>
- /// <param name="source">The source.</param>
- /// <returns>IEnumerable{BaseItem}.</returns>
- protected IEnumerable<BaseItem> GroupedSource(IEnumerable<BaseItem> source)
- {
- return source.GroupBy(i => i.IndexContainer).Select(container => new IndexFolder(this, container.Key, container, null, false));
- }
-
- /// <summary>
- /// The item we are shadowing as a folder (Genre, Actor, etc.)
- /// We inherit the images and other meta from this item
- /// </summary>
- /// <value>The shadow item.</value>
- protected BaseItem ShadowItem { get; set; }
-
- /// <summary>
- /// Sets the shadow values.
- /// </summary>
- protected void SetShadowValues()
- {
- if (ShadowItem != null)
- {
- Name = ShadowItem.Name;
- ForcedSortName = ShadowItem.SortName;
- Genres = ShadowItem.Genres;
- Studios = ShadowItem.Studios;
- OfficialRating = ShadowItem.OfficialRatingForComparison;
- BackdropImagePaths = ShadowItem.BackdropImagePaths;
- Images = ShadowItem.Images;
- Overview = ShadowItem.Overview;
- DisplayMediaType = ShadowItem.GetType().Name;
- }
- }
-
- /// <summary>
- /// Overrides the base implementation to refresh metadata for local trailers
- /// </summary>
- /// <param name="cancellationToken">The cancellation token.</param>
- /// <param name="forceSave">if set to <c>true</c> [is new item].</param>
- /// <param name="forceRefresh">if set to <c>true</c> [force].</param>
- /// <param name="allowSlowProviders">if set to <c>true</c> [allow slow providers].</param>
- /// <param name="resetResolveArgs">if set to <c>true</c> [reset resolve args].</param>
- /// <returns>Task{System.Boolean}.</returns>
- public override Task<bool> RefreshMetadata(CancellationToken cancellationToken, bool forceSave = false, bool forceRefresh = false, bool allowSlowProviders = true, bool resetResolveArgs = true)
- {
- // We should never get in here since these are not part of the library
- return Task.FromResult(false);
- }
- }
-}
diff --git a/MediaBrowser.Controller/Entities/Movies/BoxSet.cs b/MediaBrowser.Controller/Entities/Movies/BoxSet.cs
index a1154482c..6144bdd71 100644
--- a/MediaBrowser.Controller/Entities/Movies/BoxSet.cs
+++ b/MediaBrowser.Controller/Entities/Movies/BoxSet.cs
@@ -1,5 +1,6 @@
-using System;
+using MediaBrowser.Model.Configuration;
using MediaBrowser.Model.Entities;
+using System;
using System.Collections.Generic;
namespace MediaBrowser.Controller.Entities.Movies
@@ -7,7 +8,7 @@ namespace MediaBrowser.Controller.Entities.Movies
/// <summary>
/// Class BoxSet
/// </summary>
- public class BoxSet : Folder, IHasTrailers, IHasTags
+ public class BoxSet : Folder, IHasTrailers, IHasTags, IHasPreferredMetadataLanguage
{
public BoxSet()
{
@@ -29,5 +30,18 @@ namespace MediaBrowser.Controller.Entities.Movies
/// </summary>
/// <value>The tags.</value>
public List<string> Tags { get; set; }
+
+ public string PreferredMetadataLanguage { get; set; }
+
+ /// <summary>
+ /// Gets or sets the preferred metadata country code.
+ /// </summary>
+ /// <value>The preferred metadata country code.</value>
+ public string PreferredMetadataCountryCode { get; set; }
+
+ protected override bool GetBlockUnratedValue(UserConfiguration config)
+ {
+ return config.BlockUnratedMovies;
+ }
}
}
diff --git a/MediaBrowser.Controller/Entities/Movies/Movie.cs b/MediaBrowser.Controller/Entities/Movies/Movie.cs
index b4cf6c047..f9d3f845c 100644
--- a/MediaBrowser.Controller/Entities/Movies/Movie.cs
+++ b/MediaBrowser.Controller/Entities/Movies/Movie.cs
@@ -1,4 +1,5 @@
-using MediaBrowser.Model.Entities;
+using MediaBrowser.Model.Configuration;
+using MediaBrowser.Model.Entities;
using System;
using System.Collections.Generic;
using System.IO;
@@ -11,7 +12,7 @@ namespace MediaBrowser.Controller.Entities.Movies
/// <summary>
/// Class Movie
/// </summary>
- public class Movie : Video, IHasCriticRating, IHasSoundtracks, IHasBudget, IHasTrailers, IHasThemeMedia, IHasTaglines, IHasTags
+ public class Movie : Video, IHasCriticRating, IHasSoundtracks, IHasBudget, IHasTrailers, IHasThemeMedia, IHasTaglines, IHasTags, IHasPreferredMetadataLanguage
{
public List<Guid> SpecialFeatureIds { get; set; }
@@ -19,6 +20,14 @@ namespace MediaBrowser.Controller.Entities.Movies
public List<Guid> ThemeSongIds { get; set; }
public List<Guid> ThemeVideoIds { get; set; }
+
+ /// <summary>
+ /// Gets or sets the preferred metadata country code.
+ /// </summary>
+ /// <value>The preferred metadata country code.</value>
+ public string PreferredMetadataCountryCode { get; set; }
+
+ public string PreferredMetadataLanguage { get; set; }
public Movie()
{
@@ -180,5 +189,9 @@ namespace MediaBrowser.Controller.Entities.Movies
});
}
+ protected override bool GetBlockUnratedValue(UserConfiguration config)
+ {
+ return config.BlockUnratedMovies;
+ }
}
}
diff --git a/MediaBrowser.Controller/Entities/MusicVideo.cs b/MediaBrowser.Controller/Entities/MusicVideo.cs
index 68ad4630a..d9eff8fbe 100644
--- a/MediaBrowser.Controller/Entities/MusicVideo.cs
+++ b/MediaBrowser.Controller/Entities/MusicVideo.cs
@@ -1,4 +1,5 @@
using MediaBrowser.Controller.Entities.Audio;
+using MediaBrowser.Model.Configuration;
using MediaBrowser.Model.Entities;
using System;
@@ -48,5 +49,10 @@ namespace MediaBrowser.Controller.Entities
{
return this.GetProviderId(MetadataProviders.Tmdb) ?? this.GetProviderId(MetadataProviders.Imdb) ?? base.GetUserDataKey();
}
+
+ protected override bool GetBlockUnratedValue(UserConfiguration config)
+ {
+ return config.BlockUnratedMusic;
+ }
}
}
diff --git a/MediaBrowser.Controller/Entities/TV/Episode.cs b/MediaBrowser.Controller/Entities/TV/Episode.cs
index e9f250d2a..42897e09f 100644
--- a/MediaBrowser.Controller/Entities/TV/Episode.cs
+++ b/MediaBrowser.Controller/Entities/TV/Episode.cs
@@ -2,6 +2,7 @@
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
+using MediaBrowser.Model.Configuration;
namespace MediaBrowser.Controller.Entities.TV
{
@@ -41,6 +42,23 @@ namespace MediaBrowser.Controller.Entities.TV
public int? AirsBeforeEpisodeNumber { get; set; }
/// <summary>
+ /// Gets or sets the DVD season number.
+ /// </summary>
+ /// <value>The DVD season number.</value>
+ public int? DvdSeasonNumber { get; set; }
+ /// <summary>
+ /// Gets or sets the DVD episode number.
+ /// </summary>
+ /// <value>The DVD episode number.</value>
+ public float? DvdEpisodeNumber { get; set; }
+
+ /// <summary>
+ /// Gets or sets the absolute episode number.
+ /// </summary>
+ /// <value>The absolute episode number.</value>
+ public int? AbsoluteEpisodeNumber { get; set; }
+
+ /// <summary>
/// We want to group into series not show individually in an index
/// </summary>
/// <value><c>true</c> if [group in index]; otherwise, <c>false</c>.</value>
@@ -275,5 +293,10 @@ namespace MediaBrowser.Controller.Entities.TV
{
return new[] { Path };
}
+
+ protected override bool GetBlockUnratedValue(UserConfiguration config)
+ {
+ return config.BlockUnratedSeries;
+ }
}
}
diff --git a/MediaBrowser.Controller/Entities/TV/Season.cs b/MediaBrowser.Controller/Entities/TV/Season.cs
index 78e0b8bc4..2d781118e 100644
--- a/MediaBrowser.Controller/Entities/TV/Season.cs
+++ b/MediaBrowser.Controller/Entities/TV/Season.cs
@@ -1,5 +1,8 @@
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Localization;
+using MediaBrowser.Model.Configuration;
+using MediaBrowser.Model.Entities;
+using MediaBrowser.Model.Querying;
using System;
using System.Collections.Generic;
using System.IO;
@@ -55,13 +58,13 @@ namespace MediaBrowser.Controller.Entities.TV
}
// Genre, Rating and Stuido will all be the same
- protected override Dictionary<string, Func<User, IEnumerable<BaseItem>>> GetIndexByOptions()
+ protected override IEnumerable<string> GetIndexByOptions()
{
- return new Dictionary<string, Func<User, IEnumerable<BaseItem>>> {
- {LocalizedStrings.Instance.GetString("NoneDispPref"), null},
- {LocalizedStrings.Instance.GetString("PerformerDispPref"), GetIndexByPerformer},
- {LocalizedStrings.Instance.GetString("DirectorDispPref"), GetIndexByDirector},
- {LocalizedStrings.Instance.GetString("YearDispPref"), GetIndexByYear},
+ return new List<string> {
+ {LocalizedStrings.Instance.GetString("NoneDispPref")},
+ {LocalizedStrings.Instance.GetString("PerformerDispPref")},
+ {LocalizedStrings.Instance.GetString("DirectorDispPref")},
+ {LocalizedStrings.Instance.GetString("YearDispPref")},
};
}
@@ -186,7 +189,7 @@ namespace MediaBrowser.Controller.Entities.TV
[IgnoreDataMember]
public bool IsMissingSeason
{
- get { return LocationType == Model.Entities.LocationType.Virtual && GetEpisodes().All(i => i.IsMissingEpisode); }
+ get { return LocationType == LocationType.Virtual && GetEpisodes().All(i => i.IsMissingEpisode); }
}
[IgnoreDataMember]
@@ -198,13 +201,13 @@ namespace MediaBrowser.Controller.Entities.TV
[IgnoreDataMember]
public bool IsVirtualUnaired
{
- get { return LocationType == Model.Entities.LocationType.Virtual && IsUnaired; }
+ get { return LocationType == LocationType.Virtual && IsUnaired; }
}
[IgnoreDataMember]
public bool IsMissingOrVirtualUnaired
{
- get { return LocationType == Model.Entities.LocationType.Virtual && GetEpisodes().All(i => i.IsVirtualUnaired || i.IsMissingEpisode); }
+ get { return LocationType == LocationType.Virtual && GetEpisodes().All(i => i.IsVirtualUnaired || i.IsMissingEpisode); }
}
[IgnoreDataMember]
@@ -212,5 +215,57 @@ namespace MediaBrowser.Controller.Entities.TV
{
get { return (IndexNumber ?? -1) == 0; }
}
+
+ /// <summary>
+ /// Gets the episodes.
+ /// </summary>
+ /// <param name="user">The user.</param>
+ /// <returns>IEnumerable{Episode}.</returns>
+ public IEnumerable<Episode> GetEpisodes(User user)
+ {
+ var config = user.Configuration;
+
+ return GetEpisodes(user, config.DisplayMissingEpisodes, config.DisplayUnairedEpisodes);
+ }
+
+ public IEnumerable<Episode> GetEpisodes(User user, bool includeMissingEpisodes, bool includeVirtualUnairedEpisodes)
+ {
+ if (IndexNumber.HasValue)
+ {
+ var series = Series;
+
+ if (series != null)
+ {
+ return series.GetEpisodes(user, IndexNumber.Value, includeMissingEpisodes, includeVirtualUnairedEpisodes);
+ }
+ }
+
+ var episodes = GetRecursiveChildren(user)
+ .OfType<Episode>();
+
+ if (!includeMissingEpisodes)
+ {
+ episodes = episodes.Where(i => !i.IsMissingEpisode);
+ }
+ if (!includeVirtualUnairedEpisodes)
+ {
+ episodes = episodes.Where(i => !i.IsVirtualUnaired);
+ }
+
+ return LibraryManager
+ .Sort(episodes, user, new[] { ItemSortBy.SortName }, SortOrder.Ascending)
+ .Cast<Episode>();
+ }
+
+ public override IEnumerable<BaseItem> GetChildren(User user, bool includeLinkedChildren)
+ {
+ return GetEpisodes(user);
+ }
+
+ protected override bool GetBlockUnratedValue(UserConfiguration config)
+ {
+ // Don't block. Let either the entire series rating or episode rating determine it
+ return false;
+ }
}
}
diff --git a/MediaBrowser.Controller/Entities/TV/Series.cs b/MediaBrowser.Controller/Entities/TV/Series.cs
index 2312df2a1..f7e78ccd4 100644
--- a/MediaBrowser.Controller/Entities/TV/Series.cs
+++ b/MediaBrowser.Controller/Entities/TV/Series.cs
@@ -1,6 +1,8 @@
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Localization;
+using MediaBrowser.Model.Configuration;
using MediaBrowser.Model.Entities;
+using MediaBrowser.Model.Querying;
using System;
using System.Collections.Generic;
using System.IO;
@@ -12,13 +14,19 @@ namespace MediaBrowser.Controller.Entities.TV
/// <summary>
/// Class Series
/// </summary>
- public class Series : Folder, IHasSoundtracks, IHasTrailers, IHasTags
+ public class Series : Folder, IHasSoundtracks, IHasTrailers, IHasTags, IHasPreferredMetadataLanguage
{
public List<Guid> SpecialFeatureIds { get; set; }
public List<Guid> SoundtrackIds { get; set; }
public int SeasonCount { get; set; }
+ /// <summary>
+ /// Gets or sets the preferred metadata country code.
+ /// </summary>
+ /// <value>The preferred metadata country code.</value>
+ public string PreferredMetadataCountryCode { get; set; }
+
public Series()
{
AirDays = new List<DayOfWeek>();
@@ -28,8 +36,11 @@ namespace MediaBrowser.Controller.Entities.TV
RemoteTrailers = new List<MediaUrl>();
LocalTrailerIds = new List<Guid>();
Tags = new List<string>();
+ DisplaySpecialsWithSeasons = true;
}
+ public bool DisplaySpecialsWithSeasons { get; set; }
+
public List<Guid> LocalTrailerIds { get; set; }
public List<MediaUrl> RemoteTrailers { get; set; }
@@ -85,13 +96,13 @@ namespace MediaBrowser.Controller.Entities.TV
}
// Studio, Genre and Rating will all be the same so makes no sense to index by these
- protected override Dictionary<string, Func<User, IEnumerable<BaseItem>>> GetIndexByOptions()
+ protected override IEnumerable<string> GetIndexByOptions()
{
- return new Dictionary<string, Func<User, IEnumerable<BaseItem>>> {
- {LocalizedStrings.Instance.GetString("NoneDispPref"), null},
- {LocalizedStrings.Instance.GetString("PerformerDispPref"), GetIndexByPerformer},
- {LocalizedStrings.Instance.GetString("DirectorDispPref"), GetIndexByDirector},
- {LocalizedStrings.Instance.GetString("YearDispPref"), GetIndexByYear},
+ return new List<string> {
+ {LocalizedStrings.Instance.GetString("NoneDispPref")},
+ {LocalizedStrings.Instance.GetString("PerformerDispPref")},
+ {LocalizedStrings.Instance.GetString("DirectorDispPref")},
+ {LocalizedStrings.Instance.GetString("YearDispPref")},
};
}
@@ -117,5 +128,108 @@ namespace MediaBrowser.Controller.Entities.TV
return Children.OfType<Video>().Any();
}
}
+
+ public override IEnumerable<BaseItem> GetChildren(User user, bool includeLinkedChildren)
+ {
+ return GetSeasons(user);
+ }
+
+ public IEnumerable<Season> GetSeasons(User user)
+ {
+ var config = user.Configuration;
+
+ return GetSeasons(user, config.DisplayMissingEpisodes, config.DisplayUnairedEpisodes);
+ }
+
+ public IEnumerable<Season> GetSeasons(User user, bool includeMissingSeasons, bool includeVirtualUnaired)
+ {
+ var seasons = base.GetChildren(user, true)
+ .OfType<Season>();
+
+ if (!includeMissingSeasons && !includeVirtualUnaired)
+ {
+ seasons = seasons.Where(i => !i.IsMissingOrVirtualUnaired);
+ }
+ else
+ {
+ if (!includeMissingSeasons)
+ {
+ seasons = seasons.Where(i => !i.IsMissingSeason);
+ }
+ if (!includeVirtualUnaired)
+ {
+ seasons = seasons.Where(i => !i.IsVirtualUnaired);
+ }
+ }
+
+ return LibraryManager
+ .Sort(seasons, user, new[] { ItemSortBy.SortName }, SortOrder.Ascending)
+ .Cast<Season>();
+ }
+
+ public IEnumerable<Episode> GetEpisodes(User user, int seasonNumber)
+ {
+ var config = user.Configuration;
+
+ return GetEpisodes(user, seasonNumber, config.DisplayMissingEpisodes, config.DisplayUnairedEpisodes);
+ }
+
+ public IEnumerable<Episode> GetEpisodes(User user, int seasonNumber, bool includeMissingEpisodes, bool includeVirtualUnairedEpisodes)
+ {
+ var episodes = GetRecursiveChildren(user)
+ .OfType<Episode>();
+
+ episodes = FilterEpisodesBySeason(episodes, seasonNumber, DisplaySpecialsWithSeasons);
+
+ if (!includeMissingEpisodes)
+ {
+ episodes = episodes.Where(i => !i.IsMissingEpisode);
+ }
+ if (!includeVirtualUnairedEpisodes)
+ {
+ episodes = episodes.Where(i => !i.IsVirtualUnaired);
+ }
+
+ var sortBy = seasonNumber == 0 ? ItemSortBy.SortName : ItemSortBy.AiredEpisodeOrder;
+
+ return LibraryManager.Sort(episodes, user, new[] { sortBy }, SortOrder.Ascending)
+ .Cast<Episode>();
+ }
+
+ /// <summary>
+ /// Filters the episodes by season.
+ /// </summary>
+ /// <param name="episodes">The episodes.</param>
+ /// <param name="seasonNumber">The season number.</param>
+ /// <param name="includeSpecials">if set to <c>true</c> [include specials].</param>
+ /// <returns>IEnumerable{Episode}.</returns>
+ public static IEnumerable<Episode> FilterEpisodesBySeason(IEnumerable<Episode> episodes, int seasonNumber, bool includeSpecials)
+ {
+ if (!includeSpecials || seasonNumber < 1)
+ {
+ return episodes.Where(i => (i.PhysicalSeasonNumber ?? -1) == seasonNumber);
+ }
+
+ return episodes.Where(i =>
+ {
+ var episode = i;
+
+ if (episode != null)
+ {
+ var currentSeasonNumber = episode.AiredSeasonNumber;
+
+ return currentSeasonNumber.HasValue && currentSeasonNumber.Value == seasonNumber;
+ }
+
+ return false;
+ });
+ }
+
+ protected override bool GetBlockUnratedValue(UserConfiguration config)
+ {
+ return config.BlockUnratedSeries;
+ }
+
+ public string PreferredMetadataLanguage { get; set; }
}
}
diff --git a/MediaBrowser.Controller/Entities/Trailer.cs b/MediaBrowser.Controller/Entities/Trailer.cs
index 591fea14a..7000d04d3 100644
--- a/MediaBrowser.Controller/Entities/Trailer.cs
+++ b/MediaBrowser.Controller/Entities/Trailer.cs
@@ -1,4 +1,5 @@
-using MediaBrowser.Model.Entities;
+using MediaBrowser.Model.Configuration;
+using MediaBrowser.Model.Entities;
using System;
using System.Collections.Generic;
using System.Runtime.Serialization;
@@ -8,9 +9,17 @@ namespace MediaBrowser.Controller.Entities
/// <summary>
/// Class Trailer
/// </summary>
- public class Trailer : Video, IHasCriticRating, IHasSoundtracks, IHasBudget, IHasTrailers, IHasTaglines, IHasTags
+ public class Trailer : Video, IHasCriticRating, IHasSoundtracks, IHasBudget, IHasTrailers, IHasTaglines, IHasTags, IHasPreferredMetadataLanguage
{
public List<Guid> SoundtrackIds { get; set; }
+
+ public string PreferredMetadataLanguage { get; set; }
+
+ /// <summary>
+ /// Gets or sets the preferred metadata country code.
+ /// </summary>
+ /// <value>The preferred metadata country code.</value>
+ public string PreferredMetadataCountryCode { get; set; }
public Trailer()
{
@@ -113,5 +122,10 @@ namespace MediaBrowser.Controller.Entities
return base.GetUserDataKey();
}
+
+ protected override bool GetBlockUnratedValue(UserConfiguration config)
+ {
+ return config.BlockUnratedTrailers;
+ }
}
}
diff --git a/MediaBrowser.Controller/Entities/User.cs b/MediaBrowser.Controller/Entities/User.cs
index 06f50e552..466e709dd 100644
--- a/MediaBrowser.Controller/Entities/User.cs
+++ b/MediaBrowser.Controller/Entities/User.cs
@@ -1,5 +1,4 @@
using MediaBrowser.Common.Configuration;
-using MediaBrowser.Controller.IO;
using MediaBrowser.Controller.Library;
using MediaBrowser.Model.Configuration;
using MediaBrowser.Model.Serialization;
@@ -58,6 +57,7 @@ namespace MediaBrowser.Controller.Entities
/// Gets or sets the path.
/// </summary>
/// <value>The path.</value>
+ [IgnoreDataMember]
public override string Path
{
get
@@ -76,14 +76,6 @@ namespace MediaBrowser.Controller.Entities
/// </summary>
private UserRootFolder _rootFolder;
/// <summary>
- /// The _user root folder initialized
- /// </summary>
- private bool _userRootFolderInitialized;
- /// <summary>
- /// The _user root folder sync lock
- /// </summary>
- private object _userRootFolderSyncLock = new object();
- /// <summary>
/// Gets the root folder.
/// </summary>
/// <value>The root folder.</value>
@@ -92,17 +84,11 @@ namespace MediaBrowser.Controller.Entities
{
get
{
- LazyInitializer.EnsureInitialized(ref _rootFolder, ref _userRootFolderInitialized, ref _userRootFolderSyncLock, () => LibraryManager.GetUserRootFolder(RootFolderPath));
- return _rootFolder;
+ return _rootFolder ?? (LibraryManager.GetUserRootFolder(RootFolderPath));
}
private set
{
_rootFolder = value;
-
- if (_rootFolder == null)
- {
- _userRootFolderInitialized = false;
- }
}
}
@@ -154,22 +140,6 @@ namespace MediaBrowser.Controller.Entities
}
/// <summary>
- /// Gets the last date modified of the configuration
- /// </summary>
- /// <value>The configuration date last modified.</value>
- [IgnoreDataMember]
- public DateTime ConfigurationDateLastModified
- {
- get
- {
- // Ensure it's been lazy loaded
- var config = Configuration;
-
- return FileSystem.GetLastWriteTimeUtc(ConfigurationFilePath);
- }
- }
-
- /// <summary>
/// Reloads the root media folder
/// </summary>
/// <param name="cancellationToken">The cancellation token.</param>
@@ -203,13 +173,22 @@ namespace MediaBrowser.Controller.Entities
{
// Move configuration
var newConfigDirectory = GetConfigurationDirectoryPath(newName);
+ var oldConfigurationDirectory = ConfigurationDirectoryPath;
// Exceptions will be thrown if these paths already exist
if (Directory.Exists(newConfigDirectory))
{
Directory.Delete(newConfigDirectory, true);
}
- Directory.Move(ConfigurationDirectoryPath, newConfigDirectory);
+
+ if (Directory.Exists(oldConfigurationDirectory))
+ {
+ Directory.Move(oldConfigurationDirectory, newConfigDirectory);
+ }
+ else
+ {
+ Directory.CreateDirectory(newConfigDirectory);
+ }
var customLibraryPath = GetRootFolderPath(Name);
@@ -228,7 +207,6 @@ namespace MediaBrowser.Controller.Entities
Name = newName;
// Force these to be lazy loaded again
- _configurationDirectoryPath = null;
RootFolder = null;
// Kick off a task to validate the media library
@@ -238,25 +216,15 @@ namespace MediaBrowser.Controller.Entities
}
/// <summary>
- /// The _configuration directory path
- /// </summary>
- private string _configurationDirectoryPath;
- /// <summary>
/// Gets the path to the user's configuration directory
/// </summary>
/// <value>The configuration directory path.</value>
+ [IgnoreDataMember]
private string ConfigurationDirectoryPath
{
get
{
- if (_configurationDirectoryPath == null)
- {
- _configurationDirectoryPath = GetConfigurationDirectoryPath(Name);
-
- Directory.CreateDirectory(_configurationDirectoryPath);
- }
-
- return _configurationDirectoryPath;
+ return GetConfigurationDirectoryPath(Name);
}
}
@@ -267,6 +235,11 @@ namespace MediaBrowser.Controller.Entities
/// <returns>System.String.</returns>
private string GetConfigurationDirectoryPath(string username)
{
+ if (string.IsNullOrEmpty(username))
+ {
+ throw new ArgumentNullException("username");
+ }
+
var safeFolderName = FileSystem.GetValidFilename(username);
return System.IO.Path.Combine(ConfigurationManager.ApplicationPaths.UserConfigurationDirectoryPath, safeFolderName);
@@ -276,6 +249,7 @@ namespace MediaBrowser.Controller.Entities
/// Gets the path to the user's configuration file
/// </summary>
/// <value>The configuration file path.</value>
+ [IgnoreDataMember]
public string ConfigurationFilePath
{
get
@@ -289,7 +263,9 @@ namespace MediaBrowser.Controller.Entities
/// </summary>
public void SaveConfiguration(IXmlSerializer serializer)
{
- serializer.SerializeToFile(Configuration, ConfigurationFilePath);
+ var xmlPath = ConfigurationFilePath;
+ Directory.CreateDirectory(System.IO.Path.GetDirectoryName(xmlPath));
+ serializer.SerializeToFile(Configuration, xmlPath);
}
/// <summary>
diff --git a/MediaBrowser.Controller/IServerApplicationPaths.cs b/MediaBrowser.Controller/IServerApplicationPaths.cs
index 57cb7c7dd..f8b78299c 100644
--- a/MediaBrowser.Controller/IServerApplicationPaths.cs
+++ b/MediaBrowser.Controller/IServerApplicationPaths.cs
@@ -26,7 +26,7 @@ namespace MediaBrowser.Controller
/// Gets the path to the Images By Name directory
/// </summary>
/// <value>The images by name path.</value>
- string ItemsByNamePath { get; set; }
+ string ItemsByNamePath { get; }
/// <summary>
/// Gets the path to the People directory
@@ -51,13 +51,13 @@ namespace MediaBrowser.Controller
/// </summary>
/// <value>The game genre path.</value>
string GameGenrePath { get; }
-
+
/// <summary>
/// Gets the artists path.
/// </summary>
/// <value>The artists path.</value>
string ArtistsPath { get; }
-
+
/// <summary>
/// Gets the path to the Studio directory
/// </summary>
@@ -87,7 +87,7 @@ namespace MediaBrowser.Controller
/// </summary>
/// <value>The media info images path.</value>
string MediaInfoImagesPath { get; }
-
+
/// <summary>
/// Gets the path to the user configuration directory
/// </summary>
diff --git a/MediaBrowser.Controller/Kernel.cs b/MediaBrowser.Controller/Kernel.cs
deleted file mode 100644
index 37a1648c1..000000000
--- a/MediaBrowser.Controller/Kernel.cs
+++ /dev/null
@@ -1,30 +0,0 @@
-using MediaBrowser.Controller.MediaInfo;
-
-namespace MediaBrowser.Controller
-{
- /// <summary>
- /// Class Kernel
- /// </summary>
- public class Kernel
- {
- /// <summary>
- /// Gets the instance.
- /// </summary>
- /// <value>The instance.</value>
- public static Kernel Instance { get; private set; }
-
- /// <summary>
- /// Gets the FFMPEG controller.
- /// </summary>
- /// <value>The FFMPEG controller.</value>
- public FFMpegManager FFMpegManager { get; set; }
-
- /// <summary>
- /// Creates a kernel based on a Data path, which is akin to our current programdata path
- /// </summary>
- public Kernel()
- {
- Instance = this;
- }
- }
-}
diff --git a/MediaBrowser.Controller/Library/ILibraryManager.cs b/MediaBrowser.Controller/Library/ILibraryManager.cs
index 338edd568..ae34621cb 100644
--- a/MediaBrowser.Controller/Library/ILibraryManager.cs
+++ b/MediaBrowser.Controller/Library/ILibraryManager.cs
@@ -116,6 +116,11 @@ namespace MediaBrowser.Controller.Library
Task ValidateMediaLibrary(IProgress<double> progress, CancellationToken cancellationToken);
/// <summary>
+ /// Queues the library scan.
+ /// </summary>
+ void QueueLibraryScan();
+
+ /// <summary>
/// Gets the default view.
/// </summary>
/// <returns>IEnumerable{VirtualFolderInfo}.</returns>
diff --git a/MediaBrowser.Controller/Library/IUserDataManager.cs b/MediaBrowser.Controller/Library/IUserDataManager.cs
index d6d5f99aa..2bec9e3de 100644
--- a/MediaBrowser.Controller/Library/IUserDataManager.cs
+++ b/MediaBrowser.Controller/Library/IUserDataManager.cs
@@ -25,7 +25,7 @@ namespace MediaBrowser.Controller.Library
/// <param name="reason">The reason.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>Task.</returns>
- Task SaveUserData(Guid userId, BaseItem item, UserItemData userData, UserDataSaveReason reason, CancellationToken cancellationToken);
+ Task SaveUserData(Guid userId, IHasUserData item, UserItemData userData, UserDataSaveReason reason, CancellationToken cancellationToken);
/// <summary>
/// Gets the user data.
diff --git a/MediaBrowser.Controller/Library/ItemResolveArgs.cs b/MediaBrowser.Controller/Library/ItemResolveArgs.cs
index e296e0f18..c34180eb6 100644
--- a/MediaBrowser.Controller/Library/ItemResolveArgs.cs
+++ b/MediaBrowser.Controller/Library/ItemResolveArgs.cs
@@ -130,7 +130,7 @@ namespace MediaBrowser.Controller.Library
{
get
{
- return IsDirectory && Path.Equals(_appPaths.RootFolderPath, StringComparison.OrdinalIgnoreCase);
+ return IsDirectory && string.Equals(Path, _appPaths.RootFolderPath, StringComparison.OrdinalIgnoreCase);
}
}
diff --git a/MediaBrowser.Controller/Library/TVUtils.cs b/MediaBrowser.Controller/Library/TVUtils.cs
index 65308bd10..d7504a86e 100644
--- a/MediaBrowser.Controller/Library/TVUtils.cs
+++ b/MediaBrowser.Controller/Library/TVUtils.cs
@@ -32,7 +32,9 @@ namespace MediaBrowser.Controller.Library
"sæson",
"temporada",
"saison",
- "staffel"
+ "staffel",
+ "series",
+ "сезон"
};
/// <summary>
@@ -122,6 +124,11 @@ namespace MediaBrowser.Controller.Library
{
var filename = Path.GetFileName(path);
+ if (string.Equals(path, "specials", StringComparison.OrdinalIgnoreCase))
+ {
+ return 0;
+ }
+
// Look for one of the season folder names
foreach (var name in SeasonFolderNames)
{
diff --git a/MediaBrowser.Controller/Library/UserDataSaveEventArgs.cs b/MediaBrowser.Controller/Library/UserDataSaveEventArgs.cs
index 87e7f647a..ba328ff75 100644
--- a/MediaBrowser.Controller/Library/UserDataSaveEventArgs.cs
+++ b/MediaBrowser.Controller/Library/UserDataSaveEventArgs.cs
@@ -37,6 +37,6 @@ namespace MediaBrowser.Controller.Library
/// Gets or sets the item.
/// </summary>
/// <value>The item.</value>
- public BaseItem Item { get; set; }
+ public IHasUserData Item { get; set; }
}
}
diff --git a/MediaBrowser.Controller/LiveTv/Channel.cs b/MediaBrowser.Controller/LiveTv/Channel.cs
deleted file mode 100644
index 8097cea1d..000000000
--- a/MediaBrowser.Controller/LiveTv/Channel.cs
+++ /dev/null
@@ -1,73 +0,0 @@
-using MediaBrowser.Controller.Entities;
-using MediaBrowser.Model.Dto;
-using MediaBrowser.Model.LiveTv;
-using System;
-using System.Collections.Generic;
-using System.Runtime.Serialization;
-
-namespace MediaBrowser.Controller.LiveTv
-{
- public class Channel : BaseItem, IItemByName
- {
- public Channel()
- {
- UserItemCountList = new List<ItemByNameCounts>();
- }
-
- /// <summary>
- /// Gets the user data key.
- /// </summary>
- /// <returns>System.String.</returns>
- public override string GetUserDataKey()
- {
- return "Channel-" + Name;
- }
-
- [IgnoreDataMember]
- public List<ItemByNameCounts> UserItemCountList { get; set; }
-
- /// <summary>
- /// Gets or sets the number.
- /// </summary>
- /// <value>The number.</value>
- public string ChannelNumber { get; set; }
-
- /// <summary>
- /// Get or sets the Id.
- /// </summary>
- /// <value>The id of the channel.</value>
- public string ChannelId { get; set; }
-
- /// <summary>
- /// Gets or sets the name of the service.
- /// </summary>
- /// <value>The name of the service.</value>
- public string ServiceName { get; set; }
-
- /// <summary>
- /// Gets or sets the type of the channel.
- /// </summary>
- /// <value>The type of the channel.</value>
- public ChannelType ChannelType { get; set; }
-
- protected override string CreateSortName()
- {
- double number = 0;
-
- if (!string.IsNullOrEmpty(ChannelNumber))
- {
- double.TryParse(ChannelNumber, out number);
- }
-
- return number.ToString("000-") + (Name ?? string.Empty);
- }
-
- public override string MediaType
- {
- get
- {
- return ChannelType == ChannelType.Radio ? Model.Entities.MediaType.Audio : Model.Entities.MediaType.Video;
- }
- }
- }
-}
diff --git a/MediaBrowser.Controller/LiveTv/ChannelInfo.cs b/MediaBrowser.Controller/LiveTv/ChannelInfo.cs
index 27fc59630..cdc9c76c8 100644
--- a/MediaBrowser.Controller/LiveTv/ChannelInfo.cs
+++ b/MediaBrowser.Controller/LiveTv/ChannelInfo.cs
@@ -30,5 +30,24 @@ namespace MediaBrowser.Controller.LiveTv
/// </summary>
/// <value>The type of the channel.</value>
public ChannelType ChannelType { get; set; }
+
+ /// <summary>
+ /// Supply the image path if it can be accessed directly from the file system
+ /// </summary>
+ /// <value>The image path.</value>
+ public string ImagePath { get; set; }
+
+ /// <summary>
+ /// Supply the image url if it can be downloaded
+ /// </summary>
+ /// <value>The image URL.</value>
+ public string ImageUrl { get; set; }
+
+ /// <summary>
+ /// Gets or sets a value indicating whether this instance has image.
+ /// </summary>
+ /// <value><c>null</c> if [has image] contains no value, <c>true</c> if [has image]; otherwise, <c>false</c>.</value>
+ public bool? HasImage { get; set; }
+
}
}
diff --git a/MediaBrowser.Controller/LiveTv/ILiveTvManager.cs b/MediaBrowser.Controller/LiveTv/ILiveTvManager.cs
index b8dfbe05d..c26e29d94 100644
--- a/MediaBrowser.Controller/LiveTv/ILiveTvManager.cs
+++ b/MediaBrowser.Controller/LiveTv/ILiveTvManager.cs
@@ -1,4 +1,6 @@
-using MediaBrowser.Model.LiveTv;
+using System.IO;
+using MediaBrowser.Controller.Entities;
+using MediaBrowser.Model.LiveTv;
using MediaBrowser.Model.Querying;
using System.Collections.Generic;
using System.Threading;
@@ -24,13 +26,21 @@ namespace MediaBrowser.Controller.LiveTv
IReadOnlyList<ILiveTvService> Services { get; }
/// <summary>
- /// Schedules the recording.
+ /// Gets the new timer defaults asynchronous.
/// </summary>
- /// <param name="programId">The program identifier.</param>
- /// <returns>Task.</returns>
- Task ScheduleRecording(string programId);
+ /// <param name="cancellationToken">The cancellation token.</param>
+ /// <returns>Task{TimerInfo}.</returns>
+ Task<SeriesTimerInfoDto> GetNewTimerDefaults(CancellationToken cancellationToken);
/// <summary>
+ /// Gets the new timer defaults.
+ /// </summary>
+ /// <param name="programId">The program identifier.</param>
+ /// <param name="cancellationToken">The cancellation token.</param>
+ /// <returns>Task{SeriesTimerInfoDto}.</returns>
+ Task<SeriesTimerInfoDto> GetNewTimerDefaults(string programId, CancellationToken cancellationToken);
+
+ /// <summary>
/// Deletes the recording.
/// </summary>
/// <param name="id">The identifier.</param>
@@ -45,6 +55,13 @@ namespace MediaBrowser.Controller.LiveTv
Task CancelTimer(string id);
/// <summary>
+ /// Cancels the series timer.
+ /// </summary>
+ /// <param name="id">The identifier.</param>
+ /// <returns>Task.</returns>
+ Task CancelSeriesTimer(string id);
+
+ /// <summary>
/// Adds the parts.
/// </summary>
/// <param name="services">The services.</param>
@@ -54,18 +71,29 @@ namespace MediaBrowser.Controller.LiveTv
/// Gets the channels.
/// </summary>
/// <param name="query">The query.</param>
+ /// <param name="cancellationToken">The cancellation token.</param>
/// <returns>IEnumerable{Channel}.</returns>
- QueryResult<ChannelInfoDto> GetChannels(ChannelQuery query);
+ Task<QueryResult<ChannelInfoDto>> GetChannels(ChannelQuery query, CancellationToken cancellationToken);
/// <summary>
/// Gets the recording.
/// </summary>
/// <param name="id">The identifier.</param>
+ /// <param name="user">The user.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>Task{RecordingInfoDto}.</returns>
- Task<RecordingInfoDto> GetRecording(string id, CancellationToken cancellationToken);
+ Task<RecordingInfoDto> GetRecording(string id, CancellationToken cancellationToken, User user = null);
/// <summary>
+ /// Gets the channel.
+ /// </summary>
+ /// <param name="id">The identifier.</param>
+ /// <param name="cancellationToken">The cancellation token.</param>
+ /// <param name="user">The user.</param>
+ /// <returns>Task{RecordingInfoDto}.</returns>
+ Task<ChannelInfoDto> GetChannel(string id, CancellationToken cancellationToken, User user = null);
+
+ /// <summary>
/// Gets the timer.
/// </summary>
/// <param name="id">The identifier.</param>
@@ -74,6 +102,14 @@ namespace MediaBrowser.Controller.LiveTv
Task<TimerInfoDto> GetTimer(string id, CancellationToken cancellationToken);
/// <summary>
+ /// Gets the series timer.
+ /// </summary>
+ /// <param name="id">The identifier.</param>
+ /// <param name="cancellationToken">The cancellation token.</param>
+ /// <returns>Task{TimerInfoDto}.</returns>
+ Task<SeriesTimerInfoDto> GetSeriesTimer(string id, CancellationToken cancellationToken);
+
+ /// <summary>
/// Gets the recordings.
/// </summary>
/// <param name="query">The query.</param>
@@ -90,26 +126,106 @@ namespace MediaBrowser.Controller.LiveTv
Task<QueryResult<TimerInfoDto>> GetTimers(TimerQuery query, CancellationToken cancellationToken);
/// <summary>
+ /// Gets the series timers.
+ /// </summary>
+ /// <param name="query">The query.</param>
+ /// <param name="cancellationToken">The cancellation token.</param>
+ /// <returns>Task{QueryResult{SeriesTimerInfoDto}}.</returns>
+ Task<QueryResult<SeriesTimerInfoDto>> GetSeriesTimers(SeriesTimerQuery query, CancellationToken cancellationToken);
+
+ /// <summary>
/// Gets the channel.
/// </summary>
/// <param name="id">The identifier.</param>
/// <returns>Channel.</returns>
- Channel GetChannel(string id);
+ LiveTvChannel GetInternalChannel(string id);
/// <summary>
- /// Gets the channel.
+ /// Gets the internal program.
/// </summary>
/// <param name="id">The identifier.</param>
- /// <param name="userId">The user identifier.</param>
- /// <returns>Channel.</returns>
- ChannelInfoDto GetChannelInfoDto(string id, string userId);
+ /// <returns>LiveTvProgram.</returns>
+ LiveTvProgram GetInternalProgram(string id);
+
+ /// <summary>
+ /// Gets the recording.
+ /// </summary>
+ /// <param name="id">The identifier.</param>
+ /// <param name="cancellationToken">The cancellation token.</param>
+ /// <returns>LiveTvRecording.</returns>
+ Task<LiveTvRecording> GetInternalRecording(string id, CancellationToken cancellationToken);
+
+ /// <summary>
+ /// Gets the recording stream.
+ /// </summary>
+ /// <param name="id">The identifier.</param>
+ /// <param name="cancellationToken">The cancellation token.</param>
+ /// <returns>Task{Stream}.</returns>
+ Task<StreamResponseInfo> GetRecordingStream(string id, CancellationToken cancellationToken);
/// <summary>
+ /// Gets the channel stream.
+ /// </summary>
+ /// <param name="id">The identifier.</param>
+ /// <param name="cancellationToken">The cancellation token.</param>
+ /// <returns>Task{StreamResponseInfo}.</returns>
+ Task<StreamResponseInfo> GetChannelStream(string id, CancellationToken cancellationToken);
+
+ /// <summary>
+ /// Gets the program.
+ /// </summary>
+ /// <param name="id">The identifier.</param>
+ /// <param name="cancellationToken">The cancellation token.</param>
+ /// <param name="user">The user.</param>
+ /// <returns>Task{ProgramInfoDto}.</returns>
+ Task<ProgramInfoDto> GetProgram(string id, CancellationToken cancellationToken, User user = null);
+
+ /// <summary>
/// Gets the programs.
/// </summary>
/// <param name="query">The query.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>IEnumerable{ProgramInfo}.</returns>
Task<QueryResult<ProgramInfoDto>> GetPrograms(ProgramQuery query, CancellationToken cancellationToken);
+
+ /// <summary>
+ /// Updates the timer.
+ /// </summary>
+ /// <param name="timer">The timer.</param>
+ /// <param name="cancellationToken">The cancellation token.</param>
+ /// <returns>Task.</returns>
+ Task UpdateTimer(TimerInfoDto timer, CancellationToken cancellationToken);
+
+ /// <summary>
+ /// Updates the timer.
+ /// </summary>
+ /// <param name="timer">The timer.</param>
+ /// <param name="cancellationToken">The cancellation token.</param>
+ /// <returns>Task.</returns>
+ Task UpdateSeriesTimer(SeriesTimerInfoDto timer, CancellationToken cancellationToken);
+
+ /// <summary>
+ /// Creates the timer.
+ /// </summary>
+ /// <param name="timer">The timer.</param>
+ /// <param name="cancellationToken">The cancellation token.</param>
+ /// <returns>Task.</returns>
+ Task CreateTimer(TimerInfoDto timer, CancellationToken cancellationToken);
+
+ /// <summary>
+ /// Creates the series timer.
+ /// </summary>
+ /// <param name="timer">The timer.</param>
+ /// <param name="cancellationToken">The cancellation token.</param>
+ /// <returns>Task.</returns>
+ Task CreateSeriesTimer(SeriesTimerInfoDto timer, CancellationToken cancellationToken);
+
+ /// <summary>
+ /// Gets the recording groups.
+ /// </summary>
+ /// <param name="query">The query.</param>
+ /// <param name="cancellationToken">The cancellation token.</param>
+ /// <returns>Task{QueryResult{RecordingGroupDto}}.</returns>
+ Task<QueryResult<RecordingGroupDto>> GetRecordingGroups(RecordingGroupQuery query, CancellationToken cancellationToken);
}
}
diff --git a/MediaBrowser.Controller/LiveTv/ILiveTvService.cs b/MediaBrowser.Controller/LiveTv/ILiveTvService.cs
index a6c60d468..f8efbce63 100644
--- a/MediaBrowser.Controller/LiveTv/ILiveTvService.cs
+++ b/MediaBrowser.Controller/LiveTv/ILiveTvService.cs
@@ -1,5 +1,4 @@
-using MediaBrowser.Common.Net;
-using System.Collections.Generic;
+using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
@@ -32,6 +31,14 @@ namespace MediaBrowser.Controller.LiveTv
Task CancelTimerAsync(string timerId, CancellationToken cancellationToken);
/// <summary>
+ /// Cancels the series timer asynchronous.
+ /// </summary>
+ /// <param name="timerId">The timer identifier.</param>
+ /// <param name="cancellationToken">The cancellation token.</param>
+ /// <returns>Task.</returns>
+ Task CancelSeriesTimerAsync(string timerId, CancellationToken cancellationToken);
+
+ /// <summary>
/// Deletes the recording asynchronous.
/// </summary>
/// <param name="recordingId">The recording identifier.</param>
@@ -56,6 +63,14 @@ namespace MediaBrowser.Controller.LiveTv
Task CreateSeriesTimerAsync(SeriesTimerInfo info, CancellationToken cancellationToken);
/// <summary>
+ /// Updates the timer asynchronous.
+ /// </summary>
+ /// <param name="info">The information.</param>
+ /// <param name="cancellationToken">The cancellation token.</param>
+ /// <returns>Task.</returns>
+ Task UpdateTimerAsync(TimerInfo info, CancellationToken cancellationToken);
+
+ /// <summary>
/// Updates the series timer asynchronous.
/// </summary>
/// <param name="info">The information.</param>
@@ -64,20 +79,29 @@ namespace MediaBrowser.Controller.LiveTv
Task UpdateSeriesTimerAsync(SeriesTimerInfo info, CancellationToken cancellationToken);
/// <summary>
- /// Gets the channel image asynchronous.
+ /// Gets the channel image asynchronous. This only needs to be implemented if an image path or url cannot be supplied to ChannelInfo
/// </summary>
/// <param name="channelId">The channel identifier.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>Task{Stream}.</returns>
- Task<ImageResponseInfo> GetChannelImageAsync(string channelId, CancellationToken cancellationToken);
+ Task<StreamResponseInfo> GetChannelImageAsync(string channelId, CancellationToken cancellationToken);
+
+ /// <summary>
+ /// Gets the recording image asynchronous. This only needs to be implemented if an image path or url cannot be supplied to RecordingInfo
+ /// </summary>
+ /// <param name="recordingId">The recording identifier.</param>
+ /// <param name="cancellationToken">The cancellation token.</param>
+ /// <returns>Task{ImageResponseInfo}.</returns>
+ Task<StreamResponseInfo> GetRecordingImageAsync(string recordingId, CancellationToken cancellationToken);
/// <summary>
- /// Gets the program image asynchronous.
+ /// Gets the program image asynchronous. This only needs to be implemented if an image path or url cannot be supplied to ProgramInfo
/// </summary>
/// <param name="programId">The program identifier.</param>
+ /// <param name="channelId">The channel identifier.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>Task{ImageResponseInfo}.</returns>
- Task<ImageResponseInfo> GetProgramImageAsync(string programId, CancellationToken cancellationToken);
+ Task<StreamResponseInfo> GetProgramImageAsync(string programId, string channelId, CancellationToken cancellationToken);
/// <summary>
/// Gets the recordings asynchronous.
@@ -94,6 +118,13 @@ namespace MediaBrowser.Controller.LiveTv
Task<IEnumerable<TimerInfo>> GetTimersAsync(CancellationToken cancellationToken);
/// <summary>
+ /// Gets the timer defaults asynchronous.
+ /// </summary>
+ /// <param name="cancellationToken">The cancellation token.</param>
+ /// <returns>Task{TimerInfo}.</returns>
+ Task<SeriesTimerInfo> GetNewTimerDefaultsAsync(CancellationToken cancellationToken);
+
+ /// <summary>
/// Gets the series timers asynchronous.
/// </summary>
/// <param name="cancellationToken">The cancellation token.</param>
@@ -107,5 +138,21 @@ namespace MediaBrowser.Controller.LiveTv
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>Task{IEnumerable{ProgramInfo}}.</returns>
Task<IEnumerable<ProgramInfo>> GetProgramsAsync(string channelId, CancellationToken cancellationToken);
+
+ /// <summary>
+ /// Gets the recording stream.
+ /// </summary>
+ /// <param name="recordingId">The recording identifier.</param>
+ /// <param name="cancellationToken">The cancellation token.</param>
+ /// <returns>Task{Stream}.</returns>
+ Task<StreamResponseInfo> GetRecordingStream(string recordingId, CancellationToken cancellationToken);
+
+ /// <summary>
+ /// Gets the channel stream.
+ /// </summary>
+ /// <param name="channelId">The channel identifier.</param>
+ /// <param name="cancellationToken">The cancellation token.</param>
+ /// <returns>Task{Stream}.</returns>
+ Task<StreamResponseInfo> GetChannelStream(string channelId, CancellationToken cancellationToken);
}
}
diff --git a/MediaBrowser.Controller/LiveTv/LiveTvChannel.cs b/MediaBrowser.Controller/LiveTv/LiveTvChannel.cs
new file mode 100644
index 000000000..1e6d74ce8
--- /dev/null
+++ b/MediaBrowser.Controller/LiveTv/LiveTvChannel.cs
@@ -0,0 +1,57 @@
+using MediaBrowser.Controller.Entities;
+using MediaBrowser.Model.Dto;
+using MediaBrowser.Model.LiveTv;
+using System.Collections.Generic;
+using System.Runtime.Serialization;
+
+namespace MediaBrowser.Controller.LiveTv
+{
+ public class LiveTvChannel : BaseItem, IItemByName
+ {
+ public LiveTvChannel()
+ {
+ UserItemCountList = new List<ItemByNameCounts>();
+ }
+
+ /// <summary>
+ /// Gets the user data key.
+ /// </summary>
+ /// <returns>System.String.</returns>
+ public override string GetUserDataKey()
+ {
+ return GetClientTypeName() + "-" + Name;
+ }
+
+ [IgnoreDataMember]
+ public List<ItemByNameCounts> UserItemCountList { get; set; }
+
+ public ChannelInfo ChannelInfo { get; set; }
+
+ public string ServiceName { get; set; }
+
+ protected override string CreateSortName()
+ {
+ double number = 0;
+
+ if (!string.IsNullOrEmpty(ChannelInfo.Number))
+ {
+ double.TryParse(ChannelInfo.Number, out number);
+ }
+
+ return number.ToString("000-") + (Name ?? string.Empty);
+ }
+
+ public override string MediaType
+ {
+ get
+ {
+ return ChannelInfo.ChannelType == ChannelType.Radio ? Model.Entities.MediaType.Audio : Model.Entities.MediaType.Video;
+ }
+ }
+
+ public override string GetClientTypeName()
+ {
+ return "Channel";
+ }
+ }
+}
diff --git a/MediaBrowser.Controller/LiveTv/LiveTvException.cs b/MediaBrowser.Controller/LiveTv/LiveTvException.cs
new file mode 100644
index 000000000..0a68180ca
--- /dev/null
+++ b/MediaBrowser.Controller/LiveTv/LiveTvException.cs
@@ -0,0 +1,18 @@
+using System;
+
+namespace MediaBrowser.Controller.LiveTv
+{
+ /// <summary>
+ /// Class LiveTvException.
+ /// </summary>
+ public class LiveTvException : Exception
+ {
+ }
+
+ /// <summary>
+ /// Class LiveTvConflictException.
+ /// </summary>
+ public class LiveTvConflictException : LiveTvException
+ {
+ }
+}
diff --git a/MediaBrowser.Controller/LiveTv/LiveTvProgram.cs b/MediaBrowser.Controller/LiveTv/LiveTvProgram.cs
new file mode 100644
index 000000000..abacc0c18
--- /dev/null
+++ b/MediaBrowser.Controller/LiveTv/LiveTvProgram.cs
@@ -0,0 +1,36 @@
+using MediaBrowser.Controller.Entities;
+using MediaBrowser.Model.LiveTv;
+
+namespace MediaBrowser.Controller.LiveTv
+{
+ public class LiveTvProgram : BaseItem
+ {
+ /// <summary>
+ /// Gets the user data key.
+ /// </summary>
+ /// <returns>System.String.</returns>
+ public override string GetUserDataKey()
+ {
+ return GetClientTypeName() + "-" + Name;
+ }
+
+ public ProgramInfo ProgramInfo { get; set; }
+
+ public ChannelType ChannelType { get; set; }
+
+ public string ServiceName { get; set; }
+
+ public override string MediaType
+ {
+ get
+ {
+ return ChannelType == ChannelType.TV ? Model.Entities.MediaType.Video : Model.Entities.MediaType.Audio;
+ }
+ }
+
+ public override string GetClientTypeName()
+ {
+ return "Program";
+ }
+ }
+}
diff --git a/MediaBrowser.Controller/LiveTv/LiveTvRecording.cs b/MediaBrowser.Controller/LiveTv/LiveTvRecording.cs
new file mode 100644
index 000000000..1c453ab5a
--- /dev/null
+++ b/MediaBrowser.Controller/LiveTv/LiveTvRecording.cs
@@ -0,0 +1,43 @@
+using MediaBrowser.Controller.Entities;
+using MediaBrowser.Model.Entities;
+using MediaBrowser.Model.LiveTv;
+
+namespace MediaBrowser.Controller.LiveTv
+{
+ public class LiveTvRecording : BaseItem
+ {
+ /// <summary>
+ /// Gets the user data key.
+ /// </summary>
+ /// <returns>System.String.</returns>
+ public override string GetUserDataKey()
+ {
+ return GetClientTypeName() + "-" + Name;
+ }
+
+ public RecordingInfo RecordingInfo { get; set; }
+
+ public string ServiceName { get; set; }
+
+ public override string MediaType
+ {
+ get
+ {
+ return RecordingInfo.ChannelType == ChannelType.Radio ? Model.Entities.MediaType.Audio : Model.Entities.MediaType.Video;
+ }
+ }
+
+ public override LocationType LocationType
+ {
+ get
+ {
+ return LocationType.Remote;
+ }
+ }
+
+ public override string GetClientTypeName()
+ {
+ return "Recording";
+ }
+ }
+}
diff --git a/MediaBrowser.Controller/LiveTv/ProgramInfo.cs b/MediaBrowser.Controller/LiveTv/ProgramInfo.cs
index 8059c1100..d672340e4 100644
--- a/MediaBrowser.Controller/LiveTv/ProgramInfo.cs
+++ b/MediaBrowser.Controller/LiveTv/ProgramInfo.cs
@@ -45,12 +45,6 @@ namespace MediaBrowser.Controller.LiveTv
public DateTime EndDate { get; set; }
/// <summary>
- /// Gets or sets the aspect ratio.
- /// </summary>
- /// <value>The aspect ratio.</value>
- public string AspectRatio { get; set; }
-
- /// <summary>
/// Genre of the program.
/// </summary>
public List<string> Genres { get; set; }
@@ -90,7 +84,67 @@ namespace MediaBrowser.Controller.LiveTv
/// </summary>
/// <value>The episode title.</value>
public string EpisodeTitle { get; set; }
-
+
+ /// <summary>
+ /// Supply the image path if it can be accessed directly from the file system
+ /// </summary>
+ /// <value>The image path.</value>
+ public string ImagePath { get; set; }
+
+ /// <summary>
+ /// Supply the image url if it can be downloaded
+ /// </summary>
+ /// <value>The image URL.</value>
+ public string ImageUrl { get; set; }
+
+ /// <summary>
+ /// Gets or sets a value indicating whether this instance is movie.
+ /// </summary>
+ /// <value><c>true</c> if this instance is movie; otherwise, <c>false</c>.</value>
+ public bool IsMovie { get; set; }
+
+ /// <summary>
+ /// Gets or sets a value indicating whether this instance is sports.
+ /// </summary>
+ /// <value><c>true</c> if this instance is sports; otherwise, <c>false</c>.</value>
+ public bool IsSports { get; set; }
+
+ /// <summary>
+ /// Gets or sets a value indicating whether this instance is series.
+ /// </summary>
+ /// <value><c>true</c> if this instance is series; otherwise, <c>false</c>.</value>
+ public bool IsSeries { get; set; }
+
+ /// <summary>
+ /// Gets or sets a value indicating whether this instance is live.
+ /// </summary>
+ /// <value><c>true</c> if this instance is live; otherwise, <c>false</c>.</value>
+ public bool IsLive { get; set; }
+
+ /// <summary>
+ /// Gets or sets a value indicating whether this instance is news.
+ /// </summary>
+ /// <value><c>true</c> if this instance is news; otherwise, <c>false</c>.</value>
+ public bool IsNews { get; set; }
+
+ /// <summary>
+ /// Gets or sets a value indicating whether this instance is kids.
+ /// </summary>
+ /// <value><c>true</c> if this instance is kids; otherwise, <c>false</c>.</value>
+ public bool IsKids { get; set; }
+
+ /// <summary>
+ /// Gets or sets a value indicating whether this instance is premiere.
+ /// </summary>
+ /// <value><c>true</c> if this instance is premiere; otherwise, <c>false</c>.</value>
+ public bool IsPremiere { get; set; }
+
+ /// <summary>
+ /// Gets or sets a value indicating whether this instance has image.
+ /// </summary>
+ /// <value><c>null</c> if [has image] contains no value, <c>true</c> if [has image]; otherwise, <c>false</c>.</value>
+ public bool? HasImage { get; set; }
+
public ProgramInfo()
{
Genres = new List<string>();
diff --git a/MediaBrowser.Controller/LiveTv/RecordingInfo.cs b/MediaBrowser.Controller/LiveTv/RecordingInfo.cs
index 1ffbb7e23..6a0d135c8 100644
--- a/MediaBrowser.Controller/LiveTv/RecordingInfo.cs
+++ b/MediaBrowser.Controller/LiveTv/RecordingInfo.cs
@@ -12,14 +12,15 @@ namespace MediaBrowser.Controller.LiveTv
public string Id { get; set; }
/// <summary>
- /// ChannelId of the recording.
+ /// Gets or sets the series timer identifier.
/// </summary>
- public string ChannelId { get; set; }
-
+ /// <value>The series timer identifier.</value>
+ public string SeriesTimerId { get; set; }
+
/// <summary>
- /// ChannelName of the recording.
+ /// ChannelId of the recording.
/// </summary>
- public string ChannelName { get; set; }
+ public string ChannelId { get; set; }
/// <summary>
/// Gets or sets the type of the channel.
@@ -39,6 +40,12 @@ namespace MediaBrowser.Controller.LiveTv
public string Path { get; set; }
/// <summary>
+ /// Gets or sets the URL.
+ /// </summary>
+ /// <value>The URL.</value>
+ public string Url { get; set; }
+
+ /// <summary>
/// Gets or sets the overview.
/// </summary>
/// <value>The overview.</value>
@@ -96,6 +103,48 @@ namespace MediaBrowser.Controller.LiveTv
public ProgramAudio? Audio { get; set; }
/// <summary>
+ /// Gets or sets a value indicating whether this instance is movie.
+ /// </summary>
+ /// <value><c>true</c> if this instance is movie; otherwise, <c>false</c>.</value>
+ public bool IsMovie { get; set; }
+
+ /// <summary>
+ /// Gets or sets a value indicating whether this instance is sports.
+ /// </summary>
+ /// <value><c>true</c> if this instance is sports; otherwise, <c>false</c>.</value>
+ public bool IsSports { get; set; }
+
+ /// <summary>
+ /// Gets or sets a value indicating whether this instance is series.
+ /// </summary>
+ /// <value><c>true</c> if this instance is series; otherwise, <c>false</c>.</value>
+ public bool IsSeries { get; set; }
+
+ /// <summary>
+ /// Gets or sets a value indicating whether this instance is live.
+ /// </summary>
+ /// <value><c>true</c> if this instance is live; otherwise, <c>false</c>.</value>
+ public bool IsLive { get; set; }
+
+ /// <summary>
+ /// Gets or sets a value indicating whether this instance is news.
+ /// </summary>
+ /// <value><c>true</c> if this instance is news; otherwise, <c>false</c>.</value>
+ public bool IsNews { get; set; }
+
+ /// <summary>
+ /// Gets or sets a value indicating whether this instance is kids.
+ /// </summary>
+ /// <value><c>true</c> if this instance is kids; otherwise, <c>false</c>.</value>
+ public bool IsKids { get; set; }
+
+ /// <summary>
+ /// Gets or sets a value indicating whether this instance is premiere.
+ /// </summary>
+ /// <value><c>true</c> if this instance is premiere; otherwise, <c>false</c>.</value>
+ public bool IsPremiere { get; set; }
+
+ /// <summary>
/// Gets or sets the official rating.
/// </summary>
/// <value>The official rating.</value>
@@ -107,6 +156,25 @@ namespace MediaBrowser.Controller.LiveTv
/// <value>The community rating.</value>
public float? CommunityRating { get; set; }
+ /// <summary>
+ /// Supply the image path if it can be accessed directly from the file system
+ /// </summary>
+ /// <value>The image path.</value>
+ public string ImagePath { get; set; }
+
+ /// <summary>
+ /// Supply the image url if it can be downloaded
+ /// </summary>
+ /// <value>The image URL.</value>
+ public string ImageUrl { get; set; }
+
+ /// <summary>
+ /// Gets or sets a value indicating whether this instance has image.
+ /// </summary>
+ /// <value><c>null</c> if [has image] contains no value, <c>true</c> if [has image]; otherwise, <c>false</c>.</value>
+ public bool? HasImage { get; set; }
+
+
public RecordingInfo()
{
Genres = new List<string>();
diff --git a/MediaBrowser.Controller/LiveTv/SeriesTimerInfo.cs b/MediaBrowser.Controller/LiveTv/SeriesTimerInfo.cs
index 44594882c..1be6549ff 100644
--- a/MediaBrowser.Controller/LiveTv/SeriesTimerInfo.cs
+++ b/MediaBrowser.Controller/LiveTv/SeriesTimerInfo.cs
@@ -1,5 +1,4 @@
-using MediaBrowser.Model.LiveTv;
-using System;
+using System;
using System.Collections.Generic;
namespace MediaBrowser.Controller.LiveTv
@@ -15,18 +14,13 @@ namespace MediaBrowser.Controller.LiveTv
/// ChannelId of the recording.
/// </summary>
public string ChannelId { get; set; }
-
- /// <summary>
- /// ChannelName of the recording.
- /// </summary>
- public string ChannelName { get; set; }
-
+
/// <summary>
/// Gets or sets the program identifier.
/// </summary>
/// <value>The program identifier.</value>
public string ProgramId { get; set; }
-
+
/// <summary>
/// Name of the recording.
/// </summary>
@@ -35,7 +29,7 @@ namespace MediaBrowser.Controller.LiveTv
/// <summary>
/// Description of the recording.
/// </summary>
- public string Description { get; set; }
+ public string Overview { get; set; }
/// <summary>
/// The start date of the recording, in UTC.
@@ -48,23 +42,23 @@ namespace MediaBrowser.Controller.LiveTv
public DateTime EndDate { get; set; }
/// <summary>
- /// Gets or sets the pre padding seconds.
+ /// Gets or sets a value indicating whether [record any time].
/// </summary>
- /// <value>The pre padding seconds.</value>
- public int PrePaddingSeconds { get; set; }
+ /// <value><c>true</c> if [record any time]; otherwise, <c>false</c>.</value>
+ public bool RecordAnyTime { get; set; }
/// <summary>
- /// Gets or sets the post padding seconds.
+ /// Gets or sets a value indicating whether [record any channel].
/// </summary>
- /// <value>The post padding seconds.</value>
- public int PostPaddingSeconds { get; set; }
+ /// <value><c>true</c> if [record any channel]; otherwise, <c>false</c>.</value>
+ public bool RecordAnyChannel { get; set; }
/// <summary>
- /// Gets or sets the type of the recurrence.
+ /// Gets or sets a value indicating whether [record new only].
/// </summary>
- /// <value>The type of the recurrence.</value>
- public RecurrenceType RecurrenceType { get; set; }
-
+ /// <value><c>true</c> if [record new only]; otherwise, <c>false</c>.</value>
+ public bool RecordNewOnly { get; set; }
+
/// <summary>
/// Gets or sets the days.
/// </summary>
@@ -77,6 +71,30 @@ namespace MediaBrowser.Controller.LiveTv
/// <value>The priority.</value>
public int Priority { get; set; }
+ /// <summary>
+ /// Gets or sets the pre padding seconds.
+ /// </summary>
+ /// <value>The pre padding seconds.</value>
+ public int PrePaddingSeconds { get; set; }
+
+ /// <summary>
+ /// Gets or sets the post padding seconds.
+ /// </summary>
+ /// <value>The post padding seconds.</value>
+ public int PostPaddingSeconds { get; set; }
+
+ /// <summary>
+ /// Gets or sets a value indicating whether this instance is pre padding required.
+ /// </summary>
+ /// <value><c>true</c> if this instance is pre padding required; otherwise, <c>false</c>.</value>
+ public bool IsPrePaddingRequired { get; set; }
+
+ /// <summary>
+ /// Gets or sets a value indicating whether this instance is post padding required.
+ /// </summary>
+ /// <value><c>true</c> if this instance is post padding required; otherwise, <c>false</c>.</value>
+ public bool IsPostPaddingRequired { get; set; }
+
public SeriesTimerInfo()
{
Days = new List<DayOfWeek>();
diff --git a/MediaBrowser.Controller/LiveTv/ImageResponseInfo.cs b/MediaBrowser.Controller/LiveTv/StreamResponseInfo.cs
index d454a1ef8..c3b438c5e 100644
--- a/MediaBrowser.Controller/LiveTv/ImageResponseInfo.cs
+++ b/MediaBrowser.Controller/LiveTv/StreamResponseInfo.cs
@@ -2,7 +2,7 @@
namespace MediaBrowser.Controller.LiveTv
{
- public class ImageResponseInfo
+ public class StreamResponseInfo
{
/// <summary>
/// Gets or sets the stream.
diff --git a/MediaBrowser.Controller/LiveTv/TimerInfo.cs b/MediaBrowser.Controller/LiveTv/TimerInfo.cs
index 26e5869ce..5d92a212f 100644
--- a/MediaBrowser.Controller/LiveTv/TimerInfo.cs
+++ b/MediaBrowser.Controller/LiveTv/TimerInfo.cs
@@ -22,11 +22,6 @@ namespace MediaBrowser.Controller.LiveTv
public string ChannelId { get; set; }
/// <summary>
- /// ChannelName of the recording.
- /// </summary>
- public string ChannelName { get; set; }
-
- /// <summary>
/// Gets or sets the program identifier.
/// </summary>
/// <value>The program identifier.</value>
@@ -40,7 +35,7 @@ namespace MediaBrowser.Controller.LiveTv
/// <summary>
/// Description of the recording.
/// </summary>
- public string Description { get; set; }
+ public string Overview { get; set; }
/// <summary>
/// The start date of the recording, in UTC.
@@ -59,27 +54,33 @@ namespace MediaBrowser.Controller.LiveTv
public RecordingStatus Status { get; set; }
/// <summary>
- /// Gets or sets the requested pre padding seconds.
+ /// Gets or sets the pre padding seconds.
/// </summary>
- /// <value>The requested pre padding seconds.</value>
- public int RequestedPrePaddingSeconds { get; set; }
+ /// <value>The pre padding seconds.</value>
+ public int PrePaddingSeconds { get; set; }
/// <summary>
- /// Gets or sets the requested post padding seconds.
+ /// Gets or sets the post padding seconds.
/// </summary>
- /// <value>The requested post padding seconds.</value>
- public int RequestedPostPaddingSeconds { get; set; }
+ /// <value>The post padding seconds.</value>
+ public int PostPaddingSeconds { get; set; }
/// <summary>
- /// Gets or sets the required pre padding seconds.
+ /// Gets or sets a value indicating whether this instance is pre padding required.
/// </summary>
- /// <value>The required pre padding seconds.</value>
- public int RequiredPrePaddingSeconds { get; set; }
+ /// <value><c>true</c> if this instance is pre padding required; otherwise, <c>false</c>.</value>
+ public bool IsPrePaddingRequired { get; set; }
/// <summary>
- /// Gets or sets the required post padding seconds.
+ /// Gets or sets a value indicating whether this instance is post padding required.
+ /// </summary>
+ /// <value><c>true</c> if this instance is post padding required; otherwise, <c>false</c>.</value>
+ public bool IsPostPaddingRequired { get; set; }
+
+ /// <summary>
+ /// Gets or sets the priority.
/// </summary>
- /// <value>The required post padding seconds.</value>
- public int RequiredPostPaddingSeconds { get; set; }
+ /// <value>The priority.</value>
+ public int Priority { get; set; }
}
}
diff --git a/MediaBrowser.Controller/MediaBrowser.Controller.csproj b/MediaBrowser.Controller/MediaBrowser.Controller.csproj
index f1f2aaf5e..0c5c0a5cd 100644
--- a/MediaBrowser.Controller/MediaBrowser.Controller.csproj
+++ b/MediaBrowser.Controller/MediaBrowser.Controller.csproj
@@ -9,12 +9,12 @@
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>MediaBrowser.Controller</RootNamespace>
<AssemblyName>MediaBrowser.Controller</AssemblyName>
- <TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<SolutionDir Condition="$(SolutionDir) == '' Or $(SolutionDir) == '*Undefined*'">..\</SolutionDir>
<RestorePackages>true</RestorePackages>
<ProductVersion>10.0.0</ProductVersion>
<SchemaVersion>2.0</SchemaVersion>
+ <TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
@@ -24,6 +24,7 @@
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
+ <TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
@@ -32,36 +33,21 @@
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
+ <TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
</PropertyGroup>
<PropertyGroup>
<RunPostBuildEvent>Always</RunPostBuildEvent>
</PropertyGroup>
- <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x86'">
- <DebugSymbols>true</DebugSymbols>
- <OutputPath>bin\x86\Debug\</OutputPath>
- <DefineConstants>DEBUG;TRACE</DefineConstants>
- <DebugType>full</DebugType>
- <PlatformTarget>x86</PlatformTarget>
- <ErrorReport>prompt</ErrorReport>
- <CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
- <WarningLevel>4</WarningLevel>
- <Optimize>false</Optimize>
- </PropertyGroup>
- <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x86'">
- <OutputPath>bin\x86\Release\</OutputPath>
- <DefineConstants>TRACE</DefineConstants>
- <Optimize>true</Optimize>
+ <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release Mono|AnyCPU' ">
<DebugType>pdbonly</DebugType>
- <PlatformTarget>x86</PlatformTarget>
+ <Optimize>true</Optimize>
+ <OutputPath>bin\Release Mono\</OutputPath>
+ <DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
- <CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
<WarningLevel>4</WarningLevel>
+ <TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
</PropertyGroup>
<ItemGroup>
- <Reference Include="ServiceStack.Interfaces, Version=1.0.0.0, Culture=neutral, processorArchitecture=MSIL">
- <SpecificVersion>False</SpecificVersion>
- <HintPath>..\ThirdParty\ServiceStack\ServiceStack.Interfaces.dll</HintPath>
- </Reference>
<Reference Include="System" />
<Reference Include="System.Core" />
<Reference Include="System.Data" />
@@ -74,6 +60,9 @@
<Reference Include="MoreLinq">
<HintPath>..\packages\morelinq.1.0.16006\lib\net35\MoreLinq.dll</HintPath>
</Reference>
+ <Reference Include="ServiceStack.Interfaces">
+ <HintPath>..\ThirdParty\ServiceStack\ServiceStack.Interfaces.dll</HintPath>
+ </Reference>
</ItemGroup>
<ItemGroup>
<Compile Include="..\SharedVersion.cs">
@@ -96,8 +85,9 @@
<Compile Include="Entities\IHasAspectRatio.cs" />
<Compile Include="Entities\IHasBudget.cs" />
<Compile Include="Entities\IHasCriticRating.cs" />
- <Compile Include="Entities\IHasLanguage.cs" />
+ <Compile Include="Entities\IHasImages.cs" />
<Compile Include="Entities\IHasMediaStreams.cs" />
+ <Compile Include="Entities\IHasPreferredMetadataLanguage.cs" />
<Compile Include="Entities\IHasProductionLocations.cs" />
<Compile Include="Entities\IHasScreenshots.cs" />
<Compile Include="Entities\IHasSoundtracks.cs" />
@@ -105,6 +95,7 @@
<Compile Include="Entities\IHasTags.cs" />
<Compile Include="Entities\IHasThemeMedia.cs" />
<Compile Include="Entities\IHasTrailers.cs" />
+ <Compile Include="Entities\IHasUserData.cs" />
<Compile Include="Entities\IItemByName.cs" />
<Compile Include="Entities\ILibraryItem.cs" />
<Compile Include="Entities\ImageSourceInfo.cs" />
@@ -117,11 +108,14 @@
<Compile Include="Library\ItemUpdateType.cs" />
<Compile Include="Library\IUserDataManager.cs" />
<Compile Include="Library\UserDataSaveEventArgs.cs" />
- <Compile Include="LiveTv\Channel.cs" />
+ <Compile Include="LiveTv\LiveTvChannel.cs" />
<Compile Include="LiveTv\ChannelInfo.cs" />
<Compile Include="LiveTv\ILiveTvManager.cs" />
<Compile Include="LiveTv\ILiveTvService.cs" />
- <Compile Include="LiveTv\ImageResponseInfo.cs" />
+ <Compile Include="LiveTv\LiveTvException.cs" />
+ <Compile Include="LiveTv\StreamResponseInfo.cs" />
+ <Compile Include="LiveTv\LiveTvProgram.cs" />
+ <Compile Include="LiveTv\LiveTvRecording.cs" />
<Compile Include="LiveTv\ProgramInfo.cs" />
<Compile Include="LiveTv\RecordingInfo.cs" />
<Compile Include="LiveTv\SeriesTimerInfo.cs" />
@@ -148,7 +142,6 @@
<Compile Include="Entities\Folder.cs" />
<Compile Include="Entities\Genre.cs" />
<Compile Include="Entities\ICollectionFolder.cs" />
- <Compile Include="Entities\IndexFolder.cs" />
<Compile Include="Entities\IVirtualFolderCreator.cs" />
<Compile Include="Entities\Movies\BoxSet.cs" />
<Compile Include="Entities\Movies\Movie.cs" />
@@ -200,10 +193,10 @@
<Compile Include="Library\TVUtils.cs" />
<Compile Include="Library\ItemResolveArgs.cs" />
<Compile Include="IO\FileData.cs" />
- <Compile Include="Kernel.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Providers\BaseMetadataProvider.cs" />
<Compile Include="Session\ISessionController.cs" />
+ <Compile Include="Session\ISessionControllerFactory.cs" />
<Compile Include="Session\PlaybackInfo.cs" />
<Compile Include="Session\PlaybackProgressInfo.cs" />
<Compile Include="Session\PlaybackStopInfo.cs" />
@@ -227,7 +220,7 @@
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<PropertyGroup>
- <PostBuildEvent>if $(ConfigurationName) == Release (
+ <PostBuildEvent Condition=" '$(ConfigurationName)' != 'Release Mono' ">if '$(ConfigurationName)' == 'Release' (
xcopy "$(TargetPath)" "$(SolutionDir)\Nuget\dlls\" /y /d /r /i
)</PostBuildEvent>
</PropertyGroup>
@@ -235,7 +228,7 @@ xcopy "$(TargetPath)" "$(SolutionDir)\Nuget\dlls\" /y /d /r /i
<PreBuildEvent>
</PreBuildEvent>
</PropertyGroup>
- <Import Project="$(SolutionDir)\.nuget\nuget.targets" />
+ <Import Project="$(SolutionDir)\.nuget\nuget.targets" Condition=" '$(ConfigurationName)' != 'Release Mono' " />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.
<Target Name="BeforeBuild">
diff --git a/MediaBrowser.Controller/MediaInfo/FFMpegManager.cs b/MediaBrowser.Controller/MediaInfo/FFMpegManager.cs
index e53acfc02..ced53299d 100644
--- a/MediaBrowser.Controller/MediaInfo/FFMpegManager.cs
+++ b/MediaBrowser.Controller/MediaInfo/FFMpegManager.cs
@@ -1,12 +1,16 @@
-using MediaBrowser.Common.IO;
+using MediaBrowser.Common.Extensions;
+using MediaBrowser.Common.IO;
using MediaBrowser.Common.MediaInfo;
+using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Entities;
-using MediaBrowser.Controller.IO;
+using MediaBrowser.Controller.Entities.Movies;
+using MediaBrowser.Controller.Entities.TV;
using MediaBrowser.Controller.Persistence;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Logging;
using System;
using System.Collections.Generic;
+using System.Globalization;
using System.IO;
using System.Linq;
using System.Threading;
@@ -19,79 +23,89 @@ namespace MediaBrowser.Controller.MediaInfo
/// </summary>
public class FFMpegManager
{
- /// <summary>
- /// Gets or sets the video image cache.
- /// </summary>
- /// <value>The video image cache.</value>
- internal FileSystemRepository VideoImageCache { get; set; }
-
- /// <summary>
- /// Gets or sets the subtitle cache.
- /// </summary>
- /// <value>The subtitle cache.</value>
- internal FileSystemRepository SubtitleCache { get; set; }
-
- private readonly IServerApplicationPaths _appPaths;
+ private readonly IServerConfigurationManager _config;
private readonly IMediaEncoder _encoder;
private readonly ILogger _logger;
private readonly IItemRepository _itemRepo;
private readonly IFileSystem _fileSystem;
+ public static FFMpegManager Instance { get; private set; }
+
/// <summary>
/// Initializes a new instance of the <see cref="FFMpegManager" /> class.
/// </summary>
- /// <param name="appPaths">The app paths.</param>
/// <param name="encoder">The encoder.</param>
/// <param name="logger">The logger.</param>
/// <param name="itemRepo">The item repo.</param>
/// <exception cref="System.ArgumentNullException">zipClient</exception>
- public FFMpegManager(IServerApplicationPaths appPaths, IMediaEncoder encoder, ILogger logger, IItemRepository itemRepo, IFileSystem fileSystem)
+ public FFMpegManager(IMediaEncoder encoder, ILogger logger, IItemRepository itemRepo, IFileSystem fileSystem, IServerConfigurationManager config)
{
- _appPaths = appPaths;
_encoder = encoder;
_logger = logger;
_itemRepo = itemRepo;
_fileSystem = fileSystem;
+ _config = config;
- VideoImageCache = new FileSystemRepository(VideoImagesDataPath);
- SubtitleCache = new FileSystemRepository(SubtitleCachePath);
+ // TODO: Remove this static instance
+ Instance = this;
}
/// <summary>
- /// Gets the video images data path.
+ /// Gets the chapter images data path.
/// </summary>
- /// <value>The video images data path.</value>
- public string VideoImagesDataPath
+ /// <value>The chapter images data path.</value>
+ public string ChapterImagesPath
{
get
{
- return Path.Combine(_appPaths.DataPath, "extracted-video-images");
+ return Path.Combine(_config.ApplicationPaths.DataPath, "chapter-images");
}
}
/// <summary>
- /// Gets the audio images data path.
+ /// Gets the subtitle cache path.
/// </summary>
- /// <value>The audio images data path.</value>
- public string AudioImagesDataPath
+ /// <value>The subtitle cache path.</value>
+ private string SubtitleCachePath
{
get
{
- return Path.Combine(_appPaths.DataPath, "extracted-audio-images");
+ return Path.Combine(_config.ApplicationPaths.CachePath, "subtitles");
}
}
/// <summary>
- /// Gets the subtitle cache path.
+ /// Determines whether [is eligible for chapter image extraction] [the specified video].
/// </summary>
- /// <value>The subtitle cache path.</value>
- public string SubtitleCachePath
+ /// <param name="video">The video.</param>
+ /// <returns><c>true</c> if [is eligible for chapter image extraction] [the specified video]; otherwise, <c>false</c>.</returns>
+ private bool IsEligibleForChapterImageExtraction(Video video)
{
- get
+ if (video is Movie)
+ {
+ if (!_config.Configuration.EnableMovieChapterImageExtraction)
+ {
+ return false;
+ }
+ }
+ else if (video is Episode)
{
- return Path.Combine(_appPaths.CachePath, "subtitles");
+ if (!_config.Configuration.EnableEpisodeChapterImageExtraction)
+ {
+ return false;
+ }
+ }
+ else
+ {
+ if (!_config.Configuration.EnableOtherVideoChapterImageExtraction)
+ {
+ return false;
+ }
}
+
+ // Can't extract images if there are no video streams
+ return video.DefaultVideoStreamIndex.HasValue;
}
/// <summary>
@@ -111,8 +125,7 @@ namespace MediaBrowser.Controller.MediaInfo
/// <exception cref="System.ArgumentNullException"></exception>
public async Task<bool> PopulateChapterImages(Video video, List<ChapterInfo> chapters, bool extractImages, bool saveChapters, CancellationToken cancellationToken)
{
- // Can't extract images if there are no video streams
- if (!video.DefaultVideoStreamIndex.HasValue)
+ if (!IsEligibleForChapterImageExtraction(video))
{
return true;
}
@@ -122,6 +135,8 @@ namespace MediaBrowser.Controller.MediaInfo
var runtimeTicks = video.RunTimeTicks ?? 0;
+ var currentImages = GetSavedChapterImages(video);
+
foreach (var chapter in chapters)
{
if (chapter.StartPositionTicks >= runtimeTicks)
@@ -130,11 +145,9 @@ namespace MediaBrowser.Controller.MediaInfo
break;
}
- var filename = video.Path + "_" + video.DateModified.Ticks + "_" + chapter.StartPositionTicks;
-
- var path = VideoImageCache.GetResourcePath(filename, ".jpg");
+ var path = GetChapterImagePath(video, chapter.StartPositionTicks);
- if (!File.Exists(path))
+ if (!currentImages.Contains(path, StringComparer.OrdinalIgnoreCase))
{
if (extractImages)
{
@@ -157,7 +170,7 @@ namespace MediaBrowser.Controller.MediaInfo
InputType type;
- var inputPath = MediaEncoderHelpers.GetInputArgument(video, null, out type);
+ var inputPath = MediaEncoderHelpers.GetInputArgument(video.Path, false, video.VideoType, video.IsoType, null, video.PlayableStreamFileNames, out type);
try
{
@@ -188,39 +201,87 @@ namespace MediaBrowser.Controller.MediaInfo
await _itemRepo.SaveChapters(video.Id, chapters, cancellationToken).ConfigureAwait(false);
}
+ DeleteDeadImages(currentImages, chapters);
+
return success;
}
+ private void DeleteDeadImages(IEnumerable<string> images, IEnumerable<ChapterInfo> chapters)
+ {
+ var deadImages = images
+ .Except(chapters.Select(i => i.ImagePath).Where(i => !string.IsNullOrEmpty(i)), StringComparer.OrdinalIgnoreCase)
+ .Where(i => BaseItem.SupportedImageExtensions.Contains(Path.GetExtension(i), StringComparer.OrdinalIgnoreCase))
+ .ToList();
+
+ foreach (var image in deadImages)
+ {
+ _logger.Debug("Deleting dead chapter image {0}", image);
+
+ try
+ {
+ File.Delete(image);
+ }
+ catch (IOException ex)
+ {
+ _logger.ErrorException("Error deleting {0}.", ex, image);
+ }
+ }
+ }
+
+ private readonly CultureInfo _usCulture = new CultureInfo("en-US");
+
/// <summary>
/// Gets the subtitle cache path.
/// </summary>
- /// <param name="input">The input.</param>
- /// <param name="subtitleStreamIndex">Index of the subtitle stream.</param>
+ /// <param name="mediaPath">The media path.</param>
+ /// <param name="subtitleStream">The subtitle stream.</param>
/// <param name="offset">The offset.</param>
/// <param name="outputExtension">The output extension.</param>
/// <returns>System.String.</returns>
- public string GetSubtitleCachePath(Video input, int subtitleStreamIndex, TimeSpan? offset, string outputExtension)
+ public string GetSubtitleCachePath(string mediaPath, MediaStream subtitleStream, TimeSpan? offset, string outputExtension)
{
var ticksParam = offset.HasValue ? "_" + offset.Value.Ticks : "";
- var stream = _itemRepo.GetMediaStreams(new MediaStreamQuery
+ if (subtitleStream.IsExternal)
{
- ItemId = input.Id,
- Index = subtitleStreamIndex
+ ticksParam += _fileSystem.GetLastWriteTimeUtc(subtitleStream.Path).Ticks;
+ }
+
+ var date = _fileSystem.GetLastWriteTimeUtc(mediaPath);
- }).FirstOrDefault();
+ var filename = (mediaPath + "_" + subtitleStream.Index.ToString(_usCulture) + "_" + date.Ticks.ToString(_usCulture) + ticksParam).GetMD5() + outputExtension;
- if (stream == null)
+ var prefix = filename.Substring(0, 1);
+
+ return Path.Combine(SubtitleCachePath, prefix, filename);
+ }
+
+ public string GetChapterImagePath(Video video, long chapterPositionTicks)
+ {
+ var filename = video.DateModified.Ticks.ToString(_usCulture) + "_" + chapterPositionTicks.ToString(_usCulture) + ".jpg";
+
+ var videoId = video.Id.ToString();
+ var prefix = videoId.Substring(0, 1);
+
+ return Path.Combine(ChapterImagesPath, prefix, videoId, filename);
+ }
+
+ public List<string> GetSavedChapterImages(Video video)
+ {
+ var videoId = video.Id.ToString();
+ var prefix = videoId.Substring(0, 1);
+
+ var path = Path.Combine(ChapterImagesPath, prefix, videoId);
+
+ try
{
- return null;
+ return Directory.EnumerateFiles(path)
+ .ToList();
}
-
- if (stream.IsExternal)
+ catch (DirectoryNotFoundException)
{
- ticksParam += _fileSystem.GetLastWriteTimeUtc(stream.Path).Ticks;
+ return new List<string>();
}
-
- return SubtitleCache.GetResourcePath(input.Id + "_" + subtitleStreamIndex + "_" + input.DateModified.Ticks + ticksParam, outputExtension);
}
}
}
diff --git a/MediaBrowser.Controller/MediaInfo/MediaEncoderHelpers.cs b/MediaBrowser.Controller/MediaInfo/MediaEncoderHelpers.cs
index 8c2f7c219..904ecdf93 100644
--- a/MediaBrowser.Controller/MediaInfo/MediaEncoderHelpers.cs
+++ b/MediaBrowser.Controller/MediaInfo/MediaEncoderHelpers.cs
@@ -1,5 +1,8 @@
-using MediaBrowser.Common.MediaInfo;
-using MediaBrowser.Controller.Entities;
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using MediaBrowser.Common.MediaInfo;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.IO;
@@ -13,43 +16,47 @@ namespace MediaBrowser.Controller.MediaInfo
/// <summary>
/// Gets the input argument.
/// </summary>
- /// <param name="video">The video.</param>
+ /// <param name="videoPath">The video path.</param>
+ /// <param name="isRemote">if set to <c>true</c> [is remote].</param>
+ /// <param name="videoType">Type of the video.</param>
+ /// <param name="isoType">Type of the iso.</param>
/// <param name="isoMount">The iso mount.</param>
+ /// <param name="playableStreamFileNames">The playable stream file names.</param>
/// <param name="type">The type.</param>
/// <returns>System.String[][].</returns>
- public static string[] GetInputArgument(Video video, IIsoMount isoMount, out InputType type)
+ public static string[] GetInputArgument(string videoPath, bool isRemote, VideoType videoType, IsoType? isoType, IIsoMount isoMount, IEnumerable<string> playableStreamFileNames, out InputType type)
{
- var inputPath = isoMount == null ? new[] { video.Path } : new[] { isoMount.MountedPath };
+ var inputPath = isoMount == null ? new[] { videoPath } : new[] { isoMount.MountedPath };
type = InputType.VideoFile;
- switch (video.VideoType)
+ switch (videoType)
{
case VideoType.BluRay:
type = InputType.Bluray;
break;
case VideoType.Dvd:
type = InputType.Dvd;
- inputPath = video.GetPlayableStreamFiles(inputPath[0]).ToArray();
+ inputPath = GetPlayableStreamFiles(inputPath[0], playableStreamFileNames).ToArray();
break;
case VideoType.Iso:
- if (video.IsoType.HasValue)
+ if (isoType.HasValue)
{
- switch (video.IsoType.Value)
+ switch (isoType.Value)
{
case IsoType.BluRay:
type = InputType.Bluray;
break;
case IsoType.Dvd:
type = InputType.Dvd;
- inputPath = video.GetPlayableStreamFiles(inputPath[0]).ToArray();
+ inputPath = GetPlayableStreamFiles(inputPath[0], playableStreamFileNames).ToArray();
break;
}
}
break;
case VideoType.VideoFile:
{
- if (video.LocationType == LocationType.Remote)
+ if (isRemote)
{
type = InputType.Url;
}
@@ -60,6 +67,17 @@ namespace MediaBrowser.Controller.MediaInfo
return inputPath;
}
+ public static List<string> GetPlayableStreamFiles(string rootPath, IEnumerable<string> filenames)
+ {
+ var allFiles = Directory
+ .EnumerateFiles(rootPath, "*", SearchOption.AllDirectories)
+ .ToList();
+
+ return filenames.Select(name => allFiles.FirstOrDefault(f => string.Equals(Path.GetFileName(f), name, StringComparison.OrdinalIgnoreCase)))
+ .Where(f => !string.IsNullOrEmpty(f))
+ .ToList();
+ }
+
/// <summary>
/// Gets the type of the input.
/// </summary>
diff --git a/MediaBrowser.Controller/Providers/BaseItemXmlParser.cs b/MediaBrowser.Controller/Providers/BaseItemXmlParser.cs
index 3ad16b643..799f339f1 100644
--- a/MediaBrowser.Controller/Providers/BaseItemXmlParser.cs
+++ b/MediaBrowser.Controller/Providers/BaseItemXmlParser.cs
@@ -269,10 +269,10 @@ namespace MediaBrowser.Controller.Providers
{
var val = reader.ReadElementContentAsString();
- var hasLanguage = item as IHasLanguage;
+ var hasLanguage = item as IHasPreferredMetadataLanguage;
if (hasLanguage != null)
{
- hasLanguage.Language = val;
+ hasLanguage.PreferredMetadataLanguage = val;
}
break;
@@ -676,6 +676,7 @@ namespace MediaBrowser.Controller.Providers
break;
case "TMDbCollectionId":
+ case "CollectionNumber":
var tmdbCollection = reader.ReadElementContentAsString();
if (!string.IsNullOrWhiteSpace(tmdbCollection))
{
@@ -758,11 +759,30 @@ namespace MediaBrowser.Controller.Providers
break;
}
- case "MediaInfo":
+ case "Format3D":
{
- using (var subtree = reader.ReadSubtree())
+ var video = item as Video;
+
+ if (video != null)
{
- FetchFromMediaInfoNode(subtree, item);
+ var val = reader.ReadElementContentAsString();
+
+ if (string.Equals("HSBS", val))
+ {
+ video.Video3DFormat = Video3DFormat.HalfSideBySide;
+ }
+ else if (string.Equals("HTAB", val))
+ {
+ video.Video3DFormat = Video3DFormat.HalfTopAndBottom;
+ }
+ else if (string.Equals("FTAB", val))
+ {
+ video.Video3DFormat = Video3DFormat.FullTopAndBottom;
+ }
+ else if (string.Equals("FSBS", val))
+ {
+ video.Video3DFormat = Video3DFormat.FullSideBySide;
+ }
}
break;
}
@@ -774,89 +794,6 @@ namespace MediaBrowser.Controller.Providers
}
/// <summary>
- /// Fetches from media info node.
- /// </summary>
- /// <param name="reader">The reader.</param>
- /// <param name="item">The item.</param>
- private void FetchFromMediaInfoNode(XmlReader reader, T item)
- {
- reader.MoveToContent();
-
- while (reader.Read())
- {
- if (reader.NodeType == XmlNodeType.Element)
- {
- switch (reader.Name)
- {
- case "Video":
- {
- using (var subtree = reader.ReadSubtree())
- {
- FetchFromMediaInfoVideoNode(subtree, item);
- }
- break;
- }
-
- default:
- reader.Skip();
- break;
- }
- }
- }
- }
-
- /// <summary>
- /// Fetches from media info video node.
- /// </summary>
- /// <param name="reader">The reader.</param>
- /// <param name="item">The item.</param>
- private void FetchFromMediaInfoVideoNode(XmlReader reader, T item)
- {
- reader.MoveToContent();
-
- while (reader.Read())
- {
- if (reader.NodeType == XmlNodeType.Element)
- {
- switch (reader.Name)
- {
- case "Format3D":
- {
- var video = item as Video;
-
- if (video != null)
- {
- var val = reader.ReadElementContentAsString();
-
- if (string.Equals("HSBS", val))
- {
- video.Video3DFormat = Video3DFormat.HalfSideBySide;
- }
- else if (string.Equals("HTAB", val))
- {
- video.Video3DFormat = Video3DFormat.HalfTopAndBottom;
- }
- else if (string.Equals("FTAB", val))
- {
- video.Video3DFormat = Video3DFormat.FullTopAndBottom;
- }
- else if (string.Equals("FSBS", val))
- {
- video.Video3DFormat = Video3DFormat.FullSideBySide;
- }
- }
- break;
- }
-
- default:
- reader.Skip();
- break;
- }
- }
- }
- }
-
- /// <summary>
/// Fetches from taglines node.
/// </summary>
/// <param name="reader">The reader.</param>
diff --git a/MediaBrowser.Controller/Providers/BaseMetadataProvider.cs b/MediaBrowser.Controller/Providers/BaseMetadataProvider.cs
index 40afe0b54..67475b329 100644
--- a/MediaBrowser.Controller/Providers/BaseMetadataProvider.cs
+++ b/MediaBrowser.Controller/Providers/BaseMetadataProvider.cs
@@ -223,6 +223,11 @@ namespace MediaBrowser.Controller.Providers
throw new ArgumentNullException("providerInfo");
}
+ if (providerInfo.LastRefreshed == default(DateTime))
+ {
+ return true;
+ }
+
if (NeedsRefreshBasedOnCompareDate(item, providerInfo))
{
return true;
@@ -325,6 +330,16 @@ namespace MediaBrowser.Controller.Providers
return !item.ResolveArgs.IsDirectory;
}
+ protected virtual IEnumerable<BaseItem> GetItemsForFileStampComparison(BaseItem item)
+ {
+ if (UseParentFileSystemStamp(item) && item.Parent != null)
+ {
+ return new[] { item.Parent };
+ }
+
+ return new[] { item };
+ }
+
/// <summary>
/// Gets the item's current file system stamp
/// </summary>
@@ -332,12 +347,7 @@ namespace MediaBrowser.Controller.Providers
/// <returns>Guid.</returns>
private Guid GetCurrentFileSystemStamp(BaseItem item)
{
- if (UseParentFileSystemStamp(item) && item.Parent != null)
- {
- return GetFileSystemStamp(item.Parent);
- }
-
- return GetFileSystemStamp(item);
+ return GetFileSystemStamp(GetItemsForFileStampComparison(item));
}
private Dictionary<string, string> _fileStampExtensionsDictionary;
@@ -355,44 +365,40 @@ namespace MediaBrowser.Controller.Providers
/// <summary>
/// Gets the file system stamp.
/// </summary>
- /// <param name="item">The item.</param>
+ /// <param name="items">The items.</param>
/// <returns>Guid.</returns>
- protected virtual Guid GetFileSystemStamp(BaseItem item)
+ protected virtual Guid GetFileSystemStamp(IEnumerable<BaseItem> items)
{
- // If there's no path or the item is a file, there's nothing to do
- if (item.LocationType != LocationType.FileSystem)
- {
- return Guid.Empty;
- }
+ var sb = new StringBuilder();
- ItemResolveArgs resolveArgs;
+ var extensions = FileStampExtensionsDictionary;
+ var numExtensions = FilestampExtensions.Length;
- try
+ foreach (var item in items)
{
- resolveArgs = item.ResolveArgs;
- }
- catch (IOException ex)
- {
- Logger.ErrorException("Error determining if path is directory: {0}", ex, item.Path);
- throw;
+ // If there's no path or the item is a file, there's nothing to do
+ if (item.LocationType == LocationType.FileSystem)
+ {
+ var resolveArgs = item.ResolveArgs;
+
+ if (resolveArgs.IsDirectory)
+ {
+ // Record the name of each file
+ // Need to sort these because accoring to msdn docs, our i/o methods are not guaranteed in any order
+ AddFiles(sb, resolveArgs.FileSystemChildren, extensions, numExtensions);
+ AddFiles(sb, resolveArgs.MetadataFiles, extensions, numExtensions);
+ }
+ }
}
- if (!resolveArgs.IsDirectory)
+ var stamp = sb.ToString();
+
+ if (string.IsNullOrEmpty(stamp))
{
return Guid.Empty;
}
- var sb = new StringBuilder();
-
- var extensions = FileStampExtensionsDictionary;
- var numExtensions = FilestampExtensions.Length;
-
- // Record the name of each file
- // Need to sort these because accoring to msdn docs, our i/o methods are not guaranteed in any order
- AddFiles(sb, resolveArgs.FileSystemChildren, extensions, numExtensions);
- AddFiles(sb, resolveArgs.MetadataFiles, extensions, numExtensions);
-
- return sb.ToString().GetMD5();
+ return stamp.GetMD5();
}
private static readonly Dictionary<string, string> FoldersToMonitor = new[] { "extrafanart", "extrathumbs" }
diff --git a/MediaBrowser.Controller/Providers/IImageEnhancer.cs b/MediaBrowser.Controller/Providers/IImageEnhancer.cs
index 54ba6d322..ae605ec0d 100644
--- a/MediaBrowser.Controller/Providers/IImageEnhancer.cs
+++ b/MediaBrowser.Controller/Providers/IImageEnhancer.cs
@@ -14,7 +14,7 @@ namespace MediaBrowser.Controller.Providers
/// <param name="item">The item.</param>
/// <param name="imageType">Type of the image.</param>
/// <returns><c>true</c> if this enhancer will enhance the supplied image for the supplied item, <c>false</c> otherwise</returns>
- bool Supports(BaseItem item, ImageType imageType);
+ bool Supports(IHasImages item, ImageType imageType);
/// <summary>
/// Gets the priority or order in which this enhancer should be run.
@@ -28,7 +28,7 @@ namespace MediaBrowser.Controller.Providers
/// <param name="item">The item.</param>
/// <param name="imageType">Type of the image.</param>
/// <returns>Cache key relating to the current state of this item and configuration</returns>
- string GetConfigurationCacheKey(BaseItem item, ImageType imageType);
+ string GetConfigurationCacheKey(IHasImages item, ImageType imageType);
/// <summary>
/// Gets the size of the enhanced image.
@@ -38,7 +38,7 @@ namespace MediaBrowser.Controller.Providers
/// <param name="imageIndex">Index of the image.</param>
/// <param name="originalImageSize">Size of the original image.</param>
/// <returns>ImageSize.</returns>
- ImageSize GetEnhancedImageSize(BaseItem item, ImageType imageType, int imageIndex, ImageSize originalImageSize);
+ ImageSize GetEnhancedImageSize(IHasImages item, ImageType imageType, int imageIndex, ImageSize originalImageSize);
/// <summary>
/// Enhances the image async.
@@ -49,6 +49,6 @@ namespace MediaBrowser.Controller.Providers
/// <param name="imageIndex">Index of the image.</param>
/// <returns>Task{Image}.</returns>
/// <exception cref="System.ArgumentNullException"></exception>
- Task<Image> EnhanceImageAsync(BaseItem item, Image originalImage, ImageType imageType, int imageIndex);
+ Task<Image> EnhanceImageAsync(IHasImages item, Image originalImage, ImageType imageType, int imageIndex);
}
} \ No newline at end of file
diff --git a/MediaBrowser.Controller/Providers/IImageProvider.cs b/MediaBrowser.Controller/Providers/IImageProvider.cs
index d70532b59..ccf199844 100644
--- a/MediaBrowser.Controller/Providers/IImageProvider.cs
+++ b/MediaBrowser.Controller/Providers/IImageProvider.cs
@@ -23,7 +23,7 @@ namespace MediaBrowser.Controller.Providers
/// </summary>
/// <param name="item">The item.</param>
/// <returns><c>true</c> if XXXX, <c>false</c> otherwise</returns>
- bool Supports(BaseItem item);
+ bool Supports(IHasImages item);
/// <summary>
/// Gets the images.
@@ -32,7 +32,7 @@ namespace MediaBrowser.Controller.Providers
/// <param name="imageType">Type of the image.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>Task{IEnumerable{RemoteImageInfo}}.</returns>
- Task<IEnumerable<RemoteImageInfo>> GetImages(BaseItem item, ImageType imageType, CancellationToken cancellationToken);
+ Task<IEnumerable<RemoteImageInfo>> GetImages(IHasImages item, ImageType imageType, CancellationToken cancellationToken);
/// <summary>
/// Gets the images.
@@ -40,7 +40,7 @@ namespace MediaBrowser.Controller.Providers
/// <param name="item">The item.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>Task{IEnumerable{RemoteImageInfo}}.</returns>
- Task<IEnumerable<RemoteImageInfo>> GetAllImages(BaseItem item, CancellationToken cancellationToken);
+ Task<IEnumerable<RemoteImageInfo>> GetAllImages(IHasImages item, CancellationToken cancellationToken);
/// <summary>
/// Gets the priority.
diff --git a/MediaBrowser.Controller/Resolvers/EntityResolutionHelper.cs b/MediaBrowser.Controller/Resolvers/EntityResolutionHelper.cs
index 7d9739448..3cd38da45 100644
--- a/MediaBrowser.Controller/Resolvers/EntityResolutionHelper.cs
+++ b/MediaBrowser.Controller/Resolvers/EntityResolutionHelper.cs
@@ -133,6 +133,19 @@ namespace MediaBrowser.Controller.Resolvers
/// <param name="includeCreationTime">if set to <c>true</c> [include creation time].</param>
public static void EnsureDates(IFileSystem fileSystem, BaseItem item, ItemResolveArgs args, bool includeCreationTime)
{
+ if (fileSystem == null)
+ {
+ throw new ArgumentNullException("fileSystem");
+ }
+ if (item == null)
+ {
+ throw new ArgumentNullException("item");
+ }
+ if (args == null)
+ {
+ throw new ArgumentNullException("args");
+ }
+
// See if a different path came out of the resolver than what went in
if (!string.Equals(args.Path, item.Path, StringComparison.OrdinalIgnoreCase))
{
diff --git a/MediaBrowser.Controller/Session/ISessionControllerFactory.cs b/MediaBrowser.Controller/Session/ISessionControllerFactory.cs
new file mode 100644
index 000000000..92862e462
--- /dev/null
+++ b/MediaBrowser.Controller/Session/ISessionControllerFactory.cs
@@ -0,0 +1,16 @@
+
+namespace MediaBrowser.Controller.Session
+{
+ /// <summary>
+ /// Interface ISesssionControllerFactory
+ /// </summary>
+ public interface ISessionControllerFactory
+ {
+ /// <summary>
+ /// Gets the session controller.
+ /// </summary>
+ /// <param name="session">The session.</param>
+ /// <returns>ISessionController.</returns>
+ ISessionController GetSessionController(SessionInfo session);
+ }
+}
diff --git a/MediaBrowser.Controller/Session/ISessionManager.cs b/MediaBrowser.Controller/Session/ISessionManager.cs
index 771d8f72e..ec138bfb4 100644
--- a/MediaBrowser.Controller/Session/ISessionManager.cs
+++ b/MediaBrowser.Controller/Session/ISessionManager.cs
@@ -35,16 +35,23 @@ namespace MediaBrowser.Controller.Session
IEnumerable<SessionInfo> Sessions { get; }
/// <summary>
+ /// Adds the parts.
+ /// </summary>
+ /// <param name="sessionFactories">The session factories.</param>
+ void AddParts(IEnumerable<ISessionControllerFactory> sessionFactories);
+
+ /// <summary>
/// Logs the user activity.
/// </summary>
/// <param name="clientType">Type of the client.</param>
/// <param name="appVersion">The app version.</param>
/// <param name="deviceId">The device id.</param>
/// <param name="deviceName">Name of the device.</param>
+ /// <param name="remoteEndPoint">The remote end point.</param>
/// <param name="user">The user.</param>
/// <returns>Task.</returns>
/// <exception cref="System.ArgumentNullException">user</exception>
- Task<SessionInfo> LogSessionActivity(string clientType, string appVersion, string deviceId, string deviceName, User user);
+ Task<SessionInfo> LogSessionActivity(string clientType, string appVersion, string deviceId, string deviceName, string remoteEndPoint, User user);
/// <summary>
/// Used to report that playback has started for an item
diff --git a/MediaBrowser.Controller/Session/SessionInfo.cs b/MediaBrowser.Controller/Session/SessionInfo.cs
index ed2fcda67..82e9328ac 100644
--- a/MediaBrowser.Controller/Session/SessionInfo.cs
+++ b/MediaBrowser.Controller/Session/SessionInfo.cs
@@ -15,6 +15,12 @@ namespace MediaBrowser.Controller.Session
}
/// <summary>
+ /// Gets or sets the remote end point.
+ /// </summary>
+ /// <value>The remote end point.</value>
+ public string RemoteEndPoint { get; set; }
+
+ /// <summary>
/// Gets or sets a value indicating whether this instance can seek.
/// </summary>
/// <value><c>true</c> if this instance can seek; otherwise, <c>false</c>.</value>
diff --git a/MediaBrowser.Model.Portable/MediaBrowser.Model.Portable.csproj b/MediaBrowser.Model.Portable/MediaBrowser.Model.Portable.csproj
index f6e2725a6..f8b12da9e 100644
--- a/MediaBrowser.Model.Portable/MediaBrowser.Model.Portable.csproj
+++ b/MediaBrowser.Model.Portable/MediaBrowser.Model.Portable.csproj
@@ -16,7 +16,7 @@
<ProjectTypeGuids>{786C830F-07A1-408B-BD7F-6EE04809D6DB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
<SolutionDir Condition="$(SolutionDir) == '' Or $(SolutionDir) == '*Undefined*'">..\</SolutionDir>
<RestorePackages>true</RestorePackages>
- <FodyPath>..\packages\Fody.1.13.12</FodyPath>
+ <FodyPath>..\packages\Fody.1.19.1.0</FodyPath>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
@@ -77,6 +77,9 @@
<Compile Include="..\MediaBrowser.Model\Configuration\BaseApplicationConfiguration.cs">
<Link>Configuration\BaseApplicationConfiguration.cs</Link>
</Compile>
+ <Compile Include="..\MediaBrowser.Model\Configuration\ImageDownloadOptions.cs">
+ <Link>Configuration\ImageDownloadOptions.cs</Link>
+ </Compile>
<Compile Include="..\MediaBrowser.Model\Configuration\ManualLoginCategory.cs">
<Link>Configuration\ManualLoginCategory.cs</Link>
</Compile>
@@ -146,9 +149,6 @@
<Compile Include="..\MediaBrowser.Model\Entities\IHasProviderIds.cs">
<Link>Entities\IHasProviderIds.cs</Link>
</Compile>
- <Compile Include="..\MediaBrowser.Model\Entities\ImageDownloadOptions.cs">
- <Link>Entities\ImageDownloadOptions.cs</Link>
- </Compile>
<Compile Include="..\MediaBrowser.Model\Entities\ImageType.cs">
<Link>Entities\ImageType.cs</Link>
</Compile>
@@ -239,6 +239,9 @@
<Compile Include="..\MediaBrowser.Model\LiveTv\ProgramQuery.cs">
<Link>LiveTv\ProgramQuery.cs</Link>
</Compile>
+ <Compile Include="..\MediaBrowser.Model\LiveTv\RecordingGroupDto.cs">
+ <Link>LiveTv\RecordingGroupDto.cs</Link>
+ </Compile>
<Compile Include="..\MediaBrowser.Model\LiveTv\RecordingInfoDto.cs">
<Link>LiveTv\RecordingInfoDto.cs</Link>
</Compile>
@@ -248,6 +251,9 @@
<Compile Include="..\MediaBrowser.Model\LiveTv\RecordingStatus.cs">
<Link>LiveTv\RecordingStatus.cs</Link>
</Compile>
+ <Compile Include="..\MediaBrowser.Model\LiveTv\SeriesTimerInfoDto.cs">
+ <Link>LiveTv\SeriesTimerInfoDto.cs</Link>
+ </Compile>
<Compile Include="..\MediaBrowser.Model\LiveTv\TimerInfoDto.cs">
<Link>LiveTv\TimerInfoDto.cs</Link>
</Compile>
diff --git a/MediaBrowser.Model.Portable/packages.config b/MediaBrowser.Model.Portable/packages.config
index 23768650a..2d74129b8 100644
--- a/MediaBrowser.Model.Portable/packages.config
+++ b/MediaBrowser.Model.Portable/packages.config
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
- <package id="Fody" version="1.13.12" targetFramework="portable-net45+sl40+wp71+win" />
+ <package id="Fody" version="1.19.1.0" targetFramework="portable-win+net45+sl40+wp71" developmentDependency="true" />
<package id="Microsoft.Bcl" version="1.0.19" targetFramework="portable-win+net45+sl40+wp71" />
<package id="Microsoft.Bcl.Async" version="1.0.16" targetFramework="portable-win+net45+sl40+wp71" />
<package id="Microsoft.Bcl.Build" version="1.0.8" targetFramework="portable-win+net45+sl40+wp71" />
diff --git a/MediaBrowser.Model.net35/MediaBrowser.Model.net35.csproj b/MediaBrowser.Model.net35/MediaBrowser.Model.net35.csproj
index efafc7f62..d69cf56c6 100644
--- a/MediaBrowser.Model.net35/MediaBrowser.Model.net35.csproj
+++ b/MediaBrowser.Model.net35/MediaBrowser.Model.net35.csproj
@@ -64,6 +64,9 @@
<Compile Include="..\MediaBrowser.Model\Configuration\BaseApplicationConfiguration.cs">
<Link>Configuration\BaseApplicationConfiguration.cs</Link>
</Compile>
+ <Compile Include="..\MediaBrowser.Model\Configuration\ImageDownloadOptions.cs">
+ <Link>Configuration\ImageDownloadOptions.cs</Link>
+ </Compile>
<Compile Include="..\MediaBrowser.Model\Configuration\ManualLoginCategory.cs">
<Link>Configuration\ManualLoginCategory.cs</Link>
</Compile>
@@ -133,9 +136,6 @@
<Compile Include="..\MediaBrowser.Model\Entities\IHasProviderIds.cs">
<Link>Entities\IHasProviderIds.cs</Link>
</Compile>
- <Compile Include="..\MediaBrowser.Model\Entities\ImageDownloadOptions.cs">
- <Link>Entities\ImageDownloadOptions.cs</Link>
- </Compile>
<Compile Include="..\MediaBrowser.Model\Entities\ImageType.cs">
<Link>Entities\ImageType.cs</Link>
</Compile>
@@ -226,6 +226,9 @@
<Compile Include="..\MediaBrowser.Model\LiveTv\ProgramQuery.cs">
<Link>LiveTv\ProgramQuery.cs</Link>
</Compile>
+ <Compile Include="..\MediaBrowser.Model\LiveTv\RecordingGroupDto.cs">
+ <Link>LiveTv\RecordingGroupDto.cs</Link>
+ </Compile>
<Compile Include="..\MediaBrowser.Model\LiveTv\RecordingInfoDto.cs">
<Link>LiveTv\RecordingInfoDto.cs</Link>
</Compile>
@@ -235,6 +238,9 @@
<Compile Include="..\MediaBrowser.Model\LiveTv\RecordingStatus.cs">
<Link>LiveTv\RecordingStatus.cs</Link>
</Compile>
+ <Compile Include="..\MediaBrowser.Model\LiveTv\SeriesTimerInfoDto.cs">
+ <Link>LiveTv\SeriesTimerInfoDto.cs</Link>
+ </Compile>
<Compile Include="..\MediaBrowser.Model\LiveTv\TimerInfoDto.cs">
<Link>LiveTv\TimerInfoDto.cs</Link>
</Compile>
diff --git a/MediaBrowser.Model/Configuration/BaseApplicationConfiguration.cs b/MediaBrowser.Model/Configuration/BaseApplicationConfiguration.cs
index b99fefcca..19620890e 100644
--- a/MediaBrowser.Model/Configuration/BaseApplicationConfiguration.cs
+++ b/MediaBrowser.Model/Configuration/BaseApplicationConfiguration.cs
@@ -43,7 +43,13 @@ namespace MediaBrowser.Model.Configuration
/// </summary>
/// <value><c>true</c> if this instance is first run; otherwise, <c>false</c>.</value>
public bool IsStartupWizardCompleted { get; set; }
-
+
+ /// <summary>
+ /// Gets or sets the cache path.
+ /// </summary>
+ /// <value>The cache path.</value>
+ public string CachePath { get; set; }
+
/// <summary>
/// Initializes a new instance of the <see cref="BaseApplicationConfiguration" /> class.
/// </summary>
diff --git a/MediaBrowser.Model/Entities/ImageDownloadOptions.cs b/MediaBrowser.Model/Configuration/ImageDownloadOptions.cs
index 92e989a34..603112110 100644
--- a/MediaBrowser.Model/Entities/ImageDownloadOptions.cs
+++ b/MediaBrowser.Model/Configuration/ImageDownloadOptions.cs
@@ -1,5 +1,5 @@

-namespace MediaBrowser.Model.Entities
+namespace MediaBrowser.Model.Configuration
{
/// <summary>
/// Class ImageDownloadOptions
@@ -62,4 +62,20 @@ namespace MediaBrowser.Model.Entities
Banner = true;
}
}
+
+ /// <summary>
+ /// Class MetadataOptions.
+ /// </summary>
+ public class MetadataOptions
+ {
+ public int MaxBackdrops { get; set; }
+
+ public int MinBackdropWidth { get; set; }
+
+ public MetadataOptions()
+ {
+ MaxBackdrops = 3;
+ MinBackdropWidth = 1280;
+ }
+ }
}
diff --git a/MediaBrowser.Model/Configuration/ServerConfiguration.cs b/MediaBrowser.Model/Configuration/ServerConfiguration.cs
index 1f75969be..c8c205404 100644
--- a/MediaBrowser.Model/Configuration/ServerConfiguration.cs
+++ b/MediaBrowser.Model/Configuration/ServerConfiguration.cs
@@ -1,5 +1,4 @@
-using MediaBrowser.Model.Entities;
-using MediaBrowser.Model.Weather;
+using MediaBrowser.Model.Weather;
using System;
namespace MediaBrowser.Model.Configuration
@@ -88,12 +87,6 @@ namespace MediaBrowser.Model.Configuration
public string MetadataCountryCode { get; set; }
/// <summary>
- /// Gets or sets the max backdrops.
- /// </summary>
- /// <value>The max backdrops.</value>
- public int MaxBackdrops { get; set; }
-
- /// <summary>
/// Options for specific art to download for movies.
/// </summary>
public ImageDownloadOptions DownloadMovieImages { get; set; }
@@ -143,12 +136,6 @@ namespace MediaBrowser.Model.Configuration
public bool ShowLogWindow { get; set; }
/// <summary>
- /// The list of types that will NOT be allowed to have internet providers run against them even if they are turned on.
- /// </summary>
- /// <value>The internet provider exclude types.</value>
- public string[] InternetProviderExcludeTypes { get; set; }
-
- /// <summary>
/// Gets or sets the recent item days.
/// </summary>
/// <value>The recent item days.</value>
@@ -181,12 +168,6 @@ namespace MediaBrowser.Model.Configuration
public int FileWatcherDelay { get; set; }
/// <summary>
- /// Gets or sets a value indicating whether [enable developer tools].
- /// </summary>
- /// <value><c>true</c> if [enable developer tools]; otherwise, <c>false</c>.</value>
- public bool EnableDeveloperTools { get; set; }
-
- /// <summary>
/// Gets or sets a value indicating whether [enable dashboard response caching].
/// Allows potential contributors without visual studio to modify production dashboard code and test changes.
/// </summary>
@@ -217,22 +198,26 @@ namespace MediaBrowser.Model.Configuration
public ImageSavingConvention ImageSavingConvention { get; set; }
/// <summary>
- /// Gets or sets the width of the min movie backdrop.
+ /// Gets or sets a value indicating whether [enable people prefix sub folders].
/// </summary>
- /// <value>The width of the min movie backdrop.</value>
- public int MinMovieBackdropDownloadWidth { get; set; }
+ /// <value><c>true</c> if [enable people prefix sub folders]; otherwise, <c>false</c>.</value>
+ public bool EnablePeoplePrefixSubFolders { get; set; }
/// <summary>
- /// Gets or sets the width of the min series backdrop.
+ /// Gets or sets the encoding quality.
/// </summary>
- /// <value>The width of the min series backdrop.</value>
- public int MinSeriesBackdropDownloadWidth { get; set; }
+ /// <value>The encoding quality.</value>
+ public EncodingQuality EncodingQuality { get; set; }
- /// <summary>
- /// Gets or sets a value indicating whether [enable people prefix sub folders].
- /// </summary>
- /// <value><c>true</c> if [enable people prefix sub folders]; otherwise, <c>false</c>.</value>
- public bool EnablePeoplePrefixSubFolders { get; set; }
+ public bool EnableMovieChapterImageExtraction { get; set; }
+ public bool EnableEpisodeChapterImageExtraction { get; set; }
+ public bool EnableOtherVideoChapterImageExtraction { get; set; }
+
+ public MetadataOptions MovieOptions { get; set; }
+ public MetadataOptions TvOptions { get; set; }
+ public MetadataOptions MusicOptions { get; set; }
+ public MetadataOptions GameOptions { get; set; }
+ public MetadataOptions BookOptions { get; set; }
/// <summary>
/// Initializes a new instance of the <see cref="ServerConfiguration" /> class.
@@ -247,9 +232,9 @@ namespace MediaBrowser.Model.Configuration
EnableDashboardResponseCaching = true;
EnableVideoImageExtraction = true;
-#if (DEBUG)
- EnableDeveloperTools = true;
-#endif
+ EnableMovieChapterImageExtraction = true;
+ EnableEpisodeChapterImageExtraction = false;
+ EnableOtherVideoChapterImageExtraction = false;
MinResumePct = 5;
MaxResumePct = 90;
@@ -260,7 +245,6 @@ namespace MediaBrowser.Model.Configuration
RecentItemDays = 10;
EnableInternetProviders = true; //initial installs will need these
- InternetProviderExcludeTypes = new string[] { };
ManualLoginClients = new ManualLoginCategory[] { };
@@ -275,7 +259,6 @@ namespace MediaBrowser.Model.Configuration
};
DownloadMusicArtistImages = new ImageDownloadOptions();
DownloadMusicAlbumImages = new ImageDownloadOptions();
- MaxBackdrops = 3;
SortReplaceCharacters = new[] { ".", "+", "%" };
SortRemoveCharacters = new[] { ",", "&", "-", "{", "}", "'" };
@@ -283,8 +266,20 @@ namespace MediaBrowser.Model.Configuration
SeasonZeroDisplayName = "Specials";
- MinMovieBackdropDownloadWidth = 1280;
- MinSeriesBackdropDownloadWidth = 1280;
+ MovieOptions = new MetadataOptions();
+ TvOptions = new MetadataOptions();
+
+ MusicOptions = new MetadataOptions()
+ {
+ MaxBackdrops = 1
+ };
+
+ GameOptions = new MetadataOptions();
+
+ BookOptions = new MetadataOptions
+ {
+ MaxBackdrops = 1
+ };
}
}
@@ -293,4 +288,12 @@ namespace MediaBrowser.Model.Configuration
Legacy,
Compatible
}
+
+ public enum EncodingQuality
+ {
+ Auto,
+ HighSpeed,
+ HighQuality,
+ MaxQuality
+ }
}
diff --git a/MediaBrowser.Model/Configuration/UserConfiguration.cs b/MediaBrowser.Model/Configuration/UserConfiguration.cs
index b736474e0..90accff94 100644
--- a/MediaBrowser.Model/Configuration/UserConfiguration.cs
+++ b/MediaBrowser.Model/Configuration/UserConfiguration.cs
@@ -60,6 +60,13 @@ namespace MediaBrowser.Model.Configuration
public bool DisplayUnairedEpisodes { get; set; }
public bool EnableRemoteControlOfOtherUsers { get; set; }
+ public bool BlockUnratedMovies { get; set; }
+ public bool BlockUnratedTrailers { get; set; }
+ public bool BlockUnratedSeries { get; set; }
+ public bool BlockUnratedMusic { get; set; }
+ public bool BlockUnratedGames { get; set; }
+ public bool BlockUnratedBooks { get; set; }
+
/// <summary>
/// Initializes a new instance of the <see cref="UserConfiguration" /> class.
/// </summary>
diff --git a/MediaBrowser.Model/Dto/BaseItemDto.cs b/MediaBrowser.Model/Dto/BaseItemDto.cs
index 20f60586c..9adfcfa99 100644
--- a/MediaBrowser.Model/Dto/BaseItemDto.cs
+++ b/MediaBrowser.Model/Dto/BaseItemDto.cs
@@ -30,11 +30,25 @@ namespace MediaBrowser.Model.Dto
/// <value>The date created.</value>
public DateTime? DateCreated { get; set; }
+ public int? AirsBeforeSeasonNumber { get; set; }
+ public int? AirsAfterSeasonNumber { get; set; }
+ public int? AirsBeforeEpisodeNumber { get; set; }
+ public int? AbsoluteEpisodeNumber { get; set; }
+ public bool? DisplaySpecialsWithSeasons { get; set; }
+
+ public string PreferredMetadataLanguage { get; set; }
+ public string PreferredMetadataCountryCode { get; set; }
+
+ /// <summary>
+ /// Gets or sets the DVD season number.
+ /// </summary>
+ /// <value>The DVD season number.</value>
+ public int? DvdSeasonNumber { get; set; }
/// <summary>
- /// Gets or sets the special season number.
+ /// Gets or sets the DVD episode number.
/// </summary>
- /// <value>The special season number.</value>
- public int? SpecialSeasonNumber { get; set; }
+ /// <value>The DVD episode number.</value>
+ public float? DvdEpisodeNumber { get; set; }
/// <summary>
/// Gets or sets the name of the sort.
@@ -77,7 +91,7 @@ namespace MediaBrowser.Model.Dto
/// </summary>
/// <value>The path.</value>
public string Path { get; set; }
-
+
/// <summary>
/// Gets or sets the official rating.
/// </summary>
@@ -199,12 +213,6 @@ namespace MediaBrowser.Model.Dto
public Dictionary<string, string> ProviderIds { get; set; }
/// <summary>
- /// Gets or sets the language.
- /// </summary>
- /// <value>The language.</value>
- public string Language { get; set; }
-
- /// <summary>
/// Gets or sets a value indicating whether this instance is HD.
/// </summary>
/// <value><c>null</c> if [is HD] contains no value, <c>true</c> if [is HD]; otherwise, <c>false</c>.</value>
diff --git a/MediaBrowser.Model/Entities/MetadataFields.cs b/MediaBrowser.Model/Entities/MetadataFields.cs
index 85f2da31e..a99fd0fe0 100644
--- a/MediaBrowser.Model/Entities/MetadataFields.cs
+++ b/MediaBrowser.Model/Entities/MetadataFields.cs
@@ -41,6 +41,18 @@ namespace MediaBrowser.Model.Entities
/// <summary>
/// The official rating
/// </summary>
- OfficialRating
+ OfficialRating,
+ /// <summary>
+ /// The images
+ /// </summary>
+ Images,
+ /// <summary>
+ /// The backdrops
+ /// </summary>
+ Backdrops,
+ /// <summary>
+ /// The screenshots
+ /// </summary>
+ Screenshots
}
}
diff --git a/MediaBrowser.Model/LiveTv/ChannelInfoDto.cs b/MediaBrowser.Model/LiveTv/ChannelInfoDto.cs
index 020771e5e..89c92e6fd 100644
--- a/MediaBrowser.Model/LiveTv/ChannelInfoDto.cs
+++ b/MediaBrowser.Model/LiveTv/ChannelInfoDto.cs
@@ -23,6 +23,12 @@ namespace MediaBrowser.Model.LiveTv
public string Id { get; set; }
/// <summary>
+ /// Gets or sets the external identifier.
+ /// </summary>
+ /// <value>The external identifier.</value>
+ public string ExternalId { get; set; }
+
+ /// <summary>
/// Gets or sets the image tags.
/// </summary>
/// <value>The image tags.</value>
diff --git a/MediaBrowser.Model/LiveTv/ProgramInfoDto.cs b/MediaBrowser.Model/LiveTv/ProgramInfoDto.cs
index 5f35de086..0798a2294 100644
--- a/MediaBrowser.Model/LiveTv/ProgramInfoDto.cs
+++ b/MediaBrowser.Model/LiveTv/ProgramInfoDto.cs
@@ -1,4 +1,6 @@
-using System;
+using MediaBrowser.Model.Dto;
+using MediaBrowser.Model.Entities;
+using System;
using System.Collections.Generic;
namespace MediaBrowser.Model.LiveTv
@@ -11,6 +13,18 @@ namespace MediaBrowser.Model.LiveTv
public string Id { get; set; }
/// <summary>
+ /// Gets or sets the timer identifier.
+ /// </summary>
+ /// <value>The timer identifier.</value>
+ public string TimerId { get; set; }
+
+ /// <summary>
+ /// Gets or sets the series timer identifier.
+ /// </summary>
+ /// <value>The series timer identifier.</value>
+ public string SeriesTimerId { get; set; }
+
+ /// <summary>
/// Gets or sets the external identifier.
/// </summary>
/// <value>The external identifier.</value>
@@ -23,16 +37,16 @@ namespace MediaBrowser.Model.LiveTv
public string ChannelId { get; set; }
/// <summary>
+ /// Gets or sets the name of the channel.
+ /// </summary>
+ /// <value>The name of the channel.</value>
+ public string ChannelName { get; set; }
+
+ /// <summary>
/// Gets or sets the community rating.
/// </summary>
/// <value>The community rating.</value>
public float? CommunityRating { get; set; }
-
- /// <summary>
- /// Gets or sets the aspect ratio.
- /// </summary>
- /// <value>The aspect ratio.</value>
- public string AspectRatio { get; set; }
/// <summary>
/// Gets or sets the official rating.
@@ -101,14 +115,85 @@ namespace MediaBrowser.Model.LiveTv
/// <value>The episode title.</value>
public string EpisodeTitle { get; set; }
+ /// <summary>
+ /// Gets or sets the image tags.
+ /// </summary>
+ /// <value>The image tags.</value>
+ public Dictionary<ImageType, Guid> ImageTags { get; set; }
+
+ /// <summary>
+ /// Gets or sets the user data.
+ /// </summary>
+ /// <value>The user data.</value>
+ public UserItemDataDto UserData { get; set; }
+
+ /// <summary>
+ /// Gets or sets a value indicating whether this instance is movie.
+ /// </summary>
+ /// <value><c>true</c> if this instance is movie; otherwise, <c>false</c>.</value>
+ public bool IsMovie { get; set; }
+
+ /// <summary>
+ /// Gets or sets a value indicating whether this instance is sports.
+ /// </summary>
+ /// <value><c>true</c> if this instance is sports; otherwise, <c>false</c>.</value>
+ public bool IsSports { get; set; }
+
+ /// <summary>
+ /// Gets or sets a value indicating whether this instance is series.
+ /// </summary>
+ /// <value><c>true</c> if this instance is series; otherwise, <c>false</c>.</value>
+ public bool IsSeries { get; set; }
+
+ /// <summary>
+ /// Gets or sets a value indicating whether this instance is live.
+ /// </summary>
+ /// <value><c>true</c> if this instance is live; otherwise, <c>false</c>.</value>
+ public bool IsLive { get; set; }
+
+ /// <summary>
+ /// Gets or sets the type.
+ /// </summary>
+ /// <value>The type.</value>
+ public string Type { get; set; }
+
+ /// <summary>
+ /// Gets or sets the run time ticks.
+ /// </summary>
+ /// <value>The run time ticks.</value>
+ public long? RunTimeTicks { get; set; }
+
+ /// <summary>
+ /// Gets or sets a value indicating whether this instance is news.
+ /// </summary>
+ /// <value><c>true</c> if this instance is news; otherwise, <c>false</c>.</value>
+ public bool IsNews { get; set; }
+
+ /// <summary>
+ /// Gets or sets a value indicating whether this instance is kids.
+ /// </summary>
+ /// <value><c>true</c> if this instance is kids; otherwise, <c>false</c>.</value>
+ public bool IsKids { get; set; }
+
+ /// <summary>
+ /// Gets or sets a value indicating whether this instance is premiere.
+ /// </summary>
+ /// <value><c>true</c> if this instance is premiere; otherwise, <c>false</c>.</value>
+ public bool IsPremiere { get; set; }
+
public ProgramInfoDto()
{
Genres = new List<string>();
+ ImageTags = new Dictionary<ImageType, Guid>();
}
}
public enum ProgramAudio
{
- Stereo
+ Mono,
+ Stereo,
+ Dolby,
+ DolbyDigital,
+ Thx
}
} \ No newline at end of file
diff --git a/MediaBrowser.Model/LiveTv/RecordingGroupDto.cs b/MediaBrowser.Model/LiveTv/RecordingGroupDto.cs
new file mode 100644
index 000000000..29f0824fb
--- /dev/null
+++ b/MediaBrowser.Model/LiveTv/RecordingGroupDto.cs
@@ -0,0 +1,27 @@
+
+namespace MediaBrowser.Model.LiveTv
+{
+ /// <summary>
+ /// Class RecordingGroupDto.
+ /// </summary>
+ public class RecordingGroupDto
+ {
+ /// <summary>
+ /// Gets or sets the name.
+ /// </summary>
+ /// <value>The name.</value>
+ public string Name { get; set; }
+
+ /// <summary>
+ /// Gets or sets the identifier.
+ /// </summary>
+ /// <value>The identifier.</value>
+ public string Id { get; set; }
+
+ /// <summary>
+ /// Gets or sets the recording count.
+ /// </summary>
+ /// <value>The recording count.</value>
+ public int RecordingCount { get; set; }
+ }
+}
diff --git a/MediaBrowser.Model/LiveTv/RecordingInfoDto.cs b/MediaBrowser.Model/LiveTv/RecordingInfoDto.cs
index a095e1751..47accbec5 100644
--- a/MediaBrowser.Model/LiveTv/RecordingInfoDto.cs
+++ b/MediaBrowser.Model/LiveTv/RecordingInfoDto.cs
@@ -1,5 +1,7 @@
using System;
using System.Collections.Generic;
+using MediaBrowser.Model.Dto;
+using MediaBrowser.Model.Entities;
namespace MediaBrowser.Model.LiveTv
{
@@ -11,6 +13,12 @@ namespace MediaBrowser.Model.LiveTv
public string Id { get; set; }
/// <summary>
+ /// Gets or sets the series timer identifier.
+ /// </summary>
+ /// <value>The series timer identifier.</value>
+ public string SeriesTimerId { get; set; }
+
+ /// <summary>
/// Gets or sets the external identifier.
/// </summary>
/// <value>The external identifier.</value>
@@ -33,6 +41,12 @@ namespace MediaBrowser.Model.LiveTv
public string ChannelName { get; set; }
/// <summary>
+ /// Gets or sets the name of the service.
+ /// </summary>
+ /// <value>The name of the service.</value>
+ public string ServiceName { get; set; }
+
+ /// <summary>
/// Name of the recording.
/// </summary>
public string Name { get; set; }
@@ -44,6 +58,12 @@ namespace MediaBrowser.Model.LiveTv
public string Path { get; set; }
/// <summary>
+ /// Gets or sets the URL.
+ /// </summary>
+ /// <value>The URL.</value>
+ public string Url { get; set; }
+
+ /// <summary>
/// Overview of the recording.
/// </summary>
public string Overview { get; set; }
@@ -65,6 +85,12 @@ namespace MediaBrowser.Model.LiveTv
public RecordingStatus Status { get; set; }
/// <summary>
+ /// Gets or sets the name of the status.
+ /// </summary>
+ /// <value>The name of the status.</value>
+ public string StatusName { get; set; }
+
+ /// <summary>
/// Genre of the program.
/// </summary>
public List<string> Genres { get; set; }
@@ -82,10 +108,10 @@ namespace MediaBrowser.Model.LiveTv
public string EpisodeTitle { get; set; }
/// <summary>
- /// Gets or sets the duration ms.
+ /// Gets or sets the run time ticks.
/// </summary>
- /// <value>The duration ms.</value>
- public int DurationMs { get; set; }
+ /// <value>The run time ticks.</value>
+ public long? RunTimeTicks { get; set; }
/// <summary>
/// Gets or sets the type of the media.
@@ -123,9 +149,70 @@ namespace MediaBrowser.Model.LiveTv
/// <value>The audio.</value>
public ProgramAudio? Audio { get; set; }
+ /// <summary>
+ /// Gets or sets a value indicating whether this instance is movie.
+ /// </summary>
+ /// <value><c>true</c> if this instance is movie; otherwise, <c>false</c>.</value>
+ public bool IsMovie { get; set; }
+
+ /// <summary>
+ /// Gets or sets a value indicating whether this instance is sports.
+ /// </summary>
+ /// <value><c>true</c> if this instance is sports; otherwise, <c>false</c>.</value>
+ public bool IsSports { get; set; }
+
+ /// <summary>
+ /// Gets or sets a value indicating whether this instance is series.
+ /// </summary>
+ /// <value><c>true</c> if this instance is series; otherwise, <c>false</c>.</value>
+ public bool IsSeries { get; set; }
+
+ /// <summary>
+ /// Gets or sets a value indicating whether this instance is live.
+ /// </summary>
+ /// <value><c>true</c> if this instance is live; otherwise, <c>false</c>.</value>
+ public bool IsLive { get; set; }
+
+ /// <summary>
+ /// Gets or sets a value indicating whether this instance is news.
+ /// </summary>
+ /// <value><c>true</c> if this instance is news; otherwise, <c>false</c>.</value>
+ public bool IsNews { get; set; }
+
+ /// <summary>
+ /// Gets or sets a value indicating whether this instance is kids.
+ /// </summary>
+ /// <value><c>true</c> if this instance is kids; otherwise, <c>false</c>.</value>
+ public bool IsKids { get; set; }
+
+ /// <summary>
+ /// Gets or sets a value indicating whether this instance is premiere.
+ /// </summary>
+ /// <value><c>true</c> if this instance is premiere; otherwise, <c>false</c>.</value>
+ public bool IsPremiere { get; set; }
+
+ /// <summary>
+ /// Gets or sets the image tags.
+ /// </summary>
+ /// <value>The image tags.</value>
+ public Dictionary<ImageType, Guid> ImageTags { get; set; }
+
+ /// <summary>
+ /// Gets or sets the user data.
+ /// </summary>
+ /// <value>The user data.</value>
+ public UserItemDataDto UserData { get; set; }
+
+ /// <summary>
+ /// Gets or sets the type.
+ /// </summary>
+ /// <value>The type.</value>
+ public string Type { get; set; }
+
public RecordingInfoDto()
{
Genres = new List<string>();
+ ImageTags = new Dictionary<ImageType, Guid>();
}
}
} \ No newline at end of file
diff --git a/MediaBrowser.Model/LiveTv/RecordingQuery.cs b/MediaBrowser.Model/LiveTv/RecordingQuery.cs
index cd5ebe628..e63a250e6 100644
--- a/MediaBrowser.Model/LiveTv/RecordingQuery.cs
+++ b/MediaBrowser.Model/LiveTv/RecordingQuery.cs
@@ -10,6 +10,45 @@
/// </summary>
/// <value>The channel identifier.</value>
public string ChannelId { get; set; }
+
+ /// <summary>
+ /// Gets or sets the user identifier.
+ /// </summary>
+ /// <value>The user identifier.</value>
+ public string UserId { get; set; }
+
+ /// <summary>
+ /// Gets or sets the identifier.
+ /// </summary>
+ /// <value>The identifier.</value>
+ public string Id { get; set; }
+
+ /// <summary>
+ /// Gets or sets the group identifier.
+ /// </summary>
+ /// <value>The group identifier.</value>
+ public string GroupId { get; set; }
+
+ /// <summary>
+ /// Skips over a given number of items within the results. Use for paging.
+ /// </summary>
+ /// <value>The start index.</value>
+ public int? StartIndex { get; set; }
+
+ /// <summary>
+ /// The maximum number of items to return
+ /// </summary>
+ /// <value>The limit.</value>
+ public int? Limit { get; set; }
+ }
+
+ public class RecordingGroupQuery
+ {
+ /// <summary>
+ /// Gets or sets the user identifier.
+ /// </summary>
+ /// <value>The user identifier.</value>
+ public string UserId { get; set; }
}
public class TimerQuery
@@ -20,4 +59,8 @@
/// <value>The channel identifier.</value>
public string ChannelId { get; set; }
}
+
+ public class SeriesTimerQuery
+ {
+ }
}
diff --git a/MediaBrowser.Model/LiveTv/RecordingStatus.cs b/MediaBrowser.Model/LiveTv/RecordingStatus.cs
index 08a7cfb0c..95e9dcb01 100644
--- a/MediaBrowser.Model/LiveTv/RecordingStatus.cs
+++ b/MediaBrowser.Model/LiveTv/RecordingStatus.cs
@@ -3,20 +3,21 @@ namespace MediaBrowser.Model.LiveTv
{
public enum RecordingStatus
{
- Pending,
+ New,
+ Scheduled,
InProgress,
Completed,
- CompletedWithError,
- Conflicted,
- Deleted
+ Aborted,
+ Cancelled,
+ ConflictedOk,
+ ConflictedNotOk,
+ Error
}
- public enum RecurrenceType
+ public enum DayPattern
{
- Manual,
- NewProgramEventsOneChannel,
- AllProgramEventsOneChannel,
- NewProgramEventsAllChannels,
- AllProgramEventsAllChannels
+ Daily,
+ Weekdays,
+ Weekends
}
}
diff --git a/MediaBrowser.Model/LiveTv/SeriesTimerInfoDto.cs b/MediaBrowser.Model/LiveTv/SeriesTimerInfoDto.cs
new file mode 100644
index 000000000..a8c6a2e37
--- /dev/null
+++ b/MediaBrowser.Model/LiveTv/SeriesTimerInfoDto.cs
@@ -0,0 +1,138 @@
+using System;
+using System.Collections.Generic;
+
+namespace MediaBrowser.Model.LiveTv
+{
+ public class SeriesTimerInfoDto
+ {
+ /// <summary>
+ /// Id of the recording.
+ /// </summary>
+ public string Id { get; set; }
+
+ /// <summary>
+ /// Gets or sets the external identifier.
+ /// </summary>
+ /// <value>The external identifier.</value>
+ public string ExternalId { get; set; }
+
+ /// <summary>
+ /// ChannelId of the recording.
+ /// </summary>
+ public string ChannelId { get; set; }
+
+ /// <summary>
+ /// Gets or sets the name of the service.
+ /// </summary>
+ /// <value>The name of the service.</value>
+ public string ServiceName { get; set; }
+
+ /// <summary>
+ /// Gets or sets the external channel identifier.
+ /// </summary>
+ /// <value>The external channel identifier.</value>
+ public string ExternalChannelId { get; set; }
+
+ /// <summary>
+ /// ChannelName of the recording.
+ /// </summary>
+ public string ChannelName { get; set; }
+
+ /// <summary>
+ /// Gets or sets the program identifier.
+ /// </summary>
+ /// <value>The program identifier.</value>
+ public string ProgramId { get; set; }
+
+ /// <summary>
+ /// Gets or sets the external program identifier.
+ /// </summary>
+ /// <value>The external program identifier.</value>
+ public string ExternalProgramId { get; set; }
+
+ /// <summary>
+ /// Name of the recording.
+ /// </summary>
+ public string Name { get; set; }
+
+ /// <summary>
+ /// Description of the recording.
+ /// </summary>
+ public string Overview { get; set; }
+
+ /// <summary>
+ /// The start date of the recording, in UTC.
+ /// </summary>
+ public DateTime StartDate { get; set; }
+
+ /// <summary>
+ /// The end date of the recording, in UTC.
+ /// </summary>
+ public DateTime EndDate { get; set; }
+
+ /// <summary>
+ /// Gets or sets a value indicating whether [record any time].
+ /// </summary>
+ /// <value><c>true</c> if [record any time]; otherwise, <c>false</c>.</value>
+ public bool RecordAnyTime { get; set; }
+
+ /// <summary>
+ /// Gets or sets a value indicating whether [record any channel].
+ /// </summary>
+ /// <value><c>true</c> if [record any channel]; otherwise, <c>false</c>.</value>
+ public bool RecordAnyChannel { get; set; }
+
+ /// <summary>
+ /// Gets or sets a value indicating whether [record new only].
+ /// </summary>
+ /// <value><c>true</c> if [record new only]; otherwise, <c>false</c>.</value>
+ public bool RecordNewOnly { get; set; }
+
+ /// <summary>
+ /// Gets or sets the days.
+ /// </summary>
+ /// <value>The days.</value>
+ public List<DayOfWeek> Days { get; set; }
+
+ /// <summary>
+ /// Gets or sets the day pattern.
+ /// </summary>
+ /// <value>The day pattern.</value>
+ public DayPattern? DayPattern { get; set; }
+
+ /// <summary>
+ /// Gets or sets the priority.
+ /// </summary>
+ /// <value>The priority.</value>
+ public int Priority { get; set; }
+
+ /// <summary>
+ /// Gets or sets the pre padding seconds.
+ /// </summary>
+ /// <value>The pre padding seconds.</value>
+ public int PrePaddingSeconds { get; set; }
+
+ /// <summary>
+ /// Gets or sets the post padding seconds.
+ /// </summary>
+ /// <value>The post padding seconds.</value>
+ public int PostPaddingSeconds { get; set; }
+
+ /// <summary>
+ /// Gets or sets a value indicating whether this instance is pre padding required.
+ /// </summary>
+ /// <value><c>true</c> if this instance is pre padding required; otherwise, <c>false</c>.</value>
+ public bool IsPrePaddingRequired { get; set; }
+
+ /// <summary>
+ /// Gets or sets a value indicating whether this instance is post padding required.
+ /// </summary>
+ /// <value><c>true</c> if this instance is post padding required; otherwise, <c>false</c>.</value>
+ public bool IsPostPaddingRequired { get; set; }
+
+ public SeriesTimerInfoDto()
+ {
+ Days = new List<DayOfWeek>();
+ }
+ }
+}
diff --git a/MediaBrowser.Model/LiveTv/TimerInfoDto.cs b/MediaBrowser.Model/LiveTv/TimerInfoDto.cs
index e8085fc92..507ba0947 100644
--- a/MediaBrowser.Model/LiveTv/TimerInfoDto.cs
+++ b/MediaBrowser.Model/LiveTv/TimerInfoDto.cs
@@ -21,17 +21,35 @@ namespace MediaBrowser.Model.LiveTv
public string ChannelId { get; set; }
/// <summary>
+ /// Gets or sets the external channel identifier.
+ /// </summary>
+ /// <value>The external channel identifier.</value>
+ public string ExternalChannelId { get; set; }
+
+ /// <summary>
/// ChannelName of the recording.
/// </summary>
public string ChannelName { get; set; }
/// <summary>
+ /// Gets or sets the name of the service.
+ /// </summary>
+ /// <value>The name of the service.</value>
+ public string ServiceName { get; set; }
+
+ /// <summary>
/// Gets or sets the program identifier.
/// </summary>
/// <value>The program identifier.</value>
public string ProgramId { get; set; }
/// <summary>
+ /// Gets or sets the external program identifier.
+ /// </summary>
+ /// <value>The external program identifier.</value>
+ public string ExternalProgramId { get; set; }
+
+ /// <summary>
/// Name of the recording.
/// </summary>
public string Name { get; set; }
@@ -39,7 +57,7 @@ namespace MediaBrowser.Model.LiveTv
/// <summary>
/// Description of the recording.
/// </summary>
- public string Description { get; set; }
+ public string Overview { get; set; }
/// <summary>
/// The start date of the recording, in UTC.
@@ -64,33 +82,51 @@ namespace MediaBrowser.Model.LiveTv
public string SeriesTimerId { get; set; }
/// <summary>
- /// Gets or sets the requested pre padding seconds.
+ /// Gets or sets the external series timer identifier.
+ /// </summary>
+ /// <value>The external series timer identifier.</value>
+ public string ExternalSeriesTimerId { get; set; }
+
+ /// <summary>
+ /// Gets or sets the pre padding seconds.
+ /// </summary>
+ /// <value>The pre padding seconds.</value>
+ public int PrePaddingSeconds { get; set; }
+
+ /// <summary>
+ /// Gets or sets the post padding seconds.
+ /// </summary>
+ /// <value>The post padding seconds.</value>
+ public int PostPaddingSeconds { get; set; }
+
+ /// <summary>
+ /// Gets or sets a value indicating whether this instance is pre padding required.
/// </summary>
- /// <value>The requested pre padding seconds.</value>
- public int RequestedPrePaddingSeconds { get; set; }
+ /// <value><c>true</c> if this instance is pre padding required; otherwise, <c>false</c>.</value>
+ public bool IsPrePaddingRequired { get; set; }
/// <summary>
- /// Gets or sets the requested post padding seconds.
+ /// Gets or sets a value indicating whether this instance is post padding required.
/// </summary>
- /// <value>The requested post padding seconds.</value>
- public int RequestedPostPaddingSeconds { get; set; }
+ /// <value><c>true</c> if this instance is post padding required; otherwise, <c>false</c>.</value>
+ public bool IsPostPaddingRequired { get; set; }
/// <summary>
- /// Gets or sets the required pre padding seconds.
+ /// Gets or sets the run time ticks.
/// </summary>
- /// <value>The required pre padding seconds.</value>
- public int RequiredPrePaddingSeconds { get; set; }
+ /// <value>The run time ticks.</value>
+ public long? RunTimeTicks { get; set; }
/// <summary>
- /// Gets or sets the required post padding seconds.
+ /// Gets or sets the priority.
/// </summary>
- /// <value>The required post padding seconds.</value>
- public int RequiredPostPaddingSeconds { get; set; }
+ /// <value>The priority.</value>
+ public int Priority { get; set; }
/// <summary>
- /// Gets or sets the duration ms.
+ /// Gets or sets the program information.
/// </summary>
- /// <value>The duration ms.</value>
- public int DurationMs { get; set; }
+ /// <value>The program information.</value>
+ public ProgramInfoDto ProgramInfo { get; set; }
}
}
diff --git a/MediaBrowser.Model/MediaBrowser.Model.csproj b/MediaBrowser.Model/MediaBrowser.Model.csproj
index 5175bee91..ab91416b7 100644
--- a/MediaBrowser.Model/MediaBrowser.Model.csproj
+++ b/MediaBrowser.Model/MediaBrowser.Model.csproj
@@ -9,13 +9,13 @@
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>MediaBrowser.Model</RootNamespace>
<AssemblyName>MediaBrowser.Model</AssemblyName>
- <TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<SolutionDir Condition="$(SolutionDir) == '' Or $(SolutionDir) == '*Undefined*'">..\</SolutionDir>
<RestorePackages>true</RestorePackages>
- <FodyPath>..\packages\Fody.1.17.0.0</FodyPath>
+ <FodyPath>..\packages\Fody.1.19.1.0</FodyPath>
<ProductVersion>10.0.0</ProductVersion>
<SchemaVersion>2.0</SchemaVersion>
+ <TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
@@ -26,6 +26,7 @@
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<PlatformTarget>AnyCPU</PlatformTarget>
+ <TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
@@ -34,6 +35,16 @@
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
+ <TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
+ </PropertyGroup>
+ <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release Mono|AnyCPU' ">
+ <DebugType>pdbonly</DebugType>
+ <Optimize>true</Optimize>
+ <OutputPath>bin\Release Mono\</OutputPath>
+ <DefineConstants>TRACE</DefineConstants>
+ <ErrorReport>prompt</ErrorReport>
+ <WarningLevel>4</WarningLevel>
+ <TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
</PropertyGroup>
<PropertyGroup>
<RunPostBuildEvent>Always</RunPostBuildEvent>
@@ -64,8 +75,10 @@
<Compile Include="LiveTv\ChannelQuery.cs" />
<Compile Include="LiveTv\ProgramInfoDto.cs" />
<Compile Include="LiveTv\ProgramQuery.cs" />
+ <Compile Include="LiveTv\RecordingGroupDto.cs" />
<Compile Include="LiveTv\RecordingQuery.cs" />
<Compile Include="LiveTv\RecordingStatus.cs" />
+ <Compile Include="LiveTv\SeriesTimerInfoDto.cs" />
<Compile Include="LiveTv\TimerInfoDto.cs" />
<Compile Include="Providers\ImageProviderInfo.cs" />
<Compile Include="Providers\RemoteImageInfo.cs" />
@@ -106,7 +119,7 @@
<Compile Include="Session\MessageCommand.cs" />
<Compile Include="Session\PlayRequest.cs" />
<Compile Include="Session\PlaystateCommand.cs" />
- <Compile Include="Entities\ImageDownloadOptions.cs" />
+ <Compile Include="Configuration\ImageDownloadOptions.cs" />
<Compile Include="Logging\ILogManager.cs" />
<Compile Include="MediaInfo\BlurayDiscInfo.cs" />
<Compile Include="Entities\ChapterInfo.cs" />
@@ -194,11 +207,11 @@
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<PropertyGroup>
- <PostBuildEvent>if $(ConfigurationName) == Release (
+ <PostBuildEvent Condition=" '$(ConfigurationName)' != 'Release Mono' ">if '$(ConfigurationName)' == 'Release' (
xcopy "$(TargetPath)" "$(SolutionDir)\Nuget\dlls\net45\" /y /d /r /i
)</PostBuildEvent>
</PropertyGroup>
- <Import Project="$(SolutionDir)\.nuget\nuget.targets" />
+ <Import Project="$(SolutionDir)\.nuget\NuGet.targets" Condition=" '$(ConfigurationName)' != 'Release Mono' " />
<Import Project="Fody.targets" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.
diff --git a/MediaBrowser.Model/Querying/ItemFields.cs b/MediaBrowser.Model/Querying/ItemFields.cs
index 71c3d59cd..6d61d8ac7 100644
--- a/MediaBrowser.Model/Querying/ItemFields.cs
+++ b/MediaBrowser.Model/Querying/ItemFields.cs
@@ -59,7 +59,7 @@ namespace MediaBrowser.Model.Querying
/// <summary>
/// The metadata settings
/// </summary>
- MetadataSettings,
+ Settings,
/// <summary>
/// The original run time ticks
diff --git a/MediaBrowser.Model/Querying/ItemQuery.cs b/MediaBrowser.Model/Querying/ItemQuery.cs
index 6602e031f..afc0540ef 100644
--- a/MediaBrowser.Model/Querying/ItemQuery.cs
+++ b/MediaBrowser.Model/Querying/ItemQuery.cs
@@ -141,12 +141,6 @@ namespace MediaBrowser.Model.Querying
public string SearchTerm { get; set; }
/// <summary>
- /// The dynamic, localized index function name
- /// </summary>
- /// <value>The index by.</value>
- public string IndexBy { get; set; }
-
- /// <summary>
/// Gets or sets the image types.
/// </summary>
/// <value>The image types.</value>
diff --git a/MediaBrowser.Model/Search/SearchHint.cs b/MediaBrowser.Model/Search/SearchHint.cs
index 1e16b0492..bebe23734 100644
--- a/MediaBrowser.Model/Search/SearchHint.cs
+++ b/MediaBrowser.Model/Search/SearchHint.cs
@@ -32,6 +32,12 @@ namespace MediaBrowser.Model.Search
public int? IndexNumber { get; set; }
/// <summary>
+ /// Gets or sets the production year.
+ /// </summary>
+ /// <value>The production year.</value>
+ public int? ProductionYear { get; set; }
+
+ /// <summary>
/// Gets or sets the parent index number.
/// </summary>
/// <value>The parent index number.</value>
diff --git a/MediaBrowser.Model/Session/SessionInfoDto.cs b/MediaBrowser.Model/Session/SessionInfoDto.cs
index 02b7f0226..80f6ea2c0 100644
--- a/MediaBrowser.Model/Session/SessionInfoDto.cs
+++ b/MediaBrowser.Model/Session/SessionInfoDto.cs
@@ -14,6 +14,12 @@ namespace MediaBrowser.Model.Session
public bool CanSeek { get; set; }
/// <summary>
+ /// Gets or sets the remote end point.
+ /// </summary>
+ /// <value>The remote end point.</value>
+ public string RemoteEndPoint { get; set; }
+
+ /// <summary>
/// Gets or sets the queueable media types.
/// </summary>
/// <value>The queueable media types.</value>
diff --git a/MediaBrowser.Model/System/SystemInfo.cs b/MediaBrowser.Model/System/SystemInfo.cs
index 6a17ad133..d475517dc 100644
--- a/MediaBrowser.Model/System/SystemInfo.cs
+++ b/MediaBrowser.Model/System/SystemInfo.cs
@@ -99,6 +99,12 @@ namespace MediaBrowser.Model.System
public string ItemsByNamePath { get; set; }
/// <summary>
+ /// Gets or sets the cache path.
+ /// </summary>
+ /// <value>The cache path.</value>
+ public string CachePath { get; set; }
+
+ /// <summary>
/// Gets or sets the log path.
/// </summary>
/// <value>The log path.</value>
@@ -111,6 +117,12 @@ namespace MediaBrowser.Model.System
public int HttpServerPortNumber { get; set; }
/// <summary>
+ /// Gets or sets the wan address.
+ /// </summary>
+ /// <value>The wan address.</value>
+ public string WanAddress { get; set; }
+
+ /// <summary>
/// Initializes a new instance of the <see cref="SystemInfo" /> class.
/// </summary>
public SystemInfo()
diff --git a/MediaBrowser.Model/packages.config b/MediaBrowser.Model/packages.config
index 622e6f72f..3d7793afb 100644
--- a/MediaBrowser.Model/packages.config
+++ b/MediaBrowser.Model/packages.config
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
- <package id="Fody" version="1.17.0.0" targetFramework="net45" />
+ <package id="Fody" version="1.19.1.0" targetFramework="net45" developmentDependency="true" />
<package id="PropertyChanged.Fody" version="1.41.0.0" targetFramework="net45" />
</packages> \ No newline at end of file
diff --git a/MediaBrowser.Mono.sln b/MediaBrowser.Mono.sln
index 397763489..c951fda72 100644
--- a/MediaBrowser.Mono.sln
+++ b/MediaBrowser.Mono.sln
@@ -24,64 +24,83 @@ Global
Debug|x86 = Debug|x86
Release|x86 = Release|x86
Release|Any CPU = Release|Any CPU
+ Release Mono|Any CPU = Release Mono|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{175A9388-F352-4586-A6B4-070DED62B644}.Debug|x86.ActiveCfg = Debug|x86
{175A9388-F352-4586-A6B4-070DED62B644}.Debug|x86.Build.0 = Debug|x86
+ {175A9388-F352-4586-A6B4-070DED62B644}.Release Mono|Any CPU.ActiveCfg = Release Mono|Any CPU
+ {175A9388-F352-4586-A6B4-070DED62B644}.Release Mono|Any CPU.Build.0 = Release Mono|Any CPU
{175A9388-F352-4586-A6B4-070DED62B644}.Release|Any CPU.ActiveCfg = Release|Any CPU
{175A9388-F352-4586-A6B4-070DED62B644}.Release|Any CPU.Build.0 = Release|Any CPU
{175A9388-F352-4586-A6B4-070DED62B644}.Release|x86.ActiveCfg = Release|x86
{175A9388-F352-4586-A6B4-070DED62B644}.Release|x86.Build.0 = Release|x86
- {17E1F4E6-8ABD-4FE5-9ECF-43D4B6087BA2}.Debug|x86.ActiveCfg = Debug|x86
- {17E1F4E6-8ABD-4FE5-9ECF-43D4B6087BA2}.Debug|x86.Build.0 = Debug|x86
+ {17E1F4E6-8ABD-4FE5-9ECF-43D4B6087BA2}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {17E1F4E6-8ABD-4FE5-9ECF-43D4B6087BA2}.Debug|x86.Build.0 = Debug|Any CPU
+ {17E1F4E6-8ABD-4FE5-9ECF-43D4B6087BA2}.Release Mono|Any CPU.ActiveCfg = Release Mono|Any CPU
+ {17E1F4E6-8ABD-4FE5-9ECF-43D4B6087BA2}.Release Mono|Any CPU.Build.0 = Release Mono|Any CPU
{17E1F4E6-8ABD-4FE5-9ECF-43D4B6087BA2}.Release|Any CPU.ActiveCfg = Release|Any CPU
{17E1F4E6-8ABD-4FE5-9ECF-43D4B6087BA2}.Release|Any CPU.Build.0 = Release|Any CPU
{17E1F4E6-8ABD-4FE5-9ECF-43D4B6087BA2}.Release|x86.ActiveCfg = Release|Any CPU
{17E1F4E6-8ABD-4FE5-9ECF-43D4B6087BA2}.Release|x86.Build.0 = Release|Any CPU
{2E781478-814D-4A48-9D80-BFF206441A65}.Debug|x86.ActiveCfg = Debug|Any CPU
{2E781478-814D-4A48-9D80-BFF206441A65}.Debug|x86.Build.0 = Debug|Any CPU
+ {2E781478-814D-4A48-9D80-BFF206441A65}.Release Mono|Any CPU.ActiveCfg = Release Mono|Any CPU
+ {2E781478-814D-4A48-9D80-BFF206441A65}.Release Mono|Any CPU.Build.0 = Release Mono|Any CPU
{2E781478-814D-4A48-9D80-BFF206441A65}.Release|Any CPU.ActiveCfg = Release|Any CPU
{2E781478-814D-4A48-9D80-BFF206441A65}.Release|Any CPU.Build.0 = Release|Any CPU
{2E781478-814D-4A48-9D80-BFF206441A65}.Release|x86.ActiveCfg = Release|Any CPU
{2E781478-814D-4A48-9D80-BFF206441A65}.Release|x86.Build.0 = Release|Any CPU
{442B5058-DCAF-4263-BB6A-F21E31120A1B}.Debug|x86.ActiveCfg = Debug|Any CPU
{442B5058-DCAF-4263-BB6A-F21E31120A1B}.Debug|x86.Build.0 = Debug|Any CPU
+ {442B5058-DCAF-4263-BB6A-F21E31120A1B}.Release Mono|Any CPU.ActiveCfg = Release Mono|Any CPU
+ {442B5058-DCAF-4263-BB6A-F21E31120A1B}.Release Mono|Any CPU.Build.0 = Release Mono|Any CPU
{442B5058-DCAF-4263-BB6A-F21E31120A1B}.Release|Any CPU.ActiveCfg = Release|Any CPU
{442B5058-DCAF-4263-BB6A-F21E31120A1B}.Release|Any CPU.Build.0 = Release|Any CPU
{442B5058-DCAF-4263-BB6A-F21E31120A1B}.Release|x86.ActiveCfg = Release|Any CPU
{442B5058-DCAF-4263-BB6A-F21E31120A1B}.Release|x86.Build.0 = Release|Any CPU
{4FD51AC5-2C16-4308-A993-C3A84F3B4582}.Debug|x86.ActiveCfg = Debug|Any CPU
{4FD51AC5-2C16-4308-A993-C3A84F3B4582}.Debug|x86.Build.0 = Debug|Any CPU
+ {4FD51AC5-2C16-4308-A993-C3A84F3B4582}.Release Mono|Any CPU.ActiveCfg = Release Mono|Any CPU
+ {4FD51AC5-2C16-4308-A993-C3A84F3B4582}.Release Mono|Any CPU.Build.0 = Release Mono|Any CPU
{4FD51AC5-2C16-4308-A993-C3A84F3B4582}.Release|Any CPU.ActiveCfg = Release|Any CPU
{4FD51AC5-2C16-4308-A993-C3A84F3B4582}.Release|Any CPU.Build.0 = Release|Any CPU
{4FD51AC5-2C16-4308-A993-C3A84F3B4582}.Release|x86.ActiveCfg = Release|Any CPU
{4FD51AC5-2C16-4308-A993-C3A84F3B4582}.Release|x86.Build.0 = Release|Any CPU
{5624B7B5-B5A7-41D8-9F10-CC5611109619}.Debug|x86.ActiveCfg = Debug|Any CPU
{5624B7B5-B5A7-41D8-9F10-CC5611109619}.Debug|x86.Build.0 = Debug|Any CPU
+ {5624B7B5-B5A7-41D8-9F10-CC5611109619}.Release Mono|Any CPU.ActiveCfg = Release Mono|Any CPU
+ {5624B7B5-B5A7-41D8-9F10-CC5611109619}.Release Mono|Any CPU.Build.0 = Release Mono|Any CPU
{5624B7B5-B5A7-41D8-9F10-CC5611109619}.Release|Any CPU.ActiveCfg = Release|Any CPU
{5624B7B5-B5A7-41D8-9F10-CC5611109619}.Release|Any CPU.Build.0 = Release|Any CPU
{5624B7B5-B5A7-41D8-9F10-CC5611109619}.Release|x86.ActiveCfg = Release|Any CPU
{5624B7B5-B5A7-41D8-9F10-CC5611109619}.Release|x86.Build.0 = Release|Any CPU
{7EEEB4BB-F3E8-48FC-B4C5-70F0FFF8329B}.Debug|x86.ActiveCfg = Debug|Any CPU
{7EEEB4BB-F3E8-48FC-B4C5-70F0FFF8329B}.Debug|x86.Build.0 = Debug|Any CPU
+ {7EEEB4BB-F3E8-48FC-B4C5-70F0FFF8329B}.Release Mono|Any CPU.ActiveCfg = Release Mono|Any CPU
+ {7EEEB4BB-F3E8-48FC-B4C5-70F0FFF8329B}.Release Mono|Any CPU.Build.0 = Release Mono|Any CPU
{7EEEB4BB-F3E8-48FC-B4C5-70F0FFF8329B}.Release|Any CPU.ActiveCfg = Release|Any CPU
{7EEEB4BB-F3E8-48FC-B4C5-70F0FFF8329B}.Release|Any CPU.Build.0 = Release|Any CPU
{7EEEB4BB-F3E8-48FC-B4C5-70F0FFF8329B}.Release|x86.ActiveCfg = Release|Any CPU
{7EEEB4BB-F3E8-48FC-B4C5-70F0FFF8329B}.Release|x86.Build.0 = Release|Any CPU
{9142EEFA-7570-41E1-BFCC-468BB571AF2F}.Debug|x86.ActiveCfg = Debug|Any CPU
{9142EEFA-7570-41E1-BFCC-468BB571AF2F}.Debug|x86.Build.0 = Debug|Any CPU
+ {9142EEFA-7570-41E1-BFCC-468BB571AF2F}.Release Mono|Any CPU.ActiveCfg = Release Mono|Any CPU
+ {9142EEFA-7570-41E1-BFCC-468BB571AF2F}.Release Mono|Any CPU.Build.0 = Release Mono|Any CPU
{9142EEFA-7570-41E1-BFCC-468BB571AF2F}.Release|Any CPU.ActiveCfg = Release|Any CPU
{9142EEFA-7570-41E1-BFCC-468BB571AF2F}.Release|Any CPU.Build.0 = Release|Any CPU
{9142EEFA-7570-41E1-BFCC-468BB571AF2F}.Release|x86.ActiveCfg = Release|Any CPU
{9142EEFA-7570-41E1-BFCC-468BB571AF2F}.Release|x86.Build.0 = Release|Any CPU
{C4D2573A-3FD3-441F-81AF-174AC4CD4E1D}.Debug|x86.ActiveCfg = Debug|Any CPU
{C4D2573A-3FD3-441F-81AF-174AC4CD4E1D}.Debug|x86.Build.0 = Debug|Any CPU
+ {C4D2573A-3FD3-441F-81AF-174AC4CD4E1D}.Release Mono|Any CPU.ActiveCfg = Release Mono|Any CPU
+ {C4D2573A-3FD3-441F-81AF-174AC4CD4E1D}.Release Mono|Any CPU.Build.0 = Release Mono|Any CPU
{C4D2573A-3FD3-441F-81AF-174AC4CD4E1D}.Release|Any CPU.ActiveCfg = Release|Any CPU
{C4D2573A-3FD3-441F-81AF-174AC4CD4E1D}.Release|Any CPU.Build.0 = Release|Any CPU
{C4D2573A-3FD3-441F-81AF-174AC4CD4E1D}.Release|x86.ActiveCfg = Release|Any CPU
{C4D2573A-3FD3-441F-81AF-174AC4CD4E1D}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(MonoDevelopProperties) = preSolution
- StartupItem = MediaBrowser.Model\MediaBrowser.Model.csproj
+ StartupItem = MediaBrowser.Server.Mono\MediaBrowser.Server.Mono.csproj
EndGlobalSection
EndGlobal
diff --git a/MediaBrowser.Mono.userprefs b/MediaBrowser.Mono.userprefs
index 51ba9d804..1815e61ca 100644
--- a/MediaBrowser.Mono.userprefs
+++ b/MediaBrowser.Mono.userprefs
@@ -1,22 +1,9 @@
<Properties>
- <MonoDevelop.Ide.Workspace ActiveConfiguration="Release" />
+ <MonoDevelop.Ide.Workspace ActiveConfiguration="Release Mono" />
<MonoDevelop.Ide.Workbench ActiveDocument="MediaBrowser.Server.Mono\app.config">
<Files>
- <File FileName="MediaBrowser.Server.Mono\app.config" Line="3" Column="19" />
+ <File FileName="MediaBrowser.Server.Mono\app.config" Line="5" Column="20" />
</Files>
- <Pads>
- <Pad Id="ProjectPad">
- <State expanded="True" selected="True">
- <Node name="MediaBrowser.Server.Implementations" expanded="True" />
- </State>
- </Pad>
- <Pad Id="ClassPad">
- <State expanded="True" selected="True" />
- </Pad>
- <Pad Id="MonoDevelop.Debugger.WatchPad">
- <State />
- </Pad>
- </Pads>
</MonoDevelop.Ide.Workbench>
<MonoDevelop.Ide.DebuggingService.Breakpoints>
<BreakpointStore>
diff --git a/MediaBrowser.Providers/CollectionFolderImageProvider.cs b/MediaBrowser.Providers/CollectionFolderImageProvider.cs
index 45b1b36c2..6c36dbf7e 100644
--- a/MediaBrowser.Providers/CollectionFolderImageProvider.cs
+++ b/MediaBrowser.Providers/CollectionFolderImageProvider.cs
@@ -1,4 +1,5 @@
-using MediaBrowser.Common.IO;
+using System.Collections.Generic;
+using MediaBrowser.Common.IO;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library;
@@ -35,9 +36,9 @@ namespace MediaBrowser.Providers
.FirstOrDefault(i => i != null);
}
- protected override Guid GetFileSystemStamp(BaseItem item)
+ protected override Guid GetFileSystemStamp(IEnumerable<BaseItem> items)
{
- var files = item.ResolveArgs.PhysicalLocations
+ var files = items.SelectMany(i => i.ResolveArgs.PhysicalLocations)
.Select(i => new DirectoryInfo(i))
.SelectMany(i => i.EnumerateFiles("*", SearchOption.TopDirectoryOnly))
.Where(i =>
diff --git a/MediaBrowser.Providers/ImageFromMediaLocationProvider.cs b/MediaBrowser.Providers/ImageFromMediaLocationProvider.cs
index 37d39f3d9..0b6accf33 100644
--- a/MediaBrowser.Providers/ImageFromMediaLocationProvider.cs
+++ b/MediaBrowser.Providers/ImageFromMediaLocationProvider.cs
@@ -58,6 +58,21 @@ namespace MediaBrowser.Providers
return false;
}
+ protected override IEnumerable<BaseItem> GetItemsForFileStampComparison(BaseItem item)
+ {
+ var season = item as Season;
+ if (season != null)
+ {
+ var series = season.Series;
+ if (series != null)
+ {
+ return new[] { item, series };
+ }
+ }
+
+ return base.GetItemsForFileStampComparison(item);
+ }
+
/// <summary>
/// Gets the priority.
/// </summary>
@@ -197,7 +212,7 @@ namespace MediaBrowser.Providers
if (image != null)
{
- item.SetImage(ImageType.Logo, image.FullName);
+ item.SetImagePath(ImageType.Logo, image.FullName);
}
// Clearart
@@ -205,7 +220,7 @@ namespace MediaBrowser.Providers
if (image != null)
{
- item.SetImage(ImageType.Art, image.FullName);
+ item.SetImagePath(ImageType.Art, image.FullName);
}
// Disc
@@ -214,7 +229,7 @@ namespace MediaBrowser.Providers
if (image != null)
{
- item.SetImage(ImageType.Disc, image.FullName);
+ item.SetImagePath(ImageType.Disc, image.FullName);
}
// Box Image
@@ -222,7 +237,7 @@ namespace MediaBrowser.Providers
if (image != null)
{
- item.SetImage(ImageType.Box, image.FullName);
+ item.SetImagePath(ImageType.Box, image.FullName);
}
// BoxRear Image
@@ -230,7 +245,7 @@ namespace MediaBrowser.Providers
if (image != null)
{
- item.SetImage(ImageType.BoxRear, image.FullName);
+ item.SetImagePath(ImageType.BoxRear, image.FullName);
}
// Thumbnail Image
@@ -238,7 +253,7 @@ namespace MediaBrowser.Providers
if (image != null)
{
- item.SetImage(ImageType.Menu, image.FullName);
+ item.SetImagePath(ImageType.Menu, image.FullName);
}
PopulateBanner(item, args);
@@ -296,7 +311,7 @@ namespace MediaBrowser.Providers
if (image != null)
{
- item.SetImage(ImageType.Primary, image.FullName);
+ item.SetImagePath(ImageType.Primary, image.FullName);
}
}
@@ -324,7 +339,7 @@ namespace MediaBrowser.Providers
if (image != null)
{
- item.SetImage(ImageType.Banner, image.FullName);
+ item.SetImagePath(ImageType.Banner, image.FullName);
}
}
@@ -336,7 +351,8 @@ namespace MediaBrowser.Providers
private void PopulateThumb(BaseItem item, ItemResolveArgs args)
{
// Thumbnail Image
- var image = GetImage(item, args, "thumb");
+ var image = GetImage(item, args, "thumb") ??
+ GetImage(item, args, "landscape");
if (image == null)
{
@@ -352,7 +368,7 @@ namespace MediaBrowser.Providers
if (image != null)
{
- item.SetImage(ImageType.Thumb, image.FullName);
+ item.SetImagePath(ImageType.Thumb, image.FullName);
}
}
diff --git a/MediaBrowser.Providers/ImagesByNameProvider.cs b/MediaBrowser.Providers/ImagesByNameProvider.cs
index 590430823..8c5636580 100644
--- a/MediaBrowser.Providers/ImagesByNameProvider.cs
+++ b/MediaBrowser.Providers/ImagesByNameProvider.cs
@@ -1,4 +1,5 @@
-using MediaBrowser.Common.IO;
+using System.Collections.Generic;
+using MediaBrowser.Common.IO;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library;
@@ -69,9 +70,9 @@ namespace MediaBrowser.Providers
return GetImageFromLocation(location, filenameWithoutExtension);
}
- protected override Guid GetFileSystemStamp(BaseItem item)
+ protected override Guid GetFileSystemStamp(IEnumerable<BaseItem> items)
{
- var location = GetLocation(item);
+ var location = GetLocation(items.First());
try
{
diff --git a/MediaBrowser.Providers/LiveTv/ChannelProviderFromXml.cs b/MediaBrowser.Providers/LiveTv/ChannelProviderFromXml.cs
index b0bc1b875..8ee2553d0 100644
--- a/MediaBrowser.Providers/LiveTv/ChannelProviderFromXml.cs
+++ b/MediaBrowser.Providers/LiveTv/ChannelProviderFromXml.cs
@@ -28,7 +28,7 @@ namespace MediaBrowser.Providers.LiveTv
/// <returns><c>true</c> if XXXX, <c>false</c> otherwise</returns>
public override bool Supports(BaseItem item)
{
- return item is Channel;
+ return item is LiveTvChannel;
}
/// <summary>
@@ -74,7 +74,7 @@ namespace MediaBrowser.Providers.LiveTv
try
{
- new BaseItemXmlParser<Channel>(Logger).Fetch((Channel)item, path, cancellationToken);
+ new BaseItemXmlParser<LiveTvChannel>(Logger).Fetch((LiveTvChannel)item, path, cancellationToken);
}
finally
{
diff --git a/MediaBrowser.Providers/MediaBrowser.Providers.csproj b/MediaBrowser.Providers/MediaBrowser.Providers.csproj
index b5b41c6d3..94d171ce1 100644
--- a/MediaBrowser.Providers/MediaBrowser.Providers.csproj
+++ b/MediaBrowser.Providers/MediaBrowser.Providers.csproj
@@ -9,12 +9,12 @@
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>MediaBrowser.Providers</RootNamespace>
<AssemblyName>MediaBrowser.Providers</AssemblyName>
- <TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<SolutionDir Condition="$(SolutionDir) == '' Or $(SolutionDir) == '*Undefined*'">..\</SolutionDir>
<RestorePackages>true</RestorePackages>
<ProductVersion>10.0.0</ProductVersion>
<SchemaVersion>2.0</SchemaVersion>
+ <TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
@@ -24,6 +24,7 @@
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
+ <TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
@@ -32,6 +33,16 @@
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
+ <TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
+ </PropertyGroup>
+ <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release Mono|AnyCPU' ">
+ <DebugType>pdbonly</DebugType>
+ <Optimize>true</Optimize>
+ <OutputPath>bin\Release Mono\</OutputPath>
+ <DefineConstants>TRACE</DefineConstants>
+ <ErrorReport>prompt</ErrorReport>
+ <WarningLevel>4</WarningLevel>
+ <TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
</PropertyGroup>
<ItemGroup>
<Reference Include="System" />
@@ -75,7 +86,6 @@
<Compile Include="Movies\MovieProviderFromXml.cs" />
<Compile Include="Movies\OpenMovieDatabaseProvider.cs" />
<Compile Include="Movies\PersonProviderFromXml.cs" />
- <Compile Include="Movies\PersonUpdatesPreScanTask.cs" />
<Compile Include="Movies\MovieDbPersonProvider.cs" />
<Compile Include="Music\AlbumInfoFromSongProvider.cs" />
<Compile Include="Music\AlbumProviderFromXml.cs" />
@@ -110,6 +120,8 @@
<Compile Include="Savers\SeasonXmlSaver.cs" />
<Compile Include="Savers\SeriesXmlSaver.cs" />
<Compile Include="Savers\XmlSaverHelpers.cs" />
+ <Compile Include="Studios\StudioImageProvider.cs" />
+ <Compile Include="Studios\StudiosManualImageProvider.cs" />
<Compile Include="TV\EpisodeImageFromMediaLocationProvider.cs" />
<Compile Include="TV\EpisodeIndexNumberProvider.cs" />
<Compile Include="TV\EpisodeProviderFromXml.cs" />
@@ -123,6 +135,7 @@
<Compile Include="TV\ManualTvdbPersonImageProvider.cs" />
<Compile Include="TV\ManualTvdbSeasonImageProvider.cs" />
<Compile Include="TV\ManualTvdbSeriesImageProvider.cs" />
+ <Compile Include="TV\SeasonIndexNumberProvider.cs" />
<Compile Include="TV\TvdbEpisodeProvider.cs" />
<Compile Include="TV\TvdbSeasonProvider.cs" />
<Compile Include="TV\TvdbSeriesProvider.cs" />
@@ -135,6 +148,7 @@
<Compile Include="TV\TvdbPrescanTask.cs" />
<Compile Include="TV\TvdbSeriesImageProvider.cs" />
<Compile Include="UserRootFolderNameProvider.cs" />
+ <Compile Include="VirtualItemImageValidator.cs" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\MediaBrowser.Common\MediaBrowser.Common.csproj">
@@ -153,8 +167,14 @@
<ItemGroup>
<None Include="packages.config" />
</ItemGroup>
+ <ItemGroup>
+ <EmbeddedResource Include="Studios\thumbs.txt" />
+ </ItemGroup>
+ <ItemGroup>
+ <EmbeddedResource Include="Studios\posters.txt" />
+ </ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
- <Import Project="$(SolutionDir)\.nuget\NuGet.targets" Condition="Exists('$(SolutionDir)\.nuget\NuGet.targets')" />
+ <Import Project="$(SolutionDir)\.nuget\NuGet.targets" Condition=" '$(ConfigurationName)' != 'Release Mono' " />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.
<Target Name="BeforeBuild">
diff --git a/MediaBrowser.Providers/MediaInfo/AudioImageProvider.cs b/MediaBrowser.Providers/MediaInfo/AudioImageProvider.cs
index 264b24b87..5782e3e63 100644
--- a/MediaBrowser.Providers/MediaInfo/AudioImageProvider.cs
+++ b/MediaBrowser.Providers/MediaInfo/AudioImageProvider.cs
@@ -1,6 +1,5 @@
-using MediaBrowser.Common.IO;
+using MediaBrowser.Common.Extensions;
using MediaBrowser.Common.MediaInfo;
-using MediaBrowser.Controller;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.Audio;
@@ -23,12 +22,6 @@ namespace MediaBrowser.Providers.MediaInfo
public class AudioImageProvider : BaseMetadataProvider
{
/// <summary>
- /// Gets or sets the image cache.
- /// </summary>
- /// <value>The image cache.</value>
- public FileSystemRepository ImageCache { get; set; }
-
- /// <summary>
/// The _locks
/// </summary>
private readonly ConcurrentDictionary<string, SemaphoreSlim> _locks = new ConcurrentDictionary<string, SemaphoreSlim>();
@@ -48,8 +41,6 @@ namespace MediaBrowser.Providers.MediaInfo
: base(logManager, configurationManager)
{
_mediaEncoder = mediaEncoder;
-
- ImageCache = new FileSystemRepository(Kernel.Instance.FFMpegManager.AudioImagesDataPath);
}
/// <summary>
@@ -113,7 +104,7 @@ namespace MediaBrowser.Providers.MediaInfo
return ItemUpdateType.ImageUpdate;
}
}
-
+
/// <summary>
/// Fetches metadata and returns true or false indicating if any work that requires persistence was done
/// </summary>
@@ -154,13 +145,7 @@ namespace MediaBrowser.Providers.MediaInfo
{
cancellationToken.ThrowIfCancellationRequested();
- var album = item.Parent as MusicAlbum;
-
- var filename = item.Album ?? string.Empty;
- filename += item.Artists.FirstOrDefault() ?? string.Empty;
- filename += album == null ? item.Id.ToString("N") + item.DateModified.Ticks : album.Id.ToString("N") + album.DateModified.Ticks;
-
- var path = ImageCache.GetResourcePath(filename + "_primary", ".jpg");
+ var path = GetAudioImagePath(item);
if (!File.Exists(path))
{
@@ -196,6 +181,38 @@ namespace MediaBrowser.Providers.MediaInfo
}
/// <summary>
+ /// Gets the audio image path.
+ /// </summary>
+ /// <param name="item">The item.</param>
+ /// <returns>System.String.</returns>
+ private string GetAudioImagePath(Audio item)
+ {
+ var album = item.Parent as MusicAlbum;
+
+ var filename = item.Album ?? string.Empty;
+ filename += item.Artists.FirstOrDefault() ?? string.Empty;
+ filename += album == null ? item.Id.ToString("N") + item.DateModified.Ticks : album.Id.ToString("N") + album.DateModified.Ticks + "_primary";
+
+ filename = filename.GetMD5() + ".jpg";
+
+ var prefix = filename.Substring(0, 1);
+
+ return Path.Combine(AudioImagesPath, prefix, filename);
+ }
+
+ /// <summary>
+ /// Gets the audio images data path.
+ /// </summary>
+ /// <value>The audio images data path.</value>
+ public string AudioImagesPath
+ {
+ get
+ {
+ return Path.Combine(ConfigurationManager.ApplicationPaths.DataPath, "extracted-audio-images");
+ }
+ }
+
+ /// <summary>
/// Gets the lock.
/// </summary>
/// <param name="filename">The filename.</param>
diff --git a/MediaBrowser.Providers/MediaInfo/BaseFFProbeProvider.cs b/MediaBrowser.Providers/MediaInfo/BaseFFProbeProvider.cs
index 5f285e6d8..0fdeddb49 100644
--- a/MediaBrowser.Providers/MediaInfo/BaseFFProbeProvider.cs
+++ b/MediaBrowser.Providers/MediaInfo/BaseFFProbeProvider.cs
@@ -115,7 +115,7 @@ namespace MediaBrowser.Providers.MediaInfo
if (video != null)
{
- inputPath = MediaEncoderHelpers.GetInputArgument(video, isoMount, out type);
+ inputPath = MediaEncoderHelpers.GetInputArgument(video.Path, video.LocationType == LocationType.Remote, video.VideoType, video.IsoType, isoMount, video.PlayableStreamFileNames, out type);
}
return await MediaEncoder.GetMediaInfo(inputPath, type, cancellationToken).ConfigureAwait(false);
diff --git a/MediaBrowser.Providers/MediaInfo/FFProbeAudioInfoProvider.cs b/MediaBrowser.Providers/MediaInfo/FFProbeAudioInfoProvider.cs
index 673abea57..c38007288 100644
--- a/MediaBrowser.Providers/MediaInfo/FFProbeAudioInfoProvider.cs
+++ b/MediaBrowser.Providers/MediaInfo/FFProbeAudioInfoProvider.cs
@@ -151,8 +151,6 @@ namespace MediaBrowser.Providers.MediaInfo
// Disc number
audio.ParentIndexNumber = GetDictionaryDiscValue(tags, "disc");
- audio.Language = GetDictionaryValue(tags, "language");
-
audio.ProductionYear = GetDictionaryNumericValue(tags, "date");
// Several different forms of retaildate
diff --git a/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfoProvider.cs b/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfoProvider.cs
index 7e3e3da3b..8e07bc266 100644
--- a/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfoProvider.cs
+++ b/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfoProvider.cs
@@ -1,8 +1,8 @@
using MediaBrowser.Common.MediaInfo;
-using MediaBrowser.Controller;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Localization;
+using MediaBrowser.Controller.MediaInfo;
using MediaBrowser.Controller.Persistence;
using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Entities;
@@ -343,7 +343,7 @@ namespace MediaBrowser.Providers.MediaInfo
video.HasSubtitles = mediaStreams.Any(i => i.Type == MediaStreamType.Subtitle);
- await Kernel.Instance.FFMpegManager.PopulateChapterImages(video, chapters, false, false, cancellationToken).ConfigureAwait(false);
+ await FFMpegManager.Instance.PopulateChapterImages(video, chapters, false, false, cancellationToken).ConfigureAwait(false);
var videoFileChanged = CompareDate(video) > providerInfo.LastRefreshed;
@@ -377,7 +377,7 @@ namespace MediaBrowser.Providers.MediaInfo
if (!string.IsNullOrEmpty(genres))
{
- video.Genres = genres.Split(new[] { ';', '/' }, StringSplitOptions.RemoveEmptyEntries)
+ video.Genres = genres.Split(new[] { ';', '/', ',' }, StringSplitOptions.RemoveEmptyEntries)
.Where(i => !string.IsNullOrWhiteSpace(i))
.Select(i => i.Trim())
.ToList();
diff --git a/MediaBrowser.Providers/MediaInfo/VideoImageProvider.cs b/MediaBrowser.Providers/MediaInfo/VideoImageProvider.cs
index 2864983ce..d5815690f 100644
--- a/MediaBrowser.Providers/MediaInfo/VideoImageProvider.cs
+++ b/MediaBrowser.Providers/MediaInfo/VideoImageProvider.cs
@@ -1,6 +1,5 @@
-using MediaBrowser.Common.IO;
+using MediaBrowser.Common.Extensions;
using MediaBrowser.Common.MediaInfo;
-using MediaBrowser.Controller;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library;
@@ -12,7 +11,6 @@ using MediaBrowser.Model.Logging;
using System;
using System.Collections.Concurrent;
using System.IO;
-using System.Linq;
using System.Threading;
using System.Threading.Tasks;
@@ -21,12 +19,6 @@ namespace MediaBrowser.Providers.MediaInfo
class VideoImageProvider : BaseMetadataProvider
{
/// <summary>
- /// Gets or sets the image cache.
- /// </summary>
- /// <value>The image cache.</value>
- public FileSystemRepository ImageCache { get; set; }
-
- /// <summary>
/// The _locks
/// </summary>
private readonly ConcurrentDictionary<string, SemaphoreSlim> _locks = new ConcurrentDictionary<string, SemaphoreSlim>();
@@ -42,8 +34,6 @@ namespace MediaBrowser.Providers.MediaInfo
{
_mediaEncoder = mediaEncoder;
_isoManager = isoManager;
-
- ImageCache = new FileSystemRepository(Kernel.Instance.FFMpegManager.VideoImagesDataPath);
}
/// <summary>
@@ -206,9 +196,7 @@ namespace MediaBrowser.Providers.MediaInfo
{
cancellationToken.ThrowIfCancellationRequested();
- var filename = item.Path + "_" + item.DateModified.Ticks + "_primary";
-
- var path = ImageCache.GetResourcePath(filename, ".jpg");
+ var path = GetVideoImagePath(item);
if (!File.Exists(path))
{
@@ -265,7 +253,7 @@ namespace MediaBrowser.Providers.MediaInfo
InputType type;
- var inputPath = MediaEncoderHelpers.GetInputArgument(video, isoMount, out type);
+ var inputPath = MediaEncoderHelpers.GetInputArgument(video.Path, video.LocationType == LocationType.Remote, video.VideoType, video.IsoType, isoMount, video.PlayableStreamFileNames, out type);
await _mediaEncoder.ExtractImage(inputPath, type, video.Video3DFormat, imageOffset, path, cancellationToken).ConfigureAwait(false);
@@ -310,5 +298,33 @@ namespace MediaBrowser.Providers.MediaInfo
{
return _locks.GetOrAdd(filename, key => new SemaphoreSlim(1, 1));
}
+
+ /// <summary>
+ /// Gets the video images data path.
+ /// </summary>
+ /// <value>The video images data path.</value>
+ public string VideoImagesPath
+ {
+ get
+ {
+ return Path.Combine(ConfigurationManager.ApplicationPaths.DataPath, "extracted-video-images");
+ }
+ }
+
+ /// <summary>
+ /// Gets the audio image path.
+ /// </summary>
+ /// <param name="item">The item.</param>
+ /// <returns>System.String.</returns>
+ private string GetVideoImagePath(Video item)
+ {
+ var filename = item.Path + "_" + item.DateModified.Ticks + "_primary";
+
+ filename = filename.GetMD5() + ".jpg";
+
+ var prefix = filename.Substring(0, 1);
+
+ return Path.Combine(VideoImagesPath, prefix, filename);
+ }
}
}
diff --git a/MediaBrowser.Providers/Movies/FanArtMovieProvider.cs b/MediaBrowser.Providers/Movies/FanArtMovieProvider.cs
index e483b1d61..2682cf3c0 100644
--- a/MediaBrowser.Providers/Movies/FanArtMovieProvider.cs
+++ b/MediaBrowser.Providers/Movies/FanArtMovieProvider.cs
@@ -112,6 +112,11 @@ namespace MediaBrowser.Providers.Movies
/// <returns><c>true</c> if XXXX, <c>false</c> otherwise</returns>
public override bool Supports(BaseItem item)
{
+ return SupportsItem(item);
+ }
+
+ internal static bool SupportsItem(IHasImages item)
+ {
var trailer = item as Trailer;
if (trailer != null)
@@ -295,7 +300,7 @@ namespace MediaBrowser.Providers.Movies
cancellationToken.ThrowIfCancellationRequested();
- var backdropLimit = ConfigurationManager.Configuration.MaxBackdrops;
+ var backdropLimit = ConfigurationManager.Configuration.MovieOptions.MaxBackdrops;
if (ConfigurationManager.Configuration.DownloadMovieImages.Backdrops &&
item.BackdropImagePaths.Count < backdropLimit)
{
@@ -325,6 +330,7 @@ namespace MediaBrowser.Providers.Movies
{
continue;
}
+ break;
}
}
}
diff --git a/MediaBrowser.Providers/Movies/FanArtMovieUpdatesPrescanTask.cs b/MediaBrowser.Providers/Movies/FanArtMovieUpdatesPrescanTask.cs
index aff71c6db..9bd73bf65 100644
--- a/MediaBrowser.Providers/Movies/FanArtMovieUpdatesPrescanTask.cs
+++ b/MediaBrowser.Providers/Movies/FanArtMovieUpdatesPrescanTask.cs
@@ -16,7 +16,7 @@ using System.Threading.Tasks;
namespace MediaBrowser.Providers.Movies
{
- class FanArtMovieUpdatesPrescanTask : ILibraryPrescanTask
+ class FanArtMovieUpdatesPrescanTask : ILibraryPostScanTask
{
private const string UpdatesUrl = "http://api.fanart.tv/webservice/newmovies/{0}/{1}/";
diff --git a/MediaBrowser.Providers/Movies/ManualFanartMovieImageProvider.cs b/MediaBrowser.Providers/Movies/ManualFanartMovieImageProvider.cs
index d714128ea..fae8cd591 100644
--- a/MediaBrowser.Providers/Movies/ManualFanartMovieImageProvider.cs
+++ b/MediaBrowser.Providers/Movies/ManualFanartMovieImageProvider.cs
@@ -36,23 +36,24 @@ namespace MediaBrowser.Providers.Movies
get { return "FanArt"; }
}
- public bool Supports(BaseItem item)
+ public bool Supports(IHasImages item)
{
- return FanArtMovieProvider.Current.Supports(item);
+ return FanArtMovieProvider.SupportsItem(item);
}
- public async Task<IEnumerable<RemoteImageInfo>> GetImages(BaseItem item, ImageType imageType, CancellationToken cancellationToken)
+ public async Task<IEnumerable<RemoteImageInfo>> GetImages(IHasImages item, ImageType imageType, CancellationToken cancellationToken)
{
var images = await GetAllImages(item, cancellationToken).ConfigureAwait(false);
return images.Where(i => i.Type == imageType);
}
- public Task<IEnumerable<RemoteImageInfo>> GetAllImages(BaseItem item, CancellationToken cancellationToken)
+ public Task<IEnumerable<RemoteImageInfo>> GetAllImages(IHasImages item, CancellationToken cancellationToken)
{
+ var baseItem = (BaseItem)item;
var list = new List<RemoteImageInfo>();
- var movieId = item.GetProviderId(MetadataProviders.Tmdb);
+ var movieId = baseItem.GetProviderId(MetadataProviders.Tmdb);
if (!string.IsNullOrEmpty(movieId))
{
@@ -68,10 +69,10 @@ namespace MediaBrowser.Providers.Movies
}
}
- var language = _config.Configuration.PreferredMetadataLanguage;
+ var language = item.GetPreferredMetadataLanguage();
var isLanguageEn = string.Equals(language, "en", StringComparison.OrdinalIgnoreCase);
-
+
// Sort first by width to prioritize HD versions
list = list.OrderByDescending(i => i.Width ?? 0)
.ThenByDescending(i =>
diff --git a/MediaBrowser.Providers/Movies/ManualMovieDbImageProvider.cs b/MediaBrowser.Providers/Movies/ManualMovieDbImageProvider.cs
index e5bd3bf47..b9cabded7 100644
--- a/MediaBrowser.Providers/Movies/ManualMovieDbImageProvider.cs
+++ b/MediaBrowser.Providers/Movies/ManualMovieDbImageProvider.cs
@@ -35,19 +35,19 @@ namespace MediaBrowser.Providers.Movies
get { return "TheMovieDb"; }
}
- public bool Supports(BaseItem item)
+ public bool Supports(IHasImages item)
{
return MovieDbImagesProvider.SupportsItem(item);
}
- public async Task<IEnumerable<RemoteImageInfo>> GetImages(BaseItem item, ImageType imageType, CancellationToken cancellationToken)
+ public async Task<IEnumerable<RemoteImageInfo>> GetImages(IHasImages item, ImageType imageType, CancellationToken cancellationToken)
{
var images = await GetAllImages(item, cancellationToken).ConfigureAwait(false);
return images.Where(i => i.Type == imageType);
}
- public async Task<IEnumerable<RemoteImageInfo>> GetAllImages(BaseItem item, CancellationToken cancellationToken)
+ public async Task<IEnumerable<RemoteImageInfo>> GetAllImages(IHasImages item, CancellationToken cancellationToken)
{
var list = new List<RemoteImageInfo>();
@@ -87,7 +87,7 @@ namespace MediaBrowser.Providers.Movies
RatingType = RatingType.Score
}));
- var language = _config.Configuration.PreferredMetadataLanguage;
+ var language = item.GetPreferredMetadataLanguage();
var isLanguageEn = string.Equals(language, "en", StringComparison.OrdinalIgnoreCase);
@@ -114,17 +114,15 @@ namespace MediaBrowser.Providers.Movies
.ThenByDescending(i => i.VoteCount ?? 0)
.ToList();
}
-
+
/// <summary>
/// Gets the posters.
/// </summary>
/// <param name="images">The images.</param>
/// <param name="item">The item.</param>
/// <returns>IEnumerable{MovieDbProvider.Poster}.</returns>
- private IEnumerable<MovieDbProvider.Poster> GetPosters(MovieDbProvider.Images images, BaseItem item)
+ private IEnumerable<MovieDbProvider.Poster> GetPosters(MovieDbProvider.Images images, IHasImages item)
{
- var language = _config.Configuration.PreferredMetadataLanguage;
-
return images.posters ?? new List<MovieDbProvider.Poster>();
}
@@ -134,7 +132,7 @@ namespace MediaBrowser.Providers.Movies
/// <param name="images">The images.</param>
/// <param name="item">The item.</param>
/// <returns>IEnumerable{MovieDbProvider.Backdrop}.</returns>
- private IEnumerable<MovieDbProvider.Backdrop> GetBackdrops(MovieDbProvider.Images images, BaseItem item)
+ private IEnumerable<MovieDbProvider.Backdrop> GetBackdrops(MovieDbProvider.Images images, IHasImages item)
{
var eligibleBackdrops = images.backdrops == null ? new List<MovieDbProvider.Backdrop>() :
images.backdrops
@@ -150,9 +148,9 @@ namespace MediaBrowser.Providers.Movies
/// <param name="item">The item.</param>
/// <param name="jsonSerializer">The json serializer.</param>
/// <returns>Task{MovieImages}.</returns>
- private MovieDbProvider.Images FetchImages(BaseItem item, IJsonSerializer jsonSerializer)
+ private MovieDbProvider.Images FetchImages(IHasImages item, IJsonSerializer jsonSerializer)
{
- var path = MovieDbProvider.Current.GetImagesDataFilePath(item);
+ var path = MovieDbProvider.Current.GetDataFilePath((BaseItem)item);
if (!string.IsNullOrEmpty(path))
{
diff --git a/MediaBrowser.Providers/Movies/ManualMovieDbPersonImageProvider.cs b/MediaBrowser.Providers/Movies/ManualMovieDbPersonImageProvider.cs
index b381de332..453284751 100644
--- a/MediaBrowser.Providers/Movies/ManualMovieDbPersonImageProvider.cs
+++ b/MediaBrowser.Providers/Movies/ManualMovieDbPersonImageProvider.cs
@@ -6,7 +6,6 @@ using MediaBrowser.Model.Providers;
using MediaBrowser.Model.Serialization;
using System;
using System.Collections.Generic;
-using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
@@ -34,60 +33,44 @@ namespace MediaBrowser.Providers.Movies
get { return "TheMovieDb"; }
}
- public bool Supports(BaseItem item)
+ public bool Supports(IHasImages item)
{
return item is Person;
}
- public async Task<IEnumerable<RemoteImageInfo>> GetImages(BaseItem item, ImageType imageType, CancellationToken cancellationToken)
+ public async Task<IEnumerable<RemoteImageInfo>> GetImages(IHasImages item, ImageType imageType, CancellationToken cancellationToken)
{
var images = await GetAllImages(item, cancellationToken).ConfigureAwait(false);
return images.Where(i => i.Type == imageType);
}
- public Task<IEnumerable<RemoteImageInfo>> GetAllImages(BaseItem item, CancellationToken cancellationToken)
+ public async Task<IEnumerable<RemoteImageInfo>> GetAllImages(IHasImages item, CancellationToken cancellationToken)
{
- return GetAllImagesInternal(item, true, cancellationToken);
- }
-
- public async Task<IEnumerable<RemoteImageInfo>> GetAllImagesInternal(BaseItem item, bool retryOnMissingData, CancellationToken cancellationToken)
- {
- var id = item.GetProviderId(MetadataProviders.Tmdb);
+ var person = (Person)item;
+ var id = person.GetProviderId(MetadataProviders.Tmdb);
if (!string.IsNullOrEmpty(id))
{
- var dataFilePath = MovieDbPersonProvider.GetPersonDataFilePath(_config.ApplicationPaths, id);
-
- try
- {
- var result = _jsonSerializer.DeserializeFromFile<MovieDbPersonProvider.PersonResult>(dataFilePath);
-
- var images = result.images ?? new MovieDbPersonProvider.Images();
+ await MovieDbPersonProvider.Current.DownloadPersonInfoIfNeeded(id, cancellationToken).ConfigureAwait(false);
- var tmdbSettings = await MovieDbProvider.Current.GetTmdbSettings(cancellationToken).ConfigureAwait(false);
+ var dataFilePath = MovieDbPersonProvider.GetPersonDataFilePath(_config.ApplicationPaths, id);
- var tmdbImageUrl = tmdbSettings.images.base_url + "original";
+ var result = _jsonSerializer.DeserializeFromFile<MovieDbPersonProvider.PersonResult>(dataFilePath);
- return GetImages(images, tmdbImageUrl);
- }
- catch (FileNotFoundException)
- {
+ var images = result.images ?? new MovieDbPersonProvider.Images();
- }
+ var tmdbSettings = await MovieDbProvider.Current.GetTmdbSettings(cancellationToken).ConfigureAwait(false);
- if (retryOnMissingData)
- {
- await MovieDbPersonProvider.Current.DownloadPersonInfo(id, cancellationToken).ConfigureAwait(false);
+ var tmdbImageUrl = tmdbSettings.images.base_url + "original";
- return await GetAllImagesInternal(item, false, cancellationToken).ConfigureAwait(false);
- }
+ return GetImages(images, item.GetPreferredMetadataLanguage(), tmdbImageUrl);
}
return new List<RemoteImageInfo>();
}
-
- private IEnumerable<RemoteImageInfo> GetImages(MovieDbPersonProvider.Images images, string baseImageUrl)
+
+ private IEnumerable<RemoteImageInfo> GetImages(MovieDbPersonProvider.Images images, string preferredLanguage, string baseImageUrl)
{
var list = new List<RemoteImageInfo>();
@@ -104,7 +87,7 @@ namespace MediaBrowser.Providers.Movies
}));
}
- var language = _config.Configuration.PreferredMetadataLanguage;
+ var language = preferredLanguage;
var isLanguageEn = string.Equals(language, "en", StringComparison.OrdinalIgnoreCase);
diff --git a/MediaBrowser.Providers/Movies/MovieDbImagesProvider.cs b/MediaBrowser.Providers/Movies/MovieDbImagesProvider.cs
index d63fcec5c..7386f47f4 100644
--- a/MediaBrowser.Providers/Movies/MovieDbImagesProvider.cs
+++ b/MediaBrowser.Providers/Movies/MovieDbImagesProvider.cs
@@ -61,7 +61,7 @@ namespace MediaBrowser.Providers.Movies
return SupportsItem(item);
}
- public static bool SupportsItem(BaseItem item)
+ internal static bool SupportsItem(IHasImages item)
{
var trailer = item as Trailer;
@@ -132,7 +132,9 @@ namespace MediaBrowser.Providers.Movies
}
// Don't refresh if we already have both poster and backdrop and we're not refreshing images
- if (item.HasImage(ImageType.Primary) && item.BackdropImagePaths.Count >= ConfigurationManager.Configuration.MaxBackdrops)
+ if (item.HasImage(ImageType.Primary) &&
+ item.BackdropImagePaths.Count >= ConfigurationManager.Configuration.MovieOptions.MaxBackdrops &&
+ !item.LockedFields.Contains(MetadataFields.Images))
{
return false;
}
@@ -142,7 +144,7 @@ namespace MediaBrowser.Providers.Movies
protected override bool NeedsRefreshBasedOnCompareDate(BaseItem item, BaseProviderInfo providerInfo)
{
- var path = MovieDbProvider.Current.GetImagesDataFilePath(item);
+ var path = MovieDbProvider.Current.GetDataFilePath(item);
if (!string.IsNullOrEmpty(path))
{
@@ -167,7 +169,6 @@ namespace MediaBrowser.Providers.Movies
public override async Task<bool> FetchAsync(BaseItem item, bool force, BaseProviderInfo providerInfo, CancellationToken cancellationToken)
{
var images = await _providerManager.GetAvailableRemoteImages(item, cancellationToken, ManualMovieDbImageProvider.ProviderName).ConfigureAwait(false);
-
await ProcessImages(item, images.ToList(), cancellationToken).ConfigureAwait(false);
SetLastRefreshed(item, DateTime.UtcNow, providerInfo);
@@ -190,7 +191,7 @@ namespace MediaBrowser.Providers.Movies
.ToList();
// poster
- if (eligiblePosters.Count > 0 && !item.HasImage(ImageType.Primary))
+ if (eligiblePosters.Count > 0 && !item.HasImage(ImageType.Primary) && !item.LockedFields.Contains(MetadataFields.Images))
{
var poster = eligiblePosters[0];
@@ -210,13 +211,16 @@ namespace MediaBrowser.Providers.Movies
cancellationToken.ThrowIfCancellationRequested();
var eligibleBackdrops = images
- .Where(i => i.Type == ImageType.Backdrop && i.Width.HasValue && i.Width.Value >= ConfigurationManager.Configuration.MinMovieBackdropDownloadWidth)
+ .Where(i => i.Type == ImageType.Backdrop && i.Width.HasValue && i.Width.Value >= ConfigurationManager.Configuration.MovieOptions.MinBackdropWidth)
.ToList();
- var backdropLimit = ConfigurationManager.Configuration.MaxBackdrops;
+ var backdropLimit = ConfigurationManager.Configuration.MovieOptions.MaxBackdrops;
// backdrops - only download if earlier providers didn't find any (fanart)
- if (eligibleBackdrops.Count > 0 && ConfigurationManager.Configuration.DownloadMovieImages.Backdrops && item.BackdropImagePaths.Count < backdropLimit)
+ if (eligibleBackdrops.Count > 0 &&
+ ConfigurationManager.Configuration.DownloadMovieImages.Backdrops &&
+ item.BackdropImagePaths.Count < backdropLimit &&
+ !item.LockedFields.Contains(MetadataFields.Backdrops))
{
for (var i = 0; i < eligibleBackdrops.Count; i++)
{
diff --git a/MediaBrowser.Providers/Movies/MovieDbPersonImageProvider.cs b/MediaBrowser.Providers/Movies/MovieDbPersonImageProvider.cs
index 8fa2ea249..f6c908a7c 100644
--- a/MediaBrowser.Providers/Movies/MovieDbPersonImageProvider.cs
+++ b/MediaBrowser.Providers/Movies/MovieDbPersonImageProvider.cs
@@ -164,7 +164,6 @@ namespace MediaBrowser.Providers.Movies
public override async Task<bool> FetchAsync(BaseItem item, bool force, BaseProviderInfo providerInfo, CancellationToken cancellationToken)
{
var images = await _providerManager.GetAvailableRemoteImages(item, cancellationToken, ManualMovieDbPersonImageProvider.ProviderName).ConfigureAwait(false);
-
await ProcessImages(item, images.ToList(), cancellationToken).ConfigureAwait(false);
SetLastRefreshed(item, DateTime.UtcNow, providerInfo);
@@ -187,7 +186,7 @@ namespace MediaBrowser.Providers.Movies
.ToList();
// poster
- if (eligiblePosters.Count > 0 && !item.HasImage(ImageType.Primary))
+ if (eligiblePosters.Count > 0 && !item.HasImage(ImageType.Primary) && !item.LockedFields.Contains(MetadataFields.Images))
{
var poster = eligiblePosters[0];
diff --git a/MediaBrowser.Providers/Movies/MovieDbPersonProvider.cs b/MediaBrowser.Providers/Movies/MovieDbPersonProvider.cs
index 3efd8d7fe..c16c50412 100644
--- a/MediaBrowser.Providers/Movies/MovieDbPersonProvider.cs
+++ b/MediaBrowser.Providers/Movies/MovieDbPersonProvider.cs
@@ -86,7 +86,7 @@ namespace MediaBrowser.Providers.Movies
protected override bool NeedsRefreshInternal(BaseItem item, BaseProviderInfo providerInfo)
{
- if (HasAltMeta(item) && !ConfigurationManager.Configuration.EnableTmdbUpdates)
+ if (HasAltMeta(item))
return false;
return base.NeedsRefreshInternal(item, providerInfo);
@@ -235,17 +235,12 @@ namespace MediaBrowser.Providers.Movies
/// <returns>Task.</returns>
private async Task FetchInfo(Person person, string id, bool isForcedRefresh, CancellationToken cancellationToken)
{
- var dataFilePath = GetPersonDataFilePath(ConfigurationManager.ApplicationPaths, id);
+ await DownloadPersonInfoIfNeeded(id, cancellationToken).ConfigureAwait(false);
- // Only download if not already there
- // The prescan task will take care of updates so we don't need to re-download here
- if (!File.Exists(dataFilePath))
+ if (isForcedRefresh || !HasAltMeta(person))
{
- await DownloadPersonInfo(id, cancellationToken).ConfigureAwait(false);
- }
+ var dataFilePath = GetPersonDataFilePath(ConfigurationManager.ApplicationPaths, id);
- if (isForcedRefresh || ConfigurationManager.Configuration.EnableTmdbUpdates || !HasAltMeta(person))
- {
var info = JsonSerializer.DeserializeFromFile<PersonResult>(dataFilePath);
cancellationToken.ThrowIfCancellationRequested();
@@ -254,10 +249,17 @@ namespace MediaBrowser.Providers.Movies
}
}
- internal async Task DownloadPersonInfo(string id, CancellationToken cancellationToken)
+ internal async Task DownloadPersonInfoIfNeeded(string id, CancellationToken cancellationToken)
{
var personDataPath = GetPersonDataPath(ConfigurationManager.ApplicationPaths, id);
+ var fileInfo = _fileSystem.GetFileSystemInfo(personDataPath);
+
+ if (fileInfo.Exists && (DateTime.UtcNow - _fileSystem.GetLastWriteTimeUtc(fileInfo)).TotalDays <= 7)
+ {
+ return;
+ }
+
var url = string.Format(@"http://api.themoviedb.org/3/person/{1}?api_key={0}&append_to_response=credits,images", MovieDbProvider.ApiKey, id);
using (var json = await MovieDbProvider.Current.GetMovieDbResponse(new HttpRequestOptions
diff --git a/MediaBrowser.Providers/Movies/MovieDbProvider.cs b/MediaBrowser.Providers/Movies/MovieDbProvider.cs
index ecf5a5951..dc267b37c 100644
--- a/MediaBrowser.Providers/Movies/MovieDbProvider.cs
+++ b/MediaBrowser.Providers/Movies/MovieDbProvider.cs
@@ -205,13 +205,9 @@ namespace MediaBrowser.Providers.Movies
if (!string.IsNullOrEmpty(path))
{
- var imagesFilePath = GetImagesDataFilePath(item);
-
var fileInfo = new FileInfo(path);
- var imagesFileInfo = new FileInfo(imagesFilePath);
- return !fileInfo.Exists || _fileSystem.GetLastWriteTimeUtc(fileInfo) > providerInfo.LastRefreshed ||
- !imagesFileInfo.Exists || _fileSystem.GetLastWriteTimeUtc(imagesFileInfo) > providerInfo.LastRefreshed;
+ return !fileInfo.Exists || _fileSystem.GetLastWriteTimeUtc(fileInfo) > providerInfo.LastRefreshed;
}
return base.NeedsRefreshBasedOnCompareDate(item, providerInfo);
@@ -263,7 +259,8 @@ namespace MediaBrowser.Providers.Movies
id = item.GetProviderId(MetadataProviders.Imdb);
}
- if (string.IsNullOrEmpty(id))
+ // Don't search for music video id's because it is very easy to misidentify.
+ if (string.IsNullOrEmpty(id) && !(item is MusicVideo))
{
id = await FindId(item, cancellationToken).ConfigureAwait(false);
}
@@ -317,7 +314,7 @@ namespace MediaBrowser.Providers.Movies
var year = item.ProductionYear ?? yearInName;
Logger.Info("MovieDbProvider: Finding id for item: " + name);
- string language = ConfigurationManager.Configuration.PreferredMetadataLanguage.ToLower();
+ var language = item.GetPreferredMetadataLanguage().ToLower();
//if we are a boxset - look at our first child
var boxset = item as BoxSet;
@@ -501,43 +498,35 @@ namespace MediaBrowser.Providers.Movies
{
// Id could be ImdbId or TmdbId
- var language = ConfigurationManager.Configuration.PreferredMetadataLanguage;
+ var language = item.GetPreferredMetadataLanguage();
+ var country = item.GetPreferredMetadataCountryCode();
var dataFilePath = GetDataFilePath(item);
var tmdbId = item.GetProviderId(MetadataProviders.Tmdb);
- if (string.IsNullOrEmpty(dataFilePath) || !File.Exists(dataFilePath) || !File.Exists(GetImagesDataFilePath(item)))
- {
- var isBoxSet = item is BoxSet;
+ var isBoxSet = item is BoxSet;
+ if (string.IsNullOrEmpty(dataFilePath) || !File.Exists(dataFilePath))
+ {
var mainResult = await FetchMainResult(id, isBoxSet, language, cancellationToken).ConfigureAwait(false);
if (mainResult == null) return;
tmdbId = mainResult.id.ToString(_usCulture);
- var movieDataPath = GetMovieDataPath(ConfigurationManager.ApplicationPaths, isBoxSet, tmdbId);
-
- dataFilePath = Path.Combine(movieDataPath, language + ".json");
+ dataFilePath = GetDataFilePath(isBoxSet, tmdbId, language);
var directory = Path.GetDirectoryName(dataFilePath);
Directory.CreateDirectory(directory);
JsonSerializer.SerializeToFile(mainResult, dataFilePath);
-
- // Now get the language-less version
- mainResult = await FetchMainResult(id, isBoxSet, null, cancellationToken).ConfigureAwait(false);
-
- dataFilePath = Path.Combine(movieDataPath, "default.json");
-
- JsonSerializer.SerializeToFile(mainResult, dataFilePath);
}
if (isForcedRefresh || ConfigurationManager.Configuration.EnableTmdbUpdates || !HasAltMeta(item))
{
- dataFilePath = GetDataFilePath(item, tmdbId);
+ dataFilePath = GetDataFilePath(isBoxSet, tmdbId, language);
if (!string.IsNullOrEmpty(dataFilePath))
{
@@ -553,27 +542,18 @@ namespace MediaBrowser.Providers.Movies
/// </summary>
/// <param name="id">The id.</param>
/// <param name="isBoxSet">if set to <c>true</c> [is box set].</param>
- /// <param name="dataPath">The data path.</param>
+ /// <param name="preferredMetadataLanguage">The preferred metadata language.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>Task.</returns>
- internal async Task DownloadMovieInfo(string id, bool isBoxSet, string dataPath, CancellationToken cancellationToken)
+ internal async Task DownloadMovieInfo(string id, bool isBoxSet, string preferredMetadataLanguage, CancellationToken cancellationToken)
{
- var language = ConfigurationManager.Configuration.PreferredMetadataLanguage;
-
- var mainResult = await FetchMainResult(id, isBoxSet, language, cancellationToken).ConfigureAwait(false);
+ var mainResult = await FetchMainResult(id, isBoxSet, preferredMetadataLanguage, cancellationToken).ConfigureAwait(false);
if (mainResult == null) return;
- var dataFilePath = Path.Combine(dataPath, language + ".json");
-
- Directory.CreateDirectory(dataPath);
-
- JsonSerializer.SerializeToFile(mainResult, dataFilePath);
-
- // Now get the language-less version
- mainResult = await FetchMainResult(id, isBoxSet, null, cancellationToken).ConfigureAwait(false);
+ var dataFilePath = GetDataFilePath(isBoxSet, id, preferredMetadataLanguage);
- dataFilePath = Path.Combine(dataPath, "default.json");
+ Directory.CreateDirectory(Path.GetDirectoryName(dataFilePath));
JsonSerializer.SerializeToFile(mainResult, dataFilePath);
}
@@ -592,30 +572,17 @@ namespace MediaBrowser.Providers.Movies
return null;
}
- return GetDataFilePath(item, id);
- }
-
- internal string GetDataFilePath(BaseItem item, string tmdbId)
- {
- var language = ConfigurationManager.Configuration.PreferredMetadataLanguage;
-
- var path = GetMovieDataPath(ConfigurationManager.ApplicationPaths, item is BoxSet, tmdbId);
-
- path = Path.Combine(path, language + ".json");
-
- return path;
+ return GetDataFilePath(item is BoxSet, id, item.GetPreferredMetadataLanguage());
}
- internal string GetImagesDataFilePath(BaseItem item)
+ internal string GetDataFilePath(bool isBoxset, string tmdbId, string preferredLanguage)
{
- var path = GetDataFilePath(item);
+ var path = GetMovieDataPath(ConfigurationManager.ApplicationPaths, isBoxset, tmdbId);
- if (!string.IsNullOrEmpty(path))
- {
- path = Path.Combine(Path.GetDirectoryName(path), "default.json");
- }
+ var filename = string.Format("all-{0}.json",
+ preferredLanguage ?? string.Empty);
- return path;
+ return Path.Combine(path, filename);
}
/// <summary>
@@ -632,9 +599,18 @@ namespace MediaBrowser.Providers.Movies
var url = string.Format(baseUrl, id, ApiKey);
+ // Get images in english and with no language
+ url += "&include_image_language=en,null";
+
if (!string.IsNullOrEmpty(language))
{
- url += "&language=" + language;
+ // If preferred language isn't english, get those images too
+ if (!string.Equals(language, "en", StringComparison.OrdinalIgnoreCase))
+ {
+ url += string.Format(",{0}", language);
+ }
+
+ url += string.Format("&language={0}", language);
}
CompleteMovieData mainResult;
@@ -744,27 +720,29 @@ namespace MediaBrowser.Providers.Movies
// tmdb appears to have unified their numbers to always report "7.3" regardless of country
// so I removed the culture-specific processing here because it was not working for other countries -ebr
// Movies get this from imdb
- if (movie is BoxSet && float.TryParse(voteAvg, NumberStyles.AllowDecimalPoint, CultureInfo.InvariantCulture, out rating))
+ if (!(movie is Movie) && float.TryParse(voteAvg, NumberStyles.AllowDecimalPoint, CultureInfo.InvariantCulture, out rating))
{
movie.CommunityRating = rating;
}
// Movies get this from imdb
- if (movie is BoxSet)
+ if (!(movie is Movie))
{
movie.VoteCount = movieData.vote_count;
}
+ var preferredCountryCode = movie.GetPreferredMetadataCountryCode();
+
//release date and certification are retrieved based on configured country and we fall back on US if not there and to minimun release date if still no match
if (movieData.releases != null && movieData.releases.countries != null)
{
- var ourRelease = movieData.releases.countries.FirstOrDefault(c => c.iso_3166_1.Equals(ConfigurationManager.Configuration.MetadataCountryCode, StringComparison.OrdinalIgnoreCase)) ?? new Country();
+ var ourRelease = movieData.releases.countries.FirstOrDefault(c => c.iso_3166_1.Equals(preferredCountryCode, StringComparison.OrdinalIgnoreCase)) ?? new Country();
var usRelease = movieData.releases.countries.FirstOrDefault(c => c.iso_3166_1.Equals("US", StringComparison.OrdinalIgnoreCase)) ?? new Country();
var minimunRelease = movieData.releases.countries.OrderBy(c => c.release_date).FirstOrDefault() ?? new Country();
if (!movie.LockedFields.Contains(MetadataFields.OfficialRating))
{
- var ratingPrefix = ConfigurationManager.Configuration.MetadataCountryCode.Equals("us", StringComparison.OrdinalIgnoreCase) ? "" : ConfigurationManager.Configuration.MetadataCountryCode + "-";
+ var ratingPrefix = string.Equals(preferredCountryCode, "us", StringComparison.OrdinalIgnoreCase) ? "" : preferredCountryCode + "-";
movie.OfficialRating = !string.IsNullOrEmpty(ourRelease.certification)
? ratingPrefix + ourRelease.certification
: !string.IsNullOrEmpty(usRelease.certification)
@@ -773,44 +751,16 @@ namespace MediaBrowser.Providers.Movies
? minimunRelease.iso_3166_1 + "-" + minimunRelease.certification
: null;
}
-
- if (ourRelease.release_date != default(DateTime))
- {
- if (ourRelease.release_date.Year != 1)
- {
- movie.PremiereDate = ourRelease.release_date.ToUniversalTime();
- movie.ProductionYear = ourRelease.release_date.Year;
- }
- }
- else if (usRelease.release_date != default(DateTime))
- {
- if (usRelease.release_date.Year != 1)
- {
- movie.PremiereDate = usRelease.release_date.ToUniversalTime();
- movie.ProductionYear = usRelease.release_date.Year;
- }
- }
- else if (minimunRelease.release_date != default(DateTime))
- {
- if (minimunRelease.release_date.Year != 1)
- {
-
- movie.PremiereDate = minimunRelease.release_date.ToUniversalTime();
- movie.ProductionYear = minimunRelease.release_date.Year;
- }
- }
}
- else
+
+ if (movieData.release_date.Year != 1)
{
- if (movieData.release_date.Year != 1)
- {
- //no specific country release info at all
- movie.PremiereDate = movieData.release_date.ToUniversalTime();
- movie.ProductionYear = movieData.release_date.Year;
- }
+ //no specific country release info at all
+ movie.PremiereDate = movieData.release_date.ToUniversalTime();
+ movie.ProductionYear = movieData.release_date.Year;
}
- //if that didn't find a rating and we are a boxset, use the one from our first child
+ // If that didn't find a rating and we are a boxset, use the one from our first child
if (movie.OfficialRating == null && movie is BoxSet && !movie.LockedFields.Contains(MetadataFields.OfficialRating))
{
var boxset = movie as BoxSet;
@@ -837,15 +787,17 @@ namespace MediaBrowser.Providers.Movies
// genres
// Movies get this from imdb
- if (movieData.genres != null && !movie.LockedFields.Contains(MetadataFields.Genres))
+ var genres = movieData.genres ?? new List<GenreItem>();
+ if (!movie.LockedFields.Contains(MetadataFields.Genres))
{
// Only grab them if a boxset or there are no genres.
// For movies and trailers we'll use imdb via omdb
- if (movie is BoxSet || movie.Genres.Count == 0)
+ // But omdb data is for english users only so fetch if language is not english
+ if (!(movie is Movie) || movie.Genres.Count == 0 || !string.Equals(movie.GetPreferredMetadataLanguage(), "en", StringComparison.OrdinalIgnoreCase))
{
movie.Genres.Clear();
- foreach (var genre in movieData.genres.Select(g => g.name))
+ foreach (var genre in genres.Select(g => g.name))
{
movie.AddGenre(genre);
}
diff --git a/MediaBrowser.Providers/Movies/MovieProviderFromXml.cs b/MediaBrowser.Providers/Movies/MovieProviderFromXml.cs
index bb1299f67..3ba777b37 100644
--- a/MediaBrowser.Providers/Movies/MovieProviderFromXml.cs
+++ b/MediaBrowser.Providers/Movies/MovieProviderFromXml.cs
@@ -1,7 +1,7 @@
using MediaBrowser.Common.IO;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Entities;
-using MediaBrowser.Controller.Entities.Movies;
+using MediaBrowser.Controller.Entities.TV;
using MediaBrowser.Controller.Persistence;
using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Entities;
@@ -48,7 +48,8 @@ namespace MediaBrowser.Providers.Movies
return !trailer.IsLocalTrailer;
}
- return item is Movie || item is MusicVideo || item is AdultVideo;
+ // Check parent for null to avoid running this against things like video backdrops
+ return item is Video && !(item is Episode) && item.Parent != null;
}
/// <summary>
diff --git a/MediaBrowser.Providers/Movies/MovieUpdatesPrescanTask.cs b/MediaBrowser.Providers/Movies/MovieUpdatesPrescanTask.cs
index f8fb133c6..291d2ff4d 100644
--- a/MediaBrowser.Providers/Movies/MovieUpdatesPrescanTask.cs
+++ b/MediaBrowser.Providers/Movies/MovieUpdatesPrescanTask.cs
@@ -2,7 +2,10 @@
using MediaBrowser.Common.Net;
using MediaBrowser.Common.Progress;
using MediaBrowser.Controller.Configuration;
+using MediaBrowser.Controller.Entities;
+using MediaBrowser.Controller.Entities.Movies;
using MediaBrowser.Controller.Library;
+using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Logging;
using MediaBrowser.Model.Serialization;
using System;
@@ -16,7 +19,7 @@ using System.Threading.Tasks;
namespace MediaBrowser.Providers.Movies
{
- public class MovieUpdatesPreScanTask : ILibraryPrescanTask
+ public class MovieUpdatesPreScanTask : ILibraryPostScanTask
{
/// <summary>
/// The updates URL
@@ -37,6 +40,7 @@ namespace MediaBrowser.Providers.Movies
private readonly IServerConfigurationManager _config;
private readonly IJsonSerializer _json;
private readonly IFileSystem _fileSystem;
+ private readonly ILibraryManager _libraryManager;
/// <summary>
/// Initializes a new instance of the <see cref="MovieUpdatesPreScanTask"/> class.
@@ -45,13 +49,14 @@ namespace MediaBrowser.Providers.Movies
/// <param name="httpClient">The HTTP client.</param>
/// <param name="config">The config.</param>
/// <param name="json">The json.</param>
- public MovieUpdatesPreScanTask(ILogger logger, IHttpClient httpClient, IServerConfigurationManager config, IJsonSerializer json, IFileSystem fileSystem)
+ public MovieUpdatesPreScanTask(ILogger logger, IHttpClient httpClient, IServerConfigurationManager config, IJsonSerializer json, IFileSystem fileSystem, ILibraryManager libraryManager)
{
_logger = logger;
_httpClient = httpClient;
_config = config;
_json = json;
_fileSystem = fileSystem;
+ _libraryManager = libraryManager;
}
protected readonly CultureInfo UsCulture = new CultureInfo("en-US");
@@ -101,7 +106,7 @@ namespace MediaBrowser.Providers.Movies
var timestampFileInfo = new FileInfo(timestampFile);
// Don't check for updates every single time
- if (timestampFileInfo.Exists && (DateTime.UtcNow - _fileSystem.GetLastWriteTimeUtc(timestampFileInfo)).TotalDays < 3)
+ if (timestampFileInfo.Exists && (DateTime.UtcNow - _fileSystem.GetLastWriteTimeUtc(timestampFileInfo)).TotalDays < 7)
{
return;
}
@@ -196,15 +201,30 @@ namespace MediaBrowser.Providers.Movies
var list = ids.ToList();
var numComplete = 0;
+ // Gather all movies into a lookup by tmdb id
+ var allMovies = _libraryManager.RootFolder.RecursiveChildren
+ .Where(i => i is Movie || i is Trailer)
+ .Where(i => !string.IsNullOrEmpty(i.GetProviderId(MetadataProviders.Tmdb)))
+ .ToLookup(i => i.GetProviderId(MetadataProviders.Tmdb));
+
foreach (var id in list)
{
- try
- {
- await UpdateMovie(id, isBoxSet, moviesDataPath, cancellationToken).ConfigureAwait(false);
- }
- catch (Exception ex)
+ // Find the preferred language(s) for the movie in the library
+ var languages = allMovies[id]
+ .Select(i => i.GetPreferredMetadataLanguage())
+ .Distinct(StringComparer.OrdinalIgnoreCase)
+ .ToList();
+
+ foreach (var language in languages)
{
- _logger.ErrorException("Error updating tmdb movie id {0}", ex, id);
+ try
+ {
+ await UpdateMovie(id, isBoxSet, language, cancellationToken).ConfigureAwait(false);
+ }
+ catch (Exception ex)
+ {
+ _logger.ErrorException("Error updating tmdb movie id {0}, language {1}", ex, id, language);
+ }
}
numComplete++;
@@ -221,18 +241,14 @@ namespace MediaBrowser.Providers.Movies
/// </summary>
/// <param name="id">The id.</param>
/// <param name="isBoxSet">if set to <c>true</c> [is box set].</param>
- /// <param name="dataPath">The data path.</param>
+ /// <param name="preferredMetadataLanguage">The preferred metadata language.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>Task.</returns>
- private Task UpdateMovie(string id, bool isBoxSet, string dataPath, CancellationToken cancellationToken)
+ private Task UpdateMovie(string id, bool isBoxSet, string preferredMetadataLanguage, CancellationToken cancellationToken)
{
- _logger.Info("Updating movie from tmdb " + id);
-
- var itemDataPath = Path.Combine(dataPath, id);
-
- Directory.CreateDirectory(dataPath);
+ _logger.Info("Updating movie from tmdb " + id + ", language " + preferredMetadataLanguage);
- return MovieDbProvider.Current.DownloadMovieInfo(id, isBoxSet, itemDataPath, cancellationToken);
+ return MovieDbProvider.Current.DownloadMovieInfo(id, isBoxSet, preferredMetadataLanguage, cancellationToken);
}
class Result
diff --git a/MediaBrowser.Providers/Movies/OpenMovieDatabaseProvider.cs b/MediaBrowser.Providers/Movies/OpenMovieDatabaseProvider.cs
index 8370eecbb..8940f1d49 100644
--- a/MediaBrowser.Providers/Movies/OpenMovieDatabaseProvider.cs
+++ b/MediaBrowser.Providers/Movies/OpenMovieDatabaseProvider.cs
@@ -202,13 +202,21 @@ namespace MediaBrowser.Providers.Movies
private bool ShouldFetchGenres(BaseItem item)
{
+ var lang = item.GetPreferredMetadataLanguage();
+
+ // The data isn't localized and so can only be used for english users
+ if (!string.Equals(lang, "en", StringComparison.OrdinalIgnoreCase))
+ {
+ return false;
+ }
+
// Only fetch if other providers didn't get anything
if (item is Trailer)
{
return item.Genres.Count == 0;
}
- return item is Series;
+ return item is Series || item is Movie;
}
protected class RootObject
diff --git a/MediaBrowser.Providers/Movies/PersonUpdatesPreScanTask.cs b/MediaBrowser.Providers/Movies/PersonUpdatesPreScanTask.cs
deleted file mode 100644
index 489b0ad09..000000000
--- a/MediaBrowser.Providers/Movies/PersonUpdatesPreScanTask.cs
+++ /dev/null
@@ -1,236 +0,0 @@
-using MediaBrowser.Common.IO;
-using MediaBrowser.Common.Net;
-using MediaBrowser.Controller.Configuration;
-using MediaBrowser.Controller.Library;
-using MediaBrowser.Model.Logging;
-using MediaBrowser.Model.Serialization;
-using System;
-using System.Collections.Generic;
-using System.Globalization;
-using System.IO;
-using System.Linq;
-using System.Text;
-using System.Threading;
-using System.Threading.Tasks;
-
-namespace MediaBrowser.Providers.Movies
-{
- public class PersonUpdatesPreScanTask : IPeoplePrescanTask
- {
- /// <summary>
- /// The updates URL
- /// </summary>
- private const string UpdatesUrl = "http://api.themoviedb.org/3/person/changes?start_date={0}&api_key={1}&page={2}";
-
- /// <summary>
- /// The _HTTP client
- /// </summary>
- private readonly IHttpClient _httpClient;
- /// <summary>
- /// The _logger
- /// </summary>
- private readonly ILogger _logger;
- /// <summary>
- /// The _config
- /// </summary>
- private readonly IServerConfigurationManager _config;
- private readonly IJsonSerializer _json;
- private readonly IFileSystem _fileSystem;
-
- /// <summary>
- /// Initializes a new instance of the <see cref="PersonUpdatesPreScanTask"/> class.
- /// </summary>
- /// <param name="logger">The logger.</param>
- /// <param name="httpClient">The HTTP client.</param>
- /// <param name="config">The config.</param>
- public PersonUpdatesPreScanTask(ILogger logger, IHttpClient httpClient, IServerConfigurationManager config, IJsonSerializer json, IFileSystem fileSystem)
- {
- _logger = logger;
- _httpClient = httpClient;
- _config = config;
- _json = json;
- _fileSystem = fileSystem;
- }
-
- protected readonly CultureInfo UsCulture = new CultureInfo("en-US");
-
- /// <summary>
- /// Runs the specified progress.
- /// </summary>
- /// <param name="progress">The progress.</param>
- /// <param name="cancellationToken">The cancellation token.</param>
- /// <returns>Task.</returns>
- public async Task Run(IProgress<double> progress, CancellationToken cancellationToken)
- {
- if (!_config.Configuration.EnableInternetProviders || !_config.Configuration.EnableTmdbUpdates)
- {
- progress.Report(100);
- return;
- }
-
- var path = MovieDbPersonProvider.GetPersonsDataPath(_config.CommonApplicationPaths);
-
- Directory.CreateDirectory(path);
-
- var timestampFile = Path.Combine(path, "time.txt");
-
- var timestampFileInfo = new FileInfo(timestampFile);
-
- // Don't check for updates every single time
- if (timestampFileInfo.Exists && (DateTime.UtcNow - _fileSystem.GetLastWriteTimeUtc(timestampFileInfo)).TotalDays < 3)
- {
- return;
- }
-
- // Find out the last time we queried tvdb for updates
- var lastUpdateTime = timestampFileInfo.Exists ? File.ReadAllText(timestampFile, Encoding.UTF8) : string.Empty;
-
- var existingDirectories = GetExistingIds(path).ToList();
-
- if (!string.IsNullOrEmpty(lastUpdateTime))
- {
- long lastUpdateTicks;
-
- if (long.TryParse(lastUpdateTime, NumberStyles.Any, UsCulture, out lastUpdateTicks))
- {
- var lastUpdateDate = new DateTime(lastUpdateTicks, DateTimeKind.Utc);
-
- // They only allow up to 14 days of updates
- if ((DateTime.UtcNow - lastUpdateDate).TotalDays > 13)
- {
- lastUpdateDate = DateTime.UtcNow.AddDays(-13);
- }
-
- var updatedIds = await GetIdsToUpdate(lastUpdateDate, 1, cancellationToken).ConfigureAwait(false);
-
- var existingDictionary = existingDirectories.ToDictionary(i => i, StringComparer.OrdinalIgnoreCase);
-
- var idsToUpdate = updatedIds.Where(i => !string.IsNullOrWhiteSpace(i) && existingDictionary.ContainsKey(i));
-
- await UpdatePeople(idsToUpdate, progress, cancellationToken).ConfigureAwait(false);
- }
- }
-
- File.WriteAllText(timestampFile, DateTime.UtcNow.Ticks.ToString(UsCulture), Encoding.UTF8);
- progress.Report(100);
- }
-
- /// <summary>
- /// Gets the existing ids.
- /// </summary>
- /// <param name="path">The path.</param>
- /// <returns>IEnumerable{System.String}.</returns>
- private IEnumerable<string> GetExistingIds(string path)
- {
- return Directory.EnumerateDirectories(path)
- .SelectMany(Directory.EnumerateDirectories)
- .Select(Path.GetFileNameWithoutExtension);
- }
-
- /// <summary>
- /// Gets the ids to update.
- /// </summary>
- /// <param name="lastUpdateTime">The last update time.</param>
- /// <param name="page">The page.</param>
- /// <param name="cancellationToken">The cancellation token.</param>
- /// <returns>Task{IEnumerable{System.String}}.</returns>
- private async Task<IEnumerable<string>> GetIdsToUpdate(DateTime lastUpdateTime, int page, CancellationToken cancellationToken)
- {
- var hasMorePages = false;
- var list = new List<string>();
-
- // First get last time
- using (var stream = await _httpClient.Get(new HttpRequestOptions
- {
- Url = string.Format(UpdatesUrl, lastUpdateTime.ToString("yyyy-MM-dd"), MovieDbProvider.ApiKey, page),
- CancellationToken = cancellationToken,
- EnableHttpCompression = true,
- ResourcePool = MovieDbProvider.Current.MovieDbResourcePool,
- AcceptHeader = MovieDbProvider.AcceptHeader
-
- }).ConfigureAwait(false))
- {
- var obj = _json.DeserializeFromStream<RootObject>(stream);
-
- var data = obj.results.Select(i => i.id.ToString(UsCulture));
-
- list.AddRange(data);
-
- hasMorePages = page < obj.total_pages;
- }
-
- if (hasMorePages)
- {
- var more = await GetIdsToUpdate(lastUpdateTime, page + 1, cancellationToken).ConfigureAwait(false);
-
- list.AddRange(more);
- }
-
- return list;
- }
-
- /// <summary>
- /// Updates the people.
- /// </summary>
- /// <param name="ids">The ids.</param>
- /// <param name="progress">The progress.</param>
- /// <param name="cancellationToken">The cancellation token.</param>
- /// <returns>Task.</returns>
- private async Task UpdatePeople(IEnumerable<string> ids, IProgress<double> progress, CancellationToken cancellationToken)
- {
- var list = ids.ToList();
- var numComplete = 0;
-
- foreach (var id in list)
- {
- try
- {
- await UpdatePerson(id, cancellationToken).ConfigureAwait(false);
- }
- catch (Exception ex)
- {
- _logger.ErrorException("Error updating tmdb person id {0}", ex, id);
- }
-
- numComplete++;
- double percent = numComplete;
- percent /= list.Count;
- percent *= 100;
-
- progress.Report(percent);
- }
- }
-
- /// <summary>
- /// Updates the person.
- /// </summary>
- /// <param name="id">The id.</param>
- /// <param name="cancellationToken">The cancellation token.</param>
- /// <returns>Task.</returns>
- private Task UpdatePerson(string id, CancellationToken cancellationToken)
- {
- _logger.Info("Updating person from tmdb " + id);
-
- return MovieDbPersonProvider.Current.DownloadPersonInfo(id, cancellationToken);
- }
-
- class Result
- {
- public int id { get; set; }
- public bool? adult { get; set; }
- }
-
- class RootObject
- {
- public List<Result> results { get; set; }
- public int page { get; set; }
- public int total_pages { get; set; }
- public int total_results { get; set; }
-
- public RootObject()
- {
- results = new List<Result>();
- }
- }
- }
-}
diff --git a/MediaBrowser.Providers/Music/AlbumInfoFromSongProvider.cs b/MediaBrowser.Providers/Music/AlbumInfoFromSongProvider.cs
index c4b4af97f..47799b8f3 100644
--- a/MediaBrowser.Providers/Music/AlbumInfoFromSongProvider.cs
+++ b/MediaBrowser.Providers/Music/AlbumInfoFromSongProvider.cs
@@ -140,7 +140,6 @@ namespace MediaBrowser.Providers.Music
}
}
-
providerInfo.FileStamp = GetComparisonData(songs);
SetLastRefreshed(item, DateTime.UtcNow, providerInfo);
diff --git a/MediaBrowser.Providers/Music/FanArtAlbumProvider.cs b/MediaBrowser.Providers/Music/FanArtAlbumProvider.cs
index 161c96a5d..b6e0d61f7 100644
--- a/MediaBrowser.Providers/Music/FanArtAlbumProvider.cs
+++ b/MediaBrowser.Providers/Music/FanArtAlbumProvider.cs
@@ -154,9 +154,11 @@ namespace MediaBrowser.Providers.Music
/// <returns>Task{System.Boolean}.</returns>
public override async Task<bool> FetchAsync(BaseItem item, bool force, BaseProviderInfo providerInfo, CancellationToken cancellationToken)
{
- var images = await _providerManager.GetAvailableRemoteImages(item, cancellationToken, ManualFanartAlbumProvider.ProviderName).ConfigureAwait(false);
-
- await FetchFromXml(item, images.ToList(), cancellationToken).ConfigureAwait(false);
+ if (!item.LockedFields.Contains(MetadataFields.Images))
+ {
+ var images = await _providerManager.GetAvailableRemoteImages(item, cancellationToken, ManualFanartAlbumProvider.ProviderName).ConfigureAwait(false);
+ await FetchFromXml(item, images.ToList(), cancellationToken).ConfigureAwait(false);
+ }
SetLastRefreshed(item, DateTime.UtcNow, providerInfo);
@@ -203,6 +205,7 @@ namespace MediaBrowser.Providers.Music
{
continue;
}
+ break;
}
}
}
diff --git a/MediaBrowser.Providers/Music/FanArtArtistProvider.cs b/MediaBrowser.Providers/Music/FanArtArtistProvider.cs
index 4830d15b1..b248fcb40 100644
--- a/MediaBrowser.Providers/Music/FanArtArtistProvider.cs
+++ b/MediaBrowser.Providers/Music/FanArtArtistProvider.cs
@@ -213,13 +213,12 @@ namespace MediaBrowser.Providers.Music
}
if (ConfigurationManager.Configuration.DownloadMusicArtistImages.Art ||
- ConfigurationManager.Configuration.DownloadMusicArtistImages.Backdrops ||
- ConfigurationManager.Configuration.DownloadMusicArtistImages.Banner ||
- ConfigurationManager.Configuration.DownloadMusicArtistImages.Logo ||
- ConfigurationManager.Configuration.DownloadMusicArtistImages.Primary)
+ ConfigurationManager.Configuration.DownloadMusicArtistImages.Backdrops ||
+ ConfigurationManager.Configuration.DownloadMusicArtistImages.Banner ||
+ ConfigurationManager.Configuration.DownloadMusicArtistImages.Logo ||
+ ConfigurationManager.Configuration.DownloadMusicArtistImages.Primary)
{
var images = await _providerManager.GetAvailableRemoteImages(item, cancellationToken, ManualFanartArtistProvider.ProviderName).ConfigureAwait(false);
-
await FetchFromXml(item, images.ToList(), cancellationToken).ConfigureAwait(false);
}
@@ -268,46 +267,52 @@ namespace MediaBrowser.Providers.Music
/// <returns>Task.</returns>
private async Task FetchFromXml(BaseItem item, List<RemoteImageInfo> images , CancellationToken cancellationToken)
{
- cancellationToken.ThrowIfCancellationRequested();
-
- if (ConfigurationManager.Configuration.DownloadMusicArtistImages.Primary && !item.HasImage(ImageType.Primary))
+ if (!item.LockedFields.Contains(MetadataFields.Images))
{
- await SaveImage(item, images, ImageType.Primary, cancellationToken).ConfigureAwait(false);
- }
+ cancellationToken.ThrowIfCancellationRequested();
- cancellationToken.ThrowIfCancellationRequested();
+ if (ConfigurationManager.Configuration.DownloadMusicArtistImages.Primary && !item.HasImage(ImageType.Primary))
+ {
+ await SaveImage(item, images, ImageType.Primary, cancellationToken).ConfigureAwait(false);
+ }
- if (ConfigurationManager.Configuration.DownloadMusicArtistImages.Logo && !item.HasImage(ImageType.Logo))
- {
- await SaveImage(item, images, ImageType.Logo, cancellationToken).ConfigureAwait(false);
- }
+ cancellationToken.ThrowIfCancellationRequested();
- cancellationToken.ThrowIfCancellationRequested();
+ if (ConfigurationManager.Configuration.DownloadMusicArtistImages.Logo && !item.HasImage(ImageType.Logo))
+ {
+ await SaveImage(item, images, ImageType.Logo, cancellationToken).ConfigureAwait(false);
+ }
- if (ConfigurationManager.Configuration.DownloadMusicArtistImages.Art && !item.HasImage(ImageType.Art))
- {
- await SaveImage(item, images, ImageType.Art, cancellationToken).ConfigureAwait(false);
- }
+ cancellationToken.ThrowIfCancellationRequested();
- cancellationToken.ThrowIfCancellationRequested();
+ if (ConfigurationManager.Configuration.DownloadMusicArtistImages.Art && !item.HasImage(ImageType.Art))
+ {
+ await SaveImage(item, images, ImageType.Art, cancellationToken).ConfigureAwait(false);
+ }
- if (ConfigurationManager.Configuration.DownloadMusicArtistImages.Banner && !item.HasImage(ImageType.Banner))
- {
- await SaveImage(item, images, ImageType.Banner, cancellationToken).ConfigureAwait(false);
- }
+ cancellationToken.ThrowIfCancellationRequested();
- cancellationToken.ThrowIfCancellationRequested();
+ if (ConfigurationManager.Configuration.DownloadMusicArtistImages.Banner && !item.HasImage(ImageType.Banner))
+ {
+ await SaveImage(item, images, ImageType.Banner, cancellationToken).ConfigureAwait(false);
+ }
+ }
- var backdropLimit = ConfigurationManager.Configuration.MaxBackdrops;
- if (ConfigurationManager.Configuration.DownloadMusicArtistImages.Backdrops &&
- item.BackdropImagePaths.Count < backdropLimit)
+ if (!item.LockedFields.Contains(MetadataFields.Backdrops))
{
- foreach (var image in images.Where(i => i.Type == ImageType.Backdrop))
+ cancellationToken.ThrowIfCancellationRequested();
+
+ var backdropLimit = ConfigurationManager.Configuration.MusicOptions.MaxBackdrops;
+ if (ConfigurationManager.Configuration.DownloadMusicArtistImages.Backdrops &&
+ item.BackdropImagePaths.Count < backdropLimit)
{
- await _providerManager.SaveImage(item, image.Url, FanArtResourcePool, ImageType.Backdrop, null, cancellationToken)
- .ConfigureAwait(false);
+ foreach (var image in images.Where(i => i.Type == ImageType.Backdrop))
+ {
+ await _providerManager.SaveImage(item, image.Url, FanArtResourcePool, ImageType.Backdrop, null, cancellationToken)
+ .ConfigureAwait(false);
- if (item.BackdropImagePaths.Count >= backdropLimit) break;
+ if (item.BackdropImagePaths.Count >= backdropLimit) break;
+ }
}
}
}
@@ -328,6 +333,7 @@ namespace MediaBrowser.Providers.Music
{
continue;
}
+ break;
}
}
}
diff --git a/MediaBrowser.Providers/Music/FanArtUpdatesPrescanTask.cs b/MediaBrowser.Providers/Music/FanArtUpdatesPrescanTask.cs
index ddf212179..a3d0deb0e 100644
--- a/MediaBrowser.Providers/Music/FanArtUpdatesPrescanTask.cs
+++ b/MediaBrowser.Providers/Music/FanArtUpdatesPrescanTask.cs
@@ -15,7 +15,7 @@ using System.Threading.Tasks;
namespace MediaBrowser.Providers.Music
{
- class FanArtUpdatesPrescanTask : ILibraryPrescanTask
+ class FanArtUpdatesPrescanTask : ILibraryPostScanTask
{
private const string UpdatesUrl = "http://api.fanart.tv/webservice/newmusic/{0}/{1}/";
diff --git a/MediaBrowser.Providers/Music/LastFmImageProvider.cs b/MediaBrowser.Providers/Music/LastFmImageProvider.cs
index 2a30a3a2e..98ba58fa8 100644
--- a/MediaBrowser.Providers/Music/LastFmImageProvider.cs
+++ b/MediaBrowser.Providers/Music/LastFmImageProvider.cs
@@ -90,7 +90,7 @@ namespace MediaBrowser.Providers.Music
? ConfigurationManager.Configuration.DownloadMusicAlbumImages
: ConfigurationManager.Configuration.DownloadMusicArtistImages;
- if (configSetting.Primary && !item.HasImage(ImageType.Primary))
+ if (configSetting.Primary && !item.HasImage(ImageType.Primary) && !item.LockedFields.Contains(MetadataFields.Images))
{
var image = images.FirstOrDefault(i => i.Type == ImageType.Primary);
diff --git a/MediaBrowser.Providers/Music/LastfmHelper.cs b/MediaBrowser.Providers/Music/LastfmHelper.cs
index 3301d5584..df02cee5b 100644
--- a/MediaBrowser.Providers/Music/LastfmHelper.cs
+++ b/MediaBrowser.Providers/Music/LastfmHelper.cs
@@ -81,16 +81,20 @@ namespace MediaBrowser.Providers.Music
}
// Only grab the date here if the album doesn't already have one, since id3 tags are preferred
- if (!item.PremiereDate.HasValue)
- {
- DateTime release;
+ DateTime release;
- if (DateTime.TryParse(data.releasedate, out release))
+ if (DateTime.TryParse(data.releasedate, out release))
+ {
+ // Lastfm sends back null as sometimes 1901, other times 0
+ if (release.Year > 1901)
{
- // Lastfm sends back null as sometimes 1901, other times 0
- if (release.Year > 1901)
+ if (!item.PremiereDate.HasValue)
{
item.PremiereDate = release;
+ }
+
+ if (!item.ProductionYear.HasValue)
+ {
item.ProductionYear = release.Year;
}
}
diff --git a/MediaBrowser.Providers/Music/ManualFanartAlbumProvider.cs b/MediaBrowser.Providers/Music/ManualFanartAlbumProvider.cs
index d95365b02..5c923869f 100644
--- a/MediaBrowser.Providers/Music/ManualFanartAlbumProvider.cs
+++ b/MediaBrowser.Providers/Music/ManualFanartAlbumProvider.cs
@@ -37,32 +37,34 @@ namespace MediaBrowser.Providers.Music
get { return "FanArt"; }
}
- public bool Supports(BaseItem item)
+ public bool Supports(IHasImages item)
{
return item is MusicAlbum;
}
- public async Task<IEnumerable<RemoteImageInfo>> GetImages(BaseItem item, ImageType imageType, CancellationToken cancellationToken)
+ public async Task<IEnumerable<RemoteImageInfo>> GetImages(IHasImages item, ImageType imageType, CancellationToken cancellationToken)
{
var images = await GetAllImages(item, cancellationToken).ConfigureAwait(false);
return images.Where(i => i.Type == imageType);
}
- public Task<IEnumerable<RemoteImageInfo>> GetAllImages(BaseItem item, CancellationToken cancellationToken)
+ public Task<IEnumerable<RemoteImageInfo>> GetAllImages(IHasImages item, CancellationToken cancellationToken)
{
+ var album = (MusicAlbum)item;
+
var list = new List<RemoteImageInfo>();
- var artistMusicBrainzId = item.Parent.GetProviderId(MetadataProviders.Musicbrainz);
+ var artistMusicBrainzId = album.Parent.GetProviderId(MetadataProviders.Musicbrainz);
if (!string.IsNullOrEmpty(artistMusicBrainzId))
{
var artistXmlPath = FanArtArtistProvider.GetArtistDataPath(_config.CommonApplicationPaths, artistMusicBrainzId);
artistXmlPath = Path.Combine(artistXmlPath, "fanart.xml");
- var musicBrainzReleaseGroupId = item.GetProviderId(MetadataProviders.MusicBrainzReleaseGroup);
+ var musicBrainzReleaseGroupId = album.GetProviderId(MetadataProviders.MusicBrainzReleaseGroup);
- var musicBrainzId = item.GetProviderId(MetadataProviders.Musicbrainz);
+ var musicBrainzId = album.GetProviderId(MetadataProviders.Musicbrainz);
try
{
@@ -74,7 +76,7 @@ namespace MediaBrowser.Providers.Music
}
}
- var language = _config.Configuration.PreferredMetadataLanguage;
+ var language = item.GetPreferredMetadataLanguage();
var isLanguageEn = string.Equals(language, "en", StringComparison.OrdinalIgnoreCase);
diff --git a/MediaBrowser.Providers/Music/ManualFanartArtistProvider.cs b/MediaBrowser.Providers/Music/ManualFanartArtistProvider.cs
index cdb07d3d7..ddf5064aa 100644
--- a/MediaBrowser.Providers/Music/ManualFanartArtistProvider.cs
+++ b/MediaBrowser.Providers/Music/ManualFanartArtistProvider.cs
@@ -37,23 +37,25 @@ namespace MediaBrowser.Providers.Music
get { return "FanArt"; }
}
- public bool Supports(BaseItem item)
+ public bool Supports(IHasImages item)
{
return item is MusicArtist;
}
- public async Task<IEnumerable<RemoteImageInfo>> GetImages(BaseItem item, ImageType imageType, CancellationToken cancellationToken)
+ public async Task<IEnumerable<RemoteImageInfo>> GetImages(IHasImages item, ImageType imageType, CancellationToken cancellationToken)
{
var images = await GetAllImages(item, cancellationToken).ConfigureAwait(false);
return images.Where(i => i.Type == imageType);
}
- public Task<IEnumerable<RemoteImageInfo>> GetAllImages(BaseItem item, CancellationToken cancellationToken)
+ public Task<IEnumerable<RemoteImageInfo>> GetAllImages(IHasImages item, CancellationToken cancellationToken)
{
+ var artist = (MusicArtist)item;
+
var list = new List<RemoteImageInfo>();
- var artistMusicBrainzId = item.GetProviderId(MetadataProviders.Musicbrainz);
+ var artistMusicBrainzId = artist.GetProviderId(MetadataProviders.Musicbrainz);
if (!string.IsNullOrEmpty(artistMusicBrainzId))
{
@@ -70,7 +72,7 @@ namespace MediaBrowser.Providers.Music
}
}
- var language = _config.Configuration.PreferredMetadataLanguage;
+ var language = item.GetPreferredMetadataLanguage();
var isLanguageEn = string.Equals(language, "en", StringComparison.OrdinalIgnoreCase);
diff --git a/MediaBrowser.Providers/Music/ManualLastFmImageProvider.cs b/MediaBrowser.Providers/Music/ManualLastFmImageProvider.cs
index 72e8c6f6b..6d6f1ec7b 100644
--- a/MediaBrowser.Providers/Music/ManualLastFmImageProvider.cs
+++ b/MediaBrowser.Providers/Music/ManualLastFmImageProvider.cs
@@ -23,19 +23,19 @@ namespace MediaBrowser.Providers.Music
get { return "last.fm"; }
}
- public bool Supports(BaseItem item)
+ public bool Supports(IHasImages item)
{
return item is MusicAlbum || item is MusicArtist;
}
- public async Task<IEnumerable<RemoteImageInfo>> GetImages(BaseItem item, ImageType imageType, CancellationToken cancellationToken)
+ public async Task<IEnumerable<RemoteImageInfo>> GetImages(IHasImages item, ImageType imageType, CancellationToken cancellationToken)
{
var images = await GetAllImages(item, cancellationToken).ConfigureAwait(false);
return images.Where(i => i.Type == imageType);
}
- public Task<IEnumerable<RemoteImageInfo>> GetAllImages(BaseItem item, CancellationToken cancellationToken)
+ public Task<IEnumerable<RemoteImageInfo>> GetAllImages(IHasImages item, CancellationToken cancellationToken)
{
var list = new List<RemoteImageInfo>();
diff --git a/MediaBrowser.Providers/Savers/ChannelXmlSaver.cs b/MediaBrowser.Providers/Savers/ChannelXmlSaver.cs
index 6880c9948..ad7f1287f 100644
--- a/MediaBrowser.Providers/Savers/ChannelXmlSaver.cs
+++ b/MediaBrowser.Providers/Savers/ChannelXmlSaver.cs
@@ -29,7 +29,7 @@ namespace MediaBrowser.Providers.Savers
// If new metadata has been downloaded or metadata was manually edited, proceed
if ((wasMetadataEdited || wasMetadataDownloaded))
{
- return item is Channel;
+ return item is LiveTvChannel;
}
return false;
diff --git a/MediaBrowser.Providers/Savers/EpisodeXmlSaver.cs b/MediaBrowser.Providers/Savers/EpisodeXmlSaver.cs
index 35dd551f1..91e769994 100644
--- a/MediaBrowser.Providers/Savers/EpisodeXmlSaver.cs
+++ b/MediaBrowser.Providers/Savers/EpisodeXmlSaver.cs
@@ -92,6 +92,21 @@ namespace MediaBrowser.Providers.Savers
builder.Append("<SeasonNumber>" + SecurityElement.Escape(episode.ParentIndexNumber.Value.ToString(_usCulture)) + "</SeasonNumber>");
}
+ if (episode.AbsoluteEpisodeNumber.HasValue)
+ {
+ builder.Append("<absolute_number>" + SecurityElement.Escape(episode.AbsoluteEpisodeNumber.Value.ToString(_usCulture)) + "</absolute_number>");
+ }
+
+ if (episode.DvdEpisodeNumber.HasValue)
+ {
+ builder.Append("<DVD_episodenumber>" + SecurityElement.Escape(episode.DvdEpisodeNumber.Value.ToString(_usCulture)) + "</DVD_episodenumber>");
+ }
+
+ if (episode.DvdSeasonNumber.HasValue)
+ {
+ builder.Append("<DVD_season>" + SecurityElement.Escape(episode.DvdSeasonNumber.Value.ToString(_usCulture)) + "</DVD_season>");
+ }
+
if (episode.PremiereDate.HasValue)
{
builder.Append("<FirstAired>" + SecurityElement.Escape(episode.PremiereDate.Value.ToString("yyyy-MM-dd")) + "</FirstAired>");
@@ -113,7 +128,10 @@ namespace MediaBrowser.Providers.Savers
"EpisodeNumberEnd",
"airsafter_season",
"airsbefore_episode",
- "airsbefore_season"
+ "airsbefore_season",
+ "DVD_episodenumber",
+ "DVD_season",
+ "absolute_number"
});
}
diff --git a/MediaBrowser.Providers/Savers/MovieXmlSaver.cs b/MediaBrowser.Providers/Savers/MovieXmlSaver.cs
index 17dca6008..f10e24dc1 100644
--- a/MediaBrowser.Providers/Savers/MovieXmlSaver.cs
+++ b/MediaBrowser.Providers/Savers/MovieXmlSaver.cs
@@ -1,10 +1,9 @@
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.Persistence;
-using MediaBrowser.Providers.Movies;
-using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
@@ -50,7 +49,8 @@ namespace MediaBrowser.Providers.Savers
return !trailer.IsLocalTrailer;
}
- return item is Movie || item is MusicVideo || item is AdultVideo;
+ // Check parent for null to avoid running this against things like video backdrops
+ return item is Video && !(item is Episode) && item.Parent != null;
}
return false;
diff --git a/MediaBrowser.Providers/Savers/XmlSaverHelpers.cs b/MediaBrowser.Providers/Savers/XmlSaverHelpers.cs
index 522b2c90b..dc2d5eddd 100644
--- a/MediaBrowser.Providers/Savers/XmlSaverHelpers.cs
+++ b/MediaBrowser.Providers/Savers/XmlSaverHelpers.cs
@@ -42,7 +42,7 @@ namespace MediaBrowser.Providers.Savers
"LocalTitle",
"LockData",
"LockedFields",
- "MediaInfo",
+ "Format3D",
"MPAARating",
"MusicbrainzId",
"MusicBrainzReleaseGroupId",
@@ -326,12 +326,12 @@ namespace MediaBrowser.Providers.Savers
}
}
- var hasLanguage = item as IHasLanguage;
+ var hasLanguage = item as IHasPreferredMetadataLanguage;
if (hasLanguage != null)
{
- if (!string.IsNullOrEmpty(hasLanguage.Language))
+ if (!string.IsNullOrEmpty(hasLanguage.PreferredMetadataLanguage))
{
- builder.Append("<Language>" + SecurityElement.Escape(hasLanguage.Language) + "</Language>");
+ builder.Append("<Language>" + SecurityElement.Escape(hasLanguage.PreferredMetadataLanguage) + "</Language>");
}
}
@@ -426,8 +426,6 @@ namespace MediaBrowser.Providers.Savers
{
if (hasTagline.Taglines.Count > 0)
{
- builder.Append("<TagLine>" + SecurityElement.Escape(hasTagline.Taglines[0]) + "</TagLine>");
-
builder.Append("<Taglines>");
foreach (var tagline in hasTagline.Taglines)
@@ -449,8 +447,6 @@ namespace MediaBrowser.Providers.Savers
}
builder.Append("</Genres>");
-
- builder.Append("<Genre>" + SecurityElement.Escape(string.Join("|", item.Genres.ToArray())) + "</Genre>");
}
if (item.Studios.Count > 0)
@@ -534,18 +530,6 @@ namespace MediaBrowser.Providers.Savers
{
var video = item as Video;
- builder.Append("<MediaInfo>");
-
- builder.Append("<Video>");
-
- if (item.RunTimeTicks.HasValue)
- {
- var timespan = TimeSpan.FromTicks(item.RunTimeTicks.Value);
-
- builder.Append("<Duration>" + Convert.ToInt64(timespan.TotalMinutes).ToString(UsCulture) + "</Duration>");
- builder.Append("<DurationSeconds>" + Convert.ToInt64(timespan.TotalSeconds).ToString(UsCulture) + "</DurationSeconds>");
- }
-
if (video != null && video.Video3DFormat.HasValue)
{
switch (video.Video3DFormat.Value)
@@ -564,10 +548,6 @@ namespace MediaBrowser.Providers.Savers
break;
}
}
-
- builder.Append("</Video>");
-
- builder.Append("</MediaInfo>");
}
}
}
diff --git a/MediaBrowser.Providers/Studios/StudioImageProvider.cs b/MediaBrowser.Providers/Studios/StudioImageProvider.cs
new file mode 100644
index 000000000..6d8d023db
--- /dev/null
+++ b/MediaBrowser.Providers/Studios/StudioImageProvider.cs
@@ -0,0 +1,154 @@
+using MediaBrowser.Controller.Configuration;
+using MediaBrowser.Controller.Entities;
+using MediaBrowser.Controller.Library;
+using MediaBrowser.Controller.Providers;
+using MediaBrowser.Model.Entities;
+using MediaBrowser.Model.Logging;
+using MediaBrowser.Model.Net;
+using MediaBrowser.Model.Providers;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Net;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace MediaBrowser.Providers.Studios
+{
+ public class StudioImageProvider : BaseMetadataProvider
+ {
+ private readonly IProviderManager _providerManager;
+ private readonly SemaphoreSlim _resourcePool = new SemaphoreSlim(5, 5);
+
+ public StudioImageProvider(ILogManager logManager, IServerConfigurationManager configurationManager, IProviderManager providerManager)
+ : base(logManager, configurationManager)
+ {
+ _providerManager = providerManager;
+ }
+
+ public override bool Supports(BaseItem item)
+ {
+ return item is Studio;
+ }
+
+ public override bool RequiresInternet
+ {
+ get
+ {
+ return true;
+ }
+ }
+
+ public override ItemUpdateType ItemUpdateType
+ {
+ get
+ {
+ return ItemUpdateType.ImageUpdate;
+ }
+ }
+
+ protected override bool NeedsRefreshInternal(BaseItem item, BaseProviderInfo providerInfo)
+ {
+ if (item.HasImage(ImageType.Primary) && item.HasImage(ImageType.Thumb))
+ {
+ return false;
+ }
+
+ return base.NeedsRefreshInternal(item, providerInfo);
+ }
+
+ protected override bool RefreshOnVersionChange
+ {
+ get
+ {
+ return true;
+ }
+ }
+
+ protected override string ProviderVersion
+ {
+ get
+ {
+ return "5";
+ }
+ }
+
+ public override async Task<bool> FetchAsync(BaseItem item, bool force, BaseProviderInfo providerInfo, CancellationToken cancellationToken)
+ {
+ if (item.HasImage(ImageType.Primary) && item.HasImage(ImageType.Thumb))
+ {
+ SetLastRefreshed(item, DateTime.UtcNow, providerInfo);
+ return true;
+ }
+
+ var images = await _providerManager.GetAvailableRemoteImages(item, cancellationToken, StudiosManualImageProvider.ProviderName).ConfigureAwait(false);
+
+ await DownloadImages(item, images.ToList(), cancellationToken).ConfigureAwait(false);
+
+ SetLastRefreshed(item, DateTime.UtcNow, providerInfo);
+ return true;
+ }
+
+ private async Task DownloadImages(BaseItem item, List<RemoteImageInfo> images, CancellationToken cancellationToken)
+ {
+ if (!item.LockedFields.Contains(MetadataFields.Images))
+ {
+ cancellationToken.ThrowIfCancellationRequested();
+
+ if (!item.HasImage(ImageType.Primary))
+ {
+ await SaveImage(item, images, ImageType.Primary, cancellationToken).ConfigureAwait(false);
+ }
+ cancellationToken.ThrowIfCancellationRequested();
+
+ if (!item.HasImage(ImageType.Thumb))
+ {
+ await SaveImage(item, images, ImageType.Thumb, cancellationToken).ConfigureAwait(false);
+ }
+ }
+
+ if (!item.LockedFields.Contains(MetadataFields.Backdrops))
+ {
+ cancellationToken.ThrowIfCancellationRequested();
+
+ if (item.BackdropImagePaths.Count == 0)
+ {
+ foreach (var image in images.Where(i => i.Type == ImageType.Backdrop))
+ {
+ await _providerManager.SaveImage(item, image.Url, _resourcePool, ImageType.Backdrop, null, cancellationToken)
+ .ConfigureAwait(false);
+
+ break;
+ }
+ }
+ }
+ }
+
+
+ private async Task SaveImage(BaseItem item, IEnumerable<RemoteImageInfo> images, ImageType type, CancellationToken cancellationToken)
+ {
+ foreach (var image in images.Where(i => i.Type == type))
+ {
+ try
+ {
+ await _providerManager.SaveImage(item, image.Url, _resourcePool, type, null, cancellationToken).ConfigureAwait(false);
+ break;
+ }
+ catch (HttpException ex)
+ {
+ // Sometimes fanart has bad url's in their xml
+ if (ex.StatusCode.HasValue && ex.StatusCode.Value == HttpStatusCode.NotFound)
+ {
+ continue;
+ }
+ break;
+ }
+ }
+ }
+
+ public override MetadataProviderPriority Priority
+ {
+ get { return MetadataProviderPriority.Third; }
+ }
+ }
+}
diff --git a/MediaBrowser.Providers/Studios/StudiosManualImageProvider.cs b/MediaBrowser.Providers/Studios/StudiosManualImageProvider.cs
new file mode 100644
index 000000000..49f552093
--- /dev/null
+++ b/MediaBrowser.Providers/Studios/StudiosManualImageProvider.cs
@@ -0,0 +1,135 @@
+using MediaBrowser.Controller.Entities;
+using MediaBrowser.Controller.Providers;
+using MediaBrowser.Model.Entities;
+using MediaBrowser.Model.Providers;
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace MediaBrowser.Providers.Studios
+{
+ public class StudiosManualImageProvider : IImageProvider
+ {
+ public string Name
+ {
+ get { return ProviderName; }
+ }
+
+ public static string ProviderName
+ {
+ get { return "Media Browser"; }
+ }
+
+ public bool Supports(IHasImages item)
+ {
+ return item is Studio;
+ }
+
+ public Task<IEnumerable<RemoteImageInfo>> GetImages(IHasImages item, ImageType imageType, CancellationToken cancellationToken)
+ {
+ return GetImages(item, imageType == ImageType.Primary, imageType == ImageType.Backdrop, cancellationToken);
+ }
+
+ public Task<IEnumerable<RemoteImageInfo>> GetAllImages(IHasImages item, CancellationToken cancellationToken)
+ {
+ return GetImages(item, true, true, cancellationToken);
+ }
+
+ private Task<IEnumerable<RemoteImageInfo>> GetImages(IHasImages item, bool posters, bool backdrops, CancellationToken cancellationToken)
+ {
+ var list = new List<RemoteImageInfo>();
+
+ if (posters)
+ {
+ list.Add(GetImage(item, "posters.txt", ImageType.Primary, "folder"));
+ }
+
+ cancellationToken.ThrowIfCancellationRequested();
+
+ if (backdrops)
+ {
+ list.Add(GetImage(item, "thumbs.txt", ImageType.Thumb, "thumb"));
+ }
+
+ return Task.FromResult(list.Where(i => i != null));
+ }
+
+ private RemoteImageInfo GetImage(IHasImages item, string filename, ImageType type, string remoteFilename)
+ {
+ var url = GetUrl(item, filename, remoteFilename);
+
+ if (url != null)
+ {
+ return new RemoteImageInfo
+ {
+ ProviderName = Name,
+ Type = type,
+ Url = url
+ };
+ }
+
+ return null;
+ }
+
+ private string GetUrl(IHasImages item, string listingFilename, string remoteFilename)
+ {
+ var list = GetAvailableImages(listingFilename);
+
+ var match = FindMatch(item, list);
+
+ if (!string.IsNullOrEmpty(match))
+ {
+ return GetUrl(match, remoteFilename);
+ }
+
+ return null;
+ }
+
+ private string FindMatch(IHasImages item, IEnumerable<string> images)
+ {
+ var name = GetComparableName(item.Name);
+
+ return images.FirstOrDefault(i => string.Equals(name, GetComparableName(i), StringComparison.OrdinalIgnoreCase));
+ }
+
+ private string GetComparableName(string name)
+ {
+ return name.Replace(" ", string.Empty).Replace(".", string.Empty).Replace("&", string.Empty).Replace("!", string.Empty);
+ }
+
+ private string GetUrl(string image, string filename)
+ {
+ return string.Format("https://raw.github.com/MediaBrowser/MediaBrowser.Resources/master/images/studios/{0}/{1}.jpg", image, filename);
+ }
+
+ private IEnumerable<string> GetAvailableImages(string filename)
+ {
+ var path = GetType().Namespace + "." + filename;
+
+ using (var stream = GetType().Assembly.GetManifestResourceStream(path))
+ {
+ using (var reader = new StreamReader(stream))
+ {
+ var lines = new List<string>();
+
+ while (!reader.EndOfStream)
+ {
+ var text = reader.ReadLine();
+
+ lines.Add(text);
+ }
+
+ return lines;
+ }
+ }
+ }
+
+ public int Priority
+ {
+ get { return 0; }
+ }
+ }
+}
diff --git a/MediaBrowser.Providers/Studios/posters.txt b/MediaBrowser.Providers/Studios/posters.txt
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/MediaBrowser.Providers/Studios/posters.txt
diff --git a/MediaBrowser.Providers/Studios/thumbs.txt b/MediaBrowser.Providers/Studios/thumbs.txt
new file mode 100644
index 000000000..95fed18f1
--- /dev/null
+++ b/MediaBrowser.Providers/Studios/thumbs.txt
@@ -0,0 +1,493 @@
+15 Gigs
+19 Entertainment
+20th Television
+321 Productions
+4K Media
+4Kids Entertainment
+A La Carte Communications
+A&E
+Aardman
+ABC
+ABC Family
+ABC News
+ABC Studios
+Above Average
+Acacia Fitness
+Action Television
+Advertising Age
+All Channel Films
+All3Media
+Alli
+Alliance Entertainment
+Alloy
+AllWarriorNetwork
+American Pop Classics
+Ananey
+Anchor Bay Entertainment
+Anderson Digital
+Animal Planet
+Animation Domination High-Def
+Anime Network
+Aniplex
+Artists Den Entertainment
+Asian Crush
+Atlantic Records
+Attention Span
+Austin City Limits Music Festival
+Australian Broadcasting Corporation
+Australian Food TV
+Avalon UK
+Azteca America
+Bandai
+Base 79
+BBC Worldwide
+Beliefnet
+Believe
+BET
+Beta Film
+Big Air Studios
+BIGFlix
+bio
+Blame Society
+Blastro Networks
+Bloody Disgusting Selects
+Bloomberg
+Bonnier TV Group
+Border Entertainment
+Brain Farm
+Brainstorm Media
+Brave New Films
+Bravo
+Broadway Video
+Brushfire Records
+Butaca
+BVTV
+C3 Entertainment
+Canal 13 de Chile
+Candlelight Media
+Candor TV
+Caracol Television
+Carsey Werner
+CBS
+CelebTV
+Charlie Rose
+Cheflive
+CHIC.TV
+Chiller
+China Lion
+Cine Real
+Cinedigm
+CINELAN
+Cinema Guild
+Cinema Libre Studio
+Cinema Purgatorio
+CineSport
+Cirque du Soleil
+Citizens United Productions No. 3
+CJ Entertainment
+Classic Media
+Clinton Global Initiative
+Cloo
+ClubWPT
+CNBC
+CODA BOOKS
+CollegeHumor
+Comedy Central
+Comedy Time
+Conde Nast Digital
+Constantin Film
+Content and Co
+Content Family
+Content Media Corporation
+Contentino
+Cooking Channel
+Crackle
+Crime & Investigation Network
+Criterion Collection
+CRM
+Cuppa Coffee
+Dark Sky Films
+Dave Matthews Band
+Davis Panzer
+Debutante Inc
+Digital Artists
+Digital Rights Group
+Digital Studios
+Discovery Channel
+Discovery
+Distribber
+Diva
+DIY Network
+DocComTV
+DramaFever
+Duopoly
+E! Entertainment
+EA Sports
+Eagle Media
+Eagle Rock
+Echo Bridge Entertainment
+Echo Pictures
+EchoBoom Sports
+Edmunds
+ElecPlay
+Electric Entertainment
+Electric Sky
+ELLE
+EMI
+Enchanted Tales
+Endemol
+Entertainment Rights
+eOne Entertainment Distribution
+Epicurious.com
+Eqal
+Esquire Network
+Estrella TV
+Everyday Edisons
+Evil Global
+Exclusive Media
+ExerciseTV
+Fanclub
+Fangoria
+FEARnet
+Fever Dreams
+Fight TV
+Film Ideas on Demand
+Film Movement
+Film Sales Company
+FilmBuff
+Finley-Holiday Films
+First Look Studios
+First Run Features
+Focus Features
+Food Network
+FORA.tv
+Ford
+FOX
+Fox College Sports
+Fox Movie Channel
+Fox News
+Fox Reality
+Fox Sports
+Fox Sports Net
+Fox Television Classics
+Frantic Films
+FremantleMedia
+FUEL TV
+FUNimation
+FX
+FXM
+FXX
+G4
+Gaiam
+Galavision
+GameTrailers
+Generate
+George Dickel
+Giant Ape Media
+Glamour Films
+GoDigital
+Golf TV
+Gong
+Gorilla Pictures
+Gravitas
+Gravitas Horror
+GreenLight Media
+GT Media
+H2
+Handmade TV
+Hat Trick
+HD Films, Inc
+Health Science Channel
+HealthiNation
+HereTV
+HGTV
+Historic Films
+History
+History en Español
+HitFix
+Hollywood Pictures
+How it Works
+Howcast
+Howdini
+Hudsun Media
+Hulu Original Series
+Hype
+Iconix
+iCue.com
+IFC
+IFC Films
+IGN
+Image Entertainment
+Imagina US
+Independent Comedy Network
+Independent International Pictures Corp
+Indie Crush
+IndieFlix
+itsallinyourhands.tv
+ITV
+ITV1
+Janson Media
+Jim Henson Family TV
+K2
+KCET
+Kidz Bop
+Kino Lorber
+KinoNation
+Klown
+Koan
+L Studio
+Lagardere
+Laguna Productions
+Latin Crush
+Legend Fighting Championship
+Legend Films
+Lifetime
+Link TV
+Lionsgate
+Liquid Comics
+Litton Entertainment
+LMN
+Local Food Sustainable Network
+Logo
+lolflix
+Long Way Round
+Look
+Lou Reda Productions
+Lucha Libre USA
+LXTV
+MAN
+Manga Entertainment
+Manolin Studios
+Mar Vista
+Martha Stewart Living
+Marvel
+Maverick Entertainment
+Maya
+MBC America
+Media Blasters
+Mentorn
+MGM
+MHz Networks
+Midnight Pulp
+Military History
+Millennium Media Services
+Modelinia
+Mojo
+MoMedia
+Monterey Media
+Moonscoop
+Moshcam
+Movieola
+Movies by OHM
+Moving Art
+MPI
+MSNBC
+MTV
+MulticomTV
+MVD Entertainment Group
+My Vortexx
+My Yoga
+MyNetworkTV
+NASA
+Nat Geo Wild
+National Geographic Channel
+NBC
+NBC News
+NBC Sports
+NBC Universal
+NBCU TV
+NCircle
+Netflix
+New Renaissance
+NHL
+Nickelodeon
+NickMom
+Nikki Sixx
+Nirvana Films
+NIS America
+Novel Ruby Productions
+NowThisNews
+nuvoTV
+O2 Media
+OhmTV
+Oops Doughnuts
+Ora TV
+Orange Lounge
+ORF Universum
+Oscilloscope Laboratories
+Oxygen
+Paley Media
+Panna
+Paranormal TV
+Passion River
+PBS Kids
+Phase 4 Films
+Players Network
+Plum TV
+PopSugar TV
+Power Rangers
+PPI Releasing
+PRO
+Pure Adrenaline
+Pure History
+Pure Nature
+Pure Science
+Questar
+Quintus Media
+Quiver
+Rajshri Media
+Raphael Saadiq
+Razor & Tie
+RCTV
+Real Magic TV
+Red Bull
+Red Hour Digital
+ReelAfrican
+ReelzChannel
+Revolver
+Rick Steves' Network
+RiffTrax
+Right Network
+Riverhorse
+Roadside Attractions
+Ron Hazelton Productions
+RooftopComedy
+Rovio
+RSA
+RT
+RTE
+S and S Entertainment
+Saavn
+Sachs Judah
+Salient Media
+Satelight
+Saturday Morning TV
+SBS
+SBS Australia
+Scholastic
+Science Channel
+Scott Entertainment
+Screen Media
+Sesame Street
+Shaftesbury
+Shemaroo
+Shochiku
+Shout! Factory
+Showtime
+Shree International
+Sky Studios
+SnagFilms
+SOFA
+SOMA
+Sonar Entertainment
+Sony Pictures Television
+SoPeachi
+Source Interlink Media
+SpaceRip
+SPEED
+Speed Racer Enterprises
+Spike
+Spike TV
+Stand Up To Cancer
+Starz
+Strand Releasing
+Strike.TV
+Sundance Channel
+SunWorld Pictures
+Sweet Irony
+Syfy
+Syndicado
+Synergetic
+Talking Baseball with Ed Randall
+Tantao Entertainment
+TasteTV
+Telepictures
+TenduTV
+The Cannell Studios
+The CW
+The Democratic National Convention
+The Denis Leary Podcasts
+The Global Film Initiative
+The Jim Henson Company
+The Kitchen Diva
+The LXD
+The Military Network
+The Morning After
+The National Film Board of Canada
+The New York Times
+The Onion
+The OnLine Network
+The Orchard
+The Rebound
+The Situation Workout
+The Sundance Institute
+The Three Stooges
+The Weinstein Company
+The White House
+The Wine Library
+The Zalman King Company
+This Week In Studios
+Thunderbird
+Tiny Island Productions
+TLA Releasing
+TLC
+TMS Entertainment
+Toei Animation
+Tokyopop
+Total College Sports
+Total Content Digital
+Touchstone Pictures
+Tr3s
+Transworld
+Travel Channel
+Troma
+TV Globo
+TV Land
+TVF International
+TVG Interactive Horseracing
+TVGN
+Twentieth Century Fox
+Uncork'd Entertainment
+UniMas
+Universal Pictures
+Universal Sports
+Universal Television
+Univision
+unwrapped.tv
+USA
+USA Network
+uStudio
+Vanguard Cinema
+Venevision
+Venus
+VH1
+Vibrant Media
+Videofashion
+viewster
+ViKi
+Virgil Films
+Vision Films
+Vivendi Entertainment
+VIZ Media
+Vogue.TV
+Wall Street Journal
+Warner Bros. Records
+WatchMojo.com
+Water.org
+WCG
+WE tv
+Web Therapy
+Well Go
+WEP
+Westchester Films
+Wolfe Video
+WWE
+Yan Can Cook
+Young Hollywood
+YourTango
+ZDF Enterprises
+ZED
+Zeitgeist Films
+Zodiak Kids
+Zodiak Rights
+ZoomTV \ No newline at end of file
diff --git a/MediaBrowser.Providers/TV/EpisodeIndexNumberProvider.cs b/MediaBrowser.Providers/TV/EpisodeIndexNumberProvider.cs
index 4427e60e4..3e7597e0d 100644
--- a/MediaBrowser.Providers/TV/EpisodeIndexNumberProvider.cs
+++ b/MediaBrowser.Providers/TV/EpisodeIndexNumberProvider.cs
@@ -50,7 +50,12 @@ namespace MediaBrowser.Providers.TV
/// <returns><c>true</c> if XXXX, <c>false</c> otherwise</returns>
public override bool Supports(BaseItem item)
{
- return item is Episode && item.LocationType != LocationType.Virtual && item.LocationType != LocationType.Remote;
+ if (item is Episode)
+ {
+ var locationType = item.LocationType;
+ return locationType != LocationType.Virtual && locationType != LocationType.Remote;
+ }
+ return false;
}
/// <summary>
diff --git a/MediaBrowser.Providers/TV/EpisodeXmlParser.cs b/MediaBrowser.Providers/TV/EpisodeXmlParser.cs
index 5d970107e..446c34f74 100644
--- a/MediaBrowser.Providers/TV/EpisodeXmlParser.cs
+++ b/MediaBrowser.Providers/TV/EpisodeXmlParser.cs
@@ -1,4 +1,5 @@
-using MediaBrowser.Controller.Entities.TV;
+using System;
+using MediaBrowser.Controller.Entities.TV;
using MediaBrowser.Controller.Persistence;
using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Logging;
@@ -40,7 +41,7 @@ namespace MediaBrowser.Providers.TV
}
private static readonly CultureInfo UsCulture = new CultureInfo("en-US");
-
+
/// <summary>
/// Fetches the data from XML node.
/// </summary>
@@ -142,6 +143,55 @@ namespace MediaBrowser.Providers.TV
break;
}
+ case "absolute_number":
+ {
+ var val = reader.ReadElementContentAsString();
+
+ if (!string.IsNullOrWhiteSpace(val))
+ {
+ int rval;
+
+ // int.TryParse is local aware, so it can be probamatic, force us culture
+ if (int.TryParse(val, NumberStyles.Integer, UsCulture, out rval))
+ {
+ item.AbsoluteEpisodeNumber = rval;
+ }
+ }
+
+ break;
+ }
+ case "DVD_episodenumber":
+ {
+ var number = reader.ReadElementContentAsString();
+
+ if (!string.IsNullOrWhiteSpace(number))
+ {
+ float num;
+
+ if (float.TryParse(number, NumberStyles.Any, UsCulture, out num))
+ {
+ item.DvdEpisodeNumber = num;
+ }
+ }
+ break;
+ }
+
+ case "DVD_season":
+ {
+ var number = reader.ReadElementContentAsString();
+
+ if (!string.IsNullOrWhiteSpace(number))
+ {
+ float num;
+
+ if (float.TryParse(number, NumberStyles.Any, UsCulture, out num))
+ {
+ item.DvdSeasonNumber = Convert.ToInt32(num);
+ }
+ }
+ break;
+ }
+
case "airsbefore_episode":
{
var val = reader.ReadElementContentAsString();
diff --git a/MediaBrowser.Providers/TV/FanArtSeasonProvider.cs b/MediaBrowser.Providers/TV/FanArtSeasonProvider.cs
index ed5145bc2..50ce72a89 100644
--- a/MediaBrowser.Providers/TV/FanArtSeasonProvider.cs
+++ b/MediaBrowser.Providers/TV/FanArtSeasonProvider.cs
@@ -101,11 +101,10 @@ namespace MediaBrowser.Providers.TV
{
cancellationToken.ThrowIfCancellationRequested();
- var season = (Season)item;
+ var season = (Season) item;
// Process images
var images = await _providerManager.GetAvailableRemoteImages(item, cancellationToken, ManualFanartSeasonImageProvider.ProviderName).ConfigureAwait(false);
-
await FetchImages(season, images.ToList(), cancellationToken).ConfigureAwait(false);
SetLastRefreshed(item, DateTime.UtcNow, providerInfo);
@@ -121,7 +120,7 @@ namespace MediaBrowser.Providers.TV
/// <returns>Task.</returns>
private async Task FetchImages(Season season, List<RemoteImageInfo> images, CancellationToken cancellationToken)
{
- if (ConfigurationManager.Configuration.DownloadSeasonImages.Thumb && !season.HasImage(ImageType.Thumb))
+ if (ConfigurationManager.Configuration.DownloadSeasonImages.Thumb && !season.HasImage(ImageType.Thumb) && !season.LockedFields.Contains(MetadataFields.Images))
{
await SaveImage(season, images, ImageType.Thumb, cancellationToken).ConfigureAwait(false);
}
@@ -143,6 +142,7 @@ namespace MediaBrowser.Providers.TV
{
continue;
}
+ break;
}
}
}
diff --git a/MediaBrowser.Providers/TV/FanArtTVProvider.cs b/MediaBrowser.Providers/TV/FanArtTVProvider.cs
index 251420261..286702b8c 100644
--- a/MediaBrowser.Providers/TV/FanArtTVProvider.cs
+++ b/MediaBrowser.Providers/TV/FanArtTVProvider.cs
@@ -196,56 +196,61 @@ namespace MediaBrowser.Providers.TV
/// <returns>Task.</returns>
private async Task FetchFromXml(BaseItem item, List<RemoteImageInfo> images, CancellationToken cancellationToken)
{
- cancellationToken.ThrowIfCancellationRequested();
-
- if (ConfigurationManager.Configuration.DownloadSeriesImages.Primary && !item.HasImage(ImageType.Primary))
+ if (!item.LockedFields.Contains(MetadataFields.Images))
{
- await SaveImage(item, images, ImageType.Primary, cancellationToken).ConfigureAwait(false);
- }
+ cancellationToken.ThrowIfCancellationRequested();
- cancellationToken.ThrowIfCancellationRequested();
+ if (ConfigurationManager.Configuration.DownloadSeriesImages.Primary && !item.HasImage(ImageType.Primary))
+ {
+ await SaveImage(item, images, ImageType.Primary, cancellationToken).ConfigureAwait(false);
+ }
- if (ConfigurationManager.Configuration.DownloadSeriesImages.Logo && !item.HasImage(ImageType.Logo))
- {
- await SaveImage(item, images, ImageType.Logo, cancellationToken).ConfigureAwait(false);
- }
+ cancellationToken.ThrowIfCancellationRequested();
- cancellationToken.ThrowIfCancellationRequested();
+ if (ConfigurationManager.Configuration.DownloadSeriesImages.Logo && !item.HasImage(ImageType.Logo))
+ {
+ await SaveImage(item, images, ImageType.Logo, cancellationToken).ConfigureAwait(false);
+ }
- if (ConfigurationManager.Configuration.DownloadSeriesImages.Art && !item.HasImage(ImageType.Art))
- {
- await SaveImage(item, images, ImageType.Art, cancellationToken).ConfigureAwait(false);
- }
+ cancellationToken.ThrowIfCancellationRequested();
- cancellationToken.ThrowIfCancellationRequested();
+ if (ConfigurationManager.Configuration.DownloadSeriesImages.Art && !item.HasImage(ImageType.Art))
+ {
+ await SaveImage(item, images, ImageType.Art, cancellationToken).ConfigureAwait(false);
+ }
- if (ConfigurationManager.Configuration.DownloadSeriesImages.Thumb && !item.HasImage(ImageType.Thumb))
- {
- await SaveImage(item, images, ImageType.Thumb, cancellationToken).ConfigureAwait(false);
- }
+ cancellationToken.ThrowIfCancellationRequested();
- cancellationToken.ThrowIfCancellationRequested();
+ if (ConfigurationManager.Configuration.DownloadSeriesImages.Thumb && !item.HasImage(ImageType.Thumb))
+ {
+ await SaveImage(item, images, ImageType.Thumb, cancellationToken).ConfigureAwait(false);
+ }
- if (ConfigurationManager.Configuration.DownloadSeriesImages.Banner && !item.HasImage(ImageType.Banner))
- {
- await SaveImage(item, images, ImageType.Banner, cancellationToken).ConfigureAwait(false);
- }
+ cancellationToken.ThrowIfCancellationRequested();
- cancellationToken.ThrowIfCancellationRequested();
+ if (ConfigurationManager.Configuration.DownloadSeriesImages.Banner && !item.HasImage(ImageType.Banner))
+ {
+ await SaveImage(item, images, ImageType.Banner, cancellationToken).ConfigureAwait(false);
+ }
+ }
- var backdropLimit = ConfigurationManager.Configuration.MaxBackdrops;
- if (ConfigurationManager.Configuration.DownloadSeriesImages.Backdrops &&
- item.BackdropImagePaths.Count < backdropLimit)
+ if (!item.LockedFields.Contains(MetadataFields.Backdrops))
{
- foreach (var image in images.Where(i => i.Type == ImageType.Backdrop))
+ cancellationToken.ThrowIfCancellationRequested();
+
+ var backdropLimit = ConfigurationManager.Configuration.TvOptions.MaxBackdrops;
+ if (ConfigurationManager.Configuration.DownloadSeriesImages.Backdrops &&
+ item.BackdropImagePaths.Count < backdropLimit)
{
- await _providerManager.SaveImage(item, image.Url, FanArtResourcePool, ImageType.Backdrop, null, cancellationToken)
- .ConfigureAwait(false);
+ foreach (var image in images.Where(i => i.Type == ImageType.Backdrop))
+ {
+ await _providerManager.SaveImage(item, image.Url, FanArtResourcePool, ImageType.Backdrop, null, cancellationToken)
+ .ConfigureAwait(false);
- if (item.BackdropImagePaths.Count >= backdropLimit) break;
+ if (item.BackdropImagePaths.Count >= backdropLimit) break;
+ }
}
}
-
}
private async Task SaveImage(BaseItem item, List<RemoteImageInfo> images, ImageType type, CancellationToken cancellationToken)
@@ -264,6 +269,7 @@ namespace MediaBrowser.Providers.TV
{
continue;
}
+ break;
}
}
}
diff --git a/MediaBrowser.Providers/TV/FanArtTvUpdatesPrescanTask.cs b/MediaBrowser.Providers/TV/FanArtTvUpdatesPrescanTask.cs
index 8ef04ed11..46137a211 100644
--- a/MediaBrowser.Providers/TV/FanArtTvUpdatesPrescanTask.cs
+++ b/MediaBrowser.Providers/TV/FanArtTvUpdatesPrescanTask.cs
@@ -16,7 +16,7 @@ using System.Threading.Tasks;
namespace MediaBrowser.Providers.TV
{
- class FanArtTvUpdatesPrescanTask : ILibraryPrescanTask
+ class FanArtTvUpdatesPrescanTask : ILibraryPostScanTask
{
private const string UpdatesUrl = "http://api.fanart.tv/webservice/newtv/{0}/{1}/";
diff --git a/MediaBrowser.Providers/TV/ManualFanartSeasonProvider.cs b/MediaBrowser.Providers/TV/ManualFanartSeasonProvider.cs
index f9b779011..503c56d0d 100644
--- a/MediaBrowser.Providers/TV/ManualFanartSeasonProvider.cs
+++ b/MediaBrowser.Providers/TV/ManualFanartSeasonProvider.cs
@@ -37,35 +37,36 @@ namespace MediaBrowser.Providers.TV
get { return "FanArt"; }
}
- public bool Supports(BaseItem item)
+ public bool Supports(IHasImages item)
{
return item is Season;
}
- public async Task<IEnumerable<RemoteImageInfo>> GetImages(BaseItem item, ImageType imageType, CancellationToken cancellationToken)
+ public async Task<IEnumerable<RemoteImageInfo>> GetImages(IHasImages item, ImageType imageType, CancellationToken cancellationToken)
{
var images = await GetAllImages(item, cancellationToken).ConfigureAwait(false);
return images.Where(i => i.Type == imageType);
}
- public Task<IEnumerable<RemoteImageInfo>> GetAllImages(BaseItem item, CancellationToken cancellationToken)
+ public Task<IEnumerable<RemoteImageInfo>> GetAllImages(IHasImages item, CancellationToken cancellationToken)
{
var list = new List<RemoteImageInfo>();
- var series = ((Season)item).Series;
+ var season = (Season)item;
+ var series = season.Series;
if (series != null)
{
var id = series.GetProviderId(MetadataProviders.Tvdb);
- if (!string.IsNullOrEmpty(id) && item.IndexNumber.HasValue)
+ if (!string.IsNullOrEmpty(id) && season.IndexNumber.HasValue)
{
var xmlPath = FanArtTvProvider.Current.GetFanartXmlPath(id);
try
{
- AddImages(list, item.IndexNumber.Value, xmlPath, cancellationToken);
+ AddImages(list, season.IndexNumber.Value, xmlPath, cancellationToken);
}
catch (FileNotFoundException)
{
@@ -74,7 +75,7 @@ namespace MediaBrowser.Providers.TV
}
}
- var language = _config.Configuration.PreferredMetadataLanguage;
+ var language = item.GetPreferredMetadataLanguage();
var isLanguageEn = string.Equals(language, "en", StringComparison.OrdinalIgnoreCase);
diff --git a/MediaBrowser.Providers/TV/ManualFanartSeriesProvider.cs b/MediaBrowser.Providers/TV/ManualFanartSeriesProvider.cs
index cb7a4efd1..6a80f720a 100644
--- a/MediaBrowser.Providers/TV/ManualFanartSeriesProvider.cs
+++ b/MediaBrowser.Providers/TV/ManualFanartSeriesProvider.cs
@@ -37,19 +37,19 @@ namespace MediaBrowser.Providers.TV
get { return "FanArt"; }
}
- public bool Supports(BaseItem item)
+ public bool Supports(IHasImages item)
{
return item is Series;
}
- public async Task<IEnumerable<RemoteImageInfo>> GetImages(BaseItem item, ImageType imageType, CancellationToken cancellationToken)
+ public async Task<IEnumerable<RemoteImageInfo>> GetImages(IHasImages item, ImageType imageType, CancellationToken cancellationToken)
{
var images = await GetAllImages(item, cancellationToken).ConfigureAwait(false);
return images.Where(i => i.Type == imageType);
}
- public Task<IEnumerable<RemoteImageInfo>> GetAllImages(BaseItem item, CancellationToken cancellationToken)
+ public Task<IEnumerable<RemoteImageInfo>> GetAllImages(IHasImages item, CancellationToken cancellationToken)
{
var list = new List<RemoteImageInfo>();
@@ -71,7 +71,7 @@ namespace MediaBrowser.Providers.TV
}
}
- var language = _config.Configuration.PreferredMetadataLanguage;
+ var language = item.GetPreferredMetadataLanguage();
var isLanguageEn = string.Equals(language, "en", StringComparison.OrdinalIgnoreCase);
diff --git a/MediaBrowser.Providers/TV/ManualTvdbEpisodeImageProvider.cs b/MediaBrowser.Providers/TV/ManualTvdbEpisodeImageProvider.cs
index d63fb5091..6d38dee2e 100644
--- a/MediaBrowser.Providers/TV/ManualTvdbEpisodeImageProvider.cs
+++ b/MediaBrowser.Providers/TV/ManualTvdbEpisodeImageProvider.cs
@@ -31,19 +31,19 @@ namespace MediaBrowser.Providers.TV
get { return "TheTVDB"; }
}
- public bool Supports(BaseItem item)
+ public bool Supports(IHasImages item)
{
return item is Episode;
}
- public async Task<IEnumerable<RemoteImageInfo>> GetImages(BaseItem item, ImageType imageType, CancellationToken cancellationToken)
+ public async Task<IEnumerable<RemoteImageInfo>> GetImages(IHasImages item, ImageType imageType, CancellationToken cancellationToken)
{
var images = await GetAllImages(item, cancellationToken).ConfigureAwait(false);
return images.Where(i => i.Type == imageType);
}
- public Task<IEnumerable<RemoteImageInfo>> GetAllImages(BaseItem item, CancellationToken cancellationToken)
+ public Task<IEnumerable<RemoteImageInfo>> GetAllImages(IHasImages item, CancellationToken cancellationToken)
{
var episode = (Episode)item;
diff --git a/MediaBrowser.Providers/TV/ManualTvdbPersonImageProvider.cs b/MediaBrowser.Providers/TV/ManualTvdbPersonImageProvider.cs
index adeca12f2..456db1048 100644
--- a/MediaBrowser.Providers/TV/ManualTvdbPersonImageProvider.cs
+++ b/MediaBrowser.Providers/TV/ManualTvdbPersonImageProvider.cs
@@ -37,19 +37,19 @@ namespace MediaBrowser.Providers.TV
get { return "TheTVDB"; }
}
- public bool Supports(BaseItem item)
+ public bool Supports(IHasImages item)
{
return item is Person;
}
- public async Task<IEnumerable<RemoteImageInfo>> GetImages(BaseItem item, ImageType imageType, CancellationToken cancellationToken)
+ public async Task<IEnumerable<RemoteImageInfo>> GetImages(IHasImages item, ImageType imageType, CancellationToken cancellationToken)
{
var images = await GetAllImages(item, cancellationToken).ConfigureAwait(false);
return images.Where(i => i.Type == imageType);
}
- public Task<IEnumerable<RemoteImageInfo>> GetAllImages(BaseItem item, CancellationToken cancellationToken)
+ public Task<IEnumerable<RemoteImageInfo>> GetAllImages(IHasImages item, CancellationToken cancellationToken)
{
var seriesWithPerson = _library.RootFolder
.RecursiveChildren
diff --git a/MediaBrowser.Providers/TV/ManualTvdbSeasonImageProvider.cs b/MediaBrowser.Providers/TV/ManualTvdbSeasonImageProvider.cs
index 3efd0a3e3..d9a6f6507 100644
--- a/MediaBrowser.Providers/TV/ManualTvdbSeasonImageProvider.cs
+++ b/MediaBrowser.Providers/TV/ManualTvdbSeasonImageProvider.cs
@@ -38,19 +38,19 @@ namespace MediaBrowser.Providers.TV
get { return "TheTVDB"; }
}
- public bool Supports(BaseItem item)
+ public bool Supports(IHasImages item)
{
return item is Season;
}
- public async Task<IEnumerable<RemoteImageInfo>> GetImages(BaseItem item, ImageType imageType, CancellationToken cancellationToken)
+ public async Task<IEnumerable<RemoteImageInfo>> GetImages(IHasImages item, ImageType imageType, CancellationToken cancellationToken)
{
var images = await GetAllImages(item, cancellationToken).ConfigureAwait(false);
return images.Where(i => i.Type == imageType);
}
- public Task<IEnumerable<RemoteImageInfo>> GetAllImages(BaseItem item, CancellationToken cancellationToken)
+ public Task<IEnumerable<RemoteImageInfo>> GetAllImages(IHasImages item, CancellationToken cancellationToken)
{
var season = (Season)item;
@@ -65,7 +65,7 @@ namespace MediaBrowser.Providers.TV
try
{
- var result = GetImages(path, season.IndexNumber.Value, cancellationToken);
+ var result = GetImages(path, item.GetPreferredMetadataLanguage(), season.IndexNumber.Value, cancellationToken);
return Task.FromResult(result);
}
@@ -78,7 +78,7 @@ namespace MediaBrowser.Providers.TV
return Task.FromResult<IEnumerable<RemoteImageInfo>>(new RemoteImageInfo[] { });
}
- private IEnumerable<RemoteImageInfo> GetImages(string xmlPath, int seasonNumber, CancellationToken cancellationToken)
+ private IEnumerable<RemoteImageInfo> GetImages(string xmlPath, string preferredLanguage, int seasonNumber, CancellationToken cancellationToken)
{
var settings = new XmlReaderSettings
{
@@ -123,13 +123,11 @@ namespace MediaBrowser.Providers.TV
}
}
- var language = _config.Configuration.PreferredMetadataLanguage;
-
- var isLanguageEn = string.Equals(language, "en", StringComparison.OrdinalIgnoreCase);
+ var isLanguageEn = string.Equals(preferredLanguage, "en", StringComparison.OrdinalIgnoreCase);
return list.OrderByDescending(i =>
{
- if (string.Equals(language, i.Language, StringComparison.OrdinalIgnoreCase))
+ if (string.Equals(preferredLanguage, i.Language, StringComparison.OrdinalIgnoreCase))
{
return 3;
}
diff --git a/MediaBrowser.Providers/TV/ManualTvdbSeriesImageProvider.cs b/MediaBrowser.Providers/TV/ManualTvdbSeriesImageProvider.cs
index 5987215d1..644cad93b 100644
--- a/MediaBrowser.Providers/TV/ManualTvdbSeriesImageProvider.cs
+++ b/MediaBrowser.Providers/TV/ManualTvdbSeriesImageProvider.cs
@@ -38,21 +38,22 @@ namespace MediaBrowser.Providers.TV
get { return "TheTVDB"; }
}
- public bool Supports(BaseItem item)
+ public bool Supports(IHasImages item)
{
return item is Series;
}
- public async Task<IEnumerable<RemoteImageInfo>> GetImages(BaseItem item, ImageType imageType, CancellationToken cancellationToken)
+ public async Task<IEnumerable<RemoteImageInfo>> GetImages(IHasImages item, ImageType imageType, CancellationToken cancellationToken)
{
var images = await GetAllImages(item, cancellationToken).ConfigureAwait(false);
return images.Where(i => i.Type == imageType);
}
- public Task<IEnumerable<RemoteImageInfo>> GetAllImages(BaseItem item, CancellationToken cancellationToken)
+ public Task<IEnumerable<RemoteImageInfo>> GetAllImages(IHasImages item, CancellationToken cancellationToken)
{
- var seriesId = item.GetProviderId(MetadataProviders.Tvdb);
+ var series = (Series)item;
+ var seriesId = series.GetProviderId(MetadataProviders.Tvdb);
if (!string.IsNullOrEmpty(seriesId))
{
@@ -63,7 +64,7 @@ namespace MediaBrowser.Providers.TV
try
{
- var result = GetImages(path, cancellationToken);
+ var result = GetImages(path, item.GetPreferredMetadataLanguage(), cancellationToken);
return Task.FromResult(result);
}
@@ -76,7 +77,7 @@ namespace MediaBrowser.Providers.TV
return Task.FromResult<IEnumerable<RemoteImageInfo>>(new RemoteImageInfo[] { });
}
- private IEnumerable<RemoteImageInfo> GetImages(string xmlPath, CancellationToken cancellationToken)
+ private IEnumerable<RemoteImageInfo> GetImages(string xmlPath, string preferredLanguage, CancellationToken cancellationToken)
{
var settings = new XmlReaderSettings
{
@@ -121,13 +122,11 @@ namespace MediaBrowser.Providers.TV
}
}
- var language = _config.Configuration.PreferredMetadataLanguage;
-
- var isLanguageEn = string.Equals(language, "en", StringComparison.OrdinalIgnoreCase);
+ var isLanguageEn = string.Equals(preferredLanguage, "en", StringComparison.OrdinalIgnoreCase);
return list.OrderByDescending(i =>
{
- if (string.Equals(language, i.Language, StringComparison.OrdinalIgnoreCase))
+ if (string.Equals(preferredLanguage, i.Language, StringComparison.OrdinalIgnoreCase))
{
return 3;
}
diff --git a/MediaBrowser.Providers/TV/SeasonIndexNumberProvider.cs b/MediaBrowser.Providers/TV/SeasonIndexNumberProvider.cs
new file mode 100644
index 000000000..593784201
--- /dev/null
+++ b/MediaBrowser.Providers/TV/SeasonIndexNumberProvider.cs
@@ -0,0 +1,83 @@
+using MediaBrowser.Controller.Configuration;
+using MediaBrowser.Controller.Entities;
+using MediaBrowser.Controller.Entities.TV;
+using MediaBrowser.Controller.Library;
+using MediaBrowser.Controller.Providers;
+using MediaBrowser.Model.Entities;
+using MediaBrowser.Model.Logging;
+using System;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace MediaBrowser.Providers.TV
+{
+ class SeasonIndexNumberProvider : BaseMetadataProvider
+ {
+ /// <summary>
+ /// Initializes a new instance of the <see cref="BaseMetadataProvider" /> class.
+ /// </summary>
+ /// <param name="logManager">The log manager.</param>
+ /// <param name="configurationManager">The configuration manager.</param>
+ public SeasonIndexNumberProvider(ILogManager logManager, IServerConfigurationManager configurationManager)
+ : base(logManager, configurationManager)
+ {
+ }
+
+ protected override bool RefreshOnVersionChange
+ {
+ get
+ {
+ return true;
+ }
+ }
+
+ protected override string ProviderVersion
+ {
+ get
+ {
+ return "2";
+ }
+ }
+
+ /// <summary>
+ /// Supportses the specified item.
+ /// </summary>
+ /// <param name="item">The item.</param>
+ /// <returns><c>true</c> if XXXX, <c>false</c> otherwise</returns>
+ public override bool Supports(BaseItem item)
+ {
+ if (item is Season)
+ {
+ var locationType = item.LocationType;
+ return locationType != LocationType.Virtual && locationType != LocationType.Remote;
+ }
+ return false;
+ }
+
+ /// <summary>
+ /// Fetches metadata and returns true or false indicating if any work that requires persistence was done
+ /// </summary>
+ /// <param name="item">The item.</param>
+ /// <param name="force">if set to <c>true</c> [force].</param>
+ /// <param name="providerInfo">The provider information.</param>
+ /// <param name="cancellationToken">The cancellation token.</param>
+ /// <returns>Task{System.Boolean}.</returns>
+ public override Task<bool> FetchAsync(BaseItem item, bool force, BaseProviderInfo providerInfo, CancellationToken cancellationToken)
+ {
+ item.IndexNumber = TVUtils.GetSeasonNumberFromPath(item.Path);
+
+ SetLastRefreshed(item, DateTime.UtcNow, providerInfo);
+
+ return TrueTaskResult;
+ }
+
+ /// <summary>
+ /// Gets the priority.
+ /// </summary>
+ /// <value>The priority.</value>
+ public override MetadataProviderPriority Priority
+ {
+ get { return MetadataProviderPriority.First; }
+ }
+ }
+}
diff --git a/MediaBrowser.Providers/TV/SeasonProviderFromXml.cs b/MediaBrowser.Providers/TV/SeasonProviderFromXml.cs
index 2127234dc..9fbcad7c0 100644
--- a/MediaBrowser.Providers/TV/SeasonProviderFromXml.cs
+++ b/MediaBrowser.Providers/TV/SeasonProviderFromXml.cs
@@ -42,7 +42,7 @@ namespace MediaBrowser.Providers.TV
/// <value>The priority.</value>
public override MetadataProviderPriority Priority
{
- get { return MetadataProviderPriority.First; }
+ get { return MetadataProviderPriority.Second; }
}
private const string XmlFileName = "season.xml";
diff --git a/MediaBrowser.Providers/TV/SeriesPostScanTask.cs b/MediaBrowser.Providers/TV/SeriesPostScanTask.cs
index b1df4e553..cc24cad5d 100644
--- a/MediaBrowser.Providers/TV/SeriesPostScanTask.cs
+++ b/MediaBrowser.Providers/TV/SeriesPostScanTask.cs
@@ -39,8 +39,7 @@ namespace MediaBrowser.Providers.TV
private async Task RunInternal(IProgress<double> progress, CancellationToken cancellationToken)
{
- if (!_config.Configuration.EnableInternetProviders ||
- _config.Configuration.InternetProviderExcludeTypes.Contains(typeof(Series).Name, StringComparer.OrdinalIgnoreCase))
+ if (!_config.Configuration.EnableInternetProviders)
{
progress.Report(100);
return;
@@ -456,7 +455,7 @@ namespace MediaBrowser.Providers.TV
{
_logger.Info("Creating Season {0} entry for {1}", seasonNumber, series.Name);
- var name = string.Format("Season {0}", seasonNumber.ToString(UsCulture));
+ var name = seasonNumber == 0 ? _config.Configuration.SeasonZeroDisplayName : string.Format("Season {0}", seasonNumber.ToString(UsCulture));
var season = new Season
{
diff --git a/MediaBrowser.Providers/TV/TvdbEpisodeProvider.cs b/MediaBrowser.Providers/TV/TvdbEpisodeProvider.cs
index 5d9f387fe..f5e21bf69 100644
--- a/MediaBrowser.Providers/TV/TvdbEpisodeProvider.cs
+++ b/MediaBrowser.Providers/TV/TvdbEpisodeProvider.cs
@@ -404,6 +404,58 @@ namespace MediaBrowser.Providers.TV
break;
}
+ case "DVD_episodenumber":
+ {
+ var val = reader.ReadElementContentAsString();
+
+ if (!string.IsNullOrWhiteSpace(val))
+ {
+ float num;
+
+ if (float.TryParse(val, NumberStyles.Any, _usCulture, out num))
+ {
+ item.DvdEpisodeNumber = num;
+ }
+ }
+
+ break;
+ }
+
+ case "DVD_season":
+ {
+ var val = reader.ReadElementContentAsString();
+
+ if (!string.IsNullOrWhiteSpace(val))
+ {
+ float num;
+
+ if (float.TryParse(val, NumberStyles.Any, _usCulture, out num))
+ {
+ item.DvdSeasonNumber = Convert.ToInt32(num);
+ }
+ }
+
+ break;
+ }
+
+ case "absolute_number":
+ {
+ var val = reader.ReadElementContentAsString();
+
+ if (!string.IsNullOrWhiteSpace(val))
+ {
+ int rval;
+
+ // int.TryParse is local aware, so it can be probamatic, force us culture
+ if (int.TryParse(val, NumberStyles.Integer, _usCulture, out rval))
+ {
+ item.AbsoluteEpisodeNumber = rval;
+ }
+ }
+
+ break;
+ }
+
case "airsbefore_episode":
{
var val = reader.ReadElementContentAsString();
diff --git a/MediaBrowser.Providers/TV/TvdbPersonImageProvider.cs b/MediaBrowser.Providers/TV/TvdbPersonImageProvider.cs
index 3a503ea20..f2ce92efd 100644
--- a/MediaBrowser.Providers/TV/TvdbPersonImageProvider.cs
+++ b/MediaBrowser.Providers/TV/TvdbPersonImageProvider.cs
@@ -75,9 +75,10 @@ namespace MediaBrowser.Providers.TV
SetLastRefreshed(item, DateTime.UtcNow, providerInfo);
return true;
}
+
private async Task DownloadImages(BaseItem item, List<RemoteImageInfo> images, CancellationToken cancellationToken)
{
- if (!item.HasImage(ImageType.Primary))
+ if (!item.HasImage(ImageType.Primary) && !item.LockedFields.Contains(MetadataFields.Images))
{
var image = images.FirstOrDefault(i => i.Type == ImageType.Primary);
diff --git a/MediaBrowser.Providers/TV/TvdbPrescanTask.cs b/MediaBrowser.Providers/TV/TvdbPrescanTask.cs
index df5c39b65..24d231192 100644
--- a/MediaBrowser.Providers/TV/TvdbPrescanTask.cs
+++ b/MediaBrowser.Providers/TV/TvdbPrescanTask.cs
@@ -3,6 +3,7 @@ using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Entities.TV;
using MediaBrowser.Controller.Library;
+using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Logging;
using MediaBrowser.Model.Net;
using System;
@@ -45,6 +46,7 @@ namespace MediaBrowser.Providers.TV
/// </summary>
private readonly IServerConfigurationManager _config;
private readonly IFileSystem _fileSystem;
+ private readonly ILibraryManager _libraryManager;
/// <summary>
/// Initializes a new instance of the <see cref="TvdbPrescanTask"/> class.
@@ -52,12 +54,13 @@ namespace MediaBrowser.Providers.TV
/// <param name="logger">The logger.</param>
/// <param name="httpClient">The HTTP client.</param>
/// <param name="config">The config.</param>
- public TvdbPrescanTask(ILogger logger, IHttpClient httpClient, IServerConfigurationManager config, IFileSystem fileSystem)
+ public TvdbPrescanTask(ILogger logger, IHttpClient httpClient, IServerConfigurationManager config, IFileSystem fileSystem, ILibraryManager libraryManager)
{
_logger = logger;
_httpClient = httpClient;
_config = config;
_fileSystem = fileSystem;
+ _libraryManager = libraryManager;
}
protected readonly CultureInfo UsCulture = new CultureInfo("en-US");
@@ -70,8 +73,7 @@ namespace MediaBrowser.Providers.TV
/// <returns>Task.</returns>
public async Task Run(IProgress<double> progress, CancellationToken cancellationToken)
{
- if (!_config.Configuration.EnableInternetProviders ||
- _config.Configuration.InternetProviderExcludeTypes.Contains(typeof(Series).Name, StringComparer.OrdinalIgnoreCase))
+ if (!_config.Configuration.EnableInternetProviders)
{
progress.Report(100);
return;
@@ -274,19 +276,36 @@ namespace MediaBrowser.Providers.TV
var list = seriesIds.ToList();
var numComplete = 0;
+ // Gather all series into a lookup by tvdb id
+ var allSeries = _libraryManager.RootFolder.RecursiveChildren
+ .OfType<Series>()
+ .Where(i => !string.IsNullOrEmpty(i.GetProviderId(MetadataProviders.Tvdb)))
+ .ToLookup(i => i.GetProviderId(MetadataProviders.Tvdb));
+
foreach (var seriesId in list)
{
- try
- {
- await UpdateSeries(seriesId, seriesDataPath, lastTvDbUpdateTime, cancellationToken).ConfigureAwait(false);
- }
- catch (HttpException ex)
+ // Find the preferred language(s) for the movie in the library
+ var languages = allSeries[seriesId]
+ .Select(i => i.GetPreferredMetadataLanguage())
+ .Distinct(StringComparer.OrdinalIgnoreCase)
+ .ToList();
+
+ foreach (var language in languages)
{
- // Already logged at lower levels, but don't fail the whole operation, unless timed out
- // We have to fail this to make it run again otherwise new episode data could potentially be missing
- if (ex.IsTimedOut)
+ try
{
- throw;
+ await UpdateSeries(seriesId, seriesDataPath, lastTvDbUpdateTime, language, cancellationToken).ConfigureAwait(false);
+ }
+ catch (HttpException ex)
+ {
+ _logger.ErrorException("Error updating tvdb series id {0}, language {1}", ex, seriesId, language);
+
+ // Already logged at lower levels, but don't fail the whole operation, unless timed out
+ // We have to fail this to make it run again otherwise new episode data could potentially be missing
+ if (ex.IsTimedOut)
+ {
+ throw;
+ }
}
}
@@ -305,17 +324,18 @@ namespace MediaBrowser.Providers.TV
/// <param name="id">The id.</param>
/// <param name="seriesDataPath">The series data path.</param>
/// <param name="lastTvDbUpdateTime">The last tv db update time.</param>
+ /// <param name="preferredMetadataLanguage">The preferred metadata language.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>Task.</returns>
- private Task UpdateSeries(string id, string seriesDataPath, long? lastTvDbUpdateTime, CancellationToken cancellationToken)
+ private Task UpdateSeries(string id, string seriesDataPath, long? lastTvDbUpdateTime, string preferredMetadataLanguage, CancellationToken cancellationToken)
{
- _logger.Info("Updating series " + id);
+ _logger.Info("Updating movie from tmdb " + id + ", language " + preferredMetadataLanguage);
seriesDataPath = Path.Combine(seriesDataPath, id);
Directory.CreateDirectory(seriesDataPath);
- return TvdbSeriesProvider.Current.DownloadSeriesZip(id, seriesDataPath, lastTvDbUpdateTime, cancellationToken);
+ return TvdbSeriesProvider.Current.DownloadSeriesZip(id, seriesDataPath, lastTvDbUpdateTime, preferredMetadataLanguage, cancellationToken);
}
}
}
diff --git a/MediaBrowser.Providers/TV/TvdbSeasonProvider.cs b/MediaBrowser.Providers/TV/TvdbSeasonProvider.cs
index cdfe86dac..17ed6b5a2 100644
--- a/MediaBrowser.Providers/TV/TvdbSeasonProvider.cs
+++ b/MediaBrowser.Providers/TV/TvdbSeasonProvider.cs
@@ -1,4 +1,5 @@
-using MediaBrowser.Common.IO;
+using System.Net;
+using MediaBrowser.Common.IO;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.TV;
@@ -6,6 +7,7 @@ using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Logging;
+using MediaBrowser.Model.Net;
using MediaBrowser.Model.Providers;
using System;
using System.Collections.Generic;
@@ -157,29 +159,20 @@ namespace MediaBrowser.Providers.TV
private async Task DownloadImages(BaseItem item, List<RemoteImageInfo> images, int backdropLimit, CancellationToken cancellationToken)
{
- if (!item.HasImage(ImageType.Primary))
+ if (!item.LockedFields.Contains(MetadataFields.Images))
{
- var image = images.FirstOrDefault(i => i.Type == ImageType.Primary);
-
- if (image != null)
+ if (!item.HasImage(ImageType.Primary))
{
- await _providerManager.SaveImage(item, image.Url, TvdbSeriesProvider.Current.TvDbResourcePool, ImageType.Primary, null, cancellationToken)
- .ConfigureAwait(false);
+ await SaveImage(item, images, ImageType.Primary, cancellationToken).ConfigureAwait(false);
}
- }
-
- if (ConfigurationManager.Configuration.DownloadSeasonImages.Banner && !item.HasImage(ImageType.Banner))
- {
- var image = images.FirstOrDefault(i => i.Type == ImageType.Banner);
- if (image != null)
+ if (ConfigurationManager.Configuration.DownloadSeasonImages.Banner && !item.HasImage(ImageType.Banner))
{
- await _providerManager.SaveImage(item, image.Url, TvdbSeriesProvider.Current.TvDbResourcePool, ImageType.Banner, null, cancellationToken)
- .ConfigureAwait(false);
+ await SaveImage(item, images, ImageType.Banner, cancellationToken).ConfigureAwait(false);
}
}
- if (ConfigurationManager.Configuration.DownloadSeasonImages.Backdrops && item.BackdropImagePaths.Count < backdropLimit)
+ if (ConfigurationManager.Configuration.DownloadSeasonImages.Backdrops && item.BackdropImagePaths.Count < backdropLimit && !item.LockedFields.Contains(MetadataFields.Backdrops))
{
foreach (var backdrop in images.Where(i => i.Type == ImageType.Backdrop))
{
@@ -196,5 +189,26 @@ namespace MediaBrowser.Providers.TV
}
}
}
+
+ private async Task SaveImage(BaseItem item, List<RemoteImageInfo> images, ImageType type, CancellationToken cancellationToken)
+ {
+ foreach (var image in images.Where(i => i.Type == type))
+ {
+ try
+ {
+ await _providerManager.SaveImage(item, image.Url, TvdbSeriesProvider.Current.TvDbResourcePool, type, null, cancellationToken).ConfigureAwait(false);
+ break;
+ }
+ catch (HttpException ex)
+ {
+ // Sometimes fanart has bad url's in their xml
+ if (ex.StatusCode.HasValue && ex.StatusCode.Value == HttpStatusCode.NotFound)
+ {
+ continue;
+ }
+ break;
+ }
+ }
+ }
}
}
diff --git a/MediaBrowser.Providers/TV/TvdbSeriesImageProvider.cs b/MediaBrowser.Providers/TV/TvdbSeriesImageProvider.cs
index c1ac0e386..21a72dd57 100644
--- a/MediaBrowser.Providers/TV/TvdbSeriesImageProvider.cs
+++ b/MediaBrowser.Providers/TV/TvdbSeriesImageProvider.cs
@@ -137,13 +137,13 @@ namespace MediaBrowser.Providers.TV
protected override bool NeedsRefreshInternal(BaseItem item, BaseProviderInfo providerInfo)
{
- if (item.HasImage(ImageType.Primary) && item.HasImage(ImageType.Banner) && item.BackdropImagePaths.Count >= ConfigurationManager.Configuration.MaxBackdrops)
+ if (item.HasImage(ImageType.Primary) && item.HasImage(ImageType.Banner) && item.BackdropImagePaths.Count >= ConfigurationManager.Configuration.TvOptions.MaxBackdrops)
{
return false;
}
return base.NeedsRefreshInternal(item, providerInfo);
}
-
+
/// <summary>
/// Fetches metadata and returns true or false indicating if any work that requires persistence was done
/// </summary>
@@ -167,33 +167,36 @@ namespace MediaBrowser.Providers.TV
private async Task DownloadImages(BaseItem item, List<RemoteImageInfo> images, int backdropLimit, CancellationToken cancellationToken)
{
- if (!item.HasImage(ImageType.Primary))
+ if (!item.LockedFields.Contains(MetadataFields.Images))
{
- var image = images.FirstOrDefault(i => i.Type == ImageType.Primary);
-
- if (image != null)
+ if (!item.HasImage(ImageType.Primary))
{
- await _providerManager.SaveImage(item, image.Url, TvdbSeriesProvider.Current.TvDbResourcePool, ImageType.Primary, null, cancellationToken)
- .ConfigureAwait(false);
- }
- }
+ var image = images.FirstOrDefault(i => i.Type == ImageType.Primary);
- if (ConfigurationManager.Configuration.DownloadSeriesImages.Banner && !item.HasImage(ImageType.Banner))
- {
- var image = images.FirstOrDefault(i => i.Type == ImageType.Banner);
+ if (image != null)
+ {
+ await _providerManager.SaveImage(item, image.Url, TvdbSeriesProvider.Current.TvDbResourcePool, ImageType.Primary, null, cancellationToken)
+ .ConfigureAwait(false);
+ }
+ }
- if (image != null)
+ if (ConfigurationManager.Configuration.DownloadSeriesImages.Banner && !item.HasImage(ImageType.Banner))
{
- await _providerManager.SaveImage(item, image.Url, TvdbSeriesProvider.Current.TvDbResourcePool, ImageType.Banner, null, cancellationToken)
- .ConfigureAwait(false);
+ var image = images.FirstOrDefault(i => i.Type == ImageType.Banner);
+
+ if (image != null)
+ {
+ await _providerManager.SaveImage(item, image.Url, TvdbSeriesProvider.Current.TvDbResourcePool, ImageType.Banner, null, cancellationToken)
+ .ConfigureAwait(false);
+ }
}
}
- if (ConfigurationManager.Configuration.DownloadSeriesImages.Backdrops && item.BackdropImagePaths.Count < backdropLimit)
+ if (ConfigurationManager.Configuration.DownloadSeriesImages.Backdrops && item.BackdropImagePaths.Count < backdropLimit && !item.LockedFields.Contains(MetadataFields.Backdrops))
{
foreach (var backdrop in images.Where(i => i.Type == ImageType.Backdrop &&
(!i.Width.HasValue ||
- i.Width.Value >= ConfigurationManager.Configuration.MinSeriesBackdropDownloadWidth)))
+ i.Width.Value >= ConfigurationManager.Configuration.TvOptions.MinBackdropWidth)))
{
var url = backdrop.Url;
diff --git a/MediaBrowser.Providers/TV/TvdbSeriesProvider.cs b/MediaBrowser.Providers/TV/TvdbSeriesProvider.cs
index 5691f885e..4df391e2a 100644
--- a/MediaBrowser.Providers/TV/TvdbSeriesProvider.cs
+++ b/MediaBrowser.Providers/TV/TvdbSeriesProvider.cs
@@ -248,13 +248,13 @@ namespace MediaBrowser.Providers.TV
.Select(Path.GetFileName)
.ToList();
- var seriesXmlFilename = ConfigurationManager.Configuration.PreferredMetadataLanguage.ToLower() + ".xml";
+ var seriesXmlFilename = series.GetPreferredMetadataLanguage().ToLower() + ".xml";
// Only download if not already there
// The prescan task will take care of updates so we don't need to re-download here
if (!files.Contains("banners.xml", StringComparer.OrdinalIgnoreCase) || !files.Contains("actors.xml", StringComparer.OrdinalIgnoreCase) || !files.Contains(seriesXmlFilename, StringComparer.OrdinalIgnoreCase))
{
- await DownloadSeriesZip(seriesId, seriesDataPath, null, cancellationToken).ConfigureAwait(false);
+ await DownloadSeriesZip(seriesId, seriesDataPath, null, series.GetPreferredMetadataLanguage(), cancellationToken).ConfigureAwait(false);
}
// Have to check this here since we prevent the normal enforcement through ProviderManager
@@ -285,11 +285,13 @@ namespace MediaBrowser.Providers.TV
/// </summary>
/// <param name="seriesId">The series id.</param>
/// <param name="seriesDataPath">The series data path.</param>
+ /// <param name="lastTvDbUpdateTime">The last tv database update time.</param>
+ /// <param name="preferredMetadataLanguage">The preferred metadata language.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>Task.</returns>
- internal async Task DownloadSeriesZip(string seriesId, string seriesDataPath, long? lastTvDbUpdateTime, CancellationToken cancellationToken)
+ internal async Task DownloadSeriesZip(string seriesId, string seriesDataPath, long? lastTvDbUpdateTime, string preferredMetadataLanguage, CancellationToken cancellationToken)
{
- var url = string.Format(SeriesGetZip, TVUtils.TvdbApiKey, seriesId, ConfigurationManager.Configuration.PreferredMetadataLanguage);
+ var url = string.Format(SeriesGetZip, TVUtils.TvdbApiKey, seriesId, preferredMetadataLanguage);
using (var zipStream = await HttpClient.Get(new HttpRequestOptions
{
@@ -319,7 +321,7 @@ namespace MediaBrowser.Providers.TV
await SanitizeXmlFile(file).ConfigureAwait(false);
}
- await ExtractEpisodes(seriesDataPath, Path.Combine(seriesDataPath, ConfigurationManager.Configuration.PreferredMetadataLanguage + ".xml"), lastTvDbUpdateTime).ConfigureAwait(false);
+ await ExtractEpisodes(seriesDataPath, Path.Combine(seriesDataPath, preferredMetadataLanguage + ".xml"), lastTvDbUpdateTime).ConfigureAwait(false);
}
private void DeleteXmlFiles(string path)
@@ -852,7 +854,7 @@ namespace MediaBrowser.Providers.TV
if (!string.IsNullOrWhiteSpace(val))
{
// Only fill this in if there's no existing genres, because Imdb data from Omdb is preferred
- if (!item.LockedFields.Contains(MetadataFields.Genres) && (item.Genres.Count == 0 || !string.Equals(ConfigurationManager.Configuration.PreferredMetadataLanguage, "en", StringComparison.OrdinalIgnoreCase)))
+ if (!item.LockedFields.Contains(MetadataFields.Genres) && (item.Genres.Count == 0 || !string.Equals(item.GetPreferredMetadataLanguage(), "en", StringComparison.OrdinalIgnoreCase)))
{
var vals = val
.Split(new[] { '|' }, StringSplitOptions.RemoveEmptyEntries)
diff --git a/MediaBrowser.Providers/VirtualItemImageValidator.cs b/MediaBrowser.Providers/VirtualItemImageValidator.cs
new file mode 100644
index 000000000..d4bbaf713
--- /dev/null
+++ b/MediaBrowser.Providers/VirtualItemImageValidator.cs
@@ -0,0 +1,48 @@
+using MediaBrowser.Controller.Configuration;
+using MediaBrowser.Controller.Entities;
+using MediaBrowser.Controller.Providers;
+using MediaBrowser.Model.Entities;
+using MediaBrowser.Model.Logging;
+using System;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace MediaBrowser.Providers
+{
+ public class VirtualItemImageValidator : BaseMetadataProvider
+ {
+ public VirtualItemImageValidator(ILogManager logManager, IServerConfigurationManager configurationManager)
+ : base(logManager, configurationManager)
+ {
+ }
+
+ public override bool Supports(BaseItem item)
+ {
+ var locationType = item.LocationType;
+
+ return locationType == LocationType.Virtual ||
+ locationType == LocationType.Remote;
+ }
+
+ public override Task<bool> FetchAsync(BaseItem item, bool force, BaseProviderInfo providerInfo, CancellationToken cancellationToken)
+ {
+ item.ValidateImages();
+ item.ValidateBackdrops();
+
+ var hasScreenshots = item as IHasScreenshots;
+
+ if (hasScreenshots != null)
+ {
+ hasScreenshots.ValidateScreenshots();
+ }
+
+ SetLastRefreshed(item, DateTime.UtcNow, providerInfo);
+ return TrueTaskResult;
+ }
+
+ public override MetadataProviderPriority Priority
+ {
+ get { return MetadataProviderPriority.First; }
+ }
+ }
+}
diff --git a/MediaBrowser.Server.Implementations/Configuration/ServerConfigurationManager.cs b/MediaBrowser.Server.Implementations/Configuration/ServerConfigurationManager.cs
index 8165e11eb..94438e3e0 100644
--- a/MediaBrowser.Server.Implementations/Configuration/ServerConfigurationManager.cs
+++ b/MediaBrowser.Server.Implementations/Configuration/ServerConfigurationManager.cs
@@ -69,10 +69,9 @@ namespace MediaBrowser.Server.Implementations.Configuration
/// </summary>
private void UpdateItemsByNamePath()
{
- if (!string.IsNullOrEmpty(Configuration.ItemsByNamePath))
- {
- ApplicationPaths.ItemsByNamePath = Configuration.ItemsByNamePath;
- }
+ ((ServerApplicationPaths) ApplicationPaths).ItemsByNamePath = string.IsNullOrEmpty(Configuration.ItemsByNamePath) ?
+ null :
+ Configuration.ItemsByNamePath;
}
/// <summary>
@@ -84,19 +83,29 @@ namespace MediaBrowser.Server.Implementations.Configuration
{
var newConfig = (ServerConfiguration) newConfiguration;
- var newIbnPath = newConfig.ItemsByNamePath;
+ ValidateItemByNamePath(newConfig);
+
+ base.ReplaceConfiguration(newConfiguration);
+ }
+
+ /// <summary>
+ /// Replaces the item by name path.
+ /// </summary>
+ /// <param name="newConfig">The new configuration.</param>
+ /// <exception cref="System.IO.DirectoryNotFoundException"></exception>
+ private void ValidateItemByNamePath(ServerConfiguration newConfig)
+ {
+ var newPath = newConfig.ItemsByNamePath;
- if (!string.IsNullOrWhiteSpace(newIbnPath)
- && !string.Equals(Configuration.ItemsByNamePath ?? string.Empty, newIbnPath))
+ if (!string.IsNullOrWhiteSpace(newPath)
+ && !string.Equals(Configuration.ItemsByNamePath ?? string.Empty, newPath))
{
// Validate
- if (!Directory.Exists(newIbnPath))
+ if (!Directory.Exists(newPath))
{
- throw new DirectoryNotFoundException(string.Format("{0} does not exist.", newConfig.ItemsByNamePath));
+ throw new DirectoryNotFoundException(string.Format("{0} does not exist.", newPath));
}
}
-
- base.ReplaceConfiguration(newConfiguration);
}
}
}
diff --git a/MediaBrowser.Server.Implementations/Drawing/ImageHeader.cs b/MediaBrowser.Server.Implementations/Drawing/ImageHeader.cs
index f9cf90787..3d53d2b86 100644
--- a/MediaBrowser.Server.Implementations/Drawing/ImageHeader.cs
+++ b/MediaBrowser.Server.Implementations/Drawing/ImageHeader.cs
@@ -1,5 +1,4 @@
using MediaBrowser.Common.IO;
-using MediaBrowser.Controller.IO;
using MediaBrowser.Model.Logging;
using System;
using System.Collections.Generic;
@@ -69,6 +68,8 @@ namespace MediaBrowser.Server.Implementations.Drawing
{
fs.CopyTo(memoryStream);
+ memoryStream.Position = 0;
+
// Co it the old fashioned way
using (var b = Image.FromStream(memoryStream, true, false))
{
diff --git a/MediaBrowser.Server.Implementations/Drawing/ImageProcessor.cs b/MediaBrowser.Server.Implementations/Drawing/ImageProcessor.cs
index 27ce90787..7ddf63cf8 100644
--- a/MediaBrowser.Server.Implementations/Drawing/ImageProcessor.cs
+++ b/MediaBrowser.Server.Implementations/Drawing/ImageProcessor.cs
@@ -53,10 +53,6 @@ namespace MediaBrowser.Server.Implementations.Drawing
private readonly IJsonSerializer _jsonSerializer;
private readonly IServerApplicationPaths _appPaths;
- private readonly string _croppedWhitespaceImageCachePath;
- private readonly string _enhancedImageCachePath;
- private readonly string _resizedImageCachePath;
-
public ImageProcessor(ILogger logger, IServerApplicationPaths appPaths, IFileSystem fileSystem, IJsonSerializer jsonSerializer)
{
_logger = logger;
@@ -64,10 +60,6 @@ namespace MediaBrowser.Server.Implementations.Drawing
_jsonSerializer = jsonSerializer;
_appPaths = appPaths;
- _croppedWhitespaceImageCachePath = Path.Combine(appPaths.ImageCachePath, "cropped-images");
- _enhancedImageCachePath = Path.Combine(appPaths.ImageCachePath, "enhanced-images");
- _resizedImageCachePath = Path.Combine(appPaths.ImageCachePath, "resized-images");
-
_saveImageSizeTimer = new Timer(SaveImageSizeCallback, null, Timeout.Infinite, Timeout.Infinite);
Dictionary<Guid, ImageSize> sizeDictionary;
@@ -92,6 +84,30 @@ namespace MediaBrowser.Server.Implementations.Drawing
_cachedImagedSizes = new ConcurrentDictionary<Guid, ImageSize>(sizeDictionary);
}
+ private string ResizedImageCachePath
+ {
+ get
+ {
+ return Path.Combine(_appPaths.ImageCachePath, "resized-images");
+ }
+ }
+
+ private string EnhancedImageCachePath
+ {
+ get
+ {
+ return Path.Combine(_appPaths.ImageCachePath, "enhanced-images");
+ }
+ }
+
+ private string CroppedWhitespaceImageCachePath
+ {
+ get
+ {
+ return Path.Combine(_appPaths.ImageCachePath, "cropped-images");
+ }
+ }
+
public void AddParts(IEnumerable<IImageEnhancer> enhancers)
{
ImageEnhancers = enhancers.ToArray();
@@ -212,8 +228,12 @@ namespace MediaBrowser.Server.Implementations.Drawing
// Graphics.FromImage will throw an exception if the PixelFormat is Indexed, so we need to handle that here
using (var thumbnail = new Bitmap(newWidth, newHeight, PixelFormat.Format32bppPArgb))
{
- // Preserve the original resolution
- thumbnail.SetResolution(originalImage.HorizontalResolution, originalImage.VerticalResolution);
+ // Mono throw an exeception if assign 0 to SetResolution
+ if (originalImage.HorizontalResolution >= 0 && originalImage.VerticalResolution >= 0)
+ {
+ // Preserve the original resolution
+ thumbnail.SetResolution(originalImage.HorizontalResolution, originalImage.VerticalResolution);
+ }
using (var thumbnailGraph = Graphics.FromImage(thumbnail))
{
@@ -391,7 +411,7 @@ namespace MediaBrowser.Server.Implementations.Drawing
var name = originalImagePath;
name += "datemodified=" + dateModified.Ticks;
- var croppedImagePath = GetCachePath(_croppedWhitespaceImageCachePath, name, Path.GetExtension(originalImagePath));
+ var croppedImagePath = GetCachePath(CroppedWhitespaceImageCachePath, name, Path.GetExtension(originalImagePath));
var semaphore = GetLock(croppedImagePath);
@@ -480,7 +500,7 @@ namespace MediaBrowser.Server.Implementations.Drawing
filename += "b=" + backgroundColor;
}
- return GetCachePath(_resizedImageCachePath, filename, Path.GetExtension(originalPath));
+ return GetCachePath(ResizedImageCachePath, filename, Path.GetExtension(originalPath));
}
/// <summary>
@@ -578,7 +598,7 @@ namespace MediaBrowser.Server.Implementations.Drawing
/// <param name="imagePath">The image path.</param>
/// <returns>Guid.</returns>
/// <exception cref="System.ArgumentNullException">item</exception>
- public Guid GetImageCacheTag(BaseItem item, ImageType imageType, string imagePath)
+ public Guid GetImageCacheTag(IHasImages item, ImageType imageType, string imagePath)
{
if (item == null)
{
@@ -607,7 +627,7 @@ namespace MediaBrowser.Server.Implementations.Drawing
/// <param name="imageEnhancers">The image enhancers.</param>
/// <returns>Guid.</returns>
/// <exception cref="System.ArgumentNullException">item</exception>
- public Guid GetImageCacheTag(BaseItem item, ImageType imageType, string originalImagePath, DateTime dateModified, List<IImageEnhancer> imageEnhancers)
+ public Guid GetImageCacheTag(IHasImages item, ImageType imageType, string originalImagePath, DateTime dateModified, List<IImageEnhancer> imageEnhancers)
{
if (item == null)
{
@@ -644,7 +664,7 @@ namespace MediaBrowser.Server.Implementations.Drawing
/// <param name="imageType">Type of the image.</param>
/// <param name="imageIndex">Index of the image.</param>
/// <returns>Task{System.String}.</returns>
- public async Task<string> GetEnhancedImage(BaseItem item, ImageType imageType, int imageIndex)
+ public async Task<string> GetEnhancedImage(IHasImages item, ImageType imageType, int imageIndex)
{
var enhancers = GetSupportedEnhancers(item, imageType).ToList();
@@ -657,7 +677,7 @@ namespace MediaBrowser.Server.Implementations.Drawing
return result.Item1;
}
- private async Task<Tuple<string, DateTime>> GetEnhancedImage(string originalImagePath, DateTime dateModified, BaseItem item,
+ private async Task<Tuple<string, DateTime>> GetEnhancedImage(string originalImagePath, DateTime dateModified, IHasImages item,
ImageType imageType, int imageIndex,
List<IImageEnhancer> enhancers)
{
@@ -693,7 +713,7 @@ namespace MediaBrowser.Server.Implementations.Drawing
/// <param name="supportedEnhancers">The supported enhancers.</param>
/// <returns>System.String.</returns>
/// <exception cref="System.ArgumentNullException">originalImagePath</exception>
- private async Task<string> GetEnhancedImageInternal(string originalImagePath, DateTime dateModified, BaseItem item, ImageType imageType, int imageIndex, List<IImageEnhancer> supportedEnhancers)
+ private async Task<string> GetEnhancedImageInternal(string originalImagePath, DateTime dateModified, IHasImages item, ImageType imageType, int imageIndex, List<IImageEnhancer> supportedEnhancers)
{
if (string.IsNullOrEmpty(originalImagePath))
{
@@ -708,7 +728,7 @@ namespace MediaBrowser.Server.Implementations.Drawing
var cacheGuid = GetImageCacheTag(item, imageType, originalImagePath, dateModified, supportedEnhancers);
// All enhanced images are saved as png to allow transparency
- var enhancedImagePath = GetCachePath(_enhancedImageCachePath, cacheGuid + ".png");
+ var enhancedImagePath = GetCachePath(EnhancedImageCachePath, cacheGuid + ".png");
var semaphore = GetLock(enhancedImagePath);
@@ -766,7 +786,7 @@ namespace MediaBrowser.Server.Implementations.Drawing
/// <param name="imageType">Type of the image.</param>
/// <param name="imageIndex">Index of the image.</param>
/// <returns>Task{EnhancedImage}.</returns>
- private async Task<Image> ExecuteImageEnhancers(IEnumerable<IImageEnhancer> imageEnhancers, Image originalImage, BaseItem item, ImageType imageType, int imageIndex)
+ private async Task<Image> ExecuteImageEnhancers(IEnumerable<IImageEnhancer> imageEnhancers, Image originalImage, IHasImages item, ImageType imageType, int imageIndex)
{
var result = originalImage;
@@ -884,7 +904,7 @@ namespace MediaBrowser.Server.Implementations.Drawing
return Path.Combine(path, filename);
}
- public IEnumerable<IImageEnhancer> GetSupportedEnhancers(BaseItem item, ImageType imageType)
+ public IEnumerable<IImageEnhancer> GetSupportedEnhancers(IHasImages item, ImageType imageType)
{
return ImageEnhancers.Where(i =>
{
diff --git a/MediaBrowser.Server.Implementations/Dto/DtoService.cs b/MediaBrowser.Server.Implementations/Dto/DtoService.cs
index ba9dd170d..932c7ba3f 100644
--- a/MediaBrowser.Server.Implementations/Dto/DtoService.cs
+++ b/MediaBrowser.Server.Implementations/Dto/DtoService.cs
@@ -163,8 +163,7 @@ namespace MediaBrowser.Server.Implementations.Dto
{
var folder = (Folder)item;
- dto.ChildCount = folder.GetChildren(user, true)
- .Count();
+ dto.ChildCount = GetChildCount(folder, user);
if (!(folder is UserRootFolder))
{
@@ -182,6 +181,12 @@ namespace MediaBrowser.Server.Implementations.Dto
}
}
+ private int GetChildCount(Folder folder, User user)
+ {
+ return folder.GetChildren(user, true)
+ .Count();
+ }
+
public UserDto GetUserDto(User user)
{
if (user == null)
@@ -238,7 +243,8 @@ namespace MediaBrowser.Server.Implementations.Dto
NowViewingItemType = session.NowViewingItemType,
ApplicationVersion = session.ApplicationVersion,
CanSeek = session.CanSeek,
- QueueableMediaTypes = session.QueueableMediaTypes
+ QueueableMediaTypes = session.QueueableMediaTypes,
+ RemoteEndPoint = session.RemoteEndPoint
};
if (session.NowPlayingItem != null)
@@ -287,8 +293,6 @@ namespace MediaBrowser.Server.Implementations.Dto
return info;
}
- const string IndexFolderDelimeter = "-index-";
-
/// <summary>
/// Gets client-side Id of a server-side BaseItem
/// </summary>
@@ -302,13 +306,6 @@ namespace MediaBrowser.Server.Implementations.Dto
throw new ArgumentNullException("item");
}
- var indexFolder = item as IndexFolder;
-
- if (indexFolder != null)
- {
- return GetDtoId(indexFolder.Parent) + IndexFolderDelimeter + (indexFolder.IndexName ?? string.Empty) + IndexFolderDelimeter + indexFolder.Id;
- }
-
return item.Id.ToString("N");
}
@@ -613,26 +610,15 @@ namespace MediaBrowser.Server.Implementations.Dto
throw new ArgumentNullException("id");
}
- // If the item is an indexed folder we have to do a special routine to get it
- var isIndexFolder = id.IndexOf(IndexFolderDelimeter, StringComparison.OrdinalIgnoreCase) != -1;
-
- if (isIndexFolder)
- {
- if (userId.HasValue)
- {
- return GetIndexFolder(id, userId.Value);
- }
- }
-
BaseItem item = null;
- if (userId.HasValue || !isIndexFolder)
+ if (userId.HasValue)
{
item = _libraryManager.GetItemById(new Guid(id));
}
// If we still don't find it, look within individual user views
- if (item == null && !userId.HasValue && isIndexFolder)
+ if (item == null && !userId.HasValue)
{
foreach (var user in _userManager.Users)
{
@@ -649,60 +635,6 @@ namespace MediaBrowser.Server.Implementations.Dto
}
/// <summary>
- /// Finds an index folder based on an Id and userId
- /// </summary>
- /// <param name="id">The id.</param>
- /// <param name="userId">The user id.</param>
- /// <returns>BaseItem.</returns>
- private BaseItem GetIndexFolder(string id, Guid userId)
- {
- var user = _userManager.GetUserById(userId);
-
- var stringSeparators = new[] { IndexFolderDelimeter };
-
- // Split using the delimeter
- var values = id.Split(stringSeparators, StringSplitOptions.None).ToList();
-
- // Get the top folder normally using the first id
- var folder = GetItemByDtoId(values[0], userId) as Folder;
-
- values.RemoveAt(0);
-
- // Get indexed folders using the remaining values in the id string
- return GetIndexFolder(values, folder, user);
- }
-
- /// <summary>
- /// Gets indexed folders based on a list of index names and folder id's
- /// </summary>
- /// <param name="values">The values.</param>
- /// <param name="parentFolder">The parent folder.</param>
- /// <param name="user">The user.</param>
- /// <returns>BaseItem.</returns>
- private BaseItem GetIndexFolder(List<string> values, Folder parentFolder, User user)
- {
- // The index name is first
- var indexBy = values[0];
-
- // The index folder id is next
- var indexFolderId = new Guid(values[1]);
-
- // Remove them from the lst
- values.RemoveRange(0, 2);
-
- // Get the IndexFolder
- var indexFolder = parentFolder.GetChildren(user, false, indexBy).FirstOrDefault(i => i.Id == indexFolderId) as Folder;
-
- // Nested index folder
- if (values.Count > 0)
- {
- return GetIndexFolder(values, indexFolder, user);
- }
-
- return indexFolder;
- }
-
- /// <summary>
/// Sets simple property values on a DTOBaseItem
/// </summary>
/// <param name="dto">The dto.</param>
@@ -723,7 +655,7 @@ namespace MediaBrowser.Server.Implementations.Dto
dto.DisplayMediaType = item.DisplayMediaType;
- if (fields.Contains(ItemFields.MetadataSettings))
+ if (fields.Contains(ItemFields.Settings))
{
dto.LockedFields = item.LockedFields;
dto.EnableInternetProviders = !item.DontFetchMeta;
@@ -807,10 +739,12 @@ namespace MediaBrowser.Server.Implementations.Dto
dto.MediaType = item.MediaType;
dto.LocationType = item.LocationType;
- var hasLanguage = item as IHasLanguage;
- if (hasLanguage != null)
+ var hasLang = item as IHasPreferredMetadataLanguage;
+
+ if (hasLang != null)
{
- dto.Language = hasLanguage.Language;
+ dto.PreferredMetadataCountryCode = hasLang.PreferredMetadataCountryCode;
+ dto.PreferredMetadataLanguage = hasLang.PreferredMetadataLanguage;
}
var hasCriticRating = item as IHasCriticRating;
@@ -887,7 +821,7 @@ namespace MediaBrowser.Server.Implementations.Dto
{
dto.ParentLogoItemId = GetDtoId(parentWithLogo);
- dto.ParentLogoImageTag = GetImageCacheTag(parentWithLogo, ImageType.Logo, parentWithLogo.GetImage(ImageType.Logo));
+ dto.ParentLogoImageTag = GetImageCacheTag(parentWithLogo, ImageType.Logo, parentWithLogo.GetImagePath(ImageType.Logo));
}
}
@@ -900,7 +834,7 @@ namespace MediaBrowser.Server.Implementations.Dto
{
dto.ParentArtItemId = GetDtoId(parentWithImage);
- dto.ParentArtImageTag = GetImageCacheTag(parentWithImage, ImageType.Art, parentWithImage.GetImage(ImageType.Art));
+ dto.ParentArtImageTag = GetImageCacheTag(parentWithImage, ImageType.Art, parentWithImage.GetImagePath(ImageType.Art));
}
}
@@ -913,7 +847,7 @@ namespace MediaBrowser.Server.Implementations.Dto
{
dto.ParentThumbItemId = GetDtoId(parentWithImage);
- dto.ParentThumbImageTag = GetImageCacheTag(parentWithImage, ImageType.Thumb, parentWithImage.GetImage(ImageType.Thumb));
+ dto.ParentThumbImageTag = GetImageCacheTag(parentWithImage, ImageType.Thumb, parentWithImage.GetImagePath(ImageType.Thumb));
}
}
@@ -1061,7 +995,13 @@ namespace MediaBrowser.Server.Implementations.Dto
if (episode != null)
{
dto.IndexNumberEnd = episode.IndexNumberEnd;
- dto.SpecialSeasonNumber = episode.AirsAfterSeasonNumber ?? episode.AirsBeforeSeasonNumber;
+
+ dto.DvdSeasonNumber = episode.DvdSeasonNumber;
+ dto.DvdEpisodeNumber = episode.DvdEpisodeNumber;
+ dto.AirsAfterSeasonNumber = episode.AirsAfterSeasonNumber;
+ dto.AirsBeforeEpisodeNumber = episode.AirsBeforeEpisodeNumber;
+ dto.AirsBeforeSeasonNumber = episode.AirsBeforeSeasonNumber;
+ dto.AbsoluteEpisodeNumber = episode.AbsoluteEpisodeNumber;
var seasonId = episode.SeasonId;
if (seasonId.HasValue)
@@ -1082,6 +1022,11 @@ namespace MediaBrowser.Server.Implementations.Dto
dto.SpecialFeatureCount = series.SpecialFeatureIds.Count;
dto.SeasonCount = series.SeasonCount;
+
+ if (fields.Contains(ItemFields.Settings))
+ {
+ dto.DisplaySpecialsWithSeasons = series.DisplaySpecialsWithSeasons;
+ }
}
if (episode != null)
@@ -1095,7 +1040,7 @@ namespace MediaBrowser.Server.Implementations.Dto
if (series.HasImage(ImageType.Thumb))
{
- dto.SeriesThumbImageTag = GetImageCacheTag(series, ImageType.Thumb, series.GetImage(ImageType.Thumb));
+ dto.SeriesThumbImageTag = GetImageCacheTag(series, ImageType.Thumb, series.GetImagePath(ImageType.Thumb));
}
var imagePath = series.PrimaryImagePath;
@@ -1197,8 +1142,21 @@ namespace MediaBrowser.Server.Implementations.Dto
double totalPercentPlayed = 0;
+ IEnumerable<BaseItem> children;
+
+ var season = folder as Season;
+
+ if (season != null)
+ {
+ children = season.GetEpisodes(user).Where(i => i.LocationType != LocationType.Virtual);
+ }
+ else
+ {
+ children = folder.GetRecursiveChildren(user, i => !i.IsFolder && i.LocationType != LocationType.Virtual);
+ }
+
// Loop through each recursive child
- foreach (var child in folder.GetRecursiveChildren(user, i => !i.IsFolder && i.LocationType != LocationType.Virtual))
+ foreach (var child in children)
{
var userdata = _userDataRepository.GetUserData(user.Id, child.GetUserDataKey());
diff --git a/MediaBrowser.Server.Implementations/HttpServer/HttpListenerHost.cs b/MediaBrowser.Server.Implementations/HttpServer/HttpListenerHost.cs
index 29dce6747..34d705bfb 100644
--- a/MediaBrowser.Server.Implementations/HttpServer/HttpListenerHost.cs
+++ b/MediaBrowser.Server.Implementations/HttpServer/HttpListenerHost.cs
@@ -5,6 +5,7 @@ using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Net;
using MediaBrowser.Model.Logging;
using ServiceStack;
+using ServiceStack.Api.Swagger;
using ServiceStack.Host;
using ServiceStack.Host.Handlers;
using ServiceStack.Host.HttpListener;
@@ -63,8 +64,6 @@ namespace MediaBrowser.Server.Implementations.HttpServer
_logger = logManager.GetLogger("HttpServer");
- LogManager.LogFactory = new ServerLogFactory(logManager);
-
_containerAdapter = new ContainerAdapter(applicationHost);
for (var i = 0; i < 2; i++)
@@ -95,7 +94,7 @@ namespace MediaBrowser.Server.Implementations.HttpServer
container.Adapter = _containerAdapter;
- //Plugins.Add(new SwaggerFeature());
+ Plugins.Add(new SwaggerFeature());
Plugins.Add(new CorsFeature());
HostContext.GlobalResponseFilters.Add(new ResponseFilter(_logger).FilterResponse);
}
@@ -477,7 +476,8 @@ namespace MediaBrowser.Server.Implementations.HttpServer
ServiceController = CreateServiceController();
_logger.Info("Calling ServiceStack AppHost.Init");
- Init();
+
+ base.Init();
}
/// <summary>
diff --git a/MediaBrowser.Server.Implementations/HttpServer/ServerFactory.cs b/MediaBrowser.Server.Implementations/HttpServer/ServerFactory.cs
index 57acddc43..c403c09b4 100644
--- a/MediaBrowser.Server.Implementations/HttpServer/ServerFactory.cs
+++ b/MediaBrowser.Server.Implementations/HttpServer/ServerFactory.cs
@@ -1,6 +1,7 @@
using MediaBrowser.Common;
using MediaBrowser.Controller.Net;
using MediaBrowser.Model.Logging;
+using ServiceStack.Logging;
namespace MediaBrowser.Server.Implementations.HttpServer
{
@@ -20,6 +21,8 @@ namespace MediaBrowser.Server.Implementations.HttpServer
/// <returns>IHttpServer.</returns>
public static IHttpServer CreateServer(IApplicationHost applicationHost, ILogManager logManager, string serverName, string handlerPath, string defaultRedirectpath)
{
+ LogManager.LogFactory = new ServerLogFactory(logManager);
+
return new HttpListenerHost(applicationHost, logManager, serverName, handlerPath, defaultRedirectpath);
}
}
diff --git a/MediaBrowser.Server.Implementations/IO/DirectoryWatchers.cs b/MediaBrowser.Server.Implementations/IO/DirectoryWatchers.cs
index a2240f52d..1efc3bc70 100644
--- a/MediaBrowser.Server.Implementations/IO/DirectoryWatchers.cs
+++ b/MediaBrowser.Server.Implementations/IO/DirectoryWatchers.cs
@@ -529,27 +529,26 @@ namespace MediaBrowser.Server.Implementations.IO
return;
}
- await Task.WhenAll(itemsToRefresh.Select(i => Task.Run(async () =>
+ foreach (var item in itemsToRefresh)
{
- Logger.Info(i.Name + " (" + i.Path + ") will be refreshed.");
+ Logger.Info(item.Name + " (" + item.Path + ") will be refreshed.");
try
{
- await i.ChangedExternally().ConfigureAwait(false);
+ await item.ChangedExternally().ConfigureAwait(false);
}
catch (IOException ex)
{
// For now swallow and log.
// Research item: If an IOException occurs, the item may be in a disconnected state (media unavailable)
// Should we remove it from it's parent?
- Logger.ErrorException("Error refreshing {0}", ex, i.Name);
+ Logger.ErrorException("Error refreshing {0}", ex, item.Name);
}
catch (Exception ex)
{
- Logger.ErrorException("Error refreshing {0}", ex, i.Name);
+ Logger.ErrorException("Error refreshing {0}", ex, item.Name);
}
-
- }))).ConfigureAwait(false);
+ }
}
/// <summary>
diff --git a/MediaBrowser.Server.Implementations/Library/CoreResolutionIgnoreRule.cs b/MediaBrowser.Server.Implementations/Library/CoreResolutionIgnoreRule.cs
index 95ec416b6..5268faa4f 100644
--- a/MediaBrowser.Server.Implementations/Library/CoreResolutionIgnoreRule.cs
+++ b/MediaBrowser.Server.Implementations/Library/CoreResolutionIgnoreRule.cs
@@ -114,6 +114,12 @@ namespace MediaBrowser.Server.Implementations.Library
{
return true;
}
+
+ // Don't misidentify xbmc trailers as a movie
+ if (filename.IndexOf(BaseItem.XbmcTrailerFileSuffix, StringComparison.OrdinalIgnoreCase) != -1)
+ {
+ return true;
+ }
}
}
diff --git a/MediaBrowser.Server.Implementations/Library/LibraryManager.cs b/MediaBrowser.Server.Implementations/Library/LibraryManager.cs
index 41694765d..11c99a32c 100644
--- a/MediaBrowser.Server.Implementations/Library/LibraryManager.cs
+++ b/MediaBrowser.Server.Implementations/Library/LibraryManager.cs
@@ -899,6 +899,15 @@ namespace MediaBrowser.Server.Implementations.Library
}
/// <summary>
+ /// Queues the library scan.
+ /// </summary>
+ public void QueueLibraryScan()
+ {
+ // Just run the scheduled task so that the user can see it
+ _taskManager.QueueScheduledTask<RefreshMediaLibraryTask>();
+ }
+
+ /// <summary>
/// Validates the media library internal.
/// </summary>
/// <param name="progress">The progress.</param>
@@ -1311,11 +1320,6 @@ namespace MediaBrowser.Server.Implementations.Library
{
var list = items.ToList();
- foreach (var item in list)
- {
- item.DateLastSaved = DateTime.UtcNow;
- }
-
await ItemRepository.SaveItems(list, cancellationToken).ConfigureAwait(false);
foreach (var item in list)
diff --git a/MediaBrowser.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs b/MediaBrowser.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs
index 03e29dd38..3d6f7e66a 100644
--- a/MediaBrowser.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs
+++ b/MediaBrowser.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs
@@ -91,31 +91,43 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers.Movies
if (args.Path.IndexOf("[trailers]", StringComparison.OrdinalIgnoreCase) != -1 ||
string.Equals(collectionType, CollectionType.Trailers, StringComparison.OrdinalIgnoreCase))
{
- return FindMovie<Trailer>(args.Path, args.FileSystemChildren);
+ return FindMovie<Trailer>(args.Path, args.Parent, args.FileSystemChildren);
}
if (args.Path.IndexOf("[musicvideos]", StringComparison.OrdinalIgnoreCase) != -1 ||
string.Equals(collectionType, CollectionType.MusicVideos, StringComparison.OrdinalIgnoreCase))
{
- return FindMovie<MusicVideo>(args.Path, args.FileSystemChildren);
+ return FindMovie<MusicVideo>(args.Path, args.Parent, args.FileSystemChildren);
}
if (args.Path.IndexOf("[adultvideos]", StringComparison.OrdinalIgnoreCase) != -1 ||
string.Equals(collectionType, CollectionType.AdultVideos, StringComparison.OrdinalIgnoreCase))
{
- return FindMovie<AdultVideo>(args.Path, args.FileSystemChildren);
+ return FindMovie<AdultVideo>(args.Path, args.Parent, args.FileSystemChildren);
}
+ if (string.Equals(collectionType, CollectionType.HomeVideos, StringComparison.OrdinalIgnoreCase))
+ {
+ return FindMovie<Video>(args.Path, args.Parent, args.FileSystemChildren);
+ }
+
if (string.IsNullOrEmpty(collectionType) ||
string.Equals(collectionType, CollectionType.Movies, StringComparison.OrdinalIgnoreCase) ||
string.Equals(collectionType, CollectionType.BoxSets, StringComparison.OrdinalIgnoreCase))
{
- return FindMovie<Movie>(args.Path, args.FileSystemChildren);
+ return FindMovie<Movie>(args.Path, args.Parent, args.FileSystemChildren);
}
return null;
}
+ var filename = Path.GetFileName(args.Path);
+ // Don't misidentify xbmc trailers as a movie
+ if (filename.IndexOf(BaseItem.XbmcTrailerFileSuffix, StringComparison.OrdinalIgnoreCase) != -1)
+ {
+ return null;
+ }
+
// Find movies that are mixed in the same folder
if (args.Path.IndexOf("[trailers]", StringComparison.OrdinalIgnoreCase) != -1 ||
string.Equals(collectionType, CollectionType.Trailers, StringComparison.OrdinalIgnoreCase))
@@ -172,7 +184,7 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers.Movies
private void SetProviderIdFromPath(Video item)
{
//we need to only look at the name of this actual item (not parents)
- var justName = item.IsInMixedFolder ? Path.GetFileName(item.Path) : Path.GetFileName(Path.GetDirectoryName(item.Path));
+ var justName = item.IsInMixedFolder ? Path.GetFileName(item.Path) : Path.GetFileName(item.MetaLocation);
var id = justName.GetAttributeValue("tmdbid");
@@ -187,9 +199,10 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers.Movies
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="path">The path.</param>
+ /// <param name="parent">The parent.</param>
/// <param name="fileSystemEntries">The file system entries.</param>
/// <returns>Movie.</returns>
- private T FindMovie<T>(string path, IEnumerable<FileSystemInfo> fileSystemEntries)
+ private T FindMovie<T>(string path, Folder parent, IEnumerable<FileSystemInfo> fileSystemEntries)
where T : Video, new()
{
var movies = new List<T>();
@@ -237,7 +250,8 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers.Movies
var childArgs = new ItemResolveArgs(_applicationPaths, _libraryManager)
{
FileInfo = child,
- Path = child.FullName
+ Path = child.FullName,
+ Parent = parent
};
var item = ResolveVideo<T>(childArgs);
@@ -261,7 +275,9 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers.Movies
if (multiDiscFolders.Count > 0)
{
- return GetMultiDiscMovie<T>(multiDiscFolders);
+ var folders = fileSystemEntries.Where(child => (child.Attributes & FileAttributes.Directory) == FileAttributes.Directory);
+
+ return GetMultiDiscMovie<T>(multiDiscFolders, folders);
}
return null;
@@ -271,25 +287,26 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers.Movies
/// Gets the multi disc movie.
/// </summary>
/// <typeparam name="T"></typeparam>
- /// <param name="folders">The folders.</param>
+ /// <param name="multiDiscFolders">The folders.</param>
+ /// <param name="allFolders">All folders.</param>
/// <returns>``0.</returns>
- private T GetMultiDiscMovie<T>(List<FileSystemInfo> folders)
+ private T GetMultiDiscMovie<T>(List<FileSystemInfo> multiDiscFolders, IEnumerable<FileSystemInfo> allFolders)
where T : Video, new()
{
- var videoType = VideoType.BluRay;
+ var videoTypes = new List<VideoType>();
- var folderPaths = folders.Select(i => i.FullName).Where(i =>
+ var folderPaths = multiDiscFolders.Select(i => i.FullName).Where(i =>
{
var subfolders = Directory.GetDirectories(i).Select(Path.GetFileName).ToList();
if (subfolders.Any(IsDvdDirectory))
{
- videoType = VideoType.Dvd;
+ videoTypes.Add(VideoType.Dvd);
return true;
}
if (subfolders.Any(IsBluRayDirectory))
{
- videoType = VideoType.BluRay;
+ videoTypes.Add(VideoType.BluRay);
return true;
}
@@ -297,18 +314,46 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers.Movies
}).OrderBy(i => i).ToList();
+ // If different video types were found, don't allow this
+ if (videoTypes.Count > 0 && videoTypes.Any(i => i != videoTypes[0]))
+ {
+ return null;
+ }
+
if (folderPaths.Count == 0)
{
return null;
}
+ // If there are other folders side by side that are folder rips, don't allow it
+ // TODO: Improve this to return null if any folder is present aside from our regularly ignored folders
+ if (allFolders.Except(multiDiscFolders).Any(i =>
+ {
+ var subfolders = Directory.GetDirectories(i.FullName).Select(Path.GetFileName).ToList();
+
+ if (subfolders.Any(IsDvdDirectory))
+ {
+ return true;
+ }
+ if (subfolders.Any(IsBluRayDirectory))
+ {
+ return true;
+ }
+
+ return false;
+
+ }))
+ {
+ return null;
+ }
+
return new T
{
Path = folderPaths[0],
IsMultiPart = true,
- VideoType = videoType
+ VideoType = videoTypes[0]
};
}
diff --git a/MediaBrowser.Server.Implementations/Library/UserDataManager.cs b/MediaBrowser.Server.Implementations/Library/UserDataManager.cs
index 8d010aecc..79f126511 100644
--- a/MediaBrowser.Server.Implementations/Library/UserDataManager.cs
+++ b/MediaBrowser.Server.Implementations/Library/UserDataManager.cs
@@ -49,7 +49,7 @@ namespace MediaBrowser.Server.Implementations.Library
/// userId
/// or
/// key</exception>
- public async Task SaveUserData(Guid userId, BaseItem item, UserItemData userData, UserDataSaveReason reason, CancellationToken cancellationToken)
+ public async Task SaveUserData(Guid userId, IHasUserData item, UserItemData userData, UserDataSaveReason reason, CancellationToken cancellationToken)
{
if (userData == null)
{
diff --git a/MediaBrowser.Server.Implementations/Library/UserManager.cs b/MediaBrowser.Server.Implementations/Library/UserManager.cs
index 4243aecfe..d4a74f2b6 100644
--- a/MediaBrowser.Server.Implementations/Library/UserManager.cs
+++ b/MediaBrowser.Server.Implementations/Library/UserManager.cs
@@ -22,39 +22,10 @@ namespace MediaBrowser.Server.Implementations.Library
public class UserManager : IUserManager
{
/// <summary>
- /// The _users
- /// </summary>
- private IEnumerable<User> _users;
- /// <summary>
- /// The _user lock
- /// </summary>
- private object _usersSyncLock = new object();
- /// <summary>
- /// The _users initialized
- /// </summary>
- private bool _usersInitialized;
- /// <summary>
/// Gets the users.
/// </summary>
/// <value>The users.</value>
- public IEnumerable<User> Users
- {
- get
- {
- // Call ToList to exhaust the stream because we'll be iterating over this multiple times
- LazyInitializer.EnsureInitialized(ref _users, ref _usersInitialized, ref _usersSyncLock, LoadUsers);
- return _users;
- }
- internal set
- {
- _users = value;
-
- if (value == null)
- {
- _usersInitialized = false;
- }
- }
- }
+ public IEnumerable<User> Users { get; private set; }
/// <summary>
/// The _logger
@@ -78,11 +49,13 @@ namespace MediaBrowser.Server.Implementations.Library
/// </summary>
/// <param name="logger">The logger.</param>
/// <param name="configurationManager">The configuration manager.</param>
+ /// <param name="userRepository">The user repository.</param>
public UserManager(ILogger logger, IServerConfigurationManager configurationManager, IUserRepository userRepository)
{
_logger = logger;
UserRepository = userRepository;
ConfigurationManager = configurationManager;
+ Users = new List<User>();
}
#region UserUpdated Event
@@ -132,6 +105,11 @@ namespace MediaBrowser.Server.Implementations.Library
return Users.FirstOrDefault(u => u.Id == id);
}
+ public async Task Initialize()
+ {
+ Users = await LoadUsers().ConfigureAwait(false);
+ }
+
/// <summary>
/// Authenticates a User and returns a result indicating whether or not it succeeded
/// </summary>
@@ -185,7 +163,7 @@ namespace MediaBrowser.Server.Implementations.Library
/// Loads the users from the repository
/// </summary>
/// <returns>IEnumerable{User}.</returns>
- private IEnumerable<User> LoadUsers()
+ private async Task<IEnumerable<User>> LoadUsers()
{
var users = UserRepository.RetrieveAllUsers().ToList();
@@ -198,10 +176,7 @@ namespace MediaBrowser.Server.Implementations.Library
user.DateLastSaved = DateTime.UtcNow;
- var task = UserRepository.SaveUser(user, CancellationToken.None);
-
- // Hate having to block threads
- Task.WaitAll(task);
+ await UserRepository.SaveUser(user, CancellationToken.None).ConfigureAwait(false);
users.Add(user);
}
@@ -284,7 +259,7 @@ namespace MediaBrowser.Server.Implementations.Library
}
public event EventHandler<GenericEventArgs<User>> UserCreated;
-
+
/// <summary>
/// Creates the user.
/// </summary>
@@ -311,11 +286,11 @@ namespace MediaBrowser.Server.Implementations.Library
Users = list;
user.DateLastSaved = DateTime.UtcNow;
-
+
await UserRepository.SaveUser(user, CancellationToken.None).ConfigureAwait(false);
EventHelper.QueueEventIfNotNull(UserCreated, this, new GenericEventArgs<User> { Argument = user }, _logger);
-
+
return user;
}
@@ -377,10 +352,10 @@ namespace MediaBrowser.Server.Implementations.Library
}
}
- OnUserDeleted(user);
-
// Force this to be lazy loaded again
- Users = null;
+ Users = await LoadUsers().ConfigureAwait(false);
+
+ OnUserDeleted(user);
}
/// <summary>
diff --git a/MediaBrowser.Server.Implementations/LiveTv/ChannelImageProvider.cs b/MediaBrowser.Server.Implementations/LiveTv/ChannelImageProvider.cs
index e16430e69..d04ebe32d 100644
--- a/MediaBrowser.Server.Implementations/LiveTv/ChannelImageProvider.cs
+++ b/MediaBrowser.Server.Implementations/LiveTv/ChannelImageProvider.cs
@@ -1,4 +1,6 @@
-using MediaBrowser.Controller.Configuration;
+using MediaBrowser.Common.IO;
+using MediaBrowser.Common.Net;
+using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.LiveTv;
@@ -7,6 +9,7 @@ using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Logging;
using MediaBrowser.Model.Net;
using System;
+using System.IO;
using System.Linq;
using System.Net;
using System.Threading;
@@ -18,17 +21,21 @@ namespace MediaBrowser.Server.Implementations.LiveTv
{
private readonly ILiveTvManager _liveTvManager;
private readonly IProviderManager _providerManager;
+ private readonly IFileSystem _fileSystem;
+ private readonly IHttpClient _httpClient;
- public ChannelImageProvider(ILogManager logManager, IServerConfigurationManager configurationManager, ILiveTvManager liveTvManager, IProviderManager providerManager)
+ public ChannelImageProvider(ILogManager logManager, IServerConfigurationManager configurationManager, ILiveTvManager liveTvManager, IProviderManager providerManager, IFileSystem fileSystem, IHttpClient httpClient)
: base(logManager, configurationManager)
{
_liveTvManager = liveTvManager;
_providerManager = providerManager;
+ _fileSystem = fileSystem;
+ _httpClient = httpClient;
}
public override bool Supports(BaseItem item)
{
- return item is Channel;
+ return item is LiveTvChannel;
}
protected override bool NeedsRefreshInternal(BaseItem item, BaseProviderInfo providerInfo)
@@ -44,9 +51,11 @@ namespace MediaBrowser.Server.Implementations.LiveTv
return true;
}
+ var changed = true;
+
try
{
- await DownloadImage(item, cancellationToken).ConfigureAwait(false);
+ changed = await DownloadImage((LiveTvChannel)item, cancellationToken).ConfigureAwait(false);
}
catch (HttpException ex)
{
@@ -57,26 +66,78 @@ namespace MediaBrowser.Server.Implementations.LiveTv
}
}
+ if (changed)
+ {
+ SetLastRefreshed(item, DateTime.UtcNow, providerInfo);
+ }
- SetLastRefreshed(item, DateTime.UtcNow, providerInfo);
- return true;
+ return changed;
}
- private async Task DownloadImage(BaseItem item, CancellationToken cancellationToken)
+ private async Task<bool> DownloadImage(LiveTvChannel item, CancellationToken cancellationToken)
{
- var channel = (Channel)item;
+ var channelInfo = item.ChannelInfo;
+
+ Stream imageStream = null;
+ string contentType = null;
+
+ if (!string.IsNullOrEmpty(channelInfo.ImagePath))
+ {
+ contentType = "image/" + Path.GetExtension(channelInfo.ImagePath).ToLower();
+ imageStream = _fileSystem.GetFileStream(channelInfo.ImagePath, FileMode.Open, FileAccess.Read, FileShare.Read, true);
+ }
+ else if (!string.IsNullOrEmpty(channelInfo.ImageUrl))
+ {
+ var options = new HttpRequestOptions
+ {
+ CancellationToken = cancellationToken,
+ Url = channelInfo.ImageUrl
+ };
+
+ var response = await _httpClient.GetResponse(options).ConfigureAwait(false);
- var service = _liveTvManager.Services.FirstOrDefault(i => string.Equals(i.Name, channel.ServiceName, StringComparison.OrdinalIgnoreCase));
+ if (!response.ContentType.StartsWith("image/", StringComparison.OrdinalIgnoreCase))
+ {
+ Logger.Error("Provider did not return an image content type.");
+ return false;
+ }
- if (service != null)
+ imageStream = response.Content;
+ contentType = response.ContentType;
+ }
+ else if (channelInfo.HasImage ?? true)
{
- var response = await service.GetChannelImageAsync(channel.ChannelId, cancellationToken).ConfigureAwait(false);
+ var service = _liveTvManager.Services.FirstOrDefault(i => string.Equals(i.Name, item.ServiceName, StringComparison.OrdinalIgnoreCase));
+ if (service != null)
+ {
+ try
+ {
+ var response = await service.GetChannelImageAsync(channelInfo.Id, cancellationToken).ConfigureAwait(false);
+
+ if (response != null)
+ {
+ imageStream = response.Stream;
+ contentType = response.MimeType;
+ }
+ }
+ catch (NotImplementedException)
+ {
+ return false;
+ }
+ }
+ }
+
+ if (imageStream != null)
+ {
// Dummy up the original url
- var url = channel.ServiceName + channel.ChannelId;
+ var url = item.ServiceName + channelInfo.Id;
- await _providerManager.SaveImage(channel, response.Stream, response.MimeType, ImageType.Primary, null, url, cancellationToken).ConfigureAwait(false);
+ await _providerManager.SaveImage(item, imageStream, contentType, ImageType.Primary, null, url, cancellationToken).ConfigureAwait(false);
+ return true;
}
+
+ return false;
}
public override MetadataProviderPriority Priority
diff --git a/MediaBrowser.Server.Implementations/LiveTv/LiveTvDtoService.cs b/MediaBrowser.Server.Implementations/LiveTv/LiveTvDtoService.cs
new file mode 100644
index 000000000..0b2d0c5e9
--- /dev/null
+++ b/MediaBrowser.Server.Implementations/LiveTv/LiveTvDtoService.cs
@@ -0,0 +1,503 @@
+using MediaBrowser.Common.Extensions;
+using MediaBrowser.Controller.Drawing;
+using MediaBrowser.Controller.Dto;
+using MediaBrowser.Controller.Entities;
+using MediaBrowser.Controller.Library;
+using MediaBrowser.Controller.LiveTv;
+using MediaBrowser.Model.Entities;
+using MediaBrowser.Model.LiveTv;
+using MediaBrowser.Model.Logging;
+using System;
+using System.Collections.Generic;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace MediaBrowser.Server.Implementations.LiveTv
+{
+ public class LiveTvDtoService
+ {
+ private readonly ILogger _logger;
+ private readonly IImageProcessor _imageProcessor;
+
+ private readonly IUserDataManager _userDataManager;
+ private readonly IDtoService _dtoService;
+
+ public LiveTvDtoService(IDtoService dtoService, IUserDataManager userDataManager, IImageProcessor imageProcessor, ILogger logger)
+ {
+ _dtoService = dtoService;
+ _userDataManager = userDataManager;
+ _imageProcessor = imageProcessor;
+ _logger = logger;
+ }
+
+ public TimerInfoDto GetTimerInfoDto(TimerInfo info, ILiveTvService service, LiveTvProgram program, LiveTvChannel channel)
+ {
+ var dto = new TimerInfoDto
+ {
+ Id = GetInternalTimerId(service.Name, info.Id).ToString("N"),
+ Overview = info.Overview,
+ EndDate = info.EndDate,
+ Name = info.Name,
+ StartDate = info.StartDate,
+ ExternalId = info.Id,
+ ChannelId = GetInternalChannelId(service.Name, info.ChannelId).ToString("N"),
+ Status = info.Status,
+ SeriesTimerId = string.IsNullOrEmpty(info.SeriesTimerId) ? null : GetInternalSeriesTimerId(service.Name, info.SeriesTimerId).ToString("N"),
+ PrePaddingSeconds = info.PrePaddingSeconds,
+ PostPaddingSeconds = info.PostPaddingSeconds,
+ IsPostPaddingRequired = info.IsPostPaddingRequired,
+ IsPrePaddingRequired = info.IsPrePaddingRequired,
+ ExternalChannelId = info.ChannelId,
+ ExternalSeriesTimerId = info.SeriesTimerId,
+ ServiceName = service.Name,
+ ExternalProgramId = info.ProgramId,
+ Priority = info.Priority,
+ RunTimeTicks = (info.EndDate - info.StartDate).Ticks
+ };
+
+ if (!string.IsNullOrEmpty(info.ProgramId))
+ {
+ dto.ProgramId = GetInternalProgramId(service.Name, info.ProgramId).ToString("N");
+ }
+
+ if (program != null)
+ {
+ dto.ProgramInfo = GetProgramInfoDto(program, channel.ChannelInfo.Name);
+
+ dto.ProgramInfo.TimerId = dto.Id;
+ dto.ProgramInfo.SeriesTimerId = dto.SeriesTimerId;
+ }
+
+ if (channel != null)
+ {
+ dto.ChannelName = channel.ChannelInfo.Name;
+ }
+
+ return dto;
+ }
+
+ public SeriesTimerInfoDto GetSeriesTimerInfoDto(SeriesTimerInfo info, ILiveTvService service, string channelName)
+ {
+ var dto = new SeriesTimerInfoDto
+ {
+ Id = GetInternalSeriesTimerId(service.Name, info.Id).ToString("N"),
+ Overview = info.Overview,
+ EndDate = info.EndDate,
+ Name = info.Name,
+ StartDate = info.StartDate,
+ ExternalId = info.Id,
+ PrePaddingSeconds = info.PrePaddingSeconds,
+ PostPaddingSeconds = info.PostPaddingSeconds,
+ IsPostPaddingRequired = info.IsPostPaddingRequired,
+ IsPrePaddingRequired = info.IsPrePaddingRequired,
+ Days = info.Days,
+ Priority = info.Priority,
+ RecordAnyChannel = info.RecordAnyChannel,
+ RecordAnyTime = info.RecordAnyTime,
+ RecordNewOnly = info.RecordNewOnly,
+ ExternalChannelId = info.ChannelId,
+ ExternalProgramId = info.ProgramId,
+ ServiceName = service.Name,
+ ChannelName = channelName
+ };
+
+ if (!string.IsNullOrEmpty(info.ChannelId))
+ {
+ dto.ChannelId = GetInternalChannelId(service.Name, info.ChannelId).ToString("N");
+ }
+
+ if (!string.IsNullOrEmpty(info.ProgramId))
+ {
+ dto.ProgramId = GetInternalProgramId(service.Name, info.ProgramId).ToString("N");
+ }
+
+ dto.DayPattern = info.Days == null ? null : GetDayPattern(info.Days);
+
+ return dto;
+ }
+
+ public DayPattern? GetDayPattern(List<DayOfWeek> days)
+ {
+ DayPattern? pattern = null;
+
+ if (days.Count > 0)
+ {
+ if (days.Count == 7)
+ {
+ pattern = DayPattern.Daily;
+ }
+ else if (days.Count == 2)
+ {
+ if (days.Contains(DayOfWeek.Saturday) && days.Contains(DayOfWeek.Sunday))
+ {
+ pattern = DayPattern.Weekends;
+ }
+ }
+ else if (days.Count == 5)
+ {
+ if (days.Contains(DayOfWeek.Monday) && days.Contains(DayOfWeek.Tuesday) && days.Contains(DayOfWeek.Wednesday) && days.Contains(DayOfWeek.Thursday) && days.Contains(DayOfWeek.Friday))
+ {
+ pattern = DayPattern.Weekdays;
+ }
+ }
+ }
+
+ return pattern;
+ }
+
+ /// <summary>
+ /// Convert the provider 0-5 scale to our 0-10 scale
+ /// </summary>
+ /// <param name="val"></param>
+ /// <returns></returns>
+ private float? GetClientCommunityRating(float? val)
+ {
+ if (!val.HasValue)
+ {
+ return null;
+ }
+
+ return val.Value * 2;
+ }
+
+ public string GetStatusName(RecordingStatus status)
+ {
+ if (status == RecordingStatus.InProgress)
+ {
+ return "In Progress";
+ }
+
+ if (status == RecordingStatus.ConflictedNotOk)
+ {
+ return "Conflicted";
+ }
+
+ if (status == RecordingStatus.ConflictedOk)
+ {
+ return "Scheduled";
+ }
+
+ return status.ToString();
+ }
+
+ public RecordingInfoDto GetRecordingInfoDto(LiveTvRecording recording, LiveTvChannel channel, ILiveTvService service, User user = null)
+ {
+ var info = recording.RecordingInfo;
+
+ var dto = new RecordingInfoDto
+ {
+ Id = GetInternalRecordingId(service.Name, info.Id).ToString("N"),
+ SeriesTimerId = string.IsNullOrEmpty(info.SeriesTimerId) ? null : GetInternalSeriesTimerId(service.Name, info.SeriesTimerId).ToString("N"),
+ Type = recording.GetClientTypeName(),
+ Overview = info.Overview,
+ EndDate = info.EndDate,
+ Name = info.Name,
+ StartDate = info.StartDate,
+ ExternalId = info.Id,
+ ChannelId = GetInternalChannelId(service.Name, info.ChannelId).ToString("N"),
+ Status = info.Status,
+ StatusName = GetStatusName(info.Status),
+ Path = info.Path,
+ Genres = info.Genres,
+ IsRepeat = info.IsRepeat,
+ EpisodeTitle = info.EpisodeTitle,
+ ChannelType = info.ChannelType,
+ MediaType = info.ChannelType == ChannelType.Radio ? MediaType.Audio : MediaType.Video,
+ CommunityRating = GetClientCommunityRating(info.CommunityRating),
+ OfficialRating = info.OfficialRating,
+ Audio = info.Audio,
+ IsHD = info.IsHD,
+ ServiceName = service.Name,
+ Url = info.Url,
+ IsMovie = info.IsMovie,
+ IsSeries = info.IsSeries,
+ IsSports = info.IsSports,
+ IsLive = info.IsLive,
+ IsNews = info.IsNews,
+ IsKids = info.IsKids,
+ IsPremiere = info.IsPremiere,
+ RunTimeTicks = (info.EndDate - info.StartDate).Ticks
+ };
+
+ var imageTag = GetImageTag(recording);
+
+ if (imageTag.HasValue)
+ {
+ dto.ImageTags[ImageType.Primary] = imageTag.Value;
+ }
+
+ if (user != null)
+ {
+ dto.UserData = _dtoService.GetUserItemDataDto(_userDataManager.GetUserData(user.Id, recording.GetUserDataKey()));
+ }
+
+ if (!string.IsNullOrEmpty(info.ProgramId))
+ {
+ dto.ProgramId = GetInternalProgramId(service.Name, info.ProgramId).ToString("N");
+ }
+
+ if (channel != null)
+ {
+ dto.ChannelName = channel.ChannelInfo.Name;
+ }
+
+ return dto;
+ }
+
+ /// <summary>
+ /// Gets the channel info dto.
+ /// </summary>
+ /// <param name="info">The info.</param>
+ /// <param name="user">The user.</param>
+ /// <returns>ChannelInfoDto.</returns>
+ public ChannelInfoDto GetChannelInfoDto(LiveTvChannel info, User user = null)
+ {
+ var channelInfo = info.ChannelInfo;
+
+ var dto = new ChannelInfoDto
+ {
+ Name = info.Name,
+ ServiceName = info.ServiceName,
+ ChannelType = channelInfo.ChannelType,
+ Number = channelInfo.Number,
+ Type = info.GetClientTypeName(),
+ Id = info.Id.ToString("N"),
+ MediaType = info.MediaType,
+ ExternalId = channelInfo.Id
+ };
+
+ if (user != null)
+ {
+ dto.UserData = _dtoService.GetUserItemDataDto(_userDataManager.GetUserData(user.Id, info.GetUserDataKey()));
+ }
+
+ var imageTag = GetImageTag(info);
+
+ if (imageTag.HasValue)
+ {
+ dto.ImageTags[ImageType.Primary] = imageTag.Value;
+ }
+
+ return dto;
+ }
+
+ public ProgramInfoDto GetProgramInfoDto(LiveTvProgram item, string channelName, User user = null)
+ {
+ var program = item.ProgramInfo;
+
+ var dto = new ProgramInfoDto
+ {
+ Id = GetInternalProgramId(item.ServiceName, program.Id).ToString("N"),
+ ChannelId = GetInternalChannelId(item.ServiceName, program.ChannelId).ToString("N"),
+ Overview = program.Overview,
+ EndDate = program.EndDate,
+ Genres = program.Genres,
+ ExternalId = program.Id,
+ Name = program.Name,
+ ServiceName = item.ServiceName,
+ StartDate = program.StartDate,
+ OfficialRating = program.OfficialRating,
+ IsHD = program.IsHD,
+ OriginalAirDate = program.OriginalAirDate,
+ Audio = program.Audio,
+ CommunityRating = GetClientCommunityRating(program.CommunityRating),
+ IsRepeat = program.IsRepeat,
+ EpisodeTitle = program.EpisodeTitle,
+ ChannelName = channelName,
+ IsMovie = program.IsMovie,
+ IsSeries = program.IsSeries,
+ IsSports = program.IsSports,
+ IsLive = program.IsLive,
+ IsNews = program.IsNews,
+ IsKids = program.IsKids,
+ IsPremiere = program.IsPremiere,
+ RunTimeTicks = (program.EndDate - program.StartDate).Ticks,
+ Type = "Program"
+ };
+
+ var imageTag = GetImageTag(item);
+
+ if (imageTag.HasValue)
+ {
+ dto.ImageTags[ImageType.Primary] = imageTag.Value;
+ }
+
+ if (user != null)
+ {
+ dto.UserData = _dtoService.GetUserItemDataDto(_userDataManager.GetUserData(user.Id, item.GetUserDataKey()));
+ }
+
+ return dto;
+ }
+
+ private Guid? GetImageTag(BaseItem info)
+ {
+ var path = info.PrimaryImagePath;
+
+ if (string.IsNullOrEmpty(path))
+ {
+ return null;
+ }
+
+ try
+ {
+ return _imageProcessor.GetImageCacheTag(info, ImageType.Primary, path);
+ }
+ catch (Exception ex)
+ {
+ _logger.ErrorException("Error getting image info for {0}", ex, info.Name);
+ }
+
+ return null;
+ }
+
+ public Guid GetInternalChannelId(string serviceName, string externalId)
+ {
+ var name = serviceName + externalId;
+
+ return name.ToLower().GetMBId(typeof(LiveTvChannel));
+ }
+
+ public Guid GetInternalTimerId(string serviceName, string externalId)
+ {
+ var name = serviceName + externalId;
+
+ return name.ToLower().GetMD5();
+ }
+
+ public Guid GetInternalSeriesTimerId(string serviceName, string externalId)
+ {
+ var name = serviceName + externalId;
+
+ return name.ToLower().GetMD5();
+ }
+
+ public Guid GetInternalProgramId(string serviceName, string externalId)
+ {
+ var name = serviceName + externalId;
+
+ return name.ToLower().GetMD5();
+ }
+
+ public Guid GetInternalRecordingId(string serviceName, string externalId)
+ {
+ var name = serviceName + externalId;
+
+ return name.ToLower().GetMD5();
+ }
+
+ public async Task<TimerInfo> GetTimerInfo(TimerInfoDto dto, bool isNew, ILiveTvManager liveTv, CancellationToken cancellationToken)
+ {
+ var info = new TimerInfo
+ {
+ Overview = dto.Overview,
+ EndDate = dto.EndDate,
+ Name = dto.Name,
+ StartDate = dto.StartDate,
+ Status = dto.Status,
+ PrePaddingSeconds = dto.PrePaddingSeconds,
+ PostPaddingSeconds = dto.PostPaddingSeconds,
+ IsPostPaddingRequired = dto.IsPostPaddingRequired,
+ IsPrePaddingRequired = dto.IsPrePaddingRequired,
+ Priority = dto.Priority,
+ SeriesTimerId = dto.ExternalSeriesTimerId,
+ ProgramId = dto.ExternalProgramId,
+ ChannelId = dto.ExternalChannelId,
+ Id = dto.ExternalId
+ };
+
+ // Convert internal server id's to external tv provider id's
+ if (!isNew && !string.IsNullOrEmpty(dto.Id) && string.IsNullOrEmpty(info.Id))
+ {
+ var timer = await liveTv.GetSeriesTimer(dto.Id, cancellationToken).ConfigureAwait(false);
+
+ info.Id = timer.ExternalId;
+ }
+
+ if (!string.IsNullOrEmpty(dto.ChannelId) && string.IsNullOrEmpty(info.ChannelId))
+ {
+ var channel = await liveTv.GetChannel(dto.ChannelId, cancellationToken).ConfigureAwait(false);
+
+ if (channel != null)
+ {
+ info.ChannelId = channel.ExternalId;
+ }
+ }
+
+ if (!string.IsNullOrEmpty(dto.ProgramId) && string.IsNullOrEmpty(info.ProgramId))
+ {
+ var program = await liveTv.GetProgram(dto.ProgramId, cancellationToken).ConfigureAwait(false);
+
+ if (program != null)
+ {
+ info.ProgramId = program.ExternalId;
+ }
+ }
+
+ if (!string.IsNullOrEmpty(dto.SeriesTimerId) && string.IsNullOrEmpty(info.SeriesTimerId))
+ {
+ var timer = await liveTv.GetSeriesTimer(dto.SeriesTimerId, cancellationToken).ConfigureAwait(false);
+
+ if (timer != null)
+ {
+ info.SeriesTimerId = timer.ExternalId;
+ }
+ }
+
+ return info;
+ }
+
+ public async Task<SeriesTimerInfo> GetSeriesTimerInfo(SeriesTimerInfoDto dto, bool isNew, ILiveTvManager liveTv, CancellationToken cancellationToken)
+ {
+ var info = new SeriesTimerInfo
+ {
+ Overview = dto.Overview,
+ EndDate = dto.EndDate,
+ Name = dto.Name,
+ StartDate = dto.StartDate,
+ PrePaddingSeconds = dto.PrePaddingSeconds,
+ PostPaddingSeconds = dto.PostPaddingSeconds,
+ IsPostPaddingRequired = dto.IsPostPaddingRequired,
+ IsPrePaddingRequired = dto.IsPrePaddingRequired,
+ Days = dto.Days,
+ Priority = dto.Priority,
+ RecordAnyChannel = dto.RecordAnyChannel,
+ RecordAnyTime = dto.RecordAnyTime,
+ RecordNewOnly = dto.RecordNewOnly,
+ ProgramId = dto.ExternalProgramId,
+ ChannelId = dto.ExternalChannelId,
+ Id = dto.ExternalId
+ };
+
+ // Convert internal server id's to external tv provider id's
+ if (!isNew && !string.IsNullOrEmpty(dto.Id) && string.IsNullOrEmpty(info.Id))
+ {
+ var timer = await liveTv.GetSeriesTimer(dto.Id, cancellationToken).ConfigureAwait(false);
+
+ info.Id = timer.ExternalId;
+ }
+
+ if (!string.IsNullOrEmpty(dto.ChannelId) && string.IsNullOrEmpty(info.ChannelId))
+ {
+ var channel = await liveTv.GetChannel(dto.ChannelId, cancellationToken).ConfigureAwait(false);
+
+ if (channel != null)
+ {
+ info.ChannelId = channel.ExternalId;
+ }
+ }
+
+ if (!string.IsNullOrEmpty(dto.ProgramId) && string.IsNullOrEmpty(info.ProgramId))
+ {
+ var program = await liveTv.GetProgram(dto.ProgramId, cancellationToken).ConfigureAwait(false);
+
+ if (program != null)
+ {
+ info.ProgramId = program.ExternalId;
+ }
+ }
+
+ return info;
+ }
+ }
+}
diff --git a/MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs b/MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs
index 185a01663..218c930df 100644
--- a/MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs
+++ b/MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs
@@ -8,7 +8,6 @@ using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.LiveTv;
using MediaBrowser.Controller.Localization;
using MediaBrowser.Controller.Persistence;
-using MediaBrowser.Model.Entities;
using MediaBrowser.Model.LiveTv;
using MediaBrowser.Model.Logging;
using MediaBrowser.Model.Querying;
@@ -30,29 +29,26 @@ namespace MediaBrowser.Server.Implementations.LiveTv
private readonly IFileSystem _fileSystem;
private readonly ILogger _logger;
private readonly IItemRepository _itemRepo;
- private readonly IImageProcessor _imageProcessor;
-
private readonly IUserManager _userManager;
+
private readonly ILocalizationManager _localization;
- private readonly IUserDataManager _userDataManager;
- private readonly IDtoService _dtoService;
+ private readonly LiveTvDtoService _tvDtoService;
private readonly List<ILiveTvService> _services = new List<ILiveTvService>();
- private List<Channel> _channels = new List<Channel>();
- private List<ProgramInfoDto> _programs = new List<ProgramInfoDto>();
+ private Dictionary<Guid, LiveTvChannel> _channels = new Dictionary<Guid, LiveTvChannel>();
+ private Dictionary<Guid, LiveTvProgram> _programs = new Dictionary<Guid, LiveTvProgram>();
- public LiveTvManager(IServerApplicationPaths appPaths, IFileSystem fileSystem, ILogger logger, IItemRepository itemRepo, IImageProcessor imageProcessor, IUserManager userManager, ILocalizationManager localization, IUserDataManager userDataManager, IDtoService dtoService)
+ public LiveTvManager(IServerApplicationPaths appPaths, IFileSystem fileSystem, ILogger logger, IItemRepository itemRepo, IImageProcessor imageProcessor, ILocalizationManager localization, IUserDataManager userDataManager, IDtoService dtoService, IUserManager userManager)
{
_appPaths = appPaths;
_fileSystem = fileSystem;
_logger = logger;
_itemRepo = itemRepo;
- _imageProcessor = imageProcessor;
- _userManager = userManager;
_localization = localization;
- _userDataManager = userDataManager;
- _dtoService = dtoService;
+ _userManager = userManager;
+
+ _tvDtoService = new LiveTvDtoService(dtoService, userDataManager, imageProcessor, logger);
}
/// <summary>
@@ -77,77 +73,23 @@ namespace MediaBrowser.Server.Implementations.LiveTv
ActiveService = _services.FirstOrDefault();
}
- /// <summary>
- /// Gets the channel info dto.
- /// </summary>
- /// <param name="info">The info.</param>
- /// <param name="user">The user.</param>
- /// <returns>ChannelInfoDto.</returns>
- public ChannelInfoDto GetChannelInfoDto(Channel info, User user)
- {
- var dto = new ChannelInfoDto
- {
- Name = info.Name,
- ServiceName = info.ServiceName,
- ChannelType = info.ChannelType,
- Number = info.ChannelNumber,
- Type = info.GetType().Name,
- Id = info.Id.ToString("N"),
- MediaType = info.MediaType
- };
-
- if (user != null)
- {
- dto.UserData = _dtoService.GetUserItemDataDto(_userDataManager.GetUserData(user.Id, info.GetUserDataKey()));
- }
-
- var imageTag = GetLogoImageTag(info);
-
- if (imageTag.HasValue)
- {
- dto.ImageTags[ImageType.Primary] = imageTag.Value;
- }
-
- return dto;
- }
-
- private Guid? GetLogoImageTag(Channel info)
- {
- var path = info.PrimaryImagePath;
-
- if (string.IsNullOrEmpty(path))
- {
- return null;
- }
-
- try
- {
- return _imageProcessor.GetImageCacheTag(info, ImageType.Primary, path);
- }
- catch (Exception ex)
- {
- _logger.ErrorException("Error getting channel image info for {0}", ex, info.Name);
- }
-
- return null;
- }
-
- public QueryResult<ChannelInfoDto> GetChannels(ChannelQuery query)
+ public Task<QueryResult<ChannelInfoDto>> GetChannels(ChannelQuery query, CancellationToken cancellationToken)
{
var user = string.IsNullOrEmpty(query.UserId) ? null : _userManager.GetUserById(new Guid(query.UserId));
- IEnumerable<Channel> channels = _channels;
+ IEnumerable<LiveTvChannel> channels = _channels.Values;
if (user != null)
{
- channels = channels.Where(i => i.IsParentalAllowed(user, _localization))
+ channels = channels
+ .Where(i => i.IsParentalAllowed(user, _localization))
.OrderBy(i =>
{
double number = 0;
- if (!string.IsNullOrEmpty(i.ChannelNumber))
+ if (!string.IsNullOrEmpty(i.ChannelInfo.Number))
{
- double.TryParse(i.ChannelNumber, out number);
+ double.TryParse(i.ChannelInfo.Number, out number);
}
return number;
@@ -159,81 +101,81 @@ namespace MediaBrowser.Server.Implementations.LiveTv
{
double number = 0;
- if (!string.IsNullOrEmpty(i.ChannelNumber))
+ if (!string.IsNullOrEmpty(i.ChannelInfo.Number))
{
- double.TryParse(i.ChannelNumber, out number);
+ double.TryParse(i.ChannelInfo.Number, out number);
}
return number;
}).ThenBy(i => i.Name)
- .Select(i => GetChannelInfoDto(i, user))
+ .Select(i => _tvDtoService.GetChannelInfoDto(i, user))
.ToArray();
- return new QueryResult<ChannelInfoDto>
+ var result = new QueryResult<ChannelInfoDto>
{
Items = returnChannels,
TotalRecordCount = returnChannels.Length
};
+
+ return Task.FromResult(result);
}
- public Channel GetChannel(string id)
+ public LiveTvChannel GetInternalChannel(string id)
{
- var guid = new Guid(id);
+ return GetInternalChannel(new Guid(id));
+ }
+
+ private LiveTvChannel GetInternalChannel(Guid id)
+ {
+ LiveTvChannel channel = null;
- return _channels.FirstOrDefault(i => i.Id == guid);
+ _channels.TryGetValue(id, out channel);
+ return channel;
}
- public ChannelInfoDto GetChannelInfoDto(string id, string userId)
+ public LiveTvProgram GetInternalProgram(string id)
{
- var channel = GetChannel(id);
+ var guid = new Guid(id);
- var user = string.IsNullOrEmpty(userId) ? null : _userManager.GetUserById(new Guid(userId));
+ LiveTvProgram obj = null;
- return channel == null ? null : GetChannelInfoDto(channel, user);
+ _programs.TryGetValue(guid, out obj);
+ return obj;
}
- private ProgramInfoDto GetProgramInfoDto(ProgramInfo program, Channel channel)
+ public async Task<LiveTvRecording> GetInternalRecording(string id, CancellationToken cancellationToken)
{
- var id = GetInternalProgramIdId(channel.ServiceName, program.Id).ToString("N");
+ var service = ActiveService;
- return new ProgramInfoDto
- {
- ChannelId = channel.Id.ToString("N"),
- Overview = program.Overview,
- EndDate = program.EndDate,
- Genres = program.Genres,
- ExternalId = program.Id,
- Id = id,
- Name = program.Name,
- ServiceName = channel.ServiceName,
- StartDate = program.StartDate,
- OfficialRating = program.OfficialRating,
- IsHD = program.IsHD,
- OriginalAirDate = program.OriginalAirDate,
- Audio = program.Audio,
- CommunityRating = program.CommunityRating,
- AspectRatio = program.AspectRatio,
- IsRepeat = program.IsRepeat,
- EpisodeTitle = program.EpisodeTitle
- };
+ var recordings = await service.GetRecordingsAsync(cancellationToken).ConfigureAwait(false);
+
+ var recording = recordings.FirstOrDefault(i => _tvDtoService.GetInternalRecordingId(service.Name, i.Id) == new Guid(id));
+
+ return await GetRecording(recording, service.Name, cancellationToken).ConfigureAwait(false);
}
- private Guid GetInternalChannelId(string serviceName, string externalChannelId, string channelName)
+ public async Task<StreamResponseInfo> GetRecordingStream(string id, CancellationToken cancellationToken)
{
- var name = serviceName + externalChannelId + channelName;
+ var service = ActiveService;
+
+ var recordings = await service.GetRecordingsAsync(cancellationToken).ConfigureAwait(false);
- return name.ToLower().GetMBId(typeof(Channel));
+ var recording = recordings.FirstOrDefault(i => _tvDtoService.GetInternalRecordingId(service.Name, i.Id) == new Guid(id));
+
+ return await service.GetRecordingStream(recording.Id, cancellationToken).ConfigureAwait(false);
}
- private Guid GetInternalProgramIdId(string serviceName, string externalProgramId)
+ public async Task<StreamResponseInfo> GetChannelStream(string id, CancellationToken cancellationToken)
{
- var name = serviceName + externalProgramId;
+ var service = ActiveService;
+
+ var channel = GetInternalChannel(id);
- return name.ToLower().GetMD5();
+ return await service.GetChannelStream(channel.ChannelInfo.Id, cancellationToken).ConfigureAwait(false);
}
- private async Task<Channel> GetChannel(ChannelInfo channelInfo, string serviceName, CancellationToken cancellationToken)
+ private async Task<LiveTvChannel> GetChannel(ChannelInfo channelInfo, string serviceName, CancellationToken cancellationToken)
{
var path = Path.Combine(_appPaths.ItemsByNamePath, "channels", _fileSystem.GetValidFilename(serviceName), _fileSystem.GetValidFilename(channelInfo.Name));
@@ -254,27 +196,27 @@ namespace MediaBrowser.Server.Implementations.LiveTv
isNew = true;
}
- var id = GetInternalChannelId(serviceName, channelInfo.Id, channelInfo.Name);
+ var id = _tvDtoService.GetInternalChannelId(serviceName, channelInfo.Id);
- var item = _itemRepo.RetrieveItem(id) as Channel;
+ var item = _itemRepo.RetrieveItem(id) as LiveTvChannel;
if (item == null)
{
- item = new Channel
+ item = new LiveTvChannel
{
Name = channelInfo.Name,
Id = id,
DateCreated = _fileSystem.GetCreationTimeUtc(fileInfo),
DateModified = _fileSystem.GetLastWriteTimeUtc(fileInfo),
- Path = path,
- ChannelId = channelInfo.Id,
- ChannelNumber = channelInfo.Number,
- ServiceName = serviceName
+ Path = path
};
isNew = true;
}
+ item.ChannelInfo = channelInfo;
+ item.ServiceName = serviceName;
+
// Set this now so we don't cause additional file system access during provider executions
item.ResetResolveArgs(fileInfo);
@@ -283,26 +225,160 @@ namespace MediaBrowser.Server.Implementations.LiveTv
return item;
}
+ private async Task<LiveTvProgram> GetProgram(ProgramInfo info, ChannelType channelType, string serviceName, CancellationToken cancellationToken)
+ {
+ var isNew = false;
+
+ var id = _tvDtoService.GetInternalProgramId(serviceName, info.Id);
+
+ var item = _itemRepo.RetrieveItem(id) as LiveTvProgram;
+
+ if (item == null)
+ {
+ item = new LiveTvProgram
+ {
+ Name = info.Name,
+ Id = id,
+ DateCreated = DateTime.UtcNow,
+ DateModified = DateTime.UtcNow
+ };
+
+ isNew = true;
+ }
+
+ item.ChannelType = channelType;
+ item.ProgramInfo = info;
+ item.ServiceName = serviceName;
+
+ await item.RefreshMetadata(cancellationToken, forceSave: isNew, resetResolveArgs: false);
+
+ return item;
+ }
+
+ private async Task<LiveTvRecording> GetRecording(RecordingInfo info, string serviceName, CancellationToken cancellationToken)
+ {
+ var isNew = false;
+
+ var id = _tvDtoService.GetInternalRecordingId(serviceName, info.Id);
+
+ var item = _itemRepo.RetrieveItem(id) as LiveTvRecording;
+
+ if (item == null)
+ {
+ item = new LiveTvRecording
+ {
+ Name = info.Name,
+ Id = id,
+ DateCreated = DateTime.UtcNow,
+ DateModified = DateTime.UtcNow
+ };
+
+ isNew = true;
+ }
+
+ item.RecordingInfo = info;
+ item.ServiceName = serviceName;
+
+ await item.RefreshMetadata(cancellationToken, forceSave: isNew, resetResolveArgs: false);
+
+ return item;
+ }
+
+ private LiveTvChannel GetChannel(LiveTvProgram program)
+ {
+ var programChannelId = program.ProgramInfo.ChannelId;
+
+ var internalProgramChannelId = _tvDtoService.GetInternalChannelId(program.ServiceName, programChannelId);
+
+ return GetInternalChannel(internalProgramChannelId);
+ }
+
+ public async Task<ProgramInfoDto> GetProgram(string id, CancellationToken cancellationToken, User user = null)
+ {
+ var program = GetInternalProgram(id);
+
+ var channel = GetChannel(program);
+
+ var channelName = channel == null ? null : channel.ChannelInfo.Name;
+
+ var dto = _tvDtoService.GetProgramInfoDto(program, channelName, user);
+
+ await AddRecordingInfo(new[] { dto }, cancellationToken).ConfigureAwait(false);
+
+ return dto;
+ }
+
public async Task<QueryResult<ProgramInfoDto>> GetPrograms(ProgramQuery query, CancellationToken cancellationToken)
{
- IEnumerable<ProgramInfoDto> programs = _programs
- .OrderBy(i => i.StartDate)
- .ThenBy(i => i.EndDate);
+ IEnumerable<LiveTvProgram> programs = _programs.Values;
if (query.ChannelIdList.Length > 0)
{
var guids = query.ChannelIdList.Select(i => new Guid(i)).ToList();
+ var serviceName = ActiveService.Name;
+
+ programs = programs.Where(i =>
+ {
+ var programChannelId = i.ProgramInfo.ChannelId;
+
+ var internalProgramChannelId = _tvDtoService.GetInternalChannelId(serviceName, programChannelId);
+
+ return guids.Contains(internalProgramChannelId);
+ });
+ }
+
+ var user = string.IsNullOrEmpty(query.UserId) ? null : _userManager.GetUserById(new Guid(query.UserId));
- programs = programs.Where(i => guids.Contains(new Guid(i.ChannelId)));
+ if (user != null)
+ {
+ programs = programs.Where(i => i.IsParentalAllowed(user, _localization));
}
- var returnArray = programs.ToArray();
+ var returnArray = programs
+ .OrderBy(i => i.ProgramInfo.StartDate)
+ .Select(i =>
+ {
+ var channel = GetChannel(i);
+
+ var channelName = channel == null ? null : channel.ChannelInfo.Name;
+
+ return _tvDtoService.GetProgramInfoDto(i, channelName, user);
+ })
+ .ToArray();
+
+ await AddRecordingInfo(returnArray, cancellationToken).ConfigureAwait(false);
- return new QueryResult<ProgramInfoDto>
+ var result = new QueryResult<ProgramInfoDto>
{
Items = returnArray,
TotalRecordCount = returnArray.Length
};
+
+ return result;
+ }
+
+ private async Task AddRecordingInfo(IEnumerable<ProgramInfoDto> programs, CancellationToken cancellationToken)
+ {
+ var timers = await ActiveService.GetTimersAsync(cancellationToken).ConfigureAwait(false);
+ var timerList = timers.ToList();
+
+ foreach (var program in programs)
+ {
+ var timer = timerList.FirstOrDefault(i => string.Equals(i.ProgramId, program.ExternalId, StringComparison.OrdinalIgnoreCase));
+
+ if (timer != null)
+ {
+ program.TimerId = _tvDtoService.GetInternalTimerId(program.ServiceName, timer.Id)
+ .ToString("N");
+
+ if (!string.IsNullOrEmpty(timer.SeriesTimerId))
+ {
+ program.SeriesTimerId = _tvDtoService.GetInternalSeriesTimerId(program.ServiceName, timer.SeriesTimerId)
+ .ToString("N");
+ }
+ }
+ }
+
}
internal async Task RefreshChannels(IProgress<double> progress, CancellationToken cancellationToken)
@@ -321,8 +397,8 @@ namespace MediaBrowser.Server.Implementations.LiveTv
var allChannels = await GetChannels(service, cancellationToken).ConfigureAwait(false);
var allChannelsList = allChannels.ToList();
- var list = new List<Channel>();
- var programs = new List<ProgramInfoDto>();
+ var list = new List<LiveTvChannel>();
+ var programs = new List<LiveTvProgram>();
var numComplete = 0;
@@ -334,7 +410,10 @@ namespace MediaBrowser.Server.Implementations.LiveTv
var channelPrograms = await service.GetProgramsAsync(channelInfo.Item2.Id, cancellationToken).ConfigureAwait(false);
- programs.AddRange(channelPrograms.Select(program => GetProgramInfoDto(program, item)));
+ var programTasks = channelPrograms.Select(program => GetProgram(program, item.ChannelInfo.ChannelType, service.Name, cancellationToken));
+ var programEntities = await Task.WhenAll(programTasks).ConfigureAwait(false);
+
+ programs.AddRange(programEntities);
list.Add(item);
}
@@ -354,8 +433,8 @@ namespace MediaBrowser.Server.Implementations.LiveTv
progress.Report(90 * percent + 10);
}
- _programs = programs;
- _channels = list;
+ _programs = programs.ToDictionary(i => i.Id);
+ _channels = list.ToDictionary(i => i.Id);
}
private async Task<IEnumerable<Tuple<string, ChannelInfo>>> GetChannels(ILiveTvService service, CancellationToken cancellationToken)
@@ -365,70 +444,73 @@ namespace MediaBrowser.Server.Implementations.LiveTv
return channels.Select(i => new Tuple<string, ChannelInfo>(service.Name, i));
}
- private async Task<IEnumerable<RecordingInfoDto>> GetRecordings(ILiveTvService service, CancellationToken cancellationToken)
+ public async Task<QueryResult<RecordingInfoDto>> GetRecordings(RecordingQuery query, CancellationToken cancellationToken)
{
+ var service = ActiveService;
+
+ var user = string.IsNullOrEmpty(query.UserId) ? null : _userManager.GetUserById(new Guid(query.UserId));
+
+ var list = new List<RecordingInfo>();
+
var recordings = await service.GetRecordingsAsync(cancellationToken).ConfigureAwait(false);
+ list.AddRange(recordings);
- return recordings.Select(i => GetRecordingInfoDto(i, service));
- }
-
- private RecordingInfoDto GetRecordingInfoDto(RecordingInfo info, ILiveTvService service)
- {
- var id = service.Name + info.ChannelId + info.Id;
- id = id.GetMD5().ToString("N");
-
- var dto = new RecordingInfoDto
- {
- ChannelName = info.ChannelName,
- Overview = info.Overview,
- EndDate = info.EndDate,
- Name = info.Name,
- StartDate = info.StartDate,
- Id = id,
- ExternalId = info.Id,
- ChannelId = GetInternalChannelId(service.Name, info.ChannelId, info.ChannelName).ToString("N"),
- Status = info.Status,
- Path = info.Path,
- Genres = info.Genres,
- IsRepeat = info.IsRepeat,
- EpisodeTitle = info.EpisodeTitle,
- ChannelType = info.ChannelType,
- MediaType = info.ChannelType == ChannelType.Radio ? MediaType.Audio : MediaType.Video,
- CommunityRating = info.CommunityRating,
- OfficialRating = info.OfficialRating,
- Audio = info.Audio,
- IsHD = info.IsHD
- };
+ if (!string.IsNullOrEmpty(query.ChannelId))
+ {
+ var guid = new Guid(query.ChannelId);
- var duration = info.EndDate - info.StartDate;
- dto.DurationMs = Convert.ToInt32(duration.TotalMilliseconds);
+ var currentServiceName = service.Name;
- if (!string.IsNullOrEmpty(info.ProgramId))
+ list = list
+ .Where(i => _tvDtoService.GetInternalChannelId(currentServiceName, i.ChannelId) == guid)
+ .ToList();
+ }
+
+ if (!string.IsNullOrEmpty(query.Id))
{
- dto.ProgramId = GetInternalProgramIdId(service.Name, info.ProgramId).ToString("N");
+ var guid = new Guid(query.Id);
+
+ var currentServiceName = service.Name;
+
+ list = list
+ .Where(i => _tvDtoService.GetInternalRecordingId(currentServiceName, i.Id) == guid)
+ .ToList();
}
- return dto;
- }
+ if (!string.IsNullOrEmpty(query.GroupId))
+ {
+ var guid = new Guid(query.GroupId);
- public async Task<QueryResult<RecordingInfoDto>> GetRecordings(RecordingQuery query, CancellationToken cancellationToken)
- {
- var list = new List<RecordingInfoDto>();
+ list = list.Where(i => GetRecordingGroupIds(i).Contains(guid))
+ .ToList();
+ }
+
+ IEnumerable<LiveTvRecording> entities = await GetEntities(list, service.Name, cancellationToken).ConfigureAwait(false);
+
+ entities = entities.OrderByDescending(i => i.RecordingInfo.StartDate);
- if (ActiveService != null)
+ if (user != null)
{
- var recordings = await GetRecordings(ActiveService, cancellationToken).ConfigureAwait(false);
+ var currentUser = user;
+ entities = entities.Where(i => i.IsParentalAllowed(currentUser, _localization));
+ }
- list.AddRange(recordings);
+ if (query.StartIndex.HasValue)
+ {
+ entities = entities.Skip(query.StartIndex.Value);
}
- if (!string.IsNullOrEmpty(query.ChannelId))
+ if (query.Limit.HasValue)
{
- list = list.Where(i => string.Equals(i.ChannelId, query.ChannelId))
- .ToList();
+ entities = entities.Take(query.Limit.Value);
}
- var returnArray = list.OrderByDescending(i => i.StartDate)
+ var returnArray = entities
+ .Select(i =>
+ {
+ var channel = string.IsNullOrEmpty(i.RecordingInfo.ChannelId) ? null : GetInternalChannel(_tvDtoService.GetInternalChannelId(service.Name, i.RecordingInfo.ChannelId));
+ return _tvDtoService.GetRecordingInfoDto(i, channel, service, user);
+ })
.ToArray();
return new QueryResult<RecordingInfoDto>
@@ -438,17 +520,25 @@ namespace MediaBrowser.Server.Implementations.LiveTv
};
}
+ private Task<LiveTvRecording[]> GetEntities(IEnumerable<RecordingInfo> recordings, string serviceName, CancellationToken cancellationToken)
+ {
+ var tasks = recordings.Select(i => GetRecording(i, serviceName, cancellationToken));
+
+ return Task.WhenAll(tasks);
+ }
+
private IEnumerable<ILiveTvService> GetServices(string serviceName, string channelId)
{
IEnumerable<ILiveTvService> services = _services;
if (string.IsNullOrEmpty(serviceName) && !string.IsNullOrEmpty(channelId))
{
- var channelIdGuid = new Guid(channelId);
+ var channel = GetInternalChannel(channelId);
- serviceName = _channels.Where(i => i.Id == channelIdGuid)
- .Select(i => i.ServiceName)
- .FirstOrDefault();
+ if (channel != null)
+ {
+ serviceName = channel.ServiceName;
+ }
}
if (!string.IsNullOrEmpty(serviceName))
@@ -459,137 +549,340 @@ namespace MediaBrowser.Server.Implementations.LiveTv
return services;
}
- public Task ScheduleRecording(string programId)
+ public async Task<QueryResult<TimerInfoDto>> GetTimers(TimerQuery query, CancellationToken cancellationToken)
{
- throw new NotImplementedException();
+ var service = ActiveService;
+ var timers = await service.GetTimersAsync(cancellationToken).ConfigureAwait(false);
+
+ if (!string.IsNullOrEmpty(query.ChannelId))
+ {
+ var guid = new Guid(query.ChannelId);
+ timers = timers.Where(i => guid == _tvDtoService.GetInternalChannelId(service.Name, i.ChannelId));
+ }
+
+ var returnArray = timers
+ .Select(i =>
+ {
+ var program = string.IsNullOrEmpty(i.ProgramId) ? null : GetInternalProgram(_tvDtoService.GetInternalProgramId(service.Name, i.ProgramId).ToString("N"));
+ var channel = string.IsNullOrEmpty(i.ChannelId) ? null : GetInternalChannel(_tvDtoService.GetInternalChannelId(service.Name, i.ChannelId));
+
+ return _tvDtoService.GetTimerInfoDto(i, service, program, channel);
+ })
+ .OrderBy(i => i.StartDate)
+ .ToArray();
+
+ return new QueryResult<TimerInfoDto>
+ {
+ Items = returnArray,
+ TotalRecordCount = returnArray.Length
+ };
}
- public async Task<QueryResult<TimerInfoDto>> GetTimers(TimerQuery query, CancellationToken cancellationToken)
+ public async Task DeleteRecording(string recordingId)
{
- var list = new List<TimerInfoDto>();
+ var recording = await GetRecording(recordingId, CancellationToken.None).ConfigureAwait(false);
- if (ActiveService != null)
+ if (recording == null)
{
- var timers = await GetTimers(ActiveService, cancellationToken).ConfigureAwait(false);
+ throw new ResourceNotFoundException(string.Format("Recording with Id {0} not found", recordingId));
+ }
+
+ var service = GetServices(recording.ServiceName, null)
+ .First();
+
+ await service.DeleteRecordingAsync(recording.ExternalId, CancellationToken.None).ConfigureAwait(false);
+ }
+
+ public async Task CancelTimer(string id)
+ {
+ var timer = await GetTimer(id, CancellationToken.None).ConfigureAwait(false);
- list.AddRange(timers);
+ if (timer == null)
+ {
+ throw new ResourceNotFoundException(string.Format("Timer with Id {0} not found", id));
}
- if (!string.IsNullOrEmpty(query.ChannelId))
+ var service = GetServices(timer.ServiceName, null)
+ .First();
+
+ await service.CancelTimerAsync(timer.ExternalId, CancellationToken.None).ConfigureAwait(false);
+ }
+
+ public async Task CancelSeriesTimer(string id)
+ {
+ var timer = await GetSeriesTimer(id, CancellationToken.None).ConfigureAwait(false);
+
+ if (timer == null)
{
- list = list.Where(i => string.Equals(i.ChannelId, query.ChannelId))
- .ToList();
+ throw new ResourceNotFoundException(string.Format("Timer with Id {0} not found", id));
}
- var returnArray = list.OrderByDescending(i => i.StartDate)
+ var service = GetServices(timer.ServiceName, null)
+ .First();
+
+ await service.CancelSeriesTimerAsync(timer.ExternalId, CancellationToken.None).ConfigureAwait(false);
+ }
+
+ public async Task<RecordingInfoDto> GetRecording(string id, CancellationToken cancellationToken, User user = null)
+ {
+ var results = await GetRecordings(new RecordingQuery
+ {
+ UserId = user == null ? null : user.Id.ToString("N"),
+ Id = id
+
+ }, cancellationToken).ConfigureAwait(false);
+
+ return results.Items.FirstOrDefault();
+ }
+
+ public async Task<TimerInfoDto> GetTimer(string id, CancellationToken cancellationToken)
+ {
+ var results = await GetTimers(new TimerQuery(), cancellationToken).ConfigureAwait(false);
+
+ return results.Items.FirstOrDefault(i => string.Equals(i.Id, id, StringComparison.CurrentCulture));
+ }
+
+ public async Task<SeriesTimerInfoDto> GetSeriesTimer(string id, CancellationToken cancellationToken)
+ {
+ var results = await GetSeriesTimers(new SeriesTimerQuery(), cancellationToken).ConfigureAwait(false);
+
+ return results.Items.FirstOrDefault(i => string.Equals(i.Id, id, StringComparison.CurrentCulture));
+ }
+
+ public async Task<QueryResult<SeriesTimerInfoDto>> GetSeriesTimers(SeriesTimerQuery query, CancellationToken cancellationToken)
+ {
+ var service = ActiveService;
+
+ var timers = await service.GetSeriesTimersAsync(cancellationToken).ConfigureAwait(false);
+
+ var returnArray = timers
+ .Select(i =>
+ {
+ string channelName = null;
+
+ if (!string.IsNullOrEmpty(i.ChannelId))
+ {
+ var internalChannelId = _tvDtoService.GetInternalChannelId(service.Name, i.ChannelId);
+ var channel = GetInternalChannel(internalChannelId);
+ channelName = channel == null ? null : channel.ChannelInfo.Name;
+ }
+
+ return _tvDtoService.GetSeriesTimerInfoDto(i, service, channelName);
+
+ })
+ .OrderByDescending(i => i.StartDate)
.ToArray();
- return new QueryResult<TimerInfoDto>
+ return new QueryResult<SeriesTimerInfoDto>
{
Items = returnArray,
TotalRecordCount = returnArray.Length
};
}
- private async Task<IEnumerable<TimerInfoDto>> GetTimers(ILiveTvService service, CancellationToken cancellationToken)
+ public Task<ChannelInfoDto> GetChannel(string id, CancellationToken cancellationToken, User user = null)
{
- var timers = await service.GetTimersAsync(cancellationToken).ConfigureAwait(false);
+ var channel = GetInternalChannel(id);
+
+ var dto = _tvDtoService.GetChannelInfoDto(channel, user);
- return timers.Select(i => GetTimerInfoDto(i, service));
+ return Task.FromResult(dto);
}
- private TimerInfoDto GetTimerInfoDto(TimerInfo info, ILiveTvService service)
+ public async Task<SeriesTimerInfoDto> GetNewTimerDefaults(CancellationToken cancellationToken)
{
- var id = service.Name + info.ChannelId + info.Id;
- id = id.GetMD5().ToString("N");
+ var service = ActiveService;
+
+ var info = await service.GetNewTimerDefaultsAsync(cancellationToken).ConfigureAwait(false);
+
+ var obj = _tvDtoService.GetSeriesTimerInfoDto(info, service, null);
+
+ obj.Id = obj.ExternalId = string.Empty;
+
+ return obj;
+ }
- var dto = new TimerInfoDto
+ public async Task<SeriesTimerInfoDto> GetNewTimerDefaults(string programId, CancellationToken cancellationToken)
+ {
+ var info = await GetNewTimerDefaults(cancellationToken).ConfigureAwait(false);
+
+ var program = await GetProgram(programId, cancellationToken).ConfigureAwait(false);
+
+ info.Days = new List<DayOfWeek>
{
- ChannelName = info.ChannelName,
- Description = info.Description,
- EndDate = info.EndDate,
- Name = info.Name,
- StartDate = info.StartDate,
- Id = id,
- ExternalId = info.Id,
- ChannelId = GetInternalChannelId(service.Name, info.ChannelId, info.ChannelName).ToString("N"),
- Status = info.Status,
- SeriesTimerId = info.SeriesTimerId,
- RequestedPostPaddingSeconds = info.RequestedPostPaddingSeconds,
- RequestedPrePaddingSeconds = info.RequestedPrePaddingSeconds,
- RequiredPostPaddingSeconds = info.RequiredPostPaddingSeconds,
- RequiredPrePaddingSeconds = info.RequiredPrePaddingSeconds
+ program.StartDate.ToLocalTime().DayOfWeek
};
- var duration = info.EndDate - info.StartDate;
- dto.DurationMs = Convert.ToInt32(duration.TotalMilliseconds);
+ info.DayPattern = _tvDtoService.GetDayPattern(info.Days);
- if (!string.IsNullOrEmpty(info.ProgramId))
- {
- dto.ProgramId = GetInternalProgramIdId(service.Name, info.ProgramId).ToString("N");
- }
+ info.Name = program.Name;
+ info.ChannelId = program.ChannelId;
+ info.ChannelName = program.ChannelName;
+ info.EndDate = program.EndDate;
+ info.StartDate = program.StartDate;
+ info.Name = program.Name;
+ info.Overview = program.Overview;
+ info.ProgramId = program.Id;
+ info.ExternalProgramId = program.ExternalId;
- return dto;
+ return info;
}
- public async Task DeleteRecording(string recordingId)
+ public async Task CreateTimer(TimerInfoDto timer, CancellationToken cancellationToken)
{
- var recordings = await GetRecordings(new RecordingQuery
- {
+ var service = string.IsNullOrEmpty(timer.ServiceName) ? ActiveService : GetServices(timer.ServiceName, null).First();
- }, CancellationToken.None).ConfigureAwait(false);
+ var info = await _tvDtoService.GetTimerInfo(timer, true, this, cancellationToken).ConfigureAwait(false);
- var recording = recordings.Items
- .FirstOrDefault(i => string.Equals(recordingId, i.Id, StringComparison.OrdinalIgnoreCase));
+ // Set priority from default values
+ var defaultValues = await service.GetNewTimerDefaultsAsync(cancellationToken).ConfigureAwait(false);
+ info.Priority = defaultValues.Priority;
- if (recording == null)
- {
- throw new ResourceNotFoundException(string.Format("Recording with Id {0} not found", recordingId));
- }
+ await service.CreateTimerAsync(info, cancellationToken).ConfigureAwait(false);
+ }
- var channel = GetChannel(recording.ChannelId);
+ public async Task CreateSeriesTimer(SeriesTimerInfoDto timer, CancellationToken cancellationToken)
+ {
+ var service = string.IsNullOrEmpty(timer.ServiceName) ? ActiveService : GetServices(timer.ServiceName, null).First();
- var service = GetServices(channel.ServiceName, null)
- .First();
+ var info = await _tvDtoService.GetSeriesTimerInfo(timer, true, this, cancellationToken).ConfigureAwait(false);
- await service.DeleteRecordingAsync(recording.ExternalId, CancellationToken.None).ConfigureAwait(false);
+ // Set priority from default values
+ var defaultValues = await service.GetNewTimerDefaultsAsync(cancellationToken).ConfigureAwait(false);
+ info.Priority = defaultValues.Priority;
+
+ await service.CreateSeriesTimerAsync(info, cancellationToken).ConfigureAwait(false);
}
- public async Task CancelTimer(string id)
+ public async Task UpdateTimer(TimerInfoDto timer, CancellationToken cancellationToken)
+ {
+ var info = await _tvDtoService.GetTimerInfo(timer, false, this, cancellationToken).ConfigureAwait(false);
+
+ var service = string.IsNullOrEmpty(timer.ServiceName) ? ActiveService : GetServices(timer.ServiceName, null).First();
+
+ await service.UpdateTimerAsync(info, cancellationToken).ConfigureAwait(false);
+ }
+
+ public async Task UpdateSeriesTimer(SeriesTimerInfoDto timer, CancellationToken cancellationToken)
+ {
+ var info = await _tvDtoService.GetSeriesTimerInfo(timer, false, this, cancellationToken).ConfigureAwait(false);
+
+ var service = string.IsNullOrEmpty(timer.ServiceName) ? ActiveService : GetServices(timer.ServiceName, null).First();
+
+ await service.UpdateSeriesTimerAsync(info, cancellationToken).ConfigureAwait(false);
+ }
+
+ private List<string> GetRecordingGroupNames(RecordingInfo recording)
{
- var timers = await GetTimers(new TimerQuery
+ var list = new List<string>();
+
+ if (recording.IsSeries)
{
+ list.Add(recording.Name);
+ }
- }, CancellationToken.None).ConfigureAwait(false);
+ if (recording.IsKids)
+ {
+ list.Add("Kids");
+ }
- var timer = timers.Items
- .FirstOrDefault(i => string.Equals(id, i.Id, StringComparison.OrdinalIgnoreCase));
+ if (recording.IsMovie)
+ {
+ list.Add("Movies");
+ }
- if (timer == null)
+ if (recording.IsNews)
{
- throw new ResourceNotFoundException(string.Format("Timer with Id {0} not found", id));
+ list.Add("News");
}
- var channel = GetChannel(timer.ChannelId);
+ if (recording.IsPremiere)
+ {
+ list.Add("Sports");
+ }
- var service = GetServices(channel.ServiceName, null)
- .First();
+ if (!recording.IsSports && !recording.IsNews && !recording.IsMovie && !recording.IsKids && !recording.IsSeries)
+ {
+ list.Add("Others");
+ }
- await service.CancelTimerAsync(timer.ExternalId, CancellationToken.None).ConfigureAwait(false);
+ return list;
}
- public async Task<RecordingInfoDto> GetRecording(string id, CancellationToken cancellationToken)
+ private List<Guid> GetRecordingGroupIds(RecordingInfo recording)
{
- var results = await GetRecordings(new RecordingQuery(), cancellationToken).ConfigureAwait(false);
-
- return results.Items.FirstOrDefault(i => string.Equals(i.Id, id, StringComparison.CurrentCulture));
+ return GetRecordingGroupNames(recording).Select(i => i.ToLower()
+ .GetMD5())
+ .ToList();
}
- public async Task<TimerInfoDto> GetTimer(string id, CancellationToken cancellationToken)
+ public async Task<QueryResult<RecordingGroupDto>> GetRecordingGroups(RecordingGroupQuery query, CancellationToken cancellationToken)
{
- var results = await GetTimers(new TimerQuery(), cancellationToken).ConfigureAwait(false);
+ var recordingResult = await GetRecordings(new RecordingQuery
+ {
+ UserId = query.UserId
- return results.Items.FirstOrDefault(i => string.Equals(i.Id, id, StringComparison.CurrentCulture));
+ }, cancellationToken).ConfigureAwait(false);
+
+ var recordings = recordingResult.Items;
+
+ var groups = new List<RecordingGroupDto>();
+
+ var series = recordings
+ .Where(i => i.IsSeries)
+ .ToLookup(i => i.Name, StringComparer.OrdinalIgnoreCase)
+ .ToList();
+
+ groups.AddRange(series.OrderBy(i => i.Key).Select(i => new RecordingGroupDto
+ {
+ Name = i.Key,
+ RecordingCount = i.Count()
+ }));
+
+ groups.Add(new RecordingGroupDto
+ {
+ Name = "Kids",
+ RecordingCount = recordings.Count(i => i.IsKids)
+ });
+
+ groups.Add(new RecordingGroupDto
+ {
+ Name = "Movies",
+ RecordingCount = recordings.Count(i => i.IsMovie)
+ });
+
+ groups.Add(new RecordingGroupDto
+ {
+ Name = "News",
+ RecordingCount = recordings.Count(i => i.IsNews)
+ });
+
+ groups.Add(new RecordingGroupDto
+ {
+ Name = "Sports",
+ RecordingCount = recordings.Count(i => i.IsSports)
+ });
+
+ groups.Add(new RecordingGroupDto
+ {
+ Name = "Others",
+ RecordingCount = recordings.Count(i => !i.IsSports && !i.IsNews && !i.IsMovie && !i.IsKids && !i.IsSeries)
+ });
+
+ groups = groups
+ .Where(i => i.RecordingCount > 0)
+ .ToList();
+
+ foreach (var group in groups)
+ {
+ group.Id = group.Name.ToLower().GetMD5().ToString("N");
+ }
+
+ return new QueryResult<RecordingGroupDto>
+ {
+ Items = groups.ToArray(),
+ TotalRecordCount = groups.Count
+ };
}
}
}
diff --git a/MediaBrowser.Server.Implementations/LiveTv/ProgramImageProvider.cs b/MediaBrowser.Server.Implementations/LiveTv/ProgramImageProvider.cs
new file mode 100644
index 000000000..7c343f77c
--- /dev/null
+++ b/MediaBrowser.Server.Implementations/LiveTv/ProgramImageProvider.cs
@@ -0,0 +1,156 @@
+using MediaBrowser.Common.IO;
+using MediaBrowser.Common.Net;
+using MediaBrowser.Controller.Configuration;
+using MediaBrowser.Controller.Entities;
+using MediaBrowser.Controller.Library;
+using MediaBrowser.Controller.LiveTv;
+using MediaBrowser.Controller.Providers;
+using MediaBrowser.Model.Entities;
+using MediaBrowser.Model.Logging;
+using MediaBrowser.Model.Net;
+using System;
+using System.IO;
+using System.Linq;
+using System.Net;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace MediaBrowser.Server.Implementations.LiveTv
+{
+ public class ProgramImageProvider : BaseMetadataProvider
+ {
+ private readonly ILiveTvManager _liveTvManager;
+ private readonly IProviderManager _providerManager;
+ private readonly IFileSystem _fileSystem;
+ private readonly IHttpClient _httpClient;
+
+ public ProgramImageProvider(ILogManager logManager, IServerConfigurationManager configurationManager, ILiveTvManager liveTvManager, IProviderManager providerManager, IFileSystem fileSystem, IHttpClient httpClient)
+ : base(logManager, configurationManager)
+ {
+ _liveTvManager = liveTvManager;
+ _providerManager = providerManager;
+ _fileSystem = fileSystem;
+ _httpClient = httpClient;
+ }
+
+ public override bool Supports(BaseItem item)
+ {
+ return item is LiveTvProgram;
+ }
+
+ protected override bool NeedsRefreshInternal(BaseItem item, BaseProviderInfo providerInfo)
+ {
+ return !item.HasImage(ImageType.Primary);
+ }
+
+ public override async Task<bool> FetchAsync(BaseItem item, bool force, BaseProviderInfo providerInfo, CancellationToken cancellationToken)
+ {
+ if (item.HasImage(ImageType.Primary))
+ {
+ SetLastRefreshed(item, DateTime.UtcNow, providerInfo);
+ return true;
+ }
+
+ var changed = true;
+
+ try
+ {
+ changed = await DownloadImage((LiveTvProgram)item, cancellationToken).ConfigureAwait(false);
+ }
+ catch (HttpException ex)
+ {
+ // Don't fail the provider on a 404
+ if (!ex.StatusCode.HasValue || ex.StatusCode.Value != HttpStatusCode.NotFound)
+ {
+ throw;
+ }
+ }
+
+ if (changed)
+ {
+ SetLastRefreshed(item, DateTime.UtcNow, providerInfo);
+ }
+
+ return changed;
+ }
+
+ private async Task<bool> DownloadImage(LiveTvProgram item, CancellationToken cancellationToken)
+ {
+ var programInfo = item.ProgramInfo;
+
+ Stream imageStream = null;
+ string contentType = null;
+
+ if (!string.IsNullOrEmpty(programInfo.ImagePath))
+ {
+ contentType = "image/" + Path.GetExtension(programInfo.ImagePath).ToLower();
+ imageStream = _fileSystem.GetFileStream(programInfo.ImagePath, FileMode.Open, FileAccess.Read, FileShare.Read, true);
+ }
+ else if (!string.IsNullOrEmpty(programInfo.ImageUrl))
+ {
+ var options = new HttpRequestOptions
+ {
+ CancellationToken = cancellationToken,
+ Url = programInfo.ImageUrl
+ };
+
+ var response = await _httpClient.GetResponse(options).ConfigureAwait(false);
+
+ if (!response.ContentType.StartsWith("image/", StringComparison.OrdinalIgnoreCase))
+ {
+ Logger.Error("Provider did not return an image content type.");
+ return false;
+ }
+
+ imageStream = response.Content;
+ contentType = response.ContentType;
+ }
+ else if (programInfo.HasImage ?? true)
+ {
+ var service = _liveTvManager.Services.FirstOrDefault(i => string.Equals(i.Name, item.ServiceName, StringComparison.OrdinalIgnoreCase));
+
+ if (service != null)
+ {
+ try
+ {
+ var response = await service.GetProgramImageAsync(programInfo.Id, programInfo.ChannelId, cancellationToken).ConfigureAwait(false);
+
+ if (response != null)
+ {
+ imageStream = response.Stream;
+ contentType = response.MimeType;
+ }
+ }
+ catch (NotImplementedException)
+ {
+ return false;
+ }
+ }
+ }
+
+ if (imageStream != null)
+ {
+ // Dummy up the original url
+ var url = item.ServiceName + programInfo.Id;
+
+ await _providerManager.SaveImage(item, imageStream, contentType, ImageType.Primary, null, url, cancellationToken).ConfigureAwait(false);
+ return true;
+ }
+
+ return false;
+ }
+
+ public override MetadataProviderPriority Priority
+ {
+ get { return MetadataProviderPriority.Second; }
+ }
+
+ public override ItemUpdateType ItemUpdateType
+ {
+ get
+ {
+ return ItemUpdateType.ImageUpdate;
+ }
+ }
+ }
+}
diff --git a/MediaBrowser.Server.Implementations/LiveTv/RecordingImageProvider.cs b/MediaBrowser.Server.Implementations/LiveTv/RecordingImageProvider.cs
new file mode 100644
index 000000000..0b5ec285e
--- /dev/null
+++ b/MediaBrowser.Server.Implementations/LiveTv/RecordingImageProvider.cs
@@ -0,0 +1,156 @@
+using System;
+using System.IO;
+using System.Linq;
+using System.Net;
+using System.Threading;
+using System.Threading.Tasks;
+using MediaBrowser.Common.IO;
+using MediaBrowser.Common.Net;
+using MediaBrowser.Controller.Configuration;
+using MediaBrowser.Controller.Entities;
+using MediaBrowser.Controller.Library;
+using MediaBrowser.Controller.LiveTv;
+using MediaBrowser.Controller.Providers;
+using MediaBrowser.Model.Entities;
+using MediaBrowser.Model.Logging;
+using MediaBrowser.Model.Net;
+
+namespace MediaBrowser.Server.Implementations.LiveTv
+{
+ public class RecordingImageProvider : BaseMetadataProvider
+ {
+ private readonly ILiveTvManager _liveTvManager;
+ private readonly IProviderManager _providerManager;
+ private readonly IFileSystem _fileSystem;
+ private readonly IHttpClient _httpClient;
+
+ public RecordingImageProvider(ILogManager logManager, IServerConfigurationManager configurationManager, ILiveTvManager liveTvManager, IProviderManager providerManager, IFileSystem fileSystem, IHttpClient httpClient)
+ : base(logManager, configurationManager)
+ {
+ _liveTvManager = liveTvManager;
+ _providerManager = providerManager;
+ _fileSystem = fileSystem;
+ _httpClient = httpClient;
+ }
+
+ public override bool Supports(BaseItem item)
+ {
+ return item is LiveTvRecording;
+ }
+
+ protected override bool NeedsRefreshInternal(BaseItem item, BaseProviderInfo providerInfo)
+ {
+ return !item.HasImage(ImageType.Primary);
+ }
+
+ public override async Task<bool> FetchAsync(BaseItem item, bool force, BaseProviderInfo providerInfo, CancellationToken cancellationToken)
+ {
+ if (item.HasImage(ImageType.Primary))
+ {
+ SetLastRefreshed(item, DateTime.UtcNow, providerInfo);
+ return true;
+ }
+
+ var changed = true;
+
+ try
+ {
+ changed = await DownloadImage((LiveTvRecording)item, cancellationToken).ConfigureAwait(false);
+ }
+ catch (HttpException ex)
+ {
+ // Don't fail the provider on a 404
+ if (!ex.StatusCode.HasValue || ex.StatusCode.Value != HttpStatusCode.NotFound)
+ {
+ throw;
+ }
+ }
+
+ if (changed)
+ {
+ SetLastRefreshed(item, DateTime.UtcNow, providerInfo);
+ }
+
+ return changed;
+ }
+
+ private async Task<bool> DownloadImage(LiveTvRecording item, CancellationToken cancellationToken)
+ {
+ var recordingInfo = item.RecordingInfo;
+
+ Stream imageStream = null;
+ string contentType = null;
+
+ if (!string.IsNullOrEmpty(recordingInfo.ImagePath))
+ {
+ contentType = "image/" + Path.GetExtension(recordingInfo.ImagePath).ToLower();
+ imageStream = _fileSystem.GetFileStream(recordingInfo.ImagePath, FileMode.Open, FileAccess.Read, FileShare.Read, true);
+ }
+ else if (!string.IsNullOrEmpty(recordingInfo.ImageUrl))
+ {
+ var options = new HttpRequestOptions
+ {
+ CancellationToken = cancellationToken,
+ Url = recordingInfo.ImageUrl
+ };
+
+ var response = await _httpClient.GetResponse(options).ConfigureAwait(false);
+
+ if (!response.ContentType.StartsWith("image/", StringComparison.OrdinalIgnoreCase))
+ {
+ Logger.Error("Provider did not return an image content type.");
+ return false;
+ }
+
+ imageStream = response.Content;
+ contentType = response.ContentType;
+ }
+ else if (recordingInfo.HasImage ?? true)
+ {
+ var service = _liveTvManager.Services.FirstOrDefault(i => string.Equals(i.Name, item.ServiceName, StringComparison.OrdinalIgnoreCase));
+
+ if (service != null)
+ {
+ try
+ {
+ var response = await service.GetRecordingImageAsync(recordingInfo.Id, cancellationToken).ConfigureAwait(false);
+
+ if (response != null)
+ {
+ imageStream = response.Stream;
+ contentType = response.MimeType;
+ }
+ }
+ catch (NotImplementedException)
+ {
+ return false;
+ }
+ }
+ }
+
+ if (imageStream != null)
+ {
+ // Dummy up the original url
+ var url = item.ServiceName + recordingInfo.Id;
+
+ await _providerManager.SaveImage(item, imageStream, contentType, ImageType.Primary, null, url, cancellationToken).ConfigureAwait(false);
+ return true;
+ }
+
+ return false;
+ }
+
+ public override MetadataProviderPriority Priority
+ {
+ get { return MetadataProviderPriority.Second; }
+ }
+
+ public override ItemUpdateType ItemUpdateType
+ {
+ get
+ {
+ return ItemUpdateType.ImageUpdate;
+ }
+ }
+ }
+}
diff --git a/MediaBrowser.Server.Implementations/Localization/Ratings/au.txt b/MediaBrowser.Server.Implementations/Localization/Ratings/au.txt
index a68a3f5f3..fa60f5305 100644
--- a/MediaBrowser.Server.Implementations/Localization/Ratings/au.txt
+++ b/MediaBrowser.Server.Implementations/Localization/Ratings/au.txt
@@ -1,6 +1,8 @@
AU-G,1
AU-PG,5
AU-M,6
+AU-MA15+,7
AU-M15+,8
AU-R18+,9
-AU-X18+,10 \ No newline at end of file
+AU-X18+,10
+AU-RC,11
diff --git a/MediaBrowser.Server.Implementations/Localization/Ratings/be.txt b/MediaBrowser.Server.Implementations/Localization/Ratings/be.txt
new file mode 100644
index 000000000..99a53f664
--- /dev/null
+++ b/MediaBrowser.Server.Implementations/Localization/Ratings/be.txt
@@ -0,0 +1,6 @@
+BE-AL,1
+BE-MG6,2
+BE-6,3
+BE-9,5
+BE-12,6
+BE-16,8 \ No newline at end of file
diff --git a/MediaBrowser.Server.Implementations/Localization/Ratings/de.txt b/MediaBrowser.Server.Implementations/Localization/Ratings/de.txt
index 571b6eba9..e1c3639cb 100644
--- a/MediaBrowser.Server.Implementations/Localization/Ratings/de.txt
+++ b/MediaBrowser.Server.Implementations/Localization/Ratings/de.txt
@@ -2,4 +2,5 @@ DE-FSK0,1
DE-FSK6+,5
DE-FSK12+,7
DE-FSK16+,8
+DE-16,8
DE-FSK18+,9
diff --git a/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj b/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj
index 39966f0d7..86dd0bc75 100644
--- a/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj
+++ b/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj
@@ -9,12 +9,12 @@
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>MediaBrowser.Server.Implementations</RootNamespace>
<AssemblyName>MediaBrowser.Server.Implementations</AssemblyName>
- <TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<SolutionDir Condition="$(SolutionDir) == '' Or $(SolutionDir) == '*Undefined*'">..\</SolutionDir>
<RestorePackages>true</RestorePackages>
<ProductVersion>10.0.0</ProductVersion>
<SchemaVersion>2.0</SchemaVersion>
+ <TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
@@ -24,6 +24,7 @@
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
+ <TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
@@ -32,33 +33,33 @@
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
+ <TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
+ </PropertyGroup>
+ <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release Mono|AnyCPU' ">
+ <DebugType>pdbonly</DebugType>
+ <Optimize>true</Optimize>
+ <OutputPath>bin\Release Mono\</OutputPath>
+ <DefineConstants>TRACE</DefineConstants>
+ <ErrorReport>prompt</ErrorReport>
+ <WarningLevel>4</WarningLevel>
+ <TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
</PropertyGroup>
<ItemGroup>
<Reference Include="Alchemy">
<HintPath>..\packages\Alchemy.2.2.1\lib\net40\Alchemy.dll</HintPath>
</Reference>
- <Reference Include="ServiceStack, Version=4.0.0.0, Culture=neutral, processorArchitecture=MSIL">
- <SpecificVersion>False</SpecificVersion>
- <HintPath>..\ThirdParty\ServiceStack\ServiceStack.dll</HintPath>
- </Reference>
- <Reference Include="ServiceStack.Client, Version=4.0.0.0, Culture=neutral, processorArchitecture=MSIL">
- <SpecificVersion>False</SpecificVersion>
- <HintPath>..\ThirdParty\ServiceStack\ServiceStack.Client.dll</HintPath>
- </Reference>
- <Reference Include="ServiceStack.Common, Version=4.0.0.0, Culture=neutral, processorArchitecture=MSIL">
- <SpecificVersion>False</SpecificVersion>
- <HintPath>..\ThirdParty\ServiceStack\ServiceStack.Common.dll</HintPath>
+ <Reference Include="ServiceStack.Api.Swagger">
+ <HintPath>..\ThirdParty\ServiceStack\ServiceStack.Api.Swagger.dll</HintPath>
</Reference>
- <Reference Include="ServiceStack.Interfaces, Version=4.0.0.0, Culture=neutral, PublicKeyToken=e06fbc6124f57c43, processorArchitecture=MSIL">
+ <Reference Include="System" />
+ <Reference Include="System.Core" />
+ <Reference Include="System.Data.SQLite, Version=1.0.90.0, Culture=neutral, PublicKeyToken=db937bc2d44ff139, processorArchitecture=x86">
<SpecificVersion>False</SpecificVersion>
- <HintPath>..\ThirdParty\ServiceStack\ServiceStack.Interfaces.dll</HintPath>
+ <HintPath>..\packages\System.Data.SQLite.x86.1.0.90.0\lib\net45\System.Data.SQLite.dll</HintPath>
</Reference>
- <Reference Include="ServiceStack.Text, Version=4.0.0.0, Culture=neutral, processorArchitecture=MSIL">
- <SpecificVersion>False</SpecificVersion>
- <HintPath>..\ThirdParty\ServiceStack\ServiceStack.Text.dll</HintPath>
+ <Reference Include="System.Data.SQLite.Linq">
+ <HintPath>..\packages\System.Data.SQLite.x86.1.0.90.0\lib\net45\System.Data.SQLite.Linq.dll</HintPath>
</Reference>
- <Reference Include="System" />
- <Reference Include="System.Core" />
<Reference Include="System.Drawing" />
<Reference Include="Microsoft.CSharp" />
<Reference Include="System.Data" />
@@ -70,9 +71,26 @@
<Reference Include="BDInfo">
<HintPath>..\packages\MediaBrowser.BdInfo.1.0.0.5\lib\net20\BDInfo.dll</HintPath>
</Reference>
- <Reference Include="System.Data.SQLite">
- <HintPath>..\packages\System.Data.SQLite.x86.1.0.89.0\lib\net45\System.Data.SQLite.dll</HintPath>
+ <Reference Include="System.Data.SQLite" Condition=" '$(ConfigurationName)' == 'Release Mono' ">
+ <SpecificVersion>False</SpecificVersion>
+ <HintPath>..\ThirdParty\System.Data.SQLite.ManagedOnly\x86\1.0.90.0\net40\System.Data.SQLite.dll</HintPath>
+ </Reference>
+ <Reference Include="ServiceStack">
+ <HintPath>..\ThirdParty\ServiceStack\ServiceStack.dll</HintPath>
+ </Reference>
+ <Reference Include="ServiceStack.Client">
+ <HintPath>..\ThirdParty\ServiceStack\ServiceStack.Client.dll</HintPath>
</Reference>
+ <Reference Include="ServiceStack.Common">
+ <HintPath>..\ThirdParty\ServiceStack\ServiceStack.Common.dll</HintPath>
+ </Reference>
+ <Reference Include="ServiceStack.Interfaces">
+ <HintPath>..\ThirdParty\ServiceStack\ServiceStack.Interfaces.dll</HintPath>
+ </Reference>
+ <Reference Include="ServiceStack.Text">
+ <HintPath>..\ThirdParty\ServiceStack.Text\ServiceStack.Text.dll</HintPath>
+ </Reference>
+ <Reference Include="Mono.Posix" Condition=" '$(ConfigurationName)' == 'Release Mono' " />
</ItemGroup>
<ItemGroup>
<Compile Include="..\SharedVersion.cs">
@@ -139,7 +157,10 @@
<Compile Include="Library\Validators\StudiosValidator.cs" />
<Compile Include="Library\Validators\YearsPostScanTask.cs" />
<Compile Include="LiveTv\ChannelImageProvider.cs" />
+ <Compile Include="LiveTv\LiveTvDtoService.cs" />
<Compile Include="LiveTv\LiveTvManager.cs" />
+ <Compile Include="LiveTv\ProgramImageProvider.cs" />
+ <Compile Include="LiveTv\RecordingImageProvider.cs" />
<Compile Include="LiveTv\RefreshChannelsScheduledTask.cs" />
<Compile Include="Localization\LocalizationManager.cs" />
<Compile Include="MediaEncoder\MediaEncoder.cs" />
@@ -153,12 +174,14 @@
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Providers\ImageSaver.cs" />
<Compile Include="Providers\ProviderManager.cs" />
+ <Compile Include="Roku\RokuControllerFactory.cs" />
<Compile Include="ScheduledTasks\PeopleValidationTask.cs" />
<Compile Include="ScheduledTasks\ChapterImagesTask.cs" />
<Compile Include="ScheduledTasks\RefreshMediaLibraryTask.cs" />
<Compile Include="ServerApplicationPaths.cs" />
<Compile Include="ServerManager\ServerManager.cs" />
<Compile Include="ServerManager\WebSocketConnection.cs" />
+ <Compile Include="Roku\RokuSessionController.cs" />
<Compile Include="Session\SessionManager.cs">
<SubType>Code</SubType>
</Compile>
@@ -169,6 +192,7 @@
<Compile Include="Sorting\AlbumArtistComparer.cs" />
<Compile Include="Sorting\AlbumComparer.cs" />
<Compile Include="Sorting\AlbumCountComparer.cs" />
+ <Compile Include="Sorting\AlphanumComparator.cs" />
<Compile Include="Sorting\ArtistComparer.cs" />
<Compile Include="Sorting\BudgetComparer.cs" />
<Compile Include="Sorting\CommunityRatingComparer.cs" />
@@ -244,8 +268,101 @@
<ItemGroup>
<EmbeddedResource Include="Localization\Ratings\ca.txt" />
</ItemGroup>
+ <ItemGroup>
+ <Content Include="..\ThirdParty\ServiceStack\swagger-ui\css\highlight.default.css">
+ <Link>swagger-ui\css\highlight.default.css</Link>
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="..\ThirdParty\ServiceStack\swagger-ui\css\screen.css">
+ <Link>swagger-ui\css\screen.css</Link>
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="..\ThirdParty\ServiceStack\swagger-ui\images\logo_small.png">
+ <Link>swagger-ui\images\logo_small.png</Link>
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="..\ThirdParty\ServiceStack\swagger-ui\images\pet_store_api.png">
+ <Link>swagger-ui\images\pet_store_api.png</Link>
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="..\ThirdParty\ServiceStack\swagger-ui\images\throbber.gif">
+ <Link>swagger-ui\images\throbber.gif</Link>
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="..\ThirdParty\ServiceStack\swagger-ui\images\wordnik_api.png">
+ <Link>swagger-ui\images\wordnik_api.png</Link>
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="..\ThirdParty\ServiceStack\swagger-ui\index.html">
+ <Link>swagger-ui\index.html</Link>
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="..\ThirdParty\ServiceStack\swagger-ui\lib\backbone-min.js">
+ <Link>swagger-ui\lib\backbone-min.js</Link>
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="..\ThirdParty\ServiceStack\swagger-ui\lib\handlebars-1.0.0.js">
+ <Link>swagger-ui\lib\handlebars-1.0.0.js</Link>
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="..\ThirdParty\ServiceStack\swagger-ui\lib\highlight.7.3.pack.js">
+ <Link>swagger-ui\lib\highlight.7.3.pack.js</Link>
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="..\ThirdParty\ServiceStack\swagger-ui\lib\jquery-1.8.0.min.js">
+ <Link>swagger-ui\lib\jquery-1.8.0.min.js</Link>
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="..\ThirdParty\ServiceStack\swagger-ui\lib\jquery.ba-bbq.min.js">
+ <Link>swagger-ui\lib\jquery.ba-bbq.min.js</Link>
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="..\ThirdParty\ServiceStack\swagger-ui\lib\jquery.slideto.min.js">
+ <Link>swagger-ui\lib\jquery.slideto.min.js</Link>
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="..\ThirdParty\ServiceStack\swagger-ui\lib\jquery.wiggle.min.js">
+ <Link>swagger-ui\lib\jquery.wiggle.min.js</Link>
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="..\ThirdParty\ServiceStack\swagger-ui\lib\shred.bundle.js">
+ <Link>swagger-ui\lib\shred.bundle.js</Link>
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="..\ThirdParty\ServiceStack\swagger-ui\lib\shred\content.js">
+ <Link>swagger-ui\lib\shred\content.js</Link>
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="..\ThirdParty\ServiceStack\swagger-ui\lib\swagger.js">
+ <Link>swagger-ui\lib\swagger.js</Link>
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="..\ThirdParty\ServiceStack\swagger-ui\lib\underscore-min.js">
+ <Link>swagger-ui\lib\underscore-min.js</Link>
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="..\ThirdParty\ServiceStack\swagger-ui\swagger-ui.js">
+ <Link>swagger-ui\swagger-ui.js</Link>
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="..\ThirdParty\ServiceStack\swagger-ui\swagger-ui.min.js">
+ <Link>swagger-ui\swagger-ui.min.js</Link>
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <EmbeddedResource Include="Localization\Ratings\be.txt" />
+ </ItemGroup>
+ <ItemGroup>
+ <Content Include="..\ThirdParty\System.Data.SQLite.ManagedOnly\x86\1.0.90.0\net40\System.Data.SQLite.dll" Condition=" '$(ConfigurationName)' == 'Release Mono'">
+ <Link>System.Data.SQLite.dll</Link>
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="..\ThirdParty\System.Data.SQLite.ManagedOnly\x86\1.0.90.0\net40\System.Data.SQLite.Linq.dll" Condition=" '$(ConfigurationName)' == 'Release Mono'">
+ <Link>System.Data.SQLite.Linq.dll</Link>
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ </ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
- <Import Project="$(SolutionDir)\.nuget\nuget.targets" />
+ <Import Project="$(SolutionDir)\.nuget\nuget.targets" Condition=" '$(ConfigurationName)' != 'Release Mono' " />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.
<Target Name="BeforeBuild">
diff --git a/MediaBrowser.Server.Implementations/MediaEncoder/MediaEncoder.cs b/MediaBrowser.Server.Implementations/MediaEncoder/MediaEncoder.cs
index 2224c657f..187dc19bf 100644
--- a/MediaBrowser.Server.Implementations/MediaEncoder/MediaEncoder.cs
+++ b/MediaBrowser.Server.Implementations/MediaEncoder/MediaEncoder.cs
@@ -427,8 +427,8 @@ namespace MediaBrowser.Server.Implementations.MediaEncoder
throw new ArgumentNullException("outputPath");
}
- var slowSeekParam = GetSlowSeekCommandLineParameter(offset);
- var fastSeekParam = GetFastSeekCommandLineParameter(offset);
+
+ var slowSeekParam = offset.TotalSeconds > 0 ? " -ss " + offset.TotalSeconds.ToString(UsCulture) : string.Empty;
var encodingParam = string.IsNullOrEmpty(language) ? string.Empty :
GetSubtitleLanguageEncodingParam(language) + " ";
@@ -444,12 +444,7 @@ namespace MediaBrowser.Server.Implementations.MediaEncoder
UseShellExecute = false,
FileName = FFMpegPath,
Arguments =
- string.Format("{0}{1}-i \"{2}\"{3} \"{4}\"",
- fastSeekParam,
- encodingParam,
- inputPath,
- slowSeekParam,
- outputPath),
+ string.Format("{0} -i \"{1}\" {2} -c:s ass \"{3}\"", encodingParam, inputPath, slowSeekParam, outputPath),
WindowStyle = ProcessWindowStyle.Hidden,
ErrorDialog = false
@@ -459,6 +454,7 @@ namespace MediaBrowser.Server.Implementations.MediaEncoder
_logger.Debug("{0} {1}", process.StartInfo.FileName, process.StartInfo.Arguments);
var logFilePath = Path.Combine(_appPaths.LogDirectoryPath, "ffmpeg-sub-convert-" + Guid.NewGuid() + ".txt");
+ Directory.CreateDirectory(Path.GetDirectoryName(logFilePath));
var logFileStream = _fileSystem.GetFileStream(logFilePath, FileMode.Create, FileAccess.Write, FileShare.Read, true);
@@ -665,7 +661,9 @@ namespace MediaBrowser.Server.Implementations.MediaEncoder
throw new ArgumentNullException("outputPath");
}
- var slowSeekParam = offset.TotalSeconds > 0 ? " -ss " + offset.TotalSeconds.ToString(UsCulture) : string.Empty;
+
+ var slowSeekParam = GetSlowSeekCommandLineParameter(offset);
+ var fastSeekParam = GetFastSeekCommandLineParameter(offset);
var process = new Process
{
@@ -678,7 +676,7 @@ namespace MediaBrowser.Server.Implementations.MediaEncoder
RedirectStandardError = true,
FileName = FFMpegPath,
- Arguments = string.Format("-i {0}{1} -map 0:{2} -an -vn -c:s ass \"{3}\"", inputPath, slowSeekParam, subtitleStreamIndex, outputPath),
+ Arguments = string.Format(" {0} -i {1} {2} -map 0:{3} -an -vn -c:s ass \"{4}\"", fastSeekParam, inputPath, slowSeekParam, subtitleStreamIndex, outputPath),
WindowStyle = ProcessWindowStyle.Hidden,
ErrorDialog = false
}
@@ -687,6 +685,7 @@ namespace MediaBrowser.Server.Implementations.MediaEncoder
_logger.Debug("{0} {1}", process.StartInfo.FileName, process.StartInfo.Arguments);
var logFilePath = Path.Combine(_appPaths.LogDirectoryPath, "ffmpeg-sub-extract-" + Guid.NewGuid() + ".txt");
+ Directory.CreateDirectory(Path.GetDirectoryName(logFilePath));
var logFileStream = _fileSystem.GetFileStream(logFilePath, FileMode.Create, FileAccess.Write, FileShare.Read, true);
diff --git a/MediaBrowser.Server.Implementations/Persistence/SqliteChapterRepository.cs b/MediaBrowser.Server.Implementations/Persistence/SqliteChapterRepository.cs
index 075ef4239..77b993205 100644
--- a/MediaBrowser.Server.Implementations/Persistence/SqliteChapterRepository.cs
+++ b/MediaBrowser.Server.Implementations/Persistence/SqliteChapterRepository.cs
@@ -32,6 +32,8 @@ namespace MediaBrowser.Server.Implementations.Persistence
_logger = logManager.GetLogger(GetType().Name);
}
+ private SqliteShrinkMemoryTimer _shrinkMemoryTimer;
+
/// <summary>
/// Opens the connection to the database
/// </summary>
@@ -52,6 +54,8 @@ namespace MediaBrowser.Server.Implementations.Persistence
_connection.RunQueries(queries, _logger);
PrepareStatements();
+
+ _shrinkMemoryTimer = new SqliteShrinkMemoryTimer(_connection, _writeLock, _logger);
}
/// <summary>
@@ -282,6 +286,12 @@ namespace MediaBrowser.Server.Implementations.Persistence
{
lock (_disposeLock)
{
+ if (_shrinkMemoryTimer != null)
+ {
+ _shrinkMemoryTimer.Dispose();
+ _shrinkMemoryTimer = null;
+ }
+
if (_connection != null)
{
if (_connection.IsOpen())
diff --git a/MediaBrowser.Server.Implementations/Persistence/SqliteExtensions.cs b/MediaBrowser.Server.Implementations/Persistence/SqliteExtensions.cs
index 9836de735..0b3d5f784 100644
--- a/MediaBrowser.Server.Implementations/Persistence/SqliteExtensions.cs
+++ b/MediaBrowser.Server.Implementations/Persistence/SqliteExtensions.cs
@@ -1,11 +1,7 @@
using MediaBrowser.Model.Logging;
using System;
using System.Data;
-#if __MonoCS__
-using Mono.Data.Sqlite;
-#else
using System.Data.SQLite;
-#endif
using System.IO;
using System.Threading.Tasks;
@@ -140,18 +136,6 @@ namespace MediaBrowser.Server.Implementations.Persistence
logger.Info("Opening {0}", dbPath);
- #if __MonoCS__
- var connectionstr = new SqliteConnectionStringBuilder
- {
- PageSize = 4096,
- CacheSize = 4096,
- SyncMode = SynchronizationModes.Normal,
- DataSource = dbPath,
- JournalMode = SQLiteJournalModeEnum.Off
- };
-
- var connection = new SqliteConnection(connectionstr.ConnectionString);
-#else
var connectionstr = new SQLiteConnectionStringBuilder
{
PageSize = 4096,
@@ -162,7 +146,7 @@ namespace MediaBrowser.Server.Implementations.Persistence
};
var connection = new SQLiteConnection(connectionstr.ConnectionString);
-#endif
+
await connection.OpenAsync().ConfigureAwait(false);
return connection;
diff --git a/MediaBrowser.Server.Implementations/Persistence/SqliteProviderInfoRepository.cs b/MediaBrowser.Server.Implementations/Persistence/SqliteProviderInfoRepository.cs
index 5d836b090..9971c7460 100644
--- a/MediaBrowser.Server.Implementations/Persistence/SqliteProviderInfoRepository.cs
+++ b/MediaBrowser.Server.Implementations/Persistence/SqliteProviderInfoRepository.cs
@@ -25,6 +25,8 @@ namespace MediaBrowser.Server.Implementations.Persistence
_logger = logManager.GetLogger(GetType().Name);
}
+ private SqliteShrinkMemoryTimer _shrinkMemoryTimer;
+
/// <summary>
/// Opens the connection to the database
/// </summary>
@@ -50,6 +52,8 @@ namespace MediaBrowser.Server.Implementations.Persistence
_connection.RunQueries(queries, _logger);
PrepareStatements();
+
+ _shrinkMemoryTimer = new SqliteShrinkMemoryTimer(_connection, _writeLock, _logger);
}
private static readonly string[] SaveColumns =
@@ -240,6 +244,12 @@ namespace MediaBrowser.Server.Implementations.Persistence
{
lock (_disposeLock)
{
+ if (_shrinkMemoryTimer != null)
+ {
+ _shrinkMemoryTimer.Dispose();
+ _shrinkMemoryTimer = null;
+ }
+
if (_connection != null)
{
if (_connection.IsOpen())
diff --git a/MediaBrowser.Server.Implementations/Persistence/SqliteUserDataRepository.cs b/MediaBrowser.Server.Implementations/Persistence/SqliteUserDataRepository.cs
index d3f9100b1..4e388b4b0 100644
--- a/MediaBrowser.Server.Implementations/Persistence/SqliteUserDataRepository.cs
+++ b/MediaBrowser.Server.Implementations/Persistence/SqliteUserDataRepository.cs
@@ -55,6 +55,8 @@ namespace MediaBrowser.Server.Implementations.Persistence
_logger = logManager.GetLogger(GetType().Name);
}
+ private SqliteShrinkMemoryTimer _shrinkMemoryTimer;
+
/// <summary>
/// Opens the connection to the database
/// </summary>
@@ -78,6 +80,8 @@ namespace MediaBrowser.Server.Implementations.Persistence
};
_connection.RunQueries(queries, _logger);
+
+ _shrinkMemoryTimer = new SqliteShrinkMemoryTimer(_connection, _writeLock, _logger);
}
/// <summary>
@@ -267,6 +271,12 @@ namespace MediaBrowser.Server.Implementations.Persistence
{
lock (_disposeLock)
{
+ if (_shrinkMemoryTimer != null)
+ {
+ _shrinkMemoryTimer.Dispose();
+ _shrinkMemoryTimer = null;
+ }
+
if (_connection != null)
{
if (_connection.IsOpen())
diff --git a/MediaBrowser.Server.Implementations/Providers/ImageSaver.cs b/MediaBrowser.Server.Implementations/Providers/ImageSaver.cs
index 8d88b66a0..0346aba97 100644
--- a/MediaBrowser.Server.Implementations/Providers/ImageSaver.cs
+++ b/MediaBrowser.Server.Implementations/Providers/ImageSaver.cs
@@ -118,6 +118,8 @@ namespace MediaBrowser.Server.Implementations.Providers
imageIndex = hasScreenshots.ScreenshotImagePaths.Count;
}
+ var index = imageIndex ?? 0;
+
var paths = GetSavePaths(item, type, imageIndex, mimeType, saveLocally);
// If there are more than one output paths, the stream will need to be seekable
@@ -132,7 +134,7 @@ namespace MediaBrowser.Server.Implementations.Providers
source = memoryStream;
}
- var currentPath = GetCurrentImagePath(item, type, imageIndex);
+ var currentPath = GetCurrentImagePath(item, type, index);
using (source)
{
@@ -152,7 +154,7 @@ namespace MediaBrowser.Server.Implementations.Providers
}
}
- // Set the path into the BaseItem
+ // Set the path into the item
SetImagePath(item, type, imageIndex, paths[0], sourceUrl);
// Delete the current path
@@ -257,27 +259,9 @@ namespace MediaBrowser.Server.Implementations.Providers
/// or
/// imageIndex
/// </exception>
- private string GetCurrentImagePath(BaseItem item, ImageType type, int? imageIndex)
+ private string GetCurrentImagePath(IHasImages item, ImageType type, int imageIndex)
{
- switch (type)
- {
- case ImageType.Screenshot:
-
- var hasScreenshots = (IHasScreenshots)item;
- if (!imageIndex.HasValue)
- {
- throw new ArgumentNullException("imageIndex");
- }
- return hasScreenshots.ScreenshotImagePaths.Count > imageIndex.Value ? hasScreenshots.ScreenshotImagePaths[imageIndex.Value] : null;
- case ImageType.Backdrop:
- if (!imageIndex.HasValue)
- {
- throw new ArgumentNullException("imageIndex");
- }
- return item.BackdropImagePaths.Count > imageIndex.Value ? item.BackdropImagePaths[imageIndex.Value] : null;
- default:
- return item.GetImage(type);
- }
+ return item.GetImagePath(type, imageIndex);
}
/// <summary>
@@ -336,7 +320,7 @@ namespace MediaBrowser.Server.Implementations.Providers
}
break;
default:
- item.SetImage(type, path);
+ item.SetImagePath(type, path);
break;
}
}
@@ -472,6 +456,11 @@ namespace MediaBrowser.Server.Implementations.Providers
if (imageIndex.Value == 0)
{
+ if (item.IsInMixedFolder)
+ {
+ return new[] { GetSavePathForItemInMixedFolder(item, type, "fanart", extension) };
+ }
+
if (season != null && item.IndexNumber.HasValue)
{
var seriesFolder = season.SeriesPath;
@@ -493,6 +482,11 @@ namespace MediaBrowser.Server.Implementations.Providers
var outputIndex = imageIndex.Value;
+ if (item.IsInMixedFolder)
+ {
+ return new[] { GetSavePathForItemInMixedFolder(item, type, "fanart" + outputIndex.ToString(UsCulture), extension) };
+ }
+
var extraFanartFilename = GetBackdropSaveFilename(item.BackdropImagePaths, "fanart", "fanart", outputIndex);
return new[]
@@ -526,7 +520,7 @@ namespace MediaBrowser.Server.Implementations.Providers
return new[] { Path.Combine(seasonFolder, imageFilename) };
}
- if (item.IsInMixedFolder)
+ if (item.IsInMixedFolder || item is MusicVideo)
{
return new[] { GetSavePathForItemInMixedFolder(item, type, string.Empty, extension) };
}
@@ -569,6 +563,13 @@ namespace MediaBrowser.Server.Implementations.Providers
return new[] { Path.Combine(seriesFolder, imageFilename) };
}
+
+ if (item.IsInMixedFolder)
+ {
+ return new[] { GetSavePathForItemInMixedFolder(item, type, "landscape", extension) };
+ }
+
+ return new[] { Path.Combine(item.MetaLocation, "landscape" + extension) };
}
// All other paths are the same
@@ -583,7 +584,7 @@ namespace MediaBrowser.Server.Implementations.Providers
/// <param name="imageFilename">The image filename.</param>
/// <param name="extension">The extension.</param>
/// <returns>System.String.</returns>
- private string GetSavePathForItemInMixedFolder(BaseItem item, ImageType type, string imageFilename, string extension)
+ private string GetSavePathForItemInMixedFolder(IHasImages item, ImageType type, string imageFilename, string extension)
{
if (type == ImageType.Primary)
{
diff --git a/MediaBrowser.Server.Implementations/Providers/ProviderManager.cs b/MediaBrowser.Server.Implementations/Providers/ProviderManager.cs
index 511092759..cbfd7d74d 100644
--- a/MediaBrowser.Server.Implementations/Providers/ProviderManager.cs
+++ b/MediaBrowser.Server.Implementations/Providers/ProviderManager.cs
@@ -2,6 +2,7 @@
using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Entities;
+using MediaBrowser.Controller.Entities.Movies;
using MediaBrowser.Controller.IO;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Persistence;
@@ -104,9 +105,8 @@ namespace MediaBrowser.Server.Implementations.Providers
cancellationToken.ThrowIfCancellationRequested();
var enableInternetProviders = ConfigurationManager.Configuration.EnableInternetProviders;
- var excludeTypes = ConfigurationManager.Configuration.InternetProviderExcludeTypes;
- var providerHistories = item.DateLastSaved == DateTime.MinValue ?
+ var providerHistories = item.DateLastSaved == default(DateTime) ?
new List<BaseProviderInfo>() :
_itemRepo.GetProviderHistory(item.Id).ToList();
@@ -132,15 +132,6 @@ namespace MediaBrowser.Server.Implementations.Providers
continue;
}
- // Skip if internet provider and this type is not allowed
- if (provider.RequiresInternet &&
- enableInternetProviders &&
- excludeTypes.Length > 0 &&
- excludeTypes.Contains(item.GetType().Name, StringComparer.OrdinalIgnoreCase))
- {
- continue;
- }
-
// Put this check below the await because the needs refresh of the next tier of providers may depend on the previous ones running
// This is the case for the fan art provider which depends on the movie and tv providers having run before them
if (provider.RequiresInternet && item.DontFetchMeta && provider.EnforceDontFetchMetadata)
@@ -387,35 +378,45 @@ namespace MediaBrowser.Server.Implementations.Providers
providers = providers.Where(i => string.Equals(i.Name, providerName, StringComparison.OrdinalIgnoreCase));
}
- var preferredLanguage = ConfigurationManager.Configuration.PreferredMetadataLanguage;
+ var preferredLanguage = item.GetPreferredMetadataLanguage();
+
+ var tasks = providers.Select(i => GetImages(item, cancellationToken, i, preferredLanguage, type));
- var tasks = providers.Select(i => Task.Run(async () =>
+ var results = await Task.WhenAll(tasks).ConfigureAwait(false);
+
+ return results.SelectMany(i => i);
+ }
+
+ /// <summary>
+ /// Gets the images.
+ /// </summary>
+ /// <param name="item">The item.</param>
+ /// <param name="cancellationToken">The cancellation token.</param>
+ /// <param name="i">The i.</param>
+ /// <param name="preferredLanguage">The preferred language.</param>
+ /// <param name="type">The type.</param>
+ /// <returns>Task{IEnumerable{RemoteImageInfo}}.</returns>
+ private async Task<IEnumerable<RemoteImageInfo>> GetImages(BaseItem item, CancellationToken cancellationToken, IImageProvider i, string preferredLanguage, ImageType? type = null)
+ {
+ try
{
- try
+ if (type.HasValue)
{
- if (type.HasValue)
- {
- var result = await i.GetImages(item, type.Value, cancellationToken).ConfigureAwait(false);
+ var result = await i.GetImages(item, type.Value, cancellationToken).ConfigureAwait(false);
- return FilterImages(result, preferredLanguage);
- }
- else
- {
- var result = await i.GetAllImages(item, cancellationToken).ConfigureAwait(false);
- return FilterImages(result, preferredLanguage);
- }
+ return FilterImages(result, preferredLanguage);
}
- catch (Exception ex)
+ else
{
- _logger.ErrorException("{0} failed in GetImages for type {1}", ex, i.GetType().Name, item.GetType().Name);
- return new List<RemoteImageInfo>();
+ var result = await i.GetAllImages(item, cancellationToken).ConfigureAwait(false);
+ return FilterImages(result, preferredLanguage);
}
-
- }, cancellationToken));
-
- var results = await Task.WhenAll(tasks).ConfigureAwait(false);
-
- return results.SelectMany(i => i);
+ }
+ catch (Exception ex)
+ {
+ _logger.ErrorException("{0} failed in GetImages for type {1}", ex, i.GetType().Name, item.GetType().Name);
+ return new List<RemoteImageInfo>();
+ }
}
private IEnumerable<RemoteImageInfo> FilterImages(IEnumerable<RemoteImageInfo> images, string preferredLanguage)
diff --git a/MediaBrowser.Server.Implementations/Roku/RokuControllerFactory.cs b/MediaBrowser.Server.Implementations/Roku/RokuControllerFactory.cs
new file mode 100644
index 000000000..71f70421a
--- /dev/null
+++ b/MediaBrowser.Server.Implementations/Roku/RokuControllerFactory.cs
@@ -0,0 +1,32 @@
+using MediaBrowser.Common.Net;
+using MediaBrowser.Controller;
+using MediaBrowser.Controller.Session;
+using MediaBrowser.Model.Serialization;
+using System;
+
+namespace MediaBrowser.Server.Implementations.Roku
+{
+ public class RokuControllerFactory : ISessionControllerFactory
+ {
+ private readonly IHttpClient _httpClient;
+ private readonly IJsonSerializer _json;
+ private readonly IServerApplicationHost _appHost;
+
+ public RokuControllerFactory(IHttpClient httpClient, IJsonSerializer json, IServerApplicationHost appHost)
+ {
+ _httpClient = httpClient;
+ _json = json;
+ _appHost = appHost;
+ }
+
+ public ISessionController GetSessionController(SessionInfo session)
+ {
+ if (string.Equals(session.Client, "roku", StringComparison.OrdinalIgnoreCase))
+ {
+ return new RokuSessionController(_httpClient, _json, _appHost, session);
+ }
+
+ return null;
+ }
+ }
+}
diff --git a/MediaBrowser.Server.Implementations/Roku/RokuSessionController.cs b/MediaBrowser.Server.Implementations/Roku/RokuSessionController.cs
new file mode 100644
index 000000000..b9e8b7950
--- /dev/null
+++ b/MediaBrowser.Server.Implementations/Roku/RokuSessionController.cs
@@ -0,0 +1,149 @@
+using MediaBrowser.Common.Net;
+using MediaBrowser.Controller;
+using MediaBrowser.Controller.Session;
+using MediaBrowser.Model.Entities;
+using MediaBrowser.Model.Net;
+using MediaBrowser.Model.Serialization;
+using MediaBrowser.Model.Session;
+using MediaBrowser.Model.System;
+using System;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace MediaBrowser.Server.Implementations.Roku
+{
+ public class RokuSessionController : ISessionController
+ {
+ private readonly IHttpClient _httpClient;
+ private readonly IJsonSerializer _json;
+ private readonly IServerApplicationHost _appHost;
+
+ public SessionInfo Session { get; private set; }
+
+ public RokuSessionController(IHttpClient httpClient, IJsonSerializer json, IServerApplicationHost appHost, SessionInfo session)
+ {
+ _httpClient = httpClient;
+ _json = json;
+ _appHost = appHost;
+ Session = session;
+ }
+
+ public bool SupportsMediaRemoteControl
+ {
+ get { return true; }
+ }
+
+ public bool IsSessionActive
+ {
+ get
+ {
+ return (DateTime.UtcNow - Session.LastActivityDate).TotalMinutes <= 10;
+ }
+ }
+
+ public Task SendSystemCommand(SystemCommand command, CancellationToken cancellationToken)
+ {
+ return SendCommand(new WebSocketMessage<string>
+ {
+ MessageType = "SystemCommand",
+ Data = command.ToString()
+
+ }, cancellationToken);
+ }
+
+ public Task SendMessageCommand(MessageCommand command, CancellationToken cancellationToken)
+ {
+ return SendCommand(new WebSocketMessage<MessageCommand>
+ {
+ MessageType = "MessageCommand",
+ Data = command
+
+ }, cancellationToken);
+ }
+
+ public Task SendPlayCommand(PlayRequest command, CancellationToken cancellationToken)
+ {
+ return SendCommand(new WebSocketMessage<PlayRequest>
+ {
+ MessageType = "Play",
+ Data = command
+
+ }, cancellationToken);
+ }
+
+ public Task SendBrowseCommand(BrowseRequest command, CancellationToken cancellationToken)
+ {
+ return SendCommand(new WebSocketMessage<BrowseRequest>
+ {
+ MessageType = "Browse",
+ Data = command
+
+ }, cancellationToken);
+ }
+
+ public Task SendPlaystateCommand(PlaystateRequest command, CancellationToken cancellationToken)
+ {
+ return SendCommand(new WebSocketMessage<PlaystateRequest>
+ {
+ MessageType = "Playstate",
+ Data = command
+
+ }, cancellationToken);
+ }
+
+ public Task SendLibraryUpdateInfo(LibraryUpdateInfo info, CancellationToken cancellationToken)
+ {
+ // Roku probably won't care about this
+ return Task.FromResult(true);
+ }
+
+ public Task SendRestartRequiredNotification(CancellationToken cancellationToken)
+ {
+ return SendCommand(new WebSocketMessage<SystemInfo>
+ {
+ MessageType = "RestartRequired",
+ Data = _appHost.GetSystemInfo()
+
+ }, cancellationToken);
+ }
+
+ public Task SendUserDataChangeInfo(UserDataChangeInfo info, CancellationToken cancellationToken)
+ {
+ // Roku probably won't care about this
+ return Task.FromResult(true);
+ }
+
+ public Task SendServerShutdownNotification(CancellationToken cancellationToken)
+ {
+ return SendCommand(new WebSocketMessage<string>
+ {
+ MessageType = "ServerShuttingDown",
+ Data = string.Empty
+
+ }, cancellationToken);
+ }
+
+ public Task SendServerRestartNotification(CancellationToken cancellationToken)
+ {
+ return SendCommand(new WebSocketMessage<string>
+ {
+ MessageType = "ServerRestarting",
+ Data = string.Empty
+
+ }, cancellationToken);
+ }
+
+ private Task SendCommand(object obj, CancellationToken cancellationToken)
+ {
+ var json = _json.SerializeToString(obj);
+
+ return _httpClient.Post(new HttpRequestOptions
+ {
+ Url = "http://" + Session.RemoteEndPoint + "/mb/remotecontrol",
+ CancellationToken = cancellationToken,
+ RequestContent = json,
+ RequestContentType = "application/json"
+ });
+ }
+ }
+}
diff --git a/MediaBrowser.Server.Implementations/ScheduledTasks/ChapterImagesTask.cs b/MediaBrowser.Server.Implementations/ScheduledTasks/ChapterImagesTask.cs
index 9270b879a..2608ac172 100644
--- a/MediaBrowser.Server.Implementations/ScheduledTasks/ChapterImagesTask.cs
+++ b/MediaBrowser.Server.Implementations/ScheduledTasks/ChapterImagesTask.cs
@@ -1,7 +1,7 @@
using MediaBrowser.Common.ScheduledTasks;
-using MediaBrowser.Controller;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library;
+using MediaBrowser.Controller.MediaInfo;
using MediaBrowser.Controller.Persistence;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Logging;
@@ -21,10 +21,6 @@ namespace MediaBrowser.Server.Implementations.ScheduledTasks
class ChapterImagesTask : IScheduledTask
{
/// <summary>
- /// The _kernel
- /// </summary>
- private readonly Kernel _kernel;
- /// <summary>
/// The _logger
/// </summary>
private readonly ILogger _logger;
@@ -48,13 +44,11 @@ namespace MediaBrowser.Server.Implementations.ScheduledTasks
/// <summary>
/// Initializes a new instance of the <see cref="ChapterImagesTask" /> class.
/// </summary>
- /// <param name="kernel">The kernel.</param>
/// <param name="logManager">The log manager.</param>
/// <param name="libraryManager">The library manager.</param>
/// <param name="itemRepo">The item repo.</param>
- public ChapterImagesTask(Kernel kernel, ILogManager logManager, ILibraryManager libraryManager, IItemRepository itemRepo)
+ public ChapterImagesTask(ILogManager logManager, ILibraryManager libraryManager, IItemRepository itemRepo)
{
- _kernel = kernel;
_logger = logManager.GetLogger(GetType().Name);
_libraryManager = libraryManager;
_itemRepo = itemRepo;
@@ -102,13 +96,13 @@ namespace MediaBrowser.Server.Implementations.ScheduledTasks
// Limit to video files to reduce changes of ffmpeg crash dialog
foreach (var item in newItems
.Where(i => i.LocationType == LocationType.FileSystem && i.VideoType == VideoType.VideoFile && string.IsNullOrEmpty(i.PrimaryImagePath) && i.DefaultVideoStreamIndex.HasValue)
- .Take(2))
+ .Take(1))
{
try
{
var chapters = _itemRepo.GetChapters(item.Id).ToList();
- await _kernel.FFMpegManager.PopulateChapterImages(item, chapters, true, true, CancellationToken.None);
+ await FFMpegManager.Instance.PopulateChapterImages(item, chapters, true, true, CancellationToken.None);
}
catch (Exception ex)
{
@@ -123,8 +117,6 @@ namespace MediaBrowser.Server.Implementations.ScheduledTasks
/// <returns>IEnumerable{BaseTaskTrigger}.</returns>
public IEnumerable<ITaskTrigger> GetDefaultTriggers()
{
- // IMPORTANT: Make sure to update the dashboard "wizardsettings" page if this default ever changes
-
return new ITaskTrigger[]
{
new DailyTrigger { TimeOfDay = TimeSpan.FromHours(4) }
@@ -145,7 +137,7 @@ namespace MediaBrowser.Server.Implementations.ScheduledTasks
var numComplete = 0;
- var failHistoryPath = Path.Combine(_kernel.FFMpegManager.VideoImagesDataPath, "failures.txt");
+ var failHistoryPath = Path.Combine(FFMpegManager.Instance.ChapterImagesPath, "failures.txt");
List<string> previouslyFailedImages;
@@ -174,7 +166,7 @@ namespace MediaBrowser.Server.Implementations.ScheduledTasks
var chapters = _itemRepo.GetChapters(video.Id).ToList();
- var success = await _kernel.FFMpegManager.PopulateChapterImages(video, chapters, extract, true, cancellationToken);
+ var success = await FFMpegManager.Instance.PopulateChapterImages(video, chapters, extract, true, cancellationToken);
if (!success)
{
@@ -203,7 +195,6 @@ namespace MediaBrowser.Server.Implementations.ScheduledTasks
{
get
{
- // IMPORTANT: Make sure to update the dashboard "wizardsettings" page if this name ever changes
return "Chapter image extraction";
}
}
diff --git a/MediaBrowser.Server.Implementations/ServerManager/ServerManager.cs b/MediaBrowser.Server.Implementations/ServerManager/ServerManager.cs
index 553aae285..a2dfb51d2 100644
--- a/MediaBrowser.Server.Implementations/ServerManager/ServerManager.cs
+++ b/MediaBrowser.Server.Implementations/ServerManager/ServerManager.cs
@@ -243,7 +243,7 @@ namespace MediaBrowser.Server.Implementations.ServerManager
/// <param name="dataFunction">The function that generates the data to send, if there are any connected clients</param>
public void SendWebSocketMessage<T>(string messageType, Func<T> dataFunction)
{
- Task.Run(async () => await SendWebSocketMessageAsync(messageType, dataFunction, CancellationToken.None).ConfigureAwait(false));
+ SendWebSocketMessageAsync(messageType, dataFunction, CancellationToken.None);
}
/// <summary>
diff --git a/MediaBrowser.Server.Implementations/Session/SessionManager.cs b/MediaBrowser.Server.Implementations/Session/SessionManager.cs
index 3a07d33a6..c42f33ec3 100644
--- a/MediaBrowser.Server.Implementations/Session/SessionManager.cs
+++ b/MediaBrowser.Server.Implementations/Session/SessionManager.cs
@@ -39,7 +39,7 @@ namespace MediaBrowser.Server.Implementations.Session
private readonly ILogger _logger;
private readonly ILibraryManager _libraryManager;
-
+
/// <summary>
/// Gets or sets the configuration manager.
/// </summary>
@@ -65,6 +65,8 @@ namespace MediaBrowser.Server.Implementations.Session
/// </summary>
public event EventHandler<PlaybackProgressEventArgs> PlaybackStopped;
+ private IEnumerable<ISessionControllerFactory> _sessionFactories = new List<ISessionControllerFactory>();
+
/// <summary>
/// Initializes a new instance of the <see cref="SessionManager" /> class.
/// </summary>
@@ -83,6 +85,15 @@ namespace MediaBrowser.Server.Implementations.Session
}
/// <summary>
+ /// Adds the parts.
+ /// </summary>
+ /// <param name="sessionFactories">The session factories.</param>
+ public void AddParts(IEnumerable<ISessionControllerFactory> sessionFactories)
+ {
+ _sessionFactories = sessionFactories.ToList();
+ }
+
+ /// <summary>
/// Gets all connections.
/// </summary>
/// <value>All connections.</value>
@@ -98,11 +109,12 @@ namespace MediaBrowser.Server.Implementations.Session
/// <param name="appVersion">The app version.</param>
/// <param name="deviceId">The device id.</param>
/// <param name="deviceName">Name of the device.</param>
+ /// <param name="remoteEndPoint">The remote end point.</param>
/// <param name="user">The user.</param>
/// <returns>Task.</returns>
- /// <exception cref="System.UnauthorizedAccessException"></exception>
/// <exception cref="System.ArgumentNullException">user</exception>
- public async Task<SessionInfo> LogSessionActivity(string clientType, string appVersion, string deviceId, string deviceName, User user)
+ /// <exception cref="System.UnauthorizedAccessException"></exception>
+ public async Task<SessionInfo> LogSessionActivity(string clientType, string appVersion, string deviceId, string deviceName, string remoteEndPoint, User user)
{
if (string.IsNullOrEmpty(clientType))
{
@@ -128,7 +140,7 @@ namespace MediaBrowser.Server.Implementations.Session
var activityDate = DateTime.UtcNow;
- var session = GetSessionInfo(clientType, appVersion, deviceId, deviceName, user);
+ var session = GetSessionInfo(clientType, appVersion, deviceId, deviceName, remoteEndPoint, user);
session.LastActivityDate = activityDate;
@@ -196,9 +208,10 @@ namespace MediaBrowser.Server.Implementations.Session
/// <param name="appVersion">The app version.</param>
/// <param name="deviceId">The device id.</param>
/// <param name="deviceName">Name of the device.</param>
+ /// <param name="remoteEndPoint">The remote end point.</param>
/// <param name="user">The user.</param>
/// <returns>SessionInfo.</returns>
- private SessionInfo GetSessionInfo(string clientType, string appVersion, string deviceId, string deviceName, User user)
+ private SessionInfo GetSessionInfo(string clientType, string appVersion, string deviceId, string deviceName, string remoteEndPoint, User user)
{
var key = clientType + deviceId + appVersion;
@@ -212,6 +225,14 @@ namespace MediaBrowser.Server.Implementations.Session
connection.DeviceName = deviceName;
connection.User = user;
+ connection.RemoteEndPoint = remoteEndPoint;
+
+ if (connection.SessionController == null)
+ {
+ connection.SessionController = _sessionFactories
+ .Select(i => i.GetSessionController(connection))
+ .FirstOrDefault(i => i != null);
+ }
return connection;
}
@@ -335,7 +356,7 @@ namespace MediaBrowser.Server.Implementations.Session
{
throw new ArgumentException("PlaybackStopInfo.SessionId cannot be Guid.Empty");
}
-
+
if (info.PositionTicks.HasValue && info.PositionTicks.Value < 0)
{
throw new ArgumentOutOfRangeException("positionTicks");
@@ -497,7 +518,7 @@ namespace MediaBrowser.Server.Implementations.Session
{
throw new ArgumentException("Virtual items are not playable.");
}
-
+
if (command.PlayCommand != PlayCommand.PlayNow)
{
if (items.Any(i => !session.QueueableMediaTypes.Contains(i.MediaType, StringComparer.OrdinalIgnoreCase)))
@@ -505,7 +526,7 @@ namespace MediaBrowser.Server.Implementations.Session
throw new ArgumentException(string.Format("Session {0} is unable to queue the requested media type.", session.Id));
}
}
-
+
return session.SessionController.SendPlayCommand(command, cancellationToken);
}
@@ -587,7 +608,7 @@ namespace MediaBrowser.Server.Implementations.Session
_logger.ErrorException("Error in SendServerShutdownNotification.", ex);
}
- }));
+ }, cancellationToken));
return Task.WhenAll(tasks);
}
@@ -612,7 +633,7 @@ namespace MediaBrowser.Server.Implementations.Session
_logger.ErrorException("Error in SendServerRestartNotification.", ex);
}
- }));
+ }, cancellationToken));
return Task.WhenAll(tasks);
}
diff --git a/MediaBrowser.Server.Implementations/Session/SessionWebSocketListener.cs b/MediaBrowser.Server.Implementations/Session/SessionWebSocketListener.cs
index 41cb7eb6b..08481f09f 100644
--- a/MediaBrowser.Server.Implementations/Session/SessionWebSocketListener.cs
+++ b/MediaBrowser.Server.Implementations/Session/SessionWebSocketListener.cs
@@ -104,7 +104,7 @@ namespace MediaBrowser.Server.Implementations.Session
{
_logger.Debug("Logging session activity");
- await _sessionManager.LogSessionActivity(client, version, deviceId, deviceName, null).ConfigureAwait(false);
+ await _sessionManager.LogSessionActivity(client, version, deviceId, deviceName, message.Connection.RemoteEndPoint, null).ConfigureAwait(false);
session = _sessionManager.Sessions
.FirstOrDefault(i => string.Equals(i.DeviceId, deviceId) &&
@@ -114,7 +114,13 @@ namespace MediaBrowser.Server.Implementations.Session
if (session != null)
{
- var controller = new WebSocketController(session, _appHost);
+ var controller = session.SessionController as WebSocketController;
+
+ if (controller == null)
+ {
+ controller = new WebSocketController(session, _appHost);
+ }
+
controller.Sockets.Add(message.Connection);
session.SessionController = controller;
diff --git a/MediaBrowser.Server.Implementations/Sorting/AiredEpisodeOrderComparer.cs b/MediaBrowser.Server.Implementations/Sorting/AiredEpisodeOrderComparer.cs
index 76971342a..8707f2e5b 100644
--- a/MediaBrowser.Server.Implementations/Sorting/AiredEpisodeOrderComparer.cs
+++ b/MediaBrowser.Server.Implementations/Sorting/AiredEpisodeOrderComparer.cs
@@ -62,7 +62,7 @@ namespace MediaBrowser.Server.Implementations.Sorting
return CompareEpisodes(x, y);
}
- if (!isXSpecial && isYSpecial)
+ if (!isXSpecial)
{
return CompareEpisodeToSpecial(x, y);
}
@@ -80,16 +80,36 @@ namespace MediaBrowser.Server.Implementations.Sorting
return xSeason.CompareTo(ySeason);
}
- // Now we know they have the same season
+ // Special comes after episode
+ if (y.AirsAfterSeasonNumber.HasValue)
+ {
+ return -1;
+ }
+
+ var yEpisode = y.AirsBeforeEpisodeNumber;
+
+ // Special comes before the season
+ if (!yEpisode.HasValue)
+ {
+ return 1;
+ }
// Compare episode number
+ var xEpisode = x.IndexNumber;
+
+ if (!xEpisode.HasValue)
+ {
+ // Can't really compare if this happens
+ return 0;
+ }
- // Add 1 to to non-specials to account for AirsBeforeEpisodeNumber
- var xEpisode = x.IndexNumber ?? -1;
- xEpisode++;
- var yEpisode = y.AirsBeforeEpisodeNumber ?? 10000;
+ // Special comes before episode
+ if (xEpisode.Value == yEpisode.Value)
+ {
+ return 1;
+ }
- return xEpisode.CompareTo(yEpisode);
+ return xEpisode.Value.CompareTo(yEpisode.Value);
}
private int CompareSpecials(Episode x, Episode y)
diff --git a/MediaBrowser.Server.Implementations/Sorting/AlphanumComparator.cs b/MediaBrowser.Server.Implementations/Sorting/AlphanumComparator.cs
new file mode 100644
index 000000000..39a68b3f6
--- /dev/null
+++ b/MediaBrowser.Server.Implementations/Sorting/AlphanumComparator.cs
@@ -0,0 +1,119 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace MediaBrowser.Server.Implementations.Sorting
+{
+ public class AlphanumComparator : IComparer<string>
+ {
+ private enum ChunkType { Alphanumeric, Numeric };
+
+ private static bool InChunk(char ch, char otherCh)
+ {
+ var type = ChunkType.Alphanumeric;
+
+ if (char.IsDigit(otherCh))
+ {
+ type = ChunkType.Numeric;
+ }
+
+ if ((type == ChunkType.Alphanumeric && char.IsDigit(ch))
+ || (type == ChunkType.Numeric && !char.IsDigit(ch)))
+ {
+ return false;
+ }
+
+ return true;
+ }
+
+ public static int CompareValues(string s1, string s2)
+ {
+ if (s1 == null || s2 == null)
+ {
+ return 0;
+ }
+
+ int thisMarker = 0, thisNumericChunk = 0;
+ int thatMarker = 0, thatNumericChunk = 0;
+
+ while ((thisMarker < s1.Length) || (thatMarker < s2.Length))
+ {
+ if (thisMarker >= s1.Length)
+ {
+ return -1;
+ }
+ else if (thatMarker >= s2.Length)
+ {
+ return 1;
+ }
+ char thisCh = s1[thisMarker];
+ char thatCh = s2[thatMarker];
+
+ StringBuilder thisChunk = new StringBuilder();
+ StringBuilder thatChunk = new StringBuilder();
+
+ while ((thisMarker < s1.Length) && (thisChunk.Length == 0 || InChunk(thisCh, thisChunk[0])))
+ {
+ thisChunk.Append(thisCh);
+ thisMarker++;
+
+ if (thisMarker < s1.Length)
+ {
+ thisCh = s1[thisMarker];
+ }
+ }
+
+ while ((thatMarker < s2.Length) && (thatChunk.Length == 0 || InChunk(thatCh, thatChunk[0])))
+ {
+ thatChunk.Append(thatCh);
+ thatMarker++;
+
+ if (thatMarker < s2.Length)
+ {
+ thatCh = s2[thatMarker];
+ }
+ }
+
+ int result = 0;
+ // If both chunks contain numeric characters, sort them numerically
+ if (char.IsDigit(thisChunk[0]) && char.IsDigit(thatChunk[0]))
+ {
+ if (!int.TryParse(thisChunk.ToString(), out thisNumericChunk))
+ {
+ return 0;
+ }
+ if (!int.TryParse(thatChunk.ToString(), out thatNumericChunk))
+ {
+ return 0;
+ }
+
+ if (thisNumericChunk < thatNumericChunk)
+ {
+ result = -1;
+ }
+
+ if (thisNumericChunk > thatNumericChunk)
+ {
+ result = 1;
+ }
+ }
+ else
+ {
+ result = thisChunk.ToString().CompareTo(thatChunk.ToString());
+ }
+
+ if (result != 0)
+ {
+ return result;
+ }
+ }
+
+ return 0;
+ }
+
+ public int Compare(string x, string y)
+ {
+ return CompareValues(x, y);
+ }
+ }
+}
diff --git a/MediaBrowser.Server.Implementations/Sorting/NameComparer.cs b/MediaBrowser.Server.Implementations/Sorting/NameComparer.cs
index 49f86c485..83b1b2d16 100644
--- a/MediaBrowser.Server.Implementations/Sorting/NameComparer.cs
+++ b/MediaBrowser.Server.Implementations/Sorting/NameComparer.cs
@@ -1,7 +1,6 @@
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Sorting;
using MediaBrowser.Model.Querying;
-using System;
namespace MediaBrowser.Server.Implementations.Sorting
{
@@ -18,7 +17,7 @@ namespace MediaBrowser.Server.Implementations.Sorting
/// <returns>System.Int32.</returns>
public int Compare(BaseItem x, BaseItem y)
{
- return string.Compare(x.Name, y.Name, StringComparison.CurrentCultureIgnoreCase);
+ return AlphanumComparator.CompareValues(x.Name, y.Name);
}
/// <summary>
diff --git a/MediaBrowser.Server.Implementations/Sorting/SeriesSortNameComparer.cs b/MediaBrowser.Server.Implementations/Sorting/SeriesSortNameComparer.cs
index 4efc3218b..09612a49c 100644
--- a/MediaBrowser.Server.Implementations/Sorting/SeriesSortNameComparer.cs
+++ b/MediaBrowser.Server.Implementations/Sorting/SeriesSortNameComparer.cs
@@ -16,7 +16,7 @@ namespace MediaBrowser.Server.Implementations.Sorting
/// <returns>System.Int32.</returns>
public int Compare(BaseItem x, BaseItem y)
{
- return string.Compare(GetValue(x), GetValue(y), StringComparison.CurrentCultureIgnoreCase);
+ return AlphanumComparator.CompareValues(GetValue(x), GetValue(y));
}
private string GetValue(BaseItem item)
diff --git a/MediaBrowser.Server.Implementations/Sorting/SortNameComparer.cs b/MediaBrowser.Server.Implementations/Sorting/SortNameComparer.cs
index 873753a2b..e635cfbe5 100644
--- a/MediaBrowser.Server.Implementations/Sorting/SortNameComparer.cs
+++ b/MediaBrowser.Server.Implementations/Sorting/SortNameComparer.cs
@@ -1,7 +1,6 @@
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Sorting;
using MediaBrowser.Model.Querying;
-using System;
namespace MediaBrowser.Server.Implementations.Sorting
{
@@ -18,7 +17,7 @@ namespace MediaBrowser.Server.Implementations.Sorting
/// <returns>System.Int32.</returns>
public int Compare(BaseItem x, BaseItem y)
{
- return string.Compare(x.SortName, y.SortName, StringComparison.CurrentCultureIgnoreCase);
+ return AlphanumComparator.CompareValues(x.SortName, y.SortName);
}
/// <summary>
diff --git a/MediaBrowser.Server.Implementations/WebSocket/AlchemyServer.cs b/MediaBrowser.Server.Implementations/WebSocket/AlchemyServer.cs
index 8be35071a..e46dab23e 100644
--- a/MediaBrowser.Server.Implementations/WebSocket/AlchemyServer.cs
+++ b/MediaBrowser.Server.Implementations/WebSocket/AlchemyServer.cs
@@ -4,6 +4,9 @@ using MediaBrowser.Common.Net;
using MediaBrowser.Model.Logging;
using System;
using System.Net;
+#if __MonoCS__
+using Mono.Unix.Native;
+#endif
namespace MediaBrowser.Server.Implementations.WebSocket
{
@@ -66,6 +69,20 @@ namespace MediaBrowser.Server.Implementations.WebSocket
TimeOut = TimeSpan.FromHours(24)
};
+ #if __MonoCS__
+ //Linux: port below 1024 require root or cap_net_bind_service
+ if (Environment.OSVersion.Platform == PlatformID.Unix || Environment.OSVersion.Platform == PlatformID.MacOSX)
+ {
+ if (Syscall.getuid() == 0)
+ {
+ WebSocketServer.FlashAccessPolicyEnabled = true;
+ }
+ else
+ {
+ WebSocketServer.FlashAccessPolicyEnabled = false;
+ }
+ }
+ #endif
WebSocketServer.Start();
}
catch (Exception ex)
diff --git a/MediaBrowser.Server.Implementations/packages.config b/MediaBrowser.Server.Implementations/packages.config
index 54c8c9f9d..a504bc6ab 100644
--- a/MediaBrowser.Server.Implementations/packages.config
+++ b/MediaBrowser.Server.Implementations/packages.config
@@ -3,5 +3,5 @@
<package id="Alchemy" version="2.2.1" targetFramework="net45" />
<package id="MediaBrowser.BdInfo" version="1.0.0.5" targetFramework="net45" />
<package id="morelinq" version="1.0.16006" targetFramework="net45" />
- <package id="System.Data.SQLite.x86" version="1.0.89.0" targetFramework="net45" />
+ <package id="System.Data.SQLite.x86" version="1.0.90.0" targetFramework="net45" />
</packages> \ No newline at end of file
diff --git a/MediaBrowser.Server.Mono/FFMpeg/FFMpegDownloadInfo.cs b/MediaBrowser.Server.Mono/FFMpeg/FFMpegDownloadInfo.cs
index 970e5a3e0..7cb7278dc 100644
--- a/MediaBrowser.Server.Mono/FFMpeg/FFMpegDownloadInfo.cs
+++ b/MediaBrowser.Server.Mono/FFMpeg/FFMpegDownloadInfo.cs
@@ -1,20 +1,57 @@
-
+using System;
+
namespace MediaBrowser.ServerApplication.FFMpeg
{
public static class FFMpegDownloadInfo
{
- public static string Version = "ffmpeg20130904";
+ public static string Version = ffmpegOsType("Version");
- public static string[] FfMpegUrls = new[]
- {
- "http://ffmpeg.gusari.org/static/32bit/ffmpeg.static.32bit.2013-10-11.tar.gz",
+ public static string[] FfMpegUrls = ffmpegOsType("FfMpegUrls").Split(',');
- "https://www.dropbox.com/s/b9v17h105cps7p0/ffmpeg.static.32bit.2013-10-11.tar.gz?dl=1"
- };
+ public static string FFMpegFilename = ffmpegOsType("FFMpegFilename");
+ public static string FFProbeFilename = ffmpegOsType("FFProbeFilename");
- public static string FFMpegFilename = "ffmpeg";
- public static string FFProbeFilename = "ffprobe";
+ public static string ArchiveType = ffmpegOsType("ArchiveType");
- public static string ArchiveType = "gz";
+ private static string ffmpegOsType(string arg)
+ {
+ OperatingSystem os = Environment.OSVersion;
+ PlatformID pid = os.Platform;
+ switch (pid)
+ {
+ case PlatformID.Win32NT:
+ switch (arg)
+ {
+ case "Version":
+ return "ffmpeg20131221";
+ case "FfMpegUrls":
+ return "http://ffmpeg.zeranoe.com/builds/win32/static/ffmpeg-20131221-git-70d6ce7-win32-static.7z,https://www.dropbox.com/s/d38uj7857trbw1g/ffmpeg-20131209-git-a12f679-win32-static.7z?dl=1";
+ case "FFMpegFilename":
+ return "ffmpeg.exe";
+ case "FFProbeFilename":
+ return "ffprobe.exe";
+ case "ArchiveType":
+ return "7z";
+ }
+ break;
+ case PlatformID.Unix:
+ case PlatformID.MacOSX:
+ switch (arg)
+ {
+ case "Version":
+ return "ffmpeg20131221";
+ case "FfMpegUrls":
+ return "http://ffmpeg.gusari.org/static/32bit/ffmpeg.static.32bit.2013-12-21.tar.gz,https://www.dropbox.com/s/b9v17h105cps7p0/ffmpeg.static.32bit.2013-10-11.tar.gz?dl=1";
+ case "FFMpegFilename":
+ return "ffmpeg";
+ case "FFProbeFilename":
+ return "ffprobe";
+ case "ArchiveType":
+ return "gz";
+ }
+ break;
+ }
+ return "";
+ }
}
}
diff --git a/MediaBrowser.Server.Mono/MediaBrowser.Server.Mono.csproj b/MediaBrowser.Server.Mono/MediaBrowser.Server.Mono.csproj
index be7fb7b27..d37330821 100644
--- a/MediaBrowser.Server.Mono/MediaBrowser.Server.Mono.csproj
+++ b/MediaBrowser.Server.Mono/MediaBrowser.Server.Mono.csproj
@@ -22,6 +22,7 @@
<WarningLevel>4</WarningLevel>
<PlatformTarget>x86</PlatformTarget>
<Externalconsole>true</Externalconsole>
+ <TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|x86' ">
<DebugType>full</DebugType>
@@ -31,20 +32,25 @@
<WarningLevel>4</WarningLevel>
<PlatformTarget>x86</PlatformTarget>
<Externalconsole>true</Externalconsole>
+ <TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<Optimize>false</Optimize>
<OutputPath>bin\Release</OutputPath>
<WarningLevel>4</WarningLevel>
+ <TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
+ </PropertyGroup>
+ <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release Mono|AnyCPU' ">
+ <Optimize>false</Optimize>
+ <OutputPath>bin\Release Mono</OutputPath>
+ <WarningLevel>4</WarningLevel>
</PropertyGroup>
<ItemGroup>
<Reference Include="System" />
- <Reference Include="ServiceStack.Common">
- <HintPath>..\packages\ServiceStack.Common.3.9.70\lib\net35\ServiceStack.Common.dll</HintPath>
- </Reference>
<Reference Include="ServiceStack.Interfaces">
- <HintPath>..\packages\ServiceStack.Common.3.9.70\lib\net35\ServiceStack.Interfaces.dll</HintPath>
+ <HintPath>..\ThirdParty\ServiceStack\ServiceStack.Interfaces.dll</HintPath>
</Reference>
+ <Reference Include="Mono.Posix" Condition=" '$(ConfigurationName)' == 'Release Mono' "/>
</ItemGroup>
<ItemGroup>
<Compile Include="..\SharedVersion.cs">
@@ -74,6 +80,9 @@
</Compile>
<Compile Include="FFMpeg\FFMpegDownloadInfo.cs" />
<Compile Include="IO\FileSystemFactory.cs" />
+ <Compile Include="..\MediaBrowser.ServerApplication\EntryPoints\WanAddressEntryPoint.cs">
+ <Link>EntryPoints\WanAddressEntryPoint.cs</Link>
+ </Compile>
</ItemGroup>
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
<ItemGroup>
@@ -119,8 +128,11 @@
</ItemGroup>
<ItemGroup>
<None Include="app.config" />
- <None Include="sqlite3.dll">
+ </ItemGroup>
+ <ItemGroup>
+ <Content Include="..\ThirdParty\SQLite3\x86\3.8.2\sqlite3.dll">
+ <Link>sqlite3.dll</Link>
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </None>
+ </Content>
</ItemGroup>
</Project> \ No newline at end of file
diff --git a/MediaBrowser.Server.Mono/Program.cs b/MediaBrowser.Server.Mono/Program.cs
index cf0b4c6d7..59fc11c07 100644
--- a/MediaBrowser.Server.Mono/Program.cs
+++ b/MediaBrowser.Server.Mono/Program.cs
@@ -102,7 +102,9 @@ namespace MediaBrowser.Server.Mono
Console.WriteLine ("appHost.Init");
- var task = _appHost.Init();
+ var initProgress = new Progress<double>();
+
+ var task = _appHost.Init(initProgress);
Task.WaitAll (task);
Console.WriteLine ("Running startup tasks");
diff --git a/MediaBrowser.Server.Mono/app.config b/MediaBrowser.Server.Mono/app.config
index 7a240c6dd..c5abd3a20 100644
--- a/MediaBrowser.Server.Mono/app.config
+++ b/MediaBrowser.Server.Mono/app.config
@@ -8,7 +8,6 @@
</nlog>
<appSettings>
<add key="DebugProgramDataPath" value="ProgramData-Server" />
- <add key="ReleaseProgramDataPath" value="" />
- <add key="ProgramDataFolderName" value="ProgramData-Server" />
+ <add key="ReleaseProgramDataPath" value="ProgramData-Server" />
</appSettings>
</configuration>
diff --git a/MediaBrowser.ServerApplication/App.config b/MediaBrowser.ServerApplication/App.config
index ba6d74214..53788e09a 100644
--- a/MediaBrowser.ServerApplication/App.config
+++ b/MediaBrowser.ServerApplication/App.config
@@ -11,8 +11,7 @@
</nlog>
<appSettings>
<add key="DebugProgramDataPath" value="..\..\..\..\ProgramData-Server" />
- <add key="ReleaseProgramDataPath" value="%ApplicationData%" />
- <add key="ProgramDataFolderName" value="MediaBrowser-Server" />
+ <add key="ReleaseProgramDataPath" value="%ApplicationData%\MediaBrowser-Server" />
<add key="ClientSettingsProvider.ServiceUri" value="" />
</appSettings>
<startup useLegacyV2RuntimeActivationPolicy="true">
@@ -46,6 +45,10 @@
<assemblyIdentity name="System.Data.SQLite" publicKeyToken="db937bc2d44ff139" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-1.0.89.0" newVersion="1.0.89.0" />
</dependentAssembly>
+ <dependentAssembly>
+ <assemblyIdentity name="SimpleInjector" publicKeyToken="984cb50dea722e99" culture="neutral" />
+ <bindingRedirect oldVersion="0.0.0.0-2.3.6.0" newVersion="2.3.6.0" />
+ </dependentAssembly>
</assemblyBinding>
</runtime>
<system.web>
diff --git a/MediaBrowser.ServerApplication/App.xaml.cs b/MediaBrowser.ServerApplication/App.xaml.cs
index 9b978ca2d..839841620 100644
--- a/MediaBrowser.ServerApplication/App.xaml.cs
+++ b/MediaBrowser.ServerApplication/App.xaml.cs
@@ -72,12 +72,14 @@ namespace MediaBrowser.ServerApplication
{
try
{
+ var initProgress = new Progress<double>();
+
if (!IsRunningAsService)
{
- ShowSplashWindow();
+ ShowSplashWindow(initProgress);
}
- await _appHost.Init();
+ await _appHost.Init(initProgress);
var task = _appHost.RunStartupTasks();
@@ -114,7 +116,8 @@ namespace MediaBrowser.ServerApplication
var win = new MainWindow(host.LogManager, host,
host.ServerConfigurationManager, host.UserManager,
host.LibraryManager, host.JsonSerializer,
- host.DisplayPreferencesRepository);
+ host.DisplayPreferencesRepository,
+ host.ItemRepository);
win.Show();
@@ -131,9 +134,9 @@ namespace MediaBrowser.ServerApplication
}
private SplashWindow _splashWindow;
- private void ShowSplashWindow()
+ private void ShowSplashWindow(Progress<double> progress)
{
- var win = new SplashWindow(_appHost.ApplicationVersion);
+ var win = new SplashWindow(_appHost.ApplicationVersion, progress);
win.Show();
_splashWindow = win;
diff --git a/MediaBrowser.ServerApplication/ApplicationHost.cs b/MediaBrowser.ServerApplication/ApplicationHost.cs
index 6058c5958..514fbe6c1 100644
--- a/MediaBrowser.ServerApplication/ApplicationHost.cs
+++ b/MediaBrowser.ServerApplication/ApplicationHost.cs
@@ -8,6 +8,7 @@ using MediaBrowser.Common.Implementations.ScheduledTasks;
using MediaBrowser.Common.IO;
using MediaBrowser.Common.MediaInfo;
using MediaBrowser.Common.Net;
+using MediaBrowser.Common.Progress;
using MediaBrowser.Controller;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Drawing;
@@ -28,7 +29,6 @@ using MediaBrowser.Controller.Session;
using MediaBrowser.Controller.Sorting;
using MediaBrowser.Model.Logging;
using MediaBrowser.Model.MediaInfo;
-using MediaBrowser.Model.Serialization;
using MediaBrowser.Model.System;
using MediaBrowser.Model.Updates;
using MediaBrowser.Providers;
@@ -49,6 +49,7 @@ using MediaBrowser.Server.Implementations.Providers;
using MediaBrowser.Server.Implementations.ServerManager;
using MediaBrowser.Server.Implementations.Session;
using MediaBrowser.Server.Implementations.WebSocket;
+using MediaBrowser.ServerApplication.EntryPoints;
using MediaBrowser.ServerApplication.FFMpeg;
using MediaBrowser.ServerApplication.IO;
using MediaBrowser.ServerApplication.Native;
@@ -70,12 +71,6 @@ namespace MediaBrowser.ServerApplication
public class ApplicationHost : BaseApplicationHost<ServerApplicationPaths>, IServerApplicationHost
{
/// <summary>
- /// Gets the server kernel.
- /// </summary>
- /// <value>The server kernel.</value>
- protected Kernel ServerKernel { get; set; }
-
- /// <summary>
/// Gets the server configuration manager.
/// </summary>
/// <value>The server configuration manager.</value>
@@ -167,11 +162,9 @@ namespace MediaBrowser.ServerApplication
private IUserDataManager UserDataManager { get; set; }
private IUserRepository UserRepository { get; set; }
internal IDisplayPreferencesRepository DisplayPreferencesRepository { get; set; }
- private IItemRepository ItemRepository { get; set; }
+ internal IItemRepository ItemRepository { get; set; }
private INotificationsRepository NotificationsRepository { get; set; }
- private Task<IHttpServer> _httpServerCreationTask;
-
/// <summary>
/// Initializes a new instance of the <see cref="ApplicationHost"/> class.
/// </summary>
@@ -216,39 +209,24 @@ namespace MediaBrowser.ServerApplication
}
/// <summary>
- /// Called when [logger loaded].
- /// </summary>
- protected override void OnLoggerLoaded()
- {
- base.OnLoggerLoaded();
-
- _httpServerCreationTask = Task.Run(() => ServerFactory.CreateServer(this, LogManager, "Media Browser", "mediabrowser", "dashboard/index.html"));
- }
-
- /// <summary>
/// Registers resources that classes will depend on
/// </summary>
/// <returns>Task.</returns>
- protected override async Task RegisterResources()
+ protected override async Task RegisterResources(IProgress<double> progress)
{
- ServerKernel = new Kernel();
-
- await base.RegisterResources().ConfigureAwait(false);
+ await base.RegisterResources(progress).ConfigureAwait(false);
RegisterSingleInstance<IHttpResultFactory>(new HttpResultFactory(LogManager, FileSystemManager));
RegisterSingleInstance<IServerApplicationHost>(this);
RegisterSingleInstance<IServerApplicationPaths>(ApplicationPaths);
- RegisterSingleInstance(ServerKernel);
RegisterSingleInstance(ServerConfigurationManager);
RegisterSingleInstance<IWebSocketServer>(() => new AlchemyServer(Logger));
RegisterSingleInstance<IBlurayExaminer>(() => new BdInfoExaminer());
- var mediaEncoderTask = RegisterMediaEncoder();
-
UserDataManager = new UserDataManager(LogManager);
RegisterSingleInstance(UserDataManager);
@@ -278,8 +256,9 @@ namespace MediaBrowser.ServerApplication
SessionManager = new SessionManager(UserDataManager, ServerConfigurationManager, Logger, UserRepository, LibraryManager);
RegisterSingleInstance(SessionManager);
- HttpServer = await _httpServerCreationTask.ConfigureAwait(false);
+ HttpServer = ServerFactory.CreateServer(this, LogManager, "Media Browser", "mediabrowser", "dashboard/index.html");
RegisterSingleInstance(HttpServer, false);
+ progress.Report(10);
ServerManager = new ServerManager(this, JsonSerializer, Logger, ServerConfigurationManager);
RegisterSingleInstance(ServerManager);
@@ -293,16 +272,27 @@ namespace MediaBrowser.ServerApplication
DtoService = new DtoService(Logger, LibraryManager, UserManager, UserDataManager, ItemRepository, ImageProcessor);
RegisterSingleInstance(DtoService);
- LiveTvManager = new LiveTvManager(ApplicationPaths, FileSystemManager, Logger, ItemRepository, ImageProcessor, UserManager, LocalizationManager, UserDataManager, DtoService);
+ LiveTvManager = new LiveTvManager(ApplicationPaths, FileSystemManager, Logger, ItemRepository, ImageProcessor, LocalizationManager, UserDataManager, DtoService, UserManager);
RegisterSingleInstance(LiveTvManager);
+ progress.Report(15);
+
+ var innerProgress = new ActionableProgress<double>();
+ innerProgress.RegisterAction(p => progress.Report((.75 * p) + 15));
+
+ await RegisterMediaEncoder(innerProgress).ConfigureAwait(false);
+ progress.Report(90);
var displayPreferencesTask = Task.Run(async () => await ConfigureDisplayPreferencesRepositories().ConfigureAwait(false));
var itemsTask = Task.Run(async () => await ConfigureItemRepositories().ConfigureAwait(false));
var userdataTask = Task.Run(async () => await ConfigureUserDataRepositories().ConfigureAwait(false));
await ConfigureNotificationsRepository().ConfigureAwait(false);
+ progress.Report(92);
+
+ await Task.WhenAll(itemsTask, displayPreferencesTask, userdataTask).ConfigureAwait(false);
+ progress.Report(100);
- await Task.WhenAll(itemsTask, displayPreferencesTask, userdataTask, mediaEncoderTask).ConfigureAwait(false);
+ await ((UserManager) UserManager).Initialize().ConfigureAwait(false);
SetKernelProperties();
}
@@ -321,9 +311,9 @@ namespace MediaBrowser.ServerApplication
/// Registers the media encoder.
/// </summary>
/// <returns>Task.</returns>
- private async Task RegisterMediaEncoder()
+ private async Task RegisterMediaEncoder(IProgress<double> progress)
{
- var info = await new FFMpegDownloader(Logger, ApplicationPaths, HttpClient, ZipClient, FileSystemManager).GetFFMpegInfo().ConfigureAwait(false);
+ var info = await new FFMpegDownloader(Logger, ApplicationPaths, HttpClient, ZipClient, FileSystemManager).GetFFMpegInfo(progress).ConfigureAwait(false);
MediaEncoder = new MediaEncoder(LogManager.GetLogger("MediaEncoder"), ApplicationPaths, JsonSerializer, info.Path, info.ProbePath, info.Version, FileSystemManager);
RegisterSingleInstance(MediaEncoder);
@@ -334,11 +324,11 @@ namespace MediaBrowser.ServerApplication
/// </summary>
private void SetKernelProperties()
{
- Parallel.Invoke(
- () => ServerKernel.FFMpegManager = new FFMpegManager(ApplicationPaths, MediaEncoder, Logger, ItemRepository, FileSystemManager),
- () => LocalizedStrings.StringFiles = GetExports<LocalizedStringData>(),
- SetStaticProperties
- );
+ new FFMpegManager(MediaEncoder, Logger, ItemRepository, FileSystemManager, ServerConfigurationManager);
+
+ LocalizedStrings.StringFiles = GetExports<LocalizedStringData>();
+
+ SetStaticProperties();
}
/// <summary>
@@ -454,6 +444,8 @@ namespace MediaBrowser.ServerApplication
ImageProcessor.AddParts(GetExports<IImageEnhancer>());
LiveTvManager.AddParts(GetExports<ILiveTvService>());
+
+ SessionManager.AddParts(GetExports<ISessionControllerFactory>());
}
/// <summary>
@@ -550,9 +542,7 @@ namespace MediaBrowser.ServerApplication
/// <returns>IEnumerable{Assembly}.</returns>
protected override IEnumerable<Assembly> GetComposablePartAssemblies()
{
- var list = Directory.EnumerateFiles(ApplicationPaths.PluginsPath, "*.dll", SearchOption.TopDirectoryOnly)
- .Select(LoadAssembly)
- .Where(a => a != null)
+ var list = GetPluginAssemblies()
.ToList();
// Gets all plugin assemblies by first reading all bytes of the .dll and calling Assembly.Load against that
@@ -571,7 +561,7 @@ namespace MediaBrowser.ServerApplication
list.Add(typeof(IApplicationHost).Assembly);
// Include composable parts in the Controller assembly
- list.Add(typeof(Kernel).Assembly);
+ list.Add(typeof(IServerApplicationHost).Assembly);
// Include composable parts in the Providers assembly
list.Add(typeof(ImagesByNameProvider).Assembly);
@@ -590,6 +580,25 @@ namespace MediaBrowser.ServerApplication
return list;
}
+ /// <summary>
+ /// Gets the plugin assemblies.
+ /// </summary>
+ /// <returns>IEnumerable{Assembly}.</returns>
+ private IEnumerable<Assembly> GetPluginAssemblies()
+ {
+ try
+ {
+ return Directory.EnumerateFiles(ApplicationPaths.PluginsPath, "*.dll", SearchOption.TopDirectoryOnly)
+ .Select(LoadAssembly)
+ .Where(a => a != null)
+ .ToList();
+ }
+ catch (DirectoryNotFoundException)
+ {
+ return new List<Assembly>();
+ }
+ }
+
private readonly string _systemId = Environment.MachineName.GetMD5().ToString();
/// <summary>
@@ -612,11 +621,13 @@ namespace MediaBrowser.ServerApplication
ProgramDataPath = ApplicationPaths.ProgramDataPath,
LogPath = ApplicationPaths.LogDirectoryPath,
ItemsByNamePath = ApplicationPaths.ItemsByNamePath,
+ CachePath = ApplicationPaths.CachePath,
MacAddress = GetMacAddress(),
HttpServerPortNumber = ServerConfigurationManager.Configuration.HttpServerPortNumber,
OperatingSystem = Environment.OSVersion.ToString(),
CanSelfRestart = CanSelfRestart,
- CanSelfUpdate = CanSelfUpdate
+ CanSelfUpdate = CanSelfUpdate,
+ WanAddress = WanAddressEntryPoint.WanAddress
};
}
diff --git a/MediaBrowser.ServerApplication/EntryPoints/WanAddressEntryPoint.cs b/MediaBrowser.ServerApplication/EntryPoints/WanAddressEntryPoint.cs
new file mode 100644
index 000000000..7b2a1314e
--- /dev/null
+++ b/MediaBrowser.ServerApplication/EntryPoints/WanAddressEntryPoint.cs
@@ -0,0 +1,55 @@
+using MediaBrowser.Common.Net;
+using MediaBrowser.Controller.Plugins;
+using System;
+using System.IO;
+using System.Threading;
+
+namespace MediaBrowser.ServerApplication.EntryPoints
+{
+ public class WanAddressEntryPoint : IServerEntryPoint
+ {
+ public static string WanAddress;
+ private Timer _timer;
+ private readonly IHttpClient _httpClient;
+
+ public WanAddressEntryPoint(IHttpClient httpClient)
+ {
+ _httpClient = httpClient;
+ }
+
+ public void Run()
+ {
+ _timer = new Timer(TimerCallback, null, TimeSpan.FromMinutes(1), TimeSpan.FromHours(24));
+ }
+
+ private async void TimerCallback(object state)
+ {
+ try
+ {
+ using (var stream = await _httpClient.Get(new HttpRequestOptions
+ {
+ Url = "http://bot.whatismyipaddress.com/"
+
+ }).ConfigureAwait(false))
+ {
+ using (var reader = new StreamReader(stream))
+ {
+ WanAddress = await reader.ReadToEndAsync().ConfigureAwait(false);
+ }
+ }
+ }
+ catch
+ {
+ }
+ }
+
+ public void Dispose()
+ {
+ if (_timer != null)
+ {
+ _timer.Dispose();
+ _timer = null;
+ }
+ }
+ }
+}
diff --git a/MediaBrowser.ServerApplication/FFMpeg/FFMpegDownloadInfo.cs b/MediaBrowser.ServerApplication/FFMpeg/FFMpegDownloadInfo.cs
index ec7dc582d..fc50df216 100644
--- a/MediaBrowser.ServerApplication/FFMpeg/FFMpegDownloadInfo.cs
+++ b/MediaBrowser.ServerApplication/FFMpeg/FFMpegDownloadInfo.cs
@@ -3,14 +3,12 @@ namespace MediaBrowser.ServerApplication.FFMpeg
{
public static class FFMpegDownloadInfo
{
- public static string Version = "ffmpeg20131110.1";
+ public static string Version = "ffmpeg20131209";
public static string[] FfMpegUrls = new[]
{
- "https://github.com/MediaBrowser/MediaBrowser.Resources/raw/master/ffmpeg/windows/ffmpeg-20131110-git-8cdf4e0-win32-static.7z",
-
- "http://ffmpeg.zeranoe.com/builds/win32/static/ffmpeg-20131110-git-8cdf4e0-win32-static.7z",
- "https://www.dropbox.com/s/5clspc636v9hie6/ffmpeg-20131110-git-8cdf4e0-win32-static.7z?dl=1"
+ "http://ffmpeg.zeranoe.com/builds/win32/static/ffmpeg-20131209-git-a12f679-win32-static.7z",
+ "https://www.dropbox.com/s/d38uj7857trbw1g/ffmpeg-20131209-git-a12f679-win32-static.7z?dl=1"
};
public static string FFMpegFilename = "ffmpeg.exe";
diff --git a/MediaBrowser.ServerApplication/FFMpeg/FFMpegDownloader.cs b/MediaBrowser.ServerApplication/FFMpeg/FFMpegDownloader.cs
index 1f329446e..1e99c4eb0 100644
--- a/MediaBrowser.ServerApplication/FFMpeg/FFMpegDownloader.cs
+++ b/MediaBrowser.ServerApplication/FFMpeg/FFMpegDownloader.cs
@@ -1,7 +1,7 @@
using MediaBrowser.Common.Configuration;
using MediaBrowser.Common.IO;
using MediaBrowser.Common.Net;
-using MediaBrowser.Controller.IO;
+using MediaBrowser.Common.Progress;
using MediaBrowser.Model.IO;
using MediaBrowser.Model.Logging;
using MediaBrowser.Model.Net;
@@ -12,6 +12,9 @@ using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
+#if __MonoCS__
+using Mono.Unix.Native;
+#endif
namespace MediaBrowser.ServerApplication.FFMpeg
{
@@ -37,7 +40,7 @@ namespace MediaBrowser.ServerApplication.FFMpeg
_fileSystem = fileSystem;
}
- public async Task<FFMpegInfo> GetFFMpegInfo()
+ public async Task<FFMpegInfo> GetFFMpegInfo(IProgress<double> progress)
{
var versionedDirectoryPath = Path.Combine(GetMediaToolsPath(true), FFMpegDownloadInfo.Version);
@@ -52,30 +55,69 @@ namespace MediaBrowser.ServerApplication.FFMpeg
var tasks = new List<Task>();
+ double ffmpegPercent = 0;
+ double fontPercent = 0;
+ var syncLock = new object();
+
if (!File.Exists(info.ProbePath) || !File.Exists(info.Path))
{
- tasks.Add(DownloadFFMpeg(info));
+ var ffmpegProgress = new ActionableProgress<double>();
+ ffmpegProgress.RegisterAction(p =>
+ {
+ ffmpegPercent = p;
+
+ lock (syncLock)
+ {
+ progress.Report((ffmpegPercent / 2) + (fontPercent / 2));
+ }
+ });
+
+ tasks.Add(DownloadFFMpeg(info, ffmpegProgress));
+ }
+ else
+ {
+ ffmpegPercent = 100;
+ progress.Report(50);
}
- tasks.Add(DownloadFonts(versionedDirectoryPath));
+ var fontProgress = new ActionableProgress<double>();
+ fontProgress.RegisterAction(p =>
+ {
+ fontPercent = p;
+
+ lock (syncLock)
+ {
+ progress.Report((ffmpegPercent / 2) + (fontPercent / 2));
+ }
+ });
+
+ tasks.Add(DownloadFonts(versionedDirectoryPath, fontProgress));
await Task.WhenAll(tasks).ConfigureAwait(false);
return info;
}
- private async Task DownloadFFMpeg(FFMpegInfo info)
+ private async Task DownloadFFMpeg(FFMpegInfo info, IProgress<double> progress)
{
foreach (var url in FFMpegDownloadInfo.FfMpegUrls)
{
+ progress.Report(0);
+
try
{
- var tempFile = await DownloadFFMpeg(info, url).ConfigureAwait(false);
+ var tempFile = await _httpClient.GetTempFile(new HttpRequestOptions
+ {
+ Url = url,
+ CancellationToken = CancellationToken.None,
+ Progress = progress
+
+ }).ConfigureAwait(false);
ExtractFFMpeg(tempFile, Path.GetDirectoryName(info.Path));
return;
}
- catch (HttpException ex)
+ catch (HttpException)
{
}
@@ -84,16 +126,6 @@ namespace MediaBrowser.ServerApplication.FFMpeg
throw new ApplicationException("Unable to download required components. Please try again later.");
}
- private Task<string> DownloadFFMpeg(FFMpegInfo info, string url)
- {
- return _httpClient.GetTempFile(new HttpRequestOptions
- {
- Url = url,
- CancellationToken = CancellationToken.None,
- Progress = new Progress<double>()
- });
- }
-
private void ExtractFFMpeg(string tempFile, string targetFolder)
{
_logger.Debug("Extracting ffmpeg from {0}", tempFile);
@@ -118,6 +150,13 @@ namespace MediaBrowser.ServerApplication.FFMpeg
}))
{
File.Copy(file, Path.Combine(targetFolder, Path.GetFileName(file)), true);
+ #if __MonoCS__
+ //Linux: File permission to 666, and user's execute bit
+ if (Environment.OSVersion.Platform == PlatformID.Unix || Environment.OSVersion.Platform == PlatformID.MacOSX)
+ {
+ Syscall.chmod(Path.Combine(targetFolder, Path.GetFileName(file)), FilePermissions.DEFFILEMODE | FilePermissions.S_IXUSR);
+ }
+ #endif
}
}
finally
@@ -158,7 +197,7 @@ namespace MediaBrowser.ServerApplication.FFMpeg
/// Extracts the fonts.
/// </summary>
/// <param name="targetPath">The target path.</param>
- private async Task DownloadFonts(string targetPath)
+ private async Task DownloadFonts(string targetPath, IProgress<double> progress)
{
try
{
@@ -172,7 +211,7 @@ namespace MediaBrowser.ServerApplication.FFMpeg
if (!File.Exists(fontFile))
{
- await DownloadFontFile(fontsDirectory, fontFilename).ConfigureAwait(false);
+ await DownloadFontFile(fontsDirectory, fontFilename, progress).ConfigureAwait(false);
}
await WriteFontConfigFile(fontsDirectory).ConfigureAwait(false);
@@ -187,6 +226,8 @@ namespace MediaBrowser.ServerApplication.FFMpeg
// Don't let the server crash because of this
_logger.ErrorException("Error writing ffmpeg font files", ex);
}
+
+ progress.Report(100);
}
/// <summary>
@@ -195,7 +236,7 @@ namespace MediaBrowser.ServerApplication.FFMpeg
/// <param name="fontsDirectory">The fonts directory.</param>
/// <param name="fontFilename">The font filename.</param>
/// <returns>Task.</returns>
- private async Task DownloadFontFile(string fontsDirectory, string fontFilename)
+ private async Task DownloadFontFile(string fontsDirectory, string fontFilename, IProgress<double> progress)
{
var existingFile = Directory
.EnumerateFiles(_appPaths.ProgramDataPath, fontFilename, SearchOption.AllDirectories)
@@ -219,12 +260,14 @@ namespace MediaBrowser.ServerApplication.FFMpeg
foreach (var url in _fontUrls)
{
+ progress.Report(0);
+
try
{
tempFile = await _httpClient.GetTempFile(new HttpRequestOptions
{
Url = url,
- Progress = new Progress<double>()
+ Progress = progress
}).ConfigureAwait(false);
diff --git a/MediaBrowser.ServerApplication/LibraryExplorer.xaml.cs b/MediaBrowser.ServerApplication/LibraryExplorer.xaml.cs
index a3d470689..1a5d73e6b 100644
--- a/MediaBrowser.ServerApplication/LibraryExplorer.xaml.cs
+++ b/MediaBrowser.ServerApplication/LibraryExplorer.xaml.cs
@@ -35,6 +35,8 @@ namespace MediaBrowser.ServerApplication
private readonly ILibraryManager _libraryManager;
private readonly IDisplayPreferencesRepository _displayPreferencesManager;
+ private readonly IItemRepository _itemRepository;
+
/// <summary>
/// The current user
/// </summary>
@@ -48,7 +50,7 @@ namespace MediaBrowser.ServerApplication
/// <param name="userManager">The user manager.</param>
/// <param name="libraryManager">The library manager.</param>
/// <param name="displayPreferencesManager">The display preferences manager.</param>
- public LibraryExplorer(IJsonSerializer jsonSerializer, ILogger logger, IApplicationHost appHost, IUserManager userManager, ILibraryManager libraryManager, IDisplayPreferencesRepository displayPreferencesManager)
+ public LibraryExplorer(IJsonSerializer jsonSerializer, ILogger logger, IApplicationHost appHost, IUserManager userManager, ILibraryManager libraryManager, IDisplayPreferencesRepository displayPreferencesManager, IItemRepository itemRepo)
{
_logger = logger;
_jsonSerializer = jsonSerializer;
@@ -62,7 +64,7 @@ namespace MediaBrowser.ServerApplication
ddlProfile.Items.Insert(0, new User { Name = "Physical" });
ddlProfile.SelectedIndex = 0;
ddlIndexBy.Visibility = ddlSortBy.Visibility = lblIndexBy.Visibility = lblSortBy.Visibility = Visibility.Hidden;
-
+ _itemRepository = itemRepo;
}
/// <summary>
@@ -88,7 +90,7 @@ namespace MediaBrowser.ServerApplication
Cursor = Cursors.Wait;
await Task.Run(() =>
{
- IEnumerable<BaseItem> children = CurrentUser.Name == "Physical" ? _libraryManager.RootFolder.Children : _libraryManager.RootFolder.GetChildren(CurrentUser, true);
+ IEnumerable<BaseItem> children = CurrentUser.Name == "Physical" ? new[] { _libraryManager.RootFolder } : _libraryManager.RootFolder.GetChildren(CurrentUser, true);
children = OrderByName(children, CurrentUser);
foreach (Folder folder in children)
@@ -100,7 +102,7 @@ namespace MediaBrowser.ServerApplication
var prefs = ddlProfile.SelectedItem != null ? _displayPreferencesManager.GetDisplayPreferences(currentFolder.DisplayPreferencesId, (ddlProfile.SelectedItem as User).Id, "LibraryExplorer") ?? new DisplayPreferences { SortBy = ItemSortBy.SortName } : new DisplayPreferences { SortBy = ItemSortBy.SortName };
var node = new TreeViewItem { Tag = currentFolder };
- var subChildren = currentFolder.GetChildren(CurrentUser, true, prefs.IndexBy);
+ var subChildren = currentFolder.GetChildren(CurrentUser, true);
subChildren = OrderByName(subChildren, CurrentUser);
AddChildren(node, subChildren, CurrentUser);
node.Header = currentFolder.Name + " (" +
@@ -212,7 +214,24 @@ namespace MediaBrowser.ServerApplication
lblIndexBy.Visibility = ddlIndexBy.Visibility = ddlSortBy.Visibility = lblSortBy.Visibility = Visibility.Hidden;
}
- txtData.Text = FormatJson(_jsonSerializer.SerializeToString(item));
+
+ var json = FormatJson(_jsonSerializer.SerializeToString(item));
+
+ if (item is IHasMediaStreams)
+ {
+ var mediaStreams = _itemRepository.GetMediaStreams(new MediaStreamQuery
+ {
+ ItemId = item.Id
+
+ }).ToList();
+
+ if (mediaStreams.Count > 0)
+ {
+ json += "\n\nMedia Streams:\n\n"+FormatJson(_jsonSerializer.SerializeToString(mediaStreams));
+ }
+ }
+
+ txtData.Text = json;
var previews = new List<PreviewItem>();
await Task.Run(() =>
@@ -223,23 +242,23 @@ namespace MediaBrowser.ServerApplication
}
if (item.HasImage(ImageType.Banner))
{
- previews.Add(new PreviewItem(item.GetImage(ImageType.Banner), "Banner"));
+ previews.Add(new PreviewItem(item.GetImagePath(ImageType.Banner), "Banner"));
}
if (item.HasImage(ImageType.Logo))
{
- previews.Add(new PreviewItem(item.GetImage(ImageType.Logo), "Logo"));
+ previews.Add(new PreviewItem(item.GetImagePath(ImageType.Logo), "Logo"));
}
if (item.HasImage(ImageType.Art))
{
- previews.Add(new PreviewItem(item.GetImage(ImageType.Art), "Art"));
+ previews.Add(new PreviewItem(item.GetImagePath(ImageType.Art), "Art"));
}
if (item.HasImage(ImageType.Thumb))
{
- previews.Add(new PreviewItem(item.GetImage(ImageType.Thumb), "Thumb"));
+ previews.Add(new PreviewItem(item.GetImagePath(ImageType.Thumb), "Thumb"));
}
- previews.AddRange(
- item.BackdropImagePaths.Select(
- image => new PreviewItem(image, "Backdrop")));
+ previews.AddRange(
+ item.BackdropImagePaths.Select(
+ image => new PreviewItem(image, "Backdrop")));
});
lstPreviews.ItemsSource = previews;
lstPreviews.Items.Refresh();
@@ -371,7 +390,7 @@ namespace MediaBrowser.ServerApplication
//re-build the current item's children as an index
prefs.IndexBy = ddlIndexBy.SelectedItem as string;
treeItem.Items.Clear();
- AddChildren(treeItem, OrderBy(folder.GetChildren(CurrentUser, true, prefs.IndexBy), CurrentUser, prefs.SortBy), CurrentUser);
+ AddChildren(treeItem, OrderBy(folder.GetChildren(CurrentUser, true), CurrentUser, prefs.SortBy), CurrentUser);
treeItem.Header = folder.Name + "(" +
treeItem.Items.Count + ")";
Cursor = Cursors.Arrow;
@@ -412,7 +431,7 @@ namespace MediaBrowser.ServerApplication
//re-sort
prefs.SortBy = ddlSortBy.SelectedItem as string;
treeItem.Items.Clear();
- AddChildren(treeItem, OrderBy(folder.GetChildren(CurrentUser, true, prefs.IndexBy), CurrentUser, prefs.SortBy ?? ItemSortBy.SortName), CurrentUser);
+ AddChildren(treeItem, OrderBy(folder.GetChildren(CurrentUser, true), CurrentUser, prefs.SortBy ?? ItemSortBy.SortName), CurrentUser);
treeItem.Header = folder.Name + "(" +
treeItem.Items.Count + ")";
Cursor = Cursors.Arrow;
diff --git a/MediaBrowser.ServerApplication/MainStartup.cs b/MediaBrowser.ServerApplication/MainStartup.cs
index 3733d55af..7da17bc22 100644
--- a/MediaBrowser.ServerApplication/MainStartup.cs
+++ b/MediaBrowser.ServerApplication/MainStartup.cs
@@ -36,7 +36,9 @@ namespace MediaBrowser.ServerApplication
var startFlag = Environment.GetCommandLineArgs().ElementAtOrDefault(1);
_isRunningAsService = string.Equals(startFlag, "-service", StringComparison.OrdinalIgnoreCase);
- var appPaths = CreateApplicationPaths(_isRunningAsService);
+ var applicationPath = Process.GetCurrentProcess().MainModule.FileName;
+
+ var appPaths = CreateApplicationPaths(applicationPath, _isRunningAsService);
var logManager = new NlogManager(appPaths.LogDirectoryPath, "server");
logManager.ReloadLogger(LogSeverity.Debug);
@@ -49,7 +51,7 @@ namespace MediaBrowser.ServerApplication
if (string.Equals(startFlag, "-installservice", StringComparison.OrdinalIgnoreCase))
{
logger.Info("Performing service installation");
- InstallService(logger);
+ InstallService(applicationPath, logger);
return;
}
@@ -57,7 +59,7 @@ namespace MediaBrowser.ServerApplication
if (string.Equals(startFlag, "-installserviceasadmin", StringComparison.OrdinalIgnoreCase))
{
logger.Info("Performing service installation");
- RunServiceInstallation();
+ RunServiceInstallation(applicationPath);
return;
}
@@ -65,7 +67,7 @@ namespace MediaBrowser.ServerApplication
if (string.Equals(startFlag, "-uninstallservice", StringComparison.OrdinalIgnoreCase))
{
logger.Info("Performing service uninstallation");
- UninstallService(logger);
+ UninstallService(applicationPath, logger);
return;
}
@@ -73,17 +75,17 @@ namespace MediaBrowser.ServerApplication
if (string.Equals(startFlag, "-uninstallserviceasadmin", StringComparison.OrdinalIgnoreCase))
{
logger.Info("Performing service uninstallation");
- RunServiceUninstallation();
+ RunServiceUninstallation(applicationPath);
return;
}
AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException;
- RunServiceInstallationIfNeeded();
+ RunServiceInstallationIfNeeded(applicationPath);
var currentProcess = Process.GetCurrentProcess();
- if (IsAlreadyRunning(currentProcess))
+ if (IsAlreadyRunning(applicationPath, currentProcess))
{
logger.Info("Shutting down because another instance of Media Browser Server is already running.");
return;
@@ -110,21 +112,19 @@ namespace MediaBrowser.ServerApplication
/// </summary>
/// <param name="currentProcess">The current process.</param>
/// <returns><c>true</c> if [is already running] [the specified current process]; otherwise, <c>false</c>.</returns>
- private static bool IsAlreadyRunning(Process currentProcess)
+ private static bool IsAlreadyRunning(string applicationPath, Process currentProcess)
{
- var runningPath = currentProcess.MainModule.FileName;
-
var duplicate = Process.GetProcesses().FirstOrDefault(i =>
+ {
+ try
+ {
+ return string.Equals(applicationPath, i.MainModule.FileName) && currentProcess.Id != i.Id;
+ }
+ catch (Exception)
{
- try
- {
- return string.Equals(runningPath, i.MainModule.FileName) && currentProcess.Id != i.Id;
- }
- catch (Exception)
- {
- return false;
- }
- });
+ return false;
+ }
+ });
if (duplicate != null)
{
@@ -145,19 +145,17 @@ namespace MediaBrowser.ServerApplication
/// </summary>
/// <param name="runAsService">if set to <c>true</c> [run as service].</param>
/// <returns>ServerApplicationPaths.</returns>
- private static ServerApplicationPaths CreateApplicationPaths(bool runAsService)
+ private static ServerApplicationPaths CreateApplicationPaths(string applicationPath, bool runAsService)
{
if (runAsService)
{
- var systemPath = Path.GetDirectoryName(Process.GetCurrentProcess().MainModule.FileName);
+ var systemPath = Path.GetDirectoryName(applicationPath);
var programDataPath = Path.GetDirectoryName(systemPath);
- return new ServerApplicationPaths(programDataPath);
+ return new ServerApplicationPaths(programDataPath, applicationPath);
}
- var applicationPath = Process.GetCurrentProcess().MainModule.FileName;
-
return new ServerApplicationPaths(applicationPath);
}
@@ -199,8 +197,7 @@ namespace MediaBrowser.ServerApplication
logger.Info("Operating system: {0}", Environment.OSVersion.ToString());
logger.Info("Program data path: {0}", appPaths.ProgramDataPath);
- var runningPath = Process.GetCurrentProcess().MainModule.FileName;
- logger.Info("Executable: {0}", runningPath);
+ logger.Info("Application Path: {0}", appPaths.ApplicationPath);
}
/// <summary>
@@ -279,13 +276,11 @@ namespace MediaBrowser.ServerApplication
/// <summary>
/// Installs the service.
/// </summary>
- private static void InstallService(ILogger logger)
+ private static void InstallService(string applicationPath, ILogger logger)
{
- var runningPath = Process.GetCurrentProcess().MainModule.FileName;
-
try
{
- ManagedInstallerClass.InstallHelper(new[] { runningPath });
+ ManagedInstallerClass.InstallHelper(new[] { applicationPath });
logger.Info("Service installation succeeded");
}
@@ -298,13 +293,11 @@ namespace MediaBrowser.ServerApplication
/// <summary>
/// Uninstalls the service.
/// </summary>
- private static void UninstallService(ILogger logger)
+ private static void UninstallService(string applicationPath, ILogger logger)
{
- var runningPath = Process.GetCurrentProcess().MainModule.FileName;
-
try
{
- ManagedInstallerClass.InstallHelper(new[] { "/u", runningPath });
+ ManagedInstallerClass.InstallHelper(new[] { "/u", applicationPath });
logger.Info("Service uninstallation succeeded");
}
@@ -314,26 +307,24 @@ namespace MediaBrowser.ServerApplication
}
}
- private static void RunServiceInstallationIfNeeded()
+ private static void RunServiceInstallationIfNeeded(string applicationPath)
{
var ctl = ServiceController.GetServices().FirstOrDefault(s => s.ServiceName == BackgroundService.Name);
if (ctl == null)
{
- RunServiceInstallation();
+ RunServiceInstallation(applicationPath);
}
}
/// <summary>
/// Runs the service installation.
/// </summary>
- private static void RunServiceInstallation()
+ private static void RunServiceInstallation(string applicationPath)
{
- var runningPath = Process.GetCurrentProcess().MainModule.FileName;
-
var startInfo = new ProcessStartInfo
{
- FileName = runningPath,
+ FileName = applicationPath,
Arguments = "-installservice",
@@ -352,13 +343,11 @@ namespace MediaBrowser.ServerApplication
/// <summary>
/// Runs the service uninstallation.
/// </summary>
- private static void RunServiceUninstallation()
+ private static void RunServiceUninstallation(string applicationPath)
{
- var runningPath = Process.GetCurrentProcess().MainModule.FileName;
-
var startInfo = new ProcessStartInfo
{
- FileName = runningPath,
+ FileName = applicationPath,
Arguments = "-uninstallservice",
@@ -416,6 +405,7 @@ namespace MediaBrowser.ServerApplication
_logger.ErrorException("UnhandledException", ex);
var path = Path.Combine(_appHost.ServerConfigurationManager.ApplicationPaths.LogDirectoryPath, "unhandled_" + Guid.NewGuid() + ".txt");
+ Directory.CreateDirectory(Path.GetDirectoryName(path));
var builder = LogHelper.GetLogMessage(ex);
diff --git a/MediaBrowser.ServerApplication/MainWindow.xaml.cs b/MediaBrowser.ServerApplication/MainWindow.xaml.cs
index b1972fbdb..040d714cf 100644
--- a/MediaBrowser.ServerApplication/MainWindow.xaml.cs
+++ b/MediaBrowser.ServerApplication/MainWindow.xaml.cs
@@ -45,6 +45,7 @@ namespace MediaBrowser.ServerApplication
private readonly ILibraryManager _libraryManager;
private readonly IJsonSerializer _jsonSerializer;
private readonly IDisplayPreferencesRepository _displayPreferencesManager;
+ private readonly IItemRepository _itemRepository;
/// <summary>
/// Initializes a new instance of the <see cref="MainWindow" /> class.
@@ -57,7 +58,7 @@ namespace MediaBrowser.ServerApplication
/// <param name="jsonSerializer">The json serializer.</param>
/// <param name="displayPreferencesManager">The display preferences manager.</param>
/// <exception cref="System.ArgumentNullException">logger</exception>
- public MainWindow(ILogManager logManager, IServerApplicationHost appHost, IServerConfigurationManager configurationManager, IUserManager userManager, ILibraryManager libraryManager, IJsonSerializer jsonSerializer, IDisplayPreferencesRepository displayPreferencesManager)
+ public MainWindow(ILogManager logManager, IServerApplicationHost appHost, IServerConfigurationManager configurationManager, IUserManager userManager, ILibraryManager libraryManager, IJsonSerializer jsonSerializer, IDisplayPreferencesRepository displayPreferencesManager, IItemRepository itemRepo)
{
if (logManager == null)
{
@@ -73,6 +74,7 @@ namespace MediaBrowser.ServerApplication
}
_logger = logManager.GetLogger("MainWindow");
+ _itemRepository = itemRepo;
_appHost = appHost;
_logManager = logManager;
_configurationManager = configurationManager;
@@ -131,14 +133,10 @@ namespace MediaBrowser.ServerApplication
{
Dispatcher.InvokeAsync(() =>
{
- var developerToolsVisibility = _configurationManager.Configuration.EnableDeveloperTools
- ? Visibility.Visible
- : Visibility.Collapsed;
-
- separatorDeveloperTools.Visibility = developerToolsVisibility;
- cmdReloadServer.Visibility = developerToolsVisibility;
- cmOpenExplorer.Visibility = developerToolsVisibility;
- cmShowLogWindow.Visibility = developerToolsVisibility;
+ separatorDeveloperTools.Visibility = Visibility.Visible;
+ cmdReloadServer.Visibility = Visibility.Visible;
+ cmOpenExplorer.Visibility = Visibility.Visible;
+ cmShowLogWindow.Visibility = Visibility.Visible;
});
}
@@ -234,7 +232,7 @@ namespace MediaBrowser.ServerApplication
/// <param name="e">The <see cref="RoutedEventArgs" /> instance containing the event data.</param>
private void cmOpenExplorer_click(object sender, RoutedEventArgs e)
{
- new LibraryExplorer(_jsonSerializer, _logger, _appHost, _userManager, _libraryManager, _displayPreferencesManager).Show();
+ new LibraryExplorer(_jsonSerializer, _logger, _appHost, _userManager, _libraryManager, _displayPreferencesManager, _itemRepository).Show();
}
/// <summary>
diff --git a/MediaBrowser.ServerApplication/MediaBrowser.ServerApplication.csproj b/MediaBrowser.ServerApplication/MediaBrowser.ServerApplication.csproj
index e02bb1d69..cf7eb989b 100644
--- a/MediaBrowser.ServerApplication/MediaBrowser.ServerApplication.csproj
+++ b/MediaBrowser.ServerApplication/MediaBrowser.ServerApplication.csproj
@@ -134,9 +134,12 @@
<Reference Include="ServiceStack.Interfaces">
<HintPath>..\ThirdParty\ServiceStack\ServiceStack.Interfaces.dll</HintPath>
</Reference>
- <Reference Include="SimpleInjector, Version=2.3.6.0, Culture=neutral, PublicKeyToken=984cb50dea722e99, processorArchitecture=MSIL">
+ <Reference Include="SimpleInjector, Version=2.4.0.0, Culture=neutral, PublicKeyToken=984cb50dea722e99, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
- <HintPath>..\packages\SimpleInjector.2.3.6\lib\net40-client\SimpleInjector.dll</HintPath>
+ <HintPath>..\packages\SimpleInjector.2.4.0\lib\net45\SimpleInjector.dll</HintPath>
+ </Reference>
+ <Reference Include="SimpleInjector.Diagnostics">
+ <HintPath>..\packages\SimpleInjector.2.4.0\lib\net45\SimpleInjector.Diagnostics.dll</HintPath>
</Reference>
<Reference Include="System" />
<Reference Include="System.Configuration.Install" />
@@ -162,6 +165,7 @@
</Compile>
<Compile Include="EntryPoints\ResourceEntryPoint.cs" />
<Compile Include="EntryPoints\StartupWizard.cs" />
+ <Compile Include="EntryPoints\WanAddressEntryPoint.cs" />
<Compile Include="FFMpeg\FFMpegDownloadInfo.cs" />
<Compile Include="FFMpeg\FFMpegInfo.cs" />
<Compile Include="IO\FileSystemFactory.cs" />
diff --git a/MediaBrowser.ServerApplication/Native/ServerAuthorization.cs b/MediaBrowser.ServerApplication/Native/ServerAuthorization.cs
index 91f0974eb..d2e542536 100644
--- a/MediaBrowser.ServerApplication/Native/ServerAuthorization.cs
+++ b/MediaBrowser.ServerApplication/Native/ServerAuthorization.cs
@@ -20,6 +20,8 @@ namespace MediaBrowser.ServerApplication.Native
/// <param name="tempDirectory">The temp directory.</param>
public static void AuthorizeServer(int httpServerPort, string httpServerUrlPrefix, int webSocketPort, int udpPort, string tempDirectory)
{
+ Directory.CreateDirectory(tempDirectory);
+
// Create a temp file path to extract the bat file to
var tmpFile = Path.Combine(tempDirectory, Guid.NewGuid() + ".bat");
diff --git a/MediaBrowser.ServerApplication/Splash/SplashWindow.xaml b/MediaBrowser.ServerApplication/Splash/SplashWindow.xaml
index 315c88cd2..b35eadd06 100644
--- a/MediaBrowser.ServerApplication/Splash/SplashWindow.xaml
+++ b/MediaBrowser.ServerApplication/Splash/SplashWindow.xaml
@@ -2,13 +2,18 @@
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Height="386.939" Width="664.49" WindowStartupLocation="CenterScreen" Title="Media Browser Server" ShowInTaskbar="True" WindowStyle="None" BorderThickness="1" BorderBrush="#cccccc" AllowsTransparency="True">
- <Border BorderBrush="DarkGray" BorderThickness="2" Margin="0,0,0,0">
- <Grid Margin="-2,0,0,0">
- <Image x:Name="imgLogo" HorizontalAlignment="Center" Height="146" Margin="0,10,44,0" VerticalAlignment="Top" Width="616" Source="/Resources/Images/mb3logo800.png" Opacity="0.5"/>
+
+ <Border BorderBrush="DarkGray" BorderThickness="1">
+ <Grid>
+
+ <Image HorizontalAlignment="Center" Height="146" Margin="0,10,44,0" VerticalAlignment="Top" Width="616" Source="/Resources/Images/mb3logo800.png" Opacity="0.5"/>
+
<Grid HorizontalAlignment="Left" Height="153" Margin="0,173,0,0" VerticalAlignment="Top" Width="662" Background="Gray">
+
<TextBlock x:Name="lblStatus" HorizontalAlignment="Left" Margin="12,14,0,18" Width="637" FontSize="36" Foreground="#FFE6D7D7" Text="Loading Media Browser Server..." TextWrapping="WrapWithOverflow"/>
+
<Rectangle Fill="#FF49494B" HorizontalAlignment="Left" Height="13" Stroke="Black" VerticalAlignment="Bottom" Width="662"/>
- <Rectangle x:Name="rectProgress" Fill="#FF0A0ABF" HorizontalAlignment="Left" Height="13" Stroke="Black" VerticalAlignment="Bottom" Width="0"/>
+ <Rectangle x:Name="RectProgress" Fill="#52B54B" HorizontalAlignment="Left" Height="13" Stroke="Black" VerticalAlignment="Bottom" Width="0"/>
</Grid>
</Grid>
diff --git a/MediaBrowser.ServerApplication/Splash/SplashWindow.xaml.cs b/MediaBrowser.ServerApplication/Splash/SplashWindow.xaml.cs
index db2f1dbc4..29eb4bf45 100644
--- a/MediaBrowser.ServerApplication/Splash/SplashWindow.xaml.cs
+++ b/MediaBrowser.ServerApplication/Splash/SplashWindow.xaml.cs
@@ -1,16 +1,6 @@
using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
+using System.ComponentModel;
using System.Windows;
-using System.Windows.Controls;
-using System.Windows.Data;
-using System.Windows.Documents;
-using System.Windows.Input;
-using System.Windows.Media;
-using System.Windows.Media.Imaging;
-using System.Windows.Shapes;
namespace MediaBrowser.ServerApplication.Splash
{
@@ -19,10 +9,33 @@ namespace MediaBrowser.ServerApplication.Splash
/// </summary>
public partial class SplashWindow : Window
{
- public SplashWindow(Version version)
+ private readonly Progress<double> _progress;
+
+ public SplashWindow(Version version, Progress<double> progress)
{
InitializeComponent();
lblStatus.Text = string.Format("Loading Media Browser Server\nVersion {0}...", version);
+
+ _progress = progress;
+
+ progress.ProgressChanged += progress_ProgressChanged;
+ }
+
+ void progress_ProgressChanged(object sender, double e)
+ {
+ Dispatcher.InvokeAsync(() =>
+ {
+ var width = e * 6.62;
+
+ RectProgress.Width = width;
+ });
+ }
+
+ protected override void OnClosing(CancelEventArgs e)
+ {
+ _progress.ProgressChanged += progress_ProgressChanged;
+
+ base.OnClosing(e);
}
}
}
diff --git a/MediaBrowser.ServerApplication/packages.config b/MediaBrowser.ServerApplication/packages.config
index f7af7084e..740cfe5f3 100644
--- a/MediaBrowser.ServerApplication/packages.config
+++ b/MediaBrowser.ServerApplication/packages.config
@@ -3,5 +3,5 @@
<package id="Hardcodet.Wpf.TaskbarNotification" version="1.0.4.0" targetFramework="net45" />
<package id="MediaBrowser.IsoMounting" version="3.0.65" targetFramework="net45" />
<package id="NLog" version="2.1.0" targetFramework="net45" />
- <package id="SimpleInjector" version="2.3.6" targetFramework="net45" />
+ <package id="SimpleInjector" version="2.4.0" targetFramework="net45" />
</packages> \ No newline at end of file
diff --git a/MediaBrowser.WebDashboard/Api/DashboardService.cs b/MediaBrowser.WebDashboard/Api/DashboardService.cs
index 371ec818b..7743cc527 100644
--- a/MediaBrowser.WebDashboard/Api/DashboardService.cs
+++ b/MediaBrowser.WebDashboard/Api/DashboardService.cs
@@ -242,7 +242,24 @@ namespace MediaBrowser.WebDashboard.Api
pages = pages.Where(p => p.ConfigurationPageType == request.PageType.Value);
}
- return ResultFactory.GetOptimizedResult(Request, pages.Select(p => new ConfigurationPageInfo(p)).ToList());
+ // Don't allow a failing plugin to fail them all
+ var configPages = pages.Select(p =>
+ {
+
+ try
+ {
+ return new ConfigurationPageInfo(p);
+ }
+ catch (Exception ex)
+ {
+ Logger.ErrorException("Error getting plugin information from {0}", ex, p.GetType().Name);
+ return null;
+ }
+ })
+ .Where(i => i != null)
+ .ToList();
+
+ return ResultFactory.GetOptimizedResult(Request, configPages);
}
/// <summary>
@@ -258,7 +275,9 @@ namespace MediaBrowser.WebDashboard.Api
// Don't cache if not configured to do so
// But always cache images to simulate production
- if (!_serverConfigurationManager.Configuration.EnableDashboardResponseCaching && !contentType.StartsWith("image/", StringComparison.OrdinalIgnoreCase))
+ if (!_serverConfigurationManager.Configuration.EnableDashboardResponseCaching &&
+ !contentType.StartsWith("image/", StringComparison.OrdinalIgnoreCase) &&
+ !contentType.StartsWith("font/", StringComparison.OrdinalIgnoreCase))
{
return ResultFactory.GetResult(GetResourceStream(path).Result, contentType);
}
@@ -267,7 +286,7 @@ namespace MediaBrowser.WebDashboard.Api
// Cache images unconditionally - updates to image files will require new filename
// If there's a version number in the query string we can cache this unconditionally
- if (contentType.StartsWith("image/", StringComparison.OrdinalIgnoreCase) || !string.IsNullOrEmpty(request.V))
+ if (contentType.StartsWith("image/", StringComparison.OrdinalIgnoreCase) || contentType.StartsWith("font/", StringComparison.OrdinalIgnoreCase) || !string.IsNullOrEmpty(request.V))
{
cacheDuration = TimeSpan.FromDays(365);
}
@@ -397,8 +416,7 @@ namespace MediaBrowser.WebDashboard.Api
var files = new[]
{
- "http://code.jquery.com/mobile/1.3.2/jquery.mobile-1.3.2.min.css",
- "thirdparty/jqm-icon-pack-3.0/font-awesome/jqm-icon-pack-3.0.0-fa.css" + versionString,
+ "thirdparty/jquerymobile-1.4.0/jquery.mobile-1.4.0.min.css",
"css/all.css" + versionString
};
@@ -423,10 +441,8 @@ namespace MediaBrowser.WebDashboard.Api
var files = new[]
{
- "http://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js",
- "http://code.jquery.com/mobile/1.3.2/jquery.mobile-1.3.2.min.js",
"scripts/all.js" + versionString,
- "thirdparty/jstree1.0/jquery.jstree.js"
+ "thirdparty/jstree1.0/jquery.jstree.min.js"
};
var tags = files.Select(s => string.Format("<script src=\"{0}\"></script>", s)).ToArray();
@@ -449,21 +465,22 @@ namespace MediaBrowser.WebDashboard.Api
"extensions.js",
"site.js",
"librarybrowser.js",
+ "librarymenu.js",
"ratingdialog.js",
"aboutpage.js",
"allusersettings.js",
"alphapicker.js",
"addpluginpage.js",
"advancedconfigurationpage.js",
- "advancedmetadataconfigurationpage.js",
+ "metadataadvanced.js",
"boxsets.js",
- "clientsettings.js",
+ "appsplayback.js",
+ "appsweather.js",
"dashboardpage.js",
"directorybrowser.js",
"edititemmetadata.js",
"edititempeople.js",
"edititemimages.js",
- "edituserpage.js",
"gamesrecommendedpage.js",
"gamesystemspage.js",
"gamespage.js",
@@ -478,9 +495,13 @@ namespace MediaBrowser.WebDashboard.Api
"livetvchannel.js",
"livetvchannels.js",
"livetvguide.js",
+ "livetvnewrecording.js",
+ "livetvprogram.js",
"livetvrecording.js",
"livetvrecordings.js",
"livetvtimer.js",
+ "livetvseriestimer.js",
+ "livetvseriestimers.js",
"livetvtimers.js",
"loginpage.js",
"logpage.js",
@@ -520,11 +541,14 @@ namespace MediaBrowser.WebDashboard.Api
"tvshows.js",
"tvstudios.js",
"tvupcoming.js",
- "updatepasswordpage.js",
+ "useredit.js",
+ "userpassword.js",
"userimagepage.js",
"userprofilespage.js",
"usersettings.js",
+ "userparentalcontrol.js",
"wizardfinishpage.js",
+ "wizardimagesettings.js",
"wizardservice.js",
"wizardstartpage.js",
"wizardsettings.js",
@@ -532,17 +556,20 @@ namespace MediaBrowser.WebDashboard.Api
};
var memoryStream = new MemoryStream();
-
var newLineBytes = Encoding.UTF8.GetBytes(Environment.NewLine);
+ await AppendResource(memoryStream, "thirdparty/jquery-2.0.3.min.js", newLineBytes).ConfigureAwait(false);
+ await AppendResource(memoryStream, "thirdparty/jquerymobile-1.4.0/jquery.mobile-1.4.0.min.js", newLineBytes).ConfigureAwait(false);
+
+ //await AppendResource(memoryStream, "thirdparty/jquery.infinite-scroll-helper.min.js", newLineBytes).ConfigureAwait(false);
+
var versionString = string.Format("window.dashboardVersion='{0}';", _appHost.ApplicationVersion);
var versionBytes = Encoding.UTF8.GetBytes(versionString);
await memoryStream.WriteAsync(versionBytes, 0, versionBytes.Length).ConfigureAwait(false);
await memoryStream.WriteAsync(newLineBytes, 0, newLineBytes.Length).ConfigureAwait(false);
- await AppendResource(memoryStream, "thirdparty/autoNumeric.js", newLineBytes).ConfigureAwait(false);
- await AppendResource(memoryStream, "thirdparty/html5slider.js", newLineBytes).ConfigureAwait(false);
+ await AppendResource(memoryStream, "thirdparty/autonumeric/autoNumeric.min.js", newLineBytes).ConfigureAwait(false);
await AppendResource(assembly, memoryStream, "MediaBrowser.WebDashboard.ApiClient.js", newLineBytes).ConfigureAwait(false);
@@ -564,6 +591,7 @@ namespace MediaBrowser.WebDashboard.Api
var files = new[]
{
"site.css",
+ "mediaplayer.css",
"librarybrowser.css",
"detailtable.css",
"posteritem.css",
@@ -573,7 +601,9 @@ namespace MediaBrowser.WebDashboard.Api
"search.css",
"pluginupdates.css",
"remotecontrol.css",
- "userimage.css"
+ "userimage.css",
+ "livetv.css",
+ "icons.css"
};
var memoryStream = new MemoryStream();
diff --git a/MediaBrowser.WebDashboard/ApiClient.js b/MediaBrowser.WebDashboard/ApiClient.js
index 16e2ae3b9..274be16b9 100644
--- a/MediaBrowser.WebDashboard/ApiClient.js
+++ b/MediaBrowser.WebDashboard/ApiClient.js
@@ -389,13 +389,21 @@ MediaBrowser.ApiClient = function ($, navigator, JSON, WebSocket, setTimeout, wi
});
};
- self.getLiveTvChannel = function (id) {
+ self.getLiveTvChannel = function (id, userId) {
if (!id) {
throw new Error("null id");
}
- var url = self.getUrl("LiveTv/Channels/" + id);
+ var options = {
+
+ };
+
+ if (userId) {
+ options.userId = userId;
+ }
+
+ var url = self.getUrl("LiveTv/Channels/" + id, options);
return self.ajax({
type: "GET",
@@ -437,13 +445,55 @@ MediaBrowser.ApiClient = function ($, navigator, JSON, WebSocket, setTimeout, wi
});
};
- self.getLiveTvRecording = function (id) {
+ self.getLiveTvRecordingGroups = function (options) {
+
+ var url = self.getUrl("LiveTv/Recordings/Groups", options || {});
+
+ return self.ajax({
+ type: "GET",
+ url: url,
+ dataType: "json"
+ });
+ };
+
+ self.getLiveTvRecording = function (id, userId) {
if (!id) {
throw new Error("null id");
}
- var url = self.getUrl("LiveTv/Recordings/" + id);
+ var options = {
+
+ };
+
+ if (userId) {
+ options.userId = userId;
+ }
+
+ var url = self.getUrl("LiveTv/Recordings/" + id, options);
+
+ return self.ajax({
+ type: "GET",
+ url: url,
+ dataType: "json"
+ });
+ };
+
+ self.getLiveTvProgram = function (id, userId) {
+
+ if (!id) {
+ throw new Error("null id");
+ }
+
+ var options = {
+
+ };
+
+ if (userId) {
+ options.userId = userId;
+ }
+
+ var url = self.getUrl("LiveTv/Programs/" + id, options);
return self.ajax({
type: "GET",
@@ -506,20 +556,123 @@ MediaBrowser.ApiClient = function ($, navigator, JSON, WebSocket, setTimeout, wi
});
};
- self.createLiveTvTimer = function (options) {
+ self.getNewLiveTvTimerDefaults = function (options) {
- if (!options) {
- throw new Error("null options");
+ options = options || {};
+
+ var url = self.getUrl("LiveTv/Timers/Defaults", options);
+
+ return self.ajax({
+ type: "GET",
+ url: url,
+ dataType: "json"
+ });
+ };
+
+ self.createLiveTvTimer = function (item) {
+
+ if (!item) {
+ throw new Error("null item");
+ }
+
+ var url = self.getUrl("LiveTv/Timers");
+
+ return self.ajax({
+ type: "POST",
+ url: url,
+ data: JSON.stringify(item),
+ contentType: "application/json"
+ });
+ };
+
+ self.updateLiveTvTimer = function (item) {
+
+ if (!item) {
+ throw new Error("null item");
}
- var url = self.getUrl("LiveTv/Timers", options);
+ var url = self.getUrl("LiveTv/Timers/" + item.Id);
return self.ajax({
type: "POST",
+ url: url,
+ data: JSON.stringify(item),
+ contentType: "application/json"
+ });
+ };
+
+ self.getLiveTvSeriesTimers = function (options) {
+
+ var url = self.getUrl("LiveTv/SeriesTimers", options || {});
+
+ return self.ajax({
+ type: "GET",
+ url: url,
+ dataType: "json"
+ });
+ };
+
+ self.getLiveTvSeriesTimer = function (id) {
+
+ if (!id) {
+ throw new Error("null id");
+ }
+
+ var url = self.getUrl("LiveTv/SeriesTimers/" + id);
+
+ return self.ajax({
+ type: "GET",
+ url: url,
+ dataType: "json"
+ });
+ };
+
+ self.cancelLiveTvSeriesTimer = function (id) {
+
+ if (!id) {
+ throw new Error("null id");
+ }
+
+ var url = self.getUrl("LiveTv/SeriesTimers/" + id);
+
+ return self.ajax({
+ type: "DELETE",
url: url
});
};
+ self.createLiveTvSeriesTimer = function (item) {
+
+ if (!item) {
+ throw new Error("null item");
+ }
+
+ var url = self.getUrl("LiveTv/SeriesTimers");
+
+ return self.ajax({
+ type: "POST",
+ url: url,
+ data: JSON.stringify(item),
+ contentType: "application/json"
+ });
+ };
+
+ self.updateLiveTvSeriesTimer = function (item) {
+
+ if (!item) {
+ throw new Error("null item");
+ }
+
+ var url = self.getUrl("LiveTv/SeriesTimers/" + item.Id);
+
+ return self.ajax({
+ type: "POST",
+ url: url,
+ data: JSON.stringify(item),
+ contentType: "application/json"
+ });
+ };
+
/**
* Gets the current server status
*/
@@ -1175,10 +1328,9 @@ MediaBrowser.ApiClient = function ($, navigator, JSON, WebSocket, setTimeout, wi
var url = userId ? "Users/" + userId + "/VirtualFolders" : "Library/VirtualFolders";
- url += "/" + name;
-
url = self.getUrl(url, {
- refreshLibrary: refreshLibrary ? true : false
+ refreshLibrary: refreshLibrary ? true : false,
+ name: name
});
return self.ajax({
@@ -1204,10 +1356,10 @@ MediaBrowser.ApiClient = function ($, navigator, JSON, WebSocket, setTimeout, wi
}
options.refreshLibrary = refreshLibrary ? true : false;
+ options.name = name;
var url = userId ? "Users/" + userId + "/VirtualFolders" : "Library/VirtualFolders";
- url += "/" + name;
url = self.getUrl(url, options);
return self.ajax({
@@ -1228,11 +1380,12 @@ MediaBrowser.ApiClient = function ($, navigator, JSON, WebSocket, setTimeout, wi
var url = userId ? "Users/" + userId + "/VirtualFolders" : "Library/VirtualFolders";
- url += "/" + name + "/Name";
+ url += "/Name";
url = self.getUrl(url, {
refreshLibrary: refreshLibrary ? true : false,
- newName: newName
+ newName: newName,
+ name: name
});
return self.ajax({
@@ -1257,11 +1410,12 @@ MediaBrowser.ApiClient = function ($, navigator, JSON, WebSocket, setTimeout, wi
var url = userId ? "Users/" + userId + "/VirtualFolders" : "Library/VirtualFolders";
- url += "/" + virtualFolderName + "/Paths";
+ url += "/Paths";
url = self.getUrl(url, {
refreshLibrary: refreshLibrary ? true : false,
- path: mediaPath
+ path: mediaPath,
+ name: virtualFolderName
});
return self.ajax({
@@ -1286,11 +1440,12 @@ MediaBrowser.ApiClient = function ($, navigator, JSON, WebSocket, setTimeout, wi
var url = userId ? "Users/" + userId + "/VirtualFolders" : "Library/VirtualFolders";
- url += "/" + virtualFolderName + "/Paths";
+ url += "/Paths";
url = self.getUrl(url, {
refreshLibrary: refreshLibrary ? true : false,
- path: mediaPath
+ path: mediaPath,
+ name: virtualFolderName
});
return self.ajax({
diff --git a/MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj b/MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj
index 35ed3f1e2..e11c8390f 100644
--- a/MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj
+++ b/MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj
@@ -9,12 +9,12 @@
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>MediaBrowser.WebDashboard</RootNamespace>
<AssemblyName>MediaBrowser.WebDashboard</AssemblyName>
- <TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<SolutionDir Condition="$(SolutionDir) == '' Or $(SolutionDir) == '*Undefined*'">..\</SolutionDir>
<RestorePackages>true</RestorePackages>
<ProductVersion>10.0.0</ProductVersion>
<SchemaVersion>2.0</SchemaVersion>
+ <TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
@@ -24,6 +24,7 @@
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
+ <TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
@@ -32,21 +33,30 @@
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
+ <TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
+ </PropertyGroup>
+ <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release Mono|AnyCPU' ">
+ <DebugType>pdbonly</DebugType>
+ <Optimize>true</Optimize>
+ <OutputPath>bin\Release Mono\</OutputPath>
+ <DefineConstants>TRACE</DefineConstants>
+ <ErrorReport>prompt</ErrorReport>
+ <WarningLevel>4</WarningLevel>
+ <TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
</PropertyGroup>
<PropertyGroup>
<RunPostBuildEvent>Always</RunPostBuildEvent>
</PropertyGroup>
<ItemGroup>
- <Reference Include="ServiceStack.Interfaces, Version=1.0.0.0, Culture=neutral, processorArchitecture=MSIL">
- <SpecificVersion>False</SpecificVersion>
- <HintPath>..\ThirdParty\ServiceStack\ServiceStack.Interfaces.dll</HintPath>
- </Reference>
<Reference Include="System" />
<Reference Include="System.Core" />
<Reference Include="System.Data.DataSetExtensions" />
<Reference Include="Microsoft.CSharp" />
<Reference Include="System.Data" />
<Reference Include="System.Xml" />
+ <Reference Include="ServiceStack.Interfaces">
+ <HintPath>..\ThirdParty\ServiceStack\ServiceStack.Interfaces.dll</HintPath>
+ </Reference>
</ItemGroup>
<ItemGroup>
<Compile Include="..\SharedVersion.cs">
@@ -75,18 +85,78 @@
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="ApiClient.js" />
- <Content Include="dashboard-ui\css\images\editor.png">
+ <Content Include="dashboard-ui\appsplayback.html">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\css\icons.css">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\css\images\clients\xbmc.png">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\css\images\icons\audiocd.png">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\css\images\icons\filter.png">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\css\images\icons\mute.png">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\css\images\icons\nexttrack.png">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\css\images\icons\pause.png">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\css\images\icons\play.png">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\css\images\icons\previoustrack.png">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\css\images\icons\remote.png">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\css\images\icons\sort.png">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\css\images\icons\stop.png">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\css\images\icons\subtitles.png">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\css\images\icons\volumedown.png">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\css\images\icons\volumeup.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="dashboard-ui\css\images\items\detail\tv.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
+ <Content Include="dashboard-ui\css\livetv.css">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\css\mediaplayer.css">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
<Content Include="dashboard-ui\livetvchannel.html">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
+ <Content Include="dashboard-ui\livetvnewrecording.html">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\livetvprogram.html">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
<Content Include="dashboard-ui\livetvrecording.html">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
+ <Content Include="dashboard-ui\livetvseriestimer.html">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
<Content Include="dashboard-ui\livetvtimers.html">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
@@ -120,9 +190,6 @@
<Content Include="dashboard-ui\css\images\editor\missingtrailer.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
- <Content Include="dashboard-ui\css\images\remote.png">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
<Content Include="dashboard-ui\css\metadataeditor.css">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
@@ -135,9 +202,6 @@
<Content Include="dashboard-ui\css\detailtable.css">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
- <Content Include="dashboard-ui\css\images\bgflip.png">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
<Content Include="dashboard-ui\css\images\clients\chrome.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
@@ -159,19 +223,19 @@
<Content Include="dashboard-ui\css\images\fresh.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
- <Content Include="dashboard-ui\css\images\items\searchhints\film.png">
+ <Content Include="dashboard-ui\css\images\items\searchhintsv2\film.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
- <Content Include="dashboard-ui\css\images\items\searchhints\game.png">
+ <Content Include="dashboard-ui\css\images\items\searchhintsv2\game.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
- <Content Include="dashboard-ui\css\images\items\searchhints\music.png">
+ <Content Include="dashboard-ui\css\images\items\searchhintsv2\music.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
- <Content Include="dashboard-ui\css\images\items\searchhints\person.png">
+ <Content Include="dashboard-ui\css\images\items\searchhintsv2\person.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
- <Content Include="dashboard-ui\css\images\items\searchhints\tv.png">
+ <Content Include="dashboard-ui\css\images\items\searchhintsv2\tv.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="dashboard-ui\css\images\media\audioflyout.png">
@@ -216,9 +280,6 @@
<Content Include="dashboard-ui\css\images\rotten.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
- <Content Include="dashboard-ui\css\images\searchbutton.png">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
<Content Include="dashboard-ui\css\images\currentuserdefaultblack.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
@@ -342,15 +403,30 @@
<Content Include="dashboard-ui\livetvrecordings.html">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
+ <Content Include="dashboard-ui\scripts\appsplayback.js">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\scripts\librarymenu.js">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
<Content Include="dashboard-ui\scripts\livetvchannel.js">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="dashboard-ui\livetvtimer.html">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
+ <Content Include="dashboard-ui\scripts\livetvnewrecording.js">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\scripts\livetvprogram.js">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
<Content Include="dashboard-ui\scripts\livetvrecording.js">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
+ <Content Include="dashboard-ui\scripts\livetvseriestimer.js">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
<Content Include="dashboard-ui\scripts\livetvtimer.js">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
@@ -375,24 +451,663 @@
<Content Include="dashboard-ui\scripts\episodes.js">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
+ <Content Include="dashboard-ui\scripts\livetvseriestimers.js">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
<Content Include="dashboard-ui\scripts\tvupcoming.js">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
+ <Content Include="dashboard-ui\scripts\userparentalcontrol.js">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
<Content Include="dashboard-ui\scripts\usersettings.js">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
+ <Content Include="dashboard-ui\scripts\wizardimagesettings.js">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
<Content Include="dashboard-ui\scripts\wizardservice.js">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="dashboard-ui\scripts\wizardsettings.js">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
+ <Content Include="dashboard-ui\livetvseriestimers.html">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\autonumeric\autoNumeric.min.js">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquery-2.0.3.min.js">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquery.infinite-scroll-helper.min.js">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\ajax-loader.gif">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-png\action-black.png">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-png\action-white.png">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-png\alert-black.png">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-png\alert-white.png">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-png\arrow-d-black.png">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-png\arrow-d-l-black.png">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-png\arrow-d-l-white.png">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-png\arrow-d-r-black.png">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-png\arrow-d-r-white.png">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-png\arrow-d-white.png">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-png\arrow-l-black.png">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-png\arrow-l-white.png">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-png\arrow-r-black.png">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-png\arrow-r-white.png">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-png\arrow-u-black.png">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-png\arrow-u-l-black.png">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-png\arrow-u-l-white.png">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-png\arrow-u-r-black.png">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-png\arrow-u-r-white.png">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-png\arrow-u-white.png">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-png\audio-black.png">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-png\audio-white.png">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-png\back-black.png">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-png\back-white.png">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-png\bars-black.png">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-png\bars-white.png">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-png\bullets-black.png">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-png\bullets-white.png">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-png\calendar-black.png">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-png\calendar-white.png">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-png\camera-black.png">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-png\camera-white.png">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-png\carat-d-black.png">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-png\carat-d-white.png">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-png\carat-l-black.png">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-png\carat-l-white.png">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-png\carat-r-black.png">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-png\carat-r-white.png">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-png\carat-u-black.png">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-png\carat-u-white.png">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-png\check-black.png">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-png\check-white.png">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-png\clock-black.png">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-png\clock-white.png">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-png\cloud-black.png">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-png\cloud-white.png">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-png\comment-black.png">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-png\comment-white.png">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-png\delete-black.png">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-png\delete-white.png">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-png\edit-black.png">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-png\edit-white.png">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-png\eye-black.png">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-png\eye-white.png">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-png\forbidden-black.png">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-png\forbidden-white.png">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-png\forward-black.png">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-png\forward-white.png">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-png\gear-black.png">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-png\gear-white.png">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-png\grid-black.png">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-png\grid-white.png">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-png\heart-black.png">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-png\heart-white.png">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-png\home-black.png">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-png\home-white.png">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-png\info-black.png">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-png\info-white.png">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-png\location-black.png">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-png\location-white.png">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-png\lock-black.png">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-png\lock-white.png">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-png\mail-black.png">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-png\mail-white.png">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-png\minus-black.png">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-png\minus-white.png">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-png\navigation-black.png">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-png\navigation-white.png">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-png\phone-black.png">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-png\phone-white.png">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-png\plus-black.png">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-png\plus-white.png">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-png\power-black.png">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-png\power-white.png">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-png\recycle-black.png">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-png\recycle-white.png">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-png\refresh-black.png">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-png\refresh-white.png">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-png\search-black.png">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-png\search-white.png">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-png\shop-black.png">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-png\shop-white.png">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-png\star-black.png">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-png\star-white.png">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-png\tag-black.png">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-png\tag-white.png">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-png\user-black.png">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-png\user-white.png">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-png\video-black.png">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-png\video-white.png">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-svg\action-black.svg">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-svg\action-white.svg">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-svg\alert-black.svg">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-svg\alert-white.svg">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-svg\arrow-d-black.svg">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-svg\arrow-d-l-black.svg">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-svg\arrow-d-l-white.svg">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-svg\arrow-d-r-black.svg">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-svg\arrow-d-r-white.svg">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-svg\arrow-d-white.svg">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-svg\arrow-l-black.svg">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-svg\arrow-l-white.svg">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-svg\arrow-r-black.svg">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-svg\arrow-r-white.svg">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-svg\arrow-u-black.svg">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-svg\arrow-u-l-black.svg">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-svg\arrow-u-l-white.svg">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-svg\arrow-u-r-black.svg">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-svg\arrow-u-r-white.svg">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-svg\arrow-u-white.svg">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-svg\audio-black.svg">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-svg\audio-white.svg">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-svg\back-black.svg">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-svg\back-white.svg">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-svg\bars-black.svg">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-svg\bars-white.svg">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-svg\bullets-black.svg">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-svg\bullets-white.svg">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-svg\calendar-black.svg">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-svg\calendar-white.svg">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-svg\camera-black.svg">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-svg\camera-white.svg">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-svg\carat-d-black.svg">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-svg\carat-d-white.svg">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-svg\carat-l-black.svg">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-svg\carat-l-white.svg">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-svg\carat-r-black.svg">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-svg\carat-r-white.svg">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-svg\carat-u-black.svg">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-svg\carat-u-white.svg">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-svg\check-black.svg">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-svg\check-white.svg">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-svg\clock-black.svg">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-svg\clock-white.svg">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-svg\cloud-black.svg">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-svg\cloud-white.svg">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-svg\comment-black.svg">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-svg\comment-white.svg">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-svg\delete-black.svg">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-svg\delete-white.svg">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-svg\edit-black.svg">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-svg\edit-white.svg">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-svg\eye-black.svg">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-svg\eye-white.svg">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-svg\forbidden-black.svg">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-svg\forbidden-white.svg">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-svg\forward-black.svg">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-svg\forward-white.svg">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-svg\gear-black.svg">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-svg\gear-white.svg">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-svg\grid-black.svg">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-svg\grid-white.svg">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-svg\heart-black.svg">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-svg\heart-white.svg">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-svg\home-black.svg">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-svg\home-white.svg">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-svg\info-black.svg">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-svg\info-white.svg">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-svg\location-black.svg">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-svg\location-white.svg">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-svg\lock-black.svg">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-svg\lock-white.svg">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-svg\mail-black.svg">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-svg\mail-white.svg">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-svg\minus-black.svg">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-svg\minus-white.svg">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-svg\navigation-black.svg">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-svg\navigation-white.svg">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-svg\phone-black.svg">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-svg\phone-white.svg">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-svg\plus-black.svg">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-svg\plus-white.svg">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-svg\power-black.svg">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-svg\power-white.svg">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-svg\recycle-black.svg">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-svg\recycle-white.svg">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-svg\refresh-black.svg">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-svg\refresh-white.svg">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-svg\search-black.svg">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-svg\search-white.svg">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-svg\shop-black.svg">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-svg\shop-white.svg">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-svg\star-black.svg">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-svg\star-white.svg">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-svg\tag-black.svg">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-svg\tag-white.svg">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-svg\user-black.svg">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-svg\user-white.svg">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-svg\video-black.svg">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\images\icons-svg\video-white.svg">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\jquery.mobile-1.4.0.min.css">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.0\jquery.mobile-1.4.0.min.js">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\thirdparty\jstree1.0\jquery.jstree.min.js">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
<Content Include="dashboard-ui\tvupcoming.html">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
+ <Content Include="dashboard-ui\userparentalcontrol.html">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
<Content Include="dashboard-ui\usersettings.html">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
+ <Content Include="dashboard-ui\wizardimagesettings.html">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
<Content Include="dashboard-ui\wizardservice.html">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
@@ -596,10 +1311,7 @@
<Content Include="dashboard-ui\scripts\tvstudios.js">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
- <Content Include="dashboard-ui\thirdparty\autoNumeric.js">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\thirdparty\html5slider.js">
+ <Content Include="dashboard-ui\thirdparty\autonumeric\autoNumeric.js">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="dashboard-ui\thirdparty\jstree1.0\jquery.jstree.js">
@@ -712,12 +1424,12 @@
</Content>
</ItemGroup>
<ItemGroup>
- <Content Include="dashboard-ui\clientsettings.html">
+ <Content Include="dashboard-ui\appsweather.html">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
</ItemGroup>
<ItemGroup>
- <Content Include="dashboard-ui\edituser.html">
+ <Content Include="dashboard-ui\useredit.html">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
</ItemGroup>
@@ -743,7 +1455,7 @@
</Content>
</ItemGroup>
<ItemGroup>
- <Content Include="dashboard-ui\updatepassword.html">
+ <Content Include="dashboard-ui\userpassword.html">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
</ItemGroup>
@@ -763,11 +1475,6 @@
</Content>
</ItemGroup>
<ItemGroup>
- <Content Include="dashboard-ui\css\images\toolswhite.png">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- </ItemGroup>
- <ItemGroup>
<Content Include="dashboard-ui\css\images\rightarrow.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
@@ -783,7 +1490,7 @@
</Content>
</ItemGroup>
<ItemGroup>
- <Content Include="dashboard-ui\scripts\clientsettings.js">
+ <Content Include="dashboard-ui\scripts\appsweather.js">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
</ItemGroup>
@@ -803,7 +1510,7 @@
</Content>
</ItemGroup>
<ItemGroup>
- <Content Include="dashboard-ui\scripts\updatepasswordpage.js">
+ <Content Include="dashboard-ui\scripts\userpassword.js">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
</ItemGroup>
@@ -811,7 +1518,7 @@
<Content Include="dashboard-ui\scripts\advancedconfigurationpage.js">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
- <Content Include="dashboard-ui\scripts\edituserpage.js">
+ <Content Include="dashboard-ui\scripts\useredit.js">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="dashboard-ui\scripts\userimagepage.js">
@@ -822,12 +1529,12 @@
</Content>
</ItemGroup>
<ItemGroup>
- <Content Include="dashboard-ui\advancedmetadata.html">
+ <Content Include="dashboard-ui\metadataadvanced.html">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
</ItemGroup>
<ItemGroup>
- <Content Include="dashboard-ui\scripts\advancedmetadataconfigurationpage.js">
+ <Content Include="dashboard-ui\scripts\metadataadvanced.js">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
</ItemGroup>
@@ -850,11 +1557,6 @@
</Content>
</ItemGroup>
<ItemGroup>
- <Content Include="dashboard-ui\css\images\bg.png">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- </ItemGroup>
- <ItemGroup>
<Content Include="dashboard-ui\plugincatalog.html">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
@@ -863,21 +1565,6 @@
</Content>
</ItemGroup>
<ItemGroup>
- <Content Include="dashboard-ui\css\images\leftarrowblack.png">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- </ItemGroup>
- <ItemGroup>
- <Content Include="dashboard-ui\css\images\leftarrowwhite.png">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- </ItemGroup>
- <ItemGroup>
- <Content Include="dashboard-ui\css\images\toolsblack.png">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- </ItemGroup>
- <ItemGroup>
<Content Include="dashboard-ui\scheduledtasks.html">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
@@ -1037,86 +1724,11 @@
</Content>
</ItemGroup>
<ItemGroup>
- <Content Include="dashboard-ui\thirdparty\jqm-icon-pack-3.0\font-awesome\faicons-v2.png">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\thirdparty\jqm-icon-pack-3.0\font-awesome\faicons.png">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\thirdparty\jqm-icon-pack-3.0\font-awesome\images\ajax-loader.png">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\thirdparty\jqm-icon-pack-3.0\font-awesome\images\icons-18-black-pack.png">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\thirdparty\jqm-icon-pack-3.0\font-awesome\images\icons-18-white-pack.png">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\thirdparty\jqm-icon-pack-3.0\font-awesome\images\icons-36-black-pack.png">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\thirdparty\jqm-icon-pack-3.0\font-awesome\images\icons-36-white-pack.png">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\thirdparty\jqm-icon-pack-3.0\font-awesome\index.html">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\thirdparty\jqm-icon-pack-3.0\font-awesome\jqm-icon-pack-3.0.0-fa.css">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\thirdparty\jqm-icon-pack-3.0\original\images\ajax-loader.gif">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\thirdparty\jqm-icon-pack-3.0\original\images\ajax-loader.png">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\thirdparty\jqm-icon-pack-3.0\original\images\icons-18-black-pack.png">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\thirdparty\jqm-icon-pack-3.0\original\images\icons-18-white-pack.png">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\thirdparty\jqm-icon-pack-3.0\original\images\icons-36-black-pack.png">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\thirdparty\jqm-icon-pack-3.0\original\images\icons-36-white-pack.png">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\thirdparty\jqm-icon-pack-3.0\original\index.html">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\thirdparty\jqm-icon-pack-3.0\original\jqm-icon-pack-2.0-original.css">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- </ItemGroup>
- <ItemGroup>
- <Content Include="dashboard-ui\thirdparty\jqm-icon-pack-3.0\font-awesome\font\fontawesome-webfont.eot">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\thirdparty\jqm-icon-pack-3.0\font-awesome\font\fontawesome-webfont.ttf">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\thirdparty\jqm-icon-pack-3.0\font-awesome\font\fontawesome-webfont.woff">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\thirdparty\jqm-icon-pack-3.0\font-awesome\font\FontAwesome.otf">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\thirdparty\jqm-icon-pack-3.0\font-awesome\jqm-icon-pack-3.0.0-fa.scss">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- </ItemGroup>
- <ItemGroup>
<Content Include="dashboard-ui\css\images\supporter\registerpaypal.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
</ItemGroup>
<ItemGroup>
- <Content Include="dashboard-ui\css\images\home.png">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- </ItemGroup>
- <ItemGroup>
<Content Include="dashboard-ui\css\images\notifications\done.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
@@ -1210,14 +1822,27 @@
</Content>
</ItemGroup>
<ItemGroup>
- <EmbeddedResource Include="packages.config" />
+ <None Include="dashboard-ui\css\fonts\OpenSans-ExtraBold.woff">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </None>
+ <None Include="dashboard-ui\css\fonts\OpenSans-Bold.woff">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </None>
+ <None Include="dashboard-ui\css\fonts\OpenSans.woff">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </None>
+ <None Include="dashboard-ui\css\fonts\OpenSans-Light.woff">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </None>
+ <None Include="packages.config" />
</ItemGroup>
+ <ItemGroup />
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<PropertyGroup>
<PostBuildEvent>
</PostBuildEvent>
</PropertyGroup>
- <Import Project="$(SolutionDir)\.nuget\nuget.targets" />
+ <Import Project="$(SolutionDir)\.nuget\nuget.targets" Condition=" '$(ConfigurationName)' != 'Release Mono' " />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.
<Target Name="BeforeBuild">
diff --git a/MediaBrowser.WebDashboard/packages.config b/MediaBrowser.WebDashboard/packages.config
index 9cbd2cd59..4cc0c4d55 100644
--- a/MediaBrowser.WebDashboard/packages.config
+++ b/MediaBrowser.WebDashboard/packages.config
@@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
- <package id="MediaBrowser.ApiClient.Javascript" version="3.0.204" targetFramework="net45" />
+ <package id="MediaBrowser.ApiClient.Javascript" version="3.0.213" targetFramework="net45" />
</packages> \ No newline at end of file
diff --git a/Nuget/MediaBrowser.Common.Internal.nuspec b/Nuget/MediaBrowser.Common.Internal.nuspec
index 1531534db..cdbd51d84 100644
--- a/Nuget/MediaBrowser.Common.Internal.nuspec
+++ b/Nuget/MediaBrowser.Common.Internal.nuspec
@@ -2,7 +2,7 @@
<package xmlns="http://schemas.microsoft.com/packaging/2011/08/nuspec.xsd">
<metadata>
<id>MediaBrowser.Common.Internal</id>
- <version>3.0.262</version>
+ <version>3.0.293</version>
<title>MediaBrowser.Common.Internal</title>
<authors>Luke</authors>
<owners>ebr,Luke,scottisafool</owners>
@@ -12,13 +12,14 @@
<description>Contains common components shared by Media Browser Theater and Media Browser Server. Not intended for plugin developer consumption.</description>
<copyright>Copyright © Media Browser 2013</copyright>
<dependencies>
- <dependency id="MediaBrowser.Common" version="3.0.262" />
+ <dependency id="MediaBrowser.Common" version="3.0.293" />
<dependency id="NLog" version="2.1.0" />
- <dependency id="SimpleInjector" version="2.3.6" />
+ <dependency id="SimpleInjector" version="2.4.0" />
<dependency id="sharpcompress" version="0.10.2" />
</dependencies>
</metadata>
<files>
<file src="dlls\MediaBrowser.Common.Implementations.dll" target="lib\net45\MediaBrowser.Common.Implementations.dll" />
+ <file src="..\ThirdParty\ServiceStack.Text\ServiceStack.Text.dll" target="lib\net45\ServiceStack.Text.dll" />
</files>
</package> \ No newline at end of file
diff --git a/Nuget/MediaBrowser.Common.nuspec b/Nuget/MediaBrowser.Common.nuspec
index d8f155a8a..20bdf1300 100644
--- a/Nuget/MediaBrowser.Common.nuspec
+++ b/Nuget/MediaBrowser.Common.nuspec
@@ -2,7 +2,7 @@
<package xmlns="http://schemas.microsoft.com/packaging/2011/08/nuspec.xsd">
<metadata>
<id>MediaBrowser.Common</id>
- <version>3.0.262</version>
+ <version>3.0.293</version>
<title>MediaBrowser.Common</title>
<authors>Media Browser Team</authors>
<owners>ebr,Luke,scottisafool</owners>
diff --git a/Nuget/MediaBrowser.Server.Core.nuspec b/Nuget/MediaBrowser.Server.Core.nuspec
index e8a63a36a..8f040c00f 100644
--- a/Nuget/MediaBrowser.Server.Core.nuspec
+++ b/Nuget/MediaBrowser.Server.Core.nuspec
@@ -2,7 +2,7 @@
<package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd">
<metadata>
<id>MediaBrowser.Server.Core</id>
- <version>3.0.262</version>
+ <version>3.0.293</version>
<title>Media Browser.Server.Core</title>
<authors>Media Browser Team</authors>
<owners>ebr,Luke,scottisafool</owners>
@@ -12,7 +12,7 @@
<description>Contains core components required to build plugins for Media Browser Server.</description>
<copyright>Copyright © Media Browser 2013</copyright>
<dependencies>
- <dependency id="MediaBrowser.Common" version="3.0.262" />
+ <dependency id="MediaBrowser.Common" version="3.0.293" />
</dependencies>
</metadata>
<files>
diff --git a/README.md b/README.md
index 43cf18bdc..c06ffec0e 100644
--- a/README.md
+++ b/README.md
@@ -11,7 +11,7 @@ We have several client apps released and in production:
- Html5
- [iOS](https://itunes.apple.com/us/app/media-browser-for-ios/id705058087 "iOS")
- [Media Portal](http://www.team-mediaportal.com/ "Media Portal")
-- Roku
+- [Roku](http://www.roku.com/channels/#!details/34503/media-browser "Roku")
- Windows 7/8 Desktop
- Windows Media Center
- [Windows Phone](http://www.windowsphone.com/s?appid=f4971ed9-f651-4bf6-84bb-94fd98613b86 "Windows Phone")
@@ -36,8 +36,8 @@ http://mediabrowser3.com/community
## Current Versions ##
-Release: 3.0.4999.38224<br/>
-Beta: 3.0.5028.39800<br/>
+Release: 3.0.5099.2102<br/>
+Beta: 3.0.5097.16641<br/>
## Images