aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Emby.Drawing/Emby.Drawing.csproj1
-rw-r--r--Emby.Drawing/ImageMagick/ImageMagickEncoder.cs17
-rw-r--r--Emby.Drawing/ImageMagick/StripCollageBuilder.cs357
-rw-r--r--Emby.Drawing/ImageProcessor.cs53
-rw-r--r--MediaBrowser.Api/ApiEntryPoint.cs17
-rw-r--r--MediaBrowser.Api/BaseApiService.cs191
-rw-r--r--MediaBrowser.Api/BrandingService.cs1
-rw-r--r--MediaBrowser.Api/ConfigurationService.cs22
-rw-r--r--MediaBrowser.Api/Dlna/DlnaService.cs6
-rw-r--r--MediaBrowser.Api/EnvironmentService.cs35
-rw-r--r--MediaBrowser.Api/FilterService.cs5
-rw-r--r--MediaBrowser.Api/GamesService.cs55
-rw-r--r--MediaBrowser.Api/Images/ImageService.cs19
-rw-r--r--MediaBrowser.Api/Images/RemoteImageService.cs10
-rw-r--r--MediaBrowser.Api/ItemLookupService.cs64
-rw-r--r--MediaBrowser.Api/ItemUpdateService.cs50
-rw-r--r--MediaBrowser.Api/Library/FileOrganizationService.cs2
-rw-r--r--MediaBrowser.Api/Library/LibraryHelpers.cs89
-rw-r--r--MediaBrowser.Api/Library/LibraryService.cs11
-rw-r--r--MediaBrowser.Api/Library/LibraryStructureService.cs115
-rw-r--r--MediaBrowser.Api/LiveTv/LiveTvService.cs147
-rw-r--r--MediaBrowser.Api/MediaBrowser.Api.csproj3
-rw-r--r--MediaBrowser.Api/Movies/MoviesService.cs263
-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.cs251
-rw-r--r--MediaBrowser.Api/Playback/Dash/ManifestBuilder.cs224
-rw-r--r--MediaBrowser.Api/Playback/Dash/MpegDashService.cs547
-rw-r--r--MediaBrowser.Api/Playback/Hls/BaseHlsService.cs53
-rw-r--r--MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs76
-rw-r--r--MediaBrowser.Api/Playback/Hls/HlsSegmentService.cs28
-rw-r--r--MediaBrowser.Api/Playback/Hls/VideoHlsService.cs21
-rw-r--r--MediaBrowser.Api/Playback/MediaInfoService.cs61
-rw-r--r--MediaBrowser.Api/Playback/Progressive/AudioService.cs5
-rw-r--r--MediaBrowser.Api/Playback/Progressive/BaseProgressiveStreamingService.cs39
-rw-r--r--MediaBrowser.Api/Playback/Progressive/ProgressiveStreamWriter.cs139
-rw-r--r--MediaBrowser.Api/Playback/Progressive/VideoService.cs10
-rw-r--r--MediaBrowser.Api/Playback/StreamRequest.cs13
-rw-r--r--MediaBrowser.Api/Playback/StreamState.cs40
-rw-r--r--MediaBrowser.Api/PlaylistService.cs4
-rw-r--r--MediaBrowser.Api/Reports/ReportsService.cs168
-rw-r--r--MediaBrowser.Api/SimilarItemsHelper.cs36
-rw-r--r--MediaBrowser.Api/StartupWizardService.cs28
-rw-r--r--MediaBrowser.Api/Subtitles/SubtitleService.cs39
-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.cs119
-rw-r--r--MediaBrowser.Api/UserLibrary/ArtistsService.cs40
-rw-r--r--MediaBrowser.Api/UserLibrary/BaseItemsByNameService.cs158
-rw-r--r--MediaBrowser.Api/UserLibrary/BaseItemsRequest.cs17
-rw-r--r--MediaBrowser.Api/UserLibrary/GameGenresService.cs29
-rw-r--r--MediaBrowser.Api/UserLibrary/GenresService.cs61
-rw-r--r--MediaBrowser.Api/UserLibrary/ItemsService.cs94
-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.cs20
-rw-r--r--MediaBrowser.Api/UserService.cs9
-rw-r--r--MediaBrowser.Api/VideosService.cs16
-rw-r--r--MediaBrowser.Common.Implementations/BaseApplicationHost.cs8
-rw-r--r--MediaBrowser.Common.Implementations/Configuration/BaseConfigurationManager.cs1
-rw-r--r--MediaBrowser.Common.Implementations/HttpClientManager/HttpClientManager.cs52
-rw-r--r--MediaBrowser.Common.Implementations/MediaBrowser.Common.Implementations.csproj10
-rw-r--r--MediaBrowser.Common.Implementations/ScheduledTasks/ScheduledTaskWorker.cs45
-rw-r--r--MediaBrowser.Common.Implementations/ScheduledTasks/TaskManager.cs4
-rw-r--r--MediaBrowser.Common.Implementations/ScheduledTasks/Tasks/DeleteCacheFileTask.cs2
-rw-r--r--MediaBrowser.Common.Implementations/Security/MbAdmin.cs4
-rw-r--r--MediaBrowser.Common.Implementations/Security/PluginSecurityManager.cs2
-rw-r--r--MediaBrowser.Common.Implementations/Serialization/JsonSerializer.cs8
-rw-r--r--MediaBrowser.Common.Implementations/Serialization/XmlSerializer.cs15
-rw-r--r--MediaBrowser.Common.Implementations/Updates/GithubUpdater.cs9
-rw-r--r--MediaBrowser.Common.Implementations/Updates/InstallationManager.cs1
-rw-r--r--MediaBrowser.Common.Implementations/packages.config6
-rw-r--r--MediaBrowser.Common/Net/HttpRequestOptions.cs3
-rw-r--r--MediaBrowser.Common/ScheduledTasks/DailyTrigger.cs10
-rw-r--r--MediaBrowser.Common/ScheduledTasks/ITaskTrigger.cs3
-rw-r--r--MediaBrowser.Common/ScheduledTasks/IntervalTrigger.cs13
-rw-r--r--MediaBrowser.Common/ScheduledTasks/StartupTrigger.cs3
-rw-r--r--MediaBrowser.Common/ScheduledTasks/SystemEventTrigger.cs3
-rw-r--r--MediaBrowser.Common/ScheduledTasks/WeeklyTrigger.cs3
-rw-r--r--MediaBrowser.Controller/Channels/Channel.cs2
-rw-r--r--MediaBrowser.Controller/Channels/ChannelAudioItem.cs101
-rw-r--r--MediaBrowser.Controller/Channels/ChannelFolderItem.cs89
-rw-r--r--MediaBrowser.Controller/Channels/ChannelItemInfo.cs8
-rw-r--r--MediaBrowser.Controller/Channels/ChannelMediaInfo.cs2
-rw-r--r--MediaBrowser.Controller/Channels/ChannelVideoItem.cs126
-rw-r--r--MediaBrowser.Controller/Channels/IChannelItem.cs11
-rw-r--r--MediaBrowser.Controller/Channels/IChannelMediaItem.cs18
-rw-r--r--MediaBrowser.Controller/Chapters/IChapterManager.cs2
-rw-r--r--MediaBrowser.Controller/Drawing/IImageProcessor.cs2
-rw-r--r--MediaBrowser.Controller/Drawing/ImageCollageOptions.cs5
-rw-r--r--MediaBrowser.Controller/Dto/IDtoService.cs3
-rw-r--r--MediaBrowser.Controller/Entities/AggregateFolder.cs37
-rw-r--r--MediaBrowser.Controller/Entities/Audio/Audio.cs49
-rw-r--r--MediaBrowser.Controller/Entities/Audio/MusicAlbum.cs64
-rw-r--r--MediaBrowser.Controller/Entities/Audio/MusicArtist.cs64
-rw-r--r--MediaBrowser.Controller/Entities/Audio/MusicGenre.cs28
-rw-r--r--MediaBrowser.Controller/Entities/BaseItem.cs215
-rw-r--r--MediaBrowser.Controller/Entities/Book.cs30
-rw-r--r--MediaBrowser.Controller/Entities/CollectionFolder.cs87
-rw-r--r--MediaBrowser.Controller/Entities/Folder.cs585
-rw-r--r--MediaBrowser.Controller/Entities/Game.cs17
-rw-r--r--MediaBrowser.Controller/Entities/GameGenre.cs28
-rw-r--r--MediaBrowser.Controller/Entities/GameSystem.cs13
-rw-r--r--MediaBrowser.Controller/Entities/Genre.cs28
-rw-r--r--MediaBrowser.Controller/Entities/IHasImages.cs3
-rw-r--r--MediaBrowser.Controller/Entities/IHasMetadata.cs8
-rw-r--r--MediaBrowser.Controller/Entities/IHasSeries.cs9
-rw-r--r--MediaBrowser.Controller/Entities/IHasTrailers.cs12
-rw-r--r--MediaBrowser.Controller/Entities/IHasUserData.cs14
-rw-r--r--MediaBrowser.Controller/Entities/IItemByName.cs6
-rw-r--r--MediaBrowser.Controller/Entities/InternalItemsQuery.cs44
-rw-r--r--MediaBrowser.Controller/Entities/KeywordExtensions.cs (renamed from MediaBrowser.Controller/Entities/IHasKeywords.cs)12
-rw-r--r--MediaBrowser.Controller/Entities/Movies/BoxSet.cs12
-rw-r--r--MediaBrowser.Controller/Entities/Movies/Movie.cs63
-rw-r--r--MediaBrowser.Controller/Entities/MusicVideo.cs15
-rw-r--r--MediaBrowser.Controller/Entities/Person.cs27
-rw-r--r--MediaBrowser.Controller/Entities/Photo.cs8
-rw-r--r--MediaBrowser.Controller/Entities/PhotoAlbum.cs9
-rw-r--r--MediaBrowser.Controller/Entities/Studio.cs29
-rw-r--r--MediaBrowser.Controller/Entities/TV/Episode.cs141
-rw-r--r--MediaBrowser.Controller/Entities/TV/Season.cs193
-rw-r--r--MediaBrowser.Controller/Entities/TV/Series.cs281
-rw-r--r--MediaBrowser.Controller/Entities/TagExtensions.cs (renamed from MediaBrowser.Controller/Entities/IHasTags.cs)15
-rw-r--r--MediaBrowser.Controller/Entities/Trailer.cs48
-rw-r--r--MediaBrowser.Controller/Entities/User.cs9
-rw-r--r--MediaBrowser.Controller/Entities/UserItemData.cs4
-rw-r--r--MediaBrowser.Controller/Entities/UserRootFolder.cs22
-rw-r--r--MediaBrowser.Controller/Entities/UserView.cs20
-rw-r--r--MediaBrowser.Controller/Entities/UserViewBuilder.cs424
-rw-r--r--MediaBrowser.Controller/Entities/Video.cs293
-rw-r--r--MediaBrowser.Controller/Entities/Year.cs27
-rw-r--r--MediaBrowser.Controller/Health/IHealthMonitor.cs12
-rw-r--r--MediaBrowser.Controller/IServerApplicationHost.cs11
-rw-r--r--MediaBrowser.Controller/Library/ILibraryManager.cs57
-rw-r--r--MediaBrowser.Controller/Library/IUserDataManager.cs21
-rw-r--r--MediaBrowser.Controller/Library/TVUtils.cs2
-rw-r--r--MediaBrowser.Controller/Library/UserDataSaveEventArgs.cs7
-rw-r--r--MediaBrowser.Controller/LiveTv/ChannelInfo.cs4
-rw-r--r--MediaBrowser.Controller/LiveTv/IListingsProvider.cs1
-rw-r--r--MediaBrowser.Controller/LiveTv/ILiveTvManager.cs20
-rw-r--r--MediaBrowser.Controller/LiveTv/ILiveTvService.cs19
-rw-r--r--MediaBrowser.Controller/LiveTv/ITunerHost.cs2
-rw-r--r--MediaBrowser.Controller/LiveTv/LiveTvAudioRecording.cs11
-rw-r--r--MediaBrowser.Controller/LiveTv/LiveTvChannel.cs20
-rw-r--r--MediaBrowser.Controller/LiveTv/LiveTvProgram.cs49
-rw-r--r--MediaBrowser.Controller/LiveTv/LiveTvVideoRecording.cs26
-rw-r--r--MediaBrowser.Controller/LiveTv/TimerEventInfo.cs14
-rw-r--r--MediaBrowser.Controller/LiveTv/TunerChannelMapping.cs16
-rw-r--r--MediaBrowser.Controller/MediaBrowser.Controller.csproj20
-rw-r--r--MediaBrowser.Controller/MediaEncoding/EncodingJobOptions.cs8
-rw-r--r--MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs33
-rw-r--r--MediaBrowser.Controller/MediaEncoding/ISubtitleEncoder.cs10
-rw-r--r--MediaBrowser.Controller/Net/IHttpResultFactory.cs10
-rw-r--r--MediaBrowser.Controller/Notifications/INotificationsRepository.cs6
-rw-r--r--MediaBrowser.Controller/Persistence/IDisplayPreferencesRepository.cs6
-rw-r--r--MediaBrowser.Controller/Persistence/IItemRepository.cs20
-rw-r--r--MediaBrowser.Controller/Persistence/IUserDataRepository.cs8
-rw-r--r--MediaBrowser.Controller/Playlists/Playlist.cs74
-rw-r--r--MediaBrowser.Controller/Providers/BaseItemXmlParser.cs27
-rw-r--r--MediaBrowser.Controller/Providers/DirectoryService.cs6
-rw-r--r--MediaBrowser.Controller/Providers/EpisodeInfo.cs3
-rw-r--r--MediaBrowser.Controller/Providers/IHasItemChangeMonitor.cs3
-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.cs31
-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/MetadataRefreshOptions.cs3
-rw-r--r--MediaBrowser.Controller/Providers/MetadataStatus.cs49
-rw-r--r--MediaBrowser.Controller/Session/ISessionManager.cs3
-rw-r--r--MediaBrowser.Controller/Subtitles/SubtitleSearchRequest.cs1
-rw-r--r--MediaBrowser.Dlna/Channels/DlnaChannelFactory.cs393
-rw-r--r--MediaBrowser.Dlna/ContentDirectory/ContentDirectory.cs8
-rw-r--r--MediaBrowser.Dlna/ContentDirectory/ControlHandler.cs42
-rw-r--r--MediaBrowser.Dlna/Didl/DidlBuilder.cs74
-rw-r--r--MediaBrowser.Dlna/DlnaManager.cs44
-rw-r--r--MediaBrowser.Dlna/Main/DlnaEntryPoint.cs105
-rw-r--r--MediaBrowser.Dlna/PlayTo/PlayToController.cs51
-rw-r--r--MediaBrowser.Dlna/PlayTo/PlayToManager.cs10
-rw-r--r--MediaBrowser.Dlna/Profiles/BubbleUpnpProfile.cs94
-rw-r--r--MediaBrowser.Dlna/Profiles/DefaultProfile.cs7
-rw-r--r--MediaBrowser.Dlna/Profiles/LgTvProfile.cs15
-rw-r--r--MediaBrowser.Dlna/Profiles/SamsungSmartTvProfile.cs4
-rw-r--r--MediaBrowser.Dlna/Profiles/VlcProfile.cs93
-rw-r--r--MediaBrowser.Dlna/Profiles/Xml/BubbleUPnp.xml34
-rw-r--r--MediaBrowser.Dlna/Profiles/Xml/Default.xml13
-rw-r--r--MediaBrowser.Dlna/Profiles/Xml/Denon AVR.xml13
-rw-r--r--MediaBrowser.Dlna/Profiles/Xml/DirecTV HD-DVR.xml11
-rw-r--r--MediaBrowser.Dlna/Profiles/Xml/Dish Hopper-Joey.xml13
-rw-r--r--MediaBrowser.Dlna/Profiles/Xml/Kodi.xml9
-rw-r--r--MediaBrowser.Dlna/Profiles/Xml/LG Smart TV.xml23
-rw-r--r--MediaBrowser.Dlna/Profiles/Xml/Linksys DMA2100.xml13
-rw-r--r--MediaBrowser.Dlna/Profiles/Xml/MediaMonkey.xml13
-rw-r--r--MediaBrowser.Dlna/Profiles/Xml/Panasonic Viera.xml13
-rw-r--r--MediaBrowser.Dlna/Profiles/Xml/Popcorn Hour.xml13
-rw-r--r--MediaBrowser.Dlna/Profiles/Xml/Samsung Smart TV.xml15
-rw-r--r--MediaBrowser.Dlna/Profiles/Xml/Sony Blu-ray Player 2013.xml11
-rw-r--r--MediaBrowser.Dlna/Profiles/Xml/Sony Blu-ray Player.xml11
-rw-r--r--MediaBrowser.Dlna/Profiles/Xml/Sony Bravia (2010).xml11
-rw-r--r--MediaBrowser.Dlna/Profiles/Xml/Sony Bravia (2011).xml11
-rw-r--r--MediaBrowser.Dlna/Profiles/Xml/Sony Bravia (2012).xml11
-rw-r--r--MediaBrowser.Dlna/Profiles/Xml/Sony Bravia (2013).xml11
-rw-r--r--MediaBrowser.Dlna/Profiles/Xml/Sony Bravia (2014).xml11
-rw-r--r--MediaBrowser.Dlna/Profiles/Xml/Sony PlayStation 3.xml13
-rw-r--r--MediaBrowser.Dlna/Profiles/Xml/Sony PlayStation 4.xml13
-rw-r--r--MediaBrowser.Dlna/Profiles/Xml/Vlc.xml32
-rw-r--r--MediaBrowser.Dlna/Profiles/Xml/WDTV Live.xml13
-rw-r--r--MediaBrowser.Dlna/Profiles/Xml/Xbox 360.xml10
-rw-r--r--MediaBrowser.Dlna/Profiles/Xml/Xbox One.xml13
-rw-r--r--MediaBrowser.Dlna/Profiles/Xml/foobar2000.xml13
-rw-r--r--MediaBrowser.Dlna/Server/DescriptionXmlBuilder.cs5
-rw-r--r--MediaBrowser.Dlna/Ssdp/DeviceDiscovery.cs34
-rw-r--r--MediaBrowser.Dlna/Ssdp/SsdpHandler.cs49
-rw-r--r--MediaBrowser.LocalMetadata/BaseXmlProvider.cs6
-rw-r--r--MediaBrowser.LocalMetadata/Savers/XmlSaverHelpers.cs36
-rw-r--r--MediaBrowser.MediaEncoding/Encoder/AudioEncoder.cs30
-rw-r--r--MediaBrowser.MediaEncoding/Encoder/BaseEncoder.cs86
-rw-r--r--MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs (renamed from MediaBrowser.Server.Startup.Common/FFMpeg/FFmpegValidator.cs)89
-rw-r--r--MediaBrowser.MediaEncoding/Encoder/EncodingJob.cs14
-rw-r--r--MediaBrowser.MediaEncoding/Encoder/EncodingJobFactory.cs59
-rw-r--r--MediaBrowser.MediaEncoding/Encoder/FontConfigLoader.cs179
-rw-r--r--MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs365
-rw-r--r--MediaBrowser.MediaEncoding/Encoder/VideoEncoder.cs16
-rw-r--r--MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj2
-rw-r--r--MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs46
-rw-r--r--MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs130
-rw-r--r--MediaBrowser.Model.Portable/FodyWeavers.xml3
-rw-r--r--MediaBrowser.Model.Portable/MediaBrowser.Model.Portable.csproj34
-rw-r--r--MediaBrowser.Model.Portable/packages.config5
-rw-r--r--MediaBrowser.Model.net35/MediaBrowser.Model.net35.csproj25
-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/BaseApplicationConfiguration.cs6
-rw-r--r--MediaBrowser.Model/Configuration/EncodingOptions.cs2
-rw-r--r--MediaBrowser.Model/Configuration/FanartOptions.cs5
-rw-r--r--MediaBrowser.Model/Configuration/ServerConfiguration.cs31
-rw-r--r--MediaBrowser.Model/Configuration/TheMovieDbOptions.cs12
-rw-r--r--MediaBrowser.Model/Configuration/TvdbOptions.cs12
-rw-r--r--MediaBrowser.Model/Configuration/UserConfiguration.cs11
-rw-r--r--MediaBrowser.Model/Connect/ConnectUser.cs1
-rw-r--r--MediaBrowser.Model/Dlna/AudioOptions.cs8
-rw-r--r--MediaBrowser.Model/Dlna/CodecProfile.cs3
-rw-r--r--MediaBrowser.Model/Dlna/ConditionProcessor.cs3
-rw-r--r--MediaBrowser.Model/Dlna/ContentFeatureBuilder.cs2
-rw-r--r--MediaBrowser.Model/Dlna/DeviceProfile.cs3
-rw-r--r--MediaBrowser.Model/Dlna/ILocalPlayer.cs26
-rw-r--r--MediaBrowser.Model/Dlna/ITranscoderSupport.cs15
-rw-r--r--MediaBrowser.Model/Dlna/NullLocalPlayer.cs21
-rw-r--r--MediaBrowser.Model/Dlna/ProfileConditionValue.cs1
-rw-r--r--MediaBrowser.Model/Dlna/ResolutionNormalizer.cs20
-rw-r--r--MediaBrowser.Model/Dlna/StreamBuilder.cs390
-rw-r--r--MediaBrowser.Model/Dlna/StreamInfo.cs68
-rw-r--r--MediaBrowser.Model/Dlna/StreamInfoSorter.cs19
-rw-r--r--MediaBrowser.Model/Dlna/SubtitleStreamInfo.cs1
-rw-r--r--MediaBrowser.Model/Dlna/TranscodingProfile.cs11
-rw-r--r--MediaBrowser.Model/Dto/BaseItemDto.cs24
-rw-r--r--MediaBrowser.Model/Dto/BaseItemPerson.cs10
-rw-r--r--MediaBrowser.Model/Dto/ChapterInfoDto.cs8
-rw-r--r--MediaBrowser.Model/Dto/ItemCounts.cs1
-rw-r--r--MediaBrowser.Model/Dto/MediaSourceInfo.cs10
-rw-r--r--MediaBrowser.Model/Dto/UserDto.cs8
-rw-r--r--MediaBrowser.Model/Dto/UserItemDataDto.cs7
-rw-r--r--MediaBrowser.Model/Entities/ChapterInfo.cs4
-rw-r--r--MediaBrowser.Model/Entities/DisplayPreferences.cs9
-rw-r--r--MediaBrowser.Model/Entities/ImageType.cs24
-rw-r--r--MediaBrowser.Model/Entities/MediaStream.cs125
-rw-r--r--MediaBrowser.Model/Entities/MediaUrl.cs1
-rw-r--r--MediaBrowser.Model/Entities/VideoSize.cs8
-rw-r--r--MediaBrowser.Model/Extensions/IHasPropertyChangedEvent.cs8
-rw-r--r--MediaBrowser.Model/Extensions/StringHelper.cs5
-rw-r--r--MediaBrowser.Model/Games/GameSystem.cs48
-rw-r--r--MediaBrowser.Model/LiveTv/BaseTimerInfoDto.cs9
-rw-r--r--MediaBrowser.Model/LiveTv/ChannelInfoDto.cs5
-rw-r--r--MediaBrowser.Model/LiveTv/LiveTvOptions.cs40
-rw-r--r--MediaBrowser.Model/LiveTv/ProgramQuery.cs3
-rw-r--r--MediaBrowser.Model/LiveTv/RecommendedProgramQuery.cs9
-rw-r--r--MediaBrowser.Model/LiveTv/RecordingQuery.cs7
-rw-r--r--MediaBrowser.Model/LiveTv/TimerInfoDto.cs7
-rw-r--r--MediaBrowser.Model/LiveTv/TimerQuery.cs2
-rw-r--r--MediaBrowser.Model/MediaBrowser.Model.csproj9
-rw-r--r--MediaBrowser.Model/MediaInfo/AudioCodec.cs17
-rw-r--r--MediaBrowser.Model/Providers/SubtitleOptions.cs3
-rw-r--r--MediaBrowser.Model/Querying/ItemFields.cs4
-rw-r--r--MediaBrowser.Model/Querying/ItemQuery.cs4
-rw-r--r--MediaBrowser.Model/Querying/ItemSortBy.cs1
-rw-r--r--MediaBrowser.Model/Session/SessionInfoDto.cs5
-rw-r--r--MediaBrowser.Model/Sync/SyncJobItem.cs2
-rw-r--r--MediaBrowser.Model/System/Architecture.cs9
-rw-r--r--MediaBrowser.Model/System/SystemInfo.cs4
-rw-r--r--MediaBrowser.Model/Updates/PackageVersionInfo.cs2
-rw-r--r--MediaBrowser.Providers/Books/BookMetadataService.cs8
-rw-r--r--MediaBrowser.Providers/BoxSets/BoxSetMetadataService.cs8
-rw-r--r--MediaBrowser.Providers/BoxSets/MovieDbBoxSetImageProvider.cs4
-rw-r--r--MediaBrowser.Providers/BoxSets/MovieDbBoxSetProvider.cs6
-rw-r--r--MediaBrowser.Providers/Channels/ChannelMetadataService.cs6
-rw-r--r--MediaBrowser.Providers/Chapters/ChapterManager.cs2
-rw-r--r--MediaBrowser.Providers/Folders/CollectionFolderMetadataService.cs28
-rw-r--r--MediaBrowser.Providers/Folders/DefaultImageProvider.cs165
-rw-r--r--MediaBrowser.Providers/Folders/FolderMetadataService.cs8
-rw-r--r--MediaBrowser.Providers/Folders/UserViewMetadataService.cs15
-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.cs254
-rw-r--r--MediaBrowser.Providers/Manager/ProviderManager.cs65
-rw-r--r--MediaBrowser.Providers/Manager/ProviderUtils.cs41
-rw-r--r--MediaBrowser.Providers/Manager/SeriesOrderManager.cs35
-rw-r--r--MediaBrowser.Providers/MediaBrowser.Providers.csproj9
-rw-r--r--MediaBrowser.Providers/MediaInfo/AudioImageProvider.cs28
-rw-r--r--MediaBrowser.Providers/MediaInfo/FFProbeProvider.cs16
-rw-r--r--MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs7
-rw-r--r--MediaBrowser.Providers/MediaInfo/SubtitleDownloader.cs8
-rw-r--r--MediaBrowser.Providers/MediaInfo/SubtitleResolver.cs9
-rw-r--r--MediaBrowser.Providers/MediaInfo/SubtitleScheduledTask.cs1
-rw-r--r--MediaBrowser.Providers/MediaInfo/VideoImageProvider.cs70
-rw-r--r--MediaBrowser.Providers/Movies/FanArtMovieUpdatesPostScanTask.cs196
-rw-r--r--MediaBrowser.Providers/Movies/FanartMovieImageProvider.cs35
-rw-r--r--MediaBrowser.Providers/Movies/GenericMovieDbInfo.cs18
-rw-r--r--MediaBrowser.Providers/Movies/MovieDbImageProvider.cs15
-rw-r--r--MediaBrowser.Providers/Movies/MovieDbProvider.cs89
-rw-r--r--MediaBrowser.Providers/Movies/MovieDbSearch.cs28
-rw-r--r--MediaBrowser.Providers/Movies/MovieDbTrailerProvider.cs5
-rw-r--r--MediaBrowser.Providers/Movies/MovieExternalIds.cs17
-rw-r--r--MediaBrowser.Providers/Movies/MovieMetadataService.cs17
-rw-r--r--MediaBrowser.Providers/Movies/MovieUpdatesPrescanTask.cs245
-rw-r--r--MediaBrowser.Providers/Movies/TmdbSettings.cs2
-rw-r--r--MediaBrowser.Providers/Music/AlbumMetadataService.cs8
-rw-r--r--MediaBrowser.Providers/Music/ArtistMetadataService.cs16
-rw-r--r--MediaBrowser.Providers/Music/AudioMetadataService.cs8
-rw-r--r--MediaBrowser.Providers/Music/FanArtAlbumProvider.cs35
-rw-r--r--MediaBrowser.Providers/Music/FanArtArtistProvider.cs31
-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.cs34
-rw-r--r--MediaBrowser.Providers/Music/MusicExternalIds.cs12
-rw-r--r--MediaBrowser.Providers/Music/MusicVideoMetadataService.cs8
-rw-r--r--MediaBrowser.Providers/MusicGenres/MusicGenreImageProvider.cs145
-rw-r--r--MediaBrowser.Providers/MusicGenres/MusicGenreMetadataService.cs6
-rw-r--r--MediaBrowser.Providers/Omdb/OmdbImageProvider.cs48
-rw-r--r--MediaBrowser.Providers/Omdb/OmdbItemProvider.cs35
-rw-r--r--MediaBrowser.Providers/Omdb/OmdbProvider.cs376
-rw-r--r--MediaBrowser.Providers/People/MovieDbPersonImageProvider.cs2
-rw-r--r--MediaBrowser.Providers/People/MovieDbPersonProvider.cs8
-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/Photos/PhotoProvider.cs7
-rw-r--r--MediaBrowser.Providers/Playlists/PlaylistMetadataService.cs8
-rw-r--r--MediaBrowser.Providers/Studios/StudioMetadataService.cs6
-rw-r--r--MediaBrowser.Providers/Subtitles/OpenSubtitleDownloader.cs4
-rw-r--r--MediaBrowser.Providers/TV/DummySeasonProvider.cs17
-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.cs31
-rw-r--r--MediaBrowser.Providers/TV/MissingEpisodeProvider.cs27
-rw-r--r--MediaBrowser.Providers/TV/Omdb/OmdbEpisodeProvider.cs36
-rw-r--r--MediaBrowser.Providers/TV/SeasonMetadataService.cs49
-rw-r--r--MediaBrowser.Providers/TV/SeriesMetadataService.cs7
-rw-r--r--MediaBrowser.Providers/TV/SeriesPostScanTask.cs136
-rw-r--r--MediaBrowser.Providers/TV/TheMovieDb/MovieDbEpisodeImageProvider.cs8
-rw-r--r--MediaBrowser.Providers/TV/TheMovieDb/MovieDbEpisodeProvider.cs22
-rw-r--r--MediaBrowser.Providers/TV/TheMovieDb/MovieDbProviderBase.cs18
-rw-r--r--MediaBrowser.Providers/TV/TheMovieDb/MovieDbSeasonProvider.cs4
-rw-r--r--MediaBrowser.Providers/TV/TheMovieDb/MovieDbSeriesImageProvider.cs15
-rw-r--r--MediaBrowser.Providers/TV/TheMovieDb/MovieDbSeriesProvider.cs101
-rw-r--r--MediaBrowser.Providers/TV/TheTVDB/TvdbEpisodeImageProvider.cs31
-rw-r--r--MediaBrowser.Providers/TV/TheTVDB/TvdbEpisodeProvider.cs106
-rw-r--r--MediaBrowser.Providers/TV/TheTVDB/TvdbPrescanTask.cs42
-rw-r--r--MediaBrowser.Providers/TV/TheTVDB/TvdbSeasonIdentityProvider.cs65
-rw-r--r--MediaBrowser.Providers/TV/TheTVDB/TvdbSeasonImageProvider.cs44
-rw-r--r--MediaBrowser.Providers/TV/TheTVDB/TvdbSeriesImageProvider.cs22
-rw-r--r--MediaBrowser.Providers/TV/TheTVDB/TvdbSeriesProvider.cs54
-rw-r--r--MediaBrowser.Providers/TV/TvExternalIds.cs4
-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.cs253
-rw-r--r--MediaBrowser.Server.Implementations/Channels/ChannelImageProvider.cs2
-rw-r--r--MediaBrowser.Server.Implementations/Channels/ChannelManager.cs60
-rw-r--r--MediaBrowser.Server.Implementations/Collections/ManualCollectionsFolder.cs2
-rw-r--r--MediaBrowser.Server.Implementations/Configuration/ServerConfigurationManager.cs6
-rw-r--r--MediaBrowser.Server.Implementations/Connect/ConnectEntryPoint.cs14
-rw-r--r--MediaBrowser.Server.Implementations/Connect/ConnectManager.cs58
-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.cs607
-rw-r--r--MediaBrowser.Server.Implementations/EntryPoints/AutomaticRestartEntryPoint.cs50
-rw-r--r--MediaBrowser.Server.Implementations/EntryPoints/ExternalPortForwarding.cs65
-rw-r--r--MediaBrowser.Server.Implementations/EntryPoints/Notifications/Notifications.cs37
-rw-r--r--MediaBrowser.Server.Implementations/EntryPoints/RecordingNotifier.cs83
-rw-r--r--MediaBrowser.Server.Implementations/EntryPoints/UsageReporter.cs2
-rw-r--r--MediaBrowser.Server.Implementations/EntryPoints/UserDataChangeNotifier.cs2
-rw-r--r--MediaBrowser.Server.Implementations/FileOrganization/EpisodeFileOrganizer.cs34
-rw-r--r--MediaBrowser.Server.Implementations/FileOrganization/FileOrganizationService.cs10
-rw-r--r--MediaBrowser.Server.Implementations/HttpServer/AsyncStreamWriterFunc.cs56
-rw-r--r--MediaBrowser.Server.Implementations/HttpServer/HttpListenerHost.cs77
-rw-r--r--MediaBrowser.Server.Implementations/HttpServer/HttpResultFactory.cs21
-rw-r--r--MediaBrowser.Server.Implementations/HttpServer/LoggerUtils.cs2
-rw-r--r--MediaBrowser.Server.Implementations/HttpServer/NetListener/HttpListenerServer.cs285
-rw-r--r--MediaBrowser.Server.Implementations/HttpServer/RangeRequestWriter.cs78
-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.cs151
-rw-r--r--MediaBrowser.Server.Implementations/HttpServer/SocketSharp/WebSocketSharpRequest.cs87
-rw-r--r--MediaBrowser.Server.Implementations/HttpServer/SocketSharp/WebSocketSharpResponse.cs17
-rw-r--r--MediaBrowser.Server.Implementations/HttpServer/StreamWriter.cs47
-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.cs280
-rw-r--r--MediaBrowser.Server.Implementations/Intros/DefaultIntroProvider.cs188
-rw-r--r--MediaBrowser.Server.Implementations/Library/LibraryManager.cs529
-rw-r--r--MediaBrowser.Server.Implementations/Library/LocalTrailerPostScanTask.cs24
-rw-r--r--MediaBrowser.Server.Implementations/Library/MediaSourceManager.cs27
-rw-r--r--MediaBrowser.Server.Implementations/Library/MusicManager.cs22
-rw-r--r--MediaBrowser.Server.Implementations/Library/ResolverHelper.cs25
-rw-r--r--MediaBrowser.Server.Implementations/Library/Resolvers/BaseVideoResolver.cs2
-rw-r--r--MediaBrowser.Server.Implementations/Library/Resolvers/PhotoResolver.cs48
-rw-r--r--MediaBrowser.Server.Implementations/Library/Resolvers/TV/EpisodeResolver.cs29
-rw-r--r--MediaBrowser.Server.Implementations/Library/Resolvers/TV/SeasonResolver.cs6
-rw-r--r--MediaBrowser.Server.Implementations/Library/SearchEngine.cs11
-rw-r--r--MediaBrowser.Server.Implementations/Library/UserDataManager.cs113
-rw-r--r--MediaBrowser.Server.Implementations/Library/UserManager.cs3
-rw-r--r--MediaBrowser.Server.Implementations/Library/UserViewManager.cs41
-rw-r--r--MediaBrowser.Server.Implementations/Library/Validators/StudiosValidator.cs24
-rw-r--r--MediaBrowser.Server.Implementations/Library/Validators/YearsPostScanTask.cs13
-rw-r--r--MediaBrowser.Server.Implementations/LiveTv/ChannelImageProvider.cs4
-rw-r--r--MediaBrowser.Server.Implementations/LiveTv/EmbyTV/DirectRecorder.cs26
-rw-r--r--MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs726
-rw-r--r--MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EmbyTVRegistration.cs36
-rw-r--r--MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs126
-rw-r--r--MediaBrowser.Server.Implementations/LiveTv/EmbyTV/IRecorder.cs2
-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.cs371
-rw-r--r--MediaBrowser.Server.Implementations/LiveTv/LiveTvMediaSourceProvider.cs2
-rw-r--r--MediaBrowser.Server.Implementations/LiveTv/ProgramImageProvider.cs2
-rw-r--r--MediaBrowser.Server.Implementations/LiveTv/RecordingImageProvider.cs2
-rw-r--r--MediaBrowser.Server.Implementations/LiveTv/TunerHosts/BaseTunerHost.cs30
-rw-r--r--MediaBrowser.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs183
-rw-r--r--MediaBrowser.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs9
-rw-r--r--MediaBrowser.Server.Implementations/LiveTv/TunerHosts/SatIp/ChannelScan.cs105
-rw-r--r--MediaBrowser.Server.Implementations/LiveTv/TunerHosts/SatIp/Rtsp/RtspMethod.cs88
-rw-r--r--MediaBrowser.Server.Implementations/LiveTv/TunerHosts/SatIp/Rtsp/RtspRequest.cs140
-rw-r--r--MediaBrowser.Server.Implementations/LiveTv/TunerHosts/SatIp/Rtsp/RtspResponse.cs149
-rw-r--r--MediaBrowser.Server.Implementations/LiveTv/TunerHosts/SatIp/Rtsp/RtspSession.cs688
-rw-r--r--MediaBrowser.Server.Implementations/LiveTv/TunerHosts/SatIp/Rtsp/RtspStatusCode.cs251
-rw-r--r--MediaBrowser.Server.Implementations/LiveTv/TunerHosts/SatIp/SatIpDiscovery.cs169
-rw-r--r--MediaBrowser.Server.Implementations/LiveTv/TunerHosts/SatIp/SatIpHost.cs8
-rw-r--r--MediaBrowser.Server.Implementations/Localization/Core/ca.json10
-rw-r--r--MediaBrowser.Server.Implementations/Localization/Core/en-US.json353
-rw-r--r--MediaBrowser.Server.Implementations/Localization/Core/es.json142
-rw-r--r--MediaBrowser.Server.Implementations/Localization/Core/hu.json10
-rw-r--r--MediaBrowser.Server.Implementations/Localization/Core/nl.json2
-rw-r--r--MediaBrowser.Server.Implementations/Localization/Core/ru.json16
-rw-r--r--MediaBrowser.Server.Implementations/Localization/Core/sv.json80
-rw-r--r--MediaBrowser.Server.Implementations/Localization/Core/zh-TW.json18
-rw-r--r--MediaBrowser.Server.Implementations/Localization/LocalizationManager.cs10
-rw-r--r--MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj213
-rw-r--r--MediaBrowser.Server.Implementations/MediaEncoder/EncodingManager.cs16
-rw-r--r--MediaBrowser.Server.Implementations/Notifications/SqliteNotificationsRepository.cs449
-rw-r--r--MediaBrowser.Server.Implementations/Persistence/BaseSqliteRepository.cs33
-rw-r--r--MediaBrowser.Server.Implementations/Persistence/CleanDatabaseScheduledTask.cs81
-rw-r--r--MediaBrowser.Server.Implementations/Persistence/DataExtensions.cs185
-rw-r--r--MediaBrowser.Server.Implementations/Persistence/IDbConnector.cs10
-rw-r--r--MediaBrowser.Server.Implementations/Persistence/MediaStreamColumns.cs166
-rw-r--r--MediaBrowser.Server.Implementations/Persistence/SqliteDisplayPreferencesRepository.cs261
-rw-r--r--MediaBrowser.Server.Implementations/Persistence/SqliteExtensions.cs208
-rw-r--r--MediaBrowser.Server.Implementations/Persistence/SqliteFileOrganizationRepository.cs432
-rw-r--r--MediaBrowser.Server.Implementations/Persistence/SqliteItemRepository.cs2273
-rw-r--r--MediaBrowser.Server.Implementations/Persistence/SqliteProviderInfoRepository.cs248
-rw-r--r--MediaBrowser.Server.Implementations/Persistence/SqliteUserDataRepository.cs112
-rw-r--r--MediaBrowser.Server.Implementations/Persistence/SqliteUserRepository.cs202
-rw-r--r--MediaBrowser.Server.Implementations/Photos/BaseDynamicImageProvider.cs22
-rw-r--r--MediaBrowser.Server.Implementations/Playlists/PlaylistImageProvider.cs34
-rw-r--r--MediaBrowser.Server.Implementations/Playlists/PlaylistManager.cs19
-rw-r--r--MediaBrowser.Server.Implementations/ScheduledTasks/ChapterImagesTask.cs12
-rw-r--r--MediaBrowser.Server.Implementations/Security/AuthenticationRepository.cs330
-rw-r--r--MediaBrowser.Server.Implementations/ServerManager/ServerManager.cs19
-rw-r--r--MediaBrowser.Server.Implementations/Session/SessionManager.cs97
-rw-r--r--MediaBrowser.Server.Implementations/Session/SessionWebSocketListener.cs7
-rw-r--r--MediaBrowser.Server.Implementations/Social/SharingManager.cs12
-rw-r--r--MediaBrowser.Server.Implementations/Social/SharingRepository.cs172
-rw-r--r--MediaBrowser.Server.Implementations/Sorting/AiredEpisodeOrderComparer.cs10
-rw-r--r--MediaBrowser.Server.Implementations/Sorting/DateLastMediaAddedComparer.cs10
-rw-r--r--MediaBrowser.Server.Implementations/Sorting/DatePlayedComparer.cs2
-rw-r--r--MediaBrowser.Server.Implementations/Sorting/PlayCountComparer.cs2
-rw-r--r--MediaBrowser.Server.Implementations/Sorting/SeriesSortNameComparer.cs24
-rw-r--r--MediaBrowser.Server.Implementations/Sync/SyncJobProcessor.cs116
-rw-r--r--MediaBrowser.Server.Implementations/Sync/SyncManager.cs22
-rw-r--r--MediaBrowser.Server.Implementations/Sync/SyncRepository.cs875
-rw-r--r--MediaBrowser.Server.Implementations/TV/TVSeriesManager.cs184
-rw-r--r--MediaBrowser.Server.Implementations/Udp/UdpServer.cs12
-rw-r--r--MediaBrowser.Server.Implementations/UserViews/CollectionFolderImageProvider.cs5
-rw-r--r--MediaBrowser.Server.Implementations/UserViews/DynamicImageProvider.cs8
-rw-r--r--MediaBrowser.Server.Implementations/packages.config9
-rw-r--r--MediaBrowser.Server.Mac/Emby.Server.Mac.csproj4583
-rw-r--r--MediaBrowser.Server.Mac/Main.cs2
-rw-r--r--MediaBrowser.Server.Mac/MenuBarIcon.cs6
-rw-r--r--MediaBrowser.Server.Mac/Native/BaseMonoApp.cs101
-rw-r--r--MediaBrowser.Server.Mac/Native/DbConnector.cs24
-rw-r--r--MediaBrowser.Server.Mac/Native/NativeApp.cs8
-rw-r--r--MediaBrowser.Server.Mac/statusicon.pngbin937 -> 922 bytes
-rw-r--r--MediaBrowser.Server.Mono/MediaBrowser.Server.Mono.csproj34
-rw-r--r--MediaBrowser.Server.Mono/Native/BaseMonoApp.cs106
-rw-r--r--MediaBrowser.Server.Mono/Native/DbConnector.cs24
-rw-r--r--MediaBrowser.Server.Mono/Native/NativeApp.cs7
-rw-r--r--MediaBrowser.Server.Mono/Networking/CertificateGenerator.cs2
-rw-r--r--MediaBrowser.Server.Mono/Program.cs2
-rw-r--r--MediaBrowser.Server.Mono/Security/ASN1.cs339
-rw-r--r--MediaBrowser.Server.Mono/Security/ASN1Convert.cs206
-rw-r--r--MediaBrowser.Server.Mono/Security/BitConverterLE.cs239
-rw-r--r--MediaBrowser.Server.Mono/Security/CryptoConvert.cs744
-rw-r--r--MediaBrowser.Server.Mono/Security/PKCS1.cs490
-rw-r--r--MediaBrowser.Server.Mono/Security/PKCS12.cs1933
-rw-r--r--MediaBrowser.Server.Mono/Security/PKCS7.cs1011
-rw-r--r--MediaBrowser.Server.Mono/Security/PKCS8.cs494
-rw-r--r--MediaBrowser.Server.Mono/Security/X501Name.cs392
-rw-r--r--MediaBrowser.Server.Mono/Security/X509Builder.cs152
-rw-r--r--MediaBrowser.Server.Mono/Security/X509Certificate.cs562
-rw-r--r--MediaBrowser.Server.Mono/Security/X509CertificateBuilder.cs244
-rw-r--r--MediaBrowser.Server.Mono/Security/X509CertificateCollection.cs200
-rw-r--r--MediaBrowser.Server.Mono/Security/X509Extension.cs207
-rw-r--r--MediaBrowser.Server.Mono/Security/X509Extensions.cs194
-rw-r--r--MediaBrowser.Server.Mono/Security/X520Attributes.cs345
-rw-r--r--MediaBrowser.Server.Startup.Common/ApplicationHost.cs264
-rw-r--r--MediaBrowser.Server.Startup.Common/Browser/BrowserLauncher.cs59
-rw-r--r--MediaBrowser.Server.Startup.Common/EntryPoints/KeepServerAwake.cs31
-rw-r--r--MediaBrowser.Server.Startup.Common/EntryPoints/StartupWizard.cs2
-rw-r--r--MediaBrowser.Server.Startup.Common/FFMpeg/FFMpegDownloadInfo.cs142
-rw-r--r--MediaBrowser.Server.Startup.Common/FFMpeg/FFMpegDownloader.cs439
-rw-r--r--MediaBrowser.Server.Startup.Common/FFMpeg/FFMpegInstallInfo.cs20
-rw-r--r--MediaBrowser.Server.Startup.Common/FFMpeg/FFMpegLoader.cs248
-rw-r--r--MediaBrowser.Server.Startup.Common/INativeApp.cs10
-rw-r--r--MediaBrowser.Server.Startup.Common/MediaBrowser.Server.Startup.Common.csproj9
-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.config61
-rw-r--r--MediaBrowser.ServerApplication/BackgroundServiceInstaller.cs2
-rw-r--r--MediaBrowser.ServerApplication/MainStartup.cs121
-rw-r--r--MediaBrowser.ServerApplication/MediaBrowser.ServerApplication.csproj42
-rw-r--r--MediaBrowser.ServerApplication/Native/Autorun.cs35
-rw-r--r--MediaBrowser.ServerApplication/Native/DbConnector.cs24
-rw-r--r--MediaBrowser.ServerApplication/Native/LnkShortcutHandler.cs402
-rw-r--r--MediaBrowser.ServerApplication/Native/Standby.cs40
-rw-r--r--MediaBrowser.ServerApplication/Native/WindowsApp.cs92
-rw-r--r--MediaBrowser.ServerApplication/Networking/NetworkManager.cs15
-rw-r--r--MediaBrowser.ServerApplication/Networking/NetworkShares.cs4
-rw-r--r--MediaBrowser.ServerApplication/Properties/Resources.Designer.cs2
-rw-r--r--MediaBrowser.ServerApplication/ServerNotifyIcon.cs15
-rw-r--r--MediaBrowser.ServerApplication/packages.config3
-rw-r--r--MediaBrowser.WebDashboard/Api/DashboardService.cs54
-rw-r--r--MediaBrowser.WebDashboard/Api/PackageCreator.cs51
-rw-r--r--MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj232
-rw-r--r--MediaBrowser.WebDashboard/packages.config1
-rw-r--r--MediaBrowser.XbmcMetadata/Parsers/BaseNfoParser.cs22
-rw-r--r--MediaBrowser.XbmcMetadata/Parsers/EpisodeNfoParser.cs42
-rw-r--r--MediaBrowser.XbmcMetadata/Providers/BaseNfoProvider.cs6
-rw-r--r--MediaBrowser.XbmcMetadata/Savers/BaseNfoSaver.cs35
-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.Model.Signed.nuspec20
-rw-r--r--Nuget/MediaBrowser.Server.Core.nuspec4
-rw-r--r--OpenSubtitlesHandler/Utilities.cs4
-rw-r--r--OpenSubtitlesHandler/XML-RPC/XmlRpcGenerator.cs64
-rw-r--r--SharedVersion.cs4
592 files changed, 27699 insertions, 18535 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 8c50fb5eb..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);
@@ -295,9 +115,9 @@ namespace Emby.Drawing.ImageMagick
wand.OpenImage("gradient:#111111-#111111");
using (var draw = new DrawingWand())
{
- var iSlice = Convert.ToInt32(width * .1166666667 * 2);
+ var iSlice = Convert.ToInt32(width * 0.24125);
int iTrans = Convert.ToInt32(height * .25);
- int iHeight = Convert.ToInt32(height * .62);
+ int iHeight = Convert.ToInt32(height * .70);
var horizontalImagePadding = Convert.ToInt32(width * 0.0125);
foreach (var element in wandImages.ImageList)
@@ -339,7 +159,7 @@ namespace Emby.Drawing.ImageMagick
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 * .085));
+ wand.CurrentImage.CompositeImage(wandList.AppendImages(true), CompositeOperator.AtopCompositeOp, ex, Convert.ToInt32(height * .045));
}
}
}
@@ -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 * .3);
- int iTrans = Convert.ToInt32(height * .25);
- int iHeight = Convert.ToInt32(height * .63);
- 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 * .07));
- }
- }
- }
- }
- }
- }
-
- 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/Emby.Drawing/ImageProcessor.cs b/Emby.Drawing/ImageProcessor.cs
index dc93cb730..80ebbb719 100644
--- a/Emby.Drawing/ImageProcessor.cs
+++ b/Emby.Drawing/ImageProcessor.cs
@@ -163,7 +163,7 @@ namespace Emby.Drawing
return _imageEncoder.SupportedOutputFormats;
}
- public async Task<Tuple<string, string>> ProcessImage(ImageProcessingOptions options)
+ public async Task<Tuple<string, string, DateTime>> ProcessImage(ImageProcessingOptions options)
{
if (options == null)
{
@@ -178,14 +178,13 @@ namespace Emby.Drawing
}
var originalImagePath = originalImage.Path;
+ var dateModified = originalImage.DateModified;
if (!_imageEncoder.SupportsImageEncoding)
{
- return new Tuple<string, string>(originalImagePath, MimeTypes.GetMimeType(originalImagePath));
+ return new Tuple<string, string, DateTime>(originalImagePath, MimeTypes.GetMimeType(originalImagePath), dateModified);
}
- var dateModified = originalImage.DateModified;
-
if (options.CropWhiteSpace && _imageEncoder.SupportsImageEncoding)
{
var tuple = await GetWhitespaceCroppedImage(originalImagePath, dateModified).ConfigureAwait(false);
@@ -211,7 +210,7 @@ namespace Emby.Drawing
if (options.HasDefaultOptions(originalImagePath))
{
// Just spit out the original file if all the options are default
- return new Tuple<string, string>(originalImagePath, MimeTypes.GetMimeType(originalImagePath));
+ return new Tuple<string, string, DateTime>(originalImagePath, MimeTypes.GetMimeType(originalImagePath), dateModified);
}
ImageSize? originalImageSize;
@@ -221,7 +220,7 @@ namespace Emby.Drawing
if (options.HasDefaultOptions(originalImagePath, originalImageSize.Value))
{
// Just spit out the original file if all the options are default
- return new Tuple<string, string>(originalImagePath, MimeTypes.GetMimeType(originalImagePath));
+ return new Tuple<string, string, DateTime>(originalImagePath, MimeTypes.GetMimeType(originalImagePath), dateModified);
}
}
catch
@@ -235,10 +234,6 @@ namespace Emby.Drawing
var outputFormat = GetOutputFormat(options.SupportedOutputFormats[0]);
var cacheFilePath = GetCacheFilePath(originalImagePath, newSize, quality, dateModified, outputFormat, options.AddPlayedIndicator, options.PercentPlayed, options.UnplayedCount, options.BackgroundColor, options.ForegroundLayer);
- var semaphore = GetLock(cacheFilePath);
-
- await semaphore.WaitAsync().ConfigureAwait(false);
-
var imageProcessingLockTaken = false;
try
@@ -251,15 +246,20 @@ namespace Emby.Drawing
var newHeight = Convert.ToInt32(newSize.Height);
_fileSystem.CreateDirectory(Path.GetDirectoryName(cacheFilePath));
+ var tmpPath = Path.ChangeExtension(Path.Combine(_appPaths.TempDirectory, Guid.NewGuid().ToString("N")), Path.GetExtension(cacheFilePath));
+ _fileSystem.CreateDirectory(Path.GetDirectoryName(tmpPath));
await _imageProcessingSemaphore.WaitAsync().ConfigureAwait(false);
imageProcessingLockTaken = true;
- _imageEncoder.EncodeImage(originalImagePath, cacheFilePath, AutoOrient(options.Item), newWidth, newHeight, quality, options, outputFormat);
+ _imageEncoder.EncodeImage(originalImagePath, tmpPath, AutoOrient(options.Item), newWidth, newHeight, quality, options, outputFormat);
+ CopyFile(tmpPath, cacheFilePath);
+
+ return new Tuple<string, string, DateTime>(tmpPath, GetMimeType(outputFormat, cacheFilePath), _fileSystem.GetLastWriteTimeUtc(tmpPath));
}
- return new Tuple<string, string>(cacheFilePath, GetMimeType(outputFormat, cacheFilePath));
+ return new Tuple<string, string, DateTime>(cacheFilePath, GetMimeType(outputFormat, cacheFilePath), _fileSystem.GetLastWriteTimeUtc(cacheFilePath));
}
catch (Exception ex)
{
@@ -267,7 +267,7 @@ namespace Emby.Drawing
_logger.ErrorException("Error encoding image", ex);
// Just spit out the original file if all the options are default
- return new Tuple<string, string>(originalImagePath, MimeTypes.GetMimeType(originalImagePath));
+ return new Tuple<string, string, DateTime>(originalImagePath, MimeTypes.GetMimeType(originalImagePath), dateModified);
}
finally
{
@@ -275,8 +275,18 @@ namespace Emby.Drawing
{
_imageProcessingSemaphore.Release();
}
+ }
+ }
+
+ private void CopyFile(string src, string destination)
+ {
+ try
+ {
+ File.Copy(src, destination, true);
+ }
+ catch
+ {
- semaphore.Release();
}
}
@@ -412,14 +422,9 @@ namespace Emby.Drawing
var croppedImagePath = GetCachePath(CroppedWhitespaceImageCachePath, name, Path.GetExtension(originalImagePath));
- var semaphore = GetLock(croppedImagePath);
-
- await semaphore.WaitAsync().ConfigureAwait(false);
-
// Check again in case of contention
if (_fileSystem.FileExists(croppedImagePath))
{
- semaphore.Release();
return GetResult(croppedImagePath);
}
@@ -428,11 +433,15 @@ namespace Emby.Drawing
try
{
_fileSystem.CreateDirectory(Path.GetDirectoryName(croppedImagePath));
+ var tmpPath = Path.ChangeExtension(Path.Combine(_appPaths.TempDirectory, Guid.NewGuid().ToString("N")), Path.GetExtension(croppedImagePath));
+ _fileSystem.CreateDirectory(Path.GetDirectoryName(tmpPath));
await _imageProcessingSemaphore.WaitAsync().ConfigureAwait(false);
imageProcessingLockTaken = true;
- _imageEncoder.CropWhiteSpace(originalImagePath, croppedImagePath);
+ _imageEncoder.CropWhiteSpace(originalImagePath, tmpPath);
+ CopyFile(tmpPath, croppedImagePath);
+ return GetResult(tmpPath);
}
catch (NotImplementedException)
{
@@ -452,11 +461,7 @@ namespace Emby.Drawing
{
_imageProcessingSemaphore.Release();
}
-
- semaphore.Release();
}
-
- return GetResult(croppedImagePath);
}
private Tuple<string, DateTime> GetResult(string path)
diff --git a/MediaBrowser.Api/ApiEntryPoint.cs b/MediaBrowser.Api/ApiEntryPoint.cs
index 8233717ab..dc811812a 100644
--- a/MediaBrowser.Api/ApiEntryPoint.cs
+++ b/MediaBrowser.Api/ApiEntryPoint.cs
@@ -237,9 +237,12 @@ namespace MediaBrowser.Api
{
lock (_activeTranscodingJobs)
{
- var job = _activeTranscodingJobs.First(j => j.Type == type && string.Equals(j.Path, path, StringComparison.OrdinalIgnoreCase));
+ var job = _activeTranscodingJobs.FirstOrDefault(j => j.Type == type && string.Equals(j.Path, path, StringComparison.OrdinalIgnoreCase));
- _activeTranscodingJobs.Remove(job);
+ if (job != null)
+ {
+ _activeTranscodingJobs.Remove(job);
+ }
}
if (!string.IsNullOrWhiteSpace(state.Request.DeviceId))
@@ -349,7 +352,7 @@ namespace MediaBrowser.Api
if (job.Type != TranscodingJobType.Progressive)
{
- timerDuration = 1800000;
+ timerDuration = 60000;
}
job.PingTimeout = timerDuration;
@@ -488,13 +491,17 @@ namespace MediaBrowser.Api
{
try
{
- Logger.Info("Killing ffmpeg process for {0}", job.Path);
+ Logger.Info("Stopping ffmpeg process with q command for {0}", job.Path);
//process.Kill();
process.StandardInput.WriteLine("q");
// Need to wait because killing is asynchronous
- process.WaitForExit(5000);
+ if (!process.WaitForExit(5000))
+ {
+ Logger.Info("Killing ffmpeg process for {0}", job.Path);
+ process.Kill();
+ }
}
catch (Exception ex)
{
diff --git a/MediaBrowser.Api/BaseApiService.cs b/MediaBrowser.Api/BaseApiService.cs
index 30750b374..44a367be0 100644
--- a/MediaBrowser.Api/BaseApiService.cs
+++ b/MediaBrowser.Api/BaseApiService.cs
@@ -79,7 +79,7 @@ namespace MediaBrowser.Api
}
}
}
-
+
/// <summary>
/// To the optimized serialized result using cache.
/// </summary>
@@ -115,12 +115,9 @@ namespace MediaBrowser.Api
/// <returns>System.Object.</returns>
protected object ToStaticFileResult(string path)
{
- return ResultFactory.GetStaticFileResult(Request, path);
+ return ResultFactory.GetStaticFileResult(Request, path).Result;
}
- private readonly char[] _dashReplaceChars = { '?', '/', '&' };
- private const char SlugChar = '-';
-
protected DtoOptions GetDtoOptions(object request)
{
var options = new DtoOptions();
@@ -154,152 +151,122 @@ namespace MediaBrowser.Api
protected MusicArtist GetArtist(string name, ILibraryManager libraryManager)
{
- return libraryManager.GetArtist(DeSlugArtistName(name, libraryManager));
- }
-
- protected Studio GetStudio(string name, ILibraryManager libraryManager)
- {
- return libraryManager.GetStudio(DeSlugStudioName(name, libraryManager));
- }
-
- protected Genre GetGenre(string name, ILibraryManager libraryManager)
- {
- return libraryManager.GetGenre(DeSlugGenreName(name, libraryManager));
- }
+ if (name.IndexOf(BaseItem.SlugChar) != -1)
+ {
+ var result = libraryManager.GetItemList(new InternalItemsQuery
+ {
+ SlugName = name,
+ IncludeItemTypes = new[] { typeof(MusicArtist).Name }
- protected MusicGenre GetMusicGenre(string name, ILibraryManager libraryManager)
- {
- return libraryManager.GetMusicGenre(DeSlugGenreName(name, libraryManager));
- }
+ }).OfType<MusicArtist>().FirstOrDefault();
- protected GameGenre GetGameGenre(string name, ILibraryManager libraryManager)
- {
- return libraryManager.GetGameGenre(DeSlugGameGenreName(name, libraryManager));
- }
+ if (result != null)
+ {
+ return result;
+ }
+ }
- protected Person GetPerson(string name, ILibraryManager libraryManager)
- {
- return libraryManager.GetPerson(DeSlugPersonName(name, libraryManager));
+ return libraryManager.GetArtist(name);
}
- /// <summary>
- /// Deslugs an artist name by finding the correct entry in the library
- /// </summary>
- /// <param name="name"></param>
- /// <param name="libraryManager"></param>
- /// <returns></returns>
- protected string DeSlugArtistName(string name, ILibraryManager libraryManager)
+ protected Studio GetStudio(string name, ILibraryManager libraryManager)
{
- if (name.IndexOf(SlugChar) == -1)
- {
- return name;
- }
-
- var items = libraryManager.GetItemList(new InternalItemsQuery
+ if (name.IndexOf(BaseItem.SlugChar) != -1)
{
- IncludeItemTypes = new[] { typeof(Audio).Name, typeof(MusicVideo).Name, typeof(MusicAlbum).Name }
- });
-
- return items
- .OfType<IHasArtist>()
- .SelectMany(i => i.AllArtists)
- .DistinctNames()
- .FirstOrDefault(i =>
+ var result = libraryManager.GetItemList(new InternalItemsQuery
{
- i = _dashReplaceChars.Aggregate(i, (current, c) => current.Replace(c, SlugChar));
+ SlugName = name,
+ IncludeItemTypes = new[] { typeof(Studio).Name }
- return string.Equals(i, name, StringComparison.OrdinalIgnoreCase);
+ }).OfType<Studio>().FirstOrDefault();
+
+ if (result != null)
+ {
+ return result;
+ }
+ }
- }) ?? name;
+ return libraryManager.GetStudio(name);
}
- /// <summary>
- /// Deslugs a genre name by finding the correct entry in the library
- /// </summary>
- protected string DeSlugGenreName(string name, ILibraryManager libraryManager)
+ protected Genre GetGenre(string name, ILibraryManager libraryManager)
{
- if (name.IndexOf(SlugChar) == -1)
+ if (name.IndexOf(BaseItem.SlugChar) != -1)
{
- return name;
- }
-
- return libraryManager.RootFolder.GetRecursiveChildren()
- .SelectMany(i => i.Genres)
- .DistinctNames()
- .FirstOrDefault(i =>
+ var result = libraryManager.GetItemList(new InternalItemsQuery
{
- i = _dashReplaceChars.Aggregate(i, (current, c) => current.Replace(c, SlugChar));
+ SlugName = name,
+ IncludeItemTypes = new[] { typeof(Genre).Name }
- return string.Equals(i, name, StringComparison.OrdinalIgnoreCase);
+ }).OfType<Genre>().FirstOrDefault();
+
+ if (result != null)
+ {
+ return result;
+ }
+ }
- }) ?? name;
+ return libraryManager.GetGenre(name);
}
- protected string DeSlugGameGenreName(string name, ILibraryManager libraryManager)
+ protected MusicGenre GetMusicGenre(string name, ILibraryManager libraryManager)
{
- if (name.IndexOf(SlugChar) == -1)
+ if (name.IndexOf(BaseItem.SlugChar) != -1)
{
- return name;
- }
+ var result = libraryManager.GetItemList(new InternalItemsQuery
+ {
+ SlugName = name,
+ IncludeItemTypes = new[] { typeof(MusicGenre).Name }
- var items = libraryManager.GetItemList(new InternalItemsQuery
- {
- IncludeItemTypes = new[] { typeof(Game).Name }
- });
+ }).OfType<MusicGenre>().FirstOrDefault();
- return items
- .SelectMany(i => i.Genres)
- .DistinctNames()
- .FirstOrDefault(i =>
+ if (result != null)
{
- i = _dashReplaceChars.Aggregate(i, (current, c) => current.Replace(c, SlugChar));
-
- return string.Equals(i, name, StringComparison.OrdinalIgnoreCase);
+ return result;
+ }
+ }
- }) ?? name;
+ return libraryManager.GetMusicGenre(name);
}
- /// <summary>
- /// Deslugs a studio name by finding the correct entry in the library
- /// </summary>
- protected string DeSlugStudioName(string name, ILibraryManager libraryManager)
+ protected GameGenre GetGameGenre(string name, ILibraryManager libraryManager)
{
- if (name.IndexOf(SlugChar) == -1)
+ if (name.IndexOf(BaseItem.SlugChar) != -1)
{
- return name;
- }
-
- return libraryManager.RootFolder
- .GetRecursiveChildren()
- .SelectMany(i => i.Studios)
- .DistinctNames()
- .FirstOrDefault(i =>
+ var result = libraryManager.GetItemList(new InternalItemsQuery
{
- i = _dashReplaceChars.Aggregate(i, (current, c) => current.Replace(c, SlugChar));
+ SlugName = name,
+ IncludeItemTypes = new[] { typeof(GameGenre).Name }
- return string.Equals(i, name, StringComparison.OrdinalIgnoreCase);
+ }).OfType<GameGenre>().FirstOrDefault();
- }) ?? name;
+ if (result != null)
+ {
+ return result;
+ }
+ }
+
+ return libraryManager.GetGameGenre(name);
}
- /// <summary>
- /// Deslugs a person name by finding the correct entry in the library
- /// </summary>
- protected string DeSlugPersonName(string name, ILibraryManager libraryManager)
+ protected Person GetPerson(string name, ILibraryManager libraryManager)
{
- if (name.IndexOf(SlugChar) == -1)
+ if (name.IndexOf(BaseItem.SlugChar) != -1)
{
- return name;
- }
-
- return libraryManager.GetPeopleNames(new InternalPeopleQuery())
- .FirstOrDefault(i =>
+ var result = libraryManager.GetItemList(new InternalItemsQuery
{
- i = _dashReplaceChars.Aggregate(i, (current, c) => current.Replace(c, SlugChar));
+ SlugName = name,
+ IncludeItemTypes = new[] { typeof(Person).Name }
- return string.Equals(i, name, StringComparison.OrdinalIgnoreCase);
+ }).OfType<Person>().FirstOrDefault();
+
+ if (result != null)
+ {
+ return result;
+ }
+ }
- }) ?? name;
+ return libraryManager.GetPerson(name);
}
protected string GetPathValue(int index)
diff --git a/MediaBrowser.Api/BrandingService.cs b/MediaBrowser.Api/BrandingService.cs
index c900e4d06..e991565ad 100644
--- a/MediaBrowser.Api/BrandingService.cs
+++ b/MediaBrowser.Api/BrandingService.cs
@@ -10,6 +10,7 @@ namespace MediaBrowser.Api
}
[Route("/Branding/Css", "GET", Summary = "Gets custom css")]
+ [Route("/Branding/Css.css", "GET", Summary = "Gets custom css")]
public class GetBrandingCss
{
}
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/Dlna/DlnaService.cs b/MediaBrowser.Api/Dlna/DlnaService.cs
index b6c4f5dfb..dd4b59b92 100644
--- a/MediaBrowser.Api/Dlna/DlnaService.cs
+++ b/MediaBrowser.Api/Dlna/DlnaService.cs
@@ -31,11 +31,9 @@ namespace MediaBrowser.Api.Dlna
public string Id { get; set; }
}
- [Route("/Dlna/Profiles/{ProfileId}", "POST", Summary = "Updates a profile")]
+ [Route("/Dlna/Profiles/{Id}", "POST", Summary = "Updates a profile")]
public class UpdateProfile : DeviceProfile, IReturnVoid
{
- [ApiMember(Name = "ProfileId", Description = "Profile Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")]
- public string ProfileId { get; set; }
}
[Route("/Dlna/Profiles", "POST", Summary = "Creates a profile")]
@@ -89,4 +87,4 @@ namespace MediaBrowser.Api.Dlna
_dlnaManager.CreateProfile(request);
}
}
-}
+} \ No newline at end of file
diff --git a/MediaBrowser.Api/EnvironmentService.cs b/MediaBrowser.Api/EnvironmentService.cs
index 4e88e946f..b354cb26c 100644
--- a/MediaBrowser.Api/EnvironmentService.cs
+++ b/MediaBrowser.Api/EnvironmentService.cs
@@ -90,6 +90,17 @@ namespace MediaBrowser.Api
public string Path { get; set; }
}
+ public class DefaultDirectoryBrowserInfo
+ {
+ public string Path { get; set; }
+ }
+
+ [Route("/Environment/DefaultDirectoryBrowser", "GET", Summary = "Gets the parent path of a given path")]
+ public class GetDefaultDirectoryBrowser : IReturn<DefaultDirectoryBrowserInfo>
+ {
+
+ }
+
/// <summary>
/// Class EnvironmentService
/// </summary>
@@ -108,7 +119,6 @@ namespace MediaBrowser.Api
/// Initializes a new instance of the <see cref="EnvironmentService" /> class.
/// </summary>
/// <param name="networkManager">The network manager.</param>
- /// <exception cref="System.ArgumentNullException">networkManager</exception>
public EnvironmentService(INetworkManager networkManager, IFileSystem fileSystem)
{
if (networkManager == null)
@@ -120,6 +130,29 @@ namespace MediaBrowser.Api
_fileSystem = fileSystem;
}
+ public object Get(GetDefaultDirectoryBrowser request)
+ {
+ var result = new DefaultDirectoryBrowserInfo();
+
+ if (Environment.OSVersion.Platform == PlatformID.Unix)
+ {
+ try
+ {
+ var qnap = "/share/CACHEDEV1_DATA";
+ if (Directory.Exists(qnap))
+ {
+ result.Path = qnap;
+ }
+ }
+ catch
+ {
+
+ }
+ }
+
+ return ToOptimizedResult(result);
+ }
+
/// <summary>
/// Gets the specified request.
/// </summary>
diff --git a/MediaBrowser.Api/FilterService.cs b/MediaBrowser.Api/FilterService.cs
index 6d1c5d868..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)
@@ -103,7 +103,8 @@ namespace MediaBrowser.Api
User = user,
MediaTypes = request.GetMediaTypes(),
IncludeItemTypes = request.GetIncludeItemTypes(),
- Recursive = true
+ Recursive = true,
+ EnableTotalRecordCount = false
};
return query;
diff --git a/MediaBrowser.Api/GamesService.cs b/MediaBrowser.Api/GamesService.cs
index 040872fcc..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();
@@ -162,7 +162,10 @@ namespace MediaBrowser.Api
var items = user == null ?
system.GetRecursiveChildren(i => i is Game) :
- system.GetRecursiveChildren(user, i => i is Game);
+ system.GetRecursiveChildren(user, new InternalItemsQuery(user)
+ {
+ IncludeItemTypes = new[] { typeof(Game).Name }
+ });
var games = items.Cast<Game>().ToList();
@@ -182,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..5866ad15b 100644
--- a/MediaBrowser.Api/Images/ImageService.cs
+++ b/MediaBrowser.Api/Images/ImageService.cs
@@ -273,7 +273,9 @@ namespace MediaBrowser.Api.Images
{
var list = new List<ImageInfo>();
- foreach (var image in item.ImageInfos.Where(i => !item.AllowsMultipleImages(i.Type)))
+ var itemImages = item.ImageInfos;
+
+ foreach (var image in itemImages.Where(i => !item.AllowsMultipleImages(i.Type)))
{
var info = GetImageInfo(item, image, null);
@@ -283,14 +285,14 @@ namespace MediaBrowser.Api.Images
}
}
- foreach (var imageType in item.ImageInfos.Select(i => i.Type).Distinct().Where(item.AllowsMultipleImages))
+ foreach (var imageType in itemImages.Select(i => i.Type).Distinct().Where(item.AllowsMultipleImages))
{
var index = 0;
// Prevent implicitly captured closure
var currentImageType = imageType;
- foreach (var image in item.ImageInfos.Where(i => i.Type == currentImageType))
+ foreach (var image in itemImages.Where(i => i.Type == currentImageType))
{
var info = GetImageInfo(item, image, index);
@@ -514,7 +516,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 +596,7 @@ namespace MediaBrowser.Api.Images
supportedImageEnhancers,
cacheDuration,
responseHeaders,
- isHeadRequest)
- .Result;
+ isHeadRequest);
}
private async Task<object> GetImageResult(IHasImages item,
@@ -632,18 +633,20 @@ namespace MediaBrowser.Api.Images
headers["Vary"] = "Accept";
- return ResultFactory.GetStaticFileResult(Request, new StaticFileResultOptions
+ return await ResultFactory.GetStaticFileResult(Request, new StaticFileResultOptions
{
CacheDuration = cacheDuration,
ResponseHeaders = headers,
ContentType = imageResult.Item2,
+ DateLastModified = imageResult.Item3,
IsHeadRequest = isHeadRequest,
Path = imageResult.Item1,
// 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 8be37e210..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)
@@ -202,14 +213,19 @@ namespace MediaBrowser.Api
// }
//}
Logger.Info("Setting provider id's to item {0}-{1}: {2}", item.Id, item.Name, _json.SerializeToString(request.ProviderIds));
+
+ // Since the refresh process won't erase provider Ids, we need to set this explicitly now.
item.ProviderIds = request.ProviderIds;
+ //item.ProductionYear = request.ProductionYear;
+ //item.Name = request.Name;
- var task = _providerManager.RefreshFullItem(item, new MetadataRefreshOptions(_fileSystem)
+ var task = _providerManager.RefreshFullItem(item, new MetadataRefreshOptions(_fileSystem)
{
MetadataRefreshMode = MetadataRefreshMode.FullRefresh,
ImageRefreshMode = ImageRefreshMode.FullRefresh,
ReplaceAllMetadata = true,
- ReplaceAllImages = request.ReplaceAllImages
+ ReplaceAllImages = request.ReplaceAllImages,
+ SearchResult = request
}, CancellationToken.None);
Task.WaitAll(task);
@@ -234,9 +250,9 @@ namespace MediaBrowser.Api
contentPath = await reader.ReadToEndAsync().ConfigureAwait(false);
}
- if (_fileSystem.FileExists(contentPath))
+ if (_fileSystem.FileExists(contentPath))
{
- return ToStaticFileResult(contentPath);
+ return await ResultFactory.GetStaticFileResult(Request, contentPath).ConfigureAwait(false);
}
}
catch (DirectoryNotFoundException)
@@ -256,7 +272,7 @@ namespace MediaBrowser.Api
contentPath = await reader.ReadToEndAsync().ConfigureAwait(false);
}
- return ToStaticFileResult(contentPath);
+ return await ResultFactory.GetStaticFileResult(Request, contentPath).ConfigureAwait(false);
}
/// <summary>
@@ -275,7 +291,7 @@ namespace MediaBrowser.Api
var fullCachePath = GetFullCachePath(urlHash + "." + ext);
- _fileSystem.CreateDirectory(Path.GetDirectoryName(fullCachePath));
+ _fileSystem.CreateDirectory(Path.GetDirectoryName(fullCachePath));
using (var stream = result.Content)
{
using (var filestream = _fileSystem.GetFileStream(fullCachePath, FileMode.Create, FileAccess.Write, FileShare.Read, true))
@@ -284,7 +300,7 @@ namespace MediaBrowser.Api
}
}
- _fileSystem.CreateDirectory(Path.GetDirectoryName(pointerCachePath));
+ _fileSystem.CreateDirectory(Path.GetDirectoryName(pointerCachePath));
using (var writer = new StreamWriter(pointerCachePath))
{
await writer.WriteAsync(fullCachePath).ConfigureAwait(false);
diff --git a/MediaBrowser.Api/ItemUpdateService.cs b/MediaBrowser.Api/ItemUpdateService.cs
index f37874774..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();
}
}
}
@@ -247,6 +242,12 @@ namespace MediaBrowser.Api
hasBudget.Revenue = request.Revenue;
}
+ var hasOriginalTitle = item as IHasOriginalTitle;
+ if (hasOriginalTitle != null)
+ {
+ hasOriginalTitle.OriginalTitle = hasOriginalTitle.OriginalTitle;
+ }
+
var hasCriticRating = item as IHasCriticRating;
if (hasCriticRating != null)
{
@@ -274,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)
@@ -292,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)
{
@@ -421,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/FileOrganizationService.cs b/MediaBrowser.Api/Library/FileOrganizationService.cs
index 849e9cf0d..0ed08a860 100644
--- a/MediaBrowser.Api/Library/FileOrganizationService.cs
+++ b/MediaBrowser.Api/Library/FileOrganizationService.cs
@@ -119,8 +119,6 @@ namespace MediaBrowser.Api.Library
{
private readonly IFileOrganizationService _iFileOrganizationService;
- /// The _json serializer
- /// </summary>
private readonly IJsonSerializer _jsonSerializer;
public FileOrganizationService(IFileOrganizationService iFileOrganizationService, IJsonSerializer jsonSerializer)
diff --git a/MediaBrowser.Api/Library/LibraryHelpers.cs b/MediaBrowser.Api/Library/LibraryHelpers.cs
deleted file mode 100644
index 46ec4f270..000000000
--- a/MediaBrowser.Api/Library/LibraryHelpers.cs
+++ /dev/null
@@ -1,89 +0,0 @@
-using MediaBrowser.Controller;
-using System;
-using System.IO;
-using System.Linq;
-using CommonIO;
-
-namespace MediaBrowser.Api.Library
-{
- /// <summary>
- /// Class LibraryHelpers
- /// </summary>
- public static class LibraryHelpers
- {
- /// <summary>
- /// The shortcut file extension
- /// </summary>
- private const string ShortcutFileExtension = ".mblink";
- /// <summary>
- /// The shortcut file search
- /// </summary>
- private const string ShortcutFileSearch = "*" + ShortcutFileExtension;
-
- /// <summary>
- /// Deletes a shortcut from within a virtual folder, within either the default view or a user view
- /// </summary>
- /// <param name="fileSystem">The file system.</param>
- /// <param name="virtualFolderName">Name of the virtual folder.</param>
- /// <param name="mediaPath">The media path.</param>
- /// <param name="appPaths">The app paths.</param>
- /// <exception cref="System.IO.DirectoryNotFoundException">The media folder does not exist</exception>
- public static void RemoveMediaPath(IFileSystem fileSystem, string virtualFolderName, string mediaPath, IServerApplicationPaths appPaths)
- {
- if (string.IsNullOrWhiteSpace(mediaPath))
- {
- throw new ArgumentNullException("mediaPath");
- }
-
- var rootFolderPath = appPaths.DefaultUserViewsPath;
- var path = Path.Combine(rootFolderPath, virtualFolderName);
-
- if (!fileSystem.DirectoryExists(path))
- {
- throw new DirectoryNotFoundException(string.Format("The media collection {0} does not exist", virtualFolderName));
- }
-
- var shortcut = Directory.EnumerateFiles(path, ShortcutFileSearch, SearchOption.AllDirectories).FirstOrDefault(f => fileSystem.ResolveShortcut(f).Equals(mediaPath, StringComparison.OrdinalIgnoreCase));
-
- if (!string.IsNullOrEmpty(shortcut))
- {
- fileSystem.DeleteFile(shortcut);
- }
- }
-
- /// <summary>
- /// Adds an additional mediaPath to an existing virtual folder, within either the default view or a user view
- /// </summary>
- /// <param name="fileSystem">The file system.</param>
- /// <param name="virtualFolderName">Name of the virtual folder.</param>
- /// <param name="path">The path.</param>
- /// <param name="appPaths">The app paths.</param>
- public static void AddMediaPath(IFileSystem fileSystem, string virtualFolderName, string path, IServerApplicationPaths appPaths)
- {
- if (string.IsNullOrWhiteSpace(path))
- {
- throw new ArgumentNullException("path");
- }
-
- if (!fileSystem.DirectoryExists(path))
- {
- throw new DirectoryNotFoundException("The path does not exist.");
- }
-
- var rootFolderPath = appPaths.DefaultUserViewsPath;
- var virtualFolderPath = Path.Combine(rootFolderPath, virtualFolderName);
-
- var shortcutFilename = fileSystem.GetFileNameWithoutExtension(path);
-
- var lnk = Path.Combine(virtualFolderPath, shortcutFilename + ShortcutFileExtension);
-
- while (fileSystem.FileExists(lnk))
- {
- shortcutFilename += "1";
- lnk = Path.Combine(virtualFolderPath, shortcutFilename + ShortcutFileExtension);
- }
-
- fileSystem.CreateShortcut(lnk, path);
- }
- }
-}
diff --git a/MediaBrowser.Api/Library/LibraryService.cs b/MediaBrowser.Api/Library/LibraryService.cs
index f9b3def97..14a771db0 100644
--- a/MediaBrowser.Api/Library/LibraryService.cs
+++ b/MediaBrowser.Api/Library/LibraryService.cs
@@ -350,7 +350,8 @@ namespace MediaBrowser.Api.Library
Fields = request.Fields,
Id = request.Id,
Limit = request.Limit,
- UserId = request.UserId
+ UserId = request.UserId,
+ ExcludeArtistIds = request.ExcludeArtistIds
});
}
if (item is MusicArtist)
@@ -493,7 +494,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 +553,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 +566,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>
@@ -839,6 +840,7 @@ namespace MediaBrowser.Api.Library
var dtoOptions = GetDtoOptions(request);
var dtos = GetThemeSongIds(item).Select(_libraryManager.GetItemById)
+ .Where(i => i != null)
.OrderBy(i => i.SortName)
.Select(i => _dtoService.GetBaseItemDto(i, dtoOptions, user, item));
@@ -882,6 +884,7 @@ namespace MediaBrowser.Api.Library
var dtoOptions = GetDtoOptions(request);
var dtos = GetThemeVideoIds(item).Select(_libraryManager.GetItemById)
+ .Where(i => i != null)
.OrderBy(i => i.SortName)
.Select(i => _dtoService.GetBaseItemDto(i, dtoOptions, user, item));
diff --git a/MediaBrowser.Api/Library/LibraryStructureService.cs b/MediaBrowser.Api/Library/LibraryStructureService.cs
index 244dcf09f..3cf0d5d93 100644
--- a/MediaBrowser.Api/Library/LibraryStructureService.cs
+++ b/MediaBrowser.Api/Library/LibraryStructureService.cs
@@ -190,75 +190,7 @@ namespace MediaBrowser.Api.Library
/// <param name="request">The request.</param>
public void Post(AddVirtualFolder request)
{
- if (string.IsNullOrWhiteSpace(request.Name))
- {
- throw new ArgumentNullException("request");
- }
-
- var name = _fileSystem.GetValidFilename(request.Name);
-
- var rootFolderPath = _appPaths.DefaultUserViewsPath;
-
- var virtualFolderPath = Path.Combine(rootFolderPath, name);
- while (_fileSystem.DirectoryExists(virtualFolderPath))
- {
- name += "1";
- virtualFolderPath = Path.Combine(rootFolderPath, name);
- }
-
- if (request.Paths != null)
- {
- var invalidpath = request.Paths.FirstOrDefault(i => !_fileSystem.DirectoryExists(i));
- if (invalidpath != null)
- {
- throw new ArgumentException("The specified path does not exist: " + invalidpath + ".");
- }
- }
-
- _libraryMonitor.Stop();
-
- try
- {
- _fileSystem.CreateDirectory(virtualFolderPath);
-
- if (!string.IsNullOrEmpty(request.CollectionType))
- {
- var path = Path.Combine(virtualFolderPath, request.CollectionType + ".collection");
-
- using (File.Create(path))
- {
-
- }
- }
-
- if (request.Paths != null)
- {
- foreach (var path in request.Paths)
- {
- LibraryHelpers.AddMediaPath(_fileSystem, name, path, _appPaths);
- }
- }
- }
- finally
- {
- Task.Run(() =>
- {
- // No need to start if scanning the library because it will handle it
- if (request.RefreshLibrary)
- {
- _libraryManager.ValidateMediaLibrary(new Progress<double>(), CancellationToken.None);
- }
- else
- {
- // Need to add a delay here or directory watchers may still pick up the changes
- var task = Task.Delay(1000);
- // Have to block here to allow exceptions to bubble
- Task.WaitAll(task);
-
- _libraryMonitor.Start();
- }
- });
- }
+ _libraryManager.AddVirtualFolder(request.Name, request.CollectionType, request.Paths, request.RefreshLibrary);
}
/// <summary>
@@ -336,46 +268,7 @@ namespace MediaBrowser.Api.Library
/// <param name="request">The request.</param>
public void Delete(RemoveVirtualFolder request)
{
- if (string.IsNullOrWhiteSpace(request.Name))
- {
- throw new ArgumentNullException("request");
- }
-
- var rootFolderPath = _appPaths.DefaultUserViewsPath;
-
- var path = Path.Combine(rootFolderPath, request.Name);
-
- if (!_fileSystem.DirectoryExists(path))
- {
- throw new DirectoryNotFoundException("The media folder does not exist");
- }
-
- _libraryMonitor.Stop();
-
- try
- {
- _fileSystem.DeleteDirectory(path, true);
- }
- finally
- {
- Task.Run(() =>
- {
- // No need to start if scanning the library because it will handle it
- if (request.RefreshLibrary)
- {
- _libraryManager.ValidateMediaLibrary(new Progress<double>(), CancellationToken.None);
- }
- else
- {
- // Need to add a delay here or directory watchers may still pick up the changes
- var task = Task.Delay(1000);
- // Have to block here to allow exceptions to bubble
- Task.WaitAll(task);
-
- _libraryMonitor.Start();
- }
- });
- }
+ _libraryManager.RemoveVirtualFolder(request.Name, request.RefreshLibrary);
}
/// <summary>
@@ -393,7 +286,7 @@ namespace MediaBrowser.Api.Library
try
{
- LibraryHelpers.AddMediaPath(_fileSystem, request.Name, request.Path, _appPaths);
+ _libraryManager.AddMediaPath(request.Name, request.Path);
}
finally
{
@@ -432,7 +325,7 @@ namespace MediaBrowser.Api.Library
try
{
- LibraryHelpers.RemoveMediaPath(_fileSystem, request.Name, request.Path, _appPaths);
+ _libraryManager.RemoveMediaPath(request.Name, request.Path);
}
finally
{
diff --git a/MediaBrowser.Api/LiveTv/LiveTvService.cs b/MediaBrowser.Api/LiveTv/LiveTvService.cs
index 5b7bc78a8..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..")]
@@ -254,6 +263,8 @@ namespace MediaBrowser.Api.LiveTv
[ApiMember(Name = "EnableImages", Description = "Optional, include image information in output", IsRequired = false, DataType = "boolean", ParameterType = "query", Verb = "GET")]
public bool? EnableImages { get; set; }
+ public bool EnableTotalRecordCount { get; set; }
+
[ApiMember(Name = "ImageTypeLimit", Description = "Optional, the max number of images to return, per image type", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")]
public int? ImageTypeLimit { get; set; }
@@ -266,12 +277,24 @@ 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 GetPrograms()
+ {
+ EnableTotalRecordCount = true;
+ }
}
[Route("/LiveTv/Programs/Recommended", "GET", Summary = "Gets available live tv epgs..")]
[Authenticated]
public class GetRecommendedPrograms : IReturn<QueryResult<BaseItemDto>>, IHasDtoOptions
{
+ public bool EnableTotalRecordCount { get; set; }
+
+ public GetRecommendedPrograms()
+ {
+ EnableTotalRecordCount = true;
+ }
+
[ApiMember(Name = "UserId", Description = "Optional filter by user id.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET,POST")]
public string UserId { get; set; }
@@ -425,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>
@@ -464,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>
@@ -482,7 +537,14 @@ namespace MediaBrowser.Api.LiveTv
[Authenticated(AllowBeforeStartupWizard = true)]
public class GetSatIniMappings : IReturn<List<NameValuePair>>
{
-
+
+ }
+
+ [Route("/LiveTv/TunerHosts/Satip/ChannelScan", "GET", Summary = "Scans for available channels")]
+ [Authenticated(AllowBeforeStartupWizard = true)]
+ public class GetSatChannnelScanResult : TunerHostInfo
+ {
+
}
public class LiveTvService : BaseApiService
@@ -504,6 +566,18 @@ 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);
+
+ return ToOptimizedResult(result);
+ }
+
public async Task<object> Get(GetLiveTvRegistrationInfo request)
{
var result = await _liveTvManager.GetRegistrationInfo(request.ChannelId, request.ProgramId, request.Feature).ConfigureAwait(false);
@@ -511,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());
@@ -522,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);
@@ -554,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)
@@ -581,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);
@@ -613,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);
}
@@ -648,7 +761,8 @@ namespace MediaBrowser.Api.LiveTv
{
ChannelIds = (request.ChannelIds ?? string.Empty).Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries).ToArray(),
UserId = request.UserId,
- HasAired = request.HasAired
+ HasAired = request.HasAired,
+ EnableTotalRecordCount = request.EnableTotalRecordCount
};
if (!string.IsNullOrEmpty(request.MinStartDate))
@@ -695,7 +809,8 @@ namespace MediaBrowser.Api.LiveTv
HasAired = request.HasAired,
IsMovie = request.IsMovie,
IsKids = request.IsKids,
- IsSports = request.IsSports
+ IsSports = request.IsSports,
+ EnableTotalRecordCount = request.EnableTotalRecordCount
};
var result = await _liveTvManager.GetRecommendedPrograms(query, GetDtoOptions(request), CancellationToken.None).ConfigureAwait(false);
@@ -722,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);
@@ -753,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/MediaBrowser.Api.csproj b/MediaBrowser.Api/MediaBrowser.Api.csproj
index cdc0cd6ae..77949d179 100644
--- a/MediaBrowser.Api/MediaBrowser.Api.csproj
+++ b/MediaBrowser.Api/MediaBrowser.Api.csproj
@@ -80,8 +80,6 @@
<Compile Include="FilterService.cs" />
<Compile Include="IHasDtoOptions.cs" />
<Compile Include="Library\ChapterService.cs" />
- <Compile Include="Playback\Dash\ManifestBuilder.cs" />
- <Compile Include="Playback\Dash\MpegDashService.cs" />
<Compile Include="Playback\MediaInfoService.cs" />
<Compile Include="Playback\TranscodingThrottler.cs" />
<Compile Include="PlaylistService.cs" />
@@ -131,7 +129,6 @@
<Compile Include="ItemUpdateService.cs" />
<Compile Include="Library\LibraryService.cs" />
<Compile Include="Library\FileOrganizationService.cs" />
- <Compile Include="Library\LibraryHelpers.cs" />
<Compile Include="Library\LibraryStructureService.cs" />
<Compile Include="LiveTv\LiveTvService.cs" />
<Compile Include="LocalizationService.cs" />
diff --git a/MediaBrowser.Api/Movies/MoviesService.cs b/MediaBrowser.Api/Movies/MoviesService.cs
index e06b2c7f8..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,152 +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);
- movies = _libraryManager.ReplaceVideosWithPrimaryVersions(movies);
-
- var listEligibleForCategories = new List<BaseItem>();
- var listEligibleForSuggestion = new List<BaseItem>();
-
- var list = movies.ToList();
-
- listEligibleForCategories.AddRange(list);
- listEligibleForSuggestion.AddRange(list);
-
- listEligibleForCategories = listEligibleForCategories
- .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 itemsResult = _libraryManager.GetItemList(new InternalItemsQuery(user)
{
- var includeList = query.IncludeItemTypes.ToList();
- includeList.Add(typeof(Trailer).Name);
- query.IncludeItemTypes = includeList.ToArray();
- }
-
- var parentIds = new string[] { };
- var list = _libraryManager.GetItemList(query, parentIds)
- .Where(i =>
+ Limit = request.Limit,
+ IncludeItemTypes = new[]
{
- // Strip out secondary versions
- var v = i as Video;
- return v != null && !v.PrimaryVersionId.HasValue;
- })
- .DistinctBy(i => i.GetProviderId(MetadataProviders.Imdb) ?? Guid.NewGuid().ToString("N"))
- .ToList();
-
- if (item is Video)
- {
- var imdbId = item.GetProviderId(MetadataProviders.Imdb);
-
- // Use imdb id to try to filter duplicates of the same item
- if (!string.IsNullOrWhiteSpace(imdbId))
- {
- list = list
- .Where(i => !string.Equals(imdbId, i.GetProviderId(MetadataProviders.Imdb), StringComparison.OrdinalIgnoreCase))
- .ToList();
- }
- }
-
- var items = SimilarItemsHelper.GetSimilaritems(item, _libraryManager, list, getSimilarityScore).ToList();
+ typeof(Movie).Name,
+ typeof(Trailer).Name,
+ typeof(LiveTvProgram).Name
+ },
+ IsMovie = true,
+ SimilarTo = item,
+ EnableGroupByMetadataKey = true
- IEnumerable<BaseItem> returnItems = items;
-
- 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.Id, i.GetUserDataKey());
- 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.Id, i.GetUserDataKey());
+ 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
@@ -288,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>>
{
@@ -335,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)
{
@@ -381,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)
{
@@ -403,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 44bc95ed1..ce3691095 100644
--- a/MediaBrowser.Api/Playback/BaseStreamingService.cs
+++ b/MediaBrowser.Api/Playback/BaseStreamingService.cs
@@ -286,13 +286,25 @@ namespace MediaBrowser.Api.Playback
protected string GetH264Encoder(StreamState state)
{
- if (string.Equals(ApiEntryPoint.Instance.GetEncodingOptions().HardwareAccelerationType, "qsv", StringComparison.OrdinalIgnoreCase))
+ // Only use alternative encoders for video files.
+ // When using concat with folder rips, if the mfx session fails to initialize, ffmpeg will be stuck retrying and will not exit gracefully
+ // Since transcoding of folder rips is expiremental anyway, it's not worth adding additional variables such as this.
+ if (state.VideoType == VideoType.VideoFile)
{
- // It's currently failing on live tv
- if (state.RunTimeTicks.HasValue)
+ 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, "nvenc", StringComparison.OrdinalIgnoreCase))
+ {
+ return "h264_nvenc";
+ }
+ if (string.Equals(ApiEntryPoint.Instance.GetEncodingOptions().HardwareAccelerationType, "h264_omx", StringComparison.OrdinalIgnoreCase))
+ {
+ return "h264_omx";
+ }
}
return "libx264";
@@ -332,10 +344,10 @@ namespace MediaBrowser.Api.Playback
}
- // h264 (libnvenc)
- else if (string.Equals(videoCodec, "libnvenc", StringComparison.OrdinalIgnoreCase))
+ // h264 (h264_nvenc)
+ else if (string.Equals(videoCodec, "h264_nvenc", StringComparison.OrdinalIgnoreCase))
{
- param = "-preset high-performance";
+ param = "-preset default";
}
// webm
@@ -397,15 +409,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))
+ // h264_qsv and h264_nvenc expect levels to be expressed as a decimal. libx264 supports decimal and non-decimal format
+ if (string.Equals(videoCodec, "h264_qsv", StringComparison.OrdinalIgnoreCase) ||
+ string.Equals(videoCodec, "h264_nvenc", StringComparison.OrdinalIgnoreCase))
{
switch (state.VideoRequest.Level)
{
@@ -441,13 +456,20 @@ namespace MediaBrowser.Api.Playback
break;
}
}
- 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, "h264_nvenc", StringComparison.OrdinalIgnoreCase))
+ {
+ param = "-pix_fmt yuv420p " + param;
+ }
+
+ return param;
}
protected string GetAudioFilterParam(StreamState state, bool isHls)
@@ -460,7 +482,7 @@ namespace MediaBrowser.Api.Playback
// Boost volume to 200% when downsampling from 6ch to 2ch
if (channels.HasValue && channels.Value <= 2)
{
- if (state.AudioStream != null && state.AudioStream.Channels.HasValue && state.AudioStream.Channels.Value > 5)
+ if (state.AudioStream != null && state.AudioStream.Channels.HasValue && state.AudioStream.Channels.Value > 5 && !ApiEntryPoint.Instance.GetEncodingOptions().DownMixAudioBoost.Equals(1))
{
volParam = ",volume=" + ApiEntryPoint.Instance.GetEncodingOptions().DownMixAudioBoost.ToString(UsCulture);
}
@@ -563,14 +585,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)
@@ -614,7 +628,7 @@ namespace MediaBrowser.Api.Playback
if (!string.IsNullOrEmpty(state.SubtitleStream.Language))
{
- var charenc = SubtitleEncoder.GetSubtitleFileCharacterSet(subtitlePath, state.MediaSource.Protocol, CancellationToken.None).Result;
+ var charenc = SubtitleEncoder.GetSubtitleFileCharacterSet(subtitlePath, state.SubtitleStream.Language, state.MediaSource.Protocol, CancellationToken.None).Result;
if (!string.IsNullOrEmpty(charenc))
{
@@ -712,15 +726,16 @@ namespace MediaBrowser.Api.Playback
inputChannels = null;
}
+ int? resultChannels = null;
var codec = outputAudioCodec ?? string.Empty;
if (codec.IndexOf("wma", StringComparison.OrdinalIgnoreCase) != -1)
{
// wmav2 currently only supports two channel output
- return Math.Min(2, inputChannels ?? 2);
+ resultChannels = Math.Min(2, inputChannels ?? 2);
}
- if (request.MaxAudioChannels.HasValue)
+ else if (request.MaxAudioChannels.HasValue)
{
var channelLimit = codec.IndexOf("mp3", StringComparison.OrdinalIgnoreCase) != -1
? 2
@@ -732,10 +747,18 @@ namespace MediaBrowser.Api.Playback
}
// If we don't have any media info then limit it to 5 to prevent encoding errors due to asking for too many channels
- return Math.Min(request.MaxAudioChannels.Value, channelLimit);
+ resultChannels = Math.Min(request.MaxAudioChannels.Value, channelLimit);
}
- return request.AudioChannels;
+ if (resultChannels.HasValue && !string.Equals(codec, "copy", StringComparison.OrdinalIgnoreCase))
+ {
+ if (request.TranscodingMaxAudioChannels.HasValue)
+ {
+ resultChannels = Math.Min(request.TranscodingMaxAudioChannels.Value, resultChannels.Value);
+ }
+ }
+
+ return resultChannels ?? request.AudioChannels;
}
/// <summary>
@@ -821,9 +844,22 @@ namespace MediaBrowser.Api.Playback
/// <returns>System.String.</returns>
protected string GetVideoDecoder(StreamState state)
{
- if (string.Equals(ApiEntryPoint.Instance.GetEncodingOptions().HardwareAccelerationType, "qsv", StringComparison.OrdinalIgnoreCase))
+ if (string.Equals(state.OutputVideoCodec, "copy", StringComparison.OrdinalIgnoreCase))
+ {
+ return null;
+ }
+
+ // Only use alternative encoders for video files.
+ // When using concat with folder rips, if the mfx session fails to initialize, ffmpeg will be stuck retrying and will not exit gracefully
+ // Since transcoding of folder rips is expiremental anyway, it's not worth adding additional variables such as this.
+ if (state.VideoType != VideoType.VideoFile)
+ {
+ return null;
+ }
+
+ if (state.VideoStream != null && !string.IsNullOrWhiteSpace(state.VideoStream.Codec))
{
- if (state.VideoStream != null && !string.IsNullOrWhiteSpace(state.VideoStream.Codec))
+ if (string.Equals(ApiEntryPoint.Instance.GetEncodingOptions().HardwareAccelerationType, "qsv", StringComparison.OrdinalIgnoreCase))
{
switch (state.MediaSource.VideoStream.Codec.ToLower())
{
@@ -831,6 +867,7 @@ namespace MediaBrowser.Api.Playback
case "h264":
if (MediaEncoder.SupportsDecoder("h264_qsv"))
{
+ // Seeing stalls and failures with decoding. Not worth it compared to encoding.
return "-c:v h264_qsv ";
}
break;
@@ -947,14 +984,24 @@ namespace MediaBrowser.Api.Playback
await AcquireResources(state, cancellationTokenSource).ConfigureAwait(false);
- var transcodingId = Guid.NewGuid().ToString("N");
- var commandLineArgs = GetCommandLineArguments(outputPath, state, true);
-
- if (ApiEntryPoint.Instance.GetEncodingOptions().EnableDebugLogging)
+ if (state.VideoRequest != null && !string.Equals(state.OutputVideoCodec, "copy", StringComparison.OrdinalIgnoreCase))
{
- commandLineArgs = "-loglevel debug " + commandLineArgs;
+ var auth = AuthorizationContext.GetAuthorizationInfo(Request);
+ if (!string.IsNullOrWhiteSpace(auth.UserId))
+ {
+ var user = UserManager.GetUserById(auth.UserId);
+ if (!user.Policy.EnableVideoPlaybackTranscoding)
+ {
+ ApiEntryPoint.Instance.OnTranscodeFailedToStart(outputPath, TranscodingJobType, state);
+
+ throw new ArgumentException("User does not have access to video transcoding");
+ }
+ }
}
+ var transcodingId = Guid.NewGuid().ToString("N");
+ var commandLineArgs = GetCommandLineArguments(outputPath, state, true);
+
var process = new Process
{
StartInfo = new ProcessStartInfo
@@ -963,7 +1010,7 @@ namespace MediaBrowser.Api.Playback
UseShellExecute = false,
// Must consume both stdout and stderr or deadlocks may occur
- RedirectStandardOutput = true,
+ //RedirectStandardOutput = true,
RedirectStandardError = true,
RedirectStandardInput = true,
@@ -995,7 +1042,17 @@ namespace MediaBrowser.Api.Playback
var commandLineLogMessage = process.StartInfo.FileName + " " + process.StartInfo.Arguments;
Logger.Info(commandLineLogMessage);
- var logFilePath = Path.Combine(ServerConfigurationManager.ApplicationPaths.LogDirectoryPath, "transcode-" + Guid.NewGuid() + ".txt");
+ var logFilePrefix = "transcode";
+ if (state.VideoRequest != null && string.Equals(state.OutputVideoCodec, "copy", StringComparison.OrdinalIgnoreCase) && string.Equals(state.OutputAudioCodec, "copy", StringComparison.OrdinalIgnoreCase))
+ {
+ logFilePrefix = "directstream";
+ }
+ else if (state.VideoRequest != null && string.Equals(state.OutputVideoCodec, "copy", StringComparison.OrdinalIgnoreCase))
+ {
+ logFilePrefix = "remux";
+ }
+
+ var logFilePath = Path.Combine(ServerConfigurationManager.ApplicationPaths.LogDirectoryPath, logFilePrefix + "-" + Guid.NewGuid() + ".txt");
FileSystem.CreateDirectory(Path.GetDirectoryName(logFilePath));
// FFMpeg writes debug/error info to stderr. This is useful when debugging so let's put it in the log directory.
@@ -1020,13 +1077,13 @@ namespace MediaBrowser.Api.Playback
}
// MUST read both stdout and stderr asynchronously or a deadlock may occurr
- process.BeginOutputReadLine();
+ //process.BeginOutputReadLine();
// Important - don't await the log task or we won't be able to kill ffmpeg when the user stops playback
- StartStreamingLog(transcodingJob, state, process.StandardError.BaseStream, state.LogFileStream);
+ Task.Run(() => StartStreamingLog(transcodingJob, state, process.StandardError.BaseStream, state.LogFileStream));
// Wait for the file to exist before proceeeding
- while (!FileSystem.FileExists(state.WaitForPath ?? outputPath) && !transcodingJob.HasExited)
+ while (!FileSystem.FileExists(state.WaitForPath ?? outputPath) && !transcodingJob.HasExited)
{
await Task.Delay(100, cancellationTokenSource.Token).ConfigureAwait(false);
}
@@ -1066,7 +1123,7 @@ namespace MediaBrowser.Api.Playback
return true;
}
- private async void StartStreamingLog(TranscodingJob transcodingJob, StreamState state, Stream source, Stream target)
+ private async Task StartStreamingLog(TranscodingJob transcodingJob, StreamState state, Stream source, Stream target)
{
try
{
@@ -1172,7 +1229,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;
@@ -1197,6 +1254,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;
}
@@ -1452,10 +1521,7 @@ namespace MediaBrowser.Api.Playback
}
else if (i == 19)
{
- if (videoRequest != null)
- {
- videoRequest.Cabac = string.Equals("true", val, StringComparison.OrdinalIgnoreCase);
- }
+ // cabac no longer used
}
else if (i == 20)
{
@@ -1482,6 +1548,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;
@@ -1491,6 +1564,17 @@ namespace MediaBrowser.Api.Playback
}
}
}
+ else if (i == 27)
+ {
+ request.TranscodingMaxAudioChannels = int.Parse(val, UsCulture);
+ }
+ else if (i == 28)
+ {
+ if (videoRequest != null)
+ {
+ videoRequest.EnableSubtitlesInManifest = string.Equals("true", val, StringComparison.OrdinalIgnoreCase);
+ }
+ }
}
}
@@ -1582,7 +1666,8 @@ namespace MediaBrowser.Api.Playback
var state = new StreamState(MediaSourceManager, Logger)
{
Request = request,
- RequestedUrl = url
+ RequestedUrl = url,
+ UserAgent = Request.UserAgent
};
//if ((Request.UserAgent ?? string.Empty).IndexOf("iphone", StringComparison.OrdinalIgnoreCase) != -1 ||
@@ -1595,7 +1680,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);
@@ -1649,14 +1735,14 @@ 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)
{
var resolution = ResolutionNormalizer.Normalize(
- state.VideoStream == null ? (int?)null : state.VideoStream.BitRate,
- state.OutputVideoBitrate.Value,
- state.VideoStream == null ? null : state.VideoStream.Codec,
+ state.VideoStream == null ? (int?)null : state.VideoStream.BitRate,
+ state.OutputVideoBitrate.Value,
+ state.VideoStream == null ? null : state.VideoStream.Codec,
state.OutputVideoCodec,
videoRequest.MaxWidth,
videoRequest.MaxHeight);
@@ -1680,12 +1766,25 @@ namespace MediaBrowser.Api.Playback
private void TryStreamCopy(StreamState state, VideoStreamRequest videoRequest)
{
- if (state.VideoStream != null && CanStreamCopyVideo(videoRequest, state.VideoStream))
+ if (state.VideoStream != null && CanStreamCopyVideo(state))
{
state.OutputVideoCodec = "copy";
}
+ else
+ {
+ // If the user doesn't have access to transcoding, then force stream copy, regardless of whether it will be compatible or not
+ var auth = AuthorizationContext.GetAuthorizationInfo(Request);
+ if (!string.IsNullOrWhiteSpace(auth.UserId))
+ {
+ var user = UserManager.GetUserById(auth.UserId);
+ if (!user.Policy.EnableVideoPlaybackTranscoding)
+ {
+ state.OutputVideoCodec = "copy";
+ }
+ }
+ }
- if (state.AudioStream != null && CanStreamCopyAudio(videoRequest, state.AudioStream, state.SupportedAudioCodecs))
+ if (state.AudioStream != null && CanStreamCopyAudio(state, state.SupportedAudioCodecs))
{
state.OutputAudioCodec = "copy";
}
@@ -1773,8 +1872,11 @@ namespace MediaBrowser.Api.Playback
state.MediaSource = mediaSource;
}
- protected virtual bool CanStreamCopyVideo(VideoStreamRequest request, MediaStream videoStream)
+ protected virtual bool CanStreamCopyVideo(StreamState state)
{
+ var request = state.VideoRequest;
+ var videoStream = state.VideoStream;
+
if (videoStream.IsInterlaced)
{
return false;
@@ -1784,7 +1886,7 @@ namespace MediaBrowser.Api.Playback
{
return false;
}
-
+
// Can't stream copy if we're burning in subtitles
if (request.SubtitleStreamIndex.HasValue)
{
@@ -1794,6 +1896,15 @@ namespace MediaBrowser.Api.Playback
}
}
+ if (string.Equals("h264", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
+ {
+ if (videoStream.IsAVC.HasValue && !videoStream.IsAVC.Value)
+ {
+ Logger.Debug("Cannot stream copy video. Stream is marked as not AVC");
+ return false;
+ }
+ }
+
// Source and target codecs must match
if (!string.Equals(request.VideoCodec, videoStream.Codec, StringComparison.OrdinalIgnoreCase))
{
@@ -1805,10 +1916,10 @@ namespace MediaBrowser.Api.Playback
{
if (string.IsNullOrEmpty(videoStream.Profile))
{
- return false;
+ //return false;
}
- if (!string.Equals(request.Profile, videoStream.Profile, StringComparison.OrdinalIgnoreCase))
+ if (!string.IsNullOrEmpty(videoStream.Profile) && !string.Equals(request.Profile, videoStream.Profile, StringComparison.OrdinalIgnoreCase))
{
var currentScore = GetVideoProfileScore(videoStream.Profile);
var requestedScore = GetVideoProfileScore(request.Profile);
@@ -1884,24 +1995,16 @@ namespace MediaBrowser.Api.Playback
{
if (!videoStream.Level.HasValue)
{
- return false;
+ //return false;
}
- if (videoStream.Level.Value > requestLevel)
+ if (videoStream.Level.HasValue && videoStream.Level.Value > requestLevel)
{
return false;
}
}
}
- if (request.Cabac.HasValue && request.Cabac.Value)
- {
- if (videoStream.IsCabac.HasValue && !videoStream.IsCabac.Value)
- {
- return false;
- }
- }
-
return request.EnableAutoStreamCopy;
}
@@ -1921,8 +2024,11 @@ namespace MediaBrowser.Api.Playback
return Array.FindIndex(list.ToArray(), t => string.Equals(t, profile, StringComparison.OrdinalIgnoreCase));
}
- protected virtual bool CanStreamCopyAudio(VideoStreamRequest request, MediaStream audioStream, List<string> supportedAudioCodecs)
+ protected virtual bool CanStreamCopyAudio(StreamState state, List<string> supportedAudioCodecs)
{
+ var request = state.VideoRequest;
+ var audioStream = state.AudioStream;
+
// Source and target codecs must match
if (string.IsNullOrEmpty(audioStream.Codec) || !supportedAudioCodecs.Contains(audioStream.Codec, StringComparer.OrdinalIgnoreCase))
{
@@ -2028,7 +2134,6 @@ namespace MediaBrowser.Api.Playback
state.TargetPacketLength,
state.TargetTimestamp,
state.IsTargetAnamorphic,
- state.IsTargetCabac,
state.TargetRefFrames,
state.TargetVideoStreamCount,
state.TargetAudioStreamCount,
@@ -2054,6 +2159,8 @@ namespace MediaBrowser.Api.Playback
if (state.VideoRequest != null)
{
state.VideoRequest.CopyTimestamps = transcodingProfile.CopyTimestamps;
+ state.VideoRequest.ForceLiveStream = transcodingProfile.ForceLiveStream;
+ state.VideoRequest.EnableSubtitlesInManifest = transcodingProfile.EnableSubtitlesInManifest;
}
}
}
@@ -2131,7 +2238,6 @@ namespace MediaBrowser.Api.Playback
state.TargetPacketLength,
state.TranscodeSeekInfo,
state.IsTargetAnamorphic,
- state.IsTargetCabac,
state.TargetRefFrames,
state.TargetVideoStreamCount,
state.TargetAudioStreamCount,
@@ -2218,12 +2324,13 @@ namespace MediaBrowser.Api.Playback
if (state.VideoRequest != null)
{
+ // Important: If this is ever re-enabled, make sure not to use it with wtv because it breaks seeking
if (string.Equals(state.OutputContainer, "mkv", StringComparison.OrdinalIgnoreCase) && state.VideoRequest.CopyTimestamps)
{
- inputModifier += " -noaccurate_seek";
+ //inputModifier += " -noaccurate_seek";
}
}
-
+
return inputModifier;
}
diff --git a/MediaBrowser.Api/Playback/Dash/ManifestBuilder.cs b/MediaBrowser.Api/Playback/Dash/ManifestBuilder.cs
deleted file mode 100644
index 35e252a19..000000000
--- a/MediaBrowser.Api/Playback/Dash/ManifestBuilder.cs
+++ /dev/null
@@ -1,224 +0,0 @@
-using System;
-using System.Globalization;
-using System.Security;
-using System.Text;
-
-namespace MediaBrowser.Api.Playback.Dash
-{
- public class ManifestBuilder
- {
- protected readonly CultureInfo UsCulture = new CultureInfo("en-US");
-
- public string GetManifestText(StreamState state, string playlistUrl)
- {
- var builder = new StringBuilder();
-
- var time = TimeSpan.FromTicks(state.RunTimeTicks.Value);
-
- var duration = "PT" + time.Hours.ToString("00", UsCulture) + "H" + time.Minutes.ToString("00", UsCulture) + "M" + time.Seconds.ToString("00", UsCulture) + ".00S";
-
- builder.Append("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
-
- builder.AppendFormat(
- "<MPD xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns=\"urn:mpeg:dash:schema:mpd:2011\" xmlns:xlink=\"http://www.w3.org/1999/xlink\" xsi:schemaLocation=\"urn:mpeg:DASH:schema:MPD:2011 http://standards.iso.org/ittf/PubliclyAvailableStandards/MPEG-DASH_schema_files/DASH-MPD.xsd\" profiles=\"urn:mpeg:dash:profile:isoff-live:2011\" type=\"static\" mediaPresentationDuration=\"{0}\" minBufferTime=\"PT5.0S\">",
- duration);
-
- builder.Append("<ProgramInformation>");
- builder.Append("</ProgramInformation>");
-
- builder.Append("<Period start=\"PT0S\">");
- builder.Append(GetVideoAdaptationSet(state, playlistUrl));
- builder.Append(GetAudioAdaptationSet(state, playlistUrl));
- builder.Append("</Period>");
-
- builder.Append("</MPD>");
-
- return builder.ToString();
- }
-
- private string GetVideoAdaptationSet(StreamState state, string playlistUrl)
- {
- var builder = new StringBuilder();
-
- builder.Append("<AdaptationSet id=\"video\" segmentAlignment=\"true\" bitstreamSwitching=\"true\">");
- builder.Append(GetVideoRepresentationOpenElement(state));
-
- AppendSegmentList(state, builder, "0", playlistUrl);
-
- builder.Append("</Representation>");
- builder.Append("</AdaptationSet>");
-
- return builder.ToString();
- }
-
- private string GetAudioAdaptationSet(StreamState state, string playlistUrl)
- {
- var builder = new StringBuilder();
-
- builder.Append("<AdaptationSet id=\"audio\" segmentAlignment=\"true\" bitstreamSwitching=\"true\">");
- builder.Append(GetAudioRepresentationOpenElement(state));
-
- builder.Append("<AudioChannelConfiguration schemeIdUri=\"urn:mpeg:dash:23003:3:audio_channel_configuration:2011\" value=\"6\" />");
-
- AppendSegmentList(state, builder, "1", playlistUrl);
-
- builder.Append("</Representation>");
- builder.Append("</AdaptationSet>");
-
- return builder.ToString();
- }
-
- private string GetVideoRepresentationOpenElement(StreamState state)
- {
- var codecs = GetVideoCodecDescriptor(state);
-
- var mime = "video/mp4";
-
- var xml = "<Representation id=\"0\" mimeType=\"" + mime + "\" codecs=\"" + codecs + "\"";
-
- if (state.OutputWidth.HasValue)
- {
- xml += " width=\"" + state.OutputWidth.Value.ToString(UsCulture) + "\"";
- }
- if (state.OutputHeight.HasValue)
- {
- xml += " height=\"" + state.OutputHeight.Value.ToString(UsCulture) + "\"";
- }
- if (state.OutputVideoBitrate.HasValue)
- {
- xml += " bandwidth=\"" + state.OutputVideoBitrate.Value.ToString(UsCulture) + "\"";
- }
-
- xml += ">";
-
- return xml;
- }
-
- private string GetAudioRepresentationOpenElement(StreamState state)
- {
- var codecs = GetAudioCodecDescriptor(state);
-
- var mime = "audio/mp4";
-
- var xml = "<Representation id=\"1\" mimeType=\"" + mime + "\" codecs=\"" + codecs + "\"";
-
- if (state.OutputAudioSampleRate.HasValue)
- {
- xml += " audioSamplingRate=\"" + state.OutputAudioSampleRate.Value.ToString(UsCulture) + "\"";
- }
- if (state.OutputAudioBitrate.HasValue)
- {
- xml += " bandwidth=\"" + state.OutputAudioBitrate.Value.ToString(UsCulture) + "\"";
- }
-
- xml += ">";
-
- return xml;
- }
-
- private string GetVideoCodecDescriptor(StreamState state)
- {
- // https://developer.apple.com/library/ios/documentation/networkinginternet/conceptual/streamingmediaguide/FrequentlyAskedQuestions/FrequentlyAskedQuestions.html
- // http://www.chipwreck.de/blog/2010/02/25/html-5-video-tag-and-attributes/
-
- var level = state.TargetVideoLevel ?? 0;
- var profile = state.TargetVideoProfile ?? string.Empty;
-
- if (profile.IndexOf("high", StringComparison.OrdinalIgnoreCase) != -1)
- {
- if (level >= 4.1)
- {
- return "avc1.640028";
- }
-
- if (level >= 4)
- {
- return "avc1.640028";
- }
-
- return "avc1.64001f";
- }
-
- if (profile.IndexOf("main", StringComparison.OrdinalIgnoreCase) != -1)
- {
- if (level >= 4)
- {
- return "avc1.4d0028";
- }
-
- if (level >= 3.1)
- {
- return "avc1.4d001f";
- }
-
- return "avc1.4d001e";
- }
-
- if (level >= 3.1)
- {
- return "avc1.42001f";
- }
-
- return "avc1.42E01E";
- }
-
- private string GetAudioCodecDescriptor(StreamState state)
- {
- // https://developer.apple.com/library/ios/documentation/networkinginternet/conceptual/streamingmediaguide/FrequentlyAskedQuestions/FrequentlyAskedQuestions.html
-
- if (string.Equals(state.OutputAudioCodec, "mp3", StringComparison.OrdinalIgnoreCase))
- {
- return "mp4a.40.34";
- }
-
- // AAC 5ch
- if (state.OutputAudioChannels.HasValue && state.OutputAudioChannels.Value >= 5)
- {
- return "mp4a.40.5";
- }
-
- // AAC 2ch
- return "mp4a.40.2";
- }
-
- private void AppendSegmentList(StreamState state, StringBuilder builder, string type, string playlistUrl)
- {
- var extension = ".m4s";
-
- var seconds = TimeSpan.FromTicks(state.RunTimeTicks ?? 0).TotalSeconds;
-
- var queryStringIndex = playlistUrl.IndexOf('?');
- var queryString = queryStringIndex == -1 ? string.Empty : playlistUrl.Substring(queryStringIndex);
-
- var index = 0;
- var duration = 1000000 * state.SegmentLength;
- builder.AppendFormat("<SegmentList timescale=\"1000000\" duration=\"{0}\" startNumber=\"1\">", duration.ToString(CultureInfo.InvariantCulture));
-
- while (seconds > 0)
- {
- var filename = index == 0
- ? "init"
- : (index - 1).ToString(UsCulture);
-
- var segmentUrl = string.Format("dash/{3}/{0}{1}{2}",
- filename,
- extension,
- SecurityElement.Escape(queryString),
- type);
-
- if (index == 0)
- {
- builder.AppendFormat("<Initialization sourceURL=\"{0}\"/>", segmentUrl);
- }
- else
- {
- builder.AppendFormat("<SegmentURL media=\"{0}\"/>", segmentUrl);
- }
-
- seconds -= state.SegmentLength;
- index++;
- }
- builder.Append("</SegmentList>");
- }
- }
-}
diff --git a/MediaBrowser.Api/Playback/Dash/MpegDashService.cs b/MediaBrowser.Api/Playback/Dash/MpegDashService.cs
deleted file mode 100644
index a35d13c5b..000000000
--- a/MediaBrowser.Api/Playback/Dash/MpegDashService.cs
+++ /dev/null
@@ -1,547 +0,0 @@
-using MediaBrowser.Api.Playback.Hls;
-using MediaBrowser.Common.Net;
-using MediaBrowser.Controller.Configuration;
-using MediaBrowser.Controller.Devices;
-using MediaBrowser.Controller.Dlna;
-using MediaBrowser.Controller.Library;
-using MediaBrowser.Controller.MediaEncoding;
-using MediaBrowser.Controller.Net;
-using MediaBrowser.Model.IO;
-using MediaBrowser.Model.Serialization;
-using ServiceStack;
-using System;
-using System.Collections.Generic;
-using System.Globalization;
-using System.IO;
-using System.Linq;
-using System.Threading;
-using System.Threading.Tasks;
-using CommonIO;
-using MimeTypes = MediaBrowser.Model.Net.MimeTypes;
-
-namespace MediaBrowser.Api.Playback.Dash
-{
- /// <summary>
- /// Options is needed for chromecast. Threw Head in there since it's related
- /// </summary>
- [Route("/Videos/{Id}/master.mpd", "GET", Summary = "Gets a video stream using Mpeg dash.")]
- [Route("/Videos/{Id}/master.mpd", "HEAD", Summary = "Gets a video stream using Mpeg dash.")]
- public class GetMasterManifest : VideoStreamRequest
- {
- public bool EnableAdaptiveBitrateStreaming { get; set; }
-
- public GetMasterManifest()
- {
- EnableAdaptiveBitrateStreaming = true;
- }
- }
-
- [Route("/Videos/{Id}/dash/{RepresentationId}/{SegmentId}.m4s", "GET")]
- public class GetDashSegment : VideoStreamRequest
- {
- /// <summary>
- /// Gets or sets the segment id.
- /// </summary>
- /// <value>The segment id.</value>
- public string SegmentId { get; set; }
-
- /// <summary>
- /// Gets or sets the representation identifier.
- /// </summary>
- /// <value>The representation identifier.</value>
- public string RepresentationId { get; set; }
- }
-
- public class MpegDashService : BaseHlsService
- {
- public MpegDashService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IFileSystem fileSystem, IDlnaManager dlnaManager, ISubtitleEncoder subtitleEncoder, IDeviceManager deviceManager, IMediaSourceManager mediaSourceManager, IZipClient zipClient, IJsonSerializer jsonSerializer, INetworkManager networkManager) : base(serverConfig, userManager, libraryManager, isoManager, mediaEncoder, fileSystem, dlnaManager, subtitleEncoder, deviceManager, mediaSourceManager, zipClient, jsonSerializer)
- {
- NetworkManager = networkManager;
- }
-
- protected INetworkManager NetworkManager { get; private set; }
-
- public object Get(GetMasterManifest request)
- {
- var result = GetAsync(request, "GET").Result;
-
- return result;
- }
-
- public object Head(GetMasterManifest request)
- {
- var result = GetAsync(request, "HEAD").Result;
-
- return result;
- }
-
- protected override bool EnableOutputInSubFolder
- {
- get
- {
- return true;
- }
- }
-
- private async Task<object> GetAsync(GetMasterManifest request, string method)
- {
- if (string.IsNullOrEmpty(request.MediaSourceId))
- {
- throw new ArgumentException("MediaSourceId is required");
- }
-
- var state = await GetState(request, CancellationToken.None).ConfigureAwait(false);
-
- var playlistText = string.Empty;
-
- if (string.Equals(method, "GET", StringComparison.OrdinalIgnoreCase))
- {
- playlistText = new ManifestBuilder().GetManifestText(state, Request.RawUrl);
- }
-
- return ResultFactory.GetResult(playlistText, MimeTypes.GetMimeType("playlist.mpd"), new Dictionary<string, string>());
- }
-
- public object Get(GetDashSegment request)
- {
- return GetDynamicSegment(request, request.SegmentId, request.RepresentationId).Result;
- }
-
- private async Task<object> GetDynamicSegment(VideoStreamRequest request, string segmentId, string representationId)
- {
- if ((request.StartTimeTicks ?? 0) > 0)
- {
- throw new ArgumentException("StartTimeTicks is not allowed.");
- }
-
- var cancellationTokenSource = new CancellationTokenSource();
- var cancellationToken = cancellationTokenSource.Token;
-
- var requestedIndex = string.Equals(segmentId, "init", StringComparison.OrdinalIgnoreCase) ?
- -1 :
- int.Parse(segmentId, NumberStyles.Integer, UsCulture);
-
- var state = await GetState(request, cancellationToken).ConfigureAwait(false);
-
- var playlistPath = Path.ChangeExtension(state.OutputFilePath, ".mpd");
-
- var segmentExtension = GetSegmentFileExtension(state);
-
- var segmentPath = FindSegment(playlistPath, representationId, segmentExtension, requestedIndex);
- var segmentLength = state.SegmentLength;
-
- TranscodingJob job = null;
-
- if (!string.IsNullOrWhiteSpace(segmentPath))
- {
- job = ApiEntryPoint.Instance.GetTranscodingJob(playlistPath, TranscodingJobType);
- return await GetSegmentResult(playlistPath, segmentPath, requestedIndex, segmentLength, job, cancellationToken).ConfigureAwait(false);
- }
-
- await ApiEntryPoint.Instance.TranscodingStartLock.WaitAsync(cancellationTokenSource.Token).ConfigureAwait(false);
- try
- {
- segmentPath = FindSegment(playlistPath, representationId, segmentExtension, requestedIndex);
- if (!string.IsNullOrWhiteSpace(segmentPath))
- {
- job = ApiEntryPoint.Instance.GetTranscodingJob(playlistPath, TranscodingJobType);
- return await GetSegmentResult(playlistPath, segmentPath, requestedIndex, segmentLength, job, cancellationToken).ConfigureAwait(false);
- }
- else
- {
- if (string.Equals(representationId, "0", StringComparison.OrdinalIgnoreCase))
- {
- job = ApiEntryPoint.Instance.GetTranscodingJob(playlistPath, TranscodingJobType);
- var currentTranscodingIndex = GetCurrentTranscodingIndex(playlistPath, segmentExtension);
- var segmentGapRequiringTranscodingChange = 24 / state.SegmentLength;
- Logger.Debug("Current transcoding index is {0}. requestedIndex={1}. segmentGapRequiringTranscodingChange={2}", currentTranscodingIndex ?? -2, requestedIndex, segmentGapRequiringTranscodingChange);
- if (currentTranscodingIndex == null || requestedIndex < currentTranscodingIndex.Value || requestedIndex - currentTranscodingIndex.Value > segmentGapRequiringTranscodingChange)
- {
- // If the playlist doesn't already exist, startup ffmpeg
- try
- {
- ApiEntryPoint.Instance.KillTranscodingJobs(request.DeviceId, request.PlaySessionId, p => false);
-
- if (currentTranscodingIndex.HasValue)
- {
- DeleteLastTranscodedFiles(playlistPath, 0);
- }
-
- var positionTicks = GetPositionTicks(state, requestedIndex);
- request.StartTimeTicks = positionTicks;
-
- var startNumber = GetStartNumber(state);
-
- var workingDirectory = Path.Combine(Path.GetDirectoryName(playlistPath), (startNumber == -1 ? 0 : startNumber).ToString(CultureInfo.InvariantCulture));
- state.WaitForPath = Path.Combine(workingDirectory, Path.GetFileName(playlistPath));
- FileSystem.CreateDirectory(workingDirectory);
- job = await StartFfMpeg(state, playlistPath, cancellationTokenSource, workingDirectory).ConfigureAwait(false);
- await WaitForMinimumDashSegmentCount(Path.Combine(workingDirectory, Path.GetFileName(playlistPath)), 1, cancellationTokenSource.Token).ConfigureAwait(false);
- }
- catch
- {
- state.Dispose();
- throw;
- }
- }
- }
- }
- }
- finally
- {
- ApiEntryPoint.Instance.TranscodingStartLock.Release();
- }
-
- while (string.IsNullOrWhiteSpace(segmentPath))
- {
- segmentPath = FindSegment(playlistPath, representationId, segmentExtension, requestedIndex);
- await Task.Delay(50, cancellationToken).ConfigureAwait(false);
- }
-
- Logger.Info("returning {0}", segmentPath);
- return await GetSegmentResult(playlistPath, segmentPath, requestedIndex, segmentLength, job ?? ApiEntryPoint.Instance.GetTranscodingJob(playlistPath, TranscodingJobType), cancellationToken).ConfigureAwait(false);
- }
-
- private long GetPositionTicks(StreamState state, int requestedIndex)
- {
- if (requestedIndex <= 0)
- {
- return 0;
- }
-
- var startSeconds = requestedIndex * state.SegmentLength;
- return TimeSpan.FromSeconds(startSeconds).Ticks;
- }
-
- protected Task WaitForMinimumDashSegmentCount(string playlist, int segmentCount, CancellationToken cancellationToken)
- {
- return WaitForSegment(playlist, "stream0-" + segmentCount.ToString("00000", CultureInfo.InvariantCulture) + ".m4s", cancellationToken);
- }
-
- private async Task<object> GetSegmentResult(string playlistPath,
- string segmentPath,
- int segmentIndex,
- int segmentLength,
- TranscodingJob transcodingJob,
- CancellationToken cancellationToken)
- {
- // If all transcoding has completed, just return immediately
- if (transcodingJob != null && transcodingJob.HasExited)
- {
- return GetSegmentResult(segmentPath, segmentIndex, segmentLength, transcodingJob);
- }
-
- // Wait for the file to stop being written to, then stream it
- var length = new FileInfo(segmentPath).Length;
- var eofCount = 0;
-
- while (eofCount < 10)
- {
- var info = new FileInfo(segmentPath);
-
- if (!info.Exists)
- {
- break;
- }
-
- var newLength = info.Length;
-
- if (newLength == length)
- {
- eofCount++;
- }
- else
- {
- eofCount = 0;
- }
-
- length = newLength;
- await Task.Delay(100, cancellationToken).ConfigureAwait(false);
- }
-
- return GetSegmentResult(segmentPath, segmentIndex, segmentLength, transcodingJob);
- }
-
- private object GetSegmentResult(string segmentPath, int index, int segmentLength, TranscodingJob transcodingJob)
- {
- var segmentEndingSeconds = (1 + index) * segmentLength;
- var segmentEndingPositionTicks = TimeSpan.FromSeconds(segmentEndingSeconds).Ticks;
-
- return ResultFactory.GetStaticFileResult(Request, new StaticFileResultOptions
- {
- Path = segmentPath,
- FileShare = FileShare.ReadWrite,
- OnComplete = () =>
- {
- if (transcodingJob != null)
- {
- transcodingJob.DownloadPositionTicks = Math.Max(transcodingJob.DownloadPositionTicks ?? segmentEndingPositionTicks, segmentEndingPositionTicks);
- }
-
- }
- });
- }
-
- public int? GetCurrentTranscodingIndex(string playlist, string segmentExtension)
- {
- var job = ApiEntryPoint.Instance.GetTranscodingJob(playlist, TranscodingJobType);
-
- if (job == null || job.HasExited)
- {
- return null;
- }
-
- var file = GetLastTranscodingFiles(playlist, segmentExtension, FileSystem, 1).FirstOrDefault();
-
- if (file == null)
- {
- return null;
- }
-
- return GetIndex(file.FullName);
- }
-
- public int GetIndex(string segmentPath)
- {
- var indexString = Path.GetFileNameWithoutExtension(segmentPath).Split('-').LastOrDefault();
-
- if (string.Equals(indexString, "init", StringComparison.OrdinalIgnoreCase))
- {
- return -1;
- }
- var startNumber = int.Parse(Path.GetFileNameWithoutExtension(Path.GetDirectoryName(segmentPath)), NumberStyles.Integer, UsCulture);
-
- return startNumber + int.Parse(indexString, NumberStyles.Integer, UsCulture) - 1;
- }
-
- private void DeleteLastTranscodedFiles(string playlistPath, int retryCount)
- {
- if (retryCount >= 5)
- {
- return;
- }
- }
-
- private static List<FileSystemMetadata> GetLastTranscodingFiles(string playlist, string segmentExtension, IFileSystem fileSystem, int count)
- {
- var folder = Path.GetDirectoryName(playlist);
-
- try
- {
- return fileSystem.GetFiles(folder)
- .Where(i => string.Equals(i.Extension, segmentExtension, StringComparison.OrdinalIgnoreCase))
- .OrderByDescending(fileSystem.GetLastWriteTimeUtc)
- .Take(count)
- .ToList();
- }
- catch (DirectoryNotFoundException)
- {
- return new List<FileSystemMetadata>();
- }
- }
-
- private string FindSegment(string playlist, string representationId, string segmentExtension, int requestedIndex)
- {
- var folder = Path.GetDirectoryName(playlist);
-
- if (requestedIndex == -1)
- {
- var path = Path.Combine(folder, "0", "stream" + representationId + "-" + "init" + segmentExtension);
- return FileSystem.FileExists(path) ? path : null;
- }
-
- try
- {
- foreach (var subfolder in FileSystem.GetDirectoryPaths(folder).ToList())
- {
- var subfolderName = Path.GetFileNameWithoutExtension(subfolder);
- int startNumber;
- if (int.TryParse(subfolderName, NumberStyles.Any, UsCulture, out startNumber))
- {
- var segmentIndex = requestedIndex - startNumber + 1;
- var path = Path.Combine(folder, subfolderName, "stream" + representationId + "-" + segmentIndex.ToString("00000", CultureInfo.InvariantCulture) + segmentExtension);
- if (FileSystem.FileExists(path))
- {
- return path;
- }
- }
- }
- }
- catch (DirectoryNotFoundException)
- {
-
- }
-
- return null;
- }
-
- protected override string GetAudioArguments(StreamState state)
- {
- var codec = GetAudioEncoder(state);
-
- if (string.Equals(codec, "copy", StringComparison.OrdinalIgnoreCase))
- {
- return "-codec:a:0 copy";
- }
-
- var args = "-codec:a:0 " + codec;
-
- var channels = state.OutputAudioChannels;
-
- if (channels.HasValue)
- {
- args += " -ac " + channels.Value;
- }
-
- var bitrate = state.OutputAudioBitrate;
-
- if (bitrate.HasValue)
- {
- args += " -ab " + bitrate.Value.ToString(UsCulture);
- }
-
- args += " " + GetAudioFilterParam(state, true);
-
- return args;
- }
-
- protected override string GetVideoArguments(StreamState state)
- {
- var codec = GetVideoEncoder(state);
-
- var args = "-codec:v:0 " + codec;
-
- if (state.EnableMpegtsM2TsMode)
- {
- args += " -mpegts_m2ts_mode 1";
- }
-
- // See if we can save come cpu cycles by avoiding encoding
- if (codec.Equals("copy", StringComparison.OrdinalIgnoreCase))
- {
- return state.VideoStream != null && IsH264(state.VideoStream) ?
- args + " -bsf:v h264_mp4toannexb" :
- args;
- }
-
- var keyFrameArg = string.Format(" -force_key_frames expr:gte(t,n_forced*{0})",
- state.SegmentLength.ToString(UsCulture));
-
- var hasGraphicalSubs = state.SubtitleStream != null && !state.SubtitleStream.IsTextSubtitleStream;
-
- args += " " + GetVideoQualityParam(state, GetH264Encoder(state)) + keyFrameArg;
-
- // Add resolution params, if specified
- if (!hasGraphicalSubs)
- {
- args += GetOutputSizeParam(state, codec, false);
- }
-
- // This is for internal graphical subs
- if (hasGraphicalSubs)
- {
- args += GetGraphicalSubtitleParam(state, codec);
- }
-
- return args;
- }
-
- protected override string GetCommandLineArguments(string outputPath, StreamState state, bool isEncoding)
- {
- // test url http://192.168.1.2:8096/videos/233e8905d559a8f230db9bffd2ac9d6d/master.mpd?mediasourceid=233e8905d559a8f230db9bffd2ac9d6d&videocodec=h264&audiocodec=aac&maxwidth=1280&videobitrate=500000&audiobitrate=128000&profile=baseline&level=3
- // Good info on i-frames http://blog.streamroot.io/encode-multi-bitrate-videos-mpeg-dash-mse-based-media-players/
-
- var threads = GetNumberOfThreads(state, false);
-
- var inputModifier = GetInputModifier(state);
-
- var initSegmentName = "stream$RepresentationID$-init.m4s";
- var segmentName = "stream$RepresentationID$-$Number%05d$.m4s";
-
- var args = string.Format("{0} {1} -map_metadata -1 -threads {2} {3} {4} -copyts {5} -f dash -init_seg_name \"{6}\" -media_seg_name \"{7}\" -use_template 0 -use_timeline 1 -min_seg_duration {8} -y \"{9}\"",
- inputModifier,
- GetInputArgument(state),
- threads,
- GetMapArgs(state),
- GetVideoArguments(state),
- GetAudioArguments(state),
- initSegmentName,
- segmentName,
- (state.SegmentLength * 1000000).ToString(CultureInfo.InvariantCulture),
- state.WaitForPath
- ).Trim();
-
- return args;
- }
-
- protected override int GetStartNumber(StreamState state)
- {
- return GetStartNumber(state.VideoRequest);
- }
-
- private int GetStartNumber(VideoStreamRequest request)
- {
- var segmentId = "0";
-
- var segmentRequest = request as GetDashSegment;
- if (segmentRequest != null)
- {
- segmentId = segmentRequest.SegmentId;
- }
-
- if (string.Equals(segmentId, "init", StringComparison.OrdinalIgnoreCase))
- {
- return -1;
- }
-
- return int.Parse(segmentId, NumberStyles.Integer, UsCulture);
- }
-
- /// <summary>
- /// Gets the segment file extension.
- /// </summary>
- /// <param name="state">The state.</param>
- /// <returns>System.String.</returns>
- protected override string GetSegmentFileExtension(StreamState state)
- {
- return ".m4s";
- }
-
- protected override TranscodingJobType TranscodingJobType
- {
- get
- {
- return TranscodingJobType.Dash;
- }
- }
-
- private async Task WaitForSegment(string playlist, string segment, CancellationToken cancellationToken)
- {
- var segmentFilename = Path.GetFileName(segment);
-
- Logger.Debug("Waiting for {0} in {1}", segmentFilename, playlist);
-
- while (true)
- {
- // Need to use FileShare.ReadWrite because we're reading the file at the same time it's being written
- using (var fileStream = GetPlaylistFileStream(playlist))
- {
- using (var reader = new StreamReader(fileStream))
- {
- while (!reader.EndOfStream)
- {
- var line = await reader.ReadLineAsync().ConfigureAwait(false);
-
- if (line.IndexOf(segmentFilename, StringComparison.OrdinalIgnoreCase) != -1)
- {
- Logger.Debug("Finished waiting for {0} in {1}", segmentFilename, playlist);
- return;
- }
- }
- await Task.Delay(100, cancellationToken).ConfigureAwait(false);
- }
- }
- }
- }
- }
-}
diff --git a/MediaBrowser.Api/Playback/Hls/BaseHlsService.cs b/MediaBrowser.Api/Playback/Hls/BaseHlsService.cs
index c11265742..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>
@@ -83,11 +83,6 @@ namespace MediaBrowser.Api.Playback.Hls
var state = await GetState(request, cancellationTokenSource.Token).ConfigureAwait(false);
- if (isLive)
- {
- state.Request.StartTimeTicks = null;
- }
-
TranscodingJob job = null;
var playlist = state.OutputFilePath;
@@ -137,13 +132,6 @@ namespace MediaBrowser.Api.Playback.Hls
var appendBaselineStream = false;
var baselineStreamBitrate = 64000;
- var hlsVideoRequest = state.VideoRequest as GetHlsVideoStreamLegacy;
- if (hlsVideoRequest != null)
- {
- appendBaselineStream = hlsVideoRequest.AppendBaselineStream;
- baselineStreamBitrate = hlsVideoRequest.BaselineStreamAudioBitRate ?? baselineStreamBitrate;
- }
-
var playlistText = GetMasterPlaylistFileText(playlist, videoBitrate + audioBitrate, appendBaselineStream, baselineStreamBitrate);
job = job ?? ApiEntryPoint.Instance.OnTranscodeBeginRequest(playlist, TranscodingJobType);
@@ -248,11 +236,7 @@ namespace MediaBrowser.Api.Playback.Hls
protected override string GetCommandLineArguments(string outputPath, StreamState state, bool isEncoding)
{
- var hlsVideoRequest = state.VideoRequest as GetHlsVideoStreamLegacy;
-
- var itsOffsetMs = hlsVideoRequest == null
- ? 0
- : hlsVideoRequest.TimeStampOffsetMs;
+ var itsOffsetMs = 0;
var itsOffset = itsOffsetMs == 0 ? string.Empty : string.Format("-itsoffset {0} ", TimeSpan.FromMilliseconds(itsOffsetMs).TotalSeconds.ToString(UsCulture));
@@ -286,26 +270,6 @@ namespace MediaBrowser.Api.Playback.Hls
outputPath
).Trim();
- if (hlsVideoRequest != null)
- {
- if (hlsVideoRequest.AppendBaselineStream)
- {
- var lowBitratePath = Path.Combine(Path.GetDirectoryName(outputPath), Path.GetFileNameWithoutExtension(outputPath) + "-low.m3u8");
-
- var bitrate = hlsVideoRequest.BaselineStreamAudioBitRate ?? 64000;
-
- var lowBitrateParams = string.Format(" -threads {0} -vn -codec:a:0 libmp3lame -ac 2 -ab {1} -hls_time {2} -start_number {3} -hls_list_size {4} -y \"{5}\"",
- threads,
- bitrate / 2,
- state.SegmentLength.ToString(UsCulture),
- startNumberParam,
- state.HlsListSize.ToString(UsCulture),
- lowBitratePath);
-
- args += " " + lowBitrateParams;
- }
- }
-
return args;
}
@@ -314,9 +278,16 @@ namespace MediaBrowser.Api.Playback.Hls
return 0;
}
- protected override bool CanStreamCopyAudio(VideoStreamRequest request, MediaStream audioStream, List<string> supportedAudioCodecs)
+ protected bool IsLiveStream(StreamState state)
{
- return false;
+ var isLiveStream = (state.RunTimeTicks ?? 0) == 0;
+
+ if (state.VideoRequest.ForceLiveStream)
+ {
+ return true;
+ }
+
+ return isLiveStream;
}
}
} \ No newline at end of file
diff --git a/MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs b/MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs
index 3e46ee426..e029d4e99 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)
@@ -506,7 +506,7 @@ namespace MediaBrowser.Api.Playback.Hls
builder.AppendLine("#EXTM3U");
- var isLiveStream = (state.RunTimeTicks ?? 0) == 0;
+ var isLiveStream = IsLiveStream(state);
var queryStringIndex = Request.RawUrl.IndexOf('?');
var queryString = queryStringIndex == -1 ? string.Empty : Request.RawUrl.Substring(queryStringIndex);
@@ -525,10 +525,16 @@ namespace MediaBrowser.Api.Playback.Hls
var subtitleGroup = subtitleStreams.Count > 0 &&
request is GetMasterHlsVideoPlaylist &&
- ((GetMasterHlsVideoPlaylist)request).SubtitleMethod == SubtitleDeliveryMethod.Hls ?
+ (state.VideoRequest.SubtitleMethod == SubtitleDeliveryMethod.Hls || state.VideoRequest.EnableSubtitlesInManifest) ?
"subs" :
null;
+ // If we're burning in subtitles then don't add additional subs to the manifest
+ if (state.SubtitleStream != null && state.VideoRequest.SubtitleMethod == SubtitleDeliveryMethod.Encode)
+ {
+ subtitleGroup = null;
+ }
+
if (!string.IsNullOrWhiteSpace(subtitleGroup))
{
AddSubtitles(state, subtitleStreams, builder);
@@ -572,13 +578,11 @@ namespace MediaBrowser.Api.Playback.Hls
{
const string format = "#EXT-X-MEDIA:TYPE=SUBTITLES,GROUP-ID=\"subs\",NAME=\"{0}\",DEFAULT={1},FORCED={2},AUTOSELECT=YES,URI=\"{3}\",LANGUAGE=\"{4}\"";
- var name = stream.Language;
+ var name = stream.DisplayTitle;
var isDefault = selectedIndex.HasValue && selectedIndex.Value == stream.Index;
var isForced = stream.IsForced;
- if (string.IsNullOrWhiteSpace(name)) name = stream.Codec ?? "Unknown";
-
var url = string.Format("{0}/Subtitles/{1}/subtitles.m3u8?SegmentLength={2}&api_key={3}",
state.Request.MediaSourceId,
stream.Index.ToString(UsCulture),
@@ -816,12 +820,12 @@ namespace MediaBrowser.Api.Playback.Hls
// See if we can save come cpu cycles by avoiding encoding
if (string.Equals(codec, "copy", StringComparison.OrdinalIgnoreCase))
{
- if (state.VideoStream != null && IsH264(state.VideoStream))
+ if (state.VideoStream != null && IsH264(state.VideoStream) && !string.Equals(state.VideoStream.NalLengthSize, "0", StringComparison.OrdinalIgnoreCase))
{
args += " -bsf:v h264_mp4toannexb";
}
- args += " -flags -global_header -sc_threshold 0";
+ args += " -flags -global_header";
}
else
{
@@ -846,7 +850,12 @@ namespace MediaBrowser.Api.Playback.Hls
args += GetGraphicalSubtitleParam(state, codec);
}
- args += " -flags -global_header -sc_threshold 0";
+ args += " -flags -global_header";
+ }
+
+ if (EnableCopyTs(state) && args.IndexOf("-copyts", StringComparison.OrdinalIgnoreCase) == -1)
+ {
+ args += " -copyts";
}
return args;
@@ -854,7 +863,8 @@ namespace MediaBrowser.Api.Playback.Hls
private bool EnableCopyTs(StreamState state)
{
- return state.SubtitleStream != null && state.SubtitleStream.IsTextSubtitleStream && state.VideoRequest.SubtitleMethod == SubtitleDeliveryMethod.Encode;
+ //return state.SubtitleStream != null && state.SubtitleStream.IsTextSubtitleStream && state.VideoRequest.SubtitleMethod == SubtitleDeliveryMethod.Encode;
+ return true;
}
protected override string GetCommandLineArguments(string outputPath, StreamState state, bool isEncoding)
@@ -876,24 +886,28 @@ namespace MediaBrowser.Api.Playback.Hls
var mapArgs = state.IsOutputVideo ? GetMapArgs(state) : string.Empty;
- //var outputTsArg = Path.Combine(Path.GetDirectoryName(outputPath), Path.GetFileNameWithoutExtension(outputPath)) + "%d" + GetSegmentFileExtension(state);
-
- //return string.Format("{0} {11} {1}{10} -map_metadata -1 -threads {2} {3} {4} {5} -f segment -segment_time {6} -segment_format mpegts -segment_list_type m3u8 -segment_start_number {7} -segment_list \"{8}\" -y \"{9}\"",
- // inputModifier,
- // GetInputArgument(state),
- // threads,
- // mapArgs,
- // GetVideoArguments(state),
- // GetAudioArguments(state),
- // state.SegmentLength.ToString(UsCulture),
- // startNumberParam,
- // outputPath,
- // outputTsArg,
- // slowSeekParam,
- // toTimeParam
- // ).Trim();
-
- return string.Format("{0}{11} {1} -map_metadata -1 -threads {2} {3} {4}{5} {6} -hls_time {7} -start_number {8} -hls_list_size {9} -y \"{10}\"",
+ var enableGenericSegmenter = false;
+
+ if (enableGenericSegmenter)
+ {
+ var outputTsArg = Path.Combine(Path.GetDirectoryName(outputPath), Path.GetFileNameWithoutExtension(outputPath)) + "%d" + GetSegmentFileExtension(state);
+
+ return string.Format("{0} {10} {1} -map_metadata -1 -threads {2} {3} {4} {5} -f segment -max_delay 5000000 -avoid_negative_ts disabled -start_at_zero -segment_time {6} -segment_format mpegts -segment_list_type m3u8 -segment_start_number {7} -segment_list \"{8}\" -y \"{9}\"",
+ inputModifier,
+ GetInputArgument(state),
+ threads,
+ mapArgs,
+ GetVideoArguments(state),
+ GetAudioArguments(state),
+ state.SegmentLength.ToString(UsCulture),
+ startNumberParam,
+ outputPath,
+ outputTsArg,
+ toTimeParam
+ ).Trim();
+ }
+
+ return string.Format("{0}{11} {1} -map_metadata -1 -threads {2} {3} {4}{5} {6} -max_delay 5000000 -avoid_negative_ts disabled -start_at_zero -hls_time {7} -start_number {8} -hls_list_size {9} -y \"{10}\"",
inputModifier,
GetInputArgument(state),
threads,
@@ -928,11 +942,5 @@ namespace MediaBrowser.Api.Playback.Hls
{
return isOutputVideo ? ".ts" : ".ts";
}
-
- protected override bool CanStreamCopyVideo(VideoStreamRequest request, MediaStream videoStream)
- {
- return false;
- //return base.CanStreamCopyVideo(request, videoStream);
- }
}
} \ No newline at end of file
diff --git a/MediaBrowser.Api/Playback/Hls/HlsSegmentService.cs b/MediaBrowser.Api/Playback/Hls/HlsSegmentService.cs
index b44d7f660..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
{
@@ -32,25 +33,6 @@ namespace MediaBrowser.Api.Playback.Hls
}
/// <summary>
- /// Class GetHlsVideoStream
- /// </summary>
- [Route("/Videos/{Id}/stream.m3u8", "GET")]
- [Api(Description = "Gets a video stream using HTTP live streaming.")]
- public class GetHlsVideoStreamLegacy : VideoStreamRequest
- {
- // TODO: Deprecate with new iOS app
-
- [ApiMember(Name = "BaselineStreamAudioBitRate", Description = "Optional. Specify the audio bitrate for the baseline stream.", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")]
- public int? BaselineStreamAudioBitRate { get; set; }
-
- [ApiMember(Name = "AppendBaselineStream", Description = "Optional. Whether or not to include a baseline audio-only stream in the master playlist.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")]
- public bool AppendBaselineStream { get; set; }
-
- [ApiMember(Name = "TimeStampOffsetMs", Description = "Optional. Alter the timestamps in the playlist by a given amount, in ms. Default is 1000.", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")]
- public int TimeStampOffsetMs { get; set; }
- }
-
- /// <summary>
/// Class GetHlsVideoSegment
/// </summary>
[Route("/Videos/{Id}/hls/{PlaylistId}/stream.m3u8", "GET")]
@@ -108,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);
@@ -126,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);
@@ -150,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/Hls/VideoHlsService.cs b/MediaBrowser.Api/Playback/Hls/VideoHlsService.cs
index f154a05cc..8a14948d2 100644
--- a/MediaBrowser.Api/Playback/Hls/VideoHlsService.cs
+++ b/MediaBrowser.Api/Playback/Hls/VideoHlsService.cs
@@ -27,16 +27,6 @@ namespace MediaBrowser.Api.Playback.Hls
{
}
- /// <summary>
- /// Gets the specified request.
- /// </summary>
- /// <param name="request">The request.</param>
- /// <returns>System.Object.</returns>
- public object Get(GetHlsVideoStreamLegacy request)
- {
- return ProcessRequest(request, false);
- }
-
public object Get(GetLiveHlsStream request)
{
return ProcessRequest(request, true);
@@ -96,11 +86,14 @@ namespace MediaBrowser.Api.Playback.Hls
// See if we can save come cpu cycles by avoiding encoding
if (codec.Equals("copy", StringComparison.OrdinalIgnoreCase))
{
- return state.VideoStream != null && IsH264(state.VideoStream) ?
- args + " -bsf:v h264_mp4toannexb" :
- args;
+ // if h264_mp4toannexb is ever added, do not use it for live tv
+ if (state.VideoStream != null && IsH264(state.VideoStream) && !string.Equals(state.VideoStream.NalLengthSize, "0", StringComparison.OrdinalIgnoreCase))
+ {
+ args += " -bsf:v h264_mp4toannexb";
+ }
+ return args;
}
-
+
var keyFrameArg = string.Format(" -force_key_frames \"expr:gte(t,n_forced*{0})\"",
state.SegmentLength.ToString(UsCulture));
diff --git a/MediaBrowser.Api/Playback/MediaInfoService.cs b/MediaBrowser.Api/Playback/MediaInfoService.cs
index 2bf61f90b..0b989784c 100644
--- a/MediaBrowser.Api/Playback/MediaInfoService.cs
+++ b/MediaBrowser.Api/Playback/MediaInfoService.cs
@@ -15,6 +15,8 @@ using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
+using MediaBrowser.Controller.Entities.Audio;
+using MediaBrowser.Controller.MediaEncoding;
namespace MediaBrowser.Api.Playback
{
@@ -66,14 +68,18 @@ namespace MediaBrowser.Api.Playback
private readonly ILibraryManager _libraryManager;
private readonly IServerConfigurationManager _config;
private readonly INetworkManager _networkManager;
+ private readonly IMediaEncoder _mediaEncoder;
+ private readonly IUserManager _userManager;
- public MediaInfoService(IMediaSourceManager mediaSourceManager, IDeviceManager deviceManager, ILibraryManager libraryManager, IServerConfigurationManager config, INetworkManager networkManager)
+ public MediaInfoService(IMediaSourceManager mediaSourceManager, IDeviceManager deviceManager, ILibraryManager libraryManager, IServerConfigurationManager config, INetworkManager networkManager, IMediaEncoder mediaEncoder, IUserManager userManager)
{
_mediaSourceManager = mediaSourceManager;
_deviceManager = deviceManager;
_libraryManager = libraryManager;
_config = config;
_networkManager = networkManager;
+ _mediaEncoder = mediaEncoder;
+ _userManager = userManager;
}
public object Get(GetBitrateTestBytes request)
@@ -116,7 +122,7 @@ namespace MediaBrowser.Api.Playback
SetDeviceSpecificData(item, result.MediaSource, profile, authInfo, request.MaxStreamingBitrate,
request.StartTimeTicks ?? 0, result.MediaSource.Id, request.AudioStreamIndex,
- request.SubtitleStreamIndex, request.PlaySessionId);
+ request.SubtitleStreamIndex, request.PlaySessionId, request.UserId);
}
else
{
@@ -156,7 +162,7 @@ namespace MediaBrowser.Api.Playback
{
var mediaSourceId = request.MediaSourceId;
- SetDeviceSpecificData(request.Id, info, profile, authInfo, request.MaxStreamingBitrate ?? profile.MaxStreamingBitrate, request.StartTimeTicks ?? 0, mediaSourceId, request.AudioStreamIndex, request.SubtitleStreamIndex);
+ SetDeviceSpecificData(request.Id, info, profile, authInfo, request.MaxStreamingBitrate ?? profile.MaxStreamingBitrate, request.StartTimeTicks ?? 0, mediaSourceId, request.AudioStreamIndex, request.SubtitleStreamIndex, request.UserId);
}
return ToOptimizedResult(info);
@@ -218,16 +224,17 @@ namespace MediaBrowser.Api.Playback
long startTimeTicks,
string mediaSourceId,
int? audioStreamIndex,
- int? subtitleStreamIndex)
+ int? subtitleStreamIndex,
+ string userId)
{
var item = _libraryManager.GetItemById(itemId);
foreach (var mediaSource in result.MediaSources)
{
- SetDeviceSpecificData(item, mediaSource, profile, auth, maxBitrate, startTimeTicks, mediaSourceId, audioStreamIndex, subtitleStreamIndex, result.PlaySessionId);
+ SetDeviceSpecificData(item, mediaSource, profile, auth, maxBitrate, startTimeTicks, mediaSourceId, audioStreamIndex, subtitleStreamIndex, result.PlaySessionId, userId);
}
- SortMediaSources(result);
+ SortMediaSources(result, maxBitrate);
}
private void SetDeviceSpecificData(BaseItem item,
@@ -239,9 +246,10 @@ namespace MediaBrowser.Api.Playback
string mediaSourceId,
int? audioStreamIndex,
int? subtitleStreamIndex,
- string playSessionId)
+ string playSessionId,
+ string userId)
{
- var streamBuilder = new StreamBuilder(Logger);
+ var streamBuilder = new StreamBuilder(_mediaEncoder, Logger);
var options = new VideoOptions
{
@@ -259,6 +267,8 @@ namespace MediaBrowser.Api.Playback
options.SubtitleStreamIndex = subtitleStreamIndex;
}
+ var user = _userManager.GetUserById(userId);
+
if (mediaSource.SupportsDirectPlay)
{
var supportsDirectStream = mediaSource.SupportsDirectStream;
@@ -267,6 +277,14 @@ namespace MediaBrowser.Api.Playback
mediaSource.SupportsDirectStream = true;
options.MaxBitrate = maxBitrate;
+ if (item is Audio)
+ {
+ if (!user.Policy.EnableAudioPlaybackTranscoding)
+ {
+ options.ForceDirectPlay = true;
+ }
+ }
+
// The MediaSource supports direct stream, now test to see if the client supports it
var streamInfo = string.Equals(item.MediaType, MediaType.Audio, StringComparison.OrdinalIgnoreCase) ?
streamBuilder.BuildAudioItem(options) :
@@ -290,6 +308,14 @@ namespace MediaBrowser.Api.Playback
{
options.MaxBitrate = GetMaxBitrate(maxBitrate);
+ if (item is Audio)
+ {
+ if (!user.Policy.EnableAudioPlaybackTranscoding)
+ {
+ options.ForceDirectStream = true;
+ }
+ }
+
// The MediaSource supports direct stream, now test to see if the client supports it
var streamInfo = string.Equals(item.MediaType, MediaType.Audio, StringComparison.OrdinalIgnoreCase) ?
streamBuilder.BuildAudioItem(options) :
@@ -375,7 +401,7 @@ namespace MediaBrowser.Api.Playback
}
}
- private void SortMediaSources(PlaybackInfoResponse result)
+ private void SortMediaSources(PlaybackInfoResponse result, int? maxBitrate)
{
var originalList = result.MediaSources.ToList();
@@ -409,6 +435,23 @@ namespace MediaBrowser.Api.Playback
return 1;
}
+ }).ThenBy(i =>
+ {
+ if (maxBitrate.HasValue)
+ {
+ if (i.Bitrate.HasValue)
+ {
+ if (i.Bitrate.Value <= maxBitrate.Value)
+ {
+ return 0;
+ }
+
+ return 2;
+ }
+ }
+
+ return 1;
+
}).ThenBy(originalList.IndexOf)
.ToList();
}
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..d75b8947a 100644
--- a/MediaBrowser.Api/Playback/Progressive/BaseProgressiveStreamingService.cs
+++ b/MediaBrowser.Api/Playback/Progressive/BaseProgressiveStreamingService.cs
@@ -13,6 +13,7 @@ using ServiceStack.Web;
using System;
using System.Collections.Generic;
using System.Globalization;
+using System.IO;
using System.Threading;
using System.Threading.Tasks;
using CommonIO;
@@ -113,11 +114,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 +129,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 +140,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 +153,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 +170,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 +187,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 +232,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 +245,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 +327,7 @@ namespace MediaBrowser.Api.Playback.Progressive
{
TranscodingJob job;
- if (!FileSystem.FileExists(outputPath))
+ if (!FileSystem.FileExists(outputPath))
{
job = await StartFfMpeg(state, outputPath, cancellationTokenSource).ConfigureAwait(false);
}
@@ -334,17 +337,19 @@ namespace MediaBrowser.Api.Playback.Progressive
state.Dispose();
}
- var result = new ProgressiveStreamWriter(outputPath, Logger, FileSystem, job);
+ var outputHeaders = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
- result.Options["Content-Type"] = contentType;
+ outputHeaders["Content-Type"] = contentType;
// Add the response headers to the result object
foreach (var item in responseHeaders)
{
- result.Options[item.Key] = item.Value;
+ outputHeaders[item.Key] = item.Value;
}
- return result;
+ Func<Stream,Task> streamWriter = stream => new ProgressiveFileCopier(FileSystem, job, Logger).StreamFile(outputPath, stream, CancellationToken.None);
+
+ return ResultFactory.GetAsyncStreamWriter(streamWriter, outputHeaders);
}
finally
{
diff --git a/MediaBrowser.Api/Playback/Progressive/ProgressiveStreamWriter.cs b/MediaBrowser.Api/Playback/Progressive/ProgressiveStreamWriter.cs
index 9f02c51cd..13d59240f 100644
--- a/MediaBrowser.Api/Playback/Progressive/ProgressiveStreamWriter.cs
+++ b/MediaBrowser.Api/Playback/Progressive/ProgressiveStreamWriter.cs
@@ -3,90 +3,12 @@ using ServiceStack.Web;
using System;
using System.Collections.Generic;
using System.IO;
+using System.Threading;
using System.Threading.Tasks;
using CommonIO;
namespace MediaBrowser.Api.Playback.Progressive
{
- public class ProgressiveStreamWriter : IStreamWriter, IHasOptions
- {
- private string Path { get; set; }
- private ILogger Logger { get; set; }
- private readonly IFileSystem _fileSystem;
- private readonly TranscodingJob _job;
-
- /// <summary>
- /// The _options
- /// </summary>
- private readonly IDictionary<string, string> _options = new Dictionary<string, string>();
- /// <summary>
- /// Gets the options.
- /// </summary>
- /// <value>The options.</value>
- public IDictionary<string, string> Options
- {
- get { return _options; }
- }
-
- /// <summary>
- /// Initializes a new instance of the <see cref="ProgressiveStreamWriter" /> class.
- /// </summary>
- /// <param name="path">The path.</param>
- /// <param name="logger">The logger.</param>
- /// <param name="fileSystem">The file system.</param>
- public ProgressiveStreamWriter(string path, ILogger logger, IFileSystem fileSystem, TranscodingJob job)
- {
- Path = path;
- Logger = logger;
- _fileSystem = fileSystem;
- _job = job;
- }
-
- /// <summary>
- /// Writes to.
- /// </summary>
- /// <param name="responseStream">The response stream.</param>
- public void WriteTo(Stream responseStream)
- {
- WriteToInternal(responseStream);
- }
-
- /// <summary>
- /// Writes to async.
- /// </summary>
- /// <param name="responseStream">The response stream.</param>
- /// <returns>Task.</returns>
- private void WriteToInternal(Stream responseStream)
- {
- try
- {
- var task = new ProgressiveFileCopier(_fileSystem, _job, Logger).StreamFile(Path, responseStream);
-
- Task.WaitAll(task);
- }
- catch (IOException)
- {
- // These error are always the same so don't dump the whole stack trace
- Logger.Error("Error streaming media. The client has most likely disconnected or transcoding has failed.");
-
- throw;
- }
- catch (Exception ex)
- {
- Logger.ErrorException("Error streaming media. The client has most likely disconnected or transcoding has failed.", ex);
-
- throw;
- }
- finally
- {
- if (_job != null)
- {
- ApiEntryPoint.Instance.OnTranscodeEndRequest(_job);
- }
- }
- }
- }
-
public class ProgressiveFileCopier
{
private readonly IFileSystem _fileSystem;
@@ -105,22 +27,18 @@ namespace MediaBrowser.Api.Playback.Progressive
_logger = logger;
}
- public async Task StreamFile(string path, Stream outputStream)
+ public async Task StreamFile(string path, Stream outputStream, CancellationToken cancellationToken)
{
var eofCount = 0;
- long position = 0;
- using (var fs = _fileSystem.GetFileStream(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite, false))
+ using (var fs = _fileSystem.GetFileStream(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite, true))
{
while (eofCount < 15)
{
- CopyToInternal(fs, outputStream, BufferSize);
-
- var fsPosition = fs.Position;
-
- var bytesRead = fsPosition - position;
+ var bytesRead = await CopyToAsyncInternal(fs, outputStream, BufferSize, cancellationToken).ConfigureAwait(false);
- //Logger.Debug("Streamed {0} bytes from file {1}", bytesRead, path);
+ //var position = fs.Position;
+ //_logger.Debug("Streamed {0} bytes to position {1} from file {2}", bytesRead, position, path);
if (bytesRead == 0)
{
@@ -128,57 +46,36 @@ namespace MediaBrowser.Api.Playback.Progressive
{
eofCount++;
}
- await Task.Delay(100).ConfigureAwait(false);
+ await Task.Delay(100, cancellationToken).ConfigureAwait(false);
}
else
{
eofCount = 0;
}
-
- position = fsPosition;
}
}
}
- private void CopyToInternal(Stream source, Stream destination, int bufferSize)
+ private async Task<int> CopyToAsyncInternal(Stream source, Stream destination, Int32 bufferSize, CancellationToken cancellationToken)
{
- var array = new byte[bufferSize];
- int count;
- while ((count = source.Read(array, 0, array.Length)) != 0)
- {
- //if (_job != null)
- //{
- // var didPause = false;
- // var totalPauseTime = 0;
+ byte[] buffer = new byte[bufferSize];
+ int bytesRead;
+ int totalBytesRead = 0;
- // if (_job.IsUserPaused)
- // {
- // _logger.Debug("Pausing writing to network stream while user has paused playback.");
-
- // while (_job.IsUserPaused && totalPauseTime < 30000)
- // {
- // didPause = true;
- // var pauseTime = 500;
- // totalPauseTime += pauseTime;
- // await Task.Delay(pauseTime).ConfigureAwait(false);
- // }
- // }
-
- // if (didPause)
- // {
- // _logger.Debug("Resuming writing to network stream due to user unpausing playback.");
- // }
- //}
-
- destination.Write(array, 0, count);
+ while ((bytesRead = await source.ReadAsync(buffer, 0, buffer.Length, cancellationToken).ConfigureAwait(false)) != 0)
+ {
+ await destination.WriteAsync(buffer, 0, bytesRead, cancellationToken).ConfigureAwait(false);
- _bytesWritten += count;
+ _bytesWritten += bytesRead;
+ totalBytesRead += bytesRead;
if (_job != null)
{
_job.BytesDownloaded = Math.Max(_job.BytesDownloaded ?? _bytesWritten, _bytesWritten);
}
}
+
+ return totalBytesRead;
}
}
}
diff --git a/MediaBrowser.Api/Playback/Progressive/VideoService.cs b/MediaBrowser.Api/Playback/Progressive/VideoService.cs
index 7c68b1731..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);
}
@@ -137,12 +138,9 @@ namespace MediaBrowser.Api.Playback.Progressive
args += " -mpegts_m2ts_mode 1";
}
- var isOutputMkv = string.Equals(state.OutputContainer, "mkv", StringComparison.OrdinalIgnoreCase);
-
if (string.Equals(videoCodec, "copy", StringComparison.OrdinalIgnoreCase))
{
- if (state.VideoStream != null && IsH264(state.VideoStream) &&
- (string.Equals(state.OutputContainer, "ts", StringComparison.OrdinalIgnoreCase) || isOutputMkv))
+ if (state.VideoStream != null && IsH264(state.VideoStream) && string.Equals(state.OutputContainer, "ts", StringComparison.OrdinalIgnoreCase) && !string.Equals(state.VideoStream.NalLengthSize, "0", StringComparison.OrdinalIgnoreCase))
{
args += " -bsf:v h264_mp4toannexb";
}
diff --git a/MediaBrowser.Api/Playback/StreamRequest.cs b/MediaBrowser.Api/Playback/StreamRequest.cs
index 1135a3a54..a8ca6aaa3 100644
--- a/MediaBrowser.Api/Playback/StreamRequest.cs
+++ b/MediaBrowser.Api/Playback/StreamRequest.cs
@@ -51,7 +51,9 @@ namespace MediaBrowser.Api.Playback
[ApiMember(Name = "MaxAudioChannels", Description = "Optional. Specify a maximum number of audio channels to encode to, e.g. 2", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")]
public int? MaxAudioChannels { get; set; }
-
+
+ public int? TranscodingMaxAudioChannels { get; set; }
+
/// <summary>
/// Gets or sets the audio sample rate.
/// </summary>
@@ -189,10 +191,11 @@ namespace MediaBrowser.Api.Playback
[ApiMember(Name = "CopyTimestamps", Description = "Whether or not to copy timestamps when transcoding with an offset. Defaults to false.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")]
public bool CopyTimestamps { get; set; }
-
- [ApiMember(Name = "Cabac", Description = "Enable if cabac encoding is required", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")]
- public bool? Cabac { get; set; }
-
+
+ public bool ForceLiveStream { get; set; }
+
+ public bool EnableSubtitlesInManifest { get; set; }
+
public VideoStreamRequest()
{
EnableAutoStreamCopy = true;
diff --git a/MediaBrowser.Api/Playback/StreamState.cs b/MediaBrowser.Api/Playback/StreamState.cs
index f1f6bb71f..da6be97b6 100644
--- a/MediaBrowser.Api/Playback/StreamState.cs
+++ b/MediaBrowser.Api/Playback/StreamState.cs
@@ -69,7 +69,29 @@ namespace MediaBrowser.Api.Playback
public List<string> PlayableStreamFileNames { get; set; }
- public int SegmentLength = 3;
+ public int SegmentLength
+ {
+ get
+ {
+ if (string.Equals(OutputVideoCodec, "copy", StringComparison.OrdinalIgnoreCase))
+ {
+ var userAgent = UserAgent ?? string.Empty;
+ if (userAgent.IndexOf("AppleTV", StringComparison.OrdinalIgnoreCase) != -1)
+ {
+ return 10;
+ }
+ if (userAgent.IndexOf("cfnetwork", StringComparison.OrdinalIgnoreCase) != -1)
+ {
+ return 10;
+ }
+
+ return 6;
+ }
+
+ return 3;
+ }
+ }
+
public int HlsListSize
{
get
@@ -84,9 +106,10 @@ 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; }
+ public string UserAgent { get; set; }
public StreamState(IMediaSourceManager mediaSourceManager, ILogger logger)
{
@@ -480,18 +503,5 @@ namespace MediaBrowser.Api.Playback
return false;
}
}
-
- public bool? IsTargetCabac
- {
- get
- {
- if (Request.Static)
- {
- return VideoStream == null ? null : VideoStream.IsCabac;
- }
-
- return true;
- }
- }
}
}
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 c3af09cd5..d0b6d6e78 100644
--- a/MediaBrowser.Api/Reports/ReportsService.cs
+++ b/MediaBrowser.Api/Reports/ReportsService.cs
@@ -42,7 +42,7 @@ namespace MediaBrowser.Api.Reports
/// <summary> Manager for library. </summary>
private readonly ILibraryManager _libraryManager; ///< Manager for library
- /// <summary> The localization. </summary>
+ /// <summary> The localization. </summary>
private readonly ILocalizationManager _localization; ///< The localization
@@ -58,10 +58,10 @@ namespace MediaBrowser.Api.Reports
/// <summary> Gets the given request. </summary>
/// <param name="request"> The request. </param>
/// <returns> A Task&lt;object&gt; </returns>
- public async Task<object> Get(GetActivityLogs request)
+ public object Get(GetActivityLogs request)
{
request.DisplayType = "Screen";
- ReportResult result = await GetReportActivities(request).ConfigureAwait(false);
+ ReportResult result = GetReportActivities(request);
return ToOptimizedResult(result);
}
@@ -104,7 +104,8 @@ namespace MediaBrowser.Api.Reports
return null;
request.DisplayType = "Screen";
- var reportResult = await GetReportResult(request);
+ var user = !string.IsNullOrWhiteSpace(request.UserId) ? _userManager.GetUserById(request.UserId) : null;
+ var reportResult = await GetReportResult(request, user);
return ToOptimizedResult(reportResult);
}
@@ -117,7 +118,8 @@ namespace MediaBrowser.Api.Reports
if (string.IsNullOrEmpty(request.IncludeItemTypes))
return null;
request.DisplayType = "Screen";
- var reportResult = await GetReportStatistic(request);
+ var user = !string.IsNullOrWhiteSpace(request.UserId) ? _userManager.GetUserById(request.UserId) : null;
+ var reportResult = await GetReportStatistic(request, user);
return ToOptimizedResult(reportResult);
}
@@ -150,6 +152,7 @@ namespace MediaBrowser.Api.Reports
headers["Content-Disposition"] = string.Format("attachment; filename=\"{0}\"", filename);
headers["Content-Encoding"] = "UTF-8";
+ var user = !string.IsNullOrWhiteSpace(request.UserId) ? _userManager.GetUserById(request.UserId) : null;
ReportResult result = null;
switch (reportViewType)
{
@@ -157,12 +160,12 @@ namespace MediaBrowser.Api.Reports
case ReportViewType.ReportData:
ReportIncludeItemTypes reportRowType = ReportHelper.GetRowType(request.IncludeItemTypes);
ReportBuilder dataBuilder = new ReportBuilder(_libraryManager);
- QueryResult<BaseItem> queryResult = await GetQueryResult(request).ConfigureAwait(false);
+ QueryResult<BaseItem> queryResult = await GetQueryResult(request, user).ConfigureAwait(false);
result = dataBuilder.GetResult(queryResult.Items, request);
result.TotalRecordCount = queryResult.TotalRecordCount;
break;
case ReportViewType.ReportActivities:
- result = await GetReportActivities(request).ConfigureAwait(false);
+ result = GetReportActivities(request);
break;
}
@@ -177,23 +180,15 @@ namespace MediaBrowser.Api.Reports
break;
}
- object ro = ResultFactory.GetResult(returnResult, contentType, headers);
- return ro;
+ return ResultFactory.GetResult(returnResult, contentType, headers);
}
#endregion
- #region [Private Methods]
-
- /// <summary> Gets items query. </summary>
- /// <param name="request"> The request. </param>
- /// <param name="user"> The user. </param>
- /// <returns> The items query. </returns>
private InternalItemsQuery GetItemsQuery(BaseReportRequest request, User user)
{
- var query = new InternalItemsQuery
+ var query = new InternalItemsQuery(user)
{
- User = user,
IsPlayed = request.IsPlayed,
MediaTypes = request.GetMediaTypes(),
IncludeItemTypes = request.GetIncludeItemTypes(),
@@ -213,7 +208,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,
@@ -232,6 +226,7 @@ namespace MediaBrowser.Api.Reports
Tags = request.GetTags(),
OfficialRatings = request.GetOfficialRatings(),
Genres = request.GetGenres(),
+ GenreIds = request.GetGenreIds(),
Studios = request.GetStudios(),
StudioIds = request.GetStudioIds(),
Person = request.Person,
@@ -246,9 +241,11 @@ namespace MediaBrowser.Api.Reports
MaxPlayers = request.MaxPlayers,
MinCommunityRating = request.MinCommunityRating,
MinCriticRating = request.MinCriticRating,
+ ParentId = string.IsNullOrWhiteSpace(request.ParentId) ? (Guid?)null : new Guid(request.ParentId),
ParentIndexNumber = request.ParentIndexNumber,
AiredDuringSeason = request.AiredDuringSeason,
- AlbumArtistStartsWithOrGreater = request.AlbumArtistStartsWithOrGreater
+ AlbumArtistStartsWithOrGreater = request.AlbumArtistStartsWithOrGreater,
+ EnableTotalRecordCount = request.EnableTotalRecordCount
};
if (!string.IsNullOrWhiteSpace(request.Ids))
@@ -326,15 +323,15 @@ namespace MediaBrowser.Api.Reports
}
// Min official rating
- if (!string.IsNullOrEmpty(request.MinOfficialRating))
+ if (!string.IsNullOrWhiteSpace(request.MinOfficialRating))
{
query.MinParentalRating = _localization.GetRatingLevel(request.MinOfficialRating);
}
// Max official rating
- if (!string.IsNullOrEmpty(request.MaxOfficialRating))
+ if (!string.IsNullOrWhiteSpace(request.MaxOfficialRating))
{
- query.MaxParentalRating = _localization.GetRatingLevel(request.MinOfficialRating);
+ query.MaxParentalRating = _localization.GetRatingLevel(request.MaxOfficialRating);
}
// Artists
@@ -358,98 +355,111 @@ namespace MediaBrowser.Api.Reports
query.AlbumNames = request.Albums.Split('|');
}
- if (request.HasQueryLimit == false)
- {
- query.StartIndex = null;
- query.Limit = null;
- }
-
return query;
}
- /// <summary> Gets query result. </summary>
- /// <param name="request"> The request. </param>
- /// <returns> The query result. </returns>
- private async Task<QueryResult<BaseItem>> GetQueryResult(BaseReportRequest request)
+ private async Task<QueryResult<BaseItem>> GetQueryResult(BaseReportRequest request, User user)
{
- // Placeholder in case needed later
+ // all report queries currently need this because it's not being specified
request.Recursive = true;
- var user = !string.IsNullOrWhiteSpace(request.UserId) ? _userManager.GetUserById(request.UserId) : null;
- request.Fields = "MediaSources,DateCreated,Settings,Studios,SyncInfo,ItemCounts";
-
- var parentItem = string.IsNullOrEmpty(request.ParentId) ?
- (user == null ? _libraryManager.RootFolder : user.RootFolder) :
- _libraryManager.GetItemById(request.ParentId);
var item = string.IsNullOrEmpty(request.ParentId) ?
user == null ? _libraryManager.RootFolder : user.RootFolder :
- parentItem;
+ _libraryManager.GetItemById(request.ParentId);
- IEnumerable<BaseItem> items;
+ if (string.Equals(request.IncludeItemTypes, "Playlist", StringComparison.OrdinalIgnoreCase))
+ {
+ //item = user == null ? _libraryManager.RootFolder : user.RootFolder;
+ }
+ else if (string.Equals(request.IncludeItemTypes, "BoxSet", StringComparison.OrdinalIgnoreCase))
+ {
+ item = user == null ? _libraryManager.RootFolder : user.RootFolder;
+ }
- if (request.Recursive)
+ // Default list type = children
+
+ var folder = item as Folder;
+ if (folder == null)
{
- var result = await ((Folder)item).GetItems(GetItemsQuery(request, user)).ConfigureAwait(false);
- return result;
+ folder = user == null ? _libraryManager.RootFolder : _libraryManager.GetUserRootFolder();
}
- else
+
+ if (!string.IsNullOrEmpty(request.Ids))
{
- if (user == null)
+ request.Recursive = true;
+ var query = GetItemsQuery(request, user);
+ var result = await folder.GetItems(query).ConfigureAwait(false);
+
+ if (string.IsNullOrWhiteSpace(request.SortBy))
{
- var result = await ((Folder)item).GetItems(GetItemsQuery(request, null)).ConfigureAwait(false);
- return result;
+ var ids = query.ItemIds.ToList();
+
+ // Try to preserve order
+ result.Items = result.Items.OrderBy(i => ids.IndexOf(i.Id.ToString("N"))).ToArray();
}
- var userRoot = item as UserRootFolder;
+ return result;
+ }
- if (userRoot == null)
- {
- var result = await ((Folder)item).GetItems(GetItemsQuery(request, user)).ConfigureAwait(false);
+ if (request.Recursive)
+ {
+ return await folder.GetItems(GetItemsQuery(request, user)).ConfigureAwait(false);
+ }
- return result;
- }
+ if (user == null)
+ {
+ return await folder.GetItems(GetItemsQuery(request, null)).ConfigureAwait(false);
+ }
+
+ var userRoot = item as UserRootFolder;
- items = ((Folder)item).GetChildren(user, true);
+ if (userRoot == null)
+ {
+ return await folder.GetItems(GetItemsQuery(request, user)).ConfigureAwait(false);
}
- return new QueryResult<BaseItem> { Items = items.ToArray() };
+ IEnumerable<BaseItem> items = folder.GetChildren(user, true);
+ var itemsArray = items.ToArray();
+
+ return new QueryResult<BaseItem>
+ {
+ Items = itemsArray,
+ TotalRecordCount = itemsArray.Length
+ };
}
+ #region [Private Methods]
+
/// <summary> Gets report activities. </summary>
/// <param name="request"> The request. </param>
/// <returns> The report activities. </returns>
- private Task<ReportResult> GetReportActivities(IReportsDownload request)
+ private ReportResult GetReportActivities(IReportsDownload request)
{
- return Task<ReportResult>.Run(() =>
- {
- DateTime? minDate = string.IsNullOrWhiteSpace(request.MinDate) ?
- (DateTime?)null :
- DateTime.Parse(request.MinDate, null, DateTimeStyles.RoundtripKind).ToUniversalTime();
-
- QueryResult<ActivityLogEntry> queryResult;
- if (request.HasQueryLimit)
- queryResult = _repo.GetActivityLogEntries(minDate, request.StartIndex, request.Limit);
- else
- queryResult = _repo.GetActivityLogEntries(minDate, request.StartIndex, null);
- //var queryResult = _activityManager.GetActivityLogEntries(minDate, request.StartIndex, request.Limit);
-
- ReportActivitiesBuilder builder = new ReportActivitiesBuilder(_libraryManager, _userManager);
- var result = builder.GetResult(queryResult, request);
- result.TotalRecordCount = queryResult.TotalRecordCount;
- return result;
+ DateTime? minDate = string.IsNullOrWhiteSpace(request.MinDate) ?
+ (DateTime?)null :
+ DateTime.Parse(request.MinDate, null, DateTimeStyles.RoundtripKind).ToUniversalTime();
- });
+ QueryResult<ActivityLogEntry> queryResult;
+ if (request.HasQueryLimit)
+ queryResult = _repo.GetActivityLogEntries(minDate, request.StartIndex, request.Limit);
+ else
+ queryResult = _repo.GetActivityLogEntries(minDate, request.StartIndex, null);
+ //var queryResult = _activityManager.GetActivityLogEntries(minDate, request.StartIndex, request.Limit);
+ ReportActivitiesBuilder builder = new ReportActivitiesBuilder(_libraryManager, _userManager);
+ var result = builder.GetResult(queryResult, request);
+ result.TotalRecordCount = queryResult.TotalRecordCount;
+ return result;
}
/// <summary> Gets report result. </summary>
/// <param name="request"> The request. </param>
/// <returns> The report result. </returns>
- private async Task<ReportResult> GetReportResult(GetItemReport request)
+ private async Task<ReportResult> GetReportResult(GetItemReport request, User user)
{
ReportBuilder reportBuilder = new ReportBuilder(_libraryManager);
- QueryResult<BaseItem> queryResult = await GetQueryResult(request).ConfigureAwait(false);
+ QueryResult<BaseItem> queryResult = await GetQueryResult(request, user).ConfigureAwait(false);
ReportResult reportResult = reportBuilder.GetResult(queryResult.Items, request);
reportResult.TotalRecordCount = queryResult.TotalRecordCount;
@@ -459,10 +469,10 @@ namespace MediaBrowser.Api.Reports
/// <summary> Gets report statistic. </summary>
/// <param name="request"> The request. </param>
/// <returns> The report statistic. </returns>
- private async Task<ReportStatResult> GetReportStatistic(GetReportStatistics request)
+ private async Task<ReportStatResult> GetReportStatistic(GetReportStatistics request, User user)
{
ReportIncludeItemTypes reportRowType = ReportHelper.GetRowType(request.IncludeItemTypes);
- QueryResult<BaseItem> queryResult = await GetQueryResult(request).ConfigureAwait(false);
+ QueryResult<BaseItem> queryResult = await GetQueryResult(request, user).ConfigureAwait(false);
ReportStatBuilder reportBuilder = new ReportStatBuilder(_libraryManager);
ReportStatResult reportResult = reportBuilder.GetResult(queryResult.Items, ReportHelper.GetRowType(request.IncludeItemTypes), request.TopItems ?? 5);
diff --git a/MediaBrowser.Api/SimilarItemsHelper.cs b/MediaBrowser.Api/SimilarItemsHelper.cs
index 277bba1dd..a1e47bd8f 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
{
@@ -23,6 +25,8 @@ namespace MediaBrowser.Api
/// <value>The id.</value>
[ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")]
public string Id { get; set; }
+
+ public string ExcludeArtistIds { get; set; }
}
public class BaseGetSimilarItems : IReturn<ItemsResult>, IHasItemFields
@@ -54,7 +58,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;
@@ -68,6 +72,12 @@ namespace MediaBrowser.Api
Recursive = true
};
+ // ExcludeArtistIds
+ if (!string.IsNullOrEmpty(request.ExcludeArtistIds))
+ {
+ query.ExcludeArtistIds = request.ExcludeArtistIds.Split('|');
+ }
+
var inputItems = libraryManager.GetItemList(query);
var items = GetSimilaritems(item, libraryManager, inputItems, getSimilarityScore)
@@ -80,14 +90,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 +126,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 14bd6b61f..1bebd42eb 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,34 +53,33 @@ 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)
{
_config.Configuration.IsStartupWizardCompleted = true;
- _config.Configuration.EnableLocalizedGuids = true;
- _config.Configuration.EnableCustomPathSubFolders = true;
- _config.Configuration.EnableDateLastRefresh = true;
- _config.Configuration.EnableStandaloneMusicKeys = true;
- _config.Configuration.EnableCaseSensitiveItemIds = true;
+ SetWizardFinishValues(_config.Configuration);
_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,6 +111,15 @@ namespace MediaBrowser.Api
return result;
}
+ private void SetWizardFinishValues(ServerConfiguration config)
+ {
+ config.EnableLocalizedGuids = true;
+ config.EnableStandaloneMusicKeys = true;
+ config.EnableCaseSensitiveItemIds = true;
+ //config.EnableFolderView = true;
+ config.SchemaVersion = 108;
+ }
+
public void Post(UpdateStartupConfiguration request)
{
_config.Configuration.UICulture = request.UICulture;
@@ -225,6 +234,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 c3f31e75a..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.")]
@@ -159,6 +163,7 @@ namespace MediaBrowser.Api.Subtitles
builder.AppendLine("#EXT-X-TARGETDURATION:" + request.SegmentLength.ToString(CultureInfo.InvariantCulture));
builder.AppendLine("#EXT-X-VERSION:3");
builder.AppendLine("#EXT-X-MEDIA-SEQUENCE:0");
+ builder.AppendLine("#EXT-X-PLAYLIST-TYPE:VOD");
long positionTicks = 0;
var segmentLengthTicks = TimeSpan.FromSeconds(request.SegmentLength).Ticks;
@@ -170,11 +175,11 @@ namespace MediaBrowser.Api.Subtitles
var remaining = runtime - positionTicks;
var lengthTicks = Math.Min(remaining, segmentLengthTicks);
- builder.AppendLine("#EXTINF:" + TimeSpan.FromTicks(lengthTicks).TotalSeconds.ToString(CultureInfo.InvariantCulture));
+ builder.AppendLine("#EXTINF:" + TimeSpan.FromTicks(lengthTicks).TotalSeconds.ToString(CultureInfo.InvariantCulture) + ",");
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);
@@ -189,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))
{
@@ -205,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)
@@ -247,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 5b5b0a902..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
{
@@ -123,7 +125,7 @@ namespace MediaBrowser.Api
}
[Route("/Shows/{Id}/Episodes", "GET", Summary = "Gets episodes for a tv season")]
- public class GetEpisodes : IReturn<ItemsResult>, IHasItemFields
+ public class GetEpisodes : IReturn<ItemsResult>, IHasItemFields, IHasDtoOptions
{
/// <summary>
/// Gets or sets the user id.
@@ -173,10 +175,19 @@ namespace MediaBrowser.Api
/// <value>The limit.</value>
[ApiMember(Name = "Limit", Description = "Optional. The maximum number of records to return", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")]
public int? Limit { get; set; }
+
+ [ApiMember(Name = "EnableImages", Description = "Optional, include image information in output", IsRequired = false, DataType = "boolean", ParameterType = "query", Verb = "GET")]
+ public bool? EnableImages { get; set; }
+
+ [ApiMember(Name = "ImageTypeLimit", Description = "Optional, the max number of images to return, per image type", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")]
+ public int? ImageTypeLimit { get; set; }
+
+ [ApiMember(Name = "EnableImageTypes", Description = "Optional. The image types to include in the output.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
+ public string EnableImageTypes { get; set; }
}
[Route("/Shows/{Id}/Seasons", "GET", Summary = "Gets seasons for a tv series")]
- public class GetSeasons : IReturn<ItemsResult>, IHasItemFields
+ public class GetSeasons : IReturn<ItemsResult>, IHasItemFields, IHasDtoOptions
{
/// <summary>
/// Gets or sets the user id.
@@ -206,6 +217,15 @@ namespace MediaBrowser.Api
[ApiMember(Name = "AdjacentTo", Description = "Optional. Return items that are siblings of a supplied item.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
public string AdjacentTo { get; set; }
+
+ [ApiMember(Name = "EnableImages", Description = "Optional, include image information in output", IsRequired = false, DataType = "boolean", ParameterType = "query", Verb = "GET")]
+ public bool? EnableImages { get; set; }
+
+ [ApiMember(Name = "ImageTypeLimit", Description = "Optional, the max number of images to return, per image type", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")]
+ public int? ImageTypeLimit { get; set; }
+
+ [ApiMember(Name = "EnableImageTypes", Description = "Optional. The image types to include in the output.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
+ public string EnableImageTypes { get; set; }
}
/// <summary>
@@ -253,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();
+ 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)
{
@@ -284,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
{
@@ -306,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
{
@@ -321,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
{
@@ -354,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);
@@ -385,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
@@ -397,21 +441,10 @@ namespace MediaBrowser.Api
private IEnumerable<Season> FilterVirtualSeasons(GetSeasons request, IEnumerable<Season> items)
{
- if (request.IsMissing.HasValue && request.IsVirtualUnaired.HasValue)
- {
- var isMissing = request.IsMissing.Value;
- var isVirtualUnaired = request.IsVirtualUnaired.Value;
-
- if (!isMissing && !isVirtualUnaired)
- {
- return items.Where(i => !i.IsMissingOrVirtualUnaired);
- }
- }
-
if (request.IsMissing.HasValue)
{
var val = request.IsMissing.Value;
- items = items.Where(i => i.IsMissingSeason == val);
+ items = items.Where(i => (i.IsMissingSeason) == val);
}
if (request.IsVirtualUnaired.HasValue)
@@ -423,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);
@@ -449,7 +482,16 @@ namespace MediaBrowser.Api
throw new ResourceNotFoundException("No series exists with Id " + request.Id);
}
- episodes = series.GetEpisodes(user, request.Season.Value);
+ var season = series.GetSeasons(user).FirstOrDefault(i => i.IndexNumber == request.Season.Value);
+
+ if (season == null)
+ {
+ episodes = new List<Episode>();
+ }
+ else
+ {
+ episodes = series.GetEpisodes(user, season);
+ }
}
else
{
@@ -490,14 +532,13 @@ namespace MediaBrowser.Api
returnItems = UserViewBuilder.FilterForAdjacency(returnItems, request.AdjacentTo);
}
- var returnList = _libraryManager.ReplaceVideosWithPrimaryVersions(returnItems)
- .ToList();
+ var returnList = returnItems.ToList();
var pagedItems = ApplyPaging(returnList, request.StartIndex, request.Limit);
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 6ae2b0832..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>
@@ -121,6 +253,13 @@ namespace MediaBrowser.Api.UserLibrary
var includeItemTypes = request.GetIncludeItemTypes();
var mediaTypes = request.GetMediaTypes();
+ var query = new InternalItemsQuery(user)
+ {
+ ExcludeItemTypes = excludeItemTypes,
+ IncludeItemTypes = includeItemTypes,
+ MediaTypes = mediaTypes
+ };
+
Func<BaseItem, bool> filter = i => FilterItem(request, i, excludeItemTypes, includeItemTypes, mediaTypes);
if (parentItem.IsFolder)
@@ -130,7 +269,7 @@ namespace MediaBrowser.Api.UserLibrary
if (!string.IsNullOrWhiteSpace(request.UserId))
{
items = request.Recursive ?
- folder.GetRecursiveChildren(user, filter) :
+ folder.GetRecursiveChildren(user, query) :
folder.GetChildren(user, true).Where(filter);
}
else
@@ -274,7 +413,7 @@ namespace MediaBrowser.Api.UserLibrary
{
items = items.Where(i =>
{
- var userdata = UserDataRepository.GetUserData(user.Id, i.GetUserDataKey());
+ var userdata = UserDataRepository.GetUserData(user, i);
return userdata != null && userdata.Likes.HasValue && !userdata.Likes.Value;
});
@@ -284,7 +423,7 @@ namespace MediaBrowser.Api.UserLibrary
{
items = items.Where(i =>
{
- var userdata = UserDataRepository.GetUserData(user.Id, i.GetUserDataKey());
+ var userdata = UserDataRepository.GetUserData(user, i);
return userdata != null && userdata.Likes.HasValue && userdata.Likes.Value;
});
@@ -294,7 +433,7 @@ namespace MediaBrowser.Api.UserLibrary
{
items = items.Where(i =>
{
- var userdata = UserDataRepository.GetUserData(user.Id, i.GetUserDataKey());
+ var userdata = UserDataRepository.GetUserData(user, i);
var likes = userdata.Likes ?? false;
var favorite = userdata.IsFavorite;
@@ -307,7 +446,7 @@ namespace MediaBrowser.Api.UserLibrary
{
items = items.Where(i =>
{
- var userdata = UserDataRepository.GetUserData(user.Id, i.GetUserDataKey());
+ var userdata = UserDataRepository.GetUserData(user, i);
return userdata != null && userdata.IsFavorite;
});
@@ -326,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;
}
@@ -372,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 6867f6308..3e9a541c0 100644
--- a/MediaBrowser.Api/UserLibrary/BaseItemsRequest.cs
+++ b/MediaBrowser.Api/UserLibrary/BaseItemsRequest.cs
@@ -12,6 +12,7 @@ namespace MediaBrowser.Api.UserLibrary
protected BaseItemsRequest()
{
EnableImages = true;
+ EnableTotalRecordCount = true;
}
/// <summary>
@@ -99,12 +100,13 @@ 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; }
-
+
+ public string ExcludeItemIds { get; set; }
+
+ public bool EnableTotalRecordCount { get; set; }
+
/// <summary>
/// Skips over a given number of items within the results. Use for paging.
/// </summary>
@@ -264,6 +266,8 @@ namespace MediaBrowser.Api.UserLibrary
[ApiMember(Name = "Artists", Description = "Optional. If specified, results will be filtered based on artist. This allows multiple, pipe delimeted.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)]
public string Artists { get; set; }
+ public string ExcludeArtistIds { get; set; }
+
[ApiMember(Name = "ArtistIds", Description = "Optional. If specified, results will be filtered based on artist. This allows multiple, pipe delimeted.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)]
public string ArtistIds { get; set; }
@@ -367,6 +371,11 @@ namespace MediaBrowser.Api.UserLibrary
return (IncludeItemTypes ?? string.Empty).Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
}
+ public string[] GetExcludeItemIds()
+ {
+ return (ExcludeItemIds ?? string.Empty).Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
+ }
+
public string[] GetExcludeItemTypes()
{
return (ExcludeItemTypes ?? string.Empty).Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
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 cfdc40bb2..b4d88a7f8 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 GetQueryResult(request, user).ConfigureAwait(false);
+
+ if (result == null)
+ {
+ throw new InvalidOperationException("GetItemsToSerialize returned null");
+ }
- var result = await GetItemsToSerialize(request, user).ConfigureAwait(false);
+ 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()
};
}
@@ -102,7 +135,7 @@ namespace MediaBrowser.Api.UserLibrary
/// <param name="request">The request.</param>
/// <param name="user">The user.</param>
/// <returns>IEnumerable{BaseItem}.</returns>
- private async Task<QueryResult<BaseItem>> GetItemsToSerialize(GetItems request, User user)
+ private async Task<QueryResult<BaseItem>> GetQueryResult(GetItems request, User user)
{
var item = string.IsNullOrEmpty(request.ParentId) ?
user == null ? _libraryManager.RootFolder : user.RootFolder :
@@ -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,28 +177,22 @@ namespace MediaBrowser.Api.UserLibrary
if (request.Recursive)
{
- var result = await ((Folder)item).GetItems(GetItemsQuery(request, user)).ConfigureAwait(false);
-
- return result;
+ return await folder.GetItems(GetItemsQuery(request, user)).ConfigureAwait(false);
}
if (user == null)
{
- var result = await ((Folder)item).GetItems(GetItemsQuery(request, null)).ConfigureAwait(false);
-
- return result;
+ return await folder.GetItems(GetItemsQuery(request, null)).ConfigureAwait(false);
}
var userRoot = item as UserRootFolder;
if (userRoot == null)
{
- var result = await ((Folder)item).GetItems(GetItemsQuery(request, user)).ConfigureAwait(false);
-
- return result;
+ 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();
@@ -193,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,
@@ -230,7 +262,9 @@ namespace MediaBrowser.Api.UserLibrary
ParentId = string.IsNullOrWhiteSpace(request.ParentId) ? (Guid?)null : new Guid(request.ParentId),
ParentIndexNumber = request.ParentIndexNumber,
AiredDuringSeason = request.AiredDuringSeason,
- AlbumArtistStartsWithOrGreater = request.AlbumArtistStartsWithOrGreater
+ AlbumArtistStartsWithOrGreater = request.AlbumArtistStartsWithOrGreater,
+ EnableTotalRecordCount = request.EnableTotalRecordCount,
+ ExcludeItemIds = request.GetExcludeItemIds()
};
if (!string.IsNullOrWhiteSpace(request.Ids))
@@ -306,17 +340,17 @@ namespace MediaBrowser.Api.UserLibrary
{
query.LocationTypes = request.LocationTypes.Split(',').Select(d => (LocationType)Enum.Parse(typeof(LocationType), d, true)).ToArray();
}
-
+
// Min official rating
- if (!string.IsNullOrEmpty(request.MinOfficialRating))
+ if (!string.IsNullOrWhiteSpace(request.MinOfficialRating))
{
query.MinParentalRating = _localization.GetRatingLevel(request.MinOfficialRating);
}
// Max official rating
- if (!string.IsNullOrEmpty(request.MaxOfficialRating))
+ if (!string.IsNullOrWhiteSpace(request.MaxOfficialRating))
{
- query.MaxParentalRating = _localization.GetRatingLevel(request.MinOfficialRating);
+ query.MaxParentalRating = _localization.GetRatingLevel(request.MaxOfficialRating);
}
// Artists
@@ -334,6 +368,12 @@ namespace MediaBrowser.Api.UserLibrary
query.ArtistNames = request.Artists.Split('|');
}
+ // ExcludeArtistIds
+ if (!string.IsNullOrEmpty(request.ExcludeArtistIds))
+ {
+ query.ExcludeArtistIds = request.ExcludeArtistIds.Split('|');
+ }
+
// Albums
if (!string.IsNullOrEmpty(request.Albums))
{
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 c2c481cb6..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);
}
@@ -519,17 +519,15 @@ namespace MediaBrowser.Api.UserLibrary
var item = string.IsNullOrEmpty(itemId) ? user.RootFolder : _libraryManager.GetItemById(itemId);
- var key = item.GetUserDataKey();
-
// Get the user data for this item
- var data = _userDataRepository.GetUserData(user.Id, key);
+ var data = _userDataRepository.GetUserData(user, item);
// Set favorite status
data.IsFavorite = isFavorite;
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>
@@ -547,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);
}
@@ -567,16 +565,14 @@ namespace MediaBrowser.Api.UserLibrary
var item = string.IsNullOrEmpty(itemId) ? user.RootFolder : _libraryManager.GetItemById(itemId);
- var key = item.GetUserDataKey();
-
// Get the user data for this item
- var data = _userDataRepository.GetUserData(user.Id, key);
+ var data = _userDataRepository.GetUserData(user, item);
data.Likes = likes;
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.Api/VideosService.cs b/MediaBrowser.Api/VideosService.cs
index c6ec69c85..c8dbb7bb2 100644
--- a/MediaBrowser.Api/VideosService.cs
+++ b/MediaBrowser.Api/VideosService.cs
@@ -130,6 +130,7 @@ namespace MediaBrowser.Api
var items = request.Ids.Split(',')
.Select(i => new Guid(i))
.Select(i => _libraryManager.GetItemById(i))
+ .OfType<Video>()
.ToList();
if (items.Count < 2)
@@ -137,14 +138,7 @@ namespace MediaBrowser.Api
throw new ArgumentException("Please supply at least two videos to merge.");
}
- if (items.Any(i => !(i is Video)))
- {
- throw new ArgumentException("Only videos can be grouped together.");
- }
-
- var videos = items.Cast<Video>().ToList();
-
- var videosWithVersions = videos.Where(i => i.MediaSourceCount > 1)
+ var videosWithVersions = items.Where(i => i.MediaSourceCount > 1)
.ToList();
if (videosWithVersions.Count > 1)
@@ -156,7 +150,7 @@ namespace MediaBrowser.Api
if (primaryVersion == null)
{
- primaryVersion = videos.OrderBy(i =>
+ primaryVersion = items.OrderBy(i =>
{
if (i.Video3DFormat.HasValue)
{
@@ -179,9 +173,9 @@ namespace MediaBrowser.Api
}).First();
}
- foreach (var item in videos.Where(i => i.Id != primaryVersion.Id))
+ foreach (var item in items.Where(i => i.Id != primaryVersion.Id))
{
- item.PrimaryVersionId = primaryVersion.Id;
+ item.PrimaryVersionId = primaryVersion.Id.ToString("N");
await item.UpdateToRepository(ItemUpdateType.MetadataEdit, CancellationToken.None).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/Configuration/BaseConfigurationManager.cs b/MediaBrowser.Common.Implementations/Configuration/BaseConfigurationManager.cs
index 7f9299ff2..fa15023ca 100644
--- a/MediaBrowser.Common.Implementations/Configuration/BaseConfigurationManager.cs
+++ b/MediaBrowser.Common.Implementations/Configuration/BaseConfigurationManager.cs
@@ -123,6 +123,7 @@ namespace MediaBrowser.Common.Implementations.Configuration
/// </summary>
public void SaveConfiguration()
{
+ Logger.Info("Saving system configuration");
var path = CommonApplicationPaths.SystemConfigurationFilePath;
Directory.CreateDirectory(Path.GetDirectoryName(path));
diff --git a/MediaBrowser.Common.Implementations/HttpClientManager/HttpClientManager.cs b/MediaBrowser.Common.Implementations/HttpClientManager/HttpClientManager.cs
index f9dbd766f..371757f6c 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,33 @@ 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 url = options.Url;
+
+ var uriAddress = new Uri(url);
+ var userInfo = uriAddress.UserInfo;
+ if (!string.IsNullOrWhiteSpace(userInfo))
+ {
+ _logger.Info("Found userInfo in url: {0} ... url: {1}", userInfo, url);
+ url = url.Replace(userInfo + "@", string.Empty);
+ }
+
+ var request = CreateWebRequest(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);
@@ -183,9 +193,27 @@ namespace MediaBrowser.Common.Implementations.HttpClientManager
}
}
+ if (!string.IsNullOrWhiteSpace(userInfo))
+ {
+ var parts = userInfo.Split(':');
+ if (parts.Length == 2)
+ {
+ request.Credentials = GetCredential(url, parts[0], parts[1]);
+ request.PreAuthenticate = true;
+ }
+ }
+
return request;
}
+ private CredentialCache GetCredential(string url, string username, string password)
+ {
+ //ServicePointManager.SecurityProtocol = SecurityProtocolType.Ssl3;
+ CredentialCache credentialCache = new CredentialCache();
+ credentialCache.Add(new Uri(url), "Basic", new NetworkCredential(username, password));
+ return credentialCache;
+ }
+
private void AddRequestHeaders(HttpWebRequest request, HttpRequestOptions options)
{
foreach (var header in options.RequestHeaders.ToList())
@@ -296,6 +324,8 @@ namespace MediaBrowser.Common.Implementations.HttpClientManager
private async Task<HttpResponseInfo> GetCachedResponse(string responseCachePath, TimeSpan cacheLength, string url)
{
+ _logger.Info("Checking for cache file {0}", responseCachePath);
+
try
{
if (_fileSystem.GetLastWriteTimeUtc(responseCachePath).Add(cacheLength) > DateTime.UtcNow)
@@ -366,7 +396,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 +586,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 2017b40f4..108eddcf9 100644
--- a/MediaBrowser.Common.Implementations/MediaBrowser.Common.Implementations.csproj
+++ b/MediaBrowser.Common.Implementations/MediaBrowser.Common.Implementations.csproj
@@ -54,8 +54,9 @@
<Reference Include="MoreLinq">
<HintPath>..\packages\morelinq.1.4.0\lib\net35\MoreLinq.dll</HintPath>
</Reference>
- <Reference Include="NLog">
- <HintPath>..\packages\NLog.4.2.3\lib\net45\NLog.dll</HintPath>
+ <Reference Include="NLog, Version=4.0.0.0, Culture=neutral, PublicKeyToken=5120e14c03d0593c, processorArchitecture=MSIL">
+ <HintPath>..\packages\NLog.4.3.5\lib\net45\NLog.dll</HintPath>
+ <Private>True</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>
@@ -64,8 +65,9 @@
<SpecificVersion>False</SpecificVersion>
<HintPath>..\ThirdParty\SharpCompress\SharpCompress.dll</HintPath>
</Reference>
- <Reference Include="SimpleInjector">
- <HintPath>..\packages\SimpleInjector.3.1.2\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" />
<Reference Include="System.Configuration" />
diff --git a/MediaBrowser.Common.Implementations/ScheduledTasks/ScheduledTaskWorker.cs b/MediaBrowser.Common.Implementations/ScheduledTasks/ScheduledTaskWorker.cs
index 8d727a112..dcd3a3025 100644
--- a/MediaBrowser.Common.Implementations/ScheduledTasks/ScheduledTaskWorker.cs
+++ b/MediaBrowser.Common.Implementations/ScheduledTasks/ScheduledTaskWorker.cs
@@ -106,6 +106,7 @@ namespace MediaBrowser.Common.Implementations.ScheduledTasks
InitTriggerEvents();
}
+ private bool _readFromFile = false;
/// <summary>
/// The _last execution result
/// </summary>
@@ -122,31 +123,29 @@ namespace MediaBrowser.Common.Implementations.ScheduledTasks
{
get
{
- if (_lastExecutionResult == null)
- {
- var path = GetHistoryFilePath();
+ var path = GetHistoryFilePath();
- lock (_lastExecutionResultSyncLock)
+ lock (_lastExecutionResultSyncLock)
+ {
+ if (_lastExecutionResult == null && !_readFromFile)
{
- 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);
}
+ _readFromFile = true;
}
}
@@ -311,7 +310,7 @@ namespace MediaBrowser.Common.Implementations.ScheduledTasks
trigger.Triggered -= trigger_Triggered;
trigger.Triggered += trigger_Triggered;
- trigger.Start(LastExecutionResult, isApplicationStartup);
+ trigger.Start(LastExecutionResult, Logger, Name, isApplicationStartup);
}
}
@@ -339,7 +338,7 @@ namespace MediaBrowser.Common.Implementations.ScheduledTasks
await Task.Delay(1000).ConfigureAwait(false);
- trigger.Start(LastExecutionResult, false);
+ trigger.Start(LastExecutionResult, Logger, Name, false);
}
private Task _currentTask;
diff --git a/MediaBrowser.Common.Implementations/ScheduledTasks/TaskManager.cs b/MediaBrowser.Common.Implementations/ScheduledTasks/TaskManager.cs
index 3aab59ee1..b3a00b35f 100644
--- a/MediaBrowser.Common.Implementations/ScheduledTasks/TaskManager.cs
+++ b/MediaBrowser.Common.Implementations/ScheduledTasks/TaskManager.cs
@@ -88,8 +88,6 @@ namespace MediaBrowser.Common.Implementations.ScheduledTasks
_fileSystem = fileSystem;
ScheduledTasks = new IScheduledTaskWorker[] { };
-
- BindToSystemEvent();
}
private void BindToSystemEvent()
@@ -259,6 +257,8 @@ namespace MediaBrowser.Common.Implementations.ScheduledTasks
myTasks.AddRange(list.Select(t => new ScheduledTaskWorker(t, ApplicationPaths, this, JsonSerializer, Logger, _fileSystem)));
ScheduledTasks = myTasks.ToArray();
+
+ BindToSystemEvent();
}
/// <summary>
diff --git a/MediaBrowser.Common.Implementations/ScheduledTasks/Tasks/DeleteCacheFileTask.cs b/MediaBrowser.Common.Implementations/ScheduledTasks/Tasks/DeleteCacheFileTask.cs
index b4cc5d753..0a2b9222a 100644
--- a/MediaBrowser.Common.Implementations/ScheduledTasks/Tasks/DeleteCacheFileTask.cs
+++ b/MediaBrowser.Common.Implementations/ScheduledTasks/Tasks/DeleteCacheFileTask.cs
@@ -71,7 +71,7 @@ namespace MediaBrowser.Common.Implementations.ScheduledTasks.Tasks
progress.Report(90);
- minDateModified = DateTime.UtcNow.AddDays(-2);
+ minDateModified = DateTime.UtcNow.AddDays(-1);
try
{
diff --git a/MediaBrowser.Common.Implementations/Security/MbAdmin.cs b/MediaBrowser.Common.Implementations/Security/MbAdmin.cs
index ab4a83257..76ff92c2e 100644
--- a/MediaBrowser.Common.Implementations/Security/MbAdmin.cs
+++ b/MediaBrowser.Common.Implementations/Security/MbAdmin.cs
@@ -3,11 +3,11 @@ namespace MediaBrowser.Common.Implementations.Security
{
public class MbAdmin
{
- public const string HttpUrl = "http://www.mb3admin.com/admin/";
+ public const string HttpUrl = "https://www.mb3admin.com/admin/";
/// <summary>
/// Leaving as http for now until we get it squared away
/// </summary>
- public const string HttpsUrl = "http://www.mb3admin.com/admin/";
+ public const string HttpsUrl = "https://www.mb3admin.com/admin/";
}
}
diff --git a/MediaBrowser.Common.Implementations/Security/PluginSecurityManager.cs b/MediaBrowser.Common.Implementations/Security/PluginSecurityManager.cs
index af58c3731..4e01041bc 100644
--- a/MediaBrowser.Common.Implementations/Security/PluginSecurityManager.cs
+++ b/MediaBrowser.Common.Implementations/Security/PluginSecurityManager.cs
@@ -21,7 +21,7 @@ namespace MediaBrowser.Common.Implementations.Security
public class PluginSecurityManager : ISecurityManager
{
private const string MBValidateUrl = MbAdmin.HttpsUrl + "service/registration/validate";
- private const string AppstoreRegUrl = /*MbAdmin.HttpsUrl*/ "http://mb3admin.com/admin/service/appstore/register";
+ private const string AppstoreRegUrl = /*MbAdmin.HttpsUrl*/ "https://mb3admin.com/admin/service/appstore/register";
/// <summary>
/// The _is MB supporter
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..756741e0d 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
@@ -77,6 +80,7 @@ namespace MediaBrowser.Common.Implementations.Serialization
/// <param name="file">The file.</param>
public void SerializeToFile(object obj, string file)
{
+ _logger.Debug("Serializing to file {0}", file);
using (var stream = new FileStream(file, FileMode.Create))
{
SerializeToStream(obj, stream);
@@ -91,6 +95,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/Updates/GithubUpdater.cs b/MediaBrowser.Common.Implementations/Updates/GithubUpdater.cs
index 82ebf92b2..d1ec30210 100644
--- a/MediaBrowser.Common.Implementations/Updates/GithubUpdater.cs
+++ b/MediaBrowser.Common.Implementations/Updates/GithubUpdater.cs
@@ -54,7 +54,9 @@ namespace MediaBrowser.Common.Implementations.Updates
{
if (updateLevel == PackageVersionClass.Release)
{
- obj = obj.Where(i => !i.prerelease).ToArray();
+ // Technically all we need to do is check that it's not pre-release
+ // But let's addititional checks for -beta and -dev to handle builds that might be temporarily tagged incorrectly.
+ obj = obj.Where(i => !i.prerelease && !i.name.EndsWith("-beta", StringComparison.OrdinalIgnoreCase) && !i.name.EndsWith("-dev", StringComparison.OrdinalIgnoreCase)).ToArray();
}
else if (updateLevel == PackageVersionClass.Beta)
{
@@ -70,7 +72,7 @@ namespace MediaBrowser.Common.Implementations.Updates
.Where(i => i != null)
.OrderByDescending(i => Version.Parse(i.AvailableVersion))
.FirstOrDefault();
-
+
return availableUpdate ?? new CheckForUpdateResult
{
IsUpdateAvailable = false
@@ -111,7 +113,8 @@ namespace MediaBrowser.Common.Implementations.Updates
targetFilename = targetFilename,
versionStr = version.ToString(),
requiredVersionStr = "1.0.0",
- description = obj.body
+ description = obj.body,
+ infoUrl = obj.html_url
}
};
}
diff --git a/MediaBrowser.Common.Implementations/Updates/InstallationManager.cs b/MediaBrowser.Common.Implementations/Updates/InstallationManager.cs
index 5c82ccb0e..8c7646209 100644
--- a/MediaBrowser.Common.Implementations/Updates/InstallationManager.cs
+++ b/MediaBrowser.Common.Implementations/Updates/InstallationManager.cs
@@ -193,6 +193,7 @@ namespace MediaBrowser.Common.Implementations.Updates
/// <returns>Task{List{PackageInfo}}.</returns>
public async Task<IEnumerable<PackageInfo>> GetAvailablePackagesWithoutRegistrationInfo(CancellationToken cancellationToken)
{
+ _logger.Info("Opening {0}", PackageCachePath);
try
{
using (var stream = _fileSystem.OpenRead(PackageCachePath))
diff --git a/MediaBrowser.Common.Implementations/packages.config b/MediaBrowser.Common.Implementations/packages.config
index 64b337221..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.2.3" 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.2" targetFramework="net45" />
-</packages>
+ <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.Common/ScheduledTasks/DailyTrigger.cs b/MediaBrowser.Common/ScheduledTasks/DailyTrigger.cs
index 382a41255..3d33e958d 100644
--- a/MediaBrowser.Common/ScheduledTasks/DailyTrigger.cs
+++ b/MediaBrowser.Common/ScheduledTasks/DailyTrigger.cs
@@ -1,7 +1,9 @@
using MediaBrowser.Model.Events;
using MediaBrowser.Model.Tasks;
using System;
+using System.Globalization;
using System.Threading;
+using MediaBrowser.Model.Logging;
namespace MediaBrowser.Common.ScheduledTasks
{
@@ -35,7 +37,7 @@ namespace MediaBrowser.Common.ScheduledTasks
/// </summary>
/// <param name="lastResult">The last result.</param>
/// <param name="isApplicationStartup">if set to <c>true</c> [is application startup].</param>
- public void Start(TaskResult lastResult, bool isApplicationStartup)
+ public void Start(TaskResult lastResult, ILogger logger, string taskName, bool isApplicationStartup)
{
DisposeTimer();
@@ -44,7 +46,11 @@ namespace MediaBrowser.Common.ScheduledTasks
var triggerDate = now.TimeOfDay > TimeOfDay ? now.Date.AddDays(1) : now.Date;
triggerDate = triggerDate.Add(TimeOfDay);
- Timer = new Timer(state => OnTriggered(), null, triggerDate - now, TimeSpan.FromMilliseconds(-1));
+ var dueTime = triggerDate - now;
+
+ logger.Info("Daily trigger for {0} set to fire at {1}, which is {2} minutes from now.", taskName, triggerDate.ToString(), dueTime.TotalMinutes.ToString(CultureInfo.InvariantCulture));
+
+ Timer = new Timer(state => OnTriggered(), null, dueTime, TimeSpan.FromMilliseconds(-1));
}
/// <summary>
diff --git a/MediaBrowser.Common/ScheduledTasks/ITaskTrigger.cs b/MediaBrowser.Common/ScheduledTasks/ITaskTrigger.cs
index 8c87f8f38..ef1ea9d38 100644
--- a/MediaBrowser.Common/ScheduledTasks/ITaskTrigger.cs
+++ b/MediaBrowser.Common/ScheduledTasks/ITaskTrigger.cs
@@ -1,6 +1,7 @@
using MediaBrowser.Model.Events;
using MediaBrowser.Model.Tasks;
using System;
+using MediaBrowser.Model.Logging;
namespace MediaBrowser.Common.ScheduledTasks
{
@@ -19,7 +20,7 @@ namespace MediaBrowser.Common.ScheduledTasks
/// </summary>
/// <param name="lastResult">The last result.</param>
/// <param name="isApplicationStartup">if set to <c>true</c> [is application startup].</param>
- void Start(TaskResult lastResult, bool isApplicationStartup);
+ void Start(TaskResult lastResult, ILogger logger, string taskName, bool isApplicationStartup);
/// <summary>
/// Stops waiting for the trigger action
diff --git a/MediaBrowser.Common/ScheduledTasks/IntervalTrigger.cs b/MediaBrowser.Common/ScheduledTasks/IntervalTrigger.cs
index e07dfcceb..8038d5551 100644
--- a/MediaBrowser.Common/ScheduledTasks/IntervalTrigger.cs
+++ b/MediaBrowser.Common/ScheduledTasks/IntervalTrigger.cs
@@ -3,6 +3,7 @@ using MediaBrowser.Model.Tasks;
using System;
using System.Linq;
using System.Threading;
+using MediaBrowser.Model.Logging;
namespace MediaBrowser.Common.ScheduledTasks
{
@@ -38,7 +39,7 @@ namespace MediaBrowser.Common.ScheduledTasks
/// </summary>
/// <param name="lastResult">The last result.</param>
/// <param name="isApplicationStartup">if set to <c>true</c> [is application startup].</param>
- public void Start(TaskResult lastResult, bool isApplicationStartup)
+ public void Start(TaskResult lastResult, ILogger logger, string taskName, bool isApplicationStartup)
{
DisposeTimer();
@@ -59,7 +60,15 @@ namespace MediaBrowser.Common.ScheduledTasks
triggerDate = DateTime.UtcNow.AddMinutes(1);
}
- Timer = new Timer(state => OnTriggered(), null, triggerDate - DateTime.UtcNow, TimeSpan.FromMilliseconds(-1));
+ var dueTime = triggerDate - DateTime.UtcNow;
+ var maxDueTime = TimeSpan.FromDays(7);
+
+ if (dueTime > maxDueTime)
+ {
+ dueTime = maxDueTime;
+ }
+
+ Timer = new Timer(state => OnTriggered(), null, dueTime, TimeSpan.FromMilliseconds(-1));
}
/// <summary>
diff --git a/MediaBrowser.Common/ScheduledTasks/StartupTrigger.cs b/MediaBrowser.Common/ScheduledTasks/StartupTrigger.cs
index 1d82dc76a..41f58a7ad 100644
--- a/MediaBrowser.Common/ScheduledTasks/StartupTrigger.cs
+++ b/MediaBrowser.Common/ScheduledTasks/StartupTrigger.cs
@@ -2,6 +2,7 @@
using MediaBrowser.Model.Tasks;
using System;
using System.Threading.Tasks;
+using MediaBrowser.Model.Logging;
namespace MediaBrowser.Common.ScheduledTasks
{
@@ -30,7 +31,7 @@ namespace MediaBrowser.Common.ScheduledTasks
/// </summary>
/// <param name="lastResult">The last result.</param>
/// <param name="isApplicationStartup">if set to <c>true</c> [is application startup].</param>
- public async void Start(TaskResult lastResult, bool isApplicationStartup)
+ public async void Start(TaskResult lastResult, ILogger logger, string taskName, bool isApplicationStartup)
{
if (isApplicationStartup)
{
diff --git a/MediaBrowser.Common/ScheduledTasks/SystemEventTrigger.cs b/MediaBrowser.Common/ScheduledTasks/SystemEventTrigger.cs
index eaf4afc75..9972dc804 100644
--- a/MediaBrowser.Common/ScheduledTasks/SystemEventTrigger.cs
+++ b/MediaBrowser.Common/ScheduledTasks/SystemEventTrigger.cs
@@ -3,6 +3,7 @@ using MediaBrowser.Model.Tasks;
using Microsoft.Win32;
using System;
using System.Threading.Tasks;
+using MediaBrowser.Model.Logging;
namespace MediaBrowser.Common.ScheduledTasks
{
@@ -30,7 +31,7 @@ namespace MediaBrowser.Common.ScheduledTasks
/// </summary>
/// <param name="lastResult">The last result.</param>
/// <param name="isApplicationStartup">if set to <c>true</c> [is application startup].</param>
- public void Start(TaskResult lastResult, bool isApplicationStartup)
+ public void Start(TaskResult lastResult, ILogger logger, string taskName, bool isApplicationStartup)
{
switch (SystemEvent)
{
diff --git a/MediaBrowser.Common/ScheduledTasks/WeeklyTrigger.cs b/MediaBrowser.Common/ScheduledTasks/WeeklyTrigger.cs
index 2e38264b2..318802e07 100644
--- a/MediaBrowser.Common/ScheduledTasks/WeeklyTrigger.cs
+++ b/MediaBrowser.Common/ScheduledTasks/WeeklyTrigger.cs
@@ -1,6 +1,7 @@
using System;
using System.Threading;
using MediaBrowser.Model.Events;
+using MediaBrowser.Model.Logging;
using MediaBrowser.Model.Tasks;
namespace MediaBrowser.Common.ScheduledTasks
@@ -41,7 +42,7 @@ namespace MediaBrowser.Common.ScheduledTasks
/// </summary>
/// <param name="lastResult">The last result.</param>
/// <param name="isApplicationStartup">if set to <c>true</c> [is application startup].</param>
- public void Start(TaskResult lastResult, bool isApplicationStartup)
+ public void Start(TaskResult lastResult, ILogger logger, string taskName, bool isApplicationStartup)
{
DisposeTimer();
diff --git a/MediaBrowser.Controller/Channels/Channel.cs b/MediaBrowser.Controller/Channels/Channel.cs
index 43f7b6637..718a0d878 100644
--- a/MediaBrowser.Controller/Channels/Channel.cs
+++ b/MediaBrowser.Controller/Channels/Channel.cs
@@ -38,7 +38,7 @@ namespace MediaBrowser.Controller.Channels
set { }
}
- public override async Task<QueryResult<BaseItem>> GetItems(InternalItemsQuery query)
+ protected override async Task<QueryResult<BaseItem>> GetItemsInternal(InternalItemsQuery query)
{
try
{
diff --git a/MediaBrowser.Controller/Channels/ChannelAudioItem.cs b/MediaBrowser.Controller/Channels/ChannelAudioItem.cs
deleted file mode 100644
index 41e9dd203..000000000
--- a/MediaBrowser.Controller/Channels/ChannelAudioItem.cs
+++ /dev/null
@@ -1,101 +0,0 @@
-using MediaBrowser.Controller.Entities;
-using MediaBrowser.Controller.Entities.Audio;
-using MediaBrowser.Model.Channels;
-using MediaBrowser.Model.Configuration;
-using MediaBrowser.Model.Dto;
-using MediaBrowser.Model.Entities;
-using System.Collections.Generic;
-using System.Linq;
-using System.Runtime.Serialization;
-using System.Threading;
-
-namespace MediaBrowser.Controller.Channels
-{
- public class ChannelAudioItem : Audio
- {
- public ChannelMediaContentType ContentType { get; set; }
-
- public List<ChannelMediaInfo> ChannelMediaSources { get; set; }
-
- public override UnratedItem GetBlockUnratedType()
- {
- return UnratedItem.ChannelContent;
- }
-
- protected override string CreateUserDataKey()
- {
- return ExternalId;
- }
-
- [IgnoreDataMember]
- public override bool SupportsLocalMetadata
- {
- get
- {
- return false;
- }
- }
-
- public override bool IsSaveLocalMetadataEnabled()
- {
- return false;
- }
-
- public ChannelAudioItem()
- {
- ChannelMediaSources = new List<ChannelMediaInfo>();
- }
-
- [IgnoreDataMember]
- public override LocationType LocationType
- {
- get
- {
- if (string.IsNullOrEmpty(Path))
- {
- return LocationType.Remote;
- }
-
- return base.LocationType;
- }
- }
-
- protected override string GetInternalMetadataPath(string basePath)
- {
- return System.IO.Path.Combine(basePath, "channels", ChannelId, Id.ToString("N"));
- }
-
- public override IEnumerable<MediaSourceInfo> GetMediaSources(bool enablePathSubstitution)
- {
- var sources = ChannelManager.GetStaticMediaSources(this, false, CancellationToken.None)
- .Result.ToList();
-
- if (sources.Count > 0)
- {
- return sources;
- }
-
- var list = base.GetMediaSources(enablePathSubstitution).ToList();
-
- foreach (var mediaSource in list)
- {
- if (string.IsNullOrWhiteSpace(mediaSource.Path))
- {
- mediaSource.Type = MediaSourceType.Placeholder;
- }
- }
-
- return list;
- }
-
- public override bool CanDelete()
- {
- return false;
- }
-
- public override bool IsVisibleStandalone(User user)
- {
- return IsVisibleStandaloneInternal(user, false) && ChannelVideoItem.IsChannelVisible(this, user);
- }
- }
-}
diff --git a/MediaBrowser.Controller/Channels/ChannelFolderItem.cs b/MediaBrowser.Controller/Channels/ChannelFolderItem.cs
deleted file mode 100644
index da5d60863..000000000
--- a/MediaBrowser.Controller/Channels/ChannelFolderItem.cs
+++ /dev/null
@@ -1,89 +0,0 @@
-using MediaBrowser.Controller.Entities;
-using MediaBrowser.Model.Channels;
-using MediaBrowser.Model.Querying;
-using MediaBrowser.Model.Users;
-using System;
-using System.Runtime.Serialization;
-using System.Threading;
-using System.Threading.Tasks;
-using MediaBrowser.Model.Configuration;
-
-namespace MediaBrowser.Controller.Channels
-{
- public class ChannelFolderItem : Folder
- {
- public ChannelFolderType ChannelFolderType { get; set; }
-
- protected override bool GetBlockUnratedValue(UserPolicy config)
- {
- // Don't block.
- return false;
- }
-
- public override UnratedItem GetBlockUnratedType()
- {
- return UnratedItem.ChannelContent;
- }
-
- [IgnoreDataMember]
- public override bool SupportsLocalMetadata
- {
- get
- {
- return false;
- }
- }
-
- public override bool IsSaveLocalMetadataEnabled()
- {
- return false;
- }
-
- protected override string CreateUserDataKey()
- {
- return ExternalId;
- }
-
- public override async Task<QueryResult<BaseItem>> GetItems(InternalItemsQuery query)
- {
- try
- {
- // Don't blow up here because it could cause parent screens with other content to fail
- return await ChannelManager.GetChannelItemsInternal(new ChannelItemQuery
- {
- ChannelId = ChannelId,
- FolderId = Id.ToString("N"),
- Limit = query.Limit,
- StartIndex = query.StartIndex,
- UserId = query.User.Id.ToString("N"),
- SortBy = query.SortBy,
- SortOrder = query.SortOrder
-
- }, new Progress<double>(), CancellationToken.None);
- }
- catch
- {
- // Already logged at lower levels
- return new QueryResult<BaseItem>
- {
-
- };
- }
- }
-
- protected override string GetInternalMetadataPath(string basePath)
- {
- return System.IO.Path.Combine(basePath, "channels", ChannelId, Id.ToString("N"));
- }
-
- public override bool CanDelete()
- {
- return false;
- }
-
- public override bool IsVisibleStandalone(User user)
- {
- return IsVisibleStandaloneInternal(user, false) && ChannelVideoItem.IsChannelVisible(this, user);
- }
- }
-}
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/Channels/ChannelMediaInfo.cs b/MediaBrowser.Controller/Channels/ChannelMediaInfo.cs
index 1672b75fa..9424568b4 100644
--- a/MediaBrowser.Controller/Channels/ChannelMediaInfo.cs
+++ b/MediaBrowser.Controller/Channels/ChannelMediaInfo.cs
@@ -65,7 +65,7 @@ namespace MediaBrowser.Controller.Channels
Name = id,
Id = id,
ReadAtNativeFramerate = ReadAtNativeFramerate,
- SupportsDirectStream = Protocol == MediaProtocol.File || Protocol == MediaProtocol.Http,
+ SupportsDirectStream = Protocol == MediaProtocol.File,
SupportsDirectPlay = SupportsDirectPlay
};
diff --git a/MediaBrowser.Controller/Channels/ChannelVideoItem.cs b/MediaBrowser.Controller/Channels/ChannelVideoItem.cs
deleted file mode 100644
index 9fe04812e..000000000
--- a/MediaBrowser.Controller/Channels/ChannelVideoItem.cs
+++ /dev/null
@@ -1,126 +0,0 @@
-using MediaBrowser.Controller.Entities;
-using MediaBrowser.Model.Channels;
-using MediaBrowser.Model.Configuration;
-using MediaBrowser.Model.Dto;
-using MediaBrowser.Model.Entities;
-using System.Collections.Generic;
-using System.Globalization;
-using System.Linq;
-using System.Runtime.Serialization;
-using System.Threading;
-
-namespace MediaBrowser.Controller.Channels
-{
- public class ChannelVideoItem : Video
- {
- public ChannelMediaContentType ContentType { get; set; }
-
- public List<ChannelMediaInfo> ChannelMediaSources { get; set; }
-
- protected override string CreateUserDataKey()
- {
- if (ContentType == ChannelMediaContentType.MovieExtra)
- {
- var key = this.GetProviderId(MetadataProviders.Imdb) ?? this.GetProviderId(MetadataProviders.Tmdb);
-
- if (!string.IsNullOrWhiteSpace(key))
- {
- key = key + "-" + ExtraType.ToString().ToLower();
-
- // Make sure different trailers have their own data.
- if (RunTimeTicks.HasValue)
- {
- key += "-" + RunTimeTicks.Value.ToString(CultureInfo.InvariantCulture);
- }
-
- return key;
- }
- }
-
- return ExternalId;
- }
-
- public override UnratedItem GetBlockUnratedType()
- {
- return UnratedItem.ChannelContent;
- }
-
- [IgnoreDataMember]
- public override bool SupportsLocalMetadata
- {
- get
- {
- return false;
- }
- }
-
- public override bool IsSaveLocalMetadataEnabled()
- {
- return false;
- }
-
- public ChannelVideoItem()
- {
- ChannelMediaSources = new List<ChannelMediaInfo>();
- }
-
- [IgnoreDataMember]
- public override LocationType LocationType
- {
- get
- {
- if (string.IsNullOrEmpty(Path))
- {
- return LocationType.Remote;
- }
-
- return base.LocationType;
- }
- }
-
- public override IEnumerable<MediaSourceInfo> GetMediaSources(bool enablePathSubstitution)
- {
- var sources = ChannelManager.GetStaticMediaSources(this, false, CancellationToken.None)
- .Result.ToList();
-
- if (sources.Count > 0)
- {
- return sources;
- }
-
- var list = base.GetMediaSources(enablePathSubstitution).ToList();
-
- foreach (var mediaSource in list)
- {
- if (string.IsNullOrWhiteSpace(mediaSource.Path))
- {
- mediaSource.Type = MediaSourceType.Placeholder;
- }
- }
-
- return list;
- }
-
- protected override string GetInternalMetadataPath(string basePath)
- {
- return System.IO.Path.Combine(basePath, "channels", ChannelId, Id.ToString("N"));
- }
-
- public override bool CanDelete()
- {
- return false;
- }
-
- public override bool IsVisibleStandalone(User user)
- {
- return IsVisibleStandaloneInternal(user, false) && IsChannelVisible(this, user);
- }
-
- internal static bool IsChannelVisible(BaseItem item, User user)
- {
- var channel = ChannelManager.GetChannel(item.ChannelId);
-
- return channel.IsVisible(user);
- }
- }
-}
diff --git a/MediaBrowser.Controller/Channels/IChannelItem.cs b/MediaBrowser.Controller/Channels/IChannelItem.cs
deleted file mode 100644
index 9b5f0359b..000000000
--- a/MediaBrowser.Controller/Channels/IChannelItem.cs
+++ /dev/null
@@ -1,11 +0,0 @@
-using MediaBrowser.Controller.Entities;
-
-namespace MediaBrowser.Controller.Channels
-{
- public interface IChannelItem : IHasImages, IHasTags
- {
- string ChannelId { get; set; }
-
- string ExternalId { get; set; }
- }
-}
diff --git a/MediaBrowser.Controller/Channels/IChannelMediaItem.cs b/MediaBrowser.Controller/Channels/IChannelMediaItem.cs
deleted file mode 100644
index 60a29da90..000000000
--- a/MediaBrowser.Controller/Channels/IChannelMediaItem.cs
+++ /dev/null
@@ -1,18 +0,0 @@
-using MediaBrowser.Model.Channels;
-using MediaBrowser.Model.Entities;
-using System.Collections.Generic;
-
-namespace MediaBrowser.Controller.Channels
-{
- public interface IChannelMediaItem : IChannelItem
- {
- long? RunTimeTicks { get; set; }
- string MediaType { get; }
-
- ChannelMediaContentType ContentType { get; set; }
-
- ExtraType? ExtraType { get; set; }
-
- List<ChannelMediaInfo> ChannelMediaSources { get; set; }
- }
-} \ No newline at end of file
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/IImageProcessor.cs b/MediaBrowser.Controller/Drawing/IImageProcessor.cs
index d42a04f2e..19f391b4a 100644
--- a/MediaBrowser.Controller/Drawing/IImageProcessor.cs
+++ b/MediaBrowser.Controller/Drawing/IImageProcessor.cs
@@ -84,7 +84,7 @@ namespace MediaBrowser.Controller.Drawing
/// </summary>
/// <param name="options">The options.</param>
/// <returns>Task.</returns>
- Task<Tuple<string, string>> ProcessImage(ImageProcessingOptions options);
+ Task<Tuple<string, string, DateTime>> ProcessImage(ImageProcessingOptions options);
/// <summary>
/// Gets the enhanced image.
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/AggregateFolder.cs b/MediaBrowser.Controller/Entities/AggregateFolder.cs
index 018ff6da0..588a65e98 100644
--- a/MediaBrowser.Controller/Entities/AggregateFolder.cs
+++ b/MediaBrowser.Controller/Entities/AggregateFolder.cs
@@ -64,10 +64,37 @@ namespace MediaBrowser.Controller.Entities
protected override IEnumerable<FileSystemMetadata> GetFileSystemChildren(IDirectoryService directoryService)
{
- return CreateResolveArgs(directoryService).FileSystemChildren;
+ return CreateResolveArgs(directoryService, true).FileSystemChildren;
}
- private ItemResolveArgs CreateResolveArgs(IDirectoryService directoryService)
+ private bool _requiresRefresh;
+ public override bool RequiresRefresh()
+ {
+ var changed = base.RequiresRefresh() || _requiresRefresh;
+
+ if (!changed)
+ {
+ var locations = PhysicalLocations.ToList();
+
+ var newLocations = CreateResolveArgs(new DirectoryService(BaseItem.FileSystem), false).PhysicalLocations.ToList();
+
+ if (!locations.SequenceEqual(newLocations))
+ {
+ changed = true;
+ }
+ }
+
+ return changed;
+ }
+
+ public override bool BeforeMetadataRefresh()
+ {
+ var changed = base.BeforeMetadataRefresh() || _requiresRefresh;
+ _requiresRefresh = false;
+ return changed;
+ }
+
+ private ItemResolveArgs CreateResolveArgs(IDirectoryService directoryService, bool setPhysicalLocations)
{
var path = ContainingFolderPath;
@@ -100,7 +127,11 @@ namespace MediaBrowser.Controller.Entities
args.FileSystemDictionary = fileSystemDictionary;
}
- PhysicalLocationsList = args.PhysicalLocations.ToList();
+ _requiresRefresh = _requiresRefresh || !args.PhysicalLocations.SequenceEqual(PhysicalLocations);
+ if (setPhysicalLocations)
+ {
+ PhysicalLocationsList = args.PhysicalLocations.ToList();
+ }
return args;
}
diff --git a/MediaBrowser.Controller/Entities/Audio/Audio.cs b/MediaBrowser.Controller/Entities/Audio/Audio.cs
index 929308ba0..1897511af 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; }
@@ -40,12 +37,6 @@ namespace MediaBrowser.Controller.Entities.Audio
public List<string> AlbumArtists { get; set; }
- /// <summary>
- /// Gets or sets the album.
- /// </summary>
- /// <value>The album.</value>
- public string Album { get; set; }
-
[IgnoreDataMember]
public bool IsThemeMedia
{
@@ -55,6 +46,12 @@ namespace MediaBrowser.Controller.Entities.Audio
}
}
+ [IgnoreDataMember]
+ public override bool EnableForceSaveOnDateModifiedChange
+ {
+ get { return true; }
+ }
+
public Audio()
{
Artists = new List<string>();
@@ -150,12 +147,10 @@ namespace MediaBrowser.Controller.Entities.Audio
+ (IndexNumber != null ? IndexNumber.Value.ToString("0000 - ") : "") + Name;
}
- /// <summary>
- /// Gets the user data key.
- /// </summary>
- /// <returns>System.String.</returns>
- protected override string CreateUserDataKey()
+ public override List<string> GetUserDataKeys()
{
+ var list = base.GetUserDataKeys();
+
if (ConfigurationManager.Configuration.EnableStandaloneMusicKeys)
{
var songKey = IndexNumber.HasValue ? IndexNumber.Value.ToString("0000") : string.Empty;
@@ -165,7 +160,7 @@ namespace MediaBrowser.Controller.Entities.Audio
{
songKey = ParentIndexNumber.Value.ToString("0000") + "-" + songKey;
}
- songKey+= Name;
+ songKey += Name;
if (!string.IsNullOrWhiteSpace(Album))
{
@@ -178,25 +173,25 @@ namespace MediaBrowser.Controller.Entities.Audio
songKey = albumArtist + "-" + songKey;
}
- return songKey;
+ list.Insert(0, songKey);
}
-
- var parent = AlbumEntity;
-
- if (parent != null)
+ else
{
- var parentKey = parent.GetUserDataKey();
+ var parent = AlbumEntity;
- if (IndexNumber.HasValue)
+ if (parent != null && IndexNumber.HasValue)
{
- var songKey = (ParentIndexNumber != null ? ParentIndexNumber.Value.ToString("0000 - ") : "")
- + IndexNumber.Value.ToString("0000 - ");
+ list.InsertRange(0, parent.GetUserDataKeys().Select(i =>
+ {
+ var songKey = (ParentIndexNumber != null ? ParentIndexNumber.Value.ToString("0000 - ") : "")
+ + IndexNumber.Value.ToString("0000 - ");
- return parentKey + songKey;
+ return i + songKey;
+ }));
}
}
- return base.CreateUserDataKey();
+ return list;
}
public override UnratedItem GetBlockUnratedType()
diff --git a/MediaBrowser.Controller/Entities/Audio/MusicAlbum.cs b/MediaBrowser.Controller/Entities/Audio/MusicAlbum.cs
index e6178c183..1f3b0c92a 100644
--- a/MediaBrowser.Controller/Entities/Audio/MusicAlbum.cs
+++ b/MediaBrowser.Controller/Entities/Audio/MusicAlbum.cs
@@ -49,6 +49,15 @@ namespace MediaBrowser.Controller.Entities.Audio
}
[IgnoreDataMember]
+ public override bool SupportsCumulativeRunTimeTicks
+ {
+ get
+ {
+ return true;
+ }
+ }
+
+ [IgnoreDataMember]
public List<string> AllArtists
{
get
@@ -96,36 +105,34 @@ namespace MediaBrowser.Controller.Entities.Audio
public List<string> Artists { get; set; }
- /// <summary>
- /// Gets the user data key.
- /// </summary>
- /// <returns>System.String.</returns>
- protected override string CreateUserDataKey()
+ public override List<string> GetUserDataKeys()
{
- var id = this.GetProviderId(MetadataProviders.MusicBrainzReleaseGroup);
+ var list = base.GetUserDataKeys();
- if (!string.IsNullOrWhiteSpace(id))
+ if (ConfigurationManager.Configuration.EnableStandaloneMusicKeys)
{
- return "MusicAlbum-MusicBrainzReleaseGroup-" + id;
+ var albumArtist = AlbumArtist;
+ if (!string.IsNullOrWhiteSpace(albumArtist))
+ {
+ list.Insert(0, albumArtist + "-" + Name);
+ }
}
- id = this.GetProviderId(MetadataProviders.MusicBrainzAlbum);
+ var id = this.GetProviderId(MetadataProviders.MusicBrainzAlbum);
if (!string.IsNullOrWhiteSpace(id))
{
- return "MusicAlbum-Musicbrainz-" + id;
+ list.Insert(0, "MusicAlbum-Musicbrainz-" + id);
}
- if (ConfigurationManager.Configuration.EnableStandaloneMusicKeys)
+ id = this.GetProviderId(MetadataProviders.MusicBrainzReleaseGroup);
+
+ if (!string.IsNullOrWhiteSpace(id))
{
- var albumArtist = AlbumArtist;
- if (!string.IsNullOrWhiteSpace(albumArtist))
- {
- return albumArtist + "-" + Name;
- }
+ list.Insert(0, "MusicAlbum-MusicBrainzReleaseGroup-" + id);
}
- return base.CreateUserDataKey();
+ return list;
}
protected override bool GetBlockUnratedValue(UserPolicy config)
@@ -172,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();
@@ -192,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;
@@ -205,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 02bcceada..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
{
@@ -17,7 +18,12 @@ namespace MediaBrowser.Controller.Entities.Audio
/// </summary>
public class MusicArtist : Folder, IMetadataContainer, IItemByName, IHasMusicGenres, IHasDualAccess, IHasProductionLocations, IHasLookupInfo<ArtistInfo>
{
- public bool IsAccessedByName { get; set; }
+ [IgnoreDataMember]
+ public bool IsAccessedByName
+ {
+ get { return ParentId == Guid.Empty; }
+ }
+
public List<string> ProductionLocations { get; set; }
[IgnoreDataMember]
@@ -30,6 +36,15 @@ namespace MediaBrowser.Controller.Entities.Audio
}
[IgnoreDataMember]
+ public override bool SupportsCumulativeRunTimeTicks
+ {
+ get
+ {
+ return true;
+ }
+ }
+
+ [IgnoreDataMember]
public override bool SupportsAddingToPlaylist
{
get { return true; }
@@ -40,6 +55,18 @@ namespace MediaBrowser.Controller.Entities.Audio
return !IsAccessedByName;
}
+ public IEnumerable<BaseItem> GetTaggedItems(InternalItemsQuery query)
+ {
+ if (query.IncludeItemTypes.Length == 0)
+ {
+ query.IncludeItemTypes = new[] { typeof(Audio).Name, typeof(MusicVideo).Name, typeof(MusicAlbum).Name };
+ query.ArtistNames = new[] { Name };
+ }
+
+ return LibraryManager.GetItemList(query);
+ }
+
+ [IgnoreDataMember]
protected override IEnumerable<BaseItem> ActualChildren
{
get
@@ -53,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)
@@ -80,13 +116,12 @@ namespace MediaBrowser.Controller.Entities.Audio
ProductionLocations = new List<string>();
}
- /// <summary>
- /// Gets the user data key.
- /// </summary>
- /// <returns>System.String.</returns>
- protected override string CreateUserDataKey()
+ public override List<string> GetUserDataKeys()
{
- return GetUserDataKey(this);
+ var list = base.GetUserDataKeys();
+
+ list.InsertRange(0, GetUserDataKeys(this));
+ return list;
}
/// <summary>
@@ -121,18 +156,27 @@ namespace MediaBrowser.Controller.Entities.Audio
/// </summary>
/// <param name="item">The item.</param>
/// <returns>System.String.</returns>
- private static string GetUserDataKey(MusicArtist item)
+ private static List<string> GetUserDataKeys(MusicArtist item)
{
+ var list = new List<string>();
var id = item.GetProviderId(MetadataProviders.MusicBrainzArtist);
if (!string.IsNullOrEmpty(id))
{
- return "Artist-Musicbrainz-" + id;
+ list.Add("Artist-Musicbrainz-" + id);
}
- return "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 45304d47e..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
{
@@ -10,13 +11,20 @@ namespace MediaBrowser.Controller.Entities.Audio
/// </summary>
public class MusicGenre : BaseItem, IItemByName
{
- /// <summary>
- /// Gets the user data key.
- /// </summary>
- /// <returns>System.String.</returns>
- protected override string CreateUserDataKey()
+ public override List<string> GetUserDataKeys()
{
- return "MusicGenre-" + Name;
+ var list = base.GetUserDataKeys();
+
+ list.Insert(0, GetType().Name + "-" + (Name ?? string.Empty).RemoveDiacritics());
+ return list;
+ }
+
+ public override string PresentationUniqueKey
+ {
+ get
+ {
+ return GetUserDataKeys()[0];
+ }
}
[IgnoreDataMember]
@@ -80,5 +88,13 @@ namespace MediaBrowser.Controller.Entities.Audio
return false;
}
}
+
+ public IEnumerable<BaseItem> GetTaggedItems(InternalItemsQuery query)
+ {
+ query.Genres = new[] { Name };
+ query.IncludeItemTypes = new[] { typeof(MusicVideo).Name, typeof(Audio).Name, typeof(MusicAlbum).Name, typeof(MusicArtist).Name };
+
+ return LibraryManager.GetItemList(query);
+ }
}
}
diff --git a/MediaBrowser.Controller/Entities/BaseItem.cs b/MediaBrowser.Controller/Entities/BaseItem.cs
index 903c5cfd5..c7a6b75ff 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>();
@@ -44,6 +46,9 @@ namespace MediaBrowser.Controller.Entities
ImageInfos = new List<ItemImageInfo>();
}
+ public static readonly char[] SlugReplaceChars = { '?', '/', '&' };
+ public static char SlugChar = '-';
+
/// <summary>
/// The supported image extensions
/// </summary>
@@ -64,8 +69,22 @@ 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>
/// Gets or sets the channel identifier.
/// </summary>
@@ -125,6 +144,29 @@ namespace MediaBrowser.Controller.Entities
}
}
+ [IgnoreDataMember]
+ public string SlugName
+ {
+ get
+ {
+ var name = Name;
+ if (string.IsNullOrWhiteSpace(name))
+ {
+ return string.Empty;
+ }
+
+ return SlugReplaceChars.Aggregate(name, (current, c) => current.Replace(c, SlugChar));
+ }
+ }
+
+ [IgnoreDataMember]
+ public bool IsUnaired
+ {
+ get { return PremiereDate.HasValue && PremiereDate.Value.ToLocalTime().Date >= DateTime.Now.Date; }
+ }
+
+ public string OriginalTitle { get; set; }
+
/// <summary>
/// Gets or sets the id.
/// </summary>
@@ -255,6 +297,11 @@ namespace MediaBrowser.Controller.Entities
if (string.IsNullOrWhiteSpace(Path))
{
+ if (SourceType == SourceType.Channel)
+ {
+ return LocationType.Remote;
+ }
+
return LocationType.Virtual;
}
@@ -301,7 +348,7 @@ namespace MediaBrowser.Controller.Entities
}
}
- private List<Tuple<StringBuilder,bool>> GetSortChunks(string s1)
+ private List<Tuple<StringBuilder, bool>> GetSortChunks(string s1)
{
var list = new List<Tuple<StringBuilder, bool>>();
@@ -407,6 +454,12 @@ namespace MediaBrowser.Controller.Entities
[IgnoreDataMember]
public DateTime DateLastRefreshed { get; set; }
+ [IgnoreDataMember]
+ public virtual bool EnableForceSaveOnDateModifiedChange
+ {
+ get { return false; }
+ }
+
/// <summary>
/// The logger
/// </summary>
@@ -494,7 +547,19 @@ namespace MediaBrowser.Controller.Entities
{
get
{
- return _sortName ?? (_sortName = CreateSortName());
+ if (_sortName == null)
+ {
+ if (!string.IsNullOrWhiteSpace(ForcedSortName))
+ {
+ // Need the ToLower because that's what CreateSortName does
+ _sortName = ModifySortChunks(ForcedSortName).ToLower();
+ }
+ else
+ {
+ _sortName = CreateSortName();
+ }
+ }
+ return _sortName;
}
set
{
@@ -529,11 +594,6 @@ namespace MediaBrowser.Controller.Entities
/// <returns>System.String.</returns>
protected virtual string CreateSortName()
{
- if (!string.IsNullOrWhiteSpace(ForcedSortName))
- {
- return ModifySortChunks(ForcedSortName).ToLower();
- }
-
if (Name == null) return null; //some items may not have name filled in properly
if (!EnableAlphaNumericSorting)
@@ -588,7 +648,7 @@ namespace MediaBrowser.Controller.Entities
builder.Append(chunkBuilder);
}
//Logger.Debug("ModifySortChunks Start: {0} End: {1}", name, builder.ToString());
- return builder.ToString();
+ return builder.ToString().RemoveDiacritics();
}
[IgnoreDataMember]
@@ -653,9 +713,30 @@ namespace MediaBrowser.Controller.Entities
}
[IgnoreDataMember]
- public virtual BaseItem DisplayParent
+ public virtual Guid? DisplayParentId
+ {
+ get
+ {
+ if (ParentId == Guid.Empty)
+ {
+ return null;
+ }
+ return ParentId;
+ }
+ }
+
+ [IgnoreDataMember]
+ public BaseItem DisplayParent
{
- get { return GetParent(); }
+ get
+ {
+ var id = DisplayParentId;
+ if (!id.HasValue || id.Value == Guid.Empty)
+ {
+ return null;
+ }
+ return LibraryManager.GetItemById(id.Value);
+ }
}
/// <summary>
@@ -690,12 +771,14 @@ namespace MediaBrowser.Controller.Entities
/// Gets or sets the critic rating.
/// </summary>
/// <value>The critic rating.</value>
+ [IgnoreDataMember]
public float? CriticRating { get; set; }
/// <summary>
/// Gets or sets the critic rating summary.
/// </summary>
/// <value>The critic rating summary.</value>
+ [IgnoreDataMember]
public string CriticRatingSummary { get; set; }
/// <summary>
@@ -740,6 +823,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>
@@ -961,9 +1046,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]
@@ -1111,33 +1194,31 @@ namespace MediaBrowser.Controller.Entities
get { return null; }
}
- private string _userDataKey;
- /// <summary>
- /// Gets the user data key.
- /// </summary>
- /// <returns>System.String.</returns>
- public string GetUserDataKey()
+ [IgnoreDataMember]
+ public virtual string PresentationUniqueKey
{
- if (string.IsNullOrWhiteSpace(_userDataKey))
- {
- var key = CreateUserDataKey();
- _userDataKey = key;
- return key;
- }
+ get { return Id.ToString("N"); }
+ }
- return _userDataKey;
+ public virtual bool RequiresRefresh()
+ {
+ return false;
}
- protected virtual string CreateUserDataKey()
+ public virtual List<string> GetUserDataKeys()
{
+ var list = new List<string>();
+
if (SourceType == SourceType.Channel)
{
if (!string.IsNullOrWhiteSpace(ExternalId))
{
- return ExternalId;
+ list.Add(ExternalId);
}
}
- return Id.ToString();
+
+ list.Add(Id.ToString());
+ return list;
}
internal virtual bool IsValidFromResolver(BaseItem newItem)
@@ -1150,7 +1231,6 @@ namespace MediaBrowser.Controller.Entities
public void AfterMetadataRefresh()
{
_sortName = null;
- _userDataKey = null;
}
/// <summary>
@@ -1312,17 +1392,25 @@ namespace MediaBrowser.Controller.Entities
return LocalizationManager.GetRatingLevel(rating);
}
- private bool IsVisibleViaTags(User user)
+ public List<string> GetInheritedTags()
{
- var hasTags = this as IHasTags;
+ var list = new List<string>();
+ list.AddRange(Tags);
- if (hasTags != null)
+ foreach (var parent in GetParents())
{
- var policy = user.Policy;
- if (policy.BlockedTags.Any(i => hasTags.Tags.Contains(i, StringComparer.OrdinalIgnoreCase)))
- {
- return false;
- }
+ list.AddRange(parent.Tags);
+ }
+
+ return list.Distinct(StringComparer.OrdinalIgnoreCase).ToList();
+ }
+
+ private bool IsVisibleViaTags(User user)
+ {
+ var policy = user.Policy;
+ if (policy.BlockedTags.Any(i => Tags.Contains(i, StringComparer.OrdinalIgnoreCase)))
+ {
+ return false;
}
return true;
@@ -1439,7 +1527,7 @@ namespace MediaBrowser.Controller.Entities
public virtual string GetClientTypeName()
{
- if (IsFolder && SourceType == SourceType.Channel)
+ if (IsFolder && SourceType == SourceType.Channel && !(this is Channel))
{
return "ChannelFolderItem";
}
@@ -1491,11 +1579,11 @@ namespace MediaBrowser.Controller.Entities
{
if (!string.IsNullOrEmpty(info.Path))
{
- var itemByPath = LibraryManager.FindByPath(info.Path);
+ var itemByPath = LibraryManager.FindByPath(info.Path, null);
if (itemByPath == null)
{
- Logger.Warn("Unable to find linked item at path {0}", info.Path);
+ //Logger.Warn("Unable to find linked item at path {0}", info.Path);
}
return itemByPath;
@@ -1504,6 +1592,15 @@ namespace MediaBrowser.Controller.Entities
return null;
}
+ [IgnoreDataMember]
+ public virtual bool EnableRememberingTrackSelections
+ {
+ get
+ {
+ return true;
+ }
+ }
+
/// <summary>
/// Adds a studio to the item
/// </summary>
@@ -1557,13 +1654,11 @@ namespace MediaBrowser.Controller.Entities
throw new ArgumentNullException();
}
- var key = GetUserDataKey();
-
- var data = UserDataManager.GetUserData(user.Id, key);
+ var data = UserDataManager.GetUserData(user, this);
if (datePlayed.HasValue)
{
- // Incremenet
+ // Increment
data.PlayCount++;
}
@@ -1575,7 +1670,7 @@ namespace MediaBrowser.Controller.Entities
data.PlaybackPositionTicks = 0;
}
- data.LastPlayedDate = datePlayed ?? data.LastPlayedDate;
+ data.LastPlayedDate = datePlayed ?? data.LastPlayedDate ?? DateTime.UtcNow;
data.Played = true;
await UserDataManager.SaveUserData(user.Id, this, data, UserDataSaveReason.TogglePlayed, CancellationToken.None).ConfigureAwait(false);
@@ -1594,9 +1689,7 @@ namespace MediaBrowser.Controller.Entities
throw new ArgumentNullException();
}
- var key = GetUserDataKey();
-
- var data = UserDataManager.GetUserData(user.Id, key);
+ var data = UserDataManager.GetUserData(user, this);
//I think it is okay to do this here.
// if this is only called when a user is manually forcing something to un-played
@@ -1788,7 +1881,7 @@ namespace MediaBrowser.Controller.Entities
return new ItemImageInfo
{
Path = path,
- DateModified = FileSystem.GetLastWriteTimeUtc(path),
+ DateModified = chapter.ImageDateModified,
Type = imageType
};
}
@@ -1927,14 +2020,14 @@ namespace MediaBrowser.Controller.Entities
public virtual bool IsPlayed(User user)
{
- var userdata = UserDataManager.GetUserData(user.Id, GetUserDataKey());
+ var userdata = UserDataManager.GetUserData(user, this);
return userdata != null && userdata.Played;
}
public bool IsFavoriteOrLiked(User user)
{
- var userdata = UserDataManager.GetUserData(user.Id, GetUserDataKey());
+ var userdata = UserDataManager.GetUserData(user, this);
return userdata != null && (userdata.IsFavorite || (userdata.Likes ?? false));
}
@@ -1946,7 +2039,7 @@ namespace MediaBrowser.Controller.Entities
throw new ArgumentNullException("user");
}
- var userdata = UserDataManager.GetUserData(user.Id, GetUserDataKey());
+ var userdata = UserDataManager.GetUserData(user, this);
return userdata == null || !userdata.Played;
}
@@ -1977,7 +2070,6 @@ namespace MediaBrowser.Controller.Entities
/// </summary>
public virtual bool BeforeMetadataRefresh()
{
- _userDataKey = null;
_sortName = null;
var hasChanges = false;
@@ -2004,7 +2096,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)
{
@@ -2020,6 +2112,8 @@ namespace MediaBrowser.Controller.Entities
}
}
}
+
+ return Task.FromResult(true);
}
protected Task RefreshMetadataForOwnedVideo(MetadataRefreshOptions options, string path, CancellationToken cancellationToken)
@@ -2084,7 +2178,7 @@ namespace MediaBrowser.Controller.Entities
{
get
{
- if (GetParent() is AggregateFolder || this is Channel || this is BasePluginFolder)
+ if (GetParent() is AggregateFolder || this is BasePluginFolder || this is Channel)
{
return true;
}
@@ -2094,6 +2188,10 @@ namespace MediaBrowser.Controller.Entities
{
return true;
}
+ if (view != null && string.Equals(view.ViewType, CollectionType.Channels, StringComparison.OrdinalIgnoreCase))
+ {
+ return true;
+ }
return false;
}
@@ -2126,5 +2224,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..59ab95437 100644
--- a/MediaBrowser.Controller/Entities/Book.cs
+++ b/MediaBrowser.Controller/Entities/Book.cs
@@ -1,4 +1,5 @@
-using MediaBrowser.Controller.Providers;
+using System;
+using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Configuration;
using System.Linq;
using System.Runtime.Serialization;
@@ -6,7 +7,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
@@ -17,7 +18,32 @@ namespace MediaBrowser.Controller.Entities
}
}
+ [IgnoreDataMember]
public string SeriesName { get; set; }
+ [IgnoreDataMember]
+ public Guid? SeriesId { get; set; }
+ [IgnoreDataMember]
+ public string SeriesSortName { get; set; }
+
+ public string FindSeriesSortName()
+ {
+ return SeriesSortName;
+ }
+ public string FindSeriesName()
+ {
+ return SeriesName;
+ }
+
+ [IgnoreDataMember]
+ public override bool EnableForceSaveOnDateModifiedChange
+ {
+ get { return true; }
+ }
+
+ public Guid? FindSeriesId()
+ {
+ return SeriesId;
+ }
public override bool CanDownload()
{
diff --git a/MediaBrowser.Controller/Entities/CollectionFolder.cs b/MediaBrowser.Controller/Entities/CollectionFolder.cs
index 429700327..35dfd52e9 100644
--- a/MediaBrowser.Controller/Entities/CollectionFolder.cs
+++ b/MediaBrowser.Controller/Entities/CollectionFolder.cs
@@ -8,6 +8,7 @@ using System.Runtime.Serialization;
using System.Threading;
using System.Threading.Tasks;
using CommonIO;
+using MoreLinq;
namespace MediaBrowser.Controller.Entities
{
@@ -22,19 +23,6 @@ namespace MediaBrowser.Controller.Entities
PhysicalLocationsList = new List<string>();
}
- /// <summary>
- /// Gets a value indicating whether this instance is virtual folder.
- /// </summary>
- /// <value><c>true</c> if this instance is virtual folder; otherwise, <c>false</c>.</value>
- [IgnoreDataMember]
- public override bool IsVirtualFolder
- {
- get
- {
- return true;
- }
- }
-
[IgnoreDataMember]
protected override bool SupportsShortcutChildren
{
@@ -82,7 +70,34 @@ namespace MediaBrowser.Controller.Entities
protected override IEnumerable<FileSystemMetadata> GetFileSystemChildren(IDirectoryService directoryService)
{
- return CreateResolveArgs(directoryService).FileSystemChildren;
+ return CreateResolveArgs(directoryService, true).FileSystemChildren;
+ }
+
+ private bool _requiresRefresh;
+ public override bool RequiresRefresh()
+ {
+ var changed = base.RequiresRefresh() || _requiresRefresh;
+
+ if (!changed)
+ {
+ var locations = PhysicalLocations.ToList();
+
+ var newLocations = CreateResolveArgs(new DirectoryService(BaseItem.FileSystem), false).PhysicalLocations.ToList();
+
+ if (!locations.SequenceEqual(newLocations))
+ {
+ changed = true;
+ }
+ }
+
+ return changed;
+ }
+
+ public override bool BeforeMetadataRefresh()
+ {
+ var changed = base.BeforeMetadataRefresh() || _requiresRefresh;
+ _requiresRefresh = false;
+ return changed;
}
internal override bool IsValidFromResolver(BaseItem newItem)
@@ -97,11 +112,10 @@ namespace MediaBrowser.Controller.Entities
}
}
-
return base.IsValidFromResolver(newItem);
}
- private ItemResolveArgs CreateResolveArgs(IDirectoryService directoryService)
+ private ItemResolveArgs CreateResolveArgs(IDirectoryService directoryService, bool setPhysicalLocations)
{
var path = ContainingFolderPath;
@@ -135,7 +149,11 @@ namespace MediaBrowser.Controller.Entities
args.FileSystemDictionary = fileSystemDictionary;
}
- PhysicalLocationsList = args.PhysicalLocations.ToList();
+ _requiresRefresh = _requiresRefresh || !args.PhysicalLocations.SequenceEqual(PhysicalLocations);
+ if (setPhysicalLocations)
+ {
+ PhysicalLocationsList = args.PhysicalLocations.ToList();
+ }
return args;
}
@@ -153,15 +171,6 @@ namespace MediaBrowser.Controller.Entities
/// <returns>Task.</returns>
protected override Task ValidateChildrenInternal(IProgress<double> progress, CancellationToken cancellationToken, bool recursive, bool refreshChildMetadata, MetadataRefreshOptions refreshOptions, IDirectoryService directoryService)
{
- var list = PhysicalLocationsList.ToList();
-
- CreateResolveArgs(directoryService);
-
- if (!list.SequenceEqual(PhysicalLocationsList))
- {
- return UpdateToRepository(ItemUpdateType.MetadataImport, cancellationToken);
- }
-
return Task.FromResult(true);
}
@@ -188,6 +197,7 @@ namespace MediaBrowser.Controller.Entities
/// Our children are actually just references to the ones in the physical root...
/// </summary>
/// <value>The actual children.</value>
+ [IgnoreDataMember]
protected override IEnumerable<BaseItem> ActualChildren
{
get { return GetActualChildren(); }
@@ -200,9 +210,30 @@ namespace MediaBrowser.Controller.Entities
public IEnumerable<Folder> GetPhysicalParents()
{
- return LibraryManager.RootFolder.Children
+ var rootChildren = LibraryManager.RootFolder.Children
.OfType<Folder>()
- .Where(i => i.Path != null && PhysicalLocations.Contains(i.Path, StringComparer.OrdinalIgnoreCase));
+ .ToList();
+
+ return PhysicalLocations.Where(i => !string.Equals(i, Path, StringComparison.OrdinalIgnoreCase)).SelectMany(i => GetPhysicalParents(i, rootChildren)).DistinctBy(i => i.Id);
+ }
+
+ private IEnumerable<Folder> GetPhysicalParents(string path, List<Folder> rootChildren)
+ {
+ var result = rootChildren
+ .Where(i => string.Equals(i.Path, path, StringComparison.OrdinalIgnoreCase))
+ .ToList();
+
+ if (result.Count == 0)
+ {
+ var folder = LibraryManager.FindByPath(path, true) as Folder;
+
+ if (folder != null)
+ {
+ result.Add(folder);
+ }
+ }
+
+ return result;
}
[IgnoreDataMember]
diff --git a/MediaBrowser.Controller/Entities/Folder.cs b/MediaBrowser.Controller/Entities/Folder.cs
index af1cbdf2c..0397e9a88 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; }
@@ -28,6 +29,9 @@ namespace MediaBrowser.Controller.Entities
public List<Guid> ThemeSongIds { get; set; }
public List<Guid> ThemeVideoIds { get; set; }
+ [IgnoreDataMember]
+ public DateTime? DateLastMediaAdded { get; set; }
+
public Folder()
{
LinkedChildren = new List<LinkedChild>();
@@ -56,6 +60,36 @@ namespace MediaBrowser.Controller.Entities
}
[IgnoreDataMember]
+ public virtual bool SupportsCumulativeRunTimeTicks
+ {
+ get
+ {
+ return false;
+ }
+ }
+
+ [IgnoreDataMember]
+ public virtual bool SupportsDateLastMediaAdded
+ {
+ get
+ {
+ return false;
+ }
+ }
+
+ public override bool RequiresRefresh()
+ {
+ var baseResult = base.RequiresRefresh();
+
+ if (SupportsCumulativeRunTimeTicks && !RunTimeTicks.HasValue)
+ {
+ baseResult = true;
+ }
+
+ return baseResult;
+ }
+
+ [IgnoreDataMember]
public override string FileNameWithoutExtension
{
get
@@ -93,25 +127,12 @@ namespace MediaBrowser.Controller.Entities
/// <value><c>true</c> if this instance is root; otherwise, <c>false</c>.</value>
public bool IsRoot { get; set; }
- /// <summary>
- /// Gets a value indicating whether this instance is virtual folder.
- /// </summary>
- /// <value><c>true</c> if this instance is virtual folder; otherwise, <c>false</c>.</value>
- [IgnoreDataMember]
- public virtual bool IsVirtualFolder
- {
- get
- {
- return false;
- }
- }
-
public virtual List<LinkedChild> LinkedChildren { get; set; }
[IgnoreDataMember]
protected virtual bool SupportsShortcutChildren
{
- get { return ConfigurationManager.Configuration.EnableWindowsShortcuts; }
+ get { return false; }
}
/// <summary>
@@ -144,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);
}
@@ -199,8 +186,8 @@ namespace MediaBrowser.Controller.Entities
/// <returns>Dictionary{System.StringFunc{UserIEnumerable{BaseItem}}}.</returns>
protected virtual IEnumerable<string> GetIndexByOptions()
{
- return new List<string> {
- {"None"},
+ return new List<string> {
+ {"None"},
{"Performer"},
{"Genre"},
{"Director"},
@@ -222,41 +209,15 @@ 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>
+ [IgnoreDataMember]
protected virtual IEnumerable<BaseItem> ActualChildren
{
get
{
- return ChildIds.Select(LibraryManager.GetItemById).Where(i => i != null);
+ return LoadChildren();
}
}
@@ -310,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();
@@ -412,13 +373,6 @@ namespace MediaBrowser.Controller.Entities
if (currentChildren.TryGetValue(child.Id, out currentChild) && IsValidFromResolver(currentChild, child))
{
- var currentChildLocationType = currentChild.LocationType;
- if (currentChildLocationType != LocationType.Remote &&
- currentChildLocationType != LocationType.Virtual)
- {
- currentChild.DateModified = child.DateModified;
- }
-
await UpdateIsOffline(currentChild, false).ConfigureAwait(false);
validChildren.Add(currentChild);
@@ -440,17 +394,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
{
@@ -460,8 +412,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);
@@ -474,8 +424,6 @@ namespace MediaBrowser.Controller.Entities
}
await LibraryManager.CreateItems(newItems, cancellationToken).ConfigureAwait(false);
-
- AddChildrenInternal(newItems.Select(i => i.Id).ToList());
}
}
@@ -703,20 +651,41 @@ 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;
- if (RequiresPostFiltering(query))
+ if (!query.ForceDirect && RequiresPostFiltering(query))
{
IEnumerable<BaseItem> items;
Func<BaseItem, bool> filter = i => UserViewBuilder.Filter(i, user, query, UserDataManager, LibraryManager);
@@ -727,7 +696,7 @@ namespace MediaBrowser.Controller.Entities
}
else
{
- items = GetRecursiveChildren(user, filter);
+ items = GetRecursiveChildren(user, query);
}
return PostFilterAndSort(items, query);
@@ -747,63 +716,23 @@ 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;
}
}
if (query.SortBy != null && query.SortBy.Length > 0)
{
- if (query.SortBy.Contains(ItemSortBy.DatePlayed, StringComparer.OrdinalIgnoreCase))
- {
- Logger.Debug("Query requires post-filtering due to ItemSortBy.DatePlayed");
- 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 (query.SortBy.Contains(ItemSortBy.AiredEpisodeOrder, StringComparer.OrdinalIgnoreCase))
{
Logger.Debug("Query requires post-filtering due to ItemSortBy.AiredEpisodeOrder");
return true;
}
- if (query.SortBy.Contains(ItemSortBy.Album, StringComparer.OrdinalIgnoreCase))
- {
- Logger.Debug("Query requires post-filtering due to ItemSortBy.Album");
- return true;
- }
- 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.Budget, StringComparer.OrdinalIgnoreCase))
{
Logger.Debug("Query requires post-filtering due to ItemSortBy.Budget");
return true;
}
- if (query.SortBy.Contains(ItemSortBy.DateLastContentAdded, StringComparer.OrdinalIgnoreCase))
- {
- Logger.Debug("Query requires post-filtering due to ItemSortBy.DateLastContentAdded");
- return true;
- }
if (query.SortBy.Contains(ItemSortBy.GameSystem, StringComparer.OrdinalIgnoreCase))
{
Logger.Debug("Query requires post-filtering due to ItemSortBy.GameSystem");
@@ -814,16 +743,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.PlayCount, StringComparer.OrdinalIgnoreCase))
- {
- Logger.Debug("Query requires post-filtering due to ItemSortBy.PlayCount");
- return true;
- }
if (query.SortBy.Contains(ItemSortBy.Players, StringComparer.OrdinalIgnoreCase))
{
Logger.Debug("Query requires post-filtering due to ItemSortBy.Players");
@@ -834,21 +753,6 @@ namespace MediaBrowser.Controller.Entities
Logger.Debug("Query requires post-filtering due to ItemSortBy.Revenue");
return true;
}
- if (query.SortBy.Contains(ItemSortBy.SeriesSortName, StringComparer.OrdinalIgnoreCase))
- {
- Logger.Debug("Query requires post-filtering due to ItemSortBy.SeriesSortName");
- return true;
- }
- if (query.SortBy.Contains(ItemSortBy.StartDate, StringComparer.OrdinalIgnoreCase))
- {
- Logger.Debug("Query requires post-filtering due to ItemSortBy.StartDate");
- 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");
@@ -862,42 +766,6 @@ namespace MediaBrowser.Controller.Entities
return true;
}
- if (query.PersonIds.Length > 0)
- {
- Logger.Debug("Query requires post-filtering due to PersonIds");
- return true;
- }
-
- 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");
@@ -911,30 +779,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");
@@ -984,26 +828,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)
{
@@ -1023,31 +847,7 @@ 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");
- return true;
- }
-
- if (query.IsUnaired.HasValue)
- {
- Logger.Debug("Query requires post-filtering due to IsUnaired");
- return true;
- }
-
- if (query.IsVirtualUnaired.HasValue)
- {
- Logger.Debug("Query requires post-filtering due to IsVirtualUnaired");
- 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;
@@ -1059,30 +859,6 @@ namespace MediaBrowser.Controller.Entities
return true;
}
- if (!string.IsNullOrWhiteSpace(query.NameContains))
- {
- Logger.Debug("Query requires post-filtering due to NameContains");
- return true;
- }
-
- if (!string.IsNullOrWhiteSpace(query.NameLessThan))
- {
- Logger.Debug("Query requires post-filtering due to NameLessThan");
- return true;
- }
-
- if (!string.IsNullOrWhiteSpace(query.NameStartsWith))
- {
- Logger.Debug("Query requires post-filtering due to NameStartsWith");
- return true;
- }
-
- if (!string.IsNullOrWhiteSpace(query.NameStartsWithOrGreater))
- {
- Logger.Debug("Query requires post-filtering due to NameStartsWithOrGreater");
- return true;
- }
-
if (query.AirDays.Length > 0)
{
Logger.Debug("Query requires post-filtering due to AirDays");
@@ -1107,22 +883,21 @@ namespace MediaBrowser.Controller.Entities
return true;
}
- if (query.AlbumNames.Length > 0)
- {
- Logger.Debug("Query requires post-filtering due to AlbumNames");
- return true;
- }
+ return false;
+ }
- if (query.ArtistNames.Length > 0)
+ public Task<QueryResult<BaseItem>> GetItems(InternalItemsQuery query)
+ {
+ if (query.ItemIds.Length > 0)
{
- Logger.Debug("Query requires post-filtering due to ArtistNames");
- return true;
+ var specificItems = query.ItemIds.Select(LibraryManager.GetItemById).Where(i => i != null).ToList();
+ return Task.FromResult(PostFilterAndSort(specificItems, query));
}
- return false;
+ return GetItemsInternal(query);
}
- public virtual async Task<QueryResult<BaseItem>> GetItems(InternalItemsQuery query)
+ protected virtual async Task<QueryResult<BaseItem>> GetItemsInternal(InternalItemsQuery query)
{
if (SourceType == SourceType.Channel)
{
@@ -1171,18 +946,16 @@ namespace MediaBrowser.Controller.Entities
else
{
items = query.Recursive
- ? GetRecursiveChildren(user, filter)
+ ? GetRecursiveChildren(user, query)
: GetChildren(user, true).Where(filter);
}
- var result = PostFilterAndSort(items, query);
-
- return result;
+ return PostFilterAndSort(items, query);
}
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)
@@ -1210,19 +983,14 @@ namespace MediaBrowser.Controller.Entities
/// <summary>
/// Adds the children to list.
/// </summary>
- /// <param name="user">The user.</param>
- /// <param name="includeLinkedChildren">if set to <c>true</c> [include linked children].</param>
- /// <param name="result">The result.</param>
- /// <param name="recursive">if set to <c>true</c> [recursive].</param>
- /// <param name="filter">The filter.</param>
/// <returns><c>true</c> if XXXX, <c>false</c> otherwise</returns>
- private void AddChildren(User user, bool includeLinkedChildren, Dictionary<Guid, BaseItem> result, bool recursive, Func<BaseItem, bool> filter)
+ private void AddChildren(User user, bool includeLinkedChildren, Dictionary<Guid, BaseItem> result, bool recursive, InternalItemsQuery query)
{
foreach (var child in GetEligibleChildrenForRecursiveChildren(user))
{
if (child.IsVisible(user))
{
- if (filter == null || filter(child))
+ if (query == null || UserViewBuilder.FilterItem(child, query))
{
result[child.Id] = child;
}
@@ -1231,7 +999,7 @@ namespace MediaBrowser.Controller.Entities
{
var folder = (Folder)child;
- folder.AddChildren(user, includeLinkedChildren, result, true, filter);
+ folder.AddChildren(user, includeLinkedChildren, result, true, query);
}
}
}
@@ -1242,7 +1010,7 @@ namespace MediaBrowser.Controller.Entities
{
if (child.IsVisible(user))
{
- if (filter == null || filter(child))
+ if (query == null || UserViewBuilder.FilterItem(child, query))
{
result[child.Id] = child;
}
@@ -1260,10 +1028,10 @@ namespace MediaBrowser.Controller.Entities
/// <exception cref="System.ArgumentNullException"></exception>
public IEnumerable<BaseItem> GetRecursiveChildren(User user, bool includeLinkedChildren = true)
{
- return GetRecursiveChildren(user, i => true);
+ return GetRecursiveChildren(user, null);
}
- public virtual IEnumerable<BaseItem> GetRecursiveChildren(User user, Func<BaseItem, bool> filter)
+ public virtual IEnumerable<BaseItem> GetRecursiveChildren(User user, InternalItemsQuery query)
{
if (user == null)
{
@@ -1272,7 +1040,7 @@ namespace MediaBrowser.Controller.Entities
var result = new Dictionary<Guid, BaseItem>();
- AddChildren(user, true, result, true, filter);
+ AddChildren(user, true, result, true, query);
return result.Values;
}
@@ -1288,33 +1056,42 @@ namespace MediaBrowser.Controller.Entities
public IList<BaseItem> GetRecursiveChildren(Func<BaseItem, bool> filter)
{
- var list = new List<BaseItem>();
+ var result = new Dictionary<Guid, BaseItem>();
- AddChildrenToList(list, true, filter);
+ AddChildrenToList(result, true, true, filter);
- return list;
+ return result.Values.ToList();
}
/// <summary>
/// Adds the children to list.
/// </summary>
- /// <param name="list">The list.</param>
- /// <param name="recursive">if set to <c>true</c> [recursive].</param>
- /// <param name="filter">The filter.</param>
- private void AddChildrenToList(List<BaseItem> list, bool recursive, Func<BaseItem, bool> filter)
+ private void AddChildrenToList(Dictionary<Guid, BaseItem> result, bool includeLinkedChildren, bool recursive, Func<BaseItem, bool> filter)
{
foreach (var child in Children)
{
if (filter == null || filter(child))
{
- list.Add(child);
+ result[child.Id] = child;
}
if (recursive && child.IsFolder)
{
var folder = (Folder)child;
- folder.AddChildrenToList(list, true, filter);
+ // We can only support includeLinkedChildren for the first folder, or we might end up stuck in a loop of linked items
+ folder.AddChildrenToList(result, false, true, filter);
+ }
+ }
+
+ if (includeLinkedChildren)
+ {
+ foreach (var child in GetLinkedChildren())
+ {
+ if (filter == null || filter(child))
+ {
+ result[child.Id] = child;
+ }
}
}
}
@@ -1520,13 +1297,12 @@ namespace MediaBrowser.Controller.Entities
User = user,
Recursive = true,
IsFolder = false,
- IsUnaired = false
-
+ EnableTotalRecordCount = false
};
- if (!user.Configuration.DisplayMissingEpisodes)
+ if (!user.Configuration.DisplayMissingEpisodes || !user.Configuration.DisplayUnairedEpisodes)
{
- query.IsMissing = false;
+ query.ExcludeLocationTypes = new[] { LocationType.Virtual };
}
var itemsResult = await GetItems(query).ConfigureAwait(false);
@@ -1548,7 +1324,8 @@ namespace MediaBrowser.Controller.Entities
{
User = user,
Recursive = true,
- IsFolder = false
+ IsFolder = false,
+ EnableTotalRecordCount = false
}).ConfigureAwait(false);
@@ -1558,38 +1335,18 @@ namespace MediaBrowser.Controller.Entities
await Task.WhenAll(tasks).ConfigureAwait(false);
}
- /// <summary>
- /// Finds an item by path, recursively
- /// </summary>
- /// <param name="path">The path.</param>
- /// <returns>BaseItem.</returns>
- /// <exception cref="System.ArgumentNullException"></exception>
- public BaseItem FindByPath(string path)
+ public override bool IsPlayed(User user)
{
- if (string.IsNullOrEmpty(path))
- {
- throw new ArgumentNullException();
- }
-
- if (string.Equals(Path, path, StringComparison.OrdinalIgnoreCase))
- {
- return this;
- }
-
- if (PhysicalLocations.Contains(path, StringComparer.OrdinalIgnoreCase))
+ var itemsResult = GetItems(new InternalItemsQuery(user)
{
- return this;
- }
+ Recursive = true,
+ IsFolder = false,
+ ExcludeLocationTypes = new[] { LocationType.Virtual },
+ EnableTotalRecordCount = false
- return GetRecursiveChildren(i => string.Equals(i.Path, path, StringComparison.OrdinalIgnoreCase) ||
- (!i.IsFolder && !i.IsInMixedFolder && string.Equals(i.ContainingFolderPath, path, StringComparison.OrdinalIgnoreCase)) ||
- i.PhysicalLocations.Contains(path, StringComparer.OrdinalIgnoreCase))
- .FirstOrDefault();
- }
+ }).Result;
- public override bool IsPlayed(User user)
- {
- return GetRecursiveChildren(user, i => !i.IsFolder && i.LocationType != LocationType.Virtual)
+ return itemsResult.Items
.All(i => i.IsPlayed(user));
}
@@ -1598,65 +1355,79 @@ namespace MediaBrowser.Controller.Entities
return !IsPlayed(user);
}
- public override void FillUserDataDtoValues(UserItemDataDto dto, UserItemData userData, User user)
+ [IgnoreDataMember]
+ public virtual bool SupportsUserDataFromChildren
{
- var recursiveItemCount = 0;
- var unplayed = 0;
-
- double totalPercentPlayed = 0;
-
- IEnumerable<BaseItem> children;
- var folder = this;
-
- var season = folder as Season;
-
- if (season != null)
+ get
{
- children = season.GetEpisodes(user).Where(i => i.LocationType != LocationType.Virtual);
+ // These are just far too slow.
+ if (this is ICollectionFolder)
+ {
+ return false;
+ }
+ if (this is UserView)
+ {
+ return false;
+ }
+ if (this is UserRootFolder)
+ {
+ return false;
+ }
+ if (this is Channel)
+ {
+ return false;
+ }
+ if (SourceType != SourceType.Library)
+ {
+ return false;
+ }
+
+ return true;
}
- else
+ }
+
+ public override async Task FillUserDataDtoValues(UserItemDataDto dto, UserItemData userData, BaseItemDto itemDto, User user)
+ {
+ if (!SupportsUserDataFromChildren)
{
- children = folder.GetRecursiveChildren(user, i => !i.IsFolder && i.LocationType != LocationType.Virtual);
+ return;
}
- // Loop through each recursive child
- foreach (var child in children)
+ var unplayedQueryResult = await GetItems(new InternalItemsQuery(user)
{
- recursiveItemCount++;
-
- var isUnplayed = true;
+ Recursive = true,
+ IsFolder = false,
+ IsVirtualItem = false,
+ EnableTotalRecordCount = true,
+ Limit = 0,
+ IsPlayed = false
- var itemUserData = UserDataManager.GetUserData(user.Id, child.GetUserDataKey());
+ }).ConfigureAwait(false);
- // Incrememt totalPercentPlayed
- if (itemUserData != null)
- {
- if (itemUserData.Played)
- {
- totalPercentPlayed += 100;
+ var allItemsQueryResult = await GetItems(new InternalItemsQuery(user)
+ {
+ 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 e597b2a15..54386a179 100644
--- a/MediaBrowser.Controller/Entities/Game.cs
+++ b/MediaBrowser.Controller/Entities/Game.cs
@@ -4,10 +4,11 @@ using MediaBrowser.Model.Entities;
using System;
using System.Collections.Generic;
using System.Linq;
+using System.Runtime.Serialization;
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; }
@@ -32,6 +33,12 @@ namespace MediaBrowser.Controller.Entities
locationType != LocationType.Virtual;
}
+ [IgnoreDataMember]
+ public override bool EnableForceSaveOnDateModifiedChange
+ {
+ get { return true; }
+ }
+
/// <summary>
/// Gets or sets the remote trailers.
/// </summary>
@@ -42,6 +49,7 @@ namespace MediaBrowser.Controller.Entities
/// Gets the type of the media.
/// </summary>
/// <value>The type of the media.</value>
+ [IgnoreDataMember]
public override string MediaType
{
get { return Model.Entities.MediaType.Game; }
@@ -76,15 +84,16 @@ namespace MediaBrowser.Controller.Entities
/// </summary>
public List<string> MultiPartGameFiles { get; set; }
- protected override string CreateUserDataKey()
+ public override List<string> GetUserDataKeys()
{
+ var list = base.GetUserDataKeys();
var id = this.GetProviderId(MetadataProviders.Gamesdb);
if (!string.IsNullOrEmpty(id))
{
- return "Game-Gamesdb-" + id;
+ list.Insert(0, "Game-Gamesdb-" + id);
}
- return base.CreateUserDataKey();
+ return list;
}
public override IEnumerable<string> GetDeletePaths()
diff --git a/MediaBrowser.Controller/Entities/GameGenre.cs b/MediaBrowser.Controller/Entities/GameGenre.cs
index d2b6b4856..45e766c0f 100644
--- a/MediaBrowser.Controller/Entities/GameGenre.cs
+++ b/MediaBrowser.Controller/Entities/GameGenre.cs
@@ -2,18 +2,26 @@
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
+using MediaBrowser.Common.Extensions;
namespace MediaBrowser.Controller.Entities
{
public class GameGenre : BaseItem, IItemByName
{
- /// <summary>
- /// Gets the user data key.
- /// </summary>
- /// <returns>System.String.</returns>
- protected override string CreateUserDataKey()
+ public override List<string> GetUserDataKeys()
{
- return "GameGenre-" + Name;
+ var list = base.GetUserDataKeys();
+
+ list.Insert(0, GetType().Name + "-" + (Name ?? string.Empty).RemoveDiacritics());
+ return list;
+ }
+
+ public override string PresentationUniqueKey
+ {
+ get
+ {
+ return GetUserDataKeys()[0];
+ }
}
/// <summary>
@@ -63,6 +71,14 @@ namespace MediaBrowser.Controller.Entities
return i => i is Game && i.Genres.Contains(Name, StringComparer.OrdinalIgnoreCase);
}
+ public IEnumerable<BaseItem> GetTaggedItems(InternalItemsQuery query)
+ {
+ query.Genres = new[] { Name };
+ query.IncludeItemTypes = new[] { typeof(Game).Name };
+
+ return LibraryManager.GetItemList(query);
+ }
+
[IgnoreDataMember]
public override bool SupportsPeople
{
diff --git a/MediaBrowser.Controller/Entities/GameSystem.cs b/MediaBrowser.Controller/Entities/GameSystem.cs
index bc35c4738..1c09ee507 100644
--- a/MediaBrowser.Controller/Entities/GameSystem.cs
+++ b/MediaBrowser.Controller/Entities/GameSystem.cs
@@ -2,6 +2,7 @@
using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Configuration;
using System;
+using System.Collections.Generic;
using MediaBrowser.Model.Users;
namespace MediaBrowser.Controller.Entities
@@ -31,17 +32,15 @@ namespace MediaBrowser.Controller.Entities
/// <value>The game system.</value>
public string GameSystemName { get; set; }
- /// <summary>
- /// Gets the user data key.
- /// </summary>
- /// <returns>System.String.</returns>
- protected override string CreateUserDataKey()
+ public override List<string> GetUserDataKeys()
{
+ var list = base.GetUserDataKeys();
+
if (!string.IsNullOrEmpty(GameSystemName))
{
- return "GameSystem-" + GameSystemName;
+ list.Insert(0, "GameSystem-" + GameSystemName);
}
- return base.CreateUserDataKey();
+ return list;
}
protected override bool GetBlockUnratedValue(UserPolicy config)
diff --git a/MediaBrowser.Controller/Entities/Genre.cs b/MediaBrowser.Controller/Entities/Genre.cs
index 233e1e0fd..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
{
@@ -11,13 +12,20 @@ namespace MediaBrowser.Controller.Entities
/// </summary>
public class Genre : BaseItem, IItemByName
{
- /// <summary>
- /// Gets the user data key.
- /// </summary>
- /// <returns>System.String.</returns>
- protected override string CreateUserDataKey()
+ public override List<string> GetUserDataKeys()
{
- return "Genre-" + Name;
+ var list = base.GetUserDataKeys();
+
+ list.Insert(0, GetType().Name + "-" + (Name ?? string.Empty).RemoveDiacritics());
+ return list;
+ }
+
+ public override string PresentationUniqueKey
+ {
+ get
+ {
+ return GetUserDataKeys()[0];
+ }
}
/// <summary>
@@ -67,6 +75,14 @@ namespace MediaBrowser.Controller.Entities
return i => !(i is Game) && !(i is IHasMusicGenres) && i.Genres.Contains(Name, StringComparer.OrdinalIgnoreCase);
}
+ public IEnumerable<BaseItem> GetTaggedItems(InternalItemsQuery query)
+ {
+ query.Genres = new[] { Name };
+ query.ExcludeItemTypes = new[] { typeof(Game).Name, typeof(MusicVideo).Name, typeof(Audio.Audio).Name, typeof(MusicAlbum).Name, typeof(MusicArtist).Name };
+
+ return LibraryManager.GetItemList(query);
+ }
+
[IgnoreDataMember]
public override bool SupportsPeople
{
diff --git a/MediaBrowser.Controller/Entities/IHasImages.cs b/MediaBrowser.Controller/Entities/IHasImages.cs
index a38b7394d..226748098 100644
--- a/MediaBrowser.Controller/Entities/IHasImages.cs
+++ b/MediaBrowser.Controller/Entities/IHasImages.cs
@@ -1,4 +1,5 @@
-using MediaBrowser.Controller.Providers;
+using System;
+using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Entities;
using System.Collections.Generic;
using System.Threading;
diff --git a/MediaBrowser.Controller/Entities/IHasMetadata.cs b/MediaBrowser.Controller/Entities/IHasMetadata.cs
index 0e4ae04ff..378c4a390 100644
--- a/MediaBrowser.Controller/Entities/IHasMetadata.cs
+++ b/MediaBrowser.Controller/Entities/IHasMetadata.cs
@@ -17,7 +17,7 @@ namespace MediaBrowser.Controller.Entities
/// Gets the date modified.
/// </summary>
/// <value>The date modified.</value>
- DateTime DateModified { get; }
+ DateTime DateModified { get; set; }
/// <summary>
/// Gets or sets the date last saved.
@@ -25,6 +25,8 @@ namespace MediaBrowser.Controller.Entities
/// <value>The date last saved.</value>
DateTime DateLastSaved { get; set; }
+ SourceType SourceType { get; set; }
+
/// <summary>
/// Gets or sets the date last refreshed.
/// </summary>
@@ -47,5 +49,9 @@ namespace MediaBrowser.Controller.Entities
/// </summary>
/// <value><c>true</c> if [supports people]; otherwise, <c>false</c>.</value>
bool SupportsPeople { get; }
+
+ bool RequiresRefresh();
+
+ bool EnableForceSaveOnDateModifiedChange { get; }
}
}
diff --git a/MediaBrowser.Controller/Entities/IHasSeries.cs b/MediaBrowser.Controller/Entities/IHasSeries.cs
index 64c33a376..531f58788 100644
--- a/MediaBrowser.Controller/Entities/IHasSeries.cs
+++ b/MediaBrowser.Controller/Entities/IHasSeries.cs
@@ -1,4 +1,6 @@

+using System;
+
namespace MediaBrowser.Controller.Entities
{
public interface IHasSeries
@@ -7,6 +9,11 @@ namespace MediaBrowser.Controller.Entities
/// Gets the name of the series.
/// </summary>
/// <value>The name of the series.</value>
- string SeriesName { get; }
+ string SeriesName { get; set; }
+ string FindSeriesName();
+ string SeriesSortName { get; set; }
+ string FindSeriesSortName();
+ Guid? SeriesId { get; set; }
+ Guid? FindSeriesId();
}
}
diff --git a/MediaBrowser.Controller/Entities/IHasTrailers.cs b/MediaBrowser.Controller/Entities/IHasTrailers.cs
index bc1c7d875..e5cbdff72 100644
--- a/MediaBrowser.Controller/Entities/IHasTrailers.cs
+++ b/MediaBrowser.Controller/Entities/IHasTrailers.cs
@@ -1,6 +1,7 @@
using MediaBrowser.Model.Entities;
using System;
using System.Collections.Generic;
+using System.Linq;
namespace MediaBrowser.Controller.Entities
{
@@ -18,11 +19,20 @@ namespace MediaBrowser.Controller.Entities
/// <value>The local trailer ids.</value>
List<Guid> LocalTrailerIds { get; set; }
List<Guid> RemoteTrailerIds { get; set; }
+ }
+ public static class HasTrailerExtensions
+ {
/// <summary>
/// Gets the trailer ids.
/// </summary>
/// <returns>List&lt;Guid&gt;.</returns>
- List<Guid> GetTrailerIds();
+ public static List<Guid> GetTrailerIds(this IHasTrailers item)
+ {
+ var list = item.LocalTrailerIds.ToList();
+ list.AddRange(item.RemoteTrailerIds);
+ return list;
+ }
+
}
}
diff --git a/MediaBrowser.Controller/Entities/IHasUserData.cs b/MediaBrowser.Controller/Entities/IHasUserData.cs
index 34a820853..2495b0ccd 100644
--- a/MediaBrowser.Controller/Entities/IHasUserData.cs
+++ b/MediaBrowser.Controller/Entities/IHasUserData.cs
@@ -1,4 +1,6 @@
-using MediaBrowser.Model.Dto;
+using System.Collections.Generic;
+using System.Threading.Tasks;
+using MediaBrowser.Model.Dto;
namespace MediaBrowser.Controller.Entities
{
@@ -7,11 +9,7 @@ namespace MediaBrowser.Controller.Entities
/// </summary>
public interface IHasUserData : IHasId
{
- /// <summary>
- /// Gets the user data key.
- /// </summary>
- /// <returns>System.String.</returns>
- string GetUserDataKey();
+ List<string> GetUserDataKeys();
/// <summary>
/// Fills the user data dto values.
@@ -19,6 +17,8 @@ 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/IItemByName.cs b/MediaBrowser.Controller/Entities/IItemByName.cs
index e6667290c..2ac4af1af 100644
--- a/MediaBrowser.Controller/Entities/IItemByName.cs
+++ b/MediaBrowser.Controller/Entities/IItemByName.cs
@@ -15,11 +15,7 @@ namespace MediaBrowser.Controller.Entities
/// <returns>IEnumerable{BaseItem}.</returns>
IEnumerable<BaseItem> GetTaggedItems(IEnumerable<BaseItem> inputItems);
- /// <summary>
- /// Gets the item filter.
- /// </summary>
- /// <returns>Func&lt;BaseItem, System.Boolean&gt;.</returns>
- Func<BaseItem, bool> GetItemFilter();
+ IEnumerable<BaseItem> GetTaggedItems(InternalItemsQuery query);
}
public interface IHasDualAccess : IItemByName
diff --git a/MediaBrowser.Controller/Entities/InternalItemsQuery.cs b/MediaBrowser.Controller/Entities/InternalItemsQuery.cs
index b568aec18..69cab5ec5 100644
--- a/MediaBrowser.Controller/Entities/InternalItemsQuery.cs
+++ b/MediaBrowser.Controller/Entities/InternalItemsQuery.cs
@@ -19,7 +19,7 @@ namespace MediaBrowser.Controller.Entities
public User User { get; set; }
- public Func<BaseItem, bool> Filter { get; set; }
+ public BaseItem SimilarTo { get; set; }
public bool? IsFolder { get; set; }
public bool? IsFavorite { get; set; }
@@ -33,7 +33,9 @@ namespace MediaBrowser.Controller.Entities
public string[] IncludeItemTypes { get; set; }
public string[] ExcludeItemTypes { get; set; }
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; }
@@ -44,12 +46,17 @@ 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; }
-
+ public string Name { get; set; }
+ public string SlugName { get; set; }
+
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; }
@@ -58,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; }
@@ -105,12 +111,15 @@ 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; }
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; }
@@ -122,7 +131,6 @@ namespace MediaBrowser.Controller.Entities
public SourceType[] SourceTypes { get; set; }
public SourceType[] ExcludeSourceTypes { get; set; }
public TrailerType[] TrailerTypes { get; set; }
- public TrailerType[] ExcludeTrailerTypes { get; set; }
public DayOfWeek[] AirDays { get; set; }
public SeriesStatus[] SeriesStatuses { get; set; }
@@ -130,17 +138,31 @@ namespace MediaBrowser.Controller.Entities
public string[] AlbumNames { get; set; }
public string[] ArtistNames { get; set; }
-
+ public string[] ExcludeArtistIds { get; set; }
+ public string AncestorWithPresentationUniqueKey { get; set; }
+
+ 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()
{
+ GroupByPresentationUniqueKey = true;
+ EnableTotalRecordCount = true;
+
AlbumNames = new string[] { };
ArtistNames = new string[] { };
-
+ ExcludeArtistIds = 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[] { };
@@ -154,16 +176,17 @@ 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[] { };
+ ExcludeInheritedTags = new string[] { };
LocationTypes = new LocationType[] { };
ExcludeLocationTypes = new LocationType[] { };
PresetViews = new string[] { };
SourceTypes = new SourceType[] { };
ExcludeSourceTypes = new SourceType[] { };
TrailerTypes = new TrailerType[] { };
- ExcludeTrailerTypes = new TrailerType[] { };
AirDays = new DayOfWeek[] { };
SeriesStatuses = new SeriesStatus[] { };
}
@@ -171,6 +194,11 @@ namespace MediaBrowser.Controller.Entities
public InternalItemsQuery(User user)
: this()
{
+ SetUser(user);
+ }
+
+ public void SetUser(User user)
+ {
if (user != null)
{
var policy = user.Policy;
@@ -181,7 +209,7 @@ namespace MediaBrowser.Controller.Entities
BlockUnratedItems = policy.BlockUnratedItems;
}
- ExcludeTags = policy.BlockedTags;
+ ExcludeInheritedTags = policy.BlockedTags;
User = user;
}
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 cd3e07ea3..4effc162e 100644
--- a/MediaBrowser.Controller/Entities/Movies/BoxSet.cs
+++ b/MediaBrowser.Controller/Entities/Movies/BoxSet.cs
@@ -8,13 +8,14 @@ using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
+using MediaBrowser.Controller.Entities.Audio;
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; }
@@ -25,7 +26,6 @@ namespace MediaBrowser.Controller.Entities.Movies
RemoteTrailerIds = new List<Guid>();
DisplayOrder = ItemSortBy.PremiereDate;
- Keywords = new List<string>();
Shares = new List<Share>();
}
@@ -47,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>
@@ -118,7 +112,7 @@ namespace MediaBrowser.Controller.Entities.Movies
// Gather all possible ratings
var ratings = GetRecursiveChildren()
.Concat(GetLinkedChildren())
- .Where(i => i is Movie || i is Series)
+ .Where(i => i is Movie || i is Series || i is MusicAlbum || i is Game)
.Select(i => i.OfficialRating)
.Where(i => !string.IsNullOrEmpty(i))
.Distinct(StringComparer.OrdinalIgnoreCase)
diff --git a/MediaBrowser.Controller/Entities/Movies/Movie.cs b/MediaBrowser.Controller/Entities/Movies/Movie.cs
index f5c2a9935..c7a833c58 100644
--- a/MediaBrowser.Controller/Entities/Movies/Movie.cs
+++ b/MediaBrowser.Controller/Entities/Movies/Movie.cs
@@ -8,18 +8,17 @@ 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; }
- public string OriginalTitle { get; set; }
-
public List<Guid> ThemeSongIds { get; set; }
public List<Guid> ThemeVideoIds { get; set; }
public List<string> ProductionLocations { get; set; }
@@ -33,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>();
}
@@ -43,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; }
@@ -77,45 +74,6 @@ namespace MediaBrowser.Controller.Entities.Movies
get { return TmdbCollectionName; }
set { TmdbCollectionName = value; }
}
-
- /// <summary>
- /// Gets the trailer ids.
- /// </summary>
- /// <returns>List&lt;Guid&gt;.</returns>
- public List<Guid> GetTrailerIds()
- {
- var list = LocalTrailerIds.ToList();
- list.AddRange(RemoteTrailerIds);
- return list;
- }
-
- /// <summary>
- /// Gets the user data key.
- /// </summary>
- /// <returns>System.String.</returns>
- protected override string CreateUserDataKey()
- {
- var key = GetMovieUserDataKey(this);
-
- if (string.IsNullOrWhiteSpace(key))
- {
- key = base.CreateUserDataKey();
- }
-
- return key;
- }
-
- public static string GetMovieUserDataKey(BaseItem movie)
- {
- var key = movie.GetProviderId(MetadataProviders.Tmdb);
-
- if (string.IsNullOrWhiteSpace(key))
- {
- key = movie.GetProviderId(MetadataProviders.Imdb);
- }
-
- return key;
- }
protected override async Task<bool> RefreshedOwnedItems(MetadataRefreshOptions options, List<FileSystemMetadata> fileSystemChildren, CancellationToken cancellationToken)
{
@@ -204,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/MusicVideo.cs b/MediaBrowser.Controller/Entities/MusicVideo.cs
index b52f16a46..7119828e2 100644
--- a/MediaBrowser.Controller/Entities/MusicVideo.cs
+++ b/MediaBrowser.Controller/Entities/MusicVideo.cs
@@ -10,12 +10,6 @@ namespace MediaBrowser.Controller.Entities
public class MusicVideo : Video, IHasArtist, IHasMusicGenres, IHasProductionLocations, IHasBudget, IHasLookupInfo<MusicVideoInfo>
{
/// <summary>
- /// Gets or sets the album.
- /// </summary>
- /// <value>The album.</value>
- public string Album { get; set; }
-
- /// <summary>
/// Gets or sets the budget.
/// </summary>
/// <value>The budget.</value>
@@ -44,15 +38,6 @@ namespace MediaBrowser.Controller.Entities
}
}
- /// <summary>
- /// Gets the user data key.
- /// </summary>
- /// <returns>System.String.</returns>
- protected override string CreateUserDataKey()
- {
- return this.GetProviderId(MetadataProviders.Tmdb) ?? this.GetProviderId(MetadataProviders.Imdb) ?? base.CreateUserDataKey();
- }
-
public override UnratedItem GetBlockUnratedType()
{
return UnratedItem.Music;
diff --git a/MediaBrowser.Controller/Entities/Person.cs b/MediaBrowser.Controller/Entities/Person.cs
index 560ea6e05..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
@@ -18,13 +19,20 @@ namespace MediaBrowser.Controller.Entities
/// <value>The place of birth.</value>
public string PlaceOfBirth { get; set; }
- /// <summary>
- /// Gets the user data key.
- /// </summary>
- /// <returns>System.String.</returns>
- protected override string CreateUserDataKey()
+ public override List<string> GetUserDataKeys()
{
- return "Person-" + Name;
+ var list = base.GetUserDataKeys();
+
+ list.Insert(0, GetType().Name + "-" + (Name ?? string.Empty).RemoveDiacritics());
+ return list;
+ }
+
+ public override string PresentationUniqueKey
+ {
+ get
+ {
+ return GetUserDataKeys()[0];
+ }
}
public PersonLookupInfo GetLookupInfo()
@@ -32,6 +40,13 @@ namespace MediaBrowser.Controller.Entities
return GetItemLookupInfo<PersonLookupInfo>();
}
+ public IEnumerable<BaseItem> GetTaggedItems(InternalItemsQuery query)
+ {
+ query.Person = Name;
+
+ return LibraryManager.GetItemList(query);
+ }
+
/// <summary>
/// Returns the folder containing the item.
/// If the item is a folder, it returns the folder itself
diff --git a/MediaBrowser.Controller/Entities/Photo.cs b/MediaBrowser.Controller/Entities/Photo.cs
index 3358ccc6f..804ea04a5 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; }
@@ -51,6 +51,12 @@ namespace MediaBrowser.Controller.Entities
}
}
+ [IgnoreDataMember]
+ public override bool EnableForceSaveOnDateModifiedChange
+ {
+ get { return true; }
+ }
+
public override bool CanDownload()
{
return true;
diff --git a/MediaBrowser.Controller/Entities/PhotoAlbum.cs b/MediaBrowser.Controller/Entities/PhotoAlbum.cs
index c8ab67a69..b0ddcfb8c 100644
--- a/MediaBrowser.Controller/Entities/PhotoAlbum.cs
+++ b/MediaBrowser.Controller/Entities/PhotoAlbum.cs
@@ -8,15 +8,6 @@ namespace MediaBrowser.Controller.Entities
public class PhotoAlbum : Folder
{
[IgnoreDataMember]
- public override bool SupportsLocalMetadata
- {
- get
- {
- return false;
- }
- }
-
- [IgnoreDataMember]
public override bool AlwaysScanInternalMetadataPath
{
get
diff --git a/MediaBrowser.Controller/Entities/Studio.cs b/MediaBrowser.Controller/Entities/Studio.cs
index a55527f37..762798b55 100644
--- a/MediaBrowser.Controller/Entities/Studio.cs
+++ b/MediaBrowser.Controller/Entities/Studio.cs
@@ -2,21 +2,29 @@
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
{
- /// <summary>
- /// Gets the user data key.
- /// </summary>
- /// <returns>System.String.</returns>
- protected override string CreateUserDataKey()
+ public override List<string> GetUserDataKeys()
{
- return "Studio-" + Name;
+ var list = base.GetUserDataKeys();
+
+ list.Insert(0, GetType().Name + "-" + (Name ?? string.Empty).RemoveDiacritics());
+ return list;
+ }
+
+ public override string PresentationUniqueKey
+ {
+ get
+ {
+ return GetUserDataKeys()[0];
+ }
}
/// <summary>
@@ -66,6 +74,13 @@ namespace MediaBrowser.Controller.Entities
return i => i.Studios.Contains(Name, StringComparer.OrdinalIgnoreCase);
}
+ public IEnumerable<BaseItem> GetTaggedItems(InternalItemsQuery query)
+ {
+ query.Studios = new[] { Name };
+
+ return LibraryManager.GetItemList(query);
+ }
+
[IgnoreDataMember]
public override bool SupportsPeople
{
diff --git a/MediaBrowser.Controller/Entities/TV/Episode.cs b/MediaBrowser.Controller/Entities/TV/Episode.cs
index 88dae3c7c..726390f65 100644
--- a/MediaBrowser.Controller/Entities/TV/Episode.cs
+++ b/MediaBrowser.Controller/Entities/TV/Episode.cs
@@ -11,8 +11,19 @@ namespace MediaBrowser.Controller.Entities.TV
/// <summary>
/// Class Episode
/// </summary>
- public class Episode : Video, IHasLookupInfo<EpisodeInfo>, IHasSeries
- {
+ 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>
@@ -42,7 +53,16 @@ namespace MediaBrowser.Controller.Entities.TV
/// This is the ending episode number for double episodes.
/// </summary>
/// <value>The index number.</value>
- public int? IndexNumberEnd { get; set; }
+ public int? IndexNumberEnd { get; set; }
+
+ [IgnoreDataMember]
+ public string SeriesSortName { get; set; }
+
+ public string FindSeriesSortName()
+ {
+ var series = Series;
+ return series == null ? SeriesSortName : series.SortName;
+ }
[IgnoreDataMember]
protected override bool SupportsOwnedItems
@@ -58,60 +78,54 @@ namespace MediaBrowser.Controller.Entities.TV
{
get
{
- return AirsAfterSeasonNumber ?? AirsBeforeSeasonNumber ?? PhysicalSeasonNumber;
+ return AirsAfterSeasonNumber ?? AirsBeforeSeasonNumber ?? ParentIndexNumber;
}
}
[IgnoreDataMember]
- public int? PhysicalSeasonNumber
+ public override Folder LatestItemsIndexContainer
{
get
{
- var value = ParentIndexNumber;
-
- if (value.HasValue)
- {
- return value;
- }
-
- var season = Season;
-
- return season != null ? season.IndexNumber : null;
+ return Series;
}
}
[IgnoreDataMember]
- public override Folder LatestItemsIndexContainer
+ public override Guid? DisplayParentId
{
get
{
- return Series;
+ return SeasonId;
}
}
[IgnoreDataMember]
- public override BaseItem DisplayParent
+ protected override bool EnableDefaultVideoUserDataKeys
{
get
{
- return Season ?? GetParent();
+ return false;
}
}
- /// <summary>
- /// Gets the user data key.
- /// </summary>
- /// <returns>System.String.</returns>
- protected override string CreateUserDataKey()
+ public override List<string> GetUserDataKeys()
{
- var series = Series;
+ var list = base.GetUserDataKeys();
+ var series = Series;
if (series != null && ParentIndexNumber.HasValue && IndexNumber.HasValue)
{
- return series.GetUserDataKey() + 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 base.CreateUserDataKey();
+ return list;
}
/// <summary>
@@ -160,13 +174,27 @@ namespace MediaBrowser.Controller.Entities.TV
}
[IgnoreDataMember]
- public string SeriesName
- {
- get
- {
- var series = Series;
- return series == null ? null : series.Name;
- }
+ public string SeriesName { get; set; }
+
+ [IgnoreDataMember]
+ public string SeasonName { get; set; }
+
+ public string FindSeasonName()
+ {
+ var season = Season;
+ return season == null ? SeasonName : season.Name;
+ }
+
+ public string FindSeriesName()
+ {
+ var series = Series;
+ return series == null ? SeriesName : series.Name;
+ }
+
+ public Guid? FindSeasonId()
+ {
+ var season = Season;
+ return season == null ? (Guid?)null : season.Id;
}
/// <summary>
@@ -175,7 +203,7 @@ namespace MediaBrowser.Controller.Entities.TV
/// <returns>System.String.</returns>
protected override string CreateSortName()
{
- return (ParentIndexNumber != null ? ParentIndexNumber.Value.ToString("000-") : "")
+ return (ParentIndexNumber != null ? ParentIndexNumber.Value.ToString("000 - ") : "")
+ (IndexNumber != null ? IndexNumber.Value.ToString("0000 - ") : "") + Name;
}
@@ -223,32 +251,20 @@ namespace MediaBrowser.Controller.Entities.TV
}
[IgnoreDataMember]
- public bool IsUnaired
- {
- get { return PremiereDate.HasValue && PremiereDate.Value.ToLocalTime().Date >= DateTime.Now.Date; }
- }
-
- [IgnoreDataMember]
public bool IsVirtualUnaired
{
get { return LocationType == LocationType.Virtual && IsUnaired; }
}
[IgnoreDataMember]
- public Guid? SeasonId
- {
- get
- {
- // First see if the parent is a Season
- var season = Season;
-
- if (season != null)
- {
- return season.Id;
- }
-
- return null;
- }
+ public Guid? SeasonId { get; set; }
+ [IgnoreDataMember]
+ public Guid? SeriesId { get; set; }
+
+ public Guid? FindSeriesId()
+ {
+ var series = Series;
+ return series == null ? (Guid?)null : series.Id;
}
public override IEnumerable<Guid> GetAncestorIds()
@@ -287,7 +303,9 @@ namespace MediaBrowser.Controller.Entities.TV
id.AnimeSeriesIndex = series.AnimeSeriesIndex;
}
+ id.IsMissingEpisode = IsMissingEpisode;
id.IndexNumberEnd = IndexNumberEnd;
+ id.IsVirtualUnaired = IsVirtualUnaired;
return id;
}
@@ -308,6 +326,19 @@ namespace MediaBrowser.Controller.Entities.TV
Logger.ErrorException("Error in FillMissingEpisodeNumbersFromPath. Episode: {0}", ex, Path ?? Name ?? Id.ToString());
}
+ if (!ParentIndexNumber.HasValue)
+ {
+ var season = Season;
+ if (season != null)
+ {
+ if (season.ParentIndexNumber.HasValue)
+ {
+ ParentIndexNumber = season.ParentIndexNumber;
+ hasChanges = true;
+ }
+ }
+ }
+
return hasChanges;
}
}
diff --git a/MediaBrowser.Controller/Entities/TV/Season.cs b/MediaBrowser.Controller/Entities/TV/Season.cs
index 9efa609ef..ee01c60b1 100644
--- a/MediaBrowser.Controller/Entities/TV/Season.cs
+++ b/MediaBrowser.Controller/Entities/TV/Season.cs
@@ -33,35 +33,60 @@ namespace MediaBrowser.Controller.Entities.TV
}
[IgnoreDataMember]
- public override BaseItem DisplayParent
+ public override bool SupportsDateLastMediaAdded
{
- get { return Series ?? GetParent(); }
+ get
+ {
+ return true;
+ }
+ }
+
+ [IgnoreDataMember]
+ public override Guid? DisplayParentId
+ {
+ get
+ {
+ var series = Series;
+ return series == null ? ParentId : series.Id;
+ }
+ }
+
+ [IgnoreDataMember]
+ public string SeriesSortName { get; set; }
+
+ public string FindSeriesSortName()
+ {
+ var series = Series;
+ return series == null ? SeriesSortName : series.SortName;
}
// Genre, Rating and Stuido will all be the same
protected override IEnumerable<string> GetIndexByOptions()
{
- return new List<string> {
- {"None"},
+ return new List<string> {
+ {"None"},
{"Performer"},
{"Director"},
{"Year"},
};
}
- /// <summary>
- /// Gets the user data key.
- /// </summary>
- /// <returns>System.String.</returns>
- protected override string CreateUserDataKey()
+ public override List<string> GetUserDataKeys()
{
- if (Series != null)
+ var list = base.GetUserDataKeys();
+
+ var series = Series;
+ if (series != null)
{
- var seasonNo = IndexNumber ?? 0;
- return Series.GetUserDataKey() + seasonNo.ToString("000");
+ list.InsertRange(0, series.GetUserDataKeys().Select(i => i + (IndexNumber ?? 0).ToString("000")));
}
- return base.CreateUserDataKey();
+ return list;
+ }
+
+ public override int GetChildCount(User user)
+ {
+ return GetChildren(user, true).Count();
}
/// <summary>
@@ -90,6 +115,24 @@ namespace MediaBrowser.Controller.Entities.TV
}
}
+ [IgnoreDataMember]
+ public override string PresentationUniqueKey
+ {
+ get
+ {
+ if (IndexNumber.HasValue)
+ {
+ var series = Series;
+ if (series != null)
+ {
+ return series.PresentationUniqueKey + "-" + (IndexNumber ?? 0).ToString("000");
+ }
+ }
+
+ return base.PresentationUniqueKey;
+ }
+ }
+
/// <summary>
/// Creates the name of the sort.
/// </summary>
@@ -102,25 +145,13 @@ namespace MediaBrowser.Controller.Entities.TV
[IgnoreDataMember]
public bool IsMissingSeason
{
- get { return LocationType == LocationType.Virtual && GetEpisodes().All(i => i.IsMissingEpisode); }
- }
-
- [IgnoreDataMember]
- public bool IsUnaired
- {
- get { return GetEpisodes().All(i => i.IsUnaired); }
+ get { return (IsVirtualItem) && !IsUnaired; }
}
[IgnoreDataMember]
public bool IsVirtualUnaired
{
- get { return LocationType == LocationType.Virtual && IsUnaired; }
- }
-
- [IgnoreDataMember]
- public bool IsMissingOrVirtualUnaired
- {
- get { return LocationType == LocationType.Virtual && GetEpisodes().All(i => i.IsVirtualUnaired || i.IsMissingEpisode); }
+ get { return (IsVirtualItem) && IsUnaired; }
}
[IgnoreDataMember]
@@ -129,24 +160,18 @@ namespace MediaBrowser.Controller.Entities.TV
get { return (IndexNumber ?? -1) == 0; }
}
- public override Task<QueryResult<BaseItem>> GetItems(InternalItemsQuery query)
+ protected override Task<QueryResult<BaseItem>> GetItemsInternal(InternalItemsQuery query)
{
+ if (query.User == null)
+ {
+ return base.GetItemsInternal(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);
- }
- else
- {
- items = GetEpisodes(query.User).Where(filter);
- }
+ var items = GetEpisodes(user).Where(filter);
var result = PostFilterAndSort(items, query);
@@ -162,55 +187,20 @@ 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 episodes = GetRecursiveChildren(user)
- .OfType<Episode>();
-
- var series = Series;
-
- if (IndexNumber.HasValue && series != null)
- {
- return series.GetEpisodes(user, IndexNumber.Value, includeMissingEpisodes, includeVirtualUnairedEpisodes, episodes);
- }
-
- 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);
}
- private IEnumerable<Episode> GetEpisodes()
+ public IEnumerable<Episode> GetEpisodes()
{
var episodes = GetRecursiveChildren().OfType<Episode>();
var series = Series;
@@ -254,13 +244,21 @@ namespace MediaBrowser.Controller.Entities.TV
}
[IgnoreDataMember]
- public string SeriesName
+ public string SeriesName { get; set; }
+
+ [IgnoreDataMember]
+ public Guid? SeriesId { get; set; }
+
+ public string FindSeriesName()
{
- get
- {
- var series = Series;
- return series == null ? null : series.Name;
- }
+ var series = Series;
+ return series == null ? SeriesName : series.Name;
+ }
+
+ public Guid? FindSeriesId()
+ {
+ var series = Series;
+ return series == null ? (Guid?)null : series.Id;
}
/// <summary>
@@ -290,19 +288,14 @@ namespace MediaBrowser.Controller.Entities.TV
{
var hasChanges = base.BeforeMetadataRefresh();
- var locationType = LocationType;
-
- if (locationType == LocationType.FileSystem || locationType == LocationType.Offline)
+ if (!IndexNumber.HasValue && !string.IsNullOrEmpty(Path))
{
- if (!IndexNumber.HasValue && !string.IsNullOrEmpty(Path))
- {
- IndexNumber = IndexNumber ?? LibraryManager.GetSeasonNumberFromPath(Path);
+ IndexNumber = IndexNumber ?? LibraryManager.GetSeasonNumberFromPath(Path);
- // If a change was made record it
- if (IndexNumber.HasValue)
- {
- hasChanges = true;
- }
+ // If a change was made record it
+ if (IndexNumber.HasValue)
+ {
+ hasChanges = true;
}
}
diff --git a/MediaBrowser.Controller/Entities/TV/Series.cs b/MediaBrowser.Controller/Entities/TV/Series.cs
index aa07ab378..ad35b3d36 100644
--- a/MediaBrowser.Controller/Entities/TV/Series.cs
+++ b/MediaBrowser.Controller/Entities/TV/Series.cs
@@ -9,6 +9,8 @@ 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
{
@@ -19,8 +21,6 @@ namespace MediaBrowser.Controller.Entities.TV
{
public List<Guid> SpecialFeatureIds { get; set; }
- public string OriginalTitle { get; set; }
-
public int? AnimeSeriesIndex { get; set; }
public Series()
@@ -31,7 +31,6 @@ namespace MediaBrowser.Controller.Entities.TV
RemoteTrailers = new List<MediaUrl>();
LocalTrailerIds = new List<Guid>();
RemoteTrailerIds = new List<Guid>();
- DisplaySpecialsWithSeasons = true;
}
[IgnoreDataMember]
@@ -49,7 +48,14 @@ namespace MediaBrowser.Controller.Entities.TV
}
}
- public bool DisplaySpecialsWithSeasons { get; set; }
+ [IgnoreDataMember]
+ public override bool SupportsDateLastMediaAdded
+ {
+ get
+ {
+ return true;
+ }
+ }
public List<Guid> LocalTrailerIds { get; set; }
public List<Guid> RemoteTrailerIds { get; set; }
@@ -86,32 +92,69 @@ namespace MediaBrowser.Controller.Entities.TV
{
get
{
- return GetRecursiveChildren(i => i is Episode)
- .Select(i => i.DateCreated)
- .OrderByDescending(i => i)
- .FirstOrDefault();
+ return DateLastMediaAdded ?? DateTime.MinValue;
}
}
+ [IgnoreDataMember]
+ public override string PresentationUniqueKey
+ {
+ get
+ {
+ var userdatakeys = GetUserDataKeys();
+
+ if (userdatakeys.Count > 1)
+ {
+ return userdatakeys[0];
+ }
+ return base.PresentationUniqueKey;
+ }
+ }
+
+ private static string GetUniqueSeriesKey(BaseItem series)
+ {
+ if (ConfigurationManager.Configuration.SchemaVersion < 97)
+ {
+ return series.Id.ToString("N");
+ }
+ return series.PresentationUniqueKey;
+ }
+
+ public override int GetChildCount(User user)
+ {
+ var result = LibraryManager.GetItemsResult(new InternalItemsQuery(user)
+ {
+ AncestorWithPresentationUniqueKey = GetUniqueSeriesKey(this),
+ IncludeItemTypes = new[] { typeof(Season).Name },
+ SortBy = new[] { ItemSortBy.SortName },
+ IsVirtualItem = false,
+ Limit = 0
+ });
+
+ return result.TotalRecordCount;
+ }
+
/// <summary>
/// Gets the user data key.
/// </summary>
/// <returns>System.String.</returns>
- protected override string CreateUserDataKey()
+ public override List<string> GetUserDataKeys()
{
- var key = this.GetProviderId(MetadataProviders.Tvdb);
+ var list = base.GetUserDataKeys();
- if (string.IsNullOrWhiteSpace(key))
+ var key = this.GetProviderId(MetadataProviders.Imdb);
+ if (!string.IsNullOrWhiteSpace(key))
{
- key = this.GetProviderId(MetadataProviders.Imdb);
+ list.Insert(0, key);
}
- if (string.IsNullOrWhiteSpace(key))
+ key = this.GetProviderId(MetadataProviders.Tvdb);
+ if (!string.IsNullOrWhiteSpace(key))
{
- key = base.CreateUserDataKey();
+ list.Insert(0, key);
}
- return key;
+ return list;
}
/// <summary>
@@ -128,8 +171,8 @@ namespace MediaBrowser.Controller.Entities.TV
// Studio, Genre and Rating will all be the same so makes no sense to index by these
protected override IEnumerable<string> GetIndexByOptions()
{
- return new List<string> {
- {"None"},
+ return new List<string> {
+ {"None"},
{"Performer"},
{"Director"},
{"Year"},
@@ -157,56 +200,59 @@ namespace MediaBrowser.Controller.Entities.TV
return GetSeasons(user, config.DisplayMissingEpisodes, config.DisplayUnairedEpisodes);
}
- public override Task<QueryResult<BaseItem>> GetItems(InternalItemsQuery query)
+ 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
- ? GetRecursiveChildren(user, filter)
- : GetSeasons(user).Where(filter);
+ query.AncestorWithPresentationUniqueKey = GetUniqueSeriesKey(this);
+ 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);
}
public IEnumerable<Season> GetSeasons(User user, bool includeMissingSeasons, bool includeVirtualUnaired)
{
- var seasons = base.GetChildren(user, true)
- .OfType<Season>();
+ IEnumerable<Season> seasons;
+
+ seasons = LibraryManager.GetItemList(new InternalItemsQuery(user)
+ {
+ AncestorWithPresentationUniqueKey = GetUniqueSeriesKey(this),
+ IncludeItemTypes = new[] { typeof(Season).Name },
+ SortBy = new[] { ItemSortBy.SortName }
+
+ }).Cast<Season>();
- if (!includeMissingSeasons && !includeVirtualUnaired)
+ if (!includeMissingSeasons)
{
- seasons = seasons.Where(i => !i.IsMissingOrVirtualUnaired);
+ seasons = seasons.Where(i => !(i.IsMissingSeason));
}
- else
+ if (!includeVirtualUnaired)
{
- if (!includeMissingSeasons)
- {
- seasons = seasons.Where(i => !i.IsMissingSeason);
- }
- if (!includeVirtualUnaired)
- {
- seasons = seasons.Where(i => !i.IsVirtualUnaired);
- }
+ seasons = seasons.Where(i => !i.IsVirtualUnaired);
}
- return LibraryManager
- .Sort(seasons, user, new[] { ItemSortBy.SortName }, SortOrder.Ascending)
- .Cast<Season>();
+ return seasons;
}
public IEnumerable<Episode> GetEpisodes(User user)
@@ -218,25 +264,26 @@ 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 = GetUniqueSeriesKey(this),
+ 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();
// Specials could appear twice based on above - once in season 0, once in the aired season
// This depends on settings for that series
// When this happens, remove the duplicate from season 0
- var returnList = new List<Episode>();
-
- foreach (var episode in allEpisodes)
- {
- if (!returnList.Contains(episode))
- {
- returnList.Insert(0, episode);
- }
- }
- return returnList;
+ return allEpisodes.DistinctBy(i => i.Id).Reverse();
}
public async Task RefreshAllMetadata(MetadataRefreshOptions refreshOptions, IProgress<double> progress, CancellationToken cancellationToken)
@@ -251,13 +298,10 @@ 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);
- // Refresh TV
+ // Refresh seasons
foreach (var item in seasons)
{
cancellationToken.ThrowIfCancellationRequested();
@@ -270,12 +314,30 @@ namespace MediaBrowser.Controller.Entities.TV
progress.Report(percent * 100);
}
- // Refresh all non-songs
+ // Refresh episodes and other children
foreach (var item in otherItems)
{
cancellationToken.ThrowIfCancellationRequested();
- await item.RefreshMetadata(refreshOptions, cancellationToken).ConfigureAwait(false);
+ var skipItem = false;
+
+ var episode = item as Episode;
+
+ if (episode != null
+ && refreshOptions.MetadataRefreshMode != MetadataRefreshMode.FullRefresh
+ && !refreshOptions.ReplaceAllMetadata
+ && episode.IsMissingEpisode
+ && episode.LocationType == LocationType.Virtual
+ && episode.PremiereDate.HasValue
+ && (DateTime.UtcNow - episode.PremiereDate.Value).TotalDays > 30)
+ {
+ skipItem = true;
+ }
+
+ if (!skipItem)
+ {
+ await item.RefreshMetadata(refreshOptions, cancellationToken).ConfigureAwait(false);
+ }
numComplete++;
double percent = numComplete;
@@ -283,32 +345,46 @@ 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);
}
- public IEnumerable<Episode> GetEpisodes(User user, int seasonNumber)
+ public IEnumerable<Episode> GetEpisodes(User user, Season season)
{
var config = user.Configuration;
- return GetEpisodes(user, seasonNumber, config.DisplayMissingEpisodes, config.DisplayUnairedEpisodes);
+ return GetEpisodes(user, season, config.DisplayMissingEpisodes, config.DisplayUnairedEpisodes);
}
- public IEnumerable<Episode> GetEpisodes(User user, int seasonNumber, bool includeMissingEpisodes, bool includeVirtualUnairedEpisodes)
+ private IEnumerable<Episode> GetAllEpisodes(User user)
{
- return GetEpisodes(user, seasonNumber, includeMissingEpisodes, includeVirtualUnairedEpisodes,
- new List<Episode>());
+ return LibraryManager.GetItemList(new InternalItemsQuery(user)
+ {
+ AncestorWithPresentationUniqueKey = GetUniqueSeriesKey(this),
+ IncludeItemTypes = new[] { typeof(Episode).Name },
+ SortBy = new[] { ItemSortBy.SortName }
+
+ }).Cast<Episode>();
}
- internal IEnumerable<Episode> GetEpisodes(User user, int seasonNumber, bool includeMissingEpisodes, bool includeVirtualUnairedEpisodes, IEnumerable<Episode> additionalEpisodes)
+ public IEnumerable<Episode> GetEpisodes(User user, Season parentSeason, bool includeMissingEpisodes, bool includeVirtualUnairedEpisodes)
{
- var episodes = GetRecursiveChildren(user, i => i is Episode)
- .Cast<Episode>();
+ IEnumerable<Episode> episodes = GetAllEpisodes(user);
- episodes = FilterEpisodesBySeason(episodes, seasonNumber, DisplaySpecialsWithSeasons);
+ return GetEpisodes(user, parentSeason, includeMissingEpisodes, includeVirtualUnairedEpisodes, episodes);
+ }
- episodes = episodes.Concat(additionalEpisodes).Distinct();
+ public IEnumerable<Episode> GetEpisodes(User user, Season parentSeason, bool includeMissingEpisodes, bool includeVirtualUnairedEpisodes, IEnumerable<Episode> allSeriesEpisodes)
+ {
+ if (allSeriesEpisodes == null)
+ {
+ return GetEpisodes(user, parentSeason, includeMissingEpisodes, includeVirtualUnairedEpisodes);
+ }
+
+ var episodes = FilterEpisodesBySeason(allSeriesEpisodes, parentSeason, ConfigurationManager.Configuration.DisplaySpecialsWithinSeasons);
if (!includeMissingEpisodes)
{
@@ -319,7 +395,7 @@ namespace MediaBrowser.Controller.Entities.TV
episodes = episodes.Where(i => !i.IsVirtualUnaired);
}
- var sortBy = seasonNumber == 0 ? ItemSortBy.SortName : ItemSortBy.AiredEpisodeOrder;
+ var sortBy = (parentSeason.IndexNumber ?? -1) == 0 ? ItemSortBy.SortName : ItemSortBy.AiredEpisodeOrder;
return LibraryManager.Sort(episodes, user, new[] { sortBy }, SortOrder.Ascending)
.Cast<Episode>();
@@ -328,15 +404,11 @@ namespace MediaBrowser.Controller.Entities.TV
/// <summary>
/// Filters the episodes by season.
/// </summary>
- /// <param name="episodes">The episodes.</param>
- /// <param name="seasonNumber">The season number.</param>
- /// <param name="includeSpecials">if set to <c>true</c> [include specials].</param>
- /// <returns>IEnumerable{Episode}.</returns>
public static IEnumerable<Episode> FilterEpisodesBySeason(IEnumerable<Episode> episodes, int seasonNumber, bool includeSpecials)
{
if (!includeSpecials || seasonNumber < 1)
{
- return episodes.Where(i => (i.PhysicalSeasonNumber ?? -1) == seasonNumber);
+ return episodes.Where(i => (i.ParentIndexNumber ?? -1) == seasonNumber);
}
return episodes.Where(i =>
@@ -354,6 +426,34 @@ namespace MediaBrowser.Controller.Entities.TV
});
}
+ /// <summary>
+ /// Filters the episodes by season.
+ /// </summary>
+ public static IEnumerable<Episode> FilterEpisodesBySeason(IEnumerable<Episode> episodes, Season parentSeason, bool includeSpecials)
+ {
+ var seasonNumber = parentSeason.IndexNumber;
+ var seasonPresentationKey = GetUniqueSeriesKey(parentSeason);
+
+ var supportSpecialsInSeason = includeSpecials && seasonNumber.HasValue && seasonNumber.Value != 0;
+
+ return episodes.Where(episode =>
+ {
+ var currentSeasonNumber = supportSpecialsInSeason ? episode.AiredSeasonNumber : episode.ParentIndexNumber;
+ if (currentSeasonNumber.HasValue && seasonNumber.HasValue && currentSeasonNumber.Value == seasonNumber.Value)
+ {
+ return true;
+ }
+
+ if (!currentSeasonNumber.HasValue && !seasonNumber.HasValue && parentSeason.LocationType == LocationType.Virtual)
+ {
+ return true;
+ }
+
+ var season = episode.Season;
+ return season != null && string.Equals(GetUniqueSeriesKey(season), seasonPresentationKey, StringComparison.OrdinalIgnoreCase);
+ });
+ }
+
protected override bool GetBlockUnratedValue(UserPolicy config)
{
return config.BlockUnratedItems.Contains(UnratedItem.Series);
@@ -392,5 +492,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 bd7d30858..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, IHasLookupInfo<TrailerInfo>
+ public class Trailer : Video, IHasCriticRating, IHasProductionLocations, IHasBudget, IHasTaglines, IHasMetascore, IHasOriginalTitle, IHasLookupInfo<TrailerInfo>
{
public List<string> ProductionLocations { get; set; }
@@ -21,17 +22,15 @@ namespace MediaBrowser.Controller.Entities
Taglines = new List<string>();
Keywords = new List<string>();
ProductionLocations = new List<string>();
- TrailerTypes = new List<TrailerType>();
+ TrailerTypes = new List<TrailerType> { TrailerType.LocalTrailer };
}
public List<TrailerType> TrailerTypes { get; set; }
-
+
public float? Metascore { get; set; }
public List<MediaUrl> RemoteTrailers { get; set; }
- public List<string> Keywords { get; set; }
-
[IgnoreDataMember]
public bool IsLocalTrailer
{
@@ -56,26 +55,6 @@ namespace MediaBrowser.Controller.Entities
/// <value>The revenue.</value>
public double? Revenue { get; set; }
- protected override string CreateUserDataKey()
- {
- var key = Movie.GetMovieUserDataKey(this);
-
- if (!string.IsNullOrWhiteSpace(key))
- {
- key = key + "-trailer";
-
- // Make sure different trailers have their own data.
- if (RunTimeTicks.HasValue)
- {
- key += "-" + RunTimeTicks.Value.ToString(CultureInfo.InvariantCulture);
- }
-
- return key;
- }
-
- return base.CreateUserDataKey();
- }
-
public override UnratedItem GetBlockUnratedType()
{
return UnratedItem.Trailer;
@@ -86,7 +65,7 @@ namespace MediaBrowser.Controller.Entities
var info = GetItemLookupInfo<TrailerInfo>();
info.IsLocalTrailer = TrailerTypes.Contains(TrailerType.LocalTrailer);
-
+
if (!IsInMixedFolder)
{
info.Name = System.IO.Path.GetFileName(ContainingFolderPath);
@@ -130,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/User.cs b/MediaBrowser.Controller/Entities/User.cs
index e5b39003d..5c68308f5 100644
--- a/MediaBrowser.Controller/Entities/User.cs
+++ b/MediaBrowser.Controller/Entities/User.cs
@@ -305,14 +305,7 @@ namespace MediaBrowser.Controller.Entities
public bool IsFolderGrouped(Guid id)
{
- var config = Configuration;
-
- if (config.ExcludeFoldersFromGrouping != null)
- {
- return !config.ExcludeFoldersFromGrouping.Select(i => new Guid(i)).Contains(id);
- }
-
- return config.GroupedFolders.Select(i => new Guid(i)).Contains(id);
+ return Configuration.GroupedFolders.Select(i => new Guid(i)).Contains(id);
}
[IgnoreDataMember]
diff --git a/MediaBrowser.Controller/Entities/UserItemData.cs b/MediaBrowser.Controller/Entities/UserItemData.cs
index 16c37e7d3..f95fd7036 100644
--- a/MediaBrowser.Controller/Entities/UserItemData.cs
+++ b/MediaBrowser.Controller/Entities/UserItemData.cs
@@ -88,6 +88,8 @@ namespace MediaBrowser.Controller.Entities
/// </summary>
/// <value>The index of the subtitle stream.</value>
public int? SubtitleStreamIndex { get; set; }
+
+ public const double MinLikeValue = 6.5;
/// <summary>
/// This is an interpreted property to indicate likes or dislikes
@@ -101,7 +103,7 @@ namespace MediaBrowser.Controller.Entities
{
if (Rating != null)
{
- return Rating >= 6.5;
+ return Rating >= MinLikeValue;
}
return null;
diff --git a/MediaBrowser.Controller/Entities/UserRootFolder.cs b/MediaBrowser.Controller/Entities/UserRootFolder.cs
index 104408860..8e6f11c2c 100644
--- a/MediaBrowser.Controller/Entities/UserRootFolder.cs
+++ b/MediaBrowser.Controller/Entities/UserRootFolder.cs
@@ -8,6 +8,7 @@ using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
+using MediaBrowser.Controller.Library;
namespace MediaBrowser.Controller.Entities
{
@@ -17,7 +18,7 @@ namespace MediaBrowser.Controller.Entities
/// </summary>
public class UserRootFolder : Folder
{
- public override async Task<QueryResult<BaseItem>> GetItems(InternalItemsQuery query)
+ protected override async Task<QueryResult<BaseItem>> GetItemsInternal(InternalItemsQuery query)
{
if (query.Recursive)
{
@@ -37,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
{
@@ -63,15 +69,6 @@ namespace MediaBrowser.Controller.Entities
return list;
}
- /// <summary>
- /// Get the children of this folder from the actual file system
- /// </summary>
- /// <returns>IEnumerable{BaseItem}.</returns>
- protected override IEnumerable<BaseItem> GetNonCachedChildren(IDirectoryService directoryService)
- {
- return base.GetNonCachedChildren(directoryService);
- }
-
public override bool BeforeMetadataRefresh()
{
var hasChanges = base.BeforeMetadataRefresh();
@@ -101,10 +98,5 @@ namespace MediaBrowser.Controller.Entities
LibraryManager.RegisterItem(item);
}
}
-
- public override void FillUserDataDtoValues(UserItemDataDto dto, UserItemData userData, User user)
- {
- // Nothing meaninful here and will only waste resources
- }
}
}
diff --git a/MediaBrowser.Controller/Entities/UserView.cs b/MediaBrowser.Controller/Entities/UserView.cs
index 41c19f11d..35375e7e6 100644
--- a/MediaBrowser.Controller/Entities/UserView.cs
+++ b/MediaBrowser.Controller/Entities/UserView.cs
@@ -45,7 +45,12 @@ namespace MediaBrowser.Controller.Entities
return list;
}
- public override Task<QueryResult<BaseItem>> GetItems(InternalItemsQuery query)
+ 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);
}
@@ -66,7 +71,8 @@ namespace MediaBrowser.Controller.Entities
{
var result = GetItems(new InternalItemsQuery
{
- User = user
+ User = user,
+ EnableTotalRecordCount = false
}).Result;
@@ -83,17 +89,19 @@ namespace MediaBrowser.Controller.Entities
return true;
}
- public override IEnumerable<BaseItem> GetRecursiveChildren(User user, Func<BaseItem, bool> filter)
+ public override IEnumerable<BaseItem> GetRecursiveChildren(User user, InternalItemsQuery query)
{
var result = GetItems(new InternalItemsQuery
{
User = user,
Recursive = true,
- Filter = filter
+ EnableTotalRecordCount = false,
+
+ ForceDirect = true
}).Result;
- return result.Items;
+ return result.Items.Where(i => UserViewBuilder.FilterItem(i, query));
}
protected override IEnumerable<BaseItem> GetEligibleChildrenForRecursiveChildren(User user)
diff --git a/MediaBrowser.Controller/Entities/UserViewBuilder.cs b/MediaBrowser.Controller/Entities/UserViewBuilder.cs
index 2df2227bc..e2228bcaf 100644
--- a/MediaBrowser.Controller/Entities/UserViewBuilder.cs
+++ b/MediaBrowser.Controller/Entities/UserViewBuilder.cs
@@ -18,6 +18,9 @@ using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
+using MediaBrowser.Controller.Configuration;
+using MediaBrowser.Model.Configuration;
+using MoreLinq;
namespace MediaBrowser.Controller.Entities
{
@@ -30,10 +33,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 +45,7 @@ namespace MediaBrowser.Controller.Entities
_logger = logger;
_userDataManager = userDataManager;
_tvSeriesManager = tvSeriesManager;
- _collectionManager = collectionManager;
+ _config = config;
_playlistManager = playlistManager;
}
@@ -125,13 +128,17 @@ namespace MediaBrowser.Controller.Entities
case CollectionType.HomeVideos:
case CollectionType.Games:
case CollectionType.MusicVideos:
- {
- if (query.Recursive)
{
- return GetResult(queryParent.GetRecursiveChildren(user, true), queryParent, query);
+ if (query.Recursive)
+ {
+ query.Recursive = true;
+ query.ParentId = queryParent.Id;
+ query.SetUser(user);
+
+ return _libraryManager.GetItemsResult(query);
+ }
+ return GetResult(queryParent.GetChildren(user, true), queryParent, query);
}
- return GetResult(queryParent.GetChildren(user, true), queryParent, query);
- }
case CollectionType.Folders:
return GetResult(user.RootFolder.GetChildren(user, true), queryParent, query);
@@ -140,7 +147,7 @@ namespace MediaBrowser.Controller.Entities
return await GetPlaylistsView(queryParent, user, query).ConfigureAwait(false);
case CollectionType.BoxSets:
- return await GetBoxsetView(queryParent, user, query).ConfigureAwait(false);
+ return GetBoxsetView(queryParent, user, query);
case CollectionType.TvShows:
return await GetTvView(queryParent, user, query).ConfigureAwait(false);
@@ -155,7 +162,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);
@@ -206,7 +213,7 @@ namespace MediaBrowser.Controller.Entities
return GetMusicLatest(queryParent, user, query);
case SpecialFolder.MusicPlaylists:
- return await GetMusicPlaylists(queryParent, user, query).ConfigureAwait(false);
+ return GetMusicPlaylists(queryParent, user, query);
case SpecialFolder.MusicAlbums:
return GetMusicAlbums(queryParent, user, query);
@@ -250,9 +257,15 @@ namespace MediaBrowser.Controller.Entities
{
if (query.Recursive)
{
- var items = GetRecursiveChildren(parent, user, new[] { CollectionType.Music, CollectionType.MusicVideos }, i => FilterItem(i, query));
+ query.Recursive = true;
+ query.SetUser(user);
- return PostFilterAndSort(items, parent, null, query);
+ if (query.IncludeItemTypes.Length == 0)
+ {
+ query.IncludeItemTypes = new[] { typeof(MusicArtist).Name, typeof(MusicAlbum).Name, typeof(Audio.Audio).Name, typeof(MusicVideo).Name };
+ }
+
+ return parent.QueryRecursive(query);
}
var list = new List<BaseItem>();
@@ -310,21 +323,26 @@ namespace MediaBrowser.Controller.Entities
private async Task<QueryResult<BaseItem>> GetMusicGenreItems(Folder queryParent, Folder displayParent, User user, InternalItemsQuery query)
{
- var items = GetRecursiveChildren(queryParent, user, new[] { CollectionType.Music, CollectionType.MusicVideos })
- .Where(i => !i.IsFolder)
- .Where(i => i.Genres.Contains(displayParent.Name, StringComparer.OrdinalIgnoreCase))
- .OfType<IHasAlbumArtist>();
+ query.Recursive = true;
+ query.ParentId = queryParent.Id;
+ query.Genres = new[] { displayParent.Name };
+ query.SetUser(user);
- var artists = _libraryManager.GetAlbumArtists(items);
+ query.IncludeItemTypes = new[] { typeof(MusicAlbum).Name };
- return GetResult(artists, queryParent, query);
+ return _libraryManager.GetItemsResult(query);
}
private QueryResult<BaseItem> GetMusicAlbumArtists(Folder parent, User user, InternalItemsQuery query)
{
- var items = GetRecursiveChildren(parent, user, new[] { CollectionType.Music, CollectionType.MusicVideos })
- .Where(i => !i.IsFolder)
- .OfType<IHasAlbumArtist>();
+ var items = parent.QueryRecursive(new InternalItemsQuery(user)
+ {
+ Recursive = true,
+ ParentId = parent.Id,
+ IncludeItemTypes = new[] { typeof(Audio.Audio).Name },
+ EnableTotalRecordCount = false
+
+ }).Items.Cast<IHasAlbumArtist>();
var artists = _libraryManager.GetAlbumArtists(items);
@@ -333,9 +351,14 @@ namespace MediaBrowser.Controller.Entities
private QueryResult<BaseItem> GetMusicArtists(Folder parent, User user, InternalItemsQuery query)
{
- var items = GetRecursiveChildren(parent, user, new[] { CollectionType.Music, CollectionType.MusicVideos })
- .Where(i => !i.IsFolder)
- .OfType<IHasArtist>();
+ var items = parent.QueryRecursive(new InternalItemsQuery(user)
+ {
+ Recursive = true,
+ ParentId = parent.Id,
+ IncludeItemTypes = new[] { typeof(Audio.Audio).Name, typeof(MusicVideo).Name },
+ EnableTotalRecordCount = false
+
+ }).Items.Cast<IHasArtist>();
var artists = _libraryManager.GetArtists(items);
@@ -344,35 +367,50 @@ namespace MediaBrowser.Controller.Entities
private QueryResult<BaseItem> GetFavoriteArtists(Folder parent, User user, InternalItemsQuery query)
{
- var items = GetRecursiveChildren(parent, user, new[] { CollectionType.Music, CollectionType.MusicVideos })
- .Where(i => !i.IsFolder)
- .OfType<IHasAlbumArtist>();
+ var items = parent.QueryRecursive(new InternalItemsQuery(user)
+ {
+ Recursive = true,
+ ParentId = parent.Id,
+ IncludeItemTypes = new[] { typeof(Audio.Audio).Name },
+ EnableTotalRecordCount = false
- var artists = _libraryManager.GetAlbumArtists(items).Where(i => _userDataManager.GetUserData(user.Id, i.GetUserDataKey()).IsFavorite);
+ }).Items.Cast<IHasAlbumArtist>();
+
+ var artists = _libraryManager.GetAlbumArtists(items).Where(i => _userDataManager.GetUserData(user, i).IsFavorite);
return GetResult(artists, parent, query);
}
- private Task<QueryResult<BaseItem>> GetMusicPlaylists(Folder parent, User user, InternalItemsQuery query)
+ private QueryResult<BaseItem> GetMusicPlaylists(Folder parent, User user, InternalItemsQuery query)
{
- query.IncludeItemTypes = new[] { "Playlist" };
+ query.ParentId = null;
+ query.IncludeItemTypes = new[] { typeof(Playlist).Name };
+ query.SetUser(user);
query.Recursive = true;
- return parent.GetItems(query);
+ return _libraryManager.GetItemsResult(query);
}
private QueryResult<BaseItem> GetMusicAlbums(Folder parent, User user, InternalItemsQuery query)
{
- var items = GetRecursiveChildren(parent, user, new[] { CollectionType.Music, CollectionType.MusicVideos }, i => i is MusicAlbum && FilterItem(i, query));
+ query.Recursive = true;
+ query.ParentId = parent.Id;
+ query.SetUser(user);
- return PostFilterAndSort(items, parent, null, query);
+ query.IncludeItemTypes = new[] { typeof(MusicAlbum).Name };
+
+ return _libraryManager.GetItemsResult(query);
}
private QueryResult<BaseItem> GetMusicSongs(Folder parent, User user, InternalItemsQuery query)
{
- var items = GetRecursiveChildren(parent, user, new[] { CollectionType.Music, CollectionType.MusicVideos }, i => i is Audio.Audio && FilterItem(i, query));
+ query.Recursive = true;
+ query.ParentId = parent.Id;
+ query.SetUser(user);
- return PostFilterAndSort(items, parent, null, query);
+ query.IncludeItemTypes = new[] { typeof(Audio.Audio).Name };
+
+ return _libraryManager.GetItemsResult(query);
}
private QueryResult<BaseItem> GetMusicLatest(Folder parent, User user, InternalItemsQuery query)
@@ -389,27 +427,29 @@ namespace MediaBrowser.Controller.Entities
query.SortBy = new string[] { };
- //var items = GetRecursiveChildren(parent, user, new[] { CollectionType.Music, CollectionType.MusicVideos }, i => i is MusicVideo || i is Audio.Audio && FilterItem(i, query));
-
return PostFilterAndSort(items, parent, null, query);
}
private QueryResult<BaseItem> GetFavoriteSongs(Folder parent, User user, InternalItemsQuery query)
{
+ query.Recursive = true;
+ query.ParentId = parent.Id;
+ query.SetUser(user);
query.IsFavorite = true;
+ query.IncludeItemTypes = new[] { typeof(Audio.Audio).Name };
- var items = GetRecursiveChildren(parent, user, new[] { CollectionType.Music }, i => i is Audio.Audio && FilterItem(i, query));
-
- return PostFilterAndSort(items, parent, null, query);
+ return _libraryManager.GetItemsResult(query);
}
private QueryResult<BaseItem> GetFavoriteAlbums(Folder parent, User user, InternalItemsQuery query)
{
+ query.Recursive = true;
+ query.ParentId = parent.Id;
+ query.SetUser(user);
query.IsFavorite = true;
+ query.IncludeItemTypes = new[] { typeof(MusicAlbum).Name };
- var items = GetRecursiveChildren(parent, user, new[] { CollectionType.Music }, i => i is MusicAlbum && FilterItem(i, query));
-
- return PostFilterAndSort(items, parent, null, query);
+ return _libraryManager.GetItemsResult(query);
}
private async Task<QueryResult<BaseItem>> FindPlaylists(Folder parent, User user, InternalItemsQuery query)
@@ -428,19 +468,15 @@ namespace MediaBrowser.Controller.Entities
{
if (query.Recursive)
{
- var recursiveItems = GetRecursiveChildren(parent, user,
- new[] { CollectionType.Movies, CollectionType.BoxSets, string.Empty })
- .Where(i => i is Movie || i is BoxSet);
+ query.Recursive = true;
+ query.SetUser(user);
- //var collections = _collectionManager.CollapseItemsWithinBoxSets(recursiveItems, user).ToList();
-
- //if (collections.Count > 0)
- //{
- // recursiveItems.AddRange(_collectionManager.CollapseItemsWithinBoxSets(recursiveItems, user));
- // recursiveItems = recursiveItems.DistinctBy(i => i.Id).ToList();
- //}
+ if (query.IncludeItemTypes.Length == 0)
+ {
+ query.IncludeItemTypes = new[] { typeof(Movie).Name, typeof(BoxSet).Name };
+ }
- return GetResult(recursiveItems, parent, query);
+ return parent.QueryRecursive(query);
}
var list = new List<BaseItem>();
@@ -457,43 +493,51 @@ namespace MediaBrowser.Controller.Entities
private QueryResult<BaseItem> GetFavoriteMovies(Folder parent, User user, InternalItemsQuery query)
{
+ query.Recursive = true;
+ query.ParentId = parent.Id;
+ query.SetUser(user);
query.IsFavorite = true;
+ query.IncludeItemTypes = new[] { typeof(Movie).Name };
- var items = GetRecursiveChildren(parent, user, new[] { CollectionType.Movies, CollectionType.BoxSets, string.Empty }, i => i is Movie && FilterItem(i, query));
-
- return PostFilterAndSort(items, parent, null, query);
+ return _libraryManager.GetItemsResult(query);
}
private QueryResult<BaseItem> GetFavoriteSeries(Folder parent, User user, InternalItemsQuery query)
{
+ query.Recursive = true;
+ query.ParentId = parent.Id;
+ query.SetUser(user);
query.IsFavorite = true;
+ query.IncludeItemTypes = new[] { typeof(Series).Name };
- var items = GetRecursiveChildren(parent, user, new[] { CollectionType.TvShows, string.Empty }, i => i is Series && FilterItem(i, query));
-
- return PostFilterAndSort(items, parent, null, query);
+ return _libraryManager.GetItemsResult(query);
}
private QueryResult<BaseItem> GetFavoriteEpisodes(Folder parent, User user, InternalItemsQuery query)
{
+ query.Recursive = true;
+ query.ParentId = parent.Id;
+ query.SetUser(user);
query.IsFavorite = true;
+ query.IncludeItemTypes = new[] { typeof(Episode).Name };
- var items = GetRecursiveChildren(parent, user, new[] { CollectionType.TvShows, string.Empty }, i => i is Episode && FilterItem(i, query));
-
- return PostFilterAndSort(items, parent, null, query);
+ return _libraryManager.GetItemsResult(query);
}
private QueryResult<BaseItem> GetMovieMovies(Folder parent, User user, InternalItemsQuery query)
{
- var items = GetRecursiveChildren(parent, user, new[] { CollectionType.Movies, CollectionType.BoxSets, string.Empty }, i => i is Movie && FilterItem(i, query));
+ query.Recursive = true;
+ query.ParentId = parent.Id;
+ query.SetUser(user);
- return PostFilterAndSort(items, parent, null, query);
+ query.IncludeItemTypes = new[] { typeof(Movie).Name };
+
+ return _libraryManager.GetItemsResult(query);
}
private QueryResult<BaseItem> GetMovieCollections(Folder parent, User user, InternalItemsQuery query)
{
- var items = GetRecursiveChildren(parent, user, new[] { CollectionType.Movies, CollectionType.BoxSets, string.Empty }, i => i is BoxSet && FilterItem(i, query));
-
- return PostFilterAndSort(items, parent, null, query);
+ return GetBoxsetView(parent, user, query);
}
private QueryResult<BaseItem> GetMovieLatest(Folder parent, User user, InternalItemsQuery query)
@@ -501,9 +545,13 @@ namespace MediaBrowser.Controller.Entities
query.SortBy = new[] { ItemSortBy.DateCreated, ItemSortBy.SortName };
query.SortOrder = SortOrder.Descending;
- var items = GetRecursiveChildren(parent, user, new[] { CollectionType.Movies, CollectionType.BoxSets, string.Empty }, i => i is Movie && FilterItem(i, query));
+ query.Recursive = true;
+ query.ParentId = parent.Id;
+ query.SetUser(user);
+ query.Limit = GetSpecialItemsLimit();
+ query.IncludeItemTypes = new[] { typeof(Movie).Name };
- return PostFilterAndSort(items, parent, GetSpecialItemsLimit(), query);
+ return ConvertToResult(_libraryManager.GetItemList(query));
}
private QueryResult<BaseItem> GetMovieResume(Folder parent, User user, InternalItemsQuery query)
@@ -511,16 +559,34 @@ namespace MediaBrowser.Controller.Entities
query.SortBy = new[] { ItemSortBy.DatePlayed, ItemSortBy.SortName };
query.SortOrder = SortOrder.Descending;
query.IsResumable = true;
+ query.Recursive = true;
+ query.ParentId = parent.Id;
+ query.SetUser(user);
+ query.Limit = GetSpecialItemsLimit();
+ query.IncludeItemTypes = new[] { typeof(Movie).Name };
- var items = GetRecursiveChildren(parent, user, new[] { CollectionType.Movies, CollectionType.BoxSets, string.Empty }, i => i is Movie && FilterItem(i, query));
+ return ConvertToResult(_libraryManager.GetItemList(query));
+ }
- return PostFilterAndSort(items, parent, GetSpecialItemsLimit(), 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)
{
- var tasks = GetRecursiveChildren(parent, user, new[] { CollectionType.Movies, CollectionType.BoxSets, string.Empty })
- .Where(i => i is Movie)
+ var tasks = parent.QueryRecursive(new InternalItemsQuery(user)
+ {
+ IncludeItemTypes = new[] { typeof(Movie).Name },
+ Recursive = true,
+ EnableTotalRecordCount = false
+
+ }).Items
.SelectMany(i => i.Genres)
.DistinctNames()
.Select(i =>
@@ -547,11 +613,14 @@ namespace MediaBrowser.Controller.Entities
private async Task<QueryResult<BaseItem>> GetMovieGenreItems(Folder queryParent, Folder displayParent, User user, InternalItemsQuery query)
{
- var items = GetRecursiveChildren(queryParent, user, new[] { CollectionType.Movies, CollectionType.BoxSets, string.Empty })
- .Where(i => i is Movie)
- .Where(i => i.Genres.Contains(displayParent.Name, StringComparer.OrdinalIgnoreCase));
+ query.Recursive = true;
+ query.ParentId = queryParent.Id;
+ query.Genres = new[] { displayParent.Name };
+ query.SetUser(user);
+
+ query.IncludeItemTypes = new[] { typeof(Movie).Name };
- return GetResult(items, queryParent, query);
+ return _libraryManager.GetItemsResult(query);
}
private async Task<QueryResult<BaseItem>> GetPlaylistsView(Folder parent, User user, InternalItemsQuery query)
@@ -559,20 +628,29 @@ namespace MediaBrowser.Controller.Entities
return GetResult(_playlistManager.GetPlaylists(user.Id.ToString("N")), parent, query);
}
- private async Task<QueryResult<BaseItem>> GetBoxsetView(Folder parent, User user, InternalItemsQuery query)
+ private QueryResult<BaseItem> GetBoxsetView(Folder parent, User user, InternalItemsQuery query)
{
- var collections = _collectionManager.GetCollections(user);
+ query.ParentId = null;
+ query.IncludeItemTypes = new[] { typeof(BoxSet).Name };
+ query.SetUser(user);
+ query.Recursive = true;
- return GetResult(collections, parent, query);
+ return _libraryManager.GetItemsResult(query);
}
private async Task<QueryResult<BaseItem>> GetTvView(Folder parent, User user, InternalItemsQuery query)
{
if (query.Recursive)
{
- var items = GetRecursiveChildren(parent, user, new[] { CollectionType.TvShows, string.Empty }, i => (i is Series || i is Season || i is Episode) && FilterItem(i, query));
+ query.Recursive = true;
+ query.SetUser(user);
+
+ if (query.IncludeItemTypes.Length == 0)
+ {
+ query.IncludeItemTypes = new[] { typeof(Series).Name, typeof(Season).Name, typeof(Episode).Name };
+ }
- return PostFilterAndSort(items, parent, null, query);
+ return parent.QueryRecursive(query);
}
var list = new List<BaseItem>();
@@ -593,9 +671,14 @@ namespace MediaBrowser.Controller.Entities
query.SortBy = new[] { ItemSortBy.DateCreated, ItemSortBy.SortName };
query.SortOrder = SortOrder.Descending;
- var items = GetRecursiveChildren(parent, user, new[] { CollectionType.TvShows, string.Empty }, i => i is Episode && FilterItem(i, query));
+ query.Recursive = true;
+ query.ParentId = parent.Id;
+ query.SetUser(user);
+ query.Limit = GetSpecialItemsLimit();
+ query.IncludeItemTypes = new[] { typeof(Episode).Name };
+ query.ExcludeLocationTypes = new[] { LocationType.Virtual };
- return PostFilterAndSort(items, parent, GetSpecialItemsLimit(), query);
+ return ConvertToResult(_libraryManager.GetItemList(query));
}
private QueryResult<BaseItem> GetTvNextUp(Folder parent, InternalItemsQuery query)
@@ -618,23 +701,35 @@ namespace MediaBrowser.Controller.Entities
query.SortBy = new[] { ItemSortBy.DatePlayed, ItemSortBy.SortName };
query.SortOrder = SortOrder.Descending;
query.IsResumable = true;
+ query.Recursive = true;
+ query.ParentId = parent.Id;
+ query.SetUser(user);
+ query.Limit = GetSpecialItemsLimit();
+ query.IncludeItemTypes = new[] { typeof(Episode).Name };
- var items = GetRecursiveChildren(parent, user, new[] { CollectionType.TvShows, string.Empty }, i => i is Episode && FilterItem(i, query));
-
- return PostFilterAndSort(items, parent, GetSpecialItemsLimit(), query);
+ return ConvertToResult(_libraryManager.GetItemList(query));
}
private QueryResult<BaseItem> GetTvSeries(Folder parent, User user, InternalItemsQuery query)
{
- var items = GetRecursiveChildren(parent, user, new[] { CollectionType.TvShows, string.Empty }, i => i is Series && FilterItem(i, query));
+ query.Recursive = true;
+ query.ParentId = parent.Id;
+ query.SetUser(user);
- return PostFilterAndSort(items, parent, null, query);
+ query.IncludeItemTypes = new[] { typeof(Series).Name };
+
+ return _libraryManager.GetItemsResult(query);
}
private async Task<QueryResult<BaseItem>> GetTvGenres(Folder parent, User user, InternalItemsQuery query)
{
- var tasks = GetRecursiveChildren(parent, user, new[] { CollectionType.TvShows, string.Empty })
- .OfType<Series>()
+ var tasks = parent.QueryRecursive(new InternalItemsQuery(user)
+ {
+ IncludeItemTypes = new[] { typeof(Series).Name },
+ Recursive = true,
+ EnableTotalRecordCount = false
+
+ }).Items
.SelectMany(i => i.Genres)
.DistinctNames()
.Select(i =>
@@ -659,13 +754,16 @@ 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)
{
- var items = GetRecursiveChildren(queryParent, user, new[] { CollectionType.TvShows, string.Empty })
- .Where(i => i is Series)
- .Where(i => i.Genres.Contains(displayParent.Name, StringComparer.OrdinalIgnoreCase));
+ query.Recursive = true;
+ query.ParentId = queryParent.Id;
+ query.Genres = new[] { displayParent.Name };
+ query.SetUser(user);
- return GetResult(items, queryParent, query);
+ query.IncludeItemTypes = new[] { typeof(Series).Name };
+
+ return _libraryManager.GetItemsResult(query);
}
private QueryResult<BaseItem> GetResult<T>(QueryResult<T> result)
@@ -685,12 +783,12 @@ 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 bool FilterItem(BaseItem item, InternalItemsQuery query)
+ public static bool FilterItem(BaseItem item, InternalItemsQuery query)
{
- return Filter(item, query.User, query, _userDataManager, _libraryManager);
+ return Filter(item, query.User, query, BaseItem.UserDataManager, BaseItem.LibraryManager);
}
private QueryResult<BaseItem> PostFilterAndSort(IEnumerable<BaseItem> items,
@@ -698,14 +796,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;
@@ -714,7 +813,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))
@@ -728,14 +827,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);
}
@@ -768,7 +868,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)
@@ -780,12 +881,12 @@ namespace MediaBrowser.Controller.Entities
if (!param.HasValue)
{
- if (user != null && !user.Configuration.GroupMoviesIntoBoxSets)
+ if (user != null && !configurationManager.Configuration.EnableGroupingIntoCollections)
{
return false;
}
- if (query.IncludeItemTypes.Contains("Movie", StringComparer.OrdinalIgnoreCase))
+ if (query.IncludeItemTypes.Length == 0 || query.IncludeItemTypes.Contains("Movie", StringComparer.OrdinalIgnoreCase))
{
param = true;
}
@@ -911,11 +1012,6 @@ namespace MediaBrowser.Controller.Entities
return false;
}
- if (request.IsYearMismatched.HasValue)
- {
- return false;
- }
-
if (!string.IsNullOrWhiteSpace(request.Person))
{
return false;
@@ -1053,22 +1149,6 @@ namespace MediaBrowser.Controller.Entities
bool? isVirtualUnaired,
bool? isUnaired)
{
- if (isMissing.HasValue && isVirtualUnaired.HasValue)
- {
- if (!isMissing.Value && !isVirtualUnaired.Value)
- {
- return items.Where(i =>
- {
- var e = i as Season;
- if (e != null)
- {
- return !e.IsMissingOrVirtualUnaired;
- }
- return true;
- });
- }
- }
-
if (isMissing.HasValue)
{
var val = isMissing.Value;
@@ -1077,7 +1157,7 @@ namespace MediaBrowser.Controller.Entities
var e = i as Season;
if (e != null)
{
- return e.IsMissingSeason == val;
+ return (e.IsMissingSeason) == val;
}
return true;
});
@@ -1121,7 +1201,7 @@ namespace MediaBrowser.Controller.Entities
{
var user = query.User;
- items = libraryManager.ReplaceVideosWithPrimaryVersions(items);
+ items = items.DistinctBy(i => i.PresentationUniqueKey, StringComparer.OrdinalIgnoreCase);
if (query.SortBy.Length > 0)
{
@@ -1209,16 +1289,11 @@ namespace MediaBrowser.Controller.Entities
return false;
}
- if (query.Filter != null && !query.Filter(item))
- {
- return false;
- }
-
UserItemData userData = null;
if (query.IsLiked.HasValue)
{
- userData = userData ?? userDataManager.GetUserData(user.Id, item.GetUserDataKey());
+ userData = userData ?? userDataManager.GetUserData(user, item);
if (!userData.Likes.HasValue || userData.Likes != query.IsLiked.Value)
{
@@ -1228,7 +1303,7 @@ namespace MediaBrowser.Controller.Entities
if (query.IsFavoriteOrLiked.HasValue)
{
- userData = userData ?? userDataManager.GetUserData(user.Id, item.GetUserDataKey());
+ userData = userData ?? userDataManager.GetUserData(user, item);
var isFavoriteOrLiked = userData.IsFavorite || (userData.Likes ?? false);
if (isFavoriteOrLiked != query.IsFavoriteOrLiked.Value)
@@ -1239,7 +1314,7 @@ namespace MediaBrowser.Controller.Entities
if (query.IsFavorite.HasValue)
{
- userData = userData ?? userDataManager.GetUserData(user.Id, item.GetUserDataKey());
+ userData = userData ?? userDataManager.GetUserData(user, item);
if (userData.IsFavorite != query.IsFavorite.Value)
{
@@ -1249,7 +1324,7 @@ namespace MediaBrowser.Controller.Entities
if (query.IsResumable.HasValue)
{
- userData = userData ?? userDataManager.GetUserData(user.Id, item.GetUserDataKey());
+ userData = userData ?? userDataManager.GetUserData(user, item);
var isResumable = userData.PlaybackPositionTicks > 0;
if (isResumable != query.IsResumable.Value)
@@ -1355,16 +1430,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;
@@ -1598,12 +1663,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;
}
@@ -1735,7 +1795,7 @@ namespace MediaBrowser.Controller.Entities
if (query.SeriesStatuses.Length > 0)
{
- var ok = new[] { item }.OfType<Series>().Any(p => p.Status.HasValue && query.SeriesStatuses.Contains(p.Status.Value));
+ var ok = new[] { item }.OfType<Series>().Any(p => p.Status.HasValue && query.SeriesStatuses.Contains(p.Status.Value));
if (!ok)
{
return false;
@@ -1884,26 +1944,6 @@ namespace MediaBrowser.Controller.Entities
return parent.GetRecursiveChildren(user);
}
- private IEnumerable<BaseItem> GetRecursiveChildren(Folder parent, User user, IEnumerable<string> viewTypes, Func<BaseItem, bool> filter)
- {
- if (parent == null || parent is UserView)
- {
- if (user == null)
- {
- return GetMediaFolders(null, viewTypes).SelectMany(i => i.GetRecursiveChildren(filter));
- }
-
- return GetMediaFolders(user, viewTypes).SelectMany(i => i.GetRecursiveChildren(user, filter));
- }
-
- if (user == null)
- {
- return parent.GetRecursiveChildren(filter);
- }
-
- return parent.GetRecursiveChildren(user, filter);
- }
-
private async Task<QueryResult<BaseItem>> GetLiveTvView(Folder queryParent, User user, InternalItemsQuery query)
{
if (query.Recursive)
@@ -1936,34 +1976,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 511606efc..eba1e466a 100644
--- a/MediaBrowser.Controller/Entities/Video.cs
+++ b/MediaBrowser.Controller/Entities/Video.cs
@@ -21,14 +21,14 @@ namespace MediaBrowser.Controller.Entities
/// </summary>
public class Video : BaseItem,
IHasAspectRatio,
- IHasTags,
ISupportsPlaceHolders,
IHasMediaSources,
IHasShortOverview,
IThemeMedia,
IArchivable
{
- public Guid? PrimaryVersionId { get; set; }
+ [IgnoreDataMember]
+ public string PrimaryVersionId { get; set; }
public List<string> AdditionalParts { get; set; }
public List<string> LocalAlternateVersions { get; set; }
@@ -44,10 +44,27 @@ namespace MediaBrowser.Controller.Entities
}
}
- public long? Size { get; set; }
- public string Container { get; set; }
+ [IgnoreDataMember]
+ public override string PresentationUniqueKey
+ {
+ get
+ {
+ if (!string.IsNullOrWhiteSpace(PrimaryVersionId))
+ {
+ return PrimaryVersionId;
+ }
+
+ return base.PresentationUniqueKey;
+ }
+ }
+
+ [IgnoreDataMember]
+ public override bool EnableForceSaveOnDateModifiedChange
+ {
+ get { return true; }
+ }
+
public int? TotalBitrate { get; set; }
- public string ShortOverview { get; set; }
public ExtraType? ExtraType { get; set; }
/// <summary>
@@ -56,6 +73,72 @@ namespace MediaBrowser.Controller.Entities
/// <value>The timestamp.</value>
public TransportStreamTimestamp? Timestamp { get; set; }
+ /// <summary>
+ /// Gets or sets the subtitle paths.
+ /// </summary>
+ /// <value>The subtitle paths.</value>
+ public List<string> SubtitleFiles { get; set; }
+
+ /// <summary>
+ /// Gets or sets a value indicating whether this instance has subtitles.
+ /// </summary>
+ /// <value><c>true</c> if this instance has subtitles; otherwise, <c>false</c>.</value>
+ public bool HasSubtitles { get; set; }
+
+ public bool IsPlaceHolder { get; set; }
+ public bool IsShortcut { get; set; }
+ public string ShortcutPath { get; set; }
+
+ /// <summary>
+ /// Gets or sets the video bit rate.
+ /// </summary>
+ /// <value>The video bit rate.</value>
+ public int? VideoBitRate { get; set; }
+
+ /// <summary>
+ /// Gets or sets the default index of the video stream.
+ /// </summary>
+ /// <value>The default index of the video stream.</value>
+ public int? DefaultVideoStreamIndex { get; set; }
+
+ /// <summary>
+ /// Gets or sets the type of the video.
+ /// </summary>
+ /// <value>The type of the video.</value>
+ public VideoType VideoType { get; set; }
+
+ /// <summary>
+ /// Gets or sets the type of the iso.
+ /// </summary>
+ /// <value>The type of the iso.</value>
+ public IsoType? IsoType { get; set; }
+
+ /// <summary>
+ /// Gets or sets the video3 D format.
+ /// </summary>
+ /// <value>The video3 D format.</value>
+ public Video3DFormat? Video3DFormat { get; set; }
+
+ /// <summary>
+ /// If the video is a folder-rip, this will hold the file list for the largest playlist
+ /// </summary>
+ public List<string> PlayableStreamFileNames { get; set; }
+
+ /// <summary>
+ /// Gets the playable stream files.
+ /// </summary>
+ /// <returns>List{System.String}.</returns>
+ public List<string> GetPlayableStreamFiles()
+ {
+ return GetPlayableStreamFiles(Path);
+ }
+
+ /// <summary>
+ /// Gets or sets the aspect ratio.
+ /// </summary>
+ /// <value>The aspect ratio.</value>
+ public string AspectRatio { get; set; }
+
public Video()
{
PlayableStreamFileNames = new List<string>();
@@ -80,23 +163,6 @@ namespace MediaBrowser.Controller.Entities
}
[IgnoreDataMember]
- public override LocationType LocationType
- {
- get
- {
- if (SourceType == SourceType.Channel)
- {
- if (string.IsNullOrEmpty(Path))
- {
- return LocationType.Remote;
- }
- }
-
- return base.LocationType;
- }
- }
-
- [IgnoreDataMember]
public override bool SupportsAddingToPlaylist
{
get { return LocationType == LocationType.FileSystem && RunTimeTicks.HasValue; }
@@ -107,6 +173,14 @@ namespace MediaBrowser.Controller.Entities
{
get
{
+ if (!string.IsNullOrWhiteSpace(PrimaryVersionId))
+ {
+ var item = LibraryManager.GetItemById(PrimaryVersionId) as Video;
+ if (item != null)
+ {
+ return item.MediaSourceCount;
+ }
+ }
return LinkedAlternateVersions.Count + LocalAlternateVersions.Count + 1;
}
}
@@ -148,42 +222,65 @@ namespace MediaBrowser.Controller.Entities
return LocalAlternateVersions.Select(i => LibraryManager.GetNewItemId(i, typeof(Video)));
}
- protected override string CreateUserDataKey()
+ [IgnoreDataMember]
+ protected virtual bool EnableDefaultVideoUserDataKeys
{
- if (ExtraType.HasValue)
+ get
{
- var key = this.GetProviderId(MetadataProviders.Imdb) ?? this.GetProviderId(MetadataProviders.Tmdb);
+ return true;
+ }
+ }
+
+ public override List<string> GetUserDataKeys()
+ {
+ var list = base.GetUserDataKeys();
- if (!string.IsNullOrWhiteSpace(key))
+ if (EnableDefaultVideoUserDataKeys)
+ {
+ if (ExtraType.HasValue)
{
- key = key + "-" + ExtraType.ToString().ToLower();
+ var key = this.GetProviderId(MetadataProviders.Tmdb);
+ if (!string.IsNullOrWhiteSpace(key))
+ {
+ list.Insert(0, GetUserDataKey(key));
+ }
- // Make sure different trailers have their own data.
- if (RunTimeTicks.HasValue)
+ key = this.GetProviderId(MetadataProviders.Imdb);
+ if (!string.IsNullOrWhiteSpace(key))
{
- key += "-" + RunTimeTicks.Value.ToString(CultureInfo.InvariantCulture);
+ list.Insert(0, GetUserDataKey(key));
+ }
+ }
+ else
+ {
+ var key = this.GetProviderId(MetadataProviders.Imdb);
+ if (!string.IsNullOrWhiteSpace(key))
+ {
+ list.Insert(0, key);
}
- return key;
+ key = this.GetProviderId(MetadataProviders.Tmdb);
+ if (!string.IsNullOrWhiteSpace(key))
+ {
+ list.Insert(0, key);
+ }
}
}
- return base.CreateUserDataKey();
+ return list;
}
- /// <summary>
- /// Gets the linked children.
- /// </summary>
- /// <returns>IEnumerable{BaseItem}.</returns>
- public IEnumerable<Video> GetAlternateVersions()
+ private string GetUserDataKey(string providerId)
{
- var filesWithinSameDirectory = GetLocalAlternateVersionIds()
- .Select(i => LibraryManager.GetItemById(i))
- .Where(i => i != null)
- .OfType<Video>();
+ var key = providerId + "-" + ExtraType.ToString().ToLower();
- return filesWithinSameDirectory.Concat(GetLinkedAlternateVersions())
- .OrderBy(i => i.SortName);
+ // Make sure different trailers have their own data.
+ if (RunTimeTicks.HasValue)
+ {
+ key += "-" + RunTimeTicks.Value.ToString(CultureInfo.InvariantCulture);
+ }
+
+ return key;
}
public IEnumerable<Video> GetLinkedAlternateVersions()
@@ -210,72 +307,6 @@ namespace MediaBrowser.Controller.Entities
.OrderBy(i => i.SortName);
}
- /// <summary>
- /// Gets or sets the subtitle paths.
- /// </summary>
- /// <value>The subtitle paths.</value>
- public List<string> SubtitleFiles { get; set; }
-
- /// <summary>
- /// Gets or sets a value indicating whether this instance has subtitles.
- /// </summary>
- /// <value><c>true</c> if this instance has subtitles; otherwise, <c>false</c>.</value>
- public bool HasSubtitles { get; set; }
-
- public bool IsPlaceHolder { get; set; }
- public bool IsShortcut { get; set; }
- public string ShortcutPath { get; set; }
-
- /// <summary>
- /// Gets or sets the video bit rate.
- /// </summary>
- /// <value>The video bit rate.</value>
- public int? VideoBitRate { get; set; }
-
- /// <summary>
- /// Gets or sets the default index of the video stream.
- /// </summary>
- /// <value>The default index of the video stream.</value>
- public int? DefaultVideoStreamIndex { get; set; }
-
- /// <summary>
- /// Gets or sets the type of the video.
- /// </summary>
- /// <value>The type of the video.</value>
- public VideoType VideoType { get; set; }
-
- /// <summary>
- /// Gets or sets the type of the iso.
- /// </summary>
- /// <value>The type of the iso.</value>
- public IsoType? IsoType { get; set; }
-
- /// <summary>
- /// Gets or sets the video3 D format.
- /// </summary>
- /// <value>The video3 D format.</value>
- public Video3DFormat? Video3DFormat { get; set; }
-
- /// <summary>
- /// If the video is a folder-rip, this will hold the file list for the largest playlist
- /// </summary>
- public List<string> PlayableStreamFileNames { get; set; }
-
- /// <summary>
- /// Gets the playable stream files.
- /// </summary>
- /// <returns>List{System.String}.</returns>
- public List<string> GetPlayableStreamFiles()
- {
- return GetPlayableStreamFiles(Path);
- }
-
- /// <summary>
- /// Gets or sets the aspect ratio.
- /// </summary>
- /// <value>The aspect ratio.</value>
- public string AspectRatio { get; set; }
-
[IgnoreDataMember]
public override string ContainingFolderPath
{
@@ -334,6 +365,11 @@ namespace MediaBrowser.Controller.Entities
{
return false;
}
+
+ if (newAsVideo.VideoType != VideoType)
+ {
+ return false;
+ }
}
return base.IsValidFromResolver(newItem);
@@ -480,6 +516,36 @@ namespace MediaBrowser.Controller.Entities
}).FirstOrDefault();
}
+ private List<Tuple<Video, MediaSourceType>> GetAllVideosForMediaSources()
+ {
+ var list = new List<Tuple<Video, MediaSourceType>>();
+
+ list.Add(new Tuple<Video, MediaSourceType>(this, MediaSourceType.Default));
+ list.AddRange(GetLinkedAlternateVersions().Select(i => new Tuple<Video, MediaSourceType>(i, MediaSourceType.Grouping)));
+
+ if (!string.IsNullOrWhiteSpace(PrimaryVersionId))
+ {
+ var primary = LibraryManager.GetItemById(PrimaryVersionId) as Video;
+ if (primary != null)
+ {
+ var existingIds = list.Select(i => i.Item1.Id).ToList();
+ list.Add(new Tuple<Video, MediaSourceType>(primary, MediaSourceType.Grouping));
+ list.AddRange(primary.GetLinkedAlternateVersions().Where(i => !existingIds.Contains(i.Id)).Select(i => new Tuple<Video, MediaSourceType>(i, MediaSourceType.Grouping)));
+ }
+ }
+
+ var localAlternates = list
+ .SelectMany(i => i.Item1.GetLocalAlternateVersionIds())
+ .Select(LibraryManager.GetItemById)
+ .Where(i => i != null)
+ .OfType<Video>()
+ .ToList();
+
+ list.AddRange(localAlternates.Select(i => new Tuple<Video, MediaSourceType>(i, MediaSourceType.Default)));
+
+ return list;
+ }
+
public virtual IEnumerable<MediaSourceInfo> GetMediaSources(bool enablePathSubstitution)
{
if (SourceType == SourceType.Channel)
@@ -498,13 +564,8 @@ namespace MediaBrowser.Controller.Entities
};
}
- var item = this;
-
- var result = item.GetAlternateVersions()
- .Select(i => GetVersionInfo(enablePathSubstitution, i, MediaSourceType.Grouping))
- .ToList();
-
- result.Add(GetVersionInfo(enablePathSubstitution, item, MediaSourceType.Default));
+ var list = GetAllVideosForMediaSources();
+ var result = list.Select(i => GetVersionInfo(enablePathSubstitution, i.Item1, i.Item2)).ToList();
return result.OrderBy(i =>
{
diff --git a/MediaBrowser.Controller/Entities/Year.cs b/MediaBrowser.Controller/Entities/Year.cs
index 163dcd667..db896f1fc 100644
--- a/MediaBrowser.Controller/Entities/Year.cs
+++ b/MediaBrowser.Controller/Entities/Year.cs
@@ -11,13 +11,12 @@ namespace MediaBrowser.Controller.Entities
/// </summary>
public class Year : BaseItem, IItemByName
{
- /// <summary>
- /// Gets the user data key.
- /// </summary>
- /// <returns>System.String.</returns>
- protected override string CreateUserDataKey()
+ public override List<string> GetUserDataKeys()
{
- return "Year-" + Name;
+ var list = base.GetUserDataKeys();
+
+ list.Insert(0, "Year-" + Name);
+ return list;
}
/// <summary>
@@ -71,6 +70,22 @@ namespace MediaBrowser.Controller.Entities
return inputItems.Where(i => i.ProductionYear.HasValue && i.ProductionYear.Value == year);
}
+ public IEnumerable<BaseItem> GetTaggedItems(InternalItemsQuery query)
+ {
+ int year;
+
+ var usCulture = new CultureInfo("en-US");
+
+ if (!int.TryParse(Name, NumberStyles.Integer, usCulture, out year))
+ {
+ return new List<BaseItem>();
+ }
+
+ query.Years = new[] { year };
+
+ return LibraryManager.GetItemList(query);
+ }
+
public int? GetYearValue()
{
int i;
diff --git a/MediaBrowser.Controller/Health/IHealthMonitor.cs b/MediaBrowser.Controller/Health/IHealthMonitor.cs
new file mode 100644
index 000000000..b8ad98fc1
--- /dev/null
+++ b/MediaBrowser.Controller/Health/IHealthMonitor.cs
@@ -0,0 +1,12 @@
+using System.Collections.Generic;
+using System.Threading;
+using System.Threading.Tasks;
+using MediaBrowser.Model.Notifications;
+
+namespace MediaBrowser.Controller.Health
+{
+ public interface IHealthMonitor
+ {
+ Task<List<Notification>> GetNotifications(CancellationToken cancellationToken);
+ }
+}
diff --git a/MediaBrowser.Controller/IServerApplicationHost.cs b/MediaBrowser.Controller/IServerApplicationHost.cs
index e4eecec18..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
{
@@ -12,12 +13,12 @@ namespace MediaBrowser.Controller
public interface IServerApplicationHost : IApplicationHost
{
event EventHandler HasUpdateAvailableChanged;
-
+
/// <summary>
/// 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.
@@ -86,5 +87,7 @@ namespace MediaBrowser.Controller
/// <param name="ipAddress">The ip address.</param>
/// <returns>System.String.</returns>
string GetLocalApiUrl(IPAddress ipAddress);
+
+ void LaunchUrl(string url);
}
}
diff --git a/MediaBrowser.Controller/Library/ILibraryManager.cs b/MediaBrowser.Controller/Library/ILibraryManager.cs
index c8b3d5131..ad38b9ea5 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
{
@@ -25,7 +26,7 @@ namespace MediaBrowser.Controller.Library
/// <param name="fileInfo">The file information.</param>
/// <param name="parent">The parent.</param>
/// <returns>BaseItem.</returns>
- BaseItem ResolvePath(FileSystemMetadata fileInfo,
+ BaseItem ResolvePath(FileSystemMetadata fileInfo,
Folder parent = null);
/// <summary>
@@ -36,9 +37,9 @@ namespace MediaBrowser.Controller.Library
/// <param name="parent">The parent.</param>
/// <param name="collectionType">Type of the collection.</param>
/// <returns>List{``0}.</returns>
- IEnumerable<BaseItem> ResolvePaths(IEnumerable<FileSystemMetadata> files,
+ IEnumerable<BaseItem> ResolvePaths(IEnumerable<FileSystemMetadata> files,
IDirectoryService directoryService,
- Folder parent, string
+ Folder parent, string
collectionType = null);
/// <summary>
@@ -59,8 +60,8 @@ namespace MediaBrowser.Controller.Library
/// </summary>
/// <param name="path">The path.</param>
/// <returns>BaseItem.</returns>
- BaseItem FindByPath(string path);
-
+ BaseItem FindByPath(string path, bool? isFolder);
+
/// <summary>
/// Gets the artist.
/// </summary>
@@ -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>
@@ -243,6 +237,8 @@ namespace MediaBrowser.Controller.Library
/// <returns>BaseItem.</returns>
BaseItem RetrieveItem(Guid id);
+ bool IsScanRunning { get; }
+
/// <summary>
/// Occurs when [item added].
/// </summary>
@@ -290,7 +286,7 @@ namespace MediaBrowser.Controller.Library
/// <param name="path">The path.</param>
/// <returns>System.String.</returns>
string GetConfiguredContentType(string path);
-
+
/// <summary>
/// Normalizes the root path list.
/// </summary>
@@ -313,13 +309,6 @@ namespace MediaBrowser.Controller.Library
Task DeleteItem(BaseItem item, DeleteOptions options);
/// <summary>
- /// Replaces the videos with primary versions.
- /// </summary>
- /// <param name="items">The items.</param>
- /// <returns>IEnumerable{BaseItem}.</returns>
- IEnumerable<BaseItem> ReplaceVideosWithPrimaryVersions(IEnumerable<BaseItem> items);
-
- /// <summary>
/// Gets the named view.
/// </summary>
/// <param name="user">The user.</param>
@@ -332,8 +321,8 @@ namespace MediaBrowser.Controller.Library
Task<UserView> GetNamedView(User user,
string name,
string parentId,
- string viewType,
- string sortName,
+ string viewType,
+ string sortName,
CancellationToken cancellationToken);
/// <summary>
@@ -346,8 +335,8 @@ namespace MediaBrowser.Controller.Library
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>Task&lt;UserView&gt;.</returns>
Task<UserView> GetNamedView(User user,
- string name,
- string viewType,
+ string name,
+ string viewType,
string sortName,
CancellationToken cancellationToken);
@@ -393,7 +382,7 @@ namespace MediaBrowser.Controller.Library
string viewType,
string sortName,
CancellationToken cancellationToken);
-
+
/// <summary>
/// Determines whether [is video file] [the specified path].
/// </summary>
@@ -477,14 +466,14 @@ namespace MediaBrowser.Controller.Library
/// <param name="query">The query.</param>
/// <returns>List&lt;PersonInfo&gt;.</returns>
List<PersonInfo> GetPeople(InternalPeopleQuery query);
-
+
/// <summary>
/// Gets the people items.
/// </summary>
/// <param name="query">The query.</param>
/// <returns>List&lt;Person&gt;.</returns>
List<Person> GetPeopleItems(InternalPeopleQuery query);
-
+
/// <summary>
/// Gets all people names.
/// </summary>
@@ -559,7 +548,7 @@ namespace MediaBrowser.Controller.Library
/// <param name="query">The query.</param>
/// <returns>QueryResult&lt;BaseItem&gt;.</returns>
QueryResult<BaseItem> GetItemsResult(InternalItemsQuery query);
-
+
/// <summary>
/// Ignores the file.
/// </summary>
@@ -567,5 +556,17 @@ namespace MediaBrowser.Controller.Library
/// <param name="parent">The parent.</param>
/// <returns><c>true</c> if XXXX, <c>false</c> otherwise.</returns>
bool IgnoreFile(FileSystemMetadata file, BaseItem parent);
+
+ void AddVirtualFolder(string name, string collectionType, string[] mediaPaths, bool refreshLibrary);
+ 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 56ac14e9d..86c52c4c3 100644
--- a/MediaBrowser.Controller/Library/IUserDataManager.cs
+++ b/MediaBrowser.Controller/Library/IUserDataManager.cs
@@ -29,21 +29,10 @@ namespace MediaBrowser.Controller.Library
/// <returns>Task.</returns>
Task SaveUserData(Guid userId, IHasUserData item, UserItemData userData, UserDataSaveReason reason, CancellationToken cancellationToken);
- /// <summary>
- /// Gets the user data.
- /// </summary>
- /// <param name="userId">The user id.</param>
- /// <param name="key">The key.</param>
- /// <returns>Task{UserItemData}.</returns>
- UserItemData GetUserData(string userId, string key);
+ UserItemData GetUserData(IHasUserData user, IHasUserData item);
- /// <summary>
- /// Gets the user data.
- /// </summary>
- /// <param name="userId">The user id.</param>
- /// <param name="key">The key.</param>
- /// <returns>Task{UserItemData}.</returns>
- UserItemData GetUserData(Guid userId, string key);
+ UserItemData GetUserData(string userId, IHasUserData item);
+ UserItemData GetUserData(Guid userId, IHasUserData item);
/// <summary>
/// Gets the user data dto.
@@ -51,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/Library/TVUtils.cs b/MediaBrowser.Controller/Library/TVUtils.cs
index f8039b2cf..29421ebaf 100644
--- a/MediaBrowser.Controller/Library/TVUtils.cs
+++ b/MediaBrowser.Controller/Library/TVUtils.cs
@@ -15,7 +15,7 @@ namespace MediaBrowser.Controller.Library
/// <summary>
/// The banner URL
/// </summary>
- public static readonly string BannerUrl = "http://www.thetvdb.com/banners/";
+ public static readonly string BannerUrl = "https://www.thetvdb.com/banners/";
/// <summary>
/// Gets the air days.
diff --git a/MediaBrowser.Controller/Library/UserDataSaveEventArgs.cs b/MediaBrowser.Controller/Library/UserDataSaveEventArgs.cs
index ba328ff75..654c6b581 100644
--- a/MediaBrowser.Controller/Library/UserDataSaveEventArgs.cs
+++ b/MediaBrowser.Controller/Library/UserDataSaveEventArgs.cs
@@ -1,6 +1,7 @@
using MediaBrowser.Controller.Entities;
using MediaBrowser.Model.Entities;
using System;
+using System.Collections.Generic;
namespace MediaBrowser.Controller.Library
{
@@ -15,11 +16,7 @@ namespace MediaBrowser.Controller.Library
/// <value>The user id.</value>
public Guid UserId { get; set; }
- /// <summary>
- /// Gets or sets the key.
- /// </summary>
- /// <value>The key.</value>
- public string Key { get; set; }
+ public List<string> Keys { get; set; }
/// <summary>
/// Gets or sets the save reason.
diff --git a/MediaBrowser.Controller/LiveTv/ChannelInfo.cs b/MediaBrowser.Controller/LiveTv/ChannelInfo.cs
index 7d8df96ed..372b095fd 100644
--- a/MediaBrowser.Controller/LiveTv/ChannelInfo.cs
+++ b/MediaBrowser.Controller/LiveTv/ChannelInfo.cs
@@ -59,5 +59,9 @@ namespace MediaBrowser.Controller.LiveTv
/// </summary>
/// <value><c>null</c> if [is favorite] contains no value, <c>true</c> if [is favorite]; otherwise, <c>false</c>.</value>
public bool? IsFavorite { get; set; }
+
+ public bool? IsHD { get; set; }
+ public string AudioCodec { get; set; }
+ public string VideoCodec { get; set; }
}
}
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 56b7a307a..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>
@@ -383,5 +391,17 @@ namespace MediaBrowser.Controller.LiveTv
/// </summary>
/// <returns>List&lt;NameValuePair&gt;.</returns>
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/ITunerHost.cs b/MediaBrowser.Controller/LiveTv/ITunerHost.cs
index 498602ddf..1e7aa3de5 100644
--- a/MediaBrowser.Controller/LiveTv/ITunerHost.cs
+++ b/MediaBrowser.Controller/LiveTv/ITunerHost.cs
@@ -46,6 +46,8 @@ namespace MediaBrowser.Controller.LiveTv
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>Task&lt;List&lt;MediaSourceInfo&gt;&gt;.</returns>
Task<List<MediaSourceInfo>> GetChannelStreamMediaSources(string channelId, CancellationToken cancellationToken);
+
+ string ApplyDuration(string streamPath, TimeSpan duration);
}
public interface IConfigurableTunerHost
{
diff --git a/MediaBrowser.Controller/LiveTv/LiveTvAudioRecording.cs b/MediaBrowser.Controller/LiveTv/LiveTvAudioRecording.cs
index 17a27eac1..e6f472414 100644
--- a/MediaBrowser.Controller/LiveTv/LiveTvAudioRecording.cs
+++ b/MediaBrowser.Controller/LiveTv/LiveTvAudioRecording.cs
@@ -46,17 +46,6 @@ namespace MediaBrowser.Controller.LiveTv
}
/// <summary>
- /// Gets the user data key.
- /// </summary>
- /// <returns>System.String.</returns>
- protected override string CreateUserDataKey()
- {
- var name = GetClientTypeName();
-
- return name + "-" + Name + (EpisodeTitle ?? string.Empty);
- }
-
- /// <summary>
/// Gets a value indicating whether this instance is owned item.
/// </summary>
/// <value><c>true</c> if this instance is owned item; otherwise, <c>false</c>.</value>
diff --git a/MediaBrowser.Controller/LiveTv/LiveTvChannel.cs b/MediaBrowser.Controller/LiveTv/LiveTvChannel.cs
index 35b9a1959..50aeed27d 100644
--- a/MediaBrowser.Controller/LiveTv/LiveTvChannel.cs
+++ b/MediaBrowser.Controller/LiveTv/LiveTvChannel.cs
@@ -11,13 +11,12 @@ namespace MediaBrowser.Controller.LiveTv
{
public class LiveTvChannel : BaseItem, IHasMediaSources
{
- /// <summary>
- /// Gets the user data key.
- /// </summary>
- /// <returns>System.String.</returns>
- protected override string CreateUserDataKey()
+ public override List<string> GetUserDataKeys()
{
- return GetClientTypeName() + "-" + Name;
+ var list = base.GetUserDataKeys();
+
+ list.Insert(0, GetClientTypeName() + "-" + Name);
+ return list;
}
public override UnratedItem GetBlockUnratedType()
@@ -45,6 +44,15 @@ namespace MediaBrowser.Controller.LiveTv
set { }
}
+ [IgnoreDataMember]
+ public override bool EnableRememberingTrackSelections
+ {
+ get
+ {
+ return false;
+ }
+ }
+
/// <summary>
/// Gets or sets the number.
/// </summary>
diff --git a/MediaBrowser.Controller/LiveTv/LiveTvProgram.cs b/MediaBrowser.Controller/LiveTv/LiveTvProgram.cs
index 59b921c6a..74c993248 100644
--- a/MediaBrowser.Controller/LiveTv/LiveTvProgram.cs
+++ b/MediaBrowser.Controller/LiveTv/LiveTvProgram.cs
@@ -4,36 +4,41 @@ using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Configuration;
using MediaBrowser.Model.LiveTv;
using System;
+using System.Collections.Generic;
using System.Runtime.Serialization;
+using MediaBrowser.Model.Entities;
+using MediaBrowser.Model.Providers;
namespace MediaBrowser.Controller.LiveTv
{
public class LiveTvProgram : BaseItem, IHasLookupInfo<LiveTvProgramLookupInfo>, IHasStartDate, IHasProgramAttributes
{
- /// <summary>
- /// Gets the user data key.
- /// </summary>
- /// <returns>System.String.</returns>
- protected override string CreateUserDataKey()
+ public override List<string> GetUserDataKeys()
{
- if (IsMovie)
+ var list = base.GetUserDataKeys();
+
+ if (!IsSeries)
{
- var key = Movie.GetMovieUserDataKey(this);
+ var key = this.GetProviderId(MetadataProviders.Imdb);
+ if (!string.IsNullOrWhiteSpace(key))
+ {
+ list.Insert(0, key);
+ }
+ key = this.GetProviderId(MetadataProviders.Tmdb);
if (!string.IsNullOrWhiteSpace(key))
{
- return key;
+ list.Insert(0, key);
}
}
-
- if (IsSeries && !string.IsNullOrWhiteSpace(EpisodeTitle))
+ else if (!string.IsNullOrWhiteSpace(EpisodeTitle))
{
var name = GetClientTypeName();
- return name + "-" + Name + (EpisodeTitle ?? string.Empty);
+ list.Insert(0, name + "-" + Name + (EpisodeTitle ?? string.Empty));
}
- return base.CreateUserDataKey();
+ return list;
}
[IgnoreDataMember]
@@ -231,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/LiveTvVideoRecording.cs b/MediaBrowser.Controller/LiveTv/LiveTvVideoRecording.cs
index f310a957c..a8c737673 100644
--- a/MediaBrowser.Controller/LiveTv/LiveTvVideoRecording.cs
+++ b/MediaBrowser.Controller/LiveTv/LiveTvVideoRecording.cs
@@ -45,32 +45,6 @@ namespace MediaBrowser.Controller.LiveTv
set { }
}
- /// <summary>
- /// Gets the user data key.
- /// </summary>
- /// <returns>System.String.</returns>
- protected override string CreateUserDataKey()
- {
- if (IsMovie)
- {
- var key = Movie.GetMovieUserDataKey(this);
-
- if (!string.IsNullOrWhiteSpace(key))
- {
- return key;
- }
- }
-
- if (IsSeries && !string.IsNullOrWhiteSpace(EpisodeTitle))
- {
- var name = GetClientTypeName();
-
- return name + "-" + Name + (EpisodeTitle ?? string.Empty);
- }
-
- return base.CreateUserDataKey();
- }
-
[IgnoreDataMember]
public override string MediaType
{
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 6f429ed2f..0462117cb 100644
--- a/MediaBrowser.Controller/MediaBrowser.Controller.csproj
+++ b/MediaBrowser.Controller/MediaBrowser.Controller.csproj
@@ -75,20 +75,15 @@
</Compile>
<Compile Include="Activity\IActivityManager.cs" />
<Compile Include="Activity\IActivityRepository.cs" />
- <Compile Include="Channels\ChannelAudioItem.cs" />
- <Compile Include="Channels\ChannelFolderItem.cs" />
<Compile Include="Channels\ChannelItemInfo.cs" />
<Compile Include="Channels\ChannelItemResult.cs" />
<Compile Include="Channels\ChannelItemType.cs" />
<Compile Include="Channels\ChannelMediaInfo.cs" />
<Compile Include="Channels\ChannelParentalRating.cs" />
<Compile Include="Channels\ChannelSearchInfo.cs" />
- <Compile Include="Channels\ChannelVideoItem.cs" />
<Compile Include="Channels\IChannel.cs" />
- <Compile Include="Channels\IChannelItem.cs" />
<Compile Include="Channels\IChannelManager.cs" />
<Compile Include="Channels\Channel.cs" />
- <Compile Include="Channels\IChannelMediaItem.cs" />
<Compile Include="Channels\IHasCacheKey.cs" />
<Compile Include="Channels\IIndexableChannel.cs" />
<Compile Include="Channels\InternalAllChannelMediaQuery.cs" />
@@ -147,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" />
@@ -159,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" />
@@ -182,9 +176,11 @@
<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" />
+ <Compile Include="Health\IHealthMonitor.cs" />
<Compile Include="IO\ThrottledStream.cs" />
<Compile Include="Library\DeleteOptions.cs" />
<Compile Include="Library\ILibraryPostScanTask.cs" />
@@ -222,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" />
@@ -294,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" />
@@ -334,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 ddaf7ff6d..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
{
@@ -58,8 +59,6 @@ namespace MediaBrowser.Controller.MediaEncoding
}
}
- public bool? Cabac { get; set; }
-
public EncodingJobOptions()
{
@@ -76,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;
@@ -87,7 +86,6 @@ namespace MediaBrowser.Controller.MediaEncoding
MaxRefFrames = info.MaxRefFrames;
MaxVideoBitDepth = info.MaxVideoBitDepth;
SubtitleMethod = info.SubtitleDeliveryMethod;
- Cabac = info.Cabac;
Context = info.Context;
if (info.SubtitleDeliveryMethod != SubtitleDeliveryMethod.External)
diff --git a/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs b/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs
index 76ef054de..c8a8caa91 100644
--- a/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs
+++ b/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs
@@ -4,14 +4,17 @@ using System;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
+using MediaBrowser.Model.Dlna;
namespace MediaBrowser.Controller.MediaEncoding
{
/// <summary>
/// Interface IMediaEncoder
/// </summary>
- public interface IMediaEncoder
+ public interface IMediaEncoder : ITranscoderSupport
{
+ string EncoderLocationType { get; }
+
/// <summary>
/// Gets the encoder path.
/// </summary>
@@ -19,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>
@@ -38,7 +35,7 @@ namespace MediaBrowser.Controller.MediaEncoding
/// <param name="imageStreamIndex">Index of the image stream.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>Task{Stream}.</returns>
- Task<Stream> ExtractAudioImage(string path, int? imageStreamIndex, CancellationToken cancellationToken);
+ Task<string> ExtractAudioImage(string path, int? imageStreamIndex, CancellationToken cancellationToken);
/// <summary>
/// Extracts the video image.
@@ -49,7 +46,9 @@ namespace MediaBrowser.Controller.MediaEncoding
/// <param name="offset">The offset.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>Task{Stream}.</returns>
- Task<Stream> ExtractVideoImage(string[] inputFiles, MediaProtocol protocol, Video3DFormat? threedFormat, TimeSpan? offset, CancellationToken cancellationToken);
+ Task<string> ExtractVideoImage(string[] inputFiles, MediaProtocol protocol, Video3DFormat? threedFormat, TimeSpan? offset, CancellationToken cancellationToken);
+
+ Task<string> ExtractVideoImage(string[] inputFiles, MediaProtocol protocol, int? imageStreamIndex, CancellationToken cancellationToken);
/// <summary>
/// Extracts the video images on interval.
@@ -63,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);
@@ -131,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 826711e51..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>
@@ -33,6 +27,6 @@ namespace MediaBrowser.Controller.MediaEncoding
/// <param name="protocol">The protocol.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>System.String.</returns>
- Task<string> GetSubtitleFileCharacterSet(string path, MediaProtocol protocol, CancellationToken cancellationToken);
+ Task<string> GetSubtitleFileCharacterSet(string path, string language, MediaProtocol protocol, CancellationToken cancellationToken);
}
}
diff --git a/MediaBrowser.Controller/Net/IHttpResultFactory.cs b/MediaBrowser.Controller/Net/IHttpResultFactory.cs
index 526bf4be2..49d4614d8 100644
--- a/MediaBrowser.Controller/Net/IHttpResultFactory.cs
+++ b/MediaBrowser.Controller/Net/IHttpResultFactory.cs
@@ -28,6 +28,8 @@ namespace MediaBrowser.Controller.Net
/// <returns>System.Object.</returns>
object GetResult(object content, string contentType, IDictionary<string,string> responseHeaders = null);
+ object GetAsyncStreamWriter(Func<Stream,Task> streamWriter, IDictionary<string, string> responseHeaders = null);
+
/// <summary>
/// Gets the optimized result.
/// </summary>
@@ -80,7 +82,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 +96,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 +105,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 +113,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/Notifications/INotificationsRepository.cs b/MediaBrowser.Controller/Notifications/INotificationsRepository.cs
index 6ad4a5377..cd587a509 100644
--- a/MediaBrowser.Controller/Notifications/INotificationsRepository.cs
+++ b/MediaBrowser.Controller/Notifications/INotificationsRepository.cs
@@ -19,12 +19,6 @@ namespace MediaBrowser.Controller.Notifications
/// Occurs when [notifications marked read].
/// </summary>
event EventHandler<NotificationReadEventArgs> NotificationsMarkedRead;
-
- /// <summary>
- /// Opens the connection to the repository
- /// </summary>
- /// <returns>Task.</returns>
- Task Initialize();
/// <summary>
/// Gets the notifications.
diff --git a/MediaBrowser.Controller/Persistence/IDisplayPreferencesRepository.cs b/MediaBrowser.Controller/Persistence/IDisplayPreferencesRepository.cs
index 17de730cb..abf96994f 100644
--- a/MediaBrowser.Controller/Persistence/IDisplayPreferencesRepository.cs
+++ b/MediaBrowser.Controller/Persistence/IDisplayPreferencesRepository.cs
@@ -12,12 +12,6 @@ namespace MediaBrowser.Controller.Persistence
public interface IDisplayPreferencesRepository : IRepository
{
/// <summary>
- /// Opens the connection to the repository
- /// </summary>
- /// <returns>Task.</returns>
- Task Initialize();
-
- /// <summary>
/// Saves display preferences for an item
/// </summary>
/// <param name="displayPreferences">The display preferences.</param>
diff --git a/MediaBrowser.Controller/Persistence/IItemRepository.cs b/MediaBrowser.Controller/Persistence/IItemRepository.cs
index 15df1f649..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
@@ -14,12 +15,6 @@ namespace MediaBrowser.Controller.Persistence
public interface IItemRepository : IRepository
{
/// <summary>
- /// Opens the connection to the repository
- /// </summary>
- /// <returns>Task.</returns>
- Task Initialize();
-
- /// <summary>
/// Saves an item
/// </summary>
/// <param name="item">The item.</param>
@@ -87,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.
@@ -103,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.
@@ -159,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.
@@ -167,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 2a904be0d..ca4dc9751 100644
--- a/MediaBrowser.Controller/Persistence/IUserDataRepository.cs
+++ b/MediaBrowser.Controller/Persistence/IUserDataRepository.cs
@@ -12,12 +12,6 @@ namespace MediaBrowser.Controller.Persistence
public interface IUserDataRepository : IRepository
{
/// <summary>
- /// Opens the connection to the repository
- /// </summary>
- /// <returns>Task.</returns>
- Task Initialize();
-
- /// <summary>
/// Saves the user data.
/// </summary>
/// <param name="userId">The user id.</param>
@@ -35,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/Playlists/Playlist.cs b/MediaBrowser.Controller/Playlists/Playlist.cs
index 0f9af6550..5ffe3d5da 100644
--- a/MediaBrowser.Controller/Playlists/Playlist.cs
+++ b/MediaBrowser.Controller/Playlists/Playlist.cs
@@ -6,6 +6,7 @@ using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
+using System.Threading.Tasks;
namespace MediaBrowser.Controller.Playlists
{
@@ -38,6 +39,15 @@ namespace MediaBrowser.Controller.Playlists
}
}
+ [IgnoreDataMember]
+ public override bool SupportsCumulativeRunTimeTicks
+ {
+ get
+ {
+ return true;
+ }
+ }
+
public override bool IsAuthorizedToDelete(User user)
{
return true;
@@ -50,16 +60,16 @@ namespace MediaBrowser.Controller.Playlists
public override IEnumerable<BaseItem> GetChildren(User user, bool includeLinkedChildren)
{
- return GetPlayableItems(user);
+ return GetPlayableItems(user).Result;
}
- public override IEnumerable<BaseItem> GetRecursiveChildren(User user, Func<BaseItem, bool> filter)
+ public override IEnumerable<BaseItem> GetRecursiveChildren(User user, InternalItemsQuery query)
{
- var items = GetPlayableItems(user);
+ var items = GetPlayableItems(user).Result;
- if (filter != null)
+ if (query != null)
{
- items = items.Where(filter);
+ items = items.Where(i => UserViewBuilder.FilterItem(i, query));
}
return items;
@@ -70,32 +80,40 @@ namespace MediaBrowser.Controller.Playlists
return GetLinkedChildrenInfos();
}
- private IEnumerable<BaseItem> GetPlayableItems(User user)
+ private Task<IEnumerable<BaseItem>> GetPlayableItems(User user)
{
return GetPlaylistItems(MediaType, base.GetChildren(user, true), user);
}
- public static IEnumerable<BaseItem> GetPlaylistItems(string playlistMediaType, IEnumerable<BaseItem> inputItems, User user)
+ public static async Task<IEnumerable<BaseItem>> GetPlaylistItems(string playlistMediaType, IEnumerable<BaseItem> inputItems, User user)
{
if (user != null)
{
inputItems = inputItems.Where(i => i.IsVisible(user));
}
- return inputItems.SelectMany(i => GetPlaylistItems(i, user))
- .Where(m => string.Equals(m.MediaType, playlistMediaType, StringComparison.OrdinalIgnoreCase));
+ var list = new List<BaseItem>();
+
+ foreach (var item in inputItems)
+ {
+ var playlistItems = await GetPlaylistItems(item, user, playlistMediaType).ConfigureAwait(false);
+ list.AddRange(playlistItems);
+ }
+
+ return list;
}
- private static IEnumerable<BaseItem> GetPlaylistItems(BaseItem item, User user)
+ private static async Task<IEnumerable<BaseItem>> GetPlaylistItems(BaseItem item, User user, string mediaType)
{
var musicGenre = item as MusicGenre;
if (musicGenre != null)
{
- Func<BaseItem, bool> filter = i => i is Audio && i.Genres.Contains(musicGenre.Name, StringComparer.OrdinalIgnoreCase);
-
- var items = user == null
- ? LibraryManager.RootFolder.GetRecursiveChildren(filter)
- : user.RootFolder.GetRecursiveChildren(user, filter);
+ var items = LibraryManager.GetItemList(new InternalItemsQuery(user)
+ {
+ Recursive = true,
+ IncludeItemTypes = new[] { typeof(Audio).Name },
+ Genres = new[] { musicGenre.Name }
+ });
return LibraryManager.Sort(items, user, new[] { ItemSortBy.AlbumArtist, ItemSortBy.Album, ItemSortBy.SortName }, SortOrder.Ascending);
}
@@ -111,7 +129,11 @@ namespace MediaBrowser.Controller.Playlists
var items = user == null
? LibraryManager.RootFolder.GetRecursiveChildren(filter)
- : user.RootFolder.GetRecursiveChildren(user, filter);
+ : user.RootFolder.GetRecursiveChildren(user, new InternalItemsQuery(user)
+ {
+ IncludeItemTypes = new[] { typeof(Audio).Name },
+ ArtistNames = new[] { musicArtist.Name }
+ });
return LibraryManager.Sort(items, user, new[] { ItemSortBy.AlbumArtist, ItemSortBy.Album, ItemSortBy.SortName }, SortOrder.Ascending);
}
@@ -119,15 +141,19 @@ namespace MediaBrowser.Controller.Playlists
var folder = item as Folder;
if (folder != null)
{
- var items = user == null
- ? folder.GetRecursiveChildren(m => !m.IsFolder)
- : folder.GetRecursiveChildren(user, m => !m.IsFolder);
-
- if (folder.IsPreSorted)
+ var query = new InternalItemsQuery(user)
{
- return items;
- }
- return LibraryManager.Sort(items, user, new[] { ItemSortBy.SortName }, SortOrder.Ascending);
+ Recursive = true,
+ IsFolder = false,
+ SortBy = new[] { ItemSortBy.SortName },
+ MediaTypes = new[] { mediaType },
+ EnableTotalRecordCount = false
+ };
+
+ var itemsResult = await folder.GetItems(query).ConfigureAwait(false);
+ var items = itemsResult.Items;
+
+ return items;
}
return new[] { item };
diff --git a/MediaBrowser.Controller/Providers/BaseItemXmlParser.cs b/MediaBrowser.Controller/Providers/BaseItemXmlParser.cs
index f9060d184..a783910e3 100644
--- a/MediaBrowser.Controller/Providers/BaseItemXmlParser.cs
+++ b/MediaBrowser.Controller/Providers/BaseItemXmlParser.cs
@@ -61,16 +61,7 @@ namespace MediaBrowser.Controller.Providers
};
//Fetch(item, metadataFile, settings, Encoding.GetEncoding("ISO-8859-1"), cancellationToken);
-
- try
- {
- Fetch(item, metadataFile, settings, Encoding.UTF8, cancellationToken);
- }
- catch
- {
- Logger.Error("Error parsing xml file {0}", metadataFile);
- throw;
- }
+ Fetch(item, metadataFile, settings, Encoding.UTF8, cancellationToken);
}
/// <summary>
@@ -812,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;
}
@@ -825,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;
}
@@ -1079,7 +1062,7 @@ namespace MediaBrowser.Controller.Providers
}
}
- private void FetchFromTagsNode(XmlReader reader, IHasTags item)
+ private void FetchFromTagsNode(XmlReader reader, BaseItem item)
{
reader.MoveToContent();
@@ -1108,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/DirectoryService.cs b/MediaBrowser.Controller/Providers/DirectoryService.cs
index c057c9707..e7e3323c2 100644
--- a/MediaBrowser.Controller/Providers/DirectoryService.cs
+++ b/MediaBrowser.Controller/Providers/DirectoryService.cs
@@ -102,6 +102,12 @@ namespace MediaBrowser.Controller.Providers
{
var directory = Path.GetDirectoryName(path);
+ if (string.IsNullOrWhiteSpace(directory))
+ {
+ _logger.Debug("Parent path is null for {0}", path);
+ return null;
+ }
+
var dict = GetFileSystemDictionary(directory, false);
FileSystemMetadata entry;
diff --git a/MediaBrowser.Controller/Providers/EpisodeInfo.cs b/MediaBrowser.Controller/Providers/EpisodeInfo.cs
index 28abd636a..b879040f8 100644
--- a/MediaBrowser.Controller/Providers/EpisodeInfo.cs
+++ b/MediaBrowser.Controller/Providers/EpisodeInfo.cs
@@ -10,6 +10,9 @@ namespace MediaBrowser.Controller.Providers
public int? IndexNumberEnd { get; set; }
public int? AnimeSeriesIndex { get; set; }
+ public bool IsMissingEpisode { get; set; }
+ public bool IsVirtualUnaired { get; set; }
+
public EpisodeInfo()
{
SeriesProviderIds = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
diff --git a/MediaBrowser.Controller/Providers/IHasItemChangeMonitor.cs b/MediaBrowser.Controller/Providers/IHasItemChangeMonitor.cs
index 4c7069dd6..9441c3ecd 100644
--- a/MediaBrowser.Controller/Providers/IHasItemChangeMonitor.cs
+++ b/MediaBrowser.Controller/Providers/IHasItemChangeMonitor.cs
@@ -8,9 +8,8 @@ namespace MediaBrowser.Controller.Providers
/// Determines whether the specified item has changed.
/// </summary>
/// <param name="item">The item.</param>
- /// <param name="status">The status.</param>
/// <param name="directoryService">The directory service.</param>
/// <returns><c>true</c> if the specified item has changed; otherwise, <c>false</c>.</returns>
- bool HasChanged(IHasMetadata item, MetadataStatus status, IDirectoryService directoryService);
+ bool HasChanged(IHasMetadata item, IDirectoryService directoryService);
}
} \ No newline at end of file
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 1f77d0ca1..000000000
--- a/MediaBrowser.Controller/Providers/IProviderRepository.cs
+++ /dev/null
@@ -1,31 +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);
-
- /// <summary>
- /// Initializes this instance.
- /// </summary>
- /// <returns>Task.</returns>
- Task Initialize();
- }
-}
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/MetadataRefreshOptions.cs b/MediaBrowser.Controller/Providers/MetadataRefreshOptions.cs
index 29f4feb3d..9427b2afd 100644
--- a/MediaBrowser.Controller/Providers/MetadataRefreshOptions.cs
+++ b/MediaBrowser.Controller/Providers/MetadataRefreshOptions.cs
@@ -1,5 +1,6 @@
using System.Linq;
using CommonIO;
+using MediaBrowser.Model.Providers;
namespace MediaBrowser.Controller.Providers
{
@@ -13,6 +14,7 @@ namespace MediaBrowser.Controller.Providers
public bool IsPostRecursiveRefresh { get; set; }
public MetadataRefreshMode MetadataRefreshMode { get; set; }
+ public RemoteSearchResult SearchResult { get; set; }
public bool ForceSave { get; set; }
@@ -37,6 +39,7 @@ namespace MediaBrowser.Controller.Providers
ImageRefreshMode = copy.ImageRefreshMode;
ReplaceAllImages = copy.ReplaceAllImages;
ReplaceImages = copy.ReplaceImages.ToList();
+ SearchResult = copy.SearchResult;
}
}
}
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.Controller/Subtitles/SubtitleSearchRequest.cs b/MediaBrowser.Controller/Subtitles/SubtitleSearchRequest.cs
index e781c048b..a2371426a 100644
--- a/MediaBrowser.Controller/Subtitles/SubtitleSearchRequest.cs
+++ b/MediaBrowser.Controller/Subtitles/SubtitleSearchRequest.cs
@@ -19,6 +19,7 @@ namespace MediaBrowser.Controller.Subtitles
public int? ParentIndexNumber { get; set; }
public int? ProductionYear { get; set; }
public long? RuntimeTicks { get; set; }
+ public bool IsPerfectMatch { get; set; }
public Dictionary<string, string> ProviderIds { get; set; }
public bool SearchAllProviders { get; set; }
diff --git a/MediaBrowser.Dlna/Channels/DlnaChannelFactory.cs b/MediaBrowser.Dlna/Channels/DlnaChannelFactory.cs
index b6ee3d434..a8e778751 100644
--- a/MediaBrowser.Dlna/Channels/DlnaChannelFactory.cs
+++ b/MediaBrowser.Dlna/Channels/DlnaChannelFactory.cs
@@ -1,37 +1,190 @@
-namespace MediaBrowser.Dlna.Channels
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+using MediaBrowser.Common.Net;
+using MediaBrowser.Controller.Channels;
+using MediaBrowser.Controller.Configuration;
+using MediaBrowser.Controller.Dlna;
+using MediaBrowser.Controller.Providers;
+using MediaBrowser.Dlna.ContentDirectory;
+using MediaBrowser.Dlna.PlayTo;
+using MediaBrowser.Model.Channels;
+using MediaBrowser.Model.Entities;
+using MediaBrowser.Model.Logging;
+
+namespace MediaBrowser.Dlna.Channels
{
- //public class DlnaChannelFactory : IChannelFactory, IDisposable
+ //public class DlnaChannel : IChannel, IDisposable
//{
- // private readonly IServerConfigurationManager _config;
// private readonly ILogger _logger;
// private readonly IHttpClient _httpClient;
+ // private readonly IServerConfigurationManager _config;
+ // private List<Device> _servers = new List<Device>();
// private readonly IDeviceDiscovery _deviceDiscovery;
-
// private readonly SemaphoreSlim _syncLock = new SemaphoreSlim(1, 1);
- // private List<Device> _servers = new List<Device>();
-
- // public static DlnaChannelFactory Instance;
-
// private Func<List<string>> _localServersLookup;
- // public DlnaChannelFactory(IServerConfigurationManager config, IHttpClient httpClient, ILogger logger, IDeviceDiscovery deviceDiscovery)
+ // public static DlnaChannel Current;
+
+ // public DlnaChannel(ILogger logger, IHttpClient httpClient, IDeviceDiscovery deviceDiscovery, IServerConfigurationManager config)
// {
- // _config = config;
- // _httpClient = httpClient;
// _logger = logger;
+ // _httpClient = httpClient;
// _deviceDiscovery = deviceDiscovery;
- // Instance = this;
+ // _config = config;
+ // Current = this;
+ // }
+
+ // public string Name
+ // {
+ // get { return "Devices"; }
+ // }
+
+ // public string Description
+ // {
+ // get { return string.Empty; }
+ // }
+
+ // public string DataVersion
+ // {
+ // get { return DateTime.UtcNow.Ticks.ToString(); }
+ // }
+
+ // public string HomePageUrl
+ // {
+ // get { return string.Empty; }
+ // }
+
+ // public ChannelParentalRating ParentalRating
+ // {
+ // get { return ChannelParentalRating.GeneralAudience; }
+ // }
+
+ // public InternalChannelFeatures GetChannelFeatures()
+ // {
+ // return new InternalChannelFeatures
+ // {
+ // ContentTypes = new List<ChannelMediaContentType>
+ // {
+ // ChannelMediaContentType.Song,
+ // ChannelMediaContentType.Clip
+ // },
+
+ // MediaTypes = new List<ChannelMediaType>
+ // {
+ // ChannelMediaType.Audio,
+ // ChannelMediaType.Video,
+ // ChannelMediaType.Photo
+ // }
+ // };
+ // }
+
+ // public bool IsEnabledFor(string userId)
+ // {
+ // return true;
+ // }
+
+ // public Task<DynamicImageResponse> GetChannelImage(ImageType type, CancellationToken cancellationToken)
+ // {
+ // throw new NotImplementedException();
+ // }
+
+ // public IEnumerable<ImageType> GetSupportedChannelImages()
+ // {
+ // return new List<ImageType>
+ // {
+ // ImageType.Primary
+ // };
// }
- // internal void Start(Func<List<string>> localServersLookup)
+ // public void Start(Func<List<string>> localServersLookup)
// {
// _localServersLookup = localServersLookup;
- // //deviceDiscovery.DeviceDiscovered += deviceDiscovery_DeviceDiscovered;
+ // _deviceDiscovery.DeviceDiscovered -= deviceDiscovery_DeviceDiscovered;
+ // _deviceDiscovery.DeviceLeft -= deviceDiscovery_DeviceLeft;
+
+ // _deviceDiscovery.DeviceDiscovered += deviceDiscovery_DeviceDiscovered;
// _deviceDiscovery.DeviceLeft += deviceDiscovery_DeviceLeft;
// }
+ // public async Task<ChannelItemResult> GetChannelItems(InternalChannelItemQuery query, CancellationToken cancellationToken)
+ // {
+ // if (string.IsNullOrWhiteSpace(query.FolderId))
+ // {
+ // return await GetServers(query, cancellationToken).ConfigureAwait(false);
+ // }
+
+ // return new ChannelItemResult();
+
+ // //var idParts = query.FolderId.Split('|');
+ // //var folderId = idParts.Length == 2 ? idParts[1] : null;
+
+ // //var result = await new ContentDirectoryBrowser(_httpClient, _logger).Browse(new ContentDirectoryBrowseRequest
+ // //{
+ // // Limit = query.Limit,
+ // // StartIndex = query.StartIndex,
+ // // ParentId = folderId,
+ // // ContentDirectoryUrl = ControlUrl
+
+ // //}, cancellationToken).ConfigureAwait(false);
+
+ // //items = result.Items.ToList();
+
+ // //var list = items.ToList();
+ // //var count = list.Count;
+
+ // //list = ApplyPaging(list, query).ToList();
+
+ // //return new ChannelItemResult
+ // //{
+ // // Items = list,
+ // // TotalRecordCount = count
+ // //};
+ // }
+
+ // public async Task<ChannelItemResult> GetServers(InternalChannelItemQuery query, CancellationToken cancellationToken)
+ // {
+ // await _syncLock.WaitAsync(cancellationToken).ConfigureAwait(false);
+
+ // try
+ // {
+ // var items = _servers.Select(i =>
+ // {
+ // var service = i.Properties.Services
+ // .FirstOrDefault(s => string.Equals(s.ServiceType, "urn:schemas-upnp-org:service:ContentDirectory:1", StringComparison.OrdinalIgnoreCase));
+
+ // var controlUrl = service == null ? null : (_servers[0].Properties.BaseUrl.TrimEnd('/') + "/" + service.ControlUrl.TrimStart('/'));
+
+ // if (string.IsNullOrWhiteSpace(controlUrl))
+ // {
+ // return null;
+ // }
+
+ // return new ChannelItemInfo
+ // {
+ // Id = i.Properties.UUID,
+ // Name = i.Properties.Name,
+ // Type = ChannelItemType.Folder
+ // };
+
+ // }).Where(i => i != null).ToList();
+
+ // return new ChannelItemResult
+ // {
+ // TotalRecordCount = items.Count,
+ // Items = items
+ // };
+ // }
+ // finally
+ // {
+ // _syncLock.Release();
+ // }
+ // }
+
// async void deviceDiscovery_DeviceDiscovered(object sender, SsdpMessageEventArgs e)
// {
// string usn;
@@ -57,16 +210,13 @@
// }
// }
- // if (GetExistingServers(usn).Any())
- // {
- // return;
- // }
-
// await _syncLock.WaitAsync().ConfigureAwait(false);
+ // var serverList = _servers.ToList();
+
// try
// {
- // if (GetExistingServers(usn).Any())
+ // if (GetExistingServers(serverList, usn).Any())
// {
// return;
// }
@@ -74,9 +224,9 @@
// var device = await Device.CreateuPnpDeviceAsync(new Uri(location), _httpClient, _config, _logger)
// .ConfigureAwait(false);
- // if (!_servers.Any(i => string.Equals(i.Properties.UUID, device.Properties.UUID, StringComparison.OrdinalIgnoreCase)))
+ // if (!serverList.Any(i => string.Equals(i.Properties.UUID, device.Properties.UUID, StringComparison.OrdinalIgnoreCase)))
// {
- // _servers.Add(device);
+ // serverList.Add(device);
// }
// }
// catch (Exception ex)
@@ -102,23 +252,22 @@
// return;
// }
- // if (!GetExistingServers(usn).Any())
- // {
- // return;
- // }
-
// await _syncLock.WaitAsync().ConfigureAwait(false);
// try
// {
- // var list = _servers.ToList();
+ // var serverList = _servers.ToList();
- // foreach (var device in GetExistingServers(usn).ToList())
+ // var matchingServers = GetExistingServers(serverList, usn);
+ // if (matchingServers.Count > 0)
// {
- // list.Remove(device);
- // }
+ // foreach (var device in matchingServers)
+ // {
+ // serverList.Remove(device);
+ // }
- // _servers = list;
+ // _servers = serverList;
+ // }
// }
// finally
// {
@@ -140,185 +289,17 @@
// return true;
// }
- // private IEnumerable<Device> GetExistingServers(string usn)
- // {
- // return _servers
- // .Where(i => usn.IndexOf(i.Properties.UUID, StringComparison.OrdinalIgnoreCase) != -1);
- // }
-
- // public IEnumerable<IChannel> GetChannels()
+ // private List<Device> GetExistingServers(List<Device> allDevices, string usn)
// {
- // //if (_servers.Count > 0)
- // //{
- // // var service = _servers[0].Properties.Services
- // // .FirstOrDefault(i => string.Equals(i.ServiceType, "urn:schemas-upnp-org:service:ContentDirectory:1", StringComparison.OrdinalIgnoreCase));
-
- // // var controlUrl = service == null ? null : (_servers[0].Properties.BaseUrl.TrimEnd('/') + "/" + service.ControlUrl.TrimStart('/'));
-
- // // if (!string.IsNullOrEmpty(controlUrl))
- // // {
- // // return new List<IChannel>
- // // {
- // // new ServerChannel(_servers.ToList(), _httpClient, _logger, controlUrl)
- // // };
- // // }
- // //}
-
- // return new List<IChannel>();
+ // return allDevices
+ // .Where(i => usn.IndexOf(i.Properties.UUID, StringComparison.OrdinalIgnoreCase) != -1)
+ // .ToList();
// }
// public void Dispose()
// {
- // if (_deviceDiscovery != null)
- // {
- // _deviceDiscovery.DeviceDiscovered -= deviceDiscovery_DeviceDiscovered;
- // _deviceDiscovery.DeviceLeft -= deviceDiscovery_DeviceLeft;
- // }
- // }
- //}
-
- //public class ServerChannel : IChannel, IFactoryChannel
- //{
- // private readonly IHttpClient _httpClient;
- // private readonly ILogger _logger;
- // public string ControlUrl { get; set; }
- // public List<Device> Servers { get; set; }
-
- // public ServerChannel(IHttpClient httpClient, ILogger logger)
- // {
- // _httpClient = httpClient;
- // _logger = logger;
- // Servers = new List<Device>();
- // }
-
- // public string Name
- // {
- // get { return "Devices"; }
- // }
-
- // public string Description
- // {
- // get { return string.Empty; }
- // }
-
- // public string DataVersion
- // {
- // get { return DateTime.UtcNow.Ticks.ToString(); }
- // }
-
- // public string HomePageUrl
- // {
- // get { return string.Empty; }
- // }
-
- // public ChannelParentalRating ParentalRating
- // {
- // get { return ChannelParentalRating.GeneralAudience; }
- // }
-
- // public InternalChannelFeatures GetChannelFeatures()
- // {
- // return new InternalChannelFeatures
- // {
- // ContentTypes = new List<ChannelMediaContentType>
- // {
- // ChannelMediaContentType.Song,
- // ChannelMediaContentType.Clip
- // },
-
- // MediaTypes = new List<ChannelMediaType>
- // {
- // ChannelMediaType.Audio,
- // ChannelMediaType.Video,
- // ChannelMediaType.Photo
- // }
- // };
- // }
-
- // public bool IsEnabledFor(string userId)
- // {
- // return true;
- // }
-
- // public async Task<ChannelItemResult> GetChannelItems(InternalChannelItemQuery query, CancellationToken cancellationToken)
- // {
- // IEnumerable<ChannelItemInfo> items;
-
- // if (string.IsNullOrWhiteSpace(query.FolderId))
- // {
- // items = Servers.Select(i => new ChannelItemInfo
- // {
- // FolderType = ChannelFolderType.Container,
- // Id = GetServerId(i),
- // Name = i.Properties.Name,
- // Overview = i.Properties.ModelDescription,
- // Type = ChannelItemType.Folder
- // });
- // }
- // else
- // {
- // var idParts = query.FolderId.Split('|');
- // var folderId = idParts.Length == 2 ? idParts[1] : null;
-
- // var result = await new ContentDirectoryBrowser(_httpClient, _logger).Browse(new ContentDirectoryBrowseRequest
- // {
- // Limit = query.Limit,
- // StartIndex = query.StartIndex,
- // ParentId = folderId,
- // ContentDirectoryUrl = ControlUrl
-
- // }, cancellationToken).ConfigureAwait(false);
-
- // items = result.Items.ToList();
- // }
-
- // var list = items.ToList();
- // var count = list.Count;
-
- // list = ApplyPaging(list, query).ToList();
-
- // return new ChannelItemResult
- // {
- // Items = list,
- // TotalRecordCount = count
- // };
- // }
-
- // private string GetServerId(Device device)
- // {
- // return device.Properties.UUID.GetMD5().ToString("N");
- // }
-
- // private IEnumerable<T> ApplyPaging<T>(IEnumerable<T> items, InternalChannelItemQuery query)
- // {
- // if (query.StartIndex.HasValue)
- // {
- // items = items.Skip(query.StartIndex.Value);
- // }
-
- // if (query.Limit.HasValue)
- // {
- // items = items.Take(query.Limit.Value);
- // }
-
- // return items;
- // }
-
- // public Task<DynamicImageResponse> GetChannelImage(ImageType type, CancellationToken cancellationToken)
- // {
- // // TODO: Implement
- // return Task.FromResult(new DynamicImageResponse
- // {
- // HasImage = false
- // });
- // }
-
- // public IEnumerable<ImageType> GetSupportedChannelImages()
- // {
- // return new List<ImageType>
- // {
- // ImageType.Primary
- // };
+ // _deviceDiscovery.DeviceDiscovered -= deviceDiscovery_DeviceDiscovered;
+ // _deviceDiscovery.DeviceLeft -= deviceDiscovery_DeviceLeft;
// }
//}
}
diff --git a/MediaBrowser.Dlna/ContentDirectory/ContentDirectory.cs b/MediaBrowser.Dlna/ContentDirectory/ContentDirectory.cs
index 21289970e..093b37df3 100644
--- a/MediaBrowser.Dlna/ContentDirectory/ContentDirectory.cs
+++ b/MediaBrowser.Dlna/ContentDirectory/ContentDirectory.cs
@@ -12,6 +12,7 @@ using MediaBrowser.Model.Logging;
using System;
using System.Collections.Generic;
using System.Linq;
+using MediaBrowser.Controller.MediaEncoding;
namespace MediaBrowser.Dlna.ContentDirectory
{
@@ -27,6 +28,7 @@ namespace MediaBrowser.Dlna.ContentDirectory
private readonly IChannelManager _channelManager;
private readonly IMediaSourceManager _mediaSourceManager;
private readonly IUserViewManager _userViewManager;
+ private readonly Func<IMediaEncoder> _mediaEncoder;
public ContentDirectory(IDlnaManager dlna,
IUserDataManager userDataManager,
@@ -35,7 +37,7 @@ namespace MediaBrowser.Dlna.ContentDirectory
IServerConfigurationManager config,
IUserManager userManager,
ILogger logger,
- IHttpClient httpClient, ILocalizationManager localization, IChannelManager channelManager, IMediaSourceManager mediaSourceManager, IUserViewManager userViewManager)
+ IHttpClient httpClient, ILocalizationManager localization, IChannelManager channelManager, IMediaSourceManager mediaSourceManager, IUserViewManager userViewManager, Func<IMediaEncoder> mediaEncoder)
: base(logger, httpClient)
{
_dlna = dlna;
@@ -48,6 +50,7 @@ namespace MediaBrowser.Dlna.ContentDirectory
_channelManager = channelManager;
_mediaSourceManager = mediaSourceManager;
_userViewManager = userViewManager;
+ _mediaEncoder = mediaEncoder;
}
private int SystemUpdateId
@@ -89,7 +92,8 @@ namespace MediaBrowser.Dlna.ContentDirectory
_localization,
_channelManager,
_mediaSourceManager,
- _userViewManager)
+ _userViewManager,
+ _mediaEncoder())
.ProcessControlRequest(request);
}
diff --git a/MediaBrowser.Dlna/ContentDirectory/ControlHandler.cs b/MediaBrowser.Dlna/ContentDirectory/ControlHandler.cs
index 01c7c33b6..233ec9546 100644
--- a/MediaBrowser.Dlna/ContentDirectory/ControlHandler.cs
+++ b/MediaBrowser.Dlna/ContentDirectory/ControlHandler.cs
@@ -23,6 +23,7 @@ using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Xml;
+using MediaBrowser.Controller.MediaEncoding;
namespace MediaBrowser.Dlna.ContentDirectory
{
@@ -34,6 +35,7 @@ namespace MediaBrowser.Dlna.ContentDirectory
private readonly IServerConfigurationManager _config;
private readonly User _user;
private readonly IUserViewManager _userViewManager;
+ private readonly IMediaEncoder _mediaEncoder;
private const string NS_DC = "http://purl.org/dc/elements/1.1/";
private const string NS_DIDL = "urn:schemas-upnp-org:metadata-1-0/DIDL-Lite/";
@@ -47,7 +49,7 @@ namespace MediaBrowser.Dlna.ContentDirectory
private readonly DeviceProfile _profile;
- public ControlHandler(ILogger logger, ILibraryManager libraryManager, DeviceProfile profile, string serverAddress, string accessToken, IImageProcessor imageProcessor, IUserDataManager userDataManager, User user, int systemUpdateId, IServerConfigurationManager config, ILocalizationManager localization, IChannelManager channelManager, IMediaSourceManager mediaSourceManager, IUserViewManager userViewManager)
+ public ControlHandler(ILogger logger, ILibraryManager libraryManager, DeviceProfile profile, string serverAddress, string accessToken, IImageProcessor imageProcessor, IUserDataManager userDataManager, User user, int systemUpdateId, IServerConfigurationManager config, ILocalizationManager localization, IChannelManager channelManager, IMediaSourceManager mediaSourceManager, IUserViewManager userViewManager, IMediaEncoder mediaEncoder)
: base(config, logger)
{
_libraryManager = libraryManager;
@@ -56,10 +58,11 @@ namespace MediaBrowser.Dlna.ContentDirectory
_systemUpdateId = systemUpdateId;
_channelManager = channelManager;
_userViewManager = userViewManager;
+ _mediaEncoder = mediaEncoder;
_profile = profile;
_config = config;
- _didlBuilder = new DidlBuilder(profile, user, imageProcessor, serverAddress, accessToken, userDataManager, localization, mediaSourceManager, Logger, libraryManager);
+ _didlBuilder = new DidlBuilder(profile, user, imageProcessor, serverAddress, accessToken, userDataManager, localization, mediaSourceManager, Logger, libraryManager, _mediaEncoder);
}
protected override IEnumerable<KeyValuePair<string, string>> GetResult(string methodName, Headers methodParams)
@@ -108,7 +111,7 @@ namespace MediaBrowser.Dlna.ContentDirectory
var newbookmark = int.Parse(sparams["PosSecond"], _usCulture);
- var userdata = _userDataManager.GetUserData(user.Id, item.GetUserDataKey());
+ var userdata = _userDataManager.GetUserData(user, item);
userdata.PlaybackPositionTicks = TimeSpan.FromSeconds(newbookmark).Ticks;
@@ -398,10 +401,10 @@ namespace MediaBrowser.Dlna.ContentDirectory
SortOrder = sort.SortOrder,
User = user,
Recursive = true,
- Filter = FilterUnsupportedContent,
+ IsMissing = false,
+ ExcludeItemTypes = new[] { typeof(Game).Name, typeof(Book).Name },
IsFolder = isFolder,
MediaTypes = mediaTypes.ToArray()
-
});
}
@@ -458,8 +461,10 @@ namespace MediaBrowser.Dlna.ContentDirectory
SortBy = sortOrders.ToArray(),
SortOrder = sort.SortOrder,
User = user,
- Filter = FilterUnsupportedContent,
- PresetViews = new[] { CollectionType.Movies, CollectionType.TvShows, CollectionType.Music }
+ IsMissing = false,
+ PresetViews = new[] { CollectionType.Movies, CollectionType.TvShows, CollectionType.Music },
+ ExcludeItemTypes = new[] { typeof(Game).Name, typeof(Book).Name },
+ IsPlaceHolder = false
}).ConfigureAwait(false);
@@ -576,29 +581,6 @@ namespace MediaBrowser.Dlna.ContentDirectory
});
}
- private bool FilterUnsupportedContent(BaseItem i)
- {
- // Unplayable
- if (i.LocationType == LocationType.Virtual && !i.IsFolder)
- {
- return false;
- }
-
- // Unplayable
- var supportsPlaceHolder = i as ISupportsPlaceHolders;
- if (supportsPlaceHolder != null && supportsPlaceHolder.IsPlaceHolder)
- {
- return false;
- }
-
- if (i is Game || i is Book)
- {
- //return false;
- }
-
- return true;
- }
-
private ServerItem GetItemFromObjectId(string id, User user)
{
return DidlBuilder.IsIdRoot(id)
diff --git a/MediaBrowser.Dlna/Didl/DidlBuilder.cs b/MediaBrowser.Dlna/Didl/DidlBuilder.cs
index e8e969a5f..84c1b3bde 100644
--- a/MediaBrowser.Dlna/Didl/DidlBuilder.cs
+++ b/MediaBrowser.Dlna/Didl/DidlBuilder.cs
@@ -19,6 +19,7 @@ using System.Globalization;
using System.IO;
using System.Linq;
using System.Xml;
+using MediaBrowser.Controller.MediaEncoding;
using MediaBrowser.Model.Configuration;
namespace MediaBrowser.Dlna.Didl
@@ -42,8 +43,9 @@ namespace MediaBrowser.Dlna.Didl
private readonly IMediaSourceManager _mediaSourceManager;
private readonly ILogger _logger;
private readonly ILibraryManager _libraryManager;
+ private readonly IMediaEncoder _mediaEncoder;
- public DidlBuilder(DeviceProfile profile, User user, IImageProcessor imageProcessor, string serverAddress, string accessToken, IUserDataManager userDataManager, ILocalizationManager localization, IMediaSourceManager mediaSourceManager, ILogger logger, ILibraryManager libraryManager)
+ public DidlBuilder(DeviceProfile profile, User user, IImageProcessor imageProcessor, string serverAddress, string accessToken, IUserDataManager userDataManager, ILocalizationManager localization, IMediaSourceManager mediaSourceManager, ILogger logger, ILibraryManager libraryManager, IMediaEncoder mediaEncoder)
{
_profile = profile;
_imageProcessor = imageProcessor;
@@ -53,6 +55,7 @@ namespace MediaBrowser.Dlna.Didl
_mediaSourceManager = mediaSourceManager;
_logger = logger;
_libraryManager = libraryManager;
+ _mediaEncoder = mediaEncoder;
_accessToken = accessToken;
_user = user;
}
@@ -93,10 +96,10 @@ namespace MediaBrowser.Dlna.Didl
}
else
{
- var parent = item.DisplayParent;
- if (parent != null)
+ var parent = item.DisplayParentId;
+ if (parent.HasValue)
{
- element.SetAttribute("parentID", GetClientId(parent, null));
+ element.SetAttribute("parentID", GetClientId(parent.Value, null));
}
}
@@ -142,7 +145,7 @@ namespace MediaBrowser.Dlna.Didl
{
var sources = _mediaSourceManager.GetStaticMediaSources(video, true, _user).ToList();
- streamInfo = new StreamBuilder(GetStreamBuilderLogger(options)).BuildVideoItem(new VideoOptions
+ streamInfo = new StreamBuilder(_mediaEncoder, GetStreamBuilderLogger(options)).BuildVideoItem(new VideoOptions
{
ItemId = GetClientId(video),
MediaSources = sources,
@@ -157,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,
@@ -171,7 +174,6 @@ namespace MediaBrowser.Dlna.Didl
streamInfo.TargetPacketLength,
streamInfo.TranscodeSeekInfo,
streamInfo.IsTargetAnamorphic,
- streamInfo.IsTargetCabac,
streamInfo.TargetRefFrames,
streamInfo.TargetVideoStreamCount,
streamInfo.TargetAudioStreamCount,
@@ -305,7 +307,7 @@ namespace MediaBrowser.Dlna.Didl
}
var mediaProfile = _profile.GetVideoMediaProfile(streamInfo.Container,
- streamInfo.AudioCodec,
+ streamInfo.TargetAudioCodec,
streamInfo.VideoCodec,
streamInfo.TargetAudioBitrate,
targetWidth,
@@ -317,7 +319,6 @@ namespace MediaBrowser.Dlna.Didl
streamInfo.TargetPacketLength,
streamInfo.TargetTimestamp,
streamInfo.IsTargetAnamorphic,
- streamInfo.IsTargetCabac,
streamInfo.TargetRefFrames,
streamInfo.TargetVideoStreamCount,
streamInfo.TargetAudioStreamCount,
@@ -387,7 +388,7 @@ namespace MediaBrowser.Dlna.Didl
{
var sources = _mediaSourceManager.GetStaticMediaSources(audio, true, _user).ToList();
- streamInfo = new StreamBuilder(GetStreamBuilderLogger(options)).BuildAudioItem(new AudioOptions
+ streamInfo = new StreamBuilder(_mediaEncoder, GetStreamBuilderLogger(options)).BuildAudioItem(new AudioOptions
{
ItemId = GetClientId(audio),
MediaSources = sources,
@@ -440,7 +441,7 @@ namespace MediaBrowser.Dlna.Didl
}
var mediaProfile = _profile.GetAudioMediaProfile(streamInfo.Container,
- streamInfo.AudioCodec,
+ streamInfo.TargetAudioCodec,
targetChannels,
targetAudioBitrate);
@@ -501,14 +502,21 @@ namespace MediaBrowser.Dlna.Didl
{
container.SetAttribute("id", clientId);
- var parent = context ?? folder.DisplayParent;
- if (parent == null)
+ if (context != null)
{
- container.SetAttribute("parentID", "0");
+ container.SetAttribute("parentID", GetClientId(context, null));
}
else
{
- container.SetAttribute("parentID", GetClientId(parent, null));
+ var parent = folder.DisplayParentId;
+ if (!parent.HasValue)
+ {
+ container.SetAttribute("parentID", "0");
+ }
+ else
+ {
+ container.SetAttribute("parentID", GetClientId(parent.Value, null));
+ }
}
}
@@ -812,7 +820,7 @@ namespace MediaBrowser.Dlna.Didl
var episode = item as Episode;
if (episode != null)
{
- var parent = (BaseItem)episode.Series ?? episode.Season;
+ var parent = episode.Series;
if (parent != null)
{
imageInfo = GetImageInfo(parent);
@@ -838,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)
@@ -848,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)
{
@@ -981,7 +989,10 @@ namespace MediaBrowser.Dlna.Didl
if (item != null)
{
- return GetImageInfo(item, ImageType.Primary);
+ if (item.HasImage(ImageType.Primary))
+ {
+ return GetImageInfo(item, ImageType.Primary);
+ }
}
return null;
@@ -1004,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('.')
@@ -1058,7 +1069,12 @@ namespace MediaBrowser.Dlna.Didl
public static string GetClientId(BaseItem item, StubType? stubType)
{
- var id = item.Id.ToString("N");
+ return GetClientId(item.Id, stubType);
+ }
+
+ public static string GetClientId(Guid idValue, StubType? stubType)
+ {
+ var id = idValue.ToString("N");
if (stubType.HasValue)
{
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 37584f006..b9d9944ec 100644
--- a/MediaBrowser.Dlna/Main/DlnaEntryPoint.cs
+++ b/MediaBrowser.Dlna/Main/DlnaEntryPoint.cs
@@ -14,6 +14,10 @@ using MediaBrowser.Dlna.Ssdp;
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;
namespace MediaBrowser.Dlna.Main
{
@@ -34,11 +38,13 @@ namespace MediaBrowser.Dlna.Main
private readonly IUserDataManager _userDataManager;
private readonly ILocalizationManager _localization;
private readonly IMediaSourceManager _mediaSourceManager;
+ private readonly IMediaEncoder _mediaEncoder;
private readonly SsdpHandler _ssdpHandler;
private readonly IDeviceDiscovery _deviceDiscovery;
private readonly List<string> _registeredServerIds = new List<string>();
+ private bool _ssdpHandlerStarted;
private bool _dlnaServerStarted;
public DlnaEntryPoint(IServerConfigurationManager config,
@@ -54,7 +60,7 @@ namespace MediaBrowser.Dlna.Main
IUserDataManager userDataManager,
ILocalizationManager localization,
IMediaSourceManager mediaSourceManager,
- ISsdpHandler ssdpHandler, IDeviceDiscovery deviceDiscovery)
+ ISsdpHandler ssdpHandler, IDeviceDiscovery deviceDiscovery, IMediaEncoder mediaEncoder)
{
_config = config;
_appHost = appHost;
@@ -69,18 +75,29 @@ namespace MediaBrowser.Dlna.Main
_localization = localization;
_mediaSourceManager = mediaSourceManager;
_deviceDiscovery = deviceDiscovery;
+ _mediaEncoder = mediaEncoder;
_ssdpHandler = (SsdpHandler)ssdpHandler;
_logger = logManager.GetLogger("Dlna");
}
public void Run()
{
- StartSsdpHandler();
ReloadComponents();
+ _config.ConfigurationUpdated += _config_ConfigurationUpdated;
_config.NamedConfigurationUpdated += _config_NamedConfigurationUpdated;
}
+ private bool _lastEnableUpnP;
+ void _config_ConfigurationUpdated(object sender, EventArgs e)
+ {
+ if (_lastEnableUpnP != _config.Configuration.EnableUPnP)
+ {
+ ReloadComponents();
+ }
+ _lastEnableUpnP = _config.Configuration.EnableUPnP;
+ }
+
void _config_NamedConfigurationUpdated(object sender, ConfigurationUpdateEventArgs e)
{
if (string.Equals(e.Key, "dlna", StringComparison.OrdinalIgnoreCase))
@@ -89,15 +106,32 @@ namespace MediaBrowser.Dlna.Main
}
}
- private void ReloadComponents()
+ private async void ReloadComponents()
{
- var isServerStarted = _dlnaServerStarted;
-
var options = _config.GetDlnaConfiguration();
+ if (!options.EnableServer && !options.EnablePlayTo && !_config.Configuration.EnableUPnP)
+ {
+ if (_ssdpHandlerStarted)
+ {
+ // Sat/ip live tv depends on device discovery, as well as hd homerun detection
+ // In order to allow this to be disabled, we need a modular way of knowing if there are
+ // any parts of the system that are dependant on it
+ // DisposeSsdpHandler();
+ }
+ return;
+ }
+
+ if (!_ssdpHandlerStarted)
+ {
+ StartSsdpHandler();
+ }
+
+ var isServerStarted = _dlnaServerStarted;
+
if (options.EnableServer && !isServerStarted)
{
- StartDlnaServer();
+ await StartDlnaServer().ConfigureAwait(false);
}
else if (!options.EnableServer && isServerStarted)
{
@@ -121,8 +155,9 @@ namespace MediaBrowser.Dlna.Main
try
{
_ssdpHandler.Start();
+ _ssdpHandlerStarted = true;
- ((DeviceDiscovery)_deviceDiscovery).Start(_ssdpHandler);
+ StartDeviceDiscovery();
}
catch (Exception ex)
{
@@ -130,11 +165,55 @@ namespace MediaBrowser.Dlna.Main
}
}
- public void StartDlnaServer()
+ private void StartDeviceDiscovery()
+ {
+ try
+ {
+ ((DeviceDiscovery)_deviceDiscovery).Start(_ssdpHandler);
+
+ //DlnaChannel.Current.Start(() => _registeredServerIds.ToList());
+ }
+ catch (Exception ex)
+ {
+ _logger.ErrorException("Error starting device discovery", ex);
+ }
+ }
+
+ private void DisposeDeviceDiscovery()
+ {
+ try
+ {
+ ((DeviceDiscovery)_deviceDiscovery).Dispose();
+ }
+ catch (Exception ex)
+ {
+ _logger.ErrorException("Error stopping device discovery", ex);
+ }
+ }
+
+ private void DisposeSsdpHandler()
+ {
+ DisposeDeviceDiscovery();
+
+ try
+ {
+ ((DeviceDiscovery)_deviceDiscovery).Dispose();
+
+ _ssdpHandler.Dispose();
+
+ _ssdpHandlerStarted = false;
+ }
+ catch (Exception ex)
+ {
+ _logger.ErrorException("Error stopping ssdp handlers", ex);
+ }
+ }
+
+ public async Task StartDlnaServer()
{
try
{
- RegisterServerEndpoints();
+ await RegisterServerEndpoints().ConfigureAwait(false);
_dlnaServerStarted = true;
}
@@ -144,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))
//{
@@ -196,7 +275,8 @@ namespace MediaBrowser.Dlna.Main
_config,
_userDataManager,
_localization,
- _mediaSourceManager);
+ _mediaSourceManager,
+ _mediaEncoder);
_manager.Start();
}
@@ -230,6 +310,7 @@ namespace MediaBrowser.Dlna.Main
{
DisposeDlnaServer();
DisposePlayToManager();
+ DisposeSsdpHandler();
}
public void DisposeDlnaServer()
diff --git a/MediaBrowser.Dlna/PlayTo/PlayToController.cs b/MediaBrowser.Dlna/PlayTo/PlayToController.cs
index 314756cdf..873ae5ae4 100644
--- a/MediaBrowser.Dlna/PlayTo/PlayToController.cs
+++ b/MediaBrowser.Dlna/PlayTo/PlayToController.cs
@@ -18,6 +18,7 @@ using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using MediaBrowser.Common.Configuration;
+using MediaBrowser.Controller.MediaEncoding;
namespace MediaBrowser.Dlna.PlayTo
{
@@ -35,6 +36,7 @@ namespace MediaBrowser.Dlna.PlayTo
private readonly ILocalizationManager _localization;
private readonly IMediaSourceManager _mediaSourceManager;
private readonly IConfigurationManager _config;
+ private readonly IMediaEncoder _mediaEncoder;
private readonly IDeviceDiscovery _deviceDiscovery;
private readonly string _serverAddress;
@@ -60,7 +62,7 @@ namespace MediaBrowser.Dlna.PlayTo
}
return false;
}
-
+
return _device != null;
}
}
@@ -74,7 +76,7 @@ namespace MediaBrowser.Dlna.PlayTo
get { return IsSessionActive; }
}
- public PlayToController(SessionInfo session, ISessionManager sessionManager, ILibraryManager libraryManager, ILogger logger, IDlnaManager dlnaManager, IUserManager userManager, IImageProcessor imageProcessor, string serverAddress, string accessToken, IDeviceDiscovery deviceDiscovery, IUserDataManager userDataManager, ILocalizationManager localization, IMediaSourceManager mediaSourceManager, IConfigurationManager config)
+ public PlayToController(SessionInfo session, ISessionManager sessionManager, ILibraryManager libraryManager, ILogger logger, IDlnaManager dlnaManager, IUserManager userManager, IImageProcessor imageProcessor, string serverAddress, string accessToken, IDeviceDiscovery deviceDiscovery, IUserDataManager userDataManager, ILocalizationManager localization, IMediaSourceManager mediaSourceManager, IConfigurationManager config, IMediaEncoder mediaEncoder)
{
_session = session;
_sessionManager = sessionManager;
@@ -88,6 +90,7 @@ namespace MediaBrowser.Dlna.PlayTo
_localization = localization;
_mediaSourceManager = mediaSourceManager;
_config = config;
+ _mediaEncoder = mediaEncoder;
_accessToken = accessToken;
_logger = logger;
_creationTime = DateTime.UtcNow;
@@ -116,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 ||
@@ -138,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);
@@ -148,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);
@@ -165,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;
@@ -227,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)
{
@@ -246,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)
{
@@ -374,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))
{
@@ -467,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()
@@ -478,7 +482,7 @@ namespace MediaBrowser.Dlna.PlayTo
playlistItem.StreamUrl = playlistItem.StreamInfo.ToDlnaUrl(_serverAddress, _accessToken);
- var itemXml = new DidlBuilder(profile, user, _imageProcessor, _serverAddress, _accessToken, _userDataManager, _localization, _mediaSourceManager, _logger, _libraryManager)
+ var itemXml = new DidlBuilder(profile, user, _imageProcessor, _serverAddress, _accessToken, _userDataManager, _localization, _mediaSourceManager, _logger, _libraryManager, _mediaEncoder)
.GetItemDidl(_config.GetDlnaConfiguration(), item, null, _session.DeviceId, new Filter(), playlistItem.StreamInfo);
playlistItem.Didl = itemXml;
@@ -495,7 +499,7 @@ namespace MediaBrowser.Dlna.PlayTo
{
return new ContentFeatureBuilder(profile)
.BuildAudioHeader(streamInfo.Container,
- streamInfo.AudioCodec,
+ streamInfo.TargetAudioCodec,
streamInfo.TargetAudioBitrate,
streamInfo.TargetAudioSampleRate,
streamInfo.TargetAudioChannels,
@@ -509,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,
@@ -523,7 +527,6 @@ namespace MediaBrowser.Dlna.PlayTo
streamInfo.TargetPacketLength,
streamInfo.TranscodeSeekInfo,
streamInfo.IsTargetAnamorphic,
- streamInfo.IsTargetCabac,
streamInfo.TargetRefFrames,
streamInfo.TargetVideoStreamCount,
streamInfo.TargetAudioStreamCount,
@@ -551,7 +554,7 @@ namespace MediaBrowser.Dlna.PlayTo
{
return new PlaylistItem
{
- StreamInfo = new StreamBuilder(GetStreamBuilderLogger()).BuildVideoItem(new VideoOptions
+ StreamInfo = new StreamBuilder(_mediaEncoder, GetStreamBuilderLogger()).BuildVideoItem(new VideoOptions
{
ItemId = item.Id.ToString("N"),
MediaSources = mediaSources,
@@ -571,7 +574,7 @@ namespace MediaBrowser.Dlna.PlayTo
{
return new PlaylistItem
{
- StreamInfo = new StreamBuilder(GetStreamBuilderLogger()).BuildAudioItem(new AudioOptions
+ StreamInfo = new StreamBuilder(_mediaEncoder, GetStreamBuilderLogger()).BuildAudioItem(new AudioOptions
{
ItemId = item.Id.ToString("N"),
MediaSources = mediaSources,
@@ -737,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)
{
@@ -763,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)
{
@@ -838,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
{
@@ -904,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 18daef331..cd9a7b1f0 100644
--- a/MediaBrowser.Dlna/PlayTo/PlayToManager.cs
+++ b/MediaBrowser.Dlna/PlayTo/PlayToManager.cs
@@ -12,6 +12,7 @@ using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
+using MediaBrowser.Controller.MediaEncoding;
namespace MediaBrowser.Dlna.PlayTo
{
@@ -32,11 +33,12 @@ namespace MediaBrowser.Dlna.PlayTo
private readonly IDeviceDiscovery _deviceDiscovery;
private readonly IMediaSourceManager _mediaSourceManager;
+ private readonly IMediaEncoder _mediaEncoder;
private readonly List<string> _nonRendererUrls = new List<string>();
private DateTime _lastRendererClear;
- public PlayToManager(ILogger logger, ISessionManager sessionManager, ILibraryManager libraryManager, IUserManager userManager, IDlnaManager dlnaManager, IServerApplicationHost appHost, IImageProcessor imageProcessor, IDeviceDiscovery deviceDiscovery, IHttpClient httpClient, IServerConfigurationManager config, IUserDataManager userDataManager, ILocalizationManager localization, IMediaSourceManager mediaSourceManager)
+ public PlayToManager(ILogger logger, ISessionManager sessionManager, ILibraryManager libraryManager, IUserManager userManager, IDlnaManager dlnaManager, IServerApplicationHost appHost, IImageProcessor imageProcessor, IDeviceDiscovery deviceDiscovery, IHttpClient httpClient, IServerConfigurationManager config, IUserDataManager userDataManager, ILocalizationManager localization, IMediaSourceManager mediaSourceManager, IMediaEncoder mediaEncoder)
{
_logger = logger;
_sessionManager = sessionManager;
@@ -51,6 +53,7 @@ namespace MediaBrowser.Dlna.PlayTo
_userDataManager = userDataManager;
_localization = localization;
_mediaSourceManager = mediaSourceManager;
+ _mediaEncoder = mediaEncoder;
}
public void Start()
@@ -98,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)
@@ -109,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);
@@ -132,7 +137,8 @@ namespace MediaBrowser.Dlna.PlayTo
_userDataManager,
_localization,
_mediaSourceManager,
- _config);
+ _config,
+ _mediaEncoder);
controller.Init(device);
diff --git a/MediaBrowser.Dlna/Profiles/BubbleUpnpProfile.cs b/MediaBrowser.Dlna/Profiles/BubbleUpnpProfile.cs
index a9af85346..8f3ad82ba 100644
--- a/MediaBrowser.Dlna/Profiles/BubbleUpnpProfile.cs
+++ b/MediaBrowser.Dlna/Profiles/BubbleUpnpProfile.cs
@@ -1,5 +1,5 @@
-using System.Xml.Serialization;
-using MediaBrowser.Model.Dlna;
+using MediaBrowser.Model.Dlna;
+using System.Xml.Serialization;
namespace MediaBrowser.Dlna.Profiles
{
@@ -10,8 +10,6 @@ namespace MediaBrowser.Dlna.Profiles
{
Name = "BubbleUPnp";
- TimelineOffsetSeconds = 5;
-
Identification = new DeviceIdentification
{
ModelName = "BubbleUPnp",
@@ -27,16 +25,18 @@ namespace MediaBrowser.Dlna.Profiles
new TranscodingProfile
{
Container = "mp3",
- Type = DlnaProfileType.Audio,
- AudioCodec = "mp3"
+ AudioCodec = "mp3",
+ Type = DlnaProfileType.Audio
},
+
new TranscodingProfile
{
Container = "ts",
Type = DlnaProfileType.Video,
- VideoCodec = "h264",
- AudioCodec = "aac"
+ AudioCodec = "aac",
+ VideoCodec = "h264"
},
+
new TranscodingProfile
{
Container = "jpeg",
@@ -48,21 +48,20 @@ namespace MediaBrowser.Dlna.Profiles
{
new DirectPlayProfile
{
- Container = "avi,mpeg,mkv,ts,mp4,mov,m4v,asf,webm,ogg,ogv,iso",
+ Container = "",
Type = DlnaProfileType.Video
},
new DirectPlayProfile
{
- Container = "mp3,flac,asf,off,oga,aac",
+ Container = "",
Type = DlnaProfileType.Audio
},
new DirectPlayProfile
{
+ Container = "",
Type = DlnaProfileType.Photo,
-
- Container = "jpeg,png,gif,bmp,tiff"
}
};
@@ -71,6 +70,77 @@ namespace MediaBrowser.Dlna.Profiles
ContainerProfiles = new ContainerProfile[] { };
CodecProfiles = new CodecProfile[] { };
+
+ SubtitleProfiles = new[]
+ {
+ new SubtitleProfile
+ {
+ Format = "srt",
+ Method = SubtitleDeliveryMethod.External,
+ },
+
+ new SubtitleProfile
+ {
+ Format = "sub",
+ Method = SubtitleDeliveryMethod.External,
+ },
+
+ new SubtitleProfile
+ {
+ Format = "srt",
+ Method = SubtitleDeliveryMethod.Embed,
+ DidlMode = "",
+ },
+
+ new SubtitleProfile
+ {
+ Format = "ass",
+ Method = SubtitleDeliveryMethod.Embed,
+ DidlMode = "",
+ },
+
+ new SubtitleProfile
+ {
+ Format = "ssa",
+ Method = SubtitleDeliveryMethod.Embed,
+ DidlMode = "",
+ },
+
+ new SubtitleProfile
+ {
+ Format = "smi",
+ Method = SubtitleDeliveryMethod.Embed,
+ DidlMode = "",
+ },
+
+ new SubtitleProfile
+ {
+ Format = "dvdsub",
+ Method = SubtitleDeliveryMethod.Embed,
+ DidlMode = "",
+ },
+
+ new SubtitleProfile
+ {
+ Format = "pgs",
+ Method = SubtitleDeliveryMethod.Embed,
+ DidlMode = "",
+ },
+
+ new SubtitleProfile
+ {
+ Format = "pgssub",
+ Method = SubtitleDeliveryMethod.Embed,
+ DidlMode = "",
+ },
+
+ new SubtitleProfile
+ {
+ Format = "sub",
+ Method = SubtitleDeliveryMethod.Embed,
+ DidlMode = "",
+ }
+ };
}
}
}
diff --git a/MediaBrowser.Dlna/Profiles/DefaultProfile.cs b/MediaBrowser.Dlna/Profiles/DefaultProfile.cs
index a8e6b1ca6..76797c0e3 100644
--- a/MediaBrowser.Dlna/Profiles/DefaultProfile.cs
+++ b/MediaBrowser.Dlna/Profiles/DefaultProfile.cs
@@ -15,10 +15,9 @@ namespace MediaBrowser.Dlna.Profiles
XDlnaDoc = "DMS-1.50";
- FriendlyName = "Emby";
Manufacturer = "Emby";
ModelDescription = "Emby";
- ModelName = "Emby";
+ ModelName = "Emby Server";
ModelNumber = "Emby";
ModelUrl = "http://emby.media/";
ManufacturerUrl = "http://emby.media/";
@@ -31,8 +30,8 @@ namespace MediaBrowser.Dlna.Profiles
MaxIconWidth = 48;
MaxIconHeight = 48;
- MaxStreamingBitrate = 15000000;
- MaxStaticBitrate = 15000000;
+ MaxStreamingBitrate = 20000000;
+ MaxStaticBitrate = 20000000;
MusicStreamingTranscodingBitrate = 192000;
MusicSyncBitrate = 192000;
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/VlcProfile.cs b/MediaBrowser.Dlna/Profiles/VlcProfile.cs
index 5b3f7c0d1..09d290f3a 100644
--- a/MediaBrowser.Dlna/Profiles/VlcProfile.cs
+++ b/MediaBrowser.Dlna/Profiles/VlcProfile.cs
@@ -1,5 +1,5 @@
-using System.Xml.Serialization;
-using MediaBrowser.Model.Dlna;
+using MediaBrowser.Model.Dlna;
+using System.Xml.Serialization;
namespace MediaBrowser.Dlna.Profiles
{
@@ -10,6 +10,7 @@ namespace MediaBrowser.Dlna.Profiles
{
Name = "Vlc";
+
TimelineOffsetSeconds = 5;
Identification = new DeviceIdentification
@@ -27,16 +28,18 @@ namespace MediaBrowser.Dlna.Profiles
new TranscodingProfile
{
Container = "mp3",
- Type = DlnaProfileType.Audio,
- AudioCodec = "mp3"
+ AudioCodec = "mp3",
+ Type = DlnaProfileType.Audio
},
+
new TranscodingProfile
{
Container = "ts",
Type = DlnaProfileType.Video,
- VideoCodec = "h264",
- AudioCodec = "aac"
+ AudioCodec = "aac",
+ VideoCodec = "h264"
},
+
new TranscodingProfile
{
Container = "jpeg",
@@ -48,21 +51,20 @@ namespace MediaBrowser.Dlna.Profiles
{
new DirectPlayProfile
{
- Container = "avi,mpeg,mkv,ts,mp4,mov,m4v,asf,webm,ogg,ogv,iso",
+ Container = "",
Type = DlnaProfileType.Video
},
new DirectPlayProfile
{
- Container = "mp3,flac,asf,off,oga,aac",
+ Container = "",
Type = DlnaProfileType.Audio
},
new DirectPlayProfile
{
+ Container = "",
Type = DlnaProfileType.Photo,
-
- Container = "jpeg,png,gif,bmp,tiff"
}
};
@@ -71,6 +73,77 @@ namespace MediaBrowser.Dlna.Profiles
ContainerProfiles = new ContainerProfile[] { };
CodecProfiles = new CodecProfile[] { };
+
+ SubtitleProfiles = new[]
+ {
+ new SubtitleProfile
+ {
+ Format = "srt",
+ Method = SubtitleDeliveryMethod.External,
+ },
+
+ new SubtitleProfile
+ {
+ Format = "sub",
+ Method = SubtitleDeliveryMethod.External,
+ },
+
+ new SubtitleProfile
+ {
+ Format = "srt",
+ Method = SubtitleDeliveryMethod.Embed,
+ DidlMode = "",
+ },
+
+ new SubtitleProfile
+ {
+ Format = "ass",
+ Method = SubtitleDeliveryMethod.Embed,
+ DidlMode = "",
+ },
+
+ new SubtitleProfile
+ {
+ Format = "ssa",
+ Method = SubtitleDeliveryMethod.Embed,
+ DidlMode = "",
+ },
+
+ new SubtitleProfile
+ {
+ Format = "smi",
+ Method = SubtitleDeliveryMethod.Embed,
+ DidlMode = "",
+ },
+
+ new SubtitleProfile
+ {
+ Format = "dvdsub",
+ Method = SubtitleDeliveryMethod.Embed,
+ DidlMode = "",
+ },
+
+ new SubtitleProfile
+ {
+ Format = "pgs",
+ Method = SubtitleDeliveryMethod.Embed,
+ DidlMode = "",
+ },
+
+ new SubtitleProfile
+ {
+ Format = "pgssub",
+ Method = SubtitleDeliveryMethod.Embed,
+ DidlMode = "",
+ },
+
+ new SubtitleProfile
+ {
+ Format = "sub",
+ Method = SubtitleDeliveryMethod.Embed,
+ DidlMode = "",
+ }
+ };
}
}
}
diff --git a/MediaBrowser.Dlna/Profiles/Xml/BubbleUPnp.xml b/MediaBrowser.Dlna/Profiles/Xml/BubbleUPnp.xml
index b80137dea..38b741454 100644
--- a/MediaBrowser.Dlna/Profiles/Xml/BubbleUPnp.xml
+++ b/MediaBrowser.Dlna/Profiles/Xml/BubbleUPnp.xml
@@ -7,10 +7,9 @@
<HttpHeaderInfo name="User-Agent" value="BubbleUPnp" match="Substring" />
</Headers>
</Identification>
- <FriendlyName>Emby</FriendlyName>
<Manufacturer>Emby</Manufacturer>
<ManufacturerUrl>http://emby.media/</ManufacturerUrl>
- <ModelName>Emby</ModelName>
+ <ModelName>Emby Server</ModelName>
<ModelDescription>Emby</ModelDescription>
<ModelNumber>Emby</ModelNumber>
<ModelUrl>http://emby.media/</ModelUrl>
@@ -23,30 +22,41 @@
<MaxAlbumArtHeight>480</MaxAlbumArtHeight>
<MaxIconWidth>48</MaxIconWidth>
<MaxIconHeight>48</MaxIconHeight>
- <MaxStreamingBitrate>15000000</MaxStreamingBitrate>
- <MaxStaticBitrate>15000000</MaxStaticBitrate>
+ <MaxStreamingBitrate>20000000</MaxStreamingBitrate>
+ <MaxStaticBitrate>20000000</MaxStaticBitrate>
<MusicStreamingTranscodingBitrate>192000</MusicStreamingTranscodingBitrate>
<MusicSyncBitrate>192000</MusicSyncBitrate>
<XDlnaDoc>DMS-1.50</XDlnaDoc>
<ProtocolInfo>http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=AVC_TS_HD_50_AC3;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=AVC_TS_HD_50_AC3_T;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=AVC_TS_HD_50_AC3_ISO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:audio/mpeg:DLNA.ORG_PN=MP3;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:audio/L16;rate=44100;channels=1:DLNA.ORG_PN=LPCM;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:audio/L16;rate=44100;channels=2:DLNA.ORG_PN=LPCM;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:audio/L16;rate=48000;channels=1:DLNA.ORG_PN=LPCM;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:audio/L16;rate=48000;channels=2:DLNA.ORG_PN=LPCM;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:audio/x-ms-wma:DLNA.ORG_PN=WMA_BASE;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:audio/x-ms-wma:DLNA.ORG_PN=WMA_FULL;DLNA.ORG_OP=01;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:image/jpeg:DLNA.ORG_PN=JPEG_SM;DLNA.ORG_OP=00;DLNA.ORG_FLAGS=00D00000000000000000000000000000,http-get:*:image/jpeg:DLNA.ORG_PN=JPEG_MED;DLNA.ORG_OP=00;DLNA.ORG_FLAGS=00D00000000000000000000000000000,http-get:*:image/jpeg:DLNA.ORG_PN=JPEG_LRG;DLNA.ORG_OP=00;DLNA.ORG_FLAGS=00D00000000000000000000000000000,http-get:*:image/jpeg:DLNA.ORG_PN=JPEG_TN;DLNA.ORG_OP=00;DLNA.ORG_FLAGS=00D00000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=MPEG1;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=MPEG_PS_PAL;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=MPEG_PS_NTSC;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=MPEG_TS_SD_EU;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=MPEG_TS_SD_EU_T;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=MPEG_TS_SD_EU_ISO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=MPEG_TS_SD_NA;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=MPEG_TS_SD_NA_T;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=MPEG_TS_SD_NA_ISO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=MPEG_TS_SD_KO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=MPEG_TS_SD_KO_T;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=MPEG_TS_SD_KO_ISO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/x-msvideo:DLNA.ORG_PN=AVI;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/x-matroska:DLNA.ORG_PN=MATROSKA;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mp4:DLNA.ORG_PN=AVC_MP4_MP_SD_AAC_MULT5;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mp4:DLNA.ORG_PN=AVC_MP4_MP_SD_MPEG1_L3;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mp4:DLNA.ORG_PN=AVC_MP4_MP_SD_AC3;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mp4:DLNA.ORG_PN=AVC_MP4_MP_HD_720p_AAC;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mp4:DLNA.ORG_PN=AVC_MP4_MP_HD_1080i_AAC;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mp4:DLNA.ORG_PN=AVC_MP4_HP_HD_AAC;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mp4:DLNA.ORG_PN=AVC_MP4_LPCM;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mp4:DLNA.ORG_PN=MPEG4_P2_MP4_ASP_AAC;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mp4:DLNA.ORG_PN=MPEG4_P2_MP4_SP_L6_AAC;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mp4:DLNA.ORG_PN=MPEG4_P2_MP4_NDSD;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=AVC_TS_MP_SD_AAC_MULT5;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=AVC_TS_MP_SD_AAC_MULT5_T;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=AVC_TS_MP_SD_AAC_MULT5_ISO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=AVC_TS_MP_SD_MPEG1_L3;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=AVC_TS_MP_SD_MPEG1_L3_T;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=AVC_TS_MP_SD_MPEG1_L3_ISO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=AVC_TS_MP_HD_AAC_MULT5;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=AVC_TS_MP_HD_AAC_MULT5_T;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=AVC_TS_MP_HD_AAC_MULT5_ISO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=AVC_TS_MP_HD_MPEG1_L3;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=AVC_TS_MP_HD_MPEG1_L3_T;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/mpeg:DLNA.ORG_PN=AVC_TS_MP_HD_MPEG1_L3_ISO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/vnd.dlna.mpeg-tts:DLNA.ORG_PN=AVC_TS_HD_50_LPCM_T;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/x-ms-wmv:DLNA.ORG_PN=WMVMED_BASE;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/x-ms-wmv:DLNA.ORG_PN=WMVMED_FULL;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/x-ms-wmv:DLNA.ORG_PN=WMVHIGH_FULL;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/x-ms-wmv:DLNA.ORG_PN=WMVMED_PRO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/x-ms-wmv:DLNA.ORG_PN=WMVHIGH_PRO;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/x-ms-asf:DLNA.ORG_PN=VC1_ASF_AP_L1_WMA;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/x-ms-asf:DLNA.ORG_PN=VC1_ASF_AP_L2_WMA;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000,http-get:*:video/x-ms-asf:DLNA.ORG_PN=VC1_ASF_AP_L3_WMA;DLNA.ORG_OP=11;DLNA.ORG_FLAGS=01500000000000000000000000000000</ProtocolInfo>
- <TimelineOffsetSeconds>5</TimelineOffsetSeconds>
+ <TimelineOffsetSeconds>0</TimelineOffsetSeconds>
<RequiresPlainVideoItems>false</RequiresPlainVideoItems>
<RequiresPlainFolders>false</RequiresPlainFolders>
<EnableMSMediaReceiverRegistrar>false</EnableMSMediaReceiverRegistrar>
<IgnoreTranscodeByteRangeRequests>false</IgnoreTranscodeByteRangeRequests>
<XmlRootAttributes />
<DirectPlayProfiles>
- <DirectPlayProfile container="avi,mpeg,mkv,ts,mp4,mov,m4v,asf,webm,ogg,ogv,iso" type="Video" />
- <DirectPlayProfile container="mp3,flac,asf,off,oga,aac" type="Audio" />
- <DirectPlayProfile container="jpeg,png,gif,bmp,tiff" type="Photo" />
+ <DirectPlayProfile container="" type="Video" />
+ <DirectPlayProfile container="" type="Audio" />
+ <DirectPlayProfile container="" type="Photo" />
</DirectPlayProfiles>
<TranscodingProfiles>
- <TranscodingProfile container="mp3" type="Audio" audioCodec="mp3" estimateContentLength="false" enableMpegtsM2TsMode="false" transcodeSeekInfo="Auto" copyTimestamps="false" context="Streaming" />
- <TranscodingProfile container="ts" type="Video" videoCodec="h264" audioCodec="aac" estimateContentLength="false" enableMpegtsM2TsMode="false" transcodeSeekInfo="Auto" copyTimestamps="false" context="Streaming" />
- <TranscodingProfile container="jpeg" type="Photo" estimateContentLength="false" enableMpegtsM2TsMode="false" transcodeSeekInfo="Auto" copyTimestamps="false" context="Streaming" />
+ <TranscodingProfile container="mp3" type="Audio" audioCodec="mp3" estimateContentLength="false" enableMpegtsM2TsMode="false" transcodeSeekInfo="Auto" copyTimestamps="false" context="Streaming" forceLiveStream="false" />
+ <TranscodingProfile container="ts" type="Video" videoCodec="h264" audioCodec="aac" estimateContentLength="false" enableMpegtsM2TsMode="false" transcodeSeekInfo="Auto" copyTimestamps="false" context="Streaming" forceLiveStream="false" />
+ <TranscodingProfile container="jpeg" type="Photo" estimateContentLength="false" enableMpegtsM2TsMode="false" transcodeSeekInfo="Auto" copyTimestamps="false" context="Streaming" forceLiveStream="false" />
</TranscodingProfiles>
<ContainerProfiles />
<CodecProfiles />
<ResponseProfiles />
- <SubtitleProfiles />
+ <SubtitleProfiles>
+ <SubtitleProfile format="srt" method="External" />
+ <SubtitleProfile format="sub" method="External" />
+ <SubtitleProfile format="srt" method="Embed" didlMode="" />
+ <SubtitleProfile format="ass" method="Embed" didlMode="" />
+ <SubtitleProfile format="ssa" method="Embed" didlMode="" />
+ <SubtitleProfile format="smi" method="Embed" didlMode="" />
+ <SubtitleProfile format="dvdsub" method="Embed" didlMode="" />
+ <SubtitleProfile format="pgs" method="Embed" didlMode="" />
+ <SubtitleProfile format="pgssub" method="Embed" didlMode="" />
+ <SubtitleProfile format="sub" method="Embed" didlMode="" />
+ </SubtitleProfiles>
</Profile> \ No newline at end of file
diff --git a/MediaBrowser.Dlna/Profiles/Xml/Default.xml b/MediaBrowser.Dlna/Profiles/Xml/Default.xml
index bfb4a7bc2..9364f464b 100644
--- a/MediaBrowser.Dlna/Profiles/Xml/Default.xml
+++ b/MediaBrowser.Dlna/Profiles/Xml/Default.xml
@@ -1,10 +1,9 @@
<?xml version="1.0"?>
<Profile xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<Name>Generic Device</Name>
- <FriendlyName>Emby</FriendlyName>
<Manufacturer>Emby</Manufacturer>
<ManufacturerUrl>http://emby.media/</ManufacturerUrl>
- <ModelName>Emby</ModelName>
+ <ModelName>Emby Server</ModelName>
<ModelDescription>Emby</ModelDescription>
<ModelNumber>Emby</ModelNumber>
<ModelUrl>http://emby.media/</ModelUrl>
@@ -17,8 +16,8 @@
<MaxAlbumArtHeight>480</MaxAlbumArtHeight>
<MaxIconWidth>48</MaxIconWidth>
<MaxIconHeight>48</MaxIconHeight>
- <MaxStreamingBitrate>15000000</MaxStreamingBitrate>
- <MaxStaticBitrate>15000000</MaxStaticBitrate>
+ <MaxStreamingBitrate>20000000</MaxStreamingBitrate>
+ <MaxStaticBitrate>20000000</MaxStaticBitrate>
<MusicStreamingTranscodingBitrate>192000</MusicStreamingTranscodingBitrate>
<MusicSyncBitrate>192000</MusicSyncBitrate>
<XDlnaDoc>DMS-1.50</XDlnaDoc>
@@ -34,9 +33,9 @@
<DirectPlayProfile container="avi,mp4" type="Video" />
</DirectPlayProfiles>
<TranscodingProfiles>
- <TranscodingProfile container="mp3" type="Audio" audioCodec="mp3" estimateContentLength="false" enableMpegtsM2TsMode="false" transcodeSeekInfo="Auto" copyTimestamps="false" context="Streaming" />
- <TranscodingProfile container="ts" type="Video" videoCodec="h264" audioCodec="aac" estimateContentLength="false" enableMpegtsM2TsMode="false" transcodeSeekInfo="Auto" copyTimestamps="false" context="Streaming" />
- <TranscodingProfile container="jpeg" type="Photo" estimateContentLength="false" enableMpegtsM2TsMode="false" transcodeSeekInfo="Auto" copyTimestamps="false" context="Streaming" />
+ <TranscodingProfile container="mp3" type="Audio" audioCodec="mp3" estimateContentLength="false" enableMpegtsM2TsMode="false" transcodeSeekInfo="Auto" copyTimestamps="false" context="Streaming" forceLiveStream="false" />
+ <TranscodingProfile container="ts" type="Video" videoCodec="h264" audioCodec="aac" estimateContentLength="false" enableMpegtsM2TsMode="false" transcodeSeekInfo="Auto" copyTimestamps="false" context="Streaming" forceLiveStream="false" />
+ <TranscodingProfile container="jpeg" type="Photo" estimateContentLength="false" enableMpegtsM2TsMode="false" transcodeSeekInfo="Auto" copyTimestamps="false" context="Streaming" forceLiveStream="false" />
</TranscodingProfiles>
<ContainerProfiles />
<CodecProfiles />
diff --git a/MediaBrowser.Dlna/Profiles/Xml/Denon AVR.xml b/MediaBrowser.Dlna/Profiles/Xml/Denon AVR.xml
index d0a5026fd..5b8ff5d68 100644
--- a/MediaBrowser.Dlna/Profiles/Xml/Denon AVR.xml
+++ b/MediaBrowser.Dlna/Profiles/Xml/Denon AVR.xml
@@ -6,10 +6,9 @@
<Manufacturer>Denon</Manufacturer>
<Headers />
</Identification>
- <FriendlyName>Emby</FriendlyName>
<Manufacturer>Emby</Manufacturer>
<ManufacturerUrl>http://emby.media/</ManufacturerUrl>
- <ModelName>Emby</ModelName>
+ <ModelName>Emby Server</ModelName>
<ModelDescription>Emby</ModelDescription>
<ModelNumber>Emby</ModelNumber>
<ModelUrl>http://emby.media/</ModelUrl>
@@ -22,8 +21,8 @@
<MaxAlbumArtHeight>480</MaxAlbumArtHeight>
<MaxIconWidth>48</MaxIconWidth>
<MaxIconHeight>48</MaxIconHeight>
- <MaxStreamingBitrate>15000000</MaxStreamingBitrate>
- <MaxStaticBitrate>15000000</MaxStaticBitrate>
+ <MaxStreamingBitrate>20000000</MaxStreamingBitrate>
+ <MaxStaticBitrate>20000000</MaxStaticBitrate>
<MusicStreamingTranscodingBitrate>192000</MusicStreamingTranscodingBitrate>
<MusicSyncBitrate>192000</MusicSyncBitrate>
<XDlnaDoc>DMS-1.50</XDlnaDoc>
@@ -38,9 +37,9 @@
<DirectPlayProfile container="mp3,flac,m4a,wma" type="Audio" />
</DirectPlayProfiles>
<TranscodingProfiles>
- <TranscodingProfile container="mp3" type="Audio" audioCodec="mp3" estimateContentLength="false" enableMpegtsM2TsMode="false" transcodeSeekInfo="Auto" copyTimestamps="false" context="Streaming" />
- <TranscodingProfile container="ts" type="Video" videoCodec="h264" audioCodec="aac" estimateContentLength="false" enableMpegtsM2TsMode="false" transcodeSeekInfo="Auto" copyTimestamps="false" context="Streaming" />
- <TranscodingProfile container="jpeg" type="Photo" estimateContentLength="false" enableMpegtsM2TsMode="false" transcodeSeekInfo="Auto" copyTimestamps="false" context="Streaming" />
+ <TranscodingProfile container="mp3" type="Audio" audioCodec="mp3" estimateContentLength="false" enableMpegtsM2TsMode="false" transcodeSeekInfo="Auto" copyTimestamps="false" context="Streaming" forceLiveStream="false" />
+ <TranscodingProfile container="ts" type="Video" videoCodec="h264" audioCodec="aac" estimateContentLength="false" enableMpegtsM2TsMode="false" transcodeSeekInfo="Auto" copyTimestamps="false" context="Streaming" forceLiveStream="false" />
+ <TranscodingProfile container="jpeg" type="Photo" estimateContentLength="false" enableMpegtsM2TsMode="false" transcodeSeekInfo="Auto" copyTimestamps="false" context="Streaming" forceLiveStream="false" />
</TranscodingProfiles>
<ContainerProfiles />
<CodecProfiles />
diff --git a/MediaBrowser.Dlna/Profiles/Xml/DirecTV HD-DVR.xml b/MediaBrowser.Dlna/Profiles/Xml/DirecTV HD-DVR.xml
index 5cc027549..561abe0e5 100644
--- a/MediaBrowser.Dlna/Profiles/Xml/DirecTV HD-DVR.xml
+++ b/MediaBrowser.Dlna/Profiles/Xml/DirecTV HD-DVR.xml
@@ -7,10 +7,9 @@
<HttpHeaderInfo name="User-Agent" value="DIRECTV" match="Substring" />
</Headers>
</Identification>
- <FriendlyName>Emby</FriendlyName>
<Manufacturer>Emby</Manufacturer>
<ManufacturerUrl>http://emby.media/</ManufacturerUrl>
- <ModelName>Emby</ModelName>
+ <ModelName>Emby Server</ModelName>
<ModelDescription>Emby</ModelDescription>
<ModelNumber>Emby</ModelNumber>
<ModelUrl>http://emby.media/</ModelUrl>
@@ -23,8 +22,8 @@
<MaxAlbumArtHeight>480</MaxAlbumArtHeight>
<MaxIconWidth>48</MaxIconWidth>
<MaxIconHeight>48</MaxIconHeight>
- <MaxStreamingBitrate>15000000</MaxStreamingBitrate>
- <MaxStaticBitrate>15000000</MaxStaticBitrate>
+ <MaxStreamingBitrate>20000000</MaxStreamingBitrate>
+ <MaxStaticBitrate>20000000</MaxStaticBitrate>
<MusicStreamingTranscodingBitrate>192000</MusicStreamingTranscodingBitrate>
<MusicSyncBitrate>192000</MusicSyncBitrate>
<XDlnaDoc>DMS-1.50</XDlnaDoc>
@@ -40,8 +39,8 @@
<DirectPlayProfile container="jpeg,jpg" type="Photo" />
</DirectPlayProfiles>
<TranscodingProfiles>
- <TranscodingProfile container="mpeg" type="Video" videoCodec="mpeg2video" audioCodec="mp2" estimateContentLength="false" enableMpegtsM2TsMode="false" transcodeSeekInfo="Auto" copyTimestamps="false" context="Streaming" />
- <TranscodingProfile container="jpeg" type="Photo" estimateContentLength="false" enableMpegtsM2TsMode="false" transcodeSeekInfo="Auto" copyTimestamps="false" context="Streaming" />
+ <TranscodingProfile container="mpeg" type="Video" videoCodec="mpeg2video" audioCodec="mp2" estimateContentLength="false" enableMpegtsM2TsMode="false" transcodeSeekInfo="Auto" copyTimestamps="false" context="Streaming" forceLiveStream="false" />
+ <TranscodingProfile container="jpeg" type="Photo" estimateContentLength="false" enableMpegtsM2TsMode="false" transcodeSeekInfo="Auto" copyTimestamps="false" context="Streaming" forceLiveStream="false" />
</TranscodingProfiles>
<ContainerProfiles />
<CodecProfiles>
diff --git a/MediaBrowser.Dlna/Profiles/Xml/Dish Hopper-Joey.xml b/MediaBrowser.Dlna/Profiles/Xml/Dish Hopper-Joey.xml
index dfd8730fb..b2d267657 100644
--- a/MediaBrowser.Dlna/Profiles/Xml/Dish Hopper-Joey.xml
+++ b/MediaBrowser.Dlna/Profiles/Xml/Dish Hopper-Joey.xml
@@ -8,10 +8,9 @@
<HttpHeaderInfo name="User-Agent" value="XiP" match="Substring" />
</Headers>
</Identification>
- <FriendlyName>Emby</FriendlyName>
<Manufacturer>Emby</Manufacturer>
<ManufacturerUrl>http://emby.media/</ManufacturerUrl>
- <ModelName>Emby</ModelName>
+ <ModelName>Emby Server</ModelName>
<ModelDescription>Emby</ModelDescription>
<ModelNumber>Emby</ModelNumber>
<ModelUrl>http://emby.media/</ModelUrl>
@@ -24,8 +23,8 @@
<MaxAlbumArtHeight>480</MaxAlbumArtHeight>
<MaxIconWidth>48</MaxIconWidth>
<MaxIconHeight>48</MaxIconHeight>
- <MaxStreamingBitrate>15000000</MaxStreamingBitrate>
- <MaxStaticBitrate>15000000</MaxStaticBitrate>
+ <MaxStreamingBitrate>20000000</MaxStreamingBitrate>
+ <MaxStaticBitrate>20000000</MaxStaticBitrate>
<MusicStreamingTranscodingBitrate>192000</MusicStreamingTranscodingBitrate>
<MusicSyncBitrate>192000</MusicSyncBitrate>
<XDlnaDoc>DMS-1.50</XDlnaDoc>
@@ -44,9 +43,9 @@
<DirectPlayProfile container="jpeg" type="Photo" />
</DirectPlayProfiles>
<TranscodingProfiles>
- <TranscodingProfile container="mp3" type="Audio" audioCodec="mp3" estimateContentLength="false" enableMpegtsM2TsMode="false" transcodeSeekInfo="Auto" copyTimestamps="false" context="Streaming" />
- <TranscodingProfile container="mp4" type="Video" videoCodec="h264" audioCodec="aac" estimateContentLength="false" enableMpegtsM2TsMode="false" transcodeSeekInfo="Auto" copyTimestamps="false" context="Streaming" />
- <TranscodingProfile container="jpeg" type="Photo" estimateContentLength="false" enableMpegtsM2TsMode="false" transcodeSeekInfo="Auto" copyTimestamps="false" context="Streaming" />
+ <TranscodingProfile container="mp3" type="Audio" audioCodec="mp3" estimateContentLength="false" enableMpegtsM2TsMode="false" transcodeSeekInfo="Auto" copyTimestamps="false" context="Streaming" forceLiveStream="false" />
+ <TranscodingProfile container="mp4" type="Video" videoCodec="h264" audioCodec="aac" estimateContentLength="false" enableMpegtsM2TsMode="false" transcodeSeekInfo="Auto" copyTimestamps="false" context="Streaming" forceLiveStream="false" />
+ <TranscodingProfile container="jpeg" type="Photo" estimateContentLength="false" enableMpegtsM2TsMode="false" transcodeSeekInfo="Auto" copyTimestamps="false" context="Streaming" forceLiveStream="false" />
</TranscodingProfiles>
<ContainerProfiles />
<CodecProfiles>
diff --git a/MediaBrowser.Dlna/Profiles/Xml/Kodi.xml b/MediaBrowser.Dlna/Profiles/Xml/Kodi.xml
index 48c4dcd8e..d0985e135 100644
--- a/MediaBrowser.Dlna/Profiles/Xml/Kodi.xml
+++ b/MediaBrowser.Dlna/Profiles/Xml/Kodi.xml
@@ -7,10 +7,9 @@
<HttpHeaderInfo name="User-Agent" value="Kodi" match="Substring" />
</Headers>
</Identification>
- <FriendlyName>Emby</FriendlyName>
<Manufacturer>Emby</Manufacturer>
<ManufacturerUrl>http://emby.media/</ManufacturerUrl>
- <ModelName>Emby</ModelName>
+ <ModelName>Emby Server</ModelName>
<ModelDescription>Emby</ModelDescription>
<ModelNumber>Emby</ModelNumber>
<ModelUrl>http://emby.media/</ModelUrl>
@@ -41,9 +40,9 @@
<DirectPlayProfile container="" type="Photo" />
</DirectPlayProfiles>
<TranscodingProfiles>
- <TranscodingProfile container="mp3" type="Audio" audioCodec="mp3" estimateContentLength="false" enableMpegtsM2TsMode="false" transcodeSeekInfo="Auto" copyTimestamps="false" context="Streaming" />
- <TranscodingProfile container="ts" type="Video" videoCodec="h264" audioCodec="aac" estimateContentLength="false" enableMpegtsM2TsMode="false" transcodeSeekInfo="Auto" copyTimestamps="false" context="Streaming" />
- <TranscodingProfile container="jpeg" type="Photo" estimateContentLength="false" enableMpegtsM2TsMode="false" transcodeSeekInfo="Auto" copyTimestamps="false" context="Streaming" />
+ <TranscodingProfile container="mp3" type="Audio" audioCodec="mp3" estimateContentLength="false" enableMpegtsM2TsMode="false" transcodeSeekInfo="Auto" copyTimestamps="false" context="Streaming" forceLiveStream="false" />
+ <TranscodingProfile container="ts" type="Video" videoCodec="h264" audioCodec="aac" estimateContentLength="false" enableMpegtsM2TsMode="false" transcodeSeekInfo="Auto" copyTimestamps="false" context="Streaming" forceLiveStream="false" />
+ <TranscodingProfile container="jpeg" type="Photo" estimateContentLength="false" enableMpegtsM2TsMode="false" transcodeSeekInfo="Auto" copyTimestamps="false" context="Streaming" forceLiveStream="false" />
</TranscodingProfiles>
<ContainerProfiles />
<CodecProfiles />
diff --git a/MediaBrowser.Dlna/Profiles/Xml/LG Smart TV.xml b/MediaBrowser.Dlna/Profiles/Xml/LG Smart TV.xml
index 6717504bf..1147aa299 100644
--- a/MediaBrowser.Dlna/Profiles/Xml/LG Smart TV.xml
+++ b/MediaBrowser.Dlna/Profiles/Xml/LG Smart TV.xml
@@ -7,10 +7,9 @@
<HttpHeaderInfo name="User-Agent" value="LG" match="Substring" />
</Headers>
</Identification>
- <FriendlyName>Emby</FriendlyName>
<Manufacturer>Emby</Manufacturer>
<ManufacturerUrl>http://emby.media/</ManufacturerUrl>
- <ModelName>Emby</ModelName>
+ <ModelName>Emby Server</ModelName>
<ModelDescription>Emby</ModelDescription>
<ModelNumber>Emby</ModelNumber>
<ModelUrl>http://emby.media/</ModelUrl>
@@ -23,8 +22,8 @@
<MaxAlbumArtHeight>480</MaxAlbumArtHeight>
<MaxIconWidth>48</MaxIconWidth>
<MaxIconHeight>48</MaxIconHeight>
- <MaxStreamingBitrate>15000000</MaxStreamingBitrate>
- <MaxStaticBitrate>15000000</MaxStaticBitrate>
+ <MaxStreamingBitrate>20000000</MaxStreamingBitrate>
+ <MaxStaticBitrate>20000000</MaxStaticBitrate>
<MusicStreamingTranscodingBitrate>192000</MusicStreamingTranscodingBitrate>
<MusicSyncBitrate>192000</MusicSyncBitrate>
<XDlnaDoc>DMS-1.50</XDlnaDoc>
@@ -36,16 +35,16 @@
<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>
<TranscodingProfiles>
- <TranscodingProfile container="mp3" type="Audio" audioCodec="mp3" estimateContentLength="false" enableMpegtsM2TsMode="false" transcodeSeekInfo="Auto" copyTimestamps="false" context="Streaming" />
- <TranscodingProfile container="ts" type="Video" videoCodec="h264" audioCodec="ac3" estimateContentLength="false" enableMpegtsM2TsMode="false" transcodeSeekInfo="Auto" copyTimestamps="false" context="Streaming" />
- <TranscodingProfile container="jpeg" type="Photo" estimateContentLength="false" enableMpegtsM2TsMode="false" transcodeSeekInfo="Auto" copyTimestamps="false" context="Streaming" />
+ <TranscodingProfile container="mp3" type="Audio" audioCodec="mp3" estimateContentLength="false" enableMpegtsM2TsMode="false" transcodeSeekInfo="Auto" copyTimestamps="false" context="Streaming" forceLiveStream="false" />
+ <TranscodingProfile container="ts" type="Video" videoCodec="h264" audioCodec="ac3" estimateContentLength="false" enableMpegtsM2TsMode="false" transcodeSeekInfo="Auto" copyTimestamps="false" context="Streaming" forceLiveStream="false" />
+ <TranscodingProfile container="jpeg" type="Photo" estimateContentLength="false" enableMpegtsM2TsMode="false" transcodeSeekInfo="Auto" copyTimestamps="false" context="Streaming" forceLiveStream="false" />
</TranscodingProfiles>
<ContainerProfiles>
<ContainerProfile type="Photo">
@@ -78,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/Linksys DMA2100.xml b/MediaBrowser.Dlna/Profiles/Xml/Linksys DMA2100.xml
index f101fb7b6..1e6db99b1 100644
--- a/MediaBrowser.Dlna/Profiles/Xml/Linksys DMA2100.xml
+++ b/MediaBrowser.Dlna/Profiles/Xml/Linksys DMA2100.xml
@@ -5,10 +5,9 @@
<ModelName>DMA2100us</ModelName>
<Headers />
</Identification>
- <FriendlyName>Emby</FriendlyName>
<Manufacturer>Emby</Manufacturer>
<ManufacturerUrl>http://emby.media/</ManufacturerUrl>
- <ModelName>Emby</ModelName>
+ <ModelName>Emby Server</ModelName>
<ModelDescription>Emby</ModelDescription>
<ModelNumber>Emby</ModelNumber>
<ModelUrl>http://emby.media/</ModelUrl>
@@ -21,8 +20,8 @@
<MaxAlbumArtHeight>480</MaxAlbumArtHeight>
<MaxIconWidth>48</MaxIconWidth>
<MaxIconHeight>48</MaxIconHeight>
- <MaxStreamingBitrate>15000000</MaxStreamingBitrate>
- <MaxStaticBitrate>15000000</MaxStaticBitrate>
+ <MaxStreamingBitrate>20000000</MaxStreamingBitrate>
+ <MaxStaticBitrate>20000000</MaxStaticBitrate>
<MusicStreamingTranscodingBitrate>192000</MusicStreamingTranscodingBitrate>
<MusicSyncBitrate>192000</MusicSyncBitrate>
<XDlnaDoc>DMS-1.50</XDlnaDoc>
@@ -38,9 +37,9 @@
<DirectPlayProfile container="avi,mp4,mkv,ts" type="Video" />
</DirectPlayProfiles>
<TranscodingProfiles>
- <TranscodingProfile container="mp3" type="Audio" audioCodec="mp3" estimateContentLength="false" enableMpegtsM2TsMode="false" transcodeSeekInfo="Auto" copyTimestamps="false" context="Streaming" />
- <TranscodingProfile container="ts" type="Video" videoCodec="h264" audioCodec="aac" estimateContentLength="false" enableMpegtsM2TsMode="false" transcodeSeekInfo="Auto" copyTimestamps="false" context="Streaming" />
- <TranscodingProfile container="jpeg" type="Photo" estimateContentLength="false" enableMpegtsM2TsMode="false" transcodeSeekInfo="Auto" copyTimestamps="false" context="Streaming" />
+ <TranscodingProfile container="mp3" type="Audio" audioCodec="mp3" estimateContentLength="false" enableMpegtsM2TsMode="false" transcodeSeekInfo="Auto" copyTimestamps="false" context="Streaming" forceLiveStream="false" />
+ <TranscodingProfile container="ts" type="Video" videoCodec="h264" audioCodec="aac" estimateContentLength="false" enableMpegtsM2TsMode="false" transcodeSeekInfo="Auto" copyTimestamps="false" context="Streaming" forceLiveStream="false" />
+ <TranscodingProfile container="jpeg" type="Photo" estimateContentLength="false" enableMpegtsM2TsMode="false" transcodeSeekInfo="Auto" copyTimestamps="false" context="Streaming" forceLiveStream="false" />
</TranscodingProfiles>
<ContainerProfiles />
<CodecProfiles />
diff --git a/MediaBrowser.Dlna/Profiles/Xml/MediaMonkey.xml b/MediaBrowser.Dlna/Profiles/Xml/MediaMonkey.xml
index 5756bc219..679aa26bd 100644
--- a/MediaBrowser.Dlna/Profiles/Xml/MediaMonkey.xml
+++ b/MediaBrowser.Dlna/Profiles/Xml/MediaMonkey.xml
@@ -7,10 +7,9 @@
<HttpHeaderInfo name="User-Agent" value="MediaMonkey" match="Substring" />
</Headers>
</Identification>
- <FriendlyName>Emby</FriendlyName>
<Manufacturer>Emby</Manufacturer>
<ManufacturerUrl>http://emby.media/</ManufacturerUrl>
- <ModelName>Emby</ModelName>
+ <ModelName>Emby Server</ModelName>
<ModelDescription>Emby</ModelDescription>
<ModelNumber>Emby</ModelNumber>
<ModelUrl>http://emby.media/</ModelUrl>
@@ -23,8 +22,8 @@
<MaxAlbumArtHeight>480</MaxAlbumArtHeight>
<MaxIconWidth>48</MaxIconWidth>
<MaxIconHeight>48</MaxIconHeight>
- <MaxStreamingBitrate>15000000</MaxStreamingBitrate>
- <MaxStaticBitrate>15000000</MaxStaticBitrate>
+ <MaxStreamingBitrate>20000000</MaxStreamingBitrate>
+ <MaxStaticBitrate>20000000</MaxStaticBitrate>
<MusicStreamingTranscodingBitrate>192000</MusicStreamingTranscodingBitrate>
<MusicSyncBitrate>192000</MusicSyncBitrate>
<XDlnaDoc>DMS-1.50</XDlnaDoc>
@@ -44,9 +43,9 @@
<DirectPlayProfile container="ogg" audioCodec="vorbis" type="Audio" />
</DirectPlayProfiles>
<TranscodingProfiles>
- <TranscodingProfile container="mp3" type="Audio" audioCodec="mp3" estimateContentLength="false" enableMpegtsM2TsMode="false" transcodeSeekInfo="Auto" copyTimestamps="false" context="Streaming" />
- <TranscodingProfile container="ts" type="Video" videoCodec="h264" audioCodec="aac" estimateContentLength="false" enableMpegtsM2TsMode="false" transcodeSeekInfo="Auto" copyTimestamps="false" context="Streaming" />
- <TranscodingProfile container="jpeg" type="Photo" estimateContentLength="false" enableMpegtsM2TsMode="false" transcodeSeekInfo="Auto" copyTimestamps="false" context="Streaming" />
+ <TranscodingProfile container="mp3" type="Audio" audioCodec="mp3" estimateContentLength="false" enableMpegtsM2TsMode="false" transcodeSeekInfo="Auto" copyTimestamps="false" context="Streaming" forceLiveStream="false" />
+ <TranscodingProfile container="ts" type="Video" videoCodec="h264" audioCodec="aac" estimateContentLength="false" enableMpegtsM2TsMode="false" transcodeSeekInfo="Auto" copyTimestamps="false" context="Streaming" forceLiveStream="false" />
+ <TranscodingProfile container="jpeg" type="Photo" estimateContentLength="false" enableMpegtsM2TsMode="false" transcodeSeekInfo="Auto" copyTimestamps="false" context="Streaming" forceLiveStream="false" />
</TranscodingProfiles>
<ContainerProfiles />
<CodecProfiles />
diff --git a/MediaBrowser.Dlna/Profiles/Xml/Panasonic Viera.xml b/MediaBrowser.Dlna/Profiles/Xml/Panasonic Viera.xml
index adbbbb386..256443093 100644
--- a/MediaBrowser.Dlna/Profiles/Xml/Panasonic Viera.xml
+++ b/MediaBrowser.Dlna/Profiles/Xml/Panasonic Viera.xml
@@ -8,10 +8,9 @@
<HttpHeaderInfo name="User-Agent" value="Panasonic MIL DLNA" match="Substring" />
</Headers>
</Identification>
- <FriendlyName>Emby</FriendlyName>
<Manufacturer>Emby</Manufacturer>
<ManufacturerUrl>http://emby.media/</ManufacturerUrl>
- <ModelName>Emby</ModelName>
+ <ModelName>Emby Server</ModelName>
<ModelDescription>Emby</ModelDescription>
<ModelNumber>Emby</ModelNumber>
<ModelUrl>http://emby.media/</ModelUrl>
@@ -24,8 +23,8 @@
<MaxAlbumArtHeight>480</MaxAlbumArtHeight>
<MaxIconWidth>48</MaxIconWidth>
<MaxIconHeight>48</MaxIconHeight>
- <MaxStreamingBitrate>15000000</MaxStreamingBitrate>
- <MaxStaticBitrate>15000000</MaxStaticBitrate>
+ <MaxStreamingBitrate>20000000</MaxStreamingBitrate>
+ <MaxStaticBitrate>20000000</MaxStaticBitrate>
<MusicStreamingTranscodingBitrate>192000</MusicStreamingTranscodingBitrate>
<MusicSyncBitrate>192000</MusicSyncBitrate>
<XDlnaDoc>DMS-1.50</XDlnaDoc>
@@ -51,9 +50,9 @@
<DirectPlayProfile container="jpeg" type="Photo" />
</DirectPlayProfiles>
<TranscodingProfiles>
- <TranscodingProfile container="mp3" type="Audio" audioCodec="mp3" estimateContentLength="false" enableMpegtsM2TsMode="false" transcodeSeekInfo="Auto" copyTimestamps="false" context="Streaming" />
- <TranscodingProfile container="ts" type="Video" videoCodec="h264" audioCodec="ac3" estimateContentLength="false" enableMpegtsM2TsMode="false" transcodeSeekInfo="Auto" copyTimestamps="false" context="Streaming" />
- <TranscodingProfile container="jpeg" type="Photo" estimateContentLength="false" enableMpegtsM2TsMode="false" transcodeSeekInfo="Auto" copyTimestamps="false" context="Streaming" />
+ <TranscodingProfile container="mp3" type="Audio" audioCodec="mp3" estimateContentLength="false" enableMpegtsM2TsMode="false" transcodeSeekInfo="Auto" copyTimestamps="false" context="Streaming" forceLiveStream="false" />
+ <TranscodingProfile container="ts" type="Video" videoCodec="h264" audioCodec="ac3" estimateContentLength="false" enableMpegtsM2TsMode="false" transcodeSeekInfo="Auto" copyTimestamps="false" context="Streaming" forceLiveStream="false" />
+ <TranscodingProfile container="jpeg" type="Photo" estimateContentLength="false" enableMpegtsM2TsMode="false" transcodeSeekInfo="Auto" copyTimestamps="false" context="Streaming" forceLiveStream="false" />
</TranscodingProfiles>
<ContainerProfiles>
<ContainerProfile type="Photo">
diff --git a/MediaBrowser.Dlna/Profiles/Xml/Popcorn Hour.xml b/MediaBrowser.Dlna/Profiles/Xml/Popcorn Hour.xml
index a38236476..3d50b1724 100644
--- a/MediaBrowser.Dlna/Profiles/Xml/Popcorn Hour.xml
+++ b/MediaBrowser.Dlna/Profiles/Xml/Popcorn Hour.xml
@@ -1,10 +1,9 @@
<?xml version="1.0"?>
<Profile xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<Name>Popcorn Hour</Name>
- <FriendlyName>Emby</FriendlyName>
<Manufacturer>Emby</Manufacturer>
<ManufacturerUrl>http://emby.media/</ManufacturerUrl>
- <ModelName>Emby</ModelName>
+ <ModelName>Emby Server</ModelName>
<ModelDescription>Emby</ModelDescription>
<ModelNumber>Emby</ModelNumber>
<ModelUrl>http://emby.media/</ModelUrl>
@@ -17,8 +16,8 @@
<MaxAlbumArtHeight>480</MaxAlbumArtHeight>
<MaxIconWidth>48</MaxIconWidth>
<MaxIconHeight>48</MaxIconHeight>
- <MaxStreamingBitrate>15000000</MaxStreamingBitrate>
- <MaxStaticBitrate>15000000</MaxStaticBitrate>
+ <MaxStreamingBitrate>20000000</MaxStreamingBitrate>
+ <MaxStaticBitrate>20000000</MaxStaticBitrate>
<MusicStreamingTranscodingBitrate>192000</MusicStreamingTranscodingBitrate>
<MusicSyncBitrate>192000</MusicSyncBitrate>
<XDlnaDoc>DMS-1.50</XDlnaDoc>
@@ -39,9 +38,9 @@
<DirectPlayProfile container="jpeg,gif,bmp,png" type="Photo" />
</DirectPlayProfiles>
<TranscodingProfiles>
- <TranscodingProfile container="mp3" type="Audio" audioCodec="mp3" estimateContentLength="false" enableMpegtsM2TsMode="false" transcodeSeekInfo="Auto" copyTimestamps="false" context="Streaming" />
- <TranscodingProfile container="mp4" type="Video" videoCodec="h264" audioCodec="aac" estimateContentLength="false" enableMpegtsM2TsMode="false" transcodeSeekInfo="Auto" copyTimestamps="false" context="Streaming" />
- <TranscodingProfile container="jpeg" type="Photo" estimateContentLength="false" enableMpegtsM2TsMode="false" transcodeSeekInfo="Auto" copyTimestamps="false" context="Streaming" />
+ <TranscodingProfile container="mp3" type="Audio" audioCodec="mp3" estimateContentLength="false" enableMpegtsM2TsMode="false" transcodeSeekInfo="Auto" copyTimestamps="false" context="Streaming" forceLiveStream="false" />
+ <TranscodingProfile container="mp4" type="Video" videoCodec="h264" audioCodec="aac" estimateContentLength="false" enableMpegtsM2TsMode="false" transcodeSeekInfo="Auto" copyTimestamps="false" context="Streaming" forceLiveStream="false" />
+ <TranscodingProfile container="jpeg" type="Photo" estimateContentLength="false" enableMpegtsM2TsMode="false" transcodeSeekInfo="Auto" copyTimestamps="false" context="Streaming" forceLiveStream="false" />
</TranscodingProfiles>
<ContainerProfiles />
<CodecProfiles>
diff --git a/MediaBrowser.Dlna/Profiles/Xml/Samsung Smart TV.xml b/MediaBrowser.Dlna/Profiles/Xml/Samsung Smart TV.xml
index af4f6e913..967538bdf 100644
--- a/MediaBrowser.Dlna/Profiles/Xml/Samsung Smart TV.xml
+++ b/MediaBrowser.Dlna/Profiles/Xml/Samsung Smart TV.xml
@@ -4,13 +4,12 @@
<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>
- <FriendlyName>Emby</FriendlyName>
<Manufacturer>Emby</Manufacturer>
<ManufacturerUrl>http://emby.media/</ManufacturerUrl>
- <ModelName>Emby</ModelName>
+ <ModelName>Emby Server</ModelName>
<ModelDescription>Emby</ModelDescription>
<ModelNumber>Emby</ModelNumber>
<ModelUrl>http://emby.media/</ModelUrl>
@@ -23,8 +22,8 @@
<MaxAlbumArtHeight>480</MaxAlbumArtHeight>
<MaxIconWidth>48</MaxIconWidth>
<MaxIconHeight>48</MaxIconHeight>
- <MaxStreamingBitrate>15000000</MaxStreamingBitrate>
- <MaxStaticBitrate>15000000</MaxStaticBitrate>
+ <MaxStreamingBitrate>20000000</MaxStreamingBitrate>
+ <MaxStaticBitrate>20000000</MaxStaticBitrate>
<MusicStreamingTranscodingBitrate>192000</MusicStreamingTranscodingBitrate>
<MusicSyncBitrate>192000</MusicSyncBitrate>
<XDlnaDoc>DMS-1.50</XDlnaDoc>
@@ -51,9 +50,9 @@
<DirectPlayProfile container="jpeg" type="Photo" />
</DirectPlayProfiles>
<TranscodingProfiles>
- <TranscodingProfile container="mp3" type="Audio" audioCodec="mp3" estimateContentLength="false" enableMpegtsM2TsMode="false" transcodeSeekInfo="Auto" copyTimestamps="false" context="Streaming" />
- <TranscodingProfile container="ts" type="Video" videoCodec="h264" audioCodec="ac3" estimateContentLength="true" enableMpegtsM2TsMode="false" transcodeSeekInfo="Auto" copyTimestamps="false" context="Streaming" />
- <TranscodingProfile container="jpeg" type="Photo" estimateContentLength="false" enableMpegtsM2TsMode="false" transcodeSeekInfo="Auto" copyTimestamps="false" context="Streaming" />
+ <TranscodingProfile container="mp3" type="Audio" audioCodec="mp3" estimateContentLength="false" enableMpegtsM2TsMode="false" transcodeSeekInfo="Auto" copyTimestamps="false" context="Streaming" forceLiveStream="false" />
+ <TranscodingProfile container="ts" type="Video" videoCodec="h264" audioCodec="ac3" estimateContentLength="true" enableMpegtsM2TsMode="false" transcodeSeekInfo="Auto" copyTimestamps="false" context="Streaming" forceLiveStream="false" />
+ <TranscodingProfile container="jpeg" type="Photo" estimateContentLength="false" enableMpegtsM2TsMode="false" transcodeSeekInfo="Auto" copyTimestamps="false" context="Streaming" forceLiveStream="false" />
</TranscodingProfiles>
<ContainerProfiles>
<ContainerProfile type="Photo">
diff --git a/MediaBrowser.Dlna/Profiles/Xml/Sony Blu-ray Player 2013.xml b/MediaBrowser.Dlna/Profiles/Xml/Sony Blu-ray Player 2013.xml
index b32c369f3..49f4759b9 100644
--- a/MediaBrowser.Dlna/Profiles/Xml/Sony Blu-ray Player 2013.xml
+++ b/MediaBrowser.Dlna/Profiles/Xml/Sony Blu-ray Player 2013.xml
@@ -7,7 +7,6 @@
<Manufacturer>Sony</Manufacturer>
<Headers />
</Identification>
- <FriendlyName>Emby</FriendlyName>
<Manufacturer>Microsoft Corporation</Manufacturer>
<ManufacturerUrl>http://emby.media/</ManufacturerUrl>
<ModelName>Windows Media Player Sharing</ModelName>
@@ -23,8 +22,8 @@
<MaxAlbumArtHeight>480</MaxAlbumArtHeight>
<MaxIconWidth>48</MaxIconWidth>
<MaxIconHeight>48</MaxIconHeight>
- <MaxStreamingBitrate>15000000</MaxStreamingBitrate>
- <MaxStaticBitrate>15000000</MaxStaticBitrate>
+ <MaxStreamingBitrate>20000000</MaxStreamingBitrate>
+ <MaxStaticBitrate>20000000</MaxStaticBitrate>
<MusicStreamingTranscodingBitrate>192000</MusicStreamingTranscodingBitrate>
<MusicSyncBitrate>192000</MusicSyncBitrate>
<XDlnaDoc>DMS-1.50</XDlnaDoc>
@@ -49,9 +48,9 @@
<DirectPlayProfile container="jpeg" type="Photo" />
</DirectPlayProfiles>
<TranscodingProfiles>
- <TranscodingProfile container="mp3" type="Audio" audioCodec="mp3" estimateContentLength="false" enableMpegtsM2TsMode="false" transcodeSeekInfo="Auto" copyTimestamps="false" context="Streaming" />
- <TranscodingProfile container="ts" type="Video" videoCodec="h264" audioCodec="ac3" estimateContentLength="false" enableMpegtsM2TsMode="false" transcodeSeekInfo="Auto" copyTimestamps="false" context="Streaming" />
- <TranscodingProfile container="jpeg" type="Photo" estimateContentLength="false" enableMpegtsM2TsMode="false" transcodeSeekInfo="Auto" copyTimestamps="false" context="Streaming" />
+ <TranscodingProfile container="mp3" type="Audio" audioCodec="mp3" estimateContentLength="false" enableMpegtsM2TsMode="false" transcodeSeekInfo="Auto" copyTimestamps="false" context="Streaming" forceLiveStream="false" />
+ <TranscodingProfile container="ts" type="Video" videoCodec="h264" audioCodec="ac3" estimateContentLength="false" enableMpegtsM2TsMode="false" transcodeSeekInfo="Auto" copyTimestamps="false" context="Streaming" forceLiveStream="false" />
+ <TranscodingProfile container="jpeg" type="Photo" estimateContentLength="false" enableMpegtsM2TsMode="false" transcodeSeekInfo="Auto" copyTimestamps="false" context="Streaming" forceLiveStream="false" />
</TranscodingProfiles>
<ContainerProfiles>
<ContainerProfile type="Photo">
diff --git a/MediaBrowser.Dlna/Profiles/Xml/Sony Blu-ray Player.xml b/MediaBrowser.Dlna/Profiles/Xml/Sony Blu-ray Player.xml
index 7d05fdbdc..41a996b66 100644
--- a/MediaBrowser.Dlna/Profiles/Xml/Sony Blu-ray Player.xml
+++ b/MediaBrowser.Dlna/Profiles/Xml/Sony Blu-ray Player.xml
@@ -9,7 +9,6 @@
<HttpHeaderInfo name="X-AV-Physical-Unit-Info" value="(Blu-ray Disc Player|Home Theater System|Home Theatre System|Media Player)" match="Regex" />
</Headers>
</Identification>
- <FriendlyName>Emby</FriendlyName>
<Manufacturer>Microsoft Corporation</Manufacturer>
<ManufacturerUrl>http://emby.media/</ManufacturerUrl>
<ModelName>Windows Media Player Sharing</ModelName>
@@ -25,8 +24,8 @@
<MaxAlbumArtHeight>480</MaxAlbumArtHeight>
<MaxIconWidth>48</MaxIconWidth>
<MaxIconHeight>48</MaxIconHeight>
- <MaxStreamingBitrate>15000000</MaxStreamingBitrate>
- <MaxStaticBitrate>15000000</MaxStaticBitrate>
+ <MaxStreamingBitrate>20000000</MaxStreamingBitrate>
+ <MaxStaticBitrate>20000000</MaxStaticBitrate>
<MusicStreamingTranscodingBitrate>192000</MusicStreamingTranscodingBitrate>
<MusicSyncBitrate>192000</MusicSyncBitrate>
<XDlnaDoc>DMS-1.50</XDlnaDoc>
@@ -48,9 +47,9 @@
<DirectPlayProfile container="jpeg" type="Photo" />
</DirectPlayProfiles>
<TranscodingProfiles>
- <TranscodingProfile container="mp3" type="Audio" audioCodec="mp3" estimateContentLength="false" enableMpegtsM2TsMode="false" transcodeSeekInfo="Auto" copyTimestamps="false" context="Streaming" />
- <TranscodingProfile container="ts" type="Video" videoCodec="mpeg2video" audioCodec="ac3" estimateContentLength="false" enableMpegtsM2TsMode="false" transcodeSeekInfo="Auto" copyTimestamps="false" context="Streaming" />
- <TranscodingProfile container="jpeg" type="Photo" estimateContentLength="false" enableMpegtsM2TsMode="false" transcodeSeekInfo="Auto" copyTimestamps="false" context="Streaming" />
+ <TranscodingProfile container="mp3" type="Audio" audioCodec="mp3" estimateContentLength="false" enableMpegtsM2TsMode="false" transcodeSeekInfo="Auto" copyTimestamps="false" context="Streaming" forceLiveStream="false" />
+ <TranscodingProfile container="ts" type="Video" videoCodec="mpeg2video" audioCodec="ac3" estimateContentLength="false" enableMpegtsM2TsMode="false" transcodeSeekInfo="Auto" copyTimestamps="false" context="Streaming" forceLiveStream="false" />
+ <TranscodingProfile container="jpeg" type="Photo" estimateContentLength="false" enableMpegtsM2TsMode="false" transcodeSeekInfo="Auto" copyTimestamps="false" context="Streaming" forceLiveStream="false" />
</TranscodingProfiles>
<ContainerProfiles>
<ContainerProfile type="Photo">
diff --git a/MediaBrowser.Dlna/Profiles/Xml/Sony Bravia (2010).xml b/MediaBrowser.Dlna/Profiles/Xml/Sony Bravia (2010).xml
index 73f5baf65..ed66118d3 100644
--- a/MediaBrowser.Dlna/Profiles/Xml/Sony Bravia (2010).xml
+++ b/MediaBrowser.Dlna/Profiles/Xml/Sony Bravia (2010).xml
@@ -8,7 +8,6 @@
<HttpHeaderInfo name="X-AV-Client-Info" value=".*KDL-\d{2}[EHLNPB]X\d[01]\d.*" match="Regex" />
</Headers>
</Identification>
- <FriendlyName>Emby</FriendlyName>
<Manufacturer>Microsoft Corporation</Manufacturer>
<ManufacturerUrl>http://www.microsoft.com/</ManufacturerUrl>
<ModelName>Windows Media Player Sharing</ModelName>
@@ -24,8 +23,8 @@
<MaxAlbumArtHeight>480</MaxAlbumArtHeight>
<MaxIconWidth>48</MaxIconWidth>
<MaxIconHeight>48</MaxIconHeight>
- <MaxStreamingBitrate>15000000</MaxStreamingBitrate>
- <MaxStaticBitrate>15000000</MaxStaticBitrate>
+ <MaxStreamingBitrate>20000000</MaxStreamingBitrate>
+ <MaxStaticBitrate>20000000</MaxStaticBitrate>
<MusicStreamingTranscodingBitrate>192000</MusicStreamingTranscodingBitrate>
<MusicSyncBitrate>192000</MusicSyncBitrate>
<XDlnaDoc>DMS-1.50</XDlnaDoc>
@@ -46,9 +45,9 @@
<DirectPlayProfile container="mp3" audioCodec="mp3" type="Audio" />
</DirectPlayProfiles>
<TranscodingProfiles>
- <TranscodingProfile container="mp3" type="Audio" audioCodec="mp3" estimateContentLength="false" enableMpegtsM2TsMode="false" transcodeSeekInfo="Auto" copyTimestamps="false" context="Streaming" />
- <TranscodingProfile container="ts" type="Video" videoCodec="h264" audioCodec="ac3" estimateContentLength="false" enableMpegtsM2TsMode="true" transcodeSeekInfo="Auto" copyTimestamps="false" context="Streaming" />
- <TranscodingProfile container="jpeg" type="Photo" estimateContentLength="false" enableMpegtsM2TsMode="false" transcodeSeekInfo="Auto" copyTimestamps="false" context="Streaming" />
+ <TranscodingProfile container="mp3" type="Audio" audioCodec="mp3" estimateContentLength="false" enableMpegtsM2TsMode="false" transcodeSeekInfo="Auto" copyTimestamps="false" context="Streaming" forceLiveStream="false" />
+ <TranscodingProfile container="ts" type="Video" videoCodec="h264" audioCodec="ac3" estimateContentLength="false" enableMpegtsM2TsMode="true" transcodeSeekInfo="Auto" copyTimestamps="false" context="Streaming" forceLiveStream="false" />
+ <TranscodingProfile container="jpeg" type="Photo" estimateContentLength="false" enableMpegtsM2TsMode="false" transcodeSeekInfo="Auto" copyTimestamps="false" context="Streaming" forceLiveStream="false" />
</TranscodingProfiles>
<ContainerProfiles>
<ContainerProfile type="Photo">
diff --git a/MediaBrowser.Dlna/Profiles/Xml/Sony Bravia (2011).xml b/MediaBrowser.Dlna/Profiles/Xml/Sony Bravia (2011).xml
index b156bfa5b..88ff6047f 100644
--- a/MediaBrowser.Dlna/Profiles/Xml/Sony Bravia (2011).xml
+++ b/MediaBrowser.Dlna/Profiles/Xml/Sony Bravia (2011).xml
@@ -8,7 +8,6 @@
<HttpHeaderInfo name="X-AV-Client-Info" value=".*KDL-\d{2}([A-Z]X\d2\d|CX400).*" match="Regex" />
</Headers>
</Identification>
- <FriendlyName>Emby</FriendlyName>
<Manufacturer>Microsoft Corporation</Manufacturer>
<ManufacturerUrl>http://www.microsoft.com/</ManufacturerUrl>
<ModelName>Windows Media Player Sharing</ModelName>
@@ -24,8 +23,8 @@
<MaxAlbumArtHeight>480</MaxAlbumArtHeight>
<MaxIconWidth>48</MaxIconWidth>
<MaxIconHeight>48</MaxIconHeight>
- <MaxStreamingBitrate>15000000</MaxStreamingBitrate>
- <MaxStaticBitrate>15000000</MaxStaticBitrate>
+ <MaxStreamingBitrate>20000000</MaxStreamingBitrate>
+ <MaxStaticBitrate>20000000</MaxStaticBitrate>
<MusicStreamingTranscodingBitrate>192000</MusicStreamingTranscodingBitrate>
<MusicSyncBitrate>192000</MusicSyncBitrate>
<XDlnaDoc>DMS-1.50</XDlnaDoc>
@@ -49,9 +48,9 @@
<DirectPlayProfile container="asf" audioCodec="wmav2,wmapro,wmavoice" type="Audio" />
</DirectPlayProfiles>
<TranscodingProfiles>
- <TranscodingProfile container="mp3" type="Audio" audioCodec="mp3" estimateContentLength="false" enableMpegtsM2TsMode="false" transcodeSeekInfo="Auto" copyTimestamps="false" context="Streaming" />
- <TranscodingProfile container="ts" type="Video" videoCodec="h264" audioCodec="ac3" estimateContentLength="false" enableMpegtsM2TsMode="true" transcodeSeekInfo="Auto" copyTimestamps="false" context="Streaming" />
- <TranscodingProfile container="jpeg" type="Photo" estimateContentLength="false" enableMpegtsM2TsMode="false" transcodeSeekInfo="Auto" copyTimestamps="false" context="Streaming" />
+ <TranscodingProfile container="mp3" type="Audio" audioCodec="mp3" estimateContentLength="false" enableMpegtsM2TsMode="false" transcodeSeekInfo="Auto" copyTimestamps="false" context="Streaming" forceLiveStream="false" />
+ <TranscodingProfile container="ts" type="Video" videoCodec="h264" audioCodec="ac3" estimateContentLength="false" enableMpegtsM2TsMode="true" transcodeSeekInfo="Auto" copyTimestamps="false" context="Streaming" forceLiveStream="false" />
+ <TranscodingProfile container="jpeg" type="Photo" estimateContentLength="false" enableMpegtsM2TsMode="false" transcodeSeekInfo="Auto" copyTimestamps="false" context="Streaming" forceLiveStream="false" />
</TranscodingProfiles>
<ContainerProfiles>
<ContainerProfile type="Photo">
diff --git a/MediaBrowser.Dlna/Profiles/Xml/Sony Bravia (2012).xml b/MediaBrowser.Dlna/Profiles/Xml/Sony Bravia (2012).xml
index c0f92fe15..fb06ab0ac 100644
--- a/MediaBrowser.Dlna/Profiles/Xml/Sony Bravia (2012).xml
+++ b/MediaBrowser.Dlna/Profiles/Xml/Sony Bravia (2012).xml
@@ -8,7 +8,6 @@
<HttpHeaderInfo name="X-AV-Client-Info" value=".*KDL-\d{2}[A-Z]X\d5(\d|G).*" match="Regex" />
</Headers>
</Identification>
- <FriendlyName>Emby</FriendlyName>
<Manufacturer>Microsoft Corporation</Manufacturer>
<ManufacturerUrl>http://www.microsoft.com/</ManufacturerUrl>
<ModelName>Windows Media Player Sharing</ModelName>
@@ -24,8 +23,8 @@
<MaxAlbumArtHeight>480</MaxAlbumArtHeight>
<MaxIconWidth>48</MaxIconWidth>
<MaxIconHeight>48</MaxIconHeight>
- <MaxStreamingBitrate>15000000</MaxStreamingBitrate>
- <MaxStaticBitrate>15000000</MaxStaticBitrate>
+ <MaxStreamingBitrate>20000000</MaxStreamingBitrate>
+ <MaxStaticBitrate>20000000</MaxStaticBitrate>
<MusicStreamingTranscodingBitrate>192000</MusicStreamingTranscodingBitrate>
<MusicSyncBitrate>192000</MusicSyncBitrate>
<XDlnaDoc>DMS-1.50</XDlnaDoc>
@@ -51,9 +50,9 @@
<DirectPlayProfile container="jpeg" type="Photo" />
</DirectPlayProfiles>
<TranscodingProfiles>
- <TranscodingProfile container="mp3" type="Audio" audioCodec="mp3" estimateContentLength="false" enableMpegtsM2TsMode="false" transcodeSeekInfo="Auto" copyTimestamps="false" context="Streaming" />
- <TranscodingProfile container="ts" type="Video" videoCodec="h264" audioCodec="ac3" estimateContentLength="false" enableMpegtsM2TsMode="true" transcodeSeekInfo="Auto" copyTimestamps="false" context="Streaming" />
- <TranscodingProfile container="jpeg" type="Photo" estimateContentLength="false" enableMpegtsM2TsMode="false" transcodeSeekInfo="Auto" copyTimestamps="false" context="Streaming" />
+ <TranscodingProfile container="mp3" type="Audio" audioCodec="mp3" estimateContentLength="false" enableMpegtsM2TsMode="false" transcodeSeekInfo="Auto" copyTimestamps="false" context="Streaming" forceLiveStream="false" />
+ <TranscodingProfile container="ts" type="Video" videoCodec="h264" audioCodec="ac3" estimateContentLength="false" enableMpegtsM2TsMode="true" transcodeSeekInfo="Auto" copyTimestamps="false" context="Streaming" forceLiveStream="false" />
+ <TranscodingProfile container="jpeg" type="Photo" estimateContentLength="false" enableMpegtsM2TsMode="false" transcodeSeekInfo="Auto" copyTimestamps="false" context="Streaming" forceLiveStream="false" />
</TranscodingProfiles>
<ContainerProfiles>
<ContainerProfile type="Photo">
diff --git a/MediaBrowser.Dlna/Profiles/Xml/Sony Bravia (2013).xml b/MediaBrowser.Dlna/Profiles/Xml/Sony Bravia (2013).xml
index f21da6ecc..67526f5f2 100644
--- a/MediaBrowser.Dlna/Profiles/Xml/Sony Bravia (2013).xml
+++ b/MediaBrowser.Dlna/Profiles/Xml/Sony Bravia (2013).xml
@@ -8,7 +8,6 @@
<HttpHeaderInfo name="X-AV-Client-Info" value=".*KDL-\d{2}[WR][5689]\d{2}A.*" match="Regex" />
</Headers>
</Identification>
- <FriendlyName>Emby</FriendlyName>
<Manufacturer>Microsoft Corporation</Manufacturer>
<ManufacturerUrl>http://www.microsoft.com/</ManufacturerUrl>
<ModelName>Windows Media Player Sharing</ModelName>
@@ -24,8 +23,8 @@
<MaxAlbumArtHeight>480</MaxAlbumArtHeight>
<MaxIconWidth>48</MaxIconWidth>
<MaxIconHeight>48</MaxIconHeight>
- <MaxStreamingBitrate>15000000</MaxStreamingBitrate>
- <MaxStaticBitrate>15000000</MaxStaticBitrate>
+ <MaxStreamingBitrate>20000000</MaxStreamingBitrate>
+ <MaxStaticBitrate>20000000</MaxStaticBitrate>
<MusicStreamingTranscodingBitrate>192000</MusicStreamingTranscodingBitrate>
<MusicSyncBitrate>192000</MusicSyncBitrate>
<XDlnaDoc>DMS-1.50</XDlnaDoc>
@@ -56,9 +55,9 @@
<DirectPlayProfile container="jpeg" type="Photo" />
</DirectPlayProfiles>
<TranscodingProfiles>
- <TranscodingProfile container="mp3" type="Audio" estimateContentLength="false" enableMpegtsM2TsMode="false" transcodeSeekInfo="Auto" copyTimestamps="false" context="Streaming" />
- <TranscodingProfile container="ts" type="Video" videoCodec="h264" audioCodec="ac3" estimateContentLength="false" enableMpegtsM2TsMode="true" transcodeSeekInfo="Auto" copyTimestamps="false" context="Streaming" />
- <TranscodingProfile container="jpeg" type="Photo" estimateContentLength="false" enableMpegtsM2TsMode="false" transcodeSeekInfo="Auto" copyTimestamps="false" context="Streaming" />
+ <TranscodingProfile container="mp3" type="Audio" estimateContentLength="false" enableMpegtsM2TsMode="false" transcodeSeekInfo="Auto" copyTimestamps="false" context="Streaming" forceLiveStream="false" />
+ <TranscodingProfile container="ts" type="Video" videoCodec="h264" audioCodec="ac3" estimateContentLength="false" enableMpegtsM2TsMode="true" transcodeSeekInfo="Auto" copyTimestamps="false" context="Streaming" forceLiveStream="false" />
+ <TranscodingProfile container="jpeg" type="Photo" estimateContentLength="false" enableMpegtsM2TsMode="false" transcodeSeekInfo="Auto" copyTimestamps="false" context="Streaming" forceLiveStream="false" />
</TranscodingProfiles>
<ContainerProfiles>
<ContainerProfile type="Photo">
diff --git a/MediaBrowser.Dlna/Profiles/Xml/Sony Bravia (2014).xml b/MediaBrowser.Dlna/Profiles/Xml/Sony Bravia (2014).xml
index a407ba473..850237756 100644
--- a/MediaBrowser.Dlna/Profiles/Xml/Sony Bravia (2014).xml
+++ b/MediaBrowser.Dlna/Profiles/Xml/Sony Bravia (2014).xml
@@ -8,7 +8,6 @@
<HttpHeaderInfo name="X-AV-Client-Info" value=".*(KDL-\d{2}W[5-9]\d{2}B|KDL-\d{2}R480|XBR-\d{2}X[89]\d{2}B|KD-\d{2}[SX][89]\d{3}B).*" match="Regex" />
</Headers>
</Identification>
- <FriendlyName>Emby</FriendlyName>
<Manufacturer>Microsoft Corporation</Manufacturer>
<ManufacturerUrl>http://www.microsoft.com/</ManufacturerUrl>
<ModelName>Windows Media Player Sharing</ModelName>
@@ -24,8 +23,8 @@
<MaxAlbumArtHeight>480</MaxAlbumArtHeight>
<MaxIconWidth>48</MaxIconWidth>
<MaxIconHeight>48</MaxIconHeight>
- <MaxStreamingBitrate>15000000</MaxStreamingBitrate>
- <MaxStaticBitrate>15000000</MaxStaticBitrate>
+ <MaxStreamingBitrate>20000000</MaxStreamingBitrate>
+ <MaxStaticBitrate>20000000</MaxStaticBitrate>
<MusicStreamingTranscodingBitrate>192000</MusicStreamingTranscodingBitrate>
<MusicSyncBitrate>192000</MusicSyncBitrate>
<XDlnaDoc>DMS-1.50</XDlnaDoc>
@@ -56,9 +55,9 @@
<DirectPlayProfile container="jpeg" type="Photo" />
</DirectPlayProfiles>
<TranscodingProfiles>
- <TranscodingProfile container="mp3" type="Audio" estimateContentLength="false" enableMpegtsM2TsMode="false" transcodeSeekInfo="Auto" copyTimestamps="false" context="Streaming" />
- <TranscodingProfile container="ts" type="Video" videoCodec="h264" audioCodec="ac3" estimateContentLength="false" enableMpegtsM2TsMode="true" transcodeSeekInfo="Auto" copyTimestamps="false" context="Streaming" />
- <TranscodingProfile container="jpeg" type="Photo" estimateContentLength="false" enableMpegtsM2TsMode="false" transcodeSeekInfo="Auto" copyTimestamps="false" context="Streaming" />
+ <TranscodingProfile container="mp3" type="Audio" estimateContentLength="false" enableMpegtsM2TsMode="false" transcodeSeekInfo="Auto" copyTimestamps="false" context="Streaming" forceLiveStream="false" />
+ <TranscodingProfile container="ts" type="Video" videoCodec="h264" audioCodec="ac3" estimateContentLength="false" enableMpegtsM2TsMode="true" transcodeSeekInfo="Auto" copyTimestamps="false" context="Streaming" forceLiveStream="false" />
+ <TranscodingProfile container="jpeg" type="Photo" estimateContentLength="false" enableMpegtsM2TsMode="false" transcodeSeekInfo="Auto" copyTimestamps="false" context="Streaming" forceLiveStream="false" />
</TranscodingProfiles>
<ContainerProfiles>
<ContainerProfile type="Photo">
diff --git a/MediaBrowser.Dlna/Profiles/Xml/Sony PlayStation 3.xml b/MediaBrowser.Dlna/Profiles/Xml/Sony PlayStation 3.xml
index f0c7cb40a..11dc33239 100644
--- a/MediaBrowser.Dlna/Profiles/Xml/Sony PlayStation 3.xml
+++ b/MediaBrowser.Dlna/Profiles/Xml/Sony PlayStation 3.xml
@@ -8,10 +8,9 @@
<HttpHeaderInfo name="X-AV-Client-Info" value="PLAYSTATION 3" match="Substring" />
</Headers>
</Identification>
- <FriendlyName>Emby</FriendlyName>
<Manufacturer>Emby</Manufacturer>
<ManufacturerUrl>http://emby.media/</ManufacturerUrl>
- <ModelName>Emby</ModelName>
+ <ModelName>Emby Server</ModelName>
<ModelDescription>Emby</ModelDescription>
<ModelNumber>Emby</ModelNumber>
<ModelUrl>http://emby.media/</ModelUrl>
@@ -24,8 +23,8 @@
<MaxAlbumArtHeight>480</MaxAlbumArtHeight>
<MaxIconWidth>48</MaxIconWidth>
<MaxIconHeight>48</MaxIconHeight>
- <MaxStreamingBitrate>15000000</MaxStreamingBitrate>
- <MaxStaticBitrate>15000000</MaxStaticBitrate>
+ <MaxStreamingBitrate>20000000</MaxStreamingBitrate>
+ <MaxStaticBitrate>20000000</MaxStaticBitrate>
<MusicStreamingTranscodingBitrate>192000</MusicStreamingTranscodingBitrate>
<MusicSyncBitrate>192000</MusicSyncBitrate>
<XDlnaDoc>DMS-1.50</XDlnaDoc>
@@ -46,9 +45,9 @@
<DirectPlayProfile container="jpeg,png,gif,bmp,tiff" type="Photo" />
</DirectPlayProfiles>
<TranscodingProfiles>
- <TranscodingProfile container="mp3" type="Audio" audioCodec="mp3" estimateContentLength="false" enableMpegtsM2TsMode="false" transcodeSeekInfo="Auto" copyTimestamps="false" context="Streaming" />
- <TranscodingProfile container="ts" type="Video" videoCodec="h264" audioCodec="ac3,aac,mp3" estimateContentLength="false" enableMpegtsM2TsMode="false" transcodeSeekInfo="Auto" copyTimestamps="false" context="Streaming" />
- <TranscodingProfile container="jpeg" type="Photo" estimateContentLength="false" enableMpegtsM2TsMode="false" transcodeSeekInfo="Auto" copyTimestamps="false" context="Streaming" />
+ <TranscodingProfile container="mp3" type="Audio" audioCodec="mp3" estimateContentLength="false" enableMpegtsM2TsMode="false" transcodeSeekInfo="Auto" copyTimestamps="false" context="Streaming" forceLiveStream="false" />
+ <TranscodingProfile container="ts" type="Video" videoCodec="h264" audioCodec="ac3,aac,mp3" estimateContentLength="false" enableMpegtsM2TsMode="false" transcodeSeekInfo="Auto" copyTimestamps="false" context="Streaming" forceLiveStream="false" />
+ <TranscodingProfile container="jpeg" type="Photo" estimateContentLength="false" enableMpegtsM2TsMode="false" transcodeSeekInfo="Auto" copyTimestamps="false" context="Streaming" forceLiveStream="false" />
</TranscodingProfiles>
<ContainerProfiles>
<ContainerProfile type="Photo">
diff --git a/MediaBrowser.Dlna/Profiles/Xml/Sony PlayStation 4.xml b/MediaBrowser.Dlna/Profiles/Xml/Sony PlayStation 4.xml
index 58c76d914..5a763006b 100644
--- a/MediaBrowser.Dlna/Profiles/Xml/Sony PlayStation 4.xml
+++ b/MediaBrowser.Dlna/Profiles/Xml/Sony PlayStation 4.xml
@@ -8,10 +8,9 @@
<HttpHeaderInfo name="X-AV-Client-Info" value="PLAYSTATION 4" match="Substring" />
</Headers>
</Identification>
- <FriendlyName>Emby</FriendlyName>
<Manufacturer>Emby</Manufacturer>
<ManufacturerUrl>http://emby.media/</ManufacturerUrl>
- <ModelName>Emby</ModelName>
+ <ModelName>Emby Server</ModelName>
<ModelDescription>Emby</ModelDescription>
<ModelNumber>Emby</ModelNumber>
<ModelUrl>http://emby.media/</ModelUrl>
@@ -24,8 +23,8 @@
<MaxAlbumArtHeight>480</MaxAlbumArtHeight>
<MaxIconWidth>48</MaxIconWidth>
<MaxIconHeight>48</MaxIconHeight>
- <MaxStreamingBitrate>15000000</MaxStreamingBitrate>
- <MaxStaticBitrate>15000000</MaxStaticBitrate>
+ <MaxStreamingBitrate>20000000</MaxStreamingBitrate>
+ <MaxStaticBitrate>20000000</MaxStaticBitrate>
<MusicStreamingTranscodingBitrate>192000</MusicStreamingTranscodingBitrate>
<MusicSyncBitrate>192000</MusicSyncBitrate>
<XDlnaDoc>DMS-1.50</XDlnaDoc>
@@ -46,9 +45,9 @@
<DirectPlayProfile container="jpeg,png,gif,bmp,tiff" type="Photo" />
</DirectPlayProfiles>
<TranscodingProfiles>
- <TranscodingProfile container="mp3" type="Audio" audioCodec="mp3" estimateContentLength="false" enableMpegtsM2TsMode="false" transcodeSeekInfo="Auto" copyTimestamps="false" context="Streaming" />
- <TranscodingProfile container="ts" type="Video" videoCodec="h264" audioCodec="mp3" estimateContentLength="false" enableMpegtsM2TsMode="false" transcodeSeekInfo="Auto" copyTimestamps="false" context="Streaming" />
- <TranscodingProfile container="jpeg" type="Photo" estimateContentLength="false" enableMpegtsM2TsMode="false" transcodeSeekInfo="Auto" copyTimestamps="false" context="Streaming" />
+ <TranscodingProfile container="mp3" type="Audio" audioCodec="mp3" estimateContentLength="false" enableMpegtsM2TsMode="false" transcodeSeekInfo="Auto" copyTimestamps="false" context="Streaming" forceLiveStream="false" />
+ <TranscodingProfile container="ts" type="Video" videoCodec="h264" audioCodec="mp3" estimateContentLength="false" enableMpegtsM2TsMode="false" transcodeSeekInfo="Auto" copyTimestamps="false" context="Streaming" forceLiveStream="false" />
+ <TranscodingProfile container="jpeg" type="Photo" estimateContentLength="false" enableMpegtsM2TsMode="false" transcodeSeekInfo="Auto" copyTimestamps="false" context="Streaming" forceLiveStream="false" />
</TranscodingProfiles>
<ContainerProfiles>
<ContainerProfile type="Photo">
diff --git a/MediaBrowser.Dlna/Profiles/Xml/Vlc.xml b/MediaBrowser.Dlna/Profiles/Xml/Vlc.xml
index 7a7f37b66..a007a4aa3 100644
--- a/MediaBrowser.Dlna/Profiles/Xml/Vlc.xml
+++ b/MediaBrowser.Dlna/Profiles/Xml/Vlc.xml
@@ -7,10 +7,9 @@
<HttpHeaderInfo name="User-Agent" value="vlc" match="Substring" />
</Headers>
</Identification>
- <FriendlyName>Emby</FriendlyName>
<Manufacturer>Emby</Manufacturer>
<ManufacturerUrl>http://emby.media/</ManufacturerUrl>
- <ModelName>Emby</ModelName>
+ <ModelName>Emby Server</ModelName>
<ModelDescription>Emby</ModelDescription>
<ModelNumber>Emby</ModelNumber>
<ModelUrl>http://emby.media/</ModelUrl>
@@ -23,8 +22,8 @@
<MaxAlbumArtHeight>480</MaxAlbumArtHeight>
<MaxIconWidth>48</MaxIconWidth>
<MaxIconHeight>48</MaxIconHeight>
- <MaxStreamingBitrate>15000000</MaxStreamingBitrate>
- <MaxStaticBitrate>15000000</MaxStaticBitrate>
+ <MaxStreamingBitrate>20000000</MaxStreamingBitrate>
+ <MaxStaticBitrate>20000000</MaxStaticBitrate>
<MusicStreamingTranscodingBitrate>192000</MusicStreamingTranscodingBitrate>
<MusicSyncBitrate>192000</MusicSyncBitrate>
<XDlnaDoc>DMS-1.50</XDlnaDoc>
@@ -36,17 +35,28 @@
<IgnoreTranscodeByteRangeRequests>false</IgnoreTranscodeByteRangeRequests>
<XmlRootAttributes />
<DirectPlayProfiles>
- <DirectPlayProfile container="avi,mpeg,mkv,ts,mp4,mov,m4v,asf,webm,ogg,ogv,iso" type="Video" />
- <DirectPlayProfile container="mp3,flac,asf,off,oga,aac" type="Audio" />
- <DirectPlayProfile container="jpeg,png,gif,bmp,tiff" type="Photo" />
+ <DirectPlayProfile container="" type="Video" />
+ <DirectPlayProfile container="" type="Audio" />
+ <DirectPlayProfile container="" type="Photo" />
</DirectPlayProfiles>
<TranscodingProfiles>
- <TranscodingProfile container="mp3" type="Audio" audioCodec="mp3" estimateContentLength="false" enableMpegtsM2TsMode="false" transcodeSeekInfo="Auto" copyTimestamps="false" context="Streaming" />
- <TranscodingProfile container="ts" type="Video" videoCodec="h264" audioCodec="aac" estimateContentLength="false" enableMpegtsM2TsMode="false" transcodeSeekInfo="Auto" copyTimestamps="false" context="Streaming" />
- <TranscodingProfile container="jpeg" type="Photo" estimateContentLength="false" enableMpegtsM2TsMode="false" transcodeSeekInfo="Auto" copyTimestamps="false" context="Streaming" />
+ <TranscodingProfile container="mp3" type="Audio" audioCodec="mp3" estimateContentLength="false" enableMpegtsM2TsMode="false" transcodeSeekInfo="Auto" copyTimestamps="false" context="Streaming" forceLiveStream="false" />
+ <TranscodingProfile container="ts" type="Video" videoCodec="h264" audioCodec="aac" estimateContentLength="false" enableMpegtsM2TsMode="false" transcodeSeekInfo="Auto" copyTimestamps="false" context="Streaming" forceLiveStream="false" />
+ <TranscodingProfile container="jpeg" type="Photo" estimateContentLength="false" enableMpegtsM2TsMode="false" transcodeSeekInfo="Auto" copyTimestamps="false" context="Streaming" forceLiveStream="false" />
</TranscodingProfiles>
<ContainerProfiles />
<CodecProfiles />
<ResponseProfiles />
- <SubtitleProfiles />
+ <SubtitleProfiles>
+ <SubtitleProfile format="srt" method="External" />
+ <SubtitleProfile format="sub" method="External" />
+ <SubtitleProfile format="srt" method="Embed" didlMode="" />
+ <SubtitleProfile format="ass" method="Embed" didlMode="" />
+ <SubtitleProfile format="ssa" method="Embed" didlMode="" />
+ <SubtitleProfile format="smi" method="Embed" didlMode="" />
+ <SubtitleProfile format="dvdsub" method="Embed" didlMode="" />
+ <SubtitleProfile format="pgs" method="Embed" didlMode="" />
+ <SubtitleProfile format="pgssub" method="Embed" didlMode="" />
+ <SubtitleProfile format="sub" method="Embed" didlMode="" />
+ </SubtitleProfiles>
</Profile> \ No newline at end of file
diff --git a/MediaBrowser.Dlna/Profiles/Xml/WDTV Live.xml b/MediaBrowser.Dlna/Profiles/Xml/WDTV Live.xml
index 50f30d40f..6f245202d 100644
--- a/MediaBrowser.Dlna/Profiles/Xml/WDTV Live.xml
+++ b/MediaBrowser.Dlna/Profiles/Xml/WDTV Live.xml
@@ -8,10 +8,9 @@
<HttpHeaderInfo name="User-Agent" value="ALPHA Networks" match="Substring" />
</Headers>
</Identification>
- <FriendlyName>Emby</FriendlyName>
<Manufacturer>Emby</Manufacturer>
<ManufacturerUrl>http://emby.media/</ManufacturerUrl>
- <ModelName>Emby</ModelName>
+ <ModelName>Emby Server</ModelName>
<ModelDescription>Emby</ModelDescription>
<ModelNumber>Emby</ModelNumber>
<ModelUrl>http://emby.media/</ModelUrl>
@@ -24,8 +23,8 @@
<MaxAlbumArtHeight>480</MaxAlbumArtHeight>
<MaxIconWidth>48</MaxIconWidth>
<MaxIconHeight>48</MaxIconHeight>
- <MaxStreamingBitrate>15000000</MaxStreamingBitrate>
- <MaxStaticBitrate>15000000</MaxStaticBitrate>
+ <MaxStreamingBitrate>20000000</MaxStreamingBitrate>
+ <MaxStaticBitrate>20000000</MaxStaticBitrate>
<MusicStreamingTranscodingBitrate>192000</MusicStreamingTranscodingBitrate>
<MusicSyncBitrate>192000</MusicSyncBitrate>
<XDlnaDoc>DMS-1.50</XDlnaDoc>
@@ -52,9 +51,9 @@
<DirectPlayProfile container="jpeg,png,gif,bmp,tiff" type="Photo" />
</DirectPlayProfiles>
<TranscodingProfiles>
- <TranscodingProfile container="mp3" type="Audio" audioCodec="mp3" estimateContentLength="false" enableMpegtsM2TsMode="false" transcodeSeekInfo="Auto" copyTimestamps="false" context="Streaming" />
- <TranscodingProfile container="ts" type="Video" videoCodec="h264" audioCodec="aac" estimateContentLength="false" enableMpegtsM2TsMode="false" transcodeSeekInfo="Auto" copyTimestamps="false" context="Streaming" />
- <TranscodingProfile container="jpeg" type="Photo" estimateContentLength="false" enableMpegtsM2TsMode="false" transcodeSeekInfo="Auto" copyTimestamps="false" context="Streaming" />
+ <TranscodingProfile container="mp3" type="Audio" audioCodec="mp3" estimateContentLength="false" enableMpegtsM2TsMode="false" transcodeSeekInfo="Auto" copyTimestamps="false" context="Streaming" forceLiveStream="false" />
+ <TranscodingProfile container="ts" type="Video" videoCodec="h264" audioCodec="aac" estimateContentLength="false" enableMpegtsM2TsMode="false" transcodeSeekInfo="Auto" copyTimestamps="false" context="Streaming" forceLiveStream="false" />
+ <TranscodingProfile container="jpeg" type="Photo" estimateContentLength="false" enableMpegtsM2TsMode="false" transcodeSeekInfo="Auto" copyTimestamps="false" context="Streaming" forceLiveStream="false" />
</TranscodingProfiles>
<ContainerProfiles>
<ContainerProfile type="Photo">
diff --git a/MediaBrowser.Dlna/Profiles/Xml/Xbox 360.xml b/MediaBrowser.Dlna/Profiles/Xml/Xbox 360.xml
index 9dbefc0e4..bb937101d 100644
--- a/MediaBrowser.Dlna/Profiles/Xml/Xbox 360.xml
+++ b/MediaBrowser.Dlna/Profiles/Xml/Xbox 360.xml
@@ -24,8 +24,8 @@
<MaxAlbumArtHeight>480</MaxAlbumArtHeight>
<MaxIconWidth>48</MaxIconWidth>
<MaxIconHeight>48</MaxIconHeight>
- <MaxStreamingBitrate>15000000</MaxStreamingBitrate>
- <MaxStaticBitrate>15000000</MaxStaticBitrate>
+ <MaxStreamingBitrate>20000000</MaxStreamingBitrate>
+ <MaxStaticBitrate>20000000</MaxStaticBitrate>
<MusicStreamingTranscodingBitrate>192000</MusicStreamingTranscodingBitrate>
<MusicSyncBitrate>192000</MusicSyncBitrate>
<XDlnaDoc>DMS-1.50</XDlnaDoc>
@@ -46,9 +46,9 @@
<DirectPlayProfile container="jpeg" type="Photo" />
</DirectPlayProfiles>
<TranscodingProfiles>
- <TranscodingProfile container="mp3" type="Audio" audioCodec="mp3" estimateContentLength="false" enableMpegtsM2TsMode="false" transcodeSeekInfo="Auto" copyTimestamps="false" context="Streaming" />
- <TranscodingProfile container="asf" type="Video" videoCodec="wmv2" audioCodec="wmav2" estimateContentLength="true" enableMpegtsM2TsMode="false" transcodeSeekInfo="Bytes" copyTimestamps="false" context="Streaming" />
- <TranscodingProfile container="jpeg" type="Photo" estimateContentLength="false" enableMpegtsM2TsMode="false" transcodeSeekInfo="Auto" copyTimestamps="false" context="Streaming" />
+ <TranscodingProfile container="mp3" type="Audio" audioCodec="mp3" estimateContentLength="false" enableMpegtsM2TsMode="false" transcodeSeekInfo="Auto" copyTimestamps="false" context="Streaming" forceLiveStream="false" />
+ <TranscodingProfile container="asf" type="Video" videoCodec="wmv2" audioCodec="wmav2" estimateContentLength="true" enableMpegtsM2TsMode="false" transcodeSeekInfo="Bytes" copyTimestamps="false" context="Streaming" forceLiveStream="false" />
+ <TranscodingProfile container="jpeg" type="Photo" estimateContentLength="false" enableMpegtsM2TsMode="false" transcodeSeekInfo="Auto" copyTimestamps="false" context="Streaming" forceLiveStream="false" />
</TranscodingProfiles>
<ContainerProfiles>
<ContainerProfile type="Video" container="mp4,mov">
diff --git a/MediaBrowser.Dlna/Profiles/Xml/Xbox One.xml b/MediaBrowser.Dlna/Profiles/Xml/Xbox One.xml
index ab8ed713f..381676944 100644
--- a/MediaBrowser.Dlna/Profiles/Xml/Xbox One.xml
+++ b/MediaBrowser.Dlna/Profiles/Xml/Xbox One.xml
@@ -8,10 +8,9 @@
<HttpHeaderInfo name="User-Agent" value="NSPlayer/12" match="Substring" />
</Headers>
</Identification>
- <FriendlyName>Emby</FriendlyName>
<Manufacturer>Emby</Manufacturer>
<ManufacturerUrl>http://emby.media/</ManufacturerUrl>
- <ModelName>Emby</ModelName>
+ <ModelName>Emby Server</ModelName>
<ModelDescription>Emby</ModelDescription>
<ModelNumber>Emby</ModelNumber>
<ModelUrl>http://emby.media/</ModelUrl>
@@ -24,8 +23,8 @@
<MaxAlbumArtHeight>480</MaxAlbumArtHeight>
<MaxIconWidth>48</MaxIconWidth>
<MaxIconHeight>48</MaxIconHeight>
- <MaxStreamingBitrate>15000000</MaxStreamingBitrate>
- <MaxStaticBitrate>15000000</MaxStaticBitrate>
+ <MaxStreamingBitrate>20000000</MaxStreamingBitrate>
+ <MaxStaticBitrate>20000000</MaxStaticBitrate>
<MusicStreamingTranscodingBitrate>192000</MusicStreamingTranscodingBitrate>
<MusicSyncBitrate>192000</MusicSyncBitrate>
<XDlnaDoc>DMS-1.50</XDlnaDoc>
@@ -47,9 +46,9 @@
<DirectPlayProfile container="jpeg" type="Photo" />
</DirectPlayProfiles>
<TranscodingProfiles>
- <TranscodingProfile container="mp3" type="Audio" audioCodec="mp3" estimateContentLength="false" enableMpegtsM2TsMode="false" transcodeSeekInfo="Auto" copyTimestamps="false" context="Streaming" />
- <TranscodingProfile container="jpeg" type="Photo" videoCodec="jpeg" estimateContentLength="false" enableMpegtsM2TsMode="false" transcodeSeekInfo="Auto" copyTimestamps="false" context="Streaming" />
- <TranscodingProfile container="ts" type="Video" videoCodec="h264" audioCodec="aac" estimateContentLength="false" enableMpegtsM2TsMode="false" transcodeSeekInfo="Auto" copyTimestamps="false" context="Streaming" />
+ <TranscodingProfile container="mp3" type="Audio" audioCodec="mp3" estimateContentLength="false" enableMpegtsM2TsMode="false" transcodeSeekInfo="Auto" copyTimestamps="false" context="Streaming" forceLiveStream="false" />
+ <TranscodingProfile container="jpeg" type="Photo" videoCodec="jpeg" estimateContentLength="false" enableMpegtsM2TsMode="false" transcodeSeekInfo="Auto" copyTimestamps="false" context="Streaming" forceLiveStream="false" />
+ <TranscodingProfile container="ts" type="Video" videoCodec="h264" audioCodec="aac" estimateContentLength="false" enableMpegtsM2TsMode="false" transcodeSeekInfo="Auto" copyTimestamps="false" context="Streaming" forceLiveStream="false" />
</TranscodingProfiles>
<ContainerProfiles>
<ContainerProfile type="Video" container="mp4,mov">
diff --git a/MediaBrowser.Dlna/Profiles/Xml/foobar2000.xml b/MediaBrowser.Dlna/Profiles/Xml/foobar2000.xml
index 0aeb4d912..ebc5e8366 100644
--- a/MediaBrowser.Dlna/Profiles/Xml/foobar2000.xml
+++ b/MediaBrowser.Dlna/Profiles/Xml/foobar2000.xml
@@ -7,10 +7,9 @@
<HttpHeaderInfo name="User-Agent" value="foobar" match="Substring" />
</Headers>
</Identification>
- <FriendlyName>Emby</FriendlyName>
<Manufacturer>Emby</Manufacturer>
<ManufacturerUrl>http://emby.media/</ManufacturerUrl>
- <ModelName>Emby</ModelName>
+ <ModelName>Emby Server</ModelName>
<ModelDescription>Emby</ModelDescription>
<ModelNumber>Emby</ModelNumber>
<ModelUrl>http://emby.media/</ModelUrl>
@@ -23,8 +22,8 @@
<MaxAlbumArtHeight>480</MaxAlbumArtHeight>
<MaxIconWidth>48</MaxIconWidth>
<MaxIconHeight>48</MaxIconHeight>
- <MaxStreamingBitrate>15000000</MaxStreamingBitrate>
- <MaxStaticBitrate>15000000</MaxStaticBitrate>
+ <MaxStreamingBitrate>20000000</MaxStreamingBitrate>
+ <MaxStaticBitrate>20000000</MaxStaticBitrate>
<MusicStreamingTranscodingBitrate>192000</MusicStreamingTranscodingBitrate>
<MusicSyncBitrate>192000</MusicSyncBitrate>
<XDlnaDoc>DMS-1.50</XDlnaDoc>
@@ -44,9 +43,9 @@
<DirectPlayProfile container="ogg" audioCodec="vorbis" type="Audio" />
</DirectPlayProfiles>
<TranscodingProfiles>
- <TranscodingProfile container="mp3" type="Audio" audioCodec="mp3" estimateContentLength="false" enableMpegtsM2TsMode="false" transcodeSeekInfo="Auto" copyTimestamps="false" context="Streaming" />
- <TranscodingProfile container="ts" type="Video" videoCodec="h264" audioCodec="aac" estimateContentLength="false" enableMpegtsM2TsMode="false" transcodeSeekInfo="Auto" copyTimestamps="false" context="Streaming" />
- <TranscodingProfile container="jpeg" type="Photo" estimateContentLength="false" enableMpegtsM2TsMode="false" transcodeSeekInfo="Auto" copyTimestamps="false" context="Streaming" />
+ <TranscodingProfile container="mp3" type="Audio" audioCodec="mp3" estimateContentLength="false" enableMpegtsM2TsMode="false" transcodeSeekInfo="Auto" copyTimestamps="false" context="Streaming" forceLiveStream="false" />
+ <TranscodingProfile container="ts" type="Video" videoCodec="h264" audioCodec="aac" estimateContentLength="false" enableMpegtsM2TsMode="false" transcodeSeekInfo="Auto" copyTimestamps="false" context="Streaming" forceLiveStream="false" />
+ <TranscodingProfile container="jpeg" type="Photo" estimateContentLength="false" enableMpegtsM2TsMode="false" transcodeSeekInfo="Auto" copyTimestamps="false" context="Streaming" forceLiveStream="false" />
</TranscodingProfiles>
<ContainerProfiles />
<CodecProfiles />
diff --git a/MediaBrowser.Dlna/Server/DescriptionXmlBuilder.cs b/MediaBrowser.Dlna/Server/DescriptionXmlBuilder.cs
index 386370596..37006915c 100644
--- a/MediaBrowser.Dlna/Server/DescriptionXmlBuilder.cs
+++ b/MediaBrowser.Dlna/Server/DescriptionXmlBuilder.cs
@@ -138,6 +138,11 @@ namespace MediaBrowser.Dlna.Server
private string GetFriendlyName()
{
+ if (string.IsNullOrWhiteSpace(_profile.FriendlyName))
+ {
+ return "Emby - " + _serverName;
+ }
+
var characters = _serverName.Where(c => (char.IsLetterOrDigit(c) || c == '-')).ToArray();
var serverName = new string(characters);
diff --git a/MediaBrowser.Dlna/Ssdp/DeviceDiscovery.cs b/MediaBrowser.Dlna/Ssdp/DeviceDiscovery.cs
index a2eac41ec..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);
@@ -109,30 +109,26 @@ namespace MediaBrowser.Dlna.Ssdp
var endPoint = new IPEndPoint(localIp, 1900);
- var socket = GetMulticastSocket(localIp, endPoint);
-
- var receiveBuffer = new byte[64000];
-
- CreateNotifier(localIp);
-
- while (!_tokenSource.IsCancellationRequested)
+ using (var socket = GetMulticastSocket(localIp, endPoint))
{
- var receivedBytes = await socket.ReceiveAsync(receiveBuffer, 0, 64000);
+ var receiveBuffer = new byte[64000];
- if (receivedBytes > 0)
+ CreateNotifier(localIp);
+
+ while (!_tokenSource.IsCancellationRequested)
{
- var args = SsdpHelper.ParseSsdpResponse(receiveBuffer);
- args.EndPoint = endPoint;
- args.LocalEndPoint = new IPEndPoint(localIp, 0);
+ var receivedBytes = await socket.ReceiveAsync(receiveBuffer, 0, 64000);
- if (_ssdpHandler.IgnoreMessage(args, true))
+ if (receivedBytes > 0)
{
- return;
- }
+ var args = SsdpHelper.ParseSsdpResponse(receiveBuffer);
+ args.EndPoint = endPoint;
+ args.LocalEndPoint = new IPEndPoint(localIp, 0);
- _ssdpHandler.LogMessageReceived(args, true);
+ _ssdpHandler.LogMessageReceived(args, true);
- TryCreateDevice(args);
+ TryCreateDevice(args);
+ }
}
}
diff --git a/MediaBrowser.Dlna/Ssdp/SsdpHandler.cs b/MediaBrowser.Dlna/Ssdp/SsdpHandler.cs
index 2d1ec1273..0e791eb98 100644
--- a/MediaBrowser.Dlna/Ssdp/SsdpHandler.cs
+++ b/MediaBrowser.Dlna/Ssdp/SsdpHandler.cs
@@ -36,7 +36,7 @@ namespace MediaBrowser.Dlna.Ssdp
private Timer _notificationTimer;
private bool _isDisposed;
- private readonly ConcurrentDictionary<string, List<UpnpDevice>> _devices = new ConcurrentDictionary<string, List<UpnpDevice>>();
+ private readonly Dictionary<string, List<UpnpDevice>> _devices = new Dictionary<string, List<UpnpDevice>>();
private readonly IApplicationHost _appHost;
@@ -172,9 +172,12 @@ namespace MediaBrowser.Dlna.Ssdp
{
get
{
- var devices = _devices.ToList();
+ lock (_devices)
+ {
+ var devices = _devices.ToList();
- return devices.SelectMany(i => i.Value).ToList();
+ return devices.SelectMany(i => i.Value).ToList();
+ }
}
}
@@ -476,32 +479,48 @@ namespace MediaBrowser.Dlna.Ssdp
var msg = new SsdpMessageBuilder().BuildMessage(header, values);
- SendDatagram(msg, _ssdpEndp, new IPEndPoint(dev.Address, 0), true, 1);
+ SendDatagram(msg, _ssdpEndp, new IPEndPoint(dev.Address, 0), true, 2);
//SendUnicastRequest(msg, 1);
}
public void RegisterNotification(string uuid, Uri descriptionUri, IPAddress address, IEnumerable<string> services)
{
- var list = _devices.GetOrAdd(uuid, new List<UpnpDevice>());
+ lock (_devices)
+ {
+ List<UpnpDevice> list;
+ List<UpnpDevice> dl;
+ if (_devices.TryGetValue(uuid, out dl))
+ {
+ list = dl;
+ }
+ else
+ {
+ list = new List<UpnpDevice>();
+ _devices[uuid] = list;
+ }
- list.AddRange(services.Select(i => new UpnpDevice(uuid, i, descriptionUri, address)));
+ list.AddRange(services.Select(i => new UpnpDevice(uuid, i, descriptionUri, address)));
- NotifyAll();
- _logger.Debug("Registered mount {0} at {1}", uuid, descriptionUri);
+ NotifyAll();
+ _logger.Debug("Registered mount {0} at {1}", uuid, descriptionUri);
+ }
}
public void UnregisterNotification(string uuid)
{
- List<UpnpDevice> dl;
- if (_devices.TryRemove(uuid, out dl))
+ lock (_devices)
{
-
- foreach (var d in dl.ToList())
+ List<UpnpDevice> dl;
+ if (_devices.TryGetValue(uuid, out dl))
{
- NotifyDevice(d, "byebye", true);
- }
+ _devices.Remove(uuid);
+ foreach (var d in dl.ToList())
+ {
+ NotifyDevice(d, "byebye", true);
+ }
- _logger.Debug("Unregistered mount {0}", uuid);
+ _logger.Debug("Unregistered mount {0}", uuid);
+ }
}
}
diff --git a/MediaBrowser.LocalMetadata/BaseXmlProvider.cs b/MediaBrowser.LocalMetadata/BaseXmlProvider.cs
index 4fc1d210d..4b64295ea 100644
--- a/MediaBrowser.LocalMetadata/BaseXmlProvider.cs
+++ b/MediaBrowser.LocalMetadata/BaseXmlProvider.cs
@@ -8,7 +8,7 @@ using CommonIO;
namespace MediaBrowser.LocalMetadata
{
- public abstract class BaseXmlProvider<T> : ILocalMetadataProvider<T>, IHasChangeMonitor, IHasOrder
+ public abstract class BaseXmlProvider<T> : ILocalMetadataProvider<T>, IHasItemChangeMonitor, IHasOrder
where T : IHasMetadata, new()
{
protected IFileSystem FileSystem;
@@ -56,7 +56,7 @@ namespace MediaBrowser.LocalMetadata
protected abstract FileSystemMetadata GetXmlFile(ItemInfo info, IDirectoryService directoryService);
- public bool HasChanged(IHasMetadata item, IDirectoryService directoryService, DateTime date)
+ public bool HasChanged(IHasMetadata item, IDirectoryService directoryService)
{
var file = GetXmlFile(new ItemInfo(item), directoryService);
@@ -65,7 +65,7 @@ namespace MediaBrowser.LocalMetadata
return false;
}
- return file.Exists && FileSystem.GetLastWriteTimeUtc(file) > date;
+ return file.Exists && FileSystem.GetLastWriteTimeUtc(file) > item.DateLastSaved;
}
public string Name
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 968d703be..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>();
@@ -41,19 +42,38 @@ namespace MediaBrowser.MediaEncoding.Encoder
}
}
- const string vn = " -vn";
-
var threads = GetNumberOfThreads(state, false);
var inputModifier = GetInputModifier(state);
- return string.Format("{0} {1} -threads {2}{3} {4} -id3v2_version 3 -write_id3v1 1 -y \"{5}\"",
+ var albumCoverInput = string.Empty;
+ var mapArgs = string.Empty;
+ var metadata = string.Empty;
+ var vn = string.Empty;
+
+ if (!string.IsNullOrWhiteSpace(state.AlbumCoverPath))
+ {
+ albumCoverInput = " -i \"" + state.AlbumCoverPath + "\"";
+ mapArgs = " -map 0:a -map 1:v -c:v copy";
+ metadata = " -metadata:s:v title=\"Album cover\" -metadata:s:v comment=\"Cover(Front)\"";
+ }
+ else
+ {
+ vn = " -vn";
+ }
+
+ 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,
vn,
string.Join(" ", audioTranscodeParams.ToArray()),
- state.OutputFilePath).Trim();
+ state.OutputFilePath,
+ 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 8c0a7b0b5..32cd950af 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
{
@@ -86,7 +81,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
UseShellExecute = false,
// Must consume both stdout and stderr or deadlocks may occur
- RedirectStandardOutput = true,
+ //RedirectStandardOutput = true,
RedirectStandardError = true,
RedirectStandardInput = true,
@@ -136,15 +131,15 @@ namespace MediaBrowser.MediaEncoding.Encoder
}
cancellationToken.Register(() => Cancel(process, encodingJob));
-
+
// MUST read both stdout and stderr asynchronously or a deadlock may occurr
- process.BeginOutputReadLine();
+ //process.BeginOutputReadLine();
// Important - don't await the log task or we won't be able to kill ffmpeg when the user stops playback
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;
@@ -366,9 +361,22 @@ namespace MediaBrowser.MediaEncoding.Encoder
/// <returns>System.String.</returns>
protected string GetVideoDecoder(EncodingJob state)
{
- if (string.Equals(GetEncodingOptions().HardwareAccelerationType, "qsv", StringComparison.OrdinalIgnoreCase))
+ if (string.Equals(state.OutputVideoCodec, "copy", StringComparison.OrdinalIgnoreCase))
+ {
+ return null;
+ }
+
+ // Only use alternative encoders for video files.
+ // When using concat with folder rips, if the mfx session fails to initialize, ffmpeg will be stuck retrying and will not exit gracefully
+ // Since transcoding of folder rips is expiremental anyway, it's not worth adding additional variables such as this.
+ if (state.VideoType != VideoType.VideoFile)
+ {
+ return null;
+ }
+
+ if (state.VideoStream != null && !string.IsNullOrWhiteSpace(state.VideoStream.Codec))
{
- if (state.VideoStream != null && !string.IsNullOrWhiteSpace(state.VideoStream.Codec))
+ if (string.Equals(GetEncodingOptions().HardwareAccelerationType, "qsv", StringComparison.OrdinalIgnoreCase))
{
switch (state.MediaSource.VideoStream.Codec.ToLower())
{
@@ -376,6 +384,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
case "h264":
if (MediaEncoder.SupportsDecoder("h264_qsv"))
{
+ // Seeing stalls and failures with decoding. Not worth it compared to encoding.
return "-c:v h264_qsv ";
}
break;
@@ -535,7 +544,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;
@@ -544,7 +553,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));
}
@@ -605,10 +615,10 @@ namespace MediaBrowser.MediaEncoding.Encoder
}
- // h264 (libnvenc)
- else if (string.Equals(videoCodec, "libnvenc", StringComparison.OrdinalIgnoreCase))
+ // h264 (h264_nvenc)
+ else if (string.Equals(videoCodec, "h264_nvenc", StringComparison.OrdinalIgnoreCase))
{
- param = "-preset high-performance";
+ param = "-preset llhq";
}
// webm
@@ -670,17 +680,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))
+ // h264_qsv and h264_nvenc expect levels to be expressed as a decimal. libx264 supports decimal and non-decimal format
+ if (string.Equals(videoCodec, "h264_qsv", StringComparison.OrdinalIgnoreCase) ||
+ string.Equals(videoCodec, "h264_nvenc", StringComparison.OrdinalIgnoreCase))
{
switch (levelString)
{
@@ -716,13 +729,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, "h264_nvenc", StringComparison.OrdinalIgnoreCase))
+ {
+ param = "-pix_fmt yuv420p " + param;
+ }
+
+ return param;
}
protected string GetVideoBitrateParam(EncodingJob state, string videoCodec)
@@ -857,7 +877,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)
{
@@ -934,7 +954,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);
@@ -957,7 +977,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);
@@ -969,7 +989,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
if (!string.IsNullOrEmpty(state.SubtitleStream.Language))
{
- var charenc = SubtitleEncoder.GetSubtitleFileCharacterSet(subtitlePath, 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))
{
@@ -1002,7 +1022,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
// Boost volume to 200% when downsampling from 6ch to 2ch
if (channels.HasValue && channels.Value <= 2)
{
- if (state.AudioStream != null && state.AudioStream.Channels.HasValue && state.AudioStream.Channels.Value > 5)
+ if (state.AudioStream != null && state.AudioStream.Channels.HasValue && state.AudioStream.Channels.Value > 5 && !GetEncodingOptions().DownMixAudioBoost.Equals(1))
{
volParam = ",volume=" + GetEncodingOptions().DownMixAudioBoost.ToString(UsCulture);
}
diff --git a/MediaBrowser.Server.Startup.Common/FFMpeg/FFmpegValidator.cs b/MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs
index 5ba5fb44a..0866bd255 100644
--- a/MediaBrowser.Server.Startup.Common/FFMpeg/FFmpegValidator.cs
+++ b/MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs
@@ -1,43 +1,38 @@
-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
{
@@ -55,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);
}
}
@@ -68,12 +60,12 @@ 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
{
@@ -86,33 +78,42 @@ namespace MediaBrowser.Server.Startup.Common.FFMpeg
"libx265",
"mpeg4",
"msmpeg4",
- //"libvpx",
- //"libvpx-vp9",
+ "libvpx",
+ "libvpx-vp9",
"aac",
- "ac3",
"libmp3lame",
- //"libvorbis",
- "srt"
+ "libopus",
+ "libvorbis",
+ "srt",
+ "h264_nvenc",
+ "h264_qsv",
+ "ac3"
};
+ output = output ?? string.Empty;
+
+ var index = 0;
+
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)
{
+ if (index < required.Length - 1)
+ {
+ _logger.Info("Encoder available: " + codec);
+ }
+
found.Add(codec);
}
+ index++;
}
return found;
}
- private string GetFFMpegOutput(string path, string arguments)
+ private string GetProcessOutput(string path, string arguments)
{
var process = new Process
{
@@ -124,8 +125,7 @@ namespace MediaBrowser.Server.Startup.Common.FFMpeg
Arguments = arguments,
WindowStyle = ProcessWindowStyle.Hidden,
ErrorDialog = false,
- RedirectStandardOutput = true,
- RedirectStandardError = true
+ RedirectStandardOutput = true
}
};
@@ -135,15 +135,12 @@ namespace MediaBrowser.Server.Startup.Common.FFMpeg
try
{
- 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
{
@@ -151,7 +148,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/EncodingJob.cs b/MediaBrowser.MediaEncoding/Encoder/EncodingJob.cs
index ae676dbc5..490a51128 100644
--- a/MediaBrowser.MediaEncoding/Encoder/EncodingJob.cs
+++ b/MediaBrowser.MediaEncoding/Encoder/EncodingJob.cs
@@ -64,6 +64,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
public long? InputFileSize { get; set; }
public string OutputAudioSync = "1";
public string OutputVideoSync = "vfr";
+ public string AlbumCoverPath { get; set; }
public string GetMimeType(string outputPath)
{
@@ -391,19 +392,6 @@ namespace MediaBrowser.MediaEncoding.Encoder
}
}
- public bool? IsTargetCabac
- {
- get
- {
- if (Options.Static)
- {
- return VideoStream == null ? null : VideoStream.IsCabac;
- }
-
- return true;
- }
- }
-
public int? TargetVideoStreamCount
{
get
diff --git a/MediaBrowser.MediaEncoding/Encoder/EncodingJobFactory.cs b/MediaBrowser.MediaEncoding/Encoder/EncodingJobFactory.cs
index f782fd05f..7c4b7fc2f 100644
--- a/MediaBrowser.MediaEncoding/Encoder/EncodingJobFactory.cs
+++ b/MediaBrowser.MediaEncoding/Encoder/EncodingJobFactory.cs
@@ -60,6 +60,14 @@ namespace MediaBrowser.MediaEncoding.Encoder
state.IsInputVideo = string.Equals(item.MediaType, MediaType.Video, StringComparison.OrdinalIgnoreCase);
+ var primaryImage = item.GetImageInfo(ImageType.Primary, 0) ??
+ item.Parents.Select(i => i.GetImageInfo(ImageType.Primary, 0)).FirstOrDefault(i => i != null);
+
+ if (primaryImage != null)
+ {
+ state.AlbumCoverPath = primaryImage.Path;
+ }
+
var mediaSources = await _mediaSourceManager.GetPlayackMediaSources(request.ItemId, null, false, new[] { MediaType.Audio, MediaType.Video }, cancellationToken).ConfigureAwait(false);
var mediaSource = string.IsNullOrEmpty(request.MediaSourceId)
@@ -91,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)
{
@@ -388,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;
@@ -413,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;
}
@@ -536,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, "nvenc", StringComparison.OrdinalIgnoreCase))
+ {
+ return "h264_nvenc";
+ }
+ if (string.Equals(options.HardwareAccelerationType, "h264_omx", StringComparison.OrdinalIgnoreCase))
+ {
+ return "h264_omx";
}
return "libx264";
@@ -575,6 +601,14 @@ namespace MediaBrowser.MediaEncoding.Encoder
return false;
}
+ if (string.Equals("h264", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
+ {
+ if (videoStream.IsAVC.HasValue && !videoStream.IsAVC.Value)
+ {
+ return false;
+ }
+ }
+
// If client is requesting a specific video profile, it must match the source
if (!string.IsNullOrEmpty(request.Profile))
{
@@ -664,14 +698,6 @@ namespace MediaBrowser.MediaEncoding.Encoder
}
}
- if (request.Cabac.HasValue && request.Cabac.Value)
- {
- if (videoStream.IsCabac.HasValue && !videoStream.IsCabac.Value)
- {
- return false;
- }
- }
-
return request.EnableAutoStreamCopy;
}
@@ -773,7 +799,6 @@ namespace MediaBrowser.MediaEncoding.Encoder
state.TargetPacketLength,
state.TargetTimestamp,
state.IsTargetAnamorphic,
- state.IsTargetCabac,
state.TargetRefFrames,
state.TargetVideoStreamCount,
state.TargetAudioStreamCount,
diff --git a/MediaBrowser.MediaEncoding/Encoder/FontConfigLoader.cs b/MediaBrowser.MediaEncoding/Encoder/FontConfigLoader.cs
new file mode 100644
index 000000000..d7ef493c2
--- /dev/null
+++ b/MediaBrowser.MediaEncoding/Encoder/FontConfigLoader.cs
@@ -0,0 +1,179 @@
+using System;
+using System.IO;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using CommonIO;
+using MediaBrowser.Common.Configuration;
+using MediaBrowser.Common.Net;
+using MediaBrowser.Model.IO;
+using MediaBrowser.Model.Logging;
+using MediaBrowser.Model.Net;
+
+namespace MediaBrowser.MediaEncoding.Encoder
+{
+ public class FontConfigLoader
+ {
+ private readonly IHttpClient _httpClient;
+ private readonly IApplicationPaths _appPaths;
+ private readonly ILogger _logger;
+ private readonly IZipClient _zipClient;
+ private readonly IFileSystem _fileSystem;
+
+ private readonly string[] _fontUrls =
+ {
+ "https://github.com/MediaBrowser/MediaBrowser.Resources/raw/master/ffmpeg/ARIALUNI.7z"
+ };
+
+ public FontConfigLoader(IHttpClient httpClient, IApplicationPaths appPaths, ILogger logger, IZipClient zipClient, IFileSystem fileSystem)
+ {
+ _httpClient = httpClient;
+ _appPaths = appPaths;
+ _logger = logger;
+ _zipClient = zipClient;
+ _fileSystem = fileSystem;
+ }
+
+ /// <summary>
+ /// Extracts the fonts.
+ /// </summary>
+ /// <param name="targetPath">The target path.</param>
+ /// <returns>Task.</returns>
+ public async Task DownloadFonts(string targetPath)
+ {
+ try
+ {
+ var fontsDirectory = Path.Combine(targetPath, "fonts");
+
+ _fileSystem.CreateDirectory(fontsDirectory);
+
+ const string fontFilename = "ARIALUNI.TTF";
+
+ var fontFile = Path.Combine(fontsDirectory, fontFilename);
+
+ if (_fileSystem.FileExists(fontFile))
+ {
+ await WriteFontConfigFile(fontsDirectory).ConfigureAwait(false);
+ }
+ else
+ {
+ // Kick this off, but no need to wait on it
+ Task.Run(async () =>
+ {
+ await DownloadFontFile(fontsDirectory, fontFilename, new Progress<double>()).ConfigureAwait(false);
+
+ await WriteFontConfigFile(fontsDirectory).ConfigureAwait(false);
+ });
+ }
+ }
+ catch (HttpException ex)
+ {
+ // Don't let the server crash because of this
+ _logger.ErrorException("Error downloading ffmpeg font files", ex);
+ }
+ catch (Exception ex)
+ {
+ // Don't let the server crash because of this
+ _logger.ErrorException("Error writing ffmpeg font files", ex);
+ }
+ }
+
+ /// <summary>
+ /// Downloads the font file.
+ /// </summary>
+ /// <param name="fontsDirectory">The fonts directory.</param>
+ /// <param name="fontFilename">The font filename.</param>
+ /// <returns>Task.</returns>
+ private async Task DownloadFontFile(string fontsDirectory, string fontFilename, IProgress<double> progress)
+ {
+ var existingFile = Directory
+ .EnumerateFiles(_appPaths.ProgramDataPath, fontFilename, SearchOption.AllDirectories)
+ .FirstOrDefault();
+
+ if (existingFile != null)
+ {
+ try
+ {
+ _fileSystem.CopyFile(existingFile, Path.Combine(fontsDirectory, fontFilename), true);
+ return;
+ }
+ catch (IOException ex)
+ {
+ // Log this, but don't let it fail the operation
+ _logger.ErrorException("Error copying file", ex);
+ }
+ }
+
+ string tempFile = null;
+
+ foreach (var url in _fontUrls)
+ {
+ progress.Report(0);
+
+ try
+ {
+ tempFile = await _httpClient.GetTempFile(new HttpRequestOptions
+ {
+ Url = url,
+ Progress = progress
+
+ }).ConfigureAwait(false);
+
+ break;
+ }
+ catch (Exception ex)
+ {
+ // The core can function without the font file, so handle this
+ _logger.ErrorException("Failed to download ffmpeg font file from {0}", ex, url);
+ }
+ }
+
+ if (string.IsNullOrEmpty(tempFile))
+ {
+ return;
+ }
+
+ Extract7zArchive(tempFile, fontsDirectory);
+
+ try
+ {
+ _fileSystem.DeleteFile(tempFile);
+ }
+ catch (IOException ex)
+ {
+ // Log this, but don't let it fail the operation
+ _logger.ErrorException("Error deleting temp file {0}", ex, tempFile);
+ }
+ }
+ private void Extract7zArchive(string archivePath, string targetPath)
+ {
+ _logger.Info("Extracting {0} to {1}", archivePath, targetPath);
+
+ _zipClient.ExtractAllFrom7z(archivePath, targetPath, true);
+ }
+
+ /// <summary>
+ /// Writes the font config file.
+ /// </summary>
+ /// <param name="fontsDirectory">The fonts directory.</param>
+ /// <returns>Task.</returns>
+ private async Task WriteFontConfigFile(string fontsDirectory)
+ {
+ const string fontConfigFilename = "fonts.conf";
+ var fontConfigFile = Path.Combine(fontsDirectory, fontConfigFilename);
+
+ if (!_fileSystem.FileExists(fontConfigFile))
+ {
+ var contents = string.Format("<?xml version=\"1.0\"?><fontconfig><dir>{0}</dir><alias><family>Arial</family><prefer>Arial Unicode MS</prefer></alias></fontconfig>", fontsDirectory);
+
+ var bytes = Encoding.UTF8.GetBytes(contents);
+
+ using (var fileStream = _fileSystem.GetFileStream(fontConfigFile, FileMode.Create, FileAccess.Write,
+ FileShare.Read, true))
+ {
+ await fileStream.WriteAsync(bytes, 0, bytes.Length);
+ }
+ }
+ }
+ }
+}
diff --git a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs
index 97567db0e..897684b73 100644
--- a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs
+++ b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs
@@ -21,6 +21,10 @@ using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using CommonIO;
+using MediaBrowser.Model.Configuration;
+using MediaBrowser.Common.Configuration;
+using MediaBrowser.Common.Extensions;
+using MediaBrowser.Common.Net;
namespace MediaBrowser.MediaEncoding.Encoder
{
@@ -64,8 +68,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;
@@ -75,14 +77,16 @@ namespace MediaBrowser.MediaEncoding.Encoder
protected readonly ISessionManager SessionManager;
protected readonly Func<ISubtitleEncoder> SubtitleEncoder;
protected readonly Func<IMediaSourceManager> MediaSourceManager;
+ private readonly IHttpClient _httpClient;
+ private readonly IZipClient _zipClient;
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, IHttpClient httpClient, IZipClient zipClient)
{
_logger = logger;
_jsonSerializer = jsonSerializer;
- Version = version;
ConfigurationManager = configurationManager;
FileSystem = fileSystem;
LiveTvManager = liveTvManager;
@@ -92,19 +96,305 @@ namespace MediaBrowser.MediaEncoding.Encoder
SessionManager = sessionManager;
SubtitleEncoder = subtitleEncoder;
MediaSourceManager = mediaSourceManager;
+ _httpClient = httpClient;
+ _zipClient = zipClient;
FFProbePath = ffProbePath;
FFMpegPath = ffMpegPath;
+
+ _hasExternalEncoder = hasExternalEncoder;
}
- public void SetAvailableEncoders(List<string> list)
+ 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);
+
+ if (Environment.OSVersion.Platform == PlatformID.Win32NT)
+ {
+ var directory = Path.GetDirectoryName(FFMpegPath);
+
+ if (!string.IsNullOrWhiteSpace(directory) && FileSystem.ContainsSubPath(ConfigurationManager.ApplicationPaths.ProgramDataPath, directory))
+ {
+ await new FontConfigLoader(_httpClient, ConfigurationManager.ApplicationPaths, _logger, _zipClient,
+ FileSystem).DownloadFonts(directory).ConfigureAwait(false);
+ }
+ }
+ }
+ }
+
+ 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>();
+ public void SetAvailableEncoders(List<string> list)
+ {
+ _encoders = list.ToList();
+ //_logger.Info("Supported encoders: {0}", string.Join(",", list.ToArray()));
}
private List<string> _decoders = new List<string>();
public void SetAvailableDecoders(List<string> list)
{
_decoders = list.ToList();
+ //_logger.Info("Supported decoders: {0}", string.Join(",", list.ToArray()));
+ }
+
+ public bool SupportsEncoder(string decoder)
+ {
+ return _encoders.Contains(decoder, StringComparer.OrdinalIgnoreCase);
}
public bool SupportsDecoder(string decoder)
@@ -112,6 +402,20 @@ namespace MediaBrowser.MediaEncoding.Encoder
return _decoders.Contains(decoder, StringComparer.OrdinalIgnoreCase);
}
+ public bool CanEncodeToAudioCodec(string codec)
+ {
+ if (string.Equals(codec, "opus", StringComparison.OrdinalIgnoreCase))
+ {
+ codec = "libopus";
+ }
+ else if (string.Equals(codec, "mp3", StringComparison.OrdinalIgnoreCase))
+ {
+ codec = "libmp3lame";
+ }
+
+ return SupportsEncoder(codec);
+ }
+
/// <summary>
/// Gets the encoder path.
/// </summary>
@@ -195,7 +499,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
// Must consume both or ffmpeg may hang due to deadlocks. See comments below.
RedirectStandardOutput = true,
- RedirectStandardError = true,
+ //RedirectStandardError = true,
RedirectStandardInput = true,
FileName = FFProbePath,
Arguments = string.Format(args,
@@ -229,7 +533,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
try
{
- process.BeginErrorReadLine();
+ //process.BeginErrorReadLine();
var result = _jsonSerializer.DeserializeFromStream<InternalMediaInfoResult>(process.StandardOutput.BaseStream);
@@ -296,7 +600,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
formats.Contains("ts", StringComparer.OrdinalIgnoreCase) ||
formats.Contains("mpegts", StringComparer.OrdinalIgnoreCase) ||
formats.Contains("wtv", StringComparer.OrdinalIgnoreCase);
-
+
// If it's mpeg based, assume true
if ((videoStream.Codec ?? string.Empty).IndexOf("mpeg", StringComparison.OrdinalIgnoreCase) != -1)
{
@@ -324,7 +628,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
UseShellExecute = false,
// Must consume both or ffmpeg may hang due to deadlocks. See comments below.
- RedirectStandardOutput = true,
+ //RedirectStandardOutput = true,
RedirectStandardError = true,
RedirectStandardInput = true,
FileName = FFMpegPath,
@@ -355,7 +659,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
try
{
- process.BeginOutputReadLine();
+ //process.BeginOutputReadLine();
using (var reader = new StreamReader(process.StandardError.BaseStream))
{
@@ -479,18 +783,22 @@ namespace MediaBrowser.MediaEncoding.Encoder
/// </summary>
protected readonly CultureInfo UsCulture = new CultureInfo("en-US");
- public Task<Stream> ExtractAudioImage(string path, int? imageStreamIndex, CancellationToken cancellationToken)
+ public Task<string> ExtractAudioImage(string path, int? imageStreamIndex, CancellationToken cancellationToken)
{
return ExtractImage(new[] { path }, imageStreamIndex, MediaProtocol.File, true, null, null, cancellationToken);
}
- public Task<Stream> ExtractVideoImage(string[] inputFiles, MediaProtocol protocol, Video3DFormat? threedFormat,
- TimeSpan? offset, CancellationToken cancellationToken)
+ public Task<string> ExtractVideoImage(string[] inputFiles, MediaProtocol protocol, Video3DFormat? threedFormat, TimeSpan? offset, CancellationToken cancellationToken)
{
return ExtractImage(inputFiles, null, protocol, false, threedFormat, offset, cancellationToken);
}
- private async Task<Stream> ExtractImage(string[] inputFiles, int? imageStreamIndex, MediaProtocol protocol, bool isAudio,
+ public Task<string> ExtractVideoImage(string[] inputFiles, MediaProtocol protocol, int? imageStreamIndex, CancellationToken cancellationToken)
+ {
+ return ExtractImage(inputFiles, imageStreamIndex, protocol, false, null, null, cancellationToken);
+ }
+
+ private async Task<string> ExtractImage(string[] inputFiles, int? imageStreamIndex, MediaProtocol protocol, bool isAudio,
Video3DFormat? threedFormat, TimeSpan? offset, CancellationToken cancellationToken)
{
var resourcePool = isAudio ? _audioImageResourcePool : _videoImageResourcePool;
@@ -524,13 +832,16 @@ namespace MediaBrowser.MediaEncoding.Encoder
return await ExtractImageInternal(inputArgument, imageStreamIndex, protocol, threedFormat, offset, false, resourcePool, cancellationToken).ConfigureAwait(false);
}
- private async Task<Stream> ExtractImageInternal(string inputPath, int? imageStreamIndex, MediaProtocol protocol, Video3DFormat? threedFormat, TimeSpan? offset, bool useIFrame, SemaphoreSlim resourcePool, CancellationToken cancellationToken)
+ private async Task<string> ExtractImageInternal(string inputPath, int? imageStreamIndex, MediaProtocol protocol, Video3DFormat? threedFormat, TimeSpan? offset, bool useIFrame, SemaphoreSlim resourcePool, CancellationToken cancellationToken)
{
if (string.IsNullOrEmpty(inputPath))
{
throw new ArgumentNullException("inputPath");
}
+ var tempExtractPath = Path.Combine(ConfigurationManager.ApplicationPaths.TempDirectory, Guid.NewGuid() + ".jpg");
+ Directory.CreateDirectory(Path.GetDirectoryName(tempExtractPath));
+
// apply some filters to thumbnail extracted below (below) crop any black lines that we made and get the correct ar then scale to width 600.
// This filter chain may have adverse effects on recorded tv thumbnails if ar changes during presentation ex. commercials @ diff ar
var vf = "scale=600:trunc(600/dar/2)*2";
@@ -563,8 +874,8 @@ namespace MediaBrowser.MediaEncoding.Encoder
var mapArg = imageStreamIndex.HasValue ? (" -map 0:v:" + imageStreamIndex.Value.ToString(CultureInfo.InvariantCulture)) : string.Empty;
// Use ffmpeg to sample 100 (we can drop this if required using thumbnail=50 for 50 frames) frames and pick the best thumbnail. Have a fall back just in case.
- var args = useIFrame ? string.Format("-i {0}{3} -threads 1 -v quiet -vframes 1 -vf \"{2},thumbnail=30\" -f image2 \"{1}\"", inputPath, "-", vf, mapArg) :
- string.Format("-i {0}{3} -threads 1 -v quiet -vframes 1 -vf \"{2}\" -f image2 \"{1}\"", inputPath, "-", vf, mapArg);
+ var args = useIFrame ? string.Format("-i {0}{3} -threads 1 -v quiet -vframes 1 -vf \"{2},thumbnail=30\" -f image2 \"{1}\"", inputPath, tempExtractPath, vf, mapArg) :
+ string.Format("-i {0}{3} -threads 1 -v quiet -vframes 1 -vf \"{2}\" -f image2 \"{1}\"", inputPath, tempExtractPath, vf, mapArg);
var probeSize = GetProbeSizeArgument(new[] { inputPath }, protocol);
@@ -588,8 +899,6 @@ namespace MediaBrowser.MediaEncoding.Encoder
Arguments = args,
WindowStyle = ProcessWindowStyle.Hidden,
ErrorDialog = false,
- RedirectStandardOutput = true,
- RedirectStandardError = true,
RedirectStandardInput = true
}
};
@@ -602,20 +911,10 @@ namespace MediaBrowser.MediaEncoding.Encoder
bool ranToCompletion;
- var memoryStream = new MemoryStream();
-
try
{
StartProcess(processWrapper);
-#pragma warning disable 4014
- // Important - don't await the log task or we won't be able to kill ffmpeg when the user stops playback
- process.StandardOutput.BaseStream.CopyToAsync(memoryStream);
-#pragma warning restore 4014
-
- // MUST read both stdout and stderr asynchronously or a deadlock may occurr
- process.BeginErrorReadLine();
-
ranToCompletion = process.WaitForExit(10000);
if (!ranToCompletion)
@@ -630,11 +929,10 @@ namespace MediaBrowser.MediaEncoding.Encoder
}
var exitCode = ranToCompletion ? processWrapper.ExitCode ?? 0 : -1;
+ var file = new FileInfo(tempExtractPath);
- if (exitCode == -1 || memoryStream.Length == 0)
+ if (exitCode == -1 || !file.Exists || file.Length == 0)
{
- memoryStream.Dispose();
-
var msg = string.Format("ffmpeg image extraction failed for {0}", inputPath);
_logger.Error(msg);
@@ -642,8 +940,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
throw new ApplicationException(msg);
}
- memoryStream.Position = 0;
- return memoryStream;
+ return tempExtractPath;
}
}
diff --git a/MediaBrowser.MediaEncoding/Encoder/VideoEncoder.cs b/MediaBrowser.MediaEncoding/Encoder/VideoEncoder.cs
index 41bfb3b96..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;
@@ -73,8 +76,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
if (string.Equals(videoCodec, "copy", StringComparison.OrdinalIgnoreCase))
{
- if (state.VideoStream != null && IsH264(state.VideoStream) &&
- (string.Equals(state.Options.OutputContainer, "ts", StringComparison.OrdinalIgnoreCase) || isOutputMkv))
+ if (state.VideoStream != null && IsH264(state.VideoStream) && string.Equals(state.Options.OutputContainer, "ts", StringComparison.OrdinalIgnoreCase) && !string.Equals(state.VideoStream.NalLengthSize, "0", StringComparison.OrdinalIgnoreCase))
{
args += " -bsf:v h264_mp4toannexb";
}
@@ -92,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);
@@ -105,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..1b5599577 100644
--- a/MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj
+++ b/MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj
@@ -71,6 +71,8 @@
<Compile Include="Encoder\EncodingJob.cs" />
<Compile Include="Encoder\EncodingJobFactory.cs" />
<Compile Include="Encoder\EncodingUtils.cs" />
+ <Compile Include="Encoder\EncoderValidator.cs" />
+ <Compile Include="Encoder\FontConfigLoader.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 3139392b0..44a0f264d 100644
--- a/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs
+++ b/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs
@@ -138,10 +138,14 @@ namespace MediaBrowser.MediaEncoding.Probing
var parts = iTunEXTC.Split(new[] { '|' }, StringSplitOptions.RemoveEmptyEntries);
// Example
// mpaa|G|100|For crude humor
- if (parts.Length == 4)
+ if (parts.Length > 1)
{
info.OfficialRating = parts[1];
- info.OfficialRatingDescription = parts[3];
+
+ if (parts.Length > 3)
+ {
+ info.OfficialRatingDescription = parts[3];
+ }
}
}
@@ -403,9 +407,23 @@ namespace MediaBrowser.MediaEncoding.Probing
Profile = streamInfo.profile,
Level = streamInfo.level,
Index = streamInfo.index,
- PixelFormat = streamInfo.pix_fmt
+ PixelFormat = streamInfo.pix_fmt,
+ NalLengthSize = streamInfo.nal_length_size,
+ TimeBase = streamInfo.time_base,
+ CodecTimeBase = streamInfo.codec_time_base
};
+ if (string.Equals(streamInfo.is_avc, "true", StringComparison.OrdinalIgnoreCase) ||
+ string.Equals(streamInfo.is_avc, "1", StringComparison.OrdinalIgnoreCase))
+ {
+ stream.IsAVC = true;
+ }
+ else if (string.Equals(streamInfo.is_avc, "false", StringComparison.OrdinalIgnoreCase) ||
+ string.Equals(streamInfo.is_avc, "0", StringComparison.OrdinalIgnoreCase))
+ {
+ stream.IsAVC = false;
+ }
+
// Filter out junk
if (!string.IsNullOrWhiteSpace(streamInfo.codec_tag_string) && streamInfo.codec_tag_string.IndexOf("[0]", StringComparison.OrdinalIgnoreCase) == -1)
{
@@ -416,6 +434,7 @@ namespace MediaBrowser.MediaEncoding.Probing
{
stream.Language = GetDictionaryValue(streamInfo.tags, "language");
stream.Comment = GetDictionaryValue(streamInfo.tags, "comment");
+ stream.Title = GetDictionaryValue(streamInfo.tags, "title");
}
if (string.Equals(streamInfo.codec_type, "audio", StringComparison.OrdinalIgnoreCase))
@@ -524,9 +543,25 @@ namespace MediaBrowser.MediaEncoding.Probing
stream.IsForced = string.Equals(isForced, "1", StringComparison.OrdinalIgnoreCase);
}
+ NormalizeStreamTitle(stream);
+
return stream;
}
+ private void NormalizeStreamTitle(MediaStream stream)
+ {
+ if (string.Equals(stream.Title, "sdh", StringComparison.OrdinalIgnoreCase) ||
+ string.Equals(stream.Title, "cc", StringComparison.OrdinalIgnoreCase))
+ {
+ stream.Title = null;
+ }
+
+ if (stream.Type == MediaStreamType.EmbeddedImage)
+ {
+ stream.Title = null;
+ }
+ }
+
/// <summary>
/// Gets a string from an FFProbeResult tags dictionary
/// </summary>
@@ -791,6 +826,11 @@ namespace MediaBrowser.MediaEncoding.Probing
}
+ if (audio.AlbumArtists.Count == 0)
+ {
+ audio.AlbumArtists = audio.Artists.Take(1).ToList();
+ }
+
// Track number
audio.IndexNumber = GetDictionaryDiscValue(tags, "track");
diff --git a/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs b/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs
index d539879e6..a63aca11b 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,8 +117,18 @@ namespace MediaBrowser.MediaEncoding.Subtitles
string outputFormat,
long startTimeTicks,
long? endTimeTicks,
+ bool preserveOriginalTimestamps,
CancellationToken cancellationToken)
{
+ if (string.IsNullOrWhiteSpace(itemId))
+ {
+ throw new ArgumentNullException("itemId");
+ }
+ if (string.IsNullOrWhiteSpace(mediaSourceId))
+ {
+ throw new ArgumentNullException("mediaSourceId");
+ }
+
var subtitle = await GetSubtitleStream(itemId, mediaSourceId, subtitleStreamIndex, cancellationToken)
.ConfigureAwait(false);
@@ -130,7 +141,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);
}
}
@@ -139,10 +150,19 @@ namespace MediaBrowser.MediaEncoding.Subtitles
int subtitleStreamIndex,
CancellationToken cancellationToken)
{
+ if (string.IsNullOrWhiteSpace(itemId))
+ {
+ throw new ArgumentNullException("itemId");
+ }
+ if (string.IsNullOrWhiteSpace(mediaSourceId))
+ {
+ throw new ArgumentNullException("mediaSourceId");
+ }
+
var mediaSources = await _mediaSourceManager.GetPlayackMediaSources(itemId, null, false, new[] { MediaType.Audio, MediaType.Video }, cancellationToken).ConfigureAwait(false);
var mediaSource = mediaSources
- .First(i => string.Equals(i.Id, mediaSourceId));
+ .First(i => string.Equals(i.Id, mediaSourceId, StringComparison.OrdinalIgnoreCase));
var subtitleStream = mediaSource.MediaStreams
.First(i => i.Type == MediaStreamType.Subtitle && i.Index == subtitleStreamIndex);
@@ -161,16 +181,16 @@ namespace MediaBrowser.MediaEncoding.Subtitles
var fileInfo = await GetReadableFile(mediaSource.Path, inputFiles, mediaSource.Protocol, subtitleStream, cancellationToken).ConfigureAwait(false);
- var stream = await GetSubtitleStream(fileInfo.Item1, fileInfo.Item2, fileInfo.Item4, cancellationToken).ConfigureAwait(false);
+ var stream = await GetSubtitleStream(fileInfo.Item1, subtitleStream.Language, fileInfo.Item2, fileInfo.Item4, cancellationToken).ConfigureAwait(false);
return new Tuple<Stream, string>(stream, fileInfo.Item3);
}
- private async Task<Stream> GetSubtitleStream(string path, MediaProtocol protocol, bool requiresCharset, CancellationToken cancellationToken)
+ private async Task<Stream> GetSubtitleStream(string path, string language, MediaProtocol protocol, bool requiresCharset, CancellationToken cancellationToken)
{
if (requiresCharset)
{
- var charset = await GetSubtitleFileCharacterSet(path, protocol, cancellationToken).ConfigureAwait(false);
+ var charset = await GetSubtitleFileCharacterSet(path, language, protocol, cancellationToken).ConfigureAwait(false);
if (!string.IsNullOrEmpty(charset))
{
@@ -197,14 +217,19 @@ namespace MediaBrowser.MediaEncoding.Subtitles
{
throw new ArgumentNullException("charset");
}
-
+
+ _logger.Debug("Getting encoding object for character set: {0}", charset);
+
try
{
return Encoding.GetEncoding(charset);
}
catch (ArgumentException)
{
- return Encoding.GetEncoding(charset.Replace("-", string.Empty));
+ charset = charset.Replace("-", string.Empty);
+ _logger.Debug("Getting encoding object for character set: {0}", charset);
+
+ return Encoding.GetEncoding(charset);
}
}
@@ -219,14 +244,15 @@ namespace MediaBrowser.MediaEncoding.Subtitles
string outputFormat;
string outputCodec;
- if (string.Equals(subtitleStream.Codec, "ass", StringComparison.OrdinalIgnoreCase))
+ if (string.Equals(subtitleStream.Codec, "ass", StringComparison.OrdinalIgnoreCase) ||
+ string.Equals(subtitleStream.Codec, "ssa", StringComparison.OrdinalIgnoreCase) ||
+ string.Equals(subtitleStream.Codec, "srt", StringComparison.OrdinalIgnoreCase))
{
// Extract
outputCodec = "copy";
- outputFormat = "ass";
+ outputFormat = subtitleStream.Codec;
}
- else if (string.Equals(subtitleStream.Codec, "subrip", StringComparison.OrdinalIgnoreCase) ||
- string.Equals(subtitleStream.Codec, "srt", StringComparison.OrdinalIgnoreCase))
+ else if (string.Equals(subtitleStream.Codec, "subrip", StringComparison.OrdinalIgnoreCase))
{
// Extract
outputCodec = "copy";
@@ -256,7 +282,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles
// Convert
var outputPath = GetSubtitleCachePath(mediaPath, protocol, subtitleStream.Index, ".srt");
- await ConvertTextSubtitleToSrt(subtitleStream.Path, protocol, outputPath, cancellationToken).ConfigureAwait(false);
+ await ConvertTextSubtitleToSrt(subtitleStream.Path, subtitleStream.Language, protocol, outputPath, cancellationToken).ConfigureAwait(false);
return new Tuple<string, MediaProtocol, string, bool>(outputPath, MediaProtocol.File, "srt", true);
}
@@ -355,7 +381,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles
/// <param name="outputPath">The output path.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>Task.</returns>
- private async Task ConvertTextSubtitleToSrt(string inputPath, MediaProtocol inputProtocol, string outputPath, CancellationToken cancellationToken)
+ private async Task ConvertTextSubtitleToSrt(string inputPath, string language, MediaProtocol inputProtocol, string outputPath, CancellationToken cancellationToken)
{
var semaphore = GetLock(outputPath);
@@ -363,9 +389,9 @@ namespace MediaBrowser.MediaEncoding.Subtitles
try
{
- if (!_fileSystem.FileExists(outputPath))
+ if (!_fileSystem.FileExists(outputPath))
{
- await ConvertTextSubtitleToSrtInternal(inputPath, inputProtocol, outputPath, cancellationToken).ConfigureAwait(false);
+ await ConvertTextSubtitleToSrtInternal(inputPath, language, inputProtocol, outputPath, cancellationToken).ConfigureAwait(false);
}
}
finally
@@ -388,7 +414,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles
/// outputPath
/// </exception>
/// <exception cref="System.ApplicationException"></exception>
- private async Task ConvertTextSubtitleToSrtInternal(string inputPath, MediaProtocol inputProtocol, string outputPath, CancellationToken cancellationToken)
+ private async Task ConvertTextSubtitleToSrtInternal(string inputPath, string language, MediaProtocol inputProtocol, string outputPath, CancellationToken cancellationToken)
{
if (string.IsNullOrEmpty(inputPath))
{
@@ -400,9 +426,9 @@ namespace MediaBrowser.MediaEncoding.Subtitles
throw new ArgumentNullException("outputPath");
}
- _fileSystem.CreateDirectory(Path.GetDirectoryName(outputPath));
+ _fileSystem.CreateDirectory(Path.GetDirectoryName(outputPath));
- var encodingParam = await GetSubtitleFileCharacterSet(inputPath, inputProtocol, cancellationToken).ConfigureAwait(false);
+ var encodingParam = await GetSubtitleFileCharacterSet(inputPath, language, inputProtocol, cancellationToken).ConfigureAwait(false);
if (!string.IsNullOrEmpty(encodingParam))
{
@@ -430,7 +456,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles
_logger.Info("{0} {1}", process.StartInfo.FileName, process.StartInfo.Arguments);
var logFilePath = Path.Combine(_appPaths.LogDirectoryPath, "ffmpeg-sub-convert-" + Guid.NewGuid() + ".txt");
- _fileSystem.CreateDirectory(Path.GetDirectoryName(logFilePath));
+ _fileSystem.CreateDirectory(Path.GetDirectoryName(logFilePath));
var logFileStream = _fileSystem.GetFileStream(logFilePath, FileMode.Create, FileAccess.Write, FileShare.Read,
true);
@@ -447,7 +473,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles
throw;
}
-
+
var logTask = process.StandardError.BaseStream.CopyToAsync(logFileStream);
var ranToCompletion = process.WaitForExit(60000);
@@ -483,7 +509,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles
{
failed = true;
- if (_fileSystem.FileExists(outputPath))
+ if (_fileSystem.FileExists(outputPath))
{
try
{
@@ -496,7 +522,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles
}
}
}
- else if (!_fileSystem.FileExists(outputPath))
+ else if (!_fileSystem.FileExists(outputPath))
{
failed = true;
}
@@ -532,7 +558,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles
try
{
- if (!_fileSystem.FileExists(outputPath))
+ if (!_fileSystem.FileExists(outputPath))
{
await ExtractTextSubtitleInternal(_mediaEncoder.GetInputArgument(inputFiles, protocol), subtitleStreamIndex,
outputCodec, outputPath, cancellationToken).ConfigureAwait(false);
@@ -557,7 +583,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles
throw new ArgumentNullException("outputPath");
}
- _fileSystem.CreateDirectory(Path.GetDirectoryName(outputPath));
+ _fileSystem.CreateDirectory(Path.GetDirectoryName(outputPath));
var processArgs = string.Format("-i {0} -map 0:{1} -an -vn -c:s {2} \"{3}\"", inputPath,
subtitleStreamIndex, outputCodec, outputPath);
@@ -583,7 +609,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles
_logger.Info("{0} {1}", process.StartInfo.FileName, process.StartInfo.Arguments);
var logFilePath = Path.Combine(_appPaths.LogDirectoryPath, "ffmpeg-sub-extract-" + Guid.NewGuid() + ".txt");
- _fileSystem.CreateDirectory(Path.GetDirectoryName(logFilePath));
+ _fileSystem.CreateDirectory(Path.GetDirectoryName(logFilePath));
var logFileStream = _fileSystem.GetFileStream(logFilePath, FileMode.Create, FileAccess.Write, FileShare.Read,
true);
@@ -601,7 +627,8 @@ namespace MediaBrowser.MediaEncoding.Subtitles
throw;
}
- process.StandardError.BaseStream.CopyToAsync(logFileStream);
+ // Important - don't await the log task or we won't be able to kill ffmpeg when the user stops playback
+ Task.Run(() => StartStreamingLog(process.StandardError.BaseStream, logFileStream));
var ranToCompletion = process.WaitForExit(300000);
@@ -652,7 +679,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles
_logger.ErrorException("Error deleting extracted subtitle {0}", ex, outputPath);
}
}
- else if (!_fileSystem.FileExists(outputPath))
+ else if (!_fileSystem.FileExists(outputPath))
{
failed = true;
}
@@ -678,6 +705,33 @@ namespace MediaBrowser.MediaEncoding.Subtitles
}
}
+ private async Task StartStreamingLog(Stream source, Stream target)
+ {
+ try
+ {
+ using (var reader = new StreamReader(source))
+ {
+ while (!reader.EndOfStream)
+ {
+ var line = await reader.ReadLineAsync().ConfigureAwait(false);
+
+ var bytes = Encoding.UTF8.GetBytes(Environment.NewLine + line);
+
+ await target.WriteAsync(bytes, 0, bytes.Length).ConfigureAwait(false);
+ await target.FlushAsync().ConfigureAwait(false);
+ }
+ }
+ }
+ catch (ObjectDisposedException)
+ {
+ // Don't spam the log. This doesn't seem to throw in windows, but sometimes under linux
+ }
+ catch (Exception ex)
+ {
+ _logger.ErrorException("Error reading ffmpeg log", ex);
+ }
+ }
+
/// <summary>
/// Sets the ass font.
/// </summary>
@@ -732,7 +786,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles
}
}
- public async Task<string> GetSubtitleFileCharacterSet(string path, MediaProtocol protocol, CancellationToken cancellationToken)
+ public async Task<string> GetSubtitleFileCharacterSet(string path, string language, MediaProtocol protocol, CancellationToken cancellationToken)
{
if (protocol == MediaProtocol.File)
{
@@ -742,7 +796,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles
}
}
- var charset = await DetectCharset(path, protocol, cancellationToken).ConfigureAwait(false);
+ var charset = await DetectCharset(path, language, protocol, cancellationToken).ConfigureAwait(false);
if (!string.IsNullOrWhiteSpace(charset))
{
@@ -754,6 +808,11 @@ namespace MediaBrowser.MediaEncoding.Subtitles
return charset;
}
+ if (!string.IsNullOrWhiteSpace(language))
+ {
+ return GetSubtitleFileCharacterSetFromLanguage(language);
+ }
+
return null;
}
@@ -798,7 +857,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles
}
}
- private async Task<string> DetectCharset(string path, MediaProtocol protocol, CancellationToken cancellationToken)
+ private async Task<string> DetectCharset(string path, string language, MediaProtocol protocol, CancellationToken cancellationToken)
{
try
{
@@ -815,6 +874,15 @@ namespace MediaBrowser.MediaEncoding.Subtitles
_logger.Info("UniversalDetector detected charset {0} for {1}", charset, path);
}
+ // This is often incorrectly indetected. If this happens, try to use other techniques instead
+ if (string.Equals("x-mac-cyrillic", charset, StringComparison.OrdinalIgnoreCase))
+ {
+ if (!string.IsNullOrWhiteSpace(language))
+ {
+ return null;
+ }
+ }
+
return charset;
}
}
diff --git a/MediaBrowser.Model.Portable/FodyWeavers.xml b/MediaBrowser.Model.Portable/FodyWeavers.xml
index 736992810..6e2fa02e6 100644
--- a/MediaBrowser.Model.Portable/FodyWeavers.xml
+++ b/MediaBrowser.Model.Portable/FodyWeavers.xml
@@ -1,4 +1,3 @@
-<?xml version="1.0" encoding="utf-8"?>
+<?xml version="1.0" encoding="utf-8"?>
<Weavers>
- <PropertyChanged />
</Weavers> \ No newline at end of file
diff --git a/MediaBrowser.Model.Portable/MediaBrowser.Model.Portable.csproj b/MediaBrowser.Model.Portable/MediaBrowser.Model.Portable.csproj
index 54fdc6400..7a9589c98 100644
--- a/MediaBrowser.Model.Portable/MediaBrowser.Model.Portable.csproj
+++ b/MediaBrowser.Model.Portable/MediaBrowser.Model.Portable.csproj
@@ -36,6 +36,8 @@
<UseApplicationTrust>false</UseApplicationTrust>
<BootstrapperEnabled>true</BootstrapperEnabled>
<RestorePackages>true</RestorePackages>
+ <NuGetPackageImportStamp>
+ </NuGetPackageImportStamp>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
@@ -230,12 +232,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>
@@ -350,8 +346,8 @@
<Compile Include="..\MediaBrowser.Model\Dlna\HttpHeaderInfo.cs">
<Link>Dlna\HttpHeaderInfo.cs</Link>
</Compile>
- <Compile Include="..\MediaBrowser.Model\Dlna\ILocalPlayer.cs">
- <Link>Dlna\ILocalPlayer.cs</Link>
+ <Compile Include="..\MediaBrowser.Model\Dlna\ITranscoderSupport.cs">
+ <Link>Dlna\ITranscoderSupport.cs</Link>
</Compile>
<Compile Include="..\MediaBrowser.Model\Dlna\MediaFormatProfile.cs">
<Link>Dlna\MediaFormatProfile.cs</Link>
@@ -359,9 +355,6 @@
<Compile Include="..\MediaBrowser.Model\Dlna\MediaFormatProfileResolver.cs">
<Link>Dlna\MediaFormatProfileResolver.cs</Link>
</Compile>
- <Compile Include="..\MediaBrowser.Model\Dlna\NullLocalPlayer.cs">
- <Link>Dlna\NullLocalPlayer.cs</Link>
- </Compile>
<Compile Include="..\MediaBrowser.Model\Dlna\PlaybackErrorCode.cs">
<Link>Dlna\PlaybackErrorCode.cs</Link>
</Compile>
@@ -605,9 +598,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>
@@ -626,9 +616,6 @@
<Compile Include="..\MediaBrowser.Model\Extensions\FloatHelper.cs">
<Link>Extensions\FloatHelper.cs</Link>
</Compile>
- <Compile Include="..\MediaBrowser.Model\Extensions\IHasPropertyChangedEvent.cs">
- <Link>Extensions\IHasPropertyChangedEvent.cs</Link>
- </Compile>
<Compile Include="..\MediaBrowser.Model\Extensions\IntHelper.cs">
<Link>Extensions\IntHelper.cs</Link>
</Compile>
@@ -662,9 +649,6 @@
<Compile Include="..\MediaBrowser.Model\FileOrganization\TvFileOrganizationOptions.cs">
<Link>FileOrganization\TvFileOrganizationOptions.cs</Link>
</Compile>
- <Compile Include="..\MediaBrowser.Model\Games\GameSystem.cs">
- <Link>Games\GameSystem.cs</Link>
- </Compile>
<Compile Include="..\MediaBrowser.Model\Globalization\CountryInfo.cs">
<Link>Globalization\CountryInfo.cs</Link>
</Compile>
@@ -1148,6 +1132,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>
@@ -1242,13 +1229,6 @@
<PostBuildEvent>
</PostBuildEvent>
</PropertyGroup>
- <Import Project="..\packages\Fody.1.29.2\build\portable-net+sl+win+wpa+wp\Fody.targets" Condition="Exists('..\packages\Fody.1.29.2\build\portable-net+sl+win+wpa+wp\Fody.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\Fody.1.29.2\build\portable-net+sl+win+wpa+wp\Fody.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\Fody.1.29.2\build\portable-net+sl+win+wpa+wp\Fody.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.
<Target Name="BeforeBuild">
diff --git a/MediaBrowser.Model.Portable/packages.config b/MediaBrowser.Model.Portable/packages.config
deleted file mode 100644
index cd4eb36a4..000000000
--- a/MediaBrowser.Model.Portable/packages.config
+++ /dev/null
@@ -1,5 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<packages>
- <package id="Fody" version="1.29.2" targetFramework="portable45-net45+win8+wp8+wpa81" developmentDependency="true" />
- <package id="PropertyChanged.Fody" version="1.50.4" targetFramework="portable45-net45+win8+wp8+wpa81" developmentDependency="true" />
-</packages> \ No newline at end of file
diff --git a/MediaBrowser.Model.net35/MediaBrowser.Model.net35.csproj b/MediaBrowser.Model.net35/MediaBrowser.Model.net35.csproj
index 473186a46..420b536ae 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>
@@ -324,8 +318,8 @@
<Compile Include="..\MediaBrowser.Model\Dlna\HttpHeaderInfo.cs">
<Link>Dlna\HttpHeaderInfo.cs</Link>
</Compile>
- <Compile Include="..\MediaBrowser.Model\Dlna\ILocalPlayer.cs">
- <Link>Dlna\ILocalPlayer.cs</Link>
+ <Compile Include="..\MediaBrowser.Model\Dlna\ITranscoderSupport.cs">
+ <Link>Dlna\ITranscoderSupport.cs</Link>
</Compile>
<Compile Include="..\MediaBrowser.Model\Dlna\MediaFormatProfile.cs">
<Link>Dlna\MediaFormatProfile.cs</Link>
@@ -333,9 +327,6 @@
<Compile Include="..\MediaBrowser.Model\Dlna\MediaFormatProfileResolver.cs">
<Link>Dlna\MediaFormatProfileResolver.cs</Link>
</Compile>
- <Compile Include="..\MediaBrowser.Model\Dlna\NullLocalPlayer.cs">
- <Link>Dlna\NullLocalPlayer.cs</Link>
- </Compile>
<Compile Include="..\MediaBrowser.Model\Dlna\PlaybackErrorCode.cs">
<Link>Dlna\PlaybackErrorCode.cs</Link>
</Compile>
@@ -579,9 +570,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>
@@ -600,9 +588,6 @@
<Compile Include="..\MediaBrowser.Model\Extensions\FloatHelper.cs">
<Link>Extensions\FloatHelper.cs</Link>
</Compile>
- <Compile Include="..\MediaBrowser.Model\Extensions\IHasPropertyChangedEvent.cs">
- <Link>Extensions\IHasPropertyChangedEvent.cs</Link>
- </Compile>
<Compile Include="..\MediaBrowser.Model\Extensions\IntHelper.cs">
<Link>Extensions\IntHelper.cs</Link>
</Compile>
@@ -636,9 +621,6 @@
<Compile Include="..\MediaBrowser.Model\FileOrganization\TvFileOrganizationOptions.cs">
<Link>FileOrganization\TvFileOrganizationOptions.cs</Link>
</Compile>
- <Compile Include="..\MediaBrowser.Model\Games\GameSystem.cs">
- <Link>Games\GameSystem.cs</Link>
- </Compile>
<Compile Include="..\MediaBrowser.Model\Globalization\CountryInfo.cs">
<Link>Globalization\CountryInfo.cs</Link>
</Compile>
@@ -1113,6 +1095,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/BaseApplicationConfiguration.cs b/MediaBrowser.Model/Configuration/BaseApplicationConfiguration.cs
index 2b53c6688..c4f9f206d 100644
--- a/MediaBrowser.Model/Configuration/BaseApplicationConfiguration.cs
+++ b/MediaBrowser.Model/Configuration/BaseApplicationConfiguration.cs
@@ -49,12 +49,6 @@ namespace MediaBrowser.Model.Configuration
/// </summary>
/// <value>The cache path.</value>
public string CachePath { get; set; }
-
- /// <summary>
- /// Gets or sets a value indicating whether [enable custom path sub folders].
- /// </summary>
- /// <value><c>true</c> if [enable custom path sub folders]; otherwise, <c>false</c>.</value>
- public bool EnableCustomPathSubFolders { get; set; }
/// <summary>
/// Initializes a new instance of the <see cref="BaseApplicationConfiguration" /> class.
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 041f51a89..58b74ba64 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,31 +184,38 @@ 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 EnableWindowsShortcuts { get; set; }
-
- public bool EnableDateLastRefresh { get; set; }
-
public string[] Migrations { get; set; }
public int MigrationVersion { get; set; }
+ public int SchemaVersion { get; set; }
+ public int SqliteCacheSize { 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[] { };
+ SqliteCacheSize = 0;
+
+ EnableLocalizedGuids = true;
+ DisplaySpecialsWithinSeasons = true;
ImageSavingConvention = ImageSavingConvention.Compatible;
PublicPort = 8096;
@@ -227,10 +228,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 f294d1dec..69dc23b21 100644
--- a/MediaBrowser.Model/Configuration/UserConfiguration.cs
+++ b/MediaBrowser.Model/Configuration/UserConfiguration.cs
@@ -34,24 +34,22 @@ 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; }
public bool HidePlayedInLatest { get; set; }
- public bool DisplayChannelsInline { get; set; }
+ public bool EnableChannelView { get; set; }
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>
@@ -60,7 +58,6 @@ namespace MediaBrowser.Model.Configuration
EnableNextEpisodeAutoPlay = true;
RememberAudioSelections = true;
RememberSubtitleSelections = true;
- DisplayChannelsInline = true;
HidePlayedInLatest = true;
PlayDefaultAudioTrack = true;
@@ -70,8 +67,6 @@ namespace MediaBrowser.Model.Configuration
PlainFolderViews = new string[] { };
- IncludeTrailersInSuggestions = true;
-
GroupedFolders = new string[] { };
}
}
diff --git a/MediaBrowser.Model/Connect/ConnectUser.cs b/MediaBrowser.Model/Connect/ConnectUser.cs
index 383261a6b..da290da12 100644
--- a/MediaBrowser.Model/Connect/ConnectUser.cs
+++ b/MediaBrowser.Model/Connect/ConnectUser.cs
@@ -8,6 +8,5 @@ namespace MediaBrowser.Model.Connect
public string Email { get; set; }
public bool IsActive { get; set; }
public string ImageUrl { get; set; }
- public bool IsSupporter { get; set; }
}
}
diff --git a/MediaBrowser.Model/Dlna/AudioOptions.cs b/MediaBrowser.Model/Dlna/AudioOptions.cs
index 6ad4fa265..c208e8ab0 100644
--- a/MediaBrowser.Model/Dlna/AudioOptions.cs
+++ b/MediaBrowser.Model/Dlna/AudioOptions.cs
@@ -11,8 +11,16 @@ namespace MediaBrowser.Model.Dlna
public AudioOptions()
{
Context = EncodingContext.Streaming;
+
+ EnableDirectPlay = true;
+ EnableDirectStream = true;
}
+ public bool EnableDirectPlay { get; set; }
+ public bool EnableDirectStream { get; set; }
+ public bool ForceDirectPlay { get; set; }
+ public bool ForceDirectStream { get; set; }
+
public string ItemId { get; set; }
public List<MediaSourceInfo> MediaSources { get; set; }
public DeviceProfile Profile { get; set; }
diff --git a/MediaBrowser.Model/Dlna/CodecProfile.cs b/MediaBrowser.Model/Dlna/CodecProfile.cs
index 7200f648c..385e98f61 100644
--- a/MediaBrowser.Model/Dlna/CodecProfile.cs
+++ b/MediaBrowser.Model/Dlna/CodecProfile.cs
@@ -11,6 +11,8 @@ namespace MediaBrowser.Model.Dlna
public ProfileCondition[] Conditions { get; set; }
+ public ProfileCondition[] ApplyConditions { get; set; }
+
[XmlAttribute("codec")]
public string Codec { get; set; }
@@ -20,6 +22,7 @@ namespace MediaBrowser.Model.Dlna
public CodecProfile()
{
Conditions = new ProfileCondition[] {};
+ ApplyConditions = new ProfileCondition[] { };
}
public List<string> GetCodecs()
diff --git a/MediaBrowser.Model/Dlna/ConditionProcessor.cs b/MediaBrowser.Model/Dlna/ConditionProcessor.cs
index fef04647a..69f1369dc 100644
--- a/MediaBrowser.Model/Dlna/ConditionProcessor.cs
+++ b/MediaBrowser.Model/Dlna/ConditionProcessor.cs
@@ -17,7 +17,6 @@ namespace MediaBrowser.Model.Dlna
int? packetLength,
TransportStreamTimestamp? timestamp,
bool? isAnamorphic,
- bool? isCabac,
int? refFrames,
int? numVideoStreams,
int? numAudioStreams,
@@ -27,8 +26,6 @@ namespace MediaBrowser.Model.Dlna
{
case ProfileConditionValue.IsAnamorphic:
return IsConditionSatisfied(condition, isAnamorphic);
- case ProfileConditionValue.IsCabac:
- return IsConditionSatisfied(condition, isCabac);
case ProfileConditionValue.VideoFramerate:
return IsConditionSatisfied(condition, videoFramerate);
case ProfileConditionValue.VideoLevel:
diff --git a/MediaBrowser.Model/Dlna/ContentFeatureBuilder.cs b/MediaBrowser.Model/Dlna/ContentFeatureBuilder.cs
index 58d669c22..c4b3383a2 100644
--- a/MediaBrowser.Model/Dlna/ContentFeatureBuilder.cs
+++ b/MediaBrowser.Model/Dlna/ContentFeatureBuilder.cs
@@ -115,7 +115,6 @@ namespace MediaBrowser.Model.Dlna
int? packetLength,
TranscodeSeekInfo transcodeSeekInfo,
bool? isAnamorphic,
- bool? isCabac,
int? refFrames,
int? numVideoStreams,
int? numAudioStreams,
@@ -157,7 +156,6 @@ namespace MediaBrowser.Model.Dlna
packetLength,
timestamp,
isAnamorphic,
- isCabac,
refFrames,
numVideoStreams,
numAudioStreams,
diff --git a/MediaBrowser.Model/Dlna/DeviceProfile.cs b/MediaBrowser.Model/Dlna/DeviceProfile.cs
index 80c060c49..423928f62 100644
--- a/MediaBrowser.Model/Dlna/DeviceProfile.cs
+++ b/MediaBrowser.Model/Dlna/DeviceProfile.cs
@@ -283,7 +283,6 @@ namespace MediaBrowser.Model.Dlna
int? packetLength,
TransportStreamTimestamp timestamp,
bool? isAnamorphic,
- bool? isCabac,
int? refFrames,
int? numVideoStreams,
int? numAudioStreams,
@@ -321,7 +320,7 @@ namespace MediaBrowser.Model.Dlna
var anyOff = false;
foreach (ProfileCondition c in i.Conditions)
{
- if (!conditionProcessor.IsVideoConditionSatisfied(c, width, height, bitDepth, videoBitrate, videoProfile, videoLevel, videoFramerate, packetLength, timestamp, isAnamorphic, isCabac, refFrames, numVideoStreams, numAudioStreams, videoCodecTag))
+ if (!conditionProcessor.IsVideoConditionSatisfied(c, width, height, bitDepth, videoBitrate, videoProfile, videoLevel, videoFramerate, packetLength, timestamp, isAnamorphic, refFrames, numVideoStreams, numAudioStreams, videoCodecTag))
{
anyOff = true;
break;
diff --git a/MediaBrowser.Model/Dlna/ILocalPlayer.cs b/MediaBrowser.Model/Dlna/ILocalPlayer.cs
deleted file mode 100644
index 55e11ec4b..000000000
--- a/MediaBrowser.Model/Dlna/ILocalPlayer.cs
+++ /dev/null
@@ -1,26 +0,0 @@
-
-namespace MediaBrowser.Model.Dlna
-{
- public interface ILocalPlayer
- {
- /// <summary>
- /// Determines whether this instance [can access file] the specified path.
- /// </summary>
- /// <param name="path">The path.</param>
- /// <returns><c>true</c> if this instance [can access file] the specified path; otherwise, <c>false</c>.</returns>
- bool CanAccessFile(string path);
- /// <summary>
- /// Determines whether this instance [can access directory] the specified path.
- /// </summary>
- /// <param name="path">The path.</param>
- /// <returns><c>true</c> if this instance [can access directory] the specified path; otherwise, <c>false</c>.</returns>
- bool CanAccessDirectory(string path);
- /// <summary>
- /// Determines whether this instance [can access URL] the specified URL.
- /// </summary>
- /// <param name="url">The URL.</param>
- /// <param name="requiresCustomRequestHeaders">if set to <c>true</c> [requires custom request headers].</param>
- /// <returns><c>true</c> if this instance [can access URL] the specified URL; otherwise, <c>false</c>.</returns>
- bool CanAccessUrl(string url, bool requiresCustomRequestHeaders);
- }
-}
diff --git a/MediaBrowser.Model/Dlna/ITranscoderSupport.cs b/MediaBrowser.Model/Dlna/ITranscoderSupport.cs
new file mode 100644
index 000000000..0dac23403
--- /dev/null
+++ b/MediaBrowser.Model/Dlna/ITranscoderSupport.cs
@@ -0,0 +1,15 @@
+namespace MediaBrowser.Model.Dlna
+{
+ public interface ITranscoderSupport
+ {
+ bool CanEncodeToAudioCodec(string codec);
+ }
+
+ public class FullTranscoderSupport : ITranscoderSupport
+ {
+ public bool CanEncodeToAudioCodec(string codec)
+ {
+ return true;
+ }
+ }
+}
diff --git a/MediaBrowser.Model/Dlna/NullLocalPlayer.cs b/MediaBrowser.Model/Dlna/NullLocalPlayer.cs
deleted file mode 100644
index c34b63887..000000000
--- a/MediaBrowser.Model/Dlna/NullLocalPlayer.cs
+++ /dev/null
@@ -1,21 +0,0 @@
-
-namespace MediaBrowser.Model.Dlna
-{
- public class NullLocalPlayer : ILocalPlayer
- {
- public bool CanAccessFile(string path)
- {
- return false;
- }
-
- public bool CanAccessDirectory(string path)
- {
- return false;
- }
-
- public bool CanAccessUrl(string url, bool requiresCustomRequestHeaders)
- {
- return false;
- }
- }
-}
diff --git a/MediaBrowser.Model/Dlna/ProfileConditionValue.cs b/MediaBrowser.Model/Dlna/ProfileConditionValue.cs
index 4ad326e51..c17a09c3f 100644
--- a/MediaBrowser.Model/Dlna/ProfileConditionValue.cs
+++ b/MediaBrowser.Model/Dlna/ProfileConditionValue.cs
@@ -17,7 +17,6 @@
VideoTimestamp = 12,
IsAnamorphic = 13,
RefFrames = 14,
- IsCabac = 15,
NumAudioStreams = 16,
NumVideoStreams = 17,
IsSecondaryAudio = 18,
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 b04f1b0fb..c05ca4187 100644
--- a/MediaBrowser.Model/Dlna/StreamBuilder.cs
+++ b/MediaBrowser.Model/Dlna/StreamBuilder.cs
@@ -11,17 +11,17 @@ namespace MediaBrowser.Model.Dlna
{
public class StreamBuilder
{
- private readonly ILocalPlayer _localPlayer;
private readonly ILogger _logger;
+ private readonly ITranscoderSupport _transcoderSupport;
- public StreamBuilder(ILocalPlayer localPlayer, ILogger logger)
+ public StreamBuilder(ITranscoderSupport transcoderSupport, ILogger logger)
{
- _localPlayer = localPlayer;
+ _transcoderSupport = transcoderSupport;
_logger = logger;
}
public StreamBuilder(ILogger logger)
- : this(new NullLocalPlayer(), logger)
+ : this(new FullTranscoderSupport(), logger)
{
}
@@ -55,7 +55,7 @@ namespace MediaBrowser.Model.Dlna
stream.DeviceProfileId = options.Profile.Id;
}
- return GetOptimalStream(streams);
+ return GetOptimalStream(streams, options.GetMaxBitrate());
}
public StreamInfo BuildVideoItem(VideoOptions options)
@@ -88,12 +88,12 @@ namespace MediaBrowser.Model.Dlna
stream.DeviceProfileId = options.Profile.Id;
}
- return GetOptimalStream(streams);
+ return GetOptimalStream(streams, options.GetMaxBitrate());
}
- private StreamInfo GetOptimalStream(List<StreamInfo> streams)
+ private StreamInfo GetOptimalStream(List<StreamInfo> streams, int? maxBitrate)
{
- streams = StreamInfoSorter.SortMediaSources(streams);
+ streams = StreamInfoSorter.SortMediaSources(streams, maxBitrate);
foreach (StreamInfo stream in streams)
{
@@ -115,10 +115,29 @@ namespace MediaBrowser.Model.Dlna
DeviceProfile = options.Profile
};
+ if (options.ForceDirectPlay)
+ {
+ playlistItem.PlayMethod = PlayMethod.DirectPlay;
+ playlistItem.Container = item.Container;
+ return playlistItem;
+ }
+
+ if (options.ForceDirectStream)
+ {
+ playlistItem.PlayMethod = PlayMethod.DirectStream;
+ playlistItem.Container = item.Container;
+ return playlistItem;
+ }
+
MediaStream audioStream = item.GetDefaultAudioStream(null);
List<PlayMethod> directPlayMethods = GetAudioDirectPlayMethods(item, audioStream, options);
+ ConditionProcessor conditionProcessor = new ConditionProcessor();
+
+ int? inputAudioChannels = audioStream == null ? null : audioStream.Channels;
+ int? inputAudioBitrate = audioStream == null ? null : audioStream.BitDepth;
+
if (directPlayMethods.Count > 0)
{
string audioCodec = audioStream == null ? null : audioStream.Codec;
@@ -126,27 +145,36 @@ namespace MediaBrowser.Model.Dlna
// Make sure audio codec profiles are satisfied
if (!string.IsNullOrEmpty(audioCodec))
{
- ConditionProcessor conditionProcessor = new ConditionProcessor();
-
List<ProfileCondition> conditions = new List<ProfileCondition>();
foreach (CodecProfile i in options.Profile.CodecProfiles)
{
if (i.Type == CodecType.Audio && i.ContainsCodec(audioCodec, item.Container))
{
- foreach (ProfileCondition c in i.Conditions)
+ bool applyConditions = true;
+ foreach (ProfileCondition applyCondition in i.ApplyConditions)
{
- conditions.Add(c);
+ if (!conditionProcessor.IsAudioConditionSatisfied(applyCondition, inputAudioChannels, inputAudioBitrate))
+ {
+ LogConditionFailure(options.Profile, "AudioCodecProfile", applyCondition, item);
+ applyConditions = false;
+ break;
+ }
+ }
+
+ if (applyConditions)
+ {
+ foreach (ProfileCondition c in i.Conditions)
+ {
+ conditions.Add(c);
+ }
}
}
}
- int? audioChannels = audioStream.Channels;
- int? audioBitrate = audioStream.BitRate;
-
bool all = true;
foreach (ProfileCondition c in conditions)
{
- if (!conditionProcessor.IsAudioConditionSatisfied(c, audioChannels, audioBitrate))
+ if (!conditionProcessor.IsAudioConditionSatisfied(c, inputAudioChannels, inputAudioBitrate))
{
LogConditionFailure(options.Profile, "AudioCodecProfile", c, item);
all = false;
@@ -156,19 +184,7 @@ namespace MediaBrowser.Model.Dlna
if (all)
{
- if (item.Protocol == MediaProtocol.File &&
- directPlayMethods.Contains(PlayMethod.DirectPlay) &&
- _localPlayer.CanAccessFile(item.Path))
- {
- playlistItem.PlayMethod = PlayMethod.DirectPlay;
- }
- else if (item.Protocol == MediaProtocol.Http &&
- directPlayMethods.Contains(PlayMethod.DirectPlay) &&
- _localPlayer.CanAccessUrl(item.Path, item.RequiredHttpHeaders.Count > 0))
- {
- playlistItem.PlayMethod = PlayMethod.DirectPlay;
- }
- else if (directPlayMethods.Contains(PlayMethod.DirectStream))
+ if (directPlayMethods.Contains(PlayMethod.DirectStream))
{
playlistItem.PlayMethod = PlayMethod.DirectStream;
}
@@ -185,8 +201,11 @@ namespace MediaBrowser.Model.Dlna
{
if (i.Type == playlistItem.MediaType && i.Context == options.Context)
{
- transcodingProfile = i;
- break;
+ if (_transcoderSupport.CanEncodeToAudioCodec(i.AudioCodec ?? i.Container))
+ {
+ transcodingProfile = i;
+ break;
+ }
}
}
@@ -201,7 +220,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>();
@@ -218,9 +245,23 @@ namespace MediaBrowser.Model.Dlna
List<ProfileCondition> audioTranscodingConditions = new List<ProfileCondition>();
foreach (CodecProfile i in audioCodecProfiles)
{
- foreach (ProfileCondition c in i.Conditions)
+ bool applyConditions = true;
+ foreach (ProfileCondition applyCondition in i.ApplyConditions)
{
- audioTranscodingConditions.Add(c);
+ if (!conditionProcessor.IsAudioConditionSatisfied(applyCondition, inputAudioChannels, inputAudioBitrate))
+ {
+ LogConditionFailure(options.Profile, "AudioCodecProfile", applyCondition, item);
+ applyConditions = false;
+ break;
+ }
+ }
+
+ if (applyConditions)
+ {
+ foreach (ProfileCondition c in i.Conditions)
+ {
+ audioTranscodingConditions.Add(c);
+ }
}
}
@@ -271,7 +312,7 @@ namespace MediaBrowser.Model.Dlna
if (directPlayProfile != null)
{
// While options takes the network and other factors into account. Only applies to direct stream
- if (item.SupportsDirectStream && IsAudioEligibleForDirectPlay(item, options.GetMaxBitrate()))
+ if (item.SupportsDirectStream && IsAudioEligibleForDirectPlay(item, options.GetMaxBitrate()) && options.EnableDirectStream)
{
playMethods.Add(PlayMethod.DirectStream);
}
@@ -279,7 +320,7 @@ namespace MediaBrowser.Model.Dlna
// The profile describes what the device supports
// If device requirements are satisfied then allow both direct stream and direct play
if (item.SupportsDirectPlay &&
- IsAudioEligibleForDirectPlay(item, GetBitrateForDirectPlayCheck(item, options)))
+ IsAudioEligibleForDirectPlay(item, GetBitrateForDirectPlayCheck(item, options)) && options.EnableDirectPlay)
{
playMethods.Add(PlayMethod.DirectPlay);
}
@@ -362,8 +403,8 @@ namespace MediaBrowser.Model.Dlna
MediaStream videoStream = item.VideoStream;
// TODO: This doesn't accout for situation of device being able to handle media bitrate, but wifi connection not fast enough
- bool isEligibleForDirectPlay = IsEligibleForDirectPlay(item, GetBitrateForDirectPlayCheck(item, options), subtitleStream, options, PlayMethod.DirectPlay);
- bool isEligibleForDirectStream = IsEligibleForDirectPlay(item, options.GetMaxBitrate(), subtitleStream, options, PlayMethod.DirectStream);
+ bool isEligibleForDirectPlay = options.EnableDirectPlay && (options.ForceDirectPlay || IsEligibleForDirectPlay(item, GetBitrateForDirectPlayCheck(item, options), subtitleStream, options, PlayMethod.DirectPlay));
+ bool isEligibleForDirectStream = options.EnableDirectStream && (options.ForceDirectStream || IsEligibleForDirectPlay(item, options.GetMaxBitrate(), subtitleStream, options, PlayMethod.DirectStream));
_logger.Info("Profile: {0}, Path: {1}, isEligibleForDirectPlay: {2}, isEligibleForDirectStream: {3}",
options.Profile.Name ?? "Unknown Profile",
@@ -374,7 +415,7 @@ namespace MediaBrowser.Model.Dlna
if (isEligibleForDirectPlay || isEligibleForDirectStream)
{
// See if it can be direct played
- PlayMethod? directPlay = GetVideoDirectPlayProfile(options.Profile, item, videoStream, audioStream, isEligibleForDirectPlay, isEligibleForDirectStream);
+ PlayMethod? directPlay = GetVideoDirectPlayProfile(options, item, videoStream, audioStream, isEligibleForDirectPlay, isEligibleForDirectStream);
if (directPlay != null)
{
@@ -383,7 +424,7 @@ namespace MediaBrowser.Model.Dlna
if (subtitleStream != null)
{
- SubtitleProfile subtitleProfile = GetSubtitleProfile(subtitleStream, options.Profile.SubtitleProfiles, options.Context, directPlay.Value);
+ SubtitleProfile subtitleProfile = GetSubtitleProfile(subtitleStream, options.Profile.SubtitleProfiles, directPlay.Value);
playlistItem.SubtitleDeliveryMethod = subtitleProfile.Method;
playlistItem.SubtitleFormat = subtitleProfile.Format;
@@ -413,7 +454,7 @@ namespace MediaBrowser.Model.Dlna
if (subtitleStream != null)
{
- SubtitleProfile subtitleProfile = GetSubtitleProfile(subtitleStream, options.Profile.SubtitleProfiles, options.Context, PlayMethod.Transcode);
+ SubtitleProfile subtitleProfile = GetSubtitleProfile(subtitleStream, options.Profile.SubtitleProfiles, PlayMethod.Transcode);
playlistItem.SubtitleDeliveryMethod = subtitleProfile.Method;
playlistItem.SubtitleFormat = subtitleProfile.Format;
@@ -424,38 +465,54 @@ namespace MediaBrowser.Model.Dlna
playlistItem.EstimateContentLength = transcodingProfile.EstimateContentLength;
playlistItem.TranscodeSeekInfo = transcodingProfile.TranscodeSeekInfo;
- // TODO: We should probably preserve the full list and sent it tp the server that way
- string[] supportedAudioCodecs = transcodingProfile.AudioCodec.Split(',');
- string inputAudioCodec = audioStream == null ? null : audioStream.Codec;
- foreach (string supportedAudioCodec in supportedAudioCodecs)
+ playlistItem.AudioCodecs = transcodingProfile.AudioCodec.Split(',');
+
+ playlistItem.VideoCodec = transcodingProfile.VideoCodec;
+ playlistItem.CopyTimestamps = transcodingProfile.CopyTimestamps;
+ playlistItem.ForceLiveStream = transcodingProfile.ForceLiveStream;
+ playlistItem.EnableSubtitlesInManifest = transcodingProfile.EnableSubtitlesInManifest;
+
+ if (!string.IsNullOrEmpty(transcodingProfile.MaxAudioChannels))
{
- if (StringHelper.EqualsIgnoreCase(supportedAudioCodec, inputAudioCodec))
+ int transcodingMaxAudioChannels;
+ if (IntHelper.TryParseCultureInvariant(transcodingProfile.MaxAudioChannels, out transcodingMaxAudioChannels))
{
- playlistItem.AudioCodec = supportedAudioCodec;
- break;
+ playlistItem.TranscodingMaxAudioChannels = transcodingMaxAudioChannels;
}
}
-
- if (string.IsNullOrEmpty(playlistItem.AudioCodec))
- {
- playlistItem.AudioCodec = supportedAudioCodecs[0];
- }
-
- playlistItem.VideoCodec = transcodingProfile.VideoCodec;
- playlistItem.CopyTimestamps = transcodingProfile.CopyTimestamps;
playlistItem.SubProtocol = transcodingProfile.Protocol;
playlistItem.AudioStreamIndex = audioStreamIndex;
+ ConditionProcessor conditionProcessor = new ConditionProcessor();
List<ProfileCondition> videoTranscodingConditions = new List<ProfileCondition>();
foreach (CodecProfile i in options.Profile.CodecProfiles)
{
if (i.Type == CodecType.Video && i.ContainsCodec(transcodingProfile.VideoCodec, transcodingProfile.Container))
{
- foreach (ProfileCondition c in i.Conditions)
+ bool applyConditions = true;
+ foreach (ProfileCondition applyCondition in i.ApplyConditions)
{
- videoTranscodingConditions.Add(c);
+ bool? isSecondaryAudio = audioStream == null ? null : item.IsSecondaryAudio(audioStream);
+ int? inputAudioBitrate = audioStream == null ? null : audioStream.BitRate;
+ int? audioChannels = audioStream == null ? null : audioStream.Channels;
+ string audioProfile = audioStream == null ? null : audioStream.Profile;
+
+ if (!conditionProcessor.IsVideoAudioConditionSatisfied(applyCondition, audioChannels, inputAudioBitrate, audioProfile, isSecondaryAudio))
+ {
+ LogConditionFailure(options.Profile, "AudioCodecProfile", applyCondition, item);
+ applyConditions = false;
+ break;
+ }
+ }
+
+ if (applyConditions)
+ {
+ foreach (ProfileCondition c in i.Conditions)
+ {
+ videoTranscodingConditions.Add(c);
+ }
+ break;
}
- break;
}
}
ApplyTranscodingConditions(playlistItem, videoTranscodingConditions);
@@ -463,13 +520,44 @@ 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)
+ bool applyConditions = true;
+ foreach (ProfileCondition applyCondition in i.ApplyConditions)
{
- audioTranscodingConditions.Add(c);
+ int? width = videoStream == null ? null : videoStream.Width;
+ int? height = videoStream == null ? null : videoStream.Height;
+ int? bitDepth = videoStream == null ? null : videoStream.BitDepth;
+ int? videoBitrate = videoStream == null ? null : videoStream.BitRate;
+ double? videoLevel = videoStream == null ? null : videoStream.Level;
+ string videoProfile = videoStream == null ? null : videoStream.Profile;
+ float? videoFramerate = videoStream == null ? null : videoStream.AverageFrameRate ?? videoStream.AverageFrameRate;
+ bool? isAnamorphic = videoStream == null ? null : videoStream.IsAnamorphic;
+ string videoCodecTag = videoStream == null ? null : videoStream.CodecTag;
+
+ TransportStreamTimestamp? timestamp = videoStream == null ? TransportStreamTimestamp.None : item.Timestamp;
+ int? packetLength = videoStream == null ? null : videoStream.PacketLength;
+ int? refFrames = videoStream == null ? null : videoStream.RefFrames;
+
+ int? numAudioStreams = item.GetStreamCount(MediaStreamType.Audio);
+ int? numVideoStreams = item.GetStreamCount(MediaStreamType.Video);
+
+ if (!conditionProcessor.IsVideoConditionSatisfied(applyCondition, width, height, bitDepth, videoBitrate, videoProfile, videoLevel, videoFramerate, packetLength, timestamp, isAnamorphic, refFrames, numVideoStreams, numAudioStreams, videoCodecTag))
+ {
+ LogConditionFailure(options.Profile, "VideoCodecProfile", applyCondition, item);
+ applyConditions = false;
+ break;
+ }
+ }
+
+ if (applyConditions)
+ {
+ foreach (ProfileCondition c in i.Conditions)
+ {
+ audioTranscodingConditions.Add(c);
+ }
+ break;
}
- break;
}
}
ApplyTranscodingConditions(playlistItem, audioTranscodingConditions);
@@ -508,12 +596,23 @@ namespace MediaBrowser.Model.Dlna
private int GetAudioBitrate(int? maxTotalBitrate, int? targetAudioChannels, string targetAudioCodec, MediaStream audioStream)
{
var defaultBitrate = 128000;
+ if (StringHelper.EqualsIgnoreCase(targetAudioCodec, "ac3"))
+ {
+ defaultBitrate = 192000;
+ }
if (targetAudioChannels.HasValue)
{
if (targetAudioChannels.Value >= 5 && (maxTotalBitrate ?? 0) >= 2000000)
{
- defaultBitrate = 320000;
+ if (StringHelper.EqualsIgnoreCase(targetAudioCodec, "ac3"))
+ {
+ defaultBitrate = 448000;
+ }
+ else
+ {
+ defaultBitrate = 320000;
+ }
}
}
@@ -536,13 +635,24 @@ namespace MediaBrowser.Model.Dlna
return Math.Min(defaultBitrate, encoderAudioBitrateLimit);
}
- private PlayMethod? GetVideoDirectPlayProfile(DeviceProfile profile,
+ private PlayMethod? GetVideoDirectPlayProfile(VideoOptions options,
MediaSourceInfo mediaSource,
MediaStream videoStream,
MediaStream audioStream,
bool isEligibleForDirectPlay,
bool isEligibleForDirectStream)
{
+ DeviceProfile profile = options.Profile;
+
+ if (options.ForceDirectPlay)
+ {
+ return PlayMethod.DirectPlay;
+ }
+ if (options.ForceDirectStream)
+ {
+ return PlayMethod.DirectStream;
+ }
+
if (videoStream == null)
{
_logger.Info("Profile: {0}, Cannot direct stream with no known video stream. Path: {1}",
@@ -597,7 +707,6 @@ namespace MediaBrowser.Model.Dlna
string videoProfile = videoStream == null ? null : videoStream.Profile;
float? videoFramerate = videoStream == null ? null : videoStream.AverageFrameRate ?? videoStream.AverageFrameRate;
bool? isAnamorphic = videoStream == null ? null : videoStream.IsAnamorphic;
- bool? isCabac = videoStream == null ? null : videoStream.IsCabac;
string videoCodecTag = videoStream == null ? null : videoStream.CodecTag;
int? audioBitrate = audioStream == null ? null : audioStream.BitRate;
@@ -614,7 +723,7 @@ namespace MediaBrowser.Model.Dlna
// Check container conditions
foreach (ProfileCondition i in conditions)
{
- if (!conditionProcessor.IsVideoConditionSatisfied(i, width, height, bitDepth, videoBitrate, videoProfile, videoLevel, videoFramerate, packetLength, timestamp, isAnamorphic, isCabac, refFrames, numVideoStreams, numAudioStreams, videoCodecTag))
+ if (!conditionProcessor.IsVideoConditionSatisfied(i, width, height, bitDepth, videoBitrate, videoProfile, videoLevel, videoFramerate, packetLength, timestamp, isAnamorphic, refFrames, numVideoStreams, numAudioStreams, videoCodecTag))
{
LogConditionFailure(profile, "VideoContainerProfile", i, mediaSource);
@@ -638,16 +747,30 @@ namespace MediaBrowser.Model.Dlna
{
if (i.Type == CodecType.Video && i.ContainsCodec(videoCodec, container))
{
- foreach (ProfileCondition c in i.Conditions)
+ bool applyConditions = true;
+ foreach (ProfileCondition applyCondition in i.ApplyConditions)
{
- conditions.Add(c);
+ if (!conditionProcessor.IsVideoConditionSatisfied(applyCondition, width, height, bitDepth, videoBitrate, videoProfile, videoLevel, videoFramerate, packetLength, timestamp, isAnamorphic, refFrames, numVideoStreams, numAudioStreams, videoCodecTag))
+ {
+ LogConditionFailure(profile, "VideoCodecProfile", applyCondition, mediaSource);
+ applyConditions = false;
+ break;
+ }
+ }
+
+ if (applyConditions)
+ {
+ foreach (ProfileCondition c in i.Conditions)
+ {
+ conditions.Add(c);
+ }
}
}
}
foreach (ProfileCondition i in conditions)
{
- if (!conditionProcessor.IsVideoConditionSatisfied(i, width, height, bitDepth, videoBitrate, videoProfile, videoLevel, videoFramerate, packetLength, timestamp, isAnamorphic, isCabac, refFrames, numVideoStreams, numAudioStreams, videoCodecTag))
+ if (!conditionProcessor.IsVideoConditionSatisfied(i, width, height, bitDepth, videoBitrate, videoProfile, videoLevel, videoFramerate, packetLength, timestamp, isAnamorphic, refFrames, numVideoStreams, numAudioStreams, videoCodecTag))
{
LogConditionFailure(profile, "VideoCodecProfile", i, mediaSource);
@@ -669,20 +792,35 @@ namespace MediaBrowser.Model.Dlna
}
conditions = new List<ProfileCondition>();
+ bool? isSecondaryAudio = audioStream == null ? null : mediaSource.IsSecondaryAudio(audioStream);
+
foreach (CodecProfile i in profile.CodecProfiles)
{
if (i.Type == CodecType.VideoAudio && i.ContainsCodec(audioCodec, container))
{
- foreach (ProfileCondition c in i.Conditions)
+ bool applyConditions = true;
+ foreach (ProfileCondition applyCondition in i.ApplyConditions)
{
- conditions.Add(c);
+ if (!conditionProcessor.IsVideoAudioConditionSatisfied(applyCondition, audioChannels, audioBitrate, audioProfile, isSecondaryAudio))
+ {
+ LogConditionFailure(profile, "VideoAudioCodecProfile", applyCondition, mediaSource);
+ applyConditions = false;
+ break;
+ }
+ }
+
+ if (applyConditions)
+ {
+ foreach (ProfileCondition c in i.Conditions)
+ {
+ conditions.Add(c);
+ }
}
}
}
foreach (ProfileCondition i in conditions)
{
- bool? isSecondaryAudio = audioStream == null ? null : mediaSource.IsSecondaryAudio(audioStream);
if (!conditionProcessor.IsVideoAudioConditionSatisfied(i, audioChannels, audioBitrate, audioProfile, isSecondaryAudio))
{
LogConditionFailure(profile, "VideoAudioCodecProfile", i, mediaSource);
@@ -692,25 +830,6 @@ namespace MediaBrowser.Model.Dlna
}
}
- if (isEligibleForDirectPlay && mediaSource.SupportsDirectPlay)
- {
- if (mediaSource.Protocol == MediaProtocol.Http)
- {
- if (_localPlayer.CanAccessUrl(mediaSource.Path, mediaSource.RequiredHttpHeaders.Count > 0))
- {
- return PlayMethod.DirectPlay;
- }
- }
-
- else if (mediaSource.Protocol == MediaProtocol.File)
- {
- if (_localPlayer.CanAccessFile(mediaSource.Path))
- {
- return PlayMethod.DirectPlay;
- }
- }
- }
-
if (isEligibleForDirectStream && mediaSource.SupportsDirectStream)
{
return PlayMethod.DirectStream;
@@ -739,7 +858,7 @@ namespace MediaBrowser.Model.Dlna
{
if (subtitleStream != null)
{
- SubtitleProfile subtitleProfile = GetSubtitleProfile(subtitleStream, options.Profile.SubtitleProfiles, options.Context, playMethod);
+ SubtitleProfile subtitleProfile = GetSubtitleProfile(subtitleStream, options.Profile.SubtitleProfiles, playMethod);
if (subtitleProfile.Method != SubtitleDeliveryMethod.External && subtitleProfile.Method != SubtitleDeliveryMethod.Embed)
{
@@ -751,7 +870,7 @@ namespace MediaBrowser.Model.Dlna
return IsAudioEligibleForDirectPlay(item, maxBitrate);
}
- public static SubtitleProfile GetSubtitleProfile(MediaStream subtitleStream, SubtitleProfile[] subtitleProfiles, EncodingContext context, PlayMethod playMethod)
+ public static SubtitleProfile GetSubtitleProfile(MediaStream subtitleStream, SubtitleProfile[] subtitleProfiles, PlayMethod playMethod)
{
if (playMethod != PlayMethod.Transcode && !subtitleStream.IsExternal)
{
@@ -775,7 +894,16 @@ namespace MediaBrowser.Model.Dlna
}
}
- // Look for an external profile that matches the stream type (text/graphical)
+ // Look for an external or hls profile that matches the stream type (text/graphical) and doesn't require conversion
+ return GetExternalSubtitleProfile(subtitleStream, subtitleProfiles, playMethod, false) ?? GetExternalSubtitleProfile(subtitleStream, subtitleProfiles, playMethod, true) ?? new SubtitleProfile
+ {
+ Method = SubtitleDeliveryMethod.Encode,
+ Format = subtitleStream.Codec
+ };
+ }
+
+ private static SubtitleProfile GetExternalSubtitleProfile(MediaStream subtitleStream, SubtitleProfile[] subtitleProfiles, PlayMethod playMethod, bool allowConversion)
+ {
foreach (SubtitleProfile profile in subtitleProfiles)
{
if (profile.Method != SubtitleDeliveryMethod.External && profile.Method != SubtitleDeliveryMethod.Hls)
@@ -798,32 +926,47 @@ namespace MediaBrowser.Model.Dlna
{
bool requiresConversion = !StringHelper.EqualsIgnoreCase(subtitleStream.Codec, profile.Format);
- if (subtitleStream.IsTextSubtitleStream || !requiresConversion)
+ if (!requiresConversion)
{
- if (subtitleStream.SupportsExternalStream)
- {
- return profile;
- }
+ return profile;
+ }
+
+ if (!allowConversion)
+ {
+ continue;
+ }
+
+ if (subtitleStream.IsTextSubtitleStream && subtitleStream.SupportsExternalStream && subtitleStream.SupportsSubtitleConversionTo(profile.Format))
+ {
+ return profile;
}
}
}
- return new SubtitleProfile
- {
- Method = SubtitleDeliveryMethod.Encode,
- Format = subtitleStream.Codec
- };
+ return null;
}
private bool IsAudioEligibleForDirectPlay(MediaSourceInfo item, int? maxBitrate)
{
- if (!maxBitrate.HasValue || (item.Bitrate.HasValue && item.Bitrate.Value <= maxBitrate.Value))
+ if (!maxBitrate.HasValue)
+ {
+ _logger.Info("Cannot direct play due to unknown supported bitrate");
+ return false;
+ }
+
+ if (!item.Bitrate.HasValue)
+ {
+ _logger.Info("Cannot direct play due to unknown content bitrate");
+ return false;
+ }
+
+ if (item.Bitrate.Value > maxBitrate.Value)
{
- return true;
+ _logger.Info("Bitrate exceeds DirectPlay limit");
+ return false;
}
- _logger.Info("Bitrate exceeds DirectPlay limit");
- return false;
+ return true;
}
private void ValidateInput(VideoOptions options)
@@ -898,22 +1041,6 @@ namespace MediaBrowser.Model.Dlna
}
break;
}
- case ProfileConditionValue.IsCabac:
- {
- bool val;
- if (BoolHelper.TryParseCultureInvariant(value, out val))
- {
- if (condition.Condition == ProfileConditionType.Equals)
- {
- item.Cabac = val;
- }
- else if (condition.Condition == ProfileConditionType.NotEquals)
- {
- item.Cabac = !val;
- }
- }
- break;
- }
case ProfileConditionValue.IsAnamorphic:
case ProfileConditionValue.AudioProfile:
case ProfileConditionValue.Has64BitOffsets:
@@ -1019,6 +1146,18 @@ namespace MediaBrowser.Model.Dlna
}
}
+ // Check audio codec
+ List<string> audioCodecs = profile.GetAudioCodecs();
+ if (audioCodecs.Count > 0)
+ {
+ // Check audio codecs
+ string audioCodec = audioStream == null ? null : audioStream.Codec;
+ if (string.IsNullOrEmpty(audioCodec) || !ListHelper.ContainsIgnoreCase(audioCodecs, audioCodec))
+ {
+ return false;
+ }
+ }
+
return true;
}
@@ -1054,6 +1193,7 @@ namespace MediaBrowser.Model.Dlna
}
}
+ // Check audio codec
List<string> audioCodecs = profile.GetAudioCodecs();
if (audioCodecs.Count > 0)
{
diff --git a/MediaBrowser.Model/Dlna/StreamInfo.cs b/MediaBrowser.Model/Dlna/StreamInfo.cs
index f78047d47..f95c6a070 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; }
@@ -30,14 +35,16 @@ namespace MediaBrowser.Model.Dlna
public string VideoCodec { get; set; }
public string VideoProfile { get; set; }
- public bool? Cabac { get; set; }
public bool CopyTimestamps { get; set; }
- public string AudioCodec { get; set; }
+ public bool ForceLiveStream { get; set; }
+ public bool EnableSubtitlesInManifest { get; set; }
+ public string[] AudioCodecs { get; set; }
public int? AudioStreamIndex { get; set; }
public int? SubtitleStreamIndex { get; set; }
+ public int? TranscodingMaxAudioChannels { get; set; }
public int? MaxAudioChannels { get; set; }
public int? AudioBitrate { get; set; }
@@ -190,12 +197,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));
@@ -205,7 +216,7 @@ namespace MediaBrowser.Model.Dlna
list.Add(new NameValuePair("MaxWidth", item.MaxWidth.HasValue ? StringHelper.ToStringCultureInvariant(item.MaxWidth.Value) : string.Empty));
list.Add(new NameValuePair("MaxHeight", item.MaxHeight.HasValue ? StringHelper.ToStringCultureInvariant(item.MaxHeight.Value) : string.Empty));
- if (StringHelper.EqualsIgnoreCase(item.SubProtocol, "hls"))
+ if (StringHelper.EqualsIgnoreCase(item.SubProtocol, "hls") && !item.ForceLiveStream)
{
list.Add(new NameValuePair("StartTimeTicks", string.Empty));
}
@@ -219,7 +230,9 @@ namespace MediaBrowser.Model.Dlna
list.Add(new NameValuePair("MaxRefFrames", item.MaxRefFrames.HasValue ? StringHelper.ToStringCultureInvariant(item.MaxRefFrames.Value) : string.Empty));
list.Add(new NameValuePair("MaxVideoBitDepth", item.MaxVideoBitDepth.HasValue ? StringHelper.ToStringCultureInvariant(item.MaxVideoBitDepth.Value) : string.Empty));
list.Add(new NameValuePair("Profile", item.VideoProfile ?? string.Empty));
- list.Add(new NameValuePair("Cabac", item.Cabac.HasValue ? item.Cabac.Value.ToString() : string.Empty));
+
+ // no longer used
+ list.Add(new NameValuePair("Cabac", string.Empty));
list.Add(new NameValuePair("PlaySessionId", item.PlaySessionId ?? string.Empty));
list.Add(new NameValuePair("api_key", accessToken ?? string.Empty));
@@ -233,8 +246,12 @@ namespace MediaBrowser.Model.Dlna
}
list.Add(new NameValuePair("CopyTimestamps", item.CopyTimestamps.ToString().ToLower()));
+ list.Add(new NameValuePair("ForceLiveStream", item.ForceLiveStream.ToString().ToLower()));
list.Add(new NameValuePair("SubtitleMethod", item.SubtitleStreamIndex.HasValue && item.SubtitleDeliveryMethod != SubtitleDeliveryMethod.External ? item.SubtitleDeliveryMethod.ToString() : string.Empty));
-
+
+ list.Add(new NameValuePair("TranscodingMaxAudioChannels", item.TranscodingMaxAudioChannels.HasValue ? StringHelper.ToStringCultureInvariant(item.TranscodingMaxAudioChannels.Value) : string.Empty));
+ list.Add(new NameValuePair("EnableSubtitlesInManifest", item.EnableSubtitlesInManifest.ToString().ToLower()));
+
return list;
}
@@ -272,7 +289,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)
@@ -321,7 +338,7 @@ namespace MediaBrowser.Model.Dlna
private SubtitleStreamInfo GetSubtitleStreamInfo(MediaStream stream, string baseUrl, string accessToken, long startPositionTicks, SubtitleProfile[] subtitleProfiles)
{
- SubtitleProfile subtitleProfile = StreamBuilder.GetSubtitleProfile(stream, subtitleProfiles, Context, PlayMethod);
+ SubtitleProfile subtitleProfile = StreamBuilder.GetSubtitleProfile(stream, subtitleProfiles, PlayMethod);
SubtitleStreamInfo info = new SubtitleStreamInfo
{
IsForced = stream.IsForced,
@@ -329,7 +346,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)
@@ -548,9 +566,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];
}
}
@@ -632,19 +663,6 @@ namespace MediaBrowser.Model.Dlna
}
}
- public bool? IsTargetCabac
- {
- get
- {
- if (IsDirectStream)
- {
- return TargetVideoStream == null ? null : TargetVideoStream.IsCabac;
- }
-
- return true;
- }
- }
-
public int? TargetWidth
{
get
diff --git a/MediaBrowser.Model/Dlna/StreamInfoSorter.cs b/MediaBrowser.Model/Dlna/StreamInfoSorter.cs
index 0cccd8080..293054e5b 100644
--- a/MediaBrowser.Model/Dlna/StreamInfoSorter.cs
+++ b/MediaBrowser.Model/Dlna/StreamInfoSorter.cs
@@ -7,7 +7,7 @@ namespace MediaBrowser.Model.Dlna
{
public class StreamInfoSorter
{
- public static List<StreamInfo> SortMediaSources(List<StreamInfo> streams)
+ public static List<StreamInfo> SortMediaSources(List<StreamInfo> streams, int? maxBitrate)
{
return streams.OrderBy(i =>
{
@@ -41,6 +41,23 @@ namespace MediaBrowser.Model.Dlna
return 1;
}
+ }).ThenBy(i =>
+ {
+ if (maxBitrate.HasValue)
+ {
+ if (i.MediaSource.Bitrate.HasValue)
+ {
+ if (i.MediaSource.Bitrate.Value <= maxBitrate.Value)
+ {
+ return 0;
+ }
+
+ return 2;
+ }
+ }
+
+ return 1;
+
}).ToList();
}
}
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/Dlna/TranscodingProfile.cs b/MediaBrowser.Model/Dlna/TranscodingProfile.cs
index e59ee6d63..19caf85eb 100644
--- a/MediaBrowser.Model/Dlna/TranscodingProfile.cs
+++ b/MediaBrowser.Model/Dlna/TranscodingProfile.cs
@@ -19,7 +19,7 @@ namespace MediaBrowser.Model.Dlna
[XmlAttribute("protocol")]
public string Protocol { get; set; }
-
+
[XmlAttribute("estimateContentLength")]
public bool EstimateContentLength { get; set; }
@@ -35,6 +35,15 @@ namespace MediaBrowser.Model.Dlna
[XmlAttribute("context")]
public EncodingContext Context { get; set; }
+ [XmlAttribute("forceLiveStream")]
+ public bool ForceLiveStream { get; set; }
+
+ [XmlAttribute("enableSubtitlesInManifest")]
+ public bool EnableSubtitlesInManifest { get; set; }
+
+ [XmlAttribute("maxAudioChannels")]
+ public string MaxAudioChannels { get; set; }
+
public List<string> GetAudioCodecs()
{
List<string> list = new List<string>();
diff --git a/MediaBrowser.Model/Dto/BaseItemDto.cs b/MediaBrowser.Model/Dto/BaseItemDto.cs
index 7928d0bf9..8ca1dfcb1 100644
--- a/MediaBrowser.Model/Dto/BaseItemDto.cs
+++ b/MediaBrowser.Model/Dto/BaseItemDto.cs
@@ -18,7 +18,7 @@ namespace MediaBrowser.Model.Dto
/// This holds information about a BaseItem in a format that is convenient for the client.
/// </summary>
[DebuggerDisplay("Name = {Name}, ID = {Id}, Type = {Type}")]
- public class BaseItemDto : IHasProviderIds, IHasPropertyChangedEvent, IItemDto, IHasServerId, IHasSyncInfo
+ public class BaseItemDto : IHasProviderIds, IItemDto, IHasServerId, IHasSyncInfo
{
/// <summary>
/// Gets or sets the name.
@@ -26,6 +26,8 @@ namespace MediaBrowser.Model.Dto
/// <value>The name.</value>
public string Name { get; set; }
+ public string OriginalTitle { get; set; }
+
/// <summary>
/// Gets or sets the server identifier.
/// </summary>
@@ -112,6 +114,8 @@ namespace MediaBrowser.Model.Dto
/// <value>The synchronize percent.</value>
public double? SyncPercent { get; set; }
+ public string Container { get; set; }
+
/// <summary>
/// Gets or sets the DVD season number.
/// </summary>
@@ -295,7 +299,8 @@ namespace MediaBrowser.Model.Dto
/// </summary>
/// <value>The number.</value>
public string Number { get; set; }
-
+ public string ChannelNumber { get; set; }
+
/// <summary>
/// Gets or sets the index number.
/// </summary>
@@ -952,6 +957,16 @@ namespace MediaBrowser.Model.Dto
}
/// <summary>
+ /// Gets a value indicating whether this instance has thumb.
+ /// </summary>
+ /// <value><c>true</c> if this instance has thumb; otherwise, <c>false</c>.</value>
+ [IgnoreDataMember]
+ public bool HasBackdrop
+ {
+ get { return (BackdropImageTags != null && BackdropImageTags.Count > 0) || (ParentBackdropImageTags != null && ParentBackdropImageTags.Count > 0); }
+ }
+
+ /// <summary>
/// Gets a value indicating whether this instance has primary image.
/// </summary>
/// <value><c>true</c> if this instance has primary image; otherwise, <c>false</c>.</value>
@@ -1097,11 +1112,6 @@ namespace MediaBrowser.Model.Dto
}
/// <summary>
- /// Occurs when [property changed].
- /// </summary>
- public event PropertyChangedEventHandler PropertyChanged;
-
- /// <summary>
/// Gets or sets the program identifier.
/// </summary>
/// <value>The program identifier.</value>
diff --git a/MediaBrowser.Model/Dto/BaseItemPerson.cs b/MediaBrowser.Model/Dto/BaseItemPerson.cs
index 8e7750562..b74912907 100644
--- a/MediaBrowser.Model/Dto/BaseItemPerson.cs
+++ b/MediaBrowser.Model/Dto/BaseItemPerson.cs
@@ -1,5 +1,4 @@
-using MediaBrowser.Model.Extensions;
-using System.ComponentModel;
+using System.ComponentModel;
using System.Diagnostics;
using System.Runtime.Serialization;
@@ -9,7 +8,7 @@ namespace MediaBrowser.Model.Dto
/// This is used by the api to get information about a Person within a BaseItem
/// </summary>
[DebuggerDisplay("Name = {Name}, Role = {Role}, Type = {Type}")]
- public class BaseItemPerson : IHasPropertyChangedEvent
+ public class BaseItemPerson
{
/// <summary>
/// Gets or sets the name.
@@ -53,10 +52,5 @@ namespace MediaBrowser.Model.Dto
return PrimaryImageTag != null;
}
}
-
- /// <summary>
- /// Occurs when [property changed].
- /// </summary>
- public event PropertyChangedEventHandler PropertyChanged;
}
}
diff --git a/MediaBrowser.Model/Dto/ChapterInfoDto.cs b/MediaBrowser.Model/Dto/ChapterInfoDto.cs
index 62b1839d4..a71d97990 100644
--- a/MediaBrowser.Model/Dto/ChapterInfoDto.cs
+++ b/MediaBrowser.Model/Dto/ChapterInfoDto.cs
@@ -1,7 +1,5 @@
-using System.ComponentModel;
-using System.Diagnostics;
+using System.Diagnostics;
using System.Runtime.Serialization;
-using MediaBrowser.Model.Extensions;
namespace MediaBrowser.Model.Dto
{
@@ -9,7 +7,7 @@ namespace MediaBrowser.Model.Dto
/// Class ChapterInfo
/// </summary>
[DebuggerDisplay("Name = {Name}")]
- public class ChapterInfoDto : IHasPropertyChangedEvent
+ public class ChapterInfoDto
{
/// <summary>
/// Gets or sets the start position ticks.
@@ -38,7 +36,5 @@ namespace MediaBrowser.Model.Dto
{
get { return ImageTag != null; }
}
-
- public event PropertyChangedEventHandler PropertyChanged;
}
}
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/Dto/MediaSourceInfo.cs b/MediaBrowser.Model/Dto/MediaSourceInfo.cs
index 2de4aa8ea..4e3e60063 100644
--- a/MediaBrowser.Model/Dto/MediaSourceInfo.cs
+++ b/MediaBrowser.Model/Dto/MediaSourceInfo.cs
@@ -160,6 +160,16 @@ namespace MediaBrowser.Model.Dto
public bool? IsSecondaryAudio(MediaStream stream)
{
+ // Look for the first audio track marked as default
+ foreach (MediaStream currentStream in MediaStreams)
+ {
+ if (currentStream.Type == MediaStreamType.Audio && currentStream.IsDefault)
+ {
+ return currentStream.Index != stream.Index;
+ }
+ }
+
+ // Look for the first audio track
foreach (MediaStream currentStream in MediaStreams)
{
if (currentStream.Type == MediaStreamType.Audio)
diff --git a/MediaBrowser.Model/Dto/UserDto.cs b/MediaBrowser.Model/Dto/UserDto.cs
index f133f3343..18470466c 100644
--- a/MediaBrowser.Model/Dto/UserDto.cs
+++ b/MediaBrowser.Model/Dto/UserDto.cs
@@ -1,6 +1,5 @@
using MediaBrowser.Model.Configuration;
using MediaBrowser.Model.Connect;
-using MediaBrowser.Model.Extensions;
using MediaBrowser.Model.Users;
using System;
using System.ComponentModel;
@@ -13,7 +12,7 @@ namespace MediaBrowser.Model.Dto
/// Class UserDto
/// </summary>
[DebuggerDisplay("Name = {Name}, ID = {Id}, HasPassword = {HasPassword}")]
- public class UserDto : IHasPropertyChangedEvent, IItemDto, IHasServerId
+ public class UserDto : IItemDto, IHasServerId
{
/// <summary>
/// Gets or sets the name.
@@ -141,11 +140,6 @@ namespace MediaBrowser.Model.Dto
Policy = new UserPolicy();
}
- /// <summary>
- /// Occurs when [property changed].
- /// </summary>
- public event PropertyChangedEventHandler PropertyChanged;
-
public override string ToString()
{
return Name ?? base.ToString();
diff --git a/MediaBrowser.Model/Dto/UserItemDataDto.cs b/MediaBrowser.Model/Dto/UserItemDataDto.cs
index ce0c8fa99..00491002b 100644
--- a/MediaBrowser.Model/Dto/UserItemDataDto.cs
+++ b/MediaBrowser.Model/Dto/UserItemDataDto.cs
@@ -1,5 +1,4 @@
-using MediaBrowser.Model.Extensions;
-using System;
+using System;
using System.ComponentModel;
namespace MediaBrowser.Model.Dto
@@ -7,7 +6,7 @@ namespace MediaBrowser.Model.Dto
/// <summary>
/// Class UserItemDataDto
/// </summary>
- public class UserItemDataDto : IHasPropertyChangedEvent
+ public class UserItemDataDto
{
/// <summary>
/// Gets or sets the rating.
@@ -74,7 +73,5 @@ namespace MediaBrowser.Model.Dto
/// </summary>
/// <value>The item identifier.</value>
public string ItemId { get; set; }
-
- public event PropertyChangedEventHandler PropertyChanged;
}
}
diff --git a/MediaBrowser.Model/Entities/ChapterInfo.cs b/MediaBrowser.Model/Entities/ChapterInfo.cs
index 9da7a9caa..7e5700965 100644
--- a/MediaBrowser.Model/Entities/ChapterInfo.cs
+++ b/MediaBrowser.Model/Entities/ChapterInfo.cs
@@ -1,4 +1,5 @@
-
+using System;
+
namespace MediaBrowser.Model.Entities
{
/// <summary>
@@ -23,5 +24,6 @@ namespace MediaBrowser.Model.Entities
/// </summary>
/// <value>The image path.</value>
public string ImagePath { get; set; }
+ public DateTime ImageDateModified { get; set; }
}
}
diff --git a/MediaBrowser.Model/Entities/DisplayPreferences.cs b/MediaBrowser.Model/Entities/DisplayPreferences.cs
index 6399670c9..0ba8eaa4f 100644
--- a/MediaBrowser.Model/Entities/DisplayPreferences.cs
+++ b/MediaBrowser.Model/Entities/DisplayPreferences.cs
@@ -1,22 +1,15 @@
using MediaBrowser.Model.Drawing;
using System;
using System.Collections.Generic;
-using System.ComponentModel;
-using MediaBrowser.Model.Extensions;
namespace MediaBrowser.Model.Entities
{
/// <summary>
/// Defines the display preferences for any item that supports them (usually Folders)
/// </summary>
- public class DisplayPreferences : IHasPropertyChangedEvent
+ public class DisplayPreferences
{
/// <summary>
- /// Occurs when [property changed].
- /// </summary>
- public event PropertyChangedEventHandler PropertyChanged;
-
- /// <summary>
/// The image scale
/// </summary>
private const double ImageScale = .9;
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 79fa46baf..990de332e 100644
--- a/MediaBrowser.Model/Entities/MediaStream.cs
+++ b/MediaBrowser.Model/Entities/MediaStream.cs
@@ -1,6 +1,8 @@
-using MediaBrowser.Model.Dlna;
+using System.Collections.Generic;
+using MediaBrowser.Model.Dlna;
using MediaBrowser.Model.Extensions;
using System.Diagnostics;
+using MediaBrowser.Model.MediaInfo;
namespace MediaBrowser.Model.Entities
{
@@ -33,13 +35,96 @@ namespace MediaBrowser.Model.Entities
/// </summary>
/// <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
+ {
+ get
+ {
+ if (!string.IsNullOrEmpty(Title))
+ {
+ return Title;
+ }
+
+ if (Type == MediaStreamType.Audio)
+ {
+ List<string> attributes = new List<string>();
+
+ if (!string.IsNullOrEmpty(Language))
+ {
+ attributes.Add(StringHelper.FirstToUpper(Language));
+ }
+ if (!string.IsNullOrEmpty(Codec) && !StringHelper.EqualsIgnoreCase(Codec, "dca"))
+ {
+ attributes.Add(AudioCodec.GetFriendlyName(Codec));
+ }
+ else if (!string.IsNullOrEmpty(Profile) && !StringHelper.EqualsIgnoreCase(Profile, "lc"))
+ {
+ attributes.Add(Profile);
+ }
+
+ if (!string.IsNullOrEmpty(ChannelLayout))
+ {
+ attributes.Add(ChannelLayout);
+ }
+ else if (Channels.HasValue)
+ {
+ attributes.Add(StringHelper.ToStringCultureInvariant(Channels.Value) + " ch");
+ }
+ if (IsDefault)
+ {
+ attributes.Add("Default");
+ }
+
+ return string.Join(" ", attributes.ToArray());
+ }
+
+ if (Type == MediaStreamType.Subtitle)
+ {
+ List<string> attributes = new List<string>();
+
+ if (!string.IsNullOrEmpty(Language))
+ {
+ attributes.Add(StringHelper.FirstToUpper(Language));
+ }
+ if (IsDefault)
+ {
+ attributes.Add("Default");
+ }
+
+ if (IsForced)
+ {
+ attributes.Add("Forced");
+ }
+
+ string name = string.Join(" ", attributes.ToArray());
+
+ return name;
+ }
+
+ if (Type == MediaStreamType.Video)
+ {
+
+ }
+
+ return null;
+ }
+ }
+
+ public string NalLengthSize { get; set; }
+
/// <summary>
/// Gets or sets a value indicating whether this instance is interlaced.
/// </summary>
/// <value><c>true</c> if this instance is interlaced; otherwise, <c>false</c>.</value>
public bool IsInterlaced { get; set; }
+ public bool? IsAVC { get; set; }
+
/// <summary>
/// Gets or sets the channel layout.
/// </summary>
@@ -197,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>
@@ -232,11 +347,5 @@ namespace MediaBrowser.Model.Entities
/// </summary>
/// <value><c>true</c> if this instance is anamorphic; otherwise, <c>false</c>.</value>
public bool? IsAnamorphic { get; set; }
-
- /// <summary>
- /// Gets or sets a value indicating whether this instance is cabac.
- /// </summary>
- /// <value><c>null</c> if [is cabac] contains no value, <c>true</c> if [is cabac]; otherwise, <c>false</c>.</value>
- public bool? IsCabac { get; set; }
}
}
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/Extensions/IHasPropertyChangedEvent.cs b/MediaBrowser.Model/Extensions/IHasPropertyChangedEvent.cs
deleted file mode 100644
index c87550620..000000000
--- a/MediaBrowser.Model/Extensions/IHasPropertyChangedEvent.cs
+++ /dev/null
@@ -1,8 +0,0 @@
-using System.ComponentModel;
-
-namespace MediaBrowser.Model.Extensions
-{
- public interface IHasPropertyChangedEvent : INotifyPropertyChanged
- {
- }
-}
diff --git a/MediaBrowser.Model/Extensions/StringHelper.cs b/MediaBrowser.Model/Extensions/StringHelper.cs
index 99bec68a7..9cde3bfa4 100644
--- a/MediaBrowser.Model/Extensions/StringHelper.cs
+++ b/MediaBrowser.Model/Extensions/StringHelper.cs
@@ -125,5 +125,10 @@ namespace MediaBrowser.Model.Extensions
return sb.ToString();
}
+
+ public static string FirstToUpper(this string str)
+ {
+ return string.IsNullOrEmpty(str) ? "" : str.Substring(0, 1).ToUpper() + str.Substring(1);
+ }
}
}
diff --git a/MediaBrowser.Model/Games/GameSystem.cs b/MediaBrowser.Model/Games/GameSystem.cs
deleted file mode 100644
index ab8f1efa7..000000000
--- a/MediaBrowser.Model/Games/GameSystem.cs
+++ /dev/null
@@ -1,48 +0,0 @@
-
-namespace MediaBrowser.Model.Games
-{
- public class GameSystem
- {
- public const string Nintendo = "Nintendo";
- public const string SuperNintendo = "Super Nintendo";
- public const string Panasonic3DO = "3DO";
- public const string Amiga = "Amiga";
- public const string Arcade = "Arcade";
- public const string Atari2600 = "Atari 2600";
- public const string Atari5200 = "Atari 5200";
- public const string Atari7800 = "Atari 7800";
- public const string AtariXE = "Atari XE";
- public const string AtariJaguar = "Atari Jaguar";
- public const string AtariJaguarCD = "Atari Jaguar CD";
- public const string Colecovision = "Colecovision";
- public const string Commodore64 = "Commodore 64";
- public const string CommodoreVic20 = "Commodore Vic-20";
- public const string Intellivision = "Intellivision";
- public const string MicrosoftXBox = "Xbox";
- public const string NeoGeo = "Neo Geo";
- public const string Nintendo64 = "Nintendo 64";
- public const string NintendoDS = "Nintendo DS";
- public const string NintendoGameBoy = "Game Boy";
- public const string NintendoGameBoyAdvance = "Game Boy Advance";
- public const string NintendoGameBoyColor = "Game Boy Color";
- public const string NintendoGameCube = "Gamecube";
- public const string VirtualBoy = "Virtual Boy";
- public const string Wii = "Nintendo Wii";
- public const string DOS = "DOS";
- public const string Windows = "Windows";
- public const string Sega32X = "Sega 32X";
- public const string SegaCD = "Sega CD";
- public const string SegaDreamcast = "Dreamcast";
- public const string SegaGameGear = "Game Gear";
- public const string SegaGenesis = "Sega Genesis";
- public const string SegaMasterSystem = "Sega Master System";
- public const string SegaMegaDrive = "Sega Mega Drive";
- public const string SegaSaturn = "Sega Saturn";
- public const string SonyPlaystation = "Sony Playstation";
- public const string SonyPlaystation2 = "PS2";
- public const string SonyPSP = "PSP";
- public const string TurboGrafx16 = "TurboGrafx 16";
- public const string TurboGrafxCD = "TurboGrafx CD";
- public const string ZxSpectrum = "ZX Spectrum";
- }
-}
diff --git a/MediaBrowser.Model/LiveTv/BaseTimerInfoDto.cs b/MediaBrowser.Model/LiveTv/BaseTimerInfoDto.cs
index 296ead5c2..4d863c6eb 100644
--- a/MediaBrowser.Model/LiveTv/BaseTimerInfoDto.cs
+++ b/MediaBrowser.Model/LiveTv/BaseTimerInfoDto.cs
@@ -1,18 +1,11 @@
using MediaBrowser.Model.Dto;
-using MediaBrowser.Model.Extensions;
using System;
-using System.ComponentModel;
namespace MediaBrowser.Model.LiveTv
{
- public class BaseTimerInfoDto : IHasPropertyChangedEvent, IHasServerId
+ public class BaseTimerInfoDto : IHasServerId
{
/// <summary>
- /// Occurs when a property value changes.
- /// </summary>
- public event PropertyChangedEventHandler PropertyChanged;
-
- /// <summary>
/// Id of the recording.
/// </summary>
public string Id { get; set; }
diff --git a/MediaBrowser.Model/LiveTv/ChannelInfoDto.cs b/MediaBrowser.Model/LiveTv/ChannelInfoDto.cs
index 91493def8..aa91e3c74 100644
--- a/MediaBrowser.Model/LiveTv/ChannelInfoDto.cs
+++ b/MediaBrowser.Model/LiveTv/ChannelInfoDto.cs
@@ -1,6 +1,5 @@
using MediaBrowser.Model.Dto;
using MediaBrowser.Model.Entities;
-using MediaBrowser.Model.Extensions;
using MediaBrowser.Model.Library;
using System.Collections.Generic;
using System.ComponentModel;
@@ -13,7 +12,7 @@ namespace MediaBrowser.Model.LiveTv
/// Class ChannelInfoDto
/// </summary>
[DebuggerDisplay("Name = {Name}, Number = {Number}")]
- public class ChannelInfoDto : IHasPropertyChangedEvent, IItemDto, IHasServerId
+ public class ChannelInfoDto : IItemDto, IHasServerId
{
/// <summary>
/// Gets or sets the name.
@@ -120,7 +119,5 @@ namespace MediaBrowser.Model.LiveTv
ImageTags = new Dictionary<ImageType, string>();
MediaSources = new List<MediaSourceInfo>();
}
-
- public event PropertyChangedEventHandler PropertyChanged;
}
}
diff --git a/MediaBrowser.Model/LiveTv/LiveTvOptions.cs b/MediaBrowser.Model/LiveTv/LiveTvOptions.cs
index 71f87ac3a..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
{
@@ -7,8 +9,12 @@ namespace MediaBrowser.Model.LiveTv
public int? GuideDays { get; set; }
public bool EnableMovieProviders { get; set; }
public string RecordingPath { get; set; }
+ public string MovieRecordingPath { get; set; }
+ public string SeriesRecordingPath { get; set; }
public bool EnableAutoOrganize { get; set; }
public bool EnableRecordingEncoding { get; set; }
+ public bool EnableRecordingSubfolders { get; set; }
+ public bool EnableOriginalAudioWithEncodedRecordings { get; set; }
public List<TunerHostInfo> TunerHosts { get; set; }
public List<ListingsProviderInfo> ListingProviders { get; set; }
@@ -16,11 +22,15 @@ namespace MediaBrowser.Model.LiveTv
public int PrePaddingSeconds { get; set; }
public int PostPaddingSeconds { get; set; }
+ public string[] MediaLocationsCreated { get; set; }
+
public LiveTvOptions()
{
EnableMovieProviders = true;
+ EnableRecordingSubfolders = true;
TunerHosts = new List<TunerHostInfo>();
ListingProviders = new List<ListingsProviderInfo>();
+ MediaLocationsCreated = new string[] { };
}
}
@@ -31,18 +41,24 @@ namespace MediaBrowser.Model.LiveTv
public string Type { get; set; }
public string DeviceId { get; set; }
public bool ImportFavoritesOnly { get; set; }
+ public bool AllowHWTranscoding { get; set; }
public bool IsEnabled { get; set; }
public string M3UUrl { get; set; }
public string InfoUrl { get; set; }
public string FriendlyName { get; set; }
public int Tuners { get; set; }
public string DiseqC { get; set; }
+ public string SourceA { get; set; }
+ public string SourceB { get; set; }
+ public string SourceC { get; set; }
+ public string SourceD { get; set; }
public int DataVersion { get; set; }
public TunerHostInfo()
{
IsEnabled = true;
+ AllowHWTranscoding = true;
}
}
@@ -59,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;
}
}
-} \ No newline at end of file
+}
diff --git a/MediaBrowser.Model/LiveTv/ProgramQuery.cs b/MediaBrowser.Model/LiveTv/ProgramQuery.cs
index 7a877e356..0141191c1 100644
--- a/MediaBrowser.Model/LiveTv/ProgramQuery.cs
+++ b/MediaBrowser.Model/LiveTv/ProgramQuery.cs
@@ -14,8 +14,11 @@ namespace MediaBrowser.Model.LiveTv
ChannelIds = new string[] { };
SortBy = new string[] { };
Genres = new string[] { };
+ EnableTotalRecordCount = true;
}
+ public bool EnableTotalRecordCount { get; set; }
+
/// <summary>
/// Fields to return within the items, in addition to basic information
/// </summary>
diff --git a/MediaBrowser.Model/LiveTv/RecommendedProgramQuery.cs b/MediaBrowser.Model/LiveTv/RecommendedProgramQuery.cs
index e83a8fda6..0e6d081a1 100644
--- a/MediaBrowser.Model/LiveTv/RecommendedProgramQuery.cs
+++ b/MediaBrowser.Model/LiveTv/RecommendedProgramQuery.cs
@@ -13,7 +13,14 @@ 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 RecommendedProgramQuery()
+ {
+ EnableTotalRecordCount = true;
+ }
+
/// <summary>
/// Gets or sets the user identifier.
/// </summary>
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/TimerInfoDto.cs b/MediaBrowser.Model/LiveTv/TimerInfoDto.cs
index c33535a3d..a95678fae 100644
--- a/MediaBrowser.Model/LiveTv/TimerInfoDto.cs
+++ b/MediaBrowser.Model/LiveTv/TimerInfoDto.cs
@@ -4,6 +4,11 @@ namespace MediaBrowser.Model.LiveTv
{
public class TimerInfoDto : BaseTimerInfoDto
{
+ public TimerInfoDto()
+ {
+ Type = "Timer";
+ }
+
/// <summary>
/// Gets or sets the status.
/// </summary>
@@ -22,6 +27,8 @@ namespace MediaBrowser.Model.LiveTv
/// <value>The external series timer identifier.</value>
public string ExternalSeriesTimerId { get; set; }
+ public string Type { get; set; }
+
/// <summary>
/// Gets or sets the run time ticks.
/// </summary>
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 41952963c..e3c1e52a5 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" />
@@ -120,9 +118,8 @@
<Compile Include="Devices\DeviceInfo.cs" />
<Compile Include="Devices\DevicesOptions.cs" />
<Compile Include="Dlna\EncodingContext.cs" />
- <Compile Include="Dlna\ILocalPlayer.cs" />
+ <Compile Include="Dlna\ITranscoderSupport.cs" />
<Compile Include="Dlna\StreamInfoSorter.cs" />
- <Compile Include="Dlna\NullLocalPlayer.cs" />
<Compile Include="Dlna\PlaybackErrorCode.cs" />
<Compile Include="Dlna\PlaybackException.cs" />
<Compile Include="Dlna\ResolutionConfiguration.cs" />
@@ -229,10 +226,8 @@
<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" />
<Compile Include="Extensions\IntHelper.cs" />
<Compile Include="Extensions\ListHelper.cs" />
<Compile Include="Extensions\StringHelper.cs" />
@@ -287,7 +282,6 @@
<Compile Include="Entities\MetadataFields.cs" />
<Compile Include="Entities\UserDataSaveReason.cs" />
<Compile Include="Entities\Video3DFormat.cs" />
- <Compile Include="Games\GameSystem.cs" />
<Compile Include="IO\IIsoManager.cs" />
<Compile Include="IO\IIsoMount.cs" />
<Compile Include="IO\IIsoMounter.cs" />
@@ -399,6 +393,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/MediaInfo/AudioCodec.cs b/MediaBrowser.Model/MediaInfo/AudioCodec.cs
index 5353f2b3e..93aba2f43 100644
--- a/MediaBrowser.Model/MediaInfo/AudioCodec.cs
+++ b/MediaBrowser.Model/MediaInfo/AudioCodec.cs
@@ -5,5 +5,22 @@
public const string AAC = "aac";
public const string MP3 = "mp3";
public const string AC3 = "ac3";
+
+ public static string GetFriendlyName(string codec)
+ {
+ if (string.IsNullOrEmpty(codec)) return "";
+
+ switch (codec.ToLower())
+ {
+ case "ac3":
+ return "Dolby Digital";
+ case "eac3":
+ return "Dolby Digital+";
+ case "dca":
+ return "DTS";
+ default:
+ return codec.ToUpper();
+ }
+ }
}
} \ No newline at end of file
diff --git a/MediaBrowser.Model/Providers/SubtitleOptions.cs b/MediaBrowser.Model/Providers/SubtitleOptions.cs
index 2b15c0e1f..5587e897f 100644
--- a/MediaBrowser.Model/Providers/SubtitleOptions.cs
+++ b/MediaBrowser.Model/Providers/SubtitleOptions.cs
@@ -12,11 +12,14 @@ namespace MediaBrowser.Model.Providers
public string OpenSubtitlesPasswordHash { get; set; }
public bool IsOpenSubtitleVipAccount { get; set; }
+ public bool RequirePerfectMatch { get; set; }
+
public SubtitleOptions()
{
DownloadLanguages = new string[] { };
SkipIfAudioTrackMatches = true;
+ RequirePerfectMatch = true;
}
}
} \ No newline at end of file
diff --git a/MediaBrowser.Model/Querying/ItemFields.cs b/MediaBrowser.Model/Querying/ItemFields.cs
index 1540f178a..21f87247a 100644
--- a/MediaBrowser.Model/Querying/ItemFields.cs
+++ b/MediaBrowser.Model/Querying/ItemFields.cs
@@ -130,6 +130,8 @@
/// </summary>
Metascore,
+ OriginalTitle,
+
/// <summary>
/// The item overview
/// </summary>
@@ -195,6 +197,8 @@
/// </summary>
SeriesGenres,
+ SeriesPrimaryImage,
+
/// <summary>
/// The series studio
/// </summary>
diff --git a/MediaBrowser.Model/Querying/ItemQuery.cs b/MediaBrowser.Model/Querying/ItemQuery.cs
index 5a88c0d43..11c046452 100644
--- a/MediaBrowser.Model/Querying/ItemQuery.cs
+++ b/MediaBrowser.Model/Querying/ItemQuery.cs
@@ -288,6 +288,8 @@ namespace MediaBrowser.Model.Querying
[Obsolete]
public string Person { get; set; }
+ public bool EnableTotalRecordCount { get; set; }
+
/// <summary>
/// Initializes a new instance of the <see cref="ItemQuery" /> class.
/// </summary>
@@ -306,6 +308,8 @@ namespace MediaBrowser.Model.Querying
VideoTypes = new VideoType[] { };
+ EnableTotalRecordCount = true;
+
Artists = new string[] { };
Studios = new string[] { };
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/Session/SessionInfoDto.cs b/MediaBrowser.Model/Session/SessionInfoDto.cs
index da8ab9b8a..5c3c9a79c 100644
--- a/MediaBrowser.Model/Session/SessionInfoDto.cs
+++ b/MediaBrowser.Model/Session/SessionInfoDto.cs
@@ -1,5 +1,4 @@
using MediaBrowser.Model.Entities;
-using MediaBrowser.Model.Extensions;
using System;
using System.Collections.Generic;
using System.ComponentModel;
@@ -8,7 +7,7 @@ using System.Diagnostics;
namespace MediaBrowser.Model.Session
{
[DebuggerDisplay("Client = {Client}, Username = {UserName}")]
- public class SessionInfoDto : IHasPropertyChangedEvent
+ public class SessionInfoDto
{
/// <summary>
/// Gets or sets the supported commands.
@@ -116,8 +115,6 @@ namespace MediaBrowser.Model.Session
public TranscodingInfo TranscodingInfo { get; set; }
- public event PropertyChangedEventHandler PropertyChanged;
-
public SessionInfoDto()
{
AdditionalUsers = new List<SessionUserInfo>();
diff --git a/MediaBrowser.Model/Sync/SyncJobItem.cs b/MediaBrowser.Model/Sync/SyncJobItem.cs
index 77464be58..1c72ccd52 100644
--- a/MediaBrowser.Model/Sync/SyncJobItem.cs
+++ b/MediaBrowser.Model/Sync/SyncJobItem.cs
@@ -102,6 +102,8 @@ namespace MediaBrowser.Model.Sync
/// <value>The index of the job item.</value>
public int JobItemIndex { get; set; }
+ public long ItemDateModifiedTicks { get; set; }
+
public SyncJobItem()
{
AdditionalFiles = new List<ItemFileInfo>();
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.Model/Updates/PackageVersionInfo.cs b/MediaBrowser.Model/Updates/PackageVersionInfo.cs
index b9bf6e7fe..22404b6f6 100644
--- a/MediaBrowser.Model/Updates/PackageVersionInfo.cs
+++ b/MediaBrowser.Model/Updates/PackageVersionInfo.cs
@@ -87,5 +87,7 @@ namespace MediaBrowser.Model.Updates
/// </summary>
/// <value>The target filename.</value>
public string targetFilename { get; set; }
+
+ public string infoUrl { get; set; }
}
} \ No newline at end of file
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 ff3d5a5b2..2dce13ebc 100644
--- a/MediaBrowser.Providers/BoxSets/MovieDbBoxSetImageProvider.cs
+++ b/MediaBrowser.Providers/BoxSets/MovieDbBoxSetImageProvider.cs
@@ -61,7 +61,7 @@ namespace MediaBrowser.Providers.BoxSets
{
var tmdbSettings = await MovieDbProvider.Current.GetTmdbSettings(cancellationToken).ConfigureAwait(false);
- var tmdbImageUrl = tmdbSettings.images.base_url + "original";
+ var tmdbImageUrl = tmdbSettings.images.secure_base_url + "original";
return GetImages(mainResult, language, tmdbImageUrl);
}
@@ -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/BoxSets/MovieDbBoxSetProvider.cs b/MediaBrowser.Providers/BoxSets/MovieDbBoxSetProvider.cs
index 53a573bd1..ab05c959e 100644
--- a/MediaBrowser.Providers/BoxSets/MovieDbBoxSetProvider.cs
+++ b/MediaBrowser.Providers/BoxSets/MovieDbBoxSetProvider.cs
@@ -23,7 +23,7 @@ namespace MediaBrowser.Providers.BoxSets
{
public class MovieDbBoxSetProvider : IRemoteMetadataProvider<BoxSet, BoxSetInfo>
{
- private const string GetCollectionInfo3 = @"http://api.themoviedb.org/3/collection/{0}?api_key={1}&append_to_response=images";
+ private const string GetCollectionInfo3 = @"https://api.themoviedb.org/3/collection/{0}?api_key={1}&append_to_response=images";
internal static MovieDbBoxSetProvider Current;
@@ -64,7 +64,7 @@ namespace MediaBrowser.Providers.BoxSets
var tmdbSettings = await MovieDbProvider.Current.GetTmdbSettings(cancellationToken).ConfigureAwait(false);
- var tmdbImageUrl = tmdbSettings.images.base_url + "original";
+ var tmdbImageUrl = tmdbSettings.images.secure_base_url + "original";
var result = new RemoteSearchResult
{
@@ -190,7 +190,7 @@ namespace MediaBrowser.Providers.BoxSets
cancellationToken.ThrowIfCancellationRequested();
- if (mainResult != null && string.IsNullOrEmpty(mainResult.overview))
+ if (mainResult != null && string.IsNullOrEmpty(mainResult.name))
{
if (!string.IsNullOrEmpty(language) && !string.Equals(language, "en", StringComparison.OrdinalIgnoreCase))
{
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
new file mode 100644
index 000000000..35c61b5c5
--- /dev/null
+++ b/MediaBrowser.Providers/Folders/CollectionFolderMetadataService.cs
@@ -0,0 +1,28 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using CommonIO;
+using MediaBrowser.Controller.Configuration;
+using MediaBrowser.Controller.Entities;
+using MediaBrowser.Controller.Library;
+using MediaBrowser.Controller.Providers;
+using MediaBrowser.Model.Entities;
+using MediaBrowser.Model.Logging;
+using MediaBrowser.Providers.Manager;
+
+namespace MediaBrowser.Providers.Folders
+{
+ public class CollectionFolderMetadataService : MetadataService<CollectionFolder, ItemLookupInfo>
+ {
+ protected override void MergeData(MetadataResult<CollectionFolder> source, MetadataResult<CollectionFolder> target, List<MetadataFields> lockedFields, bool replaceData, bool mergeMetadataSettings)
+ {
+ ProviderUtils.MergeBaseItemData(source, target, lockedFields, replaceData, mergeMetadataSettings);
+ }
+
+ public CollectionFolderMetadataService(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/DefaultImageProvider.cs b/MediaBrowser.Providers/Folders/DefaultImageProvider.cs
deleted file mode 100644
index ca543163d..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, MetadataStatus status, 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 545b178e2..b8f58307a 100644
--- a/MediaBrowser.Providers/Folders/UserViewMetadataService.cs
+++ b/MediaBrowser.Providers/Folders/UserViewMetadataService.cs
@@ -12,26 +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);
}
- }
- 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)
+ public UserViewMetadataService(IServerConfigurationManager serverConfigurationManager, ILogger logger, IProviderManager providerManager, IFileSystem fileSystem, IUserDataManager userDataManager, ILibraryManager libraryManager) : base(serverConfigurationManager, logger, providerManager, 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);
- }
}
}
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 c1ae43124..9776c4e2f 100644
--- a/MediaBrowser.Providers/Manager/MetadataService.cs
+++ b/MediaBrowser.Providers/Manager/MetadataService.cs
@@ -11,8 +11,7 @@ using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using CommonIO;
-using MediaBrowser.Controller.Entities.Audio;
-using MediaBrowser.Controller.Entities.Movies;
+using MediaBrowser.Model.Providers;
namespace MediaBrowser.Providers.Manager
{
@@ -23,75 +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;
-
- 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 };
- }
-
- return ProviderRepo.GetMetadataStatus(item.Id) ?? new MetadataStatus { ItemId = item.Id };
- }
-
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;
@@ -118,13 +68,22 @@ namespace MediaBrowser.Providers.Manager
Item = itemOfType
};
+ bool hasRefreshedMetadata = true;
+ bool hasRefreshedImages = true;
+ var requiresRefresh = false;
+
// Next run metadata providers
if (refreshOptions.MetadataRefreshMode != MetadataRefreshMode.None)
{
- var providers = GetProviders(item, refreshResult, refreshOptions)
+ // TODO: If this returns true, should we instead just change metadata refresh mode to Full?
+ requiresRefresh = item.RequiresRefresh();
+
+ var providers = GetProviders(item, refreshOptions, requiresRefresh)
.ToList();
- if (providers.Count > 0 || !refreshResult.DateLastMetadataRefresh.HasValue)
+ var dateLastRefresh = item.DateLastRefreshed;
+
+ if (providers.Count > 0 || dateLastRefresh == default(DateTime))
{
if (item.BeforeMetadataRefresh())
{
@@ -136,6 +95,11 @@ namespace MediaBrowser.Providers.Manager
{
var id = itemOfType.GetLookupInfo();
+ if (refreshOptions.SearchResult != null)
+ {
+ ApplySearchResult(id, refreshOptions.SearchResult);
+ }
+
//await FindIdentities(id, cancellationToken).ConfigureAwait(false);
id.IsAutomated = refreshOptions.IsAutomated;
@@ -144,11 +108,11 @@ namespace MediaBrowser.Providers.Manager
updateType = updateType | result.UpdateType;
if (result.Failures == 0)
{
- refreshResult.SetDateLastMetadataRefresh(DateTime.UtcNow);
+ hasRefreshedMetadata = true;
}
else
{
- refreshResult.SetDateLastMetadataRefresh(null);
+ hasRefreshedMetadata = false;
}
}
}
@@ -156,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)
{
@@ -165,22 +129,38 @@ namespace MediaBrowser.Providers.Manager
updateType = updateType | result.UpdateType;
if (result.Failures == 0)
{
- refreshResult.SetDateLastImagesRefresh(DateTime.UtcNow);
+ hasRefreshedImages = true;
}
else
{
- refreshResult.SetDateLastImagesRefresh(null);
+ hasRefreshedImages = false;
}
}
}
var isFirstRefresh = GetLastRefreshDate(item) == default(DateTime);
- var beforeSaveResult = await BeforeSave(itemOfType, isFirstRefresh || refreshOptions.ReplaceAllMetadata || refreshOptions.MetadataRefreshMode == MetadataRefreshMode.FullRefresh, updateType).ConfigureAwait(false);
+ var beforeSaveResult = await BeforeSave(itemOfType, isFirstRefresh || refreshOptions.ReplaceAllMetadata || refreshOptions.MetadataRefreshMode == MetadataRefreshMode.FullRefresh || requiresRefresh, updateType).ConfigureAwait(false);
updateType = updateType | beforeSaveResult;
+ if (item.LocationType == LocationType.FileSystem)
+ {
+ var file = refreshOptions.DirectoryService.GetFile(item.Path);
+ if (file != null)
+ {
+ var fileLastWriteTime = file.LastWriteTimeUtc;
+ if (item.EnableForceSaveOnDateModifiedChange && fileLastWriteTime != item.DateModified)
+ {
+ Logger.Debug("Date modified for {0}. Old date {1} new date {2} Id {3}", item.Path, item.DateModified, fileLastWriteTime, item.Id);
+ requiresRefresh = true;
+ }
+
+ item.DateModified = fileLastWriteTime;
+ }
+ }
+
// Save if changes were made, or it's never been saved before
- if (refreshOptions.ForceSave || updateType > ItemUpdateType.None || isFirstRefresh || refreshOptions.ReplaceAllMetadata)
+ if (refreshOptions.ForceSave || updateType > ItemUpdateType.None || isFirstRefresh || refreshOptions.ReplaceAllMetadata || requiresRefresh)
{
// If any of these properties are set then make sure the updateType is not None, just to force everything to save
if (refreshOptions.ForceSave || refreshOptions.ReplaceAllMetadata)
@@ -188,65 +168,34 @@ namespace MediaBrowser.Providers.Manager
updateType = updateType | ItemUpdateType.MetadataDownload;
}
- if (refreshOptions.MetadataRefreshMode >= MetadataRefreshMode.Default && refreshOptions.ImageRefreshMode >= ImageRefreshMode.Default)
+ if (hasRefreshedMetadata && hasRefreshedImages)
{
item.DateLastRefreshed = DateTime.UtcNow;
}
+ else
+ {
+ item.DateLastRefreshed = default(DateTime);
+ }
// Save to database
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;
}
- private async Task FindIdentities(TIdType id, CancellationToken cancellationToken)
+ private void ApplySearchResult(ItemLookupInfo lookupInfo, RemoteSearchResult result)
{
- try
- {
- await ItemIdentifier<TIdType>.FindIdentities(id, ProviderManager, cancellationToken).ConfigureAwait(false);
- }
- catch (Exception ex)
- {
- Logger.ErrorException("Error in FindIdentities", ex);
- }
+ lookupInfo.ProviderIds = result.ProviderIds;
+ lookupInfo.Name = result.Name;
+ lookupInfo.Year = result.ProductionYear;
}
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 BoxSet || (item is IItemByName && !(item is MusicArtist)))
- {
- return true;
- }
-
- return false;
+ return item.DateLastRefreshed;
}
protected async Task SaveItem(MetadataResult<TItemType> result, ItemUpdateType reason, CancellationToken cancellationToken)
@@ -326,7 +275,6 @@ namespace MediaBrowser.Providers.Manager
return _cachedTask;
}
- private readonly Task<ItemUpdateType> _cachedResult = Task.FromResult(ItemUpdateType.None);
/// <summary>
/// Befores the save.
/// </summary>
@@ -334,25 +282,75 @@ namespace MediaBrowser.Providers.Manager
/// <param name="isFullRefresh">if set to <c>true</c> [is full refresh].</param>
/// <param name="currentUpdateType">Type of the current update.</param>
/// <returns>ItemUpdateType.</returns>
- protected virtual Task<ItemUpdateType> BeforeSave(TItemType item, bool isFullRefresh, ItemUpdateType currentUpdateType)
+ protected virtual async Task<ItemUpdateType> BeforeSave(TItemType item, bool isFullRefresh, ItemUpdateType currentUpdateType)
{
- return _cachedResult;
+ var updateType = ItemUpdateType.None;
+
+ updateType |= SaveCumulativeRunTimeTicks(item, isFullRefresh, currentUpdateType);
+ updateType |= SaveDateLastMediaAdded(item, isFullRefresh, currentUpdateType);
+
+ return updateType;
+ }
+
+ private ItemUpdateType SaveCumulativeRunTimeTicks(TItemType item, bool isFullRefresh, ItemUpdateType currentUpdateType)
+ {
+ var updateType = ItemUpdateType.None;
+
+ if (isFullRefresh || currentUpdateType > ItemUpdateType.None)
+ {
+ var folder = item as Folder;
+ if (folder != null && folder.SupportsCumulativeRunTimeTicks)
+ {
+ var items = folder.GetRecursiveChildren(i => !i.IsFolder).ToList();
+ var ticks = items.Select(i => i.RunTimeTicks ?? 0).Sum();
+
+ if (!folder.RunTimeTicks.HasValue || folder.RunTimeTicks.Value != ticks)
+ {
+ folder.RunTimeTicks = ticks;
+ updateType = ItemUpdateType.MetadataEdit;
+ }
+ }
+ }
+
+ return updateType;
+ }
+
+ private ItemUpdateType SaveDateLastMediaAdded(TItemType item, bool isFullRefresh, ItemUpdateType currentUpdateType)
+ {
+ var updateType = ItemUpdateType.None;
+
+ if (isFullRefresh || currentUpdateType > ItemUpdateType.None)
+ {
+ var folder = item as Folder;
+ if (folder != null && folder.SupportsDateLastMediaAdded)
+ {
+ var items = folder.GetRecursiveChildren(i => !i.IsFolder).Select(i => i.DateCreated).ToList();
+ var date = items.Count == 0 ? (DateTime?)null : items.Max();
+
+ if ((!folder.DateLastMediaAdded.HasValue && date.HasValue) || folder.DateLastMediaAdded != date)
+ {
+ folder.DateLastMediaAdded = date;
+ updateType = ItemUpdateType.MetadataEdit;
+ }
+ }
+ }
+
+ return updateType;
}
/// <summary>
/// Gets the providers.
/// </summary>
- /// <param name="item">The item.</param>
- /// <param name="status">The status.</param>
- /// <param name="options">The options.</param>
/// <returns>IEnumerable{`0}.</returns>
- protected IEnumerable<IMetadataProvider> GetProviders(IHasMetadata item, MetadataStatus status, MetadataRefreshOptions options)
+ protected IEnumerable<IMetadataProvider> GetProviders(IHasMetadata item, MetadataRefreshOptions options, bool requiresRefresh)
{
// Get providers to refresh
var providers = ((ProviderManager)ProviderManager).GetMetadataProviders<TItemType>(item).ToList();
+ var dateLastRefresh = item.DateLastRefreshed;
+
// Run all if either of these flags are true
- var runAllProviders = options.ReplaceAllMetadata || options.MetadataRefreshMode == MetadataRefreshMode.FullRefresh || !status.DateLastMetadataRefresh.HasValue;
+ var runAllProviders = options.ReplaceAllMetadata || options.MetadataRefreshMode == MetadataRefreshMode.FullRefresh || dateLastRefresh == default(DateTime) || requiresRefresh;
if (!runAllProviders)
{
@@ -362,16 +360,16 @@ namespace MediaBrowser.Providers.Manager
var providersWithChanges = providers
.Where(i =>
{
- var hasChangeMonitor = i as IHasChangeMonitor;
- if (hasChangeMonitor != null)
+ var hasFileChangeMonitor = i as IHasItemChangeMonitor;
+ if (hasFileChangeMonitor != null)
{
- return HasChanged(item, hasChangeMonitor, currentItem.DateLastSaved, options.DirectoryService);
+ return HasChanged(item, hasFileChangeMonitor, options.DirectoryService);
}
- var hasFileChangeMonitor = i as IHasItemChangeMonitor;
- if (hasFileChangeMonitor != null)
+ var hasChangeMonitor = i as IHasChangeMonitor;
+ if (hasChangeMonitor != null)
{
- return HasChanged(item, hasFileChangeMonitor, status, options.DirectoryService);
+ return HasChanged(item, hasChangeMonitor, currentItem.DateLastSaved, options.DirectoryService);
}
return false;
@@ -411,29 +409,25 @@ 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 = item.DateLastRefreshed;
+
// Run all if either of these flags are true
- var runAllProviders = options.ImageRefreshMode == ImageRefreshMode.FullRefresh || !status.DateLastImagesRefresh.HasValue;
+ var runAllProviders = options.ImageRefreshMode == ImageRefreshMode.FullRefresh || dateLastImageRefresh == default(DateTime);
if (!runAllProviders)
{
providers = providers
.Where(i =>
{
- var hasChangeMonitor = i as IHasChangeMonitor;
- if (hasChangeMonitor != null)
- {
- return HasChanged(item, hasChangeMonitor, status.DateLastImagesRefresh.Value, options.DirectoryService);
- }
-
var hasFileChangeMonitor = i as IHasItemChangeMonitor;
if (hasFileChangeMonitor != null)
{
- return HasChanged(item, hasFileChangeMonitor, status, options.DirectoryService);
+ return HasChanged(item, hasFileChangeMonitor, options.DirectoryService);
}
return false;
@@ -541,12 +535,6 @@ namespace MediaBrowser.Providers.Manager
// If a local provider fails, consider that a failure
refreshResult.ErrorMessage = ex.Message;
-
- if (options.MetadataRefreshMode != MetadataRefreshMode.FullRefresh)
- {
- // If the local provider fails don't continue with remote providers because the user's saved metadata could be lost
- return refreshResult;
- }
}
}
@@ -725,11 +713,11 @@ namespace MediaBrowser.Providers.Manager
}
}
- private bool HasChanged(IHasMetadata item, IHasItemChangeMonitor changeMonitor, MetadataStatus status, IDirectoryService directoryService)
+ private bool HasChanged(IHasMetadata item, IHasItemChangeMonitor changeMonitor, IDirectoryService directoryService)
{
try
{
- var hasChanged = changeMonitor.HasChanged(item, status, directoryService);
+ var hasChanged = changeMonitor.HasChanged(item, directoryService);
//if (hasChanged)
//{
diff --git a/MediaBrowser.Providers/Manager/ProviderManager.cs b/MediaBrowser.Providers/Manager/ProviderManager.cs
index 06db8a237..0f0745d1b 100644
--- a/MediaBrowser.Providers/Manager/ProviderManager.cs
+++ b/MediaBrowser.Providers/Manager/ProviderManager.cs
@@ -20,6 +20,7 @@ using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using CommonIO;
+using MediaBrowser.Model.Serialization;
namespace MediaBrowser.Providers.Manager
{
@@ -54,12 +55,11 @@ 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;
private readonly IServerApplicationPaths _appPaths;
+ private readonly IJsonSerializer _json;
private IExternalId[] _externalIds;
@@ -73,7 +73,7 @@ namespace MediaBrowser.Providers.Manager
/// <param name="libraryMonitor">The directory watchers.</param>
/// <param name="logManager">The log manager.</param>
/// <param name="fileSystem">The file system.</param>
- public ProviderManager(IHttpClient httpClient, IServerConfigurationManager configurationManager, ILibraryMonitor libraryMonitor, ILogManager logManager, IFileSystem fileSystem, IServerApplicationPaths appPaths, Func<ILibraryManager> libraryManagerFactory)
+ public ProviderManager(IHttpClient httpClient, IServerConfigurationManager configurationManager, ILibraryMonitor libraryMonitor, ILogManager logManager, IFileSystem fileSystem, IServerApplicationPaths appPaths, Func<ILibraryManager> libraryManagerFactory, IJsonSerializer json)
{
_logger = logManager.GetLogger("ProviderManager");
_httpClient = httpClient;
@@ -82,6 +82,7 @@ namespace MediaBrowser.Providers.Manager
_fileSystem = fileSystem;
_appPaths = appPaths;
_libraryManagerFactory = libraryManagerFactory;
+ _json = json;
}
/// <summary>
@@ -89,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();
@@ -298,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);
@@ -730,8 +714,6 @@ namespace MediaBrowser.Providers.Manager
where TItemType : BaseItem, new()
where TLookupType : ItemLookupInfo
{
- const int maxResults = 10;
-
// Give it a dummy path just so that it looks like a file system item
var dummy = new TItemType
{
@@ -761,8 +743,6 @@ namespace MediaBrowser.Providers.Manager
}
var resultList = new List<RemoteSearchResult>();
- var foundProviderIds = new Dictionary<Tuple<string, string>, RemoteSearchResult>();
- var foundTitleYearStrings = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
foreach (var provider in providers)
{
@@ -772,31 +752,26 @@ namespace MediaBrowser.Providers.Manager
foreach (var result in results)
{
- var bFound = false;
+ var existingMatch = resultList.FirstOrDefault(i => i.ProviderIds.Any(p => string.Equals(result.GetProviderId(p.Key), p.Value, StringComparison.OrdinalIgnoreCase)));
- // This check prevents duplicate search results by comparing provider ids
- foreach (var providerId in result.ProviderIds)
+ if (existingMatch == null)
{
- var idTuple = new Tuple<string, string>(providerId.Key.ToLower(), providerId.Value.ToLower());
-
- if (!foundProviderIds.ContainsKey(idTuple))
- {
- foundProviderIds.Add(idTuple, result);
- }
- else
+ resultList.Add(result);
+ }
+ else
+ {
+ foreach (var providerId in result.ProviderIds)
{
- bFound = true;
- var existingResult = foundProviderIds[idTuple];
- if (string.IsNullOrEmpty(existingResult.ImageUrl) && !string.IsNullOrEmpty(result.ImageUrl))
+ if (!existingMatch.ProviderIds.ContainsKey(providerId.Key))
{
- existingResult.ImageUrl = result.ImageUrl;
+ existingMatch.ProviderIds.Add(providerId.Key, providerId.Value);
}
}
- }
- if (!bFound && resultList.Count < maxResults)
- {
- resultList.Add(result);
+ if (string.IsNullOrWhiteSpace(existingMatch.ImageUrl))
+ {
+ existingMatch.ImageUrl = result.ImageUrl;
+ }
}
}
}
@@ -806,6 +781,8 @@ namespace MediaBrowser.Providers.Manager
}
}
+ //_logger.Debug("Returning search results {0}", _json.SerializeToString(resultList));
+
return resultList;
}
@@ -853,7 +830,7 @@ namespace MediaBrowser.Providers.Manager
});
}
- public IEnumerable<ExternalUrl> GetExternalUrls(IHasProviderIds item)
+ public IEnumerable<ExternalUrl> GetExternalUrls(BaseItem item)
{
return GetExternalIds(item)
.Select(i =>
@@ -876,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 20b2ac6cd..5f23cf69c 100644
--- a/MediaBrowser.Providers/Manager/ProviderUtils.cs
+++ b/MediaBrowser.Providers/Manager/ProviderUtils.cs
@@ -10,9 +10,9 @@ namespace MediaBrowser.Providers.Manager
public static class ProviderUtils
{
public static void MergeBaseItemData<T>(MetadataResult<T> sourceResult,
- MetadataResult<T> targetResult,
- List<MetadataFields> lockedFields,
- bool replaceData,
+ MetadataResult<T> targetResult,
+ List<MetadataFields> lockedFields,
+ bool replaceData,
bool mergeMetadataSettings)
where T : BaseItem
{
@@ -40,6 +40,15 @@ namespace MediaBrowser.Providers.Manager
}
}
+ if (replaceData || string.IsNullOrEmpty(target.OriginalTitle))
+ {
+ // Safeguard against incoming data having an emtpy name
+ if (!string.IsNullOrWhiteSpace(source.OriginalTitle))
+ {
+ target.OriginalTitle = source.OriginalTitle;
+ }
+ }
+
if (replaceData || !target.CommunityRating.HasValue)
{
target.CommunityRating = source.CommunityRating;
@@ -89,7 +98,7 @@ namespace MediaBrowser.Providers.Manager
{
target.CustomRating = source.CustomRating;
}
-
+
if (!lockedFields.Contains(MetadataFields.Overview))
{
if (replaceData || string.IsNullOrEmpty(target.Overview))
@@ -107,7 +116,7 @@ namespace MediaBrowser.Providers.Manager
{
if (replaceData || targetResult.People == null || targetResult.People.Count == 0)
{
- targetResult.People = sourceResult.People ?? new List<PersonInfo>();
+ targetResult.People = sourceResult.People;
}
}
@@ -142,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;
}
}
@@ -238,7 +235,7 @@ namespace MediaBrowser.Providers.Manager
targetHasDisplayOrder.DisplayOrder = sourceHasDisplayOrder.DisplayOrder;
}
}
-
+
private static void MergeShortOverview(BaseItem source, BaseItem target, List<MetadataFields> lockedFields, bool replaceData)
{
var sourceHasShortOverview = source as IHasShortOverview;
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 8927e00aa..8b3d1fb7e 100644
--- a/MediaBrowser.Providers/MediaBrowser.Providers.csproj
+++ b/MediaBrowser.Providers/MediaBrowser.Providers.csproj
@@ -88,7 +88,7 @@
<Compile Include="BoxSets\MovieDbBoxSetProvider.cs" />
<Compile Include="Channels\ChannelMetadataService.cs" />
<Compile Include="Chapters\ChapterManager.cs" />
- <Compile Include="Folders\DefaultImageProvider.cs" />
+ <Compile Include="Folders\CollectionFolderMetadataService.cs" />
<Compile Include="Folders\FolderMetadataService.cs" />
<Compile Include="Folders\UserViewMetadataService.cs" />
<Compile Include="GameGenres\GameGenreMetadataService.cs" />
@@ -103,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" />
@@ -117,7 +116,6 @@
<Compile Include="Movies\MovieDbSearch.cs" />
<Compile Include="Movies\MovieMetadataService.cs" />
<Compile Include="Movies\TmdbSettings.cs" />
- <Compile Include="MusicGenres\MusicGenreImageProvider.cs" />
<Compile Include="GameGenres\GameGenreImageProvider.cs" />
<Compile Include="Genres\GenreImageProvider.cs" />
<Compile Include="ImagesByName\ImageUtils.cs" />
@@ -144,10 +142,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" />
@@ -167,7 +162,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" />
@@ -181,7 +175,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/MediaInfo/AudioImageProvider.cs b/MediaBrowser.Providers/MediaInfo/AudioImageProvider.cs
index af610520f..6c7918988 100644
--- a/MediaBrowser.Providers/MediaInfo/AudioImageProvider.cs
+++ b/MediaBrowser.Providers/MediaInfo/AudioImageProvider.cs
@@ -78,16 +78,22 @@ namespace MediaBrowser.Providers.MediaInfo
_fileSystem.CreateDirectory(Path.GetDirectoryName(path));
var imageStream = imageStreams.FirstOrDefault(i => (i.Comment ?? string.Empty).IndexOf("front", StringComparison.OrdinalIgnoreCase) != -1) ??
- imageStreams.FirstOrDefault(i => (i.Comment ?? string.Empty).IndexOf("cover", StringComparison.OrdinalIgnoreCase) != -1);
+ imageStreams.FirstOrDefault(i => (i.Comment ?? string.Empty).IndexOf("cover", StringComparison.OrdinalIgnoreCase) != -1) ??
+ imageStreams.FirstOrDefault();
var imageStreamIndex = imageStream == null ? (int?)null : imageStream.Index;
- using (var stream = await _mediaEncoder.ExtractAudioImage(item.Path, imageStreamIndex, cancellationToken).ConfigureAwait(false))
+ var tempFile = await _mediaEncoder.ExtractAudioImage(item.Path, imageStreamIndex, cancellationToken).ConfigureAwait(false);
+
+ File.Copy(tempFile, path, true);
+
+ try
{
- using (var fileStream = _fileSystem.GetFileStream(path, FileMode.Create, FileAccess.Write, FileShare.Read, true))
- {
- await stream.CopyToAsync(fileStream).ConfigureAwait(false);
- }
+ File.Delete(tempFile);
+ }
+ catch
+ {
+
}
}
}
@@ -159,14 +165,12 @@ namespace MediaBrowser.Providers.MediaInfo
return item.LocationType == LocationType.FileSystem && audio != null && !audio.IsArchive;
}
- public bool HasChanged(IHasMetadata item, MetadataStatus status, IDirectoryService directoryService)
+ public bool HasChanged(IHasMetadata item, IDirectoryService directoryService)
{
- if (status.ItemDateModified.HasValue)
+ var file = directoryService.GetFile(item.Path);
+ if (file != null && file.LastWriteTimeUtc != item.DateModified)
{
- if (status.ItemDateModified.Value != item.DateModified)
- {
- return true;
- }
+ return true;
}
return false;
diff --git a/MediaBrowser.Providers/MediaInfo/FFProbeProvider.cs b/MediaBrowser.Providers/MediaInfo/FFProbeProvider.cs
index a2d15d863..11280cff2 100644
--- a/MediaBrowser.Providers/MediaInfo/FFProbeProvider.cs
+++ b/MediaBrowser.Providers/MediaInfo/FFProbeProvider.cs
@@ -30,6 +30,7 @@ namespace MediaBrowser.Providers.MediaInfo
ICustomMetadataProvider<Movie>,
ICustomMetadataProvider<LiveTvVideoRecording>,
ICustomMetadataProvider<LiveTvAudioRecording>,
+ ICustomMetadataProvider<Trailer>,
ICustomMetadataProvider<Video>,
ICustomMetadataProvider<Audio>,
IHasItemChangeMonitor,
@@ -77,6 +78,11 @@ namespace MediaBrowser.Providers.MediaInfo
return FetchVideoInfo(item, options, cancellationToken);
}
+ public Task<ItemUpdateType> FetchAsync(Trailer item, MetadataRefreshOptions options, CancellationToken cancellationToken)
+ {
+ return FetchVideoInfo(item, options, cancellationToken);
+ }
+
public Task<ItemUpdateType> FetchAsync(Video item, MetadataRefreshOptions options, CancellationToken cancellationToken)
{
return FetchVideoInfo(item, options, cancellationToken);
@@ -163,14 +169,12 @@ namespace MediaBrowser.Providers.MediaInfo
return prober.Probe(item, cancellationToken);
}
- public bool HasChanged(IHasMetadata item, MetadataStatus status, IDirectoryService directoryService)
+ public bool HasChanged(IHasMetadata item, IDirectoryService directoryService)
{
- if (status.ItemDateModified.HasValue)
+ var file = directoryService.GetFile(item.Path);
+ if (file != null && file.LastWriteTimeUtc != item.DateModified)
{
- if (status.ItemDateModified.Value != item.DateModified)
- {
- return true;
- }
+ return true;
}
if (item.SupportsLocalMetadata)
diff --git a/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs b/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs
index 24bf8b497..e1ab61cbb 100644
--- a/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs
+++ b/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs
@@ -444,7 +444,11 @@ namespace MediaBrowser.Providers.MediaInfo
{
if (string.IsNullOrWhiteSpace(video.Name) || string.Equals(video.Name, Path.GetFileNameWithoutExtension(video.Path), StringComparison.OrdinalIgnoreCase))
{
- video.Name = data.Name;
+ // Don't use the embedded name for extras because it will often be the same name as the movie
+ if (!video.ExtraType.HasValue && !video.IsOwnedItem)
+ {
+ video.Name = data.Name;
+ }
}
}
@@ -532,6 +536,7 @@ namespace MediaBrowser.Providers.MediaInfo
currentStreams.Concat(externalSubtitleStreams).ToList(),
subtitleOptions.SkipIfEmbeddedSubtitlesPresent,
subtitleOptions.SkipIfAudioTrackMatches,
+ subtitleOptions.RequirePerfectMatch,
subtitleOptions.DownloadLanguages,
cancellationToken).ConfigureAwait(false);
diff --git a/MediaBrowser.Providers/MediaInfo/SubtitleDownloader.cs b/MediaBrowser.Providers/MediaInfo/SubtitleDownloader.cs
index d82289032..9ae8413d1 100644
--- a/MediaBrowser.Providers/MediaInfo/SubtitleDownloader.cs
+++ b/MediaBrowser.Providers/MediaInfo/SubtitleDownloader.cs
@@ -28,6 +28,7 @@ namespace MediaBrowser.Providers.MediaInfo
List<MediaStream> mediaStreams,
bool skipIfEmbeddedSubtitlesPresent,
bool skipIfAudioTrackMatches,
+ bool requirePerfectMatch,
IEnumerable<string> languages,
CancellationToken cancellationToken)
{
@@ -59,7 +60,7 @@ namespace MediaBrowser.Providers.MediaInfo
{
try
{
- var downloaded = await DownloadSubtitles(video, mediaStreams, skipIfEmbeddedSubtitlesPresent, skipIfAudioTrackMatches, lang, mediaType, cancellationToken)
+ var downloaded = await DownloadSubtitles(video, mediaStreams, skipIfEmbeddedSubtitlesPresent, skipIfAudioTrackMatches, requirePerfectMatch, lang, mediaType, cancellationToken)
.ConfigureAwait(false);
if (downloaded)
@@ -80,6 +81,7 @@ namespace MediaBrowser.Providers.MediaInfo
List<MediaStream> mediaStreams,
bool skipIfEmbeddedSubtitlesPresent,
bool skipIfAudioTrackMatches,
+ bool requirePerfectMatch,
string language,
VideoContentType mediaType,
CancellationToken cancellationToken)
@@ -125,7 +127,9 @@ namespace MediaBrowser.Providers.MediaInfo
ProviderIds = video.ProviderIds,
// Stop as soon as we find something
- SearchAllProviders = false
+ SearchAllProviders = false,
+
+ IsPerfectMatch = requirePerfectMatch
};
var episode = video as Episode;
diff --git a/MediaBrowser.Providers/MediaInfo/SubtitleResolver.cs b/MediaBrowser.Providers/MediaInfo/SubtitleResolver.cs
index d2c0feabe..f64b7b792 100644
--- a/MediaBrowser.Providers/MediaInfo/SubtitleResolver.cs
+++ b/MediaBrowser.Providers/MediaInfo/SubtitleResolver.cs
@@ -72,7 +72,10 @@ namespace MediaBrowser.Providers.MediaInfo
// Try to translate to three character code
// Be flexible and check against both the full and three character versions
var culture = _localization.GetCultures()
- .FirstOrDefault(i => string.Equals(i.DisplayName, language, StringComparison.OrdinalIgnoreCase) || string.Equals(i.Name, language, StringComparison.OrdinalIgnoreCase) || string.Equals(i.ThreeLetterISOLanguageName, language, StringComparison.OrdinalIgnoreCase) || string.Equals(i.TwoLetterISOLanguageName, language, StringComparison.OrdinalIgnoreCase));
+ .FirstOrDefault(i => string.Equals(i.DisplayName, language, StringComparison.OrdinalIgnoreCase) ||
+ string.Equals(i.Name, language, StringComparison.OrdinalIgnoreCase) ||
+ string.Equals(i.ThreeLetterISOLanguageName, language, StringComparison.OrdinalIgnoreCase) ||
+ string.Equals(i.TwoLetterISOLanguageName, language, StringComparison.OrdinalIgnoreCase));
if (culture != null)
{
@@ -99,10 +102,12 @@ namespace MediaBrowser.Providers.MediaInfo
private string NormalizeFilenameForSubtitleComparison(string filename)
{
// Try to account for sloppy file naming
- filename = filename.Replace("-", string.Empty);
filename = filename.Replace("_", string.Empty);
filename = filename.Replace(" ", string.Empty);
+ // can't normalize this due to languages such as pt-br
+ //filename = filename.Replace("-", string.Empty);
+
//filename = filename.Replace(".", string.Empty);
return filename;
diff --git a/MediaBrowser.Providers/MediaInfo/SubtitleScheduledTask.cs b/MediaBrowser.Providers/MediaInfo/SubtitleScheduledTask.cs
index b8b17cefe..79da291b7 100644
--- a/MediaBrowser.Providers/MediaInfo/SubtitleScheduledTask.cs
+++ b/MediaBrowser.Providers/MediaInfo/SubtitleScheduledTask.cs
@@ -116,6 +116,7 @@ namespace MediaBrowser.Providers.MediaInfo
mediaStreams,
options.SkipIfEmbeddedSubtitlesPresent,
options.SkipIfAudioTrackMatches,
+ options.RequirePerfectMatch,
options.DownloadLanguages,
cancellationToken).ConfigureAwait(false);
diff --git a/MediaBrowser.Providers/MediaInfo/VideoImageProvider.cs b/MediaBrowser.Providers/MediaInfo/VideoImageProvider.cs
index f3235a102..5fc363062 100644
--- a/MediaBrowser.Providers/MediaInfo/VideoImageProvider.cs
+++ b/MediaBrowser.Providers/MediaInfo/VideoImageProvider.cs
@@ -10,6 +10,8 @@ using MediaBrowser.Model.Logging;
using MediaBrowser.Model.MediaInfo;
using System;
using System.Collections.Generic;
+using System.IO;
+using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using CommonIO;
@@ -93,26 +95,66 @@ namespace MediaBrowser.Providers.MediaInfo
try
{
- // If we know the duration, grab it from 10% into the video. Otherwise just 10 seconds in.
- // Always use 10 seconds for dvd because our duration could be out of whack
- var imageOffset = item.VideoType != VideoType.Dvd && item.RunTimeTicks.HasValue &&
- item.RunTimeTicks.Value > 0
- ? TimeSpan.FromTicks(Convert.ToInt64(item.RunTimeTicks.Value * .1))
- : TimeSpan.FromSeconds(10);
-
var protocol = item.LocationType == LocationType.Remote
? MediaProtocol.Http
: MediaProtocol.File;
var inputPath = MediaEncoderHelpers.GetInputArgument(_fileSystem, item.Path, protocol, isoMount, item.PlayableStreamFileNames);
- var stream = await _mediaEncoder.ExtractVideoImage(inputPath, protocol, item.Video3DFormat, imageOffset, cancellationToken).ConfigureAwait(false);
+ var mediaStreams =
+ item.GetMediaSources(false)
+ .Take(1)
+ .SelectMany(i => i.MediaStreams)
+ .ToList();
+
+ var imageStreams =
+ mediaStreams
+ .Where(i => i.Type == MediaStreamType.EmbeddedImage)
+ .ToList();
+
+ var imageStream = imageStreams.FirstOrDefault(i => (i.Comment ?? string.Empty).IndexOf("front", StringComparison.OrdinalIgnoreCase) != -1) ??
+ imageStreams.FirstOrDefault(i => (i.Comment ?? string.Empty).IndexOf("cover", StringComparison.OrdinalIgnoreCase) != -1) ??
+ imageStreams.FirstOrDefault();
+
+ string extractedImagePath;
+
+ if (imageStream != null)
+ {
+ // Instead of using the raw stream index, we need to use nth video/embedded image stream
+ var videoIndex = -1;
+ foreach (var mediaStream in mediaStreams)
+ {
+ if (mediaStream.Type == MediaStreamType.Video ||
+ mediaStream.Type == MediaStreamType.EmbeddedImage)
+ {
+ videoIndex++;
+ }
+ if (mediaStream == imageStream)
+ {
+ break;
+ }
+ }
+
+ extractedImagePath = await _mediaEncoder.ExtractVideoImage(inputPath, protocol, videoIndex, cancellationToken).ConfigureAwait(false);
+ }
+ else
+ {
+ // If we know the duration, grab it from 10% into the video. Otherwise just 10 seconds in.
+ // Always use 10 seconds for dvd because our duration could be out of whack
+ var imageOffset = item.VideoType != VideoType.Dvd && item.RunTimeTicks.HasValue &&
+ item.RunTimeTicks.Value > 0
+ ? TimeSpan.FromTicks(Convert.ToInt64(item.RunTimeTicks.Value * .1))
+ : TimeSpan.FromSeconds(10);
+
+ extractedImagePath = await _mediaEncoder.ExtractVideoImage(inputPath, protocol, item.Video3DFormat, imageOffset, cancellationToken).ConfigureAwait(false);
+ }
return new DynamicImageResponse
{
Format = ImageFormat.Jpg,
HasImage = true,
- Stream = stream
+ Path = extractedImagePath,
+ Protocol = MediaProtocol.File
};
}
finally
@@ -151,14 +193,12 @@ namespace MediaBrowser.Providers.MediaInfo
}
}
- public bool HasChanged(IHasMetadata item, MetadataStatus status, IDirectoryService directoryService)
+ public bool HasChanged(IHasMetadata item, IDirectoryService directoryService)
{
- if (status.ItemDateModified.HasValue)
+ var file = directoryService.GetFile(item.Path);
+ if (file != null && file.LastWriteTimeUtc != item.DateModified)
{
- if (status.ItemDateModified.Value != item.DateModified)
- {
- return true;
- }
+ return true;
}
return false;
diff --git a/MediaBrowser.Providers/Movies/FanArtMovieUpdatesPostScanTask.cs b/MediaBrowser.Providers/Movies/FanArtMovieUpdatesPostScanTask.cs
deleted file mode 100644
index d207e6e7c..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 = "http://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 a1dbc1967..18f177932 100644
--- a/MediaBrowser.Providers/Movies/FanartMovieImageProvider.cs
+++ b/MediaBrowser.Providers/Movies/FanartMovieImageProvider.cs
@@ -16,6 +16,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;
@@ -23,7 +24,7 @@ using MediaBrowser.Providers.TV;
namespace MediaBrowser.Providers.Movies
{
- public class FanartMovieImageProvider : IRemoteImageProvider, IHasChangeMonitor, IHasOrder
+ public class FanartMovieImageProvider : IRemoteImageProvider, IHasOrder
{
private readonly CultureInfo _usCulture = new CultureInfo("en-US");
private readonly IServerConfigurationManager _config;
@@ -31,7 +32,7 @@ namespace MediaBrowser.Providers.Movies
private readonly IFileSystem _fileSystem;
private readonly IJsonSerializer _json;
- private const string FanArtBaseUrl = "http://webservice.fanart.tv/v3/movies/{1}?api_key={0}";
+ private const string FanArtBaseUrl = "https://webservice.fanart.tv/v3/movies/{1}?api_key={0}";
// &client_key=52c813aa7b8c8b3bb87f4797532a2f8c
internal static FanartMovieImageProvider Current;
@@ -185,6 +186,7 @@ namespace MediaBrowser.Providers.Movies
PopulateImages(list, obj.moviebackground, ImageType.Backdrop, 1920, 1080);
}
+ private Regex _regex_http = new Regex("^http://");
private void PopulateImages(List<RemoteImageInfo> list, List<Image> images, ImageType type, int width, int height)
{
if (images == null)
@@ -208,7 +210,7 @@ namespace MediaBrowser.Providers.Movies
Width = width,
Height = height,
ProviderName = Name,
- Url = url,
+ Url = _regex_http.Replace(url, "https://", 1),
Language = i.lang
};
@@ -239,33 +241,6 @@ namespace MediaBrowser.Providers.Movies
});
}
- public bool HasChanged(IHasMetadata item, IDirectoryService directoryService, DateTime date)
- {
- 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) > date;
- }
-
- return false;
- }
-
/// <summary>
/// Gets the movie data path.
/// </summary>
diff --git a/MediaBrowser.Providers/Movies/GenericMovieDbInfo.cs b/MediaBrowser.Providers/Movies/GenericMovieDbInfo.cs
index 6c6f6f0eb..1bf4ed6c0 100644
--- a/MediaBrowser.Providers/Movies/GenericMovieDbInfo.cs
+++ b/MediaBrowser.Providers/Movies/GenericMovieDbInfo.cs
@@ -199,7 +199,6 @@ namespace MediaBrowser.Providers.Movies
var ourRelease = releases.FirstOrDefault(c => c.iso_3166_1.Equals(preferredCountryCode, StringComparison.OrdinalIgnoreCase));
var usRelease = releases.FirstOrDefault(c => c.iso_3166_1.Equals("US", StringComparison.OrdinalIgnoreCase));
- var minimunRelease = releases.OrderBy(c => c.release_date).FirstOrDefault();
if (ourRelease != null)
{
@@ -210,10 +209,6 @@ namespace MediaBrowser.Providers.Movies
{
movie.OfficialRating = usRelease.certification;
}
- else if (minimunRelease != null)
- {
- movie.OfficialRating = minimunRelease.iso_3166_1 + "-" + minimunRelease.certification;
- }
}
if (!string.IsNullOrWhiteSpace(movieData.release_date))
@@ -249,7 +244,7 @@ namespace MediaBrowser.Providers.Movies
}
resultItem.ResetPeople();
- var tmdbImageUrl = settings.images.base_url + "original";
+ var tmdbImageUrl = settings.images.secure_base_url + "original";
//Actors, Directors, Writers - all in People
//actors come from cast
@@ -314,11 +309,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 &&
@@ -329,9 +320,8 @@ namespace MediaBrowser.Providers.Movies
{
hasTrailers.RemoteTrailers = movieData.trailers.youtube.Select(i => new MediaUrl
{
- Url = string.Format("http://www.youtube.com/watch?v={0}", i.source),
- Name = i.name,
- VideoSize = string.Equals("hd", i.size, StringComparison.OrdinalIgnoreCase) ? VideoSize.HighDefinition : VideoSize.StandardDefinition
+ Url = string.Format("https://www.youtube.com/watch?v={0}", i.source),
+ Name = i.name
}).ToList();
}
diff --git a/MediaBrowser.Providers/Movies/MovieDbImageProvider.cs b/MediaBrowser.Providers/Movies/MovieDbImageProvider.cs
index e091cddc6..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, IHasChangeMonitor
+ 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)
@@ -72,7 +74,7 @@ namespace MediaBrowser.Providers.Movies
var tmdbSettings = await MovieDbProvider.Current.GetTmdbSettings(cancellationToken).ConfigureAwait(false);
- var tmdbImageUrl = tmdbSettings.images.base_url + "original";
+ var tmdbImageUrl = tmdbSettings.images.secure_base_url + "original";
var supportedImages = GetSupportedImages(item).ToList();
@@ -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, DateTime date)
- {
- return MovieDbProvider.Current.HasChanged(item, date);
- }
}
}
diff --git a/MediaBrowser.Providers/Movies/MovieDbProvider.cs b/MediaBrowser.Providers/Movies/MovieDbProvider.cs
index 51051e41d..27b61225b 100644
--- a/MediaBrowser.Providers/Movies/MovieDbProvider.cs
+++ b/MediaBrowser.Providers/Movies/MovieDbProvider.cs
@@ -78,7 +78,7 @@ namespace MediaBrowser.Providers.Movies
var tmdbSettings = await GetTmdbSettings(cancellationToken).ConfigureAwait(false);
- var tmdbImageUrl = tmdbSettings.images.base_url + "original";
+ var tmdbImageUrl = tmdbSettings.images.secure_base_url + "original";
var remoteResult = new RemoteSearchResult
{
@@ -172,8 +172,8 @@ namespace MediaBrowser.Providers.Movies
}
}
- private const string TmdbConfigUrl = "http://api.themoviedb.org/3/configuration?api_key={0}";
- private const string GetMovieInfo3 = @"http://api.themoviedb.org/3/movie/{0}?api_key={1}&append_to_response=casts,releases,images,keywords,trailers";
+ private const string TmdbConfigUrl = "https://api.themoviedb.org/3/configuration?api_key={0}";
+ private const string GetMovieInfo3 = @"https://api.themoviedb.org/3/movie/{0}?api_key={1}&append_to_response=casts,releases,images,keywords,trailers";
internal static string ApiKey = "f6bd687ffa63cd282b6ff2c6877f2669";
internal static string AcceptHeader = "application/json,image/*";
@@ -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
- // http://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, DateTime date)
- {
- 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) > date;
- }
-
- 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 e8eeab9c5..ab2cd3bed 100644
--- a/MediaBrowser.Providers/Movies/MovieDbSearch.cs
+++ b/MediaBrowser.Providers/Movies/MovieDbSearch.cs
@@ -18,11 +18,11 @@ namespace MediaBrowser.Providers.Movies
public class MovieDbSearch
{
private static readonly CultureInfo EnUs = new CultureInfo("en-US");
- private const string Search3 = @"http://api.themoviedb.org/3/search/{3}?api_key={1}&query={0}&language={2}";
+ private const string Search3 = @"https://api.themoviedb.org/3/search/{3}?api_key={1}&query={0}&language={2}";
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,9 +54,14 @@ 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.base_url + "original";
+ var tmdbImageUrl = tmdbSettings.images.secure_base_url + "original";
if (!string.IsNullOrWhiteSpace(name))
{
@@ -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 336968a84..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, DateTime date)
- {
- return MovieDbProvider.Current.HasChanged(item, date);
- }
-
public int Order
{
get
diff --git a/MediaBrowser.Providers/Movies/MovieExternalIds.cs b/MediaBrowser.Providers/Movies/MovieExternalIds.cs
index 02c330267..a6b7bde6f 100644
--- a/MediaBrowser.Providers/Movies/MovieExternalIds.cs
+++ b/MediaBrowser.Providers/Movies/MovieExternalIds.cs
@@ -21,7 +21,7 @@ namespace MediaBrowser.Providers.Movies
public string UrlFormatString
{
- get { return "http://www.themoviedb.org/movie/{0}"; }
+ get { return "https://www.themoviedb.org/movie/{0}"; }
}
public bool Supports(IHasProviderIds item)
@@ -51,7 +51,7 @@ namespace MediaBrowser.Providers.Movies
public string UrlFormatString
{
- get { return "http://www.themoviedb.org/tv/{0}"; }
+ get { return "https://www.themoviedb.org/tv/{0}"; }
}
public bool Supports(IHasProviderIds item)
@@ -74,7 +74,7 @@ namespace MediaBrowser.Providers.Movies
public string UrlFormatString
{
- get { return "http://www.themoviedb.org/collection/{0}"; }
+ get { return "https://www.themoviedb.org/collection/{0}"; }
}
public bool Supports(IHasProviderIds item)
@@ -97,7 +97,7 @@ namespace MediaBrowser.Providers.Movies
public string UrlFormatString
{
- get { return "http://www.themoviedb.org/person/{0}"; }
+ get { return "https://www.themoviedb.org/person/{0}"; }
}
public bool Supports(IHasProviderIds item)
@@ -120,7 +120,7 @@ namespace MediaBrowser.Providers.Movies
public string UrlFormatString
{
- get { return "http://www.themoviedb.org/collection/{0}"; }
+ get { return "https://www.themoviedb.org/collection/{0}"; }
}
public bool Supports(IHasProviderIds item)
@@ -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 7fa7e0d15..000000000
--- a/MediaBrowser.Providers/Movies/MovieUpdatesPrescanTask.cs
+++ /dev/null
@@ -1,245 +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 = "http://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.RootFolder
- .GetRecursiveChildren(i => i is Movie && !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/Movies/TmdbSettings.cs b/MediaBrowser.Providers/Movies/TmdbSettings.cs
index 59e8f7cef..12bb77afc 100644
--- a/MediaBrowser.Providers/Movies/TmdbSettings.cs
+++ b/MediaBrowser.Providers/Movies/TmdbSettings.cs
@@ -5,7 +5,7 @@ namespace MediaBrowser.Providers.Movies
internal class TmdbImageSettings
{
public List<string> backdrop_sizes { get; set; }
- public string base_url { get; set; }
+ public string secure_base_url { get; set; }
public List<string> poster_sizes { get; set; }
public List<string> profile_sizes { get; set; }
}
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 0c0339e12..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);
@@ -27,10 +23,12 @@ namespace MediaBrowser.Providers.Music
{
if (!item.IsLocked)
{
- var itemFilter = item.GetItemFilter();
-
var taggedItems = item.IsAccessedByName ?
- LibraryManager.RootFolder.GetRecursiveChildren(i => !i.IsFolder && itemFilter(i)).ToList() :
+ item.GetTaggedItems(new Controller.Entities.InternalItemsQuery()
+ {
+ Recursive = true,
+ IsFolder = false
+ }) :
item.GetRecursiveChildren(i => i is IHasArtist && !i.IsFolder).ToList();
if (!item.LockedFields.Contains(MetadataFields.Genres))
@@ -56,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 444046208..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, IHasChangeMonitor, 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, DateTime date)
- {
- 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) > date;
- }
- }
-
- return false;
- }
}
}
diff --git a/MediaBrowser.Providers/Music/FanArtArtistProvider.cs b/MediaBrowser.Providers/Music/FanArtArtistProvider.cs
index b715803ea..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,11 +23,11 @@ using MediaBrowser.Model.Serialization;
namespace MediaBrowser.Providers.Music
{
- public class FanartArtistProvider : IRemoteImageProvider, IHasChangeMonitor, IHasOrder
+ public class FanartArtistProvider : IRemoteImageProvider, IHasOrder
{
internal readonly SemaphoreSlim FanArtResourcePool = new SemaphoreSlim(3, 3);
internal const string ApiKey = "5c6b04c68e904cfed1e6cbc9a9e683d4";
- private const string FanArtBaseUrl = "http://webservice.fanart.tv/v3.1/music/{1}?api_key={0}";
+ private const string FanArtBaseUrl = "https://webservice.fanart.tv/v3.1/music/{1}?api_key={0}";
private readonly CultureInfo _usCulture = new CultureInfo("en-US");
private readonly IServerConfigurationManager _config;
@@ -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, DateTime date)
- {
- 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) > date;
- }
-
- 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 30507b891..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 = "http://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 55dcc99f5..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, DateTime date)
- {
- return MovieDbProvider.Current.HasChanged(item, date);
- }
-
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 e73a98b6f..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("http://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("http://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("http://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", "http://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("http://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("http://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", "http://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("http://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", "http://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 c04f80549..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("http://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("http://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("http://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,12 +62,22 @@ namespace MediaBrowser.Providers.Music
private IEnumerable<RemoteSearchResult> GetResultsFromResponse(XmlDocument doc)
{
- var ns = new XmlNamespaceManager(doc.NameTable);
- ns.AddNamespace("mb", "http://musicbrainz.org/ns/mmd-2.0#");
-
var list = new List<RemoteSearchResult>();
- var nodes = doc.SelectNodes("//mb:artist-list/mb:artist", ns);
+ var docElem = doc.DocumentElement;
+
+ if (docElem == null)
+ {
+ return list;
+ }
+
+ var artistList = docElem.FirstChild;
+ if (artistList == null)
+ {
+ return list;
+ }
+
+ var nodes = artistList.ChildNodes;
if (nodes != null)
{
@@ -79,11 +89,13 @@ namespace MediaBrowser.Providers.Music
string mbzId = node.Attributes["id"].Value;
- var nameNode = node.SelectSingleNode("//mb:name", ns);
-
- if (nameNode != null)
+ foreach (var child in node.ChildNodes.Cast<XmlNode>())
{
- name = nameNode.InnerText;
+ if (string.Equals(child.Name, "name", StringComparison.OrdinalIgnoreCase))
+ {
+ name = child.InnerText;
+ break;
+ }
}
if (!string.IsNullOrWhiteSpace(mbzId) && !string.IsNullOrWhiteSpace(name))
diff --git a/MediaBrowser.Providers/Music/MusicExternalIds.cs b/MediaBrowser.Providers/Music/MusicExternalIds.cs
index bcafdc6f6..814488df1 100644
--- a/MediaBrowser.Providers/Music/MusicExternalIds.cs
+++ b/MediaBrowser.Providers/Music/MusicExternalIds.cs
@@ -18,7 +18,7 @@ namespace MediaBrowser.Providers.Music
public string UrlFormatString
{
- get { return "http://musicbrainz.org/release-group/{0}"; }
+ get { return "https://musicbrainz.org/release-group/{0}"; }
}
public bool Supports(IHasProviderIds item)
@@ -41,7 +41,7 @@ namespace MediaBrowser.Providers.Music
public string UrlFormatString
{
- get { return "http://musicbrainz.org/artist/{0}"; }
+ get { return "https://musicbrainz.org/artist/{0}"; }
}
public bool Supports(IHasProviderIds item)
@@ -64,7 +64,7 @@ namespace MediaBrowser.Providers.Music
public string UrlFormatString
{
- get { return "http://musicbrainz.org/release/{0}"; }
+ get { return "https://musicbrainz.org/release/{0}"; }
}
public bool Supports(IHasProviderIds item)
@@ -87,7 +87,7 @@ namespace MediaBrowser.Providers.Music
public string UrlFormatString
{
- get { return "http://musicbrainz.org/artist/{0}"; }
+ get { return "https://musicbrainz.org/artist/{0}"; }
}
public bool Supports(IHasProviderIds item)
@@ -110,7 +110,7 @@ namespace MediaBrowser.Providers.Music
public string UrlFormatString
{
- get { return "http://musicbrainz.org/artist/{0}"; }
+ get { return "https://musicbrainz.org/artist/{0}"; }
}
public bool Supports(IHasProviderIds item)
@@ -133,7 +133,7 @@ namespace MediaBrowser.Providers.Music
public string UrlFormatString
{
- get { return "http://musicbrainz.org/track/{0}"; }
+ get { return "https://musicbrainz.org/track/{0}"; }
}
public bool Supports(IHasProviderIds item)
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/MusicGenreImageProvider.cs b/MediaBrowser.Providers/MusicGenres/MusicGenreImageProvider.cs
deleted file mode 100644
index 43495aaea..000000000
--- a/MediaBrowser.Providers/MusicGenres/MusicGenreImageProvider.cs
+++ /dev/null
@@ -1,145 +0,0 @@
-using MediaBrowser.Common.Net;
-using MediaBrowser.Controller.Configuration;
-using MediaBrowser.Controller.Entities;
-using MediaBrowser.Controller.Entities.Audio;
-using MediaBrowser.Controller.Providers;
-using MediaBrowser.Model.Entities;
-using MediaBrowser.Model.Providers;
-using MediaBrowser.Providers.Genres;
-using MediaBrowser.Providers.ImagesByName;
-using System.Collections.Generic;
-using System.IO;
-using System.Linq;
-using System.Threading;
-using System.Threading.Tasks;
-using CommonIO;
-
-namespace MediaBrowser.Providers.MusicGenres
-{
- public class MusicGenreImageProvider : IRemoteImageProvider
- {
- private readonly IServerConfigurationManager _config;
- private readonly IHttpClient _httpClient;
- private readonly IFileSystem _fileSystem;
-
- private readonly SemaphoreSlim _listResourcePool = new SemaphoreSlim(1, 1);
-
- public MusicGenreImageProvider(IServerConfigurationManager config, IHttpClient httpClient, IFileSystem fileSystem)
- {
- _config = config;
- _httpClient = httpClient;
- _fileSystem = fileSystem;
- }
-
- public string Name
- {
- get { return ProviderName; }
- }
-
- public static string ProviderName
- {
- get { return "Media Browser Designs"; }
- }
-
- public bool Supports(IHasImages item)
- {
- return item is MusicGenre;
- }
-
- public IEnumerable<ImageType> GetSupportedImages(IHasImages item)
- {
- return new List<ImageType>
- {
- ImageType.Primary,
- ImageType.Thumb
- };
- }
-
- public Task<IEnumerable<RemoteImageInfo>> GetImages(IHasImages item, CancellationToken cancellationToken)
- {
- return GetImages(item, true, true, cancellationToken);
- }
-
- private async Task<IEnumerable<RemoteImageInfo>> GetImages(IHasImages item, bool posters, bool thumbs, CancellationToken cancellationToken)
- {
- var list = new List<RemoteImageInfo>();
-
- if (posters)
- {
- var posterPath = Path.Combine(_config.ApplicationPaths.CachePath, "imagesbyname", "remotemusicgenreposters.txt");
-
- await EnsurePosterList(posterPath, cancellationToken).ConfigureAwait(false);
-
- list.Add(GetImage(item, posterPath, ImageType.Primary, "folder"));
- }
-
- cancellationToken.ThrowIfCancellationRequested();
-
- if (thumbs)
- {
- var thumbsPath = Path.Combine(_config.ApplicationPaths.CachePath, "imagesbyname", "remotemusicgenrethumbs.txt");
-
- await EnsureThumbsList(thumbsPath, cancellationToken).ConfigureAwait(false);
-
- list.Add(GetImage(item, thumbsPath, ImageType.Thumb, "thumb"));
- }
-
- return list.Where(i => i != null);
- }
-
- private RemoteImageInfo GetImage(IHasImages item, string filename, ImageType type, string remoteFilename)
- {
- var list = ImageUtils.GetAvailableImages(filename);
-
- var match = ImageUtils.FindMatch(item, list);
-
- if (!string.IsNullOrEmpty(match))
- {
- var url = GetUrl(match, remoteFilename);
-
- return new RemoteImageInfo
- {
- ProviderName = Name,
- Type = type,
- Url = url
- };
- }
-
- return null;
- }
-
- private string GetUrl(string image, string filename)
- {
- return string.Format("https://raw.github.com/MediaBrowser/MediaBrowser.Resources/master/images/imagesbyname/musicgenres/{0}/{1}.jpg", image, filename);
- }
-
- private Task EnsureThumbsList(string file, CancellationToken cancellationToken)
- {
- const string url = "https://raw.github.com/MediaBrowser/MediaBrowser.Resources/master/images/imagesbyname/musicgenrethumbs.txt";
-
- return ImageUtils.EnsureList(url, file, _httpClient, _fileSystem, _listResourcePool, cancellationToken);
- }
-
- private Task EnsurePosterList(string file, CancellationToken cancellationToken)
- {
- const string url = "https://raw.github.com/MediaBrowser/MediaBrowser.Resources/master/images/imagesbyname/musicgenreposters.txt";
-
- return ImageUtils.EnsureList(url, file, _httpClient, _fileSystem, _listResourcePool, cancellationToken);
- }
-
- public int Order
- {
- get { return 0; }
- }
-
- public Task<HttpResponseInfo> GetImageResponse(string url, CancellationToken cancellationToken)
- {
- return _httpClient.GetResponse(new HttpRequestOptions
- {
- CancellationToken = cancellationToken,
- Url = url,
- ResourcePool = GenreImageProvider.ImageDownloadResourcePool
- });
- }
- }
-}
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 ae563b287..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("http://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 75bec7b65..914b775af 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)
@@ -60,22 +66,18 @@ namespace MediaBrowser.Providers.Omdb
return GetSearchResultsInternal(searchInfo, type, true, cancellationToken);
}
- private async Task<IEnumerable<RemoteSearchResult>> GetSearchResultsInternal(ItemLookupInfo searchInfo, string type, bool enableMultipleResults, CancellationToken cancellationToken)
+ private async Task<IEnumerable<RemoteSearchResult>> GetSearchResultsInternal(ItemLookupInfo searchInfo, string type, bool isSearch, CancellationToken cancellationToken)
{
- bool isSearch = false;
var episodeSearchInfo = searchInfo as EpisodeInfo;
- var list = new List<RemoteSearchResult>();
-
var imdbId = searchInfo.GetProviderId(MetadataProviders.Imdb);
- var url = "http://www.omdbapi.com/?plot=full&r=json";
+ var url = "https://www.omdbapi.com/?plot=full&r=json";
if (type == "episode" && episodeSearchInfo != null)
{
episodeSearchInfo.SeriesProviderIds.TryGetValue(MetadataProviders.Imdb.ToString(), out imdbId);
}
-
var name = searchInfo.Name;
var year = searchInfo.Year;
@@ -95,10 +97,9 @@ namespace MediaBrowser.Providers.Omdb
}
// &s means search and returns a list of results as opposed to t
- if (enableMultipleResults)
+ if (isSearch)
{
url += "&s=" + WebUtility.UrlEncode(name);
- isSearch = true;
}
else
{
@@ -109,6 +110,7 @@ namespace MediaBrowser.Providers.Omdb
else
{
url += "&i=" + imdbId;
+ isSearch = false;
}
if (type == "episode")
@@ -150,14 +152,13 @@ namespace MediaBrowser.Providers.Omdb
}
}
- foreach (var result in resultList)
+ return resultList.Select(result =>
{
var item = new RemoteSearchResult
{
IndexNumber = searchInfo.IndexNumber,
Name = result.Title,
ParentIndexNumber = searchInfo.ParentIndexNumber,
- ProviderIds = searchInfo.ProviderIds,
SearchProviderName = Name
};
@@ -187,11 +188,9 @@ namespace MediaBrowser.Providers.Omdb
item.ImageUrl = result.Poster;
}
- list.Add(item);
- }
+ return item;
+ });
}
-
- return list;
}
public Task<MetadataResult<Trailer>> GetMetadata(TrailerInfo info, CancellationToken cancellationToken)
@@ -227,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, imdbId, info.MetadataLanguage, info.MetadataCountryCode, cancellationToken).ConfigureAwait(false);
}
return result;
@@ -266,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, 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 b68f93cf6..037931300 100644
--- a/MediaBrowser.Providers/Omdb/OmdbProvider.cs
+++ b/MediaBrowser.Providers/Omdb/OmdbProvider.cs
@@ -1,11 +1,18 @@
-using MediaBrowser.Common.Net;
+using CommonIO;
+using MediaBrowser.Common.Configuration;
+using MediaBrowser.Common.Net;
+using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Entities;
+using MediaBrowser.Controller.Providers;
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,39 +22,30 @@ 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)
+ public async Task Fetch<T>(MetadataResult<T> itemResult, string imdbId, string language, string country, CancellationToken cancellationToken)
+ where T :BaseItem
{
if (string.IsNullOrWhiteSpace(imdbId))
{
throw new ArgumentNullException("imdbId");
}
- var imdbParam = imdbId.StartsWith("tt", StringComparison.OrdinalIgnoreCase) ? imdbId : "tt" + imdbId;
-
- var url = string.Format("http://www.omdbapi.com/?i={0}&tomatoes=true", imdbParam);
+ T item = itemResult.Item;
- 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))
@@ -62,8 +60,8 @@ namespace MediaBrowser.Providers.Omdb
int year;
- if (!string.IsNullOrEmpty(result.Year)
- && int.TryParse(result.Year, NumberStyles.Number, _usCulture, out year)
+ if (!string.IsNullOrEmpty(result.Year) && result.Year.Length >= 4
+ && int.TryParse(result.Year.Substring(0, 4), NumberStyles.Number, _usCulture, out year)
&& year >= 0)
{
item.ProductionYear = year;
@@ -84,7 +82,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,29 +106,292 @@ 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);
+ ParseAdditionalMetadata(itemResult, result);
+ }
+
+ public async Task<bool> FetchEpisodeData<T>(MetadataResult<T> itemResult, int episodeNumber, int seasonNumber, string imdbId, string language, string country, CancellationToken cancellationToken)
+ where T : BaseItem
+ {
+ if (string.IsNullOrWhiteSpace(imdbId))
+ {
+ throw new ArgumentNullException("imdbId");
+ }
+
+ T item = itemResult.Item;
+
+ 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) && result.Year.Length >= 4
+ && int.TryParse(result.Year.Substring(0, 4), 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(itemResult, 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;
}
- private void ParseAdditionalMetadata(BaseItem item, RootObject result)
+ 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<T>(MetadataResult<T> itemResult, RootObject result)
+ where T : BaseItem
+ {
+ T item = itemResult.Item;
+
// 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 +416,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);
}
@@ -168,6 +427,46 @@ namespace MediaBrowser.Providers.Omdb
// Imdb plots are usually pretty short
hasShortOverview.ShortOverview = result.Plot;
}
+
+ //if (!string.IsNullOrWhiteSpace(result.Director))
+ //{
+ // var person = new PersonInfo
+ // {
+ // Name = result.Director.Trim(),
+ // Type = PersonType.Director
+ // };
+
+ // itemResult.AddPerson(person);
+ //}
+
+ //if (!string.IsNullOrWhiteSpace(result.Writer))
+ //{
+ // var person = new PersonInfo
+ // {
+ // Name = result.Director.Trim(),
+ // Type = PersonType.Writer
+ // };
+
+ // itemResult.AddPerson(person);
+ //}
+
+ //if (!string.IsNullOrWhiteSpace(result.Actors))
+ //{
+ // var actorList = result.Actors.Split(',');
+ // foreach (var actor in actorList)
+ // {
+ // if (!string.IsNullOrWhiteSpace(actor))
+ // {
+ // var person = new PersonInfo
+ // {
+ // Name = actor.Trim(),
+ // Type = PersonType.Actor
+ // };
+
+ // itemResult.AddPerson(person);
+ // }
+ // }
+ //}
}
private bool ShouldFetchGenres(BaseItem item)
@@ -178,12 +477,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/MovieDbPersonImageProvider.cs b/MediaBrowser.Providers/People/MovieDbPersonImageProvider.cs
index 2ac9fdfa0..93eee69ae 100644
--- a/MediaBrowser.Providers/People/MovieDbPersonImageProvider.cs
+++ b/MediaBrowser.Providers/People/MovieDbPersonImageProvider.cs
@@ -67,7 +67,7 @@ namespace MediaBrowser.Providers.People
var tmdbSettings = await MovieDbProvider.Current.GetTmdbSettings(cancellationToken).ConfigureAwait(false);
- var tmdbImageUrl = tmdbSettings.images.base_url + "original";
+ var tmdbImageUrl = tmdbSettings.images.secure_base_url + "original";
return GetImages(images, item.GetPreferredMetadataLanguage(), tmdbImageUrl);
}
diff --git a/MediaBrowser.Providers/People/MovieDbPersonProvider.cs b/MediaBrowser.Providers/People/MovieDbPersonProvider.cs
index 24f44fea1..bb17b83ec 100644
--- a/MediaBrowser.Providers/People/MovieDbPersonProvider.cs
+++ b/MediaBrowser.Providers/People/MovieDbPersonProvider.cs
@@ -59,7 +59,7 @@ namespace MediaBrowser.Providers.People
var tmdbSettings = await MovieDbProvider.Current.GetTmdbSettings(cancellationToken).ConfigureAwait(false);
- var tmdbImageUrl = tmdbSettings.images.base_url + "original";
+ var tmdbImageUrl = tmdbSettings.images.secure_base_url + "original";
if (!string.IsNullOrEmpty(tmdbId))
{
@@ -97,7 +97,7 @@ namespace MediaBrowser.Providers.People
var requestCount = _requestCount;
- if (requestCount >= 20)
+ if (requestCount >= 40)
{
//_logger.Debug("Throttling Tmdb people");
@@ -109,7 +109,7 @@ namespace MediaBrowser.Providers.People
}
}
- var url = string.Format(@"http://api.themoviedb.org/3/search/person?api_key={1}&query={0}", WebUtility.UrlEncode(searchInfo.Name), MovieDbProvider.ApiKey);
+ var url = string.Format(@"https://api.themoviedb.org/3/search/person?api_key={1}&query={0}", WebUtility.UrlEncode(searchInfo.Name), MovieDbProvider.ApiKey);
using (var json = await MovieDbProvider.Current.GetMovieDbResponse(new HttpRequestOptions
{
@@ -234,7 +234,7 @@ namespace MediaBrowser.Providers.People
return;
}
- var url = string.Format(@"http://api.themoviedb.org/3/person/{1}?api_key={0}&append_to_response=credits,images,external_ids", MovieDbProvider.ApiKey, id);
+ var url = string.Format(@"https://api.themoviedb.org/3/person/{1}?api_key={0}&append_to_response=credits,images,external_ids", MovieDbProvider.ApiKey, id);
using (var json = await MovieDbProvider.Current.GetMovieDbResponse(new HttpRequestOptions
{
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/Photos/PhotoProvider.cs b/MediaBrowser.Providers/Photos/PhotoProvider.cs
index ef3144958..619b72636 100644
--- a/MediaBrowser.Providers/Photos/PhotoProvider.cs
+++ b/MediaBrowser.Providers/Photos/PhotoProvider.cs
@@ -152,11 +152,12 @@ namespace MediaBrowser.Providers.Photos
get { return "Embedded Information"; }
}
- public bool HasChanged(IHasMetadata item, MetadataStatus status, IDirectoryService directoryService)
+ public bool HasChanged(IHasMetadata item, IDirectoryService directoryService)
{
- if (status.ItemDateModified.HasValue)
+ var file = directoryService.GetFile(item.Path);
+ if (file != null && file.LastWriteTimeUtc != item.DateModified)
{
- return status.ItemDateModified.Value != item.DateModified;
+ return true;
}
return false;
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/Subtitles/OpenSubtitleDownloader.cs b/MediaBrowser.Providers/Subtitles/OpenSubtitleDownloader.cs
index 2276e391d..271f8170f 100644
--- a/MediaBrowser.Providers/Subtitles/OpenSubtitleDownloader.cs
+++ b/MediaBrowser.Providers/Subtitles/OpenSubtitleDownloader.cs
@@ -308,8 +308,8 @@ namespace MediaBrowser.Providers.Subtitles
// Avoid implicitly captured closure
var hasCopy = hash;
- return results.Where(x => x.SubBad == "0" && mediaFilter(x))
- .OrderBy(x => (x.MovieHash == hash ? 0 : 1))
+ return results.Where(x => x.SubBad == "0" && mediaFilter(x) && (!request.IsPerfectMatch || string.Equals(x.MovieHash, hash, StringComparison.OrdinalIgnoreCase)))
+ .OrderBy(x => (string.Equals(x.MovieHash, hash, StringComparison.OrdinalIgnoreCase) ? 0 : 1))
.ThenBy(x => Math.Abs(long.Parse(x.MovieByteSize, _usCulture) - movieByteSize))
.ThenByDescending(x => int.Parse(x.SubDownloadsCnt, _usCulture))
.ThenByDescending(x => double.Parse(x.SubRating, _usCulture))
diff --git a/MediaBrowser.Providers/TV/DummySeasonProvider.cs b/MediaBrowser.Providers/TV/DummySeasonProvider.cs
index 5cf9966e8..baea0a06d 100644
--- a/MediaBrowser.Providers/TV/DummySeasonProvider.cs
+++ b/MediaBrowser.Providers/TV/DummySeasonProvider.cs
@@ -40,7 +40,7 @@ namespace MediaBrowser.Providers.TV
if (hasNewSeasons)
{
- var directoryService = new DirectoryService(_fileSystem);
+ //var directoryService = new DirectoryService(_fileSystem);
//await series.RefreshMetadata(new MetadataRefreshOptions(directoryService), cancellationToken).ConfigureAwait(false);
@@ -69,7 +69,7 @@ namespace MediaBrowser.Providers.TV
if (!hasSeason)
{
- await AddSeason(series, seasonNumber, cancellationToken).ConfigureAwait(false);
+ await AddSeason(series, seasonNumber, false, cancellationToken).ConfigureAwait(false);
hasChanges = true;
}
@@ -83,7 +83,7 @@ namespace MediaBrowser.Providers.TV
if (!hasSeason)
{
- await AddSeason(series, null, cancellationToken).ConfigureAwait(false);
+ await AddSeason(series, null, false, cancellationToken).ConfigureAwait(false);
hasChanges = true;
}
@@ -95,12 +95,9 @@ namespace MediaBrowser.Providers.TV
/// <summary>
/// Adds the season.
/// </summary>
- /// <param name="series">The series.</param>
- /// <param name="seasonNumber">The season number.</param>
- /// <param name="cancellationToken">The cancellation token.</param>
- /// <returns>Task{Season}.</returns>
public async Task<Season> AddSeason(Series series,
int? seasonNumber,
+ bool isVirtualItem,
CancellationToken cancellationToken)
{
var seasonName = seasonNumber == 0 ?
@@ -113,7 +110,9 @@ namespace MediaBrowser.Providers.TV
{
Name = seasonName,
IndexNumber = seasonNumber,
- Id = _libraryManager.GetNewItemId((series.Id + (seasonNumber ?? -1).ToString(_usCulture) + seasonName), typeof(Season))
+ Id = _libraryManager.GetNewItemId((series.Id + (seasonNumber ?? -1).ToString(_usCulture) + seasonName), typeof(Season)),
+ IsVirtualItem = isVirtualItem,
+ SeriesId = series.Id
};
season.SetParent(series);
@@ -163,7 +162,7 @@ namespace MediaBrowser.Providers.TV
// Season does not have a number
// Remove if there are no episodes directly in series without a season number
- return episodes.All(s => s.ParentIndexNumber.HasValue || !s.IsInSeasonFolder);
+ return episodes.All(s => s.ParentIndexNumber.HasValue || s.IsInSeasonFolder);
})
.ToList();
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 e683907c4..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, IHasChangeMonitor
+ 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, DateTime date)
- {
- 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) > date;
- }
-
- return false;
- }
}
}
diff --git a/MediaBrowser.Providers/TV/FanArt/FanArtTvUpdatesPostScanTask.cs b/MediaBrowser.Providers/TV/FanArt/FanArtTvUpdatesPostScanTask.cs
deleted file mode 100644
index 049ffd7d8..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 = "http://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 517951cb8..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, IHasChangeMonitor
+ public class FanartSeriesProvider : IRemoteImageProvider, IHasOrder
{
private readonly CultureInfo _usCulture = new CultureInfo("en-US");
private readonly IServerConfigurationManager _config;
@@ -31,7 +32,7 @@ namespace MediaBrowser.Providers.TV
private readonly IFileSystem _fileSystem;
private readonly IJsonSerializer _json;
- private const string FanArtBaseUrl = "http://webservice.fanart.tv/v3/tv/{1}?api_key={0}";
+ private const string FanArtBaseUrl = "https://webservice.fanart.tv/v3/tv/{1}?api_key={0}";
// &client_key=52c813aa7b8c8b3bb87f4797532a2f8c
internal static FanartSeriesProvider Current { get; private set; }
@@ -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, DateTime date)
- {
- 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) > date;
- }
-
- 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 ad55c186a..22e126795 100644
--- a/MediaBrowser.Providers/TV/MissingEpisodeProvider.cs
+++ b/MediaBrowser.Providers/TV/MissingEpisodeProvider.cs
@@ -27,6 +27,8 @@ namespace MediaBrowser.Providers.TV
private readonly IFileSystem _fileSystem;
private readonly CultureInfo _usCulture = new CultureInfo("en-US");
+ private static readonly SemaphoreSlim _resourceLock = new SemaphoreSlim(1, 1);
+ public static bool IsRunning = false;
public MissingEpisodeProvider(ILogger logger, IServerConfigurationManager config, ILibraryManager libraryManager, ILocalizationManager localization, IFileSystem fileSystem)
{
@@ -37,13 +39,16 @@ namespace MediaBrowser.Providers.TV
_fileSystem = fileSystem;
}
- public async Task Run(IEnumerable<IGrouping<string, Series>> series, CancellationToken cancellationToken)
+ public async Task Run(List<IGrouping<string, Series>> series, bool addNewItems, CancellationToken cancellationToken)
{
+ await _resourceLock.WaitAsync(cancellationToken).ConfigureAwait(false);
+ IsRunning = true;
+
foreach (var seriesGroup in series)
{
try
{
- await Run(seriesGroup, cancellationToken).ConfigureAwait(false);
+ await Run(seriesGroup, addNewItems, cancellationToken).ConfigureAwait(false);
}
catch (OperationCanceledException)
{
@@ -58,9 +63,12 @@ namespace MediaBrowser.Providers.TV
_logger.ErrorException("Error in missing episode provider for series id {0}", ex, seriesGroup.Key);
}
}
+
+ IsRunning = false;
+ _resourceLock.Release();
}
- private async Task Run(IGrouping<string, Series> group, CancellationToken cancellationToken)
+ private async Task Run(IGrouping<string, Series> group, bool addNewItems, CancellationToken cancellationToken)
{
var tvdbId = group.Key;
@@ -110,7 +118,7 @@ namespace MediaBrowser.Providers.TV
var hasNewEpisodes = false;
- if (_config.Configuration.EnableInternetProviders)
+ if (_config.Configuration.EnableInternetProviders && addNewItems)
{
var seriesConfig = _config.Configuration.MetadataOptions.FirstOrDefault(i => string.Equals(i.ItemType, typeof(Series).Name, StringComparison.OrdinalIgnoreCase));
@@ -372,7 +380,7 @@ namespace MediaBrowser.Providers.TV
// Season does not have a number
// Remove if there are no episodes directly in series without a season number
- return i.Season.Series.GetRecursiveChildren().OfType<Episode>().All(s => s.ParentIndexNumber.HasValue || !s.IsInSeasonFolder);
+ return i.Season.Series.GetRecursiveChildren().OfType<Episode>().All(s => s.ParentIndexNumber.HasValue || s.IsInSeasonFolder);
})
.ToList();
@@ -410,7 +418,7 @@ namespace MediaBrowser.Providers.TV
if (season == null)
{
var provider = new DummySeasonProvider(_config, _logger, _localization, _libraryManager, _fileSystem);
- season = await provider.AddSeason(series, seasonNumber, cancellationToken).ConfigureAwait(false);
+ season = await provider.AddSeason(series, seasonNumber, true, cancellationToken).ConfigureAwait(false);
}
var name = string.Format("Episode {0}", episodeNumber.ToString(_usCulture));
@@ -420,14 +428,17 @@ 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,
+ SeasonId = season == null ? (Guid?)null : season.Id,
+ SeriesId = series.Id
};
episode.SetParent(season);
await season.AddChild(episode, cancellationToken).ConfigureAwait(false);
- await episode.RefreshMetadata(new MetadataRefreshOptions(_fileSystem)
+ await episode.RefreshMetadata(new MetadataRefreshOptions(_fileSystem)
{
}, cancellationToken).ConfigureAwait(false);
}
diff --git a/MediaBrowser.Providers/TV/Omdb/OmdbEpisodeProvider.cs b/MediaBrowser.Providers/TV/Omdb/OmdbEpisodeProvider.cs
index 785efc3b6..64e30f47f 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;
@@ -20,13 +22,17 @@ namespace MediaBrowser.Providers.TV
{
private readonly IJsonSerializer _jsonSerializer;
private readonly IHttpClient _httpClient;
- private OmdbItemProvider _itemProvider;
+ 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,35 +42,27 @@ 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()
};
- var imdbId = info.GetProviderId(MetadataProviders.Imdb);
- if (string.IsNullOrWhiteSpace(imdbId))
+ // Allowing this will dramatically increase scan times
+ if (info.IsMissingEpisode || info.IsVirtualUnaired)
{
- imdbId = await GetEpisodeImdbId(info, cancellationToken).ConfigureAwait(false);
+ return result;
}
- if (!string.IsNullOrEmpty(imdbId))
+ if (OmdbProvider.IsValidSeries(info.SeriesProviderIds) && info.IndexNumber.HasValue && info.ParentIndexNumber.HasValue)
{
- 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, 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 1af116289..e4894915d 100644
--- a/MediaBrowser.Providers/TV/SeasonMetadataService.cs
+++ b/MediaBrowser.Providers/TV/SeasonMetadataService.cs
@@ -7,6 +7,7 @@ using MediaBrowser.Model.Logging;
using MediaBrowser.Providers.Manager;
using System;
using System.Collections.Generic;
+using System.Linq;
using System.Threading.Tasks;
using CommonIO;
@@ -14,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);
@@ -31,6 +28,13 @@ namespace MediaBrowser.Providers.TV
}
}
+ if (isFullRefresh || currentUpdateType > ItemUpdateType.None)
+ {
+ var episodes = item.GetEpisodes().ToList();
+ updateType |= SavePremiereDate(item, episodes);
+ updateType |= SaveIsVirtualItem(item, episodes);
+ }
+
return updateType;
}
@@ -38,5 +42,42 @@ namespace MediaBrowser.Providers.TV
{
ProviderUtils.MergeBaseItemData(source, target, lockedFields, replaceData, mergeMetadataSettings);
}
+
+ private ItemUpdateType SavePremiereDate(Season item, List<Episode> episodes)
+ {
+ var dates = episodes.Where(i => i.PremiereDate.HasValue).Select(i => i.PremiereDate.Value).ToList();
+
+ DateTime? premiereDate = null;
+
+ if (dates.Count > 0)
+ {
+ premiereDate = dates.Min();
+ }
+
+ if (item.PremiereDate != premiereDate)
+ {
+ item.PremiereDate = premiereDate;
+ return ItemUpdateType.MetadataEdit;
+ }
+
+ return ItemUpdateType.None;
+ }
+
+ private ItemUpdateType SaveIsVirtualItem(Season item, List<Episode> episodes)
+ {
+ var isVirtualItem = item.LocationType == LocationType.Virtual && (episodes.Count == 0 || episodes.All(i => i.LocationType == LocationType.Virtual));
+
+ if (item.IsVirtualItem != isVirtualItem)
+ {
+ item.IsVirtualItem = isVirtualItem;
+ return ItemUpdateType.MetadataEdit;
+ }
+
+ 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 5428e6c92..d044c828f 100644
--- a/MediaBrowser.Providers/TV/SeriesPostScanTask.cs
+++ b/MediaBrowser.Providers/TV/SeriesPostScanTask.cs
@@ -11,6 +11,10 @@ using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using CommonIO;
+using MediaBrowser.Common.ScheduledTasks;
+using MediaBrowser.Controller.Entities;
+using MediaBrowser.Controller.Plugins;
+using MediaBrowser.Model.Tasks;
namespace MediaBrowser.Providers.TV
{
@@ -46,14 +50,18 @@ namespace MediaBrowser.Providers.TV
private async Task RunInternal(IProgress<double> progress, CancellationToken cancellationToken)
{
- var seriesList = _libraryManager.RootFolder
- .GetRecursiveChildren(i => i is Series)
- .Cast<Series>()
- .ToList();
+ var seriesList = _libraryManager.GetItemList(new InternalItemsQuery()
+ {
+ IncludeItemTypes = new[] { typeof(Series).Name },
+ Recursive = true,
+ GroupByPresentationUniqueKey = false
+
+ }).Cast<Series>().ToList();
var seriesGroups = FindSeriesGroups(seriesList).Where(g => !string.IsNullOrEmpty(g.Key)).ToList();
- await new MissingEpisodeProvider(_logger, _config, _libraryManager, _localization, _fileSystem).Run(seriesGroups, cancellationToken).ConfigureAwait(false);
+ await new MissingEpisodeProvider(_logger, _config, _libraryManager, _localization, _fileSystem)
+ .Run(seriesGroups, true, cancellationToken).ConfigureAwait(false);
var numComplete = 0;
@@ -82,7 +90,7 @@ namespace MediaBrowser.Providers.TV
}
}
- private IEnumerable<IGrouping<string, Series>> FindSeriesGroups(List<Series> seriesList)
+ internal static IEnumerable<IGrouping<string, Series>> FindSeriesGroups(List<Series> seriesList)
{
var links = seriesList.ToDictionary(s => s, s => seriesList.Where(c => c != s && ShareProviderId(s, c)).ToList());
@@ -102,7 +110,7 @@ namespace MediaBrowser.Providers.TV
}
}
- private void FindAllLinked(Series series, HashSet<Series> visited, IDictionary<Series, List<Series>> linksMap, List<Series> results)
+ private static void FindAllLinked(Series series, HashSet<Series> visited, IDictionary<Series, List<Series>> linksMap, List<Series> results)
{
results.Add(series);
visited.Add(series);
@@ -118,7 +126,7 @@ namespace MediaBrowser.Providers.TV
}
}
- private bool ShareProviderId(Series a, Series b)
+ private static bool ShareProviderId(Series a, Series b)
{
return a.ProviderIds.Any(id =>
{
@@ -137,4 +145,116 @@ namespace MediaBrowser.Providers.TV
}
}
+ public class CleanMissingEpisodesEntryPoint : IServerEntryPoint
+ {
+ private readonly ILibraryManager _libraryManager;
+ private readonly IServerConfigurationManager _config;
+ private readonly ILogger _logger;
+ private readonly ILocalizationManager _localization;
+ private readonly IFileSystem _fileSystem;
+ private readonly object _libraryChangedSyncLock = new object();
+ private const int LibraryUpdateDuration = 180000;
+ private readonly ITaskManager _taskManager;
+
+ public CleanMissingEpisodesEntryPoint(ILibraryManager libraryManager, IServerConfigurationManager config, ILogger logger, ILocalizationManager localization, IFileSystem fileSystem, ITaskManager taskManager)
+ {
+ _libraryManager = libraryManager;
+ _config = config;
+ _logger = logger;
+ _localization = localization;
+ _fileSystem = fileSystem;
+ _taskManager = taskManager;
+ }
+
+ private Timer LibraryUpdateTimer { get; set; }
+
+ public void Run()
+ {
+ _libraryManager.ItemAdded += _libraryManager_ItemAdded;
+ }
+
+ private void _libraryManager_ItemAdded(object sender, ItemChangeEventArgs e)
+ {
+ if (!FilterItem(e.Item))
+ {
+ return;
+ }
+
+ lock (_libraryChangedSyncLock)
+ {
+ if (LibraryUpdateTimer == null)
+ {
+ LibraryUpdateTimer = new Timer(LibraryUpdateTimerCallback, null, LibraryUpdateDuration, Timeout.Infinite);
+ }
+ else
+ {
+ LibraryUpdateTimer.Change(LibraryUpdateDuration, Timeout.Infinite);
+ }
+ }
+ }
+
+ private async void LibraryUpdateTimerCallback(object state)
+ {
+ try
+ {
+ if (MissingEpisodeProvider.IsRunning)
+ {
+ return;
+ }
+
+ if (_libraryManager.IsScanRunning)
+ {
+ return;
+ }
+
+ var seriesList = _libraryManager.GetItemList(new InternalItemsQuery()
+ {
+ IncludeItemTypes = new[] { typeof(Series).Name },
+ Recursive = true,
+ GroupByPresentationUniqueKey = false
+
+ }).Cast<Series>().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);
+ }
+ catch (Exception ex)
+ {
+ _logger.ErrorException("Error in SeriesPostScanTask", ex);
+ }
+ }
+
+ private bool FilterItem(BaseItem item)
+ {
+ return item is Episode && item.LocationType != LocationType.Virtual;
+ }
+
+ /// <summary>
+ /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
+ /// </summary>
+ public void Dispose()
+ {
+ Dispose(true);
+ }
+
+ /// <summary>
+ /// Releases unmanaged and - optionally - managed resources.
+ /// </summary>
+ /// <param name="dispose"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param>
+ protected virtual void Dispose(bool dispose)
+ {
+ if (dispose)
+ {
+ if (LibraryUpdateTimer != null)
+ {
+ LibraryUpdateTimer.Dispose();
+ LibraryUpdateTimer = null;
+ }
+
+ _libraryManager.ItemAdded -= _libraryManager_ItemAdded;
+ }
+ }
+ }
}
diff --git a/MediaBrowser.Providers/TV/TheMovieDb/MovieDbEpisodeImageProvider.cs b/MediaBrowser.Providers/TV/TheMovieDb/MovieDbEpisodeImageProvider.cs
index 9d1684948..0feb92e89 100644
--- a/MediaBrowser.Providers/TV/TheMovieDb/MovieDbEpisodeImageProvider.cs
+++ b/MediaBrowser.Providers/TV/TheMovieDb/MovieDbEpisodeImageProvider.cs
@@ -57,12 +57,14 @@ 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);
- var tmdbImageUrl = tmdbSettings.images.base_url + "original";
+ var tmdbImageUrl = tmdbSettings.images.secure_base_url + "original";
list.AddRange(GetPosters(response.images).Select(i => new RemoteImageInfo
{
@@ -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 42254f360..bc9842b73 100644
--- a/MediaBrowser.Providers/TV/TheMovieDb/MovieDbEpisodeProvider.cs
+++ b/MediaBrowser.Providers/TV/TheMovieDb/MovieDbEpisodeProvider.cs
@@ -64,6 +64,12 @@ namespace MediaBrowser.Providers.TV
{
var result = new MetadataResult<Episode>();
+ // Allowing this will dramatically increase scan times
+ if (info.IsMissingEpisode || info.IsVirtualUnaired)
+ {
+ return result;
+ }
+
string seriesTmdbId;
info.SeriesProviderIds.TryGetValue(MetadataProviders.Tmdb.ToString(), out seriesTmdbId);
@@ -108,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 d22827c25..821c26e4b 100644
--- a/MediaBrowser.Providers/TV/TheMovieDb/MovieDbProviderBase.cs
+++ b/MediaBrowser.Providers/TV/TheMovieDb/MovieDbProviderBase.cs
@@ -16,7 +16,7 @@ namespace MediaBrowser.Providers.TV
{
public abstract class MovieDbProviderBase
{
- private const string EpisodeUrlPattern = @"http://api.themoviedb.org/3/tv/{0}/season/{1}/episode/{2}?api_key={3}&append_to_response=images,external_ids,credits,videos";
+ private const string EpisodeUrlPattern = @"https://api.themoviedb.org/3/tv/{0}/season/{1}/episode/{2}?api_key={3}&append_to_response=images,external_ids,credits,videos";
private readonly IHttpClient _httpClient;
private readonly IServerConfigurationManager _configurationManager;
private readonly IJsonSerializer _jsonSerializer;
@@ -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 fe0bda828..194af5b99 100644
--- a/MediaBrowser.Providers/TV/TheMovieDb/MovieDbSeasonProvider.cs
+++ b/MediaBrowser.Providers/TV/TheMovieDb/MovieDbSeasonProvider.cs
@@ -22,7 +22,7 @@ namespace MediaBrowser.Providers.TV
{
public class MovieDbSeasonProvider : IRemoteMetadataProvider<Season, SeasonInfo>
{
- private const string GetTvInfo3 = @"http://api.themoviedb.org/3/tv/{0}/season/{1}?api_key={2}&append_to_response=images,keywords,external_ids,credits,videos";
+ private const string GetTvInfo3 = @"https://api.themoviedb.org/3/tv/{0}/season/{1}?api_key={2}&append_to_response=images,keywords,external_ids,credits,videos";
private readonly IHttpClient _httpClient;
private readonly IServerConfigurationManager _configurationManager;
private readonly IJsonSerializer _jsonSerializer;
@@ -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 f7c19988c..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, IHasChangeMonitor
+ public class MovieDbSeriesImageProvider : IRemoteImageProvider, IHasOrder
{
private readonly IJsonSerializer _jsonSerializer;
private readonly IHttpClient _httpClient;
@@ -64,7 +64,9 @@ namespace MediaBrowser.Providers.TV
var tmdbSettings = await MovieDbProvider.Current.GetTmdbSettings(cancellationToken).ConfigureAwait(false);
- var tmdbImageUrl = tmdbSettings.images.base_url + "original";
+ var tmdbImageUrl = tmdbSettings.images.secure_base_url + "original";
+
+ var language = item.GetPreferredMetadataLanguage();
list.AddRange(GetPosters(results).Select(i => new RemoteImageInfo
{
@@ -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, DateTime date)
- {
- return MovieDbSeriesProvider.Current.HasChanged(item, date);
- }
}
}
diff --git a/MediaBrowser.Providers/TV/TheMovieDb/MovieDbSeriesProvider.cs b/MediaBrowser.Providers/TV/TheMovieDb/MovieDbSeriesProvider.cs
index 05b1ebc80..3245a2c85 100644
--- a/MediaBrowser.Providers/TV/TheMovieDb/MovieDbSeriesProvider.cs
+++ b/MediaBrowser.Providers/TV/TheMovieDb/MovieDbSeriesProvider.cs
@@ -24,7 +24,7 @@ namespace MediaBrowser.Providers.TV
{
public class MovieDbSeriesProvider : IRemoteMetadataProvider<Series, SeriesInfo>, IHasOrder
{
- private const string GetTvInfo3 = @"http://api.themoviedb.org/3/tv/{0}?api_key={1}&append_to_response=credits,images,keywords,external_ids,videos";
+ private const string GetTvInfo3 = @"https://api.themoviedb.org/3/tv/{0}?api_key={1}&append_to_response=credits,images,keywords,external_ids,videos,content_ratings";
private readonly CultureInfo _usCulture = new CultureInfo("en-US");
internal static MovieDbSeriesProvider Current { get; private set; }
@@ -69,7 +69,7 @@ namespace MediaBrowser.Providers.TV
var obj = _jsonSerializer.DeserializeFromFile<RootObject>(dataFilePath);
var tmdbSettings = await MovieDbProvider.Current.GetTmdbSettings(cancellationToken).ConfigureAwait(false);
- var tmdbImageUrl = tmdbSettings.images.base_url + "original";
+ var tmdbImageUrl = tmdbSettings.images.secure_base_url + "original";
var remoteResult = new RemoteSearchResult
{
@@ -168,7 +168,7 @@ namespace MediaBrowser.Providers.TV
{
cancellationToken.ThrowIfCancellationRequested();
- result.Item = await FetchMovieData(tmdbId, info.MetadataLanguage, 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, CancellationToken cancellationToken)
+ private async Task<Series> FetchSeriesData(string tmdbId, string language, string preferredCountryCode, CancellationToken cancellationToken)
{
string dataFilePath = null;
RootObject seriesInfo = null;
@@ -201,12 +201,12 @@ namespace MediaBrowser.Providers.TV
var item = new Series();
- ProcessMainInfo(item, seriesInfo);
+ ProcessMainInfo(item, seriesInfo, preferredCountryCode);
return item;
}
- private void ProcessMainInfo(Series series, RootObject seriesInfo)
+ private void ProcessMainInfo(Series series, RootObject seriesInfo, string preferredCountryCode)
{
series.Name = seriesInfo.name;
series.SetProviderId(MetadataProviders.Tmdb, seriesInfo.id.ToString(_usCulture));
@@ -265,6 +265,41 @@ namespace MediaBrowser.Providers.TV
series.SetProviderId(MetadataProviders.Tvdb, ids.tvdb_id.ToString(_usCulture));
}
}
+
+ var contentRatings = (seriesInfo.content_ratings ?? new ContentRatings()).results ?? new List<ContentRating>();
+
+ var ourRelease = contentRatings.FirstOrDefault(c => string.Equals(c.iso_3166_1, preferredCountryCode, StringComparison.OrdinalIgnoreCase));
+ var usRelease = contentRatings.FirstOrDefault(c => string.Equals(c.iso_3166_1, "US", StringComparison.OrdinalIgnoreCase));
+ var minimumRelease = contentRatings.FirstOrDefault();
+
+ if (ourRelease != null)
+ {
+ series.OfficialRating = ourRelease.rating;
+ }
+ else if (usRelease != null)
+ {
+ series.OfficialRating = usRelease.rating;
+ }
+ else if (minimumRelease != null)
+ {
+ 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)
@@ -394,31 +429,9 @@ namespace MediaBrowser.Providers.TV
return Path.Combine(path, filename);
}
- public bool HasChanged(IHasMetadata item, DateTime date)
- {
- 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) > date;
- }
-
- return false;
- }
-
private async Task<RemoteSearchResult> FindByExternalId(string id, string externalSource, CancellationToken cancellationToken)
{
- var url = string.Format("http://api.themoviedb.org/3/tv/find/{0}?api_key={1}&external_source={2}",
+ var url = string.Format("https://api.themoviedb.org/3/tv/find/{0}?api_key={1}&external_source={2}",
id,
MovieDbProvider.ApiKey,
externalSource);
@@ -440,7 +453,7 @@ namespace MediaBrowser.Providers.TV
if (tv != null)
{
var tmdbSettings = await MovieDbProvider.Current.GetTmdbSettings(cancellationToken).ConfigureAwait(false);
- var tmdbImageUrl = tmdbSettings.images.base_url + "original";
+ var tmdbImageUrl = tmdbSettings.images.secure_base_url + "original";
var remoteResult = new RemoteSearchResult
{
@@ -481,6 +494,7 @@ namespace MediaBrowser.Providers.TV
public class Season
{
public string air_date { get; set; }
+ public int episode_count { get; set; }
public int id { get; set; }
public string poster_path { get; set; }
public int season_number { get; set; }
@@ -528,7 +542,6 @@ namespace MediaBrowser.Providers.TV
public double aspect_ratio { get; set; }
public string file_path { get; set; }
public int height { get; set; }
- public string id { get; set; }
public string iso_639_1 { get; set; }
public double vote_average { get; set; }
public int vote_count { get; set; }
@@ -557,7 +570,30 @@ 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
+ {
+ public string iso_3166_1 { get; set; }
+ public string rating { get; set; }
+ }
+
+ public class ContentRatings
+ {
+ public List<ContentRating> results { get; set; }
}
public class RootObject
@@ -590,6 +626,7 @@ namespace MediaBrowser.Providers.TV
public Keywords keywords { get; set; }
public ExternalIds external_ids { get; set; }
public Videos videos { get; set; }
+ public ContentRatings content_ratings { get; set; }
}
public int Order
diff --git a/MediaBrowser.Providers/TV/TheTVDB/TvdbEpisodeImageProvider.cs b/MediaBrowser.Providers/TV/TheTVDB/TvdbEpisodeImageProvider.cs
index 49d41e06c..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, IHasChangeMonitor
+ public class TvdbEpisodeImageProvider : IRemoteImageProvider
{
private readonly IServerConfigurationManager _config;
private readonly CultureInfo _usCulture = new CultureInfo("en-US");
@@ -173,34 +173,5 @@ namespace MediaBrowser.Providers.TV
ResourcePool = TvdbSeriesProvider.Current.TvDbResourcePool
});
}
-
- public bool HasChanged(IHasMetadata item, IDirectoryService directoryService, DateTime date)
- {
- var episode = (Episode)item;
-
- if (!episode.IsVirtualUnaired)
- {
- // For non-unaired items, only enable if configured
- if (!TvdbSeriesProvider.Current.GetTvDbOptions().EnableAutomaticUpdates)
- {
- return false;
- }
- }
-
- if (!item.HasImage(ImageType.Primary))
- {
- 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) > date;
- }
- }
-
- return false;
- }
}
}
diff --git a/MediaBrowser.Providers/TV/TheTVDB/TvdbEpisodeProvider.cs b/MediaBrowser.Providers/TV/TheTVDB/TvdbEpisodeProvider.cs
index 291214fcd..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>, IHasChangeMonitor
+ class TvdbEpisodeProvider : IRemoteMetadataProvider<Episode, EpisodeInfo>
{
private static readonly string FullIdKey = MetadataProviders.Tvdb + "-Full";
@@ -144,28 +144,6 @@ namespace MediaBrowser.Providers.TV
return result;
}
- public bool HasChanged(IHasMetadata item, IDirectoryService directoryService, DateTime date)
- {
- // Only enable for virtual items
- if (item.LocationType != LocationType.Virtual)
- {
- 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) > date;
- }
-
- return false;
- }
-
/// <summary>
/// Gets the episode XML files.
/// </summary>
@@ -750,7 +728,7 @@ namespace MediaBrowser.Providers.TV
private void AddPeople<T>(MetadataResult<T> result, string val, string personType)
{
// Sometimes tvdb actors have leading spaces
- foreach (var person in val.Split(new[] { '|', ',' }, StringSplitOptions.RemoveEmptyEntries)
+ foreach (var person in val.Split(new[] { '|' }, StringSplitOptions.RemoveEmptyEntries)
.Where(i => !string.IsNullOrWhiteSpace(i))
.Select(str => new PersonInfo { Type = personType, Name = str.Trim() }))
{
@@ -893,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/TvdbPrescanTask.cs b/MediaBrowser.Providers/TV/TheTVDB/TvdbPrescanTask.cs
index 1c83d73fb..215e0640f 100644
--- a/MediaBrowser.Providers/TV/TheTVDB/TvdbPrescanTask.cs
+++ b/MediaBrowser.Providers/TV/TheTVDB/TvdbPrescanTask.cs
@@ -15,6 +15,7 @@ using System.Threading;
using System.Threading.Tasks;
using System.Xml;
using CommonIO;
+using MediaBrowser.Controller.Entities;
namespace MediaBrowser.Providers.TV
{
@@ -26,12 +27,12 @@ namespace MediaBrowser.Providers.TV
/// <summary>
/// The server time URL
/// </summary>
- private const string ServerTimeUrl = "http://thetvdb.com/api/Updates.php?type=none";
+ private const string ServerTimeUrl = "https://thetvdb.com/api/Updates.php?type=none";
/// <summary>
/// The updates URL
/// </summary>
- private const string UpdatesUrl = "http://thetvdb.com/api/Updates.php?type=all&time={0}";
+ private const string UpdatesUrl = "https://thetvdb.com/api/Updates.php?type=all&time={0}";
/// <summary>
/// The _HTTP client
@@ -89,7 +90,7 @@ namespace MediaBrowser.Providers.TV
var path = TvdbSeriesProvider.GetSeriesDataPath(_config.CommonApplicationPaths);
- _fileSystem.CreateDirectory(path);
+ _fileSystem.CreateDirectory(path);
var timestampFile = Path.Combine(path, "time.txt");
@@ -102,7 +103,7 @@ namespace MediaBrowser.Providers.TV
}
// Find out the last time we queried tvdb for updates
- var lastUpdateTime = timestampFileInfo.Exists ? _fileSystem.ReadAllText(timestampFile, Encoding.UTF8) : string.Empty;
+ var lastUpdateTime = timestampFileInfo.Exists ? _fileSystem.ReadAllText(timestampFile, Encoding.UTF8) : string.Empty;
string newUpdateTime;
@@ -110,15 +111,21 @@ namespace MediaBrowser.Providers.TV
.Select(Path.GetFileName)
.ToList();
- var seriesIdsInLibrary = _libraryManager.RootFolder
- .GetRecursiveChildren(i => i is Series && !string.IsNullOrEmpty(i.GetProviderId(MetadataProviders.Tvdb)))
- .Cast<Series>()
+ var seriesList = _libraryManager.GetItemList(new InternalItemsQuery()
+ {
+ IncludeItemTypes = new[] { typeof(Series).Name },
+ Recursive = true,
+ GroupByPresentationUniqueKey = false
+ }).Cast<Series>();
+
+ var seriesIdsInLibrary = seriesList
+ .Where(i => !string.IsNullOrEmpty(i.GetProviderId(MetadataProviders.Tvdb)))
.Select(i => i.GetProviderId(MetadataProviders.Tvdb))
.ToList();
var missingSeries = seriesIdsInLibrary.Except(existingDirectories, StringComparer.OrdinalIgnoreCase)
.ToList();
-
+
// If this is our first time, update all series
if (string.IsNullOrEmpty(lastUpdateTime))
{
@@ -157,7 +164,7 @@ namespace MediaBrowser.Providers.TV
await UpdateSeries(listToUpdate, path, nullableUpdateValue, progress, cancellationToken).ConfigureAwait(false);
}
- _fileSystem.WriteAllText(timestampFile, newUpdateTime, Encoding.UTF8);
+ _fileSystem.WriteAllText(timestampFile, newUpdateTime, Encoding.UTF8);
progress.Report(100);
}
@@ -300,10 +307,17 @@ namespace MediaBrowser.Providers.TV
var list = seriesIds.ToList();
var numComplete = 0;
+ var seriesList = _libraryManager.GetItemList(new InternalItemsQuery()
+ {
+ IncludeItemTypes = new[] { typeof(Series).Name },
+ Recursive = true,
+ GroupByPresentationUniqueKey = false
+
+ }).Cast<Series>();
+
// Gather all series into a lookup by tvdb id
- var allSeries = _libraryManager.RootFolder
- .GetRecursiveChildren(i => i is Series && !string.IsNullOrEmpty(i.GetProviderId(MetadataProviders.Tvdb)))
- .Cast<Series>()
+ var allSeries = seriesList
+ .Where(i => !string.IsNullOrEmpty(i.GetProviderId(MetadataProviders.Tvdb)))
.ToLookup(i => i.GetProviderId(MetadataProviders.Tvdb));
foreach (var seriesId in list)
@@ -323,7 +337,7 @@ namespace MediaBrowser.Providers.TV
catch (HttpException ex)
{
_logger.ErrorException("Error updating tvdb series id {0}, language {1}", ex, seriesId, language);
-
+
// Already logged at lower levels, but don't fail the whole operation, unless timed out
// We have to fail this to make it run again otherwise new episode data could potentially be missing
if (ex.IsTimedOut)
@@ -357,7 +371,7 @@ namespace MediaBrowser.Providers.TV
seriesDataPath = Path.Combine(seriesDataPath, id);
- _fileSystem.CreateDirectory(seriesDataPath);
+ _fileSystem.CreateDirectory(seriesDataPath);
return TvdbSeriesProvider.Current.DownloadSeriesZip(id, MetadataProviders.Tvdb.ToString(), seriesDataPath, lastTvDbUpdateTime, preferredMetadataLanguage, cancellationToken);
}
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 5e7ce9f7e..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, IHasChangeMonitor
+ 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,32 +347,5 @@ namespace MediaBrowser.Providers.TV
ResourcePool = TvdbSeriesProvider.Current.TvDbResourcePool
});
}
-
- public bool HasChanged(IHasMetadata item, IDirectoryService directoryService, DateTime date)
- {
- if (item.LocationType != LocationType.Virtual)
- {
- // For non-virtual items, only enable if configured
- 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) > date;
- }
-
- return false;
- }
}
}
diff --git a/MediaBrowser.Providers/TV/TheTVDB/TvdbSeriesImageProvider.cs b/MediaBrowser.Providers/TV/TheTVDB/TvdbSeriesImageProvider.cs
index 011ed9ed0..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, MetadataStatus status, 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) > (status.DateLastMetadataRefresh ?? DateTime.MinValue);
- }
-
- return false;
- }
}
}
diff --git a/MediaBrowser.Providers/TV/TheTVDB/TvdbSeriesProvider.cs b/MediaBrowser.Providers/TV/TheTVDB/TvdbSeriesProvider.cs
index f66e9254e..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,24 +38,22 @@ 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;
}
- private const string SeriesSearchUrl = "http://www.thetvdb.com/api/GetSeries.php?seriesname={0}&language={1}";
- private const string SeriesGetZip = "http://www.thetvdb.com/api/{0}/series/{1}/all/{2}.zip";
- private const string GetSeriesByImdbId = "http://www.thetvdb.com/api/GetSeriesByRemoteID.php?imdbid={0}&language={1}";
+ private const string SeriesSearchUrl = "https://www.thetvdb.com/api/GetSeries.php?seriesname={0}&language={1}";
+ private const string SeriesGetZip = "https://www.thetvdb.com/api/{0}/series/{1}/all/{2}.zip";
+ private const string GetSeriesByImdbId = "https://www.thetvdb.com/api/GetSeriesByRemoteID.php?imdbid={0}&language={1}";
private string NormalizeLanguage(string language)
{
@@ -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)
- }
- };
- }
- }
-} \ No newline at end of file
+}
diff --git a/MediaBrowser.Providers/TV/TvExternalIds.cs b/MediaBrowser.Providers/TV/TvExternalIds.cs
index 82baae250..2ba3b6ff6 100644
--- a/MediaBrowser.Providers/TV/TvExternalIds.cs
+++ b/MediaBrowser.Providers/TV/TvExternalIds.cs
@@ -42,7 +42,7 @@ namespace MediaBrowser.Providers.TV
public string UrlFormatString
{
- get { return "http://thetvdb.com/index.php?tab=series&id={0}"; }
+ get { return "https://thetvdb.com/index.php?tab=series&id={0}"; }
}
public bool Supports(IHasProviderIds item)
@@ -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 85ab76182..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()
{
- var dbFile = Path.Combine(_appPaths.DataPath, "activitylog.db");
-
- _connection = await SqliteExtensions.ConnectToDb(dbFile, Logger).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/ChannelImageProvider.cs b/MediaBrowser.Server.Implementations/Channels/ChannelImageProvider.cs
index f13c71c6d..c98f71ce2 100644
--- a/MediaBrowser.Server.Implementations/Channels/ChannelImageProvider.cs
+++ b/MediaBrowser.Server.Implementations/Channels/ChannelImageProvider.cs
@@ -47,7 +47,7 @@ namespace MediaBrowser.Server.Implementations.Channels
return ((ChannelManager)_channelManager).GetChannelProvider(channel);
}
- public bool HasChanged(IHasMetadata item, MetadataStatus status, IDirectoryService directoryService)
+ public bool HasChanged(IHasMetadata item, IDirectoryService directoryService)
{
return GetSupportedImages(item).Any(i => !item.HasImage(i));
}
diff --git a/MediaBrowser.Server.Implementations/Channels/ChannelManager.cs b/MediaBrowser.Server.Implementations/Channels/ChannelManager.cs
index a206c1925..41592865c 100644
--- a/MediaBrowser.Server.Implementations/Channels/ChannelManager.cs
+++ b/MediaBrowser.Server.Implementations/Channels/ChannelManager.cs
@@ -133,7 +133,7 @@ namespace MediaBrowser.Server.Implementations.Channels
if (query.IsFavorite.HasValue)
{
var val = query.IsFavorite.Value;
- channels = channels.Where(i => _userDataManager.GetUserData(user.Id, i.GetUserDataKey()).IsFavorite == val)
+ channels = channels.Where(i => _userDataManager.GetUserData(user, i).IsFavorite == val)
.ToList();
}
@@ -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;
@@ -1406,7 +1439,8 @@ namespace MediaBrowser.Server.Implementations.Channels
throw new ArgumentNullException("channel");
}
- var result = GetAllChannels().FirstOrDefault(i => string.Equals(GetInternalChannelId(i.Name).ToString("N"), channel.ChannelId, StringComparison.OrdinalIgnoreCase) || string.Equals(i.Name, channel.Name, StringComparison.OrdinalIgnoreCase));
+ var result = GetAllChannels()
+ .FirstOrDefault(i => string.Equals(GetInternalChannelId(i.Name).ToString("N"), channel.ChannelId, StringComparison.OrdinalIgnoreCase) || string.Equals(i.Name, channel.Name, StringComparison.OrdinalIgnoreCase));
if (result == null)
{
@@ -1436,7 +1470,7 @@ namespace MediaBrowser.Server.Implementations.Channels
case ItemFilter.IsFavoriteOrLikes:
return items.Where(item =>
{
- var userdata = _userDataManager.GetUserData(user.Id, item.GetUserDataKey());
+ var userdata = _userDataManager.GetUserData(user, item);
if (userdata == null)
{
@@ -1452,7 +1486,7 @@ namespace MediaBrowser.Server.Implementations.Channels
case ItemFilter.Likes:
return items.Where(item =>
{
- var userdata = _userDataManager.GetUserData(user.Id, item.GetUserDataKey());
+ var userdata = _userDataManager.GetUserData(user, item);
return userdata != null && userdata.Likes.HasValue && userdata.Likes.Value;
});
@@ -1460,7 +1494,7 @@ namespace MediaBrowser.Server.Implementations.Channels
case ItemFilter.Dislikes:
return items.Where(item =>
{
- var userdata = _userDataManager.GetUserData(user.Id, item.GetUserDataKey());
+ var userdata = _userDataManager.GetUserData(user, item);
return userdata != null && userdata.Likes.HasValue && !userdata.Likes.Value;
});
@@ -1468,7 +1502,7 @@ namespace MediaBrowser.Server.Implementations.Channels
case ItemFilter.IsFavorite:
return items.Where(item =>
{
- var userdata = _userDataManager.GetUserData(user.Id, item.GetUserDataKey());
+ var userdata = _userDataManager.GetUserData(user, item);
return userdata != null && userdata.IsFavorite;
});
@@ -1476,7 +1510,7 @@ namespace MediaBrowser.Server.Implementations.Channels
case ItemFilter.IsResumable:
return items.Where(item =>
{
- var userdata = _userDataManager.GetUserData(user.Id, item.GetUserDataKey());
+ var userdata = _userDataManager.GetUserData(user, item);
return userdata != null && userdata.PlaybackPositionTicks > 0;
});
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/Configuration/ServerConfigurationManager.cs b/MediaBrowser.Server.Implementations/Configuration/ServerConfigurationManager.cs
index 7db457c6e..e8669bbc2 100644
--- a/MediaBrowser.Server.Implementations/Configuration/ServerConfigurationManager.cs
+++ b/MediaBrowser.Server.Implementations/Configuration/ServerConfigurationManager.cs
@@ -95,13 +95,9 @@ namespace MediaBrowser.Server.Implementations.Configuration
{
metadataPath = GetInternalMetadataPath();
}
- else if (Configuration.EnableCustomPathSubFolders)
- {
- metadataPath = Path.Combine(Configuration.MetadataPath, "metadata");
- }
else
{
- metadataPath = Configuration.MetadataPath;
+ metadataPath = Path.Combine(Configuration.MetadataPath, "metadata");
}
((ServerApplicationPaths)ApplicationPaths).InternalMetadataPath = metadataPath;
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 9ed67f77e..24750de94 100644
--- a/MediaBrowser.Server.Implementations/Connect/ConnectManager.cs
+++ b/MediaBrowser.Server.Implementations/Connect/ConnectManager.cs
@@ -24,6 +24,7 @@ using System.Text;
using System.Threading;
using System.Threading.Tasks;
using CommonIO;
+using MediaBrowser.Common.Extensions;
namespace MediaBrowser.Server.Implementations.Connect
{
@@ -62,6 +63,17 @@ namespace MediaBrowser.Server.Implementations.Connect
{
var address = _config.Configuration.WanDdns;
+ if (!string.IsNullOrWhiteSpace(address))
+ {
+ try
+ {
+ address = new Uri(address).Host;
+ }
+ catch
+ {
+ }
+ }
+
if (string.IsNullOrWhiteSpace(address) && DiscoveredWanIpAddress != null)
{
if (DiscoveredWanIpAddress.AddressFamily == AddressFamily.InterNetworkV6)
@@ -127,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;
@@ -165,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);
@@ -205,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)
@@ -237,8 +254,8 @@ namespace MediaBrowser.Server.Implementations.Connect
var postData = new Dictionary<string, string>
{
- {"name", _appHost.FriendlyName},
- {"url", wanApiAddress},
+ {"name", _appHost.FriendlyName},
+ {"url", wanApiAddress},
{"systemId", _appHost.SystemId}
};
@@ -345,6 +362,8 @@ namespace MediaBrowser.Server.Implementations.Connect
{
var path = CacheFilePath;
+ _logger.Info("Loading data from {0}", path);
+
try
{
lock (_dataFileLock)
@@ -544,9 +563,22 @@ namespace MediaBrowser.Server.Implementations.Connect
}
catch (HttpException ex)
{
- if (!ex.StatusCode.HasValue ||
- ex.StatusCode.Value != HttpStatusCode.NotFound ||
- !Validator.EmailIsValid(connectUsername))
+ if (!ex.StatusCode.HasValue)
+ {
+ throw;
+ }
+
+ // If they entered a username, then whatever the error is just throw it, for example, user not found
+ if (!Validator.EmailIsValid(connectUsername))
+ {
+ if (ex.StatusCode.Value == HttpStatusCode.NotFound)
+ {
+ throw new ResourceNotFoundException();
+ }
+ throw;
+ }
+
+ if (ex.StatusCode.Value != HttpStatusCode.NotFound)
{
throw;
}
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 234e15a66..616625bc9 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)
@@ -115,11 +125,10 @@ namespace MediaBrowser.Server.Implementations.Dto
{
if (options.Fields.Contains(ItemFields.ItemCounts))
{
- var itemFilter = byName.GetItemFilter();
-
- var libraryItems = user != null ?
- user.RootFolder.GetRecursiveChildren(user, itemFilter) :
- _libraryManager.RootFolder.GetRecursiveChildren(itemFilter);
+ var libraryItems = byName.GetTaggedItems(new InternalItemsQuery(user)
+ {
+ Recursive = true
+ });
SetItemByNameInfo(item, dto, libraryItems.ToList(), user);
}
@@ -132,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)
@@ -160,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)
{
@@ -194,24 +202,13 @@ namespace MediaBrowser.Server.Implementations.Dto
private List<BaseItem> GetTaggedItems(IItemByName byName, User user)
{
- var person = byName as Person;
-
- if (person != null)
+ var items = byName.GetTaggedItems(new InternalItemsQuery(user)
{
- var items = _libraryManager.GetItemList(new InternalItemsQuery(user)
- {
- Person = byName.Name
-
- }, new string[] { });
+ Recursive = true
- return items.ToList();
- }
-
- var itemFilter = byName.GetItemFilter();
+ }).ToList();
- return user != null ?
- user.RootFolder.GetRecursiveChildren(user, itemFilter).ToList() :
- _libraryManager.RootFolder.GetRecursiveChildren(itemFilter).ToList();
+ return items;
}
private SyncedItemProgress[] GetSyncedItemProgress(DtoOptions options)
@@ -282,7 +279,7 @@ namespace MediaBrowser.Server.Implementations.Dto
else if (dto.HasSyncJob.Value)
{
- dto.SyncStatus = SyncJobItemStatus.Queued;
+ dto.SyncStatus = syncProgress.Where(i => string.Equals(i.ItemId, dto.Id, StringComparison.OrdinalIgnoreCase)).Select(i => i.Status).Max();
}
}
}
@@ -307,12 +304,12 @@ namespace MediaBrowser.Server.Implementations.Dto
else if (dto.HasSyncJob.Value)
{
- dto.SyncStatus = SyncJobItemStatus.Queued;
+ dto.SyncStatus = syncProgress.Where(i => string.Equals(i.ItemId, dto.Id, StringComparison.OrdinalIgnoreCase)).Select(i => i.Status).Max();
}
}
}
- 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;
@@ -361,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;
@@ -397,12 +394,6 @@ namespace MediaBrowser.Server.Implementations.Dto
collectionFolder.GetViewType(user);
}
- var playlist = item as Playlist;
- if (playlist != null)
- {
- AttachLinkedChildImages(dto, playlist, user, options);
- }
-
if (fields.Contains(ItemFields.CanDelete))
{
dto.CanDelete = user == null
@@ -434,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);
}
@@ -483,35 +474,47 @@ 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.Id, item.GetUserDataKey());
+ 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);
+ }
- // These are just far too slow.
- if (!(folder is UserRootFolder) && !(folder is UserView) && !(folder is ICollectionFolder))
- {
- SetSpecialCounts(folder, user, dto, fields, syncProgress);
- }
+ if (fields.Contains(ItemFields.CumulativeRunTimeTicks))
+ {
+ dto.CumulativeRunTimeTicks = item.RunTimeTicks;
}
- dto.UserData.Played = dto.UserData.PlayedPercentage.HasValue && dto.UserData.PlayedPercentage.Value >= 100;
+ if (fields.Contains(ItemFields.DateLastMediaAdded))
+ {
+ dto.DateLastMediaAdded = folder.DateLastMediaAdded;
+ }
}
else
{
- dto.UserData = _userDataRepository.GetUserDataDto(item, user);
+ dto.UserData = _userDataRepository.GetUserDataDto(item, user).Result;
}
dto.PlayAccess = item.GetPlayAccess(user);
@@ -526,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);
}
}
}
@@ -546,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>
@@ -626,9 +635,12 @@ namespace MediaBrowser.Server.Implementations.Dto
{
if (!string.IsNullOrEmpty(item.Album))
{
- var parentAlbum = _libraryManager.RootFolder
- .GetRecursiveChildren(i => i is MusicAlbum && string.Equals(i.Name, item.Album, StringComparison.OrdinalIgnoreCase))
- .FirstOrDefault();
+ var parentAlbum = _libraryManager.GetItemList(new InternalItemsQuery
+ {
+ IncludeItemTypes = new[] { typeof(MusicAlbum).Name },
+ Name = item.Album
+
+ }).FirstOrDefault();
if (parentAlbum != null)
{
@@ -651,29 +663,11 @@ namespace MediaBrowser.Server.Implementations.Dto
dto.GameSystem = item.GameSystemName;
}
- private List<string> GetBackdropImageTags(BaseItem item, int limit)
- {
- return GetCacheTags(item, ImageType.Backdrop, limit).ToList();
- }
-
- private List<string> GetScreenshotImageTags(BaseItem item, int limit)
+ private List<string> GetImageTags(BaseItem item, List<ItemImageInfo> images)
{
- var hasScreenshots = item as IHasScreenshots;
- if (hasScreenshots == null)
- {
- return new List<string>();
- }
- return GetCacheTags(item, ImageType.Screenshot, limit).ToList();
- }
-
- private IEnumerable<string> GetCacheTags(BaseItem item, ImageType type, int limit)
- {
- return item.GetImages(type)
- // Convert to a list now in case GetImageCacheTag is slow
- .ToList()
+ return images
.Select(p => GetImageCacheTag(item, p))
.Where(i => i != null)
- .Take(limit)
.ToList();
}
@@ -839,53 +833,6 @@ namespace MediaBrowser.Server.Implementations.Dto
}
/// <summary>
- /// If an item does not any backdrops, this can be used to find the first parent that does have one
- /// </summary>
- /// <param name="item">The item.</param>
- /// <param name="owner">The owner.</param>
- /// <returns>BaseItem.</returns>
- private BaseItem GetParentBackdropItem(BaseItem item, BaseItem owner)
- {
- var parent = item.GetParent() ?? owner;
-
- while (parent != null)
- {
- if (parent.GetImages(ImageType.Backdrop).Any())
- {
- return parent;
- }
-
- parent = parent.GetParent();
- }
-
- return null;
- }
-
- /// <summary>
- /// If an item does not have a logo, this can be used to find the first parent that does have one
- /// </summary>
- /// <param name="item">The item.</param>
- /// <param name="type">The type.</param>
- /// <param name="owner">The owner.</param>
- /// <returns>BaseItem.</returns>
- private BaseItem GetParentImageItem(BaseItem item, ImageType type, BaseItem owner)
- {
- var parent = item.GetParent() ?? owner;
-
- while (parent != null)
- {
- if (parent.HasImage(type))
- {
- return parent;
- }
-
- parent = parent.GetParent();
- }
-
- return null;
- }
-
- /// <summary>
/// Gets the chapter info dto.
/// </summary>
/// <param name="chapterInfo">The chapter info.</param>
@@ -905,7 +852,7 @@ namespace MediaBrowser.Server.Implementations.Dto
{
Path = chapterInfo.ImagePath,
Type = ImageType.Chapter,
- DateModified = _fileSystem.GetLastWriteTimeUtc(chapterInfo.ImagePath)
+ DateModified = chapterInfo.ImageDateModified
});
}
@@ -946,6 +893,7 @@ namespace MediaBrowser.Server.Implementations.Dto
dto.LockData = item.IsLocked;
dto.ForcedSortName = item.ForcedSortName;
}
+ dto.Container = item.Container;
var hasBudget = item as IHasBudget;
if (hasBudget != null)
@@ -975,30 +923,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))
@@ -1033,7 +963,7 @@ namespace MediaBrowser.Server.Implementations.Dto
var backdropLimit = options.GetImageLimit(ImageType.Backdrop);
if (backdropLimit > 0)
{
- dto.BackdropImageTags = GetBackdropImageTags(item, backdropLimit);
+ dto.BackdropImageTags = GetImageTags(item, item.GetImages(ImageType.Backdrop).Take(backdropLimit).ToList());
}
if (fields.Contains(ItemFields.ScreenshotImageTags))
@@ -1041,7 +971,7 @@ namespace MediaBrowser.Server.Implementations.Dto
var screenshotLimit = options.GetImageLimit(ImageType.Screenshot);
if (screenshotLimit > 0)
{
- dto.ScreenshotImageTags = GetScreenshotImageTags(item, screenshotLimit);
+ dto.ScreenshotImageTags = GetImageTags(item, item.GetImages(ImageType.Screenshot).Take(screenshotLimit).ToList());
}
}
@@ -1070,6 +1000,7 @@ namespace MediaBrowser.Server.Implementations.Dto
dto.Id = GetDtoId(item);
dto.IndexNumber = item.IndexNumber;
+ dto.ParentIndexNumber = item.ParentIndexNumber;
dto.IsFolder = item.IsFolder;
dto.MediaType = item.MediaType;
dto.LocationType = item.LocationType;
@@ -1082,15 +1013,11 @@ namespace MediaBrowser.Server.Implementations.Dto
dto.PreferredMetadataCountryCode = item.PreferredMetadataCountryCode;
dto.PreferredMetadataLanguage = item.PreferredMetadataLanguage;
- var hasCriticRating = item as IHasCriticRating;
- if (hasCriticRating != null)
- {
- dto.CriticRating = hasCriticRating.CriticRating;
+ dto.CriticRating = item.CriticRating;
- if (fields.Contains(ItemFields.CriticRatingSummary))
- {
- dto.CriticRatingSummary = hasCriticRating.CriticRatingSummary;
- }
+ if (fields.Contains(ItemFields.CriticRatingSummary))
+ {
+ dto.CriticRatingSummary = item.CriticRatingSummary;
}
var hasTrailers = item as IHasTrailers;
@@ -1126,76 +1053,26 @@ namespace MediaBrowser.Server.Implementations.Dto
dto.Overview = item.Overview;
}
- if (fields.Contains(ItemFields.ShortOverview))
+ if (fields.Contains(ItemFields.OriginalTitle))
{
- var hasShortOverview = item as IHasShortOverview;
- if (hasShortOverview != null)
- {
- dto.ShortOverview = hasShortOverview.ShortOverview;
- }
+ dto.OriginalTitle = item.OriginalTitle;
}
- // If there are no backdrops, indicate what parent has them in case the Ui wants to allow inheritance
- if (backdropLimit > 0 && dto.BackdropImageTags.Count == 0)
+ if (fields.Contains(ItemFields.ShortOverview))
{
- var parentWithBackdrop = GetParentBackdropItem(item, owner);
-
- if (parentWithBackdrop != null)
- {
- dto.ParentBackdropItemId = GetDtoId(parentWithBackdrop);
- dto.ParentBackdropImageTags = GetBackdropImageTags(parentWithBackdrop, backdropLimit);
- }
+ dto.ShortOverview = item.ShortOverview;
}
if (fields.Contains(ItemFields.ParentId))
{
- var displayParent = item.DisplayParent;
- if (displayParent != null)
+ var displayParentId = item.DisplayParentId;
+ if (displayParentId.HasValue)
{
- dto.ParentId = GetDtoId(displayParent);
+ dto.ParentId = displayParentId.Value.ToString("N");
}
}
- dto.ParentIndexNumber = item.ParentIndexNumber;
-
- // If there is no logo, indicate what parent has one in case the Ui wants to allow inheritance
- if (!dto.HasLogo && options.GetImageLimit(ImageType.Logo) > 0)
- {
- var parentWithLogo = GetParentImageItem(item, ImageType.Logo, owner);
-
- if (parentWithLogo != null)
- {
- dto.ParentLogoItemId = GetDtoId(parentWithLogo);
-
- dto.ParentLogoImageTag = GetImageCacheTag(parentWithLogo, ImageType.Logo);
- }
- }
-
- // If there is no art, indicate what parent has one in case the Ui wants to allow inheritance
- if (!dto.HasArtImage && options.GetImageLimit(ImageType.Art) > 0)
- {
- var parentWithImage = GetParentImageItem(item, ImageType.Art, owner);
-
- if (parentWithImage != null)
- {
- dto.ParentArtItemId = GetDtoId(parentWithImage);
-
- dto.ParentArtImageTag = GetImageCacheTag(parentWithImage, ImageType.Art);
- }
- }
-
- // If there is no thumb, indicate what parent has one in case the Ui wants to allow inheritance
- if (!dto.HasThumb && options.GetImageLimit(ImageType.Thumb) > 0)
- {
- var parentWithImage = GetParentImageItem(item, ImageType.Thumb, owner);
-
- if (parentWithImage != null)
- {
- dto.ParentThumbItemId = GetDtoId(parentWithImage);
-
- dto.ParentThumbImageTag = GetImageCacheTag(parentWithImage, ImageType.Thumb);
- }
- }
+ AddInheritedImages(dto, item, options, owner);
if (fields.Contains(ItemFields.Path))
{
@@ -1287,26 +1164,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 = _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 artist", ex);
- return null;
- }
+ Name = artist.Name,
+ Id = artist.Id.ToString("N")
+ };
})
- .Where(i => i != null)
.ToList();
}
@@ -1315,26 +1188,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 = 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 album artist", ex);
- return null;
- }
+ Name = artist.Name,
+ Id = artist.Id.ToString("N")
+ };
})
- .Where(i => i != null)
.ToList();
}
@@ -1358,9 +1227,10 @@ namespace MediaBrowser.Server.Implementations.Dto
if (fields.Contains(ItemFields.MediaSourceCount))
{
- if (video.MediaSourceCount != 1)
+ var mediaSourceCount = video.MediaSourceCount;
+ if (mediaSourceCount != 1)
{
- dto.MediaSourceCount = video.MediaSourceCount;
+ dto.MediaSourceCount = mediaSourceCount;
}
}
@@ -1412,6 +1282,7 @@ namespace MediaBrowser.Server.Implementations.Dto
if (episode != null)
{
dto.IndexNumberEnd = episode.IndexNumberEnd;
+ dto.SeriesName = episode.SeriesName;
if (fields.Contains(ItemFields.AlternateEpisodeNumbers))
{
@@ -1427,23 +1298,46 @@ namespace MediaBrowser.Server.Implementations.Dto
dto.AirsBeforeSeasonNumber = episode.AirsBeforeSeasonNumber;
}
- var episodeSeason = episode.Season;
- if (episodeSeason != null)
+ var seasonId = episode.SeasonId;
+ if (seasonId.HasValue)
{
- dto.SeasonId = episodeSeason.Id.ToString("N");
+ dto.SeasonId = seasonId.Value.ToString("N");
+ }
+
+ dto.SeasonName = episode.SeasonName;
- if (fields.Contains(ItemFields.SeasonName))
+ var seriesId = episode.SeriesId;
+ if (seriesId.HasValue)
+ {
+ dto.SeriesId = seriesId.Value.ToString("N");
+ }
+
+ Series episodeSeries = null;
+
+ if (fields.Contains(ItemFields.SeriesGenres))
+ {
+ episodeSeries = episodeSeries ?? episode.Series;
+ if (episodeSeries != null)
{
- dto.SeasonName = episodeSeason.Name;
+ dto.SeriesGenres = episodeSeries.Genres.ToList();
}
}
- if (fields.Contains(ItemFields.SeriesGenres))
+ //if (fields.Contains(ItemFields.SeriesPrimaryImage))
{
- var episodeseries = episode.Series;
- if (episodeseries != null)
+ episodeSeries = episodeSeries ?? episode.Series;
+ if (episodeSeries != null)
{
- dto.SeriesGenres = episodeseries.Genres.ToList();
+ dto.SeriesPrimaryImageTag = GetImageCacheTag(episodeSeries, ImageType.Primary);
+ }
+ }
+
+ if (fields.Contains(ItemFields.SeriesStudio))
+ {
+ episodeSeries = episodeSeries ?? episode.Series;
+ if (episodeSeries != null)
+ {
+ dto.SeriesStudio = episodeSeries.Studios.FirstOrDefault();
}
}
}
@@ -1456,59 +1350,36 @@ namespace MediaBrowser.Server.Implementations.Dto
dto.AirTime = series.AirTime;
dto.SeriesStatus = series.Status;
- if (fields.Contains(ItemFields.Settings))
- {
- dto.DisplaySpecialsWithSeasons = series.DisplaySpecialsWithSeasons;
- }
-
dto.AnimeSeriesIndex = series.AnimeSeriesIndex;
}
- if (episode != null)
+ // Add SeasonInfo
+ var season = item as Season;
+ if (season != null)
{
- series = episode.Series;
+ dto.SeriesName = season.SeriesName;
- if (series != null)
+ var seriesId = season.SeriesId;
+ if (seriesId.HasValue)
{
- dto.SeriesId = GetDtoId(series);
- dto.SeriesName = series.Name;
-
- if (fields.Contains(ItemFields.AirTime))
- {
- dto.AirTime = series.AirTime;
- }
-
- if (options.GetImageLimit(ImageType.Thumb) > 0)
- {
- dto.SeriesThumbImageTag = GetImageCacheTag(series, ImageType.Thumb);
- }
+ dto.SeriesId = seriesId.Value.ToString("N");
+ }
- if (options.GetImageLimit(ImageType.Primary) > 0)
- {
- dto.SeriesPrimaryImageTag = GetImageCacheTag(series, ImageType.Primary);
- }
+ series = null;
- if (fields.Contains(ItemFields.SeriesStudio))
+ if (fields.Contains(ItemFields.SeriesStudio))
+ {
+ series = series ?? season.Series;
+ if (series != null)
{
dto.SeriesStudio = series.Studios.FirstOrDefault();
}
}
- }
- // Add SeasonInfo
- var season = item as Season;
- if (season != null)
- {
- series = season.Series;
-
- if (series != null)
+ if (fields.Contains(ItemFields.SeriesPrimaryImage))
{
- dto.SeriesId = GetDtoId(series);
- dto.SeriesName = series.Name;
- dto.AirTime = series.AirTime;
- dto.SeriesStudio = series.Studios.FirstOrDefault();
-
- if (options.GetImageLimit(ImageType.Primary) > 0)
+ series = series ?? season.Series;
+ if (series != null)
{
dto.SeriesPrimaryImageTag = GetImageCacheTag(series, ImageType.Primary);
}
@@ -1559,42 +1430,74 @@ namespace MediaBrowser.Server.Implementations.Dto
}
}
- private void AttachLinkedChildImages(BaseItemDto dto, Folder folder, User user, DtoOptions options)
+ private void AddInheritedImages(BaseItemDto dto, BaseItem item, DtoOptions options, BaseItem owner)
{
- List<BaseItem> linkedChildren = null;
-
+ var logoLimit = options.GetImageLimit(ImageType.Logo);
+ var artLimit = options.GetImageLimit(ImageType.Art);
+ var thumbLimit = options.GetImageLimit(ImageType.Thumb);
var backdropLimit = options.GetImageLimit(ImageType.Backdrop);
- if (backdropLimit > 0 && dto.BackdropImageTags.Count == 0)
+ if (logoLimit == 0 && artLimit == 0 && thumbLimit == 0 && backdropLimit == 0)
{
- linkedChildren = user == null
- ? folder.GetRecursiveChildren().ToList()
- : folder.GetRecursiveChildren(user).ToList();
+ return;
+ }
- var parentWithBackdrop = linkedChildren.FirstOrDefault(i => i.GetImages(ImageType.Backdrop).Any());
+ BaseItem parent = null;
+ var isFirst = true;
- if (parentWithBackdrop != null)
+ while (((!dto.HasLogo && logoLimit > 0) || (!dto.HasArtImage && artLimit > 0) || (!dto.HasThumb && thumbLimit > 0) || parent is Series) &&
+ (parent = parent ?? (isFirst ? item.GetParent() ?? owner : parent)) != null)
+ {
+ if (parent == null)
{
- dto.ParentBackdropItemId = GetDtoId(parentWithBackdrop);
- dto.ParentBackdropImageTags = GetBackdropImageTags(parentWithBackdrop, backdropLimit);
+ break;
}
- }
- if (!dto.ImageTags.ContainsKey(ImageType.Primary) && options.GetImageLimit(ImageType.Primary) > 0)
- {
- if (linkedChildren == null)
+ var allImages = parent.ImageInfos;
+
+ if (logoLimit > 0 && !dto.HasLogo && dto.ParentLogoItemId == null)
{
- linkedChildren = user == null
- ? folder.GetRecursiveChildren().ToList()
- : folder.GetRecursiveChildren(user).ToList();
+ var image = allImages.FirstOrDefault(i => i.Type == ImageType.Logo);
+
+ if (image != null)
+ {
+ dto.ParentLogoItemId = GetDtoId(parent);
+ dto.ParentLogoImageTag = GetImageCacheTag(parent, image);
+ }
}
- var parentWithImage = linkedChildren.FirstOrDefault(i => i.GetImages(ImageType.Primary).Any());
+ if (artLimit > 0 && !dto.HasArtImage && dto.ParentArtItemId == null)
+ {
+ var image = allImages.FirstOrDefault(i => i.Type == ImageType.Art);
- if (parentWithImage != null)
+ if (image != null)
+ {
+ dto.ParentArtItemId = GetDtoId(parent);
+ dto.ParentArtImageTag = GetImageCacheTag(parent, image);
+ }
+ }
+ if (thumbLimit > 0 && !dto.HasThumb && (dto.ParentThumbItemId == null || parent is Series))
{
- dto.ParentPrimaryImageItemId = GetDtoId(parentWithImage);
- dto.ParentPrimaryImageTag = GetImageCacheTag(parentWithImage, ImageType.Primary);
+ var image = allImages.FirstOrDefault(i => i.Type == ImageType.Thumb);
+
+ if (image != null)
+ {
+ dto.ParentThumbItemId = GetDtoId(parent);
+ dto.ParentThumbImageTag = GetImageCacheTag(parent, image);
+ }
+ }
+ if (backdropLimit > 0 && !dto.HasBackdrop)
+ {
+ var images = allImages.Where(i => i.Type == ImageType.Backdrop).Take(backdropLimit).ToList();
+
+ if (images.Count > 0)
+ {
+ dto.ParentBackdropItemId = GetDtoId(parent);
+ dto.ParentBackdropImageTags = GetImageTags(parent, images);
+ }
}
+
+ isFirst = false;
+ parent = parent.GetParent();
}
}
@@ -1649,39 +1552,27 @@ 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;
- long runtime = 0;
- DateTime? dateLastMediaAdded = null;
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)
{
- if (!dateLastMediaAdded.HasValue)
- {
- dateLastMediaAdded = child.DateCreated;
- }
- else
- {
- dateLastMediaAdded = new[] { dateLastMediaAdded.Value, child.DateCreated }.Max();
- }
-
- var userdata = _userDataRepository.GetUserData(user.Id, child.GetUserDataKey());
+ var userdata = _userDataRepository.GetUserData(user, child);
recursiveItemCount++;
@@ -1709,28 +1600,23 @@ namespace MediaBrowser.Server.Implementations.Dto
unplayed++;
}
- runtime += child.RunTimeTicks ?? 0;
-
- 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;
@@ -1740,25 +1626,12 @@ 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;
}
}
-
- if (runtime > 0 && fields.Contains(ItemFields.CumulativeRunTimeTicks))
- {
- dto.CumulativeRunTimeTicks = runtime;
- }
-
- if (fields.Contains(ItemFields.DateLastMediaAdded))
- {
- dto.DateLastMediaAdded = dateLastMediaAdded;
- }
}
/// <summary>
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 95763c43f..50ad3cfbc 100644
--- a/MediaBrowser.Server.Implementations/EntryPoints/ExternalPortForwarding.cs
+++ b/MediaBrowser.Server.Implementations/EntryPoints/ExternalPortForwarding.cs
@@ -50,8 +50,6 @@ namespace MediaBrowser.Server.Implementations.EntryPoints
void _config_ConfigurationUpdated(object sender, EventArgs e)
{
- _config.ConfigurationUpdated -= _config_ConfigurationUpdated;
-
if (!string.Equals(_lastConfigIdentifier, GetConfigIdentifier(), StringComparison.OrdinalIgnoreCase))
{
if (_isStarted)
@@ -95,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;
@@ -104,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);
}
}
@@ -153,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
@@ -226,30 +256,5 @@ namespace MediaBrowser.Server.Implementations.EntryPoints
_isStarted = false;
}
}
-
- private class LogWriter : TextWriter
- {
- private readonly ILogger _logger;
-
- public LogWriter(ILogger logger)
- {
- _logger = logger;
- }
-
- public override Encoding Encoding
- {
- get { return Encoding.UTF8; }
- }
-
- public override void WriteLine(string format, params object[] arg)
- {
- _logger.Debug(format, arg);
- }
-
- public override void WriteLine(string value)
- {
- _logger.Debug(value);
- }
- }
}
-}
+} \ No newline at end of file
diff --git a/MediaBrowser.Server.Implementations/EntryPoints/Notifications/Notifications.cs b/MediaBrowser.Server.Implementations/EntryPoints/Notifications/Notifications.cs
index 918110226..e84b66c5a 100644
--- a/MediaBrowser.Server.Implementations/EntryPoints/Notifications/Notifications.cs
+++ b/MediaBrowser.Server.Implementations/EntryPoints/Notifications/Notifications.cs
@@ -22,6 +22,7 @@ using System.Globalization;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
+using MediaBrowser.Controller.Entities.TV;
namespace MediaBrowser.Server.Implementations.EntryPoints.Notifications
{
@@ -116,7 +117,8 @@ namespace MediaBrowser.Server.Implementations.EntryPoints.Notifications
var notification = new NotificationRequest
{
- NotificationType = type
+ NotificationType = type,
+ Url = e.Argument.infoUrl
};
notification.Variables["Version"] = e.Argument.versionStr;
@@ -213,6 +215,12 @@ namespace MediaBrowser.Server.Implementations.EntryPoints.Notifications
return;
}
+ var video = e.Item as Video;
+ if (video != null && video.IsThemeMedia)
+ {
+ return;
+ }
+
var type = GetPlaybackNotificationType(item.MediaType);
SendPlaybackNotification(type, e);
@@ -228,6 +236,12 @@ namespace MediaBrowser.Server.Implementations.EntryPoints.Notifications
return;
}
+ var video = e.Item as Video;
+ if (video != null && video.IsThemeMedia)
+ {
+ return;
+ }
+
var type = GetPlaybackStoppedNotificationType(item.MediaType);
SendPlaybackNotification(type, e);
@@ -334,12 +348,17 @@ namespace MediaBrowser.Server.Implementations.EntryPoints.Notifications
private bool FilterItem(BaseItem item)
{
- if (!item.IsFolder && item.LocationType == LocationType.Virtual)
+ if (item.IsFolder)
{
return false;
}
- if (item is IItemByName && !(item is MusicArtist))
+ if (item.LocationType == LocationType.Virtual)
+ {
+ return false;
+ }
+
+ if (item is IItemByName)
{
return false;
}
@@ -387,6 +406,18 @@ namespace MediaBrowser.Server.Implementations.EntryPoints.Notifications
public static string GetItemName(BaseItem item)
{
var name = item.Name;
+ var episode = item as Episode;
+ if (episode != null)
+ {
+ if (episode.IndexNumber.HasValue)
+ {
+ name = string.Format("Ep{0} - {1}", episode.IndexNumber.Value.ToString(CultureInfo.InvariantCulture), name);
+ }
+ if (episode.ParentIndexNumber.HasValue)
+ {
+ name = string.Format("S{0}, {1}", episode.ParentIndexNumber.Value.ToString(CultureInfo.InvariantCulture), name);
+ }
+ }
var hasSeries = item as IHasSeries;
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/UsageReporter.cs b/MediaBrowser.Server.Implementations/EntryPoints/UsageReporter.cs
index 7e22efb23..7b3a7a30d 100644
--- a/MediaBrowser.Server.Implementations/EntryPoints/UsageReporter.cs
+++ b/MediaBrowser.Server.Implementations/EntryPoints/UsageReporter.cs
@@ -18,7 +18,7 @@ namespace MediaBrowser.Server.Implementations.EntryPoints
private readonly IHttpClient _httpClient;
private readonly IUserManager _userManager;
private readonly ILogger _logger;
- private const string MbAdminUrl = "http://www.mb3admin.com/admin/";
+ private const string MbAdminUrl = "https://www.mb3admin.com/admin/";
public UsageReporter(IApplicationHost applicationHost, IHttpClient httpClient, IUserManager userManager, ILogger logger)
{
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 e45df3f4a..2109f8d59 100644
--- a/MediaBrowser.Server.Implementations/FileOrganization/EpisodeFileOrganizer.cs
+++ b/MediaBrowser.Server.Implementations/FileOrganization/EpisodeFileOrganizer.cs
@@ -116,7 +116,7 @@ namespace MediaBrowser.Server.Implementations.FileOrganization
premiereDate,
options,
overwriteExisting,
- false,
+ false,
result,
cancellationToken).ConfigureAwait(false);
}
@@ -202,7 +202,7 @@ namespace MediaBrowser.Server.Implementations.FileOrganization
null,
options,
true,
- request.RememberCorrection,
+ request.RememberCorrection,
result,
cancellationToken).ConfigureAwait(false);
@@ -219,7 +219,7 @@ namespace MediaBrowser.Server.Implementations.FileOrganization
DateTime? premiereDate,
AutoOrganizeOptions options,
bool overwriteExisting,
- bool rememberCorrection,
+ bool rememberCorrection,
FileOrganizationResult result,
CancellationToken cancellationToken)
{
@@ -242,7 +242,7 @@ namespace MediaBrowser.Server.Implementations.FileOrganization
premiereDate,
options,
overwriteExisting,
- rememberCorrection,
+ rememberCorrection,
result,
cancellationToken);
}
@@ -255,7 +255,7 @@ namespace MediaBrowser.Server.Implementations.FileOrganization
DateTime? premiereDate,
AutoOrganizeOptions options,
bool overwriteExisting,
- bool rememberCorrection,
+ bool rememberCorrection,
FileOrganizationResult result,
CancellationToken cancellationToken)
{
@@ -304,7 +304,7 @@ namespace MediaBrowser.Server.Implementations.FileOrganization
if (otherDuplicatePaths.Count > 0)
{
- var msg = string.Format("File '{0}' already exists as '{1}', stopping organization", sourcePath, otherDuplicatePaths);
+ var msg = string.Format("File '{0}' already exists as these:'{1}'. Stopping organization", sourcePath, string.Join("', '", otherDuplicatePaths));
_logger.Info(msg);
result.Status = FileSortingStatus.SkippedExisting;
result.StatusMessage = msg;
@@ -356,6 +356,11 @@ namespace MediaBrowser.Server.Implementations.FileOrganization
private void SaveSmartMatchString(string matchString, Series series, AutoOrganizeOptions options)
{
+ if (string.IsNullOrEmpty(matchString) || matchString.Length < 3)
+ {
+ return;
+ }
+
SmartMatchInfo info = options.SmartMatchInfos.FirstOrDefault(i => string.Equals(i.ItemName, series.Name, StringComparison.OrdinalIgnoreCase));
if (info == null)
@@ -536,7 +541,11 @@ namespace MediaBrowser.Server.Implementations.FileOrganization
result.ExtractedName = nameWithoutYear;
result.ExtractedYear = yearInName;
- var series = _libraryManager.RootFolder.GetRecursiveChildren(i => i is Series)
+ var series = _libraryManager.GetItemList(new Controller.Entities.InternalItemsQuery
+ {
+ IncludeItemTypes = new[] { typeof(Series).Name },
+ Recursive = true
+ })
.Cast<Series>()
.Select(i => NameUtils.GetMatchScore(nameWithoutYear, yearInName, i))
.Where(i => i.Item2 > 0)
@@ -550,10 +559,13 @@ namespace MediaBrowser.Server.Implementations.FileOrganization
if (info != null)
{
- series = _libraryManager.RootFolder
- .GetRecursiveChildren(i => i is Series)
- .Cast<Series>()
- .FirstOrDefault(i => string.Equals(i.Name, info.ItemName, StringComparison.OrdinalIgnoreCase));
+ series = _libraryManager.GetItemList(new Controller.Entities.InternalItemsQuery
+ {
+ IncludeItemTypes = new[] { typeof(Series).Name },
+ Recursive = true,
+ Name = info.ItemName
+
+ }).Cast<Series>().FirstOrDefault();
}
}
diff --git a/MediaBrowser.Server.Implementations/FileOrganization/FileOrganizationService.cs b/MediaBrowser.Server.Implementations/FileOrganization/FileOrganizationService.cs
index 0e8a60612..60d515e12 100644
--- a/MediaBrowser.Server.Implementations/FileOrganization/FileOrganizationService.cs
+++ b/MediaBrowser.Server.Implementations/FileOrganization/FileOrganizationService.cs
@@ -95,7 +95,7 @@ namespace MediaBrowser.Server.Implementations.FileOrganization
return _repo.Delete(resultId);
}
- private AutoOrganizeOptions GetAutoOrganizeptions()
+ private AutoOrganizeOptions GetAutoOrganizeOptions()
{
return _config.GetAutoOrganizeOptions();
}
@@ -112,7 +112,7 @@ namespace MediaBrowser.Server.Implementations.FileOrganization
var organizer = new EpisodeFileOrganizer(this, _config, _fileSystem, _logger, _libraryManager,
_libraryMonitor, _providerManager);
- await organizer.OrganizeEpisodeFile(result.OriginalPath, GetAutoOrganizeptions(), true, CancellationToken.None)
+ await organizer.OrganizeEpisodeFile(result.OriginalPath, GetAutoOrganizeOptions(), true, CancellationToken.None)
.ConfigureAwait(false);
}
@@ -126,7 +126,7 @@ namespace MediaBrowser.Server.Implementations.FileOrganization
var organizer = new EpisodeFileOrganizer(this, _config, _fileSystem, _logger, _libraryManager,
_libraryMonitor, _providerManager);
- await organizer.OrganizeWithCorrection(request, GetAutoOrganizeptions(), CancellationToken.None).ConfigureAwait(false);
+ await organizer.OrganizeWithCorrection(request, GetAutoOrganizeOptions(), CancellationToken.None).ConfigureAwait(false);
}
public QueryResult<SmartMatchInfo> GetSmartMatchInfos(FileOrganizationResultQuery query)
@@ -136,7 +136,7 @@ namespace MediaBrowser.Server.Implementations.FileOrganization
throw new ArgumentNullException("query");
}
- var options = GetAutoOrganizeptions();
+ var options = GetAutoOrganizeOptions();
var items = options.SmartMatchInfos.Skip(query.StartIndex ?? 0).Take(query.Limit ?? Int32.MaxValue).ToArray();
@@ -159,7 +159,7 @@ namespace MediaBrowser.Server.Implementations.FileOrganization
throw new ArgumentNullException("matchString");
}
- var options = GetAutoOrganizeptions();
+ var options = GetAutoOrganizeOptions();
SmartMatchInfo info = options.SmartMatchInfos.FirstOrDefault(i => string.Equals(i.ItemName, itemName));
diff --git a/MediaBrowser.Server.Implementations/HttpServer/AsyncStreamWriterFunc.cs b/MediaBrowser.Server.Implementations/HttpServer/AsyncStreamWriterFunc.cs
new file mode 100644
index 000000000..5aa01c706
--- /dev/null
+++ b/MediaBrowser.Server.Implementations/HttpServer/AsyncStreamWriterFunc.cs
@@ -0,0 +1,56 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Threading.Tasks;
+using ServiceStack;
+using ServiceStack.Web;
+
+namespace MediaBrowser.Server.Implementations.HttpServer
+{
+ public class AsyncStreamWriterFunc : IStreamWriter, IAsyncStreamWriter, IHasOptions
+ {
+ /// <summary>
+ /// Gets or sets the source stream.
+ /// </summary>
+ /// <value>The source stream.</value>
+ private Func<Stream, Task> Writer { get; set; }
+
+ /// <summary>
+ /// Gets the options.
+ /// </summary>
+ /// <value>The options.</value>
+ public IDictionary<string, string> Options { get; private set; }
+
+ public Action OnComplete { get; set; }
+ public Action OnError { get; set; }
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="StreamWriter" /> class.
+ /// </summary>
+ public AsyncStreamWriterFunc(Func<Stream, Task> writer, IDictionary<string, string> headers)
+ {
+ Writer = writer;
+
+ if (headers == null)
+ {
+ headers = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
+ }
+ Options = headers;
+ }
+
+ /// <summary>
+ /// Writes to.
+ /// </summary>
+ /// <param name="responseStream">The response stream.</param>
+ public void WriteTo(Stream responseStream)
+ {
+ var task = Writer(responseStream);
+ Task.WaitAll(task);
+ }
+
+ public async Task WriteToAsync(Stream responseStream)
+ {
+ await Writer(responseStream).ConfigureAwait(false);
+ }
+ }
+}
diff --git a/MediaBrowser.Server.Implementations/HttpServer/HttpListenerHost.cs b/MediaBrowser.Server.Implementations/HttpServer/HttpListenerHost.cs
index 3e4f4a70c..17e4793cb 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()
@@ -179,6 +179,11 @@ namespace MediaBrowser.Server.Implementations.HttpServer
private void OnWebSocketConnecting(WebSocketConnectingEventArgs args)
{
+ if (_disposed)
+ {
+ return;
+ }
+
if (WebSocketConnecting != null)
{
WebSocketConnecting(this, args);
@@ -187,6 +192,11 @@ namespace MediaBrowser.Server.Implementations.HttpServer
private void OnWebSocketConnected(WebSocketConnectEventArgs args)
{
+ if (_disposed)
+ {
+ return;
+ }
+
if (WebSocketConnected != null)
{
WebSocketConnected(this, args);
@@ -325,12 +335,19 @@ namespace MediaBrowser.Server.Implementations.HttpServer
/// <param name="httpReq">The HTTP req.</param>
/// <param name="url">The URL.</param>
/// <returns>Task.</returns>
- protected Task RequestHandler(IHttpRequest httpReq, Uri url)
+ protected async Task RequestHandler(IHttpRequest httpReq, Uri url)
{
var date = DateTime.Now;
var httpRes = httpReq.Response;
+ if (_disposed)
+ {
+ httpRes.StatusCode = 503;
+ httpRes.Close();
+ return ;
+ }
+
var operationName = httpReq.OperationName;
var localPath = url.LocalPath;
@@ -344,6 +361,19 @@ namespace MediaBrowser.Server.Implementations.HttpServer
LoggerUtils.LogRequest(_logger, urlToLog, httpReq.HttpMethod, httpReq.UserAgent);
}
+ if (string.Equals(localPath, "/emby/", StringComparison.OrdinalIgnoreCase) ||
+ string.Equals(localPath, "/mediabrowser/", StringComparison.OrdinalIgnoreCase))
+ {
+ httpRes.RedirectToUrl(DefaultRedirectPath);
+ return;
+ }
+ if (string.Equals(localPath, "/emby", StringComparison.OrdinalIgnoreCase) ||
+ string.Equals(localPath, "/mediabrowser", StringComparison.OrdinalIgnoreCase))
+ {
+ httpRes.RedirectToUrl("emby/" + DefaultRedirectPath);
+ return;
+ }
+
if (string.Equals(localPath, "/mediabrowser/", StringComparison.OrdinalIgnoreCase) ||
string.Equals(localPath, "/mediabrowser", StringComparison.OrdinalIgnoreCase) ||
localPath.IndexOf("mediabrowser/web", StringComparison.OrdinalIgnoreCase) != -1 ||
@@ -359,45 +389,35 @@ namespace MediaBrowser.Server.Implementations.HttpServer
httpRes.Write("<!doctype html><html><head><title>Emby</title></head><body>Please update your Emby bookmark to <a href=\"" + newUrl + "\">" + newUrl + "</a></body></html>");
httpRes.Close();
- return Task.FromResult(true);
+ return;
}
}
- if (string.Equals(localPath, "/emby/", StringComparison.OrdinalIgnoreCase))
- {
- httpRes.RedirectToUrl(DefaultRedirectPath);
- return Task.FromResult(true);
- }
- if (string.Equals(localPath, "/emby", StringComparison.OrdinalIgnoreCase))
- {
- httpRes.RedirectToUrl("emby/" + DefaultRedirectPath);
- return Task.FromResult(true);
- }
if (string.Equals(localPath, "/web", StringComparison.OrdinalIgnoreCase))
{
httpRes.RedirectToUrl(DefaultRedirectPath);
- return Task.FromResult(true);
+ return;
}
if (string.Equals(localPath, "/web/", StringComparison.OrdinalIgnoreCase))
{
httpRes.RedirectToUrl("../" + DefaultRedirectPath);
- return Task.FromResult(true);
+ return;
}
if (string.Equals(localPath, "/", StringComparison.OrdinalIgnoreCase))
{
httpRes.RedirectToUrl(DefaultRedirectPath);
- return Task.FromResult(true);
+ return;
}
if (string.IsNullOrEmpty(localPath))
{
httpRes.RedirectToUrl("/" + DefaultRedirectPath);
- return Task.FromResult(true);
+ return;
}
if (string.Equals(localPath, "/emby/pin", StringComparison.OrdinalIgnoreCase))
{
httpRes.RedirectToUrl("web/pin.html");
- return Task.FromResult(true);
+ return;
}
if (!string.IsNullOrWhiteSpace(GlobalResponse))
@@ -407,7 +427,7 @@ namespace MediaBrowser.Server.Implementations.HttpServer
httpRes.Write(GlobalResponse);
httpRes.Close();
- return Task.FromResult(true);
+ return;
}
var handler = HttpHandlerFactory.GetHandler(httpReq);
@@ -423,13 +443,13 @@ namespace MediaBrowser.Server.Implementations.HttpServer
httpReq.OperationName = operationName = restHandler.RestPath.RequestType.GetOperationName();
}
- var task = serviceStackHandler.ProcessRequestAsync(httpReq, httpRes, operationName);
-
- task.ContinueWith(x => httpRes.Close(), TaskContinuationOptions.OnlyOnRanToCompletion | TaskContinuationOptions.AttachedToParent);
- //Matches Exceptions handled in HttpListenerBase.InitTask()
-
- task.ContinueWith(x =>
+ try
+ {
+ await serviceStackHandler.ProcessRequestAsync(httpReq, httpRes, operationName).ConfigureAwait(false);
+ }
+ finally
{
+ httpRes.Close();
var statusCode = httpRes.StatusCode;
var duration = DateTime.Now - date;
@@ -438,13 +458,10 @@ namespace MediaBrowser.Server.Implementations.HttpServer
{
LoggerUtils.LogResponse(_logger, statusCode, urlToLog, remoteIp, duration);
}
-
- }, TaskContinuationOptions.None);
- return task;
+ }
}
- return new NotImplementedException("Cannot execute handler: " + handler + " at PathInfo: " + httpReq.PathInfo)
- .AsTaskException();
+ throw new NotImplementedException("Cannot execute handler: " + handler + " at PathInfo: " + httpReq.PathInfo);
}
/// <summary>
diff --git a/MediaBrowser.Server.Implementations/HttpServer/HttpResultFactory.cs b/MediaBrowser.Server.Implementations/HttpServer/HttpResultFactory.cs
index 6cedaa6a9..c0a2a5eb3 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;
@@ -331,7 +331,11 @@ namespace MediaBrowser.Server.Implementations.HttpServer
options.ContentType = MimeTypes.GetMimeType(path);
}
- options.DateLastModified = _fileSystem.GetLastWriteTimeUtc(path);
+ if (!options.DateLastModified.HasValue)
+ {
+ options.DateLastModified = _fileSystem.GetLastWriteTimeUtc(path);
+ }
+
var cacheKey = path + options.DateLastModified.Value.Ticks;
options.CacheKey = cacheKey.GetMD5();
@@ -351,7 +355,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 +376,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 +402,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;
@@ -699,5 +703,10 @@ namespace MediaBrowser.Server.Implementations.HttpServer
throw error;
}
+
+ public object GetAsyncStreamWriter(Func<Stream, Task> streamWriter, IDictionary<string, string> responseHeaders = null)
+ {
+ return new AsyncStreamWriterFunc(streamWriter, responseHeaders);
+ }
}
} \ No newline at end of file
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/NetListener/HttpListenerServer.cs b/MediaBrowser.Server.Implementations/HttpServer/NetListener/HttpListenerServer.cs
deleted file mode 100644
index 31c0e87b3..000000000
--- a/MediaBrowser.Server.Implementations/HttpServer/NetListener/HttpListenerServer.cs
+++ /dev/null
@@ -1,285 +0,0 @@
-using MediaBrowser.Controller.Net;
-using MediaBrowser.Model.Logging;
-using ServiceStack;
-using ServiceStack.Host.HttpListener;
-using ServiceStack.Web;
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Net;
-using System.Text;
-using System.Threading;
-using System.Threading.Tasks;
-
-namespace MediaBrowser.Server.Implementations.HttpServer.NetListener
-{
- public class HttpListenerServer : IHttpListener
- {
- private readonly ILogger _logger;
- private HttpListener _listener;
- private readonly ManualResetEventSlim _listenForNextRequest = new ManualResetEventSlim(false);
-
- public Action<Exception, IRequest> ErrorHandler { get; set; }
- public Action<WebSocketConnectEventArgs> WebSocketHandler { get; set; }
- public Func<IHttpRequest, Uri, Task> RequestHandler { get; set; }
-
- private readonly Action<string> _endpointListener;
-
- public HttpListenerServer(ILogger logger, Action<string> endpointListener)
- {
- _logger = logger;
- _endpointListener = endpointListener;
- }
-
- private List<string> UrlPrefixes { get; set; }
-
- public void Start(IEnumerable<string> urlPrefixes)
- {
- UrlPrefixes = urlPrefixes.ToList();
-
- if (_listener == null)
- _listener = new HttpListener();
-
- //HostContext.Config.HandlerFactoryPath = ListenerRequest.GetHandlerPathIfAny(UrlPrefixes.First());
-
- foreach (var prefix in UrlPrefixes)
- {
- _logger.Info("Adding HttpListener prefix " + prefix);
- _listener.Prefixes.Add(prefix);
- }
-
- _listener.Start();
-
- Task.Factory.StartNew(Listen, TaskCreationOptions.LongRunning);
- }
-
- private bool IsListening
- {
- get { return _listener != null && _listener.IsListening; }
- }
-
- // Loop here to begin processing of new requests.
- private void Listen()
- {
- while (IsListening)
- {
- if (_listener == null) return;
- _listenForNextRequest.Reset();
-
- try
- {
- _listener.BeginGetContext(ListenerCallback, _listener);
- _listenForNextRequest.Wait();
- }
- catch (Exception ex)
- {
- _logger.Error("Listen()", ex);
- return;
- }
- if (_listener == null) return;
- }
- }
-
- // Handle the processing of a request in here.
- private void ListenerCallback(IAsyncResult asyncResult)
- {
- _listenForNextRequest.Set();
-
- var listener = asyncResult.AsyncState as HttpListener;
- HttpListenerContext context;
-
- if (listener == null) return;
- var isListening = listener.IsListening;
-
- try
- {
- if (!isListening)
- {
- _logger.Debug("Ignoring ListenerCallback() as HttpListener is no longer listening"); return;
- }
- // The EndGetContext() method, as with all Begin/End asynchronous methods in the .NET Framework,
- // blocks until there is a request to be processed or some type of data is available.
- context = listener.EndGetContext(asyncResult);
- }
- catch (Exception ex)
- {
- // You will get an exception when httpListener.Stop() is called
- // because there will be a thread stopped waiting on the .EndGetContext()
- // method, and again, that is just the way most Begin/End asynchronous
- // methods of the .NET Framework work.
- var errMsg = ex + ": " + IsListening;
- _logger.Warn(errMsg);
- return;
- }
-
- Task.Factory.StartNew(() => InitTask(context));
- }
-
- private void InitTask(HttpListenerContext context)
- {
- try
- {
- var task = this.ProcessRequestAsync(context);
- task.ContinueWith(x => HandleError(x.Exception, context), TaskContinuationOptions.OnlyOnFaulted | TaskContinuationOptions.AttachedToParent);
-
- if (task.Status == TaskStatus.Created)
- {
- task.RunSynchronously();
- }
- }
- catch (Exception ex)
- {
- HandleError(ex, context);
- }
- }
-
- private Task ProcessRequestAsync(HttpListenerContext context)
- {
- var request = context.Request;
-
- LogHttpRequest(request);
-
- if (request.IsWebSocketRequest)
- {
- return ProcessWebSocketRequest(context);
- }
-
- if (string.IsNullOrEmpty(context.Request.RawUrl))
- return ((object)null).AsTaskResult();
-
- var operationName = context.Request.GetOperationName();
-
- var httpReq = GetRequest(context, operationName);
-
- return RequestHandler(httpReq, request.Url);
- }
-
- /// <summary>
- /// Processes the web socket request.
- /// </summary>
- /// <param name="ctx">The CTX.</param>
- /// <returns>Task.</returns>
- private async Task ProcessWebSocketRequest(HttpListenerContext ctx)
- {
-#if !__MonoCS__
- try
- {
- var webSocketContext = await ctx.AcceptWebSocketAsync(null).ConfigureAwait(false);
-
- if (WebSocketHandler != null)
- {
- WebSocketHandler(new WebSocketConnectEventArgs
- {
- WebSocket = new NativeWebSocket(webSocketContext.WebSocket, _logger),
- Endpoint = ctx.Request.RemoteEndPoint.ToString()
- });
- }
- }
- catch (Exception ex)
- {
- _logger.ErrorException("AcceptWebSocketAsync error", ex);
- ctx.Response.StatusCode = 500;
- ctx.Response.Close();
- }
-#endif
- }
-
- private void HandleError(Exception ex, HttpListenerContext context)
- {
- var operationName = context.Request.GetOperationName();
- var httpReq = GetRequest(context, operationName);
-
- if (ErrorHandler != null)
- {
- ErrorHandler(ex, httpReq);
- }
- }
-
- private static ListenerRequest GetRequest(HttpListenerContext httpContext, string operationName)
- {
- var req = new ListenerRequest(httpContext, operationName, RequestAttributes.None);
- req.RequestAttributes = req.GetAttributes();
-
- return req;
- }
-
- /// <summary>
- /// Logs the HTTP request.
- /// </summary>
- /// <param name="request">The request.</param>
- private void LogHttpRequest(HttpListenerRequest request)
- {
- var endpoint = request.LocalEndPoint;
-
- if (endpoint != null)
- {
- var address = endpoint.ToString();
-
- _endpointListener(address);
- }
-
- LogRequest(_logger, request);
- }
-
- /// <summary>
- /// Logs the request.
- /// </summary>
- /// <param name="logger">The logger.</param>
- /// <param name="request">The request.</param>
- private static void LogRequest(ILogger logger, HttpListenerRequest request)
- {
- var log = new StringBuilder();
-
- var logHeaders = true;
-
- if (logHeaders)
- {
- var headers = string.Join(",", request.Headers.AllKeys.Where(i => !string.Equals(i, "cookie", StringComparison.OrdinalIgnoreCase) && !string.Equals(i, "Referer", StringComparison.OrdinalIgnoreCase)).Select(k => k + "=" + request.Headers[k]));
-
- log.AppendLine("Ip: " + request.RemoteEndPoint + ". Headers: " + headers);
- }
-
- var type = request.IsWebSocketRequest ? "Web Socket" : "HTTP " + request.HttpMethod;
-
- logger.LogMultiline(type + " " + request.Url, LogSeverity.Debug, log);
- }
-
- public void Stop()
- {
- if (_listener != null)
- {
- foreach (var prefix in UrlPrefixes)
- {
- _listener.Prefixes.Remove(prefix);
- }
-
- _listener.Close();
- }
- }
-
- public void Dispose()
- {
- Dispose(true);
- }
-
- private bool _disposed;
- private readonly object _disposeLock = new object();
- protected virtual void Dispose(bool disposing)
- {
- if (_disposed) return;
-
- lock (_disposeLock)
- {
- if (_disposed) return;
-
- if (disposing)
- {
- Stop();
- }
-
- //release unmanaged resources here...
- _disposed = true;
- }
- }
- }
-} \ No newline at end of file
diff --git a/MediaBrowser.Server.Implementations/HttpServer/RangeRequestWriter.cs b/MediaBrowser.Server.Implementations/HttpServer/RangeRequestWriter.cs
index 020856886..7ac92408b 100644
--- a/MediaBrowser.Server.Implementations/HttpServer/RangeRequestWriter.cs
+++ b/MediaBrowser.Server.Implementations/HttpServer/RangeRequestWriter.cs
@@ -5,10 +5,12 @@ using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Net;
+using System.Threading.Tasks;
+using ServiceStack;
namespace MediaBrowser.Server.Implementations.HttpServer
{
- public class RangeRequestWriter : IStreamWriter, IHttpResult
+ public class RangeRequestWriter : IStreamWriter, IAsyncStreamWriter, IHttpResult
{
/// <summary>
/// Gets or sets the source stream.
@@ -39,6 +41,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 +86,7 @@ namespace MediaBrowser.Server.Implementations.HttpServer
Options["Accept-Ranges"] = "bytes";
StatusCode = HttpStatusCode.PartialContent;
+ Cookies = new List<Cookie>();
SetRangeValues();
}
@@ -165,16 +171,6 @@ namespace MediaBrowser.Server.Implementations.HttpServer
/// <param name="responseStream">The response stream.</param>
public void WriteTo(Stream responseStream)
{
- WriteToInternal(responseStream);
- }
-
- /// <summary>
- /// Writes to async.
- /// </summary>
- /// <param name="responseStream">The response stream.</param>
- /// <returns>Task.</returns>
- private void WriteToInternal(Stream responseStream)
- {
try
{
// Headers only
@@ -233,6 +229,66 @@ namespace MediaBrowser.Server.Implementations.HttpServer
}
}
+ public async Task WriteToAsync(Stream responseStream)
+ {
+ try
+ {
+ // Headers only
+ if (IsHeadRequest)
+ {
+ return;
+ }
+
+ using (var source = SourceStream)
+ {
+ // If the requested range is "0-", we can optimize by just doing a stream copy
+ if (RangeEnd >= TotalContentLength - 1)
+ {
+ await source.CopyToAsync(responseStream, BufferSize).ConfigureAwait(false);
+ }
+ else
+ {
+ await CopyToInternalAsync(source, responseStream, RangeLength).ConfigureAwait(false);
+ }
+ }
+ }
+ catch (IOException ex)
+ {
+ throw;
+ }
+ catch (Exception ex)
+ {
+ _logger.ErrorException("Error in range request writer", ex);
+ throw;
+ }
+ finally
+ {
+ if (OnComplete != null)
+ {
+ OnComplete();
+ }
+ }
+ }
+
+ private async Task CopyToInternalAsync(Stream source, Stream destination, long copyLength)
+ {
+ var array = new byte[BufferSize];
+ int count;
+ while ((count = await source.ReadAsync(array, 0, array.Length).ConfigureAwait(false)) != 0)
+ {
+ var bytesToCopy = Math.Min(count, copyLength);
+
+ await destination.WriteAsync(array, 0, Convert.ToInt32(bytesToCopy)).ConfigureAwait(false);
+
+ copyLength -= bytesToCopy;
+
+ if (copyLength <= 0)
+ {
+ break;
+ }
+ }
+ }
+
public string ContentType { get; set; }
public IRequest RequestContext { get; set; }
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..bfa65ac6b 100644
--- a/MediaBrowser.Server.Implementations/HttpServer/SocketSharp/RequestMono.cs
+++ b/MediaBrowser.Server.Implementations/HttpServer/SocketSharp/RequestMono.cs
@@ -3,7 +3,9 @@ using System.Collections.Specialized;
using System.Globalization;
using System.IO;
using System.Text;
+using System.Threading.Tasks;
using System.Web;
+using ServiceStack;
using ServiceStack.Web;
namespace MediaBrowser.Server.Implementations.HttpServer.SocketSharp
@@ -31,53 +33,54 @@ namespace MediaBrowser.Server.Implementations.HttpServer.SocketSharp
return header.Substring(ap + 1, end - ap - 1);
}
- void LoadMultiPart()
+ async Task LoadMultiPart()
{
string boundary = GetParameter(ContentType, "; boundary=");
if (boundary == null)
return;
- var input = GetSubStream(InputStream);
+ using (var requestStream = GetSubStream(InputStream))
+ {
+ //DB: 30/01/11 - Hack to get around non-seekable stream and received HTTP request
+ //Not ending with \r\n?
+ var ms = new MemoryStream(32 * 1024);
+ await requestStream.CopyToAsync(ms).ConfigureAwait(false);
- //DB: 30/01/11 - Hack to get around non-seekable stream and received HTTP request
- //Not ending with \r\n?
- var ms = new MemoryStream(32 * 1024);
- input.CopyTo(ms);
- input = ms;
- ms.WriteByte((byte)'\r');
- ms.WriteByte((byte)'\n');
+ var input = ms;
+ ms.WriteByte((byte)'\r');
+ ms.WriteByte((byte)'\n');
- input.Position = 0;
+ input.Position = 0;
- //Uncomment to debug
- //var content = new StreamReader(ms).ReadToEnd();
- //Console.WriteLine(boundary + "::" + content);
- //input.Position = 0;
+ //Uncomment to debug
+ //var content = new StreamReader(ms).ReadToEnd();
+ //Console.WriteLine(boundary + "::" + content);
+ //input.Position = 0;
- var multi_part = new HttpMultipart(input, boundary, ContentEncoding);
+ var multi_part = new HttpMultipart(input, boundary, ContentEncoding);
- HttpMultipart.Element e;
- while ((e = multi_part.ReadNextElement()) != null)
- {
- if (e.Filename == null)
+ HttpMultipart.Element e;
+ while ((e = multi_part.ReadNextElement()) != null)
{
- byte[] copy = new byte[e.Length];
+ if (e.Filename == null)
+ {
+ byte[] copy = new byte[e.Length];
- input.Position = e.Start;
- input.Read(copy, 0, (int)e.Length);
+ input.Position = e.Start;
+ input.Read(copy, 0, (int)e.Length);
- form.Add(e.Name, (e.Encoding ?? ContentEncoding).GetString(copy));
- }
- else
- {
- //
- // We use a substream, as in 2.x we will support large uploads streamed to disk,
- //
- HttpPostedFile sub = new HttpPostedFile(e.Filename, e.ContentType, input, e.Start, e.Length);
- files.AddFile(e.Name, sub);
+ form.Add(e.Name, (e.Encoding ?? ContentEncoding).GetString(copy));
+ }
+ else
+ {
+ //
+ // We use a substream, as in 2.x we will support large uploads streamed to disk,
+ //
+ HttpPostedFile sub = new HttpPostedFile(e.Filename, e.ContentType, input, e.Start, e.Length);
+ files.AddFile(e.Name, sub);
+ }
}
}
- EndSubStream(input);
}
public NameValueCollection Form
@@ -90,10 +93,15 @@ namespace MediaBrowser.Server.Implementations.HttpServer.SocketSharp
files = new HttpFileCollection();
if (IsContentType("multipart/form-data", true))
- LoadMultiPart();
- else if (
- IsContentType("application/x-www-form-urlencoded", true))
- LoadWwwForm();
+ {
+ var task = LoadMultiPart();
+ Task.WaitAll(task);
+ }
+ else if (IsContentType("application/x-www-form-urlencoded", true))
+ {
+ var task = LoadWwwForm();
+ Task.WaitAll(task);
+ }
form.Protect();
}
@@ -116,6 +124,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;
@@ -204,50 +227,50 @@ namespace MediaBrowser.Server.Implementations.HttpServer.SocketSharp
return String.Compare(ContentType, ct, true, Helpers.InvariantCulture) == 0;
}
-
-
-
-
- void LoadWwwForm()
+ async Task LoadWwwForm()
{
using (Stream input = GetSubStream(InputStream))
{
- using (StreamReader s = new StreamReader(input, ContentEncoding))
+ using (var ms = new MemoryStream())
{
- StringBuilder key = new StringBuilder();
- StringBuilder value = new StringBuilder();
- int c;
+ await input.CopyToAsync(ms).ConfigureAwait(false);
+ ms.Position = 0;
- while ((c = s.Read()) != -1)
+ using (StreamReader s = new StreamReader(ms, ContentEncoding))
{
- if (c == '=')
+ StringBuilder key = new StringBuilder();
+ StringBuilder value = new StringBuilder();
+ int c;
+
+ while ((c = s.Read()) != -1)
{
- value.Length = 0;
- while ((c = s.Read()) != -1)
+ if (c == '=')
{
- if (c == '&')
+ value.Length = 0;
+ while ((c = s.Read()) != -1)
+ {
+ if (c == '&')
+ {
+ AddRawKeyValue(key, value);
+ break;
+ }
+ else
+ value.Append((char)c);
+ }
+ if (c == -1)
{
AddRawKeyValue(key, value);
- break;
+ return;
}
- else
- value.Append((char)c);
}
- if (c == -1)
- {
+ else if (c == '&')
AddRawKeyValue(key, value);
- return;
- }
+ else
+ key.Append((char)c);
}
- else if (c == '&')
+ if (c == -1)
AddRawKeyValue(key, value);
- else
- key.Append((char)c);
}
- if (c == -1)
- AddRawKeyValue(key, value);
-
- EndSubStream(input);
}
}
}
diff --git a/MediaBrowser.Server.Implementations/HttpServer/SocketSharp/WebSocketSharpRequest.cs b/MediaBrowser.Server.Implementations/HttpServer/SocketSharp/WebSocketSharpRequest.cs
index 30849d441..dc2aec3e1 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);
}
@@ -134,12 +134,89 @@ namespace MediaBrowser.Server.Implementations.HttpServer.SocketSharp
get
{
return remoteIp ??
- (remoteIp = XForwardedFor ??
- (NormalizeIp(XRealIp) ??
+ (remoteIp = (CheckBadChars(XForwardedFor)) ??
+ (NormalizeIp(CheckBadChars(XRealIp)) ??
(request.RemoteEndPoint != null ? NormalizeIp(request.RemoteEndPoint.Address.ToString()) : null)));
}
}
+ private static readonly char[] HttpTrimCharacters = new char[] { (char)0x09, (char)0xA, (char)0xB, (char)0xC, (char)0xD, (char)0x20 };
+
+ //
+ // CheckBadChars - throws on invalid chars to be not found in header name/value
+ //
+ internal static string CheckBadChars(string name)
+ {
+ if (name == null || name.Length == 0)
+ {
+ return name;
+ }
+
+ // VALUE check
+ //Trim spaces from both ends
+ name = name.Trim(HttpTrimCharacters);
+
+ //First, check for correctly formed multi-line value
+ //Second, check for absenece of CTL characters
+ int crlf = 0;
+ for (int i = 0; i < name.Length; ++i)
+ {
+ char c = (char)(0x000000ff & (uint)name[i]);
+ switch (crlf)
+ {
+ case 0:
+ if (c == '\r')
+ {
+ crlf = 1;
+ }
+ else if (c == '\n')
+ {
+ // Technically this is bad HTTP. But it would be a breaking change to throw here.
+ // Is there an exploit?
+ crlf = 2;
+ }
+ else if (c == 127 || (c < ' ' && c != '\t'))
+ {
+ throw new ArgumentException("net_WebHeaderInvalidControlChars");
+ }
+ break;
+
+ case 1:
+ if (c == '\n')
+ {
+ crlf = 2;
+ break;
+ }
+ throw new ArgumentException("net_WebHeaderInvalidCRLFChars");
+
+ case 2:
+ if (c == ' ' || c == '\t')
+ {
+ crlf = 0;
+ break;
+ }
+ throw new ArgumentException("net_WebHeaderInvalidCRLFChars");
+ }
+ }
+ if (crlf != 0)
+ {
+ throw new ArgumentException("net_WebHeaderInvalidCRLFChars");
+ }
+ return name;
+ }
+
+ internal static bool ContainsNonAsciiChars(string token)
+ {
+ for (int i = 0; i < token.Length; ++i)
+ {
+ if ((token[i] < 0x20) || (token[i] > 0x7e))
+ {
+ return true;
+ }
+ }
+ return false;
+ }
+
private string NormalizeIp(string ip)
{
if (!string.IsNullOrWhiteSpace(ip))
@@ -388,10 +465,6 @@ namespace MediaBrowser.Server.Implementations.HttpServer.SocketSharp
return stream;
}
- static void EndSubStream(Stream stream)
- {
- }
-
public static string GetHandlerPathIfAny(string listenerUrl)
{
if (listenerUrl == null) return null;
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/StreamWriter.cs b/MediaBrowser.Server.Implementations/HttpServer/StreamWriter.cs
index a756f4aa8..f5906f6b7 100644
--- a/MediaBrowser.Server.Implementations/HttpServer/StreamWriter.cs
+++ b/MediaBrowser.Server.Implementations/HttpServer/StreamWriter.cs
@@ -4,13 +4,15 @@ using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
+using System.Threading.Tasks;
+using ServiceStack;
namespace MediaBrowser.Server.Implementations.HttpServer
{
/// <summary>
/// Class StreamWriter
/// </summary>
- public class StreamWriter : IStreamWriter, IHasOptions
+ public class StreamWriter : IStreamWriter, IAsyncStreamWriter, IHasOptions
{
private ILogger Logger { get; set; }
@@ -73,30 +75,49 @@ namespace MediaBrowser.Server.Implementations.HttpServer
{
}
+ // 256k
+ private const int BufferSize = 262144;
+
/// <summary>
/// Writes to.
/// </summary>
/// <param name="responseStream">The response stream.</param>
public void WriteTo(Stream responseStream)
{
- WriteToInternal(responseStream);
+ try
+ {
+ using (var src = SourceStream)
+ {
+ src.CopyTo(responseStream, BufferSize);
+ }
+ }
+ catch (Exception ex)
+ {
+ Logger.ErrorException("Error streaming data", ex);
+
+ if (OnError != null)
+ {
+ OnError();
+ }
+
+ throw;
+ }
+ finally
+ {
+ if (OnComplete != null)
+ {
+ OnComplete();
+ }
+ }
}
- // 256k
- private const int BufferSize = 262144;
-
- /// <summary>
- /// Writes to async.
- /// </summary>
- /// <param name="responseStream">The response stream.</param>
- /// <returns>Task.</returns>
- private void WriteToInternal(Stream responseStream)
+ public async Task WriteToAsync(Stream responseStream)
{
try
{
using (var src = SourceStream)
{
- src.CopyTo(responseStream, BufferSize);
+ await src.CopyToAsync(responseStream, BufferSize).ConfigureAwait(false);
}
}
catch (Exception ex)
@@ -107,7 +128,7 @@ namespace MediaBrowser.Server.Implementations.HttpServer
{
OnError();
}
-
+
throw;
}
finally
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..f48beacb5
--- /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;
+
+ _fileSystem = fileSystem;
+ ConfigurationManager = configurationManager;
+ LibraryManager = libraryManager;
+ TaskManager = taskManager;
+ Logger = logger;
+ AddPath(path);
+ }
+
+ 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 2c0257c5f..99cb80cb2 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>
@@ -93,7 +84,7 @@ namespace MediaBrowser.Server.Implementations.IO
// This is an arbitraty amount of time, but delay it because file system writes often trigger events long after the file was actually written to.
// Seeing long delays in some situations, especially over the network, sometimes up to 45 seconds
// But if we make this delay too high, we risk missing legitimate changes, such as user adding a new file, or hand-editing metadata
- await Task.Delay(25000).ConfigureAwait(false);
+ await Task.Delay(45000).ConfigureAwait(false);
string val;
_tempIgnoredPaths.TryRemove(path, out val);
@@ -251,7 +242,7 @@ namespace MediaBrowser.Server.Implementations.IO
/// <exception cref="System.ArgumentNullException">path</exception>
private static bool ContainsParentFolder(IEnumerable<string> lst, string path)
{
- if (string.IsNullOrEmpty(path))
+ if (string.IsNullOrWhiteSpace(path))
{
throw new ArgumentNullException("path");
}
@@ -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);
+ CreateRefresher(path);
}
-
- RestartTimer();
}
- private void RestartTimer()
+ private void CreateRefresher(string path)
{
- 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));
- }
- }
- }
+ var parentPath = Path.GetDirectoryName(path);
- /// <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)))
+ lock (_activeRefreshers)
{
- 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);
- }
- }
-
- 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))
+ 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);
-
- 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 28671fb7c..712ea4ef3 100644
--- a/MediaBrowser.Server.Implementations/Library/LibraryManager.cs
+++ b/MediaBrowser.Server.Implementations/Library/LibraryManager.cs
@@ -33,6 +33,9 @@ using System.Net;
using System.Threading;
using System.Threading.Tasks;
using CommonIO;
+using MediaBrowser.Controller.Channels;
+using MediaBrowser.Model.Channels;
+using MediaBrowser.Model.Dto;
using MediaBrowser.Model.Extensions;
using MediaBrowser.Model.Library;
using MediaBrowser.Model.Net;
@@ -143,6 +146,7 @@ namespace MediaBrowser.Server.Implementations.Library
private readonly Func<ILibraryMonitor> _libraryMonitorFactory;
private readonly Func<IProviderManager> _providerManagerFactory;
private readonly Func<IUserViewManager> _userviewManager;
+ public bool IsScanRunning { get; private set; }
/// <summary>
/// The _library items cache
@@ -305,9 +309,14 @@ namespace MediaBrowser.Server.Implementations.Library
/// <returns>Task.</returns>
private async Task UpdateSeasonZeroNames(string newName, CancellationToken cancellationToken)
{
- var seasons = RootFolder.GetRecursiveChildren(i => i is Season)
- .Cast<Season>()
- .Where(i => i.IndexNumber.HasValue && i.IndexNumber.Value == 0 && !string.Equals(i.Name, newName, StringComparison.Ordinal))
+ var seasons = GetItemList(new InternalItemsQuery
+ {
+ IncludeItemTypes = new[] { typeof(Season).Name },
+ Recursive = true,
+ IndexNumber = 0
+
+ }).Cast<Season>()
+ .Where(i => !string.Equals(i.Name, newName, StringComparison.Ordinal))
.ToList();
foreach (var season in seasons)
@@ -345,10 +354,6 @@ namespace MediaBrowser.Server.Implementations.Library
private void RegisterItem(Guid id, BaseItem item)
{
- if (item.SourceType != SourceType.Library)
- {
- return;
- }
if (item is IItemByName)
{
if (!(item is MusicArtist))
@@ -356,10 +361,25 @@ namespace MediaBrowser.Server.Implementations.Library
return;
}
}
- //if (!(item is Folder))
- //{
- // return;
- //}
+
+ if (item.IsFolder)
+ {
+ if (!(item is ICollectionFolder) && !(item is UserView) && !(item is Channel))
+ {
+ if (item.SourceType != SourceType.Library)
+ {
+ return;
+ }
+ }
+ }
+ else
+ {
+ if (item is Photo)
+ {
+ return;
+ }
+ }
+
LibraryItemsCache.AddOrUpdate(id, item, delegate { return item; });
}
@@ -514,38 +534,6 @@ namespace MediaBrowser.Server.Implementations.Library
return key.GetMD5();
}
- public IEnumerable<BaseItem> ReplaceVideosWithPrimaryVersions(IEnumerable<BaseItem> items)
- {
- if (items == null)
- {
- throw new ArgumentNullException("items");
- }
-
- var dict = new Dictionary<Guid, BaseItem>();
-
- foreach (var item in items)
- {
- var video = item as Video;
-
- if (video != null)
- {
- if (video.PrimaryVersionId.HasValue)
- {
- var primary = GetItemById(video.PrimaryVersionId.Value) as Video;
-
- if (primary != null)
- {
- dict[primary.Id] = primary;
- continue;
- }
- }
- }
- dict[item.Id] = item;
- }
-
- return dict.Values;
- }
-
/// <summary>
/// Ensure supplied item has only one instance throughout
/// </summary>
@@ -800,27 +788,22 @@ namespace MediaBrowser.Server.Implementations.Library
return _userRootFolder;
}
- public BaseItem FindByPath(string path)
+ public BaseItem FindByPath(string path, bool? isFolder)
{
+ // If this returns multiple items it could be tricky figuring out which one is correct.
+ // In most cases, the newest one will be and the others obsolete but not yet cleaned up
+
var query = new InternalItemsQuery
{
- Path = path
+ Path = path,
+ IsFolder = isFolder,
+ SortBy = new[] { ItemSortBy.DateCreated },
+ SortOrder = SortOrder.Descending,
+ Limit = 1
};
- // Only use the database result if there's exactly one item, otherwise we run the risk of returning old data that hasn't been cleaned yet.
- var items = GetItemIds(query).Select(GetItemById).Where(i => i != null).ToArray();
-
- if (items.Length == 1)
- {
- return items[0];
- }
-
- if (items.Length == 0)
- {
- return null;
- }
-
- return RootFolder.FindByPath(path);
+ return GetItemList(query)
+ .FirstOrDefault();
}
/// <summary>
@@ -929,7 +912,10 @@ namespace MediaBrowser.Server.Implementations.Library
throw new ArgumentNullException("name");
}
- var validFilename = _fileSystem.GetValidFilename(name).Trim();
+ // Trim the period at the end because windows will have a hard time with that
+ var validFilename = _fileSystem.GetValidFilename(name)
+ .Trim()
+ .TrimEnd('.');
string subFolderPrefix = null;
@@ -951,31 +937,23 @@ namespace MediaBrowser.Server.Implementations.Library
Path.Combine(path, validFilename) :
Path.Combine(path, subFolderPrefix, validFilename);
- var id = GetNewItemId(fullPath, type);
-
- BaseItem obj;
-
- if (!_libraryItemsCache.TryGetValue(id, out obj))
- {
- obj = CreateItemByName<T>(fullPath, name, id);
-
- RegisterItem(id, obj);
- }
-
- return obj as T;
+ return CreateItemByName<T>(fullPath, name);
}
- private T CreateItemByName<T>(string path, string name, Guid id)
+ 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 = RootFolder
- .GetRecursiveChildren(i => i is T && NameExtensions.AreEqual(i.Name, name))
- .Cast<T>()
- .FirstOrDefault();
+ var existing = GetItemList(new InternalItemsQuery
+ {
+ IncludeItemTypes = new[] { typeof(T).Name },
+ Name = name
+
+ }).Cast<MusicArtist>()
+ .OrderBy(i => i.IsAccessedByName ? 1 : 0)
+ .Cast<T>()
+ .FirstOrDefault();
if (existing != null)
{
@@ -983,6 +961,8 @@ namespace MediaBrowser.Server.Implementations.Library
}
}
+ var id = GetNewItemId(path, typeof(T));
+
var item = GetItemById(id) as T;
if (item == null)
@@ -996,11 +976,6 @@ namespace MediaBrowser.Server.Implementations.Library
Path = path
};
- if (isArtist)
- {
- (item as MusicArtist).IsAccessedByName = true;
- }
-
var task = CreateItem(item, CancellationToken.None);
Task.WaitAll(task);
}
@@ -1102,6 +1077,7 @@ namespace MediaBrowser.Server.Implementations.Library
/// <returns>Task.</returns>
public async Task ValidateMediaLibraryInternal(IProgress<double> progress, CancellationToken cancellationToken)
{
+ IsScanRunning = true;
_libraryMonitorFactory().Stop();
try
@@ -1111,6 +1087,7 @@ namespace MediaBrowser.Server.Implementations.Library
finally
{
_libraryMonitorFactory().Start();
+ IsScanRunning = false;
}
}
@@ -1289,6 +1266,8 @@ namespace MediaBrowser.Server.Implementations.Library
item = RetrieveItem(id);
+ //_logger.Debug("GetitemById {0}", id);
+
if (item != null)
{
RegisterItem(item);
@@ -1297,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);
+ }
- return GetItemIds(query).Select(GetItemById).Where(i => i != null);
+ 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[] { };
+ }
+ }
+
+ 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)
@@ -1369,24 +1451,17 @@ namespace MediaBrowser.Server.Implementations.Library
AddUserToQuery(query, query.User);
}
- var initialResult = ItemRepository.GetItemIds(query);
+ if (query.EnableTotalRecordCount)
+ {
+ return ItemRepository.GetItems(query);
+ }
return new QueryResult<BaseItem>
{
- TotalRecordCount = initialResult.TotalRecordCount,
- Items = initialResult.Items.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 =>
@@ -1396,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;
}))
@@ -1413,7 +1488,7 @@ namespace MediaBrowser.Server.Implementations.Library
private void AddUserToQuery(InternalItemsQuery query, User user)
{
- if (query.AncestorIds.Length == 0 && !query.ParentId.HasValue && query.ChannelIds.Length == 0 && query.TopParentIds.Length == 0)
+ if (query.AncestorIds.Length == 0 && !query.ParentId.HasValue && query.ChannelIds.Length == 0 && query.TopParentIds.Length == 0 && string.IsNullOrWhiteSpace(query.AncestorWithPresentationUniqueKey))
{
var userViews = _userviewManager().GetUserViews(new UserViewQuery
{
@@ -1438,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
@@ -1465,8 +1545,12 @@ namespace MediaBrowser.Server.Implementations.Library
// Handle grouping
if (user != null && !string.IsNullOrWhiteSpace(view.ViewType) && UserView.IsEligibleForGrouping(view.ViewType))
{
- var collectionFolders = user.RootFolder.GetChildren(user, true).OfType<CollectionFolder>().Where(i => string.IsNullOrWhiteSpace(i.CollectionType) || string.Equals(i.CollectionType, view.ViewType, StringComparison.OrdinalIgnoreCase));
- return collectionFolders.SelectMany(i => GetTopParentsForQuery(i, user));
+ return user.RootFolder
+ .GetChildren(user, true)
+ .OfType<CollectionFolder>()
+ .Where(i => string.IsNullOrWhiteSpace(i.CollectionType) || string.Equals(i.CollectionType, view.ViewType, StringComparison.OrdinalIgnoreCase))
+ .Where(i => user.IsFolderGrouped(i.Id))
+ .SelectMany(i => GetTopParentsForQuery(i, user));
}
return new BaseItem[] { };
}
@@ -1847,7 +1931,7 @@ namespace MediaBrowser.Server.Implementations.Library
private string GetContentTypeOverride(string path, bool inherit)
{
- var nameValuePair = ConfigurationManager.Configuration.ContentTypes.FirstOrDefault(i => string.Equals(i.Name, path, StringComparison.OrdinalIgnoreCase) || (inherit && _fileSystem.ContainsSubPath(i.Name, path)));
+ var nameValuePair = ConfigurationManager.Configuration.ContentTypes.FirstOrDefault(i => string.Equals(i.Name, path, StringComparison.OrdinalIgnoreCase) || (inherit && !string.IsNullOrWhiteSpace(i.Name) && _fileSystem.ContainsSubPath(i.Name, path)));
if (nameValuePair != null)
{
return nameValuePair.Value;
@@ -2322,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();
@@ -2361,6 +2445,7 @@ namespace MediaBrowser.Server.Implementations.Library
}
video.ExtraType = ExtraType.Trailer;
+ video.TrailerTypes = new List<TrailerType> { TrailerType.LocalTrailer };
return video;
@@ -2575,5 +2660,205 @@ namespace MediaBrowser.Server.Implementations.Library
throw new InvalidOperationException();
}
+
+ public void AddVirtualFolder(string name, string collectionType, string[] mediaPaths, bool refreshLibrary)
+ {
+ if (string.IsNullOrWhiteSpace(name))
+ {
+ throw new ArgumentNullException("name");
+ }
+
+ name = _fileSystem.GetValidFilename(name);
+
+ var rootFolderPath = ConfigurationManager.ApplicationPaths.DefaultUserViewsPath;
+
+ var virtualFolderPath = Path.Combine(rootFolderPath, name);
+ while (_fileSystem.DirectoryExists(virtualFolderPath))
+ {
+ name += "1";
+ virtualFolderPath = Path.Combine(rootFolderPath, name);
+ }
+
+ if (mediaPaths != null)
+ {
+ var invalidpath = mediaPaths.FirstOrDefault(i => !_fileSystem.DirectoryExists(i));
+ if (invalidpath != null)
+ {
+ throw new ArgumentException("The specified path does not exist: " + invalidpath + ".");
+ }
+ }
+
+ _libraryMonitorFactory().Stop();
+
+ try
+ {
+ _fileSystem.CreateDirectory(virtualFolderPath);
+
+ if (!string.IsNullOrEmpty(collectionType))
+ {
+ var path = Path.Combine(virtualFolderPath, collectionType + ".collection");
+
+ using (File.Create(path))
+ {
+
+ }
+ }
+
+ if (mediaPaths != null)
+ {
+ foreach (var path in mediaPaths)
+ {
+ AddMediaPath(name, path);
+ }
+ }
+ }
+ finally
+ {
+ Task.Run(() =>
+ {
+ // No need to start if scanning the library because it will handle it
+ if (refreshLibrary)
+ {
+ ValidateMediaLibrary(new Progress<double>(), CancellationToken.None);
+ }
+ else
+ {
+ // Need to add a delay here or directory watchers may still pick up the changes
+ var task = Task.Delay(1000);
+ // Have to block here to allow exceptions to bubble
+ Task.WaitAll(task);
+
+ _libraryMonitorFactory().Start();
+ }
+ });
+ }
+ }
+
+ public void RemoveVirtualFolder(string name, bool refreshLibrary)
+ {
+ if (string.IsNullOrWhiteSpace(name))
+ {
+ throw new ArgumentNullException("name");
+ }
+
+ var rootFolderPath = ConfigurationManager.ApplicationPaths.DefaultUserViewsPath;
+
+ var path = Path.Combine(rootFolderPath, name);
+
+ if (!_fileSystem.DirectoryExists(path))
+ {
+ throw new DirectoryNotFoundException("The media folder does not exist");
+ }
+
+ _libraryMonitorFactory().Stop();
+
+ try
+ {
+ _fileSystem.DeleteDirectory(path, true);
+ }
+ finally
+ {
+ Task.Run(() =>
+ {
+ // No need to start if scanning the library because it will handle it
+ if (refreshLibrary)
+ {
+ ValidateMediaLibrary(new Progress<double>(), CancellationToken.None);
+ }
+ else
+ {
+ // Need to add a delay here or directory watchers may still pick up the changes
+ var task = Task.Delay(1000);
+ // Have to block here to allow exceptions to bubble
+ Task.WaitAll(task);
+
+ _libraryMonitorFactory().Start();
+ }
+ });
+ }
+ }
+
+ private const string ShortcutFileExtension = ".mblink";
+ private const string ShortcutFileSearch = "*" + ShortcutFileExtension;
+ public void AddMediaPath(string virtualFolderName, string path)
+ {
+ if (string.IsNullOrWhiteSpace(path))
+ {
+ throw new ArgumentNullException("path");
+ }
+
+ if (!_fileSystem.DirectoryExists(path))
+ {
+ throw new DirectoryNotFoundException("The path does not exist.");
+ }
+
+ var rootFolderPath = ConfigurationManager.ApplicationPaths.DefaultUserViewsPath;
+ var virtualFolderPath = Path.Combine(rootFolderPath, virtualFolderName);
+
+ var shortcutFilename = _fileSystem.GetFileNameWithoutExtension(path);
+
+ var lnk = Path.Combine(virtualFolderPath, shortcutFilename + ShortcutFileExtension);
+
+ while (_fileSystem.FileExists(lnk))
+ {
+ shortcutFilename += "1";
+ lnk = Path.Combine(virtualFolderPath, shortcutFilename + ShortcutFileExtension);
+ }
+
+ _fileSystem.CreateShortcut(lnk, path);
+
+ RemoveContentTypeOverrides(path);
+ }
+
+ private void RemoveContentTypeOverrides(string path)
+ {
+ if (string.IsNullOrWhiteSpace(path))
+ {
+ throw new ArgumentNullException("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)
+ {
+ if (string.IsNullOrWhiteSpace(mediaPath))
+ {
+ throw new ArgumentNullException("mediaPath");
+ }
+
+ var rootFolderPath = ConfigurationManager.ApplicationPaths.DefaultUserViewsPath;
+ var path = Path.Combine(rootFolderPath, virtualFolderName);
+
+ if (!_fileSystem.DirectoryExists(path))
+ {
+ throw new DirectoryNotFoundException(string.Format("The media collection {0} does not exist", virtualFolderName));
+ }
+
+ var shortcut = Directory.EnumerateFiles(path, ShortcutFileSearch, SearchOption.AllDirectories).FirstOrDefault(f => _fileSystem.ResolveShortcut(f).Equals(mediaPath, StringComparison.OrdinalIgnoreCase));
+
+ if (!string.IsNullOrEmpty(shortcut))
+ {
+ _fileSystem.DeleteFile(shortcut);
+ }
+ }
}
} \ No newline at end of file
diff --git a/MediaBrowser.Server.Implementations/Library/LocalTrailerPostScanTask.cs b/MediaBrowser.Server.Implementations/Library/LocalTrailerPostScanTask.cs
index 96d570ef9..78107b82d 100644
--- a/MediaBrowser.Server.Implementations/Library/LocalTrailerPostScanTask.cs
+++ b/MediaBrowser.Server.Implementations/Library/LocalTrailerPostScanTask.cs
@@ -6,6 +6,8 @@ using System;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
+using MediaBrowser.Controller.Entities.Movies;
+using MediaBrowser.Controller.Entities.TV;
namespace MediaBrowser.Server.Implementations.Library
{
@@ -22,18 +24,24 @@ namespace MediaBrowser.Server.Implementations.Library
public async Task Run(IProgress<double> progress, CancellationToken cancellationToken)
{
- var items = _libraryManager.RootFolder
- .GetRecursiveChildren(i => i is IHasTrailers)
- .Cast<IHasTrailers>()
- .ToList();
+ var items = _libraryManager.GetItemList(new InternalItemsQuery
+ {
+ IncludeItemTypes = new[] { typeof(BoxSet).Name, typeof(Game).Name, typeof(Movie).Name, typeof(Series).Name },
+ Recursive = true
+
+ }).OfType<IHasTrailers>().ToList();
+
+ var trailerTypes = Enum.GetNames(typeof(TrailerType))
+ .Select(i => (TrailerType)Enum.Parse(typeof(TrailerType), i, true))
+ .Except(new[] { TrailerType.LocalTrailer })
+ .ToArray();
var trailers = _libraryManager.GetItemList(new InternalItemsQuery
{
IncludeItemTypes = new[] { typeof(Trailer).Name },
- ExcludeTrailerTypes = new[]
- {
- TrailerType.LocalTrailer
- }
+ TrailerTypes = trailerTypes,
+ Recursive = true
+
}).ToArray();
var numComplete = 0;
diff --git a/MediaBrowser.Server.Implementations/Library/MediaSourceManager.cs b/MediaBrowser.Server.Implementations/Library/MediaSourceManager.cs
index 95f5cb0e1..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;
}
@@ -175,13 +171,6 @@ namespace MediaBrowser.Server.Implementations.Library
source.SupportsTranscoding = false;
}
}
- else if (string.Equals(item.MediaType, MediaType.Video, StringComparison.OrdinalIgnoreCase))
- {
- if (!user.Policy.EnableVideoPlaybackTranscoding)
- {
- source.SupportsTranscoding = false;
- }
- }
}
}
@@ -267,15 +256,17 @@ namespace MediaBrowser.Server.Implementations.Library
private void SetUserProperties(IHasUserData item, MediaSourceInfo source, User user)
{
- var userData = item == null ? new UserItemData() : _userDataManager.GetUserData(user.Id, item.GetUserDataKey());
+ var userData = item == null ? new UserItemData() : _userDataManager.GetUserData(user, item);
+
+ var allowRememberingSelection = item == null || item.EnableRememberingTrackSelections;
- SetDefaultAudioStreamIndex(source, userData, user);
- SetDefaultSubtitleStreamIndex(source, userData, user);
+ SetDefaultAudioStreamIndex(source, userData, user, allowRememberingSelection);
+ SetDefaultSubtitleStreamIndex(source, userData, user, allowRememberingSelection);
}
- private void SetDefaultSubtitleStreamIndex(MediaSourceInfo source, UserItemData userData, User user)
+ private void SetDefaultSubtitleStreamIndex(MediaSourceInfo source, UserItemData userData, User user, bool allowRememberingSelection)
{
- if (userData.SubtitleStreamIndex.HasValue && user.Configuration.RememberSubtitleSelections && user.Configuration.SubtitleMode != SubtitlePlaybackMode.None)
+ if (userData.SubtitleStreamIndex.HasValue && user.Configuration.RememberSubtitleSelections && user.Configuration.SubtitleMode != SubtitlePlaybackMode.None && allowRememberingSelection)
{
var index = userData.SubtitleStreamIndex.Value;
// Make sure the saved index is still valid
@@ -304,9 +295,9 @@ namespace MediaBrowser.Server.Implementations.Library
user.Configuration.SubtitleMode, audioLangage);
}
- private void SetDefaultAudioStreamIndex(MediaSourceInfo source, UserItemData userData, User user)
+ private void SetDefaultAudioStreamIndex(MediaSourceInfo source, UserItemData userData, User user, bool allowRememberingSelection)
{
- if (userData.AudioStreamIndex.HasValue && user.Configuration.RememberAudioSelections)
+ if (userData.AudioStreamIndex.HasValue && user.Configuration.RememberAudioSelections && allowRememberingSelection)
{
var index = userData.AudioStreamIndex.Value;
// Make sure the saved index is still valid
diff --git a/MediaBrowser.Server.Implementations/Library/MusicManager.cs b/MediaBrowser.Server.Implementations/Library/MusicManager.cs
index aad7c112b..3ff434898 100644
--- a/MediaBrowser.Server.Implementations/Library/MusicManager.cs
+++ b/MediaBrowser.Server.Implementations/Library/MusicManager.cs
@@ -30,7 +30,10 @@ namespace MediaBrowser.Server.Implementations.Library
public IEnumerable<Audio> GetInstantMixFromArtist(MusicArtist artist, User user)
{
var genres = user.RootFolder
- .GetRecursiveChildren(user, i => i is Audio)
+ .GetRecursiveChildren(user, new InternalItemsQuery(user)
+ {
+ IncludeItemTypes = new[] { typeof(Audio).Name }
+ })
.Cast<Audio>()
.Where(i => i.HasAnyArtist(artist.Name))
.SelectMany(i => i.Genres)
@@ -43,7 +46,10 @@ namespace MediaBrowser.Server.Implementations.Library
public IEnumerable<Audio> GetInstantMixFromAlbum(MusicAlbum item, User user)
{
var genres = item
- .GetRecursiveChildren(user, i => i is Audio)
+ .GetRecursiveChildren(user, new InternalItemsQuery(user)
+ {
+ IncludeItemTypes = new[] { typeof(Audio).Name }
+ })
.Cast<Audio>()
.SelectMany(i => i.Genres)
.Concat(item.Genres)
@@ -55,7 +61,10 @@ namespace MediaBrowser.Server.Implementations.Library
public IEnumerable<Audio> GetInstantMixFromFolder(Folder item, User user)
{
var genres = item
- .GetRecursiveChildren(user, i => i is Audio)
+ .GetRecursiveChildren(user, new InternalItemsQuery(user)
+ {
+ IncludeItemTypes = new[] {typeof(Audio).Name}
+ })
.Cast<Audio>()
.SelectMany(i => i.Genres)
.Concat(item.Genres)
@@ -67,7 +76,10 @@ namespace MediaBrowser.Server.Implementations.Library
public IEnumerable<Audio> GetInstantMixFromPlaylist(Playlist item, User user)
{
var genres = item
- .GetRecursiveChildren(user, i => i is Audio)
+ .GetRecursiveChildren(user, new InternalItemsQuery(user)
+ {
+ IncludeItemTypes = new[] { typeof(Audio).Name }
+ })
.Cast<Audio>()
.SelectMany(i => i.Genres)
.Concat(item.Genres)
@@ -86,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/ResolverHelper.cs b/MediaBrowser.Server.Implementations/Library/ResolverHelper.cs
index 60e7e2df3..9f949db92 100644
--- a/MediaBrowser.Server.Implementations/Library/ResolverHelper.cs
+++ b/MediaBrowser.Server.Implementations/Library/ResolverHelper.cs
@@ -44,7 +44,6 @@ namespace MediaBrowser.Server.Implementations.Library
// Make sure DateCreated and DateModified have values
var fileInfo = directoryService.GetFile(item.Path);
- item.DateModified = fileSystem.GetLastWriteTimeUtc(fileInfo);
SetDateCreated(item, fileSystem, fileInfo);
EnsureName(item, fileInfo);
@@ -80,7 +79,7 @@ namespace MediaBrowser.Server.Implementations.Library
item.GetParents().Any(i => i.IsLocked);
// Make sure DateCreated and DateModified have values
- EnsureDates(fileSystem, item, args, true);
+ EnsureDates(fileSystem, item, args);
}
/// <summary>
@@ -125,8 +124,7 @@ namespace MediaBrowser.Server.Implementations.Library
/// <param name="fileSystem">The file system.</param>
/// <param name="item">The item.</param>
/// <param name="args">The args.</param>
- /// <param name="includeCreationTime">if set to <c>true</c> [include creation time].</param>
- private static void EnsureDates(IFileSystem fileSystem, BaseItem item, ItemResolveArgs args, bool includeCreationTime)
+ private static void EnsureDates(IFileSystem fileSystem, BaseItem item, ItemResolveArgs args)
{
if (fileSystem == null)
{
@@ -148,12 +146,7 @@ namespace MediaBrowser.Server.Implementations.Library
if (childData != null)
{
- if (includeCreationTime)
- {
- SetDateCreated(item, fileSystem, childData);
- }
-
- item.DateModified = fileSystem.GetLastWriteTimeUtc(childData);
+ SetDateCreated(item, fileSystem, childData);
}
else
{
@@ -161,21 +154,13 @@ namespace MediaBrowser.Server.Implementations.Library
if (fileData.Exists)
{
- if (includeCreationTime)
- {
- SetDateCreated(item, fileSystem, fileData);
- }
- item.DateModified = fileSystem.GetLastWriteTimeUtc(fileData);
+ SetDateCreated(item, fileSystem, fileData);
}
}
}
else
{
- if (includeCreationTime)
- {
- SetDateCreated(item, fileSystem, args.FileInfo);
- }
- item.DateModified = fileSystem.GetLastWriteTimeUtc(args.FileInfo);
+ SetDateCreated(item, fileSystem, args.FileInfo);
}
}
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/PhotoResolver.cs b/MediaBrowser.Server.Implementations/Library/Resolvers/PhotoResolver.cs
index 8beb03b71..9dd30edde 100644
--- a/MediaBrowser.Server.Implementations/Library/Resolvers/PhotoResolver.cs
+++ b/MediaBrowser.Server.Implementations/Library/Resolvers/PhotoResolver.cs
@@ -5,15 +5,19 @@ using MediaBrowser.Model.Entities;
using System;
using System.IO;
using System.Linq;
+using CommonIO;
namespace MediaBrowser.Server.Implementations.Library.Resolvers
{
public class PhotoResolver : ItemResolver<Photo>
{
private readonly IImageProcessor _imageProcessor;
- public PhotoResolver(IImageProcessor imageProcessor)
+ private readonly ILibraryManager _libraryManager;
+
+ public PhotoResolver(IImageProcessor imageProcessor, ILibraryManager libraryManager)
{
_imageProcessor = imageProcessor;
+ _libraryManager = libraryManager;
}
/// <summary>
@@ -23,20 +27,45 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers
/// <returns>Trailer.</returns>
protected override Photo Resolve(ItemResolveArgs args)
{
- // Must be an image file within a photo collection
- if (string.Equals(args.GetCollectionType(), CollectionType.Photos, StringComparison.OrdinalIgnoreCase) &&
- !args.IsDirectory &&
- IsImageFile(args.Path, _imageProcessor))
+ if (!args.IsDirectory)
{
- return new Photo
+ // Must be an image file within a photo collection
+ var collectionType = args.GetCollectionType();
+
+ if (string.Equals(collectionType, CollectionType.Photos, StringComparison.OrdinalIgnoreCase) ||
+ string.Equals(collectionType, CollectionType.HomeVideos, StringComparison.OrdinalIgnoreCase))
{
- Path = args.Path
- };
+ if (IsImageFile(args.Path, _imageProcessor))
+ {
+ var filename = Path.GetFileNameWithoutExtension(args.Path);
+
+ // Make sure the image doesn't belong to a video file
+ if (args.DirectoryService.GetFiles(Path.GetDirectoryName(args.Path)).Any(i => IsOwnedByMedia(i, filename)))
+ {
+ return null;
+ }
+
+ return new Photo
+ {
+ Path = args.Path
+ };
+ }
+ }
}
return null;
}
+ private bool IsOwnedByMedia(FileSystemMetadata file, string imageFilename)
+ {
+ if (_libraryManager.IsVideoFile(file.FullName) && imageFilename.StartsWith(Path.GetFileNameWithoutExtension(file.Name), StringComparison.OrdinalIgnoreCase))
+ {
+ return true;
+ }
+
+ return false;
+ }
+
private static readonly string[] IgnoreFiles =
{
"folder",
@@ -44,7 +73,8 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers
"landscape",
"fanart",
"backdrop",
- "poster"
+ "poster",
+ "cover"
};
internal static bool IsImageFile(string path, IImageProcessor imageProcessor)
diff --git a/MediaBrowser.Server.Implementations/Library/Resolvers/TV/EpisodeResolver.cs b/MediaBrowser.Server.Implementations/Library/Resolvers/TV/EpisodeResolver.cs
index e62049821..7b8832c59 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
{
@@ -28,7 +31,6 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers.TV
}
var season = parent as Season;
-
// Just in case the user decided to nest episodes.
// Not officially supported but in some cases we can handle it.
if (season == null)
@@ -37,10 +39,31 @@ 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 ||
+ string.Equals(args.GetCollectionType(), CollectionType.TvShows, StringComparison.OrdinalIgnoreCase) ||
+ args.HasParent<Series>())
{
var episode = ResolveVideo<Episode>(args, false);
+ if (episode != null)
+ {
+ var series = parent as Series;
+ if (series == null)
+ {
+ series = parent.GetParents().OfType<Series>().FirstOrDefault();
+ }
+
+ if (series != null)
+ {
+ episode.SeriesId = series.Id;
+ }
+ if (season != null)
+ {
+ episode.SeasonId = season.Id;
+ }
+ }
+
return episode;
}
diff --git a/MediaBrowser.Server.Implementations/Library/Resolvers/TV/SeasonResolver.cs b/MediaBrowser.Server.Implementations/Library/Resolvers/TV/SeasonResolver.cs
index 7d13b11ad..eeac1345e 100644
--- a/MediaBrowser.Server.Implementations/Library/Resolvers/TV/SeasonResolver.cs
+++ b/MediaBrowser.Server.Implementations/Library/Resolvers/TV/SeasonResolver.cs
@@ -38,10 +38,12 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers.TV
if (args.Parent is Series && args.IsDirectory)
{
var namingOptions = ((LibraryManager)_libraryManager).GetNamingOptions();
-
+ var series = ((Series)args.Parent);
+
var season = new Season
{
- IndexNumber = new SeasonPathParser(namingOptions, new RegexProvider()).Parse(args.Path, true, true).SeasonNumber
+ IndexNumber = new SeasonPathParser(namingOptions, new RegexProvider()).Parse(args.Path, true, true).SeasonNumber,
+ SeriesId = series.Id
};
if (season.IndexNumber.HasValue && season.IndexNumber.Value == 0)
diff --git a/MediaBrowser.Server.Implementations/Library/SearchEngine.cs b/MediaBrowser.Server.Implementations/Library/SearchEngine.cs
index 276fc329f..cf6f070d0 100644
--- a/MediaBrowser.Server.Implementations/Library/SearchEngine.cs
+++ b/MediaBrowser.Server.Implementations/Library/SearchEngine.cs
@@ -87,13 +87,16 @@ namespace MediaBrowser.Server.Implementations.Library
{
var searchTerm = query.SearchTerm;
+ if (searchTerm != null)
+ {
+ searchTerm = searchTerm.Trim().RemoveDiacritics();
+ }
+
if (string.IsNullOrWhiteSpace(searchTerm))
{
throw new ArgumentNullException("searchTerm");
}
- searchTerm = searchTerm.RemoveDiacritics();
-
var terms = GetWords(searchTerm);
var hints = new List<Tuple<BaseItem, string, int>>();
@@ -119,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)
{
@@ -165,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 ae737d244..715f3c522 100644
--- a/MediaBrowser.Server.Implementations/Library/UserDataManager.cs
+++ b/MediaBrowser.Server.Implementations/Library/UserDataManager.cs
@@ -10,6 +10,7 @@ using MediaBrowser.Model.Logging;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
+using System.Linq;
using System.Threading;
using System.Threading.Tasks;
@@ -22,7 +23,8 @@ namespace MediaBrowser.Server.Implementations.Library
{
public event EventHandler<UserDataSaveEventArgs> UserDataSaved;
- private readonly ConcurrentDictionary<string, UserItemData> _userData = new ConcurrentDictionary<string, UserItemData>();
+ private readonly ConcurrentDictionary<string, UserItemData> _userData =
+ new ConcurrentDictionary<string, UserItemData>(StringComparer.OrdinalIgnoreCase);
private readonly ILogger _logger;
private readonly IServerConfigurationManager _config;
@@ -56,27 +58,28 @@ namespace MediaBrowser.Server.Implementations.Library
cancellationToken.ThrowIfCancellationRequested();
- var key = item.GetUserDataKey();
+ var keys = item.GetUserDataKeys();
- try
+ foreach (var key in keys)
{
- await Repository.SaveUserData(userId, key, userData, cancellationToken).ConfigureAwait(false);
-
- var newValue = userData;
+ try
+ {
+ await Repository.SaveUserData(userId, key, userData, cancellationToken).ConfigureAwait(false);
+ }
+ catch (Exception ex)
+ {
+ _logger.ErrorException("Error saving user data", ex);
- // Once it succeeds, put it into the dictionary to make it available to everyone else
- _userData.AddOrUpdate(GetCacheKey(userId, key), newValue, delegate { return newValue; });
+ throw;
+ }
}
- catch (Exception ex)
- {
- _logger.ErrorException("Error saving user data", ex);
- throw;
- }
+ var cacheKey = GetCacheKey(userId, item.Id);
+ _userData.AddOrUpdate(cacheKey, userData, (k, v) => userData);
EventHelper.FireEventIfNotNull(UserDataSaved, this, new UserDataSaveEventArgs
{
- Key = key,
+ Keys = keys,
UserData = userData,
SaveReason = reason,
UserId = userId,
@@ -116,7 +119,7 @@ namespace MediaBrowser.Server.Implementations.Library
throw;
}
-
+
}
/// <summary>
@@ -134,51 +137,86 @@ namespace MediaBrowser.Server.Implementations.Library
return Repository.GetAllUserData(userId);
}
- /// <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)
+ public UserItemData GetUserData(Guid userId, Guid itemId, List<string> keys)
{
if (userId == Guid.Empty)
{
throw new ArgumentNullException("userId");
}
- if (string.IsNullOrEmpty(key))
+ if (keys == null)
+ {
+ throw new ArgumentNullException("keys");
+ }
+ if (keys.Count == 0)
{
- throw new ArgumentNullException("key");
+ throw new ArgumentException("UserData keys cannot be empty.");
}
- return _userData.GetOrAdd(GetCacheKey(userId, key), keyName => GetUserDataFromRepository(userId, key));
+ var cacheKey = GetCacheKey(userId, itemId);
+
+ return _userData.GetOrAdd(cacheKey, k => GetUserDataInternal(userId, keys));
}
- public UserItemData GetUserDataFromRepository(Guid userId, string key)
+ private UserItemData GetUserDataInternal(Guid userId, List<string> keys)
{
- var data = Repository.GetUserData(userId, key);
+ var userData = Repository.GetUserData(userId, keys);
- return data;
+ if (userData != null)
+ {
+ return userData;
+ }
+
+ if (keys.Count > 0)
+ {
+ return new UserItemData
+ {
+ 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.ToString("N") + itemId.ToString("N");
+ }
+
+ public UserItemData GetUserData(IHasUserData user, IHasUserData item)
+ {
+ return GetUserData(user.Id, item);
+ }
+
+ public UserItemData GetUserData(string userId, IHasUserData item)
+ {
+ return GetUserData(new Guid(userId), item);
+ }
+
+ public UserItemData GetUserData(Guid userId, IHasUserData item)
{
- return userId + key;
+ 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.GetUserDataKey());
+ 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;
}
@@ -261,10 +299,5 @@ namespace MediaBrowser.Server.Implementations.Library
return playedToCompletion;
}
-
- public UserItemData GetUserData(string userId, string key)
- {
- return GetUserData(new Guid(userId), key);
- }
}
}
diff --git a/MediaBrowser.Server.Implementations/Library/UserManager.cs b/MediaBrowser.Server.Implementations/Library/UserManager.cs
index c1807efe9..6456d7f81 100644
--- a/MediaBrowser.Server.Implementations/Library/UserManager.cs
+++ b/MediaBrowser.Server.Implementations/Library/UserManager.cs
@@ -352,6 +352,7 @@ namespace MediaBrowser.Server.Implementations.Library
users.Add(user);
user.Policy.IsAdministrator = true;
+ user.Policy.EnableContentDeletion = true;
user.Policy.EnableRemoteControlOfOtherUsers = true;
await UpdateUserPolicy(user, user.Policy, false).ConfigureAwait(false);
}
@@ -728,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 9f6e39b46..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));
@@ -121,7 +121,7 @@ namespace MediaBrowser.Server.Implementations.Library
var channels = channelResult.Items;
- if (!user.Configuration.DisplayChannelsInline && channels.Length > 0)
+ if (user.Configuration.EnableChannelView && channels.Length > 0)
{
list.Add(await _channelManager.GetInternalChannelFolder(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/Library/Validators/StudiosValidator.cs b/MediaBrowser.Server.Implementations/Library/Validators/StudiosValidator.cs
index c122d64d3..c1803b5e4 100644
--- a/MediaBrowser.Server.Implementations/Library/Validators/StudiosValidator.cs
+++ b/MediaBrowser.Server.Implementations/Library/Validators/StudiosValidator.cs
@@ -35,7 +35,7 @@ namespace MediaBrowser.Server.Implementations.Library.Validators
/// <returns>Task.</returns>
public async Task Run(IProgress<double> progress, CancellationToken cancellationToken)
{
- var items = _libraryManager.RootFolder.GetRecursiveChildren()
+ var items = _libraryManager.RootFolder.GetRecursiveChildren(i => true)
.SelectMany(i => i.Studios)
.DistinctNames()
.ToList();
@@ -73,28 +73,6 @@ namespace MediaBrowser.Server.Implementations.Library.Validators
progress.Report(percent);
}
- var allIds = _libraryManager.GetItemIds(new InternalItemsQuery
- {
- IncludeItemTypes = new[] { typeof(Studio).Name }
- });
-
- var invalidIds = allIds
- .Except(validIds)
- .ToList();
-
- foreach (var id in invalidIds)
- {
- cancellationToken.ThrowIfCancellationRequested();
-
- var item = _libraryManager.GetItemById(id);
-
- await _libraryManager.DeleteItem(item, new DeleteOptions
- {
- DeleteFileLocation = false
-
- }).ConfigureAwait(false);
- }
-
progress.Report(100);
}
}
diff --git a/MediaBrowser.Server.Implementations/Library/Validators/YearsPostScanTask.cs b/MediaBrowser.Server.Implementations/Library/Validators/YearsPostScanTask.cs
index 5ea5fb254..7f52a4506 100644
--- a/MediaBrowser.Server.Implementations/Library/Validators/YearsPostScanTask.cs
+++ b/MediaBrowser.Server.Implementations/Library/Validators/YearsPostScanTask.cs
@@ -20,16 +20,12 @@ namespace MediaBrowser.Server.Implementations.Library.Validators
public async Task Run(IProgress<double> progress, CancellationToken cancellationToken)
{
- var allYears = _libraryManager.RootFolder.GetRecursiveChildren(i => i.ProductionYear.HasValue)
- .Select(i => i.ProductionYear ?? -1)
- .Where(i => i > 0)
- .Distinct()
- .ToList();
-
- var count = allYears.Count;
+ var yearNumber = 1900;
+ var maxYear = DateTime.UtcNow.Year + 3;
+ var count = maxYear - yearNumber + 1;
var numComplete = 0;
- foreach (var yearNumber in allYears)
+ while (yearNumber < maxYear)
{
try
{
@@ -53,6 +49,7 @@ namespace MediaBrowser.Server.Implementations.Library.Validators
percent *= 100;
progress.Report(percent);
+ yearNumber++;
}
}
}
diff --git a/MediaBrowser.Server.Implementations/LiveTv/ChannelImageProvider.cs b/MediaBrowser.Server.Implementations/LiveTv/ChannelImageProvider.cs
index 24d38a63e..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
{
@@ -77,7 +77,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv
get { return 0; }
}
- public bool HasChanged(IHasMetadata item, MetadataStatus status, IDirectoryService directoryService)
+ public bool HasChanged(IHasMetadata item, IDirectoryService directoryService)
{
return GetSupportedImages(item).Any(i => !item.HasImage(i));
}
diff --git a/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/DirectRecorder.cs b/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/DirectRecorder.cs
index d33b2c51d..b21aa904b 100644
--- a/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/DirectRecorder.cs
+++ b/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/DirectRecorder.cs
@@ -23,6 +23,11 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
_fileSystem = fileSystem;
}
+ public string GetOutputPath(MediaSourceInfo mediaSource, string targetFile)
+ {
+ return targetFile;
+ }
+
public async Task Record(MediaSourceInfo mediaSource, string targetFile, TimeSpan duration, Action onStarted, CancellationToken cancellationToken)
{
var httpRequestOptions = new HttpRequestOptions()
@@ -40,14 +45,27 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
{
onStarted();
- _logger.Info("Copying recording stream to file stream");
+ _logger.Info("Copying recording stream to file {0}", targetFile);
- var durationToken = new CancellationTokenSource(duration);
- var linkedToken = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, durationToken.Token).Token;
+ 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);
+ cancellationToken = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, durationToken.Token).Token;
+ }
- await response.Content.CopyToAsync(output, StreamDefaults.DefaultCopyToBufferSize, linkedToken).ConfigureAwait(false);
+ await response.Content.CopyToAsync(output, StreamDefaults.DefaultCopyToBufferSize, cancellationToken).ConfigureAwait(false);
}
}
+
+ _logger.Info("Recording completed to file {0}", targetFile);
}
}
}
diff --git a/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs b/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs
index 60ff23b04..8f56554f1 100644
--- a/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs
+++ b/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs
@@ -26,13 +26,16 @@ using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using CommonIO;
+using MediaBrowser.Common.Events;
using MediaBrowser.Common.Extensions;
+using MediaBrowser.Controller.Entities;
+using MediaBrowser.Controller.Entities.TV;
using MediaBrowser.Controller.Power;
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;
@@ -40,7 +43,6 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
private readonly IServerConfigurationManager _config;
private readonly IJsonSerializer _jsonSerializer;
- private readonly ItemDataProvider<RecordingInfo> _recordingProvider;
private readonly ItemDataProvider<SeriesTimerInfo> _seriesTimerProvider;
private readonly TimerManager _timerProvider;
@@ -56,6 +58,12 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
public static EmbyTV Current;
+ public event EventHandler DataSourceChanged;
+ public event EventHandler<RecordingStatusChangedEventArgs> RecordingStatusChanged;
+
+ private readonly ConcurrentDictionary<string, ActiveRecordingInfo> _activeRecordings =
+ new ConcurrentDictionary<string, ActiveRecordingInfo>(StringComparer.OrdinalIgnoreCase);
+
public EmbyTV(IApplicationHost appHost, ILogger logger, IJsonSerializer jsonSerializer, IHttpClient httpClient, IServerConfigurationManager config, ILiveTvManager liveTvManager, IFileSystem fileSystem, ISecurityManager security, ILibraryManager libraryManager, ILibraryMonitor libraryMonitor, IProviderManager providerManager, IFileOrganizationService organizationService, IMediaEncoder mediaEncoder, IPowerManagement powerManagement)
{
Current = this;
@@ -74,10 +82,19 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
_liveTvManager = (LiveTvManager)liveTvManager;
_jsonSerializer = jsonSerializer;
- _recordingProvider = new ItemDataProvider<RecordingInfo>(fileSystem, jsonSerializer, _logger, Path.Combine(DataPath, "recordings"), (r1, r2) => string.Equals(r1.Id, r2.Id, StringComparison.OrdinalIgnoreCase));
_seriesTimerProvider = new SeriesTimerManager(fileSystem, jsonSerializer, _logger, Path.Combine(DataPath, "seriestimers"));
_timerProvider = new TimerManager(fileSystem, jsonSerializer, _logger, Path.Combine(DataPath, "timers"), powerManagement, _logger);
_timerProvider.TimerFired += _timerProvider_TimerFired;
+
+ _config.NamedConfigurationUpdated += _config_NamedConfigurationUpdated;
+ }
+
+ private void _config_NamedConfigurationUpdated(object sender, ConfigurationUpdateEventArgs e)
+ {
+ if (string.Equals(e.Key, "livetv", StringComparison.OrdinalIgnoreCase))
+ {
+ OnRecordingFoldersChanged();
+ }
}
public void Start()
@@ -85,6 +102,124 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
_timerProvider.RestartTimers();
SystemEvents.PowerModeChanged += SystemEvents_PowerModeChanged;
+ CreateRecordingFolders();
+ }
+
+ private void OnRecordingFoldersChanged()
+ {
+ CreateRecordingFolders();
+ }
+
+ internal void CreateRecordingFolders()
+ {
+ try
+ {
+ CreateRecordingFoldersInternal();
+ }
+ catch (Exception ex)
+ {
+ _logger.ErrorException("Error creating recording folders", ex);
+ }
+ }
+
+ internal void CreateRecordingFoldersInternal()
+ {
+ var recordingFolders = GetRecordingFolders();
+
+ var virtualFolders = _libraryManager.GetVirtualFolders()
+ .ToList();
+
+ var allExistingPaths = virtualFolders.SelectMany(i => i.Locations).ToList();
+
+ var pathsAdded = new List<string>();
+
+ foreach (var recordingFolder in recordingFolders)
+ {
+ var pathsToCreate = recordingFolder.Locations
+ .Where(i => !allExistingPaths.Contains(i, StringComparer.OrdinalIgnoreCase))
+ .ToList();
+
+ if (pathsToCreate.Count == 0)
+ {
+ continue;
+ }
+
+ try
+ {
+ _libraryManager.AddVirtualFolder(recordingFolder.Name, recordingFolder.CollectionType, pathsToCreate.ToArray(), true);
+ }
+ catch (Exception ex)
+ {
+ _logger.ErrorException("Error creating virtual folder", ex);
+ }
+
+ pathsAdded.AddRange(pathsToCreate);
+ }
+
+ var config = GetConfiguration();
+
+ var pathsToRemove = config.MediaLocationsCreated
+ .Except(recordingFolders.SelectMany(i => i.Locations))
+ .ToList();
+
+ if (pathsAdded.Count > 0 || pathsToRemove.Count > 0)
+ {
+ pathsAdded.InsertRange(0, config.MediaLocationsCreated);
+ config.MediaLocationsCreated = pathsAdded.Except(pathsToRemove).Distinct(StringComparer.OrdinalIgnoreCase).ToArray();
+ _config.SaveConfiguration("livetv", config);
+ }
+
+ foreach (var path in pathsToRemove)
+ {
+ RemovePathFromLibrary(path);
+ }
+ }
+
+ private void RemovePathFromLibrary(string path)
+ {
+ _logger.Debug("Removing path from library: {0}", path);
+
+ var requiresRefresh = false;
+ var virtualFolders = _libraryManager.GetVirtualFolders()
+ .ToList();
+
+ foreach (var virtualFolder in virtualFolders)
+ {
+ if (!virtualFolder.Locations.Contains(path, StringComparer.OrdinalIgnoreCase))
+ {
+ continue;
+ }
+
+ if (virtualFolder.Locations.Count == 1)
+ {
+ // remove entire virtual folder
+ try
+ {
+ _libraryManager.RemoveVirtualFolder(virtualFolder.Name, true);
+ }
+ catch (Exception ex)
+ {
+ _logger.ErrorException("Error removing virtual folder", ex);
+ }
+ }
+ else
+ {
+ try
+ {
+ _libraryManager.RemoveMediaPath(virtualFolder.Name, path);
+ requiresRefresh = true;
+ }
+ catch (Exception ex)
+ {
+ _logger.ErrorException("Error removing media path", ex);
+ }
+ }
+ }
+
+ if (requiresRefresh)
+ {
+ _libraryManager.ValidateMediaLibrary(new Progress<Double>(), CancellationToken.None);
+ }
}
void SystemEvents_PowerModeChanged(object sender, PowerModeChangedEventArgs e)
@@ -97,13 +232,6 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
}
}
- public event EventHandler DataSourceChanged;
-
- public event EventHandler<RecordingStatusChangedEventArgs> RecordingStatusChanged;
-
- private readonly ConcurrentDictionary<string, ActiveRecordingInfo> _activeRecordings =
- new ConcurrentDictionary<string, ActiveRecordingInfo>(StringComparer.OrdinalIgnoreCase);
-
public string Name
{
get { return "Emby"; }
@@ -114,6 +242,26 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
get { return Path.Combine(_config.CommonApplicationPaths.DataPath, "livetv"); }
}
+ private string DefaultRecordingPath
+ {
+ get
+ {
+ return Path.Combine(DataPath, "recordings");
+ }
+ }
+
+ private string RecordingPath
+ {
+ get
+ {
+ var path = GetConfiguration().RecordingPath;
+
+ return string.IsNullOrWhiteSpace(path)
+ ? DefaultRecordingPath
+ : path;
+ }
+ }
+
public string HomePageUrl
{
get { return "http://emby.media"; }
@@ -234,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);
@@ -280,59 +451,29 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
return Task.FromResult(true);
}
- public async Task DeleteRecordingAsync(string recordingId, CancellationToken cancellationToken)
+ public Task DeleteRecordingAsync(string recordingId, CancellationToken cancellationToken)
{
- var remove = _recordingProvider.GetAll().FirstOrDefault(i => string.Equals(i.Id, recordingId, StringComparison.OrdinalIgnoreCase));
- if (remove != null)
- {
- if (!string.IsNullOrWhiteSpace(remove.TimerId))
- {
- var enableDelay = _activeRecordings.ContainsKey(remove.TimerId);
-
- CancelTimerInternal(remove.TimerId);
-
- if (enableDelay)
- {
- // A hack yes, but need to make sure the file is closed before attempting to delete it
- await Task.Delay(3000, cancellationToken).ConfigureAwait(false);
- }
- }
-
- if (!string.IsNullOrWhiteSpace(remove.Path))
- {
- try
- {
- _fileSystem.DeleteFile(remove.Path);
- }
- catch (DirectoryNotFoundException)
- {
+ return Task.FromResult(true);
+ }
- }
- catch (FileNotFoundException)
- {
+ public Task CreateTimerAsync(TimerInfo info, CancellationToken cancellationToken)
+ {
+ return CreateTimer(info, cancellationToken);
+ }
- }
- catch (Exception ex)
- {
- _logger.ErrorException("Error deleting recording file {0}", ex, remove.Path);
- }
- }
- _recordingProvider.Delete(remove);
- }
- else
- {
- throw new ResourceNotFoundException("Recording not found: " + recordingId);
- }
+ public Task CreateSeriesTimerAsync(SeriesTimerInfo info, CancellationToken cancellationToken)
+ {
+ return CreateSeriesTimer(info, cancellationToken);
}
- public Task CreateTimerAsync(TimerInfo info, CancellationToken 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");
@@ -362,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)
@@ -424,29 +567,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
public async Task<IEnumerable<RecordingInfo>> GetRecordingsAsync(CancellationToken cancellationToken)
{
- var recordings = _recordingProvider.GetAll().ToList();
- var updated = false;
-
- foreach (var recording in recordings)
- {
- if (recording.Status == RecordingStatus.InProgress)
- {
- if (string.IsNullOrWhiteSpace(recording.TimerId) || !_activeRecordings.ContainsKey(recording.TimerId))
- {
- recording.Status = RecordingStatus.Cancelled;
- recording.DateLastUpdated = DateTime.UtcNow;
- _recordingProvider.Update(recording);
- updated = true;
- }
- }
- }
-
- if (updated)
- {
- recordings = _recordingProvider.GetAll().ToList();
- }
-
- return recordings;
+ return new List<RecordingInfo>();
}
public Task<IEnumerable<TimerInfo>> GetTimersAsync(CancellationToken cancellationToken)
@@ -462,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)
@@ -528,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();
@@ -550,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
@@ -591,7 +744,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
throw new ApplicationException("Tuner not found.");
}
- private async Task<Tuple<MediaSourceInfo, SemaphoreSlim>> GetChannelStreamInternal(string channelId, string streamId, CancellationToken cancellationToken)
+ private async Task<Tuple<MediaSourceInfo, ITunerHost, SemaphoreSlim>> GetChannelStreamInternal(string channelId, string streamId, CancellationToken cancellationToken)
{
_logger.Info("Streaming Channel " + channelId);
@@ -599,7 +752,9 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
{
try
{
- return await hostInstance.GetChannelStream(channelId, streamId, cancellationToken).ConfigureAwait(false);
+ var result = await hostInstance.GetChannelStream(channelId, streamId, cancellationToken).ConfigureAwait(false);
+
+ return new Tuple<MediaSourceInfo, ITunerHost, SemaphoreSlim>(result.Item1, hostInstance, result.Item2);
}
catch (Exception e)
{
@@ -693,6 +848,106 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
}
}
+ private string GetRecordingPath(TimerInfo timer, ProgramInfo info)
+ {
+ var recordPath = RecordingPath;
+ var config = GetConfiguration();
+
+ if (info.IsMovie)
+ {
+ var customRecordingPath = config.MovieRecordingPath;
+ var allowSubfolder = true;
+ if (!string.IsNullOrWhiteSpace(customRecordingPath))
+ {
+ allowSubfolder = string.Equals(customRecordingPath, recordPath, StringComparison.OrdinalIgnoreCase);
+ recordPath = customRecordingPath;
+ }
+
+ if (allowSubfolder && config.EnableRecordingSubfolders)
+ {
+ recordPath = Path.Combine(recordPath, "Movies");
+ }
+
+ var folderName = _fileSystem.GetValidFilename(info.Name).Trim();
+ if (info.ProductionYear.HasValue)
+ {
+ folderName += " (" + info.ProductionYear.Value.ToString(CultureInfo.InvariantCulture) + ")";
+ }
+ recordPath = Path.Combine(recordPath, folderName);
+ }
+ else if (info.IsSeries)
+ {
+ var customRecordingPath = config.SeriesRecordingPath;
+ var allowSubfolder = true;
+ if (!string.IsNullOrWhiteSpace(customRecordingPath))
+ {
+ allowSubfolder = string.Equals(customRecordingPath, recordPath, StringComparison.OrdinalIgnoreCase);
+ recordPath = customRecordingPath;
+ }
+
+ if (allowSubfolder && config.EnableRecordingSubfolders)
+ {
+ recordPath = Path.Combine(recordPath, "Series");
+ }
+
+ var folderName = _fileSystem.GetValidFilename(info.Name).Trim();
+ var folderNameWithYear = folderName;
+ if (info.ProductionYear.HasValue)
+ {
+ folderNameWithYear += " (" + info.ProductionYear.Value.ToString(CultureInfo.InvariantCulture) + ")";
+ }
+
+ if (Directory.Exists(Path.Combine(recordPath, folderName)))
+ {
+ recordPath = Path.Combine(recordPath, folderName);
+ }
+ else
+ {
+ recordPath = Path.Combine(recordPath, folderNameWithYear);
+ }
+
+ if (info.SeasonNumber.HasValue)
+ {
+ folderName = string.Format("Season {0}", info.SeasonNumber.Value.ToString(CultureInfo.InvariantCulture));
+ recordPath = Path.Combine(recordPath, folderName);
+ }
+ }
+ else if (info.IsKids)
+ {
+ if (config.EnableRecordingSubfolders)
+ {
+ recordPath = Path.Combine(recordPath, "Kids");
+ }
+
+ var folderName = _fileSystem.GetValidFilename(info.Name).Trim();
+ if (info.ProductionYear.HasValue)
+ {
+ folderName += " (" + info.ProductionYear.Value.ToString(CultureInfo.InvariantCulture) + ")";
+ }
+ recordPath = Path.Combine(recordPath, folderName);
+ }
+ else if (info.IsSports)
+ {
+ if (config.EnableRecordingSubfolders)
+ {
+ recordPath = Path.Combine(recordPath, "Sports");
+ }
+ recordPath = Path.Combine(recordPath, _fileSystem.GetValidFilename(info.Name).Trim());
+ }
+ else
+ {
+ if (config.EnableRecordingSubfolders)
+ {
+ recordPath = Path.Combine(recordPath, "Other");
+ }
+ recordPath = Path.Combine(recordPath, _fileSystem.GetValidFilename(info.Name).Trim());
+ }
+
+ var recordingFileName = _fileSystem.GetValidFilename(RecordingHelper.GetRecordingName(timer, info)).Trim() + ".ts";
+
+ return Path.Combine(recordPath, recordingFileName);
+ }
+
private async Task RecordStream(TimerInfo timer, DateTime recordingEndDate, ActiveRecordingInfo activeRecordingInfo, CancellationToken cancellationToken)
{
if (timer == null)
@@ -722,161 +977,102 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
throw new InvalidOperationException(string.Format("Program with Id {0} not found", timer.ProgramId));
}
- var recordPath = RecordingPath;
-
- if (info.IsMovie)
- {
- recordPath = Path.Combine(recordPath, "Movies", _fileSystem.GetValidFilename(info.Name).Trim());
- }
- else if (info.IsSeries)
- {
- recordPath = Path.Combine(recordPath, "Series", _fileSystem.GetValidFilename(info.Name).Trim());
- }
- else if (info.IsKids)
- {
- recordPath = Path.Combine(recordPath, "Kids", _fileSystem.GetValidFilename(info.Name).Trim());
- }
- else if (info.IsSports)
- {
- recordPath = Path.Combine(recordPath, "Sports", _fileSystem.GetValidFilename(info.Name).Trim());
- }
- else
- {
- recordPath = Path.Combine(recordPath, "Other", _fileSystem.GetValidFilename(info.Name).Trim());
- }
-
- var recordingFileName = _fileSystem.GetValidFilename(RecordingHelper.GetRecordingName(timer, info)).Trim() + ".ts";
-
- recordPath = Path.Combine(recordPath, recordingFileName);
-
- var recordingId = info.Id.GetMD5().ToString("N");
- var recording = _recordingProvider.GetAll().FirstOrDefault(x => string.Equals(x.Id, recordingId, StringComparison.OrdinalIgnoreCase));
-
- if (recording == null)
- {
- recording = new RecordingInfo
- {
- ChannelId = info.ChannelId,
- Id = recordingId,
- StartDate = info.StartDate,
- EndDate = info.EndDate,
- Genres = info.Genres,
- IsKids = info.IsKids,
- IsLive = info.IsLive,
- IsMovie = info.IsMovie,
- IsHD = info.IsHD,
- IsNews = info.IsNews,
- IsPremiere = info.IsPremiere,
- IsSeries = info.IsSeries,
- IsSports = info.IsSports,
- IsRepeat = !info.IsPremiere,
- Name = info.Name,
- EpisodeTitle = info.EpisodeTitle,
- ProgramId = info.Id,
- ImagePath = info.ImagePath,
- ImageUrl = info.ImageUrl,
- OriginalAirDate = info.OriginalAirDate,
- Status = RecordingStatus.Scheduled,
- Overview = info.Overview,
- SeriesTimerId = timer.SeriesTimerId,
- TimerId = timer.Id,
- ShowId = info.ShowId
- };
- _recordingProvider.AddOrUpdate(recording);
- }
+ 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 duration = recordingEndDate - DateTime.UtcNow;
+ var recorder = await GetRecorder().ConfigureAwait(false);
- var recorder = await GetRecorder().ConfigureAwait(false);
+ recordPath = recorder.GetOutputPath(mediaStreamInfo, recordPath);
+ recordPath = EnsureFileUnique(recordPath, timer.Id);
- if (recorder is EncodedRecorder)
- {
- recordPath = Path.ChangeExtension(recordPath, ".mp4");
- }
- recordPath = EnsureFileUnique(recordPath, timer.Id);
- _fileSystem.CreateDirectory(Path.GetDirectoryName(recordPath));
- activeRecordingInfo.Path = recordPath;
+ _libraryMonitor.ReportFileSystemChangeBeginning(recordPath);
+ _fileSystem.CreateDirectory(Path.GetDirectoryName(recordPath));
+ activeRecordingInfo.Path = recordPath;
- _libraryMonitor.ReportFileSystemChangeBeginning(recordPath);
+ var duration = recordingEndDate - DateTime.UtcNow;
- recording.Path = recordPath;
- recording.Status = RecordingStatus.InProgress;
- recording.DateLastUpdated = DateTime.UtcNow;
- _recordingProvider.AddOrUpdate(recording);
+ _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 = () =>
+ {
+ timer.Status = RecordingStatus.InProgress;
+ _timerProvider.AddOrUpdate(timer, false);
- Action onStarted = () =>
- {
- result.Item2.Release();
- isResourceOpen = false;
- };
+ result.Item3.Release();
+ isResourceOpen = false;
+ };
- await recorder.Record(mediaStreamInfo, recordPath, duration, onStarted, cancellationToken).ConfigureAwait(false);
+ var pathWithDuration = result.Item2.ApplyDuration(mediaStreamInfo.Path, duration);
- recording.Status = 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.Item2.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)
{
_logger.Info("Recording stopped: {0}", recordPath);
- recording.Status = RecordingStatus.Completed;
+ recordingStatus = RecordingStatus.Completed;
}
catch (Exception ex)
{
_logger.ErrorException("Error recording to {0}", ex, recordPath);
- recording.Status = RecordingStatus.Error;
+ recordingStatus = RecordingStatus.Error;
}
finally
{
+ if (isResourceOpen && semaphore != null)
+ {
+ semaphore.Release();
+ }
+
+ _libraryMonitor.ReportFileSystemChangeComplete(recordPath, true);
+
ActiveRecordingInfo removed;
_activeRecordings.TryRemove(timer.Id, out removed);
}
- recording.DateLastUpdated = DateTime.UtcNow;
- _recordingProvider.AddOrUpdate(recording);
-
- if (recording.Status == RecordingStatus.Completed)
+ if (recordingStatus == RecordingStatus.Completed)
{
- OnSuccessfulRecording(recording);
+ 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
{
_timerProvider.Delete(timer);
- _recordingProvider.Delete(recording);
}
}
@@ -916,24 +1112,26 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
private async Task<IRecorder> GetRecorder()
{
- if (GetConfiguration().EnableRecordingEncoding)
+ var config = GetConfiguration();
+
+ if (config.EnableRecordingEncoding)
{
var regInfo = await _security.GetRegistrationStatus("embytvrecordingconversion").ConfigureAwait(false);
if (regInfo.IsValid)
{
- return new EncodedRecorder(_logger, _fileSystem, _mediaEncoder, _config.ApplicationPaths, _jsonSerializer);
+ return new EncodedRecorder(_logger, _fileSystem, _mediaEncoder, _config.ApplicationPaths, _jsonSerializer, config, _httpClient);
}
}
return new DirectRecorder(_logger, _httpClient, _fileSystem);
}
- private async void OnSuccessfulRecording(RecordingInfo recording)
+ private async void OnSuccessfulRecording(bool isSeries, string path)
{
if (GetConfiguration().EnableAutoOrganize)
{
- if (recording.IsSeries)
+ if (isSeries)
{
try
{
@@ -943,12 +1141,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
var organize = new EpisodeFileOrganizer(_organizationService, _config, _fileSystem, _logger, _libraryManager, _libraryMonitor, _providerManager);
- var result = await organize.OrganizeEpisodeFile(recording.Path, CancellationToken.None).ConfigureAwait(false);
-
- if (result.Status == FileSortingStatus.Success)
- {
- _recordingProvider.Delete(recording);
- }
+ var result = await organize.OrganizeEpisodeFile(path, CancellationToken.None).ConfigureAwait(false);
}
catch (Exception ex)
{
@@ -972,18 +1165,6 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
return epgData.FirstOrDefault(p => Math.Abs(startDateTicks - p.StartDate.Ticks) <= TimeSpan.FromMinutes(3).Ticks);
}
- private string RecordingPath
- {
- get
- {
- var path = GetConfiguration().RecordingPath;
-
- return string.IsNullOrWhiteSpace(path)
- ? Path.Combine(DataPath, "recordings")
- : path;
- }
- }
-
private LiveTvOptions GetConfiguration()
{
return _config.GetConfiguration<LiveTvOptions>("livetv");
@@ -991,7 +1172,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
private async Task UpdateTimersForSeriesTimer(List<ProgramInfo> epgData, SeriesTimerInfo seriesTimer, bool deleteInvalidTimers)
{
- var newTimers = GetTimersForSeries(seriesTimer, epgData, _recordingProvider.GetAll()).ToList();
+ var newTimers = GetTimersForSeries(seriesTimer, epgData, true).ToList();
var registration = await GetRegistrationInfo("seriesrecordings").ConfigureAwait(false);
@@ -1005,7 +1186,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
if (deleteInvalidTimers)
{
- var allTimers = GetTimersForSeries(seriesTimer, epgData, new List<RecordingInfo>())
+ var allTimers = GetTimersForSeries(seriesTimer, epgData, false)
.Select(i => i.Id)
.ToList();
@@ -1021,7 +1202,9 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
}
}
- private IEnumerable<TimerInfo> GetTimersForSeries(SeriesTimerInfo seriesTimer, IEnumerable<ProgramInfo> allPrograms, IReadOnlyList<RecordingInfo> currentRecordings)
+ private IEnumerable<TimerInfo> GetTimersForSeries(SeriesTimerInfo seriesTimer,
+ IEnumerable<ProgramInfo> allPrograms,
+ bool filterByCurrentRecordings)
{
if (seriesTimer == null)
{
@@ -1031,28 +1214,80 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
{
throw new ArgumentNullException("allPrograms");
}
- if (currentRecordings == null)
- {
- throw new ArgumentNullException("currentRecordings");
- }
// Exclude programs that have already ended
allPrograms = allPrograms.Where(i => i.EndDate > DateTime.UtcNow && i.StartDate > DateTime.UtcNow);
allPrograms = GetProgramsForSeries(seriesTimer, allPrograms);
- var recordingShowIds = currentRecordings.Select(i => i.ProgramId).Where(i => !string.IsNullOrWhiteSpace(i)).ToList();
-
- allPrograms = allPrograms.Where(i => !recordingShowIds.Contains(i.Id, StringComparer.OrdinalIgnoreCase));
+ if (filterByCurrentRecordings)
+ {
+ allPrograms = allPrograms.Where(i => !IsProgramAlreadyInLibrary(i));
+ }
return allPrograms.Select(i => RecordingHelper.CreateTimer(i, seriesTimer));
}
+ private bool IsProgramAlreadyInLibrary(ProgramInfo program)
+ {
+ if ((program.EpisodeNumber.HasValue && program.SeasonNumber.HasValue) || !string.IsNullOrWhiteSpace(program.EpisodeTitle))
+ {
+ var seriesIds = _libraryManager.GetItemIds(new InternalItemsQuery
+ {
+ IncludeItemTypes = new[] { typeof(Series).Name },
+ Name = program.Name
+
+ }).Select(i => i.ToString("N")).ToArray();
+
+ if (seriesIds.Length == 0)
+ {
+ return false;
+ }
+
+ if (program.EpisodeNumber.HasValue && program.SeasonNumber.HasValue)
+ {
+ var result = _libraryManager.GetItemsResult(new InternalItemsQuery
+ {
+ IncludeItemTypes = new[] { typeof(Episode).Name },
+ ParentIndexNumber = program.SeasonNumber.Value,
+ IndexNumber = program.EpisodeNumber.Value,
+ AncestorIds = seriesIds,
+ ExcludeLocationTypes = new[] { LocationType.Virtual }
+ });
+
+ if (result.TotalRecordCount > 0)
+ {
+ return true;
+ }
+ }
+
+ if (!string.IsNullOrWhiteSpace(program.EpisodeTitle))
+ {
+ var result = _libraryManager.GetItemsResult(new InternalItemsQuery
+ {
+ IncludeItemTypes = new[] { typeof(Episode).Name },
+ Name = program.EpisodeTitle,
+ AncestorIds = seriesIds,
+ ExcludeLocationTypes = new[] { LocationType.Virtual }
+ });
+
+ if (result.TotalRecordCount > 0)
+ {
+ return true;
+ }
+ }
+ }
+
+ return false;
+ }
+
private IEnumerable<ProgramInfo> GetProgramsForSeries(SeriesTimerInfo seriesTimer, IEnumerable<ProgramInfo> allPrograms)
{
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)
@@ -1065,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");
@@ -1132,6 +1365,47 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
});
}
+ public List<VirtualFolderInfo> GetRecordingFolders()
+ {
+ var list = new List<VirtualFolderInfo>();
+
+ var defaultFolder = RecordingPath;
+ var defaultName = "Recordings";
+
+ if (Directory.Exists(defaultFolder))
+ {
+ list.Add(new VirtualFolderInfo
+ {
+ Locations = new List<string> { defaultFolder },
+ Name = defaultName
+ });
+ }
+
+ var customPath = GetConfiguration().MovieRecordingPath;
+ if ((!string.IsNullOrWhiteSpace(customPath) && !string.Equals(customPath, defaultFolder, StringComparison.OrdinalIgnoreCase)) && Directory.Exists(customPath))
+ {
+ list.Add(new VirtualFolderInfo
+ {
+ Locations = new List<string> { customPath },
+ Name = "Recorded Movies",
+ CollectionType = CollectionType.Movies
+ });
+ }
+
+ customPath = GetConfiguration().SeriesRecordingPath;
+ if ((!string.IsNullOrWhiteSpace(customPath) && !string.Equals(customPath, defaultFolder, StringComparison.OrdinalIgnoreCase)) && Directory.Exists(customPath))
+ {
+ list.Add(new VirtualFolderInfo
+ {
+ Locations = new List<string> { customPath },
+ Name = "Recorded Series",
+ CollectionType = CollectionType.TvShows
+ });
+ }
+
+ return list;
+ }
+
class ActiveRecordingInfo
{
public string Path { get; set; }
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 69cc8ebf7..5e428e6f0 100644
--- a/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs
+++ b/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs
@@ -8,10 +8,13 @@ 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;
+using MediaBrowser.Model.LiveTv;
using MediaBrowser.Model.Logging;
using MediaBrowser.Model.Serialization;
@@ -21,8 +24,10 @@ 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;
private string _targetPath;
@@ -30,17 +35,99 @@ 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)
+ public EncodedRecorder(ILogger logger, IFileSystem fileSystem, IMediaEncoder mediaEncoder, IServerApplicationPaths appPaths, IJsonSerializer json, LiveTvOptions liveTvOptions, IHttpClient httpClient)
{
_logger = logger;
_fileSystem = fileSystem;
_mediaEncoder = mediaEncoder;
_appPaths = appPaths;
_json = json;
+ _liveTvOptions = liveTvOptions;
+ _httpClient = httpClient;
+ }
+
+ public string GetOutputPath(MediaSourceInfo mediaSource, string targetFile)
+ {
+ 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
+ {
+ try
+ {
+ File.Delete(tempfile);
+ }
+ catch (Exception ex)
+ {
+ _logger.ErrorException("Error deleting recording temp file", ex);
+ }
+ }
+ }
+
+ public async Task RecordInternal(MediaSourceInfo mediaSource, string tempFile, string targetFile, TimeSpan duration, Action onStarted, CancellationToken cancellationToken)
+ {
+ var httpRequestOptions = new HttpRequestOptions()
+ {
+ Url = mediaSource.Path
+ };
+
+ httpRequestOptions.BufferContent = false;
+
+ using (var response = await _httpClient.SendAsync(httpRequestOptions, "GET").ConfigureAwait(false))
+ {
+ _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));
@@ -52,12 +139,12 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
UseShellExecute = false,
// Must consume both stdout and stderr or deadlocks may occur
- RedirectStandardOutput = true,
+ //RedirectStandardOutput = true,
RedirectStandardError = true,
RedirectStandardInput = true,
FileName = _mediaEncoder.EncoderPath,
- Arguments = GetCommandLineArgs(mediaSource, targetFile, duration),
+ Arguments = GetCommandLineArgs(mediaSource, inputFile, targetFile, duration),
WindowStyle = ProcessWindowStyle.Hidden,
ErrorDialog = false
@@ -78,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);
@@ -87,24 +174,24 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
cancellationToken.Register(Stop);
// MUST read both stdout and stderr asynchronously or a deadlock may occurr
- process.BeginOutputReadLine();
+ //process.BeginOutputReadLine();
onStarted();
// 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))
{
var maxBitrate = 25000000;
videoArgs = string.Format(
- "-codec:v:0 libx264 -force_key_frames expr:gte(t,n_forced*5) {0} -pix_fmt yuv420p -preset superfast -crf 23 -b:v {1} -maxrate {1} -bufsize ({1}*2) -vsync vfr -profile:v high -level 41",
+ "-codec:v:0 libx264 -force_key_frames expr:gte(t,n_forced*5) {0} -pix_fmt yuv420p -preset superfast -crf 23 -b:v {1} -maxrate {1} -bufsize ({1}*2) -vsync -1 -profile:v high -level 41",
GetOutputSizeParam(),
maxBitrate.ToString(CultureInfo.InvariantCulture));
}
@@ -113,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 (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";
}
@@ -175,9 +270,6 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
//process.Kill();
_process.StandardInput.WriteLine("q");
-
- // Need to wait because killing is asynchronous
- _process.WaitForExit(5000);
}
catch (Exception ex)
{
diff --git a/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/IRecorder.cs b/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/IRecorder.cs
index 268a4f751..5706b6ae9 100644
--- a/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/IRecorder.cs
+++ b/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/IRecorder.cs
@@ -17,5 +17,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>Task.</returns>
Task Record(MediaSourceInfo mediaSource, string targetFile, TimeSpan duration, Action onStarted, CancellationToken cancellationToken);
+
+ string GetOutputPath(MediaSourceInfo mediaSource, string targetFile);
}
}
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 3849f44ab..64af35a9a 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,9 +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 }
+ IncludeItemTypes = new[] { typeof(LiveTvChannel).Name },
+ SortBy = new[] { ItemSortBy.SortName },
+ TopParentIds = new[] { topFolder.Id.ToString("N") }
}).Cast<LiveTvChannel>();
@@ -164,7 +175,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv
var val = query.IsFavorite.Value;
channels = channels
- .Where(i => _userDataManager.GetUserData(user.Id, i.GetUserDataKey()).IsFavorite == val);
+ .Where(i => _userDataManager.GetUserData(user, i).IsFavorite == val);
}
if (query.IsLiked.HasValue)
@@ -174,7 +185,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv
channels = channels
.Where(i =>
{
- var likes = _userDataManager.GetUserData(user.Id, i.GetUserDataKey()).Likes;
+ var likes = _userDataManager.GetUserData(user, i).Likes;
return likes.HasValue && likes.Value == val;
});
@@ -187,7 +198,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv
channels = channels
.Where(i =>
{
- var likes = _userDataManager.GetUserData(user.Id, i.GetUserDataKey()).Likes;
+ var likes = _userDataManager.GetUserData(user, i).Likes;
return likes.HasValue && likes.Value != val;
});
@@ -200,7 +211,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv
{
if (enableFavoriteSorting)
{
- var userData = _userDataManager.GetUserData(user.Id, i.GetUserDataKey());
+ var userData = _userDataManager.GetUserData(user, i);
if (userData.IsFavorite)
{
@@ -511,14 +522,22 @@ namespace MediaBrowser.Server.Implementations.LiveTv
if (!(service is EmbyTV.EmbyTV))
{
// We can't trust that we'll be able to direct stream it through emby server, no matter what the provider says
- mediaSource.SupportsDirectStream = true;
+ mediaSource.SupportsDirectStream = false;
mediaSource.SupportsTranscoding = true;
+ foreach (var stream in mediaSource.MediaStreams)
+ {
+ if (stream.Type == MediaStreamType.Video && string.IsNullOrWhiteSpace(stream.NalLengthSize))
+ {
+ stream.NalLengthSize = "0";
+ }
+ }
}
}
private async Task<LiveTvChannel> GetChannel(ChannelInfo channelInfo, string serviceName, Guid parentFolderId, CancellationToken cancellationToken)
{
var isNew = false;
+ var forceUpdate = false;
var id = _tvDtoService.GetInternalChannelId(serviceName, channelInfo.Id);
@@ -568,10 +587,12 @@ namespace MediaBrowser.Server.Implementations.LiveTv
if (!string.IsNullOrWhiteSpace(channelInfo.ImagePath))
{
item.SetImagePath(ImageType.Primary, channelInfo.ImagePath);
+ forceUpdate = true;
}
else if (!string.IsNullOrWhiteSpace(channelInfo.ImageUrl))
{
item.SetImagePath(ImageType.Primary, channelInfo.ImageUrl);
+ forceUpdate = true;
}
}
@@ -580,9 +601,18 @@ namespace MediaBrowser.Server.Implementations.LiveTv
item.Name = channelInfo.Name;
}
+ if (isNew)
+ {
+ await _libraryManager.CreateItem(item, cancellationToken).ConfigureAwait(false);
+ }
+ else if (forceUpdate)
+ {
+ await _libraryManager.UpdateItem(item, ItemUpdateType.MetadataImport, cancellationToken).ConfigureAwait(false);
+ }
+
await item.RefreshMetadata(new MetadataRefreshOptions(_fileSystem)
{
- ForceSave = isNew
+ ForceSave = isNew || forceUpdate
}, cancellationToken);
@@ -626,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;
@@ -643,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;
@@ -864,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 },
@@ -879,7 +929,9 @@ namespace MediaBrowser.Server.Implementations.LiveTv
StartIndex = query.StartIndex,
Limit = query.Limit,
SortBy = query.SortBy,
- SortOrder = query.SortOrder ?? SortOrder.Ascending
+ SortOrder = query.SortOrder ?? SortOrder.Ascending,
+ EnableTotalRecordCount = query.EnableTotalRecordCount,
+ TopParentIds = new[] { topFolder.Id.ToString("N") }
};
if (query.HasAired.HasValue)
@@ -896,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>
{
@@ -911,15 +963,25 @@ 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 },
IsAiring = query.IsAiring,
IsMovie = query.IsMovie,
IsSports = query.IsSports,
- IsKids = query.IsKids
+ IsKids = query.IsKids,
+ EnableTotalRecordCount = query.EnableTotalRecordCount,
+ SortBy = new[] { ItemSortBy.StartDate },
+ TopParentIds = new[] { topFolder.Id.ToString("N") }
};
+ if (query.Limit.HasValue)
+ {
+ internalQuery.Limit = Math.Max(query.Limit.Value * 4, 200);
+ }
+
if (query.HasAired.HasValue)
{
if (query.HasAired.Value)
@@ -936,15 +998,10 @@ namespace MediaBrowser.Server.Implementations.LiveTv
var programList = programs.ToList();
- var genres = programList.SelectMany(i => i.Genres)
- .Where(i => !string.IsNullOrWhiteSpace(i))
- .DistinctNames()
- .Select(i => _libraryManager.GetGenre(i))
- .DistinctBy(i => i.Id)
- .ToDictionary(i => i.Name, StringComparer.OrdinalIgnoreCase);
+ var factorChannelWatchCount = (query.IsAiring ?? false) || (query.IsKids ?? false) || (query.IsSports ?? false) || (query.IsMovie ?? false);
programs = programList.OrderBy(i => i.HasImage(ImageType.Primary) ? 0 : 1)
- .ThenByDescending(i => GetRecommendationScore(i, user.Id, genres))
+ .ThenByDescending(i => GetRecommendationScore(i, user.Id, factorChannelWatchCount))
.ThenBy(i => i.StartDate);
if (query.Limit.HasValue)
@@ -971,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>
{
@@ -982,7 +1039,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv
return result;
}
- private int GetRecommendationScore(LiveTvProgram program, Guid userId, Dictionary<string, Genre> genres)
+ private int GetRecommendationScore(LiveTvProgram program, Guid userId, bool factorChannelWatchCount)
{
var score = 0;
@@ -998,7 +1055,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv
var channel = GetInternalChannel(program.ChannelId);
- var channelUserdata = _userDataManager.GetUserData(userId, channel.GetUserDataKey());
+ var channelUserdata = _userDataManager.GetUserData(userId, channel);
if (channelUserdata.Likes ?? false)
{
@@ -1014,41 +1071,12 @@ namespace MediaBrowser.Server.Implementations.LiveTv
score += 3;
}
- score += GetGenreScore(program.Genres, userId, genres);
-
- return score;
- }
-
- private int GetGenreScore(IEnumerable<string> programGenres, Guid userId, Dictionary<string, Genre> genres)
- {
- return programGenres.Select(i =>
+ if (factorChannelWatchCount)
{
- var score = 0;
-
- Genre genre;
-
- if (genres.TryGetValue(i, out genre))
- {
- var genreUserdata = _userDataManager.GetUserData(userId, genre.GetUserDataKey());
-
- if (genreUserdata.Likes ?? false)
- {
- score++;
- }
- else if (!(genreUserdata.Likes ?? true))
- {
- score--;
- }
-
- if (genreUserdata.IsFavorite)
- {
- score += 2;
- }
- }
-
- return score;
+ score += channelUserdata.PlayCount;
+ }
- }).Sum();
+ return score;
}
private async Task AddRecordingInfo(IEnumerable<Tuple<BaseItemDto, string, string>> programs, CancellationToken cancellationToken)
@@ -1104,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
@@ -1184,8 +1214,6 @@ namespace MediaBrowser.Server.Implementations.LiveTv
var item = await GetChannel(channelInfo.Item2, channelInfo.Item1, parentFolderId, cancellationToken).ConfigureAwait(false);
list.Add(item);
-
- _libraryManager.RegisterItem(item);
}
catch (OperationCanceledException)
{
@@ -1256,11 +1284,11 @@ namespace MediaBrowser.Server.Implementations.LiveTv
private async Task CleanDatabaseInternal(List<Guid> currentIdList, string[] validTypes, IProgress<double> progress, CancellationToken cancellationToken)
{
- var list = _itemRepo.GetItemIds(new InternalItemsQuery
+ var list = _itemRepo.GetItemIdsList(new InternalItemsQuery
{
IncludeItemTypes = validTypes
- }).Items.ToList();
+ }).ToList();
var numComplete = 0;
@@ -1373,6 +1401,40 @@ namespace MediaBrowser.Server.Implementations.LiveTv
}
}
+ private QueryResult<BaseItem> GetEmbyRecordings(RecordingQuery query, User user)
+ {
+ if (user == null || (query.IsInProgress ?? false))
+ {
+ return new QueryResult<BaseItem>();
+ }
+
+ var folders = EmbyTV.EmbyTV.Current.GetRecordingFolders()
+ .SelectMany(i => i.Locations)
+ .Distinct(StringComparer.OrdinalIgnoreCase)
+ .Select(i => _libraryManager.FindByPath(i, true))
+ .Where(i => i != null)
+ .Where(i => i.IsVisibleStandalone(user))
+ .ToList();
+
+ if (folders.Count == 0)
+ {
+ return new QueryResult<BaseItem>();
+ }
+
+ return _libraryManager.GetItemsResult(new InternalItemsQuery(user)
+ {
+ MediaTypes = new[] { MediaType.Video },
+ Recursive = true,
+ AncestorIds = folders.Select(i => i.Id.ToString("N")).ToArray(),
+ IsFolder = false,
+ ExcludeLocationTypes = new[] { LocationType.Virtual },
+ Limit = Math.Min(200, query.Limit ?? int.MaxValue),
+ SortBy = new[] { ItemSortBy.DateCreated },
+ SortOrder = SortOrder.Descending,
+ EnableTotalRecordCount = query.EnableTotalRecordCount
+ });
+ }
+
public async Task<QueryResult<BaseItem>> GetInternalRecordings(RecordingQuery query, CancellationToken cancellationToken)
{
var user = string.IsNullOrEmpty(query.UserId) ? null : _userManager.GetUserById(query.UserId);
@@ -1381,6 +1443,11 @@ namespace MediaBrowser.Server.Implementations.LiveTv
return new QueryResult<BaseItem>();
}
+ if (_services.Count == 1)
+ {
+ return GetEmbyRecordings(query, user);
+ }
+
await RefreshRecordings(cancellationToken).ConfigureAwait(false);
var internalQuery = new InternalItemsQuery(user)
@@ -1393,7 +1460,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))
@@ -1506,6 +1573,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv
{
dto.ChannelName = channel.Name;
dto.MediaType = channel.MediaType;
+ dto.ChannelNumber = channel.Number;
if (channel.HasImage(ImageType.Primary))
{
@@ -1596,7 +1664,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>
{
@@ -1623,6 +1691,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);
@@ -1724,6 +1804,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)
@@ -1739,6 +1827,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)
@@ -1835,9 +1931,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)
{
@@ -1845,6 +1942,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv
var channel = tuple.Item2;
dto.Number = channel.Number;
+ dto.ChannelNumber = channel.Number;
dto.ChannelType = channel.ChannelType;
dto.ServiceName = GetService(channel).Name;
@@ -1923,10 +2021,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);
@@ -1957,9 +2052,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)
@@ -1972,8 +2087,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)
@@ -2048,7 +2183,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv
}, cancellationToken).ConfigureAwait(false);
- var recordings = recordingResult.Items.Cast<ILiveTvRecording>().ToList();
+ var recordings = recordingResult.Items.OfType<ILiveTvRecording>().ToList();
var groups = new List<BaseItemDto>();
@@ -2389,6 +2524,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();
@@ -2450,7 +2658,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv
public List<NameValuePair> GetSatIniMappings()
{
- var names = GetType().Assembly.GetManifestResourceNames().Where(i => i.IndexOf("SatIp.ini.satellite", StringComparison.OrdinalIgnoreCase) != -1).ToList();
+ var names = GetType().Assembly.GetManifestResourceNames().Where(i => i.IndexOf("SatIp.ini", StringComparison.OrdinalIgnoreCase) != -1).ToList();
return names.Select(GetSatIniMappings).Where(i => i != null).DistinctBy(i => i.Value.Split('|')[0]).ToList();
}
@@ -2472,13 +2680,34 @@ namespace MediaBrowser.Server.Implementations.LiveTv
return null;
}
+ var srch = "SatIp.ini.";
+ var filename = Path.GetFileName(resource);
+
return new NameValuePair
{
Name = satType1 + " " + satType2,
- Value = satType2 + "|" + Path.GetFileName(resource)
+ Value = satType2 + "|" + filename.Substring(filename.IndexOf(srch) + srch.Length)
};
}
}
}
+
+ public Task<List<ChannelInfo>> GetSatChannelScanResult(TunerHostInfo info, CancellationToken cancellationToken)
+ {
+ 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/LiveTv/ProgramImageProvider.cs b/MediaBrowser.Server.Implementations/LiveTv/ProgramImageProvider.cs
index ab8ec720b..3f0538bd0 100644
--- a/MediaBrowser.Server.Implementations/LiveTv/ProgramImageProvider.cs
+++ b/MediaBrowser.Server.Implementations/LiveTv/ProgramImageProvider.cs
@@ -74,7 +74,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv
}
}
- public bool HasChanged(IHasMetadata item, MetadataStatus status, IDirectoryService directoryService)
+ public bool HasChanged(IHasMetadata item, IDirectoryService directoryService)
{
var liveTvItem = item as LiveTvProgram;
diff --git a/MediaBrowser.Server.Implementations/LiveTv/RecordingImageProvider.cs b/MediaBrowser.Server.Implementations/LiveTv/RecordingImageProvider.cs
index fce3223ea..25678c29d 100644
--- a/MediaBrowser.Server.Implementations/LiveTv/RecordingImageProvider.cs
+++ b/MediaBrowser.Server.Implementations/LiveTv/RecordingImageProvider.cs
@@ -68,7 +68,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv
get { return 0; }
}
- public bool HasChanged(IHasMetadata item, MetadataStatus status, IDirectoryService directoryService)
+ public bool HasChanged(IHasMetadata item, IDirectoryService directoryService)
{
var liveTvItem = item as ILiveTvRecording;
diff --git a/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/BaseTunerHost.cs b/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/BaseTunerHost.cs
index 02a8d6938..9bb5b4fd7 100644
--- a/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/BaseTunerHost.cs
+++ b/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/BaseTunerHost.cs
@@ -224,7 +224,11 @@ namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts
var stream = await GetChannelStream(host, channelId, streamId, cancellationToken).ConfigureAwait(false);
- //await AddMediaInfo(stream, false, resourcePool, cancellationToken).ConfigureAwait(false);
+ if (EnableMediaProbing)
+ {
+ await AddMediaInfo(stream, false, resourcePool, cancellationToken).ConfigureAwait(false);
+ }
+
return new Tuple<MediaSourceInfo, SemaphoreSlim>(stream, resourcePool);
}
catch (Exception ex)
@@ -239,6 +243,11 @@ namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts
throw new LiveTvConflictException();
}
+ protected virtual bool EnableMediaProbing
+ {
+ get { return false; }
+ }
+
protected async Task<bool> IsAvailable(TunerHostInfo tuner, string channelId, CancellationToken cancellationToken)
{
try
@@ -268,6 +277,25 @@ namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts
return _semaphoreLocks.GetOrAdd(url, key => new SemaphoreSlim(1, 1));
}
+ private async Task AddMediaInfo(MediaSourceInfo mediaSource, bool isAudio, SemaphoreSlim resourcePool, CancellationToken cancellationToken)
+ {
+ await resourcePool.WaitAsync(cancellationToken).ConfigureAwait(false);
+
+ try
+ {
+ await AddMediaInfoInternal(mediaSource, isAudio, cancellationToken).ConfigureAwait(false);
+
+ // Leave the resource locked. it will be released upstream
+ }
+ catch (Exception)
+ {
+ // Release the resource if there's some kind of failure.
+ resourcePool.Release();
+
+ throw;
+ }
+ }
+
private async Task AddMediaInfoInternal(MediaSourceInfo mediaSource, bool isAudio, CancellationToken cancellationToken)
{
var originalRuntime = mediaSource.RunTimeTicks;
diff --git a/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs b/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs
index db7f6f86c..69b6fb5a9 100644
--- a/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs
+++ b/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs
@@ -17,6 +17,7 @@ using System.Threading.Tasks;
using MediaBrowser.Common.Extensions;
using MediaBrowser.Controller.MediaEncoding;
using MediaBrowser.Model.Configuration;
+using MediaBrowser.Model.Net;
namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts.HdHomerun
{
@@ -59,7 +60,15 @@ namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts.HdHomerun
return id;
}
- protected override async Task<IEnumerable<ChannelInfo>> GetChannelsInternal(TunerHostInfo info, CancellationToken cancellationToken)
+ public string ApplyDuration(string streamPath, TimeSpan duration)
+ {
+ streamPath += streamPath.IndexOf('?') == -1 ? "?" : "&";
+ streamPath += "duration=" + Convert.ToInt32(duration.TotalSeconds).ToString(CultureInfo.InvariantCulture);
+
+ return streamPath;
+ }
+
+ private async Task<IEnumerable<Channels>> GetLineup(TunerHostInfo info, CancellationToken cancellationToken)
{
var options = new HttpRequestOptions
{
@@ -68,45 +77,61 @@ namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts.HdHomerun
};
using (var stream = await _httpClient.Get(options))
{
- var root = JsonSerializer.DeserializeFromStream<List<Channels>>(stream);
+ var lineup = JsonSerializer.DeserializeFromStream<List<Channels>>(stream) ?? new List<Channels>();
- if (root != null)
+ if (info.ImportFavoritesOnly)
{
- var result = root.Select(i => new ChannelInfo
- {
- Name = i.GuideName,
- Number = i.GuideNumber.ToString(CultureInfo.InvariantCulture),
- Id = GetChannelId(info, i),
- IsFavorite = i.Favorite,
- TunerHostId = info.Id
+ lineup = lineup.Where(i => i.Favorite).ToList();
+ }
- });
+ return lineup.Where(i => !i.DRM).ToList();
+ }
+ }
- if (info.ImportFavoritesOnly)
- {
- result = result.Where(i => i.IsFavorite ?? true).ToList();
- }
+ protected override async Task<IEnumerable<ChannelInfo>> GetChannelsInternal(TunerHostInfo info, CancellationToken cancellationToken)
+ {
+ var lineup = await GetLineup(info, cancellationToken).ConfigureAwait(false);
- return result;
- }
- return new List<ChannelInfo>();
- }
+ return lineup.Select(i => new ChannelInfo
+ {
+ Name = i.GuideName,
+ Number = i.GuideNumber.ToString(CultureInfo.InvariantCulture),
+ Id = GetChannelId(info, i),
+ IsFavorite = i.Favorite,
+ TunerHostId = info.Id,
+ IsHD = i.HD == 1,
+ AudioCodec = i.AudioCodec,
+ VideoCodec = i.VideoCodec
+ });
}
private async Task<string> GetModelInfo(TunerHostInfo info, CancellationToken cancellationToken)
{
- using (var stream = await _httpClient.Get(new HttpRequestOptions()
+ try
{
- Url = string.Format("{0}/discover.json", GetApiUrl(info, false)),
- CancellationToken = cancellationToken,
- CacheLength = TimeSpan.FromDays(1),
- CacheMode = CacheMode.Unconditional,
- TimeoutMs = Convert.ToInt32(TimeSpan.FromSeconds(5).TotalMilliseconds)
- }))
+ using (var stream = await _httpClient.Get(new HttpRequestOptions()
+ {
+ Url = string.Format("{0}/discover.json", GetApiUrl(info, false)),
+ CancellationToken = cancellationToken,
+ CacheLength = TimeSpan.FromDays(1),
+ CacheMode = CacheMode.Unconditional,
+ TimeoutMs = Convert.ToInt32(TimeSpan.FromSeconds(5).TotalMilliseconds)
+ }))
+ {
+ var response = JsonSerializer.DeserializeFromStream<DiscoverResponse>(stream);
+
+ return response.ModelNumber;
+ }
+ }
+ catch (HttpException ex)
{
- var response = JsonSerializer.DeserializeFromStream<DiscoverResponse>(stream);
+ if (ex.StatusCode.HasValue && ex.StatusCode.Value == System.Net.HttpStatusCode.NotFound)
+ {
+ // HDHR4 doesn't have this api
+ return "HDHR";
+ }
- return response.ModelNumber;
+ throw;
}
}
@@ -226,19 +251,24 @@ namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts.HdHomerun
{
public string GuideNumber { get; set; }
public string GuideName { get; set; }
+ public string VideoCodec { get; set; }
+ public string AudioCodec { get; set; }
public string URL { get; set; }
public bool Favorite { get; set; }
public bool DRM { get; set; }
+ public int HD { get; set; }
}
- private MediaSourceInfo GetMediaSource(TunerHostInfo info, string channelId, string profile)
+ private async Task<MediaSourceInfo> GetMediaSource(TunerHostInfo info, string channelId, string profile)
{
int? width = null;
int? height = null;
bool isInterlaced = true;
- var videoCodec = !string.IsNullOrWhiteSpace(GetEncodingOptions().HardwareAccelerationType) ? null : "mpeg2video";
+ string videoCodec = null;
+ string audioCodec = "ac3";
int? videoBitrate = null;
+ int? audioBitrate = null;
if (string.Equals(profile, "mobile", StringComparison.OrdinalIgnoreCase))
{
@@ -256,18 +286,10 @@ namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts.HdHomerun
videoCodec = "h264";
videoBitrate = 15000000;
}
- else if (string.Equals(profile, "internet720", StringComparison.OrdinalIgnoreCase))
- {
- width = 1280;
- height = 720;
- isInterlaced = false;
- videoCodec = "h264";
- videoBitrate = 8000000;
- }
else if (string.Equals(profile, "internet540", StringComparison.OrdinalIgnoreCase))
{
- width = 1280;
- height = 720;
+ width = 960;
+ height = 546;
isInterlaced = false;
videoCodec = "h264";
videoBitrate = 2500000;
@@ -297,6 +319,32 @@ namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts.HdHomerun
videoBitrate = 1000000;
}
+ if (string.IsNullOrWhiteSpace(videoCodec))
+ {
+ var channels = await GetChannels(info, true, CancellationToken.None).ConfigureAwait(false);
+ var channel = channels.FirstOrDefault(i => string.Equals(i.Number, channelId, StringComparison.OrdinalIgnoreCase));
+ if (channel != null)
+ {
+ videoCodec = channel.VideoCodec;
+ audioCodec = channel.AudioCodec;
+
+ videoBitrate = (channel.IsHD ?? true) ? 15000000 : 2000000;
+ audioBitrate = (channel.IsHD ?? true) ? 448000 : 192000;
+ }
+ }
+
+ // normalize
+ if (string.Equals(videoCodec, "mpeg2", StringComparison.OrdinalIgnoreCase))
+ {
+ videoCodec = "mpeg2video";
+ }
+
+ string nal = null;
+ if (string.Equals(videoCodec, "h264", StringComparison.OrdinalIgnoreCase))
+ {
+ nal = "0";
+ }
+
var url = GetApiUrl(info, true) + "/auto/v" + channelId;
if (!string.IsNullOrWhiteSpace(profile) && !string.Equals(profile, "native", StringComparison.OrdinalIgnoreCase))
@@ -319,16 +367,17 @@ namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts.HdHomerun
Codec = videoCodec,
Width = width,
Height = height,
- BitRate = videoBitrate
-
+ BitRate = videoBitrate,
+ NalLengthSize = nal
+
},
new MediaStream
{
Type = MediaStreamType.Audio,
// Set the index to -1 because we don't know the exact index of the audio stream within the container
Index = -1,
- Codec = "ac3",
- BitRate = 192000
+ Codec = audioCodec,
+ BitRate = audioBitrate
}
},
RequiresOpening = false,
@@ -337,7 +386,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts.HdHomerun
Container = "ts",
Id = profile,
SupportsDirectPlay = true,
- SupportsDirectStream = true,
+ SupportsDirectStream = false,
SupportsTranscoding = true
};
@@ -364,21 +413,22 @@ namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts.HdHomerun
}
var hdhrId = GetHdHrIdFromChannelId(channelId);
- list.Add(GetMediaSource(info, hdhrId, "native"));
+ list.Add(await GetMediaSource(info, hdhrId, "native").ConfigureAwait(false));
try
{
string model = await GetModelInfo(info, cancellationToken).ConfigureAwait(false);
model = model ?? string.Empty;
- if (model.IndexOf("hdtc", StringComparison.OrdinalIgnoreCase) != -1)
+ if (info.AllowHWTranscoding && (model.IndexOf("hdtc", StringComparison.OrdinalIgnoreCase) != -1))
{
- list.Insert(0, GetMediaSource(info, hdhrId, "heavy"));
+ list.Add(await GetMediaSource(info, hdhrId, "heavy").ConfigureAwait(false));
- list.Add(GetMediaSource(info, hdhrId, "internet480"));
- list.Add(GetMediaSource(info, hdhrId, "internet360"));
- list.Add(GetMediaSource(info, hdhrId, "internet240"));
- list.Add(GetMediaSource(info, hdhrId, "mobile"));
+ list.Add(await GetMediaSource(info, hdhrId, "internet540").ConfigureAwait(false));
+ list.Add(await GetMediaSource(info, hdhrId, "internet480").ConfigureAwait(false));
+ list.Add(await GetMediaSource(info, hdhrId, "internet360").ConfigureAwait(false));
+ list.Add(await GetMediaSource(info, hdhrId, "internet240").ConfigureAwait(false));
+ list.Add(await GetMediaSource(info, hdhrId, "mobile").ConfigureAwait(false));
}
}
catch (Exception ex)
@@ -409,7 +459,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts.HdHomerun
}
var hdhrId = GetHdHrIdFromChannelId(channelId);
- return GetMediaSource(info, hdhrId, streamId);
+ return await GetMediaSource(info, hdhrId, streamId).ConfigureAwait(false);
}
public async Task Validate(TunerHostInfo info)
@@ -419,16 +469,29 @@ namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts.HdHomerun
return;
}
- // Test it by pulling down the lineup
- using (var stream = await _httpClient.Get(new HttpRequestOptions
+ try
{
- Url = string.Format("{0}/discover.json", GetApiUrl(info, false)),
- CancellationToken = CancellationToken.None
- }))
+ // Test it by pulling down the lineup
+ using (var stream = await _httpClient.Get(new HttpRequestOptions
+ {
+ Url = string.Format("{0}/discover.json", GetApiUrl(info, false)),
+ CancellationToken = CancellationToken.None
+ }))
+ {
+ var response = JsonSerializer.DeserializeFromStream<DiscoverResponse>(stream);
+
+ info.DeviceId = response.DeviceID;
+ }
+ }
+ catch (HttpException ex)
{
- var response = JsonSerializer.DeserializeFromStream<DiscoverResponse>(stream);
+ if (ex.StatusCode.HasValue && ex.StatusCode.Value == System.Net.HttpStatusCode.NotFound)
+ {
+ // HDHR4 doesn't have this api
+ return;
+ }
- info.DeviceId = response.DeviceID;
+ throw;
}
}
diff --git a/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs b/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs
index 523f14dfc..2a974b545 100644
--- a/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs
+++ b/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs
@@ -134,7 +134,9 @@ namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts
}
},
RequiresOpening = false,
- RequiresClosing = false
+ RequiresClosing = false,
+
+ ReadAtNativeFramerate = true
};
return new List<MediaSourceInfo> { mediaSource };
@@ -146,5 +148,10 @@ namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts
{
return Task.FromResult(true);
}
+
+ public string ApplyDuration(string streamPath, TimeSpan duration)
+ {
+ return streamPath;
+ }
}
}
diff --git a/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/SatIp/ChannelScan.cs b/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/SatIp/ChannelScan.cs
new file mode 100644
index 000000000..fdeae25b0
--- /dev/null
+++ b/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/SatIp/ChannelScan.cs
@@ -0,0 +1,105 @@
+using System;
+using System.Collections.Generic;
+using System.Globalization;
+using System.IO;
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+using IniParser;
+using IniParser.Model;
+using MediaBrowser.Controller.LiveTv;
+using MediaBrowser.Model.LiveTv;
+using MediaBrowser.Model.Logging;
+using MediaBrowser.Server.Implementations.LiveTv.TunerHosts.SatIp.Rtsp;
+
+namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts.SatIp
+{
+ public class ChannelScan
+ {
+ private readonly ILogger _logger;
+
+ public ChannelScan(ILogger logger)
+ {
+ _logger = logger;
+ }
+
+ public async Task<List<ChannelInfo>> Scan(TunerHostInfo info, CancellationToken cancellationToken)
+ {
+ var ini = info.SourceA.Split('|')[1];
+ var resource = GetType().Assembly.GetManifestResourceNames().FirstOrDefault(i => i.EndsWith(ini, StringComparison.OrdinalIgnoreCase));
+
+ _logger.Info("Opening ini file {0}", resource);
+ var list = new List<ChannelInfo>();
+
+ using (var stream = GetType().Assembly.GetManifestResourceStream(resource))
+ {
+ using (var reader = new StreamReader(stream))
+ {
+ var parser = new StreamIniDataParser();
+ var data = parser.ReadData(reader);
+
+ var count = GetInt(data, "DVB", "0", 0);
+
+ _logger.Info("DVB Count: {0}", count);
+
+ var index = 1;
+ var source = "1";
+
+ while (index <= count)
+ {
+ cancellationToken.ThrowIfCancellationRequested();
+
+ using (var rtspSession = new RtspSession(info.Url, _logger))
+ {
+ float percent = count == 0 ? 0 : (float)(index) / count;
+ percent = Math.Max(percent * 100, 100);
+
+ //SetControlPropertyThreadSafe(pgbSearchResult, "Value", (int)percent);
+ var strArray = data["DVB"][index.ToString(CultureInfo.InvariantCulture)].Split(',');
+
+ string tuning;
+ if (strArray[4] == "S2")
+ {
+ tuning = string.Format("src={0}&freq={1}&pol={2}&sr={3}&fec={4}&msys=dvbs2&mtype={5}&plts=on&ro=0.35&pids=0,16,17,18,20", source, strArray[0], strArray[1].ToLower(), strArray[2].ToLower(), strArray[3], strArray[5].ToLower());
+ }
+ else
+ {
+ tuning = string.Format("src={0}&freq={1}&pol={2}&sr={3}&fec={4}&msys=dvbs&mtype={5}&pids=0,16,17,18,20", source, strArray[0], strArray[1].ToLower(), strArray[2], strArray[3], strArray[5].ToLower());
+ }
+
+ rtspSession.Setup(tuning, "unicast");
+
+ rtspSession.Play(string.Empty);
+
+ int signallevel;
+ int signalQuality;
+ rtspSession.Describe(out signallevel, out signalQuality);
+
+ await Task.Delay(500).ConfigureAwait(false);
+ index++;
+ }
+ }
+ }
+ }
+
+ return list;
+ }
+
+ private int GetInt(IniData data, string s1, string s2, int defaultValue)
+ {
+ var value = data[s1][s2];
+ int numericValue;
+ if (int.TryParse(value, NumberStyles.Any, CultureInfo.InvariantCulture, out numericValue))
+ {
+ return numericValue;
+ }
+
+ return defaultValue;
+ }
+ }
+
+ public class SatChannel
+ {
+ // TODO: Add properties
+ }
+}
diff --git a/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/SatIp/Rtsp/RtspMethod.cs b/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/SatIp/Rtsp/RtspMethod.cs
new file mode 100644
index 000000000..5f286f1db
--- /dev/null
+++ b/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/SatIp/Rtsp/RtspMethod.cs
@@ -0,0 +1,88 @@
+/*
+ Copyright (C) <2007-2016> <Kay Diefenthal>
+
+ SatIp.RtspSample is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ SatIp.RtspSample is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with SatIp.RtspSample. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+using System.Collections.Generic;
+
+namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts.SatIp.Rtsp
+{
+ /// <summary>
+ /// Standard RTSP request methods.
+ /// </summary>
+ public sealed class RtspMethod
+ {
+ public override int GetHashCode()
+ {
+ return (_name != null ? _name.GetHashCode() : 0);
+ }
+
+ private readonly string _name;
+ private static readonly IDictionary<string, RtspMethod> _values = new Dictionary<string, RtspMethod>();
+
+ public static readonly RtspMethod Describe = new RtspMethod("DESCRIBE");
+ public static readonly RtspMethod Announce = new RtspMethod("ANNOUNCE");
+ public static readonly RtspMethod GetParameter = new RtspMethod("GET_PARAMETER");
+ public static readonly RtspMethod Options = new RtspMethod("OPTIONS");
+ public static readonly RtspMethod Pause = new RtspMethod("PAUSE");
+ public static readonly RtspMethod Play = new RtspMethod("PLAY");
+ public static readonly RtspMethod Record = new RtspMethod("RECORD");
+ public static readonly RtspMethod Redirect = new RtspMethod("REDIRECT");
+ public static readonly RtspMethod Setup = new RtspMethod("SETUP");
+ public static readonly RtspMethod SetParameter = new RtspMethod("SET_PARAMETER");
+ public static readonly RtspMethod Teardown = new RtspMethod("TEARDOWN");
+
+ private RtspMethod(string name)
+ {
+ _name = name;
+ _values.Add(name, this);
+ }
+
+ public override string ToString()
+ {
+ return _name;
+ }
+
+ public override bool Equals(object obj)
+ {
+ var method = obj as RtspMethod;
+ if (method != null && this == method)
+ {
+ return true;
+ }
+ return false;
+ }
+
+ public static ICollection<RtspMethod> Values
+ {
+ get { return _values.Values; }
+ }
+
+ public static explicit operator RtspMethod(string name)
+ {
+ RtspMethod value;
+ if (!_values.TryGetValue(name, out value))
+ {
+ return null;
+ }
+ return value;
+ }
+
+ public static implicit operator string(RtspMethod method)
+ {
+ return method._name;
+ }
+ }
+}
diff --git a/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/SatIp/Rtsp/RtspRequest.cs b/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/SatIp/Rtsp/RtspRequest.cs
new file mode 100644
index 000000000..600eda02d
--- /dev/null
+++ b/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/SatIp/Rtsp/RtspRequest.cs
@@ -0,0 +1,140 @@
+/*
+ Copyright (C) <2007-2016> <Kay Diefenthal>
+
+ SatIp.RtspSample is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ SatIp.RtspSample is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with SatIp.RtspSample. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+using System.Collections.Generic;
+using System.Text;
+
+namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts.SatIp.Rtsp
+{
+ /// <summary>
+ /// A simple class that can be used to serialise RTSP requests.
+ /// </summary>
+ public class RtspRequest
+ {
+ private readonly RtspMethod _method;
+ private readonly string _uri;
+ private readonly int _majorVersion;
+ private readonly int _minorVersion;
+ private IDictionary<string, string> _headers = new Dictionary<string, string>();
+ private string _body = string.Empty;
+
+ /// <summary>
+ /// Initialise a new instance of the <see cref="RtspRequest"/> class.
+ /// </summary>
+ /// <param name="method">The request method.</param>
+ /// <param name="uri">The request URI</param>
+ /// <param name="majorVersion">The major version number.</param>
+ /// <param name="minorVersion">The minor version number.</param>
+ public RtspRequest(RtspMethod method, string uri, int majorVersion, int minorVersion)
+ {
+ _method = method;
+ _uri = uri;
+ _majorVersion = majorVersion;
+ _minorVersion = minorVersion;
+ }
+
+ /// <summary>
+ /// Get the request method.
+ /// </summary>
+ public RtspMethod Method
+ {
+ get
+ {
+ return _method;
+ }
+ }
+
+ /// <summary>
+ /// Get the request URI.
+ /// </summary>
+ public string Uri
+ {
+ get
+ {
+ return _uri;
+ }
+ }
+
+ /// <summary>
+ /// Get the request major version number.
+ /// </summary>
+ public int MajorVersion
+ {
+ get
+ {
+ return _majorVersion;
+ }
+ }
+
+ /// <summary>
+ /// Get the request minor version number.
+ /// </summary>
+ public int MinorVersion
+ {
+ get
+ {
+ return _minorVersion;
+ }
+ }
+
+ /// <summary>
+ /// Get or set the request headers.
+ /// </summary>
+ public IDictionary<string, string> Headers
+ {
+ get
+ {
+ return _headers;
+ }
+ set
+ {
+ _headers = value;
+ }
+ }
+
+ /// <summary>
+ /// Get or set the request body.
+ /// </summary>
+ public string Body
+ {
+ get
+ {
+ return _body;
+ }
+ set
+ {
+ _body = value;
+ }
+ }
+
+ /// <summary>
+ /// Serialise this request.
+ /// </summary>
+ /// <returns>raw request bytes</returns>
+ public byte[] Serialise()
+ {
+ var request = new StringBuilder();
+ request.AppendFormat("{0} {1} RTSP/{2}.{3}\r\n", _method, _uri, _majorVersion, _minorVersion);
+ foreach (var header in _headers)
+ {
+ request.AppendFormat("{0}: {1}\r\n", header.Key, header.Value);
+ }
+ request.AppendFormat("\r\n{0}", _body);
+ return Encoding.UTF8.GetBytes(request.ToString());
+ }
+ }
+}
diff --git a/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/SatIp/Rtsp/RtspResponse.cs b/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/SatIp/Rtsp/RtspResponse.cs
new file mode 100644
index 000000000..97290623b
--- /dev/null
+++ b/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/SatIp/Rtsp/RtspResponse.cs
@@ -0,0 +1,149 @@
+/*
+ Copyright (C) <2007-2016> <Kay Diefenthal>
+
+ SatIp.RtspSample is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ SatIp.RtspSample is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with SatIp.RtspSample. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Text.RegularExpressions;
+
+namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts.SatIp.Rtsp
+{
+ /// <summary>
+ /// A simple class that can be used to deserialise RTSP responses.
+ /// </summary>
+ public class RtspResponse
+ {
+ private static readonly Regex RegexStatusLine = new Regex(@"RTSP/(\d+)\.(\d+)\s+(\d+)\s+([^.]+?)\r\n(.*)", RegexOptions.Singleline);
+
+ private int _majorVersion = 1;
+ private int _minorVersion;
+ private RtspStatusCode _statusCode;
+ private string _reasonPhrase;
+ private IDictionary<string, string> _headers;
+ private string _body;
+
+ /// <summary>
+ /// Initialise a new instance of the <see cref="RtspResponse"/> class.
+ /// </summary>
+ private RtspResponse()
+ {
+ }
+
+ /// <summary>
+ /// Get the response major version number.
+ /// </summary>
+ public int MajorVersion
+ {
+ get
+ {
+ return _majorVersion;
+ }
+ }
+
+ /// <summary>
+ /// Get the response minor version number.
+ /// </summary>
+ public int MinorVersion
+ {
+ get
+ {
+ return _minorVersion;
+ }
+ }
+
+ /// <summary>
+ /// Get the response status code.
+ /// </summary>
+ public RtspStatusCode StatusCode
+ {
+ get
+ {
+ return _statusCode;
+ }
+ }
+
+ /// <summary>
+ /// Get the response reason phrase.
+ /// </summary>
+ public string ReasonPhrase
+ {
+ get
+ {
+ return _reasonPhrase;
+ }
+ }
+
+ /// <summary>
+ /// Get the response headers.
+ /// </summary>
+ public IDictionary<string, string> Headers
+ {
+ get
+ {
+ return _headers;
+ }
+ }
+
+ /// <summary>
+ /// Get the response body.
+ /// </summary>
+ public string Body
+ {
+ get
+ {
+ return _body;
+ }
+ set
+ {
+ _body = value;
+ }
+ }
+
+ /// <summary>
+ /// Deserialise/parse an RTSP response.
+ /// </summary>
+ /// <param name="responseBytes">The raw response bytes.</param>
+ /// <param name="responseByteCount">The number of valid bytes in the response.</param>
+ /// <returns>a response object</returns>
+ public static RtspResponse Deserialise(byte[] responseBytes, int responseByteCount)
+ {
+ var response = new RtspResponse();
+ var responseString = Encoding.UTF8.GetString(responseBytes, 0, responseByteCount);
+
+ var m = RegexStatusLine.Match(responseString);
+ if (m.Success)
+ {
+ response._majorVersion = int.Parse(m.Groups[1].Captures[0].Value);
+ response._minorVersion = int.Parse(m.Groups[2].Captures[0].Value);
+ response._statusCode = (RtspStatusCode)int.Parse(m.Groups[3].Captures[0].Value);
+ response._reasonPhrase = m.Groups[4].Captures[0].Value;
+ responseString = m.Groups[5].Captures[0].Value;
+ }
+
+ var sections = responseString.Split(new[] { "\r\n\r\n" }, StringSplitOptions.None);
+ response._body = sections[1];
+ var headers = sections[0].Split(new[] { "\r\n" }, StringSplitOptions.None);
+ response._headers = new Dictionary<string, string>();
+ foreach (var headerInfo in headers.Select(header => header.Split(':')))
+ {
+ response._headers.Add(headerInfo[0], headerInfo[1].Trim());
+ }
+ return response;
+ }
+ }
+}
diff --git a/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/SatIp/Rtsp/RtspSession.cs b/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/SatIp/Rtsp/RtspSession.cs
new file mode 100644
index 000000000..71b3f8a18
--- /dev/null
+++ b/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/SatIp/Rtsp/RtspSession.cs
@@ -0,0 +1,688 @@
+/*
+ Copyright (C) <2007-2016> <Kay Diefenthal>
+
+ SatIp.RtspSample is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ SatIp.RtspSample is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with SatIp.RtspSample. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Linq;
+using System.Net;
+using System.Net.NetworkInformation;
+using System.Net.Sockets;
+using System.Text.RegularExpressions;
+using MediaBrowser.Model.Logging;
+
+namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts.SatIp.Rtsp
+{
+ public class RtspSession : IDisposable
+ {
+ #region Private Fields
+ private static readonly Regex RegexRtspSessionHeader = new Regex(@"\s*([^\s;]+)(;timeout=(\d+))?");
+ private const int DefaultRtspSessionTimeout = 30; // unit = s
+ private static readonly Regex RegexDescribeResponseSignalInfo = new Regex(@";tuner=\d+,(\d+),(\d+),(\d+),", RegexOptions.Singleline | RegexOptions.IgnoreCase);
+ private string _address;
+ private string _rtspSessionId;
+
+ public string RtspSessionId
+ {
+ get { return _rtspSessionId; }
+ set { _rtspSessionId = value; }
+ }
+ private int _rtspSessionTimeToLive = 0;
+ private string _rtspStreamId;
+ private int _clientRtpPort;
+ private int _clientRtcpPort;
+ private int _serverRtpPort;
+ private int _serverRtcpPort;
+ private int _rtpPort;
+ private int _rtcpPort;
+ private string _rtspStreamUrl;
+ private string _destination;
+ private string _source;
+ private string _transport;
+ private int _signalLevel;
+ private int _signalQuality;
+ private Socket _rtspSocket;
+ private int _rtspSequenceNum = 1;
+ private bool _disposed = false;
+ private readonly ILogger _logger;
+ #endregion
+
+ #region Constructor
+
+ public RtspSession(string address, ILogger logger)
+ {
+ if (string.IsNullOrWhiteSpace(address))
+ {
+ throw new ArgumentNullException("address");
+ }
+
+ _address = address;
+ _logger = logger;
+
+ _logger.Info("Creating RtspSession with url {0}", address);
+ }
+ ~RtspSession()
+ {
+ Dispose(false);
+ }
+ #endregion
+
+ #region Properties
+
+ #region Rtsp
+
+ public string RtspStreamId
+ {
+ get { return _rtspStreamId; }
+ set { if (_rtspStreamId != value) { _rtspStreamId = value; OnPropertyChanged("RtspStreamId"); } }
+ }
+ public string RtspStreamUrl
+ {
+ get { return _rtspStreamUrl; }
+ set { if (_rtspStreamUrl != value) { _rtspStreamUrl = value; OnPropertyChanged("RtspStreamUrl"); } }
+ }
+
+ public int RtspSessionTimeToLive
+ {
+ get
+ {
+ if (_rtspSessionTimeToLive == 0)
+ _rtspSessionTimeToLive = DefaultRtspSessionTimeout;
+ return _rtspSessionTimeToLive * 1000 - 20;
+ }
+ set { if (_rtspSessionTimeToLive != value) { _rtspSessionTimeToLive = value; OnPropertyChanged("RtspSessionTimeToLive"); } }
+ }
+
+ #endregion
+
+ #region Rtp Rtcp
+
+ /// <summary>
+ /// The LocalEndPoint Address
+ /// </summary>
+ public string Destination
+ {
+ get
+ {
+ if (string.IsNullOrEmpty(_destination))
+ {
+ var result = "";
+ var host = Dns.GetHostName();
+ var hostentry = Dns.GetHostEntry(host);
+ foreach (var ip in hostentry.AddressList.Where(ip => ip.AddressFamily == AddressFamily.InterNetwork))
+ {
+ result = ip.ToString();
+ }
+
+ _destination = result;
+ }
+ return _destination;
+ }
+ set
+ {
+ if (_destination != value)
+ {
+ _destination = value;
+ OnPropertyChanged("Destination");
+ }
+ }
+ }
+
+ /// <summary>
+ /// The RemoteEndPoint Address
+ /// </summary>
+ public string Source
+ {
+ get { return _source; }
+ set
+ {
+ if (_source != value)
+ {
+ _source = value;
+ OnPropertyChanged("Source");
+ }
+ }
+ }
+
+ /// <summary>
+ /// The Media Data Delivery RemoteEndPoint Port if we use Unicast
+ /// </summary>
+ public int ServerRtpPort
+ {
+ get
+ {
+ return _serverRtpPort;
+ }
+ set { if (_serverRtpPort != value) { _serverRtpPort = value; OnPropertyChanged("ServerRtpPort"); } }
+ }
+
+ /// <summary>
+ /// The Media Metadata Delivery RemoteEndPoint Port if we use Unicast
+ /// </summary>
+ public int ServerRtcpPort
+ {
+ get { return _serverRtcpPort; }
+ set { if (_serverRtcpPort != value) { _serverRtcpPort = value; OnPropertyChanged("ServerRtcpPort"); } }
+ }
+
+ /// <summary>
+ /// The Media Data Delivery LocalEndPoint Port if we use Unicast
+ /// </summary>
+ public int ClientRtpPort
+ {
+ get { return _clientRtpPort; }
+ set { if (_clientRtpPort != value) { _clientRtpPort = value; OnPropertyChanged("ClientRtpPort"); } }
+ }
+
+ /// <summary>
+ /// The Media Metadata Delivery LocalEndPoint Port if we use Unicast
+ /// </summary>
+ public int ClientRtcpPort
+ {
+ get { return _clientRtcpPort; }
+ set { if (_clientRtcpPort != value) { _clientRtcpPort = value; OnPropertyChanged("ClientRtcpPort"); } }
+ }
+
+ /// <summary>
+ /// The Media Data Delivery RemoteEndPoint Port if we use Multicast
+ /// </summary>
+ public int RtpPort
+ {
+ get { return _rtpPort; }
+ set { if (_rtpPort != value) { _rtpPort = value; OnPropertyChanged("RtpPort"); } }
+ }
+
+ /// <summary>
+ /// The Media Meta Delivery RemoteEndPoint Port if we use Multicast
+ /// </summary>
+ public int RtcpPort
+ {
+ get { return _rtcpPort; }
+ set { if (_rtcpPort != value) { _rtcpPort = value; OnPropertyChanged("RtcpPort"); } }
+ }
+
+ #endregion
+
+ public string Transport
+ {
+ get
+ {
+ if (string.IsNullOrEmpty(_transport))
+ {
+ _transport = "unicast";
+ }
+ return _transport;
+ }
+ set
+ {
+ if (_transport != value)
+ {
+ _transport = value;
+ OnPropertyChanged("Transport");
+ }
+ }
+ }
+ public int SignalLevel
+ {
+ get { return _signalLevel; }
+ set { if (_signalLevel != value) { _signalLevel = value; OnPropertyChanged("SignalLevel"); } }
+ }
+ public int SignalQuality
+ {
+ get { return _signalQuality; }
+ set { if (_signalQuality != value) { _signalQuality = value; OnPropertyChanged("SignalQuality"); } }
+ }
+
+ #endregion
+
+ #region Private Methods
+
+ private void ProcessSessionHeader(string sessionHeader, string response)
+ {
+ if (!string.IsNullOrEmpty(sessionHeader))
+ {
+ var m = RegexRtspSessionHeader.Match(sessionHeader);
+ if (!m.Success)
+ {
+ _logger.Error("Failed to tune, RTSP {0} response session header {1} format not recognised", response, sessionHeader);
+ }
+ _rtspSessionId = m.Groups[1].Captures[0].Value;
+ _rtspSessionTimeToLive = m.Groups[3].Captures.Count == 1 ? int.Parse(m.Groups[3].Captures[0].Value) : DefaultRtspSessionTimeout;
+ }
+ }
+ private void ProcessTransportHeader(string transportHeader)
+ {
+ if (!string.IsNullOrEmpty(transportHeader))
+ {
+ var transports = transportHeader.Split(',');
+ foreach (var transport in transports)
+ {
+ if (transport.Trim().StartsWith("RTP/AVP"))
+ {
+ var sections = transport.Split(';');
+ foreach (var section in sections)
+ {
+ var parts = section.Split('=');
+ if (parts[0].Equals("server_port"))
+ {
+ var ports = parts[1].Split('-');
+ _serverRtpPort = int.Parse(ports[0]);
+ _serverRtcpPort = int.Parse(ports[1]);
+ }
+ else if (parts[0].Equals("destination"))
+ {
+ _destination = parts[1];
+ }
+ else if (parts[0].Equals("port"))
+ {
+ var ports = parts[1].Split('-');
+ _rtpPort = int.Parse(ports[0]);
+ _rtcpPort = int.Parse(ports[1]);
+ }
+ else if (parts[0].Equals("ttl"))
+ {
+ _rtspSessionTimeToLive = int.Parse(parts[1]);
+ }
+ else if (parts[0].Equals("source"))
+ {
+ _source = parts[1];
+ }
+ else if (parts[0].Equals("client_port"))
+ {
+ var ports = parts[1].Split('-');
+ var rtp = int.Parse(ports[0]);
+ var rtcp = int.Parse(ports[1]);
+ //if (!rtp.Equals(_rtpPort))
+ //{
+ // Logger.Error("SAT>IP base: server specified RTP client port {0} instead of {1}", rtp, _rtpPort);
+ //}
+ //if (!rtcp.Equals(_rtcpPort))
+ //{
+ // Logger.Error("SAT>IP base: server specified RTCP client port {0} instead of {1}", rtcp, _rtcpPort);
+ //}
+ _rtpPort = rtp;
+ _rtcpPort = rtcp;
+ }
+ }
+ }
+ }
+ }
+ }
+ private void Connect()
+ {
+ _rtspSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
+ var ip = IPAddress.Parse(_address);
+ var rtspEndpoint = new IPEndPoint(ip, 554);
+ _rtspSocket.Connect(rtspEndpoint);
+ }
+ private void Disconnect()
+ {
+ if (_rtspSocket != null && _rtspSocket.Connected)
+ {
+ _rtspSocket.Shutdown(SocketShutdown.Both);
+ _rtspSocket.Close();
+ }
+ }
+ private void SendRequest(RtspRequest request)
+ {
+ if (_rtspSocket == null)
+ {
+ Connect();
+ }
+ try
+ {
+ request.Headers.Add("CSeq", _rtspSequenceNum.ToString());
+ _rtspSequenceNum++;
+ byte[] requestBytes = request.Serialise();
+ if (_rtspSocket != null)
+ {
+ var requestBytesCount = _rtspSocket.Send(requestBytes, requestBytes.Length, SocketFlags.None);
+ if (requestBytesCount < 1)
+ {
+
+ }
+ }
+ }
+ catch (Exception e)
+ {
+ _logger.Error(e.Message);
+ }
+ }
+ private void ReceiveResponse(out RtspResponse response)
+ {
+ response = null;
+ var responseBytesCount = 0;
+ byte[] responseBytes = new byte[1024];
+ try
+ {
+ responseBytesCount = _rtspSocket.Receive(responseBytes, responseBytes.Length, SocketFlags.None);
+ response = RtspResponse.Deserialise(responseBytes, responseBytesCount);
+ string contentLengthString;
+ int contentLength = 0;
+ if (response.Headers.TryGetValue("Content-Length", out contentLengthString))
+ {
+ contentLength = int.Parse(contentLengthString);
+ if ((string.IsNullOrEmpty(response.Body) && contentLength > 0) || response.Body.Length < contentLength)
+ {
+ if (response.Body == null)
+ {
+ response.Body = string.Empty;
+ }
+ while (responseBytesCount > 0 && response.Body.Length < contentLength)
+ {
+ responseBytesCount = _rtspSocket.Receive(responseBytes, responseBytes.Length, SocketFlags.None);
+ response.Body += System.Text.Encoding.UTF8.GetString(responseBytes, 0, responseBytesCount);
+ }
+ }
+ }
+ }
+ catch (SocketException)
+ {
+ }
+ }
+
+ #endregion
+
+ #region Public Methods
+
+ public RtspStatusCode Setup(string query, string transporttype)
+ {
+
+ RtspRequest request;
+ RtspResponse response;
+ //_rtspClient = new RtspClient(_rtspDevice.ServerAddress);
+ if ((_rtspSocket == null))
+ {
+ Connect();
+ }
+ if (string.IsNullOrEmpty(_rtspSessionId))
+ {
+ request = new RtspRequest(RtspMethod.Setup, string.Format("rtsp://{0}:{1}/?{2}", _address, 554, query), 1, 0);
+ switch (transporttype)
+ {
+ case "multicast":
+ request.Headers.Add("Transport", string.Format("RTP/AVP;multicast"));
+ break;
+ case "unicast":
+ var activeTcpConnections = IPGlobalProperties.GetIPGlobalProperties().GetActiveTcpConnections();
+ var usedPorts = new HashSet<int>();
+ foreach (var connection in activeTcpConnections)
+ {
+ usedPorts.Add(connection.LocalEndPoint.Port);
+ }
+ for (var port = 40000; port <= 65534; port += 2)
+ {
+ if (!usedPorts.Contains(port) && !usedPorts.Contains(port + 1))
+ {
+
+ _clientRtpPort = port;
+ _clientRtcpPort = port + 1;
+ break;
+ }
+ }
+ request.Headers.Add("Transport", string.Format("RTP/AVP;unicast;client_port={0}-{1}", _clientRtpPort, _clientRtcpPort));
+ break;
+ }
+ }
+ else
+ {
+ request = new RtspRequest(RtspMethod.Setup, string.Format("rtsp://{0}:{1}/?{2}", _address, 554, query), 1, 0);
+ switch (transporttype)
+ {
+ case "multicast":
+ request.Headers.Add("Transport", string.Format("RTP/AVP;multicast"));
+ break;
+ case "unicast":
+ request.Headers.Add("Transport", string.Format("RTP/AVP;unicast;client_port={0}-{1}", _clientRtpPort, _clientRtcpPort));
+ break;
+ }
+
+ }
+ SendRequest(request);
+ ReceiveResponse(out response);
+
+ //if (_rtspClient.SendRequest(request, out response) != RtspStatusCode.Ok)
+ //{
+ // Logger.Error("Failed to tune, non-OK RTSP SETUP status code {0} {1}", response.StatusCode, response.ReasonPhrase);
+ //}
+ if (!response.Headers.TryGetValue("com.ses.streamID", out _rtspStreamId))
+ {
+ _logger.Error(string.Format("Failed to tune, not able to locate Stream ID header in RTSP SETUP response"));
+ }
+ string sessionHeader;
+ if (!response.Headers.TryGetValue("Session", out sessionHeader))
+ {
+ _logger.Error(string.Format("Failed to tune, not able to locate Session header in RTSP SETUP response"));
+ }
+ ProcessSessionHeader(sessionHeader, "Setup");
+ string transportHeader;
+ if (!response.Headers.TryGetValue("Transport", out transportHeader))
+ {
+ _logger.Error(string.Format("Failed to tune, not able to locate Transport header in RTSP SETUP response"));
+ }
+ ProcessTransportHeader(transportHeader);
+ return response.StatusCode;
+ }
+
+ public RtspStatusCode Play(string query)
+ {
+ if ((_rtspSocket == null))
+ {
+ Connect();
+ }
+ //_rtspClient = new RtspClient(_rtspDevice.ServerAddress);
+ RtspResponse response;
+ string data;
+ if (string.IsNullOrEmpty(query))
+ {
+ data = string.Format("rtsp://{0}:{1}/stream={2}", _address,
+ 554, _rtspStreamId);
+ }
+ else
+ {
+ data = string.Format("rtsp://{0}:{1}/stream={2}?{3}", _address,
+ 554, _rtspStreamId, query);
+ }
+ var request = new RtspRequest(RtspMethod.Play, data, 1, 0);
+ request.Headers.Add("Session", _rtspSessionId);
+ SendRequest(request);
+ ReceiveResponse(out response);
+ //if (_rtspClient.SendRequest(request, out response) != RtspStatusCode.Ok)
+ //{
+ // Logger.Error("Failed to tune, non-OK RTSP SETUP status code {0} {1}", response.StatusCode, response.ReasonPhrase);
+ //}
+ //Logger.Info("RtspSession-Play : \r\n {0}", response);
+ string sessionHeader;
+ if (!response.Headers.TryGetValue("Session", out sessionHeader))
+ {
+ _logger.Error(string.Format("Failed to tune, not able to locate Session header in RTSP Play response"));
+ }
+ ProcessSessionHeader(sessionHeader, "Play");
+ string rtpinfoHeader;
+ if (!response.Headers.TryGetValue("RTP-Info", out rtpinfoHeader))
+ {
+ _logger.Error(string.Format("Failed to tune, not able to locate Rtp-Info header in RTSP Play response"));
+ }
+ return response.StatusCode;
+ }
+
+ public RtspStatusCode Options()
+ {
+ if ((_rtspSocket == null))
+ {
+ Connect();
+ }
+ //_rtspClient = new RtspClient(_rtspDevice.ServerAddress);
+ RtspRequest request;
+ RtspResponse response;
+
+
+ if (string.IsNullOrEmpty(_rtspSessionId))
+ {
+ request = new RtspRequest(RtspMethod.Options, string.Format("rtsp://{0}:{1}/", _address, 554), 1, 0);
+ }
+ else
+ {
+ request = new RtspRequest(RtspMethod.Options, string.Format("rtsp://{0}:{1}/", _address, 554), 1, 0);
+ request.Headers.Add("Session", _rtspSessionId);
+ }
+ SendRequest(request);
+ ReceiveResponse(out response);
+ //if (_rtspClient.SendRequest(request, out response) != RtspStatusCode.Ok)
+ //{
+ // Logger.Error("Failed to tune, non-OK RTSP SETUP status code {0} {1}", response.StatusCode, response.ReasonPhrase);
+ //}
+ //Logger.Info("RtspSession-Options : \r\n {0}", response);
+ string sessionHeader;
+ if (!response.Headers.TryGetValue("Session", out sessionHeader))
+ {
+ _logger.Error(string.Format("Failed to tune, not able to locate session header in RTSP Options response"));
+ }
+ ProcessSessionHeader(sessionHeader, "Options");
+ string optionsHeader;
+ if (!response.Headers.TryGetValue("Public", out optionsHeader))
+ {
+ _logger.Error(string.Format("Failed to tune, not able to Options header in RTSP Options response"));
+ }
+ return response.StatusCode;
+ }
+
+ public RtspStatusCode Describe(out int level, out int quality)
+ {
+ if ((_rtspSocket == null))
+ {
+ Connect();
+ }
+ //_rtspClient = new RtspClient(_rtspDevice.ServerAddress);
+ RtspRequest request;
+ RtspResponse response;
+ level = 0;
+ quality = 0;
+
+ if (string.IsNullOrEmpty(_rtspSessionId))
+ {
+ request = new RtspRequest(RtspMethod.Describe, string.Format("rtsp://{0}:{1}/", _address, 554), 1, 0);
+ request.Headers.Add("Accept", "application/sdp");
+
+ }
+ else
+ {
+ request = new RtspRequest(RtspMethod.Describe, string.Format("rtsp://{0}:{1}/stream={2}", _address, 554, _rtspStreamId), 1, 0);
+ request.Headers.Add("Accept", "application/sdp");
+ request.Headers.Add("Session", _rtspSessionId);
+ }
+ SendRequest(request);
+ ReceiveResponse(out response);
+ //if (_rtspClient.SendRequest(request, out response) != RtspStatusCode.Ok)
+ //{
+ // Logger.Error("Failed to tune, non-OK RTSP Describe status code {0} {1}", response.StatusCode, response.ReasonPhrase);
+ //}
+ //Logger.Info("RtspSession-Describe : \r\n {0}", response);
+ string sessionHeader;
+ if (!response.Headers.TryGetValue("Session", out sessionHeader))
+ {
+ _logger.Error(string.Format("Failed to tune, not able to locate session header in RTSP Describe response"));
+ }
+ ProcessSessionHeader(sessionHeader, "Describe");
+ var m = RegexDescribeResponseSignalInfo.Match(response.Body);
+ if (m.Success)
+ {
+
+ //isSignalLocked = m.Groups[2].Captures[0].Value.Equals("1");
+ level = int.Parse(m.Groups[1].Captures[0].Value) * 100 / 255; // level: 0..255 => 0..100
+ quality = int.Parse(m.Groups[3].Captures[0].Value) * 100 / 15; // quality: 0..15 => 0..100
+
+ }
+ /*
+ v=0
+ o=- 1378633020884883 1 IN IP4 192.168.2.108
+ s=SatIPServer:1 4
+ t=0 0
+ a=tool:idl4k
+ m=video 52780 RTP/AVP 33
+ c=IN IP4 0.0.0.0
+ b=AS:5000
+ a=control:stream=4
+ a=fmtp:33 ver=1.0;tuner=1,0,0,0,12344,h,dvbs2,,off,,22000,34;pids=0,100,101,102,103,106
+ =sendonly
+ */
+
+
+ return response.StatusCode;
+ }
+
+ public RtspStatusCode TearDown()
+ {
+ if ((_rtspSocket == null))
+ {
+ Connect();
+ }
+ //_rtspClient = new RtspClient(_rtspDevice.ServerAddress);
+ RtspResponse response;
+
+ var request = new RtspRequest(RtspMethod.Teardown, string.Format("rtsp://{0}:{1}/stream={2}", _address, 554, _rtspStreamId), 1, 0);
+ request.Headers.Add("Session", _rtspSessionId);
+ SendRequest(request);
+ ReceiveResponse(out response);
+ //if (_rtspClient.SendRequest(request, out response) != RtspStatusCode.Ok)
+ //{
+ // Logger.Error("Failed to tune, non-OK RTSP Teardown status code {0} {1}", response.StatusCode, response.ReasonPhrase);
+ //}
+ return response.StatusCode;
+ }
+
+ #endregion
+
+ #region Public Events
+
+ public event PropertyChangedEventHandler PropertyChanged;
+
+ #endregion
+
+ #region Protected Methods
+
+ protected void OnPropertyChanged(string name)
+ {
+ //var handler = PropertyChanged;
+ //if (handler != null)
+ //{
+ // handler(this, new PropertyChangedEventArgs(name));
+ //}
+ }
+
+ #endregion
+
+ public void Dispose()
+ {
+ Dispose(true);
+ GC.SuppressFinalize(this);//Disconnect();
+ }
+
+ protected virtual void Dispose(bool disposing)
+ {
+ if (!_disposed)
+ {
+ if (disposing)
+ {
+ TearDown();
+ Disconnect();
+ }
+ }
+ _disposed = true;
+ }
+ }
+}
diff --git a/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/SatIp/Rtsp/RtspStatusCode.cs b/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/SatIp/Rtsp/RtspStatusCode.cs
new file mode 100644
index 000000000..6d6d50623
--- /dev/null
+++ b/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/SatIp/Rtsp/RtspStatusCode.cs
@@ -0,0 +1,251 @@
+/*
+ Copyright (C) <2007-2016> <Kay Diefenthal>
+
+ SatIp.RtspSample is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ SatIp.RtspSample is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with SatIp.RtspSample. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+using System.ComponentModel;
+
+namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts.SatIp.Rtsp
+{
+ /// <summary>
+ /// Standard RTSP status codes.
+ /// </summary>
+ public enum RtspStatusCode
+ {
+ /// <summary>
+ /// 100 continue
+ /// </summary>
+ Continue = 100,
+
+ /// <summary>
+ /// 200 OK
+ /// </summary>
+ [Description("Okay")]
+ Ok = 200,
+ /// <summary>
+ /// 201 created
+ /// </summary>
+ Created = 201,
+
+ /// <summary>
+ /// 250 low on storage space
+ /// </summary>
+ [Description("Low On Storage Space")]
+ LowOnStorageSpace = 250,
+
+ /// <summary>
+ /// 300 multiple choices
+ /// </summary>
+ [Description("Multiple Choices")]
+ MultipleChoices = 300,
+ /// <summary>
+ /// 301 moved permanently
+ /// </summary>
+ [Description("Moved Permanently")]
+ MovedPermanently = 301,
+ /// <summary>
+ /// 302 moved temporarily
+ /// </summary>
+ [Description("Moved Temporarily")]
+ MovedTemporarily = 302,
+ /// <summary>
+ /// 303 see other
+ /// </summary>
+ [Description("See Other")]
+ SeeOther = 303,
+ /// <summary>
+ /// 304 not modified
+ /// </summary>
+ [Description("Not Modified")]
+ NotModified = 304,
+ /// <summary>
+ /// 305 use proxy
+ /// </summary>
+ [Description("Use Proxy")]
+ UseProxy = 305,
+
+ /// <summary>
+ /// 400 bad request
+ /// </summary>
+ [Description("Bad Request")]
+ BadRequest = 400,
+ /// <summary>
+ /// 401 unauthorised
+ /// </summary>
+ Unauthorised = 401,
+ /// <summary>
+ /// 402 payment required
+ /// </summary>
+ [Description("Payment Required")]
+ PaymentRequired = 402,
+ /// <summary>
+ /// 403 forbidden
+ /// </summary>
+ Forbidden = 403,
+ /// <summary>
+ /// 404 not found
+ /// </summary>
+ [Description("Not Found")]
+ NotFound = 404,
+ /// <summary>
+ /// 405 method not allowed
+ /// </summary>
+ [Description("Method Not Allowed")]
+ MethodNotAllowed = 405,
+ /// <summary>
+ /// 406 not acceptable
+ /// </summary>
+ [Description("Not Acceptable")]
+ NotAcceptable = 406,
+ /// <summary>
+ /// 407 proxy authentication required
+ /// </summary>
+ [Description("Proxy Authentication Required")]
+ ProxyAuthenticationRequred = 407,
+ /// <summary>
+ /// 408 request time-out
+ /// </summary>
+ [Description("Request Time-Out")]
+ RequestTimeOut = 408,
+
+ /// <summary>
+ /// 410 gone
+ /// </summary>
+ Gone = 410,
+ /// <summary>
+ /// 411 length required
+ /// </summary>
+ [Description("Length Required")]
+ LengthRequired = 411,
+ /// <summary>
+ /// 412 precondition failed
+ /// </summary>
+ [Description("Precondition Failed")]
+ PreconditionFailed = 412,
+ /// <summary>
+ /// 413 request entity too large
+ /// </summary>
+ [Description("Request Entity Too Large")]
+ RequestEntityTooLarge = 413,
+ /// <summary>
+ /// 414 request URI too large
+ /// </summary>
+ [Description("Request URI Too Large")]
+ RequestUriTooLarge = 414,
+ /// <summary>
+ /// 415 unsupported media type
+ /// </summary>
+ [Description("Unsupported Media Type")]
+ UnsupportedMediaType = 415,
+
+ /// <summary>
+ /// 451 parameter not understood
+ /// </summary>
+ [Description("Parameter Not Understood")]
+ ParameterNotUnderstood = 451,
+ /// <summary>
+ /// 452 conference not found
+ /// </summary>
+ [Description("Conference Not Found")]
+ ConferenceNotFound = 452,
+ /// <summary>
+ /// 453 not enough bandwidth
+ /// </summary>
+ [Description("Not Enough Bandwidth")]
+ NotEnoughBandwidth = 453,
+ /// <summary>
+ /// 454 session not found
+ /// </summary>
+ [Description("Session Not Found")]
+ SessionNotFound = 454,
+ /// <summary>
+ /// 455 method not valid in this state
+ /// </summary>
+ [Description("Method Not Valid In This State")]
+ MethodNotValidInThisState = 455,
+ /// <summary>
+ /// 456 header field not valid for this resource
+ /// </summary>
+ [Description("Header Field Not Valid For This Resource")]
+ HeaderFieldNotValidForThisResource = 456,
+ /// <summary>
+ /// 457 invalid range
+ /// </summary>
+ [Description("Invalid Range")]
+ InvalidRange = 457,
+ /// <summary>
+ /// 458 parameter is read-only
+ /// </summary>
+ [Description("Parameter Is Read-Only")]
+ ParameterIsReadOnly = 458,
+ /// <summary>
+ /// 459 aggregate operation not allowed
+ /// </summary>
+ [Description("Aggregate Operation Not Allowed")]
+ AggregateOperationNotAllowed = 459,
+ /// <summary>
+ /// 460 only aggregate operation allowed
+ /// </summary>
+ [Description("Only Aggregate Operation Allowed")]
+ OnlyAggregateOperationAllowed = 460,
+ /// <summary>
+ /// 461 unsupported transport
+ /// </summary>
+ [Description("Unsupported Transport")]
+ UnsupportedTransport = 461,
+ /// <summary>
+ /// 462 destination unreachable
+ /// </summary>
+ [Description("Destination Unreachable")]
+ DestinationUnreachable = 462,
+
+ /// <summary>
+ /// 500 internal server error
+ /// </summary>
+ [Description("Internal Server Error")]
+ InternalServerError = 500,
+ /// <summary>
+ /// 501 not implemented
+ /// </summary>
+ [Description("Not Implemented")]
+ NotImplemented = 501,
+ /// <summary>
+ /// 502 bad gateway
+ /// </summary>
+ [Description("Bad Gateway")]
+ BadGateway = 502,
+ /// <summary>
+ /// 503 service unavailable
+ /// </summary>
+ [Description("Service Unavailable")]
+ ServiceUnavailable = 503,
+ /// <summary>
+ /// 504 gateway time-out
+ /// </summary>
+ [Description("Gateway Time-Out")]
+ GatewayTimeOut = 504,
+ /// <summary>
+ /// 505 RTSP version not supported
+ /// </summary>
+ [Description("RTSP Version Not Supported")]
+ RtspVersionNotSupported = 505,
+
+ /// <summary>
+ /// 551 option not supported
+ /// </summary>
+ [Description("Option Not Supported")]
+ OptionNotSupported = 551
+ }
+}
diff --git a/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/SatIp/SatIpDiscovery.cs b/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/SatIp/SatIpDiscovery.cs
index da1894bb7..d0a55966f 100644
--- a/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/SatIp/SatIpDiscovery.cs
+++ b/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/SatIp/SatIpDiscovery.cs
@@ -15,6 +15,7 @@ using MediaBrowser.Model.LiveTv;
using MediaBrowser.Model.Logging;
using MediaBrowser.Model.Serialization;
using MediaBrowser.Model.Extensions;
+using System.Xml.Linq;
namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts.SatIp
{
@@ -171,58 +172,86 @@ namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts.SatIp
public async Task<SatIpTunerHostInfo> GetInfo(string url, CancellationToken cancellationToken)
{
+ Uri locationUri = new Uri(url);
+ string devicetype = "";
+ string friendlyname = "";
+ string uniquedevicename = "";
+ string manufacturer = "";
+ string manufacturerurl = "";
+ string modelname = "";
+ string modeldescription = "";
+ string modelnumber = "";
+ string modelurl = "";
+ string serialnumber = "";
+ string presentationurl = "";
+ string capabilities = "";
+ string m3u = "";
+ var document = XDocument.Load(locationUri.AbsoluteUri);
+ var xnm = new XmlNamespaceManager(new NameTable());
+ XNamespace n1 = "urn:ses-com:satip";
+ XNamespace n0 = "urn:schemas-upnp-org:device-1-0";
+ xnm.AddNamespace("root", n0.NamespaceName);
+ xnm.AddNamespace("satip:", n1.NamespaceName);
+ if (document.Root != null)
+ {
+ var deviceElement = document.Root.Element(n0 + "device");
+ if (deviceElement != null)
+ {
+ var devicetypeElement = deviceElement.Element(n0 + "deviceType");
+ if (devicetypeElement != null)
+ devicetype = devicetypeElement.Value;
+ var friendlynameElement = deviceElement.Element(n0 + "friendlyName");
+ if (friendlynameElement != null)
+ friendlyname = friendlynameElement.Value;
+ var manufactureElement = deviceElement.Element(n0 + "manufacturer");
+ if (manufactureElement != null)
+ manufacturer = manufactureElement.Value;
+ var manufactureurlElement = deviceElement.Element(n0 + "manufacturerURL");
+ if (manufactureurlElement != null)
+ manufacturerurl = manufactureurlElement.Value;
+ var modeldescriptionElement = deviceElement.Element(n0 + "modelDescription");
+ if (modeldescriptionElement != null)
+ modeldescription = modeldescriptionElement.Value;
+ var modelnameElement = deviceElement.Element(n0 + "modelName");
+ if (modelnameElement != null)
+ modelname = modelnameElement.Value;
+ var modelnumberElement = deviceElement.Element(n0 + "modelNumber");
+ if (modelnumberElement != null)
+ modelnumber = modelnumberElement.Value;
+ var modelurlElement = deviceElement.Element(n0 + "modelURL");
+ if (modelurlElement != null)
+ modelurl = modelurlElement.Value;
+ var serialnumberElement = deviceElement.Element(n0 + "serialNumber");
+ if (serialnumberElement != null)
+ serialnumber = serialnumberElement.Value;
+ var uniquedevicenameElement = deviceElement.Element(n0 + "UDN");
+ if (uniquedevicenameElement != null) uniquedevicename = uniquedevicenameElement.Value;
+ var presentationUrlElement = deviceElement.Element(n0 + "presentationURL");
+ if (presentationUrlElement != null) presentationurl = presentationUrlElement.Value;
+ var capabilitiesElement = deviceElement.Element(n1 + "X_SATIPCAP");
+ if (capabilitiesElement != null) capabilities = capabilitiesElement.Value;
+ var m3uElement = deviceElement.Element(n1 + "X_SATIPM3U");
+ if (m3uElement != null) m3u = m3uElement.Value;
+ }
+ }
+
var result = new SatIpTunerHostInfo
{
Url = url,
+ Id = uniquedevicename,
IsEnabled = true,
Type = SatIpHost.DeviceType,
Tuners = 1,
- TunersAvailable = 1
+ TunersAvailable = 1,
+ M3UUrl = m3u
};
- using (var stream = await _httpClient.Get(url, cancellationToken).ConfigureAwait(false))
- {
- using (var streamReader = new StreamReader(stream))
- {
- // Use XmlReader for best performance
- using (var reader = XmlReader.Create(streamReader))
- {
- reader.MoveToContent();
-
- // Loop through each element
- while (reader.Read())
- {
- if (reader.NodeType == XmlNodeType.Element)
- {
- switch (reader.Name)
- {
- case "device":
- using (var subtree = reader.ReadSubtree())
- {
- FillFromDeviceNode(result, subtree);
- }
- break;
- default:
- reader.Skip();
- break;
- }
- }
- }
- }
- }
- }
-
- if (string.IsNullOrWhiteSpace(result.DeviceId))
+ result.FriendlyName = friendlyname;
+ if (string.IsNullOrWhiteSpace(result.Id))
{
throw new NotImplementedException();
}
- // Device hasn't implemented an m3u list
- if (string.IsNullOrWhiteSpace(result.M3UUrl))
- {
- result.IsEnabled = false;
- }
-
else if (!result.M3UUrl.StartsWith("http", StringComparison.OrdinalIgnoreCase))
{
var fullM3uUrl = url.Substring(0, url.LastIndexOf('/'));
@@ -233,66 +262,6 @@ namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts.SatIp
return result;
}
-
- private void FillFromDeviceNode(SatIpTunerHostInfo info, XmlReader reader)
- {
- reader.MoveToContent();
-
- while (reader.Read())
- {
- if (reader.NodeType == XmlNodeType.Element)
- {
- switch (reader.LocalName)
- {
- case "UDN":
- {
- info.DeviceId = reader.ReadElementContentAsString();
- break;
- }
-
- case "friendlyName":
- {
- info.FriendlyName = reader.ReadElementContentAsString();
- break;
- }
-
- case "satip:X_SATIPCAP":
- case "X_SATIPCAP":
- {
- // <satip:X_SATIPCAP xmlns:satip="urn:ses-com:satip">DVBS2-2</satip:X_SATIPCAP>
- var value = reader.ReadElementContentAsString() ?? string.Empty;
- var parts = value.Split(new[] { '-' }, StringSplitOptions.RemoveEmptyEntries);
- if (parts.Length == 2)
- {
- int intValue;
- if (int.TryParse(parts[1], NumberStyles.Any, CultureInfo.InvariantCulture, out intValue))
- {
- info.TunersAvailable = intValue;
- }
-
- if (int.TryParse(parts[0].Substring(parts[0].Length - 1), NumberStyles.Any, CultureInfo.InvariantCulture, out intValue))
- {
- info.Tuners = intValue;
- }
- }
- break;
- }
-
- case "satip:X_SATIPM3U":
- case "X_SATIPM3U":
- {
- // <satip:X_SATIPM3U xmlns:satip="urn:ses-com:satip">/channellist.lua?select=m3u</satip:X_SATIPM3U>
- info.M3UUrl = reader.ReadElementContentAsString();
- break;
- }
-
- default:
- reader.Skip();
- break;
- }
- }
- }
- }
}
public class SatIpTunerHostInfo : TunerHostInfo
diff --git a/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/SatIp/SatIpHost.cs b/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/SatIp/SatIpHost.cs
index 46a2a8524..1e571c84f 100644
--- a/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/SatIp/SatIpHost.cs
+++ b/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/SatIp/SatIpHost.cs
@@ -40,7 +40,8 @@ namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts.SatIp
return await new M3uParser(Logger, _fileSystem, _httpClient).Parse(tuner.M3UUrl, ChannelIdPrefix, tuner.Id, cancellationToken).ConfigureAwait(false);
}
- return new List<ChannelInfo>();
+ var channels = await new ChannelScan(Logger).Scan(tuner, cancellationToken).ConfigureAwait(false);
+ return channels;
}
public static string DeviceType
@@ -163,5 +164,10 @@ namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts.SatIp
return list;
}
+
+ public string ApplyDuration(string streamPath, TimeSpan duration)
+ {
+ return streamPath;
+ }
}
}
diff --git a/MediaBrowser.Server.Implementations/Localization/Core/ca.json b/MediaBrowser.Server.Implementations/Localization/Core/ca.json
index 7cec213d3..7ca8e1553 100644
--- a/MediaBrowser.Server.Implementations/Localization/Core/ca.json
+++ b/MediaBrowser.Server.Implementations/Localization/Core/ca.json
@@ -1,5 +1,5 @@
{
- "DbUpgradeMessage": "Please wait while your Emby Server database is upgraded. {0}% complete.",
+ "DbUpgradeMessage": "Si et plau espera mentre la teva base de dades del Servidor Emby \u00e9s actualitzada. {0}% completat.",
"AppDeviceValues": "App: {0}, Dispositiu: {1}",
"UserDownloadingItemWithValues": "{0} est\u00e0 descarregant {1}",
"FolderTypeMixed": "Contingut barrejat",
@@ -19,11 +19,11 @@
"LabelChapterName": "Cap\u00edtol {0}",
"NameSeasonNumber": "Temporada {0}",
"LabelExit": "Sortir",
- "LabelVisitCommunity": "Visita la comunitat",
+ "LabelVisitCommunity": "Visita la Comunitat",
"LabelGithub": "Github",
"LabelApiDocumentation": "Documentaci\u00f3 de l'API",
- "LabelDeveloperResources": "Recursos per a programadors",
- "LabelBrowseLibrary": "Examina la biblioteca",
+ "LabelDeveloperResources": "Recursos per a Desenvolupadors",
+ "LabelBrowseLibrary": "Examina la Biblioteca",
"LabelConfigureServer": "Configura Emby",
"LabelRestartServer": "Reiniciar Servidor",
"CategorySync": "Sync",
@@ -131,7 +131,7 @@
"HeaderType": "Type",
"HeaderSeverity": "Severity",
"HeaderUser": "Usuari",
- "HeaderName": "Name",
+ "HeaderName": "Nom",
"HeaderDate": "Data",
"HeaderPremiereDate": "Premiere Date",
"HeaderDateAdded": "Data afegida",
diff --git a/MediaBrowser.Server.Implementations/Localization/Core/en-US.json b/MediaBrowser.Server.Implementations/Localization/Core/en-US.json
index 0d5b5c4aa..5e2f98c09 100644
--- a/MediaBrowser.Server.Implementations/Localization/Core/en-US.json
+++ b/MediaBrowser.Server.Implementations/Localization/Core/en-US.json
@@ -1,178 +1,179 @@
{
- "DbUpgradeMessage": "Please wait while your Emby Server database is upgraded. {0}% complete.",
- "AppDeviceValues": "App: {0}, Device: {1}",
- "UserDownloadingItemWithValues": "{0} is downloading {1}",
- "FolderTypeMixed": "Mixed content",
- "FolderTypeMovies": "Movies",
- "FolderTypeMusic": "Music",
- "FolderTypeAdultVideos": "Adult videos",
- "FolderTypePhotos": "Photos",
- "FolderTypeMusicVideos": "Music videos",
- "FolderTypeHomeVideos": "Home videos",
- "FolderTypeGames": "Games",
- "FolderTypeBooks": "Books",
- "FolderTypeTvShows": "TV",
- "FolderTypeInherit": "Inherit",
- "HeaderCastCrew": "Cast & Crew",
- "HeaderPeople": "People",
- "ValueSpecialEpisodeName": "Special - {0}",
- "LabelChapterName": "Chapter {0}",
- "NameSeasonNumber": "Season {0}",
- "LabelExit": "Exit",
- "LabelVisitCommunity": "Visit Community",
- "LabelGithub": "Github",
- "LabelApiDocumentation": "Api Documentation",
- "LabelDeveloperResources": "Developer Resources",
- "LabelBrowseLibrary": "Browse Library",
- "LabelConfigureServer": "Configure Emby",
- "LabelRestartServer": "Restart Server",
- "CategorySync": "Sync",
- "CategoryUser": "User",
- "CategorySystem": "System",
- "CategoryApplication": "Application",
- "CategoryPlugin": "Plugin",
- "NotificationOptionPluginError": "Plugin failure",
- "NotificationOptionApplicationUpdateAvailable": "Application update available",
- "NotificationOptionApplicationUpdateInstalled": "Application update installed",
- "NotificationOptionPluginUpdateInstalled": "Plugin update installed",
- "NotificationOptionPluginInstalled": "Plugin installed",
- "NotificationOptionPluginUninstalled": "Plugin uninstalled",
- "NotificationOptionVideoPlayback": "Video playback started",
- "NotificationOptionAudioPlayback": "Audio playback started",
- "NotificationOptionGamePlayback": "Game playback started",
- "NotificationOptionVideoPlaybackStopped": "Video playback stopped",
- "NotificationOptionAudioPlaybackStopped": "Audio playback stopped",
- "NotificationOptionGamePlaybackStopped": "Game playback stopped",
- "NotificationOptionTaskFailed": "Scheduled task failure",
- "NotificationOptionInstallationFailed": "Installation failure",
- "NotificationOptionNewLibraryContent": "New content added",
- "NotificationOptionNewLibraryContentMultiple": "New content added (multiple)",
- "NotificationOptionCameraImageUploaded": "Camera image uploaded",
- "NotificationOptionUserLockedOut": "User locked out",
- "NotificationOptionServerRestartRequired": "Server restart required",
- "ViewTypePlaylists": "Playlists",
- "ViewTypeMovies": "Movies",
- "ViewTypeTvShows": "TV",
- "ViewTypeGames": "Games",
- "ViewTypeMusic": "Music",
- "ViewTypeMusicGenres": "Genres",
- "ViewTypeMusicArtists": "Artists",
- "ViewTypeBoxSets": "Collections",
- "ViewTypeChannels": "Channels",
- "ViewTypeLiveTV": "Live TV",
- "ViewTypeLiveTvNowPlaying": "Now Airing",
- "ViewTypeLatestGames": "Latest Games",
- "ViewTypeRecentlyPlayedGames": "Recently Played",
- "ViewTypeGameFavorites": "Favorites",
- "ViewTypeGameSystems": "Game Systems",
- "ViewTypeGameGenres": "Genres",
- "ViewTypeTvResume": "Resume",
- "ViewTypeTvNextUp": "Next Up",
- "ViewTypeTvLatest": "Latest",
- "ViewTypeTvShowSeries": "Series",
- "ViewTypeTvGenres": "Genres",
- "ViewTypeTvFavoriteSeries": "Favorite Series",
- "ViewTypeTvFavoriteEpisodes": "Favorite Episodes",
- "ViewTypeMovieResume": "Resume",
- "ViewTypeMovieLatest": "Latest",
- "ViewTypeMovieMovies": "Movies",
- "ViewTypeMovieCollections": "Collections",
- "ViewTypeMovieFavorites": "Favorites",
- "ViewTypeMovieGenres": "Genres",
- "ViewTypeMusicLatest": "Latest",
- "ViewTypeMusicPlaylists": "Playlists",
- "ViewTypeMusicAlbums": "Albums",
- "ViewTypeMusicAlbumArtists": "Album Artists",
- "HeaderOtherDisplaySettings": "Display Settings",
- "ViewTypeMusicSongs": "Songs",
- "ViewTypeMusicFavorites": "Favorites",
- "ViewTypeMusicFavoriteAlbums": "Favorite Albums",
- "ViewTypeMusicFavoriteArtists": "Favorite Artists",
- "ViewTypeMusicFavoriteSongs": "Favorite Songs",
- "ViewTypeFolders": "Folders",
- "ViewTypeLiveTvRecordingGroups": "Recordings",
- "ViewTypeLiveTvChannels": "Channels",
- "ScheduledTaskFailedWithName": "{0} failed",
- "LabelRunningTimeValue": "Running time: {0}",
- "ScheduledTaskStartedWithName": "{0} started",
- "VersionNumber": "Version {0}",
- "PluginInstalledWithName": "{0} was installed",
- "PluginUpdatedWithName": "{0} was updated",
- "PluginUninstalledWithName": "{0} was uninstalled",
- "ItemAddedWithName": "{0} was added to the library",
- "ItemRemovedWithName": "{0} was removed from the library",
- "LabelIpAddressValue": "Ip address: {0}",
- "DeviceOnlineWithName": "{0} is connected",
- "UserOnlineFromDevice": "{0} is online from {1}",
- "ProviderValue": "Provider: {0}",
- "SubtitlesDownloadedForItem": "Subtitles downloaded for {0}",
- "UserConfigurationUpdatedWithName": "User configuration has been updated for {0}",
- "UserCreatedWithName": "User {0} has been created",
- "UserPasswordChangedWithName": "Password has been changed for user {0}",
- "UserDeletedWithName": "User {0} has been deleted",
- "MessageServerConfigurationUpdated": "Server configuration has been updated",
- "MessageNamedServerConfigurationUpdatedWithValue": "Server configuration section {0} has been updated",
- "MessageApplicationUpdated": "Emby Server has been updated",
- "FailedLoginAttemptWithUserName": "Failed login attempt from {0}",
- "AuthenticationSucceededWithUserName": "{0} successfully authenticated",
- "DeviceOfflineWithName": "{0} has disconnected",
- "UserLockedOutWithName": "User {0} has been locked out",
- "UserOfflineFromDevice": "{0} has disconnected from {1}",
- "UserStartedPlayingItemWithValues": "{0} has started playing {1}",
- "UserStoppedPlayingItemWithValues": "{0} has stopped playing {1}",
- "SubtitleDownloadFailureForItem": "Subtitles failed to download for {0}",
- "HeaderUnidentified": "Unidentified",
- "HeaderImagePrimary": "Primary",
- "HeaderImageBackdrop": "Backdrop",
- "HeaderImageLogo": "Logo",
- "HeaderUserPrimaryImage": "User Image",
- "HeaderOverview": "Overview",
- "HeaderShortOverview": "Short Overview",
- "HeaderType": "Type",
- "HeaderSeverity": "Severity",
- "HeaderUser": "User",
- "HeaderName": "Name",
- "HeaderDate": "Date",
- "HeaderPremiereDate": "Premiere Date",
- "HeaderDateAdded": "Date Added",
- "HeaderReleaseDate": "Release date",
- "HeaderRuntime": "Runtime",
- "HeaderPlayCount": "Play Count",
- "HeaderSeason": "Season",
- "HeaderSeasonNumber": "Season number",
- "HeaderSeries": "Series:",
- "HeaderNetwork": "Network",
- "HeaderYear": "Year:",
- "HeaderYears": "Years:",
- "HeaderParentalRating": "Parental Rating",
- "HeaderCommunityRating": "Community rating",
- "HeaderTrailers": "Trailers",
- "HeaderSpecials": "Specials",
- "HeaderGameSystems": "Game Systems",
- "HeaderPlayers": "Players:",
- "HeaderAlbumArtists": "Album Artists",
- "HeaderAlbums": "Albums",
- "HeaderDisc": "Disc",
- "HeaderTrack": "Track",
- "HeaderAudio": "Audio",
- "HeaderVideo": "Video",
- "HeaderEmbeddedImage": "Embedded image",
- "HeaderResolution": "Resolution",
- "HeaderSubtitles": "Subtitles",
- "HeaderGenres": "Genres",
- "HeaderCountries": "Countries",
- "HeaderStatus": "Status",
- "HeaderTracks": "Tracks",
- "HeaderMusicArtist": "Music artist",
- "HeaderLocked": "Locked",
- "HeaderStudios": "Studios",
- "HeaderActor": "Actors",
- "HeaderComposer": "Composers",
- "HeaderDirector": "Directors",
- "HeaderGuestStar": "Guest star",
- "HeaderProducer": "Producers",
- "HeaderWriter": "Writers",
- "HeaderParentalRatings": "Parental Ratings",
- "HeaderCommunityRatings": "Community ratings",
- "StartupEmbyServerIsLoading": "Emby Server is loading. Please try again shortly."
+ "DbUpgradeMessage": "Please wait while your Emby Server database is upgraded. {0}% complete.",
+ "AppDeviceValues": "App: {0}, Device: {1}",
+ "UserDownloadingItemWithValues": "{0} is downloading {1}",
+ "FolderTypeMixed": "Mixed content",
+ "FolderTypeMovies": "Movies",
+ "FolderTypeMusic": "Music",
+ "FolderTypeAdultVideos": "Adult videos",
+ "FolderTypePhotos": "Photos",
+ "FolderTypeMusicVideos": "Music videos",
+ "FolderTypeHomeVideos": "Home videos",
+ "FolderTypeGames": "Games",
+ "FolderTypeBooks": "Books",
+ "FolderTypeTvShows": "TV",
+ "FolderTypeInherit": "Inherit",
+ "HeaderCastCrew": "Cast & Crew",
+ "HeaderPeople": "People",
+ "ValueSpecialEpisodeName": "Special - {0}",
+ "LabelChapterName": "Chapter {0}",
+ "NameSeasonUnknown": "Season Unknown",
+ "NameSeasonNumber": "Season {0}",
+ "LabelExit": "Exit",
+ "LabelVisitCommunity": "Visit Community",
+ "LabelGithub": "Github",
+ "LabelApiDocumentation": "Api Documentation",
+ "LabelDeveloperResources": "Developer Resources",
+ "LabelBrowseLibrary": "Browse Library",
+ "LabelConfigureServer": "Configure Emby",
+ "LabelRestartServer": "Restart Server",
+ "CategorySync": "Sync",
+ "CategoryUser": "User",
+ "CategorySystem": "System",
+ "CategoryApplication": "Application",
+ "CategoryPlugin": "Plugin",
+ "NotificationOptionPluginError": "Plugin failure",
+ "NotificationOptionApplicationUpdateAvailable": "Application update available",
+ "NotificationOptionApplicationUpdateInstalled": "Application update installed",
+ "NotificationOptionPluginUpdateInstalled": "Plugin update installed",
+ "NotificationOptionPluginInstalled": "Plugin installed",
+ "NotificationOptionPluginUninstalled": "Plugin uninstalled",
+ "NotificationOptionVideoPlayback": "Video playback started",
+ "NotificationOptionAudioPlayback": "Audio playback started",
+ "NotificationOptionGamePlayback": "Game playback started",
+ "NotificationOptionVideoPlaybackStopped": "Video playback stopped",
+ "NotificationOptionAudioPlaybackStopped": "Audio playback stopped",
+ "NotificationOptionGamePlaybackStopped": "Game playback stopped",
+ "NotificationOptionTaskFailed": "Scheduled task failure",
+ "NotificationOptionInstallationFailed": "Installation failure",
+ "NotificationOptionNewLibraryContent": "New content added",
+ "NotificationOptionNewLibraryContentMultiple": "New content added (multiple)",
+ "NotificationOptionCameraImageUploaded": "Camera image uploaded",
+ "NotificationOptionUserLockedOut": "User locked out",
+ "NotificationOptionServerRestartRequired": "Server restart required",
+ "ViewTypePlaylists": "Playlists",
+ "ViewTypeMovies": "Movies",
+ "ViewTypeTvShows": "TV",
+ "ViewTypeGames": "Games",
+ "ViewTypeMusic": "Music",
+ "ViewTypeMusicGenres": "Genres",
+ "ViewTypeMusicArtists": "Artists",
+ "ViewTypeBoxSets": "Collections",
+ "ViewTypeChannels": "Channels",
+ "ViewTypeLiveTV": "Live TV",
+ "ViewTypeLiveTvNowPlaying": "Now Airing",
+ "ViewTypeLatestGames": "Latest Games",
+ "ViewTypeRecentlyPlayedGames": "Recently Played",
+ "ViewTypeGameFavorites": "Favorites",
+ "ViewTypeGameSystems": "Game Systems",
+ "ViewTypeGameGenres": "Genres",
+ "ViewTypeTvResume": "Resume",
+ "ViewTypeTvNextUp": "Next Up",
+ "ViewTypeTvLatest": "Latest",
+ "ViewTypeTvShowSeries": "Series",
+ "ViewTypeTvGenres": "Genres",
+ "ViewTypeTvFavoriteSeries": "Favorite Series",
+ "ViewTypeTvFavoriteEpisodes": "Favorite Episodes",
+ "ViewTypeMovieResume": "Resume",
+ "ViewTypeMovieLatest": "Latest",
+ "ViewTypeMovieMovies": "Movies",
+ "ViewTypeMovieCollections": "Collections",
+ "ViewTypeMovieFavorites": "Favorites",
+ "ViewTypeMovieGenres": "Genres",
+ "ViewTypeMusicLatest": "Latest",
+ "ViewTypeMusicPlaylists": "Playlists",
+ "ViewTypeMusicAlbums": "Albums",
+ "ViewTypeMusicAlbumArtists": "Album Artists",
+ "HeaderOtherDisplaySettings": "Display Settings",
+ "ViewTypeMusicSongs": "Songs",
+ "ViewTypeMusicFavorites": "Favorites",
+ "ViewTypeMusicFavoriteAlbums": "Favorite Albums",
+ "ViewTypeMusicFavoriteArtists": "Favorite Artists",
+ "ViewTypeMusicFavoriteSongs": "Favorite Songs",
+ "ViewTypeFolders": "Folders",
+ "ViewTypeLiveTvRecordingGroups": "Recordings",
+ "ViewTypeLiveTvChannels": "Channels",
+ "ScheduledTaskFailedWithName": "{0} failed",
+ "LabelRunningTimeValue": "Running time: {0}",
+ "ScheduledTaskStartedWithName": "{0} started",
+ "VersionNumber": "Version {0}",
+ "PluginInstalledWithName": "{0} was installed",
+ "PluginUpdatedWithName": "{0} was updated",
+ "PluginUninstalledWithName": "{0} was uninstalled",
+ "ItemAddedWithName": "{0} was added to the library",
+ "ItemRemovedWithName": "{0} was removed from the library",
+ "LabelIpAddressValue": "Ip address: {0}",
+ "DeviceOnlineWithName": "{0} is connected",
+ "UserOnlineFromDevice": "{0} is online from {1}",
+ "ProviderValue": "Provider: {0}",
+ "SubtitlesDownloadedForItem": "Subtitles downloaded for {0}",
+ "UserConfigurationUpdatedWithName": "User configuration has been updated for {0}",
+ "UserCreatedWithName": "User {0} has been created",
+ "UserPasswordChangedWithName": "Password has been changed for user {0}",
+ "UserDeletedWithName": "User {0} has been deleted",
+ "MessageServerConfigurationUpdated": "Server configuration has been updated",
+ "MessageNamedServerConfigurationUpdatedWithValue": "Server configuration section {0} has been updated",
+ "MessageApplicationUpdated": "Emby Server has been updated",
+ "FailedLoginAttemptWithUserName": "Failed login attempt from {0}",
+ "AuthenticationSucceededWithUserName": "{0} successfully authenticated",
+ "DeviceOfflineWithName": "{0} has disconnected",
+ "UserLockedOutWithName": "User {0} has been locked out",
+ "UserOfflineFromDevice": "{0} has disconnected from {1}",
+ "UserStartedPlayingItemWithValues": "{0} has started playing {1}",
+ "UserStoppedPlayingItemWithValues": "{0} has stopped playing {1}",
+ "SubtitleDownloadFailureForItem": "Subtitles failed to download for {0}",
+ "HeaderUnidentified": "Unidentified",
+ "HeaderImagePrimary": "Primary",
+ "HeaderImageBackdrop": "Backdrop",
+ "HeaderImageLogo": "Logo",
+ "HeaderUserPrimaryImage": "User Image",
+ "HeaderOverview": "Overview",
+ "HeaderShortOverview": "Short Overview",
+ "HeaderType": "Type",
+ "HeaderSeverity": "Severity",
+ "HeaderUser": "User",
+ "HeaderName": "Name",
+ "HeaderDate": "Date",
+ "HeaderPremiereDate": "Premiere Date",
+ "HeaderDateAdded": "Date Added",
+ "HeaderReleaseDate": "Release date",
+ "HeaderRuntime": "Runtime",
+ "HeaderPlayCount": "Play Count",
+ "HeaderSeason": "Season",
+ "HeaderSeasonNumber": "Season number",
+ "HeaderSeries": "Series:",
+ "HeaderNetwork": "Network",
+ "HeaderYear": "Year:",
+ "HeaderYears": "Years:",
+ "HeaderParentalRating": "Parental Rating",
+ "HeaderCommunityRating": "Community rating",
+ "HeaderTrailers": "Trailers",
+ "HeaderSpecials": "Specials",
+ "HeaderGameSystems": "Game Systems",
+ "HeaderPlayers": "Players:",
+ "HeaderAlbumArtists": "Album Artists",
+ "HeaderAlbums": "Albums",
+ "HeaderDisc": "Disc",
+ "HeaderTrack": "Track",
+ "HeaderAudio": "Audio",
+ "HeaderVideo": "Video",
+ "HeaderEmbeddedImage": "Embedded image",
+ "HeaderResolution": "Resolution",
+ "HeaderSubtitles": "Subtitles",
+ "HeaderGenres": "Genres",
+ "HeaderCountries": "Countries",
+ "HeaderStatus": "Status",
+ "HeaderTracks": "Tracks",
+ "HeaderMusicArtist": "Music artist",
+ "HeaderLocked": "Locked",
+ "HeaderStudios": "Studios",
+ "HeaderActor": "Actors",
+ "HeaderComposer": "Composers",
+ "HeaderDirector": "Directors",
+ "HeaderGuestStar": "Guest star",
+ "HeaderProducer": "Producers",
+ "HeaderWriter": "Writers",
+ "HeaderParentalRatings": "Parental Ratings",
+ "HeaderCommunityRatings": "Community ratings",
+ "StartupEmbyServerIsLoading": "Emby Server is loading. Please try again shortly."
} \ No newline at end of file
diff --git a/MediaBrowser.Server.Implementations/Localization/Core/es.json b/MediaBrowser.Server.Implementations/Localization/Core/es.json
index 2a715a5b3..d1a56240d 100644
--- a/MediaBrowser.Server.Implementations/Localization/Core/es.json
+++ b/MediaBrowser.Server.Implementations/Localization/Core/es.json
@@ -1,7 +1,7 @@
{
- "DbUpgradeMessage": "Please wait while your Emby Server database is upgraded. {0}% complete.",
- "AppDeviceValues": "App: {0}, Device: {1}",
- "UserDownloadingItemWithValues": "{0} is downloading {1}",
+ "DbUpgradeMessage": "Por favor espere mientras la base de datos de su servidor Emby se actualiza. {0}% completado.",
+ "AppDeviceValues": "Aplicaci\u00f3n: {0}, Dispositivo: {1}",
+ "UserDownloadingItemWithValues": "{0} est\u00e1 descargando {1}",
"FolderTypeMixed": "Contenido mezclado",
"FolderTypeMovies": "Peliculas",
"FolderTypeMusic": "Musica",
@@ -14,10 +14,10 @@
"FolderTypeTvShows": "TV",
"FolderTypeInherit": "Heredado",
"HeaderCastCrew": "Reparto y equipo t\u00e9cnico",
- "HeaderPeople": "People",
- "ValueSpecialEpisodeName": "Special - {0}",
+ "HeaderPeople": "Gente",
+ "ValueSpecialEpisodeName": "Especial - {0}",
"LabelChapterName": "Cap\u00edtulo {0}",
- "NameSeasonNumber": "Season {0}",
+ "NameSeasonNumber": "Temporada {0}",
"LabelExit": "Salir",
"LabelVisitCommunity": "Visitar la comunidad",
"LabelGithub": "Github",
@@ -50,77 +50,77 @@
"NotificationOptionCameraImageUploaded": "Imagen de camara se a carcado",
"NotificationOptionUserLockedOut": "Usuario bloqueado",
"NotificationOptionServerRestartRequired": "Se requiere el reinicio del servidor",
- "ViewTypePlaylists": "Playlists",
+ "ViewTypePlaylists": "Listas de reproducci\u00f3n",
"ViewTypeMovies": "Pel\u00edculas",
"ViewTypeTvShows": "TV",
"ViewTypeGames": "Juegos",
"ViewTypeMusic": "M\u00fasica",
- "ViewTypeMusicGenres": "Genres",
- "ViewTypeMusicArtists": "Artists",
+ "ViewTypeMusicGenres": "G\u00e9neros",
+ "ViewTypeMusicArtists": "Artistas",
"ViewTypeBoxSets": "Colecciones",
"ViewTypeChannels": "Canales",
"ViewTypeLiveTV": "Tv en vivo",
- "ViewTypeLiveTvNowPlaying": "Now Airing",
- "ViewTypeLatestGames": "Latest Games",
- "ViewTypeRecentlyPlayedGames": "Recently Played",
- "ViewTypeGameFavorites": "Favorites",
- "ViewTypeGameSystems": "Game Systems",
- "ViewTypeGameGenres": "Genres",
- "ViewTypeTvResume": "Resume",
- "ViewTypeTvNextUp": "Next Up",
- "ViewTypeTvLatest": "Latest",
+ "ViewTypeLiveTvNowPlaying": "Transmiti\u00e9ndose ahora",
+ "ViewTypeLatestGames": "\u00daltimos juegos",
+ "ViewTypeRecentlyPlayedGames": "Reproducido recientemente",
+ "ViewTypeGameFavorites": "Favoritos",
+ "ViewTypeGameSystems": "Sistemas de juego",
+ "ViewTypeGameGenres": "G\u00e9neros",
+ "ViewTypeTvResume": "Reanudar",
+ "ViewTypeTvNextUp": "Pr\u00f3ximamente",
+ "ViewTypeTvLatest": "\u00daltimas",
"ViewTypeTvShowSeries": "Series",
- "ViewTypeTvGenres": "Genres",
- "ViewTypeTvFavoriteSeries": "Favorite Series",
- "ViewTypeTvFavoriteEpisodes": "Favorite Episodes",
- "ViewTypeMovieResume": "Resume",
- "ViewTypeMovieLatest": "Latest",
- "ViewTypeMovieMovies": "Movies",
- "ViewTypeMovieCollections": "Collections",
- "ViewTypeMovieFavorites": "Favorites",
- "ViewTypeMovieGenres": "Genres",
- "ViewTypeMusicLatest": "Latest",
- "ViewTypeMusicPlaylists": "Playlists",
- "ViewTypeMusicAlbums": "Albums",
- "ViewTypeMusicAlbumArtists": "Album Artists",
+ "ViewTypeTvGenres": "G\u00e9neros",
+ "ViewTypeTvFavoriteSeries": "Series favoritas",
+ "ViewTypeTvFavoriteEpisodes": "Episodios favoritos",
+ "ViewTypeMovieResume": "Reanudar",
+ "ViewTypeMovieLatest": "\u00daltimas",
+ "ViewTypeMovieMovies": "Pel\u00edculas",
+ "ViewTypeMovieCollections": "Colecciones",
+ "ViewTypeMovieFavorites": "Favoritos",
+ "ViewTypeMovieGenres": "G\u00e9neros",
+ "ViewTypeMusicLatest": "\u00daltimas",
+ "ViewTypeMusicPlaylists": "Lista",
+ "ViewTypeMusicAlbums": "\u00c1lbumes",
+ "ViewTypeMusicAlbumArtists": "\u00c1lbumes de artistas",
"HeaderOtherDisplaySettings": "Configuraci\u00f3n de pantalla",
- "ViewTypeMusicSongs": "Songs",
- "ViewTypeMusicFavorites": "Favorites",
- "ViewTypeMusicFavoriteAlbums": "Favorite Albums",
- "ViewTypeMusicFavoriteArtists": "Favorite Artists",
- "ViewTypeMusicFavoriteSongs": "Favorite Songs",
- "ViewTypeFolders": "Folders",
- "ViewTypeLiveTvRecordingGroups": "Recordings",
- "ViewTypeLiveTvChannels": "Channels",
- "ScheduledTaskFailedWithName": "{0} failed",
- "LabelRunningTimeValue": "Running time: {0}",
- "ScheduledTaskStartedWithName": "{0} started",
+ "ViewTypeMusicSongs": "Canciones",
+ "ViewTypeMusicFavorites": "Favoritos",
+ "ViewTypeMusicFavoriteAlbums": "\u00c1lbumes favoritos",
+ "ViewTypeMusicFavoriteArtists": "Artistas favoritos",
+ "ViewTypeMusicFavoriteSongs": "Canciones favoritas",
+ "ViewTypeFolders": "Carpetas",
+ "ViewTypeLiveTvRecordingGroups": "Grabaciones",
+ "ViewTypeLiveTvChannels": "Canales",
+ "ScheduledTaskFailedWithName": "{0} fall\u00f3",
+ "LabelRunningTimeValue": "Tiempo de ejecuci\u00f3n: {0}",
+ "ScheduledTaskStartedWithName": "{0} iniciado",
"VersionNumber": "Versi\u00f3n {0}",
- "PluginInstalledWithName": "{0} was installed",
- "PluginUpdatedWithName": "{0} was updated",
- "PluginUninstalledWithName": "{0} was uninstalled",
- "ItemAddedWithName": "{0} was added to the library",
- "ItemRemovedWithName": "{0} was removed from the library",
- "LabelIpAddressValue": "Ip address: {0}",
- "DeviceOnlineWithName": "{0} is connected",
- "UserOnlineFromDevice": "{0} is online from {1}",
- "ProviderValue": "Provider: {0}",
- "SubtitlesDownloadedForItem": "Subtitles downloaded for {0}",
- "UserConfigurationUpdatedWithName": "User configuration has been updated for {0}",
- "UserCreatedWithName": "User {0} has been created",
- "UserPasswordChangedWithName": "Password has been changed for user {0}",
- "UserDeletedWithName": "User {0} has been deleted",
- "MessageServerConfigurationUpdated": "Server configuration has been updated",
- "MessageNamedServerConfigurationUpdatedWithValue": "Server configuration section {0} has been updated",
- "MessageApplicationUpdated": "Emby Server has been updated",
- "FailedLoginAttemptWithUserName": "Failed login attempt from {0}",
- "AuthenticationSucceededWithUserName": "{0} successfully authenticated",
- "DeviceOfflineWithName": "{0} has disconnected",
- "UserLockedOutWithName": "User {0} has been locked out",
- "UserOfflineFromDevice": "{0} has disconnected from {1}",
- "UserStartedPlayingItemWithValues": "{0} has started playing {1}",
- "UserStoppedPlayingItemWithValues": "{0} has stopped playing {1}",
- "SubtitleDownloadFailureForItem": "Subtitles failed to download for {0}",
+ "PluginInstalledWithName": "{0} ha sido instalado",
+ "PluginUpdatedWithName": "{0} ha sido actualizado",
+ "PluginUninstalledWithName": "{0} ha sido desinstalado",
+ "ItemAddedWithName": "{0} ha sido a\u00f1adido a la biblioteca",
+ "ItemRemovedWithName": "{0} se ha eliminado de la biblioteca",
+ "LabelIpAddressValue": "Direcci\u00f3n IP: {0}",
+ "DeviceOnlineWithName": "{0} est\u00e1 conectado",
+ "UserOnlineFromDevice": "{0} est\u00e1 conectado desde {1}",
+ "ProviderValue": "Proveedor: {0}",
+ "SubtitlesDownloadedForItem": "Subt\u00edtulos descargados para {0}",
+ "UserConfigurationUpdatedWithName": "Se ha actualizado la configuraci\u00f3n de usuario para {0}",
+ "UserCreatedWithName": "Se ha creado el usuario {0}",
+ "UserPasswordChangedWithName": "Contrase\u00f1a cambiada al usuario {0}",
+ "UserDeletedWithName": "El usuario {0} ha sido eliminado",
+ "MessageServerConfigurationUpdated": "Se ha actualizado la configuraci\u00f3n del servidor",
+ "MessageNamedServerConfigurationUpdatedWithValue": "Se ha actualizado la secci\u00f3n {0} de la configuraci\u00f3n del servidor",
+ "MessageApplicationUpdated": "Se ha actualizado el servidor Emby",
+ "FailedLoginAttemptWithUserName": "Intento de inicio de sesi\u00f3n fallido desde {0}",
+ "AuthenticationSucceededWithUserName": "{0} se ha autenticado satisfactoriamente",
+ "DeviceOfflineWithName": "{0} se ha desconectado",
+ "UserLockedOutWithName": "El usuario {0} ha sido bloqueado",
+ "UserOfflineFromDevice": "{0} se ha desconectado de {1}",
+ "UserStartedPlayingItemWithValues": "{0} ha empezado a reproducir {1}",
+ "UserStoppedPlayingItemWithValues": "{0} ha parado de reproducir {1}",
+ "SubtitleDownloadFailureForItem": "Fallo en la descarga de subt\u00edtulos para {0}",
"HeaderUnidentified": "Unidentified",
"HeaderImagePrimary": "Primary",
"HeaderImageBackdrop": "Backdrop",
@@ -159,20 +159,20 @@
"HeaderEmbeddedImage": "Embedded image",
"HeaderResolution": "Resolution",
"HeaderSubtitles": "Subt\u00edtulos",
- "HeaderGenres": "Genres",
- "HeaderCountries": "Countries",
+ "HeaderGenres": "G\u00e9neros",
+ "HeaderCountries": "Paises",
"HeaderStatus": "Estado",
"HeaderTracks": "Tracks",
"HeaderMusicArtist": "Music artist",
"HeaderLocked": "Locked",
- "HeaderStudios": "Studios",
+ "HeaderStudios": "Estudios",
"HeaderActor": "Actors",
"HeaderComposer": "Composers",
"HeaderDirector": "Directors",
"HeaderGuestStar": "Guest star",
"HeaderProducer": "Producers",
"HeaderWriter": "Writers",
- "HeaderParentalRatings": "Parental Ratings",
+ "HeaderParentalRatings": "Clasificaci\u00f3n parental",
"HeaderCommunityRatings": "Community ratings",
"StartupEmbyServerIsLoading": "Emby Server is loading. Please try again shortly."
} \ No newline at end of file
diff --git a/MediaBrowser.Server.Implementations/Localization/Core/hu.json b/MediaBrowser.Server.Implementations/Localization/Core/hu.json
index b175ae6c1..2b9d28d8c 100644
--- a/MediaBrowser.Server.Implementations/Localization/Core/hu.json
+++ b/MediaBrowser.Server.Implementations/Localization/Core/hu.json
@@ -33,10 +33,10 @@
"CategoryPlugin": "B\u0151v\u00edtm\u00e9ny",
"NotificationOptionPluginError": "B\u0151v\u00edtm\u00e9ny hiba",
"NotificationOptionApplicationUpdateAvailable": "Friss\u00edt\u00e9s el\u00e9rhet\u0151",
- "NotificationOptionApplicationUpdateInstalled": "Friss\u00edt\u00e9s telep\u00edtve",
- "NotificationOptionPluginUpdateInstalled": "B\u0151v\u00edtm\u00e9ny friss\u00edtve",
+ "NotificationOptionApplicationUpdateInstalled": "Program friss\u00edt\u00e9s telep\u00edtve",
+ "NotificationOptionPluginUpdateInstalled": "B\u0151v\u00edtm\u00e9ny friss\u00edt\u00e9s telep\u00edtve",
"NotificationOptionPluginInstalled": "B\u0151v\u00edtm\u00e9ny telep\u00edtve",
- "NotificationOptionPluginUninstalled": "B\u0151v\u00edtm\u00e9ny t\u00f6r\u00f6lve",
+ "NotificationOptionPluginUninstalled": "B\u0151v\u00edtm\u00e9ny elt\u00e1vol\u00edtva",
"NotificationOptionVideoPlayback": "Vide\u00f3 elind\u00edtva",
"NotificationOptionAudioPlayback": "Zene elind\u00edtva",
"NotificationOptionGamePlayback": "J\u00e1t\u00e9k elind\u00edtva",
@@ -169,8 +169,8 @@
"HeaderActor": "Sz\u00edn\u00e9szek",
"HeaderComposer": "Zeneszerz\u0151k",
"HeaderDirector": "Rendez\u0151k",
- "HeaderGuestStar": "Guest star",
- "HeaderProducer": "Producers",
+ "HeaderGuestStar": "Vend\u00e9g szt\u00e1r",
+ "HeaderProducer": "Producerek",
"HeaderWriter": "\u00cdr\u00f3k",
"HeaderParentalRatings": "Korhat\u00e1r besorol\u00e1s",
"HeaderCommunityRatings": "K\u00f6z\u00f6ss\u00e9gi \u00e9rt\u00e9kel\u00e9sek",
diff --git a/MediaBrowser.Server.Implementations/Localization/Core/nl.json b/MediaBrowser.Server.Implementations/Localization/Core/nl.json
index a83182ee8..2818fbf6a 100644
--- a/MediaBrowser.Server.Implementations/Localization/Core/nl.json
+++ b/MediaBrowser.Server.Implementations/Localization/Core/nl.json
@@ -1,5 +1,5 @@
{
- "DbUpgradeMessage": "Even geduld svp terwijl de Emby Server database ge-upgrade wordt. {0}% gereed.",
+ "DbUpgradeMessage": "Een ogenblik geduld terwijl uw Emby Server-database wordt bijgewerkt. {0}% voltooid.",
"AppDeviceValues": "App: {0}, Apparaat: {1}",
"UserDownloadingItemWithValues": "{0} download {1}",
"FolderTypeMixed": "Gemengde inhoud",
diff --git a/MediaBrowser.Server.Implementations/Localization/Core/ru.json b/MediaBrowser.Server.Implementations/Localization/Core/ru.json
index fa68aadb9..62fe3b496 100644
--- a/MediaBrowser.Server.Implementations/Localization/Core/ru.json
+++ b/MediaBrowser.Server.Implementations/Localization/Core/ru.json
@@ -2,17 +2,17 @@
"DbUpgradeMessage": "\u041f\u043e\u0434\u043e\u0436\u0434\u0438\u0442\u0435, \u043f\u043e\u043a\u0430 \u0431\u0430\u0437\u0430 \u0434\u0430\u043d\u043d\u044b\u0445 \u043d\u0430 \u0432\u0430\u0448\u0435\u043c Emby Server \u043c\u043e\u0434\u0435\u0440\u043d\u0438\u0437\u0438\u0440\u0443\u0435\u0442\u0441\u044f. {0} % \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u043e.",
"AppDeviceValues": "\u041f\u0440\u0438\u043b.: {0}, \u0423\u0441\u0442\u0440.: {1}",
"UserDownloadingItemWithValues": "{0} \u0437\u0430\u0433\u0440\u0443\u0436\u0430\u0435\u0442 {1}",
- "FolderTypeMixed": "\u0420\u0430\u0437\u043d\u043e\u0442\u0438\u043f\u043d\u043e\u0435 \u0441\u043e\u0434\u0435\u0440\u0436\u0430\u043d\u0438\u0435",
+ "FolderTypeMixed": "\u0421\u043c\u0435\u0448\u0430\u043d\u043d\u043e\u0435 \u0441\u043e\u0434\u0435\u0440\u0436\u0430\u043d\u0438\u0435",
"FolderTypeMovies": "\u041a\u0438\u043d\u043e",
"FolderTypeMusic": "\u041c\u0443\u0437\u044b\u043a\u0430",
- "FolderTypeAdultVideos": "\u0412\u0438\u0434\u0435\u043e \u0434\u043b\u044f \u0432\u0437\u0440\u043e\u0441\u043b\u044b\u0445",
- "FolderTypePhotos": "\u0424\u043e\u0442\u043e\u0433\u0440\u0430\u0444\u0438\u0438",
- "FolderTypeMusicVideos": "\u041c\u0443\u0437\u044b\u043a\u0430\u043b\u044c\u043d\u044b\u0435 \u0432\u0438\u0434\u0435\u043e",
- "FolderTypeHomeVideos": "\u0414\u043e\u043c\u0430\u0448\u043d\u0438\u0435 \u0432\u0438\u0434\u0435\u043e",
+ "FolderTypeAdultVideos": "\u0414\u043b\u044f \u0432\u0437\u0440\u043e\u0441\u043b\u044b\u0445",
+ "FolderTypePhotos": "\u0424\u043e\u0442\u043e",
+ "FolderTypeMusicVideos": "\u041c\u0443\u0437. \u0432\u0438\u0434\u0435\u043e",
+ "FolderTypeHomeVideos": "\u0414\u043e\u043c. \u0432\u0438\u0434\u0435\u043e",
"FolderTypeGames": "\u0418\u0433\u0440\u044b",
- "FolderTypeBooks": "\u041a\u043d\u0438\u0433\u0438",
+ "FolderTypeBooks": "\u041b\u0438\u0442\u0435\u0440\u0430\u0442\u0443\u0440\u0430",
"FolderTypeTvShows": "\u0422\u0412",
- "FolderTypeInherit": "\u041d\u0430\u0441\u043b\u0435\u0434\u043e\u0432\u0430\u043d\u0438\u0435",
+ "FolderTypeInherit": "\u041d\u0430\u0441\u043b\u0435\u0434\u0443\u0435\u043c\u044b\u0439",
"HeaderCastCrew": "\u0421\u043d\u0438\u043c\u0430\u043b\u0438\u0441\u044c \u0438 \u0441\u043d\u0438\u043c\u0430\u043b\u0438",
"HeaderPeople": "\u041b\u044e\u0434\u0438",
"ValueSpecialEpisodeName": "\u0421\u043f\u0435\u0446\u044d\u043f\u0438\u0437\u043e\u0434 - {0}",
@@ -163,7 +163,7 @@
"HeaderCountries": "\u0421\u0442\u0440\u0430\u043d\u044b",
"HeaderStatus": "\u0421\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u0435",
"HeaderTracks": "\u0414\u043e\u0440-\u043a\u0438",
- "HeaderMusicArtist": "\u0418\u0441\u043f. \u043c\u0443\u0437\u044b\u043a\u0438",
+ "HeaderMusicArtist": "\u041c\u0443\u0437. \u0438\u0441\u043f\u043e\u043b\u043d\u0438\u0442\u0435\u043b\u044c",
"HeaderLocked": "\u0417\u0430\u0431\u043b\u043e\u043a\u0438\u0440\u043e\u0432\u0430\u043d\u043e",
"HeaderStudios": "\u0421\u0442\u0443\u0434\u0438\u0438",
"HeaderActor": "\u0410\u043a\u0442\u0451\u0440\u044b",
diff --git a/MediaBrowser.Server.Implementations/Localization/Core/sv.json b/MediaBrowser.Server.Implementations/Localization/Core/sv.json
index f52f656d4..4a6565aff 100644
--- a/MediaBrowser.Server.Implementations/Localization/Core/sv.json
+++ b/MediaBrowser.Server.Implementations/Localization/Core/sv.json
@@ -1,7 +1,7 @@
{
- "DbUpgradeMessage": "Please wait while your Emby Server database is upgraded. {0}% complete.",
+ "DbUpgradeMessage": "V\u00e4nligen v\u00e4nta medan databasen p\u00e5 din Emby Server uppgraderas. {0}% klar",
"AppDeviceValues": "App: {0}, enhet: {1}",
- "UserDownloadingItemWithValues": "{0} is downloading {1}",
+ "UserDownloadingItemWithValues": "{0} laddar ned {1}",
"FolderTypeMixed": "Blandat inneh\u00e5ll",
"FolderTypeMovies": "Filmer",
"FolderTypeMusic": "Musik",
@@ -15,18 +15,18 @@
"FolderTypeInherit": "\u00c4rv",
"HeaderCastCrew": "Rollista & bes\u00e4ttning",
"HeaderPeople": "Personer",
- "ValueSpecialEpisodeName": "Special - {0}",
+ "ValueSpecialEpisodeName": "Specialavsnitt - {0}",
"LabelChapterName": "Kapitel {0}",
- "NameSeasonNumber": "Season {0}",
+ "NameSeasonNumber": "S\u00e4song {0}",
"LabelExit": "Avsluta",
"LabelVisitCommunity": "Bes\u00f6k v\u00e5rt diskussionsforum",
"LabelGithub": "Github",
- "LabelApiDocumentation": "Api Dokumentation",
+ "LabelApiDocumentation": "Api-dokumentation",
"LabelDeveloperResources": "Resurser f\u00f6r utvecklare",
"LabelBrowseLibrary": "Bl\u00e4ddra i biblioteket",
- "LabelConfigureServer": "Configure Emby",
+ "LabelConfigureServer": "Konfigurera Emby",
"LabelRestartServer": "Starta om servern",
- "CategorySync": "Sync",
+ "CategorySync": "Synkronisera",
"CategoryUser": "Anv\u00e4ndare",
"CategorySystem": "System",
"CategoryApplication": "App",
@@ -47,10 +47,10 @@
"NotificationOptionInstallationFailed": "Fel vid installation",
"NotificationOptionNewLibraryContent": "Nytt inneh\u00e5ll har tillkommit",
"NotificationOptionNewLibraryContentMultiple": "Nytillkommet inneh\u00e5ll finns (flera objekt)",
- "NotificationOptionCameraImageUploaded": "Camera image uploaded",
- "NotificationOptionUserLockedOut": "User locked out",
+ "NotificationOptionCameraImageUploaded": "Kaberabild uppladdad",
+ "NotificationOptionUserLockedOut": "Anv\u00e4ndare har l\u00e5sts ute",
"NotificationOptionServerRestartRequired": "Servern m\u00e5ste startas om",
- "ViewTypePlaylists": "Playlists",
+ "ViewTypePlaylists": "Spellistor",
"ViewTypeMovies": "Filmer",
"ViewTypeTvShows": "TV",
"ViewTypeGames": "Spel",
@@ -80,10 +80,10 @@
"ViewTypeMovieFavorites": "Favoriter",
"ViewTypeMovieGenres": "Genrer",
"ViewTypeMusicLatest": "Nytillkommet",
- "ViewTypeMusicPlaylists": "Playlists",
+ "ViewTypeMusicPlaylists": "Spellistor",
"ViewTypeMusicAlbums": "Album",
"ViewTypeMusicAlbumArtists": "Albumartister",
- "HeaderOtherDisplaySettings": "Visningsinst\u00e4llningar",
+ "HeaderOtherDisplaySettings": "Visningsalternativ",
"ViewTypeMusicSongs": "L\u00e5tar",
"ViewTypeMusicFavorites": "Favoriter",
"ViewTypeMusicFavoriteAlbums": "Favoritalbum",
@@ -112,45 +112,45 @@
"UserDeletedWithName": "Anv\u00e4ndaren {0} har tagits bort",
"MessageServerConfigurationUpdated": "Server konfigurationen har uppdaterats",
"MessageNamedServerConfigurationUpdatedWithValue": "Serverinst\u00e4llningarnas del {0} ar uppdaterats",
- "MessageApplicationUpdated": "Emby Server has been updated",
+ "MessageApplicationUpdated": "Emby Server har uppdaterats",
"FailedLoginAttemptWithUserName": "Misslyckat inloggningsf\u00f6rs\u00f6k fr\u00e5n {0}",
"AuthenticationSucceededWithUserName": "{0} har autentiserats",
"DeviceOfflineWithName": "{0} har avbrutit anslutningen",
- "UserLockedOutWithName": "User {0} has been locked out",
- "UserOfflineFromDevice": "{0} har kopplats bort fr\u00e5n {1}",
+ "UserLockedOutWithName": "Anv\u00e4ndare {0} har l\u00e5sts ute",
+ "UserOfflineFromDevice": "{0} har avbrutit anslutningen fr\u00e5n {1}",
"UserStartedPlayingItemWithValues": "{0} har p\u00e5b\u00f6rjat uppspelning av {1}",
"UserStoppedPlayingItemWithValues": "{0} har avslutat uppspelning av {1}",
"SubtitleDownloadFailureForItem": "Nerladdning av undertexter f\u00f6r {0} misslyckades",
- "HeaderUnidentified": "Unidentified",
- "HeaderImagePrimary": "Primary",
- "HeaderImageBackdrop": "Backdrop",
- "HeaderImageLogo": "Logo",
- "HeaderUserPrimaryImage": "User Image",
- "HeaderOverview": "Overview",
- "HeaderShortOverview": "Short Overview",
- "HeaderType": "Type",
+ "HeaderUnidentified": "Oidentifierad",
+ "HeaderImagePrimary": "Huvudbild",
+ "HeaderImageBackdrop": "Bakgrundsbild",
+ "HeaderImageLogo": "Logotyp",
+ "HeaderUserPrimaryImage": "Anv\u00e4ndarbild",
+ "HeaderOverview": "\u00d6versikt",
+ "HeaderShortOverview": "Kort \u00f6versikt",
+ "HeaderType": "Typ",
"HeaderSeverity": "Severity",
"HeaderUser": "Anv\u00e4ndare",
"HeaderName": "Namn",
"HeaderDate": "Datum",
- "HeaderPremiereDate": "Premiere Date",
+ "HeaderPremiereDate": "Premi\u00e4rdatum",
"HeaderDateAdded": "Date Added",
"HeaderReleaseDate": "Premi\u00e4rdatum:",
"HeaderRuntime": "Speltid",
- "HeaderPlayCount": "Play Count",
+ "HeaderPlayCount": "Antal spelningar",
"HeaderSeason": "S\u00e4song",
"HeaderSeasonNumber": "S\u00e4songsnummer:",
- "HeaderSeries": "Series:",
+ "HeaderSeries": "Serie:",
"HeaderNetwork": "TV-bolag",
- "HeaderYear": "Year:",
- "HeaderYears": "Years:",
+ "HeaderYear": "\u00c5r:",
+ "HeaderYears": "\u00c5r:",
"HeaderParentalRating": "Parental Rating",
"HeaderCommunityRating": "Anv\u00e4ndaromd\u00f6me",
"HeaderTrailers": "Trailers",
- "HeaderSpecials": "Specialer",
+ "HeaderSpecials": "Specialavsnitt",
"HeaderGameSystems": "Game Systems",
- "HeaderPlayers": "Players:",
- "HeaderAlbumArtists": "Album Artists",
+ "HeaderPlayers": "Spelare:",
+ "HeaderAlbumArtists": "Albumartister",
"HeaderAlbums": "Album",
"HeaderDisc": "Skiva",
"HeaderTrack": "Sp\u00e5r",
@@ -163,16 +163,16 @@
"HeaderCountries": "L\u00e4nder",
"HeaderStatus": "Status",
"HeaderTracks": "Sp\u00e5r",
- "HeaderMusicArtist": "Music artist",
- "HeaderLocked": "Locked",
+ "HeaderMusicArtist": "Musikartist",
+ "HeaderLocked": "L\u00e5st",
"HeaderStudios": "Studior",
- "HeaderActor": "Actors",
- "HeaderComposer": "Composers",
- "HeaderDirector": "Directors",
- "HeaderGuestStar": "Guest star",
- "HeaderProducer": "Producers",
- "HeaderWriter": "Writers",
+ "HeaderActor": "Sk\u00e5despelare",
+ "HeaderComposer": "Komposit\u00f6rer",
+ "HeaderDirector": "Regiss\u00f6r",
+ "HeaderGuestStar": "G\u00e4startist",
+ "HeaderProducer": "Producenter",
+ "HeaderWriter": "F\u00f6rfattare",
"HeaderParentalRatings": "Parental Ratings",
"HeaderCommunityRatings": "Community ratings",
- "StartupEmbyServerIsLoading": "Emby Server is loading. Please try again shortly."
+ "StartupEmbyServerIsLoading": "Emby Server startar. V\u00e4nligen f\u00f6rs\u00f6k igen om en kort stund."
} \ No newline at end of file
diff --git a/MediaBrowser.Server.Implementations/Localization/Core/zh-TW.json b/MediaBrowser.Server.Implementations/Localization/Core/zh-TW.json
index 7f289fc1f..b711aab1f 100644
--- a/MediaBrowser.Server.Implementations/Localization/Core/zh-TW.json
+++ b/MediaBrowser.Server.Implementations/Localization/Core/zh-TW.json
@@ -1,5 +1,5 @@
{
- "DbUpgradeMessage": "Please wait while your Emby Server database is upgraded. {0}% complete.",
+ "DbUpgradeMessage": "\u8acb\u7a0d\u5019\uff0cEmby\u4f3a\u670d\u5668\u8cc7\u6599\u5eab\u6b63\u5728\u66f4\u65b0...\uff08\u5df2\u5b8c\u6210{0}%\uff09",
"AppDeviceValues": "App: {0}, Device: {1}",
"UserDownloadingItemWithValues": "{0} is downloading {1}",
"FolderTypeMixed": "Mixed content",
@@ -19,13 +19,13 @@
"LabelChapterName": "Chapter {0}",
"NameSeasonNumber": "Season {0}",
"LabelExit": "\u96e2\u958b",
- "LabelVisitCommunity": "\u8a2a\u554f\u793e\u5340",
- "LabelGithub": "Github",
- "LabelApiDocumentation": "Api Documentation",
- "LabelDeveloperResources": "Developer Resources",
- "LabelBrowseLibrary": "\u700f\u89bd\u5a92\u9ad4\u5eab",
- "LabelConfigureServer": "Configure Emby",
- "LabelRestartServer": "\u91cd\u65b0\u555f\u52d5\u4f3a\u5668\u670d",
+ "LabelVisitCommunity": "\u8a2a\u554f\u793e\u7fa4",
+ "LabelGithub": "GitHub",
+ "LabelApiDocumentation": "API\u8aaa\u660e\u6587\u4ef6",
+ "LabelDeveloperResources": "\u958b\u767c\u4eba\u54e1\u5c08\u5340",
+ "LabelBrowseLibrary": "\u700f\u89bd\u5a92\u9ad4\u6ac3",
+ "LabelConfigureServer": "Emby\u8a2d\u5b9a",
+ "LabelRestartServer": "\u91cd\u65b0\u555f\u52d5\u4f3a\u670d\u5668",
"CategorySync": "Sync",
"CategoryUser": "User",
"CategorySystem": "System",
@@ -59,7 +59,7 @@
"ViewTypeMusicArtists": "Artists",
"ViewTypeBoxSets": "Collections",
"ViewTypeChannels": "Channels",
- "ViewTypeLiveTV": "Live TV",
+ "ViewTypeLiveTV": "\u96fb\u8996",
"ViewTypeLiveTvNowPlaying": "Now Airing",
"ViewTypeLatestGames": "Latest Games",
"ViewTypeRecentlyPlayedGames": "Recently Played",
diff --git a/MediaBrowser.Server.Implementations/Localization/LocalizationManager.cs b/MediaBrowser.Server.Implementations/Localization/LocalizationManager.cs
index 0c627d751..ec544dd70 100644
--- a/MediaBrowser.Server.Implementations/Localization/LocalizationManager.cs
+++ b/MediaBrowser.Server.Implementations/Localization/LocalizationManager.cs
@@ -12,6 +12,7 @@ using System.IO;
using System.Linq;
using System.Reflection;
using CommonIO;
+using MediaBrowser.Model.Logging;
namespace MediaBrowser.Server.Implementations.Localization
{
@@ -35,6 +36,7 @@ namespace MediaBrowser.Server.Implementations.Localization
private readonly IFileSystem _fileSystem;
private readonly IJsonSerializer _jsonSerializer;
+ private readonly ILogger _logger;
/// <summary>
/// Initializes a new instance of the <see cref="LocalizationManager" /> class.
@@ -42,11 +44,12 @@ namespace MediaBrowser.Server.Implementations.Localization
/// <param name="configurationManager">The configuration manager.</param>
/// <param name="fileSystem">The file system.</param>
/// <param name="jsonSerializer">The json serializer.</param>
- public LocalizationManager(IServerConfigurationManager configurationManager, IFileSystem fileSystem, IJsonSerializer jsonSerializer)
+ public LocalizationManager(IServerConfigurationManager configurationManager, IFileSystem fileSystem, IJsonSerializer jsonSerializer, ILogger logger)
{
_configurationManager = configurationManager;
_fileSystem = fileSystem;
_jsonSerializer = jsonSerializer;
+ _logger = logger;
ExtractAll();
}
@@ -75,7 +78,10 @@ namespace MediaBrowser.Server.Implementations.Localization
{
using (var stream = type.Assembly.GetManifestResourceStream(resource))
{
- using (var fs = _fileSystem.GetFileStream(Path.Combine(localizationPath, filename), FileMode.Create, FileAccess.Write, FileShare.Read))
+ var target = Path.Combine(localizationPath, filename);
+ _logger.Info("Extracting ratings to {0}", target);
+
+ using (var fs = _fileSystem.GetFileStream(target, FileMode.Create, FileAccess.Write, FileShare.Read))
{
stream.CopyTo(fs);
}
diff --git a/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj b/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj
index 97f090ab2..0f91d5285 100644
--- a/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj
+++ b/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj
@@ -46,18 +46,19 @@
<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.5917.1514, Culture=neutral, processorArchitecture=MSIL">
- <SpecificVersion>False</SpecificVersion>
- <HintPath>..\packages\MediaBrowser.Naming.1.0.0.49\lib\portable-net45+sl4+wp71+win8+wpa81\MediaBrowser.Naming.dll</HintPath>
+ <Reference Include="MediaBrowser.Naming, Version=1.0.6046.32295, Culture=neutral, processorArchitecture=MSIL">
+ <HintPath>..\packages\MediaBrowser.Naming.1.0.0.53\lib\portable-net45+sl4+wp71+win8+wpa81\MediaBrowser.Naming.dll</HintPath>
+ <Private>True</Private>
</Reference>
<Reference Include="MoreLinq">
<HintPath>..\packages\morelinq.1.4.0\lib\net35\MoreLinq.dll</HintPath>
@@ -68,15 +69,16 @@
<Reference Include="ServiceStack.Api.Swagger">
<HintPath>..\ThirdParty\ServiceStack\ServiceStack.Api.Swagger.dll</HintPath>
</Reference>
- <Reference Include="SocketHttpListener, Version=1.0.5908.28560, Culture=neutral, processorArchitecture=MSIL">
- <SpecificVersion>False</SpecificVersion>
- <HintPath>..\packages\SocketHttpListener.1.0.0.29\lib\net45\SocketHttpListener.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.6046.26351, Culture=neutral, processorArchitecture=MSIL">
+ <HintPath>..\packages\SocketHttpListener.1.0.0.35\lib\net45\SocketHttpListener.dll</HintPath>
+ <Private>True</Private>
</Reference>
<Reference Include="System" />
<Reference Include="System.Core" />
- <Reference Include="System.Data.SQLite">
- <HintPath>..\ThirdParty\System.Data.SQLite.ManagedOnly\1.0.94.0\System.Data.SQLite.dll</HintPath>
- </Reference>
<Reference Include="Microsoft.CSharp" />
<Reference Include="System.Data" />
<Reference Include="System.Net" />
@@ -99,6 +101,7 @@
<Reference Include="ServiceStack.Text">
<HintPath>..\ThirdParty\ServiceStack.Text\ServiceStack.Text.dll</HintPath>
</Reference>
+ <Reference Include="System.Xml.Linq" />
<Reference Include="UniversalDetector">
<HintPath>..\ThirdParty\UniversalDetector\UniversalDetector.dll</HintPath>
</Reference>
@@ -139,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" />
@@ -152,6 +156,7 @@
<Compile Include="EntryPoints\ServerEventNotifier.cs" />
<Compile Include="EntryPoints\UserDataChangeNotifier.cs" />
<Compile Include="FileOrganization\OrganizerScheduledTask.cs" />
+ <Compile Include="HttpServer\AsyncStreamWriterFunc.cs" />
<Compile Include="HttpServer\IHttpListener.cs" />
<Compile Include="HttpServer\Security\AuthorizationContext.cs" />
<Compile Include="HttpServer\ContainerAdapter.cs" />
@@ -178,6 +183,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" />
@@ -221,6 +227,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" />
@@ -229,7 +236,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" />
@@ -242,6 +249,12 @@
<Compile Include="LiveTv\ProgramImageProvider.cs" />
<Compile Include="LiveTv\RecordingImageProvider.cs" />
<Compile Include="LiveTv\RefreshChannelsScheduledTask.cs" />
+ <Compile Include="LiveTv\TunerHosts\SatIp\ChannelScan.cs" />
+ <Compile Include="LiveTv\TunerHosts\SatIp\Rtsp\RtspMethod.cs" />
+ <Compile Include="LiveTv\TunerHosts\SatIp\Rtsp\RtspRequest.cs" />
+ <Compile Include="LiveTv\TunerHosts\SatIp\Rtsp\RtspResponse.cs" />
+ <Compile Include="LiveTv\TunerHosts\SatIp\Rtsp\RtspSession.cs" />
+ <Compile Include="LiveTv\TunerHosts\SatIp\Rtsp\RtspStatusCode.cs" />
<Compile Include="LiveTv\TunerHosts\SatIp\SatIpHost.cs" />
<Compile Include="LiveTv\TunerHosts\SatIp\SatIpDiscovery.cs" />
<Compile Include="Localization\LocalizationManager.cs" />
@@ -250,6 +263,8 @@
<Compile Include="Notifications\IConfigurableNotificationService.cs" />
<Compile Include="Persistence\BaseSqliteRepository.cs" />
<Compile Include="Persistence\CleanDatabaseScheduledTask.cs" />
+ <Compile Include="Persistence\DataExtensions.cs" />
+ <Compile Include="Persistence\IDbConnector.cs" />
<Compile Include="Persistence\MediaStreamColumns.cs" />
<Compile Include="Social\SharingManager.cs" />
<Compile Include="Social\SharingRepository.cs" />
@@ -264,10 +279,8 @@
<Compile Include="Notifications\InternalNotificationService.cs" />
<Compile Include="Notifications\NotificationConfigurationFactory.cs" />
<Compile Include="Notifications\NotificationManager.cs" />
- <Compile Include="Persistence\SqliteExtensions.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" />
@@ -381,7 +394,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" />
@@ -598,10 +707,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>
@@ -622,64 +751,14 @@
<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>
- <ItemGroup>
- <Folder Include="HttpServer\NetListener\" />
- </ItemGroup>
+ <ItemGroup />
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.
diff --git a/MediaBrowser.Server.Implementations/MediaEncoder/EncodingManager.cs b/MediaBrowser.Server.Implementations/MediaEncoder/EncodingManager.cs
index a7b0d61c7..7f709d084 100644
--- a/MediaBrowser.Server.Implementations/MediaEncoder/EncodingManager.cs
+++ b/MediaBrowser.Server.Implementations/MediaEncoder/EncodingManager.cs
@@ -139,15 +139,20 @@ namespace MediaBrowser.Server.Implementations.MediaEncoder
{
_fileSystem.CreateDirectory(Path.GetDirectoryName(path));
- using (var stream = await _encoder.ExtractVideoImage(inputPath, protocol, video.Video3DFormat, time, cancellationToken).ConfigureAwait(false))
+ var tempFile = await _encoder.ExtractVideoImage(inputPath, protocol, video.Video3DFormat, time, cancellationToken).ConfigureAwait(false);
+ File.Copy(tempFile, path, true);
+
+ try
+ {
+ File.Delete(tempFile);
+ }
+ catch
{
- using (var fileStream = _fileSystem.GetFileStream(path, FileMode.Create, FileAccess.Write, FileShare.Read, true))
- {
- await stream.CopyToAsync(fileStream).ConfigureAwait(false);
- }
+
}
chapter.ImagePath = path;
+ chapter.ImageDateModified = _fileSystem.GetLastWriteTimeUtc(path);
changesMade = true;
}
catch (Exception ex)
@@ -166,6 +171,7 @@ namespace MediaBrowser.Server.Implementations.MediaEncoder
else if (!string.Equals(path, chapter.ImagePath, StringComparison.OrdinalIgnoreCase))
{
chapter.ImagePath = path;
+ chapter.ImageDateModified = _fileSystem.GetLastWriteTimeUtc(path);
changesMade = true;
}
}
diff --git a/MediaBrowser.Server.Implementations/Notifications/SqliteNotificationsRepository.cs b/MediaBrowser.Server.Implementations/Notifications/SqliteNotificationsRepository.cs
index 7302431e1..be8c6d48d 100644
--- a/MediaBrowser.Server.Implementations/Notifications/SqliteNotificationsRepository.cs
+++ b/MediaBrowser.Server.Implementations/Notifications/SqliteNotificationsRepository.cs
@@ -15,73 +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)
- {
- _appPaths = appPaths;
- }
-
public async Task Initialize()
{
- var dbFile = Path.Combine(_appPaths.DataPath, "notifications.db");
-
- _connection = await SqliteExtensions.ConnectToDb(dbFile, Logger).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_Notifications on Notifications(Id, UserId)",
-
- //pragmas
- "pragma temp_store = memory",
-
- "pragma shrink_memory"
+ "create index if not exists idx_Notifications1 on Notifications(Id)",
+ "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>
@@ -93,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;
+ }
}
}
@@ -143,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;
+ }
}
}
@@ -178,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)
@@ -272,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();
}
}
@@ -365,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();
+ }
+ }
+ }
}
}
@@ -417,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 3c8a0ffeb..bf2afb5ac 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;
@@ -32,7 +33,7 @@ namespace MediaBrowser.Server.Implementations.Persistence
private readonly ILocalizationManager _localization;
private readonly ITaskManager _taskManager;
- public const int MigrationVersion = 20;
+ public const int MigrationVersion = 23;
public static bool EnableUnavailableMessage = false;
public CleanDatabaseScheduledTask(ILibraryManager libraryManager, IItemRepository itemRepo, ILogger logger, IServerConfigurationManager config, IFileSystem fileSystem, IHttpServer httpServer, ILocalizationManager localization, ITaskManager taskManager)
@@ -110,6 +111,12 @@ namespace MediaBrowser.Server.Implementations.Persistence
_config.SaveConfiguration();
}
+ if (_config.Configuration.SchemaVersion < SqliteItemRepository.LatestSchemaVersion)
+ {
+ _config.Configuration.SchemaVersion = SqliteItemRepository.LatestSchemaVersion;
+ _config.SaveConfiguration();
+ }
+
if (EnableUnavailableMessage)
{
EnableUnavailableMessage = false;
@@ -139,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;
@@ -147,6 +155,8 @@ namespace MediaBrowser.Server.Implementations.Persistence
_logger.Debug("Upgrading schema for {0} items", numItems);
+ var list = new List<BaseItem>();
+
foreach (var itemId in itemIds)
{
cancellationToken.ThrowIfCancellationRequested();
@@ -158,27 +168,50 @@ namespace MediaBrowser.Server.Implementations.Persistence
if (item != null)
{
- try
- {
- await _itemRepo.SaveItem(item, cancellationToken).ConfigureAwait(false);
- }
- catch (OperationCanceledException)
- {
- throw;
- }
- catch (Exception ex)
- {
- _logger.ErrorException("Error saving item", ex);
- }
+ list.Add(item);
}
}
+ if (list.Count >= 1000)
+ {
+ try
+ {
+ await _itemRepo.SaveItems(list, cancellationToken).ConfigureAwait(false);
+ }
+ catch (OperationCanceledException)
+ {
+ throw;
+ }
+ catch (Exception ex)
+ {
+ _logger.ErrorException("Error saving item", ex);
+ }
+
+ list.Clear();
+ }
+
numComplete++;
double percent = numComplete;
percent /= numItems;
progress.Report(percent * 100);
}
+ if (list.Count > 0)
+ {
+ try
+ {
+ await _itemRepo.SaveItems(list, cancellationToken).ConfigureAwait(false);
+ }
+ catch (OperationCanceledException)
+ {
+ throw;
+ }
+ catch (Exception ex)
+ {
+ _logger.ErrorException("Error saving item", ex);
+ }
+ }
+
progress.Report(100);
}
@@ -230,14 +263,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
}
});
@@ -307,8 +340,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/DataExtensions.cs b/MediaBrowser.Server.Implementations/Persistence/DataExtensions.cs
new file mode 100644
index 000000000..61ce6e351
--- /dev/null
+++ b/MediaBrowser.Server.Implementations/Persistence/DataExtensions.cs
@@ -0,0 +1,185 @@
+using System.Text;
+using MediaBrowser.Model.Logging;
+using MediaBrowser.Model.Serialization;
+using System;
+using System.Data;
+using System.IO;
+using System.Threading.Tasks;
+
+namespace MediaBrowser.Server.Implementations.Persistence
+{
+ static class DataExtensions
+ {
+ /// <summary>
+ /// Determines whether the specified conn is open.
+ /// </summary>
+ /// <param name="conn">The conn.</param>
+ /// <returns><c>true</c> if the specified conn is open; otherwise, <c>false</c>.</returns>
+ public static bool IsOpen(this IDbConnection conn)
+ {
+ return conn.State == ConnectionState.Open;
+ }
+
+ public static IDataParameter GetParameter(this IDbCommand cmd, int index)
+ {
+ return (IDataParameter)cmd.Parameters[index];
+ }
+
+ public static IDataParameter Add(this IDataParameterCollection paramCollection, IDbCommand cmd, string name, DbType type)
+ {
+ var param = cmd.CreateParameter();
+
+ param.ParameterName = name;
+ param.DbType = type;
+
+ paramCollection.Add(param);
+
+ return param;
+ }
+
+ public static IDataParameter Add(this IDataParameterCollection paramCollection, IDbCommand cmd, string name)
+ {
+ var param = cmd.CreateParameter();
+
+ param.ParameterName = name;
+
+ paramCollection.Add(param);
+
+ return param;
+ }
+
+
+ /// <summary>
+ /// Gets a stream from a DataReader at a given ordinal
+ /// </summary>
+ /// <param name="reader">The reader.</param>
+ /// <param name="ordinal">The ordinal.</param>
+ /// <returns>Stream.</returns>
+ /// <exception cref="System.ArgumentNullException">reader</exception>
+ public static Stream GetMemoryStream(this IDataReader reader, int ordinal)
+ {
+ if (reader == null)
+ {
+ throw new ArgumentNullException("reader");
+ }
+
+ var memoryStream = new MemoryStream();
+ var num = 0L;
+ var array = new byte[4096];
+ long bytes;
+ do
+ {
+ bytes = reader.GetBytes(ordinal, num, array, 0, array.Length);
+ memoryStream.Write(array, 0, (int)bytes);
+ num += bytes;
+ }
+ while (bytes > 0L);
+ memoryStream.Position = 0;
+ return memoryStream;
+ }
+
+ /// <summary>
+ /// Runs the queries.
+ /// </summary>
+ /// <param name="connection">The connection.</param>
+ /// <param name="queries">The queries.</param>
+ /// <param name="logger">The logger.</param>
+ /// <returns><c>true</c> if XXXX, <c>false</c> otherwise</returns>
+ /// <exception cref="System.ArgumentNullException">queries</exception>
+ public static void RunQueries(this IDbConnection connection, string[] queries, ILogger logger)
+ {
+ if (queries == null)
+ {
+ throw new ArgumentNullException("queries");
+ }
+
+ using (var tran = connection.BeginTransaction())
+ {
+ try
+ {
+ using (var cmd = connection.CreateCommand())
+ {
+ foreach (var query in queries)
+ {
+ cmd.Transaction = tran;
+ cmd.CommandText = query;
+ cmd.ExecuteNonQuery();
+ }
+ }
+
+ tran.Commit();
+ }
+ catch (Exception e)
+ {
+ logger.ErrorException("Error running queries", e);
+ tran.Rollback();
+ throw;
+ }
+ }
+ }
+
+ public static void Attach(IDbConnection db, string path, string alias)
+ {
+ using (var cmd = db.CreateCommand())
+ {
+ cmd.CommandText = string.Format("attach @dbPath as {0};", alias);
+ cmd.Parameters.Add(cmd, "@dbPath", DbType.String);
+ cmd.GetParameter(0).Value = path;
+
+ cmd.ExecuteNonQuery();
+ }
+ }
+
+ /// <summary>
+ /// Serializes to bytes.
+ /// </summary>
+ /// <param name="json">The json.</param>
+ /// <param name="obj">The obj.</param>
+ /// <returns>System.Byte[][].</returns>
+ /// <exception cref="System.ArgumentNullException">obj</exception>
+ public static byte[] SerializeToBytes(this IJsonSerializer json, object obj)
+ {
+ if (obj == null)
+ {
+ throw new ArgumentNullException("obj");
+ }
+
+ using (var stream = new MemoryStream())
+ {
+ json.SerializeToStream(obj, stream);
+ return stream.ToArray();
+ }
+ }
+
+ public static void AddColumn(this IDbConnection connection, ILogger logger, string table, string columnName, string type)
+ {
+ using (var cmd = connection.CreateCommand())
+ {
+ cmd.CommandText = "PRAGMA table_info(" + table + ")";
+
+ 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, columnName, StringComparison.OrdinalIgnoreCase))
+ {
+ return;
+ }
+ }
+ }
+ }
+ }
+
+ var builder = new StringBuilder();
+
+ builder.AppendLine("alter table " + table);
+ builder.AppendLine("add column " + columnName + " " + type);
+
+ connection.RunQueries(new[] { builder.ToString() }, logger);
+ }
+ }
+} \ No newline at end of file
diff --git a/MediaBrowser.Server.Implementations/Persistence/IDbConnector.cs b/MediaBrowser.Server.Implementations/Persistence/IDbConnector.cs
new file mode 100644
index 000000000..596cf8407
--- /dev/null
+++ b/MediaBrowser.Server.Implementations/Persistence/IDbConnector.cs
@@ -0,0 +1,10 @@
+using System.Data;
+using System.Threading.Tasks;
+
+namespace MediaBrowser.Server.Implementations.Persistence
+{
+ public interface IDbConnector
+ {
+ 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 211c77107..1d9be2e0d 100644
--- a/MediaBrowser.Server.Implementations/Persistence/MediaStreamColumns.cs
+++ b/MediaBrowser.Server.Implementations/Persistence/MediaStreamColumns.cs
@@ -21,14 +21,18 @@ namespace MediaBrowser.Server.Implementations.Persistence
AddPixelFormatColumnCommand();
AddBitDepthCommand();
AddIsAnamorphicColumn();
- AddIsCabacColumn();
AddKeyFramesColumn();
AddRefFramesCommand();
AddCodecTagColumn();
AddCommentColumn();
+ AddNalColumn();
+ AddIsAvcColumn();
+ AddTitleColumn();
+ AddTimeBaseColumn();
+ AddCodecTimeBaseColumn();
}
- private void AddCommentColumn()
+ private void AddIsAvcColumn()
{
using (var cmd = _connection.CreateCommand())
{
@@ -42,7 +46,7 @@ namespace MediaBrowser.Server.Implementations.Persistence
{
var name = reader.GetString(1);
- if (string.Equals(name, "Comment", StringComparison.OrdinalIgnoreCase))
+ if (string.Equals(name, "IsAvc", StringComparison.OrdinalIgnoreCase))
{
return;
}
@@ -54,12 +58,12 @@ namespace MediaBrowser.Server.Implementations.Persistence
var builder = new StringBuilder();
builder.AppendLine("alter table mediastreams");
- builder.AppendLine("add column Comment TEXT");
+ builder.AppendLine("add column IsAvc BIT NULL");
_connection.RunQueries(new[] { builder.ToString() }, _logger);
}
- private void AddCodecTagColumn()
+ private void AddTimeBaseColumn()
{
using (var cmd = _connection.CreateCommand())
{
@@ -73,7 +77,7 @@ namespace MediaBrowser.Server.Implementations.Persistence
{
var name = reader.GetString(1);
- if (string.Equals(name, "CodecTag", StringComparison.OrdinalIgnoreCase))
+ if (string.Equals(name, "TimeBase", StringComparison.OrdinalIgnoreCase))
{
return;
}
@@ -85,12 +89,12 @@ namespace MediaBrowser.Server.Implementations.Persistence
var builder = new StringBuilder();
builder.AppendLine("alter table mediastreams");
- builder.AppendLine("add column CodecTag TEXT");
+ builder.AppendLine("add column TimeBase TEXT");
_connection.RunQueries(new[] { builder.ToString() }, _logger);
}
- private void AddPixelFormatColumnCommand()
+ private void AddCodecTimeBaseColumn()
{
using (var cmd = _connection.CreateCommand())
{
@@ -104,7 +108,7 @@ namespace MediaBrowser.Server.Implementations.Persistence
{
var name = reader.GetString(1);
- if (string.Equals(name, "PixelFormat", StringComparison.OrdinalIgnoreCase))
+ if (string.Equals(name, "CodecTimeBase", StringComparison.OrdinalIgnoreCase))
{
return;
}
@@ -116,12 +120,12 @@ namespace MediaBrowser.Server.Implementations.Persistence
var builder = new StringBuilder();
builder.AppendLine("alter table mediastreams");
- builder.AppendLine("add column PixelFormat TEXT");
+ builder.AppendLine("add column CodecTimeBase TEXT");
_connection.RunQueries(new[] { builder.ToString() }, _logger);
}
- private void AddBitDepthCommand()
+ private void AddTitleColumn()
{
using (var cmd = _connection.CreateCommand())
{
@@ -135,7 +139,7 @@ namespace MediaBrowser.Server.Implementations.Persistence
{
var name = reader.GetString(1);
- if (string.Equals(name, "BitDepth", StringComparison.OrdinalIgnoreCase))
+ if (string.Equals(name, "Title", StringComparison.OrdinalIgnoreCase))
{
return;
}
@@ -147,12 +151,12 @@ namespace MediaBrowser.Server.Implementations.Persistence
var builder = new StringBuilder();
builder.AppendLine("alter table mediastreams");
- builder.AppendLine("add column BitDepth INT NULL");
+ builder.AppendLine("add column Title TEXT");
_connection.RunQueries(new[] { builder.ToString() }, _logger);
}
- private void AddRefFramesCommand()
+ private void AddNalColumn()
{
using (var cmd = _connection.CreateCommand())
{
@@ -166,7 +170,7 @@ namespace MediaBrowser.Server.Implementations.Persistence
{
var name = reader.GetString(1);
- if (string.Equals(name, "RefFrames", StringComparison.OrdinalIgnoreCase))
+ if (string.Equals(name, "NalLengthSize", StringComparison.OrdinalIgnoreCase))
{
return;
}
@@ -178,12 +182,74 @@ namespace MediaBrowser.Server.Implementations.Persistence
var builder = new StringBuilder();
builder.AppendLine("alter table mediastreams");
- builder.AppendLine("add column RefFrames INT NULL");
+ builder.AppendLine("add column NalLengthSize TEXT");
+
+ _connection.RunQueries(new[] { builder.ToString() }, _logger);
+ }
+
+ private void AddCommentColumn()
+ {
+ 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, "Comment", StringComparison.OrdinalIgnoreCase))
+ {
+ return;
+ }
+ }
+ }
+ }
+ }
+
+ var builder = new StringBuilder();
+
+ builder.AppendLine("alter table mediastreams");
+ builder.AppendLine("add column Comment TEXT");
+
+ _connection.RunQueries(new[] { builder.ToString() }, _logger);
+ }
+
+ private void AddCodecTagColumn()
+ {
+ 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, "CodecTag", StringComparison.OrdinalIgnoreCase))
+ {
+ return;
+ }
+ }
+ }
+ }
+ }
+
+ var builder = new StringBuilder();
+
+ builder.AppendLine("alter table mediastreams");
+ builder.AppendLine("add column CodecTag TEXT");
_connection.RunQueries(new[] { builder.ToString() }, _logger);
}
- private void AddIsCabacColumn()
+ private void AddPixelFormatColumnCommand()
{
using (var cmd = _connection.CreateCommand())
{
@@ -197,7 +263,7 @@ namespace MediaBrowser.Server.Implementations.Persistence
{
var name = reader.GetString(1);
- if (string.Equals(name, "IsCabac", StringComparison.OrdinalIgnoreCase))
+ if (string.Equals(name, "PixelFormat", StringComparison.OrdinalIgnoreCase))
{
return;
}
@@ -209,7 +275,69 @@ namespace MediaBrowser.Server.Implementations.Persistence
var builder = new StringBuilder();
builder.AppendLine("alter table mediastreams");
- builder.AppendLine("add column IsCabac BIT NULL");
+ builder.AppendLine("add column PixelFormat TEXT");
+
+ _connection.RunQueries(new[] { builder.ToString() }, _logger);
+ }
+
+ private void AddBitDepthCommand()
+ {
+ 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, "BitDepth", StringComparison.OrdinalIgnoreCase))
+ {
+ return;
+ }
+ }
+ }
+ }
+ }
+
+ var builder = new StringBuilder();
+
+ builder.AppendLine("alter table mediastreams");
+ builder.AppendLine("add column BitDepth INT NULL");
+
+ _connection.RunQueries(new[] { builder.ToString() }, _logger);
+ }
+
+ private void AddRefFramesCommand()
+ {
+ 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, "RefFrames", StringComparison.OrdinalIgnoreCase))
+ {
+ return;
+ }
+ }
+ }
+ }
+ }
+
+ var builder = new StringBuilder();
+
+ builder.AppendLine("alter table mediastreams");
+ builder.AppendLine("add column RefFrames INT NULL");
_connection.RunQueries(new[] { builder.ToString() }, _logger);
}
diff --git a/MediaBrowser.Server.Implementations/Persistence/SqliteDisplayPreferencesRepository.cs b/MediaBrowser.Server.Implementations/Persistence/SqliteDisplayPreferencesRepository.cs
index 45e0304c1..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()
{
- var dbFile = Path.Combine(_appPaths.DataPath, "displaypreferences.db");
-
- _connection = await SqliteExtensions.ConnectToDb(dbFile, Logger).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
index 4fb1e07dd..d5b582da5 100644
--- a/MediaBrowser.Server.Implementations/Persistence/SqliteExtensions.cs
+++ b/MediaBrowser.Server.Implementations/Persistence/SqliteExtensions.cs
@@ -1,218 +1,58 @@
-using System.Text;
-using MediaBrowser.Model.Logging;
-using MediaBrowser.Model.Serialization;
-using System;
+using System;
+using System.Collections.Generic;
using System.Data;
using System.Data.SQLite;
-using System.IO;
+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>
- static class SqliteExtensions
+ public static class SqliteExtensions
{
/// <summary>
- /// Determines whether the specified conn is open.
- /// </summary>
- /// <param name="conn">The conn.</param>
- /// <returns><c>true</c> if the specified conn is open; otherwise, <c>false</c>.</returns>
- public static bool IsOpen(this IDbConnection conn)
- {
- return conn.State == ConnectionState.Open;
- }
-
- public static IDataParameter GetParameter(this IDbCommand cmd, int index)
- {
- return (IDataParameter)cmd.Parameters[index];
- }
-
- public static IDataParameter Add(this IDataParameterCollection paramCollection, IDbCommand cmd, string name, DbType type)
- {
- var param = cmd.CreateParameter();
-
- param.ParameterName = name;
- param.DbType = type;
-
- paramCollection.Add(param);
-
- return param;
- }
-
- public static IDataParameter Add(this IDataParameterCollection paramCollection, IDbCommand cmd, string name)
- {
- var param = cmd.CreateParameter();
-
- param.ParameterName = name;
-
- paramCollection.Add(param);
-
- return param;
- }
-
-
- /// <summary>
- /// Gets a stream from a DataReader at a given ordinal
- /// </summary>
- /// <param name="reader">The reader.</param>
- /// <param name="ordinal">The ordinal.</param>
- /// <returns>Stream.</returns>
- /// <exception cref="System.ArgumentNullException">reader</exception>
- public static Stream GetMemoryStream(this IDataReader reader, int ordinal)
- {
- if (reader == null)
- {
- throw new ArgumentNullException("reader");
- }
-
- var memoryStream = new MemoryStream();
- var num = 0L;
- var array = new byte[4096];
- long bytes;
- do
- {
- bytes = reader.GetBytes(ordinal, num, array, 0, array.Length);
- memoryStream.Write(array, 0, (int)bytes);
- num += bytes;
- }
- while (bytes > 0L);
- memoryStream.Position = 0;
- return memoryStream;
- }
-
- /// <summary>
- /// Runs the queries.
- /// </summary>
- /// <param name="connection">The connection.</param>
- /// <param name="queries">The queries.</param>
- /// <param name="logger">The logger.</param>
- /// <returns><c>true</c> if XXXX, <c>false</c> otherwise</returns>
- /// <exception cref="System.ArgumentNullException">queries</exception>
- public static void RunQueries(this IDbConnection connection, string[] queries, ILogger logger)
- {
- if (queries == null)
- {
- throw new ArgumentNullException("queries");
- }
-
- using (var tran = connection.BeginTransaction())
- {
- try
- {
- using (var cmd = connection.CreateCommand())
- {
- foreach (var query in queries)
- {
- cmd.Transaction = tran;
- cmd.CommandText = query;
- cmd.ExecuteNonQuery();
- }
- }
-
- tran.Commit();
- }
- catch (Exception e)
- {
- logger.ErrorException("Error running queries", e);
- tran.Rollback();
- throw;
- }
- }
- }
-
- /// <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)
+ public static async Task<IDbConnection> ConnectToDb(string dbPath, bool isReadOnly, bool enablePooling, int? cacheSize, ILogger logger)
{
if (string.IsNullOrEmpty(dbPath))
{
throw new ArgumentNullException("dbPath");
}
- logger.Info("Sqlite {0} opening {1}", SQLiteConnection.SQLiteVersion, dbPath);
+ SQLiteConnection.SetMemoryStatus(false);
var connectionstr = new SQLiteConnectionStringBuilder
{
PageSize = 4096,
- CacheSize = 2000,
- SyncMode = SynchronizationModes.Full,
+ CacheSize = cacheSize ?? 2000,
+ SyncMode = SynchronizationModes.Normal,
DataSource = dbPath,
- JournalMode = SQLiteJournalModeEnum.Wal
- };
-
- var connection = new SQLiteConnection(connectionstr.ConnectionString);
+ JournalMode = SQLiteJournalModeEnum.Wal,
- await connection.OpenAsync().ConfigureAwait(false);
-
- return connection;
- }
-
- public static void Attach(IDbConnection db, string path, string alias)
- {
- using (var cmd = db.CreateCommand())
- {
- cmd.CommandText = string.Format("attach '{0}' as {1};", path, alias);
- cmd.ExecuteNonQuery();
- }
- }
-
- /// <summary>
- /// Serializes to bytes.
- /// </summary>
- /// <param name="json">The json.</param>
- /// <param name="obj">The obj.</param>
- /// <returns>System.Byte[][].</returns>
- /// <exception cref="System.ArgumentNullException">obj</exception>
- public static byte[] SerializeToBytes(this IJsonSerializer json, object obj)
- {
- if (obj == null)
- {
- throw new ArgumentNullException("obj");
- }
+ // This is causing crashing under linux
+ Pooling = enablePooling && Environment.OSVersion.Platform == PlatformID.Win32NT,
+ ReadOnly = isReadOnly
+ };
- using (var stream = new MemoryStream())
- {
- json.SerializeToStream(obj, stream);
- return stream.ToArray();
- }
- }
+ var connectionString = connectionstr.ConnectionString;
- public static void AddColumn(this IDbConnection connection, ILogger logger, string table, string columnName, string type)
- {
- using (var cmd = connection.CreateCommand())
+ if (!enablePooling)
{
- cmd.CommandText = "PRAGMA table_info(" + table + ")";
-
- 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, columnName, StringComparison.OrdinalIgnoreCase))
- {
- return;
- }
- }
- }
- }
+ logger.Info("Sqlite {0} opening {1}", SQLiteConnection.SQLiteVersion, connectionString);
}
- var builder = new StringBuilder();
+ var connection = new SQLiteConnection(connectionString);
- builder.AppendLine("alter table " + table);
- builder.AppendLine("add column " + columnName + " " + type);
+ await connection.OpenAsync().ConfigureAwait(false);
- connection.RunQueries(new[] { builder.ToString() }, logger);
+ return connection;
}
}
-} \ No newline at end of file
+}
diff --git a/MediaBrowser.Server.Implementations/Persistence/SqliteFileOrganizationRepository.cs b/MediaBrowser.Server.Implementations/Persistence/SqliteFileOrganizationRepository.cs
index 2d5aad04d..7a5e00090 100644
--- a/MediaBrowser.Server.Implementations/Persistence/SqliteFileOrganizationRepository.cs
+++ b/MediaBrowser.Server.Implementations/Persistence/SqliteFileOrganizationRepository.cs
@@ -16,19 +16,11 @@ 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>
@@ -37,53 +29,16 @@ namespace MediaBrowser.Server.Implementations.Persistence
/// <returns>Task.</returns>
public async Task Initialize()
{
- var dbFile = Path.Combine(_appPaths.DataPath, "fileorganization.db");
-
- _connection = await SqliteExtensions.ConnectToDb(dbFile, Logger).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 af275faee..63dd29e0d 100644
--- a/MediaBrowser.Server.Implementations/Persistence/SqliteItemRepository.cs
+++ b/MediaBrowser.Server.Implementations/Persistence/SqliteItemRepository.cs
@@ -1,4 +1,3 @@
-using MediaBrowser.Common.Configuration;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.Audio;
using MediaBrowser.Controller.Entities.Movies;
@@ -16,10 +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
@@ -54,7 +57,7 @@ namespace MediaBrowser.Server.Implementations.Persistence
/// <summary>
/// The _app paths
/// </summary>
- private readonly IApplicationPaths _appPaths;
+ private readonly IServerConfigurationManager _config;
/// <summary>
/// The _save item command
@@ -77,79 +80,110 @@ namespace MediaBrowser.Server.Implementations.Persistence
private IDbCommand _deleteAncestorsCommand;
private IDbCommand _saveAncestorCommand;
+ private IDbCommand _deleteUserDataKeysCommand;
+ private IDbCommand _saveUserDataKeysCommand;
+
+ private IDbCommand _deleteItemValuesCommand;
+ private IDbCommand _saveItemValuesCommand;
+
+ private IDbCommand _deleteProviderIdsCommand;
+ private IDbCommand _saveProviderIdsCommand;
+
+ private IDbCommand _deleteImagesCommand;
+ private IDbCommand _saveImagesCommand;
+
private IDbCommand _updateInheritedRatingCommand;
+ private IDbCommand _updateInheritedTagsCommand;
- private const int LatestSchemaVersion = 53;
+ public const int LatestSchemaVersion = 108;
/// <summary>
/// Initializes a new instance of the <see cref="SqliteItemRepository"/> class.
/// </summary>
- /// <param name="appPaths">The app paths.</param>
- /// <param name="jsonSerializer">The json serializer.</param>
- /// <param name="logManager">The log manager.</param>
- /// <exception cref="System.ArgumentNullException">
- /// appPaths
- /// or
- /// jsonSerializer
- /// </exception>
- public SqliteItemRepository(IApplicationPaths appPaths, IJsonSerializer jsonSerializer, ILogManager logManager)
- : base(logManager)
+ public SqliteItemRepository(IServerConfigurationManager config, IJsonSerializer jsonSerializer, ILogManager logManager, IDbConnector connector)
+ : base(logManager, connector)
{
- if (appPaths == null)
+ if (config == null)
{
- throw new ArgumentNullException("appPaths");
+ throw new ArgumentNullException("config");
}
if (jsonSerializer == null)
{
throw new ArgumentNullException("jsonSerializer");
}
- _appPaths = appPaths;
+ _config = config;
_jsonSerializer = jsonSerializer;
- _criticReviewsPath = Path.Combine(_appPaths.DataPath, "critic-reviews");
+ _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 cacheSize = _config.Configuration.SqliteCacheSize;
+ if (cacheSize <= 0)
+ {
+ cacheSize = Math.Min(Environment.ProcessorCount * 50000, 200000);
+ }
+
+ var connection = await DbConnector.Connect(DbFilePath, false, false, 0 - cacheSize).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()
+ public async Task Initialize(SqliteUserDataRepository userDataRepo)
{
- var dbFile = Path.Combine(_appPaths.DataPath, "library.db");
-
- _connection = await SqliteExtensions.ConnectToDb(dbFile, Logger).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, IsCabac BIT NULL, CodecTag TEXT NULL, Comment 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)",
"create table if not exists AncestorIds (ItemId GUID, AncestorId GUID, AncestorIdText TEXT, PRIMARY KEY (ItemId, AncestorId))",
"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 Priority INT, PRIMARY KEY (ItemId, UserDataKey))",
+
+ "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+" on "+ChaptersTableName+"(ItemId, ChapterIndex)",
createMediaStreamsTableCommand,
- "create index if not exists idx_mediastreams on mediastreams(ItemId, StreamIndex)",
- //pragmas
- "pragma temp_store = memory",
+ "create index if not exists idx_mediastreams1 on mediastreams(ItemId)",
- "pragma shrink_memory"
};
_connection.RunQueries(queries, Logger);
@@ -223,84 +257,95 @@ namespace MediaBrowser.Server.Implementations.Persistence
_connection.AddColumn(Logger, "TypedBaseItems", "TrailerTypes", "Text");
_connection.AddColumn(Logger, "TypedBaseItems", "CriticRating", "Float");
_connection.AddColumn(Logger, "TypedBaseItems", "CriticRatingSummary", "Text");
+ _connection.AddColumn(Logger, "TypedBaseItems", "InheritedTags", "Text");
+ _connection.AddColumn(Logger, "TypedBaseItems", "CleanName", "Text");
+ _connection.AddColumn(Logger, "TypedBaseItems", "PresentationUniqueKey", "Text");
+ _connection.AddColumn(Logger, "TypedBaseItems", "SlugName", "Text");
+ _connection.AddColumn(Logger, "TypedBaseItems", "OriginalTitle", "Text");
+ _connection.AddColumn(Logger, "TypedBaseItems", "PrimaryVersionId", "Text");
+ _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, "TypedBaseItems", "SeasonName", "Text");
+ _connection.AddColumn(Logger, "TypedBaseItems", "SeasonId", "GUID");
+ _connection.AddColumn(Logger, "TypedBaseItems", "SeriesId", "GUID");
+ _connection.AddColumn(Logger, "TypedBaseItems", "SeriesSortName", "Text");
+
+ _connection.AddColumn(Logger, "UserDataKeys", "Priority", "INT");
+ _connection.AddColumn(Logger, "ItemValues", "CleanValue", "Text");
+
+ _connection.AddColumn(Logger, ChaptersTableName, "ImageDateModified", "DATETIME");
+
+ 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_PathTypedBaseItems on TypedBaseItems(Path)",
+ "create index if not exists idx_ParentIdTypedBaseItems on TypedBaseItems(ParentId)",
+
+ "create index if not exists idx_PresentationUniqueKey on TypedBaseItems(PresentationUniqueKey)",
+ "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)",
+
+ // covering index
+ "create index if not exists idx_TopParentIdGuid on TypedBaseItems(TopParentId,Guid)",
+
+ // live tv programs
+ "create index if not exists idx_TypeTopParentIdStartDate on TypedBaseItems(Type,TopParentId,StartDate)",
+
+ // covering index for getitemvalues
+ "create index if not exists idx_TypeTopParentIdGuid on TypedBaseItems(Type,TopParentId,Guid)",
+
+ // 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)",
+
+ // 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)",
+
+ // resume
+ "create index if not exists idx_TypeTopParentId7 on TypedBaseItems(TopParentId,MediaType,IsVirtualItem,PresentationUniqueKey)",
+
+ // 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)",
+
+ // covering index
+ "create index if not exists idx_UserDataKeys3 on UserDataKeys(ItemId,Priority,UserDataKey)"
+ };
+
+ _connection.RunQueries(postQueries, Logger);
PrepareStatements();
new MediaStreamColumns(_connection, Logger).AddColumns();
- var chapterDbFile = Path.Combine(_appPaths.DataPath, "chapters.db");
- if (File.Exists(chapterDbFile))
- {
- MigrateChapters(chapterDbFile);
- }
-
- var mediaStreamsDbFile = Path.Combine(_appPaths.DataPath, "mediainfo.db");
- if (File.Exists(mediaStreamsDbFile))
- {
- MigrateMediaStreams(mediaStreamsDbFile);
- }
- }
-
- private void MigrateMediaStreams(string file)
- {
- try
- {
- var backupFile = file + ".bak";
- File.Copy(file, backupFile, true);
- SqliteExtensions.Attach(_connection, backupFile, "MediaInfoOld");
-
- var columns = string.Join(",", _mediaStreamSaveColumns);
-
- string[] queries = {
- "REPLACE INTO mediastreams("+columns+") SELECT "+columns+" FROM MediaInfoOld.mediastreams;"
- };
-
- _connection.RunQueries(queries, Logger);
- }
- catch (Exception ex)
- {
- Logger.ErrorException("Error migrating media info database", ex);
- }
- finally
- {
- TryDeleteFile(file);
- }
- }
-
- private void MigrateChapters(string file)
- {
- try
- {
- var backupFile = file + ".bak";
- File.Copy(file, backupFile, true);
- SqliteExtensions.Attach(_connection, backupFile, "ChaptersOld");
-
- string[] queries = {
- "REPLACE INTO "+ChaptersTableName+"(ItemId, ChapterIndex, StartPositionTicks, Name, ImagePath) SELECT ItemId, ChapterIndex, StartPositionTicks, Name, ImagePath FROM ChaptersOld.Chapters;"
- };
-
- _connection.RunQueries(queries, Logger);
- }
- catch (Exception ex)
- {
- Logger.ErrorException("Error migrating chapter database", ex);
- }
- finally
- {
- TryDeleteFile(file);
- }
- }
-
- private void TryDeleteFile(string file)
- {
- try
- {
- File.Delete(file);
- }
- catch (Exception ex)
- {
- Logger.ErrorException("Error deleting file {0}", ex, file);
- }
+ 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 =
@@ -355,7 +400,19 @@ namespace MediaBrowser.Server.Implementations.Persistence
"Studios",
"Tags",
"SourceType",
- "TrailerTypes"
+ "TrailerTypes",
+ "OriginalTitle",
+ "PrimaryVersionId",
+ "DateLastMediaAdded",
+ "Album",
+ "CriticRating",
+ "CriticRatingSummary",
+ "IsVirtualItem",
+ "SeriesName",
+ "SeasonName",
+ "SeasonId",
+ "SeriesId",
+ "SeriesSortName"
};
private readonly string[] _mediaStreamSaveColumns =
@@ -385,9 +442,13 @@ namespace MediaBrowser.Server.Implementations.Persistence
"BitDepth",
"IsAnamorphic",
"RefFrames",
- "IsCabac",
"CodecTag",
- "Comment"
+ "Comment",
+ "NalLengthSize",
+ "IsAvc",
+ "Title",
+ "TimeBase",
+ "CodecTimeBase"
};
/// <summary>
@@ -400,7 +461,7 @@ namespace MediaBrowser.Server.Implementations.Persistence
"guid",
"type",
"data",
- "Path",
+ "Path",
"StartDate",
"EndDate",
"ChannelId",
@@ -459,7 +520,22 @@ namespace MediaBrowser.Server.Implementations.Persistence
"SourceType",
"TrailerTypes",
"CriticRating",
- "CriticRatingSummary"
+ "CriticRatingSummary",
+ "InheritedTags",
+ "CleanName",
+ "PresentationUniqueKey",
+ "SlugName",
+ "OriginalTitle",
+ "PrimaryVersionId",
+ "DateLastMediaAdded",
+ "Album",
+ "IsVirtualItem",
+ "SeriesName",
+ "UserDataKey",
+ "SeasonName",
+ "SeasonId",
+ "SeriesId",
+ "SeriesSortName"
};
_saveItemCommand = _connection.CreateCommand();
_saveItemCommand.CommandText = "replace into TypedBaseItems (" + string.Join(",", saveColumns.ToArray()) + ") values (";
@@ -518,6 +594,7 @@ namespace MediaBrowser.Server.Implementations.Persistence
_saveChapterCommand.Parameters.Add(_saveChapterCommand, "@StartPositionTicks");
_saveChapterCommand.Parameters.Add(_saveChapterCommand, "@Name");
_saveChapterCommand.Parameters.Add(_saveChapterCommand, "@ImagePath");
+ _saveChapterCommand.Parameters.Add(_saveChapterCommand, "@ImageDateModified");
// MediaStreams
_deleteStreamsCommand = _connection.CreateCommand();
@@ -537,8 +614,61 @@ namespace MediaBrowser.Server.Implementations.Persistence
_updateInheritedRatingCommand = _connection.CreateCommand();
_updateInheritedRatingCommand.CommandText = "Update TypedBaseItems set InheritedParentalRatingValue=@InheritedParentalRatingValue where Guid=@Guid";
- _updateInheritedRatingCommand.Parameters.Add(_updateInheritedRatingCommand, "@InheritedParentalRatingValue");
_updateInheritedRatingCommand.Parameters.Add(_updateInheritedRatingCommand, "@Guid");
+ _updateInheritedRatingCommand.Parameters.Add(_updateInheritedRatingCommand, "@InheritedParentalRatingValue");
+
+ _updateInheritedTagsCommand = _connection.CreateCommand();
+ _updateInheritedTagsCommand.CommandText = "Update TypedBaseItems set InheritedTags=@InheritedTags where Guid=@Guid";
+ _updateInheritedTagsCommand.Parameters.Add(_updateInheritedTagsCommand, "@Guid");
+ _updateInheritedTagsCommand.Parameters.Add(_updateInheritedTagsCommand, "@InheritedTags");
+
+ // user data
+ _deleteUserDataKeysCommand = _connection.CreateCommand();
+ _deleteUserDataKeysCommand.CommandText = "delete from UserDataKeys where ItemId=@Id";
+ _deleteUserDataKeysCommand.Parameters.Add(_deleteUserDataKeysCommand, "@Id");
+
+ _saveUserDataKeysCommand = _connection.CreateCommand();
+ _saveUserDataKeysCommand.CommandText = "insert into UserDataKeys (ItemId, UserDataKey, Priority) values (@ItemId, @UserDataKey, @Priority)";
+ _saveUserDataKeysCommand.Parameters.Add(_saveUserDataKeysCommand, "@ItemId");
+ _saveUserDataKeysCommand.Parameters.Add(_saveUserDataKeysCommand, "@UserDataKey");
+ _saveUserDataKeysCommand.Parameters.Add(_saveUserDataKeysCommand, "@Priority");
+
+ // item values
+ _deleteItemValuesCommand = _connection.CreateCommand();
+ _deleteItemValuesCommand.CommandText = "delete from ItemValues where ItemId=@Id";
+ _deleteItemValuesCommand.Parameters.Add(_deleteItemValuesCommand, "@Id");
+
+ _saveItemValuesCommand = _connection.CreateCommand();
+ _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>
@@ -696,7 +826,15 @@ namespace MediaBrowser.Server.Implementations.Persistence
_saveItemCommand.GetParameter(index++).Value = item.DateLastRefreshed;
}
- _saveItemCommand.GetParameter(index++).Value = item.DateLastSaved;
+ if (item.DateLastSaved == default(DateTime))
+ {
+ _saveItemCommand.GetParameter(index++).Value = null;
+ }
+ else
+ {
+ _saveItemCommand.GetParameter(index++).Value = item.DateLastSaved;
+ }
+
_saveItemCommand.GetParameter(index++).Value = item.IsInMixedFolder;
_saveItemCommand.GetParameter(index++).Value = string.Join("|", item.LockedFields.Select(i => i.ToString()).ToArray());
_saveItemCommand.GetParameter(index++).Value = string.Join("|", item.Studios.ToArray());
@@ -712,7 +850,15 @@ namespace MediaBrowser.Server.Implementations.Persistence
_saveItemCommand.GetParameter(index++).Value = item.ServiceName;
- _saveItemCommand.GetParameter(index++).Value = string.Join("|", item.Tags.ToArray());
+ if (item.Tags.Count > 0)
+ {
+ _saveItemCommand.GetParameter(index++).Value = string.Join("|", item.Tags.ToArray());
+ }
+ else
+ {
+ _saveItemCommand.GetParameter(index++).Value = null;
+ }
+
_saveItemCommand.GetParameter(index++).Value = item.IsFolder;
_saveItemCommand.GetParameter(index++).Value = item.GetBlockUnratedType().ToString();
@@ -741,7 +887,7 @@ namespace MediaBrowser.Server.Implementations.Persistence
_saveItemCommand.GetParameter(index++).Value = item.SourceType.ToString();
var trailer = item as Trailer;
- if (trailer != null)
+ if (trailer != null && trailer.TrailerTypes.Count > 0)
{
_saveItemCommand.GetParameter(index++).Value = string.Join("|", trailer.TrailerTypes.Select(i => i.ToString()).ToArray());
}
@@ -752,7 +898,89 @@ namespace MediaBrowser.Server.Implementations.Persistence
_saveItemCommand.GetParameter(index++).Value = item.CriticRating;
_saveItemCommand.GetParameter(index++).Value = item.CriticRatingSummary;
-
+
+ var inheritedTags = item.GetInheritedTags();
+ if (inheritedTags.Count > 0)
+ {
+ _saveItemCommand.GetParameter(index++).Value = string.Join("|", inheritedTags.ToArray());
+ }
+ else
+ {
+ _saveItemCommand.GetParameter(index++).Value = null;
+ }
+
+ if (string.IsNullOrWhiteSpace(item.Name))
+ {
+ _saveItemCommand.GetParameter(index++).Value = null;
+ }
+ else
+ {
+ _saveItemCommand.GetParameter(index++).Value = item.Name.RemoveDiacritics();
+ }
+
+ _saveItemCommand.GetParameter(index++).Value = item.PresentationUniqueKey;
+ _saveItemCommand.GetParameter(index++).Value = item.SlugName;
+ _saveItemCommand.GetParameter(index++).Value = item.OriginalTitle;
+
+ var video = item as Video;
+ if (video != null)
+ {
+ _saveItemCommand.GetParameter(index++).Value = video.PrimaryVersionId;
+ }
+ else
+ {
+ _saveItemCommand.GetParameter(index++).Value = null;
+ }
+
+ var folder = item as Folder;
+ if (folder != null && folder.DateLastMediaAdded.HasValue)
+ {
+ _saveItemCommand.GetParameter(index++).Value = folder.DateLastMediaAdded.Value;
+ }
+ else
+ {
+ _saveItemCommand.GetParameter(index++).Value = null;
+ }
+
+ _saveItemCommand.GetParameter(index++).Value = item.Album;
+
+ _saveItemCommand.GetParameter(index++).Value = item.IsVirtualItem || (!item.IsFolder && item.LocationType == LocationType.Virtual);
+
+ var hasSeries = item as IHasSeries;
+ if (hasSeries != null)
+ {
+ _saveItemCommand.GetParameter(index++).Value = hasSeries.FindSeriesName();
+ }
+ else
+ {
+ _saveItemCommand.GetParameter(index++).Value = null;
+ }
+
+ _saveItemCommand.GetParameter(index++).Value = item.GetUserDataKeys().FirstOrDefault();
+
+ var episode = item as Episode;
+ if (episode != null)
+ {
+ _saveItemCommand.GetParameter(index++).Value = episode.FindSeasonName();
+ _saveItemCommand.GetParameter(index++).Value = episode.FindSeasonId();
+ }
+ else
+ {
+ _saveItemCommand.GetParameter(index++).Value = null;
+ _saveItemCommand.GetParameter(index++).Value = null;
+ }
+
+ if (hasSeries != null)
+ {
+ _saveItemCommand.GetParameter(index++).Value = hasSeries.FindSeriesId();
+ _saveItemCommand.GetParameter(index++).Value = hasSeries.FindSeriesSortName();
+ }
+ else
+ {
+ _saveItemCommand.GetParameter(index++).Value = null;
+ _saveItemCommand.GetParameter(index++).Value = null;
+ }
+
_saveItemCommand.Transaction = transaction;
_saveItemCommand.ExecuteNonQuery();
@@ -761,6 +989,11 @@ namespace MediaBrowser.Server.Implementations.Persistence
{
UpdateAncestors(item.Id, item.GetAncestorIds().Distinct().ToList(), transaction);
}
+
+ UpdateUserDataKeys(item.Id, item.GetUserDataKeys().Distinct(StringComparer.OrdinalIgnoreCase).ToList(), transaction);
+ UpdateImages(item.Id, item.ImageInfos, transaction);
+ UpdateProviderIds(item.Id, item.ProviderIds, transaction);
+ UpdateItemValues(item.Id, GetItemValuesToSave(item), transaction);
}
transaction.Commit();
@@ -836,7 +1069,7 @@ namespace MediaBrowser.Server.Implementations.Persistence
if (type == null)
{
- Logger.Debug("Unknown type {0}", typeString);
+ //Logger.Debug("Unknown type {0}", typeString);
return null;
}
@@ -1125,6 +1358,102 @@ namespace MediaBrowser.Server.Implementations.Persistence
}
}
+ var index = 51;
+
+ if (!reader.IsDBNull(index))
+ {
+ item.OriginalTitle = reader.GetString(index);
+ }
+ index++;
+
+ var video = item as Video;
+ if (video != null)
+ {
+ if (!reader.IsDBNull(index))
+ {
+ video.PrimaryVersionId = reader.GetString(index);
+ }
+ }
+ index++;
+
+ var folder = item as Folder;
+ if (folder != null && !reader.IsDBNull(index))
+ {
+ folder.DateLastMediaAdded = reader.GetDateTime(index).ToUniversalTime();
+ }
+ index++;
+
+ if (!reader.IsDBNull(index))
+ {
+ item.Album = reader.GetString(index);
+ }
+ index++;
+
+ if (!reader.IsDBNull(index))
+ {
+ item.CriticRating = reader.GetFloat(index);
+ }
+ index++;
+
+ if (!reader.IsDBNull(index))
+ {
+ item.CriticRatingSummary = reader.GetString(index);
+ }
+ index++;
+
+ if (!reader.IsDBNull(index))
+ {
+ item.IsVirtualItem = reader.GetBoolean(index);
+ }
+ index++;
+
+ var hasSeries = item as IHasSeries;
+ if (hasSeries != null)
+ {
+ if (!reader.IsDBNull(index))
+ {
+ hasSeries.SeriesName = reader.GetString(index);
+ }
+ }
+ index++;
+
+ var episode = item as Episode;
+ if (episode != null)
+ {
+ if (!reader.IsDBNull(index))
+ {
+ episode.SeasonName = reader.GetString(index);
+ }
+ index++;
+ if (!reader.IsDBNull(index))
+ {
+ episode.SeasonId = reader.GetGuid(index);
+ }
+ }
+ else
+ {
+ index++;
+ }
+ index++;
+
+ if (hasSeries != null)
+ {
+ if (!reader.IsDBNull(index))
+ {
+ hasSeries.SeriesId = reader.GetGuid(index);
+ }
+ }
+ index++;
+
+ if (hasSeries != null)
+ {
+ if (!reader.IsDBNull(index))
+ {
+ hasSeries.SeriesSortName = reader.GetString(index);
+ }
+ }
+ index++;
+
return item;
}
@@ -1182,10 +1511,11 @@ namespace MediaBrowser.Server.Implementations.Persistence
{
throw new ArgumentNullException("id");
}
+ var list = new List<ChapterInfo>();
using (var cmd = _connection.CreateCommand())
{
- cmd.CommandText = "select StartPositionTicks,Name,ImagePath from " + ChaptersTableName + " where ItemId = @ItemId order by ChapterIndex asc";
+ cmd.CommandText = "select StartPositionTicks,Name,ImagePath,ImageDateModified from " + ChaptersTableName + " where ItemId = @ItemId order by ChapterIndex asc";
cmd.Parameters.Add(cmd, "@ItemId", DbType.Guid).Value = id;
@@ -1193,10 +1523,12 @@ namespace MediaBrowser.Server.Implementations.Persistence
{
while (reader.Read())
{
- yield return GetChapter(reader);
+ list.Add(GetChapter(reader));
}
}
}
+
+ return list;
}
/// <summary>
@@ -1216,7 +1548,7 @@ namespace MediaBrowser.Server.Implementations.Persistence
using (var cmd = _connection.CreateCommand())
{
- cmd.CommandText = "select StartPositionTicks,Name,ImagePath from " + ChaptersTableName + " where ItemId = @ItemId and ChapterIndex=@ChapterIndex";
+ cmd.CommandText = "select StartPositionTicks,Name,ImagePath,ImageDateModified from " + ChaptersTableName + " where ItemId = @ItemId and ChapterIndex=@ChapterIndex";
cmd.Parameters.Add(cmd, "@ItemId", DbType.Guid).Value = id;
cmd.Parameters.Add(cmd, "@ChapterIndex", DbType.Int32).Value = index;
@@ -1254,6 +1586,11 @@ namespace MediaBrowser.Server.Implementations.Persistence
chapter.ImagePath = reader.GetString(2);
}
+ if (!reader.IsDBNull(3))
+ {
+ chapter.ImageDateModified = reader.GetDateTime(3).ToUniversalTime();
+ }
+
return chapter;
}
@@ -1271,7 +1608,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();
@@ -1313,6 +1650,7 @@ namespace MediaBrowser.Server.Implementations.Persistence
_saveChapterCommand.GetParameter(2).Value = chapter.StartPositionTicks;
_saveChapterCommand.GetParameter(3).Value = chapter.Name;
_saveChapterCommand.GetParameter(4).Value = chapter.ImagePath;
+ _saveChapterCommand.GetParameter(5).Value = chapter.ImageDateModified;
_saveChapterCommand.Transaction = transaction;
@@ -1368,37 +1706,167 @@ namespace MediaBrowser.Server.Implementations.Persistence
}
}
- public IEnumerable<BaseItem> GetItemsOfType(Type type)
+ private bool EnableJoinUserData(InternalItemsQuery query)
{
- if (type == null)
+ if (query.User == null)
{
- throw new ArgumentNullException("type");
+ return false;
}
- CheckDisposed();
+ if (query.SimilarTo != null && query.User != null)
+ {
+ return true;
+ }
- using (var cmd = _connection.CreateCommand())
+ if (query.SortBy != null && query.SortBy.Length > 0)
+ {
+ if (query.SortBy.Contains(ItemSortBy.IsFavoriteOrLiked, StringComparer.OrdinalIgnoreCase))
+ {
+ return true;
+ }
+ if (query.SortBy.Contains(ItemSortBy.IsPlayed, StringComparer.OrdinalIgnoreCase))
+ {
+ return true;
+ }
+ if (query.SortBy.Contains(ItemSortBy.IsUnplayed, StringComparer.OrdinalIgnoreCase))
+ {
+ return true;
+ }
+ if (query.SortBy.Contains(ItemSortBy.PlayCount, StringComparer.OrdinalIgnoreCase))
+ {
+ return true;
+ }
+ if (query.SortBy.Contains(ItemSortBy.DatePlayed, StringComparer.OrdinalIgnoreCase))
+ {
+ return true;
+ }
+ }
+
+ if (query.IsFavoriteOrLiked.HasValue)
{
- cmd.CommandText = "select " + string.Join(",", _retriveItemColumns) + " from TypedBaseItems where type = @type";
+ return true;
+ }
- cmd.Parameters.Add(cmd, "@type", DbType.String).Value = type.FullName;
+ if (query.IsFavorite.HasValue)
+ {
+ return true;
+ }
- using (var reader = cmd.ExecuteReader(CommandBehavior.SequentialAccess | CommandBehavior.SingleResult))
- {
- while (reader.Read())
- {
- var item = GetItem(reader);
+ if (query.IsResumable.HasValue)
+ {
+ return true;
+ }
- if (item != null)
- {
- yield return item;
- }
- }
- }
+ if (query.IsPlayed.HasValue)
+ {
+ return true;
+ }
+
+ if (query.IsLiked.HasValue)
+ {
+ return true;
}
+
+ return false;
}
- public IEnumerable<BaseItem> GetItemList(InternalItemsQuery query)
+ private string[] GetFinalColumnsToSelect(InternalItemsQuery query, string[] startColumns, IDbCommand cmd)
+ {
+ var list = startColumns.ToList();
+
+ if (EnableJoinUserData(query))
+ {
+ list.Add("UserDataDb.UserData.UserId");
+ list.Add("UserDataDb.UserData.lastPlayedDate");
+ list.Add("UserDataDb.UserData.playbackPositionTicks");
+ list.Add("UserDataDb.UserData.playcount");
+ list.Add("UserDataDb.UserData.isFavorite");
+ list.Add("UserDataDb.UserData.played");
+ 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();
+ }
+
+ private string GetJoinUserDataText(InternalItemsQuery query)
+ {
+ if (!EnableJoinUserData(query))
+ {
+ return string.Empty;
+ }
+
+ 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)";
+ }
+
+ 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)
{
@@ -1407,11 +1875,27 @@ namespace MediaBrowser.Server.Implementations.Persistence
CheckDisposed();
+ 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(",", _retriveItemColumns) + " from TypedBaseItems";
+ cmd.CommandText = "select " + string.Join(",", GetFinalColumnsToSelect(query, _retriveItemColumns, cmd)) + GetFromText();
+ cmd.CommandText += GetJoinUserDataText(query);
+
+ if (EnableJoinUserData(query))
+ {
+ cmd.Parameters.Add(cmd, "@UserId", DbType.Guid).Value = query.User.Id;
+ }
- var whereClauses = GetWhereClauses(query, cmd, true);
+ var whereClauses = GetWhereClauses(query, cmd);
var whereText = whereClauses.Count == 0 ?
string.Empty :
@@ -1419,27 +1903,115 @@ namespace MediaBrowser.Server.Implementations.Persistence
cmd.CommandText += whereText;
+ cmd.CommandText += GetGroupBy(query);
+
cmd.CommandText += GetOrderByText(query);
- if (query.Limit.HasValue)
+ if (query.Limit.HasValue || query.StartIndex.HasValue)
{
- cmd.CommandText += " LIMIT " + query.Limit.Value.ToString(CultureInfo.InvariantCulture);
- }
+ var offset = query.StartIndex ?? 0;
- //Logger.Debug(cmd.CommandText);
+ 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);
+ }
+ }
using (var reader = cmd.ExecuteReader(CommandBehavior.SequentialAccess | CommandBehavior.SingleResult))
{
+ LogQueryTime("GetItemList", cmd, now);
+
while (reader.Read())
{
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)
+ {
+ var elapsed = (DateTime.UtcNow - startDate).TotalMilliseconds;
+
+ var slowThreshold = 1000;
+
+#if DEBUG
+ slowThreshold = 50;
+#endif
+
+ if (elapsed >= slowThreshold)
+ {
+ Logger.Debug("{2} query time (slow): {0}ms. Query: {1}",
+ Convert.ToInt32(elapsed),
+ cmd.CommandText,
+ methodName);
+ }
+ else
+ {
+ //Logger.Debug("{2} query time: {0}ms. Query: {1}",
+ // Convert.ToInt32(elapsed),
+ // cmd.CommandText,
+ // methodName);
+ }
}
public QueryResult<BaseItem> GetItems(InternalItemsQuery query)
@@ -1451,52 +2023,109 @@ 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(",", _retriveItemColumns) + " from TypedBaseItems";
+ cmd.CommandText = "select " + string.Join(",", GetFinalColumnsToSelect(query, _retriveItemColumns, cmd)) + GetFromText();
+ cmd.CommandText += GetJoinUserDataText(query);
- var whereClauses = GetWhereClauses(query, cmd, false);
+ if (EnableJoinUserData(query))
+ {
+ cmd.Parameters.Add(cmd, "@UserId", DbType.Guid).Value = query.User.Id;
+ }
+
+ var whereClauses = GetWhereClauses(query, cmd);
var whereTextWithoutPaging = whereClauses.Count == 0 ?
string.Empty :
" where " + string.Join(" AND ", whereClauses.ToArray());
- whereClauses = GetWhereClauses(query, cmd, true);
-
var whereText = whereClauses.Count == 0 ?
string.Empty :
" where " + string.Join(" AND ", whereClauses.ToArray());
cmd.CommandText += whereText;
+ cmd.CommandText += GetGroupBy(query);
+
cmd.CommandText += GetOrderByText(query);
- if (query.Limit.HasValue)
+ if (query.Limit.HasValue || query.StartIndex.HasValue)
{
- cmd.CommandText += " LIMIT " + query.Limit.Value.ToString(CultureInfo.InvariantCulture);
+ 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 += "; select count (guid) from TypedBaseItems" + whereTextWithoutPaging;
+ cmd.CommandText += ";";
- //Logger.Debug(cmd.CommandText);
+ var isReturningZeroItems = query.Limit.HasValue && query.Limit <= 0;
+
+ if (isReturningZeroItems)
+ {
+ cmd.CommandText = "";
+ }
+
+ if (EnableGroupByPresentationUniqueKey(query))
+ {
+ cmd.CommandText += " select count (distinct PresentationUniqueKey)" + GetFromText();
+ }
+ else
+ {
+ cmd.CommandText += " select count (guid)" + GetFromText();
+ }
+
+ cmd.CommandText += GetJoinUserDataText(query);
+ cmd.CommandText += whereTextWithoutPaging;
var list = new List<BaseItem>();
var count = 0;
using (var reader = cmd.ExecuteReader(CommandBehavior.SequentialAccess))
{
- while (reader.Read())
+ LogQueryTime("GetItems", cmd, now);
+
+ 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);
+ }
}
}
@@ -1510,33 +2139,109 @@ 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[] { ItemSortBy.IsPlayed, "SimilarityScore", ItemSortBy.Random };
+ }
+ else
+ {
+ query.SortBy = new[] { "SimilarityScore", ItemSortBy.Random };
+ }
+ query.SortOrder = SortOrder.Descending;
+ }
+ }
+
if (query.SortBy == null || query.SortBy.Length == 0)
{
return string.Empty;
}
- var sortOrder = query.SortOrder == SortOrder.Descending ? "DESC" : "ASC";
+ var isAscending = query.SortOrder != SortOrder.Descending;
- return " ORDER BY " + string.Join(",", query.SortBy.Select(i => MapOrderByField(i) + " " + sortOrder).ToArray());
+ return " ORDER BY " + string.Join(",", query.SortBy.Select(i =>
+ {
+ var columnMap = MapOrderByField(i, query);
+ var columnAscending = isAscending;
+ if (columnMap.Item2)
+ {
+ columnAscending = !columnAscending;
+ }
+
+ var sortOrder = columnAscending ? "ASC" : "DESC";
+
+ return columnMap.Item1 + " " + sortOrder;
+ }).ToArray());
}
- private string MapOrderByField(string name)
+ private Tuple<string, bool> MapOrderByField(string name, InternalItemsQuery query)
{
if (string.Equals(name, ItemSortBy.AirTime, StringComparison.OrdinalIgnoreCase))
{
// TODO
- return "SortName";
+ return new Tuple<string, bool>("SortName", false);
}
if (string.Equals(name, ItemSortBy.Runtime, StringComparison.OrdinalIgnoreCase))
{
- return "RuntimeTicks";
+ return new Tuple<string, bool>("RuntimeTicks", false);
}
if (string.Equals(name, ItemSortBy.Random, StringComparison.OrdinalIgnoreCase))
{
- return "RANDOM()";
+ 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 (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);
+ }
+ 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 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 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 name;
+ return new Tuple<string, bool>(name, false);
}
public List<Guid> GetItemIdsList(InternalItemsQuery query)
@@ -1548,11 +2253,19 @@ namespace MediaBrowser.Server.Implementations.Persistence
CheckDisposed();
+ var now = DateTime.UtcNow;
+
using (var cmd = _connection.CreateCommand())
{
- cmd.CommandText = "select guid from TypedBaseItems";
+ cmd.CommandText = "select " + string.Join(",", GetFinalColumnsToSelect(query, new[] { "guid" }, cmd)) + GetFromText();
+ cmd.CommandText += GetJoinUserDataText(query);
+
+ if (EnableJoinUserData(query))
+ {
+ cmd.Parameters.Add(cmd, "@UserId", DbType.Guid).Value = query.User.Id;
+ }
- var whereClauses = GetWhereClauses(query, cmd, true);
+ var whereClauses = GetWhereClauses(query, cmd);
var whereText = whereClauses.Count == 0 ?
string.Empty :
@@ -1560,19 +2273,31 @@ namespace MediaBrowser.Server.Implementations.Persistence
cmd.CommandText += whereText;
+ cmd.CommandText += GetGroupBy(query);
+
cmd.CommandText += GetOrderByText(query);
- if (query.Limit.HasValue)
+ if (query.Limit.HasValue || query.StartIndex.HasValue)
{
- cmd.CommandText += " LIMIT " + query.Limit.Value.ToString(CultureInfo.InvariantCulture);
+ 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);
+ }
}
var list = new List<Guid>();
- //Logger.Debug(cmd.CommandText);
-
using (var reader = cmd.ExecuteReader(CommandBehavior.SequentialAccess | CommandBehavior.SingleResult))
{
+ LogQueryTime("GetItemIdsList", cmd, now);
+
while (reader.Read())
{
list.Add(reader.GetGuid(0));
@@ -1596,25 +2321,35 @@ namespace MediaBrowser.Server.Implementations.Persistence
{
cmd.CommandText = "select guid,path from TypedBaseItems";
- var whereClauses = GetWhereClauses(query, cmd, false);
+ var whereClauses = GetWhereClauses(query, cmd);
var whereTextWithoutPaging = whereClauses.Count == 0 ?
string.Empty :
" where " + string.Join(" AND ", whereClauses.ToArray());
- whereClauses = GetWhereClauses(query, cmd, true);
-
var whereText = whereClauses.Count == 0 ?
string.Empty :
" where " + string.Join(" AND ", whereClauses.ToArray());
cmd.CommandText += whereText;
+ cmd.CommandText += GetGroupBy(query);
+
cmd.CommandText += GetOrderByText(query);
- if (query.Limit.HasValue)
+ if (query.Limit.HasValue || query.StartIndex.HasValue)
{
- cmd.CommandText += " LIMIT " + query.Limit.Value.ToString(CultureInfo.InvariantCulture);
+ 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 += "; select count (guid) from TypedBaseItems" + whereTextWithoutPaging;
@@ -1661,17 +2396,29 @@ namespace MediaBrowser.Server.Implementations.Persistence
CheckDisposed();
- using (var cmd = _connection.CreateCommand())
+ if (!query.EnableTotalRecordCount || (!query.Limit.HasValue && (query.StartIndex ?? 0) == 0))
{
- cmd.CommandText = "select guid from TypedBaseItems";
+ var list = GetItemIdsList(query);
+ return new QueryResult<Guid>
+ {
+ Items = list.ToArray(),
+ TotalRecordCount = list.Count
+ };
+ }
- var whereClauses = GetWhereClauses(query, cmd, false);
+ var now = DateTime.UtcNow;
- var whereTextWithoutPaging = whereClauses.Count == 0 ?
- string.Empty :
- " where " + string.Join(" AND ", whereClauses.ToArray());
+ using (var cmd = _connection.CreateCommand())
+ {
+ cmd.CommandText = "select " + string.Join(",", GetFinalColumnsToSelect(query, new[] { "guid" }, cmd)) + GetFromText();
+
+ var whereClauses = GetWhereClauses(query, cmd);
+ cmd.CommandText += GetJoinUserDataText(query);
- whereClauses = GetWhereClauses(query, cmd, true);
+ if (EnableJoinUserData(query))
+ {
+ cmd.Parameters.Add(cmd, "@UserId", DbType.Guid).Value = query.User.Id;
+ }
var whereText = whereClauses.Count == 0 ?
string.Empty :
@@ -1679,22 +2426,44 @@ namespace MediaBrowser.Server.Implementations.Persistence
cmd.CommandText += whereText;
+ cmd.CommandText += GetGroupBy(query);
+
cmd.CommandText += GetOrderByText(query);
- if (query.Limit.HasValue)
+ if (query.Limit.HasValue || query.StartIndex.HasValue)
{
- cmd.CommandText += " LIMIT " + query.Limit.Value.ToString(CultureInfo.InvariantCulture);
+ 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 += "; select count (guid) from TypedBaseItems" + whereTextWithoutPaging;
+ if (EnableGroupByPresentationUniqueKey(query))
+ {
+ cmd.CommandText += "; select count (distinct PresentationUniqueKey)" + GetFromText();
+ }
+ else
+ {
+ cmd.CommandText += "; select count (guid)" + GetFromText();
+ }
+
+ cmd.CommandText += GetJoinUserDataText(query);
+ cmd.CommandText += whereText;
var list = new List<Guid>();
var count = 0;
- //Logger.Debug(cmd.CommandText);
-
using (var reader = cmd.ExecuteReader(CommandBehavior.SequentialAccess))
{
+ LogQueryTime("GetItemIds", cmd, now);
+
while (reader.Read())
{
list.Add(reader.GetGuid(0));
@@ -1714,10 +2483,14 @@ namespace MediaBrowser.Server.Implementations.Persistence
}
}
- private List<string> GetWhereClauses(InternalItemsQuery query, IDbCommand cmd, bool addPaging)
+ 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)");
+ }
if (query.IsCurrentSchema.HasValue)
{
if (query.IsCurrentSchema.Value)
@@ -1747,7 +2520,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)
@@ -1769,8 +2559,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)
{
@@ -1813,6 +2603,12 @@ namespace MediaBrowser.Server.Implementations.Persistence
cmd.Parameters.Add(cmd, "@Path", DbType.String).Value = query.Path;
}
+ if (!string.IsNullOrWhiteSpace(query.PresentationUniqueKey))
+ {
+ whereClauses.Add("PresentationUniqueKey=@PresentationUniqueKey");
+ cmd.Parameters.Add(cmd, "@PresentationUniqueKey", DbType.String).Value = query.PresentationUniqueKey;
+ }
+
if (query.MinCommunityRating.HasValue)
{
whereClauses.Add("CommunityRating>=@MinCommunityRating");
@@ -1837,11 +2633,21 @@ namespace MediaBrowser.Server.Implementations.Persistence
// cmd.Parameters.Add(cmd, "@MaxPlayers", DbType.Int32).Value = query.MaxPlayers.Value;
//}
+ if (query.IndexNumber.HasValue)
+ {
+ whereClauses.Add("IndexNumber=@IndexNumber");
+ cmd.Parameters.Add(cmd, "@IndexNumber", DbType.Int32).Value = query.IndexNumber.Value;
+ }
if (query.ParentIndexNumber.HasValue)
{
- whereClauses.Add("ParentIndexNumber=@MinEndDate");
+ 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");
@@ -1913,20 +2719,6 @@ namespace MediaBrowser.Server.Implementations.Persistence
whereClauses.Add(clause);
}
- if (query.ExcludeTrailerTypes.Length > 0)
- {
- var clauses = new List<string>();
- var index = 0;
- foreach (var type in query.ExcludeTrailerTypes)
- {
- clauses.Add("TrailerTypes not like @TrailerTypes" + index);
- cmd.Parameters.Add(cmd, "@TrailerTypes" + index, DbType.String).Value = "%" + type + "%";
- index++;
- }
- var clause = "(" + string.Join(" AND ", clauses.ToArray()) + ")";
- whereClauses.Add(clause);
- }
-
if (query.IsAiring.HasValue)
{
if (query.IsAiring.Value)
@@ -1944,16 +2736,176 @@ 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)");
cmd.Parameters.Add(cmd, "@PersonName", DbType.String).Value = query.Person;
}
+ if (!string.IsNullOrWhiteSpace(query.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))
+ {
+ whereClauses.Add("CleanName=@Name");
+ cmd.Parameters.Add(cmd, "@Name", DbType.String).Value = query.Name.RemoveDiacritics();
+ }
+
if (!string.IsNullOrWhiteSpace(query.NameContains))
{
- whereClauses.Add("Name like @NameContains");
- cmd.Parameters.Add(cmd, "@NameContains", DbType.String).Value = "%" + query.NameContains + "%";
+ whereClauses.Add("CleanName like @NameContains");
+ cmd.Parameters.Add(cmd, "@NameContains", DbType.String).Value = "%" + query.NameContains.RemoveDiacritics() + "%";
+ }
+ if (!string.IsNullOrWhiteSpace(query.NameStartsWith))
+ {
+ whereClauses.Add("SortName like @NameStartsWith");
+ cmd.Parameters.Add(cmd, "@NameStartsWith", DbType.String).Value = query.NameStartsWith + "%";
+ }
+ if (!string.IsNullOrWhiteSpace(query.NameStartsWithOrGreater))
+ {
+ whereClauses.Add("SortName >= @NameStartsWithOrGreater");
+ // lowercase this because SortName is stored as lowercase
+ cmd.Parameters.Add(cmd, "@NameStartsWithOrGreater", DbType.String).Value = query.NameStartsWithOrGreater.ToLower();
+ }
+ if (!string.IsNullOrWhiteSpace(query.NameLessThan))
+ {
+ whereClauses.Add("SortName < @NameLessThan");
+ // lowercase this because SortName is stored as lowercase
+ cmd.Parameters.Add(cmd, "@NameLessThan", DbType.String).Value = query.NameLessThan.ToLower();
+ }
+
+ if (query.ImageTypes.Length > 0 && _config.Configuration.SchemaVersion >= 87)
+ {
+ var requiredImageIndex = 0;
+
+ foreach (var requiredImage in query.ImageTypes)
+ {
+ 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.IsLiked.HasValue)
+ {
+ 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;
+ }
+ }
+
+ if (query.IsFavoriteOrLiked.HasValue)
+ {
+ 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;
+ }
+
+ 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)
+ {
+ whereClauses.Add("(played=@IsPlayed)");
+ }
+ else
+ {
+ whereClauses.Add("(played is null or played=@IsPlayed)");
+ }
+ cmd.Parameters.Add(cmd, "@IsPlayed", DbType.Boolean).Value = query.IsPlayed.Value;
+ }
+ }
+
+ if (query.IsResumable.HasValue)
+ {
+ if (query.IsResumable.Value)
+ {
+ whereClauses.Add("playbackPositionTicks > 0");
+ }
+ else
+ {
+ whereClauses.Add("(playbackPositionTicks is null or playbackPositionTicks = 0)");
+ }
+ }
+
+ if (query.ArtistNames.Length > 0)
+ {
+ var clauses = new List<string>();
+ var index = 0;
+ foreach (var artist in query.ArtistNames)
+ {
+ 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.ExcludeArtistIds.Length > 0)
+ {
+ var clauses = new List<string>();
+ var index = 0;
+ foreach (var artistId in query.ExcludeArtistIds)
+ {
+ var artistItem = RetrieveItem(new Guid(artistId));
+ if (artistItem != null)
+ {
+ clauses.Add("@ExcludeArtistName" + index + " not in (select CleanValue from itemvalues where ItemId=Guid and Type <= 1)");
+ cmd.Parameters.Add(cmd, "@ExcludeArtistName" + index, DbType.String).Value = artistItem.Name.RemoveDiacritics();
+ index++;
+ }
+ }
+ var clause = "(" + string.Join(" AND ", 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)
@@ -1962,8 +2914,8 @@ namespace MediaBrowser.Server.Implementations.Persistence
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()) + ")";
@@ -1976,22 +2928,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()) + ")";
@@ -2056,8 +3042,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)
{
@@ -2067,8 +3060,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)
{
@@ -2076,10 +3076,55 @@ namespace MediaBrowser.Server.Implementations.Persistence
whereClauses.Add("LocationType not in (" + val + ")");
}
+ if (query.IsVirtualItem.HasValue)
+ {
+ if (_config.Configuration.SchemaVersion >= 90)
+ {
+ whereClauses.Add("IsVirtualItem=@IsVirtualItem");
+ cmd.Parameters.Add(cmd, "@IsVirtualItem", DbType.Boolean).Value = query.IsVirtualItem.Value;
+ }
+ else if (!query.IsVirtualItem.Value)
+ {
+ whereClauses.Add("LocationType<>'Virtual'");
+ }
+ }
+ if (query.IsUnaired.HasValue)
+ {
+ if (query.IsUnaired.Value)
+ {
+ whereClauses.Add("PremiereDate >= DATETIME('now')");
+ }
+ else
+ {
+ whereClauses.Add("PremiereDate < DATETIME('now')");
+ }
+ }
+ if (query.IsMissing.HasValue && _config.Configuration.SchemaVersion >= 90)
+ {
+ if (query.IsMissing.Value)
+ {
+ whereClauses.Add("(IsVirtualItem=1 AND PremiereDate < DATETIME('now'))");
+ }
+ else
+ {
+ whereClauses.Add("(IsVirtualItem=0 OR PremiereDate >= DATETIME('now'))");
+ }
+ }
+ if (query.IsVirtualUnaired.HasValue && _config.Configuration.SchemaVersion >= 90)
+ {
+ if (query.IsVirtualUnaired.Value)
+ {
+ whereClauses.Add("(IsVirtualItem=1 AND PremiereDate >= DATETIME('now'))");
+ }
+ else
+ {
+ whereClauses.Add("(IsVirtualItem=0 OR PremiereDate < DATETIME('now'))");
+ }
+ }
if (query.MediaTypes.Length == 1)
{
whereClauses.Add("MediaType=@MediaTypes");
- cmd.Parameters.Add(cmd, "@MediaTypes", DbType.String).Value = query.MediaTypes[0].ToString();
+ cmd.Parameters.Add(cmd, "@MediaTypes", DbType.String).Value = query.MediaTypes[0];
}
if (query.MediaTypes.Length > 1)
{
@@ -2087,8 +3132,96 @@ namespace MediaBrowser.Server.Implementations.Persistence
whereClauses.Add("MediaType in (" + val + ")");
}
+ if (query.ItemIds.Length > 0)
+ {
+ var excludeIds = new List<string>();
- var enableItemsByName = query.IncludeItemsByName ?? query.IncludeItemTypes.Length > 0;
+ 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)
+ {
+ var clause = "(";
+
+ var index = 0;
+ foreach (var name in query.AlbumNames)
+ {
+ if (index > 0)
+ {
+ clause += " OR ";
+ }
+ clause += "Album=@AlbumName" + index;
+ cmd.Parameters.Add(cmd, "@AlbumName" + index, DbType.String).Value = name;
+ index++;
+ }
+
+ clause += ")";
+ whereClauses.Add(clause);
+ }
+
+ //var enableItemsByName = query.IncludeItemsByName ?? query.IncludeItemTypes.Length > 0;
+ var enableItemsByName = query.IncludeItemsByName ?? false;
if (query.TopParentIds.Length == 1)
{
@@ -2128,6 +3261,12 @@ namespace MediaBrowser.Server.Implementations.Persistence
var inClause = string.Join(",", query.AncestorIds.Select(i => "'" + new Guid(i).ToString("N") + "'").ToArray());
whereClauses.Add(string.Format("Guid in (select itemId from AncestorIds where AncestorIdText in ({0}))", inClause));
}
+ if (!string.IsNullOrWhiteSpace(query.AncestorWithPresentationUniqueKey))
+ {
+ var inClause = "select guid from TypedBaseItems where PresentationUniqueKey=@AncestorWithPresentationUniqueKey";
+ whereClauses.Add(string.Format("Guid in (select itemId from AncestorIds where AncestorId in ({0}))", inClause));
+ cmd.Parameters.Add(cmd, "@AncestorWithPresentationUniqueKey", DbType.String).Value = query.AncestorWithPresentationUniqueKey;
+ }
if (query.BlockUnratedItems.Length == 1)
{
@@ -2143,28 +3282,58 @@ namespace MediaBrowser.Server.Implementations.Persistence
var excludeTagIndex = 0;
foreach (var excludeTag in query.ExcludeTags)
{
- whereClauses.Add("Tags not like @excludeTag" + excludeTagIndex);
+ whereClauses.Add("(Tags is null OR Tags not like @excludeTag" + excludeTagIndex + ")");
cmd.Parameters.Add(cmd, "@excludeTag" + excludeTagIndex, DbType.String).Value = "%" + excludeTag + "%";
excludeTagIndex++;
}
- if (addPaging)
+ excludeTagIndex = 0;
+ foreach (var excludeTag in query.ExcludeInheritedTags)
{
- if (query.StartIndex.HasValue && query.StartIndex.Value > 0)
- {
- var pagingWhereText = whereClauses.Count == 0 ?
- string.Empty :
- " where " + string.Join(" AND ", whereClauses.ToArray());
+ whereClauses.Add("(InheritedTags is null OR InheritedTags not like @excludeInheritedTag" + excludeTagIndex + ")");
+ cmd.Parameters.Add(cmd, "@excludeInheritedTag" + excludeTagIndex, DbType.String).Value = "%" + excludeTag + "%";
+ excludeTagIndex++;
+ }
- var orderBy = GetOrderByText(query);
+ return whereClauses;
+ }
- whereClauses.Add(string.Format("guid NOT IN (SELECT guid FROM TypedBaseItems {0}" + orderBy + " LIMIT {1})",
- pagingWhereText,
- query.StartIndex.Value.ToString(CultureInfo.InvariantCulture)));
- }
+ private bool EnableGroupByPresentationUniqueKey(InternalItemsQuery query)
+ {
+ if (!query.GroupByPresentationUniqueKey)
+ {
+ return false;
}
- return whereClauses;
+ if (!string.IsNullOrWhiteSpace(query.PresentationUniqueKey))
+ {
+ return false;
+ }
+
+ if (query.User == null)
+ {
+ return false;
+ }
+
+ if (query.IncludeItemTypes.Length == 0)
+ {
+ return true;
+ }
+
+ var types = new[] {
+ typeof(Episode).Name,
+ typeof(Video).Name ,
+ typeof(Movie).Name ,
+ typeof(MusicVideo).Name ,
+ typeof(Series).Name ,
+ typeof(Season).Name };
+
+ if (types.Any(i => query.IncludeItemTypes.Contains(i, StringComparer.OrdinalIgnoreCase)))
+ {
+ return true;
+ }
+
+ return false;
}
private static readonly Type[] KnownTypes =
@@ -2208,6 +3377,88 @@ namespace MediaBrowser.Server.Implementations.Persistence
public async Task UpdateInheritedValues(CancellationToken cancellationToken)
{
+ await UpdateInheritedParentalRating(cancellationToken).ConfigureAwait(false);
+ await UpdateInheritedTags(cancellationToken).ConfigureAwait(false);
+ }
+
+ private async Task UpdateInheritedTags(CancellationToken cancellationToken)
+ {
+ var newValues = new List<Tuple<Guid, string>>();
+
+ using (var cmd = _connection.CreateCommand())
+ {
+ cmd.CommandText = "select Guid,InheritedTags,(select group_concat(Tags, '|') from TypedBaseItems where (guid=outer.guid) OR (guid in (Select AncestorId from AncestorIds where ItemId=Outer.guid))) as NewInheritedTags from typedbaseitems as Outer where NewInheritedTags <> InheritedTags";
+
+ using (var reader = cmd.ExecuteReader(CommandBehavior.SequentialAccess | CommandBehavior.SingleResult))
+ {
+ while (reader.Read())
+ {
+ var id = reader.GetGuid(0);
+ string value = reader.IsDBNull(2) ? null : reader.GetString(2);
+
+ newValues.Add(new Tuple<Guid, string>(id, value));
+ }
+ }
+ }
+
+ Logger.Debug("UpdateInheritedTags - {0} rows", newValues.Count);
+ if (newValues.Count == 0)
+ {
+ return;
+ }
+
+ await WriteLock.WaitAsync(cancellationToken).ConfigureAwait(false);
+
+ IDbTransaction transaction = null;
+
+ try
+ {
+ transaction = _connection.BeginTransaction();
+
+ foreach (var item in newValues)
+ {
+ _updateInheritedTagsCommand.GetParameter(0).Value = item.Item1;
+ _updateInheritedTagsCommand.GetParameter(1).Value = item.Item2;
+
+ _updateInheritedTagsCommand.Transaction = transaction;
+ _updateInheritedTagsCommand.ExecuteNonQuery();
+ }
+
+ transaction.Commit();
+ }
+ catch (OperationCanceledException)
+ {
+ if (transaction != null)
+ {
+ transaction.Rollback();
+ }
+
+ throw;
+ }
+ catch (Exception e)
+ {
+ Logger.ErrorException("Error running query:", e);
+
+ if (transaction != null)
+ {
+ transaction.Rollback();
+ }
+
+ throw;
+ }
+ finally
+ {
+ if (transaction != null)
+ {
+ transaction.Dispose();
+ }
+
+ WriteLock.Release();
+ }
+ }
+
+ private async Task UpdateInheritedParentalRating(CancellationToken cancellationToken)
+ {
var newValues = new List<Tuple<Guid, int>>();
using (var cmd = _connection.CreateCommand())
@@ -2226,6 +3477,7 @@ namespace MediaBrowser.Server.Implementations.Persistence
}
}
+ Logger.Debug("UpdateInheritedParentalRatings - {0} rows", newValues.Count);
if (newValues.Count == 0)
{
return;
@@ -2283,7 +3535,7 @@ namespace MediaBrowser.Server.Implementations.Persistence
private static Dictionary<string, string[]> GetTypeMapDictionary()
{
- var dict = new Dictionary<string, string[]>();
+ var dict = new Dictionary<string, string[]>(StringComparer.OrdinalIgnoreCase);
foreach (var t in KnownTypes)
{
@@ -2348,6 +3600,26 @@ namespace MediaBrowser.Server.Implementations.Persistence
_deleteAncestorsCommand.Transaction = transaction;
_deleteAncestorsCommand.ExecuteNonQuery();
+ // Delete user data keys
+ _deleteUserDataKeysCommand.GetParameter(0).Value = id;
+ _deleteUserDataKeysCommand.Transaction = transaction;
+ _deleteUserDataKeysCommand.ExecuteNonQuery();
+
+ // Delete item values
+ _deleteItemValuesCommand.GetParameter(0).Value = id;
+ _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;
@@ -2540,6 +3812,490 @@ namespace MediaBrowser.Server.Implementations.Persistence
}
}
+ 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>>();
+
+ var hasArtist = item as IHasArtist;
+ if (hasArtist != null)
+ {
+ list.AddRange(hasArtist.Artists.Select(i => new Tuple<int, string>(0, i)));
+ }
+
+ var hasAlbumArtist = item as IHasAlbumArtist;
+ if (hasAlbumArtist != null)
+ {
+ 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");
+ }
+
+ // Just in case there might be case-insensitive duplicates, strip them out now
+ var newValues = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
+ foreach (var pair in values)
+ {
+ newValues[pair.Key] = pair.Value;
+ }
+
+ CheckDisposed();
+
+ // First delete
+ _deleteProviderIdsCommand.GetParameter(0).Value = itemId;
+ _deleteProviderIdsCommand.Transaction = transaction;
+
+ _deleteProviderIdsCommand.ExecuteNonQuery();
+
+ foreach (var pair in newValues)
+ {
+ _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)
+ {
+ throw new ArgumentNullException("itemId");
+ }
+
+ if (values == null)
+ {
+ throw new ArgumentNullException("keys");
+ }
+
+ CheckDisposed();
+
+ // First delete
+ _deleteItemValuesCommand.GetParameter(0).Value = itemId;
+ _deleteItemValuesCommand.Transaction = transaction;
+
+ _deleteItemValuesCommand.ExecuteNonQuery();
+
+ foreach (var pair in values)
+ {
+ _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();
+ }
+ }
+
+ private void UpdateUserDataKeys(Guid itemId, List<string> keys, IDbTransaction transaction)
+ {
+ if (itemId == Guid.Empty)
+ {
+ throw new ArgumentNullException("itemId");
+ }
+
+ if (keys == null)
+ {
+ throw new ArgumentNullException("keys");
+ }
+
+ CheckDisposed();
+
+ // First delete
+ _deleteUserDataKeysCommand.GetParameter(0).Value = itemId;
+ _deleteUserDataKeysCommand.Transaction = transaction;
+
+ _deleteUserDataKeysCommand.ExecuteNonQuery();
+ var index = 0;
+
+ foreach (var key in keys)
+ {
+ _saveUserDataKeysCommand.GetParameter(0).Value = itemId;
+ _saveUserDataKeysCommand.GetParameter(1).Value = key;
+ _saveUserDataKeysCommand.GetParameter(2).Value = index;
+ index++;
+ _saveUserDataKeysCommand.Transaction = transaction;
+
+ _saveUserDataKeysCommand.ExecuteNonQuery();
+ }
+ }
+
public async Task UpdatePeople(Guid itemId, List<PersonInfo> people)
{
if (itemId == Guid.Empty)
@@ -2656,6 +4412,8 @@ namespace MediaBrowser.Server.Implementations.Persistence
throw new ArgumentNullException("query");
}
+ var list = new List<MediaStream>();
+
using (var cmd = _connection.CreateCommand())
{
var cmdText = "select " + string.Join(",", _mediaStreamSaveColumns) + " from mediastreams where";
@@ -2683,13 +4441,15 @@ namespace MediaBrowser.Server.Implementations.Persistence
{
while (reader.Read())
{
- yield return GetMediaStream(reader);
+ list.Add(GetMediaStream(reader));
}
}
}
+
+ 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();
@@ -2755,10 +4515,15 @@ namespace MediaBrowser.Server.Implementations.Persistence
_saveStreamCommand.GetParameter(index++).Value = stream.BitDepth;
_saveStreamCommand.GetParameter(index++).Value = stream.IsAnamorphic;
_saveStreamCommand.GetParameter(index++).Value = stream.RefFrames;
- _saveStreamCommand.GetParameter(index++).Value = stream.IsCabac;
_saveStreamCommand.GetParameter(index++).Value = stream.CodecTag;
_saveStreamCommand.GetParameter(index++).Value = stream.Comment;
+ _saveStreamCommand.GetParameter(index++).Value = stream.NalLengthSize;
+ _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();
@@ -2909,17 +4674,37 @@ namespace MediaBrowser.Server.Implementations.Persistence
if (!reader.IsDBNull(25))
{
- item.IsCabac = reader.GetBoolean(25);
+ item.CodecTag = reader.GetString(25);
}
if (!reader.IsDBNull(26))
{
- item.CodecTag = reader.GetString(26);
+ item.Comment = reader.GetString(26);
}
if (!reader.IsDBNull(27))
{
- item.Comment = reader.GetString(27);
+ item.NalLengthSize = reader.GetString(27);
+ }
+
+ if (!reader.IsDBNull(28))
+ {
+ item.IsAVC = reader.GetBoolean(28);
+ }
+
+ if (!reader.IsDBNull(29))
+ {
+ 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 dbceda727..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()
- {
- var dbFile = Path.Combine(_appPaths.DataPath, "refreshinfo.db");
-
- _connection = await SqliteExtensions.ConnectToDb(dbFile, Logger).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 63c41c71f..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,21 +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()
+ public async Task Initialize(IDbConnection connection, SemaphoreSlim writeLock)
{
- var dbFile = Path.Combine(_appPaths.DataPath, "userdata_v2.db");
-
- _connection = await SqliteExtensions.ConnectToDb(dbFile, Logger).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 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",
@@ -295,11 +322,54 @@ namespace MediaBrowser.Server.Implementations.Persistence
}
}
- return new UserItemData
+ return null;
+ }
+ }
+
+ 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))
{
- UserId = userId,
- Key = key
- };
+ if (reader.Read())
+ {
+ return ReadRow(reader);
+ }
+ }
+
+ return null;
}
}
@@ -370,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 9bd7e47f3..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>
@@ -45,23 +44,19 @@ namespace MediaBrowser.Server.Implementations.Persistence
/// <returns>Task.</returns>
public async Task Initialize()
{
- var dbFile = Path.Combine(_appPaths.DataPath, "users.db");
-
- _connection = await SqliteExtensions.ConnectToDb(dbFile, Logger).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();
- cmd.Transaction = transaction;
+ 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.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 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";
-
- cmd.Parameters.Add(cmd, "@guid", DbType.Guid).Value = user.Id;
+ transaction = connection.BeginTransaction();
- 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/Photos/BaseDynamicImageProvider.cs b/MediaBrowser.Server.Implementations/Photos/BaseDynamicImageProvider.cs
index 4a69646f8..abf0f3425 100644
--- a/MediaBrowser.Server.Implementations/Photos/BaseDynamicImageProvider.cs
+++ b/MediaBrowser.Server.Implementations/Photos/BaseDynamicImageProvider.cs
@@ -13,11 +13,12 @@ using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using CommonIO;
+using MediaBrowser.Controller.Entities.Audio;
using MediaBrowser.Model.Configuration;
namespace MediaBrowser.Server.Implementations.Photos
{
- public abstract class BaseDynamicImageProvider<T> : IHasChangeMonitor, IForcedProvider, ICustomMetadataProvider<T>, IHasOrder
+ public abstract class BaseDynamicImageProvider<T> : IHasItemChangeMonitor, IForcedProvider, ICustomMetadataProvider<T>, IHasOrder
where T : IHasMetadata
{
protected IFileSystem FileSystem { get; private set; }
@@ -109,6 +110,21 @@ namespace MediaBrowser.Server.Implementations.Photos
protected async Task<ItemUpdateType> FetchAsync(IHasImages item, ImageType imageType, MetadataRefreshOptions options, CancellationToken cancellationToken)
{
+ var image = item.GetImageInfo(imageType, 0);
+
+ if (image != null)
+ {
+ if (!image.IsLocalFile)
+ {
+ return ItemUpdateType.None;
+ }
+
+ if (!FileSystem.ContainsSubPath(item.GetInternalMetadataPath(), image.Path))
+ {
+ return ItemUpdateType.None;
+ }
+ }
+
var items = await GetItemsWithImages(item).ConfigureAwait(false);
return await FetchToFileInternal(item, items, imageType, cancellationToken).ConfigureAwait(false);
@@ -232,7 +248,7 @@ namespace MediaBrowser.Server.Implementations.Photos
{
return await CreateSquareCollage(item, itemsWithImages, outputPath).ConfigureAwait(false);
}
- if (item is Playlist)
+ if (item is Playlist || item is MusicGenre)
{
return await CreateSquareCollage(item, itemsWithImages, outputPath).ConfigureAwait(false);
}
@@ -247,7 +263,7 @@ namespace MediaBrowser.Server.Implementations.Photos
get { return 7; }
}
- public bool HasChanged(IHasMetadata item, IDirectoryService directoryService, DateTime date)
+ public bool HasChanged(IHasMetadata item, IDirectoryService directoryServicee)
{
if (!Supports(item))
{
diff --git a/MediaBrowser.Server.Implementations/Playlists/PlaylistImageProvider.cs b/MediaBrowser.Server.Implementations/Playlists/PlaylistImageProvider.cs
index bdb73ea38..5b234d0c6 100644
--- a/MediaBrowser.Server.Implementations/Playlists/PlaylistImageProvider.cs
+++ b/MediaBrowser.Server.Implementations/Playlists/PlaylistImageProvider.cs
@@ -12,6 +12,8 @@ using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using CommonIO;
+using MediaBrowser.Controller.Library;
+using MediaBrowser.Model.Querying;
namespace MediaBrowser.Server.Implementations.Playlists
{
@@ -65,4 +67,36 @@ namespace MediaBrowser.Server.Implementations.Playlists
return Task.FromResult(GetFinalItems(items));
}
}
+
+ public class MusicGenreImageProvider : BaseDynamicImageProvider<MusicGenre>
+ {
+ private readonly ILibraryManager _libraryManager;
+
+ public MusicGenreImageProvider(IFileSystem fileSystem, IProviderManager providerManager, IApplicationPaths applicationPaths, IImageProcessor imageProcessor, ILibraryManager libraryManager) : base(fileSystem, providerManager, applicationPaths, imageProcessor)
+ {
+ _libraryManager = libraryManager;
+ }
+
+ protected override Task<List<BaseItem>> GetItemsWithImages(IHasImages item)
+ {
+ var items = _libraryManager.GetItemList(new InternalItemsQuery
+ {
+ Genres = new[] { item.Name },
+ IncludeItemTypes = new[] { typeof(MusicAlbum).Name, typeof(MusicVideo).Name, typeof(Audio).Name },
+ SortBy = new[] { ItemSortBy.Random },
+ Limit = 4,
+ Recursive = true,
+ ImageTypes = new[] { ImageType.Primary }
+
+ }).ToList();
+
+ return Task.FromResult(GetFinalItems(items));
+ }
+
+ //protected override Task<string> CreateImage(IHasImages item, List<BaseItem> itemsWithImages, string outputPathWithoutExtension, ImageType imageType, int imageIndex)
+ //{
+ // return CreateSingleImage(itemsWithImages, outputPathWithoutExtension, ImageType.Primary);
+ //}
+ }
+
}
diff --git a/MediaBrowser.Server.Implementations/Playlists/PlaylistManager.cs b/MediaBrowser.Server.Implementations/Playlists/PlaylistManager.cs
index 06ef05951..ba1559bd0 100644
--- a/MediaBrowser.Server.Implementations/Playlists/PlaylistManager.cs
+++ b/MediaBrowser.Server.Implementations/Playlists/PlaylistManager.cs
@@ -158,7 +158,7 @@ namespace MediaBrowser.Server.Implementations.Playlists
return path;
}
- private IEnumerable<BaseItem> GetPlaylistItems(IEnumerable<string> itemIds, string playlistMediaType, User user)
+ private Task<IEnumerable<BaseItem>> GetPlaylistItems(IEnumerable<string> itemIds, string playlistMediaType, User user)
{
var items = itemIds.Select(i => _libraryManager.GetItemById(i)).Where(i => i != null);
@@ -183,7 +183,7 @@ namespace MediaBrowser.Server.Implementations.Playlists
var list = new List<LinkedChild>();
- var items = GetPlaylistItems(itemIds, playlist.MediaType, user)
+ var items = (await GetPlaylistItems(itemIds, playlist.MediaType, user).ConfigureAwait(false))
.Where(i => i.SupportsAddingToPlaylist)
.ToList();
@@ -247,15 +247,18 @@ namespace MediaBrowser.Server.Implementations.Playlists
return;
}
- if (newIndex > oldIndex)
- {
- newIndex--;
- }
-
var item = playlist.LinkedChildren[oldIndex];
playlist.LinkedChildren.Remove(item);
- playlist.LinkedChildren.Insert(newIndex, item);
+
+ if (newIndex >= playlist.LinkedChildren.Count)
+ {
+ playlist.LinkedChildren.Add(item);
+ }
+ else
+ {
+ playlist.LinkedChildren.Insert(newIndex, item);
+ }
await playlist.UpdateToRepository(ItemUpdateType.MetadataEdit, CancellationToken.None).ConfigureAwait(false);
}
diff --git a/MediaBrowser.Server.Implementations/ScheduledTasks/ChapterImagesTask.cs b/MediaBrowser.Server.Implementations/ScheduledTasks/ChapterImagesTask.cs
index e50de7bac..607a043a6 100644
--- a/MediaBrowser.Server.Implementations/ScheduledTasks/ChapterImagesTask.cs
+++ b/MediaBrowser.Server.Implementations/ScheduledTasks/ChapterImagesTask.cs
@@ -12,6 +12,7 @@ using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using CommonIO;
+using MediaBrowser.Model.Entities;
namespace MediaBrowser.Server.Implementations.ScheduledTasks
{
@@ -85,8 +86,13 @@ namespace MediaBrowser.Server.Implementations.ScheduledTasks
/// <returns>Task.</returns>
public async Task Execute(CancellationToken cancellationToken, IProgress<double> progress)
{
- var videos = _libraryManager.RootFolder.GetRecursiveChildren(i => i is Video)
- .Cast<Video>()
+ var videos = _libraryManager.GetItemList(new InternalItemsQuery
+ {
+ MediaTypes = new[] { MediaType.Video },
+ IsFolder = false,
+ Recursive = true
+ })
+ .OfType<Video>()
.ToList();
var numComplete = 0;
@@ -97,7 +103,7 @@ namespace MediaBrowser.Server.Implementations.ScheduledTasks
try
{
- previouslyFailedImages = _fileSystem.ReadAllText(failHistoryPath)
+ previouslyFailedImages = _fileSystem.ReadAllText(failHistoryPath)
.Split(new[] { '|' }, StringSplitOptions.RemoveEmptyEntries)
.ToList();
}
diff --git a/MediaBrowser.Server.Implementations/Security/AuthenticationRepository.cs b/MediaBrowser.Server.Implementations/Security/AuthenticationRepository.cs
index b932f0cac..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()
{
- var dbFile = Path.Combine(_appPaths.DataPath, "authentication.db");
-
- _connection = await SqliteExtensions.ConnectToDb(dbFile, Logger).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/ServerManager/ServerManager.cs b/MediaBrowser.Server.Implementations/ServerManager/ServerManager.cs
index 8719f5448..33d106916 100644
--- a/MediaBrowser.Server.Implementations/ServerManager/ServerManager.cs
+++ b/MediaBrowser.Server.Implementations/ServerManager/ServerManager.cs
@@ -71,6 +71,8 @@ namespace MediaBrowser.Server.Implementations.ServerManager
/// <value>The web socket listeners.</value>
private readonly List<IWebSocketListener> _webSocketListeners = new List<IWebSocketListener>();
+ private bool _disposed;
+
/// <summary>
/// Initializes a new instance of the <see cref="ServerManager" /> class.
/// </summary>
@@ -143,6 +145,11 @@ namespace MediaBrowser.Server.Implementations.ServerManager
/// <param name="e">The <see cref="WebSocketConnectEventArgs" /> instance containing the event data.</param>
void HttpServer_WebSocketConnected(object sender, WebSocketConnectEventArgs e)
{
+ if (_disposed)
+ {
+ return;
+ }
+
var connection = new WebSocketConnection(e.WebSocket, e.Endpoint, _jsonSerializer, _logger)
{
OnReceive = ProcessWebSocketMessageReceived,
@@ -164,6 +171,11 @@ namespace MediaBrowser.Server.Implementations.ServerManager
/// <param name="result">The result.</param>
private async void ProcessWebSocketMessageReceived(WebSocketMessageInfo result)
{
+ if (_disposed)
+ {
+ return;
+ }
+
//_logger.Debug("Websocket message received: {0}", result.MessageType);
var tasks = _webSocketListeners.Select(i => Task.Run(async () =>
@@ -244,6 +256,11 @@ namespace MediaBrowser.Server.Implementations.ServerManager
throw new ArgumentNullException("dataFunction");
}
+ if (_disposed)
+ {
+ throw new ObjectDisposedException(GetType().Name);
+ }
+
cancellationToken.ThrowIfCancellationRequested();
var connectionsList = connections.Where(s => s.State == WebSocketState.Open).ToList();
@@ -301,6 +318,8 @@ namespace MediaBrowser.Server.Implementations.ServerManager
/// </summary>
public void Dispose()
{
+ _disposed = true;
+
Dispose(true);
GC.SuppressFinalize(this);
}
diff --git a/MediaBrowser.Server.Implementations/Session/SessionManager.cs b/MediaBrowser.Server.Implementations/Session/SessionManager.cs
index 88f11c368..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);
@@ -601,11 +605,9 @@ namespace MediaBrowser.Server.Implementations.Session
if (libraryItem != null)
{
- var key = libraryItem.GetUserDataKey();
-
foreach (var user in users)
{
- await OnPlaybackStart(user.Id, key, libraryItem).ConfigureAwait(false);
+ await OnPlaybackStart(user.Id, libraryItem).ConfigureAwait(false);
}
}
@@ -632,12 +634,11 @@ namespace MediaBrowser.Server.Implementations.Session
/// Called when [playback start].
/// </summary>
/// <param name="userId">The user identifier.</param>
- /// <param name="userDataKey">The user data key.</param>
/// <param name="item">The item.</param>
/// <returns>Task.</returns>
- private async Task OnPlaybackStart(Guid userId, string userDataKey, IHasUserData item)
+ private async Task OnPlaybackStart(Guid userId, IHasUserData item)
{
- var data = _userDataRepository.GetUserData(userId, userDataKey);
+ var data = _userDataRepository.GetUserData(userId, item);
data.PlayCount++;
data.LastPlayedDate = DateTime.UtcNow;
@@ -676,11 +677,9 @@ namespace MediaBrowser.Server.Implementations.Session
if (libraryItem != null)
{
- var key = libraryItem.GetUserDataKey();
-
foreach (var user in users)
{
- await OnPlaybackProgress(user, key, libraryItem, info).ConfigureAwait(false);
+ await OnPlaybackProgress(user, libraryItem, info).ConfigureAwait(false);
}
}
@@ -714,9 +713,9 @@ namespace MediaBrowser.Server.Implementations.Session
StartIdleCheckTimer();
}
- private async Task OnPlaybackProgress(User user, string userDataKey, BaseItem item, PlaybackProgressInfo info)
+ private async Task OnPlaybackProgress(User user, BaseItem item, PlaybackProgressInfo info)
{
- var data = _userDataRepository.GetUserData(user.Id, userDataKey);
+ var data = _userDataRepository.GetUserData(user.Id, item);
var positionTicks = info.PositionTicks;
@@ -811,11 +810,9 @@ namespace MediaBrowser.Server.Implementations.Session
if (libraryItem != null)
{
- var key = libraryItem.GetUserDataKey();
-
foreach (var user in users)
{
- playedToCompletion = await OnPlaybackStopped(user.Id, key, libraryItem, info.PositionTicks, info.Failed).ConfigureAwait(false);
+ playedToCompletion = await OnPlaybackStopped(user.Id, libraryItem, info.PositionTicks, info.Failed).ConfigureAwait(false);
}
}
@@ -848,14 +845,14 @@ namespace MediaBrowser.Server.Implementations.Session
await SendPlaybackStoppedNotification(session, CancellationToken.None).ConfigureAwait(false);
}
- private async Task<bool> OnPlaybackStopped(Guid userId, string userDataKey, BaseItem item, long? positionTicks, bool playbackFailed)
+ private async Task<bool> OnPlaybackStopped(Guid userId, BaseItem item, long? positionTicks, bool playbackFailed)
{
bool playedToCompletion = false;
if (!playbackFailed)
{
- var data = _userDataRepository.GetUserData(userId, userDataKey);
-
+ var data = _userDataRepository.GetUserData(userId, item);
+
if (positionTicks.HasValue)
{
playedToCompletion = _userDataRepository.UpdatePlayState(item, data, positionTicks.Value);
@@ -935,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);
@@ -953,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();
}
@@ -1016,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,29 +1037,34 @@ namespace MediaBrowser.Server.Implementations.Session
if (byName != null)
{
- var itemFilter = byName.GetItemFilter();
-
- var items = user == null ?
- _libraryManager.RootFolder.GetRecursiveChildren(i => !i.IsFolder && itemFilter(i)) :
- user.RootFolder.GetRecursiveChildren(user, i => !i.IsFolder && itemFilter(i));
+ var items = byName.GetTaggedItems(new InternalItemsQuery(user)
+ {
+ IsFolder = false,
+ Recursive = true
+ });
return FilterToSingleMediaType(items)
- .OrderBy(i => i.SortName);
+ .OrderBy(i => i.SortName)
+ .ToList();
}
if (item.IsFolder)
{
var folder = (Folder)item;
- var items = user == null ?
- folder.GetRecursiveChildren(i => !i.IsFolder) :
- folder.GetRecursiveChildren(user, i => !i.IsFolder);
+ var itemsResult = await folder.GetItems(new InternalItemsQuery(user)
+ {
+ Recursive = true,
+ IsFolder = false
+
+ }).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)
@@ -1125,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 () =>
{
@@ -1144,7 +1153,7 @@ namespace MediaBrowser.Server.Implementations.Session
}, cancellationToken));
- return Task.WhenAll(tasks);
+ await Task.WhenAll(tasks).ConfigureAwait(false);
}
/// <summary>
@@ -1374,8 +1383,8 @@ namespace MediaBrowser.Server.Implementations.Session
ServerId = _appHost.SystemId
};
}
-
-
+
+
private async Task<string> GetAuthorizationToken(string userId, string deviceId, string app, string appVersion, string deviceName)
{
var existing = _authRepo.Get(new AuthenticationInfoQuery
@@ -1451,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
{
@@ -1461,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);
+ }
}
}
@@ -1752,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 326b2893c..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,17 +74,15 @@ 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 + "/web/shared.html?id=" + info.Id;
+ info.Url = externalUrl + "/emby/web/shared.html?id=" + info.Id;
var item = _libraryManager.GetItemById(info.ItemId);
diff --git a/MediaBrowser.Server.Implementations/Social/SharingRepository.cs b/MediaBrowser.Server.Implementations/Social/SharingRepository.cs
index d6d7f021a..c4243c1a7 100644
--- a/MediaBrowser.Server.Implementations/Social/SharingRepository.cs
+++ b/MediaBrowser.Server.Implementations/Social/SharingRepository.cs
@@ -12,14 +12,10 @@ 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>
@@ -28,38 +24,18 @@ namespace MediaBrowser.Server.Implementations.Social
/// <returns>Task.</returns>
public async Task Initialize()
{
- var dbFile = Path.Combine(_appPaths.DataPath, "shares.db");
-
- _connection = await SqliteExtensions.ConnectToDb(dbFile, Logger).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/Sorting/AiredEpisodeOrderComparer.cs b/MediaBrowser.Server.Implementations/Sorting/AiredEpisodeOrderComparer.cs
index 70cf805cf..91abbe34c 100644
--- a/MediaBrowser.Server.Implementations/Sorting/AiredEpisodeOrderComparer.cs
+++ b/MediaBrowser.Server.Implementations/Sorting/AiredEpisodeOrderComparer.cs
@@ -49,8 +49,8 @@ namespace MediaBrowser.Server.Implementations.Sorting
private int Compare(Episode x, Episode y)
{
- var isXSpecial = (x.PhysicalSeasonNumber ?? -1) == 0;
- var isYSpecial = (y.PhysicalSeasonNumber ?? -1) == 0;
+ var isXSpecial = (x.ParentIndexNumber ?? -1) == 0;
+ var isYSpecial = (y.ParentIndexNumber ?? -1) == 0;
if (isXSpecial && isYSpecial)
{
@@ -74,7 +74,7 @@ namespace MediaBrowser.Server.Implementations.Sorting
{
// http://thetvdb.com/wiki/index.php?title=Special_Episodes
- var xSeason = x.PhysicalSeasonNumber ?? -1;
+ var xSeason = x.ParentIndexNumber ?? -1;
var ySeason = y.AirsAfterSeasonNumber ?? y.AirsBeforeSeasonNumber ?? -1;
if (xSeason != ySeason)
@@ -142,8 +142,8 @@ namespace MediaBrowser.Server.Implementations.Sorting
private int CompareEpisodes(Episode x, Episode y)
{
- var xValue = (x.PhysicalSeasonNumber ?? -1) * 1000 + (x.IndexNumber ?? -1);
- var yValue = (y.PhysicalSeasonNumber ?? -1) * 1000 + (y.IndexNumber ?? -1);
+ var xValue = (x.ParentIndexNumber ?? -1) * 1000 + (x.IndexNumber ?? -1);
+ var yValue = (y.ParentIndexNumber ?? -1) * 1000 + (y.IndexNumber ?? -1);
return xValue.CompareTo(yValue);
}
diff --git a/MediaBrowser.Server.Implementations/Sorting/DateLastMediaAddedComparer.cs b/MediaBrowser.Server.Implementations/Sorting/DateLastMediaAddedComparer.cs
index 68cd44ec9..e8c78b8e7 100644
--- a/MediaBrowser.Server.Implementations/Sorting/DateLastMediaAddedComparer.cs
+++ b/MediaBrowser.Server.Implementations/Sorting/DateLastMediaAddedComparer.cs
@@ -49,13 +49,13 @@ namespace MediaBrowser.Server.Implementations.Sorting
if (folder != null)
{
- return folder.GetRecursiveChildren(User, i => !i.IsFolder)
- .Select(i => i.DateCreated)
- .OrderByDescending(i => i)
- .FirstOrDefault();
+ if (folder.DateLastMediaAdded.HasValue)
+ {
+ return folder.DateLastMediaAdded.Value;
+ }
}
- return x.DateCreated;
+ return DateTime.MinValue;
}
/// <summary>
diff --git a/MediaBrowser.Server.Implementations/Sorting/DatePlayedComparer.cs b/MediaBrowser.Server.Implementations/Sorting/DatePlayedComparer.cs
index c881591be..3edf23020 100644
--- a/MediaBrowser.Server.Implementations/Sorting/DatePlayedComparer.cs
+++ b/MediaBrowser.Server.Implementations/Sorting/DatePlayedComparer.cs
@@ -47,7 +47,7 @@ namespace MediaBrowser.Server.Implementations.Sorting
/// <returns>DateTime.</returns>
private DateTime GetDate(BaseItem x)
{
- var userdata = UserDataRepository.GetUserData(User.Id, x.GetUserDataKey());
+ var userdata = UserDataRepository.GetUserData(User, x);
if (userdata != null && userdata.LastPlayedDate.HasValue)
{
diff --git a/MediaBrowser.Server.Implementations/Sorting/PlayCountComparer.cs b/MediaBrowser.Server.Implementations/Sorting/PlayCountComparer.cs
index 1bc5261b4..8b14efffc 100644
--- a/MediaBrowser.Server.Implementations/Sorting/PlayCountComparer.cs
+++ b/MediaBrowser.Server.Implementations/Sorting/PlayCountComparer.cs
@@ -34,7 +34,7 @@ namespace MediaBrowser.Server.Implementations.Sorting
/// <returns>DateTime.</returns>
private int GetValue(BaseItem x)
{
- var userdata = UserDataRepository.GetUserData(User.Id, x.GetUserDataKey());
+ var userdata = UserDataRepository.GetUserData(User, x);
return userdata == null ? 0 : userdata.PlayCount;
}
diff --git a/MediaBrowser.Server.Implementations/Sorting/SeriesSortNameComparer.cs b/MediaBrowser.Server.Implementations/Sorting/SeriesSortNameComparer.cs
index 4efc3218b..6bc1264a4 100644
--- a/MediaBrowser.Server.Implementations/Sorting/SeriesSortNameComparer.cs
+++ b/MediaBrowser.Server.Implementations/Sorting/SeriesSortNameComparer.cs
@@ -1,5 +1,4 @@
using MediaBrowser.Controller.Entities;
-using MediaBrowser.Controller.Entities.TV;
using MediaBrowser.Controller.Sorting;
using MediaBrowser.Model.Querying;
using System;
@@ -21,28 +20,9 @@ namespace MediaBrowser.Server.Implementations.Sorting
private string GetValue(BaseItem item)
{
- Series series = null;
+ var hasSeries = item as IHasSeries;
- var season = item as Season;
-
- if (season != null)
- {
- series = season.Series;
- }
-
- var episode = item as Episode;
-
- if (episode != null)
- {
- series = episode.Series;
- }
-
- if (series == null)
- {
- series = item as Series;
- }
-
- return series != null ? series.SortName : null;
+ return hasSeries != null ? hasSeries.SeriesSortName : null;
}
/// <summary>
diff --git a/MediaBrowser.Server.Implementations/Sync/SyncJobProcessor.cs b/MediaBrowser.Server.Implementations/Sync/SyncJobProcessor.cs
index 01334c121..e120d3a4d 100644
--- a/MediaBrowser.Server.Implementations/Sync/SyncJobProcessor.cs
+++ b/MediaBrowser.Server.Implementations/Sync/SyncJobProcessor.cs
@@ -149,14 +149,9 @@ namespace MediaBrowser.Server.Implementations.Sync
{
var job = _syncRepo.GetJob(id);
- return UpdateJobStatus(job);
- }
-
- private Task UpdateJobStatus(SyncJob job)
- {
if (job == null)
{
- throw new ArgumentNullException("job");
+ return Task.FromResult(true);
}
var result = _syncManager.GetJobItems(new SyncJobItemQuery
@@ -239,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)
@@ -319,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);
@@ -331,38 +338,35 @@ namespace MediaBrowser.Server.Implementations.Sync
var itemByName = item as IItemByName;
if (itemByName != null)
{
- var itemByNameFilter = itemByName.GetItemFilter();
-
- return user.RootFolder
- .GetRecursiveChildren(user, i => !i.IsFolder && itemByNameFilter(i));
- }
-
- var series = item as Series;
- if (series != null)
- {
- return series.GetEpisodes(user, false, false);
- }
-
- var season = item as Season;
- if (season != null)
- {
- return season.GetEpisodes(user, false, false);
+ return itemByName.GetTaggedItems(new InternalItemsQuery(user)
+ {
+ IsFolder = false,
+ Recursive = true
+ }).ToList();
}
if (item.IsFolder)
{
var folder = (Folder)item;
- var items = folder.GetRecursiveChildren(user, i => !i.IsFolder);
+ var itemsResult = await folder.GetItems(new InternalItemsQuery(user)
+ {
+ Recursive = true,
+ IsFolder = false
+
+ }).ConfigureAwait(false);
+
+ var items = itemsResult.Items;
if (!folder.IsPreSorted)
{
- items = items.OrderBy(i => i.SortName);
+ items = _libraryManager.Sort(items, user, new[] { ItemSortBy.SortName }, SortOrder.Ascending)
+ .ToArray();
}
- return items;
+ return items.ToList();
}
- return new[] { item };
+ return new List<BaseItem> { item };
}
private async Task EnsureSyncJobItems(string targetId, CancellationToken cancellationToken)
@@ -476,14 +480,12 @@ namespace MediaBrowser.Server.Implementations.Sync
if (jobItem != null)
{
- var job = _syncRepo.GetJob(jobItem.JobId);
if (jobItem.Status != SyncJobItemStatus.Cancelled)
{
- await ProcessJobItem(job, jobItem, enableConversion, innerProgress, cancellationToken).ConfigureAwait(false);
+ await ProcessJobItem(jobItem, enableConversion, innerProgress, cancellationToken).ConfigureAwait(false);
}
- job = _syncRepo.GetJob(jobItem.JobId);
- await UpdateJobStatus(job).ConfigureAwait(false);
+ await UpdateJobStatus(jobItem.JobId).ConfigureAwait(false);
}
numComplete++;
@@ -493,8 +495,13 @@ namespace MediaBrowser.Server.Implementations.Sync
}
}
- private async Task ProcessJobItem(SyncJob job, SyncJobItem jobItem, bool enableConversion, IProgress<double> progress, CancellationToken cancellationToken)
+ 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)
{
@@ -507,6 +514,7 @@ namespace MediaBrowser.Server.Implementations.Sync
jobItem.Progress = 0;
var syncOptions = _config.GetSyncOptions();
+ var job = _syncManager.GetJob(jobItem.JobId);
var user = _userManager.GetUserById(job.UserId);
if (user == null)
{
@@ -521,7 +529,7 @@ namespace MediaBrowser.Server.Implementations.Sync
{
AddMetadata = false,
ItemId = jobItem.ItemId,
- TargetId = job.TargetId,
+ TargetId = jobItem.TargetId,
Statuses = new[] { SyncJobItemStatus.Converting, SyncJobItemStatus.Queued, SyncJobItemStatus.ReadyToTransfer, SyncJobItemStatus.Synced, SyncJobItemStatus.Transferring }
});
@@ -531,7 +539,7 @@ namespace MediaBrowser.Server.Implementations.Sync
if (duplicateJobItems.Count > 0)
{
- var syncProvider = _syncManager.GetSyncProvider(jobItem, job) as IHasDuplicateCheck;
+ var syncProvider = _syncManager.GetSyncProvider(jobItem) as IHasDuplicateCheck;
if (!duplicateJobItems.Any(i => AllowDuplicateJobItem(syncProvider, i, jobItem)))
{
@@ -545,12 +553,12 @@ namespace MediaBrowser.Server.Implementations.Sync
var video = item as Video;
if (video != null)
{
- await Sync(jobItem, job, video, user, enableConversion, syncOptions, progress, cancellationToken).ConfigureAwait(false);
+ await Sync(jobItem, video, user, enableConversion, syncOptions, progress, cancellationToken).ConfigureAwait(false);
}
else if (item is Audio)
{
- await Sync(jobItem, job, (Audio)item, user, enableConversion, syncOptions, progress, cancellationToken).ConfigureAwait(false);
+ await Sync(jobItem, (Audio)item, user, enableConversion, syncOptions, progress, cancellationToken).ConfigureAwait(false);
}
else if (item is Photo)
@@ -574,8 +582,9 @@ namespace MediaBrowser.Server.Implementations.Sync
return true;
}
- private async Task Sync(SyncJobItem jobItem, SyncJob job, Video item, User user, bool enableConversion, SyncOptions syncOptions, IProgress<double> progress, CancellationToken cancellationToken)
+ private async Task Sync(SyncJobItem jobItem, Video item, User user, bool enableConversion, SyncOptions syncOptions, IProgress<double> progress, CancellationToken cancellationToken)
{
+ var job = _syncManager.GetJob(jobItem.JobId);
var jobOptions = _syncManager.GetVideoOptions(jobItem, job);
var conversionOptions = new VideoOptions
{
@@ -587,7 +596,7 @@ namespace MediaBrowser.Server.Implementations.Sync
conversionOptions.ItemId = item.Id.ToString("N");
conversionOptions.MediaSources = _mediaSourceManager.GetStaticMediaSources(item, false, user).ToList();
- var streamInfo = new StreamBuilder(_logger).BuildVideoItem(conversionOptions);
+ var streamInfo = new StreamBuilder(_mediaEncoder, _logger).BuildVideoItem(conversionOptions);
var mediaSource = streamInfo.MediaSource;
// No sense creating external subs if we're already burning one into the video
@@ -616,7 +625,7 @@ namespace MediaBrowser.Server.Implementations.Sync
{
// Save the job item now since conversion could take a while
await _syncManager.UpdateSyncJobItemInternal(jobItem).ConfigureAwait(false);
- await UpdateJobStatus(job).ConfigureAwait(false);
+ await UpdateJobStatus(jobItem.JobId).ConfigureAwait(false);
try
{
@@ -630,7 +639,7 @@ namespace MediaBrowser.Server.Implementations.Sync
{
jobItem.Progress = pct / 2;
await _syncManager.UpdateSyncJobItemInternal(jobItem).ConfigureAwait(false);
- await UpdateJobStatus(job).ConfigureAwait(false);
+ await UpdateJobStatus(jobItem.JobId).ConfigureAwait(false);
}
});
@@ -642,7 +651,8 @@ namespace MediaBrowser.Server.Implementations.Sync
}, innerProgress, cancellationToken);
- _syncManager.OnConversionComplete(jobItem, job);
+ jobItem.ItemDateModifiedTicks = item.DateModified.Ticks;
+ _syncManager.OnConversionComplete(jobItem);
}
catch (OperationCanceledException)
{
@@ -678,6 +688,7 @@ namespace MediaBrowser.Server.Implementations.Sync
throw new InvalidOperationException(string.Format("Cannot direct stream {0} protocol", mediaSource.Protocol));
}
+ jobItem.ItemDateModifiedTicks = item.DateModified.Ticks;
jobItem.MediaSource = mediaSource;
}
@@ -756,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))
{
@@ -775,8 +786,9 @@ namespace MediaBrowser.Server.Implementations.Sync
private const int DatabaseProgressUpdateIntervalSeconds = 2;
- private async Task Sync(SyncJobItem jobItem, SyncJob job, Audio item, User user, bool enableConversion, SyncOptions syncOptions, IProgress<double> progress, CancellationToken cancellationToken)
+ private async Task Sync(SyncJobItem jobItem, Audio item, User user, bool enableConversion, SyncOptions syncOptions, IProgress<double> progress, CancellationToken cancellationToken)
{
+ var job = _syncManager.GetJob(jobItem.JobId);
var jobOptions = _syncManager.GetAudioOptions(jobItem, job);
var conversionOptions = new AudioOptions
{
@@ -788,7 +800,7 @@ namespace MediaBrowser.Server.Implementations.Sync
conversionOptions.ItemId = item.Id.ToString("N");
conversionOptions.MediaSources = _mediaSourceManager.GetStaticMediaSources(item, false, user).ToList();
- var streamInfo = new StreamBuilder(_logger).BuildAudioItem(conversionOptions);
+ var streamInfo = new StreamBuilder(_mediaEncoder, _logger).BuildAudioItem(conversionOptions);
var mediaSource = streamInfo.MediaSource;
jobItem.MediaSourceId = streamInfo.MediaSourceId;
@@ -803,7 +815,7 @@ namespace MediaBrowser.Server.Implementations.Sync
jobItem.Status = SyncJobItemStatus.Converting;
await _syncManager.UpdateSyncJobItemInternal(jobItem).ConfigureAwait(false);
- await UpdateJobStatus(job).ConfigureAwait(false);
+ await UpdateJobStatus(jobItem.JobId).ConfigureAwait(false);
try
{
@@ -817,7 +829,7 @@ namespace MediaBrowser.Server.Implementations.Sync
{
jobItem.Progress = pct / 2;
await _syncManager.UpdateSyncJobItemInternal(jobItem).ConfigureAwait(false);
- await UpdateJobStatus(job).ConfigureAwait(false);
+ await UpdateJobStatus(jobItem.JobId).ConfigureAwait(false);
}
});
@@ -828,7 +840,8 @@ namespace MediaBrowser.Server.Implementations.Sync
}, innerProgress, cancellationToken);
- _syncManager.OnConversionComplete(jobItem, job);
+ jobItem.ItemDateModifiedTicks = item.DateModified.Ticks;
+ _syncManager.OnConversionComplete(jobItem);
}
catch (OperationCanceledException)
{
@@ -864,6 +877,7 @@ namespace MediaBrowser.Server.Implementations.Sync
throw new InvalidOperationException(string.Format("Cannot direct stream {0} protocol", mediaSource.Protocol));
}
+ jobItem.ItemDateModifiedTicks = item.DateModified.Ticks;
jobItem.MediaSource = mediaSource;
}
@@ -880,6 +894,7 @@ namespace MediaBrowser.Server.Implementations.Sync
jobItem.Progress = 50;
jobItem.Status = SyncJobItemStatus.ReadyToTransfer;
+ jobItem.ItemDateModifiedTicks = item.DateModified.Ticks;
await _syncManager.UpdateSyncJobItemInternal(jobItem).ConfigureAwait(false);
}
@@ -889,6 +904,7 @@ namespace MediaBrowser.Server.Implementations.Sync
jobItem.Progress = 50;
jobItem.Status = SyncJobItemStatus.ReadyToTransfer;
+ jobItem.ItemDateModifiedTicks = item.DateModified.Ticks;
await _syncManager.UpdateSyncJobItemInternal(jobItem).ConfigureAwait(false);
}
diff --git a/MediaBrowser.Server.Implementations/Sync/SyncManager.cs b/MediaBrowser.Server.Implementations/Sync/SyncManager.cs
index 2effad2f7..38edc3024 100644
--- a/MediaBrowser.Server.Implementations/Sync/SyncManager.cs
+++ b/MediaBrowser.Server.Implementations/Sync/SyncManager.cs
@@ -687,7 +687,7 @@ namespace MediaBrowser.Server.Implementations.Sync
private Task ReportOfflinePlayedItem(UserAction action)
{
var item = _libraryManager.GetItemById(action.ItemId);
- var userData = _userDataManager.GetUserData(new Guid(action.UserId), item.GetUserDataKey());
+ var userData = _userDataManager.GetUserData(action.UserId, item);
userData.LastPlayedDate = action.Date;
_userDataManager.UpdatePlayState(item, userData, action.PositionTicks);
@@ -775,6 +775,13 @@ namespace MediaBrowser.Server.Implementations.Sync
removeFromDevice = true;
}
}
+ else if (libraryItem != null && libraryItem.DateModified.Ticks != jobItem.ItemDateModifiedTicks && jobItem.ItemDateModifiedTicks > 0)
+ {
+ _logger.Info("Setting status to Queued for {0} because the media has been modified since the original sync.", jobItem.ItemId);
+ jobItem.Status = SyncJobItemStatus.Queued;
+ jobItem.Progress = 0;
+ requiresSaving = true;
+ }
}
else
{
@@ -881,6 +888,13 @@ namespace MediaBrowser.Server.Implementations.Sync
removeFromDevice = true;
}
}
+ else if (libraryItem != null && libraryItem.DateModified.Ticks != jobItem.ItemDateModifiedTicks && jobItem.ItemDateModifiedTicks > 0)
+ {
+ _logger.Info("Setting status to Queued for {0} because the media has been modified since the original sync.", jobItem.ItemId);
+ jobItem.Status = SyncJobItemStatus.Queued;
+ jobItem.Progress = 0;
+ requiresSaving = true;
+ }
}
else
{
@@ -1126,7 +1140,7 @@ namespace MediaBrowser.Server.Implementations.Sync
return options;
}
- public ISyncProvider GetSyncProvider(SyncJobItem jobItem, SyncJob job)
+ public ISyncProvider GetSyncProvider(SyncJobItem jobItem)
{
foreach (var provider in _providers)
{
@@ -1323,9 +1337,9 @@ namespace MediaBrowser.Server.Implementations.Sync
return list;
}
- protected internal void OnConversionComplete(SyncJobItem item, SyncJob job)
+ protected internal void OnConversionComplete(SyncJobItem item)
{
- var syncProvider = GetSyncProvider(item, job);
+ var syncProvider = GetSyncProvider(item);
if (syncProvider is AppSyncProvider)
{
return;
diff --git a/MediaBrowser.Server.Implementations/Sync/SyncRepository.cs b/MediaBrowser.Server.Implementations/Sync/SyncRepository.cs
index 39153526a..a1ed66a99 100644
--- a/MediaBrowser.Server.Implementations/Sync/SyncRepository.cs
+++ b/MediaBrowser.Server.Implementations/Sync/SyncRepository.cs
@@ -18,156 +18,44 @@ 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()
{
- var dbFile = Path.Combine(_appPaths.DataPath, "sync14.db");
-
- _connection = await SqliteExtensions.ConnectToDb(dbFile, Logger).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)",
+ "create index if not exists idx_SyncJobs1 on SyncJobs(TargetId)",
- "create table if not exists SyncJobItems (Id GUID PRIMARY KEY, ItemId TEXT, ItemName TEXT, MediaSourceId TEXT, JobId TEXT, TemporaryPath TEXT, OutputPath TEXT, Status TEXT, TargetId TEXT, DateCreated DateTime, Progress FLOAT, AdditionalFiles TEXT, MediaSource TEXT, IsMarkedForRemoval BIT, JobItemIndex INT)",
- "create index if not exists idx_SyncJobItems on SyncJobs(Id)",
-
- //pragmas
- "pragma temp_store = memory",
+ "create table if not exists SyncJobItems (Id GUID PRIMARY KEY, ItemId TEXT, ItemName TEXT, MediaSourceId TEXT, JobId TEXT, TemporaryPath TEXT, OutputPath TEXT, Status TEXT, TargetId TEXT, DateCreated DateTime, Progress FLOAT, AdditionalFiles TEXT, MediaSource TEXT, IsMarkedForRemoval BIT, JobItemIndex INT, ItemDateModifiedTicks BIGINT)",
+ "create index if not exists idx_SyncJobItems1 on SyncJobItems(Id)",
+ "create index if not exists idx_SyncJobItems2 on SyncJobItems(TargetId)",
"pragma shrink_memory"
};
- _connection.RunQueries(queries, Logger);
+ connection.RunQueries(queries, Logger);
- _connection.AddColumn(Logger, "SyncJobs", "Profile", "TEXT");
- _connection.AddColumn(Logger, "SyncJobs", "Bitrate", "INT");
-
- 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) values (@Id, @ItemId, @ItemName, @MediaSourceId, @JobId, @TemporaryPath, @OutputPath, @Status, @TargetId, @DateCreated, @Progress, @AdditionalFiles, @MediaSource, @IsMarkedForRemoval, @JobItemIndex)";
-
- _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");
-
- // _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 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");
+ 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";
- private const string BaseJobItemSelectText = "select Id, ItemId, ItemName, MediaSourceId, JobId, TemporaryPath, OutputPath, Status, TargetId, DateCreated, Progress, AdditionalFiles, MediaSource, IsMarkedForRemoval, JobItemIndex from SyncJobItems";
+ private const string BaseJobItemSelectText = "select Id, ItemId, ItemName, MediaSourceId, JobId, TemporaryPath, OutputPath, Status, TargetId, DateCreated, Progress, AdditionalFiles, MediaSource, IsMarkedForRemoval, JobItemIndex, ItemDateModifiedTicks from SyncJobItems";
public SyncJob GetJob(string id)
{
@@ -177,7 +65,7 @@ namespace MediaBrowser.Server.Implementations.Sync
}
CheckDisposed();
-
+
var guid = new Guid(id);
if (guid == Guid.Empty)
@@ -185,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)
@@ -278,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)
{
@@ -294,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();
}
}
@@ -369,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();
}
}
@@ -430,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
- };
}
}
@@ -518,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)
@@ -546,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
- };
}
}
@@ -636,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)
{
@@ -652,67 +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.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();
}
}
@@ -782,6 +788,11 @@ namespace MediaBrowser.Server.Implementations.Sync
info.IsMarkedForRemoval = reader.GetBoolean(13);
info.JobItemIndex = reader.GetInt32(14);
+ if (!reader.IsDBNull(15))
+ {
+ info.ItemDateModifiedTicks = reader.GetInt64(15);
+ }
+
return info;
}
@@ -798,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 3e43ebe9b..ddc1de9cd 100644
--- a/MediaBrowser.Server.Implementations/TV/TVSeriesManager.cs
+++ b/MediaBrowser.Server.Implementations/TV/TVSeriesManager.cs
@@ -7,6 +7,7 @@ using MediaBrowser.Model.Querying;
using System;
using System.Collections.Generic;
using System.Linq;
+using MediaBrowser.Controller.Configuration;
namespace MediaBrowser.Server.Implementations.TV
{
@@ -15,12 +16,14 @@ namespace MediaBrowser.Server.Implementations.TV
private readonly IUserManager _userManager;
private readonly IUserDataManager _userDataManager;
private readonly ILibraryManager _libraryManager;
+ private readonly IServerConfigurationManager _config;
- public TVSeriesManager(IUserManager userManager, IUserDataManager userDataManager, ILibraryManager libraryManager)
+ public TVSeriesManager(IUserManager userManager, IUserDataManager userDataManager, ILibraryManager libraryManager, IServerConfigurationManager config)
{
_userManager = userManager;
_userDataManager = userDataManager;
_libraryManager = libraryManager;
+ _config = config;
}
public QueryResult<BaseItem> GetNextUp(NextUpQuery request)
@@ -32,16 +35,36 @@ 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;
+ if (!string.IsNullOrWhiteSpace(request.SeriesId))
+ {
+ var series = _libraryManager.GetItemById(request.SeriesId);
+
+ if (series != null)
+ {
+ presentationUniqueKey = GetUniqueSeriesKey(series);
+ limit = 1;
+ }
+ }
+
+ if (string.IsNullOrWhiteSpace(presentationUniqueKey) && limit.HasValue)
+ {
+ limit = limit.Value + 10;
+ }
var items = _libraryManager.GetItemList(new InternalItemsQuery(user)
{
IncludeItemTypes = new[] { typeof(Series).Name },
- SortOrder = SortOrder.Ascending
+ SortOrder = SortOrder.Ascending,
+ PresentationUniqueKey = presentationUniqueKey,
+ Limit = limit,
+ ParentId = parentIdGuid,
+ Recursive = true
- }, parentIds).Cast<Series>();
+ }).Cast<Series>();
// Avoid implicitly captured closure
var episodes = GetNextUpEpisodes(request, user, items);
@@ -58,10 +81,30 @@ namespace MediaBrowser.Server.Implementations.TV
throw new ArgumentException("User not found");
}
+ string presentationUniqueKey = null;
+ int? limit = null;
+ if (!string.IsNullOrWhiteSpace(request.SeriesId))
+ {
+ var series = _libraryManager.GetItemById(request.SeriesId);
+
+ if (series != null)
+ {
+ presentationUniqueKey = GetUniqueSeriesKey(series);
+ limit = 1;
+ }
+ }
+
+ if (string.IsNullOrWhiteSpace(presentationUniqueKey) && limit.HasValue)
+ {
+ limit = limit.Value + 10;
+ }
+
var items = _libraryManager.GetItemList(new InternalItemsQuery(user)
{
IncludeItemTypes = new[] { typeof(Series).Name },
- SortOrder = SortOrder.Ascending
+ SortOrder = SortOrder.Ascending,
+ PresentationUniqueKey = presentationUniqueKey,
+ Limit = limit
}, parentsFolders.Select(i => i.Id.ToString("N"))).Cast<Series>();
@@ -76,32 +119,40 @@ namespace MediaBrowser.Server.Implementations.TV
// Avoid implicitly captured closure
var currentUser = user;
- return FilterSeries(request, series)
- .AsParallel()
+ var allNextUp = series
.Select(i => GetNextUp(i, currentUser))
+ .Where(i => i.Item1 != null)
// 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;
+ .OrderByDescending(i => i.Item2)
+ .ThenByDescending(i => i.Item1.PremiereDate ?? DateTime.MinValue)
+ .ToList();
- var seriesUserData = _userDataManager.GetUserData(user.Id, episode.Series.GetUserDataKey());
+ // If viewing all next up for all series, remove first episodes
+ if (string.IsNullOrWhiteSpace(request.SeriesId))
+ {
+ var withoutFirstEpisode = allNextUp
+ .Where(i => !i.Item3)
+ .ToList();
- if (seriesUserData.IsFavorite)
- {
- return 2;
- }
+ // But if that returns empty, keep those first episodes (avoid completely empty view)
+ if (withoutFirstEpisode.Count > 0)
+ {
+ allNextUp = withoutFirstEpisode;
+ }
+ }
- if (seriesUserData.Likes.HasValue)
- {
- return seriesUserData.Likes.Value ? 1 : -1;
- }
+ return allNextUp
+ .Select(i => i.Item1)
+ .Take(request.Limit ?? int.MaxValue);
+ }
- return 0;
- })
- .ThenByDescending(i => i.Item2)
- .ThenByDescending(i => i.Item1.PremiereDate ?? DateTime.MinValue)
- .Select(i => i.Item1);
+ private string GetUniqueSeriesKey(BaseItem series)
+ {
+ if (_config.Configuration.SchemaVersion < 97)
+ {
+ return series.Id.ToString("N");
+ }
+ return series.PresentationUniqueKey;
}
/// <summary>
@@ -112,64 +163,43 @@ 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 includeMissing = user.Configuration.DisplayMissingEpisodes;
-
- // Go back starting with the most recent episodes
- foreach (var episode in allEpisodes)
+ var lastWatchedEpisode = _libraryManager.GetItemList(new InternalItemsQuery(user)
{
- var userData = _userDataManager.GetUserData(user.Id, episode.GetUserDataKey());
+ AncestorWithPresentationUniqueKey = GetUniqueSeriesKey(series),
+ IncludeItemTypes = new[] { typeof(Episode).Name },
+ SortBy = new[] { ItemSortBy.SortName },
+ SortOrder = SortOrder.Descending,
+ IsPlayed = true,
+ Limit = 1,
+ ParentIndexNumberNotEquals = 0
- if (userData.Played)
- {
- if (lastWatched != null || nextUp == null)
- {
- break;
- }
-
- lastWatched = episode;
- lastWatchedDate = userData.LastPlayedDate ?? DateTime.MinValue;
- }
- else
- {
- if (!episode.IsVirtualUnaired && (!episode.IsMissingEpisode || includeMissing))
- {
- nextUp = episode;
- }
- }
- }
+ }).FirstOrDefault();
- if (lastWatched != null)
+ var firstUnwatchedEpisode = _libraryManager.GetItemList(new InternalItemsQuery(user)
{
- return new Tuple<Episode, DateTime, bool>(nextUp, lastWatchedDate, false);
- }
-
- var firstEpisode = allEpisodes.LastOrDefault(i => !i.IsVirtualUnaired && (!i.IsMissingEpisode || includeMissing) && !i.IsPlayed(user));
-
- // Return the first episode
- return new Tuple<Episode, DateTime, bool>(firstEpisode, DateTime.MinValue, true);
- }
-
- private IEnumerable<Series> FilterSeries(NextUpQuery request, IEnumerable<Series> items)
- {
- if (!string.IsNullOrWhiteSpace(request.SeriesId))
+ AncestorWithPresentationUniqueKey = GetUniqueSeriesKey(series),
+ 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
+
+ }).Cast<Episode>().FirstOrDefault();
+
+ if (lastWatchedEpisode != null && firstUnwatchedEpisode != null)
{
- var id = new Guid(request.SeriesId);
+ var userData = _userDataManager.GetUserData(user, lastWatchedEpisode);
+
+ var lastWatchedDate = userData.LastPlayedDate ?? DateTime.MinValue.AddDays(1);
- items = items.Where(i => i.Id == id);
+ return new Tuple<Episode, DateTime, bool>(firstUnwatchedEpisode, lastWatchedDate, false);
}
- return items;
+ // Return the first episode
+ 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/UserViews/CollectionFolderImageProvider.cs b/MediaBrowser.Server.Implementations/UserViews/CollectionFolderImageProvider.cs
index a66884f89..29716d33e 100644
--- a/MediaBrowser.Server.Implementations/UserViews/CollectionFolderImageProvider.cs
+++ b/MediaBrowser.Server.Implementations/UserViews/CollectionFolderImageProvider.cs
@@ -54,11 +54,6 @@ namespace MediaBrowser.Server.Implementations.UserViews
{
return series;
}
- var episodeSeason = episode.Season;
- if (episodeSeason != null)
- {
- return episodeSeason;
- }
return episode;
}
diff --git a/MediaBrowser.Server.Implementations/UserViews/DynamicImageProvider.cs b/MediaBrowser.Server.Implementations/UserViews/DynamicImageProvider.cs
index 911dbb0cb..ea4da19b2 100644
--- a/MediaBrowser.Server.Implementations/UserViews/DynamicImageProvider.cs
+++ b/MediaBrowser.Server.Implementations/UserViews/DynamicImageProvider.cs
@@ -86,11 +86,6 @@ namespace MediaBrowser.Server.Implementations.UserViews
{
return series;
}
- var episodeSeason = episode.Season;
- if (episodeSeason != null)
- {
- return episodeSeason;
- }
return episode;
}
@@ -153,7 +148,8 @@ namespace MediaBrowser.Server.Implementations.UserViews
CollectionType.HomeVideos,
CollectionType.BoxSets,
CollectionType.Playlists,
- CollectionType.Photos
+ CollectionType.Photos,
+ string.Empty
};
return collectionStripViewTypes.Contains(view.ViewType ?? string.Empty);
diff --git a/MediaBrowser.Server.Implementations/packages.config b/MediaBrowser.Server.Implementations/packages.config
index 66aede029..9ae0a126a 100644
--- a/MediaBrowser.Server.Implementations/packages.config
+++ b/MediaBrowser.Server.Implementations/packages.config
@@ -1,12 +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.49" targetFramework="net45" />
+ <package id="MediaBrowser.Naming" version="1.0.0.53" 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="SocketHttpListener" version="1.0.0.29" targetFramework="net45" />
+ <package id="SimpleInjector" version="3.2.0" targetFramework="net45" />
+ <package id="SocketHttpListener" version="1.0.0.35" 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 01531c3a3..d80bec7d8 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>
@@ -32,6 +33,11 @@
<CreatePackage>true</CreatePackage>
<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>
@@ -47,6 +53,11 @@
<CreatePackage>true</CreatePackage>
<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>
@@ -63,6 +74,11 @@
<EnableCodeSigning>false</EnableCodeSigning>
<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" />
@@ -74,22 +90,21 @@
<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>
<Reference Include="CommonIO">
<HintPath>..\packages\CommonIO.1.0.0.9\lib\net45\CommonIO.dll</HintPath>
</Reference>
+ <Reference Include="System.Data.SQLite">
+ <HintPath>..\ThirdParty\System.Data.SQLite.ManagedOnly\1.0.94.0\System.Data.SQLite.dll</HintPath>
+ </Reference>
+ <Reference Include="System.Data" />
</ItemGroup>
<ItemGroup>
<Folder Include="Resources\" />
<Folder Include="Native\" />
- <Folder Include="Resources\swagger-ui\" />
- <Folder Include="Resources\dashboard-ui\" />
+ <Folder Include="Security\" />
</ItemGroup>
<ItemGroup>
<Compile Include="AppDelegate.cs" />
@@ -111,6 +126,58 @@
<Compile Include="..\MediaBrowser.Server.Mono\Networking\CertificateGenerator.cs">
<Link>Native\CertificateGenerator.cs</Link>
</Compile>
+ <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" />
@@ -195,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>
@@ -222,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>
@@ -240,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>
@@ -252,17 +372,12 @@
<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>
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\addplugin.html">
<Link>Resources\dashboard-ui\addplugin.html</Link>
</BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\advanced.html">
- <Link>Resources\dashboard-ui\advanced.html</Link>
- </BundleResource>
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\appservices.html">
<Link>Resources\dashboard-ui\appservices.html</Link>
</BundleResource>
@@ -281,15 +396,9 @@
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\channels.html">
<Link>Resources\dashboard-ui\channels.html</Link>
</BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\channelsettings.html">
- <Link>Resources\dashboard-ui\channelsettings.html</Link>
- </BundleResource>
<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>
@@ -317,9 +426,6 @@
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\dlnaprofiles.html">
<Link>Resources\dashboard-ui\dlnaprofiles.html</Link>
</BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\dlnaserversettings.html">
- <Link>Resources\dashboard-ui\dlnaserversettings.html</Link>
- </BundleResource>
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\dlnasettings.html">
<Link>Resources\dashboard-ui\dlnasettings.html</Link>
</BundleResource>
@@ -368,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>
@@ -401,9 +507,6 @@
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\livetvstatus.html">
<Link>Resources\dashboard-ui\livetvstatus.html</Link>
</BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\livetvtimer.html">
- <Link>Resources\dashboard-ui\livetvtimer.html</Link>
- </BundleResource>
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\livetvtunerprovider-hdhomerun.html">
<Link>Resources\dashboard-ui\livetvtunerprovider-hdhomerun.html</Link>
</BundleResource>
@@ -425,9 +528,6 @@
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\metadata.html">
<Link>Resources\dashboard-ui\metadata.html</Link>
</BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\metadataadvanced.html">
- <Link>Resources\dashboard-ui\metadataadvanced.html</Link>
- </BundleResource>
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\metadataimages.html">
<Link>Resources\dashboard-ui\metadataimages.html</Link>
</BundleResource>
@@ -524,9 +624,6 @@
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\streamingsettings.html">
<Link>Resources\dashboard-ui\streamingsettings.html</Link>
</BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\support.html">
- <Link>Resources\dashboard-ui\support.html</Link>
- </BundleResource>
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\supporterkey.html">
<Link>Resources\dashboard-ui\supporterkey.html</Link>
</BundleResource>
@@ -563,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>
@@ -1301,29 +1401,44 @@
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\emby-webcomponents\browserdeviceprofile.js">
<Link>Resources\dashboard-ui\bower_components\emby-webcomponents\browserdeviceprofile.js</Link>
</BundleResource>
+ <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\emby-webcomponents\clearbutton.css">
+ <Link>Resources\dashboard-ui\bower_components\emby-webcomponents\clearbutton.css</Link>
+ </BundleResource>
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\emby-webcomponents\datetime.js">
<Link>Resources\dashboard-ui\bower_components\emby-webcomponents\datetime.js</Link>
</BundleResource>
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\emby-webcomponents\fetchhelper.js">
<Link>Resources\dashboard-ui\bower_components\emby-webcomponents\fetchhelper.js</Link>
</BundleResource>
+ <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\emby-webcomponents\filedownloader.js">
+ <Link>Resources\dashboard-ui\bower_components\emby-webcomponents\filedownloader.js</Link>
+ </BundleResource>
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\emby-webcomponents\focusmanager.js">
<Link>Resources\dashboard-ui\bower_components\emby-webcomponents\focusmanager.js</Link>
</BundleResource>
+ <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\emby-webcomponents\formdialog.css">
+ <Link>Resources\dashboard-ui\bower_components\emby-webcomponents\formdialog.css</Link>
+ </BundleResource>
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\emby-webcomponents\globalize.js">
<Link>Resources\dashboard-ui\bower_components\emby-webcomponents\globalize.js</Link>
</BundleResource>
+ <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\emby-webcomponents\itemcontextmenu.js">
+ <Link>Resources\dashboard-ui\bower_components\emby-webcomponents\itemcontextmenu.js</Link>
+ </BundleResource>
+ <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\emby-webcomponents\itemhelper.js">
+ <Link>Resources\dashboard-ui\bower_components\emby-webcomponents\itemhelper.js</Link>
+ </BundleResource>
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\emby-webcomponents\layoutmanager.js">
<Link>Resources\dashboard-ui\bower_components\emby-webcomponents\layoutmanager.js</Link>
</BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\emby-webcomponents\qualityoptions.js">
- <Link>Resources\dashboard-ui\bower_components\emby-webcomponents\qualityoptions.js</Link>
+ <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\emby-webcomponents\multidownload.js">
+ <Link>Resources\dashboard-ui\bower_components\emby-webcomponents\multidownload.js</Link>
</BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\emby-webcomponents\requirecss.js">
- <Link>Resources\dashboard-ui\bower_components\emby-webcomponents\requirecss.js</Link>
+ <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\emby-webcomponents\objectassign.js">
+ <Link>Resources\dashboard-ui\bower_components\emby-webcomponents\objectassign.js</Link>
</BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\emby-webcomponents\requirehtml.js">
- <Link>Resources\dashboard-ui\bower_components\emby-webcomponents\requirehtml.js</Link>
+ <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\emby-webcomponents\qualityoptions.js">
+ <Link>Resources\dashboard-ui\bower_components\emby-webcomponents\qualityoptions.js</Link>
</BundleResource>
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\emby-webcomponents\router.js">
<Link>Resources\dashboard-ui\bower_components\emby-webcomponents\router.js</Link>
@@ -1331,12 +1446,21 @@
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\emby-webcomponents\scrollhelper.js">
<Link>Resources\dashboard-ui\bower_components\emby-webcomponents\scrollhelper.js</Link>
</BundleResource>
+ <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\emby-webcomponents\scrollstyles.css">
+ <Link>Resources\dashboard-ui\bower_components\emby-webcomponents\scrollstyles.css</Link>
+ </BundleResource>
+ <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\emby-webcomponents\servernotifications.js">
+ <Link>Resources\dashboard-ui\bower_components\emby-webcomponents\servernotifications.js</Link>
+ </BundleResource>
+ <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\emby-webcomponents\shell.js">
+ <Link>Resources\dashboard-ui\bower_components\emby-webcomponents\shell.js</Link>
+ </BundleResource>
+ <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\emby-webcomponents\shortcuts.js">
+ <Link>Resources\dashboard-ui\bower_components\emby-webcomponents\shortcuts.js</Link>
+ </BundleResource>
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\emby-webcomponents\usersettings.js">
<Link>Resources\dashboard-ui\bower_components\emby-webcomponents\usersettings.js</Link>
</BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\emby-webcomponents\viewmanager.js">
- <Link>Resources\dashboard-ui\bower_components\emby-webcomponents\viewmanager.js</Link>
- </BundleResource>
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\emby-webcomponents\visibleinviewport.js">
<Link>Resources\dashboard-ui\bower_components\emby-webcomponents\visibleinviewport.js</Link>
</BundleResource>
@@ -1352,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>
@@ -1370,6 +1503,51 @@
<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-collapse\emby-collapse.css">
+ <Link>Resources\dashboard-ui\bower_components\emby-webcomponents\emby-collapse\emby-collapse.css</Link>
+ </BundleResource>
+ <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\emby-webcomponents\emby-collapse\emby-collapse.js">
+ <Link>Resources\dashboard-ui\bower_components\emby-webcomponents\emby-collapse\emby-collapse.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>
@@ -1559,6 +1737,15 @@
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\emby-webcomponents\fonts\roboto\ty9dfvLAziwdqQ2dHoyjphTbgVql8nDJpwnrE27mub0.woff2">
<Link>Resources\dashboard-ui\bower_components\emby-webcomponents\fonts\roboto\ty9dfvLAziwdqQ2dHoyjphTbgVql8nDJpwnrE27mub0.woff2</Link>
</BundleResource>
+ <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\emby-webcomponents\guide\guide.css">
+ <Link>Resources\dashboard-ui\bower_components\emby-webcomponents\guide\guide.css</Link>
+ </BundleResource>
+ <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\emby-webcomponents\guide\guide.js">
+ <Link>Resources\dashboard-ui\bower_components\emby-webcomponents\guide\guide.js</Link>
+ </BundleResource>
+ <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\images\basicimagefetcher.js">
<Link>Resources\dashboard-ui\bower_components\emby-webcomponents\images\basicimagefetcher.js</Link>
</BundleResource>
@@ -1568,6 +1755,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>
@@ -1580,11 +1785,23 @@
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\emby-webcomponents\loading\loading.js">
<Link>Resources\dashboard-ui\bower_components\emby-webcomponents\loading\loading.js</Link>
</BundleResource>
+ <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\emby-webcomponents\mediainfo\fresh.png">
+ <Link>Resources\dashboard-ui\bower_components\emby-webcomponents\mediainfo\fresh.png</Link>
+ </BundleResource>
+ <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\emby-webcomponents\mediainfo\mediainfo.css">
+ <Link>Resources\dashboard-ui\bower_components\emby-webcomponents\mediainfo\mediainfo.css</Link>
+ </BundleResource>
+ <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\emby-webcomponents\mediainfo\mediainfo.js">
+ <Link>Resources\dashboard-ui\bower_components\emby-webcomponents\mediainfo\mediainfo.js</Link>
+ </BundleResource>
+ <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\emby-webcomponents\mediainfo\rotten.png">
+ <Link>Resources\dashboard-ui\bower_components\emby-webcomponents\mediainfo\rotten.png</Link>
+ </BundleResource>
<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\prompt\icons.html">
- <Link>Resources\dashboard-ui\bower_components\emby-webcomponents\prompt\icons.html</Link>
+ <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>
@@ -1595,8 +1812,65 @@
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\emby-webcomponents\prompt\style.css">
<Link>Resources\dashboard-ui\bower_components\emby-webcomponents\prompt\style.css</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 Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\emby-webcomponents\recordingcreator\recordingcreator.css">
+ <Link>Resources\dashboard-ui\bower_components\emby-webcomponents\recordingcreator\recordingcreator.css</Link>
+ </BundleResource>
+ <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\emby-webcomponents\recordingcreator\recordingcreator.js">
+ <Link>Resources\dashboard-ui\bower_components\emby-webcomponents\recordingcreator\recordingcreator.js</Link>
+ </BundleResource>
+ <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\emby-webcomponents\recordingcreator\recordingcreator.template.html">
+ <Link>Resources\dashboard-ui\bower_components\emby-webcomponents\recordingcreator\recordingcreator.template.html</Link>
+ </BundleResource>
+ <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\emby-webcomponents\recordingcreator\recordingeditor.js">
+ <Link>Resources\dashboard-ui\bower_components\emby-webcomponents\recordingcreator\recordingeditor.js</Link>
+ </BundleResource>
+ <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>
+ <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\emby-webcomponents\require\requirehtml.js">
+ <Link>Resources\dashboard-ui\bower_components\emby-webcomponents\require\requirehtml.js</Link>
+ </BundleResource>
+ <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\emby-webcomponents\require\requiretext.js">
+ <Link>Resources\dashboard-ui\bower_components\emby-webcomponents\require\requiretext.js</Link>
+ </BundleResource>
+ <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\emby-webcomponents\sharing\sharingmanager.js">
+ <Link>Resources\dashboard-ui\bower_components\emby-webcomponents\sharing\sharingmanager.js</Link>
+ </BundleResource>
+ <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\emby-webcomponents\sharing\sharingmenu.js">
+ <Link>Resources\dashboard-ui\bower_components\emby-webcomponents\sharing\sharingmenu.js</Link>
+ </BundleResource>
+ <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\emby-webcomponents\sharing\social-share-kit-1.0.4\LICENSE">
+ <Link>Resources\dashboard-ui\bower_components\emby-webcomponents\sharing\social-share-kit-1.0.4\LICENSE</Link>
+ </BundleResource>
+ <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\emby-webcomponents\sharing\social-share-kit-1.0.4\README.md">
+ <Link>Resources\dashboard-ui\bower_components\emby-webcomponents\sharing\social-share-kit-1.0.4\README.md</Link>
+ </BundleResource>
+ <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\emby-webcomponents\sharing\social-share-kit-1.0.4\dist\css\social-share-kit.css">
+ <Link>Resources\dashboard-ui\bower_components\emby-webcomponents\sharing\social-share-kit-1.0.4\dist\css\social-share-kit.css</Link>
+ </BundleResource>
+ <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\emby-webcomponents\sharing\social-share-kit-1.0.4\dist\fonts\social-share-kit.eot">
+ <Link>Resources\dashboard-ui\bower_components\emby-webcomponents\sharing\social-share-kit-1.0.4\dist\fonts\social-share-kit.eot</Link>
+ </BundleResource>
+ <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\emby-webcomponents\sharing\social-share-kit-1.0.4\dist\fonts\social-share-kit.svg">
+ <Link>Resources\dashboard-ui\bower_components\emby-webcomponents\sharing\social-share-kit-1.0.4\dist\fonts\social-share-kit.svg</Link>
+ </BundleResource>
+ <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\emby-webcomponents\sharing\social-share-kit-1.0.4\dist\fonts\social-share-kit.ttf">
+ <Link>Resources\dashboard-ui\bower_components\emby-webcomponents\sharing\social-share-kit-1.0.4\dist\fonts\social-share-kit.ttf</Link>
+ </BundleResource>
+ <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\emby-webcomponents\sharing\social-share-kit-1.0.4\dist\fonts\social-share-kit.woff">
+ <Link>Resources\dashboard-ui\bower_components\emby-webcomponents\sharing\social-share-kit-1.0.4\dist\fonts\social-share-kit.woff</Link>
+ </BundleResource>
+ <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\emby-webcomponents\sharing\social-share-kit-1.0.4\dist\js\social-share-kit.js">
+ <Link>Resources\dashboard-ui\bower_components\emby-webcomponents\sharing\social-share-kit-1.0.4\dist\js\social-share-kit.js</Link>
+ </BundleResource>
+ <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\slideshow.js">
<Link>Resources\dashboard-ui\bower_components\emby-webcomponents\slideshow\slideshow.js</Link>
@@ -1604,23 +1878,59 @@
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\emby-webcomponents\slideshow\style.css">
<Link>Resources\dashboard-ui\bower_components\emby-webcomponents\slideshow\style.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 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\fastclick\.bower.json">
- <Link>Resources\dashboard-ui\bower_components\fastclick\.bower.json</Link>
+ <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>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\fastclick\LICENSE">
- <Link>Resources\dashboard-ui\bower_components\fastclick\LICENSE</Link>
+ <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\emby-webcomponents\strings\es-MX.json">
+ <Link>Resources\dashboard-ui\bower_components\emby-webcomponents\strings\es-MX.json</Link>
</BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\fastclick\README.md">
- <Link>Resources\dashboard-ui\bower_components\fastclick\README.md</Link>
+ <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\emby-webcomponents\strings\kk.json">
+ <Link>Resources\dashboard-ui\bower_components\emby-webcomponents\strings\kk.json</Link>
</BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\fastclick\bower.json">
- <Link>Resources\dashboard-ui\bower_components\fastclick\bower.json</Link>
+ <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\emby-webcomponents\strings\nb.json">
+ <Link>Resources\dashboard-ui\bower_components\emby-webcomponents\strings\nb.json</Link>
</BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\fastclick\lib\fastclick.js">
- <Link>Resources\dashboard-ui\bower_components\fastclick\lib\fastclick.js</Link>
+ <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>
+ <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\emby-webcomponents\viewmanager\viewmanager.js">
+ <Link>Resources\dashboard-ui\bower_components\emby-webcomponents\viewmanager\viewmanager.js</Link>
</BundleResource>
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\fetch\.bower.json">
<Link>Resources\dashboard-ui\bower_components\fetch\.bower.json</Link>
@@ -1634,6 +1944,78 @@
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\fetch\fetch.js">
<Link>Resources\dashboard-ui\bower_components\fetch\fetch.js</Link>
</BundleResource>
+ <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\fingerprintjs2\.bower.json">
+ <Link>Resources\dashboard-ui\bower_components\fingerprintjs2\.bower.json</Link>
+ </BundleResource>
+ <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\fingerprintjs2\CONTRIBUTING.md">
+ <Link>Resources\dashboard-ui\bower_components\fingerprintjs2\CONTRIBUTING.md</Link>
+ </BundleResource>
+ <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\fingerprintjs2\FAQ.md">
+ <Link>Resources\dashboard-ui\bower_components\fingerprintjs2\FAQ.md</Link>
+ </BundleResource>
+ <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\fingerprintjs2\README.md">
+ <Link>Resources\dashboard-ui\bower_components\fingerprintjs2\README.md</Link>
+ </BundleResource>
+ <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\fingerprintjs2\bower.json">
+ <Link>Resources\dashboard-ui\bower_components\fingerprintjs2\bower.json</Link>
+ </BundleResource>
+ <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\fingerprintjs2\fingerprint2.js">
+ <Link>Resources\dashboard-ui\bower_components\fingerprintjs2\fingerprint2.js</Link>
+ </BundleResource>
+ <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\fingerprintjs2\gulpfile.js">
+ <Link>Resources\dashboard-ui\bower_components\fingerprintjs2\gulpfile.js</Link>
+ </BundleResource>
+ <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\fingerprintjs2\index.html">
+ <Link>Resources\dashboard-ui\bower_components\fingerprintjs2\index.html</Link>
+ </BundleResource>
+ <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\fingerprintjs2\package.json">
+ <Link>Resources\dashboard-ui\bower_components\fingerprintjs2\package.json</Link>
+ </BundleResource>
+ <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\fingerprintjs2\dist\fingerprint2.min.js">
+ <Link>Resources\dashboard-ui\bower_components\fingerprintjs2\dist\fingerprint2.min.js</Link>
+ </BundleResource>
+ <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\fingerprintjs2\flash\FontList.as">
+ <Link>Resources\dashboard-ui\bower_components\fingerprintjs2\flash\FontList.as</Link>
+ </BundleResource>
+ <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\fingerprintjs2\flash\Makefile">
+ <Link>Resources\dashboard-ui\bower_components\fingerprintjs2\flash\Makefile</Link>
+ </BundleResource>
+ <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\fingerprintjs2\flash\compiled\FontList.swf">
+ <Link>Resources\dashboard-ui\bower_components\fingerprintjs2\flash\compiled\FontList.swf</Link>
+ </BundleResource>
+ <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\fingerprintjs2\specs\phantomjs-testrunner.js">
+ <Link>Resources\dashboard-ui\bower_components\fingerprintjs2\specs\phantomjs-testrunner.js</Link>
+ </BundleResource>
+ <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\fingerprintjs2\specs\phantomjs.runner.sh">
+ <Link>Resources\dashboard-ui\bower_components\fingerprintjs2\specs\phantomjs.runner.sh</Link>
+ </BundleResource>
+ <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\fingerprintjs2\specs\spec_runner.html">
+ <Link>Resources\dashboard-ui\bower_components\fingerprintjs2\specs\spec_runner.html</Link>
+ </BundleResource>
+ <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\fingerprintjs2\specs\specs.js">
+ <Link>Resources\dashboard-ui\bower_components\fingerprintjs2\specs\specs.js</Link>
+ </BundleResource>
+ <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\fingerprintjs2\specs\lib\jasmine-2.3.4\boot.js">
+ <Link>Resources\dashboard-ui\bower_components\fingerprintjs2\specs\lib\jasmine-2.3.4\boot.js</Link>
+ </BundleResource>
+ <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\fingerprintjs2\specs\lib\jasmine-2.3.4\jasmine-html.js">
+ <Link>Resources\dashboard-ui\bower_components\fingerprintjs2\specs\lib\jasmine-2.3.4\jasmine-html.js</Link>
+ </BundleResource>
+ <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\fingerprintjs2\specs\lib\jasmine-2.3.4\jasmine-matchers.js">
+ <Link>Resources\dashboard-ui\bower_components\fingerprintjs2\specs\lib\jasmine-2.3.4\jasmine-matchers.js</Link>
+ </BundleResource>
+ <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\fingerprintjs2\specs\lib\jasmine-2.3.4\jasmine.css">
+ <Link>Resources\dashboard-ui\bower_components\fingerprintjs2\specs\lib\jasmine-2.3.4\jasmine.css</Link>
+ </BundleResource>
+ <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\fingerprintjs2\specs\lib\jasmine-2.3.4\jasmine.js">
+ <Link>Resources\dashboard-ui\bower_components\fingerprintjs2\specs\lib\jasmine-2.3.4\jasmine.js</Link>
+ </BundleResource>
+ <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\fingerprintjs2\specs\lib\jasmine-2.3.4\jasmine_favicon.png">
+ <Link>Resources\dashboard-ui\bower_components\fingerprintjs2\specs\lib\jasmine-2.3.4\jasmine_favicon.png</Link>
+ </BundleResource>
+ <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\fingerprintjs2\specs\lib\jasmine-2.3.4\terminal.js">
+ <Link>Resources\dashboard-ui\bower_components\fingerprintjs2\specs\lib\jasmine-2.3.4\terminal.js</Link>
+ </BundleResource>
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\font-roboto\.bower.json">
<Link>Resources\dashboard-ui\bower_components\font-roboto\.bower.json</Link>
</BundleResource>
@@ -1673,6 +2055,9 @@
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\hammerjs\hammer.min.js">
<Link>Resources\dashboard-ui\bower_components\hammerjs\hammer.min.js</Link>
</BundleResource>
+ <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\hammerjs\hammer.min.js.map">
+ <Link>Resources\dashboard-ui\bower_components\hammerjs\hammer.min.js.map</Link>
+ </BundleResource>
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\hammerjs\hammer.min.map">
<Link>Resources\dashboard-ui\bower_components\hammerjs\hammer.min.map</Link>
</BundleResource>
@@ -1790,6 +2175,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>
@@ -1871,6 +2259,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>
@@ -1967,6 +2358,9 @@
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-a11y-keys-behavior\iron-a11y-keys-behavior.html">
<Link>Resources\dashboard-ui\bower_components\iron-a11y-keys-behavior\iron-a11y-keys-behavior.html</Link>
</BundleResource>
+ <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-a11y-keys-behavior\.github\ISSUE_TEMPLATE.md">
+ <Link>Resources\dashboard-ui\bower_components\iron-a11y-keys-behavior\.github\ISSUE_TEMPLATE.md</Link>
+ </BundleResource>
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-a11y-keys-behavior\demo\index.html">
<Link>Resources\dashboard-ui\bower_components\iron-a11y-keys-behavior\demo\index.html</Link>
</BundleResource>
@@ -2042,6 +2436,9 @@
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-behaviors\iron-control-state.html">
<Link>Resources\dashboard-ui\bower_components\iron-behaviors\iron-control-state.html</Link>
</BundleResource>
+ <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-behaviors\.github\ISSUE_TEMPLATE.md">
+ <Link>Resources\dashboard-ui\bower_components\iron-behaviors\.github\ISSUE_TEMPLATE.md</Link>
+ </BundleResource>
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-behaviors\demo\index.html">
<Link>Resources\dashboard-ui\bower_components\iron-behaviors\demo\index.html</Link>
</BundleResource>
@@ -2159,102 +2556,24 @@
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-demo-helpers\index.html">
<Link>Resources\dashboard-ui\bower_components\iron-demo-helpers\index.html</Link>
</BundleResource>
+ <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-demo-helpers\url-bar.html">
+ <Link>Resources\dashboard-ui\bower_components\iron-demo-helpers\url-bar.html</Link>
+ </BundleResource>
+ <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-demo-helpers\.github\ISSUE_TEMPLATE.md">
+ <Link>Resources\dashboard-ui\bower_components\iron-demo-helpers\.github\ISSUE_TEMPLATE.md</Link>
+ </BundleResource>
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-demo-helpers\demo\index.html">
<Link>Resources\dashboard-ui\bower_components\iron-demo-helpers\demo\index.html</Link>
</BundleResource>
+ <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-demo-helpers\demo\url-bar.html">
+ <Link>Resources\dashboard-ui\bower_components\iron-demo-helpers\demo\url-bar.html</Link>
+ </BundleResource>
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-demo-helpers\test\basic.html">
<Link>Resources\dashboard-ui\bower_components\iron-demo-helpers\test\basic.html</Link>
</BundleResource>
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-demo-helpers\test\index.html">
<Link>Resources\dashboard-ui\bower_components\iron-demo-helpers\test\index.html</Link>
</BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-dropdown\.bower.json">
- <Link>Resources\dashboard-ui\bower_components\iron-dropdown\.bower.json</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-dropdown\.gitignore">
- <Link>Resources\dashboard-ui\bower_components\iron-dropdown\.gitignore</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-dropdown\.travis.yml">
- <Link>Resources\dashboard-ui\bower_components\iron-dropdown\.travis.yml</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-dropdown\CONTRIBUTING.md">
- <Link>Resources\dashboard-ui\bower_components\iron-dropdown\CONTRIBUTING.md</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-dropdown\README.md">
- <Link>Resources\dashboard-ui\bower_components\iron-dropdown\README.md</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-dropdown\bower.json">
- <Link>Resources\dashboard-ui\bower_components\iron-dropdown\bower.json</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-dropdown\index.html">
- <Link>Resources\dashboard-ui\bower_components\iron-dropdown\index.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-dropdown\iron-dropdown-scroll-manager.html">
- <Link>Resources\dashboard-ui\bower_components\iron-dropdown\iron-dropdown-scroll-manager.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-dropdown\iron-dropdown.html">
- <Link>Resources\dashboard-ui\bower_components\iron-dropdown\iron-dropdown.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-dropdown\demo\grow-height-animation.html">
- <Link>Resources\dashboard-ui\bower_components\iron-dropdown\demo\grow-height-animation.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-dropdown\demo\index.html">
- <Link>Resources\dashboard-ui\bower_components\iron-dropdown\demo\index.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-dropdown\demo\x-select.html">
- <Link>Resources\dashboard-ui\bower_components\iron-dropdown\demo\x-select.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-dropdown\test\index.html">
- <Link>Resources\dashboard-ui\bower_components\iron-dropdown\test\index.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-dropdown\test\iron-dropdown-scroll-manager.html">
- <Link>Resources\dashboard-ui\bower_components\iron-dropdown\test\iron-dropdown-scroll-manager.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-dropdown\test\iron-dropdown.html">
- <Link>Resources\dashboard-ui\bower_components\iron-dropdown\test\iron-dropdown.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-dropdown\test\x-scrollable-element.html">
- <Link>Resources\dashboard-ui\bower_components\iron-dropdown\test\x-scrollable-element.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-fit-behavior\.bower.json">
- <Link>Resources\dashboard-ui\bower_components\iron-fit-behavior\.bower.json</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-fit-behavior\.gitignore">
- <Link>Resources\dashboard-ui\bower_components\iron-fit-behavior\.gitignore</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-fit-behavior\.travis.yml">
- <Link>Resources\dashboard-ui\bower_components\iron-fit-behavior\.travis.yml</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-fit-behavior\CONTRIBUTING.md">
- <Link>Resources\dashboard-ui\bower_components\iron-fit-behavior\CONTRIBUTING.md</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-fit-behavior\README.md">
- <Link>Resources\dashboard-ui\bower_components\iron-fit-behavior\README.md</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-fit-behavior\bower.json">
- <Link>Resources\dashboard-ui\bower_components\iron-fit-behavior\bower.json</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-fit-behavior\index.html">
- <Link>Resources\dashboard-ui\bower_components\iron-fit-behavior\index.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-fit-behavior\iron-fit-behavior.html">
- <Link>Resources\dashboard-ui\bower_components\iron-fit-behavior\iron-fit-behavior.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-fit-behavior\demo\index.html">
- <Link>Resources\dashboard-ui\bower_components\iron-fit-behavior\demo\index.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-fit-behavior\demo\simple-fit.html">
- <Link>Resources\dashboard-ui\bower_components\iron-fit-behavior\demo\simple-fit.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-fit-behavior\test\index.html">
- <Link>Resources\dashboard-ui\bower_components\iron-fit-behavior\test\index.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-fit-behavior\test\iron-fit-behavior.html">
- <Link>Resources\dashboard-ui\bower_components\iron-fit-behavior\test\iron-fit-behavior.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-fit-behavior\test\test-fit.html">
- <Link>Resources\dashboard-ui\bower_components\iron-fit-behavior\test\test-fit.html</Link>
- </BundleResource>
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-flex-layout\.bower.json">
<Link>Resources\dashboard-ui\bower_components\iron-flex-layout\.bower.json</Link>
</BundleResource>
@@ -2510,6 +2829,9 @@
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-input\iron-input.html">
<Link>Resources\dashboard-ui\bower_components\iron-input\iron-input.html</Link>
</BundleResource>
+ <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-input\.github\ISSUE_TEMPLATE.md">
+ <Link>Resources\dashboard-ui\bower_components\iron-input\.github\ISSUE_TEMPLATE.md</Link>
+ </BundleResource>
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-input\demo\index.html">
<Link>Resources\dashboard-ui\bower_components\iron-input\demo\index.html</Link>
</BundleResource>
@@ -2525,41 +2847,62 @@
<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-media-query\.bower.json">
- <Link>Resources\dashboard-ui\bower_components\iron-media-query\.bower.json</Link>
+ <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-location\.bower.json">
+ <Link>Resources\dashboard-ui\bower_components\iron-location\.bower.json</Link>
+ </BundleResource>
+ <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-location\.gitignore">
+ <Link>Resources\dashboard-ui\bower_components\iron-location\.gitignore</Link>
+ </BundleResource>
+ <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-location\.travis.yml">
+ <Link>Resources\dashboard-ui\bower_components\iron-location\.travis.yml</Link>
+ </BundleResource>
+ <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-location\CONTRIBUTING.md">
+ <Link>Resources\dashboard-ui\bower_components\iron-location\CONTRIBUTING.md</Link>
+ </BundleResource>
+ <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-location\README.md">
+ <Link>Resources\dashboard-ui\bower_components\iron-location\README.md</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>
+ <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-location\index.html">
+ <Link>Resources\dashboard-ui\bower_components\iron-location\index.html</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 Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-location\iron-location.html">
+ <Link>Resources\dashboard-ui\bower_components\iron-location\iron-location.html</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 Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-location\iron-query-params.html">
+ <Link>Resources\dashboard-ui\bower_components\iron-location\iron-query-params.html</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 Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-location\.github\ISSUE_TEMPLATE.md">
+ <Link>Resources\dashboard-ui\bower_components\iron-location\.github\ISSUE_TEMPLATE.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 Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-location\demo\index.html">
+ <Link>Resources\dashboard-ui\bower_components\iron-location\demo\index.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 Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-location\demo\iron-query-params.html">
+ <Link>Resources\dashboard-ui\bower_components\iron-location\demo\iron-query-params.html</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 Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-location\test\index.html">
+ <Link>Resources\dashboard-ui\bower_components\iron-location\test\index.html</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 Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-location\test\initialization-cases.html">
+ <Link>Resources\dashboard-ui\bower_components\iron-location\test\initialization-cases.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 Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-location\test\initialization-iframe.html">
+ <Link>Resources\dashboard-ui\bower_components\iron-location\test\initialization-iframe.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 Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-location\test\initialization-tests.html">
+ <Link>Resources\dashboard-ui\bower_components\iron-location\test\initialization-tests.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 Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-location\test\iron-location.html">
+ <Link>Resources\dashboard-ui\bower_components\iron-location\test\iron-location.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 Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-location\test\iron-query-params.html">
+ <Link>Resources\dashboard-ui\bower_components\iron-location\test\iron-query-params.html</Link>
+ </BundleResource>
+ <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-menu-behavior\.bower.json">
<Link>Resources\dashboard-ui\bower_components\iron-menu-behavior\.bower.json</Link>
@@ -2654,60 +2997,6 @@
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-meta\test\iron-meta.html">
<Link>Resources\dashboard-ui\bower_components\iron-meta\test\iron-meta.html</Link>
</BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-overlay-behavior\.bower.json">
- <Link>Resources\dashboard-ui\bower_components\iron-overlay-behavior\.bower.json</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-overlay-behavior\.gitignore">
- <Link>Resources\dashboard-ui\bower_components\iron-overlay-behavior\.gitignore</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-overlay-behavior\.travis.yml">
- <Link>Resources\dashboard-ui\bower_components\iron-overlay-behavior\.travis.yml</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-overlay-behavior\CONTRIBUTING.md">
- <Link>Resources\dashboard-ui\bower_components\iron-overlay-behavior\CONTRIBUTING.md</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-overlay-behavior\README.md">
- <Link>Resources\dashboard-ui\bower_components\iron-overlay-behavior\README.md</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-overlay-behavior\bower.json">
- <Link>Resources\dashboard-ui\bower_components\iron-overlay-behavior\bower.json</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-overlay-behavior\index.html">
- <Link>Resources\dashboard-ui\bower_components\iron-overlay-behavior\index.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-overlay-behavior\iron-overlay-backdrop.html">
- <Link>Resources\dashboard-ui\bower_components\iron-overlay-behavior\iron-overlay-backdrop.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-overlay-behavior\iron-overlay-behavior.html">
- <Link>Resources\dashboard-ui\bower_components\iron-overlay-behavior\iron-overlay-behavior.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-overlay-behavior\iron-overlay-manager.html">
- <Link>Resources\dashboard-ui\bower_components\iron-overlay-behavior\iron-overlay-manager.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-overlay-behavior\.github\ISSUE_TEMPLATE.md">
- <Link>Resources\dashboard-ui\bower_components\iron-overlay-behavior\.github\ISSUE_TEMPLATE.md</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-overlay-behavior\demo\index.html">
- <Link>Resources\dashboard-ui\bower_components\iron-overlay-behavior\demo\index.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-overlay-behavior\demo\simple-overlay.html">
- <Link>Resources\dashboard-ui\bower_components\iron-overlay-behavior\demo\simple-overlay.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-overlay-behavior\test\index.html">
- <Link>Resources\dashboard-ui\bower_components\iron-overlay-behavior\test\index.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-overlay-behavior\test\iron-overlay-behavior.html">
- <Link>Resources\dashboard-ui\bower_components\iron-overlay-behavior\test\iron-overlay-behavior.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-overlay-behavior\test\test-buttons.html">
- <Link>Resources\dashboard-ui\bower_components\iron-overlay-behavior\test\test-buttons.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-overlay-behavior\test\test-overlay.html">
- <Link>Resources\dashboard-ui\bower_components\iron-overlay-behavior\test\test-overlay.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-overlay-behavior\test\test-overlay2.html">
- <Link>Resources\dashboard-ui\bower_components\iron-overlay-behavior\test\test-overlay2.html</Link>
- </BundleResource>
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-pages\.bower.json">
<Link>Resources\dashboard-ui\bower_components\iron-pages\.bower.json</Link>
</BundleResource>
@@ -2753,6 +3042,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>
@@ -2765,6 +3060,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>
@@ -2819,6 +3117,93 @@
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-resizable-behavior\test\test-elements.html">
<Link>Resources\dashboard-ui\bower_components\iron-resizable-behavior\test\test-elements.html</Link>
</BundleResource>
+ <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-scroll-target-behavior\.bower.json">
+ <Link>Resources\dashboard-ui\bower_components\iron-scroll-target-behavior\.bower.json</Link>
+ </BundleResource>
+ <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-scroll-target-behavior\.gitignore">
+ <Link>Resources\dashboard-ui\bower_components\iron-scroll-target-behavior\.gitignore</Link>
+ </BundleResource>
+ <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-scroll-target-behavior\.travis.yml">
+ <Link>Resources\dashboard-ui\bower_components\iron-scroll-target-behavior\.travis.yml</Link>
+ </BundleResource>
+ <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-scroll-target-behavior\CONTRIBUTING.md">
+ <Link>Resources\dashboard-ui\bower_components\iron-scroll-target-behavior\CONTRIBUTING.md</Link>
+ </BundleResource>
+ <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-scroll-target-behavior\README.md">
+ <Link>Resources\dashboard-ui\bower_components\iron-scroll-target-behavior\README.md</Link>
+ </BundleResource>
+ <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-scroll-target-behavior\bower.json">
+ <Link>Resources\dashboard-ui\bower_components\iron-scroll-target-behavior\bower.json</Link>
+ </BundleResource>
+ <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-scroll-target-behavior\index.html">
+ <Link>Resources\dashboard-ui\bower_components\iron-scroll-target-behavior\index.html</Link>
+ </BundleResource>
+ <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-scroll-target-behavior\iron-scroll-target-behavior.html">
+ <Link>Resources\dashboard-ui\bower_components\iron-scroll-target-behavior\iron-scroll-target-behavior.html</Link>
+ </BundleResource>
+ <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-scroll-target-behavior\.github\ISSUE_TEMPLATE.md">
+ <Link>Resources\dashboard-ui\bower_components\iron-scroll-target-behavior\.github\ISSUE_TEMPLATE.md</Link>
+ </BundleResource>
+ <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-scroll-target-behavior\demo\document.html">
+ <Link>Resources\dashboard-ui\bower_components\iron-scroll-target-behavior\demo\document.html</Link>
+ </BundleResource>
+ <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-scroll-target-behavior\demo\scrolling-region.html">
+ <Link>Resources\dashboard-ui\bower_components\iron-scroll-target-behavior\demo\scrolling-region.html</Link>
+ </BundleResource>
+ <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-scroll-target-behavior\demo\x-scrollable.html">
+ <Link>Resources\dashboard-ui\bower_components\iron-scroll-target-behavior\demo\x-scrollable.html</Link>
+ </BundleResource>
+ <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-scroll-target-behavior\test\basic.html">
+ <Link>Resources\dashboard-ui\bower_components\iron-scroll-target-behavior\test\basic.html</Link>
+ </BundleResource>
+ <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-scroll-target-behavior\test\index.html">
+ <Link>Resources\dashboard-ui\bower_components\iron-scroll-target-behavior\test\index.html</Link>
+ </BundleResource>
+ <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-scroll-target-behavior\test\x-nested-scrollable.html">
+ <Link>Resources\dashboard-ui\bower_components\iron-scroll-target-behavior\test\x-nested-scrollable.html</Link>
+ </BundleResource>
+ <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-scroll-target-behavior\test\x-scrollable.html">
+ <Link>Resources\dashboard-ui\bower_components\iron-scroll-target-behavior\test\x-scrollable.html</Link>
+ </BundleResource>
+ <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-scroll-threshold\.bower.json">
+ <Link>Resources\dashboard-ui\bower_components\iron-scroll-threshold\.bower.json</Link>
+ </BundleResource>
+ <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-scroll-threshold\.gitignore">
+ <Link>Resources\dashboard-ui\bower_components\iron-scroll-threshold\.gitignore</Link>
+ </BundleResource>
+ <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-scroll-threshold\CONTRIBUTING.md">
+ <Link>Resources\dashboard-ui\bower_components\iron-scroll-threshold\CONTRIBUTING.md</Link>
+ </BundleResource>
+ <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-scroll-threshold\README.md">
+ <Link>Resources\dashboard-ui\bower_components\iron-scroll-threshold\README.md</Link>
+ </BundleResource>
+ <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-scroll-threshold\bower.json">
+ <Link>Resources\dashboard-ui\bower_components\iron-scroll-threshold\bower.json</Link>
+ </BundleResource>
+ <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-scroll-threshold\index.html">
+ <Link>Resources\dashboard-ui\bower_components\iron-scroll-threshold\index.html</Link>
+ </BundleResource>
+ <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-scroll-threshold\iron-scroll-threshold.html">
+ <Link>Resources\dashboard-ui\bower_components\iron-scroll-threshold\iron-scroll-threshold.html</Link>
+ </BundleResource>
+ <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-scroll-threshold\demo\document.html">
+ <Link>Resources\dashboard-ui\bower_components\iron-scroll-threshold\demo\document.html</Link>
+ </BundleResource>
+ <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-scroll-threshold\demo\sample-content.html">
+ <Link>Resources\dashboard-ui\bower_components\iron-scroll-threshold\demo\sample-content.html</Link>
+ </BundleResource>
+ <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-scroll-threshold\demo\scrolling-region-decoupled.html">
+ <Link>Resources\dashboard-ui\bower_components\iron-scroll-threshold\demo\scrolling-region-decoupled.html</Link>
+ </BundleResource>
+ <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-scroll-threshold\demo\scrolling-region.html">
+ <Link>Resources\dashboard-ui\bower_components\iron-scroll-threshold\demo\scrolling-region.html</Link>
+ </BundleResource>
+ <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-scroll-threshold\test\basic.html">
+ <Link>Resources\dashboard-ui\bower_components\iron-scroll-threshold\test\basic.html</Link>
+ </BundleResource>
+ <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-scroll-threshold\test\index.html">
+ <Link>Resources\dashboard-ui\bower_components\iron-scroll-threshold\test\index.html</Link>
+ </BundleResource>
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-selector\.bower.json">
<Link>Resources\dashboard-ui\bower_components\iron-selector\.bower.json</Link>
</BundleResource>
@@ -2852,6 +3237,9 @@
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-selector\iron-selector.html">
<Link>Resources\dashboard-ui\bower_components\iron-selector\iron-selector.html</Link>
</BundleResource>
+ <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-selector\.github\ISSUE_TEMPLATE.md">
+ <Link>Resources\dashboard-ui\bower_components\iron-selector\.github\ISSUE_TEMPLATE.md</Link>
+ </BundleResource>
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-selector\demo\index.html">
<Link>Resources\dashboard-ui\bower_components\iron-selector\demo\index.html</Link>
</BundleResource>
@@ -2900,6 +3288,12 @@
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-validatable-behavior\.gitignore">
<Link>Resources\dashboard-ui\bower_components\iron-validatable-behavior\.gitignore</Link>
</BundleResource>
+ <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-validatable-behavior\.travis.yml">
+ <Link>Resources\dashboard-ui\bower_components\iron-validatable-behavior\.travis.yml</Link>
+ </BundleResource>
+ <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-validatable-behavior\CONTRIBUTING.md">
+ <Link>Resources\dashboard-ui\bower_components\iron-validatable-behavior\CONTRIBUTING.md</Link>
+ </BundleResource>
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-validatable-behavior\README.md">
<Link>Resources\dashboard-ui\bower_components\iron-validatable-behavior\README.md</Link>
</BundleResource>
@@ -2912,6 +3306,9 @@
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-validatable-behavior\iron-validatable-behavior.html">
<Link>Resources\dashboard-ui\bower_components\iron-validatable-behavior\iron-validatable-behavior.html</Link>
</BundleResource>
+ <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-validatable-behavior\.github\ISSUE_TEMPLATE.md">
+ <Link>Resources\dashboard-ui\bower_components\iron-validatable-behavior\.github\ISSUE_TEMPLATE.md</Link>
+ </BundleResource>
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-validatable-behavior\demo\cats-only.html">
<Link>Resources\dashboard-ui\bower_components\iron-validatable-behavior\demo\cats-only.html</Link>
</BundleResource>
@@ -2921,6 +3318,12 @@
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-validatable-behavior\demo\validatable-input.html">
<Link>Resources\dashboard-ui\bower_components\iron-validatable-behavior\demo\validatable-input.html</Link>
</BundleResource>
+ <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-validatable-behavior\test\cats-only.html">
+ <Link>Resources\dashboard-ui\bower_components\iron-validatable-behavior\test\cats-only.html</Link>
+ </BundleResource>
+ <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-validatable-behavior\test\dogs-only.html">
+ <Link>Resources\dashboard-ui\bower_components\iron-validatable-behavior\test\dogs-only.html</Link>
+ </BundleResource>
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\iron-validatable-behavior\test\index.html">
<Link>Resources\dashboard-ui\bower_components\iron-validatable-behavior\test\index.html</Link>
</BundleResource>
@@ -2963,6 +3366,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>
@@ -2981,17 +3387,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>
@@ -3029,9 +3435,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>
@@ -3041,9 +3444,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>
@@ -3059,9 +3459,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>
@@ -3074,9 +3471,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>
@@ -3113,6 +3507,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>
@@ -3128,9 +3525,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>
@@ -3143,12 +3537,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>
@@ -3158,9 +3546,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>
@@ -3188,9 +3573,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>
@@ -3347,6 +3729,36 @@
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\jstree\src\themes\default-dark\throbber.gif">
<Link>Resources\dashboard-ui\bower_components\jstree\src\themes\default-dark\throbber.gif</Link>
</BundleResource>
+ <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\libjass\.bower.json">
+ <Link>Resources\dashboard-ui\bower_components\libjass\.bower.json</Link>
+ </BundleResource>
+ <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\libjass\CHANGELOG.md">
+ <Link>Resources\dashboard-ui\bower_components\libjass\CHANGELOG.md</Link>
+ </BundleResource>
+ <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\libjass\LICENSE">
+ <Link>Resources\dashboard-ui\bower_components\libjass\LICENSE</Link>
+ </BundleResource>
+ <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\libjass\README.md">
+ <Link>Resources\dashboard-ui\bower_components\libjass\README.md</Link>
+ </BundleResource>
+ <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\libjass\libjass.css">
+ <Link>Resources\dashboard-ui\bower_components\libjass\libjass.css</Link>
+ </BundleResource>
+ <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\libjass\libjass.js">
+ <Link>Resources\dashboard-ui\bower_components\libjass\libjass.js</Link>
+ </BundleResource>
+ <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\libjass\libjass.js.map">
+ <Link>Resources\dashboard-ui\bower_components\libjass\libjass.js.map</Link>
+ </BundleResource>
+ <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\libjass\libjass.min.js">
+ <Link>Resources\dashboard-ui\bower_components\libjass\libjass.min.js</Link>
+ </BundleResource>
+ <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\libjass\libjass.min.js.map">
+ <Link>Resources\dashboard-ui\bower_components\libjass\libjass.min.js.map</Link>
+ </BundleResource>
+ <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\libjass\package.json">
+ <Link>Resources\dashboard-ui\bower_components\libjass\package.json</Link>
+ </BundleResource>
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\marked\.bower.json">
<Link>Resources\dashboard-ui\bower_components\marked\.bower.json</Link>
</BundleResource>
@@ -3428,660 +3840,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>
@@ -4157,6 +3915,9 @@
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\neon-animation\web-animations.html">
<Link>Resources\dashboard-ui\bower_components\neon-animation\web-animations.html</Link>
</BundleResource>
+ <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\neon-animation\.github\ISSUE_TEMPLATE.md">
+ <Link>Resources\dashboard-ui\bower_components\neon-animation\.github\ISSUE_TEMPLATE.md</Link>
+ </BundleResource>
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\neon-animation\animations\cascaded-animation.html">
<Link>Resources\dashboard-ui\bower_components\neon-animation\animations\cascaded-animation.html</Link>
</BundleResource>
@@ -4301,6 +4062,12 @@
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\neon-animation\test\index.html">
<Link>Resources\dashboard-ui\bower_components\neon-animation\test\index.html</Link>
</BundleResource>
+ <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\neon-animation\test\neon-animated-pages-descendant-selection.html">
+ <Link>Resources\dashboard-ui\bower_components\neon-animation\test\neon-animated-pages-descendant-selection.html</Link>
+ </BundleResource>
+ <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\neon-animation\test\neon-animated-pages-lazy.html">
+ <Link>Resources\dashboard-ui\bower_components\neon-animation\test\neon-animated-pages-lazy.html</Link>
+ </BundleResource>
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\neon-animation\test\neon-animated-pages.html">
<Link>Resources\dashboard-ui\bower_components\neon-animation\test\neon-animated-pages.html</Link>
</BundleResource>
@@ -4397,6 +4164,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>
@@ -4442,195 +4212,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-dialog\.bower.json">
- <Link>Resources\dashboard-ui\bower_components\paper-dialog\.bower.json</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\paper-dialog\.gitignore">
- <Link>Resources\dashboard-ui\bower_components\paper-dialog\.gitignore</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\paper-dialog\.travis.yml">
- <Link>Resources\dashboard-ui\bower_components\paper-dialog\.travis.yml</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\paper-dialog\CONTRIBUTING.md">
- <Link>Resources\dashboard-ui\bower_components\paper-dialog\CONTRIBUTING.md</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\paper-dialog\README.md">
- <Link>Resources\dashboard-ui\bower_components\paper-dialog\README.md</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\paper-dialog\bower.json">
- <Link>Resources\dashboard-ui\bower_components\paper-dialog\bower.json</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\paper-dialog\hero.svg">
- <Link>Resources\dashboard-ui\bower_components\paper-dialog\hero.svg</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\paper-dialog\index.html">
- <Link>Resources\dashboard-ui\bower_components\paper-dialog\index.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\paper-dialog\paper-dialog.html">
- <Link>Resources\dashboard-ui\bower_components\paper-dialog\paper-dialog.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\paper-dialog\demo\index.html">
- <Link>Resources\dashboard-ui\bower_components\paper-dialog\demo\index.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\paper-dialog\test\index.html">
- <Link>Resources\dashboard-ui\bower_components\paper-dialog\test\index.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\paper-dialog\test\paper-dialog.html">
- <Link>Resources\dashboard-ui\bower_components\paper-dialog\test\paper-dialog.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\paper-dialog-behavior\.bower.json">
- <Link>Resources\dashboard-ui\bower_components\paper-dialog-behavior\.bower.json</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\paper-dialog-behavior\.gitignore">
- <Link>Resources\dashboard-ui\bower_components\paper-dialog-behavior\.gitignore</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\paper-dialog-behavior\.travis.yml">
- <Link>Resources\dashboard-ui\bower_components\paper-dialog-behavior\.travis.yml</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\paper-dialog-behavior\CONTRIBUTING.md">
- <Link>Resources\dashboard-ui\bower_components\paper-dialog-behavior\CONTRIBUTING.md</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\paper-dialog-behavior\README.md">
- <Link>Resources\dashboard-ui\bower_components\paper-dialog-behavior\README.md</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\paper-dialog-behavior\bower.json">
- <Link>Resources\dashboard-ui\bower_components\paper-dialog-behavior\bower.json</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\paper-dialog-behavior\hero.svg">
- <Link>Resources\dashboard-ui\bower_components\paper-dialog-behavior\hero.svg</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\paper-dialog-behavior\index.html">
- <Link>Resources\dashboard-ui\bower_components\paper-dialog-behavior\index.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\paper-dialog-behavior\paper-dialog-behavior.html">
- <Link>Resources\dashboard-ui\bower_components\paper-dialog-behavior\paper-dialog-behavior.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\paper-dialog-behavior\paper-dialog-common.css">
- <Link>Resources\dashboard-ui\bower_components\paper-dialog-behavior\paper-dialog-common.css</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\paper-dialog-behavior\paper-dialog-shared-styles.html">
- <Link>Resources\dashboard-ui\bower_components\paper-dialog-behavior\paper-dialog-shared-styles.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\paper-dialog-behavior\.github\ISSUE_TEMPLATE.md">
- <Link>Resources\dashboard-ui\bower_components\paper-dialog-behavior\.github\ISSUE_TEMPLATE.md</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\paper-dialog-behavior\demo\index.html">
- <Link>Resources\dashboard-ui\bower_components\paper-dialog-behavior\demo\index.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\paper-dialog-behavior\demo\simple-dialog.html">
- <Link>Resources\dashboard-ui\bower_components\paper-dialog-behavior\demo\simple-dialog.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\paper-dialog-behavior\test\index.html">
- <Link>Resources\dashboard-ui\bower_components\paper-dialog-behavior\test\index.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\paper-dialog-behavior\test\paper-dialog-behavior.html">
- <Link>Resources\dashboard-ui\bower_components\paper-dialog-behavior\test\paper-dialog-behavior.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\paper-dialog-behavior\test\test-buttons.html">
- <Link>Resources\dashboard-ui\bower_components\paper-dialog-behavior\test\test-buttons.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\paper-dialog-behavior\test\test-dialog.html">
- <Link>Resources\dashboard-ui\bower_components\paper-dialog-behavior\test\test-dialog.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\paper-dialog-scrollable\.bower.json">
- <Link>Resources\dashboard-ui\bower_components\paper-dialog-scrollable\.bower.json</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\paper-dialog-scrollable\.gitignore">
- <Link>Resources\dashboard-ui\bower_components\paper-dialog-scrollable\.gitignore</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\paper-dialog-scrollable\README.md">
- <Link>Resources\dashboard-ui\bower_components\paper-dialog-scrollable\README.md</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\paper-dialog-scrollable\bower.json">
- <Link>Resources\dashboard-ui\bower_components\paper-dialog-scrollable\bower.json</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\paper-dialog-scrollable\hero.svg">
- <Link>Resources\dashboard-ui\bower_components\paper-dialog-scrollable\hero.svg</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\paper-dialog-scrollable\index.html">
- <Link>Resources\dashboard-ui\bower_components\paper-dialog-scrollable\index.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\paper-dialog-scrollable\paper-dialog-scrollable.html">
- <Link>Resources\dashboard-ui\bower_components\paper-dialog-scrollable\paper-dialog-scrollable.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\paper-dialog-scrollable\demo\index.html">
- <Link>Resources\dashboard-ui\bower_components\paper-dialog-scrollable\demo\index.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\paper-dialog-scrollable\test\index.html">
- <Link>Resources\dashboard-ui\bower_components\paper-dialog-scrollable\test\index.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\paper-dialog-scrollable\test\paper-dialog-scrollable.html">
- <Link>Resources\dashboard-ui\bower_components\paper-dialog-scrollable\test\paper-dialog-scrollable.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-dropdown-menu\.bower.json">
- <Link>Resources\dashboard-ui\bower_components\paper-dropdown-menu\.bower.json</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\paper-dropdown-menu\.gitignore">
- <Link>Resources\dashboard-ui\bower_components\paper-dropdown-menu\.gitignore</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\paper-dropdown-menu\README.md">
- <Link>Resources\dashboard-ui\bower_components\paper-dropdown-menu\README.md</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\paper-dropdown-menu\bower.json">
- <Link>Resources\dashboard-ui\bower_components\paper-dropdown-menu\bower.json</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\paper-dropdown-menu\index.html">
- <Link>Resources\dashboard-ui\bower_components\paper-dropdown-menu\index.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\paper-dropdown-menu\paper-dropdown-menu.html">
- <Link>Resources\dashboard-ui\bower_components\paper-dropdown-menu\paper-dropdown-menu.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\paper-dropdown-menu\demo\index.html">
- <Link>Resources\dashboard-ui\bower_components\paper-dropdown-menu\demo\index.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\paper-dropdown-menu\test\index.html">
- <Link>Resources\dashboard-ui\bower_components\paper-dropdown-menu\test\index.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\paper-dropdown-menu\test\paper-dropdown-menu.html">
- <Link>Resources\dashboard-ui\bower_components\paper-dropdown-menu\test\paper-dropdown-menu.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>
@@ -4682,12 +4263,21 @@
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\paper-icon-button\index.html">
<Link>Resources\dashboard-ui\bower_components\paper-icon-button\index.html</Link>
</BundleResource>
+ <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\paper-icon-button\paper-icon-button-light.html">
+ <Link>Resources\dashboard-ui\bower_components\paper-icon-button\paper-icon-button-light.html</Link>
+ </BundleResource>
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\paper-icon-button\paper-icon-button.html">
<Link>Resources\dashboard-ui\bower_components\paper-icon-button\paper-icon-button.html</Link>
</BundleResource>
+ <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\paper-icon-button\.github\ISSUE_TEMPLATE.md">
+ <Link>Resources\dashboard-ui\bower_components\paper-icon-button\.github\ISSUE_TEMPLATE.md</Link>
+ </BundleResource>
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\paper-icon-button\demo\index.html">
<Link>Resources\dashboard-ui\bower_components\paper-icon-button\demo\index.html</Link>
</BundleResource>
+ <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\paper-icon-button\demo\paper-icon-button-light.html">
+ <Link>Resources\dashboard-ui\bower_components\paper-icon-button\demo\paper-icon-button-light.html</Link>
+ </BundleResource>
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\paper-icon-button\test\a11y.html">
<Link>Resources\dashboard-ui\bower_components\paper-icon-button\test\a11y.html</Link>
</BundleResource>
@@ -4745,6 +4335,9 @@
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\paper-input\paper-textarea.html">
<Link>Resources\dashboard-ui\bower_components\paper-input\paper-textarea.html</Link>
</BundleResource>
+ <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\paper-input\.github\ISSUE_TEMPLATE.md">
+ <Link>Resources\dashboard-ui\bower_components\paper-input\.github\ISSUE_TEMPLATE.md</Link>
+ </BundleResource>
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\paper-input\demo\index.html">
<Link>Resources\dashboard-ui\bower_components\paper-input\demo\index.html</Link>
</BundleResource>
@@ -4814,6 +4407,9 @@
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\paper-item\paper-item.html">
<Link>Resources\dashboard-ui\bower_components\paper-item\paper-item.html</Link>
</BundleResource>
+ <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\paper-item\.github\ISSUE_TEMPLATE.md">
+ <Link>Resources\dashboard-ui\bower_components\paper-item\.github\ISSUE_TEMPLATE.md</Link>
+ </BundleResource>
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\paper-item\demo\index.html">
<Link>Resources\dashboard-ui\bower_components\paper-item\demo\index.html</Link>
</BundleResource>
@@ -4889,45 +4485,6 @@
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\paper-menu\test\paper-menu.html">
<Link>Resources\dashboard-ui\bower_components\paper-menu\test\paper-menu.html</Link>
</BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\paper-menu-button\.bower.json">
- <Link>Resources\dashboard-ui\bower_components\paper-menu-button\.bower.json</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\paper-menu-button\.gitignore">
- <Link>Resources\dashboard-ui\bower_components\paper-menu-button\.gitignore</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\paper-menu-button\.travis.yml">
- <Link>Resources\dashboard-ui\bower_components\paper-menu-button\.travis.yml</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\paper-menu-button\CONTRIBUTING.md">
- <Link>Resources\dashboard-ui\bower_components\paper-menu-button\CONTRIBUTING.md</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\paper-menu-button\README.md">
- <Link>Resources\dashboard-ui\bower_components\paper-menu-button\README.md</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\paper-menu-button\bower.json">
- <Link>Resources\dashboard-ui\bower_components\paper-menu-button\bower.json</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\paper-menu-button\hero.svg">
- <Link>Resources\dashboard-ui\bower_components\paper-menu-button\hero.svg</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\paper-menu-button\index.html">
- <Link>Resources\dashboard-ui\bower_components\paper-menu-button\index.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\paper-menu-button\paper-menu-button-animations.html">
- <Link>Resources\dashboard-ui\bower_components\paper-menu-button\paper-menu-button-animations.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\paper-menu-button\paper-menu-button.html">
- <Link>Resources\dashboard-ui\bower_components\paper-menu-button\paper-menu-button.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\paper-menu-button\demo\index.html">
- <Link>Resources\dashboard-ui\bower_components\paper-menu-button\demo\index.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\paper-menu-button\test\index.html">
- <Link>Resources\dashboard-ui\bower_components\paper-menu-button\test\index.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\paper-menu-button\test\paper-menu-button.html">
- <Link>Resources\dashboard-ui\bower_components\paper-menu-button\test\paper-menu-button.html</Link>
- </BundleResource>
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\paper-progress\.bower.json">
<Link>Resources\dashboard-ui\bower_components\paper-progress\.bower.json</Link>
</BundleResource>
@@ -5063,6 +4620,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>
@@ -5072,45 +4632,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>
@@ -5255,30 +4776,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>
@@ -5447,6 +4944,12 @@
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\components\prism-brainfuck.min.js">
<Link>Resources\dashboard-ui\bower_components\prism\components\prism-brainfuck.min.js</Link>
</BundleResource>
+ <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\components\prism-bro.js">
+ <Link>Resources\dashboard-ui\bower_components\prism\components\prism-bro.js</Link>
+ </BundleResource>
+ <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\components\prism-bro.min.js">
+ <Link>Resources\dashboard-ui\bower_components\prism\components\prism-bro.min.js</Link>
+ </BundleResource>
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\components\prism-c.js">
<Link>Resources\dashboard-ui\bower_components\prism\components\prism-c.js</Link>
</BundleResource>
@@ -5855,6 +5358,12 @@
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\components\prism-prolog.min.js">
<Link>Resources\dashboard-ui\bower_components\prism\components\prism-prolog.min.js</Link>
</BundleResource>
+ <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\components\prism-protobuf.js">
+ <Link>Resources\dashboard-ui\bower_components\prism\components\prism-protobuf.js</Link>
+ </BundleResource>
+ <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\components\prism-protobuf.min.js">
+ <Link>Resources\dashboard-ui\bower_components\prism\components\prism-protobuf.min.js</Link>
+ </BundleResource>
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\components\prism-puppet.js">
<Link>Resources\dashboard-ui\bower_components\prism\components\prism-puppet.js</Link>
</BundleResource>
@@ -6110,6 +5619,12 @@
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\plugins\line-numbers\prism-line-numbers.min.js">
<Link>Resources\dashboard-ui\bower_components\prism\plugins\line-numbers\prism-line-numbers.min.js</Link>
</BundleResource>
+ <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\plugins\normalize-whitespace\prism-normalize-whitespace.js">
+ <Link>Resources\dashboard-ui\bower_components\prism\plugins\normalize-whitespace\prism-normalize-whitespace.js</Link>
+ </BundleResource>
+ <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\plugins\normalize-whitespace\prism-normalize-whitespace.min.js">
+ <Link>Resources\dashboard-ui\bower_components\prism\plugins\normalize-whitespace\prism-normalize-whitespace.min.js</Link>
+ </BundleResource>
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\plugins\previewer-angle\prism-previewer-angle.css">
<Link>Resources\dashboard-ui\bower_components\prism\plugins\previewer-angle\prism-previewer-angle.css</Link>
</BundleResource>
@@ -6188,6 +5703,15 @@
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\plugins\show-language\prism-show-language.min.js">
<Link>Resources\dashboard-ui\bower_components\prism\plugins\show-language\prism-show-language.min.js</Link>
</BundleResource>
+ <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\plugins\unescaped-markup\prism-unescaped-markup.css">
+ <Link>Resources\dashboard-ui\bower_components\prism\plugins\unescaped-markup\prism-unescaped-markup.css</Link>
+ </BundleResource>
+ <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\plugins\unescaped-markup\prism-unescaped-markup.js">
+ <Link>Resources\dashboard-ui\bower_components\prism\plugins\unescaped-markup\prism-unescaped-markup.js</Link>
+ </BundleResource>
+ <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\plugins\unescaped-markup\prism-unescaped-markup.min.js">
+ <Link>Resources\dashboard-ui\bower_components\prism\plugins\unescaped-markup\prism-unescaped-markup.min.js</Link>
+ </BundleResource>
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\plugins\wpd\prism-wpd.css">
<Link>Resources\dashboard-ui\bower_components\prism\plugins\wpd\prism-wpd.css</Link>
</BundleResource>
@@ -6197,2445 +5721,6 @@
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\plugins\wpd\prism-wpd.min.js">
<Link>Resources\dashboard-ui\bower_components\prism\plugins\wpd\prism-wpd.min.js</Link>
</BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\run-child.js">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\run-child.js</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\run.js">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\run.js</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\testrunner-tests.js">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\testrunner-tests.js</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\helper\components.js">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\helper\components.js</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\helper\prism-loader.js">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\helper\prism-loader.js</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\helper\test-case.js">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\helper\test-case.js</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\helper\test-discovery.js">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\helper\test-discovery.js</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\helper\token-stream-transformer.js">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\helper\token-stream-transformer.js</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\abap\comment_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\abap\comment_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\abap\eol-comment_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\abap\eol-comment_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\abap\keyword_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\abap\keyword_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\abap\number_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\abap\number_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\abap\operator_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\abap\operator_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\abap\string-template_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\abap\string-template_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\abap\string_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\abap\string_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\actionscript\keyword_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\actionscript\keyword_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\actionscript\operator_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\actionscript\operator_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\apacheconf\comment_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\apacheconf\comment_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\apacheconf\directive-block_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\apacheconf\directive-block_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\apacheconf\directive-flags_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\apacheconf\directive-flags_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\apacheconf\directive-inline_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\apacheconf\directive-inline_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\apacheconf\regex_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\apacheconf\regex_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\apacheconf\string_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\apacheconf\string_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\apacheconf\variable_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\apacheconf\variable_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\apl\assignment_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\apl\assignment_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\apl\comment_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\apl\comment_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\apl\constant_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\apl\constant_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\apl\dfn_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\apl\dfn_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\apl\dyadic-operator_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\apl\dyadic-operator_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\apl\function_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\apl\function_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\apl\monadic-operator_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\apl\monadic-operator_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\apl\number_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\apl\number_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\apl\statement_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\apl\statement_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\apl\string_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\apl\string_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\apl\system-function_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\apl\system-function_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\applescript\class_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\applescript\class_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\applescript\comment_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\applescript\comment_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\applescript\keyword_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\applescript\keyword_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\applescript\number_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\applescript\number_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\applescript\operator_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\applescript\operator_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\applescript\string_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\applescript\string_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\asciidoc\admonition_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\asciidoc\admonition_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\asciidoc\attribute-entry_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\asciidoc\attribute-entry_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\asciidoc\attributes_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\asciidoc\attributes_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\asciidoc\callout_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\asciidoc\callout_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\asciidoc\comment-block_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\asciidoc\comment-block_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\asciidoc\comment_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\asciidoc\comment_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\asciidoc\entity_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\asciidoc\entity_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\asciidoc\hr_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\asciidoc\hr_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\asciidoc\indented-block_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\asciidoc\indented-block_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\asciidoc\inline_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\asciidoc\inline_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\asciidoc\line-continuation_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\asciidoc\line-continuation_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\asciidoc\list-label_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\asciidoc\list-label_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\asciidoc\list-punctuation_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\asciidoc\list-punctuation_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\asciidoc\literal-block_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\asciidoc\literal-block_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\asciidoc\macro_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\asciidoc\macro_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\asciidoc\other-block_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\asciidoc\other-block_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\asciidoc\page-break_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\asciidoc\page-break_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\asciidoc\passthrough-block_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\asciidoc\passthrough-block_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\asciidoc\replacement_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\asciidoc\replacement_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\asciidoc\table_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\asciidoc\table_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\asciidoc\title_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\asciidoc\title_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\aspnet\comment_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\aspnet\comment_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\aspnet\page-directive_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\aspnet\page-directive_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\autohotkey\boolean_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\autohotkey\boolean_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\autohotkey\builtin_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\autohotkey\builtin_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\autohotkey\comment_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\autohotkey\comment_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\autohotkey\constant_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\autohotkey\constant_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\autohotkey\function_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\autohotkey\function_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\autohotkey\important_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\autohotkey\important_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\autohotkey\keyword_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\autohotkey\keyword_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\autohotkey\number_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\autohotkey\number_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\autohotkey\operator_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\autohotkey\operator_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\autohotkey\selector_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\autohotkey\selector_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\autohotkey\string_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\autohotkey\string_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\autohotkey\symbol_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\autohotkey\symbol_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\autohotkey\tag_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\autohotkey\tag_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\autohotkey\variable_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\autohotkey\variable_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\autoit\boolean_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\autoit\boolean_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\autoit\comment_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\autoit\comment_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\autoit\directive_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\autoit\directive_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\autoit\function_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\autoit\function_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\autoit\keyword_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\autoit\keyword_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\autoit\number_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\autoit\number_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\autoit\operator_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\autoit\operator_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\autoit\string_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\autoit\string_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\autoit\url_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\autoit\url_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\autoit\variable_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\autoit\variable_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\bash\arithmetic_environment_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\bash\arithmetic_environment_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\bash\command_substitution_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\bash\command_substitution_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\bash\comment_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\bash\comment_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\bash\function_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\bash\function_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\bash\keyword_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\bash\keyword_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\bash\shebang_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\bash\shebang_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\bash\string_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\bash\string_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\bash\variable_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\bash\variable_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\basic\comment_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\basic\comment_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\basic\function_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\basic\function_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\basic\keyword_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\basic\keyword_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\basic\number_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\basic\number_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\basic\operator_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\basic\operator_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\basic\string_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\basic\string_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\batch\command_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\batch\command_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\batch\comment_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\batch\comment_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\batch\label_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\batch\label_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\bison\c_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\bison\c_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\bison\comment_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\bison\comment_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\bison\keyword_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\bison\keyword_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\bison\number_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\bison\number_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\bison\property_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\bison\property_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\bison\string_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\bison\string_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\brainfuck\all_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\brainfuck\all_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\c\keyword_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\c\keyword_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\c\macro_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\c\macro_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\c\number_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\c\number_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\c\operator_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\c\operator_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\c+pure\c_inclusion.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\c+pure\c_inclusion.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\clike\boolean_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\clike\boolean_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\clike\class-name_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\clike\class-name_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\clike\comment_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\clike\comment_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\clike\function_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\clike\function_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\clike\keyword_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\clike\keyword_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\clike\number_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\clike\number_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\clike\operator_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\clike\operator_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\clike\string_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\clike\string_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\coffeescript\block-regex_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\coffeescript\block-regex_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\coffeescript\class-member_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\coffeescript\class-member_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\coffeescript\comment_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\coffeescript\comment_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\coffeescript\inline-javascript_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\coffeescript\inline-javascript_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\coffeescript\keyword_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\coffeescript\keyword_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\coffeescript\property_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\coffeescript\property_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\coffeescript\string_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\coffeescript\string_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\coffeescript+haml\coffeescript_inclusion.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\coffeescript+haml\coffeescript_inclusion.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\coffeescript+jade\coffeescript_inclusion.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\coffeescript+jade\coffeescript_inclusion.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\cpp\boolean_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\cpp\boolean_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\cpp\class-name_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\cpp\class-name_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\cpp\keyword_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\cpp\keyword_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\cpp+pure\cpp_inclusion.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\cpp+pure\cpp_inclusion.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\crystal\attribute_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\crystal\attribute_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\crystal\expansion_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\crystal\expansion_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\crystal\keyword_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\crystal\keyword_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\crystal\number_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\crystal\number_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\csharp\issue806.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\csharp\issue806.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\csharp\keyword_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\csharp\keyword_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\csharp\number_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\csharp\number_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\csharp\preprocessor_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\csharp\preprocessor_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\csharp\string_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\csharp\string_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\csharp+aspnet\directive_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\csharp+aspnet\directive_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\css\atrule_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\css\atrule_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\css\comment_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\css\comment_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\css\function_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\css\function_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\css\important_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\css\important_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\css\property_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\css\property_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\css\selector_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\css\selector_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\css\string_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\css\string_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\css\url_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\css\url_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\css!+css-extras\entity_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\css!+css-extras\entity_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\css!+css-extras\hexcode_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\css!+css-extras\hexcode_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\css!+css-extras\number_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\css!+css-extras\number_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\css!+css-extras\selector_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\css!+css-extras\selector_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\css+haml\css+haml_usage.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\css+haml\css+haml_usage.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\css+textile\css_inclusion.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\css+textile\css_inclusion.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\d\comment_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\d\comment_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\d\keyword_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\d\keyword_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\d\number_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\d\number_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\d\operator_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\d\operator_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\d\property_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\d\property_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\d\register_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\d\register_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\d\string_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\d\string_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\d\token-string_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\d\token-string_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\dart\keyword_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\dart\keyword_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\dart\metadata_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\dart\metadata_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\dart\operator_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\dart\operator_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\dart\string_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\dart\string_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\diff\coord_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\diff\coord_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\diff\diff_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\diff\diff_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\docker\comment_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\docker\comment_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\docker\keyword_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\docker\keyword_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\docker\string_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\docker\string_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\eiffel\boolean_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\eiffel\boolean_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\eiffel\char_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\eiffel\char_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\eiffel\comment_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\eiffel\comment_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\eiffel\keyword_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\eiffel\keyword_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\eiffel\number_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\eiffel\number_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\eiffel\operator_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\eiffel\operator_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\eiffel\string_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\eiffel\string_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\elixir\atom_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\elixir\atom_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\elixir\attr-name_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\elixir\attr-name_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\elixir\attribute_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\elixir\attribute_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\elixir\boolean_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\elixir\boolean_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\elixir\capture_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\elixir\capture_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\elixir\comment_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\elixir\comment_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\elixir\issue775.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\elixir\issue775.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\elixir\keyword_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\elixir\keyword_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\elixir\number_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\elixir\number_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\elixir\operator_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\elixir\operator_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\elixir\regex_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\elixir\regex_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\elixir\string_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\elixir\string_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\erlang\atom_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\erlang\atom_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\erlang\boolean_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\erlang\boolean_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\erlang\comment_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\erlang\comment_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\erlang\function_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\erlang\function_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\erlang\keyword_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\erlang\keyword_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\erlang\number_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\erlang\number_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\erlang\operator_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\erlang\operator_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\erlang\string_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\erlang\string_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\erlang\variable_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\erlang\variable_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\fortran\boolean_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\fortran\boolean_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\fortran\comment_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\fortran\comment_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\fortran\keyword_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\fortran\keyword_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\fortran\number_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\fortran\number_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\fortran\operator_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\fortran\operator_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\fortran\string_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\fortran\string_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\fortran+pure\fortran_inclusion.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\fortran+pure\fortran_inclusion.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\fsharp\comment_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\fsharp\comment_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\fsharp\keyword_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\fsharp\keyword_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\fsharp\number_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\fsharp\number_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\fsharp\preprocessor_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\fsharp\preprocessor_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\fsharp\string_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\fsharp\string_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\gherkin\atrule_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\gherkin\atrule_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\gherkin\comment_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\gherkin\comment_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\gherkin\feature_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\gherkin\feature_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\gherkin\outline_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\gherkin\outline_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\gherkin\pystring_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\gherkin\pystring_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\gherkin\scenario_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\gherkin\scenario_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\gherkin\string_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\gherkin\string_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\gherkin\table_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\gherkin\table_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\gherkin\tag_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\gherkin\tag_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\git\command_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\git\command_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\git\comment_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\git\comment_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\git\commit_sha1_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\git\commit_sha1_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\git\coord_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\git\coord_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\git\diff_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\git\diff_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\git\string_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\git\string_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\glsl\comment_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\glsl\comment_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\glsl\keyword_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\glsl\keyword_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\glsl\number_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\glsl\number_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\glsl\preprocessor_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\glsl\preprocessor_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\go\boolean_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\go\boolean_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\go\builtin_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\go\builtin_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\go\keyword_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\go\keyword_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\go\number_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\go\number_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\go\operator_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\go\operator_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\go\string_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\go\string_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\groovy\annotation_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\groovy\annotation_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\groovy\keyword_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\groovy\keyword_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\groovy\number_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\groovy\number_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\groovy\operator_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\groovy\operator_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\groovy\shebang_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\groovy\shebang_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\groovy\spock-block_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\groovy\spock-block_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\groovy\string_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\groovy\string_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\haml\code_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\haml\code_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\haml\doctype_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\haml\doctype_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\haml\interpolation_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\haml\interpolation_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\haml\multiline-code_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\haml\multiline-code_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\haml\multiline-comment_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\haml\multiline-comment_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\haml\tag_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\haml\tag_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\handlebars\block_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\handlebars\block_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\handlebars\boolean_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\handlebars\boolean_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\handlebars\comment_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\handlebars\comment_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\handlebars\number_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\handlebars\number_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\handlebars\string_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\handlebars\string_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\handlebars+jade\handlebars_inclusion.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\handlebars+jade\handlebars_inclusion.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\haskell\builtin_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\haskell\builtin_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\haskell\char_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\haskell\char_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\haskell\comment_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\haskell\comment_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\haskell\constant_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\haskell\constant_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\haskell\hvariable_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\haskell\hvariable_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\haskell\import_statement_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\haskell\import_statement_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\haskell\keyword_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\haskell\keyword_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\haskell\number_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\haskell\number_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\haskell\operator_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\haskell\operator_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\haskell\string_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\haskell\string_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\haxe\keyword_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\haxe\keyword_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\haxe\metadata_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\haxe\metadata_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\haxe\operator_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\haxe\operator_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\haxe\preprocessor_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\haxe\preprocessor_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\haxe\regex_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\haxe\regex_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\haxe\reification_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\haxe\reification_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\haxe\string_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\haxe\string_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\http\header-name_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\http\header-name_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\http\request-line_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\http\request-line_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\http\response-status_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\http\response-status_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\icon\builtin-keyword_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\icon\builtin-keyword_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\icon\comment_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\icon\comment_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\icon\directive_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\icon\directive_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\icon\function_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\icon\function_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\icon\keyword_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\icon\keyword_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\icon\number_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\icon\number_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\icon\operator_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\icon\operator_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\icon\string_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\icon\string_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\inform7\comment_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\inform7\comment_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\inform7\keyword_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\inform7\keyword_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\inform7\number_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\inform7\number_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\inform7\position_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\inform7\position_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\inform7\property_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\inform7\property_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\inform7\string_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\inform7\string_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\inform7\title_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\inform7\title_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\inform7\variable_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\inform7\variable_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\inform7\verb_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\inform7\verb_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\ini\comment_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\ini\comment_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\ini\important_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\ini\important_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\ini\key_value_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\ini\key_value_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\j\adverb_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\j\adverb_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\j\comment_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\j\comment_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\j\conjunction_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\j\conjunction_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\j\keyword_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\j\keyword_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\j\number_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\j\number_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\j\string_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\j\string_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\j\verb_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\j\verb_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\jade\code_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\jade\code_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\jade\comment_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\jade\comment_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\jade\doctype_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\jade\doctype_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\jade\flow-control_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\jade\flow-control_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\jade\keyword_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\jade\keyword_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\jade\mixin_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\jade\mixin_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\jade\multiline-plain-text_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\jade\multiline-plain-text_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\jade\multiline-script_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\jade\multiline-script_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\jade\plain-text_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\jade\plain-text_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\jade\script_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\jade\script_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\jade\tag_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\jade\tag_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\java\keyword_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\java\keyword_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\java\number_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\java\number_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\java\operator_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\java\operator_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\javascript\boolean_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\javascript\boolean_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\javascript\function_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\javascript\function_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\javascript\keyword_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\javascript\keyword_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\javascript\number_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\javascript\number_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\javascript\regex_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\javascript\regex_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\javascript\template-string_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\javascript\template-string_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\javascript+haml\javascript_inclusion.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\javascript+haml\javascript_inclusion.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\javascript+http\javascript_inclusion.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\javascript+http\javascript_inclusion.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\jsx\tag_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\jsx\tag_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\julia\boolean_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\julia\boolean_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\julia\comment_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\julia\comment_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\julia\keyword_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\julia\keyword_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\julia\number_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\julia\number_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\julia\operator_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\julia\operator_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\julia\string_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\julia\string_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\keyman\atrule_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\keyman\atrule_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\keyman\bold_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\keyman\bold_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\keyman\comment_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\keyman\comment_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\keyman\function_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\keyman\function_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\keyman\keyword_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\keyman\keyword_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\keyman\number_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\keyman\number_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\keyman\operator_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\keyman\operator_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\keyman\string_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\keyman\string_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\keyman\tag_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\keyman\tag_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\kotlin\annotation_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\kotlin\annotation_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\kotlin\function_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\kotlin\function_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\kotlin\interpolation_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\kotlin\interpolation_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\kotlin\keyword_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\kotlin\keyword_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\kotlin\label_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\kotlin\label_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\kotlin\number_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\kotlin\number_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\kotlin\operator_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\kotlin\operator_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\kotlin\raw-string_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\kotlin\raw-string_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\latex\cdata_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\latex\cdata_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\latex\comment_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\latex\comment_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\latex\equation_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\latex\equation_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\latex\headline_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\latex\headline_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\latex\keyword_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\latex\keyword_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\latex\url_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\latex\url_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\less\atrule_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\less\atrule_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\less\comment_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\less\comment_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\less\operator_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\less\operator_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\less\property_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\less\property_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\less\selector_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\less\selector_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\less+haml\less_inclusion.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\less+haml\less_inclusion.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\less+jade\less_inclusion.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\less+jade\less_inclusion.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\lolcode\boolean_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\lolcode\boolean_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\lolcode\comment_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\lolcode\comment_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\lolcode\function_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\lolcode\function_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\lolcode\keyword_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\lolcode\keyword_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\lolcode\label_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\lolcode\label_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\lolcode\number_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\lolcode\number_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\lolcode\operator_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\lolcode\operator_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\lolcode\string_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\lolcode\string_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\lolcode\symbol_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\lolcode\symbol_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\lolcode\variable_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\lolcode\variable_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\lua\comment_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\lua\comment_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\lua\function_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\lua\function_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\lua\keyword_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\lua\keyword_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\lua\number_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\lua\number_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\lua\operator_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\lua\operator_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\lua\string_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\lua\string_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\makefile\builtin_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\makefile\builtin_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\makefile\comment_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\makefile\comment_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\makefile\keyword_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\makefile\keyword_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\makefile\operator_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\makefile\operator_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\makefile\string_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\makefile\string_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\makefile\symbol_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\makefile\symbol_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\makefile\variable_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\makefile\variable_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\markdown\blockquote_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\markdown\blockquote_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\markdown\bold_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\markdown\bold_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\markdown\code_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\markdown\code_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\markdown\hr_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\markdown\hr_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\markdown\italic_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\markdown\italic_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\markdown\list_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\markdown\list_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\markdown\title_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\markdown\title_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\markdown\url-reference_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\markdown\url-reference_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\markdown\url_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\markdown\url_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\markdown+haml\markdown_inclusion.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\markdown+haml\markdown_inclusion.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\markdown+jade\markdown_inclusion.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\markdown+jade\markdown_inclusion.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\markup\cdata_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\markup\cdata_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\markup\comment_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\markup\comment_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\markup\doctype_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\markup\doctype_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\markup\entity_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\markup\entity_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\markup\issue585.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\markup\issue585.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\markup\prolog_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\markup\prolog_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\markup\tag_attribute_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\markup\tag_attribute_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\markup\tag_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\markup\tag_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\markup!+css\css_inclusion.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\markup!+css\css_inclusion.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\markup!+javascript\javascript_inclusion.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\markup!+javascript\javascript_inclusion.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\markup+actionscript\xml_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\markup+actionscript\xml_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\markup+css+wiki\table-tag_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\markup+css+wiki\table-tag_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\markup+haml\markup_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\markup+haml\markup_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\markup+http\markup_inclusion.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\markup+http\markup_inclusion.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\markup+jade\markup_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\markup+jade\markup_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\markup+javascript+csharp+aspnet\script_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\markup+javascript+csharp+aspnet\script_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\markup+php\markup_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\markup+php\markup_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\matlab\comment_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\matlab\comment_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\matlab\function_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\matlab\function_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\matlab\keyword_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\matlab\keyword_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\matlab\number_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\matlab\number_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\matlab\operator_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\matlab\operator_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\matlab\string_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\matlab\string_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\mel\code_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\mel\code_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\mel\comment_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\mel\comment_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\mel\flag_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\mel\flag_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\mel\function_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\mel\function_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\mel\keyword_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\mel\keyword_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\mel\number_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\mel\number_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\mel\operator_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\mel\operator_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\mel\string_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\mel\string_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\mel\variable_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\mel\variable_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\mizar\comment_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\mizar\comment_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\mizar\keyword_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\mizar\keyword_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\mizar\number_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\mizar\number_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\mizar\operator_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\mizar\operator_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\mizar\parameter_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\mizar\parameter_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\mizar\variable_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\mizar\variable_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\monkey\comment_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\monkey\comment_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\monkey\function_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\monkey\function_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\monkey\keyword_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\monkey\keyword_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\monkey\number_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\monkey\number_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\monkey\operator_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\monkey\operator_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\monkey\preprocessor_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\monkey\preprocessor_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\monkey\string_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\monkey\string_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\monkey\type-char_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\monkey\type-char_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\nasm\comment_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\nasm\comment_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\nasm\keyword_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\nasm\keyword_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\nasm\label_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\nasm\label_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\nasm\number_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\nasm\number_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\nasm\operator_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\nasm\operator_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\nasm\register_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\nasm\register_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\nasm\string_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\nasm\string_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\nginx\comment_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\nginx\comment_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\nginx\keyword_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\nginx\keyword_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\nginx\variable_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\nginx\variable_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\nim\comment_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\nim\comment_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\nim\function_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\nim\function_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\nim\keyword_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\nim\keyword_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\nim\number_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\nim\number_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\nim\operator_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\nim\operator_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\nim\string_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\nim\string_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\nix\antiquotation_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\nix\antiquotation_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\nix\boolean_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\nix\boolean_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\nix\comment_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\nix\comment_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\nix\function_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\nix\function_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\nix\keyword_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\nix\keyword_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\nix\number_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\nix\number_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\nix\operator_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\nix\operator_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\nix\string_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\nix\string_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\nix\url_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\nix\url_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\nsis\comment_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\nsis\comment_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\nsis\important_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\nsis\important_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\nsis\keyword_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\nsis\keyword_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\nsis\number_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\nsis\number_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\nsis\operator_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\nsis\operator_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\nsis\property_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\nsis\property_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\nsis\string_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\nsis\string_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\nsis\variable_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\nsis\variable_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\objectivec\keyword_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\objectivec\keyword_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\objectivec\operator_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\objectivec\operator_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\objectivec\string_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\objectivec\string_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\ocaml\boolean_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\ocaml\boolean_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\ocaml\comment_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\ocaml\comment_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\ocaml\directive_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\ocaml\directive_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\ocaml\keyword_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\ocaml\keyword_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\ocaml\number_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\ocaml\number_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\ocaml\operator_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\ocaml\operator_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\ocaml\string_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\ocaml\string_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\ocaml\type_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\ocaml\type_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\oz\atom_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\oz\atom_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\oz\attr-name_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\oz\attr-name_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\oz\comment_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\oz\comment_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\oz\function_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\oz\function_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\oz\keyword_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\oz\keyword_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\oz\number_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\oz\number_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\oz\operator_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\oz\operator_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\oz\string_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\oz\string_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\oz\variable_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\oz\variable_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\parigp\comment_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\parigp\comment_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\parigp\function_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\parigp\function_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\parigp\keyword_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\parigp\keyword_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\parigp\number_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\parigp\number_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\parigp\operator_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\parigp\operator_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\parigp\string_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\parigp\string_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\parser\boolean_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\parser\boolean_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\parser\escape_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\parser\escape_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\parser\expression_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\parser\expression_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\parser\function_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\parser\function_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\parser\keyword_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\parser\keyword_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\parser\number_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\parser\number_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\parser\operator_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\parser\operator_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\parser\parser-comment_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\parser\parser-comment_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\parser\string_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\parser\string_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\parser\variable_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\parser\variable_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\pascal\comment_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\pascal\comment_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\pascal\keyword_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\pascal\keyword_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\pascal\number_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\pascal\number_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\pascal\operator_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\pascal\operator_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\pascal\string_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\pascal\string_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\perl\comment_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\perl\comment_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\perl\filehandle_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\perl\filehandle_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\perl\function_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\perl\function_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\perl\keyword_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\perl\keyword_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\perl\number_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\perl\number_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\perl\operator_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\perl\operator_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\perl\regex_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\perl\regex_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\perl\string_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\perl\string_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\perl\variable_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\perl\variable_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\perl\vstring_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\perl\vstring_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\php\comment_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\php\comment_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\php\constant_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\php\constant_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\php\delimiter_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\php\delimiter_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\php\keyword_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\php\keyword_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\php\package_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\php\package_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\php\property_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\php\property_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\php\shell-comment_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\php\shell-comment_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\php\variable_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\php\variable_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\php!+php-extras\global_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\php!+php-extras\global_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\php!+php-extras\scope_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\php!+php-extras\scope_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\php!+php-extras\this_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\php!+php-extras\this_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\powershell\boolean_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\powershell\boolean_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\powershell\comment_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\powershell\comment_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\powershell\function_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\powershell\function_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\powershell\keyword_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\powershell\keyword_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\powershell\namespace_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\powershell\namespace_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\powershell\operator_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\powershell\operator_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\powershell\string_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\powershell\string_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\powershell\variable_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\powershell\variable_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\processing\constant_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\processing\constant_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\processing\function_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\processing\function_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\processing\keyword_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\processing\keyword_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\processing\operator_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\processing\operator_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\processing\type_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\processing\type_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\prolog\builtin_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\prolog\builtin_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\prolog\comment_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\prolog\comment_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\prolog\function_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\prolog\function_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\prolog\number_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\prolog\number_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\prolog\operator_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\prolog\operator_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\prolog\string_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\prolog\string_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\prolog\variable_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\prolog\variable_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\puppet\attr-name_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\puppet\attr-name_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\puppet\boolean_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\puppet\boolean_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\puppet\comment_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\puppet\comment_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\puppet\datatype_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\puppet\datatype_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\puppet\function_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\puppet\function_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\puppet\heredoc_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\puppet\heredoc_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\puppet\interpolation_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\puppet\interpolation_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\puppet\keyword_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\puppet\keyword_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\puppet\number_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\puppet\number_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\puppet\operator_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\puppet\operator_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\puppet\regex_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\puppet\regex_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\puppet\string_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\puppet\string_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\puppet\variable_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\puppet\variable_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\pure\comment_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\pure\comment_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\pure\function_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\pure\function_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\pure\keyword_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\pure\keyword_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\pure\number_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\pure\number_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\pure\operator_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\pure\operator_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\pure\special_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\pure\special_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\pure\string_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\pure\string_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\python\boolean_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\python\boolean_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\python\class-name_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\python\class-name_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\python\comment_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\python\comment_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\python\function_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\python\function_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\python\keyword_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\python\keyword_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\python\number_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\python\number_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\python\operator_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\python\operator_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\python\string_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\python\string_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\python\triple-quoted-string_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\python\triple-quoted-string_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\q\adverb_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\q\adverb_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\q\comment_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\q\comment_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\q\datetime_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\q\datetime_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\q\keyword_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\q\keyword_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\q\number_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\q\number_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\q\string_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\q\string_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\q\symbol_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\q\symbol_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\q\verb_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\q\verb_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\qore\boolean_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\qore\boolean_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\qore\comment_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\qore\comment_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\qore\function_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\qore\function_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\qore\keyword_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\qore\keyword_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\qore\number_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\qore\number_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\qore\operator_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\qore\operator_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\qore\string_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\qore\string_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\qore\variable_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\qore\variable_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\r\boolean_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\r\boolean_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\r\comment_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\r\comment_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\r\ellipsis_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\r\ellipsis_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\r\keyword_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\r\keyword_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\r\number_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\r\number_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\r\operator_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\r\operator_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\r\percent-operator_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\r\percent-operator_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\r\string_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\r\string_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\rest\command-line-option_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\rest\command-line-option_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\rest\comment_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\rest\comment_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\rest\directive_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\rest\directive_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\rest\doctest-block_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\rest\doctest-block_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\rest\field_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\rest\field_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\rest\hr_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\rest\hr_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\rest\inline_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\rest\inline_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\rest\link-target_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\rest\link-target_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\rest\link_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\rest\link_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\rest\list-bullet_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\rest\list-bullet_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\rest\literal-block_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\rest\literal-block_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\rest\quoted-literal-block_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\rest\quoted-literal-block_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\rest\substitution-def_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\rest\substitution-def_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\rest\table_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\rest\table_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\rest\title_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\rest\title_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\rip\boolean_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\rip\boolean_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\rip\builtin_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\rip\builtin_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\rip\character_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\rip\character_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\rip\comment_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\rip\comment_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\rip\date_time_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\rip\date_time_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\rip\keyword_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\rip\keyword_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\rip\number_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\rip\number_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\rip\reference_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\rip\reference_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\rip\regex_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\rip\regex_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\rip\string_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\rip\string_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\rip\symbol_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\rip\symbol_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\roboconf\comment_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\roboconf\comment_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\roboconf\component_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\roboconf\component_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\roboconf\keyword_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\roboconf\keyword_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\roboconf\optional_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\roboconf\optional_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\roboconf\property_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\roboconf\property_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\roboconf\value_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\roboconf\value_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\roboconf\wildcard_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\roboconf\wildcard_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\ruby\builtin_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\ruby\builtin_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\ruby\comment_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\ruby\comment_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\ruby\constant_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\ruby\constant_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\ruby\keyword_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\ruby\keyword_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\ruby\regex_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\ruby\regex_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\ruby\string_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\ruby\string_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\ruby\symbol_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\ruby\symbol_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\ruby\variable_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\ruby\variable_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\rust\attribute_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\rust\attribute_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\rust\closure-params_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\rust\closure-params_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\rust\comment_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\rust\comment_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\rust\function_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\rust\function_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\rust\keyword_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\rust\keyword_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\rust\macro-rules_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\rust\macro-rules_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\rust\number_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\rust\number_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\rust\operator_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\rust\operator_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\rust\string_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\rust\string_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\sas\comment_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\sas\comment_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\sas\datalines_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\sas\datalines_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\sas\datetime_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\sas\datetime_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\sas\keyword_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\sas\keyword_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\sas\number_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\sas\number_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\sas\operator_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\sas\operator_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\sas\string_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\sas\string_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\sass\atrule-line_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\sass\atrule-line_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\sass\comment_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\sass\comment_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\sass\property-line_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\sass\property-line_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\sass\selector_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\sass\selector_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\sass\variable-line_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\sass\variable-line_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\scala\builtin_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\scala\builtin_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\scala\keyword_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\scala\keyword_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\scala\number_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\scala\number_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\scala\string_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\scala\string_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\scala\symbol_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\scala\symbol_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\scheme\boolean_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\scheme\boolean_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\scheme\builtin_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\scheme\builtin_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\scheme\comment_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\scheme\comment_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\scheme\function_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\scheme\function_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\scheme\keyword_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\scheme\keyword_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\scheme\number_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\scheme\number_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\scheme\operator_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\scheme\operator_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\scheme\string_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\scheme\string_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\scss\atrule_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\scss\atrule_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\scss\boolean_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\scss\boolean_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\scss\comment_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\scss\comment_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\scss\keyword_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\scss\keyword_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\scss\null_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\scss\null_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\scss\operator_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\scss\operator_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\scss\placeholder_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\scss\placeholder_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\scss\selector_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\scss\selector_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\scss\statement_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\scss\statement_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\scss\url_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\scss\url_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\scss\variable_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\scss\variable_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\scss+haml\scss_inclusion.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\scss+haml\scss_inclusion.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\scss+jade\scss_inclusion.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\scss+jade\scss_inclusion.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\smalltalk\block-arguments_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\smalltalk\block-arguments_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\smalltalk\character_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\smalltalk\character_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\smalltalk\comment_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\smalltalk\comment_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\smalltalk\keyword_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\smalltalk\keyword_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\smalltalk\number_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\smalltalk\number_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\smalltalk\operator_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\smalltalk\operator_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\smalltalk\string_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\smalltalk\string_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\smalltalk\symbol_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\smalltalk\symbol_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\smalltalk\temporary-variables_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\smalltalk\temporary-variables_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\smarty\attr-name_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\smarty\attr-name_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\smarty\function_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\smarty\function_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\smarty\keyword_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\smarty\keyword_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\smarty\number_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\smarty\number_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\smarty\operator_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\smarty\operator_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\smarty\smarty-comment_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\smarty\smarty-comment_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\smarty\string_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\smarty\string_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\smarty\variable_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\smarty\variable_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\stylus\atrule-declaration_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\stylus\atrule-declaration_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\stylus\boolean_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\stylus\boolean_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\stylus\comment_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\stylus\comment_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\stylus\func_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\stylus\func_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\stylus\hexcode_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\stylus\hexcode_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\stylus\important_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\stylus\important_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\stylus\keyword_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\stylus\keyword_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\stylus\number_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\stylus\number_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\stylus\operator_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\stylus\operator_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\stylus\property-declaration_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\stylus\property-declaration_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\stylus\selector_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\stylus\selector_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\stylus\string_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\stylus\string_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\stylus\url_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\stylus\url_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\stylus\variable-declaration_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\stylus\variable-declaration_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\stylus+jade\stylus_inclusion.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\stylus+jade\stylus_inclusion.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\swift\atrule_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\swift\atrule_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\swift\builtin_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\swift\builtin_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\swift\constant_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\swift\constant_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\swift\keyword_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\swift\keyword_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\swift\number_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\swift\number_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\swift\string_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\swift\string_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\tcl\builtin_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\tcl\builtin_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\tcl\comment_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\tcl\comment_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\tcl\function_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\tcl\function_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\tcl\keyword_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\tcl\keyword_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\tcl\operator_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\tcl\operator_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\tcl\scope_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\tcl\scope_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\tcl\string_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\tcl\string_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\tcl\variable_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\tcl\variable_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\textile\acronym_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\textile\acronym_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\textile\block-tag_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\textile\block-tag_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\textile\footnote_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\textile\footnote_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\textile\image_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\textile\image_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\textile\inline_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\textile\inline_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\textile\link-ref_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\textile\link-ref_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\textile\link_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\textile\link_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\textile\list_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\textile\list_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\textile\mark_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\textile\mark_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\textile\table_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\textile\table_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\twig\boolean_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\twig\boolean_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\twig\comment_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\twig\comment_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\twig\keyword_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\twig\keyword_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\twig\number_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\twig\number_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\twig\operator_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\twig\operator_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\twig\string_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\twig\string_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\twig+jade\twig_inclusion.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\twig+jade\twig_inclusion.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\typescript\keyword_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\typescript\keyword_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\verilog\comment_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\verilog\comment_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\verilog\constant_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\verilog\constant_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\verilog\function_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\verilog\function_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\verilog\important_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\verilog\important_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\verilog\keyword_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\verilog\keyword_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\verilog\number_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\verilog\number_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\verilog\operator_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\verilog\operator_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\verilog\property_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\verilog\property_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\verilog\string_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\verilog\string_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\vhdl\boolean_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\vhdl\boolean_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\vhdl\comment_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\vhdl\comment_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\vhdl\constant_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\vhdl\constant_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\vhdl\function_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\vhdl\function_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\vhdl\keyword_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\vhdl\keyword_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\vhdl\number_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\vhdl\number_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\vhdl\operator_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\vhdl\operator_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\vhdl\string_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\vhdl\string_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\vhdl\vhdl-vectors_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\vhdl\vhdl-vectors_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\vim\builtin_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\vim\builtin_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\vim\comment_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\vim\comment_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\vim\function_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\vim\function_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\vim\keyword_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\vim\keyword_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\vim\number_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\vim\number_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\vim\operator_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\vim\operator_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\vim\string_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\vim\string_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\wiki\block-comment_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\wiki\block-comment_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\wiki\emphasis_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\wiki\emphasis_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\wiki\heading_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\wiki\heading_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\wiki\hr_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\wiki\hr_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\wiki\nowiki_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\wiki\nowiki_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\wiki\symbol_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\wiki\symbol_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\wiki\url_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\wiki\url_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\wiki\variable_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\wiki\variable_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\yaml\boolean_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\yaml\boolean_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\yaml\comment_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\yaml\comment_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\yaml\datetime_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\yaml\datetime_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\yaml\directive_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\yaml\directive_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\yaml\important_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\yaml\important_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\yaml\key_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\yaml\key_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\yaml\null_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\yaml\null_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\yaml\number_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\yaml\number_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\yaml\scalar_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\yaml\scalar_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\yaml\string_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\yaml\string_feature.test</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\tests\languages\yaml\tag_feature.test">
- <Link>Resources\dashboard-ui\bower_components\prism\tests\languages\yaml\tag_feature.test</Link>
- </BundleResource>
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\prism\themes\prism-coy.css">
<Link>Resources\dashboard-ui\bower_components\prism\themes\prism-coy.css</Link>
</BundleResource>
@@ -8684,6 +5769,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>
@@ -8741,51 +5829,6 @@
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\requirejs\require.js">
<Link>Resources\dashboard-ui\bower_components\requirejs\require.js</Link>
</BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\velocity\.bower.json">
- <Link>Resources\dashboard-ui\bower_components\velocity\.bower.json</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\velocity\bower.json">
- <Link>Resources\dashboard-ui\bower_components\velocity\bower.json</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\velocity\velocity.js">
- <Link>Resources\dashboard-ui\bower_components\velocity\velocity.js</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\velocity\velocity.min.js">
- <Link>Resources\dashboard-ui\bower_components\velocity\velocity.min.js</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\velocity\velocity.ui.js">
- <Link>Resources\dashboard-ui\bower_components\velocity\velocity.ui.js</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\velocity\velocity.ui.min.js">
- <Link>Resources\dashboard-ui\bower_components\velocity\velocity.ui.min.js</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\velocity\test\bluebird.js">
- <Link>Resources\dashboard-ui\bower_components\velocity\test\bluebird.js</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\velocity\test\index.html">
- <Link>Resources\dashboard-ui\bower_components\velocity\test\index.html</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\velocity\test\jquery-1.11.1.js">
- <Link>Resources\dashboard-ui\bower_components\velocity\test\jquery-1.11.1.js</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\velocity\test\jquery-1.4.3.js">
- <Link>Resources\dashboard-ui\bower_components\velocity\test\jquery-1.4.3.js</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\velocity\test\q.js">
- <Link>Resources\dashboard-ui\bower_components\velocity\test\q.js</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\velocity\test\qunit-1.14.0.css">
- <Link>Resources\dashboard-ui\bower_components\velocity\test\qunit-1.14.0.css</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\velocity\test\qunit-1.14.0.js">
- <Link>Resources\dashboard-ui\bower_components\velocity\test\qunit-1.14.0.js</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\velocity\test\when.js">
- <Link>Resources\dashboard-ui\bower_components\velocity\test\when.js</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\velocity\test\zepto.js">
- <Link>Resources\dashboard-ui\bower_components\velocity\test\zepto.js</Link>
- </BundleResource>
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\web-animations-js\.bower.json">
<Link>Resources\dashboard-ui\bower_components\web-animations-js\.bower.json</Link>
</BundleResource>
@@ -8870,9 +5913,15 @@
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\webcomponentsjs\webcomponents.min.js">
<Link>Resources\dashboard-ui\bower_components\webcomponentsjs\webcomponents.min.js</Link>
</BundleResource>
+ <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\components\apphost.js">
+ <Link>Resources\dashboard-ui\components\apphost.js</Link>
+ </BundleResource>
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\components\chromecasthelpers.js">
<Link>Resources\dashboard-ui\components\chromecasthelpers.js</Link>
</BundleResource>
+ <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\components\favoriteitems.js">
+ <Link>Resources\dashboard-ui\components\favoriteitems.js</Link>
+ </BundleResource>
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\components\humanedate.js">
<Link>Resources\dashboard-ui\components\humanedate.js</Link>
</BundleResource>
@@ -8882,14 +5931,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\sharingwidget.js">
- <Link>Resources\dashboard-ui\components\sharingwidget.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>
@@ -8963,26 +6009,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>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\components\recordingcreator\recordingcreator.js">
- <Link>Resources\dashboard-ui\components\recordingcreator\recordingcreator.js</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\components\recordingcreator\recordingcreator.template.html">
- <Link>Resources\dashboard-ui\components\recordingcreator\recordingcreator.template.html</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>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\components\tvguide\tvguide.js">
- <Link>Resources\dashboard-ui\components\tvguide\tvguide.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\tvguide\tvguide.template.html">
- <Link>Resources\dashboard-ui\components\tvguide\tvguide.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>
@@ -8990,12 +6021,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>
@@ -9023,9 +6063,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>
@@ -9062,6 +6099,9 @@
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\css\images\logindefault.png">
<Link>Resources\dashboard-ui\css\images\logindefault.png</Link>
</BundleResource>
+ <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\css\images\logo.png">
+ <Link>Resources\dashboard-ui\css\images\logo.png</Link>
+ </BundleResource>
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\css\images\logo536.png">
<Link>Resources\dashboard-ui\css\images\logo536.png</Link>
</BundleResource>
@@ -9140,9 +6180,6 @@
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\css\images\clients\windowsrt.png">
<Link>Resources\dashboard-ui\css\images\clients\windowsrt.png</Link>
</BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\css\images\editor\lock.png">
- <Link>Resources\dashboard-ui\css\images\editor\lock.png</Link>
- </BundleResource>
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\css\images\editor\missing.png">
<Link>Resources\dashboard-ui\css\images\editor\missing.png</Link>
</BundleResource>
@@ -9155,9 +6192,6 @@
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\css\images\editor\missingprimaryimage.png">
<Link>Resources\dashboard-ui\css\images\editor\missingprimaryimage.png</Link>
</BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\css\images\editor\missingtrailer.png">
- <Link>Resources\dashboard-ui\css\images\editor\missingtrailer.png</Link>
- </BundleResource>
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\css\images\items\detail\audio.png">
<Link>Resources\dashboard-ui\css\images\items\detail\audio.png</Link>
</BundleResource>
@@ -9308,12 +6342,6 @@
<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>
@@ -9332,9 +6360,6 @@
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\legacy\fnchecked.js">
<Link>Resources\dashboard-ui\legacy\fnchecked.js</Link>
</BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\legacy\objectassign.js">
- <Link>Resources\dashboard-ui\legacy\objectassign.js</Link>
- </BundleResource>
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\legacy\selectmenu.js">
<Link>Resources\dashboard-ui\legacy\selectmenu.js</Link>
</BundleResource>
@@ -9344,12 +6369,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\advancedconfigurationpage.js">
- <Link>Resources\dashboard-ui\scripts\advancedconfigurationpage.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>
@@ -9371,9 +6390,6 @@
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\scripts\channels.js">
<Link>Resources\dashboard-ui\scripts\channels.js</Link>
</BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\scripts\channelsettings.js">
- <Link>Resources\dashboard-ui\scripts\channelsettings.js</Link>
- </BundleResource>
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\scripts\channelslatest.js">
<Link>Resources\dashboard-ui\scripts\channelslatest.js</Link>
</BundleResource>
@@ -9410,9 +6426,6 @@
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\scripts\dlnaprofiles.js">
<Link>Resources\dashboard-ui\scripts\dlnaprofiles.js</Link>
</BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\scripts\dlnaserversettings.js">
- <Link>Resources\dashboard-ui\scripts\dlnaserversettings.js</Link>
- </BundleResource>
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\scripts\dlnasettings.js">
<Link>Resources\dashboard-ui\scripts\dlnasettings.js</Link>
</BundleResource>
@@ -9458,6 +6471,9 @@
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\scripts\gamesystemspage.js">
<Link>Resources\dashboard-ui\scripts\gamesystemspage.js</Link>
</BundleResource>
+ <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\scripts\homefavorites.js">
+ <Link>Resources\dashboard-ui\scripts\homefavorites.js</Link>
+ </BundleResource>
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\scripts\homenextup.js">
<Link>Resources\dashboard-ui\scripts\homenextup.js</Link>
</BundleResource>
@@ -9479,12 +6495,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>
@@ -9536,12 +6552,6 @@
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\scripts\livetvsuggested.js">
<Link>Resources\dashboard-ui\scripts\livetvsuggested.js</Link>
</BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\scripts\livetvtimer.js">
- <Link>Resources\dashboard-ui\scripts\livetvtimer.js</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\scripts\livetvtimers.js">
- <Link>Resources\dashboard-ui\scripts\livetvtimers.js</Link>
- </BundleResource>
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\scripts\livetvtunerprovider-hdhomerun.js">
<Link>Resources\dashboard-ui\scripts\livetvtunerprovider-hdhomerun.js</Link>
</BundleResource>
@@ -9572,9 +6582,6 @@
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\scripts\mediaplayer.js">
<Link>Resources\dashboard-ui\scripts\mediaplayer.js</Link>
</BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\scripts\metadataadvanced.js">
- <Link>Resources\dashboard-ui\scripts\metadataadvanced.js</Link>
- </BundleResource>
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\scripts\metadataconfigurationpage.js">
<Link>Resources\dashboard-ui\scripts\metadataconfigurationpage.js</Link>
</BundleResource>
@@ -9605,9 +6612,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>
@@ -9671,9 +6675,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>
@@ -9701,12 +6702,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>
@@ -9725,9 +6720,6 @@
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\scripts\shared.js">
<Link>Resources\dashboard-ui\scripts\shared.js</Link>
</BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\scripts\sharingmanager.js">
- <Link>Resources\dashboard-ui\scripts\sharingmanager.js</Link>
- </BundleResource>
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\scripts\site.js">
<Link>Resources\dashboard-ui\scripts\site.js</Link>
</BundleResource>
@@ -9791,12 +6783,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>
@@ -9860,6 +6861,9 @@
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\strings\fr-CA.json">
<Link>Resources\dashboard-ui\strings\fr-CA.json</Link>
</BundleResource>
+ <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\strings\fr-FR.json">
+ <Link>Resources\dashboard-ui\strings\fr-FR.json</Link>
+ </BundleResource>
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\strings\fr.json">
<Link>Resources\dashboard-ui\strings\fr.json</Link>
</BundleResource>
@@ -9911,8 +6915,8 @@
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\strings\ru.json">
<Link>Resources\dashboard-ui\strings\ru.json</Link>
</BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\strings\server.json">
- <Link>Resources\dashboard-ui\strings\server.json</Link>
+ <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\strings\sk.json">
+ <Link>Resources\dashboard-ui\strings\sk.json</Link>
</BundleResource>
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\strings\sl-SI.json">
<Link>Resources\dashboard-ui\strings\sl-SI.json</Link>
@@ -10001,15 +7005,9 @@
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\thirdparty\jquerymobile-1.4.5\jqm.widget.js">
<Link>Resources\dashboard-ui\thirdparty\jquerymobile-1.4.5\jqm.widget.js</Link>
</BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\thirdparty\jquerymobile-1.4.5\jquery.mobile-1.4.5.min.map">
- <Link>Resources\dashboard-ui\thirdparty\jquerymobile-1.4.5\jquery.mobile-1.4.5.min.map</Link>
- </BundleResource>
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\thirdparty\jquerymobile-1.4.5\jquery.mobile.custom.icons.css">
<Link>Resources\dashboard-ui\thirdparty\jquerymobile-1.4.5\jquery.mobile.custom.icons.css</Link>
</BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\thirdparty\jquerymobile-1.4.5\jquery.mobile.custom.js">
- <Link>Resources\dashboard-ui\thirdparty\jquerymobile-1.4.5\jquery.mobile.custom.js</Link>
- </BundleResource>
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\thirdparty\jquerymobile-1.4.5\jquery.mobile.custom.theme.css">
<Link>Resources\dashboard-ui\thirdparty\jquerymobile-1.4.5\jquery.mobile.custom.theme.css</Link>
</BundleResource>
@@ -10028,33 +7026,6 @@
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\thirdparty\jstree\themes\default\throbber.gif">
<Link>Resources\dashboard-ui\thirdparty\jstree\themes\default\throbber.gif</Link>
</BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\thirdparty\social-share-kit-1.0.4\LICENSE">
- <Link>Resources\dashboard-ui\thirdparty\social-share-kit-1.0.4\LICENSE</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\thirdparty\social-share-kit-1.0.4\README.md">
- <Link>Resources\dashboard-ui\thirdparty\social-share-kit-1.0.4\README.md</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\thirdparty\social-share-kit-1.0.4\dist\css\social-share-kit.css">
- <Link>Resources\dashboard-ui\thirdparty\social-share-kit-1.0.4\dist\css\social-share-kit.css</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\thirdparty\social-share-kit-1.0.4\dist\fonts\social-share-kit.eot">
- <Link>Resources\dashboard-ui\thirdparty\social-share-kit-1.0.4\dist\fonts\social-share-kit.eot</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\thirdparty\social-share-kit-1.0.4\dist\fonts\social-share-kit.svg">
- <Link>Resources\dashboard-ui\thirdparty\social-share-kit-1.0.4\dist\fonts\social-share-kit.svg</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\thirdparty\social-share-kit-1.0.4\dist\fonts\social-share-kit.ttf">
- <Link>Resources\dashboard-ui\thirdparty\social-share-kit-1.0.4\dist\fonts\social-share-kit.ttf</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\thirdparty\social-share-kit-1.0.4\dist\fonts\social-share-kit.woff">
- <Link>Resources\dashboard-ui\thirdparty\social-share-kit-1.0.4\dist\fonts\social-share-kit.woff</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\thirdparty\social-share-kit-1.0.4\dist\js\social-share-kit.js">
- <Link>Resources\dashboard-ui\thirdparty\social-share-kit-1.0.4\dist\js\social-share-kit.js</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\thirdparty\social-share-kit-1.0.4\dist\js\social-share-kit.min.js">
- <Link>Resources\dashboard-ui\thirdparty\social-share-kit-1.0.4\dist\js\social-share-kit.min.js</Link>
- </BundleResource>
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\voice\Readme.md">
<Link>Resources\dashboard-ui\voice\Readme.md</Link>
</BundleResource>
diff --git a/MediaBrowser.Server.Mac/Main.cs b/MediaBrowser.Server.Mac/Main.cs
index b7e158c5d..b48f44707 100644
--- a/MediaBrowser.Server.Mac/Main.cs
+++ b/MediaBrowser.Server.Mac/Main.cs
@@ -94,7 +94,7 @@ namespace MediaBrowser.Server.Mac
var fileSystem = new ManagedFileSystem(new PatternsLogger(logManager.GetLogger("FileSystem")), false, true);
fileSystem.AddShortcutHandler(new MbLinkShortcutHandler(fileSystem));
- var nativeApp = new NativeApp();
+ var nativeApp = new NativeApp(logManager.GetLogger("App"));
AppHost = new ApplicationHost(appPaths, logManager, options, fileSystem, "Emby.Server.Mac.pkg", nativeApp);
diff --git a/MediaBrowser.Server.Mac/MenuBarIcon.cs b/MediaBrowser.Server.Mac/MenuBarIcon.cs
index 51ba7f432..865f45057 100644
--- a/MediaBrowser.Server.Mac/MenuBarIcon.cs
+++ b/MediaBrowser.Server.Mac/MenuBarIcon.cs
@@ -88,17 +88,17 @@ namespace MediaBrowser.Server.Mac
private void Community(NSObject sender)
{
- BrowserLauncher.OpenCommunity(Logger);
+ BrowserLauncher.OpenCommunity(MainClass.AppHost);
}
private void Configure(NSObject sender)
{
- BrowserLauncher.OpenDashboard(MainClass.AppHost, Logger);
+ BrowserLauncher.OpenDashboard(MainClass.AppHost);
}
private void Browse(NSObject sender)
{
- BrowserLauncher.OpenWebClient(MainClass.AppHost, Logger);
+ BrowserLauncher.OpenWebClient(MainClass.AppHost);
}
public void Terminate()
diff --git a/MediaBrowser.Server.Mac/Native/BaseMonoApp.cs b/MediaBrowser.Server.Mac/Native/BaseMonoApp.cs
index 33df83b2f..7c9b43026 100644
--- a/MediaBrowser.Server.Mac/Native/BaseMonoApp.cs
+++ b/MediaBrowser.Server.Mac/Native/BaseMonoApp.cs
@@ -7,11 +7,22 @@ using System.Collections.Generic;
using System.Reflection;
using System.Text.RegularExpressions;
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
{
public abstract class BaseMonoApp : INativeApp
{
+ protected ILogger Logger { get; private set; }
+
+ protected BaseMonoApp(ILogger logger)
+ {
+ Logger = logger;
+ }
+
/// <summary>
/// Shutdowns this instance.
/// </summary>
@@ -37,6 +48,21 @@ namespace MediaBrowser.Server.Mac
}
}
+ public void PreventSystemStandby()
+ {
+
+ }
+
+ public void AllowSystemStandby()
+ {
+
+ }
+
+ public IDbConnector GetDbConnector()
+ {
+ return new DbConnector(Logger);
+ }
+
public virtual bool SupportsLibraryMonitor
{
get
@@ -62,11 +88,6 @@ namespace MediaBrowser.Server.Mac
get { return false; }
}
- public void PreventSystemStandby()
- {
-
- }
-
public List<Assembly> GetAssembliesWithParts()
{
var list = new List<Assembly>();
@@ -106,6 +127,74 @@ namespace MediaBrowser.Server.Mac
{
}
+ public void LaunchUrl(string url)
+ {
+ var process = new Process
+ {
+ StartInfo = new ProcessStartInfo
+ {
+ FileName = url
+ },
+
+ EnableRaisingEvents = true,
+ };
+
+ process.Exited += ProcessExited;
+
+ process.Start();
+ }
+
+ /// <summary>
+ /// Processes the exited.
+ /// </summary>
+ /// <param name="sender">The sender.</param>
+ /// <param name="e">The <see cref="EventArgs" /> instance containing the event data.</param>
+ private static void ProcessExited(object sender, EventArgs e)
+ {
+ ((Process)sender).Dispose();
+ }
+
+ public FFMpegInstallInfo GetFfmpegInstallInfo()
+ {
+ return GetInfo(Environment);
+ }
+
+ public static FFMpegInstallInfo GetInfo(NativeEnvironment environment)
+ {
+ var info = new FFMpegInstallInfo();
+
+ info.ArchiveType = "7z";
+
+ switch (environment.SystemArchitecture)
+ {
+ case Architecture.X64:
+ info.Version = "20160124";
+ break;
+ case Architecture.X86:
+ info.Version = "20150110";
+ break;
+ }
+
+ info.DownloadUrls = GetDownloadUrls(environment);
+
+ return info;
+ }
+
+ private static string[] GetDownloadUrls(NativeEnvironment environment)
+ {
+ switch (environment.SystemArchitecture)
+ {
+ case Architecture.X64:
+ return new[]
+ {
+ "https://github.com/MediaBrowser/Emby.Resources/raw/master/ffmpeg/osx/ffmpeg-x64-2.8.5.7z"
+ };
+ }
+
+ // No version available
+ return new string[] { };
+ }
+
public INetworkManager CreateNetworkManager(ILogger logger)
{
return new NetworkManager(logger);
@@ -137,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/NativeApp.cs b/MediaBrowser.Server.Mac/Native/NativeApp.cs
index 005fb0914..59fa92dd1 100644
--- a/MediaBrowser.Server.Mac/Native/NativeApp.cs
+++ b/MediaBrowser.Server.Mac/Native/NativeApp.cs
@@ -1,5 +1,6 @@
using System;
using MediaBrowser.Server.Startup.Common;
+using MediaBrowser.Model.Logging;
namespace MediaBrowser.Server.Mac
{
@@ -8,7 +9,12 @@ namespace MediaBrowser.Server.Mac
/// </summary>
public class NativeApp : BaseMonoApp
{
- /// <summary>
+ public NativeApp(ILogger logger)
+ : base(logger)
+ {
+ }
+
+ /// <summary>
/// Shutdowns this instance.
/// </summary>
public override void Shutdown()
diff --git a/MediaBrowser.Server.Mac/statusicon.png b/MediaBrowser.Server.Mac/statusicon.png
index 9b2cdd8a0..8f1269505 100644
--- a/MediaBrowser.Server.Mac/statusicon.png
+++ b/MediaBrowser.Server.Mac/statusicon.png
Binary files differ
diff --git a/MediaBrowser.Server.Mono/MediaBrowser.Server.Mono.csproj b/MediaBrowser.Server.Mono/MediaBrowser.Server.Mono.csproj
index 48d2df7ce..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>
@@ -76,17 +76,41 @@
<Reference Include="MediaBrowser.IsoMounting.Linux">
<HintPath>..\ThirdParty\MediaBrowser.IsoMounting.Linux\MediaBrowser.IsoMounting.Linux.dll</HintPath>
</Reference>
+ <Reference Include="System.Data" />
+ <Reference Include="System.Data.SQLite">
+ <HintPath>..\ThirdParty\System.Data.SQLite.ManagedOnly\1.0.94.0\System.Data.SQLite.dll</HintPath>
+ </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\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 e54bc0b4a..4011fa3de 100644
--- a/MediaBrowser.Server.Mono/Native/BaseMonoApp.cs
+++ b/MediaBrowser.Server.Mono/Native/BaseMonoApp.cs
@@ -9,15 +9,22 @@ 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;
namespace MediaBrowser.Server.Mono.Native
{
public abstract class BaseMonoApp : INativeApp
{
protected StartupOptions StartupOptions { get; private set; }
- protected BaseMonoApp(StartupOptions startupOptions)
+ protected ILogger Logger { get; private set; }
+
+ protected BaseMonoApp(StartupOptions startupOptions, ILogger logger)
{
StartupOptions = startupOptions;
+ Logger = logger;
}
/// <summary>
@@ -67,6 +74,11 @@ namespace MediaBrowser.Server.Mono.Native
}
+ public void AllowSystemStandby()
+ {
+
+ }
+
public List<Assembly> GetAssembliesWithParts()
{
var list = new List<Assembly>();
@@ -165,12 +177,20 @@ 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))
{
info.SystemArchitecture = Architecture.Arm;
}
+ else if (System.Environment.Is64BitOperatingSystem)
+ {
+ info.SystemArchitecture = Architecture.X64;
+ }
+ else
+ {
+ info.SystemArchitecture = Architecture.X86;
+ }
info.OperatingSystemVersionString = string.IsNullOrWhiteSpace(sysName) ?
System.Environment.OSVersion.VersionString :
@@ -186,14 +206,21 @@ namespace MediaBrowser.Server.Mono.Native
if (_unixName == null)
{
var uname = new Uname();
- Utsname utsname;
- var callResult = Syscall.uname(out utsname);
- if (callResult == 0)
+ try
{
- uname.sysname = utsname.sysname;
- uname.machine = utsname.machine;
- }
+ Utsname utsname;
+ var callResult = Syscall.uname(out utsname);
+ if (callResult == 0)
+ {
+ uname.sysname = utsname.sysname ?? string.Empty;
+ uname.machine = utsname.machine ?? string.Empty;
+ }
+ }
+ catch (Exception ex)
+ {
+ Logger.ErrorException("Error getting unix name", ex);
+ }
_unixName = uname;
}
return _unixName;
@@ -209,6 +236,69 @@ namespace MediaBrowser.Server.Mono.Native
{
return new NullPowerManagement();
}
+
+ public FFMpegInstallInfo GetFfmpegInstallInfo()
+ {
+ return GetInfo(Environment);
+ }
+
+ public void LaunchUrl(string url)
+ {
+ throw new NotImplementedException();
+ }
+
+ public IDbConnector GetDbConnector()
+ {
+ return new DbConnector(Logger);
+ }
+
+ public static FFMpegInstallInfo GetInfo(NativeEnvironment environment)
+ {
+ var info = new FFMpegInstallInfo();
+
+ // Windows builds: http://ffmpeg.zeranoe.com/builds/
+ // Linux builds: http://johnvansickle.com/ffmpeg/
+ // OS X builds: http://ffmpegmac.net/
+ // OS X x64: http://www.evermeet.cx/ffmpeg/
+
+ switch (environment.OperatingSystem)
+ {
+ case OperatingSystem.Osx:
+ case OperatingSystem.Bsd:
+ break;
+ case OperatingSystem.Linux:
+
+ info.ArchiveType = "7z";
+ info.Version = "20160215";
+ break;
+ }
+
+ info.DownloadUrls = GetDownloadUrls(environment);
+
+ return info;
+ }
+
+ private static string[] GetDownloadUrls(NativeEnvironment environment)
+ {
+ switch (environment.OperatingSystem)
+ {
+ case OperatingSystem.Linux:
+
+ switch (environment.SystemArchitecture)
+ {
+ case Architecture.X64:
+ return new[]
+ {
+ "https://github.com/MediaBrowser/Emby.Resources/raw/master/ffmpeg/linux/ffmpeg-git-20160215-64bit-static.7z"
+ };
+ }
+ break;
+ }
+
+ // No version available
+ return new string[] { };
+ }
+
}
public class NullPowerManagement : IPowerManagement
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/NativeApp.cs b/MediaBrowser.Server.Mono/Native/NativeApp.cs
index c73a96497..c0874a1d8 100644
--- a/MediaBrowser.Server.Mono/Native/NativeApp.cs
+++ b/MediaBrowser.Server.Mono/Native/NativeApp.cs
@@ -1,4 +1,5 @@
-using MediaBrowser.Server.Startup.Common;
+using MediaBrowser.Model.Logging;
+using MediaBrowser.Server.Startup.Common;
namespace MediaBrowser.Server.Mono.Native
{
@@ -7,8 +8,8 @@ namespace MediaBrowser.Server.Mono.Native
/// </summary>
internal class NativeApp : BaseMonoApp
{
- public NativeApp(StartupOptions startupOptions)
- : base(startupOptions)
+ public NativeApp(StartupOptions startupOptions, ILogger logger)
+ : base(startupOptions, logger)
{
}
diff --git a/MediaBrowser.Server.Mono/Networking/CertificateGenerator.cs b/MediaBrowser.Server.Mono/Networking/CertificateGenerator.cs
index 6e37322cb..58c5bba2d 100644
--- a/MediaBrowser.Server.Mono/Networking/CertificateGenerator.cs
+++ b/MediaBrowser.Server.Mono/Networking/CertificateGenerator.cs
@@ -1,8 +1,8 @@
using MediaBrowser.Model.Logging;
-using Mono.Security.X509;
using System;
using System.Collections;
using System.Security.Cryptography;
+using MediaBrowser.Server.Mono.Security;
namespace MediaBrowser.Server.Mono.Networking
{
diff --git a/MediaBrowser.Server.Mono/Program.cs b/MediaBrowser.Server.Mono/Program.cs
index 2a0609449..32de45242 100644
--- a/MediaBrowser.Server.Mono/Program.cs
+++ b/MediaBrowser.Server.Mono/Program.cs
@@ -79,7 +79,7 @@ namespace MediaBrowser.Server.Mono
var fileSystem = new ManagedFileSystem(new PatternsLogger(logManager.GetLogger("FileSystem")), false, false);
fileSystem.AddShortcutHandler(new MbLinkShortcutHandler(fileSystem));
- var nativeApp = new NativeApp(options);
+ var nativeApp = new NativeApp(options, logManager.GetLogger("App"));
_appHost = new ApplicationHost(appPaths, logManager, options, fileSystem, "emby.mono.zip", nativeApp);
diff --git a/MediaBrowser.Server.Mono/Security/ASN1.cs b/MediaBrowser.Server.Mono/Security/ASN1.cs
new file mode 100644
index 000000000..2fcbde7c1
--- /dev/null
+++ b/MediaBrowser.Server.Mono/Security/ASN1.cs
@@ -0,0 +1,339 @@
+//
+// 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 MediaBrowser.Server.Mono.Security {
+
+ // References:
+ // a. ITU ASN.1 standards (free download)
+ // http://www.itu.int/ITU-T/studygroups/com17/languages/
+
+ public 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..f25a5275a
--- /dev/null
+++ b/MediaBrowser.Server.Mono/Security/ASN1Convert.cs
@@ -0,0 +1,206 @@
+//
+// 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.Globalization;
+using System.Security.Cryptography;
+using System.Text;
+
+namespace MediaBrowser.Server.Mono.Security {
+
+ // References:
+ // a. ITU ASN.1 standards (free download)
+ // http://www.itu.int/ITU-T/studygroups/com17/languages/
+
+ public 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..29b6ee023
--- /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 MediaBrowser.Server.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..62c28bd27
--- /dev/null
+++ b/MediaBrowser.Server.Mono/Security/CryptoConvert.cs
@@ -0,0 +1,744 @@
+//
+// 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 MediaBrowser.Server.Mono.Security {
+
+ public 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);
+ }
+
+ 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;
+ }
+ }
+ 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);
+ }
+
+ 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;
+ }
+ }
+ 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);
+ 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);
+ }
+ 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..86ed6f2d8
--- /dev/null
+++ b/MediaBrowser.Server.Mono/Security/PKCS1.cs
@@ -0,0 +1,490 @@
+//
+// 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 MediaBrowser.Server.Mono.Security {
+
+ // References:
+ // a. PKCS#1: RSA Cryptography Standard
+ // http://www.rsasecurity.com/rsalabs/pkcs/pkcs-1/index.html
+
+ public 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..b5da09c63
--- /dev/null
+++ b/MediaBrowser.Server.Mono/Security/PKCS12.cs
@@ -0,0 +1,1933 @@
+//
+// 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;
+
+namespace MediaBrowser.Server.Mono.Security {
+
+ public 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 () {}
+ }
+
+ public 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; }
+ }
+ }
+
+
+ public 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;
+ sa = SymmetricAlgorithm.Create(algorithm);
+ 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;
+ }
+ }
+ }
+}
diff --git a/MediaBrowser.Server.Mono/Security/PKCS7.cs b/MediaBrowser.Server.Mono/Security/PKCS7.cs
new file mode 100644
index 000000000..7a34580e9
--- /dev/null
+++ b/MediaBrowser.Server.Mono/Security/PKCS7.cs
@@ -0,0 +1,1011 @@
+//
+// 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;
+
+namespace MediaBrowser.Server.Mono.Security {
+
+ public 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..b2f28f318
--- /dev/null
+++ b/MediaBrowser.Server.Mono/Security/PKCS8.cs
@@ -0,0 +1,494 @@
+//
+// 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;
+
+namespace MediaBrowser.Server.Mono.Security {
+
+ public 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) {
+ // 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);
+ }
+ 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..d77158e45
--- /dev/null
+++ b/MediaBrowser.Server.Mono/Security/X501Name.cs
@@ -0,0 +1,392 @@
+//
+// 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;
+
+namespace MediaBrowser.Server.Mono.Security {
+
+ // 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
+ */
+ public 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..4801f3d8a
--- /dev/null
+++ b/MediaBrowser.Server.Mono/Security/X509Builder.cs
@@ -0,0 +1,152 @@
+//
+// 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;
+
+namespace MediaBrowser.Server.Mono.Security {
+
+ 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..fa817d959
--- /dev/null
+++ b/MediaBrowser.Server.Mono/Security/X509Certificate.cs
@@ -0,0 +1,562 @@
+//
+// 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 System.Security.Permissions;
+using System.Text;
+
+namespace MediaBrowser.Server.Mono.Security {
+
+ // 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/
+
+ public class X509Certificate : ISerializable
+ {
+
+ 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..9b51d9e4e
--- /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 MediaBrowser.Server.Mono.Security {
+ // 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..6bb465b28
--- /dev/null
+++ b/MediaBrowser.Server.Mono/Security/X509CertificateCollection.cs
@@ -0,0 +1,200 @@
+//
+// 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 MediaBrowser.Server.Mono.Security {
+
+ [Serializable]
+ public 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..984c3542b
--- /dev/null
+++ b/MediaBrowser.Server.Mono/Security/X509Extension.cs
@@ -0,0 +1,207 @@
+//
+// 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;
+
+namespace MediaBrowser.Server.Mono.Security {
+ /*
+ * Extension ::= SEQUENCE {
+ * extnID OBJECT IDENTIFIER,
+ * critical BOOLEAN DEFAULT FALSE,
+ * extnValue OCTET STRING
+ * }
+ */
+ public 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..b86fe1c40
--- /dev/null
+++ b/MediaBrowser.Server.Mono/Security/X509Extensions.cs
@@ -0,0 +1,194 @@
+//
+// 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;
+
+namespace MediaBrowser.Server.Mono.Security {
+ /*
+ * Extensions ::= SEQUENCE SIZE (1..MAX) OF Extension
+ *
+ * Note: 1..MAX -> There shouldn't be 0 Extensions in the ASN1 structure
+ */
+ public 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..a61d31ad9
--- /dev/null
+++ b/MediaBrowser.Server.Mono/Security/X520Attributes.cs
@@ -0,0 +1,345 @@
+//
+// 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.Text;
+
+namespace MediaBrowser.Server.Mono.Security {
+
+ // 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
+ */
+ public 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 dd7e3cc01..b7ea5bdad 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; }
@@ -276,7 +274,7 @@ namespace MediaBrowser.Server.Startup.Common
{
get
{
- return "Media Browser Server";
+ return "Emby Server";
}
}
@@ -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);
@@ -399,7 +418,7 @@ namespace MediaBrowser.Server.Startup.Common
RegisterSingleInstance(ServerConfigurationManager);
- LocalizationManager = new LocalizationManager(ServerConfigurationManager, FileSystemManager, JsonSerializer);
+ LocalizationManager = new LocalizationManager(ServerConfigurationManager, FileSystemManager, JsonSerializer, LogManager.GetLogger("LocalizationManager"));
RegisterSingleInstance(LocalizationManager);
RegisterSingleInstance<IBlurayExaminer>(() => new BdInfoExaminer());
@@ -410,15 +429,14 @@ namespace MediaBrowser.Server.Startup.Common
UserRepository = await GetUserRepository().ConfigureAwait(false);
RegisterSingleInstance(UserRepository);
- DisplayPreferencesRepository = new SqliteDisplayPreferencesRepository(LogManager, JsonSerializer, ApplicationPaths);
+ var displayPreferencesRepo = new SqliteDisplayPreferencesRepository(LogManager, JsonSerializer, ApplicationPaths, NativeApp.GetDbConnector());
+ DisplayPreferencesRepository = displayPreferencesRepo;
RegisterSingleInstance(DisplayPreferencesRepository);
- ItemRepository = new SqliteItemRepository(ApplicationPaths, JsonSerializer, LogManager);
+ var itemRepo = new SqliteItemRepository(ServerConfigurationManager, JsonSerializer, LogManager, NativeApp.GetDbConnector());
+ ItemRepository = itemRepo;
RegisterSingleInstance(ItemRepository);
- ProviderRepository = new SqliteProviderInfoRepository(LogManager, ApplicationPaths);
- RegisterSingleInstance(ProviderRepository);
-
FileOrganizationRepository = await GetFileOrganizationRepository().ConfigureAwait(false);
RegisterSingleInstance(FileOrganizationRepository);
@@ -440,12 +458,9 @@ namespace MediaBrowser.Server.Startup.Common
LibraryMonitor = new LibraryMonitor(LogManager, TaskManager, LibraryManager, ServerConfigurationManager, FileSystemManager, this);
RegisterSingleInstance(LibraryMonitor);
- ProviderManager = new ProviderManager(HttpClient, ServerConfigurationManager, LibraryMonitor, LogManager, FileSystemManager, ApplicationPaths, () => LibraryManager);
+ 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");
@@ -462,7 +477,7 @@ namespace MediaBrowser.Server.Startup.Common
ImageProcessor = GetImageProcessor();
RegisterSingleInstance(ImageProcessor);
- TVSeriesManager = new TVSeriesManager(UserManager, UserDataManager, LibraryManager);
+ TVSeriesManager = new TVSeriesManager(UserManager, UserDataManager, LibraryManager, ServerConfigurationManager);
RegisterSingleInstance(TVSeriesManager);
SyncManager = new SyncManager(LibraryManager, SyncRepository, ImageProcessor, LogManager.GetLogger("SyncManager"), UserManager, () => DtoService, this, TVSeriesManager, () => MediaEncoder, FileSystemManager, () => SubtitleEncoder, ServerConfigurationManager, UserDataManager, () => MediaSourceManager, JsonSerializer, TaskManager);
@@ -474,7 +489,7 @@ namespace MediaBrowser.Server.Startup.Common
var encryptionManager = new EncryptionManager();
RegisterSingleInstance<IEncryptionManager>(encryptionManager);
- ConnectManager = new ConnectManager(LogManager.GetLogger("Connect"), ApplicationPaths, JsonSerializer, encryptionManager, HttpClient, this, ServerConfigurationManager, UserManager, ProviderManager, SecurityManager, FileSystemManager);
+ ConnectManager = new ConnectManager(LogManager.GetLogger("ConnectManager"), ApplicationPaths, JsonSerializer, encryptionManager, HttpClient, this, ServerConfigurationManager, UserManager, ProviderManager, SecurityManager, FileSystemManager);
RegisterSingleInstance(ConnectManager);
DeviceManager = new DeviceManager(new DeviceRepository(ApplicationPaths, JsonSerializer, LogManager.GetLogger("DeviceManager"), FileSystemManager), UserManager, FileSystemManager, LibraryMonitor, ServerConfigurationManager, LogManager.GetLogger("DeviceManager"), NetworkManager);
@@ -515,7 +530,7 @@ namespace MediaBrowser.Server.Startup.Common
UserViewManager = new UserViewManager(LibraryManager, LocalizationManager, UserManager, ChannelManager, LiveTvManager, ServerConfigurationManager);
RegisterSingleInstance(UserViewManager);
- var contentDirectory = new ContentDirectory(dlnaManager, UserDataManager, ImageProcessor, LibraryManager, ServerConfigurationManager, UserManager, LogManager.GetLogger("UpnpContentDirectory"), HttpClient, LocalizationManager, ChannelManager, MediaSourceManager, UserViewManager);
+ var contentDirectory = new ContentDirectory(dlnaManager, UserDataManager, ImageProcessor, LibraryManager, ServerConfigurationManager, UserManager, LogManager.GetLogger("UpnpContentDirectory"), HttpClient, LocalizationManager, ChannelManager, MediaSourceManager, UserViewManager, () => MediaEncoder);
RegisterSingleInstance<IContentDirectory>(contentDirectory);
var mediaRegistrar = new MediaReceiverRegistrar(LogManager.GetLogger("MediaReceiverRegistrar"), HttpClient, ServerConfigurationManager);
@@ -540,7 +555,7 @@ namespace MediaBrowser.Server.Startup.Common
RegisterSingleInstance(NativeApp.GetPowerManagement());
- var sharingRepo = new SharingRepository(LogManager, ApplicationPaths);
+ var sharingRepo = new SharingRepository(LogManager, ApplicationPaths, NativeApp.GetDbConnector());
await sharingRepo.Initialize().ConfigureAwait(false);
RegisterSingleInstance<ISharingManager>(new SharingManager(sharingRepo, ServerConfigurationManager, LibraryManager, this));
@@ -558,9 +573,13 @@ namespace MediaBrowser.Server.Startup.Common
SubtitleEncoder = new SubtitleEncoder(LibraryManager, LogManager.GetLogger("SubtitleEncoder"), ApplicationPaths, FileSystemManager, MediaEncoder, JsonSerializer, HttpClient, MediaSourceManager);
RegisterSingleInstance(SubtitleEncoder);
- await ConfigureDisplayPreferencesRepositories().ConfigureAwait(false);
- await ConfigureItemRepositories().ConfigureAwait(false);
- await ConfigureUserDataRepositories().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);
@@ -618,14 +637,21 @@ namespace MediaBrowser.Server.Startup.Common
/// <returns>Task.</returns>
private async Task RegisterMediaEncoder(IProgress<double> progress)
{
- var info = await new FFMpegDownloader(Logger, ApplicationPaths, HttpClient, ZipClient, FileSystemManager, NativeApp.Environment)
+ 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,
@@ -634,18 +660,12 @@ namespace MediaBrowser.Server.Startup.Common
ChannelManager,
SessionManager,
() => SubtitleEncoder,
- () => MediaSourceManager);
+ () => MediaSourceManager,
+ HttpClient,
+ ZipClient);
MediaEncoder = mediaEncoder;
RegisterSingleInstance(MediaEncoder);
-
- Task.Run(() =>
- {
- var result = new FFmpegValidator(Logger, ApplicationPaths, FileSystemManager).Validate(info);
-
- mediaEncoder.SetAvailableDecoders(result.Item1);
- mediaEncoder.SetAvailableEncoders(result.Item2);
- });
}
/// <summary>
@@ -656,7 +676,7 @@ 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().ConfigureAwait(false);
@@ -675,7 +695,7 @@ 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().ConfigureAwait(false);
@@ -684,7 +704,7 @@ namespace MediaBrowser.Server.Startup.Common
private async Task<IAuthenticationRepository> GetAuthenticationRepository()
{
- var repo = new AuthenticationRepository(LogManager, ServerConfigurationManager.ApplicationPaths);
+ var repo = new AuthenticationRepository(LogManager, ServerConfigurationManager.ApplicationPaths, NativeApp.GetDbConnector());
await repo.Initialize().ConfigureAwait(false);
@@ -693,7 +713,7 @@ namespace MediaBrowser.Server.Startup.Common
private async Task<IActivityRepository> GetActivityLogRepository()
{
- var repo = new ActivityRepository(LogManager, ServerConfigurationManager.ApplicationPaths);
+ var repo = new ActivityRepository(LogManager, ServerConfigurationManager.ApplicationPaths, NativeApp.GetDbConnector());
await repo.Initialize().ConfigureAwait(false);
@@ -702,7 +722,7 @@ namespace MediaBrowser.Server.Startup.Common
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().ConfigureAwait(false);
@@ -712,10 +732,9 @@ 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().ConfigureAwait(false);
@@ -725,41 +744,6 @@ namespace MediaBrowser.Server.Startup.Common
}
/// <summary>
- /// Configures the repositories.
- /// </summary>
- /// <returns>Task.</returns>
- private async Task ConfigureDisplayPreferencesRepositories()
- {
- await DisplayPreferencesRepository.Initialize().ConfigureAwait(false);
- }
-
- /// <summary>
- /// Configures the item repositories.
- /// </summary>
- /// <returns>Task.</returns>
- private async Task ConfigureItemRepositories()
- {
- await ItemRepository.Initialize().ConfigureAwait(false);
-
- await ProviderRepository.Initialize().ConfigureAwait(false);
-
- ((LibraryManager)LibraryManager).ItemRepository = ItemRepository;
- }
-
- /// <summary>
- /// Configures the user data repositories.
- /// </summary>
- /// <returns>Task.</returns>
- private async Task ConfigureUserDataRepositories()
- {
- var repo = new SqliteUserDataRepository(LogManager, ApplicationPaths);
-
- await repo.Initialize().ConfigureAwait(false);
-
- ((UserDataManager)UserDataManager).Repository = repo;
- }
-
- /// <summary>
/// Dirty hacks
/// </summary>
private void SetStaticProperties()
@@ -814,15 +798,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>());
@@ -842,19 +822,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 (hosts.Count == 0)
+ {
+ hosts.Add("+");
+ }
- if (!string.IsNullOrWhiteSpace(CertificatePath))
+ if (!hosts.Contains("+", StringComparer.OrdinalIgnoreCase))
{
- prefixes.Add("https://+:" + ServerConfigurationManager.Configuration.HttpsPortNumber + "/");
+ 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 prefixes;
+ 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>
@@ -1073,8 +1091,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,
@@ -1105,8 +1125,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
};
}
@@ -1123,29 +1145,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)
@@ -1165,16 +1184,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);
@@ -1360,7 +1376,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);
@@ -1404,5 +1419,10 @@ namespace MediaBrowser.Server.Startup.Common
return externalDns;
}
}
+
+ public void LaunchUrl(string url)
+ {
+ NativeApp.LaunchUrl(url);
+ }
}
}
diff --git a/MediaBrowser.Server.Startup.Common/Browser/BrowserLauncher.cs b/MediaBrowser.Server.Startup.Common/Browser/BrowserLauncher.cs
index a4504f25a..db48d1110 100644
--- a/MediaBrowser.Server.Startup.Common/Browser/BrowserLauncher.cs
+++ b/MediaBrowser.Server.Startup.Common/Browser/BrowserLauncher.cs
@@ -15,87 +15,58 @@ namespace MediaBrowser.Server.Startup.Common.Browser
/// </summary>
/// <param name="page">The page.</param>
/// <param name="appHost">The app host.</param>
- /// <param name="logger">The logger.</param>
- public static void OpenDashboardPage(string page, IServerApplicationHost appHost, ILogger logger)
+ public static void OpenDashboardPage(string page, IServerApplicationHost appHost)
{
var url = appHost.GetLocalApiUrl("localhost") + "/web/" + page;
- OpenUrl(url, logger);
+ OpenUrl(appHost, url);
}
/// <summary>
/// Opens the community.
/// </summary>
- /// <param name="logger">The logger.</param>
- public static void OpenCommunity(ILogger logger)
+ public static void OpenCommunity(IServerApplicationHost appHost)
{
- OpenUrl("http://emby.media/community", logger);
+ OpenUrl(appHost, "http://emby.media/community");
}
/// <summary>
/// Opens the web client.
/// </summary>
/// <param name="appHost">The app host.</param>
- /// <param name="logger">The logger.</param>
- public static void OpenWebClient(IServerApplicationHost appHost, ILogger logger)
+ public static void OpenWebClient(IServerApplicationHost appHost)
{
- OpenDashboardPage("index.html", appHost, logger);
+ OpenDashboardPage("index.html", appHost);
}
/// <summary>
/// Opens the dashboard.
/// </summary>
/// <param name="appHost">The app host.</param>
- /// <param name="logger">The logger.</param>
- public static void OpenDashboard(IServerApplicationHost appHost, ILogger logger)
+ public static void OpenDashboard(IServerApplicationHost appHost)
{
- OpenDashboardPage("dashboard.html", appHost, logger);
+ OpenDashboardPage("dashboard.html", appHost);
}
/// <summary>
/// Opens the URL.
/// </summary>
/// <param name="url">The URL.</param>
- /// <param name="logger">The logger.</param>
- private static void OpenUrl(string url, ILogger logger)
+ private static void OpenUrl(IServerApplicationHost appHost, string url)
{
- var process = new Process
- {
- StartInfo = new ProcessStartInfo
- {
- FileName = url
- },
-
- EnableRaisingEvents = true,
- };
-
- process.Exited += ProcessExited;
-
try
{
- process.Start();
+ appHost.LaunchUrl(url);
+ }
+ catch (NotImplementedException)
+ {
+
}
catch (Exception ex)
{
- logger.ErrorException("Error launching url: {0}", ex, url);
-
- Console.WriteLine("Error launching url: {0}", ex.Message);
+ Console.WriteLine("Error launching url: " + url);
Console.WriteLine(ex.Message);
-
-//#if !__MonoCS__
-// System.Windows.Forms.MessageBox.Show("There was an error launching your web browser. Please check your default browser settings.");
-//#endif
}
}
-
- /// <summary>
- /// Processes the exited.
- /// </summary>
- /// <param name="sender">The sender.</param>
- /// <param name="e">The <see cref="EventArgs" /> instance containing the event data.</param>
- private static void ProcessExited(object sender, EventArgs e)
- {
- ((Process)sender).Dispose();
- }
}
}
diff --git a/MediaBrowser.Server.Startup.Common/EntryPoints/KeepServerAwake.cs b/MediaBrowser.Server.Startup.Common/EntryPoints/KeepServerAwake.cs
index 20d4c6b2a..dbfd6f4e8 100644
--- a/MediaBrowser.Server.Startup.Common/EntryPoints/KeepServerAwake.cs
+++ b/MediaBrowser.Server.Startup.Common/EntryPoints/KeepServerAwake.cs
@@ -27,28 +27,27 @@ namespace MediaBrowser.Server.Startup.Common.EntryPoints
_timer = new PeriodicTimer(obj =>
{
var now = DateTime.UtcNow;
- if (_sessionManager.Sessions.Any(i => (now - i.LastActivityDate).TotalMinutes < 15))
+ var nativeApp = ((ApplicationHost)_appHost).NativeApp;
+
+ try
+ {
+ if (_sessionManager.Sessions.Any(i => (now - i.LastActivityDate).TotalMinutes < 15))
+ {
+ nativeApp.PreventSystemStandby();
+ }
+ else
+ {
+ nativeApp.AllowSystemStandby();
+ }
+ }
+ catch (Exception ex)
{
- KeepAlive();
+ _logger.ErrorException("Error resetting system standby timer", ex);
}
}, null, TimeSpan.FromMinutes(5), TimeSpan.FromMinutes(5));
}
- private void KeepAlive()
- {
- var nativeApp = ((ApplicationHost)_appHost).NativeApp;
-
- try
- {
- nativeApp.PreventSystemStandby();
- }
- catch (Exception ex)
- {
- _logger.ErrorException("Error resetting system standby timer", ex);
- }
- }
-
public void Dispose()
{
if (_timer != null)
diff --git a/MediaBrowser.Server.Startup.Common/EntryPoints/StartupWizard.cs b/MediaBrowser.Server.Startup.Common/EntryPoints/StartupWizard.cs
index 854fa44c1..f9d173c59 100644
--- a/MediaBrowser.Server.Startup.Common/EntryPoints/StartupWizard.cs
+++ b/MediaBrowser.Server.Startup.Common/EntryPoints/StartupWizard.cs
@@ -46,7 +46,7 @@ namespace MediaBrowser.Server.Startup.Common.EntryPoints
/// </summary>
private void LaunchStartupWizard()
{
- BrowserLauncher.OpenDashboardPage("wizardstart.html", _appHost, _logger);
+ BrowserLauncher.OpenDashboardPage("wizardstart.html", _appHost);
}
/// <summary>
diff --git a/MediaBrowser.Server.Startup.Common/FFMpeg/FFMpegDownloadInfo.cs b/MediaBrowser.Server.Startup.Common/FFMpeg/FFMpegDownloadInfo.cs
deleted file mode 100644
index 60cb50e30..000000000
--- a/MediaBrowser.Server.Startup.Common/FFMpeg/FFMpegDownloadInfo.cs
+++ /dev/null
@@ -1,142 +0,0 @@
-
-namespace MediaBrowser.Server.Startup.Common.FFMpeg
-{
- public class FFMpegDownloadInfo
- {
- public string Version { get; set; }
- public string FFMpegFilename { get; set; }
- public string FFProbeFilename { get; set; }
- public string ArchiveType { get; set; }
- public string[] DownloadUrls { get; set; }
-
- public FFMpegDownloadInfo()
- {
- DownloadUrls = new string[] { };
- Version = "Path";
- FFMpegFilename = "ffmpeg";
- FFProbeFilename = "ffprobe";
- }
-
- public static FFMpegDownloadInfo GetInfo(NativeEnvironment environment)
- {
- var info = new FFMpegDownloadInfo();
-
- // Windows builds: http://ffmpeg.zeranoe.com/builds/
- // Linux builds: http://johnvansickle.com/ffmpeg/
- // OS X builds: http://ffmpegmac.net/
- // OS X x64: http://www.evermeet.cx/ffmpeg/
-
- switch (environment.OperatingSystem)
- {
- case OperatingSystem.Bsd:
- break;
- case OperatingSystem.Linux:
-
- 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;
-
- case OperatingSystem.Windows:
-
- info.FFMpegFilename = "ffmpeg.exe";
- info.FFProbeFilename = "ffprobe.exe";
- info.Version = "20160131";
- info.ArchiveType = "7z";
-
- switch (environment.SystemArchitecture)
- {
- case Architecture.X86_X64:
- break;
- case Architecture.X86:
- break;
- }
- break;
- }
-
- info.DownloadUrls = GetDownloadUrls(environment);
-
- return info;
- }
-
- private static string[] GetDownloadUrls(NativeEnvironment environment)
- {
- switch (environment.OperatingSystem)
- {
- case OperatingSystem.Windows:
-
- switch (environment.SystemArchitecture)
- {
- case Architecture.X86_X64:
- return new[]
- {
- "https://github.com/MediaBrowser/Emby.Resources/raw/master/ffmpeg/windows/ffmpeg-20160131-win64.7z",
- "http://ffmpeg.zeranoe.com/builds/win64/static/ffmpeg-20151109-git-480bad7-win64-static.7z"
- };
- case Architecture.X86:
- return new[]
- {
- "https://github.com/MediaBrowser/Emby.Resources/raw/master/ffmpeg/windows/ffmpeg-20160131-win32.7z",
- "http://ffmpeg.zeranoe.com/builds/win32/static/ffmpeg-20151109-git-480bad7-win32-static.7z"
- };
- }
- break;
-
- 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:
- 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;
- }
-
- // No version available
- return new string[] { };
- }
- }
-} \ No newline at end of file
diff --git a/MediaBrowser.Server.Startup.Common/FFMpeg/FFMpegDownloader.cs b/MediaBrowser.Server.Startup.Common/FFMpeg/FFMpegDownloader.cs
deleted file mode 100644
index 000568c15..000000000
--- a/MediaBrowser.Server.Startup.Common/FFMpeg/FFMpegDownloader.cs
+++ /dev/null
@@ -1,439 +0,0 @@
-using MediaBrowser.Common.Configuration;
-using MediaBrowser.Common.Net;
-using MediaBrowser.Model.IO;
-using MediaBrowser.Model.Logging;
-using MediaBrowser.Model.Net;
-using Mono.Unix.Native;
-using System;
-using System.Collections.Generic;
-using System.IO;
-using System.Linq;
-using System.Text;
-using System.Threading;
-using System.Threading.Tasks;
-using CommonIO;
-
-namespace MediaBrowser.Server.Startup.Common.FFMpeg
-{
- public class FFMpegDownloader
- {
- private readonly IHttpClient _httpClient;
- private readonly IApplicationPaths _appPaths;
- private readonly ILogger _logger;
- private readonly IZipClient _zipClient;
- private readonly IFileSystem _fileSystem;
- private readonly NativeEnvironment _environment;
-
- private readonly string[] _fontUrls =
- {
- "https://github.com/MediaBrowser/MediaBrowser.Resources/raw/master/ffmpeg/ARIALUNI.7z"
- };
-
- public FFMpegDownloader(ILogger logger, IApplicationPaths appPaths, IHttpClient httpClient, IZipClient zipClient, IFileSystem fileSystem, NativeEnvironment environment)
- {
- _logger = logger;
- _appPaths = appPaths;
- _httpClient = httpClient;
- _zipClient = zipClient;
- _fileSystem = fileSystem;
- _environment = environment;
- }
-
- public async Task<FFMpegInfo> GetFFMpegInfo(NativeEnvironment environment, StartupOptions options, IProgress<double> progress)
- {
- var customffMpegPath = options.GetOption("-ffmpeg");
- var customffProbePath = options.GetOption("-ffprobe");
-
- if (!string.IsNullOrWhiteSpace(customffMpegPath) && !string.IsNullOrWhiteSpace(customffProbePath))
- {
- return new FFMpegInfo
- {
- ProbePath = customffProbePath,
- EncoderPath = customffMpegPath,
- Version = "custom"
- };
- }
-
- var downloadInfo = FFMpegDownloadInfo.GetInfo(environment);
-
- var version = downloadInfo.Version;
-
- if (string.Equals(version, "path", StringComparison.OrdinalIgnoreCase))
- {
- return new FFMpegInfo
- {
- ProbePath = downloadInfo.FFProbeFilename,
- EncoderPath = downloadInfo.FFMpegFilename,
- Version = version
- };
- }
-
- var rootEncoderPath = Path.Combine(_appPaths.ProgramDataPath, "ffmpeg");
- var versionedDirectoryPath = Path.Combine(rootEncoderPath, version);
-
- var info = new FFMpegInfo
- {
- ProbePath = Path.Combine(versionedDirectoryPath, downloadInfo.FFProbeFilename),
- EncoderPath = Path.Combine(versionedDirectoryPath, downloadInfo.FFMpegFilename),
- Version = version
- };
-
- _fileSystem.CreateDirectory(versionedDirectoryPath);
-
- var excludeFromDeletions = new List<string> { versionedDirectoryPath };
-
- if (!_fileSystem.FileExists(info.ProbePath) || !_fileSystem.FileExists(info.EncoderPath))
- {
- // ffmpeg not present. See if there's an older version we can start with
- var existingVersion = GetExistingVersion(info, rootEncoderPath);
-
- // No older version. Need to download and block until complete
- if (existingVersion == null)
- {
- await DownloadFFMpeg(downloadInfo, versionedDirectoryPath, progress).ConfigureAwait(false);
- }
- 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);
- }
- }
-
- await DownloadFonts(versionedDirectoryPath).ConfigureAwait(false);
-
- DeleteOlderFolders(Path.GetDirectoryName(versionedDirectoryPath), excludeFromDeletions);
-
- // Allow just one of these to be overridden, if desired.
- if (!string.IsNullOrWhiteSpace(customffMpegPath))
- {
- info.EncoderPath = customffMpegPath;
- }
- if (!string.IsNullOrWhiteSpace(customffProbePath))
- {
- info.EncoderPath = customffProbePath;
- }
-
- return info;
- }
-
- private void DeleteOlderFolders(string path, IEnumerable<string> excludeFolders)
- {
- var folders = Directory.GetDirectories(path)
- .Where(i => !excludeFolders.Contains(i, StringComparer.OrdinalIgnoreCase))
- .ToList();
-
- foreach (var folder in folders)
- {
- DeleteFolder(folder);
- }
- }
-
- private void DeleteFolder(string path)
- {
- try
- {
- _fileSystem.DeleteDirectory(path, true);
- }
- catch (Exception ex)
- {
- _logger.ErrorException("Error deleting {0}", ex, path);
- }
- }
-
- private FFMpegInfo GetExistingVersion(FFMpegInfo info, string rootEncoderPath)
- {
- var encoderFilename = Path.GetFileName(info.EncoderPath);
- var probeFilename = Path.GetFileName(info.ProbePath);
-
- foreach (var directory in Directory.EnumerateDirectories(rootEncoderPath, "*", SearchOption.TopDirectoryOnly)
- .ToList())
- {
- var allFiles = Directory.EnumerateFiles(directory, "*", SearchOption.AllDirectories).ToList();
-
- var encoder = allFiles.FirstOrDefault(i => string.Equals(Path.GetFileName(i), encoderFilename, StringComparison.OrdinalIgnoreCase));
- var probe = allFiles.FirstOrDefault(i => string.Equals(Path.GetFileName(i), probeFilename, StringComparison.OrdinalIgnoreCase));
-
- if (!string.IsNullOrWhiteSpace(encoder) &&
- !string.IsNullOrWhiteSpace(probe))
- {
- return new FFMpegInfo
- {
- EncoderPath = encoder,
- ProbePath = probe,
- Version = Path.GetFileName(Path.GetDirectoryName(probe))
- };
- }
- }
-
- return null;
- }
-
- private async void DownloadFFMpegInBackground(FFMpegDownloadInfo 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(FFMpegDownloadInfo downloadinfo, string directory, IProgress<double> progress)
- {
- foreach (var url in downloadinfo.DownloadUrls)
- {
- progress.Report(0);
-
- try
- {
- var tempFile = await _httpClient.GetTempFile(new HttpRequestOptions
- {
- Url = url,
- CancellationToken = CancellationToken.None,
- Progress = progress
-
- }).ConfigureAwait(false);
-
- ExtractFFMpeg(downloadinfo, tempFile, directory);
- return;
- }
- 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}\"");
- }
- else
- {
- throw new ApplicationException("Unable to download required components. Please try again later.");
- }
- }
-
- private void ExtractFFMpeg(FFMpegDownloadInfo downloadinfo, string tempFile, string targetFolder)
- {
- _logger.Info("Extracting ffmpeg from {0}", tempFile);
-
- var tempFolder = Path.Combine(_appPaths.TempDirectory, Guid.NewGuid().ToString());
-
- _fileSystem.CreateDirectory(tempFolder);
-
- try
- {
- ExtractArchive(downloadinfo, tempFile, tempFolder);
-
- var files = Directory.EnumerateFiles(tempFolder, "*", SearchOption.AllDirectories)
- .ToList();
-
- foreach (var file in files.Where(i =>
- {
- var filename = Path.GetFileName(i);
-
- return
- string.Equals(filename, downloadinfo.FFProbeFilename, StringComparison.OrdinalIgnoreCase) ||
- string.Equals(filename, downloadinfo.FFMpegFilename, StringComparison.OrdinalIgnoreCase);
- }))
- {
- var targetFile = Path.Combine(targetFolder, Path.GetFileName(file));
- _fileSystem.CopyFile(file, targetFile, true);
- SetFilePermissions(targetFile);
- }
- }
- finally
- {
- DeleteFile(tempFile);
- }
- }
-
- private void SetFilePermissions(string path)
- {
- // Linux: File permission to 666, and user's execute bit
- if (_environment.OperatingSystem == OperatingSystem.Bsd || _environment.OperatingSystem == OperatingSystem.Linux || _environment.OperatingSystem == OperatingSystem.Osx)
- {
- _logger.Info("Syscall.chmod {0} FilePermissions.DEFFILEMODE | FilePermissions.S_IRWXU | FilePermissions.S_IXGRP | FilePermissions.S_IXOTH", path);
-
- Syscall.chmod(path, FilePermissions.DEFFILEMODE | FilePermissions.S_IRWXU | FilePermissions.S_IXGRP | FilePermissions.S_IXOTH);
- }
- }
-
- private void ExtractArchive(FFMpegDownloadInfo downloadinfo, string archivePath, string targetPath)
- {
- _logger.Info("Extracting {0} to {1}", archivePath, targetPath);
-
- if (string.Equals(downloadinfo.ArchiveType, "7z", StringComparison.OrdinalIgnoreCase))
- {
- _zipClient.ExtractAllFrom7z(archivePath, targetPath, true);
- }
- else if (string.Equals(downloadinfo.ArchiveType, "gz", StringComparison.OrdinalIgnoreCase))
- {
- _zipClient.ExtractAllFromTar(archivePath, targetPath, true);
- }
- }
- private void Extract7zArchive(string archivePath, string targetPath)
- {
- _logger.Info("Extracting {0} to {1}", archivePath, targetPath);
-
- _zipClient.ExtractAllFrom7z(archivePath, targetPath, true);
- }
-
- private void DeleteFile(string path)
- {
- try
- {
- _fileSystem.DeleteFile(path);
- }
- catch (IOException ex)
- {
- _logger.ErrorException("Error deleting temp file {0}", ex, path);
- }
- }
-
- /// <summary>
- /// Extracts the fonts.
- /// </summary>
- /// <param name="targetPath">The target path.</param>
- /// <returns>Task.</returns>
- private async Task DownloadFonts(string targetPath)
- {
- try
- {
- var fontsDirectory = Path.Combine(targetPath, "fonts");
-
- _fileSystem.CreateDirectory(fontsDirectory);
-
- const string fontFilename = "ARIALUNI.TTF";
-
- var fontFile = Path.Combine(fontsDirectory, fontFilename);
-
- if (_fileSystem.FileExists(fontFile))
- {
- await WriteFontConfigFile(fontsDirectory).ConfigureAwait(false);
- }
- else
- {
- // Kick this off, but no need to wait on it
- Task.Run(async () =>
- {
- await DownloadFontFile(fontsDirectory, fontFilename, new Progress<double>()).ConfigureAwait(false);
-
- await WriteFontConfigFile(fontsDirectory).ConfigureAwait(false);
- });
- }
- }
- catch (HttpException ex)
- {
- // Don't let the server crash because of this
- _logger.ErrorException("Error downloading ffmpeg font files", ex);
- }
- catch (Exception ex)
- {
- // Don't let the server crash because of this
- _logger.ErrorException("Error writing ffmpeg font files", ex);
- }
- }
-
- /// <summary>
- /// Downloads the font file.
- /// </summary>
- /// <param name="fontsDirectory">The fonts directory.</param>
- /// <param name="fontFilename">The font filename.</param>
- /// <returns>Task.</returns>
- private async Task DownloadFontFile(string fontsDirectory, string fontFilename, IProgress<double> progress)
- {
- var existingFile = Directory
- .EnumerateFiles(_appPaths.ProgramDataPath, fontFilename, SearchOption.AllDirectories)
- .FirstOrDefault();
-
- if (existingFile != null)
- {
- try
- {
- _fileSystem.CopyFile(existingFile, Path.Combine(fontsDirectory, fontFilename), true);
- return;
- }
- catch (IOException ex)
- {
- // Log this, but don't let it fail the operation
- _logger.ErrorException("Error copying file", ex);
- }
- }
-
- string tempFile = null;
-
- foreach (var url in _fontUrls)
- {
- progress.Report(0);
-
- try
- {
- tempFile = await _httpClient.GetTempFile(new HttpRequestOptions
- {
- Url = url,
- Progress = progress
-
- }).ConfigureAwait(false);
-
- break;
- }
- catch (Exception ex)
- {
- // The core can function without the font file, so handle this
- _logger.ErrorException("Failed to download ffmpeg font file from {0}", ex, url);
- }
- }
-
- if (string.IsNullOrEmpty(tempFile))
- {
- return;
- }
-
- Extract7zArchive(tempFile, fontsDirectory);
-
- try
- {
- _fileSystem.DeleteFile(tempFile);
- }
- catch (IOException ex)
- {
- // Log this, but don't let it fail the operation
- _logger.ErrorException("Error deleting temp file {0}", ex, tempFile);
- }
- }
-
- /// <summary>
- /// Writes the font config file.
- /// </summary>
- /// <param name="fontsDirectory">The fonts directory.</param>
- /// <returns>Task.</returns>
- private async Task WriteFontConfigFile(string fontsDirectory)
- {
- const string fontConfigFilename = "fonts.conf";
- var fontConfigFile = Path.Combine(fontsDirectory, fontConfigFilename);
-
- if (!_fileSystem.FileExists(fontConfigFile))
- {
- var contents = string.Format("<?xml version=\"1.0\"?><fontconfig><dir>{0}</dir><alias><family>Arial</family><prefer>Arial Unicode MS</prefer></alias></fontconfig>", fontsDirectory);
-
- var bytes = Encoding.UTF8.GetBytes(contents);
-
- using (var fileStream = _fileSystem.GetFileStream(fontConfigFile, FileMode.Create, FileAccess.Write,
- FileShare.Read, true))
- {
- await fileStream.WriteAsync(bytes, 0, bytes.Length);
- }
- }
- }
- }
-}
diff --git a/MediaBrowser.Server.Startup.Common/FFMpeg/FFMpegInstallInfo.cs b/MediaBrowser.Server.Startup.Common/FFMpeg/FFMpegInstallInfo.cs
new file mode 100644
index 000000000..a2a44f805
--- /dev/null
+++ b/MediaBrowser.Server.Startup.Common/FFMpeg/FFMpegInstallInfo.cs
@@ -0,0 +1,20 @@
+
+namespace MediaBrowser.Server.Startup.Common.FFMpeg
+{
+ public class FFMpegInstallInfo
+ {
+ public string Version { get; set; }
+ public string FFMpegFilename { get; set; }
+ public string FFProbeFilename { get; set; }
+ public string ArchiveType { get; set; }
+ public string[] DownloadUrls { get; set; }
+
+ public FFMpegInstallInfo()
+ {
+ DownloadUrls = new string[] { };
+ Version = "Path";
+ FFMpegFilename = "ffmpeg";
+ FFProbeFilename = "ffprobe";
+ }
+ }
+} \ No newline at end of file
diff --git a/MediaBrowser.Server.Startup.Common/FFMpeg/FFMpegLoader.cs b/MediaBrowser.Server.Startup.Common/FFMpeg/FFMpegLoader.cs
new file mode 100644
index 000000000..68e2a4927
--- /dev/null
+++ b/MediaBrowser.Server.Startup.Common/FFMpeg/FFMpegLoader.cs
@@ -0,0 +1,248 @@
+using MediaBrowser.Common.Configuration;
+using MediaBrowser.Common.Net;
+using MediaBrowser.Model.IO;
+using MediaBrowser.Model.Logging;
+using Mono.Unix.Native;
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+using CommonIO;
+
+namespace MediaBrowser.Server.Startup.Common.FFMpeg
+{
+ public class FFMpegLoader
+ {
+ private readonly IHttpClient _httpClient;
+ private readonly IApplicationPaths _appPaths;
+ private readonly ILogger _logger;
+ private readonly IZipClient _zipClient;
+ private readonly IFileSystem _fileSystem;
+ private readonly NativeEnvironment _environment;
+ private readonly FFMpegInstallInfo _ffmpegInstallInfo;
+
+ public FFMpegLoader(ILogger logger, IApplicationPaths appPaths, IHttpClient httpClient, IZipClient zipClient, IFileSystem fileSystem, NativeEnvironment environment, FFMpegInstallInfo ffmpegInstallInfo)
+ {
+ _logger = logger;
+ _appPaths = appPaths;
+ _httpClient = httpClient;
+ _zipClient = zipClient;
+ _fileSystem = fileSystem;
+ _environment = environment;
+ _ffmpegInstallInfo = ffmpegInstallInfo;
+ }
+
+ public async Task<FFMpegInfo> GetFFMpegInfo(NativeEnvironment environment, StartupOptions options, IProgress<double> progress)
+ {
+ var customffMpegPath = options.GetOption("-ffmpeg");
+ var customffProbePath = options.GetOption("-ffprobe");
+
+ if (!string.IsNullOrWhiteSpace(customffMpegPath) && !string.IsNullOrWhiteSpace(customffProbePath))
+ {
+ return new FFMpegInfo
+ {
+ ProbePath = customffProbePath,
+ EncoderPath = customffMpegPath,
+ Version = "external"
+ };
+ }
+
+ var downloadInfo = _ffmpegInstallInfo;
+
+ var version = downloadInfo.Version;
+
+ if (string.Equals(version, "path", StringComparison.OrdinalIgnoreCase))
+ {
+ return new FFMpegInfo
+ {
+ ProbePath = downloadInfo.FFProbeFilename,
+ EncoderPath = downloadInfo.FFMpegFilename,
+ Version = version
+ };
+ }
+
+ if (string.Equals(version, "0", StringComparison.OrdinalIgnoreCase))
+ {
+ return new FFMpegInfo();
+ }
+
+ var rootEncoderPath = Path.Combine(_appPaths.ProgramDataPath, "ffmpeg");
+ var versionedDirectoryPath = Path.Combine(rootEncoderPath, version);
+
+ var info = new FFMpegInfo
+ {
+ ProbePath = Path.Combine(versionedDirectoryPath, downloadInfo.FFProbeFilename),
+ EncoderPath = Path.Combine(versionedDirectoryPath, downloadInfo.FFMpegFilename),
+ Version = version
+ };
+
+ _fileSystem.CreateDirectory(versionedDirectoryPath);
+
+ var excludeFromDeletions = new List<string> { versionedDirectoryPath };
+
+ if (!_fileSystem.FileExists(info.ProbePath) || !_fileSystem.FileExists(info.EncoderPath))
+ {
+ // ffmpeg not present. See if there's an older version we can start with
+ var existingVersion = GetExistingVersion(info, rootEncoderPath);
+
+ // No older version. Need to download and block until complete
+ if (existingVersion == null)
+ {
+ var success = await DownloadFFMpeg(downloadInfo, versionedDirectoryPath, progress).ConfigureAwait(false);
+ if (!success)
+ {
+ return new FFMpegInfo();
+ }
+ }
+ else
+ {
+ info = existingVersion;
+ versionedDirectoryPath = Path.GetDirectoryName(info.EncoderPath);
+ excludeFromDeletions.Add(versionedDirectoryPath);
+ }
+ }
+
+ // Allow just one of these to be overridden, if desired.
+ if (!string.IsNullOrWhiteSpace(customffMpegPath))
+ {
+ info.EncoderPath = customffMpegPath;
+ }
+ if (!string.IsNullOrWhiteSpace(customffProbePath))
+ {
+ info.EncoderPath = customffProbePath;
+ }
+
+ return info;
+ }
+
+ private FFMpegInfo GetExistingVersion(FFMpegInfo info, string rootEncoderPath)
+ {
+ var encoderFilename = Path.GetFileName(info.EncoderPath);
+ var probeFilename = Path.GetFileName(info.ProbePath);
+
+ foreach (var directory in Directory.EnumerateDirectories(rootEncoderPath, "*", SearchOption.TopDirectoryOnly)
+ .ToList())
+ {
+ var allFiles = Directory.EnumerateFiles(directory, "*", SearchOption.AllDirectories).ToList();
+
+ var encoder = allFiles.FirstOrDefault(i => string.Equals(Path.GetFileName(i), encoderFilename, StringComparison.OrdinalIgnoreCase));
+ var probe = allFiles.FirstOrDefault(i => string.Equals(Path.GetFileName(i), probeFilename, StringComparison.OrdinalIgnoreCase));
+
+ if (!string.IsNullOrWhiteSpace(encoder) &&
+ !string.IsNullOrWhiteSpace(probe))
+ {
+ return new FFMpegInfo
+ {
+ EncoderPath = encoder,
+ ProbePath = probe,
+ Version = Path.GetFileName(Path.GetDirectoryName(probe))
+ };
+ }
+ }
+
+ return null;
+ }
+
+ private async Task<bool> DownloadFFMpeg(FFMpegInstallInfo downloadinfo, string directory, IProgress<double> progress)
+ {
+ foreach (var url in downloadinfo.DownloadUrls)
+ {
+ progress.Report(0);
+
+ try
+ {
+ var tempFile = await _httpClient.GetTempFile(new HttpRequestOptions
+ {
+ Url = url,
+ CancellationToken = CancellationToken.None,
+ Progress = progress
+
+ }).ConfigureAwait(false);
+
+ ExtractFFMpeg(downloadinfo, tempFile, directory);
+ return true;
+ }
+ catch (Exception ex)
+ {
+ _logger.ErrorException("Error downloading {0}", ex, url);
+ }
+ }
+ return false;
+ }
+
+ private void ExtractFFMpeg(FFMpegInstallInfo downloadinfo, string tempFile, string targetFolder)
+ {
+ _logger.Info("Extracting ffmpeg from {0}", tempFile);
+
+ var tempFolder = Path.Combine(_appPaths.TempDirectory, Guid.NewGuid().ToString());
+
+ _fileSystem.CreateDirectory(tempFolder);
+
+ try
+ {
+ ExtractArchive(downloadinfo, tempFile, tempFolder);
+
+ var files = Directory.EnumerateFiles(tempFolder, "*", SearchOption.AllDirectories)
+ .ToList();
+
+ foreach (var file in files.Where(i =>
+ {
+ var filename = Path.GetFileName(i);
+
+ return
+ string.Equals(filename, downloadinfo.FFProbeFilename, StringComparison.OrdinalIgnoreCase) ||
+ string.Equals(filename, downloadinfo.FFMpegFilename, StringComparison.OrdinalIgnoreCase);
+ }))
+ {
+ var targetFile = Path.Combine(targetFolder, Path.GetFileName(file));
+ _fileSystem.CopyFile(file, targetFile, true);
+ SetFilePermissions(targetFile);
+ }
+ }
+ finally
+ {
+ DeleteFile(tempFile);
+ }
+ }
+
+ private void SetFilePermissions(string path)
+ {
+ // Linux: File permission to 666, and user's execute bit
+ if (_environment.OperatingSystem == OperatingSystem.Bsd || _environment.OperatingSystem == OperatingSystem.Linux || _environment.OperatingSystem == OperatingSystem.Osx)
+ {
+ _logger.Info("Syscall.chmod {0} FilePermissions.DEFFILEMODE | FilePermissions.S_IRWXU | FilePermissions.S_IXGRP | FilePermissions.S_IXOTH", path);
+
+ Syscall.chmod(path, FilePermissions.DEFFILEMODE | FilePermissions.S_IRWXU | FilePermissions.S_IXGRP | FilePermissions.S_IXOTH);
+ }
+ }
+
+ private void ExtractArchive(FFMpegInstallInfo downloadinfo, string archivePath, string targetPath)
+ {
+ _logger.Info("Extracting {0} to {1}", archivePath, targetPath);
+
+ if (string.Equals(downloadinfo.ArchiveType, "7z", StringComparison.OrdinalIgnoreCase))
+ {
+ _zipClient.ExtractAllFrom7z(archivePath, targetPath, true);
+ }
+ else if (string.Equals(downloadinfo.ArchiveType, "gz", StringComparison.OrdinalIgnoreCase))
+ {
+ _zipClient.ExtractAllFromTar(archivePath, targetPath, true);
+ }
+ }
+
+ private void DeleteFile(string path)
+ {
+ try
+ {
+ _fileSystem.DeleteFile(path);
+ }
+ catch (IOException ex)
+ {
+ _logger.ErrorException("Error deleting temp file {0}", ex, path);
+ }
+ }
+
+ }
+}
diff --git a/MediaBrowser.Server.Startup.Common/INativeApp.cs b/MediaBrowser.Server.Startup.Common/INativeApp.cs
index 9df670bda..c13d3624e 100644
--- a/MediaBrowser.Server.Startup.Common/INativeApp.cs
+++ b/MediaBrowser.Server.Startup.Common/INativeApp.cs
@@ -3,6 +3,8 @@ using MediaBrowser.Model.Logging;
using System.Collections.Generic;
using System.Reflection;
using MediaBrowser.Controller.Power;
+using MediaBrowser.Server.Implementations.Persistence;
+using MediaBrowser.Server.Startup.Common.FFMpeg;
namespace MediaBrowser.Server.Startup.Common
{
@@ -92,10 +94,18 @@ namespace MediaBrowser.Server.Startup.Common
/// </summary>
void PreventSystemStandby();
+ void AllowSystemStandby();
+
/// <summary>
/// Gets the power management.
/// </summary>
/// <returns>IPowerManagement.</returns>
IPowerManagement GetPowerManagement();
+
+ FFMpegInstallInfo GetFfmpegInstallInfo();
+
+ void LaunchUrl(string url);
+
+ IDbConnector GetDbConnector();
}
}
diff --git a/MediaBrowser.Server.Startup.Common/MediaBrowser.Server.Startup.Common.csproj b/MediaBrowser.Server.Startup.Common/MediaBrowser.Server.Startup.Common.csproj
index 80ce88fa3..808d25fc9 100644
--- a/MediaBrowser.Server.Startup.Common/MediaBrowser.Server.Startup.Common.csproj
+++ b/MediaBrowser.Server.Startup.Common/MediaBrowser.Server.Startup.Common.csproj
@@ -65,17 +65,18 @@
<Compile Include="Browser\BrowserLauncher.cs" />
<Compile Include="EntryPoints\KeepServerAwake.cs" />
<Compile Include="EntryPoints\StartupWizard.cs" />
- <Compile Include="FFMpeg\FFMpegDownloader.cs" />
- <Compile Include="FFMpeg\FFMpegDownloadInfo.cs" />
+ <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 14ce35a96..6d840c191 100644
--- a/MediaBrowser.ServerApplication/App.config
+++ b/MediaBrowser.ServerApplication/App.config
@@ -1,80 +1,81 @@
-<?xml version="1.0" encoding="utf-8"?>
+<?xml version="1.0" encoding="utf-8"?>
<configuration>
<configSections>
- <section name="nlog" type="NLog.Config.ConfigSectionHandler, NLog" />
+ <section name="nlog" type="NLog.Config.ConfigSectionHandler, NLog"/>
- <section name="entityFramework" type="System.Data.Entity.Internal.ConfigFile.EntityFrameworkSection, EntityFramework, Version=6.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" requirePermission="false" />
+ <section name="entityFramework" type="System.Data.Entity.Internal.ConfigFile.EntityFrameworkSection, EntityFramework, Version=6.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" requirePermission="false"/>
</configSections>
<system.diagnostics>
- <assert assertuienabled="false" />
+ <assert assertuienabled="false"/>
</system.diagnostics>
<nlog xmlns="http://www.nlog-project.org/schemas/NLog.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<targets async="true"></targets>
</nlog>
<appSettings>
- <add key="DebugProgramDataPath" value="..\..\..\ProgramData-Server" />
- <add key="ReleaseProgramDataPath" value=".." />
- <add key="ClientSettingsProvider.ServiceUri" value="" />
+ <add key="DebugProgramDataPath" value="..\..\..\ProgramData-Server"/>
+ <add key="ReleaseProgramDataPath" value=".."/>
+ <add key="ClientSettingsProvider.ServiceUri" value=""/>
</appSettings>
<startup useLegacyV2RuntimeActivationPolicy="true">
- <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5" />
+ <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.6"/>
</startup>
<runtime>
- <gcAllowVeryLargeObjects enabled="true" />
- <gcServer enabled="true" />
+ <gcAllowVeryLargeObjects enabled="true"/>
+ <gcServer enabled="true"/>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<dependentAssembly>
- <assemblyIdentity name="System.Net.Http" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
- <bindingRedirect oldVersion="0.0.0.0-4.0.0.0" newVersion="4.0.0.0" />
+ <assemblyIdentity name="System.Net.Http" publicKeyToken="b03f5f7f11d50a3a" culture="neutral"/>
+ <bindingRedirect oldVersion="0.0.0.0-4.0.0.0" newVersion="4.0.0.0"/>
</dependentAssembly>
<dependentAssembly>
- <assemblyIdentity name="System.Reactive.Core" publicKeyToken="f300afd708cefcd3" culture="neutral" />
- <bindingRedirect oldVersion="0.0.0.0-2.0.20823.0" newVersion="2.0.20823.0" />
+ <assemblyIdentity name="System.Reactive.Core" publicKeyToken="f300afd708cefcd3" culture="neutral"/>
+ <bindingRedirect oldVersion="0.0.0.0-2.0.20823.0" newVersion="2.0.20823.0"/>
</dependentAssembly>
<dependentAssembly>
- <assemblyIdentity name="System.Reactive.Interfaces" publicKeyToken="f300afd708cefcd3" culture="neutral" />
- <bindingRedirect oldVersion="0.0.0.0-2.0.20823.0" newVersion="2.0.20823.0" />
+ <assemblyIdentity name="System.Reactive.Interfaces" publicKeyToken="f300afd708cefcd3" culture="neutral"/>
+ <bindingRedirect oldVersion="0.0.0.0-2.0.20823.0" newVersion="2.0.20823.0"/>
</dependentAssembly>
<dependentAssembly>
- <assemblyIdentity name="System.Runtime" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
- <bindingRedirect oldVersion="0.0.0.0-1.5.11.0" newVersion="1.5.11.0" />
+ <assemblyIdentity name="System.Runtime" publicKeyToken="b03f5f7f11d50a3a" culture="neutral"/>
+ <bindingRedirect oldVersion="0.0.0.0-1.5.11.0" newVersion="1.5.11.0"/>
</dependentAssembly>
<dependentAssembly>
- <assemblyIdentity name="System.Threading.Tasks" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
- <bindingRedirect oldVersion="0.0.0.0-1.5.11.0" newVersion="1.5.11.0" />
+ <assemblyIdentity name="System.Threading.Tasks" publicKeyToken="b03f5f7f11d50a3a" culture="neutral"/>
+ <bindingRedirect oldVersion="0.0.0.0-1.5.11.0" newVersion="1.5.11.0"/>
</dependentAssembly>
<dependentAssembly>
- <assemblyIdentity name="System.Data.SQLite" publicKeyToken="db937bc2d44ff139" culture="neutral" />
- <bindingRedirect oldVersion="0.0.0.0-1.0.94.0" newVersion="1.0.94.0" />
+ <assemblyIdentity name="System.Data.SQLite" publicKeyToken="db937bc2d44ff139" culture="neutral"/>
+ <bindingRedirect oldVersion="0.0.0.0-1.0.94.0" newVersion="1.0.94.0"/>
</dependentAssembly>
<dependentAssembly>
- <assemblyIdentity name="SimpleInjector" publicKeyToken="984cb50dea722e99" culture="neutral" />
- <bindingRedirect oldVersion="0.0.0.0-2.3.6.0" newVersion="2.3.6.0" />
+ <assemblyIdentity name="SimpleInjector" publicKeyToken="984cb50dea722e99" culture="neutral"/>
+ <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">
<providers>
- <add name="ClientAuthenticationMembershipProvider" type="System.Web.ClientServices.Providers.ClientFormsAuthenticationMembershipProvider, System.Web.Extensions, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" serviceUri="" />
+ <add name="ClientAuthenticationMembershipProvider" type="System.Web.ClientServices.Providers.ClientFormsAuthenticationMembershipProvider, System.Web.Extensions, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" serviceUri=""/>
</providers>
</membership>
<roleManager defaultProvider="ClientRoleProvider" enabled="true">
<providers>
- <add name="ClientRoleProvider" type="System.Web.ClientServices.Providers.ClientRoleProvider, System.Web.Extensions, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" serviceUri="" cacheTimeout="86400" />
+ <add name="ClientRoleProvider" type="System.Web.ClientServices.Providers.ClientRoleProvider, System.Web.Extensions, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" serviceUri="" cacheTimeout="86400"/>
</providers>
</roleManager>
</system.web>
<entityFramework>
<defaultConnectionFactory type="System.Data.Entity.Infrastructure.LocalDbConnectionFactory, EntityFramework">
<parameters>
- <parameter value="v11.0" />
+ <parameter value="v11.0"/>
</parameters>
</defaultConnectionFactory>
<providers>
- <provider invariantName="System.Data.SqlClient" type="System.Data.Entity.SqlServer.SqlProviderServices, EntityFramework.SqlServer" />
- <provider invariantName="System.Data.SQLite.EF6" type="System.Data.SQLite.EF6.SQLiteProviderServices, System.Data.SQLite.EF6" />
+ <provider invariantName="System.Data.SqlClient" type="System.Data.Entity.SqlServer.SqlProviderServices, EntityFramework.SqlServer"/>
+ <provider invariantName="System.Data.SQLite.EF6" type="System.Data.SQLite.EF6.SQLiteProviderServices, System.Data.SQLite.EF6"/>
</providers>
</entityFramework>
-</configuration> \ No newline at end of file
+</configuration>
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 87acd652e..bdfd7d1bb 100644
--- a/MediaBrowser.ServerApplication/MainStartup.cs
+++ b/MediaBrowser.ServerApplication/MainStartup.cs
@@ -12,6 +12,7 @@ using System.Configuration.Install;
using System.Diagnostics;
using System.IO;
using System.Linq;
+using System.Management;
using System.Runtime.InteropServices;
using System.ServiceProcess;
using System.Threading;
@@ -102,7 +103,7 @@ namespace MediaBrowser.ServerApplication
if (IsAlreadyRunning(applicationPath, currentProcess))
{
- logger.Info("Shutting down because another instance of Media Browser Server is already running.");
+ logger.Info("Shutting down because another instance of Emby Server is already running.");
return;
}
@@ -130,13 +131,28 @@ namespace MediaBrowser.ServerApplication
/// <returns><c>true</c> if [is already running] [the specified current process]; otherwise, <c>false</c>.</returns>
private static bool IsAlreadyRunning(string applicationPath, Process currentProcess)
{
- var filename = Path.GetFileName(applicationPath);
-
var duplicate = Process.GetProcesses().FirstOrDefault(i =>
{
try
{
- return string.Equals(filename, Path.GetFileName(i.MainModule.FileName)) && currentProcess.Id != i.Id;
+ if (currentProcess.Id == i.Id)
+ {
+ return false;
+ }
+ }
+ catch (Exception)
+ {
+ return false;
+ }
+
+ try
+ {
+ //_logger.Info("Module: {0}", i.MainModule.FileName);
+ if (string.Equals(applicationPath, i.MainModule.FileName, StringComparison.OrdinalIgnoreCase))
+ {
+ return true;
+ }
+ return false;
}
catch (Exception)
{
@@ -155,6 +171,41 @@ namespace MediaBrowser.ServerApplication
}
}
+ if (!_isRunningAsService)
+ {
+ return IsAlreadyRunningAsService(applicationPath);
+ }
+
+ return false;
+ }
+
+ private static bool IsAlreadyRunningAsService(string applicationPath)
+ {
+ var serviceName = BackgroundService.GetExistingServiceName();
+
+ WqlObjectQuery wqlObjectQuery = new WqlObjectQuery(string.Format("SELECT * FROM Win32_Service WHERE State = 'Running' AND Name = '{0}'", serviceName));
+ ManagementObjectSearcher managementObjectSearcher = new ManagementObjectSearcher(wqlObjectQuery);
+ ManagementObjectCollection managementObjectCollection = managementObjectSearcher.Get();
+
+ foreach (ManagementObject managementObject in managementObjectCollection)
+ {
+ var obj = managementObject.GetPropertyValue("PathName");
+ if (obj == null)
+ {
+ continue;
+ }
+ var path = obj.ToString();
+
+ _logger.Info("Service path: {0}", path);
+ // Need to use indexOf instead of equality because the path will have the full service command line
+ if (path.IndexOf(applicationPath, StringComparison.OrdinalIgnoreCase) != -1)
+ {
+ _logger.Info("The windows service is already running");
+ MessageBox.Show("Emby Server is already running as a Windows Service. Only one instance is allowed at a time. To run as a tray icon, shut down the Windows Service.");
+ return true;
+ }
+ }
+
return false;
}
@@ -217,6 +268,7 @@ namespace MediaBrowser.ServerApplication
{
var fileSystem = new WindowsFileSystem(new PatternsLogger(logManager.GetLogger("FileSystem")));
fileSystem.AddShortcutHandler(new MbLinkShortcutHandler(fileSystem));
+ //fileSystem.AddShortcutHandler(new LnkShortcutHandler(fileSystem));
var nativeApp = new WindowsApp(fileSystem, _logger)
{
@@ -243,7 +295,9 @@ namespace MediaBrowser.ServerApplication
var task = _appHost.Init(initProgress);
- task = task.ContinueWith(new Action<Task>(a => _appHost.RunStartupTasks()));
+ Task.WaitAll(task);
+
+ task = task.ContinueWith(new Action<Task>(a => _appHost.RunStartupTasks()), TaskContinuationOptions.OnlyOnRanToCompletion | TaskContinuationOptions.AttachedToParent);
if (runService)
{
@@ -253,7 +307,7 @@ namespace MediaBrowser.ServerApplication
{
Task.WaitAll(task);
- task = InstallVcredistIfNeeded(_appHost, _logger);
+ task = InstallVcredist2013IfNeeded(_appHost, _logger);
Task.WaitAll(task);
SystemEvents.SessionEnding += SystemEvents_SessionEnding;
@@ -269,11 +323,13 @@ namespace MediaBrowser.ServerApplication
}
private static ServerNotifyIcon _serverNotifyIcon;
+ private static TaskScheduler _mainTaskScheduler;
private static void ShowTrayIcon()
{
//Application.EnableVisualStyles();
//Application.SetCompatibleTextRenderingDefault(false);
_serverNotifyIcon = new ServerNotifyIcon(_appHost.LogManager, _appHost, _appHost.ServerConfigurationManager, _appHost.LocalizationManager);
+ _mainTaskScheduler = TaskScheduler.FromCurrentSynchronizationContext();
Application.Run();
}
@@ -313,7 +369,19 @@ namespace MediaBrowser.ServerApplication
{
if (e.Reason == SessionSwitchReason.SessionLogon)
{
- BrowserLauncher.OpenDashboard(_appHost, _logger);
+ BrowserLauncher.OpenDashboard(_appHost);
+ }
+ }
+
+ public static void Invoke(Action action)
+ {
+ if (_isRunningAsService)
+ {
+ action();
+ }
+ else
+ {
+ Task.Factory.StartNew(action, CancellationToken.None, TaskCreationOptions.None, _mainTaskScheduler ?? TaskScheduler.Current);
}
}
@@ -551,9 +619,10 @@ namespace MediaBrowser.ServerApplication
private static void ShutdownWindowsApplication()
{
- _logger.Info("Calling Application.Exit");
- Application.Exit();
+ //_logger.Info("Calling Application.Exit");
+ //Application.Exit();
+ _logger.Info("Calling Environment.Exit");
Environment.Exit(0);
_logger.Info("Calling ApplicationTaskCompletionSource.SetResult");
@@ -573,21 +642,39 @@ namespace MediaBrowser.ServerApplication
}
}
- private static async Task InstallVcredistIfNeeded(ApplicationHost appHost, ILogger logger)
+ private static async Task InstallVcredist2013IfNeeded(ApplicationHost appHost, ILogger logger)
{
+ // Reference
+ // http://stackoverflow.com/questions/12206314/detect-if-visual-c-redistributable-for-visual-studio-2012-is-installed
+
try
{
- var version = ImageMagickEncoder.GetVersion();
- return;
+ var subkey = Environment.Is64BitProcess
+ ? "SOFTWARE\\WOW6432Node\\Microsoft\\VisualStudio\\12.0\\VC\\Runtimes\\x64"
+ : "SOFTWARE\\Microsoft\\VisualStudio\\12.0\\VC\\Runtimes\\x86";
+
+ using (RegistryKey ndpKey = RegistryKey.OpenBaseKey(RegistryHive.LocalMachine, RegistryView.Default)
+ .OpenSubKey(subkey))
+ {
+ if (ndpKey != null && ndpKey.GetValue("Version") != null)
+ {
+ var installedVersion = ((string)ndpKey.GetValue("Version")).TrimStart('v');
+ if (installedVersion.StartsWith("12", StringComparison.OrdinalIgnoreCase))
+ {
+ return;
+ }
+ }
+ }
}
catch (Exception ex)
{
- logger.ErrorException("Error loading ImageMagick", ex);
+ logger.ErrorException("Error getting .NET Framework version", ex);
+ return;
}
try
{
- await InstallVcredist().ConfigureAwait(false);
+ await InstallVcredist2013().ConfigureAwait(false);
}
catch (Exception ex)
{
@@ -595,13 +682,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);
@@ -627,7 +714,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 6ba91c06f..b01d8c43f 100644
--- a/MediaBrowser.ServerApplication/MediaBrowser.ServerApplication.csproj
+++ b/MediaBrowser.ServerApplication/MediaBrowser.ServerApplication.csproj
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
-<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+<Project ToolsVersion="12.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
@@ -9,9 +9,12 @@
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>MediaBrowser.ServerApplication</RootNamespace>
<AssemblyName>MediaBrowser.ServerApplication</AssemblyName>
- <TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
+ <TargetFrameworkVersion>v4.6</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<SolutionDir Condition="$(SolutionDir) == '' Or $(SolutionDir) == '*Undefined*'">..\</SolutionDir>
+ <TargetFrameworkProfile />
+ <NuGetPackageImportStamp>
+ </NuGetPackageImportStamp>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget>
@@ -69,16 +72,10 @@
<SpecificVersion>False</SpecificVersion>
<HintPath>..\packages\ImageMagickSharp.1.0.0.18\lib\net45\ImageMagickSharp.dll</HintPath>
</Reference>
- <Reference Include="MediaBrowser.IsoMounter">
- <HintPath>..\packages\MediaBrowser.IsoMounting.3.0.69\lib\net45\MediaBrowser.IsoMounter.dll</HintPath>
- </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>
</Reference>
- <Reference Include="pfmclrapi">
- <HintPath>..\packages\MediaBrowser.IsoMounting.3.0.69\lib\net45\pfmclrapi.dll</HintPath>
- </Reference>
<Reference Include="ServiceStack.Interfaces">
<HintPath>..\ThirdParty\ServiceStack\ServiceStack.Interfaces.dll</HintPath>
</Reference>
@@ -86,11 +83,12 @@
<Reference Include="System.Configuration" />
<Reference Include="System.Configuration.Install" />
<Reference Include="System.Core" />
- <Reference Include="System.Data.SQLite, Version=1.0.94.0, Culture=neutral, PublicKeyToken=db937bc2d44ff139, processorArchitecture=MSIL">
- <SpecificVersion>False</SpecificVersion>
- <HintPath>..\packages\System.Data.SQLite.Core.1.0.94.0\lib\net45\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" />
+ <Reference Include="System.Management" />
<Reference Include="System.ServiceProcess" />
<Reference Include="System.Windows.Forms" />
<Reference Include="System.Xml.Linq" />
@@ -100,6 +98,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>
@@ -116,7 +117,8 @@
<DependentUpon>MainForm.cs</DependentUpon>
</Compile>
<Compile Include="MainStartup.cs" />
- <Compile Include="Native\Autorun.cs" />
+ <Compile Include="Native\LnkShortcutHandler.cs" />
+ <Compile Include="Native\DbConnector.cs" />
<Compile Include="Native\Standby.cs" />
<Compile Include="Native\ServerAuthorization.cs" />
<Compile Include="Native\WindowsApp.cs" />
@@ -159,14 +161,6 @@
</EmbeddedResource>
</ItemGroup>
<ItemGroup>
- <Content Include="..\packages\System.Data.SQLite.Core.1.0.94.0\build\net45\x64\SQLite.Interop.dll">
- <Link>x64\SQLite.Interop.dll</Link>
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="..\packages\System.Data.SQLite.Core.1.0.94.0\build\net45\x86\SQLite.Interop.dll">
- <Link>x86\SQLite.Interop.dll</Link>
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
<Content Include="..\Tools\Installation\MediaBrowser.InstallUtil.dll">
<Link>MediaBrowser.InstallUtil.dll</Link>
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
@@ -1119,6 +1113,13 @@
<PostBuildEvent>
</PostBuildEvent>
</PropertyGroup>
+ <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.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.
<Target Name="BeforeBuild">
@@ -1126,5 +1127,4 @@
<Target Name="AfterBuild">
</Target>
-->
- <Import Project="..\packages\System.Data.SQLite.Core.1.0.94.0\build\net45\System.Data.SQLite.Core.targets" Condition="Exists('..\packages\System.Data.SQLite.Core.1.0.94.0\build\net45\System.Data.SQLite.Core.targets')" />
</Project> \ No newline at end of file
diff --git a/MediaBrowser.ServerApplication/Native/Autorun.cs b/MediaBrowser.ServerApplication/Native/Autorun.cs
deleted file mode 100644
index 4d3f66b91..000000000
--- a/MediaBrowser.ServerApplication/Native/Autorun.cs
+++ /dev/null
@@ -1,35 +0,0 @@
-using System;
-using System.IO;
-using CommonIO;
-
-namespace MediaBrowser.ServerApplication.Native
-{
- /// <summary>
- /// Class Autorun
- /// </summary>
- public static class Autorun
- {
- /// <summary>
- /// Configures the specified autorun.
- /// </summary>
- /// <param name="autorun">if set to <c>true</c> [autorun].</param>
- /// <param name="fileSystem">The file system.</param>
- public static void Configure(bool autorun, IFileSystem fileSystem)
- {
- var shortcutPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.StartMenu), "Emby", "Emby Server.lnk");
-
- var startupPath = Environment.GetFolderPath(Environment.SpecialFolder.Startup);
-
- if (autorun)
- {
- //Copy our shortut into the startup folder for this user
- File.Copy(shortcutPath, Path.Combine(startupPath, Path.GetFileName(shortcutPath) ?? "Emby Server.lnk"), true);
- }
- else
- {
- //Remove our shortcut from the startup folder for this user
- fileSystem.DeleteFile(Path.Combine(startupPath, Path.GetFileName(shortcutPath) ?? "Emby Server.lnk"));
- }
- }
- }
-}
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/LnkShortcutHandler.cs b/MediaBrowser.ServerApplication/Native/LnkShortcutHandler.cs
new file mode 100644
index 000000000..67d2e83f0
--- /dev/null
+++ b/MediaBrowser.ServerApplication/Native/LnkShortcutHandler.cs
@@ -0,0 +1,402 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Runtime.InteropServices;
+using System.Security;
+using System.Text;
+using System.Threading.Tasks;
+using CommonIO;
+
+namespace MediaBrowser.ServerApplication.Native
+{
+ public class LnkShortcutHandler : IShortcutHandler
+ {
+ public string Extension
+ {
+ get { return ".lnk"; }
+ }
+
+ public string Resolve(string shortcutPath)
+ {
+ var link = new ShellLink();
+ ((IPersistFile)link).Load(shortcutPath, NativeMethods.STGM_READ);
+ // ((IShellLinkW)link).Resolve(hwnd, 0)
+ var sb = new StringBuilder(NativeMethods.MAX_PATH);
+ WIN32_FIND_DATA data;
+ ((IShellLinkW)link).GetPath(sb, sb.Capacity, out data, 0);
+ return sb.ToString();
+ }
+
+ public void Create(string shortcutPath, string targetPath)
+ {
+ throw new NotImplementedException();
+ }
+ }
+
+ /// <summary>
+ /// Class NativeMethods
+ /// </summary>
+ [SuppressUnmanagedCodeSecurity]
+ public static class NativeMethods
+ {
+ /// <summary>
+ /// The MA x_ PATH
+ /// </summary>
+ public const int MAX_PATH = 260;
+ /// <summary>
+ /// The MA x_ ALTERNATE
+ /// </summary>
+ public const int MAX_ALTERNATE = 14;
+ /// <summary>
+ /// The INVALI d_ HANDL e_ VALUE
+ /// </summary>
+ public static IntPtr INVALID_HANDLE_VALUE = new IntPtr(-1);
+ /// <summary>
+ /// The STG m_ READ
+ /// </summary>
+ public const uint STGM_READ = 0;
+ }
+
+ /// <summary>
+ /// Struct FILETIME
+ /// </summary>
+ [StructLayout(LayoutKind.Sequential)]
+ public struct FILETIME
+ {
+ /// <summary>
+ /// The dw low date time
+ /// </summary>
+ public uint dwLowDateTime;
+ /// <summary>
+ /// The dw high date time
+ /// </summary>
+ public uint dwHighDateTime;
+ }
+
+ /// <summary>
+ /// Struct WIN32_FIND_DATA
+ /// </summary>
+ [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
+ public struct WIN32_FIND_DATA
+ {
+ /// <summary>
+ /// The dw file attributes
+ /// </summary>
+ public FileAttributes dwFileAttributes;
+ /// <summary>
+ /// The ft creation time
+ /// </summary>
+ public FILETIME ftCreationTime;
+ /// <summary>
+ /// The ft last access time
+ /// </summary>
+ public FILETIME ftLastAccessTime;
+ /// <summary>
+ /// The ft last write time
+ /// </summary>
+ public FILETIME ftLastWriteTime;
+ /// <summary>
+ /// The n file size high
+ /// </summary>
+ public int nFileSizeHigh;
+ /// <summary>
+ /// The n file size low
+ /// </summary>
+ public int nFileSizeLow;
+ /// <summary>
+ /// The dw reserved0
+ /// </summary>
+ public int dwReserved0;
+ /// <summary>
+ /// The dw reserved1
+ /// </summary>
+ public int dwReserved1;
+
+ /// <summary>
+ /// The c file name
+ /// </summary>
+ [MarshalAs(UnmanagedType.ByValTStr, SizeConst = NativeMethods.MAX_PATH)]
+ public string cFileName;
+
+ /// <summary>
+ /// This will always be null when FINDEX_INFO_LEVELS = basic
+ /// </summary>
+ [MarshalAs(UnmanagedType.ByValTStr, SizeConst = NativeMethods.MAX_ALTERNATE)]
+ public string cAlternate;
+
+ /// <summary>
+ /// Gets or sets the path.
+ /// </summary>
+ /// <value>The path.</value>
+ public string Path { get; set; }
+
+ /// <summary>
+ /// Returns a <see cref="System.String" /> that represents this instance.
+ /// </summary>
+ /// <returns>A <see cref="System.String" /> that represents this instance.</returns>
+ public override string ToString()
+ {
+ return Path ?? string.Empty;
+ }
+ }
+
+ /// <summary>
+ /// Enum SLGP_FLAGS
+ /// </summary>
+ [Flags]
+ public enum SLGP_FLAGS
+ {
+ /// <summary>
+ /// Retrieves the standard short (8.3 format) file name
+ /// </summary>
+ SLGP_SHORTPATH = 0x1,
+ /// <summary>
+ /// Retrieves the Universal Naming Convention (UNC) path name of the file
+ /// </summary>
+ SLGP_UNCPRIORITY = 0x2,
+ /// <summary>
+ /// Retrieves the raw path name. A raw path is something that might not exist and may include environment variables that need to be expanded
+ /// </summary>
+ SLGP_RAWPATH = 0x4
+ }
+ /// <summary>
+ /// Enum SLR_FLAGS
+ /// </summary>
+ [Flags]
+ public enum SLR_FLAGS
+ {
+ /// <summary>
+ /// Do not display a dialog box if the link cannot be resolved. When SLR_NO_UI is set,
+ /// the high-order word of fFlags can be set to a time-out value that specifies the
+ /// maximum amount of time to be spent resolving the link. The function returns if the
+ /// link cannot be resolved within the time-out duration. If the high-order word is set
+ /// to zero, the time-out duration will be set to the default value of 3,000 milliseconds
+ /// (3 seconds). To specify a value, set the high word of fFlags to the desired time-out
+ /// duration, in milliseconds.
+ /// </summary>
+ SLR_NO_UI = 0x1,
+ /// <summary>
+ /// Obsolete and no longer used
+ /// </summary>
+ SLR_ANY_MATCH = 0x2,
+ /// <summary>
+ /// If the link object has changed, update its path and list of identifiers.
+ /// If SLR_UPDATE is set, you do not need to call IPersistFile::IsDirty to determine
+ /// whether or not the link object has changed.
+ /// </summary>
+ SLR_UPDATE = 0x4,
+ /// <summary>
+ /// Do not update the link information
+ /// </summary>
+ SLR_NOUPDATE = 0x8,
+ /// <summary>
+ /// Do not execute the search heuristics
+ /// </summary>
+ SLR_NOSEARCH = 0x10,
+ /// <summary>
+ /// Do not use distributed link tracking
+ /// </summary>
+ SLR_NOTRACK = 0x20,
+ /// <summary>
+ /// Disable distributed link tracking. By default, distributed link tracking tracks
+ /// removable media across multiple devices based on the volume name. It also uses the
+ /// Universal Naming Convention (UNC) path to track remote file systems whose drive letter
+ /// has changed. Setting SLR_NOLINKINFO disables both types of tracking.
+ /// </summary>
+ SLR_NOLINKINFO = 0x40,
+ /// <summary>
+ /// Call the Microsoft Windows Installer
+ /// </summary>
+ SLR_INVOKE_MSI = 0x80
+ }
+
+ /// <summary>
+ /// The IShellLink interface allows Shell links to be created, modified, and resolved
+ /// </summary>
+ [ComImport, InterfaceType(ComInterfaceType.InterfaceIsIUnknown), Guid("000214F9-0000-0000-C000-000000000046")]
+ public interface IShellLinkW
+ {
+ /// <summary>
+ /// Retrieves the path and file name of a Shell link object
+ /// </summary>
+ /// <param name="pszFile">The PSZ file.</param>
+ /// <param name="cchMaxPath">The CCH max path.</param>
+ /// <param name="pfd">The PFD.</param>
+ /// <param name="fFlags">The f flags.</param>
+ void GetPath([Out, MarshalAs(UnmanagedType.LPWStr)] StringBuilder pszFile, int cchMaxPath, out WIN32_FIND_DATA pfd, SLGP_FLAGS fFlags);
+ /// <summary>
+ /// Retrieves the list of item identifiers for a Shell link object
+ /// </summary>
+ /// <param name="ppidl">The ppidl.</param>
+ void GetIDList(out IntPtr ppidl);
+ /// <summary>
+ /// Sets the pointer to an item identifier list (PIDL) for a Shell link object.
+ /// </summary>
+ /// <param name="pidl">The pidl.</param>
+ void SetIDList(IntPtr pidl);
+ /// <summary>
+ /// Retrieves the description string for a Shell link object
+ /// </summary>
+ /// <param name="pszName">Name of the PSZ.</param>
+ /// <param name="cchMaxName">Name of the CCH max.</param>
+ void GetDescription([Out, MarshalAs(UnmanagedType.LPWStr)] StringBuilder pszName, int cchMaxName);
+ /// <summary>
+ /// Sets the description for a Shell link object. The description can be any application-defined string
+ /// </summary>
+ /// <param name="pszName">Name of the PSZ.</param>
+ void SetDescription([MarshalAs(UnmanagedType.LPWStr)] string pszName);
+ /// <summary>
+ /// Retrieves the name of the working directory for a Shell link object
+ /// </summary>
+ /// <param name="pszDir">The PSZ dir.</param>
+ /// <param name="cchMaxPath">The CCH max path.</param>
+ void GetWorkingDirectory([Out, MarshalAs(UnmanagedType.LPWStr)] StringBuilder pszDir, int cchMaxPath);
+ /// <summary>
+ /// Sets the name of the working directory for a Shell link object
+ /// </summary>
+ /// <param name="pszDir">The PSZ dir.</param>
+ void SetWorkingDirectory([MarshalAs(UnmanagedType.LPWStr)] string pszDir);
+ /// <summary>
+ /// Retrieves the command-line arguments associated with a Shell link object
+ /// </summary>
+ /// <param name="pszArgs">The PSZ args.</param>
+ /// <param name="cchMaxPath">The CCH max path.</param>
+ void GetArguments([Out, MarshalAs(UnmanagedType.LPWStr)] StringBuilder pszArgs, int cchMaxPath);
+ /// <summary>
+ /// Sets the command-line arguments for a Shell link object
+ /// </summary>
+ /// <param name="pszArgs">The PSZ args.</param>
+ void SetArguments([MarshalAs(UnmanagedType.LPWStr)] string pszArgs);
+ /// <summary>
+ /// Retrieves the hot key for a Shell link object
+ /// </summary>
+ /// <param name="pwHotkey">The pw hotkey.</param>
+ void GetHotkey(out short pwHotkey);
+ /// <summary>
+ /// Sets a hot key for a Shell link object
+ /// </summary>
+ /// <param name="wHotkey">The w hotkey.</param>
+ void SetHotkey(short wHotkey);
+ /// <summary>
+ /// Retrieves the show command for a Shell link object
+ /// </summary>
+ /// <param name="piShowCmd">The pi show CMD.</param>
+ void GetShowCmd(out int piShowCmd);
+ /// <summary>
+ /// Sets the show command for a Shell link object. The show command sets the initial show state of the window.
+ /// </summary>
+ /// <param name="iShowCmd">The i show CMD.</param>
+ void SetShowCmd(int iShowCmd);
+ /// <summary>
+ /// Retrieves the location (path and index) of the icon for a Shell link object
+ /// </summary>
+ /// <param name="pszIconPath">The PSZ icon path.</param>
+ /// <param name="cchIconPath">The CCH icon path.</param>
+ /// <param name="piIcon">The pi icon.</param>
+ void GetIconLocation([Out, MarshalAs(UnmanagedType.LPWStr)] StringBuilder pszIconPath,
+ int cchIconPath, out int piIcon);
+ /// <summary>
+ /// Sets the location (path and index) of the icon for a Shell link object
+ /// </summary>
+ /// <param name="pszIconPath">The PSZ icon path.</param>
+ /// <param name="iIcon">The i icon.</param>
+ void SetIconLocation([MarshalAs(UnmanagedType.LPWStr)] string pszIconPath, int iIcon);
+ /// <summary>
+ /// Sets the relative path to the Shell link object
+ /// </summary>
+ /// <param name="pszPathRel">The PSZ path rel.</param>
+ /// <param name="dwReserved">The dw reserved.</param>
+ void SetRelativePath([MarshalAs(UnmanagedType.LPWStr)] string pszPathRel, int dwReserved);
+ /// <summary>
+ /// Attempts to find the target of a Shell link, even if it has been moved or renamed
+ /// </summary>
+ /// <param name="hwnd">The HWND.</param>
+ /// <param name="fFlags">The f flags.</param>
+ void Resolve(IntPtr hwnd, SLR_FLAGS fFlags);
+ /// <summary>
+ /// Sets the path and file name of a Shell link object
+ /// </summary>
+ /// <param name="pszFile">The PSZ file.</param>
+ void SetPath([MarshalAs(UnmanagedType.LPWStr)] string pszFile);
+
+ }
+
+ /// <summary>
+ /// Interface IPersist
+ /// </summary>
+ [ComImport, Guid("0000010c-0000-0000-c000-000000000046"),
+ InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
+ public interface IPersist
+ {
+ /// <summary>
+ /// Gets the class ID.
+ /// </summary>
+ /// <param name="pClassID">The p class ID.</param>
+ [PreserveSig]
+ void GetClassID(out Guid pClassID);
+ }
+
+ /// <summary>
+ /// Interface IPersistFile
+ /// </summary>
+ [ComImport, Guid("0000010b-0000-0000-C000-000000000046"),
+ InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
+ public interface IPersistFile : IPersist
+ {
+ /// <summary>
+ /// Gets the class ID.
+ /// </summary>
+ /// <param name="pClassID">The p class ID.</param>
+ new void GetClassID(out Guid pClassID);
+ /// <summary>
+ /// Determines whether this instance is dirty.
+ /// </summary>
+ [PreserveSig]
+ int IsDirty();
+
+ /// <summary>
+ /// Loads the specified PSZ file name.
+ /// </summary>
+ /// <param name="pszFileName">Name of the PSZ file.</param>
+ /// <param name="dwMode">The dw mode.</param>
+ [PreserveSig]
+ void Load([In, MarshalAs(UnmanagedType.LPWStr)]
+ string pszFileName, uint dwMode);
+
+ /// <summary>
+ /// Saves the specified PSZ file name.
+ /// </summary>
+ /// <param name="pszFileName">Name of the PSZ file.</param>
+ /// <param name="remember">if set to <c>true</c> [remember].</param>
+ [PreserveSig]
+ void Save([In, MarshalAs(UnmanagedType.LPWStr)] string pszFileName,
+ [In, MarshalAs(UnmanagedType.Bool)] bool remember);
+
+ /// <summary>
+ /// Saves the completed.
+ /// </summary>
+ /// <param name="pszFileName">Name of the PSZ file.</param>
+ [PreserveSig]
+ void SaveCompleted([In, MarshalAs(UnmanagedType.LPWStr)] string pszFileName);
+
+ /// <summary>
+ /// Gets the cur file.
+ /// </summary>
+ /// <param name="ppszFileName">Name of the PPSZ file.</param>
+ [PreserveSig]
+ void GetCurFile([In, MarshalAs(UnmanagedType.LPWStr)] string ppszFileName);
+ }
+
+ // CLSID_ShellLink from ShlGuid.h
+ /// <summary>
+ /// Class ShellLink
+ /// </summary>
+ [
+ ComImport,
+ Guid("00021401-0000-0000-C000-000000000046")
+ ]
+ public class ShellLink
+ {
+ }
+}
diff --git a/MediaBrowser.ServerApplication/Native/Standby.cs b/MediaBrowser.ServerApplication/Native/Standby.cs
index 274c72b25..919709538 100644
--- a/MediaBrowser.ServerApplication/Native/Standby.cs
+++ b/MediaBrowser.ServerApplication/Native/Standby.cs
@@ -1,4 +1,5 @@
-using System.Runtime.InteropServices;
+using System;
+using System.Runtime.InteropServices;
namespace MediaBrowser.ServerApplication.Native
{
@@ -7,11 +8,33 @@ namespace MediaBrowser.ServerApplication.Native
/// </summary>
public static class Standby
{
- public static void PreventSystemStandby()
+ public static void PreventSleepAndMonitorOff()
{
- SystemHelper.ResetStandbyTimer();
+ NativeMethods.SetThreadExecutionState(NativeMethods.ES_CONTINUOUS | NativeMethods.ES_SYSTEM_REQUIRED | NativeMethods.ES_DISPLAY_REQUIRED);
}
+ public static void PreventSleep()
+ {
+ NativeMethods.SetThreadExecutionState(NativeMethods.ES_CONTINUOUS | NativeMethods.ES_SYSTEM_REQUIRED);
+ }
+
+ // Clear EXECUTION_STATE flags to allow the system to sleep and turn off monitor normally
+ public static void AllowSleep()
+ {
+ NativeMethods.SetThreadExecutionState(NativeMethods.ES_CONTINUOUS);
+ }
+
+ internal static class NativeMethods
+ {
+ // Import SetThreadExecutionState Win32 API and necessary flags
+ [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
+ public static extern uint SetThreadExecutionState(uint esFlags);
+ public const uint ES_CONTINUOUS = 0x80000000;
+ public const uint ES_SYSTEM_REQUIRED = 0x00000001;
+ public const uint ES_DISPLAY_REQUIRED = 0x00000002;
+ }
+
+ [Flags]
internal enum EXECUTION_STATE : uint
{
ES_NONE = 0,
@@ -21,16 +44,5 @@ namespace MediaBrowser.ServerApplication.Native
ES_AWAYMODE_REQUIRED = 0x00000040,
ES_CONTINUOUS = 0x80000000
}
-
- public class SystemHelper
- {
- [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
- static extern EXECUTION_STATE SetThreadExecutionState(EXECUTION_STATE esFlags);
-
- public static void ResetStandbyTimer()
- {
- EXECUTION_STATE es = SetThreadExecutionState(EXECUTION_STATE.ES_SYSTEM_REQUIRED);
- }
- }
}
}
diff --git a/MediaBrowser.ServerApplication/Native/WindowsApp.cs b/MediaBrowser.ServerApplication/Native/WindowsApp.cs
index 164037dc5..d8b2720c2 100644
--- a/MediaBrowser.ServerApplication/Native/WindowsApp.cs
+++ b/MediaBrowser.ServerApplication/Native/WindowsApp.cs
@@ -1,11 +1,19 @@
-using MediaBrowser.Common.Net;
+using System;
+using MediaBrowser.Common.Net;
using MediaBrowser.Model.Logging;
using MediaBrowser.Server.Startup.Common;
using MediaBrowser.ServerApplication.Networking;
using System.Collections.Generic;
+using System.Diagnostics;
+using System.IO;
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;
namespace MediaBrowser.ServerApplication.Native
{
@@ -30,7 +38,7 @@ namespace MediaBrowser.ServerApplication.Native
}
list.Add(GetType().Assembly);
-
+
return list;
}
@@ -46,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
};
}
@@ -107,7 +115,22 @@ namespace MediaBrowser.ServerApplication.Native
public void ConfigureAutoRun(bool autorun)
{
- Autorun.Configure(autorun, _fileSystem);
+ var shortcutPath = Path.Combine(System.Environment.GetFolderPath(System.Environment.SpecialFolder.StartMenu), "Emby", "Emby Server.lnk");
+
+ var startupPath = System.Environment.GetFolderPath(System.Environment.SpecialFolder.Startup);
+
+ if (autorun)
+ {
+ //Copy our shortut into the startup folder for this user
+ var targetPath = Path.Combine(startupPath, Path.GetFileName(shortcutPath) ?? "Emby Server.lnk");
+ _fileSystem.CreateDirectory(Path.GetDirectoryName(targetPath));
+ File.Copy(shortcutPath, targetPath, true);
+ }
+ else
+ {
+ //Remove our shortcut from the startup folder for this user
+ _fileSystem.DeleteFile(Path.Combine(startupPath, Path.GetFileName(shortcutPath) ?? "Emby Server.lnk"));
+ }
}
public INetworkManager CreateNetworkManager(ILogger logger)
@@ -117,12 +140,69 @@ namespace MediaBrowser.ServerApplication.Native
public void PreventSystemStandby()
{
- Standby.PreventSystemStandby();
+ MainStartup.Invoke(Standby.PreventSleep);
+ }
+
+ public void AllowSystemStandby()
+ {
+ MainStartup.Invoke(Standby.AllowSleep);
}
public IPowerManagement GetPowerManagement()
{
return new WindowsPowerManagement(_logger);
}
+
+ public FFMpegInstallInfo GetFfmpegInstallInfo()
+ {
+ var info = new FFMpegInstallInfo();
+
+ info.FFMpegFilename = "ffmpeg.exe";
+ info.FFProbeFilename = "ffprobe.exe";
+ info.Version = "0";
+
+ return info;
+ }
+
+ public void LaunchUrl(string url)
+ {
+ var process = new Process
+ {
+ StartInfo = new ProcessStartInfo
+ {
+ FileName = url
+ },
+
+ EnableRaisingEvents = true,
+ };
+
+ process.Exited += ProcessExited;
+
+ try
+ {
+ process.Start();
+ }
+ catch (Exception ex)
+ {
+ _logger.ErrorException("Error launching url: {0}", ex, url);
+
+ throw;
+ }
+ }
+
+ public IDbConnector GetDbConnector()
+ {
+ return new DbConnector(_logger);
+ }
+
+ /// <summary>
+ /// Processes the exited.
+ /// </summary>
+ /// <param name="sender">The sender.</param>
+ /// <param name="e">The <see cref="EventArgs" /> instance containing the event data.</param>
+ private static void ProcessExited(object sender, EventArgs e)
+ {
+ ((Process)sender).Dispose();
+ }
}
-}
+} \ No newline at end of file
diff --git a/MediaBrowser.ServerApplication/Networking/NetworkManager.cs b/MediaBrowser.ServerApplication/Networking/NetworkManager.cs
index 978a29db0..ed60de9d2 100644
--- a/MediaBrowser.ServerApplication/Networking/NetworkManager.cs
+++ b/MediaBrowser.ServerApplication/Networking/NetworkManager.cs
@@ -29,6 +29,7 @@ namespace MediaBrowser.ServerApplication.Networking
/// <returns>IEnumerable{NetworkShare}.</returns>
public IEnumerable<NetworkShare> GetNetworkShares(string path)
{
+ Logger.Info("Getting network shares from {0}", path);
return new ShareCollection(path).OfType<Share>().Select(ToNetworkShare);
}
@@ -88,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
@@ -117,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
@@ -128,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);
}
}
}
@@ -139,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/Networking/NetworkShares.cs b/MediaBrowser.ServerApplication/Networking/NetworkShares.cs
index 91bd167b8..f9a59203d 100644
--- a/MediaBrowser.ServerApplication/Networking/NetworkShares.cs
+++ b/MediaBrowser.ServerApplication/Networking/NetworkShares.cs
@@ -390,7 +390,9 @@ namespace MediaBrowser.ServerApplication.Networking
Type t = (2 == level) ? typeof(SHARE_INFO_2) : typeof(SHARE_INFO_1);
int offset = Marshal.SizeOf(t);
- for (int i = 0, lpItem = pBuffer.ToInt32(); i < entriesRead; i++, lpItem += offset)
+ var lpItem = pBuffer.ToInt64();
+
+ for (int i = 0; i < entriesRead; i++, lpItem += offset)
{
IntPtr pItem = new IntPtr(lpItem);
if (1 == level)
diff --git a/MediaBrowser.ServerApplication/Properties/Resources.Designer.cs b/MediaBrowser.ServerApplication/Properties/Resources.Designer.cs
index 6323af6fd..e166f35d0 100644
--- a/MediaBrowser.ServerApplication/Properties/Resources.Designer.cs
+++ b/MediaBrowser.ServerApplication/Properties/Resources.Designer.cs
@@ -1,7 +1,7 @@
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
-// Runtime Version:4.0.30319.34011
+// Runtime Version:4.0.30319.42000
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
diff --git a/MediaBrowser.ServerApplication/ServerNotifyIcon.cs b/MediaBrowser.ServerApplication/ServerNotifyIcon.cs
index 673c6cddd..27816db5a 100644
--- a/MediaBrowser.ServerApplication/ServerNotifyIcon.cs
+++ b/MediaBrowser.ServerApplication/ServerNotifyIcon.cs
@@ -36,10 +36,15 @@ namespace MediaBrowser.ServerApplication
set
{
Action act = () => notifyIcon1.Visible = false;
- contextMenuStrip1.Invoke(act);
+ Invoke(act);
}
}
+ public void Invoke(Action action)
+ {
+ contextMenuStrip1.Invoke(action);
+ }
+
public ServerNotifyIcon(ILogManager logManager,
IServerApplicationHost appHost,
IServerConfigurationManager configurationManager,
@@ -163,7 +168,7 @@ namespace MediaBrowser.ServerApplication
void notifyIcon1_DoubleClick(object sender, EventArgs e)
{
- BrowserLauncher.OpenDashboard(_appHost, _logger);
+ BrowserLauncher.OpenDashboard(_appHost);
}
private void LocalizeText()
@@ -194,17 +199,17 @@ namespace MediaBrowser.ServerApplication
void cmdBrowse_Click(object sender, EventArgs e)
{
- BrowserLauncher.OpenWebClient(_appHost, _logger);
+ BrowserLauncher.OpenWebClient(_appHost);
}
void cmdCommunity_Click(object sender, EventArgs e)
{
- BrowserLauncher.OpenCommunity(_logger);
+ BrowserLauncher.OpenCommunity(_appHost);
}
void cmdConfigure_Click(object sender, EventArgs e)
{
- BrowserLauncher.OpenDashboard(_appHost, _logger);
+ BrowserLauncher.OpenDashboard(_appHost);
}
void cmdRestart_Click(object sender, EventArgs e)
diff --git a/MediaBrowser.ServerApplication/packages.config b/MediaBrowser.ServerApplication/packages.config
index 5187a1db3..a573c6fd9 100644
--- a/MediaBrowser.ServerApplication/packages.config
+++ b/MediaBrowser.ServerApplication/packages.config
@@ -2,7 +2,6 @@
<packages>
<package id="CommonIO" version="1.0.0.9" targetFramework="net45" />
<package id="ImageMagickSharp" version="1.0.0.18" targetFramework="net45" />
- <package id="MediaBrowser.IsoMounting" version="3.0.69" targetFramework="net45" />
<package id="Patterns.Logging" version="1.0.0.2" targetFramework="net45" />
- <package id="System.Data.SQLite.Core" version="1.0.94.0" targetFramework="net45" />
+ <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 3a69c9666..34ad32111 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()
@@ -316,7 +317,7 @@ namespace MediaBrowser.WebDashboard.Api
DeleteFilesByExtension(bowerPath, ".txt");
DeleteFilesByExtension(bowerPath, ".map");
DeleteFilesByExtension(bowerPath, ".md");
- DeleteFilesByExtension(bowerPath, ".json");
+ DeleteFilesByExtension(bowerPath, ".json", "strings\\");
DeleteFilesByExtension(bowerPath, ".gz");
DeleteFilesByExtension(bowerPath, ".bat");
DeleteFilesByExtension(bowerPath, ".sh");
@@ -342,26 +343,22 @@ 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);
-
+ //_fileSystem.DeleteDirectory(Path.Combine(bowerPath, "fingerprintjs2", "flash"), true);
+ //_fileSystem.DeleteDirectory(Path.Combine(bowerPath, "fingerprintjs2", "specs"), true);
+
DeleteCryptoFiles(Path.Combine(bowerPath, "cryptojslib", "components"));
DeleteFoldersByName(Path.Combine(bowerPath, "jquery"), "src");
DeleteFoldersByName(Path.Combine(bowerPath, "jstree"), "src");
- 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);
- _fileSystem.DeleteDirectory(Path.Combine(bowerPath, "prism"), true);
- _fileSystem.DeleteDirectory(Path.Combine(bowerPath, "prism-element"), true);
+ //DeleteFoldersByName(Path.Combine(bowerPath, "Sortable"), "meteor");
+ //DeleteFoldersByName(Path.Combine(bowerPath, "Sortable"), "st");
+ //DeleteFoldersByName(Path.Combine(bowerPath, "Swiper"), "src");
if (string.Equals(mode, "cordova", StringComparison.OrdinalIgnoreCase))
{
@@ -399,7 +396,7 @@ namespace MediaBrowser.WebDashboard.Api
}
}
- private void DeleteFilesByExtension(string path, string extension)
+ private void DeleteFilesByExtension(string path, string extension, string exclude = null)
{
var files = _fileSystem.GetFiles(path, true)
.Where(i => string.Equals(i.Extension, extension, StringComparison.OrdinalIgnoreCase))
@@ -407,6 +404,13 @@ namespace MediaBrowser.WebDashboard.Api
foreach (var file in files)
{
+ if (!string.IsNullOrWhiteSpace(exclude))
+ {
+ if (file.FullName.IndexOf(exclude, StringComparison.OrdinalIgnoreCase) != -1)
+ {
+ continue;
+ }
+ }
_fileSystem.DeleteFile(file.FullName);
}
}
@@ -515,18 +519,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 79e2d6102..b7b1f1dfd 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>
@@ -384,7 +348,7 @@ namespace MediaBrowser.WebDashboard.Api
if (string.Equals(mode, "cordova", StringComparison.OrdinalIgnoreCase))
{
- sb.Append("<meta http-equiv=\"Content-Security-Policy\" content=\"default-src * 'unsafe-inline' 'unsafe-eval' data:;\">");
+ sb.Append("<meta http-equiv=\"Content-Security-Policy\" content=\"default-src * 'unsafe-inline' 'unsafe-eval' data: filesystem:;\">");
}
sb.Append("<link rel=\"manifest\" href=\"manifest.json\">");
@@ -415,6 +379,7 @@ namespace MediaBrowser.WebDashboard.Api
sb.Append("<link rel=\"shortcut icon\" href=\"css/images/favicon.ico\">");
sb.Append("<meta name=\"msapplication-TileImage\" content=\"css/images/touchicon144.png\">");
sb.Append("<meta name=\"msapplication-TileColor\" content=\"#333333\">");
+ sb.Append("<meta name=\"theme-color\" content=\"#43A047\">");
return sb.ToString();
}
@@ -475,15 +440,7 @@ namespace MediaBrowser.WebDashboard.Api
files.Insert(0, "cordova.js");
}
- var tags = files.Select(s =>
- {
- if (s.IndexOf("require", StringComparison.OrdinalIgnoreCase) == -1)
- {
- return string.Format("<script src=\"{0}\" async></script>", s);
- }
- return string.Format("<script src=\"{0}\"></script>", s);
-
- }).ToArray();
+ var tags = files.Select(s => string.Format("<script src=\"{0}\" defer></script>", s)).ToArray();
builder.Append(string.Join(string.Empty, tags));
diff --git a/MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj b/MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj
index 6c67e4483..e4150d85c 100644
--- a/MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj
+++ b/MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj
@@ -101,10 +101,22 @@
<Content Include="dashboard-ui\autoorganizesmart.html">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
+ <Content Include="dashboard-ui\components\apphost.js">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\components\categorysyncbuttons.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>
- <Content Include="dashboard-ui\bower_components\fastclick\lib\fastclick.js">
+ <Content Include="dashboard-ui\components\directorybrowser\directorybrowser.css">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\components\favoriteitems.js">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="dashboard-ui\components\filterdialog\filterdialog.js">
@@ -116,22 +128,25 @@
<Content Include="dashboard-ui\components\filterdialog\style.css">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
- <Content Include="dashboard-ui\components\guestinviter\guestinviter.js">
+ <Content Include="dashboard-ui\components\groupedcards.js">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
- <Content Include="dashboard-ui\components\guestinviter\guestinviter.template.html">
+ <Content Include="dashboard-ui\components\guestinviter\connectlink.js">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
- <Content Include="dashboard-ui\components\metadataeditor\metadataeditor.js">
+ <Content Include="dashboard-ui\components\guestinviter\connectlink.template.html">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
- <Content Include="dashboard-ui\components\metadataeditor\personeditor.template.html">
+ <Content Include="dashboard-ui\components\guestinviter\guestinviter.js">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
- <Content Include="dashboard-ui\components\recordingcreator\recordingcreator.js">
+ <Content Include="dashboard-ui\components\guestinviter\guestinviter.template.html">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
- <Content Include="dashboard-ui\components\recordingcreator\recordingcreator.template.html">
+ <Content Include="dashboard-ui\components\navdrawer\navdrawer.css">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\components\navdrawer\navdrawer.js">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="dashboard-ui\components\remotecontrol.js">
@@ -140,9 +155,21 @@
<Content Include="dashboard-ui\components\remotecontrolautoplay.js">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
+ <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\dashboard.css">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
<Content Include="dashboard-ui\css\images\logo.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
@@ -158,9 +185,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>
@@ -203,24 +227,6 @@
<Content Include="dashboard-ui\components\medialibraryeditor\medialibraryeditor.template.html">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
- <Content Include="dashboard-ui\components\metadataeditor\personeditor.js">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <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\components\tvguide\tvguide.js">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\components\tvguide\tvguide.template.html">
- <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>
@@ -263,19 +269,16 @@
<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>
<Content Include="dashboard-ui\legacy\dashboard.js">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
- <Content Include="dashboard-ui\legacy\objectassign.js">
+ <Content Include="dashboard-ui\legacy\selectmenu.js">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
- <Content Include="dashboard-ui\legacy\selectmenu.js">
+ <Content Include="dashboard-ui\librarydisplay.html">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="dashboard-ui\livetvguideprovider.html">
@@ -305,12 +308,18 @@
<Content Include="dashboard-ui\scripts\autobackdrops.js">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
+ <Content Include="dashboard-ui\scripts\homefavorites.js">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
<Content Include="dashboard-ui\scripts\homenextup.js">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<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>
@@ -341,9 +350,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>
@@ -356,13 +362,16 @@
<Content Include="dashboard-ui\scripts\shared.js">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
- <Content Include="dashboard-ui\scripts\sharingmanager.js">
+ <Content Include="dashboard-ui\scripts\supporterkeypage.js">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
- <Content Include="dashboard-ui\components\sharingwidget.js">
+ <Content Include="dashboard-ui\scripts\tvlatest.js">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
- <Content Include="dashboard-ui\scripts\supporterkeypage.js">
+ <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">
@@ -383,9 +392,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>
@@ -455,26 +461,12 @@
<Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.5\jquery.mobile.custom.icons.css">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
- <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.5\jquery.mobile.custom.js">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
<Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.5\jquery.mobile.custom.theme.css">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="dashboard-ui\thirdparty\paper-button-style.css">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
- <Content Include="dashboard-ui\thirdparty\social-share-kit-1.0.4\.gitignore" />
- <Content Include="dashboard-ui\thirdparty\social-share-kit-1.0.4\dist\css\social-share-kit.css">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\thirdparty\social-share-kit-1.0.4\dist\fonts\social-share-kit.svg">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\thirdparty\social-share-kit-1.0.4\dist\js\social-share-kit.js" />
- <Content Include="dashboard-ui\thirdparty\social-share-kit-1.0.4\dist\js\social-share-kit.min.js">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
<Content Include="dashboard-ui\components\tvproviders\schedulesdirect.js">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
@@ -487,9 +479,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>
@@ -511,9 +500,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>
@@ -769,12 +755,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\lock.png">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
<Content Include="dashboard-ui\css\images\editor\missing.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
@@ -787,9 +767,6 @@
<Content Include="dashboard-ui\css\images\editor\missingprimaryimage.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
- <Content Include="dashboard-ui\css\images\editor\missingtrailer.png">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
<Content Include="dashboard-ui\css\metadataeditor.css">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
@@ -841,9 +818,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>
@@ -916,9 +890,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>
@@ -934,9 +905,6 @@
<Content Include="dashboard-ui\scripts\favorites.js">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
- <Content Include="dashboard-ui\scripts\librarylist.js">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
<Content Include="dashboard-ui\scripts\librarymenu.js">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
@@ -961,18 +929,12 @@
<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>
<Content Include="dashboard-ui\scripts\livetvchannel.js">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
- <Content Include="dashboard-ui\livetvtimer.html">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
<Content Include="dashboard-ui\scripts\livetvrecordinglist.js">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
@@ -985,12 +947,6 @@
<Content Include="dashboard-ui\scripts\livetvstatus.js">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
- <Content Include="dashboard-ui\scripts\livetvtimer.js">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\scripts\livetvtimers.js">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
<Content Include="dashboard-ui\scripts\editorsidebar.js">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
@@ -1003,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>
@@ -1090,43 +1043,10 @@
<Content Include="dashboard-ui\userpassword.html">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
- <Content Include="dashboard-ui\voice\commands\controlcommands.js">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\voice\commands\disablecommands.js">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\voice\commands\enablecommands.js">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\voice\commands\playcommands.js">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\voice\commands\searchcommands.js">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\voice\commands\showcommands.js">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\voice\commands\togglecommands.js">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\voice\grammarprocessor.js">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\voice\voicedialog.js">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\voice\voice.css">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\voice\voice.js">
+ <Content Include="dashboard-ui\wizardagreement.html">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
- <Content Include="dashboard-ui\voice\voicecommands.js">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\wizardagreement.html">
+ <Content Include="dashboard-ui\wizardcomponents.html">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="dashboard-ui\wizardlivetvguide.html">
@@ -1149,11 +1069,6 @@
</Content>
</ItemGroup>
<ItemGroup>
- <Content Include="dashboard-ui\advanced.html">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- </ItemGroup>
- <ItemGroup>
<Content Include="dashboard-ui\dashboard.html">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
@@ -1168,9 +1083,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>
@@ -1189,12 +1101,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>
@@ -1372,9 +1278,6 @@
</Content>
</ItemGroup>
<ItemGroup>
- <Content Include="dashboard-ui\scripts\advancedconfigurationpage.js">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
<Content Include="dashboard-ui\scripts\useredit.js">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
@@ -1600,9 +1503,6 @@
</Content>
</ItemGroup>
<ItemGroup>
- <None Include="dashboard-ui\css\fonts\Montserrat.woff">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </None>
<Content Include="dashboard-ui\strings\ar.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
@@ -1687,9 +1587,6 @@
<Content Include="dashboard-ui\strings\ru.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
- <Content Include="dashboard-ui\strings\server.json">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
<Content Include="dashboard-ui\strings\sl-SI.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
@@ -1717,12 +1614,18 @@
<None Include="dashboard-ui\strings\fr-CA.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
+ <None Include="dashboard-ui\strings\fr-FR.json">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </None>
<None Include="dashboard-ui\strings\hu.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Include="dashboard-ui\strings\id.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
+ <None Include="dashboard-ui\strings\sk.json">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </None>
<None Include="dashboard-ui\strings\zh-HK.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
@@ -1732,29 +1635,6 @@
<Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.5\jqm.popup.js">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
- <None Include="dashboard-ui\thirdparty\jquerymobile-1.4.5\jquery.mobile-1.4.5.min.map">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </None>
- <None Include="dashboard-ui\thirdparty\social-share-kit-1.0.4\dist\fonts\social-share-kit.eot">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </None>
- <None Include="dashboard-ui\thirdparty\social-share-kit-1.0.4\dist\fonts\social-share-kit.ttf">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </None>
- <None Include="dashboard-ui\thirdparty\social-share-kit-1.0.4\dist\fonts\social-share-kit.woff">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </None>
- <None Include="dashboard-ui\thirdparty\social-share-kit-1.0.4\LICENSE" />
- <None Include="dashboard-ui\thirdparty\social-share-kit-1.0.4\README.md" />
- <None Include="dashboard-ui\voice\grammar\en-US.json">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </None>
- <None Include="dashboard-ui\voice\grammar\grammar.json">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </None>
- <None Include="dashboard-ui\voice\Readme.md">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </None>
<None Include="packages.config" />
</ItemGroup>
<ItemGroup />
diff --git a/MediaBrowser.WebDashboard/packages.config b/MediaBrowser.WebDashboard/packages.config
index eecb6bec2..a2d13fdf5 100644
--- a/MediaBrowser.WebDashboard/packages.config
+++ b/MediaBrowser.WebDashboard/packages.config
@@ -1,7 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="CommonIO" version="1.0.0.9" targetFramework="net45" />
- <package id="MediaBrowser.ApiClient.Javascript" version="3.0.249" targetFramework="net45" />
<package id="Patterns.Logging" version="1.0.0.2" targetFramework="net45" />
<package id="WebMarkupMin.Core" version="1.0.1" targetFramework="net45" />
</packages> \ No newline at end of file
diff --git a/MediaBrowser.XbmcMetadata/Parsers/BaseNfoParser.cs b/MediaBrowser.XbmcMetadata/Parsers/BaseNfoParser.cs
index cb74d4dd7..2e34135a6 100644
--- a/MediaBrowser.XbmcMetadata/Parsers/BaseNfoParser.cs
+++ b/MediaBrowser.XbmcMetadata/Parsers/BaseNfoParser.cs
@@ -85,8 +85,6 @@ namespace MediaBrowser.XbmcMetadata.Parsers
/// <param name="cancellationToken">The cancellation token.</param>
private void Fetch(MetadataResult<T> item, string metadataFile, XmlReaderSettings settings, CancellationToken cancellationToken)
{
- item.ResetPeople();
-
if (!SupportsUrlAfterClosingXmlTag)
{
using (var streamReader = BaseNfoSaver.GetStreamReader(metadataFile))
@@ -94,6 +92,8 @@ namespace MediaBrowser.XbmcMetadata.Parsers
// Use XmlReader for best performance
using (var reader = XmlReader.Create(streamReader, settings))
{
+ item.ResetPeople();
+
reader.MoveToContent();
// Loop through each element
@@ -113,6 +113,8 @@ namespace MediaBrowser.XbmcMetadata.Parsers
using (var streamReader = BaseNfoSaver.GetStreamReader(metadataFile))
{
+ item.ResetPeople();
+
// Need to handle a url after the xml data
// http://kodi.wiki/view/NFO_files/movies
@@ -660,7 +662,7 @@ namespace MediaBrowser.XbmcMetadata.Parsers
{
if (!string.IsNullOrWhiteSpace(val))
{
- val = val.Replace("plugin://plugin.video.youtube/?action=play_video&videoid=", "http://www.youtube.com/watch?v=", StringComparison.OrdinalIgnoreCase);
+ val = val.Replace("plugin://plugin.video.youtube/?action=play_video&videoid=", "https://www.youtube.com/watch?v=", StringComparison.OrdinalIgnoreCase);
hasTrailer.AddTrailerUrl(val, false);
}
@@ -917,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;
}
@@ -930,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/Providers/BaseNfoProvider.cs b/MediaBrowser.XbmcMetadata/Providers/BaseNfoProvider.cs
index 5ce8d30ad..c1dd92987 100644
--- a/MediaBrowser.XbmcMetadata/Providers/BaseNfoProvider.cs
+++ b/MediaBrowser.XbmcMetadata/Providers/BaseNfoProvider.cs
@@ -9,7 +9,7 @@ using CommonIO;
namespace MediaBrowser.XbmcMetadata.Providers
{
- public abstract class BaseNfoProvider<T> : ILocalMetadataProvider<T>, IHasChangeMonitor
+ public abstract class BaseNfoProvider<T> : ILocalMetadataProvider<T>, IHasItemChangeMonitor
where T : IHasMetadata, new()
{
protected IFileSystem FileSystem;
@@ -57,7 +57,7 @@ namespace MediaBrowser.XbmcMetadata.Providers
protected abstract FileSystemMetadata GetXmlFile(ItemInfo info, IDirectoryService directoryService);
- public bool HasChanged(IHasMetadata item, IDirectoryService directoryService, DateTime date)
+ public bool HasChanged(IHasMetadata item, IDirectoryService directoryService)
{
var file = GetXmlFile(new ItemInfo(item), directoryService);
@@ -66,7 +66,7 @@ namespace MediaBrowser.XbmcMetadata.Providers
return false;
}
- return file.Exists && FileSystem.GetLastWriteTimeUtc(file) > date;
+ return file.Exists && FileSystem.GetLastWriteTimeUtc(file) > item.DateLastSaved;
}
public string Name
diff --git a/MediaBrowser.XbmcMetadata/Savers/BaseNfoSaver.cs b/MediaBrowser.XbmcMetadata/Savers/BaseNfoSaver.cs
index ba8a3bdf5..954fb2a47 100644
--- a/MediaBrowser.XbmcMetadata/Savers/BaseNfoSaver.cs
+++ b/MediaBrowser.XbmcMetadata/Savers/BaseNfoSaver.cs
@@ -99,7 +99,9 @@ namespace MediaBrowser.XbmcMetadata.Savers
"collectionitem",
"isuserfavorite",
- "userrating"
+ "userrating",
+
+ "countrycode"
}.ToDictionary(i => i, StringComparer.OrdinalIgnoreCase);
@@ -479,7 +481,6 @@ namespace MediaBrowser.XbmcMetadata.Savers
writer.WriteElementString("dateadded", item.DateCreated.ToLocalTime().ToString(DateAddedFormat));
writer.WriteElementString("title", item.Name ?? string.Empty);
- writer.WriteElementString("originaltitle", item.Name ?? string.Empty);
var hasOriginalTitle = item as IHasOriginalTitle;
if (hasOriginalTitle != null)
@@ -737,29 +738,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;
@@ -890,7 +883,7 @@ namespace MediaBrowser.XbmcMetadata.Savers
{
// This is what xbmc expects
- return url.Replace("http://www.youtube.com/watch?v=",
+ return url.Replace("https://www.youtube.com/watch?v=",
"plugin://plugin.video.youtube/?action=play_video&videoid=",
StringComparison.OrdinalIgnoreCase);
}
@@ -937,7 +930,7 @@ namespace MediaBrowser.XbmcMetadata.Savers
return;
}
- var userdata = userDataRepo.GetUserData(user.Id, item.GetUserDataKey());
+ var userdata = userDataRepo.GetUserData(user, item);
writer.WriteElementString("isuserfavorite", userdata.IsFavorite.ToString().ToLower());
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 10a898785..21173feaa 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.644</version>
+ <version>3.0.653</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.644" />
- <dependency id="NLog" version="4.2.3" />
- <dependency id="SimpleInjector" version="3.1.2" />
+ <dependency id="MediaBrowser.Common" version="3.0.653" />
+ <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 616f0ca2a..294685847 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.644</version>
+ <version>3.0.653</version>
<title>MediaBrowser.Common</title>
<authors>Emby Team</authors>
<owners>ebr,Luke,scottisafool</owners>
diff --git a/Nuget/MediaBrowser.Model.Signed.nuspec b/Nuget/MediaBrowser.Model.Signed.nuspec
deleted file mode 100644
index d17ff16b6..000000000
--- a/Nuget/MediaBrowser.Model.Signed.nuspec
+++ /dev/null
@@ -1,20 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<package xmlns="http://schemas.microsoft.com/packaging/2011/08/nuspec.xsd">
- <metadata>
- <id>MediaBrowser.Model.Signed</id>
- <version>3.0.644</version>
- <title>MediaBrowser.Model - Signed Edition</title>
- <authors>Emby Team</authors>
- <owners>ebr,Luke,scottisafool</owners>
- <projectUrl>https://github.com/MediaBrowser/MediaBrowser</projectUrl>
- <iconUrl>http://www.mb3admin.com/images/mb3icons1-1.png</iconUrl>
- <requireLicenseAcceptance>false</requireLicenseAcceptance>
- <description>Contains common model objects and interfaces used by all Emby solutions.</description>
- <copyright>Copyright © Emby 2013</copyright>
- <dependencies>
- </dependencies>
- </metadata>
- <files>
- <file src="dllssigned\net45\MediaBrowser.Model.dll" target="lib\net45\MediaBrowser.Model.dll" />
- </files>
-</package> \ No newline at end of file
diff --git a/Nuget/MediaBrowser.Server.Core.nuspec b/Nuget/MediaBrowser.Server.Core.nuspec
index 2098bbcfe..18e8a5c13 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.644</version>
+ <version>3.0.653</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.644" />
+ <dependency id="MediaBrowser.Common" version="3.0.653" />
<dependency id="Interfaces.IO" version="1.0.0.5" />
</dependencies>
</metadata>
diff --git a/OpenSubtitlesHandler/Utilities.cs b/OpenSubtitlesHandler/Utilities.cs
index 2ae116521..d26c76b7c 100644
--- a/OpenSubtitlesHandler/Utilities.cs
+++ b/OpenSubtitlesHandler/Utilities.cs
@@ -32,7 +32,7 @@ namespace OpenSubtitlesHandler
/// </summary>
public sealed class Utilities
{
- private const string XML_RPC_SERVER = "http://api.opensubtitles.org/xml-rpc";
+ private const string XML_RPC_SERVER = "https://api.opensubtitles.org/xml-rpc";
/// <summary>
/// Compute movie hash
@@ -195,7 +195,7 @@ namespace OpenSubtitlesHandler
RequestContentBytes = request,
RequestContentType = "text/xml",
UserAgent = userAgent,
- Host = "api.opensubtitles.org:80",
+ Host = "api.opensubtitles.org:443",
Url = XML_RPC_SERVER,
// Response parsing will fail with this enabled
diff --git a/OpenSubtitlesHandler/XML-RPC/XmlRpcGenerator.cs b/OpenSubtitlesHandler/XML-RPC/XmlRpcGenerator.cs
index 5a190ab72..c39917e29 100644
--- a/OpenSubtitlesHandler/XML-RPC/XmlRpcGenerator.cs
+++ b/OpenSubtitlesHandler/XML-RPC/XmlRpcGenerator.cs
@@ -51,47 +51,45 @@ namespace XmlRpcHandler
XmlWriterSettings sett = new XmlWriterSettings();
sett.Indent = true;
- var requestXmlPath = Path.Combine(Path.GetTempPath(), "request.xml");
-
sett.Encoding = Encoding.UTF8;
- FileStream str = new FileStream(requestXmlPath, FileMode.Create, FileAccess.Write);
- XmlWriter XMLwrt = XmlWriter.Create(str, sett);
- // Let's write the methods
- foreach (XmlRpcMethodCall method in methods)
+ using (var ms = new MemoryStream())
{
- XMLwrt.WriteStartElement("methodCall");//methodCall
- XMLwrt.WriteStartElement("methodName");//methodName
- XMLwrt.WriteString(method.Name);
- XMLwrt.WriteEndElement();//methodName
- XMLwrt.WriteStartElement("params");//params
- // Write values
- foreach (IXmlRpcValue p in method.Parameters)
+ XmlWriter XMLwrt = XmlWriter.Create(ms, sett);
+ // Let's write the methods
+ foreach (XmlRpcMethodCall method in methods)
{
- XMLwrt.WriteStartElement("param");//param
- if (p is XmlRpcValueBasic)
- {
- WriteBasicValue(XMLwrt, (XmlRpcValueBasic)p);
- }
- else if (p is XmlRpcValueStruct)
+ XMLwrt.WriteStartElement("methodCall");//methodCall
+ XMLwrt.WriteStartElement("methodName");//methodName
+ XMLwrt.WriteString(method.Name);
+ XMLwrt.WriteEndElement();//methodName
+ XMLwrt.WriteStartElement("params");//params
+ // Write values
+ foreach (IXmlRpcValue p in method.Parameters)
{
- WriteStructValue(XMLwrt, (XmlRpcValueStruct)p);
- }
- else if (p is XmlRpcValueArray)
- {
- WriteArrayValue(XMLwrt, (XmlRpcValueArray)p);
+ XMLwrt.WriteStartElement("param");//param
+ if (p is XmlRpcValueBasic)
+ {
+ WriteBasicValue(XMLwrt, (XmlRpcValueBasic)p);
+ }
+ else if (p is XmlRpcValueStruct)
+ {
+ WriteStructValue(XMLwrt, (XmlRpcValueStruct)p);
+ }
+ else if (p is XmlRpcValueArray)
+ {
+ WriteArrayValue(XMLwrt, (XmlRpcValueArray)p);
+ }
+ XMLwrt.WriteEndElement();//param
}
- XMLwrt.WriteEndElement();//param
- }
- XMLwrt.WriteEndElement();//params
- XMLwrt.WriteEndElement();//methodCall
+ XMLwrt.WriteEndElement();//params
+ XMLwrt.WriteEndElement();//methodCall
+ }
+ XMLwrt.Flush();
+ XMLwrt.Close();
+ return ms.ToArray();
}
- XMLwrt.Flush();
- XMLwrt.Close();
- str.Close();
- string requestContent = File.ReadAllText(requestXmlPath);
- return Encoding.UTF8.GetBytes(requestContent);
}
/// <summary>
/// Decode response then return the values
diff --git a/SharedVersion.cs b/SharedVersion.cs
index 2d2a44ae5..d996b5e97 100644
--- a/SharedVersion.cs
+++ b/SharedVersion.cs
@@ -1,4 +1,4 @@
using System.Reflection;
-[assembly: AssemblyVersion("3.0.*")]
-//[assembly: AssemblyVersion("3.0.5911")]
+[assembly: AssemblyVersion("3.1.*")]
+//[assembly: AssemblyVersion("3.0.5986")]