aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLuke <luke.pulverenti@gmail.com>2016-06-30 15:27:06 -0400
committerGitHub <noreply@github.com>2016-06-30 15:27:06 -0400
commit2708df6cc28c48a89416bdfbdde7e78fc4227c62 (patch)
tree9f892d6350a4d694c96985d679f622c0f7005278
parentd9406d48ca0231bc096aeadc595c30f0596c8dda (diff)
parent5bdc96bb6a9b863980661e2d11c1ad00a02eb601 (diff)
Merge pull request #1899 from MediaBrowser/beta
Beta
-rw-r--r--Emby.Drawing/Emby.Drawing.csproj1
-rw-r--r--Emby.Drawing/ImageMagick/ImageMagickEncoder.cs17
-rw-r--r--Emby.Drawing/ImageMagick/StripCollageBuilder.cs351
-rw-r--r--MediaBrowser.Api/BaseApiService.cs2
-rw-r--r--MediaBrowser.Api/ConfigurationService.cs22
-rw-r--r--MediaBrowser.Api/FilterService.cs2
-rw-r--r--MediaBrowser.Api/GamesService.cs50
-rw-r--r--MediaBrowser.Api/Images/ImageService.cs10
-rw-r--r--MediaBrowser.Api/Images/RemoteImageService.cs10
-rw-r--r--MediaBrowser.Api/ItemLookupService.cs49
-rw-r--r--MediaBrowser.Api/ItemUpdateService.cs44
-rw-r--r--MediaBrowser.Api/Library/LibraryService.cs6
-rw-r--r--MediaBrowser.Api/LiveTv/LiveTvService.cs111
-rw-r--r--MediaBrowser.Api/Movies/MoviesService.cs259
-rw-r--r--MediaBrowser.Api/Movies/TrailersService.cs2
-rw-r--r--MediaBrowser.Api/Music/AlbumsService.cs13
-rw-r--r--MediaBrowser.Api/Music/InstantMixService.cs21
-rw-r--r--MediaBrowser.Api/PackageReviewService.cs12
-rw-r--r--MediaBrowser.Api/Playback/BaseStreamingService.cs79
-rw-r--r--MediaBrowser.Api/Playback/Hls/BaseHlsService.cs4
-rw-r--r--MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs2
-rw-r--r--MediaBrowser.Api/Playback/Hls/HlsSegmentService.cs9
-rw-r--r--MediaBrowser.Api/Playback/Progressive/AudioService.cs5
-rw-r--r--MediaBrowser.Api/Playback/Progressive/BaseProgressiveStreamingService.cs28
-rw-r--r--MediaBrowser.Api/Playback/Progressive/VideoService.cs5
-rw-r--r--MediaBrowser.Api/Playback/StreamState.cs2
-rw-r--r--MediaBrowser.Api/PlaylistService.cs4
-rw-r--r--MediaBrowser.Api/Reports/ReportsService.cs1
-rw-r--r--MediaBrowser.Api/SimilarItemsHelper.cs28
-rw-r--r--MediaBrowser.Api/StartupWizardService.cs17
-rw-r--r--MediaBrowser.Api/Subtitles/SubtitleService.cs36
-rw-r--r--MediaBrowser.Api/Sync/SyncService.cs19
-rw-r--r--MediaBrowser.Api/System/SystemInfoWebSocketListener.cs2
-rw-r--r--MediaBrowser.Api/System/SystemService.cs16
-rw-r--r--MediaBrowser.Api/TvShowsService.cs68
-rw-r--r--MediaBrowser.Api/UserLibrary/ArtistsService.cs40
-rw-r--r--MediaBrowser.Api/UserLibrary/BaseItemsByNameService.cs141
-rw-r--r--MediaBrowser.Api/UserLibrary/BaseItemsRequest.cs3
-rw-r--r--MediaBrowser.Api/UserLibrary/GameGenresService.cs29
-rw-r--r--MediaBrowser.Api/UserLibrary/GenresService.cs61
-rw-r--r--MediaBrowser.Api/UserLibrary/ItemsService.cs70
-rw-r--r--MediaBrowser.Api/UserLibrary/MusicGenresService.cs21
-rw-r--r--MediaBrowser.Api/UserLibrary/PlaystateService.cs6
-rw-r--r--MediaBrowser.Api/UserLibrary/StudiosService.cs11
-rw-r--r--MediaBrowser.Api/UserLibrary/UserLibraryService.cs12
-rw-r--r--MediaBrowser.Api/UserService.cs9
-rw-r--r--MediaBrowser.Common.Implementations/BaseApplicationHost.cs8
-rw-r--r--MediaBrowser.Common.Implementations/HttpClientManager/HttpClientManager.cs20
-rw-r--r--MediaBrowser.Common.Implementations/MediaBrowser.Common.Implementations.csproj6
-rw-r--r--MediaBrowser.Common.Implementations/ScheduledTasks/ScheduledTaskWorker.cs39
-rw-r--r--MediaBrowser.Common.Implementations/Serialization/JsonSerializer.cs8
-rw-r--r--MediaBrowser.Common.Implementations/Serialization/XmlSerializer.cs14
-rw-r--r--MediaBrowser.Common.Implementations/packages.config4
-rw-r--r--MediaBrowser.Common/Net/HttpRequestOptions.cs3
-rw-r--r--MediaBrowser.Controller/Channels/ChannelItemInfo.cs8
-rw-r--r--MediaBrowser.Controller/Chapters/IChapterManager.cs2
-rw-r--r--MediaBrowser.Controller/Drawing/ImageCollageOptions.cs5
-rw-r--r--MediaBrowser.Controller/Dto/IDtoService.cs3
-rw-r--r--MediaBrowser.Controller/Entities/Audio/Audio.cs3
-rw-r--r--MediaBrowser.Controller/Entities/Audio/MusicAlbum.cs23
-rw-r--r--MediaBrowser.Controller/Entities/Audio/MusicArtist.cs26
-rw-r--r--MediaBrowser.Controller/Entities/Audio/MusicGenre.cs11
-rw-r--r--MediaBrowser.Controller/Entities/BaseItem.cs38
-rw-r--r--MediaBrowser.Controller/Entities/Book.cs2
-rw-r--r--MediaBrowser.Controller/Entities/Folder.cs335
-rw-r--r--MediaBrowser.Controller/Entities/Game.cs2
-rw-r--r--MediaBrowser.Controller/Entities/GameGenre.cs11
-rw-r--r--MediaBrowser.Controller/Entities/Genre.cs11
-rw-r--r--MediaBrowser.Controller/Entities/IHasUserData.cs3
-rw-r--r--MediaBrowser.Controller/Entities/InternalItemsQuery.cs15
-rw-r--r--MediaBrowser.Controller/Entities/KeywordExtensions.cs (renamed from MediaBrowser.Controller/Entities/IHasKeywords.cs)12
-rw-r--r--MediaBrowser.Controller/Entities/Movies/BoxSet.cs9
-rw-r--r--MediaBrowser.Controller/Entities/Movies/Movie.cs22
-rw-r--r--MediaBrowser.Controller/Entities/Person.cs11
-rw-r--r--MediaBrowser.Controller/Entities/Photo.cs2
-rw-r--r--MediaBrowser.Controller/Entities/Studio.cs13
-rw-r--r--MediaBrowser.Controller/Entities/TV/Episode.cs34
-rw-r--r--MediaBrowser.Controller/Entities/TV/Season.cs81
-rw-r--r--MediaBrowser.Controller/Entities/TV/Series.cs221
-rw-r--r--MediaBrowser.Controller/Entities/TagExtensions.cs (renamed from MediaBrowser.Controller/Entities/IHasTags.cs)15
-rw-r--r--MediaBrowser.Controller/Entities/Trailer.cs22
-rw-r--r--MediaBrowser.Controller/Entities/UserRootFolder.cs5
-rw-r--r--MediaBrowser.Controller/Entities/UserView.cs7
-rw-r--r--MediaBrowser.Controller/Entities/UserViewBuilder.cs121
-rw-r--r--MediaBrowser.Controller/Entities/Video.cs4
-rw-r--r--MediaBrowser.Controller/IServerApplicationHost.cs7
-rw-r--r--MediaBrowser.Controller/Library/ILibraryManager.cs15
-rw-r--r--MediaBrowser.Controller/Library/IUserDataManager.cs4
-rw-r--r--MediaBrowser.Controller/LiveTv/IListingsProvider.cs1
-rw-r--r--MediaBrowser.Controller/LiveTv/ILiveTvManager.cs18
-rw-r--r--MediaBrowser.Controller/LiveTv/ILiveTvService.cs19
-rw-r--r--MediaBrowser.Controller/LiveTv/LiveTvProgram.cs21
-rw-r--r--MediaBrowser.Controller/LiveTv/TimerEventInfo.cs14
-rw-r--r--MediaBrowser.Controller/LiveTv/TunerChannelMapping.cs16
-rw-r--r--MediaBrowser.Controller/MediaBrowser.Controller.csproj14
-rw-r--r--MediaBrowser.Controller/MediaEncoding/EncodingJobOptions.cs5
-rw-r--r--MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs24
-rw-r--r--MediaBrowser.Controller/MediaEncoding/ISubtitleEncoder.cs8
-rw-r--r--MediaBrowser.Controller/Net/IHttpResultFactory.cs8
-rw-r--r--MediaBrowser.Controller/Persistence/IItemRepository.cs14
-rw-r--r--MediaBrowser.Controller/Persistence/IUserDataRepository.cs2
-rw-r--r--MediaBrowser.Controller/Providers/BaseItemXmlParser.cs16
-rw-r--r--MediaBrowser.Controller/Providers/IItemIdentityConverter.cs4
-rw-r--r--MediaBrowser.Controller/Providers/IItemIdentityProvider.cs4
-rw-r--r--MediaBrowser.Controller/Providers/IProviderManager.cs22
-rw-r--r--MediaBrowser.Controller/Providers/IProviderRepository.cs25
-rw-r--r--MediaBrowser.Controller/Providers/ISeriesOrderManager.cs11
-rw-r--r--MediaBrowser.Controller/Providers/ISeriesOrderProvider.cs10
-rw-r--r--MediaBrowser.Controller/Providers/ItemIdentifier.cs36
-rw-r--r--MediaBrowser.Controller/Providers/ItemIdentities.cs16
-rw-r--r--MediaBrowser.Controller/Providers/ItemInfo.cs8
-rw-r--r--MediaBrowser.Controller/Providers/MetadataStatus.cs49
-rw-r--r--MediaBrowser.Controller/Session/ISessionManager.cs3
-rw-r--r--MediaBrowser.Dlna/Didl/DidlBuilder.cs28
-rw-r--r--MediaBrowser.Dlna/DlnaManager.cs44
-rw-r--r--MediaBrowser.Dlna/Main/DlnaEntryPoint.cs13
-rw-r--r--MediaBrowser.Dlna/PlayTo/PlayToController.cs39
-rw-r--r--MediaBrowser.Dlna/PlayTo/PlayToManager.cs2
-rw-r--r--MediaBrowser.Dlna/Profiles/LgTvProfile.cs15
-rw-r--r--MediaBrowser.Dlna/Profiles/SamsungSmartTvProfile.cs4
-rw-r--r--MediaBrowser.Dlna/Profiles/Xml/LG Smart TV.xml10
-rw-r--r--MediaBrowser.Dlna/Profiles/Xml/Samsung Smart TV.xml2
-rw-r--r--MediaBrowser.Dlna/Ssdp/DeviceDiscovery.cs4
-rw-r--r--MediaBrowser.LocalMetadata/Savers/XmlSaverHelpers.cs36
-rw-r--r--MediaBrowser.MediaEncoding/Encoder/AudioEncoder.cs7
-rw-r--r--MediaBrowser.MediaEncoding/Encoder/BaseEncoder.cs58
-rw-r--r--MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs (renamed from MediaBrowser.Server.Startup.Common/FFMpeg/FFmpegValidator.cs)68
-rw-r--r--MediaBrowser.MediaEncoding/Encoder/EncodingJobFactory.cs34
-rw-r--r--MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs274
-rw-r--r--MediaBrowser.MediaEncoding/Encoder/VideoEncoder.cs13
-rw-r--r--MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj1
-rw-r--r--MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs4
-rw-r--r--MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs6
-rw-r--r--MediaBrowser.Model.Portable/MediaBrowser.Model.Portable.csproj12
-rw-r--r--MediaBrowser.Model.net35/MediaBrowser.Model.net35.csproj12
-rw-r--r--MediaBrowser.Model/ApiClient/ServerCredentials.cs4
-rw-r--r--MediaBrowser.Model/ApiClient/ServerInfo.cs1
-rw-r--r--MediaBrowser.Model/Channels/ChannelFolderType.cs8
-rw-r--r--MediaBrowser.Model/Configuration/EncodingOptions.cs2
-rw-r--r--MediaBrowser.Model/Configuration/FanartOptions.cs5
-rw-r--r--MediaBrowser.Model/Configuration/ServerConfiguration.cs28
-rw-r--r--MediaBrowser.Model/Configuration/TheMovieDbOptions.cs12
-rw-r--r--MediaBrowser.Model/Configuration/TvdbOptions.cs12
-rw-r--r--MediaBrowser.Model/Configuration/UserConfiguration.cs8
-rw-r--r--MediaBrowser.Model/Dlna/ResolutionNormalizer.cs20
-rw-r--r--MediaBrowser.Model/Dlna/StreamBuilder.cs39
-rw-r--r--MediaBrowser.Model/Dlna/StreamInfo.cs37
-rw-r--r--MediaBrowser.Model/Dlna/SubtitleStreamInfo.cs1
-rw-r--r--MediaBrowser.Model/Dto/ItemCounts.cs1
-rw-r--r--MediaBrowser.Model/Entities/ImageType.cs24
-rw-r--r--MediaBrowser.Model/Entities/MediaStream.cs58
-rw-r--r--MediaBrowser.Model/Entities/MediaUrl.cs1
-rw-r--r--MediaBrowser.Model/Entities/VideoSize.cs8
-rw-r--r--MediaBrowser.Model/LiveTv/LiveTvOptions.cs24
-rw-r--r--MediaBrowser.Model/LiveTv/RecordingQuery.cs7
-rw-r--r--MediaBrowser.Model/LiveTv/TimerQuery.cs2
-rw-r--r--MediaBrowser.Model/MediaBrowser.Model.csproj4
-rw-r--r--MediaBrowser.Model/Querying/ItemSortBy.cs1
-rw-r--r--MediaBrowser.Model/System/Architecture.cs9
-rw-r--r--MediaBrowser.Model/System/SystemInfo.cs4
-rw-r--r--MediaBrowser.Providers/Books/BookMetadataService.cs8
-rw-r--r--MediaBrowser.Providers/BoxSets/BoxSetMetadataService.cs8
-rw-r--r--MediaBrowser.Providers/BoxSets/MovieDbBoxSetImageProvider.cs2
-rw-r--r--MediaBrowser.Providers/Channels/ChannelMetadataService.cs6
-rw-r--r--MediaBrowser.Providers/Chapters/ChapterManager.cs2
-rw-r--r--MediaBrowser.Providers/Folders/CollectionFolderMetadataService.cs7
-rw-r--r--MediaBrowser.Providers/Folders/DefaultImageProvider.cs165
-rw-r--r--MediaBrowser.Providers/Folders/FolderMetadataService.cs8
-rw-r--r--MediaBrowser.Providers/Folders/UserViewMetadataService.cs6
-rw-r--r--MediaBrowser.Providers/GameGenres/GameGenreMetadataService.cs6
-rw-r--r--MediaBrowser.Providers/Games/GameMetadataService.cs8
-rw-r--r--MediaBrowser.Providers/Games/GameSystemMetadataService.cs8
-rw-r--r--MediaBrowser.Providers/Genres/GenreMetadataService.cs6
-rw-r--r--MediaBrowser.Providers/LiveTv/AudioRecordingService.cs6
-rw-r--r--MediaBrowser.Providers/LiveTv/ChannelMetadataService.cs6
-rw-r--r--MediaBrowser.Providers/LiveTv/ProgramMetadataService.cs6
-rw-r--r--MediaBrowser.Providers/LiveTv/VideoRecordingService.cs6
-rw-r--r--MediaBrowser.Providers/Manager/ImageSaver.cs14
-rw-r--r--MediaBrowser.Providers/Manager/MetadataService.cs166
-rw-r--r--MediaBrowser.Providers/Manager/ProviderManager.cs23
-rw-r--r--MediaBrowser.Providers/Manager/ProviderUtils.cs20
-rw-r--r--MediaBrowser.Providers/Manager/SeriesOrderManager.cs35
-rw-r--r--MediaBrowser.Providers/MediaBrowser.Providers.csproj7
-rw-r--r--MediaBrowser.Providers/Movies/FanArtMovieUpdatesPostScanTask.cs196
-rw-r--r--MediaBrowser.Providers/Movies/FanartMovieImageProvider.cs29
-rw-r--r--MediaBrowser.Providers/Movies/GenericMovieDbInfo.cs9
-rw-r--r--MediaBrowser.Providers/Movies/MovieDbImageProvider.cs13
-rw-r--r--MediaBrowser.Providers/Movies/MovieDbProvider.cs83
-rw-r--r--MediaBrowser.Providers/Movies/MovieDbSearch.cs24
-rw-r--r--MediaBrowser.Providers/Movies/MovieDbTrailerProvider.cs5
-rw-r--r--MediaBrowser.Providers/Movies/MovieExternalIds.cs7
-rw-r--r--MediaBrowser.Providers/Movies/MovieMetadataService.cs17
-rw-r--r--MediaBrowser.Providers/Movies/MovieUpdatesPrescanTask.cs249
-rw-r--r--MediaBrowser.Providers/Music/AlbumMetadataService.cs8
-rw-r--r--MediaBrowser.Providers/Music/ArtistMetadataService.cs8
-rw-r--r--MediaBrowser.Providers/Music/AudioMetadataService.cs8
-rw-r--r--MediaBrowser.Providers/Music/FanArtAlbumProvider.cs35
-rw-r--r--MediaBrowser.Providers/Music/FanArtArtistProvider.cs29
-rw-r--r--MediaBrowser.Providers/Music/FanArtUpdatesPostScanTask.cs203
-rw-r--r--MediaBrowser.Providers/Music/MovieDbMusicVideoProvider.cs5
-rw-r--r--MediaBrowser.Providers/Music/MusicBrainzAlbumProvider.cs199
-rw-r--r--MediaBrowser.Providers/Music/MusicBrainzArtistProvider.cs11
-rw-r--r--MediaBrowser.Providers/Music/MusicVideoMetadataService.cs8
-rw-r--r--MediaBrowser.Providers/MusicGenres/MusicGenreMetadataService.cs6
-rw-r--r--MediaBrowser.Providers/Omdb/OmdbImageProvider.cs48
-rw-r--r--MediaBrowser.Providers/Omdb/OmdbItemProvider.cs14
-rw-r--r--MediaBrowser.Providers/Omdb/OmdbProvider.cs318
-rw-r--r--MediaBrowser.Providers/People/PersonMetadataService.cs8
-rw-r--r--MediaBrowser.Providers/Photos/PhotoAlbumMetadataService.cs6
-rw-r--r--MediaBrowser.Providers/Photos/PhotoMetadataService.cs6
-rw-r--r--MediaBrowser.Providers/Playlists/PlaylistMetadataService.cs8
-rw-r--r--MediaBrowser.Providers/Studios/StudioMetadataService.cs6
-rw-r--r--MediaBrowser.Providers/TV/EpisodeMetadataService.cs8
-rw-r--r--MediaBrowser.Providers/TV/FanArt/FanArtSeasonProvider.cs37
-rw-r--r--MediaBrowser.Providers/TV/FanArt/FanArtTvUpdatesPostScanTask.cs192
-rw-r--r--MediaBrowser.Providers/TV/FanArt/FanartSeriesProvider.cs29
-rw-r--r--MediaBrowser.Providers/TV/MissingEpisodeProvider.cs3
-rw-r--r--MediaBrowser.Providers/TV/Omdb/OmdbEpisodeProvider.cs34
-rw-r--r--MediaBrowser.Providers/TV/SeasonMetadataService.cs8
-rw-r--r--MediaBrowser.Providers/TV/SeriesMetadataService.cs7
-rw-r--r--MediaBrowser.Providers/TV/SeriesPostScanTask.cs39
-rw-r--r--MediaBrowser.Providers/TV/TheMovieDb/MovieDbEpisodeImageProvider.cs6
-rw-r--r--MediaBrowser.Providers/TV/TheMovieDb/MovieDbEpisodeProvider.cs16
-rw-r--r--MediaBrowser.Providers/TV/TheMovieDb/MovieDbProviderBase.cs16
-rw-r--r--MediaBrowser.Providers/TV/TheMovieDb/MovieDbSeasonProvider.cs2
-rw-r--r--MediaBrowser.Providers/TV/TheMovieDb/MovieDbSeriesImageProvider.cs13
-rw-r--r--MediaBrowser.Providers/TV/TheMovieDb/MovieDbSeriesProvider.cs55
-rw-r--r--MediaBrowser.Providers/TV/TheTVDB/TvdbEpisodeImageProvider.cs28
-rw-r--r--MediaBrowser.Providers/TV/TheTVDB/TvdbEpisodeProvider.cs103
-rw-r--r--MediaBrowser.Providers/TV/TheTVDB/TvdbSeasonIdentityProvider.cs65
-rw-r--r--MediaBrowser.Providers/TV/TheTVDB/TvdbSeasonImageProvider.cs40
-rw-r--r--MediaBrowser.Providers/TV/TheTVDB/TvdbSeriesImageProvider.cs22
-rw-r--r--MediaBrowser.Providers/TV/TheTVDB/TvdbSeriesProvider.cs46
-rw-r--r--MediaBrowser.Providers/TV/TvExternalIds.cs2
-rw-r--r--MediaBrowser.Providers/Users/UserMetadataService.cs6
-rw-r--r--MediaBrowser.Providers/Videos/VideoMetadataService.cs8
-rw-r--r--MediaBrowser.Providers/Years/YearMetadataService.cs6
-rw-r--r--MediaBrowser.Server.Implementations/Activity/ActivityRepository.cs255
-rw-r--r--MediaBrowser.Server.Implementations/Channels/ChannelManager.cs45
-rw-r--r--MediaBrowser.Server.Implementations/Collections/ManualCollectionsFolder.cs2
-rw-r--r--MediaBrowser.Server.Implementations/Connect/ConnectEntryPoint.cs14
-rw-r--r--MediaBrowser.Server.Implementations/Connect/ConnectManager.cs23
-rw-r--r--MediaBrowser.Server.Implementations/Connect/Responses.cs1
-rw-r--r--MediaBrowser.Server.Implementations/Devices/CameraUploadsFolder.cs1
-rw-r--r--MediaBrowser.Server.Implementations/Devices/DeviceManager.cs5
-rw-r--r--MediaBrowser.Server.Implementations/Devices/DeviceRepository.cs20
-rw-r--r--MediaBrowser.Server.Implementations/Dto/DtoService.cs259
-rw-r--r--MediaBrowser.Server.Implementations/EntryPoints/AutomaticRestartEntryPoint.cs50
-rw-r--r--MediaBrowser.Server.Implementations/EntryPoints/ExternalPortForwarding.cs36
-rw-r--r--MediaBrowser.Server.Implementations/EntryPoints/RecordingNotifier.cs83
-rw-r--r--MediaBrowser.Server.Implementations/EntryPoints/UserDataChangeNotifier.cs2
-rw-r--r--MediaBrowser.Server.Implementations/FileOrganization/EpisodeFileOrganizer.cs7
-rw-r--r--MediaBrowser.Server.Implementations/HttpServer/HttpListenerHost.cs2
-rw-r--r--MediaBrowser.Server.Implementations/HttpServer/HttpResultFactory.cs10
-rw-r--r--MediaBrowser.Server.Implementations/HttpServer/LoggerUtils.cs2
-rw-r--r--MediaBrowser.Server.Implementations/HttpServer/RangeRequestWriter.cs4
-rw-r--r--MediaBrowser.Server.Implementations/HttpServer/ResponseFilter.cs9
-rw-r--r--MediaBrowser.Server.Implementations/HttpServer/Security/AuthorizationContext.cs4
-rw-r--r--MediaBrowser.Server.Implementations/HttpServer/SocketSharp/RequestMono.cs16
-rw-r--r--MediaBrowser.Server.Implementations/HttpServer/SocketSharp/WebSocketSharpRequest.cs2
-rw-r--r--MediaBrowser.Server.Implementations/HttpServer/SocketSharp/WebSocketSharpResponse.cs17
-rw-r--r--MediaBrowser.Server.Implementations/HttpServer/SwaggerService.cs2
-rw-r--r--MediaBrowser.Server.Implementations/IO/FileRefresher.cs289
-rw-r--r--MediaBrowser.Server.Implementations/IO/LibraryMonitor.cs276
-rw-r--r--MediaBrowser.Server.Implementations/Intros/DefaultIntroProvider.cs188
-rw-r--r--MediaBrowser.Server.Implementations/Library/LibraryManager.cs204
-rw-r--r--MediaBrowser.Server.Implementations/Library/MediaSourceManager.cs4
-rw-r--r--MediaBrowser.Server.Implementations/Library/MusicManager.cs2
-rw-r--r--MediaBrowser.Server.Implementations/Library/Resolvers/BaseVideoResolver.cs2
-rw-r--r--MediaBrowser.Server.Implementations/Library/Resolvers/TV/EpisodeResolver.cs8
-rw-r--r--MediaBrowser.Server.Implementations/Library/SearchEngine.cs4
-rw-r--r--MediaBrowser.Server.Implementations/Library/UserDataManager.cs120
-rw-r--r--MediaBrowser.Server.Implementations/Library/UserManager.cs2
-rw-r--r--MediaBrowser.Server.Implementations/Library/UserViewManager.cs39
-rw-r--r--MediaBrowser.Server.Implementations/LiveTv/ChannelImageProvider.cs2
-rw-r--r--MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs190
-rw-r--r--MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EmbyTVRegistration.cs36
-rw-r--r--MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs121
-rw-r--r--MediaBrowser.Server.Implementations/LiveTv/EmbyTV/TimerManager.cs32
-rw-r--r--MediaBrowser.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs165
-rw-r--r--MediaBrowser.Server.Implementations/LiveTv/Listings/XmlTv.cs44
-rw-r--r--MediaBrowser.Server.Implementations/LiveTv/Listings/XmlTvListingsProvider.cs219
-rw-r--r--MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs225
-rw-r--r--MediaBrowser.Server.Implementations/LiveTv/LiveTvMediaSourceProvider.cs2
-rw-r--r--MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj187
-rw-r--r--MediaBrowser.Server.Implementations/Notifications/SqliteNotificationsRepository.cs450
-rw-r--r--MediaBrowser.Server.Implementations/Persistence/BaseSqliteRepository.cs33
-rw-r--r--MediaBrowser.Server.Implementations/Persistence/CleanDatabaseScheduledTask.cs24
-rw-r--r--MediaBrowser.Server.Implementations/Persistence/IDbConnector.cs2
-rw-r--r--MediaBrowser.Server.Implementations/Persistence/MediaStreamColumns.cs64
-rw-r--r--MediaBrowser.Server.Implementations/Persistence/SqliteDisplayPreferencesRepository.cs263
-rw-r--r--MediaBrowser.Server.Implementations/Persistence/SqliteExtensions.cs58
-rw-r--r--MediaBrowser.Server.Implementations/Persistence/SqliteFileOrganizationRepository.cs434
-rw-r--r--MediaBrowser.Server.Implementations/Persistence/SqliteItemRepository.cs1355
-rw-r--r--MediaBrowser.Server.Implementations/Persistence/SqliteProviderInfoRepository.cs248
-rw-r--r--MediaBrowser.Server.Implementations/Persistence/SqliteUserDataRepository.cs109
-rw-r--r--MediaBrowser.Server.Implementations/Persistence/SqliteUserRepository.cs204
-rw-r--r--MediaBrowser.Server.Implementations/Security/AuthenticationRepository.cs332
-rw-r--r--MediaBrowser.Server.Implementations/Session/SessionManager.cs51
-rw-r--r--MediaBrowser.Server.Implementations/Session/SessionWebSocketListener.cs7
-rw-r--r--MediaBrowser.Server.Implementations/Social/SharingManager.cs10
-rw-r--r--MediaBrowser.Server.Implementations/Social/SharingRepository.cs174
-rw-r--r--MediaBrowser.Server.Implementations/Sync/SyncJobProcessor.cs39
-rw-r--r--MediaBrowser.Server.Implementations/Sync/SyncRepository.cs868
-rw-r--r--MediaBrowser.Server.Implementations/TV/TVSeriesManager.cs96
-rw-r--r--MediaBrowser.Server.Implementations/Udp/UdpServer.cs12
-rw-r--r--MediaBrowser.Server.Implementations/packages.config8
-rw-r--r--MediaBrowser.Server.Mac/Emby.Server.Mac.csproj1291
-rw-r--r--MediaBrowser.Server.Mac/Native/BaseMonoApp.cs12
-rw-r--r--MediaBrowser.Server.Mac/Native/DbConnector.cs24
-rw-r--r--MediaBrowser.Server.Mac/Native/SqliteExtensions.cs62
-rw-r--r--MediaBrowser.Server.Mono/MediaBrowser.Server.Mono.csproj31
-rw-r--r--MediaBrowser.Server.Mono/Native/BaseMonoApp.cs47
-rw-r--r--MediaBrowser.Server.Mono/Native/DbConnector.cs24
-rw-r--r--MediaBrowser.Server.Mono/Native/SqliteExtensions.cs62
-rw-r--r--MediaBrowser.Server.Mono/Security/ASN1.cs344
-rw-r--r--MediaBrowser.Server.Mono/Security/ASN1Convert.cs212
-rw-r--r--MediaBrowser.Server.Mono/Security/BitConverterLE.cs239
-rw-r--r--MediaBrowser.Server.Mono/Security/CryptoConvert.cs754
-rw-r--r--MediaBrowser.Server.Mono/Security/PKCS1.cs495
-rw-r--r--MediaBrowser.Server.Mono/Security/PKCS12.cs2001
-rw-r--r--MediaBrowser.Server.Mono/Security/PKCS7.cs1018
-rw-r--r--MediaBrowser.Server.Mono/Security/PKCS8.cs505
-rw-r--r--MediaBrowser.Server.Mono/Security/X501Name.cs400
-rw-r--r--MediaBrowser.Server.Mono/Security/X509Builder.cs154
-rw-r--r--MediaBrowser.Server.Mono/Security/X509Certificate.cs567
-rw-r--r--MediaBrowser.Server.Mono/Security/X509CertificateBuilder.cs244
-rw-r--r--MediaBrowser.Server.Mono/Security/X509CertificateCollection.cs205
-rw-r--r--MediaBrowser.Server.Mono/Security/X509Extension.cs214
-rw-r--r--MediaBrowser.Server.Mono/Security/X509Extensions.cs201
-rw-r--r--MediaBrowser.Server.Mono/Security/X520Attributes.cs353
-rw-r--r--MediaBrowser.Server.Startup.Common/ApplicationHost.cs236
-rw-r--r--MediaBrowser.Server.Startup.Common/FFMpeg/FFMpegInstallInfo.cs1
-rw-r--r--MediaBrowser.Server.Startup.Common/FFMpeg/FFMpegLoader.cs63
-rw-r--r--MediaBrowser.Server.Startup.Common/MediaBrowser.Server.Startup.Common.csproj5
-rw-r--r--MediaBrowser.Server.Startup.Common/Migrations/CollectionGroupingMigration.cs44
-rw-r--r--MediaBrowser.Server.Startup.Common/Migrations/CollectionsViewMigration.cs44
-rw-r--r--MediaBrowser.Server.Startup.Common/Migrations/DbMigration.cs20
-rw-r--r--MediaBrowser.Server.Startup.Common/Migrations/FolderViewSettingMigration.cs40
-rw-r--r--MediaBrowser.Server.Startup.Common/Migrations/RenameXmlOptions.cs61
-rw-r--r--MediaBrowser.Server.Startup.Common/NativeEnvironment.cs10
-rw-r--r--MediaBrowser.ServerApplication/App.config1
-rw-r--r--MediaBrowser.ServerApplication/BackgroundServiceInstaller.cs2
-rw-r--r--MediaBrowser.ServerApplication/MainStartup.cs102
-rw-r--r--MediaBrowser.ServerApplication/MediaBrowser.ServerApplication.csproj13
-rw-r--r--MediaBrowser.ServerApplication/Native/DbConnector.cs24
-rw-r--r--MediaBrowser.ServerApplication/Native/SqliteExtensions.cs62
-rw-r--r--MediaBrowser.ServerApplication/Native/WindowsApp.cs28
-rw-r--r--MediaBrowser.ServerApplication/Networking/NetworkManager.cs14
-rw-r--r--MediaBrowser.ServerApplication/packages.config2
-rw-r--r--MediaBrowser.WebDashboard/Api/DashboardService.cs28
-rw-r--r--MediaBrowser.WebDashboard/Api/PackageCreator.cs38
-rw-r--r--MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj89
-rw-r--r--MediaBrowser.XbmcMetadata/Parsers/BaseNfoParser.cs14
-rw-r--r--MediaBrowser.XbmcMetadata/Parsers/EpisodeNfoParser.cs42
-rw-r--r--MediaBrowser.XbmcMetadata/Savers/BaseNfoSaver.cs26
-rw-r--r--MediaBrowser.XbmcMetadata/Savers/EpisodeNfoSaver.cs10
-rw-r--r--MediaBrowser.XbmcMetadata/Savers/MovieNfoSaver.cs7
-rw-r--r--Nuget/MediaBrowser.Common.Internal.nuspec8
-rw-r--r--Nuget/MediaBrowser.Common.nuspec2
-rw-r--r--Nuget/MediaBrowser.Server.Core.nuspec4
-rw-r--r--SharedVersion.cs4
362 files changed, 16594 insertions, 9157 deletions
diff --git a/Emby.Drawing/Emby.Drawing.csproj b/Emby.Drawing/Emby.Drawing.csproj
index dc410c321..aa7dc5a15 100644
--- a/Emby.Drawing/Emby.Drawing.csproj
+++ b/Emby.Drawing/Emby.Drawing.csproj
@@ -80,7 +80,6 @@
<Compile Include="ImageMagick\UnplayedCountIndicator.cs" />
</ItemGroup>
<ItemGroup>
- <EmbeddedResource Include="ImageMagick\fonts\MontserratLight.otf" />
<EmbeddedResource Include="ImageMagick\fonts\robotoregular.ttf" />
</ItemGroup>
<ItemGroup>
diff --git a/Emby.Drawing/ImageMagick/ImageMagickEncoder.cs b/Emby.Drawing/ImageMagick/ImageMagickEncoder.cs
index cb60d1123..3dbe7239d 100644
--- a/Emby.Drawing/ImageMagick/ImageMagickEncoder.cs
+++ b/Emby.Drawing/ImageMagick/ImageMagickEncoder.cs
@@ -111,7 +111,6 @@ namespace Emby.Drawing.ImageMagick
wand.CurrentImage.TrimImage(10);
wand.SaveImage(outputPath);
}
- SaveDelay();
}
public ImageSize GetImageSize(string path)
@@ -189,7 +188,6 @@ namespace Emby.Drawing.ImageMagick
}
}
}
- SaveDelay();
}
private void AddForegroundLayer(MagickWand wand, ImageProcessingOptions options)
@@ -284,25 +282,16 @@ namespace Emby.Drawing.ImageMagick
if (ratio >= 1.4)
{
- new StripCollageBuilder(_appPaths, _fileSystem).BuildThumbCollage(options.InputPaths.ToList(), options.OutputPath, options.Width, options.Height, options.Text);
+ new StripCollageBuilder(_appPaths, _fileSystem).BuildThumbCollage(options.InputPaths.ToList(), options.OutputPath, options.Width, options.Height);
}
else if (ratio >= .9)
{
- new StripCollageBuilder(_appPaths, _fileSystem).BuildSquareCollage(options.InputPaths.ToList(), options.OutputPath, options.Width, options.Height, options.Text);
+ new StripCollageBuilder(_appPaths, _fileSystem).BuildSquareCollage(options.InputPaths.ToList(), options.OutputPath, options.Width, options.Height);
}
else
{
- new StripCollageBuilder(_appPaths, _fileSystem).BuildPosterCollage(options.InputPaths.ToList(), options.OutputPath, options.Width, options.Height, options.Text);
+ new StripCollageBuilder(_appPaths, _fileSystem).BuildPosterCollage(options.InputPaths.ToList(), options.OutputPath, options.Width, options.Height);
}
-
- SaveDelay();
- }
-
- private void SaveDelay()
- {
- // For some reason the images are not always getting released right away
- //var task = Task.Delay(300);
- //Task.WaitAll(task);
}
public string Name
diff --git a/Emby.Drawing/ImageMagick/StripCollageBuilder.cs b/Emby.Drawing/ImageMagick/StripCollageBuilder.cs
index c9e291a5e..7bc144c11 100644
--- a/Emby.Drawing/ImageMagick/StripCollageBuilder.cs
+++ b/Emby.Drawing/ImageMagick/StripCollageBuilder.cs
@@ -9,140 +9,35 @@ namespace Emby.Drawing.ImageMagick
public class StripCollageBuilder
{
private readonly IApplicationPaths _appPaths;
- private readonly IFileSystem _fileSystem;
+ private readonly IFileSystem _fileSystem;
- public StripCollageBuilder(IApplicationPaths appPaths, IFileSystem fileSystem)
+ public StripCollageBuilder(IApplicationPaths appPaths, IFileSystem fileSystem)
{
_appPaths = appPaths;
- _fileSystem = fileSystem;
+ _fileSystem = fileSystem;
}
- public void BuildPosterCollage(List<string> paths, string outputPath, int width, int height, string text)
+ public void BuildPosterCollage(List<string> paths, string outputPath, int width, int height)
{
- if (!string.IsNullOrWhiteSpace(text))
+ using (var wand = BuildPosterCollageWand(paths, width, height))
{
- using (var wand = BuildPosterCollageWandWithText(paths, text, width, height))
- {
- wand.SaveImage(outputPath);
- }
- }
- else
- {
- using (var wand = BuildPosterCollageWand(paths, width, height))
- {
- wand.SaveImage(outputPath);
- }
+ wand.SaveImage(outputPath);
}
}
- public void BuildSquareCollage(List<string> paths, string outputPath, int width, int height, string text)
+ public void BuildSquareCollage(List<string> paths, string outputPath, int width, int height)
{
- if (!string.IsNullOrWhiteSpace(text))
+ using (var wand = BuildSquareCollageWand(paths, width, height))
{
- using (var wand = BuildSquareCollageWandWithText(paths, text, width, height))
- {
- wand.SaveImage(outputPath);
- }
- }
- else
- {
- using (var wand = BuildSquareCollageWand(paths, width, height))
- {
- wand.SaveImage(outputPath);
- }
+ wand.SaveImage(outputPath);
}
}
- public void BuildThumbCollage(List<string> paths, string outputPath, int width, int height, string text)
+ public void BuildThumbCollage(List<string> paths, string outputPath, int width, int height)
{
- if (!string.IsNullOrWhiteSpace(text))
+ using (var wand = BuildThumbCollageWand(paths, width, height))
{
- using (var wand = BuildThumbCollageWandWithText(paths, text, width, height))
- {
- wand.SaveImage(outputPath);
- }
- }
- else
- {
- using (var wand = BuildThumbCollageWand(paths, width, height))
- {
- wand.SaveImage(outputPath);
- }
- }
- }
-
- private MagickWand BuildThumbCollageWandWithText(List<string> paths, string text, int width, int height)
- {
- var inputPaths = ImageHelpers.ProjectPaths(paths, 8);
- using (var wandImages = new MagickWand(inputPaths.ToArray()))
- {
- var wand = new MagickWand(width, height);
- wand.OpenImage("gradient:#111111-#111111");
- using (var draw = new DrawingWand())
- {
- using (var fcolor = new PixelWand(ColorName.White))
- {
- draw.FillColor = fcolor;
- draw.Font = MontserratLightFont;
- draw.FontSize = 60;
- draw.FontWeight = FontWeightType.LightStyle;
- draw.TextAntialias = true;
- }
-
- var fontMetrics = wand.QueryFontMetrics(draw, text);
- var textContainerY = Convert.ToInt32(height * .165);
- wand.CurrentImage.AnnotateImage(draw, (width - fontMetrics.TextWidth) / 2, textContainerY, 0.0, text);
-
- var iSlice = Convert.ToInt32(width * .1166666667);
- int iTrans = Convert.ToInt32(height * 0.2);
- int iHeight = Convert.ToInt32(height * 0.46296296296296296296296296296296);
- var horizontalImagePadding = Convert.ToInt32(width * 0.0125);
-
- foreach (var element in wandImages.ImageList)
- {
- int iWidth = (int)Math.Abs(iHeight * element.Width / element.Height);
- element.Gravity = GravityType.CenterGravity;
- element.BackgroundColor = new PixelWand("none", 1);
- element.ResizeImage(iWidth, iHeight, FilterTypes.LanczosFilter);
- int ix = (int)Math.Abs((iWidth - iSlice) / 2);
- element.CropImage(iSlice, iHeight, ix, 0);
-
- element.ExtentImage(iSlice, iHeight, 0 - horizontalImagePadding, 0);
- }
-
- wandImages.SetFirstIterator();
- using (var wandList = wandImages.AppendImages())
- {
- wandList.CurrentImage.TrimImage(1);
- using (var mwr = wandList.CloneMagickWand())
- {
- using (var blackPixelWand = new PixelWand(ColorName.Black))
- {
- using (var greyPixelWand = new PixelWand(ColorName.Grey70))
- {
- mwr.CurrentImage.ResizeImage(wandList.CurrentImage.Width, (wandList.CurrentImage.Height / 2), FilterTypes.LanczosFilter, 1);
- mwr.CurrentImage.FlipImage();
-
- mwr.CurrentImage.AlphaChannel = AlphaChannelType.DeactivateAlphaChannel;
- mwr.CurrentImage.ColorizeImage(blackPixelWand, greyPixelWand);
-
- using (var mwg = new MagickWand(wandList.CurrentImage.Width, iTrans))
- {
- mwg.OpenImage("gradient:black-none");
- var verticalSpacing = Convert.ToInt32(height * 0.01111111111111111111111111111111);
- mwr.CurrentImage.CompositeImage(mwg, CompositeOperator.DstInCompositeOp, 0, verticalSpacing);
-
- wandList.AddImage(mwr);
- int ex = (int)(wand.CurrentImage.Width - mwg.CurrentImage.Width) / 2;
- wand.CurrentImage.CompositeImage(wandList.AppendImages(true), CompositeOperator.AtopCompositeOp, ex, Convert.ToInt32(height * 0.26851851851851851851851851851852));
- }
- }
- }
- }
- }
- }
-
- return wand;
+ wand.SaveImage(outputPath);
}
}
@@ -211,81 +106,6 @@ namespace Emby.Drawing.ImageMagick
}
}
- private MagickWand BuildPosterCollageWandWithText(List<string> paths, string label, int width, int height)
- {
- var inputPaths = ImageHelpers.ProjectPaths(paths, 4);
- using (var wandImages = new MagickWand(inputPaths.ToArray()))
- {
- var wand = new MagickWand(width, height);
- wand.OpenImage("gradient:#111111-#111111");
- using (var draw = new DrawingWand())
- {
- using (var fcolor = new PixelWand(ColorName.White))
- {
- draw.FillColor = fcolor;
- draw.Font = MontserratLightFont;
- draw.FontSize = 60;
- draw.FontWeight = FontWeightType.LightStyle;
- draw.TextAntialias = true;
- }
-
- var fontMetrics = wand.QueryFontMetrics(draw, label);
- var textContainerY = Convert.ToInt32(height * .165);
- wand.CurrentImage.AnnotateImage(draw, (width - fontMetrics.TextWidth) / 2, textContainerY, 0.0, label);
-
- var iSlice = Convert.ToInt32(width * 0.225);
- int iTrans = Convert.ToInt32(height * 0.2);
- int iHeight = Convert.ToInt32(height * 0.46296296296296296296296296296296);
- var horizontalImagePadding = Convert.ToInt32(width * 0.0275);
-
- foreach (var element in wandImages.ImageList)
- {
- int iWidth = (int)Math.Abs(iHeight * element.Width / element.Height);
- element.Gravity = GravityType.CenterGravity;
- element.BackgroundColor = new PixelWand("none", 1);
- element.ResizeImage(iWidth, iHeight, FilterTypes.LanczosFilter);
- int ix = (int)Math.Abs((iWidth - iSlice) / 2);
- element.CropImage(iSlice, iHeight, ix, 0);
-
- element.ExtentImage(iSlice, iHeight, 0 - horizontalImagePadding, 0);
- }
-
- wandImages.SetFirstIterator();
- using (var wandList = wandImages.AppendImages())
- {
- wandList.CurrentImage.TrimImage(1);
- using (var mwr = wandList.CloneMagickWand())
- {
- using (var blackPixelWand = new PixelWand(ColorName.Black))
- {
- using (var greyPixelWand = new PixelWand(ColorName.Grey70))
- {
- mwr.CurrentImage.ResizeImage(wandList.CurrentImage.Width, (wandList.CurrentImage.Height / 2), FilterTypes.LanczosFilter, 1);
- mwr.CurrentImage.FlipImage();
-
- mwr.CurrentImage.AlphaChannel = AlphaChannelType.DeactivateAlphaChannel;
- mwr.CurrentImage.ColorizeImage(blackPixelWand, greyPixelWand);
-
- using (var mwg = new MagickWand(wandList.CurrentImage.Width, iTrans))
- {
- mwg.OpenImage("gradient:black-none");
- var verticalSpacing = Convert.ToInt32(height * 0.01111111111111111111111111111111);
- mwr.CurrentImage.CompositeImage(mwg, CompositeOperator.DstInCompositeOp, 0, verticalSpacing);
-
- wandList.AddImage(mwr);
- int ex = (int)(wand.CurrentImage.Width - mwg.CurrentImage.Width) / 2;
- wand.CurrentImage.CompositeImage(wandList.AppendImages(true), CompositeOperator.AtopCompositeOp, ex, Convert.ToInt32(height * 0.26851851851851851851851851851852));
- }
- }
- }
- }
- }
- }
-
- return wand;
- }
- }
-
private MagickWand BuildThumbCollageWand(List<string> paths, int width, int height)
{
var inputPaths = ImageHelpers.ProjectPaths(paths, 4);
@@ -353,147 +173,28 @@ namespace Emby.Drawing.ImageMagick
private MagickWand BuildSquareCollageWand(List<string> paths, int width, int height)
{
- var inputPaths = ImageHelpers.ProjectPaths(paths, 3);
- using (var wandImages = new MagickWand(inputPaths.ToArray()))
- {
- var wand = new MagickWand(width, height);
- wand.OpenImage("gradient:#111111-#111111");
- using (var draw = new DrawingWand())
- {
- var iSlice = Convert.ToInt32(width * .32);
- int iTrans = Convert.ToInt32(height * .25);
- int iHeight = Convert.ToInt32(height * .68);
- var horizontalImagePadding = Convert.ToInt32(width * 0.02);
-
- foreach (var element in wandImages.ImageList)
- {
- using (var blackPixelWand = new PixelWand(ColorName.Black))
- {
- int iWidth = (int)Math.Abs(iHeight * element.Width / element.Height);
- element.Gravity = GravityType.CenterGravity;
- element.BackgroundColor = blackPixelWand;
- element.ResizeImage(iWidth, iHeight, FilterTypes.LanczosFilter);
- int ix = (int)Math.Abs((iWidth - iSlice) / 2);
- element.CropImage(iSlice, iHeight, ix, 0);
-
- element.ExtentImage(iSlice, iHeight, 0 - horizontalImagePadding, 0);
- }
- }
-
- wandImages.SetFirstIterator();
- using (var wandList = wandImages.AppendImages())
- {
- wandList.CurrentImage.TrimImage(1);
- using (var mwr = wandList.CloneMagickWand())
- {
- using (var blackPixelWand = new PixelWand(ColorName.Black))
- {
- using (var greyPixelWand = new PixelWand(ColorName.Grey70))
- {
- mwr.CurrentImage.ResizeImage(wandList.CurrentImage.Width, (wandList.CurrentImage.Height / 2), FilterTypes.LanczosFilter, 1);
- mwr.CurrentImage.FlipImage();
-
- mwr.CurrentImage.AlphaChannel = AlphaChannelType.DeactivateAlphaChannel;
- mwr.CurrentImage.ColorizeImage(blackPixelWand, greyPixelWand);
-
- using (var mwg = new MagickWand(wandList.CurrentImage.Width, iTrans))
- {
- mwg.OpenImage("gradient:black-none");
- var verticalSpacing = Convert.ToInt32(height * 0.01111111111111111111111111111111);
- mwr.CurrentImage.CompositeImage(mwg, CompositeOperator.CopyOpacityCompositeOp, 0, verticalSpacing);
-
- wandList.AddImage(mwr);
- int ex = (int)(wand.CurrentImage.Width - mwg.CurrentImage.Width) / 2;
- wand.CurrentImage.CompositeImage(wandList.AppendImages(true), CompositeOperator.AtopCompositeOp, ex, Convert.ToInt32(height * .03));
- }
- }
- }
- }
- }
- }
-
- return wand;
- }
- }
-
- private MagickWand BuildSquareCollageWandWithText(List<string> paths, string label, int width, int height)
- {
var inputPaths = ImageHelpers.ProjectPaths(paths, 4);
- using (var wandImages = new MagickWand(inputPaths.ToArray()))
+ var outputWand = new MagickWand(width, height, new PixelWand("none", 1));
+ var imageIndex = 0;
+ var cellWidth = width/2;
+ var cellHeight = height/2;
+ for (var x = 0; x < 2; x++)
{
- var wand = new MagickWand(width, height);
- wand.OpenImage("gradient:#111111-#111111");
- using (var draw = new DrawingWand())
+ for (var y = 0; y < 2; y++)
{
- using (var fcolor = new PixelWand(ColorName.White))
- {
- draw.FillColor = fcolor;
- draw.Font = MontserratLightFont;
- draw.FontSize = 60;
- draw.FontWeight = FontWeightType.LightStyle;
- draw.TextAntialias = true;
- }
-
- var fontMetrics = wand.QueryFontMetrics(draw, label);
- var textContainerY = Convert.ToInt32(height * .165);
- wand.CurrentImage.AnnotateImage(draw, (width - fontMetrics.TextWidth) / 2, textContainerY, 0.0, label);
-
- var iSlice = Convert.ToInt32(width * .225);
- int iTrans = Convert.ToInt32(height * 0.2);
- int iHeight = Convert.ToInt32(height * 0.46296296296296296296296296296296);
- var horizontalImagePadding = Convert.ToInt32(width * 0.02);
-
- foreach (var element in wandImages.ImageList)
- {
- int iWidth = (int)Math.Abs(iHeight * element.Width / element.Height);
- element.Gravity = GravityType.CenterGravity;
- element.BackgroundColor = new PixelWand("none", 1);
- element.ResizeImage(iWidth, iHeight, FilterTypes.LanczosFilter);
- int ix = (int)Math.Abs((iWidth - iSlice) / 2);
- element.CropImage(iSlice, iHeight, ix, 0);
-
- element.ExtentImage(iSlice, iHeight, 0 - horizontalImagePadding, 0);
- }
-
- wandImages.SetFirstIterator();
- using (var wandList = wandImages.AppendImages())
+ using (var temp = new MagickWand(inputPaths[imageIndex]))
{
- wandList.CurrentImage.TrimImage(1);
- using (var mwr = wandList.CloneMagickWand())
- {
- using (var blackPixelWand = new PixelWand(ColorName.Black))
- {
- using (var greyPixelWand = new PixelWand(ColorName.Grey70))
- {
- mwr.CurrentImage.ResizeImage(wandList.CurrentImage.Width, (wandList.CurrentImage.Height / 2), FilterTypes.LanczosFilter, 1);
- mwr.CurrentImage.FlipImage();
-
- mwr.CurrentImage.AlphaChannel = AlphaChannelType.DeactivateAlphaChannel;
- mwr.CurrentImage.ColorizeImage(blackPixelWand, greyPixelWand);
-
- using (var mwg = new MagickWand(wandList.CurrentImage.Width, iTrans))
- {
- mwg.OpenImage("gradient:black-none");
- var verticalSpacing = Convert.ToInt32(height * 0.01111111111111111111111111111111);
- mwr.CurrentImage.CompositeImage(mwg, CompositeOperator.DstInCompositeOp, 0, verticalSpacing);
-
- wandList.AddImage(mwr);
- int ex = (int)(wand.CurrentImage.Width - mwg.CurrentImage.Width) / 2;
- wand.CurrentImage.CompositeImage(wandList.AppendImages(true), CompositeOperator.AtopCompositeOp, ex, Convert.ToInt32(height * 0.26851851851851851851851851851852));
- }
- }
- }
- }
+ temp.CurrentImage.ScaleImage(cellWidth, cellHeight);
+ // draw this image into the strip at the next position
+ var xPos = x*cellWidth;
+ var yPos = y*cellHeight;
+ outputWand.CurrentImage.CompositeImage(temp, CompositeOperator.OverCompositeOp, xPos, yPos);
}
+ imageIndex++;
}
-
- return wand;
}
- }
- private string MontserratLightFont
- {
- get { return PlayedIndicatorDrawer.ExtractFont("MontserratLight.otf", _appPaths, _fileSystem); }
+ return outputWand;
}
}
}
diff --git a/MediaBrowser.Api/BaseApiService.cs b/MediaBrowser.Api/BaseApiService.cs
index 44d459a01..44a367be0 100644
--- a/MediaBrowser.Api/BaseApiService.cs
+++ b/MediaBrowser.Api/BaseApiService.cs
@@ -115,7 +115,7 @@ namespace MediaBrowser.Api
/// <returns>System.Object.</returns>
protected object ToStaticFileResult(string path)
{
- return ResultFactory.GetStaticFileResult(Request, path);
+ return ResultFactory.GetStaticFileResult(Request, path).Result;
}
protected DtoOptions GetDtoOptions(object request)
diff --git a/MediaBrowser.Api/ConfigurationService.cs b/MediaBrowser.Api/ConfigurationService.cs
index 446415fbb..2e5c252d6 100644
--- a/MediaBrowser.Api/ConfigurationService.cs
+++ b/MediaBrowser.Api/ConfigurationService.cs
@@ -9,7 +9,9 @@ using ServiceStack.Web;
using System.Collections.Generic;
using System.IO;
using System.Linq;
+using System.Threading.Tasks;
using CommonIO;
+using MediaBrowser.Controller.MediaEncoding;
namespace MediaBrowser.Api
{
@@ -71,6 +73,16 @@ namespace MediaBrowser.Api
}
+ [Route("/System/MediaEncoder/Path", "POST", Summary = "Updates the path to the media encoder")]
+ [Authenticated(Roles = "Admin", AllowBeforeStartupWizard = true)]
+ public class UpdateMediaEncoderPath : IReturnVoid
+ {
+ [ApiMember(Name = "Path", Description = "Path", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")]
+ public string Path { get; set; }
+ [ApiMember(Name = "PathType", Description = "PathType", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")]
+ public string PathType { get; set; }
+ }
+
public class ConfigurationService : BaseApiService
{
/// <summary>
@@ -86,14 +98,22 @@ namespace MediaBrowser.Api
private readonly IFileSystem _fileSystem;
private readonly IProviderManager _providerManager;
private readonly ILibraryManager _libraryManager;
+ private readonly IMediaEncoder _mediaEncoder;
- public ConfigurationService(IJsonSerializer jsonSerializer, IServerConfigurationManager configurationManager, IFileSystem fileSystem, IProviderManager providerManager, ILibraryManager libraryManager)
+ public ConfigurationService(IJsonSerializer jsonSerializer, IServerConfigurationManager configurationManager, IFileSystem fileSystem, IProviderManager providerManager, ILibraryManager libraryManager, IMediaEncoder mediaEncoder)
{
_jsonSerializer = jsonSerializer;
_configurationManager = configurationManager;
_fileSystem = fileSystem;
_providerManager = providerManager;
_libraryManager = libraryManager;
+ _mediaEncoder = mediaEncoder;
+ }
+
+ public void Post(UpdateMediaEncoderPath request)
+ {
+ var task = _mediaEncoder.UpdateEncoderPath(request.Path, request.PathType);
+ Task.WaitAll(task);
}
/// <summary>
diff --git a/MediaBrowser.Api/FilterService.cs b/MediaBrowser.Api/FilterService.cs
index c4419531c..b3b75359a 100644
--- a/MediaBrowser.Api/FilterService.cs
+++ b/MediaBrowser.Api/FilterService.cs
@@ -80,7 +80,7 @@ namespace MediaBrowser.Api
.OrderBy(i => i)
.ToArray();
- result.Tags = items.OfType<IHasTags>()
+ result.Tags = items
.SelectMany(i => i.Tags)
.Distinct(StringComparer.OrdinalIgnoreCase)
.OrderBy(i => i)
diff --git a/MediaBrowser.Api/GamesService.cs b/MediaBrowser.Api/GamesService.cs
index 387771b6d..0953b95e6 100644
--- a/MediaBrowser.Api/GamesService.cs
+++ b/MediaBrowser.Api/GamesService.cs
@@ -10,6 +10,8 @@ using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
+using System.Threading.Tasks;
+using MediaBrowser.Model.Querying;
namespace MediaBrowser.Api
{
@@ -107,8 +109,7 @@ namespace MediaBrowser.Api
{
IncludeItemTypes = new[] { typeof(GameSystem).Name }
};
- var parentIds = new string[] { } ;
- var gameSystems = _libraryManager.GetItemList(query, parentIds)
+ var gameSystems = _libraryManager.GetItemList(query)
.Cast<GameSystem>()
.ToList();
@@ -128,8 +129,7 @@ namespace MediaBrowser.Api
{
IncludeItemTypes = new[] { typeof(Game).Name }
};
- var parentIds = new string[] { };
- var games = _libraryManager.GetItemList(query, parentIds)
+ var games = _libraryManager.GetItemList(query)
.Cast<Game>()
.ToList();
@@ -185,20 +185,42 @@ namespace MediaBrowser.Api
/// </summary>
/// <param name="request">The request.</param>
/// <returns>System.Object.</returns>
- public object Get(GetSimilarGames request)
+ public async Task<object> Get(GetSimilarGames request)
{
+ var result = await GetSimilarItemsResult(request).ConfigureAwait(false);
+
+ return ToOptimizedSerializedResultUsingCache(result);
+ }
+
+ private async Task<QueryResult<BaseItemDto>> GetSimilarItemsResult(BaseGetSimilarItemsFromItem request)
+ {
+ var user = !string.IsNullOrWhiteSpace(request.UserId) ? _userManager.GetUserById(request.UserId) : null;
+
+ var item = string.IsNullOrEmpty(request.Id) ?
+ (!string.IsNullOrWhiteSpace(request.UserId) ? user.RootFolder :
+ _libraryManager.RootFolder) : _libraryManager.GetItemById(request.Id);
+
+ var itemsResult = _libraryManager.GetItemList(new InternalItemsQuery(user)
+ {
+ Limit = request.Limit,
+ IncludeItemTypes = new[]
+ {
+ typeof(Game).Name
+ },
+ SimilarTo = item
+
+ }).ToList();
+
var dtoOptions = GetDtoOptions(request);
- var result = SimilarItemsHelper.GetSimilarItemsResult(dtoOptions, _userManager,
- _itemRepo,
- _libraryManager,
- _userDataRepository,
- _dtoService,
- Logger,
- request, new[] { typeof(Game) },
- SimilarItemsHelper.GetSimiliarityScore);
+ var result = new QueryResult<BaseItemDto>
+ {
+ Items = (await _dtoService.GetBaseItemDtos(itemsResult, dtoOptions, user).ConfigureAwait(false)).ToArray(),
- return ToOptimizedSerializedResultUsingCache(result);
+ TotalRecordCount = itemsResult.Count
+ };
+
+ return result;
}
}
}
diff --git a/MediaBrowser.Api/Images/ImageService.cs b/MediaBrowser.Api/Images/ImageService.cs
index e5fe5bd68..a511f8c72 100644
--- a/MediaBrowser.Api/Images/ImageService.cs
+++ b/MediaBrowser.Api/Images/ImageService.cs
@@ -514,7 +514,7 @@ namespace MediaBrowser.Api.Images
/// <param name="isHeadRequest">if set to <c>true</c> [is head request].</param>
/// <returns>System.Object.</returns>
/// <exception cref="ResourceNotFoundException"></exception>
- public object GetImage(ImageRequest request, IHasImages item, bool isHeadRequest)
+ public Task<object> GetImage(ImageRequest request, IHasImages item, bool isHeadRequest)
{
if (request.PercentPlayed.HasValue)
{
@@ -594,8 +594,7 @@ namespace MediaBrowser.Api.Images
supportedImageEnhancers,
cacheDuration,
responseHeaders,
- isHeadRequest)
- .Result;
+ isHeadRequest);
}
private async Task<object> GetImageResult(IHasImages item,
@@ -632,7 +631,7 @@ namespace MediaBrowser.Api.Images
headers["Vary"] = "Accept";
- return ResultFactory.GetStaticFileResult(Request, new StaticFileResultOptions
+ return await ResultFactory.GetStaticFileResult(Request, new StaticFileResultOptions
{
CacheDuration = cacheDuration,
ResponseHeaders = headers,
@@ -643,7 +642,8 @@ namespace MediaBrowser.Api.Images
// Sometimes imagemagick keeps a hold on the file briefly even after it's done writing to it.
// I'd rather do this than add a delay after saving the file
FileShare = FileShare.ReadWrite
- });
+
+ }).ConfigureAwait(false);
}
private List<ImageFormat> GetOutputFormats(ImageRequest request, ItemImageInfo image, bool cropwhitespace, List<IImageEnhancer> enhancers)
diff --git a/MediaBrowser.Api/Images/RemoteImageService.cs b/MediaBrowser.Api/Images/RemoteImageService.cs
index 02d1cdbe2..b21e54495 100644
--- a/MediaBrowser.Api/Images/RemoteImageService.cs
+++ b/MediaBrowser.Api/Images/RemoteImageService.cs
@@ -238,9 +238,9 @@ namespace MediaBrowser.Api.Images
}
if (_fileSystem.FileExists(contentPath))
- {
- return ToStaticFileResult(contentPath);
- }
+ {
+ return await ResultFactory.GetStaticFileResult(Request, contentPath).ConfigureAwait(false);
+ }
}
catch (DirectoryNotFoundException)
{
@@ -259,9 +259,9 @@ namespace MediaBrowser.Api.Images
contentPath = await reader.ReadToEndAsync().ConfigureAwait(false);
}
- return ToStaticFileResult(contentPath);
+ return await ResultFactory.GetStaticFileResult(Request, contentPath).ConfigureAwait(false);
}
-
+
/// <summary>
/// Downloads the image.
/// </summary>
diff --git a/MediaBrowser.Api/ItemLookupService.cs b/MediaBrowser.Api/ItemLookupService.cs
index 41bfd9da2..357ff4394 100644
--- a/MediaBrowser.Api/ItemLookupService.cs
+++ b/MediaBrowser.Api/ItemLookupService.cs
@@ -38,6 +38,12 @@ namespace MediaBrowser.Api
{
}
+ [Route("/Items/RemoteSearch/Trailer", "POST")]
+ [Authenticated]
+ public class GetTrailerRemoteSearchResults : RemoteSearchQuery<TrailerInfo>, IReturn<List<RemoteSearchResult>>
+ {
+ }
+
[Route("/Items/RemoteSearch/AdultVideo", "POST")]
[Authenticated]
public class GetAdultVideoRemoteSearchResults : RemoteSearchQuery<ItemLookupInfo>, IReturn<List<RemoteSearchResult>>
@@ -132,60 +138,65 @@ namespace MediaBrowser.Api
return ToOptimizedResult(infos);
}
- public object Post(GetMovieRemoteSearchResults request)
+ public async Task<object> Post(GetTrailerRemoteSearchResults request)
{
- var result = _providerManager.GetRemoteSearchResults<Movie, MovieInfo>(request, CancellationToken.None).Result;
+ var result = await _providerManager.GetRemoteSearchResults<Trailer, TrailerInfo>(request, CancellationToken.None).ConfigureAwait(false);
return ToOptimizedResult(result);
}
- public object Post(GetSeriesRemoteSearchResults request)
+ public async Task<object> Post(GetMovieRemoteSearchResults request)
{
- var result = _providerManager.GetRemoteSearchResults<Series, SeriesInfo>(request, CancellationToken.None).Result;
+ var result = await _providerManager.GetRemoteSearchResults<Movie, MovieInfo>(request, CancellationToken.None).ConfigureAwait(false);
return ToOptimizedResult(result);
}
- public object Post(GetGameRemoteSearchResults request)
+ public async Task<object> Post(GetSeriesRemoteSearchResults request)
{
- var result = _providerManager.GetRemoteSearchResults<Game, GameInfo>(request, CancellationToken.None).Result;
+ var result = await _providerManager.GetRemoteSearchResults<Series, SeriesInfo>(request, CancellationToken.None).ConfigureAwait(false);
return ToOptimizedResult(result);
}
- public object Post(GetBoxSetRemoteSearchResults request)
+ public async Task<object> Post(GetGameRemoteSearchResults request)
{
- var result = _providerManager.GetRemoteSearchResults<BoxSet, BoxSetInfo>(request, CancellationToken.None).Result;
+ var result = await _providerManager.GetRemoteSearchResults<Game, GameInfo>(request, CancellationToken.None).ConfigureAwait(false);
return ToOptimizedResult(result);
}
- public object Post(GetPersonRemoteSearchResults request)
+ public async Task<object> Post(GetBoxSetRemoteSearchResults request)
{
- var result = _providerManager.GetRemoteSearchResults<Person, PersonLookupInfo>(request, CancellationToken.None).Result;
+ var result = await _providerManager.GetRemoteSearchResults<BoxSet, BoxSetInfo>(request, CancellationToken.None).ConfigureAwait(false);
return ToOptimizedResult(result);
}
- public object Post(GetMusicAlbumRemoteSearchResults request)
+ public async Task<object> Post(GetPersonRemoteSearchResults request)
{
- var result = _providerManager.GetRemoteSearchResults<MusicAlbum, AlbumInfo>(request, CancellationToken.None).Result;
+ var result = await _providerManager.GetRemoteSearchResults<Person, PersonLookupInfo>(request, CancellationToken.None).ConfigureAwait(false);
return ToOptimizedResult(result);
}
- public object Post(GetMusicArtistRemoteSearchResults request)
+ public async Task<object> Post(GetMusicAlbumRemoteSearchResults request)
{
- var result = _providerManager.GetRemoteSearchResults<MusicArtist, ArtistInfo>(request, CancellationToken.None).Result;
+ var result = await _providerManager.GetRemoteSearchResults<MusicAlbum, AlbumInfo>(request, CancellationToken.None).ConfigureAwait(false);
return ToOptimizedResult(result);
}
- public object Get(GetRemoteSearchImage request)
+ public async Task<object> Post(GetMusicArtistRemoteSearchResults request)
{
- var result = GetRemoteImage(request).Result;
+ var result = await _providerManager.GetRemoteSearchResults<MusicArtist, ArtistInfo>(request, CancellationToken.None).ConfigureAwait(false);
- return result;
+ return ToOptimizedResult(result);
+ }
+
+ public Task<object> Get(GetRemoteSearchImage request)
+ {
+ return GetRemoteImage(request);
}
public void Post(ApplySearchCriteria request)
@@ -241,7 +252,7 @@ namespace MediaBrowser.Api
if (_fileSystem.FileExists(contentPath))
{
- return ToStaticFileResult(contentPath);
+ return await ResultFactory.GetStaticFileResult(Request, contentPath).ConfigureAwait(false);
}
}
catch (DirectoryNotFoundException)
@@ -261,7 +272,7 @@ namespace MediaBrowser.Api
contentPath = await reader.ReadToEndAsync().ConfigureAwait(false);
}
- return ToStaticFileResult(contentPath);
+ return await ResultFactory.GetStaticFileResult(Request, contentPath).ConfigureAwait(false);
}
/// <summary>
diff --git a/MediaBrowser.Api/ItemUpdateService.cs b/MediaBrowser.Api/ItemUpdateService.cs
index 5bb4ed5f0..b944a39b6 100644
--- a/MediaBrowser.Api/ItemUpdateService.cs
+++ b/MediaBrowser.Api/ItemUpdateService.cs
@@ -70,26 +70,21 @@ namespace MediaBrowser.Api
Cultures = _localizationManager.GetCultures().ToList()
};
- var locationType = item.LocationType;
- if (locationType == LocationType.FileSystem ||
- locationType == LocationType.Offline)
+ if (!item.IsVirtualItem && !(item is ICollectionFolder) && !(item is UserView) && !(item is AggregateFolder) && !(item is LiveTvChannel) && !(item is IItemByName))
{
- if (!(item is ICollectionFolder) && !(item is UserView) && !(item is AggregateFolder) && !(item is LiveTvChannel) && !(item is IItemByName))
+ var inheritedContentType = _libraryManager.GetInheritedContentType(item);
+ var configuredContentType = _libraryManager.GetConfiguredContentType(item);
+
+ if (string.IsNullOrWhiteSpace(inheritedContentType) || string.Equals(inheritedContentType, CollectionType.TvShows, StringComparison.OrdinalIgnoreCase) || !string.IsNullOrWhiteSpace(configuredContentType))
{
- var inheritedContentType = _libraryManager.GetInheritedContentType(item);
- var configuredContentType = _libraryManager.GetConfiguredContentType(item);
+ info.ContentTypeOptions = GetContentTypeOptions(true);
+ info.ContentType = configuredContentType;
- if (string.IsNullOrWhiteSpace(inheritedContentType) || string.Equals(inheritedContentType, CollectionType.TvShows, StringComparison.OrdinalIgnoreCase) || !string.IsNullOrWhiteSpace(configuredContentType))
+ if (string.Equals(inheritedContentType, CollectionType.TvShows, StringComparison.OrdinalIgnoreCase))
{
- info.ContentTypeOptions = GetContentTypeOptions(true);
- info.ContentType = configuredContentType;
-
- if (string.Equals(inheritedContentType, CollectionType.TvShows, StringComparison.OrdinalIgnoreCase))
- {
- info.ContentTypeOptions = info.ContentTypeOptions
- .Where(i => string.IsNullOrWhiteSpace(i.Value) || string.Equals(i.Value, CollectionType.TvShows, StringComparison.OrdinalIgnoreCase))
- .ToList();
- }
+ info.ContentTypeOptions = info.ContentTypeOptions
+ .Where(i => string.IsNullOrWhiteSpace(i.Value) || string.Equals(i.Value, CollectionType.TvShows, StringComparison.OrdinalIgnoreCase))
+ .ToList();
}
}
}
@@ -280,11 +275,7 @@ namespace MediaBrowser.Api
episode.AbsoluteEpisodeNumber = request.AbsoluteEpisodeNumber;
}
- var hasTags = item as IHasTags;
- if (hasTags != null)
- {
- hasTags.Tags = request.Tags;
- }
+ item.Tags = request.Tags;
var hasTaglines = item as IHasTaglines;
if (hasTaglines != null)
@@ -298,11 +289,7 @@ namespace MediaBrowser.Api
hasShortOverview.ShortOverview = request.ShortOverview;
}
- var hasKeywords = item as IHasKeywords;
- if (hasKeywords != null)
- {
- hasKeywords.Keywords = request.Keywords;
- }
+ item.Keywords = request.Keywords;
if (request.Studios != null)
{
@@ -427,11 +414,6 @@ namespace MediaBrowser.Api
series.Status = request.SeriesStatus;
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 f9b3def97..4cd6a66ef 100644
--- a/MediaBrowser.Api/Library/LibraryService.cs
+++ b/MediaBrowser.Api/Library/LibraryService.cs
@@ -493,7 +493,7 @@ namespace MediaBrowser.Api.Library
}
}
- public object Get(GetDownload request)
+ public Task<object> Get(GetDownload request)
{
var item = _libraryManager.GetItemById(request.Id);
var auth = _authContext.GetAuthorizationInfo(Request);
@@ -552,7 +552,7 @@ namespace MediaBrowser.Api.Library
}
}
- public object Get(GetFile request)
+ public Task<object> Get(GetFile request)
{
var item = _libraryManager.GetItemById(request.Id);
var locationType = item.LocationType;
@@ -565,7 +565,7 @@ namespace MediaBrowser.Api.Library
throw new ArgumentException("This command cannot be used for directories.");
}
- return ToStaticFileResult(item.Path);
+ return ResultFactory.GetStaticFileResult(Request, item.Path);
}
/// <summary>
diff --git a/MediaBrowser.Api/LiveTv/LiveTvService.cs b/MediaBrowser.Api/LiveTv/LiveTvService.cs
index d3a4558c8..c687758b7 100644
--- a/MediaBrowser.Api/LiveTv/LiveTvService.cs
+++ b/MediaBrowser.Api/LiveTv/LiveTvService.cs
@@ -146,6 +146,13 @@ namespace MediaBrowser.Api.LiveTv
/// <value>The fields.</value>
[ApiMember(Name = "Fields", Description = "Optional. Specify additional fields of information to return in the output. This allows multiple, comma delimeted. Options: Budget, Chapters, CriticRatingSummary, DateCreated, Genres, HomePageUrl, IndexOptions, MediaStreams, Overview, ParentId, Path, People, ProviderIds, PrimaryImageAspectRatio, Revenue, SortName, Studios, Taglines", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)]
public string Fields { get; set; }
+
+ public bool EnableTotalRecordCount { get; set; }
+
+ public GetRecordings()
+ {
+ EnableTotalRecordCount = true;
+ }
}
[Route("/LiveTv/Recordings/Groups", "GET", Summary = "Gets live tv recording groups")]
@@ -200,6 +207,8 @@ namespace MediaBrowser.Api.LiveTv
[ApiMember(Name = "SeriesTimerId", Description = "Optional filter by timers belonging to a series timer", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
public string SeriesTimerId { get; set; }
+
+ public bool? IsActive { get; set; }
}
[Route("/LiveTv/Programs", "GET,POST", Summary = "Gets available live tv epgs..")]
@@ -439,6 +448,12 @@ namespace MediaBrowser.Api.LiveTv
public string Id { get; set; }
}
+ [Route("/LiveTv/ListingProviders/Default", "GET")]
+ [Authenticated(AllowBeforeStartupWizard = true)]
+ public class GetDefaultListingProvider : ListingsProviderInfo, IReturn<ListingsProviderInfo>
+ {
+ }
+
[Route("/LiveTv/ListingProviders", "POST", Summary = "Adds a listing provider")]
[Authenticated(AllowBeforeStartupWizard = true)]
public class AddListingProvider : ListingsProviderInfo, IReturn<ListingsProviderInfo>
@@ -478,6 +493,32 @@ namespace MediaBrowser.Api.LiveTv
{
}
+ [Route("/LiveTv/ChannelMappingOptions")]
+ [Authenticated(AllowBeforeStartupWizard = true)]
+ public class GetChannelMappingOptions
+ {
+ [ApiMember(Name = "Id", Description = "Provider id", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "GET")]
+ public string ProviderId { get; set; }
+ }
+
+ [Route("/LiveTv/ChannelMappings")]
+ [Authenticated(AllowBeforeStartupWizard = true)]
+ public class SetChannelMapping
+ {
+ [ApiMember(Name = "Id", Description = "Provider id", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "GET")]
+ public string ProviderId { get; set; }
+ public string TunerChannelNumber { get; set; }
+ public string ProviderChannelNumber { get; set; }
+ }
+
+ public class ChannelMappingOptions
+ {
+ public List<TunerChannelMapping> TunerChannels { get; set; }
+ public List<NameIdPair> ProviderChannels { get; set; }
+ public List<NameValuePair> Mappings { get; set; }
+ public string ProviderName { get; set; }
+ }
+
[Route("/LiveTv/Registration", "GET")]
[Authenticated]
public class GetLiveTvRegistrationInfo : IReturn<MBRegistrationRecord>
@@ -525,6 +566,11 @@ namespace MediaBrowser.Api.LiveTv
_dtoService = dtoService;
}
+ public object Get(GetDefaultListingProvider request)
+ {
+ return ToOptimizedResult(new ListingsProviderInfo());
+ }
+
public async Task<object> Get(GetSatChannnelScanResult request)
{
var result = await _liveTvManager.GetSatChannelScanResult(request, CancellationToken.None).ConfigureAwait(false);
@@ -539,6 +585,46 @@ namespace MediaBrowser.Api.LiveTv
return ToOptimizedResult(result);
}
+ public async Task<object> Post(SetChannelMapping request)
+ {
+ return await _liveTvManager.SetChannelMapping(request.ProviderId, request.TunerChannelNumber, request.ProviderChannelNumber).ConfigureAwait(false);
+ }
+
+ public async Task<object> Get(GetChannelMappingOptions request)
+ {
+ var config = GetConfiguration();
+
+ var listingsProviderInfo = config.ListingProviders.First(i => string.Equals(request.ProviderId, i.Id, StringComparison.OrdinalIgnoreCase));
+
+ var listingsProviderName = _liveTvManager.ListingProviders.First(i => string.Equals(i.Type, listingsProviderInfo.Type, StringComparison.OrdinalIgnoreCase)).Name;
+
+ var tunerChannels = await _liveTvManager.GetChannelsForListingsProvider(request.ProviderId, CancellationToken.None)
+ .ConfigureAwait(false);
+
+ var providerChannels = await _liveTvManager.GetChannelsFromListingsProviderData(request.ProviderId, CancellationToken.None)
+ .ConfigureAwait(false);
+
+ var mappings = listingsProviderInfo.ChannelMappings.ToList();
+
+ var result = new ChannelMappingOptions
+ {
+ TunerChannels = tunerChannels.Select(i => _liveTvManager.GetTunerChannelMapping(i, mappings, providerChannels)).ToList(),
+
+ ProviderChannels = providerChannels.Select(i => new NameIdPair
+ {
+ Name = i.Name,
+ Id = i.Number
+
+ }).ToList(),
+
+ Mappings = mappings,
+
+ ProviderName = listingsProviderName
+ };
+
+ return ToOptimizedResult(result);
+ }
+
public object Get(GetSatIniMappings request)
{
return ToOptimizedResult(_liveTvManager.GetSatIniMappings());
@@ -550,9 +636,7 @@ namespace MediaBrowser.Api.LiveTv
var response = await _httpClient.Get(new HttpRequestOptions
{
- Url = "https://json.schedulesdirect.org/20141201/available/countries",
- CacheLength = TimeSpan.FromDays(1),
- CacheMode = CacheMode.Unconditional
+ Url = "https://json.schedulesdirect.org/20141201/available/countries"
}).ConfigureAwait(false);
@@ -582,11 +666,7 @@ namespace MediaBrowser.Api.LiveTv
public void Delete(DeleteListingProvider request)
{
- var config = GetConfiguration();
-
- config.ListingProviders = config.ListingProviders.Where(i => !string.Equals(request.Id, i.Id, StringComparison.OrdinalIgnoreCase)).ToList();
-
- _config.SaveConfiguration("livetv", config);
+ _liveTvManager.DeleteListingsProvider(request.Id);
}
public async Task<object> Post(AddTunerHost request)
@@ -609,6 +689,11 @@ namespace MediaBrowser.Api.LiveTv
return _config.GetConfiguration<LiveTvOptions>("livetv");
}
+ private void UpdateConfiguration(LiveTvOptions options)
+ {
+ _config.SaveConfiguration("livetv", options);
+ }
+
public async Task<object> Get(GetLineups request)
{
var info = await _liveTvManager.GetLineups(request.Type, request.Id, request.Country, request.Location).ConfigureAwait(false);
@@ -641,14 +726,14 @@ namespace MediaBrowser.Api.LiveTv
var user = string.IsNullOrEmpty(request.UserId) ? null : _userManager.GetUserById(request.UserId);
- var returnArray = _dtoService.GetBaseItemDtos(channelResult.Items, GetDtoOptions(Request), user).ToArray();
+ var returnArray = (await _dtoService.GetBaseItemDtos(channelResult.Items, GetDtoOptions(Request), user).ConfigureAwait(false)).ToArray();
var result = new QueryResult<BaseItemDto>
{
Items = returnArray,
TotalRecordCount = channelResult.TotalRecordCount
};
-
+
return ToOptimizedSerializedResultUsingCache(result);
}
@@ -752,7 +837,8 @@ namespace MediaBrowser.Api.LiveTv
Limit = request.Limit,
Status = request.Status,
SeriesTimerId = request.SeriesTimerId,
- IsInProgress = request.IsInProgress
+ IsInProgress = request.IsInProgress,
+ EnableTotalRecordCount = request.EnableTotalRecordCount
}, options, CancellationToken.None).ConfigureAwait(false);
@@ -783,7 +869,8 @@ namespace MediaBrowser.Api.LiveTv
var result = await _liveTvManager.GetTimers(new TimerQuery
{
ChannelId = request.ChannelId,
- SeriesTimerId = request.SeriesTimerId
+ SeriesTimerId = request.SeriesTimerId,
+ IsActive = request.IsActive
}, CancellationToken.None).ConfigureAwait(false);
diff --git a/MediaBrowser.Api/Movies/MoviesService.cs b/MediaBrowser.Api/Movies/MoviesService.cs
index d8946e416..755713c84 100644
--- a/MediaBrowser.Api/Movies/MoviesService.cs
+++ b/MediaBrowser.Api/Movies/MoviesService.cs
@@ -14,6 +14,7 @@ using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
+using MediaBrowser.Controller.LiveTv;
namespace MediaBrowser.Api.Movies
{
@@ -112,16 +113,14 @@ namespace MediaBrowser.Api.Movies
/// <returns>System.Object.</returns>
public async Task<object> Get(GetSimilarMovies request)
{
- var result = await GetSimilarItemsResult(
- request, SimilarItemsHelper.GetSimiliarityScore).ConfigureAwait(false);
+ var result = await GetSimilarItemsResult(request).ConfigureAwait(false);
return ToOptimizedSerializedResultUsingCache(result);
}
public async Task<object> Get(GetSimilarTrailers request)
{
- var result = await GetSimilarItemsResult(
- request, SimilarItemsHelper.GetSimiliarityScore).ConfigureAwait(false);
+ var result = await GetSimilarItemsResult(request).ConfigureAwait(false);
return ToOptimizedSerializedResultUsingCache(result);
}
@@ -130,148 +129,93 @@ namespace MediaBrowser.Api.Movies
{
var user = _userManager.GetUserById(request.UserId);
- var query = new InternalItemsQuery(user)
- {
- IncludeItemTypes = new[] { typeof(Movie).Name }
- };
-
- if (user.Configuration.IncludeTrailersInSuggestions)
- {
- var includeList = query.IncludeItemTypes.ToList();
- includeList.Add(typeof(Trailer).Name);
- query.IncludeItemTypes = includeList.ToArray();
- }
-
- var parentIds = string.IsNullOrWhiteSpace(request.ParentId) ? new string[] { } : new[] { request.ParentId };
- var movies = _libraryManager.GetItemList(query, parentIds)
- .OrderBy(i => (int)i.SourceType);
-
- var listEligibleForCategories = new List<BaseItem>();
- var listEligibleForSuggestion = new List<BaseItem>();
-
- var list = movies.ToList();
-
- listEligibleForCategories.AddRange(list);
- listEligibleForSuggestion.AddRange(list);
-
- listEligibleForCategories = listEligibleForCategories
- // Exclude trailers from the suggestion categories
- .Where(i => i is Movie)
- .DistinctBy(i => i.Name, StringComparer.OrdinalIgnoreCase)
- .DistinctBy(i => i.GetProviderId(MetadataProviders.Imdb) ?? Guid.NewGuid().ToString(), StringComparer.OrdinalIgnoreCase)
- .ToList();
-
- listEligibleForSuggestion = listEligibleForSuggestion
- .DistinctBy(i => i.Name, StringComparer.OrdinalIgnoreCase)
- .DistinctBy(i => i.GetProviderId(MetadataProviders.Imdb) ?? Guid.NewGuid().ToString(), StringComparer.OrdinalIgnoreCase)
- .ToList();
-
var dtoOptions = GetDtoOptions(request);
dtoOptions.Fields = request.GetItemFields().ToList();
- var result = GetRecommendationCategories(user, listEligibleForCategories, listEligibleForSuggestion, request.CategoryLimit, request.ItemLimit, dtoOptions);
+ var result = GetRecommendationCategories(user, request.ParentId, request.CategoryLimit, request.ItemLimit, dtoOptions);
return ToOptimizedResult(result);
}
- private async Task<ItemsResult> GetSimilarItemsResult(BaseGetSimilarItemsFromItem request, Func<BaseItem, List<PersonInfo>, List<PersonInfo>, BaseItem, int> getSimilarityScore)
+ private async Task<QueryResult<BaseItemDto>> GetSimilarItemsResult(BaseGetSimilarItemsFromItem request)
{
var user = !string.IsNullOrWhiteSpace(request.UserId) ? _userManager.GetUserById(request.UserId) : null;
var item = string.IsNullOrEmpty(request.Id) ?
(!string.IsNullOrWhiteSpace(request.UserId) ? user.RootFolder :
_libraryManager.RootFolder) : _libraryManager.GetItemById(request.Id);
-
- var query = new InternalItemsQuery(user)
- {
- IncludeItemTypes = new[] { typeof(Movie).Name }
- };
-
- if (user == null || user.Configuration.IncludeTrailersInSuggestions)
- {
- var includeList = query.IncludeItemTypes.ToList();
- includeList.Add(typeof(Trailer).Name);
- query.IncludeItemTypes = includeList.ToArray();
- }
-
- var list = _libraryManager.GetItemList(query)
- .OrderBy(i => (int)i.SourceType)
- .DistinctBy(i => i.GetProviderId(MetadataProviders.Imdb) ?? Guid.NewGuid().ToString("N"))
- .ToList();
- if (item is Video)
+ var itemsResult = _libraryManager.GetItemList(new InternalItemsQuery(user)
{
- var imdbId = item.GetProviderId(MetadataProviders.Imdb);
-
- // Use imdb id to try to filter duplicates of the same item
- if (!string.IsNullOrWhiteSpace(imdbId))
+ Limit = request.Limit,
+ IncludeItemTypes = new[]
{
- list = list
- .Where(i => !string.Equals(imdbId, i.GetProviderId(MetadataProviders.Imdb), StringComparison.OrdinalIgnoreCase))
- .ToList();
- }
- }
-
- var items = SimilarItemsHelper.GetSimilaritems(item, _libraryManager, list, getSimilarityScore).ToList();
-
- IEnumerable<BaseItem> returnItems = items;
+ typeof(Movie).Name,
+ typeof(Trailer).Name,
+ typeof(LiveTvProgram).Name
+ },
+ IsMovie = true,
+ SimilarTo = item,
+ EnableGroupByMetadataKey = true
- if (request.Limit.HasValue)
- {
- returnItems = returnItems.Take(request.Limit.Value);
- }
+ }).ToList();
var dtoOptions = GetDtoOptions(request);
- var result = new ItemsResult
+ var result = new QueryResult<BaseItemDto>
{
- Items = _dtoService.GetBaseItemDtos(returnItems, dtoOptions, user).ToArray(),
+ Items = (await _dtoService.GetBaseItemDtos(itemsResult, dtoOptions, user).ConfigureAwait(false)).ToArray(),
- TotalRecordCount = items.Count
+ TotalRecordCount = itemsResult.Count
};
return result;
}
- private IEnumerable<RecommendationDto> GetRecommendationCategories(User user, List<BaseItem> allMoviesForCategories, List<BaseItem> allMovies, int categoryLimit, int itemLimit, DtoOptions dtoOptions)
+ private IEnumerable<RecommendationDto> GetRecommendationCategories(User user, string parentId, int categoryLimit, int itemLimit, DtoOptions dtoOptions)
{
var categories = new List<RecommendationDto>();
- var recentlyPlayedMovies = allMoviesForCategories
- .Select(i =>
- {
- var userdata = _userDataRepository.GetUserData(user, i);
- return new Tuple<BaseItem, bool, DateTime>(i, userdata.Played, userdata.LastPlayedDate ?? DateTime.MinValue);
- })
- .Where(i => i.Item2)
- .OrderByDescending(i => i.Item3)
- .Select(i => i.Item1)
- .ToList();
+ var parentIdGuid = string.IsNullOrWhiteSpace(parentId) ? (Guid?)null : new Guid(parentId);
- var excludeFromLiked = recentlyPlayedMovies.Take(10);
- var likedMovies = allMovies
- .Select(i =>
+ var query = new InternalItemsQuery(user)
+ {
+ IncludeItemTypes = new[]
{
- var score = 0;
- var userData = _userDataRepository.GetUserData(user, i);
+ typeof(Movie).Name,
+ //typeof(Trailer).Name,
+ //typeof(LiveTvProgram).Name
+ },
+ // IsMovie = true
+ SortBy = new[] { ItemSortBy.DatePlayed, ItemSortBy.Random },
+ SortOrder = SortOrder.Descending,
+ Limit = 7,
+ ParentId = parentIdGuid,
+ Recursive = true
+ };
- if (userData.IsFavorite)
- {
- score = 2;
- }
- else
- {
- score = userData.Likes.HasValue ? userData.Likes.Value ? 1 : -1 : 0;
- }
+ var recentlyPlayedMovies = _libraryManager.GetItemList(query).ToList();
- return new Tuple<BaseItem, int>(i, score);
- })
- .OrderByDescending(i => i.Item2)
- .ThenBy(i => Guid.NewGuid())
- .Where(i => i.Item2 > 0)
- .Select(i => i.Item1)
- .Where(i => !excludeFromLiked.Contains(i));
+ var likedMovies = _libraryManager.GetItemList(new InternalItemsQuery(user)
+ {
+ IncludeItemTypes = new[]
+ {
+ typeof(Movie).Name,
+ typeof(Trailer).Name,
+ typeof(LiveTvProgram).Name
+ },
+ IsMovie = true,
+ SortBy = new[] { ItemSortBy.Random },
+ SortOrder = SortOrder.Descending,
+ Limit = 10,
+ IsFavoriteOrLiked = true,
+ ExcludeItemIds = recentlyPlayedMovies.Select(i => i.Id.ToString("N")).ToArray(),
+ EnableGroupByMetadataKey = true,
+ ParentId = parentIdGuid,
+ Recursive = true
+
+ }).ToList();
var mostRecentMovies = recentlyPlayedMovies.Take(6).ToList();
// Get recently played directors
@@ -284,11 +228,11 @@ namespace MediaBrowser.Api.Movies
.OrderBy(i => Guid.NewGuid())
.ToList();
- var similarToRecentlyPlayed = GetSimilarTo(user, allMovies, recentlyPlayedMovies.Take(7).OrderBy(i => Guid.NewGuid()), itemLimit, dtoOptions, RecommendationType.SimilarToRecentlyPlayed).GetEnumerator();
- var similarToLiked = GetSimilarTo(user, allMovies, likedMovies, itemLimit, dtoOptions, RecommendationType.SimilarToLikedItem).GetEnumerator();
+ var similarToRecentlyPlayed = GetSimilarTo(user, recentlyPlayedMovies, itemLimit, dtoOptions, RecommendationType.SimilarToRecentlyPlayed).GetEnumerator();
+ var similarToLiked = GetSimilarTo(user, likedMovies, itemLimit, dtoOptions, RecommendationType.SimilarToLikedItem).GetEnumerator();
- var hasDirectorFromRecentlyPlayed = GetWithDirector(user, allMovies, recentDirectors, itemLimit, dtoOptions, RecommendationType.HasDirectorFromRecentlyPlayed).GetEnumerator();
- var hasActorFromRecentlyPlayed = GetWithActor(user, allMovies, recentActors, itemLimit, dtoOptions, RecommendationType.HasActorFromRecentlyPlayed).GetEnumerator();
+ var hasDirectorFromRecentlyPlayed = GetWithDirector(user, recentDirectors, itemLimit, dtoOptions, RecommendationType.HasDirectorFromRecentlyPlayed).GetEnumerator();
+ var hasActorFromRecentlyPlayed = GetWithActor(user, recentActors, itemLimit, dtoOptions, RecommendationType.HasActorFromRecentlyPlayed).GetEnumerator();
var categoryTypes = new List<IEnumerator<RecommendationDto>>
{
@@ -331,44 +275,63 @@ namespace MediaBrowser.Api.Movies
return categories.OrderBy(i => i.RecommendationType).ThenBy(i => Guid.NewGuid());
}
- private IEnumerable<RecommendationDto> GetWithDirector(User user, List<BaseItem> allMovies, IEnumerable<string> directors, int itemLimit, DtoOptions dtoOptions, RecommendationType type)
+ private IEnumerable<RecommendationDto> GetWithDirector(User user, IEnumerable<string> names, int itemLimit, DtoOptions dtoOptions, RecommendationType type)
{
- var userId = user.Id;
-
- foreach (var director in directors)
+ foreach (var name in names)
{
- var items = allMovies
- .Where(i => _libraryManager.GetPeople(i).Any(p => string.Equals(p.Type, PersonType.Director, StringComparison.OrdinalIgnoreCase) && string.Equals(p.Name, director, StringComparison.OrdinalIgnoreCase)))
- .Take(itemLimit)
- .ToList();
+ var items = _libraryManager.GetItemList(new InternalItemsQuery(user)
+ {
+ Person = name,
+ // Account for duplicates by imdb id, since the database doesn't support this yet
+ Limit = itemLimit + 2,
+ PersonTypes = new[] { PersonType.Director },
+ IncludeItemTypes = new[]
+ {
+ typeof(Movie).Name,
+ typeof(Trailer).Name,
+ typeof(LiveTvProgram).Name
+ },
+ IsMovie = true,
+ EnableGroupByMetadataKey = true
+
+ }).DistinctBy(i => i.GetProviderId(MetadataProviders.Imdb) ?? Guid.NewGuid().ToString("N"))
+ .Take(itemLimit)
+ .ToList();
if (items.Count > 0)
{
yield return new RecommendationDto
{
- BaselineItemName = director,
- CategoryId = director.GetMD5().ToString("N"),
+ BaselineItemName = name,
+ CategoryId = name.GetMD5().ToString("N"),
RecommendationType = type,
- Items = _dtoService.GetBaseItemDtos(items, dtoOptions, user).ToArray()
+ Items = _dtoService.GetBaseItemDtos(items, dtoOptions, user).Result.ToArray()
};
}
}
}
- private IEnumerable<RecommendationDto> GetWithActor(User user, List<BaseItem> allMovies, IEnumerable<string> names, int itemLimit, DtoOptions dtoOptions, RecommendationType type)
+ private IEnumerable<RecommendationDto> GetWithActor(User user, IEnumerable<string> names, int itemLimit, DtoOptions dtoOptions, RecommendationType type)
{
foreach (var name in names)
{
- var itemsWithActor = _libraryManager.GetItemIds(new InternalItemsQuery(user)
+ var items = _libraryManager.GetItemList(new InternalItemsQuery(user)
{
- Person = name
-
- });
-
- var items = allMovies
- .Where(i => itemsWithActor.Contains(i.Id))
- .Take(itemLimit)
- .ToList();
+ Person = name,
+ // Account for duplicates by imdb id, since the database doesn't support this yet
+ Limit = itemLimit + 2,
+ IncludeItemTypes = new[]
+ {
+ typeof(Movie).Name,
+ typeof(Trailer).Name,
+ typeof(LiveTvProgram).Name
+ },
+ IsMovie = true,
+ EnableGroupByMetadataKey = true
+
+ }).DistinctBy(i => i.GetProviderId(MetadataProviders.Imdb) ?? Guid.NewGuid().ToString("N"))
+ .Take(itemLimit)
+ .ToList();
if (items.Count > 0)
{
@@ -377,20 +340,30 @@ namespace MediaBrowser.Api.Movies
BaselineItemName = name,
CategoryId = name.GetMD5().ToString("N"),
RecommendationType = type,
- Items = _dtoService.GetBaseItemDtos(items, dtoOptions, user).ToArray()
+ Items = _dtoService.GetBaseItemDtos(items, dtoOptions, user).Result.ToArray()
};
}
}
}
- private IEnumerable<RecommendationDto> GetSimilarTo(User user, List<BaseItem> allMovies, IEnumerable<BaseItem> baselineItems, int itemLimit, DtoOptions dtoOptions, RecommendationType type)
+ private IEnumerable<RecommendationDto> GetSimilarTo(User user, List<BaseItem> baselineItems, int itemLimit, DtoOptions dtoOptions, RecommendationType type)
{
foreach (var item in baselineItems)
{
- var similar = SimilarItemsHelper
- .GetSimilaritems(item, _libraryManager, allMovies, SimilarItemsHelper.GetSimiliarityScore)
- .Take(itemLimit)
- .ToList();
+ var similar = _libraryManager.GetItemList(new InternalItemsQuery(user)
+ {
+ Limit = itemLimit,
+ IncludeItemTypes = new[]
+ {
+ typeof(Movie).Name,
+ typeof(Trailer).Name,
+ typeof(LiveTvProgram).Name
+ },
+ IsMovie = true,
+ SimilarTo = item,
+ EnableGroupByMetadataKey = true
+
+ }).ToList();
if (similar.Count > 0)
{
@@ -399,7 +372,7 @@ namespace MediaBrowser.Api.Movies
BaselineItemName = item.Name,
CategoryId = item.Id.ToString("N"),
RecommendationType = type,
- Items = _dtoService.GetBaseItemDtos(similar, dtoOptions, user).ToArray()
+ Items = _dtoService.GetBaseItemDtos(similar, dtoOptions, user).Result.ToArray()
};
}
}
diff --git a/MediaBrowser.Api/Movies/TrailersService.cs b/MediaBrowser.Api/Movies/TrailersService.cs
index d74dd5b6a..c70620cf9 100644
--- a/MediaBrowser.Api/Movies/TrailersService.cs
+++ b/MediaBrowser.Api/Movies/TrailersService.cs
@@ -58,7 +58,7 @@ namespace MediaBrowser.Api.Movies
getItems.IncludeItemTypes = "Trailer";
- return new ItemsService(_userManager, _libraryManager, _userDataRepository, _localizationManager, _dtoService, _collectionManager)
+ return new ItemsService(_userManager, _libraryManager, _localizationManager, _dtoService)
{
AuthorizationContext = AuthorizationContext,
Logger = Logger,
diff --git a/MediaBrowser.Api/Music/AlbumsService.cs b/MediaBrowser.Api/Music/AlbumsService.cs
index e774c3077..2d7e909bf 100644
--- a/MediaBrowser.Api/Music/AlbumsService.cs
+++ b/MediaBrowser.Api/Music/AlbumsService.cs
@@ -8,6 +8,7 @@ using ServiceStack;
using System;
using System.Collections.Generic;
using System.Linq;
+using System.Threading.Tasks;
namespace MediaBrowser.Api.Music
{
@@ -49,18 +50,18 @@ namespace MediaBrowser.Api.Music
_dtoService = dtoService;
}
- public object Get(GetSimilarArtists request)
+ public async Task<object> Get(GetSimilarArtists request)
{
var dtoOptions = GetDtoOptions(request);
- var result = SimilarItemsHelper.GetSimilarItemsResult(dtoOptions, _userManager,
+ var result = await SimilarItemsHelper.GetSimilarItemsResult(dtoOptions, _userManager,
_itemRepo,
_libraryManager,
_userDataRepository,
_dtoService,
Logger,
request, new[] { typeof(MusicArtist) },
- SimilarItemsHelper.GetSimiliarityScore);
+ SimilarItemsHelper.GetSimiliarityScore).ConfigureAwait(false);
return ToOptimizedSerializedResultUsingCache(result);
}
@@ -70,18 +71,18 @@ namespace MediaBrowser.Api.Music
/// </summary>
/// <param name="request">The request.</param>
/// <returns>System.Object.</returns>
- public object Get(GetSimilarAlbums request)
+ public async Task<object> Get(GetSimilarAlbums request)
{
var dtoOptions = GetDtoOptions(request);
- var result = SimilarItemsHelper.GetSimilarItemsResult(dtoOptions, _userManager,
+ var result = await SimilarItemsHelper.GetSimilarItemsResult(dtoOptions, _userManager,
_itemRepo,
_libraryManager,
_userDataRepository,
_dtoService,
Logger,
request, new[] { typeof(MusicAlbum) },
- GetAlbumSimilarityScore);
+ GetAlbumSimilarityScore).ConfigureAwait(false);
return ToOptimizedSerializedResultUsingCache(result);
}
diff --git a/MediaBrowser.Api/Music/InstantMixService.cs b/MediaBrowser.Api/Music/InstantMixService.cs
index d2a4aa60c..19265408b 100644
--- a/MediaBrowser.Api/Music/InstantMixService.cs
+++ b/MediaBrowser.Api/Music/InstantMixService.cs
@@ -8,6 +8,7 @@ using MediaBrowser.Model.Querying;
using ServiceStack;
using System.Collections.Generic;
using System.Linq;
+using System.Threading.Tasks;
namespace MediaBrowser.Api.Music
{
@@ -76,7 +77,7 @@ namespace MediaBrowser.Api.Music
_libraryManager = libraryManager;
}
- public object Get(GetInstantMixFromItem request)
+ public Task<object> Get(GetInstantMixFromItem request)
{
var item = _libraryManager.GetItemById(request.Id);
@@ -87,7 +88,7 @@ namespace MediaBrowser.Api.Music
return GetResult(items, user, request);
}
- public object Get(GetInstantMixFromArtistId request)
+ public Task<object> Get(GetInstantMixFromArtistId request)
{
var item = _libraryManager.GetItemById(request.Id);
@@ -98,7 +99,7 @@ namespace MediaBrowser.Api.Music
return GetResult(items, user, request);
}
- public object Get(GetInstantMixFromMusicGenreId request)
+ public Task<object> Get(GetInstantMixFromMusicGenreId request)
{
var item = _libraryManager.GetItemById(request.Id);
@@ -109,7 +110,7 @@ namespace MediaBrowser.Api.Music
return GetResult(items, user, request);
}
- public object Get(GetInstantMixFromSong request)
+ public Task<object> Get(GetInstantMixFromSong request)
{
var item = _libraryManager.GetItemById(request.Id);
@@ -120,7 +121,7 @@ namespace MediaBrowser.Api.Music
return GetResult(items, user, request);
}
- public object Get(GetInstantMixFromAlbum request)
+ public Task<object> Get(GetInstantMixFromAlbum request)
{
var album = _libraryManager.GetItemById(request.Id);
@@ -131,7 +132,7 @@ namespace MediaBrowser.Api.Music
return GetResult(items, user, request);
}
- public object Get(GetInstantMixFromPlaylist request)
+ public Task<object> Get(GetInstantMixFromPlaylist request)
{
var playlist = (Playlist)_libraryManager.GetItemById(request.Id);
@@ -142,7 +143,7 @@ namespace MediaBrowser.Api.Music
return GetResult(items, user, request);
}
- public object Get(GetInstantMixFromMusicGenre request)
+ public Task<object> Get(GetInstantMixFromMusicGenre request)
{
var user = _userManager.GetUserById(request.UserId);
@@ -151,7 +152,7 @@ namespace MediaBrowser.Api.Music
return GetResult(items, user, request);
}
- public object Get(GetInstantMixFromArtist request)
+ public Task<object> Get(GetInstantMixFromArtist request)
{
var user = _userManager.GetUserById(request.UserId);
var artist = _libraryManager.GetArtist(request.Name);
@@ -161,7 +162,7 @@ namespace MediaBrowser.Api.Music
return GetResult(items, user, request);
}
- private object GetResult(IEnumerable<Audio> items, User user, BaseGetSimilarItems request)
+ private async Task<object> GetResult(IEnumerable<Audio> items, User user, BaseGetSimilarItems request)
{
var list = items.ToList();
@@ -172,7 +173,7 @@ namespace MediaBrowser.Api.Music
var dtoOptions = GetDtoOptions(request);
- result.Items = _dtoService.GetBaseItemDtos(list.Take(request.Limit ?? list.Count), dtoOptions, user).ToArray();
+ result.Items = (await _dtoService.GetBaseItemDtos(list.Take(request.Limit ?? list.Count), dtoOptions, user).ConfigureAwait(false)).ToArray();
return ToOptimizedResult(result);
}
diff --git a/MediaBrowser.Api/PackageReviewService.cs b/MediaBrowser.Api/PackageReviewService.cs
index e70d6a713..0a5b9bef8 100644
--- a/MediaBrowser.Api/PackageReviewService.cs
+++ b/MediaBrowser.Api/PackageReviewService.cs
@@ -112,7 +112,7 @@ namespace MediaBrowser.Api
_appHost = appHost;
}
- public object Get(ReviewRequest request)
+ public async Task<object> Get(ReviewRequest request)
{
var parms = "?id=" + request.Id;
@@ -133,11 +133,13 @@ namespace MediaBrowser.Api
parms += "&title=true";
}
- var result = _httpClient.Get(MbAdminUrl + "/service/packageReview/retrieve" + parms, CancellationToken.None).Result;
-
- var reviews = _serializer.DeserializeFromStream<List<PackageReviewInfo>>(result);
+ using (var result = await _httpClient.Get(MbAdminUrl + "/service/packageReview/retrieve" + parms, CancellationToken.None)
+ .ConfigureAwait(false))
+ {
+ var reviews = _serializer.DeserializeFromStream<List<PackageReviewInfo>>(result);
- return ToOptimizedResult(reviews);
+ return ToOptimizedResult(reviews);
+ }
}
public void Post(CreateReviewRequest request)
diff --git a/MediaBrowser.Api/Playback/BaseStreamingService.cs b/MediaBrowser.Api/Playback/BaseStreamingService.cs
index 421ccdb5d..01d959d70 100644
--- a/MediaBrowser.Api/Playback/BaseStreamingService.cs
+++ b/MediaBrowser.Api/Playback/BaseStreamingService.cs
@@ -286,11 +286,19 @@ namespace MediaBrowser.Api.Playback
protected string GetH264Encoder(StreamState state)
{
- if (string.Equals(ApiEntryPoint.Instance.GetEncodingOptions().HardwareAccelerationType, "qsv", StringComparison.OrdinalIgnoreCase))
+ if (string.Equals(ApiEntryPoint.Instance.GetEncodingOptions().HardwareAccelerationType, "qsv", StringComparison.OrdinalIgnoreCase) ||
+ string.Equals(ApiEntryPoint.Instance.GetEncodingOptions().HardwareAccelerationType, "h264_qsv", StringComparison.OrdinalIgnoreCase))
{
-
return "h264_qsv";
+ }
+ if (string.Equals(ApiEntryPoint.Instance.GetEncodingOptions().HardwareAccelerationType, "libnvenc", StringComparison.OrdinalIgnoreCase))
+ {
+ return "libnvenc";
+ }
+ if (string.Equals(ApiEntryPoint.Instance.GetEncodingOptions().HardwareAccelerationType, "h264_omx", StringComparison.OrdinalIgnoreCase))
+ {
+ return "h264_omx";
}
return "libx264";
@@ -395,15 +403,18 @@ namespace MediaBrowser.Api.Playback
if (!string.IsNullOrEmpty(state.VideoRequest.Profile))
{
- param += " -profile:v " + state.VideoRequest.Profile;
+ if (!string.Equals(videoCodec, "h264_omx", StringComparison.OrdinalIgnoreCase))
+ {
+ // not supported by h264_omx
+ param += " -profile:v " + state.VideoRequest.Profile;
+ }
}
if (!string.IsNullOrEmpty(state.VideoRequest.Level))
{
- var h264Encoder = GetH264Encoder(state);
-
// h264_qsv and libnvenc expect levels to be expressed as a decimal. libx264 supports decimal and non-decimal format
- if (String.Equals(h264Encoder, "h264_qsv", StringComparison.OrdinalIgnoreCase) || String.Equals(h264Encoder, "libnvenc", StringComparison.OrdinalIgnoreCase))
+ if (string.Equals(videoCodec, "h264_qsv", StringComparison.OrdinalIgnoreCase) ||
+ string.Equals(videoCodec, "libnvenc", StringComparison.OrdinalIgnoreCase))
{
switch (state.VideoRequest.Level)
{
@@ -438,16 +449,21 @@ namespace MediaBrowser.Api.Playback
param += " -level " + state.VideoRequest.Level;
break;
}
-
- return param;
}
- else
+ else if (!string.Equals(videoCodec, "h264_omx", StringComparison.OrdinalIgnoreCase))
{
param += " -level " + state.VideoRequest.Level;
}
}
- return "-pix_fmt yuv420p " + param;
+ if (!string.Equals(videoCodec, "h264_omx", StringComparison.OrdinalIgnoreCase) &&
+ !string.Equals(videoCodec, "h264_qsv", StringComparison.OrdinalIgnoreCase) &&
+ !string.Equals(videoCodec, "libnvenc", StringComparison.OrdinalIgnoreCase))
+ {
+ param = "-pix_fmt yuv420p " + param;
+ }
+
+ return param;
}
protected string GetAudioFilterParam(StreamState state, bool isHls)
@@ -563,14 +579,6 @@ namespace MediaBrowser.Api.Playback
filters.Add(string.Format("scale=trunc(oh*a/2)*2:min(ih\\,{0})", maxHeightParam));
}
- if (string.Equals(outputVideoCodec, "h264_qsv", StringComparison.OrdinalIgnoreCase))
- {
- if (filters.Count > 1)
- {
- //filters[filters.Count - 1] += ":flags=fast_bilinear";
- }
- }
-
var output = string.Empty;
if (state.SubtitleStream != null && state.SubtitleStream.IsTextSubtitleStream && state.VideoRequest.SubtitleMethod == SubtitleDeliveryMethod.Encode)
@@ -846,7 +854,7 @@ namespace MediaBrowser.Api.Playback
if (MediaEncoder.SupportsDecoder("h264_qsv"))
{
// Seeing stalls and failures with decoding. Not worth it compared to encoding.
- //return "-c:v h264_qsv ";
+ return "-c:v h264_qsv ";
}
break;
case "mpeg2video":
@@ -980,11 +988,6 @@ namespace MediaBrowser.Api.Playback
var transcodingId = Guid.NewGuid().ToString("N");
var commandLineArgs = GetCommandLineArguments(outputPath, state, true);
- if (ApiEntryPoint.Instance.GetEncodingOptions().EnableDebugLogging)
- {
- commandLineArgs = "-loglevel debug " + commandLineArgs;
- }
-
var process = new Process
{
StartInfo = new ProcessStartInfo
@@ -1212,7 +1215,7 @@ namespace MediaBrowser.Api.Playback
}
}
- private int? GetVideoBitrateParamValue(VideoStreamRequest request, MediaStream videoStream)
+ private int? GetVideoBitrateParamValue(VideoStreamRequest request, MediaStream videoStream, string outputVideoCodec)
{
var bitrate = request.VideoBitRate;
@@ -1237,6 +1240,18 @@ namespace MediaBrowser.Api.Playback
}
}
+ if (bitrate.HasValue)
+ {
+ var inputVideoCodec = videoStream == null ? null : videoStream.Codec;
+ bitrate = ResolutionNormalizer.ScaleBitrate(bitrate.Value, inputVideoCodec, outputVideoCodec);
+
+ // If a max bitrate was requested, don't let the scaled bitrate exceed it
+ if (request.VideoBitRate.HasValue)
+ {
+ bitrate = Math.Min(bitrate.Value, request.VideoBitRate.Value);
+ }
+ }
+
return bitrate;
}
@@ -1519,6 +1534,13 @@ namespace MediaBrowser.Api.Playback
}
else if (i == 25)
{
+ if (videoRequest != null)
+ {
+ videoRequest.ForceLiveStream = string.Equals("true", val, StringComparison.OrdinalIgnoreCase);
+ }
+ }
+ else if (i == 26)
+ {
if (!string.IsNullOrWhiteSpace(val) && videoRequest != null)
{
SubtitleDeliveryMethod method;
@@ -1528,7 +1550,7 @@ namespace MediaBrowser.Api.Playback
}
}
}
- else if (i == 26)
+ else if (i == 27)
{
request.TranscodingMaxAudioChannels = int.Parse(val, UsCulture);
}
@@ -1636,7 +1658,8 @@ namespace MediaBrowser.Api.Playback
if (!string.IsNullOrWhiteSpace(request.AudioCodec))
{
state.SupportedAudioCodecs = request.AudioCodec.Split(',').Where(i => !string.IsNullOrWhiteSpace(i)).ToList();
- state.Request.AudioCodec = state.SupportedAudioCodecs.FirstOrDefault();
+ state.Request.AudioCodec = state.SupportedAudioCodecs.FirstOrDefault(i => MediaEncoder.CanEncodeToAudioCodec(i))
+ ?? state.SupportedAudioCodecs.FirstOrDefault();
}
var item = LibraryManager.GetItemById(request.Id);
@@ -1690,7 +1713,7 @@ namespace MediaBrowser.Api.Playback
if (videoRequest != null)
{
state.OutputVideoCodec = state.VideoRequest.VideoCodec;
- state.OutputVideoBitrate = GetVideoBitrateParamValue(state.VideoRequest, state.VideoStream);
+ state.OutputVideoBitrate = GetVideoBitrateParamValue(state.VideoRequest, state.VideoStream, state.OutputVideoCodec);
if (state.OutputVideoBitrate.HasValue)
{
diff --git a/MediaBrowser.Api/Playback/Hls/BaseHlsService.cs b/MediaBrowser.Api/Playback/Hls/BaseHlsService.cs
index 49f50735f..3d8957086 100644
--- a/MediaBrowser.Api/Playback/Hls/BaseHlsService.cs
+++ b/MediaBrowser.Api/Playback/Hls/BaseHlsService.cs
@@ -63,9 +63,9 @@ namespace MediaBrowser.Api.Playback.Hls
/// <param name="request">The request.</param>
/// <param name="isLive">if set to <c>true</c> [is live].</param>
/// <returns>System.Object.</returns>
- protected object ProcessRequest(StreamRequest request, bool isLive)
+ protected async Task<object> ProcessRequest(StreamRequest request, bool isLive)
{
- return ProcessRequestAsync(request, isLive).Result;
+ return await ProcessRequestAsync(request, isLive).ConfigureAwait(false);
}
/// <summary>
diff --git a/MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs b/MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs
index f857a43e4..780e20f87 100644
--- a/MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs
+++ b/MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs
@@ -475,7 +475,7 @@ namespace MediaBrowser.Api.Playback.Hls
ApiEntryPoint.Instance.OnTranscodeEndRequest(transcodingJob);
}
}
- });
+ }).Result;
}
private async Task<object> GetMasterPlaylistInternal(StreamRequest request, string method)
diff --git a/MediaBrowser.Api/Playback/Hls/HlsSegmentService.cs b/MediaBrowser.Api/Playback/Hls/HlsSegmentService.cs
index e9f33161e..27deaf25e 100644
--- a/MediaBrowser.Api/Playback/Hls/HlsSegmentService.cs
+++ b/MediaBrowser.Api/Playback/Hls/HlsSegmentService.cs
@@ -5,6 +5,7 @@ using ServiceStack;
using System;
using System.IO;
using System.Linq;
+using System.Threading.Tasks;
namespace MediaBrowser.Api.Playback.Hls
{
@@ -89,7 +90,7 @@ namespace MediaBrowser.Api.Playback.Hls
_config = config;
}
- public object Get(GetHlsPlaylistLegacy request)
+ public Task<object> Get(GetHlsPlaylistLegacy request)
{
var file = request.PlaylistId + Path.GetExtension(Request.PathInfo);
file = Path.Combine(_appPaths.TranscodingTempPath, file);
@@ -107,7 +108,7 @@ namespace MediaBrowser.Api.Playback.Hls
/// </summary>
/// <param name="request">The request.</param>
/// <returns>System.Object.</returns>
- public object Get(GetHlsVideoSegmentLegacy request)
+ public Task<object> Get(GetHlsVideoSegmentLegacy request)
{
var file = request.SegmentId + Path.GetExtension(Request.PathInfo);
file = Path.Combine(_config.ApplicationPaths.TranscodingTempPath, file);
@@ -131,10 +132,10 @@ namespace MediaBrowser.Api.Playback.Hls
var file = request.SegmentId + Path.GetExtension(Request.PathInfo);
file = Path.Combine(_appPaths.TranscodingTempPath, file);
- return ResultFactory.GetStaticFileResult(Request, file, FileShare.ReadWrite);
+ return ResultFactory.GetStaticFileResult(Request, file, FileShare.ReadWrite).Result;
}
- private object GetFileResult(string path, string playlistPath)
+ private Task<object> GetFileResult(string path, string playlistPath)
{
var transcodingJob = ApiEntryPoint.Instance.OnTranscodeBeginRequest(playlistPath, TranscodingJobType.Hls);
diff --git a/MediaBrowser.Api/Playback/Progressive/AudioService.cs b/MediaBrowser.Api/Playback/Progressive/AudioService.cs
index 032a0719c..e828a53c9 100644
--- a/MediaBrowser.Api/Playback/Progressive/AudioService.cs
+++ b/MediaBrowser.Api/Playback/Progressive/AudioService.cs
@@ -9,6 +9,7 @@ using MediaBrowser.Model.IO;
using MediaBrowser.Model.Serialization;
using ServiceStack;
using System.Collections.Generic;
+using System.Threading.Tasks;
using CommonIO;
namespace MediaBrowser.Api.Playback.Progressive
@@ -40,7 +41,7 @@ namespace MediaBrowser.Api.Playback.Progressive
/// </summary>
/// <param name="request">The request.</param>
/// <returns>System.Object.</returns>
- public object Get(GetAudioStream request)
+ public Task<object> Get(GetAudioStream request)
{
return ProcessRequest(request, false);
}
@@ -50,7 +51,7 @@ namespace MediaBrowser.Api.Playback.Progressive
/// </summary>
/// <param name="request">The request.</param>
/// <returns>System.Object.</returns>
- public object Head(GetAudioStream request)
+ public Task<object> Head(GetAudioStream request)
{
return ProcessRequest(request, true);
}
diff --git a/MediaBrowser.Api/Playback/Progressive/BaseProgressiveStreamingService.cs b/MediaBrowser.Api/Playback/Progressive/BaseProgressiveStreamingService.cs
index 3211f9e39..868d8d488 100644
--- a/MediaBrowser.Api/Playback/Progressive/BaseProgressiveStreamingService.cs
+++ b/MediaBrowser.Api/Playback/Progressive/BaseProgressiveStreamingService.cs
@@ -113,11 +113,11 @@ namespace MediaBrowser.Api.Playback.Progressive
/// <param name="request">The request.</param>
/// <param name="isHeadRequest">if set to <c>true</c> [is head request].</param>
/// <returns>Task.</returns>
- protected object ProcessRequest(StreamRequest request, bool isHeadRequest)
+ protected async Task<object> ProcessRequest(StreamRequest request, bool isHeadRequest)
{
var cancellationTokenSource = new CancellationTokenSource();
- var state = GetState(request, cancellationTokenSource.Token).Result;
+ var state = await GetState(request, cancellationTokenSource.Token).ConfigureAwait(false);
var responseHeaders = new Dictionary<string, string>();
@@ -128,7 +128,8 @@ namespace MediaBrowser.Api.Playback.Progressive
using (state)
{
- return GetStaticRemoteStreamResult(state, responseHeaders, isHeadRequest, cancellationTokenSource).Result;
+ return await GetStaticRemoteStreamResult(state, responseHeaders, isHeadRequest, cancellationTokenSource)
+ .ConfigureAwait(false);
}
}
@@ -138,7 +139,7 @@ namespace MediaBrowser.Api.Playback.Progressive
}
var outputPath = state.OutputFilePath;
- var outputPathExists = FileSystem.FileExists(outputPath);
+ var outputPathExists = FileSystem.FileExists(outputPath);
var isTranscodeCached = outputPathExists && !ApiEntryPoint.Instance.HasActiveTranscodingJob(outputPath, TranscodingJobType.Progressive);
@@ -151,13 +152,13 @@ namespace MediaBrowser.Api.Playback.Progressive
using (state)
{
- return ResultFactory.GetStaticFileResult(Request, new StaticFileResultOptions
+ return await ResultFactory.GetStaticFileResult(Request, new StaticFileResultOptions
{
ResponseHeaders = responseHeaders,
ContentType = contentType,
IsHeadRequest = isHeadRequest,
Path = state.MediaPath
- });
+ }).ConfigureAwait(false);
}
}
@@ -168,13 +169,13 @@ namespace MediaBrowser.Api.Playback.Progressive
try
{
- return ResultFactory.GetStaticFileResult(Request, new StaticFileResultOptions
+ return await ResultFactory.GetStaticFileResult(Request, new StaticFileResultOptions
{
ResponseHeaders = responseHeaders,
ContentType = contentType,
IsHeadRequest = isHeadRequest,
Path = outputPath
- });
+ }).ConfigureAwait(false);
}
finally
{
@@ -185,7 +186,8 @@ namespace MediaBrowser.Api.Playback.Progressive
// Need to start ffmpeg
try
{
- return GetStreamResult(state, responseHeaders, isHeadRequest, cancellationTokenSource).Result;
+ return await GetStreamResult(state, responseHeaders, isHeadRequest, cancellationTokenSource)
+ .ConfigureAwait(false);
}
catch
{
@@ -229,7 +231,7 @@ namespace MediaBrowser.Api.Playback.Progressive
if (trySupportSeek)
{
- foreach (var name in new[] {"Content-Range", "Accept-Ranges"})
+ foreach (var name in new[] { "Content-Range", "Accept-Ranges" })
{
var val = response.Headers[name];
if (!string.IsNullOrWhiteSpace(val))
@@ -242,12 +244,12 @@ namespace MediaBrowser.Api.Playback.Progressive
{
responseHeaders["Accept-Ranges"] = "none";
}
-
+
if (response.ContentLength.HasValue)
{
responseHeaders["Content-Length"] = response.ContentLength.Value.ToString(UsCulture);
}
-
+
if (isHeadRequest)
{
using (response)
@@ -324,7 +326,7 @@ namespace MediaBrowser.Api.Playback.Progressive
{
TranscodingJob job;
- if (!FileSystem.FileExists(outputPath))
+ if (!FileSystem.FileExists(outputPath))
{
job = await StartFfMpeg(state, outputPath, cancellationTokenSource).ConfigureAwait(false);
}
diff --git a/MediaBrowser.Api/Playback/Progressive/VideoService.cs b/MediaBrowser.Api/Playback/Progressive/VideoService.cs
index be3995aeb..3fd67c51e 100644
--- a/MediaBrowser.Api/Playback/Progressive/VideoService.cs
+++ b/MediaBrowser.Api/Playback/Progressive/VideoService.cs
@@ -10,6 +10,7 @@ using MediaBrowser.Model.Serialization;
using ServiceStack;
using System;
using System.IO;
+using System.Threading.Tasks;
using CommonIO;
using MediaBrowser.Model.Dlna;
@@ -76,7 +77,7 @@ namespace MediaBrowser.Api.Playback.Progressive
/// </summary>
/// <param name="request">The request.</param>
/// <returns>System.Object.</returns>
- public object Get(GetVideoStream request)
+ public Task<object> Get(GetVideoStream request)
{
return ProcessRequest(request, false);
}
@@ -86,7 +87,7 @@ namespace MediaBrowser.Api.Playback.Progressive
/// </summary>
/// <param name="request">The request.</param>
/// <returns>System.Object.</returns>
- public object Head(GetVideoStream request)
+ public Task<object> Head(GetVideoStream request)
{
return ProcessRequest(request, true);
}
diff --git a/MediaBrowser.Api/Playback/StreamState.cs b/MediaBrowser.Api/Playback/StreamState.cs
index 061afed6d..fbcccbaca 100644
--- a/MediaBrowser.Api/Playback/StreamState.cs
+++ b/MediaBrowser.Api/Playback/StreamState.cs
@@ -96,7 +96,7 @@ namespace MediaBrowser.Api.Playback
public long? InputFileSize { get; set; }
public string OutputAudioSync = "1";
- public string OutputVideoSync = "vfr";
+ public string OutputVideoSync = "-1";
public List<string> SupportedAudioCodecs { get; set; }
diff --git a/MediaBrowser.Api/PlaylistService.cs b/MediaBrowser.Api/PlaylistService.cs
index 3dafd0eeb..604227a15 100644
--- a/MediaBrowser.Api/PlaylistService.cs
+++ b/MediaBrowser.Api/PlaylistService.cs
@@ -157,7 +157,7 @@ namespace MediaBrowser.Api
Task.WaitAll(task);
}
- public object Get(GetPlaylistItems request)
+ public async Task<object> Get(GetPlaylistItems request)
{
var playlist = (Playlist)_libraryManager.GetItemById(request.Id);
var user = !string.IsNullOrWhiteSpace(request.UserId) ? _userManager.GetUserById(request.UserId) : null;
@@ -178,7 +178,7 @@ namespace MediaBrowser.Api
var dtoOptions = GetDtoOptions(request);
- var dtos = _dtoService.GetBaseItemDtos(items.Select(i => i.Item2), dtoOptions, user)
+ var dtos = (await _dtoService.GetBaseItemDtos(items.Select(i => i.Item2), dtoOptions, user).ConfigureAwait(false))
.ToArray();
var index = 0;
diff --git a/MediaBrowser.Api/Reports/ReportsService.cs b/MediaBrowser.Api/Reports/ReportsService.cs
index cb1615826..36a2a4b61 100644
--- a/MediaBrowser.Api/Reports/ReportsService.cs
+++ b/MediaBrowser.Api/Reports/ReportsService.cs
@@ -213,7 +213,6 @@ namespace MediaBrowser.Api.Reports
NameStartsWith = request.NameStartsWith,
NameStartsWithOrGreater = request.NameStartsWithOrGreater,
HasImdbId = request.HasImdbId,
- IsYearMismatched = request.IsYearMismatched,
IsPlaceHolder = request.IsPlaceHolder,
IsLocked = request.IsLocked,
IsInBoxSet = request.IsInBoxSet,
diff --git a/MediaBrowser.Api/SimilarItemsHelper.cs b/MediaBrowser.Api/SimilarItemsHelper.cs
index 277bba1dd..c98a91a55 100644
--- a/MediaBrowser.Api/SimilarItemsHelper.cs
+++ b/MediaBrowser.Api/SimilarItemsHelper.cs
@@ -9,6 +9,8 @@ using ServiceStack;
using System;
using System.Collections.Generic;
using System.Linq;
+using System.Threading.Tasks;
+using MediaBrowser.Model.Dto;
namespace MediaBrowser.Api
{
@@ -54,7 +56,7 @@ namespace MediaBrowser.Api
/// </summary>
public static class SimilarItemsHelper
{
- internal static ItemsResult GetSimilarItemsResult(DtoOptions dtoOptions, IUserManager userManager, IItemRepository itemRepository, ILibraryManager libraryManager, IUserDataManager userDataRepository, IDtoService dtoService, ILogger logger, BaseGetSimilarItemsFromItem request, Type[] includeTypes, Func<BaseItem, List<PersonInfo>, List<PersonInfo>, BaseItem, int> getSimilarityScore)
+ internal static async Task<QueryResult<BaseItemDto>> GetSimilarItemsResult(DtoOptions dtoOptions, IUserManager userManager, IItemRepository itemRepository, ILibraryManager libraryManager, IUserDataManager userDataRepository, IDtoService dtoService, ILogger logger, BaseGetSimilarItemsFromItem request, Type[] includeTypes, Func<BaseItem, List<PersonInfo>, List<PersonInfo>, BaseItem, int> getSimilarityScore)
{
var user = !string.IsNullOrWhiteSpace(request.UserId) ? userManager.GetUserById(request.UserId) : null;
@@ -80,14 +82,14 @@ namespace MediaBrowser.Api
returnItems = returnItems.Take(request.Limit.Value);
}
- var result = new ItemsResult
+ var dtos = await dtoService.GetBaseItemDtos(returnItems, dtoOptions, user).ConfigureAwait(false);
+
+ return new QueryResult<BaseItemDto>
{
- Items = dtoService.GetBaseItemDtos(returnItems, dtoOptions, user).ToArray(),
+ Items = dtos.ToArray(),
TotalRecordCount = items.Count
};
-
- return result;
}
/// <summary>
@@ -116,24 +118,12 @@ namespace MediaBrowser.Api
private static IEnumerable<string> GetTags(BaseItem item)
{
- var hasTags = item as IHasTags;
- if (hasTags != null)
- {
- return hasTags.Tags;
- }
-
- return new List<string>();
+ return item.Tags;
}
private static IEnumerable<string> GetKeywords(BaseItem item)
{
- var hasTags = item as IHasKeywords;
- if (hasTags != null)
- {
- return hasTags.Keywords;
- }
-
- return new List<string>();
+ return item.Keywords;
}
/// <summary>
diff --git a/MediaBrowser.Api/StartupWizardService.cs b/MediaBrowser.Api/StartupWizardService.cs
index ca934830d..38a7337ec 100644
--- a/MediaBrowser.Api/StartupWizardService.cs
+++ b/MediaBrowser.Api/StartupWizardService.cs
@@ -11,6 +11,7 @@ using ServiceStack;
using System;
using System.Linq;
using System.Threading.Tasks;
+using MediaBrowser.Controller.MediaEncoding;
namespace MediaBrowser.Api
{
@@ -52,14 +53,16 @@ namespace MediaBrowser.Api
private readonly IUserManager _userManager;
private readonly IConnectManager _connectManager;
private readonly ILiveTvManager _liveTvManager;
+ private readonly IMediaEncoder _mediaEncoder;
- public StartupWizardService(IServerConfigurationManager config, IServerApplicationHost appHost, IUserManager userManager, IConnectManager connectManager, ILiveTvManager liveTvManager)
+ public StartupWizardService(IServerConfigurationManager config, IServerApplicationHost appHost, IUserManager userManager, IConnectManager connectManager, ILiveTvManager liveTvManager, IMediaEncoder mediaEncoder)
{
_config = config;
_appHost = appHost;
_userManager = userManager;
_connectManager = connectManager;
_liveTvManager = liveTvManager;
+ _mediaEncoder = mediaEncoder;
}
public void Post(ReportStartupWizardComplete request)
@@ -69,13 +72,14 @@ namespace MediaBrowser.Api
_config.SaveConfiguration();
}
- public object Get(GetStartupInfo request)
+ public async Task<object> Get(GetStartupInfo request)
{
- var info = _appHost.GetSystemInfo();
+ var info = await _appHost.GetSystemInfo().ConfigureAwait(false);
return new StartupInfo
{
- SupportsRunningAsService = info.SupportsRunningAsService
+ SupportsRunningAsService = info.SupportsRunningAsService,
+ HasMediaEncoder = !string.IsNullOrWhiteSpace(_mediaEncoder.EncoderPath)
};
}
@@ -111,10 +115,10 @@ namespace MediaBrowser.Api
{
config.EnableLocalizedGuids = true;
config.EnableCustomPathSubFolders = true;
- config.EnableDateLastRefresh = true;
config.EnableStandaloneMusicKeys = true;
config.EnableCaseSensitiveItemIds = true;
- config.SchemaVersion = 79;
+ config.EnableFolderView = true;
+ config.SchemaVersion = 97;
}
public void Post(UpdateStartupConfiguration request)
@@ -231,6 +235,7 @@ namespace MediaBrowser.Api
public class StartupInfo
{
public bool SupportsRunningAsService { get; set; }
+ public bool HasMediaEncoder { get; set; }
}
public class StartupUser
diff --git a/MediaBrowser.Api/Subtitles/SubtitleService.cs b/MediaBrowser.Api/Subtitles/SubtitleService.cs
index c2183ad7b..fe13e8b21 100644
--- a/MediaBrowser.Api/Subtitles/SubtitleService.cs
+++ b/MediaBrowser.Api/Subtitles/SubtitleService.cs
@@ -98,6 +98,10 @@ namespace MediaBrowser.Api.Subtitles
[ApiMember(Name = "EndPositionTicks", Description = "EndPositionTicks", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
public long? EndPositionTicks { get; set; }
+
+ [ApiMember(Name = "CopyTimestamps", Description = "CopyTimestamps", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")]
+ public bool CopyTimestamps { get; set; }
+ public bool AddVttTimeMap { get; set; }
}
[Route("/Videos/{Id}/{MediaSourceId}/Subtitles/{Index}/subtitles.m3u8", "GET", Summary = "Gets an HLS subtitle playlist.")]
@@ -175,7 +179,7 @@ namespace MediaBrowser.Api.Subtitles
var endPositionTicks = Math.Min(runtime, positionTicks + segmentLengthTicks);
- var url = string.Format("stream.vtt?StartPositionTicks={0}&EndPositionTicks={1}&api_key={2}",
+ var url = string.Format("stream.vtt?CopyTimestamps=true&AddVttTimeMap=true&StartPositionTicks={0}&EndPositionTicks={1}&api_key={2}",
positionTicks.ToString(CultureInfo.InvariantCulture),
endPositionTicks.ToString(CultureInfo.InvariantCulture),
accessToken);
@@ -190,7 +194,7 @@ namespace MediaBrowser.Api.Subtitles
return ResultFactory.GetResult(builder.ToString(), MimeTypes.GetMimeType("playlist.m3u8"), new Dictionary<string, string>());
}
- public object Get(GetSubtitle request)
+ public async Task<object> Get(GetSubtitle request)
{
if (string.Equals(request.Format, "js", StringComparison.OrdinalIgnoreCase))
{
@@ -206,23 +210,35 @@ namespace MediaBrowser.Api.Subtitles
var subtitleStream = mediaSource.MediaStreams
.First(i => i.Type == MediaStreamType.Subtitle && i.Index == request.Index);
- return ToStaticFileResult(subtitleStream.Path);
+ return await ResultFactory.GetStaticFileResult(Request, subtitleStream.Path).ConfigureAwait(false);
}
- var stream = GetSubtitles(request).Result;
+ using (var stream = await GetSubtitles(request).ConfigureAwait(false))
+ {
+ using (var reader = new StreamReader(stream))
+ {
+ var text = reader.ReadToEnd();
+
+ if (string.Equals(request.Format, "vtt", StringComparison.OrdinalIgnoreCase) && request.AddVttTimeMap)
+ {
+ text = text.Replace("WEBVTT", "WEBVTT\nX-TIMESTAMP-MAP=MPEGTS:900000,LOCAL:00:00:00.000");
+ }
- return ResultFactory.GetResult(stream, MimeTypes.GetMimeType("file." + request.Format));
+ return ResultFactory.GetResult(text, MimeTypes.GetMimeType("file." + request.Format));
+ }
+ }
}
- private async Task<Stream> GetSubtitles(GetSubtitle request)
+ private Task<Stream> GetSubtitles(GetSubtitle request)
{
- return await _subtitleEncoder.GetSubtitles(request.Id,
+ return _subtitleEncoder.GetSubtitles(request.Id,
request.MediaSourceId,
request.Index,
request.Format,
request.StartPositionTicks,
request.EndPositionTicks,
- CancellationToken.None).ConfigureAwait(false);
+ request.CopyTimestamps,
+ CancellationToken.None);
}
public object Get(SearchRemoteSubtitles request)
@@ -248,9 +264,9 @@ namespace MediaBrowser.Api.Subtitles
return ToOptimizedResult(result);
}
- public object Get(GetRemoteSubtitles request)
+ public async Task<object> Get(GetRemoteSubtitles request)
{
- var result = _subtitleManager.GetRemoteSubtitles(request.Id, CancellationToken.None).Result;
+ var result = await _subtitleManager.GetRemoteSubtitles(request.Id, CancellationToken.None).ConfigureAwait(false);
return ResultFactory.GetResult(result.Stream, MimeTypes.GetMimeType("file." + result.Format));
}
diff --git a/MediaBrowser.Api/Sync/SyncService.cs b/MediaBrowser.Api/Sync/SyncService.cs
index 593c3a108..a15ce216f 100644
--- a/MediaBrowser.Api/Sync/SyncService.cs
+++ b/MediaBrowser.Api/Sync/SyncService.cs
@@ -227,7 +227,7 @@ namespace MediaBrowser.Api.Sync
Task.WaitAll(task);
}
- public object Get(GetSyncJobItemFile request)
+ public async Task<object> Get(GetSyncJobItemFile request)
{
var jobItem = _syncManager.GetJobItem(request.Id);
@@ -241,10 +241,9 @@ namespace MediaBrowser.Api.Sync
throw new ArgumentException("The job item is not yet ready for transfer.");
}
- var task = _syncManager.ReportSyncJobItemTransferBeginning(request.Id);
- Task.WaitAll(task);
+ await _syncManager.ReportSyncJobItemTransferBeginning(request.Id).ConfigureAwait(false);
- return ResultFactory.GetStaticFileResult(Request, new StaticFileResultOptions
+ return await ResultFactory.GetStaticFileResult(Request, new StaticFileResultOptions
{
Path = jobItem.OutputPath,
OnError = () =>
@@ -252,10 +251,11 @@ namespace MediaBrowser.Api.Sync
var failedTask = _syncManager.ReportSyncJobItemTransferFailed(request.Id);
Task.WaitAll(failedTask);
}
- });
+
+ }).ConfigureAwait(false);
}
- public object Get(GetSyncDialogOptions request)
+ public async Task<object> Get(GetSyncDialogOptions request)
{
var result = new SyncDialogOptions();
@@ -298,8 +298,7 @@ namespace MediaBrowser.Api.Sync
.Select(_libraryManager.GetItemById)
.Where(i => i != null);
- var dtos = _dtoService.GetBaseItemDtos(items, dtoOptions, authenticatedUser)
- .ToList();
+ var dtos = (await _dtoService.GetBaseItemDtos(items, dtoOptions, authenticatedUser).ConfigureAwait(false));
result.Options = SyncHelper.GetSyncOptions(dtos);
}
@@ -343,7 +342,7 @@ namespace MediaBrowser.Api.Sync
Task.WaitAll(task);
}
- public object Get(GetSyncJobItemAdditionalFile request)
+ public Task<object> Get(GetSyncJobItemAdditionalFile request)
{
var jobItem = _syncManager.GetJobItem(request.Id);
@@ -359,7 +358,7 @@ namespace MediaBrowser.Api.Sync
throw new ArgumentException("Sync job additional file not found.");
}
- return ToStaticFileResult(file.Path);
+ return ResultFactory.GetStaticFileResult(Request, file.Path);
}
public void Post(EnableSyncJobItem request)
diff --git a/MediaBrowser.Api/System/SystemInfoWebSocketListener.cs b/MediaBrowser.Api/System/SystemInfoWebSocketListener.cs
index 9ab7770ed..a53bfac27 100644
--- a/MediaBrowser.Api/System/SystemInfoWebSocketListener.cs
+++ b/MediaBrowser.Api/System/SystemInfoWebSocketListener.cs
@@ -43,7 +43,7 @@ namespace MediaBrowser.Api.System
/// <returns>Task{SystemInfo}.</returns>
protected override Task<SystemInfo> GetDataToSend(WebSocketListenerState state)
{
- return Task.FromResult(_appHost.GetSystemInfo());
+ return _appHost.GetSystemInfo();
}
}
}
diff --git a/MediaBrowser.Api/System/SystemService.cs b/MediaBrowser.Api/System/SystemService.cs
index b4b41c844..c2318dccb 100644
--- a/MediaBrowser.Api/System/SystemService.cs
+++ b/MediaBrowser.Api/System/SystemService.cs
@@ -19,7 +19,7 @@ namespace MediaBrowser.Api.System
/// Class GetSystemInfo
/// </summary>
[Route("/System/Info", "GET", Summary = "Gets information about the server")]
- [Authenticated(EscapeParentalControl = true)]
+ [Authenticated(EscapeParentalControl = true, AllowBeforeStartupWizard = true)]
public class GetSystemInfo : IReturn<SystemInfo>
{
@@ -120,7 +120,7 @@ namespace MediaBrowser.Api.System
try
{
- files = _fileSystem.GetFiles(_appPaths.LogDirectoryPath)
+ files = _fileSystem.GetFiles(_appPaths.LogDirectoryPath)
.Where(i => string.Equals(i.Extension, ".txt", StringComparison.OrdinalIgnoreCase))
.ToList();
}
@@ -144,9 +144,9 @@ namespace MediaBrowser.Api.System
return ToOptimizedResult(result);
}
- public object Get(GetLogFile request)
+ public Task<object> Get(GetLogFile request)
{
- var file = _fileSystem.GetFiles(_appPaths.LogDirectoryPath)
+ var file = _fileSystem.GetFiles(_appPaths.LogDirectoryPath)
.First(i => string.Equals(i.Name, request.Name, StringComparison.OrdinalIgnoreCase));
return ResultFactory.GetStaticFileResult(Request, file.FullName, FileShare.ReadWrite);
@@ -157,16 +157,16 @@ namespace MediaBrowser.Api.System
/// </summary>
/// <param name="request">The request.</param>
/// <returns>System.Object.</returns>
- public object Get(GetSystemInfo request)
+ public async Task<object> Get(GetSystemInfo request)
{
- var result = _appHost.GetSystemInfo();
+ var result = await _appHost.GetSystemInfo().ConfigureAwait(false);
return ToOptimizedResult(result);
}
- public object Get(GetPublicSystemInfo request)
+ public async Task<object> Get(GetPublicSystemInfo request)
{
- var result = _appHost.GetSystemInfo();
+ var result = await _appHost.GetSystemInfo().ConfigureAwait(false);
var publicInfo = new PublicSystemInfo
{
diff --git a/MediaBrowser.Api/TvShowsService.cs b/MediaBrowser.Api/TvShowsService.cs
index aa0485d57..3f248ea8f 100644
--- a/MediaBrowser.Api/TvShowsService.cs
+++ b/MediaBrowser.Api/TvShowsService.cs
@@ -12,6 +12,8 @@ using ServiceStack;
using System;
using System.Collections.Generic;
using System.Linq;
+using System.Threading.Tasks;
+using MediaBrowser.Model.Dto;
namespace MediaBrowser.Api
{
@@ -271,29 +273,51 @@ namespace MediaBrowser.Api
/// </summary>
/// <param name="request">The request.</param>
/// <returns>System.Object.</returns>
- public object Get(GetSimilarShows request)
+ public async Task<object> Get(GetSimilarShows request)
{
+ var result = await GetSimilarItemsResult(request).ConfigureAwait(false);
+
+ return ToOptimizedSerializedResultUsingCache(result);
+ }
+
+ private async Task<QueryResult<BaseItemDto>> GetSimilarItemsResult(BaseGetSimilarItemsFromItem request)
+ {
+ var user = !string.IsNullOrWhiteSpace(request.UserId) ? _userManager.GetUserById(request.UserId) : null;
+
+ var item = string.IsNullOrEmpty(request.Id) ?
+ (!string.IsNullOrWhiteSpace(request.UserId) ? user.RootFolder :
+ _libraryManager.RootFolder) : _libraryManager.GetItemById(request.Id);
+
+ var itemsResult = _libraryManager.GetItemList(new InternalItemsQuery(user)
+ {
+ Limit = request.Limit,
+ IncludeItemTypes = new[]
+ {
+ typeof(Series).Name
+ },
+ SimilarTo = item
+
+ }).ToList();
+
var dtoOptions = GetDtoOptions(request);
- var result = SimilarItemsHelper.GetSimilarItemsResult(dtoOptions, _userManager,
- _itemRepo,
- _libraryManager,
- _userDataManager,
- _dtoService,
- Logger,
- request, new[] { typeof(Series) },
- SimilarItemsHelper.GetSimiliarityScore);
+ var result = new QueryResult<BaseItemDto>
+ {
+ Items = (await _dtoService.GetBaseItemDtos(itemsResult, dtoOptions, user).ConfigureAwait(false)).ToArray(),
- return ToOptimizedSerializedResultUsingCache(result);
+ TotalRecordCount = itemsResult.Count
+ };
+
+ return result;
}
- public object Get(GetUpcomingEpisodes request)
+ public async Task<object> Get(GetUpcomingEpisodes request)
{
var user = _userManager.GetUserById(request.UserId);
var minPremiereDate = DateTime.Now.Date.ToUniversalTime().AddDays(-1);
- var parentIds = string.IsNullOrWhiteSpace(request.ParentId) ? new string[] { } : new[] { request.ParentId };
+ var parentIdGuid = string.IsNullOrWhiteSpace(request.ParentId) ? (Guid?)null : new Guid(request.ParentId);
var itemsResult = _libraryManager.GetItemList(new InternalItemsQuery(user)
{
@@ -302,13 +326,15 @@ namespace MediaBrowser.Api
SortOrder = SortOrder.Ascending,
MinPremiereDate = minPremiereDate,
StartIndex = request.StartIndex,
- Limit = request.Limit
+ Limit = request.Limit,
+ ParentId = parentIdGuid,
+ Recursive = true
- }, parentIds).ToList();
+ }).ToList();
var options = GetDtoOptions(request);
- var returnItems = _dtoService.GetBaseItemDtos(itemsResult, options, user).ToArray();
+ var returnItems = (await _dtoService.GetBaseItemDtos(itemsResult, options, user).ConfigureAwait(false)).ToArray();
var result = new ItemsResult
{
@@ -324,7 +350,7 @@ namespace MediaBrowser.Api
/// </summary>
/// <param name="request">The request.</param>
/// <returns>System.Object.</returns>
- public object Get(GetNextUpEpisodes request)
+ public async Task<object> Get(GetNextUpEpisodes request)
{
var result = _tvSeriesManager.GetNextUp(new NextUpQuery
{
@@ -339,7 +365,7 @@ namespace MediaBrowser.Api
var options = GetDtoOptions(request);
- var returnItems = _dtoService.GetBaseItemDtos(result.Items, options, user).ToArray();
+ var returnItems = (await _dtoService.GetBaseItemDtos(result.Items, options, user).ConfigureAwait(false)).ToArray();
return ToOptimizedSerializedResultUsingCache(new ItemsResult
{
@@ -372,7 +398,7 @@ namespace MediaBrowser.Api
return items;
}
- public object Get(GetSeasons request)
+ public async Task<object> Get(GetSeasons request)
{
var user = _userManager.GetUserById(request.UserId);
@@ -403,7 +429,7 @@ namespace MediaBrowser.Api
var dtoOptions = GetDtoOptions(request);
- var returnItems = _dtoService.GetBaseItemDtos(seasons, dtoOptions, user)
+ var returnItems = (await _dtoService.GetBaseItemDtos(seasons, dtoOptions, user).ConfigureAwait(false))
.ToArray();
return new ItemsResult
@@ -430,7 +456,7 @@ namespace MediaBrowser.Api
return items;
}
- public object Get(GetEpisodes request)
+ public async Task<object> Get(GetEpisodes request)
{
var user = _userManager.GetUserById(request.UserId);
@@ -512,7 +538,7 @@ namespace MediaBrowser.Api
var dtoOptions = GetDtoOptions(request);
- var dtos = _dtoService.GetBaseItemDtos(pagedItems, dtoOptions, user)
+ var dtos = (await _dtoService.GetBaseItemDtos(pagedItems, dtoOptions, user).ConfigureAwait(false))
.ToArray();
return new ItemsResult
diff --git a/MediaBrowser.Api/UserLibrary/ArtistsService.cs b/MediaBrowser.Api/UserLibrary/ArtistsService.cs
index cde5eade5..df73ef720 100644
--- a/MediaBrowser.Api/UserLibrary/ArtistsService.cs
+++ b/MediaBrowser.Api/UserLibrary/ArtistsService.cs
@@ -1,4 +1,5 @@
-using MediaBrowser.Controller.Dto;
+using System;
+using MediaBrowser.Controller.Dto;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.Audio;
using MediaBrowser.Controller.Library;
@@ -8,6 +9,8 @@ using MediaBrowser.Model.Dto;
using ServiceStack;
using System.Collections.Generic;
using System.Linq;
+using MediaBrowser.Model.Entities;
+using MediaBrowser.Model.Querying;
namespace MediaBrowser.Api.UserLibrary
{
@@ -100,7 +103,12 @@ namespace MediaBrowser.Api.UserLibrary
/// <returns>System.Object.</returns>
public object Get(GetArtists request)
{
- var result = GetResult(request);
+ if (string.IsNullOrWhiteSpace(request.IncludeItemTypes))
+ {
+ //request.IncludeItemTypes = "Audio,MusicVideo";
+ }
+
+ var result = GetResultSlim(request);
return ToOptimizedResult(result);
}
@@ -112,11 +120,26 @@ namespace MediaBrowser.Api.UserLibrary
/// <returns>System.Object.</returns>
public object Get(GetAlbumArtists request)
{
- var result = GetResult(request);
+ if (string.IsNullOrWhiteSpace(request.IncludeItemTypes))
+ {
+ //request.IncludeItemTypes = "Audio,MusicVideo";
+ }
+
+ var result = GetResultSlim(request);
return ToOptimizedResult(result);
}
+ protected override QueryResult<Tuple<BaseItem, ItemCounts>> GetItems(GetItemsByName request, InternalItemsQuery query)
+ {
+ if (request is GetAlbumArtists)
+ {
+ return LibraryManager.GetAlbumArtists(query);
+ }
+
+ return LibraryManager.GetArtists(query);
+ }
+
/// <summary>
/// Gets all items.
/// </summary>
@@ -125,16 +148,7 @@ namespace MediaBrowser.Api.UserLibrary
/// <returns>IEnumerable{Tuple{System.StringFunc{System.Int32}}}.</returns>
protected override IEnumerable<BaseItem> GetAllItems(GetItemsByName request, IEnumerable<BaseItem> items)
{
- if (request is GetAlbumArtists)
- {
- return LibraryManager.GetAlbumArtists(items
- .Where(i => !i.IsFolder)
- .OfType<IHasAlbumArtist>());
- }
-
- return LibraryManager.GetArtists(items
- .Where(i => !i.IsFolder)
- .OfType<IHasArtist>());
+ throw new NotImplementedException();
}
}
}
diff --git a/MediaBrowser.Api/UserLibrary/BaseItemsByNameService.cs b/MediaBrowser.Api/UserLibrary/BaseItemsByNameService.cs
index 565bed053..9465d1fdc 100644
--- a/MediaBrowser.Api/UserLibrary/BaseItemsByNameService.cs
+++ b/MediaBrowser.Api/UserLibrary/BaseItemsByNameService.cs
@@ -8,6 +8,7 @@ using ServiceStack;
using System;
using System.Collections.Generic;
using System.Linq;
+using MediaBrowser.Model.Dto;
namespace MediaBrowser.Api.UserLibrary
{
@@ -83,6 +84,137 @@ namespace MediaBrowser.Api.UserLibrary
return null;
}
+ protected ItemsResult GetResultSlim(GetItemsByName request)
+ {
+ var dtoOptions = GetDtoOptions(request);
+
+ User user = null;
+ BaseItem parentItem;
+
+ if (!string.IsNullOrWhiteSpace(request.UserId))
+ {
+ user = UserManager.GetUserById(request.UserId);
+ parentItem = string.IsNullOrEmpty(request.ParentId) ? user.RootFolder : LibraryManager.GetItemById(request.ParentId);
+ }
+ else
+ {
+ parentItem = string.IsNullOrEmpty(request.ParentId) ? LibraryManager.RootFolder : LibraryManager.GetItemById(request.ParentId);
+ }
+
+ var excludeItemTypes = request.GetExcludeItemTypes();
+ var includeItemTypes = request.GetIncludeItemTypes();
+ var mediaTypes = request.GetMediaTypes();
+
+ var query = new InternalItemsQuery(user)
+ {
+ ExcludeItemTypes = excludeItemTypes,
+ IncludeItemTypes = includeItemTypes,
+ MediaTypes = mediaTypes,
+ StartIndex = request.StartIndex,
+ Limit = request.Limit,
+ IsFavorite = request.IsFavorite,
+ NameLessThan = request.NameLessThan,
+ NameStartsWith = request.NameStartsWith,
+ NameStartsWithOrGreater = request.NameStartsWithOrGreater,
+ AlbumArtistStartsWithOrGreater = request.AlbumArtistStartsWithOrGreater,
+ Tags = request.GetTags(),
+ OfficialRatings = request.GetOfficialRatings(),
+ Genres = request.GetGenres(),
+ GenreIds = request.GetGenreIds(),
+ Studios = request.GetStudios(),
+ StudioIds = request.GetStudioIds(),
+ Person = request.Person,
+ PersonIds = request.GetPersonIds(),
+ PersonTypes = request.GetPersonTypes(),
+ Years = request.GetYears(),
+ MinCommunityRating = request.MinCommunityRating
+ };
+
+ if (!string.IsNullOrWhiteSpace(request.ParentId))
+ {
+ if (parentItem is Folder)
+ {
+ query.AncestorIds = new[] { request.ParentId };
+ }
+ else
+ {
+ query.ItemIds = new[] { request.ParentId };
+ }
+ }
+
+ foreach (var filter in request.GetFilters())
+ {
+ switch (filter)
+ {
+ case ItemFilter.Dislikes:
+ query.IsLiked = false;
+ break;
+ case ItemFilter.IsFavorite:
+ query.IsFavorite = true;
+ break;
+ case ItemFilter.IsFavoriteOrLikes:
+ query.IsFavoriteOrLiked = true;
+ break;
+ case ItemFilter.IsFolder:
+ query.IsFolder = true;
+ break;
+ case ItemFilter.IsNotFolder:
+ query.IsFolder = false;
+ break;
+ case ItemFilter.IsPlayed:
+ query.IsPlayed = true;
+ break;
+ case ItemFilter.IsRecentlyAdded:
+ break;
+ case ItemFilter.IsResumable:
+ query.IsResumable = true;
+ break;
+ case ItemFilter.IsUnplayed:
+ query.IsPlayed = false;
+ break;
+ case ItemFilter.Likes:
+ query.IsLiked = true;
+ break;
+ }
+ }
+
+ var result = GetItems(request, query);
+
+ var dtos = result.Items.Select(i =>
+ {
+ var dto = DtoService.GetItemByNameDto(i.Item1, dtoOptions, null, user);
+
+ if (!string.IsNullOrWhiteSpace(request.IncludeItemTypes))
+ {
+ SetItemCounts(dto, i.Item2);
+ }
+ return dto;
+ });
+
+ return new ItemsResult
+ {
+ Items = dtos.ToArray(),
+ TotalRecordCount = result.TotalRecordCount
+ };
+ }
+
+ protected virtual QueryResult<Tuple<BaseItem, ItemCounts>> GetItems(GetItemsByName request, InternalItemsQuery query)
+ {
+ return new QueryResult<Tuple<BaseItem, ItemCounts>>();
+ }
+
+ private void SetItemCounts(BaseItemDto dto, ItemCounts counts)
+ {
+ dto.ChildCount = counts.ItemCount;
+ dto.SeriesCount = counts.SeriesCount;
+ dto.EpisodeCount = counts.EpisodeCount;
+ dto.MovieCount = counts.MovieCount;
+ dto.TrailerCount = counts.TrailerCount;
+ dto.AlbumCount = counts.AlbumCount;
+ dto.SongCount = counts.SongCount;
+ dto.GameCount = counts.GameCount;
+ }
+
/// <summary>
/// Gets the specified request.
/// </summary>
@@ -333,12 +465,7 @@ namespace MediaBrowser.Api.UserLibrary
var tags = request.GetTags();
if (tags.Length > 0)
{
- var hasTags = i as IHasTags;
- if (hasTags == null)
- {
- return false;
- }
- if (!tags.Any(v => hasTags.Tags.Contains(v, StringComparer.OrdinalIgnoreCase)))
+ if (!tags.Any(v => i.Tags.Contains(v, StringComparer.OrdinalIgnoreCase)))
{
return false;
}
@@ -379,7 +506,7 @@ namespace MediaBrowser.Api.UserLibrary
/// <param name="includeItemTypes">The include item types.</param>
/// <param name="mediaTypes">The media types.</param>
/// <returns>IEnumerable{BaseItem}.</returns>
- protected bool FilterItem(GetItemsByName request, BaseItem f, string[] excludeItemTypes, string[] includeItemTypes, string[] mediaTypes)
+ private bool FilterItem(GetItemsByName request, BaseItem f, string[] excludeItemTypes, string[] includeItemTypes, string[] mediaTypes)
{
// Exclude item types
if (excludeItemTypes.Length > 0)
diff --git a/MediaBrowser.Api/UserLibrary/BaseItemsRequest.cs b/MediaBrowser.Api/UserLibrary/BaseItemsRequest.cs
index aee1a8d54..d27a560ba 100644
--- a/MediaBrowser.Api/UserLibrary/BaseItemsRequest.cs
+++ b/MediaBrowser.Api/UserLibrary/BaseItemsRequest.cs
@@ -100,9 +100,6 @@ namespace MediaBrowser.Api.UserLibrary
[ApiMember(Name = "HasTvdbId", Description = "Optional filter by items that have a tvdb id or not.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")]
public bool? HasTvdbId { get; set; }
- [ApiMember(Name = "IsYearMismatched", Description = "Optional filter by items that are potentially misidentified.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")]
- public bool? IsYearMismatched { get; set; }
-
[ApiMember(Name = "IsInBoxSet", Description = "Optional filter by items that are in boxsets, or not.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")]
public bool? IsInBoxSet { get; set; }
diff --git a/MediaBrowser.Api/UserLibrary/GameGenresService.cs b/MediaBrowser.Api/UserLibrary/GameGenresService.cs
index 58237f80f..a0883f98c 100644
--- a/MediaBrowser.Api/UserLibrary/GameGenresService.cs
+++ b/MediaBrowser.Api/UserLibrary/GameGenresService.cs
@@ -9,16 +9,13 @@ using ServiceStack;
using System;
using System.Collections.Generic;
using System.Linq;
+using MediaBrowser.Model.Querying;
namespace MediaBrowser.Api.UserLibrary
{
[Route("/GameGenres", "GET", Summary = "Gets all Game genres from a given item, folder, or the entire library")]
public class GetGameGenres : GetItemsByName
{
- public GetGameGenres()
- {
- MediaTypes = MediaType.Game;
- }
}
[Route("/GameGenres/{Name}", "GET", Summary = "Gets a Game genre, by name")]
@@ -87,11 +84,16 @@ namespace MediaBrowser.Api.UserLibrary
/// <returns>System.Object.</returns>
public object Get(GetGameGenres request)
{
- var result = GetResult(request);
+ var result = GetResultSlim(request);
return ToOptimizedSerializedResultUsingCache(result);
}
+ protected override QueryResult<Tuple<BaseItem, ItemCounts>> GetItems(GetItemsByName request, InternalItemsQuery query)
+ {
+ return LibraryManager.GetGameGenres(query);
+ }
+
/// <summary>
/// Gets all items.
/// </summary>
@@ -100,22 +102,7 @@ namespace MediaBrowser.Api.UserLibrary
/// <returns>IEnumerable{Tuple{System.StringFunc{System.Int32}}}.</returns>
protected override IEnumerable<BaseItem> GetAllItems(GetItemsByName request, IEnumerable<BaseItem> items)
{
- return items
- .SelectMany(i => i.Genres)
- .DistinctNames()
- .Select(name =>
- {
- try
- {
- return LibraryManager.GetGameGenre(name);
- }
- catch (Exception ex)
- {
- Logger.ErrorException("Error getting genre {0}", ex, name);
- return null;
- }
- })
- .Where(i => i != null);
+ throw new NotImplementedException();
}
}
}
diff --git a/MediaBrowser.Api/UserLibrary/GenresService.cs b/MediaBrowser.Api/UserLibrary/GenresService.cs
index d383bd0ad..57c11a1fa 100644
--- a/MediaBrowser.Api/UserLibrary/GenresService.cs
+++ b/MediaBrowser.Api/UserLibrary/GenresService.cs
@@ -9,6 +9,7 @@ using ServiceStack;
using System;
using System.Collections.Generic;
using System.Linq;
+using MediaBrowser.Model.Querying;
namespace MediaBrowser.Api.UserLibrary
{
@@ -92,65 +93,37 @@ namespace MediaBrowser.Api.UserLibrary
/// <returns>System.Object.</returns>
public object Get(GetGenres request)
{
- var result = GetResult(request);
+ var result = GetResultSlim(request);
return ToOptimizedSerializedResultUsingCache(result);
}
- /// <summary>
- /// Gets all items.
- /// </summary>
- /// <param name="request">The request.</param>
- /// <param name="items">The items.</param>
- /// <returns>IEnumerable{Tuple{System.StringFunc{System.Int32}}}.</returns>
- protected override IEnumerable<BaseItem> GetAllItems(GetItemsByName request, IEnumerable<BaseItem> items)
+ protected override QueryResult<Tuple<BaseItem, ItemCounts>> GetItems(GetItemsByName request, InternalItemsQuery query)
{
var viewType = GetParentItemViewType(request);
if (string.Equals(viewType, CollectionType.Music) || string.Equals(viewType, CollectionType.MusicVideos))
{
- return items
- .SelectMany(i => i.Genres)
- .DistinctNames()
- .Select(name => LibraryManager.GetMusicGenre(name));
+ return LibraryManager.GetMusicGenres(query);
}
if (string.Equals(viewType, CollectionType.Games))
{
- return items
- .SelectMany(i => i.Genres)
- .DistinctNames()
- .Select(name =>
- {
- try
- {
- return LibraryManager.GetGameGenre(name);
- }
- catch (Exception ex)
- {
- Logger.ErrorException("Error getting genre {0}", ex, name);
- return null;
- }
- })
- .Where(i => i != null);
+ return LibraryManager.GetGameGenres(query);
}
- return items
- .SelectMany(i => i.Genres)
- .DistinctNames()
- .Select(name =>
- {
- try
- {
- return LibraryManager.GetGenre(name);
- }
- catch (Exception ex)
- {
- Logger.ErrorException("Error getting genre {0}", ex, name);
- return null;
- }
- })
- .Where(i => i != null);
+ return LibraryManager.GetGenres(query);
+ }
+
+ /// <summary>
+ /// Gets all items.
+ /// </summary>
+ /// <param name="request">The request.</param>
+ /// <param name="items">The items.</param>
+ /// <returns>IEnumerable{Tuple{System.StringFunc{System.Int32}}}.</returns>
+ protected override IEnumerable<BaseItem> GetAllItems(GetItemsByName request, IEnumerable<BaseItem> items)
+ {
+ throw new NotImplementedException();
}
}
}
diff --git a/MediaBrowser.Api/UserLibrary/ItemsService.cs b/MediaBrowser.Api/UserLibrary/ItemsService.cs
index dac1a6b1a..a6f1d8b98 100644
--- a/MediaBrowser.Api/UserLibrary/ItemsService.cs
+++ b/MediaBrowser.Api/UserLibrary/ItemsService.cs
@@ -34,7 +34,6 @@ namespace MediaBrowser.Api.UserLibrary
/// The _user manager
/// </summary>
private readonly IUserManager _userManager;
- private readonly IUserDataManager _userDataRepository;
/// <summary>
/// The _library manager
@@ -43,25 +42,37 @@ namespace MediaBrowser.Api.UserLibrary
private readonly ILocalizationManager _localization;
private readonly IDtoService _dtoService;
- private readonly ICollectionManager _collectionManager;
/// <summary>
/// Initializes a new instance of the <see cref="ItemsService" /> class.
/// </summary>
/// <param name="userManager">The user manager.</param>
/// <param name="libraryManager">The library manager.</param>
- /// <param name="userDataRepository">The user data repository.</param>
/// <param name="localization">The localization.</param>
/// <param name="dtoService">The dto service.</param>
- /// <param name="collectionManager">The collection manager.</param>
- public ItemsService(IUserManager userManager, ILibraryManager libraryManager, IUserDataManager userDataRepository, ILocalizationManager localization, IDtoService dtoService, ICollectionManager collectionManager)
+ public ItemsService(IUserManager userManager, ILibraryManager libraryManager, ILocalizationManager localization, IDtoService dtoService)
{
+ if (userManager == null)
+ {
+ throw new ArgumentNullException("userManager");
+ }
+ if (libraryManager == null)
+ {
+ throw new ArgumentNullException("libraryManager");
+ }
+ if (localization == null)
+ {
+ throw new ArgumentNullException("localization");
+ }
+ if (dtoService == null)
+ {
+ throw new ArgumentNullException("dtoService");
+ }
+
_userManager = userManager;
_libraryManager = libraryManager;
- _userDataRepository = userDataRepository;
_localization = localization;
_dtoService = dtoService;
- _collectionManager = collectionManager;
}
/// <summary>
@@ -71,6 +82,11 @@ namespace MediaBrowser.Api.UserLibrary
/// <returns>System.Object.</returns>
public async Task<object> Get(GetItems request)
{
+ if (request == null)
+ {
+ throw new ArgumentNullException("request");
+ }
+
var result = await GetItems(request).ConfigureAwait(false);
return ToOptimizedSerializedResultUsingCache(result);
@@ -84,15 +100,32 @@ namespace MediaBrowser.Api.UserLibrary
private async Task<ItemsResult> GetItems(GetItems request)
{
var user = !string.IsNullOrWhiteSpace(request.UserId) ? _userManager.GetUserById(request.UserId) : null;
-
+
var result = await GetItemsToSerialize(request, user).ConfigureAwait(false);
+ if (result == null)
+ {
+ throw new InvalidOperationException("GetItemsToSerialize returned null");
+ }
+
+ if (result.Items == null)
+ {
+ throw new InvalidOperationException("GetItemsToSerialize result.Items returned null");
+ }
+
var dtoOptions = GetDtoOptions(request);
+ var dtoList = await _dtoService.GetBaseItemDtos(result.Items, dtoOptions, user).ConfigureAwait(false);
+
+ if (dtoList == null)
+ {
+ throw new InvalidOperationException("GetBaseItemDtos returned null");
+ }
+
return new ItemsResult
{
TotalRecordCount = result.TotalRecordCount,
- Items = _dtoService.GetBaseItemDtos(result.Items, dtoOptions, user).ToArray()
+ Items = dtoList.ToArray()
};
}
@@ -119,11 +152,17 @@ namespace MediaBrowser.Api.UserLibrary
// Default list type = children
+ var folder = item as Folder;
+ if (folder == null)
+ {
+ folder = user == null ? _libraryManager.RootFolder : _libraryManager.GetUserRootFolder();
+ }
+
if (!string.IsNullOrEmpty(request.Ids))
{
request.Recursive = true;
var query = GetItemsQuery(request, user);
- var result = await ((Folder)item).GetItems(query).ConfigureAwait(false);
+ var result = await folder.GetItems(query).ConfigureAwait(false);
if (string.IsNullOrWhiteSpace(request.SortBy))
{
@@ -138,22 +177,22 @@ namespace MediaBrowser.Api.UserLibrary
if (request.Recursive)
{
- return await ((Folder)item).GetItems(GetItemsQuery(request, user)).ConfigureAwait(false);
+ return await folder.GetItems(GetItemsQuery(request, user)).ConfigureAwait(false);
}
if (user == null)
{
- return await ((Folder)item).GetItems(GetItemsQuery(request, null)).ConfigureAwait(false);
+ return await folder.GetItems(GetItemsQuery(request, null)).ConfigureAwait(false);
}
var userRoot = item as UserRootFolder;
if (userRoot == null)
{
- return await ((Folder)item).GetItems(GetItemsQuery(request, user)).ConfigureAwait(false);
+ return await folder.GetItems(GetItemsQuery(request, user)).ConfigureAwait(false);
}
- IEnumerable<BaseItem> items = ((Folder)item).GetChildren(user, true);
+ IEnumerable<BaseItem> items = folder.GetChildren(user, true);
var itemsArray = items.ToArray();
@@ -187,7 +226,6 @@ namespace MediaBrowser.Api.UserLibrary
NameStartsWith = request.NameStartsWith,
NameStartsWithOrGreater = request.NameStartsWithOrGreater,
HasImdbId = request.HasImdbId,
- IsYearMismatched = request.IsYearMismatched,
IsPlaceHolder = request.IsPlaceHolder,
IsLocked = request.IsLocked,
IsInBoxSet = request.IsInBoxSet,
@@ -301,7 +339,7 @@ namespace MediaBrowser.Api.UserLibrary
{
query.LocationTypes = request.LocationTypes.Split(',').Select(d => (LocationType)Enum.Parse(typeof(LocationType), d, true)).ToArray();
}
-
+
// Min official rating
if (!string.IsNullOrWhiteSpace(request.MinOfficialRating))
{
diff --git a/MediaBrowser.Api/UserLibrary/MusicGenresService.cs b/MediaBrowser.Api/UserLibrary/MusicGenresService.cs
index 12cb62fac..887c99941 100644
--- a/MediaBrowser.Api/UserLibrary/MusicGenresService.cs
+++ b/MediaBrowser.Api/UserLibrary/MusicGenresService.cs
@@ -1,4 +1,5 @@
-using MediaBrowser.Controller.Dto;
+using System;
+using MediaBrowser.Controller.Dto;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.Audio;
using MediaBrowser.Controller.Library;
@@ -8,16 +9,14 @@ using MediaBrowser.Model.Dto;
using ServiceStack;
using System.Collections.Generic;
using System.Linq;
+using MediaBrowser.Model.Entities;
+using MediaBrowser.Model.Querying;
namespace MediaBrowser.Api.UserLibrary
{
[Route("/MusicGenres", "GET", Summary = "Gets all music genres from a given item, folder, or the entire library")]
public class GetMusicGenres : GetItemsByName
{
- public GetMusicGenres()
- {
- IncludeItemTypes = typeof(Audio).Name;
- }
}
[Route("/MusicGenres/{Name}", "GET", Summary = "Gets a music genre, by name")]
@@ -86,11 +85,16 @@ namespace MediaBrowser.Api.UserLibrary
/// <returns>System.Object.</returns>
public object Get(GetMusicGenres request)
{
- var result = GetResult(request);
+ var result = GetResultSlim(request);
return ToOptimizedSerializedResultUsingCache(result);
}
+ protected override QueryResult<Tuple<BaseItem, ItemCounts>> GetItems(GetItemsByName request, InternalItemsQuery query)
+ {
+ return LibraryManager.GetMusicGenres(query);
+ }
+
/// <summary>
/// Gets all items.
/// </summary>
@@ -99,10 +103,7 @@ namespace MediaBrowser.Api.UserLibrary
/// <returns>IEnumerable{Tuple{System.StringFunc{System.Int32}}}.</returns>
protected override IEnumerable<BaseItem> GetAllItems(GetItemsByName request, IEnumerable<BaseItem> items)
{
- return items
- .SelectMany(i => i.Genres)
- .DistinctNames()
- .Select(name => LibraryManager.GetMusicGenre(name));
+ throw new NotImplementedException();
}
}
}
diff --git a/MediaBrowser.Api/UserLibrary/PlaystateService.cs b/MediaBrowser.Api/UserLibrary/PlaystateService.cs
index 94c391cb5..710d337ec 100644
--- a/MediaBrowser.Api/UserLibrary/PlaystateService.cs
+++ b/MediaBrowser.Api/UserLibrary/PlaystateService.cs
@@ -247,9 +247,9 @@ namespace MediaBrowser.Api.UserLibrary
/// Posts the specified request.
/// </summary>
/// <param name="request">The request.</param>
- public object Post(MarkPlayedItem request)
+ public async Task<object> Post(MarkPlayedItem request)
{
- var result = MarkPlayed(request).Result;
+ var result = await MarkPlayed(request).ConfigureAwait(false);
return ToOptimizedResult(result);
}
@@ -429,7 +429,7 @@ namespace MediaBrowser.Api.UserLibrary
await item.MarkUnplayed(user).ConfigureAwait(false);
}
- return _userDataRepository.GetUserDataDto(item, user);
+ return await _userDataRepository.GetUserDataDto(item, user).ConfigureAwait(false);
}
}
} \ No newline at end of file
diff --git a/MediaBrowser.Api/UserLibrary/StudiosService.cs b/MediaBrowser.Api/UserLibrary/StudiosService.cs
index 2cdabf721..9e9c25d78 100644
--- a/MediaBrowser.Api/UserLibrary/StudiosService.cs
+++ b/MediaBrowser.Api/UserLibrary/StudiosService.cs
@@ -1,4 +1,5 @@
-using MediaBrowser.Controller.Dto;
+using System;
+using MediaBrowser.Controller.Dto;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Net;
@@ -7,6 +8,7 @@ using MediaBrowser.Model.Dto;
using ServiceStack;
using System.Collections.Generic;
using System.Linq;
+using MediaBrowser.Model.Querying;
namespace MediaBrowser.Api.UserLibrary
{
@@ -90,11 +92,16 @@ namespace MediaBrowser.Api.UserLibrary
/// <returns>System.Object.</returns>
public object Get(GetStudios request)
{
- var result = GetResult(request);
+ var result = GetResultSlim(request);
return ToOptimizedSerializedResultUsingCache(result);
}
+ protected override QueryResult<Tuple<BaseItem, ItemCounts>> GetItems(GetItemsByName request, InternalItemsQuery query)
+ {
+ return LibraryManager.GetStudios(query);
+ }
+
/// <summary>
/// Gets all items.
/// </summary>
diff --git a/MediaBrowser.Api/UserLibrary/UserLibraryService.cs b/MediaBrowser.Api/UserLibrary/UserLibraryService.cs
index 8cc5cab35..3be11bdc5 100644
--- a/MediaBrowser.Api/UserLibrary/UserLibraryService.cs
+++ b/MediaBrowser.Api/UserLibrary/UserLibraryService.cs
@@ -488,9 +488,9 @@ namespace MediaBrowser.Api.UserLibrary
/// Posts the specified request.
/// </summary>
/// <param name="request">The request.</param>
- public object Post(MarkFavoriteItem request)
+ public async Task<object> Post(MarkFavoriteItem request)
{
- var dto = MarkFavorite(request.UserId, request.Id, true).Result;
+ var dto = await MarkFavorite(request.UserId, request.Id, true).ConfigureAwait(false);
return ToOptimizedResult(dto);
}
@@ -527,7 +527,7 @@ namespace MediaBrowser.Api.UserLibrary
await _userDataRepository.SaveUserData(user.Id, item, data, UserDataSaveReason.UpdateUserRating, CancellationToken.None).ConfigureAwait(false);
- return _userDataRepository.GetUserDataDto(item, user);
+ return await _userDataRepository.GetUserDataDto(item, user).ConfigureAwait(false);
}
/// <summary>
@@ -545,9 +545,9 @@ namespace MediaBrowser.Api.UserLibrary
/// Posts the specified request.
/// </summary>
/// <param name="request">The request.</param>
- public object Post(UpdateUserItemRating request)
+ public async Task<object> Post(UpdateUserItemRating request)
{
- var dto = UpdateUserItemRating(request.UserId, request.Id, request.Likes).Result;
+ var dto = await UpdateUserItemRating(request.UserId, request.Id, request.Likes).ConfigureAwait(false);
return ToOptimizedResult(dto);
}
@@ -572,7 +572,7 @@ namespace MediaBrowser.Api.UserLibrary
await _userDataRepository.SaveUserData(user.Id, item, data, UserDataSaveReason.UpdateUserRating, CancellationToken.None).ConfigureAwait(false);
- return _userDataRepository.GetUserDataDto(item, user);
+ return await _userDataRepository.GetUserDataDto(item, user).ConfigureAwait(false);
}
}
}
diff --git a/MediaBrowser.Api/UserService.cs b/MediaBrowser.Api/UserService.cs
index 9b611c397..07ff36c41 100644
--- a/MediaBrowser.Api/UserService.cs
+++ b/MediaBrowser.Api/UserService.cs
@@ -385,7 +385,7 @@ namespace MediaBrowser.Api
throw new ResourceNotFoundException("User not found");
}
- await _sessionMananger.RevokeUserTokens(user.Id.ToString("N")).ConfigureAwait(false);
+ await _sessionMananger.RevokeUserTokens(user.Id.ToString("N"), null).ConfigureAwait(false);
await _userManager.DeleteUser(user).ConfigureAwait(false);
}
@@ -465,6 +465,10 @@ namespace MediaBrowser.Api
}
await _userManager.ChangePassword(user, request.NewPassword).ConfigureAwait(false);
+
+ var currentToken = AuthorizationContext.GetAuthorizationInfo(Request).Token;
+
+ await _sessionMananger.RevokeUserTokens(user.Id.ToString("N"), currentToken).ConfigureAwait(false);
}
}
@@ -602,7 +606,8 @@ namespace MediaBrowser.Api
throw new ArgumentException("There must be at least one enabled user in the system.");
}
- await _sessionMananger.RevokeUserTokens(user.Id.ToString("N")).ConfigureAwait(false);
+ var currentToken = AuthorizationContext.GetAuthorizationInfo(Request).Token;
+ await _sessionMananger.RevokeUserTokens(user.Id.ToString("N"), currentToken).ConfigureAwait(false);
}
await _userManager.UpdateUserPolicy(request.Id, request).ConfigureAwait(false);
diff --git a/MediaBrowser.Common.Implementations/BaseApplicationHost.cs b/MediaBrowser.Common.Implementations/BaseApplicationHost.cs
index f44c975d4..baf5afc1b 100644
--- a/MediaBrowser.Common.Implementations/BaseApplicationHost.cs
+++ b/MediaBrowser.Common.Implementations/BaseApplicationHost.cs
@@ -199,7 +199,7 @@ namespace MediaBrowser.Common.Implementations
ILogManager logManager,
IFileSystem fileSystem)
{
- XmlSerializer = new MediaBrowser.Common.Implementations.Serialization.XmlSerializer (fileSystem);
+ XmlSerializer = new XmlSerializer (fileSystem, logManager.GetLogger("XmlSerializer"));
FailedAssemblies = new List<string>();
ApplicationPaths = applicationPaths;
@@ -321,7 +321,7 @@ namespace MediaBrowser.Common.Implementations
protected virtual IJsonSerializer CreateJsonSerializer()
{
- return new JsonSerializer(FileSystemManager);
+ return new JsonSerializer(FileSystemManager, LogManager.GetLogger("JsonSerializer"));
}
private void SetHttpLimit()
@@ -552,7 +552,7 @@ namespace MediaBrowser.Common.Implementations
}
catch (Exception ex)
{
- Logger.Error("Error creating {0}", ex, type.Name);
+ Logger.ErrorException("Error creating {0}", ex, type.Name);
throw;
}
@@ -571,7 +571,7 @@ namespace MediaBrowser.Common.Implementations
}
catch (Exception ex)
{
- Logger.Error("Error creating {0}", ex, type.Name);
+ Logger.ErrorException("Error creating {0}", ex, type.Name);
// Don't blow up in release mode
return null;
}
diff --git a/MediaBrowser.Common.Implementations/HttpClientManager/HttpClientManager.cs b/MediaBrowser.Common.Implementations/HttpClientManager/HttpClientManager.cs
index f9dbd766f..5c70179a8 100644
--- a/MediaBrowser.Common.Implementations/HttpClientManager/HttpClientManager.cs
+++ b/MediaBrowser.Common.Implementations/HttpClientManager/HttpClientManager.cs
@@ -128,11 +128,6 @@ namespace MediaBrowser.Common.Implementations.HttpClientManager
private void AddIpv4Option(HttpWebRequest request, HttpRequestOptions options)
{
- if (!options.PreferIpv4)
- {
- return;
- }
-
request.ServicePoint.BindIPEndPointDelegate = (servicePount, remoteEndPoint, retryCount) =>
{
if (remoteEndPoint.AddressFamily == AddressFamily.InterNetwork)
@@ -143,18 +138,23 @@ namespace MediaBrowser.Common.Implementations.HttpClientManager
};
}
- private WebRequest GetRequest(HttpRequestOptions options, string method, bool enableHttpCompression)
+ private WebRequest GetRequest(HttpRequestOptions options, string method)
{
var request = CreateWebRequest(options.Url);
var httpWebRequest = request as HttpWebRequest;
if (httpWebRequest != null)
{
- AddIpv4Option(httpWebRequest, options);
+ if (options.PreferIpv4)
+ {
+ AddIpv4Option(httpWebRequest, options);
+ }
AddRequestHeaders(httpWebRequest, options);
- httpWebRequest.AutomaticDecompression = enableHttpCompression ? DecompressionMethods.Deflate : DecompressionMethods.None;
+ httpWebRequest.AutomaticDecompression = options.EnableHttpCompression ?
+ (options.DecompressionMethod ?? DecompressionMethods.Deflate) :
+ DecompressionMethods.None;
}
request.CachePolicy = new RequestCachePolicy(RequestCacheLevel.BypassCache);
@@ -366,7 +366,7 @@ namespace MediaBrowser.Common.Implementations.HttpClientManager
};
}
- var httpWebRequest = GetRequest(options, httpMethod, options.EnableHttpCompression);
+ var httpWebRequest = GetRequest(options, httpMethod);
if (options.RequestContentBytes != null ||
!string.IsNullOrEmpty(options.RequestContent) ||
@@ -556,7 +556,7 @@ namespace MediaBrowser.Common.Implementations.HttpClientManager
options.CancellationToken.ThrowIfCancellationRequested();
- var httpWebRequest = GetRequest(options, "GET", options.EnableHttpCompression);
+ var httpWebRequest = GetRequest(options, "GET");
if (options.ResourcePool != null)
{
diff --git a/MediaBrowser.Common.Implementations/MediaBrowser.Common.Implementations.csproj b/MediaBrowser.Common.Implementations/MediaBrowser.Common.Implementations.csproj
index 70489d714..108eddcf9 100644
--- a/MediaBrowser.Common.Implementations/MediaBrowser.Common.Implementations.csproj
+++ b/MediaBrowser.Common.Implementations/MediaBrowser.Common.Implementations.csproj
@@ -55,7 +55,7 @@
<HintPath>..\packages\morelinq.1.4.0\lib\net35\MoreLinq.dll</HintPath>
</Reference>
<Reference Include="NLog, Version=4.0.0.0, Culture=neutral, PublicKeyToken=5120e14c03d0593c, processorArchitecture=MSIL">
- <HintPath>..\packages\NLog.4.3.4\lib\net45\NLog.dll</HintPath>
+ <HintPath>..\packages\NLog.4.3.5\lib\net45\NLog.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="Patterns.Logging">
@@ -65,8 +65,8 @@
<SpecificVersion>False</SpecificVersion>
<HintPath>..\ThirdParty\SharpCompress\SharpCompress.dll</HintPath>
</Reference>
- <Reference Include="SimpleInjector, Version=3.1.4.0, Culture=neutral, PublicKeyToken=984cb50dea722e99, processorArchitecture=MSIL">
- <HintPath>..\packages\SimpleInjector.3.1.4\lib\net45\SimpleInjector.dll</HintPath>
+ <Reference Include="SimpleInjector, Version=3.2.0.0, Culture=neutral, PublicKeyToken=984cb50dea722e99, processorArchitecture=MSIL">
+ <HintPath>..\packages\SimpleInjector.3.2.0\lib\net45\SimpleInjector.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="System" />
diff --git a/MediaBrowser.Common.Implementations/ScheduledTasks/ScheduledTaskWorker.cs b/MediaBrowser.Common.Implementations/ScheduledTasks/ScheduledTaskWorker.cs
index 090966d2b..b34d57c42 100644
--- a/MediaBrowser.Common.Implementations/ScheduledTasks/ScheduledTaskWorker.cs
+++ b/MediaBrowser.Common.Implementations/ScheduledTasks/ScheduledTaskWorker.cs
@@ -122,30 +122,27 @@ namespace MediaBrowser.Common.Implementations.ScheduledTasks
{
get
{
- if (_lastExecutionResult == null)
- {
- var path = GetHistoryFilePath();
+ var path = GetHistoryFilePath();
- lock (_lastExecutionResultSyncLock)
+ lock (_lastExecutionResultSyncLock)
+ {
+ if (_lastExecutionResult == null)
{
- if (_lastExecutionResult == null)
+ try
+ {
+ _lastExecutionResult = JsonSerializer.DeserializeFromFile<TaskResult>(path);
+ }
+ catch (DirectoryNotFoundException)
+ {
+ // File doesn't exist. No biggie
+ }
+ catch (FileNotFoundException)
+ {
+ // File doesn't exist. No biggie
+ }
+ catch (Exception ex)
{
- try
- {
- return JsonSerializer.DeserializeFromFile<TaskResult>(path);
- }
- catch (DirectoryNotFoundException)
- {
- // File doesn't exist. No biggie
- }
- catch (FileNotFoundException)
- {
- // File doesn't exist. No biggie
- }
- catch (Exception ex)
- {
- Logger.ErrorException("Error deserializing {0}", ex, path);
- }
+ Logger.ErrorException("Error deserializing {0}", ex, path);
}
}
}
diff --git a/MediaBrowser.Common.Implementations/Serialization/JsonSerializer.cs b/MediaBrowser.Common.Implementations/Serialization/JsonSerializer.cs
index 6610cd3ff..5dbbe5373 100644
--- a/MediaBrowser.Common.Implementations/Serialization/JsonSerializer.cs
+++ b/MediaBrowser.Common.Implementations/Serialization/JsonSerializer.cs
@@ -2,6 +2,7 @@
using System;
using System.IO;
using CommonIO;
+using MediaBrowser.Model.Logging;
namespace MediaBrowser.Common.Implementations.Serialization
{
@@ -11,10 +12,12 @@ namespace MediaBrowser.Common.Implementations.Serialization
public class JsonSerializer : IJsonSerializer
{
private readonly IFileSystem _fileSystem;
-
- public JsonSerializer(IFileSystem fileSystem)
+ private readonly ILogger _logger;
+
+ public JsonSerializer(IFileSystem fileSystem, ILogger logger)
{
_fileSystem = fileSystem;
+ _logger = logger;
Configure();
}
@@ -65,6 +68,7 @@ namespace MediaBrowser.Common.Implementations.Serialization
private Stream OpenFile(string path)
{
+ _logger.Debug("Deserializing file {0}", path);
return new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read, 131072);
}
diff --git a/MediaBrowser.Common.Implementations/Serialization/XmlSerializer.cs b/MediaBrowser.Common.Implementations/Serialization/XmlSerializer.cs
index 189fb7afc..290524921 100644
--- a/MediaBrowser.Common.Implementations/Serialization/XmlSerializer.cs
+++ b/MediaBrowser.Common.Implementations/Serialization/XmlSerializer.cs
@@ -4,6 +4,7 @@ using System.Collections.Concurrent;
using System.IO;
using System.Xml;
using CommonIO;
+using MediaBrowser.Model.Logging;
namespace MediaBrowser.Common.Implementations.Serialization
{
@@ -12,12 +13,14 @@ namespace MediaBrowser.Common.Implementations.Serialization
/// </summary>
public class XmlSerializer : IXmlSerializer
{
- private IFileSystem _fileSystem;
+ private readonly IFileSystem _fileSystem;
+ private readonly ILogger _logger;
- public XmlSerializer(IFileSystem fileSystem)
- {
- _fileSystem = fileSystem;
- }
+ public XmlSerializer(IFileSystem fileSystem, ILogger logger)
+ {
+ _fileSystem = fileSystem;
+ _logger = logger;
+ }
// Need to cache these
// http://dotnetcodebox.blogspot.com/2013/01/xmlserializer-class-may-result-in.html
@@ -91,6 +94,7 @@ namespace MediaBrowser.Common.Implementations.Serialization
/// <returns>System.Object.</returns>
public object DeserializeFromFile(Type type, string file)
{
+ _logger.Debug("Deserializing file {0}", file);
using (var stream = _fileSystem.OpenRead(file))
{
return DeserializeFromStream(type, stream);
diff --git a/MediaBrowser.Common.Implementations/packages.config b/MediaBrowser.Common.Implementations/packages.config
index d1d135b20..882acc9ff 100644
--- a/MediaBrowser.Common.Implementations/packages.config
+++ b/MediaBrowser.Common.Implementations/packages.config
@@ -2,7 +2,7 @@
<packages>
<package id="CommonIO" version="1.0.0.9" targetFramework="net45" />
<package id="morelinq" version="1.4.0" targetFramework="net45" />
- <package id="NLog" version="4.3.4" targetFramework="net45" />
+ <package id="NLog" version="4.3.5" targetFramework="net45" />
<package id="Patterns.Logging" version="1.0.0.2" targetFramework="net45" />
- <package id="SimpleInjector" version="3.1.4" targetFramework="net45" />
+ <package id="SimpleInjector" version="3.2.0" targetFramework="net45" />
</packages> \ No newline at end of file
diff --git a/MediaBrowser.Common/Net/HttpRequestOptions.cs b/MediaBrowser.Common/Net/HttpRequestOptions.cs
index 75368a5fc..1a7f414a7 100644
--- a/MediaBrowser.Common/Net/HttpRequestOptions.cs
+++ b/MediaBrowser.Common/Net/HttpRequestOptions.cs
@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
+using System.Net;
using System.Threading;
namespace MediaBrowser.Common.Net
@@ -16,6 +17,8 @@ namespace MediaBrowser.Common.Net
/// <value>The URL.</value>
public string Url { get; set; }
+ public DecompressionMethods? DecompressionMethod { get; set; }
+
/// <summary>
/// Gets or sets the accept header.
/// </summary>
diff --git a/MediaBrowser.Controller/Channels/ChannelItemInfo.cs b/MediaBrowser.Controller/Channels/ChannelItemInfo.cs
index 587023ab4..57c2f1f7f 100644
--- a/MediaBrowser.Controller/Channels/ChannelItemInfo.cs
+++ b/MediaBrowser.Controller/Channels/ChannelItemInfo.cs
@@ -53,6 +53,12 @@ namespace MediaBrowser.Controller.Channels
public bool IsInfiniteStream { get; set; }
+ public string HomePageUrl { get; set; }
+
+ public List<string> Artists { get; set; }
+
+ public List<string> AlbumArtists { get; set; }
+
public ChannelItemInfo()
{
MediaSources = new List<ChannelMediaInfo>();
@@ -62,6 +68,8 @@ namespace MediaBrowser.Controller.Channels
People = new List<PersonInfo>();
Tags = new List<string>();
ProviderIds = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
+ Artists = new List<string>();
+ AlbumArtists = new List<string>();
}
}
}
diff --git a/MediaBrowser.Controller/Chapters/IChapterManager.cs b/MediaBrowser.Controller/Chapters/IChapterManager.cs
index 676ef9c56..27e06fb8d 100644
--- a/MediaBrowser.Controller/Chapters/IChapterManager.cs
+++ b/MediaBrowser.Controller/Chapters/IChapterManager.cs
@@ -33,7 +33,7 @@ namespace MediaBrowser.Controller.Chapters
/// <param name="chapters">The chapters.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>Task.</returns>
- Task SaveChapters(string itemId, IEnumerable<ChapterInfo> chapters, CancellationToken cancellationToken);
+ Task SaveChapters(string itemId, List<ChapterInfo> chapters, CancellationToken cancellationToken);
/// <summary>
/// Searches the specified video.
diff --git a/MediaBrowser.Controller/Drawing/ImageCollageOptions.cs b/MediaBrowser.Controller/Drawing/ImageCollageOptions.cs
index edc4f8558..92a7f5ac9 100644
--- a/MediaBrowser.Controller/Drawing/ImageCollageOptions.cs
+++ b/MediaBrowser.Controller/Drawing/ImageCollageOptions.cs
@@ -23,10 +23,5 @@ namespace MediaBrowser.Controller.Drawing
/// </summary>
/// <value>The height.</value>
public int Height { get; set; }
- /// <summary>
- /// Gets or sets the text.
- /// </summary>
- /// <value>The text.</value>
- public string Text { get; set; }
}
}
diff --git a/MediaBrowser.Controller/Dto/IDtoService.cs b/MediaBrowser.Controller/Dto/IDtoService.cs
index 5f0442f93..e4aa466df 100644
--- a/MediaBrowser.Controller/Dto/IDtoService.cs
+++ b/MediaBrowser.Controller/Dto/IDtoService.cs
@@ -3,6 +3,7 @@ using MediaBrowser.Controller.Entities;
using MediaBrowser.Model.Dto;
using MediaBrowser.Model.Querying;
using System.Collections.Generic;
+using System.Threading.Tasks;
namespace MediaBrowser.Controller.Dto
{
@@ -68,7 +69,7 @@ namespace MediaBrowser.Controller.Dto
/// <param name="user">The user.</param>
/// <param name="owner">The owner.</param>
/// <returns>IEnumerable&lt;BaseItemDto&gt;.</returns>
- IEnumerable<BaseItemDto> GetBaseItemDtos(IEnumerable<BaseItem> items, DtoOptions options, User user = null,
+ Task<List<BaseItemDto>> GetBaseItemDtos(IEnumerable<BaseItem> items, DtoOptions options, User user = null,
BaseItem owner = null);
/// <summary>
diff --git a/MediaBrowser.Controller/Entities/Audio/Audio.cs b/MediaBrowser.Controller/Entities/Audio/Audio.cs
index c34a884ff..b3df34c4d 100644
--- a/MediaBrowser.Controller/Entities/Audio/Audio.cs
+++ b/MediaBrowser.Controller/Entities/Audio/Audio.cs
@@ -20,15 +20,12 @@ namespace MediaBrowser.Controller.Entities.Audio
IHasArtist,
IHasMusicGenres,
IHasLookupInfo<SongInfo>,
- IHasTags,
IHasMediaSources,
IThemeMedia,
IArchivable
{
public List<ChannelMediaInfo> ChannelMediaSources { get; set; }
- public long? Size { get; set; }
- public string Container { get; set; }
public int? TotalBitrate { get; set; }
public ExtraType? ExtraType { get; set; }
diff --git a/MediaBrowser.Controller/Entities/Audio/MusicAlbum.cs b/MediaBrowser.Controller/Entities/Audio/MusicAlbum.cs
index 615276e83..1f3b0c92a 100644
--- a/MediaBrowser.Controller/Entities/Audio/MusicAlbum.cs
+++ b/MediaBrowser.Controller/Entities/Audio/MusicAlbum.cs
@@ -179,17 +179,13 @@ namespace MediaBrowser.Controller.Entities.Audio
{
var items = GetRecursiveChildren().ToList();
- var songs = items.OfType<Audio>().ToList();
-
- var others = items.Except(songs).ToList();
-
- var totalItems = songs.Count + others.Count;
+ var totalItems = items.Count;
var numComplete = 0;
var childUpdateType = ItemUpdateType.None;
// Refresh songs
- foreach (var item in songs)
+ foreach (var item in items)
{
cancellationToken.ThrowIfCancellationRequested();
@@ -199,7 +195,7 @@ namespace MediaBrowser.Controller.Entities.Audio
numComplete++;
double percent = numComplete;
percent /= totalItems;
- progress.Report(percent * 100);
+ progress.Report(percent * 95);
}
var parentRefreshOptions = refreshOptions;
@@ -212,19 +208,6 @@ namespace MediaBrowser.Controller.Entities.Audio
// Refresh current item
await RefreshMetadata(parentRefreshOptions, cancellationToken).ConfigureAwait(false);
- // Refresh all non-songs
- foreach (var item in others)
- {
- cancellationToken.ThrowIfCancellationRequested();
-
- var updateType = await item.RefreshMetadata(parentRefreshOptions, cancellationToken).ConfigureAwait(false);
-
- numComplete++;
- double percent = numComplete;
- percent /= totalItems;
- progress.Report(percent * 100);
- }
-
progress.Report(100);
}
}
diff --git a/MediaBrowser.Controller/Entities/Audio/MusicArtist.cs b/MediaBrowser.Controller/Entities/Audio/MusicArtist.cs
index 02dad93cb..6790a1bcf 100644
--- a/MediaBrowser.Controller/Entities/Audio/MusicArtist.cs
+++ b/MediaBrowser.Controller/Entities/Audio/MusicArtist.cs
@@ -9,6 +9,7 @@ using System.Linq;
using System.Runtime.Serialization;
using System.Threading;
using System.Threading.Tasks;
+using MediaBrowser.Common.Extensions;
namespace MediaBrowser.Controller.Entities.Audio
{
@@ -62,13 +63,6 @@ namespace MediaBrowser.Controller.Entities.Audio
query.ArtistNames = new[] { Name };
}
- // Need this for now since the artist filter isn't yet supported by the db
- if (ConfigurationManager.Configuration.SchemaVersion < 79)
- {
- var filter = GetItemFilter();
- return LibraryManager.GetItemList(query).Where(filter);
- }
-
return LibraryManager.GetItemList(query);
}
@@ -86,6 +80,15 @@ namespace MediaBrowser.Controller.Entities.Audio
}
}
+ public override int GetChildCount(User user)
+ {
+ if (IsAccessedByName)
+ {
+ return 0;
+ }
+ return base.GetChildCount(user);
+ }
+
public override bool IsSaveLocalMetadataEnabled()
{
if (IsAccessedByName)
@@ -163,10 +166,17 @@ namespace MediaBrowser.Controller.Entities.Audio
list.Add("Artist-Musicbrainz-" + id);
}
- list.Add("Artist-" + item.Name);
+ list.Add("Artist-" + (item.Name ?? string.Empty).RemoveDiacritics());
return list;
}
+ public override string PresentationUniqueKey
+ {
+ get
+ {
+ return "Artist-" + (Name ?? string.Empty).RemoveDiacritics();
+ }
+ }
protected override bool GetBlockUnratedValue(UserPolicy config)
{
return config.BlockUnratedItems.Contains(UnratedItem.Music);
diff --git a/MediaBrowser.Controller/Entities/Audio/MusicGenre.cs b/MediaBrowser.Controller/Entities/Audio/MusicGenre.cs
index 77cf0cc49..798bc79fb 100644
--- a/MediaBrowser.Controller/Entities/Audio/MusicGenre.cs
+++ b/MediaBrowser.Controller/Entities/Audio/MusicGenre.cs
@@ -2,6 +2,7 @@
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
+using MediaBrowser.Common.Extensions;
namespace MediaBrowser.Controller.Entities.Audio
{
@@ -14,10 +15,18 @@ namespace MediaBrowser.Controller.Entities.Audio
{
var list = base.GetUserDataKeys();
- list.Insert(0, "MusicGenre-" + Name);
+ list.Insert(0, GetType().Name + "-" + (Name ?? string.Empty).RemoveDiacritics());
return list;
}
+ public override string PresentationUniqueKey
+ {
+ get
+ {
+ return GetUserDataKeys()[0];
+ }
+ }
+
[IgnoreDataMember]
public override bool SupportsAddingToPlaylist
{
diff --git a/MediaBrowser.Controller/Entities/BaseItem.cs b/MediaBrowser.Controller/Entities/BaseItem.cs
index 61060766f..0dd7b3c83 100644
--- a/MediaBrowser.Controller/Entities/BaseItem.cs
+++ b/MediaBrowser.Controller/Entities/BaseItem.cs
@@ -26,6 +26,7 @@ using System.Threading.Tasks;
using CommonIO;
using MediaBrowser.Controller.Sorting;
using MediaBrowser.Model.LiveTv;
+using MediaBrowser.Model.Providers;
namespace MediaBrowser.Controller.Entities
{
@@ -36,6 +37,7 @@ namespace MediaBrowser.Controller.Entities
{
protected BaseItem()
{
+ Keywords = new List<string>();
Tags = new List<string>();
Genres = new List<string>();
Studios = new List<string>();
@@ -67,12 +69,20 @@ namespace MediaBrowser.Controller.Entities
[IgnoreDataMember]
public string PreferredMetadataLanguage { get; set; }
+ public long? Size { get; set; }
+ public string Container { get; set; }
+ public string ShortOverview { get; set; }
+
public List<ItemImageInfo> ImageInfos { get; set; }
+ [IgnoreDataMember]
+ public bool IsVirtualItem { get; set; }
+
/// <summary>
/// Gets or sets the album.
/// </summary>
/// <value>The album.</value>
+ [IgnoreDataMember]
public string Album { get; set; }
/// <summary>
@@ -810,6 +820,8 @@ namespace MediaBrowser.Controller.Entities
[IgnoreDataMember]
public List<string> Tags { get; set; }
+ public List<string> Keywords { get; set; }
+
/// <summary>
/// Gets or sets the home page URL.
/// </summary>
@@ -1031,9 +1043,7 @@ namespace MediaBrowser.Controller.Entities
}
: options;
- var result = await ProviderManager.RefreshSingleItem(this, refreshOptions, cancellationToken).ConfigureAwait(false);
-
- return result;
+ return await ProviderManager.RefreshSingleItem(this, refreshOptions, cancellationToken).ConfigureAwait(false);
}
[IgnoreDataMember]
@@ -1394,15 +1404,10 @@ namespace MediaBrowser.Controller.Entities
private bool IsVisibleViaTags(User user)
{
- var hasTags = this as IHasTags;
-
- if (hasTags != null)
+ var policy = user.Policy;
+ if (policy.BlockedTags.Any(i => Tags.Contains(i, StringComparer.OrdinalIgnoreCase)))
{
- var policy = user.Policy;
- if (policy.BlockedTags.Any(i => hasTags.Tags.Contains(i, StringComparer.OrdinalIgnoreCase)))
- {
- return false;
- }
+ return false;
}
return true;
@@ -2088,7 +2093,7 @@ namespace MediaBrowser.Controller.Entities
return path;
}
- public virtual void FillUserDataDtoValues(UserItemDataDto dto, UserItemData userData, User user)
+ public virtual Task FillUserDataDtoValues(UserItemDataDto dto, UserItemData userData, BaseItemDto itemDto, User user)
{
if (RunTimeTicks.HasValue)
{
@@ -2104,6 +2109,8 @@ namespace MediaBrowser.Controller.Entities
}
}
}
+
+ return Task.FromResult(true);
}
protected Task RefreshMetadataForOwnedVideo(MetadataRefreshOptions options, string path, CancellationToken cancellationToken)
@@ -2168,7 +2175,7 @@ namespace MediaBrowser.Controller.Entities
{
get
{
- if (GetParent() is AggregateFolder || this is BasePluginFolder)
+ if (GetParent() is AggregateFolder || this is BasePluginFolder || this is Channel)
{
return true;
}
@@ -2214,5 +2221,10 @@ namespace MediaBrowser.Controller.Entities
DeleteFileLocation = false
});
}
+
+ public virtual List<ExternalUrl> GetRelatedUrls()
+ {
+ return new List<ExternalUrl>();
+ }
}
} \ No newline at end of file
diff --git a/MediaBrowser.Controller/Entities/Book.cs b/MediaBrowser.Controller/Entities/Book.cs
index 1c86a53f0..96fad670e 100644
--- a/MediaBrowser.Controller/Entities/Book.cs
+++ b/MediaBrowser.Controller/Entities/Book.cs
@@ -6,7 +6,7 @@ using MediaBrowser.Model.Entities;
namespace MediaBrowser.Controller.Entities
{
- public class Book : BaseItem, IHasTags, IHasLookupInfo<BookInfo>, IHasSeries
+ public class Book : BaseItem, IHasLookupInfo<BookInfo>, IHasSeries
{
[IgnoreDataMember]
public override string MediaType
diff --git a/MediaBrowser.Controller/Entities/Folder.cs b/MediaBrowser.Controller/Entities/Folder.cs
index e7b1df55a..210c11564 100644
--- a/MediaBrowser.Controller/Entities/Folder.cs
+++ b/MediaBrowser.Controller/Entities/Folder.cs
@@ -13,6 +13,7 @@ using System.Runtime.Serialization;
using System.Threading;
using System.Threading.Tasks;
using CommonIO;
+using MediaBrowser.Controller.Channels;
using MediaBrowser.Model.Channels;
namespace MediaBrowser.Controller.Entities
@@ -20,7 +21,7 @@ namespace MediaBrowser.Controller.Entities
/// <summary>
/// Class Folder
/// </summary>
- public class Folder : BaseItem, IHasThemeMedia, IHasTags
+ public class Folder : BaseItem, IHasThemeMedia
{
public static IUserManager UserManager { get; set; }
public static IUserViewManager UserViewManager { get; set; }
@@ -164,49 +165,15 @@ namespace MediaBrowser.Controller.Entities
item.DateModified = DateTime.UtcNow;
}
- AddChildInternal(item.Id);
-
await LibraryManager.CreateItem(item, cancellationToken).ConfigureAwait(false);
}
- protected void AddChildrenInternal(List<Guid> children)
- {
- lock (_childrenSyncLock)
- {
- var newChildren = ChildIds.ToList();
- newChildren.AddRange(children);
- _children = newChildren.ToList();
- }
- }
- protected void AddChildInternal(Guid child)
- {
- lock (_childrenSyncLock)
- {
- var childIds = ChildIds.ToList();
- if (!childIds.Contains(child))
- {
- childIds.Add(child);
- _children = childIds.ToList();
- }
- }
- }
-
- protected void RemoveChildrenInternal(List<Guid> children)
- {
- lock (_childrenSyncLock)
- {
- _children = ChildIds.Except(children).ToList();
- }
- }
-
/// <summary>
/// Removes the child.
/// </summary>
/// <param name="item">The item.</param>
public void RemoveChild(BaseItem item)
{
- RemoveChildrenInternal(new[] { item.Id }.ToList());
-
item.SetParent(null);
}
@@ -242,33 +209,6 @@ namespace MediaBrowser.Controller.Entities
#endregion
/// <summary>
- /// The children
- /// </summary>
- private IReadOnlyList<Guid> _children;
- /// <summary>
- /// The _children sync lock
- /// </summary>
- private readonly object _childrenSyncLock = new object();
- /// <summary>
- /// Gets or sets the actual children.
- /// </summary>
- /// <value>The actual children.</value>
- protected virtual IEnumerable<Guid> ChildIds
- {
- get
- {
- lock (_childrenSyncLock)
- {
- if (_children == null)
- {
- _children = LoadChildren().ToList();
- }
- return _children.ToList();
- }
- }
- }
-
- /// <summary>
/// Gets the actual children.
/// </summary>
/// <value>The actual children.</value>
@@ -277,7 +217,7 @@ namespace MediaBrowser.Controller.Entities
{
get
{
- return ChildIds.Select(LibraryManager.GetItemById).Where(i => i != null);
+ return LoadChildren();
}
}
@@ -331,7 +271,7 @@ namespace MediaBrowser.Controller.Entities
/// Loads our children. Validation will occur externally.
/// We want this sychronous.
/// </summary>
- protected virtual IEnumerable<Guid> LoadChildren()
+ protected virtual IEnumerable<BaseItem> LoadChildren()
{
//just load our children from the repo - the library will be validated and maintained in other processes
return GetCachedChildren();
@@ -461,17 +401,15 @@ namespace MediaBrowser.Controller.Entities
foreach (var item in itemsRemoved)
{
- if (item.LocationType == LocationType.Virtual ||
- item.LocationType == LocationType.Remote)
+ var itemLocationType = item.LocationType;
+ if (itemLocationType == LocationType.Virtual ||
+ itemLocationType == LocationType.Remote)
{
- // Don't remove these because there's no way to accurately validate them.
- validChildren.Add(item);
}
else if (!string.IsNullOrEmpty(item.Path) && IsPathOffline(item.Path))
{
await UpdateIsOffline(item, true).ConfigureAwait(false);
- validChildren.Add(item);
}
else
{
@@ -481,8 +419,6 @@ namespace MediaBrowser.Controller.Entities
if (actualRemovals.Count > 0)
{
- RemoveChildrenInternal(actualRemovals.Select(i => i.Id).ToList());
-
foreach (var item in actualRemovals)
{
Logger.Debug("Removed item: " + item.Path);
@@ -495,8 +431,6 @@ namespace MediaBrowser.Controller.Entities
}
await LibraryManager.CreateItems(newItems, cancellationToken).ConfigureAwait(false);
-
- AddChildrenInternal(newItems.Select(i => i.Id).ToList());
}
}
@@ -724,15 +658,36 @@ namespace MediaBrowser.Controller.Entities
/// Get our children from the repo - stubbed for now
/// </summary>
/// <returns>IEnumerable{BaseItem}.</returns>
- protected IEnumerable<Guid> GetCachedChildren()
+ protected IEnumerable<BaseItem> GetCachedChildren()
{
- return ItemRepository.GetItemIdsList(new InternalItemsQuery
+ return ItemRepository.GetItemList(new InternalItemsQuery
{
ParentId = Id,
GroupByPresentationUniqueKey = false
});
}
+ public virtual int GetChildCount(User user)
+ {
+ if (LinkedChildren.Count > 0)
+ {
+ if (!(this is ICollectionFolder))
+ {
+ return GetChildren(user, true).Count();
+ }
+ }
+
+ var result = GetItems(new InternalItemsQuery(user)
+ {
+ Recursive = false,
+ Limit = 0,
+ ParentId = Id
+
+ }).Result;
+
+ return result.TotalRecordCount;
+ }
+
public QueryResult<BaseItem> QueryRecursive(InternalItemsQuery query)
{
var user = query.User;
@@ -768,58 +723,13 @@ namespace MediaBrowser.Controller.Entities
{
if (!(this is ICollectionFolder))
{
- Logger.Debug("Query requires post-filtering due to LinkedChildren");
+ Logger.Debug("Query requires post-filtering due to LinkedChildren. Type: " + GetType().Name);
return true;
}
}
- var supportsUserDataQueries = ConfigurationManager.Configuration.SchemaVersion >= 76;
-
if (query.SortBy != null && query.SortBy.Length > 0)
{
- if (!supportsUserDataQueries)
- {
- if (query.SortBy.Contains(ItemSortBy.DatePlayed, StringComparer.OrdinalIgnoreCase))
- {
- Logger.Debug("Query requires post-filtering due to ItemSortBy.IsFavoriteOrLiked");
- return true;
- }
- if (query.SortBy.Contains(ItemSortBy.PlayCount, StringComparer.OrdinalIgnoreCase))
- {
- Logger.Debug("Query requires post-filtering due to ItemSortBy.PlayCount");
- return true;
- }
- if (query.SortBy.Contains(ItemSortBy.IsFavoriteOrLiked, StringComparer.OrdinalIgnoreCase))
- {
- Logger.Debug("Query requires post-filtering due to ItemSortBy.IsFavoriteOrLiked");
- return true;
- }
- if (query.SortBy.Contains(ItemSortBy.IsPlayed, StringComparer.OrdinalIgnoreCase))
- {
- Logger.Debug("Query requires post-filtering due to ItemSortBy.IsPlayed");
- return true;
- }
- if (query.SortBy.Contains(ItemSortBy.IsUnplayed, StringComparer.OrdinalIgnoreCase))
- {
- Logger.Debug("Query requires post-filtering due to ItemSortBy.IsUnplayed");
- return true;
- }
- }
-
- if (ConfigurationManager.Configuration.SchemaVersion < 79)
- {
- if (query.SortBy.Contains(ItemSortBy.AlbumArtist, StringComparer.OrdinalIgnoreCase))
- {
- Logger.Debug("Query requires post-filtering due to ItemSortBy.AlbumArtist");
- return true;
- }
- if (query.SortBy.Contains(ItemSortBy.Artist, StringComparer.OrdinalIgnoreCase))
- {
- Logger.Debug("Query requires post-filtering due to ItemSortBy.Artist");
- return true;
- }
- }
-
if (query.SortBy.Contains(ItemSortBy.AiredEpisodeOrder, StringComparer.OrdinalIgnoreCase))
{
Logger.Debug("Query requires post-filtering due to ItemSortBy.AiredEpisodeOrder");
@@ -840,11 +750,6 @@ namespace MediaBrowser.Controller.Entities
Logger.Debug("Query requires post-filtering due to ItemSortBy.Metascore");
return true;
}
- if (query.SortBy.Contains(ItemSortBy.OfficialRating, StringComparer.OrdinalIgnoreCase))
- {
- Logger.Debug("Query requires post-filtering due to ItemSortBy.OfficialRating");
- return true;
- }
if (query.SortBy.Contains(ItemSortBy.Players, StringComparer.OrdinalIgnoreCase))
{
Logger.Debug("Query requires post-filtering due to ItemSortBy.Players");
@@ -860,11 +765,6 @@ namespace MediaBrowser.Controller.Entities
Logger.Debug("Query requires post-filtering due to ItemSortBy.SeriesSortName");
return true;
}
- if (query.SortBy.Contains(ItemSortBy.Studio, StringComparer.OrdinalIgnoreCase))
- {
- Logger.Debug("Query requires post-filtering due to ItemSortBy.Studio");
- return true;
- }
if (query.SortBy.Contains(ItemSortBy.VideoBitRate, StringComparer.OrdinalIgnoreCase))
{
Logger.Debug("Query requires post-filtering due to ItemSortBy.VideoBitRate");
@@ -878,45 +778,6 @@ namespace MediaBrowser.Controller.Entities
return true;
}
- if (query.PersonIds.Length > 0)
- {
- Logger.Debug("Query requires post-filtering due to PersonIds");
- return true;
- }
-
- if (!supportsUserDataQueries)
- {
- if (query.IsLiked.HasValue)
- {
- Logger.Debug("Query requires post-filtering due to IsLiked");
- return true;
- }
-
- if (query.IsFavoriteOrLiked.HasValue)
- {
- Logger.Debug("Query requires post-filtering due to IsFavoriteOrLiked");
- return true;
- }
-
- if (query.IsFavorite.HasValue)
- {
- Logger.Debug("Query requires post-filtering due to IsFavorite");
- return true;
- }
-
- if (query.IsResumable.HasValue)
- {
- Logger.Debug("Query requires post-filtering due to IsResumable");
- return true;
- }
-
- if (query.IsPlayed.HasValue)
- {
- Logger.Debug("Query requires post-filtering due to IsPlayed");
- return true;
- }
- }
-
if (query.IsInBoxSet.HasValue)
{
Logger.Debug("Query requires post-filtering due to IsInBoxSet");
@@ -930,30 +791,6 @@ namespace MediaBrowser.Controller.Entities
return true;
}
- if (query.HasImdbId.HasValue)
- {
- Logger.Debug("Query requires post-filtering due to HasImdbId");
- return true;
- }
-
- if (query.HasTmdbId.HasValue)
- {
- Logger.Debug("Query requires post-filtering due to HasTmdbId");
- return true;
- }
-
- if (query.HasTvdbId.HasValue)
- {
- Logger.Debug("Query requires post-filtering due to HasTvdbId");
- return true;
- }
-
- if (query.IsYearMismatched.HasValue)
- {
- Logger.Debug("Query requires post-filtering due to IsYearMismatched");
- return true;
- }
-
if (query.HasOfficialRating.HasValue)
{
Logger.Debug("Query requires post-filtering due to HasOfficialRating");
@@ -1003,26 +840,6 @@ namespace MediaBrowser.Controller.Entities
return true;
}
- if (query.ImageTypes.Length > 0)
- {
- Logger.Debug("Query requires post-filtering due to ImageTypes");
- return true;
- }
-
- // Apply studio filter
- if (query.StudioIds.Length > 0)
- {
- Logger.Debug("Query requires post-filtering due to StudioIds");
- return true;
- }
-
- // Apply genre filter
- if (query.GenreIds.Length > 0)
- {
- Logger.Debug("Query requires post-filtering due to GenreIds");
- return true;
- }
-
// Apply person filter
if (query.ItemIdsFromPersonFilters != null)
{
@@ -1042,12 +859,6 @@ namespace MediaBrowser.Controller.Entities
return true;
}
- if (query.OfficialRatings.Length > 0)
- {
- Logger.Debug("Query requires post-filtering due to OfficialRatings");
- return true;
- }
-
if (query.IsMissing.HasValue)
{
Logger.Debug("Query requires post-filtering due to IsMissing");
@@ -1066,7 +877,7 @@ namespace MediaBrowser.Controller.Entities
return true;
}
- if (UserViewBuilder.CollapseBoxSetItems(query, this, query.User))
+ if (UserViewBuilder.CollapseBoxSetItems(query, this, query.User, ConfigurationManager))
{
Logger.Debug("Query requires post-filtering due to CollapseBoxSetItems");
return true;
@@ -1102,15 +913,6 @@ namespace MediaBrowser.Controller.Entities
return true;
}
- if (ConfigurationManager.Configuration.SchemaVersion < 79)
- {
- if (query.ArtistNames.Length > 0)
- {
- Logger.Debug("Query requires post-filtering due to ArtistNames");
- return true;
- }
- }
-
return false;
}
@@ -1183,7 +985,7 @@ namespace MediaBrowser.Controller.Entities
protected QueryResult<BaseItem> PostFilterAndSort(IEnumerable<BaseItem> items, InternalItemsQuery query)
{
- return UserViewBuilder.PostFilterAndSort(items, this, null, query, LibraryManager);
+ return UserViewBuilder.PostFilterAndSort(items, this, null, query, LibraryManager, ConfigurationManager);
}
public virtual IEnumerable<BaseItem> GetChildren(User user, bool includeLinkedChildren)
@@ -1601,72 +1403,61 @@ namespace MediaBrowser.Controller.Entities
{
return false;
}
+ if (this is Channel)
+ {
+ return false;
+ }
+ if (SourceType != SourceType.Library)
+ {
+ return false;
+ }
return true;
}
}
- public override void FillUserDataDtoValues(UserItemDataDto dto, UserItemData userData, User user)
+ public override async Task FillUserDataDtoValues(UserItemDataDto dto, UserItemData userData, BaseItemDto itemDto, User user)
{
if (!SupportsUserDataFromChildren)
{
return;
}
- var recursiveItemCount = 0;
- var unplayed = 0;
-
- double totalPercentPlayed = 0;
-
- var itemsResult = GetItems(new InternalItemsQuery(user)
+ var unplayedQueryResult = await GetItems(new InternalItemsQuery(user)
{
Recursive = true,
IsFolder = false,
- ExcludeLocationTypes = new[] { LocationType.Virtual },
- EnableTotalRecordCount = false
-
- }).Result;
+ IsVirtualItem = false,
+ EnableTotalRecordCount = true,
+ Limit = 0,
+ IsPlayed = false
- var children = itemsResult.Items;
+ }).ConfigureAwait(false);
- // Loop through each recursive child
- foreach (var child in children)
+ var allItemsQueryResult = await GetItems(new InternalItemsQuery(user)
{
- recursiveItemCount++;
-
- var isUnplayed = true;
-
- var itemUserData = UserDataManager.GetUserData(user, child);
-
- // Incrememt totalPercentPlayed
- if (itemUserData != null)
- {
- if (itemUserData.Played)
- {
- totalPercentPlayed += 100;
+ Recursive = true,
+ IsFolder = false,
+ IsVirtualItem = false,
+ EnableTotalRecordCount = true,
+ Limit = 0
- isUnplayed = false;
- }
- else if (itemUserData.PlaybackPositionTicks > 0 && child.RunTimeTicks.HasValue && child.RunTimeTicks.Value > 0)
- {
- double itemPercent = itemUserData.PlaybackPositionTicks;
- itemPercent /= child.RunTimeTicks.Value;
- totalPercentPlayed += itemPercent;
- }
- }
+ }).ConfigureAwait(false);
- if (isUnplayed)
- {
- unplayed++;
- }
+ if (itemDto != null)
+ {
+ itemDto.RecursiveItemCount = allItemsQueryResult.TotalRecordCount;
}
- dto.UnplayedItemCount = unplayed;
+ double recursiveItemCount = allItemsQueryResult.TotalRecordCount;
+ double unplayedCount = unplayedQueryResult.TotalRecordCount;
if (recursiveItemCount > 0)
{
- dto.PlayedPercentage = totalPercentPlayed / recursiveItemCount;
+ var unplayedPercentage = (unplayedCount / recursiveItemCount) * 100;
+ dto.PlayedPercentage = 100 - unplayedPercentage;
dto.Played = dto.PlayedPercentage.Value >= 100;
+ dto.UnplayedItemCount = unplayedQueryResult.TotalRecordCount;
}
}
}
diff --git a/MediaBrowser.Controller/Entities/Game.cs b/MediaBrowser.Controller/Entities/Game.cs
index 9ed240046..317c71529 100644
--- a/MediaBrowser.Controller/Entities/Game.cs
+++ b/MediaBrowser.Controller/Entities/Game.cs
@@ -7,7 +7,7 @@ using System.Linq;
namespace MediaBrowser.Controller.Entities
{
- public class Game : BaseItem, IHasTrailers, IHasThemeMedia, IHasTags, IHasScreenshots, ISupportsPlaceHolders, IHasLookupInfo<GameInfo>
+ public class Game : BaseItem, IHasTrailers, IHasThemeMedia, IHasScreenshots, ISupportsPlaceHolders, IHasLookupInfo<GameInfo>
{
public List<Guid> ThemeSongIds { get; set; }
public List<Guid> ThemeVideoIds { get; set; }
diff --git a/MediaBrowser.Controller/Entities/GameGenre.cs b/MediaBrowser.Controller/Entities/GameGenre.cs
index 7c1e88cb1..45e766c0f 100644
--- a/MediaBrowser.Controller/Entities/GameGenre.cs
+++ b/MediaBrowser.Controller/Entities/GameGenre.cs
@@ -2,6 +2,7 @@
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
+using MediaBrowser.Common.Extensions;
namespace MediaBrowser.Controller.Entities
{
@@ -11,10 +12,18 @@ namespace MediaBrowser.Controller.Entities
{
var list = base.GetUserDataKeys();
- list.Insert(0, "GameGenre-" + Name);
+ list.Insert(0, GetType().Name + "-" + (Name ?? string.Empty).RemoveDiacritics());
return list;
}
+ public override string PresentationUniqueKey
+ {
+ get
+ {
+ return GetUserDataKeys()[0];
+ }
+ }
+
/// <summary>
/// Returns the folder containing the item.
/// If the item is a folder, it returns the folder itself
diff --git a/MediaBrowser.Controller/Entities/Genre.cs b/MediaBrowser.Controller/Entities/Genre.cs
index c87d4daaf..cc5aebb2a 100644
--- a/MediaBrowser.Controller/Entities/Genre.cs
+++ b/MediaBrowser.Controller/Entities/Genre.cs
@@ -3,6 +3,7 @@ using MediaBrowser.Controller.Entities.Audio;
using System;
using System.Collections.Generic;
using System.Linq;
+using MediaBrowser.Common.Extensions;
namespace MediaBrowser.Controller.Entities
{
@@ -15,10 +16,18 @@ namespace MediaBrowser.Controller.Entities
{
var list = base.GetUserDataKeys();
- list.Insert(0, "Genre-" + Name);
+ list.Insert(0, GetType().Name + "-" + (Name ?? string.Empty).RemoveDiacritics());
return list;
}
+ public override string PresentationUniqueKey
+ {
+ get
+ {
+ return GetUserDataKeys()[0];
+ }
+ }
+
/// <summary>
/// Returns the folder containing the item.
/// If the item is a folder, it returns the folder itself
diff --git a/MediaBrowser.Controller/Entities/IHasUserData.cs b/MediaBrowser.Controller/Entities/IHasUserData.cs
index 244b319bd..2495b0ccd 100644
--- a/MediaBrowser.Controller/Entities/IHasUserData.cs
+++ b/MediaBrowser.Controller/Entities/IHasUserData.cs
@@ -1,4 +1,5 @@
using System.Collections.Generic;
+using System.Threading.Tasks;
using MediaBrowser.Model.Dto;
namespace MediaBrowser.Controller.Entities
@@ -16,7 +17,7 @@ namespace MediaBrowser.Controller.Entities
/// <param name="dto">The dto.</param>
/// <param name="userData">The user data.</param>
/// <param name="user">The user.</param>
- void FillUserDataDtoValues(UserItemDataDto dto, UserItemData userData, User user);
+ Task FillUserDataDtoValues(UserItemDataDto dto, UserItemData userData, BaseItemDto itemDto, User user);
bool EnableRememberingTrackSelections { get; }
}
diff --git a/MediaBrowser.Controller/Entities/InternalItemsQuery.cs b/MediaBrowser.Controller/Entities/InternalItemsQuery.cs
index 823f4066c..5faf85b2a 100644
--- a/MediaBrowser.Controller/Entities/InternalItemsQuery.cs
+++ b/MediaBrowser.Controller/Entities/InternalItemsQuery.cs
@@ -19,6 +19,8 @@ namespace MediaBrowser.Controller.Entities
public User User { get; set; }
+ public BaseItem SimilarTo { get; set; }
+
public bool? IsFolder { get; set; }
public bool? IsFavorite { get; set; }
public bool? IsFavoriteOrLiked { get; set; }
@@ -33,6 +35,7 @@ namespace MediaBrowser.Controller.Entities
public string[] ExcludeTags { get; set; }
public string[] ExcludeInheritedTags { get; set; }
public string[] Genres { get; set; }
+ public string[] Keywords { get; set; }
public bool? IsMissing { get; set; }
public bool? IsUnaired { get; set; }
@@ -43,6 +46,7 @@ namespace MediaBrowser.Controller.Entities
public string NameStartsWith { get; set; }
public string NameLessThan { get; set; }
public string NameContains { get; set; }
+ public string MinSortName { get; set; }
public string PresentationUniqueKey { get; set; }
public string Path { get; set; }
@@ -52,6 +56,7 @@ namespace MediaBrowser.Controller.Entities
public string Person { get; set; }
public string[] PersonIds { get; set; }
public string[] ItemIds { get; set; }
+ public string[] ExcludeItemIds { get; set; }
public string AdjacentTo { get; set; }
public string[] PersonTypes { get; set; }
@@ -60,7 +65,6 @@ namespace MediaBrowser.Controller.Entities
public bool? IsInBoxSet { get; set; }
public bool? IsLocked { get; set; }
public bool? IsPlaceHolder { get; set; }
- public bool? IsYearMismatched { get; set; }
public bool? HasImdbId { get; set; }
public bool? HasOverview { get; set; }
@@ -107,6 +111,7 @@ namespace MediaBrowser.Controller.Entities
internal List<Guid> ItemIdsFromPersonFilters { get; set; }
public int? ParentIndexNumber { get; set; }
+ public int? ParentIndexNumberNotEquals { get; set; }
public int? IndexNumber { get; set; }
public int? MinParentalRating { get; set; }
public int? MaxParentalRating { get; set; }
@@ -114,6 +119,7 @@ namespace MediaBrowser.Controller.Entities
public bool? IsCurrentSchema { get; set; }
public bool? HasDeadParentId { get; set; }
public bool? IsOffline { get; set; }
+ public bool? IsVirtualItem { get; set; }
public Guid? ParentId { get; set; }
public string[] AncestorIds { get; set; }
@@ -137,6 +143,8 @@ namespace MediaBrowser.Controller.Entities
public bool GroupByPresentationUniqueKey { get; set; }
public bool EnableTotalRecordCount { get; set; }
public bool ForceDirect { get; set; }
+ public Dictionary<string, string> ExcludeProviderIds { get; set; }
+ public bool EnableGroupByMetadataKey { get; set; }
public InternalItemsQuery()
{
@@ -145,12 +153,14 @@ namespace MediaBrowser.Controller.Entities
AlbumNames = new string[] { };
ArtistNames = new string[] { };
-
+ ExcludeProviderIds = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
+
BlockUnratedItems = new UnratedItem[] { };
Tags = new string[] { };
OfficialRatings = new string[] { };
SortBy = new string[] { };
MediaTypes = new string[] { };
+ Keywords = new string[] { };
IncludeItemTypes = new string[] { };
ExcludeItemTypes = new string[] { };
Genres = new string[] { };
@@ -164,6 +174,7 @@ namespace MediaBrowser.Controller.Entities
PersonIds = new string[] { };
ChannelIds = new string[] { };
ItemIds = new string[] { };
+ ExcludeItemIds = new string[] { };
AncestorIds = new string[] { };
TopParentIds = new string[] { };
ExcludeTags = new string[] { };
diff --git a/MediaBrowser.Controller/Entities/IHasKeywords.cs b/MediaBrowser.Controller/Entities/KeywordExtensions.cs
index ab9eb4aee..5c9afdf3d 100644
--- a/MediaBrowser.Controller/Entities/IHasKeywords.cs
+++ b/MediaBrowser.Controller/Entities/KeywordExtensions.cs
@@ -1,21 +1,11 @@
using System;
-using System.Collections.Generic;
using System.Linq;
namespace MediaBrowser.Controller.Entities
{
- public interface IHasKeywords
- {
- /// <summary>
- /// Gets or sets the keywords.
- /// </summary>
- /// <value>The keywords.</value>
- List<string> Keywords { get; set; }
- }
-
public static class KeywordExtensions
{
- public static void AddKeyword(this IHasKeywords item, string name)
+ public static void AddKeyword(this BaseItem item, string name)
{
if (string.IsNullOrWhiteSpace(name))
{
diff --git a/MediaBrowser.Controller/Entities/Movies/BoxSet.cs b/MediaBrowser.Controller/Entities/Movies/BoxSet.cs
index 09a9d97bc..4effc162e 100644
--- a/MediaBrowser.Controller/Entities/Movies/BoxSet.cs
+++ b/MediaBrowser.Controller/Entities/Movies/BoxSet.cs
@@ -15,7 +15,7 @@ namespace MediaBrowser.Controller.Entities.Movies
/// <summary>
/// Class BoxSet
/// </summary>
- public class BoxSet : Folder, IHasTrailers, IHasKeywords, IHasDisplayOrder, IHasLookupInfo<BoxSetInfo>, IHasShares
+ public class BoxSet : Folder, IHasTrailers, IHasDisplayOrder, IHasLookupInfo<BoxSetInfo>, IHasShares
{
public List<Share> Shares { get; set; }
@@ -26,7 +26,6 @@ namespace MediaBrowser.Controller.Entities.Movies
RemoteTrailerIds = new List<Guid>();
DisplayOrder = ItemSortBy.PremiereDate;
- Keywords = new List<string>();
Shares = new List<Share>();
}
@@ -48,12 +47,6 @@ namespace MediaBrowser.Controller.Entities.Movies
public List<MediaUrl> RemoteTrailers { get; set; }
/// <summary>
- /// Gets or sets the tags.
- /// </summary>
- /// <value>The tags.</value>
- public List<string> Keywords { get; set; }
-
- /// <summary>
/// Gets or sets the display order.
/// </summary>
/// <value>The display order.</value>
diff --git a/MediaBrowser.Controller/Entities/Movies/Movie.cs b/MediaBrowser.Controller/Entities/Movies/Movie.cs
index 5882b5f4d..c7a833c58 100644
--- a/MediaBrowser.Controller/Entities/Movies/Movie.cs
+++ b/MediaBrowser.Controller/Entities/Movies/Movie.cs
@@ -8,13 +8,14 @@ using System.Runtime.Serialization;
using System.Threading;
using System.Threading.Tasks;
using CommonIO;
+using MediaBrowser.Model.Providers;
namespace MediaBrowser.Controller.Entities.Movies
{
/// <summary>
/// Class Movie
/// </summary>
- public class Movie : Video, IHasCriticRating, IHasSpecialFeatures, IHasProductionLocations, IHasBudget, IHasKeywords, IHasTrailers, IHasThemeMedia, IHasTaglines, IHasAwards, IHasMetascore, IHasLookupInfo<MovieInfo>, ISupportsBoxSetGrouping, IHasOriginalTitle
+ public class Movie : Video, IHasCriticRating, IHasSpecialFeatures, IHasProductionLocations, IHasBudget, IHasTrailers, IHasThemeMedia, IHasTaglines, IHasAwards, IHasMetascore, IHasLookupInfo<MovieInfo>, ISupportsBoxSetGrouping, IHasOriginalTitle
{
public List<Guid> SpecialFeatureIds { get; set; }
@@ -31,7 +32,6 @@ namespace MediaBrowser.Controller.Entities.Movies
ThemeSongIds = new List<Guid>();
ThemeVideoIds = new List<Guid>();
Taglines = new List<string>();
- Keywords = new List<string>();
ProductionLocations = new List<string>();
}
@@ -41,7 +41,6 @@ namespace MediaBrowser.Controller.Entities.Movies
public List<Guid> LocalTrailerIds { get; set; }
public List<Guid> RemoteTrailerIds { get; set; }
- public List<string> Keywords { get; set; }
public List<MediaUrl> RemoteTrailers { get; set; }
@@ -163,5 +162,22 @@ namespace MediaBrowser.Controller.Entities.Movies
return hasChanges;
}
+
+ public override List<ExternalUrl> GetRelatedUrls()
+ {
+ var list = base.GetRelatedUrls();
+
+ var imdbId = this.GetProviderId(MetadataProviders.Imdb);
+ if (!string.IsNullOrWhiteSpace(imdbId))
+ {
+ list.Add(new ExternalUrl
+ {
+ Name = "Trakt",
+ Url = string.Format("https://trakt.tv/movies/{0}", imdbId)
+ });
+ }
+
+ return list;
+ }
}
}
diff --git a/MediaBrowser.Controller/Entities/Person.cs b/MediaBrowser.Controller/Entities/Person.cs
index 2b099e3d5..8ef0d70bf 100644
--- a/MediaBrowser.Controller/Entities/Person.cs
+++ b/MediaBrowser.Controller/Entities/Person.cs
@@ -3,6 +3,7 @@ using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
+using MediaBrowser.Common.Extensions;
using MediaBrowser.Model.Entities;
namespace MediaBrowser.Controller.Entities
@@ -22,10 +23,18 @@ namespace MediaBrowser.Controller.Entities
{
var list = base.GetUserDataKeys();
- list.Insert(0, "Person-" + Name);
+ list.Insert(0, GetType().Name + "-" + (Name ?? string.Empty).RemoveDiacritics());
return list;
}
+ public override string PresentationUniqueKey
+ {
+ get
+ {
+ return GetUserDataKeys()[0];
+ }
+ }
+
public PersonLookupInfo GetLookupInfo()
{
return GetItemLookupInfo<PersonLookupInfo>();
diff --git a/MediaBrowser.Controller/Entities/Photo.cs b/MediaBrowser.Controller/Entities/Photo.cs
index 3358ccc6f..de756563d 100644
--- a/MediaBrowser.Controller/Entities/Photo.cs
+++ b/MediaBrowser.Controller/Entities/Photo.cs
@@ -5,7 +5,7 @@ using System.Runtime.Serialization;
namespace MediaBrowser.Controller.Entities
{
- public class Photo : BaseItem, IHasTags, IHasTaglines
+ public class Photo : BaseItem, IHasTaglines
{
public List<string> Taglines { get; set; }
diff --git a/MediaBrowser.Controller/Entities/Studio.cs b/MediaBrowser.Controller/Entities/Studio.cs
index 48ca7bbcc..762798b55 100644
--- a/MediaBrowser.Controller/Entities/Studio.cs
+++ b/MediaBrowser.Controller/Entities/Studio.cs
@@ -2,22 +2,31 @@
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
+using MediaBrowser.Common.Extensions;
namespace MediaBrowser.Controller.Entities
{
/// <summary>
/// Class Studio
/// </summary>
- public class Studio : BaseItem, IItemByName, IHasTags
+ public class Studio : BaseItem, IItemByName
{
public override List<string> GetUserDataKeys()
{
var list = base.GetUserDataKeys();
- list.Insert(0, "Studio-" + Name);
+ list.Insert(0, GetType().Name + "-" + (Name ?? string.Empty).RemoveDiacritics());
return list;
}
+ public override string PresentationUniqueKey
+ {
+ get
+ {
+ return GetUserDataKeys()[0];
+ }
+ }
+
/// <summary>
/// Returns the folder containing the item.
/// If the item is a folder, it returns the folder itself
diff --git a/MediaBrowser.Controller/Entities/TV/Episode.cs b/MediaBrowser.Controller/Entities/TV/Episode.cs
index a4b0b3082..2dc459239 100644
--- a/MediaBrowser.Controller/Entities/TV/Episode.cs
+++ b/MediaBrowser.Controller/Entities/TV/Episode.cs
@@ -11,13 +11,25 @@ namespace MediaBrowser.Controller.Entities.TV
/// <summary>
/// Class Episode
/// </summary>
- public class Episode : Video, IHasLookupInfo<EpisodeInfo>, IHasSeries
- {
- /// <summary>
- /// Gets the season in which it aired.
- /// </summary>
- /// <value>The aired season.</value>
- public int? AirsBeforeSeasonNumber { get; set; }
+ public class Episode : Video, IHasTrailers, IHasLookupInfo<EpisodeInfo>, IHasSeries
+ {
+
+ public Episode()
+ {
+ RemoteTrailers = new List<MediaUrl>();
+ LocalTrailerIds = new List<Guid>();
+ RemoteTrailerIds = new List<Guid>();
+ }
+
+ public List<Guid> LocalTrailerIds { get; set; }
+ public List<Guid> RemoteTrailerIds { get; set; }
+ public List<MediaUrl> RemoteTrailers { get; set; }
+
+ /// <summary>
+ /// Gets the season in which it aired.
+ /// </summary>
+ /// <value>The aired season.</value>
+ public int? AirsBeforeSeasonNumber { get; set; }
public int? AirsAfterSeasonNumber { get; set; }
public int? AirsBeforeEpisodeNumber { get; set; }
@@ -96,7 +108,13 @@ namespace MediaBrowser.Controller.Entities.TV
var series = Series;
if (series != null && ParentIndexNumber.HasValue && IndexNumber.HasValue)
{
- list.InsertRange(0, series.GetUserDataKeys().Select(i => i + ParentIndexNumber.Value.ToString("000") + IndexNumber.Value.ToString("000")));
+ var seriesUserDataKeys = series.GetUserDataKeys();
+ var take = seriesUserDataKeys.Count;
+ if (seriesUserDataKeys.Count > 1)
+ {
+ take--;
+ }
+ list.InsertRange(0, seriesUserDataKeys.Take(take).Select(i => i + ParentIndexNumber.Value.ToString("000") + IndexNumber.Value.ToString("000")));
}
return list;
diff --git a/MediaBrowser.Controller/Entities/TV/Season.cs b/MediaBrowser.Controller/Entities/TV/Season.cs
index 7fa1b55de..9a9014844 100644
--- a/MediaBrowser.Controller/Entities/TV/Season.cs
+++ b/MediaBrowser.Controller/Entities/TV/Season.cs
@@ -75,6 +75,11 @@ namespace MediaBrowser.Controller.Entities.TV
return list;
}
+ public override int GetChildCount(User user)
+ {
+ return GetChildren(user, true).Count();
+ }
+
/// <summary>
/// This Episode's Series Instance
/// </summary>
@@ -128,39 +133,16 @@ namespace MediaBrowser.Controller.Entities.TV
return IndexNumber != null ? IndexNumber.Value.ToString("0000") : Name;
}
- public override bool RequiresRefresh()
- {
- var result = base.RequiresRefresh();
-
- if (!result)
- {
- if (!IsVirtualItem.HasValue)
- {
- return true;
- }
- }
-
- return result;
- }
-
- [IgnoreDataMember]
- public bool? IsVirtualItem { get; set; }
-
[IgnoreDataMember]
public bool IsMissingSeason
{
- get { return (IsVirtualItem ?? DetectIsVirtualItem()) && !IsUnaired; }
+ get { return (IsVirtualItem) && !IsUnaired; }
}
[IgnoreDataMember]
public bool IsVirtualUnaired
{
- get { return (IsVirtualItem ?? DetectIsVirtualItem()) && IsUnaired; }
- }
-
- private bool DetectIsVirtualItem()
- {
- return LocationType == LocationType.Virtual && GetEpisodes().All(i => i.LocationType == LocationType.Virtual);
+ get { return (IsVirtualItem) && IsUnaired; }
}
[IgnoreDataMember]
@@ -196,52 +178,17 @@ namespace MediaBrowser.Controller.Entities.TV
{
var config = user.Configuration;
- return GetEpisodes(user, config.DisplayMissingEpisodes, config.DisplayUnairedEpisodes);
+ return GetEpisodes(Series, user, config.DisplayMissingEpisodes, config.DisplayUnairedEpisodes);
}
- public IEnumerable<Episode> GetEpisodes(User user, bool includeMissingEpisodes, bool includeVirtualUnairedEpisodes)
+ public IEnumerable<Episode> GetEpisodes(Series series, User user, bool includeMissingEpisodes, bool includeVirtualUnairedEpisodes)
{
- var series = Series;
-
- if (IndexNumber.HasValue && series != null)
- {
- return series.GetEpisodes(user, this, includeMissingEpisodes, includeVirtualUnairedEpisodes);
- }
-
- var episodes = GetRecursiveChildren(user)
- .OfType<Episode>();
-
- if (series != null && series.ContainsEpisodesWithoutSeasonFolders)
- {
- var seasonNumber = IndexNumber;
- var list = episodes.ToList();
-
- if (seasonNumber.HasValue)
- {
- list.AddRange(series.GetRecursiveChildren(user).OfType<Episode>()
- .Where(i => i.ParentIndexNumber.HasValue && i.ParentIndexNumber.Value == seasonNumber.Value));
- }
- else
- {
- list.AddRange(series.GetRecursiveChildren(user).OfType<Episode>()
- .Where(i => !i.ParentIndexNumber.HasValue));
- }
-
- episodes = list.DistinctBy(i => i.Id);
- }
-
- if (!includeMissingEpisodes)
- {
- episodes = episodes.Where(i => !i.IsMissingEpisode);
- }
- if (!includeVirtualUnairedEpisodes)
- {
- episodes = episodes.Where(i => !i.IsVirtualUnaired);
- }
+ return GetEpisodes(series, user, includeMissingEpisodes, includeVirtualUnairedEpisodes, null);
+ }
- return LibraryManager
- .Sort(episodes, user, new[] { ItemSortBy.SortName }, SortOrder.Ascending)
- .Cast<Episode>();
+ public IEnumerable<Episode> GetEpisodes(Series series, User user, bool includeMissingEpisodes, bool includeVirtualUnairedEpisodes, IEnumerable<Episode> allSeriesEpisodes)
+ {
+ return series.GetEpisodes(user, this, includeMissingEpisodes, includeVirtualUnairedEpisodes, allSeriesEpisodes);
}
public IEnumerable<Episode> GetEpisodes()
diff --git a/MediaBrowser.Controller/Entities/TV/Series.cs b/MediaBrowser.Controller/Entities/TV/Series.cs
index 6c499f618..1cc496b35 100644
--- a/MediaBrowser.Controller/Entities/TV/Series.cs
+++ b/MediaBrowser.Controller/Entities/TV/Series.cs
@@ -9,6 +9,7 @@ using System.Linq;
using System.Runtime.Serialization;
using System.Threading;
using System.Threading.Tasks;
+using MediaBrowser.Model.Providers;
using MoreLinq;
namespace MediaBrowser.Controller.Entities.TV
@@ -30,7 +31,6 @@ namespace MediaBrowser.Controller.Entities.TV
RemoteTrailers = new List<MediaUrl>();
LocalTrailerIds = new List<Guid>();
RemoteTrailerIds = new List<Guid>();
- DisplaySpecialsWithSeasons = true;
}
[IgnoreDataMember]
@@ -57,8 +57,6 @@ namespace MediaBrowser.Controller.Entities.TV
}
}
- public bool DisplaySpecialsWithSeasons { get; set; }
-
public List<Guid> LocalTrailerIds { get; set; }
public List<Guid> RemoteTrailerIds { get; set; }
@@ -94,10 +92,7 @@ namespace MediaBrowser.Controller.Entities.TV
{
get
{
- return GetRecursiveChildren(i => i is Episode)
- .Select(i => i.DateCreated)
- .OrderByDescending(i => i)
- .FirstOrDefault();
+ return DateLastMediaAdded ?? DateTime.MinValue;
}
}
@@ -106,14 +101,30 @@ namespace MediaBrowser.Controller.Entities.TV
{
get
{
- if (EnablePooling())
+ var userdatakeys = GetUserDataKeys();
+
+ if (userdatakeys.Count > 1)
{
- return GetUserDataKeys().First();
+ return userdatakeys[0];
}
return base.PresentationUniqueKey;
}
}
+ public override int GetChildCount(User user)
+ {
+ var result = LibraryManager.GetItemsResult(new InternalItemsQuery(user)
+ {
+ AncestorWithPresentationUniqueKey = PresentationUniqueKey,
+ IncludeItemTypes = new[] { typeof(Season).Name },
+ SortBy = new[] { ItemSortBy.SortName },
+ IsVirtualItem = false,
+ Limit = 0
+ });
+
+ return result.TotalRecordCount;
+ }
+
/// <summary>
/// Gets the user data key.
/// </summary>
@@ -182,27 +193,32 @@ namespace MediaBrowser.Controller.Entities.TV
protected override Task<QueryResult<BaseItem>> GetItemsInternal(InternalItemsQuery query)
{
- var user = query.User;
-
- Func<BaseItem, bool> filter = i => UserViewBuilder.Filter(i, user, query, UserDataManager, LibraryManager);
-
- IEnumerable<BaseItem> items;
-
if (query.User == null)
{
- items = query.Recursive
- ? GetRecursiveChildren(filter)
- : Children.Where(filter);
+ return base.GetItemsInternal(query);
}
- else
+
+ var user = query.User;
+
+ if (query.Recursive)
{
- items = query.Recursive
- ? GetSeasons(user).Cast<BaseItem>().Concat(GetEpisodes(user)).Where(filter)
- : GetSeasons(user).Where(filter);
+ query.AncestorWithPresentationUniqueKey = PresentationUniqueKey;
+ if (query.SortBy.Length == 0)
+ {
+ query.SortBy = new[] { ItemSortBy.SortName };
+ }
+ if (query.IncludeItemTypes.Length == 0)
+ {
+ query.IncludeItemTypes = new[] { typeof(Episode).Name, typeof(Season).Name };
+ }
+ query.IsVirtualItem = false;
+ return Task.FromResult(LibraryManager.GetItemsResult(query));
}
- var result = PostFilterAndSort(items, query);
+ Func<BaseItem, bool> filter = i => UserViewBuilder.Filter(i, user, query, UserDataManager, LibraryManager);
+ var items = GetSeasons(user).Where(filter);
+ var result = PostFilterAndSort(items, query);
return Task.FromResult(result);
}
@@ -210,33 +226,13 @@ namespace MediaBrowser.Controller.Entities.TV
{
IEnumerable<Season> seasons;
- if (EnablePooling())
+ seasons = LibraryManager.GetItemList(new InternalItemsQuery(user)
{
- var seriesIds = LibraryManager.GetItemIds(new InternalItemsQuery(user)
- {
- PresentationUniqueKey = PresentationUniqueKey,
- IncludeItemTypes = new[] { typeof(Series).Name }
- });
+ AncestorWithPresentationUniqueKey = PresentationUniqueKey,
+ IncludeItemTypes = new[] { typeof(Season).Name },
+ SortBy = new[] { ItemSortBy.SortName }
- if (seriesIds.Count > 1)
- {
- seasons = LibraryManager.GetItemList(new InternalItemsQuery(user)
- {
- AncestorIds = seriesIds.Select(i => i.ToString("N")).ToArray(),
- IncludeItemTypes = new[] { typeof(Season).Name },
- SortBy = new[] { ItemSortBy.SortName }
-
- }).Cast<Season>();
- }
- else
- {
- seasons = LibraryManager.Sort(base.GetChildren(user, true), user, new[] { ItemSortBy.SortName }, SortOrder.Ascending).OfType<Season>();
- }
- }
- else
- {
- seasons = LibraryManager.Sort(base.GetChildren(user, true), user, new[] { ItemSortBy.SortName }, SortOrder.Ascending).OfType<Season>();
- }
+ }).Cast<Season>();
if (!includeMissingSeasons)
{
@@ -259,8 +255,18 @@ namespace MediaBrowser.Controller.Entities.TV
public IEnumerable<Episode> GetEpisodes(User user, bool includeMissing, bool includeVirtualUnaired)
{
- var allEpisodes = GetSeasons(user, true, true)
- .SelectMany(i => i.GetEpisodes(user, includeMissing, includeVirtualUnaired))
+ var allItems = LibraryManager.GetItemList(new InternalItemsQuery(user)
+ {
+ AncestorWithPresentationUniqueKey = PresentationUniqueKey,
+ IncludeItemTypes = new[] { typeof(Episode).Name, typeof(Season).Name },
+ SortBy = new[] { ItemSortBy.SortName }
+
+ }).ToList();
+
+ var allSeriesEpisodes = allItems.OfType<Episode>().ToList();
+
+ var allEpisodes = allItems.OfType<Season>()
+ .SelectMany(i => i.GetEpisodes(this, user, includeMissing, includeVirtualUnaired, allSeriesEpisodes))
.Reverse()
.ToList();
@@ -283,9 +289,6 @@ namespace MediaBrowser.Controller.Entities.TV
var totalItems = seasons.Count + otherItems.Count;
var numComplete = 0;
- refreshOptions = new MetadataRefreshOptions(refreshOptions);
- refreshOptions.IsPostRecursiveRefresh = true;
-
// Refresh current item
await RefreshMetadata(refreshOptions, cancellationToken).ConfigureAwait(false);
@@ -315,7 +318,7 @@ namespace MediaBrowser.Controller.Entities.TV
&& refreshOptions.MetadataRefreshMode != MetadataRefreshMode.FullRefresh
&& !refreshOptions.ReplaceAllMetadata
&& episode.IsMissingEpisode
- && episode.LocationType == Model.Entities.LocationType.Virtual
+ && episode.LocationType == LocationType.Virtual
&& episode.PremiereDate.HasValue
&& (DateTime.UtcNow - episode.PremiereDate.Value).TotalDays > 30)
{
@@ -333,6 +336,8 @@ namespace MediaBrowser.Controller.Entities.TV
progress.Report(percent * 100);
}
+ refreshOptions = new MetadataRefreshOptions(refreshOptions);
+ refreshOptions.IsPostRecursiveRefresh = true;
await ProviderManager.RefreshSingleItem(this, refreshOptions, cancellationToken).ConfigureAwait(false);
progress.Report(100);
@@ -345,50 +350,32 @@ namespace MediaBrowser.Controller.Entities.TV
return GetEpisodes(user, season, config.DisplayMissingEpisodes, config.DisplayUnairedEpisodes);
}
- private bool EnablePooling()
+ private IEnumerable<Episode> GetAllEpisodes(User user)
{
- return false;
+ return LibraryManager.GetItemList(new InternalItemsQuery(user)
+ {
+ AncestorWithPresentationUniqueKey = PresentationUniqueKey,
+ IncludeItemTypes = new[] { typeof(Episode).Name },
+ SortBy = new[] { ItemSortBy.SortName }
+
+ }).Cast<Episode>();
}
public IEnumerable<Episode> GetEpisodes(User user, Season parentSeason, bool includeMissingEpisodes, bool includeVirtualUnairedEpisodes)
{
- IEnumerable<Episode> episodes;
-
- if (EnablePooling())
- {
- var seriesIds = LibraryManager.GetItemIds(new InternalItemsQuery(user)
- {
- PresentationUniqueKey = PresentationUniqueKey,
- IncludeItemTypes = new[] { typeof(Series).Name }
- });
+ IEnumerable<Episode> episodes = GetAllEpisodes(user);
- if (seriesIds.Count > 1)
- {
- episodes = LibraryManager.GetItemList(new InternalItemsQuery(user)
- {
- AncestorIds = seriesIds.Select(i => i.ToString("N")).ToArray(),
- IncludeItemTypes = new[] { typeof(Episode).Name },
- SortBy = new[] { ItemSortBy.SortName }
+ return GetEpisodes(user, parentSeason, includeMissingEpisodes, includeVirtualUnairedEpisodes, episodes);
+ }
- }).Cast<Episode>();
- }
- else
- {
- episodes = GetRecursiveChildren(user, new InternalItemsQuery(user)
- {
- IncludeItemTypes = new[] { typeof(Episode).Name }
- }).Cast<Episode>();
- }
- }
- else
+ public IEnumerable<Episode> GetEpisodes(User user, Season parentSeason, bool includeMissingEpisodes, bool includeVirtualUnairedEpisodes, IEnumerable<Episode> allSeriesEpisodes)
+ {
+ if (allSeriesEpisodes == null)
{
- episodes = GetRecursiveChildren(user, new InternalItemsQuery(user)
- {
- IncludeItemTypes = new[] { typeof(Episode).Name }
- }).Cast<Episode>();
+ return GetEpisodes(user, parentSeason, includeMissingEpisodes, includeVirtualUnairedEpisodes);
}
- episodes = FilterEpisodesBySeason(episodes, parentSeason, DisplaySpecialsWithSeasons);
+ var episodes = FilterEpisodesBySeason(allSeriesEpisodes, parentSeason, ConfigurationManager.Configuration.DisplaySpecialsWithinSeasons);
if (!includeMissingEpisodes)
{
@@ -436,38 +423,31 @@ namespace MediaBrowser.Controller.Entities.TV
public static IEnumerable<Episode> FilterEpisodesBySeason(IEnumerable<Episode> episodes, Season parentSeason, bool includeSpecials)
{
var seasonNumber = parentSeason.IndexNumber;
- if (!includeSpecials || (seasonNumber.HasValue && seasonNumber.Value == 0))
- {
- var seasonPresentationKey = parentSeason.PresentationUniqueKey;
+ var seasonPresentationKey = parentSeason.PresentationUniqueKey;
- return episodes.Where(i =>
- {
- if ((i.ParentIndexNumber ?? -1) == seasonNumber)
- {
- return true;
- }
+ var supportSpecialsInSeason = includeSpecials && seasonNumber.HasValue && seasonNumber.Value != 0;
- var season = i.Season;
- return season != null && string.Equals(season.PresentationUniqueKey, seasonPresentationKey, StringComparison.OrdinalIgnoreCase);
- });
- }
- else
+ return episodes.Where(episode =>
{
- var seasonPresentationKey = parentSeason.PresentationUniqueKey;
-
- return episodes.Where(episode =>
+ var currentSeasonNumber = supportSpecialsInSeason ? episode.AiredSeasonNumber : episode.ParentIndexNumber;
+ if (currentSeasonNumber.HasValue && seasonNumber.HasValue && currentSeasonNumber.Value == seasonNumber.Value)
{
- var currentSeasonNumber = episode.AiredSeasonNumber;
+ return true;
+ }
- if (currentSeasonNumber.HasValue && seasonNumber.HasValue && currentSeasonNumber.Value == seasonNumber.Value)
- {
- return true;
- }
+ if (!currentSeasonNumber.HasValue && !seasonNumber.HasValue && parentSeason.LocationType == LocationType.Virtual)
+ {
+ return true;
+ }
+ if (!episode.ParentIndexNumber.HasValue)
+ {
var season = episode.Season;
return season != null && string.Equals(season.PresentationUniqueKey, seasonPresentationKey, StringComparison.OrdinalIgnoreCase);
- });
- }
+ }
+
+ return false;
+ });
}
protected override bool GetBlockUnratedValue(UserPolicy config)
@@ -508,5 +488,22 @@ namespace MediaBrowser.Controller.Entities.TV
return hasChanges;
}
+
+ public override List<ExternalUrl> GetRelatedUrls()
+ {
+ var list = base.GetRelatedUrls();
+
+ var imdbId = this.GetProviderId(MetadataProviders.Imdb);
+ if (!string.IsNullOrWhiteSpace(imdbId))
+ {
+ list.Add(new ExternalUrl
+ {
+ Name = "Trakt",
+ Url = string.Format("https://trakt.tv/shows/{0}", imdbId)
+ });
+ }
+
+ return list;
+ }
}
}
diff --git a/MediaBrowser.Controller/Entities/IHasTags.cs b/MediaBrowser.Controller/Entities/TagExtensions.cs
index 45a56009d..0e1df72cd 100644
--- a/MediaBrowser.Controller/Entities/IHasTags.cs
+++ b/MediaBrowser.Controller/Entities/TagExtensions.cs
@@ -1,24 +1,11 @@
using System;
-using System.Collections.Generic;
using System.Linq;
namespace MediaBrowser.Controller.Entities
{
- /// <summary>
- /// Interface IHasTags
- /// </summary>
- public interface IHasTags
- {
- /// <summary>
- /// Gets or sets the tags.
- /// </summary>
- /// <value>The tags.</value>
- List<string> Tags { get; set; }
- }
-
public static class TagExtensions
{
- public static void AddTag(this IHasTags item, string name)
+ public static void AddTag(this BaseItem item, string name)
{
if (string.IsNullOrWhiteSpace(name))
{
diff --git a/MediaBrowser.Controller/Entities/Trailer.cs b/MediaBrowser.Controller/Entities/Trailer.cs
index 3be2fc624..eab5ab679 100644
--- a/MediaBrowser.Controller/Entities/Trailer.cs
+++ b/MediaBrowser.Controller/Entities/Trailer.cs
@@ -5,13 +5,14 @@ using System.Collections.Generic;
using System.Globalization;
using System.Runtime.Serialization;
using MediaBrowser.Controller.Entities.Movies;
+using MediaBrowser.Model.Providers;
namespace MediaBrowser.Controller.Entities
{
/// <summary>
/// Class Trailer
/// </summary>
- public class Trailer : Video, IHasCriticRating, IHasProductionLocations, IHasBudget, IHasKeywords, IHasTaglines, IHasMetascore, IHasOriginalTitle, IHasLookupInfo<TrailerInfo>
+ public class Trailer : Video, IHasCriticRating, IHasProductionLocations, IHasBudget, IHasTaglines, IHasMetascore, IHasOriginalTitle, IHasLookupInfo<TrailerInfo>
{
public List<string> ProductionLocations { get; set; }
@@ -30,8 +31,6 @@ namespace MediaBrowser.Controller.Entities
public List<MediaUrl> RemoteTrailers { get; set; }
- public List<string> Keywords { get; set; }
-
[IgnoreDataMember]
public bool IsLocalTrailer
{
@@ -110,5 +109,22 @@ namespace MediaBrowser.Controller.Entities
return hasChanges;
}
+
+ public override List<ExternalUrl> GetRelatedUrls()
+ {
+ var list = base.GetRelatedUrls();
+
+ var imdbId = this.GetProviderId(MetadataProviders.Imdb);
+ if (!string.IsNullOrWhiteSpace(imdbId))
+ {
+ list.Add(new ExternalUrl
+ {
+ Name = "Trakt",
+ Url = string.Format("https://trakt.tv/movies/{0}", imdbId)
+ });
+ }
+
+ return list;
+ }
}
}
diff --git a/MediaBrowser.Controller/Entities/UserRootFolder.cs b/MediaBrowser.Controller/Entities/UserRootFolder.cs
index 37c4c91b1..8e6f11c2c 100644
--- a/MediaBrowser.Controller/Entities/UserRootFolder.cs
+++ b/MediaBrowser.Controller/Entities/UserRootFolder.cs
@@ -38,6 +38,11 @@ namespace MediaBrowser.Controller.Entities
return PostFilterAndSort(result.Where(filter), query);
}
+ public override int GetChildCount(User user)
+ {
+ return GetChildren(user, true).Count();
+ }
+
[IgnoreDataMember]
protected override bool SupportsShortcutChildren
{
diff --git a/MediaBrowser.Controller/Entities/UserView.cs b/MediaBrowser.Controller/Entities/UserView.cs
index e40d9ca38..35375e7e6 100644
--- a/MediaBrowser.Controller/Entities/UserView.cs
+++ b/MediaBrowser.Controller/Entities/UserView.cs
@@ -45,6 +45,11 @@ namespace MediaBrowser.Controller.Entities
return list;
}
+ public override int GetChildCount(User user)
+ {
+ return GetChildren(user, true).Count();
+ }
+
protected override Task<QueryResult<BaseItem>> GetItemsInternal(InternalItemsQuery query)
{
var parent = this as Folder;
@@ -58,7 +63,7 @@ namespace MediaBrowser.Controller.Entities
parent = LibraryManager.GetItemById(ParentId) as Folder ?? parent;
}
- return new UserViewBuilder(UserViewManager, LiveTvManager, ChannelManager, LibraryManager, Logger, UserDataManager, TVSeriesManager, CollectionManager, PlaylistManager)
+ return new UserViewBuilder(UserViewManager, LiveTvManager, ChannelManager, LibraryManager, Logger, UserDataManager, TVSeriesManager, ConfigurationManager, PlaylistManager)
.GetUserItems(parent, this, ViewType, query);
}
diff --git a/MediaBrowser.Controller/Entities/UserViewBuilder.cs b/MediaBrowser.Controller/Entities/UserViewBuilder.cs
index d4a8b0730..175a7240c 100644
--- a/MediaBrowser.Controller/Entities/UserViewBuilder.cs
+++ b/MediaBrowser.Controller/Entities/UserViewBuilder.cs
@@ -18,6 +18,8 @@ using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
+using MediaBrowser.Controller.Configuration;
+using MediaBrowser.Model.Configuration;
namespace MediaBrowser.Controller.Entities
{
@@ -30,10 +32,10 @@ namespace MediaBrowser.Controller.Entities
private readonly ILogger _logger;
private readonly IUserDataManager _userDataManager;
private readonly ITVSeriesManager _tvSeriesManager;
- private readonly ICollectionManager _collectionManager;
+ private readonly IServerConfigurationManager _config;
private readonly IPlaylistManager _playlistManager;
- public UserViewBuilder(IUserViewManager userViewManager, ILiveTvManager liveTvManager, IChannelManager channelManager, ILibraryManager libraryManager, ILogger logger, IUserDataManager userDataManager, ITVSeriesManager tvSeriesManager, ICollectionManager collectionManager, IPlaylistManager playlistManager)
+ public UserViewBuilder(IUserViewManager userViewManager, ILiveTvManager liveTvManager, IChannelManager channelManager, ILibraryManager libraryManager, ILogger logger, IUserDataManager userDataManager, ITVSeriesManager tvSeriesManager, IServerConfigurationManager config, IPlaylistManager playlistManager)
{
_userViewManager = userViewManager;
_liveTvManager = liveTvManager;
@@ -42,7 +44,7 @@ namespace MediaBrowser.Controller.Entities
_logger = logger;
_userDataManager = userDataManager;
_tvSeriesManager = tvSeriesManager;
- _collectionManager = collectionManager;
+ _config = config;
_playlistManager = playlistManager;
}
@@ -159,7 +161,7 @@ namespace MediaBrowser.Controller.Entities
return await GetTvGenres(queryParent, user, query).ConfigureAwait(false);
case SpecialFolder.TvGenre:
- return await GetTvGenreItems(queryParent, displayParent, user, query).ConfigureAwait(false);
+ return GetTvGenreItems(queryParent, displayParent, user, query);
case SpecialFolder.TvResume:
return GetTvResume(queryParent, user, query);
@@ -332,13 +334,14 @@ namespace MediaBrowser.Controller.Entities
private QueryResult<BaseItem> GetMusicAlbumArtists(Folder parent, User user, InternalItemsQuery query)
{
- var items = _libraryManager.GetItemList(new InternalItemsQuery(user)
+ var items = parent.QueryRecursive(new InternalItemsQuery(user)
{
Recursive = true,
ParentId = parent.Id,
- IncludeItemTypes = new[] { typeof(Audio.Audio).Name }
+ IncludeItemTypes = new[] { typeof(Audio.Audio).Name },
+ EnableTotalRecordCount = false
- }).Cast<IHasAlbumArtist>();
+ }).Items.Cast<IHasAlbumArtist>();
var artists = _libraryManager.GetAlbumArtists(items);
@@ -347,13 +350,14 @@ namespace MediaBrowser.Controller.Entities
private QueryResult<BaseItem> GetMusicArtists(Folder parent, User user, InternalItemsQuery query)
{
- var items = _libraryManager.GetItemList(new InternalItemsQuery(user)
+ var items = parent.QueryRecursive(new InternalItemsQuery(user)
{
Recursive = true,
ParentId = parent.Id,
- IncludeItemTypes = new[] { typeof(Audio.Audio).Name, typeof(MusicVideo).Name }
+ IncludeItemTypes = new[] { typeof(Audio.Audio).Name, typeof(MusicVideo).Name },
+ EnableTotalRecordCount = false
- }).Cast<IHasArtist>();
+ }).Items.Cast<IHasArtist>();
var artists = _libraryManager.GetArtists(items);
@@ -362,13 +366,14 @@ namespace MediaBrowser.Controller.Entities
private QueryResult<BaseItem> GetFavoriteArtists(Folder parent, User user, InternalItemsQuery query)
{
- var items = _libraryManager.GetItemList(new InternalItemsQuery(user)
+ var items = parent.QueryRecursive(new InternalItemsQuery(user)
{
Recursive = true,
ParentId = parent.Id,
- IncludeItemTypes = new[] { typeof(Audio.Audio).Name }
+ IncludeItemTypes = new[] { typeof(Audio.Audio).Name },
+ EnableTotalRecordCount = false
- }).Cast<IHasAlbumArtist>();
+ }).Items.Cast<IHasAlbumArtist>();
var artists = _libraryManager.GetAlbumArtists(items).Where(i => _userDataManager.GetUserData(user, i).IsFavorite);
@@ -545,7 +550,7 @@ namespace MediaBrowser.Controller.Entities
query.Limit = GetSpecialItemsLimit();
query.IncludeItemTypes = new[] { typeof(Movie).Name };
- return _libraryManager.GetItemsResult(query);
+ return ConvertToResult(_libraryManager.GetItemList(query));
}
private QueryResult<BaseItem> GetMovieResume(Folder parent, User user, InternalItemsQuery query)
@@ -559,7 +564,17 @@ namespace MediaBrowser.Controller.Entities
query.Limit = GetSpecialItemsLimit();
query.IncludeItemTypes = new[] { typeof(Movie).Name };
- return _libraryManager.GetItemsResult(query);
+ return ConvertToResult(_libraryManager.GetItemList(query));
+ }
+
+ private QueryResult<BaseItem> ConvertToResult(IEnumerable<BaseItem> items)
+ {
+ var arr = items.ToArray();
+ return new QueryResult<BaseItem>
+ {
+ Items = arr,
+ TotalRecordCount = arr.Length
+ };
}
private async Task<QueryResult<BaseItem>> GetMovieGenres(Folder parent, User user, InternalItemsQuery query)
@@ -660,8 +675,9 @@ namespace MediaBrowser.Controller.Entities
query.SetUser(user);
query.Limit = GetSpecialItemsLimit();
query.IncludeItemTypes = new[] { typeof(Episode).Name };
+ query.ExcludeLocationTypes = new[] { LocationType.Virtual };
- return _libraryManager.GetItemsResult(query);
+ return ConvertToResult(_libraryManager.GetItemList(query));
}
private QueryResult<BaseItem> GetTvNextUp(Folder parent, InternalItemsQuery query)
@@ -690,7 +706,7 @@ namespace MediaBrowser.Controller.Entities
query.Limit = GetSpecialItemsLimit();
query.IncludeItemTypes = new[] { typeof(Episode).Name };
- return _libraryManager.GetItemsResult(query);
+ return ConvertToResult(_libraryManager.GetItemList(query));
}
private QueryResult<BaseItem> GetTvSeries(Folder parent, User user, InternalItemsQuery query)
@@ -737,7 +753,7 @@ namespace MediaBrowser.Controller.Entities
return GetResult(genres, parent, query);
}
- private async Task<QueryResult<BaseItem>> GetTvGenreItems(Folder queryParent, Folder displayParent, User user, InternalItemsQuery query)
+ private QueryResult<BaseItem> GetTvGenreItems(Folder queryParent, Folder displayParent, User user, InternalItemsQuery query)
{
query.Recursive = true;
query.ParentId = queryParent.Id;
@@ -766,7 +782,7 @@ namespace MediaBrowser.Controller.Entities
{
items = items.Where(i => Filter(i, query.User, query, _userDataManager, _libraryManager));
- return PostFilterAndSort(items, queryParent, null, query, _libraryManager);
+ return PostFilterAndSort(items, queryParent, null, query, _libraryManager, _config);
}
public static bool FilterItem(BaseItem item, InternalItemsQuery query)
@@ -779,14 +795,15 @@ namespace MediaBrowser.Controller.Entities
int? totalRecordLimit,
InternalItemsQuery query)
{
- return PostFilterAndSort(items, queryParent, totalRecordLimit, query, _libraryManager);
+ return PostFilterAndSort(items, queryParent, totalRecordLimit, query, _libraryManager, _config);
}
public static QueryResult<BaseItem> PostFilterAndSort(IEnumerable<BaseItem> items,
BaseItem queryParent,
int? totalRecordLimit,
InternalItemsQuery query,
- ILibraryManager libraryManager)
+ ILibraryManager libraryManager,
+ IServerConfigurationManager configurationManager)
{
var user = query.User;
@@ -795,7 +812,7 @@ namespace MediaBrowser.Controller.Entities
query.IsVirtualUnaired,
query.IsUnaired);
- items = CollapseBoxSetItemsIfNeeded(items, query, queryParent, user);
+ items = CollapseBoxSetItemsIfNeeded(items, query, queryParent, user, configurationManager);
// This must be the last filter
if (!string.IsNullOrEmpty(query.AdjacentTo))
@@ -809,14 +826,15 @@ namespace MediaBrowser.Controller.Entities
public static IEnumerable<BaseItem> CollapseBoxSetItemsIfNeeded(IEnumerable<BaseItem> items,
InternalItemsQuery query,
BaseItem queryParent,
- User user)
+ User user,
+ IServerConfigurationManager configurationManager)
{
if (items == null)
{
throw new ArgumentNullException("items");
}
- if (CollapseBoxSetItems(query, queryParent, user))
+ if (CollapseBoxSetItems(query, queryParent, user, configurationManager))
{
items = BaseItem.CollectionManager.CollapseItemsWithinBoxSets(items, user);
}
@@ -849,7 +867,8 @@ namespace MediaBrowser.Controller.Entities
public static bool CollapseBoxSetItems(InternalItemsQuery query,
BaseItem queryParent,
- User user)
+ User user,
+ IServerConfigurationManager configurationManager)
{
// Could end up stuck in a loop like this
if (queryParent is BoxSet)
@@ -861,7 +880,7 @@ namespace MediaBrowser.Controller.Entities
if (!param.HasValue)
{
- if (user != null && !user.Configuration.GroupMoviesIntoBoxSets)
+ if (user != null && !configurationManager.Configuration.EnableGroupingIntoCollections)
{
return false;
}
@@ -992,11 +1011,6 @@ namespace MediaBrowser.Controller.Entities
return false;
}
- if (request.IsYearMismatched.HasValue)
- {
- return false;
- }
-
if (!string.IsNullOrWhiteSpace(request.Person))
{
return false;
@@ -1415,16 +1429,6 @@ namespace MediaBrowser.Controller.Entities
}
}
- if (query.IsYearMismatched.HasValue)
- {
- var filterValue = query.IsYearMismatched.Value;
-
- if (IsYearMismatched(item, libraryManager) != filterValue)
- {
- return false;
- }
- }
-
if (query.HasOfficialRating.HasValue)
{
var filterValue = query.HasOfficialRating.Value;
@@ -1658,12 +1662,7 @@ namespace MediaBrowser.Controller.Entities
var tags = query.Tags;
if (tags.Length > 0)
{
- var hasTags = item as IHasTags;
- if (hasTags == null)
- {
- return false;
- }
- if (!tags.Any(v => hasTags.Tags.Contains(v, StringComparer.OrdinalIgnoreCase)))
+ if (!tags.Any(v => item.Tags.Contains(v, StringComparer.OrdinalIgnoreCase)))
{
return false;
}
@@ -1976,34 +1975,6 @@ namespace MediaBrowser.Controller.Entities
return _userViewManager.GetUserSubView(parent.Id.ToString("N"), type, sortName, CancellationToken.None);
}
- public static bool IsYearMismatched(BaseItem item, ILibraryManager libraryManager)
- {
- if (item.ProductionYear.HasValue)
- {
- var path = item.Path;
-
- if (!string.IsNullOrEmpty(path))
- {
- var info = libraryManager.ParseName(Path.GetFileName(path));
- var yearInName = info.Year;
-
- // Go up a level if we didn't get a year
- if (!yearInName.HasValue)
- {
- info = libraryManager.ParseName(Path.GetFileName(Path.GetDirectoryName(path)));
- yearInName = info.Year;
- }
-
- if (yearInName.HasValue)
- {
- return yearInName.Value != item.ProductionYear.Value;
- }
- }
- }
-
- return false;
- }
-
public static IEnumerable<BaseItem> FilterForAdjacency(IEnumerable<BaseItem> items, string adjacentToId)
{
var list = items.ToList();
diff --git a/MediaBrowser.Controller/Entities/Video.cs b/MediaBrowser.Controller/Entities/Video.cs
index 6a9d7cb51..73c893dd6 100644
--- a/MediaBrowser.Controller/Entities/Video.cs
+++ b/MediaBrowser.Controller/Entities/Video.cs
@@ -21,7 +21,6 @@ namespace MediaBrowser.Controller.Entities
/// </summary>
public class Video : BaseItem,
IHasAspectRatio,
- IHasTags,
ISupportsPlaceHolders,
IHasMediaSources,
IHasShortOverview,
@@ -59,10 +58,7 @@ namespace MediaBrowser.Controller.Entities
}
}
- public long? Size { get; set; }
- public string Container { get; set; }
public int? TotalBitrate { get; set; }
- public string ShortOverview { get; set; }
public ExtraType? ExtraType { get; set; }
/// <summary>
diff --git a/MediaBrowser.Controller/IServerApplicationHost.cs b/MediaBrowser.Controller/IServerApplicationHost.cs
index 65eed1a23..f4c0e7658 100644
--- a/MediaBrowser.Controller/IServerApplicationHost.cs
+++ b/MediaBrowser.Controller/IServerApplicationHost.cs
@@ -3,6 +3,7 @@ using MediaBrowser.Model.System;
using System;
using System.Collections.Generic;
using System.Net;
+using System.Threading.Tasks;
namespace MediaBrowser.Controller
{
@@ -17,7 +18,7 @@ namespace MediaBrowser.Controller
/// Gets the system info.
/// </summary>
/// <returns>SystemInfo.</returns>
- SystemInfo GetSystemInfo();
+ Task<SystemInfo> GetSystemInfo();
/// <summary>
/// Gets a value indicating whether [supports automatic run at startup].
@@ -65,13 +66,13 @@ namespace MediaBrowser.Controller
/// Gets the local ip address.
/// </summary>
/// <value>The local ip address.</value>
- List<IPAddress> LocalIpAddresses { get; }
+ Task<List<IPAddress>> GetLocalIpAddresses();
/// <summary>
/// Gets the local API URL.
/// </summary>
/// <value>The local API URL.</value>
- string LocalApiUrl { get; }
+ Task<string> GetLocalApiUrl();
/// <summary>
/// Gets the local API URL.
diff --git a/MediaBrowser.Controller/Library/ILibraryManager.cs b/MediaBrowser.Controller/Library/ILibraryManager.cs
index 936b97c6e..4e641b005 100644
--- a/MediaBrowser.Controller/Library/ILibraryManager.cs
+++ b/MediaBrowser.Controller/Library/ILibraryManager.cs
@@ -11,6 +11,7 @@ using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using CommonIO;
+using MediaBrowser.Model.Dto;
namespace MediaBrowser.Controller.Library
{
@@ -151,13 +152,6 @@ namespace MediaBrowser.Controller.Library
BaseItem GetItemById(Guid id);
/// <summary>
- /// Gets the memory item by identifier.
- /// </summary>
- /// <param name="id">The identifier.</param>
- /// <returns>BaseItem.</returns>
- BaseItem GetMemoryItemById(Guid id);
-
- /// <summary>
/// Gets the intros.
/// </summary>
/// <param name="item">The item.</param>
@@ -574,5 +568,12 @@ namespace MediaBrowser.Controller.Library
void RemoveVirtualFolder(string name, bool refreshLibrary);
void AddMediaPath(string virtualFolderName, string path);
void RemoveMediaPath(string virtualFolderName, string path);
+
+ QueryResult<Tuple<BaseItem, ItemCounts>> GetGenres(InternalItemsQuery query);
+ QueryResult<Tuple<BaseItem, ItemCounts>> GetMusicGenres(InternalItemsQuery query);
+ QueryResult<Tuple<BaseItem, ItemCounts>> GetGameGenres(InternalItemsQuery query);
+ QueryResult<Tuple<BaseItem, ItemCounts>> GetStudios(InternalItemsQuery query);
+ QueryResult<Tuple<BaseItem, ItemCounts>> GetArtists(InternalItemsQuery query);
+ QueryResult<Tuple<BaseItem, ItemCounts>> GetAlbumArtists(InternalItemsQuery query);
}
} \ No newline at end of file
diff --git a/MediaBrowser.Controller/Library/IUserDataManager.cs b/MediaBrowser.Controller/Library/IUserDataManager.cs
index e2358650b..86c52c4c3 100644
--- a/MediaBrowser.Controller/Library/IUserDataManager.cs
+++ b/MediaBrowser.Controller/Library/IUserDataManager.cs
@@ -40,7 +40,9 @@ namespace MediaBrowser.Controller.Library
/// <param name="item">The item.</param>
/// <param name="user">The user.</param>
/// <returns>UserItemDataDto.</returns>
- UserItemDataDto GetUserDataDto(IHasUserData item, User user);
+ Task<UserItemDataDto> GetUserDataDto(IHasUserData item, User user);
+
+ Task<UserItemDataDto> GetUserDataDto(IHasUserData item, BaseItemDto itemDto, User user);
/// <summary>
/// Get all user data for the given user
diff --git a/MediaBrowser.Controller/LiveTv/IListingsProvider.cs b/MediaBrowser.Controller/LiveTv/IListingsProvider.cs
index f5048bdda..5ecd70cc5 100644
--- a/MediaBrowser.Controller/LiveTv/IListingsProvider.cs
+++ b/MediaBrowser.Controller/LiveTv/IListingsProvider.cs
@@ -15,5 +15,6 @@ namespace MediaBrowser.Controller.LiveTv
Task AddMetadata(ListingsProviderInfo info, List<ChannelInfo> channels, CancellationToken cancellationToken);
Task Validate(ListingsProviderInfo info, bool validateLogin, bool validateListings);
Task<List<NameIdPair>> GetLineups(ListingsProviderInfo info, string country, string location);
+ Task<List<ChannelInfo>> GetChannels(ListingsProviderInfo info, CancellationToken cancellationToken);
}
}
diff --git a/MediaBrowser.Controller/LiveTv/ILiveTvManager.cs b/MediaBrowser.Controller/LiveTv/ILiveTvManager.cs
index a4bd32fff..fe69b38cb 100644
--- a/MediaBrowser.Controller/LiveTv/ILiveTvManager.cs
+++ b/MediaBrowser.Controller/LiveTv/ILiveTvManager.cs
@@ -8,6 +8,7 @@ using MediaBrowser.Model.Querying;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
+using MediaBrowser.Model.Events;
namespace MediaBrowser.Controller.LiveTv
{
@@ -344,6 +345,13 @@ namespace MediaBrowser.Controller.LiveTv
/// <param name="validateListings">if set to <c>true</c> [validate listings].</param>
/// <returns>Task.</returns>
Task<ListingsProviderInfo> SaveListingProvider(ListingsProviderInfo info, bool validateLogin, bool validateListings);
+
+ void DeleteListingsProvider(string id);
+
+ Task<TunerChannelMapping> SetChannelMapping(string providerId, string tunerChannelNumber, string providerChannelNumber);
+
+ TunerChannelMapping GetTunerChannelMapping(ChannelInfo channel, List<NameValuePair> mappings, List<ChannelInfo> providerChannels);
+
/// <summary>
/// Gets the lineups.
/// </summary>
@@ -385,5 +393,15 @@ namespace MediaBrowser.Controller.LiveTv
List<NameValuePair> GetSatIniMappings();
Task<List<ChannelInfo>> GetSatChannelScanResult(TunerHostInfo info, CancellationToken cancellationToken);
+
+ Task<List<ChannelInfo>> GetChannelsForListingsProvider(string id, CancellationToken cancellationToken);
+ Task<List<ChannelInfo>> GetChannelsFromListingsProviderData(string id, CancellationToken cancellationToken);
+
+ List<IListingsProvider> ListingProviders { get;}
+
+ event EventHandler<GenericEventArgs<TimerEventInfo>> SeriesTimerCancelled;
+ event EventHandler<GenericEventArgs<TimerEventInfo>> TimerCancelled;
+ event EventHandler<GenericEventArgs<TimerEventInfo>> TimerCreated;
+ event EventHandler<GenericEventArgs<TimerEventInfo>> SeriesTimerCreated;
}
}
diff --git a/MediaBrowser.Controller/LiveTv/ILiveTvService.cs b/MediaBrowser.Controller/LiveTv/ILiveTvService.cs
index 4ef4847a3..d7d8336d0 100644
--- a/MediaBrowser.Controller/LiveTv/ILiveTvService.cs
+++ b/MediaBrowser.Controller/LiveTv/ILiveTvService.cs
@@ -226,4 +226,23 @@ namespace MediaBrowser.Controller.LiveTv
/// <returns>Task.</returns>
Task ResetTuner(string id, CancellationToken cancellationToken);
}
+
+ public interface ISupportsNewTimerIds
+ {
+ /// <summary>
+ /// Creates the timer asynchronous.
+ /// </summary>
+ /// <param name="info">The information.</param>
+ /// <param name="cancellationToken">The cancellation token.</param>
+ /// <returns>Task.</returns>
+ Task<string> CreateTimer(TimerInfo info, CancellationToken cancellationToken);
+
+ /// <summary>
+ /// Creates the series timer asynchronous.
+ /// </summary>
+ /// <param name="info">The information.</param>
+ /// <param name="cancellationToken">The cancellation token.</param>
+ /// <returns>Task.</returns>
+ Task<string> CreateSeriesTimer(SeriesTimerInfo info, CancellationToken cancellationToken);
+ }
}
diff --git a/MediaBrowser.Controller/LiveTv/LiveTvProgram.cs b/MediaBrowser.Controller/LiveTv/LiveTvProgram.cs
index cc30709db..74c993248 100644
--- a/MediaBrowser.Controller/LiveTv/LiveTvProgram.cs
+++ b/MediaBrowser.Controller/LiveTv/LiveTvProgram.cs
@@ -7,6 +7,7 @@ using System;
using System.Collections.Generic;
using System.Runtime.Serialization;
using MediaBrowser.Model.Entities;
+using MediaBrowser.Model.Providers;
namespace MediaBrowser.Controller.LiveTv
{
@@ -235,5 +236,25 @@ namespace MediaBrowser.Controller.LiveTv
return false;
}
}
+
+ public override List<ExternalUrl> GetRelatedUrls()
+ {
+ var list = base.GetRelatedUrls();
+
+ var imdbId = this.GetProviderId(MetadataProviders.Imdb);
+ if (!string.IsNullOrWhiteSpace(imdbId))
+ {
+ if (IsMovie)
+ {
+ list.Add(new ExternalUrl
+ {
+ Name = "Trakt",
+ Url = string.Format("https://trakt.tv/movies/{0}", imdbId)
+ });
+ }
+ }
+
+ return list;
+ }
}
}
diff --git a/MediaBrowser.Controller/LiveTv/TimerEventInfo.cs b/MediaBrowser.Controller/LiveTv/TimerEventInfo.cs
new file mode 100644
index 000000000..0e1a05475
--- /dev/null
+++ b/MediaBrowser.Controller/LiveTv/TimerEventInfo.cs
@@ -0,0 +1,14 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace MediaBrowser.Controller.LiveTv
+{
+ public class TimerEventInfo
+ {
+ public string Id { get; set; }
+ public string ProgramId { get; set; }
+ }
+}
diff --git a/MediaBrowser.Controller/LiveTv/TunerChannelMapping.cs b/MediaBrowser.Controller/LiveTv/TunerChannelMapping.cs
new file mode 100644
index 000000000..da0527eea
--- /dev/null
+++ b/MediaBrowser.Controller/LiveTv/TunerChannelMapping.cs
@@ -0,0 +1,16 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace MediaBrowser.Controller.LiveTv
+{
+ public class TunerChannelMapping
+ {
+ public string Name { get; set; }
+ public string Number { get; set; }
+ public string ProviderChannelNumber { get; set; }
+ public string ProviderChannelName { get; set; }
+ }
+}
diff --git a/MediaBrowser.Controller/MediaBrowser.Controller.csproj b/MediaBrowser.Controller/MediaBrowser.Controller.csproj
index bc28ec015..0462117cb 100644
--- a/MediaBrowser.Controller/MediaBrowser.Controller.csproj
+++ b/MediaBrowser.Controller/MediaBrowser.Controller.csproj
@@ -142,7 +142,7 @@
<Compile Include="Entities\IHasDisplayOrder.cs" />
<Compile Include="Entities\IHasId.cs" />
<Compile Include="Entities\IHasImages.cs" />
- <Compile Include="Entities\IHasKeywords.cs" />
+ <Compile Include="Entities\KeywordExtensions.cs" />
<Compile Include="Entities\IHasMediaSources.cs" />
<Compile Include="Entities\IHasMetascore.cs" />
<Compile Include="Entities\IHasOriginalTitle.cs" />
@@ -154,7 +154,6 @@
<Compile Include="Entities\IHasSpecialFeatures.cs" />
<Compile Include="Entities\IHasStartDate.cs" />
<Compile Include="Entities\IHasTaglines.cs" />
- <Compile Include="Entities\IHasTags.cs" />
<Compile Include="Entities\IHasThemeMedia.cs" />
<Compile Include="Entities\IHasTrailers.cs" />
<Compile Include="Entities\IHasUserData.cs" />
@@ -177,6 +176,7 @@
<Compile Include="Entities\PhotoAlbum.cs" />
<Compile Include="Entities\Share.cs" />
<Compile Include="Entities\SourceType.cs" />
+ <Compile Include="Entities\TagExtensions.cs" />
<Compile Include="Entities\UserView.cs" />
<Compile Include="Entities\UserViewBuilder.cs" />
<Compile Include="FileOrganization\IFileOrganizationService.cs" />
@@ -218,7 +218,9 @@
<Compile Include="LiveTv\ProgramInfo.cs" />
<Compile Include="LiveTv\RecordingInfo.cs" />
<Compile Include="LiveTv\SeriesTimerInfo.cs" />
+ <Compile Include="LiveTv\TimerEventInfo.cs" />
<Compile Include="LiveTv\TimerInfo.cs" />
+ <Compile Include="LiveTv\TunerChannelMapping.cs" />
<Compile Include="Localization\ILocalizationManager.cs" />
<Compile Include="MediaEncoding\ChapterImageRefreshOptions.cs" />
<Compile Include="MediaEncoding\EncodingJobOptions.cs" />
@@ -290,21 +292,17 @@
<Compile Include="Providers\IImageFileSaver.cs" />
<Compile Include="Providers\IImageProvider.cs" />
<Compile Include="Providers\IImageSaver.cs" />
- <Compile Include="Providers\IItemIdentityConverter.cs" />
- <Compile Include="Providers\IItemIdentityProvider.cs" />
<Compile Include="Providers\ILocalImageFileProvider.cs" />
<Compile Include="Providers\ILocalMetadataProvider.cs" />
<Compile Include="Providers\ImageRefreshMode.cs" />
<Compile Include="Providers\ImageRefreshOptions.cs" />
<Compile Include="Providers\IPreRefreshProvider.cs" />
- <Compile Include="Providers\IProviderRepository.cs" />
<Compile Include="Providers\IRemoteImageProvider.cs" />
<Compile Include="Providers\ILocalImageProvider.cs" />
<Compile Include="Providers\IMetadataProvider.cs" />
<Compile Include="Providers\IMetadataService.cs" />
<Compile Include="Providers\IRemoteMetadataProvider.cs" />
<Compile Include="Providers\IRemoteSearchProvider.cs" />
- <Compile Include="Providers\ISeriesOrderProvider.cs" />
<Compile Include="Providers\ItemInfo.cs" />
<Compile Include="Providers\LiveTvProgramLookupInfo.cs" />
<Compile Include="Providers\LocalImageInfo.cs" />
@@ -330,12 +328,8 @@
<Compile Include="Sorting\SortHelper.cs" />
<Compile Include="Subtitles\ISubtitleManager.cs" />
<Compile Include="Subtitles\ISubtitleProvider.cs" />
- <Compile Include="Providers\ItemIdentifier.cs" />
- <Compile Include="Providers\ItemIdentities.cs" />
<Compile Include="Providers\ItemLookupInfo.cs" />
<Compile Include="Providers\MetadataRefreshOptions.cs" />
- <Compile Include="Providers\MetadataStatus.cs" />
- <Compile Include="Providers\ISeriesOrderManager.cs" />
<Compile Include="Session\ISessionManager.cs" />
<Compile Include="Entities\AggregateFolder.cs" />
<Compile Include="Entities\Audio\Audio.cs" />
diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingJobOptions.cs b/MediaBrowser.Controller/MediaEncoding/EncodingJobOptions.cs
index c87ac4e73..44b741755 100644
--- a/MediaBrowser.Controller/MediaEncoding/EncodingJobOptions.cs
+++ b/MediaBrowser.Controller/MediaEncoding/EncodingJobOptions.cs
@@ -1,4 +1,5 @@
-using MediaBrowser.Model.Dlna;
+using System.Linq;
+using MediaBrowser.Model.Dlna;
namespace MediaBrowser.Controller.MediaEncoding
{
@@ -74,7 +75,7 @@ namespace MediaBrowser.Controller.MediaEncoding
Level = info.VideoLevel;
ItemId = info.ItemId;
MediaSourceId = info.MediaSourceId;
- AudioCodec = info.AudioCodec;
+ AudioCodec = info.TargetAudioCodec;
MaxAudioChannels = info.MaxAudioChannels;
AudioBitRate = info.AudioBitrate;
AudioSampleRate = info.TargetAudioSampleRate;
diff --git a/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs b/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs
index 7c3959f6e..62377b19e 100644
--- a/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs
+++ b/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs
@@ -13,6 +13,8 @@ namespace MediaBrowser.Controller.MediaEncoding
/// </summary>
public interface IMediaEncoder : ITranscoderSupport
{
+ string EncoderLocationType { get; }
+
/// <summary>
/// Gets the encoder path.
/// </summary>
@@ -20,12 +22,6 @@ namespace MediaBrowser.Controller.MediaEncoding
string EncoderPath { get; }
/// <summary>
- /// Gets the version.
- /// </summary>
- /// <value>The version.</value>
- string Version { get; }
-
- /// <summary>
/// Supportses the decoder.
/// </summary>
/// <param name="decoder">The decoder.</param>
@@ -66,12 +62,12 @@ namespace MediaBrowser.Controller.MediaEncoding
/// <param name="maxWidth">The maximum width.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>Task.</returns>
- Task ExtractVideoImagesOnInterval(string[] inputFiles,
- MediaProtocol protocol,
- Video3DFormat? threedFormat,
- TimeSpan interval,
- string targetDirectory,
- string filenamePrefix,
+ Task ExtractVideoImagesOnInterval(string[] inputFiles,
+ MediaProtocol protocol,
+ Video3DFormat? threedFormat,
+ TimeSpan interval,
+ string targetDirectory,
+ string filenamePrefix,
int? maxWidth,
CancellationToken cancellationToken);
@@ -134,5 +130,9 @@ namespace MediaBrowser.Controller.MediaEncoding
/// <param name="path">The path.</param>
/// <returns>System.String.</returns>
string EscapeSubtitleFilterPath(string path);
+
+ Task Init();
+
+ Task UpdateEncoderPath(string path, string pathType);
}
}
diff --git a/MediaBrowser.Controller/MediaEncoding/ISubtitleEncoder.cs b/MediaBrowser.Controller/MediaEncoding/ISubtitleEncoder.cs
index e538b84d8..44489cbf5 100644
--- a/MediaBrowser.Controller/MediaEncoding/ISubtitleEncoder.cs
+++ b/MediaBrowser.Controller/MediaEncoding/ISubtitleEncoder.cs
@@ -10,13 +10,6 @@ namespace MediaBrowser.Controller.MediaEncoding
/// <summary>
/// Gets the subtitles.
/// </summary>
- /// <param name="itemId">The item identifier.</param>
- /// <param name="mediaSourceId">The media source identifier.</param>
- /// <param name="subtitleStreamIndex">Index of the subtitle stream.</param>
- /// <param name="outputFormat">The output format.</param>
- /// <param name="startTimeTicks">The start time ticks.</param>
- /// <param name="endTimeTicks">The end time ticks.</param>
- /// <param name="cancellationToken">The cancellation token.</param>
/// <returns>Task{Stream}.</returns>
Task<Stream> GetSubtitles(string itemId,
string mediaSourceId,
@@ -24,6 +17,7 @@ namespace MediaBrowser.Controller.MediaEncoding
string outputFormat,
long startTimeTicks,
long? endTimeTicks,
+ bool preserveOriginalTimestamps,
CancellationToken cancellationToken);
/// <summary>
diff --git a/MediaBrowser.Controller/Net/IHttpResultFactory.cs b/MediaBrowser.Controller/Net/IHttpResultFactory.cs
index 526bf4be2..cd7ee603e 100644
--- a/MediaBrowser.Controller/Net/IHttpResultFactory.cs
+++ b/MediaBrowser.Controller/Net/IHttpResultFactory.cs
@@ -80,7 +80,7 @@ namespace MediaBrowser.Controller.Net
/// <param name="responseHeaders">The response headers.</param>
/// <param name="isHeadRequest">if set to <c>true</c> [is head request].</param>
/// <returns>System.Object.</returns>
- object GetStaticResult(IRequest requestContext,
+ Task<object> GetStaticResult(IRequest requestContext,
Guid cacheKey,
DateTime? lastDateModified,
TimeSpan? cacheDuration,
@@ -94,7 +94,7 @@ namespace MediaBrowser.Controller.Net
/// <param name="requestContext">The request context.</param>
/// <param name="options">The options.</param>
/// <returns>System.Object.</returns>
- object GetStaticResult(IRequest requestContext, StaticResultOptions options);
+ Task<object> GetStaticResult(IRequest requestContext, StaticResultOptions options);
/// <summary>
/// Gets the static file result.
@@ -103,7 +103,7 @@ namespace MediaBrowser.Controller.Net
/// <param name="path">The path.</param>
/// <param name="fileShare">The file share.</param>
/// <returns>System.Object.</returns>
- object GetStaticFileResult(IRequest requestContext, string path, FileShare fileShare = FileShare.Read);
+ Task<object> GetStaticFileResult(IRequest requestContext, string path, FileShare fileShare = FileShare.Read);
/// <summary>
/// Gets the static file result.
@@ -111,7 +111,7 @@ namespace MediaBrowser.Controller.Net
/// <param name="requestContext">The request context.</param>
/// <param name="options">The options.</param>
/// <returns>System.Object.</returns>
- object GetStaticFileResult(IRequest requestContext,
+ Task<object> GetStaticFileResult(IRequest requestContext,
StaticFileResultOptions options);
}
}
diff --git a/MediaBrowser.Controller/Persistence/IItemRepository.cs b/MediaBrowser.Controller/Persistence/IItemRepository.cs
index 7bcc36958..78138999c 100644
--- a/MediaBrowser.Controller/Persistence/IItemRepository.cs
+++ b/MediaBrowser.Controller/Persistence/IItemRepository.cs
@@ -4,6 +4,7 @@ using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
+using MediaBrowser.Model.Dto;
using MediaBrowser.Model.Querying;
namespace MediaBrowser.Controller.Persistence
@@ -81,7 +82,7 @@ namespace MediaBrowser.Controller.Persistence
/// <param name="chapters">The chapters.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>Task.</returns>
- Task SaveChapters(Guid id, IEnumerable<ChapterInfo> chapters, CancellationToken cancellationToken);
+ Task SaveChapters(Guid id, List<ChapterInfo> chapters, CancellationToken cancellationToken);
/// <summary>
/// Gets the media streams.
@@ -97,7 +98,7 @@ namespace MediaBrowser.Controller.Persistence
/// <param name="streams">The streams.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>Task.</returns>
- Task SaveMediaStreams(Guid id, IEnumerable<MediaStream> streams, CancellationToken cancellationToken);
+ Task SaveMediaStreams(Guid id, List<MediaStream> streams, CancellationToken cancellationToken);
/// <summary>
/// Gets the item ids.
@@ -153,7 +154,7 @@ namespace MediaBrowser.Controller.Persistence
/// </summary>
/// <param name="query">The query.</param>
/// <returns>List&lt;BaseItem&gt;.</returns>
- IEnumerable<BaseItem> GetItemList(InternalItemsQuery query);
+ List<BaseItem> GetItemList(InternalItemsQuery query);
/// <summary>
/// Updates the inherited values.
@@ -161,6 +162,13 @@ namespace MediaBrowser.Controller.Persistence
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>Task.</returns>
Task UpdateInheritedValues(CancellationToken cancellationToken);
+
+ QueryResult<Tuple<BaseItem, ItemCounts>> GetGenres(InternalItemsQuery query);
+ QueryResult<Tuple<BaseItem, ItemCounts>> GetMusicGenres(InternalItemsQuery query);
+ QueryResult<Tuple<BaseItem, ItemCounts>> GetGameGenres(InternalItemsQuery query);
+ QueryResult<Tuple<BaseItem, ItemCounts>> GetStudios(InternalItemsQuery query);
+ QueryResult<Tuple<BaseItem, ItemCounts>> GetArtists(InternalItemsQuery query);
+ QueryResult<Tuple<BaseItem, ItemCounts>> GetAlbumArtists(InternalItemsQuery query);
}
}
diff --git a/MediaBrowser.Controller/Persistence/IUserDataRepository.cs b/MediaBrowser.Controller/Persistence/IUserDataRepository.cs
index 2e165f416..ca4dc9751 100644
--- a/MediaBrowser.Controller/Persistence/IUserDataRepository.cs
+++ b/MediaBrowser.Controller/Persistence/IUserDataRepository.cs
@@ -29,6 +29,8 @@ namespace MediaBrowser.Controller.Persistence
/// <returns>Task{UserItemData}.</returns>
UserItemData GetUserData(Guid userId, string key);
+ UserItemData GetUserData(Guid userId, List<string> keys);
+
/// <summary>
/// Return all user data associated with the given user
/// </summary>
diff --git a/MediaBrowser.Controller/Providers/BaseItemXmlParser.cs b/MediaBrowser.Controller/Providers/BaseItemXmlParser.cs
index 1014fc2ee..a783910e3 100644
--- a/MediaBrowser.Controller/Providers/BaseItemXmlParser.cs
+++ b/MediaBrowser.Controller/Providers/BaseItemXmlParser.cs
@@ -803,11 +803,7 @@ namespace MediaBrowser.Controller.Providers
{
using (var subtree = reader.ReadSubtree())
{
- var hasTags = item as IHasTags;
- if (hasTags != null)
- {
- FetchFromTagsNode(subtree, hasTags);
- }
+ FetchFromTagsNode(subtree, item);
}
break;
}
@@ -816,11 +812,7 @@ namespace MediaBrowser.Controller.Providers
{
using (var subtree = reader.ReadSubtree())
{
- var hasTags = item as IHasKeywords;
- if (hasTags != null)
- {
- FetchFromKeywordsNode(subtree, hasTags);
- }
+ FetchFromKeywordsNode(subtree, item);
}
break;
}
@@ -1070,7 +1062,7 @@ namespace MediaBrowser.Controller.Providers
}
}
- private void FetchFromTagsNode(XmlReader reader, IHasTags item)
+ private void FetchFromTagsNode(XmlReader reader, BaseItem item)
{
reader.MoveToContent();
@@ -1099,7 +1091,7 @@ namespace MediaBrowser.Controller.Providers
}
}
- private void FetchFromKeywordsNode(XmlReader reader, IHasKeywords item)
+ private void FetchFromKeywordsNode(XmlReader reader, BaseItem item)
{
reader.MoveToContent();
diff --git a/MediaBrowser.Controller/Providers/IItemIdentityConverter.cs b/MediaBrowser.Controller/Providers/IItemIdentityConverter.cs
deleted file mode 100644
index bfdd1dbf3..000000000
--- a/MediaBrowser.Controller/Providers/IItemIdentityConverter.cs
+++ /dev/null
@@ -1,4 +0,0 @@
-namespace MediaBrowser.Controller.Providers
-{
- public interface IItemIdentityConverter { }
-} \ No newline at end of file
diff --git a/MediaBrowser.Controller/Providers/IItemIdentityProvider.cs b/MediaBrowser.Controller/Providers/IItemIdentityProvider.cs
deleted file mode 100644
index 6b403bb55..000000000
--- a/MediaBrowser.Controller/Providers/IItemIdentityProvider.cs
+++ /dev/null
@@ -1,4 +0,0 @@
-namespace MediaBrowser.Controller.Providers
-{
- public interface IItemIdentityProvider { }
-} \ No newline at end of file
diff --git a/MediaBrowser.Controller/Providers/IProviderManager.cs b/MediaBrowser.Controller/Providers/IProviderManager.cs
index 57e4ff320..3eefa9647 100644
--- a/MediaBrowser.Controller/Providers/IProviderManager.cs
+++ b/MediaBrowser.Controller/Providers/IProviderManager.cs
@@ -97,13 +97,11 @@ namespace MediaBrowser.Controller.Providers
/// </summary>
/// <param name="imageProviders">The image providers.</param>
/// <param name="metadataServices">The metadata services.</param>
- /// <param name="identityProviders">The identity providers.</param>
- /// <param name="identityConverters">The identity converters.</param>
/// <param name="metadataProviders">The metadata providers.</param>
/// <param name="savers">The savers.</param>
/// <param name="imageSavers">The image savers.</param>
/// <param name="externalIds">The external ids.</param>
- void AddParts(IEnumerable<IImageProvider> imageProviders, IEnumerable<IMetadataService> metadataServices, IEnumerable<IItemIdentityProvider> identityProviders, IEnumerable<IItemIdentityConverter> identityConverters, IEnumerable<IMetadataProvider> metadataProviders,
+ void AddParts(IEnumerable<IImageProvider> imageProviders, IEnumerable<IMetadataService> metadataServices, IEnumerable<IMetadataProvider> metadataProviders,
IEnumerable<IMetadataSaver> savers,
IEnumerable<IImageSaver> imageSavers,
IEnumerable<IExternalId> externalIds);
@@ -135,7 +133,7 @@ namespace MediaBrowser.Controller.Providers
/// </summary>
/// <param name="item">The item.</param>
/// <returns>IEnumerable{ExternalUrl}.</returns>
- IEnumerable<ExternalUrl> GetExternalUrls(IHasProviderIds item);
+ IEnumerable<ExternalUrl> GetExternalUrls(BaseItem item);
/// <summary>
/// Gets the external identifier infos.
@@ -190,21 +188,5 @@ namespace MediaBrowser.Controller.Providers
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>Task{HttpResponseInfo}.</returns>
Task<HttpResponseInfo> GetSearchImage(string providerName, string url, CancellationToken cancellationToken);
-
- /// <summary>
- /// Gets the item identity providers.
- /// </summary>
- /// <typeparam name="TLookupInfo">The type of the t lookup information.</typeparam>
- /// <returns>IEnumerable&lt;IItemIdentityProvider&lt;TLookupInfo, TIdentity&gt;&gt;.</returns>
- IEnumerable<IItemIdentityProvider<TLookupInfo>> GetItemIdentityProviders<TLookupInfo>()
- where TLookupInfo : ItemLookupInfo;
-
- /// <summary>
- /// Gets the item identity converters.
- /// </summary>
- /// <typeparam name="TLookupInfo">The type of the t lookup information.</typeparam>
- /// <returns>IEnumerable&lt;IItemIdentityConverter&lt;TIdentity&gt;&gt;.</returns>
- IEnumerable<IItemIdentityConverter<TLookupInfo>> GetItemIdentityConverters<TLookupInfo>()
- where TLookupInfo : ItemLookupInfo;
}
} \ No newline at end of file
diff --git a/MediaBrowser.Controller/Providers/IProviderRepository.cs b/MediaBrowser.Controller/Providers/IProviderRepository.cs
deleted file mode 100644
index 891275d77..000000000
--- a/MediaBrowser.Controller/Providers/IProviderRepository.cs
+++ /dev/null
@@ -1,25 +0,0 @@
-using MediaBrowser.Controller.Persistence;
-using System;
-using System.Threading;
-using System.Threading.Tasks;
-
-namespace MediaBrowser.Controller.Providers
-{
- public interface IProviderRepository : IRepository
- {
- /// <summary>
- /// Gets the metadata status.
- /// </summary>
- /// <param name="itemId">The item identifier.</param>
- /// <returns>MetadataStatus.</returns>
- MetadataStatus GetMetadataStatus(Guid itemId);
-
- /// <summary>
- /// Saves the metadata status.
- /// </summary>
- /// <param name="status">The status.</param>
- /// <param name="cancellationToken">The cancellation token.</param>
- /// <returns>Task.</returns>
- Task SaveMetadataStatus(MetadataStatus status, CancellationToken cancellationToken);
- }
-}
diff --git a/MediaBrowser.Controller/Providers/ISeriesOrderManager.cs b/MediaBrowser.Controller/Providers/ISeriesOrderManager.cs
deleted file mode 100644
index 970f7a7be..000000000
--- a/MediaBrowser.Controller/Providers/ISeriesOrderManager.cs
+++ /dev/null
@@ -1,11 +0,0 @@
-using System.Collections.Generic;
-using System.Threading.Tasks;
-
-namespace MediaBrowser.Controller.Providers
-{
- public interface ISeriesOrderManager
- {
- Task<int?> FindSeriesIndex(string orderType, string seriesName);
- void AddParts(IEnumerable<ISeriesOrderProvider> orderProviders);
- }
-}
diff --git a/MediaBrowser.Controller/Providers/ISeriesOrderProvider.cs b/MediaBrowser.Controller/Providers/ISeriesOrderProvider.cs
deleted file mode 100644
index ee0f3c197..000000000
--- a/MediaBrowser.Controller/Providers/ISeriesOrderProvider.cs
+++ /dev/null
@@ -1,10 +0,0 @@
-using System.Threading.Tasks;
-
-namespace MediaBrowser.Controller.Providers
-{
- public interface ISeriesOrderProvider
- {
- string OrderType { get; }
- Task<int?> FindSeriesIndex(string seriesName);
- }
-} \ No newline at end of file
diff --git a/MediaBrowser.Controller/Providers/ItemIdentifier.cs b/MediaBrowser.Controller/Providers/ItemIdentifier.cs
deleted file mode 100644
index bbc6dd76c..000000000
--- a/MediaBrowser.Controller/Providers/ItemIdentifier.cs
+++ /dev/null
@@ -1,36 +0,0 @@
-using System.Linq;
-using System.Threading;
-using System.Threading.Tasks;
-
-namespace MediaBrowser.Controller.Providers
-{
- public static class ItemIdentifier<TLookupInfo>
- where TLookupInfo : ItemLookupInfo
- {
- public static async Task FindIdentities(TLookupInfo item, IProviderManager providerManager, CancellationToken cancellationToken)
- {
- var providers = providerManager.GetItemIdentityProviders<TLookupInfo>();
- var converters = providerManager.GetItemIdentityConverters<TLookupInfo>().ToList();
-
- foreach (var provider in providers)
- {
- await provider.Identify(item);
- }
-
- bool changesMade = true;
-
- while (changesMade)
- {
- changesMade = false;
-
- foreach (var converter in converters)
- {
- if (await converter.Convert(item))
- {
- changesMade = true;
- }
- }
- }
- }
- }
-} \ No newline at end of file
diff --git a/MediaBrowser.Controller/Providers/ItemIdentities.cs b/MediaBrowser.Controller/Providers/ItemIdentities.cs
deleted file mode 100644
index 48316d0f4..000000000
--- a/MediaBrowser.Controller/Providers/ItemIdentities.cs
+++ /dev/null
@@ -1,16 +0,0 @@
-using System.Threading.Tasks;
-
-namespace MediaBrowser.Controller.Providers
-{
- public interface IItemIdentityProvider<in TLookupInfo> : IItemIdentityProvider
- where TLookupInfo : ItemLookupInfo
- {
- Task Identify(TLookupInfo info);
- }
-
- public interface IItemIdentityConverter<in TLookupInfo> : IItemIdentityConverter
- where TLookupInfo : ItemLookupInfo
- {
- Task<bool> Convert(TLookupInfo info);
- }
-} \ No newline at end of file
diff --git a/MediaBrowser.Controller/Providers/ItemInfo.cs b/MediaBrowser.Controller/Providers/ItemInfo.cs
index d16a73028..63cc48058 100644
--- a/MediaBrowser.Controller/Providers/ItemInfo.cs
+++ b/MediaBrowser.Controller/Providers/ItemInfo.cs
@@ -1,3 +1,4 @@
+using System;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Model.Entities;
@@ -5,10 +6,6 @@ namespace MediaBrowser.Controller.Providers
{
public class ItemInfo
{
- public ItemInfo()
- {
- }
-
public ItemInfo(IHasMetadata item)
{
Path = item.Path;
@@ -21,8 +18,11 @@ namespace MediaBrowser.Controller.Providers
VideoType = video.VideoType;
IsPlaceHolder = video.IsPlaceHolder;
}
+
+ ItemType = item.GetType();
}
+ public Type ItemType { get; set; }
public string Path { get; set; }
public string ContainingFolderPath { get; set; }
public VideoType VideoType { get; set; }
diff --git a/MediaBrowser.Controller/Providers/MetadataStatus.cs b/MediaBrowser.Controller/Providers/MetadataStatus.cs
deleted file mode 100644
index b19377a68..000000000
--- a/MediaBrowser.Controller/Providers/MetadataStatus.cs
+++ /dev/null
@@ -1,49 +0,0 @@
-using System;
-
-namespace MediaBrowser.Controller.Providers
-{
- public class MetadataStatus
- {
- /// <summary>
- /// Gets or sets the item identifier.
- /// </summary>
- /// <value>The item identifier.</value>
- public Guid ItemId { get; set; }
-
- /// <summary>
- /// Gets or sets the date last metadata refresh.
- /// </summary>
- /// <value>The date last metadata refresh.</value>
- public DateTime? DateLastMetadataRefresh { get; set; }
-
- /// <summary>
- /// Gets or sets the date last images refresh.
- /// </summary>
- /// <value>The date last images refresh.</value>
- public DateTime? DateLastImagesRefresh { get; set; }
-
- public DateTime? ItemDateModified { get; set; }
-
- public bool IsDirty { get; private set; }
-
- public void SetDateLastMetadataRefresh(DateTime? date)
- {
- if (date != DateLastMetadataRefresh)
- {
- IsDirty = true;
- }
-
- DateLastMetadataRefresh = date;
- }
-
- public void SetDateLastImagesRefresh(DateTime? date)
- {
- if (date != DateLastImagesRefresh)
- {
- IsDirty = true;
- }
-
- DateLastImagesRefresh = date;
- }
- }
-}
diff --git a/MediaBrowser.Controller/Session/ISessionManager.cs b/MediaBrowser.Controller/Session/ISessionManager.cs
index fa74c5749..6659d1553 100644
--- a/MediaBrowser.Controller/Session/ISessionManager.cs
+++ b/MediaBrowser.Controller/Session/ISessionManager.cs
@@ -315,9 +315,8 @@ namespace MediaBrowser.Controller.Session
/// <summary>
/// Revokes the user tokens.
/// </summary>
- /// <param name="userId">The user identifier.</param>
/// <returns>Task.</returns>
- Task RevokeUserTokens(string userId);
+ Task RevokeUserTokens(string userId, string currentAccessToken);
/// <summary>
/// Revokes the token.
diff --git a/MediaBrowser.Dlna/Didl/DidlBuilder.cs b/MediaBrowser.Dlna/Didl/DidlBuilder.cs
index af833a85c..0a15491b6 100644
--- a/MediaBrowser.Dlna/Didl/DidlBuilder.cs
+++ b/MediaBrowser.Dlna/Didl/DidlBuilder.cs
@@ -160,7 +160,7 @@ namespace MediaBrowser.Dlna.Didl
var contentFeatureList = new ContentFeatureBuilder(_profile).BuildVideoHeader(streamInfo.Container,
streamInfo.VideoCodec,
- streamInfo.AudioCodec,
+ streamInfo.TargetAudioCodec,
targetWidth,
targetHeight,
streamInfo.TargetVideoBitDepth,
@@ -307,7 +307,7 @@ namespace MediaBrowser.Dlna.Didl
}
var mediaProfile = _profile.GetVideoMediaProfile(streamInfo.Container,
- streamInfo.AudioCodec,
+ streamInfo.TargetAudioCodec,
streamInfo.VideoCodec,
streamInfo.TargetAudioBitrate,
targetWidth,
@@ -441,7 +441,7 @@ namespace MediaBrowser.Dlna.Didl
}
var mediaProfile = _profile.GetAudioMediaProfile(streamInfo.Container,
- streamInfo.AudioCodec,
+ streamInfo.TargetAudioCodec,
targetChannels,
targetAudioBitrate);
@@ -846,7 +846,7 @@ namespace MediaBrowser.Dlna.Didl
if (item is Video)
{
- var userData = _userDataManager.GetUserDataDto(item, _user);
+ var userData = _userDataManager.GetUserDataDto(item, _user).Result;
playbackPercentage = Convert.ToInt32(userData.PlayedPercentage ?? 0);
if (playbackPercentage >= 100 || userData.Played)
@@ -856,7 +856,7 @@ namespace MediaBrowser.Dlna.Didl
}
else if (item is Series || item is Season || item is BoxSet)
{
- var userData = _userDataManager.GetUserDataDto(item, _user);
+ var userData = _userDataManager.GetUserDataDto(item, _user).Result;
if (userData.Played)
{
@@ -1015,17 +1015,17 @@ namespace MediaBrowser.Dlna.Didl
int? width = null;
int? height = null;
- //try
- //{
- // var size = _imageProcessor.GetImageSize(imageInfo);
+ try
+ {
+ var size = _imageProcessor.GetImageSize(imageInfo);
- // width = Convert.ToInt32(size.Width);
- // height = Convert.ToInt32(size.Height);
- //}
- //catch
- //{
+ width = Convert.ToInt32(size.Width);
+ height = Convert.ToInt32(size.Height);
+ }
+ catch
+ {
- //}
+ }
var inputFormat = (Path.GetExtension(imageInfo.Path) ?? string.Empty)
.TrimStart('.')
diff --git a/MediaBrowser.Dlna/DlnaManager.cs b/MediaBrowser.Dlna/DlnaManager.cs
index 3cc6a4379..b4127a91f 100644
--- a/MediaBrowser.Dlna/DlnaManager.cs
+++ b/MediaBrowser.Dlna/DlnaManager.cs
@@ -29,6 +29,8 @@ namespace MediaBrowser.Dlna
private readonly IJsonSerializer _jsonSerializer;
private readonly IServerApplicationHost _appHost;
+ private readonly Dictionary<string, DeviceProfile> _profiles = new Dictionary<string, DeviceProfile>(StringComparer.Ordinal);
+
public DlnaManager(IXmlSerializer xmlSerializer,
IFileSystem fileSystem,
IApplicationPaths appPaths,
@@ -300,20 +302,31 @@ namespace MediaBrowser.Dlna
private DeviceProfile ParseProfileXmlFile(string path, DeviceProfileType type)
{
- try
+ lock (_profiles)
{
- var profile = (DeviceProfile)_xmlSerializer.DeserializeFromFile(typeof(DeviceProfile), path);
+ DeviceProfile profile;
+ if (_profiles.TryGetValue(path, out profile))
+ {
+ return profile;
+ }
+
+ try
+ {
+ profile = (DeviceProfile)_xmlSerializer.DeserializeFromFile(typeof(DeviceProfile), path);
- profile.Id = path.ToLower().GetMD5().ToString("N");
- profile.ProfileType = type;
+ profile.Id = path.ToLower().GetMD5().ToString("N");
+ profile.ProfileType = type;
- return profile;
- }
- catch (Exception ex)
- {
- _logger.ErrorException("Error parsing profile xml: {0}", ex, path);
+ _profiles[path] = profile;
- return null;
+ return profile;
+ }
+ catch (Exception ex)
+ {
+ _logger.ErrorException("Error parsing profile xml: {0}", ex, path);
+
+ return null;
+ }
}
}
@@ -428,7 +441,7 @@ namespace MediaBrowser.Dlna
var newFilename = _fileSystem.GetValidFilename(profile.Name) + ".xml";
var path = Path.Combine(UserProfilesPath, newFilename);
- _xmlSerializer.SerializeToFile(profile, path);
+ SaveProfile(profile, path);
}
public void UpdateProfile(DeviceProfile profile)
@@ -455,6 +468,15 @@ namespace MediaBrowser.Dlna
_fileSystem.DeleteFile(current.Path);
}
+ SaveProfile(profile, path);
+ }
+
+ private void SaveProfile(DeviceProfile profile, string path)
+ {
+ lock (_profiles)
+ {
+ _profiles[path] = profile;
+ }
_xmlSerializer.SerializeToFile(profile, path);
}
diff --git a/MediaBrowser.Dlna/Main/DlnaEntryPoint.cs b/MediaBrowser.Dlna/Main/DlnaEntryPoint.cs
index 22f308979..b9d9944ec 100644
--- a/MediaBrowser.Dlna/Main/DlnaEntryPoint.cs
+++ b/MediaBrowser.Dlna/Main/DlnaEntryPoint.cs
@@ -15,6 +15,7 @@ using MediaBrowser.Model.Logging;
using System;
using System.Collections.Generic;
using System.Linq;
+using System.Threading.Tasks;
using MediaBrowser.Controller.MediaEncoding;
using MediaBrowser.Dlna.Channels;
@@ -105,7 +106,7 @@ namespace MediaBrowser.Dlna.Main
}
}
- private void ReloadComponents()
+ private async void ReloadComponents()
{
var options = _config.GetDlnaConfiguration();
@@ -130,7 +131,7 @@ namespace MediaBrowser.Dlna.Main
if (options.EnableServer && !isServerStarted)
{
- StartDlnaServer();
+ await StartDlnaServer().ConfigureAwait(false);
}
else if (!options.EnableServer && isServerStarted)
{
@@ -208,11 +209,11 @@ namespace MediaBrowser.Dlna.Main
}
}
- public void StartDlnaServer()
+ public async Task StartDlnaServer()
{
try
{
- RegisterServerEndpoints();
+ await RegisterServerEndpoints().ConfigureAwait(false);
_dlnaServerStarted = true;
}
@@ -222,9 +223,9 @@ namespace MediaBrowser.Dlna.Main
}
}
- private void RegisterServerEndpoints()
+ private async Task RegisterServerEndpoints()
{
- foreach (var address in _appHost.LocalIpAddresses)
+ foreach (var address in await _appHost.GetLocalIpAddresses().ConfigureAwait(false))
{
//if (IPAddress.IsLoopback(address))
//{
diff --git a/MediaBrowser.Dlna/PlayTo/PlayToController.cs b/MediaBrowser.Dlna/PlayTo/PlayToController.cs
index 80bb756ef..873ae5ae4 100644
--- a/MediaBrowser.Dlna/PlayTo/PlayToController.cs
+++ b/MediaBrowser.Dlna/PlayTo/PlayToController.cs
@@ -62,7 +62,7 @@ namespace MediaBrowser.Dlna.PlayTo
}
return false;
}
-
+
return _device != null;
}
}
@@ -119,7 +119,7 @@ namespace MediaBrowser.Dlna.PlayTo
string nt;
if (!e.Headers.TryGetValue("NT", out nt)) nt = String.Empty;
- if ( usn.IndexOf(_device.Properties.UUID, StringComparison.OrdinalIgnoreCase) != -1 &&
+ if (usn.IndexOf(_device.Properties.UUID, StringComparison.OrdinalIgnoreCase) != -1 &&
!_disposed)
{
if (usn.IndexOf("MediaRenderer:", StringComparison.OrdinalIgnoreCase) != -1 ||
@@ -141,7 +141,7 @@ namespace MediaBrowser.Dlna.PlayTo
{
try
{
- var streamInfo = StreamParams.ParseFromUrl(e.OldMediaInfo.Url, _libraryManager, _mediaSourceManager);
+ var streamInfo = await StreamParams.ParseFromUrl(e.OldMediaInfo.Url, _libraryManager, _mediaSourceManager).ConfigureAwait(false);
if (streamInfo.Item != null)
{
var progress = GetProgressInfo(e.OldMediaInfo, streamInfo);
@@ -151,9 +151,9 @@ namespace MediaBrowser.Dlna.PlayTo
ReportPlaybackStopped(e.OldMediaInfo, streamInfo, positionTicks);
}
- streamInfo = StreamParams.ParseFromUrl(e.NewMediaInfo.Url, _libraryManager, _mediaSourceManager);
+ streamInfo = await StreamParams.ParseFromUrl(e.NewMediaInfo.Url, _libraryManager, _mediaSourceManager).ConfigureAwait(false);
if (streamInfo.Item == null) return;
-
+
var newItemProgress = GetProgressInfo(e.NewMediaInfo, streamInfo);
await _sessionManager.OnPlaybackStart(newItemProgress).ConfigureAwait(false);
@@ -168,7 +168,8 @@ namespace MediaBrowser.Dlna.PlayTo
{
try
{
- var streamInfo = StreamParams.ParseFromUrl(e.MediaInfo.Url, _libraryManager, _mediaSourceManager);
+ var streamInfo = await StreamParams.ParseFromUrl(e.MediaInfo.Url, _libraryManager, _mediaSourceManager)
+ .ConfigureAwait(false);
if (streamInfo.Item == null) return;
@@ -230,7 +231,7 @@ namespace MediaBrowser.Dlna.PlayTo
{
try
{
- var info = StreamParams.ParseFromUrl(e.MediaInfo.Url, _libraryManager, _mediaSourceManager);
+ var info = await StreamParams.ParseFromUrl(e.MediaInfo.Url, _libraryManager, _mediaSourceManager).ConfigureAwait(false);
if (info.Item != null)
{
@@ -249,7 +250,7 @@ namespace MediaBrowser.Dlna.PlayTo
{
try
{
- var info = StreamParams.ParseFromUrl(e.MediaInfo.Url, _libraryManager, _mediaSourceManager);
+ var info = await StreamParams.ParseFromUrl(e.MediaInfo.Url, _libraryManager, _mediaSourceManager).ConfigureAwait(false);
if (info.Item != null)
{
@@ -377,7 +378,7 @@ namespace MediaBrowser.Dlna.PlayTo
if (media != null)
{
- var info = StreamParams.ParseFromUrl(media.Url, _libraryManager, _mediaSourceManager);
+ var info = await StreamParams.ParseFromUrl(media.Url, _libraryManager, _mediaSourceManager).ConfigureAwait(false);
if (info.Item != null && !EnableClientSideSeek(info))
{
@@ -470,7 +471,7 @@ namespace MediaBrowser.Dlna.PlayTo
var profile = _dlnaManager.GetProfile(deviceInfo.ToDeviceIdentification()) ??
_dlnaManager.GetDefaultProfile();
-
+
var hasMediaSources = item as IHasMediaSources;
var mediaSources = hasMediaSources != null
? (_mediaSourceManager.GetStaticMediaSources(hasMediaSources, true, user)).ToList()
@@ -498,7 +499,7 @@ namespace MediaBrowser.Dlna.PlayTo
{
return new ContentFeatureBuilder(profile)
.BuildAudioHeader(streamInfo.Container,
- streamInfo.AudioCodec,
+ streamInfo.TargetAudioCodec,
streamInfo.TargetAudioBitrate,
streamInfo.TargetAudioSampleRate,
streamInfo.TargetAudioChannels,
@@ -512,7 +513,7 @@ namespace MediaBrowser.Dlna.PlayTo
var list = new ContentFeatureBuilder(profile)
.BuildVideoHeader(streamInfo.Container,
streamInfo.VideoCodec,
- streamInfo.AudioCodec,
+ streamInfo.TargetAudioCodec,
streamInfo.TargetWidth,
streamInfo.TargetHeight,
streamInfo.TargetVideoBitDepth,
@@ -739,7 +740,7 @@ namespace MediaBrowser.Dlna.PlayTo
if (media != null)
{
- var info = StreamParams.ParseFromUrl(media.Url, _libraryManager, _mediaSourceManager);
+ var info = await StreamParams.ParseFromUrl(media.Url, _libraryManager, _mediaSourceManager).ConfigureAwait(false);
if (info.Item != null)
{
@@ -765,7 +766,7 @@ namespace MediaBrowser.Dlna.PlayTo
if (media != null)
{
- var info = StreamParams.ParseFromUrl(media.Url, _libraryManager, _mediaSourceManager);
+ var info = await StreamParams.ParseFromUrl(media.Url, _libraryManager, _mediaSourceManager).ConfigureAwait(false);
if (info.Item != null)
{
@@ -840,7 +841,7 @@ namespace MediaBrowser.Dlna.PlayTo
return null;
}
- public static StreamParams ParseFromUrl(string url, ILibraryManager libraryManager, IMediaSourceManager mediaSourceManager)
+ public static async Task<StreamParams> ParseFromUrl(string url, ILibraryManager libraryManager, IMediaSourceManager mediaSourceManager)
{
var request = new StreamParams
{
@@ -906,11 +907,9 @@ namespace MediaBrowser.Dlna.PlayTo
var hasMediaSources = request.Item as IHasMediaSources;
- request.MediaSource = hasMediaSources == null ?
- null :
- mediaSourceManager.GetMediaSource(hasMediaSources, request.MediaSourceId, false).Result;
-
-
+ request.MediaSource = hasMediaSources == null
+ ? null
+ : (await mediaSourceManager.GetMediaSource(hasMediaSources, request.MediaSourceId, false).ConfigureAwait(false));
return request;
}
diff --git a/MediaBrowser.Dlna/PlayTo/PlayToManager.cs b/MediaBrowser.Dlna/PlayTo/PlayToManager.cs
index bbb9bf6de..cd9a7b1f0 100644
--- a/MediaBrowser.Dlna/PlayTo/PlayToManager.cs
+++ b/MediaBrowser.Dlna/PlayTo/PlayToManager.cs
@@ -101,6 +101,7 @@ namespace MediaBrowser.Dlna.PlayTo
}
var uri = new Uri(location);
+ _logger.Debug("Attempting to create PlayToController from location {0}", location);
var device = await Device.CreateuPnpDeviceAsync(uri, _httpClient, _config, _logger).ConfigureAwait(false);
if (device.RendererCommands == null)
@@ -112,6 +113,7 @@ namespace MediaBrowser.Dlna.PlayTo
}
}
+ _logger.Debug("Logging session activity from location {0}", location);
var sessionInfo = await _sessionManager.LogSessionActivity(device.Properties.ClientType, _appHost.ApplicationVersion.ToString(), device.Properties.UUID, device.Properties.Name, uri.OriginalString, null)
.ConfigureAwait(false);
diff --git a/MediaBrowser.Dlna/Profiles/LgTvProfile.cs b/MediaBrowser.Dlna/Profiles/LgTvProfile.cs
index 3bff80a70..202ea76fb 100644
--- a/MediaBrowser.Dlna/Profiles/LgTvProfile.cs
+++ b/MediaBrowser.Dlna/Profiles/LgTvProfile.cs
@@ -54,21 +54,21 @@ namespace MediaBrowser.Dlna.Profiles
new DirectPlayProfile
{
Container = "ts",
- VideoCodec = "h264,hevc",
+ VideoCodec = "h264",
AudioCodec = "aac,ac3,mp3",
Type = DlnaProfileType.Video
},
new DirectPlayProfile
{
Container = "mkv",
- VideoCodec = "h264,hevc",
+ VideoCodec = "h264",
AudioCodec = "aac,ac3,mp3",
Type = DlnaProfileType.Video
},
new DirectPlayProfile
{
Container = "mp4",
- VideoCodec = "h264,mpeg4,hevc",
+ VideoCodec = "h264,mpeg4",
AudioCodec = "aac,ac3,mp3",
Type = DlnaProfileType.Video
},
@@ -189,6 +189,15 @@ namespace MediaBrowser.Dlna.Profiles
}
}
};
+
+ SubtitleProfiles = new[]
+ {
+ new SubtitleProfile
+ {
+ Format = "srt",
+ Method = SubtitleDeliveryMethod.External
+ }
+ };
}
}
}
diff --git a/MediaBrowser.Dlna/Profiles/SamsungSmartTvProfile.cs b/MediaBrowser.Dlna/Profiles/SamsungSmartTvProfile.cs
index 1b06e1d6a..c117d030f 100644
--- a/MediaBrowser.Dlna/Profiles/SamsungSmartTvProfile.cs
+++ b/MediaBrowser.Dlna/Profiles/SamsungSmartTvProfile.cs
@@ -21,8 +21,8 @@ namespace MediaBrowser.Dlna.Profiles
new HttpHeaderInfo
{
Name = "User-Agent",
- Value = @"SEC_",
- Match = HeaderMatchType.Substring
+ Value = @".*(SEC_HHP_\[TV\] [A-Z]{2}\d{2}J[A-Z]?\d{3,4})*.",
+ Match = HeaderMatchType.Regex
}
}
};
diff --git a/MediaBrowser.Dlna/Profiles/Xml/LG Smart TV.xml b/MediaBrowser.Dlna/Profiles/Xml/LG Smart TV.xml
index 404bbbb20..1147aa299 100644
--- a/MediaBrowser.Dlna/Profiles/Xml/LG Smart TV.xml
+++ b/MediaBrowser.Dlna/Profiles/Xml/LG Smart TV.xml
@@ -35,9 +35,9 @@
<IgnoreTranscodeByteRangeRequests>false</IgnoreTranscodeByteRangeRequests>
<XmlRootAttributes />
<DirectPlayProfiles>
- <DirectPlayProfile container="ts" audioCodec="aac,ac3,mp3" videoCodec="h264,hevc" type="Video" />
- <DirectPlayProfile container="mkv" audioCodec="aac,ac3,mp3" videoCodec="h264,hevc" type="Video" />
- <DirectPlayProfile container="mp4" audioCodec="aac,ac3,mp3" videoCodec="h264,mpeg4,hevc" type="Video" />
+ <DirectPlayProfile container="ts" audioCodec="aac,ac3,mp3" videoCodec="h264" type="Video" />
+ <DirectPlayProfile container="mkv" audioCodec="aac,ac3,mp3" videoCodec="h264" type="Video" />
+ <DirectPlayProfile container="mp4" audioCodec="aac,ac3,mp3" videoCodec="h264,mpeg4" type="Video" />
<DirectPlayProfile container="mp3" audioCodec="mp3" type="Audio" />
<DirectPlayProfile container="jpeg" type="Photo" />
</DirectPlayProfiles>
@@ -77,5 +77,7 @@
</CodecProfile>
</CodecProfiles>
<ResponseProfiles />
- <SubtitleProfiles />
+ <SubtitleProfiles>
+ <SubtitleProfile format="srt" method="External" />
+ </SubtitleProfiles>
</Profile> \ No newline at end of file
diff --git a/MediaBrowser.Dlna/Profiles/Xml/Samsung Smart TV.xml b/MediaBrowser.Dlna/Profiles/Xml/Samsung Smart TV.xml
index b09c25afa..967538bdf 100644
--- a/MediaBrowser.Dlna/Profiles/Xml/Samsung Smart TV.xml
+++ b/MediaBrowser.Dlna/Profiles/Xml/Samsung Smart TV.xml
@@ -4,7 +4,7 @@
<Identification>
<ModelUrl>samsung.com</ModelUrl>
<Headers>
- <HttpHeaderInfo name="User-Agent" value="SEC_" match="Substring" />
+ <HttpHeaderInfo name="User-Agent" value=".*(SEC_HHP_\[TV\] [A-Z]{2}\d{2}J[A-Z]?\d{3,4})*." match="Regex" />
</Headers>
</Identification>
<Manufacturer>Emby</Manufacturer>
diff --git a/MediaBrowser.Dlna/Ssdp/DeviceDiscovery.cs b/MediaBrowser.Dlna/Ssdp/DeviceDiscovery.cs
index 1eda79f02..68768745e 100644
--- a/MediaBrowser.Dlna/Ssdp/DeviceDiscovery.cs
+++ b/MediaBrowser.Dlna/Ssdp/DeviceDiscovery.cs
@@ -61,7 +61,7 @@ namespace MediaBrowser.Dlna.Ssdp
}
}
- void _ssdpHandler_MessageReceived(object sender, SsdpMessageEventArgs e)
+ async void _ssdpHandler_MessageReceived(object sender, SsdpMessageEventArgs e)
{
string nts;
e.Headers.TryGetValue("NTS", out nts);
@@ -78,7 +78,7 @@ namespace MediaBrowser.Dlna.Ssdp
{
if (e.LocalEndPoint == null)
{
- var ip = _appHost.LocalIpAddresses.FirstOrDefault(i => !IPAddress.IsLoopback(i));
+ var ip = (await _appHost.GetLocalIpAddresses().ConfigureAwait(false)).FirstOrDefault(i => !IPAddress.IsLoopback(i));
if (ip != null)
{
e.LocalEndPoint = new IPEndPoint(ip, 0);
diff --git a/MediaBrowser.LocalMetadata/Savers/XmlSaverHelpers.cs b/MediaBrowser.LocalMetadata/Savers/XmlSaverHelpers.cs
index be81d21d2..2b3f53aeb 100644
--- a/MediaBrowser.LocalMetadata/Savers/XmlSaverHelpers.cs
+++ b/MediaBrowser.LocalMetadata/Savers/XmlSaverHelpers.cs
@@ -593,36 +593,28 @@ namespace MediaBrowser.LocalMetadata.Savers
builder.Append("</Studios>");
}
- var hasTags = item as IHasTags;
- if (hasTags != null)
+ if (item.Tags.Count > 0)
{
- if (hasTags.Tags.Count > 0)
- {
- builder.Append("<Tags>");
-
- foreach (var tag in hasTags.Tags)
- {
- builder.Append("<Tag>" + SecurityElement.Escape(tag) + "</Tag>");
- }
+ builder.Append("<Tags>");
- builder.Append("</Tags>");
+ foreach (var tag in item.Tags)
+ {
+ builder.Append("<Tag>" + SecurityElement.Escape(tag) + "</Tag>");
}
+
+ builder.Append("</Tags>");
}
- var hasKeywords = item as IHasKeywords;
- if (hasKeywords != null)
+ if (item.Keywords.Count > 0)
{
- if (hasKeywords.Keywords.Count > 0)
- {
- builder.Append("<PlotKeywords>");
-
- foreach (var tag in hasKeywords.Keywords)
- {
- builder.Append("<PlotKeyword>" + SecurityElement.Escape(tag) + "</PlotKeyword>");
- }
+ builder.Append("<PlotKeywords>");
- builder.Append("</PlotKeywords>");
+ foreach (var tag in item.Keywords)
+ {
+ builder.Append("<PlotKeyword>" + SecurityElement.Escape(tag) + "</PlotKeyword>");
}
+
+ builder.Append("</PlotKeywords>");
}
var people = libraryManager.GetPeople(item);
diff --git a/MediaBrowser.MediaEncoding/Encoder/AudioEncoder.cs b/MediaBrowser.MediaEncoding/Encoder/AudioEncoder.cs
index 2d5225344..cd1575fa5 100644
--- a/MediaBrowser.MediaEncoding/Encoder/AudioEncoder.cs
+++ b/MediaBrowser.MediaEncoding/Encoder/AudioEncoder.cs
@@ -6,6 +6,7 @@ using MediaBrowser.Model.IO;
using MediaBrowser.Model.Logging;
using System;
using System.Collections.Generic;
+using System.Threading.Tasks;
using CommonIO;
namespace MediaBrowser.MediaEncoding.Encoder
@@ -16,7 +17,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
{
}
- protected override string GetCommandLineArguments(EncodingJob state)
+ protected override Task<string> GetCommandLineArguments(EncodingJob state)
{
var audioTranscodeParams = new List<string>();
@@ -61,7 +62,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
vn = " -vn";
}
- return string.Format("{0} {1}{6}{7} -threads {2}{3} {4} -id3v2_version 3 -write_id3v1 1{8} -y \"{5}\"",
+ var result = string.Format("{0} {1}{6}{7} -threads {2}{3} {4} -id3v2_version 3 -write_id3v1 1{8} -y \"{5}\"",
inputModifier,
GetInputArgument(state),
threads,
@@ -71,6 +72,8 @@ namespace MediaBrowser.MediaEncoding.Encoder
albumCoverInput,
mapArgs,
metadata).Trim();
+
+ return Task.FromResult(result);
}
protected override string GetOutputFileExtension(EncodingJob state)
diff --git a/MediaBrowser.MediaEncoding/Encoder/BaseEncoder.cs b/MediaBrowser.MediaEncoding/Encoder/BaseEncoder.cs
index d551d5c8c..77bd50b9b 100644
--- a/MediaBrowser.MediaEncoding/Encoder/BaseEncoder.cs
+++ b/MediaBrowser.MediaEncoding/Encoder/BaseEncoder.cs
@@ -42,8 +42,8 @@ namespace MediaBrowser.MediaEncoding.Encoder
IFileSystem fileSystem,
IIsoManager isoManager,
ILibraryManager libraryManager,
- ISessionManager sessionManager,
- ISubtitleEncoder subtitleEncoder,
+ ISessionManager sessionManager,
+ ISubtitleEncoder subtitleEncoder,
IMediaSourceManager mediaSourceManager)
{
MediaEncoder = mediaEncoder;
@@ -71,12 +71,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
await AcquireResources(encodingJob, cancellationToken).ConfigureAwait(false);
- var commandLineArgs = GetCommandLineArguments(encodingJob);
-
- if (GetEncodingOptions().EnableDebugLogging)
- {
- commandLineArgs = "-loglevel debug " + commandLineArgs;
- }
+ var commandLineArgs = await GetCommandLineArguments(encodingJob).ConfigureAwait(false);
var process = new Process
{
@@ -136,7 +131,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
}
cancellationToken.Register(() => Cancel(process, encodingJob));
-
+
// MUST read both stdout and stderr asynchronously or a deadlock may occurr
process.BeginOutputReadLine();
@@ -144,7 +139,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
new JobLogger(Logger).StartStreamingLog(encodingJob, process.StandardError.BaseStream, encodingJob.LogFileStream);
// Wait for the file to exist before proceeeding
- while (!FileSystem.FileExists(encodingJob.OutputFilePath) && !encodingJob.HasExited)
+ while (!FileSystem.FileExists(encodingJob.OutputFilePath) && !encodingJob.HasExited)
{
await Task.Delay(100, cancellationToken).ConfigureAwait(false);
}
@@ -269,11 +264,11 @@ namespace MediaBrowser.MediaEncoding.Encoder
return ConfigurationManager.GetConfiguration<EncodingOptions>("encoding");
}
- protected abstract string GetCommandLineArguments(EncodingJob job);
+ protected abstract Task<string> GetCommandLineArguments(EncodingJob job);
private string GetOutputFilePath(EncodingJob state)
{
- var folder = string.IsNullOrWhiteSpace(state.Options.OutputDirectory) ?
+ var folder = string.IsNullOrWhiteSpace(state.Options.OutputDirectory) ?
ConfigurationManager.ApplicationPaths.TranscodingTempPath :
state.Options.OutputDirectory;
@@ -368,7 +363,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
{
if (string.Equals(state.OutputVideoCodec, "copy", StringComparison.OrdinalIgnoreCase))
{
- return null;
+ return null;
}
if (state.VideoStream != null && !string.IsNullOrWhiteSpace(state.VideoStream.Codec))
@@ -382,7 +377,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
if (MediaEncoder.SupportsDecoder("h264_qsv"))
{
// Seeing stalls and failures with decoding. Not worth it compared to encoding.
- //return "-c:v h264_qsv ";
+ return "-c:v h264_qsv ";
}
break;
case "mpeg2video":
@@ -541,7 +536,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
/// <param name="state">The state.</param>
/// <param name="outputVideoCodec">The output video codec.</param>
/// <returns>System.String.</returns>
- protected string GetGraphicalSubtitleParam(EncodingJob state, string outputVideoCodec)
+ protected async Task<string> GetGraphicalSubtitleParam(EncodingJob state, string outputVideoCodec)
{
var outputSizeParam = string.Empty;
@@ -550,7 +545,8 @@ namespace MediaBrowser.MediaEncoding.Encoder
// Add resolution params, if specified
if (request.Width.HasValue || request.Height.HasValue || request.MaxHeight.HasValue || request.MaxWidth.HasValue)
{
- outputSizeParam = GetOutputSizeParam(state, outputVideoCodec).TrimEnd('"');
+ outputSizeParam = await GetOutputSizeParam(state, outputVideoCodec).ConfigureAwait(false);
+ outputSizeParam = outputSizeParam.TrimEnd('"');
outputSizeParam = "," + outputSizeParam.Substring(outputSizeParam.IndexOf("scale", StringComparison.OrdinalIgnoreCase));
}
@@ -676,17 +672,20 @@ namespace MediaBrowser.MediaEncoding.Encoder
if (!string.IsNullOrEmpty(state.Options.Profile))
{
- param += " -profile:v " + state.Options.Profile;
+ if (!string.Equals(videoCodec, "h264_omx", StringComparison.OrdinalIgnoreCase))
+ {
+ // not supported by h264_omx
+ param += " -profile:v " + state.Options.Profile;
+ }
}
var levelString = state.Options.Level.HasValue ? state.Options.Level.Value.ToString(CultureInfo.InvariantCulture) : null;
if (!string.IsNullOrEmpty(levelString))
{
- var h264Encoder = EncodingJobFactory.GetH264Encoder(state, GetEncodingOptions());
-
// h264_qsv and libnvenc expect levels to be expressed as a decimal. libx264 supports decimal and non-decimal format
- if (String.Equals(h264Encoder, "h264_qsv", StringComparison.OrdinalIgnoreCase) || String.Equals(h264Encoder, "libnvenc", StringComparison.OrdinalIgnoreCase))
+ if (string.Equals(videoCodec, "h264_qsv", StringComparison.OrdinalIgnoreCase) ||
+ string.Equals(videoCodec, "libnvenc", StringComparison.OrdinalIgnoreCase))
{
switch (levelString)
{
@@ -722,13 +721,20 @@ namespace MediaBrowser.MediaEncoding.Encoder
break;
}
}
- else
+ else if (!string.Equals(videoCodec, "h264_omx", StringComparison.OrdinalIgnoreCase))
{
param += " -level " + levelString;
}
}
- return "-pix_fmt yuv420p " + param;
+ if (!string.Equals(videoCodec, "h264_omx", StringComparison.OrdinalIgnoreCase) &&
+ !string.Equals(videoCodec, "h264_qsv", StringComparison.OrdinalIgnoreCase) &&
+ !string.Equals(videoCodec, "libnvenc", StringComparison.OrdinalIgnoreCase))
+ {
+ param = "-pix_fmt yuv420p " + param;
+ }
+
+ return param;
}
protected string GetVideoBitrateParam(EncodingJob state, string videoCodec)
@@ -863,7 +869,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
/// <param name="outputVideoCodec">The output video codec.</param>
/// <param name="allowTimeStampCopy">if set to <c>true</c> [allow time stamp copy].</param>
/// <returns>System.String.</returns>
- protected string GetOutputSizeParam(EncodingJob state,
+ protected async Task<string> GetOutputSizeParam(EncodingJob state,
string outputVideoCodec,
bool allowTimeStampCopy = true)
{
@@ -940,7 +946,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
if (state.SubtitleStream != null && state.SubtitleStream.IsTextSubtitleStream && state.Options.SubtitleMethod == SubtitleDeliveryMethod.Encode)
{
- var subParam = GetTextSubtitleParam(state);
+ var subParam = await GetTextSubtitleParam(state).ConfigureAwait(false);
filters.Add(subParam);
@@ -963,7 +969,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
/// </summary>
/// <param name="state">The state.</param>
/// <returns>System.String.</returns>
- protected string GetTextSubtitleParam(EncodingJob state)
+ protected async Task<string> GetTextSubtitleParam(EncodingJob state)
{
var seconds = Math.Round(TimeSpan.FromTicks(state.Options.StartTimeTicks ?? 0).TotalSeconds);
@@ -975,7 +981,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
if (!string.IsNullOrEmpty(state.SubtitleStream.Language))
{
- var charenc = SubtitleEncoder.GetSubtitleFileCharacterSet(subtitlePath, state.SubtitleStream.Language, state.MediaSource.Protocol, CancellationToken.None).Result;
+ var charenc = await SubtitleEncoder.GetSubtitleFileCharacterSet(subtitlePath, state.SubtitleStream.Language, state.MediaSource.Protocol, CancellationToken.None).ConfigureAwait(false);
if (!string.IsNullOrEmpty(charenc))
{
diff --git a/MediaBrowser.Server.Startup.Common/FFMpeg/FFmpegValidator.cs b/MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs
index 0ae021407..133cc8d70 100644
--- a/MediaBrowser.Server.Startup.Common/FFMpeg/FFmpegValidator.cs
+++ b/MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs
@@ -1,48 +1,42 @@
-using MediaBrowser.Common.Configuration;
-using MediaBrowser.Model.Logging;
-using System;
+using System;
+using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
-using System.Collections.Generic;
-using CommonIO;
+using MediaBrowser.Model.Logging;
-namespace MediaBrowser.Server.Startup.Common.FFMpeg
+namespace MediaBrowser.MediaEncoding.Encoder
{
- public class FFmpegValidator
+ public class EncoderValidator
{
private readonly ILogger _logger;
- private readonly IApplicationPaths _appPaths;
- private readonly IFileSystem _fileSystem;
- public FFmpegValidator(ILogger logger, IApplicationPaths appPaths, IFileSystem fileSystem)
+ public EncoderValidator(ILogger logger)
{
_logger = logger;
- _appPaths = appPaths;
- _fileSystem = fileSystem;
}
- public Tuple<List<string>,List<string>> Validate(FFMpegInfo info)
+ public Tuple<List<string>, List<string>> Validate(string encoderPath)
{
- _logger.Info("FFMpeg: {0}", info.EncoderPath);
- _logger.Info("FFProbe: {0}", info.ProbePath);
+ _logger.Info("Validating media encoder at {0}", encoderPath);
+
+ var decoders = GetDecoders(encoderPath);
+ var encoders = GetEncoders(encoderPath);
- var decoders = GetDecoders(info.EncoderPath);
- var encoders = GetEncoders(info.EncoderPath);
+ _logger.Info("Encoder validation complete");
return new Tuple<List<string>, List<string>>(decoders, encoders);
}
- private List<string> GetDecoders(string ffmpegPath)
+ private List<string> GetDecoders(string encoderAppPath)
{
string output = string.Empty;
try
{
- output = GetFFMpegOutput(ffmpegPath, "-decoders");
+ output = GetProcessOutput(encoderAppPath, "-decoders");
}
catch
{
}
- //_logger.Debug("ffmpeg decoder query result: {0}", output ?? string.Empty);
var found = new List<string>();
var required = new[]
@@ -56,12 +50,9 @@ namespace MediaBrowser.Server.Startup.Common.FFMpeg
{
var srch = " " + codec + " ";
- if (output.IndexOf(srch, StringComparison.OrdinalIgnoreCase) == -1)
- {
- _logger.Warn("ffmpeg is missing decoder " + codec);
- }
- else
+ if (output.IndexOf(srch, StringComparison.OrdinalIgnoreCase) != -1)
{
+ _logger.Info("Decoder available: " + codec);
found.Add(codec);
}
}
@@ -69,17 +60,16 @@ namespace MediaBrowser.Server.Startup.Common.FFMpeg
return found;
}
- private List<string> GetEncoders(string ffmpegPath)
+ private List<string> GetEncoders(string encoderAppPath)
{
string output = null;
try
{
- output = GetFFMpegOutput(ffmpegPath, "-encoders");
+ output = GetProcessOutput(encoderAppPath, "-encoders");
}
catch
{
}
- //_logger.Debug("ffmpeg encoder query result: {0}", output ?? string.Empty);
var found = new List<string>();
var required = new[]
@@ -94,19 +84,18 @@ namespace MediaBrowser.Server.Startup.Common.FFMpeg
"libmp3lame",
"libopus",
//"libvorbis",
- "srt"
+ "srt",
+ "libnvenc",
+ "h264_qsv"
};
foreach (var codec in required)
{
var srch = " " + codec + " ";
- if (output.IndexOf(srch, StringComparison.OrdinalIgnoreCase) == -1)
- {
- _logger.Warn("ffmpeg is missing encoder " + codec);
- }
- else
+ if (output.IndexOf(srch, StringComparison.OrdinalIgnoreCase) != -1)
{
+ _logger.Info("Encoder available: " + codec);
found.Add(codec);
}
}
@@ -114,7 +103,7 @@ namespace MediaBrowser.Server.Startup.Common.FFMpeg
return found;
}
- private string GetFFMpegOutput(string path, string arguments)
+ private string GetProcessOutput(string path, string arguments)
{
var process = new Process
{
@@ -139,13 +128,12 @@ namespace MediaBrowser.Server.Startup.Common.FFMpeg
{
process.BeginErrorReadLine();
- using (var reader = new StreamReader(process.StandardOutput.BaseStream))
- {
- return reader.ReadToEnd();
- }
+ return process.StandardOutput.ReadToEnd();
}
catch
{
+ _logger.Info("Killing process {0} {1}", path, arguments);
+
// Hate having to do this
try
{
@@ -153,7 +141,7 @@ namespace MediaBrowser.Server.Startup.Common.FFMpeg
}
catch (Exception ex1)
{
- _logger.ErrorException("Error killing ffmpeg", ex1);
+ _logger.ErrorException("Error killing process", ex1);
}
throw;
diff --git a/MediaBrowser.MediaEncoding/Encoder/EncodingJobFactory.cs b/MediaBrowser.MediaEncoding/Encoder/EncodingJobFactory.cs
index 1544a78b6..177009306 100644
--- a/MediaBrowser.MediaEncoding/Encoder/EncodingJobFactory.cs
+++ b/MediaBrowser.MediaEncoding/Encoder/EncodingJobFactory.cs
@@ -99,7 +99,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
if (videoRequest != null)
{
state.OutputVideoCodec = state.Options.VideoCodec;
- state.OutputVideoBitrate = GetVideoBitrateParamValue(state.Options, state.VideoStream);
+ state.OutputVideoBitrate = GetVideoBitrateParamValue(state.Options, state.VideoStream, state.OutputVideoCodec);
if (state.OutputVideoBitrate.HasValue)
{
@@ -396,7 +396,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
return request.AudioChannels;
}
- private int? GetVideoBitrateParamValue(EncodingJobOptions request, MediaStream videoStream)
+ private int? GetVideoBitrateParamValue(EncodingJobOptions request, MediaStream videoStream, string outputVideoCodec)
{
var bitrate = request.VideoBitRate;
@@ -421,6 +421,18 @@ namespace MediaBrowser.MediaEncoding.Encoder
}
}
+ if (bitrate.HasValue)
+ {
+ var inputVideoCodec = videoStream == null ? null : videoStream.Codec;
+ bitrate = ResolutionNormalizer.ScaleBitrate(bitrate.Value, inputVideoCodec, outputVideoCodec);
+
+ // If a max bitrate was requested, don't let the scaled bitrate exceed it
+ if (request.VideoBitRate.HasValue)
+ {
+ bitrate = Math.Min(bitrate.Value, request.VideoBitRate.Value);
+ }
+ }
+
return bitrate;
}
@@ -544,13 +556,19 @@ namespace MediaBrowser.MediaEncoding.Encoder
internal static string GetH264Encoder(EncodingJob state, EncodingOptions options)
{
- if (string.Equals(options.HardwareAccelerationType, "qsv", StringComparison.OrdinalIgnoreCase))
+ if (string.Equals(options.HardwareAccelerationType, "qsv", StringComparison.OrdinalIgnoreCase) ||
+ string.Equals(options.HardwareAccelerationType, "h264_qsv", StringComparison.OrdinalIgnoreCase))
{
- // It's currently failing on live tv
- if (state.RunTimeTicks.HasValue)
- {
- return "h264_qsv";
- }
+ return "h264_qsv";
+ }
+
+ if (string.Equals(options.HardwareAccelerationType, "libnvenc", StringComparison.OrdinalIgnoreCase))
+ {
+ return "libnvenc";
+ }
+ if (string.Equals(options.HardwareAccelerationType, "h264_omx", StringComparison.OrdinalIgnoreCase))
+ {
+ return "h264_omx";
}
return "libx264";
diff --git a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs
index 399fdead9..7264ad2d1 100644
--- a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs
+++ b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs
@@ -21,6 +21,9 @@ using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using CommonIO;
+using MediaBrowser.Model.Configuration;
+using MediaBrowser.Common.Configuration;
+using MediaBrowser.Common.Extensions;
namespace MediaBrowser.MediaEncoding.Encoder
{
@@ -64,8 +67,6 @@ namespace MediaBrowser.MediaEncoding.Encoder
public string FFProbePath { get; private set; }
- public string Version { get; private set; }
-
protected readonly IServerConfigurationManager ConfigurationManager;
protected readonly IFileSystem FileSystem;
protected readonly ILiveTvManager LiveTvManager;
@@ -77,12 +78,12 @@ namespace MediaBrowser.MediaEncoding.Encoder
protected readonly Func<IMediaSourceManager> MediaSourceManager;
private readonly List<ProcessWrapper> _runningProcesses = new List<ProcessWrapper>();
+ private readonly bool _hasExternalEncoder;
- public MediaEncoder(ILogger logger, IJsonSerializer jsonSerializer, string ffMpegPath, string ffProbePath, string version, IServerConfigurationManager configurationManager, IFileSystem fileSystem, ILiveTvManager liveTvManager, IIsoManager isoManager, ILibraryManager libraryManager, IChannelManager channelManager, ISessionManager sessionManager, Func<ISubtitleEncoder> subtitleEncoder, Func<IMediaSourceManager> mediaSourceManager)
+ public MediaEncoder(ILogger logger, IJsonSerializer jsonSerializer, string ffMpegPath, string ffProbePath, bool hasExternalEncoder, IServerConfigurationManager configurationManager, IFileSystem fileSystem, ILiveTvManager liveTvManager, IIsoManager isoManager, ILibraryManager libraryManager, IChannelManager channelManager, ISessionManager sessionManager, Func<ISubtitleEncoder> subtitleEncoder, Func<IMediaSourceManager> mediaSourceManager)
{
_logger = logger;
_jsonSerializer = jsonSerializer;
- Version = version;
ConfigurationManager = configurationManager;
FileSystem = fileSystem;
LiveTvManager = liveTvManager;
@@ -94,6 +95,271 @@ namespace MediaBrowser.MediaEncoding.Encoder
MediaSourceManager = mediaSourceManager;
FFProbePath = ffProbePath;
FFMpegPath = ffMpegPath;
+
+ _hasExternalEncoder = hasExternalEncoder;
+ }
+
+ public string EncoderLocationType
+ {
+ get
+ {
+ if (_hasExternalEncoder)
+ {
+ return "External";
+ }
+
+ if (string.IsNullOrWhiteSpace(FFMpegPath))
+ {
+ return null;
+ }
+
+ if (IsSystemInstalledPath(FFMpegPath))
+ {
+ return "System";
+ }
+
+ return "Custom";
+ }
+ }
+
+ private bool IsSystemInstalledPath(string path)
+ {
+ if (path.IndexOf("/", StringComparison.Ordinal) == -1 && path.IndexOf("\\", StringComparison.Ordinal) == -1)
+ {
+ return true;
+ }
+
+ return false;
+ }
+
+ public async Task Init()
+ {
+ InitPaths();
+
+ if (!string.IsNullOrWhiteSpace(FFMpegPath))
+ {
+ var result = new EncoderValidator(_logger).Validate(FFMpegPath);
+
+ SetAvailableDecoders(result.Item1);
+ SetAvailableEncoders(result.Item2);
+ }
+ }
+
+ private void InitPaths()
+ {
+ ConfigureEncoderPaths();
+
+ if (_hasExternalEncoder)
+ {
+ LogPaths();
+ return;
+ }
+
+ // If the path was passed in, save it into config now.
+ var encodingOptions = GetEncodingOptions();
+ var appPath = encodingOptions.EncoderAppPath;
+
+ var valueToSave = FFMpegPath;
+
+ if (!string.IsNullOrWhiteSpace(valueToSave))
+ {
+ // if using system variable, don't save this.
+ if (IsSystemInstalledPath(valueToSave))
+ {
+ valueToSave = null;
+ }
+ }
+
+ if (!string.Equals(valueToSave, appPath, StringComparison.Ordinal))
+ {
+ encodingOptions.EncoderAppPath = valueToSave;
+ ConfigurationManager.SaveConfiguration("encoding", encodingOptions);
+ }
+ }
+
+ public async Task UpdateEncoderPath(string path, string pathType)
+ {
+ if (_hasExternalEncoder)
+ {
+ return;
+ }
+
+ Tuple<string, string> newPaths;
+
+ if (string.Equals(pathType, "system", StringComparison.OrdinalIgnoreCase))
+ {
+ path = "ffmpeg";
+
+ newPaths = TestForInstalledVersions();
+ }
+ else if (string.Equals(pathType, "custom", StringComparison.OrdinalIgnoreCase))
+ {
+ if (string.IsNullOrWhiteSpace(path))
+ {
+ throw new ArgumentNullException("path");
+ }
+
+ if (!File.Exists(path) && !Directory.Exists(path))
+ {
+ throw new ResourceNotFoundException();
+ }
+ newPaths = GetEncoderPaths(path);
+ }
+ else
+ {
+ throw new ArgumentException("Unexpected pathType value");
+ }
+
+ if (string.IsNullOrWhiteSpace(newPaths.Item1))
+ {
+ throw new ResourceNotFoundException("ffmpeg not found");
+ }
+ if (string.IsNullOrWhiteSpace(newPaths.Item2))
+ {
+ throw new ResourceNotFoundException("ffprobe not found");
+ }
+
+ var config = GetEncodingOptions();
+ config.EncoderAppPath = path;
+ ConfigurationManager.SaveConfiguration("encoding", config);
+
+ Init();
+ }
+
+ private void ConfigureEncoderPaths()
+ {
+ if (_hasExternalEncoder)
+ {
+ return;
+ }
+
+ var appPath = GetEncodingOptions().EncoderAppPath;
+
+ if (string.IsNullOrWhiteSpace(appPath))
+ {
+ appPath = Path.Combine(ConfigurationManager.ApplicationPaths.ProgramDataPath, "ffmpeg");
+ }
+
+ var newPaths = GetEncoderPaths(appPath);
+ if (string.IsNullOrWhiteSpace(newPaths.Item1) || string.IsNullOrWhiteSpace(newPaths.Item2))
+ {
+ newPaths = TestForInstalledVersions();
+ }
+
+ if (!string.IsNullOrWhiteSpace(newPaths.Item1) && !string.IsNullOrWhiteSpace(newPaths.Item2))
+ {
+ FFMpegPath = newPaths.Item1;
+ FFProbePath = newPaths.Item2;
+ }
+
+ LogPaths();
+ }
+
+ private Tuple<string, string> GetEncoderPaths(string configuredPath)
+ {
+ var appPath = configuredPath;
+
+ if (!string.IsNullOrWhiteSpace(appPath))
+ {
+ if (Directory.Exists(appPath))
+ {
+ return GetPathsFromDirectory(appPath);
+ }
+
+ if (File.Exists(appPath))
+ {
+ return new Tuple<string, string>(appPath, GetProbePathFromEncoderPath(appPath));
+ }
+ }
+
+ return new Tuple<string, string>(null, null);
+ }
+
+ private Tuple<string, string> TestForInstalledVersions()
+ {
+ string encoderPath = null;
+ string probePath = null;
+
+ if (TestSystemInstalled("ffmpeg"))
+ {
+ encoderPath = "ffmpeg";
+ }
+ if (TestSystemInstalled("ffprobe"))
+ {
+ probePath = "ffprobe";
+ }
+
+ return new Tuple<string, string>(encoderPath, probePath);
+ }
+
+ private bool TestSystemInstalled(string app)
+ {
+ try
+ {
+ var startInfo = new ProcessStartInfo
+ {
+ FileName = app,
+ Arguments = "-v",
+ UseShellExecute = false,
+ CreateNoWindow = true,
+ WindowStyle = ProcessWindowStyle.Hidden,
+ ErrorDialog = false
+ };
+
+ using (var process = Process.Start(startInfo))
+ {
+ process.WaitForExit();
+ }
+
+ _logger.Debug("System app installed: " + app);
+ return true;
+ }
+ catch
+ {
+ _logger.Debug("System app not installed: " + app);
+ return false;
+ }
+ }
+
+ private Tuple<string, string> GetPathsFromDirectory(string path)
+ {
+ // Since we can't predict the file extension, first try directly within the folder
+ // If that doesn't pan out, then do a recursive search
+ var files = Directory.GetFiles(path);
+
+ var ffmpegPath = files.FirstOrDefault(i => string.Equals(Path.GetFileNameWithoutExtension(i), "ffmpeg", StringComparison.OrdinalIgnoreCase));
+ var ffprobePath = files.FirstOrDefault(i => string.Equals(Path.GetFileNameWithoutExtension(i), "ffprobe", StringComparison.OrdinalIgnoreCase));
+
+ if (string.IsNullOrWhiteSpace(ffmpegPath) || !File.Exists(ffmpegPath))
+ {
+ files = Directory.GetFiles(path, "*", SearchOption.AllDirectories);
+
+ ffmpegPath = files.FirstOrDefault(i => string.Equals(Path.GetFileNameWithoutExtension(i), "ffmpeg", StringComparison.OrdinalIgnoreCase));
+
+ if (!string.IsNullOrWhiteSpace(ffmpegPath))
+ {
+ ffprobePath = GetProbePathFromEncoderPath(ffmpegPath);
+ }
+ }
+
+ return new Tuple<string, string>(ffmpegPath, ffprobePath);
+ }
+
+ private string GetProbePathFromEncoderPath(string appPath)
+ {
+ return Directory.GetFiles(Path.GetDirectoryName(appPath))
+ .FirstOrDefault(i => string.Equals(Path.GetFileNameWithoutExtension(i), "ffprobe", StringComparison.OrdinalIgnoreCase));
+ }
+
+ private void LogPaths()
+ {
+ _logger.Info("FFMpeg: {0}", FFMpegPath ?? "not found");
+ _logger.Info("FFProbe: {0}", FFProbePath ?? "not found");
+ }
+
+ private EncodingOptions GetEncodingOptions()
+ {
+ return ConfigurationManager.GetConfiguration<EncodingOptions>("encoding");
}
private List<string> _encoders = new List<string>();
diff --git a/MediaBrowser.MediaEncoding/Encoder/VideoEncoder.cs b/MediaBrowser.MediaEncoding/Encoder/VideoEncoder.cs
index 3ab55168d..82a966821 100644
--- a/MediaBrowser.MediaEncoding/Encoder/VideoEncoder.cs
+++ b/MediaBrowser.MediaEncoding/Encoder/VideoEncoder.cs
@@ -7,6 +7,7 @@ using MediaBrowser.Model.IO;
using MediaBrowser.Model.Logging;
using System;
using System.IO;
+using System.Threading.Tasks;
using CommonIO;
namespace MediaBrowser.MediaEncoding.Encoder
@@ -17,7 +18,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
{
}
- protected override string GetCommandLineArguments(EncodingJob state)
+ protected override async Task<string> GetCommandLineArguments(EncodingJob state)
{
// Get the output codec name
var videoCodec = EncodingJobFactory.GetVideoEncoder(state, GetEncodingOptions());
@@ -36,12 +37,14 @@ namespace MediaBrowser.MediaEncoding.Encoder
var inputModifier = GetInputModifier(state);
+ var videoArguments = await GetVideoArguments(state, videoCodec).ConfigureAwait(false);
+
return string.Format("{0} {1}{2} {3} {4} -map_metadata -1 -threads {5} {6}{7} -y \"{8}\"",
inputModifier,
GetInputArgument(state),
keyFrame,
GetMapArgs(state),
- GetVideoArguments(state, videoCodec),
+ videoArguments,
threads,
GetAudioArguments(state),
format,
@@ -55,7 +58,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
/// <param name="state">The state.</param>
/// <param name="videoCodec">The video codec.</param>
/// <returns>System.String.</returns>
- private string GetVideoArguments(EncodingJob state, string videoCodec)
+ private async Task<string> GetVideoArguments(EncodingJob state, string videoCodec)
{
var args = "-codec:v:0 " + videoCodec;
@@ -91,7 +94,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
// Add resolution params, if specified
if (!hasGraphicalSubs)
{
- args += GetOutputSizeParam(state, videoCodec);
+ args += await GetOutputSizeParam(state, videoCodec).ConfigureAwait(false);
}
var qualityParam = GetVideoQualityParam(state, videoCodec);
@@ -104,7 +107,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
// This is for internal graphical subs
if (hasGraphicalSubs)
{
- args += GetGraphicalSubtitleParam(state, videoCodec);
+ args += await GetGraphicalSubtitleParam(state, videoCodec).ConfigureAwait(false);
}
return args;
diff --git a/MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj b/MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj
index 5576a49bd..794353451 100644
--- a/MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj
+++ b/MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj
@@ -71,6 +71,7 @@
<Compile Include="Encoder\EncodingJob.cs" />
<Compile Include="Encoder\EncodingJobFactory.cs" />
<Compile Include="Encoder\EncodingUtils.cs" />
+ <Compile Include="Encoder\EncoderValidator.cs" />
<Compile Include="Encoder\JobLogger.cs" />
<Compile Include="Encoder\MediaEncoder.cs" />
<Compile Include="Encoder\VideoEncoder.cs" />
diff --git a/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs b/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs
index 44c69d4c1..44a0f264d 100644
--- a/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs
+++ b/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs
@@ -408,7 +408,9 @@ namespace MediaBrowser.MediaEncoding.Probing
Level = streamInfo.level,
Index = streamInfo.index,
PixelFormat = streamInfo.pix_fmt,
- NalLengthSize = streamInfo.nal_length_size
+ NalLengthSize = streamInfo.nal_length_size,
+ TimeBase = streamInfo.time_base,
+ CodecTimeBase = streamInfo.codec_time_base
};
if (string.Equals(streamInfo.is_avc, "true", StringComparison.OrdinalIgnoreCase) ||
diff --git a/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs b/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs
index 8c33cc7c0..25adbcdb0 100644
--- a/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs
+++ b/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs
@@ -58,6 +58,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles
string outputFormat,
long startTimeTicks,
long? endTimeTicks,
+ bool preserveOriginalTimestamps,
CancellationToken cancellationToken)
{
var ms = new MemoryStream();
@@ -68,7 +69,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles
var trackInfo = reader.Parse(stream, cancellationToken);
- FilterEvents(trackInfo, startTimeTicks, endTimeTicks, false);
+ FilterEvents(trackInfo, startTimeTicks, endTimeTicks, preserveOriginalTimestamps);
var writer = GetWriter(outputFormat);
@@ -116,6 +117,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles
string outputFormat,
long startTimeTicks,
long? endTimeTicks,
+ bool preserveOriginalTimestamps,
CancellationToken cancellationToken)
{
var subtitle = await GetSubtitleStream(itemId, mediaSourceId, subtitleStreamIndex, cancellationToken)
@@ -130,7 +132,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles
using (var stream = subtitle.Item1)
{
- return await ConvertSubtitles(stream, inputFormat, outputFormat, startTimeTicks, endTimeTicks, cancellationToken).ConfigureAwait(false);
+ return await ConvertSubtitles(stream, inputFormat, outputFormat, startTimeTicks, endTimeTicks, preserveOriginalTimestamps, cancellationToken).ConfigureAwait(false);
}
}
diff --git a/MediaBrowser.Model.Portable/MediaBrowser.Model.Portable.csproj b/MediaBrowser.Model.Portable/MediaBrowser.Model.Portable.csproj
index ba4d1b59e..0de9fb519 100644
--- a/MediaBrowser.Model.Portable/MediaBrowser.Model.Portable.csproj
+++ b/MediaBrowser.Model.Portable/MediaBrowser.Model.Portable.csproj
@@ -230,12 +230,6 @@
<Compile Include="..\MediaBrowser.Model\Configuration\SubtitlePlaybackMode.cs">
<Link>Configuration\SubtitlePlaybackMode.cs</Link>
</Compile>
- <Compile Include="..\MediaBrowser.Model\Configuration\TheMovieDbOptions.cs">
- <Link>Configuration\TheMovieDbOptions.cs</Link>
- </Compile>
- <Compile Include="..\MediaBrowser.Model\Configuration\TvdbOptions.cs">
- <Link>Configuration\TvdbOptions.cs</Link>
- </Compile>
<Compile Include="..\MediaBrowser.Model\Configuration\UnratedItem.cs">
<Link>Configuration\UnratedItem.cs</Link>
</Compile>
@@ -605,9 +599,6 @@
<Compile Include="..\MediaBrowser.Model\Entities\Video3DFormat.cs">
<Link>Entities\Video3DFormat.cs</Link>
</Compile>
- <Compile Include="..\MediaBrowser.Model\Entities\VideoSize.cs">
- <Link>Entities\VideoSize.cs</Link>
- </Compile>
<Compile Include="..\MediaBrowser.Model\Entities\VideoType.cs">
<Link>Entities\VideoType.cs</Link>
</Compile>
@@ -1145,6 +1136,9 @@
<Compile Include="..\MediaBrowser.Model\Sync\SyncTarget.cs">
<Link>Sync\SyncTarget.cs</Link>
</Compile>
+ <Compile Include="..\MediaBrowser.Model\System\Architecture.cs">
+ <Link>System\Architecture.cs</Link>
+ </Compile>
<Compile Include="..\MediaBrowser.Model\System\LogFile.cs">
<Link>System\LogFile.cs</Link>
</Compile>
diff --git a/MediaBrowser.Model.net35/MediaBrowser.Model.net35.csproj b/MediaBrowser.Model.net35/MediaBrowser.Model.net35.csproj
index f5d538cc4..fe0b3bcae 100644
--- a/MediaBrowser.Model.net35/MediaBrowser.Model.net35.csproj
+++ b/MediaBrowser.Model.net35/MediaBrowser.Model.net35.csproj
@@ -204,12 +204,6 @@
<Compile Include="..\MediaBrowser.Model\Configuration\SubtitlePlaybackMode.cs">
<Link>Configuration\SubtitlePlaybackMode.cs</Link>
</Compile>
- <Compile Include="..\MediaBrowser.Model\Configuration\TheMovieDbOptions.cs">
- <Link>Configuration\TheMovieDbOptions.cs</Link>
- </Compile>
- <Compile Include="..\MediaBrowser.Model\Configuration\TvdbOptions.cs">
- <Link>Configuration\TvdbOptions.cs</Link>
- </Compile>
<Compile Include="..\MediaBrowser.Model\Configuration\UnratedItem.cs">
<Link>Configuration\UnratedItem.cs</Link>
</Compile>
@@ -579,9 +573,6 @@
<Compile Include="..\MediaBrowser.Model\Entities\Video3DFormat.cs">
<Link>Entities\Video3DFormat.cs</Link>
</Compile>
- <Compile Include="..\MediaBrowser.Model\Entities\VideoSize.cs">
- <Link>Entities\VideoSize.cs</Link>
- </Compile>
<Compile Include="..\MediaBrowser.Model\Entities\VideoType.cs">
<Link>Entities\VideoType.cs</Link>
</Compile>
@@ -1110,6 +1101,9 @@
<Compile Include="..\MediaBrowser.Model\Sync\SyncTarget.cs">
<Link>Sync\SyncTarget.cs</Link>
</Compile>
+ <Compile Include="..\MediaBrowser.Model\System\Architecture.cs">
+ <Link>System\Architecture.cs</Link>
+ </Compile>
<Compile Include="..\MediaBrowser.Model\System\LogFile.cs">
<Link>System\LogFile.cs</Link>
</Compile>
diff --git a/MediaBrowser.Model/ApiClient/ServerCredentials.cs b/MediaBrowser.Model/ApiClient/ServerCredentials.cs
index 0f0ab65d4..19f68445e 100644
--- a/MediaBrowser.Model/ApiClient/ServerCredentials.cs
+++ b/MediaBrowser.Model/ApiClient/ServerCredentials.cs
@@ -57,6 +57,10 @@ namespace MediaBrowser.Model.ApiClient
{
existing.RemoteAddress = server.RemoteAddress;
}
+ if (!string.IsNullOrEmpty(server.ConnectServerId))
+ {
+ existing.ConnectServerId = server.ConnectServerId;
+ }
if (!string.IsNullOrEmpty(server.LocalAddress))
{
existing.LocalAddress = server.LocalAddress;
diff --git a/MediaBrowser.Model/ApiClient/ServerInfo.cs b/MediaBrowser.Model/ApiClient/ServerInfo.cs
index e1fa581d7..48995e80a 100644
--- a/MediaBrowser.Model/ApiClient/ServerInfo.cs
+++ b/MediaBrowser.Model/ApiClient/ServerInfo.cs
@@ -12,6 +12,7 @@ namespace MediaBrowser.Model.ApiClient
public String Name { get; set; }
public String Id { get; set; }
+ public String ConnectServerId { get; set; }
public String LocalAddress { get; set; }
public String RemoteAddress { get; set; }
public String ManualAddress { get; set; }
diff --git a/MediaBrowser.Model/Channels/ChannelFolderType.cs b/MediaBrowser.Model/Channels/ChannelFolderType.cs
index 9261cb5cd..7c97afd02 100644
--- a/MediaBrowser.Model/Channels/ChannelFolderType.cs
+++ b/MediaBrowser.Model/Channels/ChannelFolderType.cs
@@ -6,6 +6,12 @@ namespace MediaBrowser.Model.Channels
MusicAlbum = 1,
- PhotoAlbum = 2
+ PhotoAlbum = 2,
+
+ MusicArtist = 3,
+
+ Series = 4,
+
+ Season = 5
}
} \ No newline at end of file
diff --git a/MediaBrowser.Model/Configuration/EncodingOptions.cs b/MediaBrowser.Model/Configuration/EncodingOptions.cs
index 516d00ee6..91d28a296 100644
--- a/MediaBrowser.Model/Configuration/EncodingOptions.cs
+++ b/MediaBrowser.Model/Configuration/EncodingOptions.cs
@@ -6,10 +6,10 @@ namespace MediaBrowser.Model.Configuration
public int EncodingThreadCount { get; set; }
public string TranscodingTempPath { get; set; }
public double DownMixAudioBoost { get; set; }
- public bool EnableDebugLogging { get; set; }
public bool EnableThrottling { get; set; }
public int ThrottleDelaySeconds { get; set; }
public string HardwareAccelerationType { get; set; }
+ public string EncoderAppPath { get; set; }
public EncodingOptions()
{
diff --git a/MediaBrowser.Model/Configuration/FanartOptions.cs b/MediaBrowser.Model/Configuration/FanartOptions.cs
index e992abe5d..6924b25d7 100644
--- a/MediaBrowser.Model/Configuration/FanartOptions.cs
+++ b/MediaBrowser.Model/Configuration/FanartOptions.cs
@@ -4,11 +4,6 @@ namespace MediaBrowser.Model.Configuration
public class FanartOptions
{
/// <summary>
- /// Gets or sets a value indicating whether [enable automatic updates].
- /// </summary>
- /// <value><c>true</c> if [enable automatic updates]; otherwise, <c>false</c>.</value>
- public bool EnableAutomaticUpdates { get; set; }
- /// <summary>
/// Gets or sets the user API key.
/// </summary>
/// <value>The user API key.</value>
diff --git a/MediaBrowser.Model/Configuration/ServerConfiguration.cs b/MediaBrowser.Model/Configuration/ServerConfiguration.cs
index 1cb19afdf..f779fcd61 100644
--- a/MediaBrowser.Model/Configuration/ServerConfiguration.cs
+++ b/MediaBrowser.Model/Configuration/ServerConfiguration.cs
@@ -67,7 +67,7 @@ namespace MediaBrowser.Model.Configuration
/// </summary>
/// <value><c>true</c> if [enable case sensitive item ids]; otherwise, <c>false</c>.</value>
public bool EnableCaseSensitiveItemIds { get; set; }
-
+
/// <summary>
/// Gets or sets the metadata path.
/// </summary>
@@ -87,12 +87,6 @@ namespace MediaBrowser.Model.Configuration
public bool SaveLocalMeta { get; set; }
/// <summary>
- /// Gets or sets a value indicating whether [enable localized guids].
- /// </summary>
- /// <value><c>true</c> if [enable localized guids]; otherwise, <c>false</c>.</value>
- public bool EnableLocalizedGuids { get; set; }
-
- /// <summary>
/// Gets or sets the preferred metadata language.
/// </summary>
/// <value>The preferred metadata language.</value>
@@ -161,7 +155,7 @@ namespace MediaBrowser.Model.Configuration
/// </summary>
/// <value>The dashboard source path.</value>
public string DashboardSourcePath { get; set; }
-
+
/// <summary>
/// Gets or sets the image saving convention.
/// </summary>
@@ -190,33 +184,39 @@ namespace MediaBrowser.Model.Configuration
public bool EnableVideoArchiveFiles { get; set; }
public int RemoteClientBitrateLimit { get; set; }
- public bool DenyIFrameEmbedding { get; set; }
-
public AutoOnOff EnableLibraryMonitor { get; set; }
public int SharingExpirationDays { get; set; }
- public bool EnableDateLastRefresh { get; set; }
-
public string[] Migrations { get; set; }
public int MigrationVersion { get; set; }
public int SchemaVersion { get; set; }
+ public int SqliteCachePages { get; set; }
public bool DownloadImagesInAdvance { get; set; }
public bool EnableAnonymousUsageReporting { get; set; }
public bool EnableStandaloneMusicKeys { get; set; }
+ public bool EnableLocalizedGuids { get; set; }
+ public bool EnableFolderView { get; set; }
+ public bool EnableGroupingIntoCollections { get; set; }
+ public bool DisplaySpecialsWithinSeasons { get; set; }
+ public bool DisplayCollectionsView { get; set; }
+ public string[] LocalNetworkAddresses { get; set; }
/// <summary>
/// Initializes a new instance of the <see cref="ServerConfiguration" /> class.
/// </summary>
public ServerConfiguration()
{
+ LocalNetworkAddresses = new string[] { };
Migrations = new string[] { };
+ SqliteCachePages = 10000;
- EnableLocalizedGuids = true;
EnableCustomPathSubFolders = true;
+ EnableLocalizedGuids = true;
+ DisplaySpecialsWithinSeasons = true;
ImageSavingConvention = ImageSavingConvention.Compatible;
PublicPort = 8096;
@@ -229,10 +229,8 @@ namespace MediaBrowser.Model.Configuration
EnableAnonymousUsageReporting = true;
EnableAutomaticRestart = true;
- DenyIFrameEmbedding = true;
EnableUPnP = true;
-
SharingExpirationDays = 30;
MinResumePct = 5;
MaxResumePct = 90;
diff --git a/MediaBrowser.Model/Configuration/TheMovieDbOptions.cs b/MediaBrowser.Model/Configuration/TheMovieDbOptions.cs
deleted file mode 100644
index 9a73e3476..000000000
--- a/MediaBrowser.Model/Configuration/TheMovieDbOptions.cs
+++ /dev/null
@@ -1,12 +0,0 @@
-
-namespace MediaBrowser.Model.Configuration
-{
- public class TheMovieDbOptions
- {
- /// <summary>
- /// Gets or sets a value indicating whether [enable automatic updates].
- /// </summary>
- /// <value><c>true</c> if [enable automatic updates]; otherwise, <c>false</c>.</value>
- public bool EnableAutomaticUpdates { get; set; }
- }
-}
diff --git a/MediaBrowser.Model/Configuration/TvdbOptions.cs b/MediaBrowser.Model/Configuration/TvdbOptions.cs
deleted file mode 100644
index 034af609c..000000000
--- a/MediaBrowser.Model/Configuration/TvdbOptions.cs
+++ /dev/null
@@ -1,12 +0,0 @@
-
-namespace MediaBrowser.Model.Configuration
-{
- public class TvdbOptions
- {
- /// <summary>
- /// Gets or sets a value indicating whether [enable automatic updates].
- /// </summary>
- /// <value><c>true</c> if [enable automatic updates]; otherwise, <c>false</c>.</value>
- public bool EnableAutomaticUpdates { get; set; }
- }
-}
diff --git a/MediaBrowser.Model/Configuration/UserConfiguration.cs b/MediaBrowser.Model/Configuration/UserConfiguration.cs
index 5f42dd2de..69dc23b21 100644
--- a/MediaBrowser.Model/Configuration/UserConfiguration.cs
+++ b/MediaBrowser.Model/Configuration/UserConfiguration.cs
@@ -34,14 +34,11 @@ namespace MediaBrowser.Model.Configuration
public SubtitlePlaybackMode SubtitleMode { get; set; }
public bool DisplayCollectionsView { get; set; }
- public bool DisplayFoldersView { get; set; }
public bool EnableLocalPassword { get; set; }
public string[] OrderedViews { get; set; }
- public bool IncludeTrailersInSuggestions { get; set; }
-
public string[] LatestItemsExcludes { get; set; }
public string[] PlainFolderViews { get; set; }
@@ -51,7 +48,8 @@ namespace MediaBrowser.Model.Configuration
public bool RememberAudioSelections { get; set; }
public bool RememberSubtitleSelections { get; set; }
public bool EnableNextEpisodeAutoPlay { get; set; }
-
+ public bool DisplayFoldersView { get; set; }
+
/// <summary>
/// Initializes a new instance of the <see cref="UserConfiguration" /> class.
/// </summary>
@@ -69,8 +67,6 @@ namespace MediaBrowser.Model.Configuration
PlainFolderViews = new string[] { };
- IncludeTrailersInSuggestions = true;
-
GroupedFolders = new string[] { };
}
}
diff --git a/MediaBrowser.Model/Dlna/ResolutionNormalizer.cs b/MediaBrowser.Model/Dlna/ResolutionNormalizer.cs
index 8a412ac2c..ed18fed65 100644
--- a/MediaBrowser.Model/Dlna/ResolutionNormalizer.cs
+++ b/MediaBrowser.Model/Dlna/ResolutionNormalizer.cs
@@ -56,5 +56,25 @@ namespace MediaBrowser.Model.Dlna
MaxHeight = maxHeight
};
}
+
+ private static double GetVideoBitrateScaleFactor(string codec)
+ {
+ if (string.Equals(codec, "h265", StringComparison.OrdinalIgnoreCase) ||
+ string.Equals(codec, "hevc", StringComparison.OrdinalIgnoreCase))
+ {
+ return .5;
+ }
+ return 1;
+ }
+
+ public static int ScaleBitrate(int bitrate, string inputVideoCodec, string outputVideoCodec)
+ {
+ var inputScaleFactor = GetVideoBitrateScaleFactor(inputVideoCodec);
+ var outputScaleFactor = GetVideoBitrateScaleFactor(outputVideoCodec);
+ var scaleFactor = outputScaleFactor/inputScaleFactor;
+ var newBitrate = scaleFactor*bitrate;
+
+ return Convert.ToInt32(newBitrate);
+ }
}
}
diff --git a/MediaBrowser.Model/Dlna/StreamBuilder.cs b/MediaBrowser.Model/Dlna/StreamBuilder.cs
index 7721bfd15..41efa51b9 100644
--- a/MediaBrowser.Model/Dlna/StreamBuilder.cs
+++ b/MediaBrowser.Model/Dlna/StreamBuilder.cs
@@ -216,7 +216,15 @@ namespace MediaBrowser.Model.Dlna
playlistItem.TranscodeSeekInfo = transcodingProfile.TranscodeSeekInfo;
playlistItem.EstimateContentLength = transcodingProfile.EstimateContentLength;
playlistItem.Container = transcodingProfile.Container;
- playlistItem.AudioCodec = transcodingProfile.AudioCodec;
+
+ if (string.IsNullOrEmpty(transcodingProfile.AudioCodec))
+ {
+ playlistItem.AudioCodecs = new string[] { };
+ }
+ else
+ {
+ playlistItem.AudioCodecs = transcodingProfile.AudioCodec.Split(',');
+ }
playlistItem.SubProtocol = transcodingProfile.Protocol;
List<CodecProfile> audioCodecProfiles = new List<CodecProfile>();
@@ -439,22 +447,7 @@ namespace MediaBrowser.Model.Dlna
playlistItem.EstimateContentLength = transcodingProfile.EstimateContentLength;
playlistItem.TranscodeSeekInfo = transcodingProfile.TranscodeSeekInfo;
- // TODO: We should probably preserve the full list and sent it to the server that way
- string[] supportedAudioCodecs = transcodingProfile.AudioCodec.Split(',');
- string inputAudioCodec = audioStream == null ? null : audioStream.Codec;
- foreach (string supportedAudioCodec in supportedAudioCodecs)
- {
- if (StringHelper.EqualsIgnoreCase(supportedAudioCodec, inputAudioCodec))
- {
- playlistItem.AudioCodec = supportedAudioCodec;
- break;
- }
- }
-
- if (string.IsNullOrEmpty(playlistItem.AudioCodec))
- {
- playlistItem.AudioCodec = supportedAudioCodecs[0];
- }
+ playlistItem.AudioCodecs = transcodingProfile.AudioCodec.Split(',');
playlistItem.VideoCodec = transcodingProfile.VideoCodec;
playlistItem.CopyTimestamps = transcodingProfile.CopyTimestamps;
@@ -488,7 +481,7 @@ namespace MediaBrowser.Model.Dlna
List<ProfileCondition> audioTranscodingConditions = new List<ProfileCondition>();
foreach (CodecProfile i in options.Profile.CodecProfiles)
{
- if (i.Type == CodecType.VideoAudio && i.ContainsCodec(playlistItem.AudioCodec, transcodingProfile.Container))
+ if (i.Type == CodecType.VideoAudio && i.ContainsCodec(playlistItem.TargetAudioCodec, transcodingProfile.Container))
{
foreach (ProfileCondition c in i.Conditions)
{
@@ -842,17 +835,17 @@ namespace MediaBrowser.Model.Dlna
{
bool requiresConversion = !StringHelper.EqualsIgnoreCase(subtitleStream.Codec, profile.Format);
- if (requiresConversion && !allowConversion)
+ if (!requiresConversion)
{
- continue;
+ return profile;
}
- if (!requiresConversion)
+ if (!allowConversion)
{
- return profile;
+ continue;
}
- if (subtitleStream.IsTextSubtitleStream && subtitleStream.SupportsExternalStream)
+ if (subtitleStream.IsTextSubtitleStream && subtitleStream.SupportsExternalStream && subtitleStream.SupportsSubtitleConversionTo(profile.Format))
{
return profile;
}
diff --git a/MediaBrowser.Model/Dlna/StreamInfo.cs b/MediaBrowser.Model/Dlna/StreamInfo.cs
index 313c30e2c..43a31f649 100644
--- a/MediaBrowser.Model/Dlna/StreamInfo.cs
+++ b/MediaBrowser.Model/Dlna/StreamInfo.cs
@@ -14,6 +14,11 @@ namespace MediaBrowser.Model.Dlna
/// </summary>
public class StreamInfo
{
+ public StreamInfo()
+ {
+ AudioCodecs = new string[] { };
+ }
+
public string ItemId { get; set; }
public PlayMethod PlayMethod { get; set; }
@@ -32,7 +37,7 @@ namespace MediaBrowser.Model.Dlna
public bool CopyTimestamps { get; set; }
public bool ForceLiveStream { get; set; }
- public string AudioCodec { get; set; }
+ public string[] AudioCodecs { get; set; }
public int? AudioStreamIndex { get; set; }
@@ -191,12 +196,16 @@ namespace MediaBrowser.Model.Dlna
{
List<NameValuePair> list = new List<NameValuePair>();
+ string audioCodecs = item.AudioCodecs.Length == 0 ?
+ string.Empty :
+ string.Join(",", item.AudioCodecs);
+
list.Add(new NameValuePair("DeviceProfileId", item.DeviceProfileId ?? string.Empty));
list.Add(new NameValuePair("DeviceId", item.DeviceId ?? string.Empty));
list.Add(new NameValuePair("MediaSourceId", item.MediaSourceId ?? string.Empty));
list.Add(new NameValuePair("Static", item.IsDirectStream.ToString().ToLower()));
list.Add(new NameValuePair("VideoCodec", item.VideoCodec ?? string.Empty));
- list.Add(new NameValuePair("AudioCodec", item.AudioCodec ?? string.Empty));
+ list.Add(new NameValuePair("AudioCodec", audioCodecs));
list.Add(new NameValuePair("AudioStreamIndex", item.AudioStreamIndex.HasValue ? StringHelper.ToStringCultureInvariant(item.AudioStreamIndex.Value) : string.Empty));
list.Add(new NameValuePair("SubtitleStreamIndex", item.SubtitleStreamIndex.HasValue && item.SubtitleDeliveryMethod != SubtitleDeliveryMethod.External ? StringHelper.ToStringCultureInvariant(item.SubtitleStreamIndex.Value) : string.Empty));
list.Add(new NameValuePair("VideoBitrate", item.VideoBitrate.HasValue ? StringHelper.ToStringCultureInvariant(item.VideoBitrate.Value) : string.Empty));
@@ -278,7 +287,7 @@ namespace MediaBrowser.Model.Dlna
// HLS will preserve timestamps so we can just grab the full subtitle stream
long startPositionTicks = StringHelper.EqualsIgnoreCase(SubProtocol, "hls")
? 0
- : (PlayMethod == PlayMethod.Transcode && !CopyTimestamps ? StartPositionTicks : 0);
+ : (PlayMethod == PlayMethod.Transcode && !CopyTimestamps ? StartPositionTicks : 0);
// First add the selected track
if (SubtitleStreamIndex.HasValue)
@@ -335,7 +344,8 @@ namespace MediaBrowser.Model.Dlna
Name = stream.Language ?? "Unknown",
Format = subtitleProfile.Format,
Index = stream.Index,
- DeliveryMethod = subtitleProfile.Method
+ DeliveryMethod = subtitleProfile.Method,
+ DisplayTitle = stream.DisplayTitle
};
if (info.DeliveryMethod == SubtitleDeliveryMethod.External)
@@ -554,9 +564,22 @@ namespace MediaBrowser.Model.Dlna
{
MediaStream stream = TargetAudioStream;
- return IsDirectStream
- ? (stream == null ? null : stream.Codec)
- : AudioCodec;
+ string inputCodec = stream == null ? null : stream.Codec;
+
+ if (IsDirectStream)
+ {
+ return inputCodec;
+ }
+
+ foreach (string codec in AudioCodecs)
+ {
+ if (StringHelper.EqualsIgnoreCase(codec, inputCodec))
+ {
+ return codec;
+ }
+ }
+
+ return AudioCodecs.Length == 0 ? null : AudioCodecs[0];
}
}
diff --git a/MediaBrowser.Model/Dlna/SubtitleStreamInfo.cs b/MediaBrowser.Model/Dlna/SubtitleStreamInfo.cs
index 61b2895fc..7a89308dc 100644
--- a/MediaBrowser.Model/Dlna/SubtitleStreamInfo.cs
+++ b/MediaBrowser.Model/Dlna/SubtitleStreamInfo.cs
@@ -7,6 +7,7 @@ namespace MediaBrowser.Model.Dlna
public string Name { get; set; }
public bool IsForced { get; set; }
public string Format { get; set; }
+ public string DisplayTitle { get; set; }
public int Index { get; set; }
public SubtitleDeliveryMethod DeliveryMethod { get; set; }
public bool IsExternalUrl { get; set; }
diff --git a/MediaBrowser.Model/Dto/ItemCounts.cs b/MediaBrowser.Model/Dto/ItemCounts.cs
index a3a00c341..07ddfa1ac 100644
--- a/MediaBrowser.Model/Dto/ItemCounts.cs
+++ b/MediaBrowser.Model/Dto/ItemCounts.cs
@@ -60,5 +60,6 @@
/// </summary>
/// <value>The book count.</value>
public int BookCount { get; set; }
+ public int ItemCount { get; set; }
}
}
diff --git a/MediaBrowser.Model/Entities/ImageType.cs b/MediaBrowser.Model/Entities/ImageType.cs
index 18097abb4..6e0ba717f 100644
--- a/MediaBrowser.Model/Entities/ImageType.cs
+++ b/MediaBrowser.Model/Entities/ImageType.cs
@@ -9,50 +9,50 @@ namespace MediaBrowser.Model.Entities
/// <summary>
/// The primary
/// </summary>
- Primary,
+ Primary = 0,
/// <summary>
/// The art
/// </summary>
- Art,
+ Art = 1,
/// <summary>
/// The backdrop
/// </summary>
- Backdrop,
+ Backdrop = 2,
/// <summary>
/// The banner
/// </summary>
- Banner,
+ Banner = 3,
/// <summary>
/// The logo
/// </summary>
- Logo,
+ Logo = 4,
/// <summary>
/// The thumb
/// </summary>
- Thumb,
+ Thumb = 5,
/// <summary>
/// The disc
/// </summary>
- Disc,
+ Disc = 6,
/// <summary>
/// The box
/// </summary>
- Box,
+ Box = 7,
/// <summary>
/// The screenshot
/// </summary>
- Screenshot,
+ Screenshot = 8,
/// <summary>
/// The menu
/// </summary>
- Menu,
+ Menu = 9,
/// <summary>
/// The chapter image
/// </summary>
- Chapter,
+ Chapter = 10,
/// <summary>
/// The box rear
/// </summary>
- BoxRear
+ BoxRear = 11
}
}
diff --git a/MediaBrowser.Model/Entities/MediaStream.cs b/MediaBrowser.Model/Entities/MediaStream.cs
index 64c7d9aa6..990de332e 100644
--- a/MediaBrowser.Model/Entities/MediaStream.cs
+++ b/MediaBrowser.Model/Entities/MediaStream.cs
@@ -36,6 +36,9 @@ namespace MediaBrowser.Model.Entities
/// <value>The comment.</value>
public string Comment { get; set; }
+ public string TimeBase { get; set; }
+ public string CodecTimeBase { get; set; }
+
public string Title { get; set; }
public string DisplayTitle
@@ -72,15 +75,12 @@ namespace MediaBrowser.Model.Entities
{
attributes.Add(StringHelper.ToStringCultureInvariant(Channels.Value) + " ch");
}
-
- string name = string.Join(" ", attributes.ToArray());
-
if (IsDefault)
{
- name += " (D)";
+ attributes.Add("Default");
}
- return name;
+ return string.Join(" ", attributes.ToArray());
}
if (Type == MediaStreamType.Subtitle)
@@ -89,29 +89,19 @@ namespace MediaBrowser.Model.Entities
if (!string.IsNullOrEmpty(Language))
{
- attributes.Add(Language);
- }
- if (!string.IsNullOrEmpty(Codec))
- {
- attributes.Add(Codec);
+ attributes.Add(StringHelper.FirstToUpper(Language));
}
-
- string name = string.Join(" ", attributes.ToArray());
-
if (IsDefault)
{
- name += " (D)";
+ attributes.Add("Default");
}
if (IsForced)
{
- name += " (F)";
+ attributes.Add("Forced");
}
- if (IsExternal)
- {
- name += " (EXT)";
- }
+ string name = string.Join(" ", attributes.ToArray());
return name;
}
@@ -292,6 +282,36 @@ namespace MediaBrowser.Model.Entities
!StringHelper.EqualsIgnoreCase(codec, "sub");
}
+ public bool SupportsSubtitleConversionTo(string codec)
+ {
+ if (!IsTextSubtitleStream)
+ {
+ return false;
+ }
+
+ // Can't convert from this
+ if (StringHelper.EqualsIgnoreCase(Codec, "ass"))
+ {
+ return false;
+ }
+ if (StringHelper.EqualsIgnoreCase(Codec, "ssa"))
+ {
+ return false;
+ }
+
+ // Can't convert to this
+ if (StringHelper.EqualsIgnoreCase(codec, "ass"))
+ {
+ return false;
+ }
+ if (StringHelper.EqualsIgnoreCase(codec, "ssa"))
+ {
+ return false;
+ }
+
+ return true;
+ }
+
/// <summary>
/// Gets or sets a value indicating whether [supports external stream].
/// </summary>
diff --git a/MediaBrowser.Model/Entities/MediaUrl.cs b/MediaBrowser.Model/Entities/MediaUrl.cs
index 24e3b1492..2e17bba8a 100644
--- a/MediaBrowser.Model/Entities/MediaUrl.cs
+++ b/MediaBrowser.Model/Entities/MediaUrl.cs
@@ -5,6 +5,5 @@ namespace MediaBrowser.Model.Entities
{
public string Url { get; set; }
public string Name { get; set; }
- public VideoSize? VideoSize { get; set; }
}
}
diff --git a/MediaBrowser.Model/Entities/VideoSize.cs b/MediaBrowser.Model/Entities/VideoSize.cs
deleted file mode 100644
index 0100f3b90..000000000
--- a/MediaBrowser.Model/Entities/VideoSize.cs
+++ /dev/null
@@ -1,8 +0,0 @@
-namespace MediaBrowser.Model.Entities
-{
- public enum VideoSize
- {
- StandardDefinition,
- HighDefinition
- }
-} \ No newline at end of file
diff --git a/MediaBrowser.Model/LiveTv/LiveTvOptions.cs b/MediaBrowser.Model/LiveTv/LiveTvOptions.cs
index e00443d19..242a2d24e 100644
--- a/MediaBrowser.Model/LiveTv/LiveTvOptions.cs
+++ b/MediaBrowser.Model/LiveTv/LiveTvOptions.cs
@@ -1,4 +1,6 @@
using System.Collections.Generic;
+using MediaBrowser.Model.Dto;
+using MediaBrowser.Model.Extensions;
namespace MediaBrowser.Model.LiveTv
{
@@ -73,11 +75,33 @@ namespace MediaBrowser.Model.LiveTv
public string[] EnabledTuners { get; set; }
public bool EnableAllTuners { get; set; }
+ public string[] NewsCategories { get; set; }
+ public string[] SportsCategories { get; set; }
+ public string[] KidsCategories { get; set; }
+ public string[] MovieCategories { get; set; }
+ public NameValuePair[] ChannelMappings { get; set; }
public ListingsProviderInfo()
{
+ NewsCategories = new string[] { "news", "journalism", "documentary", "current affairs" };
+ SportsCategories = new string[] { "sports", "basketball", "baseball", "football" };
+ KidsCategories = new string[] { "kids", "family", "children", "childrens", "disney" };
+ MovieCategories = new string[] { "movie" };
EnabledTuners = new string[] { };
EnableAllTuners = true;
+ ChannelMappings = new NameValuePair[] {};
+ }
+
+ public string GetMappedChannel(string channelNumber)
+ {
+ foreach (NameValuePair mapping in ChannelMappings)
+ {
+ if (StringHelper.EqualsIgnoreCase(mapping.Name, channelNumber))
+ {
+ return mapping.Value;
+ }
+ }
+ return channelNumber;
}
}
}
diff --git a/MediaBrowser.Model/LiveTv/RecordingQuery.cs b/MediaBrowser.Model/LiveTv/RecordingQuery.cs
index 0cf997602..923d303f8 100644
--- a/MediaBrowser.Model/LiveTv/RecordingQuery.cs
+++ b/MediaBrowser.Model/LiveTv/RecordingQuery.cs
@@ -70,5 +70,12 @@ namespace MediaBrowser.Model.LiveTv
public bool? EnableImages { get; set; }
public int? ImageTypeLimit { get; set; }
public ImageType[] EnableImageTypes { get; set; }
+
+ public bool EnableTotalRecordCount { get; set; }
+
+ public RecordingQuery()
+ {
+ EnableTotalRecordCount = true;
+ }
}
}
diff --git a/MediaBrowser.Model/LiveTv/TimerQuery.cs b/MediaBrowser.Model/LiveTv/TimerQuery.cs
index e6ceff530..87b6b89ac 100644
--- a/MediaBrowser.Model/LiveTv/TimerQuery.cs
+++ b/MediaBrowser.Model/LiveTv/TimerQuery.cs
@@ -13,5 +13,7 @@
/// </summary>
/// <value>The series timer identifier.</value>
public string SeriesTimerId { get; set; }
+
+ public bool? IsActive { get; set; }
}
} \ No newline at end of file
diff --git a/MediaBrowser.Model/MediaBrowser.Model.csproj b/MediaBrowser.Model/MediaBrowser.Model.csproj
index 7c469b9fb..e54273b84 100644
--- a/MediaBrowser.Model/MediaBrowser.Model.csproj
+++ b/MediaBrowser.Model/MediaBrowser.Model.csproj
@@ -97,8 +97,6 @@
<Compile Include="Configuration\FanartOptions.cs" />
<Compile Include="Configuration\MetadataConfiguration.cs" />
<Compile Include="Configuration\PeopleMetadataOptions.cs" />
- <Compile Include="Configuration\TheMovieDbOptions.cs" />
- <Compile Include="Configuration\TvdbOptions.cs" />
<Compile Include="Configuration\XbmcMetadataOptions.cs" />
<Compile Include="Configuration\SubtitlePlaybackMode.cs" />
<Compile Include="Connect\ConnectAuthenticationExchangeResult.cs" />
@@ -229,7 +227,6 @@
<Compile Include="Entities\ProviderIdsExtensions.cs" />
<Compile Include="Entities\ScrollDirection.cs" />
<Compile Include="Entities\SortOrder.cs" />
- <Compile Include="Entities\VideoSize.cs" />
<Compile Include="Events\GenericEventArgs.cs" />
<Compile Include="Extensions\DoubleHelper.cs" />
<Compile Include="Extensions\IHasPropertyChangedEvent.cs" />
@@ -398,6 +395,7 @@
<Compile Include="Sync\SyncProfileOption.cs" />
<Compile Include="Sync\SyncQualityOption.cs" />
<Compile Include="Sync\SyncTarget.cs" />
+ <Compile Include="System\Architecture.cs" />
<Compile Include="System\LogFile.cs" />
<Compile Include="System\PublicSystemInfo.cs" />
<Compile Include="Updates\CheckForUpdateResult.cs" />
diff --git a/MediaBrowser.Model/Querying/ItemSortBy.cs b/MediaBrowser.Model/Querying/ItemSortBy.cs
index 9c2926b54..6f4ebd0c5 100644
--- a/MediaBrowser.Model/Querying/ItemSortBy.cs
+++ b/MediaBrowser.Model/Querying/ItemSortBy.cs
@@ -85,5 +85,6 @@ namespace MediaBrowser.Model.Querying
public const string GameSystem = "GameSystem";
public const string IsFavoriteOrLiked = "IsFavoriteOrLiked";
public const string DateLastContentAdded = "DateLastContentAdded";
+ public const string SeriesDatePlayed = "SeriesDatePlayed";
}
}
diff --git a/MediaBrowser.Model/System/Architecture.cs b/MediaBrowser.Model/System/Architecture.cs
new file mode 100644
index 000000000..09eedddc1
--- /dev/null
+++ b/MediaBrowser.Model/System/Architecture.cs
@@ -0,0 +1,9 @@
+namespace MediaBrowser.Model.System
+{
+ public enum Architecture
+ {
+ X86 = 0,
+ X64 = 1,
+ Arm = 2
+ }
+}
diff --git a/MediaBrowser.Model/System/SystemInfo.cs b/MediaBrowser.Model/System/SystemInfo.cs
index 6b54a90d4..3d1de5b37 100644
--- a/MediaBrowser.Model/System/SystemInfo.cs
+++ b/MediaBrowser.Model/System/SystemInfo.cs
@@ -152,6 +152,10 @@ namespace MediaBrowser.Model.System
/// <value><c>true</c> if [supports automatic run at startup]; otherwise, <c>false</c>.</value>
public bool SupportsAutoRunAtStartup { get; set; }
+ public string EncoderLocationType { get; set; }
+
+ public Architecture SystemArchitecture { get; set; }
+
/// <summary>
/// Initializes a new instance of the <see cref="SystemInfo" /> class.
/// </summary>
diff --git a/MediaBrowser.Providers/Books/BookMetadataService.cs b/MediaBrowser.Providers/Books/BookMetadataService.cs
index eb3335c9a..6f4a744c2 100644
--- a/MediaBrowser.Providers/Books/BookMetadataService.cs
+++ b/MediaBrowser.Providers/Books/BookMetadataService.cs
@@ -12,10 +12,6 @@ namespace MediaBrowser.Providers.Books
{
public class BookMetadataService : MetadataService<Book, BookInfo>
{
- public BookMetadataService(IServerConfigurationManager serverConfigurationManager, ILogger logger, IProviderManager providerManager, IProviderRepository providerRepo, IFileSystem fileSystem, IUserDataManager userDataManager, ILibraryManager libraryManager) : base(serverConfigurationManager, logger, providerManager, providerRepo, fileSystem, userDataManager, libraryManager)
- {
- }
-
protected override void MergeData(MetadataResult<Book> source, MetadataResult<Book> target, List<MetadataFields> lockedFields, bool replaceData, bool mergeMetadataSettings)
{
ProviderUtils.MergeBaseItemData(source, target, lockedFields, replaceData, mergeMetadataSettings);
@@ -25,5 +21,9 @@ namespace MediaBrowser.Providers.Books
target.Item.SeriesName = source.Item.SeriesName;
}
}
+
+ public BookMetadataService(IServerConfigurationManager serverConfigurationManager, ILogger logger, IProviderManager providerManager, IFileSystem fileSystem, IUserDataManager userDataManager, ILibraryManager libraryManager) : base(serverConfigurationManager, logger, providerManager, fileSystem, userDataManager, libraryManager)
+ {
+ }
}
}
diff --git a/MediaBrowser.Providers/BoxSets/BoxSetMetadataService.cs b/MediaBrowser.Providers/BoxSets/BoxSetMetadataService.cs
index 9a2488781..2dacb16ca 100644
--- a/MediaBrowser.Providers/BoxSets/BoxSetMetadataService.cs
+++ b/MediaBrowser.Providers/BoxSets/BoxSetMetadataService.cs
@@ -15,10 +15,6 @@ namespace MediaBrowser.Providers.BoxSets
{
public class BoxSetMetadataService : MetadataService<BoxSet, BoxSetInfo>
{
- public BoxSetMetadataService(IServerConfigurationManager serverConfigurationManager, ILogger logger, IProviderManager providerManager, IProviderRepository providerRepo, IFileSystem fileSystem, IUserDataManager userDataManager, ILibraryManager libraryManager) : base(serverConfigurationManager, logger, providerManager, providerRepo, fileSystem, userDataManager, libraryManager)
- {
- }
-
protected override async Task<ItemUpdateType> BeforeSave(BoxSet item, bool isFullRefresh, ItemUpdateType currentUpdateType)
{
var updateType = await base.BeforeSave(item, isFullRefresh, currentUpdateType).ConfigureAwait(false);
@@ -54,5 +50,9 @@ namespace MediaBrowser.Providers.BoxSets
targetItem.Shares = sourceItem.Shares;
}
}
+
+ public BoxSetMetadataService(IServerConfigurationManager serverConfigurationManager, ILogger logger, IProviderManager providerManager, IFileSystem fileSystem, IUserDataManager userDataManager, ILibraryManager libraryManager) : base(serverConfigurationManager, logger, providerManager, fileSystem, userDataManager, libraryManager)
+ {
+ }
}
}
diff --git a/MediaBrowser.Providers/BoxSets/MovieDbBoxSetImageProvider.cs b/MediaBrowser.Providers/BoxSets/MovieDbBoxSetImageProvider.cs
index b3e73740d..2dce13ebc 100644
--- a/MediaBrowser.Providers/BoxSets/MovieDbBoxSetImageProvider.cs
+++ b/MediaBrowser.Providers/BoxSets/MovieDbBoxSetImageProvider.cs
@@ -83,7 +83,7 @@ namespace MediaBrowser.Providers.BoxSets
VoteCount = i.vote_count,
Width = i.width,
Height = i.height,
- Language = i.iso_639_1,
+ Language = MovieDbProvider.AdjustImageLanguage(i.iso_639_1, language),
ProviderName = Name,
Type = ImageType.Primary,
RatingType = RatingType.Score
diff --git a/MediaBrowser.Providers/Channels/ChannelMetadataService.cs b/MediaBrowser.Providers/Channels/ChannelMetadataService.cs
index 3a1d2374c..22e196d72 100644
--- a/MediaBrowser.Providers/Channels/ChannelMetadataService.cs
+++ b/MediaBrowser.Providers/Channels/ChannelMetadataService.cs
@@ -12,13 +12,13 @@ namespace MediaBrowser.Providers.Channels
{
public class ChannelMetadataService : MetadataService<Channel, ItemLookupInfo>
{
- public ChannelMetadataService(IServerConfigurationManager serverConfigurationManager, ILogger logger, IProviderManager providerManager, IProviderRepository providerRepo, IFileSystem fileSystem, IUserDataManager userDataManager, ILibraryManager libraryManager) : base(serverConfigurationManager, logger, providerManager, providerRepo, fileSystem, userDataManager, libraryManager)
+ protected override void MergeData(MetadataResult<Channel> source, MetadataResult<Channel> target, List<MetadataFields> lockedFields, bool replaceData, bool mergeMetadataSettings)
{
+ ProviderUtils.MergeBaseItemData(source, target, lockedFields, replaceData, mergeMetadataSettings);
}
- protected override void MergeData(MetadataResult<Channel> source, MetadataResult<Channel> target, List<MetadataFields> lockedFields, bool replaceData, bool mergeMetadataSettings)
+ public ChannelMetadataService(IServerConfigurationManager serverConfigurationManager, ILogger logger, IProviderManager providerManager, IFileSystem fileSystem, IUserDataManager userDataManager, ILibraryManager libraryManager) : base(serverConfigurationManager, logger, providerManager, fileSystem, userDataManager, libraryManager)
{
- ProviderUtils.MergeBaseItemData(source, target, lockedFields, replaceData, mergeMetadataSettings);
}
}
}
diff --git a/MediaBrowser.Providers/Chapters/ChapterManager.cs b/MediaBrowser.Providers/Chapters/ChapterManager.cs
index 6e2cd77eb..88811c850 100644
--- a/MediaBrowser.Providers/Chapters/ChapterManager.cs
+++ b/MediaBrowser.Providers/Chapters/ChapterManager.cs
@@ -259,7 +259,7 @@ namespace MediaBrowser.Providers.Chapters
return _itemRepo.GetChapters(new Guid(itemId));
}
- public Task SaveChapters(string itemId, IEnumerable<ChapterInfo> chapters, CancellationToken cancellationToken)
+ public Task SaveChapters(string itemId, List<ChapterInfo> chapters, CancellationToken cancellationToken)
{
return _itemRepo.SaveChapters(new Guid(itemId), chapters, cancellationToken);
}
diff --git a/MediaBrowser.Providers/Folders/CollectionFolderMetadataService.cs b/MediaBrowser.Providers/Folders/CollectionFolderMetadataService.cs
index e1de443de..35c61b5c5 100644
--- a/MediaBrowser.Providers/Folders/CollectionFolderMetadataService.cs
+++ b/MediaBrowser.Providers/Folders/CollectionFolderMetadataService.cs
@@ -16,14 +16,13 @@ namespace MediaBrowser.Providers.Folders
{
public class CollectionFolderMetadataService : MetadataService<CollectionFolder, ItemLookupInfo>
{
- public CollectionFolderMetadataService(IServerConfigurationManager serverConfigurationManager, ILogger logger, IProviderManager providerManager, IProviderRepository providerRepo, IFileSystem fileSystem, IUserDataManager userDataManager, ILibraryManager libraryManager)
- : base(serverConfigurationManager, logger, providerManager, providerRepo, fileSystem, userDataManager, libraryManager)
+ protected override void MergeData(MetadataResult<CollectionFolder> source, MetadataResult<CollectionFolder> target, List<MetadataFields> lockedFields, bool replaceData, bool mergeMetadataSettings)
{
+ ProviderUtils.MergeBaseItemData(source, target, lockedFields, replaceData, mergeMetadataSettings);
}
- protected override void MergeData(MetadataResult<CollectionFolder> source, MetadataResult<CollectionFolder> target, List<MetadataFields> lockedFields, bool replaceData, bool mergeMetadataSettings)
+ public CollectionFolderMetadataService(IServerConfigurationManager serverConfigurationManager, ILogger logger, IProviderManager providerManager, IFileSystem fileSystem, IUserDataManager userDataManager, ILibraryManager libraryManager) : base(serverConfigurationManager, logger, providerManager, fileSystem, userDataManager, libraryManager)
{
- ProviderUtils.MergeBaseItemData(source, target, lockedFields, replaceData, mergeMetadataSettings);
}
}
}
diff --git a/MediaBrowser.Providers/Folders/DefaultImageProvider.cs b/MediaBrowser.Providers/Folders/DefaultImageProvider.cs
deleted file mode 100644
index 08b4f6461..000000000
--- a/MediaBrowser.Providers/Folders/DefaultImageProvider.cs
+++ /dev/null
@@ -1,165 +0,0 @@
-using MediaBrowser.Common.Net;
-using MediaBrowser.Controller.Entities;
-using MediaBrowser.Controller.Providers;
-using MediaBrowser.Model.Entities;
-using MediaBrowser.Model.Providers;
-using MediaBrowser.Providers.Genres;
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Threading;
-using System.Threading.Tasks;
-
-namespace MediaBrowser.Providers.Folders
-{
- public class DefaultImageProvider : IRemoteImageProvider, IHasItemChangeMonitor
- {
- private readonly IHttpClient _httpClient;
-
- public DefaultImageProvider(IHttpClient httpClient)
- {
- _httpClient = httpClient;
- }
-
- public IEnumerable<ImageType> GetSupportedImages(IHasImages item)
- {
- return new List<ImageType>
- {
- ImageType.Primary
- };
- }
-
- public Task<IEnumerable<RemoteImageInfo>> GetImages(IHasImages item, CancellationToken cancellationToken)
- {
- var view = item as UserView;
-
- if (view != null)
- {
- return GetImages(view.ViewType, cancellationToken);
- }
-
- var folder = (ICollectionFolder)item;
- return GetImages(folder.CollectionType, cancellationToken);
- }
-
- private Task<IEnumerable<RemoteImageInfo>> GetImages(string viewType, CancellationToken cancellationToken)
- {
- var url = GetImageUrl(viewType);
-
- var list = new List<RemoteImageInfo>();
-
- if (!string.IsNullOrWhiteSpace(url))
- {
- list.AddRange(new List<RemoteImageInfo>{
- new RemoteImageInfo
- {
- ProviderName = Name,
- Url = url,
- Type = ImageType.Primary
- }
- });
- }
-
- return Task.FromResult<IEnumerable<RemoteImageInfo>>(list);
- }
-
- private string GetImageUrl(string viewType)
- {
- const string urlPrefix = "https://raw.githubusercontent.com/MediaBrowser/Emby.Resources/master/images/folders/";
-
- if (string.Equals(viewType, CollectionType.Books, StringComparison.OrdinalIgnoreCase))
- {
- return urlPrefix + "books.jpg";
- }
- if (string.Equals(viewType, CollectionType.Games, StringComparison.OrdinalIgnoreCase))
- {
- return urlPrefix + "games.jpg";
- }
- if (string.Equals(viewType, CollectionType.Music, StringComparison.OrdinalIgnoreCase))
- {
- //return urlPrefix + "music.jpg";
- }
- if (string.Equals(viewType, CollectionType.Photos, StringComparison.OrdinalIgnoreCase))
- {
- //return urlPrefix + "photos.png";
- }
- if (string.Equals(viewType, CollectionType.TvShows, StringComparison.OrdinalIgnoreCase))
- {
- //return urlPrefix + "tv.jpg";
- }
- if (string.Equals(viewType, CollectionType.Channels, StringComparison.OrdinalIgnoreCase))
- {
- return urlPrefix + "channels.jpg";
- }
- if (string.Equals(viewType, CollectionType.LiveTv, StringComparison.OrdinalIgnoreCase))
- {
- return urlPrefix + "livetv.png";
- }
- if (string.Equals(viewType, CollectionType.Movies, StringComparison.OrdinalIgnoreCase))
- {
- //return urlPrefix + "movies.jpg";
- }
- if (string.Equals(viewType, CollectionType.Playlists, StringComparison.OrdinalIgnoreCase))
- {
- return urlPrefix + "playlists.jpg";
- }
- if (string.Equals(viewType, CollectionType.HomeVideos, StringComparison.OrdinalIgnoreCase))
- {
- //return urlPrefix + "homevideos.jpg";
- }
- if (string.Equals(viewType, CollectionType.MusicVideos, StringComparison.OrdinalIgnoreCase))
- {
- return urlPrefix + "musicvideos.jpg";
- }
- if (string.Equals(viewType, CollectionType.BoxSets, StringComparison.OrdinalIgnoreCase))
- {
- //return urlPrefix + "collections.jpg";
- }
- if (string.IsNullOrWhiteSpace(viewType))
- {
- //return urlPrefix + "generic.jpg";
- }
-
- return null;
- }
-
- public string Name
- {
- get { return "Default Image Provider"; }
- }
-
- public bool Supports(IHasImages item)
- {
- var view = item as UserView;
-
- if (view != null)
- {
- return !string.IsNullOrWhiteSpace(GetImageUrl(view.ViewType));
- }
-
- var folder = item as ICollectionFolder;
-
- if (folder != null)
- {
- return !string.IsNullOrWhiteSpace(GetImageUrl(folder.CollectionType));
- }
-
- return false;
- }
-
- public Task<HttpResponseInfo> GetImageResponse(string url, CancellationToken cancellationToken)
- {
- return _httpClient.GetResponse(new HttpRequestOptions
- {
- CancellationToken = cancellationToken,
- Url = url,
- ResourcePool = GenreImageProvider.ImageDownloadResourcePool
- });
- }
-
- public bool HasChanged(IHasMetadata item, IDirectoryService directoryService)
- {
- return GetSupportedImages(item).Any(i => !item.HasImage(i));
- }
- }
-} \ No newline at end of file
diff --git a/MediaBrowser.Providers/Folders/FolderMetadataService.cs b/MediaBrowser.Providers/Folders/FolderMetadataService.cs
index e938297b7..8c4737fc4 100644
--- a/MediaBrowser.Providers/Folders/FolderMetadataService.cs
+++ b/MediaBrowser.Providers/Folders/FolderMetadataService.cs
@@ -12,10 +12,6 @@ namespace MediaBrowser.Providers.Folders
{
public class FolderMetadataService : MetadataService<Folder, ItemLookupInfo>
{
- public FolderMetadataService(IServerConfigurationManager serverConfigurationManager, ILogger logger, IProviderManager providerManager, IProviderRepository providerRepo, IFileSystem fileSystem, IUserDataManager userDataManager, ILibraryManager libraryManager) : base(serverConfigurationManager, logger, providerManager, providerRepo, fileSystem, userDataManager, libraryManager)
- {
- }
-
public override int Order
{
get
@@ -29,5 +25,9 @@ namespace MediaBrowser.Providers.Folders
{
ProviderUtils.MergeBaseItemData(source, target, lockedFields, replaceData, mergeMetadataSettings);
}
+
+ public FolderMetadataService(IServerConfigurationManager serverConfigurationManager, ILogger logger, IProviderManager providerManager, IFileSystem fileSystem, IUserDataManager userDataManager, ILibraryManager libraryManager) : base(serverConfigurationManager, logger, providerManager, fileSystem, userDataManager, libraryManager)
+ {
+ }
}
}
diff --git a/MediaBrowser.Providers/Folders/UserViewMetadataService.cs b/MediaBrowser.Providers/Folders/UserViewMetadataService.cs
index 7c298a3ed..b8f58307a 100644
--- a/MediaBrowser.Providers/Folders/UserViewMetadataService.cs
+++ b/MediaBrowser.Providers/Folders/UserViewMetadataService.cs
@@ -12,13 +12,13 @@ namespace MediaBrowser.Providers.Folders
{
public class UserViewMetadataService : MetadataService<UserView, ItemLookupInfo>
{
- public UserViewMetadataService(IServerConfigurationManager serverConfigurationManager, ILogger logger, IProviderManager providerManager, IProviderRepository providerRepo, IFileSystem fileSystem, IUserDataManager userDataManager, ILibraryManager libraryManager) : base(serverConfigurationManager, logger, providerManager, providerRepo, fileSystem, userDataManager, libraryManager)
+ protected override void MergeData(MetadataResult<UserView> source, MetadataResult<UserView> target, List<MetadataFields> lockedFields, bool replaceData, bool mergeMetadataSettings)
{
+ ProviderUtils.MergeBaseItemData(source, target, lockedFields, replaceData, mergeMetadataSettings);
}
- protected override void MergeData(MetadataResult<UserView> source, MetadataResult<UserView> target, List<MetadataFields> lockedFields, bool replaceData, bool mergeMetadataSettings)
+ public UserViewMetadataService(IServerConfigurationManager serverConfigurationManager, ILogger logger, IProviderManager providerManager, IFileSystem fileSystem, IUserDataManager userDataManager, ILibraryManager libraryManager) : base(serverConfigurationManager, logger, providerManager, fileSystem, userDataManager, libraryManager)
{
- ProviderUtils.MergeBaseItemData(source, target, lockedFields, replaceData, mergeMetadataSettings);
}
}
}
diff --git a/MediaBrowser.Providers/GameGenres/GameGenreMetadataService.cs b/MediaBrowser.Providers/GameGenres/GameGenreMetadataService.cs
index f80691d72..fb2244e32 100644
--- a/MediaBrowser.Providers/GameGenres/GameGenreMetadataService.cs
+++ b/MediaBrowser.Providers/GameGenres/GameGenreMetadataService.cs
@@ -12,13 +12,13 @@ namespace MediaBrowser.Providers.GameGenres
{
public class GameGenreMetadataService : MetadataService<GameGenre, ItemLookupInfo>
{
- public GameGenreMetadataService(IServerConfigurationManager serverConfigurationManager, ILogger logger, IProviderManager providerManager, IProviderRepository providerRepo, IFileSystem fileSystem, IUserDataManager userDataManager, ILibraryManager libraryManager) : base(serverConfigurationManager, logger, providerManager, providerRepo, fileSystem, userDataManager, libraryManager)
+ protected override void MergeData(MetadataResult<GameGenre> source, MetadataResult<GameGenre> target, List<MetadataFields> lockedFields, bool replaceData, bool mergeMetadataSettings)
{
+ ProviderUtils.MergeBaseItemData(source, target, lockedFields, replaceData, mergeMetadataSettings);
}
- protected override void MergeData(MetadataResult<GameGenre> source, MetadataResult<GameGenre> target, List<MetadataFields> lockedFields, bool replaceData, bool mergeMetadataSettings)
+ public GameGenreMetadataService(IServerConfigurationManager serverConfigurationManager, ILogger logger, IProviderManager providerManager, IFileSystem fileSystem, IUserDataManager userDataManager, ILibraryManager libraryManager) : base(serverConfigurationManager, logger, providerManager, fileSystem, userDataManager, libraryManager)
{
- ProviderUtils.MergeBaseItemData(source, target, lockedFields, replaceData, mergeMetadataSettings);
}
}
}
diff --git a/MediaBrowser.Providers/Games/GameMetadataService.cs b/MediaBrowser.Providers/Games/GameMetadataService.cs
index 23284b84e..a44f1d95f 100644
--- a/MediaBrowser.Providers/Games/GameMetadataService.cs
+++ b/MediaBrowser.Providers/Games/GameMetadataService.cs
@@ -12,10 +12,6 @@ namespace MediaBrowser.Providers.Games
{
public class GameMetadataService : MetadataService<Game, GameInfo>
{
- public GameMetadataService(IServerConfigurationManager serverConfigurationManager, ILogger logger, IProviderManager providerManager, IProviderRepository providerRepo, IFileSystem fileSystem, IUserDataManager userDataManager, ILibraryManager libraryManager) : base(serverConfigurationManager, logger, providerManager, providerRepo, fileSystem, userDataManager, libraryManager)
- {
- }
-
protected override void MergeData(MetadataResult<Game> source, MetadataResult<Game> target, List<MetadataFields> lockedFields, bool replaceData, bool mergeMetadataSettings)
{
ProviderUtils.MergeBaseItemData(source, target, lockedFields, replaceData, mergeMetadataSettings);
@@ -33,5 +29,9 @@ namespace MediaBrowser.Providers.Games
targetItem.PlayersSupported = sourceItem.PlayersSupported;
}
}
+
+ public GameMetadataService(IServerConfigurationManager serverConfigurationManager, ILogger logger, IProviderManager providerManager, IFileSystem fileSystem, IUserDataManager userDataManager, ILibraryManager libraryManager) : base(serverConfigurationManager, logger, providerManager, fileSystem, userDataManager, libraryManager)
+ {
+ }
}
}
diff --git a/MediaBrowser.Providers/Games/GameSystemMetadataService.cs b/MediaBrowser.Providers/Games/GameSystemMetadataService.cs
index a70b5c7fe..6cf2a45a4 100644
--- a/MediaBrowser.Providers/Games/GameSystemMetadataService.cs
+++ b/MediaBrowser.Providers/Games/GameSystemMetadataService.cs
@@ -12,10 +12,6 @@ namespace MediaBrowser.Providers.Games
{
public class GameSystemMetadataService : MetadataService<GameSystem, GameSystemInfo>
{
- public GameSystemMetadataService(IServerConfigurationManager serverConfigurationManager, ILogger logger, IProviderManager providerManager, IProviderRepository providerRepo, IFileSystem fileSystem, IUserDataManager userDataManager, ILibraryManager libraryManager) : base(serverConfigurationManager, logger, providerManager, providerRepo, fileSystem, userDataManager, libraryManager)
- {
- }
-
protected override void MergeData(MetadataResult<GameSystem> source, MetadataResult<GameSystem> target, List<MetadataFields> lockedFields, bool replaceData, bool mergeMetadataSettings)
{
ProviderUtils.MergeBaseItemData(source, target, lockedFields, replaceData, mergeMetadataSettings);
@@ -28,5 +24,9 @@ namespace MediaBrowser.Providers.Games
targetItem.GameSystemName = sourceItem.GameSystemName;
}
}
+
+ public GameSystemMetadataService(IServerConfigurationManager serverConfigurationManager, ILogger logger, IProviderManager providerManager, IFileSystem fileSystem, IUserDataManager userDataManager, ILibraryManager libraryManager) : base(serverConfigurationManager, logger, providerManager, fileSystem, userDataManager, libraryManager)
+ {
+ }
}
}
diff --git a/MediaBrowser.Providers/Genres/GenreMetadataService.cs b/MediaBrowser.Providers/Genres/GenreMetadataService.cs
index 7aba931a2..d4ea3e9cf 100644
--- a/MediaBrowser.Providers/Genres/GenreMetadataService.cs
+++ b/MediaBrowser.Providers/Genres/GenreMetadataService.cs
@@ -12,13 +12,13 @@ namespace MediaBrowser.Providers.Genres
{
public class GenreMetadataService : MetadataService<Genre, ItemLookupInfo>
{
- public GenreMetadataService(IServerConfigurationManager serverConfigurationManager, ILogger logger, IProviderManager providerManager, IProviderRepository providerRepo, IFileSystem fileSystem, IUserDataManager userDataManager, ILibraryManager libraryManager) : base(serverConfigurationManager, logger, providerManager, providerRepo, fileSystem, userDataManager, libraryManager)
+ protected override void MergeData(MetadataResult<Genre> source, MetadataResult<Genre> target, List<MetadataFields> lockedFields, bool replaceData, bool mergeMetadataSettings)
{
+ ProviderUtils.MergeBaseItemData(source, target, lockedFields, replaceData, mergeMetadataSettings);
}
- protected override void MergeData(MetadataResult<Genre> source, MetadataResult<Genre> target, List<MetadataFields> lockedFields, bool replaceData, bool mergeMetadataSettings)
+ public GenreMetadataService(IServerConfigurationManager serverConfigurationManager, ILogger logger, IProviderManager providerManager, IFileSystem fileSystem, IUserDataManager userDataManager, ILibraryManager libraryManager) : base(serverConfigurationManager, logger, providerManager, fileSystem, userDataManager, libraryManager)
{
- ProviderUtils.MergeBaseItemData(source, target, lockedFields, replaceData, mergeMetadataSettings);
}
}
}
diff --git a/MediaBrowser.Providers/LiveTv/AudioRecordingService.cs b/MediaBrowser.Providers/LiveTv/AudioRecordingService.cs
index 4ccbb9116..1d99a678b 100644
--- a/MediaBrowser.Providers/LiveTv/AudioRecordingService.cs
+++ b/MediaBrowser.Providers/LiveTv/AudioRecordingService.cs
@@ -12,13 +12,13 @@ namespace MediaBrowser.Providers.LiveTv
{
public class AudioRecordingService : MetadataService<LiveTvAudioRecording, ItemLookupInfo>
{
- public AudioRecordingService(IServerConfigurationManager serverConfigurationManager, ILogger logger, IProviderManager providerManager, IProviderRepository providerRepo, IFileSystem fileSystem, IUserDataManager userDataManager, ILibraryManager libraryManager) : base(serverConfigurationManager, logger, providerManager, providerRepo, fileSystem, userDataManager, libraryManager)
+ protected override void MergeData(MetadataResult<LiveTvAudioRecording> source, MetadataResult<LiveTvAudioRecording> target, List<MetadataFields> lockedFields, bool replaceData, bool mergeMetadataSettings)
{
+ ProviderUtils.MergeBaseItemData(source, target, lockedFields, replaceData, mergeMetadataSettings);
}
- protected override void MergeData(MetadataResult<LiveTvAudioRecording> source, MetadataResult<LiveTvAudioRecording> target, List<MetadataFields> lockedFields, bool replaceData, bool mergeMetadataSettings)
+ public AudioRecordingService(IServerConfigurationManager serverConfigurationManager, ILogger logger, IProviderManager providerManager, IFileSystem fileSystem, IUserDataManager userDataManager, ILibraryManager libraryManager) : base(serverConfigurationManager, logger, providerManager, fileSystem, userDataManager, libraryManager)
{
- ProviderUtils.MergeBaseItemData(source, target, lockedFields, replaceData, mergeMetadataSettings);
}
}
}
diff --git a/MediaBrowser.Providers/LiveTv/ChannelMetadataService.cs b/MediaBrowser.Providers/LiveTv/ChannelMetadataService.cs
index 0b9b18bbc..8abb99689 100644
--- a/MediaBrowser.Providers/LiveTv/ChannelMetadataService.cs
+++ b/MediaBrowser.Providers/LiveTv/ChannelMetadataService.cs
@@ -12,13 +12,13 @@ namespace MediaBrowser.Providers.LiveTv
{
public class ChannelMetadataService : MetadataService<LiveTvChannel, ItemLookupInfo>
{
- public ChannelMetadataService(IServerConfigurationManager serverConfigurationManager, ILogger logger, IProviderManager providerManager, IProviderRepository providerRepo, IFileSystem fileSystem, IUserDataManager userDataManager, ILibraryManager libraryManager) : base(serverConfigurationManager, logger, providerManager, providerRepo, fileSystem, userDataManager, libraryManager)
+ protected override void MergeData(MetadataResult<LiveTvChannel> source, MetadataResult<LiveTvChannel> target, List<MetadataFields> lockedFields, bool replaceData, bool mergeMetadataSettings)
{
+ ProviderUtils.MergeBaseItemData(source, target, lockedFields, replaceData, mergeMetadataSettings);
}
- protected override void MergeData(MetadataResult<LiveTvChannel> source, MetadataResult<LiveTvChannel> target, List<MetadataFields> lockedFields, bool replaceData, bool mergeMetadataSettings)
+ public ChannelMetadataService(IServerConfigurationManager serverConfigurationManager, ILogger logger, IProviderManager providerManager, IFileSystem fileSystem, IUserDataManager userDataManager, ILibraryManager libraryManager) : base(serverConfigurationManager, logger, providerManager, fileSystem, userDataManager, libraryManager)
{
- ProviderUtils.MergeBaseItemData(source, target, lockedFields, replaceData, mergeMetadataSettings);
}
}
}
diff --git a/MediaBrowser.Providers/LiveTv/ProgramMetadataService.cs b/MediaBrowser.Providers/LiveTv/ProgramMetadataService.cs
index 0bf4a1b37..b73d82c19 100644
--- a/MediaBrowser.Providers/LiveTv/ProgramMetadataService.cs
+++ b/MediaBrowser.Providers/LiveTv/ProgramMetadataService.cs
@@ -12,13 +12,13 @@ namespace MediaBrowser.Providers.LiveTv
{
public class ProgramMetadataService : MetadataService<LiveTvProgram, LiveTvProgramLookupInfo>
{
- public ProgramMetadataService(IServerConfigurationManager serverConfigurationManager, ILogger logger, IProviderManager providerManager, IProviderRepository providerRepo, IFileSystem fileSystem, IUserDataManager userDataManager, ILibraryManager libraryManager) : base(serverConfigurationManager, logger, providerManager, providerRepo, fileSystem, userDataManager, libraryManager)
+ protected override void MergeData(MetadataResult<LiveTvProgram> source, MetadataResult<LiveTvProgram> target, List<MetadataFields> lockedFields, bool replaceData, bool mergeMetadataSettings)
{
+ ProviderUtils.MergeBaseItemData(source, target, lockedFields, replaceData, mergeMetadataSettings);
}
- protected override void MergeData(MetadataResult<LiveTvProgram> source, MetadataResult<LiveTvProgram> target, List<MetadataFields> lockedFields, bool replaceData, bool mergeMetadataSettings)
+ public ProgramMetadataService(IServerConfigurationManager serverConfigurationManager, ILogger logger, IProviderManager providerManager, IFileSystem fileSystem, IUserDataManager userDataManager, ILibraryManager libraryManager) : base(serverConfigurationManager, logger, providerManager, fileSystem, userDataManager, libraryManager)
{
- ProviderUtils.MergeBaseItemData(source, target, lockedFields, replaceData, mergeMetadataSettings);
}
}
}
diff --git a/MediaBrowser.Providers/LiveTv/VideoRecordingService.cs b/MediaBrowser.Providers/LiveTv/VideoRecordingService.cs
index 47ac546a2..15530f8f9 100644
--- a/MediaBrowser.Providers/LiveTv/VideoRecordingService.cs
+++ b/MediaBrowser.Providers/LiveTv/VideoRecordingService.cs
@@ -12,13 +12,13 @@ namespace MediaBrowser.Providers.LiveTv
{
public class VideoRecordingService : MetadataService<LiveTvVideoRecording, ItemLookupInfo>
{
- public VideoRecordingService(IServerConfigurationManager serverConfigurationManager, ILogger logger, IProviderManager providerManager, IProviderRepository providerRepo, IFileSystem fileSystem, IUserDataManager userDataManager, ILibraryManager libraryManager) : base(serverConfigurationManager, logger, providerManager, providerRepo, fileSystem, userDataManager, libraryManager)
+ protected override void MergeData(MetadataResult<LiveTvVideoRecording> source, MetadataResult<LiveTvVideoRecording> target, List<MetadataFields> lockedFields, bool replaceData, bool mergeMetadataSettings)
{
+ ProviderUtils.MergeBaseItemData(source, target, lockedFields, replaceData, mergeMetadataSettings);
}
- protected override void MergeData(MetadataResult<LiveTvVideoRecording> source, MetadataResult<LiveTvVideoRecording> target, List<MetadataFields> lockedFields, bool replaceData, bool mergeMetadataSettings)
+ public VideoRecordingService(IServerConfigurationManager serverConfigurationManager, ILogger logger, IProviderManager providerManager, IFileSystem fileSystem, IUserDataManager userDataManager, ILibraryManager libraryManager) : base(serverConfigurationManager, logger, providerManager, fileSystem, userDataManager, libraryManager)
{
- ProviderUtils.MergeBaseItemData(source, target, lockedFields, replaceData, mergeMetadataSettings);
}
}
}
diff --git a/MediaBrowser.Providers/Manager/ImageSaver.cs b/MediaBrowser.Providers/Manager/ImageSaver.cs
index bd1961143..465677efb 100644
--- a/MediaBrowser.Providers/Manager/ImageSaver.cs
+++ b/MediaBrowser.Providers/Manager/ImageSaver.cs
@@ -161,6 +161,8 @@ namespace MediaBrowser.Providers.Manager
{
var currentPath = currentImage.Path;
+ _logger.Debug("Deleting previous image {0}", currentPath);
+
_libraryMonitor.ReportFileSystemChangeBeginning(currentPath);
try
@@ -276,7 +278,7 @@ namespace MediaBrowser.Providers.Manager
/// <returns>IEnumerable{System.String}.</returns>
private string[] GetSavePaths(IHasImages item, ImageType type, int? imageIndex, string mimeType, bool saveLocally)
{
- if (_config.Configuration.ImageSavingConvention == ImageSavingConvention.Legacy || !saveLocally)
+ if (!saveLocally || (_config.Configuration.ImageSavingConvention == ImageSavingConvention.Legacy))
{
return new[] { GetStandardSavePath(item, type, imageIndex, mimeType, saveLocally) };
}
@@ -375,11 +377,11 @@ namespace MediaBrowser.Providers.Manager
}
string filename;
- var folderName = item is MusicAlbum ||
- item is MusicArtist ||
- item is PhotoAlbum ||
- (saveLocally && _config.Configuration.ImageSavingConvention == ImageSavingConvention.Legacy) ?
- "folder" :
+ var folderName = item is MusicAlbum ||
+ item is MusicArtist ||
+ item is PhotoAlbum ||
+ (saveLocally && _config.Configuration.ImageSavingConvention == ImageSavingConvention.Legacy) ?
+ "folder" :
"poster";
switch (type)
diff --git a/MediaBrowser.Providers/Manager/MetadataService.cs b/MediaBrowser.Providers/Manager/MetadataService.cs
index 7184302f1..678d495cb 100644
--- a/MediaBrowser.Providers/Manager/MetadataService.cs
+++ b/MediaBrowser.Providers/Manager/MetadataService.cs
@@ -1,5 +1,4 @@
using MediaBrowser.Common.Extensions;
-using MediaBrowser.Common.IO;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library;
@@ -12,11 +11,6 @@ using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using CommonIO;
-using MediaBrowser.Controller.Channels;
-using MediaBrowser.Controller.Entities.Audio;
-using MediaBrowser.Controller.Entities.Movies;
-using MediaBrowser.Controller.LiveTv;
-using MediaBrowser.Controller.Playlists;
using MediaBrowser.Model.Providers;
namespace MediaBrowser.Providers.Manager
@@ -28,95 +22,26 @@ namespace MediaBrowser.Providers.Manager
protected readonly IServerConfigurationManager ServerConfigurationManager;
protected readonly ILogger Logger;
protected readonly IProviderManager ProviderManager;
- protected readonly IProviderRepository ProviderRepo;
protected readonly IFileSystem FileSystem;
protected readonly IUserDataManager UserDataManager;
protected readonly ILibraryManager LibraryManager;
- protected MetadataService(IServerConfigurationManager serverConfigurationManager, ILogger logger, IProviderManager providerManager, IProviderRepository providerRepo, IFileSystem fileSystem, IUserDataManager userDataManager, ILibraryManager libraryManager)
+ protected MetadataService(IServerConfigurationManager serverConfigurationManager, ILogger logger, IProviderManager providerManager, IFileSystem fileSystem, IUserDataManager userDataManager, ILibraryManager libraryManager)
{
ServerConfigurationManager = serverConfigurationManager;
Logger = logger;
ProviderManager = providerManager;
- ProviderRepo = providerRepo;
FileSystem = fileSystem;
UserDataManager = userDataManager;
LibraryManager = libraryManager;
}
- /// <summary>
- /// Saves the provider result.
- /// </summary>
- /// <param name="item">The item.</param>
- /// <param name="result">The result.</param>
- /// <param name="directoryService">The directory service.</param>
- /// <returns>Task.</returns>
- protected Task SaveProviderResult(TItemType item, MetadataStatus result, IDirectoryService directoryService)
- {
- result.ItemId = item.Id;
-
- //var locationType = item.LocationType;
-
- //if (locationType == LocationType.FileSystem || locationType == LocationType.Offline)
- //{
- // if (!string.IsNullOrWhiteSpace(item.Path))
- // {
- // var file = directoryService.GetFile(item.Path);
-
- // if ((file.Attributes & FileAttributes.Directory) != FileAttributes.Directory && file.Exists)
- // {
- // result.ItemDateModified = FileSystem.GetLastWriteTimeUtc(file);
- // }
- // }
- //}
-
- result.ItemDateModified = item.DateModified;
-
- if (EnableDateLastRefreshed(item))
- {
- return Task.FromResult(true);
- }
-
- return ProviderRepo.SaveMetadataStatus(result, CancellationToken.None);
- }
-
- /// <summary>
- /// Gets the last result.
- /// </summary>
- /// <param name="item">The item.</param>
- /// <returns>ProviderResult.</returns>
- protected MetadataStatus GetLastResult(IHasMetadata item)
- {
- if (GetLastRefreshDate(item) == default(DateTime))
- {
- return new MetadataStatus { ItemId = item.Id };
- }
-
- if (EnableDateLastRefreshed(item) && item.DateModifiedDuringLastRefresh.HasValue)
- {
- return new MetadataStatus
- {
- ItemId = item.Id,
- DateLastImagesRefresh = item.DateLastRefreshed,
- DateLastMetadataRefresh = item.DateLastRefreshed,
- ItemDateModified = item.DateModifiedDuringLastRefresh.Value
- };
- }
-
- var result = ProviderRepo.GetMetadataStatus(item.Id) ?? new MetadataStatus { ItemId = item.Id };
-
- item.DateModifiedDuringLastRefresh = result.ItemDateModified;
-
- return result;
- }
-
public async Task<ItemUpdateType> RefreshMetadata(IHasMetadata item, MetadataRefreshOptions refreshOptions, CancellationToken cancellationToken)
{
var itemOfType = (TItemType)item;
var config = ProviderManager.GetMetadataOptions(item);
var updateType = ItemUpdateType.None;
- var refreshResult = GetLastResult(item);
var itemImageProvider = new ItemImageProvider(Logger, ProviderManager, ServerConfigurationManager, FileSystem);
var localImagesFailed = false;
@@ -153,12 +78,10 @@ namespace MediaBrowser.Providers.Manager
// TODO: If this returns true, should we instead just change metadata refresh mode to Full?
requiresRefresh = item.RequiresRefresh();
- var providers = GetProviders(item, refreshResult, refreshOptions, requiresRefresh)
+ var providers = GetProviders(item, refreshOptions, requiresRefresh)
.ToList();
- var dateLastRefresh = EnableDateLastRefreshed(item)
- ? item.DateLastRefreshed
- : refreshResult.DateLastMetadataRefresh ?? default(DateTime);
+ var dateLastRefresh = item.DateLastRefreshed;
if (providers.Count > 0 || dateLastRefresh == default(DateTime))
{
@@ -185,13 +108,11 @@ namespace MediaBrowser.Providers.Manager
updateType = updateType | result.UpdateType;
if (result.Failures == 0)
{
- refreshResult.SetDateLastMetadataRefresh(DateTime.UtcNow);
hasRefreshedMetadata = true;
}
else
{
hasRefreshedMetadata = false;
- refreshResult.SetDateLastMetadataRefresh(null);
}
}
}
@@ -199,7 +120,7 @@ namespace MediaBrowser.Providers.Manager
// Next run remote image providers, but only if local image providers didn't throw an exception
if (!localImagesFailed && refreshOptions.ImageRefreshMode != ImageRefreshMode.ValidationOnly)
{
- var providers = GetNonLocalImageProviders(item, allImageProviders, refreshResult, refreshOptions).ToList();
+ var providers = GetNonLocalImageProviders(item, allImageProviders, refreshOptions).ToList();
if (providers.Count > 0)
{
@@ -208,13 +129,11 @@ namespace MediaBrowser.Providers.Manager
updateType = updateType | result.UpdateType;
if (result.Failures == 0)
{
- refreshResult.SetDateLastImagesRefresh(DateTime.UtcNow);
hasRefreshedImages = true;
}
else
{
hasRefreshedImages = false;
- refreshResult.SetDateLastImagesRefresh(null);
}
}
}
@@ -248,11 +167,6 @@ namespace MediaBrowser.Providers.Manager
await SaveItem(metadataResult, updateType, cancellationToken).ConfigureAwait(false);
}
- if (updateType > ItemUpdateType.None || refreshResult.IsDirty)
- {
- await SaveProviderResult(itemOfType, refreshResult, refreshOptions.DirectoryService).ConfigureAwait(false);
- }
-
await AfterMetadataRefresh(itemOfType, refreshOptions, cancellationToken).ConfigureAwait(false);
return updateType;
@@ -265,61 +179,9 @@ namespace MediaBrowser.Providers.Manager
lookupInfo.Year = result.ProductionYear;
}
- private async Task FindIdentities(TIdType id, CancellationToken cancellationToken)
- {
- try
- {
- await ItemIdentifier<TIdType>.FindIdentities(id, ProviderManager, cancellationToken).ConfigureAwait(false);
- }
- catch (Exception ex)
- {
- Logger.ErrorException("Error in FindIdentities", ex);
- }
- }
-
private DateTime GetLastRefreshDate(IHasMetadata item)
{
- if (EnableDateLastRefreshed(item))
- {
- return item.DateLastRefreshed;
- }
-
- return item.DateLastSaved;
- }
-
- private bool EnableDateLastRefreshed(IHasMetadata item)
- {
- if (ServerConfigurationManager.Configuration.EnableDateLastRefresh)
- {
- return true;
- }
-
- if (item.DateLastRefreshed != default(DateTime))
- {
- return true;
- }
-
- if (!(item is Audio) && !(item is Video))
- {
- return true;
- }
-
- if (item is IItemByName)
- {
- return true;
- }
-
- if (item.SourceType != SourceType.Library)
- {
- return true;
- }
-
- if (item is MusicVideo)
- {
- return true;
- }
-
- return false;
+ return item.DateLastRefreshed;
}
protected async Task SaveItem(MetadataResult<TItemType> result, ItemUpdateType reason, CancellationToken cancellationToken)
@@ -466,14 +328,12 @@ namespace MediaBrowser.Providers.Manager
/// Gets the providers.
/// </summary>
/// <returns>IEnumerable{`0}.</returns>
- protected IEnumerable<IMetadataProvider> GetProviders(IHasMetadata item, MetadataStatus status, MetadataRefreshOptions options, bool requiresRefresh)
+ protected IEnumerable<IMetadataProvider> GetProviders(IHasMetadata item, MetadataRefreshOptions options, bool requiresRefresh)
{
// Get providers to refresh
var providers = ((ProviderManager)ProviderManager).GetMetadataProviders<TItemType>(item).ToList();
- var dateLastRefresh = EnableDateLastRefreshed(item)
- ? item.DateLastRefreshed
- : status.DateLastMetadataRefresh ?? default(DateTime);
+ var dateLastRefresh = item.DateLastRefreshed;
// Run all if either of these flags are true
var runAllProviders = options.ReplaceAllMetadata || options.MetadataRefreshMode == MetadataRefreshMode.FullRefresh || dateLastRefresh == default(DateTime) || requiresRefresh;
@@ -535,14 +395,12 @@ namespace MediaBrowser.Providers.Manager
return providers;
}
- protected virtual IEnumerable<IImageProvider> GetNonLocalImageProviders(IHasMetadata item, IEnumerable<IImageProvider> allImageProviders, MetadataStatus status, ImageRefreshOptions options)
+ protected virtual IEnumerable<IImageProvider> GetNonLocalImageProviders(IHasMetadata item, IEnumerable<IImageProvider> allImageProviders, ImageRefreshOptions options)
{
// Get providers to refresh
var providers = allImageProviders.Where(i => !(i is ILocalImageProvider)).ToList();
- var dateLastImageRefresh = EnableDateLastRefreshed(item)
- ? item.DateLastRefreshed
- : status.DateLastImagesRefresh ?? default(DateTime);
+ var dateLastImageRefresh = item.DateLastRefreshed;
// Run all if either of these flags are true
var runAllProviders = options.ImageRefreshMode == ImageRefreshMode.FullRefresh || dateLastImageRefresh == default(DateTime);
@@ -552,12 +410,6 @@ namespace MediaBrowser.Providers.Manager
providers = providers
.Where(i =>
{
- var hasChangeMonitor = i as IHasChangeMonitor;
- if (hasChangeMonitor != null)
- {
- return HasChanged(item, hasChangeMonitor, dateLastImageRefresh, options.DirectoryService);
- }
-
var hasFileChangeMonitor = i as IHasItemChangeMonitor;
if (hasFileChangeMonitor != null)
{
diff --git a/MediaBrowser.Providers/Manager/ProviderManager.cs b/MediaBrowser.Providers/Manager/ProviderManager.cs
index c95d58a42..0f0745d1b 100644
--- a/MediaBrowser.Providers/Manager/ProviderManager.cs
+++ b/MediaBrowser.Providers/Manager/ProviderManager.cs
@@ -55,8 +55,6 @@ namespace MediaBrowser.Providers.Manager
private readonly IFileSystem _fileSystem;
private IMetadataService[] _metadataServices = { };
- private IItemIdentityProvider[] _identityProviders = { };
- private IItemIdentityConverter[] _identityConverters = { };
private IMetadataProvider[] _metadataProviders = { };
private IEnumerable<IMetadataSaver> _savers;
private IImageSaver[] _imageSavers;
@@ -92,22 +90,17 @@ namespace MediaBrowser.Providers.Manager
/// </summary>
/// <param name="imageProviders">The image providers.</param>
/// <param name="metadataServices">The metadata services.</param>
- /// <param name="identityProviders">The identity providers.</param>
- /// <param name="identityConverters">The identity converters.</param>
/// <param name="metadataProviders">The metadata providers.</param>
/// <param name="metadataSavers">The metadata savers.</param>
/// <param name="imageSavers">The image savers.</param>
/// <param name="externalIds">The external ids.</param>
public void AddParts(IEnumerable<IImageProvider> imageProviders, IEnumerable<IMetadataService> metadataServices,
- IEnumerable<IItemIdentityProvider> identityProviders, IEnumerable<IItemIdentityConverter> identityConverters,
IEnumerable<IMetadataProvider> metadataProviders, IEnumerable<IMetadataSaver> metadataSavers,
IEnumerable<IImageSaver> imageSavers, IEnumerable<IExternalId> externalIds)
{
ImageProviders = imageProviders.ToArray();
_metadataServices = metadataServices.OrderBy(i => i.Order).ToArray();
- _identityProviders = identityProviders.ToArray();
- _identityConverters = identityConverters.ToArray();
_metadataProviders = metadataProviders.ToArray();
_imageSavers = imageSavers.ToArray();
_externalIds = externalIds.OrderBy(i => i.Name).ToArray();
@@ -301,18 +294,6 @@ namespace MediaBrowser.Providers.Manager
.ThenBy(GetDefaultOrder);
}
- public IEnumerable<IItemIdentityProvider<TLookupInfo>> GetItemIdentityProviders<TLookupInfo>()
- where TLookupInfo : ItemLookupInfo
- {
- return _identityProviders.OfType<IItemIdentityProvider<TLookupInfo>>();
- }
-
- public IEnumerable<IItemIdentityConverter<TLookupInfo>> GetItemIdentityConverters<TLookupInfo>()
- where TLookupInfo : ItemLookupInfo
- {
- return _identityConverters.OfType<IItemIdentityConverter<TLookupInfo>>();
- }
-
private IEnumerable<IRemoteImageProvider> GetRemoteImageProviders(IHasImages item, bool includeDisabled)
{
var options = GetMetadataOptions(item);
@@ -849,7 +830,7 @@ namespace MediaBrowser.Providers.Manager
});
}
- public IEnumerable<ExternalUrl> GetExternalUrls(IHasProviderIds item)
+ public IEnumerable<ExternalUrl> GetExternalUrls(BaseItem item)
{
return GetExternalIds(item)
.Select(i =>
@@ -872,7 +853,7 @@ namespace MediaBrowser.Providers.Manager
Url = string.Format(i.UrlFormatString, value)
};
- }).Where(i => i != null);
+ }).Where(i => i != null).Concat(item.GetRelatedUrls());
}
public IEnumerable<ExternalIdInfo> GetExternalIdInfos(IHasProviderIds item)
diff --git a/MediaBrowser.Providers/Manager/ProviderUtils.cs b/MediaBrowser.Providers/Manager/ProviderUtils.cs
index 59a2da460..5f23cf69c 100644
--- a/MediaBrowser.Providers/Manager/ProviderUtils.cs
+++ b/MediaBrowser.Providers/Manager/ProviderUtils.cs
@@ -151,29 +151,17 @@ namespace MediaBrowser.Providers.Manager
if (!lockedFields.Contains(MetadataFields.Tags))
{
- var sourceHasTags = source as IHasTags;
- var targetHasTags = target as IHasTags;
-
- if (sourceHasTags != null && targetHasTags != null)
+ if (replaceData || target.Tags.Count == 0)
{
- if (replaceData || targetHasTags.Tags.Count == 0)
- {
- targetHasTags.Tags = sourceHasTags.Tags;
- }
+ target.Tags = source.Tags;
}
}
if (!lockedFields.Contains(MetadataFields.Keywords))
{
- var sourceHasKeywords = source as IHasKeywords;
- var targetHasKeywords = target as IHasKeywords;
-
- if (sourceHasKeywords != null && targetHasKeywords != null)
+ if (replaceData || target.Keywords.Count == 0)
{
- if (replaceData || targetHasKeywords.Keywords.Count == 0)
- {
- targetHasKeywords.Keywords = sourceHasKeywords.Keywords;
- }
+ target.Keywords = source.Keywords;
}
}
diff --git a/MediaBrowser.Providers/Manager/SeriesOrderManager.cs b/MediaBrowser.Providers/Manager/SeriesOrderManager.cs
deleted file mode 100644
index 1050bdbbd..000000000
--- a/MediaBrowser.Providers/Manager/SeriesOrderManager.cs
+++ /dev/null
@@ -1,35 +0,0 @@
-using System.Collections.Generic;
-using System.Linq;
-using System.Threading.Tasks;
-using MediaBrowser.Controller.Providers;
-
-namespace MediaBrowser.Providers.Manager
-{
- public class SeriesOrderManager : ISeriesOrderManager
- {
- private Dictionary<string, ISeriesOrderProvider[]> _providers;
-
- public void AddParts(IEnumerable<ISeriesOrderProvider> orderProviders)
- {
- _providers = orderProviders
- .GroupBy(p => p.OrderType)
- .ToDictionary(g => g.Key, g => g.ToArray());
- }
-
- public async Task<int?> FindSeriesIndex(string orderType, string seriesName)
- {
- ISeriesOrderProvider[] providers;
- if (!_providers.TryGetValue(orderType, out providers))
- return null;
-
- foreach (ISeriesOrderProvider provider in providers)
- {
- int? index = await provider.FindSeriesIndex(seriesName);
- if (index != null)
- return index;
- }
-
- return null;
- }
- }
-} \ No newline at end of file
diff --git a/MediaBrowser.Providers/MediaBrowser.Providers.csproj b/MediaBrowser.Providers/MediaBrowser.Providers.csproj
index f07478f98..99d8b15b6 100644
--- a/MediaBrowser.Providers/MediaBrowser.Providers.csproj
+++ b/MediaBrowser.Providers/MediaBrowser.Providers.csproj
@@ -89,7 +89,6 @@
<Compile Include="Channels\ChannelMetadataService.cs" />
<Compile Include="Chapters\ChapterManager.cs" />
<Compile Include="Folders\CollectionFolderMetadataService.cs" />
- <Compile Include="Folders\DefaultImageProvider.cs" />
<Compile Include="Folders\FolderMetadataService.cs" />
<Compile Include="Folders\UserViewMetadataService.cs" />
<Compile Include="GameGenres\GameGenreMetadataService.cs" />
@@ -104,7 +103,6 @@
<Compile Include="Manager\ItemImageProvider.cs" />
<Compile Include="Manager\ProviderManager.cs" />
<Compile Include="Manager\MetadataService.cs" />
- <Compile Include="Manager\SeriesOrderManager.cs" />
<Compile Include="MediaInfo\FFProbeAudioInfo.cs" />
<Compile Include="MediaInfo\FFProbeProvider.cs" />
<Compile Include="MediaInfo\FFProbeVideoInfo.cs" />
@@ -145,10 +143,7 @@
<Compile Include="Omdb\OmdbProvider.cs" />
<Compile Include="Omdb\OmdbItemProvider.cs" />
<Compile Include="People\MovieDbPersonImageProvider.cs" />
- <Compile Include="Movies\MovieUpdatesPrescanTask.cs" />
- <Compile Include="Movies\FanArtMovieUpdatesPostScanTask.cs" />
<Compile Include="Movies\MovieDbProvider.cs" />
- <Compile Include="Music\FanArtUpdatesPostScanTask.cs" />
<Compile Include="Music\FanArtAlbumProvider.cs" />
<Compile Include="Music\FanArtArtistProvider.cs" />
<Compile Include="Music\MusicBrainzAlbumProvider.cs" />
@@ -168,7 +163,6 @@
<Compile Include="Subtitles\SubtitleManager.cs" />
<Compile Include="TV\DummySeasonProvider.cs" />
<Compile Include="TV\EpisodeMetadataService.cs" />
- <Compile Include="TV\FanArt\FanArtTvUpdatesPostScanTask.cs" />
<Compile Include="TV\FanArt\FanArtSeasonProvider.cs" />
<Compile Include="TV\FanArt\FanartSeriesProvider.cs" />
<Compile Include="TV\MissingEpisodeProvider.cs" />
@@ -182,7 +176,6 @@
<Compile Include="TV\SeriesMetadataService.cs" />
<Compile Include="TV\TheTVDB\TvdbEpisodeImageProvider.cs" />
<Compile Include="People\TvdbPersonImageProvider.cs" />
- <Compile Include="TV\TheTVDB\TvdbSeasonIdentityProvider.cs" />
<Compile Include="TV\TheTVDB\TvdbSeasonImageProvider.cs" />
<Compile Include="TV\TheTVDB\TvdbSeriesImageProvider.cs" />
<Compile Include="TV\SeasonMetadataService.cs" />
diff --git a/MediaBrowser.Providers/Movies/FanArtMovieUpdatesPostScanTask.cs b/MediaBrowser.Providers/Movies/FanArtMovieUpdatesPostScanTask.cs
deleted file mode 100644
index 5262f8e3b..000000000
--- a/MediaBrowser.Providers/Movies/FanArtMovieUpdatesPostScanTask.cs
+++ /dev/null
@@ -1,196 +0,0 @@
-using MediaBrowser.Common.Net;
-using MediaBrowser.Controller.Configuration;
-using MediaBrowser.Controller.Library;
-using MediaBrowser.Model.Configuration;
-using MediaBrowser.Model.Logging;
-using MediaBrowser.Model.Serialization;
-using MediaBrowser.Providers.Music;
-using MediaBrowser.Providers.TV;
-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;
-using CommonIO;
-
-namespace MediaBrowser.Providers.Movies
-{
- class FanartMovieUpdatesPostScanTask : ILibraryPostScanTask
- {
- private const string UpdatesUrl = "https://webservice.fanart.tv/v3/movies/latest?api_key={0}&date={1}";
-
- /// <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 _jsonSerializer;
- private readonly IFileSystem _fileSystem;
-
- private static readonly CultureInfo UsCulture = new CultureInfo("en-US");
-
- public FanartMovieUpdatesPostScanTask(IJsonSerializer jsonSerializer, IServerConfigurationManager config, ILogger logger, IHttpClient httpClient, IFileSystem fileSystem)
- {
- _jsonSerializer = jsonSerializer;
- _config = config;
- _logger = logger;
- _httpClient = httpClient;
- _fileSystem = fileSystem;
- }
-
- /// <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)
- {
- var options = FanartSeriesProvider.Current.GetFanartOptions();
-
- if (!options.EnableAutomaticUpdates)
- {
- progress.Report(100);
- return;
- }
-
- var path = FanartMovieImageProvider.GetMoviesDataPath(_config.CommonApplicationPaths);
-
- _fileSystem.CreateDirectory(path);
-
- var timestampFile = Path.Combine(path, "time.txt");
-
- var timestampFileInfo = _fileSystem.GetFileInfo(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 for updates
- var lastUpdateTime = timestampFileInfo.Exists ? _fileSystem.ReadAllText(timestampFile, Encoding.UTF8) : string.Empty;
-
- var existingDirectories = Directory.EnumerateDirectories(path).Select(Path.GetFileName).ToList();
-
- // If this is our first time, don't do any updates and just record the timestamp
- if (!string.IsNullOrEmpty(lastUpdateTime))
- {
- var moviesToUpdate = await GetMovieIdsToUpdate(existingDirectories, lastUpdateTime, options, cancellationToken).ConfigureAwait(false);
-
- progress.Report(5);
-
- await UpdateMovies(moviesToUpdate, progress, cancellationToken).ConfigureAwait(false);
- }
-
- var newUpdateTime = Convert.ToInt64(DateTimeToUnixTimestamp(DateTime.UtcNow)).ToString(UsCulture);
-
- _fileSystem.WriteAllText(timestampFile, newUpdateTime, Encoding.UTF8);
-
- progress.Report(100);
- }
-
- private async Task<IEnumerable<string>> GetMovieIdsToUpdate(IEnumerable<string> existingIds, string lastUpdateTime, FanartOptions options, CancellationToken cancellationToken)
- {
- var url = string.Format(UpdatesUrl, FanartArtistProvider.ApiKey, lastUpdateTime);
-
- var clientKey = options.UserApiKey;
- if (!string.IsNullOrWhiteSpace(clientKey))
- {
- url += "&client_key=" + clientKey;
- }
-
- // First get last time
- using (var stream = await _httpClient.Get(new HttpRequestOptions
- {
- Url = url,
- CancellationToken = cancellationToken,
- EnableHttpCompression = true,
- ResourcePool = FanartArtistProvider.Current.FanArtResourcePool
-
- }).ConfigureAwait(false))
- {
- using (var reader = new StreamReader(stream))
- {
- var json = await reader.ReadToEndAsync().ConfigureAwait(false);
-
- // If empty fanart will return a string of "null", rather than an empty list
- if (string.Equals(json, "null", StringComparison.OrdinalIgnoreCase))
- {
- return new List<string>();
- }
-
- var updates = _jsonSerializer.DeserializeFromString<List<RootObject>>(json);
-
- var existingDictionary = existingIds.ToDictionary(i => i, StringComparer.OrdinalIgnoreCase);
-
- return updates.SelectMany(i =>
- {
- var list = new List<string>();
-
- if (!string.IsNullOrWhiteSpace(i.imdb_id))
- {
- list.Add(i.imdb_id);
- }
- if (!string.IsNullOrWhiteSpace(i.tmdb_id))
- {
- list.Add(i.tmdb_id);
- }
-
- return list;
-
- }).Where(existingDictionary.ContainsKey);
- }
- }
- }
-
- private async Task UpdateMovies(IEnumerable<string> idList, IProgress<double> progress, CancellationToken cancellationToken)
- {
- var list = idList.ToList();
- var numComplete = 0;
-
- foreach (var id in list)
- {
- _logger.Info("Updating movie " + id);
-
- await FanartMovieImageProvider.Current.DownloadMovieJson(id, cancellationToken).ConfigureAwait(false);
-
- numComplete++;
- double percent = numComplete;
- percent /= list.Count;
- percent *= 95;
-
- progress.Report(percent + 5);
- }
- }
-
- /// <summary>
- /// Dates the time to unix timestamp.
- /// </summary>
- /// <param name="dateTime">The date time.</param>
- /// <returns>System.Double.</returns>
- private static double DateTimeToUnixTimestamp(DateTime dateTime)
- {
- return (dateTime - new DateTime(1970, 1, 1).ToUniversalTime()).TotalSeconds;
- }
-
- public class RootObject
- {
- public string tmdb_id { get; set; }
- public string imdb_id { get; set; }
- public string name { get; set; }
- public string new_images { get; set; }
- public string total_images { get; set; }
- }
- }
-}
diff --git a/MediaBrowser.Providers/Movies/FanartMovieImageProvider.cs b/MediaBrowser.Providers/Movies/FanartMovieImageProvider.cs
index 775e6dfb9..18f177932 100644
--- a/MediaBrowser.Providers/Movies/FanartMovieImageProvider.cs
+++ b/MediaBrowser.Providers/Movies/FanartMovieImageProvider.cs
@@ -24,7 +24,7 @@ using MediaBrowser.Providers.TV;
namespace MediaBrowser.Providers.Movies
{
- public class FanartMovieImageProvider : IRemoteImageProvider, IHasItemChangeMonitor, IHasOrder
+ public class FanartMovieImageProvider : IRemoteImageProvider, IHasOrder
{
private readonly CultureInfo _usCulture = new CultureInfo("en-US");
private readonly IServerConfigurationManager _config;
@@ -241,33 +241,6 @@ namespace MediaBrowser.Providers.Movies
});
}
- public bool HasChanged(IHasMetadata item, IDirectoryService directoryService)
- {
- var options = FanartSeriesProvider.Current.GetFanartOptions();
- if (!options.EnableAutomaticUpdates)
- {
- return false;
- }
-
- var id = item.GetProviderId(MetadataProviders.Tmdb);
- if (string.IsNullOrEmpty(id))
- {
- id = item.GetProviderId(MetadataProviders.Imdb);
- }
-
- if (!string.IsNullOrEmpty(id))
- {
- // Process images
- var path = GetFanartJsonPath(id);
-
- var fileInfo = _fileSystem.GetFileInfo(path);
-
- return !fileInfo.Exists || _fileSystem.GetLastWriteTimeUtc(fileInfo) > item.DateLastRefreshed;
- }
-
- return false;
- }
-
/// <summary>
/// Gets the movie data path.
/// </summary>
diff --git a/MediaBrowser.Providers/Movies/GenericMovieDbInfo.cs b/MediaBrowser.Providers/Movies/GenericMovieDbInfo.cs
index d13716cba..a6b6de7e5 100644
--- a/MediaBrowser.Providers/Movies/GenericMovieDbInfo.cs
+++ b/MediaBrowser.Providers/Movies/GenericMovieDbInfo.cs
@@ -314,11 +314,7 @@ namespace MediaBrowser.Providers.Movies
if (movieData.keywords != null && movieData.keywords.keywords != null)
{
- var hasTags = movie as IHasKeywords;
- if (hasTags != null)
- {
- hasTags.Keywords = movieData.keywords.keywords.Select(i => i.name).ToList();
- }
+ movie.Keywords = movieData.keywords.keywords.Select(i => i.name).ToList();
}
if (movieData.trailers != null && movieData.trailers.youtube != null &&
@@ -330,8 +326,7 @@ namespace MediaBrowser.Providers.Movies
hasTrailers.RemoteTrailers = movieData.trailers.youtube.Select(i => new MediaUrl
{
Url = string.Format("https://www.youtube.com/watch?v={0}", i.source),
- Name = i.name,
- VideoSize = string.Equals("hd", i.size, StringComparison.OrdinalIgnoreCase) ? VideoSize.HighDefinition : VideoSize.StandardDefinition
+ Name = i.name
}).ToList();
}
diff --git a/MediaBrowser.Providers/Movies/MovieDbImageProvider.cs b/MediaBrowser.Providers/Movies/MovieDbImageProvider.cs
index 5958c3a0a..49f341f26 100644
--- a/MediaBrowser.Providers/Movies/MovieDbImageProvider.cs
+++ b/MediaBrowser.Providers/Movies/MovieDbImageProvider.cs
@@ -17,7 +17,7 @@ using System.Threading.Tasks;
namespace MediaBrowser.Providers.Movies
{
- class MovieDbImageProvider : IRemoteImageProvider, IHasOrder, IHasItemChangeMonitor
+ class MovieDbImageProvider : IRemoteImageProvider, IHasOrder
{
private readonly IJsonSerializer _jsonSerializer;
private readonly IHttpClient _httpClient;
@@ -63,6 +63,8 @@ namespace MediaBrowser.Providers.Movies
{
var list = new List<RemoteImageInfo>();
+ var language = item.GetPreferredMetadataLanguage();
+
var results = await FetchImages((BaseItem)item, null, _jsonSerializer, cancellationToken).ConfigureAwait(false);
if (results == null)
@@ -85,7 +87,7 @@ namespace MediaBrowser.Providers.Movies
VoteCount = i.vote_count,
Width = i.width,
Height = i.height,
- Language = i.iso_639_1,
+ Language = MovieDbProvider.AdjustImageLanguage(i.iso_639_1, language),
ProviderName = Name,
Type = ImageType.Primary,
RatingType = RatingType.Score
@@ -107,8 +109,6 @@ namespace MediaBrowser.Providers.Movies
}));
}
- var language = item.GetPreferredMetadataLanguage();
-
var isLanguageEn = string.Equals(language, "en", StringComparison.OrdinalIgnoreCase);
return list.OrderByDescending(i =>
@@ -221,10 +221,5 @@ namespace MediaBrowser.Providers.Movies
ResourcePool = MovieDbProvider.Current.MovieDbResourcePool
});
}
-
- public bool HasChanged(IHasMetadata item, IDirectoryService directoryService)
- {
- return MovieDbProvider.Current.HasChanged(item);
- }
}
}
diff --git a/MediaBrowser.Providers/Movies/MovieDbProvider.cs b/MediaBrowser.Providers/Movies/MovieDbProvider.cs
index c588a9a69..27b61225b 100644
--- a/MediaBrowser.Providers/Movies/MovieDbProvider.cs
+++ b/MediaBrowser.Providers/Movies/MovieDbProvider.cs
@@ -267,9 +267,21 @@ namespace MediaBrowser.Providers.Movies
if (!string.IsNullOrEmpty(preferredLanguage))
{
+ preferredLanguage = NormalizeLanguage(preferredLanguage);
+
languages.Add(preferredLanguage);
+
+ if (preferredLanguage.Length == 5) // like en-US
+ {
+ // Currenty, TMDB supports 2-letter language codes only
+ // They are planning to change this in the future, thus we're
+ // supplying both codes if we're having a 5-letter code.
+ languages.Add(preferredLanguage.Substring(0, 2));
+ }
}
+
languages.Add("null");
+
if (!string.Equals(preferredLanguage, "en", StringComparison.OrdinalIgnoreCase))
{
languages.Add("en");
@@ -280,18 +292,35 @@ namespace MediaBrowser.Providers.Movies
public static string NormalizeLanguage(string language)
{
- // They require this to be uppercase
- // https://emby.media/community/index.php?/topic/32454-fr-follow-tmdbs-new-language-api-update/?p=311148
- var parts = language.Split('-');
-
- if (parts.Length == 2)
+ if (!string.IsNullOrEmpty(language))
{
- language = parts[0] + "-" + parts[1].ToUpper();
+ // They require this to be uppercase
+ // https://emby.media/community/index.php?/topic/32454-fr-follow-tmdbs-new-language-api-update/?p=311148
+ var parts = language.Split('-');
+
+ if (parts.Length == 2)
+ {
+ language = parts[0] + "-" + parts[1].ToUpper();
+ }
}
return language;
}
+ public static string AdjustImageLanguage(string imageLanguage, string requestLanguage)
+ {
+ if (!string.IsNullOrEmpty(imageLanguage)
+ && !string.IsNullOrEmpty(requestLanguage)
+ && requestLanguage.Length > 2
+ && imageLanguage.Length == 2
+ && requestLanguage.StartsWith(imageLanguage, StringComparison.OrdinalIgnoreCase))
+ {
+ return requestLanguage;
+ }
+
+ return imageLanguage;
+ }
+
/// <summary>
/// Fetches the main result.
/// </summary>
@@ -409,33 +438,6 @@ namespace MediaBrowser.Providers.Movies
return await _httpClient.Get(options).ConfigureAwait(false);
}
- public TheMovieDbOptions GetTheMovieDbOptions()
- {
- return _configurationManager.GetConfiguration<TheMovieDbOptions>("themoviedb");
- }
-
- public bool HasChanged(IHasMetadata item)
- {
- if (!GetTheMovieDbOptions().EnableAutomaticUpdates)
- {
- return false;
- }
-
- var tmdbId = item.GetProviderId(MetadataProviders.Tmdb);
-
- if (!String.IsNullOrEmpty(tmdbId))
- {
- // Process images
- var dataFilePath = GetDataFilePath(tmdbId, item.GetPreferredMetadataLanguage());
-
- var fileInfo = _fileSystem.GetFileInfo(dataFilePath);
-
- return !fileInfo.Exists || _fileSystem.GetLastWriteTimeUtc(fileInfo) > item.DateLastRefreshed;
- }
-
- return false;
- }
-
public void Dispose()
{
Dispose(true);
@@ -659,19 +661,4 @@ namespace MediaBrowser.Providers.Movies
});
}
}
-
- public class TmdbConfigStore : IConfigurationFactory
- {
- public IEnumerable<ConfigurationStore> GetConfigurations()
- {
- return new List<ConfigurationStore>
- {
- new ConfigurationStore
- {
- Key = "themoviedb",
- ConfigurationType = typeof(TheMovieDbOptions)
- }
- };
- }
- }
}
diff --git a/MediaBrowser.Providers/Movies/MovieDbSearch.cs b/MediaBrowser.Providers/Movies/MovieDbSearch.cs
index ceb41178e..ab2cd3bed 100644
--- a/MediaBrowser.Providers/Movies/MovieDbSearch.cs
+++ b/MediaBrowser.Providers/Movies/MovieDbSearch.cs
@@ -22,7 +22,7 @@ namespace MediaBrowser.Providers.Movies
internal static string ApiKey = "f6bd687ffa63cd282b6ff2c6877f2669";
internal static string AcceptHeader = "application/json,image/*";
-
+
private readonly ILogger _logger;
private readonly IJsonSerializer _json;
private readonly ILibraryManager _libraryManager;
@@ -54,6 +54,11 @@ namespace MediaBrowser.Providers.Movies
var name = idInfo.Name;
var year = idInfo.Year;
+ if (string.IsNullOrWhiteSpace(name))
+ {
+ return new List<RemoteSearchResult>();
+ }
+
var tmdbSettings = await MovieDbProvider.Current.GetTmdbSettings(cancellationToken).ConfigureAwait(false);
var tmdbImageUrl = tmdbSettings.images.secure_base_url + "original";
@@ -73,7 +78,7 @@ namespace MediaBrowser.Providers.Movies
//var searchType = item is BoxSet ? "collection" : "movie";
var results = await GetSearchResults(name, searchType, year, language, tmdbImageUrl, cancellationToken).ConfigureAwait(false);
-
+
if (results.Count == 0)
{
//try in english if wasn't before
@@ -123,19 +128,23 @@ namespace MediaBrowser.Providers.Movies
});
}
- private async Task<List<RemoteSearchResult>> GetSearchResults(string name, string type, int? year, string language, string baseImageUrl, CancellationToken cancellationToken)
+ private Task<List<RemoteSearchResult>> GetSearchResults(string name, string type, int? year, string language, string baseImageUrl, CancellationToken cancellationToken)
{
switch (type)
{
case "tv":
- return await GetSearchResultsTv(name, year, language, baseImageUrl, cancellationToken);
+ return GetSearchResultsTv(name, year, language, baseImageUrl, cancellationToken);
default:
- return await GetSearchResultsGeneric(name, type, year, language, baseImageUrl, cancellationToken);
+ return GetSearchResultsGeneric(name, type, year, language, baseImageUrl, cancellationToken);
}
}
private async Task<List<RemoteSearchResult>> GetSearchResultsGeneric(string name, string type, int? year, string language, string baseImageUrl, CancellationToken cancellationToken)
{
+ if (string.IsNullOrWhiteSpace(name))
+ {
+ throw new ArgumentException("name");
+ }
var url3 = string.Format(Search3, WebUtility.UrlEncode(name), ApiKey, language, type);
@@ -189,6 +198,11 @@ namespace MediaBrowser.Providers.Movies
private async Task<List<RemoteSearchResult>> GetSearchResultsTv(string name, int? year, string language, string baseImageUrl, CancellationToken cancellationToken)
{
+ if (string.IsNullOrWhiteSpace(name))
+ {
+ throw new ArgumentException("name");
+ }
+
var url3 = string.Format(Search3, WebUtility.UrlEncode(name), ApiKey, language, "tv");
using (var json = await MovieDbProvider.Current.GetMovieDbResponse(new HttpRequestOptions
diff --git a/MediaBrowser.Providers/Movies/MovieDbTrailerProvider.cs b/MediaBrowser.Providers/Movies/MovieDbTrailerProvider.cs
index 5fb3ea369..1d8691ab8 100644
--- a/MediaBrowser.Providers/Movies/MovieDbTrailerProvider.cs
+++ b/MediaBrowser.Providers/Movies/MovieDbTrailerProvider.cs
@@ -33,11 +33,6 @@ namespace MediaBrowser.Providers.Movies
get { return MovieDbProvider.Current.Name; }
}
- public bool HasChanged(IHasMetadata item, IDirectoryService directoryService)
- {
- return MovieDbProvider.Current.HasChanged(item);
- }
-
public int Order
{
get
diff --git a/MediaBrowser.Providers/Movies/MovieExternalIds.cs b/MediaBrowser.Providers/Movies/MovieExternalIds.cs
index 3bceb976e..a6b7bde6f 100644
--- a/MediaBrowser.Providers/Movies/MovieExternalIds.cs
+++ b/MediaBrowser.Providers/Movies/MovieExternalIds.cs
@@ -148,6 +148,13 @@ namespace MediaBrowser.Providers.Movies
public bool Supports(IHasProviderIds item)
{
+ // Supports images for tv movies
+ var tvProgram = item as LiveTvProgram;
+ if (tvProgram != null && tvProgram.IsMovie)
+ {
+ return true;
+ }
+
return item is Movie || item is MusicVideo || item is Series || item is Episode || item is Trailer;
}
}
diff --git a/MediaBrowser.Providers/Movies/MovieMetadataService.cs b/MediaBrowser.Providers/Movies/MovieMetadataService.cs
index e70ec0057..83be9ca6f 100644
--- a/MediaBrowser.Providers/Movies/MovieMetadataService.cs
+++ b/MediaBrowser.Providers/Movies/MovieMetadataService.cs
@@ -13,10 +13,6 @@ namespace MediaBrowser.Providers.Movies
{
public class MovieMetadataService : MetadataService<Movie, MovieInfo>
{
- public MovieMetadataService(IServerConfigurationManager serverConfigurationManager, ILogger logger, IProviderManager providerManager, IProviderRepository providerRepo, IFileSystem fileSystem, IUserDataManager userDataManager, ILibraryManager libraryManager) : base(serverConfigurationManager, logger, providerManager, providerRepo, fileSystem, userDataManager, libraryManager)
- {
- }
-
protected override bool IsFullLocalMetadata(Movie item)
{
if (string.IsNullOrWhiteSpace(item.Overview))
@@ -42,15 +38,14 @@ namespace MediaBrowser.Providers.Movies
targetItem.CollectionName = sourceItem.CollectionName;
}
}
- }
- public class TrailerMetadataService : MetadataService<Trailer, TrailerInfo>
- {
- public TrailerMetadataService(IServerConfigurationManager serverConfigurationManager, ILogger logger, IProviderManager providerManager, IProviderRepository providerRepo, IFileSystem fileSystem, IUserDataManager userDataManager, ILibraryManager libraryManager)
- : base(serverConfigurationManager, logger, providerManager, providerRepo, fileSystem, userDataManager, libraryManager)
+ public MovieMetadataService(IServerConfigurationManager serverConfigurationManager, ILogger logger, IProviderManager providerManager, IFileSystem fileSystem, IUserDataManager userDataManager, ILibraryManager libraryManager) : base(serverConfigurationManager, logger, providerManager, fileSystem, userDataManager, libraryManager)
{
}
+ }
+ public class TrailerMetadataService : MetadataService<Trailer, TrailerInfo>
+ {
protected override bool IsFullLocalMetadata(Trailer item)
{
if (string.IsNullOrWhiteSpace(item.Overview))
@@ -73,6 +68,10 @@ namespace MediaBrowser.Providers.Movies
target.Item.TrailerTypes = source.Item.TrailerTypes;
}
}
+
+ public TrailerMetadataService(IServerConfigurationManager serverConfigurationManager, ILogger logger, IProviderManager providerManager, IFileSystem fileSystem, IUserDataManager userDataManager, ILibraryManager libraryManager) : base(serverConfigurationManager, logger, providerManager, fileSystem, userDataManager, libraryManager)
+ {
+ }
}
}
diff --git a/MediaBrowser.Providers/Movies/MovieUpdatesPrescanTask.cs b/MediaBrowser.Providers/Movies/MovieUpdatesPrescanTask.cs
deleted file mode 100644
index 70c155ad5..000000000
--- a/MediaBrowser.Providers/Movies/MovieUpdatesPrescanTask.cs
+++ /dev/null
@@ -1,249 +0,0 @@
-using MediaBrowser.Common.Net;
-using MediaBrowser.Controller.Configuration;
-using MediaBrowser.Controller.Entities.Movies;
-using MediaBrowser.Controller.Library;
-using MediaBrowser.Model.Entities;
-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;
-using CommonIO;
-
-namespace MediaBrowser.Providers.Movies
-{
- public class MovieUpdatesPreScanTask : ILibraryPostScanTask
- {
- /// <summary>
- /// The updates URL
- /// </summary>
- private const string UpdatesUrl = "https://api.themoviedb.org/3/movie/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;
- private readonly ILibraryManager _libraryManager;
-
- /// <summary>
- /// Initializes a new instance of the <see cref="MovieUpdatesPreScanTask"/> class.
- /// </summary>
- /// <param name="logger">The logger.</param>
- /// <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, ILibraryManager libraryManager)
- {
- _logger = logger;
- _httpClient = httpClient;
- _config = config;
- _json = json;
- _fileSystem = fileSystem;
- _libraryManager = libraryManager;
- }
-
- 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 (!MovieDbProvider.Current.GetTheMovieDbOptions().EnableAutomaticUpdates)
- {
- progress.Report(100);
- return;
- }
-
- var path = MovieDbProvider.GetMoviesDataPath(_config.CommonApplicationPaths);
-
- _fileSystem.CreateDirectory(path);
-
- var timestampFile = Path.Combine(path, "time.txt");
-
- var timestampFileInfo = _fileSystem.GetFileInfo(timestampFile);
-
- // Don't check for updates every single time
- if (timestampFileInfo.Exists && (DateTime.UtcNow - _fileSystem.GetLastWriteTimeUtc(timestampFileInfo)).TotalDays < 7)
- {
- return;
- }
-
- // Find out the last time we queried tvdb for updates
- var lastUpdateTime = timestampFileInfo.Exists ? _fileSystem.ReadAllText(timestampFile, Encoding.UTF8) : string.Empty;
-
- var existingDirectories = Directory.EnumerateDirectories(path).Select(Path.GetFileName).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 UpdateMovies(idsToUpdate, progress, cancellationToken).ConfigureAwait(false);
- }
- }
-
- _fileSystem.WriteAllText(timestampFile, DateTime.UtcNow.Ticks.ToString(UsCulture), Encoding.UTF8);
- progress.Report(100);
- }
-
-
- /// <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)
- {
- bool hasMorePages;
- 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 movies.
- /// </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 UpdateMovies(IEnumerable<string> ids, IProgress<double> progress, CancellationToken cancellationToken)
- {
- var list = ids.ToList();
- var numComplete = 0;
-
- // Gather all movies into a lookup by tmdb id
- var allMovies = _libraryManager.GetItemList(new Controller.Entities.InternalItemsQuery
- {
- IncludeItemTypes = new[] {typeof (Movie).Name},
- Recursive = true
-
- }).Where(i => !string.IsNullOrEmpty(i.GetProviderId(MetadataProviders.Tmdb)))
- .ToLookup(i => i.GetProviderId(MetadataProviders.Tmdb));
-
- foreach (var id in list)
- {
- // 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)
- {
- try
- {
- await UpdateMovie(id, language, cancellationToken).ConfigureAwait(false);
- }
- catch (Exception ex)
- {
- _logger.ErrorException("Error updating tmdb movie id {0}, language {1}", ex, id, language);
- }
- }
-
- numComplete++;
- double percent = numComplete;
- percent /= list.Count;
- percent *= 100;
-
- progress.Report(percent);
- }
- }
-
- /// <summary>
- /// Updates the movie.
- /// </summary>
- /// <param name="id">The id.</param>
- /// <param name="preferredMetadataLanguage">The preferred metadata language.</param>
- /// <param name="cancellationToken">The cancellation token.</param>
- /// <returns>Task.</returns>
- private Task UpdateMovie(string id, string preferredMetadataLanguage, CancellationToken cancellationToken)
- {
- _logger.Info("Updating movie from tmdb " + id + ", language " + preferredMetadataLanguage);
-
- return MovieDbProvider.Current.DownloadMovieInfo(id, preferredMetadataLanguage, 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/AlbumMetadataService.cs b/MediaBrowser.Providers/Music/AlbumMetadataService.cs
index 8f951723e..4f87b2036 100644
--- a/MediaBrowser.Providers/Music/AlbumMetadataService.cs
+++ b/MediaBrowser.Providers/Music/AlbumMetadataService.cs
@@ -15,10 +15,6 @@ namespace MediaBrowser.Providers.Music
{
public class AlbumMetadataService : MetadataService<MusicAlbum, AlbumInfo>
{
- public AlbumMetadataService(IServerConfigurationManager serverConfigurationManager, ILogger logger, IProviderManager providerManager, IProviderRepository providerRepo, IFileSystem fileSystem, IUserDataManager userDataManager, ILibraryManager libraryManager) : base(serverConfigurationManager, logger, providerManager, providerRepo, fileSystem, userDataManager, libraryManager)
- {
- }
-
protected override async Task<ItemUpdateType> BeforeSave(MusicAlbum item, bool isFullRefresh, ItemUpdateType currentUpdateType)
{
var updateType = await base.BeforeSave(item, isFullRefresh, currentUpdateType).ConfigureAwait(false);
@@ -166,5 +162,9 @@ namespace MediaBrowser.Providers.Music
targetItem.Artists = sourceItem.Artists;
}
}
+
+ public AlbumMetadataService(IServerConfigurationManager serverConfigurationManager, ILogger logger, IProviderManager providerManager, IFileSystem fileSystem, IUserDataManager userDataManager, ILibraryManager libraryManager) : base(serverConfigurationManager, logger, providerManager, fileSystem, userDataManager, libraryManager)
+ {
+ }
}
}
diff --git a/MediaBrowser.Providers/Music/ArtistMetadataService.cs b/MediaBrowser.Providers/Music/ArtistMetadataService.cs
index 21e9b006b..b2f975b13 100644
--- a/MediaBrowser.Providers/Music/ArtistMetadataService.cs
+++ b/MediaBrowser.Providers/Music/ArtistMetadataService.cs
@@ -15,10 +15,6 @@ namespace MediaBrowser.Providers.Music
{
public class ArtistMetadataService : MetadataService<MusicArtist, ArtistInfo>
{
- public ArtistMetadataService(IServerConfigurationManager serverConfigurationManager, ILogger logger, IProviderManager providerManager, IProviderRepository providerRepo, IFileSystem fileSystem, IUserDataManager userDataManager, ILibraryManager libraryManager) : base(serverConfigurationManager, logger, providerManager, providerRepo, fileSystem, userDataManager, libraryManager)
- {
- }
-
protected override async Task<ItemUpdateType> BeforeSave(MusicArtist item, bool isFullRefresh, ItemUpdateType currentUpdateType)
{
var updateType = await base.BeforeSave(item, isFullRefresh, currentUpdateType).ConfigureAwait(false);
@@ -58,5 +54,9 @@ namespace MediaBrowser.Providers.Music
{
ProviderUtils.MergeBaseItemData(source, target, lockedFields, replaceData, mergeMetadataSettings);
}
+
+ public ArtistMetadataService(IServerConfigurationManager serverConfigurationManager, ILogger logger, IProviderManager providerManager, IFileSystem fileSystem, IUserDataManager userDataManager, ILibraryManager libraryManager) : base(serverConfigurationManager, logger, providerManager, fileSystem, userDataManager, libraryManager)
+ {
+ }
}
}
diff --git a/MediaBrowser.Providers/Music/AudioMetadataService.cs b/MediaBrowser.Providers/Music/AudioMetadataService.cs
index 161a16193..532128186 100644
--- a/MediaBrowser.Providers/Music/AudioMetadataService.cs
+++ b/MediaBrowser.Providers/Music/AudioMetadataService.cs
@@ -12,10 +12,6 @@ namespace MediaBrowser.Providers.Music
{
public class AudioMetadataService : MetadataService<Audio, SongInfo>
{
- public AudioMetadataService(IServerConfigurationManager serverConfigurationManager, ILogger logger, IProviderManager providerManager, IProviderRepository providerRepo, IFileSystem fileSystem, IUserDataManager userDataManager, ILibraryManager libraryManager) : base(serverConfigurationManager, logger, providerManager, providerRepo, fileSystem, userDataManager, libraryManager)
- {
- }
-
protected override void MergeData(MetadataResult<Audio> source, MetadataResult<Audio> target, List<MetadataFields> lockedFields, bool replaceData, bool mergeMetadataSettings)
{
ProviderUtils.MergeBaseItemData(source, target, lockedFields, replaceData, mergeMetadataSettings);
@@ -33,5 +29,9 @@ namespace MediaBrowser.Providers.Music
targetItem.Album = sourceItem.Album;
}
}
+
+ public AudioMetadataService(IServerConfigurationManager serverConfigurationManager, ILogger logger, IProviderManager providerManager, IFileSystem fileSystem, IUserDataManager userDataManager, ILibraryManager libraryManager) : base(serverConfigurationManager, logger, providerManager, fileSystem, userDataManager, libraryManager)
+ {
+ }
}
}
diff --git a/MediaBrowser.Providers/Music/FanArtAlbumProvider.cs b/MediaBrowser.Providers/Music/FanArtAlbumProvider.cs
index 5b3bd87db..a14c7123a 100644
--- a/MediaBrowser.Providers/Music/FanArtAlbumProvider.cs
+++ b/MediaBrowser.Providers/Music/FanArtAlbumProvider.cs
@@ -12,6 +12,7 @@ using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
+using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
using CommonIO;
@@ -19,7 +20,7 @@ using MediaBrowser.Model.Serialization;
namespace MediaBrowser.Providers.Music
{
- public class FanartAlbumProvider : IRemoteImageProvider, IHasItemChangeMonitor, IHasOrder
+ public class FanartAlbumProvider : IRemoteImageProvider, IHasOrder
{
private readonly CultureInfo _usCulture = new CultureInfo("en-US");
private readonly IServerConfigurationManager _config;
@@ -151,6 +152,7 @@ namespace MediaBrowser.Providers.Music
}
}
+ private Regex _regex_http = new Regex("^http://");
private void PopulateImages(List<RemoteImageInfo> list,
List<FanartArtistProvider.FanartArtistImage> images,
ImageType type,
@@ -178,7 +180,7 @@ namespace MediaBrowser.Providers.Music
Width = width,
Height = height,
ProviderName = Name,
- Url = url,
+ Url = _regex_http.Replace(url, "https://", 1),
Language = i.lang
};
@@ -212,34 +214,5 @@ namespace MediaBrowser.Providers.Music
ResourcePool = FanartArtistProvider.Current.FanArtResourcePool
});
}
-
- public bool HasChanged(IHasMetadata item, IDirectoryService directoryService)
- {
- var options = FanartSeriesProvider.Current.GetFanartOptions();
- if (!options.EnableAutomaticUpdates)
- {
- return false;
- }
-
- var album = (MusicAlbum)item;
- var artist = album.MusicArtist;
-
- if (artist != null)
- {
- var artistMusicBrainzId = artist.GetProviderId(MetadataProviders.MusicBrainzArtist);
-
- if (!String.IsNullOrEmpty(artistMusicBrainzId))
- {
- // Process images
- var artistJsonPath = FanartArtistProvider.GetArtistJsonPath(_config.CommonApplicationPaths, artistMusicBrainzId);
-
- var fileInfo = _fileSystem.GetFileInfo(artistJsonPath);
-
- return !fileInfo.Exists || _fileSystem.GetLastWriteTimeUtc(fileInfo) > item.DateLastRefreshed;
- }
- }
-
- return false;
- }
}
}
diff --git a/MediaBrowser.Providers/Music/FanArtArtistProvider.cs b/MediaBrowser.Providers/Music/FanArtArtistProvider.cs
index 37b51da5a..6afa80507 100644
--- a/MediaBrowser.Providers/Music/FanArtArtistProvider.cs
+++ b/MediaBrowser.Providers/Music/FanArtArtistProvider.cs
@@ -14,6 +14,7 @@ using System.Globalization;
using System.IO;
using System.Linq;
using System.Net;
+using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
using CommonIO;
@@ -22,7 +23,7 @@ using MediaBrowser.Model.Serialization;
namespace MediaBrowser.Providers.Music
{
- public class FanartArtistProvider : IRemoteImageProvider, IHasItemChangeMonitor, IHasOrder
+ public class FanartArtistProvider : IRemoteImageProvider, IHasOrder
{
internal readonly SemaphoreSlim FanArtResourcePool = new SemaphoreSlim(3, 3);
internal const string ApiKey = "5c6b04c68e904cfed1e6cbc9a9e683d4";
@@ -149,6 +150,7 @@ namespace MediaBrowser.Providers.Music
PopulateImages(list, obj.musicarts, ImageType.Art, 500, 281);
}
+ private Regex _regex_http = new Regex("^http://");
private void PopulateImages(List<RemoteImageInfo> list,
List<FanartArtistImage> images,
ImageType type,
@@ -176,7 +178,7 @@ namespace MediaBrowser.Providers.Music
Width = width,
Height = height,
ProviderName = Name,
- Url = url,
+ Url = _regex_http.Replace(url, "https://", 1),
Language = i.lang
};
@@ -207,29 +209,6 @@ namespace MediaBrowser.Providers.Music
});
}
- public bool HasChanged(IHasMetadata item, IDirectoryService directoryService)
- {
- var options = FanartSeriesProvider.Current.GetFanartOptions();
- if (!options.EnableAutomaticUpdates)
- {
- return false;
- }
-
- var id = item.GetProviderId(MetadataProviders.MusicBrainzArtist);
-
- if (!String.IsNullOrEmpty(id))
- {
- // Process images
- var artistJsonPath = GetArtistJsonPath(_config.CommonApplicationPaths, id);
-
- var fileInfo = _fileSystem.GetFileInfo(artistJsonPath);
-
- return !fileInfo.Exists || _fileSystem.GetLastWriteTimeUtc(fileInfo) > item.DateLastRefreshed;
- }
-
- return false;
- }
-
private readonly Task _cachedTask = Task.FromResult(true);
internal Task EnsureArtistJson(string musicBrainzId, CancellationToken cancellationToken)
{
diff --git a/MediaBrowser.Providers/Music/FanArtUpdatesPostScanTask.cs b/MediaBrowser.Providers/Music/FanArtUpdatesPostScanTask.cs
deleted file mode 100644
index 3b829af9e..000000000
--- a/MediaBrowser.Providers/Music/FanArtUpdatesPostScanTask.cs
+++ /dev/null
@@ -1,203 +0,0 @@
-using MediaBrowser.Common.Net;
-using MediaBrowser.Controller.Configuration;
-using MediaBrowser.Controller.Library;
-using MediaBrowser.Model.Configuration;
-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;
-using CommonIO;
-using MediaBrowser.Providers.TV;
-
-namespace MediaBrowser.Providers.Music
-{
- class FanartUpdatesPostScanTask : ILibraryPostScanTask
- {
- private const string UpdatesUrl = "https://api.fanart.tv/webservice/newmusic/{0}/{1}/";
-
- /// <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 _jsonSerializer;
- private readonly IFileSystem _fileSystem;
-
- private static readonly CultureInfo UsCulture = new CultureInfo("en-US");
-
- public FanartUpdatesPostScanTask(IJsonSerializer jsonSerializer, IServerConfigurationManager config, ILogger logger, IHttpClient httpClient, IFileSystem fileSystem)
- {
- _jsonSerializer = jsonSerializer;
- _config = config;
- _logger = logger;
- _httpClient = httpClient;
- _fileSystem = fileSystem;
- }
-
- /// <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)
- {
- var options = FanartSeriesProvider.Current.GetFanartOptions();
-
- if (!options.EnableAutomaticUpdates)
- {
- progress.Report(100);
- return;
- }
-
- var path = FanartArtistProvider.GetArtistDataPath(_config.CommonApplicationPaths);
-
- _fileSystem.CreateDirectory(path);
-
- var timestampFile = Path.Combine(path, "time.txt");
-
- var timestampFileInfo = _fileSystem.GetFileInfo(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 for updates
- var lastUpdateTime = timestampFileInfo.Exists ? _fileSystem.ReadAllText(timestampFile, Encoding.UTF8) : string.Empty;
-
- var existingDirectories = Directory.EnumerateDirectories(path).Select(Path.GetFileName).ToList();
-
- // If this is our first time, don't do any updates and just record the timestamp
- if (!string.IsNullOrEmpty(lastUpdateTime))
- {
- var artistsToUpdate = await GetArtistIdsToUpdate(existingDirectories, lastUpdateTime, options, cancellationToken).ConfigureAwait(false);
-
- progress.Report(5);
-
- await UpdateArtists(artistsToUpdate, progress, cancellationToken).ConfigureAwait(false);
- }
-
- var newUpdateTime = Convert.ToInt64(DateTimeToUnixTimestamp(DateTime.UtcNow)).ToString(UsCulture);
-
- _fileSystem.WriteAllText(timestampFile, newUpdateTime, Encoding.UTF8);
-
- progress.Report(100);
- }
-
- /// <summary>
- /// Gets the artist ids to update.
- /// </summary>
- /// <param name="existingArtistIds">The existing series ids.</param>
- /// <param name="lastUpdateTime">The last update time.</param>
- /// <param name="cancellationToken">The cancellation token.</param>
- /// <returns>Task{IEnumerable{System.String}}.</returns>
- private async Task<IEnumerable<string>> GetArtistIdsToUpdate(IEnumerable<string> existingArtistIds, string lastUpdateTime, FanartOptions options, CancellationToken cancellationToken)
- {
- var url = string.Format(UpdatesUrl, FanartArtistProvider.ApiKey, lastUpdateTime);
-
- if (!string.IsNullOrWhiteSpace(options.UserApiKey))
- {
- url += "&client_key=" + options.UserApiKey;
- }
-
- // First get last time
- using (var stream = await _httpClient.Get(new HttpRequestOptions
- {
- Url = url,
- CancellationToken = cancellationToken,
- EnableHttpCompression = true,
- ResourcePool = FanartArtistProvider.Current.FanArtResourcePool
-
- }).ConfigureAwait(false))
- {
- // If empty fanart will return a string of "null", rather than an empty list
- using (var reader = new StreamReader(stream))
- {
- var json = await reader.ReadToEndAsync().ConfigureAwait(false);
-
- if (string.Equals(json, "null", StringComparison.OrdinalIgnoreCase))
- {
- return new List<string>();
- }
-
- var existingDictionary = existingArtistIds.ToDictionary(i => i, StringComparer.OrdinalIgnoreCase);
-
- var updates = _jsonSerializer.DeserializeFromString<List<FanArtUpdate>>(json);
-
- return updates.Select(i => i.id).Where(existingDictionary.ContainsKey);
- }
- }
- }
-
- /// <summary>
- /// Updates the artists.
- /// </summary>
- /// <param name="idList">The id list.</param>
- /// <param name="progress">The progress.</param>
- /// <param name="cancellationToken">The cancellation token.</param>
- /// <returns>Task.</returns>
- private async Task UpdateArtists(IEnumerable<string> idList, IProgress<double> progress, CancellationToken cancellationToken)
- {
- var list = idList.ToList();
- var numComplete = 0;
-
- foreach (var id in list)
- {
- await UpdateArtist(id, cancellationToken).ConfigureAwait(false);
-
- numComplete++;
- double percent = numComplete;
- percent /= list.Count;
- percent *= 95;
-
- progress.Report(percent + 5);
- }
- }
-
- /// <summary>
- /// Updates the artist.
- /// </summary>
- /// <param name="musicBrainzId">The musicBrainzId.</param>
- /// <param name="cancellationToken">The cancellation token.</param>
- /// <returns>Task.</returns>
- private Task UpdateArtist(string musicBrainzId, CancellationToken cancellationToken)
- {
- _logger.Info("Updating artist " + musicBrainzId);
-
- return FanartArtistProvider.Current.DownloadArtistJson(musicBrainzId, cancellationToken);
- }
-
- /// <summary>
- /// Dates the time to unix timestamp.
- /// </summary>
- /// <param name="dateTime">The date time.</param>
- /// <returns>System.Double.</returns>
- private static double DateTimeToUnixTimestamp(DateTime dateTime)
- {
- return (dateTime - new DateTime(1970, 1, 1).ToUniversalTime()).TotalSeconds;
- }
-
- public class FanArtUpdate
- {
- public string id { get; set; }
- public string name { get; set; }
- public string new_images { get; set; }
- public string total_images { get; set; }
- }
- }
-}
diff --git a/MediaBrowser.Providers/Music/MovieDbMusicVideoProvider.cs b/MediaBrowser.Providers/Music/MovieDbMusicVideoProvider.cs
index d031b3d6b..88689dd5a 100644
--- a/MediaBrowser.Providers/Music/MovieDbMusicVideoProvider.cs
+++ b/MediaBrowser.Providers/Music/MovieDbMusicVideoProvider.cs
@@ -27,11 +27,6 @@ namespace MediaBrowser.Providers.Music
get { return MovieDbProvider.Current.Name; }
}
- public bool HasChanged(IHasMetadata item, IDirectoryService directoryService)
- {
- return MovieDbProvider.Current.HasChanged(item);
- }
-
public Task<HttpResponseInfo> GetImageResponse(string url, CancellationToken cancellationToken)
{
throw new NotImplementedException();
diff --git a/MediaBrowser.Providers/Music/MusicBrainzAlbumProvider.cs b/MediaBrowser.Providers/Music/MusicBrainzAlbumProvider.cs
index e41982ef6..1710ec2b0 100644
--- a/MediaBrowser.Providers/Music/MusicBrainzAlbumProvider.cs
+++ b/MediaBrowser.Providers/Music/MusicBrainzAlbumProvider.cs
@@ -14,6 +14,7 @@ using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Xml;
+using MediaBrowser.Model.Serialization;
namespace MediaBrowser.Providers.Music
{
@@ -24,12 +25,16 @@ namespace MediaBrowser.Providers.Music
private readonly IHttpClient _httpClient;
private readonly IApplicationHost _appHost;
private readonly ILogger _logger;
+ private readonly IJsonSerializer _json;
- public MusicBrainzAlbumProvider(IHttpClient httpClient, IApplicationHost appHost, ILogger logger)
+ public static string MusicBrainzBaseUrl = "https://www.musicbrainz.org";
+
+ public MusicBrainzAlbumProvider(IHttpClient httpClient, IApplicationHost appHost, ILogger logger, IJsonSerializer json)
{
_httpClient = httpClient;
_appHost = appHost;
_logger = logger;
+ _json = json;
Current = this;
}
@@ -42,7 +47,7 @@ namespace MediaBrowser.Providers.Music
if (!string.IsNullOrEmpty(releaseId))
{
- url = string.Format("https://www.musicbrainz.org/ws/2/release/?query=reid:{0}", releaseId);
+ url = string.Format("/ws/2/release/?query=reid:{0}", releaseId);
}
else
{
@@ -50,7 +55,7 @@ namespace MediaBrowser.Providers.Music
if (!string.IsNullOrWhiteSpace(artistMusicBrainzId))
{
- url = string.Format("https://www.musicbrainz.org/ws/2/release/?query=\"{0}\" AND arid:{1}",
+ url = string.Format("/ws/2/release/?query=\"{0}\" AND arid:{1}",
WebUtility.UrlEncode(searchInfo.Name),
artistMusicBrainzId);
}
@@ -58,7 +63,7 @@ namespace MediaBrowser.Providers.Music
{
isNameSearch = true;
- url = string.Format("https://www.musicbrainz.org/ws/2/release/?query=\"{0}\" AND artist:\"{1}\"",
+ url = string.Format("/ws/2/release/?query=\"{0}\" AND artist:\"{1}\"",
WebUtility.UrlEncode(searchInfo.Name),
WebUtility.UrlEncode(searchInfo.GetAlbumArtist()));
}
@@ -77,7 +82,7 @@ namespace MediaBrowser.Providers.Music
private IEnumerable<RemoteSearchResult> GetResultsFromResponse(XmlDocument doc)
{
var ns = new XmlNamespaceManager(doc.NameTable);
- ns.AddNamespace("mb", "https://musicbrainz.org/ns/mmd-2.0#");
+ ns.AddNamespace("mb", MusicBrainzBaseUrl + "/ns/mmd-2.0#");
var list = new List<RemoteSearchResult>();
@@ -197,57 +202,86 @@ namespace MediaBrowser.Providers.Music
private async Task<ReleaseResult> GetReleaseResult(string albumName, string artistId, CancellationToken cancellationToken)
{
- var url = string.Format("https://www.musicbrainz.org/ws/2/release/?query=\"{0}\" AND arid:{1}",
+ var url = string.Format("/ws/2/release/?query=\"{0}\" AND arid:{1}",
WebUtility.UrlEncode(albumName),
artistId);
var doc = await GetMusicBrainzResponse(url, true, cancellationToken).ConfigureAwait(false);
- return GetReleaseResult(doc);
+ return ReleaseResult.Parse(doc);
}
private async Task<ReleaseResult> GetReleaseResultByArtistName(string albumName, string artistName, CancellationToken cancellationToken)
{
- var url = string.Format("https://www.musicbrainz.org/ws/2/release/?query=\"{0}\" AND artist:\"{1}\"",
+ var url = string.Format("/ws/2/release/?query=\"{0}\" AND artist:\"{1}\"",
WebUtility.UrlEncode(albumName),
WebUtility.UrlEncode(artistName));
var doc = await GetMusicBrainzResponse(url, true, cancellationToken).ConfigureAwait(false);
- return GetReleaseResult(doc);
+ return ReleaseResult.Parse(doc);
}
- private ReleaseResult GetReleaseResult(XmlDocument doc)
+ private class ReleaseResult
{
- var ns = new XmlNamespaceManager(doc.NameTable);
- ns.AddNamespace("mb", "https://musicbrainz.org/ns/mmd-2.0#");
+ public string ReleaseId;
+ public string ReleaseGroupId;
- var result = new ReleaseResult
+ public static ReleaseResult Parse(XmlDocument doc)
{
+ var docElem = doc.DocumentElement;
- };
+ if (docElem == null)
+ {
+ return new ReleaseResult();
+ }
- var releaseIdNode = doc.SelectSingleNode("//mb:release-list/mb:release/@id", ns);
+ var releaseList = docElem.FirstChild;
+ if (releaseList == null)
+ {
+ return new ReleaseResult();
+ }
- if (releaseIdNode != null)
- {
- result.ReleaseId = releaseIdNode.Value;
- }
+ var nodes = releaseList.ChildNodes;
+ string releaseId = null;
+ string releaseGroupId = null;
- var releaseGroupIdNode = doc.SelectSingleNode("//mb:release-list/mb:release/mb:release-group/@id", ns);
+ if (nodes != null)
+ {
+ foreach (var node in nodes.Cast<XmlNode>())
+ {
+ if (string.Equals(node.Name, "release", StringComparison.OrdinalIgnoreCase))
+ {
+ releaseId = node.Attributes["id"].Value;
+ releaseGroupId = GetReleaseGroupIdFromReleaseNode(node);
+ break;
+ }
+ }
+ }
- if (releaseGroupIdNode != null)
- {
- result.ReleaseGroupId = releaseGroupIdNode.Value;
+ return new ReleaseResult
+ {
+ ReleaseId = releaseId,
+ ReleaseGroupId = releaseGroupId
+ };
}
- return result;
- }
+ private static string GetReleaseGroupIdFromReleaseNode(XmlNode node)
+ {
+ var subNodes = node.ChildNodes;
+ if (subNodes != null)
+ {
+ foreach (var subNode in subNodes.Cast<XmlNode>())
+ {
+ if (string.Equals(subNode.Name, "release-group", StringComparison.OrdinalIgnoreCase))
+ {
+ return subNode.Attributes["id"].Value;
+ }
+ }
+ }
- private class ReleaseResult
- {
- public string ReleaseId;
- public string ReleaseGroupId;
+ return null;
+ }
}
/// <summary>
@@ -258,15 +292,36 @@ namespace MediaBrowser.Providers.Music
/// <returns>Task{System.String}.</returns>
private async Task<string> GetReleaseGroupId(string releaseEntryId, CancellationToken cancellationToken)
{
- var url = string.Format("https://www.musicbrainz.org/ws/2/release-group/?query=reid:{0}", releaseEntryId);
+ var url = string.Format("/ws/2/release-group/?query=reid:{0}", releaseEntryId);
var doc = await GetMusicBrainzResponse(url, false, cancellationToken).ConfigureAwait(false);
- var ns = new XmlNamespaceManager(doc.NameTable);
- ns.AddNamespace("mb", "https://musicbrainz.org/ns/mmd-2.0#");
- var node = doc.SelectSingleNode("//mb:release-group-list/mb:release-group/@id", ns);
+ var docElem = doc.DocumentElement;
+
+ if (docElem == null)
+ {
+ return null;
+ }
- return node != null ? node.Value : null;
+ var releaseList = docElem.FirstChild;
+ if (releaseList == null)
+ {
+ return null;
+ }
+
+ var nodes = releaseList.ChildNodes;
+
+ if (nodes != null)
+ {
+ foreach (var node in nodes.Cast<XmlNode>())
+ {
+ if (string.Equals(node.Name, "release-group", StringComparison.OrdinalIgnoreCase))
+ {
+ return node.Attributes["id"].Value;
+ }
+ }
+ }
+ return null;
}
/// <summary>
@@ -274,6 +329,66 @@ namespace MediaBrowser.Providers.Music
/// </summary>
private readonly SemaphoreSlim _musicBrainzResourcePool = new SemaphoreSlim(1, 1);
+ private long _lastMbzUrlQueryTicks = 0;
+ private List<MbzUrl> _mbzUrls = null;
+ private MbzUrl _chosenUrl;
+
+ private async Task<MbzUrl> GetMbzUrl()
+ {
+ if (_chosenUrl == null || _mbzUrls == null || (DateTime.UtcNow.Ticks - _lastMbzUrlQueryTicks) > TimeSpan.FromHours(12).Ticks)
+ {
+ var urls = await RefreshMzbUrls().ConfigureAwait(false);
+
+ if (urls.Count > 1)
+ {
+ _chosenUrl = urls[new Random().Next(0, urls.Count)];
+ }
+ else
+ {
+ _chosenUrl = urls[0];
+ }
+ }
+
+ return _chosenUrl;
+ }
+
+ private async Task<List<MbzUrl>> RefreshMzbUrls()
+ {
+ List<MbzUrl> list;
+
+ try
+ {
+ var options = new HttpRequestOptions
+ {
+ Url = "https://mb3admin.com/admin/service/standards/musicBrainzUrls",
+ UserAgent = _appHost.Name + "/" + _appHost.ApplicationVersion
+ };
+
+ using (var stream = await _httpClient.Get(options).ConfigureAwait(false))
+ {
+ list = _json.DeserializeFromStream<List<MbzUrl>>(stream);
+ }
+ _lastMbzUrlQueryTicks = DateTime.UtcNow.Ticks;
+ }
+ catch (Exception ex)
+ {
+ _logger.ErrorException("Error getting music brainz info", ex);
+
+ list = new List<MbzUrl>
+ {
+ new MbzUrl
+ {
+ url = MusicBrainzBaseUrl,
+ throttleMs = 1000
+ }
+ };
+ }
+
+ _mbzUrls = list.ToList();
+
+ return list;
+ }
+
/// <summary>
/// Gets the music brainz response.
/// </summary>
@@ -283,9 +398,15 @@ namespace MediaBrowser.Providers.Music
/// <returns>Task{XmlDocument}.</returns>
internal async Task<XmlDocument> GetMusicBrainzResponse(string url, bool isSearch, CancellationToken cancellationToken)
{
- // MusicBrainz is extremely adamant about limiting to one request per second
+ var urlInfo = await GetMbzUrl().ConfigureAwait(false);
- await Task.Delay(1000, cancellationToken).ConfigureAwait(false);
+ if (urlInfo.throttleMs > 0)
+ {
+ // MusicBrainz is extremely adamant about limiting to one request per second
+ await Task.Delay(urlInfo.throttleMs, cancellationToken).ConfigureAwait(false);
+ }
+
+ url = urlInfo.url.TrimEnd('/') + url;
var doc = new XmlDocument();
@@ -317,5 +438,11 @@ namespace MediaBrowser.Providers.Music
{
throw new NotImplementedException();
}
+
+ internal class MbzUrl
+ {
+ public string url { get; set; }
+ public int throttleMs { get; set; }
+ }
}
}
diff --git a/MediaBrowser.Providers/Music/MusicBrainzArtistProvider.cs b/MediaBrowser.Providers/Music/MusicBrainzArtistProvider.cs
index 2eb65f4e5..88635bf06 100644
--- a/MediaBrowser.Providers/Music/MusicBrainzArtistProvider.cs
+++ b/MediaBrowser.Providers/Music/MusicBrainzArtistProvider.cs
@@ -23,7 +23,7 @@ namespace MediaBrowser.Providers.Music
if (!string.IsNullOrWhiteSpace(musicBrainzId))
{
- var url = string.Format("https://www.musicbrainz.org/ws/2/artist/?query=arid:{0}", musicBrainzId);
+ var url = string.Format("/ws/2/artist/?query=arid:{0}", musicBrainzId);
var doc = await MusicBrainzAlbumProvider.Current.GetMusicBrainzResponse(url, false, cancellationToken)
.ConfigureAwait(false);
@@ -35,7 +35,7 @@ namespace MediaBrowser.Providers.Music
// They seem to throw bad request failures on any term with a slash
var nameToSearch = searchInfo.Name.Replace('/', ' ');
- var url = String.Format("https://www.musicbrainz.org/ws/2/artist/?query=artist:\"{0}\"", UrlEncode(nameToSearch));
+ var url = String.Format("/ws/2/artist/?query=artist:\"{0}\"", UrlEncode(nameToSearch));
var doc = await MusicBrainzAlbumProvider.Current.GetMusicBrainzResponse(url, true, cancellationToken).ConfigureAwait(false);
@@ -49,7 +49,7 @@ namespace MediaBrowser.Providers.Music
if (HasDiacritics(searchInfo.Name))
{
// Try again using the search with accent characters url
- url = String.Format("https://www.musicbrainz.org/ws/2/artist/?query=artistaccent:\"{0}\"", UrlEncode(nameToSearch));
+ url = String.Format("/ws/2/artist/?query=artistaccent:\"{0}\"", UrlEncode(nameToSearch));
doc = await MusicBrainzAlbumProvider.Current.GetMusicBrainzResponse(url, true, cancellationToken).ConfigureAwait(false);
@@ -62,9 +62,6 @@ namespace MediaBrowser.Providers.Music
private IEnumerable<RemoteSearchResult> GetResultsFromResponse(XmlDocument doc)
{
- //var ns = new XmlNamespaceManager(doc.NameTable);
- //ns.AddNamespace("mb", "https://musicbrainz.org/ns/mmd-2.0#");
-
var list = new List<RemoteSearchResult>();
var docElem = doc.DocumentElement;
@@ -96,7 +93,7 @@ namespace MediaBrowser.Providers.Music
{
if (string.Equals(child.Name, "name", StringComparison.OrdinalIgnoreCase))
{
- name = node.InnerText;
+ name = child.InnerText;
break;
}
}
diff --git a/MediaBrowser.Providers/Music/MusicVideoMetadataService.cs b/MediaBrowser.Providers/Music/MusicVideoMetadataService.cs
index b309ce906..a50c07721 100644
--- a/MediaBrowser.Providers/Music/MusicVideoMetadataService.cs
+++ b/MediaBrowser.Providers/Music/MusicVideoMetadataService.cs
@@ -13,10 +13,6 @@ namespace MediaBrowser.Providers.Music
{
class MusicVideoMetadataService : MetadataService<MusicVideo, MusicVideoInfo>
{
- public MusicVideoMetadataService(IServerConfigurationManager serverConfigurationManager, ILogger logger, IProviderManager providerManager, IProviderRepository providerRepo, IFileSystem fileSystem, IUserDataManager userDataManager, ILibraryManager libraryManager) : base(serverConfigurationManager, logger, providerManager, providerRepo, fileSystem, userDataManager, libraryManager)
- {
- }
-
protected override void MergeData(MetadataResult<MusicVideo> source, MetadataResult<MusicVideo> target, List<MetadataFields> lockedFields, bool replaceData, bool mergeMetadataSettings)
{
ProviderUtils.MergeBaseItemData(source, target, lockedFields, replaceData, mergeMetadataSettings);
@@ -34,5 +30,9 @@ namespace MediaBrowser.Providers.Music
targetItem.Artists = sourceItem.Artists.ToList();
}
}
+
+ public MusicVideoMetadataService(IServerConfigurationManager serverConfigurationManager, ILogger logger, IProviderManager providerManager, IFileSystem fileSystem, IUserDataManager userDataManager, ILibraryManager libraryManager) : base(serverConfigurationManager, logger, providerManager, fileSystem, userDataManager, libraryManager)
+ {
+ }
}
}
diff --git a/MediaBrowser.Providers/MusicGenres/MusicGenreMetadataService.cs b/MediaBrowser.Providers/MusicGenres/MusicGenreMetadataService.cs
index adffdfca6..d7b96271b 100644
--- a/MediaBrowser.Providers/MusicGenres/MusicGenreMetadataService.cs
+++ b/MediaBrowser.Providers/MusicGenres/MusicGenreMetadataService.cs
@@ -12,13 +12,13 @@ namespace MediaBrowser.Providers.MusicGenres
{
public class MusicGenreMetadataService : MetadataService<MusicGenre, ItemLookupInfo>
{
- public MusicGenreMetadataService(IServerConfigurationManager serverConfigurationManager, ILogger logger, IProviderManager providerManager, IProviderRepository providerRepo, IFileSystem fileSystem, IUserDataManager userDataManager, ILibraryManager libraryManager) : base(serverConfigurationManager, logger, providerManager, providerRepo, fileSystem, userDataManager, libraryManager)
+ protected override void MergeData(MetadataResult<MusicGenre> source, MetadataResult<MusicGenre> target, List<MetadataFields> lockedFields, bool replaceData, bool mergeMetadataSettings)
{
+ ProviderUtils.MergeBaseItemData(source, target, lockedFields, replaceData, mergeMetadataSettings);
}
- protected override void MergeData(MetadataResult<MusicGenre> source, MetadataResult<MusicGenre> target, List<MetadataFields> lockedFields, bool replaceData, bool mergeMetadataSettings)
+ public MusicGenreMetadataService(IServerConfigurationManager serverConfigurationManager, ILogger logger, IProviderManager providerManager, IFileSystem fileSystem, IUserDataManager userDataManager, ILibraryManager libraryManager) : base(serverConfigurationManager, logger, providerManager, fileSystem, userDataManager, libraryManager)
{
- ProviderUtils.MergeBaseItemData(source, target, lockedFields, replaceData, mergeMetadataSettings);
}
}
}
diff --git a/MediaBrowser.Providers/Omdb/OmdbImageProvider.cs b/MediaBrowser.Providers/Omdb/OmdbImageProvider.cs
index a1e038374..563118940 100644
--- a/MediaBrowser.Providers/Omdb/OmdbImageProvider.cs
+++ b/MediaBrowser.Providers/Omdb/OmdbImageProvider.cs
@@ -1,4 +1,6 @@
-using MediaBrowser.Common.Net;
+using CommonIO;
+using MediaBrowser.Common.Net;
+using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.Movies;
using MediaBrowser.Controller.Entities.TV;
@@ -6,7 +8,10 @@ using MediaBrowser.Controller.LiveTv;
using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Providers;
+using MediaBrowser.Model.Serialization;
using System.Collections.Generic;
+using System.IO;
+using System.Text;
using System.Threading;
using System.Threading.Tasks;
@@ -15,10 +20,16 @@ namespace MediaBrowser.Providers.Omdb
public class OmdbImageProvider : IRemoteImageProvider, IHasOrder
{
private readonly IHttpClient _httpClient;
+ private readonly IJsonSerializer _jsonSerializer;
+ private readonly IFileSystem _fileSystem;
+ private readonly IServerConfigurationManager _configurationManager;
- public OmdbImageProvider(IHttpClient httpClient)
+ public OmdbImageProvider(IJsonSerializer jsonSerializer, IHttpClient httpClient, IFileSystem fileSystem, IServerConfigurationManager configurationManager)
{
+ _jsonSerializer = jsonSerializer;
_httpClient = httpClient;
+ _fileSystem = fileSystem;
+ _configurationManager = configurationManager;
}
public IEnumerable<ImageType> GetSupportedImages(IHasImages item)
@@ -29,22 +40,29 @@ namespace MediaBrowser.Providers.Omdb
};
}
- public Task<IEnumerable<RemoteImageInfo>> GetImages(IHasImages item, CancellationToken cancellationToken)
+ public async Task<IEnumerable<RemoteImageInfo>> GetImages(IHasImages item, CancellationToken cancellationToken)
{
var imdbId = item.GetProviderId(MetadataProviders.Imdb);
var list = new List<RemoteImageInfo>();
+ var provider = new OmdbProvider(_jsonSerializer, _httpClient, _fileSystem, _configurationManager);
+
if (!string.IsNullOrWhiteSpace(imdbId))
{
- list.Add(new RemoteImageInfo
+ OmdbProvider.RootObject rootObject = await provider.GetRootObject(imdbId, cancellationToken).ConfigureAwait(false);
+
+ if (!string.IsNullOrEmpty(rootObject.Poster))
{
- ProviderName = Name,
- Url = string.Format("https://img.omdbapi.com/?i={0}&apikey=82e83907", imdbId)
- });
+ list.Add(new RemoteImageInfo
+ {
+ ProviderName = Name,
+ Url = string.Format("https://img.omdbapi.com/?i={0}&apikey=82e83907", imdbId)
+ });
+ }
}
- return Task.FromResult<IEnumerable<RemoteImageInfo>>(list);
+ return list;
}
public Task<HttpResponseInfo> GetImageResponse(string url, CancellationToken cancellationToken)
@@ -65,18 +83,6 @@ namespace MediaBrowser.Providers.Omdb
public bool Supports(IHasImages item)
{
- // We'll hammer Omdb if we enable this
- if (item is Person)
- {
- return false;
- }
-
- // Save the http requests since we know it's not currently supported
- if (item is Season || item is Episode)
- {
- return false;
- }
-
// Supports images for tv movies
var tvProgram = item as LiveTvProgram;
if (tvProgram != null && tvProgram.IsMovie)
@@ -84,7 +90,7 @@ namespace MediaBrowser.Providers.Omdb
return true;
}
- return item is Movie || item is Trailer;
+ return item is Movie || item is Trailer || item is Episode;
}
public int Order
diff --git a/MediaBrowser.Providers/Omdb/OmdbItemProvider.cs b/MediaBrowser.Providers/Omdb/OmdbItemProvider.cs
index a0d60c166..7a803eb6f 100644
--- a/MediaBrowser.Providers/Omdb/OmdbItemProvider.cs
+++ b/MediaBrowser.Providers/Omdb/OmdbItemProvider.cs
@@ -1,4 +1,6 @@
-using MediaBrowser.Common.Net;
+using CommonIO;
+using MediaBrowser.Common.Net;
+using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.Movies;
using MediaBrowser.Controller.Entities.TV;
@@ -26,13 +28,17 @@ namespace MediaBrowser.Providers.Omdb
private readonly IHttpClient _httpClient;
private readonly ILogger _logger;
private readonly ILibraryManager _libraryManager;
+ private readonly IFileSystem _fileSystem;
+ private readonly IServerConfigurationManager _configurationManager;
- public OmdbItemProvider(IJsonSerializer jsonSerializer, IHttpClient httpClient, ILogger logger, ILibraryManager libraryManager)
+ public OmdbItemProvider(IJsonSerializer jsonSerializer, IHttpClient httpClient, ILogger logger, ILibraryManager libraryManager, IFileSystem fileSystem, IServerConfigurationManager configurationManager)
{
_jsonSerializer = jsonSerializer;
_httpClient = httpClient;
_logger = logger;
_libraryManager = libraryManager;
+ _fileSystem = fileSystem;
+ _configurationManager = configurationManager;
}
public Task<IEnumerable<RemoteSearchResult>> GetSearchResults(SeriesInfo searchInfo, CancellationToken cancellationToken)
@@ -220,7 +226,7 @@ namespace MediaBrowser.Providers.Omdb
result.Item.SetProviderId(MetadataProviders.Imdb, imdbId);
result.HasMetadata = true;
- await new OmdbProvider(_jsonSerializer, _httpClient).Fetch(result.Item, imdbId, info.MetadataLanguage, info.MetadataCountryCode, cancellationToken).ConfigureAwait(false);
+ await new OmdbProvider(_jsonSerializer, _httpClient, _fileSystem, _configurationManager).Fetch(result.Item, imdbId, info.MetadataLanguage, info.MetadataCountryCode, cancellationToken).ConfigureAwait(false);
}
return result;
@@ -259,7 +265,7 @@ namespace MediaBrowser.Providers.Omdb
result.Item.SetProviderId(MetadataProviders.Imdb, imdbId);
result.HasMetadata = true;
- await new OmdbProvider(_jsonSerializer, _httpClient).Fetch(result.Item, imdbId, info.MetadataLanguage, info.MetadataCountryCode, cancellationToken).ConfigureAwait(false);
+ await new OmdbProvider(_jsonSerializer, _httpClient, _fileSystem, _configurationManager).Fetch(result.Item, imdbId, info.MetadataLanguage, info.MetadataCountryCode, cancellationToken).ConfigureAwait(false);
}
return result;
diff --git a/MediaBrowser.Providers/Omdb/OmdbProvider.cs b/MediaBrowser.Providers/Omdb/OmdbProvider.cs
index 44e250350..397107c74 100644
--- a/MediaBrowser.Providers/Omdb/OmdbProvider.cs
+++ b/MediaBrowser.Providers/Omdb/OmdbProvider.cs
@@ -1,11 +1,17 @@
-using MediaBrowser.Common.Net;
+using CommonIO;
+using MediaBrowser.Common.Configuration;
+using MediaBrowser.Common.Net;
+using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Serialization;
using System;
+using System.Collections.Generic;
using System.Globalization;
+using System.IO;
using System.Linq;
using System.Net;
+using System.Text;
using System.Threading;
using System.Threading.Tasks;
@@ -15,17 +21,17 @@ namespace MediaBrowser.Providers.Omdb
{
internal static readonly SemaphoreSlim ResourcePool = new SemaphoreSlim(1, 1);
private readonly IJsonSerializer _jsonSerializer;
+ private readonly IFileSystem _fileSystem;
+ private readonly IServerConfigurationManager _configurationManager;
private readonly IHttpClient _httpClient;
private readonly CultureInfo _usCulture = new CultureInfo("en-US");
- public static OmdbProvider Current;
-
- public OmdbProvider(IJsonSerializer jsonSerializer, IHttpClient httpClient)
+ public OmdbProvider(IJsonSerializer jsonSerializer, IHttpClient httpClient, IFileSystem fileSystem, IServerConfigurationManager configurationManager)
{
_jsonSerializer = jsonSerializer;
_httpClient = httpClient;
-
- Current = this;
+ _fileSystem = fileSystem;
+ _configurationManager = configurationManager;
}
public async Task Fetch(BaseItem item, string imdbId, string language, string country, CancellationToken cancellationToken)
@@ -35,19 +41,7 @@ namespace MediaBrowser.Providers.Omdb
throw new ArgumentNullException("imdbId");
}
- var imdbParam = imdbId.StartsWith("tt", StringComparison.OrdinalIgnoreCase) ? imdbId : "tt" + imdbId;
-
- var url = string.Format("https://www.omdbapi.com/?i={0}&tomatoes=true", imdbParam);
-
- using (var stream = await _httpClient.Get(new HttpRequestOptions
- {
- Url = url,
- ResourcePool = ResourcePool,
- CancellationToken = cancellationToken
-
- }).ConfigureAwait(false))
- {
- var result = _jsonSerializer.DeserializeFromStream<RootObject>(stream);
+ var result = await GetRootObject(imdbId, cancellationToken);
// Only take the name and rating if the user's language is set to english, since Omdb has no localization
if (string.Equals(language, "en", StringComparison.OrdinalIgnoreCase))
@@ -84,7 +78,6 @@ namespace MediaBrowser.Providers.Omdb
}
if (!string.IsNullOrEmpty(result.tomatoConsensus)
- && !string.Equals(result.tomatoConsensus, "n/a", StringComparison.OrdinalIgnoreCase)
&& !string.Equals(result.tomatoConsensus, "No consensus yet.", StringComparison.OrdinalIgnoreCase))
{
hasCriticRating.CriticRatingSummary = WebUtility.HtmlDecode(result.tomatoConsensus);
@@ -109,20 +102,278 @@ namespace MediaBrowser.Providers.Omdb
item.CommunityRating = imdbRating;
}
- if (!string.IsNullOrEmpty(result.Website)
- && !string.Equals(result.Website, "n/a", StringComparison.OrdinalIgnoreCase))
+ if (!string.IsNullOrEmpty(result.Website))
{
item.HomePageUrl = result.Website;
}
- if (!string.IsNullOrWhiteSpace(result.imdbID)
- && !string.Equals(result.imdbID, "n/a", StringComparison.OrdinalIgnoreCase))
+ if (!string.IsNullOrWhiteSpace(result.imdbID))
{
item.SetProviderId(MetadataProviders.Imdb, result.imdbID);
}
ParseAdditionalMetadata(item, result);
+ }
+
+ public async Task<bool> FetchEpisodeData(BaseItem item, int episodeNumber, int seasonNumber, string imdbId, string language, string country, CancellationToken cancellationToken)
+ {
+ if (string.IsNullOrWhiteSpace(imdbId))
+ {
+ throw new ArgumentNullException("imdbId");
+ }
+
+ var seasonResult = await GetSeasonRootObject(imdbId, seasonNumber, cancellationToken);
+
+ RootObject result = null;
+
+ foreach (var episode in seasonResult.Episodes)
+ {
+ if (episode.Episode == episodeNumber)
+ {
+ result = episode;
+ break;
+ }
+ }
+
+ if (result == null)
+ {
+ return false;
+ }
+
+
+ // Only take the name and rating if the user's language is set to english, since Omdb has no localization
+ if (string.Equals(language, "en", StringComparison.OrdinalIgnoreCase))
+ {
+ item.Name = result.Title;
+
+ if (string.Equals(country, "us", StringComparison.OrdinalIgnoreCase))
+ {
+ item.OfficialRating = result.Rated;
+ }
+ }
+
+ int year;
+
+ if (!string.IsNullOrEmpty(result.Year)
+ && int.TryParse(result.Year, NumberStyles.Number, _usCulture, out year)
+ && year >= 0)
+ {
+ item.ProductionYear = year;
+ }
+
+ var hasCriticRating = item as IHasCriticRating;
+ if (hasCriticRating != null)
+ {
+ // Seeing some bogus RT data on omdb for series, so filter it out here
+ // RT doesn't even have tv series
+ int tomatoMeter;
+
+ if (!string.IsNullOrEmpty(result.tomatoMeter)
+ && int.TryParse(result.tomatoMeter, NumberStyles.Integer, _usCulture, out tomatoMeter)
+ && tomatoMeter >= 0)
+ {
+ hasCriticRating.CriticRating = tomatoMeter;
+ }
+
+ if (!string.IsNullOrEmpty(result.tomatoConsensus)
+ && !string.Equals(result.tomatoConsensus, "No consensus yet.", StringComparison.OrdinalIgnoreCase))
+ {
+ hasCriticRating.CriticRatingSummary = WebUtility.HtmlDecode(result.tomatoConsensus);
+ }
+ }
+
+ int voteCount;
+
+ if (!string.IsNullOrEmpty(result.imdbVotes)
+ && int.TryParse(result.imdbVotes, NumberStyles.Number, _usCulture, out voteCount)
+ && voteCount >= 0)
+ {
+ item.VoteCount = voteCount;
+ }
+
+ float imdbRating;
+
+ if (!string.IsNullOrEmpty(result.imdbRating)
+ && float.TryParse(result.imdbRating, NumberStyles.Any, _usCulture, out imdbRating)
+ && imdbRating >= 0)
+ {
+ item.CommunityRating = imdbRating;
+ }
+
+ if (!string.IsNullOrEmpty(result.Website))
+ {
+ item.HomePageUrl = result.Website;
+ }
+
+ if (!string.IsNullOrWhiteSpace(result.imdbID))
+ {
+ item.SetProviderId(MetadataProviders.Imdb, result.imdbID);
+ }
+
+ ParseAdditionalMetadata(item, result);
+
+ return true;
+ }
+
+ internal async Task<RootObject> GetRootObject(string imdbId, CancellationToken cancellationToken)
+ {
+ var path = await EnsureItemInfo(imdbId, cancellationToken);
+
+ string resultString;
+
+ using (Stream stream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read, 131072))
+ {
+ using (var reader = new StreamReader(stream, new UTF8Encoding(false)))
+ {
+ resultString = reader.ReadToEnd();
+ resultString = resultString.Replace("\"N/A\"", "\"\"");
+ }
+ }
+
+ var result = _jsonSerializer.DeserializeFromString<RootObject>(resultString);
+ return result;
+ }
+
+ internal async Task<SeasonRootObject> GetSeasonRootObject(string imdbId, int seasonId, CancellationToken cancellationToken)
+ {
+ var path = await EnsureSeasonInfo(imdbId, seasonId, cancellationToken);
+
+ string resultString;
+
+ using (Stream stream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read, 131072))
+ {
+ using (var reader = new StreamReader(stream, new UTF8Encoding(false)))
+ {
+ resultString = reader.ReadToEnd();
+ resultString = resultString.Replace("\"N/A\"", "\"\"");
+ }
+ }
+
+ var result = _jsonSerializer.DeserializeFromString<SeasonRootObject>(resultString);
+ return result;
+ }
+
+ internal static bool IsValidSeries(Dictionary<string, string> seriesProviderIds)
+ {
+ string id;
+ if (seriesProviderIds.TryGetValue(MetadataProviders.Imdb.ToString(), out id) && !string.IsNullOrEmpty(id))
+ {
+ // This check should ideally never be necessary but we're seeing some cases of this and haven't tracked them down yet.
+ if (!string.IsNullOrWhiteSpace(id))
+ {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ private async Task<string> EnsureItemInfo(string imdbId, CancellationToken cancellationToken)
+ {
+ if (string.IsNullOrWhiteSpace(imdbId))
+ {
+ throw new ArgumentNullException("imdbId");
+ }
+
+ var imdbParam = imdbId.StartsWith("tt", StringComparison.OrdinalIgnoreCase) ? imdbId : "tt" + imdbId;
+
+ var path = GetDataFilePath(imdbParam);
+
+ var fileInfo = _fileSystem.GetFileSystemInfo(path);
+
+ if (fileInfo.Exists)
+ {
+ // If it's recent or automatic updates are enabled, don't re-download
+ if ((DateTime.UtcNow - _fileSystem.GetLastWriteTimeUtc(fileInfo)).TotalDays <= 3)
+ {
+ return path;
+ }
+ }
+
+ var url = string.Format("https://www.omdbapi.com/?i={0}&tomatoes=true", imdbParam);
+
+ using (var stream = await _httpClient.Get(new HttpRequestOptions
+ {
+ Url = url,
+ ResourcePool = ResourcePool,
+ CancellationToken = cancellationToken
+
+ }).ConfigureAwait(false))
+ {
+ var rootObject = _jsonSerializer.DeserializeFromStream<RootObject>(stream);
+ _fileSystem.CreateDirectory(Path.GetDirectoryName(path));
+ _jsonSerializer.SerializeToFile(rootObject, path);
+ }
+
+ return path;
+ }
+
+ private async Task<string> EnsureSeasonInfo(string seriesImdbId, int seasonId, CancellationToken cancellationToken)
+ {
+ if (string.IsNullOrWhiteSpace(seriesImdbId))
+ {
+ throw new ArgumentNullException("imdbId");
}
+
+ var imdbParam = seriesImdbId.StartsWith("tt", StringComparison.OrdinalIgnoreCase) ? seriesImdbId : "tt" + seriesImdbId;
+
+ var path = GetSeasonFilePath(imdbParam, seasonId);
+
+ var fileInfo = _fileSystem.GetFileSystemInfo(path);
+
+ if (fileInfo.Exists)
+ {
+ // If it's recent or automatic updates are enabled, don't re-download
+ if ((DateTime.UtcNow - _fileSystem.GetLastWriteTimeUtc(fileInfo)).TotalDays <= 3)
+ {
+ return path;
+ }
+ }
+
+ var url = string.Format("https://www.omdbapi.com/?i={0}&season={1}&detail=full", imdbParam, seasonId);
+
+ using (var stream = await _httpClient.Get(new HttpRequestOptions
+ {
+ Url = url,
+ ResourcePool = ResourcePool,
+ CancellationToken = cancellationToken
+
+ }).ConfigureAwait(false))
+ {
+ var rootObject = _jsonSerializer.DeserializeFromStream<SeasonRootObject>(stream);
+ _fileSystem.CreateDirectory(Path.GetDirectoryName(path));
+ _jsonSerializer.SerializeToFile(rootObject, path);
+ }
+
+ return path;
+ }
+
+ internal string GetDataFilePath(string imdbId)
+ {
+ if (string.IsNullOrEmpty(imdbId))
+ {
+ throw new ArgumentNullException("imdbId");
+ }
+
+ var dataPath = Path.Combine(_configurationManager.ApplicationPaths.CachePath, "omdb");
+
+ var filename = string.Format("{0}.json", imdbId);
+
+ return Path.Combine(dataPath, filename);
+ }
+
+ internal string GetSeasonFilePath(string imdbId, int seasonId)
+ {
+ if (string.IsNullOrEmpty(imdbId))
+ {
+ throw new ArgumentNullException("imdbId");
+ }
+
+ var dataPath = Path.Combine(_configurationManager.ApplicationPaths.CachePath, "omdb");
+
+ var filename = string.Format("{0}_season_{1}.json", imdbId, seasonId);
+
+ return Path.Combine(dataPath, filename);
}
private void ParseAdditionalMetadata(BaseItem item, RootObject result)
@@ -130,8 +381,7 @@ namespace MediaBrowser.Providers.Omdb
// Grab series genres because imdb data is better than tvdb. Leave movies alone
// But only do it if english is the preferred language because this data will not be localized
if (ShouldFetchGenres(item) &&
- !string.IsNullOrWhiteSpace(result.Genre) &&
- !string.Equals(result.Genre, "n/a", StringComparison.OrdinalIgnoreCase))
+ !string.IsNullOrWhiteSpace(result.Genre))
{
item.Genres.Clear();
@@ -156,8 +406,7 @@ namespace MediaBrowser.Providers.Omdb
}
var hasAwards = item as IHasAwards;
- if (hasAwards != null && !string.IsNullOrEmpty(result.Awards) &&
- !string.Equals(result.Awards, "n/a", StringComparison.OrdinalIgnoreCase))
+ if (hasAwards != null && !string.IsNullOrEmpty(result.Awards))
{
hasAwards.AwardSummary = WebUtility.HtmlDecode(result.Awards);
}
@@ -178,12 +427,23 @@ namespace MediaBrowser.Providers.Omdb
return string.Equals(lang, "en", StringComparison.OrdinalIgnoreCase);
}
- private class RootObject
+ internal class SeasonRootObject
+ {
+ public string Title { get; set; }
+ public string seriesID { get; set; }
+ public int Season { get; set; }
+ public int? totalSeasons { get; set; }
+ public RootObject[] Episodes { get; set; }
+ public string Response { get; set; }
+ }
+
+ internal class RootObject
{
public string Title { get; set; }
public string Year { get; set; }
public string Rated { get; set; }
public string Released { get; set; }
+ public int Episode { get; set; }
public string Runtime { get; set; }
public string Genre { get; set; }
public string Director { get; set; }
diff --git a/MediaBrowser.Providers/People/PersonMetadataService.cs b/MediaBrowser.Providers/People/PersonMetadataService.cs
index 13a370bc5..0be5773db 100644
--- a/MediaBrowser.Providers/People/PersonMetadataService.cs
+++ b/MediaBrowser.Providers/People/PersonMetadataService.cs
@@ -12,10 +12,6 @@ namespace MediaBrowser.Providers.People
{
public class PersonMetadataService : MetadataService<Person, PersonLookupInfo>
{
- public PersonMetadataService(IServerConfigurationManager serverConfigurationManager, ILogger logger, IProviderManager providerManager, IProviderRepository providerRepo, IFileSystem fileSystem, IUserDataManager userDataManager, ILibraryManager libraryManager) : base(serverConfigurationManager, logger, providerManager, providerRepo, fileSystem, userDataManager, libraryManager)
- {
- }
-
protected override void MergeData(MetadataResult<Person> source, MetadataResult<Person> target, List<MetadataFields> lockedFields, bool replaceData, bool mergeMetadataSettings)
{
ProviderUtils.MergeBaseItemData(source, target, lockedFields, replaceData, mergeMetadataSettings);
@@ -28,5 +24,9 @@ namespace MediaBrowser.Providers.People
targetItem.PlaceOfBirth = sourceItem.PlaceOfBirth;
}
}
+
+ public PersonMetadataService(IServerConfigurationManager serverConfigurationManager, ILogger logger, IProviderManager providerManager, IFileSystem fileSystem, IUserDataManager userDataManager, ILibraryManager libraryManager) : base(serverConfigurationManager, logger, providerManager, fileSystem, userDataManager, libraryManager)
+ {
+ }
}
}
diff --git a/MediaBrowser.Providers/Photos/PhotoAlbumMetadataService.cs b/MediaBrowser.Providers/Photos/PhotoAlbumMetadataService.cs
index 262480885..9bfff6b84 100644
--- a/MediaBrowser.Providers/Photos/PhotoAlbumMetadataService.cs
+++ b/MediaBrowser.Providers/Photos/PhotoAlbumMetadataService.cs
@@ -12,13 +12,13 @@ namespace MediaBrowser.Providers.Photos
{
class PhotoAlbumMetadataService : MetadataService<PhotoAlbum, ItemLookupInfo>
{
- public PhotoAlbumMetadataService(IServerConfigurationManager serverConfigurationManager, ILogger logger, IProviderManager providerManager, IProviderRepository providerRepo, IFileSystem fileSystem, IUserDataManager userDataManager, ILibraryManager libraryManager) : base(serverConfigurationManager, logger, providerManager, providerRepo, fileSystem, userDataManager, libraryManager)
+ protected override void MergeData(MetadataResult<PhotoAlbum> source, MetadataResult<PhotoAlbum> target, List<MetadataFields> lockedFields, bool replaceData, bool mergeMetadataSettings)
{
+ ProviderUtils.MergeBaseItemData(source, target, lockedFields, replaceData, mergeMetadataSettings);
}
- protected override void MergeData(MetadataResult<PhotoAlbum> source, MetadataResult<PhotoAlbum> target, List<MetadataFields> lockedFields, bool replaceData, bool mergeMetadataSettings)
+ public PhotoAlbumMetadataService(IServerConfigurationManager serverConfigurationManager, ILogger logger, IProviderManager providerManager, IFileSystem fileSystem, IUserDataManager userDataManager, ILibraryManager libraryManager) : base(serverConfigurationManager, logger, providerManager, fileSystem, userDataManager, libraryManager)
{
- ProviderUtils.MergeBaseItemData(source, target, lockedFields, replaceData, mergeMetadataSettings);
}
}
}
diff --git a/MediaBrowser.Providers/Photos/PhotoMetadataService.cs b/MediaBrowser.Providers/Photos/PhotoMetadataService.cs
index 0836c5ede..fb6ff6f09 100644
--- a/MediaBrowser.Providers/Photos/PhotoMetadataService.cs
+++ b/MediaBrowser.Providers/Photos/PhotoMetadataService.cs
@@ -12,13 +12,13 @@ namespace MediaBrowser.Providers.Photos
{
class PhotoMetadataService : MetadataService<Photo, ItemLookupInfo>
{
- public PhotoMetadataService(IServerConfigurationManager serverConfigurationManager, ILogger logger, IProviderManager providerManager, IProviderRepository providerRepo, IFileSystem fileSystem, IUserDataManager userDataManager, ILibraryManager libraryManager) : base(serverConfigurationManager, logger, providerManager, providerRepo, fileSystem, userDataManager, libraryManager)
+ protected override void MergeData(MetadataResult<Photo> source, MetadataResult<Photo> target, List<MetadataFields> lockedFields, bool replaceData, bool mergeMetadataSettings)
{
+ ProviderUtils.MergeBaseItemData(source, target, lockedFields, replaceData, mergeMetadataSettings);
}
- protected override void MergeData(MetadataResult<Photo> source, MetadataResult<Photo> target, List<MetadataFields> lockedFields, bool replaceData, bool mergeMetadataSettings)
+ public PhotoMetadataService(IServerConfigurationManager serverConfigurationManager, ILogger logger, IProviderManager providerManager, IFileSystem fileSystem, IUserDataManager userDataManager, ILibraryManager libraryManager) : base(serverConfigurationManager, logger, providerManager, fileSystem, userDataManager, libraryManager)
{
- ProviderUtils.MergeBaseItemData(source, target, lockedFields, replaceData, mergeMetadataSettings);
}
}
}
diff --git a/MediaBrowser.Providers/Playlists/PlaylistMetadataService.cs b/MediaBrowser.Providers/Playlists/PlaylistMetadataService.cs
index 89ca8da25..d1d5ba7de 100644
--- a/MediaBrowser.Providers/Playlists/PlaylistMetadataService.cs
+++ b/MediaBrowser.Providers/Playlists/PlaylistMetadataService.cs
@@ -12,10 +12,6 @@ namespace MediaBrowser.Providers.Playlists
{
class PlaylistMetadataService : MetadataService<Playlist, ItemLookupInfo>
{
- public PlaylistMetadataService(IServerConfigurationManager serverConfigurationManager, ILogger logger, IProviderManager providerManager, IProviderRepository providerRepo, IFileSystem fileSystem, IUserDataManager userDataManager, ILibraryManager libraryManager) : base(serverConfigurationManager, logger, providerManager, providerRepo, fileSystem, userDataManager, libraryManager)
- {
- }
-
protected override void MergeData(MetadataResult<Playlist> source, MetadataResult<Playlist> target, List<MetadataFields> lockedFields, bool replaceData, bool mergeMetadataSettings)
{
ProviderUtils.MergeBaseItemData(source, target, lockedFields, replaceData, mergeMetadataSettings);
@@ -34,5 +30,9 @@ namespace MediaBrowser.Providers.Playlists
targetItem.Shares = sourceItem.Shares;
}
}
+
+ public PlaylistMetadataService(IServerConfigurationManager serverConfigurationManager, ILogger logger, IProviderManager providerManager, IFileSystem fileSystem, IUserDataManager userDataManager, ILibraryManager libraryManager) : base(serverConfigurationManager, logger, providerManager, fileSystem, userDataManager, libraryManager)
+ {
+ }
}
}
diff --git a/MediaBrowser.Providers/Studios/StudioMetadataService.cs b/MediaBrowser.Providers/Studios/StudioMetadataService.cs
index eef1e8e07..9ee594a66 100644
--- a/MediaBrowser.Providers/Studios/StudioMetadataService.cs
+++ b/MediaBrowser.Providers/Studios/StudioMetadataService.cs
@@ -12,13 +12,13 @@ namespace MediaBrowser.Providers.Studios
{
public class StudioMetadataService : MetadataService<Studio, ItemLookupInfo>
{
- public StudioMetadataService(IServerConfigurationManager serverConfigurationManager, ILogger logger, IProviderManager providerManager, IProviderRepository providerRepo, IFileSystem fileSystem, IUserDataManager userDataManager, ILibraryManager libraryManager) : base(serverConfigurationManager, logger, providerManager, providerRepo, fileSystem, userDataManager, libraryManager)
+ protected override void MergeData(MetadataResult<Studio> source, MetadataResult<Studio> target, List<MetadataFields> lockedFields, bool replaceData, bool mergeMetadataSettings)
{
+ ProviderUtils.MergeBaseItemData(source, target, lockedFields, replaceData, mergeMetadataSettings);
}
- protected override void MergeData(MetadataResult<Studio> source, MetadataResult<Studio> target, List<MetadataFields> lockedFields, bool replaceData, bool mergeMetadataSettings)
+ public StudioMetadataService(IServerConfigurationManager serverConfigurationManager, ILogger logger, IProviderManager providerManager, IFileSystem fileSystem, IUserDataManager userDataManager, ILibraryManager libraryManager) : base(serverConfigurationManager, logger, providerManager, fileSystem, userDataManager, libraryManager)
{
- ProviderUtils.MergeBaseItemData(source, target, lockedFields, replaceData, mergeMetadataSettings);
}
}
}
diff --git a/MediaBrowser.Providers/TV/EpisodeMetadataService.cs b/MediaBrowser.Providers/TV/EpisodeMetadataService.cs
index c848fcd0e..a15de4866 100644
--- a/MediaBrowser.Providers/TV/EpisodeMetadataService.cs
+++ b/MediaBrowser.Providers/TV/EpisodeMetadataService.cs
@@ -12,10 +12,6 @@ namespace MediaBrowser.Providers.TV
{
public class EpisodeMetadataService : MetadataService<Episode, EpisodeInfo>
{
- public EpisodeMetadataService(IServerConfigurationManager serverConfigurationManager, ILogger logger, IProviderManager providerManager, IProviderRepository providerRepo, IFileSystem fileSystem, IUserDataManager userDataManager, ILibraryManager libraryManager) : base(serverConfigurationManager, logger, providerManager, providerRepo, fileSystem, userDataManager, libraryManager)
- {
- }
-
protected override void MergeData(MetadataResult<Episode> source, MetadataResult<Episode> target, List<MetadataFields> lockedFields, bool replaceData, bool mergeMetadataSettings)
{
ProviderUtils.MergeBaseItemData(source, target, lockedFields, replaceData, mergeMetadataSettings);
@@ -58,5 +54,9 @@ namespace MediaBrowser.Providers.TV
targetItem.IndexNumberEnd = sourceItem.IndexNumberEnd;
}
}
+
+ public EpisodeMetadataService(IServerConfigurationManager serverConfigurationManager, ILogger logger, IProviderManager providerManager, IFileSystem fileSystem, IUserDataManager userDataManager, ILibraryManager libraryManager) : base(serverConfigurationManager, logger, providerManager, fileSystem, userDataManager, libraryManager)
+ {
+ }
}
}
diff --git a/MediaBrowser.Providers/TV/FanArt/FanArtSeasonProvider.cs b/MediaBrowser.Providers/TV/FanArt/FanArtSeasonProvider.cs
index 673663d7f..e6a47d9d2 100644
--- a/MediaBrowser.Providers/TV/FanArt/FanArtSeasonProvider.cs
+++ b/MediaBrowser.Providers/TV/FanArt/FanArtSeasonProvider.cs
@@ -15,13 +15,14 @@ using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
+using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
using CommonIO;
namespace MediaBrowser.Providers.TV
{
- public class FanArtSeasonProvider : IRemoteImageProvider, IHasOrder, IHasItemChangeMonitor
+ public class FanArtSeasonProvider : IRemoteImageProvider, IHasOrder
{
private readonly CultureInfo _usCulture = new CultureInfo("en-US");
private readonly IServerConfigurationManager _config;
@@ -160,6 +161,7 @@ namespace MediaBrowser.Providers.TV
PopulateImages(list, obj.showbackground, ImageType.Backdrop, 1920, 1080, seasonNumber);
}
+ private Regex _regex_http = new Regex("^http://");
private void PopulateImages(List<RemoteImageInfo> list,
List<FanartSeriesProvider.Image> images,
ImageType type,
@@ -194,7 +196,7 @@ namespace MediaBrowser.Providers.TV
Width = width,
Height = height,
ProviderName = Name,
- Url = url,
+ Url = _regex_http.Replace(url, "https://", 1),
Language = i.lang
};
@@ -224,36 +226,5 @@ namespace MediaBrowser.Providers.TV
ResourcePool = FanartArtistProvider.Current.FanArtResourcePool
});
}
-
- public bool HasChanged(IHasMetadata item, IDirectoryService directoryService)
- {
- var options = FanartSeriesProvider.Current.GetFanartOptions();
- if (!options.EnableAutomaticUpdates)
- {
- return false;
- }
-
- var season = (Season)item;
- var series = season.Series;
-
- if (series == null)
- {
- return false;
- }
-
- var tvdbId = series.GetProviderId(MetadataProviders.Tvdb);
-
- if (!String.IsNullOrEmpty(tvdbId))
- {
- // Process images
- var imagesFilePath = FanartSeriesProvider.Current.GetFanartJsonPath(tvdbId);
-
- var fileInfo = _fileSystem.GetFileInfo(imagesFilePath);
-
- return !fileInfo.Exists || _fileSystem.GetLastWriteTimeUtc(fileInfo) > item.DateLastRefreshed;
- }
-
- return false;
- }
}
}
diff --git a/MediaBrowser.Providers/TV/FanArt/FanArtTvUpdatesPostScanTask.cs b/MediaBrowser.Providers/TV/FanArt/FanArtTvUpdatesPostScanTask.cs
deleted file mode 100644
index 37e76531b..000000000
--- a/MediaBrowser.Providers/TV/FanArt/FanArtTvUpdatesPostScanTask.cs
+++ /dev/null
@@ -1,192 +0,0 @@
-using MediaBrowser.Common.Net;
-using MediaBrowser.Controller.Configuration;
-using MediaBrowser.Controller.Library;
-using MediaBrowser.Model.Configuration;
-using MediaBrowser.Model.Logging;
-using MediaBrowser.Model.Serialization;
-using MediaBrowser.Providers.Music;
-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;
-using CommonIO;
-
-namespace MediaBrowser.Providers.TV
-{
- class FanArtTvUpdatesPostScanTask : ILibraryPostScanTask
- {
- private const string UpdatesUrl = "https://webservice.fanart.tv/v3/tv/latest?api_key={0}&date={1}";
-
- /// <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 _jsonSerializer;
- private readonly IFileSystem _fileSystem;
-
- private static readonly CultureInfo UsCulture = new CultureInfo("en-US");
-
- public FanArtTvUpdatesPostScanTask(IJsonSerializer jsonSerializer, IServerConfigurationManager config, ILogger logger, IHttpClient httpClient, IFileSystem fileSystem)
- {
- _jsonSerializer = jsonSerializer;
- _config = config;
- _logger = logger;
- _httpClient = httpClient;
- _fileSystem = fileSystem;
- }
-
- /// <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)
- {
- var options = FanartSeriesProvider.Current.GetFanartOptions();
-
- if (!options.EnableAutomaticUpdates)
- {
- progress.Report(100);
- return;
- }
-
- var path = FanartSeriesProvider.GetSeriesDataPath(_config.CommonApplicationPaths);
-
- _fileSystem.CreateDirectory(path);
-
- var timestampFile = Path.Combine(path, "time.txt");
-
- var timestampFileInfo = _fileSystem.GetFileInfo(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 for updates
- var lastUpdateTime = timestampFileInfo.Exists ? _fileSystem.ReadAllText(timestampFile, Encoding.UTF8) : string.Empty;
-
- var existingDirectories = Directory.EnumerateDirectories(path).Select(Path.GetFileName).ToList();
-
- // If this is our first time, don't do any updates and just record the timestamp
- if (!string.IsNullOrEmpty(lastUpdateTime))
- {
- var seriesToUpdate = await GetSeriesIdsToUpdate(existingDirectories, lastUpdateTime, options, cancellationToken).ConfigureAwait(false);
-
- progress.Report(5);
-
- await UpdateSeries(seriesToUpdate, progress, cancellationToken).ConfigureAwait(false);
- }
-
- var newUpdateTime = Convert.ToInt64(DateTimeToUnixTimestamp(DateTime.UtcNow)).ToString(UsCulture);
-
- _fileSystem.WriteAllText(timestampFile, newUpdateTime, Encoding.UTF8);
-
- progress.Report(100);
- }
-
- /// <summary>
- /// Gets the series ids to update.
- /// </summary>
- /// <param name="existingSeriesIds">The existing series ids.</param>
- /// <param name="lastUpdateTime">The last update time.</param>
- /// <param name="cancellationToken">The cancellation token.</param>
- /// <returns>Task{IEnumerable{System.String}}.</returns>
- private async Task<IEnumerable<string>> GetSeriesIdsToUpdate(IEnumerable<string> existingSeriesIds, string lastUpdateTime, FanartOptions options, CancellationToken cancellationToken)
- {
- var url = string.Format(UpdatesUrl, FanartArtistProvider.ApiKey, lastUpdateTime);
-
- if (!string.IsNullOrWhiteSpace(options.UserApiKey))
- {
- url += "&client_key=" + options.UserApiKey;
- }
-
- // First get last time
- using (var stream = await _httpClient.Get(new HttpRequestOptions
- {
- Url = url,
- CancellationToken = cancellationToken,
- EnableHttpCompression = true,
- ResourcePool = FanartArtistProvider.Current.FanArtResourcePool
-
- }).ConfigureAwait(false))
- {
- // If empty fanart will return a string of "null", rather than an empty list
- using (var reader = new StreamReader(stream))
- {
- var json = await reader.ReadToEndAsync().ConfigureAwait(false);
-
- if (string.Equals(json, "null", StringComparison.OrdinalIgnoreCase) || string.IsNullOrWhiteSpace(json))
- {
- return new List<string>();
- }
-
- var existingDictionary = existingSeriesIds.ToDictionary(i => i, StringComparer.OrdinalIgnoreCase);
-
- var updates = _jsonSerializer.DeserializeFromString<List<FanartUpdatesPostScanTask.FanArtUpdate>>(json);
-
- return updates.Select(i => i.id).Where(existingDictionary.ContainsKey);
- }
- }
- }
-
- /// <summary>
- /// Updates the series.
- /// </summary>
- /// <param name="idList">The id list.</param>
- /// <param name="progress">The progress.</param>
- /// <param name="cancellationToken">The cancellation token.</param>
- /// <returns>Task.</returns>
- private async Task UpdateSeries(IEnumerable<string> idList, IProgress<double> progress, CancellationToken cancellationToken)
- {
- var list = idList.ToList();
- var numComplete = 0;
-
- foreach (var id in list)
- {
- _logger.Info("Updating series " + id);
-
- await FanartSeriesProvider.Current.DownloadSeriesJson(id, cancellationToken).ConfigureAwait(false);
-
- numComplete++;
- double percent = numComplete;
- percent /= list.Count;
- percent *= 95;
-
- progress.Report(percent + 5);
- }
- }
-
- /// <summary>
- /// Dates the time to unix timestamp.
- /// </summary>
- /// <param name="dateTime">The date time.</param>
- /// <returns>System.Double.</returns>
- private static double DateTimeToUnixTimestamp(DateTime dateTime)
- {
- return (dateTime - new DateTime(1970, 1, 1).ToUniversalTime()).TotalSeconds;
- }
-
- public class FanArtUpdate
- {
- public string id { get; set; }
- public string name { get; set; }
- public string new_images { get; set; }
- public string total_images { get; set; }
- }
- }
-}
diff --git a/MediaBrowser.Providers/TV/FanArt/FanartSeriesProvider.cs b/MediaBrowser.Providers/TV/FanArt/FanartSeriesProvider.cs
index 3c831dbbc..827a3c50f 100644
--- a/MediaBrowser.Providers/TV/FanArt/FanartSeriesProvider.cs
+++ b/MediaBrowser.Providers/TV/FanArt/FanartSeriesProvider.cs
@@ -17,13 +17,14 @@ using System.Globalization;
using System.IO;
using System.Linq;
using System.Net;
+using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
using CommonIO;
namespace MediaBrowser.Providers.TV
{
- public class FanartSeriesProvider : IRemoteImageProvider, IHasOrder, IHasItemChangeMonitor
+ public class FanartSeriesProvider : IRemoteImageProvider, IHasOrder
{
private readonly CultureInfo _usCulture = new CultureInfo("en-US");
private readonly IServerConfigurationManager _config;
@@ -162,6 +163,7 @@ namespace MediaBrowser.Providers.TV
PopulateImages(list, obj.tvposter, ImageType.Primary, 1000, 1426);
}
+ private Regex _regex_http = new Regex("^http://");
private void PopulateImages(List<RemoteImageInfo> list,
List<Image> images,
ImageType type,
@@ -194,7 +196,7 @@ namespace MediaBrowser.Providers.TV
Width = width,
Height = height,
ProviderName = Name,
- Url = url,
+ Url = _regex_http.Replace(url, "https://", 1),
Language = i.lang
};
@@ -341,29 +343,6 @@ namespace MediaBrowser.Providers.TV
}
}
- public bool HasChanged(IHasMetadata item, IDirectoryService directoryService)
- {
- var options = GetFanartOptions();
- if (!options.EnableAutomaticUpdates)
- {
- return false;
- }
-
- var tvdbId = item.GetProviderId(MetadataProviders.Tvdb);
-
- if (!String.IsNullOrEmpty(tvdbId))
- {
- // Process images
- var imagesFilePath = GetFanartJsonPath(tvdbId);
-
- var fileInfo = _fileSystem.GetFileInfo(imagesFilePath);
-
- return !fileInfo.Exists || _fileSystem.GetLastWriteTimeUtc(fileInfo) > item.DateLastRefreshed;
- }
-
- return false;
- }
-
public class Image
{
public string id { get; set; }
diff --git a/MediaBrowser.Providers/TV/MissingEpisodeProvider.cs b/MediaBrowser.Providers/TV/MissingEpisodeProvider.cs
index 2a3150c78..4e2d9a8d2 100644
--- a/MediaBrowser.Providers/TV/MissingEpisodeProvider.cs
+++ b/MediaBrowser.Providers/TV/MissingEpisodeProvider.cs
@@ -428,7 +428,8 @@ namespace MediaBrowser.Providers.TV
Name = name,
IndexNumber = episodeNumber,
ParentIndexNumber = seasonNumber,
- Id = _libraryManager.GetNewItemId((series.Id + seasonNumber.ToString(_usCulture) + name), typeof(Episode))
+ Id = _libraryManager.GetNewItemId((series.Id + seasonNumber.ToString(_usCulture) + name), typeof(Episode)),
+ IsVirtualItem = true
};
episode.SetParent(season);
diff --git a/MediaBrowser.Providers/TV/Omdb/OmdbEpisodeProvider.cs b/MediaBrowser.Providers/TV/Omdb/OmdbEpisodeProvider.cs
index 5da1fcf27..21668f014 100644
--- a/MediaBrowser.Providers/TV/Omdb/OmdbEpisodeProvider.cs
+++ b/MediaBrowser.Providers/TV/Omdb/OmdbEpisodeProvider.cs
@@ -1,4 +1,6 @@
-using MediaBrowser.Common.Net;
+using CommonIO;
+using MediaBrowser.Common.Net;
+using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Entities.TV;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Providers;
@@ -21,12 +23,16 @@ namespace MediaBrowser.Providers.TV
private readonly IJsonSerializer _jsonSerializer;
private readonly IHttpClient _httpClient;
private readonly OmdbItemProvider _itemProvider;
+ private readonly IFileSystem _fileSystem;
+ private readonly IServerConfigurationManager _configurationManager;
- public OmdbEpisodeProvider(IJsonSerializer jsonSerializer, IHttpClient httpClient, ILogger logger, ILibraryManager libraryManager)
+ public OmdbEpisodeProvider(IJsonSerializer jsonSerializer, IHttpClient httpClient, ILogger logger, ILibraryManager libraryManager, IFileSystem fileSystem, IServerConfigurationManager configurationManager)
{
_jsonSerializer = jsonSerializer;
_httpClient = httpClient;
- _itemProvider = new OmdbItemProvider(jsonSerializer, httpClient, logger, libraryManager);
+ _fileSystem = fileSystem;
+ _configurationManager = configurationManager;
+ _itemProvider = new OmdbItemProvider(jsonSerializer, httpClient, logger, libraryManager, fileSystem, configurationManager);
}
public Task<IEnumerable<RemoteSearchResult>> GetSearchResults(EpisodeInfo searchInfo, CancellationToken cancellationToken)
@@ -36,7 +42,7 @@ namespace MediaBrowser.Providers.TV
public async Task<MetadataResult<Episode>> GetMetadata(EpisodeInfo info, CancellationToken cancellationToken)
{
- var result = new MetadataResult<Episode>
+ var result = new MetadataResult<Episode>()
{
Item = new Episode()
};
@@ -47,30 +53,16 @@ namespace MediaBrowser.Providers.TV
return result;
}
- var imdbId = info.GetProviderId(MetadataProviders.Imdb);
- if (string.IsNullOrWhiteSpace(imdbId))
+ if (OmdbProvider.IsValidSeries(info.SeriesProviderIds) && info.IndexNumber.HasValue && info.ParentIndexNumber.HasValue)
{
- imdbId = await GetEpisodeImdbId(info, cancellationToken).ConfigureAwait(false);
- }
-
- if (!string.IsNullOrEmpty(imdbId))
- {
- result.Item.SetProviderId(MetadataProviders.Imdb, imdbId);
- result.HasMetadata = true;
+ var seriesImdbId = info.SeriesProviderIds[MetadataProviders.Imdb.ToString()];
- await new OmdbProvider(_jsonSerializer, _httpClient).Fetch(result.Item, imdbId, info.MetadataLanguage, info.MetadataCountryCode, cancellationToken).ConfigureAwait(false);
+ result.HasMetadata = await new OmdbProvider(_jsonSerializer, _httpClient, _fileSystem, _configurationManager).FetchEpisodeData(result.Item, info.IndexNumber.Value, info.ParentIndexNumber.Value, seriesImdbId, info.MetadataLanguage, info.MetadataCountryCode, cancellationToken).ConfigureAwait(false);
}
return result;
}
- private async Task<string> GetEpisodeImdbId(EpisodeInfo info, CancellationToken cancellationToken)
- {
- var results = await GetSearchResults(info, cancellationToken).ConfigureAwait(false);
- var first = results.FirstOrDefault();
- return first == null ? null : first.GetProviderId(MetadataProviders.Imdb);
- }
-
public int Order
{
get
diff --git a/MediaBrowser.Providers/TV/SeasonMetadataService.cs b/MediaBrowser.Providers/TV/SeasonMetadataService.cs
index ae19a6a44..e4894915d 100644
--- a/MediaBrowser.Providers/TV/SeasonMetadataService.cs
+++ b/MediaBrowser.Providers/TV/SeasonMetadataService.cs
@@ -15,10 +15,6 @@ namespace MediaBrowser.Providers.TV
{
public class SeasonMetadataService : MetadataService<Season, SeasonInfo>
{
- public SeasonMetadataService(IServerConfigurationManager serverConfigurationManager, ILogger logger, IProviderManager providerManager, IProviderRepository providerRepo, IFileSystem fileSystem, IUserDataManager userDataManager, ILibraryManager libraryManager) : base(serverConfigurationManager, logger, providerManager, providerRepo, fileSystem, userDataManager, libraryManager)
- {
- }
-
protected override async Task<ItemUpdateType> BeforeSave(Season item, bool isFullRefresh, ItemUpdateType currentUpdateType)
{
var updateType = await base.BeforeSave(item, isFullRefresh, currentUpdateType).ConfigureAwait(false);
@@ -79,5 +75,9 @@ namespace MediaBrowser.Providers.TV
return ItemUpdateType.None;
}
+
+ public SeasonMetadataService(IServerConfigurationManager serverConfigurationManager, ILogger logger, IProviderManager providerManager, IFileSystem fileSystem, IUserDataManager userDataManager, ILibraryManager libraryManager) : base(serverConfigurationManager, logger, providerManager, fileSystem, userDataManager, libraryManager)
+ {
+ }
}
}
diff --git a/MediaBrowser.Providers/TV/SeriesMetadataService.cs b/MediaBrowser.Providers/TV/SeriesMetadataService.cs
index dfa8e30f3..f440baf5b 100644
--- a/MediaBrowser.Providers/TV/SeriesMetadataService.cs
+++ b/MediaBrowser.Providers/TV/SeriesMetadataService.cs
@@ -18,7 +18,7 @@ namespace MediaBrowser.Providers.TV
{
private readonly ILocalizationManager _localization;
- public SeriesMetadataService(IServerConfigurationManager serverConfigurationManager, ILogger logger, IProviderManager providerManager, IProviderRepository providerRepo, IFileSystem fileSystem, IUserDataManager userDataManager, ILibraryManager libraryManager, ILocalizationManager localization) : base(serverConfigurationManager, logger, providerManager, providerRepo, fileSystem, userDataManager, libraryManager)
+ public SeriesMetadataService(IServerConfigurationManager serverConfigurationManager, ILogger logger, IProviderManager providerManager, IFileSystem fileSystem, IUserDataManager userDataManager, ILibraryManager libraryManager, ILocalizationManager localization) : base(serverConfigurationManager, logger, providerManager, fileSystem, userDataManager, libraryManager)
{
_localization = localization;
}
@@ -76,11 +76,6 @@ namespace MediaBrowser.Providers.TV
{
targetItem.AirDays = sourceItem.AirDays;
}
-
- if (mergeMetadataSettings)
- {
- targetItem.DisplaySpecialsWithSeasons = sourceItem.DisplaySpecialsWithSeasons;
- }
}
}
}
diff --git a/MediaBrowser.Providers/TV/SeriesPostScanTask.cs b/MediaBrowser.Providers/TV/SeriesPostScanTask.cs
index 3c0a5fc73..d044c828f 100644
--- a/MediaBrowser.Providers/TV/SeriesPostScanTask.cs
+++ b/MediaBrowser.Providers/TV/SeriesPostScanTask.cs
@@ -195,28 +195,35 @@ namespace MediaBrowser.Providers.TV
private async void LibraryUpdateTimerCallback(object state)
{
- if (MissingEpisodeProvider.IsRunning)
+ try
{
- return;
- }
+ if (MissingEpisodeProvider.IsRunning)
+ {
+ return;
+ }
- if (_libraryManager.IsScanRunning)
- {
- return ;
- }
+ if (_libraryManager.IsScanRunning)
+ {
+ return;
+ }
- var seriesList = _libraryManager.GetItemList(new InternalItemsQuery()
- {
- IncludeItemTypes = new[] { typeof(Series).Name },
- Recursive = true,
- GroupByPresentationUniqueKey = false
+ var seriesList = _libraryManager.GetItemList(new InternalItemsQuery()
+ {
+ IncludeItemTypes = new[] { typeof(Series).Name },
+ Recursive = true,
+ GroupByPresentationUniqueKey = false
- }).Cast<Series>().ToList();
+ }).Cast<Series>().ToList();
- var seriesGroups = SeriesPostScanTask.FindSeriesGroups(seriesList).Where(g => !string.IsNullOrEmpty(g.Key)).ToList();
+ var seriesGroups = SeriesPostScanTask.FindSeriesGroups(seriesList).Where(g => !string.IsNullOrEmpty(g.Key)).ToList();
- await new MissingEpisodeProvider(_logger, _config, _libraryManager, _localization, _fileSystem)
- .Run(seriesGroups, false, CancellationToken.None).ConfigureAwait(false);
+ await new MissingEpisodeProvider(_logger, _config, _libraryManager, _localization, _fileSystem)
+ .Run(seriesGroups, false, CancellationToken.None).ConfigureAwait(false);
+ }
+ catch (Exception ex)
+ {
+ _logger.ErrorException("Error in SeriesPostScanTask", ex);
+ }
}
private bool FilterItem(BaseItem item)
diff --git a/MediaBrowser.Providers/TV/TheMovieDb/MovieDbEpisodeImageProvider.cs b/MediaBrowser.Providers/TV/TheMovieDb/MovieDbEpisodeImageProvider.cs
index 719779674..0feb92e89 100644
--- a/MediaBrowser.Providers/TV/TheMovieDb/MovieDbEpisodeImageProvider.cs
+++ b/MediaBrowser.Providers/TV/TheMovieDb/MovieDbEpisodeImageProvider.cs
@@ -57,8 +57,10 @@ namespace MediaBrowser.Providers.TV
return list;
}
+ var language = item.GetPreferredMetadataLanguage();
+
var response = await GetEpisodeInfo(seriesId, seasonNumber.Value, episodeNumber.Value,
- item.GetPreferredMetadataLanguage(), cancellationToken).ConfigureAwait(false);
+ language, cancellationToken).ConfigureAwait(false);
var tmdbSettings = await MovieDbProvider.Current.GetTmdbSettings(cancellationToken).ConfigureAwait(false);
@@ -71,12 +73,12 @@ namespace MediaBrowser.Providers.TV
VoteCount = i.vote_count,
Width = i.width,
Height = i.height,
+ Language = MovieDbProvider.AdjustImageLanguage(i.iso_639_1, language),
ProviderName = Name,
Type = ImageType.Primary,
RatingType = RatingType.Score
}));
- var language = item.GetPreferredMetadataLanguage();
var isLanguageEn = string.Equals(language, "en", StringComparison.OrdinalIgnoreCase);
diff --git a/MediaBrowser.Providers/TV/TheMovieDb/MovieDbEpisodeProvider.cs b/MediaBrowser.Providers/TV/TheMovieDb/MovieDbEpisodeProvider.cs
index 9bab3d380..bc9842b73 100644
--- a/MediaBrowser.Providers/TV/TheMovieDb/MovieDbEpisodeProvider.cs
+++ b/MediaBrowser.Providers/TV/TheMovieDb/MovieDbEpisodeProvider.cs
@@ -114,6 +114,22 @@ namespace MediaBrowser.Providers.TV
item.CommunityRating = (float)response.vote_average;
item.VoteCount = response.vote_count;
+ if (response.videos != null && response.videos.results != null)
+ {
+ foreach (var video in response.videos.results)
+ {
+ if (video.type.Equals("trailer", System.StringComparison.OrdinalIgnoreCase)
+ || video.type.Equals("clip", System.StringComparison.OrdinalIgnoreCase))
+ {
+ if (video.site.Equals("youtube", System.StringComparison.OrdinalIgnoreCase))
+ {
+ var videoUrl = string.Format("http://www.youtube.com/watch?v={0}", video.key);
+ item.AddTrailerUrl(videoUrl, true);
+ }
+ }
+ }
+ }
+
result.ResetPeople();
var credits = response.credits;
diff --git a/MediaBrowser.Providers/TV/TheMovieDb/MovieDbProviderBase.cs b/MediaBrowser.Providers/TV/TheMovieDb/MovieDbProviderBase.cs
index 36800202f..821c26e4b 100644
--- a/MediaBrowser.Providers/TV/TheMovieDb/MovieDbProviderBase.cs
+++ b/MediaBrowser.Providers/TV/TheMovieDb/MovieDbProviderBase.cs
@@ -151,7 +151,7 @@ namespace MediaBrowser.Providers.TV
public string file_path { get; set; }
public int height { get; set; }
public string id { get; set; }
- public object iso_639_1 { get; set; }
+ public string iso_639_1 { get; set; }
public double vote_average { get; set; }
public int vote_count { get; set; }
public int width { get; set; }
@@ -210,7 +210,19 @@ namespace MediaBrowser.Providers.TV
public class Videos
{
- public List<object> results { get; set; }
+ public List<Video> results { get; set; }
+ }
+
+ public class Video
+ {
+ public string id { get; set; }
+ public string iso_639_1 { get; set; }
+ public string iso_3166_1 { get; set; }
+ public string key { get; set; }
+ public string name { get; set; }
+ public string site { get; set; }
+ public string size { get; set; }
+ public string type { get; set; }
}
public class RootObject
diff --git a/MediaBrowser.Providers/TV/TheMovieDb/MovieDbSeasonProvider.cs b/MediaBrowser.Providers/TV/TheMovieDb/MovieDbSeasonProvider.cs
index 2e51393e3..194af5b99 100644
--- a/MediaBrowser.Providers/TV/TheMovieDb/MovieDbSeasonProvider.cs
+++ b/MediaBrowser.Providers/TV/TheMovieDb/MovieDbSeasonProvider.cs
@@ -58,7 +58,7 @@ namespace MediaBrowser.Providers.TV
result.HasMetadata = true;
result.Item = new Season();
- result.Item.Name = info.Name;
+ result.Item.Name = seasonInfo.name;
result.Item.IndexNumber = seasonNumber;
result.Item.Overview = seasonInfo.overview;
diff --git a/MediaBrowser.Providers/TV/TheMovieDb/MovieDbSeriesImageProvider.cs b/MediaBrowser.Providers/TV/TheMovieDb/MovieDbSeriesImageProvider.cs
index 65ac12adf..ad46db677 100644
--- a/MediaBrowser.Providers/TV/TheMovieDb/MovieDbSeriesImageProvider.cs
+++ b/MediaBrowser.Providers/TV/TheMovieDb/MovieDbSeriesImageProvider.cs
@@ -16,7 +16,7 @@ using System.Threading.Tasks;
namespace MediaBrowser.Providers.TV
{
- public class MovieDbSeriesImageProvider : IRemoteImageProvider, IHasOrder, IHasItemChangeMonitor
+ public class MovieDbSeriesImageProvider : IRemoteImageProvider, IHasOrder
{
private readonly IJsonSerializer _jsonSerializer;
private readonly IHttpClient _httpClient;
@@ -66,6 +66,8 @@ namespace MediaBrowser.Providers.TV
var tmdbImageUrl = tmdbSettings.images.secure_base_url + "original";
+ var language = item.GetPreferredMetadataLanguage();
+
list.AddRange(GetPosters(results).Select(i => new RemoteImageInfo
{
Url = tmdbImageUrl + i.file_path,
@@ -73,7 +75,7 @@ namespace MediaBrowser.Providers.TV
VoteCount = i.vote_count,
Width = i.width,
Height = i.height,
- Language = i.iso_639_1,
+ Language = MovieDbProvider.AdjustImageLanguage(i.iso_639_1, language),
ProviderName = Name,
Type = ImageType.Primary,
RatingType = RatingType.Score
@@ -91,8 +93,6 @@ namespace MediaBrowser.Providers.TV
RatingType = RatingType.Score
}));
- var language = item.GetPreferredMetadataLanguage();
-
var isLanguageEn = string.Equals(language, "en", StringComparison.OrdinalIgnoreCase);
return list.OrderByDescending(i =>
@@ -195,10 +195,5 @@ namespace MediaBrowser.Providers.TV
ResourcePool = MovieDbProvider.Current.MovieDbResourcePool
});
}
-
- public bool HasChanged(IHasMetadata item, IDirectoryService directoryService)
- {
- return MovieDbSeriesProvider.Current.HasChanged(item);
- }
}
}
diff --git a/MediaBrowser.Providers/TV/TheMovieDb/MovieDbSeriesProvider.cs b/MediaBrowser.Providers/TV/TheMovieDb/MovieDbSeriesProvider.cs
index 7d0f49955..3245a2c85 100644
--- a/MediaBrowser.Providers/TV/TheMovieDb/MovieDbSeriesProvider.cs
+++ b/MediaBrowser.Providers/TV/TheMovieDb/MovieDbSeriesProvider.cs
@@ -168,7 +168,7 @@ namespace MediaBrowser.Providers.TV
{
cancellationToken.ThrowIfCancellationRequested();
- result.Item = await FetchMovieData(tmdbId, info.MetadataLanguage, info.MetadataCountryCode, cancellationToken).ConfigureAwait(false);
+ result.Item = await FetchSeriesData(tmdbId, info.MetadataLanguage, info.MetadataCountryCode, cancellationToken).ConfigureAwait(false);
result.HasMetadata = result.Item != null;
}
@@ -176,7 +176,7 @@ namespace MediaBrowser.Providers.TV
return result;
}
- private async Task<Series> FetchMovieData(string tmdbId, string language, string preferredCountryCode, CancellationToken cancellationToken)
+ private async Task<Series> FetchSeriesData(string tmdbId, string language, string preferredCountryCode, CancellationToken cancellationToken)
{
string dataFilePath = null;
RootObject seriesInfo = null;
@@ -285,6 +285,21 @@ namespace MediaBrowser.Providers.TV
series.OfficialRating = minimumRelease.rating;
}
+ if (seriesInfo.videos != null && seriesInfo.videos.results != null)
+ {
+ foreach (var video in seriesInfo.videos.results)
+ {
+ if (video.type.Equals("trailer", System.StringComparison.OrdinalIgnoreCase)
+ || video.type.Equals("clip", System.StringComparison.OrdinalIgnoreCase))
+ {
+ if (video.site.Equals("youtube", System.StringComparison.OrdinalIgnoreCase))
+ {
+ var videoUrl = string.Format("http://www.youtube.com/watch?v={0}", video.key);
+ series.AddTrailerUrl(videoUrl, true);
+ }
+ }
+ }
+ }
}
internal static string GetSeriesDataPath(IApplicationPaths appPaths, string tmdbId)
@@ -414,28 +429,6 @@ namespace MediaBrowser.Providers.TV
return Path.Combine(path, filename);
}
- public bool HasChanged(IHasMetadata item)
- {
- if (!MovieDbProvider.Current.GetTheMovieDbOptions().EnableAutomaticUpdates)
- {
- return false;
- }
-
- var tmdbId = item.GetProviderId(MetadataProviders.Tmdb);
-
- if (!String.IsNullOrEmpty(tmdbId))
- {
- // Process images
- var dataFilePath = GetDataFilePath(tmdbId, item.GetPreferredMetadataLanguage());
-
- var fileInfo = _fileSystem.GetFileInfo(dataFilePath);
-
- return !fileInfo.Exists || _fileSystem.GetLastWriteTimeUtc(fileInfo) > item.DateLastRefreshed;
- }
-
- return false;
- }
-
private async Task<RemoteSearchResult> FindByExternalId(string id, string externalSource, CancellationToken cancellationToken)
{
var url = string.Format("https://api.themoviedb.org/3/tv/find/{0}?api_key={1}&external_source={2}",
@@ -577,7 +570,19 @@ namespace MediaBrowser.Providers.TV
public class Videos
{
- public List<object> results { get; set; }
+ public List<Video> results { get; set; }
+ }
+
+ public class Video
+ {
+ public string id { get; set; }
+ public string iso_639_1 { get; set; }
+ public string iso_3166_1 { get; set; }
+ public string key { get; set; }
+ public string name { get; set; }
+ public string site { get; set; }
+ public string size { get; set; }
+ public string type { get; set; }
}
public class ContentRating
diff --git a/MediaBrowser.Providers/TV/TheTVDB/TvdbEpisodeImageProvider.cs b/MediaBrowser.Providers/TV/TheTVDB/TvdbEpisodeImageProvider.cs
index 7a0b2c90c..881513286 100644
--- a/MediaBrowser.Providers/TV/TheTVDB/TvdbEpisodeImageProvider.cs
+++ b/MediaBrowser.Providers/TV/TheTVDB/TvdbEpisodeImageProvider.cs
@@ -17,7 +17,7 @@ using CommonIO;
namespace MediaBrowser.Providers.TV
{
- public class TvdbEpisodeImageProvider : IRemoteImageProvider, IHasItemChangeMonitor
+ public class TvdbEpisodeImageProvider : IRemoteImageProvider
{
private readonly IServerConfigurationManager _config;
private readonly CultureInfo _usCulture = new CultureInfo("en-US");
@@ -173,31 +173,5 @@ namespace MediaBrowser.Providers.TV
ResourcePool = TvdbSeriesProvider.Current.TvDbResourcePool
});
}
-
- public bool HasChanged(IHasMetadata item, IDirectoryService directoryService)
- {
- // For non-unaired items, only enable if configured
- if (!TvdbSeriesProvider.Current.GetTvDbOptions().EnableAutomaticUpdates)
- {
- return false;
- }
-
- if (!item.HasImage(ImageType.Primary))
- {
- var episode = (Episode)item;
-
- var series = episode.Series;
-
- if (series != null && TvdbSeriesProvider.IsValidSeries(series.ProviderIds))
- {
- // Process images
- var seriesXmlPath = TvdbSeriesProvider.Current.GetSeriesXmlPath(series.ProviderIds, series.GetPreferredMetadataLanguage());
-
- return _fileSystem.GetLastWriteTimeUtc(seriesXmlPath) > item.DateLastRefreshed;
- }
- }
-
- return false;
- }
}
}
diff --git a/MediaBrowser.Providers/TV/TheTVDB/TvdbEpisodeProvider.cs b/MediaBrowser.Providers/TV/TheTVDB/TvdbEpisodeProvider.cs
index 509b22ee6..a41a95c12 100644
--- a/MediaBrowser.Providers/TV/TheTVDB/TvdbEpisodeProvider.cs
+++ b/MediaBrowser.Providers/TV/TheTVDB/TvdbEpisodeProvider.cs
@@ -24,7 +24,7 @@ namespace MediaBrowser.Providers.TV
/// <summary>
/// Class RemoteEpisodeProvider
/// </summary>
- class TvdbEpisodeProvider : IRemoteMetadataProvider<Episode, EpisodeInfo>, IItemIdentityProvider<EpisodeInfo>, IHasItemChangeMonitor
+ class TvdbEpisodeProvider : IRemoteMetadataProvider<Episode, EpisodeInfo>
{
private static readonly string FullIdKey = MetadataProviders.Tvdb + "-Full";
@@ -144,27 +144,6 @@ namespace MediaBrowser.Providers.TV
return result;
}
- public bool HasChanged(IHasMetadata item, IDirectoryService directoryService)
- {
- if (!TvdbSeriesProvider.Current.GetTvDbOptions().EnableAutomaticUpdates)
- {
- return false;
- }
-
- var episode = (Episode)item;
- var series = episode.Series;
-
- if (series != null && TvdbSeriesProvider.IsValidSeries(series.ProviderIds))
- {
- // Process images
- var seriesXmlPath = TvdbSeriesProvider.Current.GetSeriesXmlPath(series.ProviderIds, series.GetPreferredMetadataLanguage());
-
- return _fileSystem.GetLastWriteTimeUtc(seriesXmlPath) > item.DateLastRefreshed;
- }
-
- return false;
- }
-
/// <summary>
/// Gets the episode XML files.
/// </summary>
@@ -892,86 +871,6 @@ namespace MediaBrowser.Providers.TV
});
}
- public Task Identify(EpisodeInfo info)
- {
- if (info.ProviderIds.ContainsKey(FullIdKey))
- {
- return Task.FromResult<object>(null);
- }
-
- string seriesTvdbId;
- info.SeriesProviderIds.TryGetValue(MetadataProviders.Tvdb.ToString(), out seriesTvdbId);
-
- if (string.IsNullOrEmpty(seriesTvdbId) || info.IndexNumber == null)
- {
- return Task.FromResult<object>(null);
- }
-
- var id = new Identity(seriesTvdbId, info.ParentIndexNumber, info.IndexNumber.Value, info.IndexNumberEnd);
- info.SetProviderId(FullIdKey, id.ToString());
-
- return Task.FromResult(id);
- }
-
public int Order { get { return 0; } }
-
- public struct Identity
- {
- public string SeriesId { get; private set; }
- public int? SeasonIndex { get; private set; }
- public int EpisodeNumber { get; private set; }
- public int? EpisodeNumberEnd { get; private set; }
-
- public Identity(string id)
- : this()
- {
- this = ParseIdentity(id).Value;
- }
-
- public Identity(string seriesId, int? seasonIndex, int episodeNumber, int? episodeNumberEnd)
- : this()
- {
- SeriesId = seriesId;
- SeasonIndex = seasonIndex;
- EpisodeNumber = episodeNumber;
- EpisodeNumberEnd = episodeNumberEnd;
- }
-
- public override string ToString()
- {
- return string.Format("{0}:{1}:{2}",
- SeriesId,
- SeasonIndex != null ? SeasonIndex.Value.ToString() : "A",
- EpisodeNumber + (EpisodeNumberEnd != null ? "-" + EpisodeNumberEnd.Value.ToString() : ""));
- }
-
- public static Identity? ParseIdentity(string id)
- {
- if (string.IsNullOrEmpty(id))
- return null;
-
- try {
- var parts = id.Split(':');
- var series = parts[0];
- var season = parts[1] != "A" ? (int?)int.Parse(parts[1]) : null;
-
- int index;
- int? indexEnd;
-
- if (parts[2].Contains("-")) {
- var split = parts[2].IndexOf("-", StringComparison.OrdinalIgnoreCase);
- index = int.Parse(parts[2].Substring(0, split));
- indexEnd = int.Parse(parts[2].Substring(split + 1));
- } else {
- index = int.Parse(parts[2]);
- indexEnd = null;
- }
-
- return new Identity(series, season, index, indexEnd);
- } catch {
- return null;
- }
- }
- }
}
}
diff --git a/MediaBrowser.Providers/TV/TheTVDB/TvdbSeasonIdentityProvider.cs b/MediaBrowser.Providers/TV/TheTVDB/TvdbSeasonIdentityProvider.cs
deleted file mode 100644
index 4198430c9..000000000
--- a/MediaBrowser.Providers/TV/TheTVDB/TvdbSeasonIdentityProvider.cs
+++ /dev/null
@@ -1,65 +0,0 @@
-using System.Threading.Tasks;
-using MediaBrowser.Controller.Providers;
-using MediaBrowser.Model.Entities;
-
-namespace MediaBrowser.Providers.TV
-{
- public class TvdbSeasonIdentityProvider : IItemIdentityProvider<SeasonInfo>
- {
- public static readonly string FullIdKey = MetadataProviders.Tvdb + "-Full";
-
- public Task Identify(SeasonInfo info)
- {
- string tvdbSeriesId;
- if (!info.SeriesProviderIds.TryGetValue(MetadataProviders.Tvdb.ToString(), out tvdbSeriesId) || string.IsNullOrEmpty(tvdbSeriesId) || info.IndexNumber == null)
- {
- return Task.FromResult<object>(null);
- }
-
- if (string.IsNullOrEmpty(info.GetProviderId(FullIdKey)))
- {
- var id = string.Format("{0}:{1}", tvdbSeriesId, info.IndexNumber.Value);
- info.SetProviderId(FullIdKey, id);
- }
-
- return Task.FromResult<object>(null);
- }
-
- public static TvdbSeasonIdentity? ParseIdentity(string id)
- {
- if (id == null)
- {
- return null;
- }
-
- try
- {
- var parts = id.Split(':');
- return new TvdbSeasonIdentity(parts[0], int.Parse(parts[1]));
- }
- catch
- {
- return null;
- }
- }
- }
-
- public struct TvdbSeasonIdentity
- {
- public string SeriesId { get; private set; }
- public int Index { get; private set; }
-
- public TvdbSeasonIdentity(string id)
- : this()
- {
- this = TvdbSeasonIdentityProvider.ParseIdentity(id).Value;
- }
-
- public TvdbSeasonIdentity(string seriesId, int index)
- : this()
- {
- SeriesId = seriesId;
- Index = index;
- }
- }
-} \ No newline at end of file
diff --git a/MediaBrowser.Providers/TV/TheTVDB/TvdbSeasonImageProvider.cs b/MediaBrowser.Providers/TV/TheTVDB/TvdbSeasonImageProvider.cs
index 52d12e501..4b619665c 100644
--- a/MediaBrowser.Providers/TV/TheTVDB/TvdbSeasonImageProvider.cs
+++ b/MediaBrowser.Providers/TV/TheTVDB/TvdbSeasonImageProvider.cs
@@ -20,7 +20,7 @@ using CommonIO;
namespace MediaBrowser.Providers.TV
{
- public class TvdbSeasonImageProvider : IRemoteImageProvider, IHasOrder, IHasItemChangeMonitor
+ public class TvdbSeasonImageProvider : IRemoteImageProvider, IHasOrder
{
private static readonly CultureInfo UsCulture = new CultureInfo("en-US");
@@ -70,21 +70,6 @@ namespace MediaBrowser.Providers.TV
var seriesProviderIds = series.ProviderIds;
var seasonNumber = season.IndexNumber.Value;
- var identity = TvdbSeasonIdentityProvider.ParseIdentity(season.GetProviderId(TvdbSeasonIdentityProvider.FullIdKey));
- if (identity == null)
- {
- identity = new TvdbSeasonIdentity(series.GetProviderId(MetadataProviders.Tvdb), seasonNumber);
- }
-
- if (identity != null)
- {
- var id = identity.Value;
- seasonNumber = AdjustForSeriesOffset(series, id.Index);
-
- seriesProviderIds = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
- seriesProviderIds[MetadataProviders.Tvdb.ToString()] = id.SeriesId;
- }
-
var seriesDataPath = await TvdbSeriesProvider.Current.EnsureSeriesInfo(seriesProviderIds, series.GetPreferredMetadataLanguage(), cancellationToken).ConfigureAwait(false);
if (!string.IsNullOrWhiteSpace(seriesDataPath))
@@ -362,28 +347,5 @@ namespace MediaBrowser.Providers.TV
ResourcePool = TvdbSeriesProvider.Current.TvDbResourcePool
});
}
-
- public bool HasChanged(IHasMetadata item, IDirectoryService directoryService)
- {
- if (!TvdbSeriesProvider.Current.GetTvDbOptions().EnableAutomaticUpdates)
- {
- return false;
- }
-
- var season = (Season)item;
- var series = season.Series;
-
- if (series != null && TvdbSeriesProvider.IsValidSeries(series.ProviderIds))
- {
- // Process images
- var imagesXmlPath = Path.Combine(TvdbSeriesProvider.GetSeriesDataPath(_config.ApplicationPaths, series.ProviderIds), "banners.xml");
-
- var fileInfo = _fileSystem.GetFileInfo(imagesXmlPath);
-
- return fileInfo.Exists && _fileSystem.GetLastWriteTimeUtc(fileInfo) > item.DateLastRefreshed;
- }
-
- return false;
- }
}
}
diff --git a/MediaBrowser.Providers/TV/TheTVDB/TvdbSeriesImageProvider.cs b/MediaBrowser.Providers/TV/TheTVDB/TvdbSeriesImageProvider.cs
index d1cdc717e..c8efbfb14 100644
--- a/MediaBrowser.Providers/TV/TheTVDB/TvdbSeriesImageProvider.cs
+++ b/MediaBrowser.Providers/TV/TheTVDB/TvdbSeriesImageProvider.cs
@@ -20,7 +20,7 @@ using CommonIO;
namespace MediaBrowser.Providers.TV
{
- public class TvdbSeriesImageProvider : IRemoteImageProvider, IHasOrder, IHasItemChangeMonitor
+ public class TvdbSeriesImageProvider : IRemoteImageProvider, IHasOrder
{
private readonly IServerConfigurationManager _config;
private readonly IHttpClient _httpClient;
@@ -331,25 +331,5 @@ namespace MediaBrowser.Providers.TV
ResourcePool = TvdbSeriesProvider.Current.TvDbResourcePool
});
}
-
- public bool HasChanged(IHasMetadata item, IDirectoryService directoryService)
- {
- if (!TvdbSeriesProvider.Current.GetTvDbOptions().EnableAutomaticUpdates)
- {
- return false;
- }
-
- if (TvdbSeriesProvider.IsValidSeries(item.ProviderIds))
- {
- // Process images
- var imagesXmlPath = Path.Combine(TvdbSeriesProvider.GetSeriesDataPath(_config.ApplicationPaths, item.ProviderIds), "banners.xml");
-
- var fileInfo = _fileSystem.GetFileInfo(imagesXmlPath);
-
- return fileInfo.Exists && _fileSystem.GetLastWriteTimeUtc(fileInfo) > item.DateLastRefreshed;
- }
-
- return false;
- }
}
}
diff --git a/MediaBrowser.Providers/TV/TheTVDB/TvdbSeriesProvider.cs b/MediaBrowser.Providers/TV/TheTVDB/TvdbSeriesProvider.cs
index ee1505b46..b6cc8777d 100644
--- a/MediaBrowser.Providers/TV/TheTVDB/TvdbSeriesProvider.cs
+++ b/MediaBrowser.Providers/TV/TheTVDB/TvdbSeriesProvider.cs
@@ -25,7 +25,7 @@ using CommonIO;
namespace MediaBrowser.Providers.TV
{
- public class TvdbSeriesProvider : IRemoteMetadataProvider<Series, SeriesInfo>, IItemIdentityProvider<SeriesInfo>, IHasOrder
+ public class TvdbSeriesProvider : IRemoteMetadataProvider<Series, SeriesInfo>, IHasOrder
{
private const string TvdbSeriesOffset = "TvdbSeriesOffset";
private const string TvdbSeriesOffsetFormat = "{0}-{1}";
@@ -38,17 +38,15 @@ namespace MediaBrowser.Providers.TV
private readonly IServerConfigurationManager _config;
private readonly CultureInfo _usCulture = new CultureInfo("en-US");
private readonly ILogger _logger;
- private readonly ISeriesOrderManager _seriesOrder;
private readonly ILibraryManager _libraryManager;
- public TvdbSeriesProvider(IZipClient zipClient, IHttpClient httpClient, IFileSystem fileSystem, IServerConfigurationManager config, ILogger logger, ISeriesOrderManager seriesOrder, ILibraryManager libraryManager)
+ public TvdbSeriesProvider(IZipClient zipClient, IHttpClient httpClient, IFileSystem fileSystem, IServerConfigurationManager config, ILogger logger, ILibraryManager libraryManager)
{
_zipClient = zipClient;
_httpClient = httpClient;
_fileSystem = fileSystem;
_config = config;
_logger = logger;
- _seriesOrder = seriesOrder;
_libraryManager = libraryManager;
Current = this;
}
@@ -112,23 +110,11 @@ namespace MediaBrowser.Providers.TV
result.HasMetadata = true;
FetchSeriesData(result, itemId.MetadataLanguage, itemId.ProviderIds, cancellationToken);
- await FindAnimeSeriesIndex(result.Item, itemId).ConfigureAwait(false);
}
return result;
}
- private async Task FindAnimeSeriesIndex(Series series, SeriesInfo info)
- {
- var index = await _seriesOrder.FindSeriesIndex(SeriesOrderTypes.Anime, series.Name);
- if (index == null)
- return;
-
- var offset = info.AnimeSeriesIndex - index;
- var id = string.Format(TvdbSeriesOffsetFormat, series.GetProviderId(MetadataProviders.Tvdb), offset);
- series.SetProviderId(TvdbSeriesOffset, id);
- }
-
internal static int? GetSeriesOffset(Dictionary<string, string> seriesProviderIds)
{
string idString;
@@ -311,11 +297,6 @@ namespace MediaBrowser.Providers.TV
return null;
}
- public TvdbOptions GetTvDbOptions()
- {
- return _config.GetConfiguration<TvdbOptions>("tvdb");
- }
-
internal static bool IsValidSeries(Dictionary<string, string> seriesProviderIds)
{
string id;
@@ -392,27 +373,25 @@ namespace MediaBrowser.Providers.TV
var seriesXmlFilename = preferredMetadataLanguage + ".xml";
- var automaticUpdatesEnabled = GetTvDbOptions().EnableAutomaticUpdates;
-
const int cacheDays = 1;
var seriesFile = files.FirstOrDefault(i => string.Equals(seriesXmlFilename, i.Name, StringComparison.OrdinalIgnoreCase));
// No need to check age if automatic updates are enabled
- if (seriesFile == null || !seriesFile.Exists || (!automaticUpdatesEnabled && (DateTime.UtcNow - _fileSystem.GetLastWriteTimeUtc(seriesFile)).TotalDays > cacheDays))
+ if (seriesFile == null || !seriesFile.Exists || (DateTime.UtcNow - _fileSystem.GetLastWriteTimeUtc(seriesFile)).TotalDays > cacheDays)
{
return false;
}
var actorsXml = files.FirstOrDefault(i => string.Equals("actors.xml", i.Name, StringComparison.OrdinalIgnoreCase));
// No need to check age if automatic updates are enabled
- if (actorsXml == null || !actorsXml.Exists || (!automaticUpdatesEnabled && (DateTime.UtcNow - _fileSystem.GetLastWriteTimeUtc(actorsXml)).TotalDays > cacheDays))
+ if (actorsXml == null || !actorsXml.Exists || (DateTime.UtcNow - _fileSystem.GetLastWriteTimeUtc(actorsXml)).TotalDays > cacheDays)
{
return false;
}
var bannersXml = files.FirstOrDefault(i => string.Equals("banners.xml", i.Name, StringComparison.OrdinalIgnoreCase));
// No need to check age if automatic updates are enabled
- if (bannersXml == null || !bannersXml.Exists || (!automaticUpdatesEnabled && (DateTime.UtcNow - _fileSystem.GetLastWriteTimeUtc(bannersXml)).TotalDays > cacheDays))
+ if (bannersXml == null || !bannersXml.Exists || (DateTime.UtcNow - _fileSystem.GetLastWriteTimeUtc(bannersXml)).TotalDays > cacheDays)
{
return false;
}
@@ -1450,19 +1429,4 @@ namespace MediaBrowser.Providers.TV
});
}
}
-
- public class TvdbConfigStore : IConfigurationFactory
- {
- public IEnumerable<ConfigurationStore> GetConfigurations()
- {
- return new List<ConfigurationStore>
- {
- new ConfigurationStore
- {
- Key = "tvdb",
- ConfigurationType = typeof(TvdbOptions)
- }
- };
- }
- }
}
diff --git a/MediaBrowser.Providers/TV/TvExternalIds.cs b/MediaBrowser.Providers/TV/TvExternalIds.cs
index f5a26ba0a..2ba3b6ff6 100644
--- a/MediaBrowser.Providers/TV/TvExternalIds.cs
+++ b/MediaBrowser.Providers/TV/TvExternalIds.cs
@@ -88,7 +88,7 @@ namespace MediaBrowser.Providers.TV
public string UrlFormatString
{
- get { return null; }
+ get { return "https://thetvdb.com/index.php?tab=episode&id={0}"; }
}
public bool Supports(IHasProviderIds item)
diff --git a/MediaBrowser.Providers/Users/UserMetadataService.cs b/MediaBrowser.Providers/Users/UserMetadataService.cs
index 90a874191..0637e9a02 100644
--- a/MediaBrowser.Providers/Users/UserMetadataService.cs
+++ b/MediaBrowser.Providers/Users/UserMetadataService.cs
@@ -12,13 +12,13 @@ namespace MediaBrowser.Providers.Users
{
public class UserMetadataService : MetadataService<User, ItemLookupInfo>
{
- public UserMetadataService(IServerConfigurationManager serverConfigurationManager, ILogger logger, IProviderManager providerManager, IProviderRepository providerRepo, IFileSystem fileSystem, IUserDataManager userDataManager, ILibraryManager libraryManager) : base(serverConfigurationManager, logger, providerManager, providerRepo, fileSystem, userDataManager, libraryManager)
+ protected override void MergeData(MetadataResult<User> source, MetadataResult<User> target, List<MetadataFields> lockedFields, bool replaceData, bool mergeMetadataSettings)
{
+ ProviderUtils.MergeBaseItemData(source, target, lockedFields, replaceData, mergeMetadataSettings);
}
- protected override void MergeData(MetadataResult<User> source, MetadataResult<User> target, List<MetadataFields> lockedFields, bool replaceData, bool mergeMetadataSettings)
+ public UserMetadataService(IServerConfigurationManager serverConfigurationManager, ILogger logger, IProviderManager providerManager, IFileSystem fileSystem, IUserDataManager userDataManager, ILibraryManager libraryManager) : base(serverConfigurationManager, logger, providerManager, fileSystem, userDataManager, libraryManager)
{
- ProviderUtils.MergeBaseItemData(source, target, lockedFields, replaceData, mergeMetadataSettings);
}
}
}
diff --git a/MediaBrowser.Providers/Videos/VideoMetadataService.cs b/MediaBrowser.Providers/Videos/VideoMetadataService.cs
index fb2a7638e..a4fc462ef 100644
--- a/MediaBrowser.Providers/Videos/VideoMetadataService.cs
+++ b/MediaBrowser.Providers/Videos/VideoMetadataService.cs
@@ -12,10 +12,6 @@ namespace MediaBrowser.Providers.Videos
{
public class VideoMetadataService : MetadataService<Video, ItemLookupInfo>
{
- public VideoMetadataService(IServerConfigurationManager serverConfigurationManager, ILogger logger, IProviderManager providerManager, IProviderRepository providerRepo, IFileSystem fileSystem, IUserDataManager userDataManager, ILibraryManager libraryManager) : base(serverConfigurationManager, logger, providerManager, providerRepo, fileSystem, userDataManager, libraryManager)
- {
- }
-
public override int Order
{
get
@@ -29,5 +25,9 @@ namespace MediaBrowser.Providers.Videos
{
ProviderUtils.MergeBaseItemData(source, target, lockedFields, replaceData, mergeMetadataSettings);
}
+
+ public VideoMetadataService(IServerConfigurationManager serverConfigurationManager, ILogger logger, IProviderManager providerManager, IFileSystem fileSystem, IUserDataManager userDataManager, ILibraryManager libraryManager) : base(serverConfigurationManager, logger, providerManager, fileSystem, userDataManager, libraryManager)
+ {
+ }
}
}
diff --git a/MediaBrowser.Providers/Years/YearMetadataService.cs b/MediaBrowser.Providers/Years/YearMetadataService.cs
index 3c348b2de..fd65c379d 100644
--- a/MediaBrowser.Providers/Years/YearMetadataService.cs
+++ b/MediaBrowser.Providers/Years/YearMetadataService.cs
@@ -12,13 +12,13 @@ namespace MediaBrowser.Providers.Years
{
public class YearMetadataService : MetadataService<Year, ItemLookupInfo>
{
- public YearMetadataService(IServerConfigurationManager serverConfigurationManager, ILogger logger, IProviderManager providerManager, IProviderRepository providerRepo, IFileSystem fileSystem, IUserDataManager userDataManager, ILibraryManager libraryManager) : base(serverConfigurationManager, logger, providerManager, providerRepo, fileSystem, userDataManager, libraryManager)
+ protected override void MergeData(MetadataResult<Year> source, MetadataResult<Year> target, List<MetadataFields> lockedFields, bool replaceData, bool mergeMetadataSettings)
{
+ ProviderUtils.MergeBaseItemData(source, target, lockedFields, replaceData, mergeMetadataSettings);
}
- protected override void MergeData(MetadataResult<Year> source, MetadataResult<Year> target, List<MetadataFields> lockedFields, bool replaceData, bool mergeMetadataSettings)
+ public YearMetadataService(IServerConfigurationManager serverConfigurationManager, ILogger logger, IProviderManager providerManager, IFileSystem fileSystem, IUserDataManager userDataManager, ILibraryManager libraryManager) : base(serverConfigurationManager, logger, providerManager, fileSystem, userDataManager, libraryManager)
{
- ProviderUtils.MergeBaseItemData(source, target, lockedFields, replaceData, mergeMetadataSettings);
}
}
}
diff --git a/MediaBrowser.Server.Implementations/Activity/ActivityRepository.cs b/MediaBrowser.Server.Implementations/Activity/ActivityRepository.cs
index b0e05a5bc..c992def39 100644
--- a/MediaBrowser.Server.Implementations/Activity/ActivityRepository.cs
+++ b/MediaBrowser.Server.Implementations/Activity/ActivityRepository.cs
@@ -15,54 +15,26 @@ namespace MediaBrowser.Server.Implementations.Activity
{
public class ActivityRepository : BaseSqliteRepository, IActivityRepository
{
- private IDbConnection _connection;
- private readonly IServerApplicationPaths _appPaths;
private readonly CultureInfo _usCulture = new CultureInfo("en-US");
- private IDbCommand _saveActivityCommand;
-
- public ActivityRepository(ILogManager logManager, IServerApplicationPaths appPaths)
- : base(logManager)
+ public ActivityRepository(ILogManager logManager, IServerApplicationPaths appPaths, IDbConnector connector)
+ : base(logManager, connector)
{
- _appPaths = appPaths;
+ DbFilePath = Path.Combine(appPaths.DataPath, "activitylog.db");
}
- public async Task Initialize(IDbConnector dbConnector)
+ public async Task Initialize()
{
- var dbFile = Path.Combine(_appPaths.DataPath, "activitylog.db");
-
- _connection = await dbConnector.Connect(dbFile).ConfigureAwait(false);
-
- string[] queries = {
+ using (var connection = await CreateConnection().ConfigureAwait(false))
+ {
+ string[] queries = {
"create table if not exists ActivityLogEntries (Id GUID PRIMARY KEY, Name TEXT, Overview TEXT, ShortOverview TEXT, Type TEXT, ItemId TEXT, UserId TEXT, DateCreated DATETIME, LogSeverity TEXT)",
- "create index if not exists idx_ActivityLogEntries on ActivityLogEntries(Id)",
-
- //pragmas
- "pragma temp_store = memory",
-
- "pragma shrink_memory"
+ "create index if not exists idx_ActivityLogEntries on ActivityLogEntries(Id)"
};
- _connection.RunQueries(queries, Logger);
-
- PrepareStatements();
- }
-
- private void PrepareStatements()
- {
- _saveActivityCommand = _connection.CreateCommand();
- _saveActivityCommand.CommandText = "replace into ActivityLogEntries (Id, Name, Overview, ShortOverview, Type, ItemId, UserId, DateCreated, LogSeverity) values (@Id, @Name, @Overview, @ShortOverview, @Type, @ItemId, @UserId, @DateCreated, @LogSeverity)";
-
- _saveActivityCommand.Parameters.Add(_saveActivityCommand, "@Id");
- _saveActivityCommand.Parameters.Add(_saveActivityCommand, "@Name");
- _saveActivityCommand.Parameters.Add(_saveActivityCommand, "@Overview");
- _saveActivityCommand.Parameters.Add(_saveActivityCommand, "@ShortOverview");
- _saveActivityCommand.Parameters.Add(_saveActivityCommand, "@Type");
- _saveActivityCommand.Parameters.Add(_saveActivityCommand, "@ItemId");
- _saveActivityCommand.Parameters.Add(_saveActivityCommand, "@UserId");
- _saveActivityCommand.Parameters.Add(_saveActivityCommand, "@DateCreated");
- _saveActivityCommand.Parameters.Add(_saveActivityCommand, "@LogSeverity");
+ connection.RunQueries(queries, Logger);
+ }
}
private const string BaseActivitySelectText = "select Id, Name, Overview, ShortOverview, Type, ItemId, UserId, DateCreated, LogSeverity from ActivityLogEntries";
@@ -79,128 +51,145 @@ namespace MediaBrowser.Server.Implementations.Activity
throw new ArgumentNullException("entry");
}
- await WriteLock.WaitAsync().ConfigureAwait(false);
+ using (var connection = await CreateConnection().ConfigureAwait(false))
+ {
+ using (var saveActivityCommand = connection.CreateCommand())
+ {
+ saveActivityCommand.CommandText = "replace into ActivityLogEntries (Id, Name, Overview, ShortOverview, Type, ItemId, UserId, DateCreated, LogSeverity) values (@Id, @Name, @Overview, @ShortOverview, @Type, @ItemId, @UserId, @DateCreated, @LogSeverity)";
- IDbTransaction transaction = null;
+ saveActivityCommand.Parameters.Add(saveActivityCommand, "@Id");
+ saveActivityCommand.Parameters.Add(saveActivityCommand, "@Name");
+ saveActivityCommand.Parameters.Add(saveActivityCommand, "@Overview");
+ saveActivityCommand.Parameters.Add(saveActivityCommand, "@ShortOverview");
+ saveActivityCommand.Parameters.Add(saveActivityCommand, "@Type");
+ saveActivityCommand.Parameters.Add(saveActivityCommand, "@ItemId");
+ saveActivityCommand.Parameters.Add(saveActivityCommand, "@UserId");
+ saveActivityCommand.Parameters.Add(saveActivityCommand, "@DateCreated");
+ saveActivityCommand.Parameters.Add(saveActivityCommand, "@LogSeverity");
- try
- {
- transaction = _connection.BeginTransaction();
+ IDbTransaction transaction = null;
- var index = 0;
+ try
+ {
+ transaction = connection.BeginTransaction();
- _saveActivityCommand.GetParameter(index++).Value = new Guid(entry.Id);
- _saveActivityCommand.GetParameter(index++).Value = entry.Name;
- _saveActivityCommand.GetParameter(index++).Value = entry.Overview;
- _saveActivityCommand.GetParameter(index++).Value = entry.ShortOverview;
- _saveActivityCommand.GetParameter(index++).Value = entry.Type;
- _saveActivityCommand.GetParameter(index++).Value = entry.ItemId;
- _saveActivityCommand.GetParameter(index++).Value = entry.UserId;
- _saveActivityCommand.GetParameter(index++).Value = entry.Date;
- _saveActivityCommand.GetParameter(index++).Value = entry.Severity.ToString();
+ var index = 0;
- _saveActivityCommand.Transaction = transaction;
+ saveActivityCommand.GetParameter(index++).Value = new Guid(entry.Id);
+ saveActivityCommand.GetParameter(index++).Value = entry.Name;
+ saveActivityCommand.GetParameter(index++).Value = entry.Overview;
+ saveActivityCommand.GetParameter(index++).Value = entry.ShortOverview;
+ saveActivityCommand.GetParameter(index++).Value = entry.Type;
+ saveActivityCommand.GetParameter(index++).Value = entry.ItemId;
+ saveActivityCommand.GetParameter(index++).Value = entry.UserId;
+ saveActivityCommand.GetParameter(index++).Value = entry.Date;
+ saveActivityCommand.GetParameter(index++).Value = entry.Severity.ToString();
- _saveActivityCommand.ExecuteNonQuery();
+ saveActivityCommand.Transaction = transaction;
- transaction.Commit();
- }
- catch (OperationCanceledException)
- {
- if (transaction != null)
- {
- transaction.Rollback();
- }
+ saveActivityCommand.ExecuteNonQuery();
- throw;
- }
- catch (Exception e)
- {
- Logger.ErrorException("Failed to save record:", e);
+ transaction.Commit();
+ }
+ catch (OperationCanceledException)
+ {
+ if (transaction != null)
+ {
+ transaction.Rollback();
+ }
- if (transaction != null)
- {
- transaction.Rollback();
- }
+ throw;
+ }
+ catch (Exception e)
+ {
+ Logger.ErrorException("Failed to save record:", e);
- throw;
- }
- finally
- {
- if (transaction != null)
- {
- transaction.Dispose();
- }
+ if (transaction != null)
+ {
+ transaction.Rollback();
+ }
- WriteLock.Release();
+ throw;
+ }
+ finally
+ {
+ if (transaction != null)
+ {
+ transaction.Dispose();
+ }
+ }
+ }
}
}
public QueryResult<ActivityLogEntry> GetActivityLogEntries(DateTime? minDate, int? startIndex, int? limit)
{
- using (var cmd = _connection.CreateCommand())
+ using (var connection = CreateConnection(true).Result)
{
- cmd.CommandText = BaseActivitySelectText;
-
- var whereClauses = new List<string>();
-
- if (minDate.HasValue)
+ using (var cmd = connection.CreateCommand())
{
- whereClauses.Add("DateCreated>=@DateCreated");
- cmd.Parameters.Add(cmd, "@DateCreated", DbType.Date).Value = minDate.Value;
- }
+ cmd.CommandText = BaseActivitySelectText;
- var whereTextWithoutPaging = whereClauses.Count == 0 ?
- string.Empty :
- " where " + string.Join(" AND ", whereClauses.ToArray());
+ var whereClauses = new List<string>();
- if (startIndex.HasValue && startIndex.Value > 0)
- {
- var pagingWhereText = whereClauses.Count == 0 ?
+ if (minDate.HasValue)
+ {
+ whereClauses.Add("DateCreated>=@DateCreated");
+ cmd.Parameters.Add(cmd, "@DateCreated", DbType.Date).Value = minDate.Value;
+ }
+
+ var whereTextWithoutPaging = whereClauses.Count == 0 ?
string.Empty :
" where " + string.Join(" AND ", whereClauses.ToArray());
-
- whereClauses.Add(string.Format("Id NOT IN (SELECT Id FROM ActivityLogEntries {0} ORDER BY DateCreated DESC LIMIT {1})",
- pagingWhereText,
- startIndex.Value.ToString(_usCulture)));
- }
- var whereText = whereClauses.Count == 0 ?
- string.Empty :
- " where " + string.Join(" AND ", whereClauses.ToArray());
-
- cmd.CommandText += whereText;
+ if (startIndex.HasValue && startIndex.Value > 0)
+ {
+ var pagingWhereText = whereClauses.Count == 0 ?
+ string.Empty :
+ " where " + string.Join(" AND ", whereClauses.ToArray());
- cmd.CommandText += " ORDER BY DateCreated DESC";
+ whereClauses.Add(string.Format("Id NOT IN (SELECT Id FROM ActivityLogEntries {0} ORDER BY DateCreated DESC LIMIT {1})",
+ pagingWhereText,
+ startIndex.Value.ToString(_usCulture)));
+ }
- if (limit.HasValue)
- {
- cmd.CommandText += " LIMIT " + limit.Value.ToString(_usCulture);
- }
+ var whereText = whereClauses.Count == 0 ?
+ string.Empty :
+ " where " + string.Join(" AND ", whereClauses.ToArray());
- cmd.CommandText += "; select count (Id) from ActivityLogEntries" + whereTextWithoutPaging;
+ cmd.CommandText += whereText;
- var list = new List<ActivityLogEntry>();
- var count = 0;
+ cmd.CommandText += " ORDER BY DateCreated DESC";
- using (var reader = cmd.ExecuteReader(CommandBehavior.SequentialAccess))
- {
- while (reader.Read())
+ if (limit.HasValue)
{
- list.Add(GetEntry(reader));
+ cmd.CommandText += " LIMIT " + limit.Value.ToString(_usCulture);
}
- if (reader.NextResult() && reader.Read())
+ cmd.CommandText += "; select count (Id) from ActivityLogEntries" + whereTextWithoutPaging;
+
+ var list = new List<ActivityLogEntry>();
+ var count = 0;
+
+ using (var reader = cmd.ExecuteReader(CommandBehavior.SequentialAccess))
{
- count = reader.GetInt32(0);
+ while (reader.Read())
+ {
+ list.Add(GetEntry(reader));
+ }
+
+ if (reader.NextResult() && reader.Read())
+ {
+ count = reader.GetInt32(0);
+ }
}
- }
- return new QueryResult<ActivityLogEntry>()
- {
- Items = list.ToArray(),
- TotalRecordCount = count
- };
+ return new QueryResult<ActivityLogEntry>()
+ {
+ Items = list.ToArray(),
+ TotalRecordCount = count
+ };
+ }
}
}
@@ -260,19 +249,5 @@ namespace MediaBrowser.Server.Implementations.Activity
return info;
}
-
- protected override void CloseConnection()
- {
- if (_connection != null)
- {
- if (_connection.IsOpen())
- {
- _connection.Close();
- }
-
- _connection.Dispose();
- _connection = null;
- }
- }
}
}
diff --git a/MediaBrowser.Server.Implementations/Channels/ChannelManager.cs b/MediaBrowser.Server.Implementations/Channels/ChannelManager.cs
index 6a9842cb2..41592865c 100644
--- a/MediaBrowser.Server.Implementations/Channels/ChannelManager.cs
+++ b/MediaBrowser.Server.Implementations/Channels/ChannelManager.cs
@@ -191,7 +191,7 @@ namespace MediaBrowser.Server.Implementations.Channels
var dtoOptions = new DtoOptions();
- var returnItems = _dtoService.GetBaseItemDtos(internalResult.Items, dtoOptions, user)
+ var returnItems = (await _dtoService.GetBaseItemDtos(internalResult.Items, dtoOptions, user).ConfigureAwait(false))
.ToArray();
var result = new QueryResult<BaseItemDto>
@@ -596,7 +596,7 @@ namespace MediaBrowser.Server.Implementations.Channels
var dtoOptions = new DtoOptions();
- var returnItems = _dtoService.GetBaseItemDtos(items, dtoOptions, user)
+ var returnItems = (await _dtoService.GetBaseItemDtos(items, dtoOptions, user).ConfigureAwait(false))
.ToArray();
var result = new QueryResult<BaseItemDto>
@@ -863,7 +863,7 @@ namespace MediaBrowser.Server.Implementations.Channels
var dtoOptions = new DtoOptions();
- var returnItems = _dtoService.GetBaseItemDtos(internalResult.Items, dtoOptions, user)
+ var returnItems = (await _dtoService.GetBaseItemDtos(internalResult.Items, dtoOptions, user).ConfigureAwait(false))
.ToArray();
var result = new QueryResult<BaseItemDto>
@@ -1012,7 +1012,7 @@ namespace MediaBrowser.Server.Implementations.Channels
var dtoOptions = new DtoOptions();
- var returnItems = _dtoService.GetBaseItemDtos(internalResult.Items, dtoOptions, user)
+ var returnItems = (await _dtoService.GetBaseItemDtos(internalResult.Items, dtoOptions, user).ConfigureAwait(false))
.ToArray();
var result = new QueryResult<BaseItemDto>
@@ -1172,8 +1172,7 @@ namespace MediaBrowser.Server.Implementations.Channels
{
items = ApplyFilters(items, query.Filters, user);
- var sortBy = query.SortBy.Length == 0 ? new[] { ItemSortBy.SortName } : query.SortBy;
- items = _libraryManager.Sort(items, user, sortBy, query.SortOrder ?? SortOrder.Ascending);
+ items = _libraryManager.Sort(items, user, query.SortBy, query.SortOrder ?? SortOrder.Ascending);
var all = items.ToList();
var totalCount = totalCountFromProvider ?? all.Count;
@@ -1250,10 +1249,22 @@ namespace MediaBrowser.Server.Implementations.Channels
{
item = GetItemById<MusicAlbum>(info.Id, channelProvider.Name, channelProvider.DataVersion, out isNew);
}
+ else if (info.FolderType == ChannelFolderType.MusicArtist)
+ {
+ item = GetItemById<MusicArtist>(info.Id, channelProvider.Name, channelProvider.DataVersion, out isNew);
+ }
else if (info.FolderType == ChannelFolderType.PhotoAlbum)
{
item = GetItemById<PhotoAlbum>(info.Id, channelProvider.Name, channelProvider.DataVersion, out isNew);
}
+ else if (info.FolderType == ChannelFolderType.Series)
+ {
+ item = GetItemById<Series>(info.Id, channelProvider.Name, channelProvider.DataVersion, out isNew);
+ }
+ else if (info.FolderType == ChannelFolderType.Season)
+ {
+ item = GetItemById<Season>(info.Id, channelProvider.Name, channelProvider.DataVersion, out isNew);
+ }
else
{
item = GetItemById<Folder>(info.Id, channelProvider.Name, channelProvider.DataVersion, out isNew);
@@ -1307,6 +1318,28 @@ namespace MediaBrowser.Server.Implementations.Channels
item.OfficialRating = info.OfficialRating;
item.DateCreated = info.DateCreated ?? DateTime.UtcNow;
item.Tags = info.Tags;
+ item.HomePageUrl = info.HomePageUrl;
+ }
+ else if (info.Type == ChannelItemType.Folder && info.FolderType == ChannelFolderType.Container)
+ {
+ // At least update names of container folders
+ if (item.Name != info.Name)
+ {
+ item.Name = info.Name;
+ forceUpdate = true;
+ }
+ }
+
+ var hasArtists = item as IHasArtist;
+ if (hasArtists != null)
+ {
+ hasArtists.Artists = info.Artists;
+ }
+
+ var hasAlbumArtists = item as IHasAlbumArtist;
+ if (hasAlbumArtists != null)
+ {
+ hasAlbumArtists.AlbumArtists = info.AlbumArtists;
}
var trailer = item as Trailer;
diff --git a/MediaBrowser.Server.Implementations/Collections/ManualCollectionsFolder.cs b/MediaBrowser.Server.Implementations/Collections/ManualCollectionsFolder.cs
index 561d46229..3e33066ae 100644
--- a/MediaBrowser.Server.Implementations/Collections/ManualCollectionsFolder.cs
+++ b/MediaBrowser.Server.Implementations/Collections/ManualCollectionsFolder.cs
@@ -20,7 +20,7 @@ namespace MediaBrowser.Server.Implementations.Collections
public bool IsHiddenFromUser(User user)
{
- return !user.Configuration.DisplayCollectionsView;
+ return !ConfigurationManager.Configuration.DisplayCollectionsView;
}
public override string CollectionType
diff --git a/MediaBrowser.Server.Implementations/Connect/ConnectEntryPoint.cs b/MediaBrowser.Server.Implementations/Connect/ConnectEntryPoint.cs
index ea12e332d..28a62c012 100644
--- a/MediaBrowser.Server.Implementations/Connect/ConnectEntryPoint.cs
+++ b/MediaBrowser.Server.Implementations/Connect/ConnectEntryPoint.cs
@@ -41,14 +41,15 @@ namespace MediaBrowser.Server.Implementations.Connect
public void Run()
{
- Task.Run(() => LoadCachedAddress());
+ LoadCachedAddress();
_timer = new PeriodicTimer(TimerCallback, null, TimeSpan.FromSeconds(5), TimeSpan.FromHours(3));
+ ((ConnectManager)_connectManager).Start();
}
private readonly string[] _ipLookups =
{
- "http://bot.whatismyipaddress.com",
+ "http://bot.whatismyipaddress.com",
"https://connect.emby.media/service/ip"
};
@@ -78,17 +79,18 @@ namespace MediaBrowser.Server.Implementations.Connect
}
// If this produced an ipv6 address, try again
- if (validIpAddress == null || validIpAddress.AddressFamily == AddressFamily.InterNetworkV6)
+ if (validIpAddress != null && validIpAddress.AddressFamily == AddressFamily.InterNetworkV6)
{
foreach (var ipLookupUrl in _ipLookups)
{
try
{
- validIpAddress = await GetIpAddress(ipLookupUrl, true).ConfigureAwait(false);
+ var newAddress = await GetIpAddress(ipLookupUrl, true).ConfigureAwait(false);
// Try to find the ipv4 address, if present
- if (validIpAddress.AddressFamily == AddressFamily.InterNetwork)
+ if (newAddress.AddressFamily == AddressFamily.InterNetwork)
{
+ validIpAddress = newAddress;
break;
}
}
@@ -162,6 +164,8 @@ namespace MediaBrowser.Server.Implementations.Connect
{
var path = CacheFilePath;
+ _logger.Info("Loading data from {0}", path);
+
try
{
var endpoint = _fileSystem.ReadAllText(path, Encoding.UTF8);
diff --git a/MediaBrowser.Server.Implementations/Connect/ConnectManager.cs b/MediaBrowser.Server.Implementations/Connect/ConnectManager.cs
index f3d545492..24750de94 100644
--- a/MediaBrowser.Server.Implementations/Connect/ConnectManager.cs
+++ b/MediaBrowser.Server.Implementations/Connect/ConnectManager.cs
@@ -139,11 +139,14 @@ namespace MediaBrowser.Server.Implementations.Connect
_securityManager = securityManager;
_fileSystem = fileSystem;
- _config.ConfigurationUpdated += _config_ConfigurationUpdated;
-
LoadCachedData();
}
+ internal void Start()
+ {
+ _config.ConfigurationUpdated += _config_ConfigurationUpdated;
+ }
+
internal void OnWanAddressResolved(IPAddress address)
{
DiscoveredWanIpAddress = address;
@@ -177,7 +180,7 @@ namespace MediaBrowser.Server.Implementations.Connect
try
{
- var localAddress = _appHost.LocalApiUrl;
+ var localAddress = await _appHost.GetLocalApiUrl().ConfigureAwait(false);
var hasExistingRecord = !string.IsNullOrWhiteSpace(ConnectServerId) &&
!string.IsNullOrWhiteSpace(ConnectAccessKey);
@@ -217,24 +220,26 @@ namespace MediaBrowser.Server.Implementations.Connect
}
private string _lastReportedIdentifier;
- private string GetConnectReportingIdentifier()
+ private async Task<string> GetConnectReportingIdentifier()
{
- return GetConnectReportingIdentifier(_appHost.LocalApiUrl, WanApiAddress);
+ var url = await _appHost.GetLocalApiUrl().ConfigureAwait(false);
+ return GetConnectReportingIdentifier(url, WanApiAddress);
}
private string GetConnectReportingIdentifier(string localAddress, string remoteAddress)
{
return (remoteAddress ?? string.Empty) + (localAddress ?? string.Empty);
}
- void _config_ConfigurationUpdated(object sender, EventArgs e)
+ async void _config_ConfigurationUpdated(object sender, EventArgs e)
{
// If info hasn't changed, don't report anything
- if (string.Equals(_lastReportedIdentifier, GetConnectReportingIdentifier(), StringComparison.OrdinalIgnoreCase))
+ var connectIdentifier = await GetConnectReportingIdentifier().ConfigureAwait(false);
+ if (string.Equals(_lastReportedIdentifier, connectIdentifier, StringComparison.OrdinalIgnoreCase))
{
return;
}
- UpdateConnectInfo();
+ await UpdateConnectInfo().ConfigureAwait(false);
}
private async Task CreateServerRegistration(string wanApiAddress, string localAddress)
@@ -357,6 +362,8 @@ namespace MediaBrowser.Server.Implementations.Connect
{
var path = CacheFilePath;
+ _logger.Info("Loading data from {0}", path);
+
try
{
lock (_dataFileLock)
diff --git a/MediaBrowser.Server.Implementations/Connect/Responses.cs b/MediaBrowser.Server.Implementations/Connect/Responses.cs
index e7c3f8154..f86527829 100644
--- a/MediaBrowser.Server.Implementations/Connect/Responses.cs
+++ b/MediaBrowser.Server.Implementations/Connect/Responses.cs
@@ -60,7 +60,6 @@ namespace MediaBrowser.Server.Implementations.Connect
{
return new ConnectUserPreferences
{
- GroupMoviesIntoBoxSets = config.GroupMoviesIntoBoxSets,
PlayDefaultAudioTrack = config.PlayDefaultAudioTrack,
SubtitleMode = config.SubtitleMode,
PreferredAudioLanguages = string.IsNullOrWhiteSpace(config.AudioLanguagePreference) ? new string[] { } : new[] { config.AudioLanguagePreference },
diff --git a/MediaBrowser.Server.Implementations/Devices/CameraUploadsFolder.cs b/MediaBrowser.Server.Implementations/Devices/CameraUploadsFolder.cs
index 947933561..3dfc04c26 100644
--- a/MediaBrowser.Server.Implementations/Devices/CameraUploadsFolder.cs
+++ b/MediaBrowser.Server.Implementations/Devices/CameraUploadsFolder.cs
@@ -28,6 +28,7 @@ namespace MediaBrowser.Server.Implementations.Devices
return base.IsVisible(user) && HasChildren();
}
+ [IgnoreDataMember]
public override string CollectionType
{
get { return Model.Entities.CollectionType.Photos; }
diff --git a/MediaBrowser.Server.Implementations/Devices/DeviceManager.cs b/MediaBrowser.Server.Implementations/Devices/DeviceManager.cs
index 6b1af8d2d..c3db9140c 100644
--- a/MediaBrowser.Server.Implementations/Devices/DeviceManager.cs
+++ b/MediaBrowser.Server.Implementations/Devices/DeviceManager.cs
@@ -51,6 +51,11 @@ namespace MediaBrowser.Server.Implementations.Devices
public async Task<DeviceInfo> RegisterDevice(string reportedId, string name, string appName, string appVersion, string usedByUserId)
{
+ if (string.IsNullOrWhiteSpace(reportedId))
+ {
+ throw new ArgumentNullException("reportedId");
+ }
+
var device = GetDevice(reportedId) ?? new DeviceInfo
{
Id = reportedId
diff --git a/MediaBrowser.Server.Implementations/Devices/DeviceRepository.cs b/MediaBrowser.Server.Implementations/Devices/DeviceRepository.cs
index 368d21322..6e67af82b 100644
--- a/MediaBrowser.Server.Implementations/Devices/DeviceRepository.cs
+++ b/MediaBrowser.Server.Implementations/Devices/DeviceRepository.cs
@@ -23,7 +23,7 @@ namespace MediaBrowser.Server.Implementations.Devices
private readonly ILogger _logger;
private readonly IFileSystem _fileSystem;
- private List<DeviceInfo> _devices;
+ private Dictionary<string, DeviceInfo> _devices;
public DeviceRepository(IApplicationPaths appPaths, IJsonSerializer json, ILogger logger, IFileSystem fileSystem)
{
@@ -46,12 +46,12 @@ namespace MediaBrowser.Server.Implementations.Devices
public Task SaveDevice(DeviceInfo device)
{
var path = Path.Combine(GetDevicePath(device.Id), "device.json");
- _fileSystem.CreateDirectory(Path.GetDirectoryName(path));
+ _fileSystem.CreateDirectory(Path.GetDirectoryName(path));
lock (_syncLock)
{
_json.SerializeToFile(device, path);
- _devices = null;
+ _devices[device.Id] = device;
}
return Task.FromResult(true);
}
@@ -95,9 +95,15 @@ namespace MediaBrowser.Server.Implementations.Devices
{
if (_devices == null)
{
- _devices = LoadDevices().ToList();
+ _devices = new Dictionary<string, DeviceInfo>(StringComparer.OrdinalIgnoreCase);
+
+ var devices = LoadDevices().ToList();
+ foreach (var device in devices)
+ {
+ _devices[device.Id] = device;
+ }
}
- return _devices.ToList();
+ return _devices.Values.ToList();
}
}
@@ -144,7 +150,7 @@ namespace MediaBrowser.Server.Implementations.Devices
catch (DirectoryNotFoundException)
{
}
-
+
_devices = null;
}
@@ -174,7 +180,7 @@ namespace MediaBrowser.Server.Implementations.Devices
public void AddCameraUpload(string deviceId, LocalFileInfo file)
{
var path = Path.Combine(GetDevicePath(deviceId), "camerauploads.json");
- _fileSystem.CreateDirectory(Path.GetDirectoryName(path));
+ _fileSystem.CreateDirectory(Path.GetDirectoryName(path));
lock (_syncLock)
{
diff --git a/MediaBrowser.Server.Implementations/Dto/DtoService.cs b/MediaBrowser.Server.Implementations/Dto/DtoService.cs
index dfbac47d5..8805d567a 100644
--- a/MediaBrowser.Server.Implementations/Dto/DtoService.cs
+++ b/MediaBrowser.Server.Implementations/Dto/DtoService.cs
@@ -86,8 +86,18 @@ namespace MediaBrowser.Server.Implementations.Dto
return GetBaseItemDto(item, options, user, owner);
}
- public IEnumerable<BaseItemDto> GetBaseItemDtos(IEnumerable<BaseItem> items, DtoOptions options, User user = null, BaseItem owner = null)
+ public async Task<List<BaseItemDto>> GetBaseItemDtos(IEnumerable<BaseItem> items, DtoOptions options, User user = null, BaseItem owner = null)
{
+ if (items == null)
+ {
+ throw new ArgumentNullException("items");
+ }
+
+ if (options == null)
+ {
+ throw new ArgumentNullException("options");
+ }
+
var syncJobItems = GetSyncedItemProgress(options);
var syncDictionary = GetSyncedItemProgressDictionary(syncJobItems);
@@ -97,7 +107,7 @@ namespace MediaBrowser.Server.Implementations.Dto
foreach (var item in items)
{
- var dto = GetBaseItemDtoInternal(item, options, syncDictionary, user, owner);
+ var dto = await GetBaseItemDtoInternal(item, options, syncDictionary, user, owner).ConfigureAwait(false);
var tvChannel = item as LiveTvChannel;
if (tvChannel != null)
@@ -131,8 +141,7 @@ namespace MediaBrowser.Server.Implementations.Dto
if (programTuples.Count > 0)
{
- var task = _livetvManager().AddInfoToProgramDto(programTuples, options.Fields, user);
- Task.WaitAll(task);
+ await _livetvManager().AddInfoToProgramDto(programTuples, options.Fields, user).ConfigureAwait(false);
}
if (channelTuples.Count > 0)
@@ -159,7 +168,7 @@ namespace MediaBrowser.Server.Implementations.Dto
{
var syncProgress = GetSyncedItemProgress(options);
- var dto = GetBaseItemDtoInternal(item, options, GetSyncedItemProgressDictionary(syncProgress), user, owner);
+ var dto = GetBaseItemDtoInternal(item, options, GetSyncedItemProgressDictionary(syncProgress), user, owner).Result;
var tvChannel = item as LiveTvChannel;
if (tvChannel != null)
{
@@ -300,7 +309,7 @@ namespace MediaBrowser.Server.Implementations.Dto
}
}
- private BaseItemDto GetBaseItemDtoInternal(BaseItem item, DtoOptions options, Dictionary<string, SyncedItemProgress> syncProgress, User user = null, BaseItem owner = null)
+ private async Task<BaseItemDto> GetBaseItemDtoInternal(BaseItem item, DtoOptions options, Dictionary<string, SyncedItemProgress> syncProgress, User user = null, BaseItem owner = null)
{
var fields = options.Fields;
@@ -349,7 +358,7 @@ namespace MediaBrowser.Server.Implementations.Dto
if (user != null)
{
- AttachUserSpecificInfo(dto, item, user, fields, syncProgress);
+ await AttachUserSpecificInfo(dto, item, user, fields, syncProgress).ConfigureAwait(false);
}
var hasMediaSources = item as IHasMediaSources;
@@ -416,9 +425,9 @@ namespace MediaBrowser.Server.Implementations.Dto
{
var syncProgress = GetSyncedItemProgress(options);
- var dto = GetBaseItemDtoInternal(item, options, GetSyncedItemProgressDictionary(syncProgress), user);
+ var dto = GetBaseItemDtoInternal(item, options, GetSyncedItemProgressDictionary(syncProgress), user).Result;
- if (options.Fields.Contains(ItemFields.ItemCounts))
+ if (taggedItems != null && options.Fields.Contains(ItemFields.ItemCounts))
{
SetItemByNameInfo(item, dto, taggedItems, user);
}
@@ -465,26 +474,31 @@ namespace MediaBrowser.Server.Implementations.Dto
/// <param name="user">The user.</param>
/// <param name="fields">The fields.</param>
/// <param name="syncProgress">The synchronize progress.</param>
- private void AttachUserSpecificInfo(BaseItemDto dto, BaseItem item, User user, List<ItemFields> fields, Dictionary<string, SyncedItemProgress> syncProgress)
+ private async Task AttachUserSpecificInfo(BaseItemDto dto, BaseItem item, User user, List<ItemFields> fields, Dictionary<string, SyncedItemProgress> syncProgress)
{
if (item.IsFolder)
{
- var userData = _userDataRepository.GetUserData(user, item);
+ var folder = (Folder)item;
- // Skip the user data manager because we've already looped through the recursive tree and don't want to do it twice
- // TODO: Improve in future
- dto.UserData = GetUserItemDataDto(userData);
+ if (item.SourceType == SourceType.Library && folder.SupportsUserDataFromChildren && fields.Contains(ItemFields.SyncInfo))
+ {
+ // Skip the user data manager because we've already looped through the recursive tree and don't want to do it twice
+ // TODO: Improve in future
+ dto.UserData = GetUserItemDataDto(_userDataRepository.GetUserData(user, item));
- var folder = (Folder)item;
+ await SetSpecialCounts(folder, user, dto, fields, syncProgress).ConfigureAwait(false);
+
+ dto.UserData.Played = dto.UserData.PlayedPercentage.HasValue &&
+ dto.UserData.PlayedPercentage.Value >= 100;
+ }
+ else
+ {
+ dto.UserData = await _userDataRepository.GetUserDataDto(item, dto, user).ConfigureAwait(false);
+ }
if (item.SourceType == SourceType.Library)
{
dto.ChildCount = GetChildCount(folder, user);
-
- if (folder.SupportsUserDataFromChildren)
- {
- SetSpecialCounts(folder, user, dto, fields, syncProgress);
- }
}
if (fields.Contains(ItemFields.CumulativeRunTimeTicks))
@@ -496,13 +510,11 @@ namespace MediaBrowser.Server.Implementations.Dto
{
dto.DateLastMediaAdded = folder.DateLastMediaAdded;
}
-
- dto.UserData.Played = dto.UserData.PlayedPercentage.HasValue && dto.UserData.PlayedPercentage.Value >= 100;
}
else
{
- dto.UserData = _userDataRepository.GetUserDataDto(item, user);
+ dto.UserData = _userDataRepository.GetUserDataDto(item, user).Result;
}
dto.PlayAccess = item.GetPlayAccess(user);
@@ -517,7 +529,7 @@ namespace MediaBrowser.Server.Implementations.Dto
if (season != null)
{
- dto.SeasonUserData = _userDataRepository.GetUserDataDto(season, user);
+ dto.SeasonUserData = await _userDataRepository.GetUserDataDto(season, user).ConfigureAwait(false);
}
}
}
@@ -537,8 +549,14 @@ namespace MediaBrowser.Server.Implementations.Dto
private int GetChildCount(Folder folder, User user)
{
- return folder.GetChildren(user, true)
- .Count();
+ // Right now this is too slow to calculate for top level folders on a per-user basis
+ // Just return something so that apps that are expecting a value won't think the folders are empty
+ if (folder is ICollectionFolder || folder is UserView)
+ {
+ return new Random().Next(1, 10);
+ }
+
+ return folder.GetChildCount(user);
}
/// <summary>
@@ -969,30 +987,12 @@ namespace MediaBrowser.Server.Implementations.Dto
if (fields.Contains(ItemFields.Tags))
{
- var hasTags = item as IHasTags;
- if (hasTags != null)
- {
- dto.Tags = hasTags.Tags;
- }
-
- if (dto.Tags == null)
- {
- dto.Tags = new List<string>();
- }
+ dto.Tags = item.Tags;
}
if (fields.Contains(ItemFields.Keywords))
{
- var hasTags = item as IHasKeywords;
- if (hasTags != null)
- {
- dto.Keywords = hasTags.Keywords;
- }
-
- if (dto.Keywords == null)
- {
- dto.Keywords = new List<string>();
- }
+ dto.Keywords = item.Keywords;
}
if (fields.Contains(ItemFields.ProductionLocations))
@@ -1286,26 +1286,22 @@ namespace MediaBrowser.Server.Implementations.Dto
{
dto.Artists = hasArtist.Artists;
- dto.ArtistItems = hasArtist
- .Artists
+ var artistItems = _libraryManager.GetArtists(new InternalItemsQuery
+ {
+ EnableTotalRecordCount = false,
+ ItemIds = new[] { item.Id.ToString("N") }
+ });
+
+ dto.ArtistItems = artistItems.Items
.Select(i =>
{
- try
+ var artist = i.Item1;
+ return new NameIdPair
{
- var artist = _libraryManager.GetArtist(i);
- return new NameIdPair
- {
- Name = artist.Name,
- Id = artist.Id.ToString("N")
- };
- }
- catch (Exception ex)
- {
- _logger.ErrorException("Error getting artist", ex);
- return null;
- }
+ Name = artist.Name,
+ Id = artist.Id.ToString("N")
+ };
})
- .Where(i => i != null)
.ToList();
}
@@ -1314,26 +1310,22 @@ namespace MediaBrowser.Server.Implementations.Dto
{
dto.AlbumArtist = hasAlbumArtist.AlbumArtists.FirstOrDefault();
- dto.AlbumArtists = hasAlbumArtist
- .AlbumArtists
+ var artistItems = _libraryManager.GetAlbumArtists(new InternalItemsQuery
+ {
+ EnableTotalRecordCount = false,
+ ItemIds = new[] { item.Id.ToString("N") }
+ });
+
+ dto.AlbumArtists = artistItems.Items
.Select(i =>
{
- try
- {
- var artist = _libraryManager.GetArtist(i);
- return new NameIdPair
- {
- Name = artist.Name,
- Id = artist.Id.ToString("N")
- };
- }
- catch (Exception ex)
+ var artist = i.Item1;
+ return new NameIdPair
{
- _logger.ErrorException("Error getting album artist", ex);
- return null;
- }
+ Name = artist.Name,
+ Id = artist.Id.ToString("N")
+ };
})
- .Where(i => i != null)
.ToList();
}
@@ -1412,6 +1404,7 @@ namespace MediaBrowser.Server.Implementations.Dto
if (episode != null)
{
dto.IndexNumberEnd = episode.IndexNumberEnd;
+ dto.SeriesName = episode.SeriesName;
if (fields.Contains(ItemFields.AlternateEpisodeNumbers))
{
@@ -1427,74 +1420,65 @@ namespace MediaBrowser.Server.Implementations.Dto
dto.AirsBeforeSeasonNumber = episode.AirsBeforeSeasonNumber;
}
+ var seasonId = episode.SeasonId;
+ if (seasonId.HasValue)
+ {
+ dto.SeasonId = seasonId.Value.ToString("N");
+ }
+
var episodeSeason = episode.Season;
if (episodeSeason != null)
{
- dto.SeasonId = episodeSeason.Id.ToString("N");
-
if (fields.Contains(ItemFields.SeasonName))
{
dto.SeasonName = episodeSeason.Name;
}
}
- if (fields.Contains(ItemFields.SeriesGenres))
+ var episodeSeries = episode.Series;
+
+ if (episodeSeries != null)
{
- var episodeseries = episode.Series;
- if (episodeseries != null)
+ if (fields.Contains(ItemFields.SeriesGenres))
{
- dto.SeriesGenres = episodeseries.Genres.ToList();
+ dto.SeriesGenres = episodeSeries.Genres.ToList();
}
- }
- }
- // Add SeriesInfo
- var series = item as Series;
- if (series != null)
- {
- dto.AirDays = series.AirDays;
- dto.AirTime = series.AirTime;
- dto.SeriesStatus = series.Status;
-
- if (fields.Contains(ItemFields.Settings))
- {
- dto.DisplaySpecialsWithSeasons = series.DisplaySpecialsWithSeasons;
- }
-
- dto.AnimeSeriesIndex = series.AnimeSeriesIndex;
- }
-
- if (episode != null)
- {
- series = episode.Series;
-
- if (series != null)
- {
- dto.SeriesId = GetDtoId(series);
- dto.SeriesName = series.Name;
+ dto.SeriesId = GetDtoId(episodeSeries);
if (fields.Contains(ItemFields.AirTime))
{
- dto.AirTime = series.AirTime;
+ dto.AirTime = episodeSeries.AirTime;
}
if (options.GetImageLimit(ImageType.Thumb) > 0)
{
- dto.SeriesThumbImageTag = GetImageCacheTag(series, ImageType.Thumb);
+ dto.SeriesThumbImageTag = GetImageCacheTag(episodeSeries, ImageType.Thumb);
}
if (options.GetImageLimit(ImageType.Primary) > 0)
{
- dto.SeriesPrimaryImageTag = GetImageCacheTag(series, ImageType.Primary);
+ dto.SeriesPrimaryImageTag = GetImageCacheTag(episodeSeries, ImageType.Primary);
}
if (fields.Contains(ItemFields.SeriesStudio))
{
- dto.SeriesStudio = series.Studios.FirstOrDefault();
+ dto.SeriesStudio = episodeSeries.Studios.FirstOrDefault();
}
}
}
+ // Add SeriesInfo
+ var series = item as Series;
+ if (series != null)
+ {
+ dto.AirDays = series.AirDays;
+ dto.AirTime = series.AirTime;
+ dto.SeriesStatus = series.Status;
+
+ dto.AnimeSeriesIndex = series.AnimeSeriesIndex;
+ }
+
// Add SeasonInfo
var season = item as Season;
if (season != null)
@@ -1610,26 +1594,25 @@ namespace MediaBrowser.Server.Implementations.Dto
/// <param name="fields">The fields.</param>
/// <param name="syncProgress">The synchronize progress.</param>
/// <returns>Task.</returns>
- private void SetSpecialCounts(Folder folder, User user, BaseItemDto dto, List<ItemFields> fields, Dictionary<string, SyncedItemProgress> syncProgress)
+ private async Task SetSpecialCounts(Folder folder, User user, BaseItemDto dto, List<ItemFields> fields, Dictionary<string, SyncedItemProgress> syncProgress)
{
var recursiveItemCount = 0;
var unplayed = 0;
double totalPercentPlayed = 0;
double totalSyncPercent = 0;
- var addSyncInfo = fields.Contains(ItemFields.SyncInfo);
- var children = folder.GetItems(new InternalItemsQuery
+ var children = await folder.GetItems(new InternalItemsQuery
{
IsFolder = false,
Recursive = true,
ExcludeLocationTypes = new[] { LocationType.Virtual },
User = user
- }).Result.Items;
+ }).ConfigureAwait(false);
// Loop through each recursive child
- foreach (var child in children)
+ foreach (var child in children.Items)
{
var userdata = _userDataRepository.GetUserData(user, child);
@@ -1659,26 +1642,23 @@ namespace MediaBrowser.Server.Implementations.Dto
unplayed++;
}
- if (addSyncInfo)
+ double percent = 0;
+ SyncedItemProgress syncItemProgress;
+ if (syncProgress.TryGetValue(child.Id.ToString("N"), out syncItemProgress))
{
- double percent = 0;
- SyncedItemProgress syncItemProgress;
- if (syncProgress.TryGetValue(child.Id.ToString("N"), out syncItemProgress))
+ switch (syncItemProgress.Status)
{
- switch (syncItemProgress.Status)
- {
- case SyncJobItemStatus.Synced:
- percent = 100;
- break;
- case SyncJobItemStatus.Converting:
- case SyncJobItemStatus.ReadyToTransfer:
- case SyncJobItemStatus.Transferring:
- percent = 50;
- break;
- }
+ case SyncJobItemStatus.Synced:
+ percent = 100;
+ break;
+ case SyncJobItemStatus.Converting:
+ case SyncJobItemStatus.ReadyToTransfer:
+ case SyncJobItemStatus.Transferring:
+ percent = 50;
+ break;
}
- totalSyncPercent += percent;
}
+ totalSyncPercent += percent;
}
dto.RecursiveItemCount = recursiveItemCount;
@@ -1688,13 +1668,10 @@ namespace MediaBrowser.Server.Implementations.Dto
{
dto.UserData.PlayedPercentage = totalPercentPlayed / recursiveItemCount;
- if (addSyncInfo)
+ var pct = totalSyncPercent / recursiveItemCount;
+ if (pct > 0)
{
- var pct = totalSyncPercent / recursiveItemCount;
- if (pct > 0)
- {
- dto.SyncPercent = pct;
- }
+ dto.SyncPercent = pct;
}
}
}
diff --git a/MediaBrowser.Server.Implementations/EntryPoints/AutomaticRestartEntryPoint.cs b/MediaBrowser.Server.Implementations/EntryPoints/AutomaticRestartEntryPoint.cs
index df6a9e654..d5f265dda 100644
--- a/MediaBrowser.Server.Implementations/EntryPoints/AutomaticRestartEntryPoint.cs
+++ b/MediaBrowser.Server.Implementations/EntryPoints/AutomaticRestartEntryPoint.cs
@@ -8,6 +8,9 @@ using MediaBrowser.Model.Tasks;
using System;
using System.Linq;
using System.Threading;
+using System.Threading.Tasks;
+using MediaBrowser.Controller.LiveTv;
+using MediaBrowser.Model.LiveTv;
namespace MediaBrowser.Server.Implementations.EntryPoints
{
@@ -18,16 +21,18 @@ namespace MediaBrowser.Server.Implementations.EntryPoints
private readonly ITaskManager _iTaskManager;
private readonly ISessionManager _sessionManager;
private readonly IServerConfigurationManager _config;
+ private readonly ILiveTvManager _liveTvManager;
private Timer _timer;
- public AutomaticRestartEntryPoint(IServerApplicationHost appHost, ILogger logger, ITaskManager iTaskManager, ISessionManager sessionManager, IServerConfigurationManager config)
+ public AutomaticRestartEntryPoint(IServerApplicationHost appHost, ILogger logger, ITaskManager iTaskManager, ISessionManager sessionManager, IServerConfigurationManager config, ILiveTvManager liveTvManager)
{
_appHost = appHost;
_logger = logger;
_iTaskManager = iTaskManager;
_sessionManager = sessionManager;
_config = config;
+ _liveTvManager = liveTvManager;
}
public void Run()
@@ -44,34 +49,55 @@ namespace MediaBrowser.Server.Implementations.EntryPoints
if (_appHost.HasPendingRestart)
{
- _timer = new Timer(TimerCallback, null, TimeSpan.FromMinutes(5), TimeSpan.FromMinutes(5));
+ _timer = new Timer(TimerCallback, null, TimeSpan.FromMinutes(10), TimeSpan.FromMinutes(10));
}
}
- private void TimerCallback(object state)
+ private async void TimerCallback(object state)
{
- if (_config.Configuration.EnableAutomaticRestart && IsIdle())
+ if (_config.Configuration.EnableAutomaticRestart)
{
- DisposeTimer();
+ var isIdle = await IsIdle().ConfigureAwait(false);
- try
- {
- _appHost.Restart();
- }
- catch (Exception ex)
+ if (isIdle)
{
- _logger.ErrorException("Error restarting server", ex);
+ DisposeTimer();
+
+ try
+ {
+ _appHost.Restart();
+ }
+ catch (Exception ex)
+ {
+ _logger.ErrorException("Error restarting server", ex);
+ }
}
}
}
- private bool IsIdle()
+ private async Task<bool> IsIdle()
{
if (_iTaskManager.ScheduledTasks.Any(i => i.State != TaskState.Idle))
{
return false;
}
+ if (_liveTvManager.Services.Count == 1)
+ {
+ try
+ {
+ var timers = await _liveTvManager.GetTimers(new TimerQuery(), CancellationToken.None).ConfigureAwait(false);
+ if (timers.Items.Any(i => i.Status == RecordingStatus.InProgress))
+ {
+ return false;
+ }
+ }
+ catch (Exception ex)
+ {
+ _logger.ErrorException("Error getting timers", ex);
+ }
+ }
+
var now = DateTime.UtcNow;
return !_sessionManager.Sessions.Any(i => (now - i.LastActivityDate).TotalMinutes < 30);
diff --git a/MediaBrowser.Server.Implementations/EntryPoints/ExternalPortForwarding.cs b/MediaBrowser.Server.Implementations/EntryPoints/ExternalPortForwarding.cs
index 5777a0af7..50ad3cfbc 100644
--- a/MediaBrowser.Server.Implementations/EntryPoints/ExternalPortForwarding.cs
+++ b/MediaBrowser.Server.Implementations/EntryPoints/ExternalPortForwarding.cs
@@ -93,7 +93,7 @@ namespace MediaBrowser.Server.Implementations.EntryPoints
NatUtility.UnhandledException += NatUtility_UnhandledException;
NatUtility.StartDiscovery();
- _timer = new PeriodicTimer(s => _createdRules = new List<string>(), null, TimeSpan.FromMinutes(5), TimeSpan.FromMinutes(5));
+ _timer = new PeriodicTimer(ClearCreatedRules, null, TimeSpan.FromMinutes(5), TimeSpan.FromMinutes(5));
_ssdp.MessageReceived += _ssdp_MessageReceived;
@@ -102,12 +102,43 @@ namespace MediaBrowser.Server.Implementations.EntryPoints
_isStarted = true;
}
+ private void ClearCreatedRules(object state)
+ {
+ _createdRules = new List<string>();
+ _usnsHandled = new List<string>();
+ }
+
void _ssdp_MessageReceived(object sender, SsdpMessageEventArgs e)
{
var endpoint = e.EndPoint as IPEndPoint;
- if (endpoint != null && e.LocalEndPoint != null)
+ if (endpoint == null || e.LocalEndPoint == null)
{
+ return;
+ }
+
+ string usn;
+ if (!e.Headers.TryGetValue("USN", out usn)) usn = string.Empty;
+
+ string nt;
+ if (!e.Headers.TryGetValue("NT", out nt)) nt = string.Empty;
+
+ // Filter device type
+ if (usn.IndexOf("WANIPConnection:", StringComparison.OrdinalIgnoreCase) == -1 &&
+ nt.IndexOf("WANIPConnection:", StringComparison.OrdinalIgnoreCase) == -1 &&
+ usn.IndexOf("WANPPPConnection:", StringComparison.OrdinalIgnoreCase) == -1 &&
+ nt.IndexOf("WANPPPConnection:", StringComparison.OrdinalIgnoreCase) == -1)
+ {
+ return;
+ }
+
+ var identifier = string.IsNullOrWhiteSpace(usn) ? nt : usn;
+
+ if (!_usnsHandled.Contains(identifier))
+ {
+ _usnsHandled.Add(identifier);
+
+ _logger.Debug("Calling Nat.Handle on " + identifier);
NatUtility.Handle(e.LocalEndPoint.Address, e.Message, endpoint, NatProtocol.Upnp);
}
}
@@ -151,6 +182,7 @@ namespace MediaBrowser.Server.Implementations.EntryPoints
}
private List<string> _createdRules = new List<string>();
+ private List<string> _usnsHandled = new List<string>();
private void CreateRules(INatDevice device)
{
// On some systems the device discovered event seems to fire repeatedly
diff --git a/MediaBrowser.Server.Implementations/EntryPoints/RecordingNotifier.cs b/MediaBrowser.Server.Implementations/EntryPoints/RecordingNotifier.cs
new file mode 100644
index 000000000..cc4ef1972
--- /dev/null
+++ b/MediaBrowser.Server.Implementations/EntryPoints/RecordingNotifier.cs
@@ -0,0 +1,83 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+using MediaBrowser.Controller.Library;
+using MediaBrowser.Controller.LiveTv;
+using MediaBrowser.Controller.Plugins;
+using MediaBrowser.Controller.Session;
+using MediaBrowser.Model.Logging;
+
+namespace MediaBrowser.Server.Implementations.EntryPoints
+{
+ public class RecordingNotifier : IServerEntryPoint
+ {
+ private readonly ILiveTvManager _liveTvManager;
+ private readonly ISessionManager _sessionManager;
+ private readonly IUserManager _userManager;
+ private readonly ILogger _logger;
+
+ public RecordingNotifier(ISessionManager sessionManager, IUserManager userManager, ILogger logger, ILiveTvManager liveTvManager)
+ {
+ _sessionManager = sessionManager;
+ _userManager = userManager;
+ _logger = logger;
+ _liveTvManager = liveTvManager;
+ }
+
+ public void Run()
+ {
+ _liveTvManager.TimerCancelled += _liveTvManager_TimerCancelled;
+ _liveTvManager.SeriesTimerCancelled += _liveTvManager_SeriesTimerCancelled;
+ _liveTvManager.TimerCreated += _liveTvManager_TimerCreated;
+ _liveTvManager.SeriesTimerCreated += _liveTvManager_SeriesTimerCreated;
+ }
+
+ private void _liveTvManager_SeriesTimerCreated(object sender, Model.Events.GenericEventArgs<TimerEventInfo> e)
+ {
+ SendMessage("SeriesTimerCreated", e.Argument);
+ }
+
+ private void _liveTvManager_TimerCreated(object sender, Model.Events.GenericEventArgs<TimerEventInfo> e)
+ {
+ SendMessage("TimerCreated", e.Argument);
+ }
+
+ private void _liveTvManager_SeriesTimerCancelled(object sender, Model.Events.GenericEventArgs<TimerEventInfo> e)
+ {
+ SendMessage("SeriesTimerCancelled", e.Argument);
+ }
+
+ private void _liveTvManager_TimerCancelled(object sender, Model.Events.GenericEventArgs<TimerEventInfo> e)
+ {
+ SendMessage("TimerCancelled", e.Argument);
+ }
+
+ private async void SendMessage(string name, TimerEventInfo info)
+ {
+ var users = _userManager.Users.Where(i => i.Policy.EnableLiveTvAccess).ToList();
+
+ foreach (var user in users)
+ {
+ try
+ {
+ await _sessionManager.SendMessageToUserSessions<TimerEventInfo>(user.Id.ToString("N"), name, info, CancellationToken.None);
+ }
+ catch (Exception ex)
+ {
+ _logger.ErrorException("Error sending message", ex);
+ }
+ }
+ }
+
+ public void Dispose()
+ {
+ _liveTvManager.TimerCancelled -= _liveTvManager_TimerCancelled;
+ _liveTvManager.SeriesTimerCancelled -= _liveTvManager_SeriesTimerCancelled;
+ _liveTvManager.TimerCreated -= _liveTvManager_TimerCreated;
+ _liveTvManager.SeriesTimerCreated -= _liveTvManager_SeriesTimerCreated;
+ }
+ }
+}
diff --git a/MediaBrowser.Server.Implementations/EntryPoints/UserDataChangeNotifier.cs b/MediaBrowser.Server.Implementations/EntryPoints/UserDataChangeNotifier.cs
index b059e4144..b616b7761 100644
--- a/MediaBrowser.Server.Implementations/EntryPoints/UserDataChangeNotifier.cs
+++ b/MediaBrowser.Server.Implementations/EntryPoints/UserDataChangeNotifier.cs
@@ -119,7 +119,7 @@ namespace MediaBrowser.Server.Implementations.EntryPoints
.DistinctBy(i => i.Id)
.Select(i =>
{
- var dto = _userDataManager.GetUserDataDto(i, user);
+ var dto = _userDataManager.GetUserDataDto(i, user).Result;
dto.ItemId = i.Id.ToString("N");
return dto;
})
diff --git a/MediaBrowser.Server.Implementations/FileOrganization/EpisodeFileOrganizer.cs b/MediaBrowser.Server.Implementations/FileOrganization/EpisodeFileOrganizer.cs
index 83801b3e7..2109f8d59 100644
--- a/MediaBrowser.Server.Implementations/FileOrganization/EpisodeFileOrganizer.cs
+++ b/MediaBrowser.Server.Implementations/FileOrganization/EpisodeFileOrganizer.cs
@@ -562,9 +562,10 @@ namespace MediaBrowser.Server.Implementations.FileOrganization
series = _libraryManager.GetItemList(new Controller.Entities.InternalItemsQuery
{
IncludeItemTypes = new[] { typeof(Series).Name },
- Recursive = true
- }).Cast<Series>()
- .FirstOrDefault(i => string.Equals(i.Name, info.ItemName, StringComparison.OrdinalIgnoreCase));
+ Recursive = true,
+ Name = info.ItemName
+
+ }).Cast<Series>().FirstOrDefault();
}
}
diff --git a/MediaBrowser.Server.Implementations/HttpServer/HttpListenerHost.cs b/MediaBrowser.Server.Implementations/HttpServer/HttpListenerHost.cs
index c5cb810e5..f091f0f1f 100644
--- a/MediaBrowser.Server.Implementations/HttpServer/HttpListenerHost.cs
+++ b/MediaBrowser.Server.Implementations/HttpServer/HttpListenerHost.cs
@@ -106,7 +106,7 @@ namespace MediaBrowser.Server.Implementations.HttpServer
}
});
- HostContext.GlobalResponseFilters.Add(new ResponseFilter(_logger, () => _config.Configuration.DenyIFrameEmbedding).FilterResponse);
+ HostContext.GlobalResponseFilters.Add(new ResponseFilter(_logger).FilterResponse);
}
public override void OnAfterInit()
diff --git a/MediaBrowser.Server.Implementations/HttpServer/HttpResultFactory.cs b/MediaBrowser.Server.Implementations/HttpServer/HttpResultFactory.cs
index 6cedaa6a9..1d4829260 100644
--- a/MediaBrowser.Server.Implementations/HttpServer/HttpResultFactory.cs
+++ b/MediaBrowser.Server.Implementations/HttpServer/HttpResultFactory.cs
@@ -294,7 +294,7 @@ namespace MediaBrowser.Server.Implementations.HttpServer
return null;
}
- public object GetStaticFileResult(IRequest requestContext,
+ public Task<object> GetStaticFileResult(IRequest requestContext,
string path,
FileShare fileShare = FileShare.Read)
{
@@ -310,7 +310,7 @@ namespace MediaBrowser.Server.Implementations.HttpServer
});
}
- public object GetStaticFileResult(IRequest requestContext,
+ public Task<object> GetStaticFileResult(IRequest requestContext,
StaticFileResultOptions options)
{
var path = options.Path;
@@ -351,7 +351,7 @@ namespace MediaBrowser.Server.Implementations.HttpServer
return _fileSystem.GetFileStream(path, FileMode.Open, FileAccess.Read, fileShare);
}
- public object GetStaticResult(IRequest requestContext,
+ public Task<object> GetStaticResult(IRequest requestContext,
Guid cacheKey,
DateTime? lastDateModified,
TimeSpan? cacheDuration,
@@ -372,7 +372,7 @@ namespace MediaBrowser.Server.Implementations.HttpServer
});
}
- public object GetStaticResult(IRequest requestContext, StaticResultOptions options)
+ public async Task<object> GetStaticResult(IRequest requestContext, StaticResultOptions options)
{
var cacheKey = options.CacheKey;
options.ResponseHeaders = options.ResponseHeaders ?? new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
@@ -398,7 +398,7 @@ namespace MediaBrowser.Server.Implementations.HttpServer
}
var compress = ShouldCompressResponse(requestContext, contentType);
- var hasOptions = GetStaticResult(requestContext, options, compress).Result;
+ var hasOptions = await GetStaticResult(requestContext, options, compress).ConfigureAwait(false);
AddResponseHeaders(hasOptions, options.ResponseHeaders);
return hasOptions;
diff --git a/MediaBrowser.Server.Implementations/HttpServer/LoggerUtils.cs b/MediaBrowser.Server.Implementations/HttpServer/LoggerUtils.cs
index ce8100025..bfbb228ed 100644
--- a/MediaBrowser.Server.Implementations/HttpServer/LoggerUtils.cs
+++ b/MediaBrowser.Server.Implementations/HttpServer/LoggerUtils.cs
@@ -35,7 +35,7 @@ namespace MediaBrowser.Server.Implementations.HttpServer
public static void LogResponse(ILogger logger, int statusCode, string url, string endPoint, TimeSpan duration)
{
var durationMs = duration.TotalMilliseconds;
- var logSuffix = durationMs >= 1000 ? "ms (slow)" : "ms";
+ var logSuffix = durationMs >= 1000 && durationMs < 60000 ? "ms (slow)" : "ms";
logger.Info("HTTP Response {0} to {1}. Time: {2}{3}. {4}", statusCode, endPoint, Convert.ToInt32(durationMs).ToString(CultureInfo.InvariantCulture), logSuffix, url);
}
diff --git a/MediaBrowser.Server.Implementations/HttpServer/RangeRequestWriter.cs b/MediaBrowser.Server.Implementations/HttpServer/RangeRequestWriter.cs
index 020856886..fb4397462 100644
--- a/MediaBrowser.Server.Implementations/HttpServer/RangeRequestWriter.cs
+++ b/MediaBrowser.Server.Implementations/HttpServer/RangeRequestWriter.cs
@@ -39,6 +39,9 @@ namespace MediaBrowser.Server.Implementations.HttpServer
/// </summary>
private static readonly CultureInfo UsCulture = new CultureInfo("en-US");
+ public Func<IDisposable> ResultScope { get; set; }
+ public List<Cookie> Cookies { get; private set; }
+
/// <summary>
/// Additional HTTP Headers
/// </summary>
@@ -81,6 +84,7 @@ namespace MediaBrowser.Server.Implementations.HttpServer
Options["Accept-Ranges"] = "bytes";
StatusCode = HttpStatusCode.PartialContent;
+ Cookies = new List<Cookie>();
SetRangeValues();
}
diff --git a/MediaBrowser.Server.Implementations/HttpServer/ResponseFilter.cs b/MediaBrowser.Server.Implementations/HttpServer/ResponseFilter.cs
index f993d4437..ee05702f4 100644
--- a/MediaBrowser.Server.Implementations/HttpServer/ResponseFilter.cs
+++ b/MediaBrowser.Server.Implementations/HttpServer/ResponseFilter.cs
@@ -12,12 +12,10 @@ namespace MediaBrowser.Server.Implementations.HttpServer
{
private static readonly CultureInfo UsCulture = new CultureInfo("en-US");
private readonly ILogger _logger;
- private readonly Func<bool> _denyIframeEmbedding;
- public ResponseFilter(ILogger logger, Func<bool> denyIframeEmbedding)
+ public ResponseFilter(ILogger logger)
{
_logger = logger;
- _denyIframeEmbedding = denyIframeEmbedding;
}
/// <summary>
@@ -31,11 +29,6 @@ namespace MediaBrowser.Server.Implementations.HttpServer
// Try to prevent compatibility view
res.AddHeader("X-UA-Compatible", "IE=Edge");
- if (_denyIframeEmbedding())
- {
- res.AddHeader("X-Frame-Options", "SAMEORIGIN");
- }
-
var exception = dto as Exception;
if (exception != null)
diff --git a/MediaBrowser.Server.Implementations/HttpServer/Security/AuthorizationContext.cs b/MediaBrowser.Server.Implementations/HttpServer/Security/AuthorizationContext.cs
index 357f5c976..bc3e7b163 100644
--- a/MediaBrowser.Server.Implementations/HttpServer/Security/AuthorizationContext.cs
+++ b/MediaBrowser.Server.Implementations/HttpServer/Security/AuthorizationContext.cs
@@ -104,6 +104,10 @@ namespace MediaBrowser.Server.Implementations.HttpServer.Security
{
info.DeviceId = tokenInfo.DeviceId;
}
+ if (string.IsNullOrWhiteSpace(info.Version))
+ {
+ info.Version = tokenInfo.AppVersion;
+ }
}
else
{
diff --git a/MediaBrowser.Server.Implementations/HttpServer/SocketSharp/RequestMono.cs b/MediaBrowser.Server.Implementations/HttpServer/SocketSharp/RequestMono.cs
index ed9e17b6b..efa850922 100644
--- a/MediaBrowser.Server.Implementations/HttpServer/SocketSharp/RequestMono.cs
+++ b/MediaBrowser.Server.Implementations/HttpServer/SocketSharp/RequestMono.cs
@@ -4,6 +4,7 @@ using System.Globalization;
using System.IO;
using System.Text;
using System.Web;
+using ServiceStack;
using ServiceStack.Web;
namespace MediaBrowser.Server.Implementations.HttpServer.SocketSharp
@@ -116,6 +117,21 @@ namespace MediaBrowser.Server.Implementations.HttpServer.SocketSharp
}
}
+ public string Accept
+ {
+ get
+ {
+ return string.IsNullOrEmpty(request.Headers[HttpHeaders.Accept]) ? null : request.Headers[HttpHeaders.Accept];
+ }
+ }
+
+ public string Authorization
+ {
+ get
+ {
+ return string.IsNullOrEmpty(request.Headers[HttpHeaders.Authorization]) ? null : request.Headers[HttpHeaders.Authorization];
+ }
+ }
protected bool validate_cookies, validate_query_string, validate_form;
protected bool checked_cookies, checked_query_string, checked_form;
diff --git a/MediaBrowser.Server.Implementations/HttpServer/SocketSharp/WebSocketSharpRequest.cs b/MediaBrowser.Server.Implementations/HttpServer/SocketSharp/WebSocketSharpRequest.cs
index 30849d441..c7d889505 100644
--- a/MediaBrowser.Server.Implementations/HttpServer/SocketSharp/WebSocketSharpRequest.cs
+++ b/MediaBrowser.Server.Implementations/HttpServer/SocketSharp/WebSocketSharpRequest.cs
@@ -22,7 +22,7 @@ namespace MediaBrowser.Server.Implementations.HttpServer.SocketSharp
this.OperationName = operationName;
this.RequestAttributes = requestAttributes;
this.request = httpContext.Request;
- this.response = new WebSocketSharpResponse(logger, httpContext.Response);
+ this.response = new WebSocketSharpResponse(logger, httpContext.Response, this);
this.RequestPreferences = new RequestPreferences(this);
}
diff --git a/MediaBrowser.Server.Implementations/HttpServer/SocketSharp/WebSocketSharpResponse.cs b/MediaBrowser.Server.Implementations/HttpServer/SocketSharp/WebSocketSharpResponse.cs
index 171dacb22..e08be8bd1 100644
--- a/MediaBrowser.Server.Implementations/HttpServer/SocketSharp/WebSocketSharpResponse.cs
+++ b/MediaBrowser.Server.Implementations/HttpServer/SocketSharp/WebSocketSharpResponse.cs
@@ -1,4 +1,5 @@
using System;
+using System.Collections.Generic;
using System.IO;
using System.Net;
using MediaBrowser.Model.Logging;
@@ -14,14 +15,17 @@ namespace MediaBrowser.Server.Implementations.HttpServer.SocketSharp
private readonly ILogger _logger;
private readonly HttpListenerResponse response;
- public WebSocketSharpResponse(ILogger logger, HttpListenerResponse response)
+ public WebSocketSharpResponse(ILogger logger, HttpListenerResponse response, IRequest request)
{
_logger = logger;
this.response = response;
+ Items = new Dictionary<string, object>();
+ Request = request;
}
+ public IRequest Request { get; private set; }
public bool UseBufferedStream { get; set; }
-
+ public Dictionary<string, object> Items { get; private set; }
public object OriginalResponse
{
get { return response; }
@@ -58,6 +62,11 @@ namespace MediaBrowser.Server.Implementations.HttpServer.SocketSharp
response.AddHeader(name, value);
}
+ public string GetHeader(string name)
+ {
+ return response.Headers[name];
+ }
+
public void Redirect(string url)
{
response.Redirect(url);
@@ -142,5 +151,9 @@ namespace MediaBrowser.Server.Implementations.HttpServer.SocketSharp
}
public bool KeepAlive { get; set; }
+
+ public void ClearCookies()
+ {
+ }
}
}
diff --git a/MediaBrowser.Server.Implementations/HttpServer/SwaggerService.cs b/MediaBrowser.Server.Implementations/HttpServer/SwaggerService.cs
index aeaac80e8..d91f316d6 100644
--- a/MediaBrowser.Server.Implementations/HttpServer/SwaggerService.cs
+++ b/MediaBrowser.Server.Implementations/HttpServer/SwaggerService.cs
@@ -25,7 +25,7 @@ namespace MediaBrowser.Server.Implementations.HttpServer
var requestedFile = Path.Combine(swaggerDirectory, request.ResourceName.Replace('/', Path.DirectorySeparatorChar));
- return ResultFactory.GetStaticFileResult(Request, requestedFile);
+ return ResultFactory.GetStaticFileResult(Request, requestedFile).Result;
}
/// <summary>
diff --git a/MediaBrowser.Server.Implementations/IO/FileRefresher.cs b/MediaBrowser.Server.Implementations/IO/FileRefresher.cs
new file mode 100644
index 000000000..4bea6ad34
--- /dev/null
+++ b/MediaBrowser.Server.Implementations/IO/FileRefresher.cs
@@ -0,0 +1,289 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+using CommonIO;
+using MediaBrowser.Common.Events;
+using MediaBrowser.Common.ScheduledTasks;
+using MediaBrowser.Controller.Configuration;
+using MediaBrowser.Controller.Entities;
+using MediaBrowser.Controller.Library;
+using MediaBrowser.Model.Logging;
+using MediaBrowser.Server.Implementations.ScheduledTasks;
+
+namespace MediaBrowser.Server.Implementations.IO
+{
+ public class FileRefresher : IDisposable
+ {
+ private ILogger Logger { get; set; }
+ private ITaskManager TaskManager { get; set; }
+ private ILibraryManager LibraryManager { get; set; }
+ private IServerConfigurationManager ConfigurationManager { get; set; }
+ private readonly IFileSystem _fileSystem;
+ private readonly List<string> _affectedPaths = new List<string>();
+ private Timer _timer;
+ private readonly object _timerLock = new object();
+ public string Path { get; private set; }
+
+ public event EventHandler<EventArgs> Completed;
+
+ public FileRefresher(string path, IFileSystem fileSystem, IServerConfigurationManager configurationManager, ILibraryManager libraryManager, ITaskManager taskManager, ILogger logger)
+ {
+ logger.Debug("New file refresher created for {0}", path);
+ Path = path;
+ _affectedPaths.Add(path);
+
+ _fileSystem = fileSystem;
+ ConfigurationManager = configurationManager;
+ LibraryManager = libraryManager;
+ TaskManager = taskManager;
+ Logger = logger;
+ }
+
+ private void AddAffectedPath(string path)
+ {
+ if (!_affectedPaths.Contains(path, StringComparer.Ordinal))
+ {
+ _affectedPaths.Add(path);
+ }
+ }
+
+ public void AddPath(string path)
+ {
+ lock (_timerLock)
+ {
+ AddAffectedPath(path);
+ }
+ RestartTimer();
+ }
+
+ public void RestartTimer()
+ {
+ lock (_timerLock)
+ {
+ if (_timer == null)
+ {
+ _timer = new Timer(OnTimerCallback, null, TimeSpan.FromSeconds(ConfigurationManager.Configuration.LibraryMonitorDelay), TimeSpan.FromMilliseconds(-1));
+ }
+ else
+ {
+ _timer.Change(TimeSpan.FromSeconds(ConfigurationManager.Configuration.LibraryMonitorDelay), TimeSpan.FromMilliseconds(-1));
+ }
+ }
+ }
+
+ public void ResetPath(string path, string affectedFile)
+ {
+ lock (_timerLock)
+ {
+ Logger.Debug("Resetting file refresher from {0} to {1}", Path, path);
+
+ Path = path;
+ AddAffectedPath(path);
+
+ if (!string.IsNullOrWhiteSpace(affectedFile))
+ {
+ AddAffectedPath(affectedFile);
+ }
+ }
+ RestartTimer();
+ }
+
+ private async void OnTimerCallback(object state)
+ {
+ List<string> paths;
+
+ lock (_timerLock)
+ {
+ paths = _affectedPaths.ToList();
+ }
+
+ // Extend the timer as long as any of the paths are still being written to.
+ if (paths.Any(IsFileLocked))
+ {
+ Logger.Info("Timer extended.");
+ RestartTimer();
+ return;
+ }
+
+ Logger.Debug("Timer stopped.");
+
+ DisposeTimer();
+ EventHelper.FireEventIfNotNull(Completed, this, EventArgs.Empty, Logger);
+
+ try
+ {
+ await ProcessPathChanges(paths.ToList()).ConfigureAwait(false);
+ }
+ catch (Exception ex)
+ {
+ Logger.ErrorException("Error processing directory changes", ex);
+ }
+ }
+
+ private async Task ProcessPathChanges(List<string> paths)
+ {
+ var itemsToRefresh = paths
+ .Select(GetAffectedBaseItem)
+ .Where(item => item != null)
+ .Distinct()
+ .ToList();
+
+ foreach (var p in paths)
+ {
+ Logger.Info(p + " reports change.");
+ }
+
+ // If the root folder changed, run the library task so the user can see it
+ if (itemsToRefresh.Any(i => i is AggregateFolder))
+ {
+ TaskManager.CancelIfRunningAndQueue<RefreshMediaLibraryTask>();
+ return;
+ }
+
+ foreach (var item in itemsToRefresh)
+ {
+ Logger.Info(item.Name + " (" + item.Path + ") will be refreshed.");
+
+ try
+ {
+ 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, item.Name);
+ }
+ catch (Exception ex)
+ {
+ Logger.ErrorException("Error refreshing {0}", ex, item.Name);
+ }
+ }
+ }
+
+ /// <summary>
+ /// Gets the affected base item.
+ /// </summary>
+ /// <param name="path">The path.</param>
+ /// <returns>BaseItem.</returns>
+ private BaseItem GetAffectedBaseItem(string path)
+ {
+ BaseItem item = null;
+
+ while (item == null && !string.IsNullOrEmpty(path))
+ {
+ item = LibraryManager.FindByPath(path, null);
+
+ path = System.IO.Path.GetDirectoryName(path);
+ }
+
+ if (item != null)
+ {
+ // If the item has been deleted find the first valid parent that still exists
+ while (!_fileSystem.DirectoryExists(item.Path) && !_fileSystem.FileExists(item.Path))
+ {
+ item = item.GetParent();
+
+ if (item == null)
+ {
+ break;
+ }
+ }
+ }
+
+ return item;
+ }
+
+ private bool IsFileLocked(string path)
+ {
+ if (Environment.OSVersion.Platform != PlatformID.Win32NT)
+ {
+ // Causing lockups on linux
+ return false;
+ }
+
+ try
+ {
+ var data = _fileSystem.GetFileSystemInfo(path);
+
+ if (!data.Exists
+ || data.IsDirectory
+
+ // Opening a writable stream will fail with readonly files
+ || data.Attributes.HasFlag(FileAttributes.ReadOnly))
+ {
+ return false;
+ }
+ }
+ catch (IOException)
+ {
+ return false;
+ }
+ catch (Exception ex)
+ {
+ Logger.ErrorException("Error getting file system info for: {0}", ex, path);
+ return false;
+ }
+
+ // In order to determine if the file is being written to, we have to request write access
+ // But if the server only has readonly access, this is going to cause this entire algorithm to fail
+ // So we'll take a best guess about our access level
+ var requestedFileAccess = ConfigurationManager.Configuration.SaveLocalMeta
+ ? FileAccess.ReadWrite
+ : FileAccess.Read;
+
+ try
+ {
+ using (_fileSystem.GetFileStream(path, FileMode.Open, requestedFileAccess, FileShare.ReadWrite))
+ {
+ //file is not locked
+ return false;
+ }
+ }
+ catch (DirectoryNotFoundException)
+ {
+ // File may have been deleted
+ return false;
+ }
+ catch (FileNotFoundException)
+ {
+ // File may have been deleted
+ return false;
+ }
+ catch (IOException)
+ {
+ //the file is unavailable because it is:
+ //still being written to
+ //or being processed by another thread
+ //or does not exist (has already been processed)
+ Logger.Debug("{0} is locked.", path);
+ return true;
+ }
+ catch (Exception ex)
+ {
+ Logger.ErrorException("Error determining if file is locked: {0}", ex, path);
+ return false;
+ }
+ }
+
+ private void DisposeTimer()
+ {
+ lock (_timerLock)
+ {
+ if (_timer != null)
+ {
+ _timer.Dispose();
+ }
+ }
+ }
+
+ public void Dispose()
+ {
+ DisposeTimer();
+ }
+ }
+}
diff --git a/MediaBrowser.Server.Implementations/IO/LibraryMonitor.cs b/MediaBrowser.Server.Implementations/IO/LibraryMonitor.cs
index 09ca134d1..0690d62dd 100644
--- a/MediaBrowser.Server.Implementations/IO/LibraryMonitor.cs
+++ b/MediaBrowser.Server.Implementations/IO/LibraryMonitor.cs
@@ -26,13 +26,9 @@ namespace MediaBrowser.Server.Implementations.IO
/// </summary>
private readonly ConcurrentDictionary<string, FileSystemWatcher> _fileSystemWatchers = new ConcurrentDictionary<string, FileSystemWatcher>(StringComparer.OrdinalIgnoreCase);
/// <summary>
- /// The update timer
- /// </summary>
- private Timer _updateTimer;
- /// <summary>
/// The affected paths
/// </summary>
- private readonly ConcurrentDictionary<string, string> _affectedPaths = new ConcurrentDictionary<string, string>();
+ private readonly List<FileRefresher> _activeRefreshers = new List<FileRefresher>();
/// <summary>
/// A dynamic list of paths that should be ignored. Added to during our own file sytem modifications.
@@ -44,8 +40,8 @@ namespace MediaBrowser.Server.Implementations.IO
/// </summary>
private readonly IReadOnlyList<string> _alwaysIgnoreFiles = new List<string>
{
- "thumbs.db",
- "small.jpg",
+ "thumbs.db",
+ "small.jpg",
"albumart.jpg",
// WMC temp recording directories that will constantly be written to
@@ -54,11 +50,6 @@ namespace MediaBrowser.Server.Implementations.IO
};
/// <summary>
- /// The timer lock
- /// </summary>
- private readonly object _timerLock = new object();
-
- /// <summary>
/// Add the path to our temporary ignore list. Use when writing to a path within our listening scope.
/// </summary>
/// <param name="path">The path.</param>
@@ -463,226 +454,58 @@ namespace MediaBrowser.Server.Implementations.IO
if (monitorPath)
{
// Avoid implicitly captured closure
- var affectedPath = path;
- _affectedPaths.AddOrUpdate(path, path, (key, oldValue) => affectedPath);
- }
-
- RestartTimer();
- }
-
- private void RestartTimer()
- {
- lock (_timerLock)
- {
- if (_updateTimer == null)
- {
- _updateTimer = new Timer(TimerStopped, null, TimeSpan.FromSeconds(ConfigurationManager.Configuration.LibraryMonitorDelay), TimeSpan.FromMilliseconds(-1));
- }
- else
- {
- _updateTimer.Change(TimeSpan.FromSeconds(ConfigurationManager.Configuration.LibraryMonitorDelay), TimeSpan.FromMilliseconds(-1));
- }
- }
- }
-
- /// <summary>
- /// Timers the stopped.
- /// </summary>
- /// <param name="stateInfo">The state info.</param>
- private async void TimerStopped(object stateInfo)
- {
- // Extend the timer as long as any of the paths are still being written to.
- if (_affectedPaths.Any(p => IsFileLocked(p.Key)))
- {
- Logger.Info("Timer extended.");
- RestartTimer();
- return;
- }
-
- Logger.Debug("Timer stopped.");
-
- DisposeTimer();
-
- var paths = _affectedPaths.Keys.ToList();
- _affectedPaths.Clear();
-
- try
- {
- await ProcessPathChanges(paths).ConfigureAwait(false);
- }
- catch (Exception ex)
- {
- Logger.ErrorException("Error processing directory changes", ex);
+ CreateRefresher(path);
}
}
- private bool IsFileLocked(string path)
+ private void CreateRefresher(string path)
{
- if (Environment.OSVersion.Platform != PlatformID.Win32NT)
- {
- // Causing lockups on linux
- return false;
- }
+ var parentPath = Path.GetDirectoryName(path);
- try
+ lock (_activeRefreshers)
{
- var data = _fileSystem.GetFileSystemInfo(path);
-
- if (!data.Exists
- || data.IsDirectory
-
- // Opening a writable stream will fail with readonly files
- || data.Attributes.HasFlag(FileAttributes.ReadOnly))
+ var refreshers = _activeRefreshers.ToList();
+ foreach (var refresher in refreshers)
{
- return false;
- }
- }
- catch (IOException)
- {
- return false;
- }
- catch (Exception ex)
- {
- Logger.ErrorException("Error getting file system info for: {0}", ex, path);
- return false;
- }
-
- // In order to determine if the file is being written to, we have to request write access
- // But if the server only has readonly access, this is going to cause this entire algorithm to fail
- // So we'll take a best guess about our access level
- var requestedFileAccess = ConfigurationManager.Configuration.SaveLocalMeta
- ? FileAccess.ReadWrite
- : FileAccess.Read;
+ // Path is already being refreshed
+ if (string.Equals(path, refresher.Path, StringComparison.Ordinal))
+ {
+ refresher.RestartTimer();
+ return;
+ }
- try
- {
- using (_fileSystem.GetFileStream(path, FileMode.Open, requestedFileAccess, FileShare.ReadWrite))
- {
- if (_updateTimer != null)
+ // Parent folder is already being refreshed
+ if (_fileSystem.ContainsSubPath(refresher.Path, path))
{
- //file is not locked
- return false;
+ refresher.AddPath(path);
+ return;
}
- }
- }
- catch (DirectoryNotFoundException)
- {
- // File may have been deleted
- return false;
- }
- catch (FileNotFoundException)
- {
- // File may have been deleted
- return false;
- }
- catch (IOException)
- {
- //the file is unavailable because it is:
- //still being written to
- //or being processed by another thread
- //or does not exist (has already been processed)
- Logger.Debug("{0} is locked.", path);
- return true;
- }
- catch (Exception ex)
- {
- Logger.ErrorException("Error determining if file is locked: {0}", ex, path);
- return false;
- }
- return false;
- }
+ // New path is a parent
+ if (_fileSystem.ContainsSubPath(path, refresher.Path))
+ {
+ refresher.ResetPath(path, null);
+ return;
+ }
- private void DisposeTimer()
- {
- lock (_timerLock)
- {
- if (_updateTimer != null)
- {
- _updateTimer.Dispose();
- _updateTimer = null;
+ // They are siblings. Rebase the refresher to the parent folder.
+ if (string.Equals(parentPath, Path.GetDirectoryName(refresher.Path), StringComparison.Ordinal))
+ {
+ refresher.ResetPath(parentPath, path);
+ return;
+ }
}
- }
- }
-
- /// <summary>
- /// Processes the path changes.
- /// </summary>
- /// <param name="paths">The paths.</param>
- /// <returns>Task.</returns>
- private async Task ProcessPathChanges(List<string> paths)
- {
- var itemsToRefresh = paths
- .Select(GetAffectedBaseItem)
- .Where(item => item != null)
- .Distinct()
- .ToList();
-
- foreach (var p in paths)
- {
- Logger.Info(p + " reports change.");
- }
-
- // If the root folder changed, run the library task so the user can see it
- if (itemsToRefresh.Any(i => i is AggregateFolder))
- {
- TaskManager.CancelIfRunningAndQueue<RefreshMediaLibraryTask>();
- return;
- }
-
- foreach (var item in itemsToRefresh)
- {
- Logger.Info(item.Name + " (" + item.Path + ") will be refreshed.");
- try
- {
- 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, item.Name);
- }
- catch (Exception ex)
- {
- Logger.ErrorException("Error refreshing {0}", ex, item.Name);
- }
+ var newRefresher = new FileRefresher(path, _fileSystem, ConfigurationManager, LibraryManager, TaskManager, Logger);
+ newRefresher.Completed += NewRefresher_Completed;
+ _activeRefreshers.Add(newRefresher);
}
}
- /// <summary>
- /// Gets the affected base item.
- /// </summary>
- /// <param name="path">The path.</param>
- /// <returns>BaseItem.</returns>
- private BaseItem GetAffectedBaseItem(string path)
+ private void NewRefresher_Completed(object sender, EventArgs e)
{
- BaseItem item = null;
-
- while (item == null && !string.IsNullOrEmpty(path))
- {
- item = LibraryManager.FindByPath(path, null);
-
- path = Path.GetDirectoryName(path);
- }
-
- if (item != null)
- {
- // If the item has been deleted find the first valid parent that still exists
- while (!_fileSystem.DirectoryExists(item.Path) && !_fileSystem.FileExists(item.Path))
- {
- item = item.GetParent();
-
- if (item == null)
- {
- break;
- }
- }
- }
-
- return item;
+ var refresher = (FileRefresher)sender;
+ DisposeRefresher(refresher);
}
/// <summary>
@@ -713,10 +536,29 @@ namespace MediaBrowser.Server.Implementations.IO
watcher.Dispose();
}
- DisposeTimer();
-
_fileSystemWatchers.Clear();
- _affectedPaths.Clear();
+ DisposeRefreshers();
+ }
+
+ private void DisposeRefresher(FileRefresher refresher)
+ {
+ lock (_activeRefreshers)
+ {
+ refresher.Dispose();
+ _activeRefreshers.Remove(refresher);
+ }
+ }
+
+ private void DisposeRefreshers()
+ {
+ lock (_activeRefreshers)
+ {
+ foreach (var refresher in _activeRefreshers.ToList())
+ {
+ refresher.Dispose();
+ }
+ _activeRefreshers.Clear();
+ }
}
/// <summary>
diff --git a/MediaBrowser.Server.Implementations/Intros/DefaultIntroProvider.cs b/MediaBrowser.Server.Implementations/Intros/DefaultIntroProvider.cs
index 49012c65a..7c7a535cd 100644
--- a/MediaBrowser.Server.Implementations/Intros/DefaultIntroProvider.cs
+++ b/MediaBrowser.Server.Implementations/Intros/DefaultIntroProvider.cs
@@ -63,16 +63,8 @@ namespace MediaBrowser.Server.Implementations.Intros
? null
: _localization.GetRatingLevel(item.OfficialRating);
- var random = new Random(Environment.TickCount + Guid.NewGuid().GetHashCode());
-
var candidates = new List<ItemWithTrailer>();
- var itemPeople = _libraryManager.GetPeople(item);
- var allPeople = _libraryManager.GetPeople(new InternalPeopleQuery
- {
- AppearsInItemId = item.Id
- });
-
var trailerTypes = new List<TrailerType>();
if (config.EnableIntrosFromMoviesInLibrary)
@@ -105,26 +97,25 @@ namespace MediaBrowser.Server.Implementations.Intros
var trailerResult = _libraryManager.GetItemList(new InternalItemsQuery
{
IncludeItemTypes = new[] { typeof(Trailer).Name },
- TrailerTypes = trailerTypes.ToArray()
+ TrailerTypes = trailerTypes.ToArray(),
+ SimilarTo = item,
+ IsPlayed = config.EnableIntrosForWatchedContent ? (bool?) null : false,
+ MaxParentalRating = config.EnableIntrosParentalControl ? ratingLevel : null,
+ Limit = config.TrailerLimit
});
candidates.AddRange(trailerResult.Select(i => new ItemWithTrailer
{
Item = i,
Type = i.SourceType == SourceType.Channel ? ItemWithTrailerType.ChannelTrailer : ItemWithTrailerType.ItemWithTrailer,
- User = user,
- WatchingItem = item,
- WatchingItemPeople = itemPeople,
- AllPeople = allPeople,
- Random = random,
LibraryManager = _libraryManager
}));
}
- return GetResult(item, candidates, config, ratingLevel);
+ return GetResult(item, candidates, config);
}
- private IEnumerable<IntroInfo> GetResult(BaseItem item, IEnumerable<ItemWithTrailer> candidates, CinemaModeConfiguration config, int? ratingLevel)
+ private IEnumerable<IntroInfo> GetResult(BaseItem item, IEnumerable<ItemWithTrailer> candidates, CinemaModeConfiguration config)
{
var customIntros = !string.IsNullOrWhiteSpace(config.CustomIntroPath) ?
GetCustomIntros(config) :
@@ -134,48 +125,12 @@ namespace MediaBrowser.Server.Implementations.Intros
GetMediaInfoIntros(config, item) :
new List<IntroInfo>();
- var trailerLimit = config.TrailerLimit;
-
// Avoid implicitly captured closure
- return candidates.Where(i =>
- {
- if (config.EnableIntrosParentalControl && !FilterByParentalRating(ratingLevel, i.Item))
- {
- return false;
- }
-
- if (!config.EnableIntrosForWatchedContent && i.IsPlayed)
- {
- return false;
- }
- return !IsDuplicate(item, i.Item);
- })
- .OrderByDescending(i => i.Score)
- .ThenBy(i => Guid.NewGuid())
- .ThenByDescending(i => i.IsPlayed ? 0 : 1)
- .Select(i => i.IntroInfo)
- .Take(trailerLimit)
+ return candidates.Select(i => i.IntroInfo)
.Concat(customIntros.Take(1))
.Concat(mediaInfoIntros);
}
- private bool IsDuplicate(BaseItem playingContent, BaseItem test)
- {
- var id = playingContent.GetProviderId(MetadataProviders.Imdb);
- if (!string.IsNullOrWhiteSpace(id) && string.Equals(id, test.GetProviderId(MetadataProviders.Imdb), StringComparison.OrdinalIgnoreCase))
- {
- return true;
- }
-
- id = playingContent.GetProviderId(MetadataProviders.Tmdb);
- if (!string.IsNullOrWhiteSpace(id) && string.Equals(id, test.GetProviderId(MetadataProviders.Tmdb), StringComparison.OrdinalIgnoreCase))
- {
- return true;
- }
-
- return false;
- }
-
private CinemaModeConfiguration GetOptions()
{
return _serverConfig.GetConfiguration<CinemaModeConfiguration>("cinemamode");
@@ -346,102 +301,6 @@ namespace MediaBrowser.Server.Implementations.Intros
return list.Distinct(StringComparer.OrdinalIgnoreCase);
}
- private bool FilterByParentalRating(int? ratingLevel, BaseItem item)
- {
- // Only content rated same or lower
- if (ratingLevel.HasValue)
- {
- var level = string.IsNullOrWhiteSpace(item.OfficialRating)
- ? (int?)null
- : _localization.GetRatingLevel(item.OfficialRating);
-
- return level.HasValue && level.Value <= ratingLevel.Value;
- }
-
- return true;
- }
-
- internal static int GetSimiliarityScore(BaseItem item1, List<PersonInfo> item1People, List<PersonInfo> allPeople, BaseItem item2, Random random, ILibraryManager libraryManager)
- {
- var points = 0;
-
- if (!string.IsNullOrEmpty(item1.OfficialRating) && string.Equals(item1.OfficialRating, item2.OfficialRating, StringComparison.OrdinalIgnoreCase))
- {
- points += 10;
- }
-
- // Find common genres
- points += item1.Genres.Where(i => item2.Genres.Contains(i, StringComparer.OrdinalIgnoreCase)).Sum(i => 10);
-
- // Find common tags
- points += GetTags(item1).Where(i => GetTags(item2).Contains(i, StringComparer.OrdinalIgnoreCase)).Sum(i => 10);
-
- // Find common keywords
- points += GetKeywords(item1).Where(i => GetKeywords(item2).Contains(i, StringComparer.OrdinalIgnoreCase)).Sum(i => 10);
-
- // Find common studios
- points += item1.Studios.Where(i => item2.Studios.Contains(i, StringComparer.OrdinalIgnoreCase)).Sum(i => 5);
-
- var item2PeopleNames = allPeople.Where(i => i.ItemId == item2.Id)
- .Select(i => i.Name)
- .Where(i => !string.IsNullOrWhiteSpace(i))
- .DistinctNames()
- .ToDictionary(i => i, StringComparer.OrdinalIgnoreCase);
-
- points += item1People.Where(i => item2PeopleNames.ContainsKey(i.Name)).Sum(i =>
- {
- if (string.Equals(i.Type, PersonType.Director, StringComparison.OrdinalIgnoreCase) || string.Equals(i.Role, PersonType.Director, StringComparison.OrdinalIgnoreCase))
- {
- return 5;
- }
- if (string.Equals(i.Type, PersonType.Actor, StringComparison.OrdinalIgnoreCase) || string.Equals(i.Role, PersonType.Actor, StringComparison.OrdinalIgnoreCase))
- {
- return 3;
- }
- if (string.Equals(i.Type, PersonType.Composer, StringComparison.OrdinalIgnoreCase) || string.Equals(i.Role, PersonType.Composer, StringComparison.OrdinalIgnoreCase))
- {
- return 3;
- }
- if (string.Equals(i.Type, PersonType.GuestStar, StringComparison.OrdinalIgnoreCase) || string.Equals(i.Role, PersonType.GuestStar, StringComparison.OrdinalIgnoreCase))
- {
- return 3;
- }
- if (string.Equals(i.Type, PersonType.Writer, StringComparison.OrdinalIgnoreCase) || string.Equals(i.Role, PersonType.Writer, StringComparison.OrdinalIgnoreCase))
- {
- return 2;
- }
-
- return 1;
- });
-
- // Add some randomization so that you're not always seeing the same ones for a given movie
- points += random.Next(0, 50);
-
- return points;
- }
-
- private static IEnumerable<string> GetTags(BaseItem item)
- {
- var hasTags = item as IHasTags;
- if (hasTags != null)
- {
- return hasTags.Tags;
- }
-
- return new List<string>();
- }
-
- private static IEnumerable<string> GetKeywords(BaseItem item)
- {
- var hasTags = item as IHasKeywords;
- if (hasTags != null)
- {
- return hasTags.Keywords;
- }
-
- return new List<string>();
- }
-
public IEnumerable<string> GetAllIntroFiles()
{
return GetCustomIntroFiles(GetOptions(), true, true);
@@ -461,39 +320,8 @@ namespace MediaBrowser.Server.Implementations.Intros
{
internal BaseItem Item;
internal ItemWithTrailerType Type;
- internal User User;
- internal BaseItem WatchingItem;
- internal List<PersonInfo> WatchingItemPeople;
- internal List<PersonInfo> AllPeople;
- internal Random Random;
internal ILibraryManager LibraryManager;
- private bool? _isPlayed;
- public bool IsPlayed
- {
- get
- {
- if (!_isPlayed.HasValue)
- {
- _isPlayed = Item.IsPlayed(User);
- }
- return _isPlayed.Value;
- }
- }
-
- private int? _score;
- public int Score
- {
- get
- {
- if (!_score.HasValue)
- {
- _score = GetSimiliarityScore(WatchingItem, WatchingItemPeople, AllPeople, Item, Random, LibraryManager);
- }
- return _score.Value;
- }
- }
-
public IntroInfo IntroInfo
{
get
diff --git a/MediaBrowser.Server.Implementations/Library/LibraryManager.cs b/MediaBrowser.Server.Implementations/Library/LibraryManager.cs
index 56d3bd4de..4c0514035 100644
--- a/MediaBrowser.Server.Implementations/Library/LibraryManager.cs
+++ b/MediaBrowser.Server.Implementations/Library/LibraryManager.cs
@@ -33,6 +33,8 @@ using System.Net;
using System.Threading;
using System.Threading.Tasks;
using CommonIO;
+using MediaBrowser.Model.Channels;
+using MediaBrowser.Model.Dto;
using MediaBrowser.Model.Extensions;
using MediaBrowser.Model.Library;
using MediaBrowser.Model.Net;
@@ -362,6 +364,10 @@ namespace MediaBrowser.Server.Implementations.Library
return;
}
}
+ if (item is Photo)
+ {
+ return;
+ }
//if (!(item is Folder))
//{
// return;
@@ -939,9 +945,7 @@ namespace MediaBrowser.Server.Implementations.Library
private T CreateItemByName<T>(string path, string name)
where T : BaseItem, new()
{
- var isArtist = typeof(T) == typeof(MusicArtist);
-
- if (isArtist)
+ if (typeof(T) == typeof(MusicArtist))
{
var existing = GetItemList(new InternalItemsQuery
{
@@ -1272,59 +1276,162 @@ namespace MediaBrowser.Server.Implementations.Library
return item;
}
- public BaseItem GetMemoryItemById(Guid id)
+ public IEnumerable<BaseItem> GetItemList(InternalItemsQuery query)
{
- if (id == Guid.Empty)
+ if (query.Recursive && query.ParentId.HasValue)
{
- throw new ArgumentNullException("id");
+ var parent = GetItemById(query.ParentId.Value);
+ if (parent != null)
+ {
+ SetTopParentIdsOrAncestors(query, new List<BaseItem> { parent });
+ query.ParentId = null;
+ }
}
- BaseItem item;
+ if (query.User != null)
+ {
+ AddUserToQuery(query, query.User);
+ }
- LibraryItemsCache.TryGetValue(id, out item);
+ return ItemRepository.GetItemList(query);
+ }
- return item;
+ public IEnumerable<BaseItem> GetItemList(InternalItemsQuery query, IEnumerable<string> parentIds)
+ {
+ var parents = parentIds.Select(i => GetItemById(new Guid(i))).Where(i => i != null).ToList();
+
+ SetTopParentIdsOrAncestors(query, parents);
+
+ if (query.AncestorIds.Length == 0 && query.TopParentIds.Length == 0)
+ {
+ if (query.User != null)
+ {
+ AddUserToQuery(query, query.User);
+ }
+ }
+
+ return ItemRepository.GetItemList(query);
}
- public IEnumerable<BaseItem> GetItemList(InternalItemsQuery query)
+ public QueryResult<BaseItem> QueryItems(InternalItemsQuery query)
{
if (query.User != null)
{
AddUserToQuery(query, query.User);
}
- var result = ItemRepository.GetItemIdsList(query);
+ if (query.EnableTotalRecordCount)
+ {
+ return ItemRepository.GetItems(query);
+ }
- return result.Select(GetItemById).Where(i => i != null);
+ return new QueryResult<BaseItem>
+ {
+ Items = ItemRepository.GetItemList(query).ToArray()
+ };
}
- public QueryResult<BaseItem> QueryItems(InternalItemsQuery query)
+ public List<Guid> GetItemIds(InternalItemsQuery query)
{
if (query.User != null)
{
AddUserToQuery(query, query.User);
}
- return ItemRepository.GetItems(query);
+ return ItemRepository.GetItemIdsList(query);
}
- public List<Guid> GetItemIds(InternalItemsQuery query)
+ public QueryResult<Tuple<BaseItem, ItemCounts>> GetStudios(InternalItemsQuery query)
{
if (query.User != null)
{
AddUserToQuery(query, query.User);
}
- return ItemRepository.GetItemIdsList(query);
+ SetTopParentOrAncestorIds(query);
+ return ItemRepository.GetStudios(query);
}
- public IEnumerable<BaseItem> GetItemList(InternalItemsQuery query, IEnumerable<string> parentIds)
+ public QueryResult<Tuple<BaseItem, ItemCounts>> GetGenres(InternalItemsQuery query)
{
- var parents = parentIds.Select(i => GetItemById(new Guid(i))).Where(i => i != null).ToList();
+ if (query.User != null)
+ {
+ AddUserToQuery(query, query.User);
+ }
- SetTopParentIdsOrAncestors(query, parents);
+ SetTopParentOrAncestorIds(query);
+ return ItemRepository.GetGenres(query);
+ }
+
+ public QueryResult<Tuple<BaseItem, ItemCounts>> GetGameGenres(InternalItemsQuery query)
+ {
+ if (query.User != null)
+ {
+ AddUserToQuery(query, query.User);
+ }
+
+ SetTopParentOrAncestorIds(query);
+ return ItemRepository.GetGameGenres(query);
+ }
+
+ public QueryResult<Tuple<BaseItem, ItemCounts>> GetMusicGenres(InternalItemsQuery query)
+ {
+ if (query.User != null)
+ {
+ AddUserToQuery(query, query.User);
+ }
+
+ SetTopParentOrAncestorIds(query);
+ return ItemRepository.GetMusicGenres(query);
+ }
+
+ public QueryResult<Tuple<BaseItem, ItemCounts>> GetArtists(InternalItemsQuery query)
+ {
+ if (query.User != null)
+ {
+ AddUserToQuery(query, query.User);
+ }
+
+ SetTopParentOrAncestorIds(query);
+ return ItemRepository.GetArtists(query);
+ }
+
+ private void SetTopParentOrAncestorIds(InternalItemsQuery query)
+ {
+ if (query.AncestorIds.Length == 0)
+ {
+ return;
+ }
+
+ var parents = query.AncestorIds.Select(i => GetItemById(new Guid(i))).ToList();
+
+ if (parents.All(i =>
+ {
+ if (i is ICollectionFolder || i is UserView)
+ {
+ return true;
+ }
+
+ //_logger.Debug("Query requires ancestor query due to type: " + i.GetType().Name);
+ return false;
+
+ }))
+ {
+ // Optimize by querying against top level views
+ query.TopParentIds = parents.SelectMany(i => GetTopParentsForQuery(i, query.User)).Select(i => i.Id.ToString("N")).ToArray();
+ query.AncestorIds = new string[] { };
+ }
+ }
- return GetItemIds(query).Select(GetItemById).Where(i => i != null);
+ public QueryResult<Tuple<BaseItem, ItemCounts>> GetAlbumArtists(InternalItemsQuery query)
+ {
+ if (query.User != null)
+ {
+ AddUserToQuery(query, query.User);
+ }
+
+ SetTopParentOrAncestorIds(query);
+ return ItemRepository.GetAlbumArtists(query);
}
public QueryResult<BaseItem> GetItemsResult(InternalItemsQuery query)
@@ -1346,30 +1453,15 @@ namespace MediaBrowser.Server.Implementations.Library
if (query.EnableTotalRecordCount)
{
- var initialResult = ItemRepository.GetItemIds(query);
-
- return new QueryResult<BaseItem>
- {
- TotalRecordCount = initialResult.TotalRecordCount,
- Items = initialResult.Items.Select(GetItemById).Where(i => i != null).ToArray()
- };
+ return ItemRepository.GetItems(query);
}
return new QueryResult<BaseItem>
{
- Items = ItemRepository.GetItemIdsList(query).Select(GetItemById).Where(i => i != null).ToArray()
+ Items = ItemRepository.GetItemList(query).ToArray()
};
}
- public QueryResult<BaseItem> GetItemsResult(InternalItemsQuery query, IEnumerable<string> parentIds)
- {
- var parents = parentIds.Select(i => GetItemById(new Guid(i))).Where(i => i != null).ToList();
-
- SetTopParentIdsOrAncestors(query, parents);
-
- return GetItemsResult(query);
- }
-
private void SetTopParentIdsOrAncestors(InternalItemsQuery query, List<BaseItem> parents)
{
if (parents.All(i =>
@@ -1379,7 +1471,7 @@ namespace MediaBrowser.Server.Implementations.Library
return true;
}
- _logger.Debug("Query requires ancestor query due to type: " + i.GetType().Name);
+ //_logger.Debug("Query requires ancestor query due to type: " + i.GetType().Name);
return false;
}))
@@ -1421,8 +1513,13 @@ namespace MediaBrowser.Server.Implementations.Library
}
if (string.Equals(view.ViewType, CollectionType.Channels))
{
- // TODO: Return channels
- return new[] { view };
+ var channelResult = BaseItem.ChannelManager.GetChannelsInternal(new ChannelQuery
+ {
+ UserId = user.Id.ToString("N")
+
+ }, CancellationToken.None).Result;
+
+ return channelResult.Items;
}
// Translate view into folders
@@ -2309,7 +2406,7 @@ namespace MediaBrowser.Server.Implementations.Library
public IEnumerable<Video> FindTrailers(BaseItem owner, List<FileSystemMetadata> fileSystemChildren, IDirectoryService directoryService)
{
- var files = fileSystemChildren.Where(i => i.IsDirectory)
+ var files = owner.IsInMixedFolder ? new List<FileSystemMetadata>() : fileSystemChildren.Where(i => i.IsDirectory)
.Where(i => string.Equals(i.Name, BaseItem.TrailerFolderName, StringComparison.OrdinalIgnoreCase))
.SelectMany(i => _fileSystem.GetFiles(i.FullName, false))
.ToList();
@@ -2709,6 +2806,31 @@ namespace MediaBrowser.Server.Implementations.Library
}
_fileSystem.CreateShortcut(lnk, path);
+
+ RemoveContentTypeOverrides(path);
+ }
+
+ private void RemoveContentTypeOverrides(string path)
+ {
+ var removeList = new List<NameValuePair>();
+
+ foreach (var contentType in ConfigurationManager.Configuration.ContentTypes)
+ {
+ if (string.Equals(path, contentType.Name, StringComparison.OrdinalIgnoreCase)
+ || _fileSystem.ContainsSubPath(path, contentType.Name))
+ {
+ removeList.Add(contentType);
+ }
+ }
+
+ if (removeList.Count > 0)
+ {
+ ConfigurationManager.Configuration.ContentTypes = ConfigurationManager.Configuration.ContentTypes
+ .Except(removeList)
+ .ToArray();
+
+ ConfigurationManager.SaveConfiguration();
+ }
}
public void RemoveMediaPath(string virtualFolderName, string mediaPath)
diff --git a/MediaBrowser.Server.Implementations/Library/MediaSourceManager.cs b/MediaBrowser.Server.Implementations/Library/MediaSourceManager.cs
index 0ef7efe1b..4f3fe1bf3 100644
--- a/MediaBrowser.Server.Implementations/Library/MediaSourceManager.cs
+++ b/MediaBrowser.Server.Implementations/Library/MediaSourceManager.cs
@@ -69,10 +69,6 @@ namespace MediaBrowser.Server.Implementations.Library
if (stream.IsTextSubtitleStream)
{
- if (string.Equals(stream.Codec, "ass", StringComparison.OrdinalIgnoreCase))
- {
- return false;
- }
return true;
}
diff --git a/MediaBrowser.Server.Implementations/Library/MusicManager.cs b/MediaBrowser.Server.Implementations/Library/MusicManager.cs
index ef13ba996..3ff434898 100644
--- a/MediaBrowser.Server.Implementations/Library/MusicManager.cs
+++ b/MediaBrowser.Server.Implementations/Library/MusicManager.cs
@@ -98,7 +98,7 @@ namespace MediaBrowser.Server.Implementations.Library
Genres = genreList.ToArray()
- }, new string[] { });
+ });
var genresDictionary = genreList.ToDictionary(i => i, StringComparer.OrdinalIgnoreCase);
diff --git a/MediaBrowser.Server.Implementations/Library/Resolvers/BaseVideoResolver.cs b/MediaBrowser.Server.Implementations/Library/Resolvers/BaseVideoResolver.cs
index 9edd3f83f..703a33856 100644
--- a/MediaBrowser.Server.Implementations/Library/Resolvers/BaseVideoResolver.cs
+++ b/MediaBrowser.Server.Implementations/Library/Resolvers/BaseVideoResolver.cs
@@ -126,7 +126,7 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers
}
else
{
- var videoInfo = parser.ResolveFile(args.Path);
+ var videoInfo = parser.Resolve(args.Path, false, false);
if (videoInfo == null)
{
diff --git a/MediaBrowser.Server.Implementations/Library/Resolvers/TV/EpisodeResolver.cs b/MediaBrowser.Server.Implementations/Library/Resolvers/TV/EpisodeResolver.cs
index e62049821..14e5e446b 100644
--- a/MediaBrowser.Server.Implementations/Library/Resolvers/TV/EpisodeResolver.cs
+++ b/MediaBrowser.Server.Implementations/Library/Resolvers/TV/EpisodeResolver.cs
@@ -1,6 +1,9 @@
-using MediaBrowser.Controller.Entities.TV;
+using System;
+using System.IO;
+using MediaBrowser.Controller.Entities.TV;
using MediaBrowser.Controller.Library;
using System.Linq;
+using MediaBrowser.Model.Entities;
namespace MediaBrowser.Server.Implementations.Library.Resolvers.TV
{
@@ -37,7 +40,8 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers.TV
}
// If the parent is a Season or Series, then this is an Episode if the VideoResolver returns something
- if (season != null || args.HasParent<Series>())
+ // Also handle flat tv folders
+ if (season != null || args.HasParent<Series>() || string.Equals(args.GetCollectionType(), CollectionType.TvShows, StringComparison.OrdinalIgnoreCase))
{
var episode = ResolveVideo<Episode>(args, false);
diff --git a/MediaBrowser.Server.Implementations/Library/SearchEngine.cs b/MediaBrowser.Server.Implementations/Library/SearchEngine.cs
index e271fbcb2..cf6f070d0 100644
--- a/MediaBrowser.Server.Implementations/Library/SearchEngine.cs
+++ b/MediaBrowser.Server.Implementations/Library/SearchEngine.cs
@@ -122,7 +122,7 @@ namespace MediaBrowser.Server.Implementations.Library
AddIfMissing(excludeItemTypes, typeof(MusicGenre).Name);
}
- if (query.IncludePeople && (includeItemTypes.Count == 0 || includeItemTypes.Contains("People", StringComparer.OrdinalIgnoreCase)))
+ if (query.IncludePeople && (includeItemTypes.Count == 0 || includeItemTypes.Contains("People", StringComparer.OrdinalIgnoreCase) || includeItemTypes.Contains("Person", StringComparer.OrdinalIgnoreCase)))
{
if (!query.IncludeMedia)
{
@@ -168,7 +168,7 @@ namespace MediaBrowser.Server.Implementations.Library
Limit = query.Limit,
IncludeItemsByName = true
- }, new string[] { });
+ });
// Add search hints based on item name
hints.AddRange(mediaItems.Where(IncludeInSearch).Select(item =>
diff --git a/MediaBrowser.Server.Implementations/Library/UserDataManager.cs b/MediaBrowser.Server.Implementations/Library/UserDataManager.cs
index 0e211937f..715f3c522 100644
--- a/MediaBrowser.Server.Implementations/Library/UserDataManager.cs
+++ b/MediaBrowser.Server.Implementations/Library/UserDataManager.cs
@@ -23,7 +23,8 @@ namespace MediaBrowser.Server.Implementations.Library
{
public event EventHandler<UserDataSaveEventArgs> UserDataSaved;
- private readonly Dictionary<string, UserItemData> _userData = new Dictionary<string, UserItemData>(StringComparer.OrdinalIgnoreCase);
+ private readonly ConcurrentDictionary<string, UserItemData> _userData =
+ new ConcurrentDictionary<string, UserItemData>(StringComparer.OrdinalIgnoreCase);
private readonly ILogger _logger;
private readonly IServerConfigurationManager _config;
@@ -64,13 +65,6 @@ namespace MediaBrowser.Server.Implementations.Library
try
{
await Repository.SaveUserData(userId, key, userData, cancellationToken).ConfigureAwait(false);
-
- var newValue = userData;
-
- lock (_userData)
- {
- _userData[GetCacheKey(userId, key)] = newValue;
- }
}
catch (Exception ex)
{
@@ -80,6 +74,9 @@ namespace MediaBrowser.Server.Implementations.Library
}
}
+ var cacheKey = GetCacheKey(userId, item.Id);
+ _userData.AddOrUpdate(cacheKey, userData, (k, v) => userData);
+
EventHelper.FireEventIfNotNull(UserDataSaved, this, new UserDataSaveEventArgs
{
Keys = keys,
@@ -122,7 +119,7 @@ namespace MediaBrowser.Server.Implementations.Library
throw;
}
-
+
}
/// <summary>
@@ -140,7 +137,7 @@ namespace MediaBrowser.Server.Implementations.Library
return Repository.GetAllUserData(userId);
}
- public UserItemData GetUserData(Guid userId, List<string> keys)
+ public UserItemData GetUserData(Guid userId, Guid itemId, List<string> keys)
{
if (userId == Guid.Empty)
{
@@ -150,95 +147,44 @@ namespace MediaBrowser.Server.Implementations.Library
{
throw new ArgumentNullException("keys");
}
-
- lock (_userData)
+ if (keys.Count == 0)
{
- foreach (var key in keys)
- {
- var cacheKey = GetCacheKey(userId, key);
- UserItemData value;
- if (_userData.TryGetValue(cacheKey, out value))
- {
- return value;
- }
-
- value = Repository.GetUserData(userId, key);
-
- if (value != null)
- {
- _userData[cacheKey] = value;
- return value;
- }
- }
+ throw new ArgumentException("UserData keys cannot be empty.");
+ }
- if (keys.Count > 0)
- {
- var key = keys[0];
- var cacheKey = GetCacheKey(userId, key);
- var userdata = new UserItemData
- {
- UserId = userId,
- Key = key
- };
- _userData[cacheKey] = userdata;
- return userdata;
- }
+ var cacheKey = GetCacheKey(userId, itemId);
- return null;
- }
+ return _userData.GetOrAdd(cacheKey, k => GetUserDataInternal(userId, keys));
}
- /// <summary>
- /// Gets the user data.
- /// </summary>
- /// <param name="userId">The user id.</param>
- /// <param name="key">The key.</param>
- /// <returns>Task{UserItemData}.</returns>
- public UserItemData GetUserData(Guid userId, string key)
+ private UserItemData GetUserDataInternal(Guid userId, List<string> keys)
{
- if (userId == Guid.Empty)
- {
- throw new ArgumentNullException("userId");
- }
- if (string.IsNullOrEmpty(key))
+ var userData = Repository.GetUserData(userId, keys);
+
+ if (userData != null)
{
- throw new ArgumentNullException("key");
+ return userData;
}
- lock (_userData)
+ if (keys.Count > 0)
{
- var cacheKey = GetCacheKey(userId, key);
- UserItemData value;
- if (_userData.TryGetValue(cacheKey, out value))
- {
- return value;
- }
-
- value = Repository.GetUserData(userId, key);
-
- if (value == null)
+ return new UserItemData
{
- value = new UserItemData
- {
- UserId = userId,
- Key = key
- };
- }
-
- _userData[cacheKey] = value;
- return value;
+ UserId = userId,
+ Key = keys[0]
+ };
}
+
+ return null;
}
/// <summary>
/// Gets the internal key.
/// </summary>
- /// <param name="userId">The user id.</param>
- /// <param name="key">The key.</param>
/// <returns>System.String.</returns>
- private string GetCacheKey(Guid userId, string key)
+ private string GetCacheKey(Guid userId, Guid itemId)
{
- return userId + key;
+ return userId.ToString("N") + itemId.ToString("N");
}
public UserItemData GetUserData(IHasUserData user, IHasUserData item)
@@ -253,16 +199,24 @@ namespace MediaBrowser.Server.Implementations.Library
public UserItemData GetUserData(Guid userId, IHasUserData item)
{
- return GetUserData(userId, item.GetUserDataKeys());
+ return GetUserData(userId, item.Id, item.GetUserDataKeys());
}
- public UserItemDataDto GetUserDataDto(IHasUserData item, User user)
+ public async Task<UserItemDataDto> GetUserDataDto(IHasUserData item, User user)
{
var userData = GetUserData(user.Id, item);
var dto = GetUserItemDataDto(userData);
- item.FillUserDataDtoValues(dto, userData, user);
+ await item.FillUserDataDtoValues(dto, userData, null, user).ConfigureAwait(false);
+ return dto;
+ }
+
+ public async Task<UserItemDataDto> GetUserDataDto(IHasUserData item, BaseItemDto itemDto, User user)
+ {
+ var userData = GetUserData(user.Id, item);
+ var dto = GetUserItemDataDto(userData);
+ await item.FillUserDataDtoValues(dto, userData, itemDto, user).ConfigureAwait(false);
return dto;
}
diff --git a/MediaBrowser.Server.Implementations/Library/UserManager.cs b/MediaBrowser.Server.Implementations/Library/UserManager.cs
index 5ba83d6c7..6456d7f81 100644
--- a/MediaBrowser.Server.Implementations/Library/UserManager.cs
+++ b/MediaBrowser.Server.Implementations/Library/UserManager.cs
@@ -729,7 +729,7 @@ namespace MediaBrowser.Server.Implementations.Library
var text = new StringBuilder();
- var localAddress = _appHost.LocalApiUrl ?? string.Empty;
+ var localAddress = _appHost.GetLocalApiUrl().Result ?? string.Empty;
text.AppendLine("Use your web browser to visit:");
text.AppendLine(string.Empty);
diff --git a/MediaBrowser.Server.Implementations/Library/UserViewManager.cs b/MediaBrowser.Server.Implementations/Library/UserViewManager.cs
index 1bba20ec5..319e715c3 100644
--- a/MediaBrowser.Server.Implementations/Library/UserViewManager.cs
+++ b/MediaBrowser.Server.Implementations/Library/UserViewManager.cs
@@ -105,7 +105,7 @@ namespace MediaBrowser.Server.Implementations.Library
}
}
- if (user.Configuration.DisplayFoldersView)
+ if (_config.Configuration.EnableFolderView)
{
var name = _localizationManager.GetLocalizedString("ViewType" + CollectionType.Folders);
list.Add(await _libraryManager.GetNamedView(name, CollectionType.Folders, string.Empty, cancellationToken).ConfigureAwait(false));
@@ -202,23 +202,7 @@ namespace MediaBrowser.Server.Implementations.Library
{
var user = _userManager.GetUserById(request.UserId);
- var includeTypes = request.IncludeItemTypes;
-
- var currentUser = user;
-
- var libraryItems = GetItemsForLatestItems(user, request.ParentId, includeTypes, request.Limit ?? 10).Where(i =>
- {
- if (request.IsPlayed.HasValue)
- {
- var val = request.IsPlayed.Value;
- if (i is Video && i.IsPlayed(currentUser) != val)
- {
- return false;
- }
- }
-
- return true;
- });
+ var libraryItems = GetItemsForLatestItems(user, request);
var list = new List<Tuple<BaseItem, List<BaseItem>>>();
@@ -254,8 +238,13 @@ namespace MediaBrowser.Server.Implementations.Library
return list;
}
- private IEnumerable<BaseItem> GetItemsForLatestItems(User user, string parentId, string[] includeItemTypes, int limit)
+ private IEnumerable<BaseItem> GetItemsForLatestItems(User user, LatestItemsQuery request)
{
+ var parentId = request.ParentId;
+
+ var includeItemTypes = request.IncludeItemTypes;
+ var limit = request.Limit ?? 10;
+
var parentIds = string.IsNullOrEmpty(parentId)
? new string[] { }
: new[] { parentId };
@@ -276,7 +265,12 @@ namespace MediaBrowser.Server.Implementations.Library
var excludeItemTypes = includeItemTypes.Length == 0 ? new[]
{
- typeof(Person).Name, typeof(Studio).Name, typeof(Year).Name, typeof(GameGenre).Name, typeof(MusicGenre).Name, typeof(Genre).Name
+ typeof(Person).Name,
+ typeof(Studio).Name,
+ typeof(Year).Name,
+ typeof(GameGenre).Name,
+ typeof(MusicGenre).Name,
+ typeof(Genre).Name
} : new string[] { };
@@ -288,8 +282,9 @@ namespace MediaBrowser.Server.Implementations.Library
IsFolder = includeItemTypes.Length == 0 ? false : (bool?)null,
ExcludeItemTypes = excludeItemTypes,
ExcludeLocationTypes = new[] { LocationType.Virtual },
- Limit = limit * 20,
- ExcludeSourceTypes = parentIds.Length == 0 ? new[] { SourceType.Channel, SourceType.LiveTV } : new SourceType[] { }
+ Limit = limit * 5,
+ ExcludeSourceTypes = parentIds.Length == 0 ? new[] { SourceType.Channel, SourceType.LiveTV } : new SourceType[] { },
+ IsPlayed = request.IsPlayed
}, parentIds);
}
diff --git a/MediaBrowser.Server.Implementations/LiveTv/ChannelImageProvider.cs b/MediaBrowser.Server.Implementations/LiveTv/ChannelImageProvider.cs
index dccc7aa93..23560b1aa 100644
--- a/MediaBrowser.Server.Implementations/LiveTv/ChannelImageProvider.cs
+++ b/MediaBrowser.Server.Implementations/LiveTv/ChannelImageProvider.cs
@@ -41,7 +41,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv
var service = _liveTvManager.Services.FirstOrDefault(i => string.Equals(i.Name, liveTvItem.ServiceName, StringComparison.OrdinalIgnoreCase));
- if (service != null)
+ if (service != null && !item.HasImage(ImageType.Primary))
{
try
{
diff --git a/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs b/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs
index de75aac9c..8f56554f1 100644
--- a/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs
+++ b/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs
@@ -35,7 +35,7 @@ using Microsoft.Win32;
namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
{
- public class EmbyTV : ILiveTvService, IHasRegistrationInfo, IDisposable
+ public class EmbyTV : ILiveTvService, ISupportsNewTimerIds, IHasRegistrationInfo, IDisposable
{
private readonly IApplicationHost _appHpst;
private readonly ILogger _logger;
@@ -102,7 +102,6 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
_timerProvider.RestartTimers();
SystemEvents.PowerModeChanged += SystemEvents_PowerModeChanged;
-
CreateRecordingFolders();
}
@@ -111,7 +110,19 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
CreateRecordingFolders();
}
- private void CreateRecordingFolders()
+ internal void CreateRecordingFolders()
+ {
+ try
+ {
+ CreateRecordingFoldersInternal();
+ }
+ catch (Exception ex)
+ {
+ _logger.ErrorException("Error creating recording folders", ex);
+ }
+ }
+
+ internal void CreateRecordingFoldersInternal()
{
var recordingFolders = GetRecordingFolders();
@@ -371,6 +382,29 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
return list;
}
+ public async Task<List<ChannelInfo>> GetChannelsForListingsProvider(ListingsProviderInfo listingsProvider, CancellationToken cancellationToken)
+ {
+ var list = new List<ChannelInfo>();
+
+ foreach (var hostInstance in _liveTvManager.TunerHosts)
+ {
+ try
+ {
+ var channels = await hostInstance.GetChannels(cancellationToken).ConfigureAwait(false);
+
+ list.AddRange(channels);
+ }
+ catch (Exception ex)
+ {
+ _logger.ErrorException("Error getting channels", ex);
+ }
+ }
+
+ return list
+ .Where(i => IsListingProviderEnabledForTuner(listingsProvider, i.TunerHostId))
+ .ToList();
+ }
+
public Task<IEnumerable<ChannelInfo>> GetChannelsAsync(CancellationToken cancellationToken)
{
return GetChannelsAsync(false, cancellationToken);
@@ -424,12 +458,22 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
public Task CreateTimerAsync(TimerInfo info, CancellationToken cancellationToken)
{
+ return CreateTimer(info, cancellationToken);
+ }
+
+ public Task CreateSeriesTimerAsync(SeriesTimerInfo info, CancellationToken cancellationToken)
+ {
+ return CreateSeriesTimer(info, cancellationToken);
+ }
+
+ public Task<string> CreateTimer(TimerInfo info, CancellationToken cancellationToken)
+ {
info.Id = Guid.NewGuid().ToString("N");
_timerProvider.Add(info);
- return Task.FromResult(0);
+ return Task.FromResult(info.Id);
}
- public async Task CreateSeriesTimerAsync(SeriesTimerInfo info, CancellationToken cancellationToken)
+ public async Task<string> CreateSeriesTimer(SeriesTimerInfo info, CancellationToken cancellationToken)
{
info.Id = Guid.NewGuid().ToString("N");
@@ -459,6 +503,8 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
_seriesTimerProvider.Add(info);
await UpdateTimersForSeriesTimer(epgData, info, false).ConfigureAwait(false);
+
+ return info.Id;
}
public async Task UpdateSeriesTimerAsync(SeriesTimerInfo info, CancellationToken cancellationToken)
@@ -537,9 +583,20 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
{
PostPaddingSeconds = Math.Max(config.PostPaddingSeconds, 0),
PrePaddingSeconds = Math.Max(config.PrePaddingSeconds, 0),
- RecordAnyChannel = false,
- RecordAnyTime = false,
- RecordNewOnly = false
+ RecordAnyChannel = true,
+ RecordAnyTime = true,
+ RecordNewOnly = false,
+
+ Days = new List<DayOfWeek>
+ {
+ DayOfWeek.Sunday,
+ DayOfWeek.Monday,
+ DayOfWeek.Tuesday,
+ DayOfWeek.Wednesday,
+ DayOfWeek.Thursday,
+ DayOfWeek.Friday,
+ DayOfWeek.Saturday
+ }
};
if (program != null)
@@ -603,7 +660,16 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
_logger.Debug("Getting programs for channel {0}-{1} from {2}-{3}", channel.Number, channel.Name, provider.Item1.Name, provider.Item2.ListingsId ?? string.Empty);
- var programs = await provider.Item1.GetProgramsAsync(provider.Item2, channel.Number, channel.Name, startDateUtc, endDateUtc, cancellationToken)
+ var channelMappings = GetChannelMappings(provider.Item2);
+ var channelNumber = channel.Number;
+ string mappedChannelNumber;
+ if (channelMappings.TryGetValue(channelNumber, out mappedChannelNumber))
+ {
+ _logger.Debug("Found mapped channel on provider {0}. Tuner channel number: {1}, Mapped channel number: {2}", provider.Item1.Name, channelNumber, mappedChannelNumber);
+ channelNumber = mappedChannelNumber;
+ }
+
+ var programs = await provider.Item1.GetProgramsAsync(provider.Item2, channelNumber, channel.Name, startDateUtc, endDateUtc, cancellationToken)
.ConfigureAwait(false);
var list = programs.ToList();
@@ -625,6 +691,18 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
return new List<ProgramInfo>();
}
+ private Dictionary<string, string> GetChannelMappings(ListingsProviderInfo info)
+ {
+ var dict = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
+
+ foreach (var mapping in info.ChannelMappings)
+ {
+ dict[mapping.Name] = mapping.Value;
+ }
+
+ return dict;
+ }
+
private List<Tuple<IListingsProvider, ListingsProviderInfo>> GetListingProviders()
{
return GetConfiguration().ListingProviders
@@ -901,64 +979,57 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
var recordPath = GetRecordingPath(timer, info);
var recordingStatus = RecordingStatus.New;
+ var isResourceOpen = false;
+ SemaphoreSlim semaphore = null;
try
{
var result = await GetChannelStreamInternal(timer.ChannelId, null, CancellationToken.None).ConfigureAwait(false);
+ isResourceOpen = true;
+ semaphore = result.Item3;
var mediaStreamInfo = result.Item1;
- var isResourceOpen = true;
- // Unfortunately due to the semaphore we have to have a nested try/finally
- try
- {
- // HDHR doesn't seem to release the tuner right away after first probing with ffmpeg
- //await Task.Delay(3000, cancellationToken).ConfigureAwait(false);
+ // HDHR doesn't seem to release the tuner right away after first probing with ffmpeg
+ //await Task.Delay(3000, cancellationToken).ConfigureAwait(false);
- var recorder = await GetRecorder().ConfigureAwait(false);
+ var recorder = await GetRecorder().ConfigureAwait(false);
- recordPath = recorder.GetOutputPath(mediaStreamInfo, recordPath);
- recordPath = EnsureFileUnique(recordPath, timer.Id);
- _fileSystem.CreateDirectory(Path.GetDirectoryName(recordPath));
- activeRecordingInfo.Path = recordPath;
+ recordPath = recorder.GetOutputPath(mediaStreamInfo, recordPath);
+ recordPath = EnsureFileUnique(recordPath, timer.Id);
- _libraryMonitor.ReportFileSystemChangeBeginning(recordPath);
+ _libraryMonitor.ReportFileSystemChangeBeginning(recordPath);
+ _fileSystem.CreateDirectory(Path.GetDirectoryName(recordPath));
+ activeRecordingInfo.Path = recordPath;
- var duration = recordingEndDate - DateTime.UtcNow;
+ var duration = recordingEndDate - DateTime.UtcNow;
- _logger.Info("Beginning recording. Will record for {0} minutes.", duration.TotalMinutes.ToString(CultureInfo.InvariantCulture));
+ _logger.Info("Beginning recording. Will record for {0} minutes.", duration.TotalMinutes.ToString(CultureInfo.InvariantCulture));
- _logger.Info("Writing file to path: " + recordPath);
- _logger.Info("Opening recording stream from tuner provider");
+ _logger.Info("Writing file to path: " + recordPath);
+ _logger.Info("Opening recording stream from tuner provider");
- Action onStarted = () =>
- {
- result.Item3.Release();
- isResourceOpen = false;
- };
-
- var pathWithDuration = result.Item2.ApplyDuration(mediaStreamInfo.Path, duration);
+ Action onStarted = () =>
+ {
+ timer.Status = RecordingStatus.InProgress;
+ _timerProvider.AddOrUpdate(timer, false);
- // If it supports supplying duration via url
- if (!string.Equals(pathWithDuration, mediaStreamInfo.Path, StringComparison.OrdinalIgnoreCase))
- {
- mediaStreamInfo.Path = pathWithDuration;
- mediaStreamInfo.RunTimeTicks = duration.Ticks;
- }
+ result.Item3.Release();
+ isResourceOpen = false;
+ };
- await recorder.Record(mediaStreamInfo, recordPath, duration, onStarted, cancellationToken).ConfigureAwait(false);
+ var pathWithDuration = result.Item2.ApplyDuration(mediaStreamInfo.Path, duration);
- recordingStatus = RecordingStatus.Completed;
- _logger.Info("Recording completed: {0}", recordPath);
- }
- finally
+ // If it supports supplying duration via url
+ if (!string.Equals(pathWithDuration, mediaStreamInfo.Path, StringComparison.OrdinalIgnoreCase))
{
- if (isResourceOpen)
- {
- result.Item3.Release();
- }
-
- _libraryMonitor.ReportFileSystemChangeComplete(recordPath, false);
+ mediaStreamInfo.Path = pathWithDuration;
+ mediaStreamInfo.RunTimeTicks = duration.Ticks;
}
+
+ await recorder.Record(mediaStreamInfo, recordPath, duration, onStarted, cancellationToken).ConfigureAwait(false);
+
+ recordingStatus = RecordingStatus.Completed;
+ _logger.Info("Recording completed: {0}", recordPath);
}
catch (OperationCanceledException)
{
@@ -972,21 +1043,32 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
}
finally
{
+ if (isResourceOpen && semaphore != null)
+ {
+ semaphore.Release();
+ }
+
+ _libraryMonitor.ReportFileSystemChangeComplete(recordPath, true);
+
ActiveRecordingInfo removed;
_activeRecordings.TryRemove(timer.Id, out removed);
}
if (recordingStatus == RecordingStatus.Completed)
{
- OnSuccessfulRecording(info.IsSeries, recordPath);
+ timer.Status = RecordingStatus.Completed;
_timerProvider.Delete(timer);
+
+ OnSuccessfulRecording(info.IsSeries, recordPath);
}
else if (DateTime.UtcNow < timer.EndDate)
{
const int retryIntervalSeconds = 60;
_logger.Info("Retrying recording in {0} seconds.", retryIntervalSeconds);
- _timerProvider.StartTimer(timer, TimeSpan.FromSeconds(retryIntervalSeconds));
+ timer.Status = RecordingStatus.New;
+ timer.StartDate = DateTime.UtcNow.AddSeconds(retryIntervalSeconds);
+ _timerProvider.AddOrUpdate(timer);
}
else
{
@@ -1038,7 +1120,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
if (regInfo.IsValid)
{
- return new EncodedRecorder(_logger, _fileSystem, _mediaEncoder, _config.ApplicationPaths, _jsonSerializer, config);
+ return new EncodedRecorder(_logger, _fileSystem, _mediaEncoder, _config.ApplicationPaths, _jsonSerializer, config, _httpClient);
}
}
@@ -1204,6 +1286,8 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
if (!seriesTimer.RecordAnyTime)
{
allPrograms = allPrograms.Where(epg => Math.Abs(seriesTimer.StartDate.TimeOfDay.Ticks - epg.StartDate.TimeOfDay.Ticks) < TimeSpan.FromMinutes(5).Ticks);
+
+ allPrograms = allPrograms.Where(i => seriesTimer.Days.Contains(i.StartDate.ToLocalTime().DayOfWeek));
}
if (seriesTimer.RecordNewOnly)
@@ -1216,8 +1300,6 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
allPrograms = allPrograms.Where(epg => string.Equals(epg.ChannelId, seriesTimer.ChannelId, StringComparison.OrdinalIgnoreCase));
}
- allPrograms = allPrograms.Where(i => seriesTimer.Days.Contains(i.StartDate.ToLocalTime().DayOfWeek));
-
if (string.IsNullOrWhiteSpace(seriesTimer.SeriesId))
{
_logger.Error("seriesTimer.SeriesId is null. Cannot find programs for series");
diff --git a/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EmbyTVRegistration.cs b/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EmbyTVRegistration.cs
new file mode 100644
index 000000000..675fca325
--- /dev/null
+++ b/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EmbyTVRegistration.cs
@@ -0,0 +1,36 @@
+using System.Threading.Tasks;
+using MediaBrowser.Common.Security;
+
+namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
+{
+ public class EmbyTVRegistration : IRequiresRegistration
+ {
+ private readonly ISecurityManager _securityManager;
+
+ public static EmbyTVRegistration Instance;
+
+ public EmbyTVRegistration(ISecurityManager securityManager)
+ {
+ _securityManager = securityManager;
+ Instance = this;
+ }
+
+ private bool? _isXmlTvEnabled;
+
+ public Task LoadRegistrationInfoAsync()
+ {
+ _isXmlTvEnabled = null;
+ return Task.FromResult(true);
+ }
+
+ public async Task<bool> EnableXmlTv()
+ {
+ if (!_isXmlTvEnabled.HasValue)
+ {
+ var info = await _securityManager.GetRegistrationStatus("xmltv").ConfigureAwait(false);
+ _isXmlTvEnabled = info.IsValid;
+ }
+ return _isXmlTvEnabled.Value;
+ }
+ }
+}
diff --git a/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs b/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs
index e9ea49fa3..21879f6f4 100644
--- a/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs
+++ b/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs
@@ -8,7 +8,9 @@ using System.Text;
using System.Threading;
using System.Threading.Tasks;
using CommonIO;
-using MediaBrowser.Common.Configuration;
+using MediaBrowser.Common.IO;
+using MediaBrowser.Common.Net;
+using MediaBrowser.Controller;
using MediaBrowser.Controller.MediaEncoding;
using MediaBrowser.Model.Dto;
using MediaBrowser.Model.Entities;
@@ -22,8 +24,9 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
{
private readonly ILogger _logger;
private readonly IFileSystem _fileSystem;
+ private readonly IHttpClient _httpClient;
private readonly IMediaEncoder _mediaEncoder;
- private readonly IApplicationPaths _appPaths;
+ private readonly IServerApplicationPaths _appPaths;
private readonly LiveTvOptions _liveTvOptions;
private bool _hasExited;
private Stream _logFileStream;
@@ -32,7 +35,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
private readonly IJsonSerializer _json;
private readonly TaskCompletionSource<bool> _taskCompletionSource = new TaskCompletionSource<bool>();
- public EncodedRecorder(ILogger logger, IFileSystem fileSystem, IMediaEncoder mediaEncoder, IApplicationPaths appPaths, IJsonSerializer json, LiveTvOptions liveTvOptions)
+ public EncodedRecorder(ILogger logger, IFileSystem fileSystem, IMediaEncoder mediaEncoder, IServerApplicationPaths appPaths, IJsonSerializer json, LiveTvOptions liveTvOptions, IHttpClient httpClient)
{
_logger = logger;
_fileSystem = fileSystem;
@@ -40,39 +43,91 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
_appPaths = appPaths;
_json = json;
_liveTvOptions = liveTvOptions;
+ _httpClient = httpClient;
}
public string GetOutputPath(MediaSourceInfo mediaSource, string targetFile)
{
- if (_liveTvOptions.EnableOriginalAudioWithEncodedRecordings)
+ return Path.ChangeExtension(targetFile, ".mp4");
+ }
+
+ public async Task Record(MediaSourceInfo mediaSource, string targetFile, TimeSpan duration, Action onStarted, CancellationToken cancellationToken)
+ {
+ var tempfile = Path.Combine(_appPaths.TranscodingTempPath, Guid.NewGuid().ToString("N") + ".ts");
+
+ try
+ {
+ await RecordInternal(mediaSource, tempfile, targetFile, duration, onStarted, cancellationToken)
+ .ConfigureAwait(false);
+ }
+ finally
{
- // if the audio is aac_latm, stream copying to mp4 will fail
- var streams = mediaSource.MediaStreams ?? new List<MediaStream>();
- if (streams.Any(i => i.Type == MediaStreamType.Audio && (i.Codec ?? string.Empty).IndexOf("aac", StringComparison.OrdinalIgnoreCase) != -1))
+ try
+ {
+ File.Delete(tempfile);
+ }
+ catch (Exception ex)
{
- return Path.ChangeExtension(targetFile, ".mkv");
+ _logger.ErrorException("Error deleting recording temp file", ex);
}
}
-
- return Path.ChangeExtension(targetFile, ".mp4");
}
- public async Task Record(MediaSourceInfo mediaSource, string targetFile, TimeSpan duration, Action onStarted, CancellationToken cancellationToken)
+ public async Task RecordInternal(MediaSourceInfo mediaSource, string tempFile, string targetFile, TimeSpan duration, Action onStarted, CancellationToken cancellationToken)
{
- if (mediaSource.RunTimeTicks.HasValue)
+ var httpRequestOptions = new HttpRequestOptions()
{
- // The media source already has a fixed duration
- // But add another stop 1 minute later just in case the recording gets stuck for any reason
- var durationToken = new CancellationTokenSource(duration.Add(TimeSpan.FromMinutes(1)));
- cancellationToken = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, durationToken.Token).Token;
- }
- else
+ Url = mediaSource.Path
+ };
+
+ httpRequestOptions.BufferContent = false;
+
+ using (var response = await _httpClient.SendAsync(httpRequestOptions, "GET").ConfigureAwait(false))
{
- // The media source if infinite so we need to handle stopping ourselves
- var durationToken = new CancellationTokenSource(duration);
- cancellationToken = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, durationToken.Token).Token;
+ _logger.Info("Opened recording stream from tuner provider");
+
+ Directory.CreateDirectory(Path.GetDirectoryName(tempFile));
+
+ using (var output = _fileSystem.GetFileStream(tempFile, FileMode.Create, FileAccess.Write, FileShare.Read))
+ {
+ //onStarted();
+
+ _logger.Info("Copying recording stream to file {0}", tempFile);
+
+ var bufferMs = 5000;
+
+ if (mediaSource.RunTimeTicks.HasValue)
+ {
+ // The media source already has a fixed duration
+ // But add another stop 1 minute later just in case the recording gets stuck for any reason
+ var durationToken = new CancellationTokenSource(duration.Add(TimeSpan.FromMinutes(1)));
+ cancellationToken = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, durationToken.Token).Token;
+ }
+ else
+ {
+ // The media source if infinite so we need to handle stopping ourselves
+ var durationToken = new CancellationTokenSource(duration.Add(TimeSpan.FromMilliseconds(bufferMs)));
+ cancellationToken = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, durationToken.Token).Token;
+ }
+
+ var tempFileTask = response.Content.CopyToAsync(output, StreamDefaults.DefaultCopyToBufferSize, cancellationToken);
+
+ // Give the temp file a little time to build up
+ await Task.Delay(bufferMs, cancellationToken).ConfigureAwait(false);
+
+ var recordTask = Task.Run(() => RecordFromFile(mediaSource, tempFile, targetFile, duration, onStarted, cancellationToken), cancellationToken);
+
+ await tempFileTask.ConfigureAwait(false);
+
+ await recordTask.ConfigureAwait(false);
+ }
}
+ _logger.Info("Recording completed to file {0}", targetFile);
+ }
+
+ private Task RecordFromFile(MediaSourceInfo mediaSource, string inputFile, string targetFile, TimeSpan duration, Action onStarted, CancellationToken cancellationToken)
+ {
_targetPath = targetFile;
_fileSystem.CreateDirectory(Path.GetDirectoryName(targetFile));
@@ -89,7 +144,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
RedirectStandardInput = true,
FileName = _mediaEncoder.EncoderPath,
- Arguments = GetCommandLineArgs(mediaSource, targetFile, duration),
+ Arguments = GetCommandLineArgs(mediaSource, inputFile, targetFile, duration),
WindowStyle = ProcessWindowStyle.Hidden,
ErrorDialog = false
@@ -110,7 +165,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
_logFileStream = _fileSystem.GetFileStream(logFilePath, FileMode.Create, FileAccess.Write, FileShare.Read, true);
var commandLineLogMessageBytes = Encoding.UTF8.GetBytes(_json.SerializeToString(mediaSource) + Environment.NewLine + Environment.NewLine + commandLineLogMessage + Environment.NewLine + Environment.NewLine);
- await _logFileStream.WriteAsync(commandLineLogMessageBytes, 0, commandLineLogMessageBytes.Length, cancellationToken).ConfigureAwait(false);
+ _logFileStream.Write(commandLineLogMessageBytes, 0, commandLineLogMessageBytes.Length);
process.Exited += (sender, args) => OnFfMpegProcessExited(process);
@@ -126,10 +181,10 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
// Important - don't await the log task or we won't be able to kill ffmpeg when the user stops playback
StartStreamingLog(process.StandardError.BaseStream, _logFileStream);
- await _taskCompletionSource.Task.ConfigureAwait(false);
+ return _taskCompletionSource.Task;
}
- private string GetCommandLineArgs(MediaSourceInfo mediaSource, string targetFile, TimeSpan duration)
+ private string GetCommandLineArgs(MediaSourceInfo mediaSource, string inputTempFile, string targetFile, TimeSpan duration)
{
string videoArgs;
if (EncodeVideo(mediaSource))
@@ -145,23 +200,31 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
videoArgs = "-codec:v:0 copy";
}
- var commandLineArgs = "-fflags +genpts -async 1 -vsync -1 -i \"{0}\" -t {4} -sn {2} -map_metadata -1 -threads 0 {3} -y \"{1}\"";
+ var durationParam = " -t " + _mediaEncoder.GetTimeParameter(duration.Ticks);
+ var commandLineArgs = "-fflags +genpts -async 1 -vsync -1 -re -i \"{0}\"{4} -sn {2} -map_metadata -1 -threads 0 {3} -y \"{1}\"";
if (mediaSource.ReadAtNativeFramerate)
{
commandLineArgs = "-re " + commandLineArgs;
}
- commandLineArgs = string.Format(commandLineArgs, mediaSource.Path, targetFile, videoArgs, GetAudioArgs(mediaSource), _mediaEncoder.GetTimeParameter(duration.Ticks));
+ commandLineArgs = string.Format(commandLineArgs, inputTempFile, targetFile, videoArgs, GetAudioArgs(mediaSource), durationParam);
return commandLineArgs;
}
private string GetAudioArgs(MediaSourceInfo mediaSource)
{
- var copyAudio = new[] { "aac", "mp3" };
+ // do not copy aac because many players have difficulty with aac_latm
+ var copyAudio = new[] { "mp3" };
var mediaStreams = mediaSource.MediaStreams ?? new List<MediaStream>();
- if (_liveTvOptions.EnableOriginalAudioWithEncodedRecordings || mediaStreams.Any(i => i.Type == MediaStreamType.Audio && copyAudio.Contains(i.Codec, StringComparer.OrdinalIgnoreCase)))
+ var inputAudioCodec = mediaStreams.Where(i => i.Type == MediaStreamType.Audio).Select(i => i.Codec).FirstOrDefault() ?? string.Empty;
+
+ if (copyAudio.Contains(inputAudioCodec, StringComparer.OrdinalIgnoreCase))
+ {
+ return "-codec:a:0 copy";
+ }
+ if (_liveTvOptions.EnableOriginalAudioWithEncodedRecordings && !string.Equals(inputAudioCodec, "aac", StringComparison.OrdinalIgnoreCase))
{
return "-codec:a:0 copy";
}
diff --git a/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/TimerManager.cs b/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/TimerManager.cs
index 5d462f106..423358906 100644
--- a/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/TimerManager.cs
+++ b/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/TimerManager.cs
@@ -10,6 +10,7 @@ using System.Linq;
using System.Threading;
using CommonIO;
using MediaBrowser.Controller.Power;
+using MediaBrowser.Model.LiveTv;
namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
{
@@ -71,6 +72,26 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
}
}
+ public void AddOrUpdate(TimerInfo item, bool resetTimer)
+ {
+ if (resetTimer)
+ {
+ AddOrUpdate(item);
+ return;
+ }
+
+ var list = GetAll().ToList();
+
+ if (!list.Any(i => EqualityComparer(i, item)))
+ {
+ base.Add(item);
+ }
+ else
+ {
+ base.Update(item);
+ }
+ }
+
public override void Add(TimerInfo item)
{
if (string.IsNullOrWhiteSpace(item.Id))
@@ -85,6 +106,11 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
private void AddTimer(TimerInfo item)
{
+ if (item.Status == RecordingStatus.Completed)
+ {
+ return;
+ }
+
var startDate = RecordingHelper.GetStartTime(item);
var now = DateTime.UtcNow;
@@ -117,15 +143,15 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
}
}
- public void StartTimer(TimerInfo item, TimeSpan length)
+ public void StartTimer(TimerInfo item, TimeSpan dueTime)
{
StopTimer(item);
- var timer = new Timer(TimerCallback, item.Id, length, TimeSpan.Zero);
+ var timer = new Timer(TimerCallback, item.Id, dueTime, TimeSpan.Zero);
if (_timers.TryAdd(item.Id, timer))
{
- _logger.Info("Creating recording timer for {0}, {1}. Timer will fire in {2} minutes", item.Id, item.Name, length.TotalMinutes.ToString(CultureInfo.InvariantCulture));
+ _logger.Info("Creating recording timer for {0}, {1}. Timer will fire in {2} minutes", item.Id, item.Name, dueTime.TotalMinutes.ToString(CultureInfo.InvariantCulture));
}
else
{
diff --git a/MediaBrowser.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs b/MediaBrowser.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs
index ae2a85090..e37109c14 100644
--- a/MediaBrowser.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs
+++ b/MediaBrowser.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs
@@ -506,8 +506,8 @@ namespace MediaBrowser.Server.Implementations.LiveTv.Listings
}
private async Task<List<ScheduleDirect.ShowImages>> GetImageForPrograms(
- ListingsProviderInfo info,
- List<string> programIds,
+ ListingsProviderInfo info,
+ List<string> programIds,
CancellationToken cancellationToken)
{
var imageIdString = "[";
@@ -564,7 +564,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.Listings
try
{
- using (Stream responce = await Get(options, false, info).ConfigureAwait(false))
+ using (Stream responce = await Get(options, false, info).ConfigureAwait(false))
{
var root = _jsonSerializer.DeserializeFromStream<List<ScheduleDirect.Headends>>(responce);
@@ -666,58 +666,60 @@ namespace MediaBrowser.Server.Implementations.LiveTv.Listings
}
}
- private async Task<HttpResponseInfo> Post(HttpRequestOptions options,
- bool enableRetry,
- ListingsProviderInfo providerInfo)
+ private async Task<HttpResponseInfo> Post(HttpRequestOptions options,
+ bool enableRetry,
+ ListingsProviderInfo providerInfo)
{
try
{
return await _httpClient.Post(options).ConfigureAwait(false);
- }
- catch (HttpException ex)
- {
- _tokens.Clear();
-
- if (!ex.StatusCode.HasValue || (int)ex.StatusCode.Value >= 500)
- {
- enableRetry = false;
- }
-
- if (!enableRetry) {
- throw;
- }
- }
-
- var newToken = await GetToken (providerInfo, options.CancellationToken).ConfigureAwait (false);
- options.RequestHeaders ["token"] = newToken;
- return await Post (options, false, providerInfo).ConfigureAwait (false);
+ }
+ catch (HttpException ex)
+ {
+ _tokens.Clear();
+
+ if (!ex.StatusCode.HasValue || (int)ex.StatusCode.Value >= 500)
+ {
+ enableRetry = false;
+ }
+
+ if (!enableRetry)
+ {
+ throw;
+ }
+ }
+
+ var newToken = await GetToken(providerInfo, options.CancellationToken).ConfigureAwait(false);
+ options.RequestHeaders["token"] = newToken;
+ return await Post(options, false, providerInfo).ConfigureAwait(false);
}
- private async Task<Stream> Get(HttpRequestOptions options,
- bool enableRetry,
- ListingsProviderInfo providerInfo)
+ private async Task<Stream> Get(HttpRequestOptions options,
+ bool enableRetry,
+ ListingsProviderInfo providerInfo)
{
try
{
return await _httpClient.Get(options).ConfigureAwait(false);
- }
- catch (HttpException ex)
- {
- _tokens.Clear();
-
- if (!ex.StatusCode.HasValue || (int)ex.StatusCode.Value >= 500)
- {
- enableRetry = false;
- }
-
- if (!enableRetry) {
- throw;
- }
- }
-
- var newToken = await GetToken (providerInfo, options.CancellationToken).ConfigureAwait (false);
- options.RequestHeaders ["token"] = newToken;
- return await Get (options, false, providerInfo).ConfigureAwait (false);
+ }
+ catch (HttpException ex)
+ {
+ _tokens.Clear();
+
+ if (!ex.StatusCode.HasValue || (int)ex.StatusCode.Value >= 500)
+ {
+ enableRetry = false;
+ }
+
+ if (!enableRetry)
+ {
+ throw;
+ }
+ }
+
+ var newToken = await GetToken(providerInfo, options.CancellationToken).ConfigureAwait(false);
+ options.RequestHeaders["token"] = newToken;
+ return await Get(options, false, providerInfo).ConfigureAwait(false);
}
private async Task<string> GetTokenInternal(string username, string password,
@@ -734,7 +736,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.Listings
//_logger.Info("Obtaining token from Schedules Direct from addres: " + httpOptions.Url + " with body " +
// httpOptions.RequestContent);
- using (var responce = await Post(httpOptions, false, null).ConfigureAwait(false))
+ using (var responce = await Post(httpOptions, false, null).ConfigureAwait(false))
{
var root = _jsonSerializer.DeserializeFromStream<ScheduleDirect.Token>(responce.Content);
if (root.message == "OK")
@@ -816,7 +818,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.Listings
try
{
- using (var response = await Get(options, false, null).ConfigureAwait(false))
+ using (var response = await Get(options, false, null).ConfigureAwait(false))
{
var root = _jsonSerializer.DeserializeFromStream<ScheduleDirect.Lineups>(response);
@@ -869,6 +871,75 @@ namespace MediaBrowser.Server.Implementations.LiveTv.Listings
return GetHeadends(info, country, location, CancellationToken.None);
}
+ public async Task<List<ChannelInfo>> GetChannels(ListingsProviderInfo info, CancellationToken cancellationToken)
+ {
+ var listingsId = info.ListingsId;
+ if (string.IsNullOrWhiteSpace(listingsId))
+ {
+ throw new Exception("ListingsId required");
+ }
+
+ await AddMetadata(info, new List<ChannelInfo>(), cancellationToken).ConfigureAwait(false);
+
+ var token = await GetToken(info, cancellationToken);
+
+ if (string.IsNullOrWhiteSpace(token))
+ {
+ throw new Exception("token required");
+ }
+
+ var httpOptions = new HttpRequestOptions()
+ {
+ Url = ApiUrl + "/lineups/" + listingsId,
+ UserAgent = UserAgent,
+ CancellationToken = cancellationToken,
+ LogErrorResponseBody = true,
+ // The data can be large so give it some extra time
+ TimeoutMs = 60000
+ };
+
+ httpOptions.RequestHeaders["token"] = token;
+
+ var list = new List<ChannelInfo>();
+
+ using (var response = await Get(httpOptions, true, info).ConfigureAwait(false))
+ {
+ var root = _jsonSerializer.DeserializeFromStream<ScheduleDirect.Channel>(response);
+ _logger.Info("Found " + root.map.Count + " channels on the lineup on ScheduleDirect");
+ _logger.Info("Mapping Stations to Channel");
+ foreach (ScheduleDirect.Map map in root.map)
+ {
+ var channelNumber = map.logicalChannelNumber;
+
+ if (string.IsNullOrWhiteSpace(channelNumber))
+ {
+ channelNumber = map.channel;
+ }
+ if (string.IsNullOrWhiteSpace(channelNumber))
+ {
+ channelNumber = map.atscMajor + "." + map.atscMinor;
+ }
+ channelNumber = channelNumber.TrimStart('0');
+
+ var name = channelNumber;
+ var station = GetStation(listingsId, channelNumber, null);
+
+ if (station != null)
+ {
+ name = station.name;
+ }
+
+ list.Add(new ChannelInfo
+ {
+ Number = channelNumber,
+ Name = name
+ });
+ }
+ }
+
+ return list;
+ }
+
public class ScheduleDirect
{
public class Token
diff --git a/MediaBrowser.Server.Implementations/LiveTv/Listings/XmlTv.cs b/MediaBrowser.Server.Implementations/LiveTv/Listings/XmlTv.cs
deleted file mode 100644
index ac316f9a1..000000000
--- a/MediaBrowser.Server.Implementations/LiveTv/Listings/XmlTv.cs
+++ /dev/null
@@ -1,44 +0,0 @@
-using MediaBrowser.Controller.LiveTv;
-using MediaBrowser.Model.Dto;
-using MediaBrowser.Model.LiveTv;
-using System;
-using System.Collections.Generic;
-using System.Threading;
-using System.Threading.Tasks;
-
-namespace MediaBrowser.Server.Implementations.LiveTv.Listings
-{
- public class XmlTv : IListingsProvider
- {
- public string Name
- {
- get { return "XmlTV"; }
- }
-
- public string Type
- {
- get { return "xmltv"; }
- }
-
- public Task<IEnumerable<ProgramInfo>> GetProgramsAsync(ListingsProviderInfo info, string channelNumber, string channelName, DateTime startDateUtc, DateTime endDateUtc, CancellationToken cancellationToken)
- {
- throw new NotImplementedException();
- }
-
- public async Task AddMetadata(ListingsProviderInfo info, List<ChannelInfo> channels, CancellationToken cancellationToken)
- {
- // Might not be needed
- }
-
- public async Task Validate(ListingsProviderInfo info, bool validateLogin, bool validateListings)
- {
- // Check that the path or url is valid. If not, throw a file not found exception
- }
-
- public Task<List<NameIdPair>> GetLineups(ListingsProviderInfo info, string country, string location)
- {
- // In theory this should never be called because there is always only one lineup
- throw new NotImplementedException();
- }
- }
-}
diff --git a/MediaBrowser.Server.Implementations/LiveTv/Listings/XmlTvListingsProvider.cs b/MediaBrowser.Server.Implementations/LiveTv/Listings/XmlTvListingsProvider.cs
new file mode 100644
index 000000000..1e82e3fce
--- /dev/null
+++ b/MediaBrowser.Server.Implementations/LiveTv/Listings/XmlTvListingsProvider.cs
@@ -0,0 +1,219 @@
+using MediaBrowser.Controller.LiveTv;
+using MediaBrowser.Model.Dto;
+using MediaBrowser.Model.LiveTv;
+using System;
+using System.Collections.Generic;
+using System.Globalization;
+using System.IO;
+using System.Linq;
+using System.Net;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+using Emby.XmlTv.Classes;
+using Emby.XmlTv.Entities;
+using MediaBrowser.Common.Extensions;
+using MediaBrowser.Common.Net;
+using MediaBrowser.Controller.Configuration;
+using MediaBrowser.Model.Logging;
+
+namespace MediaBrowser.Server.Implementations.LiveTv.Listings
+{
+ public class XmlTvListingsProvider : IListingsProvider
+ {
+ private readonly IServerConfigurationManager _config;
+ private readonly IHttpClient _httpClient;
+ private readonly ILogger _logger;
+
+ public XmlTvListingsProvider(IServerConfigurationManager config, IHttpClient httpClient, ILogger logger)
+ {
+ _config = config;
+ _httpClient = httpClient;
+ _logger = logger;
+ }
+
+ public string Name
+ {
+ get { return "XmlTV"; }
+ }
+
+ public string Type
+ {
+ get { return "xmltv"; }
+ }
+
+ private string GetLanguage()
+ {
+ return _config.Configuration.PreferredMetadataLanguage;
+ }
+
+ private async Task<string> GetXml(string path, CancellationToken cancellationToken)
+ {
+ _logger.Info("xmltv path: {0}", path);
+
+ if (!path.StartsWith("http", StringComparison.OrdinalIgnoreCase))
+ {
+ return path;
+ }
+
+ var cacheFilename = DateTime.UtcNow.DayOfYear.ToString(CultureInfo.InvariantCulture) + "-" + DateTime.UtcNow.Hour.ToString(CultureInfo.InvariantCulture) + ".xml";
+ var cacheFile = Path.Combine(_config.ApplicationPaths.CachePath, "xmltv", cacheFilename);
+ if (File.Exists(cacheFile))
+ {
+ return cacheFile;
+ }
+
+ _logger.Info("Downloading xmltv listings from {0}", path);
+
+ var tempFile = await _httpClient.GetTempFile(new HttpRequestOptions
+ {
+ CancellationToken = cancellationToken,
+ Url = path,
+ Progress = new Progress<Double>(),
+ DecompressionMethod = DecompressionMethods.GZip,
+
+ // It's going to come back gzipped regardless of this value
+ // So we need to make sure the decompression method is set to gzip
+ EnableHttpCompression = true
+
+ }).ConfigureAwait(false);
+
+ Directory.CreateDirectory(Path.GetDirectoryName(cacheFile));
+
+ using (var stream = File.OpenRead(tempFile))
+ {
+ using (var reader = new StreamReader(stream, Encoding.UTF8))
+ {
+ using (var fileStream = File.OpenWrite(cacheFile))
+ {
+ using (var writer = new StreamWriter(fileStream))
+ {
+ while (!reader.EndOfStream)
+ {
+ writer.WriteLine(reader.ReadLine());
+ }
+ }
+ }
+ }
+ }
+
+ _logger.Debug("Returning xmltv path {0}", cacheFile);
+ return cacheFile;
+ }
+
+ public async Task<IEnumerable<ProgramInfo>> GetProgramsAsync(ListingsProviderInfo info, string channelNumber, string channelName, DateTime startDateUtc, DateTime endDateUtc, CancellationToken cancellationToken)
+ {
+ if (!await EmbyTV.EmbyTVRegistration.Instance.EnableXmlTv().ConfigureAwait(false))
+ {
+ var length = endDateUtc - startDateUtc;
+ if (length.TotalDays > 1)
+ {
+ endDateUtc = startDateUtc.AddDays(1);
+ }
+ }
+
+ var path = await GetXml(info.Path, cancellationToken).ConfigureAwait(false);
+ var reader = new XmlTvReader(path, GetLanguage(), null);
+
+ var results = reader.GetProgrammes(channelNumber, startDateUtc, endDateUtc, cancellationToken);
+ return results.Select(p => new ProgramInfo()
+ {
+ ChannelId = p.ChannelId,
+ EndDate = GetDate(p.EndDate),
+ EpisodeNumber = p.Episode == null ? null : p.Episode.Episode,
+ EpisodeTitle = p.Episode == null ? null : p.Episode.Title,
+ Genres = p.Categories,
+ Id = String.Format("{0}_{1:O}", p.ChannelId, p.StartDate), // Construct an id from the channel and start date,
+ StartDate = GetDate(p.StartDate),
+ Name = p.Title,
+ Overview = p.Description,
+ ShortOverview = p.Description,
+ ProductionYear = !p.CopyrightDate.HasValue ? (int?)null : p.CopyrightDate.Value.Year,
+ SeasonNumber = p.Episode == null ? null : p.Episode.Series,
+ IsSeries = p.Episode != null,
+ IsRepeat = p.IsRepeat,
+ IsPremiere = p.Premiere != null,
+ IsKids = p.Categories.Any(c => info.KidsCategories.Contains(c, StringComparer.InvariantCultureIgnoreCase)),
+ IsMovie = p.Categories.Any(c => info.MovieCategories.Contains(c, StringComparer.InvariantCultureIgnoreCase)),
+ IsNews = p.Categories.Any(c => info.NewsCategories.Contains(c, StringComparer.InvariantCultureIgnoreCase)),
+ IsSports = p.Categories.Any(c => info.SportsCategories.Contains(c, StringComparer.InvariantCultureIgnoreCase)),
+ ImageUrl = p.Icon != null && !String.IsNullOrEmpty(p.Icon.Source) ? p.Icon.Source : null,
+ HasImage = p.Icon != null && !String.IsNullOrEmpty(p.Icon.Source),
+ OfficialRating = p.Rating != null && !String.IsNullOrEmpty(p.Rating.Value) ? p.Rating.Value : null,
+ CommunityRating = p.StarRating.HasValue ? p.StarRating.Value : (float?)null,
+ SeriesId = p.Episode != null ? p.Title.GetMD5().ToString("N") : null
+ });
+ }
+
+ private DateTime GetDate(DateTime date)
+ {
+ if (date.Kind != DateTimeKind.Utc)
+ {
+ date = DateTime.SpecifyKind(date, DateTimeKind.Utc);
+ }
+ return date;
+ }
+
+ public async Task AddMetadata(ListingsProviderInfo info, List<ChannelInfo> channels, CancellationToken cancellationToken)
+ {
+ // Add the channel image url
+ var path = await GetXml(info.Path, cancellationToken).ConfigureAwait(false);
+ var reader = new XmlTvReader(path, GetLanguage(), null);
+ var results = reader.GetChannels().ToList();
+
+ if (channels != null)
+ {
+ channels.ForEach(c =>
+ {
+ var channelNumber = info.GetMappedChannel(c.Number);
+ var match = results.FirstOrDefault(r => string.Equals(r.Id, channelNumber, StringComparison.OrdinalIgnoreCase));
+
+ if (match != null && match.Icon != null && !String.IsNullOrEmpty(match.Icon.Source))
+ {
+ c.ImageUrl = match.Icon.Source;
+ }
+ });
+ }
+ }
+
+ public Task Validate(ListingsProviderInfo info, bool validateLogin, bool validateListings)
+ {
+ // Assume all urls are valid. check files for existence
+ if (!info.Path.StartsWith("http", StringComparison.OrdinalIgnoreCase) && !File.Exists(info.Path))
+ {
+ throw new FileNotFoundException("Could not find the XmlTv file specified:", info.Path);
+ }
+
+ return Task.FromResult(true);
+ }
+
+ public async Task<List<NameIdPair>> GetLineups(ListingsProviderInfo info, string country, string location)
+ {
+ // In theory this should never be called because there is always only one lineup
+ var path = await GetXml(info.Path, CancellationToken.None).ConfigureAwait(false);
+ var reader = new XmlTvReader(path, GetLanguage(), null);
+ var results = reader.GetChannels();
+
+ // Should this method be async?
+ return results.Select(c => new NameIdPair() { Id = c.Id, Name = c.DisplayName }).ToList();
+ }
+
+ public async Task<List<ChannelInfo>> GetChannels(ListingsProviderInfo info, CancellationToken cancellationToken)
+ {
+ // In theory this should never be called because there is always only one lineup
+ var path = await GetXml(info.Path, cancellationToken).ConfigureAwait(false);
+ var reader = new XmlTvReader(path, GetLanguage(), null);
+ var results = reader.GetChannels();
+
+ // Should this method be async?
+ return results.Select(c => new ChannelInfo()
+ {
+ Id = c.Id,
+ Name = c.DisplayName,
+ ImageUrl = c.Icon != null && !String.IsNullOrEmpty(c.Icon.Source) ? c.Icon.Source : null,
+ Number = c.Id
+
+ }).ToList();
+ }
+ }
+} \ No newline at end of file
diff --git a/MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs b/MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs
index bf7f561ec..f32a4b59e 100644
--- a/MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs
+++ b/MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs
@@ -30,6 +30,8 @@ using System.Threading.Tasks;
using CommonIO;
using IniParser;
using IniParser.Model;
+using MediaBrowser.Common.Events;
+using MediaBrowser.Model.Events;
namespace MediaBrowser.Server.Implementations.LiveTv
{
@@ -64,6 +66,11 @@ namespace MediaBrowser.Server.Implementations.LiveTv
private readonly List<IListingsProvider> _listingProviders = new List<IListingsProvider>();
private readonly IFileSystem _fileSystem;
+ public event EventHandler<GenericEventArgs<TimerEventInfo>> SeriesTimerCancelled;
+ public event EventHandler<GenericEventArgs<TimerEventInfo>> TimerCancelled;
+ public event EventHandler<GenericEventArgs<TimerEventInfo>> TimerCreated;
+ public event EventHandler<GenericEventArgs<TimerEventInfo>> SeriesTimerCreated;
+
public LiveTvManager(IApplicationHost appHost, IServerConfigurationManager config, ILogger logger, IItemRepository itemRepo, IImageProcessor imageProcessor, IUserDataManager userDataManager, IDtoService dtoService, IUserManager userManager, ILibraryManager libraryManager, ITaskManager taskManager, ILocalizationManager localization, IJsonSerializer jsonSerializer, IProviderManager providerManager, IFileSystem fileSystem)
{
_config = config;
@@ -133,10 +140,13 @@ namespace MediaBrowser.Server.Implementations.LiveTv
{
var user = string.IsNullOrEmpty(query.UserId) ? null : _userManager.GetUserById(query.UserId);
+ var topFolder = await GetInternalLiveTvFolder(cancellationToken).ConfigureAwait(false);
+
var channels = _libraryManager.GetItemList(new InternalItemsQuery
{
IncludeItemTypes = new[] { typeof(LiveTvChannel).Name },
- SortBy = new[] { ItemSortBy.SortName }
+ SortBy = new[] { ItemSortBy.SortName },
+ TopParentIds = new[] { topFolder.Id.ToString("N") }
}).Cast<LiveTvChannel>();
@@ -646,7 +656,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv
item.Audio = info.Audio;
item.ChannelId = channel.Id.ToString("N");
item.CommunityRating = item.CommunityRating ?? info.CommunityRating;
- item.EndDate = info.EndDate;
+
item.EpisodeTitle = info.EpisodeTitle;
item.ExternalId = info.Id;
item.Genres = info.Genres;
@@ -663,7 +673,19 @@ namespace MediaBrowser.Server.Implementations.LiveTv
item.OfficialRating = item.OfficialRating ?? info.OfficialRating;
item.Overview = item.Overview ?? info.Overview;
item.RunTimeTicks = (info.EndDate - info.StartDate).Ticks;
+
+ if (item.StartDate != info.StartDate)
+ {
+ forceUpdate = true;
+ }
item.StartDate = info.StartDate;
+
+ if (item.EndDate != info.EndDate)
+ {
+ forceUpdate = true;
+ }
+ item.EndDate = info.EndDate;
+
item.HomePageUrl = info.HomePageUrl;
item.ProductionYear = info.ProductionYear;
@@ -884,6 +906,14 @@ namespace MediaBrowser.Server.Implementations.LiveTv
{
var user = string.IsNullOrEmpty(query.UserId) ? null : _userManager.GetUserById(query.UserId);
+ var topFolder = await GetInternalLiveTvFolder(cancellationToken).ConfigureAwait(false);
+
+ if (query.SortBy.Length == 0)
+ {
+ // Unless something else was specified, order by start date to take advantage of a specialized index
+ query.SortBy = new[] { ItemSortBy.StartDate };
+ }
+
var internalQuery = new InternalItemsQuery(user)
{
IncludeItemTypes = new[] { typeof(LiveTvProgram).Name },
@@ -900,7 +930,8 @@ namespace MediaBrowser.Server.Implementations.LiveTv
Limit = query.Limit,
SortBy = query.SortBy,
SortOrder = query.SortOrder ?? SortOrder.Ascending,
- EnableTotalRecordCount = query.EnableTotalRecordCount
+ EnableTotalRecordCount = query.EnableTotalRecordCount,
+ TopParentIds = new[] { topFolder.Id.ToString("N") }
};
if (query.HasAired.HasValue)
@@ -917,7 +948,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv
var queryResult = _libraryManager.QueryItems(internalQuery);
- var returnArray = _dtoService.GetBaseItemDtos(queryResult.Items, options, user).ToArray();
+ var returnArray = (await _dtoService.GetBaseItemDtos(queryResult.Items, options, user).ConfigureAwait(false)).ToArray();
var result = new QueryResult<BaseItemDto>
{
@@ -932,6 +963,8 @@ namespace MediaBrowser.Server.Implementations.LiveTv
{
var user = _userManager.GetUserById(query.UserId);
+ var topFolder = await GetInternalLiveTvFolder(cancellationToken).ConfigureAwait(false);
+
var internalQuery = new InternalItemsQuery(user)
{
IncludeItemTypes = new[] { typeof(LiveTvProgram).Name },
@@ -940,12 +973,13 @@ namespace MediaBrowser.Server.Implementations.LiveTv
IsSports = query.IsSports,
IsKids = query.IsKids,
EnableTotalRecordCount = query.EnableTotalRecordCount,
- SortBy = new[] { ItemSortBy.StartDate }
+ SortBy = new[] { ItemSortBy.StartDate },
+ TopParentIds = new[] { topFolder.Id.ToString("N") }
};
if (query.Limit.HasValue)
{
- internalQuery.Limit = Math.Max(query.Limit.Value * 5, 300);
+ internalQuery.Limit = Math.Max(query.Limit.Value * 4, 200);
}
if (query.HasAired.HasValue)
@@ -994,7 +1028,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv
var user = _userManager.GetUserById(query.UserId);
- var returnArray = _dtoService.GetBaseItemDtos(internalResult.Items, options, user).ToArray();
+ var returnArray = (await _dtoService.GetBaseItemDtos(internalResult.Items, options, user).ConfigureAwait(false)).ToArray();
var result = new QueryResult<BaseItemDto>
{
@@ -1098,6 +1132,8 @@ namespace MediaBrowser.Server.Implementations.LiveTv
private async Task RefreshChannelsInternal(IProgress<double> progress, CancellationToken cancellationToken)
{
+ EmbyTV.EmbyTV.Current.CreateRecordingFolders();
+
var numComplete = 0;
double progressPerService = _services.Count == 0
? 0
@@ -1396,7 +1432,8 @@ namespace MediaBrowser.Server.Implementations.LiveTv
ExcludeLocationTypes = new[] { LocationType.Virtual },
Limit = Math.Min(200, query.Limit ?? int.MaxValue),
SortBy = new[] { ItemSortBy.DateCreated },
- SortOrder = SortOrder.Descending
+ SortOrder = SortOrder.Descending,
+ EnableTotalRecordCount = query.EnableTotalRecordCount
});
}
@@ -1425,7 +1462,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv
internalQuery.ChannelIds = new[] { query.ChannelId };
}
- var queryResult = _libraryManager.GetItemList(internalQuery, new string[] { });
+ var queryResult = _libraryManager.GetItemList(internalQuery);
IEnumerable<ILiveTvRecording> recordings = queryResult.Cast<ILiveTvRecording>();
if (!string.IsNullOrWhiteSpace(query.Id))
@@ -1629,7 +1666,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv
var internalResult = await GetInternalRecordings(query, cancellationToken).ConfigureAwait(false);
- var returnArray = _dtoService.GetBaseItemDtos(internalResult.Items, options, user).ToArray();
+ var returnArray = (await _dtoService.GetBaseItemDtos(internalResult.Items, options, user).ConfigureAwait(false)).ToArray();
return new QueryResult<BaseItemDto>
{
@@ -1656,6 +1693,18 @@ namespace MediaBrowser.Server.Implementations.LiveTv
var results = await Task.WhenAll(tasks).ConfigureAwait(false);
var timers = results.SelectMany(i => i.ToList());
+ if (query.IsActive.HasValue)
+ {
+ if (query.IsActive.Value)
+ {
+ timers = timers.Where(i => i.Item1.Status == RecordingStatus.InProgress);
+ }
+ else
+ {
+ timers = timers.Where(i => i.Item1.Status != RecordingStatus.InProgress);
+ }
+ }
+
if (!string.IsNullOrEmpty(query.ChannelId))
{
var guid = new Guid(query.ChannelId);
@@ -1757,6 +1806,14 @@ namespace MediaBrowser.Server.Implementations.LiveTv
await service.CancelTimerAsync(timer.ExternalId, CancellationToken.None).ConfigureAwait(false);
_lastRecordingRefreshTime = DateTime.MinValue;
+
+ EventHelper.QueueEventIfNotNull(TimerCancelled, this, new GenericEventArgs<TimerEventInfo>
+ {
+ Argument = new TimerEventInfo
+ {
+ Id = id
+ }
+ }, _logger);
}
public async Task CancelSeriesTimer(string id)
@@ -1772,6 +1829,14 @@ namespace MediaBrowser.Server.Implementations.LiveTv
await service.CancelSeriesTimerAsync(timer.ExternalId, CancellationToken.None).ConfigureAwait(false);
_lastRecordingRefreshTime = DateTime.MinValue;
+
+ EventHelper.QueueEventIfNotNull(SeriesTimerCancelled, this, new GenericEventArgs<TimerEventInfo>
+ {
+ Argument = new TimerEventInfo
+ {
+ Id = id
+ }
+ }, _logger);
}
public async Task<BaseItemDto> GetRecording(string id, DtoOptions options, CancellationToken cancellationToken, User user = null)
@@ -1868,9 +1933,10 @@ namespace MediaBrowser.Server.Implementations.LiveTv
MaxStartDate = now,
MinEndDate = now,
Limit = channelIds.Length,
- SortBy = new[] { "StartDate" }
+ SortBy = new[] { "StartDate" },
+ TopParentIds = new[] { GetInternalLiveTvFolder(CancellationToken.None).Result.Id.ToString("N") }
- }, new string[] { }).ToList();
+ }).ToList();
foreach (var tuple in tuples)
{
@@ -1957,10 +2023,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv
var defaults = await GetNewTimerDefaultsInternal(cancellationToken, program).ConfigureAwait(false);
var info = _tvDtoService.GetSeriesTimerInfoDto(defaults.Item1, defaults.Item2, null);
- info.Days = new List<DayOfWeek>
- {
- program.StartDate.ToLocalTime().DayOfWeek
- };
+ info.Days = defaults.Item1.Days;
info.DayPattern = _tvDtoService.GetDayPattern(info.Days);
@@ -1991,9 +2054,29 @@ namespace MediaBrowser.Server.Implementations.LiveTv
var defaultValues = await service.GetNewTimerDefaultsAsync(cancellationToken).ConfigureAwait(false);
info.Priority = defaultValues.Priority;
- await service.CreateTimerAsync(info, cancellationToken).ConfigureAwait(false);
+ string newTimerId = null;
+ var supportsNewTimerIds = service as ISupportsNewTimerIds;
+ if (supportsNewTimerIds != null)
+ {
+ newTimerId = await supportsNewTimerIds.CreateTimer(info, cancellationToken).ConfigureAwait(false);
+ newTimerId = _tvDtoService.GetInternalTimerId(timer.ServiceName, newTimerId).ToString("N");
+ }
+ else
+ {
+ await service.CreateTimerAsync(info, cancellationToken).ConfigureAwait(false);
+ }
+
_lastRecordingRefreshTime = DateTime.MinValue;
_logger.Info("New recording scheduled");
+
+ EventHelper.QueueEventIfNotNull(TimerCreated, this, new GenericEventArgs<TimerEventInfo>
+ {
+ Argument = new TimerEventInfo
+ {
+ ProgramId = _tvDtoService.GetInternalProgramId(timer.ServiceName, info.ProgramId).ToString("N"),
+ Id = newTimerId
+ }
+ }, _logger);
}
public async Task CreateSeriesTimer(SeriesTimerInfoDto timer, CancellationToken cancellationToken)
@@ -2006,8 +2089,28 @@ namespace MediaBrowser.Server.Implementations.LiveTv
var defaultValues = await service.GetNewTimerDefaultsAsync(cancellationToken).ConfigureAwait(false);
info.Priority = defaultValues.Priority;
- await service.CreateSeriesTimerAsync(info, cancellationToken).ConfigureAwait(false);
+ string newTimerId = null;
+ var supportsNewTimerIds = service as ISupportsNewTimerIds;
+ if (supportsNewTimerIds != null)
+ {
+ newTimerId = await supportsNewTimerIds.CreateSeriesTimer(info, cancellationToken).ConfigureAwait(false);
+ newTimerId = _tvDtoService.GetInternalSeriesTimerId(timer.ServiceName, newTimerId).ToString("N");
+ }
+ else
+ {
+ await service.CreateSeriesTimerAsync(info, cancellationToken).ConfigureAwait(false);
+ }
+
_lastRecordingRefreshTime = DateTime.MinValue;
+
+ EventHelper.QueueEventIfNotNull(SeriesTimerCreated, this, new GenericEventArgs<TimerEventInfo>
+ {
+ Argument = new TimerEventInfo
+ {
+ ProgramId = _tvDtoService.GetInternalProgramId(timer.ServiceName, info.ProgramId).ToString("N"),
+ Id = newTimerId
+ }
+ }, _logger);
}
public async Task UpdateTimer(TimerInfoDto timer, CancellationToken cancellationToken)
@@ -2423,6 +2526,79 @@ namespace MediaBrowser.Server.Implementations.LiveTv
return info;
}
+ public void DeleteListingsProvider(string id)
+ {
+ var config = GetConfiguration();
+
+ config.ListingProviders = config.ListingProviders.Where(i => !string.Equals(id, i.Id, StringComparison.OrdinalIgnoreCase)).ToList();
+
+ _config.SaveConfiguration("livetv", config);
+ _taskManager.CancelIfRunningAndQueue<RefreshChannelsScheduledTask>();
+ }
+
+ public async Task<TunerChannelMapping> SetChannelMapping(string providerId, string tunerChannelNumber, string providerChannelNumber)
+ {
+ var config = GetConfiguration();
+
+ var listingsProviderInfo = config.ListingProviders.First(i => string.Equals(providerId, i.Id, StringComparison.OrdinalIgnoreCase));
+ listingsProviderInfo.ChannelMappings = listingsProviderInfo.ChannelMappings.Where(i => !string.Equals(i.Name, tunerChannelNumber, StringComparison.OrdinalIgnoreCase)).ToArray();
+
+ if (!string.Equals(tunerChannelNumber, providerChannelNumber, StringComparison.OrdinalIgnoreCase))
+ {
+ var list = listingsProviderInfo.ChannelMappings.ToList();
+ list.Add(new NameValuePair
+ {
+ Name = tunerChannelNumber,
+ Value = providerChannelNumber
+ });
+ listingsProviderInfo.ChannelMappings = list.ToArray();
+ }
+
+ _config.SaveConfiguration("livetv", config);
+
+ var tunerChannels = await GetChannelsForListingsProvider(providerId, CancellationToken.None)
+ .ConfigureAwait(false);
+
+ var providerChannels = await GetChannelsFromListingsProviderData(providerId, CancellationToken.None)
+ .ConfigureAwait(false);
+
+ var mappings = listingsProviderInfo.ChannelMappings.ToList();
+
+ var tunerChannelMappings =
+ tunerChannels.Select(i => GetTunerChannelMapping(i, mappings, providerChannels)).ToList();
+
+ _taskManager.CancelIfRunningAndQueue<RefreshChannelsScheduledTask>();
+
+ return tunerChannelMappings.First(i => string.Equals(i.Number, tunerChannelNumber, StringComparison.OrdinalIgnoreCase));
+ }
+
+ public TunerChannelMapping GetTunerChannelMapping(ChannelInfo channel, List<NameValuePair> mappings, List<ChannelInfo> providerChannels)
+ {
+ var result = new TunerChannelMapping
+ {
+ Name = channel.Number + " " + channel.Name,
+ Number = channel.Number
+ };
+
+ var mapping = mappings.FirstOrDefault(i => string.Equals(i.Name, channel.Number, StringComparison.OrdinalIgnoreCase));
+ var providerChannelNumber = channel.Number;
+
+ if (mapping != null)
+ {
+ providerChannelNumber = mapping.Value;
+ }
+
+ var providerChannel = providerChannels.FirstOrDefault(i => string.Equals(i.Number, providerChannelNumber, StringComparison.OrdinalIgnoreCase));
+
+ if (providerChannel != null)
+ {
+ result.ProviderChannelNumber = providerChannel.Number;
+ result.ProviderChannelName = providerChannel.Name;
+ }
+
+ return result;
+ }
+
public Task<List<NameIdPair>> GetLineups(string providerType, string providerId, string country, string location)
{
var config = GetConfiguration();
@@ -2522,5 +2698,18 @@ namespace MediaBrowser.Server.Implementations.LiveTv
{
return new TunerHosts.SatIp.ChannelScan(_logger).Scan(info, cancellationToken);
}
+
+ public Task<List<ChannelInfo>> GetChannelsForListingsProvider(string id, CancellationToken cancellationToken)
+ {
+ var info = GetConfiguration().ListingProviders.First(i => string.Equals(i.Id, id, StringComparison.OrdinalIgnoreCase));
+ return EmbyTV.EmbyTV.Current.GetChannelsForListingsProvider(info, cancellationToken);
+ }
+
+ public Task<List<ChannelInfo>> GetChannelsFromListingsProviderData(string id, CancellationToken cancellationToken)
+ {
+ var info = GetConfiguration().ListingProviders.First(i => string.Equals(i.Id, id, StringComparison.OrdinalIgnoreCase));
+ var provider = _listingProviders.First(i => string.Equals(i.Type, info.Type, StringComparison.OrdinalIgnoreCase));
+ return provider.GetChannels(info, cancellationToken);
+ }
}
} \ No newline at end of file
diff --git a/MediaBrowser.Server.Implementations/LiveTv/LiveTvMediaSourceProvider.cs b/MediaBrowser.Server.Implementations/LiveTv/LiveTvMediaSourceProvider.cs
index d3bb87bc7..cdba1873e 100644
--- a/MediaBrowser.Server.Implementations/LiveTv/LiveTvMediaSourceProvider.cs
+++ b/MediaBrowser.Server.Implementations/LiveTv/LiveTvMediaSourceProvider.cs
@@ -83,7 +83,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv
}
var list = sources.ToList();
- var serverUrl = _appHost.LocalApiUrl;
+ var serverUrl = await _appHost.GetLocalApiUrl().ConfigureAwait(false);
foreach (var source in list)
{
diff --git a/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj b/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj
index 0aa499f69..c78306911 100644
--- a/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj
+++ b/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj
@@ -46,17 +46,18 @@
<HintPath>..\packages\CommonIO.1.0.0.9\lib\net45\CommonIO.dll</HintPath>
</Reference>
<Reference Include="Emby.XmlTv, Version=1.0.0.0, Culture=neutral, processorArchitecture=MSIL">
- <SpecificVersion>False</SpecificVersion>
- <HintPath>..\packages\Emby.XmlTv.1.0.0.48\lib\net45\Emby.XmlTv.dll</HintPath>
+ <HintPath>..\packages\Emby.XmlTv.1.0.0.55\lib\net45\Emby.XmlTv.dll</HintPath>
+ <Private>True</Private>
</Reference>
- <Reference Include="INIFileParser">
- <HintPath>..\packages\ini-parser.2.2.4\lib\net20\INIFileParser.dll</HintPath>
+ <Reference Include="INIFileParser, Version=2.3.0.0, Culture=neutral, PublicKeyToken=79af7b307b65cf3c, processorArchitecture=MSIL">
+ <HintPath>..\packages\ini-parser.2.3.0\lib\net20\INIFileParser.dll</HintPath>
+ <Private>True</Private>
</Reference>
<Reference Include="Interfaces.IO">
<HintPath>..\packages\Interfaces.IO.1.0.0.5\lib\portable-net45+sl4+wp71+win8+wpa81\Interfaces.IO.dll</HintPath>
</Reference>
- <Reference Include="MediaBrowser.Naming, Version=1.0.5981.21615, Culture=neutral, processorArchitecture=MSIL">
- <HintPath>..\packages\MediaBrowser.Naming.1.0.0.50\lib\portable-net45+sl4+wp71+win8+wpa81\MediaBrowser.Naming.dll</HintPath>
+ <Reference Include="MediaBrowser.Naming, Version=1.0.6012.15754, Culture=neutral, processorArchitecture=MSIL">
+ <HintPath>..\packages\MediaBrowser.Naming.1.0.0.52\lib\portable-net45+sl4+wp71+win8+wpa81\MediaBrowser.Naming.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="MoreLinq">
@@ -68,8 +69,8 @@
<Reference Include="ServiceStack.Api.Swagger">
<HintPath>..\ThirdParty\ServiceStack\ServiceStack.Api.Swagger.dll</HintPath>
</Reference>
- <Reference Include="SimpleInjector, Version=3.1.4.0, Culture=neutral, PublicKeyToken=984cb50dea722e99, processorArchitecture=MSIL">
- <HintPath>..\packages\SimpleInjector.3.1.4\lib\net45\SimpleInjector.dll</HintPath>
+ <Reference Include="SimpleInjector, Version=3.2.0.0, Culture=neutral, PublicKeyToken=984cb50dea722e99, processorArchitecture=MSIL">
+ <HintPath>..\packages\SimpleInjector.3.2.0\lib\net45\SimpleInjector.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="SocketHttpListener, Version=1.0.5955.1537, Culture=neutral, processorArchitecture=MSIL">
@@ -141,6 +142,7 @@
<Compile Include="EntryPoints\LoadRegistrations.cs" />
<Compile Include="EntryPoints\Notifications\Notifications.cs" />
<Compile Include="EntryPoints\Notifications\WebSocketNotifier.cs" />
+ <Compile Include="EntryPoints\RecordingNotifier.cs" />
<Compile Include="EntryPoints\RefreshUsersMetadata.cs" />
<Compile Include="EntryPoints\UsageEntryPoint.cs" />
<Compile Include="Connect\ConnectEntryPoint.cs" />
@@ -180,6 +182,7 @@
<Compile Include="HttpServer\SocketSharp\WebSocketSharpRequest.cs" />
<Compile Include="HttpServer\SocketSharp\WebSocketSharpResponse.cs" />
<Compile Include="Intros\DefaultIntroProvider.cs" />
+ <Compile Include="IO\FileRefresher.cs" />
<Compile Include="IO\LibraryMonitor.cs" />
<Compile Include="Library\CoreResolutionIgnoreRule.cs" />
<Compile Include="Library\LibraryManager.cs" />
@@ -223,6 +226,7 @@
<Compile Include="LiveTv\ChannelImageProvider.cs" />
<Compile Include="LiveTv\EmbyTV\DirectRecorder.cs" />
<Compile Include="LiveTv\EmbyTV\EmbyTV.cs" />
+ <Compile Include="LiveTv\EmbyTV\EmbyTVRegistration.cs" />
<Compile Include="LiveTv\EmbyTV\EncodedRecorder.cs" />
<Compile Include="LiveTv\EmbyTV\EntryPoint.cs" />
<Compile Include="LiveTv\EmbyTV\IRecorder.cs" />
@@ -231,7 +235,7 @@
<Compile Include="LiveTv\EmbyTV\SeriesTimerManager.cs" />
<Compile Include="LiveTv\EmbyTV\TimerManager.cs" />
<Compile Include="LiveTv\Listings\SchedulesDirect.cs" />
- <Compile Include="LiveTv\Listings\XmlTv.cs" />
+ <Compile Include="LiveTv\Listings\XmlTvListingsProvider.cs" />
<Compile Include="LiveTv\LiveTvConfigurationFactory.cs" />
<Compile Include="LiveTv\LiveTvDtoService.cs" />
<Compile Include="LiveTv\LiveTvManager.cs" />
@@ -276,7 +280,6 @@
<Compile Include="Notifications\NotificationManager.cs" />
<Compile Include="Persistence\SqliteFileOrganizationRepository.cs" />
<Compile Include="Notifications\SqliteNotificationsRepository.cs" />
- <Compile Include="Persistence\SqliteProviderInfoRepository.cs" />
<Compile Include="Persistence\TypeMapper.cs" />
<Compile Include="Photos\BaseDynamicImageProvider.cs" />
<Compile Include="Playlists\ManualPlaylistsFolder.cs" />
@@ -390,7 +393,103 @@
<EmbeddedResource Include="Localization\Ratings\ru.txt" />
</ItemGroup>
<ItemGroup>
+ <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-2.0.0.js">
+ <Link>swagger-ui\lib\handlebars-2.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\marked.js">
+ <Link>swagger-ui\lib\marked.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\swagger-client.js">
+ <Link>swagger-ui\lib\swagger-client.js</Link>
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="..\ThirdParty\ServiceStack\swagger-ui\lib\swagger-oauth.js">
+ <Link>swagger-ui\lib\swagger-oauth.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\o2c.html">
+ <Link>swagger-ui\o2c.html</Link>
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="..\ThirdParty\ServiceStack\swagger-ui\patch.js">
+ <Link>swagger-ui\patch.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\countries.json" />
+ <Content Include="..\ThirdParty\ServiceStack\swagger-ui\fonts\droid-sans-v6-latin-700.eot">
+ <Link>swagger-ui\fonts\droid-sans-v6-latin-700.eot</Link>
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="..\ThirdParty\ServiceStack\swagger-ui\fonts\droid-sans-v6-latin-700.ttf">
+ <Link>swagger-ui\fonts\droid-sans-v6-latin-700.ttf</Link>
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="..\ThirdParty\ServiceStack\swagger-ui\fonts\droid-sans-v6-latin-700.woff">
+ <Link>swagger-ui\fonts\droid-sans-v6-latin-700.woff</Link>
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="..\ThirdParty\ServiceStack\swagger-ui\fonts\droid-sans-v6-latin-700.woff2">
+ <Link>swagger-ui\fonts\droid-sans-v6-latin-700.woff2</Link>
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="..\ThirdParty\ServiceStack\swagger-ui\fonts\droid-sans-v6-latin-regular.eot">
+ <Link>swagger-ui\fonts\droid-sans-v6-latin-regular.eot</Link>
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="..\ThirdParty\ServiceStack\swagger-ui\fonts\droid-sans-v6-latin-regular.ttf">
+ <Link>swagger-ui\fonts\droid-sans-v6-latin-regular.ttf</Link>
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="..\ThirdParty\ServiceStack\swagger-ui\fonts\droid-sans-v6-latin-regular.woff">
+ <Link>swagger-ui\fonts\droid-sans-v6-latin-regular.woff</Link>
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="..\ThirdParty\ServiceStack\swagger-ui\fonts\droid-sans-v6-latin-regular.woff2">
+ <Link>swagger-ui\fonts\droid-sans-v6-latin-regular.woff2</Link>
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
<None Include="app.config" />
<EmbeddedResource Include="Localization\Core\core.json" />
<EmbeddedResource Include="Localization\Core\ar.json" />
@@ -607,10 +706,30 @@
<EmbeddedResource Include="Localization\Ratings\ca.txt" />
</ItemGroup>
<ItemGroup>
+ <Content Include="..\ThirdParty\ServiceStack\swagger-ui\css\reset.css">
+ <Link>swagger-ui\css\reset.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\css\typography.css">
+ <Link>swagger-ui\css\typography.css</Link>
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="..\ThirdParty\ServiceStack\swagger-ui\fonts\droid-sans-v6-latin-700.svg">
+ <Link>swagger-ui\fonts\droid-sans-v6-latin-700.svg</Link>
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="..\ThirdParty\ServiceStack\swagger-ui\fonts\droid-sans-v6-latin-regular.svg">
+ <Link>swagger-ui\fonts\droid-sans-v6-latin-regular.svg</Link>
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="..\ThirdParty\ServiceStack\swagger-ui\images\explorer_icons.png">
+ <Link>swagger-ui\images\explorer_icons.png</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>
@@ -631,58 +750,10 @@
<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\iso6392.txt" />
<EmbeddedResource Include="Localization\Ratings\be.txt" />
</ItemGroup>
diff --git a/MediaBrowser.Server.Implementations/Notifications/SqliteNotificationsRepository.cs b/MediaBrowser.Server.Implementations/Notifications/SqliteNotificationsRepository.cs
index 10e8c5699..be8c6d48d 100644
--- a/MediaBrowser.Server.Implementations/Notifications/SqliteNotificationsRepository.cs
+++ b/MediaBrowser.Server.Implementations/Notifications/SqliteNotificationsRepository.cs
@@ -15,74 +15,28 @@ namespace MediaBrowser.Server.Implementations.Notifications
{
public class SqliteNotificationsRepository : BaseSqliteRepository, INotificationsRepository
{
- private IDbConnection _connection;
- private readonly IServerApplicationPaths _appPaths;
+ public SqliteNotificationsRepository(ILogManager logManager, IServerApplicationPaths appPaths, IDbConnector dbConnector) : base(logManager, dbConnector)
+ {
+ DbFilePath = Path.Combine(appPaths.DataPath, "notifications.db");
+ }
public event EventHandler<NotificationUpdateEventArgs> NotificationAdded;
public event EventHandler<NotificationReadEventArgs> NotificationsMarkedRead;
public event EventHandler<NotificationUpdateEventArgs> NotificationUpdated;
- private IDbCommand _replaceNotificationCommand;
- private IDbCommand _markReadCommand;
- private IDbCommand _markAllReadCommand;
-
- public SqliteNotificationsRepository(ILogManager logManager, IServerApplicationPaths appPaths)
- : base(logManager)
+ public async Task Initialize()
{
- _appPaths = appPaths;
- }
-
- public async Task Initialize(IDbConnector dbConnector)
- {
- var dbFile = Path.Combine(_appPaths.DataPath, "notifications.db");
-
- _connection = await dbConnector.Connect(dbFile).ConfigureAwait(false);
-
- string[] queries = {
+ using (var connection = await CreateConnection().ConfigureAwait(false))
+ {
+ string[] queries = {
"create table if not exists Notifications (Id GUID NOT NULL, UserId GUID NOT NULL, Date DATETIME NOT NULL, Name TEXT NOT NULL, Description TEXT, Url TEXT, Level TEXT NOT NULL, IsRead BOOLEAN NOT NULL, Category TEXT NOT NULL, RelatedId TEXT, PRIMARY KEY (Id, UserId))",
"create index if not exists idx_Notifications1 on Notifications(Id)",
- "create index if not exists idx_Notifications2 on Notifications(UserId)",
-
- //pragmas
- "pragma temp_store = memory",
-
- "pragma shrink_memory"
+ "create index if not exists idx_Notifications2 on Notifications(UserId)"
};
- _connection.RunQueries(queries, Logger);
-
- PrepareStatements();
- }
-
- private void PrepareStatements()
- {
- _replaceNotificationCommand = _connection.CreateCommand();
- _replaceNotificationCommand.CommandText = "replace into Notifications (Id, UserId, Date, Name, Description, Url, Level, IsRead, Category, RelatedId) values (@Id, @UserId, @Date, @Name, @Description, @Url, @Level, @IsRead, @Category, @RelatedId)";
-
- _replaceNotificationCommand.Parameters.Add(_replaceNotificationCommand, "@Id");
- _replaceNotificationCommand.Parameters.Add(_replaceNotificationCommand, "@UserId");
- _replaceNotificationCommand.Parameters.Add(_replaceNotificationCommand, "@Date");
- _replaceNotificationCommand.Parameters.Add(_replaceNotificationCommand, "@Name");
- _replaceNotificationCommand.Parameters.Add(_replaceNotificationCommand, "@Description");
- _replaceNotificationCommand.Parameters.Add(_replaceNotificationCommand, "@Url");
- _replaceNotificationCommand.Parameters.Add(_replaceNotificationCommand, "@Level");
- _replaceNotificationCommand.Parameters.Add(_replaceNotificationCommand, "@IsRead");
- _replaceNotificationCommand.Parameters.Add(_replaceNotificationCommand, "@Category");
- _replaceNotificationCommand.Parameters.Add(_replaceNotificationCommand, "@RelatedId");
-
- _markReadCommand = _connection.CreateCommand();
- _markReadCommand.CommandText = "update Notifications set IsRead=@IsRead where Id=@Id and UserId=@UserId";
-
- _markReadCommand.Parameters.Add(_replaceNotificationCommand, "@UserId");
- _markReadCommand.Parameters.Add(_replaceNotificationCommand, "@IsRead");
- _markReadCommand.Parameters.Add(_replaceNotificationCommand, "@Id");
-
- _markAllReadCommand = _connection.CreateCommand();
- _markAllReadCommand.CommandText = "update Notifications set IsRead=@IsRead where UserId=@UserId";
-
- _markAllReadCommand.Parameters.Add(_replaceNotificationCommand, "@UserId");
- _markAllReadCommand.Parameters.Add(_replaceNotificationCommand, "@IsRead");
+ connection.RunQueries(queries, Logger);
+ }
}
/// <summary>
@@ -94,49 +48,52 @@ namespace MediaBrowser.Server.Implementations.Notifications
{
var result = new NotificationResult();
- using (var cmd = _connection.CreateCommand())
+ using (var connection = CreateConnection(true).Result)
{
- var clauses = new List<string>();
-
- if (query.IsRead.HasValue)
+ using (var cmd = connection.CreateCommand())
{
- clauses.Add("IsRead=@IsRead");
- cmd.Parameters.Add(cmd, "@IsRead", DbType.Boolean).Value = query.IsRead.Value;
- }
+ var clauses = new List<string>();
- clauses.Add("UserId=@UserId");
- cmd.Parameters.Add(cmd, "@UserId", DbType.Guid).Value = new Guid(query.UserId);
+ if (query.IsRead.HasValue)
+ {
+ clauses.Add("IsRead=@IsRead");
+ cmd.Parameters.Add(cmd, "@IsRead", DbType.Boolean).Value = query.IsRead.Value;
+ }
- var whereClause = " where " + string.Join(" And ", clauses.ToArray());
+ clauses.Add("UserId=@UserId");
+ cmd.Parameters.Add(cmd, "@UserId", DbType.Guid).Value = new Guid(query.UserId);
- cmd.CommandText = string.Format("select count(Id) from Notifications{0};select Id,UserId,Date,Name,Description,Url,Level,IsRead,Category,RelatedId from Notifications{0} order by IsRead asc, Date desc", whereClause);
+ var whereClause = " where " + string.Join(" And ", clauses.ToArray());
- using (var reader = cmd.ExecuteReader(CommandBehavior.SequentialAccess))
- {
- if (reader.Read())
- {
- result.TotalRecordCount = reader.GetInt32(0);
- }
+ cmd.CommandText = string.Format("select count(Id) from Notifications{0};select Id,UserId,Date,Name,Description,Url,Level,IsRead,Category,RelatedId from Notifications{0} order by IsRead asc, Date desc", whereClause);
- if (reader.NextResult())
+ using (var reader = cmd.ExecuteReader(CommandBehavior.SequentialAccess))
{
- var notifications = GetNotifications(reader);
-
- if (query.StartIndex.HasValue)
+ if (reader.Read())
{
- notifications = notifications.Skip(query.StartIndex.Value);
+ result.TotalRecordCount = reader.GetInt32(0);
}
- if (query.Limit.HasValue)
+ if (reader.NextResult())
{
- notifications = notifications.Take(query.Limit.Value);
- }
+ var notifications = GetNotifications(reader);
+
+ if (query.StartIndex.HasValue)
+ {
+ notifications = notifications.Skip(query.StartIndex.Value);
+ }
+
+ if (query.Limit.HasValue)
+ {
+ notifications = notifications.Take(query.Limit.Value);
+ }
- result.Notifications = notifications.ToArray();
+ result.Notifications = notifications.ToArray();
+ }
}
- }
- return result;
+ return result;
+ }
}
}
@@ -144,31 +101,34 @@ namespace MediaBrowser.Server.Implementations.Notifications
{
var result = new NotificationsSummary();
- using (var cmd = _connection.CreateCommand())
+ using (var connection = CreateConnection(true).Result)
{
- cmd.CommandText = "select Level from Notifications where UserId=@UserId and IsRead=@IsRead";
-
- cmd.Parameters.Add(cmd, "@UserId", DbType.Guid).Value = new Guid(userId);
- cmd.Parameters.Add(cmd, "@IsRead", DbType.Boolean).Value = false;
-
- using (var reader = cmd.ExecuteReader(CommandBehavior.SequentialAccess))
+ using (var cmd = connection.CreateCommand())
{
- var levels = new List<NotificationLevel>();
+ cmd.CommandText = "select Level from Notifications where UserId=@UserId and IsRead=@IsRead";
- while (reader.Read())
+ cmd.Parameters.Add(cmd, "@UserId", DbType.Guid).Value = new Guid(userId);
+ cmd.Parameters.Add(cmd, "@IsRead", DbType.Boolean).Value = false;
+
+ using (var reader = cmd.ExecuteReader(CommandBehavior.SequentialAccess))
{
- levels.Add(GetLevel(reader, 0));
- }
+ var levels = new List<NotificationLevel>();
+
+ while (reader.Read())
+ {
+ levels.Add(GetLevel(reader, 0));
+ }
- result.UnreadCount = levels.Count;
+ result.UnreadCount = levels.Count;
- if (levels.Count > 0)
- {
- result.MaxUnreadNotificationLevel = levels.Max();
+ if (levels.Count > 0)
+ {
+ result.MaxUnreadNotificationLevel = levels.Max();
+ }
}
- }
- return result;
+ return result;
+ }
}
}
@@ -179,10 +139,14 @@ namespace MediaBrowser.Server.Implementations.Notifications
/// <returns>IEnumerable{Notification}.</returns>
private IEnumerable<Notification> GetNotifications(IDataReader reader)
{
+ var list = new List<Notification>();
+
while (reader.Read())
{
- yield return GetNotification(reader);
+ list.Add(GetNotification(reader));
}
+
+ return list;
}
private Notification GetNotification(IDataReader reader)
@@ -273,59 +237,74 @@ namespace MediaBrowser.Server.Implementations.Notifications
cancellationToken.ThrowIfCancellationRequested();
- await WriteLock.WaitAsync(cancellationToken).ConfigureAwait(false);
-
- IDbTransaction transaction = null;
-
- try
+ using (var connection = await CreateConnection().ConfigureAwait(false))
{
- transaction = _connection.BeginTransaction();
+ using (var replaceNotificationCommand = connection.CreateCommand())
+ {
+ replaceNotificationCommand.CommandText = "replace into Notifications (Id, UserId, Date, Name, Description, Url, Level, IsRead, Category, RelatedId) values (@Id, @UserId, @Date, @Name, @Description, @Url, @Level, @IsRead, @Category, @RelatedId)";
+
+ replaceNotificationCommand.Parameters.Add(replaceNotificationCommand, "@Id");
+ replaceNotificationCommand.Parameters.Add(replaceNotificationCommand, "@UserId");
+ replaceNotificationCommand.Parameters.Add(replaceNotificationCommand, "@Date");
+ replaceNotificationCommand.Parameters.Add(replaceNotificationCommand, "@Name");
+ replaceNotificationCommand.Parameters.Add(replaceNotificationCommand, "@Description");
+ replaceNotificationCommand.Parameters.Add(replaceNotificationCommand, "@Url");
+ replaceNotificationCommand.Parameters.Add(replaceNotificationCommand, "@Level");
+ replaceNotificationCommand.Parameters.Add(replaceNotificationCommand, "@IsRead");
+ replaceNotificationCommand.Parameters.Add(replaceNotificationCommand, "@Category");
+ replaceNotificationCommand.Parameters.Add(replaceNotificationCommand, "@RelatedId");
+
+ IDbTransaction transaction = null;
+
+ try
+ {
+ transaction = connection.BeginTransaction();
- _replaceNotificationCommand.GetParameter(0).Value = new Guid(notification.Id);
- _replaceNotificationCommand.GetParameter(1).Value = new Guid(notification.UserId);
- _replaceNotificationCommand.GetParameter(2).Value = notification.Date.ToUniversalTime();
- _replaceNotificationCommand.GetParameter(3).Value = notification.Name;
- _replaceNotificationCommand.GetParameter(4).Value = notification.Description;
- _replaceNotificationCommand.GetParameter(5).Value = notification.Url;
- _replaceNotificationCommand.GetParameter(6).Value = notification.Level.ToString();
- _replaceNotificationCommand.GetParameter(7).Value = notification.IsRead;
- _replaceNotificationCommand.GetParameter(8).Value = string.Empty;
- _replaceNotificationCommand.GetParameter(9).Value = string.Empty;
+ replaceNotificationCommand.GetParameter(0).Value = new Guid(notification.Id);
+ replaceNotificationCommand.GetParameter(1).Value = new Guid(notification.UserId);
+ replaceNotificationCommand.GetParameter(2).Value = notification.Date.ToUniversalTime();
+ replaceNotificationCommand.GetParameter(3).Value = notification.Name;
+ replaceNotificationCommand.GetParameter(4).Value = notification.Description;
+ replaceNotificationCommand.GetParameter(5).Value = notification.Url;
+ replaceNotificationCommand.GetParameter(6).Value = notification.Level.ToString();
+ replaceNotificationCommand.GetParameter(7).Value = notification.IsRead;
+ replaceNotificationCommand.GetParameter(8).Value = string.Empty;
+ replaceNotificationCommand.GetParameter(9).Value = string.Empty;
- _replaceNotificationCommand.Transaction = transaction;
+ replaceNotificationCommand.Transaction = transaction;
- _replaceNotificationCommand.ExecuteNonQuery();
+ replaceNotificationCommand.ExecuteNonQuery();
- transaction.Commit();
- }
- catch (OperationCanceledException)
- {
- if (transaction != null)
- {
- transaction.Rollback();
- }
+ transaction.Commit();
+ }
+ catch (OperationCanceledException)
+ {
+ if (transaction != null)
+ {
+ transaction.Rollback();
+ }
- throw;
- }
- catch (Exception e)
- {
- Logger.ErrorException("Failed to save notification:", e);
+ throw;
+ }
+ catch (Exception e)
+ {
+ Logger.ErrorException("Failed to save notification:", e);
- if (transaction != null)
- {
- transaction.Rollback();
- }
+ if (transaction != null)
+ {
+ transaction.Rollback();
+ }
- throw;
- }
- finally
- {
- if (transaction != null)
- {
- transaction.Dispose();
+ throw;
+ }
+ finally
+ {
+ if (transaction != null)
+ {
+ transaction.Dispose();
+ }
+ }
}
-
- WriteLock.Release();
}
}
@@ -366,51 +345,58 @@ namespace MediaBrowser.Server.Implementations.Notifications
{
cancellationToken.ThrowIfCancellationRequested();
- await WriteLock.WaitAsync(cancellationToken).ConfigureAwait(false);
+ using (var connection = await CreateConnection().ConfigureAwait(false))
+ {
+ using (var markAllReadCommand = connection.CreateCommand())
+ {
+ markAllReadCommand.CommandText = "update Notifications set IsRead=@IsRead where UserId=@UserId";
- IDbTransaction transaction = null;
+ markAllReadCommand.Parameters.Add(markAllReadCommand, "@UserId");
+ markAllReadCommand.Parameters.Add(markAllReadCommand, "@IsRead");
- try
- {
- cancellationToken.ThrowIfCancellationRequested();
+ IDbTransaction transaction = null;
- transaction = _connection.BeginTransaction();
+ try
+ {
+ cancellationToken.ThrowIfCancellationRequested();
- _markAllReadCommand.GetParameter(0).Value = new Guid(userId);
- _markAllReadCommand.GetParameter(1).Value = isRead;
+ transaction = connection.BeginTransaction();
- _markAllReadCommand.ExecuteNonQuery();
+ markAllReadCommand.GetParameter(0).Value = new Guid(userId);
+ markAllReadCommand.GetParameter(1).Value = isRead;
- transaction.Commit();
- }
- catch (OperationCanceledException)
- {
- if (transaction != null)
- {
- transaction.Rollback();
- }
+ markAllReadCommand.ExecuteNonQuery();
- throw;
- }
- catch (Exception e)
- {
- Logger.ErrorException("Failed to save notification:", e);
+ transaction.Commit();
+ }
+ catch (OperationCanceledException)
+ {
+ if (transaction != null)
+ {
+ transaction.Rollback();
+ }
- if (transaction != null)
- {
- transaction.Rollback();
- }
+ throw;
+ }
+ catch (Exception e)
+ {
+ Logger.ErrorException("Failed to save notification:", e);
- throw;
- }
- finally
- {
- if (transaction != null)
- {
- transaction.Dispose();
- }
+ if (transaction != null)
+ {
+ transaction.Rollback();
+ }
- WriteLock.Release();
+ throw;
+ }
+ finally
+ {
+ if (transaction != null)
+ {
+ transaction.Dispose();
+ }
+ }
+ }
}
}
@@ -418,72 +404,66 @@ namespace MediaBrowser.Server.Implementations.Notifications
{
cancellationToken.ThrowIfCancellationRequested();
- await WriteLock.WaitAsync(cancellationToken).ConfigureAwait(false);
-
- IDbTransaction transaction = null;
-
- try
+ using (var connection = await CreateConnection().ConfigureAwait(false))
{
- cancellationToken.ThrowIfCancellationRequested();
+ using (var markReadCommand = connection.CreateCommand())
+ {
+ markReadCommand.CommandText = "update Notifications set IsRead=@IsRead where Id=@Id and UserId=@UserId";
- transaction = _connection.BeginTransaction();
+ markReadCommand.Parameters.Add(markReadCommand, "@UserId");
+ markReadCommand.Parameters.Add(markReadCommand, "@IsRead");
+ markReadCommand.Parameters.Add(markReadCommand, "@Id");
- _markReadCommand.GetParameter(0).Value = new Guid(userId);
- _markReadCommand.GetParameter(1).Value = isRead;
+ IDbTransaction transaction = null;
- foreach (var id in notificationIdList)
- {
- _markReadCommand.GetParameter(2).Value = id;
+ try
+ {
+ cancellationToken.ThrowIfCancellationRequested();
- _markReadCommand.Transaction = transaction;
+ transaction = connection.BeginTransaction();
- _markReadCommand.ExecuteNonQuery();
- }
+ markReadCommand.GetParameter(0).Value = new Guid(userId);
+ markReadCommand.GetParameter(1).Value = isRead;
- transaction.Commit();
- }
- catch (OperationCanceledException)
- {
- if (transaction != null)
- {
- transaction.Rollback();
- }
+ foreach (var id in notificationIdList)
+ {
+ markReadCommand.GetParameter(2).Value = id;
- throw;
- }
- catch (Exception e)
- {
- Logger.ErrorException("Failed to save notification:", e);
+ markReadCommand.Transaction = transaction;
- if (transaction != null)
- {
- transaction.Rollback();
- }
+ markReadCommand.ExecuteNonQuery();
+ }
- throw;
- }
- finally
- {
- if (transaction != null)
- {
- transaction.Dispose();
- }
+ transaction.Commit();
+ }
+ catch (OperationCanceledException)
+ {
+ if (transaction != null)
+ {
+ transaction.Rollback();
+ }
- WriteLock.Release();
- }
- }
+ throw;
+ }
+ catch (Exception e)
+ {
+ Logger.ErrorException("Failed to save notification:", e);
- protected override void CloseConnection()
- {
- if (_connection != null)
- {
- if (_connection.IsOpen())
- {
- _connection.Close();
- }
+ if (transaction != null)
+ {
+ transaction.Rollback();
+ }
- _connection.Dispose();
- _connection = null;
+ throw;
+ }
+ finally
+ {
+ if (transaction != null)
+ {
+ transaction.Dispose();
+ }
+ }
+ }
}
}
}
diff --git a/MediaBrowser.Server.Implementations/Persistence/BaseSqliteRepository.cs b/MediaBrowser.Server.Implementations/Persistence/BaseSqliteRepository.cs
index 395907844..233ab56fe 100644
--- a/MediaBrowser.Server.Implementations/Persistence/BaseSqliteRepository.cs
+++ b/MediaBrowser.Server.Implementations/Persistence/BaseSqliteRepository.cs
@@ -8,14 +8,36 @@ namespace MediaBrowser.Server.Implementations.Persistence
{
public abstract class BaseSqliteRepository : IDisposable
{
- protected readonly SemaphoreSlim WriteLock = new SemaphoreSlim(1, 1);
+ protected SemaphoreSlim WriteLock = new SemaphoreSlim(1, 1);
+ protected readonly IDbConnector DbConnector;
protected ILogger Logger;
- protected BaseSqliteRepository(ILogManager logManager)
+ protected string DbFilePath { get; set; }
+
+ protected BaseSqliteRepository(ILogManager logManager, IDbConnector dbConnector)
{
+ DbConnector = dbConnector;
Logger = logManager.GetLogger(GetType().Name);
}
+ protected virtual bool EnableConnectionPooling
+ {
+ get { return true; }
+ }
+
+ protected virtual async Task<IDbConnection> CreateConnection(bool isReadOnly = false)
+ {
+ var connection = await DbConnector.Connect(DbFilePath, false, true).ConfigureAwait(false);
+
+ connection.RunQueries(new[]
+ {
+ "pragma temp_store = memory"
+
+ }, Logger);
+
+ return connection;
+ }
+
private bool _disposed;
protected void CheckDisposed()
{
@@ -84,6 +106,9 @@ namespace MediaBrowser.Server.Implementations.Persistence
}
}
- protected abstract void CloseConnection();
+ protected virtual void CloseConnection()
+ {
+
+ }
}
-}
+} \ No newline at end of file
diff --git a/MediaBrowser.Server.Implementations/Persistence/CleanDatabaseScheduledTask.cs b/MediaBrowser.Server.Implementations/Persistence/CleanDatabaseScheduledTask.cs
index 2a2f9a09d..b11a3e496 100644
--- a/MediaBrowser.Server.Implementations/Persistence/CleanDatabaseScheduledTask.cs
+++ b/MediaBrowser.Server.Implementations/Persistence/CleanDatabaseScheduledTask.cs
@@ -15,6 +15,7 @@ using System.Threading.Tasks;
using CommonIO;
using MediaBrowser.Controller.Channels;
using MediaBrowser.Controller.Entities.Audio;
+using MediaBrowser.Controller.LiveTv;
using MediaBrowser.Controller.Localization;
using MediaBrowser.Controller.Net;
using MediaBrowser.Server.Implementations.ScheduledTasks;
@@ -145,7 +146,8 @@ namespace MediaBrowser.Server.Implementations.Persistence
{
var itemIds = _libraryManager.GetItemIds(new InternalItemsQuery
{
- IsCurrentSchema = false
+ IsCurrentSchema = false,
+ ExcludeItemTypes = new[] { typeof(LiveTvProgram).Name }
});
var numComplete = 0;
@@ -236,14 +238,14 @@ namespace MediaBrowser.Server.Implementations.Persistence
// These have their own cleanup routines
ExcludeItemTypes = new[]
{
- typeof(Person).Name,
- typeof(Genre).Name,
- typeof(MusicGenre).Name,
- typeof(GameGenre).Name,
- typeof(Studio).Name,
- typeof(Year).Name,
- typeof(Channel).Name,
- typeof(AggregateFolder).Name,
+ typeof(Person).Name,
+ typeof(Genre).Name,
+ typeof(MusicGenre).Name,
+ typeof(GameGenre).Name,
+ typeof(Studio).Name,
+ typeof(Year).Name,
+ typeof(Channel).Name,
+ typeof(AggregateFolder).Name,
typeof(CollectionFolder).Name
}
});
@@ -313,8 +315,8 @@ namespace MediaBrowser.Server.Implementations.Persistence
public IEnumerable<ITaskTrigger> GetDefaultTriggers()
{
- return new ITaskTrigger[]
- {
+ return new ITaskTrigger[]
+ {
new IntervalTrigger{ Interval = TimeSpan.FromHours(24)}
};
}
diff --git a/MediaBrowser.Server.Implementations/Persistence/IDbConnector.cs b/MediaBrowser.Server.Implementations/Persistence/IDbConnector.cs
index cac9fe983..596cf8407 100644
--- a/MediaBrowser.Server.Implementations/Persistence/IDbConnector.cs
+++ b/MediaBrowser.Server.Implementations/Persistence/IDbConnector.cs
@@ -5,6 +5,6 @@ namespace MediaBrowser.Server.Implementations.Persistence
{
public interface IDbConnector
{
- Task<IDbConnection> Connect(string dbPath);
+ Task<IDbConnection> Connect(string dbPath, bool isReadOnly, bool enablePooling = false, int? cacheSize = null);
}
}
diff --git a/MediaBrowser.Server.Implementations/Persistence/MediaStreamColumns.cs b/MediaBrowser.Server.Implementations/Persistence/MediaStreamColumns.cs
index 948e99cb8..1d9be2e0d 100644
--- a/MediaBrowser.Server.Implementations/Persistence/MediaStreamColumns.cs
+++ b/MediaBrowser.Server.Implementations/Persistence/MediaStreamColumns.cs
@@ -28,6 +28,8 @@ namespace MediaBrowser.Server.Implementations.Persistence
AddNalColumn();
AddIsAvcColumn();
AddTitleColumn();
+ AddTimeBaseColumn();
+ AddCodecTimeBaseColumn();
}
private void AddIsAvcColumn()
@@ -61,6 +63,68 @@ namespace MediaBrowser.Server.Implementations.Persistence
_connection.RunQueries(new[] { builder.ToString() }, _logger);
}
+ private void AddTimeBaseColumn()
+ {
+ using (var cmd = _connection.CreateCommand())
+ {
+ cmd.CommandText = "PRAGMA table_info(mediastreams)";
+
+ using (var reader = cmd.ExecuteReader(CommandBehavior.SequentialAccess | CommandBehavior.SingleResult))
+ {
+ while (reader.Read())
+ {
+ if (!reader.IsDBNull(1))
+ {
+ var name = reader.GetString(1);
+
+ if (string.Equals(name, "TimeBase", StringComparison.OrdinalIgnoreCase))
+ {
+ return;
+ }
+ }
+ }
+ }
+ }
+
+ var builder = new StringBuilder();
+
+ builder.AppendLine("alter table mediastreams");
+ builder.AppendLine("add column TimeBase TEXT");
+
+ _connection.RunQueries(new[] { builder.ToString() }, _logger);
+ }
+
+ private void AddCodecTimeBaseColumn()
+ {
+ using (var cmd = _connection.CreateCommand())
+ {
+ cmd.CommandText = "PRAGMA table_info(mediastreams)";
+
+ using (var reader = cmd.ExecuteReader(CommandBehavior.SequentialAccess | CommandBehavior.SingleResult))
+ {
+ while (reader.Read())
+ {
+ if (!reader.IsDBNull(1))
+ {
+ var name = reader.GetString(1);
+
+ if (string.Equals(name, "CodecTimeBase", StringComparison.OrdinalIgnoreCase))
+ {
+ return;
+ }
+ }
+ }
+ }
+ }
+
+ var builder = new StringBuilder();
+
+ builder.AppendLine("alter table mediastreams");
+ builder.AppendLine("add column CodecTimeBase TEXT");
+
+ _connection.RunQueries(new[] { builder.ToString() }, _logger);
+ }
+
private void AddTitleColumn()
{
using (var cmd = _connection.CreateCommand())
diff --git a/MediaBrowser.Server.Implementations/Persistence/SqliteDisplayPreferencesRepository.cs b/MediaBrowser.Server.Implementations/Persistence/SqliteDisplayPreferencesRepository.cs
index 6077cfdba..40970dbe4 100644
--- a/MediaBrowser.Server.Implementations/Persistence/SqliteDisplayPreferencesRepository.cs
+++ b/MediaBrowser.Server.Implementations/Persistence/SqliteDisplayPreferencesRepository.cs
@@ -18,12 +18,11 @@ namespace MediaBrowser.Server.Implementations.Persistence
/// </summary>
public class SqliteDisplayPreferencesRepository : BaseSqliteRepository, IDisplayPreferencesRepository
{
- private IDbConnection _connection;
-
- public SqliteDisplayPreferencesRepository(ILogManager logManager, IJsonSerializer jsonSerializer, IApplicationPaths appPaths) : base(logManager)
+ public SqliteDisplayPreferencesRepository(ILogManager logManager, IJsonSerializer jsonSerializer, IApplicationPaths appPaths, IDbConnector dbConnector)
+ : base(logManager, dbConnector)
{
_jsonSerializer = jsonSerializer;
- _appPaths = appPaths;
+ DbFilePath = Path.Combine(appPaths.DataPath, "displaypreferences.db");
}
/// <summary>
@@ -44,32 +43,21 @@ namespace MediaBrowser.Server.Implementations.Persistence
private readonly IJsonSerializer _jsonSerializer;
/// <summary>
- /// The _app paths
- /// </summary>
- private readonly IApplicationPaths _appPaths;
-
- /// <summary>
/// Opens the connection to the database
/// </summary>
/// <returns>Task.</returns>
- public async Task Initialize(IDbConnector dbConnector)
+ public async Task Initialize()
{
- var dbFile = Path.Combine(_appPaths.DataPath, "displaypreferences.db");
-
- _connection = await dbConnector.Connect(dbFile).ConfigureAwait(false);
-
- string[] queries = {
+ using (var connection = await CreateConnection().ConfigureAwait(false))
+ {
+ string[] queries = {
"create table if not exists userdisplaypreferences (id GUID, userId GUID, client text, data BLOB)",
- "create unique index if not exists userdisplaypreferencesindex on userdisplaypreferences (id, userId, client)",
-
- //pragmas
- "pragma temp_store = memory",
-
- "pragma shrink_memory"
+ "create unique index if not exists userdisplaypreferencesindex on userdisplaypreferences (id, userId, client)"
};
- _connection.RunQueries(queries, Logger);
+ connection.RunQueries(queries, Logger);
+ }
}
/// <summary>
@@ -96,58 +84,57 @@ namespace MediaBrowser.Server.Implementations.Persistence
var serialized = _jsonSerializer.SerializeToBytes(displayPreferences);
- await WriteLock.WaitAsync(cancellationToken).ConfigureAwait(false);
-
- IDbTransaction transaction = null;
-
- try
+ using (var connection = await CreateConnection().ConfigureAwait(false))
{
- transaction = _connection.BeginTransaction();
+ IDbTransaction transaction = null;
- using (var cmd = _connection.CreateCommand())
+ try
{
- cmd.CommandText = "replace into userdisplaypreferences (id, userid, client, data) values (@1, @2, @3, @4)";
+ transaction = connection.BeginTransaction();
- cmd.Parameters.Add(cmd, "@1", DbType.Guid).Value = new Guid(displayPreferences.Id);
- cmd.Parameters.Add(cmd, "@2", DbType.Guid).Value = userId;
- cmd.Parameters.Add(cmd, "@3", DbType.String).Value = client;
- cmd.Parameters.Add(cmd, "@4", DbType.Binary).Value = serialized;
+ using (var cmd = connection.CreateCommand())
+ {
+ cmd.CommandText = "replace into userdisplaypreferences (id, userid, client, data) values (@1, @2, @3, @4)";
- cmd.Transaction = transaction;
+ cmd.Parameters.Add(cmd, "@1", DbType.Guid).Value = new Guid(displayPreferences.Id);
+ cmd.Parameters.Add(cmd, "@2", DbType.Guid).Value = userId;
+ cmd.Parameters.Add(cmd, "@3", DbType.String).Value = client;
+ cmd.Parameters.Add(cmd, "@4", DbType.Binary).Value = serialized;
- cmd.ExecuteNonQuery();
- }
+ cmd.Transaction = transaction;
- transaction.Commit();
- }
- catch (OperationCanceledException)
- {
- if (transaction != null)
+ cmd.ExecuteNonQuery();
+ }
+
+ transaction.Commit();
+ }
+ catch (OperationCanceledException)
{
- transaction.Rollback();
+ if (transaction != null)
+ {
+ transaction.Rollback();
+ }
+
+ throw;
}
+ catch (Exception e)
+ {
+ Logger.ErrorException("Failed to save display preferences:", e);
- throw;
- }
- catch (Exception e)
- {
- Logger.ErrorException("Failed to save display preferences:", e);
+ if (transaction != null)
+ {
+ transaction.Rollback();
+ }
- if (transaction != null)
- {
- transaction.Rollback();
+ throw;
}
-
- throw;
- }
- finally
- {
- if (transaction != null)
+ finally
{
- transaction.Dispose();
+ if (transaction != null)
+ {
+ transaction.Dispose();
+ }
}
-
- WriteLock.Release();
}
}
@@ -168,64 +155,63 @@ namespace MediaBrowser.Server.Implementations.Persistence
cancellationToken.ThrowIfCancellationRequested();
- await WriteLock.WaitAsync(cancellationToken).ConfigureAwait(false);
-
- IDbTransaction transaction = null;
-
- try
+ using (var connection = await CreateConnection().ConfigureAwait(false))
{
- transaction = _connection.BeginTransaction();
+ IDbTransaction transaction = null;
- foreach (var displayPreference in displayPreferences)
+ try
{
+ transaction = connection.BeginTransaction();
- var serialized = _jsonSerializer.SerializeToBytes(displayPreference);
-
- using (var cmd = _connection.CreateCommand())
+ foreach (var displayPreference in displayPreferences)
{
- cmd.CommandText = "replace into userdisplaypreferences (id, userid, client, data) values (@1, @2, @3, @4)";
- cmd.Parameters.Add(cmd, "@1", DbType.Guid).Value = new Guid(displayPreference.Id);
- cmd.Parameters.Add(cmd, "@2", DbType.Guid).Value = userId;
- cmd.Parameters.Add(cmd, "@3", DbType.String).Value = displayPreference.Client;
- cmd.Parameters.Add(cmd, "@4", DbType.Binary).Value = serialized;
+ var serialized = _jsonSerializer.SerializeToBytes(displayPreference);
- cmd.Transaction = transaction;
+ using (var cmd = connection.CreateCommand())
+ {
+ cmd.CommandText = "replace into userdisplaypreferences (id, userid, client, data) values (@1, @2, @3, @4)";
- cmd.ExecuteNonQuery();
+ cmd.Parameters.Add(cmd, "@1", DbType.Guid).Value = new Guid(displayPreference.Id);
+ cmd.Parameters.Add(cmd, "@2", DbType.Guid).Value = userId;
+ cmd.Parameters.Add(cmd, "@3", DbType.String).Value = displayPreference.Client;
+ cmd.Parameters.Add(cmd, "@4", DbType.Binary).Value = serialized;
+
+ cmd.Transaction = transaction;
+
+ cmd.ExecuteNonQuery();
+ }
}
- }
- transaction.Commit();
- }
- catch (OperationCanceledException)
- {
- if (transaction != null)
+ transaction.Commit();
+ }
+ catch (OperationCanceledException)
{
- transaction.Rollback();
+ if (transaction != null)
+ {
+ transaction.Rollback();
+ }
+
+ throw;
}
+ catch (Exception e)
+ {
+ Logger.ErrorException("Failed to save display preferences:", e);
- throw;
- }
- catch (Exception e)
- {
- Logger.ErrorException("Failed to save display preferences:", e);
+ if (transaction != null)
+ {
+ transaction.Rollback();
+ }
- if (transaction != null)
- {
- transaction.Rollback();
+ throw;
}
-
- throw;
- }
- finally
- {
- if (transaction != null)
+ finally
{
- transaction.Dispose();
+ if (transaction != null)
+ {
+ transaction.Dispose();
+ }
}
-
- WriteLock.Release();
}
}
@@ -246,28 +232,33 @@ namespace MediaBrowser.Server.Implementations.Persistence
var guidId = displayPreferencesId.GetMD5();
- var cmd = _connection.CreateCommand();
- cmd.CommandText = "select data from userdisplaypreferences where id = @id and userId=@userId and client=@client";
-
- cmd.Parameters.Add(cmd, "@id", DbType.Guid).Value = guidId;
- cmd.Parameters.Add(cmd, "@userId", DbType.Guid).Value = userId;
- cmd.Parameters.Add(cmd, "@client", DbType.String).Value = client;
-
- using (var reader = cmd.ExecuteReader(CommandBehavior.SequentialAccess | CommandBehavior.SingleResult | CommandBehavior.SingleRow))
+ using (var connection = CreateConnection(true).Result)
{
- if (reader.Read())
+ using (var cmd = connection.CreateCommand())
{
- using (var stream = reader.GetMemoryStream(0))
+ cmd.CommandText = "select data from userdisplaypreferences where id = @id and userId=@userId and client=@client";
+
+ cmd.Parameters.Add(cmd, "@id", DbType.Guid).Value = guidId;
+ cmd.Parameters.Add(cmd, "@userId", DbType.Guid).Value = userId;
+ cmd.Parameters.Add(cmd, "@client", DbType.String).Value = client;
+
+ using (var reader = cmd.ExecuteReader(CommandBehavior.SequentialAccess | CommandBehavior.SingleResult | CommandBehavior.SingleRow))
{
- return _jsonSerializer.DeserializeFromStream<DisplayPreferences>(stream);
+ if (reader.Read())
+ {
+ using (var stream = reader.GetMemoryStream(0))
+ {
+ return _jsonSerializer.DeserializeFromStream<DisplayPreferences>(stream);
+ }
+ }
}
+
+ return new DisplayPreferences
+ {
+ Id = guidId.ToString("N")
+ };
}
}
-
- return new DisplayPreferences
- {
- Id = guidId.ToString("N")
- };
}
/// <summary>
@@ -278,36 +269,30 @@ namespace MediaBrowser.Server.Implementations.Persistence
/// <exception cref="System.ArgumentNullException">item</exception>
public IEnumerable<DisplayPreferences> GetAllDisplayPreferences(Guid userId)
{
+ var list = new List<DisplayPreferences>();
- var cmd = _connection.CreateCommand();
- cmd.CommandText = "select data from userdisplaypreferences where userId=@userId";
-
- cmd.Parameters.Add(cmd, "@userId", DbType.Guid).Value = userId;
-
- using (var reader = cmd.ExecuteReader(CommandBehavior.SequentialAccess | CommandBehavior.SingleResult))
+ using (var connection = CreateConnection(true).Result)
{
- while (reader.Read())
+ using (var cmd = connection.CreateCommand())
{
- using (var stream = reader.GetMemoryStream(0))
+ cmd.CommandText = "select data from userdisplaypreferences where userId=@userId";
+
+ cmd.Parameters.Add(cmd, "@userId", DbType.Guid).Value = userId;
+
+ using (var reader = cmd.ExecuteReader(CommandBehavior.SequentialAccess | CommandBehavior.SingleResult))
{
- yield return _jsonSerializer.DeserializeFromStream<DisplayPreferences>(stream);
+ while (reader.Read())
+ {
+ using (var stream = reader.GetMemoryStream(0))
+ {
+ list.Add(_jsonSerializer.DeserializeFromStream<DisplayPreferences>(stream));
+ }
+ }
}
}
}
- }
-
- protected override void CloseConnection()
- {
- if (_connection != null)
- {
- if (_connection.IsOpen())
- {
- _connection.Close();
- }
- _connection.Dispose();
- _connection = null;
- }
+ return list;
}
public Task SaveDisplayPreferences(DisplayPreferences displayPreferences, string userId, string client, CancellationToken cancellationToken)
diff --git a/MediaBrowser.Server.Implementations/Persistence/SqliteExtensions.cs b/MediaBrowser.Server.Implementations/Persistence/SqliteExtensions.cs
new file mode 100644
index 000000000..d5b582da5
--- /dev/null
+++ b/MediaBrowser.Server.Implementations/Persistence/SqliteExtensions.cs
@@ -0,0 +1,58 @@
+using System;
+using System.Collections.Generic;
+using System.Data;
+using System.Data.SQLite;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using MediaBrowser.Controller.Entities;
+using MediaBrowser.Model.Entities;
+using MediaBrowser.Model.Logging;
+
+namespace MediaBrowser.Server.Implementations.Persistence
+{
+ /// <summary>
+ /// Class SQLiteExtensions
+ /// </summary>
+ public static class SqliteExtensions
+ {
+ /// <summary>
+ /// Connects to db.
+ /// </summary>
+ public static async Task<IDbConnection> ConnectToDb(string dbPath, bool isReadOnly, bool enablePooling, int? cacheSize, ILogger logger)
+ {
+ if (string.IsNullOrEmpty(dbPath))
+ {
+ throw new ArgumentNullException("dbPath");
+ }
+
+ SQLiteConnection.SetMemoryStatus(false);
+
+ var connectionstr = new SQLiteConnectionStringBuilder
+ {
+ PageSize = 4096,
+ CacheSize = cacheSize ?? 2000,
+ SyncMode = SynchronizationModes.Normal,
+ DataSource = dbPath,
+ JournalMode = SQLiteJournalModeEnum.Wal,
+
+ // This is causing crashing under linux
+ Pooling = enablePooling && Environment.OSVersion.Platform == PlatformID.Win32NT,
+ ReadOnly = isReadOnly
+ };
+
+ var connectionString = connectionstr.ConnectionString;
+
+ if (!enablePooling)
+ {
+ logger.Info("Sqlite {0} opening {1}", SQLiteConnection.SQLiteVersion, connectionString);
+ }
+
+ var connection = new SQLiteConnection(connectionString);
+
+ await connection.OpenAsync().ConfigureAwait(false);
+
+ return connection;
+ }
+ }
+}
diff --git a/MediaBrowser.Server.Implementations/Persistence/SqliteFileOrganizationRepository.cs b/MediaBrowser.Server.Implementations/Persistence/SqliteFileOrganizationRepository.cs
index 037776997..7a5e00090 100644
--- a/MediaBrowser.Server.Implementations/Persistence/SqliteFileOrganizationRepository.cs
+++ b/MediaBrowser.Server.Implementations/Persistence/SqliteFileOrganizationRepository.cs
@@ -16,74 +16,29 @@ namespace MediaBrowser.Server.Implementations.Persistence
{
public class SqliteFileOrganizationRepository : BaseSqliteRepository, IFileOrganizationRepository, IDisposable
{
- private IDbConnection _connection;
-
- private readonly IServerApplicationPaths _appPaths;
-
private readonly CultureInfo _usCulture = new CultureInfo("en-US");
- private IDbCommand _saveResultCommand;
- private IDbCommand _deleteResultCommand;
- private IDbCommand _deleteAllCommand;
-
- public SqliteFileOrganizationRepository(ILogManager logManager, IServerApplicationPaths appPaths) : base(logManager)
+ public SqliteFileOrganizationRepository(ILogManager logManager, IServerApplicationPaths appPaths, IDbConnector connector) : base(logManager, connector)
{
- _appPaths = appPaths;
+ DbFilePath = Path.Combine(appPaths.DataPath, "fileorganization.db");
}
/// <summary>
/// Opens the connection to the database
/// </summary>
/// <returns>Task.</returns>
- public async Task Initialize(IDbConnector dbConnector)
+ public async Task Initialize()
{
- var dbFile = Path.Combine(_appPaths.DataPath, "fileorganization.db");
-
- _connection = await dbConnector.Connect(dbFile).ConfigureAwait(false);
-
- string[] queries = {
+ using (var connection = await CreateConnection().ConfigureAwait(false))
+ {
+ string[] queries = {
"create table if not exists FileOrganizerResults (ResultId GUID PRIMARY KEY, OriginalPath TEXT, TargetPath TEXT, FileLength INT, OrganizationDate datetime, Status TEXT, OrganizationType TEXT, StatusMessage TEXT, ExtractedName TEXT, ExtractedYear int null, ExtractedSeasonNumber int null, ExtractedEpisodeNumber int null, ExtractedEndingEpisodeNumber, DuplicatePaths TEXT int null)",
- "create index if not exists idx_FileOrganizerResults on FileOrganizerResults(ResultId)",
-
- //pragmas
- "pragma temp_store = memory",
-
- "pragma shrink_memory"
+ "create index if not exists idx_FileOrganizerResults on FileOrganizerResults(ResultId)"
};
- _connection.RunQueries(queries, Logger);
-
- PrepareStatements();
- }
-
- private void PrepareStatements()
- {
- _saveResultCommand = _connection.CreateCommand();
- _saveResultCommand.CommandText = "replace into FileOrganizerResults (ResultId, OriginalPath, TargetPath, FileLength, OrganizationDate, Status, OrganizationType, StatusMessage, ExtractedName, ExtractedYear, ExtractedSeasonNumber, ExtractedEpisodeNumber, ExtractedEndingEpisodeNumber, DuplicatePaths) values (@ResultId, @OriginalPath, @TargetPath, @FileLength, @OrganizationDate, @Status, @OrganizationType, @StatusMessage, @ExtractedName, @ExtractedYear, @ExtractedSeasonNumber, @ExtractedEpisodeNumber, @ExtractedEndingEpisodeNumber, @DuplicatePaths)";
-
- _saveResultCommand.Parameters.Add(_saveResultCommand, "@ResultId");
- _saveResultCommand.Parameters.Add(_saveResultCommand, "@OriginalPath");
- _saveResultCommand.Parameters.Add(_saveResultCommand, "@TargetPath");
- _saveResultCommand.Parameters.Add(_saveResultCommand, "@FileLength");
- _saveResultCommand.Parameters.Add(_saveResultCommand, "@OrganizationDate");
- _saveResultCommand.Parameters.Add(_saveResultCommand, "@Status");
- _saveResultCommand.Parameters.Add(_saveResultCommand, "@OrganizationType");
- _saveResultCommand.Parameters.Add(_saveResultCommand, "@StatusMessage");
- _saveResultCommand.Parameters.Add(_saveResultCommand, "@ExtractedName");
- _saveResultCommand.Parameters.Add(_saveResultCommand, "@ExtractedYear");
- _saveResultCommand.Parameters.Add(_saveResultCommand, "@ExtractedSeasonNumber");
- _saveResultCommand.Parameters.Add(_saveResultCommand, "@ExtractedEpisodeNumber");
- _saveResultCommand.Parameters.Add(_saveResultCommand, "@ExtractedEndingEpisodeNumber");
- _saveResultCommand.Parameters.Add(_saveResultCommand, "@DuplicatePaths");
-
- _deleteResultCommand = _connection.CreateCommand();
- _deleteResultCommand.CommandText = "delete from FileOrganizerResults where ResultId = @ResultId";
-
- _deleteResultCommand.Parameters.Add(_saveResultCommand, "@ResultId");
-
- _deleteAllCommand = _connection.CreateCommand();
- _deleteAllCommand.CommandText = "delete from FileOrganizerResults";
+ connection.RunQueries(queries, Logger);
+ }
}
public async Task SaveResult(FileOrganizationResult result, CancellationToken cancellationToken)
@@ -95,65 +50,84 @@ namespace MediaBrowser.Server.Implementations.Persistence
cancellationToken.ThrowIfCancellationRequested();
- await WriteLock.WaitAsync(cancellationToken).ConfigureAwait(false);
-
- IDbTransaction transaction = null;
-
- try
- {
- transaction = _connection.BeginTransaction();
-
- var index = 0;
-
- _saveResultCommand.GetParameter(index++).Value = new Guid(result.Id);
- _saveResultCommand.GetParameter(index++).Value = result.OriginalPath;
- _saveResultCommand.GetParameter(index++).Value = result.TargetPath;
- _saveResultCommand.GetParameter(index++).Value = result.FileSize;
- _saveResultCommand.GetParameter(index++).Value = result.Date;
- _saveResultCommand.GetParameter(index++).Value = result.Status.ToString();
- _saveResultCommand.GetParameter(index++).Value = result.Type.ToString();
- _saveResultCommand.GetParameter(index++).Value = result.StatusMessage;
- _saveResultCommand.GetParameter(index++).Value = result.ExtractedName;
- _saveResultCommand.GetParameter(index++).Value = result.ExtractedYear;
- _saveResultCommand.GetParameter(index++).Value = result.ExtractedSeasonNumber;
- _saveResultCommand.GetParameter(index++).Value = result.ExtractedEpisodeNumber;
- _saveResultCommand.GetParameter(index++).Value = result.ExtractedEndingEpisodeNumber;
- _saveResultCommand.GetParameter(index).Value = string.Join("|", result.DuplicatePaths.ToArray());
-
- _saveResultCommand.Transaction = transaction;
-
- _saveResultCommand.ExecuteNonQuery();
-
- transaction.Commit();
- }
- catch (OperationCanceledException)
+ using (var connection = await CreateConnection().ConfigureAwait(false))
{
- if (transaction != null)
+ using (var saveResultCommand = connection.CreateCommand())
{
- transaction.Rollback();
- }
+ saveResultCommand.CommandText = "replace into FileOrganizerResults (ResultId, OriginalPath, TargetPath, FileLength, OrganizationDate, Status, OrganizationType, StatusMessage, ExtractedName, ExtractedYear, ExtractedSeasonNumber, ExtractedEpisodeNumber, ExtractedEndingEpisodeNumber, DuplicatePaths) values (@ResultId, @OriginalPath, @TargetPath, @FileLength, @OrganizationDate, @Status, @OrganizationType, @StatusMessage, @ExtractedName, @ExtractedYear, @ExtractedSeasonNumber, @ExtractedEpisodeNumber, @ExtractedEndingEpisodeNumber, @DuplicatePaths)";
+
+ saveResultCommand.Parameters.Add(saveResultCommand, "@ResultId");
+ saveResultCommand.Parameters.Add(saveResultCommand, "@OriginalPath");
+ saveResultCommand.Parameters.Add(saveResultCommand, "@TargetPath");
+ saveResultCommand.Parameters.Add(saveResultCommand, "@FileLength");
+ saveResultCommand.Parameters.Add(saveResultCommand, "@OrganizationDate");
+ saveResultCommand.Parameters.Add(saveResultCommand, "@Status");
+ saveResultCommand.Parameters.Add(saveResultCommand, "@OrganizationType");
+ saveResultCommand.Parameters.Add(saveResultCommand, "@StatusMessage");
+ saveResultCommand.Parameters.Add(saveResultCommand, "@ExtractedName");
+ saveResultCommand.Parameters.Add(saveResultCommand, "@ExtractedYear");
+ saveResultCommand.Parameters.Add(saveResultCommand, "@ExtractedSeasonNumber");
+ saveResultCommand.Parameters.Add(saveResultCommand, "@ExtractedEpisodeNumber");
+ saveResultCommand.Parameters.Add(saveResultCommand, "@ExtractedEndingEpisodeNumber");
+ saveResultCommand.Parameters.Add(saveResultCommand, "@DuplicatePaths");
+
+ IDbTransaction transaction = null;
+
+ try
+ {
+ transaction = connection.BeginTransaction();
+
+ var index = 0;
+
+ saveResultCommand.GetParameter(index++).Value = new Guid(result.Id);
+ saveResultCommand.GetParameter(index++).Value = result.OriginalPath;
+ saveResultCommand.GetParameter(index++).Value = result.TargetPath;
+ saveResultCommand.GetParameter(index++).Value = result.FileSize;
+ saveResultCommand.GetParameter(index++).Value = result.Date;
+ saveResultCommand.GetParameter(index++).Value = result.Status.ToString();
+ saveResultCommand.GetParameter(index++).Value = result.Type.ToString();
+ saveResultCommand.GetParameter(index++).Value = result.StatusMessage;
+ saveResultCommand.GetParameter(index++).Value = result.ExtractedName;
+ saveResultCommand.GetParameter(index++).Value = result.ExtractedYear;
+ saveResultCommand.GetParameter(index++).Value = result.ExtractedSeasonNumber;
+ saveResultCommand.GetParameter(index++).Value = result.ExtractedEpisodeNumber;
+ saveResultCommand.GetParameter(index++).Value = result.ExtractedEndingEpisodeNumber;
+ saveResultCommand.GetParameter(index).Value = string.Join("|", result.DuplicatePaths.ToArray());
+
+ saveResultCommand.Transaction = transaction;
+
+ saveResultCommand.ExecuteNonQuery();
+
+ transaction.Commit();
+ }
+ catch (OperationCanceledException)
+ {
+ if (transaction != null)
+ {
+ transaction.Rollback();
+ }
- throw;
- }
- catch (Exception e)
- {
- Logger.ErrorException("Failed to save FileOrganizationResult:", e);
+ throw;
+ }
+ catch (Exception e)
+ {
+ Logger.ErrorException("Failed to save FileOrganizationResult:", e);
- if (transaction != null)
- {
- transaction.Rollback();
- }
+ if (transaction != null)
+ {
+ transaction.Rollback();
+ }
- throw;
- }
- finally
- {
- if (transaction != null)
- {
- transaction.Dispose();
+ throw;
+ }
+ finally
+ {
+ if (transaction != null)
+ {
+ transaction.Dispose();
+ }
+ }
}
-
- WriteLock.Release();
}
}
@@ -164,100 +138,110 @@ namespace MediaBrowser.Server.Implementations.Persistence
throw new ArgumentNullException("id");
}
- await WriteLock.WaitAsync().ConfigureAwait(false);
+ using (var connection = await CreateConnection().ConfigureAwait(false))
+ {
+ using (var deleteResultCommand = connection.CreateCommand())
+ {
+ deleteResultCommand.CommandText = "delete from FileOrganizerResults where ResultId = @ResultId";
- IDbTransaction transaction = null;
+ deleteResultCommand.Parameters.Add(deleteResultCommand, "@ResultId");
- try
- {
- transaction = _connection.BeginTransaction();
+ IDbTransaction transaction = null;
- _deleteResultCommand.GetParameter(0).Value = new Guid(id);
+ try
+ {
+ transaction = connection.BeginTransaction();
- _deleteResultCommand.Transaction = transaction;
+ deleteResultCommand.GetParameter(0).Value = new Guid(id);
- _deleteResultCommand.ExecuteNonQuery();
+ deleteResultCommand.Transaction = transaction;
- transaction.Commit();
- }
- catch (OperationCanceledException)
- {
- if (transaction != null)
- {
- transaction.Rollback();
- }
+ deleteResultCommand.ExecuteNonQuery();
- throw;
- }
- catch (Exception e)
- {
- Logger.ErrorException("Failed to delete FileOrganizationResult:", e);
+ transaction.Commit();
+ }
+ catch (OperationCanceledException)
+ {
+ if (transaction != null)
+ {
+ transaction.Rollback();
+ }
- if (transaction != null)
- {
- transaction.Rollback();
- }
+ throw;
+ }
+ catch (Exception e)
+ {
+ Logger.ErrorException("Failed to delete FileOrganizationResult:", e);
- throw;
- }
- finally
- {
- if (transaction != null)
- {
- transaction.Dispose();
- }
+ if (transaction != null)
+ {
+ transaction.Rollback();
+ }
- WriteLock.Release();
+ throw;
+ }
+ finally
+ {
+ if (transaction != null)
+ {
+ transaction.Dispose();
+ }
+ }
+ }
}
}
public async Task DeleteAll()
{
- await WriteLock.WaitAsync().ConfigureAwait(false);
+ using (var connection = await CreateConnection().ConfigureAwait(false))
+ {
+ using (var cmd = connection.CreateCommand())
+ {
+ cmd.CommandText = "delete from FileOrganizerResults";
- IDbTransaction transaction = null;
+ IDbTransaction transaction = null;
- try
- {
- transaction = _connection.BeginTransaction();
-
- _deleteAllCommand.Transaction = transaction;
+ try
+ {
+ transaction = connection.BeginTransaction();
- _deleteAllCommand.ExecuteNonQuery();
+ cmd.Transaction = transaction;
- transaction.Commit();
- }
- catch (OperationCanceledException)
- {
- if (transaction != null)
- {
- transaction.Rollback();
- }
+ cmd.ExecuteNonQuery();
- throw;
- }
- catch (Exception e)
- {
- Logger.ErrorException("Failed to delete results", e);
+ transaction.Commit();
+ }
+ catch (OperationCanceledException)
+ {
+ if (transaction != null)
+ {
+ transaction.Rollback();
+ }
- if (transaction != null)
- {
- transaction.Rollback();
- }
+ throw;
+ }
+ catch (Exception e)
+ {
+ Logger.ErrorException("Failed to delete results", e);
- throw;
- }
- finally
- {
- if (transaction != null)
- {
- transaction.Dispose();
- }
+ if (transaction != null)
+ {
+ transaction.Rollback();
+ }
- WriteLock.Release();
+ throw;
+ }
+ finally
+ {
+ if (transaction != null)
+ {
+ transaction.Dispose();
+ }
+ }
+ }
}
}
-
+
public QueryResult<FileOrganizationResult> GetResults(FileOrganizationResultQuery query)
{
if (query == null)
@@ -265,46 +249,49 @@ namespace MediaBrowser.Server.Implementations.Persistence
throw new ArgumentNullException("query");
}
- using (var cmd = _connection.CreateCommand())
+ using (var connection = CreateConnection(true).Result)
{
- cmd.CommandText = "SELECT ResultId, OriginalPath, TargetPath, FileLength, OrganizationDate, Status, OrganizationType, StatusMessage, ExtractedName, ExtractedYear, ExtractedSeasonNumber, ExtractedEpisodeNumber, ExtractedEndingEpisodeNumber, DuplicatePaths from FileOrganizerResults";
-
- if (query.StartIndex.HasValue && query.StartIndex.Value > 0)
+ using (var cmd = connection.CreateCommand())
{
- cmd.CommandText += string.Format(" WHERE ResultId NOT IN (SELECT ResultId FROM FileOrganizerResults ORDER BY OrganizationDate desc LIMIT {0})",
- query.StartIndex.Value.ToString(_usCulture));
- }
+ cmd.CommandText = "SELECT ResultId, OriginalPath, TargetPath, FileLength, OrganizationDate, Status, OrganizationType, StatusMessage, ExtractedName, ExtractedYear, ExtractedSeasonNumber, ExtractedEpisodeNumber, ExtractedEndingEpisodeNumber, DuplicatePaths from FileOrganizerResults";
- cmd.CommandText += " ORDER BY OrganizationDate desc";
+ if (query.StartIndex.HasValue && query.StartIndex.Value > 0)
+ {
+ cmd.CommandText += string.Format(" WHERE ResultId NOT IN (SELECT ResultId FROM FileOrganizerResults ORDER BY OrganizationDate desc LIMIT {0})",
+ query.StartIndex.Value.ToString(_usCulture));
+ }
- if (query.Limit.HasValue)
- {
- cmd.CommandText += " LIMIT " + query.Limit.Value.ToString(_usCulture);
- }
+ cmd.CommandText += " ORDER BY OrganizationDate desc";
+
+ if (query.Limit.HasValue)
+ {
+ cmd.CommandText += " LIMIT " + query.Limit.Value.ToString(_usCulture);
+ }
- cmd.CommandText += "; select count (ResultId) from FileOrganizerResults";
+ cmd.CommandText += "; select count (ResultId) from FileOrganizerResults";
- var list = new List<FileOrganizationResult>();
- var count = 0;
+ var list = new List<FileOrganizationResult>();
+ var count = 0;
- using (var reader = cmd.ExecuteReader(CommandBehavior.SequentialAccess))
- {
- while (reader.Read())
+ using (var reader = cmd.ExecuteReader(CommandBehavior.SequentialAccess))
{
- list.Add(GetResult(reader));
+ while (reader.Read())
+ {
+ list.Add(GetResult(reader));
+ }
+
+ if (reader.NextResult() && reader.Read())
+ {
+ count = reader.GetInt32(0);
+ }
}
- if (reader.NextResult() && reader.Read())
+ return new QueryResult<FileOrganizationResult>()
{
- count = reader.GetInt32(0);
- }
+ Items = list.ToArray(),
+ TotalRecordCount = count
+ };
}
-
- return new QueryResult<FileOrganizationResult>()
- {
- Items = list.ToArray(),
- TotalRecordCount = count
- };
}
}
@@ -315,24 +302,27 @@ namespace MediaBrowser.Server.Implementations.Persistence
throw new ArgumentNullException("id");
}
- var guid = new Guid(id);
-
- using (var cmd = _connection.CreateCommand())
+ using (var connection = CreateConnection(true).Result)
{
- cmd.CommandText = "select ResultId, OriginalPath, TargetPath, FileLength, OrganizationDate, Status, OrganizationType, StatusMessage, ExtractedName, ExtractedYear, ExtractedSeasonNumber, ExtractedEpisodeNumber, ExtractedEndingEpisodeNumber, DuplicatePaths from FileOrganizerResults where ResultId=@Id";
+ var guid = new Guid(id);
- cmd.Parameters.Add(cmd, "@Id", DbType.Guid).Value = guid;
-
- using (var reader = cmd.ExecuteReader(CommandBehavior.SequentialAccess | CommandBehavior.SingleResult | CommandBehavior.SingleRow))
+ using (var cmd = connection.CreateCommand())
{
- if (reader.Read())
+ cmd.CommandText = "select ResultId, OriginalPath, TargetPath, FileLength, OrganizationDate, Status, OrganizationType, StatusMessage, ExtractedName, ExtractedYear, ExtractedSeasonNumber, ExtractedEpisodeNumber, ExtractedEndingEpisodeNumber, DuplicatePaths from FileOrganizerResults where ResultId=@Id";
+
+ cmd.Parameters.Add(cmd, "@Id", DbType.Guid).Value = guid;
+
+ using (var reader = cmd.ExecuteReader(CommandBehavior.SequentialAccess | CommandBehavior.SingleResult | CommandBehavior.SingleRow))
{
- return GetResult(reader);
+ if (reader.Read())
+ {
+ return GetResult(reader);
+ }
}
}
- }
- return null;
+ return null;
+ }
}
public FileOrganizationResult GetResult(IDataReader reader)
@@ -414,19 +404,5 @@ namespace MediaBrowser.Server.Implementations.Persistence
return result;
}
-
- protected override void CloseConnection()
- {
- if (_connection != null)
- {
- if (_connection.IsOpen())
- {
- _connection.Close();
- }
-
- _connection.Dispose();
- _connection = null;
- }
- }
}
}
diff --git a/MediaBrowser.Server.Implementations/Persistence/SqliteItemRepository.cs b/MediaBrowser.Server.Implementations/Persistence/SqliteItemRepository.cs
index 57eb41bc2..5d017880c 100644
--- a/MediaBrowser.Server.Implementations/Persistence/SqliteItemRepository.cs
+++ b/MediaBrowser.Server.Implementations/Persistence/SqliteItemRepository.cs
@@ -15,12 +15,14 @@ using System.Globalization;
using System.IO;
using System.Linq;
using System.Runtime.Serialization;
+using System.Text;
using System.Threading;
using System.Threading.Tasks;
using MediaBrowser.Common.Extensions;
using MediaBrowser.Controller.Channels;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Playlists;
+using MediaBrowser.Model.Dto;
using MediaBrowser.Model.LiveTv;
namespace MediaBrowser.Server.Implementations.Persistence
@@ -84,16 +86,22 @@ namespace MediaBrowser.Server.Implementations.Persistence
private IDbCommand _deleteItemValuesCommand;
private IDbCommand _saveItemValuesCommand;
+ private IDbCommand _deleteProviderIdsCommand;
+ private IDbCommand _saveProviderIdsCommand;
+
+ private IDbCommand _deleteImagesCommand;
+ private IDbCommand _saveImagesCommand;
+
private IDbCommand _updateInheritedRatingCommand;
private IDbCommand _updateInheritedTagsCommand;
- public const int LatestSchemaVersion = 79;
+ public const int LatestSchemaVersion = 97;
/// <summary>
/// Initializes a new instance of the <see cref="SqliteItemRepository"/> class.
/// </summary>
- public SqliteItemRepository(IServerConfigurationManager config, IJsonSerializer jsonSerializer, ILogManager logManager)
- : base(logManager)
+ public SqliteItemRepository(IServerConfigurationManager config, IJsonSerializer jsonSerializer, ILogManager logManager, IDbConnector connector)
+ : base(logManager, connector)
{
if (config == null)
{
@@ -108,27 +116,40 @@ namespace MediaBrowser.Server.Implementations.Persistence
_jsonSerializer = jsonSerializer;
_criticReviewsPath = Path.Combine(_config.ApplicationPaths.DataPath, "critic-reviews");
+ DbFilePath = Path.Combine(_config.ApplicationPaths.DataPath, "library.db");
}
private const string ChaptersTableName = "Chapters2";
+ protected override async Task<IDbConnection> CreateConnection(bool isReadOnly = false)
+ {
+ var connection = await DbConnector.Connect(DbFilePath, false, false, _config.Configuration.SqliteCachePages).ConfigureAwait(false);
+
+ connection.RunQueries(new[]
+ {
+ "pragma temp_store = memory",
+ "pragma default_temp_store = memory",
+ "PRAGMA locking_mode=EXCLUSIVE"
+
+ }, Logger);
+
+ return connection;
+ }
+
/// <summary>
/// Opens the connection to the database
/// </summary>
/// <returns>Task.</returns>
- public async Task Initialize(IDbConnector dbConnector)
+ public async Task Initialize(SqliteUserDataRepository userDataRepo)
{
- var dbFile = Path.Combine(_config.ApplicationPaths.DataPath, "library.db");
-
- _connection = await dbConnector.Connect(dbFile).ConfigureAwait(false);
+ _connection = await CreateConnection(false).ConfigureAwait(false);
var createMediaStreamsTableCommand
- = "create table if not exists mediastreams (ItemId GUID, StreamIndex INT, StreamType TEXT, Codec TEXT, Language TEXT, ChannelLayout TEXT, Profile TEXT, AspectRatio TEXT, Path TEXT, IsInterlaced BIT, BitRate INT NULL, Channels INT NULL, SampleRate INT NULL, IsDefault BIT, IsForced BIT, IsExternal BIT, Height INT NULL, Width INT NULL, AverageFrameRate FLOAT NULL, RealFrameRate FLOAT NULL, Level FLOAT NULL, PixelFormat TEXT, BitDepth INT NULL, IsAnamorphic BIT NULL, RefFrames INT NULL, CodecTag TEXT NULL, Comment TEXT NULL, NalLengthSize TEXT NULL, IsAvc BIT NULL, Title TEXT NULL, PRIMARY KEY (ItemId, StreamIndex))";
+ = "create table if not exists mediastreams (ItemId GUID, StreamIndex INT, StreamType TEXT, Codec TEXT, Language TEXT, ChannelLayout TEXT, Profile TEXT, AspectRatio TEXT, Path TEXT, IsInterlaced BIT, BitRate INT NULL, Channels INT NULL, SampleRate INT NULL, IsDefault BIT, IsForced BIT, IsExternal BIT, Height INT NULL, Width INT NULL, AverageFrameRate FLOAT NULL, RealFrameRate FLOAT NULL, Level FLOAT NULL, PixelFormat TEXT, BitDepth INT NULL, IsAnamorphic BIT NULL, RefFrames INT NULL, CodecTag TEXT NULL, Comment TEXT NULL, NalLengthSize TEXT NULL, IsAvc BIT NULL, Title TEXT NULL, TimeBase TEXT NULL, CodecTimeBase TEXT NULL, PRIMARY KEY (ItemId, StreamIndex))";
string[] queries = {
"create table if not exists TypedBaseItems (guid GUID primary key, type TEXT, data BLOB, ParentId GUID, Path TEXT)",
- "create index if not exists idx_TypedBaseItems on TypedBaseItems(guid)",
"create index if not exists idx_PathTypedBaseItems on TypedBaseItems(Path)",
"create index if not exists idx_ParentIdTypedBaseItems on TypedBaseItems(ParentId)",
@@ -136,26 +157,29 @@ namespace MediaBrowser.Server.Implementations.Persistence
"create index if not exists idx_AncestorIds1 on AncestorIds(AncestorId)",
"create index if not exists idx_AncestorIds2 on AncestorIds(AncestorIdText)",
- "create table if not exists UserDataKeys (ItemId GUID, UserDataKey TEXT, PRIMARY KEY (ItemId, UserDataKey))",
- "create index if not exists idx_UserDataKeys1 on UserDataKeys(ItemId)",
+ "create table if not exists UserDataKeys (ItemId GUID, UserDataKey TEXT Priority INT, PRIMARY KEY (ItemId, UserDataKey))",
- "create table if not exists ItemValues (ItemId GUID, Type INT, Value TEXT)",
- "create index if not exists idx_ItemValues on ItemValues(ItemId)",
+ "create table if not exists ItemValues (ItemId GUID, Type INT, Value TEXT, CleanValue TEXT)",
+
+ "create table if not exists ProviderIds (ItemId GUID, Name TEXT, Value TEXT, PRIMARY KEY (ItemId, Name))",
+ // covering index
+ "create index if not exists Idx_ProviderIds1 on ProviderIds(ItemId,Name,Value)",
+
+ "create table if not exists Images (ItemId GUID NOT NULL, Path TEXT NOT NULL, ImageType INT NOT NULL, DateModified DATETIME, IsPlaceHolder BIT NOT NULL, SortOrder INT)",
+ "create index if not exists idx_Images on Images(ItemId)",
"create table if not exists People (ItemId GUID, Name TEXT NOT NULL, Role TEXT, PersonType TEXT, SortOrder int, ListOrder int)",
- "create index if not exists idxPeopleItemId on People(ItemId)",
+
+ "drop index if exists idxPeopleItemId",
+ "create index if not exists idxPeopleItemId1 on People(ItemId,ListOrder)",
"create index if not exists idxPeopleName on People(Name)",
"create table if not exists "+ChaptersTableName+" (ItemId GUID, ChapterIndex INT, StartPositionTicks BIGINT, Name TEXT, ImagePath TEXT, PRIMARY KEY (ItemId, ChapterIndex))",
- "create index if not exists idx_"+ChaptersTableName+"1 on "+ChaptersTableName+"(ItemId)",
createMediaStreamsTableCommand,
- "create index if not exists idx_mediastreams1 on mediastreams(ItemId)",
- //pragmas
- "pragma temp_store = memory",
+ "create index if not exists idx_mediastreams1 on mediastreams(ItemId)",
- "pragma shrink_memory"
};
_connection.RunQueries(queries, Logger);
@@ -239,67 +263,77 @@ namespace MediaBrowser.Server.Implementations.Persistence
_connection.AddColumn(Logger, "TypedBaseItems", "DateLastMediaAdded", "DATETIME");
_connection.AddColumn(Logger, "TypedBaseItems", "Album", "Text");
_connection.AddColumn(Logger, "TypedBaseItems", "IsVirtualItem", "BIT");
+ _connection.AddColumn(Logger, "TypedBaseItems", "SeriesName", "Text");
+ _connection.AddColumn(Logger, "TypedBaseItems", "UserDataKey", "Text");
_connection.AddColumn(Logger, "UserDataKeys", "Priority", "INT");
+ _connection.AddColumn(Logger, "ItemValues", "CleanValue", "Text");
string[] postQueries =
- {
+
+ {
+ // obsolete
+ "drop index if exists idx_TypedBaseItems",
+ "drop index if exists idx_mediastreams",
+ "drop index if exists idx_"+ChaptersTableName,
+ "drop index if exists idx_UserDataKeys1",
+ "drop index if exists idx_UserDataKeys2",
+ "drop index if exists idx_TypeTopParentId3",
+ "drop index if exists idx_TypeTopParentId2",
+ "drop index if exists idx_TypeTopParentId4",
+ "drop index if exists idx_Type",
+ "drop index if exists idx_TypeTopParentId",
+ "drop index if exists idx_GuidType",
+ "drop index if exists idx_TopParentId",
+ "drop index if exists idx_TypeTopParentId6",
+ "drop index if exists idx_ItemValues2",
+ "drop index if exists Idx_ProviderIds",
+ "drop index if exists idx_ItemValues3",
+ "drop index if exists idx_ItemValues4",
+ "drop index if exists idx_ItemValues5",
+
"create index if not exists idx_PresentationUniqueKey on TypedBaseItems(PresentationUniqueKey)",
- "create index if not exists idx_Type on TypedBaseItems(Type)",
- "create index if not exists idx_TopParentId on TypedBaseItems(TopParentId)"
- };
+ "create index if not exists idx_GuidTypeIsFolderIsVirtualItem on TypedBaseItems(Guid,Type,IsFolder,IsVirtualItem)",
+ //"create index if not exists idx_GuidMediaTypeIsFolderIsVirtualItem on TypedBaseItems(Guid,MediaType,IsFolder,IsVirtualItem)",
+ "create index if not exists idx_CleanNameType on TypedBaseItems(CleanName,Type)",
- _connection.RunQueries(postQueries, Logger);
+ // covering index
+ "create index if not exists idx_TopParentIdGuid on TypedBaseItems(TopParentId,Guid)",
- PrepareStatements();
+ // live tv programs
+ "create index if not exists idx_TypeTopParentIdStartDate on TypedBaseItems(Type,TopParentId,StartDate)",
- new MediaStreamColumns(_connection, Logger).AddColumns();
+ // covering index for getitemvalues
+ "create index if not exists idx_TypeTopParentIdGuid on TypedBaseItems(Type,TopParentId,Guid)",
- var mediaStreamsDbFile = Path.Combine(_config.ApplicationPaths.DataPath, "mediainfo.db");
- if (File.Exists(mediaStreamsDbFile))
- {
- MigrateMediaStreams(mediaStreamsDbFile);
- }
+ // used by movie suggestions
+ "create index if not exists idx_TypeTopParentIdGroup on TypedBaseItems(Type,TopParentId,PresentationUniqueKey)",
+ "create index if not exists idx_TypeTopParentId5 on TypedBaseItems(TopParentId,IsVirtualItem)",
- DataExtensions.Attach(_connection, Path.Combine(_config.ApplicationPaths.DataPath, "userdata_v2.db"), "UserDataDb");
- }
+ // latest items
+ "create index if not exists idx_TypeTopParentId9 on TypedBaseItems(TopParentId,Type,IsVirtualItem,PresentationUniqueKey,DateCreated)",
+ "create index if not exists idx_TypeTopParentId8 on TypedBaseItems(TopParentId,IsFolder,IsVirtualItem,PresentationUniqueKey,DateCreated)",
- private void MigrateMediaStreams(string file)
- {
- try
- {
- var backupFile = file + ".bak";
- File.Copy(file, backupFile, true);
- DataExtensions.Attach(_connection, backupFile, "MediaInfoOld");
+ // resume
+ "create index if not exists idx_TypeTopParentId7 on TypedBaseItems(TopParentId,MediaType,IsVirtualItem,PresentationUniqueKey)",
- var columns = string.Join(",", _mediaStreamSaveColumns);
+ // items by name
+ "create index if not exists idx_ItemValues6 on ItemValues(ItemId,Type,CleanValue)",
+ "create index if not exists idx_ItemValues7 on ItemValues(Type,CleanValue,ItemId)",
- string[] queries = {
- "REPLACE INTO mediastreams("+columns+") SELECT "+columns+" FROM MediaInfoOld.mediastreams;"
- };
+ // covering index
+ "create index if not exists idx_UserDataKeys3 on UserDataKeys(ItemId,Priority,UserDataKey)"
+ };
- _connection.RunQueries(queries, Logger);
- }
- catch (Exception ex)
- {
- Logger.ErrorException("Error migrating media info database", ex);
- }
- finally
- {
- TryDeleteFile(file);
- }
- }
+ _connection.RunQueries(postQueries, Logger);
- private void TryDeleteFile(string file)
- {
- try
- {
- File.Delete(file);
- }
- catch (Exception ex)
- {
- Logger.ErrorException("Error deleting file {0}", ex, file);
- }
+ PrepareStatements();
+
+ new MediaStreamColumns(_connection, Logger).AddColumns();
+
+ DataExtensions.Attach(_connection, Path.Combine(_config.ApplicationPaths.DataPath, "userdata_v2.db"), "UserDataDb");
+ await userDataRepo.Initialize(_connection, WriteLock).ConfigureAwait(false);
+ //await Vacuum(_connection).ConfigureAwait(false);
}
private readonly string[] _retriveItemColumns =
@@ -396,7 +430,9 @@ namespace MediaBrowser.Server.Implementations.Persistence
"Comment",
"NalLengthSize",
"IsAvc",
- "Title"
+ "Title",
+ "TimeBase",
+ "CodecTimeBase"
};
/// <summary>
@@ -478,7 +514,9 @@ namespace MediaBrowser.Server.Implementations.Persistence
"PrimaryVersionId",
"DateLastMediaAdded",
"Album",
- "IsVirtualItem"
+ "IsVirtualItem",
+ "SeriesName",
+ "UserDataKey"
};
_saveItemCommand = _connection.CreateCommand();
_saveItemCommand.CommandText = "replace into TypedBaseItems (" + string.Join(",", saveColumns.ToArray()) + ") values (";
@@ -581,11 +619,36 @@ namespace MediaBrowser.Server.Implementations.Persistence
_deleteItemValuesCommand.Parameters.Add(_deleteItemValuesCommand, "@Id");
_saveItemValuesCommand = _connection.CreateCommand();
- _saveItemValuesCommand.CommandText = "insert into ItemValues (ItemId, Type, Value) values (@ItemId, @Type, @Value)";
+ _saveItemValuesCommand.CommandText = "insert into ItemValues (ItemId, Type, Value, CleanValue) values (@ItemId, @Type, @Value, @CleanValue)";
_saveItemValuesCommand.Parameters.Add(_saveItemValuesCommand, "@ItemId");
_saveItemValuesCommand.Parameters.Add(_saveItemValuesCommand, "@Type");
_saveItemValuesCommand.Parameters.Add(_saveItemValuesCommand, "@Value");
-
+ _saveItemValuesCommand.Parameters.Add(_saveItemValuesCommand, "@CleanValue");
+
+ // provider ids
+ _deleteProviderIdsCommand = _connection.CreateCommand();
+ _deleteProviderIdsCommand.CommandText = "delete from ProviderIds where ItemId=@Id";
+ _deleteProviderIdsCommand.Parameters.Add(_deleteProviderIdsCommand, "@Id");
+
+ _saveProviderIdsCommand = _connection.CreateCommand();
+ _saveProviderIdsCommand.CommandText = "insert into ProviderIds (ItemId, Name, Value) values (@ItemId, @Name, @Value)";
+ _saveProviderIdsCommand.Parameters.Add(_saveProviderIdsCommand, "@ItemId");
+ _saveProviderIdsCommand.Parameters.Add(_saveProviderIdsCommand, "@Name");
+ _saveProviderIdsCommand.Parameters.Add(_saveProviderIdsCommand, "@Value");
+
+ // images
+ _deleteImagesCommand = _connection.CreateCommand();
+ _deleteImagesCommand.CommandText = "delete from Images where ItemId=@Id";
+ _deleteImagesCommand.Parameters.Add(_deleteImagesCommand, "@Id");
+
+ _saveImagesCommand = _connection.CreateCommand();
+ _saveImagesCommand.CommandText = "insert into Images (ItemId, ImageType, Path, DateModified, IsPlaceHolder, SortOrder) values (@ItemId, @ImageType, @Path, @DateModified, @IsPlaceHolder, @SortOrder)";
+ _saveImagesCommand.Parameters.Add(_saveImagesCommand, "@ItemId");
+ _saveImagesCommand.Parameters.Add(_saveImagesCommand, "@ImageType");
+ _saveImagesCommand.Parameters.Add(_saveImagesCommand, "@Path");
+ _saveImagesCommand.Parameters.Add(_saveImagesCommand, "@DateModified");
+ _saveImagesCommand.Parameters.Add(_saveImagesCommand, "@IsPlaceHolder");
+ _saveImagesCommand.Parameters.Add(_saveImagesCommand, "@SortOrder");
}
/// <summary>
@@ -870,16 +933,20 @@ namespace MediaBrowser.Server.Implementations.Persistence
_saveItemCommand.GetParameter(index++).Value = item.Album;
- var season = item as Season;
- if (season != null && season.IsVirtualItem.HasValue)
+ _saveItemCommand.GetParameter(index++).Value = item.IsVirtualItem || (!item.IsFolder && item.LocationType == LocationType.Virtual);
+
+ var hasSeries = item as IHasSeries;
+ if (hasSeries != null)
{
- _saveItemCommand.GetParameter(index++).Value = season.IsVirtualItem.Value;
+ _saveItemCommand.GetParameter(index++).Value = hasSeries.SeriesName;
}
else
{
_saveItemCommand.GetParameter(index++).Value = null;
}
+ _saveItemCommand.GetParameter(index++).Value = item.GetUserDataKeys().FirstOrDefault();
+
_saveItemCommand.Transaction = transaction;
_saveItemCommand.ExecuteNonQuery();
@@ -890,7 +957,9 @@ namespace MediaBrowser.Server.Implementations.Persistence
}
UpdateUserDataKeys(item.Id, item.GetUserDataKeys().Distinct(StringComparer.OrdinalIgnoreCase).ToList(), transaction);
- UpdateItemValues(item.Id, GetItemValues(item), transaction);
+ UpdateImages(item.Id, item.ImageInfos, transaction);
+ UpdateProviderIds(item.Id, item.ProviderIds, transaction);
+ UpdateItemValues(item.Id, GetItemValuesToSave(item), transaction);
}
transaction.Commit();
@@ -966,7 +1035,7 @@ namespace MediaBrowser.Server.Implementations.Persistence
if (type == null)
{
- Logger.Debug("Unknown type {0}", typeString);
+ //Logger.Debug("Unknown type {0}", typeString);
return null;
}
@@ -1295,10 +1364,9 @@ namespace MediaBrowser.Server.Implementations.Persistence
item.CriticRatingSummary = reader.GetString(57);
}
- var season = item as Season;
- if (season != null && !reader.IsDBNull(58))
+ if (!reader.IsDBNull(58))
{
- season.IsVirtualItem = reader.GetBoolean(58);
+ item.IsVirtualItem = reader.GetBoolean(58);
}
return item;
@@ -1450,7 +1518,7 @@ namespace MediaBrowser.Server.Implementations.Persistence
/// or
/// cancellationToken
/// </exception>
- public async Task SaveChapters(Guid id, IEnumerable<ChapterInfo> chapters, CancellationToken cancellationToken)
+ public async Task SaveChapters(Guid id, List<ChapterInfo> chapters, CancellationToken cancellationToken)
{
CheckDisposed();
@@ -1549,14 +1617,14 @@ namespace MediaBrowser.Server.Implementations.Persistence
private bool EnableJoinUserData(InternalItemsQuery query)
{
- if (_config.Configuration.SchemaVersion < 76)
+ if (query.User == null)
{
return false;
}
- if (query.User == null)
+ if (query.SimilarTo != null && query.User != null)
{
- return false;
+ return true;
}
if (query.SortBy != null && query.SortBy.Length > 0)
@@ -1611,7 +1679,7 @@ namespace MediaBrowser.Server.Implementations.Persistence
return false;
}
- private string[] GetFinalColumnsToSelect(InternalItemsQuery query, string[] startColumns)
+ private string[] GetFinalColumnsToSelect(InternalItemsQuery query, string[] startColumns, IDbCommand cmd)
{
var list = startColumns.ToList();
@@ -1626,6 +1694,47 @@ namespace MediaBrowser.Server.Implementations.Persistence
list.Add("UserDataDb.UserData.rating");
}
+ if (query.SimilarTo != null)
+ {
+ var item = query.SimilarTo;
+
+ var builder = new StringBuilder();
+ builder.Append("(");
+
+ builder.Append("((OfficialRating=@ItemOfficialRating) * 10)");
+ //builder.Append("+ ((ProductionYear=@ItemProductionYear) * 10)");
+
+ builder.Append("+(Select Case When Abs(COALESCE(ProductionYear, 0) - @ItemProductionYear) < 10 Then 2 Else 0 End )");
+ builder.Append("+(Select Case When Abs(COALESCE(ProductionYear, 0) - @ItemProductionYear) < 5 Then 2 Else 0 End )");
+
+ //// genres
+ builder.Append("+ ((Select count(CleanValue) from ItemValues where ItemId=Guid and Type=2 and CleanValue in (select CleanValue from itemvalues where ItemId=@SimilarItemId and type=2)) * 10)");
+
+ //// tags
+ builder.Append("+ ((Select count(CleanValue) from ItemValues where ItemId=Guid and Type=4 and CleanValue in (select CleanValue from itemvalues where ItemId=@SimilarItemId and type=4)) * 10)");
+
+ builder.Append("+ ((Select count(CleanValue) from ItemValues where ItemId=Guid and Type=5 and CleanValue in (select CleanValue from itemvalues where ItemId=@SimilarItemId and type=5)) * 10)");
+
+ builder.Append("+ ((Select count(CleanValue) from ItemValues where ItemId=Guid and Type=3 and CleanValue in (select CleanValue from itemvalues where ItemId=@SimilarItemId and type=3)) * 3)");
+
+ //builder.Append("+ ((Select count(Name) from People where ItemId=Guid and Name in (select Name from People where ItemId=@SimilarItemId)) * 3)");
+
+ ////builder.Append("(select group_concat((Select Name from People where ItemId=Guid and Name in (Select Name from People where ItemId=@SimilarItemId)), '|'))");
+
+ builder.Append(") as SimilarityScore");
+
+ list.Add(builder.ToString());
+ cmd.Parameters.Add(cmd, "@ItemOfficialRating", DbType.String).Value = item.OfficialRating;
+ cmd.Parameters.Add(cmd, "@ItemProductionYear", DbType.Int32).Value = item.ProductionYear ?? 0;
+ cmd.Parameters.Add(cmd, "@SimilarItemId", DbType.Guid).Value = item.Id;
+
+ var excludeIds = query.ExcludeItemIds.ToList();
+ excludeIds.Add(item.Id.ToString("N"));
+ query.ExcludeItemIds = excludeIds.ToArray();
+
+ query.ExcludeProviderIds = item.ProviderIds;
+ }
+
return list.ToArray();
}
@@ -1636,10 +1745,37 @@ namespace MediaBrowser.Server.Implementations.Persistence
return string.Empty;
}
- return " left join UserDataDb.UserData on (select UserDataKey from UserDataKeys where ItemId=Guid order by Priority LIMIT 1)=UserDataDb.UserData.Key";
+ if (_config.Configuration.SchemaVersion >= 96)
+ {
+ return " left join UserDataDb.UserData on UserDataKey=UserDataDb.UserData.Key And (UserId=@UserId)";
+ }
+
+ return " left join UserDataDb.UserData on (select UserDataKey from UserDataKeys where ItemId=Guid order by Priority LIMIT 1)=UserDataDb.UserData.Key And (UserId=@UserId)";
}
- public IEnumerable<BaseItem> GetItemList(InternalItemsQuery query)
+ private string GetGroupBy(InternalItemsQuery query)
+ {
+ var groups = new List<string>();
+
+ if (EnableGroupByPresentationUniqueKey(query))
+ {
+ groups.Add("PresentationUniqueKey");
+ }
+
+ if (groups.Count > 0)
+ {
+ return " Group by " + string.Join(",", groups.ToArray());
+ }
+
+ return string.Empty;
+ }
+
+ private string GetFromText(string alias = "A")
+ {
+ return " from TypedBaseItems " + alias;
+ }
+
+ public List<BaseItem> GetItemList(InternalItemsQuery query)
{
if (query == null)
{
@@ -1650,9 +1786,17 @@ namespace MediaBrowser.Server.Implementations.Persistence
var now = DateTime.UtcNow;
+ var list = new List<BaseItem>();
+
+ // Hack for right now since we currently don't support filtering out these duplicates within a query
+ if (query.Limit.HasValue && query.EnableGroupByMetadataKey)
+ {
+ query.Limit = query.Limit.Value + 4;
+ }
+
using (var cmd = _connection.CreateCommand())
{
- cmd.CommandText = "select " + string.Join(",", GetFinalColumnsToSelect(query, _retriveItemColumns)) + " from TypedBaseItems";
+ cmd.CommandText = "select " + string.Join(",", GetFinalColumnsToSelect(query, _retriveItemColumns, cmd)) + GetFromText();
cmd.CommandText += GetJoinUserDataText(query);
if (EnableJoinUserData(query))
@@ -1668,22 +1812,22 @@ namespace MediaBrowser.Server.Implementations.Persistence
cmd.CommandText += whereText;
- if (EnableGroupByPresentationUniqueKey(query) && _config.Configuration.SchemaVersion >= 66)
- {
- cmd.CommandText += " Group by PresentationUniqueKey";
- }
+ cmd.CommandText += GetGroupBy(query);
cmd.CommandText += GetOrderByText(query);
if (query.Limit.HasValue || query.StartIndex.HasValue)
{
- var limit = query.Limit ?? int.MaxValue;
+ var offset = query.StartIndex ?? 0;
- cmd.CommandText += " LIMIT " + limit.ToString(CultureInfo.InvariantCulture);
+ if (query.Limit.HasValue || offset > 0)
+ {
+ cmd.CommandText += " LIMIT " + (query.Limit ?? int.MaxValue).ToString(CultureInfo.InvariantCulture);
+ }
- if (query.StartIndex.HasValue)
+ if (offset > 0)
{
- cmd.CommandText += " OFFSET " + query.StartIndex.Value.ToString(CultureInfo.InvariantCulture);
+ cmd.CommandText += " OFFSET " + offset.ToString(CultureInfo.InvariantCulture);
}
}
@@ -1696,11 +1840,61 @@ namespace MediaBrowser.Server.Implementations.Persistence
var item = GetItem(reader);
if (item != null)
{
- yield return item;
+ list.Add(item);
}
}
}
}
+
+ // Hack for right now since we currently don't support filtering out these duplicates within a query
+ if (query.EnableGroupByMetadataKey)
+ {
+ var limit = query.Limit ?? int.MaxValue;
+ limit -= 4;
+ var newList = new List<BaseItem>();
+
+ foreach (var item in list)
+ {
+ AddItem(newList, item);
+
+ if (newList.Count >= limit)
+ {
+ break;
+ }
+ }
+
+ list = newList;
+ }
+
+ return list;
+ }
+
+ private void AddItem(List<BaseItem> items, BaseItem newItem)
+ {
+ var providerIds = newItem.ProviderIds.ToList();
+
+ for (var i = 0; i < items.Count; i++)
+ {
+ var item = items[i];
+
+ foreach (var providerId in providerIds)
+ {
+ if (providerId.Key == MetadataProviders.TmdbCollection.ToString())
+ {
+ continue;
+ }
+ if (item.GetProviderId(providerId.Key) == providerId.Value)
+ {
+ if (newItem.SourceType == SourceType.Library)
+ {
+ items[i] = newItem;
+ }
+ return;
+ }
+ }
+ }
+
+ items.Add(newItem);
}
private void LogQueryTime(string methodName, IDbCommand cmd, DateTime startDate)
@@ -1710,7 +1904,7 @@ namespace MediaBrowser.Server.Implementations.Persistence
var slowThreshold = 1000;
#if DEBUG
- slowThreshold = 200;
+ slowThreshold = 50;
#endif
if (elapsed >= slowThreshold)
@@ -1738,11 +1932,21 @@ namespace MediaBrowser.Server.Implementations.Persistence
CheckDisposed();
+ if (!query.EnableTotalRecordCount || (!query.Limit.HasValue && (query.StartIndex ?? 0) == 0))
+ {
+ var list = GetItemList(query);
+ return new QueryResult<BaseItem>
+ {
+ Items = list.ToArray(),
+ TotalRecordCount = list.Count
+ };
+ }
+
var now = DateTime.UtcNow;
using (var cmd = _connection.CreateCommand())
{
- cmd.CommandText = "select " + string.Join(",", GetFinalColumnsToSelect(query, _retriveItemColumns)) + " from TypedBaseItems";
+ cmd.CommandText = "select " + string.Join(",", GetFinalColumnsToSelect(query, _retriveItemColumns, cmd)) + GetFromText();
cmd.CommandText += GetJoinUserDataText(query);
if (EnableJoinUserData(query))
@@ -1762,32 +1966,41 @@ namespace MediaBrowser.Server.Implementations.Persistence
cmd.CommandText += whereText;
- if (EnableGroupByPresentationUniqueKey(query) && _config.Configuration.SchemaVersion >= 66)
- {
- cmd.CommandText += " Group by PresentationUniqueKey";
- }
+ cmd.CommandText += GetGroupBy(query);
cmd.CommandText += GetOrderByText(query);
if (query.Limit.HasValue || query.StartIndex.HasValue)
{
- var limit = query.Limit ?? int.MaxValue;
+ var offset = query.StartIndex ?? 0;
- cmd.CommandText += " LIMIT " + limit.ToString(CultureInfo.InvariantCulture);
+ if (query.Limit.HasValue || offset > 0)
+ {
+ cmd.CommandText += " LIMIT " + (query.Limit ?? int.MaxValue).ToString(CultureInfo.InvariantCulture);
+ }
- if (query.StartIndex.HasValue)
+ if (offset > 0)
{
- cmd.CommandText += " OFFSET " + query.StartIndex.Value.ToString(CultureInfo.InvariantCulture);
+ cmd.CommandText += " OFFSET " + offset.ToString(CultureInfo.InvariantCulture);
}
}
- if (EnableGroupByPresentationUniqueKey(query) && _config.Configuration.SchemaVersion >= 66)
+ cmd.CommandText += ";";
+
+ var isReturningZeroItems = query.Limit.HasValue && query.Limit <= 0;
+
+ if (isReturningZeroItems)
{
- cmd.CommandText += "; select count (distinct PresentationUniqueKey) from TypedBaseItems";
+ cmd.CommandText = "";
+ }
+
+ if (EnableGroupByPresentationUniqueKey(query))
+ {
+ cmd.CommandText += " select count (distinct PresentationUniqueKey)" + GetFromText();
}
else
{
- cmd.CommandText += "; select count (guid) from TypedBaseItems";
+ cmd.CommandText += " select count (guid)" + GetFromText();
}
cmd.CommandText += GetJoinUserDataText(query);
@@ -1800,18 +2013,28 @@ namespace MediaBrowser.Server.Implementations.Persistence
{
LogQueryTime("GetItems", cmd, now);
- while (reader.Read())
+ if (isReturningZeroItems)
{
- var item = GetItem(reader);
- if (item != null)
+ if (reader.Read())
{
- list.Add(item);
+ count = reader.GetInt32(0);
}
}
-
- if (reader.NextResult() && reader.Read())
+ else
{
- count = reader.GetInt32(0);
+ while (reader.Read())
+ {
+ var item = GetItem(reader);
+ if (item != null)
+ {
+ list.Add(item);
+ }
+ }
+
+ if (reader.NextResult() && reader.Read())
+ {
+ count = reader.GetInt32(0);
+ }
}
}
@@ -1825,6 +2048,22 @@ namespace MediaBrowser.Server.Implementations.Persistence
private string GetOrderByText(InternalItemsQuery query)
{
+ if (query.SimilarTo != null)
+ {
+ if (query.SortBy == null || query.SortBy.Length == 0)
+ {
+ if (query.User != null)
+ {
+ query.SortBy = new[] { "SimilarityScore", ItemSortBy.IsPlayed, ItemSortBy.Random };
+ }
+ else
+ {
+ query.SortBy = new[] { "SimilarityScore", ItemSortBy.Random };
+ }
+ query.SortOrder = SortOrder.Descending;
+ }
+ }
+
if (query.SortBy == null || query.SortBy.Length == 0)
{
return string.Empty;
@@ -1834,7 +2073,7 @@ namespace MediaBrowser.Server.Implementations.Persistence
return " ORDER BY " + string.Join(",", query.SortBy.Select(i =>
{
- var columnMap = MapOrderByField(i, EnableJoinUserData(query));
+ var columnMap = MapOrderByField(i, query);
var columnAscending = isAscending;
if (columnMap.Item2)
{
@@ -1847,7 +2086,7 @@ namespace MediaBrowser.Server.Implementations.Persistence
}).ToArray());
}
- private Tuple<string, bool> MapOrderByField(string name, bool enableUserData)
+ private Tuple<string, bool> MapOrderByField(string name, InternalItemsQuery query)
{
if (string.Equals(name, ItemSortBy.AirTime, StringComparison.OrdinalIgnoreCase))
{
@@ -1862,69 +2101,53 @@ namespace MediaBrowser.Server.Implementations.Persistence
{
return new Tuple<string, bool>("RANDOM()", false);
}
+ if (string.Equals(name, ItemSortBy.DatePlayed, StringComparison.OrdinalIgnoreCase))
+ {
+ return new Tuple<string, bool>("LastPlayedDate", false);
+ }
+ if (string.Equals(name, ItemSortBy.PlayCount, StringComparison.OrdinalIgnoreCase))
+ {
+ return new Tuple<string, bool>("PlayCount", false);
+ }
+ if (string.Equals(name, ItemSortBy.IsFavoriteOrLiked, StringComparison.OrdinalIgnoreCase))
+ {
+ return new Tuple<string, bool>("IsFavorite", true);
+ }
if (string.Equals(name, ItemSortBy.IsFolder, StringComparison.OrdinalIgnoreCase))
{
return new Tuple<string, bool>("IsFolder", true);
}
-
- if (enableUserData)
+ if (string.Equals(name, ItemSortBy.IsPlayed, StringComparison.OrdinalIgnoreCase))
{
- if (string.Equals(name, ItemSortBy.DatePlayed, StringComparison.OrdinalIgnoreCase))
- {
- return new Tuple<string, bool>("LastPlayedDate", false);
- }
- if (string.Equals(name, ItemSortBy.PlayCount, StringComparison.OrdinalIgnoreCase))
- {
- return new Tuple<string, bool>("PlayCount", false);
- }
- if (string.Equals(name, ItemSortBy.IsFavoriteOrLiked, StringComparison.OrdinalIgnoreCase))
- {
- return new Tuple<string, bool>("IsFavorite", true);
- }
- if (string.Equals(name, ItemSortBy.IsPlayed, StringComparison.OrdinalIgnoreCase))
- {
- return new Tuple<string, bool>("played", true);
- }
- if (string.Equals(name, ItemSortBy.IsUnplayed, StringComparison.OrdinalIgnoreCase))
- {
- return new Tuple<string, bool>("played", false);
- }
+ return new Tuple<string, bool>("played", true);
}
- else
+ if (string.Equals(name, ItemSortBy.IsUnplayed, StringComparison.OrdinalIgnoreCase))
{
- if (string.Equals(name, ItemSortBy.DatePlayed, StringComparison.OrdinalIgnoreCase))
- {
- return new Tuple<string, bool>("DateCreated", false);
- }
- if (string.Equals(name, ItemSortBy.PlayCount, StringComparison.OrdinalIgnoreCase))
- {
- return new Tuple<string, bool>("DateCreated", false);
- }
- if (string.Equals(name, ItemSortBy.IsFavoriteOrLiked, StringComparison.OrdinalIgnoreCase))
- {
- return new Tuple<string, bool>("DateCreated", true);
- }
- if (string.Equals(name, ItemSortBy.IsPlayed, StringComparison.OrdinalIgnoreCase))
- {
- return new Tuple<string, bool>("DateCreated", true);
- }
- if (string.Equals(name, ItemSortBy.IsUnplayed, StringComparison.OrdinalIgnoreCase))
- {
- return new Tuple<string, bool>("DateCreated", false);
- }
+ return new Tuple<string, bool>("played", false);
}
-
if (string.Equals(name, ItemSortBy.DateLastContentAdded, StringComparison.OrdinalIgnoreCase))
{
return new Tuple<string, bool>("DateLastMediaAdded", false);
}
if (string.Equals(name, ItemSortBy.Artist, StringComparison.OrdinalIgnoreCase))
{
- return new Tuple<string, bool>("(select value from itemvalues where ItemId=Guid and Type=0 LIMIT 1)", false);
+ return new Tuple<string, bool>("(select CleanValue from itemvalues where ItemId=Guid and Type=0 LIMIT 1)", false);
}
if (string.Equals(name, ItemSortBy.AlbumArtist, StringComparison.OrdinalIgnoreCase))
{
- return new Tuple<string, bool>("(select value from itemvalues where ItemId=Guid and Type=1 LIMIT 1)", false);
+ return new Tuple<string, bool>("(select CleanValue from itemvalues where ItemId=Guid and Type=1 LIMIT 1)", false);
+ }
+ if (string.Equals(name, ItemSortBy.OfficialRating, StringComparison.OrdinalIgnoreCase))
+ {
+ return new Tuple<string, bool>("ParentalRatingValue", false);
+ }
+ if (string.Equals(name, ItemSortBy.Studio, StringComparison.OrdinalIgnoreCase))
+ {
+ return new Tuple<string, bool>("(select CleanValue from itemvalues where ItemId=Guid and Type=3 LIMIT 1)", false);
+ }
+ if (string.Equals(name, ItemSortBy.SeriesDatePlayed, StringComparison.OrdinalIgnoreCase))
+ {
+ return new Tuple<string, bool>("(Select MAX(LastPlayedDate) from TypedBaseItems B" + GetJoinUserDataText(query) + " where B.Guid in (Select ItemId from AncestorIds where AncestorId in (select guid from typedbaseitems c where C.Type = 'MediaBrowser.Controller.Entities.TV.Series' And C.Guid in (Select AncestorId from AncestorIds where ItemId=A.Guid))))", false);
}
return new Tuple<string, bool>(name, false);
@@ -1943,7 +2166,7 @@ namespace MediaBrowser.Server.Implementations.Persistence
using (var cmd = _connection.CreateCommand())
{
- cmd.CommandText = "select " + string.Join(",", GetFinalColumnsToSelect(query, new[] { "guid" })) + " from TypedBaseItems";
+ cmd.CommandText = "select " + string.Join(",", GetFinalColumnsToSelect(query, new[] { "guid" }, cmd)) + GetFromText();
cmd.CommandText += GetJoinUserDataText(query);
if (EnableJoinUserData(query))
@@ -1959,22 +2182,22 @@ namespace MediaBrowser.Server.Implementations.Persistence
cmd.CommandText += whereText;
- if (EnableGroupByPresentationUniqueKey(query) && _config.Configuration.SchemaVersion >= 66)
- {
- cmd.CommandText += " Group by PresentationUniqueKey";
- }
+ cmd.CommandText += GetGroupBy(query);
cmd.CommandText += GetOrderByText(query);
if (query.Limit.HasValue || query.StartIndex.HasValue)
{
- var limit = query.Limit ?? int.MaxValue;
+ var offset = query.StartIndex ?? 0;
- cmd.CommandText += " LIMIT " + limit.ToString(CultureInfo.InvariantCulture);
+ if (query.Limit.HasValue || offset > 0)
+ {
+ cmd.CommandText += " LIMIT " + (query.Limit ?? int.MaxValue).ToString(CultureInfo.InvariantCulture);
+ }
- if (query.StartIndex.HasValue)
+ if (offset > 0)
{
- cmd.CommandText += " OFFSET " + query.StartIndex.Value.ToString(CultureInfo.InvariantCulture);
+ cmd.CommandText += " OFFSET " + offset.ToString(CultureInfo.InvariantCulture);
}
}
@@ -2019,22 +2242,22 @@ namespace MediaBrowser.Server.Implementations.Persistence
cmd.CommandText += whereText;
- if (EnableGroupByPresentationUniqueKey(query) && _config.Configuration.SchemaVersion >= 66)
- {
- cmd.CommandText += " Group by PresentationUniqueKey";
- }
+ cmd.CommandText += GetGroupBy(query);
cmd.CommandText += GetOrderByText(query);
if (query.Limit.HasValue || query.StartIndex.HasValue)
{
- var limit = query.Limit ?? int.MaxValue;
+ var offset = query.StartIndex ?? 0;
- cmd.CommandText += " LIMIT " + limit.ToString(CultureInfo.InvariantCulture);
+ if (query.Limit.HasValue || offset > 0)
+ {
+ cmd.CommandText += " LIMIT " + (query.Limit ?? int.MaxValue).ToString(CultureInfo.InvariantCulture);
+ }
- if (query.StartIndex.HasValue)
+ if (offset > 0)
{
- cmd.CommandText += " OFFSET " + query.StartIndex.Value.ToString(CultureInfo.InvariantCulture);
+ cmd.CommandText += " OFFSET " + offset.ToString(CultureInfo.InvariantCulture);
}
}
@@ -2082,11 +2305,21 @@ namespace MediaBrowser.Server.Implementations.Persistence
CheckDisposed();
+ if (!query.EnableTotalRecordCount || (!query.Limit.HasValue && (query.StartIndex ?? 0) == 0))
+ {
+ var list = GetItemIdsList(query);
+ return new QueryResult<Guid>
+ {
+ Items = list.ToArray(),
+ TotalRecordCount = list.Count
+ };
+ }
+
var now = DateTime.UtcNow;
using (var cmd = _connection.CreateCommand())
{
- cmd.CommandText = "select " + string.Join(",", GetFinalColumnsToSelect(query, new[] { "guid" })) + " from TypedBaseItems";
+ cmd.CommandText = "select " + string.Join(",", GetFinalColumnsToSelect(query, new[] { "guid" }, cmd)) + GetFromText();
var whereClauses = GetWhereClauses(query, cmd);
cmd.CommandText += GetJoinUserDataText(query);
@@ -2102,32 +2335,32 @@ namespace MediaBrowser.Server.Implementations.Persistence
cmd.CommandText += whereText;
- if (EnableGroupByPresentationUniqueKey(query) && _config.Configuration.SchemaVersion >= 66)
- {
- cmd.CommandText += " Group by PresentationUniqueKey";
- }
+ cmd.CommandText += GetGroupBy(query);
cmd.CommandText += GetOrderByText(query);
if (query.Limit.HasValue || query.StartIndex.HasValue)
{
- var limit = query.Limit ?? int.MaxValue;
+ var offset = query.StartIndex ?? 0;
- cmd.CommandText += " LIMIT " + limit.ToString(CultureInfo.InvariantCulture);
+ if (query.Limit.HasValue || offset > 0)
+ {
+ cmd.CommandText += " LIMIT " + (query.Limit ?? int.MaxValue).ToString(CultureInfo.InvariantCulture);
+ }
- if (query.StartIndex.HasValue)
+ if (offset > 0)
{
- cmd.CommandText += " OFFSET " + query.StartIndex.Value.ToString(CultureInfo.InvariantCulture);
+ cmd.CommandText += " OFFSET " + offset.ToString(CultureInfo.InvariantCulture);
}
}
- if (EnableGroupByPresentationUniqueKey(query) && _config.Configuration.SchemaVersion >= 66)
+ if (EnableGroupByPresentationUniqueKey(query))
{
- cmd.CommandText += "; select count (distinct PresentationUniqueKey) from TypedBaseItems";
+ cmd.CommandText += "; select count (distinct PresentationUniqueKey)" + GetFromText();
}
else
{
- cmd.CommandText += "; select count (guid) from TypedBaseItems";
+ cmd.CommandText += "; select count (guid)" + GetFromText();
}
cmd.CommandText += GetJoinUserDataText(query);
@@ -2159,13 +2392,13 @@ namespace MediaBrowser.Server.Implementations.Persistence
}
}
- private List<string> GetWhereClauses(InternalItemsQuery query, IDbCommand cmd)
+ private List<string> GetWhereClauses(InternalItemsQuery query, IDbCommand cmd, string paramSuffix = "")
{
var whereClauses = new List<string>();
if (EnableJoinUserData(query))
{
- whereClauses.Add("(UserId is null or UserId=@UserId)");
+ //whereClauses.Add("(UserId is null or UserId=@UserId)");
}
if (query.IsCurrentSchema.HasValue)
{
@@ -2196,7 +2429,24 @@ namespace MediaBrowser.Server.Implementations.Persistence
}
if (query.IsMovie.HasValue)
{
- whereClauses.Add("IsMovie=@IsMovie");
+ var alternateTypes = new List<string>();
+ if (query.IncludeItemTypes.Length == 0 || query.IncludeItemTypes.Contains(typeof(Movie).Name))
+ {
+ alternateTypes.Add(typeof(Movie).FullName);
+ }
+ if (query.IncludeItemTypes.Length == 0 || query.IncludeItemTypes.Contains(typeof(Trailer).Name))
+ {
+ alternateTypes.Add(typeof(Trailer).FullName);
+ }
+
+ if (alternateTypes.Count == 0)
+ {
+ whereClauses.Add("IsMovie=@IsMovie");
+ }
+ else
+ {
+ whereClauses.Add("(IsMovie is null OR IsMovie=@IsMovie)");
+ }
cmd.Parameters.Add(cmd, "@IsMovie", DbType.Boolean).Value = query.IsMovie;
}
if (query.IsKids.HasValue)
@@ -2218,8 +2468,8 @@ namespace MediaBrowser.Server.Implementations.Persistence
var includeTypes = query.IncludeItemTypes.SelectMany(MapIncludeItemTypes).ToArray();
if (includeTypes.Length == 1)
{
- whereClauses.Add("type=@type");
- cmd.Parameters.Add(cmd, "@type", DbType.String).Value = includeTypes[0];
+ whereClauses.Add("type=@type" + paramSuffix);
+ cmd.Parameters.Add(cmd, "@type" + paramSuffix, DbType.String).Value = includeTypes[0];
}
else if (includeTypes.Length > 1)
{
@@ -2302,6 +2552,11 @@ namespace MediaBrowser.Server.Implementations.Persistence
whereClauses.Add("ParentIndexNumber=@ParentIndexNumber");
cmd.Parameters.Add(cmd, "@ParentIndexNumber", DbType.Int32).Value = query.ParentIndexNumber.Value;
}
+ if (query.ParentIndexNumberNotEquals.HasValue)
+ {
+ whereClauses.Add("(ParentIndexNumber<>@ParentIndexNumberNotEquals or ParentIndexNumber is null)");
+ cmd.Parameters.Add(cmd, "@ParentIndexNumberNotEquals", DbType.Int32).Value = query.ParentIndexNumberNotEquals.Value;
+ }
if (query.MinEndDate.HasValue)
{
whereClauses.Add("EndDate>=@MinEndDate");
@@ -2390,6 +2645,12 @@ namespace MediaBrowser.Server.Implementations.Persistence
}
}
+ if (query.PersonIds.Length > 0)
+ {
+ // Todo: improve without having to do this
+ query.Person = query.PersonIds.Select(i => RetrieveItem(new Guid(i))).Where(i => i != null).Select(i => i.Name).FirstOrDefault();
+ }
+
if (!string.IsNullOrWhiteSpace(query.Person))
{
whereClauses.Add("Guid in (select ItemId from People where Name=@PersonName)");
@@ -2398,41 +2659,25 @@ namespace MediaBrowser.Server.Implementations.Persistence
if (!string.IsNullOrWhiteSpace(query.SlugName))
{
- if (_config.Configuration.SchemaVersion >= 70)
- {
- whereClauses.Add("SlugName=@SlugName");
- }
- else
- {
- whereClauses.Add("Name=@SlugName");
- }
+ whereClauses.Add("SlugName=@SlugName");
cmd.Parameters.Add(cmd, "@SlugName", DbType.String).Value = query.SlugName;
}
+ if (!string.IsNullOrWhiteSpace(query.MinSortName))
+ {
+ whereClauses.Add("SortName>=@MinSortName");
+ cmd.Parameters.Add(cmd, "@MinSortName", DbType.String).Value = query.MinSortName;
+ }
+
if (!string.IsNullOrWhiteSpace(query.Name))
{
- if (_config.Configuration.SchemaVersion >= 66)
- {
- whereClauses.Add("CleanName=@Name");
- cmd.Parameters.Add(cmd, "@Name", DbType.String).Value = query.Name.RemoveDiacritics();
- }
- else
- {
- whereClauses.Add("Name=@Name");
- cmd.Parameters.Add(cmd, "@Name", DbType.String).Value = query.Name;
- }
+ whereClauses.Add("CleanName=@Name");
+ cmd.Parameters.Add(cmd, "@Name", DbType.String).Value = query.Name.RemoveDiacritics();
}
if (!string.IsNullOrWhiteSpace(query.NameContains))
{
- if (_config.Configuration.SchemaVersion >= 66)
- {
- whereClauses.Add("CleanName like @NameContains");
- }
- else
- {
- whereClauses.Add("Name like @NameContains");
- }
+ whereClauses.Add("CleanName like @NameContains");
cmd.Parameters.Add(cmd, "@NameContains", DbType.String).Value = "%" + query.NameContains.RemoveDiacritics() + "%";
}
if (!string.IsNullOrWhiteSpace(query.NameStartsWith))
@@ -2453,48 +2698,61 @@ namespace MediaBrowser.Server.Implementations.Persistence
cmd.Parameters.Add(cmd, "@NameLessThan", DbType.String).Value = query.NameLessThan.ToLower();
}
- if (EnableJoinUserData(query))
+ if (query.ImageTypes.Length > 0 && _config.Configuration.SchemaVersion >= 87)
{
- if (query.IsLiked.HasValue)
+ var requiredImageIndex = 0;
+
+ foreach (var requiredImage in query.ImageTypes)
{
- if (query.IsLiked.Value)
- {
- whereClauses.Add("rating>=@UserRating");
- cmd.Parameters.Add(cmd, "@UserRating", DbType.Double).Value = UserItemData.MinLikeValue;
- }
- else
- {
- whereClauses.Add("(rating is null or rating<@UserRating)");
- cmd.Parameters.Add(cmd, "@UserRating", DbType.Double).Value = UserItemData.MinLikeValue;
- }
+ var paramName = "@RequiredImageType" + requiredImageIndex;
+ whereClauses.Add("(select path from images where ItemId=Guid and ImageType=" + paramName + " limit 1) not null");
+ cmd.Parameters.Add(cmd, paramName, DbType.Int32).Value = (int)requiredImage;
+ requiredImageIndex++;
}
+ }
- if (query.IsFavoriteOrLiked.HasValue)
+ if (query.IsLiked.HasValue)
+ {
+ if (query.IsLiked.Value)
{
- if (query.IsFavoriteOrLiked.Value)
- {
- whereClauses.Add("IsFavorite=@IsFavoriteOrLiked");
- }
- else
- {
- whereClauses.Add("(IsFavorite is null or IsFavorite=@IsFavoriteOrLiked)");
- }
- cmd.Parameters.Add(cmd, "@IsFavoriteOrLiked", DbType.Boolean).Value = query.IsFavoriteOrLiked.Value;
+ whereClauses.Add("rating>=@UserRating");
+ cmd.Parameters.Add(cmd, "@UserRating", DbType.Double).Value = UserItemData.MinLikeValue;
}
+ else
+ {
+ whereClauses.Add("(rating is null or rating<@UserRating)");
+ cmd.Parameters.Add(cmd, "@UserRating", DbType.Double).Value = UserItemData.MinLikeValue;
+ }
+ }
- if (query.IsFavorite.HasValue)
+ if (query.IsFavoriteOrLiked.HasValue)
+ {
+ if (query.IsFavoriteOrLiked.Value)
{
- if (query.IsFavorite.Value)
- {
- whereClauses.Add("IsFavorite=@IsFavorite");
- }
- else
- {
- whereClauses.Add("(IsFavorite is null or IsFavorite=@IsFavorite)");
- }
- cmd.Parameters.Add(cmd, "@IsFavorite", DbType.Boolean).Value = query.IsFavorite.Value;
+ whereClauses.Add("IsFavorite=@IsFavoriteOrLiked");
+ }
+ else
+ {
+ whereClauses.Add("(IsFavorite is null or IsFavorite=@IsFavoriteOrLiked)");
}
+ cmd.Parameters.Add(cmd, "@IsFavoriteOrLiked", DbType.Boolean).Value = query.IsFavoriteOrLiked.Value;
+ }
+ if (query.IsFavorite.HasValue)
+ {
+ if (query.IsFavorite.Value)
+ {
+ whereClauses.Add("IsFavorite=@IsFavorite");
+ }
+ else
+ {
+ whereClauses.Add("(IsFavorite is null or IsFavorite=@IsFavorite)");
+ }
+ cmd.Parameters.Add(cmd, "@IsFavorite", DbType.Boolean).Value = query.IsFavorite.Value;
+ }
+
+ if (EnableJoinUserData(query))
+ {
if (query.IsPlayed.HasValue)
{
if (query.IsPlayed.Value)
@@ -2507,17 +2765,17 @@ namespace MediaBrowser.Server.Implementations.Persistence
}
cmd.Parameters.Add(cmd, "@IsPlayed", DbType.Boolean).Value = query.IsPlayed.Value;
}
+ }
- if (query.IsResumable.HasValue)
+ if (query.IsResumable.HasValue)
+ {
+ if (query.IsResumable.Value)
{
- if (query.IsResumable.Value)
- {
- whereClauses.Add("playbackPositionTicks > 0");
- }
- else
- {
- whereClauses.Add("(playbackPositionTicks is null or playbackPositionTicks = 0)");
- }
+ whereClauses.Add("playbackPositionTicks > 0");
+ }
+ else
+ {
+ whereClauses.Add("(playbackPositionTicks is null or playbackPositionTicks = 0)");
}
}
@@ -2527,22 +2785,28 @@ namespace MediaBrowser.Server.Implementations.Persistence
var index = 0;
foreach (var artist in query.ArtistNames)
{
- clauses.Add("@ArtistName" + index + " in (select value from itemvalues where ItemId=Guid and Type <= 1)");
- cmd.Parameters.Add(cmd, "@ArtistName" + index, DbType.String).Value = artist;
+ clauses.Add("@ArtistName" + index + " in (select CleanValue from itemvalues where ItemId=Guid and Type <= 1)");
+ cmd.Parameters.Add(cmd, "@ArtistName" + index, DbType.String).Value = artist.RemoveDiacritics();
index++;
}
var clause = "(" + string.Join(" OR ", clauses.ToArray()) + ")";
whereClauses.Add(clause);
}
+ if (query.GenreIds.Length > 0)
+ {
+ // Todo: improve without having to do this
+ query.Genres = query.GenreIds.Select(i => RetrieveItem(new Guid(i))).Where(i => i != null).Select(i => i.Name).ToArray();
+ }
+
if (query.Genres.Length > 0)
{
var clauses = new List<string>();
var index = 0;
foreach (var item in query.Genres)
{
- clauses.Add("Genres like @Genres" + index);
- cmd.Parameters.Add(cmd, "@Genres" + index, DbType.String).Value = "%" + item + "%";
+ clauses.Add("@Genre" + index + " in (select CleanValue from itemvalues where ItemId=Guid and Type=2)");
+ cmd.Parameters.Add(cmd, "@Genre" + index, DbType.String).Value = item.RemoveDiacritics();
index++;
}
var clause = "(" + string.Join(" OR ", clauses.ToArray()) + ")";
@@ -2555,22 +2819,56 @@ namespace MediaBrowser.Server.Implementations.Persistence
var index = 0;
foreach (var item in query.Tags)
{
- clauses.Add("Tags like @Tags" + index);
- cmd.Parameters.Add(cmd, "@Tags" + index, DbType.String).Value = "%" + item + "%";
+ clauses.Add("@Tag" + index + " in (select CleanValue from itemvalues where ItemId=Guid and Type=4)");
+ cmd.Parameters.Add(cmd, "@Tag" + index, DbType.String).Value = item.RemoveDiacritics();
index++;
}
var clause = "(" + string.Join(" OR ", clauses.ToArray()) + ")";
whereClauses.Add(clause);
}
+ if (query.StudioIds.Length > 0)
+ {
+ // Todo: improve without having to do this
+ query.Studios = query.StudioIds.Select(i => RetrieveItem(new Guid(i))).Where(i => i != null).Select(i => i.Name).ToArray();
+ }
+
if (query.Studios.Length > 0)
{
var clauses = new List<string>();
var index = 0;
foreach (var item in query.Studios)
{
- clauses.Add("Studios like @Studios" + index);
- cmd.Parameters.Add(cmd, "@Studios" + index, DbType.String).Value = "%" + item + "%";
+ clauses.Add("@Studio" + index + " in (select CleanValue from itemvalues where ItemId=Guid and Type=3)");
+ cmd.Parameters.Add(cmd, "@Studio" + index, DbType.String).Value = item.RemoveDiacritics();
+ index++;
+ }
+ var clause = "(" + string.Join(" OR ", clauses.ToArray()) + ")";
+ whereClauses.Add(clause);
+ }
+
+ if (query.Keywords.Length > 0)
+ {
+ var clauses = new List<string>();
+ var index = 0;
+ foreach (var item in query.Keywords)
+ {
+ clauses.Add("@Keyword" + index + " in (select CleanValue from itemvalues where ItemId=Guid and Type=5)");
+ cmd.Parameters.Add(cmd, "@Keyword" + index, DbType.String).Value = item.RemoveDiacritics();
+ index++;
+ }
+ var clause = "(" + string.Join(" OR ", clauses.ToArray()) + ")";
+ whereClauses.Add(clause);
+ }
+
+ if (query.OfficialRatings.Length > 0)
+ {
+ var clauses = new List<string>();
+ var index = 0;
+ foreach (var item in query.OfficialRatings)
+ {
+ clauses.Add("OfficialRating=@OfficialRating" + index);
+ cmd.Parameters.Add(cmd, "@OfficialRating" + index, DbType.String).Value = item;
index++;
}
var clause = "(" + string.Join(" OR ", clauses.ToArray()) + ")";
@@ -2635,8 +2933,15 @@ namespace MediaBrowser.Server.Implementations.Persistence
if (query.LocationTypes.Length == 1)
{
- whereClauses.Add("LocationType=@LocationType");
- cmd.Parameters.Add(cmd, "@LocationType", DbType.String).Value = query.LocationTypes[0].ToString();
+ if (query.LocationTypes[0] == LocationType.Virtual && _config.Configuration.SchemaVersion >= 90)
+ {
+ query.IsVirtualItem = true;
+ }
+ else
+ {
+ whereClauses.Add("LocationType=@LocationType");
+ cmd.Parameters.Add(cmd, "@LocationType", DbType.String).Value = query.LocationTypes[0].ToString();
+ }
}
else if (query.LocationTypes.Length > 1)
{
@@ -2646,8 +2951,15 @@ namespace MediaBrowser.Server.Implementations.Persistence
}
if (query.ExcludeLocationTypes.Length == 1)
{
- whereClauses.Add("LocationType<>@ExcludeLocationTypes");
- cmd.Parameters.Add(cmd, "@ExcludeLocationTypes", DbType.String).Value = query.ExcludeLocationTypes[0].ToString();
+ if (query.ExcludeLocationTypes[0] == LocationType.Virtual && _config.Configuration.SchemaVersion >= 90)
+ {
+ query.IsVirtualItem = false;
+ }
+ else
+ {
+ whereClauses.Add("LocationType<>@ExcludeLocationTypes");
+ cmd.Parameters.Add(cmd, "@ExcludeLocationTypes", DbType.String).Value = query.ExcludeLocationTypes[0].ToString();
+ }
}
else if (query.ExcludeLocationTypes.Length > 1)
{
@@ -2655,6 +2967,11 @@ namespace MediaBrowser.Server.Implementations.Persistence
whereClauses.Add("LocationType not in (" + val + ")");
}
+ if (query.IsVirtualItem.HasValue)
+ {
+ whereClauses.Add("IsVirtualItem=@IsVirtualItem");
+ cmd.Parameters.Add(cmd, "@IsVirtualItem", DbType.Boolean).Value = query.IsVirtualItem.Value;
+ }
if (query.MediaTypes.Length == 1)
{
whereClauses.Add("MediaType=@MediaTypes");
@@ -2666,6 +2983,73 @@ namespace MediaBrowser.Server.Implementations.Persistence
whereClauses.Add("MediaType in (" + val + ")");
}
+ if (query.ItemIds.Length > 0)
+ {
+ var excludeIds = new List<string>();
+
+ var index = 0;
+ foreach (var id in query.ItemIds)
+ {
+ excludeIds.Add("Guid = @IncludeId" + index);
+ cmd.Parameters.Add(cmd, "@IncludeId" + index, DbType.Guid).Value = new Guid(id);
+ index++;
+ }
+
+ whereClauses.Add(string.Join(" OR ", excludeIds.ToArray()));
+ }
+ if (query.ExcludeItemIds.Length > 0)
+ {
+ var excludeIds = new List<string>();
+
+ var index = 0;
+ foreach (var id in query.ExcludeItemIds)
+ {
+ excludeIds.Add("Guid <> @ExcludeId" + index);
+ cmd.Parameters.Add(cmd, "@ExcludeId" + index, DbType.Guid).Value = new Guid(id);
+ index++;
+ }
+
+ whereClauses.Add(string.Join(" AND ", excludeIds.ToArray()));
+ }
+
+ if (query.ExcludeProviderIds.Count > 0)
+ {
+ var excludeIds = new List<string>();
+
+ var index = 0;
+ foreach (var pair in query.ExcludeProviderIds)
+ {
+ if (string.Equals(pair.Key, MetadataProviders.TmdbCollection.ToString(), StringComparison.OrdinalIgnoreCase))
+ {
+ continue;
+ }
+
+ var paramName = "@ExcludeProviderId" + index;
+ excludeIds.Add("(COALESCE((select value from ProviderIds where ItemId=Guid and Name = '" + pair.Key + "'), '') <> " + paramName + ")");
+ cmd.Parameters.Add(cmd, paramName, DbType.String).Value = pair.Value;
+ index++;
+ }
+
+ whereClauses.Add(string.Join(" AND ", excludeIds.ToArray()));
+ }
+
+ if (query.HasImdbId.HasValue)
+ {
+ var fn = query.HasImdbId.Value ? "<>" : "=";
+ whereClauses.Add("(COALESCE((select value from ProviderIds where ItemId=Guid and Name = 'Imdb'), '') " + fn + " '')");
+ }
+
+ if (query.HasTmdbId.HasValue)
+ {
+ var fn = query.HasTmdbId.Value ? "<>" : "=";
+ whereClauses.Add("(COALESCE((select value from ProviderIds where ItemId=Guid and Name = 'Tmdb'), '') " + fn + " '')");
+ }
+
+ if (query.HasTvdbId.HasValue)
+ {
+ var fn = query.HasTvdbId.Value ? "<>" : "=";
+ whereClauses.Add("(COALESCE((select value from ProviderIds where ItemId=Guid and Name = 'Tvdb'), '') " + fn + " '')");
+ }
if (query.AlbumNames.Length > 0)
{
@@ -3077,6 +3461,16 @@ namespace MediaBrowser.Server.Implementations.Persistence
_deleteItemValuesCommand.Transaction = transaction;
_deleteItemValuesCommand.ExecuteNonQuery();
+ // Delete provider ids
+ _deleteProviderIdsCommand.GetParameter(0).Value = id;
+ _deleteProviderIdsCommand.Transaction = transaction;
+ _deleteProviderIdsCommand.ExecuteNonQuery();
+
+ // Delete images
+ _deleteImagesCommand.GetParameter(0).Value = id;
+ _deleteImagesCommand.Transaction = transaction;
+ _deleteImagesCommand.ExecuteNonQuery();
+
// Delete the item
_deleteItemCommand.GetParameter(0).Value = id;
_deleteItemCommand.Transaction = transaction;
@@ -3269,7 +3663,311 @@ namespace MediaBrowser.Server.Implementations.Persistence
}
}
- private List<Tuple<int, string>> GetItemValues(BaseItem item)
+ public QueryResult<Tuple<BaseItem, ItemCounts>> GetArtists(InternalItemsQuery query)
+ {
+ return GetItemValues(query, 0, typeof(MusicArtist).FullName);
+ }
+
+ public QueryResult<Tuple<BaseItem, ItemCounts>> GetAlbumArtists(InternalItemsQuery query)
+ {
+ return GetItemValues(query, 1, typeof(MusicArtist).FullName);
+ }
+
+ public QueryResult<Tuple<BaseItem, ItemCounts>> GetStudios(InternalItemsQuery query)
+ {
+ return GetItemValues(query, 3, typeof(Studio).FullName);
+ }
+
+ public QueryResult<Tuple<BaseItem, ItemCounts>> GetGenres(InternalItemsQuery query)
+ {
+ return GetItemValues(query, 2, typeof(Genre).FullName);
+ }
+
+ public QueryResult<Tuple<BaseItem, ItemCounts>> GetGameGenres(InternalItemsQuery query)
+ {
+ return GetItemValues(query, 2, typeof(GameGenre).FullName);
+ }
+
+ public QueryResult<Tuple<BaseItem, ItemCounts>> GetMusicGenres(InternalItemsQuery query)
+ {
+ return GetItemValues(query, 2, typeof(MusicGenre).FullName);
+ }
+
+ private QueryResult<Tuple<BaseItem, ItemCounts>> GetItemValues(InternalItemsQuery query, int itemValueType, string returnType)
+ {
+ if (query == null)
+ {
+ throw new ArgumentNullException("query");
+ }
+
+ if (!query.Limit.HasValue)
+ {
+ query.EnableTotalRecordCount = false;
+ }
+
+ CheckDisposed();
+
+ var now = DateTime.UtcNow;
+
+ using (var cmd = _connection.CreateCommand())
+ {
+ var itemCountColumns = new List<Tuple<string, string>>();
+
+ var typesToCount = query.IncludeItemTypes.ToList();
+
+ if (typesToCount.Count > 0)
+ {
+ var itemCountColumnQuery = "select group_concat(type, '|')" + GetFromText("B");
+
+ var typeSubQuery = new InternalItemsQuery(query.User)
+ {
+ ExcludeItemTypes = query.ExcludeItemTypes,
+ IncludeItemTypes = query.IncludeItemTypes,
+ MediaTypes = query.MediaTypes,
+ AncestorIds = query.AncestorIds,
+ ExcludeItemIds = query.ExcludeItemIds,
+ ItemIds = query.ItemIds,
+ TopParentIds = query.TopParentIds,
+ ParentId = query.ParentId,
+ IsPlayed = query.IsPlayed
+ };
+ var whereClauses = GetWhereClauses(typeSubQuery, cmd, "itemTypes");
+
+ whereClauses.Add("guid in (select ItemId from ItemValues where ItemValues.CleanValue=A.CleanName AND Type=@ItemValueType)");
+
+ var typeWhereText = whereClauses.Count == 0 ?
+ string.Empty :
+ " where " + string.Join(" AND ", whereClauses.ToArray());
+
+ itemCountColumnQuery += typeWhereText;
+
+ //itemCountColumnQuery += ")";
+
+ itemCountColumns.Add(new Tuple<string, string>("itemTypes", "(" + itemCountColumnQuery + ") as itemTypes"));
+ }
+
+ var columns = _retriveItemColumns.ToList();
+ columns.AddRange(itemCountColumns.Select(i => i.Item2).ToArray());
+
+ cmd.CommandText = "select " + string.Join(",", GetFinalColumnsToSelect(query, columns.ToArray(), cmd)) + GetFromText();
+ cmd.CommandText += GetJoinUserDataText(query);
+
+ var innerQuery = new InternalItemsQuery(query.User)
+ {
+ ExcludeItemTypes = query.ExcludeItemTypes,
+ IncludeItemTypes = query.IncludeItemTypes,
+ MediaTypes = query.MediaTypes,
+ AncestorIds = query.AncestorIds,
+ ExcludeItemIds = query.ExcludeItemIds,
+ ItemIds = query.ItemIds,
+ TopParentIds = query.TopParentIds,
+ ParentId = query.ParentId,
+ IsPlayed = query.IsPlayed
+ };
+
+ var innerWhereClauses = GetWhereClauses(innerQuery, cmd);
+
+ var innerWhereText = innerWhereClauses.Count == 0 ?
+ string.Empty :
+ " where " + string.Join(" AND ", innerWhereClauses.ToArray());
+
+ var whereText = " where Type=@SelectType";
+
+ if (typesToCount.Count == 0)
+ {
+ whereText += " And CleanName In (Select CleanValue from ItemValues where Type=@ItemValueType AND ItemId in (select guid from TypedBaseItems" + innerWhereText + "))";
+ }
+ else
+ {
+ //whereText += " And itemTypes not null";
+ whereText += " And CleanName In (Select CleanValue from ItemValues where Type=@ItemValueType AND ItemId in (select guid from TypedBaseItems" + innerWhereText + "))";
+ }
+
+ var outerQuery = new InternalItemsQuery(query.User)
+ {
+ IsFavorite = query.IsFavorite,
+ IsFavoriteOrLiked = query.IsFavoriteOrLiked,
+ IsLiked = query.IsLiked,
+ IsLocked = query.IsLocked,
+ NameLessThan = query.NameLessThan,
+ NameStartsWith = query.NameStartsWith,
+ NameStartsWithOrGreater = query.NameStartsWithOrGreater,
+ AlbumArtistStartsWithOrGreater = query.AlbumArtistStartsWithOrGreater,
+ Tags = query.Tags,
+ OfficialRatings = query.OfficialRatings,
+ Genres = query.GenreIds,
+ Years = query.Years
+ };
+
+ var outerWhereClauses = GetWhereClauses(outerQuery, cmd);
+
+ whereText += outerWhereClauses.Count == 0 ?
+ string.Empty :
+ " AND " + string.Join(" AND ", outerWhereClauses.ToArray());
+ //cmd.CommandText += GetGroupBy(query);
+
+ cmd.CommandText += whereText;
+ cmd.CommandText += " group by PresentationUniqueKey";
+
+ cmd.Parameters.Add(cmd, "@SelectType", DbType.String).Value = returnType;
+ cmd.Parameters.Add(cmd, "@ItemValueType", DbType.Int32).Value = itemValueType;
+
+ if (EnableJoinUserData(query))
+ {
+ cmd.Parameters.Add(cmd, "@UserId", DbType.Guid).Value = query.User.Id;
+ }
+
+ cmd.CommandText += " order by SortName";
+
+ if (query.Limit.HasValue || query.StartIndex.HasValue)
+ {
+ var offset = query.StartIndex ?? 0;
+
+ if (query.Limit.HasValue || offset > 0)
+ {
+ cmd.CommandText += " LIMIT " + (query.Limit ?? int.MaxValue).ToString(CultureInfo.InvariantCulture);
+ }
+
+ if (offset > 0)
+ {
+ cmd.CommandText += " OFFSET " + offset.ToString(CultureInfo.InvariantCulture);
+ }
+ }
+
+ cmd.CommandText += ";";
+
+ var isReturningZeroItems = query.Limit.HasValue && query.Limit <= 0;
+
+ if (isReturningZeroItems)
+ {
+ cmd.CommandText = "";
+ }
+
+ if (query.EnableTotalRecordCount)
+ {
+ cmd.CommandText += "select count (distinct PresentationUniqueKey)" + GetFromText();
+
+ cmd.CommandText += GetJoinUserDataText(query);
+ cmd.CommandText += whereText;
+ }
+ else
+ {
+ cmd.CommandText = cmd.CommandText.TrimEnd(';');
+ }
+
+ var list = new List<Tuple<BaseItem, ItemCounts>>();
+ var count = 0;
+
+ var commandBehavior = isReturningZeroItems || !query.EnableTotalRecordCount
+ ? (CommandBehavior.SequentialAccess | CommandBehavior.SingleResult)
+ : CommandBehavior.SequentialAccess;
+
+ Logger.Debug("GetItemValues: " + cmd.CommandText);
+
+ using (var reader = cmd.ExecuteReader(commandBehavior))
+ {
+ LogQueryTime("GetItemValues", cmd, now);
+
+ if (isReturningZeroItems)
+ {
+ if (reader.Read())
+ {
+ count = reader.GetInt32(0);
+ }
+ }
+ else
+ {
+ while (reader.Read())
+ {
+ var item = GetItem(reader);
+ if (item != null)
+ {
+ var countStartColumn = columns.Count - 1;
+
+ list.Add(new Tuple<BaseItem, ItemCounts>(item, GetItemCounts(reader, countStartColumn, typesToCount)));
+ }
+ }
+
+ if (reader.NextResult() && reader.Read())
+ {
+ count = reader.GetInt32(0);
+ }
+ }
+ }
+
+ if (count == 0)
+ {
+ count = list.Count;
+ }
+
+ return new QueryResult<Tuple<BaseItem, ItemCounts>>
+ {
+ Items = list.ToArray(),
+ TotalRecordCount = count
+ };
+
+ }
+ }
+
+ private ItemCounts GetItemCounts(IDataReader reader, int countStartColumn, List<string> typesToCount)
+ {
+ var counts = new ItemCounts();
+
+ if (typesToCount.Count == 0)
+ {
+ return counts;
+ }
+
+ var typeString = reader.IsDBNull(countStartColumn) ? null : reader.GetString(countStartColumn);
+
+ if (string.IsNullOrWhiteSpace(typeString))
+ {
+ return counts;
+ }
+
+ var allTypes = typeString.Split(new[] { '|' }, StringSplitOptions.RemoveEmptyEntries)
+ .ToLookup(i => i).ToList();
+
+ foreach (var type in allTypes)
+ {
+ var value = type.ToList().Count;
+ var typeName = type.Key;
+
+ if (string.Equals(typeName, typeof(Series).FullName, StringComparison.OrdinalIgnoreCase))
+ {
+ counts.SeriesCount = value;
+ }
+ else if (string.Equals(typeName, typeof(Episode).FullName, StringComparison.OrdinalIgnoreCase))
+ {
+ counts.EpisodeCount = value;
+ }
+ else if (string.Equals(typeName, typeof(Movie).FullName, StringComparison.OrdinalIgnoreCase))
+ {
+ counts.MovieCount = value;
+ }
+ else if (string.Equals(typeName, typeof(MusicAlbum).FullName, StringComparison.OrdinalIgnoreCase))
+ {
+ counts.AlbumCount = value;
+ }
+ else if (string.Equals(typeName, typeof(Audio).FullName, StringComparison.OrdinalIgnoreCase))
+ {
+ counts.SongCount = value;
+ }
+ else if (string.Equals(typeName, typeof(Game).FullName, StringComparison.OrdinalIgnoreCase))
+ {
+ counts.GameCount = value;
+ }
+ else if (string.Equals(typeName, typeof(Trailer).FullName, StringComparison.OrdinalIgnoreCase))
+ {
+ counts.TrailerCount = value;
+ }
+ counts.ItemCount += value;
+ }
+
+ return counts;
+ }
+
+ private List<Tuple<int, string>> GetItemValuesToSave(BaseItem item)
{
var list = new List<Tuple<int, string>>();
@@ -3285,9 +3983,91 @@ namespace MediaBrowser.Server.Implementations.Persistence
list.AddRange(hasAlbumArtist.AlbumArtists.Select(i => new Tuple<int, string>(1, i)));
}
+ list.AddRange(item.Genres.Select(i => new Tuple<int, string>(2, i)));
+ list.AddRange(item.Studios.Select(i => new Tuple<int, string>(3, i)));
+ list.AddRange(item.Tags.Select(i => new Tuple<int, string>(4, i)));
+ list.AddRange(item.Keywords.Select(i => new Tuple<int, string>(5, i)));
+
return list;
}
+ private void UpdateImages(Guid itemId, List<ItemImageInfo> images, IDbTransaction transaction)
+ {
+ if (itemId == Guid.Empty)
+ {
+ throw new ArgumentNullException("itemId");
+ }
+
+ if (images == null)
+ {
+ throw new ArgumentNullException("images");
+ }
+
+ CheckDisposed();
+
+ // First delete
+ _deleteImagesCommand.GetParameter(0).Value = itemId;
+ _deleteImagesCommand.Transaction = transaction;
+
+ _deleteImagesCommand.ExecuteNonQuery();
+
+ var index = 0;
+ foreach (var image in images)
+ {
+ _saveImagesCommand.GetParameter(0).Value = itemId;
+ _saveImagesCommand.GetParameter(1).Value = image.Type;
+ _saveImagesCommand.GetParameter(2).Value = image.Path;
+
+ if (image.DateModified == default(DateTime))
+ {
+ _saveImagesCommand.GetParameter(3).Value = null;
+ }
+ else
+ {
+ _saveImagesCommand.GetParameter(3).Value = image.DateModified;
+ }
+
+ _saveImagesCommand.GetParameter(4).Value = image.IsPlaceholder;
+ _saveImagesCommand.GetParameter(5).Value = index;
+
+ _saveImagesCommand.Transaction = transaction;
+
+ _saveImagesCommand.ExecuteNonQuery();
+ index++;
+ }
+ }
+
+ private void UpdateProviderIds(Guid itemId, Dictionary<string, string> values, IDbTransaction transaction)
+ {
+ if (itemId == Guid.Empty)
+ {
+ throw new ArgumentNullException("itemId");
+ }
+
+ if (values == null)
+ {
+ throw new ArgumentNullException("values");
+ }
+
+ CheckDisposed();
+
+ // First delete
+ _deleteProviderIdsCommand.GetParameter(0).Value = itemId;
+ _deleteProviderIdsCommand.Transaction = transaction;
+
+ _deleteProviderIdsCommand.ExecuteNonQuery();
+
+ foreach (var pair in values)
+ {
+ _saveProviderIdsCommand.GetParameter(0).Value = itemId;
+ _saveProviderIdsCommand.GetParameter(1).Value = pair.Key;
+ _saveProviderIdsCommand.GetParameter(2).Value = pair.Value;
+ _saveProviderIdsCommand.Transaction = transaction;
+
+ _saveProviderIdsCommand.ExecuteNonQuery();
+ }
+ }
+
private void UpdateItemValues(Guid itemId, List<Tuple<int, string>> values, IDbTransaction transaction)
{
if (itemId == Guid.Empty)
@@ -3313,6 +4093,14 @@ namespace MediaBrowser.Server.Implementations.Persistence
_saveItemValuesCommand.GetParameter(0).Value = itemId;
_saveItemValuesCommand.GetParameter(1).Value = pair.Item1;
_saveItemValuesCommand.GetParameter(2).Value = pair.Item2;
+ if (pair.Item2 == null)
+ {
+ _saveItemValuesCommand.GetParameter(3).Value = null;
+ }
+ else
+ {
+ _saveItemValuesCommand.GetParameter(3).Value = pair.Item2.RemoveDiacritics();
+ }
_saveItemValuesCommand.Transaction = transaction;
_saveItemValuesCommand.ExecuteNonQuery();
@@ -3505,7 +4293,7 @@ namespace MediaBrowser.Server.Implementations.Persistence
return list;
}
- public async Task SaveMediaStreams(Guid id, IEnumerable<MediaStream> streams, CancellationToken cancellationToken)
+ public async Task SaveMediaStreams(Guid id, List<MediaStream> streams, CancellationToken cancellationToken)
{
CheckDisposed();
@@ -3578,6 +4366,9 @@ namespace MediaBrowser.Server.Implementations.Persistence
_saveStreamCommand.GetParameter(index++).Value = stream.IsAVC;
_saveStreamCommand.GetParameter(index++).Value = stream.Title;
+ _saveStreamCommand.GetParameter(index++).Value = stream.TimeBase;
+ _saveStreamCommand.GetParameter(index++).Value = stream.CodecTimeBase;
+
_saveStreamCommand.Transaction = transaction;
_saveStreamCommand.ExecuteNonQuery();
}
@@ -3750,6 +4541,16 @@ namespace MediaBrowser.Server.Implementations.Persistence
item.Title = reader.GetString(29);
}
+ if (!reader.IsDBNull(30))
+ {
+ item.TimeBase = reader.GetString(30);
+ }
+
+ if (!reader.IsDBNull(31))
+ {
+ item.CodecTimeBase = reader.GetString(31);
+ }
+
return item;
}
diff --git a/MediaBrowser.Server.Implementations/Persistence/SqliteProviderInfoRepository.cs b/MediaBrowser.Server.Implementations/Persistence/SqliteProviderInfoRepository.cs
deleted file mode 100644
index 40d5c9586..000000000
--- a/MediaBrowser.Server.Implementations/Persistence/SqliteProviderInfoRepository.cs
+++ /dev/null
@@ -1,248 +0,0 @@
-using MediaBrowser.Common.Configuration;
-using MediaBrowser.Controller.Providers;
-using MediaBrowser.Model.Logging;
-using System;
-using System.Data;
-using System.IO;
-using System.Linq;
-using System.Text;
-using System.Threading;
-using System.Threading.Tasks;
-
-namespace MediaBrowser.Server.Implementations.Persistence
-{
- public class SqliteProviderInfoRepository : BaseSqliteRepository, IProviderRepository
- {
- private IDbConnection _connection;
-
- private IDbCommand _saveStatusCommand;
- private readonly IApplicationPaths _appPaths;
-
- public SqliteProviderInfoRepository(ILogManager logManager, IApplicationPaths appPaths) : base(logManager)
- {
- _appPaths = appPaths;
- }
-
- /// <summary>
- /// Gets the name of the repository
- /// </summary>
- /// <value>The name.</value>
- public string Name
- {
- get
- {
- return "SQLite";
- }
- }
-
- /// <summary>
- /// Opens the connection to the database
- /// </summary>
- /// <returns>Task.</returns>
- public async Task Initialize(IDbConnector dbConnector)
- {
- var dbFile = Path.Combine(_appPaths.DataPath, "refreshinfo.db");
-
- _connection = await dbConnector.Connect(dbFile).ConfigureAwait(false);
-
- string[] queries = {
-
- "create table if not exists MetadataStatus (ItemId GUID PRIMARY KEY, DateLastMetadataRefresh datetime, DateLastImagesRefresh datetime, ItemDateModified DateTimeNull)",
- "create index if not exists idx_MetadataStatus on MetadataStatus(ItemId)",
-
- //pragmas
- "pragma temp_store = memory",
-
- "pragma shrink_memory"
- };
-
- _connection.RunQueries(queries, Logger);
-
- AddItemDateModifiedCommand();
-
- PrepareStatements();
- }
-
- private static readonly string[] StatusColumns =
- {
- "ItemId",
- "DateLastMetadataRefresh",
- "DateLastImagesRefresh",
- "ItemDateModified"
- };
-
- private void AddItemDateModifiedCommand()
- {
- using (var cmd = _connection.CreateCommand())
- {
- cmd.CommandText = "PRAGMA table_info(MetadataStatus)";
-
- using (var reader = cmd.ExecuteReader(CommandBehavior.SequentialAccess | CommandBehavior.SingleResult))
- {
- while (reader.Read())
- {
- if (!reader.IsDBNull(1))
- {
- var name = reader.GetString(1);
-
- if (string.Equals(name, "ItemDateModified", StringComparison.OrdinalIgnoreCase))
- {
- return;
- }
- }
- }
- }
- }
-
- var builder = new StringBuilder();
-
- builder.AppendLine("alter table MetadataStatus");
- builder.AppendLine("add column ItemDateModified DateTime NULL");
-
- _connection.RunQueries(new[] { builder.ToString() }, Logger);
- }
-
- /// <summary>
- /// Prepares the statements.
- /// </summary>
- private void PrepareStatements()
- {
- _saveStatusCommand = _connection.CreateCommand();
-
- _saveStatusCommand.CommandText = string.Format("replace into MetadataStatus ({0}) values ({1})",
- string.Join(",", StatusColumns),
- string.Join(",", StatusColumns.Select(i => "@" + i).ToArray()));
-
- foreach (var col in StatusColumns)
- {
- _saveStatusCommand.Parameters.Add(_saveStatusCommand, "@" + col);
- }
- }
-
- public MetadataStatus GetMetadataStatus(Guid itemId)
- {
- if (itemId == Guid.Empty)
- {
- throw new ArgumentNullException("itemId");
- }
-
- using (var cmd = _connection.CreateCommand())
- {
- var cmdText = "select " + string.Join(",", StatusColumns) + " from MetadataStatus where";
-
- cmdText += " ItemId=@ItemId";
- cmd.Parameters.Add(cmd, "@ItemId", DbType.Guid).Value = itemId;
-
- cmd.CommandText = cmdText;
-
- using (var reader = cmd.ExecuteReader(CommandBehavior.SequentialAccess | CommandBehavior.SingleResult | CommandBehavior.SingleRow))
- {
- while (reader.Read())
- {
- return GetStatus(reader);
- }
-
- return null;
- }
- }
- }
-
- private MetadataStatus GetStatus(IDataReader reader)
- {
- var result = new MetadataStatus
- {
- ItemId = reader.GetGuid(0)
- };
-
- if (!reader.IsDBNull(1))
- {
- result.DateLastMetadataRefresh = reader.GetDateTime(1).ToUniversalTime();
- }
-
- if (!reader.IsDBNull(2))
- {
- result.DateLastImagesRefresh = reader.GetDateTime(2).ToUniversalTime();
- }
-
- if (!reader.IsDBNull(3))
- {
- result.ItemDateModified = reader.GetDateTime(3).ToUniversalTime();
- }
-
- return result;
- }
-
- public async Task SaveMetadataStatus(MetadataStatus status, CancellationToken cancellationToken)
- {
- if (status == null)
- {
- throw new ArgumentNullException("status");
- }
-
- cancellationToken.ThrowIfCancellationRequested();
-
- await WriteLock.WaitAsync(cancellationToken).ConfigureAwait(false);
-
- IDbTransaction transaction = null;
-
- try
- {
- transaction = _connection.BeginTransaction();
-
- _saveStatusCommand.GetParameter(0).Value = status.ItemId;
- _saveStatusCommand.GetParameter(1).Value = status.DateLastMetadataRefresh;
- _saveStatusCommand.GetParameter(2).Value = status.DateLastImagesRefresh;
- _saveStatusCommand.GetParameter(3).Value = status.ItemDateModified;
-
- _saveStatusCommand.Transaction = transaction;
-
- _saveStatusCommand.ExecuteNonQuery();
-
- transaction.Commit();
- }
- catch (OperationCanceledException)
- {
- if (transaction != null)
- {
- transaction.Rollback();
- }
-
- throw;
- }
- catch (Exception e)
- {
- Logger.ErrorException("Failed to save provider info:", e);
-
- if (transaction != null)
- {
- transaction.Rollback();
- }
-
- throw;
- }
- finally
- {
- if (transaction != null)
- {
- transaction.Dispose();
- }
-
- WriteLock.Release();
- }
- }
-
- protected override void CloseConnection()
- {
- if (_connection != null)
- {
- if (_connection.IsOpen())
- {
- _connection.Close();
- }
-
- _connection.Dispose();
- _connection = null;
- }
- }
- }
-}
diff --git a/MediaBrowser.Server.Implementations/Persistence/SqliteUserDataRepository.cs b/MediaBrowser.Server.Implementations/Persistence/SqliteUserDataRepository.cs
index 7f3b32e06..62d9e7634 100644
--- a/MediaBrowser.Server.Implementations/Persistence/SqliteUserDataRepository.cs
+++ b/MediaBrowser.Server.Implementations/Persistence/SqliteUserDataRepository.cs
@@ -5,7 +5,9 @@ using MediaBrowser.Model.Logging;
using System;
using System.Collections.Generic;
using System.Data;
+using System.Globalization;
using System.IO;
+using System.Text;
using System.Threading;
using System.Threading.Tasks;
@@ -14,11 +16,15 @@ namespace MediaBrowser.Server.Implementations.Persistence
public class SqliteUserDataRepository : BaseSqliteRepository, IUserDataRepository
{
private IDbConnection _connection;
- private readonly IApplicationPaths _appPaths;
- public SqliteUserDataRepository(ILogManager logManager, IApplicationPaths appPaths) : base(logManager)
+ public SqliteUserDataRepository(ILogManager logManager, IApplicationPaths appPaths, IDbConnector connector) : base(logManager, connector)
{
- _appPaths = appPaths;
+ DbFilePath = Path.Combine(appPaths.DataPath, "userdata_v2.db");
+ }
+
+ protected override bool EnableConnectionPooling
+ {
+ get { return false; }
}
/// <summary>
@@ -33,22 +39,42 @@ namespace MediaBrowser.Server.Implementations.Persistence
}
}
+ protected override async Task<IDbConnection> CreateConnection(bool isReadOnly = false)
+ {
+ var connection = await DbConnector.Connect(DbFilePath, false, false, 10000).ConfigureAwait(false);
+
+ connection.RunQueries(new[]
+ {
+ "pragma temp_store = memory"
+
+ }, Logger);
+
+ return connection;
+ }
+
/// <summary>
/// Opens the connection to the database
/// </summary>
/// <returns>Task.</returns>
- public async Task Initialize(IDbConnector dbConnector)
+ public async Task Initialize(IDbConnection connection, SemaphoreSlim writeLock)
{
- var dbFile = Path.Combine(_appPaths.DataPath, "userdata_v2.db");
-
- _connection = await dbConnector.Connect(dbFile).ConfigureAwait(false);
+ WriteLock.Dispose();
+ WriteLock = writeLock;
+ _connection = connection;
string[] queries = {
- "create table if not exists userdata (key nvarchar, userId GUID, rating float null, played bit, playCount int, isFavorite bit, playbackPositionTicks bigint, lastPlayedDate datetime null)",
+ "create table if not exists UserDataDb.userdata (key nvarchar, userId GUID, rating float null, played bit, playCount int, isFavorite bit, playbackPositionTicks bigint, lastPlayedDate datetime null)",
+
+ "drop index if exists UserDataDb.idx_userdata",
+ "drop index if exists UserDataDb.idx_userdata1",
+ "drop index if exists UserDataDb.idx_userdata2",
+ "drop index if exists UserDataDb.userdataindex1",
- "create index if not exists idx_userdata on userdata(key)",
- "create unique index if not exists userdataindex on userdata (key, userId)",
+ "create unique index if not exists UserDataDb.userdataindex on userdata (key, userId)",
+ "create index if not exists UserDataDb.userdataindex2 on userdata (key, userId, played)",
+ "create index if not exists UserDataDb.userdataindex3 on userdata (key, userId, playbackPositionTicks)",
+ "create index if not exists UserDataDb.userdataindex4 on userdata (key, userId, isFavorite)",
//pragmas
"pragma temp_store = memory",
@@ -300,6 +326,53 @@ namespace MediaBrowser.Server.Implementations.Persistence
}
}
+ public UserItemData GetUserData(Guid userId, List<string> keys)
+ {
+ if (userId == Guid.Empty)
+ {
+ throw new ArgumentNullException("userId");
+ }
+ if (keys == null)
+ {
+ throw new ArgumentNullException("keys");
+ }
+
+ using (var cmd = _connection.CreateCommand())
+ {
+ var index = 0;
+ var userdataKeys = new List<string>();
+ var builder = new StringBuilder();
+ foreach (var key in keys)
+ {
+ var paramName = "@Key" + index;
+ userdataKeys.Add("Key =" + paramName);
+ cmd.Parameters.Add(cmd, paramName, DbType.String).Value = key;
+ builder.Append(" WHEN Key=" + paramName + " THEN " + index);
+ index++;
+ break;
+ }
+
+ var keyText = string.Join(" OR ", userdataKeys.ToArray());
+
+ cmd.CommandText = "select key,userid,rating,played,playCount,isFavorite,playbackPositionTicks,lastPlayedDate,AudioStreamIndex,SubtitleStreamIndex from userdata where userId=@userId AND (" + keyText + ") ";
+
+ cmd.CommandText += " ORDER BY (Case " + builder + " Else " + keys.Count.ToString(CultureInfo.InvariantCulture) + " End )";
+ cmd.CommandText += " LIMIT 1";
+
+ cmd.Parameters.Add(cmd, "@userId", DbType.Guid).Value = userId;
+
+ using (var reader = cmd.ExecuteReader(CommandBehavior.SequentialAccess | CommandBehavior.SingleResult | CommandBehavior.SingleRow))
+ {
+ if (reader.Read())
+ {
+ return ReadRow(reader);
+ }
+ }
+
+ return null;
+ }
+ }
+
/// <summary>
/// Return all user-data associated with the given user
/// </summary>
@@ -367,18 +440,14 @@ namespace MediaBrowser.Server.Implementations.Persistence
return userData;
}
- protected override void CloseConnection()
+ protected override void Dispose(bool dispose)
{
- if (_connection != null)
- {
- if (_connection.IsOpen())
- {
- _connection.Close();
- }
+ // handled by library database
+ }
- _connection.Dispose();
- _connection = null;
- }
+ protected override void CloseConnection()
+ {
+ // handled by library database
}
}
} \ No newline at end of file
diff --git a/MediaBrowser.Server.Implementations/Persistence/SqliteUserRepository.cs b/MediaBrowser.Server.Implementations/Persistence/SqliteUserRepository.cs
index f7ca39a54..25ab60ca5 100644
--- a/MediaBrowser.Server.Implementations/Persistence/SqliteUserRepository.cs
+++ b/MediaBrowser.Server.Implementations/Persistence/SqliteUserRepository.cs
@@ -17,14 +17,13 @@ namespace MediaBrowser.Server.Implementations.Persistence
/// </summary>
public class SqliteUserRepository : BaseSqliteRepository, IUserRepository
{
- private IDbConnection _connection;
- private readonly IServerApplicationPaths _appPaths;
private readonly IJsonSerializer _jsonSerializer;
- public SqliteUserRepository(ILogManager logManager, IServerApplicationPaths appPaths, IJsonSerializer jsonSerializer) : base(logManager)
+ public SqliteUserRepository(ILogManager logManager, IServerApplicationPaths appPaths, IJsonSerializer jsonSerializer, IDbConnector dbConnector) : base(logManager, dbConnector)
{
- _appPaths = appPaths;
_jsonSerializer = jsonSerializer;
+
+ DbFilePath = Path.Combine(appPaths.DataPath, "users.db");
}
/// <summary>
@@ -43,25 +42,21 @@ namespace MediaBrowser.Server.Implementations.Persistence
/// Opens the connection to the database
/// </summary>
/// <returns>Task.</returns>
- public async Task Initialize(IDbConnector dbConnector)
+ public async Task Initialize()
{
- var dbFile = Path.Combine(_appPaths.DataPath, "users.db");
-
- _connection = await dbConnector.Connect(dbFile).ConfigureAwait(false);
-
- string[] queries = {
+ using (var connection = await CreateConnection().ConfigureAwait(false))
+ {
+ string[] queries = {
"create table if not exists users (guid GUID primary key, data BLOB)",
"create index if not exists idx_users on users(guid)",
"create table if not exists schema_version (table_name primary key, version)",
- //pragmas
- "pragma temp_store = memory",
-
"pragma shrink_memory"
};
- _connection.RunQueries(queries, Logger);
+ connection.RunQueries(queries, Logger);
+ }
}
/// <summary>
@@ -84,55 +79,54 @@ namespace MediaBrowser.Server.Implementations.Persistence
cancellationToken.ThrowIfCancellationRequested();
- await WriteLock.WaitAsync(cancellationToken).ConfigureAwait(false);
-
- IDbTransaction transaction = null;
-
- try
+ using (var connection = await CreateConnection().ConfigureAwait(false))
{
- transaction = _connection.BeginTransaction();
+ IDbTransaction transaction = null;
- using (var cmd = _connection.CreateCommand())
+ try
{
- cmd.CommandText = "replace into users (guid, data) values (@1, @2)";
- cmd.Parameters.Add(cmd, "@1", DbType.Guid).Value = user.Id;
- cmd.Parameters.Add(cmd, "@2", DbType.Binary).Value = serialized;
+ transaction = connection.BeginTransaction();
+
+ using (var cmd = connection.CreateCommand())
+ {
+ cmd.CommandText = "replace into users (guid, data) values (@1, @2)";
+ cmd.Parameters.Add(cmd, "@1", DbType.Guid).Value = user.Id;
+ cmd.Parameters.Add(cmd, "@2", DbType.Binary).Value = serialized;
- cmd.Transaction = transaction;
+ cmd.Transaction = transaction;
- cmd.ExecuteNonQuery();
- }
+ cmd.ExecuteNonQuery();
+ }
- transaction.Commit();
- }
- catch (OperationCanceledException)
- {
- if (transaction != null)
+ transaction.Commit();
+ }
+ catch (OperationCanceledException)
{
- transaction.Rollback();
+ if (transaction != null)
+ {
+ transaction.Rollback();
+ }
+
+ throw;
}
+ catch (Exception e)
+ {
+ Logger.ErrorException("Failed to save user:", e);
- throw;
- }
- catch (Exception e)
- {
- Logger.ErrorException("Failed to save user:", e);
+ if (transaction != null)
+ {
+ transaction.Rollback();
+ }
- if (transaction != null)
- {
- transaction.Rollback();
+ throw;
}
-
- throw;
- }
- finally
- {
- if (transaction != null)
+ finally
{
- transaction.Dispose();
+ if (transaction != null)
+ {
+ transaction.Dispose();
+ }
}
-
- WriteLock.Release();
}
}
@@ -142,25 +136,32 @@ namespace MediaBrowser.Server.Implementations.Persistence
/// <returns>IEnumerable{User}.</returns>
public IEnumerable<User> RetrieveAllUsers()
{
- using (var cmd = _connection.CreateCommand())
- {
- cmd.CommandText = "select guid,data from users";
+ var list = new List<User>();
- using (var reader = cmd.ExecuteReader(CommandBehavior.SequentialAccess | CommandBehavior.SingleResult))
+ using (var connection = CreateConnection(true).Result)
+ {
+ using (var cmd = connection.CreateCommand())
{
- while (reader.Read())
- {
- var id = reader.GetGuid(0);
+ cmd.CommandText = "select guid,data from users";
- using (var stream = reader.GetMemoryStream(1))
+ using (var reader = cmd.ExecuteReader(CommandBehavior.SequentialAccess | CommandBehavior.SingleResult))
+ {
+ while (reader.Read())
{
- var user = _jsonSerializer.DeserializeFromStream<User>(stream);
- user.Id = id;
- yield return user;
+ var id = reader.GetGuid(0);
+
+ using (var stream = reader.GetMemoryStream(1))
+ {
+ var user = _jsonSerializer.DeserializeFromStream<User>(stream);
+ user.Id = id;
+ list.Add(user);
+ }
}
}
}
}
+
+ return list;
}
/// <summary>
@@ -179,69 +180,54 @@ namespace MediaBrowser.Server.Implementations.Persistence
cancellationToken.ThrowIfCancellationRequested();
- await WriteLock.WaitAsync(cancellationToken).ConfigureAwait(false);
-
- IDbTransaction transaction = null;
-
- try
+ using (var connection = await CreateConnection().ConfigureAwait(false))
{
- transaction = _connection.BeginTransaction();
+ IDbTransaction transaction = null;
- using (var cmd = _connection.CreateCommand())
+ try
{
- cmd.CommandText = "delete from users where guid=@guid";
+ transaction = connection.BeginTransaction();
- cmd.Parameters.Add(cmd, "@guid", DbType.Guid).Value = user.Id;
-
- cmd.Transaction = transaction;
+ using (var cmd = connection.CreateCommand())
+ {
+ cmd.CommandText = "delete from users where guid=@guid";
- cmd.ExecuteNonQuery();
- }
+ cmd.Parameters.Add(cmd, "@guid", DbType.Guid).Value = user.Id;
- transaction.Commit();
- }
- catch (OperationCanceledException)
- {
- if (transaction != null)
- {
- transaction.Rollback();
- }
+ cmd.Transaction = transaction;
- throw;
- }
- catch (Exception e)
- {
- Logger.ErrorException("Failed to delete user:", e);
+ cmd.ExecuteNonQuery();
+ }
- if (transaction != null)
- {
- transaction.Rollback();
+ transaction.Commit();
}
-
- throw;
- }
- finally
- {
- if (transaction != null)
+ catch (OperationCanceledException)
{
- transaction.Dispose();
+ if (transaction != null)
+ {
+ transaction.Rollback();
+ }
+
+ throw;
}
+ catch (Exception e)
+ {
+ Logger.ErrorException("Failed to delete user:", e);
- WriteLock.Release();
- }
- }
+ if (transaction != null)
+ {
+ transaction.Rollback();
+ }
- protected override void CloseConnection()
- {
- if (_connection != null)
- {
- if (_connection.IsOpen())
+ throw;
+ }
+ finally
{
- _connection.Close();
+ if (transaction != null)
+ {
+ transaction.Dispose();
+ }
}
-
- _connection.Dispose();
- _connection = null;
}
}
}
diff --git a/MediaBrowser.Server.Implementations/Security/AuthenticationRepository.cs b/MediaBrowser.Server.Implementations/Security/AuthenticationRepository.cs
index e8d9814ec..74a552dcc 100644
--- a/MediaBrowser.Server.Implementations/Security/AuthenticationRepository.cs
+++ b/MediaBrowser.Server.Implementations/Security/AuthenticationRepository.cs
@@ -15,57 +15,30 @@ namespace MediaBrowser.Server.Implementations.Security
{
public class AuthenticationRepository : BaseSqliteRepository, IAuthenticationRepository
{
- private IDbConnection _connection;
private readonly IServerApplicationPaths _appPaths;
private readonly CultureInfo _usCulture = new CultureInfo("en-US");
- private IDbCommand _saveInfoCommand;
-
- public AuthenticationRepository(ILogManager logManager, IServerApplicationPaths appPaths)
- : base(logManager)
+ public AuthenticationRepository(ILogManager logManager, IServerApplicationPaths appPaths, IDbConnector connector)
+ : base(logManager, connector)
{
_appPaths = appPaths;
+ DbFilePath = Path.Combine(appPaths.DataPath, "authentication.db");
}
- public async Task Initialize(IDbConnector dbConnector)
+ public async Task Initialize()
{
- var dbFile = Path.Combine(_appPaths.DataPath, "authentication.db");
-
- _connection = await dbConnector.Connect(dbFile).ConfigureAwait(false);
-
- string[] queries = {
+ using (var connection = await CreateConnection().ConfigureAwait(false))
+ {
+ string[] queries = {
"create table if not exists AccessTokens (Id GUID PRIMARY KEY, AccessToken TEXT NOT NULL, DeviceId TEXT, AppName TEXT, AppVersion TEXT, DeviceName TEXT, UserId TEXT, IsActive BIT, DateCreated DATETIME NOT NULL, DateRevoked DATETIME)",
- "create index if not exists idx_AccessTokens on AccessTokens(Id)",
-
- //pragmas
- "pragma temp_store = memory",
-
- "pragma shrink_memory"
+ "create index if not exists idx_AccessTokens on AccessTokens(Id)"
};
- _connection.RunQueries(queries, Logger);
+ connection.RunQueries(queries, Logger);
- _connection.AddColumn(Logger, "AccessTokens", "AppVersion", "TEXT");
-
- PrepareStatements();
- }
-
- private void PrepareStatements()
- {
- _saveInfoCommand = _connection.CreateCommand();
- _saveInfoCommand.CommandText = "replace into AccessTokens (Id, AccessToken, DeviceId, AppName, AppVersion, DeviceName, UserId, IsActive, DateCreated, DateRevoked) values (@Id, @AccessToken, @DeviceId, @AppName, @AppVersion, @DeviceName, @UserId, @IsActive, @DateCreated, @DateRevoked)";
-
- _saveInfoCommand.Parameters.Add(_saveInfoCommand, "@Id");
- _saveInfoCommand.Parameters.Add(_saveInfoCommand, "@AccessToken");
- _saveInfoCommand.Parameters.Add(_saveInfoCommand, "@DeviceId");
- _saveInfoCommand.Parameters.Add(_saveInfoCommand, "@AppName");
- _saveInfoCommand.Parameters.Add(_saveInfoCommand, "@AppVersion");
- _saveInfoCommand.Parameters.Add(_saveInfoCommand, "@DeviceName");
- _saveInfoCommand.Parameters.Add(_saveInfoCommand, "@UserId");
- _saveInfoCommand.Parameters.Add(_saveInfoCommand, "@IsActive");
- _saveInfoCommand.Parameters.Add(_saveInfoCommand, "@DateCreated");
- _saveInfoCommand.Parameters.Add(_saveInfoCommand, "@DateRevoked");
+ connection.AddColumn(Logger, "AccessTokens", "AppVersion", "TEXT");
+ }
}
public Task Create(AuthenticationInfo info, CancellationToken cancellationToken)
@@ -84,61 +57,76 @@ namespace MediaBrowser.Server.Implementations.Security
cancellationToken.ThrowIfCancellationRequested();
- await WriteLock.WaitAsync(cancellationToken).ConfigureAwait(false);
-
- IDbTransaction transaction = null;
-
- try
+ using (var connection = await CreateConnection().ConfigureAwait(false))
{
- transaction = _connection.BeginTransaction();
+ using (var saveInfoCommand = connection.CreateCommand())
+ {
+ saveInfoCommand.CommandText = "replace into AccessTokens (Id, AccessToken, DeviceId, AppName, AppVersion, DeviceName, UserId, IsActive, DateCreated, DateRevoked) values (@Id, @AccessToken, @DeviceId, @AppName, @AppVersion, @DeviceName, @UserId, @IsActive, @DateCreated, @DateRevoked)";
+
+ saveInfoCommand.Parameters.Add(saveInfoCommand, "@Id");
+ saveInfoCommand.Parameters.Add(saveInfoCommand, "@AccessToken");
+ saveInfoCommand.Parameters.Add(saveInfoCommand, "@DeviceId");
+ saveInfoCommand.Parameters.Add(saveInfoCommand, "@AppName");
+ saveInfoCommand.Parameters.Add(saveInfoCommand, "@AppVersion");
+ saveInfoCommand.Parameters.Add(saveInfoCommand, "@DeviceName");
+ saveInfoCommand.Parameters.Add(saveInfoCommand, "@UserId");
+ saveInfoCommand.Parameters.Add(saveInfoCommand, "@IsActive");
+ saveInfoCommand.Parameters.Add(saveInfoCommand, "@DateCreated");
+ saveInfoCommand.Parameters.Add(saveInfoCommand, "@DateRevoked");
+
+ IDbTransaction transaction = null;
+
+ try
+ {
+ transaction = connection.BeginTransaction();
- var index = 0;
+ var index = 0;
- _saveInfoCommand.GetParameter(index++).Value = new Guid(info.Id);
- _saveInfoCommand.GetParameter(index++).Value = info.AccessToken;
- _saveInfoCommand.GetParameter(index++).Value = info.DeviceId;
- _saveInfoCommand.GetParameter(index++).Value = info.AppName;
- _saveInfoCommand.GetParameter(index++).Value = info.AppVersion;
- _saveInfoCommand.GetParameter(index++).Value = info.DeviceName;
- _saveInfoCommand.GetParameter(index++).Value = info.UserId;
- _saveInfoCommand.GetParameter(index++).Value = info.IsActive;
- _saveInfoCommand.GetParameter(index++).Value = info.DateCreated;
- _saveInfoCommand.GetParameter(index++).Value = info.DateRevoked;
+ saveInfoCommand.GetParameter(index++).Value = new Guid(info.Id);
+ saveInfoCommand.GetParameter(index++).Value = info.AccessToken;
+ saveInfoCommand.GetParameter(index++).Value = info.DeviceId;
+ saveInfoCommand.GetParameter(index++).Value = info.AppName;
+ saveInfoCommand.GetParameter(index++).Value = info.AppVersion;
+ saveInfoCommand.GetParameter(index++).Value = info.DeviceName;
+ saveInfoCommand.GetParameter(index++).Value = info.UserId;
+ saveInfoCommand.GetParameter(index++).Value = info.IsActive;
+ saveInfoCommand.GetParameter(index++).Value = info.DateCreated;
+ saveInfoCommand.GetParameter(index++).Value = info.DateRevoked;
- _saveInfoCommand.Transaction = transaction;
+ saveInfoCommand.Transaction = transaction;
- _saveInfoCommand.ExecuteNonQuery();
+ saveInfoCommand.ExecuteNonQuery();
- transaction.Commit();
- }
- catch (OperationCanceledException)
- {
- if (transaction != null)
- {
- transaction.Rollback();
- }
+ transaction.Commit();
+ }
+ catch (OperationCanceledException)
+ {
+ if (transaction != null)
+ {
+ transaction.Rollback();
+ }
- throw;
- }
- catch (Exception e)
- {
- Logger.ErrorException("Failed to save record:", e);
+ throw;
+ }
+ catch (Exception e)
+ {
+ Logger.ErrorException("Failed to save record:", e);
- if (transaction != null)
- {
- transaction.Rollback();
- }
+ if (transaction != null)
+ {
+ transaction.Rollback();
+ }
- throw;
- }
- finally
- {
- if (transaction != null)
- {
- transaction.Dispose();
+ throw;
+ }
+ finally
+ {
+ if (transaction != null)
+ {
+ transaction.Dispose();
+ }
+ }
}
-
- WriteLock.Release();
}
}
@@ -151,101 +139,104 @@ namespace MediaBrowser.Server.Implementations.Security
throw new ArgumentNullException("query");
}
- using (var cmd = _connection.CreateCommand())
+ using (var connection = CreateConnection(true).Result)
{
- cmd.CommandText = BaseSelectText;
-
- var whereClauses = new List<string>();
-
- var startIndex = query.StartIndex ?? 0;
-
- if (!string.IsNullOrWhiteSpace(query.AccessToken))
+ using (var cmd = connection.CreateCommand())
{
- whereClauses.Add("AccessToken=@AccessToken");
- cmd.Parameters.Add(cmd, "@AccessToken", DbType.String).Value = query.AccessToken;
- }
+ cmd.CommandText = BaseSelectText;
- if (!string.IsNullOrWhiteSpace(query.UserId))
- {
- whereClauses.Add("UserId=@UserId");
- cmd.Parameters.Add(cmd, "@UserId", DbType.String).Value = query.UserId;
- }
+ var whereClauses = new List<string>();
- if (!string.IsNullOrWhiteSpace(query.DeviceId))
- {
- whereClauses.Add("DeviceId=@DeviceId");
- cmd.Parameters.Add(cmd, "@DeviceId", DbType.String).Value = query.DeviceId;
- }
+ var startIndex = query.StartIndex ?? 0;
- if (query.IsActive.HasValue)
- {
- whereClauses.Add("IsActive=@IsActive");
- cmd.Parameters.Add(cmd, "@IsActive", DbType.Boolean).Value = query.IsActive.Value;
- }
+ if (!string.IsNullOrWhiteSpace(query.AccessToken))
+ {
+ whereClauses.Add("AccessToken=@AccessToken");
+ cmd.Parameters.Add(cmd, "@AccessToken", DbType.String).Value = query.AccessToken;
+ }
- if (query.HasUser.HasValue)
- {
- if (query.HasUser.Value)
+ if (!string.IsNullOrWhiteSpace(query.UserId))
{
- whereClauses.Add("UserId not null");
+ whereClauses.Add("UserId=@UserId");
+ cmd.Parameters.Add(cmd, "@UserId", DbType.String).Value = query.UserId;
}
- else
+
+ if (!string.IsNullOrWhiteSpace(query.DeviceId))
{
- whereClauses.Add("UserId is null");
+ whereClauses.Add("DeviceId=@DeviceId");
+ cmd.Parameters.Add(cmd, "@DeviceId", DbType.String).Value = query.DeviceId;
}
- }
- var whereTextWithoutPaging = whereClauses.Count == 0 ?
- string.Empty :
- " where " + string.Join(" AND ", whereClauses.ToArray());
+ if (query.IsActive.HasValue)
+ {
+ whereClauses.Add("IsActive=@IsActive");
+ cmd.Parameters.Add(cmd, "@IsActive", DbType.Boolean).Value = query.IsActive.Value;
+ }
- if (startIndex > 0)
- {
- var pagingWhereText = whereClauses.Count == 0 ?
+ if (query.HasUser.HasValue)
+ {
+ if (query.HasUser.Value)
+ {
+ whereClauses.Add("UserId not null");
+ }
+ else
+ {
+ whereClauses.Add("UserId is null");
+ }
+ }
+
+ var whereTextWithoutPaging = whereClauses.Count == 0 ?
string.Empty :
" where " + string.Join(" AND ", whereClauses.ToArray());
- whereClauses.Add(string.Format("Id NOT IN (SELECT Id FROM AccessTokens {0} ORDER BY DateCreated LIMIT {1})",
- pagingWhereText,
- startIndex.ToString(_usCulture)));
- }
+ if (startIndex > 0)
+ {
+ var pagingWhereText = whereClauses.Count == 0 ?
+ string.Empty :
+ " where " + string.Join(" AND ", whereClauses.ToArray());
- var whereText = whereClauses.Count == 0 ?
- string.Empty :
- " where " + string.Join(" AND ", whereClauses.ToArray());
+ whereClauses.Add(string.Format("Id NOT IN (SELECT Id FROM AccessTokens {0} ORDER BY DateCreated LIMIT {1})",
+ pagingWhereText,
+ startIndex.ToString(_usCulture)));
+ }
- cmd.CommandText += whereText;
+ var whereText = whereClauses.Count == 0 ?
+ string.Empty :
+ " where " + string.Join(" AND ", whereClauses.ToArray());
- cmd.CommandText += " ORDER BY DateCreated";
+ cmd.CommandText += whereText;
- if (query.Limit.HasValue)
- {
- cmd.CommandText += " LIMIT " + query.Limit.Value.ToString(_usCulture);
- }
+ cmd.CommandText += " ORDER BY DateCreated";
- cmd.CommandText += "; select count (Id) from AccessTokens" + whereTextWithoutPaging;
+ if (query.Limit.HasValue)
+ {
+ cmd.CommandText += " LIMIT " + query.Limit.Value.ToString(_usCulture);
+ }
- var list = new List<AuthenticationInfo>();
- var count = 0;
+ cmd.CommandText += "; select count (Id) from AccessTokens" + whereTextWithoutPaging;
- using (var reader = cmd.ExecuteReader(CommandBehavior.SequentialAccess))
- {
- while (reader.Read())
+ var list = new List<AuthenticationInfo>();
+ var count = 0;
+
+ using (var reader = cmd.ExecuteReader(CommandBehavior.SequentialAccess))
{
- list.Add(Get(reader));
+ while (reader.Read())
+ {
+ list.Add(Get(reader));
+ }
+
+ if (reader.NextResult() && reader.Read())
+ {
+ count = reader.GetInt32(0);
+ }
}
- if (reader.NextResult() && reader.Read())
+ return new QueryResult<AuthenticationInfo>()
{
- count = reader.GetInt32(0);
- }
+ Items = list.ToArray(),
+ TotalRecordCount = count
+ };
}
-
- return new QueryResult<AuthenticationInfo>()
- {
- Items = list.ToArray(),
- TotalRecordCount = count
- };
}
}
@@ -256,24 +247,27 @@ namespace MediaBrowser.Server.Implementations.Security
throw new ArgumentNullException("id");
}
- var guid = new Guid(id);
-
- using (var cmd = _connection.CreateCommand())
+ using (var connection = CreateConnection(true).Result)
{
- cmd.CommandText = BaseSelectText + " where Id=@Id";
-
- cmd.Parameters.Add(cmd, "@Id", DbType.Guid).Value = guid;
+ var guid = new Guid(id);
- using (var reader = cmd.ExecuteReader(CommandBehavior.SequentialAccess | CommandBehavior.SingleResult | CommandBehavior.SingleRow))
+ using (var cmd = connection.CreateCommand())
{
- if (reader.Read())
+ cmd.CommandText = BaseSelectText + " where Id=@Id";
+
+ cmd.Parameters.Add(cmd, "@Id", DbType.Guid).Value = guid;
+
+ using (var reader = cmd.ExecuteReader(CommandBehavior.SequentialAccess | CommandBehavior.SingleResult | CommandBehavior.SingleRow))
{
- return Get(reader);
+ if (reader.Read())
+ {
+ return Get(reader);
+ }
}
}
- }
- return null;
+ return null;
+ }
}
private AuthenticationInfo Get(IDataReader reader)
@@ -319,19 +313,5 @@ namespace MediaBrowser.Server.Implementations.Security
return info;
}
-
- protected override void CloseConnection()
- {
- if (_connection != null)
- {
- if (_connection.IsOpen())
- {
- _connection.Close();
- }
-
- _connection.Dispose();
- _connection = null;
- }
- }
}
}
diff --git a/MediaBrowser.Server.Implementations/Session/SessionManager.cs b/MediaBrowser.Server.Implementations/Session/SessionManager.cs
index 77843ef6b..84aab5e1f 100644
--- a/MediaBrowser.Server.Implementations/Session/SessionManager.cs
+++ b/MediaBrowser.Server.Implementations/Session/SessionManager.cs
@@ -404,6 +404,10 @@ namespace MediaBrowser.Server.Implementations.Session
/// <returns>SessionInfo.</returns>
private async Task<SessionInfo> GetSessionInfo(string appName, string appVersion, string deviceId, string deviceName, string remoteEndPoint, User user)
{
+ if (string.IsNullOrWhiteSpace(deviceId))
+ {
+ throw new ArgumentNullException("deviceId");
+ }
var key = GetSessionKey(appName, deviceId);
await _sessionLock.WaitAsync(CancellationToken.None).ConfigureAwait(false);
@@ -928,7 +932,7 @@ namespace MediaBrowser.Server.Implementations.Session
return session.SessionController.SendGeneralCommand(command, cancellationToken);
}
- public Task SendPlayCommand(string controllingSessionId, string sessionId, PlayRequest command, CancellationToken cancellationToken)
+ public async Task SendPlayCommand(string controllingSessionId, string sessionId, PlayRequest command, CancellationToken cancellationToken)
{
var session = GetSessionToRemoteControl(sessionId);
@@ -946,7 +950,14 @@ namespace MediaBrowser.Server.Implementations.Session
}
else
{
- items = command.ItemIds.SelectMany(i => TranslateItemForPlayback(i, user))
+ var list = new List<BaseItem>();
+ foreach (var itemId in command.ItemIds)
+ {
+ var subItems = await TranslateItemForPlayback(itemId, user).ConfigureAwait(false);
+ list.AddRange(subItems);
+ }
+
+ items = list
.Where(i => i.LocationType != LocationType.Virtual)
.ToList();
}
@@ -1009,10 +1020,10 @@ namespace MediaBrowser.Server.Implementations.Session
command.ControllingUserId = controllingSession.UserId.Value.ToString("N");
}
- return session.SessionController.SendPlayCommand(command, cancellationToken);
+ await session.SessionController.SendPlayCommand(command, cancellationToken).ConfigureAwait(false);
}
- private IEnumerable<BaseItem> TranslateItemForPlayback(string id, User user)
+ private async Task<List<BaseItem>> TranslateItemForPlayback(string id, User user)
{
var item = _libraryManager.GetItemById(id);
@@ -1033,25 +1044,27 @@ namespace MediaBrowser.Server.Implementations.Session
});
return FilterToSingleMediaType(items)
- .OrderBy(i => i.SortName);
+ .OrderBy(i => i.SortName)
+ .ToList();
}
if (item.IsFolder)
{
var folder = (Folder)item;
- var items = folder.GetItems(new InternalItemsQuery(user)
+ var itemsResult = await folder.GetItems(new InternalItemsQuery(user)
{
Recursive = true,
IsFolder = false
- }).Result.Items;
+ }).ConfigureAwait(false);
- return FilterToSingleMediaType(items)
- .OrderBy(i => i.SortName);
+ return FilterToSingleMediaType(itemsResult.Items)
+ .OrderBy(i => i.SortName)
+ .ToList();
}
- return new[] { item };
+ return new List<BaseItem> { item };
}
private IEnumerable<BaseItem> FilterToSingleMediaType(IEnumerable<BaseItem> items)
@@ -1121,11 +1134,11 @@ namespace MediaBrowser.Server.Implementations.Session
/// </summary>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>Task.</returns>
- public Task SendRestartRequiredNotification(CancellationToken cancellationToken)
+ public async Task SendRestartRequiredNotification(CancellationToken cancellationToken)
{
var sessions = Sessions.Where(i => i.IsActive && i.SessionController != null).ToList();
- var info = _appHost.GetSystemInfo();
+ var info = await _appHost.GetSystemInfo().ConfigureAwait(false);
var tasks = sessions.Select(session => Task.Run(async () =>
{
@@ -1140,7 +1153,7 @@ namespace MediaBrowser.Server.Implementations.Session
}, cancellationToken));
- return Task.WhenAll(tasks);
+ await Task.WhenAll(tasks).ConfigureAwait(false);
}
/// <summary>
@@ -1447,7 +1460,7 @@ namespace MediaBrowser.Server.Implementations.Session
}
}
- public async Task RevokeUserTokens(string userId)
+ public async Task RevokeUserTokens(string userId, string currentAccessToken)
{
var existing = _authRepo.Get(new AuthenticationInfoQuery
{
@@ -1457,7 +1470,10 @@ namespace MediaBrowser.Server.Implementations.Session
foreach (var info in existing.Items)
{
- await Logout(info.AccessToken).ConfigureAwait(false);
+ if (!string.Equals(currentAccessToken, info.AccessToken, StringComparison.OrdinalIgnoreCase))
+ {
+ await Logout(info.AccessToken).ConfigureAwait(false);
+ }
}
}
@@ -1748,6 +1764,11 @@ namespace MediaBrowser.Server.Implementations.Session
public void ReportNowViewingItem(string sessionId, string itemId)
{
+ if (string.IsNullOrWhiteSpace(itemId))
+ {
+ throw new ArgumentNullException("itemId");
+ }
+
var item = _libraryManager.GetItemById(new Guid(itemId));
var info = GetItemInfo(item, null, null);
diff --git a/MediaBrowser.Server.Implementations/Session/SessionWebSocketListener.cs b/MediaBrowser.Server.Implementations/Session/SessionWebSocketListener.cs
index 602bc4876..ddd7ba53a 100644
--- a/MediaBrowser.Server.Implementations/Session/SessionWebSocketListener.cs
+++ b/MediaBrowser.Server.Implementations/Session/SessionWebSocketListener.cs
@@ -230,7 +230,12 @@ namespace MediaBrowser.Server.Implementations.Session
{
var vals = message.Data.Split('|');
- _sessionManager.ReportNowViewingItem(session.Id, vals[1]);
+ var itemId = vals[1];
+
+ if (!string.IsNullOrWhiteSpace(itemId))
+ {
+ _sessionManager.ReportNowViewingItem(session.Id, itemId);
+ }
}
}
diff --git a/MediaBrowser.Server.Implementations/Social/SharingManager.cs b/MediaBrowser.Server.Implementations/Social/SharingManager.cs
index 2ffd33ca4..95f0ece0c 100644
--- a/MediaBrowser.Server.Implementations/Social/SharingManager.cs
+++ b/MediaBrowser.Server.Implementations/Social/SharingManager.cs
@@ -43,7 +43,7 @@ namespace MediaBrowser.Server.Implementations.Social
throw new ResourceNotFoundException();
}
- var externalUrl = _appHost.GetSystemInfo().WanAddress;
+ var externalUrl = (await _appHost.GetSystemInfo().ConfigureAwait(false)).WanAddress;
if (string.IsNullOrWhiteSpace(externalUrl))
{
@@ -58,7 +58,7 @@ namespace MediaBrowser.Server.Implementations.Social
UserId = userId
};
- AddShareInfo(info);
+ AddShareInfo(info, externalUrl);
await _repository.CreateShare(info).ConfigureAwait(false);
@@ -74,15 +74,13 @@ namespace MediaBrowser.Server.Implementations.Social
{
var info = _repository.GetShareInfo(id);
- AddShareInfo(info);
+ AddShareInfo(info, _appHost.GetSystemInfo().Result.WanAddress);
return info;
}
- private void AddShareInfo(SocialShareInfo info)
+ private void AddShareInfo(SocialShareInfo info, string externalUrl)
{
- var externalUrl = _appHost.GetSystemInfo().WanAddress;
-
info.ImageUrl = externalUrl + "/Social/Shares/Public/" + info.Id + "/Image";
info.Url = externalUrl + "/emby/web/shared.html?id=" + info.Id;
diff --git a/MediaBrowser.Server.Implementations/Social/SharingRepository.cs b/MediaBrowser.Server.Implementations/Social/SharingRepository.cs
index 317743eb1..c4243c1a7 100644
--- a/MediaBrowser.Server.Implementations/Social/SharingRepository.cs
+++ b/MediaBrowser.Server.Implementations/Social/SharingRepository.cs
@@ -12,54 +12,30 @@ namespace MediaBrowser.Server.Implementations.Social
{
public class SharingRepository : BaseSqliteRepository
{
- private IDbConnection _connection;
- private IDbCommand _saveShareCommand;
- private readonly IApplicationPaths _appPaths;
-
- public SharingRepository(ILogManager logManager, IApplicationPaths appPaths)
- : base(logManager)
+ public SharingRepository(ILogManager logManager, IApplicationPaths appPaths, IDbConnector dbConnector)
+ : base(logManager, dbConnector)
{
- _appPaths = appPaths;
+ DbFilePath = Path.Combine(appPaths.DataPath, "shares.db");
}
/// <summary>
/// Opens the connection to the database
/// </summary>
/// <returns>Task.</returns>
- public async Task Initialize(IDbConnector dbConnector)
+ public async Task Initialize()
{
- var dbFile = Path.Combine(_appPaths.DataPath, "shares.db");
-
- _connection = await dbConnector.Connect(dbFile).ConfigureAwait(false);
-
- string[] queries = {
+ using (var connection = await CreateConnection().ConfigureAwait(false))
+ {
+ string[] queries = {
"create table if not exists Shares (Id GUID, ItemId TEXT, UserId TEXT, ExpirationDate DateTime, PRIMARY KEY (Id))",
"create index if not exists idx_Shares on Shares(Id)",
- //pragmas
- "pragma temp_store = memory",
-
"pragma shrink_memory"
};
- _connection.RunQueries(queries, Logger);
-
- PrepareStatements();
- }
-
- /// <summary>
- /// Prepares the statements.
- /// </summary>
- private void PrepareStatements()
- {
- _saveShareCommand = _connection.CreateCommand();
- _saveShareCommand.CommandText = "replace into Shares (Id, ItemId, UserId, ExpirationDate) values (@Id, @ItemId, @UserId, @ExpirationDate)";
-
- _saveShareCommand.Parameters.Add(_saveShareCommand, "@Id");
- _saveShareCommand.Parameters.Add(_saveShareCommand, "@ItemId");
- _saveShareCommand.Parameters.Add(_saveShareCommand, "@UserId");
- _saveShareCommand.Parameters.Add(_saveShareCommand, "@ExpirationDate");
+ connection.RunQueries(queries, Logger);
+ }
}
public async Task CreateShare(SocialShareInfo info)
@@ -77,53 +53,62 @@ namespace MediaBrowser.Server.Implementations.Social
cancellationToken.ThrowIfCancellationRequested();
- await WriteLock.WaitAsync(cancellationToken).ConfigureAwait(false);
-
- IDbTransaction transaction = null;
-
- try
- {
- transaction = _connection.BeginTransaction();
-
- _saveShareCommand.GetParameter(0).Value = new Guid(info.Id);
- _saveShareCommand.GetParameter(1).Value = info.ItemId;
- _saveShareCommand.GetParameter(2).Value = info.UserId;
- _saveShareCommand.GetParameter(3).Value = info.ExpirationDate;
-
- _saveShareCommand.Transaction = transaction;
-
- _saveShareCommand.ExecuteNonQuery();
-
- transaction.Commit();
- }
- catch (OperationCanceledException)
- {
- if (transaction != null)
- {
- transaction.Rollback();
- }
-
- throw;
- }
- catch (Exception e)
+ using (var connection = await CreateConnection().ConfigureAwait(false))
{
- Logger.ErrorException("Failed to save share:", e);
-
- if (transaction != null)
+ using (var saveShareCommand = connection.CreateCommand())
{
- transaction.Rollback();
+ saveShareCommand.CommandText = "replace into Shares (Id, ItemId, UserId, ExpirationDate) values (@Id, @ItemId, @UserId, @ExpirationDate)";
+
+ saveShareCommand.Parameters.Add(saveShareCommand, "@Id");
+ saveShareCommand.Parameters.Add(saveShareCommand, "@ItemId");
+ saveShareCommand.Parameters.Add(saveShareCommand, "@UserId");
+ saveShareCommand.Parameters.Add(saveShareCommand, "@ExpirationDate");
+
+ IDbTransaction transaction = null;
+
+ try
+ {
+ transaction = connection.BeginTransaction();
+
+ saveShareCommand.GetParameter(0).Value = new Guid(info.Id);
+ saveShareCommand.GetParameter(1).Value = info.ItemId;
+ saveShareCommand.GetParameter(2).Value = info.UserId;
+ saveShareCommand.GetParameter(3).Value = info.ExpirationDate;
+
+ saveShareCommand.Transaction = transaction;
+
+ saveShareCommand.ExecuteNonQuery();
+
+ transaction.Commit();
+ }
+ catch (OperationCanceledException)
+ {
+ if (transaction != null)
+ {
+ transaction.Rollback();
+ }
+
+ throw;
+ }
+ catch (Exception e)
+ {
+ Logger.ErrorException("Failed to save share:", e);
+
+ if (transaction != null)
+ {
+ transaction.Rollback();
+ }
+
+ throw;
+ }
+ finally
+ {
+ if (transaction != null)
+ {
+ transaction.Dispose();
+ }
+ }
}
-
- throw;
- }
- finally
- {
- if (transaction != null)
- {
- transaction.Dispose();
- }
-
- WriteLock.Release();
}
}
@@ -134,20 +119,23 @@ namespace MediaBrowser.Server.Implementations.Social
throw new ArgumentNullException("id");
}
- var cmd = _connection.CreateCommand();
- cmd.CommandText = "select Id, ItemId, UserId, ExpirationDate from Shares where id = @id";
+ using (var connection = CreateConnection(true).Result)
+ {
+ var cmd = connection.CreateCommand();
+ cmd.CommandText = "select Id, ItemId, UserId, ExpirationDate from Shares where id = @id";
- cmd.Parameters.Add(cmd, "@id", DbType.Guid).Value = new Guid(id);
+ cmd.Parameters.Add(cmd, "@id", DbType.Guid).Value = new Guid(id);
- using (var reader = cmd.ExecuteReader(CommandBehavior.SequentialAccess | CommandBehavior.SingleResult | CommandBehavior.SingleRow))
- {
- if (reader.Read())
+ using (var reader = cmd.ExecuteReader(CommandBehavior.SequentialAccess | CommandBehavior.SingleResult | CommandBehavior.SingleRow))
{
- return GetSocialShareInfo(reader);
+ if (reader.Read())
+ {
+ return GetSocialShareInfo(reader);
+ }
}
- }
- return null;
+ return null;
+ }
}
private SocialShareInfo GetSocialShareInfo(IDataReader reader)
@@ -164,21 +152,7 @@ namespace MediaBrowser.Server.Implementations.Social
public async Task DeleteShare(string id)
{
-
- }
- protected override void CloseConnection()
- {
- if (_connection != null)
- {
- if (_connection.IsOpen())
- {
- _connection.Close();
- }
-
- _connection.Dispose();
- _connection = null;
- }
}
}
}
diff --git a/MediaBrowser.Server.Implementations/Sync/SyncJobProcessor.cs b/MediaBrowser.Server.Implementations/Sync/SyncJobProcessor.cs
index bbba06870..e120d3a4d 100644
--- a/MediaBrowser.Server.Implementations/Sync/SyncJobProcessor.cs
+++ b/MediaBrowser.Server.Implementations/Sync/SyncJobProcessor.cs
@@ -234,10 +234,22 @@ namespace MediaBrowser.Server.Implementations.Sync
public async Task<IEnumerable<BaseItem>> GetItemsForSync(SyncCategory? category, string parentId, IEnumerable<string> itemIds, User user, bool unwatchedOnly)
{
- var items = category.HasValue ?
- await GetItemsForSync(category.Value, parentId, user).ConfigureAwait(false) :
- itemIds.SelectMany(i => GetItemsForSync(i, user));
+ var list = new List<BaseItem>();
+ if (category.HasValue)
+ {
+ list = (await GetItemsForSync(category.Value, parentId, user).ConfigureAwait(false)).ToList();
+ }
+ else
+ {
+ foreach (var itemId in itemIds)
+ {
+ var subList = await GetItemsForSync(itemId, user).ConfigureAwait(false);
+ list.AddRange(subList);
+ }
+ }
+
+ IEnumerable<BaseItem> items = list;
items = items.Where(_syncManager.SupportsSync);
if (unwatchedOnly)
@@ -314,7 +326,7 @@ namespace MediaBrowser.Server.Implementations.Sync
return result.Items;
}
- private IEnumerable<BaseItem> GetItemsForSync(string id, User user)
+ private async Task<List<BaseItem>> GetItemsForSync(string id, User user)
{
var item = _libraryManager.GetItemById(id);
@@ -330,18 +342,20 @@ namespace MediaBrowser.Server.Implementations.Sync
{
IsFolder = false,
Recursive = true
- });
+ }).ToList();
}
if (item.IsFolder)
{
var folder = (Folder)item;
- var items = folder.GetItems(new InternalItemsQuery(user)
+ var itemsResult = await folder.GetItems(new InternalItemsQuery(user)
{
Recursive = true,
IsFolder = false
- }).Result.Items;
+ }).ConfigureAwait(false);
+
+ var items = itemsResult.Items;
if (!folder.IsPreSorted)
{
@@ -349,10 +363,10 @@ namespace MediaBrowser.Server.Implementations.Sync
.ToArray();
}
- return items;
+ return items.ToList();
}
- return new[] { item };
+ return new List<BaseItem> { item };
}
private async Task EnsureSyncJobItems(string targetId, CancellationToken cancellationToken)
@@ -483,6 +497,11 @@ namespace MediaBrowser.Server.Implementations.Sync
private async Task ProcessJobItem(SyncJobItem jobItem, bool enableConversion, IProgress<double> progress, CancellationToken cancellationToken)
{
+ if (jobItem == null)
+ {
+ throw new ArgumentNullException("jobItem");
+ }
+
var item = _libraryManager.GetItemById(jobItem.ItemId);
if (item == null)
{
@@ -748,7 +767,7 @@ namespace MediaBrowser.Server.Implementations.Sync
_fileSystem.CreateDirectory(Path.GetDirectoryName(path));
- using (var stream = await _subtitleEncoder.GetSubtitles(streamInfo.ItemId, streamInfo.MediaSourceId, subtitleStreamIndex, subtitleStreamInfo.Format, 0, null, cancellationToken).ConfigureAwait(false))
+ using (var stream = await _subtitleEncoder.GetSubtitles(streamInfo.ItemId, streamInfo.MediaSourceId, subtitleStreamIndex, subtitleStreamInfo.Format, 0, null, false, cancellationToken).ConfigureAwait(false))
{
using (var fs = _fileSystem.GetFileStream(path, FileMode.Create, FileAccess.Write, FileShare.Read, true))
{
diff --git a/MediaBrowser.Server.Implementations/Sync/SyncRepository.cs b/MediaBrowser.Server.Implementations/Sync/SyncRepository.cs
index 739d1ab6e..a1ed66a99 100644
--- a/MediaBrowser.Server.Implementations/Sync/SyncRepository.cs
+++ b/MediaBrowser.Server.Implementations/Sync/SyncRepository.cs
@@ -18,34 +18,22 @@ namespace MediaBrowser.Server.Implementations.Sync
{
public class SyncRepository : BaseSqliteRepository, ISyncRepository
{
- private IDbConnection _connection;
private readonly CultureInfo _usCulture = new CultureInfo("en-US");
- private IDbCommand _insertJobCommand;
- private IDbCommand _updateJobCommand;
- private IDbCommand _deleteJobCommand;
-
- private IDbCommand _deleteJobItemsCommand;
- private IDbCommand _insertJobItemCommand;
- private IDbCommand _updateJobItemCommand;
-
private readonly IJsonSerializer _json;
- private readonly IServerApplicationPaths _appPaths;
- public SyncRepository(ILogManager logManager, IJsonSerializer json, IServerApplicationPaths appPaths)
- : base(logManager)
+ public SyncRepository(ILogManager logManager, IJsonSerializer json, IServerApplicationPaths appPaths, IDbConnector connector)
+ : base(logManager, connector)
{
_json = json;
- _appPaths = appPaths;
+ DbFilePath = Path.Combine(appPaths.DataPath, "sync14.db");
}
- public async Task Initialize(IDbConnector dbConnector)
+ public async Task Initialize()
{
- var dbFile = Path.Combine(_appPaths.DataPath, "sync14.db");
-
- _connection = await dbConnector.Connect(dbFile).ConfigureAwait(false);
-
- string[] queries = {
+ using (var connection = await CreateConnection().ConfigureAwait(false))
+ {
+ string[] queries = {
"create table if not exists SyncJobs (Id GUID PRIMARY KEY, TargetId TEXT NOT NULL, Name TEXT NOT NULL, Profile TEXT, Quality TEXT, Bitrate INT, Status TEXT NOT NULL, Progress FLOAT, UserId TEXT NOT NULL, ItemIds TEXT NOT NULL, Category TEXT, ParentId TEXT, UnwatchedOnly BIT, ItemLimit INT, SyncNewContent BIT, DateCreated DateTime, DateLastModified DateTime, ItemCount int)",
"create index if not exists idx_SyncJobs on SyncJobs(Id)",
@@ -55,120 +43,15 @@ namespace MediaBrowser.Server.Implementations.Sync
"create index if not exists idx_SyncJobItems1 on SyncJobItems(Id)",
"create index if not exists idx_SyncJobItems2 on SyncJobItems(TargetId)",
- //pragmas
- "pragma temp_store = memory",
-
"pragma shrink_memory"
};
- _connection.RunQueries(queries, Logger);
+ connection.RunQueries(queries, Logger);
- _connection.AddColumn(Logger, "SyncJobs", "Profile", "TEXT");
- _connection.AddColumn(Logger, "SyncJobs", "Bitrate", "INT");
- _connection.AddColumn(Logger, "SyncJobItems", "ItemDateModifiedTicks", "BIGINT");
-
- PrepareStatements();
- }
-
- private void PrepareStatements()
- {
- // _deleteJobCommand
- _deleteJobCommand = _connection.CreateCommand();
- _deleteJobCommand.CommandText = "delete from SyncJobs where Id=@Id";
- _deleteJobCommand.Parameters.Add(_deleteJobCommand, "@Id");
-
- // _deleteJobItemsCommand
- _deleteJobItemsCommand = _connection.CreateCommand();
- _deleteJobItemsCommand.CommandText = "delete from SyncJobItems where JobId=@JobId";
- _deleteJobItemsCommand.Parameters.Add(_deleteJobItemsCommand, "@JobId");
-
- // _insertJobCommand
- _insertJobCommand = _connection.CreateCommand();
- _insertJobCommand.CommandText = "insert into SyncJobs (Id, TargetId, Name, Profile, Quality, Bitrate, Status, Progress, UserId, ItemIds, Category, ParentId, UnwatchedOnly, ItemLimit, SyncNewContent, DateCreated, DateLastModified, ItemCount) values (@Id, @TargetId, @Name, @Profile, @Quality, @Bitrate, @Status, @Progress, @UserId, @ItemIds, @Category, @ParentId, @UnwatchedOnly, @ItemLimit, @SyncNewContent, @DateCreated, @DateLastModified, @ItemCount)";
-
- _insertJobCommand.Parameters.Add(_insertJobCommand, "@Id");
- _insertJobCommand.Parameters.Add(_insertJobCommand, "@TargetId");
- _insertJobCommand.Parameters.Add(_insertJobCommand, "@Name");
- _insertJobCommand.Parameters.Add(_insertJobCommand, "@Profile");
- _insertJobCommand.Parameters.Add(_insertJobCommand, "@Quality");
- _insertJobCommand.Parameters.Add(_insertJobCommand, "@Bitrate");
- _insertJobCommand.Parameters.Add(_insertJobCommand, "@Status");
- _insertJobCommand.Parameters.Add(_insertJobCommand, "@Progress");
- _insertJobCommand.Parameters.Add(_insertJobCommand, "@UserId");
- _insertJobCommand.Parameters.Add(_insertJobCommand, "@ItemIds");
- _insertJobCommand.Parameters.Add(_insertJobCommand, "@Category");
- _insertJobCommand.Parameters.Add(_insertJobCommand, "@ParentId");
- _insertJobCommand.Parameters.Add(_insertJobCommand, "@UnwatchedOnly");
- _insertJobCommand.Parameters.Add(_insertJobCommand, "@ItemLimit");
- _insertJobCommand.Parameters.Add(_insertJobCommand, "@SyncNewContent");
- _insertJobCommand.Parameters.Add(_insertJobCommand, "@DateCreated");
- _insertJobCommand.Parameters.Add(_insertJobCommand, "@DateLastModified");
- _insertJobCommand.Parameters.Add(_insertJobCommand, "@ItemCount");
-
- // _updateJobCommand
- _updateJobCommand = _connection.CreateCommand();
- _updateJobCommand.CommandText = "update SyncJobs set TargetId=@TargetId,Name=@Name,Profile=@Profile,Quality=@Quality,Bitrate=@Bitrate,Status=@Status,Progress=@Progress,UserId=@UserId,ItemIds=@ItemIds,Category=@Category,ParentId=@ParentId,UnwatchedOnly=@UnwatchedOnly,ItemLimit=@ItemLimit,SyncNewContent=@SyncNewContent,DateCreated=@DateCreated,DateLastModified=@DateLastModified,ItemCount=@ItemCount where Id=@Id";
-
- _updateJobCommand.Parameters.Add(_updateJobCommand, "@Id");
- _updateJobCommand.Parameters.Add(_updateJobCommand, "@TargetId");
- _updateJobCommand.Parameters.Add(_updateJobCommand, "@Name");
- _updateJobCommand.Parameters.Add(_updateJobCommand, "@Profile");
- _updateJobCommand.Parameters.Add(_updateJobCommand, "@Quality");
- _updateJobCommand.Parameters.Add(_updateJobCommand, "@Bitrate");
- _updateJobCommand.Parameters.Add(_updateJobCommand, "@Status");
- _updateJobCommand.Parameters.Add(_updateJobCommand, "@Progress");
- _updateJobCommand.Parameters.Add(_updateJobCommand, "@UserId");
- _updateJobCommand.Parameters.Add(_updateJobCommand, "@ItemIds");
- _updateJobCommand.Parameters.Add(_updateJobCommand, "@Category");
- _updateJobCommand.Parameters.Add(_updateJobCommand, "@ParentId");
- _updateJobCommand.Parameters.Add(_updateJobCommand, "@UnwatchedOnly");
- _updateJobCommand.Parameters.Add(_updateJobCommand, "@ItemLimit");
- _updateJobCommand.Parameters.Add(_updateJobCommand, "@SyncNewContent");
- _updateJobCommand.Parameters.Add(_updateJobCommand, "@DateCreated");
- _updateJobCommand.Parameters.Add(_updateJobCommand, "@DateLastModified");
- _updateJobCommand.Parameters.Add(_updateJobCommand, "@ItemCount");
-
- // _insertJobItemCommand
- _insertJobItemCommand = _connection.CreateCommand();
- _insertJobItemCommand.CommandText = "insert into SyncJobItems (Id, ItemId, ItemName, MediaSourceId, JobId, TemporaryPath, OutputPath, Status, TargetId, DateCreated, Progress, AdditionalFiles, MediaSource, IsMarkedForRemoval, JobItemIndex, ItemDateModifiedTicks) values (@Id, @ItemId, @ItemName, @MediaSourceId, @JobId, @TemporaryPath, @OutputPath, @Status, @TargetId, @DateCreated, @Progress, @AdditionalFiles, @MediaSource, @IsMarkedForRemoval, @JobItemIndex, @ItemDateModifiedTicks)";
-
- _insertJobItemCommand.Parameters.Add(_insertJobItemCommand, "@Id");
- _insertJobItemCommand.Parameters.Add(_insertJobItemCommand, "@ItemId");
- _insertJobItemCommand.Parameters.Add(_insertJobItemCommand, "@ItemName");
- _insertJobItemCommand.Parameters.Add(_insertJobItemCommand, "@MediaSourceId");
- _insertJobItemCommand.Parameters.Add(_insertJobItemCommand, "@JobId");
- _insertJobItemCommand.Parameters.Add(_insertJobItemCommand, "@TemporaryPath");
- _insertJobItemCommand.Parameters.Add(_insertJobItemCommand, "@OutputPath");
- _insertJobItemCommand.Parameters.Add(_insertJobItemCommand, "@Status");
- _insertJobItemCommand.Parameters.Add(_insertJobItemCommand, "@TargetId");
- _insertJobItemCommand.Parameters.Add(_insertJobItemCommand, "@DateCreated");
- _insertJobItemCommand.Parameters.Add(_insertJobItemCommand, "@Progress");
- _insertJobItemCommand.Parameters.Add(_insertJobItemCommand, "@AdditionalFiles");
- _insertJobItemCommand.Parameters.Add(_insertJobItemCommand, "@MediaSource");
- _insertJobItemCommand.Parameters.Add(_insertJobItemCommand, "@IsMarkedForRemoval");
- _insertJobItemCommand.Parameters.Add(_insertJobItemCommand, "@JobItemIndex");
- _insertJobItemCommand.Parameters.Add(_insertJobItemCommand, "@ItemDateModifiedTicks");
-
- // _updateJobItemCommand
- _updateJobItemCommand = _connection.CreateCommand();
- _updateJobItemCommand.CommandText = "update SyncJobItems set ItemId=@ItemId,ItemName=@ItemName,MediaSourceId=@MediaSourceId,JobId=@JobId,TemporaryPath=@TemporaryPath,OutputPath=@OutputPath,Status=@Status,TargetId=@TargetId,DateCreated=@DateCreated,Progress=@Progress,AdditionalFiles=@AdditionalFiles,MediaSource=@MediaSource,IsMarkedForRemoval=@IsMarkedForRemoval,JobItemIndex=@JobItemIndex,ItemDateModifiedTicks=@ItemDateModifiedTicks where Id=@Id";
-
- _updateJobItemCommand.Parameters.Add(_updateJobItemCommand, "@Id");
- _updateJobItemCommand.Parameters.Add(_updateJobItemCommand, "@ItemId");
- _updateJobItemCommand.Parameters.Add(_updateJobItemCommand, "@ItemName");
- _updateJobItemCommand.Parameters.Add(_updateJobItemCommand, "@MediaSourceId");
- _updateJobItemCommand.Parameters.Add(_updateJobItemCommand, "@JobId");
- _updateJobItemCommand.Parameters.Add(_updateJobItemCommand, "@TemporaryPath");
- _updateJobItemCommand.Parameters.Add(_updateJobItemCommand, "@OutputPath");
- _updateJobItemCommand.Parameters.Add(_updateJobItemCommand, "@Status");
- _updateJobItemCommand.Parameters.Add(_updateJobItemCommand, "@TargetId");
- _updateJobItemCommand.Parameters.Add(_updateJobItemCommand, "@DateCreated");
- _updateJobItemCommand.Parameters.Add(_updateJobItemCommand, "@Progress");
- _updateJobItemCommand.Parameters.Add(_updateJobItemCommand, "@AdditionalFiles");
- _updateJobItemCommand.Parameters.Add(_updateJobItemCommand, "@MediaSource");
- _updateJobItemCommand.Parameters.Add(_updateJobItemCommand, "@IsMarkedForRemoval");
- _updateJobItemCommand.Parameters.Add(_updateJobItemCommand, "@JobItemIndex");
- _updateJobItemCommand.Parameters.Add(_updateJobItemCommand, "@ItemDateModifiedTicks");
+ connection.AddColumn(Logger, "SyncJobs", "Profile", "TEXT");
+ connection.AddColumn(Logger, "SyncJobs", "Bitrate", "INT");
+ connection.AddColumn(Logger, "SyncJobItems", "ItemDateModifiedTicks", "BIGINT");
+ }
}
private const string BaseJobSelectText = "select Id, TargetId, Name, Profile, Quality, Bitrate, Status, Progress, UserId, ItemIds, Category, ParentId, UnwatchedOnly, ItemLimit, SyncNewContent, DateCreated, DateLastModified, ItemCount from SyncJobs";
@@ -182,7 +65,7 @@ namespace MediaBrowser.Server.Implementations.Sync
}
CheckDisposed();
-
+
var guid = new Guid(id);
if (guid == Guid.Empty)
@@ -190,22 +73,25 @@ namespace MediaBrowser.Server.Implementations.Sync
throw new ArgumentNullException("id");
}
- using (var cmd = _connection.CreateCommand())
+ using (var connection = CreateConnection(true).Result)
{
- cmd.CommandText = BaseJobSelectText + " where Id=@Id";
+ using (var cmd = connection.CreateCommand())
+ {
+ cmd.CommandText = BaseJobSelectText + " where Id=@Id";
- cmd.Parameters.Add(cmd, "@Id", DbType.Guid).Value = guid;
+ cmd.Parameters.Add(cmd, "@Id", DbType.Guid).Value = guid;
- using (var reader = cmd.ExecuteReader(CommandBehavior.SequentialAccess | CommandBehavior.SingleResult | CommandBehavior.SingleRow))
- {
- if (reader.Read())
+ using (var reader = cmd.ExecuteReader(CommandBehavior.SequentialAccess | CommandBehavior.SingleResult | CommandBehavior.SingleRow))
{
- return GetJob(reader);
+ if (reader.Read())
+ {
+ return GetJob(reader);
+ }
}
}
- }
- return null;
+ return null;
+ }
}
private SyncJob GetJob(IDataReader reader)
@@ -283,15 +169,15 @@ namespace MediaBrowser.Server.Implementations.Sync
public Task Create(SyncJob job)
{
- return InsertOrUpdate(job, _insertJobCommand);
+ return InsertOrUpdate(job, true);
}
public Task Update(SyncJob job)
{
- return InsertOrUpdate(job, _updateJobCommand);
+ return InsertOrUpdate(job, false);
}
- private async Task InsertOrUpdate(SyncJob job, IDbCommand cmd)
+ private async Task InsertOrUpdate(SyncJob job, bool insert)
{
if (job == null)
{
@@ -299,70 +185,119 @@ namespace MediaBrowser.Server.Implementations.Sync
}
CheckDisposed();
-
- await WriteLock.WaitAsync().ConfigureAwait(false);
-
- IDbTransaction transaction = null;
- try
+ using (var connection = await CreateConnection().ConfigureAwait(false))
{
- transaction = _connection.BeginTransaction();
-
- var index = 0;
-
- cmd.GetParameter(index++).Value = new Guid(job.Id);
- cmd.GetParameter(index++).Value = job.TargetId;
- cmd.GetParameter(index++).Value = job.Name;
- cmd.GetParameter(index++).Value = job.Profile;
- cmd.GetParameter(index++).Value = job.Quality;
- cmd.GetParameter(index++).Value = job.Bitrate;
- cmd.GetParameter(index++).Value = job.Status.ToString();
- cmd.GetParameter(index++).Value = job.Progress;
- cmd.GetParameter(index++).Value = job.UserId;
- cmd.GetParameter(index++).Value = string.Join(",", job.RequestedItemIds.ToArray());
- cmd.GetParameter(index++).Value = job.Category;
- cmd.GetParameter(index++).Value = job.ParentId;
- cmd.GetParameter(index++).Value = job.UnwatchedOnly;
- cmd.GetParameter(index++).Value = job.ItemLimit;
- cmd.GetParameter(index++).Value = job.SyncNewContent;
- cmd.GetParameter(index++).Value = job.DateCreated;
- cmd.GetParameter(index++).Value = job.DateLastModified;
- cmd.GetParameter(index++).Value = job.ItemCount;
-
- cmd.Transaction = transaction;
+ using (var cmd = connection.CreateCommand())
+ {
+ if (insert)
+ {
+ cmd.CommandText = "insert into SyncJobs (Id, TargetId, Name, Profile, Quality, Bitrate, Status, Progress, UserId, ItemIds, Category, ParentId, UnwatchedOnly, ItemLimit, SyncNewContent, DateCreated, DateLastModified, ItemCount) values (@Id, @TargetId, @Name, @Profile, @Quality, @Bitrate, @Status, @Progress, @UserId, @ItemIds, @Category, @ParentId, @UnwatchedOnly, @ItemLimit, @SyncNewContent, @DateCreated, @DateLastModified, @ItemCount)";
+
+ cmd.Parameters.Add(cmd, "@Id");
+ cmd.Parameters.Add(cmd, "@TargetId");
+ cmd.Parameters.Add(cmd, "@Name");
+ cmd.Parameters.Add(cmd, "@Profile");
+ cmd.Parameters.Add(cmd, "@Quality");
+ cmd.Parameters.Add(cmd, "@Bitrate");
+ cmd.Parameters.Add(cmd, "@Status");
+ cmd.Parameters.Add(cmd, "@Progress");
+ cmd.Parameters.Add(cmd, "@UserId");
+ cmd.Parameters.Add(cmd, "@ItemIds");
+ cmd.Parameters.Add(cmd, "@Category");
+ cmd.Parameters.Add(cmd, "@ParentId");
+ cmd.Parameters.Add(cmd, "@UnwatchedOnly");
+ cmd.Parameters.Add(cmd, "@ItemLimit");
+ cmd.Parameters.Add(cmd, "@SyncNewContent");
+ cmd.Parameters.Add(cmd, "@DateCreated");
+ cmd.Parameters.Add(cmd, "@DateLastModified");
+ cmd.Parameters.Add(cmd, "@ItemCount");
+ }
+ else
+ {
+ cmd.CommandText = "update SyncJobs set TargetId=@TargetId,Name=@Name,Profile=@Profile,Quality=@Quality,Bitrate=@Bitrate,Status=@Status,Progress=@Progress,UserId=@UserId,ItemIds=@ItemIds,Category=@Category,ParentId=@ParentId,UnwatchedOnly=@UnwatchedOnly,ItemLimit=@ItemLimit,SyncNewContent=@SyncNewContent,DateCreated=@DateCreated,DateLastModified=@DateLastModified,ItemCount=@ItemCount where Id=@Id";
+
+ cmd.Parameters.Add(cmd, "@Id");
+ cmd.Parameters.Add(cmd, "@TargetId");
+ cmd.Parameters.Add(cmd, "@Name");
+ cmd.Parameters.Add(cmd, "@Profile");
+ cmd.Parameters.Add(cmd, "@Quality");
+ cmd.Parameters.Add(cmd, "@Bitrate");
+ cmd.Parameters.Add(cmd, "@Status");
+ cmd.Parameters.Add(cmd, "@Progress");
+ cmd.Parameters.Add(cmd, "@UserId");
+ cmd.Parameters.Add(cmd, "@ItemIds");
+ cmd.Parameters.Add(cmd, "@Category");
+ cmd.Parameters.Add(cmd, "@ParentId");
+ cmd.Parameters.Add(cmd, "@UnwatchedOnly");
+ cmd.Parameters.Add(cmd, "@ItemLimit");
+ cmd.Parameters.Add(cmd, "@SyncNewContent");
+ cmd.Parameters.Add(cmd, "@DateCreated");
+ cmd.Parameters.Add(cmd, "@DateLastModified");
+ cmd.Parameters.Add(cmd, "@ItemCount");
+ }
- cmd.ExecuteNonQuery();
+ IDbTransaction transaction = null;
- transaction.Commit();
- }
- catch (OperationCanceledException)
- {
- if (transaction != null)
- {
- transaction.Rollback();
- }
+ try
+ {
+ transaction = connection.BeginTransaction();
+
+ var index = 0;
+
+ cmd.GetParameter(index++).Value = new Guid(job.Id);
+ cmd.GetParameter(index++).Value = job.TargetId;
+ cmd.GetParameter(index++).Value = job.Name;
+ cmd.GetParameter(index++).Value = job.Profile;
+ cmd.GetParameter(index++).Value = job.Quality;
+ cmd.GetParameter(index++).Value = job.Bitrate;
+ cmd.GetParameter(index++).Value = job.Status.ToString();
+ cmd.GetParameter(index++).Value = job.Progress;
+ cmd.GetParameter(index++).Value = job.UserId;
+ cmd.GetParameter(index++).Value = string.Join(",", job.RequestedItemIds.ToArray());
+ cmd.GetParameter(index++).Value = job.Category;
+ cmd.GetParameter(index++).Value = job.ParentId;
+ cmd.GetParameter(index++).Value = job.UnwatchedOnly;
+ cmd.GetParameter(index++).Value = job.ItemLimit;
+ cmd.GetParameter(index++).Value = job.SyncNewContent;
+ cmd.GetParameter(index++).Value = job.DateCreated;
+ cmd.GetParameter(index++).Value = job.DateLastModified;
+ cmd.GetParameter(index++).Value = job.ItemCount;
+
+ cmd.Transaction = transaction;
+
+ cmd.ExecuteNonQuery();
+
+ transaction.Commit();
+ }
+ catch (OperationCanceledException)
+ {
+ if (transaction != null)
+ {
+ transaction.Rollback();
+ }
- throw;
- }
- catch (Exception e)
- {
- Logger.ErrorException("Failed to save record:", e);
+ throw;
+ }
+ catch (Exception e)
+ {
+ Logger.ErrorException("Failed to save record:", e);
- if (transaction != null)
- {
- transaction.Rollback();
- }
+ if (transaction != null)
+ {
+ transaction.Rollback();
+ }
- throw;
- }
- finally
- {
- if (transaction != null)
- {
- transaction.Dispose();
+ throw;
+ }
+ finally
+ {
+ if (transaction != null)
+ {
+ transaction.Dispose();
+ }
+ }
}
-
- WriteLock.Release();
}
}
@@ -374,56 +309,66 @@ namespace MediaBrowser.Server.Implementations.Sync
}
CheckDisposed();
-
- await WriteLock.WaitAsync().ConfigureAwait(false);
-
- IDbTransaction transaction = null;
-
- try
- {
- transaction = _connection.BeginTransaction();
-
- var index = 0;
-
- _deleteJobCommand.GetParameter(index++).Value = new Guid(id);
- _deleteJobCommand.Transaction = transaction;
- _deleteJobCommand.ExecuteNonQuery();
-
- index = 0;
- _deleteJobItemsCommand.GetParameter(index++).Value = id;
- _deleteJobItemsCommand.Transaction = transaction;
- _deleteJobItemsCommand.ExecuteNonQuery();
-
- transaction.Commit();
- }
- catch (OperationCanceledException)
- {
- if (transaction != null)
- {
- transaction.Rollback();
- }
- throw;
- }
- catch (Exception e)
+ using (var connection = await CreateConnection().ConfigureAwait(false))
{
- Logger.ErrorException("Failed to save record:", e);
-
- if (transaction != null)
+ using (var deleteJobCommand = connection.CreateCommand())
{
- transaction.Rollback();
- }
-
- throw;
- }
- finally
- {
- if (transaction != null)
- {
- transaction.Dispose();
+ using (var deleteJobItemsCommand = connection.CreateCommand())
+ {
+ IDbTransaction transaction = null;
+
+ try
+ {
+ // _deleteJobCommand
+ deleteJobCommand.CommandText = "delete from SyncJobs where Id=@Id";
+ deleteJobCommand.Parameters.Add(deleteJobCommand, "@Id");
+
+ transaction = connection.BeginTransaction();
+
+ deleteJobCommand.GetParameter(0).Value = new Guid(id);
+ deleteJobCommand.Transaction = transaction;
+ deleteJobCommand.ExecuteNonQuery();
+
+ // _deleteJobItemsCommand
+ deleteJobItemsCommand.CommandText = "delete from SyncJobItems where JobId=@JobId";
+ deleteJobItemsCommand.Parameters.Add(deleteJobItemsCommand, "@JobId");
+
+ deleteJobItemsCommand.GetParameter(0).Value = id;
+ deleteJobItemsCommand.Transaction = transaction;
+ deleteJobItemsCommand.ExecuteNonQuery();
+
+ transaction.Commit();
+ }
+ catch (OperationCanceledException)
+ {
+ if (transaction != null)
+ {
+ transaction.Rollback();
+ }
+
+ throw;
+ }
+ catch (Exception e)
+ {
+ Logger.ErrorException("Failed to save record:", e);
+
+ if (transaction != null)
+ {
+ transaction.Rollback();
+ }
+
+ throw;
+ }
+ finally
+ {
+ if (transaction != null)
+ {
+ transaction.Dispose();
+ }
+ }
+ }
}
-
- WriteLock.Release();
}
}
@@ -435,83 +380,86 @@ namespace MediaBrowser.Server.Implementations.Sync
}
CheckDisposed();
-
- using (var cmd = _connection.CreateCommand())
+
+ using (var connection = CreateConnection(true).Result)
{
- cmd.CommandText = BaseJobSelectText;
+ using (var cmd = connection.CreateCommand())
+ {
+ cmd.CommandText = BaseJobSelectText;
- var whereClauses = new List<string>();
+ var whereClauses = new List<string>();
- if (query.Statuses.Length > 0)
- {
- var statuses = string.Join(",", query.Statuses.Select(i => "'" + i.ToString() + "'").ToArray());
+ if (query.Statuses.Length > 0)
+ {
+ var statuses = string.Join(",", query.Statuses.Select(i => "'" + i.ToString() + "'").ToArray());
- whereClauses.Add(string.Format("Status in ({0})", statuses));
- }
- if (!string.IsNullOrWhiteSpace(query.TargetId))
- {
- whereClauses.Add("TargetId=@TargetId");
- cmd.Parameters.Add(cmd, "@TargetId", DbType.String).Value = query.TargetId;
- }
- if (!string.IsNullOrWhiteSpace(query.UserId))
- {
- whereClauses.Add("UserId=@UserId");
- cmd.Parameters.Add(cmd, "@UserId", DbType.String).Value = query.UserId;
- }
- if (query.SyncNewContent.HasValue)
- {
- whereClauses.Add("SyncNewContent=@SyncNewContent");
- cmd.Parameters.Add(cmd, "@SyncNewContent", DbType.Boolean).Value = query.SyncNewContent.Value;
- }
+ whereClauses.Add(string.Format("Status in ({0})", statuses));
+ }
+ if (!string.IsNullOrWhiteSpace(query.TargetId))
+ {
+ whereClauses.Add("TargetId=@TargetId");
+ cmd.Parameters.Add(cmd, "@TargetId", DbType.String).Value = query.TargetId;
+ }
+ if (!string.IsNullOrWhiteSpace(query.UserId))
+ {
+ whereClauses.Add("UserId=@UserId");
+ cmd.Parameters.Add(cmd, "@UserId", DbType.String).Value = query.UserId;
+ }
+ if (query.SyncNewContent.HasValue)
+ {
+ whereClauses.Add("SyncNewContent=@SyncNewContent");
+ cmd.Parameters.Add(cmd, "@SyncNewContent", DbType.Boolean).Value = query.SyncNewContent.Value;
+ }
- cmd.CommandText += " mainTable";
+ cmd.CommandText += " mainTable";
- var whereTextWithoutPaging = whereClauses.Count == 0 ?
- string.Empty :
- " where " + string.Join(" AND ", whereClauses.ToArray());
+ var whereTextWithoutPaging = whereClauses.Count == 0 ?
+ string.Empty :
+ " where " + string.Join(" AND ", whereClauses.ToArray());
- var startIndex = query.StartIndex ?? 0;
- if (startIndex > 0)
- {
- whereClauses.Add(string.Format("Id NOT IN (SELECT Id FROM SyncJobs ORDER BY (Select Max(DateLastModified) from SyncJobs where TargetId=mainTable.TargetId) DESC, DateLastModified DESC LIMIT {0})",
- startIndex.ToString(_usCulture)));
- }
+ var startIndex = query.StartIndex ?? 0;
+ if (startIndex > 0)
+ {
+ whereClauses.Add(string.Format("Id NOT IN (SELECT Id FROM SyncJobs ORDER BY (Select Max(DateLastModified) from SyncJobs where TargetId=mainTable.TargetId) DESC, DateLastModified DESC LIMIT {0})",
+ startIndex.ToString(_usCulture)));
+ }
- if (whereClauses.Count > 0)
- {
- cmd.CommandText += " where " + string.Join(" AND ", whereClauses.ToArray());
- }
+ if (whereClauses.Count > 0)
+ {
+ cmd.CommandText += " where " + string.Join(" AND ", whereClauses.ToArray());
+ }
- cmd.CommandText += " ORDER BY (Select Max(DateLastModified) from SyncJobs where TargetId=mainTable.TargetId) DESC, DateLastModified DESC";
+ cmd.CommandText += " ORDER BY (Select Max(DateLastModified) from SyncJobs where TargetId=mainTable.TargetId) DESC, DateLastModified DESC";
- if (query.Limit.HasValue)
- {
- cmd.CommandText += " LIMIT " + query.Limit.Value.ToString(_usCulture);
- }
+ if (query.Limit.HasValue)
+ {
+ cmd.CommandText += " LIMIT " + query.Limit.Value.ToString(_usCulture);
+ }
- cmd.CommandText += "; select count (Id) from SyncJobs" + whereTextWithoutPaging;
+ cmd.CommandText += "; select count (Id) from SyncJobs" + whereTextWithoutPaging;
- var list = new List<SyncJob>();
- var count = 0;
+ var list = new List<SyncJob>();
+ var count = 0;
- using (var reader = cmd.ExecuteReader(CommandBehavior.SequentialAccess))
- {
- while (reader.Read())
+ using (var reader = cmd.ExecuteReader(CommandBehavior.SequentialAccess))
{
- list.Add(GetJob(reader));
+ while (reader.Read())
+ {
+ list.Add(GetJob(reader));
+ }
+
+ if (reader.NextResult() && reader.Read())
+ {
+ count = reader.GetInt32(0);
+ }
}
- if (reader.NextResult() && reader.Read())
+ return new QueryResult<SyncJob>()
{
- count = reader.GetInt32(0);
- }
+ Items = list.ToArray(),
+ TotalRecordCount = count
+ };
}
-
- return new QueryResult<SyncJob>()
- {
- Items = list.ToArray(),
- TotalRecordCount = count
- };
}
}
@@ -523,25 +471,28 @@ namespace MediaBrowser.Server.Implementations.Sync
}
CheckDisposed();
-
+
var guid = new Guid(id);
- using (var cmd = _connection.CreateCommand())
+ using (var connection = CreateConnection(true).Result)
{
- cmd.CommandText = BaseJobItemSelectText + " where Id=@Id";
+ using (var cmd = connection.CreateCommand())
+ {
+ cmd.CommandText = BaseJobItemSelectText + " where Id=@Id";
- cmd.Parameters.Add(cmd, "@Id", DbType.Guid).Value = guid;
+ cmd.Parameters.Add(cmd, "@Id", DbType.Guid).Value = guid;
- using (var reader = cmd.ExecuteReader(CommandBehavior.SequentialAccess | CommandBehavior.SingleResult | CommandBehavior.SingleRow))
- {
- if (reader.Read())
+ using (var reader = cmd.ExecuteReader(CommandBehavior.SequentialAccess | CommandBehavior.SingleResult | CommandBehavior.SingleRow))
{
- return GetJobItem(reader);
+ if (reader.Read())
+ {
+ return GetJobItem(reader);
+ }
}
}
- }
- return null;
+ return null;
+ }
}
private QueryResult<T> GetJobItemReader<T>(SyncJobItemQuery query, string baseSelectText, Func<IDataReader, T> itemFactory)
@@ -551,81 +502,84 @@ namespace MediaBrowser.Server.Implementations.Sync
throw new ArgumentNullException("query");
}
- using (var cmd = _connection.CreateCommand())
+ using (var connection = CreateConnection(true).Result)
{
- cmd.CommandText = baseSelectText;
+ using (var cmd = connection.CreateCommand())
+ {
+ cmd.CommandText = baseSelectText;
- var whereClauses = new List<string>();
+ var whereClauses = new List<string>();
- if (!string.IsNullOrWhiteSpace(query.JobId))
- {
- whereClauses.Add("JobId=@JobId");
- cmd.Parameters.Add(cmd, "@JobId", DbType.String).Value = query.JobId;
- }
- if (!string.IsNullOrWhiteSpace(query.ItemId))
- {
- whereClauses.Add("ItemId=@ItemId");
- cmd.Parameters.Add(cmd, "@ItemId", DbType.String).Value = query.ItemId;
- }
- if (!string.IsNullOrWhiteSpace(query.TargetId))
- {
- whereClauses.Add("TargetId=@TargetId");
- cmd.Parameters.Add(cmd, "@TargetId", DbType.String).Value = query.TargetId;
- }
+ if (!string.IsNullOrWhiteSpace(query.JobId))
+ {
+ whereClauses.Add("JobId=@JobId");
+ cmd.Parameters.Add(cmd, "@JobId", DbType.String).Value = query.JobId;
+ }
+ if (!string.IsNullOrWhiteSpace(query.ItemId))
+ {
+ whereClauses.Add("ItemId=@ItemId");
+ cmd.Parameters.Add(cmd, "@ItemId", DbType.String).Value = query.ItemId;
+ }
+ if (!string.IsNullOrWhiteSpace(query.TargetId))
+ {
+ whereClauses.Add("TargetId=@TargetId");
+ cmd.Parameters.Add(cmd, "@TargetId", DbType.String).Value = query.TargetId;
+ }
- if (query.Statuses.Length > 0)
- {
- var statuses = string.Join(",", query.Statuses.Select(i => "'" + i.ToString() + "'").ToArray());
+ if (query.Statuses.Length > 0)
+ {
+ var statuses = string.Join(",", query.Statuses.Select(i => "'" + i.ToString() + "'").ToArray());
- whereClauses.Add(string.Format("Status in ({0})", statuses));
- }
+ whereClauses.Add(string.Format("Status in ({0})", statuses));
+ }
- var whereTextWithoutPaging = whereClauses.Count == 0 ?
- string.Empty :
- " where " + string.Join(" AND ", whereClauses.ToArray());
+ var whereTextWithoutPaging = whereClauses.Count == 0 ?
+ string.Empty :
+ " where " + string.Join(" AND ", whereClauses.ToArray());
- var startIndex = query.StartIndex ?? 0;
- if (startIndex > 0)
- {
- whereClauses.Add(string.Format("Id NOT IN (SELECT Id FROM SyncJobItems ORDER BY JobItemIndex, DateCreated LIMIT {0})",
- startIndex.ToString(_usCulture)));
- }
+ var startIndex = query.StartIndex ?? 0;
+ if (startIndex > 0)
+ {
+ whereClauses.Add(string.Format("Id NOT IN (SELECT Id FROM SyncJobItems ORDER BY JobItemIndex, DateCreated LIMIT {0})",
+ startIndex.ToString(_usCulture)));
+ }
- if (whereClauses.Count > 0)
- {
- cmd.CommandText += " where " + string.Join(" AND ", whereClauses.ToArray());
- }
+ if (whereClauses.Count > 0)
+ {
+ cmd.CommandText += " where " + string.Join(" AND ", whereClauses.ToArray());
+ }
- cmd.CommandText += " ORDER BY JobItemIndex, DateCreated";
+ cmd.CommandText += " ORDER BY JobItemIndex, DateCreated";
- if (query.Limit.HasValue)
- {
- cmd.CommandText += " LIMIT " + query.Limit.Value.ToString(_usCulture);
- }
+ if (query.Limit.HasValue)
+ {
+ cmd.CommandText += " LIMIT " + query.Limit.Value.ToString(_usCulture);
+ }
- cmd.CommandText += "; select count (Id) from SyncJobItems" + whereTextWithoutPaging;
+ cmd.CommandText += "; select count (Id) from SyncJobItems" + whereTextWithoutPaging;
- var list = new List<T>();
- var count = 0;
+ var list = new List<T>();
+ var count = 0;
- using (var reader = cmd.ExecuteReader(CommandBehavior.SequentialAccess))
- {
- while (reader.Read())
+ using (var reader = cmd.ExecuteReader(CommandBehavior.SequentialAccess))
{
- list.Add(itemFactory(reader));
+ while (reader.Read())
+ {
+ list.Add(itemFactory(reader));
+ }
+
+ if (reader.NextResult() && reader.Read())
+ {
+ count = reader.GetInt32(0);
+ }
}
- if (reader.NextResult() && reader.Read())
+ return new QueryResult<T>()
{
- count = reader.GetInt32(0);
- }
+ Items = list.ToArray(),
+ TotalRecordCount = count
+ };
}
-
- return new QueryResult<T>()
- {
- Items = list.ToArray(),
- TotalRecordCount = count
- };
}
}
@@ -641,15 +595,15 @@ namespace MediaBrowser.Server.Implementations.Sync
public Task Create(SyncJobItem jobItem)
{
- return InsertOrUpdate(jobItem, _insertJobItemCommand);
+ return InsertOrUpdate(jobItem, true);
}
public Task Update(SyncJobItem jobItem)
{
- return InsertOrUpdate(jobItem, _updateJobItemCommand);
+ return InsertOrUpdate(jobItem, false);
}
- private async Task InsertOrUpdate(SyncJobItem jobItem, IDbCommand cmd)
+ private async Task InsertOrUpdate(SyncJobItem jobItem, bool insert)
{
if (jobItem == null)
{
@@ -657,68 +611,114 @@ namespace MediaBrowser.Server.Implementations.Sync
}
CheckDisposed();
-
- await WriteLock.WaitAsync().ConfigureAwait(false);
- IDbTransaction transaction = null;
-
- try
+ using (var connection = await CreateConnection().ConfigureAwait(false))
{
- transaction = _connection.BeginTransaction();
-
- var index = 0;
-
- cmd.GetParameter(index++).Value = new Guid(jobItem.Id);
- cmd.GetParameter(index++).Value = jobItem.ItemId;
- cmd.GetParameter(index++).Value = jobItem.ItemName;
- cmd.GetParameter(index++).Value = jobItem.MediaSourceId;
- cmd.GetParameter(index++).Value = jobItem.JobId;
- cmd.GetParameter(index++).Value = jobItem.TemporaryPath;
- cmd.GetParameter(index++).Value = jobItem.OutputPath;
- cmd.GetParameter(index++).Value = jobItem.Status.ToString();
- cmd.GetParameter(index++).Value = jobItem.TargetId;
- cmd.GetParameter(index++).Value = jobItem.DateCreated;
- cmd.GetParameter(index++).Value = jobItem.Progress;
- cmd.GetParameter(index++).Value = _json.SerializeToString(jobItem.AdditionalFiles);
- cmd.GetParameter(index++).Value = jobItem.MediaSource == null ? null : _json.SerializeToString(jobItem.MediaSource);
- cmd.GetParameter(index++).Value = jobItem.IsMarkedForRemoval;
- cmd.GetParameter(index++).Value = jobItem.JobItemIndex;
- cmd.GetParameter(index++).Value = jobItem.ItemDateModifiedTicks;
-
- cmd.Transaction = transaction;
+ using (var cmd = connection.CreateCommand())
+ {
+ if (insert)
+ {
+ cmd.CommandText = "insert into SyncJobItems (Id, ItemId, ItemName, MediaSourceId, JobId, TemporaryPath, OutputPath, Status, TargetId, DateCreated, Progress, AdditionalFiles, MediaSource, IsMarkedForRemoval, JobItemIndex, ItemDateModifiedTicks) values (@Id, @ItemId, @ItemName, @MediaSourceId, @JobId, @TemporaryPath, @OutputPath, @Status, @TargetId, @DateCreated, @Progress, @AdditionalFiles, @MediaSource, @IsMarkedForRemoval, @JobItemIndex, @ItemDateModifiedTicks)";
+
+ cmd.Parameters.Add(cmd, "@Id");
+ cmd.Parameters.Add(cmd, "@ItemId");
+ cmd.Parameters.Add(cmd, "@ItemName");
+ cmd.Parameters.Add(cmd, "@MediaSourceId");
+ cmd.Parameters.Add(cmd, "@JobId");
+ cmd.Parameters.Add(cmd, "@TemporaryPath");
+ cmd.Parameters.Add(cmd, "@OutputPath");
+ cmd.Parameters.Add(cmd, "@Status");
+ cmd.Parameters.Add(cmd, "@TargetId");
+ cmd.Parameters.Add(cmd, "@DateCreated");
+ cmd.Parameters.Add(cmd, "@Progress");
+ cmd.Parameters.Add(cmd, "@AdditionalFiles");
+ cmd.Parameters.Add(cmd, "@MediaSource");
+ cmd.Parameters.Add(cmd, "@IsMarkedForRemoval");
+ cmd.Parameters.Add(cmd, "@JobItemIndex");
+ cmd.Parameters.Add(cmd, "@ItemDateModifiedTicks");
+ }
+ else
+ {
+ // cmd
+ cmd.CommandText = "update SyncJobItems set ItemId=@ItemId,ItemName=@ItemName,MediaSourceId=@MediaSourceId,JobId=@JobId,TemporaryPath=@TemporaryPath,OutputPath=@OutputPath,Status=@Status,TargetId=@TargetId,DateCreated=@DateCreated,Progress=@Progress,AdditionalFiles=@AdditionalFiles,MediaSource=@MediaSource,IsMarkedForRemoval=@IsMarkedForRemoval,JobItemIndex=@JobItemIndex,ItemDateModifiedTicks=@ItemDateModifiedTicks where Id=@Id";
+
+ cmd.Parameters.Add(cmd, "@Id");
+ cmd.Parameters.Add(cmd, "@ItemId");
+ cmd.Parameters.Add(cmd, "@ItemName");
+ cmd.Parameters.Add(cmd, "@MediaSourceId");
+ cmd.Parameters.Add(cmd, "@JobId");
+ cmd.Parameters.Add(cmd, "@TemporaryPath");
+ cmd.Parameters.Add(cmd, "@OutputPath");
+ cmd.Parameters.Add(cmd, "@Status");
+ cmd.Parameters.Add(cmd, "@TargetId");
+ cmd.Parameters.Add(cmd, "@DateCreated");
+ cmd.Parameters.Add(cmd, "@Progress");
+ cmd.Parameters.Add(cmd, "@AdditionalFiles");
+ cmd.Parameters.Add(cmd, "@MediaSource");
+ cmd.Parameters.Add(cmd, "@IsMarkedForRemoval");
+ cmd.Parameters.Add(cmd, "@JobItemIndex");
+ cmd.Parameters.Add(cmd, "@ItemDateModifiedTicks");
+ }
- cmd.ExecuteNonQuery();
+ IDbTransaction transaction = null;
- transaction.Commit();
- }
- catch (OperationCanceledException)
- {
- if (transaction != null)
- {
- transaction.Rollback();
- }
+ try
+ {
+ transaction = connection.BeginTransaction();
+
+ var index = 0;
+
+ cmd.GetParameter(index++).Value = new Guid(jobItem.Id);
+ cmd.GetParameter(index++).Value = jobItem.ItemId;
+ cmd.GetParameter(index++).Value = jobItem.ItemName;
+ cmd.GetParameter(index++).Value = jobItem.MediaSourceId;
+ cmd.GetParameter(index++).Value = jobItem.JobId;
+ cmd.GetParameter(index++).Value = jobItem.TemporaryPath;
+ cmd.GetParameter(index++).Value = jobItem.OutputPath;
+ cmd.GetParameter(index++).Value = jobItem.Status.ToString();
+ cmd.GetParameter(index++).Value = jobItem.TargetId;
+ cmd.GetParameter(index++).Value = jobItem.DateCreated;
+ cmd.GetParameter(index++).Value = jobItem.Progress;
+ cmd.GetParameter(index++).Value = _json.SerializeToString(jobItem.AdditionalFiles);
+ cmd.GetParameter(index++).Value = jobItem.MediaSource == null ? null : _json.SerializeToString(jobItem.MediaSource);
+ cmd.GetParameter(index++).Value = jobItem.IsMarkedForRemoval;
+ cmd.GetParameter(index++).Value = jobItem.JobItemIndex;
+ cmd.GetParameter(index++).Value = jobItem.ItemDateModifiedTicks;
+
+ cmd.Transaction = transaction;
+
+ cmd.ExecuteNonQuery();
+
+ transaction.Commit();
+ }
+ catch (OperationCanceledException)
+ {
+ if (transaction != null)
+ {
+ transaction.Rollback();
+ }
- throw;
- }
- catch (Exception e)
- {
- Logger.ErrorException("Failed to save record:", e);
+ throw;
+ }
+ catch (Exception e)
+ {
+ Logger.ErrorException("Failed to save record:", e);
- if (transaction != null)
- {
- transaction.Rollback();
- }
+ if (transaction != null)
+ {
+ transaction.Rollback();
+ }
- throw;
- }
- finally
- {
- if (transaction != null)
- {
- transaction.Dispose();
+ throw;
+ }
+ finally
+ {
+ if (transaction != null)
+ {
+ transaction.Dispose();
+ }
+ }
}
-
- WriteLock.Release();
}
}
@@ -809,19 +809,5 @@ namespace MediaBrowser.Server.Implementations.Sync
return item;
}
-
- protected override void CloseConnection()
- {
- if (_connection != null)
- {
- if (_connection.IsOpen())
- {
- _connection.Close();
- }
-
- _connection.Dispose();
- _connection = null;
- }
- }
}
}
diff --git a/MediaBrowser.Server.Implementations/TV/TVSeriesManager.cs b/MediaBrowser.Server.Implementations/TV/TVSeriesManager.cs
index ec91dc1b7..b65c030f0 100644
--- a/MediaBrowser.Server.Implementations/TV/TVSeriesManager.cs
+++ b/MediaBrowser.Server.Implementations/TV/TVSeriesManager.cs
@@ -32,9 +32,7 @@ namespace MediaBrowser.Server.Implementations.TV
throw new ArgumentException("User not found");
}
- var parentIds = string.IsNullOrEmpty(request.ParentId)
- ? new string[] { }
- : new[] { request.ParentId };
+ var parentIdGuid = string.IsNullOrWhiteSpace(request.ParentId) ? (Guid?)null : new Guid(request.ParentId);
string presentationUniqueKey = null;
int? limit = null;
@@ -54,9 +52,11 @@ namespace MediaBrowser.Server.Implementations.TV
IncludeItemTypes = new[] { typeof(Series).Name },
SortOrder = SortOrder.Ascending,
PresentationUniqueKey = presentationUniqueKey,
- Limit = limit
+ Limit = limit,
+ ParentId = parentIdGuid,
+ Recursive = true
- }, parentIds).Cast<Series>();
+ }).Cast<Series>();
// Avoid implicitly captured closure
var episodes = GetNextUpEpisodes(request, user, items);
@@ -107,28 +107,9 @@ namespace MediaBrowser.Server.Implementations.TV
var currentUser = user;
return series
- .AsParallel()
.Select(i => GetNextUp(i, currentUser))
// Include if an episode was found, and either the series is not unwatched or the specific series was requested
.Where(i => i.Item1 != null && (!i.Item3 || !string.IsNullOrWhiteSpace(request.SeriesId)))
- //.OrderByDescending(i =>
- //{
- // var episode = i.Item1;
-
- // var seriesUserData = _userDataManager.GetUserData(user, episode.Series);
-
- // if (seriesUserData.IsFavorite)
- // {
- // return 2;
- // }
-
- // if (seriesUserData.Likes.HasValue)
- // {
- // return seriesUserData.Likes.Value ? 1 : -1;
- // }
-
- // return 0;
- //})
.OrderByDescending(i => i.Item2)
.ThenByDescending(i => i.Item1.PremiereDate ?? DateTime.MinValue)
.Select(i => i.Item1);
@@ -142,52 +123,45 @@ namespace MediaBrowser.Server.Implementations.TV
/// <returns>Task{Episode}.</returns>
private Tuple<Episode, DateTime, bool> GetNextUp(Series series, User user)
{
- // Get them in display order, then reverse
- var allEpisodes = series.GetSeasons(user, true, true)
- .Where(i => !i.IndexNumber.HasValue || i.IndexNumber.Value != 0)
- .SelectMany(i => i.GetEpisodes(user))
- .Reverse()
- .ToList();
-
- Episode lastWatched = null;
- var lastWatchedDate = DateTime.MinValue;
- Episode nextUp = null;
+ var lastWatchedEpisode = _libraryManager.GetItemList(new InternalItemsQuery(user)
+ {
+ AncestorWithPresentationUniqueKey = series.PresentationUniqueKey,
+ IncludeItemTypes = new[] { typeof(Episode).Name },
+ SortBy = new[] { ItemSortBy.SortName },
+ SortOrder = SortOrder.Descending,
+ IsPlayed = true,
+ Limit = 1,
+ IsVirtualItem = false,
+ ParentIndexNumberNotEquals = 0
+
+ }).FirstOrDefault();
+
+ var firstUnwatchedEpisode = _libraryManager.GetItemList(new InternalItemsQuery(user)
+ {
+ AncestorWithPresentationUniqueKey = series.PresentationUniqueKey,
+ IncludeItemTypes = new[] { typeof(Episode).Name },
+ SortBy = new[] { ItemSortBy.SortName },
+ SortOrder = SortOrder.Ascending,
+ Limit = 1,
+ IsPlayed = false,
+ IsVirtualItem = false,
+ ParentIndexNumberNotEquals = 0,
+ MinSortName = lastWatchedEpisode == null ? null : lastWatchedEpisode.SortName
- var includeMissing = user.Configuration.DisplayMissingEpisodes;
+ }).Cast<Episode>().FirstOrDefault();
- // Go back starting with the most recent episodes
- foreach (var episode in allEpisodes)
+ if (lastWatchedEpisode != null)
{
- var userData = _userDataManager.GetUserData(user, episode);
-
- if (userData.Played)
- {
- if (lastWatched != null || nextUp == null)
- {
- break;
- }
+ var userData = _userDataManager.GetUserData(user, lastWatchedEpisode);
- lastWatched = episode;
- lastWatchedDate = userData.LastPlayedDate ?? DateTime.MinValue;
- }
- else
+ if (userData.LastPlayedDate.HasValue)
{
- if (!episode.IsVirtualUnaired && (includeMissing || !episode.IsMissingEpisode))
- {
- nextUp = episode;
- }
+ return new Tuple<Episode, DateTime, bool>(firstUnwatchedEpisode, userData.LastPlayedDate.Value, false);
}
}
- if (lastWatched != null)
- {
- return new Tuple<Episode, DateTime, bool>(nextUp, lastWatchedDate, false);
- }
-
- var firstEpisode = allEpisodes.LastOrDefault(i => !i.IsVirtualUnaired && (includeMissing || !i.IsMissingEpisode) && !i.IsPlayed(user));
-
// Return the first episode
- return new Tuple<Episode, DateTime, bool>(firstEpisode, DateTime.MinValue, true);
+ return new Tuple<Episode, DateTime, bool>(firstUnwatchedEpisode, DateTime.MinValue, true);
}
private QueryResult<BaseItem> GetResult(IEnumerable<BaseItem> items, int? totalRecordLimit, NextUpQuery query)
diff --git a/MediaBrowser.Server.Implementations/Udp/UdpServer.cs b/MediaBrowser.Server.Implementations/Udp/UdpServer.cs
index 40c4deb19..32992b9b2 100644
--- a/MediaBrowser.Server.Implementations/Udp/UdpServer.cs
+++ b/MediaBrowser.Server.Implementations/Udp/UdpServer.cs
@@ -96,20 +96,20 @@ namespace MediaBrowser.Server.Implementations.Udp
private async void RespondToV1Message(string endpoint, Encoding encoding)
{
- var localAddress = _appHost.LocalApiUrl;
+ var localUrl = await _appHost.GetLocalApiUrl().ConfigureAwait(false);
- if (!string.IsNullOrEmpty(localAddress))
+ if (!string.IsNullOrEmpty(localUrl))
{
// This is how we did the old v1 search, so need to strip off the protocol
- var index = localAddress.IndexOf("://", StringComparison.OrdinalIgnoreCase);
+ var index = localUrl.IndexOf("://", StringComparison.OrdinalIgnoreCase);
if (index != -1)
{
- localAddress = localAddress.Substring(index + 3);
+ localUrl = localUrl.Substring(index + 3);
}
// Send a response back with our ip address and port
- var response = String.Format("MediaBrowserServer|{0}", localAddress);
+ var response = String.Format("MediaBrowserServer|{0}", localUrl);
await SendAsync(Encoding.UTF8.GetBytes(response), endpoint);
}
@@ -121,7 +121,7 @@ namespace MediaBrowser.Server.Implementations.Udp
private async void RespondToV2Message(string endpoint, Encoding encoding)
{
- var localUrl = _appHost.LocalApiUrl;
+ var localUrl = await _appHost.GetLocalApiUrl().ConfigureAwait(false);
if (!string.IsNullOrEmpty(localUrl))
{
diff --git a/MediaBrowser.Server.Implementations/packages.config b/MediaBrowser.Server.Implementations/packages.config
index b877d41a8..9592ecb16 100644
--- a/MediaBrowser.Server.Implementations/packages.config
+++ b/MediaBrowser.Server.Implementations/packages.config
@@ -1,13 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="CommonIO" version="1.0.0.9" targetFramework="net45" />
- <package id="Emby.XmlTv" version="1.0.0.48" targetFramework="net45" />
- <package id="ini-parser" version="2.2.4" targetFramework="net45" />
+ <package id="Emby.XmlTv" version="1.0.0.55" targetFramework="net45" />
+ <package id="ini-parser" version="2.3.0" targetFramework="net45" />
<package id="Interfaces.IO" version="1.0.0.5" targetFramework="net45" />
- <package id="MediaBrowser.Naming" version="1.0.0.50" targetFramework="net45" />
+ <package id="MediaBrowser.Naming" version="1.0.0.52" targetFramework="net45" />
<package id="Mono.Nat" version="1.2.24.0" targetFramework="net45" />
<package id="morelinq" version="1.4.0" targetFramework="net45" />
<package id="Patterns.Logging" version="1.0.0.2" targetFramework="net45" />
- <package id="SimpleInjector" version="3.1.4" targetFramework="net45" />
+ <package id="SimpleInjector" version="3.2.0" targetFramework="net45" />
<package id="SocketHttpListener" version="1.0.0.30" targetFramework="net45" />
</packages> \ No newline at end of file
diff --git a/MediaBrowser.Server.Mac/Emby.Server.Mac.csproj b/MediaBrowser.Server.Mac/Emby.Server.Mac.csproj
index aae9804fb..6a1c52007 100644
--- a/MediaBrowser.Server.Mac/Emby.Server.Mac.csproj
+++ b/MediaBrowser.Server.Mac/Emby.Server.Mac.csproj
@@ -14,6 +14,7 @@
</ReleaseVersion>
<StartupObject>MediaBrowser.Server.Mac.MainClass</StartupObject>
<Description>A personal media server</Description>
+ <TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
@@ -33,6 +34,10 @@
<PackageSigningKey>Developer ID Installer</PackageSigningKey>
<UseRefCounting>false</UseRefCounting>
<Profiling>false</Profiling>
+ <HttpClientHandler>HttpClientHandler</HttpClientHandler>
+ <TlsProvider>Legacy</TlsProvider>
+ <LinkMode>None</LinkMode>
+ <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<Optimize>true</Optimize>
@@ -49,6 +54,10 @@
<PackageSigningKey>Developer ID Installer</PackageSigningKey>
<UseRefCounting>false</UseRefCounting>
<Profiling>false</Profiling>
+ <HttpClientHandler>HttpClientHandler</HttpClientHandler>
+ <TlsProvider>Default</TlsProvider>
+ <LinkMode>None</LinkMode>
+ <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'AppStore|AnyCPU' ">
<DebugType>full</DebugType>
@@ -66,6 +75,10 @@
<EnablePackageSigning>false</EnablePackageSigning>
<UseRefCounting>false</UseRefCounting>
<Profiling>false</Profiling>
+ <HttpClientHandler>HttpClientHandler</HttpClientHandler>
+ <TlsProvider>Default</TlsProvider>
+ <LinkMode>None</LinkMode>
+ <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
<ItemGroup>
<Reference Include="System" />
@@ -77,10 +90,6 @@
<Reference Include="Mono.Posix">
<HintPath>..\packages\Mono.Posix.4.0.0.0\lib\net40\Mono.Posix.dll</HintPath>
</Reference>
- <Reference Include="Mono.Security">
- <HintPath>..\ThirdParty\Mono.Security\Mono.Security.dll</HintPath>
- <Private>False</Private>
- </Reference>
<Reference Include="Patterns.Logging">
<HintPath>..\packages\Patterns.Logging.1.0.0.2\lib\portable-net45+sl4+wp71+win8+wpa81\Patterns.Logging.dll</HintPath>
</Reference>
@@ -95,7 +104,7 @@
<ItemGroup>
<Folder Include="Resources\" />
<Folder Include="Native\" />
- <Folder Include="Resources\swagger-ui\" />
+ <Folder Include="Security\" />
</ItemGroup>
<ItemGroup>
<Compile Include="AppDelegate.cs" />
@@ -117,7 +126,58 @@
<Compile Include="..\MediaBrowser.Server.Mono\Networking\CertificateGenerator.cs">
<Link>Native\CertificateGenerator.cs</Link>
</Compile>
- <Compile Include="Native\SqliteExtensions.cs" />
+ <Compile Include="Native\DbConnector.cs" />
+ <Compile Include="..\MediaBrowser.Server.Implementations\Persistence\SqliteExtensions.cs">
+ <Link>Native\SqliteExtensions.cs</Link>
+ </Compile>
+ <Compile Include="..\MediaBrowser.Server.Mono\Security\ASN1.cs">
+ <Link>Security\ASN1.cs</Link>
+ </Compile>
+ <Compile Include="..\MediaBrowser.Server.Mono\Security\ASN1Convert.cs">
+ <Link>Security\ASN1Convert.cs</Link>
+ </Compile>
+ <Compile Include="..\MediaBrowser.Server.Mono\Security\BitConverterLE.cs">
+ <Link>Security\BitConverterLE.cs</Link>
+ </Compile>
+ <Compile Include="..\MediaBrowser.Server.Mono\Security\CryptoConvert.cs">
+ <Link>Security\CryptoConvert.cs</Link>
+ </Compile>
+ <Compile Include="..\MediaBrowser.Server.Mono\Security\PKCS1.cs">
+ <Link>Security\PKCS1.cs</Link>
+ </Compile>
+ <Compile Include="..\MediaBrowser.Server.Mono\Security\PKCS12.cs">
+ <Link>Security\PKCS12.cs</Link>
+ </Compile>
+ <Compile Include="..\MediaBrowser.Server.Mono\Security\PKCS7.cs">
+ <Link>Security\PKCS7.cs</Link>
+ </Compile>
+ <Compile Include="..\MediaBrowser.Server.Mono\Security\PKCS8.cs">
+ <Link>Security\PKCS8.cs</Link>
+ </Compile>
+ <Compile Include="..\MediaBrowser.Server.Mono\Security\X501Name.cs">
+ <Link>Security\X501Name.cs</Link>
+ </Compile>
+ <Compile Include="..\MediaBrowser.Server.Mono\Security\X509Builder.cs">
+ <Link>Security\X509Builder.cs</Link>
+ </Compile>
+ <Compile Include="..\MediaBrowser.Server.Mono\Security\X509Certificate.cs">
+ <Link>Security\X509Certificate.cs</Link>
+ </Compile>
+ <Compile Include="..\MediaBrowser.Server.Mono\Security\X509CertificateBuilder.cs">
+ <Link>Security\X509CertificateBuilder.cs</Link>
+ </Compile>
+ <Compile Include="..\MediaBrowser.Server.Mono\Security\X509CertificateCollection.cs">
+ <Link>Security\X509CertificateCollection.cs</Link>
+ </Compile>
+ <Compile Include="..\MediaBrowser.Server.Mono\Security\X509Extension.cs">
+ <Link>Security\X509Extension.cs</Link>
+ </Compile>
+ <Compile Include="..\MediaBrowser.Server.Mono\Security\X509Extensions.cs">
+ <Link>Security\X509Extensions.cs</Link>
+ </Compile>
+ <Compile Include="..\MediaBrowser.Server.Mono\Security\X520Attributes.cs">
+ <Link>Security\X520Attributes.cs</Link>
+ </Compile>
</ItemGroup>
<ItemGroup>
<InterfaceDefinition Include="MainMenu.xib" />
@@ -202,18 +262,65 @@
</NativeReference>
</ItemGroup>
<ItemGroup>
+ <BundleResource Include="Resources\appicon.icns" />
+ <BundleResource Include="Resources\MediaBrowser.Server.Mac\Images.xcassets\AppIcon.appiconset\Contents.json" />
<BundleResource Include="..\ThirdParty\ServiceStack\swagger-ui\index.html">
<Link>Resources\swagger-ui\index.html</Link>
</BundleResource>
+ <BundleResource Include="..\ThirdParty\ServiceStack\swagger-ui\o2c.html">
+ <Link>Resources\swagger-ui\o2c.html</Link>
+ </BundleResource>
+ <BundleResource Include="..\ThirdParty\ServiceStack\swagger-ui\patch.js">
+ <Link>Resources\swagger-ui\patch.js</Link>
+ </BundleResource>
<BundleResource Include="..\ThirdParty\ServiceStack\swagger-ui\swagger-ui.js">
<Link>Resources\swagger-ui\swagger-ui.js</Link>
</BundleResource>
<BundleResource Include="..\ThirdParty\ServiceStack\swagger-ui\swagger-ui.min.js">
<Link>Resources\swagger-ui\swagger-ui.min.js</Link>
</BundleResource>
+ <BundleResource Include="..\ThirdParty\ServiceStack\swagger-ui\css\reset.css">
+ <Link>Resources\swagger-ui\css\reset.css</Link>
+ </BundleResource>
<BundleResource Include="..\ThirdParty\ServiceStack\swagger-ui\css\screen.css">
<Link>Resources\swagger-ui\css\screen.css</Link>
</BundleResource>
+ <BundleResource Include="..\ThirdParty\ServiceStack\swagger-ui\css\typography.css">
+ <Link>Resources\swagger-ui\css\typography.css</Link>
+ </BundleResource>
+ <BundleResource Include="..\ThirdParty\ServiceStack\swagger-ui\fonts\droid-sans-v6-latin-700.eot">
+ <Link>Resources\swagger-ui\fonts\droid-sans-v6-latin-700.eot</Link>
+ </BundleResource>
+ <BundleResource Include="..\ThirdParty\ServiceStack\swagger-ui\fonts\droid-sans-v6-latin-700.svg">
+ <Link>Resources\swagger-ui\fonts\droid-sans-v6-latin-700.svg</Link>
+ </BundleResource>
+ <BundleResource Include="..\ThirdParty\ServiceStack\swagger-ui\fonts\droid-sans-v6-latin-700.ttf">
+ <Link>Resources\swagger-ui\fonts\droid-sans-v6-latin-700.ttf</Link>
+ </BundleResource>
+ <BundleResource Include="..\ThirdParty\ServiceStack\swagger-ui\fonts\droid-sans-v6-latin-700.woff">
+ <Link>Resources\swagger-ui\fonts\droid-sans-v6-latin-700.woff</Link>
+ </BundleResource>
+ <BundleResource Include="..\ThirdParty\ServiceStack\swagger-ui\fonts\droid-sans-v6-latin-700.woff2">
+ <Link>Resources\swagger-ui\fonts\droid-sans-v6-latin-700.woff2</Link>
+ </BundleResource>
+ <BundleResource Include="..\ThirdParty\ServiceStack\swagger-ui\fonts\droid-sans-v6-latin-regular.eot">
+ <Link>Resources\swagger-ui\fonts\droid-sans-v6-latin-regular.eot</Link>
+ </BundleResource>
+ <BundleResource Include="..\ThirdParty\ServiceStack\swagger-ui\fonts\droid-sans-v6-latin-regular.svg">
+ <Link>Resources\swagger-ui\fonts\droid-sans-v6-latin-regular.svg</Link>
+ </BundleResource>
+ <BundleResource Include="..\ThirdParty\ServiceStack\swagger-ui\fonts\droid-sans-v6-latin-regular.ttf">
+ <Link>Resources\swagger-ui\fonts\droid-sans-v6-latin-regular.ttf</Link>
+ </BundleResource>
+ <BundleResource Include="..\ThirdParty\ServiceStack\swagger-ui\fonts\droid-sans-v6-latin-regular.woff">
+ <Link>Resources\swagger-ui\fonts\droid-sans-v6-latin-regular.woff</Link>
+ </BundleResource>
+ <BundleResource Include="..\ThirdParty\ServiceStack\swagger-ui\fonts\droid-sans-v6-latin-regular.woff2">
+ <Link>Resources\swagger-ui\fonts\droid-sans-v6-latin-regular.woff2</Link>
+ </BundleResource>
+ <BundleResource Include="..\ThirdParty\ServiceStack\swagger-ui\images\explorer_icons.png">
+ <Link>Resources\swagger-ui\images\explorer_icons.png</Link>
+ </BundleResource>
<BundleResource Include="..\ThirdParty\ServiceStack\swagger-ui\images\logo_small.png">
<Link>Resources\swagger-ui\images\logo_small.png</Link>
</BundleResource>
@@ -229,8 +336,8 @@
<BundleResource Include="..\ThirdParty\ServiceStack\swagger-ui\lib\backbone-min.js">
<Link>Resources\swagger-ui\lib\backbone-min.js</Link>
</BundleResource>
- <BundleResource Include="..\ThirdParty\ServiceStack\swagger-ui\lib\handlebars-1.0.0.js">
- <Link>Resources\swagger-ui\lib\handlebars-1.0.0.js</Link>
+ <BundleResource Include="..\ThirdParty\ServiceStack\swagger-ui\lib\handlebars-2.0.0.js">
+ <Link>Resources\swagger-ui\lib\handlebars-2.0.0.js</Link>
</BundleResource>
<BundleResource Include="..\ThirdParty\ServiceStack\swagger-ui\lib\highlight.7.3.pack.js">
<Link>Resources\swagger-ui\lib\highlight.7.3.pack.js</Link>
@@ -247,11 +354,17 @@
<BundleResource Include="..\ThirdParty\ServiceStack\swagger-ui\lib\jquery.wiggle.min.js">
<Link>Resources\swagger-ui\lib\jquery.wiggle.min.js</Link>
</BundleResource>
+ <BundleResource Include="..\ThirdParty\ServiceStack\swagger-ui\lib\marked.js">
+ <Link>Resources\swagger-ui\lib\marked.js</Link>
+ </BundleResource>
<BundleResource Include="..\ThirdParty\ServiceStack\swagger-ui\lib\shred.bundle.js">
<Link>Resources\swagger-ui\lib\shred.bundle.js</Link>
</BundleResource>
- <BundleResource Include="..\ThirdParty\ServiceStack\swagger-ui\lib\swagger.js">
- <Link>Resources\swagger-ui\lib\swagger.js</Link>
+ <BundleResource Include="..\ThirdParty\ServiceStack\swagger-ui\lib\swagger-client.js">
+ <Link>Resources\swagger-ui\lib\swagger-client.js</Link>
+ </BundleResource>
+ <BundleResource Include="..\ThirdParty\ServiceStack\swagger-ui\lib\swagger-oauth.js">
+ <Link>Resources\swagger-ui\lib\swagger-oauth.js</Link>
</BundleResource>
<BundleResource Include="..\ThirdParty\ServiceStack\swagger-ui\lib\underscore-min.js">
<Link>Resources\swagger-ui\lib\underscore-min.js</Link>
@@ -259,8 +372,6 @@
<BundleResource Include="..\ThirdParty\ServiceStack\swagger-ui\lib\shred\content.js">
<Link>Resources\swagger-ui\lib\shred\content.js</Link>
</BundleResource>
- <BundleResource Include="Resources\appicon.icns" />
- <BundleResource Include="Resources\MediaBrowser.Server.Mac\Images.xcassets\AppIcon.appiconset\Contents.json" />
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\about.html">
<Link>Resources\dashboard-ui\about.html</Link>
</BundleResource>
@@ -288,9 +399,6 @@
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\cinemamodeconfiguration.html">
<Link>Resources\dashboard-ui\cinemamodeconfiguration.html</Link>
</BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\collections.html">
- <Link>Resources\dashboard-ui\collections.html</Link>
- </BundleResource>
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\connectlogin.html">
<Link>Resources\dashboard-ui\connectlogin.html</Link>
</BundleResource>
@@ -366,12 +474,12 @@
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\itemlist.html">
<Link>Resources\dashboard-ui\itemlist.html</Link>
</BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\kids.html">
- <Link>Resources\dashboard-ui\kids.html</Link>
- </BundleResource>
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\library.html">
<Link>Resources\dashboard-ui\library.html</Link>
</BundleResource>
+ <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\librarydisplay.html">
+ <Link>Resources\dashboard-ui\librarydisplay.html</Link>
+ </BundleResource>
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\librarypathmapping.html">
<Link>Resources\dashboard-ui\librarypathmapping.html</Link>
</BundleResource>
@@ -552,6 +660,9 @@
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\wizardagreement.html">
<Link>Resources\dashboard-ui\wizardagreement.html</Link>
</BundleResource>
+ <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\wizardcomponents.html">
+ <Link>Resources\dashboard-ui\wizardcomponents.html</Link>
+ </BundleResource>
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\wizardfinish.html">
<Link>Resources\dashboard-ui\wizardfinish.html</Link>
</BundleResource>
@@ -1365,12 +1476,21 @@
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\emby-webcomponents\alert\nativealert.js">
<Link>Resources\dashboard-ui\bower_components\emby-webcomponents\alert\nativealert.js</Link>
</BundleResource>
+ <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\emby-webcomponents\alphapicker\alphapicker.js">
+ <Link>Resources\dashboard-ui\bower_components\emby-webcomponents\alphapicker\alphapicker.js</Link>
+ </BundleResource>
+ <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\emby-webcomponents\alphapicker\style.css">
+ <Link>Resources\dashboard-ui\bower_components\emby-webcomponents\alphapicker\style.css</Link>
+ </BundleResource>
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\emby-webcomponents\backdrop\backdrop.js">
<Link>Resources\dashboard-ui\bower_components\emby-webcomponents\backdrop\backdrop.js</Link>
</BundleResource>
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\emby-webcomponents\backdrop\style.css">
<Link>Resources\dashboard-ui\bower_components\emby-webcomponents\backdrop\style.css</Link>
</BundleResource>
+ <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\emby-webcomponents\collectioneditor\collectioneditor.js">
+ <Link>Resources\dashboard-ui\bower_components\emby-webcomponents\collectioneditor\collectioneditor.js</Link>
+ </BundleResource>
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\emby-webcomponents\confirm\confirm.js">
<Link>Resources\dashboard-ui\bower_components\emby-webcomponents\confirm\confirm.js</Link>
</BundleResource>
@@ -1383,6 +1503,45 @@
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\emby-webcomponents\dialoghelper\dialoghelper.js">
<Link>Resources\dashboard-ui\bower_components\emby-webcomponents\dialoghelper\dialoghelper.js</Link>
</BundleResource>
+ <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\emby-webcomponents\emby-button\emby-button.css">
+ <Link>Resources\dashboard-ui\bower_components\emby-webcomponents\emby-button\emby-button.css</Link>
+ </BundleResource>
+ <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\emby-webcomponents\emby-button\emby-button.js">
+ <Link>Resources\dashboard-ui\bower_components\emby-webcomponents\emby-button\emby-button.js</Link>
+ </BundleResource>
+ <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\emby-webcomponents\emby-button\paper-icon-button-light.js">
+ <Link>Resources\dashboard-ui\bower_components\emby-webcomponents\emby-button\paper-icon-button-light.js</Link>
+ </BundleResource>
+ <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\emby-webcomponents\emby-checkbox\emby-checkbox.css">
+ <Link>Resources\dashboard-ui\bower_components\emby-webcomponents\emby-checkbox\emby-checkbox.css</Link>
+ </BundleResource>
+ <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\emby-webcomponents\emby-checkbox\emby-checkbox.js">
+ <Link>Resources\dashboard-ui\bower_components\emby-webcomponents\emby-checkbox\emby-checkbox.js</Link>
+ </BundleResource>
+ <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\emby-webcomponents\emby-input\emby-input.css">
+ <Link>Resources\dashboard-ui\bower_components\emby-webcomponents\emby-input\emby-input.css</Link>
+ </BundleResource>
+ <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\emby-webcomponents\emby-input\emby-input.js">
+ <Link>Resources\dashboard-ui\bower_components\emby-webcomponents\emby-input\emby-input.js</Link>
+ </BundleResource>
+ <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\emby-webcomponents\emby-select\emby-select.css">
+ <Link>Resources\dashboard-ui\bower_components\emby-webcomponents\emby-select\emby-select.css</Link>
+ </BundleResource>
+ <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\emby-webcomponents\emby-select\emby-select.js">
+ <Link>Resources\dashboard-ui\bower_components\emby-webcomponents\emby-select\emby-select.js</Link>
+ </BundleResource>
+ <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\emby-webcomponents\emby-slider\emby-slider.css">
+ <Link>Resources\dashboard-ui\bower_components\emby-webcomponents\emby-slider\emby-slider.css</Link>
+ </BundleResource>
+ <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\emby-webcomponents\emby-slider\emby-slider.js">
+ <Link>Resources\dashboard-ui\bower_components\emby-webcomponents\emby-slider\emby-slider.js</Link>
+ </BundleResource>
+ <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\emby-webcomponents\emby-textarea\emby-textarea.css">
+ <Link>Resources\dashboard-ui\bower_components\emby-webcomponents\emby-textarea\emby-textarea.css</Link>
+ </BundleResource>
+ <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\emby-webcomponents\emby-textarea\emby-textarea.js">
+ <Link>Resources\dashboard-ui\bower_components\emby-webcomponents\emby-textarea\emby-textarea.js</Link>
+ </BundleResource>
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\emby-webcomponents\fonts\material-icons\2fcrYFNaTjcS6g4U3t-Y5ZjZjT5FdEJ140U2DJYC3mY.woff2">
<Link>Resources\dashboard-ui\bower_components\emby-webcomponents\fonts\material-icons\2fcrYFNaTjcS6g4U3t-Y5ZjZjT5FdEJ140U2DJYC3mY.woff2</Link>
</BundleResource>
@@ -1581,12 +1740,6 @@
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\emby-webcomponents\guide\tvguide.template.html">
<Link>Resources\dashboard-ui\bower_components\emby-webcomponents\guide\tvguide.template.html</Link>
</BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\emby-webcomponents\icons\mediainfo.html">
- <Link>Resources\dashboard-ui\bower_components\emby-webcomponents\icons\mediainfo.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\emby-webcomponents\icons\nav.html">
- <Link>Resources\dashboard-ui\bower_components\emby-webcomponents\icons\nav.html</Link>
- </BundleResource>
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\emby-webcomponents\images\basicimagefetcher.js">
<Link>Resources\dashboard-ui\bower_components\emby-webcomponents\images\basicimagefetcher.js</Link>
</BundleResource>
@@ -1596,9 +1749,24 @@
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\emby-webcomponents\images\persistentimagefetcher.js">
<Link>Resources\dashboard-ui\bower_components\emby-webcomponents\images\persistentimagefetcher.js</Link>
</BundleResource>
+ <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\emby-webcomponents\indicators\indicators.css">
+ <Link>Resources\dashboard-ui\bower_components\emby-webcomponents\indicators\indicators.css</Link>
+ </BundleResource>
+ <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\emby-webcomponents\indicators\indicators.js">
+ <Link>Resources\dashboard-ui\bower_components\emby-webcomponents\indicators\indicators.js</Link>
+ </BundleResource>
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\emby-webcomponents\input\api.js">
<Link>Resources\dashboard-ui\bower_components\emby-webcomponents\input\api.js</Link>
</BundleResource>
+ <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\emby-webcomponents\listview\listview.css">
+ <Link>Resources\dashboard-ui\bower_components\emby-webcomponents\listview\listview.css</Link>
+ </BundleResource>
+ <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\emby-webcomponents\listview\listview.js">
+ <Link>Resources\dashboard-ui\bower_components\emby-webcomponents\listview\listview.js</Link>
+ </BundleResource>
+ <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\emby-webcomponents\loading\loading-lite.css">
+ <Link>Resources\dashboard-ui\bower_components\emby-webcomponents\loading\loading-lite.css</Link>
+ </BundleResource>
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\emby-webcomponents\loading\loading-lite.js">
<Link>Resources\dashboard-ui\bower_components\emby-webcomponents\loading\loading-lite.js</Link>
</BundleResource>
@@ -1626,6 +1794,9 @@
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\emby-webcomponents\page.js\page.js">
<Link>Resources\dashboard-ui\bower_components\emby-webcomponents\page.js\page.js</Link>
</BundleResource>
+ <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\emby-webcomponents\playlisteditor\playlisteditor.js">
+ <Link>Resources\dashboard-ui\bower_components\emby-webcomponents\playlisteditor\playlisteditor.js</Link>
+ </BundleResource>
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\emby-webcomponents\prompt\nativeprompt.js">
<Link>Resources\dashboard-ui\bower_components\emby-webcomponents\prompt\nativeprompt.js</Link>
</BundleResource>
@@ -1650,6 +1821,9 @@
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\emby-webcomponents\recordingcreator\recordingeditor.template.html">
<Link>Resources\dashboard-ui\bower_components\emby-webcomponents\recordingcreator\recordingeditor.template.html</Link>
</BundleResource>
+ <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\emby-webcomponents\refreshdialog\refreshdialog.js">
+ <Link>Resources\dashboard-ui\bower_components\emby-webcomponents\refreshdialog\refreshdialog.js</Link>
+ </BundleResource>
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\emby-webcomponents\require\requirecss.js">
<Link>Resources\dashboard-ui\bower_components\emby-webcomponents\require\requirecss.js</Link>
</BundleResource>
@@ -1692,9 +1866,6 @@
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\emby-webcomponents\sharing\social-share-kit-1.0.4\dist\js\social-share-kit.min.js">
<Link>Resources\dashboard-ui\bower_components\emby-webcomponents\sharing\social-share-kit-1.0.4\dist\js\social-share-kit.min.js</Link>
</BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\emby-webcomponents\slideshow\icons.html">
- <Link>Resources\dashboard-ui\bower_components\emby-webcomponents\slideshow\icons.html</Link>
- </BundleResource>
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\emby-webcomponents\slideshow\slideshow.js">
<Link>Resources\dashboard-ui\bower_components\emby-webcomponents\slideshow\slideshow.js</Link>
</BundleResource>
@@ -1704,6 +1875,9 @@
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\emby-webcomponents\strings\da.json">
<Link>Resources\dashboard-ui\bower_components\emby-webcomponents\strings\da.json</Link>
</BundleResource>
+ <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\emby-webcomponents\strings\de.json">
+ <Link>Resources\dashboard-ui\bower_components\emby-webcomponents\strings\de.json</Link>
+ </BundleResource>
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\emby-webcomponents\strings\en-US.json">
<Link>Resources\dashboard-ui\bower_components\emby-webcomponents\strings\en-US.json</Link>
</BundleResource>
@@ -1719,12 +1893,33 @@
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\emby-webcomponents\strings\nl.json">
<Link>Resources\dashboard-ui\bower_components\emby-webcomponents\strings\nl.json</Link>
</BundleResource>
+ <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\emby-webcomponents\strings\pt-BR.json">
+ <Link>Resources\dashboard-ui\bower_components\emby-webcomponents\strings\pt-BR.json</Link>
+ </BundleResource>
+ <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\emby-webcomponents\strings\pt-PT.json">
+ <Link>Resources\dashboard-ui\bower_components\emby-webcomponents\strings\pt-PT.json</Link>
+ </BundleResource>
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\emby-webcomponents\strings\ru.json">
<Link>Resources\dashboard-ui\bower_components\emby-webcomponents\strings\ru.json</Link>
</BundleResource>
+ <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\emby-webcomponents\subtitleeditor\subtitleeditor.css">
+ <Link>Resources\dashboard-ui\bower_components\emby-webcomponents\subtitleeditor\subtitleeditor.css</Link>
+ </BundleResource>
+ <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\emby-webcomponents\subtitleeditor\subtitleeditor.js">
+ <Link>Resources\dashboard-ui\bower_components\emby-webcomponents\subtitleeditor\subtitleeditor.js</Link>
+ </BundleResource>
+ <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\emby-webcomponents\subtitleeditor\subtitleeditor.template.html">
+ <Link>Resources\dashboard-ui\bower_components\emby-webcomponents\subtitleeditor\subtitleeditor.template.html</Link>
+ </BundleResource>
+ <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\emby-webcomponents\toast\toast.css">
+ <Link>Resources\dashboard-ui\bower_components\emby-webcomponents\toast\toast.css</Link>
+ </BundleResource>
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\emby-webcomponents\toast\toast.js">
<Link>Resources\dashboard-ui\bower_components\emby-webcomponents\toast\toast.js</Link>
</BundleResource>
+ <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\emby-webcomponents\viewmanager\viewcontainer-lite.css">
+ <Link>Resources\dashboard-ui\bower_components\emby-webcomponents\viewmanager\viewcontainer-lite.css</Link>
+ </BundleResource>
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\emby-webcomponents\viewmanager\viewcontainer-lite.js">
<Link>Resources\dashboard-ui\bower_components\emby-webcomponents\viewmanager\viewcontainer-lite.js</Link>
</BundleResource>
@@ -1974,6 +2169,9 @@
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\hls.js\src\controller\cap-level-controller.js">
<Link>Resources\dashboard-ui\bower_components\hls.js\src\controller\cap-level-controller.js</Link>
</BundleResource>
+ <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\hls.js\src\controller\ewma-bandwidth-estimator.js">
+ <Link>Resources\dashboard-ui\bower_components\hls.js\src\controller\ewma-bandwidth-estimator.js</Link>
+ </BundleResource>
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\hls.js\src\controller\fps-controller.js">
<Link>Resources\dashboard-ui\bower_components\hls.js\src\controller\fps-controller.js</Link>
</BundleResource>
@@ -2055,6 +2253,9 @@
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\hls.js\src\utils\cea-708-interpreter.js">
<Link>Resources\dashboard-ui\bower_components\hls.js\src\utils\cea-708-interpreter.js</Link>
</BundleResource>
+ <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\hls.js\src\utils\ewma.js">
+ <Link>Resources\dashboard-ui\bower_components\hls.js\src\utils\ewma.js</Link>
+ </BundleResource>
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\hls.js\src\utils\hex.js">
<Link>Resources\dashboard-ui\bower_components\hls.js\src\utils\hex.js</Link>
</BundleResource>
@@ -2640,105 +2841,6 @@
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-input\test\letters-only.html">
<Link>Resources\dashboard-ui\bower_components\iron-input\test\letters-only.html</Link>
</BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-list\.bower.json">
- <Link>Resources\dashboard-ui\bower_components\iron-list\.bower.json</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-list\.gitignore">
- <Link>Resources\dashboard-ui\bower_components\iron-list\.gitignore</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-list\.travis.yml">
- <Link>Resources\dashboard-ui\bower_components\iron-list\.travis.yml</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-list\CONTRIBUTING.md">
- <Link>Resources\dashboard-ui\bower_components\iron-list\CONTRIBUTING.md</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-list\README.md">
- <Link>Resources\dashboard-ui\bower_components\iron-list\README.md</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-list\bower.json">
- <Link>Resources\dashboard-ui\bower_components\iron-list\bower.json</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-list\index.html">
- <Link>Resources\dashboard-ui\bower_components\iron-list\index.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-list\iron-list.html">
- <Link>Resources\dashboard-ui\bower_components\iron-list\iron-list.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-list\.github\ISSUE_TEMPLATE.md">
- <Link>Resources\dashboard-ui\bower_components\iron-list\.github\ISSUE_TEMPLATE.md</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-list\demo\basic.html">
- <Link>Resources\dashboard-ui\bower_components\iron-list\demo\basic.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-list\demo\collapse.html">
- <Link>Resources\dashboard-ui\bower_components\iron-list\demo\collapse.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-list\demo\grid.html">
- <Link>Resources\dashboard-ui\bower_components\iron-list\demo\grid.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-list\demo\index.html">
- <Link>Resources\dashboard-ui\bower_components\iron-list\demo\index.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-list\demo\scroll-threshold.html">
- <Link>Resources\dashboard-ui\bower_components\iron-list\demo\scroll-threshold.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-list\demo\selection.html">
- <Link>Resources\dashboard-ui\bower_components\iron-list\demo\selection.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-list\demo\data\contacts.json">
- <Link>Resources\dashboard-ui\bower_components\iron-list\demo\data\contacts.json</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-list\test\basic.html">
- <Link>Resources\dashboard-ui\bower_components\iron-list\test\basic.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-list\test\different-heights.html">
- <Link>Resources\dashboard-ui\bower_components\iron-list\test\different-heights.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-list\test\dynamic-item-size.html">
- <Link>Resources\dashboard-ui\bower_components\iron-list\test\dynamic-item-size.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-list\test\focus.html">
- <Link>Resources\dashboard-ui\bower_components\iron-list\test\focus.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-list\test\grid.html">
- <Link>Resources\dashboard-ui\bower_components\iron-list\test\grid.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-list\test\helpers.html">
- <Link>Resources\dashboard-ui\bower_components\iron-list\test\helpers.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-list\test\hidden-list.html">
- <Link>Resources\dashboard-ui\bower_components\iron-list\test\hidden-list.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-list\test\index.html">
- <Link>Resources\dashboard-ui\bower_components\iron-list\test\index.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-list\test\mutations.html">
- <Link>Resources\dashboard-ui\bower_components\iron-list\test\mutations.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-list\test\physical-count.html">
- <Link>Resources\dashboard-ui\bower_components\iron-list\test\physical-count.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-list\test\selection.html">
- <Link>Resources\dashboard-ui\bower_components\iron-list\test\selection.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-list\test\x-grid.html">
- <Link>Resources\dashboard-ui\bower_components\iron-list\test\x-grid.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-list\test\x-list.html">
- <Link>Resources\dashboard-ui\bower_components\iron-list\test\x-list.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-list\test\smoke\avg-worst-case.html">
- <Link>Resources\dashboard-ui\bower_components\iron-list\test\smoke\avg-worst-case.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-list\test\smoke\dummy-data.html">
- <Link>Resources\dashboard-ui\bower_components\iron-list\test\smoke\dummy-data.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-list\test\smoke\index.html">
- <Link>Resources\dashboard-ui\bower_components\iron-list\test\smoke\index.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-list\test\smoke\physical-count.html">
- <Link>Resources\dashboard-ui\bower_components\iron-list\test\smoke\physical-count.html</Link>
- </BundleResource>
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-location\.bower.json">
<Link>Resources\dashboard-ui\bower_components\iron-location\.bower.json</Link>
</BundleResource>
@@ -2796,42 +2898,6 @@
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-location\test\redirection.html">
<Link>Resources\dashboard-ui\bower_components\iron-location\test\redirection.html</Link>
</BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-media-query\.bower.json">
- <Link>Resources\dashboard-ui\bower_components\iron-media-query\.bower.json</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-media-query\.gitignore">
- <Link>Resources\dashboard-ui\bower_components\iron-media-query\.gitignore</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-media-query\.travis.yml">
- <Link>Resources\dashboard-ui\bower_components\iron-media-query\.travis.yml</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-media-query\CONTRIBUTING.md">
- <Link>Resources\dashboard-ui\bower_components\iron-media-query\CONTRIBUTING.md</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-media-query\README.md">
- <Link>Resources\dashboard-ui\bower_components\iron-media-query\README.md</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-media-query\bower.json">
- <Link>Resources\dashboard-ui\bower_components\iron-media-query\bower.json</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-media-query\hero.svg">
- <Link>Resources\dashboard-ui\bower_components\iron-media-query\hero.svg</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-media-query\index.html">
- <Link>Resources\dashboard-ui\bower_components\iron-media-query\index.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-media-query\iron-media-query.html">
- <Link>Resources\dashboard-ui\bower_components\iron-media-query\iron-media-query.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-media-query\demo\index.html">
- <Link>Resources\dashboard-ui\bower_components\iron-media-query\demo\index.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-media-query\test\basic.html">
- <Link>Resources\dashboard-ui\bower_components\iron-media-query\test\basic.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-media-query\test\index.html">
- <Link>Resources\dashboard-ui\bower_components\iron-media-query\test\index.html</Link>
- </BundleResource>
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-menu-behavior\.bower.json">
<Link>Resources\dashboard-ui\bower_components\iron-menu-behavior\.bower.json</Link>
</BundleResource>
@@ -2970,6 +3036,12 @@
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-range-behavior\.gitignore">
<Link>Resources\dashboard-ui\bower_components\iron-range-behavior\.gitignore</Link>
</BundleResource>
+ <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-range-behavior\.travis.yml">
+ <Link>Resources\dashboard-ui\bower_components\iron-range-behavior\.travis.yml</Link>
+ </BundleResource>
+ <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-range-behavior\CONTRIBUTING.md">
+ <Link>Resources\dashboard-ui\bower_components\iron-range-behavior\CONTRIBUTING.md</Link>
+ </BundleResource>
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-range-behavior\README.md">
<Link>Resources\dashboard-ui\bower_components\iron-range-behavior\README.md</Link>
</BundleResource>
@@ -2982,6 +3054,9 @@
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-range-behavior\iron-range-behavior.html">
<Link>Resources\dashboard-ui\bower_components\iron-range-behavior\iron-range-behavior.html</Link>
</BundleResource>
+ <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-range-behavior\.github\ISSUE_TEMPLATE.md">
+ <Link>Resources\dashboard-ui\bower_components\iron-range-behavior\.github\ISSUE_TEMPLATE.md</Link>
+ </BundleResource>
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-range-behavior\demo\index.html">
<Link>Resources\dashboard-ui\bower_components\iron-range-behavior\demo\index.html</Link>
</BundleResource>
@@ -3285,6 +3360,9 @@
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\jquery\bower.json">
<Link>Resources\dashboard-ui\bower_components\jquery\bower.json</Link>
</BundleResource>
+ <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\jquery\dist\core.js">
+ <Link>Resources\dashboard-ui\bower_components\jquery\dist\core.js</Link>
+ </BundleResource>
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\jquery\dist\jquery.js">
<Link>Resources\dashboard-ui\bower_components\jquery\dist\jquery.js</Link>
</BundleResource>
@@ -3303,17 +3381,17 @@
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\jquery\dist\jquery.slim.min.map">
<Link>Resources\dashboard-ui\bower_components\jquery\dist\jquery.slim.min.map</Link>
</BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\jquery\sizzle\LICENSE.txt">
- <Link>Resources\dashboard-ui\bower_components\jquery\sizzle\LICENSE.txt</Link>
+ <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\jquery\external\sizzle\LICENSE.txt">
+ <Link>Resources\dashboard-ui\bower_components\jquery\external\sizzle\LICENSE.txt</Link>
</BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\jquery\sizzle\dist\sizzle.js">
- <Link>Resources\dashboard-ui\bower_components\jquery\sizzle\dist\sizzle.js</Link>
+ <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\jquery\external\sizzle\dist\sizzle.js">
+ <Link>Resources\dashboard-ui\bower_components\jquery\external\sizzle\dist\sizzle.js</Link>
</BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\jquery\sizzle\dist\sizzle.min.js">
- <Link>Resources\dashboard-ui\bower_components\jquery\sizzle\dist\sizzle.min.js</Link>
+ <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\jquery\external\sizzle\dist\sizzle.min.js">
+ <Link>Resources\dashboard-ui\bower_components\jquery\external\sizzle\dist\sizzle.min.js</Link>
</BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\jquery\sizzle\dist\sizzle.min.map">
- <Link>Resources\dashboard-ui\bower_components\jquery\sizzle\dist\sizzle.min.map</Link>
+ <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\jquery\external\sizzle\dist\sizzle.min.map">
+ <Link>Resources\dashboard-ui\bower_components\jquery\external\sizzle\dist\sizzle.min.map</Link>
</BundleResource>
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\jquery\src\.jshintrc">
<Link>Resources\dashboard-ui\bower_components\jquery\src\.jshintrc</Link>
@@ -3351,9 +3429,6 @@
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\jquery\src\event.js">
<Link>Resources\dashboard-ui\bower_components\jquery\src\event.js</Link>
</BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\jquery\src\intro.js">
- <Link>Resources\dashboard-ui\bower_components\jquery\src\intro.js</Link>
- </BundleResource>
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\jquery\src\jquery.js">
<Link>Resources\dashboard-ui\bower_components\jquery\src\jquery.js</Link>
</BundleResource>
@@ -3363,9 +3438,6 @@
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\jquery\src\offset.js">
<Link>Resources\dashboard-ui\bower_components\jquery\src\offset.js</Link>
</BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\jquery\src\outro.js">
- <Link>Resources\dashboard-ui\bower_components\jquery\src\outro.js</Link>
- </BundleResource>
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\jquery\src\queue.js">
<Link>Resources\dashboard-ui\bower_components\jquery\src\queue.js</Link>
</BundleResource>
@@ -3381,9 +3453,6 @@
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\jquery\src\serialize.js">
<Link>Resources\dashboard-ui\bower_components\jquery\src\serialize.js</Link>
</BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\jquery\src\support.js">
- <Link>Resources\dashboard-ui\bower_components\jquery\src\support.js</Link>
- </BundleResource>
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\jquery\src\traversing.js">
<Link>Resources\dashboard-ui\bower_components\jquery\src\traversing.js</Link>
</BundleResource>
@@ -3396,9 +3465,6 @@
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\jquery\src\ajax\load.js">
<Link>Resources\dashboard-ui\bower_components\jquery\src\ajax\load.js</Link>
</BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\jquery\src\ajax\parseJSON.js">
- <Link>Resources\dashboard-ui\bower_components\jquery\src\ajax\parseJSON.js</Link>
- </BundleResource>
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\jquery\src\ajax\parseXML.js">
<Link>Resources\dashboard-ui\bower_components\jquery\src\ajax\parseXML.js</Link>
</BundleResource>
@@ -3435,6 +3501,9 @@
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\jquery\src\core\parseHTML.js">
<Link>Resources\dashboard-ui\bower_components\jquery\src\core\parseHTML.js</Link>
</BundleResource>
+ <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\jquery\src\core\ready-no-deferred.js">
+ <Link>Resources\dashboard-ui\bower_components\jquery\src\core\ready-no-deferred.js</Link>
+ </BundleResource>
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\jquery\src\core\ready.js">
<Link>Resources\dashboard-ui\bower_components\jquery\src\core\ready.js</Link>
</BundleResource>
@@ -3450,9 +3519,6 @@
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\jquery\src\css\curCSS.js">
<Link>Resources\dashboard-ui\bower_components\jquery\src\css\curCSS.js</Link>
</BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\jquery\src\css\defaultDisplay.js">
- <Link>Resources\dashboard-ui\bower_components\jquery\src\css\defaultDisplay.js</Link>
- </BundleResource>
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\jquery\src\css\hiddenVisibleSelectors.js">
<Link>Resources\dashboard-ui\bower_components\jquery\src\css\hiddenVisibleSelectors.js</Link>
</BundleResource>
@@ -3465,12 +3531,6 @@
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\jquery\src\data\Data.js">
<Link>Resources\dashboard-ui\bower_components\jquery\src\data\Data.js</Link>
</BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\jquery\src\data\accepts.js">
- <Link>Resources\dashboard-ui\bower_components\jquery\src\data\accepts.js</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\jquery\src\data\support.js">
- <Link>Resources\dashboard-ui\bower_components\jquery\src\data\support.js</Link>
- </BundleResource>
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\jquery\src\deferred\exceptionHook.js">
<Link>Resources\dashboard-ui\bower_components\jquery\src\deferred\exceptionHook.js</Link>
</BundleResource>
@@ -3480,9 +3540,6 @@
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\jquery\src\effects\animatedSelector.js">
<Link>Resources\dashboard-ui\bower_components\jquery\src\effects\animatedSelector.js</Link>
</BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\jquery\src\effects\support.js">
- <Link>Resources\dashboard-ui\bower_components\jquery\src\effects\support.js</Link>
- </BundleResource>
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\jquery\src\event\ajax.js">
<Link>Resources\dashboard-ui\bower_components\jquery\src\event\ajax.js</Link>
</BundleResource>
@@ -3510,9 +3567,6 @@
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\jquery\src\manipulation\buildFragment.js">
<Link>Resources\dashboard-ui\bower_components\jquery\src\manipulation\buildFragment.js</Link>
</BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\jquery\src\manipulation\createSafeFragment.js">
- <Link>Resources\dashboard-ui\bower_components\jquery\src\manipulation\createSafeFragment.js</Link>
- </BundleResource>
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\jquery\src\manipulation\getAll.js">
<Link>Resources\dashboard-ui\bower_components\jquery\src\manipulation\getAll.js</Link>
</BundleResource>
@@ -3780,660 +3834,6 @@
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\marked-element\test\marked-element.html">
<Link>Resources\dashboard-ui\bower_components\marked-element\test\marked-element.html</Link>
</BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\material-design-lite\.bower.json">
- <Link>Resources\dashboard-ui\bower_components\material-design-lite\.bower.json</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\material-design-lite\LICENSE">
- <Link>Resources\dashboard-ui\bower_components\material-design-lite\LICENSE</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\material-design-lite\README.md">
- <Link>Resources\dashboard-ui\bower_components\material-design-lite\README.md</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\material-design-lite\bower.json">
- <Link>Resources\dashboard-ui\bower_components\material-design-lite\bower.json</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\material-design-lite\gulpfile.babel.js">
- <Link>Resources\dashboard-ui\bower_components\material-design-lite\gulpfile.babel.js</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\material-design-lite\material.css">
- <Link>Resources\dashboard-ui\bower_components\material-design-lite\material.css</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\material-design-lite\material.js">
- <Link>Resources\dashboard-ui\bower_components\material-design-lite\material.js</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\material-design-lite\material.min.css">
- <Link>Resources\dashboard-ui\bower_components\material-design-lite\material.min.css</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\material-design-lite\material.min.css.map">
- <Link>Resources\dashboard-ui\bower_components\material-design-lite\material.min.css.map</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\material-design-lite\material.min.js">
- <Link>Resources\dashboard-ui\bower_components\material-design-lite\material.min.js</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\material-design-lite\material.min.js.map">
- <Link>Resources\dashboard-ui\bower_components\material-design-lite\material.min.js.map</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\material-design-lite\package.json">
- <Link>Resources\dashboard-ui\bower_components\material-design-lite\package.json</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\material-design-lite\src\INTRODUCTION.md">
- <Link>Resources\dashboard-ui\bower_components\material-design-lite\src\INTRODUCTION.md</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\material-design-lite\src\_color-definitions.scss">
- <Link>Resources\dashboard-ui\bower_components\material-design-lite\src\_color-definitions.scss</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\material-design-lite\src\_functions.scss">
- <Link>Resources\dashboard-ui\bower_components\material-design-lite\src\_functions.scss</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\material-design-lite\src\_mixins.scss">
- <Link>Resources\dashboard-ui\bower_components\material-design-lite\src\_mixins.scss</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\material-design-lite\src\_variables.scss">
- <Link>Resources\dashboard-ui\bower_components\material-design-lite\src\_variables.scss</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\material-design-lite\src\demos.css">
- <Link>Resources\dashboard-ui\bower_components\material-design-lite\src\demos.css</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\material-design-lite\src\index.html">
- <Link>Resources\dashboard-ui\bower_components\material-design-lite\src\index.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\material-design-lite\src\material-design-lite-grid.scss">
- <Link>Resources\dashboard-ui\bower_components\material-design-lite\src\material-design-lite-grid.scss</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\material-design-lite\src\material-design-lite.scss">
- <Link>Resources\dashboard-ui\bower_components\material-design-lite\src\material-design-lite.scss</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\material-design-lite\src\mdlComponentHandler.js">
- <Link>Resources\dashboard-ui\bower_components\material-design-lite\src\mdlComponentHandler.js</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\material-design-lite\src\styleguide.scss">
- <Link>Resources\dashboard-ui\bower_components\material-design-lite\src\styleguide.scss</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\material-design-lite\src\template.scss">
- <Link>Resources\dashboard-ui\bower_components\material-design-lite\src\template.scss</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\material-design-lite\src\animation\_animation.scss">
- <Link>Resources\dashboard-ui\bower_components\material-design-lite\src\animation\_animation.scss</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\material-design-lite\src\animation\demo.css">
- <Link>Resources\dashboard-ui\bower_components\material-design-lite\src\animation\demo.css</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\material-design-lite\src\animation\demo.html">
- <Link>Resources\dashboard-ui\bower_components\material-design-lite\src\animation\demo.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\material-design-lite\src\animation\demo.js">
- <Link>Resources\dashboard-ui\bower_components\material-design-lite\src\animation\demo.js</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\material-design-lite\src\badge\README.md">
- <Link>Resources\dashboard-ui\bower_components\material-design-lite\src\badge\README.md</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\material-design-lite\src\badge\_badge.scss">
- <Link>Resources\dashboard-ui\bower_components\material-design-lite\src\badge\_badge.scss</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\material-design-lite\src\badge\snippets\badge-on-icon-icon-demo.html">
- <Link>Resources\dashboard-ui\bower_components\material-design-lite\src\badge\snippets\badge-on-icon-icon-demo.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\material-design-lite\src\badge\snippets\badge-on-icon-icon.html">
- <Link>Resources\dashboard-ui\bower_components\material-design-lite\src\badge\snippets\badge-on-icon-icon.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\material-design-lite\src\badge\snippets\badge-on-icon-text-demo.html">
- <Link>Resources\dashboard-ui\bower_components\material-design-lite\src\badge\snippets\badge-on-icon-text-demo.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\material-design-lite\src\badge\snippets\badge-on-icon-text.html">
- <Link>Resources\dashboard-ui\bower_components\material-design-lite\src\badge\snippets\badge-on-icon-text.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\material-design-lite\src\badge\snippets\badge-on-text-icon-demo.html">
- <Link>Resources\dashboard-ui\bower_components\material-design-lite\src\badge\snippets\badge-on-text-icon-demo.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\material-design-lite\src\badge\snippets\badge-on-text-icon.html">
- <Link>Resources\dashboard-ui\bower_components\material-design-lite\src\badge\snippets\badge-on-text-icon.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\material-design-lite\src\badge\snippets\badge-on-text-text-demo.html">
- <Link>Resources\dashboard-ui\bower_components\material-design-lite\src\badge\snippets\badge-on-text-text-demo.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\material-design-lite\src\badge\snippets\badge-on-text-text.html">
- <Link>Resources\dashboard-ui\bower_components\material-design-lite\src\badge\snippets\badge-on-text-text.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\material-design-lite\src\button\README.md">
- <Link>Resources\dashboard-ui\bower_components\material-design-lite\src\button\README.md</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\material-design-lite\src\button\_button.scss">
- <Link>Resources\dashboard-ui\bower_components\material-design-lite\src\button\_button.scss</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\material-design-lite\src\button\button.js">
- <Link>Resources\dashboard-ui\bower_components\material-design-lite\src\button\button.js</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\material-design-lite\src\button\snippets\fab-colored-ripple.html">
- <Link>Resources\dashboard-ui\bower_components\material-design-lite\src\button\snippets\fab-colored-ripple.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\material-design-lite\src\button\snippets\fab-colored.html">
- <Link>Resources\dashboard-ui\bower_components\material-design-lite\src\button\snippets\fab-colored.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\material-design-lite\src\button\snippets\fab-disabled.html">
- <Link>Resources\dashboard-ui\bower_components\material-design-lite\src\button\snippets\fab-disabled.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\material-design-lite\src\button\snippets\fab-mini-colored.html">
- <Link>Resources\dashboard-ui\bower_components\material-design-lite\src\button\snippets\fab-mini-colored.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\material-design-lite\src\button\snippets\fab-mini.html">
- <Link>Resources\dashboard-ui\bower_components\material-design-lite\src\button\snippets\fab-mini.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\material-design-lite\src\button\snippets\fab-ripple.html">
- <Link>Resources\dashboard-ui\bower_components\material-design-lite\src\button\snippets\fab-ripple.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\material-design-lite\src\button\snippets\fab.html">
- <Link>Resources\dashboard-ui\bower_components\material-design-lite\src\button\snippets\fab.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\material-design-lite\src\button\snippets\flat-accent.html">
- <Link>Resources\dashboard-ui\bower_components\material-design-lite\src\button\snippets\flat-accent.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\material-design-lite\src\button\snippets\flat-disabled.html">
- <Link>Resources\dashboard-ui\bower_components\material-design-lite\src\button\snippets\flat-disabled.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\material-design-lite\src\button\snippets\flat-primary.html">
- <Link>Resources\dashboard-ui\bower_components\material-design-lite\src\button\snippets\flat-primary.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\material-design-lite\src\button\snippets\flat-ripple.html">
- <Link>Resources\dashboard-ui\bower_components\material-design-lite\src\button\snippets\flat-ripple.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\material-design-lite\src\button\snippets\flat.html">
- <Link>Resources\dashboard-ui\bower_components\material-design-lite\src\button\snippets\flat.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\material-design-lite\src\button\snippets\icon-colored.html">
- <Link>Resources\dashboard-ui\bower_components\material-design-lite\src\button\snippets\icon-colored.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\material-design-lite\src\button\snippets\icon.html">
- <Link>Resources\dashboard-ui\bower_components\material-design-lite\src\button\snippets\icon.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\material-design-lite\src\button\snippets\raised-accent.html">
- <Link>Resources\dashboard-ui\bower_components\material-design-lite\src\button\snippets\raised-accent.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\material-design-lite\src\button\snippets\raised-colored.html">
- <Link>Resources\dashboard-ui\bower_components\material-design-lite\src\button\snippets\raised-colored.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\material-design-lite\src\button\snippets\raised-disabled.html">
- <Link>Resources\dashboard-ui\bower_components\material-design-lite\src\button\snippets\raised-disabled.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\material-design-lite\src\button\snippets\raised-ripple-accent.html">
- <Link>Resources\dashboard-ui\bower_components\material-design-lite\src\button\snippets\raised-ripple-accent.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\material-design-lite\src\button\snippets\raised-ripple.html">
- <Link>Resources\dashboard-ui\bower_components\material-design-lite\src\button\snippets\raised-ripple.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\material-design-lite\src\button\snippets\raised.html">
- <Link>Resources\dashboard-ui\bower_components\material-design-lite\src\button\snippets\raised.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\material-design-lite\src\card\README.md">
- <Link>Resources\dashboard-ui\bower_components\material-design-lite\src\card\README.md</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\material-design-lite\src\card\_card.scss">
- <Link>Resources\dashboard-ui\bower_components\material-design-lite\src\card\_card.scss</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\material-design-lite\src\card\snippets\event.html">
- <Link>Resources\dashboard-ui\bower_components\material-design-lite\src\card\snippets\event.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\material-design-lite\src\card\snippets\image.html">
- <Link>Resources\dashboard-ui\bower_components\material-design-lite\src\card\snippets\image.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\material-design-lite\src\card\snippets\square.html">
- <Link>Resources\dashboard-ui\bower_components\material-design-lite\src\card\snippets\square.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\material-design-lite\src\card\snippets\wide.html">
- <Link>Resources\dashboard-ui\bower_components\material-design-lite\src\card\snippets\wide.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\material-design-lite\src\checkbox\README.md">
- <Link>Resources\dashboard-ui\bower_components\material-design-lite\src\checkbox\README.md</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\material-design-lite\src\checkbox\_checkbox.scss">
- <Link>Resources\dashboard-ui\bower_components\material-design-lite\src\checkbox\_checkbox.scss</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\material-design-lite\src\checkbox\checkbox.js">
- <Link>Resources\dashboard-ui\bower_components\material-design-lite\src\checkbox\checkbox.js</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\material-design-lite\src\checkbox\snippets\check-off.html">
- <Link>Resources\dashboard-ui\bower_components\material-design-lite\src\checkbox\snippets\check-off.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\material-design-lite\src\checkbox\snippets\check-on.html">
- <Link>Resources\dashboard-ui\bower_components\material-design-lite\src\checkbox\snippets\check-on.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\material-design-lite\src\data-table\README.md">
- <Link>Resources\dashboard-ui\bower_components\material-design-lite\src\data-table\README.md</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\material-design-lite\src\data-table\_data-table.scss">
- <Link>Resources\dashboard-ui\bower_components\material-design-lite\src\data-table\_data-table.scss</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\material-design-lite\src\data-table\data-table.js">
- <Link>Resources\dashboard-ui\bower_components\material-design-lite\src\data-table\data-table.js</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\material-design-lite\src\data-table\snippets\data-table.html">
- <Link>Resources\dashboard-ui\bower_components\material-design-lite\src\data-table\snippets\data-table.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\material-design-lite\src\dialog\README.md">
- <Link>Resources\dashboard-ui\bower_components\material-design-lite\src\dialog\README.md</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\material-design-lite\src\dialog\_dialog.scss">
- <Link>Resources\dashboard-ui\bower_components\material-design-lite\src\dialog\_dialog.scss</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\material-design-lite\src\footer\README.md">
- <Link>Resources\dashboard-ui\bower_components\material-design-lite\src\footer\README.md</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\material-design-lite\src\footer\_mega_footer.scss">
- <Link>Resources\dashboard-ui\bower_components\material-design-lite\src\footer\_mega_footer.scss</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\material-design-lite\src\footer\_mini_footer.scss">
- <Link>Resources\dashboard-ui\bower_components\material-design-lite\src\footer\_mini_footer.scss</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\material-design-lite\src\footer\snippets\mega-footer.html">
- <Link>Resources\dashboard-ui\bower_components\material-design-lite\src\footer\snippets\mega-footer.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\material-design-lite\src\footer\snippets\mini-footer.html">
- <Link>Resources\dashboard-ui\bower_components\material-design-lite\src\footer\snippets\mini-footer.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\material-design-lite\src\grid\README.md">
- <Link>Resources\dashboard-ui\bower_components\material-design-lite\src\grid\README.md</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\material-design-lite\src\grid\_grid.scss">
- <Link>Resources\dashboard-ui\bower_components\material-design-lite\src\grid\_grid.scss</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\material-design-lite\src\grid\snippets\codepen-grid.css">
- <Link>Resources\dashboard-ui\bower_components\material-design-lite\src\grid\snippets\codepen-grid.css</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\material-design-lite\src\grid\snippets\grid-demo.html">
- <Link>Resources\dashboard-ui\bower_components\material-design-lite\src\grid\snippets\grid-demo.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\material-design-lite\src\grid\snippets\grid.html">
- <Link>Resources\dashboard-ui\bower_components\material-design-lite\src\grid\snippets\grid.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\material-design-lite\src\icon-toggle\README.md">
- <Link>Resources\dashboard-ui\bower_components\material-design-lite\src\icon-toggle\README.md</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\material-design-lite\src\icon-toggle\_icon-toggle.scss">
- <Link>Resources\dashboard-ui\bower_components\material-design-lite\src\icon-toggle\_icon-toggle.scss</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\material-design-lite\src\icon-toggle\icon-toggle.js">
- <Link>Resources\dashboard-ui\bower_components\material-design-lite\src\icon-toggle\icon-toggle.js</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\material-design-lite\src\icon-toggle\snippets\icon-off.html">
- <Link>Resources\dashboard-ui\bower_components\material-design-lite\src\icon-toggle\snippets\icon-off.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\material-design-lite\src\icon-toggle\snippets\icon-on.html">
- <Link>Resources\dashboard-ui\bower_components\material-design-lite\src\icon-toggle\snippets\icon-on.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\material-design-lite\src\images\buffer.svg">
- <Link>Resources\dashboard-ui\bower_components\material-design-lite\src\images\buffer.svg</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\material-design-lite\src\images\tick-mask.svg">
- <Link>Resources\dashboard-ui\bower_components\material-design-lite\src\images\tick-mask.svg</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\material-design-lite\src\images\tick.svg">
- <Link>Resources\dashboard-ui\bower_components\material-design-lite\src\images\tick.svg</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\material-design-lite\src\layout\README.md">
- <Link>Resources\dashboard-ui\bower_components\material-design-lite\src\layout\README.md</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\material-design-lite\src\layout\_layout.scss">
- <Link>Resources\dashboard-ui\bower_components\material-design-lite\src\layout\_layout.scss</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\material-design-lite\src\layout\layout.js">
- <Link>Resources\dashboard-ui\bower_components\material-design-lite\src\layout\layout.js</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\material-design-lite\src\layout\snippets\fixed-drawer-demo.html">
- <Link>Resources\dashboard-ui\bower_components\material-design-lite\src\layout\snippets\fixed-drawer-demo.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\material-design-lite\src\layout\snippets\fixed-drawer.html">
- <Link>Resources\dashboard-ui\bower_components\material-design-lite\src\layout\snippets\fixed-drawer.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\material-design-lite\src\layout\snippets\fixed-header-demo.html">
- <Link>Resources\dashboard-ui\bower_components\material-design-lite\src\layout\snippets\fixed-header-demo.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\material-design-lite\src\layout\snippets\fixed-header-drawer-demo.html">
- <Link>Resources\dashboard-ui\bower_components\material-design-lite\src\layout\snippets\fixed-header-drawer-demo.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\material-design-lite\src\layout\snippets\fixed-header-drawer.html">
- <Link>Resources\dashboard-ui\bower_components\material-design-lite\src\layout\snippets\fixed-header-drawer.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\material-design-lite\src\layout\snippets\fixed-header.html">
- <Link>Resources\dashboard-ui\bower_components\material-design-lite\src\layout\snippets\fixed-header.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\material-design-lite\src\layout\snippets\fixed-tabs-demo.html">
- <Link>Resources\dashboard-ui\bower_components\material-design-lite\src\layout\snippets\fixed-tabs-demo.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\material-design-lite\src\layout\snippets\fixed-tabs.html">
- <Link>Resources\dashboard-ui\bower_components\material-design-lite\src\layout\snippets\fixed-tabs.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\material-design-lite\src\layout\snippets\scrollable-tabs-demo.html">
- <Link>Resources\dashboard-ui\bower_components\material-design-lite\src\layout\snippets\scrollable-tabs-demo.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\material-design-lite\src\layout\snippets\scrollable-tabs.html">
- <Link>Resources\dashboard-ui\bower_components\material-design-lite\src\layout\snippets\scrollable-tabs.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\material-design-lite\src\layout\snippets\scrolling-header-demo.html">
- <Link>Resources\dashboard-ui\bower_components\material-design-lite\src\layout\snippets\scrolling-header-demo.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\material-design-lite\src\layout\snippets\scrolling-header.html">
- <Link>Resources\dashboard-ui\bower_components\material-design-lite\src\layout\snippets\scrolling-header.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\material-design-lite\src\layout\snippets\transparent-demo.html">
- <Link>Resources\dashboard-ui\bower_components\material-design-lite\src\layout\snippets\transparent-demo.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\material-design-lite\src\layout\snippets\transparent.html">
- <Link>Resources\dashboard-ui\bower_components\material-design-lite\src\layout\snippets\transparent.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\material-design-lite\src\layout\snippets\waterfall-header-demo.html">
- <Link>Resources\dashboard-ui\bower_components\material-design-lite\src\layout\snippets\waterfall-header-demo.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\material-design-lite\src\layout\snippets\waterfall-header.html">
- <Link>Resources\dashboard-ui\bower_components\material-design-lite\src\layout\snippets\waterfall-header.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\material-design-lite\src\list\README.md">
- <Link>Resources\dashboard-ui\bower_components\material-design-lite\src\list\README.md</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\material-design-lite\src\list\_list.scss">
- <Link>Resources\dashboard-ui\bower_components\material-design-lite\src\list\_list.scss</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\material-design-lite\src\list\snippets\action.html">
- <Link>Resources\dashboard-ui\bower_components\material-design-lite\src\list\snippets\action.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\material-design-lite\src\list\snippets\icon.html">
- <Link>Resources\dashboard-ui\bower_components\material-design-lite\src\list\snippets\icon.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\material-design-lite\src\list\snippets\list-control.html">
- <Link>Resources\dashboard-ui\bower_components\material-design-lite\src\list\snippets\list-control.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\material-design-lite\src\list\snippets\list-item.html">
- <Link>Resources\dashboard-ui\bower_components\material-design-lite\src\list\snippets\list-item.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\material-design-lite\src\list\snippets\three-line.html">
- <Link>Resources\dashboard-ui\bower_components\material-design-lite\src\list\snippets\three-line.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\material-design-lite\src\list\snippets\two-line.html">
- <Link>Resources\dashboard-ui\bower_components\material-design-lite\src\list\snippets\two-line.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\material-design-lite\src\menu\README.md">
- <Link>Resources\dashboard-ui\bower_components\material-design-lite\src\menu\README.md</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\material-design-lite\src\menu\_menu.scss">
- <Link>Resources\dashboard-ui\bower_components\material-design-lite\src\menu\_menu.scss</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\material-design-lite\src\menu\menu.js">
- <Link>Resources\dashboard-ui\bower_components\material-design-lite\src\menu\menu.js</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\material-design-lite\src\menu\snippets\codepen-lower-buttons.css">
- <Link>Resources\dashboard-ui\bower_components\material-design-lite\src\menu\snippets\codepen-lower-buttons.css</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\material-design-lite\src\menu\snippets\codepen-top-buttons.css">
- <Link>Resources\dashboard-ui\bower_components\material-design-lite\src\menu\snippets\codepen-top-buttons.css</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\material-design-lite\src\menu\snippets\lower-left-demo.html">
- <Link>Resources\dashboard-ui\bower_components\material-design-lite\src\menu\snippets\lower-left-demo.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\material-design-lite\src\menu\snippets\lower-left.html">
- <Link>Resources\dashboard-ui\bower_components\material-design-lite\src\menu\snippets\lower-left.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\material-design-lite\src\menu\snippets\lower-right-demo.html">
- <Link>Resources\dashboard-ui\bower_components\material-design-lite\src\menu\snippets\lower-right-demo.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\material-design-lite\src\menu\snippets\lower-right.html">
- <Link>Resources\dashboard-ui\bower_components\material-design-lite\src\menu\snippets\lower-right.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\material-design-lite\src\menu\snippets\top-left-demo.html">
- <Link>Resources\dashboard-ui\bower_components\material-design-lite\src\menu\snippets\top-left-demo.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\material-design-lite\src\menu\snippets\top-left.html">
- <Link>Resources\dashboard-ui\bower_components\material-design-lite\src\menu\snippets\top-left.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\material-design-lite\src\menu\snippets\top-right-demo.html">
- <Link>Resources\dashboard-ui\bower_components\material-design-lite\src\menu\snippets\top-right-demo.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\material-design-lite\src\menu\snippets\top-right.html">
- <Link>Resources\dashboard-ui\bower_components\material-design-lite\src\menu\snippets\top-right.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\material-design-lite\src\palette\_palette.scss">
- <Link>Resources\dashboard-ui\bower_components\material-design-lite\src\palette\_palette.scss</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\material-design-lite\src\palette\demo.css">
- <Link>Resources\dashboard-ui\bower_components\material-design-lite\src\palette\demo.css</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\material-design-lite\src\palette\demo.html">
- <Link>Resources\dashboard-ui\bower_components\material-design-lite\src\palette\demo.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\material-design-lite\src\progress\README.md">
- <Link>Resources\dashboard-ui\bower_components\material-design-lite\src\progress\README.md</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\material-design-lite\src\progress\_progress.scss">
- <Link>Resources\dashboard-ui\bower_components\material-design-lite\src\progress\_progress.scss</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\material-design-lite\src\progress\progress.js">
- <Link>Resources\dashboard-ui\bower_components\material-design-lite\src\progress\progress.js</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\material-design-lite\src\progress\snippets\progress-buffering-demo.html">
- <Link>Resources\dashboard-ui\bower_components\material-design-lite\src\progress\snippets\progress-buffering-demo.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\material-design-lite\src\progress\snippets\progress-buffering.html">
- <Link>Resources\dashboard-ui\bower_components\material-design-lite\src\progress\snippets\progress-buffering.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\material-design-lite\src\progress\snippets\progress-default-demo.html">
- <Link>Resources\dashboard-ui\bower_components\material-design-lite\src\progress\snippets\progress-default-demo.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\material-design-lite\src\progress\snippets\progress-default.html">
- <Link>Resources\dashboard-ui\bower_components\material-design-lite\src\progress\snippets\progress-default.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\material-design-lite\src\progress\snippets\progress-indeterminate-demo.html">
- <Link>Resources\dashboard-ui\bower_components\material-design-lite\src\progress\snippets\progress-indeterminate-demo.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\material-design-lite\src\progress\snippets\progress-indeterminate.html">
- <Link>Resources\dashboard-ui\bower_components\material-design-lite\src\progress\snippets\progress-indeterminate.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\material-design-lite\src\radio\README.md">
- <Link>Resources\dashboard-ui\bower_components\material-design-lite\src\radio\README.md</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\material-design-lite\src\radio\_radio.scss">
- <Link>Resources\dashboard-ui\bower_components\material-design-lite\src\radio\_radio.scss</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\material-design-lite\src\radio\radio.js">
- <Link>Resources\dashboard-ui\bower_components\material-design-lite\src\radio\radio.js</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\material-design-lite\src\radio\snippets\radio-off.html">
- <Link>Resources\dashboard-ui\bower_components\material-design-lite\src\radio\snippets\radio-off.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\material-design-lite\src\radio\snippets\radio-on.html">
- <Link>Resources\dashboard-ui\bower_components\material-design-lite\src\radio\snippets\radio-on.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\material-design-lite\src\resets\_h5bp.scss">
- <Link>Resources\dashboard-ui\bower_components\material-design-lite\src\resets\_h5bp.scss</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\material-design-lite\src\resets\_mobile.scss">
- <Link>Resources\dashboard-ui\bower_components\material-design-lite\src\resets\_mobile.scss</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\material-design-lite\src\resets\_resets.scss">
- <Link>Resources\dashboard-ui\bower_components\material-design-lite\src\resets\_resets.scss</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\material-design-lite\src\ripple\_ripple.scss">
- <Link>Resources\dashboard-ui\bower_components\material-design-lite\src\ripple\_ripple.scss</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\material-design-lite\src\ripple\ripple.js">
- <Link>Resources\dashboard-ui\bower_components\material-design-lite\src\ripple\ripple.js</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\material-design-lite\src\shadow\README.md">
- <Link>Resources\dashboard-ui\bower_components\material-design-lite\src\shadow\README.md</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\material-design-lite\src\shadow\_shadow.scss">
- <Link>Resources\dashboard-ui\bower_components\material-design-lite\src\shadow\_shadow.scss</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\material-design-lite\src\shadow\demo.css">
- <Link>Resources\dashboard-ui\bower_components\material-design-lite\src\shadow\demo.css</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\material-design-lite\src\shadow\demo.html">
- <Link>Resources\dashboard-ui\bower_components\material-design-lite\src\shadow\demo.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\material-design-lite\src\slider\README.md">
- <Link>Resources\dashboard-ui\bower_components\material-design-lite\src\slider\README.md</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\material-design-lite\src\slider\_slider.scss">
- <Link>Resources\dashboard-ui\bower_components\material-design-lite\src\slider\_slider.scss</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\material-design-lite\src\slider\slider.js">
- <Link>Resources\dashboard-ui\bower_components\material-design-lite\src\slider\slider.js</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\material-design-lite\src\slider\snippets\demo.html">
- <Link>Resources\dashboard-ui\bower_components\material-design-lite\src\slider\snippets\demo.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\material-design-lite\src\slider\snippets\slider-default-demo.html">
- <Link>Resources\dashboard-ui\bower_components\material-design-lite\src\slider\snippets\slider-default-demo.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\material-design-lite\src\slider\snippets\slider-default.html">
- <Link>Resources\dashboard-ui\bower_components\material-design-lite\src\slider\snippets\slider-default.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\material-design-lite\src\slider\snippets\slider-starting-value-demo.html">
- <Link>Resources\dashboard-ui\bower_components\material-design-lite\src\slider\snippets\slider-starting-value-demo.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\material-design-lite\src\slider\snippets\slider-starting-value.html">
- <Link>Resources\dashboard-ui\bower_components\material-design-lite\src\slider\snippets\slider-starting-value.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\material-design-lite\src\snackbar\README.md">
- <Link>Resources\dashboard-ui\bower_components\material-design-lite\src\snackbar\README.md</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\material-design-lite\src\snackbar\_snackbar.scss">
- <Link>Resources\dashboard-ui\bower_components\material-design-lite\src\snackbar\_snackbar.scss</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\material-design-lite\src\snackbar\snackbar.js">
- <Link>Resources\dashboard-ui\bower_components\material-design-lite\src\snackbar\snackbar.js</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\material-design-lite\src\snackbar\snippets\snackbar.html">
- <Link>Resources\dashboard-ui\bower_components\material-design-lite\src\snackbar\snippets\snackbar.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\material-design-lite\src\snackbar\snippets\toast.html">
- <Link>Resources\dashboard-ui\bower_components\material-design-lite\src\snackbar\snippets\toast.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\material-design-lite\src\spinner\README.md">
- <Link>Resources\dashboard-ui\bower_components\material-design-lite\src\spinner\README.md</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\material-design-lite\src\spinner\_spinner.scss">
- <Link>Resources\dashboard-ui\bower_components\material-design-lite\src\spinner\_spinner.scss</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\material-design-lite\src\spinner\spinner.js">
- <Link>Resources\dashboard-ui\bower_components\material-design-lite\src\spinner\spinner.js</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\material-design-lite\src\spinner\snippets\spinner-default.html">
- <Link>Resources\dashboard-ui\bower_components\material-design-lite\src\spinner\snippets\spinner-default.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\material-design-lite\src\spinner\snippets\spinner-single-color.html">
- <Link>Resources\dashboard-ui\bower_components\material-design-lite\src\spinner\snippets\spinner-single-color.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\material-design-lite\src\switch\README.md">
- <Link>Resources\dashboard-ui\bower_components\material-design-lite\src\switch\README.md</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\material-design-lite\src\switch\_switch.scss">
- <Link>Resources\dashboard-ui\bower_components\material-design-lite\src\switch\_switch.scss</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\material-design-lite\src\switch\switch.js">
- <Link>Resources\dashboard-ui\bower_components\material-design-lite\src\switch\switch.js</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\material-design-lite\src\switch\snippets\switch-off.html">
- <Link>Resources\dashboard-ui\bower_components\material-design-lite\src\switch\snippets\switch-off.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\material-design-lite\src\switch\snippets\switch-on.html">
- <Link>Resources\dashboard-ui\bower_components\material-design-lite\src\switch\snippets\switch-on.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\material-design-lite\src\tabs\README.md">
- <Link>Resources\dashboard-ui\bower_components\material-design-lite\src\tabs\README.md</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\material-design-lite\src\tabs\_tabs.scss">
- <Link>Resources\dashboard-ui\bower_components\material-design-lite\src\tabs\_tabs.scss</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\material-design-lite\src\tabs\tabs.js">
- <Link>Resources\dashboard-ui\bower_components\material-design-lite\src\tabs\tabs.js</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\material-design-lite\src\tabs\snippets\tabs.html">
- <Link>Resources\dashboard-ui\bower_components\material-design-lite\src\tabs\snippets\tabs.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\material-design-lite\src\textfield\README.md">
- <Link>Resources\dashboard-ui\bower_components\material-design-lite\src\textfield\README.md</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\material-design-lite\src\textfield\_textfield.scss">
- <Link>Resources\dashboard-ui\bower_components\material-design-lite\src\textfield\_textfield.scss</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\material-design-lite\src\textfield\textfield.js">
- <Link>Resources\dashboard-ui\bower_components\material-design-lite\src\textfield\textfield.js</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\material-design-lite\src\textfield\snippets\textfield-expanding-demo.html">
- <Link>Resources\dashboard-ui\bower_components\material-design-lite\src\textfield\snippets\textfield-expanding-demo.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\material-design-lite\src\textfield\snippets\textfield-expanding.html">
- <Link>Resources\dashboard-ui\bower_components\material-design-lite\src\textfield\snippets\textfield-expanding.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\material-design-lite\src\textfield\snippets\textfield-floating-numeric-demo.html">
- <Link>Resources\dashboard-ui\bower_components\material-design-lite\src\textfield\snippets\textfield-floating-numeric-demo.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\material-design-lite\src\textfield\snippets\textfield-floating-numeric.html">
- <Link>Resources\dashboard-ui\bower_components\material-design-lite\src\textfield\snippets\textfield-floating-numeric.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\material-design-lite\src\textfield\snippets\textfield-floating-text-demo.html">
- <Link>Resources\dashboard-ui\bower_components\material-design-lite\src\textfield\snippets\textfield-floating-text-demo.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\material-design-lite\src\textfield\snippets\textfield-floating-text.html">
- <Link>Resources\dashboard-ui\bower_components\material-design-lite\src\textfield\snippets\textfield-floating-text.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\material-design-lite\src\textfield\snippets\textfield-multi-line-demo.html">
- <Link>Resources\dashboard-ui\bower_components\material-design-lite\src\textfield\snippets\textfield-multi-line-demo.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\material-design-lite\src\textfield\snippets\textfield-multi-line.html">
- <Link>Resources\dashboard-ui\bower_components\material-design-lite\src\textfield\snippets\textfield-multi-line.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\material-design-lite\src\textfield\snippets\textfield-numeric-demo.html">
- <Link>Resources\dashboard-ui\bower_components\material-design-lite\src\textfield\snippets\textfield-numeric-demo.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\material-design-lite\src\textfield\snippets\textfield-numeric.html">
- <Link>Resources\dashboard-ui\bower_components\material-design-lite\src\textfield\snippets\textfield-numeric.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\material-design-lite\src\textfield\snippets\textfield-text-demo.html">
- <Link>Resources\dashboard-ui\bower_components\material-design-lite\src\textfield\snippets\textfield-text-demo.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\material-design-lite\src\textfield\snippets\textfield-text.html">
- <Link>Resources\dashboard-ui\bower_components\material-design-lite\src\textfield\snippets\textfield-text.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\material-design-lite\src\third_party\rAF.js">
- <Link>Resources\dashboard-ui\bower_components\material-design-lite\src\third_party\rAF.js</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\material-design-lite\src\tooltip\README.md">
- <Link>Resources\dashboard-ui\bower_components\material-design-lite\src\tooltip\README.md</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\material-design-lite\src\tooltip\_tooltip.scss">
- <Link>Resources\dashboard-ui\bower_components\material-design-lite\src\tooltip\_tooltip.scss</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\material-design-lite\src\tooltip\tooltip.js">
- <Link>Resources\dashboard-ui\bower_components\material-design-lite\src\tooltip\tooltip.js</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\material-design-lite\src\tooltip\snippets\tooltip-large.html">
- <Link>Resources\dashboard-ui\bower_components\material-design-lite\src\tooltip\snippets\tooltip-large.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\material-design-lite\src\tooltip\snippets\tooltip-multiline.html">
- <Link>Resources\dashboard-ui\bower_components\material-design-lite\src\tooltip\snippets\tooltip-multiline.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\material-design-lite\src\tooltip\snippets\tooltip-rich.html">
- <Link>Resources\dashboard-ui\bower_components\material-design-lite\src\tooltip\snippets\tooltip-rich.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\material-design-lite\src\tooltip\snippets\tooltip-simple.html">
- <Link>Resources\dashboard-ui\bower_components\material-design-lite\src\tooltip\snippets\tooltip-simple.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\material-design-lite\src\typography\README.md">
- <Link>Resources\dashboard-ui\bower_components\material-design-lite\src\typography\README.md</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\material-design-lite\src\typography\_typography.scss">
- <Link>Resources\dashboard-ui\bower_components\material-design-lite\src\typography\_typography.scss</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\material-design-lite\src\typography\demo.css">
- <Link>Resources\dashboard-ui\bower_components\material-design-lite\src\typography\demo.css</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\material-design-lite\src\typography\demo.html">
- <Link>Resources\dashboard-ui\bower_components\material-design-lite\src\typography\demo.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\material-design-lite\utils\uniffe.js">
- <Link>Resources\dashboard-ui\bower_components\material-design-lite\utils\uniffe.js</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\material-design-lite\utils\jscs-rules\closure-camel-case.js">
- <Link>Resources\dashboard-ui\bower_components\material-design-lite\utils\jscs-rules\closure-camel-case.js</Link>
- </BundleResource>
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\native-promise-only\.bower.json">
<Link>Resources\dashboard-ui\bower_components\native-promise-only\.bower.json</Link>
</BundleResource>
@@ -4758,6 +4158,9 @@
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\paper-button\paper-button.html">
<Link>Resources\dashboard-ui\bower_components\paper-button\paper-button.html</Link>
</BundleResource>
+ <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\paper-button\.github\ISSUE_TEMPLATE.md">
+ <Link>Resources\dashboard-ui\bower_components\paper-button\.github\ISSUE_TEMPLATE.md</Link>
+ </BundleResource>
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\paper-button\demo\index.html">
<Link>Resources\dashboard-ui\bower_components\paper-button\demo\index.html</Link>
</BundleResource>
@@ -4803,48 +4206,6 @@
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\paper-checkbox\test\index.html">
<Link>Resources\dashboard-ui\bower_components\paper-checkbox\test\index.html</Link>
</BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\paper-drawer-panel\.bower.json">
- <Link>Resources\dashboard-ui\bower_components\paper-drawer-panel\.bower.json</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\paper-drawer-panel\.gitignore">
- <Link>Resources\dashboard-ui\bower_components\paper-drawer-panel\.gitignore</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\paper-drawer-panel\.travis.yml">
- <Link>Resources\dashboard-ui\bower_components\paper-drawer-panel\.travis.yml</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\paper-drawer-panel\CONTRIBUTING.md">
- <Link>Resources\dashboard-ui\bower_components\paper-drawer-panel\CONTRIBUTING.md</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\paper-drawer-panel\README.md">
- <Link>Resources\dashboard-ui\bower_components\paper-drawer-panel\README.md</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\paper-drawer-panel\bower.json">
- <Link>Resources\dashboard-ui\bower_components\paper-drawer-panel\bower.json</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\paper-drawer-panel\hero.svg">
- <Link>Resources\dashboard-ui\bower_components\paper-drawer-panel\hero.svg</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\paper-drawer-panel\index.html">
- <Link>Resources\dashboard-ui\bower_components\paper-drawer-panel\index.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\paper-drawer-panel\paper-drawer-panel.html">
- <Link>Resources\dashboard-ui\bower_components\paper-drawer-panel\paper-drawer-panel.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\paper-drawer-panel\demo\index.html">
- <Link>Resources\dashboard-ui\bower_components\paper-drawer-panel\demo\index.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\paper-drawer-panel\test\focus.html">
- <Link>Resources\dashboard-ui\bower_components\paper-drawer-panel\test\focus.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\paper-drawer-panel\test\index.html">
- <Link>Resources\dashboard-ui\bower_components\paper-drawer-panel\test\index.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\paper-drawer-panel\test\positioning.html">
- <Link>Resources\dashboard-ui\bower_components\paper-drawer-panel\test\positioning.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\paper-drawer-panel\test\small-devices.html">
- <Link>Resources\dashboard-ui\bower_components\paper-drawer-panel\test\small-devices.html</Link>
- </BundleResource>
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\paper-fab\.bower.json">
<Link>Resources\dashboard-ui\bower_components\paper-fab\.bower.json</Link>
</BundleResource>
@@ -5253,6 +4614,9 @@
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\paper-ripple\paper-ripple.html">
<Link>Resources\dashboard-ui\bower_components\paper-ripple\paper-ripple.html</Link>
</BundleResource>
+ <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\paper-ripple\.github\ISSUE_TEMPLATE.md">
+ <Link>Resources\dashboard-ui\bower_components\paper-ripple\.github\ISSUE_TEMPLATE.md</Link>
+ </BundleResource>
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\paper-ripple\demo\index.html">
<Link>Resources\dashboard-ui\bower_components\paper-ripple\demo\index.html</Link>
</BundleResource>
@@ -5262,45 +4626,6 @@
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\paper-ripple\test\paper-ripple.html">
<Link>Resources\dashboard-ui\bower_components\paper-ripple\test\paper-ripple.html</Link>
</BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\paper-slider\.bower.json">
- <Link>Resources\dashboard-ui\bower_components\paper-slider\.bower.json</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\paper-slider\.gitignore">
- <Link>Resources\dashboard-ui\bower_components\paper-slider\.gitignore</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\paper-slider\.travis.yml">
- <Link>Resources\dashboard-ui\bower_components\paper-slider\.travis.yml</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\paper-slider\CONTRIBUTING.md">
- <Link>Resources\dashboard-ui\bower_components\paper-slider\CONTRIBUTING.md</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\paper-slider\README.md">
- <Link>Resources\dashboard-ui\bower_components\paper-slider\README.md</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\paper-slider\bower.json">
- <Link>Resources\dashboard-ui\bower_components\paper-slider\bower.json</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\paper-slider\hero.svg">
- <Link>Resources\dashboard-ui\bower_components\paper-slider\hero.svg</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\paper-slider\index.html">
- <Link>Resources\dashboard-ui\bower_components\paper-slider\index.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\paper-slider\paper-slider.html">
- <Link>Resources\dashboard-ui\bower_components\paper-slider\paper-slider.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\paper-slider\demo\index.html">
- <Link>Resources\dashboard-ui\bower_components\paper-slider\demo\index.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\paper-slider\test\a11y.html">
- <Link>Resources\dashboard-ui\bower_components\paper-slider\test\a11y.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\paper-slider\test\basic.html">
- <Link>Resources\dashboard-ui\bower_components\paper-slider\test\basic.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\paper-slider\test\index.html">
- <Link>Resources\dashboard-ui\bower_components\paper-slider\test\index.html</Link>
- </BundleResource>
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\paper-spinner\.bower.json">
<Link>Resources\dashboard-ui\bower_components\paper-spinner\.bower.json</Link>
</BundleResource>
@@ -5445,30 +4770,6 @@
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\paper-tabs\test\index.html">
<Link>Resources\dashboard-ui\bower_components\paper-tabs\test\index.html</Link>
</BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\paper-toast\.bower.json">
- <Link>Resources\dashboard-ui\bower_components\paper-toast\.bower.json</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\paper-toast\.gitignore">
- <Link>Resources\dashboard-ui\bower_components\paper-toast\.gitignore</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\paper-toast\README.md">
- <Link>Resources\dashboard-ui\bower_components\paper-toast\README.md</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\paper-toast\bower.json">
- <Link>Resources\dashboard-ui\bower_components\paper-toast\bower.json</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\paper-toast\hero.svg">
- <Link>Resources\dashboard-ui\bower_components\paper-toast\hero.svg</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\paper-toast\index.html">
- <Link>Resources\dashboard-ui\bower_components\paper-toast\index.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\paper-toast\paper-toast.html">
- <Link>Resources\dashboard-ui\bower_components\paper-toast\paper-toast.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\paper-toast\demo\index.html">
- <Link>Resources\dashboard-ui\bower_components\paper-toast\demo\index.html</Link>
- </BundleResource>
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\paper-toggle-button\.bower.json">
<Link>Resources\dashboard-ui\bower_components\paper-toggle-button\.bower.json</Link>
</BundleResource>
@@ -6462,6 +5763,9 @@
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism-element\prism-import.html">
<Link>Resources\dashboard-ui\bower_components\prism-element\prism-import.html</Link>
</BundleResource>
+ <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism-element\prism-theme-default.html">
+ <Link>Resources\dashboard-ui\bower_components\prism-element\prism-theme-default.html</Link>
+ </BundleResource>
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\query-string\.bower.json">
<Link>Resources\dashboard-ui\bower_components\query-string\.bower.json</Link>
</BundleResource>
@@ -6621,14 +5925,11 @@
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\components\remotecontrolautoplay.js">
<Link>Resources\dashboard-ui\components\remotecontrolautoplay.js</Link>
</BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\components\scrollthreshold.js">
- <Link>Resources\dashboard-ui\components\scrollthreshold.js</Link>
- </BundleResource>
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\components\viewcontainer-lite.js">
<Link>Resources\dashboard-ui\components\viewcontainer-lite.js</Link>
</BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\components\collectioneditor\collectioneditor.js">
- <Link>Resources\dashboard-ui\components\collectioneditor\collectioneditor.js</Link>
+ <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\components\channelmapper\channelmapper.js">
+ <Link>Resources\dashboard-ui\components\channelmapper\channelmapper.js</Link>
</BundleResource>
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\components\directorybrowser\directorybrowser.js">
<Link>Resources\dashboard-ui\components\directorybrowser\directorybrowser.js</Link>
@@ -6672,12 +5973,6 @@
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\components\imageuploader\imageuploader.template.html">
<Link>Resources\dashboard-ui\components\imageuploader\imageuploader.template.html</Link>
</BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\components\ironcardlist\ironcardlist.js">
- <Link>Resources\dashboard-ui\components\ironcardlist\ironcardlist.js</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\components\ironcardlist\ironcardlist.template.html">
- <Link>Resources\dashboard-ui\components\ironcardlist\ironcardlist.template.html</Link>
- </BundleResource>
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\components\itemidentifier\itemidentifier.js">
<Link>Resources\dashboard-ui\components\itemidentifier\itemidentifier.js</Link>
</BundleResource>
@@ -6708,14 +6003,11 @@
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\components\metadataeditor\personeditor.template.html">
<Link>Resources\dashboard-ui\components\metadataeditor\personeditor.template.html</Link>
</BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\components\playlisteditor\playlisteditor.js">
- <Link>Resources\dashboard-ui\components\playlisteditor\playlisteditor.js</Link>
+ <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\components\navdrawer\navdrawer.css">
+ <Link>Resources\dashboard-ui\components\navdrawer\navdrawer.css</Link>
</BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\components\subtitleeditor\subtitleeditor.js">
- <Link>Resources\dashboard-ui\components\subtitleeditor\subtitleeditor.js</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\components\subtitleeditor\subtitleeditor.template.html">
- <Link>Resources\dashboard-ui\components\subtitleeditor\subtitleeditor.template.html</Link>
+ <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\components\navdrawer\navdrawer.js">
+ <Link>Resources\dashboard-ui\components\navdrawer\navdrawer.js</Link>
</BundleResource>
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\components\tvproviders\schedulesdirect.js">
<Link>Resources\dashboard-ui\components\tvproviders\schedulesdirect.js</Link>
@@ -6723,12 +6015,21 @@
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\components\tvproviders\schedulesdirect.template.html">
<Link>Resources\dashboard-ui\components\tvproviders\schedulesdirect.template.html</Link>
</BundleResource>
+ <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\components\tvproviders\xmltv.js">
+ <Link>Resources\dashboard-ui\components\tvproviders\xmltv.js</Link>
+ </BundleResource>
+ <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\components\tvproviders\xmltv.template.html">
+ <Link>Resources\dashboard-ui\components\tvproviders\xmltv.template.html</Link>
+ </BundleResource>
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\css\card.css">
<Link>Resources\dashboard-ui\css\card.css</Link>
</BundleResource>
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\css\chromecast.css">
<Link>Resources\dashboard-ui\css\chromecast.css</Link>
</BundleResource>
+ <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\css\dashboard.css">
+ <Link>Resources\dashboard-ui\css\dashboard.css</Link>
+ </BundleResource>
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\css\detailtable.css">
<Link>Resources\dashboard-ui\css\detailtable.css</Link>
</BundleResource>
@@ -6756,9 +6057,6 @@
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\css\nowplayingbar.css">
<Link>Resources\dashboard-ui\css\nowplayingbar.css</Link>
</BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\css\search.css">
- <Link>Resources\dashboard-ui\css\search.css</Link>
- </BundleResource>
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\css\site.css">
<Link>Resources\dashboard-ui\css\site.css</Link>
</BundleResource>
@@ -7035,18 +6333,9 @@
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\css\images\userdata\password.png">
<Link>Resources\dashboard-ui\css\images\userdata\password.png</Link>
</BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\css\polymer\paper-icon-button-light.css">
- <Link>Resources\dashboard-ui\css\polymer\paper-icon-button-light.css</Link>
- </BundleResource>
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\devices\android\android.css">
<Link>Resources\dashboard-ui\devices\android\android.css</Link>
</BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\devices\ie\ie.css">
- <Link>Resources\dashboard-ui\devices\ie\ie.css</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\devices\ie\ie.js">
- <Link>Resources\dashboard-ui\devices\ie\ie.js</Link>
- </BundleResource>
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\devices\ios\ios.css">
<Link>Resources\dashboard-ui\devices\ios\ios.css</Link>
</BundleResource>
@@ -7074,9 +6363,6 @@
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\scripts\addpluginpage.js">
<Link>Resources\dashboard-ui\scripts\addpluginpage.js</Link>
</BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\scripts\alphapicker.js">
- <Link>Resources\dashboard-ui\scripts\alphapicker.js</Link>
- </BundleResource>
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\scripts\appservices.js">
<Link>Resources\dashboard-ui\scripts\appservices.js</Link>
</BundleResource>
@@ -7203,12 +6489,12 @@
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\scripts\itemlistpage.js">
<Link>Resources\dashboard-ui\scripts\itemlistpage.js</Link>
</BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\scripts\kids.js">
- <Link>Resources\dashboard-ui\scripts\kids.js</Link>
- </BundleResource>
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\scripts\librarybrowser.js">
<Link>Resources\dashboard-ui\scripts\librarybrowser.js</Link>
</BundleResource>
+ <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\scripts\librarydisplay.js">
+ <Link>Resources\dashboard-ui\scripts\librarydisplay.js</Link>
+ </BundleResource>
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\scripts\librarylist.js">
<Link>Resources\dashboard-ui\scripts\librarylist.js</Link>
</BundleResource>
@@ -7320,9 +6606,6 @@
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\scripts\movietrailers.js">
<Link>Resources\dashboard-ui\scripts\movietrailers.js</Link>
</BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\scripts\musicalbumartists.js">
- <Link>Resources\dashboard-ui\scripts\musicalbumartists.js</Link>
- </BundleResource>
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\scripts\musicalbums.js">
<Link>Resources\dashboard-ui\scripts\musicalbums.js</Link>
</BundleResource>
@@ -7386,9 +6669,6 @@
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\scripts\playlistedit.js">
<Link>Resources\dashboard-ui\scripts\playlistedit.js</Link>
</BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\scripts\playlistmanager.js">
- <Link>Resources\dashboard-ui\scripts\playlistmanager.js</Link>
- </BundleResource>
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\scripts\playlists.js">
<Link>Resources\dashboard-ui\scripts\playlists.js</Link>
</BundleResource>
@@ -7416,12 +6696,6 @@
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\scripts\scheduledtaskspage.js">
<Link>Resources\dashboard-ui\scripts\scheduledtaskspage.js</Link>
</BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\scripts\search.js">
- <Link>Resources\dashboard-ui\scripts\search.js</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\scripts\searchmenu.js">
- <Link>Resources\dashboard-ui\scripts\searchmenu.js</Link>
- </BundleResource>
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\scripts\searchpage.js">
<Link>Resources\dashboard-ui\scripts\searchpage.js</Link>
</BundleResource>
@@ -7503,12 +6777,21 @@
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\scripts\userpassword.js">
<Link>Resources\dashboard-ui\scripts\userpassword.js</Link>
</BundleResource>
+ <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\scripts\userpasswordpage.js">
+ <Link>Resources\dashboard-ui\scripts\userpasswordpage.js</Link>
+ </BundleResource>
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\scripts\userprofilespage.js">
<Link>Resources\dashboard-ui\scripts\userprofilespage.js</Link>
</BundleResource>
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\scripts\wizardagreement.js">
<Link>Resources\dashboard-ui\scripts\wizardagreement.js</Link>
</BundleResource>
+ <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\scripts\wizardcomponents.js">
+ <Link>Resources\dashboard-ui\scripts\wizardcomponents.js</Link>
+ </BundleResource>
+ <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\scripts\wizardcontroller.js">
+ <Link>Resources\dashboard-ui\scripts\wizardcontroller.js</Link>
+ </BundleResource>
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\scripts\wizardfinishpage.js">
<Link>Resources\dashboard-ui\scripts\wizardfinishpage.js</Link>
</BundleResource>
diff --git a/MediaBrowser.Server.Mac/Native/BaseMonoApp.cs b/MediaBrowser.Server.Mac/Native/BaseMonoApp.cs
index 0b2f533a4..7c9b43026 100644
--- a/MediaBrowser.Server.Mac/Native/BaseMonoApp.cs
+++ b/MediaBrowser.Server.Mac/Native/BaseMonoApp.cs
@@ -10,6 +10,7 @@ using MediaBrowser.Controller.Power;
using MediaBrowser.Server.Implementations.Persistence;
using MediaBrowser.Server.Startup.Common.FFMpeg;
using System.Diagnostics;
+using MediaBrowser.Model.System;
namespace MediaBrowser.Server.Mac
{
@@ -166,7 +167,7 @@ namespace MediaBrowser.Server.Mac
switch (environment.SystemArchitecture)
{
- case Architecture.X86_X64:
+ case Architecture.X64:
info.Version = "20160124";
break;
case Architecture.X86:
@@ -183,16 +184,11 @@ namespace MediaBrowser.Server.Mac
{
switch (environment.SystemArchitecture)
{
- case Architecture.X86_X64:
+ case Architecture.X64:
return new[]
{
"https://github.com/MediaBrowser/Emby.Resources/raw/master/ffmpeg/osx/ffmpeg-x64-2.8.5.7z"
};
- case Architecture.X86:
- return new[]
- {
- "https://github.com/MediaBrowser/Emby.Resources/raw/master/ffmpeg/osx/ffmpeg-x86-2.5.3.7z"
- };
}
// No version available
@@ -230,7 +226,7 @@ namespace MediaBrowser.Server.Mac
}
else if (string.Equals(uname.machine, "x86_64", StringComparison.OrdinalIgnoreCase))
{
- info.SystemArchitecture = Architecture.X86_X64;
+ info.SystemArchitecture = Architecture.X64;
}
else if (uname.machine.StartsWith("arm", StringComparison.OrdinalIgnoreCase))
{
diff --git a/MediaBrowser.Server.Mac/Native/DbConnector.cs b/MediaBrowser.Server.Mac/Native/DbConnector.cs
new file mode 100644
index 000000000..4c19210b8
--- /dev/null
+++ b/MediaBrowser.Server.Mac/Native/DbConnector.cs
@@ -0,0 +1,24 @@
+using System;
+using System.Data;
+using System.Data.SQLite;
+using System.Threading.Tasks;
+using MediaBrowser.Model.Logging;
+using MediaBrowser.Server.Implementations.Persistence;
+
+namespace MediaBrowser.Server.Mac
+{
+ public class DbConnector : IDbConnector
+ {
+ private readonly ILogger _logger;
+
+ public DbConnector(ILogger logger)
+ {
+ _logger = logger;
+ }
+
+ public Task<IDbConnection> Connect(string dbPath, bool isReadOnly, bool enablePooling = false, int? cacheSize = null)
+ {
+ return SqliteExtensions.ConnectToDb(dbPath, isReadOnly, enablePooling, cacheSize, _logger);
+ }
+ }
+} \ No newline at end of file
diff --git a/MediaBrowser.Server.Mac/Native/SqliteExtensions.cs b/MediaBrowser.Server.Mac/Native/SqliteExtensions.cs
deleted file mode 100644
index a05bb3f26..000000000
--- a/MediaBrowser.Server.Mac/Native/SqliteExtensions.cs
+++ /dev/null
@@ -1,62 +0,0 @@
-using System;
-using System.Data;
-using System.Data.SQLite;
-using System.Threading.Tasks;
-using MediaBrowser.Model.Logging;
-using MediaBrowser.Server.Implementations.Persistence;
-
-namespace MediaBrowser.Server.Mac
-{
- /// <summary>
- /// Class SQLiteExtensions
- /// </summary>
- static class SqliteExtensions
- {
- /// <summary>
- /// Connects to db.
- /// </summary>
- /// <param name="dbPath">The db path.</param>
- /// <param name="logger">The logger.</param>
- /// <returns>Task{IDbConnection}.</returns>
- /// <exception cref="System.ArgumentNullException">dbPath</exception>
- public static async Task<IDbConnection> ConnectToDb(string dbPath, ILogger logger)
- {
- if (string.IsNullOrEmpty(dbPath))
- {
- throw new ArgumentNullException("dbPath");
- }
-
- logger.Info("Sqlite {0} opening {1}", SQLiteConnection.SQLiteVersion, dbPath);
-
- var connectionstr = new SQLiteConnectionStringBuilder
- {
- PageSize = 4096,
- CacheSize = 2000,
- SyncMode = SynchronizationModes.Full,
- DataSource = dbPath,
- JournalMode = SQLiteJournalModeEnum.Wal
- };
-
- var connection = new SQLiteConnection(connectionstr.ConnectionString);
-
- await connection.OpenAsync().ConfigureAwait(false);
-
- return connection;
- }
- }
-
- public class DbConnector : IDbConnector
- {
- private readonly ILogger _logger;
-
- public DbConnector(ILogger logger)
- {
- _logger = logger;
- }
-
- public Task<IDbConnection> Connect(string dbPath)
- {
- return SqliteExtensions.ConnectToDb(dbPath, _logger);
- }
- }
-} \ No newline at end of file
diff --git a/MediaBrowser.Server.Mono/MediaBrowser.Server.Mono.csproj b/MediaBrowser.Server.Mono/MediaBrowser.Server.Mono.csproj
index b71877e17..bcbb10174 100644
--- a/MediaBrowser.Server.Mono/MediaBrowser.Server.Mono.csproj
+++ b/MediaBrowser.Server.Mono/MediaBrowser.Server.Mono.csproj
@@ -23,6 +23,7 @@
<WarningLevel>4</WarningLevel>
<PlatformTarget>x86</PlatformTarget>
<Externalconsole>true</Externalconsole>
+ <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|x86' ">
<DebugType>full</DebugType>
@@ -33,6 +34,7 @@
<PlatformTarget>AnyCPU</PlatformTarget>
<Externalconsole>true</Externalconsole>
<Prefer32Bit>false</Prefer32Bit>
+ <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<Optimize>false</Optimize>
@@ -40,6 +42,7 @@
<WarningLevel>4</WarningLevel>
<PlatformTarget>AnyCPU</PlatformTarget>
<Prefer32Bit>false</Prefer32Bit>
+ <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release Mono|AnyCPU' ">
<Optimize>false</Optimize>
@@ -47,9 +50,11 @@
<WarningLevel>4</WarningLevel>
<PlatformTarget>AnyCPU</PlatformTarget>
<Prefer32Bit>false</Prefer32Bit>
+ <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release Mono|x86'">
<Prefer32Bit>false</Prefer32Bit>
+ <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
<ItemGroup>
<Reference Include="CommonIO, Version=1.0.0.0, Culture=neutral, processorArchitecture=MSIL">
@@ -60,11 +65,6 @@
<SpecificVersion>False</SpecificVersion>
<HintPath>..\packages\Mono.Posix.4.0.0.0\lib\net40\Mono.Posix.dll</HintPath>
</Reference>
- <Reference Include="Mono.Security, Version=4.0.0.0, Culture=neutral, PublicKeyToken=0738eb9f132ed756, processorArchitecture=MSIL">
- <SpecificVersion>False</SpecificVersion>
- <HintPath>..\ThirdParty\Mono.Security\Mono.Security.dll</HintPath>
- <Private>False</Private>
- </Reference>
<Reference Include="Patterns.Logging, Version=1.0.5494.41209, Culture=neutral, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<HintPath>..\packages\Patterns.Logging.1.0.0.2\lib\portable-net45+sl4+wp71+win8+wpa81\Patterns.Logging.dll</HintPath>
@@ -82,16 +82,35 @@
</Reference>
</ItemGroup>
<ItemGroup>
+ <Compile Include="..\MediaBrowser.Server.Implementations\Persistence\SqliteExtensions.cs">
+ <Link>Native\SqliteExtensions.cs</Link>
+ </Compile>
<Compile Include="..\SharedVersion.cs">
<Link>Properties\SharedVersion.cs</Link>
</Compile>
<Compile Include="Native\BaseMonoApp.cs" />
- <Compile Include="Native\SqliteExtensions.cs" />
+ <Compile Include="Native\DbConnector.cs" />
<Compile Include="Networking\CertificateGenerator.cs" />
<Compile Include="Program.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Native\NativeApp.cs" />
<Compile Include="Networking\NetworkManager.cs" />
+ <Compile Include="Security\ASN1.cs" />
+ <Compile Include="Security\ASN1Convert.cs" />
+ <Compile Include="Security\BitConverterLE.cs" />
+ <Compile Include="Security\CryptoConvert.cs" />
+ <Compile Include="Security\PKCS1.cs" />
+ <Compile Include="Security\PKCS12.cs" />
+ <Compile Include="Security\PKCS7.cs" />
+ <Compile Include="Security\PKCS8.cs" />
+ <Compile Include="Security\X501Name.cs" />
+ <Compile Include="Security\X509Builder.cs" />
+ <Compile Include="Security\X509Certificate.cs" />
+ <Compile Include="Security\X509CertificateBuilder.cs" />
+ <Compile Include="Security\X509CertificateCollection.cs" />
+ <Compile Include="Security\X509Extension.cs" />
+ <Compile Include="Security\X509Extensions.cs" />
+ <Compile Include="Security\X520Attributes.cs" />
</ItemGroup>
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
<ItemGroup>
diff --git a/MediaBrowser.Server.Mono/Native/BaseMonoApp.cs b/MediaBrowser.Server.Mono/Native/BaseMonoApp.cs
index a012a19a3..19ae7b4d2 100644
--- a/MediaBrowser.Server.Mono/Native/BaseMonoApp.cs
+++ b/MediaBrowser.Server.Mono/Native/BaseMonoApp.cs
@@ -9,6 +9,7 @@ using System.Collections.Generic;
using System.Reflection;
using System.Text.RegularExpressions;
using MediaBrowser.Controller.Power;
+using MediaBrowser.Model.System;
using MediaBrowser.Server.Implementations.Persistence;
using MediaBrowser.Server.Startup.Common.FFMpeg;
using OperatingSystem = MediaBrowser.Server.Startup.Common.OperatingSystem;
@@ -176,7 +177,7 @@ namespace MediaBrowser.Server.Mono.Native
}
else if (string.Equals(uname.machine, "x86_64", StringComparison.OrdinalIgnoreCase))
{
- info.SystemArchitecture = Architecture.X86_X64;
+ info.SystemArchitecture = Architecture.X64;
}
else if (uname.machine.StartsWith("arm", StringComparison.OrdinalIgnoreCase))
{
@@ -247,6 +248,7 @@ namespace MediaBrowser.Server.Mono.Native
switch (environment.OperatingSystem)
{
+ case OperatingSystem.Osx:
case OperatingSystem.Bsd:
break;
case OperatingSystem.Linux:
@@ -254,20 +256,6 @@ namespace MediaBrowser.Server.Mono.Native
info.ArchiveType = "7z";
info.Version = "20160215";
break;
- case OperatingSystem.Osx:
-
- info.ArchiveType = "7z";
-
- switch (environment.SystemArchitecture)
- {
- case Architecture.X86_X64:
- info.Version = "20160124";
- break;
- case Architecture.X86:
- info.Version = "20150110";
- break;
- }
- break;
}
info.DownloadUrls = GetDownloadUrls(environment);
@@ -279,42 +267,15 @@ namespace MediaBrowser.Server.Mono.Native
{
switch (environment.OperatingSystem)
{
- case OperatingSystem.Osx:
-
- switch (environment.SystemArchitecture)
- {
- case Architecture.X86_X64:
- return new[]
- {
- "https://github.com/MediaBrowser/Emby.Resources/raw/master/ffmpeg/osx/ffmpeg-x64-2.8.5.7z"
- };
- case Architecture.X86:
- return new[]
- {
- "https://github.com/MediaBrowser/Emby.Resources/raw/master/ffmpeg/osx/ffmpeg-x86-2.5.3.7z"
- };
- }
- break;
-
case OperatingSystem.Linux:
switch (environment.SystemArchitecture)
{
- case Architecture.X86_X64:
+ case Architecture.X64:
return new[]
{
"https://github.com/MediaBrowser/Emby.Resources/raw/master/ffmpeg/linux/ffmpeg-git-20160215-64bit-static.7z"
};
- case Architecture.X86:
- return new[]
- {
- "https://github.com/MediaBrowser/Emby.Resources/raw/master/ffmpeg/linux/ffmpeg-git-20160215-32bit-static.7z"
- };
- case Architecture.Arm:
- return new[]
- {
- "https://github.com/MediaBrowser/Emby.Resources/raw/master/ffmpeg/linux/ffmpeg-arm.7z"
- };
}
break;
}
diff --git a/MediaBrowser.Server.Mono/Native/DbConnector.cs b/MediaBrowser.Server.Mono/Native/DbConnector.cs
new file mode 100644
index 000000000..5ad3ecfef
--- /dev/null
+++ b/MediaBrowser.Server.Mono/Native/DbConnector.cs
@@ -0,0 +1,24 @@
+using System;
+using System.Data;
+using System.Data.SQLite;
+using System.Threading.Tasks;
+using MediaBrowser.Model.Logging;
+using MediaBrowser.Server.Implementations.Persistence;
+
+namespace MediaBrowser.Server.Mono.Native
+{
+ public class DbConnector : IDbConnector
+ {
+ private readonly ILogger _logger;
+
+ public DbConnector(ILogger logger)
+ {
+ _logger = logger;
+ }
+
+ public Task<IDbConnection> Connect(string dbPath, bool isReadOnly, bool enablePooling = false, int? cacheSize = null)
+ {
+ return SqliteExtensions.ConnectToDb(dbPath, isReadOnly, enablePooling, cacheSize, _logger);
+ }
+ }
+} \ No newline at end of file
diff --git a/MediaBrowser.Server.Mono/Native/SqliteExtensions.cs b/MediaBrowser.Server.Mono/Native/SqliteExtensions.cs
deleted file mode 100644
index 385a7d0c5..000000000
--- a/MediaBrowser.Server.Mono/Native/SqliteExtensions.cs
+++ /dev/null
@@ -1,62 +0,0 @@
-using System;
-using System.Data;
-using System.Data.SQLite;
-using System.Threading.Tasks;
-using MediaBrowser.Model.Logging;
-using MediaBrowser.Server.Implementations.Persistence;
-
-namespace MediaBrowser.Server.Mono.Native
-{
- /// <summary>
- /// Class SQLiteExtensions
- /// </summary>
- static class SqliteExtensions
- {
- /// <summary>
- /// Connects to db.
- /// </summary>
- /// <param name="dbPath">The db path.</param>
- /// <param name="logger">The logger.</param>
- /// <returns>Task{IDbConnection}.</returns>
- /// <exception cref="System.ArgumentNullException">dbPath</exception>
- public static async Task<IDbConnection> ConnectToDb(string dbPath, ILogger logger)
- {
- if (string.IsNullOrEmpty(dbPath))
- {
- throw new ArgumentNullException("dbPath");
- }
-
- logger.Info("Sqlite {0} opening {1}", SQLiteConnection.SQLiteVersion, dbPath);
-
- var connectionstr = new SQLiteConnectionStringBuilder
- {
- PageSize = 4096,
- CacheSize = 2000,
- SyncMode = SynchronizationModes.Full,
- DataSource = dbPath,
- JournalMode = SQLiteJournalModeEnum.Wal
- };
-
- var connection = new SQLiteConnection(connectionstr.ConnectionString);
-
- await connection.OpenAsync().ConfigureAwait(false);
-
- return connection;
- }
- }
-
- public class DbConnector : IDbConnector
- {
- private readonly ILogger _logger;
-
- public DbConnector(ILogger logger)
- {
- _logger = logger;
- }
-
- public Task<IDbConnection> Connect(string dbPath)
- {
- return SqliteExtensions.ConnectToDb(dbPath, _logger);
- }
- }
-} \ No newline at end of file
diff --git a/MediaBrowser.Server.Mono/Security/ASN1.cs b/MediaBrowser.Server.Mono/Security/ASN1.cs
new file mode 100644
index 000000000..751a2ece4
--- /dev/null
+++ b/MediaBrowser.Server.Mono/Security/ASN1.cs
@@ -0,0 +1,344 @@
+//
+// ASN1.cs: Abstract Syntax Notation 1 - micro-parser and generator
+//
+// Authors:
+// Sebastien Pouliot <sebastien@ximian.com>
+// Jesper Pedersen <jep@itplus.dk>
+//
+// (C) 2002, 2003 Motus Technologies Inc. (http://www.motus.com)
+// Copyright (C) 2004 Novell, Inc (http://www.novell.com)
+// (C) 2004 IT+ A/S (http://www.itplus.dk)
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+using System;
+using System.Collections;
+using System.IO;
+using System.Text;
+
+namespace Mono.Security {
+
+ // References:
+ // a. ITU ASN.1 standards (free download)
+ // http://www.itu.int/ITU-T/studygroups/com17/languages/
+
+#if INSIDE_CORLIB
+ internal
+#else
+ public
+#endif
+ class ASN1 {
+
+ private byte m_nTag;
+ private byte[] m_aValue;
+ private ArrayList elist;
+
+ public ASN1 () : this (0x00, null) {}
+
+ public ASN1 (byte tag) : this (tag, null) {}
+
+ public ASN1 (byte tag, byte[] data)
+ {
+ m_nTag = tag;
+ m_aValue = data;
+ }
+
+ public ASN1 (byte[] data)
+ {
+ m_nTag = data [0];
+
+ int nLenLength = 0;
+ int nLength = data [1];
+
+ if (nLength > 0x80) {
+ // composed length
+ nLenLength = nLength - 0x80;
+ nLength = 0;
+ for (int i = 0; i < nLenLength; i++) {
+ nLength *= 256;
+ nLength += data [i + 2];
+ }
+ }
+ else if (nLength == 0x80) {
+ // undefined length encoding
+ throw new NotSupportedException ("Undefined length encoding.");
+ }
+
+ m_aValue = new byte [nLength];
+ Buffer.BlockCopy (data, (2 + nLenLength), m_aValue, 0, nLength);
+
+ if ((m_nTag & 0x20) == 0x20) {
+ int nStart = (2 + nLenLength);
+ Decode (data, ref nStart, data.Length);
+ }
+ }
+
+ public int Count {
+ get {
+ if (elist == null)
+ return 0;
+ return elist.Count;
+ }
+ }
+
+ public byte Tag {
+ get { return m_nTag; }
+ }
+
+ public int Length {
+ get {
+ if (m_aValue != null)
+ return m_aValue.Length;
+ else
+ return 0;
+ }
+ }
+
+ public byte[] Value {
+ get {
+ if (m_aValue == null)
+ GetBytes ();
+ return (byte[]) m_aValue.Clone ();
+ }
+ set {
+ if (value != null)
+ m_aValue = (byte[]) value.Clone ();
+ }
+ }
+
+ private bool CompareArray (byte[] array1, byte[] array2)
+ {
+ bool bResult = (array1.Length == array2.Length);
+ if (bResult) {
+ for (int i = 0; i < array1.Length; i++) {
+ if (array1[i] != array2[i])
+ return false;
+ }
+ }
+ return bResult;
+ }
+
+ public bool Equals (byte[] asn1)
+ {
+ return CompareArray (this.GetBytes (), asn1);
+ }
+
+ public bool CompareValue (byte[] value)
+ {
+ return CompareArray (m_aValue, value);
+ }
+
+ public ASN1 Add (ASN1 asn1)
+ {
+ if (asn1 != null) {
+ if (elist == null)
+ elist = new ArrayList ();
+ elist.Add (asn1);
+ }
+ return asn1;
+ }
+
+ public virtual byte[] GetBytes ()
+ {
+ byte[] val = null;
+
+ if (Count > 0) {
+ int esize = 0;
+ ArrayList al = new ArrayList ();
+ foreach (ASN1 a in elist) {
+ byte[] item = a.GetBytes ();
+ al.Add (item);
+ esize += item.Length;
+ }
+ val = new byte [esize];
+ int pos = 0;
+ for (int i=0; i < elist.Count; i++) {
+ byte[] item = (byte[]) al[i];
+ Buffer.BlockCopy (item, 0, val, pos, item.Length);
+ pos += item.Length;
+ }
+ } else if (m_aValue != null) {
+ val = m_aValue;
+ }
+
+ byte[] der;
+ int nLengthLen = 0;
+
+ if (val != null) {
+ int nLength = val.Length;
+ // special for length > 127
+ if (nLength > 127) {
+ if (nLength <= Byte.MaxValue) {
+ der = new byte [3 + nLength];
+ Buffer.BlockCopy (val, 0, der, 3, nLength);
+ nLengthLen = 0x81;
+ der[2] = (byte)(nLength);
+ }
+ else if (nLength <= UInt16.MaxValue) {
+ der = new byte [4 + nLength];
+ Buffer.BlockCopy (val, 0, der, 4, nLength);
+ nLengthLen = 0x82;
+ der[2] = (byte)(nLength >> 8);
+ der[3] = (byte)(nLength);
+ }
+ else if (nLength <= 0xFFFFFF) {
+ // 24 bits
+ der = new byte [5 + nLength];
+ Buffer.BlockCopy (val, 0, der, 5, nLength);
+ nLengthLen = 0x83;
+ der [2] = (byte)(nLength >> 16);
+ der [3] = (byte)(nLength >> 8);
+ der [4] = (byte)(nLength);
+ }
+ else {
+ // max (Length is an integer) 32 bits
+ der = new byte [6 + nLength];
+ Buffer.BlockCopy (val, 0, der, 6, nLength);
+ nLengthLen = 0x84;
+ der [2] = (byte)(nLength >> 24);
+ der [3] = (byte)(nLength >> 16);
+ der [4] = (byte)(nLength >> 8);
+ der [5] = (byte)(nLength);
+ }
+ }
+ else {
+ // basic case (no encoding)
+ der = new byte [2 + nLength];
+ Buffer.BlockCopy (val, 0, der, 2, nLength);
+ nLengthLen = nLength;
+ }
+ if (m_aValue == null)
+ m_aValue = val;
+ }
+ else
+ der = new byte[2];
+
+ der[0] = m_nTag;
+ der[1] = (byte)nLengthLen;
+
+ return der;
+ }
+
+ // Note: Recursive
+ protected void Decode (byte[] asn1, ref int anPos, int anLength)
+ {
+ byte nTag;
+ int nLength;
+ byte[] aValue;
+
+ // minimum is 2 bytes (tag + length of 0)
+ while (anPos < anLength - 1) {
+ DecodeTLV (asn1, ref anPos, out nTag, out nLength, out aValue);
+ // sometimes we get trailing 0
+ if (nTag == 0)
+ continue;
+
+ ASN1 elm = Add (new ASN1 (nTag, aValue));
+
+ if ((nTag & 0x20) == 0x20) {
+ int nConstructedPos = anPos;
+ elm.Decode (asn1, ref nConstructedPos, nConstructedPos + nLength);
+ }
+ anPos += nLength; // value length
+ }
+ }
+
+ // TLV : Tag - Length - Value
+ protected void DecodeTLV (byte[] asn1, ref int pos, out byte tag, out int length, out byte[] content)
+ {
+ tag = asn1 [pos++];
+ length = asn1 [pos++];
+
+ // special case where L contains the Length of the Length + 0x80
+ if ((length & 0x80) == 0x80) {
+ int nLengthLen = length & 0x7F;
+ length = 0;
+ for (int i = 0; i < nLengthLen; i++)
+ length = length * 256 + asn1 [pos++];
+ }
+
+ content = new byte [length];
+ Buffer.BlockCopy (asn1, pos, content, 0, length);
+ }
+
+ public ASN1 this [int index] {
+ get {
+ try {
+ if ((elist == null) || (index >= elist.Count))
+ return null;
+ return (ASN1) elist [index];
+ }
+ catch (ArgumentOutOfRangeException) {
+ return null;
+ }
+ }
+ }
+
+ public ASN1 Element (int index, byte anTag)
+ {
+ try {
+ if ((elist == null) || (index >= elist.Count))
+ return null;
+
+ ASN1 elm = (ASN1) elist [index];
+ if (elm.Tag == anTag)
+ return elm;
+ else
+ return null;
+ }
+ catch (ArgumentOutOfRangeException) {
+ return null;
+ }
+ }
+
+ public override string ToString()
+ {
+ StringBuilder hexLine = new StringBuilder ();
+
+ // Add tag
+ hexLine.AppendFormat ("Tag: {0} {1}", m_nTag.ToString ("X2"), Environment.NewLine);
+
+ // Add length
+ hexLine.AppendFormat ("Length: {0} {1}", Value.Length, Environment.NewLine);
+
+ // Add value
+ hexLine.Append ("Value: ");
+ hexLine.Append (Environment.NewLine);
+ for (int i = 0; i < Value.Length; i++) {
+ hexLine.AppendFormat ("{0} ", Value [i].ToString ("X2"));
+ if ((i+1) % 16 == 0)
+ hexLine.AppendFormat (Environment.NewLine);
+ }
+ return hexLine.ToString ();
+ }
+
+ public void SaveToFile (string filename)
+ {
+ if (filename == null)
+ throw new ArgumentNullException ("filename");
+
+ using (FileStream fs = File.Create (filename)) {
+ byte[] data = GetBytes ();
+ fs.Write (data, 0, data.Length);
+ }
+ }
+ }
+}
diff --git a/MediaBrowser.Server.Mono/Security/ASN1Convert.cs b/MediaBrowser.Server.Mono/Security/ASN1Convert.cs
new file mode 100644
index 000000000..3a1cf9301
--- /dev/null
+++ b/MediaBrowser.Server.Mono/Security/ASN1Convert.cs
@@ -0,0 +1,212 @@
+//
+// ASN1Convert.cs: Abstract Syntax Notation 1 convertion routines
+//
+// Authors:
+// Sebastien Pouliot <sebastien@ximian.com>
+// Jesper Pedersen <jep@itplus.dk>
+//
+// (C) 2003 Motus Technologies Inc. (http://www.motus.com)
+// (C) 2004 IT+ A/S (http://www.itplus.dk)
+// Copyright (C) 2004-2007 Novell, Inc (http://www.novell.com)
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+using System;
+using System.Collections;
+using System.Globalization;
+using System.Security.Cryptography;
+using System.Text;
+
+namespace Mono.Security {
+
+ // References:
+ // a. ITU ASN.1 standards (free download)
+ // http://www.itu.int/ITU-T/studygroups/com17/languages/
+
+#if INSIDE_CORLIB
+ internal
+#else
+ public
+#endif
+ static class ASN1Convert {
+ // RFC3280, section 4.2.1.5
+ // CAs conforming to this profile MUST always encode certificate
+ // validity dates through the year 2049 as UTCTime; certificate validity
+ // dates in 2050 or later MUST be encoded as GeneralizedTime.
+
+ // Under 1.x this API requires a Local datetime to be provided
+ // Under 2.0 it will also accept a Utc datetime
+ static public ASN1 FromDateTime (DateTime dt)
+ {
+ if (dt.Year < 2050) {
+ // UTCTIME
+ return new ASN1 (0x17, Encoding.ASCII.GetBytes (
+ dt.ToUniversalTime ().ToString ("yyMMddHHmmss",
+ CultureInfo.InvariantCulture) + "Z"));
+ }
+ else {
+ // GENERALIZEDTIME
+ return new ASN1 (0x18, Encoding.ASCII.GetBytes (
+ dt.ToUniversalTime ().ToString ("yyyyMMddHHmmss",
+ CultureInfo.InvariantCulture) + "Z"));
+ }
+ }
+
+ static public ASN1 FromInt32 (Int32 value)
+ {
+ byte[] integer = BitConverterLE.GetBytes (value);
+ Array.Reverse (integer);
+ int x = 0;
+ while ((x < integer.Length) && (integer [x] == 0x00))
+ x++;
+ ASN1 asn1 = new ASN1 (0x02);
+ switch (x) {
+ case 0:
+ asn1.Value = integer;
+ break;
+ case 4:
+ asn1.Value = new byte [1];
+ break;
+ default:
+ byte[] smallerInt = new byte [4 - x];
+ Buffer.BlockCopy (integer, x, smallerInt, 0, smallerInt.Length);
+ asn1.Value = smallerInt;
+ break;
+ }
+ return asn1;
+ }
+
+ static public ASN1 FromOid (string oid)
+ {
+ if (oid == null)
+ throw new ArgumentNullException ("oid");
+
+ return new ASN1 (CryptoConfig.EncodeOID (oid));
+ }
+
+ static public ASN1 FromUnsignedBigInteger (byte[] big)
+ {
+ if (big == null)
+ throw new ArgumentNullException ("big");
+
+ // check for numbers that could be interpreted as negative (first bit)
+ if (big [0] >= 0x80) {
+ // in thie cas we add a new, empty, byte (position 0) so we're
+ // sure this will always be interpreted an unsigned integer.
+ // However we can't feed it into RSAParameters or DSAParameters
+ int length = big.Length + 1;
+ byte[] uinteger = new byte [length];
+ Buffer.BlockCopy (big, 0, uinteger, 1, length - 1);
+ big = uinteger;
+ }
+ return new ASN1 (0x02, big);
+ }
+
+ static public int ToInt32 (ASN1 asn1)
+ {
+ if (asn1 == null)
+ throw new ArgumentNullException ("asn1");
+ if (asn1.Tag != 0x02)
+ throw new FormatException ("Only integer can be converted");
+
+ int x = 0;
+ for (int i=0; i < asn1.Value.Length; i++)
+ x = (x << 8) + asn1.Value [i];
+ return x;
+ }
+
+ // Convert a binary encoded OID to human readable string representation of
+ // an OID (IETF style). Based on DUMPASN1.C from Peter Gutmann.
+ static public string ToOid (ASN1 asn1)
+ {
+ if (asn1 == null)
+ throw new ArgumentNullException ("asn1");
+
+ byte[] aOID = asn1.Value;
+ StringBuilder sb = new StringBuilder ();
+ // Pick apart the OID
+ byte x = (byte) (aOID[0] / 40);
+ byte y = (byte) (aOID[0] % 40);
+ if (x > 2) {
+ // Handle special case for large y if x = 2
+ y += (byte) ((x - 2) * 40);
+ x = 2;
+ }
+ sb.Append (x.ToString (CultureInfo.InvariantCulture));
+ sb.Append (".");
+ sb.Append (y.ToString (CultureInfo.InvariantCulture));
+ ulong val = 0;
+ for (x = 1; x < aOID.Length; x++) {
+ val = ((val << 7) | ((byte) (aOID [x] & 0x7F)));
+ if ( !((aOID [x] & 0x80) == 0x80)) {
+ sb.Append (".");
+ sb.Append (val.ToString (CultureInfo.InvariantCulture));
+ val = 0;
+ }
+ }
+ return sb.ToString ();
+ }
+
+ static public DateTime ToDateTime (ASN1 time)
+ {
+ if (time == null)
+ throw new ArgumentNullException ("time");
+
+ string t = Encoding.ASCII.GetString (time.Value);
+ // to support both UTCTime and GeneralizedTime (and not so common format)
+ string mask = null;
+ int year;
+ switch (t.Length) {
+ case 11:
+ // illegal format, still it's supported for compatibility
+ mask = "yyMMddHHmmZ";
+ break;
+ case 13:
+ // RFC3280: 4.1.2.5.1 UTCTime
+ year = Convert.ToInt16 (t.Substring (0, 2), CultureInfo.InvariantCulture);
+ // Where YY is greater than or equal to 50, the
+ // year SHALL be interpreted as 19YY; and
+ // Where YY is less than 50, the year SHALL be
+ // interpreted as 20YY.
+ if (year >= 50)
+ t = "19" + t;
+ else
+ t = "20" + t;
+ mask = "yyyyMMddHHmmssZ";
+ break;
+ case 15:
+ mask = "yyyyMMddHHmmssZ"; // GeneralizedTime
+ break;
+ case 17:
+ // another illegal format (990630000000+1000), again supported for compatibility
+ year = Convert.ToInt16 (t.Substring (0, 2), CultureInfo.InvariantCulture);
+ string century = (year >= 50) ? "19" : "20";
+ // ASN.1 (see ITU X.680 section 43.3) deals with offset differently than .NET
+ char sign = (t[12] == '+') ? '-' : '+';
+ t = String.Format ("{0}{1}{2}{3}{4}:{5}{6}", century, t.Substring (0, 12), sign,
+ t[13], t[14], t[15], t[16]);
+ mask = "yyyyMMddHHmmsszzz";
+ break;
+ }
+ return DateTime.ParseExact (t, mask, CultureInfo.InvariantCulture, DateTimeStyles.AdjustToUniversal);
+ }
+ }
+}
diff --git a/MediaBrowser.Server.Mono/Security/BitConverterLE.cs b/MediaBrowser.Server.Mono/Security/BitConverterLE.cs
new file mode 100644
index 000000000..a787f5015
--- /dev/null
+++ b/MediaBrowser.Server.Mono/Security/BitConverterLE.cs
@@ -0,0 +1,239 @@
+//
+// Mono.Security.BitConverterLE.cs
+// Like System.BitConverter but always little endian
+//
+// Author:
+// Bernie Solomon
+//
+
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+using System;
+
+namespace Mono.Security
+{
+ internal sealed class BitConverterLE
+ {
+ private BitConverterLE ()
+ {
+ }
+
+ unsafe private static byte[] GetUShortBytes (byte *bytes)
+ {
+ if (BitConverter.IsLittleEndian)
+ return new byte [] { bytes [0], bytes [1] };
+ else
+ return new byte [] { bytes [1], bytes [0] };
+ }
+
+ unsafe private static byte[] GetUIntBytes (byte *bytes)
+ {
+ if (BitConverter.IsLittleEndian)
+ return new byte [] { bytes [0], bytes [1], bytes [2], bytes [3] };
+ else
+ return new byte [] { bytes [3], bytes [2], bytes [1], bytes [0] };
+ }
+
+ unsafe private static byte[] GetULongBytes (byte *bytes)
+ {
+ if (BitConverter.IsLittleEndian)
+ return new byte [] { bytes [0], bytes [1], bytes [2], bytes [3],
+ bytes [4], bytes [5], bytes [6], bytes [7] };
+ else
+ return new byte [] { bytes [7], bytes [6], bytes [5], bytes [4],
+ bytes [3], bytes [2], bytes [1], bytes [0] };
+ }
+
+ unsafe internal static byte[] GetBytes (bool value)
+ {
+ return new byte [] { value ? (byte)1 : (byte)0 };
+ }
+
+ unsafe internal static byte[] GetBytes (char value)
+ {
+ return GetUShortBytes ((byte *) &value);
+ }
+
+ unsafe internal static byte[] GetBytes (short value)
+ {
+ return GetUShortBytes ((byte *) &value);
+ }
+
+ unsafe internal static byte[] GetBytes (int value)
+ {
+ return GetUIntBytes ((byte *) &value);
+ }
+
+ unsafe internal static byte[] GetBytes (long value)
+ {
+ return GetULongBytes ((byte *) &value);
+ }
+
+ unsafe internal static byte[] GetBytes (ushort value)
+ {
+ return GetUShortBytes ((byte *) &value);
+ }
+
+ unsafe internal static byte[] GetBytes (uint value)
+ {
+ return GetUIntBytes ((byte *) &value);
+ }
+
+ unsafe internal static byte[] GetBytes (ulong value)
+ {
+ return GetULongBytes ((byte *) &value);
+ }
+
+ unsafe internal static byte[] GetBytes (float value)
+ {
+ return GetUIntBytes ((byte *) &value);
+ }
+
+ unsafe internal static byte[] GetBytes (double value)
+ {
+ return GetULongBytes ((byte *) &value);
+ }
+
+ unsafe private static void UShortFromBytes (byte *dst, byte[] src, int startIndex)
+ {
+ if (BitConverter.IsLittleEndian) {
+ dst [0] = src [startIndex];
+ dst [1] = src [startIndex + 1];
+ } else {
+ dst [0] = src [startIndex + 1];
+ dst [1] = src [startIndex];
+ }
+ }
+
+ unsafe private static void UIntFromBytes (byte *dst, byte[] src, int startIndex)
+ {
+ if (BitConverter.IsLittleEndian) {
+ dst [0] = src [startIndex];
+ dst [1] = src [startIndex + 1];
+ dst [2] = src [startIndex + 2];
+ dst [3] = src [startIndex + 3];
+ } else {
+ dst [0] = src [startIndex + 3];
+ dst [1] = src [startIndex + 2];
+ dst [2] = src [startIndex + 1];
+ dst [3] = src [startIndex];
+ }
+ }
+
+ unsafe private static void ULongFromBytes (byte *dst, byte[] src, int startIndex)
+ {
+ if (BitConverter.IsLittleEndian) {
+ for (int i = 0; i < 8; ++i)
+ dst [i] = src [startIndex + i];
+ } else {
+ for (int i = 0; i < 8; ++i)
+ dst [i] = src [startIndex + (7 - i)];
+ }
+ }
+
+ unsafe internal static bool ToBoolean (byte[] value, int startIndex)
+ {
+ return value [startIndex] != 0;
+ }
+
+ unsafe internal static char ToChar (byte[] value, int startIndex)
+ {
+ char ret;
+
+ UShortFromBytes ((byte *) &ret, value, startIndex);
+
+ return ret;
+ }
+
+ unsafe internal static short ToInt16 (byte[] value, int startIndex)
+ {
+ short ret;
+
+ UShortFromBytes ((byte *) &ret, value, startIndex);
+
+ return ret;
+ }
+
+ unsafe internal static int ToInt32 (byte[] value, int startIndex)
+ {
+ int ret;
+
+ UIntFromBytes ((byte *) &ret, value, startIndex);
+
+ return ret;
+ }
+
+ unsafe internal static long ToInt64 (byte[] value, int startIndex)
+ {
+ long ret;
+
+ ULongFromBytes ((byte *) &ret, value, startIndex);
+
+ return ret;
+ }
+
+ unsafe internal static ushort ToUInt16 (byte[] value, int startIndex)
+ {
+ ushort ret;
+
+ UShortFromBytes ((byte *) &ret, value, startIndex);
+
+ return ret;
+ }
+
+ unsafe internal static uint ToUInt32 (byte[] value, int startIndex)
+ {
+ uint ret;
+
+ UIntFromBytes ((byte *) &ret, value, startIndex);
+
+ return ret;
+ }
+
+ unsafe internal static ulong ToUInt64 (byte[] value, int startIndex)
+ {
+ ulong ret;
+
+ ULongFromBytes ((byte *) &ret, value, startIndex);
+
+ return ret;
+ }
+
+ unsafe internal static float ToSingle (byte[] value, int startIndex)
+ {
+ float ret;
+
+ UIntFromBytes ((byte *) &ret, value, startIndex);
+
+ return ret;
+ }
+
+ unsafe internal static double ToDouble (byte[] value, int startIndex)
+ {
+ double ret;
+
+ ULongFromBytes ((byte *) &ret, value, startIndex);
+
+ return ret;
+ }
+ }
+}
diff --git a/MediaBrowser.Server.Mono/Security/CryptoConvert.cs b/MediaBrowser.Server.Mono/Security/CryptoConvert.cs
new file mode 100644
index 000000000..3f06114dd
--- /dev/null
+++ b/MediaBrowser.Server.Mono/Security/CryptoConvert.cs
@@ -0,0 +1,754 @@
+//
+// CryptoConvert.cs - Crypto Convertion Routines
+//
+// Author:
+// Sebastien Pouliot <sebastien@ximian.com>
+//
+// (C) 2003 Motus Technologies Inc. (http://www.motus.com)
+// Copyright (C) 2004-2006 Novell Inc. (http://www.novell.com)
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+using System;
+using System.Globalization;
+using System.Security.Cryptography;
+using System.Text;
+
+namespace Mono.Security.Cryptography {
+
+#if INSIDE_CORLIB
+ internal
+#else
+ public
+#endif
+ sealed class CryptoConvert {
+
+ private CryptoConvert ()
+ {
+ }
+
+ static private int ToInt32LE (byte [] bytes, int offset)
+ {
+ return (bytes [offset+3] << 24) | (bytes [offset+2] << 16) | (bytes [offset+1] << 8) | bytes [offset];
+ }
+
+ static private uint ToUInt32LE (byte [] bytes, int offset)
+ {
+ return (uint)((bytes [offset+3] << 24) | (bytes [offset+2] << 16) | (bytes [offset+1] << 8) | bytes [offset]);
+ }
+
+ static private byte [] GetBytesLE (int val)
+ {
+ return new byte [] {
+ (byte) (val & 0xff),
+ (byte) ((val >> 8) & 0xff),
+ (byte) ((val >> 16) & 0xff),
+ (byte) ((val >> 24) & 0xff)
+ };
+ }
+
+ static private byte[] Trim (byte[] array)
+ {
+ for (int i=0; i < array.Length; i++) {
+ if (array [i] != 0x00) {
+ byte[] result = new byte [array.Length - i];
+ Buffer.BlockCopy (array, i, result, 0, result.Length);
+ return result;
+ }
+ }
+ return null;
+ }
+
+ // convert the key from PRIVATEKEYBLOB to RSA
+ // http://msdn.microsoft.com/library/default.asp?url=/library/en-us/security/Security/private_key_blobs.asp
+ // e.g. SNK files, PVK files
+ static public RSA FromCapiPrivateKeyBlob (byte[] blob)
+ {
+ return FromCapiPrivateKeyBlob (blob, 0);
+ }
+
+ static public RSA FromCapiPrivateKeyBlob (byte[] blob, int offset)
+ {
+ if (blob == null)
+ throw new ArgumentNullException ("blob");
+ if (offset >= blob.Length)
+ throw new ArgumentException ("blob is too small.");
+
+ RSAParameters rsap = new RSAParameters ();
+ try {
+ if ((blob [offset] != 0x07) || // PRIVATEKEYBLOB (0x07)
+ (blob [offset+1] != 0x02) || // Version (0x02)
+ (blob [offset+2] != 0x00) || // Reserved (word)
+ (blob [offset+3] != 0x00) ||
+ (ToUInt32LE (blob, offset+8) != 0x32415352)) // DWORD magic = RSA2
+ throw new CryptographicException ("Invalid blob header");
+
+ // ALGID (CALG_RSA_SIGN, CALG_RSA_KEYX, ...)
+ // int algId = ToInt32LE (blob, offset+4);
+
+ // DWORD bitlen
+ int bitLen = ToInt32LE (blob, offset+12);
+
+ // DWORD public exponent
+ byte[] exp = new byte [4];
+ Buffer.BlockCopy (blob, offset+16, exp, 0, 4);
+ Array.Reverse (exp);
+ rsap.Exponent = Trim (exp);
+
+ int pos = offset+20;
+ // BYTE modulus[rsapubkey.bitlen/8];
+ int byteLen = (bitLen >> 3);
+ rsap.Modulus = new byte [byteLen];
+ Buffer.BlockCopy (blob, pos, rsap.Modulus, 0, byteLen);
+ Array.Reverse (rsap.Modulus);
+ pos += byteLen;
+
+ // BYTE prime1[rsapubkey.bitlen/16];
+ int byteHalfLen = (byteLen >> 1);
+ rsap.P = new byte [byteHalfLen];
+ Buffer.BlockCopy (blob, pos, rsap.P, 0, byteHalfLen);
+ Array.Reverse (rsap.P);
+ pos += byteHalfLen;
+
+ // BYTE prime2[rsapubkey.bitlen/16];
+ rsap.Q = new byte [byteHalfLen];
+ Buffer.BlockCopy (blob, pos, rsap.Q, 0, byteHalfLen);
+ Array.Reverse (rsap.Q);
+ pos += byteHalfLen;
+
+ // BYTE exponent1[rsapubkey.bitlen/16];
+ rsap.DP = new byte [byteHalfLen];
+ Buffer.BlockCopy (blob, pos, rsap.DP, 0, byteHalfLen);
+ Array.Reverse (rsap.DP);
+ pos += byteHalfLen;
+
+ // BYTE exponent2[rsapubkey.bitlen/16];
+ rsap.DQ = new byte [byteHalfLen];
+ Buffer.BlockCopy (blob, pos, rsap.DQ, 0, byteHalfLen);
+ Array.Reverse (rsap.DQ);
+ pos += byteHalfLen;
+
+ // BYTE coefficient[rsapubkey.bitlen/16];
+ rsap.InverseQ = new byte [byteHalfLen];
+ Buffer.BlockCopy (blob, pos, rsap.InverseQ, 0, byteHalfLen);
+ Array.Reverse (rsap.InverseQ);
+ pos += byteHalfLen;
+
+ // ok, this is hackish but CryptoAPI support it so...
+ // note: only works because CRT is used by default
+ // http://bugzilla.ximian.com/show_bug.cgi?id=57941
+ rsap.D = new byte [byteLen]; // must be allocated
+ if (pos + byteLen + offset <= blob.Length) {
+ // BYTE privateExponent[rsapubkey.bitlen/8];
+ Buffer.BlockCopy (blob, pos, rsap.D, 0, byteLen);
+ Array.Reverse (rsap.D);
+ }
+ }
+ catch (Exception e) {
+ throw new CryptographicException ("Invalid blob.", e);
+ }
+
+#if INSIDE_CORLIB && MOBILE
+ RSA rsa = RSA.Create ();
+ rsa.ImportParameters (rsap);
+#else
+ RSA rsa = null;
+ try {
+ rsa = RSA.Create ();
+ rsa.ImportParameters (rsap);
+ }
+ catch (CryptographicException ce) {
+ // this may cause problem when this code is run under
+ // the SYSTEM identity on Windows (e.g. ASP.NET). See
+ // http://bugzilla.ximian.com/show_bug.cgi?id=77559
+ try {
+ CspParameters csp = new CspParameters ();
+ csp.Flags = CspProviderFlags.UseMachineKeyStore;
+ rsa = new RSACryptoServiceProvider (csp);
+ rsa.ImportParameters (rsap);
+ }
+ catch {
+ // rethrow original, not the later, exception if this fails
+ throw ce;
+ }
+ }
+#endif
+ return rsa;
+ }
+
+ static public DSA FromCapiPrivateKeyBlobDSA (byte[] blob)
+ {
+ return FromCapiPrivateKeyBlobDSA (blob, 0);
+ }
+
+ static public DSA FromCapiPrivateKeyBlobDSA (byte[] blob, int offset)
+ {
+ if (blob == null)
+ throw new ArgumentNullException ("blob");
+ if (offset >= blob.Length)
+ throw new ArgumentException ("blob is too small.");
+
+ DSAParameters dsap = new DSAParameters ();
+ try {
+ if ((blob [offset] != 0x07) || // PRIVATEKEYBLOB (0x07)
+ (blob [offset + 1] != 0x02) || // Version (0x02)
+ (blob [offset + 2] != 0x00) || // Reserved (word)
+ (blob [offset + 3] != 0x00) ||
+ (ToUInt32LE (blob, offset + 8) != 0x32535344)) // DWORD magic
+ throw new CryptographicException ("Invalid blob header");
+
+ int bitlen = ToInt32LE (blob, offset + 12);
+ int bytelen = bitlen >> 3;
+ int pos = offset + 16;
+
+ dsap.P = new byte [bytelen];
+ Buffer.BlockCopy (blob, pos, dsap.P, 0, bytelen);
+ Array.Reverse (dsap.P);
+ pos += bytelen;
+
+ dsap.Q = new byte [20];
+ Buffer.BlockCopy (blob, pos, dsap.Q, 0, 20);
+ Array.Reverse (dsap.Q);
+ pos += 20;
+
+ dsap.G = new byte [bytelen];
+ Buffer.BlockCopy (blob, pos, dsap.G, 0, bytelen);
+ Array.Reverse (dsap.G);
+ pos += bytelen;
+
+ dsap.X = new byte [20];
+ Buffer.BlockCopy (blob, pos, dsap.X, 0, 20);
+ Array.Reverse (dsap.X);
+ pos += 20;
+
+ dsap.Counter = ToInt32LE (blob, pos);
+ pos += 4;
+
+ dsap.Seed = new byte [20];
+ Buffer.BlockCopy (blob, pos, dsap.Seed, 0, 20);
+ Array.Reverse (dsap.Seed);
+ pos += 20;
+ }
+ catch (Exception e) {
+ throw new CryptographicException ("Invalid blob.", e);
+ }
+
+#if INSIDE_CORLIB && MOBILE
+ DSA dsa = (DSA)DSA.Create ();
+ dsa.ImportParameters (dsap);
+#else
+ DSA dsa = null;
+ try {
+ dsa = (DSA)DSA.Create ();
+ dsa.ImportParameters (dsap);
+ }
+ catch (CryptographicException ce) {
+ // this may cause problem when this code is run under
+ // the SYSTEM identity on Windows (e.g. ASP.NET). See
+ // http://bugzilla.ximian.com/show_bug.cgi?id=77559
+ try {
+ CspParameters csp = new CspParameters ();
+ csp.Flags = CspProviderFlags.UseMachineKeyStore;
+ dsa = new DSACryptoServiceProvider (csp);
+ dsa.ImportParameters (dsap);
+ }
+ catch {
+ // rethrow original, not the later, exception if this fails
+ throw ce;
+ }
+ }
+#endif
+ return dsa;
+ }
+
+ static public byte[] ToCapiPrivateKeyBlob (RSA rsa)
+ {
+ RSAParameters p = rsa.ExportParameters (true);
+ int keyLength = p.Modulus.Length; // in bytes
+ byte[] blob = new byte [20 + (keyLength << 2) + (keyLength >> 1)];
+
+ blob [0] = 0x07; // Type - PRIVATEKEYBLOB (0x07)
+ blob [1] = 0x02; // Version - Always CUR_BLOB_VERSION (0x02)
+ // [2], [3] // RESERVED - Always 0
+ blob [5] = 0x24; // ALGID - Always 00 24 00 00 (for CALG_RSA_SIGN)
+ blob [8] = 0x52; // Magic - RSA2 (ASCII in hex)
+ blob [9] = 0x53;
+ blob [10] = 0x41;
+ blob [11] = 0x32;
+
+ byte[] bitlen = GetBytesLE (keyLength << 3);
+ blob [12] = bitlen [0]; // bitlen
+ blob [13] = bitlen [1];
+ blob [14] = bitlen [2];
+ blob [15] = bitlen [3];
+
+ // public exponent (DWORD)
+ int pos = 16;
+ int n = p.Exponent.Length;
+ while (n > 0)
+ blob [pos++] = p.Exponent [--n];
+ // modulus
+ pos = 20;
+ byte[] part = p.Modulus;
+ int len = part.Length;
+ Array.Reverse (part, 0, len);
+ Buffer.BlockCopy (part, 0, blob, pos, len);
+ pos += len;
+ // private key
+ part = p.P;
+ len = part.Length;
+ Array.Reverse (part, 0, len);
+ Buffer.BlockCopy (part, 0, blob, pos, len);
+ pos += len;
+
+ part = p.Q;
+ len = part.Length;
+ Array.Reverse (part, 0, len);
+ Buffer.BlockCopy (part, 0, blob, pos, len);
+ pos += len;
+
+ part = p.DP;
+ len = part.Length;
+ Array.Reverse (part, 0, len);
+ Buffer.BlockCopy (part, 0, blob, pos, len);
+ pos += len;
+
+ part = p.DQ;
+ len = part.Length;
+ Array.Reverse (part, 0, len);
+ Buffer.BlockCopy (part, 0, blob, pos, len);
+ pos += len;
+
+ part = p.InverseQ;
+ len = part.Length;
+ Array.Reverse (part, 0, len);
+ Buffer.BlockCopy (part, 0, blob, pos, len);
+ pos += len;
+
+ part = p.D;
+ len = part.Length;
+ Array.Reverse (part, 0, len);
+ Buffer.BlockCopy (part, 0, blob, pos, len);
+
+ return blob;
+ }
+
+ static public byte[] ToCapiPrivateKeyBlob (DSA dsa)
+ {
+ DSAParameters p = dsa.ExportParameters (true);
+ int keyLength = p.P.Length; // in bytes
+
+ // header + P + Q + G + X + count + seed
+ byte[] blob = new byte [16 + keyLength + 20 + keyLength + 20 + 4 + 20];
+
+ blob [0] = 0x07; // Type - PRIVATEKEYBLOB (0x07)
+ blob [1] = 0x02; // Version - Always CUR_BLOB_VERSION (0x02)
+ // [2], [3] // RESERVED - Always 0
+ blob [5] = 0x22; // ALGID
+ blob [8] = 0x44; // Magic
+ blob [9] = 0x53;
+ blob [10] = 0x53;
+ blob [11] = 0x32;
+
+ byte[] bitlen = GetBytesLE (keyLength << 3);
+ blob [12] = bitlen [0];
+ blob [13] = bitlen [1];
+ blob [14] = bitlen [2];
+ blob [15] = bitlen [3];
+
+ int pos = 16;
+ byte[] part = p.P;
+ Array.Reverse (part);
+ Buffer.BlockCopy (part, 0, blob, pos, keyLength);
+ pos += keyLength;
+
+ part = p.Q;
+ Array.Reverse (part);
+ Buffer.BlockCopy (part, 0, blob, pos, 20);
+ pos += 20;
+
+ part = p.G;
+ Array.Reverse (part);
+ Buffer.BlockCopy (part, 0, blob, pos, keyLength);
+ pos += keyLength;
+
+ part = p.X;
+ Array.Reverse (part);
+ Buffer.BlockCopy (part, 0, blob, pos, 20);
+ pos += 20;
+
+ Buffer.BlockCopy (GetBytesLE (p.Counter), 0, blob, pos, 4);
+ pos += 4;
+
+ part = p.Seed;
+ Array.Reverse (part);
+ Buffer.BlockCopy (part, 0, blob, pos, 20);
+
+ return blob;
+ }
+
+ static public RSA FromCapiPublicKeyBlob (byte[] blob)
+ {
+ return FromCapiPublicKeyBlob (blob, 0);
+ }
+
+ static public RSA FromCapiPublicKeyBlob (byte[] blob, int offset)
+ {
+ if (blob == null)
+ throw new ArgumentNullException ("blob");
+ if (offset >= blob.Length)
+ throw new ArgumentException ("blob is too small.");
+
+ try {
+ if ((blob [offset] != 0x06) || // PUBLICKEYBLOB (0x06)
+ (blob [offset+1] != 0x02) || // Version (0x02)
+ (blob [offset+2] != 0x00) || // Reserved (word)
+ (blob [offset+3] != 0x00) ||
+ (ToUInt32LE (blob, offset+8) != 0x31415352)) // DWORD magic = RSA1
+ throw new CryptographicException ("Invalid blob header");
+
+ // ALGID (CALG_RSA_SIGN, CALG_RSA_KEYX, ...)
+ // int algId = ToInt32LE (blob, offset+4);
+
+ // DWORD bitlen
+ int bitLen = ToInt32LE (blob, offset+12);
+
+ // DWORD public exponent
+ RSAParameters rsap = new RSAParameters ();
+ rsap.Exponent = new byte [3];
+ rsap.Exponent [0] = blob [offset+18];
+ rsap.Exponent [1] = blob [offset+17];
+ rsap.Exponent [2] = blob [offset+16];
+
+ int pos = offset+20;
+ // BYTE modulus[rsapubkey.bitlen/8];
+ int byteLen = (bitLen >> 3);
+ rsap.Modulus = new byte [byteLen];
+ Buffer.BlockCopy (blob, pos, rsap.Modulus, 0, byteLen);
+ Array.Reverse (rsap.Modulus);
+#if INSIDE_CORLIB && MOBILE
+ RSA rsa = RSA.Create ();
+ rsa.ImportParameters (rsap);
+#else
+ RSA rsa = null;
+ try {
+ rsa = RSA.Create ();
+ rsa.ImportParameters (rsap);
+ }
+ catch (CryptographicException) {
+ // this may cause problem when this code is run under
+ // the SYSTEM identity on Windows (e.g. ASP.NET). See
+ // http://bugzilla.ximian.com/show_bug.cgi?id=77559
+ CspParameters csp = new CspParameters ();
+ csp.Flags = CspProviderFlags.UseMachineKeyStore;
+ rsa = new RSACryptoServiceProvider (csp);
+ rsa.ImportParameters (rsap);
+ }
+#endif
+ return rsa;
+ }
+ catch (Exception e) {
+ throw new CryptographicException ("Invalid blob.", e);
+ }
+ }
+
+ static public DSA FromCapiPublicKeyBlobDSA (byte[] blob)
+ {
+ return FromCapiPublicKeyBlobDSA (blob, 0);
+ }
+
+ static public DSA FromCapiPublicKeyBlobDSA (byte[] blob, int offset)
+ {
+ if (blob == null)
+ throw new ArgumentNullException ("blob");
+ if (offset >= blob.Length)
+ throw new ArgumentException ("blob is too small.");
+
+ try {
+ if ((blob [offset] != 0x06) || // PUBLICKEYBLOB (0x06)
+ (blob [offset + 1] != 0x02) || // Version (0x02)
+ (blob [offset + 2] != 0x00) || // Reserved (word)
+ (blob [offset + 3] != 0x00) ||
+ (ToUInt32LE (blob, offset + 8) != 0x31535344)) // DWORD magic
+ throw new CryptographicException ("Invalid blob header");
+
+ int bitlen = ToInt32LE (blob, offset + 12);
+ DSAParameters dsap = new DSAParameters ();
+ int bytelen = bitlen >> 3;
+ int pos = offset + 16;
+
+ dsap.P = new byte [bytelen];
+ Buffer.BlockCopy (blob, pos, dsap.P, 0, bytelen);
+ Array.Reverse (dsap.P);
+ pos += bytelen;
+
+ dsap.Q = new byte [20];
+ Buffer.BlockCopy (blob, pos, dsap.Q, 0, 20);
+ Array.Reverse (dsap.Q);
+ pos += 20;
+
+ dsap.G = new byte [bytelen];
+ Buffer.BlockCopy (blob, pos, dsap.G, 0, bytelen);
+ Array.Reverse (dsap.G);
+ pos += bytelen;
+
+ dsap.Y = new byte [bytelen];
+ Buffer.BlockCopy (blob, pos, dsap.Y, 0, bytelen);
+ Array.Reverse (dsap.Y);
+ pos += bytelen;
+
+ dsap.Counter = ToInt32LE (blob, pos);
+ pos += 4;
+
+ dsap.Seed = new byte [20];
+ Buffer.BlockCopy (blob, pos, dsap.Seed, 0, 20);
+ Array.Reverse (dsap.Seed);
+ pos += 20;
+
+ DSA dsa = (DSA)DSA.Create ();
+ dsa.ImportParameters (dsap);
+ return dsa;
+ }
+ catch (Exception e) {
+ throw new CryptographicException ("Invalid blob.", e);
+ }
+ }
+
+ static public byte[] ToCapiPublicKeyBlob (RSA rsa)
+ {
+ RSAParameters p = rsa.ExportParameters (false);
+ int keyLength = p.Modulus.Length; // in bytes
+ byte[] blob = new byte [20 + keyLength];
+
+ blob [0] = 0x06; // Type - PUBLICKEYBLOB (0x06)
+ blob [1] = 0x02; // Version - Always CUR_BLOB_VERSION (0x02)
+ // [2], [3] // RESERVED - Always 0
+ blob [5] = 0x24; // ALGID - Always 00 24 00 00 (for CALG_RSA_SIGN)
+ blob [8] = 0x52; // Magic - RSA1 (ASCII in hex)
+ blob [9] = 0x53;
+ blob [10] = 0x41;
+ blob [11] = 0x31;
+
+ byte[] bitlen = GetBytesLE (keyLength << 3);
+ blob [12] = bitlen [0]; // bitlen
+ blob [13] = bitlen [1];
+ blob [14] = bitlen [2];
+ blob [15] = bitlen [3];
+
+ // public exponent (DWORD)
+ int pos = 16;
+ int n = p.Exponent.Length;
+ while (n > 0)
+ blob [pos++] = p.Exponent [--n];
+ // modulus
+ pos = 20;
+ byte[] part = p.Modulus;
+ int len = part.Length;
+ Array.Reverse (part, 0, len);
+ Buffer.BlockCopy (part, 0, blob, pos, len);
+ pos += len;
+ return blob;
+ }
+
+ static public byte[] ToCapiPublicKeyBlob (DSA dsa)
+ {
+ DSAParameters p = dsa.ExportParameters (false);
+ int keyLength = p.P.Length; // in bytes
+
+ // header + P + Q + G + Y + count + seed
+ byte[] blob = new byte [16 + keyLength + 20 + keyLength + keyLength + 4 + 20];
+
+ blob [0] = 0x06; // Type - PUBLICKEYBLOB (0x06)
+ blob [1] = 0x02; // Version - Always CUR_BLOB_VERSION (0x02)
+ // [2], [3] // RESERVED - Always 0
+ blob [5] = 0x22; // ALGID
+ blob [8] = 0x44; // Magic
+ blob [9] = 0x53;
+ blob [10] = 0x53;
+ blob [11] = 0x31;
+
+ byte[] bitlen = GetBytesLE (keyLength << 3);
+ blob [12] = bitlen [0];
+ blob [13] = bitlen [1];
+ blob [14] = bitlen [2];
+ blob [15] = bitlen [3];
+
+ int pos = 16;
+ byte[] part;
+
+ part = p.P;
+ Array.Reverse (part);
+ Buffer.BlockCopy (part, 0, blob, pos, keyLength);
+ pos += keyLength;
+
+ part = p.Q;
+ Array.Reverse (part);
+ Buffer.BlockCopy (part, 0, blob, pos, 20);
+ pos += 20;
+
+ part = p.G;
+ Array.Reverse (part);
+ Buffer.BlockCopy (part, 0, blob, pos, keyLength);
+ pos += keyLength;
+
+ part = p.Y;
+ Array.Reverse (part);
+ Buffer.BlockCopy (part, 0, blob, pos, keyLength);
+ pos += keyLength;
+
+ Buffer.BlockCopy (GetBytesLE (p.Counter), 0, blob, pos, 4);
+ pos += 4;
+
+ part = p.Seed;
+ Array.Reverse (part);
+ Buffer.BlockCopy (part, 0, blob, pos, 20);
+
+ return blob;
+ }
+
+ // PRIVATEKEYBLOB
+ // PUBLICKEYBLOB
+ static public RSA FromCapiKeyBlob (byte[] blob)
+ {
+ return FromCapiKeyBlob (blob, 0);
+ }
+
+ static public RSA FromCapiKeyBlob (byte[] blob, int offset)
+ {
+ if (blob == null)
+ throw new ArgumentNullException ("blob");
+ if (offset >= blob.Length)
+ throw new ArgumentException ("blob is too small.");
+
+ switch (blob [offset]) {
+ case 0x00:
+ // this could be a public key inside an header
+ // like "sn -e" would produce
+ if (blob [offset + 12] == 0x06) {
+ return FromCapiPublicKeyBlob (blob, offset + 12);
+ }
+ break;
+ case 0x06:
+ return FromCapiPublicKeyBlob (blob, offset);
+ case 0x07:
+ return FromCapiPrivateKeyBlob (blob, offset);
+ }
+ throw new CryptographicException ("Unknown blob format.");
+ }
+
+ static public DSA FromCapiKeyBlobDSA (byte[] blob)
+ {
+ return FromCapiKeyBlobDSA (blob, 0);
+ }
+
+ static public DSA FromCapiKeyBlobDSA (byte[] blob, int offset)
+ {
+ if (blob == null)
+ throw new ArgumentNullException ("blob");
+ if (offset >= blob.Length)
+ throw new ArgumentException ("blob is too small.");
+
+ switch (blob [offset]) {
+ case 0x06:
+ return FromCapiPublicKeyBlobDSA (blob, offset);
+ case 0x07:
+ return FromCapiPrivateKeyBlobDSA (blob, offset);
+ }
+ throw new CryptographicException ("Unknown blob format.");
+ }
+
+ static public byte[] ToCapiKeyBlob (AsymmetricAlgorithm keypair, bool includePrivateKey)
+ {
+ if (keypair == null)
+ throw new ArgumentNullException ("keypair");
+
+ // check between RSA and DSA (and potentially others like DH)
+ if (keypair is RSA)
+ return ToCapiKeyBlob ((RSA)keypair, includePrivateKey);
+ else if (keypair is DSA)
+ return ToCapiKeyBlob ((DSA)keypair, includePrivateKey);
+ else
+ return null; // TODO
+ }
+
+ static public byte[] ToCapiKeyBlob (RSA rsa, bool includePrivateKey)
+ {
+ if (rsa == null)
+ throw new ArgumentNullException ("rsa");
+
+ if (includePrivateKey)
+ return ToCapiPrivateKeyBlob (rsa);
+ else
+ return ToCapiPublicKeyBlob (rsa);
+ }
+
+ static public byte[] ToCapiKeyBlob (DSA dsa, bool includePrivateKey)
+ {
+ if (dsa == null)
+ throw new ArgumentNullException ("dsa");
+
+ if (includePrivateKey)
+ return ToCapiPrivateKeyBlob (dsa);
+ else
+ return ToCapiPublicKeyBlob (dsa);
+ }
+
+ static public string ToHex (byte[] input)
+ {
+ if (input == null)
+ return null;
+
+ StringBuilder sb = new StringBuilder (input.Length * 2);
+ foreach (byte b in input) {
+ sb.Append (b.ToString ("X2", CultureInfo.InvariantCulture));
+ }
+ return sb.ToString ();
+ }
+
+ static private byte FromHexChar (char c)
+ {
+ if ((c >= 'a') && (c <= 'f'))
+ return (byte) (c - 'a' + 10);
+ if ((c >= 'A') && (c <= 'F'))
+ return (byte) (c - 'A' + 10);
+ if ((c >= '0') && (c <= '9'))
+ return (byte) (c - '0');
+ throw new ArgumentException ("invalid hex char");
+ }
+
+ static public byte[] FromHex (string hex)
+ {
+ if (hex == null)
+ return null;
+ if ((hex.Length & 0x1) == 0x1)
+ throw new ArgumentException ("Length must be a multiple of 2");
+
+ byte[] result = new byte [hex.Length >> 1];
+ int n = 0;
+ int i = 0;
+ while (n < result.Length) {
+ result [n] = (byte) (FromHexChar (hex [i++]) << 4);
+ result [n++] += FromHexChar (hex [i++]);
+ }
+ return result;
+ }
+ }
+}
diff --git a/MediaBrowser.Server.Mono/Security/PKCS1.cs b/MediaBrowser.Server.Mono/Security/PKCS1.cs
new file mode 100644
index 000000000..4e579eee9
--- /dev/null
+++ b/MediaBrowser.Server.Mono/Security/PKCS1.cs
@@ -0,0 +1,495 @@
+//
+// PKCS1.cs - Implements PKCS#1 primitives.
+//
+// Author:
+// Sebastien Pouliot <sebastien@xamarin.com>
+//
+// (C) 2002, 2003 Motus Technologies Inc. (http://www.motus.com)
+// Copyright (C) 2004 Novell, Inc (http://www.novell.com)
+// Copyright 2013 Xamarin Inc. (http://www.xamarin.com)
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+using System;
+using System.Security.Cryptography;
+
+namespace Mono.Security.Cryptography {
+
+ // References:
+ // a. PKCS#1: RSA Cryptography Standard
+ // http://www.rsasecurity.com/rsalabs/pkcs/pkcs-1/index.html
+
+#if INSIDE_CORLIB
+ internal
+#else
+ public
+#endif
+ sealed class PKCS1 {
+
+ private PKCS1 ()
+ {
+ }
+
+ private static bool Compare (byte[] array1, byte[] array2)
+ {
+ bool result = (array1.Length == array2.Length);
+ if (result) {
+ for (int i=0; i < array1.Length; i++)
+ if (array1[i] != array2[i])
+ return false;
+ }
+ return result;
+ }
+
+ private static byte[] xor (byte[] array1, byte[] array2)
+ {
+ byte[] result = new byte [array1.Length];
+ for (int i=0; i < result.Length; i++)
+ result[i] = (byte) (array1[i] ^ array2[i]);
+ return result;
+ }
+
+ private static byte[] emptySHA1 = { 0xda, 0x39, 0xa3, 0xee, 0x5e, 0x6b, 0x4b, 0x0d, 0x32, 0x55, 0xbf, 0xef, 0x95, 0x60, 0x18, 0x90, 0xaf, 0xd8, 0x07, 0x09 };
+ private static byte[] emptySHA256 = { 0xe3, 0xb0, 0xc4, 0x42, 0x98, 0xfc, 0x1c, 0x14, 0x9a, 0xfb, 0xf4, 0xc8, 0x99, 0x6f, 0xb9, 0x24, 0x27, 0xae, 0x41, 0xe4, 0x64, 0x9b, 0x93, 0x4c, 0xa4, 0x95, 0x99, 0x1b, 0x78, 0x52, 0xb8, 0x55 };
+ private static byte[] emptySHA384 = { 0x38, 0xb0, 0x60, 0xa7, 0x51, 0xac, 0x96, 0x38, 0x4c, 0xd9, 0x32, 0x7e, 0xb1, 0xb1, 0xe3, 0x6a, 0x21, 0xfd, 0xb7, 0x11, 0x14, 0xbe, 0x07, 0x43, 0x4c, 0x0c, 0xc7, 0xbf, 0x63, 0xf6, 0xe1, 0xda, 0x27, 0x4e, 0xde, 0xbf, 0xe7, 0x6f, 0x65, 0xfb, 0xd5, 0x1a, 0xd2, 0xf1, 0x48, 0x98, 0xb9, 0x5b };
+ private static byte[] emptySHA512 = { 0xcf, 0x83, 0xe1, 0x35, 0x7e, 0xef, 0xb8, 0xbd, 0xf1, 0x54, 0x28, 0x50, 0xd6, 0x6d, 0x80, 0x07, 0xd6, 0x20, 0xe4, 0x05, 0x0b, 0x57, 0x15, 0xdc, 0x83, 0xf4, 0xa9, 0x21, 0xd3, 0x6c, 0xe9, 0xce, 0x47, 0xd0, 0xd1, 0x3c, 0x5d, 0x85, 0xf2, 0xb0, 0xff, 0x83, 0x18, 0xd2, 0x87, 0x7e, 0xec, 0x2f, 0x63, 0xb9, 0x31, 0xbd, 0x47, 0x41, 0x7a, 0x81, 0xa5, 0x38, 0x32, 0x7a, 0xf9, 0x27, 0xda, 0x3e };
+
+ private static byte[] GetEmptyHash (HashAlgorithm hash)
+ {
+ if (hash is SHA1)
+ return emptySHA1;
+ else if (hash is SHA256)
+ return emptySHA256;
+ else if (hash is SHA384)
+ return emptySHA384;
+ else if (hash is SHA512)
+ return emptySHA512;
+ else
+ return hash.ComputeHash ((byte[])null);
+ }
+
+ // PKCS #1 v.2.1, Section 4.1
+ // I2OSP converts a non-negative integer to an octet string of a specified length.
+ public static byte[] I2OSP (int x, int size)
+ {
+ byte[] array = BitConverterLE.GetBytes (x);
+ Array.Reverse (array, 0, array.Length);
+ return I2OSP (array, size);
+ }
+
+ public static byte[] I2OSP (byte[] x, int size)
+ {
+ byte[] result = new byte [size];
+ Buffer.BlockCopy (x, 0, result, (result.Length - x.Length), x.Length);
+ return result;
+ }
+
+ // PKCS #1 v.2.1, Section 4.2
+ // OS2IP converts an octet string to a nonnegative integer.
+ public static byte[] OS2IP (byte[] x)
+ {
+ int i = 0;
+ while ((x [i++] == 0x00) && (i < x.Length)) {
+ // confuse compiler into reporting a warning with {}
+ }
+ i--;
+ if (i > 0) {
+ byte[] result = new byte [x.Length - i];
+ Buffer.BlockCopy (x, i, result, 0, result.Length);
+ return result;
+ }
+ else
+ return x;
+ }
+
+ // PKCS #1 v.2.1, Section 5.1.1
+ public static byte[] RSAEP (RSA rsa, byte[] m)
+ {
+ // c = m^e mod n
+ return rsa.EncryptValue (m);
+ }
+
+ // PKCS #1 v.2.1, Section 5.1.2
+ public static byte[] RSADP (RSA rsa, byte[] c)
+ {
+ // m = c^d mod n
+ // Decrypt value may apply CRT optimizations
+ return rsa.DecryptValue (c);
+ }
+
+ // PKCS #1 v.2.1, Section 5.2.1
+ public static byte[] RSASP1 (RSA rsa, byte[] m)
+ {
+ // first form: s = m^d mod n
+ // Decrypt value may apply CRT optimizations
+ return rsa.DecryptValue (m);
+ }
+
+ // PKCS #1 v.2.1, Section 5.2.2
+ public static byte[] RSAVP1 (RSA rsa, byte[] s)
+ {
+ // m = s^e mod n
+ return rsa.EncryptValue (s);
+ }
+
+ // PKCS #1 v.2.1, Section 7.1.1
+ // RSAES-OAEP-ENCRYPT ((n, e), M, L)
+ public static byte[] Encrypt_OAEP (RSA rsa, HashAlgorithm hash, RandomNumberGenerator rng, byte[] M)
+ {
+ int size = rsa.KeySize / 8;
+ int hLen = hash.HashSize / 8;
+ if (M.Length > size - 2 * hLen - 2)
+ throw new CryptographicException ("message too long");
+ // empty label L SHA1 hash
+ byte[] lHash = GetEmptyHash (hash);
+ int PSLength = (size - M.Length - 2 * hLen - 2);
+ // DB = lHash || PS || 0x01 || M
+ byte[] DB = new byte [lHash.Length + PSLength + 1 + M.Length];
+ Buffer.BlockCopy (lHash, 0, DB, 0, lHash.Length);
+ DB [(lHash.Length + PSLength)] = 0x01;
+ Buffer.BlockCopy (M, 0, DB, (DB.Length - M.Length), M.Length);
+
+ byte[] seed = new byte [hLen];
+ rng.GetBytes (seed);
+
+ byte[] dbMask = MGF1 (hash, seed, size - hLen - 1);
+ byte[] maskedDB = xor (DB, dbMask);
+ byte[] seedMask = MGF1 (hash, maskedDB, hLen);
+ byte[] maskedSeed = xor (seed, seedMask);
+ // EM = 0x00 || maskedSeed || maskedDB
+ byte[] EM = new byte [maskedSeed.Length + maskedDB.Length + 1];
+ Buffer.BlockCopy (maskedSeed, 0, EM, 1, maskedSeed.Length);
+ Buffer.BlockCopy (maskedDB, 0, EM, maskedSeed.Length + 1, maskedDB.Length);
+
+ byte[] m = OS2IP (EM);
+ byte[] c = RSAEP (rsa, m);
+ return I2OSP (c, size);
+ }
+
+ // PKCS #1 v.2.1, Section 7.1.2
+ // RSAES-OAEP-DECRYPT (K, C, L)
+ public static byte[] Decrypt_OAEP (RSA rsa, HashAlgorithm hash, byte[] C)
+ {
+ int size = rsa.KeySize / 8;
+ int hLen = hash.HashSize / 8;
+ if ((size < (2 * hLen + 2)) || (C.Length != size))
+ throw new CryptographicException ("decryption error");
+
+ byte[] c = OS2IP (C);
+ byte[] m = RSADP (rsa, c);
+ byte[] EM = I2OSP (m, size);
+
+ // split EM = Y || maskedSeed || maskedDB
+ byte[] maskedSeed = new byte [hLen];
+ Buffer.BlockCopy (EM, 1, maskedSeed, 0, maskedSeed.Length);
+ byte[] maskedDB = new byte [size - hLen - 1];
+ Buffer.BlockCopy (EM, (EM.Length - maskedDB.Length), maskedDB, 0, maskedDB.Length);
+
+ byte[] seedMask = MGF1 (hash, maskedDB, hLen);
+ byte[] seed = xor (maskedSeed, seedMask);
+ byte[] dbMask = MGF1 (hash, seed, size - hLen - 1);
+ byte[] DB = xor (maskedDB, dbMask);
+
+ byte[] lHash = GetEmptyHash (hash);
+ // split DB = lHash' || PS || 0x01 || M
+ byte[] dbHash = new byte [lHash.Length];
+ Buffer.BlockCopy (DB, 0, dbHash, 0, dbHash.Length);
+ bool h = Compare (lHash, dbHash);
+
+ // find separator 0x01
+ int nPos = lHash.Length;
+ while (DB[nPos] == 0)
+ nPos++;
+
+ int Msize = DB.Length - nPos - 1;
+ byte[] M = new byte [Msize];
+ Buffer.BlockCopy (DB, (nPos + 1), M, 0, Msize);
+
+ // we could have returned EM[0] sooner but would be helping a timing attack
+ if ((EM[0] != 0) || (!h) || (DB[nPos] != 0x01))
+ return null;
+ return M;
+ }
+
+ // PKCS #1 v.2.1, Section 7.2.1
+ // RSAES-PKCS1-V1_5-ENCRYPT ((n, e), M)
+ public static byte[] Encrypt_v15 (RSA rsa, RandomNumberGenerator rng, byte[] M)
+ {
+ int size = rsa.KeySize / 8;
+ if (M.Length > size - 11)
+ throw new CryptographicException ("message too long");
+ int PSLength = System.Math.Max (8, (size - M.Length - 3));
+ byte[] PS = new byte [PSLength];
+ rng.GetNonZeroBytes (PS);
+ byte[] EM = new byte [size];
+ EM [1] = 0x02;
+ Buffer.BlockCopy (PS, 0, EM, 2, PSLength);
+ Buffer.BlockCopy (M, 0, EM, (size - M.Length), M.Length);
+
+ byte[] m = OS2IP (EM);
+ byte[] c = RSAEP (rsa, m);
+ byte[] C = I2OSP (c, size);
+ return C;
+ }
+
+ // PKCS #1 v.2.1, Section 7.2.2
+ // RSAES-PKCS1-V1_5-DECRYPT (K, C)
+ public static byte[] Decrypt_v15 (RSA rsa, byte[] C)
+ {
+ int size = rsa.KeySize >> 3; // div by 8
+ if ((size < 11) || (C.Length > size))
+ throw new CryptographicException ("decryption error");
+ byte[] c = OS2IP (C);
+ byte[] m = RSADP (rsa, c);
+ byte[] EM = I2OSP (m, size);
+
+ if ((EM [0] != 0x00) || (EM [1] != 0x02))
+ return null;
+
+ int mPos = 10;
+ // PS is a minimum of 8 bytes + 2 bytes for header
+ while ((EM [mPos] != 0x00) && (mPos < EM.Length))
+ mPos++;
+ if (EM [mPos] != 0x00)
+ return null;
+ mPos++;
+ byte[] M = new byte [EM.Length - mPos];
+ Buffer.BlockCopy (EM, mPos, M, 0, M.Length);
+ return M;
+ }
+
+ // PKCS #1 v.2.1, Section 8.2.1
+ // RSASSA-PKCS1-V1_5-SIGN (K, M)
+ public static byte[] Sign_v15 (RSA rsa, HashAlgorithm hash, byte[] hashValue)
+ {
+ int size = (rsa.KeySize >> 3); // div 8
+ byte[] EM = Encode_v15 (hash, hashValue, size);
+ byte[] m = OS2IP (EM);
+ byte[] s = RSASP1 (rsa, m);
+ byte[] S = I2OSP (s, size);
+ return S;
+ }
+
+ internal static byte[] Sign_v15 (RSA rsa, string hashName, byte[] hashValue)
+ {
+ using (var hash = CreateFromName (hashName))
+ return Sign_v15 (rsa, hash, hashValue);
+ }
+
+ // PKCS #1 v.2.1, Section 8.2.2
+ // RSASSA-PKCS1-V1_5-VERIFY ((n, e), M, S)
+ public static bool Verify_v15 (RSA rsa, HashAlgorithm hash, byte[] hashValue, byte[] signature)
+ {
+ return Verify_v15 (rsa, hash, hashValue, signature, false);
+ }
+
+ internal static bool Verify_v15 (RSA rsa, string hashName, byte[] hashValue, byte[] signature)
+ {
+ using (var hash = CreateFromName (hashName))
+ return Verify_v15 (rsa, hash, hashValue, signature, false);
+ }
+
+ // DO NOT USE WITHOUT A VERY GOOD REASON
+ public static bool Verify_v15 (RSA rsa, HashAlgorithm hash, byte [] hashValue, byte [] signature, bool tryNonStandardEncoding)
+ {
+ int size = (rsa.KeySize >> 3); // div 8
+ byte[] s = OS2IP (signature);
+ byte[] m = RSAVP1 (rsa, s);
+ byte[] EM2 = I2OSP (m, size);
+ byte[] EM = Encode_v15 (hash, hashValue, size);
+ bool result = Compare (EM, EM2);
+ if (result || !tryNonStandardEncoding)
+ return result;
+
+ // NOTE: some signatures don't include the hash OID (pretty lame but real)
+ // and compatible with MS implementation. E.g. Verisign Authenticode Timestamps
+
+ // we're making this "as safe as possible"
+ if ((EM2 [0] != 0x00) || (EM2 [1] != 0x01))
+ return false;
+ int i;
+ for (i = 2; i < EM2.Length - hashValue.Length - 1; i++) {
+ if (EM2 [i] != 0xFF)
+ return false;
+ }
+ if (EM2 [i++] != 0x00)
+ return false;
+
+ byte [] decryptedHash = new byte [hashValue.Length];
+ Buffer.BlockCopy (EM2, i, decryptedHash, 0, decryptedHash.Length);
+ return Compare (decryptedHash, hashValue);
+ }
+
+ // PKCS #1 v.2.1, Section 9.2
+ // EMSA-PKCS1-v1_5-Encode
+ public static byte[] Encode_v15 (HashAlgorithm hash, byte[] hashValue, int emLength)
+ {
+ if (hashValue.Length != (hash.HashSize >> 3))
+ throw new CryptographicException ("bad hash length for " + hash.ToString ());
+
+ // DigestInfo ::= SEQUENCE {
+ // digestAlgorithm AlgorithmIdentifier,
+ // digest OCTET STRING
+ // }
+
+ byte[] t = null;
+
+ string oid = CryptoConfig.MapNameToOID (hash.ToString ());
+ if (oid != null)
+ {
+ ASN1 digestAlgorithm = new ASN1 (0x30);
+ digestAlgorithm.Add (new ASN1 (CryptoConfig.EncodeOID (oid)));
+ digestAlgorithm.Add (new ASN1 (0x05)); // NULL
+ ASN1 digest = new ASN1 (0x04, hashValue);
+ ASN1 digestInfo = new ASN1 (0x30);
+ digestInfo.Add (digestAlgorithm);
+ digestInfo.Add (digest);
+
+ t = digestInfo.GetBytes ();
+ }
+ else
+ {
+ // There are no valid OID, in this case t = hashValue
+ // This is the case of the MD5SHA hash algorithm
+ t = hashValue;
+ }
+
+ Buffer.BlockCopy (hashValue, 0, t, t.Length - hashValue.Length, hashValue.Length);
+
+ int PSLength = System.Math.Max (8, emLength - t.Length - 3);
+ // PS = PSLength of 0xff
+
+ // EM = 0x00 | 0x01 | PS | 0x00 | T
+ byte[] EM = new byte [PSLength + t.Length + 3];
+ EM [1] = 0x01;
+ for (int i=2; i < PSLength + 2; i++)
+ EM[i] = 0xff;
+ Buffer.BlockCopy (t, 0, EM, PSLength + 3, t.Length);
+
+ return EM;
+ }
+
+ // PKCS #1 v.2.1, Section B.2.1
+ public static byte[] MGF1 (HashAlgorithm hash, byte[] mgfSeed, int maskLen)
+ {
+ // 1. If maskLen > 2^32 hLen, output "mask too long" and stop.
+ // easy - this is impossible by using a int (31bits) as parameter ;-)
+ // BUT with a signed int we do have to check for negative values!
+ if (maskLen < 0)
+ throw new OverflowException();
+
+ int mgfSeedLength = mgfSeed.Length;
+ int hLen = (hash.HashSize >> 3); // from bits to bytes
+ int iterations = (maskLen / hLen);
+ if (maskLen % hLen != 0)
+ iterations++;
+ // 2. Let T be the empty octet string.
+ byte[] T = new byte [iterations * hLen];
+
+ byte[] toBeHashed = new byte [mgfSeedLength + 4];
+ int pos = 0;
+ // 3. For counter from 0 to \ceil (maskLen / hLen) - 1, do the following:
+ for (int counter = 0; counter < iterations; counter++) {
+ // a. Convert counter to an octet string C of length 4 octets
+ byte[] C = I2OSP (counter, 4);
+
+ // b. Concatenate the hash of the seed mgfSeed and C to the octet string T:
+ // T = T || Hash (mgfSeed || C)
+ Buffer.BlockCopy (mgfSeed, 0, toBeHashed, 0, mgfSeedLength);
+ Buffer.BlockCopy (C, 0, toBeHashed, mgfSeedLength, 4);
+ byte[] output = hash.ComputeHash (toBeHashed);
+ Buffer.BlockCopy (output, 0, T, pos, hLen);
+ pos += hLen;
+ }
+
+ // 4. Output the leading maskLen octets of T as the octet string mask.
+ byte[] mask = new byte [maskLen];
+ Buffer.BlockCopy (T, 0, mask, 0, maskLen);
+ return mask;
+ }
+
+ static internal string HashNameFromOid (string oid, bool throwOnError = true)
+ {
+ switch (oid) {
+ case "1.2.840.113549.1.1.2": // MD2 with RSA encryption
+ return "MD2";
+ case "1.2.840.113549.1.1.3": // MD4 with RSA encryption
+ return "MD4";
+ case "1.2.840.113549.1.1.4": // MD5 with RSA encryption
+ return "MD5";
+ case "1.2.840.113549.1.1.5": // SHA-1 with RSA Encryption
+ case "1.3.14.3.2.29": // SHA1 with RSA signature
+ case "1.2.840.10040.4.3": // SHA1-1 with DSA
+ return "SHA1";
+ case "1.2.840.113549.1.1.11": // SHA-256 with RSA Encryption
+ return "SHA256";
+ case "1.2.840.113549.1.1.12": // SHA-384 with RSA Encryption
+ return "SHA384";
+ case "1.2.840.113549.1.1.13": // SHA-512 with RSA Encryption
+ return "SHA512";
+ case "1.3.36.3.3.1.2":
+ return "RIPEMD160";
+ default:
+ if (throwOnError)
+ throw new CryptographicException ("Unsupported hash algorithm: " + oid);
+ return null;
+ }
+ }
+
+ static internal HashAlgorithm CreateFromOid (string oid)
+ {
+ return CreateFromName (HashNameFromOid (oid));
+ }
+
+ static internal HashAlgorithm CreateFromName (string name)
+ {
+#if FULL_AOT_RUNTIME
+ switch (name) {
+ case "MD2":
+ return MD2.Create ();
+ case "MD4":
+ return MD4.Create ();
+ case "MD5":
+ return MD5.Create ();
+ case "SHA1":
+ return SHA1.Create ();
+ case "SHA256":
+ return SHA256.Create ();
+ case "SHA384":
+ return SHA384.Create ();
+ case "SHA512":
+ return SHA512.Create ();
+ case "RIPEMD160":
+ return RIPEMD160.Create ();
+ default:
+ try {
+ return (HashAlgorithm) Activator.CreateInstance (Type.GetType (name));
+ }
+ catch {
+ throw new CryptographicException ("Unsupported hash algorithm: " + name);
+ }
+ }
+#else
+ return HashAlgorithm.Create (name);
+#endif
+ }
+ }
+}
diff --git a/MediaBrowser.Server.Mono/Security/PKCS12.cs b/MediaBrowser.Server.Mono/Security/PKCS12.cs
new file mode 100644
index 000000000..fe39b7036
--- /dev/null
+++ b/MediaBrowser.Server.Mono/Security/PKCS12.cs
@@ -0,0 +1,2001 @@
+//
+// PKCS12.cs: PKCS 12 - Personal Information Exchange Syntax
+//
+// Author:
+// Sebastien Pouliot <sebastien@xamarin.com>
+//
+// (C) 2003 Motus Technologies Inc. (http://www.motus.com)
+// Copyright (C) 2004,2005,2006 Novell Inc. (http://www.novell.com)
+// Copyright 2013 Xamarin Inc. (http://www.xamarin.com)
+//
+// Key derivation translated from Bouncy Castle JCE (http://www.bouncycastle.org/)
+// See bouncycastle.txt for license.
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+using System;
+using System.Collections;
+using System.IO;
+using System.Security.Cryptography;
+using System.Text;
+
+using Mono.Security;
+using Mono.Security.Cryptography;
+
+namespace Mono.Security.X509 {
+
+#if INSIDE_CORLIB
+ internal
+#else
+ public
+#endif
+ class PKCS5 {
+
+ public const string pbeWithMD2AndDESCBC = "1.2.840.113549.1.5.1";
+ public const string pbeWithMD5AndDESCBC = "1.2.840.113549.1.5.3";
+ public const string pbeWithMD2AndRC2CBC = "1.2.840.113549.1.5.4";
+ public const string pbeWithMD5AndRC2CBC = "1.2.840.113549.1.5.6";
+ public const string pbeWithSHA1AndDESCBC = "1.2.840.113549.1.5.10";
+ public const string pbeWithSHA1AndRC2CBC = "1.2.840.113549.1.5.11";
+
+ public PKCS5 () {}
+ }
+
+#if INSIDE_CORLIB
+ internal
+#else
+ public
+#endif
+ class PKCS9 {
+
+ public const string friendlyName = "1.2.840.113549.1.9.20";
+ public const string localKeyId = "1.2.840.113549.1.9.21";
+
+ public PKCS9 () {}
+ }
+
+
+ internal class SafeBag {
+ private string _bagOID;
+ private ASN1 _asn1;
+
+ public SafeBag(string bagOID, ASN1 asn1) {
+ _bagOID = bagOID;
+ _asn1 = asn1;
+ }
+
+ public string BagOID {
+ get { return _bagOID; }
+ }
+
+ public ASN1 ASN1 {
+ get { return _asn1; }
+ }
+ }
+
+
+#if INSIDE_CORLIB
+ internal
+#else
+ public
+#endif
+ class PKCS12 : ICloneable {
+
+ public const string pbeWithSHAAnd128BitRC4 = "1.2.840.113549.1.12.1.1";
+ public const string pbeWithSHAAnd40BitRC4 = "1.2.840.113549.1.12.1.2";
+ public const string pbeWithSHAAnd3KeyTripleDESCBC = "1.2.840.113549.1.12.1.3";
+ public const string pbeWithSHAAnd2KeyTripleDESCBC = "1.2.840.113549.1.12.1.4";
+ public const string pbeWithSHAAnd128BitRC2CBC = "1.2.840.113549.1.12.1.5";
+ public const string pbeWithSHAAnd40BitRC2CBC = "1.2.840.113549.1.12.1.6";
+
+ // bags
+ public const string keyBag = "1.2.840.113549.1.12.10.1.1";
+ public const string pkcs8ShroudedKeyBag = "1.2.840.113549.1.12.10.1.2";
+ public const string certBag = "1.2.840.113549.1.12.10.1.3";
+ public const string crlBag = "1.2.840.113549.1.12.10.1.4";
+ public const string secretBag = "1.2.840.113549.1.12.10.1.5";
+ public const string safeContentsBag = "1.2.840.113549.1.12.10.1.6";
+
+ // types
+ public const string x509Certificate = "1.2.840.113549.1.9.22.1";
+ public const string sdsiCertificate = "1.2.840.113549.1.9.22.2";
+ public const string x509Crl = "1.2.840.113549.1.9.23.1";
+
+ // Adapted from BouncyCastle PKCS12ParametersGenerator.java
+ public class DeriveBytes {
+
+ public enum Purpose {
+ Key,
+ IV,
+ MAC
+ }
+
+ static private byte[] keyDiversifier = { 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1 };
+ static private byte[] ivDiversifier = { 2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2 };
+ static private byte[] macDiversifier = { 3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3 };
+
+ private string _hashName;
+ private int _iterations;
+ private byte[] _password;
+ private byte[] _salt;
+
+ public DeriveBytes () {}
+
+ public string HashName {
+ get { return _hashName; }
+ set { _hashName = value; }
+ }
+
+ public int IterationCount {
+ get { return _iterations; }
+ set { _iterations = value; }
+ }
+
+ public byte[] Password {
+ get { return (byte[]) _password.Clone (); }
+ set {
+ if (value == null)
+ _password = new byte [0];
+ else
+ _password = (byte[]) value.Clone ();
+ }
+ }
+
+ public byte[] Salt {
+ get { return (byte[]) _salt.Clone (); }
+ set {
+ if (value != null)
+ _salt = (byte[]) value.Clone ();
+ else
+ _salt = null;
+ }
+ }
+
+ private void Adjust (byte[] a, int aOff, byte[] b)
+ {
+ int x = (b[b.Length - 1] & 0xff) + (a [aOff + b.Length - 1] & 0xff) + 1;
+
+ a [aOff + b.Length - 1] = (byte) x;
+ x >>= 8;
+
+ for (int i = b.Length - 2; i >= 0; i--) {
+ x += (b [i] & 0xff) + (a [aOff + i] & 0xff);
+ a [aOff + i] = (byte) x;
+ x >>= 8;
+ }
+ }
+
+ private byte[] Derive (byte[] diversifier, int n)
+ {
+ HashAlgorithm digest = PKCS1.CreateFromName (_hashName);
+ int u = (digest.HashSize >> 3); // div 8
+ int v = 64;
+ byte[] dKey = new byte [n];
+
+ byte[] S;
+ if ((_salt != null) && (_salt.Length != 0)) {
+ S = new byte[v * ((_salt.Length + v - 1) / v)];
+
+ for (int i = 0; i != S.Length; i++) {
+ S[i] = _salt[i % _salt.Length];
+ }
+ }
+ else {
+ S = new byte[0];
+ }
+
+ byte[] P;
+ if ((_password != null) && (_password.Length != 0)) {
+ P = new byte[v * ((_password.Length + v - 1) / v)];
+
+ for (int i = 0; i != P.Length; i++) {
+ P[i] = _password[i % _password.Length];
+ }
+ }
+ else {
+ P = new byte[0];
+ }
+
+ byte[] I = new byte [S.Length + P.Length];
+
+ Buffer.BlockCopy (S, 0, I, 0, S.Length);
+ Buffer.BlockCopy (P, 0, I, S.Length, P.Length);
+
+ byte[] B = new byte[v];
+ int c = (n + u - 1) / u;
+
+ for (int i = 1; i <= c; i++) {
+ digest.TransformBlock (diversifier, 0, diversifier.Length, diversifier, 0);
+ digest.TransformFinalBlock (I, 0, I.Length);
+ byte[] A = digest.Hash;
+ digest.Initialize ();
+ for (int j = 1; j != _iterations; j++) {
+ A = digest.ComputeHash (A, 0, A.Length);
+ }
+
+ for (int j = 0; j != B.Length; j++) {
+ B [j] = A [j % A.Length];
+ }
+
+ for (int j = 0; j != I.Length / v; j++) {
+ Adjust (I, j * v, B);
+ }
+
+ if (i == c) {
+ Buffer.BlockCopy(A, 0, dKey, (i - 1) * u, dKey.Length - ((i - 1) * u));
+ }
+ else {
+ Buffer.BlockCopy(A, 0, dKey, (i - 1) * u, A.Length);
+ }
+ }
+
+ return dKey;
+ }
+
+ public byte[] DeriveKey (int size)
+ {
+ return Derive (keyDiversifier, size);
+ }
+
+ public byte[] DeriveIV (int size)
+ {
+ return Derive (ivDiversifier, size);
+ }
+
+ public byte[] DeriveMAC (int size)
+ {
+ return Derive (macDiversifier, size);
+ }
+ }
+
+ const int recommendedIterationCount = 2000;
+
+ //private int _version;
+ private byte[] _password;
+ private ArrayList _keyBags;
+ private ArrayList _secretBags;
+ private X509CertificateCollection _certs;
+ private bool _keyBagsChanged;
+ private bool _secretBagsChanged;
+ private bool _certsChanged;
+ private int _iterations;
+ private ArrayList _safeBags;
+ private RandomNumberGenerator _rng;
+
+ // constructors
+
+ public PKCS12 ()
+ {
+ _iterations = recommendedIterationCount;
+ _keyBags = new ArrayList ();
+ _secretBags = new ArrayList ();
+ _certs = new X509CertificateCollection ();
+ _keyBagsChanged = false;
+ _secretBagsChanged = false;
+ _certsChanged = false;
+ _safeBags = new ArrayList ();
+ }
+
+ public PKCS12 (byte[] data)
+ : this ()
+ {
+ Password = null;
+ Decode (data);
+ }
+
+ /*
+ * PFX ::= SEQUENCE {
+ * version INTEGER {v3(3)}(v3,...),
+ * authSafe ContentInfo,
+ * macData MacData OPTIONAL
+ * }
+ *
+ * MacData ::= SEQUENCE {
+ * mac DigestInfo,
+ * macSalt OCTET STRING,
+ * iterations INTEGER DEFAULT 1
+ * -- Note: The default is for historical reasons and its use is deprecated. A higher
+ * -- value, like 1024 is recommended.
+ * }
+ *
+ * SafeContents ::= SEQUENCE OF SafeBag
+ *
+ * SafeBag ::= SEQUENCE {
+ * bagId BAG-TYPE.&id ({PKCS12BagSet}),
+ * bagValue [0] EXPLICIT BAG-TYPE.&Type({PKCS12BagSet}{@bagId}),
+ * bagAttributes SET OF PKCS12Attribute OPTIONAL
+ * }
+ */
+ public PKCS12 (byte[] data, string password)
+ : this ()
+ {
+ Password = password;
+ Decode (data);
+ }
+
+ public PKCS12 (byte[] data, byte[] password)
+ : this ()
+ {
+ _password = password;
+ Decode (data);
+ }
+
+ private void Decode (byte[] data)
+ {
+ ASN1 pfx = new ASN1 (data);
+ if (pfx.Tag != 0x30)
+ throw new ArgumentException ("invalid data");
+
+ ASN1 version = pfx [0];
+ if (version.Tag != 0x02)
+ throw new ArgumentException ("invalid PFX version");
+ //_version = version.Value [0];
+
+ PKCS7.ContentInfo authSafe = new PKCS7.ContentInfo (pfx [1]);
+ if (authSafe.ContentType != PKCS7.Oid.data)
+ throw new ArgumentException ("invalid authenticated safe");
+
+ // now that we know it's a PKCS#12 file, check the (optional) MAC
+ // before decoding anything else in the file
+ if (pfx.Count > 2) {
+ ASN1 macData = pfx [2];
+ if (macData.Tag != 0x30)
+ throw new ArgumentException ("invalid MAC");
+
+ ASN1 mac = macData [0];
+ if (mac.Tag != 0x30)
+ throw new ArgumentException ("invalid MAC");
+ ASN1 macAlgorithm = mac [0];
+ string macOid = ASN1Convert.ToOid (macAlgorithm [0]);
+ if (macOid != "1.3.14.3.2.26")
+ throw new ArgumentException ("unsupported HMAC");
+ byte[] macValue = mac [1].Value;
+
+ ASN1 macSalt = macData [1];
+ if (macSalt.Tag != 0x04)
+ throw new ArgumentException ("missing MAC salt");
+
+ _iterations = 1; // default value
+ if (macData.Count > 2) {
+ ASN1 iters = macData [2];
+ if (iters.Tag != 0x02)
+ throw new ArgumentException ("invalid MAC iteration");
+ _iterations = ASN1Convert.ToInt32 (iters);
+ }
+
+ byte[] authSafeData = authSafe.Content [0].Value;
+ byte[] calculatedMac = MAC (_password, macSalt.Value, _iterations, authSafeData);
+ if (!Compare (macValue, calculatedMac)) {
+ byte[] nullPassword = {0, 0};
+ calculatedMac = MAC(nullPassword, macSalt.Value, _iterations, authSafeData);
+ if (!Compare (macValue, calculatedMac))
+ throw new CryptographicException ("Invalid MAC - file may have been tampe red!");
+ _password = nullPassword;
+ }
+ }
+
+ // we now returns to our original presentation - PFX
+ ASN1 authenticatedSafe = new ASN1 (authSafe.Content [0].Value);
+ for (int i=0; i < authenticatedSafe.Count; i++) {
+ PKCS7.ContentInfo ci = new PKCS7.ContentInfo (authenticatedSafe [i]);
+ switch (ci.ContentType) {
+ case PKCS7.Oid.data:
+ // unencrypted (by PKCS#12)
+ ASN1 safeContents = new ASN1 (ci.Content [0].Value);
+ for (int j=0; j < safeContents.Count; j++) {
+ ASN1 safeBag = safeContents [j];
+ ReadSafeBag (safeBag);
+ }
+ break;
+ case PKCS7.Oid.encryptedData:
+ // password encrypted
+ PKCS7.EncryptedData ed = new PKCS7.EncryptedData (ci.Content [0]);
+ ASN1 decrypted = new ASN1 (Decrypt (ed));
+ for (int j=0; j < decrypted.Count; j++) {
+ ASN1 safeBag = decrypted [j];
+ ReadSafeBag (safeBag);
+ }
+ break;
+ case PKCS7.Oid.envelopedData:
+ // public key encrypted
+ throw new NotImplementedException ("public key encrypted");
+ default:
+ throw new ArgumentException ("unknown authenticatedSafe");
+ }
+ }
+ }
+
+ ~PKCS12 ()
+ {
+ if (_password != null) {
+ Array.Clear (_password, 0, _password.Length);
+ }
+ _password = null;
+ }
+
+ // properties
+
+ public string Password {
+ set {
+ // Clear old password.
+ if (_password != null)
+ Array.Clear (_password, 0, _password.Length);
+ _password = null;
+ if (value != null) {
+ if (value.Length > 0) {
+ int size = value.Length;
+ int nul = 0;
+ if (size < MaximumPasswordLength) {
+ // if not present, add space for a NULL (0x00) character
+ if (value[size - 1] != 0x00)
+ nul = 1;
+ } else {
+ size = MaximumPasswordLength;
+ }
+ _password = new byte[(size + nul) << 1]; // double for unicode
+ Encoding.BigEndianUnicode.GetBytes (value, 0, size, _password, 0);
+ } else {
+ // double-byte (Unicode) NULL (0x00) - see bug #79617
+ _password = new byte[2];
+ }
+ }
+ }
+ }
+
+ public int IterationCount {
+ get { return _iterations; }
+ set { _iterations = value; }
+ }
+
+ public ArrayList Keys {
+ get {
+ if (_keyBagsChanged) {
+ _keyBags.Clear ();
+ foreach (SafeBag sb in _safeBags) {
+ if (sb.BagOID.Equals (keyBag)) {
+ ASN1 safeBag = sb.ASN1;
+ ASN1 bagValue = safeBag [1];
+ PKCS8.PrivateKeyInfo pki = new PKCS8.PrivateKeyInfo (bagValue.Value);
+ byte[] privateKey = pki.PrivateKey;
+ switch (privateKey [0]) {
+ case 0x02:
+ DSAParameters p = new DSAParameters (); // FIXME
+ _keyBags.Add (PKCS8.PrivateKeyInfo.DecodeDSA (privateKey, p));
+ break;
+ case 0x30:
+ _keyBags.Add (PKCS8.PrivateKeyInfo.DecodeRSA (privateKey));
+ break;
+ default:
+ break;
+ }
+ Array.Clear (privateKey, 0, privateKey.Length);
+
+ } else if (sb.BagOID.Equals (pkcs8ShroudedKeyBag)) {
+ ASN1 safeBag = sb.ASN1;
+ ASN1 bagValue = safeBag [1];
+ PKCS8.EncryptedPrivateKeyInfo epki = new PKCS8.EncryptedPrivateKeyInfo (bagValue.Value);
+ byte[] decrypted = Decrypt (epki.Algorithm, epki.Salt, epki.IterationCount, epki.EncryptedData);
+ PKCS8.PrivateKeyInfo pki = new PKCS8.PrivateKeyInfo (decrypted);
+ byte[] privateKey = pki.PrivateKey;
+ switch (privateKey [0]) {
+ case 0x02:
+ DSAParameters p = new DSAParameters (); // FIXME
+ _keyBags.Add (PKCS8.PrivateKeyInfo.DecodeDSA (privateKey, p));
+ break;
+ case 0x30:
+ _keyBags.Add (PKCS8.PrivateKeyInfo.DecodeRSA (privateKey));
+ break;
+ default:
+ break;
+ }
+ Array.Clear (privateKey, 0, privateKey.Length);
+ Array.Clear (decrypted, 0, decrypted.Length);
+ }
+ }
+ _keyBagsChanged = false;
+ }
+ return ArrayList.ReadOnly(_keyBags);
+ }
+ }
+
+ public ArrayList Secrets {
+ get {
+ if (_secretBagsChanged) {
+ _secretBags.Clear ();
+ foreach (SafeBag sb in _safeBags) {
+ if (sb.BagOID.Equals (secretBag)) {
+ ASN1 safeBag = sb.ASN1;
+ ASN1 bagValue = safeBag [1];
+ byte[] secret = bagValue.Value;
+ _secretBags.Add(secret);
+ }
+ }
+ _secretBagsChanged = false;
+ }
+ return ArrayList.ReadOnly(_secretBags);
+ }
+ }
+
+ public X509CertificateCollection Certificates {
+ get {
+ if (_certsChanged) {
+ _certs.Clear ();
+ foreach (SafeBag sb in _safeBags) {
+ if (sb.BagOID.Equals (certBag)) {
+ ASN1 safeBag = sb.ASN1;
+ ASN1 bagValue = safeBag [1];
+ PKCS7.ContentInfo cert = new PKCS7.ContentInfo (bagValue.Value);
+ _certs.Add (new X509Certificate (cert.Content [0].Value));
+ }
+ }
+ _certsChanged = false;
+ }
+ return _certs;
+ }
+ }
+
+ internal RandomNumberGenerator RNG {
+ get {
+ if (_rng == null)
+ _rng = RandomNumberGenerator.Create ();
+ return _rng;
+ }
+ }
+
+ // private methods
+
+ private bool Compare (byte[] expected, byte[] actual)
+ {
+ bool compare = false;
+ if (expected.Length == actual.Length) {
+ for (int i=0; i < expected.Length; i++) {
+ if (expected [i] != actual [i])
+ return false;
+ }
+ compare = true;
+ }
+ return compare;
+ }
+
+ private SymmetricAlgorithm GetSymmetricAlgorithm (string algorithmOid, byte[] salt, int iterationCount)
+ {
+ string algorithm = null;
+ int keyLength = 8; // 64 bits (default)
+ int ivLength = 8; // 64 bits (default)
+
+ PKCS12.DeriveBytes pd = new PKCS12.DeriveBytes ();
+ pd.Password = _password;
+ pd.Salt = salt;
+ pd.IterationCount = iterationCount;
+
+ switch (algorithmOid) {
+ case PKCS5.pbeWithMD2AndDESCBC: // no unit test available
+ pd.HashName = "MD2";
+ algorithm = "DES";
+ break;
+ case PKCS5.pbeWithMD5AndDESCBC: // no unit test available
+ pd.HashName = "MD5";
+ algorithm = "DES";
+ break;
+ case PKCS5.pbeWithMD2AndRC2CBC: // no unit test available
+ // TODO - RC2-CBC-Parameter (PKCS5)
+ // if missing default to 32 bits !!!
+ pd.HashName = "MD2";
+ algorithm = "RC2";
+ keyLength = 4; // default
+ break;
+ case PKCS5.pbeWithMD5AndRC2CBC: // no unit test available
+ // TODO - RC2-CBC-Parameter (PKCS5)
+ // if missing default to 32 bits !!!
+ pd.HashName = "MD5";
+ algorithm = "RC2";
+ keyLength = 4; // default
+ break;
+ case PKCS5.pbeWithSHA1AndDESCBC: // no unit test available
+ pd.HashName = "SHA1";
+ algorithm = "DES";
+ break;
+ case PKCS5.pbeWithSHA1AndRC2CBC: // no unit test available
+ // TODO - RC2-CBC-Parameter (PKCS5)
+ // if missing default to 32 bits !!!
+ pd.HashName = "SHA1";
+ algorithm = "RC2";
+ keyLength = 4; // default
+ break;
+ case PKCS12.pbeWithSHAAnd128BitRC4: // no unit test available
+ pd.HashName = "SHA1";
+ algorithm = "RC4";
+ keyLength = 16;
+ ivLength = 0; // N/A
+ break;
+ case PKCS12.pbeWithSHAAnd40BitRC4: // no unit test available
+ pd.HashName = "SHA1";
+ algorithm = "RC4";
+ keyLength = 5;
+ ivLength = 0; // N/A
+ break;
+ case PKCS12.pbeWithSHAAnd3KeyTripleDESCBC:
+ pd.HashName = "SHA1";
+ algorithm = "TripleDES";
+ keyLength = 24;
+ break;
+ case PKCS12.pbeWithSHAAnd2KeyTripleDESCBC: // no unit test available
+ pd.HashName = "SHA1";
+ algorithm = "TripleDES";
+ keyLength = 16;
+ break;
+ case PKCS12.pbeWithSHAAnd128BitRC2CBC: // no unit test available
+ pd.HashName = "SHA1";
+ algorithm = "RC2";
+ keyLength = 16;
+ break;
+ case PKCS12.pbeWithSHAAnd40BitRC2CBC:
+ pd.HashName = "SHA1";
+ algorithm = "RC2";
+ keyLength = 5;
+ break;
+ default:
+ throw new NotSupportedException ("unknown oid " + algorithm);
+ }
+
+ SymmetricAlgorithm sa = null;
+#if INSIDE_CORLIB && FULL_AOT_RUNTIME
+ // we do not want CryptoConfig to bring the whole crypto stack
+ // in particular Rijndael which is not supported by CommonCrypto
+ switch (algorithm) {
+ case "DES":
+ sa = DES.Create ();
+ break;
+ case "RC2":
+ sa = RC2.Create ();
+ break;
+ case "TripleDES":
+ sa = TripleDES.Create ();
+ break;
+ case "RC4":
+ sa = RC4.Create ();
+ break;
+ default:
+ throw new NotSupportedException (algorithm);
+ }
+#else
+ sa = SymmetricAlgorithm.Create (algorithm);
+#endif
+ sa.Key = pd.DeriveKey (keyLength);
+ // IV required only for block ciphers (not stream ciphers)
+ if (ivLength > 0) {
+ sa.IV = pd.DeriveIV (ivLength);
+ sa.Mode = CipherMode.CBC;
+ }
+ return sa;
+ }
+
+ public byte[] Decrypt (string algorithmOid, byte[] salt, int iterationCount, byte[] encryptedData)
+ {
+ SymmetricAlgorithm sa = null;
+ byte[] result = null;
+ try {
+ sa = GetSymmetricAlgorithm (algorithmOid, salt, iterationCount);
+ ICryptoTransform ct = sa.CreateDecryptor ();
+ result = ct.TransformFinalBlock (encryptedData, 0, encryptedData.Length);
+ }
+ finally {
+ if (sa != null)
+ sa.Clear ();
+ }
+ return result;
+ }
+
+ public byte[] Decrypt (PKCS7.EncryptedData ed)
+ {
+ return Decrypt (ed.EncryptionAlgorithm.ContentType,
+ ed.EncryptionAlgorithm.Content [0].Value,
+ ASN1Convert.ToInt32 (ed.EncryptionAlgorithm.Content [1]),
+ ed.EncryptedContent);
+ }
+
+ public byte[] Encrypt (string algorithmOid, byte[] salt, int iterationCount, byte[] data)
+ {
+ byte[] result = null;
+ using (SymmetricAlgorithm sa = GetSymmetricAlgorithm (algorithmOid, salt, iterationCount)) {
+ ICryptoTransform ct = sa.CreateEncryptor ();
+ result = ct.TransformFinalBlock (data, 0, data.Length);
+ }
+ return result;
+ }
+
+ private DSAParameters GetExistingParameters (out bool found)
+ {
+ foreach (X509Certificate cert in Certificates) {
+ // FIXME: that won't work if parts of the parameters are missing
+ if (cert.KeyAlgorithmParameters != null) {
+ DSA dsa = cert.DSA;
+ if (dsa != null) {
+ found = true;
+ return dsa.ExportParameters (false);
+ }
+ }
+ }
+ found = false;
+ return new DSAParameters ();
+ }
+
+ private void AddPrivateKey (PKCS8.PrivateKeyInfo pki)
+ {
+ byte[] privateKey = pki.PrivateKey;
+ switch (privateKey [0]) {
+ case 0x02:
+ bool found;
+ DSAParameters p = GetExistingParameters (out found);
+ if (found) {
+ _keyBags.Add (PKCS8.PrivateKeyInfo.DecodeDSA (privateKey, p));
+ }
+ break;
+ case 0x30:
+ _keyBags.Add (PKCS8.PrivateKeyInfo.DecodeRSA (privateKey));
+ break;
+ default:
+ Array.Clear (privateKey, 0, privateKey.Length);
+ throw new CryptographicException ("Unknown private key format");
+ }
+ Array.Clear (privateKey, 0, privateKey.Length);
+ }
+
+ private void ReadSafeBag (ASN1 safeBag)
+ {
+ if (safeBag.Tag != 0x30)
+ throw new ArgumentException ("invalid safeBag");
+
+ ASN1 bagId = safeBag [0];
+ if (bagId.Tag != 0x06)
+ throw new ArgumentException ("invalid safeBag id");
+
+ ASN1 bagValue = safeBag [1];
+ string oid = ASN1Convert.ToOid (bagId);
+ switch (oid) {
+ case keyBag:
+ // NEED UNIT TEST
+ AddPrivateKey (new PKCS8.PrivateKeyInfo (bagValue.Value));
+ break;
+ case pkcs8ShroudedKeyBag:
+ PKCS8.EncryptedPrivateKeyInfo epki = new PKCS8.EncryptedPrivateKeyInfo (bagValue.Value);
+ byte[] decrypted = Decrypt (epki.Algorithm, epki.Salt, epki.IterationCount, epki.EncryptedData);
+ AddPrivateKey (new PKCS8.PrivateKeyInfo (decrypted));
+ Array.Clear (decrypted, 0, decrypted.Length);
+ break;
+ case certBag:
+ PKCS7.ContentInfo cert = new PKCS7.ContentInfo (bagValue.Value);
+ if (cert.ContentType != x509Certificate)
+ throw new NotSupportedException ("unsupport certificate type");
+ X509Certificate x509 = new X509Certificate (cert.Content [0].Value);
+ _certs.Add (x509);
+ break;
+ case crlBag:
+ // TODO
+ break;
+ case secretBag:
+ byte[] secret = bagValue.Value;
+ _secretBags.Add(secret);
+ break;
+ case safeContentsBag:
+ // TODO - ? recurse ?
+ break;
+ default:
+ throw new ArgumentException ("unknown safeBag oid");
+ }
+
+ if (safeBag.Count > 2) {
+ ASN1 bagAttributes = safeBag [2];
+ if (bagAttributes.Tag != 0x31)
+ throw new ArgumentException ("invalid safeBag attributes id");
+
+ for (int i = 0; i < bagAttributes.Count; i++) {
+ ASN1 pkcs12Attribute = bagAttributes[i];
+
+ if (pkcs12Attribute.Tag != 0x30)
+ throw new ArgumentException ("invalid PKCS12 attributes id");
+
+ ASN1 attrId = pkcs12Attribute [0];
+ if (attrId.Tag != 0x06)
+ throw new ArgumentException ("invalid attribute id");
+
+ string attrOid = ASN1Convert.ToOid (attrId);
+
+ ASN1 attrValues = pkcs12Attribute[1];
+ for (int j = 0; j < attrValues.Count; j++) {
+ ASN1 attrValue = attrValues[j];
+
+ switch (attrOid) {
+ case PKCS9.friendlyName:
+ if (attrValue.Tag != 0x1e)
+ throw new ArgumentException ("invalid attribute value id");
+ break;
+ case PKCS9.localKeyId:
+ if (attrValue.Tag != 0x04)
+ throw new ArgumentException ("invalid attribute value id");
+ break;
+ default:
+ // Unknown OID -- don't check Tag
+ break;
+ }
+ }
+ }
+ }
+
+ _safeBags.Add (new SafeBag(oid, safeBag));
+ }
+
+ private ASN1 Pkcs8ShroudedKeyBagSafeBag (AsymmetricAlgorithm aa, IDictionary attributes)
+ {
+ PKCS8.PrivateKeyInfo pki = new PKCS8.PrivateKeyInfo ();
+ if (aa is RSA) {
+ pki.Algorithm = "1.2.840.113549.1.1.1";
+ pki.PrivateKey = PKCS8.PrivateKeyInfo.Encode ((RSA)aa);
+ }
+ else if (aa is DSA) {
+ pki.Algorithm = null;
+ pki.PrivateKey = PKCS8.PrivateKeyInfo.Encode ((DSA)aa);
+ }
+ else
+ throw new CryptographicException ("Unknown asymmetric algorithm {0}", aa.ToString ());
+
+ PKCS8.EncryptedPrivateKeyInfo epki = new PKCS8.EncryptedPrivateKeyInfo ();
+ epki.Algorithm = pbeWithSHAAnd3KeyTripleDESCBC;
+ epki.IterationCount = _iterations;
+ epki.EncryptedData = Encrypt (pbeWithSHAAnd3KeyTripleDESCBC, epki.Salt, _iterations, pki.GetBytes ());
+
+ ASN1 safeBag = new ASN1 (0x30);
+ safeBag.Add (ASN1Convert.FromOid (pkcs8ShroudedKeyBag));
+ ASN1 bagValue = new ASN1 (0xA0);
+ bagValue.Add (new ASN1 (epki.GetBytes ()));
+ safeBag.Add (bagValue);
+
+ if (attributes != null) {
+ ASN1 bagAttributes = new ASN1 (0x31);
+ IDictionaryEnumerator de = attributes.GetEnumerator ();
+
+ while (de.MoveNext ()) {
+ string oid = (string)de.Key;
+ switch (oid) {
+ case PKCS9.friendlyName:
+ ArrayList names = (ArrayList)de.Value;
+ if (names.Count > 0) {
+ ASN1 pkcs12Attribute = new ASN1 (0x30);
+ pkcs12Attribute.Add (ASN1Convert.FromOid (PKCS9.friendlyName));
+ ASN1 attrValues = new ASN1 (0x31);
+ foreach (byte[] name in names) {
+ ASN1 attrValue = new ASN1 (0x1e);
+ attrValue.Value = name;
+ attrValues.Add (attrValue);
+ }
+ pkcs12Attribute.Add (attrValues);
+ bagAttributes.Add (pkcs12Attribute);
+ }
+ break;
+ case PKCS9.localKeyId:
+ ArrayList keys = (ArrayList)de.Value;
+ if (keys.Count > 0) {
+ ASN1 pkcs12Attribute = new ASN1 (0x30);
+ pkcs12Attribute.Add (ASN1Convert.FromOid (PKCS9.localKeyId));
+ ASN1 attrValues = new ASN1 (0x31);
+ foreach (byte[] key in keys) {
+ ASN1 attrValue = new ASN1 (0x04);
+ attrValue.Value = key;
+ attrValues.Add (attrValue);
+ }
+ pkcs12Attribute.Add (attrValues);
+ bagAttributes.Add (pkcs12Attribute);
+ }
+ break;
+ default:
+ break;
+ }
+ }
+
+ if (bagAttributes.Count > 0) {
+ safeBag.Add (bagAttributes);
+ }
+ }
+
+ return safeBag;
+ }
+
+ private ASN1 KeyBagSafeBag (AsymmetricAlgorithm aa, IDictionary attributes)
+ {
+ PKCS8.PrivateKeyInfo pki = new PKCS8.PrivateKeyInfo ();
+ if (aa is RSA) {
+ pki.Algorithm = "1.2.840.113549.1.1.1";
+ pki.PrivateKey = PKCS8.PrivateKeyInfo.Encode ((RSA)aa);
+ }
+ else if (aa is DSA) {
+ pki.Algorithm = null;
+ pki.PrivateKey = PKCS8.PrivateKeyInfo.Encode ((DSA)aa);
+ }
+ else
+ throw new CryptographicException ("Unknown asymmetric algorithm {0}", aa.ToString ());
+
+ ASN1 safeBag = new ASN1 (0x30);
+ safeBag.Add (ASN1Convert.FromOid (keyBag));
+ ASN1 bagValue = new ASN1 (0xA0);
+ bagValue.Add (new ASN1 (pki.GetBytes ()));
+ safeBag.Add (bagValue);
+
+ if (attributes != null) {
+ ASN1 bagAttributes = new ASN1 (0x31);
+ IDictionaryEnumerator de = attributes.GetEnumerator ();
+
+ while (de.MoveNext ()) {
+ string oid = (string)de.Key;
+ switch (oid) {
+ case PKCS9.friendlyName:
+ ArrayList names = (ArrayList)de.Value;
+ if (names.Count > 0) {
+ ASN1 pkcs12Attribute = new ASN1 (0x30);
+ pkcs12Attribute.Add (ASN1Convert.FromOid (PKCS9.friendlyName));
+ ASN1 attrValues = new ASN1 (0x31);
+ foreach (byte[] name in names) {
+ ASN1 attrValue = new ASN1 (0x1e);
+ attrValue.Value = name;
+ attrValues.Add (attrValue);
+ }
+ pkcs12Attribute.Add (attrValues);
+ bagAttributes.Add (pkcs12Attribute);
+ }
+ break;
+ case PKCS9.localKeyId:
+ ArrayList keys = (ArrayList)de.Value;
+ if (keys.Count > 0) {
+ ASN1 pkcs12Attribute = new ASN1 (0x30);
+ pkcs12Attribute.Add (ASN1Convert.FromOid (PKCS9.localKeyId));
+ ASN1 attrValues = new ASN1 (0x31);
+ foreach (byte[] key in keys) {
+ ASN1 attrValue = new ASN1 (0x04);
+ attrValue.Value = key;
+ attrValues.Add (attrValue);
+ }
+ pkcs12Attribute.Add (attrValues);
+ bagAttributes.Add (pkcs12Attribute);
+ }
+ break;
+ default:
+ break;
+ }
+ }
+
+ if (bagAttributes.Count > 0) {
+ safeBag.Add (bagAttributes);
+ }
+ }
+
+ return safeBag;
+ }
+
+ private ASN1 SecretBagSafeBag (byte[] secret, IDictionary attributes)
+ {
+ ASN1 safeBag = new ASN1 (0x30);
+ safeBag.Add (ASN1Convert.FromOid (secretBag));
+ ASN1 bagValue = new ASN1 (0x80, secret);
+ safeBag.Add (bagValue);
+
+ if (attributes != null) {
+ ASN1 bagAttributes = new ASN1 (0x31);
+ IDictionaryEnumerator de = attributes.GetEnumerator ();
+
+ while (de.MoveNext ()) {
+ string oid = (string)de.Key;
+ switch (oid) {
+ case PKCS9.friendlyName:
+ ArrayList names = (ArrayList)de.Value;
+ if (names.Count > 0) {
+ ASN1 pkcs12Attribute = new ASN1 (0x30);
+ pkcs12Attribute.Add (ASN1Convert.FromOid (PKCS9.friendlyName));
+ ASN1 attrValues = new ASN1 (0x31);
+ foreach (byte[] name in names) {
+ ASN1 attrValue = new ASN1 (0x1e);
+ attrValue.Value = name;
+ attrValues.Add (attrValue);
+ }
+ pkcs12Attribute.Add (attrValues);
+ bagAttributes.Add (pkcs12Attribute);
+ }
+ break;
+ case PKCS9.localKeyId:
+ ArrayList keys = (ArrayList)de.Value;
+ if (keys.Count > 0) {
+ ASN1 pkcs12Attribute = new ASN1 (0x30);
+ pkcs12Attribute.Add (ASN1Convert.FromOid (PKCS9.localKeyId));
+ ASN1 attrValues = new ASN1 (0x31);
+ foreach (byte[] key in keys) {
+ ASN1 attrValue = new ASN1 (0x04);
+ attrValue.Value = key;
+ attrValues.Add (attrValue);
+ }
+ pkcs12Attribute.Add (attrValues);
+ bagAttributes.Add (pkcs12Attribute);
+ }
+ break;
+ default:
+ break;
+ }
+ }
+
+ if (bagAttributes.Count > 0) {
+ safeBag.Add (bagAttributes);
+ }
+ }
+
+ return safeBag;
+ }
+
+ private ASN1 CertificateSafeBag (X509Certificate x509, IDictionary attributes)
+ {
+ ASN1 encapsulatedCertificate = new ASN1 (0x04, x509.RawData);
+
+ PKCS7.ContentInfo ci = new PKCS7.ContentInfo ();
+ ci.ContentType = x509Certificate;
+ ci.Content.Add (encapsulatedCertificate);
+
+ ASN1 bagValue = new ASN1 (0xA0);
+ bagValue.Add (ci.ASN1);
+
+ ASN1 safeBag = new ASN1 (0x30);
+ safeBag.Add (ASN1Convert.FromOid (certBag));
+ safeBag.Add (bagValue);
+
+ if (attributes != null) {
+ ASN1 bagAttributes = new ASN1 (0x31);
+ IDictionaryEnumerator de = attributes.GetEnumerator ();
+
+ while (de.MoveNext ()) {
+ string oid = (string)de.Key;
+ switch (oid) {
+ case PKCS9.friendlyName:
+ ArrayList names = (ArrayList)de.Value;
+ if (names.Count > 0) {
+ ASN1 pkcs12Attribute = new ASN1 (0x30);
+ pkcs12Attribute.Add (ASN1Convert.FromOid (PKCS9.friendlyName));
+ ASN1 attrValues = new ASN1 (0x31);
+ foreach (byte[] name in names) {
+ ASN1 attrValue = new ASN1 (0x1e);
+ attrValue.Value = name;
+ attrValues.Add (attrValue);
+ }
+ pkcs12Attribute.Add (attrValues);
+ bagAttributes.Add (pkcs12Attribute);
+ }
+ break;
+ case PKCS9.localKeyId:
+ ArrayList keys = (ArrayList)de.Value;
+ if (keys.Count > 0) {
+ ASN1 pkcs12Attribute = new ASN1 (0x30);
+ pkcs12Attribute.Add (ASN1Convert.FromOid (PKCS9.localKeyId));
+ ASN1 attrValues = new ASN1 (0x31);
+ foreach (byte[] key in keys) {
+ ASN1 attrValue = new ASN1 (0x04);
+ attrValue.Value = key;
+ attrValues.Add (attrValue);
+ }
+ pkcs12Attribute.Add (attrValues);
+ bagAttributes.Add (pkcs12Attribute);
+ }
+ break;
+ default:
+ break;
+ }
+ }
+
+ if (bagAttributes.Count > 0) {
+ safeBag.Add (bagAttributes);
+ }
+ }
+
+ return safeBag;
+ }
+
+ private byte[] MAC (byte[] password, byte[] salt, int iterations, byte[] data)
+ {
+ PKCS12.DeriveBytes pd = new PKCS12.DeriveBytes ();
+ pd.HashName = "SHA1";
+ pd.Password = password;
+ pd.Salt = salt;
+ pd.IterationCount = iterations;
+
+ HMACSHA1 hmac = (HMACSHA1) HMACSHA1.Create ();
+ hmac.Key = pd.DeriveMAC (20);
+ return hmac.ComputeHash (data, 0, data.Length);
+ }
+
+ /*
+ * SafeContents ::= SEQUENCE OF SafeBag
+ *
+ * SafeBag ::= SEQUENCE {
+ * bagId BAG-TYPE.&id ({PKCS12BagSet}),
+ * bagValue [0] EXPLICIT BAG-TYPE.&Type({PKCS12BagSet}{@bagId}),
+ * bagAttributes SET OF PKCS12Attribute OPTIONAL
+ * }
+ */
+ public byte[] GetBytes ()
+ {
+ // TODO (incomplete)
+ ASN1 safeBagSequence = new ASN1 (0x30);
+
+ // Sync Safe Bag list since X509CertificateCollection may be updated
+ ArrayList scs = new ArrayList ();
+ foreach (SafeBag sb in _safeBags) {
+ if (sb.BagOID.Equals (certBag)) {
+ ASN1 safeBag = sb.ASN1;
+ ASN1 bagValue = safeBag [1];
+ PKCS7.ContentInfo cert = new PKCS7.ContentInfo (bagValue.Value);
+ scs.Add (new X509Certificate (cert.Content [0].Value));
+ }
+ }
+
+ ArrayList addcerts = new ArrayList ();
+ ArrayList removecerts = new ArrayList ();
+
+ foreach (X509Certificate c in Certificates) {
+ bool found = false;
+
+ foreach (X509Certificate lc in scs) {
+ if (Compare (c.RawData, lc.RawData)) {
+ found = true;
+ }
+ }
+
+ if (!found) {
+ addcerts.Add (c);
+ }
+ }
+ foreach (X509Certificate c in scs) {
+ bool found = false;
+
+ foreach (X509Certificate lc in Certificates) {
+ if (Compare (c.RawData, lc.RawData)) {
+ found = true;
+ }
+ }
+
+ if (!found) {
+ removecerts.Add (c);
+ }
+ }
+
+ foreach (X509Certificate c in removecerts) {
+ RemoveCertificate (c);
+ }
+
+ foreach (X509Certificate c in addcerts) {
+ AddCertificate (c);
+ }
+ // Sync done
+
+ if (_safeBags.Count > 0) {
+ ASN1 certsSafeBag = new ASN1 (0x30);
+ foreach (SafeBag sb in _safeBags) {
+ if (sb.BagOID.Equals (certBag)) {
+ certsSafeBag.Add (sb.ASN1);
+ }
+ }
+
+ if (certsSafeBag.Count > 0) {
+ PKCS7.ContentInfo contentInfo = EncryptedContentInfo (certsSafeBag, pbeWithSHAAnd3KeyTripleDESCBC);
+ safeBagSequence.Add (contentInfo.ASN1);
+ }
+ }
+
+ if (_safeBags.Count > 0) {
+ ASN1 safeContents = new ASN1 (0x30);
+ foreach (SafeBag sb in _safeBags) {
+ if (sb.BagOID.Equals (keyBag) ||
+ sb.BagOID.Equals (pkcs8ShroudedKeyBag)) {
+ safeContents.Add (sb.ASN1);
+ }
+ }
+ if (safeContents.Count > 0) {
+ ASN1 content = new ASN1 (0xA0);
+ content.Add (new ASN1 (0x04, safeContents.GetBytes ()));
+
+ PKCS7.ContentInfo keyBag = new PKCS7.ContentInfo (PKCS7.Oid.data);
+ keyBag.Content = content;
+ safeBagSequence.Add (keyBag.ASN1);
+ }
+ }
+
+ // Doing SecretBags separately in case we want to change their encryption independently.
+ if (_safeBags.Count > 0) {
+ ASN1 secretsSafeBag = new ASN1 (0x30);
+ foreach (SafeBag sb in _safeBags) {
+ if (sb.BagOID.Equals (secretBag)) {
+ secretsSafeBag.Add (sb.ASN1);
+ }
+ }
+
+ if (secretsSafeBag.Count > 0) {
+ PKCS7.ContentInfo contentInfo = EncryptedContentInfo (secretsSafeBag, pbeWithSHAAnd3KeyTripleDESCBC);
+ safeBagSequence.Add (contentInfo.ASN1);
+ }
+ }
+
+
+ ASN1 encapsulates = new ASN1 (0x04, safeBagSequence.GetBytes ());
+ ASN1 ci = new ASN1 (0xA0);
+ ci.Add (encapsulates);
+ PKCS7.ContentInfo authSafe = new PKCS7.ContentInfo (PKCS7.Oid.data);
+ authSafe.Content = ci;
+
+ ASN1 macData = new ASN1 (0x30);
+ if (_password != null) {
+ // only for password based encryption
+ byte[] salt = new byte [20];
+ RNG.GetBytes (salt);
+ byte[] macValue = MAC (_password, salt, _iterations, authSafe.Content [0].Value);
+ ASN1 oidSeq = new ASN1 (0x30);
+ oidSeq.Add (ASN1Convert.FromOid ("1.3.14.3.2.26")); // SHA1
+ oidSeq.Add (new ASN1 (0x05));
+
+ ASN1 mac = new ASN1 (0x30);
+ mac.Add (oidSeq);
+ mac.Add (new ASN1 (0x04, macValue));
+
+ macData.Add (mac);
+ macData.Add (new ASN1 (0x04, salt));
+ macData.Add (ASN1Convert.FromInt32 (_iterations));
+ }
+
+ ASN1 version = new ASN1 (0x02, new byte [1] { 0x03 });
+
+ ASN1 pfx = new ASN1 (0x30);
+ pfx.Add (version);
+ pfx.Add (authSafe.ASN1);
+ if (macData.Count > 0) {
+ // only for password based encryption
+ pfx.Add (macData);
+ }
+
+ return pfx.GetBytes ();
+ }
+
+ // Creates an encrypted PKCS#7 ContentInfo with safeBags as its SafeContents. Used in GetBytes(), above.
+ private PKCS7.ContentInfo EncryptedContentInfo(ASN1 safeBags, string algorithmOid)
+ {
+ byte[] salt = new byte [8];
+ RNG.GetBytes (salt);
+
+ ASN1 seqParams = new ASN1 (0x30);
+ seqParams.Add (new ASN1 (0x04, salt));
+ seqParams.Add (ASN1Convert.FromInt32 (_iterations));
+
+ ASN1 seqPbe = new ASN1 (0x30);
+ seqPbe.Add (ASN1Convert.FromOid (algorithmOid));
+ seqPbe.Add (seqParams);
+
+ byte[] encrypted = Encrypt (algorithmOid, salt, _iterations, safeBags.GetBytes ());
+ ASN1 encryptedContent = new ASN1 (0x80, encrypted);
+
+ ASN1 seq = new ASN1 (0x30);
+ seq.Add (ASN1Convert.FromOid (PKCS7.Oid.data));
+ seq.Add (seqPbe);
+ seq.Add (encryptedContent);
+
+ ASN1 version = new ASN1 (0x02, new byte [1] { 0x00 });
+ ASN1 encData = new ASN1 (0x30);
+ encData.Add (version);
+ encData.Add (seq);
+
+ ASN1 finalContent = new ASN1 (0xA0);
+ finalContent.Add (encData);
+
+ PKCS7.ContentInfo bag = new PKCS7.ContentInfo (PKCS7.Oid.encryptedData);
+ bag.Content = finalContent;
+ return bag;
+ }
+
+ public void AddCertificate (X509Certificate cert)
+ {
+ AddCertificate (cert, null);
+ }
+
+ public void AddCertificate (X509Certificate cert, IDictionary attributes)
+ {
+ bool found = false;
+
+ for (int i = 0; !found && i < _safeBags.Count; i++) {
+ SafeBag sb = (SafeBag)_safeBags [i];
+
+ if (sb.BagOID.Equals (certBag)) {
+ ASN1 safeBag = sb.ASN1;
+ ASN1 bagValue = safeBag [1];
+ PKCS7.ContentInfo crt = new PKCS7.ContentInfo (bagValue.Value);
+ X509Certificate c = new X509Certificate (crt.Content [0].Value);
+ if (Compare (cert.RawData, c.RawData)) {
+ found = true;
+ }
+ }
+ }
+
+ if (!found) {
+ _safeBags.Add (new SafeBag (certBag, CertificateSafeBag (cert, attributes)));
+ _certsChanged = true;
+ }
+ }
+
+ public void RemoveCertificate (X509Certificate cert)
+ {
+ RemoveCertificate (cert, null);
+ }
+
+ public void RemoveCertificate (X509Certificate cert, IDictionary attrs)
+ {
+ int certIndex = -1;
+
+ for (int i = 0; certIndex == -1 && i < _safeBags.Count; i++) {
+ SafeBag sb = (SafeBag)_safeBags [i];
+
+ if (sb.BagOID.Equals (certBag)) {
+ ASN1 safeBag = sb.ASN1;
+ ASN1 bagValue = safeBag [1];
+ PKCS7.ContentInfo crt = new PKCS7.ContentInfo (bagValue.Value);
+ X509Certificate c = new X509Certificate (crt.Content [0].Value);
+ if (Compare (cert.RawData, c.RawData)) {
+ if (attrs != null) {
+ if (safeBag.Count == 3) {
+ ASN1 bagAttributes = safeBag [2];
+ int bagAttributesFound = 0;
+ for (int j = 0; j < bagAttributes.Count; j++) {
+ ASN1 pkcs12Attribute = bagAttributes [j];
+ ASN1 attrId = pkcs12Attribute [0];
+ string ao = ASN1Convert.ToOid (attrId);
+ ArrayList dattrValues = (ArrayList)attrs [ao];
+
+ if (dattrValues != null) {
+ ASN1 attrValues = pkcs12Attribute [1];
+
+ if (dattrValues.Count == attrValues.Count) {
+ int attrValuesFound = 0;
+ for (int k = 0; k < attrValues.Count; k++) {
+ ASN1 attrValue = attrValues [k];
+ byte[] value = (byte[])dattrValues [k];
+
+ if (Compare (value, attrValue.Value)) {
+ attrValuesFound += 1;
+ }
+ }
+ if (attrValuesFound == attrValues.Count) {
+ bagAttributesFound += 1;
+ }
+ }
+ }
+ }
+ if (bagAttributesFound == bagAttributes.Count) {
+ certIndex = i;
+ }
+ }
+ } else {
+ certIndex = i;
+ }
+ }
+ }
+ }
+
+ if (certIndex != -1) {
+ _safeBags.RemoveAt (certIndex);
+ _certsChanged = true;
+ }
+ }
+
+ private bool CompareAsymmetricAlgorithm (AsymmetricAlgorithm a1, AsymmetricAlgorithm a2)
+ {
+ // fast path
+ if (a1.KeySize != a2.KeySize)
+ return false;
+ // compare public keys - if they match we can assume the private match too
+ return (a1.ToXmlString (false) == a2.ToXmlString (false));
+ }
+
+ public void AddPkcs8ShroudedKeyBag (AsymmetricAlgorithm aa)
+ {
+ AddPkcs8ShroudedKeyBag (aa, null);
+ }
+
+ public void AddPkcs8ShroudedKeyBag (AsymmetricAlgorithm aa, IDictionary attributes)
+ {
+ bool found = false;
+
+ for (int i = 0; !found && i < _safeBags.Count; i++) {
+ SafeBag sb = (SafeBag)_safeBags [i];
+
+ if (sb.BagOID.Equals (pkcs8ShroudedKeyBag)) {
+ ASN1 bagValue = sb.ASN1 [1];
+ PKCS8.EncryptedPrivateKeyInfo epki = new PKCS8.EncryptedPrivateKeyInfo (bagValue.Value);
+ byte[] decrypted = Decrypt (epki.Algorithm, epki.Salt, epki.IterationCount, epki.EncryptedData);
+ PKCS8.PrivateKeyInfo pki = new PKCS8.PrivateKeyInfo (decrypted);
+ byte[] privateKey = pki.PrivateKey;
+
+ AsymmetricAlgorithm saa = null;
+ switch (privateKey [0]) {
+ case 0x02:
+ DSAParameters p = new DSAParameters (); // FIXME
+ saa = PKCS8.PrivateKeyInfo.DecodeDSA (privateKey, p);
+ break;
+ case 0x30:
+ saa = PKCS8.PrivateKeyInfo.DecodeRSA (privateKey);
+ break;
+ default:
+ Array.Clear (decrypted, 0, decrypted.Length);
+ Array.Clear (privateKey, 0, privateKey.Length);
+ throw new CryptographicException ("Unknown private key format");
+ }
+
+ Array.Clear (decrypted, 0, decrypted.Length);
+ Array.Clear (privateKey, 0, privateKey.Length);
+
+ if (CompareAsymmetricAlgorithm (aa , saa)) {
+ found = true;
+ }
+ }
+ }
+
+ if (!found) {
+ _safeBags.Add (new SafeBag (pkcs8ShroudedKeyBag, Pkcs8ShroudedKeyBagSafeBag (aa, attributes)));
+ _keyBagsChanged = true;
+ }
+ }
+
+ public void RemovePkcs8ShroudedKeyBag (AsymmetricAlgorithm aa)
+ {
+ int aaIndex = -1;
+
+ for (int i = 0; aaIndex == -1 && i < _safeBags.Count; i++) {
+ SafeBag sb = (SafeBag)_safeBags [i];
+
+ if (sb.BagOID.Equals (pkcs8ShroudedKeyBag)) {
+ ASN1 bagValue = sb.ASN1 [1];
+ PKCS8.EncryptedPrivateKeyInfo epki = new PKCS8.EncryptedPrivateKeyInfo (bagValue.Value);
+ byte[] decrypted = Decrypt (epki.Algorithm, epki.Salt, epki.IterationCount, epki.EncryptedData);
+ PKCS8.PrivateKeyInfo pki = new PKCS8.PrivateKeyInfo (decrypted);
+ byte[] privateKey = pki.PrivateKey;
+
+ AsymmetricAlgorithm saa = null;
+ switch (privateKey [0]) {
+ case 0x02:
+ DSAParameters p = new DSAParameters (); // FIXME
+ saa = PKCS8.PrivateKeyInfo.DecodeDSA (privateKey, p);
+ break;
+ case 0x30:
+ saa = PKCS8.PrivateKeyInfo.DecodeRSA (privateKey);
+ break;
+ default:
+ Array.Clear (decrypted, 0, decrypted.Length);
+ Array.Clear (privateKey, 0, privateKey.Length);
+ throw new CryptographicException ("Unknown private key format");
+ }
+
+ Array.Clear (decrypted, 0, decrypted.Length);
+ Array.Clear (privateKey, 0, privateKey.Length);
+
+ if (CompareAsymmetricAlgorithm (aa, saa)) {
+ aaIndex = i;
+ }
+ }
+ }
+
+ if (aaIndex != -1) {
+ _safeBags.RemoveAt (aaIndex);
+ _keyBagsChanged = true;
+ }
+ }
+
+ public void AddKeyBag (AsymmetricAlgorithm aa)
+ {
+ AddKeyBag (aa, null);
+ }
+
+ public void AddKeyBag (AsymmetricAlgorithm aa, IDictionary attributes)
+ {
+ bool found = false;
+
+ for (int i = 0; !found && i < _safeBags.Count; i++) {
+ SafeBag sb = (SafeBag)_safeBags [i];
+
+ if (sb.BagOID.Equals (keyBag)) {
+ ASN1 bagValue = sb.ASN1 [1];
+ PKCS8.PrivateKeyInfo pki = new PKCS8.PrivateKeyInfo (bagValue.Value);
+ byte[] privateKey = pki.PrivateKey;
+
+ AsymmetricAlgorithm saa = null;
+ switch (privateKey [0]) {
+ case 0x02:
+ DSAParameters p = new DSAParameters (); // FIXME
+ saa = PKCS8.PrivateKeyInfo.DecodeDSA (privateKey, p);
+ break;
+ case 0x30:
+ saa = PKCS8.PrivateKeyInfo.DecodeRSA (privateKey);
+ break;
+ default:
+ Array.Clear (privateKey, 0, privateKey.Length);
+ throw new CryptographicException ("Unknown private key format");
+ }
+
+ Array.Clear (privateKey, 0, privateKey.Length);
+
+ if (CompareAsymmetricAlgorithm (aa, saa)) {
+ found = true;
+ }
+ }
+ }
+
+ if (!found) {
+ _safeBags.Add (new SafeBag (keyBag, KeyBagSafeBag (aa, attributes)));
+ _keyBagsChanged = true;
+ }
+ }
+
+ public void RemoveKeyBag (AsymmetricAlgorithm aa)
+ {
+ int aaIndex = -1;
+
+ for (int i = 0; aaIndex == -1 && i < _safeBags.Count; i++) {
+ SafeBag sb = (SafeBag)_safeBags [i];
+
+ if (sb.BagOID.Equals (keyBag)) {
+ ASN1 bagValue = sb.ASN1 [1];
+ PKCS8.PrivateKeyInfo pki = new PKCS8.PrivateKeyInfo (bagValue.Value);
+ byte[] privateKey = pki.PrivateKey;
+
+ AsymmetricAlgorithm saa = null;
+ switch (privateKey [0]) {
+ case 0x02:
+ DSAParameters p = new DSAParameters (); // FIXME
+ saa = PKCS8.PrivateKeyInfo.DecodeDSA (privateKey, p);
+ break;
+ case 0x30:
+ saa = PKCS8.PrivateKeyInfo.DecodeRSA (privateKey);
+ break;
+ default:
+ Array.Clear (privateKey, 0, privateKey.Length);
+ throw new CryptographicException ("Unknown private key format");
+ }
+
+ Array.Clear (privateKey, 0, privateKey.Length);
+
+ if (CompareAsymmetricAlgorithm (aa, saa)) {
+ aaIndex = i;
+ }
+ }
+ }
+
+ if (aaIndex != -1) {
+ _safeBags.RemoveAt (aaIndex);
+ _keyBagsChanged = true;
+ }
+ }
+
+ public void AddSecretBag (byte[] secret)
+ {
+ AddSecretBag (secret, null);
+ }
+
+ public void AddSecretBag (byte[] secret, IDictionary attributes)
+ {
+ bool found = false;
+
+ for (int i = 0; !found && i < _safeBags.Count; i++) {
+ SafeBag sb = (SafeBag)_safeBags [i];
+
+ if (sb.BagOID.Equals (secretBag)) {
+ ASN1 bagValue = sb.ASN1 [1];
+ byte[] ssecret = bagValue.Value;
+
+ if (Compare (secret, ssecret)) {
+ found = true;
+ }
+ }
+ }
+
+ if (!found) {
+ _safeBags.Add (new SafeBag (secretBag, SecretBagSafeBag (secret, attributes)));
+ _secretBagsChanged = true;
+ }
+ }
+
+ public void RemoveSecretBag (byte[] secret)
+ {
+ int sIndex = -1;
+
+ for (int i = 0; sIndex == -1 && i < _safeBags.Count; i++) {
+ SafeBag sb = (SafeBag)_safeBags [i];
+
+ if (sb.BagOID.Equals (secretBag)) {
+ ASN1 bagValue = sb.ASN1 [1];
+ byte[] ssecret = bagValue.Value;
+
+ if (Compare (secret, ssecret)) {
+ sIndex = i;
+ }
+ }
+ }
+
+ if (sIndex != -1) {
+ _safeBags.RemoveAt (sIndex);
+ _secretBagsChanged = true;
+ }
+ }
+
+ public AsymmetricAlgorithm GetAsymmetricAlgorithm (IDictionary attrs)
+ {
+ foreach (SafeBag sb in _safeBags) {
+ if (sb.BagOID.Equals (keyBag) || sb.BagOID.Equals (pkcs8ShroudedKeyBag)) {
+ ASN1 safeBag = sb.ASN1;
+
+ if (safeBag.Count == 3) {
+ ASN1 bagAttributes = safeBag [2];
+
+ int bagAttributesFound = 0;
+ for (int i = 0; i < bagAttributes.Count; i++) {
+ ASN1 pkcs12Attribute = bagAttributes [i];
+ ASN1 attrId = pkcs12Attribute [0];
+ string ao = ASN1Convert.ToOid (attrId);
+ ArrayList dattrValues = (ArrayList)attrs [ao];
+
+ if (dattrValues != null) {
+ ASN1 attrValues = pkcs12Attribute [1];
+
+ if (dattrValues.Count == attrValues.Count) {
+ int attrValuesFound = 0;
+ for (int j = 0; j < attrValues.Count; j++) {
+ ASN1 attrValue = attrValues [j];
+ byte[] value = (byte[])dattrValues [j];
+
+ if (Compare (value, attrValue.Value)) {
+ attrValuesFound += 1;
+ }
+ }
+ if (attrValuesFound == attrValues.Count) {
+ bagAttributesFound += 1;
+ }
+ }
+ }
+ }
+ if (bagAttributesFound == bagAttributes.Count) {
+ ASN1 bagValue = safeBag [1];
+ AsymmetricAlgorithm aa = null;
+ if (sb.BagOID.Equals (keyBag)) {
+ PKCS8.PrivateKeyInfo pki = new PKCS8.PrivateKeyInfo (bagValue.Value);
+ byte[] privateKey = pki.PrivateKey;
+ switch (privateKey [0]) {
+ case 0x02:
+ DSAParameters p = new DSAParameters (); // FIXME
+ aa = PKCS8.PrivateKeyInfo.DecodeDSA (privateKey, p);
+ break;
+ case 0x30:
+ aa = PKCS8.PrivateKeyInfo.DecodeRSA (privateKey);
+ break;
+ default:
+ break;
+ }
+ Array.Clear (privateKey, 0, privateKey.Length);
+ } else if (sb.BagOID.Equals (pkcs8ShroudedKeyBag)) {
+ PKCS8.EncryptedPrivateKeyInfo epki = new PKCS8.EncryptedPrivateKeyInfo (bagValue.Value);
+ byte[] decrypted = Decrypt (epki.Algorithm, epki.Salt, epki.IterationCount, epki.EncryptedData);
+ PKCS8.PrivateKeyInfo pki = new PKCS8.PrivateKeyInfo (decrypted);
+ byte[] privateKey = pki.PrivateKey;
+ switch (privateKey [0]) {
+ case 0x02:
+ DSAParameters p = new DSAParameters (); // FIXME
+ aa = PKCS8.PrivateKeyInfo.DecodeDSA (privateKey, p);
+ break;
+ case 0x30:
+ aa = PKCS8.PrivateKeyInfo.DecodeRSA (privateKey);
+ break;
+ default:
+ break;
+ }
+ Array.Clear (privateKey, 0, privateKey.Length);
+ Array.Clear (decrypted, 0, decrypted.Length);
+ }
+ return aa;
+ }
+ }
+ }
+ }
+
+ return null;
+ }
+
+ public byte[] GetSecret (IDictionary attrs)
+ {
+ foreach (SafeBag sb in _safeBags) {
+ if (sb.BagOID.Equals (secretBag)) {
+ ASN1 safeBag = sb.ASN1;
+
+ if (safeBag.Count == 3) {
+ ASN1 bagAttributes = safeBag [2];
+
+ int bagAttributesFound = 0;
+ for (int i = 0; i < bagAttributes.Count; i++) {
+ ASN1 pkcs12Attribute = bagAttributes [i];
+ ASN1 attrId = pkcs12Attribute [0];
+ string ao = ASN1Convert.ToOid (attrId);
+ ArrayList dattrValues = (ArrayList)attrs [ao];
+
+ if (dattrValues != null) {
+ ASN1 attrValues = pkcs12Attribute [1];
+
+ if (dattrValues.Count == attrValues.Count) {
+ int attrValuesFound = 0;
+ for (int j = 0; j < attrValues.Count; j++) {
+ ASN1 attrValue = attrValues [j];
+ byte[] value = (byte[])dattrValues [j];
+
+ if (Compare (value, attrValue.Value)) {
+ attrValuesFound += 1;
+ }
+ }
+ if (attrValuesFound == attrValues.Count) {
+ bagAttributesFound += 1;
+ }
+ }
+ }
+ }
+ if (bagAttributesFound == bagAttributes.Count) {
+ ASN1 bagValue = safeBag [1];
+ return bagValue.Value;
+ }
+ }
+ }
+ }
+
+ return null;
+ }
+
+ public X509Certificate GetCertificate (IDictionary attrs)
+ {
+ foreach (SafeBag sb in _safeBags) {
+ if (sb.BagOID.Equals (certBag)) {
+ ASN1 safeBag = sb.ASN1;
+
+ if (safeBag.Count == 3) {
+ ASN1 bagAttributes = safeBag [2];
+
+ int bagAttributesFound = 0;
+ for (int i = 0; i < bagAttributes.Count; i++) {
+ ASN1 pkcs12Attribute = bagAttributes [i];
+ ASN1 attrId = pkcs12Attribute [0];
+ string ao = ASN1Convert.ToOid (attrId);
+ ArrayList dattrValues = (ArrayList)attrs [ao];
+
+ if (dattrValues != null) {
+ ASN1 attrValues = pkcs12Attribute [1];
+
+ if (dattrValues.Count == attrValues.Count) {
+ int attrValuesFound = 0;
+ for (int j = 0; j < attrValues.Count; j++) {
+ ASN1 attrValue = attrValues [j];
+ byte[] value = (byte[])dattrValues [j];
+
+ if (Compare (value, attrValue.Value)) {
+ attrValuesFound += 1;
+ }
+ }
+ if (attrValuesFound == attrValues.Count) {
+ bagAttributesFound += 1;
+ }
+ }
+ }
+ }
+ if (bagAttributesFound == bagAttributes.Count) {
+ ASN1 bagValue = safeBag [1];
+ PKCS7.ContentInfo crt = new PKCS7.ContentInfo (bagValue.Value);
+ return new X509Certificate (crt.Content [0].Value);
+ }
+ }
+ }
+ }
+
+ return null;
+ }
+
+ public IDictionary GetAttributes (AsymmetricAlgorithm aa)
+ {
+ IDictionary result = new Hashtable ();
+
+ foreach (SafeBag sb in _safeBags) {
+ if (sb.BagOID.Equals (keyBag) || sb.BagOID.Equals (pkcs8ShroudedKeyBag)) {
+ ASN1 safeBag = sb.ASN1;
+
+ ASN1 bagValue = safeBag [1];
+ AsymmetricAlgorithm saa = null;
+ if (sb.BagOID.Equals (keyBag)) {
+ PKCS8.PrivateKeyInfo pki = new PKCS8.PrivateKeyInfo (bagValue.Value);
+ byte[] privateKey = pki.PrivateKey;
+ switch (privateKey [0]) {
+ case 0x02:
+ DSAParameters p = new DSAParameters (); // FIXME
+ saa = PKCS8.PrivateKeyInfo.DecodeDSA (privateKey, p);
+ break;
+ case 0x30:
+ saa = PKCS8.PrivateKeyInfo.DecodeRSA (privateKey);
+ break;
+ default:
+ break;
+ }
+ Array.Clear (privateKey, 0, privateKey.Length);
+ } else if (sb.BagOID.Equals (pkcs8ShroudedKeyBag)) {
+ PKCS8.EncryptedPrivateKeyInfo epki = new PKCS8.EncryptedPrivateKeyInfo (bagValue.Value);
+ byte[] decrypted = Decrypt (epki.Algorithm, epki.Salt, epki.IterationCount, epki.EncryptedData);
+ PKCS8.PrivateKeyInfo pki = new PKCS8.PrivateKeyInfo (decrypted);
+ byte[] privateKey = pki.PrivateKey;
+ switch (privateKey [0]) {
+ case 0x02:
+ DSAParameters p = new DSAParameters (); // FIXME
+ saa = PKCS8.PrivateKeyInfo.DecodeDSA (privateKey, p);
+ break;
+ case 0x30:
+ saa = PKCS8.PrivateKeyInfo.DecodeRSA (privateKey);
+ break;
+ default:
+ break;
+ }
+ Array.Clear (privateKey, 0, privateKey.Length);
+ Array.Clear (decrypted, 0, decrypted.Length);
+ }
+
+ if (saa != null && CompareAsymmetricAlgorithm (saa, aa)) {
+ if (safeBag.Count == 3) {
+ ASN1 bagAttributes = safeBag [2];
+
+ for (int i = 0; i < bagAttributes.Count; i++) {
+ ASN1 pkcs12Attribute = bagAttributes [i];
+ ASN1 attrId = pkcs12Attribute [0];
+ string aOid = ASN1Convert.ToOid (attrId);
+ ArrayList aValues = new ArrayList ();
+
+ ASN1 attrValues = pkcs12Attribute [1];
+
+ for (int j = 0; j < attrValues.Count; j++) {
+ ASN1 attrValue = attrValues [j];
+ aValues.Add (attrValue.Value);
+ }
+ result.Add (aOid, aValues);
+ }
+ }
+ }
+ }
+ }
+
+ return result;
+ }
+
+ public IDictionary GetAttributes (X509Certificate cert)
+ {
+ IDictionary result = new Hashtable ();
+
+ foreach (SafeBag sb in _safeBags) {
+ if (sb.BagOID.Equals (certBag)) {
+ ASN1 safeBag = sb.ASN1;
+ ASN1 bagValue = safeBag [1];
+ PKCS7.ContentInfo crt = new PKCS7.ContentInfo (bagValue.Value);
+ X509Certificate xc = new X509Certificate (crt.Content [0].Value);
+
+ if (Compare (cert.RawData, xc.RawData)) {
+ if (safeBag.Count == 3) {
+ ASN1 bagAttributes = safeBag [2];
+
+ for (int i = 0; i < bagAttributes.Count; i++) {
+ ASN1 pkcs12Attribute = bagAttributes [i];
+ ASN1 attrId = pkcs12Attribute [0];
+
+ string aOid = ASN1Convert.ToOid (attrId);
+ ArrayList aValues = new ArrayList ();
+
+ ASN1 attrValues = pkcs12Attribute [1];
+
+ for (int j = 0; j < attrValues.Count; j++) {
+ ASN1 attrValue = attrValues [j];
+ aValues.Add (attrValue.Value);
+ }
+ result.Add (aOid, aValues);
+ }
+ }
+ }
+ }
+ }
+
+ return result;
+ }
+
+ public void SaveToFile (string filename)
+ {
+ if (filename == null)
+ throw new ArgumentNullException ("filename");
+
+ using (FileStream fs = File.Create (filename)) {
+ byte[] data = GetBytes ();
+ fs.Write (data, 0, data.Length);
+ }
+ }
+
+ public object Clone ()
+ {
+ PKCS12 clone = null;
+ if (_password != null) {
+ clone = new PKCS12 (GetBytes (), Encoding.BigEndianUnicode.GetString (_password));
+ } else {
+ clone = new PKCS12 (GetBytes ());
+ }
+ clone.IterationCount = this.IterationCount;
+
+ return clone;
+ }
+
+ // static
+
+ public const int CryptoApiPasswordLimit = 32;
+
+ static private int password_max_length = Int32.MaxValue;
+
+ // static properties
+
+ // MS CryptoAPI limits the password to a maximum of 31 characters
+ // other implementations, like OpenSSL, have no such limitation.
+ // Setting a maximum value will truncate the password length to
+ // ensure compatibility with MS's PFXImportCertStore API.
+ static public int MaximumPasswordLength {
+ get { return password_max_length; }
+ set {
+ if (value < CryptoApiPasswordLimit) {
+ string msg = string.Format ("Maximum password length cannot be less than {0}.", CryptoApiPasswordLimit);
+ throw new ArgumentOutOfRangeException (msg);
+ }
+ password_max_length = value;
+ }
+ }
+
+ // static methods
+
+ static private byte[] LoadFile (string filename)
+ {
+ byte[] data = null;
+ using (FileStream fs = File.OpenRead (filename)) {
+ data = new byte [fs.Length];
+ fs.Read (data, 0, data.Length);
+ fs.Close ();
+ }
+ return data;
+ }
+
+ static public PKCS12 LoadFromFile (string filename)
+ {
+ if (filename == null)
+ throw new ArgumentNullException ("filename");
+
+ return new PKCS12 (LoadFile (filename));
+ }
+
+ static public PKCS12 LoadFromFile (string filename, string password)
+ {
+ if (filename == null)
+ throw new ArgumentNullException ("filename");
+
+ return new PKCS12 (LoadFile (filename), password);
+ }
+ }
+}
diff --git a/MediaBrowser.Server.Mono/Security/PKCS7.cs b/MediaBrowser.Server.Mono/Security/PKCS7.cs
new file mode 100644
index 000000000..d7e93569d
--- /dev/null
+++ b/MediaBrowser.Server.Mono/Security/PKCS7.cs
@@ -0,0 +1,1018 @@
+//
+// PKCS7.cs: PKCS #7 - Cryptographic Message Syntax Standard
+// http://www.rsasecurity.com/rsalabs/pkcs/pkcs-7/index.html
+//
+// Authors:
+// Sebastien Pouliot <sebastien@ximian.com>
+// Daniel Granath <dgranath#gmail.com>
+//
+// (C) 2002, 2003 Motus Technologies Inc. (http://www.motus.com)
+// Copyright (C) 2004-2005 Novell, Inc (http://www.novell.com)
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+using System;
+using System.Collections;
+using System.Security.Cryptography;
+
+using Mono.Security.X509;
+
+namespace Mono.Security {
+
+#if INSIDE_CORLIB
+ internal
+#else
+ public
+#endif
+ sealed class PKCS7 {
+
+ public class Oid {
+ // pkcs 1
+ public const string rsaEncryption = "1.2.840.113549.1.1.1";
+ // pkcs 7
+ public const string data = "1.2.840.113549.1.7.1";
+ public const string signedData = "1.2.840.113549.1.7.2";
+ public const string envelopedData = "1.2.840.113549.1.7.3";
+ public const string signedAndEnvelopedData = "1.2.840.113549.1.7.4";
+ public const string digestedData = "1.2.840.113549.1.7.5";
+ public const string encryptedData = "1.2.840.113549.1.7.6";
+ // pkcs 9
+ public const string contentType = "1.2.840.113549.1.9.3";
+ public const string messageDigest = "1.2.840.113549.1.9.4";
+ public const string signingTime = "1.2.840.113549.1.9.5";
+ public const string countersignature = "1.2.840.113549.1.9.6";
+
+ public Oid ()
+ {
+ }
+ }
+
+ private PKCS7 ()
+ {
+ }
+
+ static public ASN1 Attribute (string oid, ASN1 value)
+ {
+ ASN1 attr = new ASN1 (0x30);
+ attr.Add (ASN1Convert.FromOid (oid));
+ ASN1 aset = attr.Add (new ASN1 (0x31));
+ aset.Add (value);
+ return attr;
+ }
+
+ static public ASN1 AlgorithmIdentifier (string oid)
+ {
+ ASN1 ai = new ASN1 (0x30);
+ ai.Add (ASN1Convert.FromOid (oid));
+ ai.Add (new ASN1 (0x05)); // NULL
+ return ai;
+ }
+
+ static public ASN1 AlgorithmIdentifier (string oid, ASN1 parameters)
+ {
+ ASN1 ai = new ASN1 (0x30);
+ ai.Add (ASN1Convert.FromOid (oid));
+ ai.Add (parameters);
+ return ai;
+ }
+
+ /*
+ * IssuerAndSerialNumber ::= SEQUENCE {
+ * issuer Name,
+ * serialNumber CertificateSerialNumber
+ * }
+ */
+ static public ASN1 IssuerAndSerialNumber (X509Certificate x509)
+ {
+ ASN1 issuer = null;
+ ASN1 serial = null;
+ ASN1 cert = new ASN1 (x509.RawData);
+ int tbs = 0;
+ bool flag = false;
+ while (tbs < cert[0].Count) {
+ ASN1 e = cert[0][tbs++];
+ if (e.Tag == 0x02)
+ serial = e;
+ else if (e.Tag == 0x30) {
+ if (flag) {
+ issuer = e;
+ break;
+ }
+ flag = true;
+ }
+ }
+ ASN1 iasn = new ASN1 (0x30);
+ iasn.Add (issuer);
+ iasn.Add (serial);
+ return iasn;
+ }
+
+ /*
+ * ContentInfo ::= SEQUENCE {
+ * contentType ContentType,
+ * content [0] EXPLICIT ANY DEFINED BY contentType OPTIONAL
+ * }
+ * ContentType ::= OBJECT IDENTIFIER
+ */
+ public class ContentInfo {
+
+ private string contentType;
+ private ASN1 content;
+
+ public ContentInfo ()
+ {
+ content = new ASN1 (0xA0);
+ }
+
+ public ContentInfo (string oid) : this ()
+ {
+ contentType = oid;
+ }
+
+ public ContentInfo (byte[] data)
+ : this (new ASN1 (data)) {}
+
+ public ContentInfo (ASN1 asn1)
+ {
+ // SEQUENCE with 1 or 2 elements
+ if ((asn1.Tag != 0x30) || ((asn1.Count < 1) && (asn1.Count > 2)))
+ throw new ArgumentException ("Invalid ASN1");
+ if (asn1[0].Tag != 0x06)
+ throw new ArgumentException ("Invalid contentType");
+ contentType = ASN1Convert.ToOid (asn1[0]);
+ if (asn1.Count > 1) {
+ if (asn1[1].Tag != 0xA0)
+ throw new ArgumentException ("Invalid content");
+ content = asn1[1];
+ }
+ }
+
+ public ASN1 ASN1 {
+ get { return GetASN1(); }
+ }
+
+ public ASN1 Content {
+ get { return content; }
+ set { content = value; }
+ }
+
+ public string ContentType {
+ get { return contentType; }
+ set { contentType = value; }
+ }
+
+ internal ASN1 GetASN1 ()
+ {
+ // ContentInfo ::= SEQUENCE {
+ ASN1 contentInfo = new ASN1 (0x30);
+ // contentType ContentType, -> ContentType ::= OBJECT IDENTIFIER
+ contentInfo.Add (ASN1Convert.FromOid (contentType));
+ // content [0] EXPLICIT ANY DEFINED BY contentType OPTIONAL
+ if ((content != null) && (content.Count > 0))
+ contentInfo.Add (content);
+ return contentInfo;
+ }
+
+ public byte[] GetBytes ()
+ {
+ return GetASN1 ().GetBytes ();
+ }
+ }
+
+ /*
+ * EncryptedData ::= SEQUENCE {
+ * version INTEGER {edVer0(0)} (edVer0),
+ * encryptedContentInfo EncryptedContentInfo
+ * }
+ */
+ public class EncryptedData {
+ private byte _version;
+ private ContentInfo _content;
+ private ContentInfo _encryptionAlgorithm;
+ private byte[] _encrypted;
+
+ public EncryptedData ()
+ {
+ _version = 0;
+ }
+
+ public EncryptedData (byte[] data)
+ : this (new ASN1 (data))
+ {
+ }
+
+ public EncryptedData (ASN1 asn1) : this ()
+ {
+ if ((asn1.Tag != 0x30) || (asn1.Count < 2))
+ throw new ArgumentException ("Invalid EncryptedData");
+
+ if (asn1 [0].Tag != 0x02)
+ throw new ArgumentException ("Invalid version");
+ _version = asn1 [0].Value [0];
+
+ ASN1 encryptedContentInfo = asn1 [1];
+ if (encryptedContentInfo.Tag != 0x30)
+ throw new ArgumentException ("missing EncryptedContentInfo");
+
+ ASN1 contentType = encryptedContentInfo [0];
+ if (contentType.Tag != 0x06)
+ throw new ArgumentException ("missing EncryptedContentInfo.ContentType");
+ _content = new ContentInfo (ASN1Convert.ToOid (contentType));
+
+ ASN1 contentEncryptionAlgorithm = encryptedContentInfo [1];
+ if (contentEncryptionAlgorithm.Tag != 0x30)
+ throw new ArgumentException ("missing EncryptedContentInfo.ContentEncryptionAlgorithmIdentifier");
+ _encryptionAlgorithm = new ContentInfo (ASN1Convert.ToOid (contentEncryptionAlgorithm [0]));
+ _encryptionAlgorithm.Content = contentEncryptionAlgorithm [1];
+
+ ASN1 encryptedContent = encryptedContentInfo [2];
+ if (encryptedContent.Tag != 0x80)
+ throw new ArgumentException ("missing EncryptedContentInfo.EncryptedContent");
+ _encrypted = encryptedContent.Value;
+ }
+
+ public ASN1 ASN1 {
+ get { return GetASN1(); }
+ }
+
+ public ContentInfo ContentInfo {
+ get { return _content; }
+ }
+
+ public ContentInfo EncryptionAlgorithm {
+ get { return _encryptionAlgorithm; }
+ }
+
+ public byte[] EncryptedContent {
+ get {
+ if (_encrypted == null)
+ return null;
+ return (byte[]) _encrypted.Clone ();
+ }
+ }
+
+ public byte Version {
+ get { return _version; }
+ set { _version = value; }
+ }
+
+ // methods
+
+ internal ASN1 GetASN1 ()
+ {
+ return null;
+ }
+
+ public byte[] GetBytes ()
+ {
+ return GetASN1 ().GetBytes ();
+ }
+ }
+
+ /*
+ * EnvelopedData ::= SEQUENCE {
+ * version Version,
+ * recipientInfos RecipientInfos,
+ * encryptedContentInfo EncryptedContentInfo
+ * }
+ *
+ * RecipientInfos ::= SET OF RecipientInfo
+ *
+ * EncryptedContentInfo ::= SEQUENCE {
+ * contentType ContentType,
+ * contentEncryptionAlgorithm ContentEncryptionAlgorithmIdentifier,
+ * encryptedContent [0] IMPLICIT EncryptedContent OPTIONAL
+ * }
+ *
+ * EncryptedContent ::= OCTET STRING
+ *
+ */
+ public class EnvelopedData {
+ private byte _version;
+ private ContentInfo _content;
+ private ContentInfo _encryptionAlgorithm;
+ private ArrayList _recipientInfos;
+ private byte[] _encrypted;
+
+ public EnvelopedData ()
+ {
+ _version = 0;
+ _content = new ContentInfo ();
+ _encryptionAlgorithm = new ContentInfo ();
+ _recipientInfos = new ArrayList ();
+ }
+
+ public EnvelopedData (byte[] data)
+ : this (new ASN1 (data))
+ {
+ }
+
+ public EnvelopedData (ASN1 asn1) : this ()
+ {
+ if ((asn1[0].Tag != 0x30) || (asn1[0].Count < 3))
+ throw new ArgumentException ("Invalid EnvelopedData");
+
+ if (asn1[0][0].Tag != 0x02)
+ throw new ArgumentException ("Invalid version");
+ _version = asn1[0][0].Value[0];
+
+ // recipientInfos
+
+ ASN1 recipientInfos = asn1 [0][1];
+ if (recipientInfos.Tag != 0x31)
+ throw new ArgumentException ("missing RecipientInfos");
+ for (int i=0; i < recipientInfos.Count; i++) {
+ ASN1 recipientInfo = recipientInfos [i];
+ _recipientInfos.Add (new RecipientInfo (recipientInfo));
+ }
+
+ ASN1 encryptedContentInfo = asn1[0][2];
+ if (encryptedContentInfo.Tag != 0x30)
+ throw new ArgumentException ("missing EncryptedContentInfo");
+
+ ASN1 contentType = encryptedContentInfo [0];
+ if (contentType.Tag != 0x06)
+ throw new ArgumentException ("missing EncryptedContentInfo.ContentType");
+ _content = new ContentInfo (ASN1Convert.ToOid (contentType));
+
+ ASN1 contentEncryptionAlgorithm = encryptedContentInfo [1];
+ if (contentEncryptionAlgorithm.Tag != 0x30)
+ throw new ArgumentException ("missing EncryptedContentInfo.ContentEncryptionAlgorithmIdentifier");
+ _encryptionAlgorithm = new ContentInfo (ASN1Convert.ToOid (contentEncryptionAlgorithm [0]));
+ _encryptionAlgorithm.Content = contentEncryptionAlgorithm [1];
+
+ ASN1 encryptedContent = encryptedContentInfo [2];
+ if (encryptedContent.Tag != 0x80)
+ throw new ArgumentException ("missing EncryptedContentInfo.EncryptedContent");
+ _encrypted = encryptedContent.Value;
+ }
+
+ public ArrayList RecipientInfos {
+ get { return _recipientInfos; }
+ }
+
+ public ASN1 ASN1 {
+ get { return GetASN1(); }
+ }
+
+ public ContentInfo ContentInfo {
+ get { return _content; }
+ }
+
+ public ContentInfo EncryptionAlgorithm {
+ get { return _encryptionAlgorithm; }
+ }
+
+ public byte[] EncryptedContent {
+ get {
+ if (_encrypted == null)
+ return null;
+ return (byte[]) _encrypted.Clone ();
+ }
+ }
+
+ public byte Version {
+ get { return _version; }
+ set { _version = value; }
+ }
+
+ internal ASN1 GetASN1 ()
+ {
+ // SignedData ::= SEQUENCE {
+ ASN1 signedData = new ASN1 (0x30);
+ // version Version -> Version ::= INTEGER
+/* byte[] ver = { _version };
+ signedData.Add (new ASN1 (0x02, ver));
+ // digestAlgorithms DigestAlgorithmIdentifiers -> DigestAlgorithmIdentifiers ::= SET OF DigestAlgorithmIdentifier
+ ASN1 digestAlgorithms = signedData.Add (new ASN1 (0x31));
+ if (hashAlgorithm != null) {
+ string hashOid = CryptoConfig.MapNameToOid (hashAlgorithm);
+ digestAlgorithms.Add (AlgorithmIdentifier (hashOid));
+ }
+
+ // contentInfo ContentInfo,
+ ASN1 ci = contentInfo.ASN1;
+ signedData.Add (ci);
+ if ((mda == null) && (hashAlgorithm != null)) {
+ // automatically add the messageDigest authenticated attribute
+ HashAlgorithm ha = HashAlgorithm.Create (hashAlgorithm);
+ byte[] idcHash = ha.ComputeHash (ci[1][0].Value);
+ ASN1 md = new ASN1 (0x30);
+ mda = Attribute (messageDigest, md.Add (new ASN1 (0x04, idcHash)));
+ signerInfo.AuthenticatedAttributes.Add (mda);
+ }
+
+ // certificates [0] IMPLICIT ExtendedCertificatesAndCertificates OPTIONAL,
+ if (certs.Count > 0) {
+ ASN1 a0 = signedData.Add (new ASN1 (0xA0));
+ foreach (X509Certificate x in certs)
+ a0.Add (new ASN1 (x.RawData));
+ }
+ // crls [1] IMPLICIT CertificateRevocationLists OPTIONAL,
+ if (crls.Count > 0) {
+ ASN1 a1 = signedData.Add (new ASN1 (0xA1));
+ foreach (byte[] crl in crls)
+ a1.Add (new ASN1 (crl));
+ }
+ // signerInfos SignerInfos -> SignerInfos ::= SET OF SignerInfo
+ ASN1 signerInfos = signedData.Add (new ASN1 (0x31));
+ if (signerInfo.Key != null)
+ signerInfos.Add (signerInfo.ASN1);*/
+ return signedData;
+ }
+
+ public byte[] GetBytes () {
+ return GetASN1 ().GetBytes ();
+ }
+ }
+
+ /* RecipientInfo ::= SEQUENCE {
+ * version Version,
+ * issuerAndSerialNumber IssuerAndSerialNumber,
+ * keyEncryptionAlgorithm KeyEncryptionAlgorithmIdentifier,
+ * encryptedKey EncryptedKey
+ * }
+ *
+ * KeyEncryptionAlgorithmIdentifier ::= AlgorithmIdentifier
+ *
+ * EncryptedKey ::= OCTET STRING
+ */
+ public class RecipientInfo {
+
+ private int _version;
+ private string _oid;
+ private byte[] _key;
+ private byte[] _ski;
+ private string _issuer;
+ private byte[] _serial;
+
+ public RecipientInfo () {}
+
+ public RecipientInfo (ASN1 data)
+ {
+ if (data.Tag != 0x30)
+ throw new ArgumentException ("Invalid RecipientInfo");
+
+ ASN1 version = data [0];
+ if (version.Tag != 0x02)
+ throw new ArgumentException ("missing Version");
+ _version = version.Value [0];
+
+ // issuerAndSerialNumber IssuerAndSerialNumber
+ ASN1 subjectIdentifierType = data [1];
+ if ((subjectIdentifierType.Tag == 0x80) && (_version == 3)) {
+ _ski = subjectIdentifierType.Value;
+ }
+ else {
+ _issuer = X501.ToString (subjectIdentifierType [0]);
+ _serial = subjectIdentifierType [1].Value;
+ }
+
+ ASN1 keyEncryptionAlgorithm = data [2];
+ _oid = ASN1Convert.ToOid (keyEncryptionAlgorithm [0]);
+
+ ASN1 encryptedKey = data [3];
+ _key = encryptedKey.Value;
+ }
+
+ public string Oid {
+ get { return _oid; }
+ }
+
+ public byte[] Key {
+ get {
+ if (_key == null)
+ return null;
+ return (byte[]) _key.Clone ();
+ }
+ }
+
+ public byte[] SubjectKeyIdentifier {
+ get {
+ if (_ski == null)
+ return null;
+ return (byte[]) _ski.Clone ();
+ }
+ }
+
+ public string Issuer {
+ get { return _issuer; }
+ }
+
+ public byte[] Serial {
+ get {
+ if (_serial == null)
+ return null;
+ return (byte[]) _serial.Clone ();
+ }
+ }
+
+ public int Version {
+ get { return _version; }
+ }
+ }
+
+ /*
+ * SignedData ::= SEQUENCE {
+ * version Version,
+ * digestAlgorithms DigestAlgorithmIdentifiers,
+ * contentInfo ContentInfo,
+ * certificates [0] IMPLICIT ExtendedCertificatesAndCertificates OPTIONAL,
+ * crls [1] IMPLICIT CertificateRevocationLists OPTIONAL,
+ * signerInfos SignerInfos
+ * }
+ */
+ public class SignedData {
+ private byte version;
+ private string hashAlgorithm;
+ private ContentInfo contentInfo;
+ private X509CertificateCollection certs;
+ private ArrayList crls;
+ private SignerInfo signerInfo;
+ private bool mda;
+ private bool signed;
+
+ public SignedData ()
+ {
+ version = 1;
+ contentInfo = new ContentInfo ();
+ certs = new X509CertificateCollection ();
+ crls = new ArrayList ();
+ signerInfo = new SignerInfo ();
+ mda = true;
+ signed = false;
+ }
+
+ public SignedData (byte[] data)
+ : this (new ASN1 (data))
+ {
+ }
+
+ public SignedData (ASN1 asn1)
+ {
+ if ((asn1[0].Tag != 0x30) || (asn1[0].Count < 4))
+ throw new ArgumentException ("Invalid SignedData");
+
+ if (asn1[0][0].Tag != 0x02)
+ throw new ArgumentException ("Invalid version");
+ version = asn1[0][0].Value[0];
+
+ contentInfo = new ContentInfo (asn1[0][2]);
+
+ int n = 3;
+ certs = new X509CertificateCollection ();
+ if (asn1[0][n].Tag == 0xA0) {
+ for (int i=0; i < asn1[0][n].Count; i++)
+ certs.Add (new X509Certificate (asn1[0][n][i].GetBytes ()));
+ n++;
+ }
+
+ crls = new ArrayList ();
+ if (asn1[0][n].Tag == 0xA1) {
+ for (int i=0; i < asn1[0][n].Count; i++)
+ crls.Add (asn1[0][n][i].GetBytes ());
+ n++;
+ }
+
+ if (asn1[0][n].Count > 0)
+ signerInfo = new SignerInfo (asn1[0][n]);
+ else
+ signerInfo = new SignerInfo ();
+
+ // Exchange hash algorithm Oid from SignerInfo
+ if (signerInfo.HashName != null) {
+ HashName = OidToName(signerInfo.HashName);
+ }
+
+ // Check if SignerInfo has authenticated attributes
+ mda = (signerInfo.AuthenticatedAttributes.Count > 0);
+ }
+
+ public ASN1 ASN1 {
+ get { return GetASN1(); }
+ }
+
+ public X509CertificateCollection Certificates {
+ get { return certs; }
+ }
+
+ public ContentInfo ContentInfo {
+ get { return contentInfo; }
+ }
+
+ public ArrayList Crls {
+ get { return crls; }
+ }
+
+ public string HashName {
+ get { return hashAlgorithm; }
+ // todo add validation
+ set {
+ hashAlgorithm = value;
+ signerInfo.HashName = value;
+ }
+ }
+
+ public SignerInfo SignerInfo {
+ get { return signerInfo; }
+ }
+
+ public byte Version {
+ get { return version; }
+ set { version = value; }
+ }
+
+ public bool UseAuthenticatedAttributes {
+ get { return mda; }
+ set { mda = value; }
+ }
+
+ public bool VerifySignature (AsymmetricAlgorithm aa)
+ {
+ if (aa == null) {
+ return false;
+ }
+
+ RSAPKCS1SignatureDeformatter r = new RSAPKCS1SignatureDeformatter (aa);
+ r.SetHashAlgorithm (hashAlgorithm);
+ HashAlgorithm ha = HashAlgorithm.Create (hashAlgorithm);
+
+ byte[] signature = signerInfo.Signature;
+ byte[] hash = null;
+
+ if (mda) {
+ ASN1 asn = new ASN1 (0x31);
+ foreach (ASN1 attr in signerInfo.AuthenticatedAttributes)
+ asn.Add (attr);
+
+ hash = ha.ComputeHash (asn.GetBytes ());
+ } else {
+ hash = ha.ComputeHash (contentInfo.Content[0].Value);
+ }
+
+ if (hash != null && signature != null) {
+ return r.VerifySignature (hash, signature);
+ }
+ return false;
+ }
+
+ internal string OidToName (string oid)
+ {
+ switch (oid) {
+ case "1.3.14.3.2.26" :
+ return "SHA1";
+ case "1.2.840.113549.2.2" :
+ return "MD2";
+ case "1.2.840.113549.2.5" :
+ return "MD5";
+ case "2.16.840.1.101.3.4.1" :
+ return "SHA256";
+ case "2.16.840.1.101.3.4.2" :
+ return "SHA384";
+ case "2.16.840.1.101.3.4.3" :
+ return "SHA512";
+ default :
+ break;
+ }
+ // Unknown Oid
+ return oid;
+ }
+
+ internal ASN1 GetASN1 ()
+ {
+ // SignedData ::= SEQUENCE {
+ ASN1 signedData = new ASN1 (0x30);
+ // version Version -> Version ::= INTEGER
+ byte[] ver = { version };
+ signedData.Add (new ASN1 (0x02, ver));
+ // digestAlgorithms DigestAlgorithmIdentifiers -> DigestAlgorithmIdentifiers ::= SET OF DigestAlgorithmIdentifier
+ ASN1 digestAlgorithms = signedData.Add (new ASN1 (0x31));
+ if (hashAlgorithm != null) {
+ string hashOid = CryptoConfig.MapNameToOID (hashAlgorithm);
+ digestAlgorithms.Add (AlgorithmIdentifier (hashOid));
+ }
+
+ // contentInfo ContentInfo,
+ ASN1 ci = contentInfo.ASN1;
+ signedData.Add (ci);
+ if (!signed && (hashAlgorithm != null)) {
+ if (mda) {
+ // Use authenticated attributes for signature
+
+ // Automatically add the contentType authenticated attribute
+ ASN1 ctattr = Attribute (Oid.contentType, ci[0]);
+ signerInfo.AuthenticatedAttributes.Add (ctattr);
+
+ // Automatically add the messageDigest authenticated attribute
+ HashAlgorithm ha = HashAlgorithm.Create (hashAlgorithm);
+ byte[] idcHash = ha.ComputeHash (ci[1][0].Value);
+ ASN1 md = new ASN1 (0x30);
+ ASN1 mdattr = Attribute (Oid.messageDigest, md.Add (new ASN1 (0x04, idcHash)));
+ signerInfo.AuthenticatedAttributes.Add (mdattr);
+ } else {
+ // Don't use authenticated attributes for signature -- signature is content
+ RSAPKCS1SignatureFormatter r = new RSAPKCS1SignatureFormatter (signerInfo.Key);
+ r.SetHashAlgorithm (hashAlgorithm);
+ HashAlgorithm ha = HashAlgorithm.Create (hashAlgorithm);
+ byte[] sig = ha.ComputeHash (ci[1][0].Value);
+ signerInfo.Signature = r.CreateSignature (sig);
+ }
+ signed = true;
+ }
+
+ // certificates [0] IMPLICIT ExtendedCertificatesAndCertificates OPTIONAL,
+ if (certs.Count > 0) {
+ ASN1 a0 = signedData.Add (new ASN1 (0xA0));
+ foreach (X509Certificate x in certs)
+ a0.Add (new ASN1 (x.RawData));
+ }
+ // crls [1] IMPLICIT CertificateRevocationLists OPTIONAL,
+ if (crls.Count > 0) {
+ ASN1 a1 = signedData.Add (new ASN1 (0xA1));
+ foreach (byte[] crl in crls)
+ a1.Add (new ASN1 (crl));
+ }
+ // signerInfos SignerInfos -> SignerInfos ::= SET OF SignerInfo
+ ASN1 signerInfos = signedData.Add (new ASN1 (0x31));
+ if (signerInfo.Key != null)
+ signerInfos.Add (signerInfo.ASN1);
+ return signedData;
+ }
+
+ public byte[] GetBytes ()
+ {
+ return GetASN1 ().GetBytes ();
+ }
+ }
+
+ /*
+ * SignerInfo ::= SEQUENCE {
+ * version Version,
+ * issuerAndSerialNumber IssuerAndSerialNumber,
+ * digestAlgorithm DigestAlgorithmIdentifier,
+ * authenticatedAttributes [0] IMPLICIT Attributes OPTIONAL,
+ * digestEncryptionAlgorithm DigestEncryptionAlgorithmIdentifier,
+ * encryptedDigest EncryptedDigest,
+ * unauthenticatedAttributes [1] IMPLICIT Attributes OPTIONAL
+ * }
+ *
+ * For version == 3 issuerAndSerialNumber may be replaced by ...
+ */
+ public class SignerInfo {
+
+ private byte version;
+ private X509Certificate x509;
+ private string hashAlgorithm;
+ private AsymmetricAlgorithm key;
+ private ArrayList authenticatedAttributes;
+ private ArrayList unauthenticatedAttributes;
+ private byte[] signature;
+ private string issuer;
+ private byte[] serial;
+ private byte[] ski;
+
+ public SignerInfo ()
+ {
+ version = 1;
+ authenticatedAttributes = new ArrayList ();
+ unauthenticatedAttributes = new ArrayList ();
+ }
+
+ public SignerInfo (byte[] data)
+ : this (new ASN1 (data)) {}
+
+ // TODO: INCOMPLETE
+ public SignerInfo (ASN1 asn1) : this ()
+ {
+ if ((asn1[0].Tag != 0x30) || (asn1[0].Count < 5))
+ throw new ArgumentException ("Invalid SignedData");
+
+ // version Version
+ if (asn1[0][0].Tag != 0x02)
+ throw new ArgumentException ("Invalid version");
+ version = asn1[0][0].Value[0];
+
+ // issuerAndSerialNumber IssuerAndSerialNumber
+ ASN1 subjectIdentifierType = asn1 [0][1];
+ if ((subjectIdentifierType.Tag == 0x80) && (version == 3)) {
+ ski = subjectIdentifierType.Value;
+ }
+ else {
+ issuer = X501.ToString (subjectIdentifierType [0]);
+ serial = subjectIdentifierType [1].Value;
+ }
+
+ // digestAlgorithm DigestAlgorithmIdentifier
+ ASN1 digestAlgorithm = asn1 [0][2];
+ hashAlgorithm = ASN1Convert.ToOid (digestAlgorithm [0]);
+
+ // authenticatedAttributes [0] IMPLICIT Attributes OPTIONAL
+ int n = 3;
+ ASN1 authAttributes = asn1 [0][n];
+ if (authAttributes.Tag == 0xA0) {
+ n++;
+ for (int i=0; i < authAttributes.Count; i++)
+ authenticatedAttributes.Add (authAttributes [i]);
+ }
+
+ // digestEncryptionAlgorithm DigestEncryptionAlgorithmIdentifier
+ n++;
+ // ASN1 digestEncryptionAlgorithm = asn1 [0][n++];
+ // string digestEncryptionAlgorithmOid = ASN1Convert.ToOid (digestEncryptionAlgorithm [0]);
+
+ // encryptedDigest EncryptedDigest
+ ASN1 encryptedDigest = asn1 [0][n++];
+ if (encryptedDigest.Tag == 0x04)
+ signature = encryptedDigest.Value;
+
+ // unauthenticatedAttributes [1] IMPLICIT Attributes OPTIONAL
+ ASN1 unauthAttributes = asn1 [0][n];
+ if ((unauthAttributes != null) && (unauthAttributes.Tag == 0xA1)) {
+ for (int i=0; i < unauthAttributes.Count; i++)
+ unauthenticatedAttributes.Add (unauthAttributes [i]);
+ }
+ }
+
+ public string IssuerName {
+ get { return issuer; }
+ }
+
+ public byte[] SerialNumber {
+ get {
+ if (serial == null)
+ return null;
+ return (byte[]) serial.Clone ();
+ }
+ }
+
+ public byte[] SubjectKeyIdentifier {
+ get {
+ if (ski == null)
+ return null;
+ return (byte[]) ski.Clone ();
+ }
+ }
+
+ public ASN1 ASN1 {
+ get { return GetASN1(); }
+ }
+
+ public ArrayList AuthenticatedAttributes {
+ get { return authenticatedAttributes; }
+ }
+
+ public X509Certificate Certificate {
+ get { return x509; }
+ set { x509 = value; }
+ }
+
+ public string HashName {
+ get { return hashAlgorithm; }
+ set { hashAlgorithm = value; }
+ }
+
+ public AsymmetricAlgorithm Key {
+ get { return key; }
+ set { key = value; }
+ }
+
+ public byte[] Signature {
+ get {
+ if (signature == null)
+ return null;
+ return (byte[]) signature.Clone ();
+ }
+
+ set {
+ if (value != null) {
+ signature = (byte[]) value.Clone ();
+ }
+ }
+ }
+
+ public ArrayList UnauthenticatedAttributes {
+ get { return unauthenticatedAttributes; }
+ }
+
+ public byte Version {
+ get { return version; }
+ set { version = value; }
+ }
+
+ internal ASN1 GetASN1 ()
+ {
+ if ((key == null) || (hashAlgorithm == null))
+ return null;
+ byte[] ver = { version };
+ ASN1 signerInfo = new ASN1 (0x30);
+ // version Version -> Version ::= INTEGER
+ signerInfo.Add (new ASN1 (0x02, ver));
+ // issuerAndSerialNumber IssuerAndSerialNumber,
+ signerInfo.Add (PKCS7.IssuerAndSerialNumber (x509));
+ // digestAlgorithm DigestAlgorithmIdentifier,
+ string hashOid = CryptoConfig.MapNameToOID (hashAlgorithm);
+ signerInfo.Add (AlgorithmIdentifier (hashOid));
+ // authenticatedAttributes [0] IMPLICIT Attributes OPTIONAL,
+ ASN1 aa = null;
+ if (authenticatedAttributes.Count > 0) {
+ aa = signerInfo.Add (new ASN1 (0xA0));
+ authenticatedAttributes.Sort(new SortedSet ());
+ foreach (ASN1 attr in authenticatedAttributes)
+ aa.Add (attr);
+ }
+ // digestEncryptionAlgorithm DigestEncryptionAlgorithmIdentifier,
+ if (key is RSA) {
+ signerInfo.Add (AlgorithmIdentifier (PKCS7.Oid.rsaEncryption));
+
+ if (aa != null) {
+ // Calculate the signature here; otherwise it must be set from SignedData
+ RSAPKCS1SignatureFormatter r = new RSAPKCS1SignatureFormatter (key);
+ r.SetHashAlgorithm (hashAlgorithm);
+ byte[] tbs = aa.GetBytes ();
+ tbs [0] = 0x31; // not 0xA0 for signature
+ HashAlgorithm ha = HashAlgorithm.Create (hashAlgorithm);
+ byte[] tbsHash = ha.ComputeHash (tbs);
+ signature = r.CreateSignature (tbsHash);
+ }
+ }
+ else if (key is DSA) {
+ throw new NotImplementedException ("not yet");
+ }
+ else
+ throw new CryptographicException ("Unknown assymetric algorithm");
+ // encryptedDigest EncryptedDigest,
+ signerInfo.Add (new ASN1 (0x04, signature));
+ // unauthenticatedAttributes [1] IMPLICIT Attributes OPTIONAL
+ if (unauthenticatedAttributes.Count > 0) {
+ ASN1 ua = signerInfo.Add (new ASN1 (0xA1));
+ unauthenticatedAttributes.Sort(new SortedSet ());
+ foreach (ASN1 attr in unauthenticatedAttributes)
+ ua.Add (attr);
+ }
+ return signerInfo;
+ }
+
+ public byte[] GetBytes ()
+ {
+ return GetASN1 ().GetBytes ();
+ }
+ }
+
+ internal class SortedSet : IComparer {
+
+ public int Compare (object x, object y)
+ {
+ if (x == null)
+ return (y == null) ? 0 : -1;
+ else if (y == null)
+ return 1;
+
+ ASN1 xx = x as ASN1;
+ ASN1 yy = y as ASN1;
+
+ if ((xx == null) || (yy == null)) {
+ throw new ArgumentException (("Invalid objects."));
+ }
+
+ byte[] xb = xx.GetBytes ();
+ byte[] yb = yy.GetBytes ();
+
+ for (int i = 0; i < xb.Length; i++) {
+ if (i == yb.Length)
+ break;
+
+ if (xb[i] == yb[i])
+ continue;
+
+ return (xb[i] < yb[i]) ? -1 : 1;
+ }
+
+ // The arrays are equal up to the shortest of them.
+ if (xb.Length > yb.Length)
+ return 1;
+ else if (xb.Length < yb.Length)
+ return -1;
+
+ return 0;
+ }
+ }
+ }
+}
diff --git a/MediaBrowser.Server.Mono/Security/PKCS8.cs b/MediaBrowser.Server.Mono/Security/PKCS8.cs
new file mode 100644
index 000000000..352e4649b
--- /dev/null
+++ b/MediaBrowser.Server.Mono/Security/PKCS8.cs
@@ -0,0 +1,505 @@
+//
+// PKCS8.cs: PKCS #8 - Private-Key Information Syntax Standard
+// ftp://ftp.rsasecurity.com/pub/pkcs/doc/pkcs-8.doc
+//
+// Author:
+// Sebastien Pouliot <sebastien@xamarin.com>
+//
+// (C) 2003 Motus Technologies Inc. (http://www.motus.com)
+// Copyright (C) 2004-2006 Novell Inc. (http://www.novell.com)
+// Copyright 2013 Xamarin Inc. (http://www.xamarin.com)
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+using System;
+using System.Collections;
+using System.Security.Cryptography;
+
+using Mono.Security.X509;
+
+namespace Mono.Security.Cryptography {
+
+#if !INSIDE_CORLIB
+ public
+#endif
+ sealed class PKCS8 {
+
+ public enum KeyInfo {
+ PrivateKey,
+ EncryptedPrivateKey,
+ Unknown
+ }
+
+ private PKCS8 ()
+ {
+ }
+
+ static public KeyInfo GetType (byte[] data)
+ {
+ if (data == null)
+ throw new ArgumentNullException ("data");
+
+ KeyInfo ki = KeyInfo.Unknown;
+ try {
+ ASN1 top = new ASN1 (data);
+ if ((top.Tag == 0x30) && (top.Count > 0)) {
+ ASN1 firstLevel = top [0];
+ switch (firstLevel.Tag) {
+ case 0x02:
+ ki = KeyInfo.PrivateKey;
+ break;
+ case 0x30:
+ ki = KeyInfo.EncryptedPrivateKey;
+ break;
+ }
+ }
+ }
+ catch {
+ throw new CryptographicException ("invalid ASN.1 data");
+ }
+ return ki;
+ }
+
+ /*
+ * PrivateKeyInfo ::= SEQUENCE {
+ * version Version,
+ * privateKeyAlgorithm PrivateKeyAlgorithmIdentifier,
+ * privateKey PrivateKey,
+ * attributes [0] IMPLICIT Attributes OPTIONAL
+ * }
+ *
+ * Version ::= INTEGER
+ *
+ * PrivateKeyAlgorithmIdentifier ::= AlgorithmIdentifier
+ *
+ * PrivateKey ::= OCTET STRING
+ *
+ * Attributes ::= SET OF Attribute
+ */
+ public class PrivateKeyInfo {
+
+ private int _version;
+ private string _algorithm;
+ private byte[] _key;
+ private ArrayList _list;
+
+ public PrivateKeyInfo ()
+ {
+ _version = 0;
+ _list = new ArrayList ();
+ }
+
+ public PrivateKeyInfo (byte[] data) : this ()
+ {
+ Decode (data);
+ }
+
+ // properties
+
+ public string Algorithm {
+ get { return _algorithm; }
+ set { _algorithm = value; }
+ }
+
+ public ArrayList Attributes {
+ get { return _list; }
+ }
+
+ public byte[] PrivateKey {
+ get {
+ if (_key == null)
+ return null;
+ return (byte[]) _key.Clone ();
+ }
+ set {
+ if (value == null)
+ throw new ArgumentNullException ("PrivateKey");
+ _key = (byte[]) value.Clone ();
+ }
+ }
+
+ public int Version {
+ get { return _version; }
+ set {
+ if (value < 0)
+ throw new ArgumentOutOfRangeException ("negative version");
+ _version = value;
+ }
+ }
+
+ // methods
+
+ private void Decode (byte[] data)
+ {
+ ASN1 privateKeyInfo = new ASN1 (data);
+ if (privateKeyInfo.Tag != 0x30)
+ throw new CryptographicException ("invalid PrivateKeyInfo");
+
+ ASN1 version = privateKeyInfo [0];
+ if (version.Tag != 0x02)
+ throw new CryptographicException ("invalid version");
+ _version = version.Value [0];
+
+ ASN1 privateKeyAlgorithm = privateKeyInfo [1];
+ if (privateKeyAlgorithm.Tag != 0x30)
+ throw new CryptographicException ("invalid algorithm");
+
+ ASN1 algorithm = privateKeyAlgorithm [0];
+ if (algorithm.Tag != 0x06)
+ throw new CryptographicException ("missing algorithm OID");
+ _algorithm = ASN1Convert.ToOid (algorithm);
+
+ ASN1 privateKey = privateKeyInfo [2];
+ _key = privateKey.Value;
+
+ // attributes [0] IMPLICIT Attributes OPTIONAL
+ if (privateKeyInfo.Count > 3) {
+ ASN1 attributes = privateKeyInfo [3];
+ for (int i=0; i < attributes.Count; i++) {
+ _list.Add (attributes [i]);
+ }
+ }
+ }
+
+ public byte[] GetBytes ()
+ {
+ ASN1 privateKeyAlgorithm = new ASN1 (0x30);
+ privateKeyAlgorithm.Add (ASN1Convert.FromOid (_algorithm));
+ privateKeyAlgorithm.Add (new ASN1 (0x05)); // ASN.1 NULL
+
+ ASN1 pki = new ASN1 (0x30);
+ pki.Add (new ASN1 (0x02, new byte [1] { (byte) _version }));
+ pki.Add (privateKeyAlgorithm);
+ pki.Add (new ASN1 (0x04, _key));
+
+ if (_list.Count > 0) {
+ ASN1 attributes = new ASN1 (0xA0);
+ foreach (ASN1 attribute in _list) {
+ attributes.Add (attribute);
+ }
+ pki.Add (attributes);
+ }
+
+ return pki.GetBytes ();
+ }
+
+ // static methods
+
+ static private byte[] RemoveLeadingZero (byte[] bigInt)
+ {
+ int start = 0;
+ int length = bigInt.Length;
+ if (bigInt [0] == 0x00) {
+ start = 1;
+ length--;
+ }
+ byte[] bi = new byte [length];
+ Buffer.BlockCopy (bigInt, start, bi, 0, length);
+ return bi;
+ }
+
+ static private byte[] Normalize (byte[] bigInt, int length)
+ {
+ if (bigInt.Length == length)
+ return bigInt;
+ else if (bigInt.Length > length)
+ return RemoveLeadingZero (bigInt);
+ else {
+ // pad with 0
+ byte[] bi = new byte [length];
+ Buffer.BlockCopy (bigInt, 0, bi, (length - bigInt.Length), bigInt.Length);
+ return bi;
+ }
+ }
+
+ /*
+ * RSAPrivateKey ::= SEQUENCE {
+ * version Version,
+ * modulus INTEGER, -- n
+ * publicExponent INTEGER, -- e
+ * privateExponent INTEGER, -- d
+ * prime1 INTEGER, -- p
+ * prime2 INTEGER, -- q
+ * exponent1 INTEGER, -- d mod (p-1)
+ * exponent2 INTEGER, -- d mod (q-1)
+ * coefficient INTEGER, -- (inverse of q) mod p
+ * otherPrimeInfos OtherPrimeInfos OPTIONAL
+ * }
+ */
+ static public RSA DecodeRSA (byte[] keypair)
+ {
+ ASN1 privateKey = new ASN1 (keypair);
+ if (privateKey.Tag != 0x30)
+ throw new CryptographicException ("invalid private key format");
+
+ ASN1 version = privateKey [0];
+ if (version.Tag != 0x02)
+ throw new CryptographicException ("missing version");
+
+ if (privateKey.Count < 9)
+ throw new CryptographicException ("not enough key parameters");
+
+ RSAParameters param = new RSAParameters ();
+ // note: MUST remove leading 0 - else MS wont import the key
+ param.Modulus = RemoveLeadingZero (privateKey [1].Value);
+ int keysize = param.Modulus.Length;
+ int keysize2 = (keysize >> 1); // half-size
+ // size must be normalized - else MS wont import the key
+ param.D = Normalize (privateKey [3].Value, keysize);
+ param.DP = Normalize (privateKey [6].Value, keysize2);
+ param.DQ = Normalize (privateKey [7].Value, keysize2);
+ param.Exponent = RemoveLeadingZero (privateKey [2].Value);
+ param.InverseQ = Normalize (privateKey [8].Value, keysize2);
+ param.P = Normalize (privateKey [4].Value, keysize2);
+ param.Q = Normalize (privateKey [5].Value, keysize2);
+
+ RSA rsa = null;
+ try {
+ rsa = RSA.Create ();
+ rsa.ImportParameters (param);
+ }
+ catch (CryptographicException) {
+#if MONOTOUCH
+ // there's no machine-wide store available for iOS so we can drop the dependency on
+ // CspParameters (which drops other things, like XML key persistance, unless used elsewhere)
+ throw;
+#else
+ // this may cause problem when this code is run under
+ // the SYSTEM identity on Windows (e.g. ASP.NET). See
+ // http://bugzilla.ximian.com/show_bug.cgi?id=77559
+ CspParameters csp = new CspParameters ();
+ csp.Flags = CspProviderFlags.UseMachineKeyStore;
+ rsa = new RSACryptoServiceProvider (csp);
+ rsa.ImportParameters (param);
+#endif
+ }
+ return rsa;
+ }
+
+ /*
+ * RSAPrivateKey ::= SEQUENCE {
+ * version Version,
+ * modulus INTEGER, -- n
+ * publicExponent INTEGER, -- e
+ * privateExponent INTEGER, -- d
+ * prime1 INTEGER, -- p
+ * prime2 INTEGER, -- q
+ * exponent1 INTEGER, -- d mod (p-1)
+ * exponent2 INTEGER, -- d mod (q-1)
+ * coefficient INTEGER, -- (inverse of q) mod p
+ * otherPrimeInfos OtherPrimeInfos OPTIONAL
+ * }
+ */
+ static public byte[] Encode (RSA rsa)
+ {
+ RSAParameters param = rsa.ExportParameters (true);
+
+ ASN1 rsaPrivateKey = new ASN1 (0x30);
+ rsaPrivateKey.Add (new ASN1 (0x02, new byte [1] { 0x00 }));
+ rsaPrivateKey.Add (ASN1Convert.FromUnsignedBigInteger (param.Modulus));
+ rsaPrivateKey.Add (ASN1Convert.FromUnsignedBigInteger (param.Exponent));
+ rsaPrivateKey.Add (ASN1Convert.FromUnsignedBigInteger (param.D));
+ rsaPrivateKey.Add (ASN1Convert.FromUnsignedBigInteger (param.P));
+ rsaPrivateKey.Add (ASN1Convert.FromUnsignedBigInteger (param.Q));
+ rsaPrivateKey.Add (ASN1Convert.FromUnsignedBigInteger (param.DP));
+ rsaPrivateKey.Add (ASN1Convert.FromUnsignedBigInteger (param.DQ));
+ rsaPrivateKey.Add (ASN1Convert.FromUnsignedBigInteger (param.InverseQ));
+
+ return rsaPrivateKey.GetBytes ();
+ }
+
+ // DSA only encode it's X private key inside an ASN.1 INTEGER (Hint: Tag == 0x02)
+ // which isn't enough for rebuilding the keypair. The other parameters
+ // can be found (98% of the time) in the X.509 certificate associated
+ // with the private key or (2% of the time) the parameters are in it's
+ // issuer X.509 certificate (not supported in the .NET framework).
+ static public DSA DecodeDSA (byte[] privateKey, DSAParameters dsaParameters)
+ {
+ ASN1 pvk = new ASN1 (privateKey);
+ if (pvk.Tag != 0x02)
+ throw new CryptographicException ("invalid private key format");
+
+ // X is ALWAYS 20 bytes (no matter if the key length is 512 or 1024 bits)
+ dsaParameters.X = Normalize (pvk.Value, 20);
+ DSA dsa = DSA.Create ();
+ dsa.ImportParameters (dsaParameters);
+ return dsa;
+ }
+
+ static public byte[] Encode (DSA dsa)
+ {
+ DSAParameters param = dsa.ExportParameters (true);
+ return ASN1Convert.FromUnsignedBigInteger (param.X).GetBytes ();
+ }
+
+ static public byte[] Encode (AsymmetricAlgorithm aa)
+ {
+ if (aa is RSA)
+ return Encode ((RSA)aa);
+ else if (aa is DSA)
+ return Encode ((DSA)aa);
+ else
+ throw new CryptographicException ("Unknown asymmetric algorithm {0}", aa.ToString ());
+ }
+ }
+
+ /*
+ * EncryptedPrivateKeyInfo ::= SEQUENCE {
+ * encryptionAlgorithm EncryptionAlgorithmIdentifier,
+ * encryptedData EncryptedData
+ * }
+ *
+ * EncryptionAlgorithmIdentifier ::= AlgorithmIdentifier
+ *
+ * EncryptedData ::= OCTET STRING
+ *
+ * --
+ * AlgorithmIdentifier ::= SEQUENCE {
+ * algorithm OBJECT IDENTIFIER,
+ * parameters ANY DEFINED BY algorithm OPTIONAL
+ * }
+ *
+ * -- from PKCS#5
+ * PBEParameter ::= SEQUENCE {
+ * salt OCTET STRING SIZE(8),
+ * iterationCount INTEGER
+ * }
+ */
+ public class EncryptedPrivateKeyInfo {
+
+ private string _algorithm;
+ private byte[] _salt;
+ private int _iterations;
+ private byte[] _data;
+
+ public EncryptedPrivateKeyInfo () {}
+
+ public EncryptedPrivateKeyInfo (byte[] data) : this ()
+ {
+ Decode (data);
+ }
+
+ // properties
+
+ public string Algorithm {
+ get { return _algorithm; }
+ set { _algorithm = value; }
+ }
+
+ public byte[] EncryptedData {
+ get { return (_data == null) ? null : (byte[]) _data.Clone (); }
+ set { _data = (value == null) ? null : (byte[]) value.Clone (); }
+ }
+
+ public byte[] Salt {
+ get {
+ if (_salt == null) {
+ RandomNumberGenerator rng = RandomNumberGenerator.Create ();
+ _salt = new byte [8];
+ rng.GetBytes (_salt);
+ }
+ return (byte[]) _salt.Clone ();
+ }
+ set { _salt = (byte[]) value.Clone (); }
+ }
+
+ public int IterationCount {
+ get { return _iterations; }
+ set {
+ if (value < 0)
+ throw new ArgumentOutOfRangeException ("IterationCount", "Negative");
+ _iterations = value;
+ }
+ }
+
+ // methods
+
+ private void Decode (byte[] data)
+ {
+ ASN1 encryptedPrivateKeyInfo = new ASN1 (data);
+ if (encryptedPrivateKeyInfo.Tag != 0x30)
+ throw new CryptographicException ("invalid EncryptedPrivateKeyInfo");
+
+ ASN1 encryptionAlgorithm = encryptedPrivateKeyInfo [0];
+ if (encryptionAlgorithm.Tag != 0x30)
+ throw new CryptographicException ("invalid encryptionAlgorithm");
+ ASN1 algorithm = encryptionAlgorithm [0];
+ if (algorithm.Tag != 0x06)
+ throw new CryptographicException ("invalid algorithm");
+ _algorithm = ASN1Convert.ToOid (algorithm);
+ // parameters ANY DEFINED BY algorithm OPTIONAL
+ if (encryptionAlgorithm.Count > 1) {
+ ASN1 parameters = encryptionAlgorithm [1];
+ if (parameters.Tag != 0x30)
+ throw new CryptographicException ("invalid parameters");
+
+ ASN1 salt = parameters [0];
+ if (salt.Tag != 0x04)
+ throw new CryptographicException ("invalid salt");
+ _salt = salt.Value;
+
+ ASN1 iterationCount = parameters [1];
+ if (iterationCount.Tag != 0x02)
+ throw new CryptographicException ("invalid iterationCount");
+ _iterations = ASN1Convert.ToInt32 (iterationCount);
+ }
+
+ ASN1 encryptedData = encryptedPrivateKeyInfo [1];
+ if (encryptedData.Tag != 0x04)
+ throw new CryptographicException ("invalid EncryptedData");
+ _data = encryptedData.Value;
+ }
+
+ // Note: PKCS#8 doesn't define how to generate the key required for encryption
+ // so you're on your own. Just don't try to copy the big guys too much ;)
+ // Netscape: http://www.cs.auckland.ac.nz/~pgut001/pubs/netscape.txt
+ // Microsoft: http://www.cs.auckland.ac.nz/~pgut001/pubs/breakms.txt
+ public byte[] GetBytes ()
+ {
+ if (_algorithm == null)
+ throw new CryptographicException ("No algorithm OID specified");
+
+ ASN1 encryptionAlgorithm = new ASN1 (0x30);
+ encryptionAlgorithm.Add (ASN1Convert.FromOid (_algorithm));
+
+ // parameters ANY DEFINED BY algorithm OPTIONAL
+ if ((_iterations > 0) || (_salt != null)) {
+ ASN1 salt = new ASN1 (0x04, _salt);
+ ASN1 iterations = ASN1Convert.FromInt32 (_iterations);
+
+ ASN1 parameters = new ASN1 (0x30);
+ parameters.Add (salt);
+ parameters.Add (iterations);
+ encryptionAlgorithm.Add (parameters);
+ }
+
+ // encapsulates EncryptedData into an OCTET STRING
+ ASN1 encryptedData = new ASN1 (0x04, _data);
+
+ ASN1 encryptedPrivateKeyInfo = new ASN1 (0x30);
+ encryptedPrivateKeyInfo.Add (encryptionAlgorithm);
+ encryptedPrivateKeyInfo.Add (encryptedData);
+
+ return encryptedPrivateKeyInfo.GetBytes ();
+ }
+ }
+ }
+}
diff --git a/MediaBrowser.Server.Mono/Security/X501Name.cs b/MediaBrowser.Server.Mono/Security/X501Name.cs
new file mode 100644
index 000000000..54279c1d2
--- /dev/null
+++ b/MediaBrowser.Server.Mono/Security/X501Name.cs
@@ -0,0 +1,400 @@
+//
+// X501Name.cs: X.501 Distinguished Names stuff
+//
+// Author:
+// Sebastien Pouliot <sebastien@ximian.com>
+//
+// (C) 2002, 2003 Motus Technologies Inc. (http://www.motus.com)
+// Copyright (C) 2004-2006 Novell, Inc (http://www.novell.com)
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+using System;
+using System.Globalization;
+using System.Text;
+
+using Mono.Security;
+using Mono.Security.Cryptography;
+
+namespace Mono.Security.X509 {
+
+ // References:
+ // 1. Information technology - Open Systems Interconnection - The Directory: Models
+ // http://www.itu.int/rec/recommendation.asp?type=items&lang=e&parent=T-REC-X.501-200102-I
+ // 2. RFC2253: Lightweight Directory Access Protocol (v3): UTF-8 String Representation of Distinguished Names
+ // http://www.ietf.org/rfc/rfc2253.txt
+
+ /*
+ * Name ::= CHOICE { RDNSequence }
+ *
+ * RDNSequence ::= SEQUENCE OF RelativeDistinguishedName
+ *
+ * RelativeDistinguishedName ::= SET OF AttributeTypeAndValue
+ */
+#if INSIDE_CORLIB
+ internal
+#else
+ public
+#endif
+ sealed class X501 {
+
+ static byte[] countryName = { 0x55, 0x04, 0x06 };
+ static byte[] organizationName = { 0x55, 0x04, 0x0A };
+ static byte[] organizationalUnitName = { 0x55, 0x04, 0x0B };
+ static byte[] commonName = { 0x55, 0x04, 0x03 };
+ static byte[] localityName = { 0x55, 0x04, 0x07 };
+ static byte[] stateOrProvinceName = { 0x55, 0x04, 0x08 };
+ static byte[] streetAddress = { 0x55, 0x04, 0x09 };
+ //static byte[] serialNumber = { 0x55, 0x04, 0x05 };
+ static byte[] domainComponent = { 0x09, 0x92, 0x26, 0x89, 0x93, 0xF2, 0x2C, 0x64, 0x01, 0x19 };
+ static byte[] userid = { 0x09, 0x92, 0x26, 0x89, 0x93, 0xF2, 0x2C, 0x64, 0x01, 0x01 };
+ static byte[] email = { 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x09, 0x01 };
+ static byte[] dnQualifier = { 0x55, 0x04, 0x2E };
+ static byte[] title = { 0x55, 0x04, 0x0C };
+ static byte[] surname = { 0x55, 0x04, 0x04 };
+ static byte[] givenName = { 0x55, 0x04, 0x2A };
+ static byte[] initial = { 0x55, 0x04, 0x2B };
+
+ private X501 ()
+ {
+ }
+
+ static public string ToString (ASN1 seq)
+ {
+ StringBuilder sb = new StringBuilder ();
+ for (int i = 0; i < seq.Count; i++) {
+ ASN1 entry = seq [i];
+ AppendEntry (sb, entry, true);
+
+ // separator (not on last iteration)
+ if (i < seq.Count - 1)
+ sb.Append (", ");
+ }
+ return sb.ToString ();
+ }
+
+ static public string ToString (ASN1 seq, bool reversed, string separator, bool quotes)
+ {
+ StringBuilder sb = new StringBuilder ();
+
+ if (reversed) {
+ for (int i = seq.Count - 1; i >= 0; i--) {
+ ASN1 entry = seq [i];
+ AppendEntry (sb, entry, quotes);
+
+ // separator (not on last iteration)
+ if (i > 0)
+ sb.Append (separator);
+ }
+ } else {
+ for (int i = 0; i < seq.Count; i++) {
+ ASN1 entry = seq [i];
+ AppendEntry (sb, entry, quotes);
+
+ // separator (not on last iteration)
+ if (i < seq.Count - 1)
+ sb.Append (separator);
+ }
+ }
+ return sb.ToString ();
+ }
+
+ static private void AppendEntry (StringBuilder sb, ASN1 entry, bool quotes)
+ {
+ // multiple entries are valid
+ for (int k = 0; k < entry.Count; k++) {
+ ASN1 pair = entry [k];
+ ASN1 s = pair [1];
+ if (s == null)
+ continue;
+
+ ASN1 poid = pair [0];
+ if (poid == null)
+ continue;
+
+ if (poid.CompareValue (countryName))
+ sb.Append ("C=");
+ else if (poid.CompareValue (organizationName))
+ sb.Append ("O=");
+ else if (poid.CompareValue (organizationalUnitName))
+ sb.Append ("OU=");
+ else if (poid.CompareValue (commonName))
+ sb.Append ("CN=");
+ else if (poid.CompareValue (localityName))
+ sb.Append ("L=");
+ else if (poid.CompareValue (stateOrProvinceName))
+ sb.Append ("S="); // NOTE: RFC2253 uses ST=
+ else if (poid.CompareValue (streetAddress))
+ sb.Append ("STREET=");
+ else if (poid.CompareValue (domainComponent))
+ sb.Append ("DC=");
+ else if (poid.CompareValue (userid))
+ sb.Append ("UID=");
+ else if (poid.CompareValue (email))
+ sb.Append ("E="); // NOTE: Not part of RFC2253
+ else if (poid.CompareValue (dnQualifier))
+ sb.Append ("dnQualifier=");
+ else if (poid.CompareValue (title))
+ sb.Append ("T=");
+ else if (poid.CompareValue (surname))
+ sb.Append ("SN=");
+ else if (poid.CompareValue (givenName))
+ sb.Append ("G=");
+ else if (poid.CompareValue (initial))
+ sb.Append ("I=");
+ else {
+ // unknown OID
+ sb.Append ("OID."); // NOTE: Not present as RFC2253
+ sb.Append (ASN1Convert.ToOid (poid));
+ sb.Append ("=");
+ }
+
+ string sValue = null;
+ // 16bits or 8bits string ? TODO not complete (+special chars!)
+ if (s.Tag == 0x1E) {
+ // BMPSTRING
+ StringBuilder sb2 = new StringBuilder ();
+ for (int j = 1; j < s.Value.Length; j += 2)
+ sb2.Append ((char)s.Value[j]);
+ sValue = sb2.ToString ();
+ } else {
+ if (s.Tag == 0x14)
+ sValue = Encoding.UTF7.GetString (s.Value);
+ else
+ sValue = Encoding.UTF8.GetString (s.Value);
+ // in some cases we must quote (") the value
+ // Note: this doesn't seems to conform to RFC2253
+ char[] specials = { ',', '+', '"', '\\', '<', '>', ';' };
+ if (quotes) {
+ if ((sValue.IndexOfAny (specials, 0, sValue.Length) > 0) ||
+ sValue.StartsWith (" ") || (sValue.EndsWith (" ")))
+ sValue = "\"" + sValue + "\"";
+ }
+ }
+
+ sb.Append (sValue);
+
+ // separator (not on last iteration)
+ if (k < entry.Count - 1)
+ sb.Append (", ");
+ }
+ }
+
+ static private X520.AttributeTypeAndValue GetAttributeFromOid (string attributeType)
+ {
+ string s = attributeType.ToUpper (CultureInfo.InvariantCulture).Trim ();
+ switch (s) {
+ case "C":
+ return new X520.CountryName ();
+ case "O":
+ return new X520.OrganizationName ();
+ case "OU":
+ return new X520.OrganizationalUnitName ();
+ case "CN":
+ return new X520.CommonName ();
+ case "L":
+ return new X520.LocalityName ();
+ case "S": // Microsoft
+ case "ST": // RFC2253
+ return new X520.StateOrProvinceName ();
+ case "E": // NOTE: Not part of RFC2253
+ return new X520.EmailAddress ();
+ case "DC": // RFC2247
+ return new X520.DomainComponent ();
+ case "UID": // RFC1274
+ return new X520.UserId ();
+ case "DNQUALIFIER":
+ return new X520.DnQualifier ();
+ case "T":
+ return new X520.Title ();
+ case "SN":
+ return new X520.Surname ();
+ case "G":
+ return new X520.GivenName ();
+ case "I":
+ return new X520.Initial ();
+ default:
+ if (s.StartsWith ("OID.")) {
+ // MUST support it but it OID may be without it
+ return new X520.Oid (s.Substring (4));
+ } else {
+ if (IsOid (s))
+ return new X520.Oid (s);
+ else
+ return null;
+ }
+ }
+ }
+
+ static private bool IsOid (string oid)
+ {
+ try {
+ ASN1 asn = ASN1Convert.FromOid (oid);
+ return (asn.Tag == 0x06);
+ }
+ catch {
+ return false;
+ }
+ }
+
+ // no quote processing
+ static private X520.AttributeTypeAndValue ReadAttribute (string value, ref int pos)
+ {
+ while ((value[pos] == ' ') && (pos < value.Length))
+ pos++;
+
+ // get '=' position in substring
+ int equal = value.IndexOf ('=', pos);
+ if (equal == -1) {
+ string msg = ("No attribute found.");
+ throw new FormatException (msg);
+ }
+
+ string s = value.Substring (pos, equal - pos);
+ X520.AttributeTypeAndValue atv = GetAttributeFromOid (s);
+ if (atv == null) {
+ string msg = ("Unknown attribute '{0}'.");
+ throw new FormatException (String.Format (msg, s));
+ }
+ pos = equal + 1; // skip the '='
+ return atv;
+ }
+
+ static private bool IsHex (char c)
+ {
+ if (Char.IsDigit (c))
+ return true;
+ char up = Char.ToUpper (c, CultureInfo.InvariantCulture);
+ return ((up >= 'A') && (up <= 'F'));
+ }
+
+ static string ReadHex (string value, ref int pos)
+ {
+ StringBuilder sb = new StringBuilder ();
+ // it is (at least an) 8 bits char
+ sb.Append (value[pos++]);
+ sb.Append (value[pos]);
+ // look ahead for a 16 bits char
+ if ((pos < value.Length - 4) && (value[pos+1] == '\\') && IsHex (value[pos+2])) {
+ pos += 2; // pass last char and skip \
+ sb.Append (value[pos++]);
+ sb.Append (value[pos]);
+ }
+ byte[] data = CryptoConvert.FromHex (sb.ToString ());
+ return Encoding.UTF8.GetString (data);
+ }
+
+ static private int ReadEscaped (StringBuilder sb, string value, int pos)
+ {
+ switch (value[pos]) {
+ case '\\':
+ case '"':
+ case '=':
+ case ';':
+ case '<':
+ case '>':
+ case '+':
+ case '#':
+ case ',':
+ sb.Append (value[pos]);
+ return pos;
+ default:
+ if (pos >= value.Length - 2) {
+ string msg = ("Malformed escaped value '{0}'.");
+ throw new FormatException (string.Format (msg, value.Substring (pos)));
+ }
+ // it's either a 8 bits or 16 bits char
+ sb.Append (ReadHex (value, ref pos));
+ return pos;
+ }
+ }
+
+ static private int ReadQuoted (StringBuilder sb, string value, int pos)
+ {
+ int original = pos;
+ while (pos <= value.Length) {
+ switch (value[pos]) {
+ case '"':
+ return pos;
+ case '\\':
+ return ReadEscaped (sb, value, pos);
+ default:
+ sb.Append (value[pos]);
+ pos++;
+ break;
+ }
+ }
+ string msg = ("Malformed quoted value '{0}'.");
+ throw new FormatException (string.Format (msg, value.Substring (original)));
+ }
+
+ static private string ReadValue (string value, ref int pos)
+ {
+ int original = pos;
+ StringBuilder sb = new StringBuilder ();
+ while (pos < value.Length) {
+ switch (value [pos]) {
+ case '\\':
+ pos = ReadEscaped (sb, value, ++pos);
+ break;
+ case '"':
+ pos = ReadQuoted (sb, value, ++pos);
+ break;
+ case '=':
+ case ';':
+ case '<':
+ case '>':
+ string msg =("Malformed value '{0}' contains '{1}' outside quotes.");
+ throw new FormatException (string.Format (msg, value.Substring (original), value[pos]));
+ case '+':
+ case '#':
+ throw new NotImplementedException ();
+ case ',':
+ pos++;
+ return sb.ToString ();
+ default:
+ sb.Append (value[pos]);
+ break;
+ }
+ pos++;
+ }
+ return sb.ToString ();
+ }
+
+ static public ASN1 FromString (string rdn)
+ {
+ if (rdn == null)
+ throw new ArgumentNullException ("rdn");
+
+ int pos = 0;
+ ASN1 asn1 = new ASN1 (0x30);
+ while (pos < rdn.Length) {
+ X520.AttributeTypeAndValue atv = ReadAttribute (rdn, ref pos);
+ atv.Value = ReadValue (rdn, ref pos);
+
+ ASN1 sequence = new ASN1 (0x31);
+ sequence.Add (atv.GetASN1 ());
+ asn1.Add (sequence);
+ }
+ return asn1;
+ }
+ }
+}
diff --git a/MediaBrowser.Server.Mono/Security/X509Builder.cs b/MediaBrowser.Server.Mono/Security/X509Builder.cs
new file mode 100644
index 000000000..95926ae3f
--- /dev/null
+++ b/MediaBrowser.Server.Mono/Security/X509Builder.cs
@@ -0,0 +1,154 @@
+//
+// X509Builder.cs: Abstract builder class for X509 objects
+//
+// Author:
+// Sebastien Pouliot <sebastien@ximian.com>
+//
+// (C) 2002, 2003 Motus Technologies Inc. (http://www.motus.com)
+// (C) 2004 Novell (http://www.novell.com)
+//
+
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+using System;
+using System.Globalization;
+using System.Security.Cryptography;
+
+using Mono.Security;
+
+namespace Mono.Security.X509 {
+
+ public abstract class X509Builder {
+
+ private const string defaultHash = "SHA1";
+ private string hashName;
+
+ protected X509Builder ()
+ {
+ hashName = defaultHash;
+ }
+
+ protected abstract ASN1 ToBeSigned (string hashName);
+
+ // move to PKCS1
+ protected string GetOid (string hashName)
+ {
+ switch (hashName.ToLower (CultureInfo.InvariantCulture)) {
+ case "md2":
+ // md2withRSAEncryption (1 2 840 113549 1 1 2)
+ return "1.2.840.113549.1.1.2";
+ case "md4":
+ // md4withRSAEncryption (1 2 840 113549 1 1 3)
+ return "1.2.840.113549.1.1.3";
+ case "md5":
+ // md5withRSAEncryption (1 2 840 113549 1 1 4)
+ return "1.2.840.113549.1.1.4";
+ case "sha1":
+ // sha1withRSAEncryption (1 2 840 113549 1 1 5)
+ return "1.2.840.113549.1.1.5";
+ case "sha256":
+ // sha256WithRSAEncryption OBJECT IDENTIFIER ::= { pkcs-1 11 }
+ return "1.2.840.113549.1.1.11";
+ case "sha384":
+ // sha384WithRSAEncryption OBJECT IDENTIFIER ::= { pkcs-1 12 }
+ return "1.2.840.113549.1.1.12";
+ case "sha512":
+ // sha512WithRSAEncryption OBJECT IDENTIFIER ::= { pkcs-1 13 }
+ return "1.2.840.113549.1.1.13";
+ default:
+ throw new NotSupportedException ("Unknown hash algorithm " + hashName);
+ }
+ }
+
+ public string Hash {
+ get { return hashName; }
+ set {
+ if (hashName == null)
+ hashName = defaultHash;
+ else
+ hashName = value;
+ }
+ }
+
+ public virtual byte[] Sign (AsymmetricAlgorithm aa)
+ {
+ if (aa is RSA)
+ return Sign (aa as RSA);
+ else if (aa is DSA)
+ return Sign (aa as DSA);
+ else
+ throw new NotSupportedException ("Unknown Asymmetric Algorithm " + aa.ToString());
+ }
+
+ private byte[] Build (ASN1 tbs, string hashoid, byte[] signature)
+ {
+ ASN1 builder = new ASN1 (0x30);
+ builder.Add (tbs);
+ builder.Add (PKCS7.AlgorithmIdentifier (hashoid));
+ // first byte of BITSTRING is the number of unused bits in the first byte
+ byte[] bitstring = new byte [signature.Length + 1];
+ Buffer.BlockCopy (signature, 0, bitstring, 1, signature.Length);
+ builder.Add (new ASN1 (0x03, bitstring));
+ return builder.GetBytes ();
+ }
+
+ public virtual byte[] Sign (RSA key)
+ {
+ string oid = GetOid (hashName);
+ ASN1 tbs = ToBeSigned (oid);
+ HashAlgorithm ha = HashAlgorithm.Create (hashName);
+ byte[] hash = ha.ComputeHash (tbs.GetBytes ());
+
+ RSAPKCS1SignatureFormatter pkcs1 = new RSAPKCS1SignatureFormatter (key);
+ pkcs1.SetHashAlgorithm (hashName);
+ byte[] signature = pkcs1.CreateSignature (hash);
+
+ return Build (tbs, oid, signature);
+ }
+
+ public virtual byte[] Sign (DSA key)
+ {
+ string oid = "1.2.840.10040.4.3";
+ ASN1 tbs = ToBeSigned (oid);
+ HashAlgorithm ha = HashAlgorithm.Create (hashName);
+ if (!(ha is SHA1))
+ throw new NotSupportedException ("Only SHA-1 is supported for DSA");
+ byte[] hash = ha.ComputeHash (tbs.GetBytes ());
+
+ DSASignatureFormatter dsa = new DSASignatureFormatter (key);
+ dsa.SetHashAlgorithm (hashName);
+ byte[] rs = dsa.CreateSignature (hash);
+
+ // split R and S
+ byte[] r = new byte [20];
+ Buffer.BlockCopy (rs, 0, r, 0, 20);
+ byte[] s = new byte [20];
+ Buffer.BlockCopy (rs, 20, s, 0, 20);
+ ASN1 signature = new ASN1 (0x30);
+ signature.Add (new ASN1 (0x02, r));
+ signature.Add (new ASN1 (0x02, s));
+
+ // dsaWithSha1 (1 2 840 10040 4 3)
+ return Build (tbs, oid, signature.GetBytes ());
+ }
+ }
+}
diff --git a/MediaBrowser.Server.Mono/Security/X509Certificate.cs b/MediaBrowser.Server.Mono/Security/X509Certificate.cs
new file mode 100644
index 000000000..5b356e2e2
--- /dev/null
+++ b/MediaBrowser.Server.Mono/Security/X509Certificate.cs
@@ -0,0 +1,567 @@
+//
+// X509Certificates.cs: Handles X.509 certificates.
+//
+// Author:
+// Sebastien Pouliot <sebastien@xamarin.com>
+//
+// (C) 2002, 2003 Motus Technologies Inc. (http://www.motus.com)
+// Copyright (C) 2004-2006 Novell, Inc (http://www.novell.com)
+// Copyright 2013 Xamarin Inc. (http://www.xamarin.com)
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+using System;
+using System.Runtime.Serialization;
+using System.Security.Cryptography;
+using SSCX = System.Security.Cryptography.X509Certificates;
+using System.Security.Permissions;
+using System.Text;
+using Mono.Security.Cryptography;
+
+namespace Mono.Security.X509 {
+
+ // References:
+ // a. Internet X.509 Public Key Infrastructure Certificate and CRL Profile
+ // http://www.ietf.org/rfc/rfc3280.txt
+ // b. ITU ASN.1 standards (free download)
+ // http://www.itu.int/ITU-T/studygroups/com17/languages/
+
+#if INSIDE_CORLIB
+ internal class X509Certificate : ISerializable {
+#else
+ public class X509Certificate : ISerializable {
+#endif
+
+ private ASN1 decoder;
+
+ private byte[] m_encodedcert;
+ private DateTime m_from;
+ private DateTime m_until;
+ private ASN1 issuer;
+ private string m_issuername;
+ private string m_keyalgo;
+ private byte[] m_keyalgoparams;
+ private ASN1 subject;
+ private string m_subject;
+ private byte[] m_publickey;
+ private byte[] signature;
+ private string m_signaturealgo;
+ private byte[] m_signaturealgoparams;
+ private byte[] certhash;
+ private RSA _rsa;
+ private DSA _dsa;
+
+ // from http://msdn.microsoft.com/en-gb/library/ff635835.aspx
+ private const string OID_DSA = "1.2.840.10040.4.1";
+ private const string OID_RSA = "1.2.840.113549.1.1.1";
+
+ // from http://www.ietf.org/rfc/rfc2459.txt
+ //
+ //Certificate ::= SEQUENCE {
+ // tbsCertificate TBSCertificate,
+ // signatureAlgorithm AlgorithmIdentifier,
+ // signature BIT STRING }
+ //
+ //TBSCertificate ::= SEQUENCE {
+ // version [0] Version DEFAULT v1,
+ // serialNumber CertificateSerialNumber,
+ // signature AlgorithmIdentifier,
+ // issuer Name,
+ // validity Validity,
+ // subject Name,
+ // subjectPublicKeyInfo SubjectPublicKeyInfo,
+ // issuerUniqueID [1] IMPLICIT UniqueIdentifier OPTIONAL,
+ // -- If present, version shall be v2 or v3
+ // subjectUniqueID [2] IMPLICIT UniqueIdentifier OPTIONAL,
+ // -- If present, version shall be v2 or v3
+ // extensions [3] Extensions OPTIONAL
+ // -- If present, version shall be v3 -- }
+ private int version;
+ private byte[] serialnumber;
+
+ private byte[] issuerUniqueID;
+ private byte[] subjectUniqueID;
+ private X509ExtensionCollection extensions;
+
+ private static string encoding_error = ("Input data cannot be coded as a valid certificate.");
+
+
+ // that's were the real job is!
+ private void Parse (byte[] data)
+ {
+ try {
+ decoder = new ASN1 (data);
+ // Certificate
+ if (decoder.Tag != 0x30)
+ throw new CryptographicException (encoding_error);
+ // Certificate / TBSCertificate
+ if (decoder [0].Tag != 0x30)
+ throw new CryptographicException (encoding_error);
+
+ ASN1 tbsCertificate = decoder [0];
+
+ int tbs = 0;
+ // Certificate / TBSCertificate / Version
+ ASN1 v = decoder [0][tbs];
+ version = 1; // DEFAULT v1
+ if ((v.Tag == 0xA0) && (v.Count > 0)) {
+ // version (optional) is present only in v2+ certs
+ version += v [0].Value [0]; // zero based
+ tbs++;
+ }
+
+ // Certificate / TBSCertificate / CertificateSerialNumber
+ ASN1 sn = decoder [0][tbs++];
+ if (sn.Tag != 0x02)
+ throw new CryptographicException (encoding_error);
+ serialnumber = sn.Value;
+ Array.Reverse (serialnumber, 0, serialnumber.Length);
+
+ // Certificate / TBSCertificate / AlgorithmIdentifier
+ tbs++;
+ // ASN1 signatureAlgo = tbsCertificate.Element (tbs++, 0x30);
+
+ issuer = tbsCertificate.Element (tbs++, 0x30);
+ m_issuername = X501.ToString (issuer);
+
+ ASN1 validity = tbsCertificate.Element (tbs++, 0x30);
+ ASN1 notBefore = validity [0];
+ m_from = ASN1Convert.ToDateTime (notBefore);
+ ASN1 notAfter = validity [1];
+ m_until = ASN1Convert.ToDateTime (notAfter);
+
+ subject = tbsCertificate.Element (tbs++, 0x30);
+ m_subject = X501.ToString (subject);
+
+ ASN1 subjectPublicKeyInfo = tbsCertificate.Element (tbs++, 0x30);
+
+ ASN1 algorithm = subjectPublicKeyInfo.Element (0, 0x30);
+ ASN1 algo = algorithm.Element (0, 0x06);
+ m_keyalgo = ASN1Convert.ToOid (algo);
+ // parameters ANY DEFINED BY algorithm OPTIONAL
+ // so we dont ask for a specific (Element) type and return DER
+ ASN1 parameters = algorithm [1];
+ m_keyalgoparams = ((algorithm.Count > 1) ? parameters.GetBytes () : null);
+
+ ASN1 subjectPublicKey = subjectPublicKeyInfo.Element (1, 0x03);
+ // we must drop th first byte (which is the number of unused bits
+ // in the BITSTRING)
+ int n = subjectPublicKey.Length - 1;
+ m_publickey = new byte [n];
+ Buffer.BlockCopy (subjectPublicKey.Value, 1, m_publickey, 0, n);
+
+ // signature processing
+ byte[] bitstring = decoder [2].Value;
+ // first byte contains unused bits in first byte
+ signature = new byte [bitstring.Length - 1];
+ Buffer.BlockCopy (bitstring, 1, signature, 0, signature.Length);
+
+ algorithm = decoder [1];
+ algo = algorithm.Element (0, 0x06);
+ m_signaturealgo = ASN1Convert.ToOid (algo);
+ parameters = algorithm [1];
+ if (parameters != null)
+ m_signaturealgoparams = parameters.GetBytes ();
+ else
+ m_signaturealgoparams = null;
+
+ // Certificate / TBSCertificate / issuerUniqueID
+ ASN1 issuerUID = tbsCertificate.Element (tbs, 0x81);
+ if (issuerUID != null) {
+ tbs++;
+ issuerUniqueID = issuerUID.Value;
+ }
+
+ // Certificate / TBSCertificate / subjectUniqueID
+ ASN1 subjectUID = tbsCertificate.Element (tbs, 0x82);
+ if (subjectUID != null) {
+ tbs++;
+ subjectUniqueID = subjectUID.Value;
+ }
+
+ // Certificate / TBSCertificate / Extensions
+ ASN1 extns = tbsCertificate.Element (tbs, 0xA3);
+ if ((extns != null) && (extns.Count == 1))
+ extensions = new X509ExtensionCollection (extns [0]);
+ else
+ extensions = new X509ExtensionCollection (null);
+
+ // keep a copy of the original data
+ m_encodedcert = (byte[]) data.Clone ();
+ }
+ catch (Exception ex) {
+ throw new CryptographicException (encoding_error, ex);
+ }
+ }
+
+ // constructors
+
+ public X509Certificate (byte[] data)
+ {
+ if (data != null) {
+ // does it looks like PEM ?
+ if ((data.Length > 0) && (data [0] != 0x30)) {
+ try {
+ data = PEM ("CERTIFICATE", data);
+ }
+ catch (Exception ex) {
+ throw new CryptographicException (encoding_error, ex);
+ }
+ }
+ Parse (data);
+ }
+ }
+
+ private byte[] GetUnsignedBigInteger (byte[] integer)
+ {
+ if (integer [0] == 0x00) {
+ // this first byte is added so we're sure it's an unsigned integer
+ // however we can't feed it into RSAParameters or DSAParameters
+ int length = integer.Length - 1;
+ byte[] uinteger = new byte [length];
+ Buffer.BlockCopy (integer, 1, uinteger, 0, length);
+ return uinteger;
+ }
+ else
+ return integer;
+ }
+
+ // public methods
+
+ public DSA DSA {
+ get {
+ if (m_keyalgoparams == null)
+ throw new CryptographicException ("Missing key algorithm parameters.");
+
+ if (_dsa == null && m_keyalgo == OID_DSA) {
+ DSAParameters dsaParams = new DSAParameters ();
+ // for DSA m_publickey contains 1 ASN.1 integer - Y
+ ASN1 pubkey = new ASN1 (m_publickey);
+ if ((pubkey == null) || (pubkey.Tag != 0x02))
+ return null;
+ dsaParams.Y = GetUnsignedBigInteger (pubkey.Value);
+
+ ASN1 param = new ASN1 (m_keyalgoparams);
+ if ((param == null) || (param.Tag != 0x30) || (param.Count < 3))
+ return null;
+ if ((param [0].Tag != 0x02) || (param [1].Tag != 0x02) || (param [2].Tag != 0x02))
+ return null;
+ dsaParams.P = GetUnsignedBigInteger (param [0].Value);
+ dsaParams.Q = GetUnsignedBigInteger (param [1].Value);
+ dsaParams.G = GetUnsignedBigInteger (param [2].Value);
+
+ // BUG: MS BCL 1.0 can't import a key which
+ // isn't the same size as the one present in
+ // the container.
+ _dsa = (DSA) new DSACryptoServiceProvider (dsaParams.Y.Length << 3);
+ _dsa.ImportParameters (dsaParams);
+ }
+ return _dsa;
+ }
+
+ set {
+ _dsa = value;
+ if (value != null)
+ _rsa = null;
+ }
+ }
+
+ public X509ExtensionCollection Extensions {
+ get { return extensions; }
+ }
+
+ public byte[] Hash {
+ get {
+ if (certhash == null) {
+ if ((decoder == null) || (decoder.Count < 1))
+ return null;
+ string algo = PKCS1.HashNameFromOid (m_signaturealgo, false);
+ if (algo == null)
+ return null;
+ byte[] toBeSigned = decoder [0].GetBytes ();
+ using (var hash = PKCS1.CreateFromName (algo))
+ certhash = hash.ComputeHash (toBeSigned, 0, toBeSigned.Length);
+ }
+ return (byte[]) certhash.Clone ();
+ }
+ }
+
+ public virtual string IssuerName {
+ get { return m_issuername; }
+ }
+
+ public virtual string KeyAlgorithm {
+ get { return m_keyalgo; }
+ }
+
+ public virtual byte[] KeyAlgorithmParameters {
+ get {
+ if (m_keyalgoparams == null)
+ return null;
+ return (byte[]) m_keyalgoparams.Clone ();
+ }
+ set { m_keyalgoparams = value; }
+ }
+
+ public virtual byte[] PublicKey {
+ get {
+ if (m_publickey == null)
+ return null;
+ return (byte[]) m_publickey.Clone ();
+ }
+ }
+
+ public virtual RSA RSA {
+ get {
+ if (_rsa == null && m_keyalgo == OID_RSA) {
+ RSAParameters rsaParams = new RSAParameters ();
+ // for RSA m_publickey contains 2 ASN.1 integers
+ // the modulus and the public exponent
+ ASN1 pubkey = new ASN1 (m_publickey);
+ ASN1 modulus = pubkey [0];
+ if ((modulus == null) || (modulus.Tag != 0x02))
+ return null;
+ ASN1 exponent = pubkey [1];
+ if (exponent.Tag != 0x02)
+ return null;
+
+ rsaParams.Modulus = GetUnsignedBigInteger (modulus.Value);
+ rsaParams.Exponent = exponent.Value;
+
+ // BUG: MS BCL 1.0 can't import a key which
+ // isn't the same size as the one present in
+ // the container.
+ int keySize = (rsaParams.Modulus.Length << 3);
+ _rsa = (RSA) new RSACryptoServiceProvider (keySize);
+ _rsa.ImportParameters (rsaParams);
+ }
+ return _rsa;
+ }
+
+ set {
+ if (value != null)
+ _dsa = null;
+ _rsa = value;
+ }
+ }
+
+ public virtual byte[] RawData {
+ get {
+ if (m_encodedcert == null)
+ return null;
+ return (byte[]) m_encodedcert.Clone ();
+ }
+ }
+
+ public virtual byte[] SerialNumber {
+ get {
+ if (serialnumber == null)
+ return null;
+ return (byte[]) serialnumber.Clone ();
+ }
+ }
+
+ public virtual byte[] Signature {
+ get {
+ if (signature == null)
+ return null;
+
+ switch (m_signaturealgo) {
+ case "1.2.840.113549.1.1.2": // MD2 with RSA encryption
+ case "1.2.840.113549.1.1.3": // MD4 with RSA encryption
+ case "1.2.840.113549.1.1.4": // MD5 with RSA encryption
+ case "1.2.840.113549.1.1.5": // SHA-1 with RSA Encryption
+ case "1.3.14.3.2.29": // SHA1 with RSA signature
+ case "1.2.840.113549.1.1.11": // SHA-256 with RSA Encryption
+ case "1.2.840.113549.1.1.12": // SHA-384 with RSA Encryption
+ case "1.2.840.113549.1.1.13": // SHA-512 with RSA Encryption
+ case "1.3.36.3.3.1.2": // RIPEMD160 with RSA Encryption
+ return (byte[]) signature.Clone ();
+
+ case "1.2.840.10040.4.3": // SHA-1 with DSA
+ ASN1 sign = new ASN1 (signature);
+ if ((sign == null) || (sign.Count != 2))
+ return null;
+ byte[] part1 = sign [0].Value;
+ byte[] part2 = sign [1].Value;
+ byte[] sig = new byte [40];
+ // parts may be less than 20 bytes (i.e. first bytes were 0x00)
+ // parts may be more than 20 bytes (i.e. first byte > 0x80, negative)
+ int s1 = System.Math.Max (0, part1.Length - 20);
+ int e1 = System.Math.Max (0, 20 - part1.Length);
+ Buffer.BlockCopy (part1, s1, sig, e1, part1.Length - s1);
+ int s2 = System.Math.Max (0, part2.Length - 20);
+ int e2 = System.Math.Max (20, 40 - part2.Length);
+ Buffer.BlockCopy (part2, s2, sig, e2, part2.Length - s2);
+ return sig;
+
+ default:
+ throw new CryptographicException ("Unsupported hash algorithm: " + m_signaturealgo);
+ }
+ }
+ }
+
+ public virtual string SignatureAlgorithm {
+ get { return m_signaturealgo; }
+ }
+
+ public virtual byte[] SignatureAlgorithmParameters {
+ get {
+ if (m_signaturealgoparams == null)
+ return m_signaturealgoparams;
+ return (byte[]) m_signaturealgoparams.Clone ();
+ }
+ }
+
+ public virtual string SubjectName {
+ get { return m_subject; }
+ }
+
+ public virtual DateTime ValidFrom {
+ get { return m_from; }
+ }
+
+ public virtual DateTime ValidUntil {
+ get { return m_until; }
+ }
+
+ public int Version {
+ get { return version; }
+ }
+
+ public bool IsCurrent {
+ get { return WasCurrent (DateTime.UtcNow); }
+ }
+
+ public bool WasCurrent (DateTime instant)
+ {
+ return ((instant > ValidFrom) && (instant <= ValidUntil));
+ }
+
+ // uncommon v2 "extension"
+ public byte[] IssuerUniqueIdentifier {
+ get {
+ if (issuerUniqueID == null)
+ return null;
+ return (byte[]) issuerUniqueID.Clone ();
+ }
+ }
+
+ // uncommon v2 "extension"
+ public byte[] SubjectUniqueIdentifier {
+ get {
+ if (subjectUniqueID == null)
+ return null;
+ return (byte[]) subjectUniqueID.Clone ();
+ }
+ }
+
+ internal bool VerifySignature (DSA dsa)
+ {
+ // signatureOID is check by both this.Hash and this.Signature
+ DSASignatureDeformatter v = new DSASignatureDeformatter (dsa);
+ // only SHA-1 is supported
+ v.SetHashAlgorithm ("SHA1");
+ return v.VerifySignature (this.Hash, this.Signature);
+ }
+
+ internal bool VerifySignature (RSA rsa)
+ {
+ // SHA1-1 with DSA
+ if (m_signaturealgo == "1.2.840.10040.4.3")
+ return false;
+ RSAPKCS1SignatureDeformatter v = new RSAPKCS1SignatureDeformatter (rsa);
+ v.SetHashAlgorithm (PKCS1.HashNameFromOid (m_signaturealgo));
+ return v.VerifySignature (this.Hash, this.Signature);
+ }
+
+ public bool VerifySignature (AsymmetricAlgorithm aa)
+ {
+ if (aa == null)
+ throw new ArgumentNullException ("aa");
+
+ if (aa is RSA)
+ return VerifySignature (aa as RSA);
+ else if (aa is DSA)
+ return VerifySignature (aa as DSA);
+ else
+ throw new NotSupportedException ("Unknown Asymmetric Algorithm " + aa.ToString ());
+ }
+
+ public bool CheckSignature (byte[] hash, string hashAlgorithm, byte[] signature)
+ {
+ RSACryptoServiceProvider r = (RSACryptoServiceProvider) RSA;
+ return r.VerifyHash (hash, hashAlgorithm, signature);
+ }
+
+ public bool IsSelfSigned {
+ get {
+ if (m_issuername != m_subject)
+ return false;
+
+ try {
+ if (RSA != null)
+ return VerifySignature (RSA);
+ else if (DSA != null)
+ return VerifySignature (DSA);
+ else
+ return false; // e.g. a certificate with only DSA parameters
+ }
+ catch (CryptographicException) {
+ return false;
+ }
+ }
+ }
+
+ public ASN1 GetIssuerName ()
+ {
+ return issuer;
+ }
+
+ public ASN1 GetSubjectName ()
+ {
+ return subject;
+ }
+
+ protected X509Certificate (SerializationInfo info, StreamingContext context)
+ {
+ Parse ((byte[]) info.GetValue ("raw", typeof (byte[])));
+ }
+
+ [SecurityPermission (SecurityAction.Demand, SerializationFormatter = true)]
+ public virtual void GetObjectData (SerializationInfo info, StreamingContext context)
+ {
+ info.AddValue ("raw", m_encodedcert);
+ // note: we NEVER serialize the private key
+ }
+
+ static byte[] PEM (string type, byte[] data)
+ {
+ string pem = Encoding.ASCII.GetString (data);
+ string header = String.Format ("-----BEGIN {0}-----", type);
+ string footer = String.Format ("-----END {0}-----", type);
+ int start = pem.IndexOf (header) + header.Length;
+ int end = pem.IndexOf (footer, start);
+ string base64 = pem.Substring (start, (end - start));
+ return Convert.FromBase64String (base64);
+ }
+ }
+}
diff --git a/MediaBrowser.Server.Mono/Security/X509CertificateBuilder.cs b/MediaBrowser.Server.Mono/Security/X509CertificateBuilder.cs
new file mode 100644
index 000000000..3ee281628
--- /dev/null
+++ b/MediaBrowser.Server.Mono/Security/X509CertificateBuilder.cs
@@ -0,0 +1,244 @@
+//
+// X509CertificateBuilder.cs: Handles building of X.509 certificates.
+//
+// Author:
+// Sebastien Pouliot <sebastien@ximian.com>
+//
+// (C) 2003 Motus Technologies Inc. (http://www.motus.com)
+// (C) 2004 Novell (http://www.novell.com)
+//
+
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+using System;
+using System.Security.Cryptography;
+
+namespace Mono.Security.X509 {
+ // From RFC3280
+ /*
+ * Certificate ::= SEQUENCE {
+ * tbsCertificate TBSCertificate,
+ * signatureAlgorithm AlgorithmIdentifier,
+ * signature BIT STRING
+ * }
+ * TBSCertificate ::= SEQUENCE {
+ * version [0] Version DEFAULT v1,
+ * serialNumber CertificateSerialNumber,
+ * signature AlgorithmIdentifier,
+ * issuer Name,
+ * validity Validity,
+ * subject Name,
+ * subjectPublicKeyInfo SubjectPublicKeyInfo,
+ * issuerUniqueID [1] IMPLICIT UniqueIdentifier OPTIONAL,
+ * -- If present, version MUST be v2 or v3
+ * subjectUniqueID [2] IMPLICIT UniqueIdentifier OPTIONAL,
+ * -- If present, version MUST be v2 or v3
+ * extensions [3] Extensions OPTIONAL
+ * -- If present, version MUST be v3 --
+ * }
+ * Version ::= INTEGER { v1(0), v2(1), v3(2) }
+ * CertificateSerialNumber ::= INTEGER
+ * Validity ::= SEQUENCE {
+ * notBefore Time,
+ * notAfter Time
+ * }
+ * Time ::= CHOICE {
+ * utcTime UTCTime,
+ * generalTime GeneralizedTime
+ * }
+ */
+ public class X509CertificateBuilder : X509Builder {
+
+ private byte version;
+ private byte[] sn;
+ private string issuer;
+ private DateTime notBefore;
+ private DateTime notAfter;
+ private string subject;
+ private AsymmetricAlgorithm aa;
+ private byte[] issuerUniqueID;
+ private byte[] subjectUniqueID;
+ private X509ExtensionCollection extensions;
+
+ public X509CertificateBuilder () : this (3) {}
+
+ public X509CertificateBuilder (byte version)
+ {
+ if (version > 3)
+ throw new ArgumentException ("Invalid certificate version");
+ this.version = version;
+ extensions = new X509ExtensionCollection ();
+ }
+
+ public byte Version {
+ get { return version; }
+ set { version = value; }
+ }
+
+ public byte[] SerialNumber {
+ get { return sn; }
+ set { sn = value; }
+ }
+
+ public string IssuerName {
+ get { return issuer; }
+ set { issuer = value; }
+ }
+
+ public DateTime NotBefore {
+ get { return notBefore; }
+ set { notBefore = value; }
+ }
+
+ public DateTime NotAfter {
+ get { return notAfter; }
+ set { notAfter = value; }
+ }
+
+ public string SubjectName {
+ get { return subject; }
+ set { subject = value; }
+ }
+
+ public AsymmetricAlgorithm SubjectPublicKey {
+ get { return aa; }
+ set { aa = value; }
+ }
+
+ public byte[] IssuerUniqueId {
+ get { return issuerUniqueID; }
+ set { issuerUniqueID = value; }
+ }
+
+ public byte[] SubjectUniqueId {
+ get { return subjectUniqueID; }
+ set { subjectUniqueID = value; }
+ }
+
+ public X509ExtensionCollection Extensions {
+ get { return extensions; }
+ }
+
+
+ /* SubjectPublicKeyInfo ::= SEQUENCE {
+ * algorithm AlgorithmIdentifier,
+ * subjectPublicKey BIT STRING }
+ */
+ private ASN1 SubjectPublicKeyInfo ()
+ {
+ ASN1 keyInfo = new ASN1 (0x30);
+ if (aa is RSA) {
+ keyInfo.Add (PKCS7.AlgorithmIdentifier ("1.2.840.113549.1.1.1"));
+ RSAParameters p = (aa as RSA).ExportParameters (false);
+ /* RSAPublicKey ::= SEQUENCE {
+ * modulus INTEGER, -- n
+ * publicExponent INTEGER } -- e
+ */
+ ASN1 key = new ASN1 (0x30);
+ key.Add (ASN1Convert.FromUnsignedBigInteger (p.Modulus));
+ key.Add (ASN1Convert.FromUnsignedBigInteger (p.Exponent));
+ keyInfo.Add (new ASN1 (UniqueIdentifier (key.GetBytes ())));
+ }
+ else if (aa is DSA) {
+ DSAParameters p = (aa as DSA).ExportParameters (false);
+ /* Dss-Parms ::= SEQUENCE {
+ * p INTEGER,
+ * q INTEGER,
+ * g INTEGER }
+ */
+ ASN1 param = new ASN1 (0x30);
+ param.Add (ASN1Convert.FromUnsignedBigInteger (p.P));
+ param.Add (ASN1Convert.FromUnsignedBigInteger (p.Q));
+ param.Add (ASN1Convert.FromUnsignedBigInteger (p.G));
+ keyInfo.Add (PKCS7.AlgorithmIdentifier ("1.2.840.10040.4.1", param));
+ ASN1 key = keyInfo.Add (new ASN1 (0x03));
+ // DSAPublicKey ::= INTEGER -- public key, y
+ key.Add (ASN1Convert.FromUnsignedBigInteger (p.Y));
+ }
+ else
+ throw new NotSupportedException ("Unknown Asymmetric Algorithm " + aa.ToString ());
+ return keyInfo;
+ }
+
+ private byte[] UniqueIdentifier (byte[] id)
+ {
+ // UniqueIdentifier ::= BIT STRING
+ ASN1 uid = new ASN1 (0x03);
+ // first byte in a BITSTRING is the number of unused bits in the first byte
+ byte[] v = new byte [id.Length + 1];
+ Buffer.BlockCopy (id, 0, v, 1, id.Length);
+ uid.Value = v;
+ return uid.GetBytes ();
+ }
+
+ protected override ASN1 ToBeSigned (string oid)
+ {
+ // TBSCertificate
+ ASN1 tbsCert = new ASN1 (0x30);
+
+ if (version > 1) {
+ // TBSCertificate / [0] Version DEFAULT v1,
+ byte[] ver = { (byte)(version - 1) };
+ ASN1 v = tbsCert.Add (new ASN1 (0xA0));
+ v.Add (new ASN1 (0x02, ver));
+ }
+
+ // TBSCertificate / CertificateSerialNumber,
+ tbsCert.Add (new ASN1 (0x02, sn));
+
+ // TBSCertificate / AlgorithmIdentifier,
+ tbsCert.Add (PKCS7.AlgorithmIdentifier (oid));
+
+ // TBSCertificate / Name
+ tbsCert.Add (X501.FromString (issuer));
+
+ // TBSCertificate / Validity
+ ASN1 validity = tbsCert.Add (new ASN1 (0x30));
+ // TBSCertificate / Validity / Time
+ validity.Add (ASN1Convert.FromDateTime (notBefore));
+ // TBSCertificate / Validity / Time
+ validity.Add (ASN1Convert.FromDateTime (notAfter));
+
+ // TBSCertificate / Name
+ tbsCert.Add (X501.FromString (subject));
+
+ // TBSCertificate / SubjectPublicKeyInfo
+ tbsCert.Add (SubjectPublicKeyInfo ());
+
+ if (version > 1) {
+ // TBSCertificate / [1] IMPLICIT UniqueIdentifier OPTIONAL
+ if (issuerUniqueID != null)
+ tbsCert.Add (new ASN1 (0xA1, UniqueIdentifier (issuerUniqueID)));
+
+ // TBSCertificate / [2] IMPLICIT UniqueIdentifier OPTIONAL
+ if (subjectUniqueID != null)
+ tbsCert.Add (new ASN1 (0xA1, UniqueIdentifier (subjectUniqueID)));
+
+ // TBSCertificate / [3] Extensions OPTIONAL
+ if ((version > 2) && (extensions.Count > 0))
+ tbsCert.Add (new ASN1 (0xA3, extensions.GetBytes ()));
+ }
+
+ return tbsCert;
+ }
+ }
+}
diff --git a/MediaBrowser.Server.Mono/Security/X509CertificateCollection.cs b/MediaBrowser.Server.Mono/Security/X509CertificateCollection.cs
new file mode 100644
index 000000000..3fd834b0c
--- /dev/null
+++ b/MediaBrowser.Server.Mono/Security/X509CertificateCollection.cs
@@ -0,0 +1,205 @@
+//
+// Based on System.Security.Cryptography.X509Certificates.X509CertificateCollection
+// in System assembly
+//
+// Authors:
+// Lawrence Pit (loz@cable.a2000.nl)
+// Sebastien Pouliot <sebastien@ximian.com>
+//
+
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+using System;
+using System.Collections;
+
+namespace Mono.Security.X509 {
+
+ [Serializable]
+#if INSIDE_CORLIB
+ internal
+#else
+ public
+#endif
+ class X509CertificateCollection : CollectionBase, IEnumerable {
+
+ public X509CertificateCollection ()
+ {
+ }
+
+ public X509CertificateCollection (X509Certificate [] value)
+ {
+ AddRange (value);
+ }
+
+ public X509CertificateCollection (X509CertificateCollection value)
+ {
+ AddRange (value);
+ }
+
+ // Properties
+
+ public X509Certificate this [int index] {
+ get { return (X509Certificate) InnerList [index]; }
+ set { InnerList [index] = value; }
+ }
+
+ // Methods
+
+ public int Add (X509Certificate value)
+ {
+ if (value == null)
+ throw new ArgumentNullException ("value");
+
+ return InnerList.Add (value);
+ }
+
+ public void AddRange (X509Certificate [] value)
+ {
+ if (value == null)
+ throw new ArgumentNullException ("value");
+
+ for (int i = 0; i < value.Length; i++)
+ InnerList.Add (value [i]);
+ }
+
+ public void AddRange (X509CertificateCollection value)
+ {
+ if (value == null)
+ throw new ArgumentNullException ("value");
+
+ for (int i = 0; i < value.InnerList.Count; i++)
+ InnerList.Add (value [i]);
+ }
+
+ public bool Contains (X509Certificate value)
+ {
+ return (IndexOf (value) != -1);
+ }
+
+ public void CopyTo (X509Certificate[] array, int index)
+ {
+ InnerList.CopyTo (array, index);
+ }
+
+ public new X509CertificateEnumerator GetEnumerator ()
+ {
+ return new X509CertificateEnumerator (this);
+ }
+
+ IEnumerator IEnumerable.GetEnumerator ()
+ {
+ return InnerList.GetEnumerator ();
+ }
+
+ public override int GetHashCode ()
+ {
+ return InnerList.GetHashCode ();
+ }
+
+ public int IndexOf (X509Certificate value)
+ {
+ if (value == null)
+ throw new ArgumentNullException ("value");
+
+ byte[] hash = value.Hash;
+ for (int i=0; i < InnerList.Count; i++) {
+ X509Certificate x509 = (X509Certificate) InnerList [i];
+ if (Compare (x509.Hash, hash))
+ return i;
+ }
+ return -1;
+ }
+
+ public void Insert (int index, X509Certificate value)
+ {
+ InnerList.Insert (index, value);
+ }
+
+ public void Remove (X509Certificate value)
+ {
+ InnerList.Remove (value);
+ }
+
+ // private stuff
+
+ private bool Compare (byte[] array1, byte[] array2)
+ {
+ if ((array1 == null) && (array2 == null))
+ return true;
+ if ((array1 == null) || (array2 == null))
+ return false;
+ if (array1.Length != array2.Length)
+ return false;
+ for (int i=0; i < array1.Length; i++) {
+ if (array1 [i] != array2 [i])
+ return false;
+ }
+ return true;
+ }
+
+ // Inner Class
+
+ public class X509CertificateEnumerator : IEnumerator {
+
+ private IEnumerator enumerator;
+
+ // Constructors
+
+ public X509CertificateEnumerator (X509CertificateCollection mappings)
+ {
+ enumerator = ((IEnumerable) mappings).GetEnumerator ();
+ }
+
+ // Properties
+
+ public X509Certificate Current {
+ get { return (X509Certificate) enumerator.Current; }
+ }
+
+ object IEnumerator.Current {
+ get { return enumerator.Current; }
+ }
+
+ // Methods
+
+ bool IEnumerator.MoveNext ()
+ {
+ return enumerator.MoveNext ();
+ }
+
+ void IEnumerator.Reset ()
+ {
+ enumerator.Reset ();
+ }
+
+ public bool MoveNext ()
+ {
+ return enumerator.MoveNext ();
+ }
+
+ public void Reset ()
+ {
+ enumerator.Reset ();
+ }
+ }
+ }
+}
diff --git a/MediaBrowser.Server.Mono/Security/X509Extension.cs b/MediaBrowser.Server.Mono/Security/X509Extension.cs
new file mode 100644
index 000000000..202be5132
--- /dev/null
+++ b/MediaBrowser.Server.Mono/Security/X509Extension.cs
@@ -0,0 +1,214 @@
+//
+// X509Extension.cs: Base class for all X.509 extensions.
+//
+// Author:
+// Sebastien Pouliot <sebastien@ximian.com>
+//
+// (C) 2003 Motus Technologies Inc. (http://www.motus.com)
+// Copyright (C) 2004-2005 Novell, Inc (http://www.novell.com)
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+using System;
+using System.Globalization;
+using System.Text;
+
+using Mono.Security;
+
+namespace Mono.Security.X509 {
+ /*
+ * Extension ::= SEQUENCE {
+ * extnID OBJECT IDENTIFIER,
+ * critical BOOLEAN DEFAULT FALSE,
+ * extnValue OCTET STRING
+ * }
+ */
+#if INSIDE_CORLIB
+ internal
+#else
+ public
+#endif
+ class X509Extension {
+
+ protected string extnOid;
+ protected bool extnCritical;
+ protected ASN1 extnValue;
+
+ protected X509Extension ()
+ {
+ extnCritical = false;
+ }
+
+ public X509Extension (ASN1 asn1)
+ {
+ if ((asn1.Tag != 0x30) || (asn1.Count < 2))
+ throw new ArgumentException (("Invalid X.509 extension."));
+ if (asn1[0].Tag != 0x06)
+ throw new ArgumentException (("Invalid X.509 extension."));
+
+ extnOid = ASN1Convert.ToOid (asn1[0]);
+ extnCritical = ((asn1[1].Tag == 0x01) && (asn1[1].Value[0] == 0xFF));
+ // last element is an octet string which may need to be decoded
+ extnValue = asn1 [asn1.Count - 1];
+ if ((extnValue.Tag == 0x04) && (extnValue.Length > 0) && (extnValue.Count == 0)) {
+ try {
+ ASN1 encapsulated = new ASN1 (extnValue.Value);
+ extnValue.Value = null;
+ extnValue.Add (encapsulated);
+ }
+ catch {
+ // data isn't ASN.1
+ }
+ }
+ Decode ();
+ }
+
+ public X509Extension (X509Extension extension)
+ {
+ if (extension == null)
+ throw new ArgumentNullException ("extension");
+ if ((extension.Value == null) || (extension.Value.Tag != 0x04) || (extension.Value.Count != 1))
+ throw new ArgumentException (("Invalid X.509 extension."));
+
+ extnOid = extension.Oid;
+ extnCritical = extension.Critical;
+ extnValue = extension.Value;
+ Decode ();
+ }
+
+ // encode the extension *into* an OCTET STRING
+ protected virtual void Decode ()
+ {
+ }
+
+ // decode the extension from *inside* an OCTET STRING
+ protected virtual void Encode ()
+ {
+ }
+
+ public ASN1 ASN1 {
+ get {
+ ASN1 extension = new ASN1 (0x30);
+ extension.Add (ASN1Convert.FromOid (extnOid));
+ if (extnCritical)
+ extension.Add (new ASN1 (0x01, new byte [1] { 0xFF }));
+ Encode ();
+ extension.Add (extnValue);
+ return extension;
+ }
+ }
+
+ public string Oid {
+ get { return extnOid; }
+ }
+
+ public bool Critical {
+ get { return extnCritical; }
+ set { extnCritical = value; }
+ }
+
+ // this gets overrided with more meaningful names
+ public virtual string Name {
+ get { return extnOid; }
+ }
+
+ public ASN1 Value {
+ get {
+ if (extnValue == null) {
+ Encode ();
+ }
+ return extnValue;
+ }
+ }
+
+ public override bool Equals (object obj)
+ {
+ if (obj == null)
+ return false;
+
+ X509Extension ex = (obj as X509Extension);
+ if (ex == null)
+ return false;
+
+ if (extnCritical != ex.extnCritical)
+ return false;
+ if (extnOid != ex.extnOid)
+ return false;
+ if (extnValue.Length != ex.extnValue.Length)
+ return false;
+
+ for (int i=0; i < extnValue.Length; i++) {
+ if (extnValue [i] != ex.extnValue [i])
+ return false;
+ }
+ return true;
+ }
+
+ public byte[] GetBytes ()
+ {
+ return ASN1.GetBytes ();
+ }
+
+ public override int GetHashCode ()
+ {
+ // OID should be unique in a collection of extensions
+ return extnOid.GetHashCode ();
+ }
+
+ private void WriteLine (StringBuilder sb, int n, int pos)
+ {
+ byte[] value = extnValue.Value;
+ int p = pos;
+ for (int j=0; j < 8; j++) {
+ if (j < n) {
+ sb.Append (value [p++].ToString ("X2", CultureInfo.InvariantCulture));
+ sb.Append (" ");
+ }
+ else
+ sb.Append (" ");
+ }
+ sb.Append (" ");
+ p = pos;
+ for (int j=0; j < n; j++) {
+ byte b = value [p++];
+ if (b < 0x20)
+ sb.Append (".");
+ else
+ sb.Append (Convert.ToChar (b));
+ }
+ sb.Append (Environment.NewLine);
+ }
+
+ public override string ToString ()
+ {
+ StringBuilder sb = new StringBuilder ();
+ int div = (extnValue.Length >> 3);
+ int rem = (extnValue.Length - (div << 3));
+ int x = 0;
+ for (int i=0; i < div; i++) {
+ WriteLine (sb, 8, x);
+ x += 8;
+ }
+ WriteLine (sb, rem, x);
+ return sb.ToString ();
+ }
+ }
+}
diff --git a/MediaBrowser.Server.Mono/Security/X509Extensions.cs b/MediaBrowser.Server.Mono/Security/X509Extensions.cs
new file mode 100644
index 000000000..cb8e15def
--- /dev/null
+++ b/MediaBrowser.Server.Mono/Security/X509Extensions.cs
@@ -0,0 +1,201 @@
+//
+// X509Extensions.cs: Handles X.509 extensions.
+//
+// Author:
+// Sebastien Pouliot <sebastien@ximian.com>
+//
+// (C) 2003 Motus Technologies Inc. (http://www.motus.com)
+// (C) 2004 Novell (http://www.novell.com)
+//
+
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+using System;
+using System.Collections;
+
+using Mono.Security;
+
+namespace Mono.Security.X509 {
+ /*
+ * Extensions ::= SEQUENCE SIZE (1..MAX) OF Extension
+ *
+ * Note: 1..MAX -> There shouldn't be 0 Extensions in the ASN1 structure
+ */
+#if INSIDE_CORLIB
+ internal
+#else
+ public
+#endif
+ sealed class X509ExtensionCollection : CollectionBase, IEnumerable {
+
+ private bool readOnly;
+
+ public X509ExtensionCollection () : base ()
+ {
+ }
+
+ public X509ExtensionCollection (ASN1 asn1) : this ()
+ {
+ readOnly = true;
+ if (asn1 == null)
+ return;
+ if (asn1.Tag != 0x30)
+ throw new Exception ("Invalid extensions format");
+ for (int i=0; i < asn1.Count; i++) {
+ X509Extension extension = new X509Extension (asn1 [i]);
+ InnerList.Add (extension);
+ }
+ }
+
+ public int Add (X509Extension extension)
+ {
+ if (extension == null)
+ throw new ArgumentNullException ("extension");
+ if (readOnly)
+ throw new NotSupportedException ("Extensions are read only");
+
+ return InnerList.Add (extension);
+ }
+
+ public void AddRange (X509Extension[] extension)
+ {
+ if (extension == null)
+ throw new ArgumentNullException ("extension");
+ if (readOnly)
+ throw new NotSupportedException ("Extensions are read only");
+
+ for (int i = 0; i < extension.Length; i++)
+ InnerList.Add (extension [i]);
+ }
+
+ public void AddRange (X509ExtensionCollection collection)
+ {
+ if (collection == null)
+ throw new ArgumentNullException ("collection");
+ if (readOnly)
+ throw new NotSupportedException ("Extensions are read only");
+
+ for (int i = 0; i < collection.InnerList.Count; i++)
+ InnerList.Add (collection [i]);
+ }
+
+ public bool Contains (X509Extension extension)
+ {
+ return (IndexOf (extension) != -1);
+ }
+
+ public bool Contains (string oid)
+ {
+ return (IndexOf (oid) != -1);
+ }
+
+ public void CopyTo (X509Extension[] extensions, int index)
+ {
+ if (extensions == null)
+ throw new ArgumentNullException ("extensions");
+
+ InnerList.CopyTo (extensions, index);
+ }
+
+ public int IndexOf (X509Extension extension)
+ {
+ if (extension == null)
+ throw new ArgumentNullException ("extension");
+
+ for (int i=0; i < InnerList.Count; i++) {
+ X509Extension ex = (X509Extension) InnerList [i];
+ if (ex.Equals (extension))
+ return i;
+ }
+ return -1;
+ }
+
+ public int IndexOf (string oid)
+ {
+ if (oid == null)
+ throw new ArgumentNullException ("oid");
+
+ for (int i=0; i < InnerList.Count; i++) {
+ X509Extension ex = (X509Extension) InnerList [i];
+ if (ex.Oid == oid)
+ return i;
+ }
+ return -1;
+ }
+
+ public void Insert (int index, X509Extension extension)
+ {
+ if (extension == null)
+ throw new ArgumentNullException ("extension");
+
+ InnerList.Insert (index, extension);
+ }
+
+ public void Remove (X509Extension extension)
+ {
+ if (extension == null)
+ throw new ArgumentNullException ("extension");
+
+ InnerList.Remove (extension);
+ }
+
+ public void Remove (string oid)
+ {
+ if (oid == null)
+ throw new ArgumentNullException ("oid");
+
+ int index = IndexOf (oid);
+ if (index != -1)
+ InnerList.RemoveAt (index);
+ }
+
+ IEnumerator IEnumerable.GetEnumerator ()
+ {
+ return InnerList.GetEnumerator ();
+ }
+
+ public X509Extension this [int index] {
+ get { return (X509Extension) InnerList [index]; }
+ }
+
+ public X509Extension this [string oid] {
+ get {
+ int index = IndexOf (oid);
+ if (index == -1)
+ return null;
+ return (X509Extension) InnerList [index];
+ }
+ }
+
+ public byte[] GetBytes ()
+ {
+ if (InnerList.Count < 1)
+ return null;
+ ASN1 sequence = new ASN1 (0x30);
+ for (int i=0; i < InnerList.Count; i++) {
+ X509Extension x = (X509Extension) InnerList [i];
+ sequence.Add (x.ASN1);
+ }
+ return sequence.GetBytes ();
+ }
+ }
+}
diff --git a/MediaBrowser.Server.Mono/Security/X520Attributes.cs b/MediaBrowser.Server.Mono/Security/X520Attributes.cs
new file mode 100644
index 000000000..53ad424b5
--- /dev/null
+++ b/MediaBrowser.Server.Mono/Security/X520Attributes.cs
@@ -0,0 +1,353 @@
+//
+// X520.cs: X.520 related stuff (attributes, RDN)
+//
+// Author:
+// Sebastien Pouliot <sebastien@ximian.com>
+//
+// (C) 2002, 2003 Motus Technologies Inc. (http://www.motus.com)
+// Copyright (C) 2004-2005 Novell, Inc (http://www.novell.com)
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+using System;
+using System.Globalization;
+using System.Text;
+
+using Mono.Security;
+
+namespace Mono.Security.X509 {
+
+ // References:
+ // 1. Information technology - Open Systems Interconnection - The Directory: Selected attribute types
+ // http://www.itu.int/rec/recommendation.asp?type=folders&lang=e&parent=T-REC-X.520
+ // 2. Internet X.509 Public Key Infrastructure Certificate and CRL Profile
+ // http://www.ietf.org/rfc/rfc3280.txt
+ // 3. A Summary of the X.500(96) User Schema for use with LDAPv3
+ // http://www.faqs.org/rfcs/rfc2256.html
+ // 4. RFC 2247 - Using Domains in LDAP/X.500 Distinguished Names
+ // http://www.faqs.org/rfcs/rfc2247.html
+
+ /*
+ * AttributeTypeAndValue ::= SEQUENCE {
+ * type AttributeType,
+ * value AttributeValue
+ * }
+ *
+ * AttributeType ::= OBJECT IDENTIFIER
+ *
+ * AttributeValue ::= ANY DEFINED BY AttributeType
+ */
+#if INSIDE_CORLIB
+ internal
+#else
+ public
+#endif
+ class X520 {
+
+ public abstract class AttributeTypeAndValue {
+ private string oid;
+ private string attrValue;
+ private int upperBound;
+ private byte encoding;
+
+ protected AttributeTypeAndValue (string oid, int upperBound)
+ {
+ this.oid = oid;
+ this.upperBound = upperBound;
+ this.encoding = 0xFF;
+ }
+
+ protected AttributeTypeAndValue (string oid, int upperBound, byte encoding)
+ {
+ this.oid = oid;
+ this.upperBound = upperBound;
+ this.encoding = encoding;
+ }
+
+ public string Value {
+ get { return attrValue; }
+ set {
+ if ((attrValue != null) && (attrValue.Length > upperBound)) {
+ string msg = ("Value length bigger than upperbound ({0}).");
+ throw new FormatException (String.Format (msg, upperBound));
+ }
+ attrValue = value;
+ }
+ }
+
+ public ASN1 ASN1 {
+ get { return GetASN1 (); }
+ }
+
+ internal ASN1 GetASN1 (byte encoding)
+ {
+ byte encode = encoding;
+ if (encode == 0xFF)
+ encode = SelectBestEncoding ();
+
+ ASN1 asn1 = new ASN1 (0x30);
+ asn1.Add (ASN1Convert.FromOid (oid));
+ switch (encode) {
+ case 0x13:
+ // PRINTABLESTRING
+ asn1.Add (new ASN1 (0x13, Encoding.ASCII.GetBytes (attrValue)));
+ break;
+ case 0x16:
+ // IA5STRING
+ asn1.Add (new ASN1 (0x16, Encoding.ASCII.GetBytes (attrValue)));
+ break;
+ case 0x1E:
+ // BMPSTRING
+ asn1.Add (new ASN1 (0x1E, Encoding.BigEndianUnicode.GetBytes (attrValue)));
+ break;
+ }
+ return asn1;
+ }
+
+ internal ASN1 GetASN1 ()
+ {
+ return GetASN1 (encoding);
+ }
+
+ public byte[] GetBytes (byte encoding)
+ {
+ return GetASN1 (encoding) .GetBytes ();
+ }
+
+ public byte[] GetBytes ()
+ {
+ return GetASN1 () .GetBytes ();
+ }
+
+ private byte SelectBestEncoding ()
+ {
+ foreach (char c in attrValue) {
+ switch (c) {
+ case '@':
+ case '_':
+ return 0x1E; // BMPSTRING
+ default:
+ if (c > 127)
+ return 0x1E; // BMPSTRING
+ break;
+ }
+ }
+ return 0x13; // PRINTABLESTRING
+ }
+ }
+
+ public class Name : AttributeTypeAndValue {
+
+ public Name () : base ("2.5.4.41", 32768)
+ {
+ }
+ }
+
+ public class CommonName : AttributeTypeAndValue {
+
+ public CommonName () : base ("2.5.4.3", 64)
+ {
+ }
+ }
+
+ // RFC2256, Section 5.6
+ public class SerialNumber : AttributeTypeAndValue {
+
+ // max length 64 bytes, Printable String only
+ public SerialNumber ()
+ : base ("2.5.4.5", 64, 0x13)
+ {
+ }
+ }
+
+ public class LocalityName : AttributeTypeAndValue {
+
+ public LocalityName () : base ("2.5.4.7", 128)
+ {
+ }
+ }
+
+ public class StateOrProvinceName : AttributeTypeAndValue {
+
+ public StateOrProvinceName () : base ("2.5.4.8", 128)
+ {
+ }
+ }
+
+ public class OrganizationName : AttributeTypeAndValue {
+
+ public OrganizationName () : base ("2.5.4.10", 64)
+ {
+ }
+ }
+
+ public class OrganizationalUnitName : AttributeTypeAndValue {
+
+ public OrganizationalUnitName () : base ("2.5.4.11", 64)
+ {
+ }
+ }
+
+ // NOTE: Not part of RFC2253
+ public class EmailAddress : AttributeTypeAndValue {
+
+ public EmailAddress () : base ("1.2.840.113549.1.9.1", 128, 0x16)
+ {
+ }
+ }
+
+ // RFC2247, Section 4
+ public class DomainComponent : AttributeTypeAndValue {
+
+ // no maximum length defined
+ public DomainComponent ()
+ : base ("0.9.2342.19200300.100.1.25", Int32.MaxValue, 0x16)
+ {
+ }
+ }
+
+ // RFC1274, Section 9.3.1
+ public class UserId : AttributeTypeAndValue {
+
+ public UserId ()
+ : base ("0.9.2342.19200300.100.1.1", 256)
+ {
+ }
+ }
+
+ public class Oid : AttributeTypeAndValue {
+
+ public Oid (string oid)
+ : base (oid, Int32.MaxValue)
+ {
+ }
+ }
+
+ /* -- Naming attributes of type X520Title
+ * id-at-title AttributeType ::= { id-at 12 }
+ *
+ * X520Title ::= CHOICE {
+ * teletexString TeletexString (SIZE (1..ub-title)),
+ * printableString PrintableString (SIZE (1..ub-title)),
+ * universalString UniversalString (SIZE (1..ub-title)),
+ * utf8String UTF8String (SIZE (1..ub-title)),
+ * bmpString BMPString (SIZE (1..ub-title))
+ * }
+ */
+ public class Title : AttributeTypeAndValue {
+
+ public Title () : base ("2.5.4.12", 64)
+ {
+ }
+ }
+
+ public class CountryName : AttributeTypeAndValue {
+
+ // (0x13) PRINTABLESTRING
+ public CountryName () : base ("2.5.4.6", 2, 0x13)
+ {
+ }
+ }
+
+ public class DnQualifier : AttributeTypeAndValue {
+
+ // (0x13) PRINTABLESTRING
+ public DnQualifier () : base ("2.5.4.46", 2, 0x13)
+ {
+ }
+ }
+
+ public class Surname : AttributeTypeAndValue {
+
+ public Surname () : base ("2.5.4.4", 32768)
+ {
+ }
+ }
+
+ public class GivenName : AttributeTypeAndValue {
+
+ public GivenName () : base ("2.5.4.42", 16)
+ {
+ }
+ }
+
+ public class Initial : AttributeTypeAndValue {
+
+ public Initial () : base ("2.5.4.43", 5)
+ {
+ }
+ }
+
+ }
+
+ /* From RFC3280
+ * -- specifications of Upper Bounds MUST be regarded as mandatory
+ * -- from Annex B of ITU-T X.411 Reference Definition of MTS Parameter
+ *
+ * -- Upper Bounds
+ *
+ * ub-name INTEGER ::= 32768
+ * ub-common-name INTEGER ::= 64
+ * ub-locality-name INTEGER ::= 128
+ * ub-state-name INTEGER ::= 128
+ * ub-organization-name INTEGER ::= 64
+ * ub-organizational-unit-name INTEGER ::= 64
+ * ub-title INTEGER ::= 64
+ * ub-serial-number INTEGER ::= 64
+ * ub-match INTEGER ::= 128
+ * ub-emailaddress-length INTEGER ::= 128
+ * ub-common-name-length INTEGER ::= 64
+ * ub-country-name-alpha-length INTEGER ::= 2
+ * ub-country-name-numeric-length INTEGER ::= 3
+ * ub-domain-defined-attributes INTEGER ::= 4
+ * ub-domain-defined-attribute-type-length INTEGER ::= 8
+ * ub-domain-defined-attribute-value-length INTEGER ::= 128
+ * ub-domain-name-length INTEGER ::= 16
+ * ub-extension-attributes INTEGER ::= 256
+ * ub-e163-4-number-length INTEGER ::= 15
+ * ub-e163-4-sub-address-length INTEGER ::= 40
+ * ub-generation-qualifier-length INTEGER ::= 3
+ * ub-given-name-length INTEGER ::= 16
+ * ub-initials-length INTEGER ::= 5
+ * ub-integer-options INTEGER ::= 256
+ * ub-numeric-user-id-length INTEGER ::= 32
+ * ub-organization-name-length INTEGER ::= 64
+ * ub-organizational-unit-name-length INTEGER ::= 32
+ * ub-organizational-units INTEGER ::= 4
+ * ub-pds-name-length INTEGER ::= 16
+ * ub-pds-parameter-length INTEGER ::= 30
+ * ub-pds-physical-address-lines INTEGER ::= 6
+ * ub-postal-code-length INTEGER ::= 16
+ * ub-pseudonym INTEGER ::= 128
+ * ub-surname-length INTEGER ::= 40
+ * ub-terminal-id-length INTEGER ::= 24
+ * ub-unformatted-address-length INTEGER ::= 180
+ * ub-x121-address-length INTEGER ::= 16
+ *
+ * -- Note - upper bounds on string types, such as TeletexString, are
+ * -- measured in characters. Excepting PrintableString or IA5String, a
+ * -- significantly greater number of octets will be required to hold
+ * -- such a value. As a minimum, 16 octets, or twice the specified
+ * -- upper bound, whichever is the larger, should be allowed for
+ * -- TeletexString. For UTF8String or UniversalString at least four
+ * -- times the upper bound should be allowed.
+ */
+}
diff --git a/MediaBrowser.Server.Startup.Common/ApplicationHost.cs b/MediaBrowser.Server.Startup.Common/ApplicationHost.cs
index ae839e6ee..1e5c54d93 100644
--- a/MediaBrowser.Server.Startup.Common/ApplicationHost.cs
+++ b/MediaBrowser.Server.Startup.Common/ApplicationHost.cs
@@ -160,7 +160,6 @@ namespace MediaBrowser.Server.Startup.Common
private IHttpServer HttpServer { get; set; }
private IDtoService DtoService { get; set; }
private IImageProcessor ImageProcessor { get; set; }
- private ISeriesOrderManager SeriesOrderManager { get; set; }
/// <summary>
/// Gets or sets the media encoder.
@@ -190,7 +189,6 @@ namespace MediaBrowser.Server.Startup.Common
internal IItemRepository ItemRepository { get; set; }
private INotificationsRepository NotificationsRepository { get; set; }
private IFileOrganizationRepository FileOrganizationRepository { get; set; }
- private IProviderRepository ProviderRepository { get; set; }
private INotificationManager NotificationManager { get; set; }
private ISubtitleManager SubtitleManager { get; set; }
@@ -314,7 +312,6 @@ namespace MediaBrowser.Server.Startup.Common
/// <summary>
/// Runs the startup tasks.
/// </summary>
- /// <returns>Task.</returns>
public override async Task RunStartupTasks()
{
if (ServerConfigurationManager.Configuration.MigrationVersion < CleanDatabaseScheduledTask.MigrationVersion &&
@@ -325,23 +322,30 @@ namespace MediaBrowser.Server.Startup.Common
await base.RunStartupTasks().ConfigureAwait(false);
+ await MediaEncoder.Init().ConfigureAwait(false);
+
Logger.Info("ServerId: {0}", SystemId);
Logger.Info("Core startup complete");
HttpServer.GlobalResponse = null;
PerformPostInitMigrations();
+ Logger.Info("Post-init migrations complete");
- Parallel.ForEach(GetExports<IServerEntryPoint>(), entryPoint =>
+ foreach (var entryPoint in GetExports<IServerEntryPoint>().ToList())
{
+ var name = entryPoint.GetType().FullName;
+ Logger.Info("Starting entry point {0}", name);
try
{
entryPoint.Run();
}
catch (Exception ex)
{
- Logger.ErrorException("Error in {0}", ex, entryPoint.GetType().Name);
+ Logger.ErrorException("Error in {0}", ex, name);
}
- });
+ Logger.Info("Entry point completed: {0}", name);
+ }
+ Logger.Info("All entry points have started");
LogManager.RemoveConsoleOutput();
}
@@ -360,12 +364,18 @@ namespace MediaBrowser.Server.Startup.Common
{
var migrations = new List<IVersionMigration>
{
- new RenameXmlOptions(ServerConfigurationManager)
};
foreach (var task in migrations)
{
- task.Run();
+ try
+ {
+ task.Run();
+ }
+ catch (Exception ex)
+ {
+ Logger.ErrorException("Error running migration", ex);
+ }
}
}
@@ -375,19 +385,28 @@ namespace MediaBrowser.Server.Startup.Common
{
new OmdbEpisodeProviderMigration(ServerConfigurationManager),
new MovieDbEpisodeProviderMigration(ServerConfigurationManager),
- new DbMigration(ServerConfigurationManager, TaskManager)
+ new DbMigration(ServerConfigurationManager, TaskManager),
+ new FolderViewSettingMigration(ServerConfigurationManager, UserManager),
+ new CollectionGroupingMigration(ServerConfigurationManager, UserManager),
+ new CollectionsViewMigration(ServerConfigurationManager, UserManager)
};
foreach (var task in migrations)
{
- task.Run();
+ try
+ {
+ task.Run();
+ }
+ catch (Exception ex)
+ {
+ Logger.ErrorException("Error running migration", ex);
+ }
}
}
/// <summary>
/// Registers resources that classes will depend on
/// </summary>
- /// <returns>Task.</returns>
protected override async Task RegisterResources(IProgress<double> progress)
{
await base.RegisterResources(progress).ConfigureAwait(false);
@@ -410,18 +429,14 @@ namespace MediaBrowser.Server.Startup.Common
UserRepository = await GetUserRepository().ConfigureAwait(false);
RegisterSingleInstance(UserRepository);
- var displayPreferencesRepo = new SqliteDisplayPreferencesRepository(LogManager, JsonSerializer, ApplicationPaths);
+ var displayPreferencesRepo = new SqliteDisplayPreferencesRepository(LogManager, JsonSerializer, ApplicationPaths, NativeApp.GetDbConnector());
DisplayPreferencesRepository = displayPreferencesRepo;
RegisterSingleInstance(DisplayPreferencesRepository);
- var itemRepo = new SqliteItemRepository(ServerConfigurationManager, JsonSerializer, LogManager);
+ var itemRepo = new SqliteItemRepository(ServerConfigurationManager, JsonSerializer, LogManager, NativeApp.GetDbConnector());
ItemRepository = itemRepo;
RegisterSingleInstance(ItemRepository);
- var providerRepo = new SqliteProviderInfoRepository(LogManager, ApplicationPaths);
- ProviderRepository = providerRepo;
- RegisterSingleInstance(ProviderRepository);
-
FileOrganizationRepository = await GetFileOrganizationRepository().ConfigureAwait(false);
RegisterSingleInstance(FileOrganizationRepository);
@@ -446,9 +461,6 @@ namespace MediaBrowser.Server.Startup.Common
ProviderManager = new ProviderManager(HttpClient, ServerConfigurationManager, LibraryMonitor, LogManager, FileSystemManager, ApplicationPaths, () => LibraryManager, JsonSerializer);
RegisterSingleInstance(ProviderManager);
- SeriesOrderManager = new SeriesOrderManager();
- RegisterSingleInstance(SeriesOrderManager);
-
RegisterSingleInstance<ISearchEngine>(() => new SearchEngine(LogManager, LibraryManager, UserManager));
HttpServer = ServerFactory.CreateServer(this, LogManager, ServerConfigurationManager, NetworkManager, "Emby", "web/index.html");
@@ -543,8 +555,8 @@ namespace MediaBrowser.Server.Startup.Common
RegisterSingleInstance(NativeApp.GetPowerManagement());
- var sharingRepo = new SharingRepository(LogManager, ApplicationPaths);
- await sharingRepo.Initialize(NativeApp.GetDbConnector()).ConfigureAwait(false);
+ var sharingRepo = new SharingRepository(LogManager, ApplicationPaths, NativeApp.GetDbConnector());
+ await sharingRepo.Initialize().ConfigureAwait(false);
RegisterSingleInstance<ISharingManager>(new SharingManager(sharingRepo, ServerConfigurationManager, LibraryManager, this));
RegisterSingleInstance<ISsdpHandler>(new SsdpHandler(LogManager.GetLogger("SsdpHandler"), ServerConfigurationManager, this));
@@ -560,11 +572,13 @@ namespace MediaBrowser.Server.Startup.Common
SubtitleEncoder = new SubtitleEncoder(LibraryManager, LogManager.GetLogger("SubtitleEncoder"), ApplicationPaths, FileSystemManager, MediaEncoder, JsonSerializer, HttpClient, MediaSourceManager);
RegisterSingleInstance(SubtitleEncoder);
-
- await displayPreferencesRepo.Initialize(NativeApp.GetDbConnector()).ConfigureAwait(false);
- await ConfigureUserDataRepositories().ConfigureAwait(false);
- await itemRepo.Initialize(NativeApp.GetDbConnector()).ConfigureAwait(false);
- await providerRepo.Initialize(NativeApp.GetDbConnector()).ConfigureAwait(false);
+
+ await displayPreferencesRepo.Initialize().ConfigureAwait(false);
+
+ var userDataRepo = new SqliteUserDataRepository(LogManager, ApplicationPaths, NativeApp.GetDbConnector());
+
+ ((UserDataManager)UserDataManager).Repository = userDataRepo;
+ await itemRepo.Initialize(userDataRepo).ConfigureAwait(false);
((LibraryManager)LibraryManager).ItemRepository = ItemRepository;
await ConfigureNotificationsRepository().ConfigureAwait(false);
progress.Report(100);
@@ -623,14 +637,21 @@ namespace MediaBrowser.Server.Startup.Common
/// <returns>Task.</returns>
private async Task RegisterMediaEncoder(IProgress<double> progress)
{
- var info = await new FFMpegLoader(Logger, ApplicationPaths, HttpClient, ZipClient, FileSystemManager, NativeApp.Environment, NativeApp.GetType().Assembly, NativeApp.GetFfmpegInstallInfo())
+ string encoderPath = null;
+ string probePath = null;
+
+ var info = await new FFMpegLoader(Logger, ApplicationPaths, HttpClient, ZipClient, FileSystemManager, NativeApp.Environment, NativeApp.GetFfmpegInstallInfo())
.GetFFMpegInfo(NativeApp.Environment, _startupOptions, progress).ConfigureAwait(false);
+ encoderPath = info.EncoderPath;
+ probePath = info.ProbePath;
+ var hasExternalEncoder = string.Equals(info.Version, "external", StringComparison.OrdinalIgnoreCase);
+
var mediaEncoder = new MediaEncoder(LogManager.GetLogger("MediaEncoder"),
JsonSerializer,
- info.EncoderPath,
- info.ProbePath,
- info.Version,
+ encoderPath,
+ probePath,
+ hasExternalEncoder,
ServerConfigurationManager,
FileSystemManager,
LiveTvManager,
@@ -643,14 +664,6 @@ namespace MediaBrowser.Server.Startup.Common
MediaEncoder = mediaEncoder;
RegisterSingleInstance(MediaEncoder);
-
- Task.Run(() =>
- {
- var result = new FFmpegValidator(Logger, ApplicationPaths, FileSystemManager).Validate(info);
-
- mediaEncoder.SetAvailableDecoders(result.Item1);
- mediaEncoder.SetAvailableEncoders(result.Item2);
- });
}
/// <summary>
@@ -661,9 +674,9 @@ namespace MediaBrowser.Server.Startup.Common
{
try
{
- var repo = new SqliteUserRepository(LogManager, ApplicationPaths, JsonSerializer);
+ var repo = new SqliteUserRepository(LogManager, ApplicationPaths, JsonSerializer, NativeApp.GetDbConnector());
- await repo.Initialize(NativeApp.GetDbConnector()).ConfigureAwait(false);
+ await repo.Initialize().ConfigureAwait(false);
return repo;
}
@@ -680,36 +693,36 @@ namespace MediaBrowser.Server.Startup.Common
/// <returns>Task{IUserRepository}.</returns>
private async Task<IFileOrganizationRepository> GetFileOrganizationRepository()
{
- var repo = new SqliteFileOrganizationRepository(LogManager, ServerConfigurationManager.ApplicationPaths);
+ var repo = new SqliteFileOrganizationRepository(LogManager, ServerConfigurationManager.ApplicationPaths, NativeApp.GetDbConnector());
- await repo.Initialize(NativeApp.GetDbConnector()).ConfigureAwait(false);
+ await repo.Initialize().ConfigureAwait(false);
return repo;
}
private async Task<IAuthenticationRepository> GetAuthenticationRepository()
{
- var repo = new AuthenticationRepository(LogManager, ServerConfigurationManager.ApplicationPaths);
+ var repo = new AuthenticationRepository(LogManager, ServerConfigurationManager.ApplicationPaths, NativeApp.GetDbConnector());
- await repo.Initialize(NativeApp.GetDbConnector()).ConfigureAwait(false);
+ await repo.Initialize().ConfigureAwait(false);
return repo;
}
private async Task<IActivityRepository> GetActivityLogRepository()
{
- var repo = new ActivityRepository(LogManager, ServerConfigurationManager.ApplicationPaths);
+ var repo = new ActivityRepository(LogManager, ServerConfigurationManager.ApplicationPaths, NativeApp.GetDbConnector());
- await repo.Initialize(NativeApp.GetDbConnector()).ConfigureAwait(false);
+ await repo.Initialize().ConfigureAwait(false);
return repo;
}
private async Task<ISyncRepository> GetSyncRepository()
{
- var repo = new SyncRepository(LogManager, JsonSerializer, ServerConfigurationManager.ApplicationPaths);
+ var repo = new SyncRepository(LogManager, JsonSerializer, ServerConfigurationManager.ApplicationPaths, NativeApp.GetDbConnector());
- await repo.Initialize(NativeApp.GetDbConnector()).ConfigureAwait(false);
+ await repo.Initialize().ConfigureAwait(false);
return repo;
}
@@ -717,12 +730,11 @@ namespace MediaBrowser.Server.Startup.Common
/// <summary>
/// Configures the repositories.
/// </summary>
- /// <returns>Task.</returns>
private async Task ConfigureNotificationsRepository()
{
- var repo = new SqliteNotificationsRepository(LogManager, ApplicationPaths);
+ var repo = new SqliteNotificationsRepository(LogManager, ApplicationPaths, NativeApp.GetDbConnector());
- await repo.Initialize(NativeApp.GetDbConnector()).ConfigureAwait(false);
+ await repo.Initialize().ConfigureAwait(false);
NotificationsRepository = repo;
@@ -730,19 +742,6 @@ namespace MediaBrowser.Server.Startup.Common
}
/// <summary>
- /// Configures the user data repositories.
- /// </summary>
- /// <returns>Task.</returns>
- private async Task ConfigureUserDataRepositories()
- {
- var repo = new SqliteUserDataRepository(LogManager, ApplicationPaths);
-
- await repo.Initialize(NativeApp.GetDbConnector()).ConfigureAwait(false);
-
- ((UserDataManager)UserDataManager).Repository = repo;
- }
-
- /// <summary>
/// Dirty hacks
/// </summary>
private void SetStaticProperties()
@@ -797,15 +796,11 @@ namespace MediaBrowser.Server.Startup.Common
ProviderManager.AddParts(GetExports<IImageProvider>(),
GetExports<IMetadataService>(),
- GetExports<IItemIdentityProvider>(),
- GetExports<IItemIdentityConverter>(),
GetExports<IMetadataProvider>(),
GetExports<IMetadataSaver>(),
GetExports<IImageSaver>(),
GetExports<IExternalId>());
- SeriesOrderManager.AddParts(GetExports<ISeriesOrderProvider>());
-
ImageProcessor.AddParts(GetExports<IImageEnhancer>());
LiveTvManager.AddParts(GetExports<ILiveTvService>(), GetExports<ITunerHost>(), GetExports<IListingsProvider>());
@@ -825,19 +820,57 @@ namespace MediaBrowser.Server.Startup.Common
private string CertificatePath { get; set; }
+ private string NormalizeConfiguredLocalAddress(string address)
+ {
+ var index = address.Trim('/').IndexOf('/');
+
+ if (index != -1)
+ {
+ address = address.Substring(index + 1);
+ }
+
+ return address.Trim('/');
+ }
private IEnumerable<string> GetUrlPrefixes()
{
- var prefixes = new List<string>
- {
- "http://+:" + ServerConfigurationManager.Configuration.HttpServerPortNumber + "/"
- };
+ var hosts = ServerConfigurationManager
+ .Configuration
+ .LocalNetworkAddresses
+ .Select(NormalizeConfiguredLocalAddress)
+ .ToList();
- if (!string.IsNullOrWhiteSpace(CertificatePath))
+ if (hosts.Count == 0)
{
- prefixes.Add("https://+:" + ServerConfigurationManager.Configuration.HttpsPortNumber + "/");
+ hosts.Add("+");
}
- return prefixes;
+ if (!hosts.Contains("+", StringComparer.OrdinalIgnoreCase))
+ {
+ if (!hosts.Contains("localhost", StringComparer.OrdinalIgnoreCase))
+ {
+ hosts.Add("localhost");
+ }
+
+ if (!hosts.Contains("127.0.0.1", StringComparer.OrdinalIgnoreCase))
+ {
+ hosts.Add("127.0.0.1");
+ }
+ }
+
+ return hosts.SelectMany(i =>
+ {
+ var prefixes = new List<string>
+ {
+ "http://"+i+":" + ServerConfigurationManager.Configuration.HttpServerPortNumber + "/"
+ };
+
+ if (!string.IsNullOrWhiteSpace(CertificatePath))
+ {
+ prefixes.Add("https://" + i + ":" + ServerConfigurationManager.Configuration.HttpsPortNumber + "/");
+ }
+
+ return prefixes;
+ });
}
/// <summary>
@@ -1056,8 +1089,10 @@ namespace MediaBrowser.Server.Startup.Common
/// Gets the system status.
/// </summary>
/// <returns>SystemInfo.</returns>
- public virtual SystemInfo GetSystemInfo()
+ public async Task<SystemInfo> GetSystemInfo()
{
+ var localAddress = await GetLocalApiUrl().ConfigureAwait(false);
+
return new SystemInfo
{
HasPendingRestart = HasPendingRestart,
@@ -1088,8 +1123,10 @@ namespace MediaBrowser.Server.Startup.Common
IsRunningAsService = IsRunningAsService,
SupportsRunningAsService = SupportsRunningAsService,
ServerName = FriendlyName,
- LocalAddress = LocalApiUrl,
- SupportsLibraryMonitor = SupportsLibraryMonitor
+ LocalAddress = localAddress,
+ SupportsLibraryMonitor = SupportsLibraryMonitor,
+ EncoderLocationType = MediaEncoder.EncoderLocationType,
+ SystemArchitecture = NativeApp.Environment.SystemArchitecture
};
}
@@ -1106,29 +1143,26 @@ namespace MediaBrowser.Server.Startup.Common
get { return !string.IsNullOrWhiteSpace(HttpServer.CertificatePath); }
}
- public string LocalApiUrl
+ public async Task<string> GetLocalApiUrl()
{
- get
+ try
{
- try
- {
- // Return the first matched address, if found, or the first known local address
- var address = LocalIpAddresses.FirstOrDefault(i => !IPAddress.IsLoopback(i));
+ // Return the first matched address, if found, or the first known local address
+ var address = (await GetLocalIpAddresses().ConfigureAwait(false)).FirstOrDefault(i => !IPAddress.IsLoopback(i));
- if (address != null)
- {
- return GetLocalApiUrl(address);
- }
-
- return null;
- }
- catch (Exception ex)
+ if (address != null)
{
- Logger.ErrorException("Error getting local Ip address information", ex);
+ return GetLocalApiUrl(address);
}
return null;
}
+ catch (Exception ex)
+ {
+ Logger.ErrorException("Error getting local Ip address information", ex);
+ }
+
+ return null;
}
public string GetLocalApiUrl(IPAddress ipAddress)
@@ -1148,16 +1182,13 @@ namespace MediaBrowser.Server.Startup.Common
HttpPort.ToString(CultureInfo.InvariantCulture));
}
- public List<IPAddress> LocalIpAddresses
+ public async Task<List<IPAddress>> GetLocalIpAddresses()
{
- get
- {
- var localAddresses = NetworkManager.GetLocalIpAddresses()
- .Where(IsIpAddressValid)
- .ToList();
+ var localAddresses = NetworkManager.GetLocalIpAddresses()
+ .Where(IsIpAddressValid)
+ .ToList();
- return localAddresses;
- }
+ return localAddresses;
}
private readonly ConcurrentDictionary<string, bool> _validAddressResults = new ConcurrentDictionary<string, bool>(StringComparer.OrdinalIgnoreCase);
@@ -1343,7 +1374,6 @@ namespace MediaBrowser.Server.Startup.Common
/// <param name="package">The package that contains the update</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <param name="progress">The progress.</param>
- /// <returns>Task.</returns>
public override async Task UpdateApplication(PackageVersionInfo package, CancellationToken cancellationToken, IProgress<double> progress)
{
await InstallationManager.InstallPackage(package, false, progress, cancellationToken).ConfigureAwait(false);
diff --git a/MediaBrowser.Server.Startup.Common/FFMpeg/FFMpegInstallInfo.cs b/MediaBrowser.Server.Startup.Common/FFMpeg/FFMpegInstallInfo.cs
index 1ce1b55c2..a2a44f805 100644
--- a/MediaBrowser.Server.Startup.Common/FFMpeg/FFMpegInstallInfo.cs
+++ b/MediaBrowser.Server.Startup.Common/FFMpeg/FFMpegInstallInfo.cs
@@ -8,7 +8,6 @@ namespace MediaBrowser.Server.Startup.Common.FFMpeg
public string FFProbeFilename { get; set; }
public string ArchiveType { get; set; }
public string[] DownloadUrls { get; set; }
- public bool IsEmbedded { get; set; }
public FFMpegInstallInfo()
{
diff --git a/MediaBrowser.Server.Startup.Common/FFMpeg/FFMpegLoader.cs b/MediaBrowser.Server.Startup.Common/FFMpeg/FFMpegLoader.cs
index ee284fdc5..4c5759b56 100644
--- a/MediaBrowser.Server.Startup.Common/FFMpeg/FFMpegLoader.cs
+++ b/MediaBrowser.Server.Startup.Common/FFMpeg/FFMpegLoader.cs
@@ -24,7 +24,6 @@ namespace MediaBrowser.Server.Startup.Common.FFMpeg
private readonly IZipClient _zipClient;
private readonly IFileSystem _fileSystem;
private readonly NativeEnvironment _environment;
- private readonly Assembly _ownerAssembly;
private readonly FFMpegInstallInfo _ffmpegInstallInfo;
private readonly string[] _fontUrls =
@@ -32,7 +31,7 @@ namespace MediaBrowser.Server.Startup.Common.FFMpeg
"https://github.com/MediaBrowser/MediaBrowser.Resources/raw/master/ffmpeg/ARIALUNI.7z"
};
- public FFMpegLoader(ILogger logger, IApplicationPaths appPaths, IHttpClient httpClient, IZipClient zipClient, IFileSystem fileSystem, NativeEnvironment environment, Assembly ownerAssembly, FFMpegInstallInfo ffmpegInstallInfo)
+ public FFMpegLoader(ILogger logger, IApplicationPaths appPaths, IHttpClient httpClient, IZipClient zipClient, IFileSystem fileSystem, NativeEnvironment environment, FFMpegInstallInfo ffmpegInstallInfo)
{
_logger = logger;
_appPaths = appPaths;
@@ -40,7 +39,6 @@ namespace MediaBrowser.Server.Startup.Common.FFMpeg
_zipClient = zipClient;
_fileSystem = fileSystem;
_environment = environment;
- _ownerAssembly = ownerAssembly;
_ffmpegInstallInfo = ffmpegInstallInfo;
}
@@ -55,7 +53,7 @@ namespace MediaBrowser.Server.Startup.Common.FFMpeg
{
ProbePath = customffProbePath,
EncoderPath = customffMpegPath,
- Version = "custom"
+ Version = "external"
};
}
@@ -73,6 +71,11 @@ namespace MediaBrowser.Server.Startup.Common.FFMpeg
};
}
+ if (string.Equals(version, "0", StringComparison.OrdinalIgnoreCase))
+ {
+ return new FFMpegInfo();
+ }
+
var rootEncoderPath = Path.Combine(_appPaths.ProgramDataPath, "ffmpeg");
var versionedDirectoryPath = Path.Combine(rootEncoderPath, version);
@@ -95,18 +98,16 @@ namespace MediaBrowser.Server.Startup.Common.FFMpeg
// No older version. Need to download and block until complete
if (existingVersion == null)
{
- await DownloadFFMpeg(downloadInfo, versionedDirectoryPath, progress).ConfigureAwait(false);
+ var success = await DownloadFFMpeg(downloadInfo, versionedDirectoryPath, progress).ConfigureAwait(false);
+ if (!success)
+ {
+ return new FFMpegInfo();
+ }
}
else
{
- // Older version found.
- // Start with that. Download new version in the background.
- var newPath = versionedDirectoryPath;
- Task.Run(() => DownloadFFMpegInBackground(downloadInfo, newPath));
-
info = existingVersion;
versionedDirectoryPath = Path.GetDirectoryName(info.EncoderPath);
-
excludeFromDeletions.Add(versionedDirectoryPath);
}
}
@@ -183,36 +184,8 @@ namespace MediaBrowser.Server.Startup.Common.FFMpeg
return null;
}
- private async void DownloadFFMpegInBackground(FFMpegInstallInfo downloadinfo, string directory)
- {
- try
- {
- await DownloadFFMpeg(downloadinfo, directory, new Progress<double>()).ConfigureAwait(false);
- }
- catch (Exception ex)
- {
- _logger.ErrorException("Error downloading ffmpeg", ex);
- }
- }
-
- private async Task DownloadFFMpeg(FFMpegInstallInfo downloadinfo, string directory, IProgress<double> progress)
+ private async Task<bool> DownloadFFMpeg(FFMpegInstallInfo downloadinfo, string directory, IProgress<double> progress)
{
- if (downloadinfo.IsEmbedded)
- {
- var tempFile = Path.Combine(_appPaths.TempDirectory, Guid.NewGuid().ToString());
- _fileSystem.CreateDirectory(Path.GetDirectoryName(tempFile));
-
- using (var stream = _ownerAssembly.GetManifestResourceStream(downloadinfo.DownloadUrls[0]))
- {
- using (var fs = _fileSystem.GetFileStream(tempFile, FileMode.Create, FileAccess.Write, FileShare.Read, true))
- {
- await stream.CopyToAsync(fs).ConfigureAwait(false);
- }
- }
- ExtractFFMpeg(downloadinfo, tempFile, directory);
- return;
- }
-
foreach (var url in downloadinfo.DownloadUrls)
{
progress.Report(0);
@@ -228,20 +201,14 @@ namespace MediaBrowser.Server.Startup.Common.FFMpeg
}).ConfigureAwait(false);
ExtractFFMpeg(downloadinfo, tempFile, directory);
- return;
+ return true;
}
catch (Exception ex)
{
_logger.ErrorException("Error downloading {0}", ex, url);
}
}
-
- if (downloadinfo.DownloadUrls.Length == 0)
- {
- throw new ApplicationException("ffmpeg unvailable. Please install it and start the server with two command line arguments: -ffmpeg \"{PATH}\" and -ffprobe \"{PATH}\"");
- }
-
- throw new ApplicationException("Unable to download required components. Please try again later.");
+ return false;
}
private void ExtractFFMpeg(FFMpegInstallInfo downloadinfo, string tempFile, string targetFolder)
diff --git a/MediaBrowser.Server.Startup.Common/MediaBrowser.Server.Startup.Common.csproj b/MediaBrowser.Server.Startup.Common/MediaBrowser.Server.Startup.Common.csproj
index 19ce9ed9e..808d25fc9 100644
--- a/MediaBrowser.Server.Startup.Common/MediaBrowser.Server.Startup.Common.csproj
+++ b/MediaBrowser.Server.Startup.Common/MediaBrowser.Server.Startup.Common.csproj
@@ -68,14 +68,15 @@
<Compile Include="FFMpeg\FFMpegLoader.cs" />
<Compile Include="FFMpeg\FFMpegInstallInfo.cs" />
<Compile Include="FFMpeg\FFMpegInfo.cs" />
- <Compile Include="FFMpeg\FFmpegValidator.cs" />
<Compile Include="INativeApp.cs" />
<Compile Include="MbLinkShortcutHandler.cs" />
+ <Compile Include="Migrations\CollectionGroupingMigration.cs" />
+ <Compile Include="Migrations\CollectionsViewMigration.cs" />
+ <Compile Include="Migrations\FolderViewSettingMigration.cs" />
<Compile Include="Migrations\IVersionMigration.cs" />
<Compile Include="Migrations\DbMigration.cs" />
<Compile Include="Migrations\MovieDbEpisodeProviderMigration.cs" />
<Compile Include="Migrations\OmdbEpisodeProviderMigration.cs" />
- <Compile Include="Migrations\RenameXmlOptions.cs" />
<Compile Include="NativeEnvironment.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="StartupOptions.cs" />
diff --git a/MediaBrowser.Server.Startup.Common/Migrations/CollectionGroupingMigration.cs b/MediaBrowser.Server.Startup.Common/Migrations/CollectionGroupingMigration.cs
new file mode 100644
index 000000000..b497eeb42
--- /dev/null
+++ b/MediaBrowser.Server.Startup.Common/Migrations/CollectionGroupingMigration.cs
@@ -0,0 +1,44 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using MediaBrowser.Controller.Configuration;
+using MediaBrowser.Controller.Library;
+
+namespace MediaBrowser.Server.Startup.Common.Migrations
+{
+ public class CollectionGroupingMigration : IVersionMigration
+ {
+ private readonly IServerConfigurationManager _config;
+ private readonly IUserManager _userManager;
+
+ public CollectionGroupingMigration(IServerConfigurationManager config, IUserManager userManager)
+ {
+ _config = config;
+ _userManager = userManager;
+ }
+
+ public void Run()
+ {
+ var migrationKey = this.GetType().Name;
+ var migrationKeyList = _config.Configuration.Migrations.ToList();
+
+ if (!migrationKeyList.Contains(migrationKey))
+ {
+ if (_config.Configuration.IsStartupWizardCompleted)
+ {
+ if (_userManager.Users.Any(i => i.Configuration.GroupMoviesIntoBoxSets))
+ {
+ _config.Configuration.EnableGroupingIntoCollections = true;
+ }
+ }
+
+ migrationKeyList.Add(migrationKey);
+ _config.Configuration.Migrations = migrationKeyList.ToArray();
+ _config.SaveConfiguration();
+ }
+
+ }
+ }
+}
diff --git a/MediaBrowser.Server.Startup.Common/Migrations/CollectionsViewMigration.cs b/MediaBrowser.Server.Startup.Common/Migrations/CollectionsViewMigration.cs
new file mode 100644
index 000000000..c6186ce08
--- /dev/null
+++ b/MediaBrowser.Server.Startup.Common/Migrations/CollectionsViewMigration.cs
@@ -0,0 +1,44 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using MediaBrowser.Controller.Configuration;
+using MediaBrowser.Controller.Library;
+
+namespace MediaBrowser.Server.Startup.Common.Migrations
+{
+ public class CollectionsViewMigration : IVersionMigration
+ {
+ private readonly IServerConfigurationManager _config;
+ private readonly IUserManager _userManager;
+
+ public CollectionsViewMigration(IServerConfigurationManager config, IUserManager userManager)
+ {
+ _config = config;
+ _userManager = userManager;
+ }
+
+ public void Run()
+ {
+ var migrationKey = this.GetType().Name;
+ var migrationKeyList = _config.Configuration.Migrations.ToList();
+
+ if (!migrationKeyList.Contains(migrationKey))
+ {
+ if (_config.Configuration.IsStartupWizardCompleted)
+ {
+ if (_userManager.Users.Any(i => i.Configuration.DisplayCollectionsView))
+ {
+ _config.Configuration.DisplayCollectionsView = true;
+ }
+ }
+
+ migrationKeyList.Add(migrationKey);
+ _config.Configuration.Migrations = migrationKeyList.ToArray();
+ _config.SaveConfiguration();
+ }
+
+ }
+ }
+}
diff --git a/MediaBrowser.Server.Startup.Common/Migrations/DbMigration.cs b/MediaBrowser.Server.Startup.Common/Migrations/DbMigration.cs
index 65517c09c..f0cb9e84e 100644
--- a/MediaBrowser.Server.Startup.Common/Migrations/DbMigration.cs
+++ b/MediaBrowser.Server.Startup.Common/Migrations/DbMigration.cs
@@ -18,6 +18,7 @@ namespace MediaBrowser.Server.Startup.Common.Migrations
public void Run()
{
+ // If a forced migration is required, do that now
if (_config.Configuration.MigrationVersion < CleanDatabaseScheduledTask.MigrationVersion)
{
if (!_config.Configuration.IsStartupWizardCompleted)
@@ -36,6 +37,25 @@ namespace MediaBrowser.Server.Startup.Common.Migrations
_taskManager.Execute<CleanDatabaseScheduledTask>();
});
+
+ return;
+ }
+
+ if (_config.Configuration.SchemaVersion < SqliteItemRepository.LatestSchemaVersion)
+ {
+ if (!_config.Configuration.IsStartupWizardCompleted)
+ {
+ _config.Configuration.SchemaVersion = SqliteItemRepository.LatestSchemaVersion;
+ _config.SaveConfiguration();
+ return;
+ }
+
+ Task.Run(async () =>
+ {
+ await Task.Delay(1000).ConfigureAwait(false);
+
+ _taskManager.Execute<CleanDatabaseScheduledTask>();
+ });
}
}
}
diff --git a/MediaBrowser.Server.Startup.Common/Migrations/FolderViewSettingMigration.cs b/MediaBrowser.Server.Startup.Common/Migrations/FolderViewSettingMigration.cs
new file mode 100644
index 000000000..12054864b
--- /dev/null
+++ b/MediaBrowser.Server.Startup.Common/Migrations/FolderViewSettingMigration.cs
@@ -0,0 +1,40 @@
+using System.Linq;
+using MediaBrowser.Controller.Configuration;
+using MediaBrowser.Controller.Library;
+
+namespace MediaBrowser.Server.Startup.Common.Migrations
+{
+ public class FolderViewSettingMigration : IVersionMigration
+ {
+ private readonly IServerConfigurationManager _config;
+ private readonly IUserManager _userManager;
+
+ public FolderViewSettingMigration(IServerConfigurationManager config, IUserManager userManager)
+ {
+ _config = config;
+ _userManager = userManager;
+ }
+
+ public void Run()
+ {
+ var migrationKey = this.GetType().Name;
+ var migrationKeyList = _config.Configuration.Migrations.ToList();
+
+ if (!migrationKeyList.Contains(migrationKey))
+ {
+ if (_config.Configuration.IsStartupWizardCompleted)
+ {
+ if (_userManager.Users.Any(i => i.Configuration.DisplayFoldersView))
+ {
+ _config.Configuration.EnableFolderView = true;
+ }
+ }
+
+ migrationKeyList.Add(migrationKey);
+ _config.Configuration.Migrations = migrationKeyList.ToArray();
+ _config.SaveConfiguration();
+ }
+
+ }
+ }
+}
diff --git a/MediaBrowser.Server.Startup.Common/Migrations/RenameXmlOptions.cs b/MediaBrowser.Server.Startup.Common/Migrations/RenameXmlOptions.cs
deleted file mode 100644
index 49114b96f..000000000
--- a/MediaBrowser.Server.Startup.Common/Migrations/RenameXmlOptions.cs
+++ /dev/null
@@ -1,61 +0,0 @@
-using MediaBrowser.Controller.Configuration;
-using System;
-
-namespace MediaBrowser.Server.Startup.Common.Migrations
-{
- public class RenameXmlOptions : IVersionMigration
- {
- private readonly IServerConfigurationManager _config;
-
- public RenameXmlOptions(IServerConfigurationManager config)
- {
- _config = config;
- }
-
- public void Run()
- {
- var changed = false;
-
- foreach (var option in _config.Configuration.MetadataOptions)
- {
- if (Migrate(option.DisabledMetadataSavers))
- {
- changed = true;
- }
- if (Migrate(option.LocalMetadataReaderOrder))
- {
- changed = true;
- }
- }
-
- if (changed)
- {
- _config.SaveConfiguration();
- }
- }
-
- private bool Migrate(string[] options)
- {
- var changed = false;
-
- if (options != null)
- {
- for (var i = 0; i < options.Length; i++)
- {
- if (string.Equals(options[i], "Media Browser Legacy Xml", StringComparison.OrdinalIgnoreCase))
- {
- options[i] = "Emby Xml";
- changed = true;
- }
- else if (string.Equals(options[i], "Media Browser Xml", StringComparison.OrdinalIgnoreCase))
- {
- options[i] = "Emby Xml";
- changed = true;
- }
- }
- }
-
- return changed;
- }
- }
-}
diff --git a/MediaBrowser.Server.Startup.Common/NativeEnvironment.cs b/MediaBrowser.Server.Startup.Common/NativeEnvironment.cs
index 5b45afe73..b30509982 100644
--- a/MediaBrowser.Server.Startup.Common/NativeEnvironment.cs
+++ b/MediaBrowser.Server.Startup.Common/NativeEnvironment.cs
@@ -1,4 +1,5 @@
-
+using MediaBrowser.Model.System;
+
namespace MediaBrowser.Server.Startup.Common
{
public class NativeEnvironment
@@ -15,11 +16,4 @@ namespace MediaBrowser.Server.Startup.Common
Bsd = 2,
Linux = 3
}
-
- public enum Architecture
- {
- X86 = 0,
- X86_X64 = 1,
- Arm = 2
- }
}
diff --git a/MediaBrowser.ServerApplication/App.config b/MediaBrowser.ServerApplication/App.config
index a92d92300..6d840c191 100644
--- a/MediaBrowser.ServerApplication/App.config
+++ b/MediaBrowser.ServerApplication/App.config
@@ -52,6 +52,7 @@
<bindingRedirect oldVersion="0.0.0.0-2.3.6.0" newVersion="2.3.6.0"/>
</dependentAssembly>
</assemblyBinding>
+ <enforceFIPSPolicy enabled="false"/>
</runtime>
<system.web>
<membership defaultProvider="ClientAuthenticationMembershipProvider">
diff --git a/MediaBrowser.ServerApplication/BackgroundServiceInstaller.cs b/MediaBrowser.ServerApplication/BackgroundServiceInstaller.cs
index 08c8a25b9..be381fe96 100644
--- a/MediaBrowser.ServerApplication/BackgroundServiceInstaller.cs
+++ b/MediaBrowser.ServerApplication/BackgroundServiceInstaller.cs
@@ -25,7 +25,7 @@ namespace MediaBrowser.ServerApplication
Description = "The windows background service for Emby Server.",
// Will ensure the network is available
- ServicesDependedOn = new[] { "LanmanServer", "Tcpip" }
+ ServicesDependedOn = new[] { "LanmanServer", "EventLog", "Tcpip", "http" }
};
// Microsoft didn't add the ability to add a
diff --git a/MediaBrowser.ServerApplication/MainStartup.cs b/MediaBrowser.ServerApplication/MainStartup.cs
index 63d2cf30d..fb4fb86ff 100644
--- a/MediaBrowser.ServerApplication/MainStartup.cs
+++ b/MediaBrowser.ServerApplication/MainStartup.cs
@@ -256,10 +256,7 @@ namespace MediaBrowser.ServerApplication
{
Task.WaitAll(task);
- task = InstallVcredistIfNeeded(_appHost, _logger);
- Task.WaitAll(task);
-
- task = InstallFrameworkV46IfNeeded(_logger);
+ task = InstallVcredist2013IfNeeded(_appHost, _logger);
Task.WaitAll(task);
SystemEvents.SessionEnding += SystemEvents_SessionEnding;
@@ -594,94 +591,7 @@ namespace MediaBrowser.ServerApplication
}
}
- private static async Task InstallFrameworkV46IfNeeded(ILogger logger)
- {
- bool installFrameworkV46 = false;
-
- try
- {
- using (RegistryKey ndpKey = RegistryKey.OpenBaseKey(RegistryHive.LocalMachine, RegistryView.Registry32)
- .OpenSubKey("SOFTWARE\\Microsoft\\NET Framework Setup\\NDP\\v4\\Full\\"))
- {
- if (ndpKey != null && ndpKey.GetValue("Release") != null)
- {
- if ((int)ndpKey.GetValue("Release") <= 393295)
- {
- //Found framework V4, but not yet V4.6
- installFrameworkV46 = true;
- }
- }
- else
- {
- //Nothing found in the registry for V4
- installFrameworkV46 = true;
- }
- }
- }
- catch (Exception ex)
- {
- logger.ErrorException("Error getting .NET Framework version", ex);
- }
-
- _logger.Info(".NET Framework 4.6 found: {0}", !installFrameworkV46);
-
- if (installFrameworkV46)
- {
- try
- {
- await InstallFrameworkV46().ConfigureAwait(false);
- }
- catch (Exception ex)
- {
- logger.ErrorException("Error installing .NET Framework version 4.6", ex);
- }
- }
- }
-
- private static async Task InstallFrameworkV46()
- {
- var httpClient = _appHost.HttpClient;
-
- var tmp = await httpClient.GetTempFile(new HttpRequestOptions
- {
- Url = "https://github.com/MediaBrowser/Emby.Resources/raw/master/netframeworkV46/NDP46-KB3045560-Web.exe",
- Progress = new Progress<double>()
-
- }).ConfigureAwait(false);
-
- var exePath = Path.ChangeExtension(tmp, ".exe");
- File.Copy(tmp, exePath);
-
- var startInfo = new ProcessStartInfo
- {
- FileName = exePath,
-
- CreateNoWindow = true,
- WindowStyle = ProcessWindowStyle.Hidden,
- Verb = "runas",
- ErrorDialog = false,
- Arguments = "/q /norestart"
- };
-
-
- _logger.Info("Running {0}", startInfo.FileName);
-
- using (var process = Process.Start(startInfo))
- {
- process.WaitForExit();
- //process.ExitCode
- /*
- 0 --> Installation completed successfully.
- 1602 --> The user canceled installation.
- 1603 --> A fatal error occurred during installation.
- 1641 --> A restart is required to complete the installation. This message indicates success.
- 3010 --> A restart is required to complete the installation. This message indicates success.
- 5100 --> The user's computer does not meet system requirements.
- */
- }
- }
-
- private static async Task InstallVcredistIfNeeded(ApplicationHost appHost, ILogger logger)
+ private static async Task InstallVcredist2013IfNeeded(ApplicationHost appHost, ILogger logger)
{
try
{
@@ -695,7 +605,7 @@ namespace MediaBrowser.ServerApplication
try
{
- await InstallVcredist().ConfigureAwait(false);
+ await InstallVcredist2013().ConfigureAwait(false);
}
catch (Exception ex)
{
@@ -703,13 +613,13 @@ namespace MediaBrowser.ServerApplication
}
}
- private async static Task InstallVcredist()
+ private async static Task InstallVcredist2013()
{
var httpClient = _appHost.HttpClient;
var tmp = await httpClient.GetTempFile(new HttpRequestOptions
{
- Url = GetVcredistUrl(),
+ Url = GetVcredist2013Url(),
Progress = new Progress<double>()
}).ConfigureAwait(false);
@@ -735,7 +645,7 @@ namespace MediaBrowser.ServerApplication
}
}
- private static string GetVcredistUrl()
+ private static string GetVcredist2013Url()
{
if (Environment.Is64BitProcess)
{
diff --git a/MediaBrowser.ServerApplication/MediaBrowser.ServerApplication.csproj b/MediaBrowser.ServerApplication/MediaBrowser.ServerApplication.csproj
index 366d4b608..fcd6f7faf 100644
--- a/MediaBrowser.ServerApplication/MediaBrowser.ServerApplication.csproj
+++ b/MediaBrowser.ServerApplication/MediaBrowser.ServerApplication.csproj
@@ -83,8 +83,8 @@
<Reference Include="System.Configuration" />
<Reference Include="System.Configuration.Install" />
<Reference Include="System.Core" />
- <Reference Include="System.Data.SQLite, Version=1.0.101.0, Culture=neutral, PublicKeyToken=db937bc2d44ff139, processorArchitecture=MSIL">
- <HintPath>..\packages\System.Data.SQLite.Core.1.0.101.0\lib\net46\System.Data.SQLite.dll</HintPath>
+ <Reference Include="System.Data.SQLite, Version=1.0.102.0, Culture=neutral, PublicKeyToken=db937bc2d44ff139, processorArchitecture=MSIL">
+ <HintPath>..\packages\System.Data.SQLite.Core.1.0.102.0\lib\net46\System.Data.SQLite.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="System.Drawing" />
@@ -97,6 +97,9 @@
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
+ <Compile Include="..\MediaBrowser.Server.Implementations\Persistence\SqliteExtensions.cs">
+ <Link>Native\SqliteExtensions.cs</Link>
+ </Compile>
<Compile Include="..\SharedVersion.cs">
<Link>Properties\SharedVersion.cs</Link>
</Compile>
@@ -114,7 +117,7 @@
</Compile>
<Compile Include="MainStartup.cs" />
<Compile Include="Native\LnkShortcutHandler.cs" />
- <Compile Include="Native\SqliteExtensions.cs" />
+ <Compile Include="Native\DbConnector.cs" />
<Compile Include="Native\Standby.cs" />
<Compile Include="Native\ServerAuthorization.cs" />
<Compile Include="Native\WindowsApp.cs" />
@@ -1109,12 +1112,12 @@
<PostBuildEvent>
</PostBuildEvent>
</PropertyGroup>
- <Import Project="..\packages\System.Data.SQLite.Core.1.0.101.0\build\net46\System.Data.SQLite.Core.targets" Condition="Exists('..\packages\System.Data.SQLite.Core.1.0.101.0\build\net46\System.Data.SQLite.Core.targets')" />
+ <Import Project="..\packages\System.Data.SQLite.Core.1.0.102.0\build\net46\System.Data.SQLite.Core.targets" Condition="Exists('..\packages\System.Data.SQLite.Core.1.0.102.0\build\net46\System.Data.SQLite.Core.targets')" />
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
<PropertyGroup>
<ErrorText>This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText>
</PropertyGroup>
- <Error Condition="!Exists('..\packages\System.Data.SQLite.Core.1.0.101.0\build\net46\System.Data.SQLite.Core.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\System.Data.SQLite.Core.1.0.101.0\build\net46\System.Data.SQLite.Core.targets'))" />
+ <Error Condition="!Exists('..\packages\System.Data.SQLite.Core.1.0.102.0\build\net46\System.Data.SQLite.Core.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\System.Data.SQLite.Core.1.0.102.0\build\net46\System.Data.SQLite.Core.targets'))" />
</Target>
<!-- 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.ServerApplication/Native/DbConnector.cs b/MediaBrowser.ServerApplication/Native/DbConnector.cs
new file mode 100644
index 000000000..9aaa96a80
--- /dev/null
+++ b/MediaBrowser.ServerApplication/Native/DbConnector.cs
@@ -0,0 +1,24 @@
+using System;
+using System.Data;
+using System.Data.SQLite;
+using System.Threading.Tasks;
+using MediaBrowser.Model.Logging;
+using MediaBrowser.Server.Implementations.Persistence;
+
+namespace MediaBrowser.ServerApplication.Native
+{
+ public class DbConnector : IDbConnector
+ {
+ private readonly ILogger _logger;
+
+ public DbConnector(ILogger logger)
+ {
+ _logger = logger;
+ }
+
+ public Task<IDbConnection> Connect(string dbPath, bool isReadOnly, bool enablePooling = false, int? cacheSize = null)
+ {
+ return SqliteExtensions.ConnectToDb(dbPath, isReadOnly, enablePooling, cacheSize, _logger);
+ }
+ }
+} \ No newline at end of file
diff --git a/MediaBrowser.ServerApplication/Native/SqliteExtensions.cs b/MediaBrowser.ServerApplication/Native/SqliteExtensions.cs
deleted file mode 100644
index 1cde2ea13..000000000
--- a/MediaBrowser.ServerApplication/Native/SqliteExtensions.cs
+++ /dev/null
@@ -1,62 +0,0 @@
-using System;
-using System.Data;
-using System.Data.SQLite;
-using System.Threading.Tasks;
-using MediaBrowser.Model.Logging;
-using MediaBrowser.Server.Implementations.Persistence;
-
-namespace MediaBrowser.ServerApplication.Native
-{
- /// <summary>
- /// Class SQLiteExtensions
- /// </summary>
- static class SqliteExtensions
- {
- /// <summary>
- /// Connects to db.
- /// </summary>
- /// <param name="dbPath">The db path.</param>
- /// <param name="logger">The logger.</param>
- /// <returns>Task{IDbConnection}.</returns>
- /// <exception cref="System.ArgumentNullException">dbPath</exception>
- public static async Task<IDbConnection> ConnectToDb(string dbPath, ILogger logger)
- {
- if (string.IsNullOrEmpty(dbPath))
- {
- throw new ArgumentNullException("dbPath");
- }
-
- logger.Info("Sqlite {0} opening {1}", SQLiteConnection.SQLiteVersion, dbPath);
-
- var connectionstr = new SQLiteConnectionStringBuilder
- {
- PageSize = 4096,
- CacheSize = 2000,
- SyncMode = SynchronizationModes.Full,
- DataSource = dbPath,
- JournalMode = SQLiteJournalModeEnum.Wal
- };
-
- var connection = new SQLiteConnection(connectionstr.ConnectionString);
-
- await connection.OpenAsync().ConfigureAwait(false);
-
- return connection;
- }
- }
-
- public class DbConnector : IDbConnector
- {
- private readonly ILogger _logger;
-
- public DbConnector(ILogger logger)
- {
- _logger = logger;
- }
-
- public Task<IDbConnection> Connect(string dbPath)
- {
- return SqliteExtensions.ConnectToDb(dbPath, _logger);
- }
- }
-} \ No newline at end of file
diff --git a/MediaBrowser.ServerApplication/Native/WindowsApp.cs b/MediaBrowser.ServerApplication/Native/WindowsApp.cs
index c99b0f9af..d8b2720c2 100644
--- a/MediaBrowser.ServerApplication/Native/WindowsApp.cs
+++ b/MediaBrowser.ServerApplication/Native/WindowsApp.cs
@@ -10,6 +10,7 @@ using System.Reflection;
using System.Windows.Forms;
using CommonIO;
using MediaBrowser.Controller.Power;
+using MediaBrowser.Model.System;
using MediaBrowser.Server.Implementations.Persistence;
using MediaBrowser.Server.Startup.Common.FFMpeg;
using OperatingSystem = MediaBrowser.Server.Startup.Common.OperatingSystem;
@@ -53,7 +54,7 @@ namespace MediaBrowser.ServerApplication.Native
return new NativeEnvironment
{
OperatingSystem = OperatingSystem.Windows,
- SystemArchitecture = System.Environment.Is64BitOperatingSystem ? Architecture.X86_X64 : Architecture.X86,
+ SystemArchitecture = System.Environment.Is64BitOperatingSystem ? Architecture.X64 : Architecture.X86,
OperatingSystemVersionString = System.Environment.OSVersion.VersionString
};
}
@@ -158,10 +159,7 @@ namespace MediaBrowser.ServerApplication.Native
info.FFMpegFilename = "ffmpeg.exe";
info.FFProbeFilename = "ffprobe.exe";
- info.Version = "20160410";
- info.ArchiveType = "7z";
- info.IsEmbedded = false;
- info.DownloadUrls = GetDownloadUrls();
+ info.Version = "0";
return info;
}
@@ -206,23 +204,5 @@ namespace MediaBrowser.ServerApplication.Native
{
((Process)sender).Dispose();
}
-
- private string[] GetDownloadUrls()
- {
- switch (Environment.SystemArchitecture)
- {
- case Architecture.X86_X64:
- return new[]
- {
- "https://ffmpeg.zeranoe.com/builds/win64/static/ffmpeg-20160409-git-0c90b2e-win64-static.7z"
- };
- case Architecture.X86:
- return new[]
- {
- "https://ffmpeg.zeranoe.com/builds/win32/static/ffmpeg-20160409-git-0c90b2e-win32-static.7z"
- };
- }
- return new string[] { };
- }
}
-}
+} \ No newline at end of file
diff --git a/MediaBrowser.ServerApplication/Networking/NetworkManager.cs b/MediaBrowser.ServerApplication/Networking/NetworkManager.cs
index cc9061fcd..ed60de9d2 100644
--- a/MediaBrowser.ServerApplication/Networking/NetworkManager.cs
+++ b/MediaBrowser.ServerApplication/Networking/NetworkManager.cs
@@ -89,19 +89,21 @@ namespace MediaBrowser.ServerApplication.Networking
/// </summary>
/// <returns>Arraylist that represents all the SV_TYPE_WORKSTATION and SV_TYPE_SERVER
/// PC's in the Domain</returns>
- private IEnumerable<string> GetNetworkDevicesInternal()
+ private List<string> GetNetworkDevicesInternal()
{
//local fields
const int MAX_PREFERRED_LENGTH = -1;
var SV_TYPE_WORKSTATION = 1;
var SV_TYPE_SERVER = 2;
- var buffer = IntPtr.Zero;
- var tmpBuffer = IntPtr.Zero;
+ IntPtr buffer = IntPtr.Zero;
+ IntPtr tmpBuffer = IntPtr.Zero;
var entriesRead = 0;
var totalEntries = 0;
var resHandle = 0;
var sizeofINFO = Marshal.SizeOf(typeof(_SERVER_INFO_100));
+ var returnList = new List<string>();
+
try
{
//call the DllImport : NetServerEnum with all its required parameters
@@ -118,7 +120,7 @@ namespace MediaBrowser.ServerApplication.Networking
//get pointer to, Pointer to the buffer that received the data from
//the call to NetServerEnum. Must ensure to use correct size of
//STRUCTURE to ensure correct location in memory is pointed to
- tmpBuffer = new IntPtr((int)buffer + (i * sizeofINFO));
+ tmpBuffer = new IntPtr((Int64)buffer + (i * sizeofINFO));
//Have now got a pointer to the list of SV_TYPE_WORKSTATION and
//SV_TYPE_SERVER PC's, which is unmanaged memory
//Needs to Marshal data from an unmanaged block of memory to a
@@ -129,7 +131,7 @@ namespace MediaBrowser.ServerApplication.Networking
//add the PC names to the ArrayList
if (!string.IsNullOrEmpty(svrInfo.sv100_name))
{
- yield return svrInfo.sv100_name;
+ returnList.Add(svrInfo.sv100_name);
}
}
}
@@ -140,6 +142,8 @@ namespace MediaBrowser.ServerApplication.Networking
//the memory that the NetApiBufferAllocate function allocates
NativeMethods.NetApiBufferFree(buffer);
}
+
+ return returnList;
}
/// <summary>
diff --git a/MediaBrowser.ServerApplication/packages.config b/MediaBrowser.ServerApplication/packages.config
index 2d4baae25..a573c6fd9 100644
--- a/MediaBrowser.ServerApplication/packages.config
+++ b/MediaBrowser.ServerApplication/packages.config
@@ -3,5 +3,5 @@
<package id="CommonIO" version="1.0.0.9" targetFramework="net45" />
<package id="ImageMagickSharp" version="1.0.0.18" targetFramework="net45" />
<package id="Patterns.Logging" version="1.0.0.2" targetFramework="net45" />
- <package id="System.Data.SQLite.Core" version="1.0.101.0" targetFramework="net46" />
+ <package id="System.Data.SQLite.Core" version="1.0.102.0" targetFramework="net46" />
</packages> \ No newline at end of file
diff --git a/MediaBrowser.WebDashboard/Api/DashboardService.cs b/MediaBrowser.WebDashboard/Api/DashboardService.cs
index 1569c1fc5..1a227126e 100644
--- a/MediaBrowser.WebDashboard/Api/DashboardService.cs
+++ b/MediaBrowser.WebDashboard/Api/DashboardService.cs
@@ -133,7 +133,7 @@ namespace MediaBrowser.WebDashboard.Api
/// </summary>
/// <param name="request">The request.</param>
/// <returns>System.Object.</returns>
- public object Get(GetDashboardConfigurationPage request)
+ public Task<object> Get(GetDashboardConfigurationPage request)
{
var page = ServerEntryPoint.Instance.PluginConfigurationPages.First(p => p.Name.Equals(request.Name, StringComparison.OrdinalIgnoreCase));
@@ -201,7 +201,7 @@ namespace MediaBrowser.WebDashboard.Api
/// </summary>
/// <param name="request">The request.</param>
/// <returns>System.Object.</returns>
- public object Get(GetDashboardResource request)
+ public async Task<object> Get(GetDashboardResource request)
{
var path = request.ResourceName;
@@ -230,7 +230,8 @@ namespace MediaBrowser.WebDashboard.Api
!contentType.StartsWith("image/", StringComparison.OrdinalIgnoreCase) &&
!contentType.StartsWith("font/", StringComparison.OrdinalIgnoreCase))
{
- return ResultFactory.GetResult(GetResourceStream(path, localizationCulture).Result, contentType);
+ var stream = await GetResourceStream(path, localizationCulture).ConfigureAwait(false);
+ return ResultFactory.GetResult(stream, contentType);
}
TimeSpan? cacheDuration = null;
@@ -246,7 +247,7 @@ namespace MediaBrowser.WebDashboard.Api
var cacheKey = (assembly.Version + (localizationCulture ?? string.Empty) + path).GetMD5();
- return ResultFactory.GetStaticResult(Request, cacheKey, null, cacheDuration, contentType, () => GetResourceStream(path, localizationCulture));
+ return await ResultFactory.GetStaticResult(Request, cacheKey, null, cacheDuration, contentType, () => GetResourceStream(path, localizationCulture)).ConfigureAwait(false);
}
private string GetLocalizationCulture()
@@ -342,7 +343,9 @@ namespace MediaBrowser.WebDashboard.Api
if (string.Equals(mode, "cordova", StringComparison.OrdinalIgnoreCase))
{
- DeleteFoldersByName(Path.Combine(bowerPath, "emby-webcomponents"), "fonts");
+ DeleteFoldersByName(Path.Combine(bowerPath, "emby-webcomponents", "fonts"), "montserrat");
+ DeleteFoldersByName(Path.Combine(bowerPath, "emby-webcomponents", "fonts"), "opensans");
+ DeleteFoldersByName(Path.Combine(bowerPath, "emby-webcomponents", "fonts"), "roboto");
}
_fileSystem.DeleteDirectory(Path.Combine(bowerPath, "jquery", "src"), true);
@@ -356,9 +359,6 @@ namespace MediaBrowser.WebDashboard.Api
DeleteFoldersByName(Path.Combine(bowerPath, "Sortable"), "meteor");
DeleteFoldersByName(Path.Combine(bowerPath, "Sortable"), "st");
DeleteFoldersByName(Path.Combine(bowerPath, "Swiper"), "src");
- DeleteFoldersByName(Path.Combine(bowerPath, "material-design-lite"), "src");
- DeleteFoldersByName(Path.Combine(bowerPath, "material-design-lite"), "utils");
- _fileSystem.DeleteFile(Path.Combine(bowerPath, "material-design-lite", "gulpfile.babel.js"));
_fileSystem.DeleteDirectory(Path.Combine(bowerPath, "marked"), true);
_fileSystem.DeleteDirectory(Path.Combine(bowerPath, "marked-element"), true);
@@ -524,18 +524,6 @@ namespace MediaBrowser.WebDashboard.Api
await DumpFile(filename, Path.Combine(destination, filename), mode, culture, appVersion).ConfigureAwait(false);
}
-
- var excludeFiles = new List<string>();
-
- if (string.Equals(mode, "cordova", StringComparison.OrdinalIgnoreCase))
- {
- excludeFiles.Add("supporterkey.html");
- }
-
- foreach (var file in excludeFiles)
- {
- _fileSystem.DeleteFile(Path.Combine(destination, file));
- }
}
private async Task DumpFile(string resourceVirtualPath, string destinationFilePath, string mode, string culture, string appVersion)
diff --git a/MediaBrowser.WebDashboard/Api/PackageCreator.cs b/MediaBrowser.WebDashboard/Api/PackageCreator.cs
index d8de98abe..4c353c413 100644
--- a/MediaBrowser.WebDashboard/Api/PackageCreator.cs
+++ b/MediaBrowser.WebDashboard/Api/PackageCreator.cs
@@ -258,7 +258,6 @@ namespace MediaBrowser.WebDashboard.Api
if (string.Equals(mode, "cordova", StringComparison.OrdinalIgnoreCase))
{
- html = ModifyForCordova(html);
}
else if (!string.IsNullOrWhiteSpace(path) && !string.Equals(path, "index.html", StringComparison.OrdinalIgnoreCase))
{
@@ -274,7 +273,7 @@ namespace MediaBrowser.WebDashboard.Api
}
var mainFile = File.ReadAllText(GetDashboardResourcePath("index.html"));
- html = ReplaceFirst(mainFile, "<div class=\"mainAnimatedPage hide\"></div>", "<div class=\"mainAnimatedPage hide\">" + html + "</div>");
+ html = ReplaceFirst(mainFile, "<div class=\"mainAnimatedPages skinBody\"></div>", "<div class=\"mainAnimatedPages skinBody hide\">" + html + "</div>");
}
if (!string.IsNullOrWhiteSpace(localizationCulture))
@@ -339,41 +338,6 @@ namespace MediaBrowser.WebDashboard.Api
return text.Substring(0, pos) + replace + text.Substring(pos + search.Length);
}
- private string ModifyForCordova(string html)
- {
- // Replace CORDOVA_REPLACE_SUPPORTER_SUBMIT_START
- html = ReplaceBetween(html, "<!--CORDOVA_REPLACE_SUPPORTER_SUBMIT_START-->", "<!--CORDOVA_REPLACE_SUPPORTER_SUBMIT_END-->", "<i class=\"fa fa-check\"></i><span>${ButtonPurchase}</span>");
-
- return html;
- }
-
- private string ReplaceBetween(string html, string startToken, string endToken, string newHtml)
- {
- var start = html.IndexOf(startToken, StringComparison.OrdinalIgnoreCase);
-
- if (start == -1)
- {
- return html;
- }
-
- var end = html.IndexOf(endToken, start, StringComparison.OrdinalIgnoreCase);
-
- if (end == -1)
- {
- return html;
- }
-
- string result = html.Substring(start, end - start);
- html = html.Replace(result, newHtml);
-
- return ReplaceBetween(html, startToken, endToken, newHtml);
- }
-
- private string GetLocalizationToken(string phrase)
- {
- return "${" + phrase + "}";
- }
-
/// <summary>
/// Gets the meta tags.
/// </summary>
diff --git a/MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj b/MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj
index e85f5a27b..9c786dbae 100644
--- a/MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj
+++ b/MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj
@@ -104,6 +104,9 @@
<Content Include="dashboard-ui\components\apphost.js">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
+ <Content Include="dashboard-ui\components\channelmapper\channelmapper.js">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
<Content Include="dashboard-ui\components\chromecasthelpers.js">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
@@ -125,16 +128,16 @@
<Content Include="dashboard-ui\components\guestinviter\guestinviter.template.html">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
- <Content Include="dashboard-ui\components\ironcardlist\ironcardlist.template.html">
+ <Content Include="dashboard-ui\components\metadataeditor\metadataeditor.js">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
- <Content Include="dashboard-ui\components\ironcardlist\ironcardlist.js">
+ <Content Include="dashboard-ui\components\metadataeditor\personeditor.template.html">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
- <Content Include="dashboard-ui\components\metadataeditor\metadataeditor.js">
+ <Content Include="dashboard-ui\components\navdrawer\navdrawer.css">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
- <Content Include="dashboard-ui\components\metadataeditor\personeditor.template.html">
+ <Content Include="dashboard-ui\components\navdrawer\navdrawer.js">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="dashboard-ui\components\remotecontrol.js">
@@ -143,16 +146,22 @@
<Content Include="dashboard-ui\components\remotecontrolautoplay.js">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
- <Content Include="dashboard-ui\components\scrollthreshold.js">
+ <Content Include="dashboard-ui\components\tvproviders\xmltv.js">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\components\tvproviders\xmltv.template.html">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\scripts\userpasswordpage.js">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="dashboard-ui\components\viewcontainer-lite.js">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
- <Content Include="dashboard-ui\css\images\logo.png">
+ <Content Include="dashboard-ui\css\dashboard.css">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
- <Content Include="dashboard-ui\css\polymer\paper-icon-button-light.css">
+ <Content Include="dashboard-ui\css\images\logo.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="dashboard-ui\devices\windowsphone\wp.css">
@@ -167,9 +176,6 @@
<Content Include="dashboard-ui\legacy\buttonenabled.js">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
- <Content Include="dashboard-ui\components\collectioneditor\collectioneditor.js">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
<Content Include="dashboard-ui\components\directorybrowser\directorybrowser.js">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
@@ -218,12 +224,6 @@
<Content Include="dashboard-ui\components\metadataeditor\metadataeditor.template.html">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
- <Content Include="dashboard-ui\components\playlisteditor\playlisteditor.js">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\devices\ie\ie.css">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
<Content Include="dashboard-ui\css\images\ani_equalizer_black.gif">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
@@ -266,9 +266,6 @@
<Content Include="dashboard-ui\components\imageeditor\imageeditor.template.html">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
- <Content Include="dashboard-ui\devices\ie\ie.js">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
<Content Include="dashboard-ui\favorites.html">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
@@ -278,6 +275,9 @@
<Content Include="dashboard-ui\legacy\selectmenu.js">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
+ <Content Include="dashboard-ui\librarydisplay.html">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
<Content Include="dashboard-ui\livetvguideprovider.html">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
@@ -314,6 +314,9 @@
<Content Include="dashboard-ui\scripts\homeupcoming.js">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
+ <Content Include="dashboard-ui\scripts\librarydisplay.js">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
<Content Include="dashboard-ui\scripts\livetvguideprovider.js">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
@@ -344,9 +347,6 @@
<Content Include="dashboard-ui\scripts\autoorganizesmart.js">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
- <Content Include="dashboard-ui\scripts\searchmenu.js">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
<Content Include="dashboard-ui\scripts\searchpage.js">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
@@ -362,6 +362,15 @@
<Content Include="dashboard-ui\scripts\supporterkeypage.js">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
+ <Content Include="dashboard-ui\scripts\tvlatest.js">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\scripts\wizardcomponents.js">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\scripts\wizardcontroller.js">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
<Content Include="dashboard-ui\scripts\wizardlivetvguide.js">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
@@ -380,9 +389,6 @@
<Content Include="dashboard-ui\shared.html">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
- <Content Include="dashboard-ui\components\subtitleeditor\subtitleeditor.template.html">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
<Content Include="dashboard-ui\devices\android\android.css">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
@@ -470,9 +476,6 @@
<Content Include="dashboard-ui\forgotpasswordpin.html">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
- <Content Include="dashboard-ui\kids.html">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
<Content Include="dashboard-ui\livetvitems.html">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
@@ -494,9 +497,6 @@
<Content Include="dashboard-ui\scripts\forgotpasswordpin.js">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
- <Content Include="dashboard-ui\scripts\kids.js">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
<Content Include="dashboard-ui\scripts\livetvcomponents.js">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
@@ -752,9 +752,6 @@
<Content Include="dashboard-ui\metadatasubtitles.html">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
- <Content Include="dashboard-ui\collections.html">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
<Content Include="dashboard-ui\css\images\editor\missing.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
@@ -818,9 +815,6 @@
<Content Include="dashboard-ui\css\librarybrowser.css">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
- <Content Include="dashboard-ui\css\search.css">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
<Content Include="dashboard-ui\css\tileitem.css">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
@@ -893,9 +887,6 @@
<Content Include="dashboard-ui\scripts\dlnasettings.js">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
- <Content Include="dashboard-ui\components\subtitleeditor\subtitleeditor.js">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
<Content Include="dashboard-ui\scripts\encodingsettings.js">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
@@ -938,9 +929,6 @@
<Content Include="dashboard-ui\scripts\playlistedit.js">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
- <Content Include="dashboard-ui\scripts\playlistmanager.js">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
<Content Include="dashboard-ui\scripts\playlists.js">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
@@ -971,9 +959,6 @@
<Content Include="dashboard-ui\scripts\metadatasubtitles.js">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
- <Content Include="dashboard-ui\scripts\musicalbumartists.js">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
<Content Include="dashboard-ui\scripts\livetvchannels.js">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
@@ -1097,6 +1082,9 @@
<Content Include="dashboard-ui\wizardagreement.html">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
+ <Content Include="dashboard-ui\wizardcomponents.html">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
<Content Include="dashboard-ui\wizardlivetvguide.html">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
@@ -1131,9 +1119,6 @@
<Content Include="dashboard-ui\music.html">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
- <Content Include="dashboard-ui\scripts\alphapicker.js">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
<Content Include="dashboard-ui\components\imageeditor\imageeditor.js">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
@@ -1152,12 +1137,6 @@
<Content Include="dashboard-ui\scripts\remotecontrol.js">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
- <Content Include="dashboard-ui\scripts\search.js">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\scripts\tvlatest.js">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
<Content Include="dashboard-ui\scripts\moviecollections.js">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
diff --git a/MediaBrowser.XbmcMetadata/Parsers/BaseNfoParser.cs b/MediaBrowser.XbmcMetadata/Parsers/BaseNfoParser.cs
index d020a73fe..2e34135a6 100644
--- a/MediaBrowser.XbmcMetadata/Parsers/BaseNfoParser.cs
+++ b/MediaBrowser.XbmcMetadata/Parsers/BaseNfoParser.cs
@@ -919,11 +919,7 @@ namespace MediaBrowser.XbmcMetadata.Parsers
var val = reader.ReadElementContentAsString();
if (!string.IsNullOrWhiteSpace(val))
{
- var hasTags = item as IHasTags;
- if (hasTags != null)
- {
- hasTags.AddTag(val);
- }
+ item.AddTag(val);
}
break;
}
@@ -932,13 +928,9 @@ namespace MediaBrowser.XbmcMetadata.Parsers
{
var val = reader.ReadElementContentAsString();
- var hasKeywords = item as IHasKeywords;
- if (hasKeywords != null)
+ if (!string.IsNullOrWhiteSpace(val))
{
- if (!string.IsNullOrWhiteSpace(val))
- {
- hasKeywords.AddKeyword(val);
- }
+ item.AddKeyword(val);
}
break;
}
diff --git a/MediaBrowser.XbmcMetadata/Parsers/EpisodeNfoParser.cs b/MediaBrowser.XbmcMetadata/Parsers/EpisodeNfoParser.cs
index 100ce7af9..6e3114fa1 100644
--- a/MediaBrowser.XbmcMetadata/Parsers/EpisodeNfoParser.cs
+++ b/MediaBrowser.XbmcMetadata/Parsers/EpisodeNfoParser.cs
@@ -188,6 +188,48 @@ namespace MediaBrowser.XbmcMetadata.Parsers
break;
}
+ case "displayseason":
+ {
+ 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))
+ {
+ if ((item.ParentIndexNumber ?? 0) == 0)
+ {
+ item.AirsBeforeSeasonNumber = rval;
+ }
+ }
+ }
+
+ break;
+ }
+
+ case "displayepisode":
+ {
+ 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))
+ {
+ if ((item.ParentIndexNumber ?? 0) == 0)
+ {
+ item.AirsBeforeEpisodeNumber = rval;
+ }
+ }
+ }
+
+ break;
+ }
+
default:
base.FetchDataFromXmlNode(reader, itemResult);
diff --git a/MediaBrowser.XbmcMetadata/Savers/BaseNfoSaver.cs b/MediaBrowser.XbmcMetadata/Savers/BaseNfoSaver.cs
index d2e09d4eb..42f0a3364 100644
--- a/MediaBrowser.XbmcMetadata/Savers/BaseNfoSaver.cs
+++ b/MediaBrowser.XbmcMetadata/Savers/BaseNfoSaver.cs
@@ -736,29 +736,21 @@ namespace MediaBrowser.XbmcMetadata.Savers
writer.WriteElementString("studio", studio);
}
- var hasTags = item as IHasTags;
- if (hasTags != null)
+ foreach (var tag in item.Tags)
{
- foreach (var tag in hasTags.Tags)
+ if (item is MusicAlbum || item is MusicArtist)
{
- if (item is MusicAlbum || item is MusicArtist)
- {
- writer.WriteElementString("style", tag);
- }
- else
- {
- writer.WriteElementString("tag", tag);
- }
+ writer.WriteElementString("style", tag);
+ }
+ else
+ {
+ writer.WriteElementString("tag", tag);
}
}
- var hasKeywords = item as IHasKeywords;
- if (hasKeywords != null)
+ foreach (var tag in item.Keywords)
{
- foreach (var tag in hasKeywords.Keywords)
- {
- writer.WriteElementString("plotkeyword", tag);
- }
+ writer.WriteElementString("plotkeyword", tag);
}
var hasAwards = item as IHasAwards;
diff --git a/MediaBrowser.XbmcMetadata/Savers/EpisodeNfoSaver.cs b/MediaBrowser.XbmcMetadata/Savers/EpisodeNfoSaver.cs
index 60d024072..7523ce6bf 100644
--- a/MediaBrowser.XbmcMetadata/Savers/EpisodeNfoSaver.cs
+++ b/MediaBrowser.XbmcMetadata/Savers/EpisodeNfoSaver.cs
@@ -72,11 +72,21 @@ namespace MediaBrowser.XbmcMetadata.Savers
{
writer.WriteElementString("airsbefore_episode", episode.AirsBeforeEpisodeNumber.Value.ToString(UsCulture));
}
+ if (episode.AirsBeforeEpisodeNumber.HasValue)
+ {
+ writer.WriteElementString("displayepisode", episode.AirsBeforeEpisodeNumber.Value.ToString(UsCulture));
+ }
if (episode.AirsBeforeSeasonNumber.HasValue)
{
writer.WriteElementString("airsbefore_season", episode.AirsBeforeSeasonNumber.Value.ToString(UsCulture));
}
+ var season = episode.AiredSeasonNumber;
+ if (season.HasValue)
+ {
+ writer.WriteElementString("displayseason", season.Value.ToString(UsCulture));
+ }
+
if (episode.DvdEpisodeNumber.HasValue)
{
writer.WriteElementString("DVD_episodenumber", episode.DvdEpisodeNumber.Value.ToString(UsCulture));
diff --git a/MediaBrowser.XbmcMetadata/Savers/MovieNfoSaver.cs b/MediaBrowser.XbmcMetadata/Savers/MovieNfoSaver.cs
index 18423f59e..63445b9c4 100644
--- a/MediaBrowser.XbmcMetadata/Savers/MovieNfoSaver.cs
+++ b/MediaBrowser.XbmcMetadata/Savers/MovieNfoSaver.cs
@@ -44,6 +44,13 @@ namespace MediaBrowser.XbmcMetadata.Savers
}
else
{
+ // http://kodi.wiki/view/NFO_files/Movies
+ // movie.nfo will override all and any .nfo files in the same folder as the media files if you use the "Use foldernames for lookups" setting. If you don't, then moviename.nfo is used
+ //if (!item.IsInMixedFolder && item.ItemType == typeof(Movie))
+ //{
+ // list.Add(Path.Combine(item.ContainingFolderPath, "movie.nfo"));
+ //}
+
list.Add(Path.ChangeExtension(item.Path, ".nfo"));
}
diff --git a/Nuget/MediaBrowser.Common.Internal.nuspec b/Nuget/MediaBrowser.Common.Internal.nuspec
index 56429c6a6..dc92b318e 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.648</version>
+ <version>3.0.652</version>
<title>MediaBrowser.Common.Internal</title>
<authors>Luke</authors>
<owners>ebr,Luke,scottisafool</owners>
@@ -12,9 +12,9 @@
<description>Contains common components shared by Emby Theater and Emby Server. Not intended for plugin developer consumption.</description>
<copyright>Copyright © Emby 2013</copyright>
<dependencies>
- <dependency id="MediaBrowser.Common" version="3.0.648" />
- <dependency id="NLog" version="4.3.4" />
- <dependency id="SimpleInjector" version="3.1.4" />
+ <dependency id="MediaBrowser.Common" version="3.0.652" />
+ <dependency id="NLog" version="4.3.5" />
+ <dependency id="SimpleInjector" version="3.2.0" />
</dependencies>
</metadata>
<files>
diff --git a/Nuget/MediaBrowser.Common.nuspec b/Nuget/MediaBrowser.Common.nuspec
index d12984869..a8b78b4e3 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.648</version>
+ <version>3.0.652</version>
<title>MediaBrowser.Common</title>
<authors>Emby Team</authors>
<owners>ebr,Luke,scottisafool</owners>
diff --git a/Nuget/MediaBrowser.Server.Core.nuspec b/Nuget/MediaBrowser.Server.Core.nuspec
index a9caab74c..105d2c478 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.648</version>
+ <version>3.0.652</version>
<title>Media Browser.Server.Core</title>
<authors>Emby Team</authors>
<owners>ebr,Luke,scottisafool</owners>
@@ -12,7 +12,7 @@
<description>Contains core components required to build plugins for Emby Server.</description>
<copyright>Copyright © Emby 2013</copyright>
<dependencies>
- <dependency id="MediaBrowser.Common" version="3.0.648" />
+ <dependency id="MediaBrowser.Common" version="3.0.652" />
<dependency id="Interfaces.IO" version="1.0.0.5" />
</dependencies>
</metadata>
diff --git a/SharedVersion.cs b/SharedVersion.cs
index ada1802b9..b2cbfdebd 100644
--- a/SharedVersion.cs
+++ b/SharedVersion.cs
@@ -1,4 +1,4 @@
using System.Reflection;
-//[assembly: AssemblyVersion("3.0.*")]
-[assembly: AssemblyVersion("3.0.5973")]
+//[assembly: AssemblyVersion("3.1.*")]
+[assembly: AssemblyVersion("3.1.48")]