aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBond_009 <bond.009@outlook.com>2020-05-02 00:54:04 +0200
committerBond_009 <bond.009@outlook.com>2020-05-02 00:54:04 +0200
commit15634a1913e8c1ad4e921364f30055794644d0bd (patch)
treef0dfb3502728e4786de32c0cb4eaabd06f8e2d87
parent407f54e7764a6bfd8e4ccc0df897fac7e8c658b4 (diff)
parent62e251663fce8216cea529f85382299ac2f39fbc (diff)
Merge branch 'master' into websocket
-rw-r--r--.ci/azure-pipelines-compat.yml96
-rw-r--r--.ci/azure-pipelines-main.yml93
-rw-r--r--.ci/azure-pipelines-test.yml89
-rw-r--r--.ci/azure-pipelines.yml325
-rw-r--r--.ci/publish-nightly.yml46
-rw-r--r--.ci/publish-release.yml48
l---------[-rw-r--r--].copr/Makefile60
-rw-r--r--.github/CODEOWNERS3
-rw-r--r--.github/ISSUE_TEMPLATE/bug_report.md20
-rw-r--r--.github/ISSUE_TEMPLATE/feature_request.md13
-rw-r--r--.github/ISSUE_TEMPLATE/media_playback.md6
-rw-r--r--.gitignore20
-rw-r--r--.vscode/extensions.json14
-rw-r--r--CONTRIBUTORS.md148
-rw-r--r--Dockerfile44
-rw-r--r--Dockerfile.arm45
-rw-r--r--Dockerfile.arm6436
-rw-r--r--DvdLib/BigEndianBinaryReader.cs13
-rw-r--r--DvdLib/DvdLib.csproj11
-rw-r--r--DvdLib/Ifo/Dvd.cs16
-rw-r--r--Emby.Dlna/Api/DlnaServerService.cs28
-rw-r--r--Emby.Dlna/Api/DlnaService.cs5
-rw-r--r--Emby.Dlna/Common/Argument.cs1
-rw-r--r--Emby.Dlna/Common/DeviceIcon.cs10
-rw-r--r--Emby.Dlna/Common/DeviceService.cs6
-rw-r--r--Emby.Dlna/Common/ServiceAction.cs13
-rw-r--r--Emby.Dlna/Common/StateVariable.cs17
-rw-r--r--Emby.Dlna/Configuration/DlnaOptions.cs26
-rw-r--r--Emby.Dlna/ConfigurationExtension.cs3
-rw-r--r--Emby.Dlna/ConnectionManager/ConnectionManager.cs15
-rw-r--r--Emby.Dlna/ConnectionManager/ConnectionManagerXmlBuilder.cs2
-rw-r--r--Emby.Dlna/ConnectionManager/ControlHandler.cs30
-rw-r--r--Emby.Dlna/ConnectionManager/ServiceActionListBuilder.cs2
-rw-r--r--Emby.Dlna/ContentDirectory/ContentDirectory.cs11
-rw-r--r--Emby.Dlna/ContentDirectory/ContentDirectoryXmlBuilder.cs2
-rw-r--r--Emby.Dlna/ContentDirectory/ControlHandler.cs469
-rw-r--r--Emby.Dlna/ContentDirectory/ServiceActionListBuilder.cs2
-rw-r--r--Emby.Dlna/ControlRequest.cs2
-rw-r--r--Emby.Dlna/ControlResponse.cs12
-rw-r--r--Emby.Dlna/Didl/DidlBuilder.cs346
-rw-r--r--Emby.Dlna/Didl/Filter.cs5
-rw-r--r--Emby.Dlna/Didl/StringWriterWithEncoding.cs4
-rw-r--r--Emby.Dlna/DlnaManager.cs4
-rw-r--r--Emby.Dlna/Emby.Dlna.csproj18
-rw-r--r--Emby.Dlna/EventSubscriptionResponse.cs13
-rw-r--r--Emby.Dlna/Eventing/EventManager.cs5
-rw-r--r--Emby.Dlna/Eventing/EventSubscription.cs6
-rw-r--r--Emby.Dlna/IConnectionManager.cs1
-rw-r--r--Emby.Dlna/IContentDirectory.cs1
-rw-r--r--Emby.Dlna/IEventManager.cs1
-rw-r--r--Emby.Dlna/IMediaReceiverRegistrar.cs1
-rw-r--r--Emby.Dlna/IUpnpService.cs6
-rw-r--r--Emby.Dlna/Main/DlnaEntryPoint.cs25
-rw-r--r--Emby.Dlna/MediaReceiverRegistrar/ControlHandler.cs43
-rw-r--r--Emby.Dlna/MediaReceiverRegistrar/MediaReceiverRegistrar.cs14
-rw-r--r--Emby.Dlna/MediaReceiverRegistrar/MediaReceiverRegistrarXmlBuilder.cs2
-rw-r--r--Emby.Dlna/MediaReceiverRegistrar/ServiceActionListBuilder.cs2
-rw-r--r--Emby.Dlna/PlayTo/Device.cs69
-rw-r--r--Emby.Dlna/PlayTo/DeviceInfo.cs2
-rw-r--r--Emby.Dlna/PlayTo/PlayToController.cs152
-rw-r--r--Emby.Dlna/PlayTo/PlayToManager.cs14
-rw-r--r--Emby.Dlna/PlayTo/PlaybackProgressEventArgs.cs2
-rw-r--r--Emby.Dlna/PlayTo/PlaybackStartEventArgs.cs2
-rw-r--r--Emby.Dlna/PlayTo/PlaybackStoppedEventArgs.cs2
-rw-r--r--Emby.Dlna/PlayTo/PlaylistItem.cs2
-rw-r--r--Emby.Dlna/PlayTo/PlaylistItemFactory.cs3
-rw-r--r--Emby.Dlna/PlayTo/SsdpHttpClient.cs24
-rw-r--r--Emby.Dlna/PlayTo/TRANSPORTSTATE.cs2
-rw-r--r--Emby.Dlna/PlayTo/TransportCommands.cs2
-rw-r--r--Emby.Dlna/PlayTo/UpnpContainer.cs2
-rw-r--r--Emby.Dlna/PlayTo/uBaseObject.cs2
-rw-r--r--Emby.Dlna/PlayTo/uPnpNamespaces.cs2
-rw-r--r--Emby.Dlna/Profiles/DefaultProfile.cs4
-rw-r--r--Emby.Dlna/Profiles/DenonAvrProfile.cs2
-rw-r--r--Emby.Dlna/Profiles/DirectTvProfile.cs2
-rw-r--r--Emby.Dlna/Profiles/DishHopperJoeyProfile.cs2
-rw-r--r--Emby.Dlna/Profiles/Foobar2000Profile.cs2
-rw-r--r--Emby.Dlna/Profiles/LgTvProfile.cs2
-rw-r--r--Emby.Dlna/Profiles/LinksysDMA2100Profile.cs2
-rw-r--r--Emby.Dlna/Profiles/MarantzProfile.cs2
-rw-r--r--Emby.Dlna/Profiles/MediaMonkeyProfile.cs2
-rw-r--r--Emby.Dlna/Profiles/PanasonicVieraProfile.cs2
-rw-r--r--Emby.Dlna/Profiles/PopcornHourProfile.cs2
-rw-r--r--Emby.Dlna/Profiles/SamsungSmartTvProfile.cs2
-rw-r--r--Emby.Dlna/Profiles/SharpSmartTvProfile.cs2
-rw-r--r--Emby.Dlna/Profiles/SonyBlurayPlayer2013.cs2
-rw-r--r--Emby.Dlna/Profiles/SonyBlurayPlayer2014.cs2
-rw-r--r--Emby.Dlna/Profiles/SonyBlurayPlayer2015.cs2
-rw-r--r--Emby.Dlna/Profiles/SonyBlurayPlayer2016.cs2
-rw-r--r--Emby.Dlna/Profiles/SonyBlurayPlayerProfile.cs2
-rw-r--r--Emby.Dlna/Profiles/SonyBravia2010Profile.cs2
-rw-r--r--Emby.Dlna/Profiles/SonyBravia2011Profile.cs2
-rw-r--r--Emby.Dlna/Profiles/SonyBravia2012Profile.cs2
-rw-r--r--Emby.Dlna/Profiles/SonyBravia2013Profile.cs2
-rw-r--r--Emby.Dlna/Profiles/SonyBravia2014Profile.cs2
-rw-r--r--Emby.Dlna/Profiles/SonyPs3Profile.cs2
-rw-r--r--Emby.Dlna/Profiles/SonyPs4Profile.cs2
-rw-r--r--Emby.Dlna/Profiles/WdtvLiveProfile.cs2
-rw-r--r--Emby.Dlna/Profiles/XboxOneProfile.cs2
-rw-r--r--Emby.Dlna/Profiles/Xml/Default.xml2
-rw-r--r--Emby.Dlna/Profiles/Xml/Denon AVR.xml2
-rw-r--r--Emby.Dlna/Profiles/Xml/DirecTV HD-DVR.xml2
-rw-r--r--Emby.Dlna/Profiles/Xml/LG Smart TV.xml2
-rw-r--r--Emby.Dlna/Profiles/Xml/Linksys DMA2100.xml2
-rw-r--r--Emby.Dlna/Profiles/Xml/Marantz.xml2
-rw-r--r--Emby.Dlna/Profiles/Xml/MediaMonkey.xml2
-rw-r--r--Emby.Dlna/Profiles/Xml/Panasonic Viera.xml2
-rw-r--r--Emby.Dlna/Profiles/Xml/Popcorn Hour.xml2
-rw-r--r--Emby.Dlna/Profiles/Xml/Samsung Smart TV.xml2
-rw-r--r--Emby.Dlna/Profiles/Xml/Sharp Smart TV.xml2
-rw-r--r--Emby.Dlna/Profiles/Xml/Sony Bravia (2011).xml2
-rw-r--r--Emby.Dlna/Profiles/Xml/Sony Bravia (2012).xml2
-rw-r--r--Emby.Dlna/Profiles/Xml/Sony Bravia (2013).xml2
-rw-r--r--Emby.Dlna/Profiles/Xml/Sony Bravia (2014).xml2
-rw-r--r--Emby.Dlna/Profiles/Xml/Sony PlayStation 3.xml2
-rw-r--r--Emby.Dlna/Profiles/Xml/Sony PlayStation 4.xml2
-rw-r--r--Emby.Dlna/Profiles/Xml/WDTV Live.xml2
-rw-r--r--Emby.Dlna/Profiles/Xml/Xbox One.xml2
-rw-r--r--Emby.Dlna/Profiles/Xml/foobar2000.xml2
-rw-r--r--Emby.Dlna/Server/DescriptionXmlBuilder.cs3
-rw-r--r--Emby.Dlna/Service/BaseControlHandler.cs112
-rw-r--r--Emby.Dlna/Service/BaseService.cs4
-rw-r--r--Emby.Dlna/Service/ControlErrorHandler.cs6
-rw-r--r--Emby.Dlna/Service/ServiceXmlBuilder.cs2
-rw-r--r--Emby.Dlna/Ssdp/DeviceDiscovery.cs14
-rw-r--r--Emby.Dlna/Ssdp/Extensions.cs2
-rw-r--r--Emby.Drawing/Emby.Drawing.csproj18
-rw-r--r--Emby.Drawing/ImageProcessor.cs395
-rw-r--r--Emby.Naming/Audio/AlbumParser.cs29
-rw-r--r--Emby.Naming/Audio/AudioFileParser.cs17
-rw-r--r--Emby.Naming/Audio/MultiPartResult.cs26
-rw-r--r--Emby.Naming/AudioBook/AudioBookFileInfo.cs4
-rw-r--r--Emby.Naming/AudioBook/AudioBookFilePathParser.cs1
-rw-r--r--Emby.Naming/AudioBook/AudioBookFilePathParserResult.cs1
-rw-r--r--Emby.Naming/AudioBook/AudioBookListResolver.cs32
-rw-r--r--Emby.Naming/AudioBook/AudioBookResolver.cs1
-rw-r--r--Emby.Naming/Common/EpisodeExpression.cs34
-rw-r--r--Emby.Naming/Common/MediaType.cs1
-rw-r--r--Emby.Naming/Common/NamingOptions.cs152
-rw-r--r--Emby.Naming/Emby.Naming.csproj10
-rw-r--r--Emby.Naming/Subtitles/SubtitleInfo.cs1
-rw-r--r--Emby.Naming/Subtitles/SubtitleParser.cs19
-rw-r--r--Emby.Naming/TV/EpisodeInfo.cs1
-rw-r--r--Emby.Naming/TV/EpisodePathParser.cs18
-rw-r--r--Emby.Naming/TV/EpisodePathParserResult.cs1
-rw-r--r--Emby.Naming/TV/EpisodeResolver.cs21
-rw-r--r--Emby.Naming/TV/SeasonPathParser.cs80
-rw-r--r--Emby.Naming/TV/SeasonPathParserResult.cs3
-rw-r--r--Emby.Naming/Video/CleanDateTimeParser.cs72
-rw-r--r--Emby.Naming/Video/CleanDateTimeResult.cs30
-rw-r--r--Emby.Naming/Video/CleanStringParser.cs42
-rw-r--r--Emby.Naming/Video/CleanStringResult.cs20
-rw-r--r--Emby.Naming/Video/ExtraResolver.cs12
-rw-r--r--Emby.Naming/Video/ExtraResult.cs1
-rw-r--r--Emby.Naming/Video/ExtraRule.cs14
-rw-r--r--Emby.Naming/Video/ExtraRuleType.cs14
-rw-r--r--Emby.Naming/Video/FileStack.cs1
-rw-r--r--Emby.Naming/Video/FlagParser.cs1
-rw-r--r--Emby.Naming/Video/Format3DParser.cs1
-rw-r--r--Emby.Naming/Video/Format3DResult.cs1
-rw-r--r--Emby.Naming/Video/Format3DRule.cs1
-rw-r--r--Emby.Naming/Video/StackResolver.cs51
-rw-r--r--Emby.Naming/Video/StackResult.cs17
-rw-r--r--Emby.Naming/Video/StubResolver.cs21
-rw-r--r--Emby.Naming/Video/StubResult.cs1
-rw-r--r--Emby.Naming/Video/StubTypeRule.cs1
-rw-r--r--Emby.Naming/Video/VideoFileInfo.cs6
-rw-r--r--Emby.Naming/Video/VideoInfo.cs18
-rw-r--r--Emby.Naming/Video/VideoListResolver.cs64
-rw-r--r--Emby.Naming/Video/VideoResolver.cs33
-rw-r--r--Emby.Notifications/Api/NotificationsService.cs51
-rw-r--r--Emby.Notifications/CoreNotificationTypes.cs3
-rw-r--r--Emby.Notifications/Emby.Notifications.csproj19
-rw-r--r--Emby.Notifications/NotificationConfigurationFactory.cs4
-rw-r--r--Emby.Notifications/NotificationEntryPoint.cs (renamed from Emby.Notifications/Notifications.cs)122
-rw-r--r--Emby.Notifications/NotificationManager.cs39
-rw-r--r--Emby.Photos/Emby.Photos.csproj6
-rw-r--r--Emby.Photos/PhotoProvider.cs2
-rw-r--r--Emby.Server.Implementations/Activity/ActivityLogEntryPoint.cs102
-rw-r--r--Emby.Server.Implementations/Activity/ActivityManager.cs27
-rw-r--r--Emby.Server.Implementations/Activity/ActivityRepository.cs196
-rw-r--r--Emby.Server.Implementations/AppBase/BaseApplicationPaths.cs12
-rw-r--r--Emby.Server.Implementations/AppBase/ConfigurationHelper.cs26
-rw-r--r--Emby.Server.Implementations/ApplicationHost.cs742
-rw-r--r--Emby.Server.Implementations/Archiving/ZipClient.cs130
-rw-r--r--Emby.Server.Implementations/Branding/BrandingConfigurationFactory.cs7
-rw-r--r--Emby.Server.Implementations/Browser/BrowserLauncher.cs35
-rw-r--r--Emby.Server.Implementations/Channels/ChannelDynamicMediaSourceProvider.cs16
-rw-r--r--Emby.Server.Implementations/Channels/ChannelImageProvider.cs19
-rw-r--r--Emby.Server.Implementations/Channels/ChannelManager.cs332
-rw-r--r--Emby.Server.Implementations/Channels/ChannelPostScanTask.cs54
-rw-r--r--Emby.Server.Implementations/Channels/RefreshChannelsScheduledTask.cs57
-rw-r--r--Emby.Server.Implementations/Collections/CollectionImageProvider.cs26
-rw-r--r--Emby.Server.Implementations/Collections/CollectionManager.cs63
-rw-r--r--Emby.Server.Implementations/Configuration/ServerConfigurationManager.cs20
-rw-r--r--Emby.Server.Implementations/ConfigurationOptions.cs19
-rw-r--r--Emby.Server.Implementations/Cryptography/CryptographyProvider.cs41
-rw-r--r--Emby.Server.Implementations/Data/BaseSqliteRepository.cs1
-rw-r--r--Emby.Server.Implementations/Data/CleanDatabaseScheduledTask.cs3
-rw-r--r--Emby.Server.Implementations/Data/ManagedConnection.cs1
-rw-r--r--Emby.Server.Implementations/Data/SqliteDisplayPreferencesRepository.cs1
-rw-r--r--Emby.Server.Implementations/Data/SqliteExtensions.cs30
-rw-r--r--Emby.Server.Implementations/Data/SqliteItemRepository.cs548
-rw-r--r--Emby.Server.Implementations/Data/SqliteUserDataRepository.cs1
-rw-r--r--Emby.Server.Implementations/Data/SqliteUserRepository.cs3
-rw-r--r--Emby.Server.Implementations/Devices/DeviceId.cs1
-rw-r--r--Emby.Server.Implementations/Devices/DeviceManager.cs29
-rw-r--r--Emby.Server.Implementations/Diagnostics/CommonProcess.cs153
-rw-r--r--Emby.Server.Implementations/Diagnostics/ProcessFactory.cs15
-rw-r--r--Emby.Server.Implementations/Dto/DtoService.cs97
-rw-r--r--Emby.Server.Implementations/Emby.Server.Implementations.csproj22
-rw-r--r--Emby.Server.Implementations/EntryPoints/AutomaticRestartEntryPoint.cs128
-rw-r--r--Emby.Server.Implementations/EntryPoints/ExternalPortForwarding.cs94
-rw-r--r--Emby.Server.Implementations/EntryPoints/LibraryChangedNotifier.cs9
-rw-r--r--Emby.Server.Implementations/EntryPoints/RecordingNotifier.cs41
-rw-r--r--Emby.Server.Implementations/EntryPoints/RefreshUsersMetadata.cs9
-rw-r--r--Emby.Server.Implementations/EntryPoints/StartupWizard.cs63
-rw-r--r--Emby.Server.Implementations/EntryPoints/UdpServerEntryPoint.cs63
-rw-r--r--Emby.Server.Implementations/EntryPoints/UserDataChangeNotifier.cs45
-rw-r--r--Emby.Server.Implementations/HttpClientManager/HttpClientManager.cs30
-rw-r--r--Emby.Server.Implementations/HttpServer/FileWriter.cs17
-rw-r--r--Emby.Server.Implementations/HttpServer/HttpListenerHost.cs180
-rw-r--r--Emby.Server.Implementations/HttpServer/HttpResultFactory.cs23
-rw-r--r--Emby.Server.Implementations/HttpServer/IHttpListener.cs39
-rw-r--r--Emby.Server.Implementations/HttpServer/RangeRequestWriter.cs1
-rw-r--r--Emby.Server.Implementations/HttpServer/ResponseFilter.cs4
-rw-r--r--Emby.Server.Implementations/HttpServer/Security/AuthService.cs38
-rw-r--r--Emby.Server.Implementations/HttpServer/Security/AuthorizationContext.cs1
-rw-r--r--Emby.Server.Implementations/HttpServer/Security/SessionContext.cs1
-rw-r--r--Emby.Server.Implementations/IO/ExtendedFileSystemInfo.cs3
-rw-r--r--Emby.Server.Implementations/IO/FileRefresher.cs41
-rw-r--r--Emby.Server.Implementations/IO/LibraryMonitor.cs59
-rw-r--r--Emby.Server.Implementations/IO/ManagedFileSystem.cs131
-rw-r--r--Emby.Server.Implementations/IO/MbLinkShortcutHandler.cs1
-rw-r--r--Emby.Server.Implementations/IO/StreamHelper.cs1
-rw-r--r--Emby.Server.Implementations/IStartupOptions.cs17
-rw-r--r--Emby.Server.Implementations/Images/BaseDynamicImageProvider.cs1
-rw-r--r--Emby.Server.Implementations/Library/DefaultAuthenticationProvider.cs2
-rw-r--r--Emby.Server.Implementations/Library/ExclusiveLiveStream.cs1
-rw-r--r--Emby.Server.Implementations/Library/LibraryManager.cs370
-rw-r--r--Emby.Server.Implementations/Library/LiveStreamHelper.cs1
-rw-r--r--Emby.Server.Implementations/Library/MediaSourceManager.cs23
-rw-r--r--Emby.Server.Implementations/Library/MediaStreamSelector.cs7
-rw-r--r--Emby.Server.Implementations/Library/MusicManager.cs1
-rw-r--r--Emby.Server.Implementations/Library/PathExtensions.cs24
-rw-r--r--Emby.Server.Implementations/Library/ResolverHelper.cs4
-rw-r--r--Emby.Server.Implementations/Library/Resolvers/Audio/AudioResolver.cs7
-rw-r--r--Emby.Server.Implementations/Library/Resolvers/Audio/MusicAlbumResolver.cs32
-rw-r--r--Emby.Server.Implementations/Library/Resolvers/Audio/MusicArtistResolver.cs13
-rw-r--r--Emby.Server.Implementations/Library/Resolvers/BaseVideoResolver.cs4
-rw-r--r--Emby.Server.Implementations/Library/Resolvers/Books/BookResolver.cs1
-rw-r--r--Emby.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs87
-rw-r--r--Emby.Server.Implementations/Library/Resolvers/PhotoAlbumResolver.cs3
-rw-r--r--Emby.Server.Implementations/Library/Resolvers/PhotoResolver.cs13
-rw-r--r--Emby.Server.Implementations/Library/Resolvers/PlaylistResolver.cs61
-rw-r--r--Emby.Server.Implementations/Library/Resolvers/SpecialFolderResolver.cs1
-rw-r--r--Emby.Server.Implementations/Library/Resolvers/TV/SeasonResolver.cs27
-rw-r--r--Emby.Server.Implementations/Library/Resolvers/TV/SeriesResolver.cs24
-rw-r--r--Emby.Server.Implementations/Library/Resolvers/VideoResolver.cs1
-rw-r--r--Emby.Server.Implementations/Library/SearchEngine.cs8
-rw-r--r--Emby.Server.Implementations/Library/UserDataManager.cs38
-rw-r--r--Emby.Server.Implementations/Library/UserManager.cs72
-rw-r--r--Emby.Server.Implementations/Library/UserViewManager.cs1
-rw-r--r--Emby.Server.Implementations/Library/Validators/ArtistsPostScanTask.cs5
-rw-r--r--Emby.Server.Implementations/Library/Validators/GenresPostScanTask.cs5
-rw-r--r--Emby.Server.Implementations/Library/Validators/MusicGenresPostScanTask.cs5
-rw-r--r--Emby.Server.Implementations/Library/Validators/StudiosPostScanTask.cs5
-rw-r--r--Emby.Server.Implementations/LiveTv/EmbyTV/DirectRecorder.cs12
-rw-r--r--Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs48
-rw-r--r--Emby.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs81
-rw-r--r--Emby.Server.Implementations/LiveTv/EmbyTV/EntryPoint.cs4
-rw-r--r--Emby.Server.Implementations/LiveTv/EmbyTV/EpgChannelData.cs1
-rw-r--r--Emby.Server.Implementations/LiveTv/EmbyTV/IRecorder.cs2
-rw-r--r--Emby.Server.Implementations/LiveTv/EmbyTV/ItemDataProvider.cs2
-rw-r--r--Emby.Server.Implementations/LiveTv/EmbyTV/RecordingHelper.cs17
-rw-r--r--Emby.Server.Implementations/LiveTv/EmbyTV/SeriesTimerManager.cs3
-rw-r--r--Emby.Server.Implementations/LiveTv/EmbyTV/TimerManager.cs2
-rw-r--r--Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs12
-rw-r--r--Emby.Server.Implementations/LiveTv/Listings/XmlTvListingsProvider.cs10
-rw-r--r--Emby.Server.Implementations/LiveTv/LiveTvConfigurationFactory.cs2
-rw-r--r--Emby.Server.Implementations/LiveTv/LiveTvDtoService.cs28
-rw-r--r--Emby.Server.Implementations/LiveTv/LiveTvManager.cs48
-rw-r--r--Emby.Server.Implementations/LiveTv/LiveTvMediaSourceProvider.cs47
-rw-r--r--Emby.Server.Implementations/LiveTv/TunerHosts/BaseTunerHost.cs2
-rw-r--r--Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs4
-rw-r--r--Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunManager.cs2
-rw-r--r--Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunUdpStream.cs6
-rw-r--r--Emby.Server.Implementations/LiveTv/TunerHosts/LiveStream.cs8
-rw-r--r--Emby.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs2
-rw-r--r--Emby.Server.Implementations/LiveTv/TunerHosts/M3uParser.cs3
-rw-r--r--Emby.Server.Implementations/LiveTv/TunerHosts/SharedHttpStream.cs10
-rw-r--r--Emby.Server.Implementations/Localization/Core/af.json1
-rw-r--r--Emby.Server.Implementations/Localization/Core/ar.json61
-rw-r--r--Emby.Server.Implementations/Localization/Core/bg-BG.json59
-rw-r--r--Emby.Server.Implementations/Localization/Core/bn.json95
-rw-r--r--Emby.Server.Implementations/Localization/Core/ca.json9
-rw-r--r--Emby.Server.Implementations/Localization/Core/cs.json27
-rw-r--r--Emby.Server.Implementations/Localization/Core/da.json33
-rw-r--r--Emby.Server.Implementations/Localization/Core/de.json35
-rw-r--r--Emby.Server.Implementations/Localization/Core/el.json27
-rw-r--r--Emby.Server.Implementations/Localization/Core/en-GB.json25
-rw-r--r--Emby.Server.Implementations/Localization/Core/en-US.json25
-rw-r--r--Emby.Server.Implementations/Localization/Core/es-AR.json67
-rw-r--r--Emby.Server.Implementations/Localization/Core/es-MX.json25
-rw-r--r--Emby.Server.Implementations/Localization/Core/es.json25
-rw-r--r--Emby.Server.Implementations/Localization/Core/es_DO.json21
-rw-r--r--Emby.Server.Implementations/Localization/Core/fa.json109
-rw-r--r--Emby.Server.Implementations/Localization/Core/fi.json105
-rw-r--r--Emby.Server.Implementations/Localization/Core/fil.json102
-rw-r--r--Emby.Server.Implementations/Localization/Core/fr-CA.json5
-rw-r--r--Emby.Server.Implementations/Localization/Core/fr.json35
-rw-r--r--Emby.Server.Implementations/Localization/Core/gl.json3
-rw-r--r--Emby.Server.Implementations/Localization/Core/gsw.json1
-rw-r--r--Emby.Server.Implementations/Localization/Core/he.json74
-rw-r--r--Emby.Server.Implementations/Localization/Core/hr.json1
-rw-r--r--Emby.Server.Implementations/Localization/Core/hu.json45
-rw-r--r--Emby.Server.Implementations/Localization/Core/id.json67
-rw-r--r--Emby.Server.Implementations/Localization/Core/is.json35
-rw-r--r--Emby.Server.Implementations/Localization/Core/it.json33
-rw-r--r--Emby.Server.Implementations/Localization/Core/ja.json25
-rw-r--r--Emby.Server.Implementations/Localization/Core/kk.json1
-rw-r--r--Emby.Server.Implementations/Localization/Core/ko.json25
-rw-r--r--Emby.Server.Implementations/Localization/Core/lt-LT.json1
-rw-r--r--Emby.Server.Implementations/Localization/Core/lv.json117
-rw-r--r--Emby.Server.Implementations/Localization/Core/mk.json95
-rw-r--r--Emby.Server.Implementations/Localization/Core/mr.json61
-rw-r--r--Emby.Server.Implementations/Localization/Core/ms.json11
-rw-r--r--Emby.Server.Implementations/Localization/Core/nb.json10
-rw-r--r--Emby.Server.Implementations/Localization/Core/nl.json31
-rw-r--r--Emby.Server.Implementations/Localization/Core/nn.json60
-rw-r--r--Emby.Server.Implementations/Localization/Core/pl.json25
-rw-r--r--Emby.Server.Implementations/Localization/Core/pt-BR.json25
-rw-r--r--Emby.Server.Implementations/Localization/Core/pt-PT.json27
-rw-r--r--Emby.Server.Implementations/Localization/Core/pt.json65
-rw-r--r--Emby.Server.Implementations/Localization/Core/ro.json29
-rw-r--r--Emby.Server.Implementations/Localization/Core/ru.json67
-rw-r--r--Emby.Server.Implementations/Localization/Core/sk.json61
-rw-r--r--Emby.Server.Implementations/Localization/Core/sl-SI.json5
-rw-r--r--Emby.Server.Implementations/Localization/Core/sr.json118
-rw-r--r--Emby.Server.Implementations/Localization/Core/sv.json61
-rw-r--r--Emby.Server.Implementations/Localization/Core/tr.json31
-rw-r--r--Emby.Server.Implementations/Localization/Core/ur_PK.json117
-rw-r--r--Emby.Server.Implementations/Localization/Core/zh-CN.json41
-rw-r--r--Emby.Server.Implementations/Localization/Core/zh-HK.json173
-rw-r--r--Emby.Server.Implementations/Localization/Core/zh-TW.json37
-rw-r--r--Emby.Server.Implementations/Localization/LocalizationManager.cs3
-rw-r--r--Emby.Server.Implementations/MediaEncoder/EncodingManager.cs40
-rw-r--r--Emby.Server.Implementations/Net/SocketFactory.cs30
-rw-r--r--Emby.Server.Implementations/Net/UdpSocket.cs9
-rw-r--r--Emby.Server.Implementations/Net/WebSocketConnectEventArgs.cs29
-rw-r--r--Emby.Server.Implementations/Networking/NetworkManager.cs2
-rw-r--r--Emby.Server.Implementations/Playlists/PlaylistImageProvider.cs1
-rw-r--r--Emby.Server.Implementations/Playlists/PlaylistManager.cs61
-rw-r--r--Emby.Server.Implementations/ScheduledTasks/TaskManager.cs20
-rw-r--r--Emby.Server.Implementations/ScheduledTasks/Tasks/ChapterImagesTask.cs21
-rw-r--r--Emby.Server.Implementations/ScheduledTasks/Tasks/DeleteCacheFileTask.cs15
-rw-r--r--Emby.Server.Implementations/ScheduledTasks/Tasks/DeleteLogFileTask.cs19
-rw-r--r--Emby.Server.Implementations/ScheduledTasks/Tasks/DeleteTranscodeFileTask.cs15
-rw-r--r--Emby.Server.Implementations/ScheduledTasks/Tasks/PeopleValidationTask.cs20
-rw-r--r--Emby.Server.Implementations/ScheduledTasks/Tasks/PluginUpdateTask.cs16
-rw-r--r--Emby.Server.Implementations/ScheduledTasks/Tasks/RefreshMediaLibraryTask.cs14
-rw-r--r--Emby.Server.Implementations/Security/AuthenticationRepository.cs4
-rw-r--r--Emby.Server.Implementations/Serialization/JsonSerializer.cs32
-rw-r--r--Emby.Server.Implementations/ServerApplicationPaths.cs12
-rw-r--r--Emby.Server.Implementations/Services/ServiceController.cs20
-rw-r--r--Emby.Server.Implementations/Services/StringMapTypeDeserializer.cs16
-rw-r--r--Emby.Server.Implementations/Services/SwaggerService.cs4
-rw-r--r--Emby.Server.Implementations/Services/UrlExtensions.cs20
-rw-r--r--Emby.Server.Implementations/Session/SessionManager.cs300
-rw-r--r--Emby.Server.Implementations/SocketSharp/HttpPostedFile.cs6
-rw-r--r--Emby.Server.Implementations/SocketSharp/WebSocketSharpListener.cs135
-rw-r--r--Emby.Server.Implementations/SocketSharp/WebSocketSharpRequest.cs16
-rw-r--r--Emby.Server.Implementations/Sorting/AlphanumComparator.cs99
-rw-r--r--Emby.Server.Implementations/Udp/UdpServer.cs262
-rw-r--r--Emby.Server.Implementations/Updates/InstallationManager.cs141
-rw-r--r--Emby.Server.Implementations/WebSockets/WebSocketManager.cs102
-rw-r--r--Jellyfin.Api/BaseJellyfinApiController.cs2
-rw-r--r--Jellyfin.Api/Jellyfin.Api.csproj9
-rw-r--r--Jellyfin.Data/DbContexts/Jellyfin.cs1140
-rw-r--r--Jellyfin.Data/Entities/Artwork.cs208
-rw-r--r--Jellyfin.Data/Entities/Book.cs84
-rw-r--r--Jellyfin.Data/Entities/BookMetadata.cs123
-rw-r--r--Jellyfin.Data/Entities/Chapter.cs274
-rw-r--r--Jellyfin.Data/Entities/Collection.cs131
-rw-r--r--Jellyfin.Data/Entities/CollectionItem.cs151
-rw-r--r--Jellyfin.Data/Entities/Company.cs147
-rw-r--r--Jellyfin.Data/Entities/CompanyMetadata.cs234
-rw-r--r--Jellyfin.Data/Entities/CustomItem.cs84
-rw-r--r--Jellyfin.Data/Entities/CustomItemMetadata.cs86
-rw-r--r--Jellyfin.Data/Entities/Episode.cs127
-rw-r--r--Jellyfin.Data/Entities/EpisodeMetadata.cs197
-rw-r--r--Jellyfin.Data/Entities/Genre.cs163
-rw-r--r--Jellyfin.Data/Entities/Group.cs115
-rw-r--r--Jellyfin.Data/Entities/Library.cs158
-rw-r--r--Jellyfin.Data/Entities/LibraryItem.cs180
-rw-r--r--Jellyfin.Data/Entities/LibraryRoot.cs202
-rw-r--r--Jellyfin.Data/Entities/MediaFile.cs209
-rw-r--r--Jellyfin.Data/Entities/MediaFileStream.cs160
-rw-r--r--Jellyfin.Data/Entities/Metadata.cs385
-rw-r--r--Jellyfin.Data/Entities/MetadataProvider.cs158
-rw-r--r--Jellyfin.Data/Entities/MetadataProviderId.cs189
-rw-r--r--Jellyfin.Data/Entities/Movie.cs84
-rw-r--r--Jellyfin.Data/Entities/MovieMetadata.cs239
-rw-r--r--Jellyfin.Data/Entities/MusicAlbum.cs84
-rw-r--r--Jellyfin.Data/Entities/MusicAlbumMetadata.cs202
-rw-r--r--Jellyfin.Data/Entities/Permission.cs152
-rw-r--r--Jellyfin.Data/Entities/PermissionKind.cs40
-rw-r--r--Jellyfin.Data/Entities/Person.cs312
-rw-r--r--Jellyfin.Data/Entities/PersonRole.cs215
-rw-r--r--Jellyfin.Data/Entities/Photo.cs84
-rw-r--r--Jellyfin.Data/Entities/PhotoMetadata.cs86
-rw-r--r--Jellyfin.Data/Entities/Preference.cs117
-rw-r--r--Jellyfin.Data/Entities/PreferenceKind.cs27
-rw-r--r--Jellyfin.Data/Entities/ProviderMapping.cs133
-rw-r--r--Jellyfin.Data/Entities/Rating.cs197
-rw-r--r--Jellyfin.Data/Entities/RatingSource.cs242
-rw-r--r--Jellyfin.Data/Entities/Release.cs197
-rw-r--r--Jellyfin.Data/Entities/Season.cs127
-rw-r--r--Jellyfin.Data/Entities/SeasonMetadata.cs123
-rw-r--r--Jellyfin.Data/Entities/Series.cs183
-rw-r--r--Jellyfin.Data/Entities/SeriesMetadata.cs239
-rw-r--r--Jellyfin.Data/Entities/Track.cs127
-rw-r--r--Jellyfin.Data/Entities/TrackMetadata.cs86
-rw-r--r--Jellyfin.Data/Entities/User.cs242
-rw-r--r--Jellyfin.Data/Enums/ArtKind.cs25
-rw-r--r--Jellyfin.Data/Enums/MediaFileKind.cs25
-rw-r--r--Jellyfin.Data/Enums/PersonRoleType.cs32
-rw-r--r--Jellyfin.Data/Enums/Weekday.cs27
-rw-r--r--Jellyfin.Data/Jellyfin.Data.csproj12
-rw-r--r--Jellyfin.Data/Structs/.gitkeep0
-rw-r--r--Jellyfin.Drawing.Skia/Jellyfin.Drawing.Skia.csproj14
-rw-r--r--Jellyfin.Drawing.Skia/PlayedIndicatorDrawer.cs13
-rw-r--r--Jellyfin.Drawing.Skia/SkiaCodecException.cs1
-rw-r--r--Jellyfin.Drawing.Skia/SkiaEncoder.cs72
-rw-r--r--Jellyfin.Drawing.Skia/StripCollageBuilder.cs10
-rw-r--r--Jellyfin.Drawing.Skia/UnplayedCountIndicator.cs4
-rw-r--r--Jellyfin.Server/CoreAppHost.cs35
-rw-r--r--Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs5
-rw-r--r--Jellyfin.Server/Jellyfin.Server.csproj19
-rw-r--r--Jellyfin.Server/Migrations/IMigrationRoutine.cs28
-rw-r--r--Jellyfin.Server/Migrations/MigrationOptions.cs24
-rw-r--r--Jellyfin.Server/Migrations/MigrationRunner.cs73
-rw-r--r--Jellyfin.Server/Migrations/MigrationsFactory.cs20
-rw-r--r--Jellyfin.Server/Migrations/MigrationsListStore.cs24
-rw-r--r--Jellyfin.Server/Migrations/Routines/CreateUserLoggingConfigFile.cs73
-rw-r--r--Jellyfin.Server/Migrations/Routines/DisableTranscodingThrottling.cs33
-rw-r--r--Jellyfin.Server/Program.cs217
-rw-r--r--Jellyfin.Server/Properties/launchSettings.json17
-rw-r--r--Jellyfin.Server/Resources/Configuration/logging.json15
-rw-r--r--Jellyfin.Server/Startup.cs11
-rw-r--r--Jellyfin.Server/StartupOptions.cs34
-rw-r--r--MediaBrowser.Api/ApiEntryPoint.cs32
-rw-r--r--MediaBrowser.Api/BaseApiService.cs60
-rw-r--r--MediaBrowser.Api/ChannelService.cs24
-rw-r--r--MediaBrowser.Api/Devices/DeviceService.cs16
-rw-r--r--MediaBrowser.Api/EnvironmentService.cs10
-rw-r--r--MediaBrowser.Api/FilterService.cs4
-rw-r--r--MediaBrowser.Api/Images/ImageService.cs63
-rw-r--r--MediaBrowser.Api/Images/RemoteImageService.cs28
-rw-r--r--MediaBrowser.Api/ItemLookupService.cs11
-rw-r--r--MediaBrowser.Api/ItemUpdateService.cs29
-rw-r--r--MediaBrowser.Api/Library/LibraryService.cs201
-rw-r--r--MediaBrowser.Api/Library/LibraryStructureService.cs12
-rw-r--r--MediaBrowser.Api/LiveTv/LiveTvService.cs12
-rw-r--r--MediaBrowser.Api/MediaBrowser.Api.csproj5
-rw-r--r--MediaBrowser.Api/Movies/MoviesService.cs2
-rw-r--r--MediaBrowser.Api/PackageService.cs44
-rw-r--r--MediaBrowser.Api/Playback/BaseStreamingService.cs431
-rw-r--r--MediaBrowser.Api/Playback/Hls/BaseHlsService.cs75
-rw-r--r--MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs265
-rw-r--r--MediaBrowser.Api/Playback/Hls/HlsCodecStringFactory.cs126
-rw-r--r--MediaBrowser.Api/Playback/Hls/HlsSegmentService.cs4
-rw-r--r--MediaBrowser.Api/Playback/MediaInfoService.cs164
-rw-r--r--MediaBrowser.Api/Playback/Progressive/BaseProgressiveStreamingService.cs2
-rw-r--r--MediaBrowser.Api/Playback/Progressive/ProgressiveStreamWriter.cs12
-rw-r--r--MediaBrowser.Api/Playback/StreamRequest.cs1
-rw-r--r--MediaBrowser.Api/Playback/StreamState.cs2
-rw-r--r--MediaBrowser.Api/Playback/UniversalAudioService.cs4
-rw-r--r--MediaBrowser.Api/PluginService.cs4
-rw-r--r--MediaBrowser.Api/ScheduledTasks/ScheduledTaskService.cs8
-rw-r--r--MediaBrowser.Api/ScheduledTasks/ScheduledTasksWebSocketListener.cs2
-rw-r--r--MediaBrowser.Api/SearchService.cs98
-rw-r--r--MediaBrowser.Api/Sessions/ApiKeyService.cs85
-rw-r--r--MediaBrowser.Api/Sessions/SessionInfoWebSocketListener.cs (renamed from MediaBrowser.Api/Session/SessionInfoWebSocketListener.cs)4
-rw-r--r--MediaBrowser.Api/Sessions/SessionService.cs (renamed from MediaBrowser.Api/Session/SessionsService.cs)104
-rw-r--r--MediaBrowser.Api/Subtitles/SubtitleService.cs15
-rw-r--r--MediaBrowser.Api/System/ActivityLogWebSocketListener.cs2
-rw-r--r--MediaBrowser.Api/System/SystemService.cs7
-rw-r--r--MediaBrowser.Api/TranscodingJob.cs5
-rw-r--r--MediaBrowser.Api/TvShowsService.cs15
-rw-r--r--MediaBrowser.Api/UserLibrary/ArtistsService.cs7
-rw-r--r--MediaBrowser.Api/UserLibrary/BaseItemsByNameService.cs34
-rw-r--r--MediaBrowser.Api/UserLibrary/BaseItemsRequest.cs33
-rw-r--r--MediaBrowser.Api/UserLibrary/GenresService.cs1
-rw-r--r--MediaBrowser.Api/UserLibrary/ItemsService.cs17
-rw-r--r--MediaBrowser.Api/UserLibrary/PersonsService.cs1
-rw-r--r--MediaBrowser.Api/UserLibrary/PlaystateService.cs4
-rw-r--r--MediaBrowser.Api/UserLibrary/UserLibraryService.cs3
-rw-r--r--MediaBrowser.Api/UserLibrary/YearsService.cs1
-rw-r--r--MediaBrowser.Api/UserService.cs8
-rw-r--r--MediaBrowser.Api/VideosService.cs13
-rw-r--r--MediaBrowser.Common/Configuration/ConfigurationUpdateEventArgs.cs1
-rw-r--r--MediaBrowser.Common/Configuration/EncodingConfigurationExtensions.cs14
-rw-r--r--MediaBrowser.Common/Configuration/IApplicationPaths.cs9
-rw-r--r--MediaBrowser.Common/Configuration/IConfigurationFactory.cs28
-rw-r--r--MediaBrowser.Common/Configuration/IConfigurationManager.cs1
-rw-r--r--MediaBrowser.Common/Cryptography/CryptoExtensions.cs (renamed from MediaBrowser.Common/Cryptography/Extensions.cs)2
-rw-r--r--MediaBrowser.Common/Cryptography/PasswordHash.cs1
-rw-r--r--MediaBrowser.Common/Extensions/BaseExtensions.cs2
-rw-r--r--MediaBrowser.Common/Extensions/CopyToExtensions.cs2
-rw-r--r--MediaBrowser.Common/Extensions/MethodNotAllowedException.cs2
-rw-r--r--MediaBrowser.Common/Extensions/ProcessExtensions.cs82
-rw-r--r--MediaBrowser.Common/Extensions/RateLimitExceededException.cs2
-rw-r--r--MediaBrowser.Common/Extensions/ResourceNotFoundException.cs2
-rw-r--r--MediaBrowser.Common/Extensions/ShuffleExtensions.cs15
-rw-r--r--MediaBrowser.Common/Extensions/StringExtensions.cs37
-rw-r--r--MediaBrowser.Common/IApplicationHost.cs12
-rw-r--r--MediaBrowser.Common/MediaBrowser.Common.csproj10
-rw-r--r--MediaBrowser.Common/Net/CustomHeaderNames.cs1
-rw-r--r--MediaBrowser.Common/Net/HttpRequestOptions.cs9
-rw-r--r--MediaBrowser.Common/Net/HttpResponseInfo.cs4
-rw-r--r--MediaBrowser.Common/Net/INetworkManager.cs1
-rw-r--r--MediaBrowser.Common/Plugins/BasePlugin.cs8
-rw-r--r--MediaBrowser.Common/Plugins/IPlugin.cs1
-rw-r--r--MediaBrowser.Common/Plugins/IPluginAssembly.cs1
-rw-r--r--MediaBrowser.Common/Progress/ActionableProgress.cs1
-rw-r--r--MediaBrowser.Common/Providers/SubtitleConfigurationFactory.cs1
-rw-r--r--MediaBrowser.Common/System/OperatingSystem.cs11
-rw-r--r--MediaBrowser.Common/Updates/IInstallationManager.cs23
-rw-r--r--MediaBrowser.Common/Updates/InstallationEventArgs.cs3
-rw-r--r--MediaBrowser.Common/Updates/InstallationFailedEventArgs.cs1
-rw-r--r--MediaBrowser.Controller/Authentication/AuthenticationException.cs18
-rw-r--r--MediaBrowser.Controller/Authentication/AuthenticationResult.cs1
-rw-r--r--MediaBrowser.Controller/Channels/IChannel.cs2
-rw-r--r--MediaBrowser.Controller/Chapters/IChapterManager.cs5
-rw-r--r--MediaBrowser.Controller/Drawing/IImageProcessor.cs44
-rw-r--r--MediaBrowser.Controller/Drawing/ImageHelper.cs2
-rw-r--r--MediaBrowser.Controller/Drawing/ImageProcessingOptions.cs3
-rw-r--r--MediaBrowser.Controller/Entities/Audio/MusicArtist.cs1
-rw-r--r--MediaBrowser.Controller/Entities/BaseItem.cs99
-rw-r--r--MediaBrowser.Controller/Entities/Book.cs4
-rw-r--r--MediaBrowser.Controller/Entities/Folder.cs58
-rw-r--r--MediaBrowser.Controller/Entities/IItemByName.cs2
-rw-r--r--MediaBrowser.Controller/Entities/InternalPeopleQuery.cs10
-rw-r--r--MediaBrowser.Controller/Entities/Person.cs54
-rw-r--r--MediaBrowser.Controller/Entities/PersonInfo.cs62
-rw-r--r--MediaBrowser.Controller/Entities/Photo.cs8
-rw-r--r--MediaBrowser.Controller/Entities/UserRootFolder.cs9
-rw-r--r--MediaBrowser.Controller/Entities/Video.cs1
-rw-r--r--MediaBrowser.Controller/Extensions/ConfigurationExtensions.cs40
-rw-r--r--MediaBrowser.Controller/IServerApplicationHost.cs15
-rw-r--r--MediaBrowser.Controller/IServerApplicationPaths.cs7
-rw-r--r--MediaBrowser.Controller/Library/ILibraryManager.cs6
-rw-r--r--MediaBrowser.Controller/Library/IMediaSourceProvider.cs2
-rw-r--r--MediaBrowser.Controller/Library/NameExtensions.cs2
-rw-r--r--MediaBrowser.Controller/Library/Profiler.cs2
-rw-r--r--MediaBrowser.Controller/MediaBrowser.Controller.csproj20
-rw-r--r--MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs413
-rw-r--r--MediaBrowser.Controller/MediaEncoding/EncodingJobInfo.cs16
-rw-r--r--MediaBrowser.Controller/MediaEncoding/EncodingJobOptions.cs1
-rw-r--r--MediaBrowser.Controller/MediaEncoding/IEncodingManager.cs4
-rw-r--r--MediaBrowser.Controller/MediaEncoding/JobLogger.cs12
-rw-r--r--MediaBrowser.Controller/Net/BasePeriodicWebSocketListener.cs22
-rw-r--r--MediaBrowser.Controller/Net/IAuthService.cs4
-rw-r--r--MediaBrowser.Controller/Net/IHttpResultFactory.cs4
-rw-r--r--MediaBrowser.Controller/Net/IHttpServer.cs2
-rw-r--r--MediaBrowser.Controller/Net/SecurityException.cs32
-rw-r--r--MediaBrowser.Controller/Net/StaticResultOptions.cs6
-rw-r--r--MediaBrowser.Controller/Persistence/IDisplayPreferencesRepository.cs22
-rw-r--r--MediaBrowser.Controller/Persistence/IItemRepository.cs6
-rw-r--r--MediaBrowser.Controller/Persistence/MediaAttachmentQuery.cs1
-rw-r--r--MediaBrowser.Controller/Playlists/IPlaylistManager.cs2
-rw-r--r--MediaBrowser.Controller/Plugins/IServerEntryPoint.cs10
-rw-r--r--MediaBrowser.Controller/Providers/AlbumInfo.cs1
-rw-r--r--MediaBrowser.Controller/Providers/BoxSetInfo.cs1
-rw-r--r--MediaBrowser.Controller/Providers/DirectoryService.cs11
-rw-r--r--MediaBrowser.Controller/Providers/DynamicImageInfo.cs10
-rw-r--r--MediaBrowser.Controller/Providers/DynamicImageResponse.cs4
-rw-r--r--MediaBrowser.Controller/Providers/EpisodeInfo.cs1
-rw-r--r--MediaBrowser.Controller/Providers/ExtraInfo.cs15
-rw-r--r--MediaBrowser.Controller/Providers/ExtraSource.cs9
-rw-r--r--MediaBrowser.Controller/Providers/ICustomMetadataProvider.cs2
-rw-r--r--MediaBrowser.Controller/Providers/IDirectoryService.cs7
-rw-r--r--MediaBrowser.Controller/Providers/IExtrasProvider.cs20
-rw-r--r--MediaBrowser.Controller/Providers/IForcedProvider.cs2
-rw-r--r--MediaBrowser.Controller/Providers/IImageEnhancer.cs61
-rw-r--r--MediaBrowser.Controller/Providers/IImageProvider.cs6
-rw-r--r--MediaBrowser.Controller/Providers/ILocalImageFileProvider.cs10
-rw-r--r--MediaBrowser.Controller/Providers/ILocalImageProvider.cs6
-rw-r--r--MediaBrowser.Controller/Providers/ILocalMetadataProvider.cs5
-rw-r--r--MediaBrowser.Controller/Providers/IMetadataService.cs3
-rw-r--r--MediaBrowser.Controller/Providers/IPreRefreshProvider.cs1
-rw-r--r--MediaBrowser.Controller/Providers/IProviderManager.cs6
-rw-r--r--MediaBrowser.Controller/Providers/IRemoteImageProvider.cs2
-rw-r--r--MediaBrowser.Controller/Providers/ItemInfo.cs5
-rw-r--r--MediaBrowser.Controller/Providers/ItemLookupInfo.cs8
-rw-r--r--MediaBrowser.Controller/Providers/LocalImageInfo.cs1
-rw-r--r--MediaBrowser.Controller/Providers/MetadataProviderPriority.cs39
-rw-r--r--MediaBrowser.Controller/Providers/MetadataRefreshOptions.cs2
-rw-r--r--MediaBrowser.Controller/Providers/MetadataResult.cs6
-rw-r--r--MediaBrowser.Controller/Providers/MovieInfo.cs1
-rw-r--r--MediaBrowser.Controller/Providers/PersonLookupInfo.cs1
-rw-r--r--MediaBrowser.Controller/Providers/RemoteSearchQuery.cs6
-rw-r--r--MediaBrowser.Controller/Session/SessionInfo.cs11
-rw-r--r--MediaBrowser.Controller/Sorting/AlphanumComparator.cs135
-rw-r--r--MediaBrowser.Controller/Sorting/SortExtensions.cs123
-rw-r--r--MediaBrowser.Controller/Sorting/SortHelper.cs25
-rw-r--r--MediaBrowser.LocalMetadata/Images/CollectionFolderImageProvider.cs2
-rw-r--r--MediaBrowser.LocalMetadata/Images/EpisodeLocalImageProvider.cs2
-rw-r--r--MediaBrowser.LocalMetadata/Images/InternalMetadataFolderImageProvider.cs2
-rw-r--r--MediaBrowser.LocalMetadata/Images/LocalImageProvider.cs4
-rw-r--r--MediaBrowser.LocalMetadata/MediaBrowser.LocalMetadata.csproj5
-rw-r--r--MediaBrowser.LocalMetadata/Parsers/BaseItemXmlParser.cs1
-rw-r--r--MediaBrowser.LocalMetadata/Providers/BoxSetXmlProvider.cs2
-rw-r--r--MediaBrowser.LocalMetadata/Providers/PlaylistXmlProvider.cs5
-rw-r--r--MediaBrowser.LocalMetadata/Savers/BaseXmlSaver.cs3
-rw-r--r--MediaBrowser.LocalMetadata/Savers/BoxSetXmlSaver.cs2
-rw-r--r--MediaBrowser.LocalMetadata/Savers/PlaylistXmlSaver.cs9
-rw-r--r--MediaBrowser.MediaEncoding/Attachments/AttachmentExtractor.cs67
-rw-r--r--MediaBrowser.MediaEncoding/BdInfo/BdInfoDirectoryInfo.cs38
-rw-r--r--MediaBrowser.MediaEncoding/BdInfo/BdInfoFileInfo.cs14
-rw-r--r--MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs5
-rw-r--r--MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs198
-rw-r--r--MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj9
-rw-r--r--MediaBrowser.MediaEncoding/Probing/MediaStreamInfo.cs2
-rw-r--r--MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs12
-rw-r--r--MediaBrowser.MediaEncoding/Subtitles/AssParser.cs1
-rw-r--r--MediaBrowser.MediaEncoding/Subtitles/SrtParser.cs1
-rw-r--r--MediaBrowser.MediaEncoding/Subtitles/SsaParser.cs1
-rw-r--r--MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs168
-rw-r--r--MediaBrowser.MediaEncoding/Subtitles/TtmlWriter.cs1
-rw-r--r--MediaBrowser.Model/Activity/ActivityLogEntry.cs2
-rw-r--r--MediaBrowser.Model/Activity/IActivityManager.cs2
-rw-r--r--MediaBrowser.Model/Activity/IActivityRepository.cs2
-rw-r--r--MediaBrowser.Model/ApiClient/ServerDiscoveryInfo.cs5
-rw-r--r--MediaBrowser.Model/Branding/BrandingOptions.cs3
-rw-r--r--MediaBrowser.Model/Channels/ChannelFeatures.cs2
-rw-r--r--MediaBrowser.Model/Channels/ChannelFolderType.cs2
-rw-r--r--MediaBrowser.Model/Channels/ChannelInfo.cs2
-rw-r--r--MediaBrowser.Model/Channels/ChannelItemSortField.cs2
-rw-r--r--MediaBrowser.Model/Channels/ChannelMediaContentType.cs2
-rw-r--r--MediaBrowser.Model/Channels/ChannelMediaType.cs2
-rw-r--r--MediaBrowser.Model/Channels/ChannelQuery.cs2
-rw-r--r--MediaBrowser.Model/Collections/CollectionCreationResult.cs2
-rw-r--r--MediaBrowser.Model/Configuration/AccessSchedule.cs4
-rw-r--r--MediaBrowser.Model/Configuration/BaseApplicationConfiguration.cs21
-rw-r--r--MediaBrowser.Model/Configuration/DynamicDayOfWeek.cs2
-rw-r--r--MediaBrowser.Model/Configuration/EncodingOptions.cs11
-rw-r--r--MediaBrowser.Model/Configuration/ImageOption.cs3
-rw-r--r--MediaBrowser.Model/Configuration/ImageSavingConvention.cs2
-rw-r--r--MediaBrowser.Model/Configuration/LibraryOptions.cs3
-rw-r--r--MediaBrowser.Model/Configuration/MetadataConfiguration.cs2
-rw-r--r--MediaBrowser.Model/Configuration/MetadataOptions.cs2
-rw-r--r--MediaBrowser.Model/Configuration/MetadataPlugin.cs2
-rw-r--r--MediaBrowser.Model/Configuration/MetadataPluginSummary.cs2
-rw-r--r--MediaBrowser.Model/Configuration/MetadataPluginType.cs2
-rw-r--r--MediaBrowser.Model/Configuration/ServerConfiguration.cs24
-rw-r--r--MediaBrowser.Model/Configuration/SubtitlePlaybackMode.cs2
-rw-r--r--MediaBrowser.Model/Configuration/UnratedItem.cs2
-rw-r--r--MediaBrowser.Model/Configuration/UserConfiguration.cs2
-rw-r--r--MediaBrowser.Model/Configuration/XbmcMetadataOptions.cs2
-rw-r--r--MediaBrowser.Model/Cryptography/ICryptoProvider.cs2
-rw-r--r--MediaBrowser.Model/Devices/ContentUploadHistory.cs2
-rw-r--r--MediaBrowser.Model/Devices/DeviceInfo.cs18
-rw-r--r--MediaBrowser.Model/Devices/DeviceQuery.cs3
-rw-r--r--MediaBrowser.Model/Devices/DevicesOptions.cs2
-rw-r--r--MediaBrowser.Model/Devices/LocalFileInfo.cs2
-rw-r--r--MediaBrowser.Model/Diagnostics/IProcess.cs21
-rw-r--r--MediaBrowser.Model/Diagnostics/IProcessFactory.cs22
-rw-r--r--MediaBrowser.Model/Dlna/AudioOptions.cs2
-rw-r--r--MediaBrowser.Model/Dlna/CodecProfile.cs7
-rw-r--r--MediaBrowser.Model/Dlna/CodecType.cs2
-rw-r--r--MediaBrowser.Model/Dlna/ConditionProcessor.cs6
-rw-r--r--MediaBrowser.Model/Dlna/ContainerProfile.cs2
-rw-r--r--MediaBrowser.Model/Dlna/ContentFeatureBuilder.cs2
-rw-r--r--MediaBrowser.Model/Dlna/DeviceIdentification.cs15
-rw-r--r--MediaBrowser.Model/Dlna/DeviceProfile.cs8
-rw-r--r--MediaBrowser.Model/Dlna/DeviceProfileInfo.cs2
-rw-r--r--MediaBrowser.Model/Dlna/DeviceProfileType.cs2
-rw-r--r--MediaBrowser.Model/Dlna/DirectPlayProfile.cs6
-rw-r--r--MediaBrowser.Model/Dlna/DlnaFlags.cs2
-rw-r--r--MediaBrowser.Model/Dlna/DlnaMaps.cs4
-rw-r--r--MediaBrowser.Model/Dlna/DlnaProfileType.cs2
-rw-r--r--MediaBrowser.Model/Dlna/EncodingContext.cs2
-rw-r--r--MediaBrowser.Model/Dlna/HeaderMatchType.cs2
-rw-r--r--MediaBrowser.Model/Dlna/HttpHeaderInfo.cs2
-rw-r--r--MediaBrowser.Model/Dlna/IDeviceDiscovery.cs2
-rw-r--r--MediaBrowser.Model/Dlna/ITranscoderSupport.cs2
-rw-r--r--MediaBrowser.Model/Dlna/MediaFormatProfile.cs2
-rw-r--r--MediaBrowser.Model/Dlna/MediaFormatProfileResolver.cs147
-rw-r--r--MediaBrowser.Model/Dlna/PlaybackErrorCode.cs2
-rw-r--r--MediaBrowser.Model/Dlna/ProfileCondition.cs2
-rw-r--r--MediaBrowser.Model/Dlna/ProfileConditionType.cs2
-rw-r--r--MediaBrowser.Model/Dlna/ProfileConditionValue.cs2
-rw-r--r--MediaBrowser.Model/Dlna/ResolutionConfiguration.cs2
-rw-r--r--MediaBrowser.Model/Dlna/ResolutionNormalizer.cs9
-rw-r--r--MediaBrowser.Model/Dlna/ResolutionOptions.cs2
-rw-r--r--MediaBrowser.Model/Dlna/ResponseProfile.cs2
-rw-r--r--MediaBrowser.Model/Dlna/SearchCriteria.cs15
-rw-r--r--MediaBrowser.Model/Dlna/SearchType.cs2
-rw-r--r--MediaBrowser.Model/Dlna/SortCriteria.cs2
-rw-r--r--MediaBrowser.Model/Dlna/StreamBuilder.cs15
-rw-r--r--MediaBrowser.Model/Dlna/StreamInfo.cs31
-rw-r--r--MediaBrowser.Model/Dlna/SubtitleDeliveryMethod.cs5
-rw-r--r--MediaBrowser.Model/Dlna/SubtitleProfile.cs2
-rw-r--r--MediaBrowser.Model/Dlna/SubtitleStreamInfo.cs2
-rw-r--r--MediaBrowser.Model/Dlna/TranscodeSeekInfo.cs2
-rw-r--r--MediaBrowser.Model/Dlna/TranscodingProfile.cs2
-rw-r--r--MediaBrowser.Model/Dlna/UpnpDeviceInfo.cs2
-rw-r--r--MediaBrowser.Model/Dlna/VideoOptions.cs2
-rw-r--r--MediaBrowser.Model/Dlna/XmlAttribute.cs2
-rw-r--r--MediaBrowser.Model/Drawing/DrawingUtils.cs16
-rw-r--r--MediaBrowser.Model/Drawing/ImageDimensions.cs35
-rw-r--r--MediaBrowser.Model/Drawing/ImageFormat.cs16
-rw-r--r--MediaBrowser.Model/Drawing/ImageOrientation.cs2
-rw-r--r--MediaBrowser.Model/Dto/BaseItemDto.cs2
-rw-r--r--MediaBrowser.Model/Dto/BaseItemPerson.cs2
-rw-r--r--MediaBrowser.Model/Dto/IHasServerId.cs2
-rw-r--r--MediaBrowser.Model/Dto/IItemDto.cs2
-rw-r--r--MediaBrowser.Model/Dto/ImageByNameInfo.cs5
-rw-r--r--MediaBrowser.Model/Dto/ImageInfo.cs2
-rw-r--r--MediaBrowser.Model/Dto/ImageOptions.cs2
-rw-r--r--MediaBrowser.Model/Dto/ItemCounts.cs25
-rw-r--r--MediaBrowser.Model/Dto/ItemIndex.cs2
-rw-r--r--MediaBrowser.Model/Dto/MediaSourceInfo.cs8
-rw-r--r--MediaBrowser.Model/Dto/MediaSourceType.cs2
-rw-r--r--MediaBrowser.Model/Dto/MetadataEditorInfo.cs2
-rw-r--r--MediaBrowser.Model/Dto/NameIdPair.cs3
-rw-r--r--MediaBrowser.Model/Dto/NameValuePair.cs3
-rw-r--r--MediaBrowser.Model/Dto/RatingType.cs2
-rw-r--r--MediaBrowser.Model/Dto/RecommendationDto.cs2
-rw-r--r--MediaBrowser.Model/Dto/RecommendationType.cs2
-rw-r--r--MediaBrowser.Model/Dto/UserDto.cs6
-rw-r--r--MediaBrowser.Model/Dto/UserItemDataDto.cs2
-rw-r--r--MediaBrowser.Model/Entities/ChapterInfo.cs5
-rw-r--r--MediaBrowser.Model/Entities/CollectionType.cs2
-rw-r--r--MediaBrowser.Model/Entities/DisplayPreferences.cs17
-rw-r--r--MediaBrowser.Model/Entities/EmptyRequestResult.cs6
-rw-r--r--MediaBrowser.Model/Entities/ExtraType.cs3
-rw-r--r--MediaBrowser.Model/Entities/IHasProviderIds.cs2
-rw-r--r--MediaBrowser.Model/Entities/ImageType.cs37
-rw-r--r--MediaBrowser.Model/Entities/IsoType.cs7
-rw-r--r--MediaBrowser.Model/Entities/LibraryUpdateInfo.cs2
-rw-r--r--MediaBrowser.Model/Entities/LocationType.cs13
-rw-r--r--MediaBrowser.Model/Entities/MediaAttachment.cs2
-rw-r--r--MediaBrowser.Model/Entities/MediaStream.cs24
-rw-r--r--MediaBrowser.Model/Entities/MediaStreamType.cs13
-rw-r--r--MediaBrowser.Model/Entities/MediaType.cs10
-rw-r--r--MediaBrowser.Model/Entities/MediaUrl.cs2
-rw-r--r--MediaBrowser.Model/Entities/MetadataFields.cs28
-rw-r--r--MediaBrowser.Model/Entities/MetadataProviders.cs2
-rw-r--r--MediaBrowser.Model/Entities/PackageReviewInfo.cs2
-rw-r--r--MediaBrowser.Model/Entities/ParentalRating.cs2
-rw-r--r--MediaBrowser.Model/Entities/PersonType.cs25
-rw-r--r--MediaBrowser.Model/Entities/ProviderIdsExtensions.cs10
-rw-r--r--MediaBrowser.Model/Entities/ScrollDirection.cs7
-rw-r--r--MediaBrowser.Model/Entities/SeriesStatus.cs7
-rw-r--r--MediaBrowser.Model/Entities/SortOrder.cs7
-rw-r--r--MediaBrowser.Model/Entities/TrailerType.cs2
-rw-r--r--MediaBrowser.Model/Entities/UserDataSaveReason.cs19
-rw-r--r--MediaBrowser.Model/Entities/Video3DFormat.cs2
-rw-r--r--MediaBrowser.Model/Entities/VideoType.cs13
-rw-r--r--MediaBrowser.Model/Entities/VirtualFolderInfo.cs2
-rw-r--r--MediaBrowser.Model/Events/GenericEventArgs.cs2
-rw-r--r--MediaBrowser.Model/Extensions/ListHelper.cs3
-rw-r--r--MediaBrowser.Model/Extensions/StringHelper.cs69
-rw-r--r--MediaBrowser.Model/Globalization/CountryInfo.cs2
-rw-r--r--MediaBrowser.Model/Globalization/CultureDto.cs6
-rw-r--r--MediaBrowser.Model/Globalization/ILocalizationManager.cs2
-rw-r--r--MediaBrowser.Model/Globalization/LocalizationOption.cs2
-rw-r--r--MediaBrowser.Model/IO/FileSystemEntryType.cs13
-rw-r--r--MediaBrowser.Model/IO/FileSystemMetadata.cs9
-rw-r--r--MediaBrowser.Model/IO/IFileSystem.cs143
-rw-r--r--MediaBrowser.Model/IO/IIsoManager.cs2
-rw-r--r--MediaBrowser.Model/IO/IIsoMount.cs2
-rw-r--r--MediaBrowser.Model/IO/IIsoMounter.cs2
-rw-r--r--MediaBrowser.Model/IO/IODefaults.cs (renamed from MediaBrowser.Model/IO/StreamDefaults.cs)8
-rw-r--r--MediaBrowser.Model/IO/IShortcutHandler.cs4
-rw-r--r--MediaBrowser.Model/IO/IStreamHelper.cs2
-rw-r--r--MediaBrowser.Model/IO/IZipClient.cs2
-rw-r--r--MediaBrowser.Model/Library/PlayAccess.cs2
-rw-r--r--MediaBrowser.Model/Library/UserViewQuery.cs2
-rw-r--r--MediaBrowser.Model/LiveTv/BaseTimerInfoDto.cs2
-rw-r--r--MediaBrowser.Model/LiveTv/ChannelType.cs4
-rw-r--r--MediaBrowser.Model/LiveTv/DayPattern.cs2
-rw-r--r--MediaBrowser.Model/LiveTv/GuideInfo.cs2
-rw-r--r--MediaBrowser.Model/LiveTv/LiveTvChannelQuery.cs2
-rw-r--r--MediaBrowser.Model/LiveTv/LiveTvInfo.cs2
-rw-r--r--MediaBrowser.Model/LiveTv/LiveTvOptions.cs2
-rw-r--r--MediaBrowser.Model/LiveTv/LiveTvServiceInfo.cs5
-rw-r--r--MediaBrowser.Model/LiveTv/LiveTvServiceStatus.cs2
-rw-r--r--MediaBrowser.Model/LiveTv/LiveTvTunerStatus.cs2
-rw-r--r--MediaBrowser.Model/LiveTv/ProgramAudio.cs2
-rw-r--r--MediaBrowser.Model/LiveTv/RecordingQuery.cs2
-rw-r--r--MediaBrowser.Model/LiveTv/RecordingStatus.cs2
-rw-r--r--MediaBrowser.Model/LiveTv/SeriesTimerInfoDto.cs4
-rw-r--r--MediaBrowser.Model/LiveTv/SeriesTimerQuery.cs2
-rw-r--r--MediaBrowser.Model/LiveTv/TimerInfoDto.cs2
-rw-r--r--MediaBrowser.Model/LiveTv/TimerQuery.cs2
-rw-r--r--MediaBrowser.Model/MediaBrowser.Model.csproj26
-rw-r--r--MediaBrowser.Model/MediaInfo/AudioCodec.cs4
-rw-r--r--MediaBrowser.Model/MediaInfo/BlurayDiscInfo.cs4
-rw-r--r--MediaBrowser.Model/MediaInfo/Container.cs8
-rw-r--r--MediaBrowser.Model/MediaInfo/IBlurayExaminer.cs2
-rw-r--r--MediaBrowser.Model/MediaInfo/LiveStreamRequest.cs2
-rw-r--r--MediaBrowser.Model/MediaInfo/LiveStreamResponse.cs2
-rw-r--r--MediaBrowser.Model/MediaInfo/MediaInfo.cs9
-rw-r--r--MediaBrowser.Model/MediaInfo/MediaProtocol.cs2
-rw-r--r--MediaBrowser.Model/MediaInfo/PlaybackInfoRequest.cs2
-rw-r--r--MediaBrowser.Model/MediaInfo/SubtitleFormat.cs4
-rw-r--r--MediaBrowser.Model/MediaInfo/SubtitleTrackEvent.cs2
-rw-r--r--MediaBrowser.Model/MediaInfo/SubtitleTrackInfo.cs2
-rw-r--r--MediaBrowser.Model/MediaInfo/TransportStreamTimestamp.cs2
-rw-r--r--MediaBrowser.Model/MediaInfo/VideoCodec.cs14
-rw-r--r--MediaBrowser.Model/Net/EndPointInfo.cs2
-rw-r--r--MediaBrowser.Model/Net/HttpException.cs2
-rw-r--r--MediaBrowser.Model/Net/ISocket.cs4
-rw-r--r--MediaBrowser.Model/Net/ISocketFactory.cs12
-rw-r--r--MediaBrowser.Model/Net/MimeTypes.cs17
-rw-r--r--MediaBrowser.Model/Net/NetworkShare.cs2
-rw-r--r--MediaBrowser.Model/Net/NetworkShareType.cs16
-rw-r--r--MediaBrowser.Model/Net/SocketReceiveResult.cs2
-rw-r--r--MediaBrowser.Model/Net/WebSocketMessage.cs5
-rw-r--r--MediaBrowser.Model/Notifications/NotificationLevel.cs2
-rw-r--r--MediaBrowser.Model/Notifications/NotificationOption.cs2
-rw-r--r--MediaBrowser.Model/Notifications/NotificationOptions.cs4
-rw-r--r--MediaBrowser.Model/Notifications/NotificationRequest.cs2
-rw-r--r--MediaBrowser.Model/Notifications/NotificationType.cs2
-rw-r--r--MediaBrowser.Model/Notifications/NotificationTypeInfo.cs2
-rw-r--r--MediaBrowser.Model/Notifications/SendToUserType.cs2
-rw-r--r--MediaBrowser.Model/Playlists/PlaylistCreationRequest.cs2
-rw-r--r--MediaBrowser.Model/Playlists/PlaylistCreationResult.cs2
-rw-r--r--MediaBrowser.Model/Playlists/PlaylistItemQuery.cs2
-rw-r--r--MediaBrowser.Model/Plugins/IHasWebPages.cs2
-rw-r--r--MediaBrowser.Model/Plugins/PluginPageInfo.cs2
-rw-r--r--MediaBrowser.Model/Providers/ExternalIdInfo.cs2
-rw-r--r--MediaBrowser.Model/Providers/ExternalUrl.cs2
-rw-r--r--MediaBrowser.Model/Providers/ImageProviderInfo.cs5
-rw-r--r--MediaBrowser.Model/Providers/RemoteImageInfo.cs4
-rw-r--r--MediaBrowser.Model/Providers/RemoteImageQuery.cs2
-rw-r--r--MediaBrowser.Model/Providers/RemoteSearchResult.cs2
-rw-r--r--MediaBrowser.Model/Providers/RemoteSubtitleInfo.cs2
-rw-r--r--MediaBrowser.Model/Providers/SubtitleOptions.cs2
-rw-r--r--MediaBrowser.Model/Providers/SubtitleProviderInfo.cs2
-rw-r--r--MediaBrowser.Model/Querying/AllThemeMediaResult.cs2
-rw-r--r--MediaBrowser.Model/Querying/EpisodeQuery.cs15
-rw-r--r--MediaBrowser.Model/Querying/ItemCountsQuery.cs2
-rw-r--r--MediaBrowser.Model/Querying/ItemFields.cs4
-rw-r--r--MediaBrowser.Model/Querying/ItemFilter.cs28
-rw-r--r--MediaBrowser.Model/Querying/ItemSortBy.cs2
-rw-r--r--MediaBrowser.Model/Querying/LatestItemsQuery.cs2
-rw-r--r--MediaBrowser.Model/Querying/MovieRecommendationQuery.cs10
-rw-r--r--MediaBrowser.Model/Querying/NextUpQuery.cs7
-rw-r--r--MediaBrowser.Model/Querying/QueryFilters.cs2
-rw-r--r--MediaBrowser.Model/Querying/QueryResult.cs8
-rw-r--r--MediaBrowser.Model/Querying/SessionQuery.cs13
-rw-r--r--MediaBrowser.Model/Querying/SimilarItemsQuery.cs29
-rw-r--r--MediaBrowser.Model/Querying/UpcomingEpisodesQuery.cs2
-rw-r--r--MediaBrowser.Model/Querying/UserQuery.cs8
-rw-r--r--MediaBrowser.Model/Search/SearchHint.cs4
-rw-r--r--MediaBrowser.Model/Search/SearchHintResult.cs2
-rw-r--r--MediaBrowser.Model/Search/SearchQuery.cs2
-rw-r--r--MediaBrowser.Model/Serialization/IJsonSerializer.cs2
-rw-r--r--MediaBrowser.Model/Serialization/IXmlSerializer.cs2
-rw-r--r--MediaBrowser.Model/Services/ApiMemberAttribute.cs3
-rw-r--r--MediaBrowser.Model/Services/IAsyncStreamWriter.cs2
-rw-r--r--MediaBrowser.Model/Services/IHasHeaders.cs2
-rw-r--r--MediaBrowser.Model/Services/IHasRequestFilter.cs12
-rw-r--r--MediaBrowser.Model/Services/IHttpRequest.cs2
-rw-r--r--MediaBrowser.Model/Services/IHttpResult.cs2
-rw-r--r--MediaBrowser.Model/Services/IRequest.cs2
-rw-r--r--MediaBrowser.Model/Services/IRequiresRequestStream.cs2
-rw-r--r--MediaBrowser.Model/Services/IService.cs2
-rw-r--r--MediaBrowser.Model/Services/IStreamWriter.cs2
-rw-r--r--MediaBrowser.Model/Services/QueryParamCollection.cs2
-rw-r--r--MediaBrowser.Model/Services/RouteAttribute.cs2
-rw-r--r--MediaBrowser.Model/Session/BrowseRequest.cs4
-rw-r--r--MediaBrowser.Model/Session/ClientCapabilities.cs2
-rw-r--r--MediaBrowser.Model/Session/GeneralCommand.cs2
-rw-r--r--MediaBrowser.Model/Session/GeneralCommandType.cs2
-rw-r--r--MediaBrowser.Model/Session/MessageCommand.cs2
-rw-r--r--MediaBrowser.Model/Session/PlayCommand.cs16
-rw-r--r--MediaBrowser.Model/Session/PlayMethod.cs2
-rw-r--r--MediaBrowser.Model/Session/PlayRequest.cs2
-rw-r--r--MediaBrowser.Model/Session/PlaybackProgressInfo.cs2
-rw-r--r--MediaBrowser.Model/Session/PlaybackStopInfo.cs9
-rw-r--r--MediaBrowser.Model/Session/PlayerStateInfo.cs2
-rw-r--r--MediaBrowser.Model/Session/PlaystateCommand.cs27
-rw-r--r--MediaBrowser.Model/Session/PlaystateRequest.cs2
-rw-r--r--MediaBrowser.Model/Session/TranscodingInfo.cs2
-rw-r--r--MediaBrowser.Model/Session/UserDataChangeInfo.cs2
-rw-r--r--MediaBrowser.Model/Sync/SyncCategory.cs2
-rw-r--r--MediaBrowser.Model/Sync/SyncJob.cs20
-rw-r--r--MediaBrowser.Model/Sync/SyncJobStatus.cs2
-rw-r--r--MediaBrowser.Model/Sync/SyncTarget.cs3
-rw-r--r--MediaBrowser.Model/System/LogFile.cs2
-rw-r--r--MediaBrowser.Model/System/OperatingSystemId.cs2
-rw-r--r--MediaBrowser.Model/System/PublicSystemInfo.cs2
-rw-r--r--MediaBrowser.Model/System/SystemInfo.cs4
-rw-r--r--MediaBrowser.Model/Tasks/IConfigurableScheduledTask.cs2
-rw-r--r--MediaBrowser.Model/Tasks/IScheduledTask.cs6
-rw-r--r--MediaBrowser.Model/Tasks/IScheduledTaskWorker.cs2
-rw-r--r--MediaBrowser.Model/Tasks/ITaskManager.cs2
-rw-r--r--MediaBrowser.Model/Tasks/ITaskTrigger.cs8
-rw-r--r--MediaBrowser.Model/Tasks/ScheduledTaskHelpers.cs2
-rw-r--r--MediaBrowser.Model/Tasks/TaskCompletionEventArgs.cs2
-rw-r--r--MediaBrowser.Model/Tasks/TaskCompletionStatus.cs10
-rw-r--r--MediaBrowser.Model/Tasks/TaskInfo.cs6
-rw-r--r--MediaBrowser.Model/Tasks/TaskOptions.cs2
-rw-r--r--MediaBrowser.Model/Tasks/TaskResult.cs2
-rw-r--r--MediaBrowser.Model/Tasks/TaskState.cs10
-rw-r--r--MediaBrowser.Model/Tasks/TaskTriggerInfo.cs4
-rw-r--r--MediaBrowser.Model/Updates/CheckForUpdateResult.cs29
-rw-r--r--MediaBrowser.Model/Updates/InstallationInfo.cs20
-rw-r--r--MediaBrowser.Model/Updates/PackageInfo.cs130
-rw-r--r--MediaBrowser.Model/Updates/PackageTargetSystem.cs21
-rw-r--r--MediaBrowser.Model/Updates/PackageVersionClass.cs21
-rw-r--r--MediaBrowser.Model/Updates/PackageVersionInfo.cs94
-rw-r--r--MediaBrowser.Model/Updates/VersionInfo.cs58
-rw-r--r--MediaBrowser.Model/Users/ForgotPasswordAction.cs2
-rw-r--r--MediaBrowser.Model/Users/ForgotPasswordResult.cs4
-rw-r--r--MediaBrowser.Model/Users/PinRedeemResult.cs3
-rw-r--r--MediaBrowser.Model/Users/UserAction.cs2
-rw-r--r--MediaBrowser.Model/Users/UserActionType.cs2
-rw-r--r--MediaBrowser.Model/Users/UserPolicy.cs7
-rw-r--r--MediaBrowser.Providers/Books/AudioBookMetadataService.cs2
-rw-r--r--MediaBrowser.Providers/Books/BookMetadataService.cs2
-rw-r--r--MediaBrowser.Providers/BoxSets/BoxSetMetadataService.cs2
-rw-r--r--MediaBrowser.Providers/Channels/ChannelMetadataService.cs2
-rw-r--r--MediaBrowser.Providers/Chapters/ChapterManager.cs22
-rw-r--r--MediaBrowser.Providers/Folders/CollectionFolderMetadataService.cs2
-rw-r--r--MediaBrowser.Providers/Folders/FolderMetadataService.cs2
-rw-r--r--MediaBrowser.Providers/Folders/UserViewMetadataService.cs2
-rw-r--r--MediaBrowser.Providers/Genres/GenreMetadataService.cs2
-rw-r--r--MediaBrowser.Providers/LiveTv/ProgramMetadataService.cs2
-rw-r--r--MediaBrowser.Providers/Manager/ImageSaver.cs4
-rw-r--r--MediaBrowser.Providers/Manager/ItemImageProvider.cs9
-rw-r--r--MediaBrowser.Providers/Manager/MetadataService.cs3
-rw-r--r--MediaBrowser.Providers/Manager/ProviderManager.cs155
-rw-r--r--MediaBrowser.Providers/MediaBrowser.Providers.csproj33
-rw-r--r--MediaBrowser.Providers/MediaInfo/FFProbeProvider.cs35
-rw-r--r--MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs177
-rw-r--r--MediaBrowser.Providers/MediaInfo/SubtitleResolver.cs14
-rw-r--r--MediaBrowser.Providers/MediaInfo/SubtitleScheduledTask.cs20
-rw-r--r--MediaBrowser.Providers/MediaInfo/VideoImageProvider.cs2
-rw-r--r--MediaBrowser.Providers/Movies/MovieMetadataService.cs2
-rw-r--r--MediaBrowser.Providers/Movies/TrailerMetadataService.cs2
-rw-r--r--MediaBrowser.Providers/Music/AlbumMetadataService.cs2
-rw-r--r--MediaBrowser.Providers/Music/ArtistMetadataService.cs10
-rw-r--r--MediaBrowser.Providers/Music/AudioMetadataService.cs2
-rw-r--r--MediaBrowser.Providers/Music/MusicExternalIds.cs96
-rw-r--r--MediaBrowser.Providers/Music/MusicVideoMetadataService.cs2
-rw-r--r--MediaBrowser.Providers/MusicGenres/MusicGenreMetadataService.cs2
-rw-r--r--MediaBrowser.Providers/People/PersonMetadataService.cs2
-rw-r--r--MediaBrowser.Providers/Photos/PhotoAlbumMetadataService.cs2
-rw-r--r--MediaBrowser.Providers/Photos/PhotoMetadataService.cs2
-rw-r--r--MediaBrowser.Providers/Playlists/PlaylistItemsProvider.cs2
-rw-r--r--MediaBrowser.Providers/Playlists/PlaylistMetadataService.cs2
-rw-r--r--MediaBrowser.Providers/Plugins/AudioDb/AlbumImageProvider.cs (renamed from MediaBrowser.Providers/Music/AudioDbAlbumImageProvider.cs)5
-rw-r--r--MediaBrowser.Providers/Plugins/AudioDb/AlbumProvider.cs (renamed from MediaBrowser.Providers/Music/AudioDbAlbumProvider.cs)20
-rw-r--r--MediaBrowser.Providers/Plugins/AudioDb/ArtistImageProvider.cs (renamed from MediaBrowser.Providers/Music/AudioDbArtistImageProvider.cs)5
-rw-r--r--MediaBrowser.Providers/Plugins/AudioDb/ArtistProvider.cs (renamed from MediaBrowser.Providers/Music/AudioDbArtistProvider.cs)14
-rw-r--r--MediaBrowser.Providers/Plugins/AudioDb/Configuration/PluginConfiguration.cs11
-rw-r--r--MediaBrowser.Providers/Plugins/AudioDb/Configuration/config.html57
-rw-r--r--MediaBrowser.Providers/Plugins/AudioDb/ExternalIds.cs (renamed from MediaBrowser.Providers/Music/AudioDbExternalIds.cs)8
-rw-r--r--MediaBrowser.Providers/Plugins/AudioDb/Plugin.cs35
-rw-r--r--MediaBrowser.Providers/Plugins/MusicBrainz/AlbumProvider.cs (renamed from MediaBrowser.Providers/Music/MusicBrainzAlbumProvider.cs)48
-rw-r--r--MediaBrowser.Providers/Plugins/MusicBrainz/ArtistProvider.cs (renamed from MediaBrowser.Providers/Music/MusicBrainzArtistProvider.cs)19
-rw-r--r--MediaBrowser.Providers/Plugins/MusicBrainz/Configuration/PluginConfiguration.cs48
-rw-r--r--MediaBrowser.Providers/Plugins/MusicBrainz/Configuration/config.html69
-rw-r--r--MediaBrowser.Providers/Plugins/MusicBrainz/ExternalIds.cs98
-rw-r--r--MediaBrowser.Providers/Plugins/MusicBrainz/Plugin.cs39
-rw-r--r--MediaBrowser.Providers/Plugins/Omdb/OmdbEpisodeProvider.cs (renamed from MediaBrowser.Providers/TV/Omdb/OmdbEpisodeProvider.cs)3
-rw-r--r--MediaBrowser.Providers/Plugins/Omdb/OmdbImageProvider.cs (renamed from MediaBrowser.Providers/Omdb/OmdbImageProvider.cs)2
-rw-r--r--MediaBrowser.Providers/Plugins/Omdb/OmdbItemProvider.cs (renamed from MediaBrowser.Providers/Omdb/OmdbItemProvider.cs)2
-rw-r--r--MediaBrowser.Providers/Plugins/Omdb/OmdbProvider.cs (renamed from MediaBrowser.Providers/Omdb/OmdbProvider.cs)6
-rw-r--r--MediaBrowser.Providers/Plugins/TheTvdb/TvdbClientManager.cs (renamed from MediaBrowser.Providers/TV/TheTVDB/TvDbClientManager.cs)14
-rw-r--r--MediaBrowser.Providers/Plugins/TheTvdb/TvdbEpisodeImageProvider.cs (renamed from MediaBrowser.Providers/TV/TheTVDB/TvdbEpisodeImageProvider.cs)12
-rw-r--r--MediaBrowser.Providers/Plugins/TheTvdb/TvdbEpisodeProvider.cs (renamed from MediaBrowser.Providers/TV/TheTVDB/TvdbEpisodeProvider.cs)14
-rw-r--r--MediaBrowser.Providers/Plugins/TheTvdb/TvdbPersonImageProvider.cs (renamed from MediaBrowser.Providers/People/TvdbPersonImageProvider.cs)11
-rw-r--r--MediaBrowser.Providers/Plugins/TheTvdb/TvdbSeasonImageProvider.cs (renamed from MediaBrowser.Providers/TV/TheTVDB/TvdbSeasonImageProvider.cs)12
-rw-r--r--MediaBrowser.Providers/Plugins/TheTvdb/TvdbSeriesImageProvider.cs (renamed from MediaBrowser.Providers/TV/TheTVDB/TvdbSeriesImageProvider.cs)12
-rw-r--r--MediaBrowser.Providers/Plugins/TheTvdb/TvdbSeriesProvider.cs (renamed from MediaBrowser.Providers/TV/TheTVDB/TvdbSeriesProvider.cs)31
-rw-r--r--MediaBrowser.Providers/Plugins/TheTvdb/TvdbUtils.cs (renamed from MediaBrowser.Providers/TV/TheTVDB/TvdbUtils.cs)3
-rw-r--r--MediaBrowser.Providers/Studios/StudioMetadataService.cs2
-rw-r--r--MediaBrowser.Providers/Studios/StudiosImageProvider.cs6
-rw-r--r--MediaBrowser.Providers/Subtitles/SubtitleManager.cs12
-rw-r--r--MediaBrowser.Providers/TV/DummySeasonProvider.cs1
-rw-r--r--MediaBrowser.Providers/TV/EpisodeMetadataService.cs2
-rw-r--r--MediaBrowser.Providers/TV/MissingEpisodeProvider.cs10
-rw-r--r--MediaBrowser.Providers/TV/SeasonMetadataService.cs2
-rw-r--r--MediaBrowser.Providers/TV/SeriesMetadataService.cs12
-rw-r--r--MediaBrowser.Providers/TV/TvExternalIds.cs2
-rw-r--r--MediaBrowser.Providers/Tmdb/BoxSets/TmdbBoxSetImageProvider.cs1
-rw-r--r--MediaBrowser.Providers/Tmdb/BoxSets/TmdbBoxSetProvider.cs9
-rw-r--r--MediaBrowser.Providers/Tmdb/Models/General/Profile.cs10
-rw-r--r--MediaBrowser.Providers/Tmdb/Models/Search/ExternalIdLookupResult.cs1
-rw-r--r--MediaBrowser.Providers/Tmdb/Movies/GenericTmdbMovieInfo.cs2
-rw-r--r--MediaBrowser.Providers/Tmdb/Movies/TmdbImageProvider.cs1
-rw-r--r--MediaBrowser.Providers/Tmdb/Movies/TmdbMovieProvider.cs23
-rw-r--r--MediaBrowser.Providers/Tmdb/Music/TmdbMusicVideoProvider.cs1
-rw-r--r--MediaBrowser.Providers/Tmdb/People/TmdbPersonImageProvider.cs1
-rw-r--r--MediaBrowser.Providers/Tmdb/People/TmdbPersonProvider.cs9
-rw-r--r--MediaBrowser.Providers/Tmdb/TV/TmdbEpisodeImageProvider.cs1
-rw-r--r--MediaBrowser.Providers/Tmdb/TV/TmdbEpisodeProviderBase.cs1
-rw-r--r--MediaBrowser.Providers/Tmdb/TV/TmdbSeasonImageProvider.cs2
-rw-r--r--MediaBrowser.Providers/Tmdb/TV/TmdbSeasonProvider.cs1
-rw-r--r--MediaBrowser.Providers/Tmdb/TV/TmdbSeriesImageProvider.cs1
-rw-r--r--MediaBrowser.Providers/Tmdb/TV/TmdbSeriesProvider.cs10
-rw-r--r--MediaBrowser.Providers/Tmdb/Trailers/TmdbTrailerProvider.cs1
-rw-r--r--MediaBrowser.Providers/Users/UserMetadataService.cs2
-rw-r--r--MediaBrowser.Providers/Videos/VideoMetadataService.cs2
-rw-r--r--MediaBrowser.Providers/Years/YearMetadataService.cs2
-rw-r--r--MediaBrowser.WebDashboard/Api/ConfigurationPageInfo.cs51
-rw-r--r--MediaBrowser.WebDashboard/Api/DashboardService.cs131
-rw-r--r--MediaBrowser.WebDashboard/Api/PackageCreator.cs39
-rw-r--r--MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj18
-rw-r--r--MediaBrowser.WebDashboard/ServerEntryPoint.cs22
-rw-r--r--MediaBrowser.XbmcMetadata/Configuration/NfoConfigurationExtension.cs15
-rw-r--r--MediaBrowser.XbmcMetadata/Configuration/NfoConfigurationFactory.cs (renamed from MediaBrowser.XbmcMetadata/Configuration/NfoOptions.cs)12
-rw-r--r--MediaBrowser.XbmcMetadata/EntryPoint.cs6
-rw-r--r--MediaBrowser.XbmcMetadata/MediaBrowser.XbmcMetadata.csproj18
-rw-r--r--MediaBrowser.XbmcMetadata/Parsers/BaseNfoParser.cs70
-rw-r--r--MediaBrowser.XbmcMetadata/Parsers/EpisodeNfoParser.cs14
-rw-r--r--MediaBrowser.XbmcMetadata/Parsers/MovieNfoParser.cs16
-rw-r--r--MediaBrowser.XbmcMetadata/Parsers/SeasonNfoParser.cs10
-rw-r--r--MediaBrowser.XbmcMetadata/Parsers/SeriesNfoParser.cs15
-rw-r--r--MediaBrowser.XbmcMetadata/Providers/AlbumNfoProvider.cs16
-rw-r--r--MediaBrowser.XbmcMetadata/Providers/ArtistNfoProvider.cs16
-rw-r--r--MediaBrowser.XbmcMetadata/Providers/BaseNfoProvider.cs15
-rw-r--r--MediaBrowser.XbmcMetadata/Providers/BaseVideoNfoProvider.cs10
-rw-r--r--MediaBrowser.XbmcMetadata/Providers/EpisodeNfoProvider.cs16
-rw-r--r--MediaBrowser.XbmcMetadata/Providers/MovieNfoProvider.cs35
-rw-r--r--MediaBrowser.XbmcMetadata/Providers/MusicVideoNfoProvider.cs26
-rw-r--r--MediaBrowser.XbmcMetadata/Providers/SeasonNfoProvider.cs17
-rw-r--r--MediaBrowser.XbmcMetadata/Providers/SeriesNfoProvider.cs16
-rw-r--r--MediaBrowser.XbmcMetadata/Providers/VideoNfoProvider.cs26
-rw-r--r--MediaBrowser.XbmcMetadata/Savers/AlbumNfoSaver.cs16
-rw-r--r--MediaBrowser.XbmcMetadata/Savers/ArtistNfoSaver.cs22
-rw-r--r--MediaBrowser.XbmcMetadata/Savers/BaseNfoSaver.cs53
-rw-r--r--MediaBrowser.XbmcMetadata/Savers/EpisodeNfoSaver.cs26
-rw-r--r--MediaBrowser.XbmcMetadata/Savers/MovieNfoSaver.cs30
-rw-r--r--MediaBrowser.XbmcMetadata/Savers/SeasonNfoSaver.cs14
-rw-r--r--MediaBrowser.XbmcMetadata/Savers/SeriesNfoSaver.cs16
-rw-r--r--MediaBrowser.sln20
-rw-r--r--README.md106
-rw-r--r--RSSDP/DeviceEventArgs.cs2
-rw-r--r--RSSDP/DeviceUnavailableEventArgs.cs3
-rw-r--r--RSSDP/DiscoveredSsdpDevice.cs4
-rw-r--r--RSSDP/DisposableManagedObjectBase.cs3
-rw-r--r--RSSDP/HttpParserBase.cs2
-rw-r--r--RSSDP/HttpRequestParser.cs4
-rw-r--r--RSSDP/HttpResponseParser.cs3
-rw-r--r--RSSDP/IEnumerableExtensions.cs1
-rw-r--r--RSSDP/ISsdpDevicePublisher.cs1
-rw-r--r--RSSDP/RSSDP.csproj5
-rw-r--r--RSSDP/SsdpCommunicationsServer.cs4
-rw-r--r--RSSDP/SsdpConstants.cs5
-rw-r--r--RSSDP/SsdpEmbeddedDevice.cs4
-rw-r--r--SharedVersion.cs4
-rw-r--r--benches/Jellyfin.Common.Benches/Jellyfin.Common.Benches.csproj2
l---------[-rwxr-xr-x]build198
-rwxr-xr-xbuild.sh114
-rw-r--r--build.yaml23
-rwxr-xr-xbump_version36
-rwxr-xr-xdebian/bin/restart.sh (renamed from deployment/debian-package-x64/pkg-src/bin/restart.sh)0
-rw-r--r--debian/changelog (renamed from deployment/debian-package-x64/pkg-src/changelog)6
-rw-r--r--debian/compat (renamed from deployment/debian-package-x64/pkg-src/compat)0
-rw-r--r--debian/conf/jellyfin (renamed from deployment/debian-package-x64/pkg-src/conf/jellyfin)5
-rw-r--r--debian/conf/jellyfin-sudoers (renamed from deployment/debian-package-x64/pkg-src/conf/jellyfin-sudoers)0
-rw-r--r--debian/conf/jellyfin.service.conf (renamed from deployment/debian-package-x64/pkg-src/conf/jellyfin.service.conf)0
-rw-r--r--debian/conf/logging.json (renamed from deployment/debian-package-x64/pkg-src/conf/logging.json)0
-rw-r--r--debian/control (renamed from deployment/debian-package-x64/pkg-src/control)17
-rw-r--r--debian/copyright (renamed from deployment/debian-package-x64/pkg-src/copyright)0
-rw-r--r--debian/gbp.conf (renamed from deployment/debian-package-x64/pkg-src/gbp.conf)0
-rw-r--r--debian/install (renamed from deployment/debian-package-x64/pkg-src/install)0
-rw-r--r--debian/jellyfin.init (renamed from deployment/debian-package-x64/pkg-src/jellyfin.init)0
-rw-r--r--debian/jellyfin.service (renamed from deployment/debian-package-x64/pkg-src/jellyfin.service)2
-rw-r--r--debian/jellyfin.upstart (renamed from deployment/debian-package-x64/pkg-src/jellyfin.upstart)0
-rw-r--r--debian/metapackage/jellyfin13
-rw-r--r--debian/po/POTFILES.in (renamed from deployment/debian-package-x64/pkg-src/po/POTFILES.in)0
-rw-r--r--debian/po/templates.pot (renamed from deployment/debian-package-x64/pkg-src/po/templates.pot)0
-rw-r--r--debian/postinst (renamed from deployment/debian-package-x64/pkg-src/postinst)0
-rw-r--r--debian/postrm (renamed from deployment/debian-package-x64/pkg-src/postrm)0
-rw-r--r--debian/preinst (renamed from deployment/debian-package-x64/pkg-src/preinst)0
-rw-r--r--debian/prerm (renamed from deployment/debian-package-x64/pkg-src/prerm)0
-rwxr-xr-xdebian/rules (renamed from deployment/debian-package-x64/pkg-src/rules)15
-rw-r--r--debian/source.lintian-overrides (renamed from deployment/debian-package-x64/pkg-src/source.lintian-overrides)0
-rw-r--r--debian/source/format (renamed from deployment/debian-package-x64/pkg-src/source/format)0
-rw-r--r--debian/source/options (renamed from deployment/debian-package-x64/pkg-src/source/options)0
-rw-r--r--deployment/Dockerfile.centos.amd6432
-rw-r--r--deployment/Dockerfile.debian.amd64 (renamed from deployment/win-x64/Dockerfile)18
-rw-r--r--deployment/Dockerfile.debian.arm64 (renamed from deployment/debian-package-arm64/Dockerfile.amd64)16
-rw-r--r--deployment/Dockerfile.debian.armhf (renamed from deployment/debian-package-armhf/Dockerfile.amd64)15
-rw-r--r--deployment/Dockerfile.fedora.amd6431
-rw-r--r--deployment/Dockerfile.linux.amd64 (renamed from deployment/macos/Dockerfile)16
-rw-r--r--deployment/Dockerfile.macos (renamed from deployment/portable/Dockerfile)16
-rw-r--r--deployment/Dockerfile.portable (renamed from deployment/linux-x64/Dockerfile)17
-rw-r--r--deployment/Dockerfile.ubuntu.amd6431
-rw-r--r--deployment/Dockerfile.ubuntu.arm64 (renamed from deployment/ubuntu-package-arm64/Dockerfile.amd64)19
-rw-r--r--deployment/Dockerfile.ubuntu.armhf (renamed from deployment/ubuntu-package-armhf/Dockerfile.amd64)19
-rw-r--r--deployment/Dockerfile.windows.amd64 (renamed from deployment/win-x86/Dockerfile)17
-rw-r--r--deployment/README.md62
-rwxr-xr-xdeployment/build.centos.amd6424
-rwxr-xr-xdeployment/build.debian.amd6428
-rwxr-xr-xdeployment/build.debian.arm6429
-rwxr-xr-xdeployment/build.debian.armhf29
-rwxr-xr-xdeployment/build.fedora.amd6424
-rwxr-xr-xdeployment/build.linux.amd6427
-rwxr-xr-xdeployment/build.macos27
-rwxr-xr-xdeployment/build.portable27
-rwxr-xr-xdeployment/build.ubuntu.amd6428
-rwxr-xr-xdeployment/build.ubuntu.arm6429
-rwxr-xr-xdeployment/build.ubuntu.armhf29
-rwxr-xr-xdeployment/build.windows.amd6454
-rw-r--r--deployment/centos-package-x64/Dockerfile39
-rwxr-xr-xdeployment/centos-package-x64/clean.sh32
-rw-r--r--deployment/centos-package-x64/dependencies.txt1
-rwxr-xr-xdeployment/centos-package-x64/docker-build.sh18
-rwxr-xr-xdeployment/centos-package-x64/package.sh34
l---------deployment/centos-package-x64/pkg-src1
-rw-r--r--deployment/debian-package-arm64/Dockerfile.arm6434
-rwxr-xr-xdeployment/debian-package-arm64/clean.sh27
-rw-r--r--deployment/debian-package-arm64/dependencies.txt1
-rwxr-xr-xdeployment/debian-package-arm64/docker-build.sh21
-rwxr-xr-xdeployment/debian-package-arm64/package.sh45
l---------deployment/debian-package-arm64/pkg-src1
-rw-r--r--deployment/debian-package-armhf/Dockerfile.armhf34
-rwxr-xr-xdeployment/debian-package-armhf/clean.sh27
-rw-r--r--deployment/debian-package-armhf/dependencies.txt1
-rwxr-xr-xdeployment/debian-package-armhf/docker-build.sh21
-rwxr-xr-xdeployment/debian-package-armhf/package.sh45
l---------deployment/debian-package-armhf/pkg-src1
-rw-r--r--deployment/debian-package-x64/Dockerfile34
-rwxr-xr-xdeployment/debian-package-x64/clean.sh27
-rw-r--r--deployment/debian-package-x64/dependencies.txt1
-rwxr-xr-xdeployment/debian-package-x64/docker-build.sh20
-rwxr-xr-xdeployment/debian-package-x64/package.sh34
-rw-r--r--deployment/fedora-package-x64/Dockerfile33
-rwxr-xr-xdeployment/fedora-package-x64/clean.sh32
-rw-r--r--deployment/fedora-package-x64/dependencies.txt1
-rwxr-xr-xdeployment/fedora-package-x64/docker-build.sh18
-rwxr-xr-xdeployment/fedora-package-x64/package.sh34
-rwxr-xr-xdeployment/linux-x64/clean.sh27
-rw-r--r--deployment/linux-x64/dependencies.txt1
-rwxr-xr-xdeployment/linux-x64/docker-build.sh36
-rwxr-xr-xdeployment/linux-x64/package.sh34
-rwxr-xr-xdeployment/macos/clean.sh27
-rw-r--r--deployment/macos/dependencies.txt1
-rwxr-xr-xdeployment/macos/docker-build.sh36
-rwxr-xr-xdeployment/macos/package.sh34
-rwxr-xr-xdeployment/portable/clean.sh27
-rw-r--r--deployment/portable/dependencies.txt1
-rwxr-xr-xdeployment/portable/docker-build.sh36
-rwxr-xr-xdeployment/portable/package.sh34
-rw-r--r--deployment/ubuntu-package-arm64/Dockerfile.arm6440
-rwxr-xr-xdeployment/ubuntu-package-arm64/clean.sh27
-rw-r--r--deployment/ubuntu-package-arm64/dependencies.txt1
-rwxr-xr-xdeployment/ubuntu-package-arm64/docker-build.sh21
-rwxr-xr-xdeployment/ubuntu-package-arm64/package.sh45
l---------deployment/ubuntu-package-arm64/pkg-src1
-rw-r--r--deployment/ubuntu-package-armhf/Dockerfile.armhf40
-rwxr-xr-xdeployment/ubuntu-package-armhf/clean.sh27
-rw-r--r--deployment/ubuntu-package-armhf/dependencies.txt1
-rwxr-xr-xdeployment/ubuntu-package-armhf/docker-build.sh21
-rwxr-xr-xdeployment/ubuntu-package-armhf/package.sh45
l---------deployment/ubuntu-package-armhf/pkg-src1
-rw-r--r--deployment/ubuntu-package-x64/Dockerfile36
-rwxr-xr-xdeployment/ubuntu-package-x64/clean.sh27
-rw-r--r--deployment/ubuntu-package-x64/dependencies.txt1
-rwxr-xr-xdeployment/ubuntu-package-x64/docker-build.sh20
-rwxr-xr-xdeployment/ubuntu-package-x64/package.sh34
l---------deployment/ubuntu-package-x64/pkg-src1
-rwxr-xr-xdeployment/win-x64/clean.sh27
-rw-r--r--deployment/win-x64/dependencies.txt1
-rwxr-xr-xdeployment/win-x64/docker-build.sh61
-rwxr-xr-xdeployment/win-x64/package.sh34
-rwxr-xr-xdeployment/win-x86/clean.sh27
-rw-r--r--deployment/win-x86/dependencies.txt1
-rwxr-xr-xdeployment/win-x86/docker-build.sh61
-rwxr-xr-xdeployment/win-x86/package.sh34
-rw-r--r--fedora/.gitignore (renamed from deployment/fedora-package-x64/pkg-src/.gitignore)0
-rw-r--r--fedora/Makefile26
-rw-r--r--fedora/README.md (renamed from deployment/fedora-package-x64/pkg-src/README.md)0
-rw-r--r--fedora/jellyfin-firewalld.xml (renamed from deployment/fedora-package-x64/pkg-src/jellyfin-firewalld.xml)0
-rw-r--r--fedora/jellyfin.env (renamed from deployment/fedora-package-x64/pkg-src/jellyfin.env)3
-rw-r--r--fedora/jellyfin.override.conf (renamed from deployment/fedora-package-x64/pkg-src/jellyfin.override.conf)0
-rw-r--r--fedora/jellyfin.service (renamed from deployment/fedora-package-x64/pkg-src/jellyfin.service)2
-rw-r--r--fedora/jellyfin.spec (renamed from deployment/fedora-package-x64/pkg-src/jellyfin.spec)142
-rw-r--r--fedora/jellyfin.sudoers (renamed from deployment/fedora-package-x64/pkg-src/jellyfin.sudoers)0
-rwxr-xr-xfedora/restart.sh (renamed from deployment/fedora-package-x64/pkg-src/restart.sh)6
-rw-r--r--jellyfin.ruleset4
-rw-r--r--nuget.config6
-rw-r--r--tests/Jellyfin.Api.Tests/Auth/CustomAuthenticationHandlerTests.cs175
-rw-r--r--tests/Jellyfin.Api.Tests/Auth/FirstTimeSetupOrElevatedPolicy/FirstTimeSetupOrElevatedHandlerTests.cs77
-rw-r--r--tests/Jellyfin.Api.Tests/Auth/RequiresElevationPolicy/RequiresElevationHandlerTests.cs38
-rw-r--r--tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj16
-rw-r--r--tests/Jellyfin.Common.Tests/Extensions/StringExtensionsTests.cs43
-rw-r--r--tests/Jellyfin.Common.Tests/Jellyfin.Common.Tests.csproj11
-rw-r--r--tests/Jellyfin.Controller.Tests/AlphanumComparatorTests.cs44
-rw-r--r--tests/Jellyfin.Controller.Tests/Jellyfin.Controller.Tests.csproj26
-rw-r--r--tests/Jellyfin.MediaEncoding.Tests/EncoderValidatorTests.cs16
-rw-r--r--tests/Jellyfin.MediaEncoding.Tests/EncoderValidatorTestsData.cs6
-rw-r--r--tests/Jellyfin.MediaEncoding.Tests/FFprobeParserTests.cs22
-rw-r--r--tests/Jellyfin.MediaEncoding.Tests/Jellyfin.MediaEncoding.Tests.csproj17
-rw-r--r--tests/Jellyfin.MediaEncoding.Tests/Test Data/ffprobe1.json105
-rw-r--r--tests/Jellyfin.Model.Tests/Extensions/StringHelperTests.cs19
-rw-r--r--tests/Jellyfin.Model.Tests/Jellyfin.Model.Tests.csproj21
-rw-r--r--tests/Jellyfin.Naming.Tests/Jellyfin.Naming.Tests.csproj10
-rw-r--r--tests/Jellyfin.Naming.Tests/Music/MultiDiscAlbumTests.cs81
-rw-r--r--tests/Jellyfin.Naming.Tests/Subtitles/SubtitleParserTests.cs48
-rw-r--r--tests/Jellyfin.Naming.Tests/TV/AbsoluteEpisodeNumberTests.cs54
-rw-r--r--tests/Jellyfin.Naming.Tests/TV/DailyEpisodeTests.cs53
-rw-r--r--tests/Jellyfin.Naming.Tests/TV/EpisodeNumberTests.cs481
-rw-r--r--tests/Jellyfin.Naming.Tests/TV/EpisodeNumberWithoutSeasonTests.cs127
-rw-r--r--tests/Jellyfin.Naming.Tests/TV/EpisodePathParserTest.cs37
-rw-r--r--tests/Jellyfin.Naming.Tests/TV/EpisodeWithoutSeasonTests.cs43
-rw-r--r--tests/Jellyfin.Naming.Tests/TV/MultiEpisodeTests.cs153
-rw-r--r--tests/Jellyfin.Naming.Tests/TV/SeasonFolderTests.cs123
-rw-r--r--tests/Jellyfin.Naming.Tests/TV/SeasonNumberTests.cs344
-rw-r--r--tests/Jellyfin.Naming.Tests/TV/SimpleEpisodeTests.cs94
-rw-r--r--tests/Jellyfin.Naming.Tests/Video/BaseVideoTest.cs8
-rw-r--r--tests/Jellyfin.Naming.Tests/Video/CleanDateTimeTests.cs172
-rw-r--r--tests/Jellyfin.Naming.Tests/Video/CleanStringTests.cs160
-rw-r--r--tests/Jellyfin.Naming.Tests/Video/ExtraTests.cs66
-rw-r--r--tests/Jellyfin.Naming.Tests/Video/MultiVersionTests.cs1
-rw-r--r--tests/Jellyfin.Naming.Tests/Video/StackTests.cs156
-rw-r--r--tests/Jellyfin.Naming.Tests/Video/StubTests.cs14
-rw-r--r--tests/Jellyfin.Server.Implementations.Tests/HttpServer/ResponseFilterTests.cs18
-rw-r--r--tests/Jellyfin.Server.Implementations.Tests/IO/ManagedFileSystemTests.cs42
-rw-r--r--tests/Jellyfin.Server.Implementations.Tests/Jellyfin.Server.Implementations.Tests.csproj29
-rw-r--r--tests/Jellyfin.Server.Implementations.Tests/Library/PathExtensionsTests.cs27
-rw-r--r--tests/coverletArgs.runsettings17
-rw-r--r--windows/build-jellyfin.ps1 (renamed from deployment/windows/build-jellyfin.ps1)9
-rw-r--r--windows/dependencies.txt (renamed from deployment/windows/dependencies.txt)0
-rw-r--r--windows/dialogs/confirmation.nsddef (renamed from deployment/windows/dialogs/confirmation.nsddef)0
-rw-r--r--windows/dialogs/confirmation.nsdinc (renamed from deployment/windows/dialogs/confirmation.nsdinc)0
-rw-r--r--windows/dialogs/service-config.nsddef (renamed from deployment/windows/dialogs/service-config.nsddef)0
-rw-r--r--windows/dialogs/service-config.nsdinc (renamed from deployment/windows/dialogs/service-config.nsdinc)0
-rw-r--r--windows/dialogs/setuptype.nsddef (renamed from deployment/windows/dialogs/setuptype.nsddef)0
-rw-r--r--windows/dialogs/setuptype.nsdinc (renamed from deployment/windows/dialogs/setuptype.nsdinc)0
-rw-r--r--windows/helpers/ShowError.nsh (renamed from deployment/windows/helpers/ShowError.nsh)0
-rw-r--r--windows/helpers/StrSlash.nsh (renamed from deployment/windows/helpers/StrSlash.nsh)0
-rw-r--r--windows/jellyfin.nsi (renamed from deployment/windows/jellyfin.nsi)10
-rw-r--r--windows/legacy/install-jellyfin.ps1 (renamed from deployment/windows/legacy/install-jellyfin.ps1)0
-rw-r--r--windows/legacy/install.bat (renamed from deployment/windows/legacy/install.bat)0
1246 files changed, 23601 insertions, 13990 deletions
diff --git a/.ci/azure-pipelines-compat.yml b/.ci/azure-pipelines-compat.yml
new file mode 100644
index 000000000..1ffaaf2b9
--- /dev/null
+++ b/.ci/azure-pipelines-compat.yml
@@ -0,0 +1,96 @@
+parameters:
+- name: Packages
+ type: object
+ default: {}
+- name: LinuxImage
+ type: string
+ default: "ubuntu-latest"
+- name: DotNetSdkVersion
+ type: string
+ default: 3.1.100
+
+jobs:
+ - job: CompatibilityCheck
+ displayName: Compatibility Check
+ pool:
+ vmImage: "${{ parameters.LinuxImage }}"
+ # only execute for pull requests
+ condition: and(succeeded(), variables['System.PullRequest.PullRequestNumber'])
+ strategy:
+ matrix:
+ ${{ each Package in parameters.Packages }}:
+ ${{ Package.key }}:
+ NugetPackageName: ${{ Package.value.NugetPackageName }}
+ AssemblyFileName: ${{ Package.value.AssemblyFileName }}
+ maxParallel: 2
+ dependsOn: Build
+ steps:
+ - checkout: none
+
+ - task: UseDotNet@2
+ displayName: "Update DotNet"
+ inputs:
+ packageType: sdk
+ version: ${{ parameters.DotNetSdkVersion }}
+
+ - task: DownloadPipelineArtifact@2
+ displayName: "Download New Assembly Build Artifact"
+ inputs:
+ source: "current"
+ artifact: "$(NugetPackageName)"
+ path: "$(System.ArtifactsDirectory)/new-artifacts"
+ runVersion: "latest"
+
+ - task: CopyFiles@2
+ displayName: "Copy New Assembly Build Artifact"
+ inputs:
+ sourceFolder: $(System.ArtifactsDirectory)/new-artifacts
+ contents: "**/*.dll"
+ targetFolder: $(System.ArtifactsDirectory)/new-release
+ cleanTargetFolder: true
+ overWrite: true
+ flattenFolders: true
+
+ - task: DownloadPipelineArtifact@2
+ displayName: "Download Reference Assembly Build Artifact"
+ inputs:
+ source: "specific"
+ artifact: "$(NugetPackageName)"
+ path: "$(System.ArtifactsDirectory)/current-artifacts"
+ project: "$(System.TeamProjectId)"
+ pipeline: "$(System.DefinitionId)"
+ runVersion: "latestFromBranch"
+ runBranch: "refs/heads/$(System.PullRequest.TargetBranch)"
+
+ - task: CopyFiles@2
+ displayName: "Copy Reference Assembly Build Artifact"
+ inputs:
+ sourceFolder: $(System.ArtifactsDirectory)/current-artifacts
+ contents: "**/*.dll"
+ targetFolder: $(System.ArtifactsDirectory)/current-release
+ cleanTargetFolder: true
+ overWrite: true
+ flattenFolders: true
+
+ - task: DownloadGitHubRelease@0
+ displayName: "Download ABI Compatibility Check Tool"
+ inputs:
+ connection: Jellyfin Release Download
+ userRepository: EraYaN/dotnet-compatibility
+ defaultVersionType: "latest"
+ itemPattern: "**-ci.zip"
+ downloadPath: "$(System.ArtifactsDirectory)"
+
+ - task: ExtractFiles@1
+ displayName: "Extract ABI Compatibility Check Tool"
+ inputs:
+ archiveFilePatterns: "$(System.ArtifactsDirectory)/*-ci.zip"
+ destinationFolder: $(System.ArtifactsDirectory)/tools
+ cleanDestinationFolder: true
+
+ # The `--warnings-only` switch will swallow the return code and not emit any errors.
+ - task: CmdLine@2
+ displayName: "Execute ABI Compatibility Check Tool"
+ inputs:
+ script: "dotnet tools/CompatibilityCheckerCLI.dll current-release/$(AssemblyFileName) new-release/$(AssemblyFileName) --azure-pipelines --warnings-only"
+ workingDirectory: $(System.ArtifactsDirectory)
diff --git a/.ci/azure-pipelines-main.yml b/.ci/azure-pipelines-main.yml
new file mode 100644
index 000000000..456be7108
--- /dev/null
+++ b/.ci/azure-pipelines-main.yml
@@ -0,0 +1,93 @@
+parameters:
+ LinuxImage: "ubuntu-latest"
+ RestoreBuildProjects: "Jellyfin.Server/Jellyfin.Server.csproj"
+ DotNetSdkVersion: 3.1.100
+
+jobs:
+ - job: Build
+ displayName: Build
+ strategy:
+ matrix:
+ Release:
+ BuildConfiguration: Release
+ Debug:
+ BuildConfiguration: Debug
+ pool:
+ vmImage: "${{ parameters.LinuxImage }}"
+ steps:
+ - checkout: self
+ clean: true
+ submodules: true
+ persistCredentials: true
+
+ - task: DownloadPipelineArtifact@2
+ displayName: "Download Web Branch"
+ condition: in(variables['Build.Reason'], 'IndividualCI', 'BatchedCI', 'BuildCompletion')
+ inputs:
+ path: '$(Agent.TempDirectory)'
+ artifact: 'jellyfin-web-production'
+ source: 'specific'
+ project: 'jellyfin'
+ pipeline: 'Jellyfin Web'
+ runBranch: variables['Build.SourceBranch']
+
+ - task: DownloadPipelineArtifact@2
+ displayName: "Download Web Target"
+ condition: eq(variables['Build.Reason'], 'PullRequest')
+ inputs:
+ path: '$(Agent.TempDirectory)'
+ artifact: 'jellyfin-web-production'
+ source: 'specific'
+ project: 'jellyfin'
+ pipeline: 'Jellyfin Web'
+ runBranch: variables['System.PullRequest.TargetBranch']
+
+ - task: ExtractFiles@1
+ displayName: "Extract Web Client"
+ inputs:
+ archiveFilePatterns: '$(Agent.TempDirectory)/*.zip'
+ destinationFolder: '$(Build.SourcesDirectory)/MediaBrowser.WebDashboard'
+ cleanDestinationFolder: false
+
+ - task: UseDotNet@2
+ displayName: "Update DotNet"
+ inputs:
+ packageType: sdk
+ version: ${{ parameters.DotNetSdkVersion }}
+
+ - task: DotNetCoreCLI@2
+ displayName: "Publish Server"
+ inputs:
+ command: publish
+ publishWebProjects: false
+ projects: "${{ parameters.RestoreBuildProjects }}"
+ arguments: "--configuration $(BuildConfiguration) --output $(Build.ArtifactStagingDirectory)"
+ zipAfterPublish: false
+
+ - task: PublishPipelineArtifact@0
+ displayName: "Publish Artifact Naming"
+ condition: and(succeeded(), eq(variables['BuildConfiguration'], 'Release'))
+ inputs:
+ targetPath: "$(build.ArtifactStagingDirectory)/Jellyfin.Server/Emby.Naming.dll"
+ artifactName: "Jellyfin.Naming"
+
+ - task: PublishPipelineArtifact@0
+ displayName: "Publish Artifact Controller"
+ condition: and(succeeded(), eq(variables['BuildConfiguration'], 'Release'))
+ inputs:
+ targetPath: "$(build.ArtifactStagingDirectory)/Jellyfin.Server/MediaBrowser.Controller.dll"
+ artifactName: "Jellyfin.Controller"
+
+ - task: PublishPipelineArtifact@0
+ displayName: "Publish Artifact Model"
+ condition: and(succeeded(), eq(variables['BuildConfiguration'], 'Release'))
+ inputs:
+ targetPath: "$(build.ArtifactStagingDirectory)/Jellyfin.Server/MediaBrowser.Model.dll"
+ artifactName: "Jellyfin.Model"
+
+ - task: PublishPipelineArtifact@0
+ displayName: "Publish Artifact Common"
+ condition: and(succeeded(), eq(variables['BuildConfiguration'], 'Release'))
+ inputs:
+ targetPath: "$(build.ArtifactStagingDirectory)/Jellyfin.Server/MediaBrowser.Common.dll"
+ artifactName: "Jellyfin.Common"
diff --git a/.ci/azure-pipelines-test.yml b/.ci/azure-pipelines-test.yml
new file mode 100644
index 000000000..cb5338ac8
--- /dev/null
+++ b/.ci/azure-pipelines-test.yml
@@ -0,0 +1,89 @@
+parameters:
+- name: ImageNames
+ type: object
+ default:
+ Linux: "ubuntu-latest"
+ Windows: "windows-latest"
+ macOS: "macos-latest"
+- name: TestProjects
+ type: string
+ default: "tests/**/*Tests.csproj"
+- name: DotNetSdkVersion
+ type: string
+ default: 3.1.100
+
+jobs:
+ - job: Test
+ displayName: Test
+ strategy:
+ matrix:
+ ${{ each imageName in parameters.ImageNames }}:
+ ${{ imageName.key }}:
+ ImageName: ${{ imageName.value }}
+ pool:
+ vmImage: "$(ImageName)"
+ steps:
+ - checkout: self
+ clean: true
+ submodules: true
+ persistCredentials: false
+
+ # This is required for the SonarCloud analyzer
+ - task: UseDotNet@2
+ displayName: "Install .NET Core SDK 2.1"
+ condition: eq(variables['ImageName'], 'ubuntu-latest')
+ inputs:
+ packageType: sdk
+ version: '2.1.805'
+
+ - task: UseDotNet@2
+ displayName: "Update DotNet"
+ inputs:
+ packageType: sdk
+ version: ${{ parameters.DotNetSdkVersion }}
+
+ - task: SonarCloudPrepare@1
+ displayName: 'Prepare analysis on SonarCloud'
+ condition: eq(variables['ImageName'], 'ubuntu-latest')
+ inputs:
+ SonarCloud: 'Sonarcloud for Jellyfin'
+ organization: 'jellyfin'
+ projectKey: 'jellyfin_jellyfin'
+
+ - task: DotNetCoreCLI@2
+ displayName: 'Run CLI Tests'
+ inputs:
+ command: "test"
+ projects: ${{ parameters.TestProjects }}
+ arguments: '--configuration Release --collect:"XPlat Code Coverage" --settings tests/coverletArgs.runsettings --verbosity minimal "-p:GenerateDocumentationFile=False"'
+ publishTestResults: true
+ testRunTitle: $(Agent.JobName)
+ workingDirectory: "$(Build.SourcesDirectory)"
+
+ - task: SonarCloudAnalyze@1
+ displayName: 'Run Code Analysis'
+ condition: eq(variables['ImageName'], 'ubuntu-latest')
+
+ - task: SonarCloudPublish@1
+ displayName: 'Publish Quality Gate Result'
+ condition: eq(variables['ImageName'], 'ubuntu-latest')
+
+ - task: Palmmedia.reportgenerator.reportgenerator-build-release-task.reportgenerator@4
+ condition: and(succeeded(), eq(variables['Agent.OS'], 'Linux')) # !! THIS is for V1 only V2 will/should support merging
+ displayName: 'Run ReportGenerator'
+ inputs:
+ reports: "$(Agent.TempDirectory)/**/coverage.cobertura.xml"
+ targetdir: "$(Agent.TempDirectory)/merged/"
+ reporttypes: "Cobertura"
+
+ ## V2 is already in the repository but it does not work "wrong number of segments" YAML error.
+ - task: PublishCodeCoverageResults@1
+ condition: and(succeeded(), eq(variables['Agent.OS'], 'Linux')) # !! THIS is for V1 only V2 will/should support merging
+ displayName: 'Publish Code Coverage'
+ inputs:
+ codeCoverageTool: "cobertura"
+ #summaryFileLocation: '$(Agent.TempDirectory)/**/coverage.cobertura.xml' # !!THIS IS FOR V2
+ summaryFileLocation: "$(Agent.TempDirectory)/merged/**.xml"
+ pathToSources: $(Build.SourcesDirectory)
+ failIfCoverageEmpty: true
+
diff --git a/.ci/azure-pipelines.yml b/.ci/azure-pipelines.yml
index 7bcaed70c..1a439c718 100644
--- a/.ci/azure-pipelines.yml
+++ b/.ci/azure-pipelines.yml
@@ -1,10 +1,12 @@
name: $(Date:yyyyMMdd)$(Rev:.r)
variables:
- - name: TestProjects
- value: 'tests/**/*Tests.csproj'
- - name: RestoreBuildProjects
- value: 'Jellyfin.Server/Jellyfin.Server.csproj'
+- name: TestProjects
+ value: "tests/**/*Tests.csproj"
+- name: RestoreBuildProjects
+ value: "Jellyfin.Server/Jellyfin.Server.csproj"
+- name: DotNetSdkVersion
+ value: 3.1.100
pr:
autoCancel: true
@@ -13,234 +15,21 @@ trigger:
batch: true
jobs:
- - job: main_build
- displayName: Main Build
- pool:
- vmImage: ubuntu-latest
- strategy:
- matrix:
- Release:
- BuildConfiguration: Release
- Debug:
- BuildConfiguration: Debug
- maxParallel: 2
- steps:
- - checkout: self
- clean: true
- submodules: true
- persistCredentials: true
-
- - task: CmdLine@2
- displayName: "Clone Web Client (Master, Release, or Tag)"
- condition: and(succeeded(), or(contains(variables['Build.SourceBranch'], 'release'), contains(variables['Build.SourceBranch'], 'master')) ,eq(variables['BuildConfiguration'], 'Release'), in(variables['Build.Reason'], 'IndividualCI', 'BatchedCI', 'BuildCompletion'))
- inputs:
- script: 'git clone --single-branch --branch $(Build.SourceBranchName) --depth=1 https://github.com/jellyfin/jellyfin-web.git $(Agent.TempDirectory)/jellyfin-web'
-
- - task: CmdLine@2
- displayName: "Clone Web Client (PR)"
- condition: and(succeeded(), or(contains(variables['System.PullRequest.TargetBranch'], 'release'), contains(variables['System.PullRequest.TargetBranch'], 'master')) ,eq(variables['BuildConfiguration'], 'Release'), in(variables['Build.Reason'], 'PullRequest'))
- inputs:
- script: 'git clone --single-branch --branch $(System.PullRequest.TargetBranch) --depth 1 https://github.com/jellyfin/jellyfin-web.git $(Agent.TempDirectory)/jellyfin-web'
-
- - task: NodeTool@0
- displayName: 'Install Node'
- condition: and(succeeded(), or(contains(variables['System.PullRequest.TargetBranch'], 'release'), contains(variables['System.PullRequest.TargetBranch'], 'master'), contains(variables['Build.SourceBranch'], 'release'), contains(variables['Build.SourceBranch'], 'master')) ,eq(variables['BuildConfiguration'], 'Release'), in(variables['Build.Reason'], 'PullRequest', 'IndividualCI', 'BatchedCI', 'BuildCompletion'))
- inputs:
- versionSpec: '10.x'
-
- - task: CmdLine@2
- displayName: "Build Web Client"
- condition: and(succeeded(), or(contains(variables['System.PullRequest.TargetBranch'], 'release'), contains(variables['System.PullRequest.TargetBranch'], 'master'), contains(variables['Build.SourceBranch'], 'release'), contains(variables['Build.SourceBranch'], 'master')) ,eq(variables['BuildConfiguration'], 'Release'), in(variables['Build.Reason'], 'PullRequest', 'IndividualCI', 'BatchedCI', 'BuildCompletion'))
- inputs:
- script: yarn install
- workingDirectory: $(Agent.TempDirectory)/jellyfin-web
-
- - task: CopyFiles@2
- displayName: 'Copy Web Client'
- condition: and(succeeded(), or(contains(variables['System.PullRequest.TargetBranch'], 'release'), contains(variables['System.PullRequest.TargetBranch'], 'master'), contains(variables['Build.SourceBranch'], 'release'), contains(variables['Build.SourceBranch'], 'master')) ,eq(variables['BuildConfiguration'], 'Release'), in(variables['Build.Reason'], 'PullRequest', 'IndividualCI', 'BatchedCI', 'BuildCompletion'))
- inputs:
- sourceFolder: $(Agent.TempDirectory)/jellyfin-web/dist # Optional
- contents: '**'
- targetFolder: $(Build.SourcesDirectory)/MediaBrowser.WebDashboard/jellyfin-web
- cleanTargetFolder: true # Optional
- overWrite: true # Optional
- flattenFolders: false # Optional
-
- - task: UseDotNet@2
- displayName: 'Update DotNet'
- inputs:
- packageType: sdk
- version: 3.1.100
-
- - task: DotNetCoreCLI@2
- displayName: 'Publish Server'
- inputs:
- command: publish
- publishWebProjects: false
- projects: '$(RestoreBuildProjects)'
- arguments: '--configuration $(BuildConfiguration) --output $(build.artifactstagingdirectory)'
- zipAfterPublish: false
-
- - task: PublishPipelineArtifact@0
- displayName: 'Publish Artifact Naming'
- condition: and(eq(variables['BuildConfiguration'], 'Release'), succeeded())
- inputs:
- targetPath: '$(build.artifactstagingdirectory)/Jellyfin.Server/Emby.Naming.dll'
- artifactName: 'Jellyfin.Naming'
-
- - task: PublishPipelineArtifact@0
- displayName: 'Publish Artifact Controller'
- condition: and(eq(variables['BuildConfiguration'], 'Release'), succeeded())
- inputs:
- targetPath: '$(build.artifactstagingdirectory)/Jellyfin.Server/MediaBrowser.Controller.dll'
- artifactName: 'Jellyfin.Controller'
-
- - task: PublishPipelineArtifact@0
- displayName: 'Publish Artifact Model'
- condition: and(eq(variables['BuildConfiguration'], 'Release'), succeeded())
- inputs:
- targetPath: '$(build.artifactstagingdirectory)/Jellyfin.Server/MediaBrowser.Model.dll'
- artifactName: 'Jellyfin.Model'
-
- - task: PublishPipelineArtifact@0
- displayName: 'Publish Artifact Common'
- condition: and(eq(variables['BuildConfiguration'], 'Release'), succeeded())
- inputs:
- targetPath: '$(build.artifactstagingdirectory)/Jellyfin.Server/MediaBrowser.Common.dll'
- artifactName: 'Jellyfin.Common'
-
- - job: main_test
- displayName: Main Test
- pool:
- vmImage: windows-latest
- steps:
- - checkout: self
- clean: true
- submodules: true
- persistCredentials: false
-
- - task: DotNetCoreCLI@2
- displayName: Build
- inputs:
- command: build
- publishWebProjects: false
- projects: '$(TestProjects)'
- arguments: '--configuration $(BuildConfiguration)'
- zipAfterPublish: false
-
- - task: VisualStudioTestPlatformInstaller@1
- inputs:
- packageFeedSelector: 'nugetOrg' # Options: nugetOrg, customFeed, netShare
- versionSelector: 'latestPreRelease' # Required when packageFeedSelector == NugetOrg || PackageFeedSelector == CustomFeed# Options: latestPreRelease, latestStable, specificVersion
- - task: VSTest@2
- inputs:
- testSelector: 'testAssemblies' # Options: testAssemblies, testPlan, testRun
- testAssemblyVer2: | # Required when testSelector == TestAssemblies
- **\bin\$(BuildConfiguration)\**\*tests.dll
- **\bin\$(BuildConfiguration)\**\*test.dll
- !**\obj\**
- !**\xunit.runner.visualstudio.testadapter.dll
- !**\xunit.runner.visualstudio.dotnetcore.testadapter.dll
- searchFolder: '$(System.DefaultWorkingDirectory)'
- runInParallel: True # Optional
- runTestsInIsolation: True # Optional
- codeCoverageEnabled: True # Optional
- configuration: 'Debug' # Optional
- publishRunAttachments: true # Optional
- testRunTitle: $(Agent.JobName)
- otherConsoleOptions: '/platform:x64 /Framework:.NETCoreApp,Version=v3.1 /logger:console;verbosity="normal"'
-
- - job: main_build_win
- displayName: Publish Windows
- pool:
- vmImage: windows-latest
- strategy:
- matrix:
- Release:
- BuildConfiguration: Release
- maxParallel: 2
- steps:
- - checkout: self
- clean: true
- submodules: true
- persistCredentials: true
-
- - task: CmdLine@2
- displayName: "Clone Web Client (Master, Release, or Tag)"
- condition: and(succeeded(), or(contains(variables['Build.SourceBranch'], 'release'), contains(variables['Build.SourceBranch'], 'master'), contains(variables['Build.SourceBranch'], 'tag')) ,eq(variables['BuildConfiguration'], 'Release'), in(variables['Build.Reason'], 'IndividualCI', 'BatchedCI', 'BuildCompletion'))
- inputs:
- script: 'git clone --single-branch --branch $(Build.SourceBranchName) --depth=1 https://github.com/jellyfin/jellyfin-web.git $(Agent.TempDirectory)/jellyfin-web'
-
- - task: CmdLine@2
- displayName: "Clone Web Client (PR)"
- condition: and(succeeded(), or(contains(variables['System.PullRequest.TargetBranch'], 'release'), contains(variables['System.PullRequest.TargetBranch'], 'master')) ,eq(variables['BuildConfiguration'], 'Release'), in(variables['Build.Reason'], 'PullRequest'))
- inputs:
- script: 'git clone --single-branch --branch $(System.PullRequest.TargetBranch) --depth 1 https://github.com/jellyfin/jellyfin-web.git $(Agent.TempDirectory)/jellyfin-web'
-
- - task: NodeTool@0
- displayName: 'Install Node'
- condition: and(succeeded(), or(contains(variables['System.PullRequest.TargetBranch'], 'release'), contains(variables['System.PullRequest.TargetBranch'], 'master'), contains(variables['Build.SourceBranch'], 'release'), contains(variables['Build.SourceBranch'], 'master')) ,eq(variables['BuildConfiguration'], 'Release'), in(variables['Build.Reason'], 'PullRequest', 'IndividualCI', 'BatchedCI', 'BuildCompletion'))
- inputs:
- versionSpec: '10.x'
-
- - task: CmdLine@2
- displayName: "Build Web Client"
- condition: and(succeeded(), or(contains(variables['System.PullRequest.TargetBranch'], 'release'), contains(variables['System.PullRequest.TargetBranch'], 'master'), contains(variables['Build.SourceBranch'], 'release'), contains(variables['Build.SourceBranch'], 'master')) ,eq(variables['BuildConfiguration'], 'Release'), in(variables['Build.Reason'], 'PullRequest', 'IndividualCI', 'BatchedCI', 'BuildCompletion'))
- inputs:
- script: yarn install
- workingDirectory: $(Agent.TempDirectory)/jellyfin-web
-
- - task: CopyFiles@2
- displayName: 'Copy Web Client'
- condition: and(succeeded(), or(contains(variables['System.PullRequest.TargetBranch'], 'release'), contains(variables['System.PullRequest.TargetBranch'], 'master'), contains(variables['Build.SourceBranch'], 'release'), contains(variables['Build.SourceBranch'], 'master')) ,eq(variables['BuildConfiguration'], 'Release'), in(variables['Build.Reason'], 'PullRequest', 'IndividualCI', 'BatchedCI', 'BuildCompletion'))
- inputs:
- sourceFolder: $(Agent.TempDirectory)/jellyfin-web/dist # Optional
- contents: '**'
- targetFolder: $(Build.SourcesDirectory)/MediaBrowser.WebDashboard/jellyfin-web
- cleanTargetFolder: true # Optional
- overWrite: true # Optional
- flattenFolders: false # Optional
-
- - task: CmdLine@2
- displayName: 'Clone UX Repository'
- inputs:
- script: git clone --depth=1 https://github.com/jellyfin/jellyfin-ux $(Agent.TempDirectory)\jellyfin-ux
-
- - task: PowerShell@2
- displayName: 'Build NSIS Installer'
- inputs:
- targetType: 'filePath' # Optional. Options: filePath, inline
- filePath: ./deployment/windows/build-jellyfin.ps1 # Required when targetType == FilePath
- arguments: -InstallFFMPEG -InstallNSSM -MakeNSIS -InstallTrayApp -UXLocation $(Agent.TempDirectory)\jellyfin-ux -InstallLocation $(build.artifactstagingdirectory)
- errorActionPreference: 'stop' # Optional. Options: stop, continue, silentlyContinue
- workingDirectory: $(Build.SourcesDirectory) # Optional
-
- - task: CopyFiles@2
- displayName: 'Copy NSIS Installer'
- inputs:
- sourceFolder: $(Build.SourcesDirectory)/deployment/windows/ # Optional
- contents: 'jellyfin*.exe'
- targetFolder: $(System.ArtifactsDirectory)/setup
- cleanTargetFolder: true # Optional
- overWrite: true # Optional
- flattenFolders: true # Optional
-
- - task: PublishPipelineArtifact@0
- displayName: 'Publish Artifact Setup'
- condition: and(eq(variables['BuildConfiguration'], 'Release'), succeeded())
- inputs:
- targetPath: '$(build.artifactstagingdirectory)/setup'
- artifactName: 'Jellyfin Server Setup'
-
- - job: dotnet_compat
- displayName: Compatibility Check
- pool:
- vmImage: ubuntu-latest
- dependsOn: main_build
- # only execute for pull requests
- condition: and(succeeded(), variables['System.PullRequest.PullRequestNumber'])
- strategy:
- matrix:
+ - template: azure-pipelines-main.yml
+ parameters:
+ LinuxImage: "ubuntu-latest"
+ RestoreBuildProjects: $(RestoreBuildProjects)
+
+ - template: azure-pipelines-test.yml
+ parameters:
+ ImageNames:
+ Linux: "ubuntu-latest"
+ Windows: "windows-latest"
+ macOS: "macos-latest"
+
+ - template: azure-pipelines-compat.yml
+ parameters:
+ Packages:
Naming:
NugetPackageName: Jellyfin.Naming
AssemblyFileName: Emby.Naming.dll
@@ -253,74 +42,4 @@ jobs:
Common:
NugetPackageName: Jellyfin.Common
AssemblyFileName: MediaBrowser.Common.dll
- maxParallel: 2
- steps:
- - checkout: none
-
- - task: UseDotNet@2
- displayName: 'Update DotNet'
- inputs:
- packageType: sdk
- version: 3.1.100
-
- - task: DownloadPipelineArtifact@2
- displayName: 'Download New Assembly Build Artifact'
- inputs:
- source: 'current' # Options: current, specific
- artifact: '$(NugetPackageName)' # Optional
- path: '$(System.ArtifactsDirectory)/new-artifacts'
- runVersion: 'latest' # Required when source == Specific. Options: latest, latestFromBranch, specific
-
- - task: CopyFiles@2
- displayName: 'Copy New Assembly Build Artifact'
- inputs:
- sourceFolder: $(System.ArtifactsDirectory)/new-artifacts # Optional
- contents: '**/*.dll'
- targetFolder: $(System.ArtifactsDirectory)/new-release
- cleanTargetFolder: true # Optional
- overWrite: true # Optional
- flattenFolders: true # Optional
-
- - task: DownloadPipelineArtifact@2
- displayName: 'Download Reference Assembly Build Artifact'
- inputs:
- source: 'specific' # Options: current, specific
- artifact: '$(NugetPackageName)' # Optional
- path: '$(System.ArtifactsDirectory)/current-artifacts'
- project: '$(System.TeamProjectId)' # Required when source == Specific
- pipeline: '$(System.DefinitionId)' # Required when source == Specific
- runVersion: 'latestFromBranch' # Required when source == Specific. Options: latest, latestFromBranch, specific
- runBranch: 'refs/heads/$(System.PullRequest.TargetBranch)' # Required when source == Specific && runVersion == LatestFromBranch
-
- - task: CopyFiles@2
- displayName: 'Copy Reference Assembly Build Artifact'
- inputs:
- sourceFolder: $(System.ArtifactsDirectory)/current-artifacts # Optional
- contents: '**/*.dll'
- targetFolder: $(System.ArtifactsDirectory)/current-release
- cleanTargetFolder: true # Optional
- overWrite: true # Optional
- flattenFolders: true # Optional
-
- - task: DownloadGitHubRelease@0
- displayName: 'Download ABI Compatibility Check Tool'
- inputs:
- connection: Jellyfin Release Download
- userRepository: EraYaN/dotnet-compatibility
- defaultVersionType: 'latest' # Options: latest, specificVersion, specificTag
- itemPattern: '**-ci.zip' # Optional
- downloadPath: '$(System.ArtifactsDirectory)'
-
- - task: ExtractFiles@1
- displayName: 'Extract ABI Compatibility Check Tool'
- inputs:
- archiveFilePatterns: '$(System.ArtifactsDirectory)/*-ci.zip'
- destinationFolder: $(System.ArtifactsDirectory)/tools
- cleanDestinationFolder: true
-
- # The `--warnings-only` switch will swallow the return code and not emit any errors.
- - task: CmdLine@2
- displayName: 'Execute ABI Compatibility Check Tool'
- inputs:
- script: 'dotnet tools/CompatibilityCheckerCLI.dll current-release/$(AssemblyFileName) new-release/$(AssemblyFileName) --azure-pipelines --warnings-only'
- workingDirectory: $(System.ArtifactsDirectory) # Optional
+ LinuxImage: "ubuntu-latest"
diff --git a/.ci/publish-nightly.yml b/.ci/publish-nightly.yml
deleted file mode 100644
index a693e10f6..000000000
--- a/.ci/publish-nightly.yml
+++ /dev/null
@@ -1,46 +0,0 @@
-name: Nightly-$(date:yyyyMMdd).$(rev:r)
-
-variables:
- - name: Version
- value: '1.0.0'
-
-trigger: none
-pr: none
-
-jobs:
- - job: publish_artifacts_nightly
- displayName: Publish Artifacts Nightly
- pool:
- vmImage: ubuntu-latest
- steps:
- - checkout: none
- - task: DownloadPipelineArtifact@2
- displayName: Download the Windows Setup Artifact
- inputs:
- source: 'specific' # Options: current, specific
- artifact: 'Jellyfin Server Setup' # Optional
- path: '$(System.ArtifactsDirectory)/win-installer'
- project: '$(System.TeamProjectId)' # Required when source == Specific
- pipelineId: 1 # Required when source == Specific
- runVersion: 'latestFromBranch' # Required when source == Specific. Options: latest, latestFromBranch, specific
- runBranch: 'refs/heads/master' # Required when source == Specific && runVersion == LatestFromBranch
-
- - task: SSH@0
- displayName: 'Create Drop directory'
- inputs:
- sshEndpoint: 'Jellyfin Build Server'
- commands: 'mkdir -p /srv/incoming/jellyfin_$(Version)/win-installer && ln -s /srv/incoming/jellyfin_$(Version) /srv/incoming/jellyfin_nightly_azure_upload'
-
- - task: CopyFilesOverSSH@0
- displayName: 'Copy the Windows Setup to the Repo'
- inputs:
- sshEndpoint: 'Jellyfin Build Server'
- sourceFolder: '$(System.ArtifactsDirectory)/win-installer'
- contents: 'jellyfin_*.exe'
- targetFolder: '/srv/incoming/jellyfin_nightly_azure_upload/win-installer'
-
- - task: SSH@0
- displayName: 'Clean up SCP symlink'
- inputs:
- sshEndpoint: 'Jellyfin Build Server'
- commands: 'rm -f /srv/incoming/jellyfin_nightly_azure_upload'
diff --git a/.ci/publish-release.yml b/.ci/publish-release.yml
deleted file mode 100644
index 57e77ae5a..000000000
--- a/.ci/publish-release.yml
+++ /dev/null
@@ -1,48 +0,0 @@
-name: Release-$(Version)-$(date:yyyyMMdd).$(rev:r)
-
-variables:
- - name: Version
- value: '1.0.0'
- - name: UsedRunId
- value: 0
-
-trigger: none
-pr: none
-
-jobs:
- - job: publish_artifacts_release
- displayName: Publish Artifacts Release
- pool:
- vmImage: ubuntu-latest
- steps:
- - checkout: none
- - task: DownloadPipelineArtifact@2
- displayName: Download the Windows Setup Artifact
- inputs:
- source: 'specific' # Options: current, specific
- artifact: 'Jellyfin Server Setup' # Optional
- path: '$(System.ArtifactsDirectory)/win-installer'
- project: '$(System.TeamProjectId)' # Required when source == Specific
- pipelineId: 1 # Required when source == Specific
- runVersion: 'specific' # Required when source == Specific. Options: latest, latestFromBranch, specific
- runId: $(UsedRunId)
-
- - task: SSH@0
- displayName: 'Create Drop directory'
- inputs:
- sshEndpoint: 'Jellyfin Build Server'
- commands: 'mkdir -p /srv/incoming/jellyfin_$(Version)/win-installer && ln -s /srv/incoming/jellyfin_$(Version) /srv/incoming/jellyfin_release_azure_upload'
-
- - task: CopyFilesOverSSH@0
- displayName: 'Copy the Windows Setup to the Repo'
- inputs:
- sshEndpoint: 'Jellyfin Build Server'
- sourceFolder: '$(System.ArtifactsDirectory)/win-installer'
- contents: 'jellyfin_*.exe'
- targetFolder: '/srv/incoming/jellyfin_release_azure_upload/win-installer'
-
- - task: SSH@0
- displayName: 'Clean up SCP symlink'
- inputs:
- sshEndpoint: 'Jellyfin Build Server'
- commands: 'rm -f /srv/incoming/jellyfin_release_azure_upload'
diff --git a/.copr/Makefile b/.copr/Makefile
index ba330ada9..ec3c90dfd 100644..120000
--- a/.copr/Makefile
+++ b/.copr/Makefile
@@ -1,59 +1 @@
-VERSION := $(shell sed -ne '/^Version:/s/.* *//p' \
- deployment/fedora-package-x64/pkg-src/jellyfin.spec)
-
-deployment/fedora-package-x64/pkg-src/jellyfin-web-$(VERSION).tar.gz:
- curl -f -L -o deployment/fedora-package-x64/pkg-src/jellyfin-web-$(VERSION).tar.gz \
- https://github.com/jellyfin/jellyfin-web/archive/v$(VERSION).tar.gz \
- || curl -f -L -o deployment/fedora-package-x64/pkg-src/jellyfin-web-$(VERSION).tar.gz \
- https://github.com/jellyfin/jellyfin-web/archive/master.tar.gz \
-
-srpm: deployment/fedora-package-x64/pkg-src/jellyfin-web-$(VERSION).tar.gz
- cd deployment/fedora-package-x64; \
- SOURCE_DIR=../.. \
- WORKDIR="$${PWD}"; \
- package_temporary_dir="$${WORKDIR}/pkg-dist-tmp"; \
- pkg_src_dir="$${WORKDIR}/pkg-src"; \
- GNU_TAR=1; \
- tar \
- --transform "s,^\.,jellyfin-$(VERSION)," \
- --exclude='.git*' \
- --exclude='**/.git' \
- --exclude='**/.hg' \
- --exclude='**/.vs' \
- --exclude='**/.vscode' \
- --exclude='deployment' \
- --exclude='**/bin' \
- --exclude='**/obj' \
- --exclude='**/.nuget' \
- --exclude='*.deb' \
- --exclude='*.rpm' \
- -czf "pkg-src/jellyfin-$(VERSION).tar.gz" \
- -C $${SOURCE_DIR} ./ || GNU_TAR=0; \
- if [ $${GNU_TAR} -eq 0 ]; then \
- package_temporary_dir="$$(mktemp -d)"; \
- mkdir -p "$${package_temporary_dir}/jellyfin"; \
- tar \
- --exclude='.git*' \
- --exclude='**/.git' \
- --exclude='**/.hg' \
- --exclude='**/.vs' \
- --exclude='**/.vscode' \
- --exclude='deployment' \
- --exclude='**/bin' \
- --exclude='**/obj' \
- --exclude='**/.nuget' \
- --exclude='*.deb' \
- --exclude='*.rpm' \
- -czf "$${package_temporary_dir}/jellyfin/jellyfin-$(VERSION).tar.gz" \
- -C $${SOURCE_DIR} ./; \
- mkdir -p "$${package_temporary_dir}/jellyfin-$(VERSION)"; \
- tar -xzf "$${package_temporary_dir}/jellyfin/jellyfin-$(VERSION).tar.gz" \
- -C "$${package_temporary_dir}/jellyfin-$(VERSION); \
- rm -f "$${package_temporary_dir}/jellyfin/jellyfin-$(VERSION).tar.gz"; \
- tar -czf "$${SOURCE_DIR}/SOURCES/pkg-src/jellyfin-$(VERSION).tar.gz" \
- -C "$${package_temporary_dir}" "jellyfin-$(VERSION); \
- rm -rf $${package_temporary_dir}; \
- fi; \
- rpmbuild -bs pkg-src/jellyfin.spec \
- --define "_sourcedir $$PWD/pkg-src/" \
- --define "_srcrpmdir $(outdir)"
+../fedora/Makefile \ No newline at end of file
diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS
new file mode 100644
index 000000000..e902dc712
--- /dev/null
+++ b/.github/CODEOWNERS
@@ -0,0 +1,3 @@
+# Joshua must review all changes to deployment and build.sh
+deployment/* @joshuaboniface
+build.sh @joshuaboniface
diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md
index bd13d4b00..d67e1c98b 100644
--- a/.github/ISSUE_TEMPLATE/bug_report.md
+++ b/.github/ISSUE_TEMPLATE/bug_report.md
@@ -10,6 +10,19 @@ assignees: ''
**Describe the bug**
<!-- A clear and concise description of what the bug is. -->
+**System (please complete the following information):**
+ - OS: [e.g. Debian, Windows]
+ - Virtualization: [e.g. Docker, KVM, LXC]
+ - Clients: [Browser, Android, Fire Stick, etc.]
+ - Browser: [e.g. Firefox 72, Chrome 80, Safari 13]
+ - Jellyfin Version: [e.g. 10.4.3, nightly 20191231]
+ - Playback: [Direct Play, Remux, Direct Stream, Transcode]
+ - Installed Plugins: [e.g. none, Fanart, Anime, etc.]
+ - Reverse Proxy: [e.g. none, nginx, apache, etc.]
+ - Base URL: [e.g. none, yes: /example]
+ - Networking: [e.g. Host, Bridge/NAT]
+ - Storage: [e.g. local, NFS, cloud]
+
**To Reproduce**
<!-- Steps to reproduce the behavior: -->
1. Go to '...'
@@ -26,12 +39,5 @@ assignees: ''
**Screenshots**
<!-- If applicable, add screenshots to help explain your problem. -->
-**System (please complete the following information):**
- - OS: [e.g. Docker, Debian, Windows]
- - Browser: [e.g. Firefox, Chrome, Safari]
- - Jellyfin Version: [e.g. 10.0.1]
- - Installed Plugins: [e.g. none, Fanart, Anime, etc.]
- - Reverse proxy: [e.g. no, nginx, apache, etc.]
-
**Additional context**
<!-- Add any other context about the problem here. -->
diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md
new file mode 100644
index 000000000..d886c6487
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/feature_request.md
@@ -0,0 +1,13 @@
+---
+name: Feature Request
+about: Request a new feature
+title: ''
+labels: feature-request
+assignees: ''
+---
+
+**PLEASE DO NOT OPEN FEATURE REQUEST ISSUES ON GITHUB**
+
+**Feature requests should be opened on our dedicated [feature request](https://features.jellyfin.org/) hub so they can be appropriately discussed and prioritized.**
+
+However, if you are willing to contribute to the project by adding a new feature yourself, then please ensure that you first review our [documentation](https://docs.jellyfin.org/general/contributing/development.html) on contributing code. Once you have reviewed the documentation, feel free to come back here and open an issue here outlining your proposed approach so that it can be documented, tracked, and discussed by other team members.
diff --git a/.github/ISSUE_TEMPLATE/media_playback.md b/.github/ISSUE_TEMPLATE/media_playback.md
index 93af33fbf..b51500f87 100644
--- a/.github/ISSUE_TEMPLATE/media_playback.md
+++ b/.github/ISSUE_TEMPLATE/media_playback.md
@@ -11,7 +11,10 @@ assignees: ''
<!-- Use the Media Info tool (set to text format, download here: https://mediaarea.net/en/MediaInfo) or copy the info from the web ui for the file with the playback issue. -->
**Logs**
-<!-- Please paste any log message from during the playback issue, for example the ffmpeg command line can be very useful. -->
+<!-- Please paste any log messages from during the playback issue. -->
+
+**FFmpeg Logs**
+<!-- Please paste any FFmpeg logs if remuxing or transcoding appears to be part of the issue. -->
**Stats for Nerds Screenshots**
<!-- If available, add screenshots of the stats for nerds screen to help show the issue problem. -->
@@ -29,4 +32,3 @@ assignees: ''
- Client: [e.g. Web/Browser, webOS, Android, Android TV, Electron]
- Browser (if Web client): [e.g. Firefox, Chrome, Safari]
- Client and Browser Version: [e.g. 10.3.4 and 68.0]
-
diff --git a/.gitignore b/.gitignore
index 42243f01a..46f036ad9 100644
--- a/.gitignore
+++ b/.gitignore
@@ -39,6 +39,7 @@ ProgramData*/
CorePlugins*/
ProgramData-Server*/
ProgramData-UI*/
+MediaBrowser.WebDashboard/jellyfin-web/**
#################
## Visual Studio
@@ -244,14 +245,14 @@ pip-log.txt
#########################
# Artifacts for debian-x64
-deployment/debian-package-x64/pkg-src/.debhelper/
-deployment/debian-package-x64/pkg-src/*.debhelper
-deployment/debian-package-x64/pkg-src/debhelper-build-stamp
-deployment/debian-package-x64/pkg-src/files
-deployment/debian-package-x64/pkg-src/jellyfin.substvars
-deployment/debian-package-x64/pkg-src/jellyfin/
+debian/.debhelper/
+debian/*.debhelper
+debian/debhelper-build-stamp
+debian/files
+debian/jellyfin.substvars
+debian/jellyfin/
# Don't ignore the debian/bin folder
-!deployment/debian-package-x64/pkg-src/bin/
+!debian/bin/
deployment/**/dist/
deployment/**/pkg-dist/
@@ -271,3 +272,8 @@ dist
# BenchmarkDotNet artifacts
BenchmarkDotNet.Artifacts
+
+# Ignore web artifacts from native builds
+web/
+web-src.*
+MediaBrowser.WebDashboard/jellyfin-web/
diff --git a/.vscode/extensions.json b/.vscode/extensions.json
new file mode 100644
index 000000000..59d9452fe
--- /dev/null
+++ b/.vscode/extensions.json
@@ -0,0 +1,14 @@
+{
+ // See https://go.microsoft.com/fwlink/?LinkId=827846 to learn about workspace recommendations.
+ // Extension identifier format: ${publisher}.${name}. Example: vscode.csharp
+
+ // List of extensions which should be recommended for users of this workspace.
+ "recommendations": [
+ "ms-dotnettools.csharp",
+ "editorconfig.editorconfig"
+ ],
+ // List of extensions recommended by VS Code that should not be recommended for users of this workspace.
+ "unwantedRecommendations": [
+
+ ]
+}
diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md
index 458944778..ce956176e 100644
--- a/CONTRIBUTORS.md
+++ b/CONTRIBUTORS.md
@@ -1,37 +1,135 @@
# Jellyfin Contributors
- - [JoshuaBoniface](https://github.com/joshuaboniface)
- - [nvllsvm](https://github.com/nvllsvm)
- - [JustAMan](https://github.com/JustAMan)
+ - [97carmine](https://github.com/97carmine)
+ - [Abbe98](https://github.com/Abbe98)
+ - [agrenott](https://github.com/agrenott)
+ - [AndreCarvalho](https://github.com/AndreCarvalho)
+ - [anthonylavado](https://github.com/anthonylavado)
+ - [Artiume](https://github.com/Artiume)
+ - [AThomsen](https://github.com/AThomsen)
+ - [bilde2910](https://github.com/bilde2910)
+ - [bfayers](https://github.com/bfayers)
+ - [BnMcG](https://github.com/BnMcG)
+ - [Bond-009](https://github.com/Bond-009)
+ - [brianjmurrell](https://github.com/brianjmurrell)
+ - [bugfixin](https://github.com/bugfixin)
+ - [chaosinnovator](https://github.com/chaosinnovator)
+ - [ckcr4lyf](https://github.com/ckcr4lyf)
+ - [crankdoofus](https://github.com/crankdoofus)
+ - [crobibero](https://github.com/crobibero)
+ - [cromefire](https://github.com/cromefire)
+ - [cryptobank](https://github.com/cryptobank)
+ - [cvium](https://github.com/cvium)
+ - [dannymichel](https://github.com/dannymichel)
+ - [DaveChild](https://github.com/DaveChild)
+ - [Delgan](https://github.com/Delgan)
- [dcrdev](https://github.com/dcrdev)
+ - [dhartung](https://github.com/dhartung)
+ - [dinki](https://github.com/dinki)
+ - [dkanada](https://github.com/dkanada)
+ - [dlahoti](https://github.com/dlahoti)
+ - [dmitrylyzo](https://github.com/dmitrylyzo)
+ - [DMouse10462](https://github.com/DMouse10462)
+ - [DrPandemic](https://github.com/DrPandemic)
- [EraYaN](https://github.com/EraYaN)
+ - [escabe](https://github.com/escabe)
+ - [excelite](https://github.com/excelite)
+ - [fasheng](https://github.com/fasheng)
+ - [ferferga](https://github.com/ferferga)
+ - [fhriley](https://github.com/fhriley)
- [flemse](https://github.com/flemse)
- - [bfayers](https://github.com/bfayers)
- - [Bond_009](https://github.com/Bond-009)
- - [AnthonyLavado](https://github.com/anthonylavado)
- - [sparky8251](https://github.com/sparky8251)
- - [LeoVerto](https://github.com/LeoVerto)
+ - [Froghut](https://github.com/Froghut)
+ - [fruhnow](https://github.com/fruhnow)
+ - [geilername](https://github.com/geilername)
+ - [gnattu](https://github.com/gnattu)
- [grafixeyehero](https://github.com/grafixeyehero)
- - [cvium](https://github.com/cvium)
- - [wtayl0r](https://github.com/wtayl0r)
- - [TtheCreator](https://github.com/Tthecreator)
- - [dkanada](https://github.com/dkanada)
- - [LogicalPhallacy](https://github.com/LogicalPhallacy/)
- - [RazeLighter777](https://github.com/RazeLighter777)
- - [WillWill56](https://github.com/WillWill56)
+ - [h1nk](https://github.com/h1nk)
+ - [hawken93](https://github.com/hawken93)
+ - [HelloWorld017](https://github.com/HelloWorld017)
+ - [jftuga](https://github.com/jftuga)
+ - [joern-h](https://github.com/joern-h)
+ - [joshuaboniface](https://github.com/joshuaboniface)
+ - [JustAMan](https://github.com/JustAMan)
+ - [justinfenn](https://github.com/justinfenn)
+ - [KerryRJ](https://github.com/KerryRJ)
+ - [Larvitar](https://github.com/Larvitar)
+ - [LeoVerto](https://github.com/LeoVerto)
- [Liggy](https://github.com/Liggy)
- - [fruhnow](https://github.com/fruhnow)
+ - [LogicalPhallacy](https://github.com/LogicalPhallacy)
+ - [loli10K](https://github.com/loli10K)
+ - [lostmypillow](https://github.com/lostmypillow)
- [Lynxy](https://github.com/Lynxy)
- - [fasheng](https://github.com/fasheng)
- - [ploughpuff](https://github.com/ploughpuff)
- - [pjeanjean](https://github.com/pjeanjean)
- - [DrPandemic](https://github.com/drpandemic)
- - [joern-h](https://github.com/joern-h)
- - [Khinenw](https://github.com/HelloWorld017)
- - [fhriley](https://github.com/fhriley)
- - [nevado](https://github.com/nevado)
+ - [ManfredRichthofen](https://github.com/ManfredRichthofen)
+ - [Marenz](https://github.com/Marenz)
+ - [marius-luca-87](https://github.com/marius-luca-87)
- [mark-monteiro](https://github.com/mark-monteiro)
- - [ullmie02](https://github.com/ullmie02)
+ - [Matt07211](https://github.com/Matt07211)
+ - [mcarlton00](https://github.com/mcarlton00)
+ - [mitchfizz05](https://github.com/mitchfizz05)
+ - [MrTimscampi](https://github.com/MrTimscampi)
+ - [n8225](https://github.com/n8225)
+ - [Narfinger](https://github.com/Narfinger)
+ - [NathanPickard](https://github.com/NathanPickard)
+ - [neilsb](https://github.com/neilsb)
+ - [nevado](https://github.com/nevado)
+ - [Nickbert7](https://github.com/Nickbert7)
+ - [nvllsvm](https://github.com/nvllsvm)
+ - [nyanmisaka](https://github.com/nyanmisaka)
+ - [oddstr13](https://github.com/oddstr13)
+ - [petermcneil](https://github.com/petermcneil)
+ - [Phlogi](https://github.com/Phlogi)
+ - [pjeanjean](https://github.com/pjeanjean)
+ - [ploughpuff](https://github.com/ploughpuff)
+ - [pR0Ps](https://github.com/pR0Ps)
+ - [PrplHaz4](https://github.com/PrplHaz4)
+ - [RazeLighter777](https://github.com/RazeLighter777)
+ - [redSpoutnik](https://github.com/redSpoutnik)
+ - [ringmatter](https://github.com/ringmatter)
+ - [ryan-hartzell](https://github.com/ryan-hartzell)
+ - [s0urcelab](https://github.com/s0urcelab)
+ - [sachk](https://github.com/sachk)
+ - [sammyrc34](https://github.com/sammyrc34)
+ - [samuel9554](https://github.com/samuel9554)
+ - [scheidleon](https://github.com/scheidleon)
+ - [sebPomme](https://github.com/sebPomme)
+ - [SegiH](https://github.com/SegiH)
+ - [SenorSmartyPants](https://github.com/SenorSmartyPants)
+ - [shemanaev](https://github.com/shemanaev)
+ - [skaro13](https://github.com/skaro13)
+ - [sl1288](https://github.com/sl1288)
+ - [sorinyo2004](https://github.com/sorinyo2004)
+ - [sparky8251](https://github.com/sparky8251)
+ - [stanionascu](https://github.com/stanionascu)
+ - [stevehayles](https://github.com/stevehayles)
+ - [SuperSandro2000](https://github.com/SuperSandro2000)
+ - [tbraeutigam](https://github.com/tbraeutigam)
+ - [teacupx](https://github.com/teacupx)
+ - [Terror-Gene](https://github.com/Terror-Gene)
+ - [ThatNerdyPikachu](https://github.com/ThatNerdyPikachu)
+ - [ThibaultNocchi](https://github.com/ThibaultNocchi)
+ - [thornbill](https://github.com/thornbill)
+ - [ThreeFive-O](https://github.com/ThreeFive-O)
+ - [TrisMcC](https://github.com/TrisMcC)
+ - [trumblejoe](https://github.com/trumblejoe)
+ - [TtheCreator](https://github.com/TtheCreator)
+ - [twinkybot](https://github.com/twinkybot)
+ - [Ullmie02](https://github.com/Ullmie02)
+ - [Unhelpful](https://github.com/Unhelpful)
+ - [viaregio](https://github.com/viaregio)
+ - [vitorsemeano](https://github.com/vitorsemeano)
+ - [voodoos](https://github.com/voodoos)
+ - [whooo](https://github.com/whooo)
+ - [WiiPlayer2](https://github.com/WiiPlayer2)
+ - [WillWill56](https://github.com/WillWill56)
+ - [wtayl0r](https://github.com/wtayl0r)
+ - [Wuerfelbecher](https://github.com/Wuerfelbecher)
+ - [Wunax](https://github.com/Wunax)
+ - [WWWesten](https://github.com/WWWesten)
+ - [WX9yMOXWId](https://github.com/WX9yMOXWId)
+ - [xosdy](https://github.com/xosdy)
+ - [XVicarious](https://github.com/XVicarious)
+ - [YouKnowBlom](https://github.com/YouKnowBlom)
+ - [KristupasSavickas](https://github.com/KristupasSavickas)
# Emby Contributors
diff --git a/Dockerfile b/Dockerfile
index 53a425262..6e834d4e0 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -1,48 +1,60 @@
ARG DOTNET_VERSION=3.1
-ARG FFMPEG_VERSION=latest
FROM node:alpine as web-builder
ARG JELLYFIN_WEB_VERSION=master
-RUN apk add curl \
+RUN apk add curl git zlib zlib-dev autoconf g++ make libpng-dev gifsicle alpine-sdk automake libtool make gcc musl-dev nasm \
&& curl -L https://github.com/jellyfin/jellyfin-web/archive/${JELLYFIN_WEB_VERSION}.tar.gz | tar zxf - \
&& cd jellyfin-web-* \
&& yarn install \
- && yarn build \
&& mv dist /dist
FROM mcr.microsoft.com/dotnet/core/sdk:${DOTNET_VERSION}-buster as builder
WORKDIR /repo
COPY . .
ENV DOTNET_CLI_TELEMETRY_OPTOUT=1
-RUN dotnet publish Jellyfin.Server --configuration Release --output="/jellyfin" --self-contained --runtime linux-x64 "-p:GenerateDocumentationFile=false;DebugSymbols=false;DebugType=none"
+# because of changes in docker and systemd we need to not build in parallel at the moment
+# see https://success.docker.com/article/how-to-reserve-resource-temporarily-unavailable-errors-due-to-tasksmax-setting
+RUN dotnet publish Jellyfin.Server --disable-parallel --configuration Release --output="/jellyfin" --self-contained --runtime linux-x64 "-p:GenerateDocumentationFile=false;DebugSymbols=false;DebugType=none"
-FROM jellyfin/ffmpeg:${FFMPEG_VERSION} as ffmpeg
FROM debian:buster-slim
-COPY --from=ffmpeg /opt/ffmpeg /opt/ffmpeg
+# https://askubuntu.com/questions/972516/debian-frontend-environment-variable
+ARG DEBIAN_FRONTEND="noninteractive"
+# http://stackoverflow.com/questions/48162574/ddg#49462622
+ARG APT_KEY_DONT_WARN_ON_DANGEROUS_USAGE=DontWarn
+# https://github.com/NVIDIA/nvidia-docker/wiki/Installation-(Native-GPU-Support)
+ENV NVIDIA_DRIVER_CAPABILITIES="compute,video,utility"
+
COPY --from=builder /jellyfin /jellyfin
COPY --from=web-builder /dist /jellyfin/jellyfin-web
# Install dependencies:
-# libfontconfig1: needed for Skia
-# libgomp1: needed for ffmpeg
-# libva-drm2: needed for ffmpeg
-# mesa-va-drivers: needed for VAAPI
+# mesa-va-drivers: needed for AMD VAAPI
RUN apt-get update \
+ && apt-get install --no-install-recommends --no-install-suggests -y ca-certificates gnupg wget apt-transport-https \
+ && wget -O - https://repo.jellyfin.org/jellyfin_team.gpg.key | apt-key add - \
+ && echo "deb [arch=$( dpkg --print-architecture )] https://repo.jellyfin.org/$( awk -F'=' '/^ID=/{ print $NF }' /etc/os-release ) $( awk -F'=' '/^VERSION_CODENAME=/{ print $NF }' /etc/os-release ) main" | tee /etc/apt/sources.list.d/jellyfin.list \
+ && apt-get update \
&& apt-get install --no-install-recommends --no-install-suggests -y \
- libfontconfig1 libgomp1 libva-drm2 mesa-va-drivers openssl \
- && apt-get clean autoclean \
- && apt-get autoremove \
+ mesa-va-drivers \
+ jellyfin-ffmpeg \
+ openssl \
+ locales \
+ && apt-get remove gnupg wget apt-transport-https -y \
+ && apt-get clean autoclean -y \
+ && apt-get autoremove -y \
&& rm -rf /var/lib/apt/lists/* \
&& mkdir -p /cache /config /media \
&& chmod 777 /cache /config /media \
- && ln -s /opt/ffmpeg/bin/ffmpeg /usr/local/bin \
- && ln -s /opt/ffmpeg/bin/ffprobe /usr/local/bin
+ && sed -i -e 's/# en_US.UTF-8 UTF-8/en_US.UTF-8 UTF-8/' /etc/locale.gen && locale-gen
ENV DOTNET_SYSTEM_GLOBALIZATION_INVARIANT=1
+ENV LC_ALL en_US.UTF-8
+ENV LANG en_US.UTF-8
+ENV LANGUAGE en_US:en
EXPOSE 8096
VOLUME /cache /config /media
ENTRYPOINT ["./jellyfin/jellyfin", \
"--datadir", "/config", \
"--cachedir", "/cache", \
- "--ffmpeg", "/usr/local/bin/ffmpeg"]
+ "--ffmpeg", "/usr/lib/jellyfin-ffmpeg/ffmpeg"]
diff --git a/Dockerfile.arm b/Dockerfile.arm
index 4d8fbb77d..39beaa479 100644
--- a/Dockerfile.arm
+++ b/Dockerfile.arm
@@ -1,3 +1,5 @@
+# DESIGNED FOR BUILDING ON AMD64 ONLY
+#####################################
# Requires binfm_misc registration
# https://github.com/multiarch/qemu-user-static#binfmt_misc-register
ARG DOTNET_VERSION=3.1
@@ -5,11 +7,10 @@ ARG DOTNET_VERSION=3.1
FROM node:alpine as web-builder
ARG JELLYFIN_WEB_VERSION=master
-RUN apk add curl \
+RUN apk add curl git zlib zlib-dev autoconf g++ make libpng-dev gifsicle alpine-sdk automake libtool make gcc musl-dev nasm python \
&& curl -L https://github.com/jellyfin/jellyfin-web/archive/${JELLYFIN_WEB_VERSION}.tar.gz | tar zxf - \
&& cd jellyfin-web-* \
&& yarn install \
- && yarn build \
&& mv dist /dist
@@ -24,21 +25,53 @@ RUN dotnet publish Jellyfin.Server --configuration Release --output="/jellyfin"
FROM multiarch/qemu-user-static:x86_64-arm as qemu
-FROM debian:stretch-slim-arm32v7
+FROM arm32v7/debian:buster-slim
+
+# https://askubuntu.com/questions/972516/debian-frontend-environment-variable
+ARG DEBIAN_FRONTEND="noninteractive"
+# http://stackoverflow.com/questions/48162574/ddg#49462622
+ARG APT_KEY_DONT_WARN_ON_DANGEROUS_USAGE=DontWarn
+# https://github.com/NVIDIA/nvidia-docker/wiki/Installation-(Native-GPU-Support)
+ENV NVIDIA_DRIVER_CAPABILITIES="compute,video,utility"
+
COPY --from=qemu /usr/bin/qemu-arm-static /usr/bin
RUN apt-get update \
- && apt-get install --no-install-recommends --no-install-suggests -y ffmpeg \
+ && apt-get install --no-install-recommends --no-install-suggests -y ca-certificates gnupg curl && \
+ curl -ks https://repo.jellyfin.org/debian/jellyfin_team.gpg.key | apt-key add - && \
+ curl -s https://keyserver.ubuntu.com/pks/lookup?op=get\&search=0x6587ffd6536b8826e88a62547876ae518cbcf2f2 | apt-key add - && \
+ echo 'deb [arch=armhf] https://repo.jellyfin.org/debian buster main' > /etc/apt/sources.list.d/jellyfin.list && \
+ echo "deb http://ppa.launchpad.net/ubuntu-raspi2/ppa/ubuntu bionic main">> /etc/apt/sources.list.d/raspbins.list && \
+ apt-get update && \
+ apt-get install --no-install-recommends --no-install-suggests -y \
+ jellyfin-ffmpeg \
+ libssl-dev \
+ libfontconfig1 \
+ libfreetype6 \
+ libomxil-bellagio0 \
+ libomxil-bellagio-bin \
+ libraspberrypi0 \
+ vainfo \
+ libva2 \
+ locales \
+ && apt-get remove curl gnupg -y \
+ && apt-get clean autoclean -y \
+ && apt-get autoremove -y \
&& rm -rf /var/lib/apt/lists/* \
&& mkdir -p /cache /config /media \
- && chmod 777 /cache /config /media
+ && chmod 777 /cache /config /media \
+ && sed -i -e 's/# en_US.UTF-8 UTF-8/en_US.UTF-8 UTF-8/' /etc/locale.gen && locale-gen
+
COPY --from=builder /jellyfin /jellyfin
COPY --from=web-builder /dist /jellyfin/jellyfin-web
ENV DOTNET_SYSTEM_GLOBALIZATION_INVARIANT=1
+ENV LC_ALL en_US.UTF-8
+ENV LANG en_US.UTF-8
+ENV LANGUAGE en_US:en
EXPOSE 8096
VOLUME /cache /config /media
ENTRYPOINT ["./jellyfin/jellyfin", \
"--datadir", "/config", \
"--cachedir", "/cache", \
- "--ffmpeg", "/usr/bin/ffmpeg"]
+ "--ffmpeg", "/usr/lib/jellyfin-ffmpeg/ffmpeg"]
diff --git a/Dockerfile.arm64 b/Dockerfile.arm64
index d41268f13..1a691b572 100644
--- a/Dockerfile.arm64
+++ b/Dockerfile.arm64
@@ -1,3 +1,5 @@
+# DESIGNED FOR BUILDING ON AMD64 ONLY
+#####################################
# Requires binfm_misc registration
# https://github.com/multiarch/qemu-user-static#binfmt_misc-register
ARG DOTNET_VERSION=3.1
@@ -5,11 +7,10 @@ ARG DOTNET_VERSION=3.1
FROM node:alpine as web-builder
ARG JELLYFIN_WEB_VERSION=master
-RUN apk add curl \
+RUN apk add curl git zlib zlib-dev autoconf g++ make libpng-dev gifsicle alpine-sdk automake libtool make gcc musl-dev nasm python \
&& curl -L https://github.com/jellyfin/jellyfin-web/archive/${JELLYFIN_WEB_VERSION}.tar.gz | tar zxf - \
&& cd jellyfin-web-* \
&& yarn install \
- && yarn build \
&& mv dist /dist
@@ -22,19 +23,40 @@ RUN find . -type d -name obj | xargs -r rm -r
# Build
RUN dotnet publish Jellyfin.Server --configuration Release --output="/jellyfin" --self-contained --runtime linux-arm64 "-p:GenerateDocumentationFile=false;DebugSymbols=false;DebugType=none"
-
FROM multiarch/qemu-user-static:x86_64-aarch64 as qemu
-FROM debian:stretch-slim-arm64v8
+FROM arm64v8/debian:buster-slim
+
+# https://askubuntu.com/questions/972516/debian-frontend-environment-variable
+ARG DEBIAN_FRONTEND="noninteractive"
+# http://stackoverflow.com/questions/48162574/ddg#49462622
+ARG APT_KEY_DONT_WARN_ON_DANGEROUS_USAGE=DontWarn
+# https://github.com/NVIDIA/nvidia-docker/wiki/Installation-(Native-GPU-Support)
+ENV NVIDIA_DRIVER_CAPABILITIES="compute,video,utility"
+
COPY --from=qemu /usr/bin/qemu-aarch64-static /usr/bin
-RUN apt-get update \
- && apt-get install --no-install-recommends --no-install-suggests -y ffmpeg \
+RUN apt-get update && apt-get install --no-install-recommends --no-install-suggests -y \
+ ffmpeg \
+ libssl-dev \
+ ca-certificates \
+ libfontconfig1 \
+ libfreetype6 \
+ libomxil-bellagio0 \
+ libomxil-bellagio-bin \
+ locales \
+ && apt-get clean autoclean -y \
+ && apt-get autoremove -y \
&& rm -rf /var/lib/apt/lists/* \
&& mkdir -p /cache /config /media \
- && chmod 777 /cache /config /media
+ && chmod 777 /cache /config /media \
+ && sed -i -e 's/# en_US.UTF-8 UTF-8/en_US.UTF-8 UTF-8/' /etc/locale.gen && locale-gen
+
COPY --from=builder /jellyfin /jellyfin
COPY --from=web-builder /dist /jellyfin/jellyfin-web
ENV DOTNET_SYSTEM_GLOBALIZATION_INVARIANT=1
+ENV LC_ALL en_US.UTF-8
+ENV LANG en_US.UTF-8
+ENV LANGUAGE en_US:en
EXPOSE 8096
VOLUME /cache /config /media
diff --git a/DvdLib/BigEndianBinaryReader.cs b/DvdLib/BigEndianBinaryReader.cs
index b3b2eabd5..473005b55 100644
--- a/DvdLib/BigEndianBinaryReader.cs
+++ b/DvdLib/BigEndianBinaryReader.cs
@@ -1,4 +1,4 @@
-using System;
+using System.Buffers.Binary;
using System.IO;
namespace DvdLib
@@ -12,19 +12,12 @@ namespace DvdLib
public override ushort ReadUInt16()
{
- return BitConverter.ToUInt16(ReadAndReverseBytes(2), 0);
+ return BinaryPrimitives.ReadUInt16BigEndian(base.ReadBytes(2));
}
public override uint ReadUInt32()
{
- return BitConverter.ToUInt32(ReadAndReverseBytes(4), 0);
- }
-
- private byte[] ReadAndReverseBytes(int count)
- {
- byte[] val = base.ReadBytes(count);
- Array.Reverse(val, 0, count);
- return val;
+ return BinaryPrimitives.ReadUInt32BigEndian(base.ReadBytes(4));
}
}
}
diff --git a/DvdLib/DvdLib.csproj b/DvdLib/DvdLib.csproj
index 9dbaa9e2f..fd0cb5e25 100644
--- a/DvdLib/DvdLib.csproj
+++ b/DvdLib/DvdLib.csproj
@@ -1,15 +1,16 @@
<Project Sdk="Microsoft.NET.Sdk">
- <ItemGroup>
- <Compile Include="..\SharedVersion.cs" />
- </ItemGroup>
+ <!-- ProjectGuid is only included as a requirement for SonarQube analysis -->
+ <PropertyGroup>
+ <ProjectGuid>{713F42B5-878E-499D-A878-E4C652B1D5E8}</ProjectGuid>
+ </PropertyGroup>
<ItemGroup>
- <ProjectReference Include="..\MediaBrowser.Model\MediaBrowser.Model.csproj" />
+ <Compile Include="..\SharedVersion.cs" />
</ItemGroup>
<PropertyGroup>
- <TargetFramework>netstandard2.0</TargetFramework>
+ <TargetFramework>netstandard2.1</TargetFramework>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
</PropertyGroup>
diff --git a/DvdLib/Ifo/Dvd.cs b/DvdLib/Ifo/Dvd.cs
index 90125fa3e..5af58a2dc 100644
--- a/DvdLib/Ifo/Dvd.cs
+++ b/DvdLib/Ifo/Dvd.cs
@@ -2,7 +2,6 @@ using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
-using MediaBrowser.Model.IO;
namespace DvdLib.Ifo
{
@@ -13,13 +12,10 @@ namespace DvdLib.Ifo
private ushort _titleCount;
public readonly Dictionary<ushort, string> VTSPaths = new Dictionary<ushort, string>();
- private readonly IFileSystem _fileSystem;
-
- public Dvd(string path, IFileSystem fileSystem)
+ public Dvd(string path)
{
- _fileSystem = fileSystem;
Titles = new List<Title>();
- var allFiles = _fileSystem.GetFiles(path, true).ToList();
+ var allFiles = new DirectoryInfo(path).GetFiles(path, SearchOption.AllDirectories);
var vmgPath = allFiles.FirstOrDefault(i => string.Equals(i.Name, "VIDEO_TS.IFO", StringComparison.OrdinalIgnoreCase)) ??
allFiles.FirstOrDefault(i => string.Equals(i.Name, "VIDEO_TS.BUP", StringComparison.OrdinalIgnoreCase));
@@ -33,7 +29,7 @@ namespace DvdLib.Ifo
continue;
}
- var nums = ifo.Name.Split(new [] { '_' }, StringSplitOptions.RemoveEmptyEntries);
+ var nums = ifo.Name.Split(new[] { '_' }, StringSplitOptions.RemoveEmptyEntries);
if (nums.Length >= 2 && ushort.TryParse(nums[1], out var ifoNumber))
{
ReadVTS(ifoNumber, ifo.FullName);
@@ -42,7 +38,7 @@ namespace DvdLib.Ifo
}
else
{
- using (var vmgFs = _fileSystem.GetFileStream(vmgPath.FullName, FileOpenMode.Open, FileAccessMode.Read, FileShareMode.Read))
+ using (var vmgFs = new FileStream(vmgPath.FullName, FileMode.Open, FileAccess.Read, FileShare.Read))
{
using (var vmgRead = new BigEndianBinaryReader(vmgFs))
{
@@ -76,7 +72,7 @@ namespace DvdLib.Ifo
}
}
- private void ReadVTS(ushort vtsNum, IEnumerable<FileSystemMetadata> allFiles)
+ private void ReadVTS(ushort vtsNum, IReadOnlyList<FileInfo> allFiles)
{
var filename = string.Format("VTS_{0:00}_0.IFO", vtsNum);
@@ -95,7 +91,7 @@ namespace DvdLib.Ifo
{
VTSPaths[vtsNum] = vtsPath;
- using (var vtsFs = _fileSystem.GetFileStream(vtsPath, FileOpenMode.Open, FileAccessMode.Read, FileShareMode.Read))
+ using (var vtsFs = new FileStream(vtsPath, FileMode.Open, FileAccess.Read, FileShare.Read))
{
using (var vtsRead = new BigEndianBinaryReader(vtsFs))
{
diff --git a/Emby.Dlna/Api/DlnaServerService.cs b/Emby.Dlna/Api/DlnaServerService.cs
index a451bbcf9..7fba2184a 100644
--- a/Emby.Dlna/Api/DlnaServerService.cs
+++ b/Emby.Dlna/Api/DlnaServerService.cs
@@ -1,4 +1,7 @@
+#pragma warning disable CS1591
+
using System;
+using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Text;
using System.Threading.Tasks;
@@ -149,6 +152,7 @@ namespace Emby.Dlna.Api
return _resultFactory.GetStaticResult(Request, cacheKey, null, cacheLength, XMLContentType, () => Task.FromResult<Stream>(new MemoryStream(bytes)));
}
+ [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "request", Justification = "Required for ServiceStack")]
public object Get(GetContentDirectory request)
{
var xml = ContentDirectory.GetServiceXml();
@@ -156,6 +160,7 @@ namespace Emby.Dlna.Api
return _resultFactory.GetResult(Request, xml, XMLContentType);
}
+ [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "request", Justification = "Required for ServiceStack")]
public object Get(GetMediaReceiverRegistrar request)
{
var xml = MediaReceiverRegistrar.GetServiceXml();
@@ -163,6 +168,7 @@ namespace Emby.Dlna.Api
return _resultFactory.GetResult(Request, xml, XMLContentType);
}
+ [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "request", Justification = "Required for ServiceStack")]
public object Get(GetConnnectionManager request)
{
var xml = ConnectionManager.GetServiceXml();
@@ -170,32 +176,32 @@ namespace Emby.Dlna.Api
return _resultFactory.GetResult(Request, xml, XMLContentType);
}
- public object Post(ProcessMediaReceiverRegistrarControlRequest request)
+ public async Task<object> Post(ProcessMediaReceiverRegistrarControlRequest request)
{
- var response = PostAsync(request.RequestStream, MediaReceiverRegistrar);
+ var response = await PostAsync(request.RequestStream, MediaReceiverRegistrar).ConfigureAwait(false);
return _resultFactory.GetResult(Request, response.Xml, XMLContentType);
}
- public object Post(ProcessContentDirectoryControlRequest request)
+ public async Task<object> Post(ProcessContentDirectoryControlRequest request)
{
- var response = PostAsync(request.RequestStream, ContentDirectory);
+ var response = await PostAsync(request.RequestStream, ContentDirectory).ConfigureAwait(false);
return _resultFactory.GetResult(Request, response.Xml, XMLContentType);
}
- public object Post(ProcessConnectionManagerControlRequest request)
+ public async Task<object> Post(ProcessConnectionManagerControlRequest request)
{
- var response = PostAsync(request.RequestStream, ConnectionManager);
+ var response = await PostAsync(request.RequestStream, ConnectionManager).ConfigureAwait(false);
return _resultFactory.GetResult(Request, response.Xml, XMLContentType);
}
- private ControlResponse PostAsync(Stream requestStream, IUpnpService service)
+ private Task<ControlResponse> PostAsync(Stream requestStream, IUpnpService service)
{
var id = GetPathValue(2).ToString();
- return service.ProcessControlRequest(new ControlRequest
+ return service.ProcessControlRequestAsync(new ControlRequest
{
Headers = Request.Headers,
InputXml = requestStream,
@@ -311,31 +317,37 @@ namespace Emby.Dlna.Api
return _resultFactory.GetStaticResult(Request, cacheKey, null, cacheLength, contentType, () => Task.FromResult(_dlnaManager.GetIcon(request.Filename).Stream));
}
+ [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "request", Justification = "Required for ServiceStack")]
public object Subscribe(ProcessContentDirectoryEventRequest request)
{
return ProcessEventRequest(ContentDirectory);
}
+ [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "request", Justification = "Required for ServiceStack")]
public object Subscribe(ProcessConnectionManagerEventRequest request)
{
return ProcessEventRequest(ConnectionManager);
}
+ [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "request", Justification = "Required for ServiceStack")]
public object Subscribe(ProcessMediaReceiverRegistrarEventRequest request)
{
return ProcessEventRequest(MediaReceiverRegistrar);
}
+ [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "request", Justification = "Required for ServiceStack")]
public object Unsubscribe(ProcessContentDirectoryEventRequest request)
{
return ProcessEventRequest(ContentDirectory);
}
+ [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "request", Justification = "Required for ServiceStack")]
public object Unsubscribe(ProcessConnectionManagerEventRequest request)
{
return ProcessEventRequest(ConnectionManager);
}
+ [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "request", Justification = "Required for ServiceStack")]
public object Unsubscribe(ProcessMediaReceiverRegistrarEventRequest request)
{
return ProcessEventRequest(MediaReceiverRegistrar);
diff --git a/Emby.Dlna/Api/DlnaService.cs b/Emby.Dlna/Api/DlnaService.cs
index 7f51f477a..5f984bb33 100644
--- a/Emby.Dlna/Api/DlnaService.cs
+++ b/Emby.Dlna/Api/DlnaService.cs
@@ -1,3 +1,6 @@
+#pragma warning disable CS1591
+
+using System.Diagnostics.CodeAnalysis;
using System.Linq;
using MediaBrowser.Controller.Dlna;
using MediaBrowser.Controller.Net;
@@ -50,6 +53,7 @@ namespace Emby.Dlna.Api
_dlnaManager = dlnaManager;
}
+ [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "request", Justification = "Required for ServiceStack")]
public object Get(GetProfileInfos request)
{
return _dlnaManager.GetProfileInfos().ToArray();
@@ -60,6 +64,7 @@ namespace Emby.Dlna.Api
return _dlnaManager.GetProfile(request.Id);
}
+ [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "request", Justification = "Required for ServiceStack")]
public object Get(GetDefaultProfile request)
{
return _dlnaManager.GetDefaultProfile();
diff --git a/Emby.Dlna/Common/Argument.cs b/Emby.Dlna/Common/Argument.cs
index 3e325c41c..f375e6049 100644
--- a/Emby.Dlna/Common/Argument.cs
+++ b/Emby.Dlna/Common/Argument.cs
@@ -1,3 +1,4 @@
+#pragma warning disable CS1591
namespace Emby.Dlna.Common
{
diff --git a/Emby.Dlna/Common/DeviceIcon.cs b/Emby.Dlna/Common/DeviceIcon.cs
index 3a91b952e..c3f7fa8aa 100644
--- a/Emby.Dlna/Common/DeviceIcon.cs
+++ b/Emby.Dlna/Common/DeviceIcon.cs
@@ -1,3 +1,6 @@
+#pragma warning disable CS1591
+
+using System.Globalization;
namespace Emby.Dlna.Common
{
@@ -13,9 +16,14 @@ namespace Emby.Dlna.Common
public string Depth { get; set; }
+ /// <inheritdoc />
public override string ToString()
{
- return string.Format("{0}x{1}", Height, Width);
+ return string.Format(
+ CultureInfo.InvariantCulture,
+ "{0}x{1}",
+ Height,
+ Width);
}
}
}
diff --git a/Emby.Dlna/Common/DeviceService.cs b/Emby.Dlna/Common/DeviceService.cs
index c60d65291..44c0a0412 100644
--- a/Emby.Dlna/Common/DeviceService.cs
+++ b/Emby.Dlna/Common/DeviceService.cs
@@ -1,3 +1,4 @@
+#pragma warning disable CS1591
namespace Emby.Dlna.Common
{
@@ -13,9 +14,8 @@ namespace Emby.Dlna.Common
public string EventSubUrl { get; set; }
+ /// <inheritdoc />
public override string ToString()
- {
- return string.Format("{0}", ServiceId);
- }
+ => ServiceId;
}
}
diff --git a/Emby.Dlna/Common/ServiceAction.cs b/Emby.Dlna/Common/ServiceAction.cs
index 5e030d396..db4f27063 100644
--- a/Emby.Dlna/Common/ServiceAction.cs
+++ b/Emby.Dlna/Common/ServiceAction.cs
@@ -1,21 +1,24 @@
+#pragma warning disable CS1591
+
using System.Collections.Generic;
namespace Emby.Dlna.Common
{
public class ServiceAction
{
+ public ServiceAction()
+ {
+ ArgumentList = new List<Argument>();
+ }
+
public string Name { get; set; }
public List<Argument> ArgumentList { get; set; }
+ /// <inheritdoc />
public override string ToString()
{
return Name;
}
-
- public ServiceAction()
- {
- ArgumentList = new List<Argument>();
- }
}
}
diff --git a/Emby.Dlna/Common/StateVariable.cs b/Emby.Dlna/Common/StateVariable.cs
index 4ca84bf51..a2c2bf5dd 100644
--- a/Emby.Dlna/Common/StateVariable.cs
+++ b/Emby.Dlna/Common/StateVariable.cs
@@ -1,9 +1,16 @@
+#pragma warning disable CS1591
+
using System;
namespace Emby.Dlna.Common
{
public class StateVariable
{
+ public StateVariable()
+ {
+ AllowedValues = Array.Empty<string>();
+ }
+
public string Name { get; set; }
public string DataType { get; set; }
@@ -12,14 +19,8 @@ namespace Emby.Dlna.Common
public string[] AllowedValues { get; set; }
+ /// <inheritdoc />
public override string ToString()
- {
- return Name;
- }
-
- public StateVariable()
- {
- AllowedValues = Array.Empty<string>();
- }
+ => Name;
}
}
diff --git a/Emby.Dlna/Configuration/DlnaOptions.cs b/Emby.Dlna/Configuration/DlnaOptions.cs
index c7cb364a8..6dd9a445a 100644
--- a/Emby.Dlna/Configuration/DlnaOptions.cs
+++ b/Emby.Dlna/Configuration/DlnaOptions.cs
@@ -1,17 +1,9 @@
+#pragma warning disable CS1591
namespace Emby.Dlna.Configuration
{
public class DlnaOptions
{
- public bool EnablePlayTo { get; set; }
- public bool EnableServer { get; set; }
- public bool EnableDebugLog { get; set; }
- public bool BlastAliveMessages { get; set; }
- public bool SendOnlyMatchedHost { get; set; }
- public int ClientDiscoveryIntervalSeconds { get; set; }
- public int BlastAliveMessageIntervalSeconds { get; set; }
- public string DefaultUserId { get; set; }
-
public DlnaOptions()
{
EnablePlayTo = true;
@@ -21,5 +13,21 @@ namespace Emby.Dlna.Configuration
ClientDiscoveryIntervalSeconds = 60;
BlastAliveMessageIntervalSeconds = 1800;
}
+
+ public bool EnablePlayTo { get; set; }
+
+ public bool EnableServer { get; set; }
+
+ public bool EnableDebugLog { get; set; }
+
+ public bool BlastAliveMessages { get; set; }
+
+ public bool SendOnlyMatchedHost { get; set; }
+
+ public int ClientDiscoveryIntervalSeconds { get; set; }
+
+ public int BlastAliveMessageIntervalSeconds { get; set; }
+
+ public string DefaultUserId { get; set; }
}
}
diff --git a/Emby.Dlna/ConfigurationExtension.cs b/Emby.Dlna/ConfigurationExtension.cs
index 82d726e01..dba901967 100644
--- a/Emby.Dlna/ConfigurationExtension.cs
+++ b/Emby.Dlna/ConfigurationExtension.cs
@@ -1,3 +1,6 @@
+#nullable enable
+#pragma warning disable CS1591
+
using System.Collections.Generic;
using Emby.Dlna.Configuration;
using MediaBrowser.Common.Configuration;
diff --git a/Emby.Dlna/ConnectionManager/ConnectionManager.cs b/Emby.Dlna/ConnectionManager/ConnectionManager.cs
index 83011fbab..e32cc11bf 100644
--- a/Emby.Dlna/ConnectionManager/ConnectionManager.cs
+++ b/Emby.Dlna/ConnectionManager/ConnectionManager.cs
@@ -1,3 +1,6 @@
+#pragma warning disable CS1591
+
+using System.Threading.Tasks;
using Emby.Dlna.Service;
using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Configuration;
@@ -12,7 +15,11 @@ namespace Emby.Dlna.ConnectionManager
private readonly ILogger _logger;
private readonly IServerConfigurationManager _config;
- public ConnectionManager(IDlnaManager dlna, IServerConfigurationManager config, ILogger logger, IHttpClient httpClient)
+ public ConnectionManager(
+ IDlnaManager dlna,
+ IServerConfigurationManager config,
+ ILogger<ConnectionManager> logger,
+ IHttpClient httpClient)
: base(logger, httpClient)
{
_dlna = dlna;
@@ -20,17 +27,19 @@ namespace Emby.Dlna.ConnectionManager
_logger = logger;
}
+ /// <inheritdoc />
public string GetServiceXml()
{
return new ConnectionManagerXmlBuilder().GetXml();
}
- public ControlResponse ProcessControlRequest(ControlRequest request)
+ /// <inheritdoc />
+ public Task<ControlResponse> ProcessControlRequestAsync(ControlRequest request)
{
var profile = _dlna.GetProfile(request.Headers) ??
_dlna.GetDefaultProfile();
- return new ControlHandler(_config, _logger, profile).ProcessControlRequest(request);
+ return new ControlHandler(_config, _logger, profile).ProcessControlRequestAsync(request);
}
}
}
diff --git a/Emby.Dlna/ConnectionManager/ConnectionManagerXmlBuilder.cs b/Emby.Dlna/ConnectionManager/ConnectionManagerXmlBuilder.cs
index f5873455a..b31d437c3 100644
--- a/Emby.Dlna/ConnectionManager/ConnectionManagerXmlBuilder.cs
+++ b/Emby.Dlna/ConnectionManager/ConnectionManagerXmlBuilder.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
using System.Collections.Generic;
using Emby.Dlna.Common;
using Emby.Dlna.Service;
diff --git a/Emby.Dlna/ConnectionManager/ControlHandler.cs b/Emby.Dlna/ConnectionManager/ControlHandler.cs
index 2e1104748..d4cc65394 100644
--- a/Emby.Dlna/ConnectionManager/ControlHandler.cs
+++ b/Emby.Dlna/ConnectionManager/ControlHandler.cs
@@ -1,5 +1,8 @@
+#pragma warning disable CS1591
+
using System;
using System.Collections.Generic;
+using System.Xml;
using Emby.Dlna.Service;
using MediaBrowser.Common.Extensions;
using MediaBrowser.Controller.Configuration;
@@ -12,29 +15,28 @@ namespace Emby.Dlna.ConnectionManager
{
private readonly DeviceProfile _profile;
- protected override IEnumerable<KeyValuePair<string, string>> GetResult(string methodName, IDictionary<string, string> methodParams)
+ public ControlHandler(IServerConfigurationManager config, ILogger logger, DeviceProfile profile)
+ : base(config, logger)
+ {
+ _profile = profile;
+ }
+
+ /// <inheritdoc />
+ protected override void WriteResult(string methodName, IDictionary<string, string> methodParams, XmlWriter xmlWriter)
{
if (string.Equals(methodName, "GetProtocolInfo", StringComparison.OrdinalIgnoreCase))
{
- return HandleGetProtocolInfo();
+ HandleGetProtocolInfo(xmlWriter);
+ return;
}
throw new ResourceNotFoundException("Unexpected control request name: " + methodName);
}
- private IEnumerable<KeyValuePair<string, string>> HandleGetProtocolInfo()
+ private void HandleGetProtocolInfo(XmlWriter xmlWriter)
{
- return new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
- {
- { "Source", _profile.ProtocolInfo },
- { "Sink", "" }
- };
- }
-
- public ControlHandler(IServerConfigurationManager config, ILogger logger, DeviceProfile profile)
- : base(config, logger)
- {
- _profile = profile;
+ xmlWriter.WriteElementString("Source", _profile.ProtocolInfo);
+ xmlWriter.WriteElementString("Sink", string.Empty);
}
}
}
diff --git a/Emby.Dlna/ConnectionManager/ServiceActionListBuilder.cs b/Emby.Dlna/ConnectionManager/ServiceActionListBuilder.cs
index b7727b558..b853e7eab 100644
--- a/Emby.Dlna/ConnectionManager/ServiceActionListBuilder.cs
+++ b/Emby.Dlna/ConnectionManager/ServiceActionListBuilder.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
using System.Collections.Generic;
using Emby.Dlna.Common;
diff --git a/Emby.Dlna/ContentDirectory/ContentDirectory.cs b/Emby.Dlna/ContentDirectory/ContentDirectory.cs
index 78d69b338..64cd308a2 100644
--- a/Emby.Dlna/ContentDirectory/ContentDirectory.cs
+++ b/Emby.Dlna/ContentDirectory/ContentDirectory.cs
@@ -1,4 +1,7 @@
+#pragma warning disable CS1591
+
using System;
+using System.Threading.Tasks;
using Emby.Dlna.Service;
using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Configuration;
@@ -34,7 +37,7 @@ namespace Emby.Dlna.ContentDirectory
ILibraryManager libraryManager,
IServerConfigurationManager config,
IUserManager userManager,
- ILogger logger,
+ ILogger<ContentDirectory> logger,
IHttpClient httpClient,
ILocalizationManager localization,
IMediaSourceManager mediaSourceManager,
@@ -66,12 +69,14 @@ namespace Emby.Dlna.ContentDirectory
}
}
+ /// <inheritdoc />
public string GetServiceXml()
{
return new ContentDirectoryXmlBuilder().GetXml();
}
- public ControlResponse ProcessControlRequest(ControlRequest request)
+ /// <inheritdoc />
+ public Task<ControlResponse> ProcessControlRequestAsync(ControlRequest request)
{
var profile = _dlna.GetProfile(request.Headers) ??
_dlna.GetDefaultProfile();
@@ -96,7 +101,7 @@ namespace Emby.Dlna.ContentDirectory
_userViewManager,
_mediaEncoder,
_tvSeriesManager)
- .ProcessControlRequest(request);
+ .ProcessControlRequestAsync(request);
}
private User GetUser(DeviceProfile profile)
diff --git a/Emby.Dlna/ContentDirectory/ContentDirectoryXmlBuilder.cs b/Emby.Dlna/ContentDirectory/ContentDirectoryXmlBuilder.cs
index 15fdb36c4..6db4d7cb6 100644
--- a/Emby.Dlna/ContentDirectory/ContentDirectoryXmlBuilder.cs
+++ b/Emby.Dlna/ContentDirectory/ContentDirectoryXmlBuilder.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
using System.Collections.Generic;
using Emby.Dlna.Common;
using Emby.Dlna.Service;
diff --git a/Emby.Dlna/ContentDirectory/ControlHandler.cs b/Emby.Dlna/ContentDirectory/ControlHandler.cs
index 4f74bb222..28888f031 100644
--- a/Emby.Dlna/ContentDirectory/ControlHandler.cs
+++ b/Emby.Dlna/ContentDirectory/ControlHandler.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
using System;
using System.Collections.Generic;
using System.Globalization;
@@ -44,7 +46,6 @@ namespace Emby.Dlna.ContentDirectory
private const string NS_UPNP = "urn:schemas-upnp-org:metadata-1-0/upnp/";
private readonly int _systemUpdateId;
- private readonly CultureInfo _usCulture = new CultureInfo("en-US");
private readonly DidlBuilder _didlBuilder;
@@ -58,7 +59,8 @@ namespace Emby.Dlna.ContentDirectory
string accessToken,
IImageProcessor imageProcessor,
IUserDataManager userDataManager,
- User user, int systemUpdateId,
+ User user,
+ int systemUpdateId,
IServerConfigurationManager config,
ILocalizationManager localization,
IMediaSourceManager mediaSourceManager,
@@ -76,117 +78,143 @@ namespace Emby.Dlna.ContentDirectory
_profile = profile;
_config = config;
- _didlBuilder = new DidlBuilder(profile, user, imageProcessor, serverAddress, accessToken, userDataManager, localization, mediaSourceManager, _logger, mediaEncoder);
+ _didlBuilder = new DidlBuilder(
+ profile,
+ user,
+ imageProcessor,
+ serverAddress,
+ accessToken,
+ userDataManager,
+ localization,
+ mediaSourceManager,
+ Logger,
+ mediaEncoder,
+ libraryManager);
}
- protected override IEnumerable<KeyValuePair<string, string>> GetResult(string methodName, IDictionary<string, string> methodParams)
+ /// <inheritdoc />
+ protected override void WriteResult(string methodName, IDictionary<string, string> methodParams, XmlWriter xmlWriter)
{
- var deviceId = "test";
-
- var user = _user;
+ const string DeviceId = "test";
if (string.Equals(methodName, "GetSearchCapabilities", StringComparison.OrdinalIgnoreCase))
- return HandleGetSearchCapabilities();
+ {
+ HandleGetSearchCapabilities(xmlWriter);
+ return;
+ }
if (string.Equals(methodName, "GetSortCapabilities", StringComparison.OrdinalIgnoreCase))
- return HandleGetSortCapabilities();
+ {
+ HandleGetSortCapabilities(xmlWriter);
+ return;
+ }
if (string.Equals(methodName, "GetSortExtensionCapabilities", StringComparison.OrdinalIgnoreCase))
- return HandleGetSortExtensionCapabilities();
+ {
+ HandleGetSortExtensionCapabilities(xmlWriter);
+ return;
+ }
if (string.Equals(methodName, "GetSystemUpdateID", StringComparison.OrdinalIgnoreCase))
- return HandleGetSystemUpdateID();
+ {
+ HandleGetSystemUpdateID(xmlWriter);
+ return;
+ }
if (string.Equals(methodName, "Browse", StringComparison.OrdinalIgnoreCase))
- return HandleBrowse(methodParams, user, deviceId);
+ {
+ HandleBrowse(xmlWriter, methodParams, DeviceId);
+ return;
+ }
if (string.Equals(methodName, "X_GetFeatureList", StringComparison.OrdinalIgnoreCase))
- return HandleXGetFeatureList();
+ {
+ HandleXGetFeatureList(xmlWriter);
+ return;
+ }
if (string.Equals(methodName, "GetFeatureList", StringComparison.OrdinalIgnoreCase))
- return HandleGetFeatureList();
+ {
+ HandleGetFeatureList(xmlWriter);
+ return;
+ }
if (string.Equals(methodName, "X_SetBookmark", StringComparison.OrdinalIgnoreCase))
- return HandleXSetBookmark(methodParams, user);
+ {
+ HandleXSetBookmark(methodParams);
+ return;
+ }
if (string.Equals(methodName, "Search", StringComparison.OrdinalIgnoreCase))
- return HandleSearch(methodParams, user, deviceId);
+ {
+ HandleSearch(xmlWriter, methodParams, DeviceId);
+ return;
+ }
if (string.Equals(methodName, "X_BrowseByLetter", StringComparison.OrdinalIgnoreCase))
- return HandleX_BrowseByLetter(methodParams, user, deviceId);
+ {
+ HandleXBrowseByLetter(xmlWriter, methodParams, DeviceId);
+ return;
+ }
throw new ResourceNotFoundException("Unexpected control request name: " + methodName);
}
- private IEnumerable<KeyValuePair<string, string>> HandleXSetBookmark(IDictionary<string, string> sparams, User user)
+ private void HandleXSetBookmark(IDictionary<string, string> sparams)
{
var id = sparams["ObjectID"];
- var serverItem = GetItemFromObjectId(id, user);
+ var serverItem = GetItemFromObjectId(id);
var item = serverItem.Item;
- var newbookmark = int.Parse(sparams["PosSecond"], _usCulture);
+ var newbookmark = int.Parse(sparams["PosSecond"], CultureInfo.InvariantCulture);
- var userdata = _userDataManager.GetUserData(user, item);
+ var userdata = _userDataManager.GetUserData(_user, item);
userdata.PlaybackPositionTicks = TimeSpan.FromSeconds(newbookmark).Ticks;
- _userDataManager.SaveUserData(user, item, userdata, UserDataSaveReason.TogglePlayed,
+ _userDataManager.SaveUserData(_user, item, userdata, UserDataSaveReason.TogglePlayed,
CancellationToken.None);
-
- return new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
}
- private IEnumerable<KeyValuePair<string, string>> HandleGetSearchCapabilities()
+ private void HandleGetSearchCapabilities(XmlWriter xmlWriter)
{
- return new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
- {
- { "SearchCaps", "res@resolution,res@size,res@duration,dc:title,dc:creator,upnp:actor,upnp:artist,upnp:genre,upnp:album,dc:date,upnp:class,@id,@refID,@protocolInfo,upnp:author,dc:description,pv:avKeywords" }
- };
+ xmlWriter.WriteElementString(
+ "SearchCaps",
+ "res@resolution,res@size,res@duration,dc:title,dc:creator,upnp:actor,upnp:artist,upnp:genre,upnp:album,dc:date,upnp:class,@id,@refID,@protocolInfo,upnp:author,dc:description,pv:avKeywords");
}
- private IEnumerable<KeyValuePair<string, string>> HandleGetSortCapabilities()
+ private void HandleGetSortCapabilities(XmlWriter xmlWriter)
{
- return new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
- {
- { "SortCaps", "res@duration,res@size,res@bitrate,dc:date,dc:title,dc:size,upnp:album,upnp:artist,upnp:albumArtist,upnp:episodeNumber,upnp:genre,upnp:originalTrackNumber,upnp:rating" }
- };
+ xmlWriter.WriteElementString(
+ "SortCaps",
+ "res@duration,res@size,res@bitrate,dc:date,dc:title,dc:size,upnp:album,upnp:artist,upnp:albumArtist,upnp:episodeNumber,upnp:genre,upnp:originalTrackNumber,upnp:rating");
}
- private IEnumerable<KeyValuePair<string, string>> HandleGetSortExtensionCapabilities()
+ private void HandleGetSortExtensionCapabilities(XmlWriter xmlWriter)
{
- return new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
- {
- { "SortExtensionCaps", "res@duration,res@size,res@bitrate,dc:date,dc:title,dc:size,upnp:album,upnp:artist,upnp:albumArtist,upnp:episodeNumber,upnp:genre,upnp:originalTrackNumber,upnp:rating" }
- };
+ xmlWriter.WriteElementString(
+ "SortExtensionCaps",
+ "res@duration,res@size,res@bitrate,dc:date,dc:title,dc:size,upnp:album,upnp:artist,upnp:albumArtist,upnp:episodeNumber,upnp:genre,upnp:originalTrackNumber,upnp:rating");
}
- private IEnumerable<KeyValuePair<string, string>> HandleGetSystemUpdateID()
+ private void HandleGetSystemUpdateID(XmlWriter xmlWriter)
{
- var headers = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
- headers.Add("Id", _systemUpdateId.ToString(_usCulture));
- return headers;
+ xmlWriter.WriteElementString("Id", _systemUpdateId.ToString(CultureInfo.InvariantCulture));
}
- private IEnumerable<KeyValuePair<string, string>> HandleGetFeatureList()
+ private void HandleGetFeatureList(XmlWriter xmlWriter)
{
- return new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
- {
- { "FeatureList", GetFeatureListXml() }
- };
+ xmlWriter.WriteElementString("FeatureList", WriteFeatureListXml());
}
- private IEnumerable<KeyValuePair<string, string>> HandleXGetFeatureList()
- {
- return new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
- {
- { "FeatureList", GetFeatureListXml() }
- };
- }
+ private void HandleXGetFeatureList(XmlWriter xmlWriter)
+ => HandleGetFeatureList(xmlWriter);
- private string GetFeatureListXml()
+ private string WriteFeatureListXml()
{
+ // TODO: clean this up
var builder = new StringBuilder();
builder.Append("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
@@ -213,7 +241,7 @@ namespace Emby.Dlna.ContentDirectory
return defaultValue;
}
- private IEnumerable<KeyValuePair<string, string>> HandleBrowse(IDictionary<string, string> sparams, User user, string deviceId)
+ private void HandleBrowse(XmlWriter xmlWriter, IDictionary<string, string> sparams, string deviceId)
{
var id = sparams["ObjectID"];
var flag = sparams["BrowseFlag"];
@@ -237,101 +265,95 @@ namespace Emby.Dlna.ContentDirectory
start = startVal;
}
- var settings = new XmlWriterSettings
- {
- Encoding = Encoding.UTF8,
- CloseOutput = false,
- OmitXmlDeclaration = true,
- ConformanceLevel = ConformanceLevel.Fragment
- };
-
- StringWriter builder = new StringWriterWithEncoding(Encoding.UTF8);
-
int totalCount;
- var dlnaOptions = _config.GetDlnaConfiguration();
-
- using (var writer = XmlWriter.Create(builder, settings))
+ using (StringWriter builder = new StringWriterWithEncoding(Encoding.UTF8))
{
- //writer.WriteStartDocument();
-
- writer.WriteStartElement(string.Empty, "DIDL-Lite", NS_DIDL);
-
- writer.WriteAttributeString("xmlns", "dc", null, NS_DC);
- writer.WriteAttributeString("xmlns", "dlna", null, NS_DLNA);
- writer.WriteAttributeString("xmlns", "upnp", null, NS_UPNP);
- //didl.SetAttribute("xmlns:sec", NS_SEC);
-
- DidlBuilder.WriteXmlRootAttributes(_profile, writer);
-
- var serverItem = GetItemFromObjectId(id, user);
- var item = serverItem.Item;
+ var settings = new XmlWriterSettings()
+ {
+ Encoding = Encoding.UTF8,
+ CloseOutput = false,
+ OmitXmlDeclaration = true,
+ ConformanceLevel = ConformanceLevel.Fragment
+ };
- if (string.Equals(flag, "BrowseMetadata"))
+ using (var writer = XmlWriter.Create(builder, settings))
{
- totalCount = 1;
+ writer.WriteStartElement(string.Empty, "DIDL-Lite", NS_DIDL);
- if (item.IsDisplayedAsFolder || serverItem.StubType.HasValue)
- {
- var childrenResult = GetUserItems(item, serverItem.StubType, user, sortCriteria, start, requestedCount);
+ writer.WriteAttributeString("xmlns", "dc", null, NS_DC);
+ writer.WriteAttributeString("xmlns", "dlna", null, NS_DLNA);
+ writer.WriteAttributeString("xmlns", "upnp", null, NS_UPNP);
- _didlBuilder.WriteFolderElement(writer, item, serverItem.StubType, null, childrenResult.TotalRecordCount, filter, id);
- }
- else
- {
- _didlBuilder.WriteItemElement(dlnaOptions, writer, item, user, null, null, deviceId, filter);
- }
+ DidlBuilder.WriteXmlRootAttributes(_profile, writer);
- provided++;
- }
- else
- {
- var childrenResult = GetUserItems(item, serverItem.StubType, user, sortCriteria, start, requestedCount);
- totalCount = childrenResult.TotalRecordCount;
+ var serverItem = GetItemFromObjectId(id);
+ var item = serverItem.Item;
- provided = childrenResult.Items.Count;
- foreach (var i in childrenResult.Items)
+ if (string.Equals(flag, "BrowseMetadata", StringComparison.Ordinal))
{
- var childItem = i.Item;
- var displayStubType = i.StubType;
+ totalCount = 1;
- if (childItem.IsDisplayedAsFolder || displayStubType.HasValue)
+ if (item.IsDisplayedAsFolder || serverItem.StubType.HasValue)
{
- var childCount = (GetUserItems(childItem, displayStubType, user, sortCriteria, null, 0))
- .TotalRecordCount;
+ var childrenResult = GetUserItems(item, serverItem.StubType, _user, sortCriteria, start, requestedCount);
- _didlBuilder.WriteFolderElement(writer, childItem, displayStubType, item, childCount, filter);
+ _didlBuilder.WriteFolderElement(writer, item, serverItem.StubType, null, childrenResult.TotalRecordCount, filter, id);
}
else
{
- _didlBuilder.WriteItemElement(dlnaOptions, writer, childItem, user, item, serverItem.StubType, deviceId, filter);
+ var dlnaOptions = _config.GetDlnaConfiguration();
+ _didlBuilder.WriteItemElement(writer, item, _user, null, null, deviceId, filter);
+ }
+
+ provided++;
+ }
+ else
+ {
+ var childrenResult = GetUserItems(item, serverItem.StubType, _user, sortCriteria, start, requestedCount);
+ totalCount = childrenResult.TotalRecordCount;
+
+ provided = childrenResult.Items.Count;
+
+ var dlnaOptions = _config.GetDlnaConfiguration();
+ foreach (var i in childrenResult.Items)
+ {
+ var childItem = i.Item;
+ var displayStubType = i.StubType;
+
+ if (childItem.IsDisplayedAsFolder || displayStubType.HasValue)
+ {
+ var childCount = GetUserItems(childItem, displayStubType, _user, sortCriteria, null, 0)
+ .TotalRecordCount;
+
+ _didlBuilder.WriteFolderElement(writer, childItem, displayStubType, item, childCount, filter);
+ }
+ else
+ {
+ _didlBuilder.WriteItemElement(writer, childItem, _user, item, serverItem.StubType, deviceId, filter);
+ }
}
}
+
+ writer.WriteFullEndElement();
}
- writer.WriteFullEndElement();
- //writer.WriteEndDocument();
+ xmlWriter.WriteElementString("Result", builder.ToString());
}
- var resXML = builder.ToString();
-
- return new[]
- {
- new KeyValuePair<string,string>("Result", resXML),
- new KeyValuePair<string,string>("NumberReturned", provided.ToString(_usCulture)),
- new KeyValuePair<string,string>("TotalMatches", totalCount.ToString(_usCulture)),
- new KeyValuePair<string,string>("UpdateID", _systemUpdateId.ToString(_usCulture))
- };
+ xmlWriter.WriteElementString("NumberReturned", provided.ToString(CultureInfo.InvariantCulture));
+ xmlWriter.WriteElementString("TotalMatches", totalCount.ToString(CultureInfo.InvariantCulture));
+ xmlWriter.WriteElementString("UpdateID", _systemUpdateId.ToString(CultureInfo.InvariantCulture));
}
- private IEnumerable<KeyValuePair<string, string>> HandleX_BrowseByLetter(IDictionary<string, string> sparams, User user, string deviceId)
+ private void HandleXBrowseByLetter(XmlWriter xmlWriter, IDictionary<string, string> sparams, string deviceId)
{
// TODO: Implement this method
- return HandleSearch(sparams, user, deviceId);
+ HandleSearch(xmlWriter, sparams, deviceId);
}
- private IEnumerable<KeyValuePair<string, string>> HandleSearch(IDictionary<string, string> sparams, User user, string deviceId)
+ private void HandleSearch(XmlWriter xmlWriter, IDictionary<string, string> sparams, string deviceId)
{
var searchCriteria = new SearchCriteria(GetValueOrDefault(sparams, "SearchCriteria", ""));
var sortCriteria = new SortCriteria(GetValueOrDefault(sparams, "SortCriteria", ""));
@@ -354,99 +376,86 @@ namespace Emby.Dlna.ContentDirectory
start = startVal;
}
- var settings = new XmlWriterSettings
- {
- Encoding = Encoding.UTF8,
- CloseOutput = false,
- OmitXmlDeclaration = true,
- ConformanceLevel = ConformanceLevel.Fragment
- };
+ QueryResult<BaseItem> childrenResult;
- StringWriter builder = new StringWriterWithEncoding(Encoding.UTF8);
- int totalCount = 0;
- int provided = 0;
-
- using (var writer = XmlWriter.Create(builder, settings))
+ using (StringWriter builder = new StringWriterWithEncoding(Encoding.UTF8))
{
- //writer.WriteStartDocument();
-
- writer.WriteStartElement(string.Empty, "DIDL-Lite", NS_DIDL);
-
- writer.WriteAttributeString("xmlns", "dc", null, NS_DC);
- writer.WriteAttributeString("xmlns", "dlna", null, NS_DLNA);
- writer.WriteAttributeString("xmlns", "upnp", null, NS_UPNP);
- //didl.SetAttribute("xmlns:sec", NS_SEC);
+ var settings = new XmlWriterSettings()
+ {
+ Encoding = Encoding.UTF8,
+ CloseOutput = false,
+ OmitXmlDeclaration = true,
+ ConformanceLevel = ConformanceLevel.Fragment
+ };
- DidlBuilder.WriteXmlRootAttributes(_profile, writer);
+ using (var writer = XmlWriter.Create(builder, settings))
+ {
+ writer.WriteStartElement(string.Empty, "DIDL-Lite", NS_DIDL);
- var serverItem = GetItemFromObjectId(sparams["ContainerID"], user);
+ writer.WriteAttributeString("xmlns", "dc", null, NS_DC);
+ writer.WriteAttributeString("xmlns", "dlna", null, NS_DLNA);
+ writer.WriteAttributeString("xmlns", "upnp", null, NS_UPNP);
- var item = serverItem.Item;
+ DidlBuilder.WriteXmlRootAttributes(_profile, writer);
- var childrenResult = (GetChildrenSorted(item, user, searchCriteria, sortCriteria, start, requestedCount));
+ var serverItem = GetItemFromObjectId(sparams["ContainerID"]);
- totalCount = childrenResult.TotalRecordCount;
+ var item = serverItem.Item;
- provided = childrenResult.Items.Count;
+ childrenResult = GetChildrenSorted(item, _user, searchCriteria, sortCriteria, start, requestedCount);
- var dlnaOptions = _config.GetDlnaConfiguration();
+ var dlnaOptions = _config.GetDlnaConfiguration();
- foreach (var i in childrenResult.Items)
- {
- if (i.IsDisplayedAsFolder)
+ foreach (var i in childrenResult.Items)
{
- var childCount = (GetChildrenSorted(i, user, searchCriteria, sortCriteria, null, 0))
- .TotalRecordCount;
+ if (i.IsDisplayedAsFolder)
+ {
+ var childCount = GetChildrenSorted(i, _user, searchCriteria, sortCriteria, null, 0)
+ .TotalRecordCount;
- _didlBuilder.WriteFolderElement(writer, i, null, item, childCount, filter);
- }
- else
- {
- _didlBuilder.WriteItemElement(dlnaOptions, writer, i, user, item, serverItem.StubType, deviceId, filter);
+ _didlBuilder.WriteFolderElement(writer, i, null, item, childCount, filter);
+ }
+ else
+ {
+ _didlBuilder.WriteItemElement(writer, i, _user, item, serverItem.StubType, deviceId, filter);
+ }
}
+
+ writer.WriteFullEndElement();
}
- writer.WriteFullEndElement();
- //writer.WriteEndDocument();
+ xmlWriter.WriteElementString("Result", builder.ToString());
}
- var resXML = builder.ToString();
-
- return new List<KeyValuePair<string, string>>
- {
- new KeyValuePair<string,string>("Result", resXML),
- new KeyValuePair<string,string>("NumberReturned", provided.ToString(_usCulture)),
- new KeyValuePair<string,string>("TotalMatches", totalCount.ToString(_usCulture)),
- new KeyValuePair<string,string>("UpdateID", _systemUpdateId.ToString(_usCulture))
- };
+ xmlWriter.WriteElementString("NumberReturned", childrenResult.Items.Count.ToString(CultureInfo.InvariantCulture));
+ xmlWriter.WriteElementString("TotalMatches", childrenResult.TotalRecordCount.ToString(CultureInfo.InvariantCulture));
+ xmlWriter.WriteElementString("UpdateID", _systemUpdateId.ToString(CultureInfo.InvariantCulture));
}
private QueryResult<BaseItem> GetChildrenSorted(BaseItem item, User user, SearchCriteria search, SortCriteria sort, int? startIndex, int? limit)
{
var folder = (Folder)item;
- var sortOrders = new List<(string, SortOrder)>();
- if (!folder.IsPreSorted)
- {
- sortOrders.Add((ItemSortBy.SortName, sort.SortOrder));
- }
+ var sortOrders = folder.IsPreSorted
+ ? Array.Empty<(string, SortOrder)>()
+ : new[] { (ItemSortBy.SortName, sort.SortOrder) };
- var mediaTypes = new List<string>();
+ string[] mediaTypes = Array.Empty<string>();
bool? isFolder = null;
if (search.SearchType == SearchType.Audio)
{
- mediaTypes.Add(MediaType.Audio);
+ mediaTypes = new[] { MediaType.Audio };
isFolder = false;
}
else if (search.SearchType == SearchType.Video)
{
- mediaTypes.Add(MediaType.Video);
+ mediaTypes = new[] { MediaType.Video };
isFolder = false;
}
else if (search.SearchType == SearchType.Image)
{
- mediaTypes.Add(MediaType.Photo);
+ mediaTypes = new[] { MediaType.Photo };
isFolder = false;
}
else if (search.SearchType == SearchType.Playlist)
@@ -470,7 +479,7 @@ namespace Emby.Dlna.ContentDirectory
IsMissing = false,
ExcludeItemTypes = new[] { typeof(Book).Name },
IsFolder = isFolder,
- MediaTypes = mediaTypes.ToArray(),
+ MediaTypes = mediaTypes,
DtoOptions = GetDtoOptions()
});
}
@@ -514,11 +523,11 @@ namespace Emby.Dlna.ContentDirectory
}
else if (string.Equals(CollectionType.Folders, collectionFolder.CollectionType, StringComparison.OrdinalIgnoreCase))
{
- return GetFolders(item, user, stubType, sort, startIndex, limit);
+ return GetFolders(user, startIndex, limit);
}
else if (string.Equals(CollectionType.LiveTv, collectionFolder.CollectionType, StringComparison.OrdinalIgnoreCase))
{
- return GetLiveTvChannels(item, user, stubType, sort, startIndex, limit);
+ return GetLiveTvChannels(user, sort, startIndex, limit);
}
}
@@ -549,7 +558,7 @@ namespace Emby.Dlna.ContentDirectory
return ToResult(queryResult);
}
- private QueryResult<ServerItem> GetLiveTvChannels(BaseItem item, User user, StubType? stubType, SortCriteria sort, int? startIndex, int? limit)
+ private QueryResult<ServerItem> GetLiveTvChannels(User user, SortCriteria sort, int? startIndex, int? limit)
{
var query = new InternalItemsQuery(user)
{
@@ -581,7 +590,7 @@ namespace Emby.Dlna.ContentDirectory
if (stubType.HasValue && stubType.Value == StubType.Playlists)
{
- return GetMusicPlaylists(item, user, query);
+ return GetMusicPlaylists(user, query);
}
if (stubType.HasValue && stubType.Value == StubType.Albums)
@@ -709,7 +718,7 @@ namespace Emby.Dlna.ContentDirectory
if (stubType.HasValue && stubType.Value == StubType.Collections)
{
- return GetMovieCollections(item, user, query);
+ return GetMovieCollections(user, query);
}
if (stubType.HasValue && stubType.Value == StubType.Favorites)
@@ -722,46 +731,42 @@ namespace Emby.Dlna.ContentDirectory
return GetGenres(item, user, query);
}
- var list = new List<ServerItem>();
-
- list.Add(new ServerItem(item)
+ var array = new ServerItem[]
{
- StubType = StubType.ContinueWatching
- });
-
- list.Add(new ServerItem(item)
- {
- StubType = StubType.Latest
- });
-
- list.Add(new ServerItem(item)
- {
- StubType = StubType.Movies
- });
-
- list.Add(new ServerItem(item)
- {
- StubType = StubType.Collections
- });
-
- list.Add(new ServerItem(item)
- {
- StubType = StubType.Favorites
- });
-
- list.Add(new ServerItem(item)
- {
- StubType = StubType.Genres
- });
+ new ServerItem(item)
+ {
+ StubType = StubType.ContinueWatching
+ },
+ new ServerItem(item)
+ {
+ StubType = StubType.Latest
+ },
+ new ServerItem(item)
+ {
+ StubType = StubType.Movies
+ },
+ new ServerItem(item)
+ {
+ StubType = StubType.Collections
+ },
+ new ServerItem(item)
+ {
+ StubType = StubType.Favorites
+ },
+ new ServerItem(item)
+ {
+ StubType = StubType.Genres
+ }
+ };
return new QueryResult<ServerItem>
{
- Items = list,
- TotalRecordCount = list.Count
+ Items = array,
+ TotalRecordCount = array.Length
};
}
- private QueryResult<ServerItem> GetFolders(BaseItem item, User user, StubType? stubType, SortCriteria sort, int? startIndex, int? limit)
+ private QueryResult<ServerItem> GetFolders(User user, int? startIndex, int? limit)
{
var folders = _libraryManager.GetUserRootFolder().GetChildren(user, true)
.OrderBy(i => i.SortName)
@@ -771,11 +776,11 @@ namespace Emby.Dlna.ContentDirectory
})
.ToArray();
- return new QueryResult<ServerItem>
+ return ApplyPaging(new QueryResult<ServerItem>
{
Items = folders,
TotalRecordCount = folders.Length
- };
+ }, startIndex, limit);
}
private QueryResult<ServerItem> GetTvFolders(BaseItem item, User user, StubType? stubType, SortCriteria sort, int? startIndex, int? limit)
@@ -794,7 +799,7 @@ namespace Emby.Dlna.ContentDirectory
if (stubType.HasValue && stubType.Value == StubType.NextUp)
{
- return GetNextUp(item, user, query);
+ return GetNextUp(item, query);
}
if (stubType.HasValue && stubType.Value == StubType.Latest)
@@ -912,7 +917,7 @@ namespace Emby.Dlna.ContentDirectory
return ToResult(result);
}
- private QueryResult<ServerItem> GetMovieCollections(BaseItem parent, User user, InternalItemsQuery query)
+ private QueryResult<ServerItem> GetMovieCollections(User user, InternalItemsQuery query)
{
query.Recursive = true;
//query.Parent = parent;
@@ -1107,7 +1112,7 @@ namespace Emby.Dlna.ContentDirectory
return ToResult(result);
}
- private QueryResult<ServerItem> GetMusicPlaylists(BaseItem parent, User user, InternalItemsQuery query)
+ private QueryResult<ServerItem> GetMusicPlaylists(User user, InternalItemsQuery query)
{
query.Parent = null;
query.IncludeItemTypes = new[] { typeof(Playlist).Name };
@@ -1136,7 +1141,7 @@ namespace Emby.Dlna.ContentDirectory
return ToResult(items);
}
- private QueryResult<ServerItem> GetNextUp(BaseItem parent, User user, InternalItemsQuery query)
+ private QueryResult<ServerItem> GetNextUp(BaseItem parent, InternalItemsQuery query)
{
query.OrderBy = Array.Empty<(string, SortOrder)>();
@@ -1291,24 +1296,24 @@ namespace Emby.Dlna.ContentDirectory
return result;
}
- private ServerItem GetItemFromObjectId(string id, User user)
+ private ServerItem GetItemFromObjectId(string id)
{
return DidlBuilder.IsIdRoot(id)
? new ServerItem(_libraryManager.GetUserRootFolder())
- : ParseItemId(id, user);
+ : ParseItemId(id);
}
- private ServerItem ParseItemId(string id, User user)
+ private ServerItem ParseItemId(string id)
{
StubType? stubType = null;
// After using PlayTo, MediaMonkey sends a request to the server trying to get item info
- const string paramsSrch = "Params=";
- var paramsIndex = id.IndexOf(paramsSrch, StringComparison.OrdinalIgnoreCase);
+ const string ParamsSrch = "Params=";
+ var paramsIndex = id.IndexOf(ParamsSrch, StringComparison.OrdinalIgnoreCase);
if (paramsIndex != -1)
{
- id = id.Substring(paramsIndex + paramsSrch.Length);
+ id = id.Substring(paramsIndex + ParamsSrch.Length);
var parts = id.Split(';');
id = parts[23];
@@ -1336,7 +1341,7 @@ namespace Emby.Dlna.ContentDirectory
};
}
- _logger.LogError("Error parsing item Id: {id}. Returning user root folder.", id);
+ Logger.LogError("Error parsing item Id: {id}. Returning user root folder.", id);
return new ServerItem(_libraryManager.GetUserRootFolder());
}
diff --git a/Emby.Dlna/ContentDirectory/ServiceActionListBuilder.cs b/Emby.Dlna/ContentDirectory/ServiceActionListBuilder.cs
index e999314fa..921b14e39 100644
--- a/Emby.Dlna/ContentDirectory/ServiceActionListBuilder.cs
+++ b/Emby.Dlna/ContentDirectory/ServiceActionListBuilder.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
using System.Collections.Generic;
using Emby.Dlna.Common;
diff --git a/Emby.Dlna/ControlRequest.cs b/Emby.Dlna/ControlRequest.cs
index 8c227159c..a6e03b7e6 100644
--- a/Emby.Dlna/ControlRequest.cs
+++ b/Emby.Dlna/ControlRequest.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
using System.IO;
using Microsoft.AspNetCore.Http;
diff --git a/Emby.Dlna/ControlResponse.cs b/Emby.Dlna/ControlResponse.cs
index d2b79fc58..140ef9b46 100644
--- a/Emby.Dlna/ControlResponse.cs
+++ b/Emby.Dlna/ControlResponse.cs
@@ -1,18 +1,20 @@
+#pragma warning disable CS1591
+
using System.Collections.Generic;
namespace Emby.Dlna
{
public class ControlResponse
{
+ public ControlResponse()
+ {
+ Headers = new Dictionary<string, string>();
+ }
+
public IDictionary<string, string> Headers { get; set; }
public string Xml { get; set; }
public bool IsSuccessful { get; set; }
-
- public ControlResponse()
- {
- Headers = new Dictionary<string, string>();
- }
}
}
diff --git a/Emby.Dlna/Didl/DidlBuilder.cs b/Emby.Dlna/Didl/DidlBuilder.cs
index 85ef9d482..f7d840c62 100644
--- a/Emby.Dlna/Didl/DidlBuilder.cs
+++ b/Emby.Dlna/Didl/DidlBuilder.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
using System;
using System.Globalization;
using System.IO;
@@ -18,7 +20,6 @@ using MediaBrowser.Controller.Playlists;
using MediaBrowser.Model.Dlna;
using MediaBrowser.Model.Drawing;
using MediaBrowser.Model.Entities;
-using MediaBrowser.Model.Extensions;
using MediaBrowser.Model.Globalization;
using MediaBrowser.Model.Net;
using Microsoft.Extensions.Logging;
@@ -44,6 +45,7 @@ namespace Emby.Dlna.Didl
private readonly IMediaSourceManager _mediaSourceManager;
private readonly ILogger _logger;
private readonly IMediaEncoder _mediaEncoder;
+ private readonly ILibraryManager _libraryManager;
public DidlBuilder(
DeviceProfile profile,
@@ -55,7 +57,8 @@ namespace Emby.Dlna.Didl
ILocalizationManager localization,
IMediaSourceManager mediaSourceManager,
ILogger logger,
- IMediaEncoder mediaEncoder)
+ IMediaEncoder mediaEncoder,
+ ILibraryManager libraryManager)
{
_profile = profile;
_user = user;
@@ -67,6 +70,7 @@ namespace Emby.Dlna.Didl
_mediaSourceManager = mediaSourceManager;
_logger = logger;
_mediaEncoder = mediaEncoder;
+ _libraryManager = libraryManager;
}
public static string NormalizeDlnaMediaUrl(string url)
@@ -74,7 +78,7 @@ namespace Emby.Dlna.Didl
return url + "&dlnaheaders=true";
}
- public string GetItemDidl(DlnaOptions options, BaseItem item, User user, BaseItem context, string deviceId, Filter filter, StreamInfo streamInfo)
+ public string GetItemDidl(BaseItem item, User user, BaseItem context, string deviceId, Filter filter, StreamInfo streamInfo)
{
var settings = new XmlWriterSettings
{
@@ -99,7 +103,7 @@ namespace Emby.Dlna.Didl
WriteXmlRootAttributes(_profile, writer);
- WriteItemElement(options, writer, item, user, context, null, deviceId, filter, streamInfo);
+ WriteItemElement(writer, item, user, context, null, deviceId, filter, streamInfo);
writer.WriteFullEndElement();
//writer.WriteEndDocument();
@@ -126,7 +130,6 @@ namespace Emby.Dlna.Didl
}
public void WriteItemElement(
- DlnaOptions options,
XmlWriter writer,
BaseItem item,
User user,
@@ -163,25 +166,23 @@ namespace Emby.Dlna.Didl
// refID?
// storeAttribute(itemNode, object, ClassProperties.REF_ID, false);
- var hasMediaSources = item as IHasMediaSources;
-
- if (hasMediaSources != null)
+ if (item is IHasMediaSources)
{
if (string.Equals(item.MediaType, MediaType.Audio, StringComparison.OrdinalIgnoreCase))
{
- AddAudioResource(options, writer, item, deviceId, filter, streamInfo);
+ AddAudioResource(writer, item, deviceId, filter, streamInfo);
}
else if (string.Equals(item.MediaType, MediaType.Video, StringComparison.OrdinalIgnoreCase))
{
- AddVideoResource(options, writer, item, deviceId, filter, streamInfo);
+ AddVideoResource(writer, item, deviceId, filter, streamInfo);
}
}
- AddCover(item, context, null, writer);
+ AddCover(item, null, writer);
writer.WriteFullEndElement();
}
- private void AddVideoResource(DlnaOptions options, XmlWriter writer, BaseItem video, string deviceId, Filter filter, StreamInfo streamInfo = null)
+ private void AddVideoResource(XmlWriter writer, BaseItem video, string deviceId, Filter filter, StreamInfo streamInfo = null)
{
if (streamInfo == null)
{
@@ -225,7 +226,7 @@ namespace Emby.Dlna.Didl
foreach (var contentFeature in contentFeatureList)
{
- AddVideoResource(writer, video, deviceId, filter, contentFeature, streamInfo);
+ AddVideoResource(writer, filter, contentFeature, streamInfo);
}
var subtitleProfiles = streamInfo.GetSubtitleProfiles(_mediaEncoder, false, _serverAddress, _accessToken);
@@ -282,7 +283,10 @@ namespace Emby.Dlna.Didl
else
{
writer.WriteStartElement(string.Empty, "res", NS_DIDL);
- var protocolInfo = string.Format("http-get:*:text/{0}:*", info.Format.ToLowerInvariant());
+ var protocolInfo = string.Format(
+ CultureInfo.InvariantCulture,
+ "http-get:*:text/{0}:*",
+ info.Format.ToLowerInvariant());
writer.WriteAttributeString("protocolInfo", protocolInfo);
writer.WriteString(info.Url);
@@ -292,7 +296,7 @@ namespace Emby.Dlna.Didl
return true;
}
- private void AddVideoResource(XmlWriter writer, BaseItem video, string deviceId, Filter filter, string contentFeatures, StreamInfo streamInfo)
+ private void AddVideoResource(XmlWriter writer, Filter filter, string contentFeatures, StreamInfo streamInfo)
{
writer.WriteStartElement(string.Empty, "res", NS_DIDL);
@@ -334,7 +338,13 @@ namespace Emby.Dlna.Didl
{
if (targetWidth.HasValue && targetHeight.HasValue)
{
- writer.WriteAttributeString("resolution", string.Format("{0}x{1}", targetWidth.Value, targetHeight.Value));
+ writer.WriteAttributeString(
+ "resolution",
+ string.Format(
+ CultureInfo.InvariantCulture,
+ "{0}x{1}",
+ targetWidth.Value,
+ targetHeight.Value));
}
}
@@ -368,17 +378,19 @@ namespace Emby.Dlna.Didl
streamInfo.TargetVideoCodecTag,
streamInfo.IsTargetAVC);
- var filename = url.Substring(0, url.IndexOf('?'));
+ var filename = url.Substring(0, url.IndexOf('?', StringComparison.Ordinal));
var mimeType = mediaProfile == null || string.IsNullOrEmpty(mediaProfile.MimeType)
? MimeTypes.GetMimeType(filename)
: mediaProfile.MimeType;
- writer.WriteAttributeString("protocolInfo", string.Format(
- "http-get:*:{0}:{1}",
- mimeType,
- contentFeatures
- ));
+ writer.WriteAttributeString(
+ "protocolInfo",
+ string.Format(
+ CultureInfo.InvariantCulture,
+ "http-get:*:{0}:{1}",
+ mimeType,
+ contentFeatures));
writer.WriteString(url);
@@ -391,54 +403,122 @@ namespace Emby.Dlna.Didl
{
switch (itemStubType.Value)
{
- case StubType.Latest: return _localization.GetLocalizedString("Latest");
- case StubType.Playlists: return _localization.GetLocalizedString("Playlists");
- case StubType.AlbumArtists: return _localization.GetLocalizedString("HeaderAlbumArtists");
- case StubType.Albums: return _localization.GetLocalizedString("Albums");
- case StubType.Artists: return _localization.GetLocalizedString("Artists");
- case StubType.Songs: return _localization.GetLocalizedString("Songs");
- case StubType.Genres: return _localization.GetLocalizedString("Genres");
- case StubType.FavoriteAlbums: return _localization.GetLocalizedString("HeaderFavoriteAlbums");
- case StubType.FavoriteArtists: return _localization.GetLocalizedString("HeaderFavoriteArtists");
- case StubType.FavoriteSongs: return _localization.GetLocalizedString("HeaderFavoriteSongs");
+ case StubType.Latest: return _localization.GetLocalizedString("Latest");
+ case StubType.Playlists: return _localization.GetLocalizedString("Playlists");
+ case StubType.AlbumArtists: return _localization.GetLocalizedString("HeaderAlbumArtists");
+ case StubType.Albums: return _localization.GetLocalizedString("Albums");
+ case StubType.Artists: return _localization.GetLocalizedString("Artists");
+ case StubType.Songs: return _localization.GetLocalizedString("Songs");
+ case StubType.Genres: return _localization.GetLocalizedString("Genres");
+ case StubType.FavoriteAlbums: return _localization.GetLocalizedString("HeaderFavoriteAlbums");
+ case StubType.FavoriteArtists: return _localization.GetLocalizedString("HeaderFavoriteArtists");
+ case StubType.FavoriteSongs: return _localization.GetLocalizedString("HeaderFavoriteSongs");
case StubType.ContinueWatching: return _localization.GetLocalizedString("HeaderContinueWatching");
- case StubType.Movies: return _localization.GetLocalizedString("Movies");
- case StubType.Collections: return _localization.GetLocalizedString("Collections");
- case StubType.Favorites: return _localization.GetLocalizedString("Favorites");
- case StubType.NextUp: return _localization.GetLocalizedString("HeaderNextUp");
- case StubType.FavoriteSeries: return _localization.GetLocalizedString("HeaderFavoriteShows");
+ case StubType.Movies: return _localization.GetLocalizedString("Movies");
+ case StubType.Collections: return _localization.GetLocalizedString("Collections");
+ case StubType.Favorites: return _localization.GetLocalizedString("Favorites");
+ case StubType.NextUp: return _localization.GetLocalizedString("HeaderNextUp");
+ case StubType.FavoriteSeries: return _localization.GetLocalizedString("HeaderFavoriteShows");
case StubType.FavoriteEpisodes: return _localization.GetLocalizedString("HeaderFavoriteEpisodes");
- case StubType.Series: return _localization.GetLocalizedString("Shows");
+ case StubType.Series: return _localization.GetLocalizedString("Shows");
default: break;
}
}
- if (item is Episode episode && context is Season season)
+ return item is Episode episode
+ ? GetEpisodeDisplayName(episode, context)
+ : item.Name;
+ }
+
+ /// <summary>
+ /// Gets episode display name appropriate for the given context.
+ /// </summary>
+ /// <remarks>
+ /// If context is a season, this will return a string containing just episode number and name.
+ /// Otherwise the result will include series nams and season number.
+ /// </remarks>
+ /// <param name="episode">The episode.</param>
+ /// <param name="context">Current context.</param>
+ /// <returns>Formatted name of the episode.</returns>
+ private string GetEpisodeDisplayName(Episode episode, BaseItem context)
+ {
+ string[] components;
+
+ if (context is Season season)
{
// This is a special embedded within a season
- if (item.ParentIndexNumber.HasValue && item.ParentIndexNumber.Value == 0
+ if (episode.ParentIndexNumber.HasValue && episode.ParentIndexNumber.Value == 0
&& season.IndexNumber.HasValue && season.IndexNumber.Value != 0)
{
- return string.Format(_localization.GetLocalizedString("ValueSpecialEpisodeName"), item.Name);
+ return string.Format(
+ CultureInfo.InvariantCulture,
+ _localization.GetLocalizedString("ValueSpecialEpisodeName"),
+ episode.Name);
}
- if (item.IndexNumber.HasValue)
- {
- var number = item.IndexNumber.Value.ToString("00", CultureInfo.InvariantCulture);
+ // inside a season use simple format (ex. '12 - Episode Name')
+ var epNumberName = GetEpisodeIndexFullName(episode);
+ components = new[] { epNumberName, episode.Name };
+ }
+ else
+ {
+ // outside a season include series and season details (ex. 'TV Show - S05E11 - Episode Name')
+ var epNumberName = GetEpisodeNumberDisplayName(episode);
+ components = new[] { episode.SeriesName, epNumberName, episode.Name };
+ }
- if (episode.IndexNumberEnd.HasValue)
- {
- number += "-" + episode.IndexNumberEnd.Value.ToString("00", CultureInfo.InvariantCulture);
- }
+ return string.Join(" - ", components.Where(NotNullOrWhiteSpace));
+ }
+
+ /// <summary>
+ /// Gets complete episode number.
+ /// </summary>
+ /// <param name="episode">The episode.</param>
+ /// <returns>For single episodes returns just the number. For double episodes - current and ending numbers.</returns>
+ private string GetEpisodeIndexFullName(Episode episode)
+ {
+ var name = string.Empty;
+ if (episode.IndexNumber.HasValue)
+ {
+ name += episode.IndexNumber.Value.ToString("00", CultureInfo.InvariantCulture);
- return number + " - " + item.Name;
+ if (episode.IndexNumberEnd.HasValue)
+ {
+ name += "-" + episode.IndexNumberEnd.Value.ToString("00", CultureInfo.InvariantCulture);
}
}
- return item.Name;
+ return name;
+ }
+
+ /// <summary>
+ /// Gets episode number formatted as 'S##E##'.
+ /// </summary>
+ /// <param name="episode">The episode.</param>
+ /// <returns>Formatted episode number.</returns>
+ private string GetEpisodeNumberDisplayName(Episode episode)
+ {
+ var name = string.Empty;
+ var seasonNumber = episode.Season?.IndexNumber;
+
+ if (seasonNumber.HasValue)
+ {
+ name = "S" + seasonNumber.Value.ToString("00", CultureInfo.InvariantCulture);
+ }
+
+ var indexName = GetEpisodeIndexFullName(episode);
+
+ if (!string.IsNullOrWhiteSpace(indexName))
+ {
+ name += "E" + indexName;
+ }
+
+ return name;
}
- private void AddAudioResource(DlnaOptions options, XmlWriter writer, BaseItem audio, string deviceId, Filter filter, StreamInfo streamInfo = null)
+ private bool NotNullOrWhiteSpace(string s) => !string.IsNullOrWhiteSpace(s);
+
+ private void AddAudioResource(XmlWriter writer, BaseItem audio, string deviceId, Filter filter, StreamInfo streamInfo = null)
{
writer.WriteStartElement(string.Empty, "res", NS_DIDL);
@@ -504,7 +584,7 @@ namespace Emby.Dlna.Didl
targetSampleRate,
targetAudioBitDepth);
- var filename = url.Substring(0, url.IndexOf('?'));
+ var filename = url.Substring(0, url.IndexOf('?', StringComparison.Ordinal));
var mimeType = mediaProfile == null || string.IsNullOrEmpty(mediaProfile.MimeType)
? MimeTypes.GetMimeType(filename)
@@ -520,11 +600,13 @@ namespace Emby.Dlna.Didl
streamInfo.RunTimeTicks ?? 0,
streamInfo.TranscodeSeekInfo);
- writer.WriteAttributeString("protocolInfo", string.Format(
- "http-get:*:{0}:{1}",
- mimeType,
- contentFeatures
- ));
+ writer.WriteAttributeString(
+ "protocolInfo",
+ string.Format(
+ CultureInfo.InvariantCulture,
+ "http-get:*:{0}:{1}",
+ mimeType,
+ contentFeatures));
writer.WriteString(url);
@@ -547,7 +629,7 @@ namespace Emby.Dlna.Didl
var clientId = GetClientId(folder, stubType);
- if (string.Equals(requestedId, "0"))
+ if (string.Equals(requestedId, "0", StringComparison.Ordinal))
{
writer.WriteAttributeString("id", "0");
writer.WriteAttributeString("parentID", "-1");
@@ -576,7 +658,7 @@ namespace Emby.Dlna.Didl
AddGeneralProperties(folder, stubType, context, writer, filter);
- AddCover(folder, context, stubType, writer);
+ AddCover(folder, stubType, writer);
writer.WriteFullEndElement();
}
@@ -609,7 +691,10 @@ namespace Emby.Dlna.Didl
if (playbackPositionTicks > 0)
{
- var elementValue = string.Format("BM={0}", Convert.ToInt32(TimeSpan.FromTicks(playbackPositionTicks).TotalSeconds).ToString(_usCulture));
+ var elementValue = string.Format(
+ CultureInfo.InvariantCulture,
+ "BM={0}",
+ Convert.ToInt32(TimeSpan.FromTicks(playbackPositionTicks).TotalSeconds));
AddValue(writer, "sec", "dcmInfo", elementValue, secAttribute.Value);
}
}
@@ -632,7 +717,7 @@ namespace Emby.Dlna.Didl
{
if (item.PremiereDate.HasValue)
{
- AddValue(writer, "dc", "date", item.PremiereDate.Value.ToString("o"), NS_DC);
+ AddValue(writer, "dc", "date", item.PremiereDate.Value.ToString("o", CultureInfo.InvariantCulture), NS_DC);
}
}
@@ -762,37 +847,36 @@ namespace Emby.Dlna.Didl
private void AddPeople(BaseItem item, XmlWriter writer)
{
- //var types = new[]
- //{
- // PersonType.Director,
- // PersonType.Writer,
- // PersonType.Producer,
- // PersonType.Composer,
- // "Creator"
- //};
-
- //var people = _libraryManager.GetPeople(item);
-
- //var index = 0;
-
- //// Seeing some LG models locking up due content with large lists of people
- //// The actual issue might just be due to processing a more metadata than it can handle
- //var limit = 6;
+ if (!item.SupportsPeople)
+ {
+ return;
+ }
- //foreach (var actor in people)
- //{
- // var type = types.FirstOrDefault(i => string.Equals(i, actor.Type, StringComparison.OrdinalIgnoreCase) || string.Equals(i, actor.Role, StringComparison.OrdinalIgnoreCase))
- // ?? PersonType.Actor;
+ var types = new[]
+ {
+ PersonType.Director,
+ PersonType.Writer,
+ PersonType.Producer,
+ PersonType.Composer,
+ "creator"
+ };
- // AddValue(writer, "upnp", type.ToLowerInvariant(), actor.Name, NS_UPNP);
+ // Seeing some LG models locking up due content with large lists of people
+ // The actual issue might just be due to processing a more metadata than it can handle
+ var people = _libraryManager.GetPeople(
+ new InternalPeopleQuery
+ {
+ ItemId = item.Id,
+ Limit = 6
+ });
- // index++;
+ foreach (var actor in people)
+ {
+ var type = types.FirstOrDefault(i => string.Equals(i, actor.Type, StringComparison.OrdinalIgnoreCase) || string.Equals(i, actor.Role, StringComparison.OrdinalIgnoreCase))
+ ?? PersonType.Actor;
- // if (index >= limit)
- // {
- // break;
- // }
- //}
+ AddValue(writer, "upnp", type.ToLowerInvariant(), actor.Name, NS_UPNP);
+ }
}
private void AddGeneralProperties(BaseItem item, StubType? itemStubType, BaseItem context, XmlWriter writer, Filter filter)
@@ -869,7 +953,7 @@ namespace Emby.Dlna.Didl
}
}
- private void AddCover(BaseItem item, BaseItem context, StubType? stubType, XmlWriter writer)
+ private void AddCover(BaseItem item, StubType? stubType, XmlWriter writer)
{
ImageDownloadInfo imageInfo = GetImageInfo(item);
@@ -914,17 +998,8 @@ namespace Emby.Dlna.Didl
}
- private void AddEmbeddedImageAsCover(string name, XmlWriter writer)
- {
- writer.WriteStartElement("upnp", "albumArtURI", NS_UPNP);
- writer.WriteAttributeString("dlna", "profileID", NS_DLNA, _profile.AlbumArtPn);
- writer.WriteString(_serverAddress + "/Dlna/icons/people480.jpg");
- writer.WriteFullEndElement();
-
- writer.WriteElementString("upnp", "icon", NS_UPNP, _serverAddress + "/Dlna/icons/people48.jpg");
- }
-
- private void AddImageResElement(BaseItem item,
+ private void AddImageResElement(
+ BaseItem item,
XmlWriter writer,
int maxWidth,
int maxHeight,
@@ -950,13 +1025,17 @@ namespace Emby.Dlna.Didl
var contentFeatures = new ContentFeatureBuilder(_profile)
.BuildImageHeader(format, width, height, imageInfo.IsDirectStream, org_Pn);
- writer.WriteAttributeString("protocolInfo", string.Format(
- "http-get:*:{0}:{1}",
- MimeTypes.GetMimeType("file." + format),
- contentFeatures
- ));
+ writer.WriteAttributeString(
+ "protocolInfo",
+ string.Format(
+ CultureInfo.InvariantCulture,
+ "http-get:*:{0}:{1}",
+ MimeTypes.GetMimeType("file." + format),
+ contentFeatures));
- writer.WriteAttributeString("resolution", string.Format("{0}x{1}", width, height));
+ writer.WriteAttributeString(
+ "resolution",
+ string.Format(CultureInfo.InvariantCulture, "{0}x{1}", width, height));
writer.WriteString(albumartUrlInfo.Url);
@@ -981,19 +1060,58 @@ namespace Emby.Dlna.Didl
}
}
- item = item.GetParents().FirstOrDefault(i => i.HasImage(ImageType.Primary));
+ // For audio tracks without art use album art if available.
+ if (item is Audio audioItem)
+ {
+ var album = audioItem.AlbumEntity;
+ return album != null && album.HasImage(ImageType.Primary)
+ ? GetImageInfo(album, ImageType.Primary)
+ : null;
+ }
- if (item != null)
+ // Don't look beyond album/playlist level. Metadata service may assign an image from a different album/show to the parent folder.
+ if (item is MusicAlbum || item is Playlist)
{
- if (item.HasImage(ImageType.Primary))
- {
- return GetImageInfo(item, ImageType.Primary);
- }
+ return null;
+ }
+
+ // For other item types check parents, but be aware that image retrieved from a parent may be not suitable for this media item.
+ var parentWithImage = GetFirstParentWithImageBelowUserRoot(item);
+ if (parentWithImage != null)
+ {
+ return GetImageInfo(parentWithImage, ImageType.Primary);
}
return null;
}
+ private BaseItem GetFirstParentWithImageBelowUserRoot(BaseItem item)
+ {
+ if (item == null)
+ {
+ return null;
+ }
+
+ if (item.HasImage(ImageType.Primary))
+ {
+ return item;
+ }
+
+ var parent = item.GetParent();
+ if (parent is UserRootFolder)
+ {
+ return null;
+ }
+
+ // terminate in case we went past user root folder (unlikely?)
+ if (parent is Folder folder && folder.IsRoot)
+ {
+ return null;
+ }
+
+ return GetFirstParentWithImageBelowUserRoot(parent);
+ }
+
private ImageDownloadInfo GetImageInfo(BaseItem item, ImageType type)
{
var imageInfo = item.GetImageInfo(type, 0);
@@ -1095,7 +1213,9 @@ namespace Emby.Dlna.Didl
private ImageUrlInfo GetImageUrl(ImageDownloadInfo info, int maxWidth, int maxHeight, string format)
{
- var url = string.Format("{0}/Items/{1}/Images/{2}/0/{3}/{4}/{5}/{6}/0/0",
+ var url = string.Format(
+ CultureInfo.InvariantCulture,
+ "{0}/Items/{1}/Images/{2}/0/{3}/{4}/{5}/{6}/0/0",
_serverAddress,
info.ItemId.ToString("N", CultureInfo.InvariantCulture),
info.Type,
diff --git a/Emby.Dlna/Didl/Filter.cs b/Emby.Dlna/Didl/Filter.cs
index a0e67870e..412259e90 100644
--- a/Emby.Dlna/Didl/Filter.cs
+++ b/Emby.Dlna/Didl/Filter.cs
@@ -1,5 +1,6 @@
+#pragma warning disable CS1591
+
using System;
-using MediaBrowser.Model.Extensions;
namespace Emby.Dlna.Didl
{
@@ -16,7 +17,7 @@ namespace Emby.Dlna.Didl
public Filter(string filter)
{
- _all = StringHelper.EqualsIgnoreCase(filter, "*");
+ _all = string.Equals(filter, "*", StringComparison.OrdinalIgnoreCase);
_fields = (filter ?? string.Empty).Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
}
diff --git a/Emby.Dlna/Didl/StringWriterWithEncoding.cs b/Emby.Dlna/Didl/StringWriterWithEncoding.cs
index c3c4bd393..896fe992b 100644
--- a/Emby.Dlna/Didl/StringWriterWithEncoding.cs
+++ b/Emby.Dlna/Didl/StringWriterWithEncoding.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
using System;
using System.IO;
using System.Text;
@@ -51,6 +53,6 @@ namespace Emby.Dlna.Didl
_encoding = encoding;
}
- public override Encoding Encoding => (null == _encoding) ? base.Encoding : _encoding;
+ public override Encoding Encoding => _encoding ?? base.Encoding;
}
}
diff --git a/Emby.Dlna/DlnaManager.cs b/Emby.Dlna/DlnaManager.cs
index d5d788021..10f881fe7 100644
--- a/Emby.Dlna/DlnaManager.cs
+++ b/Emby.Dlna/DlnaManager.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
using System;
using System.Collections.Generic;
using System.Globalization;
@@ -385,7 +387,7 @@ namespace Emby.Dlna
{
Directory.CreateDirectory(systemProfilesPath);
- using (var fileStream = _fileSystem.GetFileStream(path, FileOpenMode.Create, FileAccessMode.Write, FileShareMode.Read))
+ using (var fileStream = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.Read))
{
await stream.CopyToAsync(fileStream);
}
diff --git a/Emby.Dlna/Emby.Dlna.csproj b/Emby.Dlna/Emby.Dlna.csproj
index 8d6fabdb4..42a5f95c1 100644
--- a/Emby.Dlna/Emby.Dlna.csproj
+++ b/Emby.Dlna/Emby.Dlna.csproj
@@ -1,5 +1,10 @@
<Project Sdk="Microsoft.NET.Sdk">
+ <!-- ProjectGuid is only included as a requirement for SonarQube analysis -->
+ <PropertyGroup>
+ <ProjectGuid>{805844AB-E92F-45E6-9D99-4F6D48D129A5}</ProjectGuid>
+ </PropertyGroup>
+
<ItemGroup>
<Compile Include="..\SharedVersion.cs" />
</ItemGroup>
@@ -15,6 +20,19 @@
<TargetFramework>netstandard2.1</TargetFramework>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
+ <TreatWarningsAsErrors Condition=" '$(Configuration)' == 'Release'" >true</TreatWarningsAsErrors>
+ </PropertyGroup>
+
+ <!-- Code Analyzers-->
+ <ItemGroup Condition=" '$(Configuration)' == 'Debug' ">
+ <PackageReference Include="Microsoft.CodeAnalysis.FxCopAnalyzers" Version="2.9.8" PrivateAssets="All" />
+ <PackageReference Include="SerilogAnalyzer" Version="0.15.0" PrivateAssets="All" />
+ <PackageReference Include="StyleCop.Analyzers" Version="1.1.118" PrivateAssets="All" />
+ <PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" PrivateAssets="All" />
+ </ItemGroup>
+
+ <PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
+ <CodeAnalysisRuleSet>../jellyfin.ruleset</CodeAnalysisRuleSet>
</PropertyGroup>
<ItemGroup>
diff --git a/Emby.Dlna/EventSubscriptionResponse.cs b/Emby.Dlna/EventSubscriptionResponse.cs
index 6dc1aacf4..fd18343e6 100644
--- a/Emby.Dlna/EventSubscriptionResponse.cs
+++ b/Emby.Dlna/EventSubscriptionResponse.cs
@@ -1,17 +1,20 @@
+#pragma warning disable CS1591
+
using System.Collections.Generic;
namespace Emby.Dlna
{
public class EventSubscriptionResponse
{
- public string Content { get; set; }
- public string ContentType { get; set; }
-
- public Dictionary<string, string> Headers { get; set; }
-
public EventSubscriptionResponse()
{
Headers = new Dictionary<string, string>();
}
+
+ public string Content { get; set; }
+
+ public string ContentType { get; set; }
+
+ public Dictionary<string, string> Headers { get; set; }
}
}
diff --git a/Emby.Dlna/Eventing/EventManager.cs b/Emby.Dlna/Eventing/EventManager.cs
index b76a0066d..efbb53b64 100644
--- a/Emby.Dlna/Eventing/EventManager.cs
+++ b/Emby.Dlna/Eventing/EventManager.cs
@@ -1,8 +1,11 @@
+#pragma warning disable CS1591
+
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
+using System.Net.Http;
using System.Text;
using System.Threading.Tasks;
using MediaBrowser.Common.Extensions;
@@ -164,7 +167,7 @@ namespace Emby.Dlna.Eventing
try
{
- using (await _httpClient.SendAsync(options, "NOTIFY").ConfigureAwait(false))
+ using (await _httpClient.SendAsync(options, new HttpMethod("NOTIFY")).ConfigureAwait(false))
{
}
diff --git a/Emby.Dlna/Eventing/EventSubscription.cs b/Emby.Dlna/Eventing/EventSubscription.cs
index eb8781e0c..51eaee9d7 100644
--- a/Emby.Dlna/Eventing/EventSubscription.cs
+++ b/Emby.Dlna/Eventing/EventSubscription.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
using System;
namespace Emby.Dlna.Eventing
@@ -13,6 +15,8 @@ namespace Emby.Dlna.Eventing
public long TriggerCount { get; set; }
+ public bool IsExpired => SubscriptionTime.AddSeconds(TimeoutSeconds) >= DateTime.UtcNow;
+
public void IncrementTriggerCount()
{
if (TriggerCount == long.MaxValue)
@@ -22,7 +26,5 @@ namespace Emby.Dlna.Eventing
TriggerCount++;
}
-
- public bool IsExpired => SubscriptionTime.AddSeconds(TimeoutSeconds) >= DateTime.UtcNow;
}
}
diff --git a/Emby.Dlna/IConnectionManager.cs b/Emby.Dlna/IConnectionManager.cs
index 855c4454d..7b4a33a98 100644
--- a/Emby.Dlna/IConnectionManager.cs
+++ b/Emby.Dlna/IConnectionManager.cs
@@ -1,3 +1,4 @@
+#pragma warning disable CS1591
namespace Emby.Dlna
{
diff --git a/Emby.Dlna/IContentDirectory.cs b/Emby.Dlna/IContentDirectory.cs
index b54a17c00..83ef09c66 100644
--- a/Emby.Dlna/IContentDirectory.cs
+++ b/Emby.Dlna/IContentDirectory.cs
@@ -1,3 +1,4 @@
+#pragma warning disable CS1591
namespace Emby.Dlna
{
diff --git a/Emby.Dlna/IEventManager.cs b/Emby.Dlna/IEventManager.cs
index 4f67a1b9b..287203389 100644
--- a/Emby.Dlna/IEventManager.cs
+++ b/Emby.Dlna/IEventManager.cs
@@ -1,3 +1,4 @@
+#pragma warning disable CS1591
namespace Emby.Dlna
{
diff --git a/Emby.Dlna/IMediaReceiverRegistrar.cs b/Emby.Dlna/IMediaReceiverRegistrar.cs
index 5dde01f58..b0376b6a9 100644
--- a/Emby.Dlna/IMediaReceiverRegistrar.cs
+++ b/Emby.Dlna/IMediaReceiverRegistrar.cs
@@ -1,3 +1,4 @@
+#pragma warning disable CS1591
namespace Emby.Dlna
{
diff --git a/Emby.Dlna/IUpnpService.cs b/Emby.Dlna/IUpnpService.cs
index ae90e95c7..9e7859567 100644
--- a/Emby.Dlna/IUpnpService.cs
+++ b/Emby.Dlna/IUpnpService.cs
@@ -1,3 +1,7 @@
+#pragma warning disable CS1591
+
+using System.Threading.Tasks;
+
namespace Emby.Dlna
{
public interface IUpnpService
@@ -13,6 +17,6 @@ namespace Emby.Dlna
/// </summary>
/// <param name="request">The request.</param>
/// <returns>ControlResponse.</returns>
- ControlResponse ProcessControlRequest(ControlRequest request);
+ Task<ControlResponse> ProcessControlRequestAsync(ControlRequest request);
}
}
diff --git a/Emby.Dlna/Main/DlnaEntryPoint.cs b/Emby.Dlna/Main/DlnaEntryPoint.cs
index 77bde0ca2..c5d60b2a0 100644
--- a/Emby.Dlna/Main/DlnaEntryPoint.cs
+++ b/Emby.Dlna/Main/DlnaEntryPoint.cs
@@ -1,6 +1,8 @@
+#pragma warning disable CS1591
+
using System;
-using System.Net.Sockets;
using System.Globalization;
+using System.Net.Sockets;
using System.Threading;
using System.Threading.Tasks;
using Emby.Dlna.PlayTo;
@@ -24,7 +26,7 @@ using MediaBrowser.Model.System;
using Microsoft.Extensions.Logging;
using Rssdp;
using Rssdp.Infrastructure;
-using OperatingSystem = MediaBrowser.Common.System.OperatingSystem;
+using OperatingSystem = MediaBrowser.Common.System.OperatingSystem;
namespace Emby.Dlna.Main
{
@@ -56,7 +58,9 @@ namespace Emby.Dlna.Main
private ISsdpCommunicationsServer _communicationsServer;
internal IContentDirectory ContentDirectory { get; private set; }
+
internal IConnectionManager ConnectionManager { get; private set; }
+
internal IMediaReceiverRegistrar MediaReceiverRegistrar { get; private set; }
public static DlnaEntryPoint Current;
@@ -104,7 +108,7 @@ namespace Emby.Dlna.Main
libraryManager,
config,
userManager,
- _logger,
+ loggerFactory.CreateLogger<ContentDirectory.ContentDirectory>(),
httpClient,
localizationManager,
mediaSourceManager,
@@ -112,9 +116,16 @@ namespace Emby.Dlna.Main
mediaEncoder,
tvSeriesManager);
- ConnectionManager = new ConnectionManager.ConnectionManager(dlnaManager, config, _logger, httpClient);
+ ConnectionManager = new ConnectionManager.ConnectionManager(
+ dlnaManager,
+ config,
+ loggerFactory.CreateLogger<ConnectionManager.ConnectionManager>(),
+ httpClient);
- MediaReceiverRegistrar = new MediaReceiverRegistrar.MediaReceiverRegistrar(_logger, httpClient, config);
+ MediaReceiverRegistrar = new MediaReceiverRegistrar.MediaReceiverRegistrar(
+ loggerFactory.CreateLogger<MediaReceiverRegistrar.MediaReceiverRegistrar>(),
+ httpClient,
+ config);
Current = this;
}
@@ -251,8 +262,8 @@ namespace Emby.Dlna.Main
{
if (address.AddressFamily == AddressFamily.InterNetworkV6)
{
- // Not support IPv6 right now
- continue;
+ // Not supporting IPv6 right now
+ continue;
}
var fullService = "urn:schemas-upnp-org:device:MediaServer:1";
diff --git a/Emby.Dlna/MediaReceiverRegistrar/ControlHandler.cs b/Emby.Dlna/MediaReceiverRegistrar/ControlHandler.cs
index 7381e5258..8bf0cd961 100644
--- a/Emby.Dlna/MediaReceiverRegistrar/ControlHandler.cs
+++ b/Emby.Dlna/MediaReceiverRegistrar/ControlHandler.cs
@@ -1,5 +1,8 @@
+#pragma warning disable CS1591
+
using System;
using System.Collections.Generic;
+using System.Xml;
using Emby.Dlna.Service;
using MediaBrowser.Common.Extensions;
using MediaBrowser.Controller.Configuration;
@@ -9,35 +12,33 @@ namespace Emby.Dlna.MediaReceiverRegistrar
{
public class ControlHandler : BaseControlHandler
{
- protected override IEnumerable<KeyValuePair<string, string>> GetResult(string methodName, IDictionary<string, string> methodParams)
+ public ControlHandler(IServerConfigurationManager config, ILogger logger)
+ : base(config, logger)
{
- if (string.Equals(methodName, "IsAuthorized", StringComparison.OrdinalIgnoreCase))
- return HandleIsAuthorized();
- if (string.Equals(methodName, "IsValidated", StringComparison.OrdinalIgnoreCase))
- return HandleIsValidated();
-
- throw new ResourceNotFoundException("Unexpected control request name: " + methodName);
}
- private static IEnumerable<KeyValuePair<string, string>> HandleIsAuthorized()
+ /// <inheritdoc />
+ protected override void WriteResult(string methodName, IDictionary<string, string> methodParams, XmlWriter xmlWriter)
{
- return new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
+ if (string.Equals(methodName, "IsAuthorized", StringComparison.OrdinalIgnoreCase))
{
- { "Result", "1" }
- };
- }
+ HandleIsAuthorized(xmlWriter);
+ return;
+ }
- private static IEnumerable<KeyValuePair<string, string>> HandleIsValidated()
- {
- return new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
+ if (string.Equals(methodName, "IsValidated", StringComparison.OrdinalIgnoreCase))
{
- { "Result", "1" }
- };
- }
+ HandleIsValidated(xmlWriter);
+ return;
+ }
- public ControlHandler(IServerConfigurationManager config, ILogger logger)
- : base(config, logger)
- {
+ throw new ResourceNotFoundException("Unexpected control request name: " + methodName);
}
+
+ private static void HandleIsAuthorized(XmlWriter xmlWriter)
+ => xmlWriter.WriteElementString("Result", "1");
+
+ private static void HandleIsValidated(XmlWriter xmlWriter)
+ => xmlWriter.WriteElementString("Result", "1");
}
}
diff --git a/Emby.Dlna/MediaReceiverRegistrar/MediaReceiverRegistrar.cs b/Emby.Dlna/MediaReceiverRegistrar/MediaReceiverRegistrar.cs
index b565cb631..64dfc840a 100644
--- a/Emby.Dlna/MediaReceiverRegistrar/MediaReceiverRegistrar.cs
+++ b/Emby.Dlna/MediaReceiverRegistrar/MediaReceiverRegistrar.cs
@@ -1,3 +1,6 @@
+#pragma warning disable CS1591
+
+using System.Threading.Tasks;
using Emby.Dlna.Service;
using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Configuration;
@@ -9,23 +12,28 @@ namespace Emby.Dlna.MediaReceiverRegistrar
{
private readonly IServerConfigurationManager _config;
- public MediaReceiverRegistrar(ILogger logger, IHttpClient httpClient, IServerConfigurationManager config)
+ public MediaReceiverRegistrar(
+ ILogger<MediaReceiverRegistrar> logger,
+ IHttpClient httpClient,
+ IServerConfigurationManager config)
: base(logger, httpClient)
{
_config = config;
}
+ /// <inheritdoc />
public string GetServiceXml()
{
return new MediaReceiverRegistrarXmlBuilder().GetXml();
}
- public ControlResponse ProcessControlRequest(ControlRequest request)
+ /// <inheritdoc />
+ public Task<ControlResponse> ProcessControlRequestAsync(ControlRequest request)
{
return new ControlHandler(
_config,
Logger)
- .ProcessControlRequest(request);
+ .ProcessControlRequestAsync(request);
}
}
}
diff --git a/Emby.Dlna/MediaReceiverRegistrar/MediaReceiverRegistrarXmlBuilder.cs b/Emby.Dlna/MediaReceiverRegistrar/MediaReceiverRegistrarXmlBuilder.cs
index 641341185..849702546 100644
--- a/Emby.Dlna/MediaReceiverRegistrar/MediaReceiverRegistrarXmlBuilder.cs
+++ b/Emby.Dlna/MediaReceiverRegistrar/MediaReceiverRegistrarXmlBuilder.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
using System.Collections.Generic;
using Emby.Dlna.Common;
using Emby.Dlna.Service;
diff --git a/Emby.Dlna/MediaReceiverRegistrar/ServiceActionListBuilder.cs b/Emby.Dlna/MediaReceiverRegistrar/ServiceActionListBuilder.cs
index 86429f605..13545c689 100644
--- a/Emby.Dlna/MediaReceiverRegistrar/ServiceActionListBuilder.cs
+++ b/Emby.Dlna/MediaReceiverRegistrar/ServiceActionListBuilder.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
using System.Collections.Generic;
using Emby.Dlna.Common;
diff --git a/Emby.Dlna/PlayTo/Device.cs b/Emby.Dlna/PlayTo/Device.cs
index 0c5ddee65..6abc3a82c 100644
--- a/Emby.Dlna/PlayTo/Device.cs
+++ b/Emby.Dlna/PlayTo/Device.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
using System;
using System.Collections.Generic;
using System.Globalization;
@@ -221,7 +223,7 @@ namespace Emby.Dlna.PlayTo
_logger.LogDebug("Setting mute");
var value = mute ? 1 : 0;
- await new SsdpHttpClient(_httpClient, _config).SendCommandAsync(Properties.BaseUrl, service, command.Name, rendererCommands.BuildPost(command, service.ServiceType, value))
+ await new SsdpHttpClient(_httpClient).SendCommandAsync(Properties.BaseUrl, service, command.Name, rendererCommands.BuildPost(command, service.ServiceType, value))
.ConfigureAwait(false);
IsMuted = mute;
@@ -251,7 +253,7 @@ namespace Emby.Dlna.PlayTo
// Remote control will perform better
Volume = value;
- await new SsdpHttpClient(_httpClient, _config).SendCommandAsync(Properties.BaseUrl, service, command.Name, rendererCommands.BuildPost(command, service.ServiceType, value))
+ await new SsdpHttpClient(_httpClient).SendCommandAsync(Properties.BaseUrl, service, command.Name, rendererCommands.BuildPost(command, service.ServiceType, value))
.ConfigureAwait(false);
}
@@ -270,7 +272,7 @@ namespace Emby.Dlna.PlayTo
throw new InvalidOperationException("Unable to find service");
}
- await new SsdpHttpClient(_httpClient, _config).SendCommandAsync(Properties.BaseUrl, service, command.Name, avCommands.BuildPost(command, service.ServiceType, string.Format("{0:hh}:{0:mm}:{0:ss}", value), "REL_TIME"))
+ await new SsdpHttpClient(_httpClient).SendCommandAsync(Properties.BaseUrl, service, command.Name, avCommands.BuildPost(command, service.ServiceType, string.Format("{0:hh}:{0:mm}:{0:ss}", value), "REL_TIME"))
.ConfigureAwait(false);
RestartTimer(true);
@@ -302,7 +304,7 @@ namespace Emby.Dlna.PlayTo
}
var post = avCommands.BuildPost(command, service.ServiceType, url, dictionary);
- await new SsdpHttpClient(_httpClient, _config).SendCommandAsync(Properties.BaseUrl, service, command.Name, post, header: header)
+ await new SsdpHttpClient(_httpClient).SendCommandAsync(Properties.BaseUrl, service, command.Name, post, header: header)
.ConfigureAwait(false);
await Task.Delay(50).ConfigureAwait(false);
@@ -344,7 +346,12 @@ namespace Emby.Dlna.PlayTo
throw new InvalidOperationException("Unable to find service");
}
- return new SsdpHttpClient(_httpClient, _config).SendCommandAsync(Properties.BaseUrl, service, command.Name, avCommands.BuildPost(command, service.ServiceType, 1));
+ return new SsdpHttpClient(_httpClient).SendCommandAsync(
+ Properties.BaseUrl,
+ service,
+ command.Name,
+ avCommands.BuildPost(command, service.ServiceType, 1),
+ cancellationToken: cancellationToken);
}
public async Task SetPlay(CancellationToken cancellationToken)
@@ -368,7 +375,7 @@ namespace Emby.Dlna.PlayTo
var service = GetAvTransportService();
- await new SsdpHttpClient(_httpClient, _config).SendCommandAsync(Properties.BaseUrl, service, command.Name, avCommands.BuildPost(command, service.ServiceType, 1))
+ await new SsdpHttpClient(_httpClient).SendCommandAsync(Properties.BaseUrl, service, command.Name, avCommands.BuildPost(command, service.ServiceType, 1))
.ConfigureAwait(false);
RestartTimer(true);
@@ -386,7 +393,7 @@ namespace Emby.Dlna.PlayTo
var service = GetAvTransportService();
- await new SsdpHttpClient(_httpClient, _config).SendCommandAsync(Properties.BaseUrl, service, command.Name, avCommands.BuildPost(command, service.ServiceType, 1))
+ await new SsdpHttpClient(_httpClient).SendCommandAsync(Properties.BaseUrl, service, command.Name, avCommands.BuildPost(command, service.ServiceType, 1))
.ConfigureAwait(false);
TransportState = TRANSPORTSTATE.PAUSED;
@@ -513,8 +520,12 @@ namespace Emby.Dlna.PlayTo
return;
}
- var result = await new SsdpHttpClient(_httpClient, _config).SendCommandAsync(Properties.BaseUrl, service, command.Name, rendererCommands.BuildPost(command, service.ServiceType), true)
- .ConfigureAwait(false);
+ var result = await new SsdpHttpClient(_httpClient).SendCommandAsync(
+ Properties.BaseUrl,
+ service,
+ command.Name,
+ rendererCommands.BuildPost(command, service.ServiceType),
+ cancellationToken: cancellationToken).ConfigureAwait(false);
if (result == null || result.Document == null)
{
@@ -559,8 +570,12 @@ namespace Emby.Dlna.PlayTo
return;
}
- var result = await new SsdpHttpClient(_httpClient, _config).SendCommandAsync(Properties.BaseUrl, service, command.Name, rendererCommands.BuildPost(command, service.ServiceType), true)
- .ConfigureAwait(false);
+ var result = await new SsdpHttpClient(_httpClient).SendCommandAsync(
+ Properties.BaseUrl,
+ service,
+ command.Name,
+ rendererCommands.BuildPost(command, service.ServiceType),
+ cancellationToken: cancellationToken).ConfigureAwait(false);
if (result == null || result.Document == null)
return;
@@ -586,8 +601,12 @@ namespace Emby.Dlna.PlayTo
return null;
}
- var result = await new SsdpHttpClient(_httpClient, _config).SendCommandAsync(Properties.BaseUrl, service, command.Name, avCommands.BuildPost(command, service.ServiceType), false)
- .ConfigureAwait(false);
+ var result = await new SsdpHttpClient(_httpClient).SendCommandAsync(
+ Properties.BaseUrl,
+ service,
+ command.Name,
+ avCommands.BuildPost(command, service.ServiceType),
+ cancellationToken: cancellationToken).ConfigureAwait(false);
if (result == null || result.Document == null)
{
@@ -597,7 +616,7 @@ namespace Emby.Dlna.PlayTo
var transportState =
result.Document.Descendants(uPnpNamespaces.AvTransport + "GetTransportInfoResponse").Select(i => i.Element("CurrentTransportState")).FirstOrDefault(i => i != null);
- var transportStateValue = transportState == null ? null : transportState.Value;
+ var transportStateValue = transportState?.Value;
if (transportStateValue != null
&& Enum.TryParse(transportStateValue, true, out TRANSPORTSTATE state))
@@ -624,8 +643,12 @@ namespace Emby.Dlna.PlayTo
var rendererCommands = await GetRenderingProtocolAsync(cancellationToken).ConfigureAwait(false);
- var result = await new SsdpHttpClient(_httpClient, _config).SendCommandAsync(Properties.BaseUrl, service, command.Name, rendererCommands.BuildPost(command, service.ServiceType), false)
- .ConfigureAwait(false);
+ var result = await new SsdpHttpClient(_httpClient).SendCommandAsync(
+ Properties.BaseUrl,
+ service,
+ command.Name,
+ rendererCommands.BuildPost(command, service.ServiceType),
+ cancellationToken: cancellationToken).ConfigureAwait(false);
if (result == null || result.Document == null)
{
@@ -687,8 +710,12 @@ namespace Emby.Dlna.PlayTo
var rendererCommands = await GetRenderingProtocolAsync(cancellationToken).ConfigureAwait(false);
- var result = await new SsdpHttpClient(_httpClient, _config).SendCommandAsync(Properties.BaseUrl, service, command.Name, rendererCommands.BuildPost(command, service.ServiceType), false)
- .ConfigureAwait(false);
+ var result = await new SsdpHttpClient(_httpClient).SendCommandAsync(
+ Properties.BaseUrl,
+ service,
+ command.Name,
+ rendererCommands.BuildPost(command, service.ServiceType),
+ cancellationToken: cancellationToken).ConfigureAwait(false);
if (result == null || result.Document == null)
{
@@ -868,7 +895,7 @@ namespace Emby.Dlna.PlayTo
string url = NormalizeUrl(Properties.BaseUrl, avService.ScpdUrl);
- var httpClient = new SsdpHttpClient(_httpClient, _config);
+ var httpClient = new SsdpHttpClient(_httpClient);
var document = await httpClient.GetDataAsync(url, cancellationToken).ConfigureAwait(false);
@@ -896,7 +923,7 @@ namespace Emby.Dlna.PlayTo
string url = NormalizeUrl(Properties.BaseUrl, avService.ScpdUrl);
- var httpClient = new SsdpHttpClient(_httpClient, _config);
+ var httpClient = new SsdpHttpClient(_httpClient);
_logger.LogDebug("Dlna Device.GetRenderingProtocolAsync");
var document = await httpClient.GetDataAsync(url, cancellationToken).ConfigureAwait(false);
@@ -931,7 +958,7 @@ namespace Emby.Dlna.PlayTo
public static async Task<Device> CreateuPnpDeviceAsync(Uri url, IHttpClient httpClient, IServerConfigurationManager config, ILogger logger, CancellationToken cancellationToken)
{
- var ssdpHttpClient = new SsdpHttpClient(httpClient, config);
+ var ssdpHttpClient = new SsdpHttpClient(httpClient);
var document = await ssdpHttpClient.GetDataAsync(url.ToString(), cancellationToken).ConfigureAwait(false);
diff --git a/Emby.Dlna/PlayTo/DeviceInfo.cs b/Emby.Dlna/PlayTo/DeviceInfo.cs
index 9e7c04bdb..f3aaaebc4 100644
--- a/Emby.Dlna/PlayTo/DeviceInfo.cs
+++ b/Emby.Dlna/PlayTo/DeviceInfo.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
using System.Collections.Generic;
using Emby.Dlna.Common;
using MediaBrowser.Model.Dlna;
diff --git a/Emby.Dlna/PlayTo/PlayToController.cs b/Emby.Dlna/PlayTo/PlayToController.cs
index a215c9f4b..9d7c0d365 100644
--- a/Emby.Dlna/PlayTo/PlayToController.cs
+++ b/Emby.Dlna/PlayTo/PlayToController.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
using System;
using System.Collections.Generic;
using System.Globalization;
@@ -25,6 +27,8 @@ namespace Emby.Dlna.PlayTo
{
public class PlayToController : ISessionController, IDisposable
{
+ private static readonly CultureInfo _usCulture = CultureInfo.ReadOnly(new CultureInfo("en-US"));
+
private Device _device;
private readonly SessionInfo _session;
private readonly ISessionManager _sessionManager;
@@ -43,9 +47,10 @@ namespace Emby.Dlna.PlayTo
private readonly string _serverAddress;
private readonly string _accessToken;
- public bool IsSessionActive => !_disposed && _device != null;
+ private readonly List<PlaylistItem> _playlist = new List<PlaylistItem>();
+ private int _currentPlaylistIndex;
- public bool SupportsMediaControl => IsSessionActive;
+ private bool _disposed;
public PlayToController(
SessionInfo session,
@@ -81,18 +86,22 @@ namespace Emby.Dlna.PlayTo
_mediaEncoder = mediaEncoder;
}
+ public bool IsSessionActive => !_disposed && _device != null;
+
+ public bool SupportsMediaControl => IsSessionActive;
+
public void Init(Device device)
{
_device = device;
_device.OnDeviceUnavailable = OnDeviceUnavailable;
- _device.PlaybackStart += _device_PlaybackStart;
- _device.PlaybackProgress += _device_PlaybackProgress;
- _device.PlaybackStopped += _device_PlaybackStopped;
- _device.MediaChanged += _device_MediaChanged;
+ _device.PlaybackStart += OnDevicePlaybackStart;
+ _device.PlaybackProgress += OnDevicePlaybackProgress;
+ _device.PlaybackStopped += OnDevicePlaybackStopped;
+ _device.MediaChanged += OnDeviceMediaChanged;
_device.Start();
- _deviceDiscovery.DeviceLeft += _deviceDiscovery_DeviceLeft;
+ _deviceDiscovery.DeviceLeft += OnDeviceDiscoveryDeviceLeft;
}
private void OnDeviceUnavailable()
@@ -108,7 +117,7 @@ namespace Emby.Dlna.PlayTo
}
}
- void _deviceDiscovery_DeviceLeft(object sender, GenericEventArgs<UpnpDeviceInfo> e)
+ private void OnDeviceDiscoveryDeviceLeft(object sender, GenericEventArgs<UpnpDeviceInfo> e)
{
var info = e.Argument;
@@ -123,7 +132,7 @@ namespace Emby.Dlna.PlayTo
}
}
- async void _device_MediaChanged(object sender, MediaChangedEventArgs e)
+ private async void OnDeviceMediaChanged(object sender, MediaChangedEventArgs e)
{
if (_disposed)
{
@@ -135,15 +144,15 @@ namespace Emby.Dlna.PlayTo
var streamInfo = StreamParams.ParseFromUrl(e.OldMediaInfo.Url, _libraryManager, _mediaSourceManager);
if (streamInfo.Item != null)
{
- var positionTicks = GetProgressPositionTicks(e.OldMediaInfo, streamInfo);
+ var positionTicks = GetProgressPositionTicks(streamInfo);
- ReportPlaybackStopped(e.OldMediaInfo, streamInfo, positionTicks);
+ ReportPlaybackStopped(streamInfo, positionTicks);
}
streamInfo = StreamParams.ParseFromUrl(e.NewMediaInfo.Url, _libraryManager, _mediaSourceManager);
if (streamInfo.Item == null) return;
- var newItemProgress = GetProgressInfo(e.NewMediaInfo, streamInfo);
+ var newItemProgress = GetProgressInfo(streamInfo);
await _sessionManager.OnPlaybackStart(newItemProgress).ConfigureAwait(false);
}
@@ -153,7 +162,7 @@ namespace Emby.Dlna.PlayTo
}
}
- async void _device_PlaybackStopped(object sender, PlaybackStoppedEventArgs e)
+ private async void OnDevicePlaybackStopped(object sender, PlaybackStoppedEventArgs e)
{
if (_disposed)
{
@@ -166,9 +175,9 @@ namespace Emby.Dlna.PlayTo
if (streamInfo.Item == null) return;
- var positionTicks = GetProgressPositionTicks(e.MediaInfo, streamInfo);
+ var positionTicks = GetProgressPositionTicks(streamInfo);
- ReportPlaybackStopped(e.MediaInfo, streamInfo, positionTicks);
+ ReportPlaybackStopped(streamInfo, positionTicks);
var mediaSource = await streamInfo.GetMediaSource(CancellationToken.None).ConfigureAwait(false);
@@ -192,7 +201,7 @@ namespace Emby.Dlna.PlayTo
}
else
{
- Playlist.Clear();
+ _playlist.Clear();
}
}
catch (Exception ex)
@@ -201,7 +210,7 @@ namespace Emby.Dlna.PlayTo
}
}
- private async void ReportPlaybackStopped(uBaseObject mediaInfo, StreamParams streamInfo, long? positionTicks)
+ private async void ReportPlaybackStopped(StreamParams streamInfo, long? positionTicks)
{
try
{
@@ -220,7 +229,7 @@ namespace Emby.Dlna.PlayTo
}
}
- async void _device_PlaybackStart(object sender, PlaybackStartEventArgs e)
+ private async void OnDevicePlaybackStart(object sender, PlaybackStartEventArgs e)
{
if (_disposed)
{
@@ -233,7 +242,7 @@ namespace Emby.Dlna.PlayTo
if (info.Item != null)
{
- var progress = GetProgressInfo(e.MediaInfo, info);
+ var progress = GetProgressInfo(info);
await _sessionManager.OnPlaybackStart(progress).ConfigureAwait(false);
}
@@ -244,7 +253,7 @@ namespace Emby.Dlna.PlayTo
}
}
- async void _device_PlaybackProgress(object sender, PlaybackProgressEventArgs e)
+ private async void OnDevicePlaybackProgress(object sender, PlaybackProgressEventArgs e)
{
if (_disposed)
{
@@ -264,7 +273,7 @@ namespace Emby.Dlna.PlayTo
if (info.Item != null)
{
- var progress = GetProgressInfo(e.MediaInfo, info);
+ var progress = GetProgressInfo(info);
await _sessionManager.OnPlaybackProgress(progress).ConfigureAwait(false);
}
@@ -275,7 +284,7 @@ namespace Emby.Dlna.PlayTo
}
}
- private long? GetProgressPositionTicks(uBaseObject mediaInfo, StreamParams info)
+ private long? GetProgressPositionTicks(StreamParams info)
{
var ticks = _device.Position.Ticks;
@@ -287,13 +296,13 @@ namespace Emby.Dlna.PlayTo
return ticks;
}
- private PlaybackStartInfo GetProgressInfo(uBaseObject mediaInfo, StreamParams info)
+ private PlaybackStartInfo GetProgressInfo(StreamParams info)
{
return new PlaybackStartInfo
{
ItemId = info.ItemId,
SessionId = _session.Id,
- PositionTicks = GetProgressPositionTicks(mediaInfo, info),
+ PositionTicks = GetProgressPositionTicks(info),
IsMuted = _device.IsMuted,
IsPaused = _device.IsPaused,
MediaSourceId = info.MediaSourceId,
@@ -308,9 +317,7 @@ namespace Emby.Dlna.PlayTo
};
}
- #region SendCommands
-
- public async Task SendPlayCommand(PlayRequest command, CancellationToken cancellationToken)
+ public Task SendPlayCommand(PlayRequest command, CancellationToken cancellationToken)
{
_logger.LogDebug("{0} - Received PlayRequest: {1}", this._session.DeviceName, command.PlayCommand);
@@ -348,11 +355,12 @@ namespace Emby.Dlna.PlayTo
if (command.PlayCommand == PlayCommand.PlayLast)
{
- Playlist.AddRange(playlist);
+ _playlist.AddRange(playlist);
}
+
if (command.PlayCommand == PlayCommand.PlayNext)
{
- Playlist.AddRange(playlist);
+ _playlist.AddRange(playlist);
}
if (!command.ControllingUserId.Equals(Guid.Empty))
@@ -361,7 +369,7 @@ namespace Emby.Dlna.PlayTo
_session.DeviceName, _session.RemoteEndPoint, user);
}
- await PlayItems(playlist).ConfigureAwait(false);
+ return PlayItems(playlist, cancellationToken);
}
private Task SendPlaystateCommand(PlaystateRequest command, CancellationToken cancellationToken)
@@ -369,7 +377,7 @@ namespace Emby.Dlna.PlayTo
switch (command.Command)
{
case PlaystateCommand.Stop:
- Playlist.Clear();
+ _playlist.Clear();
return _device.SetStop(CancellationToken.None);
case PlaystateCommand.Pause:
@@ -385,10 +393,10 @@ namespace Emby.Dlna.PlayTo
return Seek(command.SeekPositionTicks ?? 0);
case PlaystateCommand.NextTrack:
- return SetPlaylistIndex(_currentPlaylistIndex + 1);
+ return SetPlaylistIndex(_currentPlaylistIndex + 1, cancellationToken);
case PlaystateCommand.PreviousTrack:
- return SetPlaylistIndex(_currentPlaylistIndex - 1);
+ return SetPlaylistIndex(_currentPlaylistIndex - 1, cancellationToken);
}
return Task.CompletedTask;
@@ -424,14 +432,6 @@ namespace Emby.Dlna.PlayTo
return info.IsDirectStream;
}
- #endregion
-
- #region Playlist
-
- private int _currentPlaylistIndex;
- private readonly List<PlaylistItem> _playlist = new List<PlaylistItem>();
- private List<PlaylistItem> Playlist => _playlist;
-
private void AddItemFromId(Guid id, List<BaseItem> list)
{
var item = _libraryManager.GetItemById(id);
@@ -449,7 +449,7 @@ namespace Emby.Dlna.PlayTo
_dlnaManager.GetDefaultProfile();
var mediaSources = item is IHasMediaSources
- ? (_mediaSourceManager.GetStaticMediaSources(item, true, user))
+ ? _mediaSourceManager.GetStaticMediaSources(item, true, user)
: new List<MediaSourceInfo>();
var playlistItem = GetPlaylistItem(item, mediaSources, profile, _session.DeviceId, mediaSourceId, audioStreamIndex, subtitleStreamIndex);
@@ -457,8 +457,19 @@ namespace Emby.Dlna.PlayTo
playlistItem.StreamUrl = DidlBuilder.NormalizeDlnaMediaUrl(playlistItem.StreamInfo.ToUrl(_serverAddress, _accessToken));
- var itemXml = new DidlBuilder(profile, user, _imageProcessor, _serverAddress, _accessToken, _userDataManager, _localization, _mediaSourceManager, _logger, _mediaEncoder)
- .GetItemDidl(_config.GetDlnaConfiguration(), item, user, null, _session.DeviceId, new Filter(), playlistItem.StreamInfo);
+ var itemXml = new DidlBuilder(
+ profile,
+ user,
+ _imageProcessor,
+ _serverAddress,
+ _accessToken,
+ _userDataManager,
+ _localization,
+ _mediaSourceManager,
+ _logger,
+ _mediaEncoder,
+ _libraryManager)
+ .GetItemDidl(item, user, null, _session.DeviceId, new Filter(), playlistItem.StreamInfo);
playlistItem.Didl = itemXml;
@@ -568,30 +579,31 @@ namespace Emby.Dlna.PlayTo
/// Plays the items.
/// </summary>
/// <param name="items">The items.</param>
- /// <returns></returns>
- private async Task<bool> PlayItems(IEnumerable<PlaylistItem> items)
+ /// <param name="cancellationToken">The cancellation token.</param>
+ /// <returns><c>true</c> on success.</returns>
+ private async Task<bool> PlayItems(IEnumerable<PlaylistItem> items, CancellationToken cancellationToken = default)
{
- Playlist.Clear();
- Playlist.AddRange(items);
- _logger.LogDebug("{0} - Playing {1} items", _session.DeviceName, Playlist.Count);
+ _playlist.Clear();
+ _playlist.AddRange(items);
+ _logger.LogDebug("{0} - Playing {1} items", _session.DeviceName, _playlist.Count);
- await SetPlaylistIndex(0).ConfigureAwait(false);
+ await SetPlaylistIndex(0, cancellationToken).ConfigureAwait(false);
return true;
}
- private async Task SetPlaylistIndex(int index)
+ private async Task SetPlaylistIndex(int index, CancellationToken cancellationToken = default)
{
- if (index < 0 || index >= Playlist.Count)
+ if (index < 0 || index >= _playlist.Count)
{
- Playlist.Clear();
- await _device.SetStop(CancellationToken.None);
+ _playlist.Clear();
+ await _device.SetStop(cancellationToken).ConfigureAwait(false);
return;
}
_currentPlaylistIndex = index;
- var currentitem = Playlist[index];
+ var currentitem = _playlist[index];
- await _device.SetAvTransport(currentitem.StreamUrl, GetDlnaHeaders(currentitem), currentitem.Didl, CancellationToken.None);
+ await _device.SetAvTransport(currentitem.StreamUrl, GetDlnaHeaders(currentitem), currentitem.Didl, cancellationToken).ConfigureAwait(false);
var streamInfo = currentitem.StreamInfo;
if (streamInfo.StartPositionTicks > 0 && EnableClientSideSeek(streamInfo))
@@ -600,10 +612,7 @@ namespace Emby.Dlna.PlayTo
}
}
- #endregion
-
- private bool _disposed;
-
+ /// <inheritdoc />
public void Dispose()
{
Dispose(true);
@@ -622,19 +631,17 @@ namespace Emby.Dlna.PlayTo
_device.Dispose();
}
- _device.PlaybackStart -= _device_PlaybackStart;
- _device.PlaybackProgress -= _device_PlaybackProgress;
- _device.PlaybackStopped -= _device_PlaybackStopped;
- _device.MediaChanged -= _device_MediaChanged;
- _deviceDiscovery.DeviceLeft -= _deviceDiscovery_DeviceLeft;
+ _device.PlaybackStart -= OnDevicePlaybackStart;
+ _device.PlaybackProgress -= OnDevicePlaybackProgress;
+ _device.PlaybackStopped -= OnDevicePlaybackStopped;
+ _device.MediaChanged -= OnDeviceMediaChanged;
+ _deviceDiscovery.DeviceLeft -= OnDeviceDiscoveryDeviceLeft;
_device.OnDeviceUnavailable = null;
_device = null;
_disposed = true;
}
- private static readonly CultureInfo _usCulture = CultureInfo.ReadOnly(new CultureInfo("en-US"));
-
private Task SendGeneralCommand(GeneralCommand command, CancellationToken cancellationToken)
{
if (Enum.TryParse(command.Name, true, out GeneralCommandType commandType))
@@ -711,7 +718,7 @@ namespace Emby.Dlna.PlayTo
if (info.Item != null)
{
- var newPosition = GetProgressPositionTicks(media, info) ?? 0;
+ var newPosition = GetProgressPositionTicks(info) ?? 0;
var user = !_session.UserId.Equals(Guid.Empty) ? _userManager.GetUserById(_session.UserId) : null;
var newItem = CreatePlaylistItem(info.Item, user, newPosition, info.MediaSourceId, newIndex, info.SubtitleStreamIndex);
@@ -736,7 +743,7 @@ namespace Emby.Dlna.PlayTo
if (info.Item != null)
{
- var newPosition = GetProgressPositionTicks(media, info) ?? 0;
+ var newPosition = GetProgressPositionTicks(info) ?? 0;
var user = !_session.UserId.Equals(Guid.Empty) ? _userManager.GetUserById(_session.UserId) : null;
var newItem = CreatePlaylistItem(info.Item, user, newPosition, info.MediaSourceId, info.AudioStreamIndex, newIndex);
@@ -850,8 +857,11 @@ namespace Emby.Dlna.PlayTo
return request;
}
- var index = url.IndexOf('?');
- if (index == -1) return request;
+ var index = url.IndexOf('?', StringComparison.Ordinal);
+ if (index == -1)
+ {
+ return request;
+ }
var query = url.Substring(index + 1);
Dictionary<string, string> values = QueryHelpers.ParseQuery(query).ToDictionary(kv => kv.Key, kv => kv.Value.ToString());
diff --git a/Emby.Dlna/PlayTo/PlayToManager.cs b/Emby.Dlna/PlayTo/PlayToManager.cs
index 943d52b0d..bbedd1485 100644
--- a/Emby.Dlna/PlayTo/PlayToManager.cs
+++ b/Emby.Dlna/PlayTo/PlayToManager.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
using System;
using System.Globalization;
using System.Linq;
@@ -21,7 +23,7 @@ using Microsoft.Extensions.Logging;
namespace Emby.Dlna.PlayTo
{
- public class PlayToManager : IDisposable
+ public sealed class PlayToManager : IDisposable
{
private readonly ILogger _logger;
private readonly ISessionManager _sessionManager;
@@ -64,10 +66,10 @@ namespace Emby.Dlna.PlayTo
public void Start()
{
- _deviceDiscovery.DeviceDiscovered += _deviceDiscovery_DeviceDiscovered;
+ _deviceDiscovery.DeviceDiscovered += OnDeviceDiscoveryDeviceDiscovered;
}
- async void _deviceDiscovery_DeviceDiscovered(object sender, GenericEventArgs<UpnpDeviceInfo> e)
+ private async void OnDeviceDiscoveryDeviceDiscovered(object sender, GenericEventArgs<UpnpDeviceInfo> e)
{
if (_disposed)
{
@@ -229,9 +231,10 @@ namespace Emby.Dlna.PlayTo
}
}
+ /// <inheritdoc />
public void Dispose()
{
- _deviceDiscovery.DeviceDiscovered -= _deviceDiscovery_DeviceDiscovered;
+ _deviceDiscovery.DeviceDiscovered -= OnDeviceDiscoveryDeviceDiscovered;
try
{
@@ -242,6 +245,9 @@ namespace Emby.Dlna.PlayTo
}
+ _sessionLock.Dispose();
+ _disposeCancellationTokenSource.Dispose();
+
_disposed = true;
}
}
diff --git a/Emby.Dlna/PlayTo/PlaybackProgressEventArgs.cs b/Emby.Dlna/PlayTo/PlaybackProgressEventArgs.cs
index ffa56419b..795618df2 100644
--- a/Emby.Dlna/PlayTo/PlaybackProgressEventArgs.cs
+++ b/Emby.Dlna/PlayTo/PlaybackProgressEventArgs.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
using System;
namespace Emby.Dlna.PlayTo
diff --git a/Emby.Dlna/PlayTo/PlaybackStartEventArgs.cs b/Emby.Dlna/PlayTo/PlaybackStartEventArgs.cs
index 8cd8b47ac..27883ca32 100644
--- a/Emby.Dlna/PlayTo/PlaybackStartEventArgs.cs
+++ b/Emby.Dlna/PlayTo/PlaybackStartEventArgs.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
using System;
namespace Emby.Dlna.PlayTo
diff --git a/Emby.Dlna/PlayTo/PlaybackStoppedEventArgs.cs b/Emby.Dlna/PlayTo/PlaybackStoppedEventArgs.cs
index 2afdc324d..3b169e599 100644
--- a/Emby.Dlna/PlayTo/PlaybackStoppedEventArgs.cs
+++ b/Emby.Dlna/PlayTo/PlaybackStoppedEventArgs.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
using System;
namespace Emby.Dlna.PlayTo
diff --git a/Emby.Dlna/PlayTo/PlaylistItem.cs b/Emby.Dlna/PlayTo/PlaylistItem.cs
index 1e62b61e9..85846166c 100644
--- a/Emby.Dlna/PlayTo/PlaylistItem.cs
+++ b/Emby.Dlna/PlayTo/PlaylistItem.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
using MediaBrowser.Model.Dlna;
namespace Emby.Dlna.PlayTo
diff --git a/Emby.Dlna/PlayTo/PlaylistItemFactory.cs b/Emby.Dlna/PlayTo/PlaylistItemFactory.cs
index 446d8e1e6..bedc8b9ad 100644
--- a/Emby.Dlna/PlayTo/PlaylistItemFactory.cs
+++ b/Emby.Dlna/PlayTo/PlaylistItemFactory.cs
@@ -1,4 +1,5 @@
-using System.Globalization;
+#pragma warning disable CS1591
+
using System.IO;
using System.Linq;
using MediaBrowser.Controller.Entities;
diff --git a/Emby.Dlna/PlayTo/SsdpHttpClient.cs b/Emby.Dlna/PlayTo/SsdpHttpClient.cs
index 66c634150..8c1362007 100644
--- a/Emby.Dlna/PlayTo/SsdpHttpClient.cs
+++ b/Emby.Dlna/PlayTo/SsdpHttpClient.cs
@@ -1,13 +1,15 @@
+#pragma warning disable CS1591
+
using System;
using System.Globalization;
using System.IO;
+using System.Net.Http;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Xml.Linq;
using Emby.Dlna.Common;
using MediaBrowser.Common.Net;
-using MediaBrowser.Controller.Configuration;
namespace Emby.Dlna.PlayTo
{
@@ -19,12 +21,10 @@ namespace Emby.Dlna.PlayTo
private readonly CultureInfo _usCulture = new CultureInfo("en-US");
private readonly IHttpClient _httpClient;
- private readonly IServerConfigurationManager _config;
- public SsdpHttpClient(IHttpClient httpClient, IServerConfigurationManager config)
+ public SsdpHttpClient(IHttpClient httpClient)
{
_httpClient = httpClient;
- _config = config;
}
public async Task<XDocument> SendCommandAsync(
@@ -32,18 +32,15 @@ namespace Emby.Dlna.PlayTo
DeviceService service,
string command,
string postData,
- bool logRequest = true,
- string header = null)
+ string header = null,
+ CancellationToken cancellationToken = default)
{
- var cancellationToken = CancellationToken.None;
-
var url = NormalizeServiceUrl(baseUrl, service.ControlUrl);
using (var response = await PostSoapDataAsync(
url,
$"\"{service.ServiceType}#{command}\"",
postData,
header,
- logRequest,
cancellationToken)
.ConfigureAwait(false))
using (var stream = response.Content)
@@ -63,8 +60,10 @@ namespace Emby.Dlna.PlayTo
return serviceUrl;
}
- if (!serviceUrl.StartsWith("/"))
+ if (!serviceUrl.StartsWith("/", StringComparison.Ordinal))
+ {
serviceUrl = "/" + serviceUrl;
+ }
return baseUrl + serviceUrl;
}
@@ -90,7 +89,7 @@ namespace Emby.Dlna.PlayTo
options.RequestHeaders["NT"] = "upnp:event";
options.RequestHeaders["TIMEOUT"] = "Second-" + timeOut.ToString(_usCulture);
- using (await _httpClient.SendAsync(options, "SUBSCRIBE").ConfigureAwait(false))
+ using (await _httpClient.SendAsync(options, new HttpMethod("SUBSCRIBE")).ConfigureAwait(false))
{
}
@@ -110,7 +109,7 @@ namespace Emby.Dlna.PlayTo
options.RequestHeaders["FriendlyName.DLNA.ORG"] = FriendlyName;
- using (var response = await _httpClient.SendAsync(options, "GET").ConfigureAwait(false))
+ using (var response = await _httpClient.SendAsync(options, HttpMethod.Get).ConfigureAwait(false))
using (var stream = response.Content)
using (var reader = new StreamReader(stream, Encoding.UTF8))
{
@@ -125,7 +124,6 @@ namespace Emby.Dlna.PlayTo
string soapAction,
string postData,
string header,
- bool logRequest,
CancellationToken cancellationToken)
{
if (soapAction[0] != '\"')
diff --git a/Emby.Dlna/PlayTo/TRANSPORTSTATE.cs b/Emby.Dlna/PlayTo/TRANSPORTSTATE.cs
index 9f1690b04..7daefeca8 100644
--- a/Emby.Dlna/PlayTo/TRANSPORTSTATE.cs
+++ b/Emby.Dlna/PlayTo/TRANSPORTSTATE.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
namespace Emby.Dlna.PlayTo
{
public enum TRANSPORTSTATE
diff --git a/Emby.Dlna/PlayTo/TransportCommands.cs b/Emby.Dlna/PlayTo/TransportCommands.cs
index 4f9e398e9..c0ce3ab6e 100644
--- a/Emby.Dlna/PlayTo/TransportCommands.cs
+++ b/Emby.Dlna/PlayTo/TransportCommands.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
using System;
using System.Collections.Generic;
using System.Linq;
diff --git a/Emby.Dlna/PlayTo/UpnpContainer.cs b/Emby.Dlna/PlayTo/UpnpContainer.cs
index 943e0347b..e2d7a10f0 100644
--- a/Emby.Dlna/PlayTo/UpnpContainer.cs
+++ b/Emby.Dlna/PlayTo/UpnpContainer.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
using System;
using System.Xml.Linq;
using Emby.Dlna.Ssdp;
diff --git a/Emby.Dlna/PlayTo/uBaseObject.cs b/Emby.Dlna/PlayTo/uBaseObject.cs
index f29a126df..a8ed5692c 100644
--- a/Emby.Dlna/PlayTo/uBaseObject.cs
+++ b/Emby.Dlna/PlayTo/uBaseObject.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
using System;
namespace Emby.Dlna.PlayTo
diff --git a/Emby.Dlna/PlayTo/uPnpNamespaces.cs b/Emby.Dlna/PlayTo/uPnpNamespaces.cs
index 7132ecd15..dc65cdf43 100644
--- a/Emby.Dlna/PlayTo/uPnpNamespaces.cs
+++ b/Emby.Dlna/PlayTo/uPnpNamespaces.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
using System.Xml.Linq;
namespace Emby.Dlna.PlayTo
diff --git a/Emby.Dlna/Profiles/DefaultProfile.cs b/Emby.Dlna/Profiles/DefaultProfile.cs
index ea50bd4a7..2347ebd0d 100644
--- a/Emby.Dlna/Profiles/DefaultProfile.cs
+++ b/Emby.Dlna/Profiles/DefaultProfile.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
using System.Linq;
using MediaBrowser.Model.Dlna;
@@ -10,7 +12,7 @@ namespace Emby.Dlna.Profiles
{
Name = "Generic Device";
- ProtocolInfo = "http-get:*:video/mpeg:*,http-get:*:video/mp4:*,http-get:*:video/vnd.dlna.mpeg-tts:*,http-get:*:video/avi:*,http-get:*:video/x-matroska:*,http-get:*:video/x-ms-wmv:*,http-get:*:video/wtv:*,http-get:*:audio/mpeg:*,http-get:*:audio/mp3:*,http-get:*:audio/mp4:*,http-get:*:audio/x-ms-wma*,http-get:*:audio/wav:*,http-get:*:audio/L16:*,http-get:*image/jpeg:*,http-get:*image/png:*,http-get:*image/gif:*,http-get:*image/tiff:*";
+ ProtocolInfo = "http-get:*:video/mpeg:*,http-get:*:video/mp4:*,http-get:*:video/vnd.dlna.mpeg-tts:*,http-get:*:video/avi:*,http-get:*:video/x-matroska:*,http-get:*:video/x-ms-wmv:*,http-get:*:video/wtv:*,http-get:*:audio/mpeg:*,http-get:*:audio/mp3:*,http-get:*:audio/mp4:*,http-get:*:audio/x-ms-wma:*,http-get:*:audio/wav:*,http-get:*:audio/L16:*,http-get:*:image/jpeg:*,http-get:*:image/png:*,http-get:*:image/gif:*,http-get:*:image/tiff:*";
Manufacturer = "Jellyfin";
ModelDescription = "UPnP/AV 1.0 Compliant Media Server";
diff --git a/Emby.Dlna/Profiles/DenonAvrProfile.cs b/Emby.Dlna/Profiles/DenonAvrProfile.cs
index a73885191..73a87c499 100644
--- a/Emby.Dlna/Profiles/DenonAvrProfile.cs
+++ b/Emby.Dlna/Profiles/DenonAvrProfile.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
using MediaBrowser.Model.Dlna;
namespace Emby.Dlna.Profiles
diff --git a/Emby.Dlna/Profiles/DirectTvProfile.cs b/Emby.Dlna/Profiles/DirectTvProfile.cs
index 317c0976a..5ca388167 100644
--- a/Emby.Dlna/Profiles/DirectTvProfile.cs
+++ b/Emby.Dlna/Profiles/DirectTvProfile.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
using MediaBrowser.Model.Dlna;
namespace Emby.Dlna.Profiles
diff --git a/Emby.Dlna/Profiles/DishHopperJoeyProfile.cs b/Emby.Dlna/Profiles/DishHopperJoeyProfile.cs
index 8d8ab41ca..942e36930 100644
--- a/Emby.Dlna/Profiles/DishHopperJoeyProfile.cs
+++ b/Emby.Dlna/Profiles/DishHopperJoeyProfile.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
using MediaBrowser.Model.Dlna;
namespace Emby.Dlna.Profiles
diff --git a/Emby.Dlna/Profiles/Foobar2000Profile.cs b/Emby.Dlna/Profiles/Foobar2000Profile.cs
index 947194bce..ea3de686a 100644
--- a/Emby.Dlna/Profiles/Foobar2000Profile.cs
+++ b/Emby.Dlna/Profiles/Foobar2000Profile.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
using MediaBrowser.Model.Dlna;
namespace Emby.Dlna.Profiles
diff --git a/Emby.Dlna/Profiles/LgTvProfile.cs b/Emby.Dlna/Profiles/LgTvProfile.cs
index 145685ab1..02301764c 100644
--- a/Emby.Dlna/Profiles/LgTvProfile.cs
+++ b/Emby.Dlna/Profiles/LgTvProfile.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
using MediaBrowser.Model.Dlna;
namespace Emby.Dlna.Profiles
diff --git a/Emby.Dlna/Profiles/LinksysDMA2100Profile.cs b/Emby.Dlna/Profiles/LinksysDMA2100Profile.cs
index 3f0bb4263..1b1423520 100644
--- a/Emby.Dlna/Profiles/LinksysDMA2100Profile.cs
+++ b/Emby.Dlna/Profiles/LinksysDMA2100Profile.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
using MediaBrowser.Model.Dlna;
namespace Emby.Dlna.Profiles
diff --git a/Emby.Dlna/Profiles/MarantzProfile.cs b/Emby.Dlna/Profiles/MarantzProfile.cs
index 162e284be..6cfcc3b82 100644
--- a/Emby.Dlna/Profiles/MarantzProfile.cs
+++ b/Emby.Dlna/Profiles/MarantzProfile.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
using MediaBrowser.Model.Dlna;
namespace Emby.Dlna.Profiles
diff --git a/Emby.Dlna/Profiles/MediaMonkeyProfile.cs b/Emby.Dlna/Profiles/MediaMonkeyProfile.cs
index 53cae4e2f..7161af738 100644
--- a/Emby.Dlna/Profiles/MediaMonkeyProfile.cs
+++ b/Emby.Dlna/Profiles/MediaMonkeyProfile.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
using MediaBrowser.Model.Dlna;
namespace Emby.Dlna.Profiles
diff --git a/Emby.Dlna/Profiles/PanasonicVieraProfile.cs b/Emby.Dlna/Profiles/PanasonicVieraProfile.cs
index 5f31ec484..44c35e142 100644
--- a/Emby.Dlna/Profiles/PanasonicVieraProfile.cs
+++ b/Emby.Dlna/Profiles/PanasonicVieraProfile.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
using MediaBrowser.Model.Dlna;
namespace Emby.Dlna.Profiles
diff --git a/Emby.Dlna/Profiles/PopcornHourProfile.cs b/Emby.Dlna/Profiles/PopcornHourProfile.cs
index aefe8c44f..9e9f6966f 100644
--- a/Emby.Dlna/Profiles/PopcornHourProfile.cs
+++ b/Emby.Dlna/Profiles/PopcornHourProfile.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
using MediaBrowser.Model.Dlna;
namespace Emby.Dlna.Profiles
diff --git a/Emby.Dlna/Profiles/SamsungSmartTvProfile.cs b/Emby.Dlna/Profiles/SamsungSmartTvProfile.cs
index 51a1c8173..4ff2ab9be 100644
--- a/Emby.Dlna/Profiles/SamsungSmartTvProfile.cs
+++ b/Emby.Dlna/Profiles/SamsungSmartTvProfile.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
using MediaBrowser.Model.Dlna;
namespace Emby.Dlna.Profiles
diff --git a/Emby.Dlna/Profiles/SharpSmartTvProfile.cs b/Emby.Dlna/Profiles/SharpSmartTvProfile.cs
index f840cfb34..aa8d434e3 100644
--- a/Emby.Dlna/Profiles/SharpSmartTvProfile.cs
+++ b/Emby.Dlna/Profiles/SharpSmartTvProfile.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
using MediaBrowser.Model.Dlna;
namespace Emby.Dlna.Profiles
diff --git a/Emby.Dlna/Profiles/SonyBlurayPlayer2013.cs b/Emby.Dlna/Profiles/SonyBlurayPlayer2013.cs
index 2af1d3b50..42b066d52 100644
--- a/Emby.Dlna/Profiles/SonyBlurayPlayer2013.cs
+++ b/Emby.Dlna/Profiles/SonyBlurayPlayer2013.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
using MediaBrowser.Model.Dlna;
namespace Emby.Dlna.Profiles
diff --git a/Emby.Dlna/Profiles/SonyBlurayPlayer2014.cs b/Emby.Dlna/Profiles/SonyBlurayPlayer2014.cs
index 3de0b5192..fbdf2c18e 100644
--- a/Emby.Dlna/Profiles/SonyBlurayPlayer2014.cs
+++ b/Emby.Dlna/Profiles/SonyBlurayPlayer2014.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
using MediaBrowser.Model.Dlna;
namespace Emby.Dlna.Profiles
diff --git a/Emby.Dlna/Profiles/SonyBlurayPlayer2015.cs b/Emby.Dlna/Profiles/SonyBlurayPlayer2015.cs
index 889484bea..ce32179a1 100644
--- a/Emby.Dlna/Profiles/SonyBlurayPlayer2015.cs
+++ b/Emby.Dlna/Profiles/SonyBlurayPlayer2015.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
using MediaBrowser.Model.Dlna;
namespace Emby.Dlna.Profiles
diff --git a/Emby.Dlna/Profiles/SonyBlurayPlayer2016.cs b/Emby.Dlna/Profiles/SonyBlurayPlayer2016.cs
index acb90bd01..aa1721d39 100644
--- a/Emby.Dlna/Profiles/SonyBlurayPlayer2016.cs
+++ b/Emby.Dlna/Profiles/SonyBlurayPlayer2016.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
using MediaBrowser.Model.Dlna;
namespace Emby.Dlna.Profiles
diff --git a/Emby.Dlna/Profiles/SonyBlurayPlayerProfile.cs b/Emby.Dlna/Profiles/SonyBlurayPlayerProfile.cs
index e1808b205..ecdd2e7a4 100644
--- a/Emby.Dlna/Profiles/SonyBlurayPlayerProfile.cs
+++ b/Emby.Dlna/Profiles/SonyBlurayPlayerProfile.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
using MediaBrowser.Model.Dlna;
namespace Emby.Dlna.Profiles
diff --git a/Emby.Dlna/Profiles/SonyBravia2010Profile.cs b/Emby.Dlna/Profiles/SonyBravia2010Profile.cs
index f8e8faa76..68365ba4a 100644
--- a/Emby.Dlna/Profiles/SonyBravia2010Profile.cs
+++ b/Emby.Dlna/Profiles/SonyBravia2010Profile.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
using MediaBrowser.Model.Dlna;
namespace Emby.Dlna.Profiles
diff --git a/Emby.Dlna/Profiles/SonyBravia2011Profile.cs b/Emby.Dlna/Profiles/SonyBravia2011Profile.cs
index 111f36e9b..b34af04a5 100644
--- a/Emby.Dlna/Profiles/SonyBravia2011Profile.cs
+++ b/Emby.Dlna/Profiles/SonyBravia2011Profile.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
using MediaBrowser.Model.Dlna;
namespace Emby.Dlna.Profiles
diff --git a/Emby.Dlna/Profiles/SonyBravia2012Profile.cs b/Emby.Dlna/Profiles/SonyBravia2012Profile.cs
index d5efe4270..0e75d0cb5 100644
--- a/Emby.Dlna/Profiles/SonyBravia2012Profile.cs
+++ b/Emby.Dlna/Profiles/SonyBravia2012Profile.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
using MediaBrowser.Model.Dlna;
namespace Emby.Dlna.Profiles
diff --git a/Emby.Dlna/Profiles/SonyBravia2013Profile.cs b/Emby.Dlna/Profiles/SonyBravia2013Profile.cs
index 3b0228694..3300863c9 100644
--- a/Emby.Dlna/Profiles/SonyBravia2013Profile.cs
+++ b/Emby.Dlna/Profiles/SonyBravia2013Profile.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
using MediaBrowser.Model.Dlna;
namespace Emby.Dlna.Profiles
diff --git a/Emby.Dlna/Profiles/SonyBravia2014Profile.cs b/Emby.Dlna/Profiles/SonyBravia2014Profile.cs
index e860eae34..4e833441c 100644
--- a/Emby.Dlna/Profiles/SonyBravia2014Profile.cs
+++ b/Emby.Dlna/Profiles/SonyBravia2014Profile.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
using MediaBrowser.Model.Dlna;
namespace Emby.Dlna.Profiles
diff --git a/Emby.Dlna/Profiles/SonyPs3Profile.cs b/Emby.Dlna/Profiles/SonyPs3Profile.cs
index 88d064695..7f72356bd 100644
--- a/Emby.Dlna/Profiles/SonyPs3Profile.cs
+++ b/Emby.Dlna/Profiles/SonyPs3Profile.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
using MediaBrowser.Model.Dlna;
namespace Emby.Dlna.Profiles
diff --git a/Emby.Dlna/Profiles/SonyPs4Profile.cs b/Emby.Dlna/Profiles/SonyPs4Profile.cs
index 499cf8803..411bfe2b0 100644
--- a/Emby.Dlna/Profiles/SonyPs4Profile.cs
+++ b/Emby.Dlna/Profiles/SonyPs4Profile.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
using MediaBrowser.Model.Dlna;
namespace Emby.Dlna.Profiles
diff --git a/Emby.Dlna/Profiles/WdtvLiveProfile.cs b/Emby.Dlna/Profiles/WdtvLiveProfile.cs
index bf7b1ab47..2de9a8cd9 100644
--- a/Emby.Dlna/Profiles/WdtvLiveProfile.cs
+++ b/Emby.Dlna/Profiles/WdtvLiveProfile.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
using MediaBrowser.Model.Dlna;
namespace Emby.Dlna.Profiles
diff --git a/Emby.Dlna/Profiles/XboxOneProfile.cs b/Emby.Dlna/Profiles/XboxOneProfile.cs
index 710b891e3..2cbe4e6ac 100644
--- a/Emby.Dlna/Profiles/XboxOneProfile.cs
+++ b/Emby.Dlna/Profiles/XboxOneProfile.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
using MediaBrowser.Model.Dlna;
namespace Emby.Dlna.Profiles
diff --git a/Emby.Dlna/Profiles/Xml/Default.xml b/Emby.Dlna/Profiles/Xml/Default.xml
index daac4135a..9460f9d5a 100644
--- a/Emby.Dlna/Profiles/Xml/Default.xml
+++ b/Emby.Dlna/Profiles/Xml/Default.xml
@@ -21,7 +21,7 @@
<MaxStaticBitrate>140000000</MaxStaticBitrate>
<MusicStreamingTranscodingBitrate>192000</MusicStreamingTranscodingBitrate>
<MaxStaticMusicBitrate xsi:nil="true" />
- <ProtocolInfo>http-get:*:video/mpeg:*,http-get:*:video/mp4:*,http-get:*:video/vnd.dlna.mpeg-tts:*,http-get:*:video/avi:*,http-get:*:video/x-matroska:*,http-get:*:video/x-ms-wmv:*,http-get:*:video/wtv:*,http-get:*:audio/mpeg:*,http-get:*:audio/mp3:*,http-get:*:audio/mp4:*,http-get:*:audio/x-ms-wma*,http-get:*:audio/wav:*,http-get:*:audio/L16:*,http-get:*image/jpeg:*,http-get:*image/png:*,http-get:*image/gif:*,http-get:*image/tiff:*</ProtocolInfo>
+ <ProtocolInfo>http-get:*:video/mpeg:*,http-get:*:video/mp4:*,http-get:*:video/vnd.dlna.mpeg-tts:*,http-get:*:video/avi:*,http-get:*:video/x-matroska:*,http-get:*:video/x-ms-wmv:*,http-get:*:video/wtv:*,http-get:*:audio/mpeg:*,http-get:*:audio/mp3:*,http-get:*:audio/mp4:*,http-get:*:audio/x-ms-wma:*,http-get:*:audio/wav:*,http-get:*:audio/L16:*,http-get:*:image/jpeg:*,http-get:*:image/png:*,http-get:*:image/gif:*,http-get:*:image/tiff:*</ProtocolInfo>
<TimelineOffsetSeconds>0</TimelineOffsetSeconds>
<RequiresPlainVideoItems>false</RequiresPlainVideoItems>
<RequiresPlainFolders>false</RequiresPlainFolders>
diff --git a/Emby.Dlna/Profiles/Xml/Denon AVR.xml b/Emby.Dlna/Profiles/Xml/Denon AVR.xml
index c76cd9a89..571786906 100644
--- a/Emby.Dlna/Profiles/Xml/Denon AVR.xml
+++ b/Emby.Dlna/Profiles/Xml/Denon AVR.xml
@@ -26,7 +26,7 @@
<MaxStaticBitrate>140000000</MaxStaticBitrate>
<MusicStreamingTranscodingBitrate>192000</MusicStreamingTranscodingBitrate>
<MaxStaticMusicBitrate xsi:nil="true" />
- <ProtocolInfo>http-get:*:video/mpeg:*,http-get:*:video/mp4:*,http-get:*:video/vnd.dlna.mpeg-tts:*,http-get:*:video/avi:*,http-get:*:video/x-matroska:*,http-get:*:video/x-ms-wmv:*,http-get:*:video/wtv:*,http-get:*:audio/mpeg:*,http-get:*:audio/mp3:*,http-get:*:audio/mp4:*,http-get:*:audio/x-ms-wma*,http-get:*:audio/wav:*,http-get:*:audio/L16:*,http-get:*image/jpeg:*,http-get:*image/png:*,http-get:*image/gif:*,http-get:*image/tiff:*</ProtocolInfo>
+ <ProtocolInfo>http-get:*:video/mpeg:*,http-get:*:video/mp4:*,http-get:*:video/vnd.dlna.mpeg-tts:*,http-get:*:video/avi:*,http-get:*:video/x-matroska:*,http-get:*:video/x-ms-wmv:*,http-get:*:video/wtv:*,http-get:*:audio/mpeg:*,http-get:*:audio/mp3:*,http-get:*:audio/mp4:*,http-get:*:audio/x-ms-wma:*,http-get:*:audio/wav:*,http-get:*:audio/L16:*,http-get:*:image/jpeg:*,http-get:*:image/png:*,http-get:*:image/gif:*,http-get:*:image/tiff:*</ProtocolInfo>
<TimelineOffsetSeconds>0</TimelineOffsetSeconds>
<RequiresPlainVideoItems>false</RequiresPlainVideoItems>
<RequiresPlainFolders>false</RequiresPlainFolders>
diff --git a/Emby.Dlna/Profiles/Xml/DirecTV HD-DVR.xml b/Emby.Dlna/Profiles/Xml/DirecTV HD-DVR.xml
index f2ce68ab5..eea0febfd 100644
--- a/Emby.Dlna/Profiles/Xml/DirecTV HD-DVR.xml
+++ b/Emby.Dlna/Profiles/Xml/DirecTV HD-DVR.xml
@@ -27,7 +27,7 @@
<MaxStaticBitrate>140000000</MaxStaticBitrate>
<MusicStreamingTranscodingBitrate>192000</MusicStreamingTranscodingBitrate>
<MaxStaticMusicBitrate xsi:nil="true" />
- <ProtocolInfo>http-get:*:video/mpeg:*,http-get:*:video/mp4:*,http-get:*:video/vnd.dlna.mpeg-tts:*,http-get:*:video/avi:*,http-get:*:video/x-matroska:*,http-get:*:video/x-ms-wmv:*,http-get:*:video/wtv:*,http-get:*:audio/mpeg:*,http-get:*:audio/mp3:*,http-get:*:audio/mp4:*,http-get:*:audio/x-ms-wma*,http-get:*:audio/wav:*,http-get:*:audio/L16:*,http-get:*image/jpeg:*,http-get:*image/png:*,http-get:*image/gif:*,http-get:*image/tiff:*</ProtocolInfo>
+ <ProtocolInfo>http-get:*:video/mpeg:*,http-get:*:video/mp4:*,http-get:*:video/vnd.dlna.mpeg-tts:*,http-get:*:video/avi:*,http-get:*:video/x-matroska:*,http-get:*:video/x-ms-wmv:*,http-get:*:video/wtv:*,http-get:*:audio/mpeg:*,http-get:*:audio/mp3:*,http-get:*:audio/mp4:*,http-get:*:audio/x-ms-wma:*,http-get:*:audio/wav:*,http-get:*:audio/L16:*,http-get:*:image/jpeg:*,http-get:*:image/png:*,http-get:*:image/gif:*,http-get:*:image/tiff:*</ProtocolInfo>
<TimelineOffsetSeconds>10</TimelineOffsetSeconds>
<RequiresPlainVideoItems>true</RequiresPlainVideoItems>
<RequiresPlainFolders>true</RequiresPlainFolders>
diff --git a/Emby.Dlna/Profiles/Xml/LG Smart TV.xml b/Emby.Dlna/Profiles/Xml/LG Smart TV.xml
index a0f0e0ee8..20f5ba79b 100644
--- a/Emby.Dlna/Profiles/Xml/LG Smart TV.xml
+++ b/Emby.Dlna/Profiles/Xml/LG Smart TV.xml
@@ -27,7 +27,7 @@
<MaxStaticBitrate>140000000</MaxStaticBitrate>
<MusicStreamingTranscodingBitrate>192000</MusicStreamingTranscodingBitrate>
<MaxStaticMusicBitrate xsi:nil="true" />
- <ProtocolInfo>http-get:*:video/mpeg:*,http-get:*:video/mp4:*,http-get:*:video/vnd.dlna.mpeg-tts:*,http-get:*:video/avi:*,http-get:*:video/x-matroska:*,http-get:*:video/x-ms-wmv:*,http-get:*:video/wtv:*,http-get:*:audio/mpeg:*,http-get:*:audio/mp3:*,http-get:*:audio/mp4:*,http-get:*:audio/x-ms-wma*,http-get:*:audio/wav:*,http-get:*:audio/L16:*,http-get:*image/jpeg:*,http-get:*image/png:*,http-get:*image/gif:*,http-get:*image/tiff:*</ProtocolInfo>
+ <ProtocolInfo>http-get:*:video/mpeg:*,http-get:*:video/mp4:*,http-get:*:video/vnd.dlna.mpeg-tts:*,http-get:*:video/avi:*,http-get:*:video/x-matroska:*,http-get:*:video/x-ms-wmv:*,http-get:*:video/wtv:*,http-get:*:audio/mpeg:*,http-get:*:audio/mp3:*,http-get:*:audio/mp4:*,http-get:*:audio/x-ms-wma:*,http-get:*:audio/wav:*,http-get:*:audio/L16:*,http-get:*:image/jpeg:*,http-get:*:image/png:*,http-get:*:image/gif:*,http-get:*:image/tiff:*</ProtocolInfo>
<TimelineOffsetSeconds>10</TimelineOffsetSeconds>
<RequiresPlainVideoItems>false</RequiresPlainVideoItems>
<RequiresPlainFolders>false</RequiresPlainFolders>
diff --git a/Emby.Dlna/Profiles/Xml/Linksys DMA2100.xml b/Emby.Dlna/Profiles/Xml/Linksys DMA2100.xml
index 55910c77f..d01e3a145 100644
--- a/Emby.Dlna/Profiles/Xml/Linksys DMA2100.xml
+++ b/Emby.Dlna/Profiles/Xml/Linksys DMA2100.xml
@@ -25,7 +25,7 @@
<MaxStaticBitrate>140000000</MaxStaticBitrate>
<MusicStreamingTranscodingBitrate>192000</MusicStreamingTranscodingBitrate>
<MaxStaticMusicBitrate xsi:nil="true" />
- <ProtocolInfo>http-get:*:video/mpeg:*,http-get:*:video/mp4:*,http-get:*:video/vnd.dlna.mpeg-tts:*,http-get:*:video/avi:*,http-get:*:video/x-matroska:*,http-get:*:video/x-ms-wmv:*,http-get:*:video/wtv:*,http-get:*:audio/mpeg:*,http-get:*:audio/mp3:*,http-get:*:audio/mp4:*,http-get:*:audio/x-ms-wma*,http-get:*:audio/wav:*,http-get:*:audio/L16:*,http-get:*image/jpeg:*,http-get:*image/png:*,http-get:*image/gif:*,http-get:*image/tiff:*</ProtocolInfo>
+ <ProtocolInfo>http-get:*:video/mpeg:*,http-get:*:video/mp4:*,http-get:*:video/vnd.dlna.mpeg-tts:*,http-get:*:video/avi:*,http-get:*:video/x-matroska:*,http-get:*:video/x-ms-wmv:*,http-get:*:video/wtv:*,http-get:*:audio/mpeg:*,http-get:*:audio/mp3:*,http-get:*:audio/mp4:*,http-get:*:audio/x-ms-wma:*,http-get:*:audio/wav:*,http-get:*:audio/L16:*,http-get:*:image/jpeg:*,http-get:*:image/png:*,http-get:*:image/gif:*,http-get:*:image/tiff:*</ProtocolInfo>
<TimelineOffsetSeconds>0</TimelineOffsetSeconds>
<RequiresPlainVideoItems>false</RequiresPlainVideoItems>
<RequiresPlainFolders>false</RequiresPlainFolders>
diff --git a/Emby.Dlna/Profiles/Xml/Marantz.xml b/Emby.Dlna/Profiles/Xml/Marantz.xml
index a6345ab3f..0cc9c86e8 100644
--- a/Emby.Dlna/Profiles/Xml/Marantz.xml
+++ b/Emby.Dlna/Profiles/Xml/Marantz.xml
@@ -27,7 +27,7 @@
<MaxStaticBitrate>140000000</MaxStaticBitrate>
<MusicStreamingTranscodingBitrate>192000</MusicStreamingTranscodingBitrate>
<MaxStaticMusicBitrate xsi:nil="true" />
- <ProtocolInfo>http-get:*:video/mpeg:*,http-get:*:video/mp4:*,http-get:*:video/vnd.dlna.mpeg-tts:*,http-get:*:video/avi:*,http-get:*:video/x-matroska:*,http-get:*:video/x-ms-wmv:*,http-get:*:video/wtv:*,http-get:*:audio/mpeg:*,http-get:*:audio/mp3:*,http-get:*:audio/mp4:*,http-get:*:audio/x-ms-wma*,http-get:*:audio/wav:*,http-get:*:audio/L16:*,http-get:*image/jpeg:*,http-get:*image/png:*,http-get:*image/gif:*,http-get:*image/tiff:*</ProtocolInfo>
+ <ProtocolInfo>http-get:*:video/mpeg:*,http-get:*:video/mp4:*,http-get:*:video/vnd.dlna.mpeg-tts:*,http-get:*:video/avi:*,http-get:*:video/x-matroska:*,http-get:*:video/x-ms-wmv:*,http-get:*:video/wtv:*,http-get:*:audio/mpeg:*,http-get:*:audio/mp3:*,http-get:*:audio/mp4:*,http-get:*:audio/x-ms-wma:*,http-get:*:audio/wav:*,http-get:*:audio/L16:*,http-get:*:image/jpeg:*,http-get:*:image/png:*,http-get:*:image/gif:*,http-get:*:image/tiff:*</ProtocolInfo>
<TimelineOffsetSeconds>0</TimelineOffsetSeconds>
<RequiresPlainVideoItems>false</RequiresPlainVideoItems>
<RequiresPlainFolders>false</RequiresPlainFolders>
diff --git a/Emby.Dlna/Profiles/Xml/MediaMonkey.xml b/Emby.Dlna/Profiles/Xml/MediaMonkey.xml
index 2c2c3a1de..9d5ddc3d1 100644
--- a/Emby.Dlna/Profiles/Xml/MediaMonkey.xml
+++ b/Emby.Dlna/Profiles/Xml/MediaMonkey.xml
@@ -27,7 +27,7 @@
<MaxStaticBitrate>140000000</MaxStaticBitrate>
<MusicStreamingTranscodingBitrate>192000</MusicStreamingTranscodingBitrate>
<MaxStaticMusicBitrate xsi:nil="true" />
- <ProtocolInfo>http-get:*:video/mpeg:*,http-get:*:video/mp4:*,http-get:*:video/vnd.dlna.mpeg-tts:*,http-get:*:video/avi:*,http-get:*:video/x-matroska:*,http-get:*:video/x-ms-wmv:*,http-get:*:video/wtv:*,http-get:*:audio/mpeg:*,http-get:*:audio/mp3:*,http-get:*:audio/mp4:*,http-get:*:audio/x-ms-wma*,http-get:*:audio/wav:*,http-get:*:audio/L16:*,http-get:*image/jpeg:*,http-get:*image/png:*,http-get:*image/gif:*,http-get:*image/tiff:*</ProtocolInfo>
+ <ProtocolInfo>http-get:*:video/mpeg:*,http-get:*:video/mp4:*,http-get:*:video/vnd.dlna.mpeg-tts:*,http-get:*:video/avi:*,http-get:*:video/x-matroska:*,http-get:*:video/x-ms-wmv:*,http-get:*:video/wtv:*,http-get:*:audio/mpeg:*,http-get:*:audio/mp3:*,http-get:*:audio/mp4:*,http-get:*:audio/x-ms-wma:*,http-get:*:audio/wav:*,http-get:*:audio/L16:*,http-get:*:image/jpeg:*,http-get:*:image/png:*,http-get:*:image/gif:*,http-get:*:image/tiff:*</ProtocolInfo>
<TimelineOffsetSeconds>0</TimelineOffsetSeconds>
<RequiresPlainVideoItems>false</RequiresPlainVideoItems>
<RequiresPlainFolders>false</RequiresPlainFolders>
diff --git a/Emby.Dlna/Profiles/Xml/Panasonic Viera.xml b/Emby.Dlna/Profiles/Xml/Panasonic Viera.xml
index 934f0550d..8f766853b 100644
--- a/Emby.Dlna/Profiles/Xml/Panasonic Viera.xml
+++ b/Emby.Dlna/Profiles/Xml/Panasonic Viera.xml
@@ -28,7 +28,7 @@
<MaxStaticBitrate>140000000</MaxStaticBitrate>
<MusicStreamingTranscodingBitrate>192000</MusicStreamingTranscodingBitrate>
<MaxStaticMusicBitrate xsi:nil="true" />
- <ProtocolInfo>http-get:*:video/mpeg:*,http-get:*:video/mp4:*,http-get:*:video/vnd.dlna.mpeg-tts:*,http-get:*:video/avi:*,http-get:*:video/x-matroska:*,http-get:*:video/x-ms-wmv:*,http-get:*:video/wtv:*,http-get:*:audio/mpeg:*,http-get:*:audio/mp3:*,http-get:*:audio/mp4:*,http-get:*:audio/x-ms-wma*,http-get:*:audio/wav:*,http-get:*:audio/L16:*,http-get:*image/jpeg:*,http-get:*image/png:*,http-get:*image/gif:*,http-get:*image/tiff:*</ProtocolInfo>
+ <ProtocolInfo>http-get:*:video/mpeg:*,http-get:*:video/mp4:*,http-get:*:video/vnd.dlna.mpeg-tts:*,http-get:*:video/avi:*,http-get:*:video/x-matroska:*,http-get:*:video/x-ms-wmv:*,http-get:*:video/wtv:*,http-get:*:audio/mpeg:*,http-get:*:audio/mp3:*,http-get:*:audio/mp4:*,http-get:*:audio/x-ms-wma:*,http-get:*:audio/wav:*,http-get:*:audio/L16:*,http-get:*:image/jpeg:*,http-get:*:image/png:*,http-get:*:image/gif:*,http-get:*:image/tiff:*</ProtocolInfo>
<TimelineOffsetSeconds>10</TimelineOffsetSeconds>
<RequiresPlainVideoItems>false</RequiresPlainVideoItems>
<RequiresPlainFolders>false</RequiresPlainFolders>
diff --git a/Emby.Dlna/Profiles/Xml/Popcorn Hour.xml b/Emby.Dlna/Profiles/Xml/Popcorn Hour.xml
index eab220fae..aa881d014 100644
--- a/Emby.Dlna/Profiles/Xml/Popcorn Hour.xml
+++ b/Emby.Dlna/Profiles/Xml/Popcorn Hour.xml
@@ -21,7 +21,7 @@
<MaxStaticBitrate>140000000</MaxStaticBitrate>
<MusicStreamingTranscodingBitrate>192000</MusicStreamingTranscodingBitrate>
<MaxStaticMusicBitrate xsi:nil="true" />
- <ProtocolInfo>http-get:*:video/mpeg:*,http-get:*:video/mp4:*,http-get:*:video/vnd.dlna.mpeg-tts:*,http-get:*:video/avi:*,http-get:*:video/x-matroska:*,http-get:*:video/x-ms-wmv:*,http-get:*:video/wtv:*,http-get:*:audio/mpeg:*,http-get:*:audio/mp3:*,http-get:*:audio/mp4:*,http-get:*:audio/x-ms-wma*,http-get:*:audio/wav:*,http-get:*:audio/L16:*,http-get:*image/jpeg:*,http-get:*image/png:*,http-get:*image/gif:*,http-get:*image/tiff:*</ProtocolInfo>
+ <ProtocolInfo>http-get:*:video/mpeg:*,http-get:*:video/mp4:*,http-get:*:video/vnd.dlna.mpeg-tts:*,http-get:*:video/avi:*,http-get:*:video/x-matroska:*,http-get:*:video/x-ms-wmv:*,http-get:*:video/wtv:*,http-get:*:audio/mpeg:*,http-get:*:audio/mp3:*,http-get:*:audio/mp4:*,http-get:*:audio/x-ms-wma:*,http-get:*:audio/wav:*,http-get:*:audio/L16:*,http-get:*:image/jpeg:*,http-get:*:image/png:*,http-get:*:image/gif:*,http-get:*:image/tiff:*</ProtocolInfo>
<TimelineOffsetSeconds>0</TimelineOffsetSeconds>
<RequiresPlainVideoItems>false</RequiresPlainVideoItems>
<RequiresPlainFolders>false</RequiresPlainFolders>
diff --git a/Emby.Dlna/Profiles/Xml/Samsung Smart TV.xml b/Emby.Dlna/Profiles/Xml/Samsung Smart TV.xml
index 3e6f56e5b..7160a9c2e 100644
--- a/Emby.Dlna/Profiles/Xml/Samsung Smart TV.xml
+++ b/Emby.Dlna/Profiles/Xml/Samsung Smart TV.xml
@@ -27,7 +27,7 @@
<MaxStaticBitrate>140000000</MaxStaticBitrate>
<MusicStreamingTranscodingBitrate>192000</MusicStreamingTranscodingBitrate>
<MaxStaticMusicBitrate xsi:nil="true" />
- <ProtocolInfo>http-get:*:video/mpeg:*,http-get:*:video/mp4:*,http-get:*:video/vnd.dlna.mpeg-tts:*,http-get:*:video/avi:*,http-get:*:video/x-matroska:*,http-get:*:video/x-ms-wmv:*,http-get:*:video/wtv:*,http-get:*:audio/mpeg:*,http-get:*:audio/mp3:*,http-get:*:audio/mp4:*,http-get:*:audio/x-ms-wma*,http-get:*:audio/wav:*,http-get:*:audio/L16:*,http-get:*image/jpeg:*,http-get:*image/png:*,http-get:*image/gif:*,http-get:*image/tiff:*</ProtocolInfo>
+ <ProtocolInfo>http-get:*:video/mpeg:*,http-get:*:video/mp4:*,http-get:*:video/vnd.dlna.mpeg-tts:*,http-get:*:video/avi:*,http-get:*:video/x-matroska:*,http-get:*:video/x-ms-wmv:*,http-get:*:video/wtv:*,http-get:*:audio/mpeg:*,http-get:*:audio/mp3:*,http-get:*:audio/mp4:*,http-get:*:audio/x-ms-wma:*,http-get:*:audio/wav:*,http-get:*:audio/L16:*,http-get:*:image/jpeg:*,http-get:*:image/png:*,http-get:*:image/gif:*,http-get:*:image/tiff:*</ProtocolInfo>
<TimelineOffsetSeconds>0</TimelineOffsetSeconds>
<RequiresPlainVideoItems>false</RequiresPlainVideoItems>
<RequiresPlainFolders>false</RequiresPlainFolders>
diff --git a/Emby.Dlna/Profiles/Xml/Sharp Smart TV.xml b/Emby.Dlna/Profiles/Xml/Sharp Smart TV.xml
index 74240b843..c9b907e58 100644
--- a/Emby.Dlna/Profiles/Xml/Sharp Smart TV.xml
+++ b/Emby.Dlna/Profiles/Xml/Sharp Smart TV.xml
@@ -27,7 +27,7 @@
<MaxStaticBitrate>140000000</MaxStaticBitrate>
<MusicStreamingTranscodingBitrate>192000</MusicStreamingTranscodingBitrate>
<MaxStaticMusicBitrate xsi:nil="true" />
- <ProtocolInfo>http-get:*:video/mpeg:*,http-get:*:video/mp4:*,http-get:*:video/vnd.dlna.mpeg-tts:*,http-get:*:video/avi:*,http-get:*:video/x-matroska:*,http-get:*:video/x-ms-wmv:*,http-get:*:video/wtv:*,http-get:*:audio/mpeg:*,http-get:*:audio/mp3:*,http-get:*:audio/mp4:*,http-get:*:audio/x-ms-wma*,http-get:*:audio/wav:*,http-get:*:audio/L16:*,http-get:*image/jpeg:*,http-get:*image/png:*,http-get:*image/gif:*,http-get:*image/tiff:*</ProtocolInfo>
+ <ProtocolInfo>http-get:*:video/mpeg:*,http-get:*:video/mp4:*,http-get:*:video/vnd.dlna.mpeg-tts:*,http-get:*:video/avi:*,http-get:*:video/x-matroska:*,http-get:*:video/x-ms-wmv:*,http-get:*:video/wtv:*,http-get:*:audio/mpeg:*,http-get:*:audio/mp3:*,http-get:*:audio/mp4:*,http-get:*:audio/x-ms-wma:*,http-get:*:audio/wav:*,http-get:*:audio/L16:*,http-get:*:image/jpeg:*,http-get:*:image/png:*,http-get:*:image/gif:*,http-get:*:image/tiff:*</ProtocolInfo>
<TimelineOffsetSeconds>0</TimelineOffsetSeconds>
<RequiresPlainVideoItems>true</RequiresPlainVideoItems>
<RequiresPlainFolders>true</RequiresPlainFolders>
diff --git a/Emby.Dlna/Profiles/Xml/Sony Bravia (2011).xml b/Emby.Dlna/Profiles/Xml/Sony Bravia (2011).xml
index 49819ccfd..e516ff512 100644
--- a/Emby.Dlna/Profiles/Xml/Sony Bravia (2011).xml
+++ b/Emby.Dlna/Profiles/Xml/Sony Bravia (2011).xml
@@ -29,7 +29,7 @@
<MusicStreamingTranscodingBitrate>192000</MusicStreamingTranscodingBitrate>
<MaxStaticMusicBitrate xsi:nil="true" />
<SonyAggregationFlags>10</SonyAggregationFlags>
- <ProtocolInfo>http-get:*:video/mpeg:*,http-get:*:video/mp4:*,http-get:*:video/vnd.dlna.mpeg-tts:*,http-get:*:video/avi:*,http-get:*:video/x-matroska:*,http-get:*:video/x-ms-wmv:*,http-get:*:video/wtv:*,http-get:*:audio/mpeg:*,http-get:*:audio/mp3:*,http-get:*:audio/mp4:*,http-get:*:audio/x-ms-wma*,http-get:*:audio/wav:*,http-get:*:audio/L16:*,http-get:*image/jpeg:*,http-get:*image/png:*,http-get:*image/gif:*,http-get:*image/tiff:*</ProtocolInfo>
+ <ProtocolInfo>http-get:*:video/mpeg:*,http-get:*:video/mp4:*,http-get:*:video/vnd.dlna.mpeg-tts:*,http-get:*:video/avi:*,http-get:*:video/x-matroska:*,http-get:*:video/x-ms-wmv:*,http-get:*:video/wtv:*,http-get:*:audio/mpeg:*,http-get:*:audio/mp3:*,http-get:*:audio/mp4:*,http-get:*:audio/x-ms-wma:*,http-get:*:audio/wav:*,http-get:*:audio/L16:*,http-get:*:image/jpeg:*,http-get:*:image/png:*,http-get:*:image/gif:*,http-get:*:image/tiff:*</ProtocolInfo>
<TimelineOffsetSeconds>0</TimelineOffsetSeconds>
<RequiresPlainVideoItems>false</RequiresPlainVideoItems>
<RequiresPlainFolders>false</RequiresPlainFolders>
diff --git a/Emby.Dlna/Profiles/Xml/Sony Bravia (2012).xml b/Emby.Dlna/Profiles/Xml/Sony Bravia (2012).xml
index aaad7b342..88bd1c2f5 100644
--- a/Emby.Dlna/Profiles/Xml/Sony Bravia (2012).xml
+++ b/Emby.Dlna/Profiles/Xml/Sony Bravia (2012).xml
@@ -29,7 +29,7 @@
<MusicStreamingTranscodingBitrate>192000</MusicStreamingTranscodingBitrate>
<MaxStaticMusicBitrate xsi:nil="true" />
<SonyAggregationFlags>10</SonyAggregationFlags>
- <ProtocolInfo>http-get:*:video/mpeg:*,http-get:*:video/mp4:*,http-get:*:video/vnd.dlna.mpeg-tts:*,http-get:*:video/avi:*,http-get:*:video/x-matroska:*,http-get:*:video/x-ms-wmv:*,http-get:*:video/wtv:*,http-get:*:audio/mpeg:*,http-get:*:audio/mp3:*,http-get:*:audio/mp4:*,http-get:*:audio/x-ms-wma*,http-get:*:audio/wav:*,http-get:*:audio/L16:*,http-get:*image/jpeg:*,http-get:*image/png:*,http-get:*image/gif:*,http-get:*image/tiff:*</ProtocolInfo>
+ <ProtocolInfo>http-get:*:video/mpeg:*,http-get:*:video/mp4:*,http-get:*:video/vnd.dlna.mpeg-tts:*,http-get:*:video/avi:*,http-get:*:video/x-matroska:*,http-get:*:video/x-ms-wmv:*,http-get:*:video/wtv:*,http-get:*:audio/mpeg:*,http-get:*:audio/mp3:*,http-get:*:audio/mp4:*,http-get:*:audio/x-ms-wma:*,http-get:*:audio/wav:*,http-get:*:audio/L16:*,http-get:*:image/jpeg:*,http-get:*:image/png:*,http-get:*:image/gif:*,http-get:*:image/tiff:*</ProtocolInfo>
<TimelineOffsetSeconds>0</TimelineOffsetSeconds>
<RequiresPlainVideoItems>false</RequiresPlainVideoItems>
<RequiresPlainFolders>false</RequiresPlainFolders>
diff --git a/Emby.Dlna/Profiles/Xml/Sony Bravia (2013).xml b/Emby.Dlna/Profiles/Xml/Sony Bravia (2013).xml
index 8e2e8dbaa..3ca9893cd 100644
--- a/Emby.Dlna/Profiles/Xml/Sony Bravia (2013).xml
+++ b/Emby.Dlna/Profiles/Xml/Sony Bravia (2013).xml
@@ -29,7 +29,7 @@
<MusicStreamingTranscodingBitrate>192000</MusicStreamingTranscodingBitrate>
<MaxStaticMusicBitrate xsi:nil="true" />
<SonyAggregationFlags>10</SonyAggregationFlags>
- <ProtocolInfo>http-get:*:video/mpeg:*,http-get:*:video/mp4:*,http-get:*:video/vnd.dlna.mpeg-tts:*,http-get:*:video/avi:*,http-get:*:video/x-matroska:*,http-get:*:video/x-ms-wmv:*,http-get:*:video/wtv:*,http-get:*:audio/mpeg:*,http-get:*:audio/mp3:*,http-get:*:audio/mp4:*,http-get:*:audio/x-ms-wma*,http-get:*:audio/wav:*,http-get:*:audio/L16:*,http-get:*image/jpeg:*,http-get:*image/png:*,http-get:*image/gif:*,http-get:*image/tiff:*</ProtocolInfo>
+ <ProtocolInfo>http-get:*:video/mpeg:*,http-get:*:video/mp4:*,http-get:*:video/vnd.dlna.mpeg-tts:*,http-get:*:video/avi:*,http-get:*:video/x-matroska:*,http-get:*:video/x-ms-wmv:*,http-get:*:video/wtv:*,http-get:*:audio/mpeg:*,http-get:*:audio/mp3:*,http-get:*:audio/mp4:*,http-get:*:audio/x-ms-wma:*,http-get:*:audio/wav:*,http-get:*:audio/L16:*,http-get:*:image/jpeg:*,http-get:*:image/png:*,http-get:*:image/gif:*,http-get:*:image/tiff:*</ProtocolInfo>
<TimelineOffsetSeconds>0</TimelineOffsetSeconds>
<RequiresPlainVideoItems>false</RequiresPlainVideoItems>
<RequiresPlainFolders>false</RequiresPlainFolders>
diff --git a/Emby.Dlna/Profiles/Xml/Sony Bravia (2014).xml b/Emby.Dlna/Profiles/Xml/Sony Bravia (2014).xml
index 17a6135e1..8804a75df 100644
--- a/Emby.Dlna/Profiles/Xml/Sony Bravia (2014).xml
+++ b/Emby.Dlna/Profiles/Xml/Sony Bravia (2014).xml
@@ -29,7 +29,7 @@
<MusicStreamingTranscodingBitrate>192000</MusicStreamingTranscodingBitrate>
<MaxStaticMusicBitrate xsi:nil="true" />
<SonyAggregationFlags>10</SonyAggregationFlags>
- <ProtocolInfo>http-get:*:video/mpeg:*,http-get:*:video/mp4:*,http-get:*:video/vnd.dlna.mpeg-tts:*,http-get:*:video/avi:*,http-get:*:video/x-matroska:*,http-get:*:video/x-ms-wmv:*,http-get:*:video/wtv:*,http-get:*:audio/mpeg:*,http-get:*:audio/mp3:*,http-get:*:audio/mp4:*,http-get:*:audio/x-ms-wma*,http-get:*:audio/wav:*,http-get:*:audio/L16:*,http-get:*image/jpeg:*,http-get:*image/png:*,http-get:*image/gif:*,http-get:*image/tiff:*</ProtocolInfo>
+ <ProtocolInfo>http-get:*:video/mpeg:*,http-get:*:video/mp4:*,http-get:*:video/vnd.dlna.mpeg-tts:*,http-get:*:video/avi:*,http-get:*:video/x-matroska:*,http-get:*:video/x-ms-wmv:*,http-get:*:video/wtv:*,http-get:*:audio/mpeg:*,http-get:*:audio/mp3:*,http-get:*:audio/mp4:*,http-get:*:audio/x-ms-wma:*,http-get:*:audio/wav:*,http-get:*:audio/L16:*,http-get:*:image/jpeg:*,http-get:*:image/png:*,http-get:*:image/gif:*,http-get:*:image/tiff:*</ProtocolInfo>
<TimelineOffsetSeconds>0</TimelineOffsetSeconds>
<RequiresPlainVideoItems>false</RequiresPlainVideoItems>
<RequiresPlainFolders>false</RequiresPlainFolders>
diff --git a/Emby.Dlna/Profiles/Xml/Sony PlayStation 3.xml b/Emby.Dlna/Profiles/Xml/Sony PlayStation 3.xml
index df385135c..bafa44b82 100644
--- a/Emby.Dlna/Profiles/Xml/Sony PlayStation 3.xml
+++ b/Emby.Dlna/Profiles/Xml/Sony PlayStation 3.xml
@@ -29,7 +29,7 @@
<MusicStreamingTranscodingBitrate>192000</MusicStreamingTranscodingBitrate>
<MaxStaticMusicBitrate xsi:nil="true" />
<SonyAggregationFlags>10</SonyAggregationFlags>
- <ProtocolInfo>http-get:*:video/mpeg:*,http-get:*:video/mp4:*,http-get:*:video/vnd.dlna.mpeg-tts:*,http-get:*:video/avi:*,http-get:*:video/x-matroska:*,http-get:*:video/x-ms-wmv:*,http-get:*:video/wtv:*,http-get:*:audio/mpeg:*,http-get:*:audio/mp3:*,http-get:*:audio/mp4:*,http-get:*:audio/x-ms-wma*,http-get:*:audio/wav:*,http-get:*:audio/L16:*,http-get:*image/jpeg:*,http-get:*image/png:*,http-get:*image/gif:*,http-get:*image/tiff:*</ProtocolInfo>
+ <ProtocolInfo>http-get:*:video/mpeg:*,http-get:*:video/mp4:*,http-get:*:video/vnd.dlna.mpeg-tts:*,http-get:*:video/avi:*,http-get:*:video/x-matroska:*,http-get:*:video/x-ms-wmv:*,http-get:*:video/wtv:*,http-get:*:audio/mpeg:*,http-get:*:audio/mp3:*,http-get:*:audio/mp4:*,http-get:*:audio/x-ms-wma:*,http-get:*:audio/wav:*,http-get:*:audio/L16:*,http-get:*:image/jpeg:*,http-get:*:image/png:*,http-get:*:image/gif:*,http-get:*:image/tiff:*</ProtocolInfo>
<TimelineOffsetSeconds>0</TimelineOffsetSeconds>
<RequiresPlainVideoItems>false</RequiresPlainVideoItems>
<RequiresPlainFolders>false</RequiresPlainFolders>
diff --git a/Emby.Dlna/Profiles/Xml/Sony PlayStation 4.xml b/Emby.Dlna/Profiles/Xml/Sony PlayStation 4.xml
index 20f50f6b6..eb8e645b3 100644
--- a/Emby.Dlna/Profiles/Xml/Sony PlayStation 4.xml
+++ b/Emby.Dlna/Profiles/Xml/Sony PlayStation 4.xml
@@ -29,7 +29,7 @@
<MusicStreamingTranscodingBitrate>192000</MusicStreamingTranscodingBitrate>
<MaxStaticMusicBitrate xsi:nil="true" />
<SonyAggregationFlags>10</SonyAggregationFlags>
- <ProtocolInfo>http-get:*:video/mpeg:*,http-get:*:video/mp4:*,http-get:*:video/vnd.dlna.mpeg-tts:*,http-get:*:video/avi:*,http-get:*:video/x-matroska:*,http-get:*:video/x-ms-wmv:*,http-get:*:video/wtv:*,http-get:*:audio/mpeg:*,http-get:*:audio/mp3:*,http-get:*:audio/mp4:*,http-get:*:audio/x-ms-wma*,http-get:*:audio/wav:*,http-get:*:audio/L16:*,http-get:*image/jpeg:*,http-get:*image/png:*,http-get:*image/gif:*,http-get:*image/tiff:*</ProtocolInfo>
+ <ProtocolInfo>http-get:*:video/mpeg:*,http-get:*:video/mp4:*,http-get:*:video/vnd.dlna.mpeg-tts:*,http-get:*:video/avi:*,http-get:*:video/x-matroska:*,http-get:*:video/x-ms-wmv:*,http-get:*:video/wtv:*,http-get:*:audio/mpeg:*,http-get:*:audio/mp3:*,http-get:*:audio/mp4:*,http-get:*:audio/x-ms-wma:*,http-get:*:audio/wav:*,http-get:*:audio/L16:*,http-get:*:image/jpeg:*,http-get:*:image/png:*,http-get:*:image/gif:*,http-get:*:image/tiff:*</ProtocolInfo>
<TimelineOffsetSeconds>0</TimelineOffsetSeconds>
<RequiresPlainVideoItems>false</RequiresPlainVideoItems>
<RequiresPlainFolders>false</RequiresPlainFolders>
diff --git a/Emby.Dlna/Profiles/Xml/WDTV Live.xml b/Emby.Dlna/Profiles/Xml/WDTV Live.xml
index 05380e33a..ccb74ee64 100644
--- a/Emby.Dlna/Profiles/Xml/WDTV Live.xml
+++ b/Emby.Dlna/Profiles/Xml/WDTV Live.xml
@@ -28,7 +28,7 @@
<MaxStaticBitrate>140000000</MaxStaticBitrate>
<MusicStreamingTranscodingBitrate>192000</MusicStreamingTranscodingBitrate>
<MaxStaticMusicBitrate xsi:nil="true" />
- <ProtocolInfo>http-get:*:video/mpeg:*,http-get:*:video/mp4:*,http-get:*:video/vnd.dlna.mpeg-tts:*,http-get:*:video/avi:*,http-get:*:video/x-matroska:*,http-get:*:video/x-ms-wmv:*,http-get:*:video/wtv:*,http-get:*:audio/mpeg:*,http-get:*:audio/mp3:*,http-get:*:audio/mp4:*,http-get:*:audio/x-ms-wma*,http-get:*:audio/wav:*,http-get:*:audio/L16:*,http-get:*image/jpeg:*,http-get:*image/png:*,http-get:*image/gif:*,http-get:*image/tiff:*</ProtocolInfo>
+ <ProtocolInfo>http-get:*:video/mpeg:*,http-get:*:video/mp4:*,http-get:*:video/vnd.dlna.mpeg-tts:*,http-get:*:video/avi:*,http-get:*:video/x-matroska:*,http-get:*:video/x-ms-wmv:*,http-get:*:video/wtv:*,http-get:*:audio/mpeg:*,http-get:*:audio/mp3:*,http-get:*:audio/mp4:*,http-get:*:audio/x-ms-wma:*,http-get:*:audio/wav:*,http-get:*:audio/L16:*,http-get:*:image/jpeg:*,http-get:*:image/png:*,http-get:*:image/gif:*,http-get:*:image/tiff:*</ProtocolInfo>
<TimelineOffsetSeconds>5</TimelineOffsetSeconds>
<RequiresPlainVideoItems>false</RequiresPlainVideoItems>
<RequiresPlainFolders>false</RequiresPlainFolders>
diff --git a/Emby.Dlna/Profiles/Xml/Xbox One.xml b/Emby.Dlna/Profiles/Xml/Xbox One.xml
index 5f5cf1af3..26a65bbd4 100644
--- a/Emby.Dlna/Profiles/Xml/Xbox One.xml
+++ b/Emby.Dlna/Profiles/Xml/Xbox One.xml
@@ -28,7 +28,7 @@
<MaxStaticBitrate>140000000</MaxStaticBitrate>
<MusicStreamingTranscodingBitrate>192000</MusicStreamingTranscodingBitrate>
<MaxStaticMusicBitrate xsi:nil="true" />
- <ProtocolInfo>http-get:*:video/mpeg:*,http-get:*:video/mp4:*,http-get:*:video/vnd.dlna.mpeg-tts:*,http-get:*:video/avi:*,http-get:*:video/x-matroska:*,http-get:*:video/x-ms-wmv:*,http-get:*:video/wtv:*,http-get:*:audio/mpeg:*,http-get:*:audio/mp3:*,http-get:*:audio/mp4:*,http-get:*:audio/x-ms-wma*,http-get:*:audio/wav:*,http-get:*:audio/L16:*,http-get:*image/jpeg:*,http-get:*image/png:*,http-get:*image/gif:*,http-get:*image/tiff:*</ProtocolInfo>
+ <ProtocolInfo>http-get:*:video/mpeg:*,http-get:*:video/mp4:*,http-get:*:video/vnd.dlna.mpeg-tts:*,http-get:*:video/avi:*,http-get:*:video/x-matroska:*,http-get:*:video/x-ms-wmv:*,http-get:*:video/wtv:*,http-get:*:audio/mpeg:*,http-get:*:audio/mp3:*,http-get:*:audio/mp4:*,http-get:*:audio/x-ms-wma:*,http-get:*:audio/wav:*,http-get:*:audio/L16:*,http-get:*:image/jpeg:*,http-get:*:image/png:*,http-get:*:image/gif:*,http-get:*:image/tiff:*</ProtocolInfo>
<TimelineOffsetSeconds>40</TimelineOffsetSeconds>
<RequiresPlainVideoItems>false</RequiresPlainVideoItems>
<RequiresPlainFolders>false</RequiresPlainFolders>
diff --git a/Emby.Dlna/Profiles/Xml/foobar2000.xml b/Emby.Dlna/Profiles/Xml/foobar2000.xml
index f3eedb35c..5ce75ace5 100644
--- a/Emby.Dlna/Profiles/Xml/foobar2000.xml
+++ b/Emby.Dlna/Profiles/Xml/foobar2000.xml
@@ -27,7 +27,7 @@
<MaxStaticBitrate>140000000</MaxStaticBitrate>
<MusicStreamingTranscodingBitrate>192000</MusicStreamingTranscodingBitrate>
<MaxStaticMusicBitrate xsi:nil="true" />
- <ProtocolInfo>http-get:*:video/mpeg:*,http-get:*:video/mp4:*,http-get:*:video/vnd.dlna.mpeg-tts:*,http-get:*:video/avi:*,http-get:*:video/x-matroska:*,http-get:*:video/x-ms-wmv:*,http-get:*:video/wtv:*,http-get:*:audio/mpeg:*,http-get:*:audio/mp3:*,http-get:*:audio/mp4:*,http-get:*:audio/x-ms-wma*,http-get:*:audio/wav:*,http-get:*:audio/L16:*,http-get:*image/jpeg:*,http-get:*image/png:*,http-get:*image/gif:*,http-get:*image/tiff:*</ProtocolInfo>
+ <ProtocolInfo>http-get:*:video/mpeg:*,http-get:*:video/mp4:*,http-get:*:video/vnd.dlna.mpeg-tts:*,http-get:*:video/avi:*,http-get:*:video/x-matroska:*,http-get:*:video/x-ms-wmv:*,http-get:*:video/wtv:*,http-get:*:audio/mpeg:*,http-get:*:audio/mp3:*,http-get:*:audio/mp4:*,http-get:*:audio/x-ms-wma:*,http-get:*:audio/wav:*,http-get:*:audio/L16:*,http-get:*:image/jpeg:*,http-get:*:image/png:*,http-get:*:image/gif:*,http-get:*:image/tiff:*</ProtocolInfo>
<TimelineOffsetSeconds>0</TimelineOffsetSeconds>
<RequiresPlainVideoItems>false</RequiresPlainVideoItems>
<RequiresPlainFolders>false</RequiresPlainFolders>
diff --git a/Emby.Dlna/Server/DescriptionXmlBuilder.cs b/Emby.Dlna/Server/DescriptionXmlBuilder.cs
index 03d8f80ab..5ecc81a2f 100644
--- a/Emby.Dlna/Server/DescriptionXmlBuilder.cs
+++ b/Emby.Dlna/Server/DescriptionXmlBuilder.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
using System;
using System.Collections.Generic;
using System.Globalization;
@@ -5,7 +7,6 @@ using System.Linq;
using System.Text;
using Emby.Dlna.Common;
using MediaBrowser.Model.Dlna;
-using MediaBrowser.Model.Extensions;
namespace Emby.Dlna.Server
{
diff --git a/Emby.Dlna/Service/BaseControlHandler.cs b/Emby.Dlna/Service/BaseControlHandler.cs
index 067d5fa43..161a3434c 100644
--- a/Emby.Dlna/Service/BaseControlHandler.cs
+++ b/Emby.Dlna/Service/BaseControlHandler.cs
@@ -1,8 +1,10 @@
+#pragma warning disable CS1591
+
using System;
using System.Collections.Generic;
using System.IO;
-using System.Linq;
using System.Text;
+using System.Threading.Tasks;
using System.Xml;
using Emby.Dlna.Didl;
using MediaBrowser.Controller.Configuration;
@@ -15,44 +17,34 @@ namespace Emby.Dlna.Service
{
private const string NS_SOAPENV = "http://schemas.xmlsoap.org/soap/envelope/";
- protected readonly IServerConfigurationManager Config;
- protected readonly ILogger _logger;
+ protected IServerConfigurationManager Config { get; }
+ protected ILogger Logger { get; }
protected BaseControlHandler(IServerConfigurationManager config, ILogger logger)
{
Config = config;
- _logger = logger;
+ Logger = logger;
}
- public ControlResponse ProcessControlRequest(ControlRequest request)
+ public async Task<ControlResponse> ProcessControlRequestAsync(ControlRequest request)
{
try
{
- var enableDebugLogging = Config.GetDlnaConfiguration().EnableDebugLog;
-
- if (enableDebugLogging)
- {
- LogRequest(request);
- }
-
- var response = ProcessControlRequestInternal(request);
-
- if (enableDebugLogging)
- {
- LogResponse(response);
- }
+ LogRequest(request);
+ var response = await ProcessControlRequestInternalAsync(request).ConfigureAwait(false);
+ LogResponse(response);
return response;
}
catch (Exception ex)
{
- _logger.LogError(ex, "Error processing control request");
+ Logger.LogError(ex, "Error processing control request");
- return new ControlErrorHandler().GetResponse(ex);
+ return ControlErrorHandler.GetResponse(ex);
}
}
- private ControlResponse ProcessControlRequestInternal(ControlRequest request)
+ private async Task<ControlResponse> ProcessControlRequestInternalAsync(ControlRequest request)
{
ControlRequestInfo requestInfo = null;
@@ -63,18 +55,17 @@ namespace Emby.Dlna.Service
ValidationType = ValidationType.None,
CheckCharacters = false,
IgnoreProcessingInstructions = true,
- IgnoreComments = true
+ IgnoreComments = true,
+ Async = true
};
using (var reader = XmlReader.Create(streamReader, readerSettings))
{
- requestInfo = ParseRequest(reader);
+ requestInfo = await ParseRequestAsync(reader).ConfigureAwait(false);
}
}
- _logger.LogDebug("Received control request {0}", requestInfo.LocalName);
-
- var result = GetResult(requestInfo.LocalName, requestInfo.Headers);
+ Logger.LogDebug("Received control request {0}", requestInfo.LocalName);
var settings = new XmlWriterSettings
{
@@ -93,12 +84,9 @@ namespace Emby.Dlna.Service
writer.WriteStartElement("SOAP-ENV", "Body", NS_SOAPENV);
writer.WriteStartElement("u", requestInfo.LocalName + "Response", requestInfo.NamespaceURI);
- foreach (var i in result)
- {
- writer.WriteStartElement(i.Key);
- writer.WriteString(i.Value);
- writer.WriteFullEndElement();
- }
+
+ WriteResult(requestInfo.LocalName, requestInfo.Headers, writer);
+
writer.WriteFullEndElement();
writer.WriteFullEndElement();
@@ -106,7 +94,7 @@ namespace Emby.Dlna.Service
writer.WriteEndDocument();
}
- var xml = builder.ToString().Replace("xmlns:m=", "xmlns:u=");
+ var xml = builder.ToString().Replace("xmlns:m=", "xmlns:u=", StringComparison.Ordinal);
var controlResponse = new ControlResponse
{
@@ -114,17 +102,15 @@ namespace Emby.Dlna.Service
IsSuccessful = true
};
- //logger.LogDebug(xml);
-
controlResponse.Headers.Add("EXT", string.Empty);
return controlResponse;
}
- private ControlRequestInfo ParseRequest(XmlReader reader)
+ private async Task<ControlRequestInfo> ParseRequestAsync(XmlReader reader)
{
- reader.MoveToContent();
- reader.Read();
+ await reader.MoveToContentAsync().ConfigureAwait(false);
+ await reader.ReadAsync().ConfigureAwait(false);
// Loop through each element
while (!reader.EOF && reader.ReadState == ReadState.Interactive)
@@ -139,37 +125,38 @@ namespace Emby.Dlna.Service
{
using (var subReader = reader.ReadSubtree())
{
- return ParseBodyTag(subReader);
+ return await ParseBodyTagAsync(subReader).ConfigureAwait(false);
}
}
else
{
- reader.Read();
+ await reader.ReadAsync().ConfigureAwait(false);
}
+
break;
}
default:
{
- reader.Skip();
+ await reader.SkipAsync().ConfigureAwait(false);
break;
}
}
}
else
{
- reader.Read();
+ await reader.ReadAsync().ConfigureAwait(false);
}
}
return new ControlRequestInfo();
}
- private ControlRequestInfo ParseBodyTag(XmlReader reader)
+ private async Task<ControlRequestInfo> ParseBodyTagAsync(XmlReader reader)
{
var result = new ControlRequestInfo();
- reader.MoveToContent();
- reader.Read();
+ await reader.MoveToContentAsync().ConfigureAwait(false);
+ await reader.ReadAsync().ConfigureAwait(false);
// Loop through each element
while (!reader.EOF && reader.ReadState == ReadState.Interactive)
@@ -183,28 +170,28 @@ namespace Emby.Dlna.Service
{
using (var subReader = reader.ReadSubtree())
{
- ParseFirstBodyChild(subReader, result.Headers);
+ await ParseFirstBodyChildAsync(subReader, result.Headers).ConfigureAwait(false);
return result;
}
}
else
{
- reader.Read();
+ await reader.ReadAsync().ConfigureAwait(false);
}
}
else
{
- reader.Read();
+ await reader.ReadAsync().ConfigureAwait(false);
}
}
return result;
}
- private void ParseFirstBodyChild(XmlReader reader, IDictionary<string, string> headers)
+ private async Task ParseFirstBodyChildAsync(XmlReader reader, IDictionary<string, string> headers)
{
- reader.MoveToContent();
- reader.Read();
+ await reader.MoveToContentAsync().ConfigureAwait(false);
+ await reader.ReadAsync().ConfigureAwait(false);
// Loop through each element
while (!reader.EOF && reader.ReadState == ReadState.Interactive)
@@ -212,23 +199,23 @@ namespace Emby.Dlna.Service
if (reader.NodeType == XmlNodeType.Element)
{
// TODO: Should we be doing this here, or should it be handled earlier when decoding the request?
- headers[reader.LocalName.RemoveDiacritics()] = reader.ReadElementContentAsString();
+ headers[reader.LocalName.RemoveDiacritics()] = await reader.ReadElementContentAsStringAsync().ConfigureAwait(false);
}
else
{
- reader.Read();
+ await reader.ReadAsync().ConfigureAwait(false);
}
}
}
private class ControlRequestInfo
{
- public string LocalName;
- public string NamespaceURI;
- public IDictionary<string, string> Headers = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
+ public string LocalName { get; set; }
+ public string NamespaceURI { get; set; }
+ public Dictionary<string, string> Headers { get; } = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
}
- protected abstract IEnumerable<KeyValuePair<string, string>> GetResult(string methodName, IDictionary<string, string> methodParams);
+ protected abstract void WriteResult(string methodName, IDictionary<string, string> methodParams, XmlWriter xmlWriter);
private void LogRequest(ControlRequest request)
{
@@ -237,10 +224,7 @@ namespace Emby.Dlna.Service
return;
}
- var originalHeaders = request.Headers;
- var headers = string.Join(", ", originalHeaders.Select(i => string.Format("{0}={1}", i.Key, i.Value)).ToArray());
-
- _logger.LogDebug("Control request. Headers: {0}", headers);
+ Logger.LogDebug("Control request. Headers: {@Headers}", request.Headers);
}
private void LogResponse(ControlResponse response)
@@ -250,11 +234,7 @@ namespace Emby.Dlna.Service
return;
}
- var originalHeaders = response.Headers;
- var headers = string.Join(", ", originalHeaders.Select(i => string.Format("{0}={1}", i.Key, i.Value)).ToArray());
- //builder.Append(response.Xml);
-
- _logger.LogDebug("Control response. Headers: {0}", headers);
+ Logger.LogDebug("Control response. Headers: {@Headers}\n{Xml}", response.Headers, response.Xml);
}
}
}
diff --git a/Emby.Dlna/Service/BaseService.cs b/Emby.Dlna/Service/BaseService.cs
index 5359e94c4..3704bedcd 100644
--- a/Emby.Dlna/Service/BaseService.cs
+++ b/Emby.Dlna/Service/BaseService.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
using Emby.Dlna.Eventing;
using MediaBrowser.Common.Net;
using Microsoft.Extensions.Logging;
@@ -10,7 +12,7 @@ namespace Emby.Dlna.Service
protected IHttpClient HttpClient;
protected ILogger Logger;
- protected BaseService(ILogger logger, IHttpClient httpClient)
+ protected BaseService(ILogger<BaseService> logger, IHttpClient httpClient)
{
Logger = logger;
HttpClient = httpClient;
diff --git a/Emby.Dlna/Service/ControlErrorHandler.cs b/Emby.Dlna/Service/ControlErrorHandler.cs
index d5eb4a887..047e9f014 100644
--- a/Emby.Dlna/Service/ControlErrorHandler.cs
+++ b/Emby.Dlna/Service/ControlErrorHandler.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
using System;
using System.IO;
using System.Text;
@@ -6,11 +8,11 @@ using Emby.Dlna.Didl;
namespace Emby.Dlna.Service
{
- public class ControlErrorHandler
+ public static class ControlErrorHandler
{
private const string NS_SOAPENV = "http://schemas.xmlsoap.org/soap/envelope/";
- public ControlResponse GetResponse(Exception ex)
+ public static ControlResponse GetResponse(Exception ex)
{
var settings = new XmlWriterSettings
{
diff --git a/Emby.Dlna/Service/ServiceXmlBuilder.cs b/Emby.Dlna/Service/ServiceXmlBuilder.cs
index bd1f0bf05..62ffd9e42 100644
--- a/Emby.Dlna/Service/ServiceXmlBuilder.cs
+++ b/Emby.Dlna/Service/ServiceXmlBuilder.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
using System.Collections.Generic;
using System.Text;
using Emby.Dlna.Common;
diff --git a/Emby.Dlna/Ssdp/DeviceDiscovery.cs b/Emby.Dlna/Ssdp/DeviceDiscovery.cs
index c5f3593da..f95b8ce7d 100644
--- a/Emby.Dlna/Ssdp/DeviceDiscovery.cs
+++ b/Emby.Dlna/Ssdp/DeviceDiscovery.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
using System;
using System.Collections.Generic;
using System.Linq;
@@ -9,16 +11,16 @@ using Rssdp.Infrastructure;
namespace Emby.Dlna.Ssdp
{
- public class DeviceDiscovery : IDeviceDiscovery
+ public sealed class DeviceDiscovery : IDeviceDiscovery, IDisposable
{
- private bool _disposed;
+ private readonly object _syncLock = new object();
private readonly IServerConfigurationManager _config;
- private event EventHandler<GenericEventArgs<UpnpDeviceInfo>> DeviceDiscoveredInternal;
-
private int _listenerCount;
- private object _syncLock = new object();
+ private bool _disposed;
+
+ private event EventHandler<GenericEventArgs<UpnpDeviceInfo>> DeviceDiscoveredInternal;
/// <inheritdoc />
public event EventHandler<GenericEventArgs<UpnpDeviceInfo>> DeviceDiscovered
@@ -33,6 +35,7 @@ namespace Emby.Dlna.Ssdp
StartInternal();
}
+
remove
{
lock (_syncLock)
@@ -130,6 +133,7 @@ namespace Emby.Dlna.Ssdp
DeviceLeft?.Invoke(this, args);
}
+ /// <inheritdoc />
public void Dispose()
{
if (!_disposed)
diff --git a/Emby.Dlna/Ssdp/Extensions.cs b/Emby.Dlna/Ssdp/Extensions.cs
index c680c123e..10c1f321b 100644
--- a/Emby.Dlna/Ssdp/Extensions.cs
+++ b/Emby.Dlna/Ssdp/Extensions.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
using System.Xml.Linq;
namespace Emby.Dlna.Ssdp
diff --git a/Emby.Drawing/Emby.Drawing.csproj b/Emby.Drawing/Emby.Drawing.csproj
index 85cecdc44..092f8580a 100644
--- a/Emby.Drawing/Emby.Drawing.csproj
+++ b/Emby.Drawing/Emby.Drawing.csproj
@@ -1,10 +1,16 @@
<Project Sdk="Microsoft.NET.Sdk">
+ <!-- ProjectGuid is only included as a requirement for SonarQube analysis -->
+ <PropertyGroup>
+ <ProjectGuid>{08FFF49B-F175-4807-A2B5-73B0EBD9F716}</ProjectGuid>
+ </PropertyGroup>
+
<PropertyGroup>
<TargetFramework>netstandard2.1</TargetFramework>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
+ <Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
@@ -17,4 +23,16 @@
<Compile Include="..\SharedVersion.cs" />
</ItemGroup>
+ <!-- Code analysers-->
+ <ItemGroup Condition=" '$(Configuration)' == 'Debug' ">
+ <PackageReference Include="Microsoft.CodeAnalysis.FxCopAnalyzers" Version="2.9.8" PrivateAssets="All" />
+ <PackageReference Include="SerilogAnalyzer" Version="0.15.0" PrivateAssets="All" />
+ <PackageReference Include="StyleCop.Analyzers" Version="1.1.118" PrivateAssets="All" />
+ <PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" PrivateAssets="All" />
+ </ItemGroup>
+
+ <PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
+ <CodeAnalysisRuleSet>../jellyfin.ruleset</CodeAnalysisRuleSet>
+ </PropertyGroup>
+
</Project>
diff --git a/Emby.Drawing/ImageProcessor.cs b/Emby.Drawing/ImageProcessor.cs
index ce8089e59..0b3bbe29e 100644
--- a/Emby.Drawing/ImageProcessor.cs
+++ b/Emby.Drawing/ImageProcessor.cs
@@ -3,18 +3,14 @@ using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
-using System.Threading;
using System.Threading.Tasks;
using MediaBrowser.Common.Extensions;
using MediaBrowser.Controller;
using MediaBrowser.Controller.Drawing;
using MediaBrowser.Controller.Entities;
-using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.MediaEncoding;
-using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Drawing;
using MediaBrowser.Model.Entities;
-using MediaBrowser.Model.Extensions;
using MediaBrowser.Model.IO;
using MediaBrowser.Model.Net;
using Microsoft.Extensions.Logging;
@@ -24,7 +20,7 @@ namespace Emby.Drawing
/// <summary>
/// Class ImageProcessor.
/// </summary>
- public class ImageProcessor : IImageProcessor, IDisposable
+ public sealed class ImageProcessor : IImageProcessor, IDisposable
{
// Increment this when there's a change requiring caches to be invalidated
private const string Version = "3";
@@ -32,52 +28,38 @@ namespace Emby.Drawing
private static readonly HashSet<string> _transparentImageTypes
= new HashSet<string>(StringComparer.OrdinalIgnoreCase) { ".png", ".webp", ".gif" };
- /// <summary>
- /// The _logger
- /// </summary>
private readonly ILogger _logger;
private readonly IFileSystem _fileSystem;
private readonly IServerApplicationPaths _appPaths;
- private IImageEncoder _imageEncoder;
- private readonly Func<ILibraryManager> _libraryManager;
- private readonly Func<IMediaEncoder> _mediaEncoder;
+ private readonly IImageEncoder _imageEncoder;
+ private readonly IMediaEncoder _mediaEncoder;
- private readonly Dictionary<string, LockInfo> _locks = new Dictionary<string, LockInfo>();
private bool _disposed = false;
/// <summary>
- ///
+ /// Initializes a new instance of the <see cref="ImageProcessor"/> class.
/// </summary>
- /// <param name="logger"></param>
- /// <param name="appPaths"></param>
- /// <param name="fileSystem"></param>
- /// <param name="imageEncoder"></param>
- /// <param name="libraryManager"></param>
- /// <param name="mediaEncoder"></param>
+ /// <param name="logger">The logger.</param>
+ /// <param name="appPaths">The server application paths.</param>
+ /// <param name="fileSystem">The filesystem.</param>
+ /// <param name="imageEncoder">The image encoder.</param>
+ /// <param name="mediaEncoder">The media encoder.</param>
public ImageProcessor(
ILogger<ImageProcessor> logger,
IServerApplicationPaths appPaths,
IFileSystem fileSystem,
IImageEncoder imageEncoder,
- Func<ILibraryManager> libraryManager,
- Func<IMediaEncoder> mediaEncoder)
+ IMediaEncoder mediaEncoder)
{
_logger = logger;
_fileSystem = fileSystem;
_imageEncoder = imageEncoder;
- _libraryManager = libraryManager;
_mediaEncoder = mediaEncoder;
_appPaths = appPaths;
-
- ImageEnhancers = Array.Empty<IImageEnhancer>();
-
- ImageHelper.ImageProcessor = this;
}
private string ResizedImageCachePath => Path.Combine(_appPaths.ImageCachePath, "resized-images");
- private string EnhancedImageCachePath => Path.Combine(_appPaths.ImageCachePath, "enhanced-images");
-
/// <inheritdoc />
public IReadOnlyCollection<string> SupportedInputFormats =>
new HashSet<string>(StringComparer.OrdinalIgnoreCase)
@@ -90,9 +72,7 @@ namespace Emby.Drawing
"aiff",
"cr2",
"crw",
-
- // Remove until supported
- //"nef",
+ "nef",
"orf",
"pef",
"arw",
@@ -112,24 +92,14 @@ namespace Emby.Drawing
};
/// <inheritdoc />
- public IReadOnlyCollection<IImageEnhancer> ImageEnhancers { get; set; }
-
- /// <inheritdoc />
public bool SupportsImageCollageCreation => _imageEncoder.SupportsImageCollageCreation;
/// <inheritdoc />
- public IImageEncoder ImageEncoder
- {
- get => _imageEncoder;
- set => _imageEncoder = value ?? throw new ArgumentNullException(nameof(value));
- }
-
- /// <inheritdoc />
public async Task ProcessImage(ImageProcessingOptions options, Stream toStream)
{
var file = await ProcessImage(options).ConfigureAwait(false);
- using (var fileStream = _fileSystem.GetFileStream(file.Item1, FileOpenMode.Open, FileAccessMode.Read, FileShareMode.Read, true))
+ using (var fileStream = new FileStream(file.Item1, FileMode.Open, FileAccess.Read, FileShare.Read, IODefaults.FileStreamBufferSize, true))
{
await fileStream.CopyToAsync(toStream).ConfigureAwait(false);
}
@@ -146,23 +116,9 @@ namespace Emby.Drawing
/// <inheritdoc />
public async Task<(string path, string mimeType, DateTime dateModified)> ProcessImage(ImageProcessingOptions options)
{
- if (options == null)
- {
- throw new ArgumentNullException(nameof(options));
- }
-
ItemImageInfo originalImage = options.Image;
BaseItem item = options.Item;
- if (!originalImage.IsLocalFile)
- {
- if (item == null)
- {
- item = _libraryManager().GetItemById(options.ItemId);
- }
- originalImage = await _libraryManager().ConvertImageToLocal(item, originalImage, options.ImageIndex).ConfigureAwait(false);
- }
-
string originalImagePath = originalImage.Path;
DateTime dateModified = originalImage.DateModified;
ImageDimensions? originalImageSize = null;
@@ -187,27 +143,6 @@ namespace Emby.Drawing
dateModified = supportedImageInfo.dateModified;
bool requiresTransparency = _transparentImageTypes.Contains(Path.GetExtension(originalImagePath));
- if (options.Enhancers.Count > 0)
- {
- if (item == null)
- {
- item = _libraryManager().GetItemById(options.ItemId);
- }
-
- var tuple = await GetEnhancedImage(new ItemImageInfo
- {
- DateModified = dateModified,
- Type = originalImage.Type,
- Path = originalImagePath
- }, requiresTransparency, item, options.ImageIndex, options.Enhancers, CancellationToken.None).ConfigureAwait(false);
-
- originalImagePath = tuple.path;
- dateModified = tuple.dateModified;
- requiresTransparency = tuple.transparent;
- // TODO: Get this info
- originalImageSize = null;
- }
-
bool autoOrient = false;
ImageOrientation? orientation = null;
if (item is Photo photo)
@@ -240,12 +175,6 @@ namespace Emby.Drawing
ImageFormat outputFormat = GetOutputFormat(options.SupportedOutputFormats, requiresTransparency);
string cacheFilePath = GetCacheFilePath(originalImagePath, newSize, quality, dateModified, outputFormat, options.AddPlayedIndicator, options.PercentPlayed, options.UnplayedCount, options.Blur, options.BackgroundColor, options.ForegroundLayer);
- CheckDisposed();
-
- LockInfo lockInfo = GetLock(cacheFilePath);
-
- await lockInfo.Lock.WaitAsync().ConfigureAwait(false);
-
try
{
if (!File.Exists(cacheFilePath))
@@ -271,10 +200,6 @@ namespace Emby.Drawing
_logger.LogError(ex, "Error encoding image");
return (originalImagePath, MimeTypes.GetMimeType(originalImagePath), dateModified);
}
- finally
- {
- ReleaseLock(cacheFilePath, lockInfo);
- }
}
private ImageFormat GetOutputFormat(IReadOnlyCollection<ImageFormat> clientSupportedFormats, bool requiresTransparency)
@@ -306,20 +231,18 @@ namespace Emby.Drawing
}
private string GetMimeType(ImageFormat format, string path)
- {
- switch(format)
- {
- case ImageFormat.Bmp: return MimeTypes.GetMimeType("i.bmp");
- case ImageFormat.Gif: return MimeTypes.GetMimeType("i.gif");
- case ImageFormat.Jpg: return MimeTypes.GetMimeType("i.jpg");
- case ImageFormat.Png: return MimeTypes.GetMimeType("i.png");
- case ImageFormat.Webp: return MimeTypes.GetMimeType("i.webp");
- default: return MimeTypes.GetMimeType(path);
- }
- }
+ => format switch
+ {
+ ImageFormat.Bmp => MimeTypes.GetMimeType("i.bmp"),
+ ImageFormat.Gif => MimeTypes.GetMimeType("i.gif"),
+ ImageFormat.Jpg => MimeTypes.GetMimeType("i.jpg"),
+ ImageFormat.Png => MimeTypes.GetMimeType("i.png"),
+ ImageFormat.Webp => MimeTypes.GetMimeType("i.webp"),
+ _ => MimeTypes.GetMimeType(path)
+ };
/// <summary>
- /// Gets the cache file path based on a set of parameters
+ /// Gets the cache file path based on a set of parameters.
/// </summary>
private string GetCacheFilePath(string originalPath, ImageDimensions outputSize, int quality, DateTime dateModified, ImageFormat format, bool addPlayedIndicator, double percentPlayed, int? unwatchedCount, int? blur, string backgroundColor, string foregroundLayer)
{
@@ -367,10 +290,6 @@ namespace Emby.Drawing
/// <inheritdoc />
public ImageDimensions GetImageDimensions(BaseItem item, ItemImageInfo info)
- => GetImageDimensions(item, info, true);
-
- /// <inheritdoc />
- public ImageDimensions GetImageDimensions(BaseItem item, ItemImageInfo info, bool updateItem)
{
int width = info.Width;
int height = info.Height;
@@ -387,11 +306,6 @@ namespace Emby.Drawing
info.Width = size.Width;
info.Height = size.Height;
- if (updateItem)
- {
- _libraryManager().UpdateImages(item);
- }
-
return size;
}
@@ -401,48 +315,17 @@ namespace Emby.Drawing
/// <inheritdoc />
public string GetImageCacheTag(BaseItem item, ItemImageInfo image)
- {
- var supportedEnhancers = GetSupportedEnhancers(item, image.Type).ToArray();
-
- return GetImageCacheTag(item, image, supportedEnhancers);
- }
+ => (item.Path + image.DateModified.Ticks).GetMD5().ToString("N", CultureInfo.InvariantCulture);
/// <inheritdoc />
public string GetImageCacheTag(BaseItem item, ChapterInfo chapter)
{
- try
+ return GetImageCacheTag(item, new ItemImageInfo
{
- return GetImageCacheTag(item, new ItemImageInfo
- {
- Path = chapter.ImagePath,
- Type = ImageType.Chapter,
- DateModified = chapter.ImageDateModified
- });
- }
- catch
- {
- return null;
- }
- }
-
- /// <inheritdoc />
- public string GetImageCacheTag(BaseItem item, ItemImageInfo image, IReadOnlyCollection<IImageEnhancer> imageEnhancers)
- {
- string originalImagePath = image.Path;
- DateTime dateModified = image.DateModified;
- ImageType imageType = image.Type;
-
- // Optimization
- if (imageEnhancers.Count == 0)
- {
- return (originalImagePath + dateModified.Ticks).GetMD5().ToString("N", CultureInfo.InvariantCulture);
- }
-
- // Cache name is created with supported enhancers combined with the last config change so we pick up new config changes
- var cacheKeys = imageEnhancers.Select(i => i.GetConfigurationCacheKey(item, imageType)).ToList();
- cacheKeys.Add(originalImagePath + dateModified.Ticks);
-
- return string.Join("|", cacheKeys).GetMD5().ToString("N", CultureInfo.InvariantCulture);
+ Path = chapter.ImagePath,
+ Type = ImageType.Chapter,
+ DateModified = chapter.ImageDateModified
+ });
}
private async Task<(string path, DateTime dateModified)> GetSupportedImage(string originalImagePath, DateTime dateModified)
@@ -463,13 +346,13 @@ namespace Emby.Drawing
{
string filename = (originalImagePath + dateModified.Ticks.ToString(CultureInfo.InvariantCulture)).GetMD5().ToString("N", CultureInfo.InvariantCulture);
- string cacheExtension = _mediaEncoder().SupportsEncoder("libwebp") ? ".webp" : ".png";
+ string cacheExtension = _mediaEncoder.SupportsEncoder("libwebp") ? ".webp" : ".png";
var outputPath = Path.Combine(_appPaths.ImageCachePath, "converted-images", filename + cacheExtension);
var file = _fileSystem.GetFileInfo(outputPath);
if (!file.Exists)
{
- await _mediaEncoder().ConvertImage(originalImagePath, outputPath).ConfigureAwait(false);
+ await _mediaEncoder.ConvertImage(originalImagePath, outputPath).ConfigureAwait(false);
dateModified = _fileSystem.GetLastWriteTimeUtc(outputPath);
}
else
@@ -488,154 +371,6 @@ namespace Emby.Drawing
return (originalImagePath, dateModified);
}
- /// <inheritdoc />
- public async Task<string> GetEnhancedImage(BaseItem item, ImageType imageType, int imageIndex)
- {
- var enhancers = GetSupportedEnhancers(item, imageType).ToArray();
-
- ItemImageInfo imageInfo = item.GetImageInfo(imageType, imageIndex);
-
- bool inputImageSupportsTransparency = SupportsTransparency(imageInfo.Path);
-
- var result = await GetEnhancedImage(imageInfo, inputImageSupportsTransparency, item, imageIndex, enhancers, CancellationToken.None);
-
- return result.path;
- }
-
- private async Task<(string path, DateTime dateModified, bool transparent)> GetEnhancedImage(
- ItemImageInfo image,
- bool inputImageSupportsTransparency,
- BaseItem item,
- int imageIndex,
- IReadOnlyCollection<IImageEnhancer> enhancers,
- CancellationToken cancellationToken)
- {
- var originalImagePath = image.Path;
- var dateModified = image.DateModified;
- var imageType = image.Type;
-
- try
- {
- var cacheGuid = GetImageCacheTag(item, image, enhancers);
-
- // Enhance if we have enhancers
- var enhancedImageInfo = await GetEnhancedImageInternal(originalImagePath, item, imageType, imageIndex, enhancers, cacheGuid, cancellationToken).ConfigureAwait(false);
-
- string enhancedImagePath = enhancedImageInfo.path;
-
- // If the path changed update dateModified
- if (!string.Equals(enhancedImagePath, originalImagePath, StringComparison.OrdinalIgnoreCase))
- {
- var treatmentRequiresTransparency = enhancedImageInfo.transparent;
-
- return (enhancedImagePath, _fileSystem.GetLastWriteTimeUtc(enhancedImagePath), treatmentRequiresTransparency);
- }
- }
- catch (Exception ex)
- {
- _logger.LogError(ex, "Error enhancing image");
- }
-
- return (originalImagePath, dateModified, inputImageSupportsTransparency);
- }
-
- /// <summary>
- /// Gets the enhanced image internal.
- /// </summary>
- /// <param name="originalImagePath">The original image path.</param>
- /// <param name="item">The item.</param>
- /// <param name="imageType">Type of the image.</param>
- /// <param name="imageIndex">Index of the image.</param>
- /// <param name="supportedEnhancers">The supported enhancers.</param>
- /// <param name="cacheGuid">The cache unique identifier.</param>
- /// <param name="cancellationToken">The cancellation token.</param>
- /// <returns>Task&lt;System.String&gt;.</returns>
- /// <exception cref="ArgumentNullException">
- /// originalImagePath
- /// or
- /// item
- /// </exception>
- private async Task<(string path, bool transparent)> GetEnhancedImageInternal(
- string originalImagePath,
- BaseItem item,
- ImageType imageType,
- int imageIndex,
- IReadOnlyCollection<IImageEnhancer> supportedEnhancers,
- string cacheGuid,
- CancellationToken cancellationToken = default)
- {
- if (string.IsNullOrEmpty(originalImagePath))
- {
- throw new ArgumentNullException(nameof(originalImagePath));
- }
-
- if (item == null)
- {
- throw new ArgumentNullException(nameof(item));
- }
-
- var treatmentRequiresTransparency = false;
- foreach (var enhancer in supportedEnhancers)
- {
- if (!treatmentRequiresTransparency)
- {
- treatmentRequiresTransparency = enhancer.GetEnhancedImageInfo(item, originalImagePath, imageType, imageIndex).RequiresTransparency;
- }
- }
-
- // All enhanced images are saved as png to allow transparency
- string cacheExtension = _imageEncoder.SupportedOutputFormats.Contains(ImageFormat.Webp) ?
- ".webp" :
- (treatmentRequiresTransparency ? ".png" : ".jpg");
-
- string enhancedImagePath = GetCachePath(EnhancedImageCachePath, cacheGuid + cacheExtension);
-
- LockInfo lockInfo = GetLock(enhancedImagePath);
-
- await lockInfo.Lock.WaitAsync(cancellationToken).ConfigureAwait(false);
-
- try
- {
- // Check again in case of contention
- if (File.Exists(enhancedImagePath))
- {
- return (enhancedImagePath, treatmentRequiresTransparency);
- }
-
- Directory.CreateDirectory(Path.GetDirectoryName(enhancedImagePath));
-
- await ExecuteImageEnhancers(supportedEnhancers, originalImagePath, enhancedImagePath, item, imageType, imageIndex).ConfigureAwait(false);
-
- return (enhancedImagePath, treatmentRequiresTransparency);
- }
- finally
- {
- ReleaseLock(enhancedImagePath, lockInfo);
- }
- }
-
- /// <summary>
- /// Executes the image enhancers.
- /// </summary>
- /// <param name="imageEnhancers">The image enhancers.</param>
- /// <param name="inputPath">The input path.</param>
- /// <param name="outputPath">The output path.</param>
- /// <param name="item">The item.</param>
- /// <param name="imageType">Type of the image.</param>
- /// <param name="imageIndex">Index of the image.</param>
- /// <returns>Task{EnhancedImage}.</returns>
- private static async Task ExecuteImageEnhancers(IEnumerable<IImageEnhancer> imageEnhancers, string inputPath, string outputPath, BaseItem item, ImageType imageType, int imageIndex)
- {
- // Run the enhancers sequentially in order of priority
- foreach (var enhancer in imageEnhancers)
- {
- await enhancer.EnhanceImageAsync(item, inputPath, outputPath, imageType, imageIndex).ConfigureAwait(false);
-
- // Feed the output into the next enhancer as input
- inputPath = outputPath;
- }
- }
-
/// <summary>
/// Gets the cache path.
/// </summary>
@@ -648,7 +383,7 @@ namespace Emby.Drawing
/// or
/// uniqueName
/// or
- /// fileExtension
+ /// fileExtension.
/// </exception>
public string GetCachePath(string path, string uniqueName, string fileExtension)
{
@@ -681,7 +416,7 @@ namespace Emby.Drawing
/// <exception cref="ArgumentNullException">
/// path
/// or
- /// filename
+ /// filename.
/// </exception>
public string GetCachePath(string path, string filename)
{
@@ -689,6 +424,7 @@ namespace Emby.Drawing
{
throw new ArgumentNullException(nameof(path));
}
+
if (string.IsNullOrEmpty(filename))
{
throw new ArgumentNullException(nameof(filename));
@@ -710,74 +446,19 @@ namespace Emby.Drawing
}
/// <inheritdoc />
- public IEnumerable<IImageEnhancer> GetSupportedEnhancers(BaseItem item, ImageType imageType)
- {
- foreach (var i in ImageEnhancers)
- {
- if (i.Supports(item, imageType))
- {
- yield return i;
- }
- }
- }
-
-
- private class LockInfo
- {
- public SemaphoreSlim Lock = new SemaphoreSlim(1, 1);
- public int Count = 1;
- }
-
- private LockInfo GetLock(string key)
- {
- lock (_locks)
- {
- if (_locks.TryGetValue(key, out LockInfo info))
- {
- info.Count++;
- }
- else
- {
- info = new LockInfo();
- _locks[key] = info;
- }
- return info;
- }
- }
-
- private void ReleaseLock(string key, LockInfo info)
+ public void Dispose()
{
- info.Lock.Release();
-
- lock (_locks)
+ if (_disposed)
{
- info.Count--;
- if (info.Count <= 0)
- {
- _locks.Remove(key);
- info.Lock.Dispose();
- }
+ return;
}
- }
-
- /// <inheritdoc />
- public void Dispose()
- {
- _disposed = true;
- var disposable = _imageEncoder as IDisposable;
- if (disposable != null)
+ if (_imageEncoder is IDisposable disposable)
{
disposable.Dispose();
}
- }
- private void CheckDisposed()
- {
- if (_disposed)
- {
- throw new ObjectDisposedException(GetType().Name);
- }
+ _disposed = true;
}
}
}
diff --git a/Emby.Naming/Audio/AlbumParser.cs b/Emby.Naming/Audio/AlbumParser.cs
index 4975b8e19..b63be3a64 100644
--- a/Emby.Naming/Audio/AlbumParser.cs
+++ b/Emby.Naming/Audio/AlbumParser.cs
@@ -1,10 +1,9 @@
+#nullable enable
#pragma warning disable CS1591
-#pragma warning disable SA1600
using System;
using System.Globalization;
using System.IO;
-using System.Linq;
using System.Text.RegularExpressions;
using Emby.Naming.Common;
@@ -19,15 +18,12 @@ namespace Emby.Naming.Audio
_options = options;
}
- public MultiPartResult ParseMultiPart(string path)
+ public bool IsMultiPart(string path)
{
- var result = new MultiPartResult();
-
var filename = Path.GetFileName(path);
-
- if (string.IsNullOrEmpty(filename))
+ if (filename.Length == 0)
{
- return result;
+ return false;
}
// TODO: Move this logic into options object
@@ -42,27 +38,30 @@ namespace Emby.Naming.Audio
filename = filename.Replace(')', ' ');
filename = Regex.Replace(filename, @"\s+", " ");
- filename = filename.TrimStart();
+ ReadOnlySpan<char> trimmedFilename = filename.TrimStart();
foreach (var prefix in _options.AlbumStackingPrefixes)
{
- if (filename.IndexOf(prefix, StringComparison.OrdinalIgnoreCase) != 0)
+ if (!trimmedFilename.StartsWith(prefix, StringComparison.OrdinalIgnoreCase))
{
continue;
}
- var tmp = filename.Substring(prefix.Length);
+ var tmp = trimmedFilename.Slice(prefix.Length).Trim();
- tmp = tmp.Trim().Split(' ').FirstOrDefault() ?? string.Empty;
+ int index = tmp.IndexOf(' ');
+ if (index != -1)
+ {
+ tmp = tmp.Slice(0, index);
+ }
if (int.TryParse(tmp, NumberStyles.Integer, CultureInfo.InvariantCulture, out _))
{
- result.IsMultiPart = true;
- break;
+ return true;
}
}
- return result;
+ return false;
}
}
}
diff --git a/Emby.Naming/Audio/AudioFileParser.cs b/Emby.Naming/Audio/AudioFileParser.cs
index 9f21e93dc..6b2f4be93 100644
--- a/Emby.Naming/Audio/AudioFileParser.cs
+++ b/Emby.Naming/Audio/AudioFileParser.cs
@@ -1,5 +1,5 @@
+#nullable enable
#pragma warning disable CS1591
-#pragma warning disable SA1600
using System;
using System.IO;
@@ -8,19 +8,12 @@ using Emby.Naming.Common;
namespace Emby.Naming.Audio
{
- public class AudioFileParser
+ public static class AudioFileParser
{
- private readonly NamingOptions _options;
-
- public AudioFileParser(NamingOptions options)
- {
- _options = options;
- }
-
- public bool IsAudioFile(string path)
+ public static bool IsAudioFile(string path, NamingOptions options)
{
- var extension = Path.GetExtension(path) ?? string.Empty;
- return _options.AudioFileExtensions.Contains(extension, StringComparer.OrdinalIgnoreCase);
+ var extension = Path.GetExtension(path);
+ return options.AudioFileExtensions.Contains(extension, StringComparer.OrdinalIgnoreCase);
}
}
}
diff --git a/Emby.Naming/Audio/MultiPartResult.cs b/Emby.Naming/Audio/MultiPartResult.cs
deleted file mode 100644
index 8f68d97fa..000000000
--- a/Emby.Naming/Audio/MultiPartResult.cs
+++ /dev/null
@@ -1,26 +0,0 @@
-#pragma warning disable CS1591
-#pragma warning disable SA1600
-
-namespace Emby.Naming.Audio
-{
- public class MultiPartResult
- {
- /// <summary>
- /// Gets or sets the name.
- /// </summary>
- /// <value>The name.</value>
- public string Name { get; set; }
-
- /// <summary>
- /// Gets or sets the part.
- /// </summary>
- /// <value>The part.</value>
- public string Part { get; set; }
-
- /// <summary>
- /// Gets or sets a value indicating whether this instance is multi part.
- /// </summary>
- /// <value><c>true</c> if this instance is multi part; otherwise, <c>false</c>.</value>
- public bool IsMultiPart { get; set; }
- }
-}
diff --git a/Emby.Naming/AudioBook/AudioBookFileInfo.cs b/Emby.Naming/AudioBook/AudioBookFileInfo.cs
index 769e3d7fa..c4863b50a 100644
--- a/Emby.Naming/AudioBook/AudioBookFileInfo.cs
+++ b/Emby.Naming/AudioBook/AudioBookFileInfo.cs
@@ -32,12 +32,12 @@ namespace Emby.Naming.AudioBook
public int? ChapterNumber { get; set; }
/// <summary>
- /// Gets or sets the type.
+ /// Gets or sets a value indicating whether this instance is a directory.
/// </summary>
/// <value>The type.</value>
public bool IsDirectory { get; set; }
- /// <inheritdoc/>
+ /// <inheritdoc />
public int CompareTo(AudioBookFileInfo other)
{
if (ReferenceEquals(this, other))
diff --git a/Emby.Naming/AudioBook/AudioBookFilePathParser.cs b/Emby.Naming/AudioBook/AudioBookFilePathParser.cs
index 8dc2e1b97..5494df9d6 100644
--- a/Emby.Naming/AudioBook/AudioBookFilePathParser.cs
+++ b/Emby.Naming/AudioBook/AudioBookFilePathParser.cs
@@ -1,5 +1,4 @@
#pragma warning disable CS1591
-#pragma warning disable SA1600
using System;
using System.Globalization;
diff --git a/Emby.Naming/AudioBook/AudioBookFilePathParserResult.cs b/Emby.Naming/AudioBook/AudioBookFilePathParserResult.cs
index 68d6ca4d4..e28a58db7 100644
--- a/Emby.Naming/AudioBook/AudioBookFilePathParserResult.cs
+++ b/Emby.Naming/AudioBook/AudioBookFilePathParserResult.cs
@@ -1,5 +1,4 @@
#pragma warning disable CS1591
-#pragma warning disable SA1600
namespace Emby.Naming.AudioBook
{
diff --git a/Emby.Naming/AudioBook/AudioBookListResolver.cs b/Emby.Naming/AudioBook/AudioBookListResolver.cs
index 97f359285..f4ba11a0d 100644
--- a/Emby.Naming/AudioBook/AudioBookListResolver.cs
+++ b/Emby.Naming/AudioBook/AudioBookListResolver.cs
@@ -1,5 +1,4 @@
#pragma warning disable CS1591
-#pragma warning disable SA1600
using System.Collections.Generic;
using System.Linq;
@@ -30,40 +29,19 @@ namespace Emby.Naming.AudioBook
// Filter out all extras, otherwise they could cause stacks to not be resolved
// See the unit test TestStackedWithTrailer
var metadata = audiobookFileInfos
- .Select(i => new FileSystemMetadata
- {
- FullName = i.Path,
- IsDirectory = i.IsDirectory
- });
+ .Select(i => new FileSystemMetadata { FullName = i.Path, IsDirectory = i.IsDirectory });
var stackResult = new StackResolver(_options)
.ResolveAudioBooks(metadata);
- var list = new List<AudioBookInfo>();
-
- foreach (var stack in stackResult.Stacks)
+ foreach (var stack in stackResult)
{
var stackFiles = stack.Files.Select(i => audioBookResolver.Resolve(i, stack.IsDirectoryStack)).ToList();
stackFiles.Sort();
- var info = new AudioBookInfo
- {
- Files = stackFiles,
- Name = stack.Name
- };
- list.Add(info);
- }
-
- // Whatever files are left, just add them
- /*list.AddRange(remainingFiles.Select(i => new AudioBookInfo
- {
- Files = new List<AudioBookFileInfo> { i },
- Name = i.,
- Year = i.Year
- }));*/
-
- var orderedList = list.OrderBy(i => i.Name);
+ var info = new AudioBookInfo { Files = stackFiles, Name = stack.Name };
- return orderedList;
+ yield return info;
+ }
}
}
}
diff --git a/Emby.Naming/AudioBook/AudioBookResolver.cs b/Emby.Naming/AudioBook/AudioBookResolver.cs
index 0b0d2035e..5466b4637 100644
--- a/Emby.Naming/AudioBook/AudioBookResolver.cs
+++ b/Emby.Naming/AudioBook/AudioBookResolver.cs
@@ -1,5 +1,4 @@
#pragma warning disable CS1591
-#pragma warning disable SA1600
using System;
using System.IO;
diff --git a/Emby.Naming/Common/EpisodeExpression.cs b/Emby.Naming/Common/EpisodeExpression.cs
index 30a74fb65..ed6ba8881 100644
--- a/Emby.Naming/Common/EpisodeExpression.cs
+++ b/Emby.Naming/Common/EpisodeExpression.cs
@@ -1,5 +1,4 @@
#pragma warning disable CS1591
-#pragma warning disable SA1600
using System;
using System.Text.RegularExpressions;
@@ -11,6 +10,19 @@ namespace Emby.Naming.Common
private string _expression;
private Regex _regex;
+ public EpisodeExpression(string expression, bool byDate)
+ {
+ Expression = expression;
+ IsByDate = byDate;
+ DateTimeFormats = Array.Empty<string>();
+ SupportsAbsoluteEpisodeNumbers = true;
+ }
+
+ public EpisodeExpression(string expression)
+ : this(expression, false)
+ {
+ }
+
public string Expression
{
get => _expression;
@@ -31,24 +43,6 @@ namespace Emby.Naming.Common
public string[] DateTimeFormats { get; set; }
- public Regex Regex => _regex ?? (_regex = new Regex(Expression, RegexOptions.IgnoreCase | RegexOptions.Compiled));
-
- public EpisodeExpression(string expression, bool byDate)
- {
- Expression = expression;
- IsByDate = byDate;
- DateTimeFormats = Array.Empty<string>();
- SupportsAbsoluteEpisodeNumbers = true;
- }
-
- public EpisodeExpression(string expression)
- : this(expression, false)
- {
- }
-
- public EpisodeExpression()
- : this(null)
- {
- }
+ public Regex Regex => _regex ??= new Regex(Expression, RegexOptions.IgnoreCase | RegexOptions.Compiled);
}
}
diff --git a/Emby.Naming/Common/MediaType.cs b/Emby.Naming/Common/MediaType.cs
index a61f10489..cc18ce4cd 100644
--- a/Emby.Naming/Common/MediaType.cs
+++ b/Emby.Naming/Common/MediaType.cs
@@ -1,5 +1,4 @@
#pragma warning disable CS1591
-#pragma warning disable SA1600
namespace Emby.Naming.Common
{
diff --git a/Emby.Naming/Common/NamingOptions.cs b/Emby.Naming/Common/NamingOptions.cs
index a2105889b..a2d75d0b8 100644
--- a/Emby.Naming/Common/NamingOptions.cs
+++ b/Emby.Naming/Common/NamingOptions.cs
@@ -1,5 +1,4 @@
#pragma warning disable CS1591
-#pragma warning disable SA1600
using System;
using System.Linq;
@@ -11,46 +10,6 @@ namespace Emby.Naming.Common
{
public class NamingOptions
{
- public string[] AudioFileExtensions { get; set; }
-
- public string[] AlbumStackingPrefixes { get; set; }
-
- public string[] SubtitleFileExtensions { get; set; }
-
- public char[] SubtitleFlagDelimiters { get; set; }
-
- public string[] SubtitleForcedFlags { get; set; }
-
- public string[] SubtitleDefaultFlags { get; set; }
-
- public EpisodeExpression[] EpisodeExpressions { get; set; }
-
- public string[] EpisodeWithoutSeasonExpressions { get; set; }
-
- public string[] EpisodeMultiPartExpressions { get; set; }
-
- public string[] VideoFileExtensions { get; set; }
-
- public string[] StubFileExtensions { get; set; }
-
- public string[] AudioBookPartsExpressions { get; set; }
-
- public StubTypeRule[] StubTypes { get; set; }
-
- public char[] VideoFlagDelimiters { get; set; }
-
- public Format3DRule[] Format3DRules { get; set; }
-
- public string[] VideoFileStackingExpressions { get; set; }
-
- public string[] CleanDateTimes { get; set; }
-
- public string[] CleanStrings { get; set; }
-
- public EpisodeExpression[] MultipleEpisodeExpressions { get; set; }
-
- public ExtraRule[] VideoExtraRules { get; set; }
-
public NamingOptions()
{
VideoFileExtensions = new[]
@@ -177,13 +136,13 @@ namespace Emby.Naming.Common
CleanDateTimes = new[]
{
- @"(.+[^_\,\.\(\)\[\]\-])[_\.\(\)\[\]\-](\d{4})([ _\,\.\(\)\[\]\-][^\d]|).*(\d{4})*"
+ @"(.+[^_\,\.\(\)\[\]\-])[_\.\(\)\[\]\-](19\d{2}|20\d{2})([ _\,\.\(\)\[\]\-][^0-9]|).*(19\d{2}|20\d{2})*",
+ @"(.+[^_\,\.\(\)\[\]\-])[ _\.\(\)\[\]\-]+(19\d{2}|20\d{2})([ _\,\.\(\)\[\]\-][^0-9]|).*(19\d{2}|20\d{2})*"
};
CleanStrings = new[]
{
- @"[ _\,\.\(\)\[\]\-](ac3|dts|custom|dc|divx|divx5|dsr|dsrip|dutch|dvd|dvdrip|dvdscr|dvdscreener|screener|dvdivx|cam|fragment|fs|hdtv|hdrip|hdtvrip|internal|limited|multisubs|ntsc|ogg|ogm|pal|pdtv|proper|repack|rerip|retail|cd[1-9]|r3|r5|bd5|se|svcd|swedish|german|read.nfo|nfofix|unrated|ws|telesync|ts|telecine|tc|brrip|bdrip|480p|480i|576p|576i|720p|720i|1080p|1080i|2160p|hrhd|hrhdtv|hddvd|bluray|x264|h264|xvid|xvidvd|xxx|www.www|\[.*\])([ _\,\.\(\)\[\]\-]|$)",
- @"[ _\,\.\(\)\[\]\-](3d|sbs|tab|hsbs|htab|mvc|\[.*\])([ _\,\.\(\)\[\]\-]|$)",
+ @"[ _\,\.\(\)\[\]\-](3d|sbs|tab|hsbs|htab|mvc|HDR|HDC|UHD|UltraHD|4k|ac3|dts|custom|dc|divx|divx5|dsr|dsrip|dutch|dvd|dvdrip|dvdscr|dvdscreener|screener|dvdivx|cam|fragment|fs|hdtv|hdrip|hdtvrip|internal|limited|multisubs|ntsc|ogg|ogm|pal|pdtv|proper|repack|rerip|retail|cd[1-9]|r3|r5|bd5|se|svcd|swedish|german|read.nfo|nfofix|unrated|ws|telesync|ts|telecine|tc|brrip|bdrip|480p|480i|576p|576i|720p|720i|1080p|1080i|2160p|hrhd|hrhdtv|hddvd|bluray|x264|h264|xvid|xvidvd|xxx|www.www|\[.*\])([ _\,\.\(\)\[\]\-]|$)",
@"(\[.*\])"
};
@@ -318,7 +277,7 @@ namespace Emby.Naming.Common
// This isn't a Kodi naming rule, but the expression below causes false positives,
// so we make sure this one gets tested first.
// "Foo Bar 889"
- new EpisodeExpression(@".*[\\\/](?![Ee]pisode)(?<seriesname>[\w\s]+?)\s(?<epnumber>\d{1,3})(-(?<endingepnumber>\d{2,3}))*[^\\\/]*$")
+ new EpisodeExpression(@".*[\\\/](?![Ee]pisode)(?<seriesname>[\w\s]+?)\s(?<epnumber>\d{1,3})(-(?<endingepnumber>\d{2,3}))*[^\\\/x]*$")
{
IsNamed = true
},
@@ -340,7 +299,7 @@ namespace Emby.Naming.Common
// *** End Kodi Standard Naming
-                // [bar] Foo - 1 [baz]
+ // [bar] Foo - 1 [baz]
new EpisodeExpression(@".*?(\[.*?\])+.*?(?<seriesname>[\w\s]+?)[-\s_]+(?<epnumber>\d+).*$")
{
IsNamed = true
@@ -547,7 +506,63 @@ namespace Emby.Naming.Common
RuleType = ExtraRuleType.Suffix,
Token = "-short",
MediaType = MediaType.Video
- }
+ },
+ new ExtraRule
+ {
+ ExtraType = ExtraType.BehindTheScenes,
+ RuleType = ExtraRuleType.DirectoryName,
+ Token = "behind the scenes",
+ MediaType = MediaType.Video,
+ },
+ new ExtraRule
+ {
+ ExtraType = ExtraType.DeletedScene,
+ RuleType = ExtraRuleType.DirectoryName,
+ Token = "deleted scenes",
+ MediaType = MediaType.Video,
+ },
+ new ExtraRule
+ {
+ ExtraType = ExtraType.Interview,
+ RuleType = ExtraRuleType.DirectoryName,
+ Token = "interviews",
+ MediaType = MediaType.Video,
+ },
+ new ExtraRule
+ {
+ ExtraType = ExtraType.Scene,
+ RuleType = ExtraRuleType.DirectoryName,
+ Token = "scenes",
+ MediaType = MediaType.Video,
+ },
+ new ExtraRule
+ {
+ ExtraType = ExtraType.Sample,
+ RuleType = ExtraRuleType.DirectoryName,
+ Token = "samples",
+ MediaType = MediaType.Video,
+ },
+ new ExtraRule
+ {
+ ExtraType = ExtraType.Clip,
+ RuleType = ExtraRuleType.DirectoryName,
+ Token = "shorts",
+ MediaType = MediaType.Video,
+ },
+ new ExtraRule
+ {
+ ExtraType = ExtraType.Clip,
+ RuleType = ExtraRuleType.DirectoryName,
+ Token = "featurettes",
+ MediaType = MediaType.Video,
+ },
+ new ExtraRule
+ {
+ ExtraType = ExtraType.Unknown,
+ RuleType = ExtraRuleType.DirectoryName,
+ Token = "extras",
+ MediaType = MediaType.Video,
+ },
};
Format3DRules = new[]
@@ -682,11 +697,54 @@ namespace Emby.Naming.Common
Compile();
}
+ public string[] AudioFileExtensions { get; set; }
+
+ public string[] AlbumStackingPrefixes { get; set; }
+
+ public string[] SubtitleFileExtensions { get; set; }
+
+ public char[] SubtitleFlagDelimiters { get; set; }
+
+ public string[] SubtitleForcedFlags { get; set; }
+
+ public string[] SubtitleDefaultFlags { get; set; }
+
+ public EpisodeExpression[] EpisodeExpressions { get; set; }
+
+ public string[] EpisodeWithoutSeasonExpressions { get; set; }
+
+ public string[] EpisodeMultiPartExpressions { get; set; }
+
+ public string[] VideoFileExtensions { get; set; }
+
+ public string[] StubFileExtensions { get; set; }
+
+ public string[] AudioBookPartsExpressions { get; set; }
+
+ public StubTypeRule[] StubTypes { get; set; }
+
+ public char[] VideoFlagDelimiters { get; set; }
+
+ public Format3DRule[] Format3DRules { get; set; }
+
+ public string[] VideoFileStackingExpressions { get; set; }
+
+ public string[] CleanDateTimes { get; set; }
+
+ public string[] CleanStrings { get; set; }
+
+ public EpisodeExpression[] MultipleEpisodeExpressions { get; set; }
+
+ public ExtraRule[] VideoExtraRules { get; set; }
+
public Regex[] VideoFileStackingRegexes { get; private set; }
+
public Regex[] CleanDateTimeRegexes { get; private set; }
+
public Regex[] CleanStringRegexes { get; private set; }
public Regex[] EpisodeWithoutSeasonRegexes { get; private set; }
+
public Regex[] EpisodeMultiPartRegexes { get; private set; }
public void Compile()
diff --git a/Emby.Naming/Emby.Naming.csproj b/Emby.Naming/Emby.Naming.csproj
index 900b9694c..c017e76c7 100644
--- a/Emby.Naming/Emby.Naming.csproj
+++ b/Emby.Naming/Emby.Naming.csproj
@@ -1,12 +1,14 @@
<Project Sdk="Microsoft.NET.Sdk">
+ <!-- ProjectGuid is only included as a requirement for SonarQube analysis -->
+ <PropertyGroup>
+ <ProjectGuid>{E5AF7B26-2239-4CE0-B477-0AA2018EDAA2}</ProjectGuid>
+ </PropertyGroup>
+
<PropertyGroup>
<TargetFramework>netstandard2.1</TargetFramework>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
- </PropertyGroup>
-
- <PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
</PropertyGroup>
@@ -27,7 +29,7 @@
<!-- Code Analyzers-->
<ItemGroup Condition=" '$(Configuration)' == 'Debug' ">
- <PackageReference Include="Microsoft.CodeAnalysis.FxCopAnalyzers" Version="2.9.8" PrivateAssets="All" />
+ <!-- TODO: <PackageReference Include="Microsoft.CodeAnalysis.FxCopAnalyzers" Version="2.9.8" PrivateAssets="All" /> -->
<PackageReference Include="SerilogAnalyzer" Version="0.15.0" PrivateAssets="All" />
<PackageReference Include="StyleCop.Analyzers" Version="1.1.118" PrivateAssets="All" />
<PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" PrivateAssets="All" />
diff --git a/Emby.Naming/Subtitles/SubtitleInfo.cs b/Emby.Naming/Subtitles/SubtitleInfo.cs
index fe42846c6..f39c496b7 100644
--- a/Emby.Naming/Subtitles/SubtitleInfo.cs
+++ b/Emby.Naming/Subtitles/SubtitleInfo.cs
@@ -1,5 +1,4 @@
#pragma warning disable CS1591
-#pragma warning disable SA1600
namespace Emby.Naming.Subtitles
{
diff --git a/Emby.Naming/Subtitles/SubtitleParser.cs b/Emby.Naming/Subtitles/SubtitleParser.cs
index 99680c622..24e59f90a 100644
--- a/Emby.Naming/Subtitles/SubtitleParser.cs
+++ b/Emby.Naming/Subtitles/SubtitleParser.cs
@@ -1,5 +1,5 @@
+#nullable enable
#pragma warning disable CS1591
-#pragma warning disable SA1600
using System;
using System.IO;
@@ -17,11 +17,11 @@ namespace Emby.Naming.Subtitles
_options = options;
}
- public SubtitleInfo ParseFile(string path)
+ public SubtitleInfo? ParseFile(string path)
{
- if (string.IsNullOrEmpty(path))
+ if (path.Length == 0)
{
- throw new ArgumentNullException(nameof(path));
+ throw new ArgumentException("File path can't be empty.", nameof(path));
}
var extension = Path.GetExtension(path);
@@ -31,7 +31,6 @@ namespace Emby.Naming.Subtitles
}
var flags = GetFlags(path);
-
var info = new SubtitleInfo
{
Path = path,
@@ -39,13 +38,14 @@ namespace Emby.Naming.Subtitles
IsForced = _options.SubtitleForcedFlags.Any(i => flags.Contains(i, StringComparer.OrdinalIgnoreCase))
};
- var parts = flags.Where(i => !_options.SubtitleDefaultFlags.Contains(i, StringComparer.OrdinalIgnoreCase) && !_options.SubtitleForcedFlags.Contains(i, StringComparer.OrdinalIgnoreCase))
+ var parts = flags.Where(i => !_options.SubtitleDefaultFlags.Contains(i, StringComparer.OrdinalIgnoreCase)
+ && !_options.SubtitleForcedFlags.Contains(i, StringComparer.OrdinalIgnoreCase))
.ToList();
// Should have a name, language and file extension
if (parts.Count >= 3)
{
- info.Language = parts[parts.Count - 2];
+ info.Language = parts[^2];
}
return info;
@@ -53,11 +53,6 @@ namespace Emby.Naming.Subtitles
private string[] GetFlags(string path)
{
- if (string.IsNullOrEmpty(path))
- {
- throw new ArgumentNullException(nameof(path));
- }
-
// Note: the tags need be be surrounded be either a space ( ), hyphen -, dot . or underscore _.
var file = Path.GetFileName(path);
diff --git a/Emby.Naming/TV/EpisodeInfo.cs b/Emby.Naming/TV/EpisodeInfo.cs
index 667129a57..250df4e2d 100644
--- a/Emby.Naming/TV/EpisodeInfo.cs
+++ b/Emby.Naming/TV/EpisodeInfo.cs
@@ -1,5 +1,4 @@
#pragma warning disable CS1591
-#pragma warning disable SA1600
namespace Emby.Naming.TV
{
diff --git a/Emby.Naming/TV/EpisodePathParser.cs b/Emby.Naming/TV/EpisodePathParser.cs
index 4fac543f9..a6af689c7 100644
--- a/Emby.Naming/TV/EpisodePathParser.cs
+++ b/Emby.Naming/TV/EpisodePathParser.cs
@@ -1,5 +1,5 @@
#pragma warning disable CS1591
-#pragma warning disable SA1600
+#nullable enable
using System;
using System.Collections.Generic;
@@ -18,7 +18,13 @@ namespace Emby.Naming.TV
_options = options;
}
- public EpisodePathParserResult Parse(string path, bool isDirectory, bool? isNamed = null, bool? isOptimistic = null, bool? supportsAbsoluteNumbers = null, bool fillExtendedInfo = true)
+ public EpisodePathParserResult Parse(
+ string path,
+ bool isDirectory,
+ bool? isNamed = null,
+ bool? isOptimistic = null,
+ bool? supportsAbsoluteNumbers = null,
+ bool fillExtendedInfo = true)
{
// Added to be able to use regex patterns which require a file extension.
// There were no failed tests without this block, but to be safe, we can keep it until
@@ -28,7 +34,7 @@ namespace Emby.Naming.TV
path += ".mp4";
}
- EpisodePathParserResult result = null;
+ EpisodePathParserResult? result = null;
foreach (var expression in _options.EpisodeExpressions)
{
@@ -64,7 +70,7 @@ namespace Emby.Naming.TV
{
result.SeriesName = result.SeriesName
.Trim()
- .Trim(new[] { '_', '.', '-' })
+ .Trim('_', '.', '-')
.Trim();
}
}
@@ -131,12 +137,12 @@ namespace Emby.Naming.TV
var endingNumberGroup = match.Groups["endingepnumber"];
if (endingNumberGroup.Success)
{
- // Will only set EndingEpsiodeNumber if the captured number is not followed by additional numbers
+ // Will only set EndingEpisodeNumber if the captured number is not followed by additional numbers
// or a 'p' or 'i' as what you would get with a pixel resolution specification.
// It avoids erroneous parsing of something like "series-s09e14-1080p.mkv" as a multi-episode from E14 to E108
int nextIndex = endingNumberGroup.Index + endingNumberGroup.Length;
if (nextIndex >= name.Length
- || "0123456789iIpP".IndexOf(name[nextIndex]) == -1)
+ || !"0123456789iIpP".Contains(name[nextIndex], StringComparison.Ordinal))
{
if (int.TryParse(endingNumberGroup.Value, NumberStyles.Integer, CultureInfo.InvariantCulture, out num))
{
diff --git a/Emby.Naming/TV/EpisodePathParserResult.cs b/Emby.Naming/TV/EpisodePathParserResult.cs
index 3acbbc101..05f921edc 100644
--- a/Emby.Naming/TV/EpisodePathParserResult.cs
+++ b/Emby.Naming/TV/EpisodePathParserResult.cs
@@ -1,5 +1,4 @@
#pragma warning disable CS1591
-#pragma warning disable SA1600
namespace Emby.Naming.TV
{
diff --git a/Emby.Naming/TV/EpisodeResolver.cs b/Emby.Naming/TV/EpisodeResolver.cs
index 5e115fc75..6994f69fc 100644
--- a/Emby.Naming/TV/EpisodeResolver.cs
+++ b/Emby.Naming/TV/EpisodeResolver.cs
@@ -1,5 +1,5 @@
#pragma warning disable CS1591
-#pragma warning disable SA1600
+#nullable enable
using System;
using System.IO;
@@ -18,7 +18,7 @@ namespace Emby.Naming.TV
_options = options;
}
- public EpisodeInfo Resolve(
+ public EpisodeInfo? Resolve(
string path,
bool isDirectory,
bool? isNamed = null,
@@ -26,14 +26,9 @@ namespace Emby.Naming.TV
bool? supportsAbsoluteNumbers = null,
bool fillExtendedInfo = true)
{
- if (string.IsNullOrEmpty(path))
- {
- throw new ArgumentNullException(nameof(path));
- }
-
bool isStub = false;
- string container = null;
- string stubType = null;
+ string? container = null;
+ string? stubType = null;
if (!isDirectory)
{
@@ -41,17 +36,13 @@ namespace Emby.Naming.TV
// Check supported extensions
if (!_options.VideoFileExtensions.Contains(extension, StringComparer.OrdinalIgnoreCase))
{
- var stubResult = StubResolver.ResolveFile(path, _options);
-
- isStub = stubResult.IsStub;
-
// It's not supported. Check stub extensions
- if (!isStub)
+ if (!StubResolver.TryResolveFile(path, _options, out stubType))
{
return null;
}
- stubType = stubResult.StubType;
+ isStub = true;
}
container = extension.TrimStart('.');
diff --git a/Emby.Naming/TV/SeasonPathParser.cs b/Emby.Naming/TV/SeasonPathParser.cs
index e5f90e966..2fa6b4353 100644
--- a/Emby.Naming/TV/SeasonPathParser.cs
+++ b/Emby.Naming/TV/SeasonPathParser.cs
@@ -1,32 +1,13 @@
#pragma warning disable CS1591
-#pragma warning disable SA1600
using System;
using System.Globalization;
using System.IO;
-using System.Linq;
namespace Emby.Naming.TV
{
- public class SeasonPathParser
+ public static class SeasonPathParser
{
- public SeasonPathParserResult Parse(string path, bool supportSpecialAliases, bool supportNumericSeasonFolders)
- {
- var result = new SeasonPathParserResult();
-
- var seasonNumberInfo = GetSeasonNumberFromPath(path, supportSpecialAliases, supportNumericSeasonFolders);
-
- result.SeasonNumber = seasonNumberInfo.seasonNumber;
-
- if (result.SeasonNumber.HasValue)
- {
- result.Success = true;
- result.IsSeasonFolder = seasonNumberInfo.isSeasonFolder;
- }
-
- return result;
- }
-
/// <summary>
/// A season folder must contain one of these somewhere in the name.
/// </summary>
@@ -42,6 +23,23 @@ namespace Emby.Naming.TV
"stagione"
};
+ public static SeasonPathParserResult Parse(string path, bool supportSpecialAliases, bool supportNumericSeasonFolders)
+ {
+ var result = new SeasonPathParserResult();
+
+ var (seasonNumber, isSeasonFolder) = GetSeasonNumberFromPath(path, supportSpecialAliases, supportNumericSeasonFolders);
+
+ result.SeasonNumber = seasonNumber;
+
+ if (result.SeasonNumber.HasValue)
+ {
+ result.Success = true;
+ result.IsSeasonFolder = isSeasonFolder;
+ }
+
+ return result;
+ }
+
/// <summary>
/// Gets the season number from path.
/// </summary>
@@ -90,12 +88,10 @@ namespace Emby.Naming.TV
// Look for one of the season folder names
foreach (var name in _seasonFolderNames)
{
- var index = filename.IndexOf(name, StringComparison.OrdinalIgnoreCase);
-
- if (index != -1)
+ if (filename.Contains(name, StringComparison.OrdinalIgnoreCase))
{
var result = GetSeasonNumberFromPathSubstring(filename.Replace(name, " ", StringComparison.OrdinalIgnoreCase));
- if (result.Item1.HasValue)
+ if (result.seasonNumber.HasValue)
{
return result;
}
@@ -105,25 +101,32 @@ namespace Emby.Naming.TV
}
var parts = filename.Split(new[] { '.', '_', ' ', '-' }, StringSplitOptions.RemoveEmptyEntries);
- var resultNumber = parts.Select(GetSeasonNumberFromPart).FirstOrDefault(i => i.HasValue);
- return (resultNumber, true);
+ for (int i = 0; i < parts.Length; i++)
+ {
+ if (TryGetSeasonNumberFromPart(parts[i], out int seasonNumber))
+ {
+ return (seasonNumber, true);
+ }
+ }
+
+ return (null, true);
}
- private static int? GetSeasonNumberFromPart(string part)
+ private static bool TryGetSeasonNumberFromPart(ReadOnlySpan<char> part, out int seasonNumber)
{
+ seasonNumber = 0;
if (part.Length < 2 || !part.StartsWith("s", StringComparison.OrdinalIgnoreCase))
{
- return null;
+ return false;
}
- part = part.Substring(1);
-
- if (int.TryParse(part, NumberStyles.Integer, CultureInfo.InvariantCulture, out var value))
+ if (int.TryParse(part.Slice(1), NumberStyles.Integer, CultureInfo.InvariantCulture, out var value))
{
- return value;
+ seasonNumber = value;
+ return true;
}
- return null;
+ return false;
}
/// <summary>
@@ -131,7 +134,7 @@ namespace Emby.Naming.TV
/// </summary>
/// <param name="path">The path.</param>
/// <returns>System.Nullable{System.Int32}.</returns>
- private static (int? seasonNumber, bool isSeasonFolder) GetSeasonNumberFromPathSubstring(string path)
+ private static (int? seasonNumber, bool isSeasonFolder) GetSeasonNumberFromPathSubstring(ReadOnlySpan<char> path)
{
var numericStart = -1;
var length = 0;
@@ -142,7 +145,7 @@ namespace Emby.Naming.TV
// Find out where the numbers start, and then keep going until they end
for (var i = 0; i < path.Length; i++)
{
- if (char.IsNumber(path, i))
+ if (char.IsNumber(path[i]))
{
if (!hasOpenParenth)
{
@@ -150,6 +153,7 @@ namespace Emby.Naming.TV
{
numericStart = i;
}
+
length++;
}
}
@@ -161,11 +165,11 @@ namespace Emby.Naming.TV
}
var currentChar = path[i];
- if (currentChar.Equals('('))
+ if (currentChar == '(')
{
hasOpenParenth = true;
}
- else if (currentChar.Equals(')'))
+ else if (currentChar == ')')
{
hasOpenParenth = false;
}
@@ -176,7 +180,7 @@ namespace Emby.Naming.TV
return (null, isSeasonFolder);
}
- return (int.Parse(path.Substring(numericStart, length), CultureInfo.InvariantCulture), isSeasonFolder);
+ return (int.Parse(path.Slice(numericStart, length), provider: CultureInfo.InvariantCulture), isSeasonFolder);
}
}
}
diff --git a/Emby.Naming/TV/SeasonPathParserResult.cs b/Emby.Naming/TV/SeasonPathParserResult.cs
index 57c234754..a142fafea 100644
--- a/Emby.Naming/TV/SeasonPathParserResult.cs
+++ b/Emby.Naming/TV/SeasonPathParserResult.cs
@@ -1,5 +1,4 @@
#pragma warning disable CS1591
-#pragma warning disable SA1600
namespace Emby.Naming.TV
{
@@ -12,7 +11,7 @@ namespace Emby.Naming.TV
public int? SeasonNumber { get; set; }
/// <summary>
- /// Gets or sets a value indicating whether this <see cref="SeasonPathParserResult"/> is success.
+ /// Gets or sets a value indicating whether this <see cref="SeasonPathParserResult" /> is success.
/// </summary>
/// <value><c>true</c> if success; otherwise, <c>false</c>.</value>
public bool Success { get; set; }
diff --git a/Emby.Naming/Video/CleanDateTimeParser.cs b/Emby.Naming/Video/CleanDateTimeParser.cs
index a9db4cccc..579c9e91e 100644
--- a/Emby.Naming/Video/CleanDateTimeParser.cs
+++ b/Emby.Naming/Video/CleanDateTimeParser.cs
@@ -1,89 +1,47 @@
#pragma warning disable CS1591
-#pragma warning disable SA1600
+#nullable enable
-using System;
+using System.Collections.Generic;
using System.Globalization;
-using System.IO;
-using System.Linq;
using System.Text.RegularExpressions;
-using Emby.Naming.Common;
namespace Emby.Naming.Video
{
/// <summary>
/// <see href="http://kodi.wiki/view/Advancedsettings.xml#video" />.
/// </summary>
- public class CleanDateTimeParser
+ public static class CleanDateTimeParser
{
- private readonly NamingOptions _options;
-
- public CleanDateTimeParser(NamingOptions options)
+ public static CleanDateTimeResult Clean(string name, IReadOnlyList<Regex> cleanDateTimeRegexes)
{
- _options = options;
- }
-
- public CleanDateTimeResult Clean(string name)
- {
- var originalName = name;
-
- try
+ CleanDateTimeResult result = new CleanDateTimeResult(name);
+ var len = cleanDateTimeRegexes.Count;
+ for (int i = 0; i < len; i++)
{
- var extension = Path.GetExtension(name) ?? string.Empty;
- // Check supported extensions
- if (!_options.VideoFileExtensions.Contains(extension, StringComparer.OrdinalIgnoreCase)
- && !_options.AudioFileExtensions.Contains(extension, StringComparer.OrdinalIgnoreCase))
+ if (TryClean(name, cleanDateTimeRegexes[i], ref result))
{
- // Dummy up a file extension because the expressions will fail without one
- // This is tricky because we can't just check Path.GetExtension for empty
- // If the input is "St. Vincent (2014)", it will produce ". Vincent (2014)" as the extension
- name += ".mkv";
+ return result;
}
}
- catch (ArgumentException)
- {
- }
-
- var result = _options.CleanDateTimeRegexes.Select(i => Clean(name, i))
- .FirstOrDefault(i => i.HasChanged) ??
- new CleanDateTimeResult { Name = originalName };
-
- if (result.HasChanged)
- {
- return result;
- }
-
- // Make a second pass, running clean string first
- var cleanStringResult = new CleanStringParser().Clean(name, _options.CleanStringRegexes);
- if (!cleanStringResult.HasChanged)
- {
- return result;
- }
-
- return _options.CleanDateTimeRegexes.Select(i => Clean(cleanStringResult.Name, i))
- .FirstOrDefault(i => i.HasChanged) ??
- result;
+ return result;
}
- private static CleanDateTimeResult Clean(string name, Regex expression)
+ private static bool TryClean(string name, Regex expression, ref CleanDateTimeResult result)
{
- var result = new CleanDateTimeResult();
-
var match = expression.Match(name);
if (match.Success
- && match.Groups.Count == 4
+ && match.Groups.Count == 5
&& match.Groups[1].Success
&& match.Groups[2].Success
&& int.TryParse(match.Groups[2].Value, NumberStyles.Integer, CultureInfo.InvariantCulture, out var year))
{
- name = match.Groups[1].Value;
- result.Year = year;
- result.HasChanged = true;
+ result = new CleanDateTimeResult(match.Groups[1].Value.TrimEnd(), year);
+ return true;
}
- result.Name = name;
- return result;
+ return false;
}
}
}
diff --git a/Emby.Naming/Video/CleanDateTimeResult.cs b/Emby.Naming/Video/CleanDateTimeResult.cs
index a7581972e..57eeaa7e3 100644
--- a/Emby.Naming/Video/CleanDateTimeResult.cs
+++ b/Emby.Naming/Video/CleanDateTimeResult.cs
@@ -1,26 +1,32 @@
#pragma warning disable CS1591
-#pragma warning disable SA1600
+#nullable enable
namespace Emby.Naming.Video
{
- public class CleanDateTimeResult
+ public readonly struct CleanDateTimeResult
{
+ public CleanDateTimeResult(string name, int? year)
+ {
+ Name = name;
+ Year = year;
+ }
+
+ public CleanDateTimeResult(string name)
+ {
+ Name = name;
+ Year = null;
+ }
+
/// <summary>
- /// Gets or sets the name.
+ /// Gets the name.
/// </summary>
/// <value>The name.</value>
- public string Name { get; set; }
+ public string Name { get; }
/// <summary>
- /// Gets or sets the year.
+ /// Gets the year.
/// </summary>
/// <value>The year.</value>
- public int? Year { get; set; }
-
- /// <summary>
- /// Gets or sets a value indicating whether this instance has changed.
- /// </summary>
- /// <value><c>true</c> if this instance has changed; otherwise, <c>false</c>.</value>
- public bool HasChanged { get; set; }
+ public int? Year { get; }
}
}
diff --git a/Emby.Naming/Video/CleanStringParser.cs b/Emby.Naming/Video/CleanStringParser.cs
index fcd4b65c7..3f584d584 100644
--- a/Emby.Naming/Video/CleanStringParser.cs
+++ b/Emby.Naming/Video/CleanStringParser.cs
@@ -1,6 +1,7 @@
#pragma warning disable CS1591
-#pragma warning disable SA1600
+#nullable enable
+using System;
using System.Collections.Generic;
using System.Text.RegularExpressions;
@@ -9,44 +10,35 @@ namespace Emby.Naming.Video
/// <summary>
/// <see href="http://kodi.wiki/view/Advancedsettings.xml#video" />.
/// </summary>
- public class CleanStringParser
+ public static class CleanStringParser
{
- public CleanStringResult Clean(string name, IEnumerable<Regex> expressions)
+ public static bool TryClean(string name, IReadOnlyList<Regex> expressions, out ReadOnlySpan<char> newName)
{
- var hasChanged = false;
-
- foreach (var exp in expressions)
+ var len = expressions.Count;
+ for (int i = 0; i < len; i++)
{
- var result = Clean(name, exp);
-
- if (!string.IsNullOrEmpty(result.Name))
+ if (TryClean(name, expressions[i], out newName))
{
- name = result.Name;
- hasChanged = hasChanged || result.HasChanged;
+ return true;
}
}
- return new CleanStringResult
- {
- Name = name,
- HasChanged = hasChanged
- };
+ newName = ReadOnlySpan<char>.Empty;
+ return false;
}
- private static CleanStringResult Clean(string name, Regex expression)
+ private static bool TryClean(string name, Regex expression, out ReadOnlySpan<char> newName)
{
- var result = new CleanStringResult();
-
var match = expression.Match(name);
-
- if (match.Success)
+ int index = match.Index;
+ if (match.Success && index != 0)
{
- result.HasChanged = true;
- name = name.Substring(0, match.Index);
+ newName = name.AsSpan().Slice(0, match.Index);
+ return true;
}
- result.Name = name;
- return result;
+ newName = string.Empty;
+ return false;
}
}
}
diff --git a/Emby.Naming/Video/CleanStringResult.cs b/Emby.Naming/Video/CleanStringResult.cs
deleted file mode 100644
index 786fe9e02..000000000
--- a/Emby.Naming/Video/CleanStringResult.cs
+++ /dev/null
@@ -1,20 +0,0 @@
-#pragma warning disable CS1591
-#pragma warning disable SA1600
-
-namespace Emby.Naming.Video
-{
- public class CleanStringResult
- {
- /// <summary>
- /// Gets or sets the name.
- /// </summary>
- /// <value>The name.</value>
- public string Name { get; set; }
-
- /// <summary>
- /// Gets or sets a value indicating whether this instance has changed.
- /// </summary>
- /// <value><c>true</c> if this instance has changed; otherwise, <c>false</c>.</value>
- public bool HasChanged { get; set; }
- }
-}
diff --git a/Emby.Naming/Video/ExtraResolver.cs b/Emby.Naming/Video/ExtraResolver.cs
index ea9a6d6c2..fc0424faa 100644
--- a/Emby.Naming/Video/ExtraResolver.cs
+++ b/Emby.Naming/Video/ExtraResolver.cs
@@ -1,5 +1,4 @@
#pragma warning disable CS1591
-#pragma warning disable SA1600
using System;
using System.IO;
@@ -32,7 +31,7 @@ namespace Emby.Naming.Video
if (rule.MediaType == MediaType.Audio)
{
- if (!new AudioFileParser(_options).IsAudioFile(path))
+ if (!AudioFileParser.IsAudioFile(path, _options))
{
return result;
}
@@ -81,6 +80,15 @@ namespace Emby.Naming.Video
result.Rule = rule;
}
}
+ else if (rule.RuleType == ExtraRuleType.DirectoryName)
+ {
+ var directoryName = Path.GetFileName(Path.GetDirectoryName(path));
+ if (string.Equals(directoryName, rule.Token, StringComparison.OrdinalIgnoreCase))
+ {
+ result.ExtraType = rule.ExtraType;
+ result.Rule = rule;
+ }
+ }
return result;
}
diff --git a/Emby.Naming/Video/ExtraResult.cs b/Emby.Naming/Video/ExtraResult.cs
index 4e991d685..15db32e87 100644
--- a/Emby.Naming/Video/ExtraResult.cs
+++ b/Emby.Naming/Video/ExtraResult.cs
@@ -1,5 +1,4 @@
#pragma warning disable CS1591
-#pragma warning disable SA1600
using MediaBrowser.Model.Entities;
diff --git a/Emby.Naming/Video/ExtraRule.cs b/Emby.Naming/Video/ExtraRule.cs
index cfaa84ed6..7c9702e24 100644
--- a/Emby.Naming/Video/ExtraRule.cs
+++ b/Emby.Naming/Video/ExtraRule.cs
@@ -1,35 +1,33 @@
#pragma warning disable CS1591
-#pragma warning disable SA1600
using MediaBrowser.Model.Entities;
using MediaType = Emby.Naming.Common.MediaType;
namespace Emby.Naming.Video
{
+ /// <summary>
+ /// A rule used to match a file path with an <see cref="MediaBrowser.Model.Entities.ExtraType"/>.
+ /// </summary>
public class ExtraRule
{
/// <summary>
- /// Gets or sets the token.
+ /// Gets or sets the token to use for matching against the file path.
/// </summary>
- /// <value>The token.</value>
public string Token { get; set; }
/// <summary>
- /// Gets or sets the type of the extra.
+ /// Gets or sets the type of the extra to return when matched.
/// </summary>
- /// <value>The type of the extra.</value>
public ExtraType ExtraType { get; set; }
/// <summary>
/// Gets or sets the type of the rule.
/// </summary>
- /// <value>The type of the rule.</value>
public ExtraRuleType RuleType { get; set; }
/// <summary>
- /// Gets or sets the type of the media.
+ /// Gets or sets the type of the media to return when matched.
/// </summary>
- /// <value>The type of the media.</value>
public MediaType MediaType { get; set; }
}
}
diff --git a/Emby.Naming/Video/ExtraRuleType.cs b/Emby.Naming/Video/ExtraRuleType.cs
index 2bf2799ff..e89876f4a 100644
--- a/Emby.Naming/Video/ExtraRuleType.cs
+++ b/Emby.Naming/Video/ExtraRuleType.cs
@@ -1,23 +1,27 @@
#pragma warning disable CS1591
-#pragma warning disable SA1600
namespace Emby.Naming.Video
{
public enum ExtraRuleType
{
/// <summary>
- /// The suffix
+ /// Match <see cref="ExtraRule.Token"/> against a suffix in the file name.
/// </summary>
Suffix = 0,
/// <summary>
- /// The filename
+ /// Match <see cref="ExtraRule.Token"/> against the file name, excluding the file extension.
/// </summary>
Filename = 1,
/// <summary>
- /// The regex
+ /// Match <see cref="ExtraRule.Token"/> against the file name, including the file extension.
/// </summary>
- Regex = 2
+ Regex = 2,
+
+ /// <summary>
+ /// Match <see cref="ExtraRule.Token"/> against the name of the directory containing the file.
+ /// </summary>
+ DirectoryName = 3,
}
}
diff --git a/Emby.Naming/Video/FileStack.cs b/Emby.Naming/Video/FileStack.cs
index 56adf6add..3ef190b86 100644
--- a/Emby.Naming/Video/FileStack.cs
+++ b/Emby.Naming/Video/FileStack.cs
@@ -1,5 +1,4 @@
#pragma warning disable CS1591
-#pragma warning disable SA1600
using System;
using System.Collections.Generic;
diff --git a/Emby.Naming/Video/FlagParser.cs b/Emby.Naming/Video/FlagParser.cs
index acf3438c2..a8bd9d5c5 100644
--- a/Emby.Naming/Video/FlagParser.cs
+++ b/Emby.Naming/Video/FlagParser.cs
@@ -1,5 +1,4 @@
#pragma warning disable CS1591
-#pragma warning disable SA1600
using System;
using System.IO;
diff --git a/Emby.Naming/Video/Format3DParser.cs b/Emby.Naming/Video/Format3DParser.cs
index 25905f33c..51c26af86 100644
--- a/Emby.Naming/Video/Format3DParser.cs
+++ b/Emby.Naming/Video/Format3DParser.cs
@@ -1,5 +1,4 @@
#pragma warning disable CS1591
-#pragma warning disable SA1600
using System;
using System.Linq;
diff --git a/Emby.Naming/Video/Format3DResult.cs b/Emby.Naming/Video/Format3DResult.cs
index 6ebd72f6b..fa0e9d3b8 100644
--- a/Emby.Naming/Video/Format3DResult.cs
+++ b/Emby.Naming/Video/Format3DResult.cs
@@ -1,5 +1,4 @@
#pragma warning disable CS1591
-#pragma warning disable SA1600
using System.Collections.Generic;
diff --git a/Emby.Naming/Video/Format3DRule.cs b/Emby.Naming/Video/Format3DRule.cs
index ae9fb5b19..310ec84e8 100644
--- a/Emby.Naming/Video/Format3DRule.cs
+++ b/Emby.Naming/Video/Format3DRule.cs
@@ -1,5 +1,4 @@
#pragma warning disable CS1591
-#pragma warning disable SA1600
namespace Emby.Naming.Video
{
diff --git a/Emby.Naming/Video/StackResolver.cs b/Emby.Naming/Video/StackResolver.cs
index e7a769ae6..f733cd262 100644
--- a/Emby.Naming/Video/StackResolver.cs
+++ b/Emby.Naming/Video/StackResolver.cs
@@ -1,5 +1,4 @@
#pragma warning disable CS1591
-#pragma warning disable SA1600
using System;
using System.Collections.Generic;
@@ -20,34 +19,26 @@ namespace Emby.Naming.Video
_options = options;
}
- public StackResult ResolveDirectories(IEnumerable<string> files)
+ public IEnumerable<FileStack> ResolveDirectories(IEnumerable<string> files)
{
- return Resolve(files.Select(i => new FileSystemMetadata
- {
- FullName = i,
- IsDirectory = true
- }));
+ return Resolve(files.Select(i => new FileSystemMetadata { FullName = i, IsDirectory = true }));
}
- public StackResult ResolveFiles(IEnumerable<string> files)
+ public IEnumerable<FileStack> ResolveFiles(IEnumerable<string> files)
{
- return Resolve(files.Select(i => new FileSystemMetadata
- {
- FullName = i,
- IsDirectory = false
- }));
+ return Resolve(files.Select(i => new FileSystemMetadata { FullName = i, IsDirectory = false }));
}
- public StackResult ResolveAudioBooks(IEnumerable<FileSystemMetadata> files)
+ public IEnumerable<FileStack> ResolveAudioBooks(IEnumerable<FileSystemMetadata> files)
{
- var result = new StackResult();
- foreach (var directory in files.GroupBy(file => file.IsDirectory ? file.FullName : Path.GetDirectoryName(file.FullName)))
+ var groupedDirectoryFiles = files.GroupBy(file =>
+ file.IsDirectory
+ ? file.FullName
+ : Path.GetDirectoryName(file.FullName));
+
+ foreach (var directory in groupedDirectoryFiles)
{
- var stack = new FileStack()
- {
- Name = Path.GetFileName(directory.Key),
- IsDirectoryStack = false
- };
+ var stack = new FileStack { Name = Path.GetFileName(directory.Key), IsDirectoryStack = false };
foreach (var file in directory)
{
if (file.IsDirectory)
@@ -58,20 +49,16 @@ namespace Emby.Naming.Video
stack.Files.Add(file.FullName);
}
- result.Stacks.Add(stack);
+ yield return stack;
}
-
- return result;
}
- public StackResult Resolve(IEnumerable<FileSystemMetadata> files)
+ public IEnumerable<FileStack> Resolve(IEnumerable<FileSystemMetadata> files)
{
- var result = new StackResult();
-
var resolver = new VideoResolver(_options);
var list = files
- .Where(i => i.IsDirectory || (resolver.IsVideoFile(i.FullName) || resolver.IsStubFile(i.FullName)))
+ .Where(i => i.IsDirectory || resolver.IsVideoFile(i.FullName) || resolver.IsStubFile(i.FullName))
.OrderBy(i => i.FullName)
.ToList();
@@ -191,17 +178,15 @@ namespace Emby.Naming.Video
if (stack.Files.Count > 1)
{
- result.Stacks.Add(stack);
+ yield return stack;
i += stack.Files.Count - 1;
break;
}
}
}
-
- return result;
}
- private string GetRegexInput(FileSystemMetadata file)
+ private static string GetRegexInput(FileSystemMetadata file)
{
// For directories, dummy up an extension otherwise the expressions will fail
var input = !file.IsDirectory
@@ -211,7 +196,7 @@ namespace Emby.Naming.Video
return Path.GetFileName(input);
}
- private Match FindMatch(FileSystemMetadata input, Regex regex, int offset)
+ private static Match FindMatch(FileSystemMetadata input, Regex regex, int offset)
{
var regexInput = GetRegexInput(input);
diff --git a/Emby.Naming/Video/StackResult.cs b/Emby.Naming/Video/StackResult.cs
deleted file mode 100644
index 31ef2d69c..000000000
--- a/Emby.Naming/Video/StackResult.cs
+++ /dev/null
@@ -1,17 +0,0 @@
-#pragma warning disable CS1591
-#pragma warning disable SA1600
-
-using System.Collections.Generic;
-
-namespace Emby.Naming.Video
-{
- public class StackResult
- {
- public List<FileStack> Stacks { get; set; }
-
- public StackResult()
- {
- Stacks = new List<FileStack>();
- }
- }
-}
diff --git a/Emby.Naming/Video/StubResolver.cs b/Emby.Naming/Video/StubResolver.cs
index 95868e89d..f1b5d7bcc 100644
--- a/Emby.Naming/Video/StubResolver.cs
+++ b/Emby.Naming/Video/StubResolver.cs
@@ -1,5 +1,5 @@
#pragma warning disable CS1591
-#pragma warning disable SA1600
+#nullable enable
using System;
using System.IO;
@@ -10,25 +10,22 @@ namespace Emby.Naming.Video
{
public static class StubResolver
{
- public static StubResult ResolveFile(string path, NamingOptions options)
+ public static bool TryResolveFile(string path, NamingOptions options, out string? stubType)
{
+ stubType = default;
+
if (path == null)
{
- return default;
+ return false;
}
var extension = Path.GetExtension(path);
if (!options.StubFileExtensions.Contains(extension, StringComparer.OrdinalIgnoreCase))
{
- return default;
+ return false;
}
- var result = new StubResult()
- {
- IsStub = true
- };
-
path = Path.GetFileNameWithoutExtension(path);
var token = Path.GetExtension(path).TrimStart('.');
@@ -36,12 +33,12 @@ namespace Emby.Naming.Video
{
if (string.Equals(rule.Token, token, StringComparison.OrdinalIgnoreCase))
{
- result.StubType = rule.StubType;
- break;
+ stubType = rule.StubType;
+ return true;
}
}
- return result;
+ return true;
}
}
}
diff --git a/Emby.Naming/Video/StubResult.cs b/Emby.Naming/Video/StubResult.cs
index 5ac85528f..1b8e99b0d 100644
--- a/Emby.Naming/Video/StubResult.cs
+++ b/Emby.Naming/Video/StubResult.cs
@@ -1,5 +1,4 @@
#pragma warning disable CS1591
-#pragma warning disable SA1600
namespace Emby.Naming.Video
{
diff --git a/Emby.Naming/Video/StubTypeRule.cs b/Emby.Naming/Video/StubTypeRule.cs
index 17c3ef8c5..8285cb51a 100644
--- a/Emby.Naming/Video/StubTypeRule.cs
+++ b/Emby.Naming/Video/StubTypeRule.cs
@@ -1,5 +1,4 @@
#pragma warning disable CS1591
-#pragma warning disable SA1600
namespace Emby.Naming.Video
{
diff --git a/Emby.Naming/Video/VideoFileInfo.cs b/Emby.Naming/Video/VideoFileInfo.cs
index 90c798da1..11e789b66 100644
--- a/Emby.Naming/Video/VideoFileInfo.cs
+++ b/Emby.Naming/Video/VideoFileInfo.cs
@@ -68,7 +68,7 @@ namespace Emby.Naming.Video
public string StubType { get; set; }
/// <summary>
- /// Gets or sets the type.
+ /// Gets or sets a value indicating whether this instance is a directory.
/// </summary>
/// <value>The type.</value>
public bool IsDirectory { get; set; }
@@ -77,7 +77,9 @@ namespace Emby.Naming.Video
/// Gets the file name without extension.
/// </summary>
/// <value>The file name without extension.</value>
- public string FileNameWithoutExtension => !IsDirectory ? System.IO.Path.GetFileNameWithoutExtension(Path) : System.IO.Path.GetFileName(Path);
+ public string FileNameWithoutExtension => !IsDirectory
+ ? System.IO.Path.GetFileNameWithoutExtension(Path)
+ : System.IO.Path.GetFileName(Path);
/// <inheritdoc />
public override string ToString()
diff --git a/Emby.Naming/Video/VideoInfo.cs b/Emby.Naming/Video/VideoInfo.cs
index a585bb99a..ea74c40e2 100644
--- a/Emby.Naming/Video/VideoInfo.cs
+++ b/Emby.Naming/Video/VideoInfo.cs
@@ -1,3 +1,4 @@
+using System;
using System.Collections.Generic;
namespace Emby.Naming.Video
@@ -10,11 +11,14 @@ namespace Emby.Naming.Video
/// <summary>
/// Initializes a new instance of the <see cref="VideoInfo" /> class.
/// </summary>
- public VideoInfo()
+ /// <param name="name">The name.</param>
+ public VideoInfo(string name)
{
- Files = new List<VideoFileInfo>();
- Extras = new List<VideoFileInfo>();
- AlternateVersions = new List<VideoFileInfo>();
+ Name = name;
+
+ Files = Array.Empty<VideoFileInfo>();
+ Extras = Array.Empty<VideoFileInfo>();
+ AlternateVersions = Array.Empty<VideoFileInfo>();
}
/// <summary>
@@ -33,18 +37,18 @@ namespace Emby.Naming.Video
/// Gets or sets the files.
/// </summary>
/// <value>The files.</value>
- public List<VideoFileInfo> Files { get; set; }
+ public IReadOnlyList<VideoFileInfo> Files { get; set; }
/// <summary>
/// Gets or sets the extras.
/// </summary>
/// <value>The extras.</value>
- public List<VideoFileInfo> Extras { get; set; }
+ public IReadOnlyList<VideoFileInfo> Extras { get; set; }
/// <summary>
/// Gets or sets the alternate versions.
/// </summary>
/// <value>The alternate versions.</value>
- public List<VideoFileInfo> AlternateVersions { get; set; }
+ public IReadOnlyList<VideoFileInfo> AlternateVersions { get; set; }
}
}
diff --git a/Emby.Naming/Video/VideoListResolver.cs b/Emby.Naming/Video/VideoListResolver.cs
index 87498000c..7f755fd25 100644
--- a/Emby.Naming/Video/VideoListResolver.cs
+++ b/Emby.Naming/Video/VideoListResolver.cs
@@ -1,5 +1,4 @@
#pragma warning disable CS1591
-#pragma warning disable SA1600
using System;
using System.Collections.Generic;
@@ -34,36 +33,27 @@ namespace Emby.Naming.Video
// See the unit test TestStackedWithTrailer
var nonExtras = videoInfos
.Where(i => i.ExtraType == null)
- .Select(i => new FileSystemMetadata
- {
- FullName = i.Path,
- IsDirectory = i.IsDirectory
- });
+ .Select(i => new FileSystemMetadata { FullName = i.Path, IsDirectory = i.IsDirectory });
var stackResult = new StackResolver(_options)
- .Resolve(nonExtras);
+ .Resolve(nonExtras).ToList();
var remainingFiles = videoInfos
- .Where(i => !stackResult.Stacks.Any(s => s.ContainsFile(i.Path, i.IsDirectory)))
+ .Where(i => !stackResult.Any(s => s.ContainsFile(i.Path, i.IsDirectory)))
.ToList();
var list = new List<VideoInfo>();
- foreach (var stack in stackResult.Stacks)
+ foreach (var stack in stackResult)
{
- var info = new VideoInfo
+ var info = new VideoInfo(stack.Name)
{
- Files = stack.Files.Select(i => videoResolver.Resolve(i, stack.IsDirectoryStack)).ToList(),
- Name = stack.Name
+ Files = stack.Files.Select(i => videoResolver.Resolve(i, stack.IsDirectoryStack)).ToList()
};
info.Year = info.Files[0].Year;
- var extraBaseNames = new List<string>
- {
- stack.Name,
- Path.GetFileNameWithoutExtension(stack.Files[0])
- };
+ var extraBaseNames = new List<string> { stack.Name, Path.GetFileNameWithoutExtension(stack.Files[0]) };
var extras = GetExtras(remainingFiles, extraBaseNames);
@@ -85,11 +75,7 @@ namespace Emby.Naming.Video
foreach (var media in standaloneMedia)
{
- var info = new VideoInfo
- {
- Files = new List<VideoFileInfo> { media },
- Name = media.Name
- };
+ var info = new VideoInfo(media.Name) { Files = new List<VideoFileInfo> { media } };
info.Year = info.Files[0].Year;
@@ -128,7 +114,8 @@ namespace Emby.Naming.Video
.Except(extras)
.ToList();
- info.Extras.AddRange(extras);
+ extras.AddRange(info.Extras);
+ info.Extras = extras;
}
}
@@ -141,7 +128,8 @@ namespace Emby.Naming.Video
.Except(extrasByFileName)
.ToList();
- info.Extras.AddRange(extrasByFileName);
+ extrasByFileName.AddRange(info.Extras);
+ info.Extras = extrasByFileName;
}
// If there's only one video, accept all trailers
@@ -152,7 +140,8 @@ namespace Emby.Naming.Video
.Where(i => i.ExtraType == ExtraType.Trailer)
.ToList();
- list[0].Extras.AddRange(trailers);
+ trailers.AddRange(list[0].Extras);
+ list[0].Extras = trailers;
remainingFiles = remainingFiles
.Except(trailers)
@@ -160,14 +149,13 @@ namespace Emby.Naming.Video
}
// Whatever files are left, just add them
- list.AddRange(remainingFiles.Select(i => new VideoInfo
+ list.AddRange(remainingFiles.Select(i => new VideoInfo(i.Name)
{
Files = new List<VideoFileInfo> { i },
- Name = i.Name,
Year = i.Year
}));
- return list.OrderBy(i => i.Name);
+ return list;
}
private IEnumerable<VideoInfo> GetVideosGroupedByVersion(List<VideoInfo> videos)
@@ -191,9 +179,18 @@ namespace Emby.Naming.Video
list.Add(ordered[0]);
- list[0].AlternateVersions = ordered.Skip(1).Select(i => i.Files[0]).ToList();
+ var alternateVersionsLen = ordered.Count - 1;
+ var alternateVersions = new VideoFileInfo[alternateVersionsLen];
+ for (int i = 0; i < alternateVersionsLen; i++)
+ {
+ alternateVersions[i] = ordered[i + 1].Files[0];
+ }
+
+ list[0].AlternateVersions = alternateVersions;
list[0].Name = folderName;
- list[0].Extras.AddRange(ordered.Skip(1).SelectMany(i => i.Extras));
+ var extras = ordered.Skip(1).SelectMany(i => i.Extras).ToList();
+ extras.AddRange(list[0].Extras);
+ list[0].Extras = extras;
return list;
}
@@ -214,8 +211,8 @@ namespace Emby.Naming.Video
{
testFilename = testFilename.Substring(folderName.Length).Trim();
return string.IsNullOrEmpty(testFilename)
- || testFilename[0] == '-'
- || string.IsNullOrWhiteSpace(Regex.Replace(testFilename, @"\[([^]]*)\]", string.Empty));
+ || testFilename[0] == '-'
+ || string.IsNullOrWhiteSpace(Regex.Replace(testFilename, @"\[([^]]*)\]", string.Empty));
}
return false;
@@ -231,7 +228,8 @@ namespace Emby.Naming.Video
return remainingFiles
.Where(i => i.ExtraType == null)
- .Where(i => baseNames.Any(b => i.FileNameWithoutExtension.StartsWith(b, StringComparison.OrdinalIgnoreCase)))
+ .Where(i => baseNames.Any(b =>
+ i.FileNameWithoutExtension.StartsWith(b, StringComparison.OrdinalIgnoreCase)))
.ToList();
}
}
diff --git a/Emby.Naming/Video/VideoResolver.cs b/Emby.Naming/Video/VideoResolver.cs
index 41b79697c..0b75a8cce 100644
--- a/Emby.Naming/Video/VideoResolver.cs
+++ b/Emby.Naming/Video/VideoResolver.cs
@@ -1,5 +1,5 @@
#pragma warning disable CS1591
-#pragma warning disable SA1600
+#nullable enable
using System;
using System.IO;
@@ -22,7 +22,7 @@ namespace Emby.Naming.Video
/// </summary>
/// <param name="path">The path.</param>
/// <returns>VideoFileInfo.</returns>
- public VideoFileInfo ResolveDirectory(string path)
+ public VideoFileInfo? ResolveDirectory(string path)
{
return Resolve(path, true);
}
@@ -32,7 +32,7 @@ namespace Emby.Naming.Video
/// </summary>
/// <param name="path">The path.</param>
/// <returns>VideoFileInfo.</returns>
- public VideoFileInfo ResolveFile(string path)
+ public VideoFileInfo? ResolveFile(string path)
{
return Resolve(path, false);
}
@@ -42,10 +42,10 @@ namespace Emby.Naming.Video
/// </summary>
/// <param name="path">The path.</param>
/// <param name="isDirectory">if set to <c>true</c> [is folder].</param>
- /// <param name="parseName">Whether or not the name should be parsed for info</param>
+ /// <param name="parseName">Whether or not the name should be parsed for info.</param>
/// <returns>VideoFileInfo.</returns>
/// <exception cref="ArgumentNullException"><c>path</c> is <c>null</c>.</exception>
- public VideoFileInfo Resolve(string path, bool isDirectory, bool parseName = true)
+ public VideoFileInfo? Resolve(string path, bool isDirectory, bool parseName = true)
{
if (string.IsNullOrEmpty(path))
{
@@ -53,8 +53,8 @@ namespace Emby.Naming.Video
}
bool isStub = false;
- string container = null;
- string stubType = null;
+ string? container = null;
+ string? stubType = null;
if (!isDirectory)
{
@@ -63,17 +63,13 @@ namespace Emby.Naming.Video
// Check supported extensions
if (!_options.VideoFileExtensions.Contains(extension, StringComparer.OrdinalIgnoreCase))
{
- var stubResult = StubResolver.ResolveFile(path, _options);
-
- isStub = stubResult.IsStub;
-
// It's not supported. Check stub extensions
- if (!isStub)
+ if (!StubResolver.TryResolveFile(path, _options, out stubType))
{
return null;
}
- stubType = stubResult.StubType;
+ isStub = true;
}
container = extension.TrimStart('.');
@@ -94,9 +90,10 @@ namespace Emby.Naming.Video
{
var cleanDateTimeResult = CleanDateTime(name);
- if (extraResult.ExtraType == null)
+ if (extraResult.ExtraType == null
+ && TryCleanString(cleanDateTimeResult.Name, out ReadOnlySpan<char> newName))
{
- name = CleanString(cleanDateTimeResult.Name).Name;
+ name = newName.ToString();
}
year = cleanDateTimeResult.Year;
@@ -130,14 +127,14 @@ namespace Emby.Naming.Video
return _options.StubFileExtensions.Contains(extension, StringComparer.OrdinalIgnoreCase);
}
- public CleanStringResult CleanString(string name)
+ public bool TryCleanString(string name, out ReadOnlySpan<char> newName)
{
- return new CleanStringParser().Clean(name, _options.CleanStringRegexes);
+ return CleanStringParser.TryClean(name, _options.CleanStringRegexes, out newName);
}
public CleanDateTimeResult CleanDateTime(string name)
{
- return new CleanDateTimeParser(_options).Clean(name);
+ return CleanDateTimeParser.Clean(name, _options.CleanDateTimeRegexes);
}
}
}
diff --git a/Emby.Notifications/Api/NotificationsService.cs b/Emby.Notifications/Api/NotificationsService.cs
index 83845558a..788750796 100644
--- a/Emby.Notifications/Api/NotificationsService.cs
+++ b/Emby.Notifications/Api/NotificationsService.cs
@@ -1,5 +1,10 @@
+#pragma warning disable CS1591
+#pragma warning disable SA1402
+#pragma warning disable SA1649
+
using System;
using System.Collections.Generic;
+using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
@@ -16,7 +21,7 @@ namespace Emby.Notifications.Api
public class GetNotifications : IReturn<NotificationResult>
{
[ApiMember(Name = "UserId", Description = "User Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")]
- public string UserId { get; set; }
+ public string UserId { get; set; } = string.Empty;
[ApiMember(Name = "IsRead", Description = "An optional filter by IsRead", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")]
public bool? IsRead { get; set; }
@@ -30,32 +35,34 @@ namespace Emby.Notifications.Api
public class Notification
{
- public string Id { get; set; }
+ public string Id { get; set; } = string.Empty;
- public string UserId { get; set; }
+ public string UserId { get; set; } = string.Empty;
public DateTime Date { get; set; }
public bool IsRead { get; set; }
- public string Name { get; set; }
+ public string Name { get; set; } = string.Empty;
- public string Description { get; set; }
+ public string Description { get; set; } = string.Empty;
- public string Url { get; set; }
+ public string Url { get; set; } = string.Empty;
public NotificationLevel Level { get; set; }
}
public class NotificationResult
{
- public Notification[] Notifications { get; set; }
+ public IReadOnlyList<Notification> Notifications { get; set; } = Array.Empty<Notification>();
+
public int TotalRecordCount { get; set; }
}
public class NotificationsSummary
{
public int UnreadCount { get; set; }
+
public NotificationLevel MaxUnreadNotificationLevel { get; set; }
}
@@ -63,7 +70,7 @@ namespace Emby.Notifications.Api
public class GetNotificationsSummary : IReturn<NotificationsSummary>
{
[ApiMember(Name = "UserId", Description = "User Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")]
- public string UserId { get; set; }
+ public string UserId { get; set; } = string.Empty;
}
[Route("/Notifications/Types", "GET", Summary = "Gets notification types")]
@@ -80,16 +87,16 @@ namespace Emby.Notifications.Api
public class AddAdminNotification : IReturnVoid
{
[ApiMember(Name = "Name", Description = "The notification's name", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "POST")]
- public string Name { get; set; }
+ public string Name { get; set; } = string.Empty;
[ApiMember(Name = "Description", Description = "The notification's description", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "POST")]
- public string Description { get; set; }
+ public string Description { get; set; } = string.Empty;
[ApiMember(Name = "ImageUrl", Description = "The notification's image url", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "POST")]
- public string ImageUrl { get; set; }
+ public string? ImageUrl { get; set; }
[ApiMember(Name = "Url", Description = "The notification's info url", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "POST")]
- public string Url { get; set; }
+ public string? Url { get; set; }
[ApiMember(Name = "Level", Description = "The notification level", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "POST")]
public NotificationLevel Level { get; set; }
@@ -99,20 +106,20 @@ namespace Emby.Notifications.Api
public class MarkRead : IReturnVoid
{
[ApiMember(Name = "UserId", Description = "User Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")]
- public string UserId { get; set; }
+ public string UserId { get; set; } = string.Empty;
[ApiMember(Name = "Ids", Description = "A list of notification ids, comma delimited", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "POST", AllowMultiple = true)]
- public string Ids { get; set; }
+ public string Ids { get; set; } = string.Empty;
}
[Route("/Notifications/{UserId}/Unread", "POST", Summary = "Marks notifications as unread")]
public class MarkUnread : IReturnVoid
{
[ApiMember(Name = "UserId", Description = "User Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")]
- public string UserId { get; set; }
+ public string UserId { get; set; } = string.Empty;
[ApiMember(Name = "Ids", Description = "A list of notification ids, comma delimited", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "POST", AllowMultiple = true)]
- public string Ids { get; set; }
+ public string Ids { get; set; } = string.Empty;
}
[Authenticated]
@@ -127,32 +134,29 @@ namespace Emby.Notifications.Api
_userManager = userManager;
}
+ [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "request", Justification = "Required for ServiceStack")]
public object Get(GetNotificationTypes request)
{
return _notificationManager.GetNotificationTypes();
}
+ [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "request", Justification = "Required for ServiceStack")]
public object Get(GetNotificationServices request)
{
return _notificationManager.GetNotificationServices().ToList();
}
+ [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "request", Justification = "Required for ServiceStack")]
public object Get(GetNotificationsSummary request)
{
return new NotificationsSummary
{
-
};
}
public Task Post(AddAdminNotification request)
{
// This endpoint really just exists as post of a real with sickbeard
- return AddNotification(request);
- }
-
- private Task AddNotification(AddAdminNotification request)
- {
var notification = new NotificationRequest
{
Date = DateTime.UtcNow,
@@ -166,14 +170,17 @@ namespace Emby.Notifications.Api
return _notificationManager.SendNotification(notification, CancellationToken.None);
}
+ [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "request", Justification = "Required for ServiceStack")]
public void Post(MarkRead request)
{
}
+ [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "request", Justification = "Required for ServiceStack")]
public void Post(MarkUnread request)
{
}
+ [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "request", Justification = "Required for ServiceStack")]
public object Get(GetNotifications request)
{
return new NotificationResult();
diff --git a/Emby.Notifications/CoreNotificationTypes.cs b/Emby.Notifications/CoreNotificationTypes.cs
index 0f9fc08d9..a602b7221 100644
--- a/Emby.Notifications/CoreNotificationTypes.cs
+++ b/Emby.Notifications/CoreNotificationTypes.cs
@@ -1,7 +1,8 @@
+#pragma warning disable CS1591
+
using System;
using System.Collections.Generic;
using System.Linq;
-using MediaBrowser.Controller;
using MediaBrowser.Controller.Notifications;
using MediaBrowser.Model.Globalization;
using MediaBrowser.Model.Notifications;
diff --git a/Emby.Notifications/Emby.Notifications.csproj b/Emby.Notifications/Emby.Notifications.csproj
index 004ded77b..1d430a5e5 100644
--- a/Emby.Notifications/Emby.Notifications.csproj
+++ b/Emby.Notifications/Emby.Notifications.csproj
@@ -1,9 +1,16 @@
<Project Sdk="Microsoft.NET.Sdk">
+ <!-- ProjectGuid is only included as a requirement for SonarQube analysis -->
+ <PropertyGroup>
+ <ProjectGuid>{2E030C33-6923-4530-9E54-FA29FA6AD1A9}</ProjectGuid>
+ </PropertyGroup>
+
<PropertyGroup>
<TargetFramework>netstandard2.1</TargetFramework>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
+ <TreatWarningsAsErrors>true</TreatWarningsAsErrors>
+ <Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
@@ -16,4 +23,16 @@
<ProjectReference Include="..\MediaBrowser.Common\MediaBrowser.Common.csproj" />
</ItemGroup>
+ <!-- Code analyzers-->
+ <ItemGroup Condition=" '$(Configuration)' == 'Debug' ">
+ <PackageReference Include="Microsoft.CodeAnalysis.FxCopAnalyzers" Version="2.9.8" PrivateAssets="All" />
+ <PackageReference Include="SerilogAnalyzer" Version="0.15.0" PrivateAssets="All" />
+ <PackageReference Include="StyleCop.Analyzers" Version="1.1.118" PrivateAssets="All" />
+ <PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" PrivateAssets="All" />
+ </ItemGroup>
+
+ <PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
+ <CodeAnalysisRuleSet>../jellyfin.ruleset</CodeAnalysisRuleSet>
+ </PropertyGroup>
+
</Project>
diff --git a/Emby.Notifications/NotificationConfigurationFactory.cs b/Emby.Notifications/NotificationConfigurationFactory.cs
index d08475f7d..3fb3553d0 100644
--- a/Emby.Notifications/NotificationConfigurationFactory.cs
+++ b/Emby.Notifications/NotificationConfigurationFactory.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
using System.Collections.Generic;
using MediaBrowser.Common.Configuration;
using MediaBrowser.Model.Notifications;
@@ -13,7 +15,7 @@ namespace Emby.Notifications
new ConfigurationStore
{
Key = "notifications",
- ConfigurationType = typeof (NotificationOptions)
+ ConfigurationType = typeof(NotificationOptions)
}
};
}
diff --git a/Emby.Notifications/Notifications.cs b/Emby.Notifications/NotificationEntryPoint.cs
index 7aa1e7ae8..869b7407e 100644
--- a/Emby.Notifications/Notifications.cs
+++ b/Emby.Notifications/NotificationEntryPoint.cs
@@ -21,70 +21,85 @@ using Microsoft.Extensions.Logging;
namespace Emby.Notifications
{
/// <summary>
- /// Creates notifications for various system events
+ /// Creates notifications for various system events.
/// </summary>
- public class Notifications : IServerEntryPoint
+ public class NotificationEntryPoint : IServerEntryPoint
{
private readonly ILogger _logger;
-
+ private readonly IActivityManager _activityManager;
+ private readonly ILocalizationManager _localization;
private readonly INotificationManager _notificationManager;
-
private readonly ILibraryManager _libraryManager;
private readonly IServerApplicationHost _appHost;
+ private readonly IConfigurationManager _config;
- private Timer LibraryUpdateTimer { get; set; }
private readonly object _libraryChangedSyncLock = new object();
+ private readonly List<BaseItem> _itemsAdded = new List<BaseItem>();
- private readonly IConfigurationManager _config;
- private readonly ILocalizationManager _localization;
- private readonly IActivityManager _activityManager;
+ private Timer? _libraryUpdateTimer;
private string[] _coreNotificationTypes;
- public Notifications(
+ private bool _disposed = false;
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="NotificationEntryPoint" /> class.
+ /// </summary>
+ /// <param name="logger">The logger.</param>
+ /// <param name="activityManager">The activity manager.</param>
+ /// <param name="localization">The localization manager.</param>
+ /// <param name="notificationManager">The notification manager.</param>
+ /// <param name="libraryManager">The library manager.</param>
+ /// <param name="appHost">The application host.</param>
+ /// <param name="config">The configuration manager.</param>
+ public NotificationEntryPoint(
+ ILogger<NotificationEntryPoint> logger,
IActivityManager activityManager,
ILocalizationManager localization,
- ILogger logger,
INotificationManager notificationManager,
ILibraryManager libraryManager,
IServerApplicationHost appHost,
IConfigurationManager config)
{
_logger = logger;
+ _activityManager = activityManager;
+ _localization = localization;
_notificationManager = notificationManager;
_libraryManager = libraryManager;
_appHost = appHost;
_config = config;
- _localization = localization;
- _activityManager = activityManager;
_coreNotificationTypes = new CoreNotificationTypes(localization).GetNotificationTypes().Select(i => i.Type).ToArray();
}
+ /// <inheritdoc />
public Task RunAsync()
{
- _libraryManager.ItemAdded += _libraryManager_ItemAdded;
- _appHost.HasPendingRestartChanged += _appHost_HasPendingRestartChanged;
- _appHost.HasUpdateAvailableChanged += _appHost_HasUpdateAvailableChanged;
- _activityManager.EntryCreated += _activityManager_EntryCreated;
+ _libraryManager.ItemAdded += OnLibraryManagerItemAdded;
+ _appHost.HasPendingRestartChanged += OnAppHostHasPendingRestartChanged;
+ _appHost.HasUpdateAvailableChanged += OnAppHostHasUpdateAvailableChanged;
+ _activityManager.EntryCreated += OnActivityManagerEntryCreated;
return Task.CompletedTask;
}
- private async void _appHost_HasPendingRestartChanged(object sender, EventArgs e)
+ private async void OnAppHostHasPendingRestartChanged(object sender, EventArgs e)
{
var type = NotificationType.ServerRestartRequired.ToString();
var notification = new NotificationRequest
{
NotificationType = type,
- Name = string.Format(_localization.GetLocalizedString("ServerNameNeedsToBeRestarted"), _appHost.Name)
+ Name = string.Format(
+ CultureInfo.InvariantCulture,
+ _localization.GetLocalizedString("ServerNameNeedsToBeRestarted"),
+ _appHost.Name)
};
await SendNotification(notification, null).ConfigureAwait(false);
}
- private async void _activityManager_EntryCreated(object sender, GenericEventArgs<ActivityLogEntry> e)
+ private async void OnActivityManagerEntryCreated(object sender, GenericEventArgs<ActivityLogEntry> e)
{
var entry = e.Argument;
@@ -117,7 +132,7 @@ namespace Emby.Notifications
return _config.GetConfiguration<NotificationOptions>("notifications");
}
- private async void _appHost_HasUpdateAvailableChanged(object sender, EventArgs e)
+ private async void OnAppHostHasUpdateAvailableChanged(object sender, EventArgs e)
{
if (!_appHost.HasUpdateAvailable)
{
@@ -128,7 +143,7 @@ namespace Emby.Notifications
var notification = new NotificationRequest
{
- Description = "Please see jellyfin.media for details.",
+ Description = "Please see jellyfin.org for details.",
NotificationType = type,
Name = _localization.GetLocalizedString("NewVersionIsAvailable")
};
@@ -136,8 +151,7 @@ namespace Emby.Notifications
await SendNotification(notification, null).ConfigureAwait(false);
}
- private readonly List<BaseItem> _itemsAdded = new List<BaseItem>();
- private void _libraryManager_ItemAdded(object sender, ItemChangeEventArgs e)
+ private void OnLibraryManagerItemAdded(object sender, ItemChangeEventArgs e)
{
if (!FilterItem(e.Item))
{
@@ -146,14 +160,17 @@ namespace Emby.Notifications
lock (_libraryChangedSyncLock)
{
- if (LibraryUpdateTimer == null)
+ if (_libraryUpdateTimer == null)
{
- LibraryUpdateTimer = new Timer(LibraryUpdateTimerCallback, null, 5000,
- Timeout.Infinite);
+ _libraryUpdateTimer = new Timer(
+ LibraryUpdateTimerCallback,
+ null,
+ 5000,
+ Timeout.Infinite);
}
else
{
- LibraryUpdateTimer.Change(5000, Timeout.Infinite);
+ _libraryUpdateTimer.Change(5000, Timeout.Infinite);
}
_itemsAdded.Add(e.Item);
@@ -188,7 +205,8 @@ namespace Emby.Notifications
{
items = _itemsAdded.ToList();
_itemsAdded.Clear();
- DisposeLibraryUpdateTimer();
+ _libraryUpdateTimer!.Dispose(); // Shouldn't be null as it just set off this callback
+ _libraryUpdateTimer = null;
}
items = items.Take(10).ToList();
@@ -198,7 +216,10 @@ namespace Emby.Notifications
var notification = new NotificationRequest
{
NotificationType = NotificationType.NewLibraryContent.ToString(),
- Name = string.Format(_localization.GetLocalizedString("ValueHasBeenAddedToLibrary"), GetItemName(item)),
+ Name = string.Format(
+ CultureInfo.InvariantCulture,
+ _localization.GetLocalizedString("ValueHasBeenAddedToLibrary"),
+ GetItemName(item)),
Description = item.Overview
};
@@ -206,6 +227,11 @@ namespace Emby.Notifications
}
}
+ /// <summary>
+ /// Creates a human readable name for the item.
+ /// </summary>
+ /// <param name="item">The item.</param>
+ /// <returns>A human readable name for the item.</returns>
public static string GetItemName(BaseItem item)
{
var name = item.Name;
@@ -219,6 +245,7 @@ namespace Emby.Notifications
episode.IndexNumber.Value,
name);
}
+
if (episode.ParentIndexNumber.HasValue)
{
name = string.Format(
@@ -229,7 +256,6 @@ namespace Emby.Notifications
}
}
-
if (item is IHasSeries hasSeries)
{
name = hasSeries.SeriesName + " - " + name;
@@ -257,7 +283,7 @@ namespace Emby.Notifications
return name;
}
- private async Task SendNotification(NotificationRequest notification, BaseItem relatedItem)
+ private async Task SendNotification(NotificationRequest notification, BaseItem? relatedItem)
{
try
{
@@ -269,23 +295,37 @@ namespace Emby.Notifications
}
}
+ /// <inheritdoc />
public void Dispose()
{
- DisposeLibraryUpdateTimer();
-
- _libraryManager.ItemAdded -= _libraryManager_ItemAdded;
- _appHost.HasPendingRestartChanged -= _appHost_HasPendingRestartChanged;
- _appHost.HasUpdateAvailableChanged -= _appHost_HasUpdateAvailableChanged;
- _activityManager.EntryCreated -= _activityManager_EntryCreated;
+ Dispose(true);
+ GC.SuppressFinalize(this);
}
- private void DisposeLibraryUpdateTimer()
+ /// <summary>
+ /// Releases unmanaged and optionally managed resources.
+ /// </summary>
+ /// <param name="disposing"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param>
+ protected virtual void Dispose(bool disposing)
{
- if (LibraryUpdateTimer != null)
+ if (_disposed)
+ {
+ return;
+ }
+
+ if (disposing)
{
- LibraryUpdateTimer.Dispose();
- LibraryUpdateTimer = null;
+ _libraryUpdateTimer?.Dispose();
}
+
+ _libraryUpdateTimer = null;
+
+ _libraryManager.ItemAdded -= OnLibraryManagerItemAdded;
+ _appHost.HasPendingRestartChanged -= OnAppHostHasPendingRestartChanged;
+ _appHost.HasUpdateAvailableChanged -= OnAppHostHasUpdateAvailableChanged;
+ _activityManager.EntryCreated -= OnActivityManagerEntryCreated;
+
+ _disposed = true;
}
}
}
diff --git a/Emby.Notifications/NotificationManager.cs b/Emby.Notifications/NotificationManager.cs
index eecbbea07..639a5e1aa 100644
--- a/Emby.Notifications/NotificationManager.cs
+++ b/Emby.Notifications/NotificationManager.cs
@@ -16,20 +16,32 @@ using Microsoft.Extensions.Logging;
namespace Emby.Notifications
{
+ /// <summary>
+ /// NotificationManager class.
+ /// </summary>
public class NotificationManager : INotificationManager
{
private readonly ILogger _logger;
private readonly IUserManager _userManager;
private readonly IServerConfigurationManager _config;
- private INotificationService[] _services;
- private INotificationTypeFactory[] _typeFactories;
-
- public NotificationManager(ILoggerFactory loggerFactory, IUserManager userManager, IServerConfigurationManager config)
+ private INotificationService[] _services = Array.Empty<INotificationService>();
+ private INotificationTypeFactory[] _typeFactories = Array.Empty<INotificationTypeFactory>();
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="NotificationManager" /> class.
+ /// </summary>
+ /// <param name="logger">The logger.</param>
+ /// <param name="userManager">The user manager.</param>
+ /// <param name="config">The server configuration manager.</param>
+ public NotificationManager(
+ ILogger<NotificationManager> logger,
+ IUserManager userManager,
+ IServerConfigurationManager config)
{
+ _logger = logger;
_userManager = userManager;
_config = config;
- _logger = loggerFactory.CreateLogger(GetType().Name);
}
private NotificationOptions GetConfiguration()
@@ -37,12 +49,14 @@ namespace Emby.Notifications
return _config.GetConfiguration<NotificationOptions>("notifications");
}
+ /// <inheritdoc />
public Task SendNotification(NotificationRequest request, CancellationToken cancellationToken)
{
return SendNotification(request, null, cancellationToken);
}
- public Task SendNotification(NotificationRequest request, BaseItem relatedItem, CancellationToken cancellationToken)
+ /// <inheritdoc />
+ public Task SendNotification(NotificationRequest request, BaseItem? relatedItem, CancellationToken cancellationToken)
{
var notificationType = request.NotificationType;
@@ -64,7 +78,8 @@ namespace Emby.Notifications
return Task.WhenAll(tasks);
}
- private Task SendNotification(NotificationRequest request,
+ private Task SendNotification(
+ NotificationRequest request,
INotificationService service,
IEnumerable<User> users,
string title,
@@ -79,7 +94,7 @@ namespace Emby.Notifications
return Task.WhenAll(tasks);
}
- private IEnumerable<Guid> GetUserIds(NotificationRequest request, NotificationOption options)
+ private IEnumerable<Guid> GetUserIds(NotificationRequest request, NotificationOption? options)
{
if (request.SendToUserMode.HasValue)
{
@@ -109,7 +124,8 @@ namespace Emby.Notifications
return request.UserIds;
}
- private async Task SendNotification(NotificationRequest request,
+ private async Task SendNotification(
+ NotificationRequest request,
INotificationService service,
string title,
string description,
@@ -161,12 +177,14 @@ namespace Emby.Notifications
return GetConfiguration().IsServiceEnabled(service.Name, notificationType);
}
+ /// <inheritdoc />
public void AddParts(IEnumerable<INotificationService> services, IEnumerable<INotificationTypeFactory> notificationTypeFactories)
{
_services = services.ToArray();
_typeFactories = notificationTypeFactories.ToArray();
}
+ /// <inheritdoc />
public List<NotificationTypeInfo> GetNotificationTypes()
{
var list = _typeFactories.Select(i =>
@@ -180,7 +198,6 @@ namespace Emby.Notifications
_logger.LogError(ex, "Error in GetNotificationTypes");
return new List<NotificationTypeInfo>();
}
-
}).SelectMany(i => i).ToList();
var config = GetConfiguration();
@@ -193,13 +210,13 @@ namespace Emby.Notifications
return list;
}
+ /// <inheritdoc />
public IEnumerable<NameIdPair> GetNotificationServices()
{
return _services.Select(i => new NameIdPair
{
Name = i.Name,
Id = i.Name.GetMD5().ToString("N", CultureInfo.InvariantCulture)
-
}).OrderBy(i => i.Name);
}
}
diff --git a/Emby.Photos/Emby.Photos.csproj b/Emby.Photos/Emby.Photos.csproj
index 29ed3c5f7..dbe01257f 100644
--- a/Emby.Photos/Emby.Photos.csproj
+++ b/Emby.Photos/Emby.Photos.csproj
@@ -1,7 +1,8 @@
<Project Sdk="Microsoft.NET.Sdk">
- <PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
- <TreatWarningsAsErrors>true</TreatWarningsAsErrors>
+ <!-- ProjectGuid is only included as a requirement for SonarQube analysis -->
+ <PropertyGroup>
+ <ProjectGuid>{89AB4548-770D-41FD-A891-8DAFF44F452C}</ProjectGuid>
</PropertyGroup>
<ItemGroup>
@@ -22,6 +23,7 @@
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
+ <Nullable>enable</Nullable>
</PropertyGroup>
<!-- Code Analyzers-->
diff --git a/Emby.Photos/PhotoProvider.cs b/Emby.Photos/PhotoProvider.cs
index 63631e512..987cb7fb2 100644
--- a/Emby.Photos/PhotoProvider.cs
+++ b/Emby.Photos/PhotoProvider.cs
@@ -160,7 +160,7 @@ namespace Emby.Photos
try
{
- var size = _imageProcessor.GetImageDimensions(item, img, false);
+ var size = _imageProcessor.GetImageDimensions(item, img);
if (size.Width > 0 && size.Height > 0)
{
diff --git a/Emby.Server.Implementations/Activity/ActivityLogEntryPoint.cs b/Emby.Server.Implementations/Activity/ActivityLogEntryPoint.cs
index ac8af66a2..4685a03ac 100644
--- a/Emby.Server.Implementations/Activity/ActivityLogEntryPoint.cs
+++ b/Emby.Server.Implementations/Activity/ActivityLogEntryPoint.cs
@@ -1,6 +1,3 @@
-#pragma warning disable CS1591
-#pragma warning disable SA1600
-
using System;
using System.Collections.Generic;
using System.Globalization;
@@ -9,7 +6,6 @@ using System.Text;
using System.Threading.Tasks;
using MediaBrowser.Common.Plugins;
using MediaBrowser.Common.Updates;
-using MediaBrowser.Controller;
using MediaBrowser.Controller.Authentication;
using MediaBrowser.Controller.Devices;
using MediaBrowser.Controller.Entities;
@@ -29,7 +25,10 @@ using Microsoft.Extensions.Logging;
namespace Emby.Server.Implementations.Activity
{
- public class ActivityLogEntryPoint : IServerEntryPoint
+ /// <summary>
+ /// Entry point for the activity logger.
+ /// </summary>
+ public sealed class ActivityLogEntryPoint : IServerEntryPoint
{
private readonly ILogger _logger;
private readonly IInstallationManager _installationManager;
@@ -39,22 +38,20 @@ namespace Emby.Server.Implementations.Activity
private readonly ILocalizationManager _localization;
private readonly ISubtitleManager _subManager;
private readonly IUserManager _userManager;
- private readonly IServerApplicationHost _appHost;
private readonly IDeviceManager _deviceManager;
/// <summary>
/// Initializes a new instance of the <see cref="ActivityLogEntryPoint"/> class.
/// </summary>
- /// <param name="logger"></param>
- /// <param name="sessionManager"></param>
- /// <param name="deviceManager"></param>
- /// <param name="taskManager"></param>
- /// <param name="activityManager"></param>
- /// <param name="localization"></param>
- /// <param name="installationManager"></param>
- /// <param name="subManager"></param>
- /// <param name="userManager"></param>
- /// <param name="appHost"></param>
+ /// <param name="logger">The logger.</param>
+ /// <param name="sessionManager">The session manager.</param>
+ /// <param name="deviceManager">The device manager.</param>
+ /// <param name="taskManager">The task manager.</param>
+ /// <param name="activityManager">The activity manager.</param>
+ /// <param name="localization">The localization manager.</param>
+ /// <param name="installationManager">The installation manager.</param>
+ /// <param name="subManager">The subtitle manager.</param>
+ /// <param name="userManager">The user manager.</param>
public ActivityLogEntryPoint(
ILogger<ActivityLogEntryPoint> logger,
ISessionManager sessionManager,
@@ -64,8 +61,7 @@ namespace Emby.Server.Implementations.Activity
ILocalizationManager localization,
IInstallationManager installationManager,
ISubtitleManager subManager,
- IUserManager userManager,
- IServerApplicationHost appHost)
+ IUserManager userManager)
{
_logger = logger;
_sessionManager = sessionManager;
@@ -76,9 +72,9 @@ namespace Emby.Server.Implementations.Activity
_installationManager = installationManager;
_subManager = subManager;
_userManager = userManager;
- _appHost = appHost;
}
+ /// <inheritdoc />
public Task RunAsync()
{
_taskManager.TaskCompleted += OnTaskCompleted;
@@ -141,7 +137,7 @@ namespace Emby.Server.Implementations.Activity
CultureInfo.InvariantCulture,
_localization.GetLocalizedString("SubtitleDownloadFailureFromForItem"),
e.Provider,
- Notifications.Notifications.GetItemName(e.Item)),
+ Notifications.NotificationEntryPoint.GetItemName(e.Item)),
Type = "SubtitleDownloadFailure",
ItemId = e.Item.Id.ToString("N", CultureInfo.InvariantCulture),
ShortOverview = e.Exception.Message
@@ -173,7 +169,12 @@ namespace Emby.Server.Implementations.Activity
CreateLogEntry(new ActivityLogEntry
{
- Name = string.Format(_localization.GetLocalizedString("UserStoppedPlayingItemWithValues"), user.Name, GetItemName(item), e.DeviceName),
+ Name = string.Format(
+ CultureInfo.InvariantCulture,
+ _localization.GetLocalizedString("UserStoppedPlayingItemWithValues"),
+ user.Name,
+ GetItemName(item),
+ e.DeviceName),
Type = GetPlaybackStoppedNotificationType(item.MediaType),
UserId = user.Id
});
@@ -264,31 +265,20 @@ namespace Emby.Server.Implementations.Activity
private void OnSessionEnded(object sender, SessionEventArgs e)
{
- string name;
var session = e.SessionInfo;
if (string.IsNullOrEmpty(session.UserName))
{
- name = string.Format(
- CultureInfo.InvariantCulture,
- _localization.GetLocalizedString("DeviceOfflineWithName"),
- session.DeviceName);
-
- // Causing too much spam for now
return;
}
- else
+
+ CreateLogEntry(new ActivityLogEntry
{
- name = string.Format(
+ Name = string.Format(
CultureInfo.InvariantCulture,
_localization.GetLocalizedString("UserOfflineFromDevice"),
session.UserName,
- session.DeviceName);
- }
-
- CreateLogEntry(new ActivityLogEntry
- {
- Name = name,
+ session.DeviceName),
Type = "SessionEnded",
ShortOverview = string.Format(
CultureInfo.InvariantCulture,
@@ -387,31 +377,20 @@ namespace Emby.Server.Implementations.Activity
private void OnSessionStarted(object sender, SessionEventArgs e)
{
- string name;
var session = e.SessionInfo;
if (string.IsNullOrEmpty(session.UserName))
{
- name = string.Format(
- CultureInfo.InvariantCulture,
- _localization.GetLocalizedString("DeviceOnlineWithName"),
- session.DeviceName);
-
- // Causing too much spam for now
return;
}
- else
+
+ CreateLogEntry(new ActivityLogEntry
{
- name = string.Format(
+ Name = string.Format(
CultureInfo.InvariantCulture,
_localization.GetLocalizedString("UserOnlineFromDevice"),
session.UserName,
- session.DeviceName);
- }
-
- CreateLogEntry(new ActivityLogEntry
- {
- Name = name,
+ session.DeviceName),
Type = "SessionStarted",
ShortOverview = string.Format(
CultureInfo.InvariantCulture,
@@ -421,7 +400,7 @@ namespace Emby.Server.Implementations.Activity
});
}
- private void OnPluginUpdated(object sender, GenericEventArgs<(IPlugin, PackageVersionInfo)> e)
+ private void OnPluginUpdated(object sender, GenericEventArgs<(IPlugin, VersionInfo)> e)
{
CreateLogEntry(new ActivityLogEntry
{
@@ -433,8 +412,8 @@ namespace Emby.Server.Implementations.Activity
ShortOverview = string.Format(
CultureInfo.InvariantCulture,
_localization.GetLocalizedString("VersionNumber"),
- e.Argument.Item2.versionStr),
- Overview = e.Argument.Item2.description
+ e.Argument.Item2.version),
+ Overview = e.Argument.Item2.changelog
});
}
@@ -450,7 +429,7 @@ namespace Emby.Server.Implementations.Activity
});
}
- private void OnPluginInstalled(object sender, GenericEventArgs<PackageVersionInfo> e)
+ private void OnPluginInstalled(object sender, GenericEventArgs<VersionInfo> e)
{
CreateLogEntry(new ActivityLogEntry
{
@@ -462,7 +441,7 @@ namespace Emby.Server.Implementations.Activity
ShortOverview = string.Format(
CultureInfo.InvariantCulture,
_localization.GetLocalizedString("VersionNumber"),
- e.Argument.versionStr)
+ e.Argument.version)
});
}
@@ -490,8 +469,8 @@ namespace Emby.Server.Implementations.Activity
var result = e.Result;
var task = e.Task;
- var activityTask = task.ScheduledTask as IConfigurableScheduledTask;
- if (activityTask != null && !activityTask.IsLogged)
+ if (task.ScheduledTask is IConfigurableScheduledTask activityTask
+ && !activityTask.IsLogged)
{
return;
}
@@ -533,6 +512,7 @@ namespace Emby.Server.Implementations.Activity
private void CreateLogEntry(ActivityLogEntry entry)
=> _activityManager.Create(entry);
+ /// <inheritdoc />
public void Dispose()
{
_taskManager.TaskCompleted -= OnTaskCompleted;
@@ -564,7 +544,7 @@ namespace Emby.Server.Implementations.Activity
/// <summary>
/// Constructs a user-friendly string for this TimeSpan instance.
/// </summary>
- public static string ToUserFriendlyString(TimeSpan span)
+ private static string ToUserFriendlyString(TimeSpan span)
{
const int DaysInYear = 365;
const int DaysInMonth = 30;
@@ -578,7 +558,7 @@ namespace Emby.Server.Implementations.Activity
{
int years = days / DaysInYear;
values.Add(CreateValueString(years, "year"));
- days = days % DaysInYear;
+ days %= DaysInYear;
}
// Number of months
@@ -586,7 +566,7 @@ namespace Emby.Server.Implementations.Activity
{
int months = days / DaysInMonth;
values.Add(CreateValueString(months, "month"));
- days = days % DaysInMonth;
+ days %= DaysInMonth;
}
// Number of days
diff --git a/Emby.Server.Implementations/Activity/ActivityManager.cs b/Emby.Server.Implementations/Activity/ActivityManager.cs
index b03c4d182..81bebae3d 100644
--- a/Emby.Server.Implementations/Activity/ActivityManager.cs
+++ b/Emby.Server.Implementations/Activity/ActivityManager.cs
@@ -1,34 +1,33 @@
-#pragma warning disable CS1591
-#pragma warning disable SA1600
-
using System;
-using System.Linq;
using MediaBrowser.Controller.Library;
using MediaBrowser.Model.Activity;
using MediaBrowser.Model.Events;
using MediaBrowser.Model.Querying;
-using Microsoft.Extensions.Logging;
namespace Emby.Server.Implementations.Activity
{
+ /// <summary>
+ /// The activity log manager.
+ /// </summary>
public class ActivityManager : IActivityManager
{
- public event EventHandler<GenericEventArgs<ActivityLogEntry>> EntryCreated;
-
private readonly IActivityRepository _repo;
- private readonly ILogger _logger;
private readonly IUserManager _userManager;
- public ActivityManager(
- ILoggerFactory loggerFactory,
- IActivityRepository repo,
- IUserManager userManager)
+ /// <summary>
+ /// Initializes a new instance of the <see cref="ActivityManager"/> class.
+ /// </summary>
+ /// <param name="repo">The activity repository.</param>
+ /// <param name="userManager">The user manager.</param>
+ public ActivityManager(IActivityRepository repo, IUserManager userManager)
{
- _logger = loggerFactory.CreateLogger(nameof(ActivityManager));
_repo = repo;
_userManager = userManager;
}
+ /// <inheritdoc />
+ public event EventHandler<GenericEventArgs<ActivityLogEntry>> EntryCreated;
+
public void Create(ActivityLogEntry entry)
{
entry.Date = DateTime.UtcNow;
@@ -38,6 +37,7 @@ namespace Emby.Server.Implementations.Activity
EntryCreated?.Invoke(this, new GenericEventArgs<ActivityLogEntry>(entry));
}
+ /// <inheritdoc />
public QueryResult<ActivityLogEntry> GetActivityLogEntries(DateTime? minDate, bool? hasUserId, int? startIndex, int? limit)
{
var result = _repo.GetActivityLogEntries(minDate, hasUserId, startIndex, limit);
@@ -61,6 +61,7 @@ namespace Emby.Server.Implementations.Activity
return result;
}
+ /// <inheritdoc />
public QueryResult<ActivityLogEntry> GetActivityLogEntries(DateTime? minDate, int? startIndex, int? limit)
{
return GetActivityLogEntries(minDate, null, startIndex, limit);
diff --git a/Emby.Server.Implementations/Activity/ActivityRepository.cs b/Emby.Server.Implementations/Activity/ActivityRepository.cs
index 633343bb6..22796ba3f 100644
--- a/Emby.Server.Implementations/Activity/ActivityRepository.cs
+++ b/Emby.Server.Implementations/Activity/ActivityRepository.cs
@@ -1,6 +1,3 @@
-#pragma warning disable CS1591
-#pragma warning disable SA1600
-
using System;
using System.Collections.Generic;
using System.Globalization;
@@ -16,18 +13,31 @@ using SQLitePCL.pretty;
namespace Emby.Server.Implementations.Activity
{
+ /// <summary>
+ /// The activity log repository.
+ /// </summary>
public class ActivityRepository : BaseSqliteRepository, IActivityRepository
{
- private static readonly CultureInfo _usCulture = CultureInfo.ReadOnly(new CultureInfo("en-US"));
+ private const string BaseActivitySelectText = "select Id, Name, Overview, ShortOverview, Type, ItemId, UserId, DateCreated, LogSeverity from ActivityLog";
+
private readonly IFileSystem _fileSystem;
- public ActivityRepository(ILoggerFactory loggerFactory, IServerApplicationPaths appPaths, IFileSystem fileSystem)
- : base(loggerFactory.CreateLogger(nameof(ActivityRepository)))
+ /// <summary>
+ /// Initializes a new instance of the <see cref="ActivityRepository"/> class.
+ /// </summary>
+ /// <param name="logger">The logger.</param>
+ /// <param name="appPaths">The server application paths.</param>
+ /// <param name="fileSystem">The filesystem.</param>
+ public ActivityRepository(ILogger<ActivityRepository> logger, IServerApplicationPaths appPaths, IFileSystem fileSystem)
+ : base(logger)
{
DbFilePath = Path.Combine(appPaths.DataPath, "activitylog.db");
_fileSystem = fileSystem;
}
+ /// <summary>
+ /// Initializes the <see cref="ActivityRepository"/>.
+ /// </summary>
public void Initialize()
{
try
@@ -46,16 +56,14 @@ namespace Emby.Server.Implementations.Activity
private void InitializeInternal()
{
- using (var connection = GetConnection())
+ using var connection = GetConnection();
+ connection.RunQueries(new[]
{
- connection.RunQueries(new[]
- {
- "create table if not exists ActivityLog (Id INTEGER PRIMARY KEY, Name TEXT NOT NULL, Overview TEXT, ShortOverview TEXT, Type TEXT NOT NULL, ItemId TEXT, UserId TEXT, DateCreated DATETIME NOT NULL, LogSeverity TEXT NOT NULL)",
- "drop index if exists idx_ActivityLogEntries"
- });
+ "create table if not exists ActivityLog (Id INTEGER PRIMARY KEY, Name TEXT NOT NULL, Overview TEXT, ShortOverview TEXT, Type TEXT NOT NULL, ItemId TEXT, UserId TEXT, DateCreated DATETIME NOT NULL, LogSeverity TEXT NOT NULL)",
+ "drop index if exists idx_ActivityLogEntries"
+ });
- TryMigrate(connection);
- }
+ TryMigrate(connection);
}
private void TryMigrate(ManagedConnection connection)
@@ -77,8 +85,7 @@ namespace Emby.Server.Implementations.Activity
}
}
- private const string BaseActivitySelectText = "select Id, Name, Overview, ShortOverview, Type, ItemId, UserId, DateCreated, LogSeverity from ActivityLog";
-
+ /// <inheritdoc />
public void Create(ActivityLogEntry entry)
{
if (entry == null)
@@ -86,37 +93,38 @@ namespace Emby.Server.Implementations.Activity
throw new ArgumentNullException(nameof(entry));
}
- using (var connection = GetConnection())
+ using var connection = GetConnection();
+ connection.RunInTransaction(db =>
{
- connection.RunInTransaction(db =>
- {
- using (var statement = db.PrepareStatement("insert into ActivityLog (Name, Overview, ShortOverview, Type, ItemId, UserId, DateCreated, LogSeverity) values (@Name, @Overview, @ShortOverview, @Type, @ItemId, @UserId, @DateCreated, @LogSeverity)"))
- {
- statement.TryBind("@Name", entry.Name);
+ using var statement = db.PrepareStatement("insert into ActivityLog (Name, Overview, ShortOverview, Type, ItemId, UserId, DateCreated, LogSeverity) values (@Name, @Overview, @ShortOverview, @Type, @ItemId, @UserId, @DateCreated, @LogSeverity)");
+ statement.TryBind("@Name", entry.Name);
- statement.TryBind("@Overview", entry.Overview);
- statement.TryBind("@ShortOverview", entry.ShortOverview);
- statement.TryBind("@Type", entry.Type);
- statement.TryBind("@ItemId", entry.ItemId);
+ statement.TryBind("@Overview", entry.Overview);
+ statement.TryBind("@ShortOverview", entry.ShortOverview);
+ statement.TryBind("@Type", entry.Type);
+ statement.TryBind("@ItemId", entry.ItemId);
- if (entry.UserId.Equals(Guid.Empty))
- {
- statement.TryBindNull("@UserId");
- }
- else
- {
- statement.TryBind("@UserId", entry.UserId.ToString("N", CultureInfo.InvariantCulture));
- }
+ if (entry.UserId.Equals(Guid.Empty))
+ {
+ statement.TryBindNull("@UserId");
+ }
+ else
+ {
+ statement.TryBind("@UserId", entry.UserId.ToString("N", CultureInfo.InvariantCulture));
+ }
- statement.TryBind("@DateCreated", entry.Date.ToDateTimeParamValue());
- statement.TryBind("@LogSeverity", entry.Severity.ToString());
+ statement.TryBind("@DateCreated", entry.Date.ToDateTimeParamValue());
+ statement.TryBind("@LogSeverity", entry.Severity.ToString());
- statement.MoveNext();
- }
- }, TransactionMode);
- }
+ statement.MoveNext();
+ }, TransactionMode);
}
+ /// <summary>
+ /// Adds the provided <see cref="ActivityLogEntry"/> to this repository.
+ /// </summary>
+ /// <param name="entry">The activity log entry.</param>
+ /// <exception cref="ArgumentNullException">If entry is null.</exception>
public void Update(ActivityLogEntry entry)
{
if (entry == null)
@@ -124,38 +132,35 @@ namespace Emby.Server.Implementations.Activity
throw new ArgumentNullException(nameof(entry));
}
- using (var connection = GetConnection())
+ using var connection = GetConnection();
+ connection.RunInTransaction(db =>
{
- connection.RunInTransaction(db =>
- {
- using (var statement = db.PrepareStatement("Update ActivityLog set Name=@Name,Overview=@Overview,ShortOverview=@ShortOverview,Type=@Type,ItemId=@ItemId,UserId=@UserId,DateCreated=@DateCreated,LogSeverity=@LogSeverity where Id=@Id"))
- {
- statement.TryBind("@Id", entry.Id);
+ using var statement = db.PrepareStatement("Update ActivityLog set Name=@Name,Overview=@Overview,ShortOverview=@ShortOverview,Type=@Type,ItemId=@ItemId,UserId=@UserId,DateCreated=@DateCreated,LogSeverity=@LogSeverity where Id=@Id");
+ statement.TryBind("@Id", entry.Id);
- statement.TryBind("@Name", entry.Name);
- statement.TryBind("@Overview", entry.Overview);
- statement.TryBind("@ShortOverview", entry.ShortOverview);
- statement.TryBind("@Type", entry.Type);
- statement.TryBind("@ItemId", entry.ItemId);
+ statement.TryBind("@Name", entry.Name);
+ statement.TryBind("@Overview", entry.Overview);
+ statement.TryBind("@ShortOverview", entry.ShortOverview);
+ statement.TryBind("@Type", entry.Type);
+ statement.TryBind("@ItemId", entry.ItemId);
- if (entry.UserId.Equals(Guid.Empty))
- {
- statement.TryBindNull("@UserId");
- }
- else
- {
- statement.TryBind("@UserId", entry.UserId.ToString("N", CultureInfo.InvariantCulture));
- }
+ if (entry.UserId.Equals(Guid.Empty))
+ {
+ statement.TryBindNull("@UserId");
+ }
+ else
+ {
+ statement.TryBind("@UserId", entry.UserId.ToString("N", CultureInfo.InvariantCulture));
+ }
- statement.TryBind("@DateCreated", entry.Date.ToDateTimeParamValue());
- statement.TryBind("@LogSeverity", entry.Severity.ToString());
+ statement.TryBind("@DateCreated", entry.Date.ToDateTimeParamValue());
+ statement.TryBind("@LogSeverity", entry.Severity.ToString());
- statement.MoveNext();
- }
- }, TransactionMode);
- }
+ statement.MoveNext();
+ }, TransactionMode);
}
+ /// <inheritdoc />
public QueryResult<ActivityLogEntry> GetActivityLogEntries(DateTime? minDate, bool? hasUserId, int? startIndex, int? limit)
{
var commandText = BaseActivitySelectText;
@@ -165,16 +170,10 @@ namespace Emby.Server.Implementations.Activity
{
whereClauses.Add("DateCreated>=@DateCreated");
}
+
if (hasUserId.HasValue)
{
- if (hasUserId.Value)
- {
- whereClauses.Add("UserId not null");
- }
- else
- {
- whereClauses.Add("UserId is null");
- }
+ whereClauses.Add(hasUserId.Value ? "UserId not null" : "UserId is null");
}
var whereTextWithoutPaging = whereClauses.Count == 0 ?
@@ -205,7 +204,7 @@ namespace Emby.Server.Implementations.Activity
if (limit.HasValue)
{
- commandText += " LIMIT " + limit.Value.ToString(_usCulture);
+ commandText += " LIMIT " + limit.Value.ToString(CultureInfo.InvariantCulture);
}
var statementTexts = new[]
@@ -217,38 +216,33 @@ namespace Emby.Server.Implementations.Activity
var list = new List<ActivityLogEntry>();
var result = new QueryResult<ActivityLogEntry>();
- using (var connection = GetConnection(true))
- {
- connection.RunInTransaction(
- db =>
- {
- var statements = PrepareAll(db, statementTexts).ToList();
+ using var connection = GetConnection(true);
+ connection.RunInTransaction(
+ db =>
+ {
+ var statements = PrepareAll(db, statementTexts).ToList();
- using (var statement = statements[0])
+ using (var statement = statements[0])
+ {
+ if (minDate.HasValue)
{
- if (minDate.HasValue)
- {
- statement.TryBind("@DateCreated", minDate.Value.ToDateTimeParamValue());
- }
-
- foreach (var row in statement.ExecuteQuery())
- {
- list.Add(GetEntry(row));
- }
+ statement.TryBind("@DateCreated", minDate.Value.ToDateTimeParamValue());
}
- using (var statement = statements[1])
- {
- if (minDate.HasValue)
- {
- statement.TryBind("@DateCreated", minDate.Value.ToDateTimeParamValue());
- }
+ list.AddRange(statement.ExecuteQuery().Select(GetEntry));
+ }
- result.TotalRecordCount = statement.ExecuteQuery().SelectScalarInt().First();
+ using (var statement = statements[1])
+ {
+ if (minDate.HasValue)
+ {
+ statement.TryBind("@DateCreated", minDate.Value.ToDateTimeParamValue());
}
- },
- ReadTransactionMode);
- }
+
+ result.TotalRecordCount = statement.ExecuteQuery().SelectScalarInt().First();
+ }
+ },
+ ReadTransactionMode);
result.Items = list;
return result;
@@ -305,7 +299,7 @@ namespace Emby.Server.Implementations.Activity
index++;
if (reader[index].SQLiteType != SQLiteType.Null)
{
- info.Severity = (LogLevel)Enum.Parse(typeof(LogLevel), reader[index].ToString(), true);
+ info.Severity = Enum.Parse<LogLevel>(reader[index].ToString(), true);
}
return info;
diff --git a/Emby.Server.Implementations/AppBase/BaseApplicationPaths.cs b/Emby.Server.Implementations/AppBase/BaseApplicationPaths.cs
index c3cdcc222..2adc1d6c3 100644
--- a/Emby.Server.Implementations/AppBase/BaseApplicationPaths.cs
+++ b/Emby.Server.Implementations/AppBase/BaseApplicationPaths.cs
@@ -5,7 +5,7 @@ using MediaBrowser.Common.Configuration;
namespace Emby.Server.Implementations.AppBase
{
/// <summary>
- /// Provides a base class to hold common application paths used by both the Ui and Server.
+ /// Provides a base class to hold common application paths used by both the UI and Server.
/// This can be subclassed to add application-specific paths.
/// </summary>
public abstract class BaseApplicationPaths : IApplicationPaths
@@ -15,6 +15,11 @@ namespace Emby.Server.Implementations.AppBase
/// <summary>
/// Initializes a new instance of the <see cref="BaseApplicationPaths"/> class.
/// </summary>
+ /// <param name="programDataPath">The program data path.</param>
+ /// <param name="logDirectoryPath">The log directory path.</param>
+ /// <param name="configurationDirectoryPath">The configuration directory path.</param>
+ /// <param name="cacheDirectoryPath">The cache directory path.</param>
+ /// <param name="webDirectoryPath">The web directory path.</param>
protected BaseApplicationPaths(
string programDataPath,
string logDirectoryPath,
@@ -37,10 +42,7 @@ namespace Emby.Server.Implementations.AppBase
/// <value>The program data path.</value>
public string ProgramDataPath { get; }
- /// <summary>
- /// Gets the path to the web UI resources folder.
- /// </summary>
- /// <value>The web UI resources path.</value>
+ /// <inheritdoc/>
public string WebPath { get; }
/// <summary>
diff --git a/Emby.Server.Implementations/AppBase/ConfigurationHelper.cs b/Emby.Server.Implementations/AppBase/ConfigurationHelper.cs
index 854d7b4cb..0b681fddf 100644
--- a/Emby.Server.Implementations/AppBase/ConfigurationHelper.cs
+++ b/Emby.Server.Implementations/AppBase/ConfigurationHelper.cs
@@ -36,24 +36,22 @@ namespace Emby.Server.Implementations.AppBase
configuration = Activator.CreateInstance(type);
}
- using (var stream = new MemoryStream())
- {
- xmlSerializer.SerializeToStream(configuration, stream);
-
- // Take the object we just got and serialize it back to bytes
- var newBytes = stream.ToArray();
+ using var stream = new MemoryStream();
+ xmlSerializer.SerializeToStream(configuration, stream);
- // If the file didn't exist before, or if something has changed, re-save
- if (buffer == null || !buffer.SequenceEqual(newBytes))
- {
- Directory.CreateDirectory(Path.GetDirectoryName(path));
+ // Take the object we just got and serialize it back to bytes
+ var newBytes = stream.ToArray();
- // Save it after load in case we got new items
- File.WriteAllBytes(path, newBytes);
- }
+ // If the file didn't exist before, or if something has changed, re-save
+ if (buffer == null || !buffer.SequenceEqual(newBytes))
+ {
+ Directory.CreateDirectory(Path.GetDirectoryName(path));
- return configuration;
+ // Save it after load in case we got new items
+ File.WriteAllBytes(path, newBytes);
}
+
+ return configuration;
}
}
}
diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs
index e3e071af9..8b387e195 100644
--- a/Emby.Server.Implementations/ApplicationHost.cs
+++ b/Emby.Server.Implementations/ApplicationHost.cs
@@ -1,5 +1,4 @@
#pragma warning disable CS1591
-#pragma warning disable SA1600
using System;
using System.Collections.Concurrent;
@@ -31,7 +30,6 @@ using Emby.Server.Implementations.Configuration;
using Emby.Server.Implementations.Cryptography;
using Emby.Server.Implementations.Data;
using Emby.Server.Implementations.Devices;
-using Emby.Server.Implementations.Diagnostics;
using Emby.Server.Implementations.Dto;
using Emby.Server.Implementations.HttpServer;
using Emby.Server.Implementations.HttpServer.Security;
@@ -44,6 +42,7 @@ using Emby.Server.Implementations.Playlists;
using Emby.Server.Implementations.ScheduledTasks;
using Emby.Server.Implementations.Security;
using Emby.Server.Implementations.Serialization;
+using Emby.Server.Implementations.Services;
using Emby.Server.Implementations.Session;
using Emby.Server.Implementations.TV;
using Emby.Server.Implementations.Updates;
@@ -85,9 +84,7 @@ using MediaBrowser.MediaEncoding.BdInfo;
using MediaBrowser.Model.Activity;
using MediaBrowser.Model.Configuration;
using MediaBrowser.Model.Cryptography;
-using MediaBrowser.Model.Diagnostics;
using MediaBrowser.Model.Dlna;
-using MediaBrowser.Model.Events;
using MediaBrowser.Model.Globalization;
using MediaBrowser.Model.IO;
using MediaBrowser.Model.MediaInfo;
@@ -99,15 +96,17 @@ using MediaBrowser.Model.Tasks;
using MediaBrowser.Model.Updates;
using MediaBrowser.Providers.Chapters;
using MediaBrowser.Providers.Manager;
+using MediaBrowser.Providers.Plugins.TheTvdb;
using MediaBrowser.Providers.Subtitles;
-using MediaBrowser.Providers.TV.TheTVDB;
using MediaBrowser.WebDashboard.Api;
using MediaBrowser.XbmcMetadata.Providers;
using Microsoft.AspNetCore.Http;
+using Microsoft.AspNetCore.Http.Extensions;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using OperatingSystem = MediaBrowser.Common.System.OperatingSystem;
+using Prometheus.DotNetRuntime;
namespace Emby.Server.Implementations
{
@@ -116,15 +115,25 @@ namespace Emby.Server.Implementations
/// </summary>
public abstract class ApplicationHost : IServerApplicationHost, IDisposable
{
- private SqliteUserRepository _userRepository;
+ /// <summary>
+ /// The environment variable prefixes to log at server startup.
+ /// </summary>
+ private static readonly string[] _relevantEnvVarPrefixes = { "JELLYFIN_", "DOTNET_", "ASPNETCORE_" };
+
+ private readonly IFileSystem _fileSystemManager;
+ private readonly INetworkManager _networkManager;
+ private readonly IXmlSerializer _xmlSerializer;
+ private readonly IStartupOptions _startupOptions;
- private SqliteDisplayPreferencesRepository _displayPreferencesRepository;
+ private IMediaEncoder _mediaEncoder;
+ private ISessionManager _sessionManager;
+ private IHttpServer _httpServer;
+ private IHttpClient _httpClient;
/// <summary>
/// Gets a value indicating whether this instance can self restart.
/// </summary>
- /// <value><c>true</c> if this instance can self restart; otherwise, <c>false</c>.</value>
- public abstract bool CanSelfRestart { get; }
+ public bool CanSelfRestart => _startupOptions.RestartPath != null;
public virtual bool CanLaunchWebBrowser
{
@@ -135,7 +144,7 @@ namespace Emby.Server.Implementations
return false;
}
- if (StartupOptions.IsService)
+ if (_startupOptions.IsService)
{
return false;
}
@@ -165,10 +174,9 @@ namespace Emby.Server.Implementations
public bool IsShuttingDown { get; private set; }
/// <summary>
- /// Gets or sets the logger.
+ /// Gets the logger.
/// </summary>
- /// <value>The logger.</value>
- protected ILogger Logger { get; set; }
+ protected ILogger Logger { get; }
private IPlugin[] _plugins;
@@ -179,10 +187,9 @@ namespace Emby.Server.Implementations
public IReadOnlyList<IPlugin> Plugins => _plugins;
/// <summary>
- /// Gets or sets the logger factory.
+ /// Gets the logger factory.
/// </summary>
- /// <value>The logger factory.</value>
- public ILoggerFactory LoggerFactory { get; protected set; }
+ protected ILoggerFactory LoggerFactory { get; }
/// <summary>
/// Gets or sets the application paths.
@@ -207,21 +214,6 @@ namespace Emby.Server.Implementations
/// <value>The configuration manager.</value>
protected IConfigurationManager ConfigurationManager { get; set; }
- public IFileSystem FileSystemManager { get; set; }
-
- /// <inheritdoc />
- public PackageVersionClass SystemUpdateLevel
- {
- get
- {
-#if BETA
- return PackageVersionClass.Beta;
-#else
- return PackageVersionClass.Release;
-#endif
- }
- }
-
/// <summary>
/// Gets or sets the service provider.
/// </summary>
@@ -238,125 +230,12 @@ namespace Emby.Server.Implementations
public int HttpsPort { get; private set; }
/// <summary>
- /// Gets the content root for the webhost.
- /// </summary>
- public string ContentRoot { get; private set; }
-
- /// <summary>
/// Gets the server configuration manager.
/// </summary>
/// <value>The server configuration manager.</value>
public IServerConfigurationManager ServerConfigurationManager => (IServerConfigurationManager)ConfigurationManager;
/// <summary>
- /// Gets or sets the user manager.
- /// </summary>
- /// <value>The user manager.</value>
- public IUserManager UserManager { get; set; }
-
- /// <summary>
- /// Gets or sets the library manager.
- /// </summary>
- /// <value>The library manager.</value>
- internal ILibraryManager LibraryManager { get; set; }
-
- /// <summary>
- /// Gets or sets the directory watchers.
- /// </summary>
- /// <value>The directory watchers.</value>
- private ILibraryMonitor LibraryMonitor { get; set; }
-
- /// <summary>
- /// Gets or sets the provider manager.
- /// </summary>
- /// <value>The provider manager.</value>
- private IProviderManager ProviderManager { get; set; }
-
- /// <summary>
- /// Gets or sets the HTTP server.
- /// </summary>
- /// <value>The HTTP server.</value>
- private IHttpServer HttpServer { get; set; }
-
- private IDtoService DtoService { get; set; }
-
- public IImageProcessor ImageProcessor { get; set; }
-
- /// <summary>
- /// Gets or sets the media encoder.
- /// </summary>
- /// <value>The media encoder.</value>
- private IMediaEncoder MediaEncoder { get; set; }
-
- private ISubtitleEncoder SubtitleEncoder { get; set; }
-
- private ISessionManager SessionManager { get; set; }
-
- private ILiveTvManager LiveTvManager { get; set; }
-
- public LocalizationManager LocalizationManager { get; set; }
-
- private IEncodingManager EncodingManager { get; set; }
-
- private IChannelManager ChannelManager { get; set; }
-
- /// <summary>
- /// Gets or sets the user data repository.
- /// </summary>
- /// <value>The user data repository.</value>
- private IUserDataManager UserDataManager { get; set; }
-
- internal SqliteItemRepository ItemRepository { get; set; }
-
- private INotificationManager NotificationManager { get; set; }
-
- private ISubtitleManager SubtitleManager { get; set; }
-
- private IChapterManager ChapterManager { get; set; }
-
- private IDeviceManager DeviceManager { get; set; }
-
- internal IUserViewManager UserViewManager { get; set; }
-
- private IAuthenticationRepository AuthenticationRepository { get; set; }
-
- private ITVSeriesManager TVSeriesManager { get; set; }
-
- private ICollectionManager CollectionManager { get; set; }
-
- private IMediaSourceManager MediaSourceManager { get; set; }
-
- private readonly IConfiguration _configuration;
-
- /// <summary>
- /// Gets the installation manager.
- /// </summary>
- /// <value>The installation manager.</value>
- protected IInstallationManager InstallationManager { get; private set; }
-
- protected IAuthService AuthService { get; private set; }
-
- public IStartupOptions StartupOptions { get; }
-
- internal IImageEncoder ImageEncoder { get; private set; }
-
- protected IProcessFactory ProcessFactory { get; private set; }
-
- protected readonly IXmlSerializer XmlSerializer;
-
- protected ISocketFactory SocketFactory { get; private set; }
-
- protected ITaskManager TaskManager { get; private set; }
-
- public IHttpClient HttpClient { get; private set; }
-
- protected INetworkManager NetworkManager { get; set; }
-
- public IJsonSerializer JsonSerializer { get; private set; }
-
- protected IIsoManager IsoManager { get; private set; }
-
- /// <summary>
/// Initializes a new instance of the <see cref="ApplicationHost" /> class.
/// </summary>
public ApplicationHost(
@@ -364,32 +243,39 @@ namespace Emby.Server.Implementations
ILoggerFactory loggerFactory,
IStartupOptions options,
IFileSystem fileSystem,
- IImageEncoder imageEncoder,
- INetworkManager networkManager,
- IConfiguration configuration)
+ INetworkManager networkManager)
{
- _configuration = configuration;
-
- XmlSerializer = new MyXmlSerializer();
+ _xmlSerializer = new MyXmlSerializer();
- NetworkManager = networkManager;
+ _networkManager = networkManager;
networkManager.LocalSubnetsFn = GetConfiguredLocalSubnets;
ApplicationPaths = applicationPaths;
LoggerFactory = loggerFactory;
- FileSystemManager = fileSystem;
+ _fileSystemManager = fileSystem;
- ConfigurationManager = new ServerConfigurationManager(ApplicationPaths, LoggerFactory, XmlSerializer, FileSystemManager);
+ ConfigurationManager = new ServerConfigurationManager(ApplicationPaths, LoggerFactory, _xmlSerializer, _fileSystemManager);
- Logger = LoggerFactory.CreateLogger("App");
+ Logger = LoggerFactory.CreateLogger<ApplicationHost>();
- StartupOptions = options;
+ _startupOptions = options;
- ImageEncoder = imageEncoder;
+ // Initialize runtime stat collection
+ if (ServerConfigurationManager.Configuration.EnableMetrics)
+ {
+ DotNetRuntimeStatsBuilder.Default().StartCollecting();
+ }
fileSystem.AddShortcutHandler(new MbLinkShortcutHandler(fileSystem));
- NetworkManager.NetworkChanged += OnNetworkChanged;
+ _networkManager.NetworkChanged += OnNetworkChanged;
+
+ CertificateInfo = new CertificateInfo
+ {
+ Path = ServerConfigurationManager.Configuration.CertificatePath,
+ Password = ServerConfigurationManager.Configuration.CertificatePassword
+ };
+ Certificate = GetCertificate(CertificateInfo);
}
public string ExpandVirtualPath(string path)
@@ -457,10 +343,7 @@ namespace Emby.Server.Implementations
}
}
- /// <summary>
- /// Gets the name.
- /// </summary>
- /// <value>The name.</value>
+ /// <inheritdoc/>
public string Name => ApplicationProductName;
/// <summary>
@@ -550,7 +433,7 @@ namespace Emby.Server.Implementations
ConfigurationManager.ConfigurationUpdated += OnConfigurationUpdated;
- MediaEncoder.SetFFmpegPath();
+ _mediaEncoder.SetFFmpegPath();
Logger.LogInformation("ServerId: {0}", SystemId);
@@ -562,7 +445,7 @@ namespace Emby.Server.Implementations
Logger.LogInformation("Executed all pre-startup entry points in {Elapsed:g}", stopWatch.Elapsed);
Logger.LogInformation("Core startup complete");
- HttpServer.GlobalResponse = null;
+ _httpServer.GlobalResponse = null;
stopWatch.Restart();
await Task.WhenAll(StartEntryPoints(entryPoints, false)).ConfigureAwait(false);
@@ -585,7 +468,8 @@ namespace Emby.Server.Implementations
}
}
- public async Task InitAsync(IServiceCollection serviceCollection)
+ /// <inheritdoc/>
+ public void Init(IServiceCollection serviceCollection)
{
HttpPort = ServerConfigurationManager.Configuration.HttpServerPortNumber;
HttpsPort = ServerConfigurationManager.Configuration.HttpsPortNumber;
@@ -597,8 +481,6 @@ namespace Emby.Server.Implementations
HttpsPort = ServerConfiguration.DefaultHttpsPort;
}
- JsonSerializer = new JsonSerializer(FileSystemManager);
-
if (Plugins != null)
{
var pluginBuilder = new StringBuilder();
@@ -618,23 +500,19 @@ namespace Emby.Server.Implementations
DiscoverTypes();
- await RegisterResources(serviceCollection).ConfigureAwait(false);
-
- ContentRoot = ServerConfigurationManager.Configuration.DashboardSourcePath;
- if (string.IsNullOrEmpty(ContentRoot))
- {
- ContentRoot = ServerConfigurationManager.ApplicationPaths.WebPath;
- }
+ RegisterServices(serviceCollection);
}
public Task ExecuteHttpHandlerAsync(HttpContext context, Func<Task> next)
=> HttpServer.RequestHandler(context);
/// <summary>
- /// Registers resources that classes will depend on
+ /// Registers services/resources with the service collection that will be available via DI.
/// </summary>
- protected async Task RegisterResources(IServiceCollection serviceCollection)
+ protected virtual void RegisterServices(IServiceCollection serviceCollection)
{
+ serviceCollection.AddSingleton(_startupOptions);
+
serviceCollection.AddMemoryCache();
serviceCollection.AddSingleton(ConfigurationManager);
@@ -642,223 +520,169 @@ namespace Emby.Server.Implementations
serviceCollection.AddSingleton<IApplicationPaths>(ApplicationPaths);
- serviceCollection.AddSingleton<IConfiguration>(_configuration);
-
- serviceCollection.AddSingleton(JsonSerializer);
+ serviceCollection.AddSingleton<IJsonSerializer, JsonSerializer>();
- serviceCollection.AddSingleton(LoggerFactory);
- serviceCollection.AddLogging();
- serviceCollection.AddSingleton(Logger);
-
- serviceCollection.AddSingleton(FileSystemManager);
- serviceCollection.AddSingleton<TvDbClientManager>();
+ // TODO: Remove support for injecting ILogger completely
+ serviceCollection.AddSingleton((provider) =>
+ {
+ Logger.LogWarning("Injecting ILogger directly is deprecated and should be replaced with ILogger<T>");
+ return Logger;
+ });
- HttpClient = new HttpClientManager.HttpClientManager(
- ApplicationPaths,
- LoggerFactory.CreateLogger<HttpClientManager.HttpClientManager>(),
- FileSystemManager,
- () => ApplicationUserAgent);
- serviceCollection.AddSingleton(HttpClient);
+ serviceCollection.AddSingleton(_fileSystemManager);
+ serviceCollection.AddSingleton<TvdbClientManager>();
- serviceCollection.AddSingleton(NetworkManager);
+ serviceCollection.AddSingleton<IHttpClient, HttpClientManager.HttpClientManager>();
- IsoManager = new IsoManager();
- serviceCollection.AddSingleton(IsoManager);
+ serviceCollection.AddSingleton(_networkManager);
- TaskManager = new TaskManager(ApplicationPaths, JsonSerializer, LoggerFactory, FileSystemManager);
- serviceCollection.AddSingleton(TaskManager);
+ serviceCollection.AddSingleton<IIsoManager, IsoManager>();
- serviceCollection.AddSingleton(XmlSerializer);
+ serviceCollection.AddSingleton<ITaskManager, TaskManager>();
- ProcessFactory = new ProcessFactory();
- serviceCollection.AddSingleton(ProcessFactory);
+ serviceCollection.AddSingleton(_xmlSerializer);
- serviceCollection.AddSingleton(typeof(IStreamHelper), typeof(StreamHelper));
+ serviceCollection.AddSingleton<IStreamHelper, StreamHelper>();
- var cryptoProvider = new CryptographyProvider();
- serviceCollection.AddSingleton<ICryptoProvider>(cryptoProvider);
+ serviceCollection.AddSingleton<ICryptoProvider, CryptographyProvider>();
- SocketFactory = new SocketFactory();
- serviceCollection.AddSingleton(SocketFactory);
+ serviceCollection.AddSingleton<ISocketFactory, SocketFactory>();
- serviceCollection.AddSingleton(typeof(IInstallationManager), typeof(InstallationManager));
+ serviceCollection.AddSingleton<IInstallationManager, InstallationManager>();
- serviceCollection.AddSingleton(typeof(IZipClient), typeof(ZipClient));
+ serviceCollection.AddSingleton<IZipClient, ZipClient>();
- serviceCollection.AddSingleton(typeof(IHttpResultFactory), typeof(HttpResultFactory));
+ serviceCollection.AddSingleton<IHttpResultFactory, HttpResultFactory>();
serviceCollection.AddSingleton<IServerApplicationHost>(this);
serviceCollection.AddSingleton<IServerApplicationPaths>(ApplicationPaths);
serviceCollection.AddSingleton(ServerConfigurationManager);
- LocalizationManager = new LocalizationManager(ServerConfigurationManager, JsonSerializer, LoggerFactory.CreateLogger<LocalizationManager>());
- await LocalizationManager.LoadAll().ConfigureAwait(false);
- serviceCollection.AddSingleton<ILocalizationManager>(LocalizationManager);
+ serviceCollection.AddSingleton<ILocalizationManager, LocalizationManager>();
- serviceCollection.AddSingleton<IBlurayExaminer>(new BdInfoExaminer(FileSystemManager));
+ serviceCollection.AddSingleton<IBlurayExaminer, BdInfoExaminer>();
- UserDataManager = new UserDataManager(LoggerFactory, ServerConfigurationManager, () => UserManager);
- serviceCollection.AddSingleton(UserDataManager);
+ serviceCollection.AddSingleton<IUserDataRepository, SqliteUserDataRepository>();
+ serviceCollection.AddSingleton<IUserDataManager, UserDataManager>();
- _displayPreferencesRepository = new SqliteDisplayPreferencesRepository(
- LoggerFactory.CreateLogger<SqliteDisplayPreferencesRepository>(),
- ApplicationPaths,
- FileSystemManager);
- serviceCollection.AddSingleton<IDisplayPreferencesRepository>(_displayPreferencesRepository);
+ serviceCollection.AddSingleton<IDisplayPreferencesRepository, SqliteDisplayPreferencesRepository>();
- ItemRepository = new SqliteItemRepository(ServerConfigurationManager, this, LoggerFactory.CreateLogger<SqliteItemRepository>(), LocalizationManager);
- serviceCollection.AddSingleton<IItemRepository>(ItemRepository);
+ serviceCollection.AddSingleton<IItemRepository, SqliteItemRepository>();
- AuthenticationRepository = GetAuthenticationRepository();
- serviceCollection.AddSingleton(AuthenticationRepository);
+ serviceCollection.AddSingleton<IAuthenticationRepository, AuthenticationRepository>();
- _userRepository = GetUserRepository();
+ serviceCollection.AddSingleton<IUserRepository, SqliteUserRepository>();
- UserManager = new UserManager(
- LoggerFactory.CreateLogger<UserManager>(),
- _userRepository,
- XmlSerializer,
- NetworkManager,
- () => ImageProcessor,
- () => DtoService,
- this,
- JsonSerializer,
- FileSystemManager,
- cryptoProvider);
+ // TODO: Refactor to eliminate the circular dependency here so that Lazy<T> isn't required
+ serviceCollection.AddTransient(provider => new Lazy<IDtoService>(provider.GetRequiredService<IDtoService>));
+ serviceCollection.AddSingleton<IUserManager, UserManager>();
- serviceCollection.AddSingleton(UserManager);
+ // TODO: Refactor to eliminate the circular dependency here so that Lazy<T> isn't required
+ // TODO: Add StartupOptions.FFmpegPath to IConfiguration and remove this custom activation
+ serviceCollection.AddTransient(provider => new Lazy<EncodingHelper>(provider.GetRequiredService<EncodingHelper>));
+ serviceCollection.AddSingleton<IMediaEncoder>(provider =>
+ ActivatorUtilities.CreateInstance<MediaBrowser.MediaEncoding.Encoder.MediaEncoder>(provider, _startupOptions.FFmpegPath ?? string.Empty));
- LibraryManager = new LibraryManager(this, LoggerFactory, TaskManager, UserManager, ServerConfigurationManager, UserDataManager, () => LibraryMonitor, FileSystemManager, () => ProviderManager, () => UserViewManager);
- serviceCollection.AddSingleton(LibraryManager);
+ // TODO: Refactor to eliminate the circular dependencies here so that Lazy<T> isn't required
+ serviceCollection.AddTransient(provider => new Lazy<ILibraryMonitor>(provider.GetRequiredService<ILibraryMonitor>));
+ serviceCollection.AddTransient(provider => new Lazy<IProviderManager>(provider.GetRequiredService<IProviderManager>));
+ serviceCollection.AddTransient(provider => new Lazy<IUserViewManager>(provider.GetRequiredService<IUserViewManager>));
+ serviceCollection.AddSingleton<ILibraryManager, LibraryManager>();
- var musicManager = new MusicManager(LibraryManager);
- serviceCollection.AddSingleton<IMusicManager>(musicManager);
+ serviceCollection.AddSingleton<IMusicManager, MusicManager>();
- LibraryMonitor = new LibraryMonitor(LoggerFactory, LibraryManager, ServerConfigurationManager, FileSystemManager);
- serviceCollection.AddSingleton(LibraryMonitor);
+ serviceCollection.AddSingleton<ILibraryMonitor, LibraryMonitor>();
- serviceCollection.AddSingleton<ISearchEngine>(new SearchEngine(LoggerFactory, LibraryManager, UserManager));
+ serviceCollection.AddSingleton<ISearchEngine, SearchEngine>();
- CertificateInfo = GetCertificateInfo(true);
- Certificate = GetCertificate(CertificateInfo);
+ serviceCollection.AddSingleton<ServiceController>();
+ serviceCollection.AddSingleton<IHttpListener, WebSocketSharpListener>();
+ serviceCollection.AddSingleton<IHttpServer, HttpListenerHost>();
- HttpServer = new HttpListenerHost(
- this,
- LoggerFactory.CreateLogger<HttpListenerHost>(),
- ServerConfigurationManager,
- _configuration,
- NetworkManager,
- JsonSerializer,
- XmlSerializer,
- LoggerFactory)
- {
- GlobalResponse = LocalizationManager.GetLocalizedString("StartupEmbyServerIsLoading")
- };
+ serviceCollection.AddSingleton<IImageProcessor, ImageProcessor>();
- serviceCollection.AddSingleton(HttpServer);
+ serviceCollection.AddSingleton<ITVSeriesManager, TVSeriesManager>();
- ImageProcessor = new ImageProcessor(LoggerFactory.CreateLogger<ImageProcessor>(), ServerConfigurationManager.ApplicationPaths, FileSystemManager, ImageEncoder, () => LibraryManager, () => MediaEncoder);
- serviceCollection.AddSingleton(ImageProcessor);
+ serviceCollection.AddSingleton<IDeviceManager, DeviceManager>();
- TVSeriesManager = new TVSeriesManager(UserManager, UserDataManager, LibraryManager, ServerConfigurationManager);
- serviceCollection.AddSingleton(TVSeriesManager);
+ serviceCollection.AddSingleton<IMediaSourceManager, MediaSourceManager>();
- DeviceManager = new DeviceManager(AuthenticationRepository, JsonSerializer, LibraryManager, LocalizationManager, UserManager, FileSystemManager, LibraryMonitor, ServerConfigurationManager);
- serviceCollection.AddSingleton(DeviceManager);
+ serviceCollection.AddSingleton<ISubtitleManager, SubtitleManager>();
- MediaSourceManager = new MediaSourceManager(ItemRepository, ApplicationPaths, LocalizationManager, UserManager, LibraryManager, LoggerFactory, JsonSerializer, FileSystemManager, UserDataManager, () => MediaEncoder);
- serviceCollection.AddSingleton(MediaSourceManager);
+ serviceCollection.AddSingleton<IProviderManager, ProviderManager>();
- SubtitleManager = new SubtitleManager(LoggerFactory, FileSystemManager, LibraryMonitor, MediaSourceManager, LocalizationManager);
- serviceCollection.AddSingleton(SubtitleManager);
+ // TODO: Refactor to eliminate the circular dependency here so that Lazy<T> isn't required
+ serviceCollection.AddTransient(provider => new Lazy<ILiveTvManager>(provider.GetRequiredService<ILiveTvManager>));
+ serviceCollection.AddSingleton<IDtoService, DtoService>();
- ProviderManager = new ProviderManager(HttpClient, SubtitleManager, ServerConfigurationManager, LibraryMonitor, LoggerFactory, FileSystemManager, ApplicationPaths, () => LibraryManager, JsonSerializer);
- serviceCollection.AddSingleton(ProviderManager);
+ serviceCollection.AddSingleton<IChannelManager, ChannelManager>();
- DtoService = new DtoService(LoggerFactory, LibraryManager, UserDataManager, ItemRepository, ImageProcessor, ProviderManager, this, () => MediaSourceManager, () => LiveTvManager);
- serviceCollection.AddSingleton(DtoService);
+ serviceCollection.AddSingleton<ISessionManager, SessionManager>();
- ChannelManager = new ChannelManager(UserManager, DtoService, LibraryManager, LoggerFactory, ServerConfigurationManager, FileSystemManager, UserDataManager, JsonSerializer, ProviderManager);
- serviceCollection.AddSingleton(ChannelManager);
+ serviceCollection.AddSingleton<IDlnaManager, DlnaManager>();
- SessionManager = new SessionManager(UserDataManager, LoggerFactory, LibraryManager, UserManager, musicManager, DtoService, ImageProcessor, this, AuthenticationRepository, DeviceManager, MediaSourceManager);
- serviceCollection.AddSingleton(SessionManager);
+ serviceCollection.AddSingleton<ICollectionManager, CollectionManager>();
- serviceCollection.AddSingleton<IDlnaManager>(
- new DlnaManager(XmlSerializer, FileSystemManager, ApplicationPaths, LoggerFactory, JsonSerializer, this));
+ serviceCollection.AddSingleton<IPlaylistManager, PlaylistManager>();
- CollectionManager = new CollectionManager(LibraryManager, ApplicationPaths, LocalizationManager, FileSystemManager, LibraryMonitor, LoggerFactory, ProviderManager);
- serviceCollection.AddSingleton(CollectionManager);
+ serviceCollection.AddSingleton<LiveTvDtoService>();
+ serviceCollection.AddSingleton<ILiveTvManager, LiveTvManager>();
- serviceCollection.AddSingleton(typeof(IPlaylistManager), typeof(PlaylistManager));
+ serviceCollection.AddSingleton<IUserViewManager, UserViewManager>();
- LiveTvManager = new LiveTvManager(this, ServerConfigurationManager, LoggerFactory, ItemRepository, ImageProcessor, UserDataManager, DtoService, UserManager, LibraryManager, TaskManager, LocalizationManager, JsonSerializer, FileSystemManager, () => ChannelManager);
- serviceCollection.AddSingleton(LiveTvManager);
+ serviceCollection.AddSingleton<INotificationManager, NotificationManager>();
- UserViewManager = new UserViewManager(LibraryManager, LocalizationManager, UserManager, ChannelManager, LiveTvManager, ServerConfigurationManager);
- serviceCollection.AddSingleton(UserViewManager);
+ serviceCollection.AddSingleton<IDeviceDiscovery, DeviceDiscovery>();
- NotificationManager = new NotificationManager(LoggerFactory, UserManager, ServerConfigurationManager);
- serviceCollection.AddSingleton(NotificationManager);
+ serviceCollection.AddSingleton<IChapterManager, ChapterManager>();
- serviceCollection.AddSingleton<IDeviceDiscovery>(new DeviceDiscovery(ServerConfigurationManager));
+ serviceCollection.AddSingleton<IEncodingManager, MediaEncoder.EncodingManager>();
- ChapterManager = new ChapterManager(LibraryManager, LoggerFactory, ServerConfigurationManager, ItemRepository);
- serviceCollection.AddSingleton(ChapterManager);
+ serviceCollection.AddSingleton<IActivityRepository, ActivityRepository>();
+ serviceCollection.AddSingleton<IActivityManager, ActivityManager>();
- MediaEncoder = new MediaBrowser.MediaEncoding.Encoder.MediaEncoder(
- LoggerFactory.CreateLogger<MediaBrowser.MediaEncoding.Encoder.MediaEncoder>(),
- ServerConfigurationManager,
- FileSystemManager,
- ProcessFactory,
- LocalizationManager,
- () => SubtitleEncoder,
- _configuration,
- StartupOptions.FFmpegPath);
- serviceCollection.AddSingleton(MediaEncoder);
+ serviceCollection.AddSingleton<IAuthorizationContext, AuthorizationContext>();
+ serviceCollection.AddSingleton<ISessionContext, SessionContext>();
- EncodingManager = new MediaEncoder.EncodingManager(FileSystemManager, LoggerFactory, MediaEncoder, ChapterManager, LibraryManager);
- serviceCollection.AddSingleton(EncodingManager);
+ serviceCollection.AddSingleton<IAuthService, AuthService>();
- var activityLogRepo = GetActivityLogRepository();
- serviceCollection.AddSingleton(activityLogRepo);
- serviceCollection.AddSingleton<IActivityManager>(new ActivityManager(LoggerFactory, activityLogRepo, UserManager));
+ serviceCollection.AddSingleton<ISubtitleEncoder, MediaBrowser.MediaEncoding.Subtitles.SubtitleEncoder>();
- var authContext = new AuthorizationContext(AuthenticationRepository, UserManager);
- serviceCollection.AddSingleton<IAuthorizationContext>(authContext);
- serviceCollection.AddSingleton<ISessionContext>(new SessionContext(UserManager, authContext, SessionManager));
-
- AuthService = new AuthService(LoggerFactory.CreateLogger<AuthService>(), authContext, ServerConfigurationManager, SessionManager, NetworkManager);
- serviceCollection.AddSingleton(AuthService);
-
- SubtitleEncoder = new MediaBrowser.MediaEncoding.Subtitles.SubtitleEncoder(
- LibraryManager,
- LoggerFactory.CreateLogger<MediaBrowser.MediaEncoding.Subtitles.SubtitleEncoder>(),
- ApplicationPaths,
- FileSystemManager,
- MediaEncoder,
- HttpClient,
- MediaSourceManager,
- ProcessFactory);
- serviceCollection.AddSingleton(SubtitleEncoder);
-
- serviceCollection.AddSingleton(typeof(IResourceFileManager), typeof(ResourceFileManager));
+ serviceCollection.AddSingleton<IResourceFileManager, ResourceFileManager>();
serviceCollection.AddSingleton<EncodingHelper>();
- serviceCollection.AddSingleton(typeof(IAttachmentExtractor), typeof(MediaBrowser.MediaEncoding.Attachments.AttachmentExtractor));
+ serviceCollection.AddSingleton<IAttachmentExtractor, MediaBrowser.MediaEncoding.Attachments.AttachmentExtractor>();
+ }
+
+ /// <summary>
+ /// Create services registered with the service container that need to be initialized at application startup.
+ /// </summary>
+ /// <returns>A task representing the service initialization operation.</returns>
+ public async Task InitializeServices()
+ {
+ var localizationManager = (LocalizationManager)Resolve<ILocalizationManager>();
+ await localizationManager.LoadAll().ConfigureAwait(false);
- _displayPreferencesRepository.Initialize();
+ _mediaEncoder = Resolve<IMediaEncoder>();
+ _sessionManager = Resolve<ISessionManager>();
+ _httpServer = Resolve<IHttpServer>();
+ _httpClient = Resolve<IHttpClient>();
- var userDataRepo = new SqliteUserDataRepository(LoggerFactory.CreateLogger<SqliteUserDataRepository>(), ApplicationPaths);
+ ((SqliteDisplayPreferencesRepository)Resolve<IDisplayPreferencesRepository>()).Initialize();
+ ((AuthenticationRepository)Resolve<IAuthenticationRepository>()).Initialize();
+ ((SqliteUserRepository)Resolve<IUserRepository>()).Initialize();
+ ((ActivityRepository)Resolve<IActivityRepository>()).Initialize();
SetStaticProperties();
- ((UserManager)UserManager).Initialize();
+ var userManager = (UserManager)Resolve<IUserManager>();
+ userManager.Initialize();
- ((UserDataManager)UserDataManager).Repository = userDataRepo;
- ItemRepository.Initialize(userDataRepo, UserManager);
- ((LibraryManager)LibraryManager).ItemRepository = ItemRepository;
+ var userDataRepo = (SqliteUserDataRepository)Resolve<IUserDataRepository>();
+ ((SqliteItemRepository)Resolve<IItemRepository>()).Initialize(userDataRepo, userManager);
+
+ FindParts();
}
public static void LogEnvironmentInfo(ILogger logger, IApplicationPaths appPaths)
@@ -868,6 +692,18 @@ namespace Emby.Server.Implementations
.GetCommandLineArgs()
.Distinct();
+ // Get all relevant environment variables
+ var allEnvVars = Environment.GetEnvironmentVariables();
+ var relevantEnvVars = new Dictionary<object, object>();
+ foreach (var key in allEnvVars.Keys)
+ {
+ if (_relevantEnvVarPrefixes.Any(prefix => key.ToString().StartsWith(prefix, StringComparison.OrdinalIgnoreCase)))
+ {
+ relevantEnvVars.Add(key, allEnvVars[key]);
+ }
+ }
+
+ logger.LogInformation("Environment Variables: {EnvVars}", relevantEnvVars);
logger.LogInformation("Arguments: {Args}", commandLineArgs);
logger.LogInformation("Operating system: {OS}", OperatingSystem.Name);
logger.LogInformation("Architecture: {Architecture}", RuntimeInformation.OSArchitecture);
@@ -916,110 +752,37 @@ namespace Emby.Server.Implementations
}
/// <summary>
- /// Gets the user repository.
- /// </summary>
- /// <returns><see cref="Task{SqliteUserRepository}" />.</returns>
- private SqliteUserRepository GetUserRepository()
- {
- var repo = new SqliteUserRepository(
- LoggerFactory.CreateLogger<SqliteUserRepository>(),
- ApplicationPaths);
-
- repo.Initialize();
-
- return repo;
- }
-
- private IAuthenticationRepository GetAuthenticationRepository()
- {
- var repo = new AuthenticationRepository(LoggerFactory, ServerConfigurationManager);
-
- repo.Initialize();
-
- return repo;
- }
-
- private IActivityRepository GetActivityLogRepository()
- {
- var repo = new ActivityRepository(LoggerFactory, ServerConfigurationManager.ApplicationPaths, FileSystemManager);
-
- repo.Initialize();
-
- return repo;
- }
-
- /// <summary>
/// Dirty hacks.
/// </summary>
private void SetStaticProperties()
{
- ItemRepository.ImageProcessor = ImageProcessor;
-
// For now there's no real way to inject these properly
- BaseItem.Logger = LoggerFactory.CreateLogger("BaseItem");
+ BaseItem.Logger = Resolve<ILogger<BaseItem>>();
BaseItem.ConfigurationManager = ServerConfigurationManager;
- BaseItem.LibraryManager = LibraryManager;
- BaseItem.ProviderManager = ProviderManager;
- BaseItem.LocalizationManager = LocalizationManager;
- BaseItem.ItemRepository = ItemRepository;
- User.UserManager = UserManager;
- BaseItem.FileSystem = FileSystemManager;
- BaseItem.UserDataManager = UserDataManager;
- BaseItem.ChannelManager = ChannelManager;
- Video.LiveTvManager = LiveTvManager;
- Folder.UserViewManager = UserViewManager;
- UserView.TVSeriesManager = TVSeriesManager;
- UserView.CollectionManager = CollectionManager;
- BaseItem.MediaSourceManager = MediaSourceManager;
- CollectionFolder.XmlSerializer = XmlSerializer;
- CollectionFolder.JsonSerializer = JsonSerializer;
+ BaseItem.LibraryManager = Resolve<ILibraryManager>();
+ BaseItem.ProviderManager = Resolve<IProviderManager>();
+ BaseItem.LocalizationManager = Resolve<ILocalizationManager>();
+ BaseItem.ItemRepository = Resolve<IItemRepository>();
+ User.UserManager = Resolve<IUserManager>();
+ BaseItem.FileSystem = _fileSystemManager;
+ BaseItem.UserDataManager = Resolve<IUserDataManager>();
+ BaseItem.ChannelManager = Resolve<IChannelManager>();
+ Video.LiveTvManager = Resolve<ILiveTvManager>();
+ Folder.UserViewManager = Resolve<IUserViewManager>();
+ UserView.TVSeriesManager = Resolve<ITVSeriesManager>();
+ UserView.CollectionManager = Resolve<ICollectionManager>();
+ BaseItem.MediaSourceManager = Resolve<IMediaSourceManager>();
+ CollectionFolder.XmlSerializer = _xmlSerializer;
+ CollectionFolder.JsonSerializer = Resolve<IJsonSerializer>();
CollectionFolder.ApplicationHost = this;
- AuthenticatedAttribute.AuthService = AuthService;
- }
-
- private async void PluginInstalled(object sender, GenericEventArgs<PackageVersionInfo> args)
- {
- string dir = Path.Combine(ApplicationPaths.PluginsPath, args.Argument.name);
- var types = Directory.EnumerateFiles(dir, "*.dll", SearchOption.AllDirectories)
- .Select(x => Assembly.LoadFrom(x))
- .SelectMany(x => x.ExportedTypes)
- .Where(x => x.IsClass && !x.IsAbstract && !x.IsInterface && !x.IsGenericType)
- .ToArray();
-
- int oldLen = _allConcreteTypes.Length;
- Array.Resize(ref _allConcreteTypes, oldLen + types.Length);
- types.CopyTo(_allConcreteTypes, oldLen);
-
- var plugins = types.Where(x => x.IsAssignableFrom(typeof(IPlugin)))
- .Select(CreateInstanceSafe)
- .Where(x => x != null)
- .Cast<IPlugin>()
- .Select(LoadPlugin)
- .Where(x => x != null)
- .ToArray();
-
- oldLen = _plugins.Length;
- Array.Resize(ref _plugins, oldLen + plugins.Length);
- plugins.CopyTo(_plugins, oldLen);
-
- var entries = types.Where(x => x.IsAssignableFrom(typeof(IServerEntryPoint)))
- .Select(CreateInstanceSafe)
- .Where(x => x != null)
- .Cast<IServerEntryPoint>()
- .ToList();
-
- await Task.WhenAll(StartEntryPoints(entries, true)).ConfigureAwait(false);
- await Task.WhenAll(StartEntryPoints(entries, false)).ConfigureAwait(false);
+ AuthenticatedAttribute.AuthService = Resolve<IAuthService>();
}
/// <summary>
- /// Finds the parts.
+ /// Finds plugin components and register them with the appropriate services.
/// </summary>
- public void FindParts()
+ private void FindParts()
{
- InstallationManager = ServiceProvider.GetService<IInstallationManager>();
- InstallationManager.PluginInstalled += PluginInstalled;
-
if (!ServerConfigurationManager.Configuration.IsPortAuthorized)
{
ServerConfigurationManager.Configuration.IsPortAuthorized = true;
@@ -1032,36 +795,34 @@ namespace Emby.Server.Implementations
.Where(i => i != null)
.ToArray();
- HttpServer.Init(GetExports<IService>(false), GetExports<IWebSocketListener>(), GetUrlPrefixes());
+ _httpServer.Init(GetExportTypes<IService>(), GetExports<IWebSocketListener>(), GetUrlPrefixes());
- LibraryManager.AddParts(
+ Resolve<ILibraryManager>().AddParts(
GetExports<IResolverIgnoreRule>(),
GetExports<IItemResolver>(),
GetExports<IIntroProvider>(),
GetExports<IBaseItemComparer>(),
GetExports<ILibraryPostScanTask>());
- ProviderManager.AddParts(
+ Resolve<IProviderManager>().AddParts(
GetExports<IImageProvider>(),
GetExports<IMetadataService>(),
GetExports<IMetadataProvider>(),
GetExports<IMetadataSaver>(),
GetExports<IExternalId>());
- ImageProcessor.ImageEnhancers = GetExports<IImageEnhancer>();
-
- LiveTvManager.AddParts(GetExports<ILiveTvService>(), GetExports<ITunerHost>(), GetExports<IListingsProvider>());
+ Resolve<ILiveTvManager>().AddParts(GetExports<ILiveTvService>(), GetExports<ITunerHost>(), GetExports<IListingsProvider>());
- SubtitleManager.AddParts(GetExports<ISubtitleProvider>());
+ Resolve<ISubtitleManager>().AddParts(GetExports<ISubtitleProvider>());
- ChannelManager.AddParts(GetExports<IChannel>());
+ Resolve<IChannelManager>().AddParts(GetExports<IChannel>());
- MediaSourceManager.AddParts(GetExports<IMediaSourceProvider>());
+ Resolve<IMediaSourceManager>().AddParts(GetExports<IMediaSourceProvider>());
- NotificationManager.AddParts(GetExports<INotificationService>(), GetExports<INotificationTypeFactory>());
- UserManager.AddParts(GetExports<IAuthenticationProvider>(), GetExports<IPasswordResetProvider>());
+ Resolve<INotificationManager>().AddParts(GetExports<INotificationService>(), GetExports<INotificationTypeFactory>());
+ Resolve<IUserManager>().AddParts(GetExports<IAuthenticationProvider>(), GetExports<IPasswordResetProvider>());
- IsoManager.AddParts(GetExports<IIsoMounter>());
+ Resolve<IIsoManager>().AddParts(GetExports<IIsoMounter>());
}
private IPlugin LoadPlugin(IPlugin plugin)
@@ -1128,7 +889,7 @@ namespace Emby.Server.Implementations
{
exportedTypes = ass.GetExportedTypes();
}
- catch (TypeLoadException ex)
+ catch (FileNotFoundException ex)
{
Logger.LogError(ex, "Error getting exported types from {Assembly}", ass.FullName);
continue;
@@ -1168,16 +929,6 @@ namespace Emby.Server.Implementations
});
}
- private CertificateInfo GetCertificateInfo(bool generateCertificate)
- {
- // Custom cert
- return new CertificateInfo
- {
- Path = ServerConfigurationManager.Configuration.CertificatePath,
- Password = ServerConfigurationManager.Configuration.CertificatePassword
- };
- }
-
/// <summary>
/// Called when [configuration updated].
/// </summary>
@@ -1204,14 +955,13 @@ namespace Emby.Server.Implementations
}
}
- if (!HttpServer.UrlPrefixes.SequenceEqual(GetUrlPrefixes(), StringComparer.OrdinalIgnoreCase))
+ if (!_httpServer.UrlPrefixes.SequenceEqual(GetUrlPrefixes(), StringComparer.OrdinalIgnoreCase))
{
requiresRestart = true;
}
var currentCertPath = CertificateInfo?.Path;
- var newCertInfo = GetCertificateInfo(false);
- var newCertPath = newCertInfo?.Path;
+ var newCertPath = ServerConfigurationManager.Configuration.CertificatePath;
if (!string.Equals(currentCertPath, newCertPath, StringComparison.OrdinalIgnoreCase))
{
@@ -1264,7 +1014,7 @@ namespace Emby.Server.Implementations
{
try
{
- await SessionManager.SendServerRestartNotification(CancellationToken.None).ConfigureAwait(false);
+ await _sessionManager.SendServerRestartNotification(CancellationToken.None).ConfigureAwait(false);
}
catch (Exception ex)
{
@@ -1368,7 +1118,7 @@ namespace Emby.Server.Implementations
IsShuttingDown = IsShuttingDown,
Version = ApplicationVersionString,
WebSocketPortNumber = HttpPort,
- CompletedInstallations = InstallationManager.CompletedInstallations.ToArray(),
+ CompletedInstallations = Resolve<IInstallationManager>().CompletedInstallations.ToArray(),
Id = SystemId,
ProgramDataPath = ApplicationPaths.ProgramDataPath,
WebPath = ApplicationPaths.WebPath,
@@ -1388,15 +1138,14 @@ namespace Emby.Server.Implementations
ServerName = FriendlyName,
LocalAddress = localAddress,
SupportsLibraryMonitor = true,
- EncoderLocation = MediaEncoder.EncoderLocation,
+ EncoderLocation = _mediaEncoder.EncoderLocation,
SystemArchitecture = RuntimeInformation.OSArchitecture,
- SystemUpdateLevel = SystemUpdateLevel,
- PackageName = StartupOptions.PackageName
+ PackageName = _startupOptions.PackageName
};
}
public IEnumerable<WakeOnLanInfo> GetWakeOnLanInfo()
- => NetworkManager.GetMacAddresses()
+ => _networkManager.GetMacAddresses()
.Select(i => new WakeOnLanInfo(i))
.ToList();
@@ -1419,7 +1168,7 @@ namespace Emby.Server.Implementations
public bool SupportsHttps => Certificate != null || ServerConfigurationManager.Configuration.IsBehindProxy;
- public async Task<string> GetLocalApiUrl(CancellationToken cancellationToken)
+ public async Task<string> GetLocalApiUrl(CancellationToken cancellationToken, bool forceHttp = false)
{
try
{
@@ -1428,7 +1177,7 @@ namespace Emby.Server.Implementations
foreach (var address in addresses)
{
- return GetLocalApiUrl(address);
+ return GetLocalApiUrl(address, forceHttp);
}
return null;
@@ -1458,7 +1207,7 @@ namespace Emby.Server.Implementations
}
/// <inheritdoc />
- public string GetLocalApiUrl(IPAddress ipAddress)
+ public string GetLocalApiUrl(IPAddress ipAddress, bool forceHttp = false)
{
if (ipAddress.AddressFamily == AddressFamily.InterNetworkV6)
{
@@ -1468,33 +1217,26 @@ namespace Emby.Server.Implementations
str.CopyTo(span.Slice(1));
span[^1] = ']';
- return GetLocalApiUrl(span);
+ return GetLocalApiUrl(span, forceHttp);
}
- return GetLocalApiUrl(ipAddress.ToString());
+ return GetLocalApiUrl(ipAddress.ToString(), forceHttp);
}
/// <inheritdoc />
- public string GetLocalApiUrl(ReadOnlySpan<char> host)
+ public string GetLocalApiUrl(ReadOnlySpan<char> host, bool forceHttp = false)
{
var url = new StringBuilder(64);
- if (EnableHttps)
- {
- url.Append("https://");
- }
- else
- {
- url.Append("http://");
- }
-
- url.Append(host)
+ bool useHttps = EnableHttps && !forceHttp;
+ url.Append(useHttps ? "https://" : "http://")
+ .Append(host)
.Append(':')
- .Append(HttpPort);
+ .Append(useHttps ? HttpsPort : HttpPort);
string baseUrl = ServerConfigurationManager.Configuration.BaseUrl;
if (baseUrl.Length != 0)
{
- url.Append('/').Append(baseUrl);
+ url.Append(baseUrl);
}
return url.ToString();
@@ -1516,7 +1258,7 @@ namespace Emby.Server.Implementations
if (addresses.Count == 0)
{
- addresses.AddRange(NetworkManager.GetLocalIpAddresses(ServerConfigurationManager.Configuration.IgnoreVirtualInterfaces));
+ addresses.AddRange(_networkManager.GetLocalIpAddresses(ServerConfigurationManager.Configuration.IgnoreVirtualInterfaces));
}
var resultList = new List<IPAddress>();
@@ -1583,7 +1325,7 @@ namespace Emby.Server.Implementations
try
{
- using (var response = await HttpClient.SendAsync(
+ using (var response = await _httpClient.SendAsync(
new HttpRequestOptions
{
Url = apiUrl,
@@ -1636,7 +1378,7 @@ namespace Emby.Server.Implementations
try
{
- await SessionManager.SendServerShutdownNotification(CancellationToken.None).ConfigureAwait(false);
+ await _sessionManager.SendServerShutdownNotification(CancellationToken.None).ConfigureAwait(false);
}
catch (Exception ex)
{
@@ -1679,29 +1421,6 @@ namespace Emby.Server.Implementations
_plugins = list.ToArray();
}
- /// <summary>
- /// This returns localhost in the case of no external dns, and the hostname if the
- /// dns is prefixed with a valid Uri prefix.
- /// </summary>
- /// <param name="externalDns">The external dns prefix to get the hostname of.</param>
- /// <returns>The hostname in <paramref name="externalDns"/>.</returns>
- private static string GetHostnameFromExternalDns(string externalDns)
- {
- if (string.IsNullOrEmpty(externalDns))
- {
- return "localhost";
- }
-
- try
- {
- return new Uri(externalDns).Host;
- }
- catch
- {
- return externalDns;
- }
- }
-
public virtual void LaunchUrl(string url)
{
if (!CanLaunchWebBrowser)
@@ -1709,15 +1428,17 @@ namespace Emby.Server.Implementations
throw new NotSupportedException();
}
- var process = ProcessFactory.Create(new ProcessOptions
+ var process = new Process
{
- FileName = url,
- EnableRaisingEvents = true,
- UseShellExecute = true,
- ErrorDialog = false
- });
-
- process.Exited += ProcessExited;
+ StartInfo = new ProcessStartInfo
+ {
+ FileName = url,
+ UseShellExecute = true,
+ ErrorDialog = false
+ },
+ EnableRaisingEvents = true
+ };
+ process.Exited += (sender, args) => ((Process)sender).Dispose();
try
{
@@ -1730,11 +1451,6 @@ namespace Emby.Server.Implementations
}
}
- private static void ProcessExited(object sender, EventArgs e)
- {
- ((IProcess)sender).Dispose();
- }
-
public virtual void EnableLoopback(string appName)
{
}
@@ -1783,14 +1499,8 @@ namespace Emby.Server.Implementations
Logger.LogError(ex, "Error disposing {Type}", part.GetType().Name);
}
}
-
- _userRepository?.Dispose();
- _displayPreferencesRepository.Dispose();
}
- _userRepository = null;
- _displayPreferencesRepository = null;
-
_disposed = true;
}
}
diff --git a/Emby.Server.Implementations/Archiving/ZipClient.cs b/Emby.Server.Implementations/Archiving/ZipClient.cs
index 4a6e5cfd7..591ae547d 100644
--- a/Emby.Server.Implementations/Archiving/ZipClient.cs
+++ b/Emby.Server.Implementations/Archiving/ZipClient.cs
@@ -22,10 +22,8 @@ namespace Emby.Server.Implementations.Archiving
/// <param name="overwriteExistingFiles">if set to <c>true</c> [overwrite existing files].</param>
public void ExtractAll(string sourceFile, string targetPath, bool overwriteExistingFiles)
{
- using (var fileStream = File.OpenRead(sourceFile))
- {
- ExtractAll(fileStream, targetPath, overwriteExistingFiles);
- }
+ using var fileStream = File.OpenRead(sourceFile);
+ ExtractAll(fileStream, targetPath, overwriteExistingFiles);
}
/// <summary>
@@ -36,67 +34,61 @@ namespace Emby.Server.Implementations.Archiving
/// <param name="overwriteExistingFiles">if set to <c>true</c> [overwrite existing files].</param>
public void ExtractAll(Stream source, string targetPath, bool overwriteExistingFiles)
{
- using (var reader = ReaderFactory.Open(source))
+ using var reader = ReaderFactory.Open(source);
+ var options = new ExtractionOptions
{
- var options = new ExtractionOptions();
- options.ExtractFullPath = true;
-
- if (overwriteExistingFiles)
- {
- options.Overwrite = true;
- }
+ ExtractFullPath = true
+ };
- reader.WriteAllToDirectory(targetPath, options);
+ if (overwriteExistingFiles)
+ {
+ options.Overwrite = true;
}
+
+ reader.WriteAllToDirectory(targetPath, options);
}
+ /// <inheritdoc />
public void ExtractAllFromZip(Stream source, string targetPath, bool overwriteExistingFiles)
{
- using (var reader = ZipReader.Open(source))
+ using var reader = ZipReader.Open(source);
+ var options = new ExtractionOptions
{
- var options = new ExtractionOptions();
- options.ExtractFullPath = true;
+ ExtractFullPath = true,
+ Overwrite = overwriteExistingFiles
+ };
- if (overwriteExistingFiles)
- {
- options.Overwrite = true;
- }
-
- reader.WriteAllToDirectory(targetPath, options);
- }
+ reader.WriteAllToDirectory(targetPath, options);
}
+ /// <inheritdoc />
public void ExtractAllFromGz(Stream source, string targetPath, bool overwriteExistingFiles)
{
- using (var reader = GZipReader.Open(source))
+ using var reader = GZipReader.Open(source);
+ var options = new ExtractionOptions
{
- var options = new ExtractionOptions();
- options.ExtractFullPath = true;
+ ExtractFullPath = true,
+ Overwrite = overwriteExistingFiles
+ };
- if (overwriteExistingFiles)
- {
- options.Overwrite = true;
- }
-
- reader.WriteAllToDirectory(targetPath, options);
- }
+ reader.WriteAllToDirectory(targetPath, options);
}
+ /// <inheritdoc />
public void ExtractFirstFileFromGz(Stream source, string targetPath, string defaultFileName)
{
- using (var reader = GZipReader.Open(source))
+ using var reader = GZipReader.Open(source);
+ if (reader.MoveToNextEntry())
{
- if (reader.MoveToNextEntry())
+ var entry = reader.Entry;
+
+ var filename = entry.Key;
+ if (string.IsNullOrWhiteSpace(filename))
{
- var entry = reader.Entry;
-
- var filename = entry.Key;
- if (string.IsNullOrWhiteSpace(filename))
- {
- filename = defaultFileName;
- }
- reader.WriteEntryToFile(Path.Combine(targetPath, filename));
+ filename = defaultFileName;
}
+
+ reader.WriteEntryToFile(Path.Combine(targetPath, filename));
}
}
@@ -108,10 +100,8 @@ namespace Emby.Server.Implementations.Archiving
/// <param name="overwriteExistingFiles">if set to <c>true</c> [overwrite existing files].</param>
public void ExtractAllFrom7z(string sourceFile, string targetPath, bool overwriteExistingFiles)
{
- using (var fileStream = File.OpenRead(sourceFile))
- {
- ExtractAllFrom7z(fileStream, targetPath, overwriteExistingFiles);
- }
+ using var fileStream = File.OpenRead(sourceFile);
+ ExtractAllFrom7z(fileStream, targetPath, overwriteExistingFiles);
}
/// <summary>
@@ -122,21 +112,15 @@ namespace Emby.Server.Implementations.Archiving
/// <param name="overwriteExistingFiles">if set to <c>true</c> [overwrite existing files].</param>
public void ExtractAllFrom7z(Stream source, string targetPath, bool overwriteExistingFiles)
{
- using (var archive = SevenZipArchive.Open(source))
+ using var archive = SevenZipArchive.Open(source);
+ using var reader = archive.ExtractAllEntries();
+ var options = new ExtractionOptions
{
- using (var reader = archive.ExtractAllEntries())
- {
- var options = new ExtractionOptions();
- options.ExtractFullPath = true;
-
- if (overwriteExistingFiles)
- {
- options.Overwrite = true;
- }
+ ExtractFullPath = true,
+ Overwrite = overwriteExistingFiles
+ };
- reader.WriteAllToDirectory(targetPath, options);
- }
- }
+ reader.WriteAllToDirectory(targetPath, options);
}
/// <summary>
@@ -147,10 +131,8 @@ namespace Emby.Server.Implementations.Archiving
/// <param name="overwriteExistingFiles">if set to <c>true</c> [overwrite existing files].</param>
public void ExtractAllFromTar(string sourceFile, string targetPath, bool overwriteExistingFiles)
{
- using (var fileStream = File.OpenRead(sourceFile))
- {
- ExtractAllFromTar(fileStream, targetPath, overwriteExistingFiles);
- }
+ using var fileStream = File.OpenRead(sourceFile);
+ ExtractAllFromTar(fileStream, targetPath, overwriteExistingFiles);
}
/// <summary>
@@ -161,21 +143,15 @@ namespace Emby.Server.Implementations.Archiving
/// <param name="overwriteExistingFiles">if set to <c>true</c> [overwrite existing files].</param>
public void ExtractAllFromTar(Stream source, string targetPath, bool overwriteExistingFiles)
{
- using (var archive = TarArchive.Open(source))
+ using var archive = TarArchive.Open(source);
+ using var reader = archive.ExtractAllEntries();
+ var options = new ExtractionOptions
{
- using (var reader = archive.ExtractAllEntries())
- {
- var options = new ExtractionOptions();
- options.ExtractFullPath = true;
+ ExtractFullPath = true,
+ Overwrite = overwriteExistingFiles
+ };
- if (overwriteExistingFiles)
- {
- options.Overwrite = true;
- }
-
- reader.WriteAllToDirectory(targetPath, options);
- }
- }
+ reader.WriteAllToDirectory(targetPath, options);
}
}
}
diff --git a/Emby.Server.Implementations/Branding/BrandingConfigurationFactory.cs b/Emby.Server.Implementations/Branding/BrandingConfigurationFactory.cs
index 15aee63a0..7ae26bd8b 100644
--- a/Emby.Server.Implementations/Branding/BrandingConfigurationFactory.cs
+++ b/Emby.Server.Implementations/Branding/BrandingConfigurationFactory.cs
@@ -1,14 +1,15 @@
-#pragma warning disable CS1591
-#pragma warning disable SA1600
-
using System.Collections.Generic;
using MediaBrowser.Common.Configuration;
using MediaBrowser.Model.Branding;
namespace Emby.Server.Implementations.Branding
{
+ /// <summary>
+ /// A configuration factory for <see cref="BrandingOptions"/>.
+ /// </summary>
public class BrandingConfigurationFactory : IConfigurationFactory
{
+ /// <inheritdoc />
public IEnumerable<ConfigurationStore> GetConfigurations()
{
return new[]
diff --git a/Emby.Server.Implementations/Browser/BrowserLauncher.cs b/Emby.Server.Implementations/Browser/BrowserLauncher.cs
index f5da0d018..96096e142 100644
--- a/Emby.Server.Implementations/Browser/BrowserLauncher.cs
+++ b/Emby.Server.Implementations/Browser/BrowserLauncher.cs
@@ -1,51 +1,48 @@
using System;
using MediaBrowser.Controller;
+using Microsoft.Extensions.Logging;
namespace Emby.Server.Implementations.Browser
{
/// <summary>
- /// Class BrowserLauncher.
+ /// Assists in opening application URLs in an external browser.
/// </summary>
public static class BrowserLauncher
{
/// <summary>
- /// Opens the dashboard page.
+ /// Opens the home page of the web client.
/// </summary>
- /// <param name="page">The page.</param>
/// <param name="appHost">The app host.</param>
- private static void OpenDashboardPage(string page, IServerApplicationHost appHost)
+ public static void OpenWebApp(IServerApplicationHost appHost)
{
- var url = appHost.GetLocalApiUrl("localhost") + "/web/" + page;
-
- OpenUrl(appHost, url);
+ TryOpenUrl(appHost, "/web/index.html");
}
/// <summary>
- /// Opens the web client.
+ /// Opens the swagger API page.
/// </summary>
/// <param name="appHost">The app host.</param>
- public static void OpenWebApp(IServerApplicationHost appHost)
+ public static void OpenSwaggerPage(IServerApplicationHost appHost)
{
- OpenDashboardPage("index.html", appHost);
+ TryOpenUrl(appHost, "/swagger/index.html");
}
/// <summary>
- /// Opens the URL.
+ /// Opens the specified URL in an external browser window. Any exceptions will be logged, but ignored.
/// </summary>
- /// <param name="appHost">The application host instance.</param>
+ /// <param name="appHost">The application host.</param>
/// <param name="url">The URL.</param>
- private static void OpenUrl(IServerApplicationHost appHost, string url)
+ private static void TryOpenUrl(IServerApplicationHost appHost, string url)
{
try
{
- appHost.LaunchUrl(url);
- }
- catch (NotSupportedException)
- {
-
+ string baseUrl = appHost.GetLocalApiUrl("localhost");
+ appHost.LaunchUrl(baseUrl + url);
}
- catch (Exception)
+ catch (Exception ex)
{
+ var logger = appHost.Resolve<ILogger>();
+ logger?.LogError(ex, "Failed to open browser window with URL {URL}", url);
}
}
}
diff --git a/Emby.Server.Implementations/Channels/ChannelDynamicMediaSourceProvider.cs b/Emby.Server.Implementations/Channels/ChannelDynamicMediaSourceProvider.cs
index aae416b37..3e149cc82 100644
--- a/Emby.Server.Implementations/Channels/ChannelDynamicMediaSourceProvider.cs
+++ b/Emby.Server.Implementations/Channels/ChannelDynamicMediaSourceProvider.cs
@@ -1,8 +1,6 @@
-#pragma warning disable CS1591
-#pragma warning disable SA1600
-
using System;
using System.Collections.Generic;
+using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using MediaBrowser.Controller.Channels;
@@ -12,6 +10,9 @@ using MediaBrowser.Model.Dto;
namespace Emby.Server.Implementations.Channels
{
+ /// <summary>
+ /// A media source provider for channels.
+ /// </summary>
public class ChannelDynamicMediaSourceProvider : IMediaSourceProvider
{
private readonly ChannelManager _channelManager;
@@ -28,12 +29,9 @@ namespace Emby.Server.Implementations.Channels
/// <inheritdoc />
public Task<IEnumerable<MediaSourceInfo>> GetMediaSources(BaseItem item, CancellationToken cancellationToken)
{
- if (item.SourceType == SourceType.Channel)
- {
- return _channelManager.GetDynamicMediaSources(item, cancellationToken);
- }
-
- return Task.FromResult<IEnumerable<MediaSourceInfo>>(new List<MediaSourceInfo>());
+ return item.SourceType == SourceType.Channel
+ ? _channelManager.GetDynamicMediaSources(item, cancellationToken)
+ : Task.FromResult(Enumerable.Empty<MediaSourceInfo>());
}
/// <inheritdoc />
diff --git a/Emby.Server.Implementations/Channels/ChannelImageProvider.cs b/Emby.Server.Implementations/Channels/ChannelImageProvider.cs
index fe64f1b15..25cbfcf14 100644
--- a/Emby.Server.Implementations/Channels/ChannelImageProvider.cs
+++ b/Emby.Server.Implementations/Channels/ChannelImageProvider.cs
@@ -1,6 +1,3 @@
-#pragma warning disable CS1591
-#pragma warning disable SA1600
-
using System.Collections.Generic;
using System.Linq;
using System.Threading;
@@ -12,20 +9,32 @@ using MediaBrowser.Model.Entities;
namespace Emby.Server.Implementations.Channels
{
+ /// <summary>
+ /// An image provider for channels.
+ /// </summary>
public class ChannelImageProvider : IDynamicImageProvider, IHasItemChangeMonitor
{
private readonly IChannelManager _channelManager;
+ /// <summary>
+ /// Initializes a new instance of the <see cref="ChannelImageProvider"/> class.
+ /// </summary>
+ /// <param name="channelManager">The channel manager.</param>
public ChannelImageProvider(IChannelManager channelManager)
{
_channelManager = channelManager;
}
+ /// <inheritdoc />
+ public string Name => "Channel Image Provider";
+
+ /// <inheritdoc />
public IEnumerable<ImageType> GetSupportedImages(BaseItem item)
{
return GetChannel(item).GetSupportedChannelImages();
}
+ /// <inheritdoc />
public Task<DynamicImageResponse> GetImage(BaseItem item, ImageType type, CancellationToken cancellationToken)
{
var channel = GetChannel(item);
@@ -33,8 +42,7 @@ namespace Emby.Server.Implementations.Channels
return channel.GetChannelImage(type, cancellationToken);
}
- public string Name => "Channel Image Provider";
-
+ /// <inheritdoc />
public bool Supports(BaseItem item)
{
return item is Channel;
@@ -47,6 +55,7 @@ namespace Emby.Server.Implementations.Channels
return ((ChannelManager)_channelManager).GetChannelProvider(channel);
}
+ /// <inheritdoc />
public bool HasChanged(BaseItem item, IDirectoryService directoryService)
{
return GetSupportedImages(item).Any(i => !item.HasImage(i));
diff --git a/Emby.Server.Implementations/Channels/ChannelManager.cs b/Emby.Server.Implementations/Channels/ChannelManager.cs
index de2e123af..138832fb8 100644
--- a/Emby.Server.Implementations/Channels/ChannelManager.cs
+++ b/Emby.Server.Implementations/Channels/ChannelManager.cs
@@ -1,6 +1,3 @@
-#pragma warning disable CS1591
-#pragma warning disable SA1600
-
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
@@ -30,10 +27,11 @@ using Microsoft.Extensions.Logging;
namespace Emby.Server.Implementations.Channels
{
+ /// <summary>
+ /// The LiveTV channel manager.
+ /// </summary>
public class ChannelManager : IChannelManager
{
- internal IChannel[] Channels { get; private set; }
-
private readonly IUserManager _userManager;
private readonly IUserDataManager _userDataManager;
private readonly IDtoService _dtoService;
@@ -44,11 +42,28 @@ namespace Emby.Server.Implementations.Channels
private readonly IJsonSerializer _jsonSerializer;
private readonly IProviderManager _providerManager;
+ private readonly ConcurrentDictionary<string, Tuple<DateTime, List<MediaSourceInfo>>> _channelItemMediaInfo =
+ new ConcurrentDictionary<string, Tuple<DateTime, List<MediaSourceInfo>>>();
+
+ private readonly SemaphoreSlim _resourcePool = new SemaphoreSlim(1, 1);
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="ChannelManager"/> class.
+ /// </summary>
+ /// <param name="userManager">The user manager.</param>
+ /// <param name="dtoService">The dto service.</param>
+ /// <param name="libraryManager">The library manager.</param>
+ /// <param name="loggerFactory">The logger factory.</param>
+ /// <param name="config">The server configuration manager.</param>
+ /// <param name="fileSystem">The filesystem.</param>
+ /// <param name="userDataManager">The user data manager.</param>
+ /// <param name="jsonSerializer">The JSON serializer.</param>
+ /// <param name="providerManager">The provider manager.</param>
public ChannelManager(
IUserManager userManager,
IDtoService dtoService,
ILibraryManager libraryManager,
- ILoggerFactory loggerFactory,
+ ILogger<ChannelManager> logger,
IServerConfigurationManager config,
IFileSystem fileSystem,
IUserDataManager userDataManager,
@@ -58,7 +73,7 @@ namespace Emby.Server.Implementations.Channels
_userManager = userManager;
_dtoService = dtoService;
_libraryManager = libraryManager;
- _logger = loggerFactory.CreateLogger(nameof(ChannelManager));
+ _logger = logger;
_config = config;
_fileSystem = fileSystem;
_userDataManager = userDataManager;
@@ -66,13 +81,17 @@ namespace Emby.Server.Implementations.Channels
_providerManager = providerManager;
}
+ internal IChannel[] Channels { get; private set; }
+
private static TimeSpan CacheLength => TimeSpan.FromHours(3);
+ /// <inheritdoc />
public void AddParts(IEnumerable<IChannel> channels)
{
Channels = channels.ToArray();
}
+ /// <inheritdoc />
public bool EnableMediaSourceDisplay(BaseItem item)
{
var internalChannel = _libraryManager.GetItemById(item.ChannelId);
@@ -81,15 +100,16 @@ namespace Emby.Server.Implementations.Channels
return !(channel is IDisableMediaSourceDisplay);
}
+ /// <inheritdoc />
public bool CanDelete(BaseItem item)
{
var internalChannel = _libraryManager.GetItemById(item.ChannelId);
var channel = Channels.FirstOrDefault(i => GetInternalChannelId(i.Name).Equals(internalChannel.Id));
- var supportsDelete = channel as ISupportsDelete;
- return supportsDelete != null && supportsDelete.CanDelete(item);
+ return channel is ISupportsDelete supportsDelete && supportsDelete.CanDelete(item);
}
+ /// <inheritdoc />
public bool EnableMediaProbe(BaseItem item)
{
var internalChannel = _libraryManager.GetItemById(item.ChannelId);
@@ -98,6 +118,7 @@ namespace Emby.Server.Implementations.Channels
return channel is ISupportsMediaProbe;
}
+ /// <inheritdoc />
public Task DeleteItem(BaseItem item)
{
var internalChannel = _libraryManager.GetItemById(item.ChannelId);
@@ -124,11 +145,16 @@ namespace Emby.Server.Implementations.Channels
.OrderBy(i => i.Name);
}
+ /// <summary>
+ /// Get the installed channel IDs.
+ /// </summary>
+ /// <returns>An <see cref="IEnumerable{T}"/> containing installed channel IDs.</returns>
public IEnumerable<Guid> GetInstalledChannelIds()
{
return GetAllChannels().Select(i => GetInternalChannelId(i.Name));
}
+ /// <inheritdoc />
public QueryResult<Channel> GetChannelsInternal(ChannelQuery query)
{
var user = query.UserId.Equals(Guid.Empty)
@@ -147,15 +173,13 @@ namespace Emby.Server.Implementations.Channels
{
try
{
- var hasAttributes = GetChannelProvider(i) as IHasFolderAttributes;
-
- return (hasAttributes != null && hasAttributes.Attributes.Contains("Recordings", StringComparer.OrdinalIgnoreCase)) == val;
+ return (GetChannelProvider(i) is IHasFolderAttributes hasAttributes
+ && hasAttributes.Attributes.Contains("Recordings", StringComparer.OrdinalIgnoreCase)) == val;
}
catch
{
return false;
}
-
}).ToList();
}
@@ -172,7 +196,6 @@ namespace Emby.Server.Implementations.Channels
{
return false;
}
-
}).ToList();
}
@@ -189,9 +212,9 @@ namespace Emby.Server.Implementations.Channels
{
return false;
}
-
}).ToList();
}
+
if (query.IsFavorite.HasValue)
{
var val = query.IsFavorite.Value;
@@ -216,7 +239,6 @@ namespace Emby.Server.Implementations.Channels
{
return false;
}
-
}).ToList();
}
@@ -227,6 +249,7 @@ namespace Emby.Server.Implementations.Channels
{
all = all.Skip(query.StartIndex.Value).ToList();
}
+
if (query.Limit.HasValue)
{
all = all.Take(query.Limit.Value).ToList();
@@ -249,6 +272,7 @@ namespace Emby.Server.Implementations.Channels
};
}
+ /// <inheritdoc />
public QueryResult<BaseItemDto> GetChannels(ChannelQuery query)
{
var user = query.UserId.Equals(Guid.Empty)
@@ -257,11 +281,9 @@ namespace Emby.Server.Implementations.Channels
var internalResult = GetChannelsInternal(query);
- var dtoOptions = new DtoOptions()
- {
- };
+ var dtoOptions = new DtoOptions();
- //TODO Fix The co-variant conversion (internalResult.Items) between Folder[] and BaseItem[], this can generate runtime issues.
+ // TODO Fix The co-variant conversion (internalResult.Items) between Folder[] and BaseItem[], this can generate runtime issues.
var returnItems = _dtoService.GetBaseItemDtos(internalResult.Items, dtoOptions, user);
var result = new QueryResult<BaseItemDto>
@@ -273,6 +295,12 @@ namespace Emby.Server.Implementations.Channels
return result;
}
+ /// <summary>
+ /// Refreshes the associated channels.
+ /// </summary>
+ /// <param name="progress">The progress.</param>
+ /// <param name="cancellationToken">A cancellation token that can be used to cancel the operation.</param>
+ /// <returns>The completed task.</returns>
public async Task RefreshChannels(IProgress<double> progress, CancellationToken cancellationToken)
{
var allChannelsList = GetAllChannels().ToList();
@@ -306,14 +334,7 @@ namespace Emby.Server.Implementations.Channels
private Channel GetChannelEntity(IChannel channel)
{
- var item = GetChannel(GetInternalChannelId(channel.Name));
-
- if (item == null)
- {
- item = GetChannel(channel, CancellationToken.None).Result;
- }
-
- return item;
+ return GetChannel(GetInternalChannelId(channel.Name)) ?? GetChannel(channel, CancellationToken.None).Result;
}
private List<MediaSourceInfo> GetSavedMediaSources(BaseItem item)
@@ -342,8 +363,8 @@ namespace Emby.Server.Implementations.Channels
}
catch
{
-
}
+
return;
}
@@ -352,6 +373,7 @@ namespace Emby.Server.Implementations.Channels
_jsonSerializer.SerializeToFile(mediaSources, path);
}
+ /// <inheritdoc />
public IEnumerable<MediaSourceInfo> GetStaticMediaSources(BaseItem item, CancellationToken cancellationToken)
{
IEnumerable<MediaSourceInfo> results = GetSavedMediaSources(item);
@@ -361,16 +383,20 @@ namespace Emby.Server.Implementations.Channels
.ToList();
}
+ /// <summary>
+ /// Gets the dynamic media sources based on the provided item.
+ /// </summary>
+ /// <param name="item">The item.</param>
+ /// <param name="cancellationToken">A cancellation token that can be used to cancel the operation.</param>
+ /// <returns>The task representing the operation to get the media sources.</returns>
public async Task<IEnumerable<MediaSourceInfo>> GetDynamicMediaSources(BaseItem item, CancellationToken cancellationToken)
{
var channel = GetChannel(item.ChannelId);
var channelPlugin = GetChannelProvider(channel);
- var requiresCallback = channelPlugin as IRequiresMediaInfoCallback;
-
IEnumerable<MediaSourceInfo> results;
- if (requiresCallback != null)
+ if (channelPlugin is IRequiresMediaInfoCallback requiresCallback)
{
results = await GetChannelItemMediaSourcesInternal(requiresCallback, item.ExternalId, cancellationToken)
.ConfigureAwait(false);
@@ -385,9 +411,6 @@ namespace Emby.Server.Implementations.Channels
.ToList();
}
- private readonly ConcurrentDictionary<string, Tuple<DateTime, List<MediaSourceInfo>>> _channelItemMediaInfo =
- new ConcurrentDictionary<string, Tuple<DateTime, List<MediaSourceInfo>>>();
-
private async Task<IEnumerable<MediaSourceInfo>> GetChannelItemMediaSourcesInternal(IRequiresMediaInfoCallback channel, string id, CancellationToken cancellationToken)
{
if (_channelItemMediaInfo.TryGetValue(id, out Tuple<DateTime, List<MediaSourceInfo>> cachedInfo))
@@ -410,7 +433,7 @@ namespace Emby.Server.Implementations.Channels
private static MediaSourceInfo NormalizeMediaSource(BaseItem item, MediaSourceInfo info)
{
- info.RunTimeTicks = info.RunTimeTicks ?? item.RunTimeTicks;
+ info.RunTimeTicks ??= item.RunTimeTicks;
return info;
}
@@ -445,18 +468,21 @@ namespace Emby.Server.Implementations.Channels
{
isNew = true;
}
+
item.Path = path;
if (!item.ChannelId.Equals(id))
{
forceUpdate = true;
}
+
item.ChannelId = id;
if (item.ParentId != parentFolderId)
{
forceUpdate = true;
}
+
item.ParentId = parentFolderId;
item.OfficialRating = GetOfficialRating(channelInfo.ParentalRating);
@@ -473,51 +499,56 @@ namespace Emby.Server.Implementations.Channels
_libraryManager.CreateItem(item, null);
}
- await item.RefreshMetadata(new MetadataRefreshOptions(new DirectoryService(_fileSystem))
- {
- ForceSave = !isNew && forceUpdate
- }, cancellationToken).ConfigureAwait(false);
+ await item.RefreshMetadata(
+ new MetadataRefreshOptions(new DirectoryService(_fileSystem))
+ {
+ ForceSave = !isNew && forceUpdate
+ },
+ cancellationToken).ConfigureAwait(false);
return item;
}
private static string GetOfficialRating(ChannelParentalRating rating)
{
- switch (rating)
- {
- case ChannelParentalRating.Adult:
- return "XXX";
- case ChannelParentalRating.UsR:
- return "R";
- case ChannelParentalRating.UsPG13:
- return "PG-13";
- case ChannelParentalRating.UsPG:
- return "PG";
- default:
- return null;
- }
+ return rating switch
+ {
+ ChannelParentalRating.Adult => "XXX",
+ ChannelParentalRating.UsR => "R",
+ ChannelParentalRating.UsPG13 => "PG-13",
+ ChannelParentalRating.UsPG => "PG",
+ _ => null
+ };
}
+ /// <summary>
+ /// Gets a channel with the provided Guid.
+ /// </summary>
+ /// <param name="id">The Guid.</param>
+ /// <returns>The corresponding channel.</returns>
public Channel GetChannel(Guid id)
{
return _libraryManager.GetItemById(id) as Channel;
}
+ /// <inheritdoc />
public Channel GetChannel(string id)
{
return _libraryManager.GetItemById(id) as Channel;
}
+ /// <inheritdoc />
public ChannelFeatures[] GetAllChannelFeatures()
{
- return _libraryManager.GetItemIds(new InternalItemsQuery
- {
- IncludeItemTypes = new[] { typeof(Channel).Name },
- OrderBy = new[] { (ItemSortBy.SortName, SortOrder.Ascending) }
-
- }).Select(i => GetChannelFeatures(i.ToString("N", CultureInfo.InvariantCulture))).ToArray();
+ return _libraryManager.GetItemIds(
+ new InternalItemsQuery
+ {
+ IncludeItemTypes = new[] { typeof(Channel).Name },
+ OrderBy = new[] { (ItemSortBy.SortName, SortOrder.Ascending) }
+ }).Select(i => GetChannelFeatures(i.ToString("N", CultureInfo.InvariantCulture))).ToArray();
}
+ /// <inheritdoc />
public ChannelFeatures GetChannelFeatures(string id)
{
if (string.IsNullOrEmpty(id))
@@ -531,15 +562,27 @@ namespace Emby.Server.Implementations.Channels
return GetChannelFeaturesDto(channel, channelProvider, channelProvider.GetChannelFeatures());
}
+ /// <summary>
+ /// Checks whether the provided Guid supports external transfer.
+ /// </summary>
+ /// <param name="channelId">The Guid.</param>
+ /// <returns>Whether or not the provided Guid supports external transfer.</returns>
public bool SupportsExternalTransfer(Guid channelId)
{
- //var channel = GetChannel(channelId);
var channelProvider = GetChannelProvider(channelId);
return channelProvider.GetChannelFeatures().SupportsContentDownloading;
}
- public ChannelFeatures GetChannelFeaturesDto(Channel channel,
+ /// <summary>
+ /// Gets the provided channel's supported features.
+ /// </summary>
+ /// <param name="channel">The channel.</param>
+ /// <param name="provider">The provider.</param>
+ /// <param name="features">The features.</param>
+ /// <returns>The supported features.</returns>
+ public ChannelFeatures GetChannelFeaturesDto(
+ Channel channel,
IChannel provider,
InternalChannelFeatures features)
{
@@ -568,9 +611,11 @@ namespace Emby.Server.Implementations.Channels
{
throw new ArgumentNullException(nameof(name));
}
+
return _libraryManager.GetNewItemId("Channel " + name, typeof(Channel));
}
+ /// <inheritdoc />
public async Task<QueryResult<BaseItemDto>> GetLatestChannelItems(InternalItemsQuery query, CancellationToken cancellationToken)
{
var internalResult = await GetLatestChannelItemsInternal(query, cancellationToken).ConfigureAwait(false);
@@ -589,6 +634,7 @@ namespace Emby.Server.Implementations.Channels
return result;
}
+ /// <inheritdoc />
public async Task<QueryResult<BaseItem>> GetLatestChannelItemsInternal(InternalItemsQuery query, CancellationToken cancellationToken)
{
var channels = GetAllChannels().Where(i => i is ISupportsLatestMedia).ToArray();
@@ -615,7 +661,7 @@ namespace Emby.Server.Implementations.Channels
query.IsFolder = false;
// hack for trailers, figure out a better way later
- var sortByPremiereDate = channels.Length == 1 && channels[0].GetType().Name.IndexOf("Trailer") != -1;
+ var sortByPremiereDate = channels.Length == 1 && channels[0].GetType().Name.Contains("Trailer", StringComparison.Ordinal);
if (sortByPremiereDate)
{
@@ -641,10 +687,12 @@ namespace Emby.Server.Implementations.Channels
{
var internalChannel = await GetChannel(channel, cancellationToken).ConfigureAwait(false);
- var query = new InternalItemsQuery();
- query.Parent = internalChannel;
- query.EnableTotalRecordCount = false;
- query.ChannelIds = new Guid[] { internalChannel.Id };
+ var query = new InternalItemsQuery
+ {
+ Parent = internalChannel,
+ EnableTotalRecordCount = false,
+ ChannelIds = new Guid[] { internalChannel.Id }
+ };
var result = await GetChannelItemsInternal(query, new SimpleProgress<double>(), cancellationToken).ConfigureAwait(false);
@@ -652,17 +700,20 @@ namespace Emby.Server.Implementations.Channels
{
if (item is Folder folder)
{
- await GetChannelItemsInternal(new InternalItemsQuery
- {
- Parent = folder,
- EnableTotalRecordCount = false,
- ChannelIds = new Guid[] { internalChannel.Id }
-
- }, new SimpleProgress<double>(), cancellationToken).ConfigureAwait(false);
+ await GetChannelItemsInternal(
+ new InternalItemsQuery
+ {
+ Parent = folder,
+ EnableTotalRecordCount = false,
+ ChannelIds = new Guid[] { internalChannel.Id }
+ },
+ new SimpleProgress<double>(),
+ cancellationToken).ConfigureAwait(false);
}
}
}
+ /// <inheritdoc />
public async Task<QueryResult<BaseItem>> GetChannelItemsInternal(InternalItemsQuery query, IProgress<double> progress, CancellationToken cancellationToken)
{
// Get the internal channel entity
@@ -673,7 +724,8 @@ namespace Emby.Server.Implementations.Channels
var parentItem = query.ParentId == Guid.Empty ? channel : _libraryManager.GetItemById(query.ParentId);
- var itemsResult = await GetChannelItems(channelProvider,
+ var itemsResult = await GetChannelItems(
+ channelProvider,
query.User,
parentItem is Channel ? null : parentItem.ExternalId,
null,
@@ -685,13 +737,12 @@ namespace Emby.Server.Implementations.Channels
{
query.Parent = channel;
}
+
query.ChannelIds = Array.Empty<Guid>();
// Not yet sure why this is causing a problem
query.GroupByPresentationUniqueKey = false;
- //_logger.LogDebug("GetChannelItemsInternal");
-
// null if came from cache
if (itemsResult != null)
{
@@ -708,12 +759,15 @@ namespace Emby.Server.Implementations.Channels
var deadItem = _libraryManager.GetItemById(deadId);
if (deadItem != null)
{
- _libraryManager.DeleteItem(deadItem, new DeleteOptions
- {
- DeleteFileLocation = false,
- DeleteFromExternalProvider = false
-
- }, parentItem, false);
+ _libraryManager.DeleteItem(
+ deadItem,
+ new DeleteOptions
+ {
+ DeleteFileLocation = false,
+ DeleteFromExternalProvider = false
+ },
+ parentItem,
+ false);
}
}
}
@@ -721,6 +775,7 @@ namespace Emby.Server.Implementations.Channels
return _libraryManager.GetItemsResult(query);
}
+ /// <inheritdoc />
public async Task<QueryResult<BaseItemDto>> GetChannelItems(InternalItemsQuery query, CancellationToken cancellationToken)
{
var internalResult = await GetChannelItemsInternal(query, new SimpleProgress<double>(), cancellationToken).ConfigureAwait(false);
@@ -736,7 +791,6 @@ namespace Emby.Server.Implementations.Channels
return result;
}
- private readonly SemaphoreSlim _resourcePool = new SemaphoreSlim(1, 1);
private async Task<ChannelItemResult> GetChannelItems(IChannel channel,
User user,
string externalFolderId,
@@ -744,7 +798,7 @@ namespace Emby.Server.Implementations.Channels
bool sortDescending,
CancellationToken cancellationToken)
{
- var userId = user == null ? null : user.Id.ToString("N", CultureInfo.InvariantCulture);
+ var userId = user?.Id.ToString("N", CultureInfo.InvariantCulture);
var cacheLength = CacheLength;
var cachePath = GetChannelDataCachePath(channel, userId, externalFolderId, sortField, sortDescending);
@@ -762,11 +816,9 @@ namespace Emby.Server.Implementations.Channels
}
catch (FileNotFoundException)
{
-
}
catch (IOException)
{
-
}
await _resourcePool.WaitAsync(cancellationToken).ConfigureAwait(false);
@@ -786,16 +838,14 @@ namespace Emby.Server.Implementations.Channels
}
catch (FileNotFoundException)
{
-
}
catch (IOException)
{
-
}
var query = new InternalChannelItemQuery
{
- UserId = user == null ? Guid.Empty : user.Id,
+ UserId = user?.Id ?? Guid.Empty,
SortBy = sortField,
SortDescending = sortDescending,
FolderId = externalFolderId
@@ -834,7 +884,8 @@ namespace Emby.Server.Implementations.Channels
}
}
- private string GetChannelDataCachePath(IChannel channel,
+ private string GetChannelDataCachePath(
+ IChannel channel,
string userId,
string externalFolderId,
ChannelItemSortField? sortField,
@@ -844,8 +895,7 @@ namespace Emby.Server.Implementations.Channels
var userCacheKey = string.Empty;
- var hasCacheKey = channel as IHasCacheKey;
- if (hasCacheKey != null)
+ if (channel is IHasCacheKey hasCacheKey)
{
userCacheKey = hasCacheKey.GetCacheKey(userId) ?? string.Empty;
}
@@ -859,6 +909,7 @@ namespace Emby.Server.Implementations.Channels
{
filename += "-sortField-" + sortField.Value;
}
+
if (sortDescending)
{
filename += "-sortDescending";
@@ -866,7 +917,8 @@ namespace Emby.Server.Implementations.Channels
filename = filename.GetMD5().ToString("N", CultureInfo.InvariantCulture);
- return Path.Combine(_config.ApplicationPaths.CachePath,
+ return Path.Combine(
+ _config.ApplicationPaths.CachePath,
"channels",
channelId,
version,
@@ -920,60 +972,32 @@ namespace Emby.Server.Implementations.Channels
if (info.Type == ChannelItemType.Folder)
{
- if (info.FolderType == ChannelFolderType.MusicAlbum)
- {
- item = GetItemById<MusicAlbum>(info.Id, channelProvider.Name, out isNew);
- }
- else if (info.FolderType == ChannelFolderType.MusicArtist)
- {
- item = GetItemById<MusicArtist>(info.Id, channelProvider.Name, out isNew);
- }
- else if (info.FolderType == ChannelFolderType.PhotoAlbum)
- {
- item = GetItemById<PhotoAlbum>(info.Id, channelProvider.Name, out isNew);
- }
- else if (info.FolderType == ChannelFolderType.Series)
- {
- item = GetItemById<Series>(info.Id, channelProvider.Name, out isNew);
- }
- else if (info.FolderType == ChannelFolderType.Season)
- {
- item = GetItemById<Season>(info.Id, channelProvider.Name, out isNew);
- }
- else
+ item = info.FolderType switch
{
- item = GetItemById<Folder>(info.Id, channelProvider.Name, out isNew);
- }
+ ChannelFolderType.MusicAlbum => GetItemById<MusicAlbum>(info.Id, channelProvider.Name, out isNew),
+ ChannelFolderType.MusicArtist => GetItemById<MusicArtist>(info.Id, channelProvider.Name, out isNew),
+ ChannelFolderType.PhotoAlbum => GetItemById<PhotoAlbum>(info.Id, channelProvider.Name, out isNew),
+ ChannelFolderType.Series => GetItemById<Series>(info.Id, channelProvider.Name, out isNew),
+ ChannelFolderType.Season => GetItemById<Season>(info.Id, channelProvider.Name, out isNew),
+ _ => GetItemById<Folder>(info.Id, channelProvider.Name, out isNew)
+ };
}
else if (info.MediaType == ChannelMediaType.Audio)
{
- if (info.ContentType == ChannelMediaContentType.Podcast)
- {
- item = GetItemById<AudioBook>(info.Id, channelProvider.Name, out isNew);
- }
- else
- {
- item = GetItemById<Audio>(info.Id, channelProvider.Name, out isNew);
- }
+ item = info.ContentType == ChannelMediaContentType.Podcast
+ ? GetItemById<AudioBook>(info.Id, channelProvider.Name, out isNew)
+ : GetItemById<Audio>(info.Id, channelProvider.Name, out isNew);
}
else
{
- if (info.ContentType == ChannelMediaContentType.Episode)
- {
- item = GetItemById<Episode>(info.Id, channelProvider.Name, out isNew);
- }
- else if (info.ContentType == ChannelMediaContentType.Movie)
- {
- item = GetItemById<Movie>(info.Id, channelProvider.Name, out isNew);
- }
- else if (info.ContentType == ChannelMediaContentType.Trailer || info.ExtraType == ExtraType.Trailer)
+ item = info.ContentType switch
{
- item = GetItemById<Trailer>(info.Id, channelProvider.Name, out isNew);
- }
- else
- {
- item = GetItemById<Video>(info.Id, channelProvider.Name, out isNew);
- }
+ ChannelMediaContentType.Episode => GetItemById<Episode>(info.Id, channelProvider.Name, out isNew),
+ ChannelMediaContentType.Movie => GetItemById<Movie>(info.Id, channelProvider.Name, out isNew),
+ var x when x == ChannelMediaContentType.Trailer || info.ExtraType == ExtraType.Trailer
+ => GetItemById<Trailer>(info.Id, channelProvider.Name, out isNew),
+ _ => GetItemById<Video>(info.Id, channelProvider.Name, out isNew)
+ };
}
var enableMediaProbe = channelProvider is ISupportsMediaProbe;
@@ -982,7 +1006,6 @@ namespace Emby.Server.Implementations.Channels
{
item.RunTimeTicks = null;
}
-
else if (isNew || !enableMediaProbe)
{
item.RunTimeTicks = info.RunTimeTicks;
@@ -1015,26 +1038,24 @@ namespace Emby.Server.Implementations.Channels
}
}
- var hasArtists = item as IHasArtist;
- if (hasArtists != null)
+ if (item is IHasArtist hasArtists)
{
hasArtists.Artists = info.Artists.ToArray();
}
- var hasAlbumArtists = item as IHasAlbumArtist;
- if (hasAlbumArtists != null)
+ if (item is IHasAlbumArtist hasAlbumArtists)
{
hasAlbumArtists.AlbumArtists = info.AlbumArtists.ToArray();
}
- var trailer = item as Trailer;
- if (trailer != null)
+ if (item is Trailer trailer)
{
if (!info.TrailerTypes.SequenceEqual(trailer.TrailerTypes))
{
_logger.LogDebug("Forcing update due to TrailerTypes {0}", item.Name);
forceUpdate = true;
}
+
trailer.TrailerTypes = info.TrailerTypes.ToArray();
}
@@ -1058,6 +1079,7 @@ namespace Emby.Server.Implementations.Channels
forceUpdate = true;
_logger.LogDebug("Forcing update due to ChannelId {0}", item.Name);
}
+
item.ChannelId = internalChannelId;
if (!item.ParentId.Equals(parentFolderId))
@@ -1065,16 +1087,17 @@ namespace Emby.Server.Implementations.Channels
forceUpdate = true;
_logger.LogDebug("Forcing update due to parent folder Id {0}", item.Name);
}
+
item.ParentId = parentFolderId;
- var hasSeries = item as IHasSeries;
- if (hasSeries != null)
+ if (item is IHasSeries hasSeries)
{
if (!string.Equals(hasSeries.SeriesName, info.SeriesName, StringComparison.OrdinalIgnoreCase))
{
forceUpdate = true;
_logger.LogDebug("Forcing update due to SeriesName {0}", item.Name);
}
+
hasSeries.SeriesName = info.SeriesName;
}
@@ -1083,24 +1106,23 @@ namespace Emby.Server.Implementations.Channels
forceUpdate = true;
_logger.LogDebug("Forcing update due to ExternalId {0}", item.Name);
}
+
item.ExternalId = info.Id;
- var channelAudioItem = item as Audio;
- if (channelAudioItem != null)
+ if (item is Audio channelAudioItem)
{
channelAudioItem.ExtraType = info.ExtraType;
var mediaSource = info.MediaSources.FirstOrDefault();
- item.Path = mediaSource == null ? null : mediaSource.Path;
+ item.Path = mediaSource?.Path;
}
- var channelVideoItem = item as Video;
- if (channelVideoItem != null)
+ if (item is Video channelVideoItem)
{
channelVideoItem.ExtraType = info.ExtraType;
var mediaSource = info.MediaSources.FirstOrDefault();
- item.Path = mediaSource == null ? null : mediaSource.Path;
+ item.Path = mediaSource?.Path;
}
if (!string.IsNullOrEmpty(info.ImageUrl) && !item.HasImage(ImageType.Primary))
@@ -1157,7 +1179,7 @@ namespace Emby.Server.Implementations.Channels
}
}
- if (isNew || forceUpdate || item.DateLastRefreshed == default(DateTime))
+ if (isNew || forceUpdate || item.DateLastRefreshed == default)
{
_providerManager.QueueRefresh(item.Id, new MetadataRefreshOptions(new DirectoryService(_fileSystem)), RefreshPriority.Normal);
}
diff --git a/Emby.Server.Implementations/Channels/ChannelPostScanTask.cs b/Emby.Server.Implementations/Channels/ChannelPostScanTask.cs
index 36e0e5e26..eeb49b8fe 100644
--- a/Emby.Server.Implementations/Channels/ChannelPostScanTask.cs
+++ b/Emby.Server.Implementations/Channels/ChannelPostScanTask.cs
@@ -1,6 +1,3 @@
-#pragma warning disable CS1591
-#pragma warning disable SA1600
-
using System;
using System.Linq;
using System.Threading;
@@ -12,21 +9,34 @@ using Microsoft.Extensions.Logging;
namespace Emby.Server.Implementations.Channels
{
+ /// <summary>
+ /// A task to remove all non-installed channels from the database.
+ /// </summary>
public class ChannelPostScanTask
{
private readonly IChannelManager _channelManager;
- private readonly IUserManager _userManager;
private readonly ILogger _logger;
private readonly ILibraryManager _libraryManager;
- public ChannelPostScanTask(IChannelManager channelManager, IUserManager userManager, ILogger logger, ILibraryManager libraryManager)
+ /// <summary>
+ /// Initializes a new instance of the <see cref="ChannelPostScanTask"/> class.
+ /// </summary>
+ /// <param name="channelManager">The channel manager.</param>
+ /// <param name="logger">The logger.</param>
+ /// <param name="libraryManager">The library manager.</param>
+ public ChannelPostScanTask(IChannelManager channelManager, ILogger logger, ILibraryManager libraryManager)
{
_channelManager = channelManager;
- _userManager = userManager;
_logger = logger;
_libraryManager = libraryManager;
}
+ /// <summary>
+ /// Runs this task.
+ /// </summary>
+ /// <param name="progress">The progress.</param>
+ /// <param name="cancellationToken">The cancellation token.</param>
+ /// <returns>The completed task.</returns>
public Task Run(IProgress<double> progress, CancellationToken cancellationToken)
{
CleanDatabase(cancellationToken);
@@ -35,14 +45,6 @@ namespace Emby.Server.Implementations.Channels
return Task.CompletedTask;
}
- public static string GetUserDistinctValue(User user)
- {
- var channels = user.Policy.EnabledChannels
- .OrderBy(i => i);
-
- return string.Join("|", channels);
- }
-
private void CleanDatabase(CancellationToken cancellationToken)
{
var installedChannelIds = ((ChannelManager)_channelManager).GetInstalledChannelIds();
@@ -75,19 +77,23 @@ namespace Emby.Server.Implementations.Channels
{
cancellationToken.ThrowIfCancellationRequested();
- _libraryManager.DeleteItem(item, new DeleteOptions
- {
- DeleteFileLocation = false
-
- }, false);
+ _libraryManager.DeleteItem(
+ item,
+ new DeleteOptions
+ {
+ DeleteFileLocation = false
+ },
+ false);
}
// Finally, delete the channel itself
- _libraryManager.DeleteItem(channel, new DeleteOptions
- {
- DeleteFileLocation = false
-
- }, false);
+ _libraryManager.DeleteItem(
+ channel,
+ new DeleteOptions
+ {
+ DeleteFileLocation = false
+ },
+ false);
}
}
}
diff --git a/Emby.Server.Implementations/Channels/RefreshChannelsScheduledTask.cs b/Emby.Server.Implementations/Channels/RefreshChannelsScheduledTask.cs
index 039e2c138..54b621e25 100644
--- a/Emby.Server.Implementations/Channels/RefreshChannelsScheduledTask.cs
+++ b/Emby.Server.Implementations/Channels/RefreshChannelsScheduledTask.cs
@@ -1,6 +1,3 @@
-#pragma warning disable CS1591
-#pragma warning disable SA1600
-
using System;
using System.Collections.Generic;
using System.Threading;
@@ -8,60 +5,84 @@ using System.Threading.Tasks;
using MediaBrowser.Common.Progress;
using MediaBrowser.Controller.Channels;
using MediaBrowser.Controller.Library;
+using MediaBrowser.Model.Globalization;
using MediaBrowser.Model.Tasks;
using Microsoft.Extensions.Logging;
namespace Emby.Server.Implementations.Channels
{
+ /// <summary>
+ /// The "Refresh Channels" scheduled task.
+ /// </summary>
public class RefreshChannelsScheduledTask : IScheduledTask, IConfigurableScheduledTask
{
private readonly IChannelManager _channelManager;
- private readonly IUserManager _userManager;
private readonly ILogger _logger;
private readonly ILibraryManager _libraryManager;
+ private readonly ILocalizationManager _localization;
- public RefreshChannelsScheduledTask(IChannelManager channelManager, IUserManager userManager, ILogger logger, ILibraryManager libraryManager)
+ /// <summary>
+ /// Initializes a new instance of the <see cref="RefreshChannelsScheduledTask"/> class.
+ /// </summary>
+ /// <param name="channelManager">The channel manager.</param>
+ /// <param name="logger">The logger.</param>
+ /// <param name="libraryManager">The library manager.</param>
+ /// <param name="localization">The localization manager.</param>
+ public RefreshChannelsScheduledTask(
+ IChannelManager channelManager,
+ ILogger<RefreshChannelsScheduledTask> logger,
+ ILibraryManager libraryManager,
+ ILocalizationManager localization)
{
_channelManager = channelManager;
- _userManager = userManager;
_logger = logger;
_libraryManager = libraryManager;
+ _localization = localization;
}
- public string Name => "Refresh Channels";
+ /// <inheritdoc />
+ public string Name => _localization.GetLocalizedString("TasksRefreshChannels");
- public string Description => "Refreshes internet channel information.";
+ /// <inheritdoc />
+ public string Description => _localization.GetLocalizedString("TasksRefreshChannelsDescription");
- public string Category => "Internet Channels";
+ /// <inheritdoc />
+ public string Category => _localization.GetLocalizedString("TasksChannelsCategory");
+ /// <inheritdoc />
public bool IsHidden => ((ChannelManager)_channelManager).Channels.Length == 0;
+ /// <inheritdoc />
public bool IsEnabled => true;
+ /// <inheritdoc />
public bool IsLogged => true;
+ /// <inheritdoc />
+ public string Key => "RefreshInternetChannels";
+
+ /// <inheritdoc />
public async Task Execute(CancellationToken cancellationToken, IProgress<double> progress)
{
var manager = (ChannelManager)_channelManager;
await manager.RefreshChannels(new SimpleProgress<double>(), cancellationToken).ConfigureAwait(false);
- await new ChannelPostScanTask(_channelManager, _userManager, _logger, _libraryManager).Run(progress, cancellationToken)
+ await new ChannelPostScanTask(_channelManager, _logger, _libraryManager).Run(progress, cancellationToken)
.ConfigureAwait(false);
}
- /// <summary>
- /// Creates the triggers that define when the task will run
- /// </summary>
+ /// <inheritdoc />
public IEnumerable<TaskTriggerInfo> GetDefaultTriggers()
{
- return new[] {
-
+ return new[]
+ {
// Every so often
- new TaskTriggerInfo { Type = TaskTriggerInfo.TriggerInterval, IntervalTicks = TimeSpan.FromHours(24).Ticks}
+ new TaskTriggerInfo
+ {
+ Type = TaskTriggerInfo.TriggerInterval, IntervalTicks = TimeSpan.FromHours(24).Ticks
+ }
};
}
-
- public string Key => "RefreshInternetChannels";
}
}
diff --git a/Emby.Server.Implementations/Collections/CollectionImageProvider.cs b/Emby.Server.Implementations/Collections/CollectionImageProvider.cs
index 8006b8694..c69a07e83 100644
--- a/Emby.Server.Implementations/Collections/CollectionImageProvider.cs
+++ b/Emby.Server.Implementations/Collections/CollectionImageProvider.cs
@@ -1,7 +1,3 @@
-#pragma warning disable CS1591
-#pragma warning disable SA1600
-
-using System;
using System.Collections.Generic;
using System.Linq;
using Emby.Server.Implementations.Images;
@@ -17,8 +13,18 @@ using MediaBrowser.Model.IO;
namespace Emby.Server.Implementations.Collections
{
+ /// <summary>
+ /// A collection image provider.
+ /// </summary>
public class CollectionImageProvider : BaseDynamicImageProvider<BoxSet>
{
+ /// <summary>
+ /// Initializes a new instance of the <see cref="CollectionImageProvider"/> class.
+ /// </summary>
+ /// <param name="fileSystem">The filesystem.</param>
+ /// <param name="providerManager">The provider manager.</param>
+ /// <param name="applicationPaths">The application paths.</param>
+ /// <param name="imageProcessor">The image processor.</param>
public CollectionImageProvider(
IFileSystem fileSystem,
IProviderManager providerManager,
@@ -28,6 +34,7 @@ namespace Emby.Server.Implementations.Collections
{
}
+ /// <inheritdoc />
protected override bool Supports(BaseItem item)
{
// Right now this is the only way to prevent this image from getting created ahead of internet image providers
@@ -39,6 +46,7 @@ namespace Emby.Server.Implementations.Collections
return base.Supports(item);
}
+ /// <inheritdoc />
protected override IReadOnlyList<BaseItem> GetItemsWithImages(BaseItem item)
{
var playlist = (BoxSet)item;
@@ -50,13 +58,10 @@ namespace Emby.Server.Implementations.Collections
var episode = subItem as Episode;
- if (episode != null)
+ var series = episode?.Series;
+ if (series != null && series.HasImage(ImageType.Primary))
{
- var series = episode.Series;
- if (series != null && series.HasImage(ImageType.Primary))
- {
- return series;
- }
+ return series;
}
if (subItem.HasImage(ImageType.Primary))
@@ -82,6 +87,7 @@ namespace Emby.Server.Implementations.Collections
.ToList();
}
+ /// <inheritdoc />
protected override string CreateImage(BaseItem item, IReadOnlyCollection<BaseItem> itemsWithImages, string outputPathWithoutExtension, ImageType imageType, int imageIndex)
{
return CreateSingleImage(itemsWithImages, outputPathWithoutExtension, ImageType.Primary);
diff --git a/Emby.Server.Implementations/Collections/CollectionManager.cs b/Emby.Server.Implementations/Collections/CollectionManager.cs
index efdef8481..7c518d483 100644
--- a/Emby.Server.Implementations/Collections/CollectionManager.cs
+++ b/Emby.Server.Implementations/Collections/CollectionManager.cs
@@ -1,6 +1,3 @@
-#pragma warning disable CS1591
-#pragma warning disable SA1600
-
using System;
using System.Collections.Generic;
using System.Globalization;
@@ -24,6 +21,9 @@ using Microsoft.Extensions.Logging;
namespace Emby.Server.Implementations.Collections
{
+ /// <summary>
+ /// The collection manager.
+ /// </summary>
public class CollectionManager : ICollectionManager
{
private readonly ILibraryManager _libraryManager;
@@ -34,6 +34,16 @@ namespace Emby.Server.Implementations.Collections
private readonly ILocalizationManager _localizationManager;
private readonly IApplicationPaths _appPaths;
+ /// <summary>
+ /// Initializes a new instance of the <see cref="CollectionManager"/> class.
+ /// </summary>
+ /// <param name="libraryManager">The library manager.</param>
+ /// <param name="appPaths">The application paths.</param>
+ /// <param name="localizationManager">The localization manager.</param>
+ /// <param name="fileSystem">The filesystem.</param>
+ /// <param name="iLibraryMonitor">The library monitor.</param>
+ /// <param name="loggerFactory">The logger factory.</param>
+ /// <param name="providerManager">The provider manager.</param>
public CollectionManager(
ILibraryManager libraryManager,
IApplicationPaths appPaths,
@@ -52,8 +62,13 @@ namespace Emby.Server.Implementations.Collections
_appPaths = appPaths;
}
+ /// <inheritdoc />
public event EventHandler<CollectionCreatedEventArgs> CollectionCreated;
+
+ /// <inheritdoc />
public event EventHandler<CollectionModifiedEventArgs> ItemsAddedToCollection;
+
+ /// <inheritdoc />
public event EventHandler<CollectionModifiedEventArgs> ItemsRemovedFromCollection;
private IEnumerable<Folder> FindFolders(string path)
@@ -110,11 +125,12 @@ namespace Emby.Server.Implementations.Collections
{
var folder = GetCollectionsFolder(false).Result;
- return folder == null ?
- new List<BoxSet>() :
- folder.GetChildren(user, true).OfType<BoxSet>();
+ return folder == null
+ ? Enumerable.Empty<BoxSet>()
+ : folder.GetChildren(user, true).OfType<BoxSet>();
}
+ /// <inheritdoc />
public BoxSet CreateCollection(CollectionCreationOptions options)
{
var name = options.Name;
@@ -179,11 +195,13 @@ namespace Emby.Server.Implementations.Collections
}
}
+ /// <inheritdoc />
public void AddToCollection(Guid collectionId, IEnumerable<string> ids)
{
AddToCollection(collectionId, ids, true, new MetadataRefreshOptions(new DirectoryService(_fileSystem)));
}
+ /// <inheritdoc />
public void AddToCollection(Guid collectionId, IEnumerable<Guid> ids)
{
AddToCollection(collectionId, ids.Select(i => i.ToString("N", CultureInfo.InvariantCulture)), true, new MetadataRefreshOptions(new DirectoryService(_fileSystem)));
@@ -192,7 +210,6 @@ namespace Emby.Server.Implementations.Collections
private void AddToCollection(Guid collectionId, IEnumerable<string> ids, bool fireEvent, MetadataRefreshOptions refreshOptions)
{
var collection = _libraryManager.GetItemById(collectionId) as BoxSet;
-
if (collection == null)
{
throw new ArgumentException("No collection exists with the supplied Id");
@@ -247,11 +264,13 @@ namespace Emby.Server.Implementations.Collections
}
}
+ /// <inheritdoc />
public void RemoveFromCollection(Guid collectionId, IEnumerable<string> itemIds)
{
RemoveFromCollection(collectionId, itemIds.Select(i => new Guid(i)));
}
+ /// <inheritdoc />
public void RemoveFromCollection(Guid collectionId, IEnumerable<Guid> itemIds)
{
var collection = _libraryManager.GetItemById(collectionId) as BoxSet;
@@ -290,10 +309,13 @@ namespace Emby.Server.Implementations.Collections
}
collection.UpdateToRepository(ItemUpdateType.MetadataEdit, CancellationToken.None);
- _providerManager.QueueRefresh(collection.Id, new MetadataRefreshOptions(new DirectoryService(_fileSystem))
- {
- ForceSave = true
- }, RefreshPriority.High);
+ _providerManager.QueueRefresh(
+ collection.Id,
+ new MetadataRefreshOptions(new DirectoryService(_fileSystem))
+ {
+ ForceSave = true
+ },
+ RefreshPriority.High);
ItemsRemovedFromCollection?.Invoke(this, new CollectionModifiedEventArgs
{
@@ -302,6 +324,7 @@ namespace Emby.Server.Implementations.Collections
});
}
+ /// <inheritdoc />
public IEnumerable<BaseItem> CollapseItemsWithinBoxSets(IEnumerable<BaseItem> items, User user)
{
var results = new Dictionary<Guid, BaseItem>();
@@ -310,9 +333,7 @@ namespace Emby.Server.Implementations.Collections
foreach (var item in items)
{
- var grouping = item as ISupportsBoxSetGrouping;
-
- if (grouping == null)
+ if (!(item is ISupportsBoxSetGrouping))
{
results[item.Id] = item;
}
@@ -342,13 +363,25 @@ namespace Emby.Server.Implementations.Collections
}
}
+ /// <summary>
+ /// The collection manager entry point.
+ /// </summary>
public sealed class CollectionManagerEntryPoint : IServerEntryPoint
{
private readonly CollectionManager _collectionManager;
private readonly IServerConfigurationManager _config;
private readonly ILogger _logger;
- public CollectionManagerEntryPoint(ICollectionManager collectionManager, IServerConfigurationManager config, ILogger logger)
+ /// <summary>
+ /// Initializes a new instance of the <see cref="CollectionManagerEntryPoint"/> class.
+ /// </summary>
+ /// <param name="collectionManager">The collection manager.</param>
+ /// <param name="config">The server configuration manager.</param>
+ /// <param name="logger">The logger.</param>
+ public CollectionManagerEntryPoint(
+ ICollectionManager collectionManager,
+ IServerConfigurationManager config,
+ ILogger<CollectionManagerEntryPoint> logger)
{
_collectionManager = (CollectionManager)collectionManager;
_config = config;
diff --git a/Emby.Server.Implementations/Configuration/ServerConfigurationManager.cs b/Emby.Server.Implementations/Configuration/ServerConfigurationManager.cs
index 3d8d15d19..a6eaf2d0a 100644
--- a/Emby.Server.Implementations/Configuration/ServerConfigurationManager.cs
+++ b/Emby.Server.Implementations/Configuration/ServerConfigurationManager.cs
@@ -1,5 +1,4 @@
using System;
-using System.Collections.Generic;
using System.Globalization;
using System.IO;
using Emby.Server.Implementations.AppBase;
@@ -68,23 +67,22 @@ namespace Emby.Server.Implementations.Configuration
/// <summary>
/// Updates the metadata path.
/// </summary>
+ /// <exception cref="UnauthorizedAccessException">If the directory does not exist, and the caller does not have the required permission to create it.</exception>
+ /// <exception cref="NotSupportedException">If there is a custom path transcoding path specified, but it is invalid.</exception>
+ /// <exception cref="IOException">If the directory does not exist, and it also could not be created.</exception>
private void UpdateMetadataPath()
{
- if (string.IsNullOrWhiteSpace(Configuration.MetadataPath))
- {
- ((ServerApplicationPaths)ApplicationPaths).InternalMetadataPath = Path.Combine(ApplicationPaths.ProgramDataPath, "metadata");
- }
- else
- {
- ((ServerApplicationPaths)ApplicationPaths).InternalMetadataPath = Configuration.MetadataPath;
- }
+ ((ServerApplicationPaths)ApplicationPaths).InternalMetadataPath = string.IsNullOrWhiteSpace(Configuration.MetadataPath)
+ ? ApplicationPaths.DefaultInternalMetadataPath
+ : Configuration.MetadataPath;
+ Directory.CreateDirectory(ApplicationPaths.InternalMetadataPath);
}
/// <summary>
/// Replaces the configuration.
/// </summary>
/// <param name="newConfiguration">The new configuration.</param>
- /// <exception cref="DirectoryNotFoundException"></exception>
+ /// <exception cref="DirectoryNotFoundException">If the configuration path doesn't exist.</exception>
public override void ReplaceConfiguration(BaseApplicationConfiguration newConfiguration)
{
var newConfig = (ServerConfiguration)newConfiguration;
@@ -133,7 +131,7 @@ namespace Emby.Server.Implementations.Configuration
var newPath = newConfig.MetadataPath;
if (!string.IsNullOrWhiteSpace(newPath)
- && !string.Equals(Configuration.MetadataPath, newPath, StringComparison.Ordinal))
+ && !string.Equals(Configuration.MetadataPath, newPath, StringComparison.Ordinal))
{
// Validate
if (!Directory.Exists(newPath))
diff --git a/Emby.Server.Implementations/ConfigurationOptions.cs b/Emby.Server.Implementations/ConfigurationOptions.cs
index 2ea7ff6e9..db7c35a7c 100644
--- a/Emby.Server.Implementations/ConfigurationOptions.cs
+++ b/Emby.Server.Implementations/ConfigurationOptions.cs
@@ -1,16 +1,27 @@
using System.Collections.Generic;
+using Emby.Server.Implementations.HttpServer;
+using Emby.Server.Implementations.Updates;
+using MediaBrowser.Providers.Music;
using static MediaBrowser.Controller.Extensions.ConfigurationExtensions;
namespace Emby.Server.Implementations
{
+ /// <summary>
+ /// Static class containing the default configuration options for the web server.
+ /// </summary>
public static class ConfigurationOptions
{
- public static Dictionary<string, string> Configuration => new Dictionary<string, string>
+ /// <summary>
+ /// Gets a new copy of the default configuration options.
+ /// </summary>
+ public static Dictionary<string, string> DefaultConfiguration => new Dictionary<string, string>
{
- { "HttpListenerHost:DefaultRedirectPath", "web/index.html" },
- { "MusicBrainz:BaseUrl", "https://www.musicbrainz.org" },
+ { HostWebClientKey, bool.TrueString },
+ { HttpListenerHost.DefaultRedirectKey, "web/index.html" },
+ { InstallationManager.PluginManifestUrlKey, "https://repo.jellyfin.org/releases/plugin/manifest-stable.json" },
{ FfmpegProbeSizeKey, "1G" },
- { FfmpegAnalyzeDurationKey, "200M" }
+ { FfmpegAnalyzeDurationKey, "200M" },
+ { PlaylistsAllowDuplicatesKey, bool.TrueString }
};
}
}
diff --git a/Emby.Server.Implementations/Cryptography/CryptographyProvider.cs b/Emby.Server.Implementations/Cryptography/CryptographyProvider.cs
index de83b023d..a037415a9 100644
--- a/Emby.Server.Implementations/Cryptography/CryptographyProvider.cs
+++ b/Emby.Server.Implementations/Cryptography/CryptographyProvider.cs
@@ -31,7 +31,7 @@ namespace Emby.Server.Implementations.Cryptography
private RandomNumberGenerator _randomNumberGenerator;
- private bool _disposed = false;
+ private bool _disposed;
/// <summary>
/// Initializes a new instance of the <see cref="CryptographyProvider"/> class.
@@ -56,15 +56,13 @@ namespace Emby.Server.Implementations.Cryptography
{
// downgrading for now as we need this library to be dotnetstandard compliant
// with this downgrade we'll add a check to make sure we're on the downgrade method at the moment
- if (method == DefaultHashMethod)
+ if (method != DefaultHashMethod)
{
- using (var r = new Rfc2898DeriveBytes(bytes, salt, iterations))
- {
- return r.GetBytes(32);
- }
+ throw new CryptographicException($"Cannot currently use PBKDF2 with requested hash method: {method}");
}
- throw new CryptographicException($"Cannot currently use PBKDF2 with requested hash method: {method}");
+ using var r = new Rfc2898DeriveBytes(bytes, salt, iterations);
+ return r.GetBytes(32);
}
/// <inheritdoc />
@@ -74,25 +72,22 @@ namespace Emby.Server.Implementations.Cryptography
{
return PBKDF2(hashMethod, bytes, salt, DefaultIterations);
}
- else if (_supportedHashMethods.Contains(hashMethod))
+
+ if (!_supportedHashMethods.Contains(hashMethod))
+ {
+ throw new CryptographicException($"Requested hash method is not supported: {hashMethod}");
+ }
+
+ using var h = HashAlgorithm.Create(hashMethod);
+ if (salt.Length == 0)
{
- using (var h = HashAlgorithm.Create(hashMethod))
- {
- if (salt.Length == 0)
- {
- return h.ComputeHash(bytes);
- }
- else
- {
- byte[] salted = new byte[bytes.Length + salt.Length];
- Array.Copy(bytes, salted, bytes.Length);
- Array.Copy(salt, 0, salted, bytes.Length, salt.Length);
- return h.ComputeHash(salted);
- }
- }
+ return h.ComputeHash(bytes);
}
- throw new CryptographicException($"Requested hash method is not supported: {hashMethod}");
+ byte[] salted = new byte[bytes.Length + salt.Length];
+ Array.Copy(bytes, salted, bytes.Length);
+ Array.Copy(salt, 0, salted, bytes.Length, salt.Length);
+ return h.ComputeHash(salted);
}
/// <inheritdoc />
diff --git a/Emby.Server.Implementations/Data/BaseSqliteRepository.cs b/Emby.Server.Implementations/Data/BaseSqliteRepository.cs
index b7f643819..0654132f4 100644
--- a/Emby.Server.Implementations/Data/BaseSqliteRepository.cs
+++ b/Emby.Server.Implementations/Data/BaseSqliteRepository.cs
@@ -1,5 +1,4 @@
#pragma warning disable CS1591
-#pragma warning disable SA1600
using System;
using System.Collections.Generic;
diff --git a/Emby.Server.Implementations/Data/CleanDatabaseScheduledTask.cs b/Emby.Server.Implementations/Data/CleanDatabaseScheduledTask.cs
index 8a5387e9b..37c678a5d 100644
--- a/Emby.Server.Implementations/Data/CleanDatabaseScheduledTask.cs
+++ b/Emby.Server.Implementations/Data/CleanDatabaseScheduledTask.cs
@@ -1,5 +1,4 @@
#pragma warning disable CS1591
-#pragma warning disable SA1600
using System;
using System.Threading;
@@ -15,7 +14,7 @@ namespace Emby.Server.Implementations.Data
private readonly ILibraryManager _libraryManager;
private readonly ILogger _logger;
- public CleanDatabaseScheduledTask(ILibraryManager libraryManager, ILogger logger)
+ public CleanDatabaseScheduledTask(ILibraryManager libraryManager, ILogger<CleanDatabaseScheduledTask> logger)
{
_libraryManager = libraryManager;
_logger = logger;
diff --git a/Emby.Server.Implementations/Data/ManagedConnection.cs b/Emby.Server.Implementations/Data/ManagedConnection.cs
index 2c2f19cd3..5c094ddd2 100644
--- a/Emby.Server.Implementations/Data/ManagedConnection.cs
+++ b/Emby.Server.Implementations/Data/ManagedConnection.cs
@@ -1,5 +1,4 @@
#pragma warning disable CS1591
-#pragma warning disable SA1600
using System;
using System.Collections.Generic;
diff --git a/Emby.Server.Implementations/Data/SqliteDisplayPreferencesRepository.cs b/Emby.Server.Implementations/Data/SqliteDisplayPreferencesRepository.cs
index 8087419ce..d474f1c6b 100644
--- a/Emby.Server.Implementations/Data/SqliteDisplayPreferencesRepository.cs
+++ b/Emby.Server.Implementations/Data/SqliteDisplayPreferencesRepository.cs
@@ -1,5 +1,4 @@
#pragma warning disable CS1591
-#pragma warning disable SA1600
using System;
using System.Collections.Generic;
diff --git a/Emby.Server.Implementations/Data/SqliteExtensions.cs b/Emby.Server.Implementations/Data/SqliteExtensions.cs
index 55c24ccc0..716e5071d 100644
--- a/Emby.Server.Implementations/Data/SqliteExtensions.cs
+++ b/Emby.Server.Implementations/Data/SqliteExtensions.cs
@@ -1,11 +1,8 @@
#pragma warning disable CS1591
-#pragma warning disable SA1600
using System;
using System.Collections.Generic;
using System.Globalization;
-using System.IO;
-using MediaBrowser.Model.Serialization;
using SQLitePCL.pretty;
namespace Emby.Server.Implementations.Data
@@ -110,25 +107,6 @@ namespace Emby.Server.Implementations.Data
return null;
}
- /// <summary>
- /// Serializes to bytes.
- /// </summary>
- /// <returns>System.Byte[][].</returns>
- /// <exception cref="ArgumentNullException">obj</exception>
- public static byte[] SerializeToBytes(this IJsonSerializer json, object obj)
- {
- if (obj == null)
- {
- throw new ArgumentNullException(nameof(obj));
- }
-
- using (var stream = new MemoryStream())
- {
- json.SerializeToStream(obj, stream);
- return stream.ToArray();
- }
- }
-
public static void Attach(SQLiteDatabaseConnection db, string path, string alias)
{
var commandText = string.Format(
@@ -288,7 +266,7 @@ namespace Emby.Server.Implementations.Data
}
}
- public static void TryBind(this IStatement statement, string name, byte[] value)
+ public static void TryBind(this IStatement statement, string name, ReadOnlySpan<byte> value)
{
if (statement.BindParameters.TryGetValue(name, out IBindParameter bindParam))
{
@@ -384,11 +362,11 @@ namespace Emby.Server.Implementations.Data
}
}
- public static IEnumerable<IReadOnlyList<IResultSetValue>> ExecuteQuery(this IStatement This)
+ public static IEnumerable<IReadOnlyList<IResultSetValue>> ExecuteQuery(this IStatement statement)
{
- while (This.MoveNext())
+ while (statement.MoveNext())
{
- yield return This.Current;
+ yield return statement.Current;
}
}
}
diff --git a/Emby.Server.Implementations/Data/SqliteItemRepository.cs b/Emby.Server.Implementations/Data/SqliteItemRepository.cs
index c514846e5..ca5cd6fdd 100644
--- a/Emby.Server.Implementations/Data/SqliteItemRepository.cs
+++ b/Emby.Server.Implementations/Data/SqliteItemRepository.cs
@@ -39,12 +39,11 @@ namespace Emby.Server.Implementations.Data
{
private const string ChaptersTableName = "Chapters2";
- /// <summary>
- /// The _app paths
- /// </summary>
private readonly IServerConfigurationManager _config;
private readonly IServerApplicationHost _appHost;
private readonly ILocalizationManager _localization;
+ // TODO: Remove this dependency. GetImageCacheTag() is the only method used and it can be converted to a static helper method
+ private readonly IImageProcessor _imageProcessor;
private readonly TypeMapper _typeMapper;
private readonly JsonSerializerOptions _jsonOptions;
@@ -71,7 +70,8 @@ namespace Emby.Server.Implementations.Data
IServerConfigurationManager config,
IServerApplicationHost appHost,
ILogger<SqliteItemRepository> logger,
- ILocalizationManager localization)
+ ILocalizationManager localization,
+ IImageProcessor imageProcessor)
: base(logger)
{
if (config == null)
@@ -82,6 +82,7 @@ namespace Emby.Server.Implementations.Data
_config = config;
_appHost = appHost;
_localization = localization;
+ _imageProcessor = imageProcessor;
_typeMapper = new TypeMapper();
_jsonOptions = JsonDefaults.GetOptions();
@@ -98,8 +99,6 @@ namespace Emby.Server.Implementations.Data
/// <inheritdoc />
protected override TempStoreMode TempStore => TempStoreMode.Memory;
- public IImageProcessor ImageProcessor { get; set; }
-
/// <summary>
/// Opens the connection to the database
/// </summary>
@@ -454,7 +453,7 @@ namespace Emby.Server.Implementations.Data
private static string GetSaveItemCommandText()
{
- var saveColumns = new []
+ var saveColumns = new[]
{
"guid",
"type",
@@ -560,7 +559,7 @@ namespace Emby.Server.Implementations.Data
throw new ArgumentNullException(nameof(item));
}
- SaveItems(new [] { item }, cancellationToken);
+ SaveItems(new[] { item }, cancellationToken);
}
public void SaveImages(BaseItem item)
@@ -1622,7 +1621,7 @@ namespace Emby.Server.Implementations.Data
{
IEnumerable<MetadataFields> GetLockedFields(string s)
{
- foreach (var i in s.Split(new [] { '|' }, StringSplitOptions.RemoveEmptyEntries))
+ foreach (var i in s.Split(new[] { '|' }, StringSplitOptions.RemoveEmptyEntries))
{
if (Enum.TryParse(i, true, out MetadataFields parsedValue))
{
@@ -1818,7 +1817,7 @@ namespace Emby.Server.Implementations.Data
{
if (!reader.IsDBNull(index))
{
- item.ProductionLocations = reader.GetString(index).Split(new [] { '|' }, StringSplitOptions.RemoveEmptyEntries).ToArray();
+ item.ProductionLocations = reader.GetString(index).Split(new[] { '|' }, StringSplitOptions.RemoveEmptyEntries).ToArray();
}
index++;
}
@@ -1991,7 +1990,14 @@ namespace Emby.Server.Implementations.Data
if (!string.IsNullOrEmpty(chapter.ImagePath))
{
- chapter.ImageTag = ImageProcessor.GetImageCacheTag(item, chapter);
+ try
+ {
+ chapter.ImageTag = _imageProcessor.GetImageCacheTag(item, chapter);
+ }
+ catch (Exception ex)
+ {
+ Logger.LogError(ex, "Failed to create image cache tag.");
+ }
}
}
@@ -2006,7 +2012,7 @@ namespace Emby.Server.Implementations.Data
/// <summary>
/// Saves the chapters.
/// </summary>
- public void SaveChapters(Guid id, List<ChapterInfo> chapters)
+ public void SaveChapters(Guid id, IReadOnlyList<ChapterInfo> chapters)
{
CheckDisposed();
@@ -2035,22 +2041,24 @@ namespace Emby.Server.Implementations.Data
}
}
- private void InsertChapters(byte[] idBlob, List<ChapterInfo> chapters, IDatabaseConnection db)
+ private void InsertChapters(byte[] idBlob, IReadOnlyList<ChapterInfo> chapters, IDatabaseConnection db)
{
var startIndex = 0;
var limit = 100;
var chapterIndex = 0;
+ const string StartInsertText = "insert into " + ChaptersTableName + " (ItemId, ChapterIndex, StartPositionTicks, Name, ImagePath, ImageDateModified) values ";
+ var insertText = new StringBuilder(StartInsertText, 256);
+
while (startIndex < chapters.Count)
{
- var insertText = new StringBuilder("insert into " + ChaptersTableName + " (ItemId, ChapterIndex, StartPositionTicks, Name, ImagePath, ImageDateModified) values ");
-
var endIndex = Math.Min(chapters.Count, startIndex + limit);
for (var i = startIndex; i < endIndex; i++)
{
insertText.AppendFormat("(@ItemId, @ChapterIndex{0}, @StartPositionTicks{0}, @Name{0}, @ImagePath{0}, @ImageDateModified{0}),", i.ToString(CultureInfo.InvariantCulture));
}
+
insertText.Length -= 1; // Remove last ,
using (var statement = PrepareStatement(db, insertText.ToString()))
@@ -2077,6 +2085,7 @@ namespace Emby.Server.Implementations.Data
}
startIndex += limit;
+ insertText.Length = StartInsertText.Length;
}
}
@@ -2897,8 +2906,8 @@ namespace Emby.Server.Implementations.Data
BindSimilarParams(query, statement);
BindSearchParams(query, statement);
- // Running this again will bind the params
- GetWhereClauses(query, statement);
+ // Running this again will bind the params
+ GetWhereClauses(query, statement);
result.TotalRecordCount = statement.ExecuteQuery().SelectScalarInt().First();
}
@@ -2914,29 +2923,30 @@ namespace Emby.Server.Implementations.Data
private string GetOrderByText(InternalItemsQuery query)
{
var orderBy = query.OrderBy;
- if (string.IsNullOrEmpty(query.SearchTerm))
+ bool hasSimilar = query.SimilarTo != null;
+ bool hasSearch = !string.IsNullOrEmpty(query.SearchTerm);
+
+ if (hasSimilar || hasSearch)
{
- int oldLen = orderBy.Count;
- if (oldLen == 0 && query.SimilarTo != null)
+ List<(string, SortOrder)> prepend = new List<(string, SortOrder)>(4);
+ if (hasSearch)
{
- var arr = new (string, SortOrder)[oldLen + 2];
- orderBy.CopyTo(arr, 0);
- arr[oldLen] = ("SimilarityScore", SortOrder.Descending);
- arr[oldLen + 1] = (ItemSortBy.Random, SortOrder.Ascending);
- query.OrderBy = arr;
+ prepend.Add(("SearchScore", SortOrder.Descending));
+ prepend.Add((ItemSortBy.SortName, SortOrder.Ascending));
}
- }
- else
- {
- query.OrderBy = new[]
- {
- ("SearchScore", SortOrder.Descending),
- (ItemSortBy.SortName, SortOrder.Ascending)
- };
- }
+ if (hasSimilar)
+ {
+ prepend.Add(("SimilarityScore", SortOrder.Descending));
+ prepend.Add((ItemSortBy.Random, SortOrder.Ascending));
+ }
- if (orderBy.Count == 0)
+ var arr = new (string, SortOrder)[prepend.Count + orderBy.Count];
+ prepend.CopyTo(arr, 0);
+ orderBy.CopyTo(arr, prepend.Count);
+ orderBy = query.OrderBy = arr;
+ }
+ else if (orderBy.Count == 0)
{
return string.Empty;
}
@@ -3265,8 +3275,8 @@ namespace Emby.Server.Implementations.Data
BindSimilarParams(query, statement);
BindSearchParams(query, statement);
- // Running this again will bind the params
- GetWhereClauses(query, statement);
+ // Running this again will bind the params
+ GetWhereClauses(query, statement);
foreach (var row in statement.ExecuteQuery())
{
@@ -3287,8 +3297,8 @@ namespace Emby.Server.Implementations.Data
BindSimilarParams(query, statement);
BindSearchParams(query, statement);
- // Running this again will bind the params
- GetWhereClauses(query, statement);
+ // Running this again will bind the params
+ GetWhereClauses(query, statement);
result.TotalRecordCount = statement.ExecuteQuery().SelectScalarInt().First();
}
@@ -3311,7 +3321,7 @@ namespace Emby.Server.Implementations.Data
for (int i = 0; i < str.Length; i++)
{
- if (!(char.IsLetter(str[i])) && (!(char.IsNumber(str[i]))))
+ if (!char.IsLetter(str[i]) && !char.IsNumber(str[i]))
{
return false;
}
@@ -3335,7 +3345,7 @@ namespace Emby.Server.Implementations.Data
return IsAlphaNumeric(value);
}
- private List<string> GetWhereClauses(InternalItemsQuery query, IStatement statement, string paramSuffix = "")
+ private List<string> GetWhereClauses(InternalItemsQuery query, IStatement statement)
{
if (query.IsResumable ?? false)
{
@@ -3347,27 +3357,27 @@ namespace Emby.Server.Implementations.Data
if (query.IsHD.HasValue)
{
- var threshold = 1200;
+ const int Threshold = 1200;
if (query.IsHD.Value)
{
- minWidth = threshold;
+ minWidth = Threshold;
}
else
{
- maxWidth = threshold - 1;
+ maxWidth = Threshold - 1;
}
}
if (query.Is4K.HasValue)
{
- var threshold = 3800;
+ const int Threshold = 3800;
if (query.Is4K.Value)
{
- minWidth = threshold;
+ minWidth = Threshold;
}
else
{
- maxWidth = threshold - 1;
+ maxWidth = Threshold - 1;
}
}
@@ -3376,93 +3386,61 @@ namespace Emby.Server.Implementations.Data
if (minWidth.HasValue)
{
whereClauses.Add("Width>=@MinWidth");
- if (statement != null)
- {
- statement.TryBind("@MinWidth", minWidth);
- }
+ statement?.TryBind("@MinWidth", minWidth);
}
+
if (query.MinHeight.HasValue)
{
whereClauses.Add("Height>=@MinHeight");
- if (statement != null)
- {
- statement.TryBind("@MinHeight", query.MinHeight);
- }
+ statement?.TryBind("@MinHeight", query.MinHeight);
}
+
if (maxWidth.HasValue)
{
whereClauses.Add("Width<=@MaxWidth");
- if (statement != null)
- {
- statement.TryBind("@MaxWidth", maxWidth);
- }
+ statement?.TryBind("@MaxWidth", maxWidth);
}
+
if (query.MaxHeight.HasValue)
{
whereClauses.Add("Height<=@MaxHeight");
- if (statement != null)
- {
- statement.TryBind("@MaxHeight", query.MaxHeight);
- }
+ statement?.TryBind("@MaxHeight", query.MaxHeight);
}
if (query.IsLocked.HasValue)
{
whereClauses.Add("IsLocked=@IsLocked");
- if (statement != null)
- {
- statement.TryBind("@IsLocked", query.IsLocked);
- }
+ statement?.TryBind("@IsLocked", query.IsLocked);
}
var tags = query.Tags.ToList();
var excludeTags = query.ExcludeTags.ToList();
- if (query.IsMovie ?? false)
+ if (query.IsMovie == true)
{
- var alternateTypes = new List<string>();
- if (query.IncludeItemTypes.Length == 0 || query.IncludeItemTypes.Contains(typeof(Movie).Name))
+ if (query.IncludeItemTypes.Length == 0
+ || query.IncludeItemTypes.Contains(nameof(Movie))
+ || query.IncludeItemTypes.Contains(nameof(Trailer)))
{
- alternateTypes.Add(typeof(Movie).FullName);
- }
- if (query.IncludeItemTypes.Length == 0 || query.IncludeItemTypes.Contains(typeof(Trailer).Name))
- {
- alternateTypes.Add(typeof(Trailer).FullName);
- }
-
- var programAttribtues = new List<string>();
- if (alternateTypes.Count == 0)
- {
- programAttribtues.Add("IsMovie=@IsMovie");
+ whereClauses.Add("(IsMovie is null OR IsMovie=@IsMovie)");
}
else
{
- programAttribtues.Add("(IsMovie is null OR IsMovie=@IsMovie)");
- }
-
- if (statement != null)
- {
- statement.TryBind("@IsMovie", true);
+ whereClauses.Add("IsMovie=@IsMovie");
}
- whereClauses.Add("(" + string.Join(" OR ", programAttribtues) + ")");
+ statement?.TryBind("@IsMovie", true);
}
else if (query.IsMovie.HasValue)
{
whereClauses.Add("IsMovie=@IsMovie");
- if (statement != null)
- {
- statement.TryBind("@IsMovie", query.IsMovie);
- }
+ statement?.TryBind("@IsMovie", query.IsMovie);
}
if (query.IsSeries.HasValue)
{
whereClauses.Add("IsSeries=@IsSeries");
- if (statement != null)
- {
- statement.TryBind("@IsSeries", query.IsSeries);
- }
+ statement?.TryBind("@IsSeries", query.IsSeries);
}
if (query.IsSports.HasValue)
@@ -3514,27 +3492,10 @@ namespace Emby.Server.Implementations.Data
if (query.IsFolder.HasValue)
{
whereClauses.Add("IsFolder=@IsFolder");
- if (statement != null)
- {
- statement.TryBind("@IsFolder", query.IsFolder);
- }
+ statement?.TryBind("@IsFolder", query.IsFolder);
}
var includeTypes = query.IncludeItemTypes.SelectMany(MapIncludeItemTypes).ToArray();
- if (includeTypes.Length == 1)
- {
- whereClauses.Add("type=@type");
- if (statement != null)
- {
- statement.TryBind("@type", includeTypes[0]);
- }
- }
- else if (includeTypes.Length > 1)
- {
- var inClause = string.Join(",", includeTypes.Select(i => "'" + i + "'"));
- whereClauses.Add($"type in ({inClause})");
- }
-
// Only specify excluded types if no included types are specified
if (includeTypes.Length == 0)
{
@@ -3542,10 +3503,7 @@ namespace Emby.Server.Implementations.Data
if (excludeTypes.Length == 1)
{
whereClauses.Add("type<>@type");
- if (statement != null)
- {
- statement.TryBind("@type", excludeTypes[0]);
- }
+ statement?.TryBind("@type", excludeTypes[0]);
}
else if (excludeTypes.Length > 1)
{
@@ -3553,14 +3511,21 @@ namespace Emby.Server.Implementations.Data
whereClauses.Add($"type not in ({inClause})");
}
}
+ else if (includeTypes.Length == 1)
+ {
+ whereClauses.Add("type=@type");
+ statement?.TryBind("@type", includeTypes[0]);
+ }
+ else if (includeTypes.Length > 1)
+ {
+ var inClause = string.Join(",", includeTypes.Select(i => "'" + i + "'"));
+ whereClauses.Add($"type in ({inClause})");
+ }
if (query.ChannelIds.Length == 1)
{
whereClauses.Add("ChannelId=@ChannelId");
- if (statement != null)
- {
- statement.TryBind("@ChannelId", query.ChannelIds[0].ToString("N", CultureInfo.InvariantCulture));
- }
+ statement?.TryBind("@ChannelId", query.ChannelIds[0].ToString("N", CultureInfo.InvariantCulture));
}
else if (query.ChannelIds.Length > 1)
{
@@ -3571,98 +3536,65 @@ namespace Emby.Server.Implementations.Data
if (!query.ParentId.Equals(Guid.Empty))
{
whereClauses.Add("ParentId=@ParentId");
- if (statement != null)
- {
- statement.TryBind("@ParentId", query.ParentId);
- }
+ statement?.TryBind("@ParentId", query.ParentId);
}
if (!string.IsNullOrWhiteSpace(query.Path))
{
whereClauses.Add("Path=@Path");
- if (statement != null)
- {
- statement.TryBind("@Path", GetPathToSave(query.Path));
- }
+ statement?.TryBind("@Path", GetPathToSave(query.Path));
}
if (!string.IsNullOrWhiteSpace(query.PresentationUniqueKey))
{
whereClauses.Add("PresentationUniqueKey=@PresentationUniqueKey");
- if (statement != null)
- {
- statement.TryBind("@PresentationUniqueKey", query.PresentationUniqueKey);
- }
+ statement?.TryBind("@PresentationUniqueKey", query.PresentationUniqueKey);
}
if (query.MinCommunityRating.HasValue)
{
whereClauses.Add("CommunityRating>=@MinCommunityRating");
- if (statement != null)
- {
- statement.TryBind("@MinCommunityRating", query.MinCommunityRating.Value);
- }
+ statement?.TryBind("@MinCommunityRating", query.MinCommunityRating.Value);
}
if (query.MinIndexNumber.HasValue)
{
whereClauses.Add("IndexNumber>=@MinIndexNumber");
- if (statement != null)
- {
- statement.TryBind("@MinIndexNumber", query.MinIndexNumber.Value);
- }
+ statement?.TryBind("@MinIndexNumber", query.MinIndexNumber.Value);
}
if (query.MinDateCreated.HasValue)
{
whereClauses.Add("DateCreated>=@MinDateCreated");
- if (statement != null)
- {
- statement.TryBind("@MinDateCreated", query.MinDateCreated.Value);
- }
+ statement?.TryBind("@MinDateCreated", query.MinDateCreated.Value);
}
if (query.MinDateLastSaved.HasValue)
{
whereClauses.Add("(DateLastSaved not null and DateLastSaved>=@MinDateLastSavedForUser)");
- if (statement != null)
- {
- statement.TryBind("@MinDateLastSaved", query.MinDateLastSaved.Value);
- }
+ statement?.TryBind("@MinDateLastSaved", query.MinDateLastSaved.Value);
}
if (query.MinDateLastSavedForUser.HasValue)
{
whereClauses.Add("(DateLastSaved not null and DateLastSaved>=@MinDateLastSavedForUser)");
- if (statement != null)
- {
- statement.TryBind("@MinDateLastSavedForUser", query.MinDateLastSavedForUser.Value);
- }
+ statement?.TryBind("@MinDateLastSavedForUser", query.MinDateLastSavedForUser.Value);
}
if (query.IndexNumber.HasValue)
{
whereClauses.Add("IndexNumber=@IndexNumber");
- if (statement != null)
- {
- statement.TryBind("@IndexNumber", query.IndexNumber.Value);
- }
+ statement?.TryBind("@IndexNumber", query.IndexNumber.Value);
}
if (query.ParentIndexNumber.HasValue)
{
whereClauses.Add("ParentIndexNumber=@ParentIndexNumber");
- if (statement != null)
- {
- statement.TryBind("@ParentIndexNumber", query.ParentIndexNumber.Value);
- }
+ statement?.TryBind("@ParentIndexNumber", query.ParentIndexNumber.Value);
}
if (query.ParentIndexNumberNotEquals.HasValue)
{
whereClauses.Add("(ParentIndexNumber<>@ParentIndexNumberNotEquals or ParentIndexNumber is null)");
- if (statement != null)
- {
- statement.TryBind("@ParentIndexNumberNotEquals", query.ParentIndexNumberNotEquals.Value);
- }
+ statement?.TryBind("@ParentIndexNumberNotEquals", query.ParentIndexNumberNotEquals.Value);
}
var minEndDate = query.MinEndDate;
@@ -3683,73 +3615,59 @@ namespace Emby.Server.Implementations.Data
if (minEndDate.HasValue)
{
whereClauses.Add("EndDate>=@MinEndDate");
- if (statement != null)
- {
- statement.TryBind("@MinEndDate", minEndDate.Value);
- }
+ statement?.TryBind("@MinEndDate", minEndDate.Value);
}
if (maxEndDate.HasValue)
{
whereClauses.Add("EndDate<=@MaxEndDate");
- if (statement != null)
- {
- statement.TryBind("@MaxEndDate", maxEndDate.Value);
- }
+ statement?.TryBind("@MaxEndDate", maxEndDate.Value);
}
if (query.MinStartDate.HasValue)
{
whereClauses.Add("StartDate>=@MinStartDate");
- if (statement != null)
- {
- statement.TryBind("@MinStartDate", query.MinStartDate.Value);
- }
+ statement?.TryBind("@MinStartDate", query.MinStartDate.Value);
}
if (query.MaxStartDate.HasValue)
{
whereClauses.Add("StartDate<=@MaxStartDate");
- if (statement != null)
- {
- statement.TryBind("@MaxStartDate", query.MaxStartDate.Value);
- }
+ statement?.TryBind("@MaxStartDate", query.MaxStartDate.Value);
}
if (query.MinPremiereDate.HasValue)
{
whereClauses.Add("PremiereDate>=@MinPremiereDate");
- if (statement != null)
- {
- statement.TryBind("@MinPremiereDate", query.MinPremiereDate.Value);
- }
+ statement?.TryBind("@MinPremiereDate", query.MinPremiereDate.Value);
}
+
if (query.MaxPremiereDate.HasValue)
{
whereClauses.Add("PremiereDate<=@MaxPremiereDate");
- if (statement != null)
- {
- statement.TryBind("@MaxPremiereDate", query.MaxPremiereDate.Value);
- }
+ statement?.TryBind("@MaxPremiereDate", query.MaxPremiereDate.Value);
}
- if (query.TrailerTypes.Length > 0)
+ var trailerTypes = query.TrailerTypes;
+ int trailerTypesLen = trailerTypes.Length;
+ if (trailerTypesLen > 0)
{
- var clauses = new List<string>();
- var index = 0;
- foreach (var type in query.TrailerTypes)
+ const string Or = " OR ";
+ StringBuilder clause = new StringBuilder("(", trailerTypesLen * 32);
+ for (int i = 0; i < trailerTypesLen; i++)
{
- var paramName = "@TrailerTypes" + index;
-
- clauses.Add("TrailerTypes like " + paramName);
- if (statement != null)
- {
- statement.TryBind(paramName, "%" + type + "%");
- }
- index++;
+ var paramName = "@TrailerTypes" + i;
+ clause.Append("TrailerTypes like ")
+ .Append(paramName)
+ .Append(Or);
+ statement?.TryBind(paramName, "%" + trailerTypes[i] + "%");
}
- var clause = "(" + string.Join(" OR ", clauses) + ")";
- whereClauses.Add(clause);
+
+ // Remove last " OR "
+ clause.Length -= Or.Length;
+ clause.Append(')');
+
+ whereClauses.Add(clause.ToString());
}
if (query.IsAiring.HasValue)
@@ -3757,24 +3675,15 @@ namespace Emby.Server.Implementations.Data
if (query.IsAiring.Value)
{
whereClauses.Add("StartDate<=@MaxStartDate");
- if (statement != null)
- {
- statement.TryBind("@MaxStartDate", DateTime.UtcNow);
- }
+ statement?.TryBind("@MaxStartDate", DateTime.UtcNow);
whereClauses.Add("EndDate>=@MinEndDate");
- if (statement != null)
- {
- statement.TryBind("@MinEndDate", DateTime.UtcNow);
- }
+ statement?.TryBind("@MinEndDate", DateTime.UtcNow);
}
else
{
whereClauses.Add("(StartDate>@IsAiringDate OR EndDate < @IsAiringDate)");
- if (statement != null)
- {
- statement.TryBind("@IsAiringDate", DateTime.UtcNow);
- }
+ statement?.TryBind("@IsAiringDate", DateTime.UtcNow);
}
}
@@ -3789,13 +3698,10 @@ namespace Emby.Server.Implementations.Data
var paramName = "@PersonId" + index;
clauses.Add("(guid in (select itemid from People where Name = (select Name from TypedBaseItems where guid=" + paramName + ")))");
-
- if (statement != null)
- {
- statement.TryBind(paramName, personId.ToByteArray());
- }
+ statement?.TryBind(paramName, personId.ToByteArray());
index++;
}
+
var clause = "(" + string.Join(" OR ", clauses) + ")";
whereClauses.Add(clause);
}
@@ -3803,47 +3709,31 @@ namespace Emby.Server.Implementations.Data
if (!string.IsNullOrWhiteSpace(query.Person))
{
whereClauses.Add("Guid in (select ItemId from People where Name=@PersonName)");
- if (statement != null)
- {
- statement.TryBind("@PersonName", query.Person);
- }
+ statement?.TryBind("@PersonName", query.Person);
}
if (!string.IsNullOrWhiteSpace(query.MinSortName))
{
whereClauses.Add("SortName>=@MinSortName");
- if (statement != null)
- {
- statement.TryBind("@MinSortName", query.MinSortName);
- }
+ statement?.TryBind("@MinSortName", query.MinSortName);
}
if (!string.IsNullOrWhiteSpace(query.ExternalSeriesId))
{
whereClauses.Add("ExternalSeriesId=@ExternalSeriesId");
- if (statement != null)
- {
- statement.TryBind("@ExternalSeriesId", query.ExternalSeriesId);
- }
+ statement?.TryBind("@ExternalSeriesId", query.ExternalSeriesId);
}
if (!string.IsNullOrWhiteSpace(query.ExternalId))
{
whereClauses.Add("ExternalId=@ExternalId");
- if (statement != null)
- {
- statement.TryBind("@ExternalId", query.ExternalId);
- }
+ statement?.TryBind("@ExternalId", query.ExternalId);
}
if (!string.IsNullOrWhiteSpace(query.Name))
{
whereClauses.Add("CleanName=@Name");
-
- if (statement != null)
- {
- statement.TryBind("@Name", GetCleanValue(query.Name));
- }
+ statement?.TryBind("@Name", GetCleanValue(query.Name));
}
// These are the same, for now
@@ -3862,28 +3752,21 @@ namespace Emby.Server.Implementations.Data
if (!string.IsNullOrWhiteSpace(query.NameStartsWith))
{
whereClauses.Add("SortName like @NameStartsWith");
- if (statement != null)
- {
- statement.TryBind("@NameStartsWith", query.NameStartsWith + "%");
- }
+ statement?.TryBind("@NameStartsWith", query.NameStartsWith + "%");
}
+
if (!string.IsNullOrWhiteSpace(query.NameStartsWithOrGreater))
{
whereClauses.Add("SortName >= @NameStartsWithOrGreater");
// lowercase this because SortName is stored as lowercase
- if (statement != null)
- {
- statement.TryBind("@NameStartsWithOrGreater", query.NameStartsWithOrGreater.ToLowerInvariant());
- }
+ statement?.TryBind("@NameStartsWithOrGreater", query.NameStartsWithOrGreater.ToLowerInvariant());
}
+
if (!string.IsNullOrWhiteSpace(query.NameLessThan))
{
whereClauses.Add("SortName < @NameLessThan");
// lowercase this because SortName is stored as lowercase
- if (statement != null)
- {
- statement.TryBind("@NameLessThan", query.NameLessThan.ToLowerInvariant());
- }
+ statement?.TryBind("@NameLessThan", query.NameLessThan.ToLowerInvariant());
}
if (query.ImageTypes.Length > 0)
@@ -3899,18 +3782,12 @@ namespace Emby.Server.Implementations.Data
if (query.IsLiked.Value)
{
whereClauses.Add("rating>=@UserRating");
- if (statement != null)
- {
- statement.TryBind("@UserRating", UserItemData.MinLikeValue);
- }
+ statement?.TryBind("@UserRating", UserItemData.MinLikeValue);
}
else
{
whereClauses.Add("(rating is null or rating<@UserRating)");
- if (statement != null)
- {
- statement.TryBind("@UserRating", UserItemData.MinLikeValue);
- }
+ statement?.TryBind("@UserRating", UserItemData.MinLikeValue);
}
}
@@ -3924,10 +3801,8 @@ namespace Emby.Server.Implementations.Data
{
whereClauses.Add("(IsFavorite is null or IsFavorite=@IsFavoriteOrLiked)");
}
- if (statement != null)
- {
- statement.TryBind("@IsFavoriteOrLiked", query.IsFavoriteOrLiked.Value);
- }
+
+ statement?.TryBind("@IsFavoriteOrLiked", query.IsFavoriteOrLiked.Value);
}
if (query.IsFavorite.HasValue)
@@ -3940,10 +3815,8 @@ namespace Emby.Server.Implementations.Data
{
whereClauses.Add("(IsFavorite is null or IsFavorite=@IsFavorite)");
}
- if (statement != null)
- {
- statement.TryBind("@IsFavorite", query.IsFavorite.Value);
- }
+
+ statement?.TryBind("@IsFavorite", query.IsFavorite.Value);
}
if (EnableJoinUserData(query))
@@ -3972,10 +3845,8 @@ namespace Emby.Server.Implementations.Data
{
whereClauses.Add("(played is null or played=@IsPlayed)");
}
- if (statement != null)
- {
- statement.TryBind("@IsPlayed", query.IsPlayed.Value);
- }
+
+ statement?.TryBind("@IsPlayed", query.IsPlayed.Value);
}
}
}
@@ -4007,6 +3878,7 @@ namespace Emby.Server.Implementations.Data
}
index++;
}
+
var clause = "(" + string.Join(" OR ", clauses) + ")";
whereClauses.Add(clause);
}
@@ -4026,6 +3898,7 @@ namespace Emby.Server.Implementations.Data
}
index++;
}
+
var clause = "(" + string.Join(" OR ", clauses) + ")";
whereClauses.Add(clause);
}
@@ -4759,18 +4632,22 @@ namespace Emby.Server.Implementations.Data
{
list.Add(typeof(Person).Name);
}
+
if (IsTypeInQuery(typeof(Genre).Name, query))
{
list.Add(typeof(Genre).Name);
}
+
if (IsTypeInQuery(typeof(MusicGenre).Name, query))
{
list.Add(typeof(MusicGenre).Name);
}
+
if (IsTypeInQuery(typeof(MusicArtist).Name, query))
{
list.Add(typeof(MusicArtist).Name);
}
+
if (IsTypeInQuery(typeof(Studio).Name, query))
{
list.Add(typeof(Studio).Name);
@@ -4844,7 +4721,7 @@ namespace Emby.Server.Implementations.Data
return false;
}
- private static readonly Type[] KnownTypes =
+ private static readonly Type[] _knownTypes =
{
typeof(LiveTvProgram),
typeof(LiveTvChannel),
@@ -4913,7 +4790,7 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type
{
var dict = new Dictionary<string, string[]>(StringComparer.OrdinalIgnoreCase);
- foreach (var t in KnownTypes)
+ foreach (var t in _knownTypes)
{
dict[t.Name] = new[] { t.FullName };
}
@@ -4925,9 +4802,9 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type
}
// Not crazy about having this all the way down here, but at least it's in one place
- readonly Dictionary<string, string[]> _types = GetTypeMapDictionary();
+ private readonly Dictionary<string, string[]> _types = GetTypeMapDictionary();
- private IEnumerable<string> MapIncludeItemTypes(string value)
+ private string[] MapIncludeItemTypes(string value)
{
if (_types.TryGetValue(value, out string[] result))
{
@@ -4942,7 +4819,7 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type
return Array.Empty<string>();
}
- public void DeleteItem(Guid id, CancellationToken cancellationToken)
+ public void DeleteItem(Guid id)
{
if (id == Guid.Empty)
{
@@ -4978,7 +4855,7 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type
}
}
- private void ExecuteWithSingleParam(IDatabaseConnection db, string query, byte[] value)
+ private void ExecuteWithSingleParam(IDatabaseConnection db, string query, ReadOnlySpan<byte> value)
{
using (var statement = PrepareStatement(db, query))
{
@@ -5008,6 +4885,11 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type
commandText += " order by ListOrder";
+ if (query.Limit > 0)
+ {
+ commandText += " LIMIT " + query.Limit;
+ }
+
using (var connection = GetConnection(true))
{
var list = new List<string>();
@@ -5046,6 +4928,11 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type
commandText += " order by ListOrder";
+ if (query.Limit > 0)
+ {
+ commandText += " LIMIT " + query.Limit;
+ }
+
using (var connection = GetConnection(true))
{
var list = new List<PersonInfo>();
@@ -5528,6 +5415,7 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type
{
GetWhereClauses(typeSubQuery, null);
}
+
BindSimilarParams(query, statement);
BindSearchParams(query, statement);
GetWhereClauses(innerQuery, statement);
@@ -5569,7 +5457,7 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type
}
var allTypes = typeString.Split(new[] { '|' }, StringSplitOptions.RemoveEmptyEntries)
- .ToLookup(i => i);
+ .ToLookup(x => x);
foreach (var type in allTypes)
{
@@ -5611,32 +5499,32 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type
return counts;
}
- private List<Tuple<int, string>> GetItemValuesToSave(BaseItem item, List<string> inheritedTags)
+ private List<(int, string)> GetItemValuesToSave(BaseItem item, List<string> inheritedTags)
{
- var list = new List<Tuple<int, string>>();
+ var list = new List<(int, string)>();
if (item is IHasArtist hasArtist)
{
- list.AddRange(hasArtist.Artists.Select(i => new Tuple<int, string>(0, i)));
+ list.AddRange(hasArtist.Artists.Select(i => (0, i)));
}
if (item is IHasAlbumArtist hasAlbumArtist)
{
- list.AddRange(hasAlbumArtist.AlbumArtists.Select(i => new Tuple<int, string>(1, i)));
+ list.AddRange(hasAlbumArtist.AlbumArtists.Select(i => (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.Genres.Select(i => (2, i)));
+ list.AddRange(item.Studios.Select(i => (3, i)));
+ list.AddRange(item.Tags.Select(i => (4, i)));
// keywords was 5
- list.AddRange(inheritedTags.Select(i => new Tuple<int, string>(6, i)));
+ list.AddRange(inheritedTags.Select(i => (6, i)));
return list;
}
- private void UpdateItemValues(Guid itemId, List<Tuple<int, string>> values, IDatabaseConnection db)
+ private void UpdateItemValues(Guid itemId, List<(int, string)> values, IDatabaseConnection db)
{
if (itemId.Equals(Guid.Empty))
{
@@ -5658,32 +5546,28 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type
InsertItemValues(guidBlob, values, db);
}
- private void InsertItemValues(byte[] idBlob, List<Tuple<int, string>> values, IDatabaseConnection db)
+ private void InsertItemValues(byte[] idBlob, List<(int, string)> values, IDatabaseConnection db)
{
+ const int Limit = 100;
var startIndex = 0;
- var limit = 100;
while (startIndex < values.Count)
{
var insertText = new StringBuilder("insert into ItemValues (ItemId, Type, Value, CleanValue) values ");
- var endIndex = Math.Min(values.Count, startIndex + limit);
- var isSubsequentRow = false;
+ var endIndex = Math.Min(values.Count, startIndex + Limit);
for (var i = startIndex; i < endIndex; i++)
{
- if (isSubsequentRow)
- {
- insertText.Append(',');
- }
-
insertText.AppendFormat(
CultureInfo.InvariantCulture,
- "(@ItemId, @Type{0}, @Value{0}, @CleanValue{0})",
+ "(@ItemId, @Type{0}, @Value{0}, @CleanValue{0}),",
i);
- isSubsequentRow = true;
}
+ // Remove last comma
+ insertText.Length--;
+
using (var statement = PrepareStatement(db, insertText.ToString()))
{
statement.TryBind("@ItemId", idBlob);
@@ -5711,7 +5595,7 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type
statement.MoveNext();
}
- startIndex += limit;
+ startIndex += Limit;
}
}
@@ -5746,28 +5630,23 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type
private void InsertPeople(byte[] idBlob, List<PersonInfo> people, IDatabaseConnection db)
{
+ const int Limit = 100;
var startIndex = 0;
- var limit = 100;
var listIndex = 0;
while (startIndex < people.Count)
{
var insertText = new StringBuilder("insert into People (ItemId, Name, Role, PersonType, SortOrder, ListOrder) values ");
- var endIndex = Math.Min(people.Count, startIndex + limit);
- var isSubsequentRow = false;
-
+ var endIndex = Math.Min(people.Count, startIndex + Limit);
for (var i = startIndex; i < endIndex; i++)
{
- if (isSubsequentRow)
- {
- insertText.Append(',');
- }
-
- insertText.AppendFormat("(@ItemId, @Name{0}, @Role{0}, @PersonType{0}, @SortOrder{0}, @ListOrder{0})", i.ToString(CultureInfo.InvariantCulture));
- isSubsequentRow = true;
+ insertText.AppendFormat("(@ItemId, @Name{0}, @Role{0}, @PersonType{0}, @SortOrder{0}, @ListOrder{0}),", i.ToString(CultureInfo.InvariantCulture));
}
+ // Remove last comma
+ insertText.Length--;
+
using (var statement = PrepareStatement(db, insertText.ToString()))
{
statement.TryBind("@ItemId", idBlob);
@@ -5791,16 +5670,17 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type
statement.MoveNext();
}
- startIndex += limit;
+ startIndex += Limit;
}
}
private PersonInfo GetPerson(IReadOnlyList<IResultSetValue> reader)
{
- var item = new PersonInfo();
-
- item.ItemId = reader.GetGuid(0);
- item.Name = reader.GetString(1);
+ var item = new PersonInfo
+ {
+ ItemId = reader.GetGuid(0),
+ Name = reader.GetString(1)
+ };
if (!reader.IsDBNull(2))
{
@@ -5907,20 +5787,28 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type
private void InsertMediaStreams(byte[] idBlob, List<MediaStream> streams, IDatabaseConnection db)
{
+ const int Limit = 10;
var startIndex = 0;
- var limit = 10;
while (startIndex < streams.Count)
{
- var insertText = new StringBuilder(string.Format("insert into mediastreams ({0}) values ", string.Join(",", _mediaStreamSaveColumns)));
+ var insertText = new StringBuilder("insert into mediastreams (");
+ foreach (var column in _mediaStreamSaveColumns)
+ {
+ insertText.Append(column).Append(',');
+ }
- var endIndex = Math.Min(streams.Count, startIndex + limit);
+ // Remove last comma
+ insertText.Length--;
+ insertText.Append(") values ");
+
+ var endIndex = Math.Min(streams.Count, startIndex + Limit);
for (var i = startIndex; i < endIndex; i++)
{
if (i != startIndex)
{
- insertText.Append(",");
+ insertText.Append(',');
}
var index = i.ToString(CultureInfo.InvariantCulture);
@@ -5928,11 +5816,12 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type
foreach (var column in _mediaStreamSaveColumns.Skip(1))
{
- insertText.Append("@" + column + index + ",");
+ insertText.Append('@').Append(column).Append(index).Append(',');
}
+
insertText.Length -= 1; // Remove the last comma
- insertText.Append(")");
+ insertText.Append(')');
}
using (var statement = PrepareStatement(db, insertText.ToString()))
@@ -5994,7 +5883,7 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type
statement.MoveNext();
}
- startIndex += limit;
+ startIndex += Limit;
}
}
@@ -6011,7 +5900,7 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type
Index = reader[1].ToInt()
};
- item.Type = (MediaStreamType)Enum.Parse(typeof(MediaStreamType), reader[2].ToString(), true);
+ item.Type = Enum.Parse<MediaStreamType>(reader[2].ToString(), true);
if (reader[3].SQLiteType != SQLiteType.Null)
{
@@ -6159,7 +6048,8 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type
item.ColorTransfer = reader[34].ToString();
}
- if (item.Type == MediaStreamType.Subtitle){
+ if (item.Type == MediaStreamType.Subtitle)
+ {
item.localizedUndefined = _localization.GetLocalizedString("Undefined");
item.localizedDefault = _localization.GetLocalizedString("Default");
item.localizedForced = _localization.GetLocalizedString("Forced");
@@ -6288,8 +6178,8 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type
statement.TryBind("@Codec" + index, attachment.Codec);
statement.TryBind("@CodecTag" + index, attachment.CodecTag);
statement.TryBind("@Comment" + index, attachment.Comment);
- statement.TryBind("@FileName" + index, attachment.FileName);
- statement.TryBind("@MimeType" + index, attachment.MimeType);
+ statement.TryBind("@Filename" + index, attachment.FileName);
+ statement.TryBind("@MIMEType" + index, attachment.MimeType);
}
statement.Reset();
diff --git a/Emby.Server.Implementations/Data/SqliteUserDataRepository.cs b/Emby.Server.Implementations/Data/SqliteUserDataRepository.cs
index f6c37e4e5..22955850a 100644
--- a/Emby.Server.Implementations/Data/SqliteUserDataRepository.cs
+++ b/Emby.Server.Implementations/Data/SqliteUserDataRepository.cs
@@ -1,5 +1,4 @@
#pragma warning disable CS1591
-#pragma warning disable SA1600
using System;
using System.Collections.Generic;
diff --git a/Emby.Server.Implementations/Data/SqliteUserRepository.cs b/Emby.Server.Implementations/Data/SqliteUserRepository.cs
index c82c93ffc..0c3f26974 100644
--- a/Emby.Server.Implementations/Data/SqliteUserRepository.cs
+++ b/Emby.Server.Implementations/Data/SqliteUserRepository.cs
@@ -1,5 +1,4 @@
#pragma warning disable CS1591
-#pragma warning disable SA1600
using System;
using System.Collections.Generic;
@@ -26,7 +25,7 @@ namespace Emby.Server.Implementations.Data
IServerApplicationPaths appPaths)
: base(logger)
{
- _jsonOptions = JsonDefaults.GetOptions();;
+ _jsonOptions = JsonDefaults.GetOptions();
DbFilePath = Path.Combine(appPaths.DataPath, "users.db");
}
diff --git a/Emby.Server.Implementations/Devices/DeviceId.cs b/Emby.Server.Implementations/Devices/DeviceId.cs
index ff75efa59..f0d43e665 100644
--- a/Emby.Server.Implementations/Devices/DeviceId.cs
+++ b/Emby.Server.Implementations/Devices/DeviceId.cs
@@ -1,5 +1,4 @@
#pragma warning disable CS1591
-#pragma warning disable SA1600
using System;
using System.Globalization;
diff --git a/Emby.Server.Implementations/Devices/DeviceManager.cs b/Emby.Server.Implementations/Devices/DeviceManager.cs
index ef7317050..579cb895e 100644
--- a/Emby.Server.Implementations/Devices/DeviceManager.cs
+++ b/Emby.Server.Implementations/Devices/DeviceManager.cs
@@ -1,5 +1,4 @@
#pragma warning disable CS1591
-#pragma warning disable SA1600
using System;
using System.Collections.Generic;
@@ -39,10 +38,11 @@ namespace Emby.Server.Implementations.Devices
private readonly IServerConfigurationManager _config;
private readonly ILibraryManager _libraryManager;
private readonly ILocalizationManager _localizationManager;
-
private readonly IAuthenticationRepository _authRepo;
+ private readonly Dictionary<string, ClientCapabilities> _capabilitiesCache;
public event EventHandler<GenericEventArgs<Tuple<string, DeviceOptions>>> DeviceOptionsUpdated;
+
public event EventHandler<GenericEventArgs<CameraImageUploadInfo>> CameraImageUploaded;
private readonly object _cameraUploadSyncLock = new object();
@@ -66,10 +66,9 @@ namespace Emby.Server.Implementations.Devices
_libraryManager = libraryManager;
_localizationManager = localizationManager;
_authRepo = authRepo;
+ _capabilitiesCache = new Dictionary<string, ClientCapabilities>(StringComparer.OrdinalIgnoreCase);
}
-
- private Dictionary<string, ClientCapabilities> _capabilitiesCache = new Dictionary<string, ClientCapabilities>(StringComparer.OrdinalIgnoreCase);
public void SaveCapabilities(string deviceId, ClientCapabilities capabilities)
{
var path = Path.Combine(GetDevicePath(deviceId), "capabilities.json");
@@ -142,11 +141,10 @@ namespace Emby.Server.Implementations.Devices
public QueryResult<DeviceInfo> GetDevices(DeviceQuery query)
{
- var sessions = _authRepo.Get(new AuthenticationInfoQuery
+ IEnumerable<AuthenticationInfo> sessions = _authRepo.Get(new AuthenticationInfoQuery
{
//UserId = query.UserId
HasUser = true
-
}).Items;
// TODO: DeviceQuery doesn't seem to be used from client. Not even Swagger.
@@ -154,23 +152,19 @@ namespace Emby.Server.Implementations.Devices
{
var val = query.SupportsSync.Value;
- sessions = sessions.Where(i => GetCapabilities(i.DeviceId).SupportsSync == val).ToArray();
+ sessions = sessions.Where(i => GetCapabilities(i.DeviceId).SupportsSync == val);
}
if (!query.UserId.Equals(Guid.Empty))
{
var user = _userManager.GetUserById(query.UserId);
- sessions = sessions.Where(i => CanAccessDevice(user, i.DeviceId)).ToArray();
+ sessions = sessions.Where(i => CanAccessDevice(user, i.DeviceId));
}
var array = sessions.Select(ToDeviceInfo).ToArray();
- return new QueryResult<DeviceInfo>
- {
- Items = array,
- TotalRecordCount = array.Length
- };
+ return new QueryResult<DeviceInfo>(array);
}
private DeviceInfo ToDeviceInfo(AuthenticationInfo authInfo)
@@ -186,7 +180,7 @@ namespace Emby.Server.Implementations.Devices
LastUserName = authInfo.UserName,
Name = authInfo.DeviceName,
DateLastActivity = authInfo.DateLastActivity,
- IconUrl = caps == null ? null : caps.IconUrl
+ IconUrl = caps?.IconUrl
};
}
@@ -243,7 +237,7 @@ namespace Emby.Server.Implementations.Devices
try
{
- using (var fs = _fileSystem.GetFileStream(path, FileOpenMode.Create, FileAccessMode.Write, FileShareMode.Read))
+ using (var fs = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.Read))
{
await stream.CopyToAsync(fs).ConfigureAwait(false);
}
@@ -412,7 +406,10 @@ namespace Emby.Server.Implementations.Devices
private readonly IServerConfigurationManager _config;
private ILogger _logger;
- public DeviceManagerEntryPoint(IDeviceManager deviceManager, IServerConfigurationManager config, ILogger logger)
+ public DeviceManagerEntryPoint(
+ IDeviceManager deviceManager,
+ IServerConfigurationManager config,
+ ILogger<DeviceManagerEntryPoint> logger)
{
_deviceManager = (DeviceManager)deviceManager;
_config = config;
diff --git a/Emby.Server.Implementations/Diagnostics/CommonProcess.cs b/Emby.Server.Implementations/Diagnostics/CommonProcess.cs
deleted file mode 100644
index f8b754151..000000000
--- a/Emby.Server.Implementations/Diagnostics/CommonProcess.cs
+++ /dev/null
@@ -1,153 +0,0 @@
-#pragma warning disable CS1591
-#pragma warning disable SA1600
-
-using System;
-using System.Diagnostics;
-using System.IO;
-using System.Threading;
-using System.Threading.Tasks;
-using MediaBrowser.Model.Diagnostics;
-
-namespace Emby.Server.Implementations.Diagnostics
-{
- public class CommonProcess : IProcess
- {
- private readonly Process _process;
-
- private bool _disposed = false;
- private bool _hasExited;
-
- public CommonProcess(ProcessOptions options)
- {
- StartInfo = options;
-
- var startInfo = new ProcessStartInfo
- {
- Arguments = options.Arguments,
- FileName = options.FileName,
- WorkingDirectory = options.WorkingDirectory,
- UseShellExecute = options.UseShellExecute,
- CreateNoWindow = options.CreateNoWindow,
- RedirectStandardError = options.RedirectStandardError,
- RedirectStandardInput = options.RedirectStandardInput,
- RedirectStandardOutput = options.RedirectStandardOutput,
- ErrorDialog = options.ErrorDialog
- };
-
-
- if (options.IsHidden)
- {
- startInfo.WindowStyle = ProcessWindowStyle.Hidden;
- }
-
- _process = new Process
- {
- StartInfo = startInfo
- };
-
- if (options.EnableRaisingEvents)
- {
- _process.EnableRaisingEvents = true;
- _process.Exited += OnProcessExited;
- }
- }
-
- public event EventHandler Exited;
-
- public ProcessOptions StartInfo { get; }
-
- public StreamWriter StandardInput => _process.StandardInput;
-
- public StreamReader StandardError => _process.StandardError;
-
- public StreamReader StandardOutput => _process.StandardOutput;
-
- public int ExitCode => _process.ExitCode;
-
- private bool HasExited
- {
- get
- {
- if (_hasExited)
- {
- return true;
- }
-
- try
- {
- _hasExited = _process.HasExited;
- }
- catch (InvalidOperationException)
- {
- _hasExited = true;
- }
-
- return _hasExited;
- }
- }
-
- public void Start()
- {
- _process.Start();
- }
-
- public void Kill()
- {
- _process.Kill();
- }
-
- public bool WaitForExit(int timeMs)
- {
- return _process.WaitForExit(timeMs);
- }
-
- public Task<bool> WaitForExitAsync(int timeMs)
- {
- // Note: For this function to work correctly, the option EnableRisingEvents needs to be set to true.
-
- if (HasExited)
- {
- return Task.FromResult(true);
- }
-
- timeMs = Math.Max(0, timeMs);
-
- var tcs = new TaskCompletionSource<bool>();
-
- var cancellationToken = new CancellationTokenSource(timeMs).Token;
-
- _process.Exited += (sender, args) => tcs.TrySetResult(true);
-
- cancellationToken.Register(() => tcs.TrySetResult(HasExited));
-
- return tcs.Task;
- }
-
- public void Dispose()
- {
- Dispose(true);
- GC.SuppressFinalize(this);
- }
-
- protected virtual void Dispose(bool disposing)
- {
- if (_disposed)
- {
- return;
- }
-
- if (disposing)
- {
- _process?.Dispose();
- }
-
- _disposed = true;
- }
-
- private void OnProcessExited(object sender, EventArgs e)
- {
- _hasExited = true;
- Exited?.Invoke(this, e);
- }
- }
-}
diff --git a/Emby.Server.Implementations/Diagnostics/ProcessFactory.cs b/Emby.Server.Implementations/Diagnostics/ProcessFactory.cs
deleted file mode 100644
index 219f73c78..000000000
--- a/Emby.Server.Implementations/Diagnostics/ProcessFactory.cs
+++ /dev/null
@@ -1,15 +0,0 @@
-#pragma warning disable CS1591
-#pragma warning disable SA1600
-
-using MediaBrowser.Model.Diagnostics;
-
-namespace Emby.Server.Implementations.Diagnostics
-{
- public class ProcessFactory : IProcessFactory
- {
- public IProcess Create(ProcessOptions options)
- {
- return new CommonProcess(options);
- }
- }
-}
diff --git a/Emby.Server.Implementations/Dto/DtoService.cs b/Emby.Server.Implementations/Dto/DtoService.cs
index fcf0360c7..c4b65d265 100644
--- a/Emby.Server.Implementations/Dto/DtoService.cs
+++ b/Emby.Server.Implementations/Dto/DtoService.cs
@@ -1,5 +1,4 @@
#pragma warning disable CS1591
-#pragma warning disable SA1600
using System;
using System.Collections.Generic;
@@ -39,21 +38,23 @@ namespace Emby.Server.Implementations.Dto
private readonly IProviderManager _providerManager;
private readonly IApplicationHost _appHost;
- private readonly Func<IMediaSourceManager> _mediaSourceManager;
- private readonly Func<ILiveTvManager> _livetvManager;
+ private readonly IMediaSourceManager _mediaSourceManager;
+ private readonly Lazy<ILiveTvManager> _livetvManagerFactory;
+
+ private ILiveTvManager LivetvManager => _livetvManagerFactory.Value;
public DtoService(
- ILoggerFactory loggerFactory,
+ ILogger<DtoService> logger,
ILibraryManager libraryManager,
IUserDataManager userDataRepository,
IItemRepository itemRepo,
IImageProcessor imageProcessor,
IProviderManager providerManager,
IApplicationHost appHost,
- Func<IMediaSourceManager> mediaSourceManager,
- Func<ILiveTvManager> livetvManager)
+ IMediaSourceManager mediaSourceManager,
+ Lazy<ILiveTvManager> livetvManagerFactory)
{
- _logger = loggerFactory.CreateLogger(nameof(DtoService));
+ _logger = logger;
_libraryManager = libraryManager;
_userDataRepository = userDataRepository;
_itemRepo = itemRepo;
@@ -61,7 +62,7 @@ namespace Emby.Server.Implementations.Dto
_providerManager = providerManager;
_appHost = appHost;
_mediaSourceManager = mediaSourceManager;
- _livetvManager = livetvManager;
+ _livetvManagerFactory = livetvManagerFactory;
}
/// <summary>
@@ -126,12 +127,12 @@ namespace Emby.Server.Implementations.Dto
if (programTuples.Count > 0)
{
- _livetvManager().AddInfoToProgramDto(programTuples, options.Fields, user).GetAwaiter().GetResult();
+ LivetvManager.AddInfoToProgramDto(programTuples, options.Fields, user).GetAwaiter().GetResult();
}
if (channelTuples.Count > 0)
{
- _livetvManager().AddChannelInfo(channelTuples, options, user);
+ LivetvManager.AddChannelInfo(channelTuples, options, user);
}
return returnItems;
@@ -143,12 +144,12 @@ namespace Emby.Server.Implementations.Dto
if (item is LiveTvChannel tvChannel)
{
var list = new List<(BaseItemDto, LiveTvChannel)>(1) { (dto, tvChannel) };
- _livetvManager().AddChannelInfo(list, options, user);
+ LivetvManager.AddChannelInfo(list, options, user);
}
else if (item is LiveTvProgram)
{
var list = new List<(BaseItem, BaseItemDto)>(1) { (item, dto) };
- var task = _livetvManager().AddInfoToProgramDto(list, options.Fields, user);
+ var task = LivetvManager.AddInfoToProgramDto(list, options.Fields, user);
Task.WaitAll(task);
}
@@ -224,7 +225,7 @@ namespace Emby.Server.Implementations.Dto
if (item is IHasMediaSources
&& options.ContainsField(ItemFields.MediaSources))
{
- dto.MediaSources = _mediaSourceManager().GetStaticMediaSources(item, true, user).ToArray();
+ dto.MediaSources = _mediaSourceManager.GetStaticMediaSources(item, true, user).ToArray();
NormalizeMediaSourceContainers(dto);
}
@@ -255,7 +256,7 @@ namespace Emby.Server.Implementations.Dto
dto.Etag = item.GetEtag(user);
}
- var liveTvManager = _livetvManager();
+ var liveTvManager = LivetvManager;
var activeRecording = liveTvManager.GetActiveRecordingInfo(item.Path);
if (activeRecording != null)
{
@@ -1046,7 +1047,7 @@ namespace Emby.Server.Implementations.Dto
}
else
{
- mediaStreams = _mediaSourceManager().GetStaticMediaSources(item, true)[0].MediaStreams.ToArray();
+ mediaStreams = _mediaSourceManager.GetStaticMediaSources(item, true)[0].MediaStreams.ToArray();
}
dto.MediaStreams = mediaStreams;
@@ -1057,30 +1058,19 @@ namespace Emby.Server.Implementations.Dto
if (options.ContainsField(ItemFields.SpecialFeatureCount))
{
- if (allExtras == null)
- {
- allExtras = item.GetExtras().ToArray();
- }
-
+ allExtras = item.GetExtras().ToArray();
dto.SpecialFeatureCount = allExtras.Count(i => i.ExtraType.HasValue && BaseItem.DisplayExtraTypes.Contains(i.ExtraType.Value));
}
if (options.ContainsField(ItemFields.LocalTrailerCount))
{
- int trailerCount = 0;
- if (allExtras == null)
- {
- allExtras = item.GetExtras().ToArray();
- }
-
- trailerCount += allExtras.Count(i => i.ExtraType.HasValue && i.ExtraType.Value == ExtraType.Trailer);
+ allExtras ??= item.GetExtras().ToArray();
+ dto.LocalTrailerCount = allExtras.Count(i => i.ExtraType == ExtraType.Trailer);
if (item is IHasTrailers hasTrailers)
{
- trailerCount += hasTrailers.GetTrailerCount();
+ dto.LocalTrailerCount += hasTrailers.GetTrailerCount();
}
-
- dto.LocalTrailerCount = trailerCount;
}
// Add EpisodeInfo
@@ -1362,56 +1352,33 @@ namespace Emby.Server.Implementations.Dto
return null;
}
- var supportedEnhancers = _imageProcessor.GetSupportedEnhancers(item, ImageType.Primary).ToArray();
-
ImageDimensions size;
var defaultAspectRatio = item.GetDefaultPrimaryImageAspectRatio();
if (defaultAspectRatio > 0)
{
- if (supportedEnhancers.Length == 0)
- {
- return defaultAspectRatio;
- }
-
- int dummyWidth = 200;
- int dummyHeight = Convert.ToInt32(dummyWidth / defaultAspectRatio);
- size = new ImageDimensions(dummyWidth, dummyHeight);
+ return defaultAspectRatio;
}
- else
+
+ if (!imageInfo.IsLocalFile)
{
- if (!imageInfo.IsLocalFile)
- {
- return null;
- }
+ return null;
+ }
- try
- {
- size = _imageProcessor.GetImageDimensions(item, imageInfo);
+ try
+ {
+ size = _imageProcessor.GetImageDimensions(item, imageInfo);
- if (size.Width <= 0 || size.Height <= 0)
- {
- return null;
- }
- }
- catch (Exception ex)
+ if (size.Width <= 0 || size.Height <= 0)
{
- _logger.LogError(ex, "Failed to determine primary image aspect ratio for {0}", imageInfo.Path);
return null;
}
}
-
- foreach (var enhancer in supportedEnhancers)
+ catch (Exception ex)
{
- try
- {
- size = enhancer.GetEnhancedImageSize(item, ImageType.Primary, 0, size);
- }
- catch (Exception ex)
- {
- _logger.LogError(ex, "Error in image enhancer: {0}", enhancer.GetType().Name);
- }
+ _logger.LogError(ex, "Failed to determine primary image aspect ratio for {0}", imageInfo.Path);
+ return null;
}
var width = size.Width;
diff --git a/Emby.Server.Implementations/Emby.Server.Implementations.csproj b/Emby.Server.Implementations/Emby.Server.Implementations.csproj
index 03cbe00b7..44fc932e3 100644
--- a/Emby.Server.Implementations/Emby.Server.Implementations.csproj
+++ b/Emby.Server.Implementations/Emby.Server.Implementations.csproj
@@ -1,5 +1,10 @@
<Project Sdk="Microsoft.NET.Sdk">
+ <!-- ProjectGuid is only included as a requirement for SonarQube analysis -->
+ <PropertyGroup>
+ <ProjectGuid>{E383961B-9356-4D5D-8233-9A1079D03055}</ProjectGuid>
+ </PropertyGroup>
+
<ItemGroup>
<ProjectReference Include="..\Emby.Naming\Emby.Naming.csproj" />
<ProjectReference Include="..\Emby.Notifications\Emby.Notifications.csproj" />
@@ -29,14 +34,15 @@
<PackageReference Include="Microsoft.AspNetCore.ResponseCompression" Version="2.2.0" />
<PackageReference Include="Microsoft.AspNetCore.Server.Kestrel" Version="2.2.0" />
<PackageReference Include="Microsoft.AspNetCore.WebSockets" Version="2.2.1" />
- <PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="3.1.0" />
- <PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="3.1.0" />
- <PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="3.1.0" />
- <PackageReference Include="Mono.Nat" Version="2.0.0" />
- <PackageReference Include="ServiceStack.Text.Core" Version="5.7.0" />
- <PackageReference Include="sharpcompress" Version="0.24.0" />
- <PackageReference Include="SQLitePCL.pretty.netstandard" Version="2.0.1" />
- <PackageReference Include="System.Interactive.Async" Version="4.0.0" />
+ <PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="3.1.3" />
+ <PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="3.1.3" />
+ <PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="3.1.3" />
+ <PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="3.1.3" />
+ <PackageReference Include="Mono.Nat" Version="2.0.1" />
+ <PackageReference Include="prometheus-net.DotNetRuntime" Version="3.3.1" />
+ <PackageReference Include="ServiceStack.Text.Core" Version="5.8.0" />
+ <PackageReference Include="sharpcompress" Version="0.25.0" />
+ <PackageReference Include="SQLitePCL.pretty.netstandard" Version="2.1.0" />
</ItemGroup>
<ItemGroup>
diff --git a/Emby.Server.Implementations/EntryPoints/AutomaticRestartEntryPoint.cs b/Emby.Server.Implementations/EntryPoints/AutomaticRestartEntryPoint.cs
deleted file mode 100644
index a6eb1152f..000000000
--- a/Emby.Server.Implementations/EntryPoints/AutomaticRestartEntryPoint.cs
+++ /dev/null
@@ -1,128 +0,0 @@
-#pragma warning disable CS1591
-#pragma warning disable SA1600
-
-using System;
-using System.Linq;
-using System.Threading;
-using System.Threading.Tasks;
-using MediaBrowser.Controller;
-using MediaBrowser.Controller.Configuration;
-using MediaBrowser.Controller.LiveTv;
-using MediaBrowser.Controller.Plugins;
-using MediaBrowser.Controller.Session;
-using MediaBrowser.Model.LiveTv;
-using MediaBrowser.Model.Tasks;
-using Microsoft.Extensions.Logging;
-
-namespace Emby.Server.Implementations.EntryPoints
-{
- public class AutomaticRestartEntryPoint : IServerEntryPoint
- {
- private readonly IServerApplicationHost _appHost;
- private readonly ILogger _logger;
- 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, ILiveTvManager liveTvManager)
- {
- _appHost = appHost;
- _logger = logger;
- _iTaskManager = iTaskManager;
- _sessionManager = sessionManager;
- _config = config;
- _liveTvManager = liveTvManager;
- }
-
- public Task RunAsync()
- {
- if (_appHost.CanSelfRestart)
- {
- _appHost.HasPendingRestartChanged += _appHost_HasPendingRestartChanged;
- }
-
- return Task.CompletedTask;
- }
-
- void _appHost_HasPendingRestartChanged(object sender, EventArgs e)
- {
- DisposeTimer();
-
- if (_appHost.HasPendingRestart)
- {
- _timer = new Timer(TimerCallback, null, TimeSpan.FromMinutes(15), TimeSpan.FromMinutes(15));
- }
- }
-
- private async void TimerCallback(object state)
- {
- if (_config.Configuration.EnableAutomaticRestart)
- {
- var isIdle = await IsIdle().ConfigureAwait(false);
-
- if (isIdle)
- {
- DisposeTimer();
-
- _logger.LogInformation("Automatically restarting the system because it is idle and a restart is required.");
-
- try
- {
- _appHost.Restart();
- }
- catch (Exception ex)
- {
- _logger.LogError(ex, "Error restarting server");
- }
- }
- }
- }
-
- 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.LogError(ex, "Error getting timers");
- }
- }
-
- var now = DateTime.UtcNow;
-
- return !_sessionManager.Sessions.Any(i => (now - i.LastActivityDate).TotalMinutes < 30);
- }
-
- public void Dispose()
- {
- _appHost.HasPendingRestartChanged -= _appHost_HasPendingRestartChanged;
-
- DisposeTimer();
- }
-
- private void DisposeTimer()
- {
- if (_timer != null)
- {
- _timer.Dispose();
- _timer = null;
- }
- }
- }
-}
diff --git a/Emby.Server.Implementations/EntryPoints/ExternalPortForwarding.cs b/Emby.Server.Implementations/EntryPoints/ExternalPortForwarding.cs
index 4e4ef3be0..37d7fd479 100644
--- a/Emby.Server.Implementations/EntryPoints/ExternalPortForwarding.cs
+++ b/Emby.Server.Implementations/EntryPoints/ExternalPortForwarding.cs
@@ -1,7 +1,7 @@
#pragma warning disable CS1591
-#pragma warning disable SA1600
using System;
+using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Net;
using System.Text;
@@ -27,10 +27,10 @@ namespace Emby.Server.Implementations.EntryPoints
private readonly IServerConfigurationManager _config;
private readonly IDeviceDiscovery _deviceDiscovery;
- private readonly object _createdRulesLock = new object();
- private List<IPEndPoint> _createdRules = new List<IPEndPoint>();
+ private readonly ConcurrentDictionary<IPEndPoint, byte> _createdRules = new ConcurrentDictionary<IPEndPoint, byte>();
+
private Timer _timer;
- private string _lastConfigIdentifier;
+ private string _configIdentifier;
private bool _disposed = false;
@@ -61,6 +61,7 @@ namespace Emby.Server.Implementations.EntryPoints
return new StringBuilder(32)
.Append(config.EnableUPnP).Append(Separator)
.Append(config.PublicPort).Append(Separator)
+ .Append(config.PublicHttpsPort).Append(Separator)
.Append(_appHost.HttpPort).Append(Separator)
.Append(_appHost.HttpsPort).Append(Separator)
.Append(_appHost.EnableHttps).Append(Separator)
@@ -70,7 +71,10 @@ namespace Emby.Server.Implementations.EntryPoints
private void OnConfigurationUpdated(object sender, EventArgs e)
{
- if (!string.Equals(_lastConfigIdentifier, GetConfigIdentifier(), StringComparison.OrdinalIgnoreCase))
+ var oldConfigIdentifier = _configIdentifier;
+ _configIdentifier = GetConfigIdentifier();
+
+ if (!string.Equals(_configIdentifier, oldConfigIdentifier, StringComparison.OrdinalIgnoreCase))
{
Stop();
Start();
@@ -94,21 +98,19 @@ namespace Emby.Server.Implementations.EntryPoints
return;
}
- _logger.LogDebug("Starting NAT discovery");
+ _logger.LogInformation("Starting NAT discovery");
NatUtility.DeviceFound += OnNatUtilityDeviceFound;
NatUtility.StartDiscovery();
- _timer = new Timer(ClearCreatedRules, null, TimeSpan.FromMinutes(10), TimeSpan.FromMinutes(10));
+ _timer = new Timer((_) => _createdRules.Clear(), null, TimeSpan.FromMinutes(10), TimeSpan.FromMinutes(10));
_deviceDiscovery.DeviceDiscovered += OnDeviceDiscoveryDeviceDiscovered;
-
- _lastConfigIdentifier = GetConfigIdentifier();
}
private void Stop()
{
- _logger.LogDebug("Stopping NAT discovery");
+ _logger.LogInformation("Stopping NAT discovery");
NatUtility.StopDiscovery();
NatUtility.DeviceFound -= OnNatUtilityDeviceFound;
@@ -118,26 +120,16 @@ namespace Emby.Server.Implementations.EntryPoints
_deviceDiscovery.DeviceDiscovered -= OnDeviceDiscoveryDeviceDiscovered;
}
- private void ClearCreatedRules(object state)
- {
- lock (_createdRulesLock)
- {
- _createdRules.Clear();
- }
- }
-
private void OnDeviceDiscoveryDeviceDiscovered(object sender, GenericEventArgs<UpnpDeviceInfo> e)
{
NatUtility.Search(e.Argument.LocalIpAddress, NatProtocol.Upnp);
}
- private void OnNatUtilityDeviceFound(object sender, DeviceEventArgs e)
+ private async void OnNatUtilityDeviceFound(object sender, DeviceEventArgs e)
{
try
{
- var device = e.Device;
-
- CreateRules(device);
+ await CreateRules(e.Device).ConfigureAwait(false);
}
catch (Exception ex)
{
@@ -145,7 +137,7 @@ namespace Emby.Server.Implementations.EntryPoints
}
}
- private async void CreateRules(INatDevice device)
+ private Task CreateRules(INatDevice device)
{
if (_disposed)
{
@@ -154,50 +146,46 @@ namespace Emby.Server.Implementations.EntryPoints
// On some systems the device discovered event seems to fire repeatedly
// This check will help ensure we're not trying to port map the same device over and over
- var address = device.DeviceEndpoint;
-
- lock (_createdRulesLock)
+ if (!_createdRules.TryAdd(device.DeviceEndpoint, 0))
{
- if (!_createdRules.Contains(address))
- {
- _createdRules.Add(address);
- }
- else
- {
- return;
- }
+ return Task.CompletedTask;
}
- try
- {
- await CreatePortMap(device, _appHost.HttpPort, _config.Configuration.PublicPort).ConfigureAwait(false);
- }
- catch (Exception ex)
- {
- _logger.LogError(ex, "Error creating http port map");
- return;
- }
+ return Task.WhenAll(CreatePortMaps(device));
+ }
- try
- {
- await CreatePortMap(device, _appHost.HttpsPort, _config.Configuration.PublicHttpsPort).ConfigureAwait(false);
- }
- catch (Exception ex)
+ private IEnumerable<Task> CreatePortMaps(INatDevice device)
+ {
+ yield return CreatePortMap(device, _appHost.HttpPort, _config.Configuration.PublicPort);
+
+ if (_appHost.EnableHttps)
{
- _logger.LogError(ex, "Error creating https port map");
+ yield return CreatePortMap(device, _appHost.HttpsPort, _config.Configuration.PublicHttpsPort);
}
}
- private Task<Mapping> CreatePortMap(INatDevice device, int privatePort, int publicPort)
+ private async Task CreatePortMap(INatDevice device, int privatePort, int publicPort)
{
_logger.LogDebug(
- "Creating port map on local port {0} to public port {1} with device {2}",
+ "Creating port map on local port {LocalPort} to public port {PublicPort} with device {DeviceEndpoint}",
privatePort,
publicPort,
device.DeviceEndpoint);
- return device.CreatePortMapAsync(
- new Mapping(Protocol.Tcp, privatePort, publicPort, 0, _appHost.Name));
+ try
+ {
+ var mapping = new Mapping(Protocol.Tcp, privatePort, publicPort, 0, _appHost.Name);
+ await device.CreatePortMapAsync(mapping).ConfigureAwait(false);
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(
+ ex,
+ "Error creating port map on local port {LocalPort} to public port {PublicPort} with device {DeviceEndpoint}.",
+ privatePort,
+ publicPort,
+ device.DeviceEndpoint);
+ }
}
/// <inheritdoc />
diff --git a/Emby.Server.Implementations/EntryPoints/LibraryChangedNotifier.cs b/Emby.Server.Implementations/EntryPoints/LibraryChangedNotifier.cs
index f85d52dbc..8e3236407 100644
--- a/Emby.Server.Implementations/EntryPoints/LibraryChangedNotifier.cs
+++ b/Emby.Server.Implementations/EntryPoints/LibraryChangedNotifier.cs
@@ -1,5 +1,4 @@
#pragma warning disable CS1591
-#pragma warning disable SA1600
using System;
using System.Collections.Generic;
@@ -16,7 +15,6 @@ using MediaBrowser.Controller.Providers;
using MediaBrowser.Controller.Session;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Events;
-using MediaBrowser.Model.Extensions;
using Microsoft.Extensions.Logging;
namespace Emby.Server.Implementations.EntryPoints
@@ -57,7 +55,12 @@ namespace Emby.Server.Implementations.EntryPoints
private readonly IProviderManager _providerManager;
- public LibraryChangedNotifier(ILibraryManager libraryManager, ISessionManager sessionManager, IUserManager userManager, ILogger logger, IProviderManager providerManager)
+ public LibraryChangedNotifier(
+ ILibraryManager libraryManager,
+ ISessionManager sessionManager,
+ IUserManager userManager,
+ ILogger<LibraryChangedNotifier> logger,
+ IProviderManager providerManager)
{
_libraryManager = libraryManager;
_sessionManager = sessionManager;
diff --git a/Emby.Server.Implementations/EntryPoints/RecordingNotifier.cs b/Emby.Server.Implementations/EntryPoints/RecordingNotifier.cs
index e0aa18e89..41c0c5115 100644
--- a/Emby.Server.Implementations/EntryPoints/RecordingNotifier.cs
+++ b/Emby.Server.Implementations/EntryPoints/RecordingNotifier.cs
@@ -1,5 +1,4 @@
#pragma warning disable CS1591
-#pragma warning disable SA1600
using System;
using System.Linq;
@@ -13,14 +12,18 @@ using Microsoft.Extensions.Logging;
namespace Emby.Server.Implementations.EntryPoints
{
- public class RecordingNotifier : IServerEntryPoint
+ public sealed 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)
+ public RecordingNotifier(
+ ISessionManager sessionManager,
+ IUserManager userManager,
+ ILogger<RecordingNotifier> logger,
+ ILiveTvManager liveTvManager)
{
_sessionManager = sessionManager;
_userManager = userManager;
@@ -28,32 +31,33 @@ namespace Emby.Server.Implementations.EntryPoints
_liveTvManager = liveTvManager;
}
+ /// <inheritdoc />
public Task RunAsync()
{
- _liveTvManager.TimerCancelled += _liveTvManager_TimerCancelled;
- _liveTvManager.SeriesTimerCancelled += _liveTvManager_SeriesTimerCancelled;
- _liveTvManager.TimerCreated += _liveTvManager_TimerCreated;
- _liveTvManager.SeriesTimerCreated += _liveTvManager_SeriesTimerCreated;
+ _liveTvManager.TimerCancelled += OnLiveTvManagerTimerCancelled;
+ _liveTvManager.SeriesTimerCancelled += OnLiveTvManagerSeriesTimerCancelled;
+ _liveTvManager.TimerCreated += OnLiveTvManagerTimerCreated;
+ _liveTvManager.SeriesTimerCreated += OnLiveTvManagerSeriesTimerCreated;
return Task.CompletedTask;
}
- private void _liveTvManager_SeriesTimerCreated(object sender, MediaBrowser.Model.Events.GenericEventArgs<TimerEventInfo> e)
+ private void OnLiveTvManagerSeriesTimerCreated(object sender, MediaBrowser.Model.Events.GenericEventArgs<TimerEventInfo> e)
{
SendMessage("SeriesTimerCreated", e.Argument);
}
- private void _liveTvManager_TimerCreated(object sender, MediaBrowser.Model.Events.GenericEventArgs<TimerEventInfo> e)
+ private void OnLiveTvManagerTimerCreated(object sender, MediaBrowser.Model.Events.GenericEventArgs<TimerEventInfo> e)
{
SendMessage("TimerCreated", e.Argument);
}
- private void _liveTvManager_SeriesTimerCancelled(object sender, MediaBrowser.Model.Events.GenericEventArgs<TimerEventInfo> e)
+ private void OnLiveTvManagerSeriesTimerCancelled(object sender, MediaBrowser.Model.Events.GenericEventArgs<TimerEventInfo> e)
{
SendMessage("SeriesTimerCancelled", e.Argument);
}
- private void _liveTvManager_TimerCancelled(object sender, MediaBrowser.Model.Events.GenericEventArgs<TimerEventInfo> e)
+ private void OnLiveTvManagerTimerCancelled(object sender, MediaBrowser.Model.Events.GenericEventArgs<TimerEventInfo> e)
{
SendMessage("TimerCancelled", e.Argument);
}
@@ -64,11 +68,7 @@ namespace Emby.Server.Implementations.EntryPoints
try
{
- await _sessionManager.SendMessageToUserSessions(users, name, info, CancellationToken.None);
- }
- catch (ObjectDisposedException)
- {
- // TODO Log exception or Investigate and properly fix.
+ await _sessionManager.SendMessageToUserSessions(users, name, info, CancellationToken.None).ConfigureAwait(false);
}
catch (Exception ex)
{
@@ -76,12 +76,13 @@ namespace Emby.Server.Implementations.EntryPoints
}
}
+ /// <inheritdoc />
public void Dispose()
{
- _liveTvManager.TimerCancelled -= _liveTvManager_TimerCancelled;
- _liveTvManager.SeriesTimerCancelled -= _liveTvManager_SeriesTimerCancelled;
- _liveTvManager.TimerCreated -= _liveTvManager_TimerCreated;
- _liveTvManager.SeriesTimerCreated -= _liveTvManager_SeriesTimerCreated;
+ _liveTvManager.TimerCancelled -= OnLiveTvManagerTimerCancelled;
+ _liveTvManager.SeriesTimerCancelled -= OnLiveTvManagerSeriesTimerCancelled;
+ _liveTvManager.TimerCreated -= OnLiveTvManagerTimerCreated;
+ _liveTvManager.SeriesTimerCreated -= OnLiveTvManagerSeriesTimerCreated;
}
}
}
diff --git a/Emby.Server.Implementations/EntryPoints/RefreshUsersMetadata.cs b/Emby.Server.Implementations/EntryPoints/RefreshUsersMetadata.cs
index f00996b5f..54f4b67e6 100644
--- a/Emby.Server.Implementations/EntryPoints/RefreshUsersMetadata.cs
+++ b/Emby.Server.Implementations/EntryPoints/RefreshUsersMetadata.cs
@@ -6,7 +6,6 @@ using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.IO;
using MediaBrowser.Model.Tasks;
-using Microsoft.Extensions.Logging;
namespace Emby.Server.Implementations.EntryPoints
{
@@ -15,21 +14,17 @@ namespace Emby.Server.Implementations.EntryPoints
/// </summary>
public class RefreshUsersMetadata : IScheduledTask, IConfigurableScheduledTask
{
- private readonly ILogger _logger;
-
/// <summary>
/// The user manager.
/// </summary>
private readonly IUserManager _userManager;
-
- private IFileSystem _fileSystem;
+ private readonly IFileSystem _fileSystem;
/// <summary>
/// Initializes a new instance of the <see cref="RefreshUsersMetadata" /> class.
/// </summary>
- public RefreshUsersMetadata(ILogger logger, IUserManager userManager, IFileSystem fileSystem)
+ public RefreshUsersMetadata(IUserManager userManager, IFileSystem fileSystem)
{
- _logger = logger;
_userManager = userManager;
_fileSystem = fileSystem;
}
diff --git a/Emby.Server.Implementations/EntryPoints/StartupWizard.cs b/Emby.Server.Implementations/EntryPoints/StartupWizard.cs
index 161788c63..2e738deeb 100644
--- a/Emby.Server.Implementations/EntryPoints/StartupWizard.cs
+++ b/Emby.Server.Implementations/EntryPoints/StartupWizard.cs
@@ -2,64 +2,77 @@ using System.Threading.Tasks;
using Emby.Server.Implementations.Browser;
using MediaBrowser.Controller;
using MediaBrowser.Controller.Configuration;
+using MediaBrowser.Controller.Extensions;
using MediaBrowser.Controller.Plugins;
-using Microsoft.Extensions.Logging;
+using Microsoft.Extensions.Configuration;
namespace Emby.Server.Implementations.EntryPoints
{
/// <summary>
/// Class StartupWizard.
/// </summary>
- public class StartupWizard : IServerEntryPoint
+ public sealed class StartupWizard : IServerEntryPoint
{
- /// <summary>
- /// The app host.
- /// </summary>
private readonly IServerApplicationHost _appHost;
-
- /// <summary>
- /// The user manager.
- /// </summary>
- private readonly ILogger _logger;
-
- private IServerConfigurationManager _config;
+ private readonly IConfiguration _appConfig;
+ private readonly IServerConfigurationManager _config;
+ private readonly IStartupOptions _startupOptions;
/// <summary>
/// Initializes a new instance of the <see cref="StartupWizard"/> class.
/// </summary>
/// <param name="appHost">The application host.</param>
- /// <param name="logger">The logger.</param>
+ /// <param name="appConfig">The application configuration.</param>
/// <param name="config">The configuration manager.</param>
- public StartupWizard(IServerApplicationHost appHost, ILogger logger, IServerConfigurationManager config)
+ /// <param name="startupOptions">The application startup options.</param>
+ public StartupWizard(
+ IServerApplicationHost appHost,
+ IConfiguration appConfig,
+ IServerConfigurationManager config,
+ IStartupOptions startupOptions)
{
_appHost = appHost;
- _logger = logger;
+ _appConfig = appConfig;
_config = config;
+ _startupOptions = startupOptions;
}
/// <inheritdoc />
public Task RunAsync()
{
+ Run();
+ return Task.CompletedTask;
+ }
+
+ private void Run()
+ {
if (!_appHost.CanLaunchWebBrowser)
{
- return Task.CompletedTask;
+ return;
}
- if (!_config.Configuration.IsStartupWizardCompleted)
+ // Always launch the startup wizard if possible when it has not been completed
+ if (!_config.Configuration.IsStartupWizardCompleted && _appConfig.HostWebClient())
{
BrowserLauncher.OpenWebApp(_appHost);
+ return;
}
- else if (_config.Configuration.AutoRunWebApp)
- {
- var options = ((ApplicationHost)_appHost).StartupOptions;
- if (!options.NoAutoRunWebApp)
- {
- BrowserLauncher.OpenWebApp(_appHost);
- }
+ // Do nothing if the web app is configured to not run automatically
+ if (!_config.Configuration.AutoRunWebApp || _startupOptions.NoAutoRunWebApp)
+ {
+ return;
}
- return Task.CompletedTask;
+ // Launch the swagger page if the web client is not hosted, otherwise open the web client
+ if (_appConfig.HostWebClient())
+ {
+ BrowserLauncher.OpenWebApp(_appHost);
+ }
+ else
+ {
+ BrowserLauncher.OpenSwaggerPage(_appHost);
+ }
}
/// <inheritdoc />
diff --git a/Emby.Server.Implementations/EntryPoints/UdpServerEntryPoint.cs b/Emby.Server.Implementations/EntryPoints/UdpServerEntryPoint.cs
index 9ee219854..50ba0f8fa 100644
--- a/Emby.Server.Implementations/EntryPoints/UdpServerEntryPoint.cs
+++ b/Emby.Server.Implementations/EntryPoints/UdpServerEntryPoint.cs
@@ -1,10 +1,8 @@
-using System;
+using System.Threading;
using System.Threading.Tasks;
using Emby.Server.Implementations.Udp;
using MediaBrowser.Controller;
using MediaBrowser.Controller.Plugins;
-using MediaBrowser.Model.Net;
-using MediaBrowser.Model.Serialization;
using Microsoft.Extensions.Logging;
namespace Emby.Server.Implementations.EntryPoints
@@ -12,7 +10,7 @@ namespace Emby.Server.Implementations.EntryPoints
/// <summary>
/// Class UdpServerEntryPoint.
/// </summary>
- public class UdpServerEntryPoint : IServerEntryPoint
+ public sealed class UdpServerEntryPoint : IServerEntryPoint
{
/// <summary>
/// The port of the UDP server.
@@ -23,69 +21,50 @@ namespace Emby.Server.Implementations.EntryPoints
/// The logger.
/// </summary>
private readonly ILogger _logger;
- private readonly ISocketFactory _socketFactory;
private readonly IServerApplicationHost _appHost;
- private readonly IJsonSerializer _json;
/// <summary>
/// The UDP server.
/// </summary>
private UdpServer _udpServer;
+ private CancellationTokenSource _cancellationTokenSource = new CancellationTokenSource();
+ private bool _disposed = false;
/// <summary>
/// Initializes a new instance of the <see cref="UdpServerEntryPoint" /> class.
/// </summary>
public UdpServerEntryPoint(
- ILogger logger,
- IServerApplicationHost appHost,
- IJsonSerializer json,
- ISocketFactory socketFactory)
+ ILogger<UdpServerEntryPoint> logger,
+ IServerApplicationHost appHost)
{
_logger = logger;
_appHost = appHost;
- _json = json;
- _socketFactory = socketFactory;
- }
-
- /// <inheritdoc />
- public Task RunAsync()
- {
- var udpServer = new UdpServer(_logger, _appHost, _json, _socketFactory);
- try
- {
- udpServer.Start(PortNumber);
-
- _udpServer = udpServer;
- }
- catch (Exception ex)
- {
- _logger.LogError(ex, "Failed to start UDP Server");
- }
- return Task.CompletedTask;
}
/// <inheritdoc />
- public void Dispose()
+ public async Task RunAsync()
{
- Dispose(true);
- GC.SuppressFinalize(this);
+ _udpServer = new UdpServer(_logger, _appHost);
+ _udpServer.Start(PortNumber, _cancellationTokenSource.Token);
}
- /// <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)
+ /// <inheritdoc />
+ public void Dispose()
{
- if (dispose)
+ if (_disposed)
{
- if (_udpServer != null)
- {
- _udpServer.Dispose();
- }
+ return;
}
+
+ _cancellationTokenSource.Cancel();
+ _udpServer.Dispose();
+ _cancellationTokenSource.Dispose();
+ _cancellationTokenSource = null;
+ _udpServer = null;
+
+ _disposed = true;
}
}
}
diff --git a/Emby.Server.Implementations/EntryPoints/UserDataChangeNotifier.cs b/Emby.Server.Implementations/EntryPoints/UserDataChangeNotifier.cs
index 3e22080fc..3618b88c5 100644
--- a/Emby.Server.Implementations/EntryPoints/UserDataChangeNotifier.cs
+++ b/Emby.Server.Implementations/EntryPoints/UserDataChangeNotifier.cs
@@ -1,5 +1,4 @@
#pragma warning disable CS1591
-#pragma warning disable SA1600
using System;
using System.Collections.Generic;
@@ -13,39 +12,38 @@ using MediaBrowser.Controller.Plugins;
using MediaBrowser.Controller.Session;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Session;
-using Microsoft.Extensions.Logging;
namespace Emby.Server.Implementations.EntryPoints
{
- public class UserDataChangeNotifier : IServerEntryPoint
+ public sealed class UserDataChangeNotifier : IServerEntryPoint
{
+ private const int UpdateDuration = 500;
+
private readonly ISessionManager _sessionManager;
- private readonly ILogger _logger;
private readonly IUserDataManager _userDataManager;
private readonly IUserManager _userManager;
+ private readonly Dictionary<Guid, List<BaseItem>> _changedItems = new Dictionary<Guid, List<BaseItem>>();
+
private readonly object _syncLock = new object();
- private Timer UpdateTimer { get; set; }
- private const int UpdateDuration = 500;
+ private Timer _updateTimer;
- private readonly Dictionary<Guid, List<BaseItem>> _changedItems = new Dictionary<Guid, List<BaseItem>>();
- public UserDataChangeNotifier(IUserDataManager userDataManager, ISessionManager sessionManager, ILogger logger, IUserManager userManager)
+ public UserDataChangeNotifier(IUserDataManager userDataManager, ISessionManager sessionManager, IUserManager userManager)
{
_userDataManager = userDataManager;
_sessionManager = sessionManager;
- _logger = logger;
_userManager = userManager;
}
public Task RunAsync()
{
- _userDataManager.UserDataSaved += _userDataManager_UserDataSaved;
+ _userDataManager.UserDataSaved += OnUserDataManagerUserDataSaved;
return Task.CompletedTask;
}
- void _userDataManager_UserDataSaved(object sender, UserDataSaveEventArgs e)
+ void OnUserDataManagerUserDataSaved(object sender, UserDataSaveEventArgs e)
{
if (e.SaveReason == UserDataSaveReason.PlaybackProgress)
{
@@ -54,14 +52,17 @@ namespace Emby.Server.Implementations.EntryPoints
lock (_syncLock)
{
- if (UpdateTimer == null)
+ if (_updateTimer == null)
{
- UpdateTimer = new Timer(UpdateTimerCallback, null, UpdateDuration,
- Timeout.Infinite);
+ _updateTimer = new Timer(
+ UpdateTimerCallback,
+ null,
+ UpdateDuration,
+ Timeout.Infinite);
}
else
{
- UpdateTimer.Change(UpdateDuration, Timeout.Infinite);
+ _updateTimer.Change(UpdateDuration, Timeout.Infinite);
}
if (!_changedItems.TryGetValue(e.UserId, out List<BaseItem> keys))
@@ -97,10 +98,10 @@ namespace Emby.Server.Implementations.EntryPoints
var task = SendNotifications(changes, CancellationToken.None);
- if (UpdateTimer != null)
+ if (_updateTimer != null)
{
- UpdateTimer.Dispose();
- UpdateTimer = null;
+ _updateTimer.Dispose();
+ _updateTimer = null;
}
}
}
@@ -145,13 +146,13 @@ namespace Emby.Server.Implementations.EntryPoints
public void Dispose()
{
- if (UpdateTimer != null)
+ if (_updateTimer != null)
{
- UpdateTimer.Dispose();
- UpdateTimer = null;
+ _updateTimer.Dispose();
+ _updateTimer = null;
}
- _userDataManager.UserDataSaved -= _userDataManager_UserDataSaved;
+ _userDataManager.UserDataSaved -= OnUserDataManagerUserDataSaved;
}
}
}
diff --git a/Emby.Server.Implementations/HttpClientManager/HttpClientManager.cs b/Emby.Server.Implementations/HttpClientManager/HttpClientManager.cs
index 50233ea48..d66bb7638 100644
--- a/Emby.Server.Implementations/HttpClientManager/HttpClientManager.cs
+++ b/Emby.Server.Implementations/HttpClientManager/HttpClientManager.cs
@@ -6,6 +6,7 @@ using System.Linq;
using System.Net;
using System.Net.Http;
using System.Threading.Tasks;
+using MediaBrowser.Common;
using MediaBrowser.Common.Configuration;
using MediaBrowser.Common.Extensions;
using MediaBrowser.Common.Net;
@@ -24,7 +25,7 @@ namespace Emby.Server.Implementations.HttpClientManager
private readonly ILogger _logger;
private readonly IApplicationPaths _appPaths;
private readonly IFileSystem _fileSystem;
- private readonly Func<string> _defaultUserAgentFn;
+ private readonly IApplicationHost _appHost;
/// <summary>
/// Holds a dictionary of http clients by host. Use GetHttpClient(host) to retrieve or create a client for web requests.
@@ -40,12 +41,12 @@ namespace Emby.Server.Implementations.HttpClientManager
IApplicationPaths appPaths,
ILogger<HttpClientManager> logger,
IFileSystem fileSystem,
- Func<string> defaultUserAgentFn)
+ IApplicationHost appHost)
{
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
_fileSystem = fileSystem;
_appPaths = appPaths ?? throw new ArgumentNullException(nameof(appPaths));
- _defaultUserAgentFn = defaultUserAgentFn;
+ _appHost = appHost;
}
/// <summary>
@@ -78,7 +79,7 @@ namespace Emby.Server.Implementations.HttpClientManager
if (!string.IsNullOrWhiteSpace(userInfo))
{
_logger.LogWarning("Found userInfo in url: {0} ... url: {1}", userInfo, url);
- url = url.Replace(userInfo + '@', string.Empty);
+ url = url.Replace(userInfo + '@', string.Empty, StringComparison.Ordinal);
}
var request = new HttpRequestMessage(method, url);
@@ -91,18 +92,18 @@ namespace Emby.Server.Implementations.HttpClientManager
if (options.EnableDefaultUserAgent
&& !request.Headers.TryGetValues(HeaderNames.UserAgent, out _))
{
- request.Headers.Add(HeaderNames.UserAgent, _defaultUserAgentFn());
+ request.Headers.Add(HeaderNames.UserAgent, _appHost.ApplicationUserAgent);
}
switch (options.DecompressionMethod)
{
- case CompressionMethod.Deflate | CompressionMethod.Gzip:
+ case CompressionMethods.Deflate | CompressionMethods.Gzip:
request.Headers.Add(HeaderNames.AcceptEncoding, new[] { "gzip", "deflate" });
break;
- case CompressionMethod.Deflate:
+ case CompressionMethods.Deflate:
request.Headers.Add(HeaderNames.AcceptEncoding, "deflate");
break;
- case CompressionMethod.Gzip:
+ case CompressionMethods.Gzip:
request.Headers.Add(HeaderNames.AcceptEncoding, "gzip");
break;
default:
@@ -197,7 +198,7 @@ namespace Emby.Server.Implementations.HttpClientManager
if (File.Exists(responseCachePath)
&& _fileSystem.GetLastWriteTimeUtc(responseCachePath).Add(cacheLength) > DateTime.UtcNow)
{
- var stream = _fileSystem.GetFileStream(responseCachePath, FileOpenMode.Open, FileAccessMode.Read, FileShareMode.Read, true);
+ var stream = new FileStream(responseCachePath, FileMode.Open, FileAccess.Read, FileShare.Read, IODefaults.FileStreamBufferSize, true);
return new HttpResponseInfo
{
@@ -220,7 +221,7 @@ namespace Emby.Server.Implementations.HttpClientManager
FileMode.Create,
FileAccess.Write,
FileShare.None,
- StreamDefaults.DefaultFileStreamBufferSize,
+ IODefaults.FileStreamBufferSize,
true))
{
await response.Content.CopyToAsync(fileStream).ConfigureAwait(false);
@@ -239,15 +240,10 @@ namespace Emby.Server.Implementations.HttpClientManager
var httpWebRequest = GetRequestMessage(options, httpMethod);
- if (options.RequestContentBytes != null
- || !string.IsNullOrEmpty(options.RequestContent)
+ if (!string.IsNullOrEmpty(options.RequestContent)
|| httpMethod == HttpMethod.Post)
{
- if (options.RequestContentBytes != null)
- {
- httpWebRequest.Content = new ByteArrayContent(options.RequestContentBytes);
- }
- else if (options.RequestContent != null)
+ if (options.RequestContent != null)
{
httpWebRequest.Content = new StringContent(
options.RequestContent,
diff --git a/Emby.Server.Implementations/HttpServer/FileWriter.cs b/Emby.Server.Implementations/HttpServer/FileWriter.cs
index 1795651fd..0b61e40b0 100644
--- a/Emby.Server.Implementations/HttpServer/FileWriter.cs
+++ b/Emby.Server.Implementations/HttpServer/FileWriter.cs
@@ -1,5 +1,4 @@
#pragma warning disable CS1591
-#pragma warning disable SA1600
using System;
using System.Collections.Generic;
@@ -12,8 +11,8 @@ using System.Threading;
using System.Threading.Tasks;
using MediaBrowser.Model.IO;
using MediaBrowser.Model.Services;
-using Microsoft.Extensions.Logging;
using Microsoft.AspNetCore.Http;
+using Microsoft.Extensions.Logging;
using Microsoft.Net.Http.Headers;
namespace Emby.Server.Implementations.HttpServer
@@ -72,7 +71,7 @@ namespace Emby.Server.Implementations.HttpServer
SetRangeValues();
}
- FileShare = FileShareMode.Read;
+ FileShare = FileShare.Read;
Cookies = new List<Cookie>();
}
@@ -94,7 +93,7 @@ namespace Emby.Server.Implementations.HttpServer
public List<Cookie> Cookies { get; private set; }
- public FileShareMode FileShare { get; set; }
+ public FileShare FileShare { get; set; }
/// <summary>
/// Gets the options.
@@ -222,17 +221,17 @@ namespace Emby.Server.Implementations.HttpServer
}
}
- public async Task TransmitFile(Stream stream, string path, long offset, long count, FileShareMode fileShareMode, CancellationToken cancellationToken)
+ public async Task TransmitFile(Stream stream, string path, long offset, long count, FileShare fileShare, CancellationToken cancellationToken)
{
- var fileOpenOptions = FileOpenOptions.SequentialScan;
+ var fileOptions = FileOptions.SequentialScan;
// use non-async filestream along with read due to https://github.com/dotnet/corefx/issues/6039
if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
- fileOpenOptions |= FileOpenOptions.Asynchronous;
+ fileOptions |= FileOptions.Asynchronous;
}
- using (var fs = _fileSystem.GetFileStream(path, FileOpenMode.Open, FileAccessMode.Read, fileShareMode, fileOpenOptions))
+ using (var fs = new FileStream(path, FileMode.Open, FileAccess.Read, fileShare, IODefaults.FileStreamBufferSize, fileOptions))
{
if (offset > 0)
{
@@ -245,7 +244,7 @@ namespace Emby.Server.Implementations.HttpServer
}
else
{
- await fs.CopyToAsync(stream, StreamDefaults.DefaultCopyToBufferSize, cancellationToken).ConfigureAwait(false);
+ await fs.CopyToAsync(stream, IODefaults.CopyToBufferSize, cancellationToken).ConfigureAwait(false);
}
}
}
diff --git a/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs b/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs
index 05dbad624..2648b57d5 100644
--- a/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs
+++ b/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs
@@ -1,5 +1,4 @@
#pragma warning disable CS1591
-#pragma warning disable SA1600
using System;
using System.Collections.Generic;
@@ -16,15 +15,18 @@ using Emby.Server.Implementations.SocketSharp;
using MediaBrowser.Common.Extensions;
using MediaBrowser.Common.Net;
using MediaBrowser.Controller;
+using MediaBrowser.Controller.Authentication;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Net;
using MediaBrowser.Model.Events;
+using MediaBrowser.Model.Globalization;
using MediaBrowser.Model.Serialization;
using MediaBrowser.Model.Services;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Extensions;
using Microsoft.AspNetCore.WebUtilities;
using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Microsoft.Net.Http.Headers;
using ServiceStack.Text.Jsv;
@@ -33,6 +35,12 @@ namespace Emby.Server.Implementations.HttpServer
{
public class HttpListenerHost : IHttpServer, IDisposable
{
+ /// <summary>
+ /// The key for a setting that specifies the default redirect path
+ /// to use for requests where the URL base prefix is invalid or missing.
+ /// </summary>
+ public const string DefaultRedirectKey = "HttpListenerHost:DefaultRedirectPath";
+
private readonly ILogger _logger;
private readonly ILoggerFactory _loggerFactory;
private readonly IServerConfigurationManager _config;
@@ -43,7 +51,11 @@ namespace Emby.Server.Implementations.HttpServer
private readonly Func<Type, Func<string, object>> _funcParseFn;
private readonly string _defaultRedirectPath;
private readonly string _baseUrlPrefix;
- private readonly Dictionary<Type, Type> ServiceOperationsMap = new Dictionary<Type, Type>();
+
+ private readonly Dictionary<Type, Type> _serviceOperationsMap = new Dictionary<Type, Type>();
+ private readonly List<IWebSocketConnection> _webSocketConnections = new List<IWebSocketConnection>();
+ private readonly IHostEnvironment _hostEnvironment;
+
private IWebSocketListener[] _webSocketListeners = Array.Empty<IWebSocketListener>();
private bool _disposed = false;
@@ -55,24 +67,34 @@ namespace Emby.Server.Implementations.HttpServer
INetworkManager networkManager,
IJsonSerializer jsonSerializer,
IXmlSerializer xmlSerializer,
- ILoggerFactory loggerFactory)
+ IHttpListener socketListener,
+ ILocalizationManager localizationManager,
+ ServiceController serviceController,
+ IHostEnvironment hostEnvironment)
{
_appHost = applicationHost;
_logger = logger;
_config = config;
- _defaultRedirectPath = configuration["HttpListenerHost:DefaultRedirectPath"];
+ _defaultRedirectPath = configuration[DefaultRedirectKey];
_baseUrlPrefix = _config.Configuration.BaseUrl;
_networkManager = networkManager;
_jsonSerializer = jsonSerializer;
_xmlSerializer = xmlSerializer;
- _loggerFactory = loggerFactory;
+ _socketListener = socketListener;
+ ServiceController = serviceController;
+
+ _socketListener.WebSocketConnected = OnWebSocketConnected;
+ _hostEnvironment = hostEnvironment;
_funcParseFn = t => s => JsvReader.GetParseFn(t)(s);
Instance = this;
ResponseFilters = Array.Empty<Action<IRequest, HttpResponse, object>>();
+ GlobalResponse = localizationManager.GetLocalizedString("StartupEmbyServerIsLoading");
}
+ public event EventHandler<GenericEventArgs<IWebSocketConnection>> WebSocketConnected;
+
public Action<IRequest, HttpResponse, object>[] ResponseFilters { get; set; }
public static HttpListenerHost Instance { get; protected set; }
@@ -81,9 +103,7 @@ namespace Emby.Server.Implementations.HttpServer
public string GlobalResponse { get; set; }
- public ServiceController ServiceController { get; private set; }
-
- public event EventHandler<GenericEventArgs<IWebSocketConnection>> WebSocketConnected;
+ public ServiceController ServiceController { get; }
public object CreateInstance(Type type)
{
@@ -92,7 +112,7 @@ namespace Emby.Server.Implementations.HttpServer
private static string NormalizeUrlPath(string path)
{
- if (path.StartsWith("/"))
+ if (path.Length > 0 && path[0] == '/')
{
// If the path begins with a leading slash, just return it as-is
return path;
@@ -132,13 +152,13 @@ namespace Emby.Server.Implementations.HttpServer
public Type GetServiceTypeByRequest(Type requestType)
{
- ServiceOperationsMap.TryGetValue(requestType, out var serviceType);
+ _serviceOperationsMap.TryGetValue(requestType, out var serviceType);
return serviceType;
}
public void AddServiceInfo(Type serviceType, Type requestType)
{
- ServiceOperationsMap[requestType] = serviceType;
+ _serviceOperationsMap[requestType] = serviceType;
}
private List<IHasRequestFilter> GetRequestFilterAttributes(Type requestDtoType)
@@ -168,7 +188,7 @@ namespace Emby.Server.Implementations.HttpServer
else
{
var inners = agg.InnerExceptions;
- if (inners != null && inners.Count > 0)
+ if (inners.Count > 0)
{
return GetActualException(inners[0]);
}
@@ -183,7 +203,8 @@ namespace Emby.Server.Implementations.HttpServer
switch (ex)
{
case ArgumentException _: return 400;
- case SecurityException _: return 401;
+ case AuthenticationException _: return 401;
+ case SecurityException _: return 403;
case DirectoryNotFoundException _:
case FileNotFoundException _:
case ResourceNotFoundException _: return 404;
@@ -192,55 +213,52 @@ namespace Emby.Server.Implementations.HttpServer
}
}
- private async Task ErrorHandler(Exception ex, IRequest httpReq, bool logExceptionStackTrace)
+ private async Task ErrorHandler(Exception ex, IRequest httpReq, int statusCode, string urlToLog)
{
- try
- {
- ex = GetActualException(ex);
+ bool ignoreStackTrace =
+ ex is SocketException
+ || ex is IOException
+ || ex is OperationCanceledException
+ || ex is SecurityException
+ || ex is AuthenticationException
+ || ex is FileNotFoundException;
- if (logExceptionStackTrace)
- {
- _logger.LogError(ex, "Error processing request");
- }
- else
- {
- _logger.LogError("Error processing request: {Message}", ex.Message);
- }
-
- var httpRes = httpReq.Response;
-
- if (httpRes.HasStarted)
- {
- return;
- }
+ if (ignoreStackTrace)
+ {
+ _logger.LogError("Error processing request: {Message}. URL: {Url}", ex.Message.TrimEnd('.'), urlToLog);
+ }
+ else
+ {
+ _logger.LogError(ex, "Error processing request. URL: {Url}", urlToLog);
+ }
- var statusCode = GetStatusCode(ex);
- httpRes.StatusCode = statusCode;
+ var httpRes = httpReq.Response;
- var errContent = NormalizeExceptionMessage(ex.Message);
- httpRes.ContentType = "text/plain";
- httpRes.ContentLength = errContent.Length;
- await httpRes.WriteAsync(errContent).ConfigureAwait(false);
- }
- catch (Exception errorEx)
+ if (httpRes.HasStarted)
{
- _logger.LogError(errorEx, "Error this.ProcessRequest(context)(Exception while writing error to the response)");
+ return;
}
+
+ httpRes.StatusCode = statusCode;
+
+ var errContent = NormalizeExceptionMessage(ex) ?? string.Empty;
+ httpRes.ContentType = "text/plain";
+ httpRes.ContentLength = errContent.Length;
+ await httpRes.WriteAsync(errContent).ConfigureAwait(false);
}
- private string NormalizeExceptionMessage(string msg)
+ private string NormalizeExceptionMessage(Exception ex)
{
- if (msg == null)
+ // Do not expose the exception message for AuthenticationException
+ if (ex is AuthenticationException)
{
- return string.Empty;
+ return null;
}
// Strip any information we don't want to reveal
-
- msg = msg.Replace(_config.ApplicationPaths.ProgramSystemPath, string.Empty, StringComparison.OrdinalIgnoreCase);
- msg = msg.Replace(_config.ApplicationPaths.ProgramDataPath, string.Empty, StringComparison.OrdinalIgnoreCase);
-
- return msg;
+ return ex.Message
+ ?.Replace(_config.ApplicationPaths.ProgramSystemPath, string.Empty, StringComparison.OrdinalIgnoreCase)
+ .Replace(_config.ApplicationPaths.ProgramDataPath, string.Empty, StringComparison.OrdinalIgnoreCase);
}
public static string RemoveQueryStringByKey(string url, string key)
@@ -305,7 +323,7 @@ namespace Emby.Server.Implementations.HttpServer
return true;
}
- host = host ?? string.Empty;
+ host ??= string.Empty;
if (_networkManager.IsInPrivateAddressSpace(host))
{
@@ -392,14 +410,14 @@ namespace Emby.Server.Implementations.HttpServer
}
/// <summary>
- /// Overridable method that can be used to implement a custom handler
+ /// Overridable method that can be used to implement a custom handler.
/// </summary>
private async Task RequestHandler(IHttpRequest httpReq, string urlString, string host, string localPath, CancellationToken cancellationToken)
{
var stopWatch = new Stopwatch();
stopWatch.Start();
var httpRes = httpReq.Response;
- string urlToLog = null;
+ string urlToLog = GetUrlToLog(urlString);
string remoteIp = httpReq.RemoteIp;
try
@@ -445,13 +463,11 @@ namespace Emby.Server.Implementations.HttpServer
return;
}
- urlToLog = GetUrlToLog(urlString);
-
if (string.Equals(localPath, _baseUrlPrefix + "/", StringComparison.OrdinalIgnoreCase)
|| string.Equals(localPath, _baseUrlPrefix, StringComparison.OrdinalIgnoreCase)
|| string.Equals(localPath, "/", StringComparison.OrdinalIgnoreCase)
|| string.IsNullOrEmpty(localPath)
- || !localPath.StartsWith(_baseUrlPrefix))
+ || !localPath.StartsWith(_baseUrlPrefix, StringComparison.OrdinalIgnoreCase))
{
// Always redirect back to the default path if the base prefix is invalid or missing
_logger.LogDebug("Normalizing a URL at {0}", localPath);
@@ -478,22 +494,35 @@ namespace Emby.Server.Implementations.HttpServer
}
else
{
- await ErrorHandler(new FileNotFoundException(), httpReq, false).ConfigureAwait(false);
+ throw new FileNotFoundException();
}
}
- catch (Exception ex) when (ex is SocketException || ex is IOException || ex is OperationCanceledException)
- {
- await ErrorHandler(ex, httpReq, false).ConfigureAwait(false);
- }
- catch (SecurityException ex)
+ catch (Exception requestEx)
{
- await ErrorHandler(ex, httpReq, false).ConfigureAwait(false);
- }
- catch (Exception ex)
- {
- var logException = !string.Equals(ex.GetType().Name, "SocketException", StringComparison.OrdinalIgnoreCase);
+ try
+ {
+ var requestInnerEx = GetActualException(requestEx);
+ var statusCode = GetStatusCode(requestInnerEx);
- await ErrorHandler(ex, httpReq, logException).ConfigureAwait(false);
+ // Do not handle 500 server exceptions manually when in development mode
+ // The framework-defined development exception page will be returned instead
+ if (statusCode == 500 && _hostEnvironment.IsDevelopment())
+ {
+ throw;
+ }
+
+ await ErrorHandler(requestInnerEx, httpReq, statusCode, urlToLog).ConfigureAwait(false);
+ }
+ catch (Exception handlerException)
+ {
+ var aggregateEx = new AggregateException("Error while handling request exception", requestEx, handlerException);
+ _logger.LogError(aggregateEx, "Error while handling exception in response to {Url}", urlToLog);
+
+ if (_hostEnvironment.IsDevelopment())
+ {
+ throw aggregateEx;
+ }
+ }
}
finally
{
@@ -582,17 +611,15 @@ namespace Emby.Server.Implementations.HttpServer
/// <summary>
/// Adds the rest handlers.
/// </summary>
- /// <param name="services">The services.</param>
- /// <param name="listeners"></param>
- /// <param name="urlPrefixes"></param>
- public void Init(IEnumerable<IService> services, IEnumerable<IWebSocketListener> listeners, IEnumerable<string> urlPrefixes)
+ /// <param name="serviceTypes">The service types to register with the <see cref="ServiceController"/>.</param>
+ /// <param name="listeners">The web socket listeners.</param>
+ /// <param name="urlPrefixes">The URL prefixes. See <see cref="UrlPrefixes"/>.</param>
+ public void Init(IEnumerable<Type> serviceTypes, IEnumerable<IWebSocketListener> listeners, IEnumerable<string> urlPrefixes)
{
_webSocketListeners = listeners.ToArray();
UrlPrefixes = urlPrefixes.ToArray();
- ServiceController = new ServiceController();
- var types = services.Select(r => r.GetType());
- ServiceController.Init(this, types);
+ ServiceController.Init(this, serviceTypes);
ResponseFilters = new Action<IRequest, HttpResponse, object>[]
{
@@ -684,7 +711,10 @@ namespace Emby.Server.Implementations.HttpServer
protected virtual void Dispose(bool disposing)
{
- if (_disposed) return;
+ if (_disposed)
+ {
+ return;
+ }
if (disposing)
{
diff --git a/Emby.Server.Implementations/HttpServer/HttpResultFactory.cs b/Emby.Server.Implementations/HttpServer/HttpResultFactory.cs
index cefcaa835..2e9ecc4ae 100644
--- a/Emby.Server.Implementations/HttpServer/HttpResultFactory.cs
+++ b/Emby.Server.Implementations/HttpServer/HttpResultFactory.cs
@@ -1,5 +1,4 @@
#pragma warning disable CS1591
-#pragma warning disable SA1600
using System;
using System.Collections.Generic;
@@ -29,6 +28,12 @@ namespace Emby.Server.Implementations.HttpServer
/// </summary>
public class HttpResultFactory : IHttpResultFactory
{
+ // Last-Modified and If-Modified-Since must follow strict date format,
+ // see https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/If-Modified-Since
+ private const string HttpDateFormat = "ddd, dd MMM yyyy HH:mm:ss \"GMT\"";
+ // We specifically use en-US culture because both day of week and month names require it
+ private static readonly CultureInfo _enUSculture = new CultureInfo("en-US", false);
+
/// <summary>
/// The logger.
/// </summary>
@@ -421,7 +426,11 @@ namespace Emby.Server.Implementations.HttpServer
if (!noCache)
{
- DateTime.TryParse(requestContext.Headers[HeaderNames.IfModifiedSince], out var ifModifiedSinceHeader);
+ if (!DateTime.TryParseExact(requestContext.Headers[HeaderNames.IfModifiedSince], HttpDateFormat, _enUSculture, DateTimeStyles.AssumeUniversal | DateTimeStyles.AdjustToUniversal, out var ifModifiedSinceHeader))
+ {
+ _logger.LogDebug("Failed to parse If-Modified-Since header date: {0}", requestContext.Headers[HeaderNames.IfModifiedSince]);
+ return null;
+ }
if (IsNotModified(ifModifiedSinceHeader, options.CacheDuration, options.DateLastModified))
{
@@ -440,7 +449,7 @@ namespace Emby.Server.Implementations.HttpServer
public Task<object> GetStaticFileResult(IRequest requestContext,
string path,
- FileShareMode fileShare = FileShareMode.Read)
+ FileShare fileShare = FileShare.Read)
{
if (string.IsNullOrEmpty(path))
{
@@ -464,7 +473,7 @@ namespace Emby.Server.Implementations.HttpServer
throw new ArgumentException("Path can't be empty.", nameof(options));
}
- if (fileShare != FileShareMode.Read && fileShare != FileShareMode.ReadWrite)
+ if (fileShare != FileShare.Read && fileShare != FileShare.ReadWrite)
{
throw new ArgumentException("FileShare must be either Read or ReadWrite");
}
@@ -492,9 +501,9 @@ namespace Emby.Server.Implementations.HttpServer
/// <param name="path">The path.</param>
/// <param name="fileShare">The file share.</param>
/// <returns>Stream.</returns>
- private Stream GetFileStream(string path, FileShareMode fileShare)
+ private Stream GetFileStream(string path, FileShare fileShare)
{
- return _fileSystem.GetFileStream(path, FileOpenMode.Open, FileAccessMode.Read, fileShare);
+ return new FileStream(path, FileMode.Open, FileAccess.Read, fileShare);
}
public Task<object> GetStaticResult(IRequest requestContext,
@@ -630,7 +639,7 @@ namespace Emby.Server.Implementations.HttpServer
if (lastModifiedDate.HasValue)
{
- responseHeaders[HeaderNames.LastModified] = lastModifiedDate.Value.ToString(CultureInfo.InvariantCulture);
+ responseHeaders[HeaderNames.LastModified] = lastModifiedDate.Value.ToUniversalTime().ToString(HttpDateFormat, _enUSculture);
}
}
diff --git a/Emby.Server.Implementations/HttpServer/IHttpListener.cs b/Emby.Server.Implementations/HttpServer/IHttpListener.cs
new file mode 100644
index 000000000..501593725
--- /dev/null
+++ b/Emby.Server.Implementations/HttpServer/IHttpListener.cs
@@ -0,0 +1,39 @@
+#pragma warning disable CS1591
+
+using System;
+using System.Threading;
+using System.Threading.Tasks;
+using Emby.Server.Implementations.Net;
+using MediaBrowser.Model.Services;
+using Microsoft.AspNetCore.Http;
+
+namespace Emby.Server.Implementations.HttpServer
+{
+ public interface IHttpListener : IDisposable
+ {
+ /// <summary>
+ /// Gets or sets the error handler.
+ /// </summary>
+ /// <value>The error handler.</value>
+ Func<Exception, IRequest, bool, bool, Task> ErrorHandler { get; set; }
+
+ /// <summary>
+ /// Gets or sets the request handler.
+ /// </summary>
+ /// <value>The request handler.</value>
+ Func<IHttpRequest, string, string, string, CancellationToken, Task> RequestHandler { get; set; }
+
+ /// <summary>
+ /// Gets or sets the web socket handler.
+ /// </summary>
+ /// <value>The web socket handler.</value>
+ Action<WebSocketConnectEventArgs> WebSocketConnected { get; set; }
+
+ /// <summary>
+ /// Stops this instance.
+ /// </summary>
+ Task Stop();
+
+ Task ProcessWebSocketRequest(HttpContext ctx);
+ }
+}
diff --git a/Emby.Server.Implementations/HttpServer/RangeRequestWriter.cs b/Emby.Server.Implementations/HttpServer/RangeRequestWriter.cs
index 7cb113a58..8b9028f6b 100644
--- a/Emby.Server.Implementations/HttpServer/RangeRequestWriter.cs
+++ b/Emby.Server.Implementations/HttpServer/RangeRequestWriter.cs
@@ -1,5 +1,4 @@
#pragma warning disable CS1591
-#pragma warning disable SA1600
using System;
using System.Collections.Generic;
diff --git a/Emby.Server.Implementations/HttpServer/ResponseFilter.cs b/Emby.Server.Implementations/HttpServer/ResponseFilter.cs
index 5e0466629..4089aa578 100644
--- a/Emby.Server.Implementations/HttpServer/ResponseFilter.cs
+++ b/Emby.Server.Implementations/HttpServer/ResponseFilter.cs
@@ -82,6 +82,10 @@ namespace Emby.Server.Implementations.HttpServer
{
return null;
}
+ else if (inString.Length == 0)
+ {
+ return inString;
+ }
var newString = new StringBuilder(inString.Length);
diff --git a/Emby.Server.Implementations/HttpServer/Security/AuthService.cs b/Emby.Server.Implementations/HttpServer/Security/AuthService.cs
index 03b5b748d..256b24924 100644
--- a/Emby.Server.Implementations/HttpServer/Security/AuthService.cs
+++ b/Emby.Server.Implementations/HttpServer/Security/AuthService.cs
@@ -1,8 +1,8 @@
#pragma warning disable CS1591
-#pragma warning disable SA1600
using System;
using System.Linq;
+using System.Security.Authentication;
using Emby.Server.Implementations.SocketSharp;
using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Configuration;
@@ -69,7 +69,7 @@ namespace Emby.Server.Implementations.HttpServer.Security
if (user == null && auth.UserId != Guid.Empty)
{
- throw new SecurityException("User with Id " + auth.UserId + " not found");
+ throw new AuthenticationException("User with Id " + auth.UserId + " not found");
}
if (user != null)
@@ -109,18 +109,12 @@ namespace Emby.Server.Implementations.HttpServer.Security
{
if (user.Policy.IsDisabled)
{
- throw new SecurityException("User account has been disabled.")
- {
- SecurityExceptionType = SecurityExceptionType.Unauthenticated
- };
+ throw new SecurityException("User account has been disabled.");
}
if (!user.Policy.EnableRemoteAccess && !_networkManager.IsInLocalNetwork(request.RemoteIp))
{
- throw new SecurityException("User account has been disabled.")
- {
- SecurityExceptionType = SecurityExceptionType.Unauthenticated
- };
+ throw new SecurityException("User account has been disabled.");
}
if (!user.Policy.IsAdministrator
@@ -129,10 +123,7 @@ namespace Emby.Server.Implementations.HttpServer.Security
{
request.Response.Headers.Add("X-Application-Error-Code", "ParentalControl");
- throw new SecurityException("This user account is not allowed access at this time.")
- {
- SecurityExceptionType = SecurityExceptionType.ParentalControl
- };
+ throw new SecurityException("This user account is not allowed access at this time.");
}
}
@@ -191,10 +182,7 @@ namespace Emby.Server.Implementations.HttpServer.Security
{
if (user == null || !user.Policy.IsAdministrator)
{
- throw new SecurityException("User does not have admin access.")
- {
- SecurityExceptionType = SecurityExceptionType.Unauthenticated
- };
+ throw new SecurityException("User does not have admin access.");
}
}
@@ -202,10 +190,7 @@ namespace Emby.Server.Implementations.HttpServer.Security
{
if (user == null || !user.Policy.EnableContentDeletion)
{
- throw new SecurityException("User does not have delete access.")
- {
- SecurityExceptionType = SecurityExceptionType.Unauthenticated
- };
+ throw new SecurityException("User does not have delete access.");
}
}
@@ -213,10 +198,7 @@ namespace Emby.Server.Implementations.HttpServer.Security
{
if (user == null || !user.Policy.EnableContentDownloading)
{
- throw new SecurityException("User does not have download access.")
- {
- SecurityExceptionType = SecurityExceptionType.Unauthenticated
- };
+ throw new SecurityException("User does not have download access.");
}
}
}
@@ -231,14 +213,14 @@ namespace Emby.Server.Implementations.HttpServer.Security
{
if (string.IsNullOrEmpty(token))
{
- throw new SecurityException("Access token is required.");
+ throw new AuthenticationException("Access token is required.");
}
var info = GetTokenInfo(request);
if (info == null)
{
- throw new SecurityException("Access token is invalid or expired.");
+ throw new AuthenticationException("Access token is invalid or expired.");
}
//if (!string.IsNullOrEmpty(info.UserId))
diff --git a/Emby.Server.Implementations/HttpServer/Security/AuthorizationContext.cs b/Emby.Server.Implementations/HttpServer/Security/AuthorizationContext.cs
index e8884bca0..129faeaab 100644
--- a/Emby.Server.Implementations/HttpServer/Security/AuthorizationContext.cs
+++ b/Emby.Server.Implementations/HttpServer/Security/AuthorizationContext.cs
@@ -1,5 +1,4 @@
#pragma warning disable CS1591
-#pragma warning disable SA1600
using System;
using System.Collections.Generic;
diff --git a/Emby.Server.Implementations/HttpServer/Security/SessionContext.cs b/Emby.Server.Implementations/HttpServer/Security/SessionContext.cs
index a6a0f5b03..166952c64 100644
--- a/Emby.Server.Implementations/HttpServer/Security/SessionContext.cs
+++ b/Emby.Server.Implementations/HttpServer/Security/SessionContext.cs
@@ -1,5 +1,4 @@
#pragma warning disable CS1591
-#pragma warning disable SA1600
using System;
using MediaBrowser.Controller.Entities;
diff --git a/Emby.Server.Implementations/IO/ExtendedFileSystemInfo.cs b/Emby.Server.Implementations/IO/ExtendedFileSystemInfo.cs
index 5be144452..545d73e05 100644
--- a/Emby.Server.Implementations/IO/ExtendedFileSystemInfo.cs
+++ b/Emby.Server.Implementations/IO/ExtendedFileSystemInfo.cs
@@ -1,12 +1,13 @@
#pragma warning disable CS1591
-#pragma warning disable SA1600
namespace Emby.Server.Implementations.IO
{
public class ExtendedFileSystemInfo
{
public bool IsHidden { get; set; }
+
public bool IsReadOnly { get; set; }
+
public bool Exists { get; set; }
}
}
diff --git a/Emby.Server.Implementations/IO/FileRefresher.cs b/Emby.Server.Implementations/IO/FileRefresher.cs
index cf92ddbcd..ef93779aa 100644
--- a/Emby.Server.Implementations/IO/FileRefresher.cs
+++ b/Emby.Server.Implementations/IO/FileRefresher.cs
@@ -1,5 +1,4 @@
#pragma warning disable CS1591
-#pragma warning disable SA1600
using System;
using System.Collections.Generic;
@@ -15,27 +14,29 @@ namespace Emby.Server.Implementations.IO
{
public class FileRefresher : IDisposable
{
- private ILogger Logger { get; set; }
- private ILibraryManager LibraryManager { get; set; }
- private IServerConfigurationManager ConfigurationManager { get; set; }
+ private readonly ILogger _logger;
+ private readonly ILibraryManager _libraryManager;
+ private readonly IServerConfigurationManager _configurationManager;
+
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;
+ private Timer _timer;
public FileRefresher(string path, IServerConfigurationManager configurationManager, ILibraryManager libraryManager, ILogger logger)
{
logger.LogDebug("New file refresher created for {0}", path);
Path = path;
- ConfigurationManager = configurationManager;
- LibraryManager = libraryManager;
- Logger = logger;
+ _configurationManager = configurationManager;
+ _libraryManager = libraryManager;
+ _logger = logger;
AddPath(path);
}
+ public event EventHandler<EventArgs> Completed;
+
+ public string Path { get; private set; }
+
private void AddAffectedPath(string path)
{
if (string.IsNullOrEmpty(path))
@@ -80,11 +81,11 @@ namespace Emby.Server.Implementations.IO
if (_timer == null)
{
- _timer = new Timer(OnTimerCallback, null, TimeSpan.FromSeconds(ConfigurationManager.Configuration.LibraryMonitorDelay), TimeSpan.FromMilliseconds(-1));
+ _timer = new Timer(OnTimerCallback, null, TimeSpan.FromSeconds(_configurationManager.Configuration.LibraryMonitorDelay), TimeSpan.FromMilliseconds(-1));
}
else
{
- _timer.Change(TimeSpan.FromSeconds(ConfigurationManager.Configuration.LibraryMonitorDelay), TimeSpan.FromMilliseconds(-1));
+ _timer.Change(TimeSpan.FromSeconds(_configurationManager.Configuration.LibraryMonitorDelay), TimeSpan.FromMilliseconds(-1));
}
}
}
@@ -93,7 +94,7 @@ namespace Emby.Server.Implementations.IO
{
lock (_timerLock)
{
- Logger.LogDebug("Resetting file refresher from {0} to {1}", Path, path);
+ _logger.LogDebug("Resetting file refresher from {0} to {1}", Path, path);
Path = path;
AddAffectedPath(path);
@@ -116,7 +117,7 @@ namespace Emby.Server.Implementations.IO
paths = _affectedPaths.ToList();
}
- Logger.LogDebug("Timer stopped.");
+ _logger.LogDebug("Timer stopped.");
DisposeTimer();
Completed?.Invoke(this, EventArgs.Empty);
@@ -127,7 +128,7 @@ namespace Emby.Server.Implementations.IO
}
catch (Exception ex)
{
- Logger.LogError(ex, "Error processing directory changes");
+ _logger.LogError(ex, "Error processing directory changes");
}
}
@@ -147,7 +148,7 @@ namespace Emby.Server.Implementations.IO
continue;
}
- Logger.LogInformation("{name} ({path}) will be refreshed.", item.Name, item.Path);
+ _logger.LogInformation("{name} ({path}) will be refreshed.", item.Name, item.Path);
try
{
@@ -158,11 +159,11 @@ namespace Emby.Server.Implementations.IO
// 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.LogError(ex, "Error refreshing {name}", item.Name);
+ _logger.LogError(ex, "Error refreshing {name}", item.Name);
}
catch (Exception ex)
{
- Logger.LogError(ex, "Error refreshing {name}", item.Name);
+ _logger.LogError(ex, "Error refreshing {name}", item.Name);
}
}
}
@@ -178,7 +179,7 @@ namespace Emby.Server.Implementations.IO
while (item == null && !string.IsNullOrEmpty(path))
{
- item = LibraryManager.FindByPath(path, null);
+ item = _libraryManager.FindByPath(path, null);
path = System.IO.Path.GetDirectoryName(path);
}
diff --git a/Emby.Server.Implementations/IO/LibraryMonitor.cs b/Emby.Server.Implementations/IO/LibraryMonitor.cs
index 7777efc3b..5a1eb43bc 100644
--- a/Emby.Server.Implementations/IO/LibraryMonitor.cs
+++ b/Emby.Server.Implementations/IO/LibraryMonitor.cs
@@ -1,5 +1,4 @@
#pragma warning disable CS1591
-#pragma warning disable SA1600
using System;
using System.Collections.Concurrent;
@@ -18,6 +17,11 @@ namespace Emby.Server.Implementations.IO
{
public class LibraryMonitor : ILibraryMonitor
{
+ private readonly ILogger _logger;
+ private readonly ILibraryManager _libraryManager;
+ private readonly IServerConfigurationManager _configurationManager;
+ private readonly IFileSystem _fileSystem;
+
/// <summary>
/// The file system watchers.
/// </summary>
@@ -114,34 +118,23 @@ namespace Emby.Server.Implementations.IO
}
catch (Exception ex)
{
- Logger.LogError(ex, "Error in ReportFileSystemChanged for {path}", path);
+ _logger.LogError(ex, "Error in ReportFileSystemChanged for {path}", path);
}
}
}
/// <summary>
- /// Gets or sets the logger.
- /// </summary>
- /// <value>The logger.</value>
- private ILogger Logger { get; set; }
-
- private ILibraryManager LibraryManager { get; set; }
- private IServerConfigurationManager ConfigurationManager { get; set; }
-
- private readonly IFileSystem _fileSystem;
-
- /// <summary>
/// Initializes a new instance of the <see cref="LibraryMonitor" /> class.
/// </summary>
public LibraryMonitor(
- ILoggerFactory loggerFactory,
+ ILogger<LibraryMonitor> logger,
ILibraryManager libraryManager,
IServerConfigurationManager configurationManager,
IFileSystem fileSystem)
{
- LibraryManager = libraryManager;
- Logger = loggerFactory.CreateLogger(GetType().Name);
- ConfigurationManager = configurationManager;
+ _libraryManager = libraryManager;
+ _logger = logger;
+ _configurationManager = configurationManager;
_fileSystem = fileSystem;
}
@@ -152,7 +145,7 @@ namespace Emby.Server.Implementations.IO
return false;
}
- var options = LibraryManager.GetLibraryOptions(item);
+ var options = _libraryManager.GetLibraryOptions(item);
if (options != null)
{
@@ -164,12 +157,12 @@ namespace Emby.Server.Implementations.IO
public void Start()
{
- LibraryManager.ItemAdded += OnLibraryManagerItemAdded;
- LibraryManager.ItemRemoved += OnLibraryManagerItemRemoved;
+ _libraryManager.ItemAdded += OnLibraryManagerItemAdded;
+ _libraryManager.ItemRemoved += OnLibraryManagerItemRemoved;
var pathsToWatch = new List<string>();
- var paths = LibraryManager
+ var paths = _libraryManager
.RootFolder
.Children
.Where(IsLibraryMonitorEnabled)
@@ -262,7 +255,7 @@ namespace Emby.Server.Implementations.IO
if (!Directory.Exists(path))
{
// Seeing a crash in the mono runtime due to an exception being thrown on a different thread
- Logger.LogInformation("Skipping realtime monitor for {Path} because the path does not exist", path);
+ _logger.LogInformation("Skipping realtime monitor for {Path} because the path does not exist", path);
return;
}
@@ -298,7 +291,7 @@ namespace Emby.Server.Implementations.IO
if (_fileSystemWatchers.TryAdd(path, newWatcher))
{
newWatcher.EnableRaisingEvents = true;
- Logger.LogInformation("Watching directory " + path);
+ _logger.LogInformation("Watching directory " + path);
}
else
{
@@ -308,7 +301,7 @@ namespace Emby.Server.Implementations.IO
}
catch (Exception ex)
{
- Logger.LogError(ex, "Error watching path: {path}", path);
+ _logger.LogError(ex, "Error watching path: {path}", path);
}
});
}
@@ -334,7 +327,7 @@ namespace Emby.Server.Implementations.IO
{
using (watcher)
{
- Logger.LogInformation("Stopping directory watching for path {Path}", watcher.Path);
+ _logger.LogInformation("Stopping directory watching for path {Path}", watcher.Path);
watcher.Created -= OnWatcherChanged;
watcher.Deleted -= OnWatcherChanged;
@@ -373,7 +366,7 @@ namespace Emby.Server.Implementations.IO
var ex = e.GetException();
var dw = (FileSystemWatcher)sender;
- Logger.LogError(ex, "Error in Directory watcher for: {Path}", dw.Path);
+ _logger.LogError(ex, "Error in Directory watcher for: {Path}", dw.Path);
DisposeWatcher(dw, true);
}
@@ -391,7 +384,7 @@ namespace Emby.Server.Implementations.IO
}
catch (Exception ex)
{
- Logger.LogError(ex, "Exception in ReportFileSystemChanged. Path: {FullPath}", e.FullPath);
+ _logger.LogError(ex, "Exception in ReportFileSystemChanged. Path: {FullPath}", e.FullPath);
}
}
@@ -417,13 +410,13 @@ namespace Emby.Server.Implementations.IO
{
if (_fileSystem.AreEqual(i, path))
{
- Logger.LogDebug("Ignoring change to {Path}", path);
+ _logger.LogDebug("Ignoring change to {Path}", path);
return true;
}
if (_fileSystem.ContainsSubPath(i, path))
{
- Logger.LogDebug("Ignoring change to {Path}", path);
+ _logger.LogDebug("Ignoring change to {Path}", path);
return true;
}
@@ -431,7 +424,7 @@ namespace Emby.Server.Implementations.IO
var parent = Path.GetDirectoryName(i);
if (!string.IsNullOrEmpty(parent) && _fileSystem.AreEqual(parent, path))
{
- Logger.LogDebug("Ignoring change to {Path}", path);
+ _logger.LogDebug("Ignoring change to {Path}", path);
return true;
}
@@ -486,7 +479,7 @@ namespace Emby.Server.Implementations.IO
}
}
- var newRefresher = new FileRefresher(path, ConfigurationManager, LibraryManager, Logger);
+ var newRefresher = new FileRefresher(path, _configurationManager, _libraryManager, _logger);
newRefresher.Completed += NewRefresher_Completed;
_activeRefreshers.Add(newRefresher);
}
@@ -503,8 +496,8 @@ namespace Emby.Server.Implementations.IO
/// </summary>
public void Stop()
{
- LibraryManager.ItemAdded -= OnLibraryManagerItemAdded;
- LibraryManager.ItemRemoved -= OnLibraryManagerItemRemoved;
+ _libraryManager.ItemAdded -= OnLibraryManagerItemAdded;
+ _libraryManager.ItemRemoved -= OnLibraryManagerItemRemoved;
foreach (var watcher in _fileSystemWatchers.Values.ToList())
{
diff --git a/Emby.Server.Implementations/IO/ManagedFileSystem.cs b/Emby.Server.Implementations/IO/ManagedFileSystem.cs
index e27081c45..7461ec4f1 100644
--- a/Emby.Server.Implementations/IO/ManagedFileSystem.cs
+++ b/Emby.Server.Implementations/IO/ManagedFileSystem.cs
@@ -1,10 +1,9 @@
#pragma warning disable CS1591
-#pragma warning disable SA1600
using System;
using System.Collections.Generic;
-using System.Globalization;
using System.Diagnostics;
+using System.Globalization;
using System.IO;
using System.Linq;
using System.Text;
@@ -17,7 +16,7 @@ using OperatingSystem = MediaBrowser.Common.System.OperatingSystem;
namespace Emby.Server.Implementations.IO
{
/// <summary>
- /// Class ManagedFileSystem
+ /// Class ManagedFileSystem.
/// </summary>
public class ManagedFileSystem : IFileSystem
{
@@ -80,20 +79,20 @@ namespace Emby.Server.Implementations.IO
public virtual string MakeAbsolutePath(string folderPath, string filePath)
{
- if (string.IsNullOrWhiteSpace(filePath)
- // stream
- || filePath.Contains("://"))
+ // path is actually a stream
+ if (string.IsNullOrWhiteSpace(filePath) || filePath.Contains("://", StringComparison.Ordinal))
{
return filePath;
}
if (filePath.Length > 3 && filePath[1] == ':' && filePath[2] == '/')
{
- return filePath; // absolute local path
+ // absolute local path
+ return filePath;
}
// unc path
- if (filePath.StartsWith("\\\\"))
+ if (filePath.StartsWith("\\\\", StringComparison.Ordinal))
{
return filePath;
}
@@ -101,13 +100,16 @@ namespace Emby.Server.Implementations.IO
var firstChar = filePath[0];
if (firstChar == '/')
{
- // For this we don't really know.
+ // for this we don't really know
return filePath;
}
- if (firstChar == '\\') //relative path
+
+ // relative path
+ if (firstChar == '\\')
{
filePath = filePath.Substring(1);
}
+
try
{
return Path.GetFullPath(Path.Combine(folderPath, filePath));
@@ -131,11 +133,7 @@ namespace Emby.Server.Implementations.IO
/// </summary>
/// <param name="shortcutPath">The shortcut path.</param>
/// <param name="target">The target.</param>
- /// <exception cref="ArgumentNullException">
- /// shortcutPath
- /// or
- /// target
- /// </exception>
+ /// <exception cref="ArgumentNullException">The shortcutPath or target is null.</exception>
public virtual void CreateShortcut(string shortcutPath, string target)
{
if (string.IsNullOrEmpty(shortcutPath))
@@ -281,11 +279,11 @@ namespace Emby.Server.Implementations.IO
}
/// <summary>
- /// Takes a filename and removes invalid characters
+ /// Takes a filename and removes invalid characters.
/// </summary>
/// <param name="filename">The filename.</param>
/// <returns>System.String.</returns>
- /// <exception cref="ArgumentNullException">filename</exception>
+ /// <exception cref="ArgumentNullException">The filename is null.</exception>
public virtual string GetValidFilename(string filename)
{
var builder = new StringBuilder(filename);
@@ -366,90 +364,9 @@ namespace Emby.Server.Implementations.IO
return GetLastWriteTimeUtc(GetFileSystemInfo(path));
}
- /// <summary>
- /// Gets the file stream.
- /// </summary>
- /// <param name="path">The path.</param>
- /// <param name="mode">The mode.</param>
- /// <param name="access">The access.</param>
- /// <param name="share">The share.</param>
- /// <param name="isAsync">if set to <c>true</c> [is asynchronous].</param>
- /// <returns>FileStream.</returns>
- public virtual Stream GetFileStream(string path, FileOpenMode mode, FileAccessMode access, FileShareMode share, bool isAsync = false)
- {
- if (isAsync)
- {
- return GetFileStream(path, mode, access, share, FileOpenOptions.Asynchronous);
- }
-
- return GetFileStream(path, mode, access, share, FileOpenOptions.None);
- }
-
- public virtual Stream GetFileStream(string path, FileOpenMode mode, FileAccessMode access, FileShareMode share, FileOpenOptions fileOpenOptions)
- => new FileStream(path, GetFileMode(mode), GetFileAccess(access), GetFileShare(share), 4096, GetFileOptions(fileOpenOptions));
-
- private static FileOptions GetFileOptions(FileOpenOptions mode)
- {
- var val = (int)mode;
- return (FileOptions)val;
- }
-
- private static FileMode GetFileMode(FileOpenMode mode)
- {
- switch (mode)
- {
- //case FileOpenMode.Append:
- // return FileMode.Append;
- case FileOpenMode.Create:
- return FileMode.Create;
- case FileOpenMode.CreateNew:
- return FileMode.CreateNew;
- case FileOpenMode.Open:
- return FileMode.Open;
- case FileOpenMode.OpenOrCreate:
- return FileMode.OpenOrCreate;
- //case FileOpenMode.Truncate:
- // return FileMode.Truncate;
- default:
- throw new Exception("Unrecognized FileOpenMode");
- }
- }
-
- private static FileAccess GetFileAccess(FileAccessMode mode)
- {
- switch (mode)
- {
- //case FileAccessMode.ReadWrite:
- // return FileAccess.ReadWrite;
- case FileAccessMode.Write:
- return FileAccess.Write;
- case FileAccessMode.Read:
- return FileAccess.Read;
- default:
- throw new Exception("Unrecognized FileAccessMode");
- }
- }
-
- private static FileShare GetFileShare(FileShareMode mode)
- {
- switch (mode)
- {
- case FileShareMode.ReadWrite:
- return FileShare.ReadWrite;
- case FileShareMode.Write:
- return FileShare.Write;
- case FileShareMode.Read:
- return FileShare.Read;
- case FileShareMode.None:
- return FileShare.None;
- default:
- throw new Exception("Unrecognized FileShareMode");
- }
- }
-
public virtual void SetHidden(string path, bool isHidden)
{
- if (OperatingSystem.Id != MediaBrowser.Model.System.OperatingSystemId.Windows)
+ if (OperatingSystem.Id != OperatingSystemId.Windows)
{
return;
}
@@ -473,7 +390,7 @@ namespace Emby.Server.Implementations.IO
public virtual void SetReadOnly(string path, bool isReadOnly)
{
- if (OperatingSystem.Id != MediaBrowser.Model.System.OperatingSystemId.Windows)
+ if (OperatingSystem.Id != OperatingSystemId.Windows)
{
return;
}
@@ -497,7 +414,7 @@ namespace Emby.Server.Implementations.IO
public virtual void SetAttributes(string path, bool isHidden, bool isReadOnly)
{
- if (OperatingSystem.Id != MediaBrowser.Model.System.OperatingSystemId.Windows)
+ if (OperatingSystem.Id != OperatingSystemId.Windows)
{
return;
}
@@ -670,11 +587,11 @@ namespace Emby.Server.Implementations.IO
// some drives on linux have no actual size or are used for other purposes
return DriveInfo.GetDrives().Where(d => d.IsReady && d.TotalSize != 0 && d.DriveType != DriveType.Ram)
.Select(d => new FileSystemMetadata
- {
- Name = d.Name,
- FullName = d.RootDirectory.FullName,
- IsDirectory = true
- }).ToList();
+ {
+ Name = d.Name,
+ FullName = d.RootDirectory.FullName,
+ IsDirectory = true
+ }).ToList();
}
public virtual IEnumerable<FileSystemMetadata> GetDirectories(string path, bool recursive = false)
@@ -780,7 +697,7 @@ namespace Emby.Server.Implementations.IO
public virtual void SetExecutable(string path)
{
- if (OperatingSystem.Id == MediaBrowser.Model.System.OperatingSystemId.Darwin)
+ if (OperatingSystem.Id == OperatingSystemId.Darwin)
{
RunProcess("chmod", "+x \"" + path + "\"", Path.GetDirectoryName(path));
}
diff --git a/Emby.Server.Implementations/IO/MbLinkShortcutHandler.cs b/Emby.Server.Implementations/IO/MbLinkShortcutHandler.cs
index 574b63ae6..e6696b8c4 100644
--- a/Emby.Server.Implementations/IO/MbLinkShortcutHandler.cs
+++ b/Emby.Server.Implementations/IO/MbLinkShortcutHandler.cs
@@ -1,5 +1,4 @@
#pragma warning disable CS1591
-#pragma warning disable SA1600
using System;
using System.IO;
diff --git a/Emby.Server.Implementations/IO/StreamHelper.cs b/Emby.Server.Implementations/IO/StreamHelper.cs
index c99018e40..40b397edc 100644
--- a/Emby.Server.Implementations/IO/StreamHelper.cs
+++ b/Emby.Server.Implementations/IO/StreamHelper.cs
@@ -1,5 +1,4 @@
#pragma warning disable CS1591
-#pragma warning disable SA1600
using System;
using System.Buffers;
diff --git a/Emby.Server.Implementations/IStartupOptions.cs b/Emby.Server.Implementations/IStartupOptions.cs
index 6e915de3d..16b68170b 100644
--- a/Emby.Server.Implementations/IStartupOptions.cs
+++ b/Emby.Server.Implementations/IStartupOptions.cs
@@ -3,33 +3,38 @@ namespace Emby.Server.Implementations
public interface IStartupOptions
{
/// <summary>
- /// --ffmpeg
+ /// Gets the value of the --ffmpeg command line option.
/// </summary>
string FFmpegPath { get; }
/// <summary>
- /// --service
+ /// Gets the value of the --service command line option.
/// </summary>
bool IsService { get; }
/// <summary>
- /// --noautorunwebapp
+ /// Gets the value of the --noautorunwebapp command line option.
/// </summary>
bool NoAutoRunWebApp { get; }
/// <summary>
- /// --package-name
+ /// Gets the value of the --package-name command line option.
/// </summary>
string PackageName { get; }
/// <summary>
- /// --restartpath
+ /// Gets the value of the --restartpath command line option.
/// </summary>
string RestartPath { get; }
/// <summary>
- /// --restartargs
+ /// Gets the value of the --restartargs command line option.
/// </summary>
string RestartArgs { get; }
+
+ /// <summary>
+ /// Gets the value of the --plugin-manifest-url command line option.
+ /// </summary>
+ string PluginManifestUrl { get; }
}
}
diff --git a/Emby.Server.Implementations/Images/BaseDynamicImageProvider.cs b/Emby.Server.Implementations/Images/BaseDynamicImageProvider.cs
index acf3a3b23..fd50f156a 100644
--- a/Emby.Server.Implementations/Images/BaseDynamicImageProvider.cs
+++ b/Emby.Server.Implementations/Images/BaseDynamicImageProvider.cs
@@ -1,5 +1,4 @@
#pragma warning disable CS1591
-#pragma warning disable SA1600
using System;
using System.Collections.Generic;
diff --git a/Emby.Server.Implementations/Library/DefaultAuthenticationProvider.cs b/Emby.Server.Implementations/Library/DefaultAuthenticationProvider.cs
index ab036eca7..52c8facc3 100644
--- a/Emby.Server.Implementations/Library/DefaultAuthenticationProvider.cs
+++ b/Emby.Server.Implementations/Library/DefaultAuthenticationProvider.cs
@@ -47,7 +47,7 @@ namespace Emby.Server.Implementations.Library
{
if (resolvedUser == null)
{
- throw new ArgumentNullException(nameof(resolvedUser));
+ throw new AuthenticationException($"Specified user does not exist.");
}
bool success = false;
diff --git a/Emby.Server.Implementations/Library/ExclusiveLiveStream.cs b/Emby.Server.Implementations/Library/ExclusiveLiveStream.cs
index 3eb64c29c..9a7186898 100644
--- a/Emby.Server.Implementations/Library/ExclusiveLiveStream.cs
+++ b/Emby.Server.Implementations/Library/ExclusiveLiveStream.cs
@@ -1,5 +1,4 @@
#pragma warning disable CS1591
-#pragma warning disable SA1600
using System;
using System.Globalization;
diff --git a/Emby.Server.Implementations/Library/LibraryManager.cs b/Emby.Server.Implementations/Library/LibraryManager.cs
index ae3cdece9..0b86b2db7 100644
--- a/Emby.Server.Implementations/Library/LibraryManager.cs
+++ b/Emby.Server.Implementations/Library/LibraryManager.cs
@@ -1,5 +1,4 @@
#pragma warning disable CS1591
-#pragma warning disable SA1600
using System;
using System.Collections.Concurrent;
@@ -29,14 +28,15 @@ using MediaBrowser.Controller.Entities.TV;
using MediaBrowser.Controller.IO;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.LiveTv;
+using MediaBrowser.Controller.MediaEncoding;
using MediaBrowser.Controller.Persistence;
using MediaBrowser.Controller.Providers;
using MediaBrowser.Controller.Resolvers;
using MediaBrowser.Controller.Sorting;
using MediaBrowser.Model.Configuration;
+using MediaBrowser.Model.Dlna;
using MediaBrowser.Model.Dto;
using MediaBrowser.Model.Entities;
-using MediaBrowser.Model.Extensions;
using MediaBrowser.Model.IO;
using MediaBrowser.Model.Library;
using MediaBrowser.Model.Net;
@@ -54,6 +54,29 @@ namespace Emby.Server.Implementations.Library
/// </summary>
public class LibraryManager : ILibraryManager
{
+ private readonly ILogger _logger;
+ private readonly ITaskManager _taskManager;
+ private readonly IUserManager _userManager;
+ private readonly IUserDataManager _userDataRepository;
+ private readonly IServerConfigurationManager _configurationManager;
+ private readonly Lazy<ILibraryMonitor> _libraryMonitorFactory;
+ private readonly Lazy<IProviderManager> _providerManagerFactory;
+ private readonly Lazy<IUserViewManager> _userviewManagerFactory;
+ private readonly IServerApplicationHost _appHost;
+ private readonly IMediaEncoder _mediaEncoder;
+ private readonly IFileSystem _fileSystem;
+ private readonly IItemRepository _itemRepository;
+ private readonly ConcurrentDictionary<Guid, BaseItem> _libraryItemsCache;
+
+ private NamingOptions _namingOptions;
+ private string[] _videoFileExtensions;
+
+ private ILibraryMonitor LibraryMonitor => _libraryMonitorFactory.Value;
+
+ private IProviderManager ProviderManager => _providerManagerFactory.Value;
+
+ private IUserViewManager UserViewManager => _userviewManagerFactory.Value;
+
/// <summary>
/// Gets or sets the postscan tasks.
/// </summary>
@@ -87,12 +110,6 @@ namespace Emby.Server.Implementations.Library
private IBaseItemComparer[] Comparers { get; set; }
/// <summary>
- /// Gets or sets the active item repository
- /// </summary>
- /// <value>The item repository.</value>
- public IItemRepository ItemRepository { get; set; }
-
- /// <summary>
/// Occurs when [item added].
/// </summary>
public event EventHandler<ItemChangeEventArgs> ItemAdded;
@@ -107,87 +124,47 @@ namespace Emby.Server.Implementations.Library
/// </summary>
public event EventHandler<ItemChangeEventArgs> ItemRemoved;
- /// <summary>
- /// The _logger
- /// </summary>
- private readonly ILogger _logger;
-
- /// <summary>
- /// The _task manager
- /// </summary>
- private readonly ITaskManager _taskManager;
-
- /// <summary>
- /// The _user manager
- /// </summary>
- private readonly IUserManager _userManager;
-
- /// <summary>
- /// The _user data repository
- /// </summary>
- private readonly IUserDataManager _userDataRepository;
-
- /// <summary>
- /// Gets or sets the configuration manager.
- /// </summary>
- /// <value>The configuration manager.</value>
- private IServerConfigurationManager ConfigurationManager { get; set; }
-
- private readonly Func<ILibraryMonitor> _libraryMonitorFactory;
- private readonly Func<IProviderManager> _providerManagerFactory;
- private readonly Func<IUserViewManager> _userviewManager;
public bool IsScanRunning { get; private set; }
- private IServerApplicationHost _appHost;
-
- /// <summary>
- /// The _library items cache
- /// </summary>
- private readonly ConcurrentDictionary<Guid, BaseItem> _libraryItemsCache;
-
- /// <summary>
- /// Gets the library items cache.
- /// </summary>
- /// <value>The library items cache.</value>
- private ConcurrentDictionary<Guid, BaseItem> LibraryItemsCache => _libraryItemsCache;
-
- private readonly IFileSystem _fileSystem;
-
/// <summary>
/// Initializes a new instance of the <see cref="LibraryManager" /> class.
/// </summary>
/// <param name="appHost">The application host</param>
- /// <param name="loggerFactory">The logger factory.</param>
+ /// <param name="logger">The logger.</param>
/// <param name="taskManager">The task manager.</param>
/// <param name="userManager">The user manager.</param>
/// <param name="configurationManager">The configuration manager.</param>
/// <param name="userDataRepository">The user data repository.</param>
public LibraryManager(
IServerApplicationHost appHost,
- ILoggerFactory loggerFactory,
+ ILogger<LibraryManager> logger,
ITaskManager taskManager,
IUserManager userManager,
IServerConfigurationManager configurationManager,
IUserDataManager userDataRepository,
- Func<ILibraryMonitor> libraryMonitorFactory,
+ Lazy<ILibraryMonitor> libraryMonitorFactory,
IFileSystem fileSystem,
- Func<IProviderManager> providerManagerFactory,
- Func<IUserViewManager> userviewManager)
+ Lazy<IProviderManager> providerManagerFactory,
+ Lazy<IUserViewManager> userviewManagerFactory,
+ IMediaEncoder mediaEncoder,
+ IItemRepository itemRepository)
{
_appHost = appHost;
- _logger = loggerFactory.CreateLogger(nameof(LibraryManager));
+ _logger = logger;
_taskManager = taskManager;
_userManager = userManager;
- ConfigurationManager = configurationManager;
+ _configurationManager = configurationManager;
_userDataRepository = userDataRepository;
_libraryMonitorFactory = libraryMonitorFactory;
_fileSystem = fileSystem;
_providerManagerFactory = providerManagerFactory;
- _userviewManager = userviewManager;
+ _userviewManagerFactory = userviewManagerFactory;
+ _mediaEncoder = mediaEncoder;
+ _itemRepository = itemRepository;
_libraryItemsCache = new ConcurrentDictionary<Guid, BaseItem>();
- ConfigurationManager.ConfigurationUpdated += ConfigurationUpdated;
+ _configurationManager.ConfigurationUpdated += ConfigurationUpdated;
RecordConfigurationValues(configurationManager.Configuration);
}
@@ -266,7 +243,7 @@ namespace Emby.Server.Implementations.Library
/// <param name="e">The <see cref="EventArgs" /> instance containing the event data.</param>
private void ConfigurationUpdated(object sender, EventArgs e)
{
- var config = ConfigurationManager.Configuration;
+ var config = _configurationManager.Configuration;
var wizardChanged = config.IsStartupWizardCompleted != _wizardCompleted;
@@ -300,7 +277,7 @@ namespace Emby.Server.Implementations.Library
}
}
- LibraryItemsCache.AddOrUpdate(item.Id, item, delegate { return item; });
+ _libraryItemsCache.AddOrUpdate(item.Id, item, delegate { return item; });
}
public void DeleteItem(BaseItem item, DeleteOptions options)
@@ -431,10 +408,10 @@ namespace Emby.Server.Implementations.Library
item.SetParent(null);
- ItemRepository.DeleteItem(item.Id, CancellationToken.None);
+ _itemRepository.DeleteItem(item.Id);
foreach (var child in children)
{
- ItemRepository.DeleteItem(child.Id, CancellationToken.None);
+ _itemRepository.DeleteItem(child.Id);
}
_libraryItemsCache.TryRemove(item.Id, out BaseItem removed);
@@ -503,15 +480,15 @@ namespace Emby.Server.Implementations.Library
throw new ArgumentNullException(nameof(type));
}
- if (key.StartsWith(ConfigurationManager.ApplicationPaths.ProgramDataPath, StringComparison.Ordinal))
+ if (key.StartsWith(_configurationManager.ApplicationPaths.ProgramDataPath, StringComparison.Ordinal))
{
// Try to normalize paths located underneath program-data in an attempt to make them more portable
- key = key.Substring(ConfigurationManager.ApplicationPaths.ProgramDataPath.Length)
+ key = key.Substring(_configurationManager.ApplicationPaths.ProgramDataPath.Length)
.TrimStart(new[] { '/', '\\' })
.Replace("/", "\\");
}
- if (forceCaseInsensitive || !ConfigurationManager.Configuration.EnableCaseSensitiveItemIds)
+ if (forceCaseInsensitive || !_configurationManager.Configuration.EnableCaseSensitiveItemIds)
{
key = key.ToLowerInvariant();
}
@@ -544,7 +521,7 @@ namespace Emby.Server.Implementations.Library
collectionType = GetContentTypeOverride(fullPath, true);
}
- var args = new ItemResolveArgs(ConfigurationManager.ApplicationPaths, directoryService)
+ var args = new ItemResolveArgs(_configurationManager.ApplicationPaths, directoryService)
{
Parent = parent,
Path = fullPath,
@@ -708,13 +685,13 @@ namespace Emby.Server.Implementations.Library
}
/// <summary>
- /// Creates the root media folder
+ /// Creates the root media folder.
/// </summary>
/// <returns>AggregateFolder.</returns>
- /// <exception cref="InvalidOperationException">Cannot create the root folder until plugins have loaded</exception>
+ /// <exception cref="InvalidOperationException">Cannot create the root folder until plugins have loaded.</exception>
public AggregateFolder CreateRootFolder()
{
- var rootFolderPath = ConfigurationManager.ApplicationPaths.RootFolderPath;
+ var rootFolderPath = _configurationManager.ApplicationPaths.RootFolderPath;
Directory.CreateDirectory(rootFolderPath);
@@ -728,7 +705,7 @@ namespace Emby.Server.Implementations.Library
}
// Add in the plug-in folders
- var path = Path.Combine(ConfigurationManager.ApplicationPaths.DataPath, "playlists");
+ var path = Path.Combine(_configurationManager.ApplicationPaths.DataPath, "playlists");
Directory.CreateDirectory(path);
@@ -780,7 +757,7 @@ namespace Emby.Server.Implementations.Library
{
if (_userRootFolder == null)
{
- var userRootPath = ConfigurationManager.ApplicationPaths.DefaultUserViewsPath;
+ var userRootPath = _configurationManager.ApplicationPaths.DefaultUserViewsPath;
_logger.LogDebug("Creating userRootPath at {path}", userRootPath);
Directory.CreateDirectory(userRootPath);
@@ -822,7 +799,6 @@ namespace Emby.Server.Implementations.Library
{
// 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
-
if (string.IsNullOrEmpty(path))
{
throw new ArgumentNullException(nameof(path));
@@ -842,7 +818,7 @@ namespace Emby.Server.Implementations.Library
}
/// <summary>
- /// Gets a Person
+ /// Gets the person.
/// </summary>
/// <param name="name">The name.</param>
/// <returns>Task{Person}.</returns>
@@ -852,7 +828,7 @@ namespace Emby.Server.Implementations.Library
}
/// <summary>
- /// Gets a Studio
+ /// Gets the studio.
/// </summary>
/// <param name="name">The name.</param>
/// <returns>Task{Studio}.</returns>
@@ -877,7 +853,7 @@ namespace Emby.Server.Implementations.Library
}
/// <summary>
- /// Gets a Genre
+ /// Gets the genre.
/// </summary>
/// <param name="name">The name.</param>
/// <returns>Task{Genre}.</returns>
@@ -887,7 +863,7 @@ namespace Emby.Server.Implementations.Library
}
/// <summary>
- /// Gets the genre.
+ /// Gets the music genre.
/// </summary>
/// <param name="name">The name.</param>
/// <returns>Task{MusicGenre}.</returns>
@@ -897,7 +873,7 @@ namespace Emby.Server.Implementations.Library
}
/// <summary>
- /// Gets a Year
+ /// Gets the year.
/// </summary>
/// <param name="value">The value.</param>
/// <returns>Task{Year}.</returns>
@@ -938,7 +914,6 @@ namespace Emby.Server.Implementations.Library
IncludeItemTypes = new[] { typeof(T).Name },
Name = name,
DtoOptions = options
-
}).Cast<MusicArtist>()
.OrderBy(i => i.IsAccessedByName ? 1 : 0)
.Cast<T>()
@@ -976,7 +951,7 @@ namespace Emby.Server.Implementations.Library
where T : BaseItem, new()
{
var path = getPathFn(name);
- var forceCaseInsensitiveId = ConfigurationManager.Configuration.EnableNormalizedItemByNameIds;
+ var forceCaseInsensitiveId = _configurationManager.Configuration.EnableNormalizedItemByNameIds;
return GetNewItemIdInternal(path, typeof(T), forceCaseInsensitiveId);
}
@@ -990,7 +965,7 @@ namespace Emby.Server.Implementations.Library
public Task ValidatePeople(CancellationToken cancellationToken, IProgress<double> progress)
{
// Ensure the location is available.
- Directory.CreateDirectory(ConfigurationManager.ApplicationPaths.PeoplePath);
+ Directory.CreateDirectory(_configurationManager.ApplicationPaths.PeoplePath);
return new PeopleValidator(this, _logger, _fileSystem).ValidatePeople(cancellationToken, progress);
}
@@ -1027,7 +1002,7 @@ namespace Emby.Server.Implementations.Library
public async Task ValidateMediaLibraryInternal(IProgress<double> progress, CancellationToken cancellationToken)
{
IsScanRunning = true;
- _libraryMonitorFactory().Stop();
+ LibraryMonitor.Stop();
try
{
@@ -1035,7 +1010,7 @@ namespace Emby.Server.Implementations.Library
}
finally
{
- _libraryMonitorFactory().Start();
+ LibraryMonitor.Start();
IsScanRunning = false;
}
}
@@ -1074,9 +1049,9 @@ namespace Emby.Server.Implementations.Library
var innerProgress = new ActionableProgress<double>();
- innerProgress.RegisterAction(pct => progress.Report(pct * .96));
+ innerProgress.RegisterAction(pct => progress.Report(pct * 0.96));
- // Now validate the entire media library
+ // Validate the entire media library
await RootFolder.ValidateChildren(innerProgress, cancellationToken, new MetadataRefreshOptions(new DirectoryService(_fileSystem)), recursive: true).ConfigureAwait(false);
progress.Report(96);
@@ -1085,7 +1060,6 @@ namespace Emby.Server.Implementations.Library
innerProgress.RegisterAction(pct => progress.Report(96 + (pct * .04)));
- // Run post-scan tasks
await RunPostScanTasks(innerProgress, cancellationToken).ConfigureAwait(false);
progress.Report(100);
@@ -1136,7 +1110,7 @@ namespace Emby.Server.Implementations.Library
}
catch (Exception ex)
{
- _logger.LogError(ex, "Error running postscan task");
+ _logger.LogError(ex, "Error running post-scan task");
}
numComplete++;
@@ -1145,7 +1119,7 @@ namespace Emby.Server.Implementations.Library
progress.Report(percent * 100);
}
- ItemRepository.UpdateInheritedValues(cancellationToken);
+ _itemRepository.UpdateInheritedValues(cancellationToken);
progress.Report(100);
}
@@ -1165,11 +1139,10 @@ namespace Emby.Server.Implementations.Library
var topLibraryFolders = GetUserRootFolder().Children.ToList();
_logger.LogDebug("Getting refreshQueue");
- var refreshQueue = includeRefreshState ? _providerManagerFactory().GetRefreshQueue() : null;
+ var refreshQueue = includeRefreshState ? ProviderManager.GetRefreshQueue() : null;
- return _fileSystem.GetDirectoryPaths(ConfigurationManager.ApplicationPaths.DefaultUserViewsPath)
+ return _fileSystem.GetDirectoryPaths(_configurationManager.ApplicationPaths.DefaultUserViewsPath)
.Select(dir => GetVirtualFolderInfo(dir, topLibraryFolders, refreshQueue))
- .OrderBy(i => i.Name)
.ToList();
}
@@ -1243,7 +1216,7 @@ namespace Emby.Server.Implementations.Library
throw new ArgumentException("Guid can't be empty", nameof(id));
}
- if (LibraryItemsCache.TryGetValue(id, out BaseItem item))
+ if (_libraryItemsCache.TryGetValue(id, out BaseItem item))
{
return item;
}
@@ -1274,7 +1247,7 @@ namespace Emby.Server.Implementations.Library
AddUserToQuery(query, query.User, allowExternalContent);
}
- return ItemRepository.GetItemList(query);
+ return _itemRepository.GetItemList(query);
}
public List<BaseItem> GetItemList(InternalItemsQuery query)
@@ -1298,7 +1271,7 @@ namespace Emby.Server.Implementations.Library
AddUserToQuery(query, query.User);
}
- return ItemRepository.GetCount(query);
+ return _itemRepository.GetCount(query);
}
public List<BaseItem> GetItemList(InternalItemsQuery query, List<BaseItem> parents)
@@ -1313,7 +1286,7 @@ namespace Emby.Server.Implementations.Library
}
}
- return ItemRepository.GetItemList(query);
+ return _itemRepository.GetItemList(query);
}
public QueryResult<BaseItem> QueryItems(InternalItemsQuery query)
@@ -1325,12 +1298,12 @@ namespace Emby.Server.Implementations.Library
if (query.EnableTotalRecordCount)
{
- return ItemRepository.GetItems(query);
+ return _itemRepository.GetItems(query);
}
return new QueryResult<BaseItem>
{
- Items = ItemRepository.GetItemList(query).ToArray()
+ Items = _itemRepository.GetItemList(query).ToArray()
};
}
@@ -1341,7 +1314,7 @@ namespace Emby.Server.Implementations.Library
AddUserToQuery(query, query.User);
}
- return ItemRepository.GetItemIdsList(query);
+ return _itemRepository.GetItemIdsList(query);
}
public QueryResult<(BaseItem, ItemCounts)> GetStudios(InternalItemsQuery query)
@@ -1352,7 +1325,7 @@ namespace Emby.Server.Implementations.Library
}
SetTopParentOrAncestorIds(query);
- return ItemRepository.GetStudios(query);
+ return _itemRepository.GetStudios(query);
}
public QueryResult<(BaseItem, ItemCounts)> GetGenres(InternalItemsQuery query)
@@ -1363,7 +1336,7 @@ namespace Emby.Server.Implementations.Library
}
SetTopParentOrAncestorIds(query);
- return ItemRepository.GetGenres(query);
+ return _itemRepository.GetGenres(query);
}
public QueryResult<(BaseItem, ItemCounts)> GetMusicGenres(InternalItemsQuery query)
@@ -1374,7 +1347,7 @@ namespace Emby.Server.Implementations.Library
}
SetTopParentOrAncestorIds(query);
- return ItemRepository.GetMusicGenres(query);
+ return _itemRepository.GetMusicGenres(query);
}
public QueryResult<(BaseItem, ItemCounts)> GetAllArtists(InternalItemsQuery query)
@@ -1385,7 +1358,7 @@ namespace Emby.Server.Implementations.Library
}
SetTopParentOrAncestorIds(query);
- return ItemRepository.GetAllArtists(query);
+ return _itemRepository.GetAllArtists(query);
}
public QueryResult<(BaseItem, ItemCounts)> GetArtists(InternalItemsQuery query)
@@ -1396,30 +1369,37 @@ namespace Emby.Server.Implementations.Library
}
SetTopParentOrAncestorIds(query);
- return ItemRepository.GetArtists(query);
+ return _itemRepository.GetArtists(query);
}
private void SetTopParentOrAncestorIds(InternalItemsQuery query)
{
- if (query.AncestorIds.Length == 0)
+ var ancestorIds = query.AncestorIds;
+ int len = ancestorIds.Length;
+ if (len == 0)
{
return;
}
- var parents = query.AncestorIds.Select(i => GetItemById(i)).ToList();
-
- if (parents.All(i => i is ICollectionFolder || i is UserView))
+ var parents = new BaseItem[len];
+ for (int i = 0; i < len; i++)
{
- // Optimize by querying against top level views
- query.TopParentIds = parents.SelectMany(i => GetTopParentIdsForQuery(i, query.User)).ToArray();
- query.AncestorIds = Array.Empty<Guid>();
-
- // Prevent searching in all libraries due to empty filter
- if (query.TopParentIds.Length == 0)
+ parents[i] = GetItemById(ancestorIds[i]);
+ if (!(parents[i] is ICollectionFolder || parents[i] is UserView))
{
- query.TopParentIds = new[] { Guid.NewGuid() };
+ return;
}
}
+
+ // Optimize by querying against top level views
+ query.TopParentIds = parents.SelectMany(i => GetTopParentIdsForQuery(i, query.User)).ToArray();
+ query.AncestorIds = Array.Empty<Guid>();
+
+ // Prevent searching in all libraries due to empty filter
+ if (query.TopParentIds.Length == 0)
+ {
+ query.TopParentIds = new[] { Guid.NewGuid() };
+ }
}
public QueryResult<(BaseItem, ItemCounts)> GetAlbumArtists(InternalItemsQuery query)
@@ -1430,7 +1410,7 @@ namespace Emby.Server.Implementations.Library
}
SetTopParentOrAncestorIds(query);
- return ItemRepository.GetAlbumArtists(query);
+ return _itemRepository.GetAlbumArtists(query);
}
public QueryResult<BaseItem> GetItemsResult(InternalItemsQuery query)
@@ -1451,10 +1431,10 @@ namespace Emby.Server.Implementations.Library
if (query.EnableTotalRecordCount)
{
- return ItemRepository.GetItems(query);
+ return _itemRepository.GetItems(query);
}
- var list = ItemRepository.GetItemList(query);
+ var list = _itemRepository.GetItemList(query);
return new QueryResult<BaseItem>
{
@@ -1500,7 +1480,7 @@ namespace Emby.Server.Implementations.Library
string.IsNullOrEmpty(query.SeriesPresentationUniqueKey) &&
query.ItemIds.Length == 0)
{
- var userViews = _userviewManager().GetUserViews(new UserViewQuery
+ var userViews = UserViewManager.GetUserViews(new UserViewQuery
{
UserId = user.Id,
IncludeHidden = true,
@@ -1580,7 +1560,7 @@ namespace Emby.Server.Implementations.Library
public async Task<IEnumerable<Video>> GetIntros(BaseItem item, User user)
{
var tasks = IntroProviders
- .OrderBy(i => i.GetType().Name.IndexOf("Default", StringComparison.OrdinalIgnoreCase) == -1 ? 0 : 1)
+ .OrderBy(i => i.GetType().Name.Contains("Default", StringComparison.OrdinalIgnoreCase) ? 1 : 0)
.Take(1)
.Select(i => GetIntros(i, item, user));
@@ -1800,7 +1780,7 @@ namespace Emby.Server.Implementations.Library
// Don't iterate multiple times
var itemsList = items.ToList();
- ItemRepository.SaveItems(itemsList, cancellationToken);
+ _itemRepository.SaveItems(itemsList, cancellationToken);
foreach (var item in itemsList)
{
@@ -1837,7 +1817,7 @@ namespace Emby.Server.Implementations.Library
public void UpdateImages(BaseItem item)
{
- ItemRepository.SaveImages(item);
+ _itemRepository.SaveImages(item);
RegisterItem(item);
}
@@ -1854,7 +1834,7 @@ namespace Emby.Server.Implementations.Library
{
if (item.IsFileProtocol)
{
- _providerManagerFactory().SaveMetadata(item, updateReason);
+ ProviderManager.SaveMetadata(item, updateReason);
}
item.DateLastSaved = DateTime.UtcNow;
@@ -1862,7 +1842,7 @@ namespace Emby.Server.Implementations.Library
RegisterItem(item);
}
- ItemRepository.SaveItems(itemsList, cancellationToken);
+ _itemRepository.SaveItems(itemsList, cancellationToken);
if (ItemUpdated != null)
{
@@ -1938,7 +1918,7 @@ namespace Emby.Server.Implementations.Library
/// <returns>BaseItem.</returns>
public BaseItem RetrieveItem(Guid id)
{
- return ItemRepository.RetrieveItem(id);
+ return _itemRepository.RetrieveItem(id);
}
public List<Folder> GetCollectionFolders(BaseItem item)
@@ -2057,7 +2037,7 @@ namespace Emby.Server.Implementations.Library
private string GetContentTypeOverride(string path, bool inherit)
{
- var nameValuePair = ConfigurationManager.Configuration.ContentTypes
+ var nameValuePair = _configurationManager.Configuration.ContentTypes
.FirstOrDefault(i => _fileSystem.AreEqual(i.Name, path)
|| (inherit && !string.IsNullOrEmpty(i.Name)
&& _fileSystem.ContainsSubPath(i.Name, path)));
@@ -2106,7 +2086,7 @@ namespace Emby.Server.Implementations.Library
string sortName)
{
var path = Path.Combine(
- ConfigurationManager.ApplicationPaths.InternalMetadataPath,
+ _configurationManager.ApplicationPaths.InternalMetadataPath,
"views",
_fileSystem.GetValidFilename(viewType));
@@ -2138,7 +2118,7 @@ namespace Emby.Server.Implementations.Library
if (refresh)
{
item.UpdateToRepository(ItemUpdateType.MetadataImport, CancellationToken.None);
- _providerManagerFactory().QueueRefresh(item.Id, new MetadataRefreshOptions(new DirectoryService(_fileSystem)), RefreshPriority.Normal);
+ ProviderManager.QueueRefresh(item.Id, new MetadataRefreshOptions(new DirectoryService(_fileSystem)), RefreshPriority.Normal);
}
return item;
@@ -2156,7 +2136,7 @@ namespace Emby.Server.Implementations.Library
var id = GetNewItemId(idValues, typeof(UserView));
- var path = Path.Combine(ConfigurationManager.ApplicationPaths.InternalMetadataPath, "views", id.ToString("N", CultureInfo.InvariantCulture));
+ var path = Path.Combine(_configurationManager.ApplicationPaths.InternalMetadataPath, "views", id.ToString("N", CultureInfo.InvariantCulture));
var item = GetItemById(id) as UserView;
@@ -2193,7 +2173,7 @@ namespace Emby.Server.Implementations.Library
if (refresh)
{
- _providerManagerFactory().QueueRefresh(
+ ProviderManager.QueueRefresh(
item.Id,
new MetadataRefreshOptions(new DirectoryService(_fileSystem))
{
@@ -2260,7 +2240,7 @@ namespace Emby.Server.Implementations.Library
if (refresh)
{
- _providerManagerFactory().QueueRefresh(
+ ProviderManager.QueueRefresh(
item.Id,
new MetadataRefreshOptions(new DirectoryService(_fileSystem))
{
@@ -2294,7 +2274,7 @@ namespace Emby.Server.Implementations.Library
var id = GetNewItemId(idValues, typeof(UserView));
- var path = Path.Combine(ConfigurationManager.ApplicationPaths.InternalMetadataPath, "views", id.ToString("N", CultureInfo.InvariantCulture));
+ var path = Path.Combine(_configurationManager.ApplicationPaths.InternalMetadataPath, "views", id.ToString("N", CultureInfo.InvariantCulture));
var item = GetItemById(id) as UserView;
@@ -2337,7 +2317,7 @@ namespace Emby.Server.Implementations.Library
if (refresh)
{
- _providerManagerFactory().QueueRefresh(
+ ProviderManager.QueueRefresh(
item.Id,
new MetadataRefreshOptions(new DirectoryService(_fileSystem))
{
@@ -2355,36 +2335,25 @@ namespace Emby.Server.Implementations.Library
string videoPath,
string[] files)
{
- new SubtitleResolver(BaseItem.LocalizationManager, _fileSystem).AddExternalSubtitleStreams(streams, videoPath, streams.Count, files);
+ new SubtitleResolver(BaseItem.LocalizationManager).AddExternalSubtitleStreams(streams, videoPath, streams.Count, files);
}
- public bool IsVideoFile(string path, LibraryOptions libraryOptions)
+ /// <inheritdoc />
+ public bool IsVideoFile(string path)
{
var resolver = new VideoResolver(GetNamingOptions());
return resolver.IsVideoFile(path);
}
- public bool IsVideoFile(string path)
- {
- return IsVideoFile(path, new LibraryOptions());
- }
-
- public bool IsAudioFile(string path, LibraryOptions libraryOptions)
- {
- var parser = new AudioFileParser(GetNamingOptions());
- return parser.IsAudioFile(path);
- }
-
+ /// <inheritdoc />
public bool IsAudioFile(string path)
- {
- return IsAudioFile(path, new LibraryOptions());
- }
+ => AudioFileParser.IsAudioFile(path, GetNamingOptions());
+ /// <inheritdoc />
public int? GetSeasonNumberFromPath(string path)
- {
- return new SeasonPathParser().Parse(path, true, true).SeasonNumber;
- }
+ => SeasonPathParser.Parse(path, true, true).SeasonNumber;
+ /// <inheritdoc />
public bool FillMissingEpisodeNumbersFromPath(Episode episode, bool forceRefresh)
{
var series = episode.Series;
@@ -2408,6 +2377,38 @@ namespace Emby.Server.Implementations.Library
episodeInfo = new Naming.TV.EpisodeInfo();
}
+ try
+ {
+ var libraryOptions = GetLibraryOptions(episode);
+ if (libraryOptions.EnableEmbeddedEpisodeInfos && string.Equals(episodeInfo.Container, "mp4", StringComparison.OrdinalIgnoreCase))
+ {
+ // Read from metadata
+ var mediaInfo = _mediaEncoder.GetMediaInfo(new MediaInfoRequest
+ {
+ MediaSource = episode.GetMediaSources(false)[0],
+ MediaType = DlnaProfileType.Video
+ }, CancellationToken.None).GetAwaiter().GetResult();
+ if (mediaInfo.ParentIndexNumber > 0)
+ {
+ episodeInfo.SeasonNumber = mediaInfo.ParentIndexNumber;
+ }
+
+ if (mediaInfo.IndexNumber > 0)
+ {
+ episodeInfo.EpisodeNumber = mediaInfo.IndexNumber;
+ }
+
+ if (!string.IsNullOrEmpty(mediaInfo.ShowName))
+ {
+ episodeInfo.SeriesName = mediaInfo.ShowName;
+ }
+ }
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, "Error reading the episode informations with ffprobe. Episode: {EpisodeInfo}", episodeInfo.Path);
+ }
+
var changed = false;
if (episodeInfo.IsByDate)
@@ -2509,20 +2510,10 @@ namespace Emby.Server.Implementations.Library
public NamingOptions GetNamingOptions()
{
- return GetNamingOptionsInternal();
- }
-
- private NamingOptions _namingOptions;
- private string[] _videoFileExtensions;
-
- private NamingOptions GetNamingOptionsInternal()
- {
if (_namingOptions == null)
{
- var options = new NamingOptions();
-
- _namingOptions = options;
- _videoFileExtensions = _namingOptions.VideoFileExtensions.ToArray();
+ _namingOptions = new NamingOptions();
+ _videoFileExtensions = _namingOptions.VideoFileExtensions;
}
return _namingOptions;
@@ -2533,11 +2524,10 @@ namespace Emby.Server.Implementations.Library
var resolver = new VideoResolver(GetNamingOptions());
var result = resolver.CleanDateTime(name);
- var cleanName = resolver.CleanString(result.Name);
return new ItemLookupInfo
{
- Name = cleanName.Name,
+ Name = resolver.TryCleanString(result.Name, out var newName) ? newName.ToString() : result.Name,
Year = result.Year
};
}
@@ -2590,14 +2580,12 @@ namespace Emby.Server.Implementations.Library
}).OrderBy(i => i.Path);
}
- private static readonly string[] ExtrasSubfolderNames = new[] { "extras", "specials", "shorts", "scenes", "featurettes", "behind the scenes", "deleted scenes", "interviews" };
-
public IEnumerable<Video> FindExtras(BaseItem owner, List<FileSystemMetadata> fileSystemChildren, IDirectoryService directoryService)
{
var namingOptions = GetNamingOptions();
var files = owner.IsInMixedFolder ? new List<FileSystemMetadata>() : fileSystemChildren.Where(i => i.IsDirectory)
- .Where(i => ExtrasSubfolderNames.Contains(i.Name ?? string.Empty, StringComparer.OrdinalIgnoreCase))
+ .Where(i => BaseItem.AllExtrasTypesFolderNames.Contains(i.Name ?? string.Empty, StringComparer.OrdinalIgnoreCase))
.SelectMany(i => _fileSystem.GetFiles(i.FullName, _videoFileExtensions, false, false))
.ToList();
@@ -2658,8 +2646,8 @@ namespace Emby.Server.Implementations.Library
}
}
- var metadataPath = ConfigurationManager.Configuration.MetadataPath;
- var metadataNetworkPath = ConfigurationManager.Configuration.MetadataNetworkPath;
+ var metadataPath = _configurationManager.Configuration.MetadataPath;
+ var metadataNetworkPath = _configurationManager.Configuration.MetadataNetworkPath;
if (!string.IsNullOrWhiteSpace(metadataPath) && !string.IsNullOrWhiteSpace(metadataNetworkPath))
{
@@ -2670,7 +2658,7 @@ namespace Emby.Server.Implementations.Library
}
}
- foreach (var map in ConfigurationManager.Configuration.PathSubstitutions)
+ foreach (var map in _configurationManager.Configuration.PathSubstitutions)
{
if (!string.IsNullOrWhiteSpace(map.From))
{
@@ -2739,7 +2727,7 @@ namespace Emby.Server.Implementations.Library
public List<PersonInfo> GetPeople(InternalPeopleQuery query)
{
- return ItemRepository.GetPeople(query);
+ return _itemRepository.GetPeople(query);
}
public List<PersonInfo> GetPeople(BaseItem item)
@@ -2762,7 +2750,7 @@ namespace Emby.Server.Implementations.Library
public List<Person> GetPeopleItems(InternalPeopleQuery query)
{
- return ItemRepository.GetPeopleNames(query).Select(i =>
+ return _itemRepository.GetPeopleNames(query).Select(i =>
{
try
{
@@ -2779,7 +2767,7 @@ namespace Emby.Server.Implementations.Library
public List<string> GetPeopleNames(InternalPeopleQuery query)
{
- return ItemRepository.GetPeopleNames(query);
+ return _itemRepository.GetPeopleNames(query);
}
public void UpdatePeople(BaseItem item, List<PersonInfo> people)
@@ -2789,7 +2777,7 @@ namespace Emby.Server.Implementations.Library
return;
}
- ItemRepository.UpdatePeople(item.Id, people);
+ _itemRepository.UpdatePeople(item.Id, people);
}
public async Task<ItemImageInfo> ConvertImageToLocal(BaseItem item, ItemImageInfo image, int imageIndex)
@@ -2800,7 +2788,7 @@ namespace Emby.Server.Implementations.Library
{
_logger.LogDebug("ConvertImageToLocal item {0} - image url: {1}", item.Id, url);
- await _providerManagerFactory().SaveImage(item, url, image.Type, imageIndex, CancellationToken.None).ConfigureAwait(false);
+ await ProviderManager.SaveImage(item, url, image.Type, imageIndex, CancellationToken.None).ConfigureAwait(false);
item.UpdateToRepository(ItemUpdateType.ImageUpdate, CancellationToken.None);
@@ -2833,7 +2821,7 @@ namespace Emby.Server.Implementations.Library
name = _fileSystem.GetValidFilename(name);
- var rootFolderPath = ConfigurationManager.ApplicationPaths.DefaultUserViewsPath;
+ var rootFolderPath = _configurationManager.ApplicationPaths.DefaultUserViewsPath;
var virtualFolderPath = Path.Combine(rootFolderPath, name);
while (Directory.Exists(virtualFolderPath))
@@ -2852,7 +2840,7 @@ namespace Emby.Server.Implementations.Library
}
}
- _libraryMonitorFactory().Stop();
+ LibraryMonitor.Stop();
try
{
@@ -2887,7 +2875,7 @@ namespace Emby.Server.Implementations.Library
{
// Need to add a delay here or directory watchers may still pick up the changes
await Task.Delay(1000).ConfigureAwait(false);
- _libraryMonitorFactory().Start();
+ LibraryMonitor.Start();
}
}
}
@@ -2947,7 +2935,7 @@ namespace Emby.Server.Implementations.Library
throw new FileNotFoundException("The network path does not exist.");
}
- var rootFolderPath = ConfigurationManager.ApplicationPaths.DefaultUserViewsPath;
+ var rootFolderPath = _configurationManager.ApplicationPaths.DefaultUserViewsPath;
var virtualFolderPath = Path.Combine(rootFolderPath, virtualFolderName);
var shortcutFilename = Path.GetFileNameWithoutExtension(path);
@@ -2990,7 +2978,7 @@ namespace Emby.Server.Implementations.Library
throw new FileNotFoundException("The network path does not exist.");
}
- var rootFolderPath = ConfigurationManager.ApplicationPaths.DefaultUserViewsPath;
+ var rootFolderPath = _configurationManager.ApplicationPaths.DefaultUserViewsPath;
var virtualFolderPath = Path.Combine(rootFolderPath, virtualFolderName);
var libraryOptions = CollectionFolder.GetLibraryOptions(virtualFolderPath);
@@ -3043,7 +3031,7 @@ namespace Emby.Server.Implementations.Library
throw new ArgumentNullException(nameof(name));
}
- var rootFolderPath = ConfigurationManager.ApplicationPaths.DefaultUserViewsPath;
+ var rootFolderPath = _configurationManager.ApplicationPaths.DefaultUserViewsPath;
var path = Path.Combine(rootFolderPath, name);
@@ -3052,7 +3040,7 @@ namespace Emby.Server.Implementations.Library
throw new FileNotFoundException("The media folder does not exist");
}
- _libraryMonitorFactory().Stop();
+ LibraryMonitor.Stop();
try
{
@@ -3072,7 +3060,7 @@ namespace Emby.Server.Implementations.Library
{
// Need to add a delay here or directory watchers may still pick up the changes
await Task.Delay(1000).ConfigureAwait(false);
- _libraryMonitorFactory().Start();
+ LibraryMonitor.Start();
}
}
}
@@ -3086,7 +3074,7 @@ namespace Emby.Server.Implementations.Library
var removeList = new List<NameValuePair>();
- foreach (var contentType in ConfigurationManager.Configuration.ContentTypes)
+ foreach (var contentType in _configurationManager.Configuration.ContentTypes)
{
if (string.IsNullOrWhiteSpace(contentType.Name))
{
@@ -3101,11 +3089,11 @@ namespace Emby.Server.Implementations.Library
if (removeList.Count > 0)
{
- ConfigurationManager.Configuration.ContentTypes = ConfigurationManager.Configuration.ContentTypes
+ _configurationManager.Configuration.ContentTypes = _configurationManager.Configuration.ContentTypes
.Except(removeList)
- .ToArray();
+ .ToArray();
- ConfigurationManager.SaveConfiguration();
+ _configurationManager.SaveConfiguration();
}
}
@@ -3116,7 +3104,7 @@ namespace Emby.Server.Implementations.Library
throw new ArgumentNullException(nameof(mediaPath));
}
- var rootFolderPath = ConfigurationManager.ApplicationPaths.DefaultUserViewsPath;
+ var rootFolderPath = _configurationManager.ApplicationPaths.DefaultUserViewsPath;
var virtualFolderPath = Path.Combine(rootFolderPath, virtualFolderName);
if (!Directory.Exists(virtualFolderPath))
diff --git a/Emby.Server.Implementations/Library/LiveStreamHelper.cs b/Emby.Server.Implementations/Library/LiveStreamHelper.cs
index f28f4a538..ed7d8aa40 100644
--- a/Emby.Server.Implementations/Library/LiveStreamHelper.cs
+++ b/Emby.Server.Implementations/Library/LiveStreamHelper.cs
@@ -1,5 +1,4 @@
#pragma warning disable CS1591
-#pragma warning disable SA1600
using System;
using System.Collections.Generic;
diff --git a/Emby.Server.Implementations/Library/MediaSourceManager.cs b/Emby.Server.Implementations/Library/MediaSourceManager.cs
index e310065b2..01fe98f3a 100644
--- a/Emby.Server.Implementations/Library/MediaSourceManager.cs
+++ b/Emby.Server.Implementations/Library/MediaSourceManager.cs
@@ -1,5 +1,4 @@
#pragma warning disable CS1591
-#pragma warning disable SA1600
using System;
using System.Collections.Generic;
@@ -34,13 +33,13 @@ namespace Emby.Server.Implementations.Library
private readonly ILibraryManager _libraryManager;
private readonly IJsonSerializer _jsonSerializer;
private readonly IFileSystem _fileSystem;
-
- private IMediaSourceProvider[] _providers;
private readonly ILogger _logger;
private readonly IUserDataManager _userDataManager;
- private readonly Func<IMediaEncoder> _mediaEncoder;
- private ILocalizationManager _localizationManager;
- private IApplicationPaths _appPaths;
+ private readonly IMediaEncoder _mediaEncoder;
+ private readonly ILocalizationManager _localizationManager;
+ private readonly IApplicationPaths _appPaths;
+
+ private IMediaSourceProvider[] _providers;
public MediaSourceManager(
IItemRepository itemRepo,
@@ -48,16 +47,16 @@ namespace Emby.Server.Implementations.Library
ILocalizationManager localizationManager,
IUserManager userManager,
ILibraryManager libraryManager,
- ILoggerFactory loggerFactory,
+ ILogger<MediaSourceManager> logger,
IJsonSerializer jsonSerializer,
IFileSystem fileSystem,
IUserDataManager userDataManager,
- Func<IMediaEncoder> mediaEncoder)
+ IMediaEncoder mediaEncoder)
{
_itemRepo = itemRepo;
_userManager = userManager;
_libraryManager = libraryManager;
- _logger = loggerFactory.CreateLogger(nameof(MediaSourceManager));
+ _logger = logger;
_jsonSerializer = jsonSerializer;
_fileSystem = fileSystem;
_userDataManager = userDataManager;
@@ -497,7 +496,7 @@ namespace Emby.Server.Implementations.Library
// hack - these two values were taken from LiveTVMediaSourceProvider
string cacheKey = request.OpenToken;
- await new LiveStreamHelper(_mediaEncoder(), _logger, _jsonSerializer, _appPaths)
+ await new LiveStreamHelper(_mediaEncoder, _logger, _jsonSerializer, _appPaths)
.AddMediaInfoWithProbe(mediaSource, isAudio, cacheKey, true, cancellationToken)
.ConfigureAwait(false);
}
@@ -622,7 +621,7 @@ namespace Emby.Server.Implementations.Library
if (liveStreamInfo is IDirectStreamProvider)
{
- var info = await _mediaEncoder().GetMediaInfo(new MediaInfoRequest
+ var info = await _mediaEncoder.GetMediaInfo(new MediaInfoRequest
{
MediaSource = mediaSource,
ExtractChapters = false,
@@ -675,7 +674,7 @@ namespace Emby.Server.Implementations.Library
mediaSource.AnalyzeDurationMs = 3000;
}
- mediaInfo = await _mediaEncoder().GetMediaInfo(new MediaInfoRequest
+ mediaInfo = await _mediaEncoder.GetMediaInfo(new MediaInfoRequest
{
MediaSource = mediaSource,
MediaType = isAudio ? DlnaProfileType.Audio : DlnaProfileType.Video,
diff --git a/Emby.Server.Implementations/Library/MediaStreamSelector.cs b/Emby.Server.Implementations/Library/MediaStreamSelector.cs
index 1652ad974..e27145a1d 100644
--- a/Emby.Server.Implementations/Library/MediaStreamSelector.cs
+++ b/Emby.Server.Implementations/Library/MediaStreamSelector.cs
@@ -1,5 +1,4 @@
#pragma warning disable CS1591
-#pragma warning disable SA1600
using System;
using System.Collections.Generic;
@@ -36,7 +35,8 @@ namespace Emby.Server.Implementations.Library
return null;
}
- public static int? GetDefaultSubtitleStreamIndex(List<MediaStream> streams,
+ public static int? GetDefaultSubtitleStreamIndex(
+ List<MediaStream> streams,
string[] preferredLanguages,
SubtitlePlaybackMode mode,
string audioTrackLanguage)
@@ -116,7 +116,8 @@ namespace Emby.Server.Implementations.Library
.ThenBy(i => i.Index);
}
- public static void SetSubtitleStreamScores(List<MediaStream> streams,
+ public static void SetSubtitleStreamScores(
+ List<MediaStream> streams,
string[] preferredLanguages,
SubtitlePlaybackMode mode,
string audioTrackLanguage)
diff --git a/Emby.Server.Implementations/Library/MusicManager.cs b/Emby.Server.Implementations/Library/MusicManager.cs
index 29af6670b..1ec578371 100644
--- a/Emby.Server.Implementations/Library/MusicManager.cs
+++ b/Emby.Server.Implementations/Library/MusicManager.cs
@@ -1,5 +1,4 @@
#pragma warning disable CS1591
-#pragma warning disable SA1600
using System;
using System.Collections.Generic;
diff --git a/Emby.Server.Implementations/Library/PathExtensions.cs b/Emby.Server.Implementations/Library/PathExtensions.cs
index 4fdf73b77..06ff3e611 100644
--- a/Emby.Server.Implementations/Library/PathExtensions.cs
+++ b/Emby.Server.Implementations/Library/PathExtensions.cs
@@ -1,3 +1,5 @@
+#nullable enable
+
using System;
using System.Text.RegularExpressions;
@@ -12,24 +14,24 @@ namespace Emby.Server.Implementations.Library
/// Gets the attribute value.
/// </summary>
/// <param name="str">The STR.</param>
- /// <param name="attrib">The attrib.</param>
+ /// <param name="attribute">The attrib.</param>
/// <returns>System.String.</returns>
- /// <exception cref="ArgumentNullException">attrib</exception>
- public static string GetAttributeValue(this string str, string attrib)
+ /// <exception cref="ArgumentException"><paramref name="str" /> or <paramref name="attribute" /> is empty.</exception>
+ public static string? GetAttributeValue(this string str, string attribute)
{
- if (string.IsNullOrEmpty(str))
+ if (str.Length == 0)
{
- throw new ArgumentNullException(nameof(str));
+ throw new ArgumentException("String can't be empty.", nameof(str));
}
- if (string.IsNullOrEmpty(attrib))
+ if (attribute.Length == 0)
{
- throw new ArgumentNullException(nameof(attrib));
+ throw new ArgumentException("String can't be empty.", nameof(attribute));
}
- string srch = "[" + attrib + "=";
+ string srch = "[" + attribute + "=";
int start = str.IndexOf(srch, StringComparison.OrdinalIgnoreCase);
- if (start > -1)
+ if (start != -1)
{
start += srch.Length;
int end = str.IndexOf(']', start);
@@ -37,9 +39,9 @@ namespace Emby.Server.Implementations.Library
}
// for imdbid we also accept pattern matching
- if (string.Equals(attrib, "imdbid", StringComparison.OrdinalIgnoreCase))
+ if (string.Equals(attribute, "imdbid", StringComparison.OrdinalIgnoreCase))
{
- var m = Regex.Match(str, "tt\\d{7}", RegexOptions.IgnoreCase);
+ var m = Regex.Match(str, "tt([0-9]{7,8})", RegexOptions.IgnoreCase);
return m.Success ? m.Value : null;
}
diff --git a/Emby.Server.Implementations/Library/ResolverHelper.cs b/Emby.Server.Implementations/Library/ResolverHelper.cs
index 96d1bff92..7ca15b4e5 100644
--- a/Emby.Server.Implementations/Library/ResolverHelper.cs
+++ b/Emby.Server.Implementations/Library/ResolverHelper.cs
@@ -9,7 +9,7 @@ using MediaBrowser.Model.IO;
namespace Emby.Server.Implementations.Library
{
/// <summary>
- /// Class ResolverHelper
+ /// Class ResolverHelper.
/// </summary>
public static class ResolverHelper
{
@@ -118,10 +118,12 @@ namespace Emby.Server.Implementations.Library
{
throw new ArgumentNullException(nameof(fileSystem));
}
+
if (item == null)
{
throw new ArgumentNullException(nameof(item));
}
+
if (args == null)
{
throw new ArgumentNullException(nameof(args));
diff --git a/Emby.Server.Implementations/Library/Resolvers/Audio/AudioResolver.cs b/Emby.Server.Implementations/Library/Resolvers/Audio/AudioResolver.cs
index 7e3b27a12..fefc8e789 100644
--- a/Emby.Server.Implementations/Library/Resolvers/Audio/AudioResolver.cs
+++ b/Emby.Server.Implementations/Library/Resolvers/Audio/AudioResolver.cs
@@ -1,5 +1,4 @@
#pragma warning disable CS1591
-#pragma warning disable SA1600
using System;
using System.Collections.Generic;
@@ -73,7 +72,6 @@ namespace Emby.Server.Implementations.Library.Resolvers.Audio
{
// Return audio if the path is a file and has a matching extension
- var libraryOptions = args.GetLibraryOptions();
var collectionType = args.GetCollectionType();
var isBooksCollectionType = string.Equals(collectionType, CollectionType.Books, StringComparison.OrdinalIgnoreCase);
@@ -92,7 +90,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Audio
return FindAudio<AudioBook>(args, args.Path, args.Parent, files, args.DirectoryService, collectionType, false);
}
- if (LibraryManager.IsAudioFile(args.Path, libraryOptions))
+ if (LibraryManager.IsAudioFile(args.Path))
{
var extension = Path.GetExtension(args.Path);
@@ -105,7 +103,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Audio
var isMixedCollectionType = string.IsNullOrEmpty(collectionType);
// For conflicting extensions, give priority to videos
- if (isMixedCollectionType && LibraryManager.IsVideoFile(args.Path, libraryOptions))
+ if (isMixedCollectionType && LibraryManager.IsVideoFile(args.Path))
{
return null;
}
@@ -121,7 +119,6 @@ namespace Emby.Server.Implementations.Library.Resolvers.Audio
{
item = new MediaBrowser.Controller.Entities.Audio.Audio();
}
-
else if (isBooksCollectionType)
{
item = new AudioBook();
diff --git a/Emby.Server.Implementations/Library/Resolvers/Audio/MusicAlbumResolver.cs b/Emby.Server.Implementations/Library/Resolvers/Audio/MusicAlbumResolver.cs
index 4a2d210d5..85b1b6e32 100644
--- a/Emby.Server.Implementations/Library/Resolvers/Audio/MusicAlbumResolver.cs
+++ b/Emby.Server.Implementations/Library/Resolvers/Audio/MusicAlbumResolver.cs
@@ -5,7 +5,6 @@ using MediaBrowser.Controller.Entities.Audio;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Providers;
using MediaBrowser.Controller.Resolvers;
-using MediaBrowser.Model.Configuration;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.IO;
using Microsoft.Extensions.Logging;
@@ -76,15 +75,15 @@ namespace Emby.Server.Implementations.Library.Resolvers.Audio
}
/// <summary>
- /// Determine if the supplied file data points to a music album
+ /// Determine if the supplied file data points to a music album.
/// </summary>
- public bool IsMusicAlbum(string path, IDirectoryService directoryService, LibraryOptions libraryOptions)
+ public bool IsMusicAlbum(string path, IDirectoryService directoryService)
{
- return ContainsMusic(directoryService.GetFileSystemEntries(path), true, directoryService, _logger, _fileSystem, libraryOptions, _libraryManager);
+ return ContainsMusic(directoryService.GetFileSystemEntries(path), true, directoryService, _logger, _fileSystem, _libraryManager);
}
/// <summary>
- /// Determine if the supplied resolve args should be considered a music album
+ /// Determine if the supplied resolve args should be considered a music album.
/// </summary>
/// <param name="args">The args.</param>
/// <returns><c>true</c> if [is music album] [the specified args]; otherwise, <c>false</c>.</returns>
@@ -94,7 +93,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Audio
if (args.IsDirectory)
{
// if (args.Parent is MusicArtist) return true; //saves us from testing children twice
- if (ContainsMusic(args.FileSystemChildren, true, args.DirectoryService, _logger, _fileSystem, args.GetLibraryOptions(), _libraryManager))
+ if (ContainsMusic(args.FileSystemChildren, true, args.DirectoryService, _logger, _fileSystem, _libraryManager))
{
return true;
}
@@ -104,7 +103,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Audio
}
/// <summary>
- /// Determine if the supplied list contains what we should consider music
+ /// Determine if the supplied list contains what we should consider music.
/// </summary>
private bool ContainsMusic(
IEnumerable<FileSystemMetadata> list,
@@ -112,12 +111,13 @@ namespace Emby.Server.Implementations.Library.Resolvers.Audio
IDirectoryService directoryService,
ILogger logger,
IFileSystem fileSystem,
- LibraryOptions libraryOptions,
ILibraryManager libraryManager)
{
var discSubfolderCount = 0;
var notMultiDisc = false;
+ var namingOptions = ((LibraryManager)_libraryManager).GetNamingOptions();
+ var parser = new AlbumParser(namingOptions);
foreach (var fileSystemInfo in list)
{
if (fileSystemInfo.IsDirectory)
@@ -130,11 +130,11 @@ namespace Emby.Server.Implementations.Library.Resolvers.Audio
}
var path = fileSystemInfo.FullName;
- var hasMusic = ContainsMusic(directoryService.GetFileSystemEntries(path), false, directoryService, logger, fileSystem, libraryOptions, libraryManager);
+ var hasMusic = ContainsMusic(directoryService.GetFileSystemEntries(path), false, directoryService, logger, fileSystem, libraryManager);
if (hasMusic)
{
- if (IsMultiDiscFolder(path, libraryOptions))
+ if (parser.IsMultiPart(path))
{
logger.LogDebug("Found multi-disc folder: " + path);
discSubfolderCount++;
@@ -151,7 +151,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Audio
{
var fullName = fileSystemInfo.FullName;
- if (libraryManager.IsAudioFile(fullName, libraryOptions))
+ if (libraryManager.IsAudioFile(fullName))
{
return true;
}
@@ -165,15 +165,5 @@ namespace Emby.Server.Implementations.Library.Resolvers.Audio
return discSubfolderCount > 0;
}
-
- private bool IsMultiDiscFolder(string path, LibraryOptions libraryOptions)
- {
- var namingOptions = ((LibraryManager)_libraryManager).GetNamingOptions();
-
- var parser = new AlbumParser(namingOptions);
- var result = parser.ParseMultiPart(path);
-
- return result.IsMultiPart;
- }
}
}
diff --git a/Emby.Server.Implementations/Library/Resolvers/Audio/MusicArtistResolver.cs b/Emby.Server.Implementations/Library/Resolvers/Audio/MusicArtistResolver.cs
index ee7e84929..681db4896 100644
--- a/Emby.Server.Implementations/Library/Resolvers/Audio/MusicArtistResolver.cs
+++ b/Emby.Server.Implementations/Library/Resolvers/Audio/MusicArtistResolver.cs
@@ -27,7 +27,11 @@ namespace Emby.Server.Implementations.Library.Resolvers.Audio
/// <param name="fileSystem">The file system.</param>
/// <param name="libraryManager">The library manager.</param>
/// <param name="config">The configuration manager.</param>
- public MusicArtistResolver(ILogger logger, IFileSystem fileSystem, ILibraryManager libraryManager, IServerConfigurationManager config)
+ public MusicArtistResolver(
+ ILogger<MusicArtistResolver> logger,
+ IFileSystem fileSystem,
+ ILibraryManager libraryManager,
+ IServerConfigurationManager config)
{
_logger = logger;
_fileSystem = fileSystem;
@@ -80,14 +84,17 @@ namespace Emby.Server.Implementations.Library.Resolvers.Audio
}
// Avoid mis-identifying top folders
- if (args.Parent.IsRoot) return null;
+ if (args.Parent.IsRoot)
+ {
+ return null;
+ }
var directoryService = args.DirectoryService;
var albumResolver = new MusicAlbumResolver(_logger, _fileSystem, _libraryManager);
// If we contain an album assume we are an artist folder
- return args.FileSystemChildren.Where(i => i.IsDirectory).Any(i => albumResolver.IsMusicAlbum(i.FullName, directoryService, args.GetLibraryOptions())) ? new MusicArtist() : null;
+ return args.FileSystemChildren.Where(i => i.IsDirectory).Any(i => albumResolver.IsMusicAlbum(i.FullName, directoryService)) ? new MusicArtist() : null;
}
}
}
diff --git a/Emby.Server.Implementations/Library/Resolvers/BaseVideoResolver.cs b/Emby.Server.Implementations/Library/Resolvers/BaseVideoResolver.cs
index 43302bb3f..fb75593bd 100644
--- a/Emby.Server.Implementations/Library/Resolvers/BaseVideoResolver.cs
+++ b/Emby.Server.Implementations/Library/Resolvers/BaseVideoResolver.cs
@@ -1,5 +1,4 @@
#pragma warning disable CS1591
-#pragma warning disable SA1600
using System;
using System.IO;
@@ -80,6 +79,7 @@ namespace Emby.Server.Implementations.Library.Resolvers
};
break;
}
+
if (IsBluRayDirectory(child.FullName, filename, args.DirectoryService))
{
videoInfo = parser.ResolveDirectory(args.Path);
@@ -137,7 +137,7 @@ namespace Emby.Server.Implementations.Library.Resolvers
return null;
}
- if (LibraryManager.IsVideoFile(args.Path, args.GetLibraryOptions()) || videoInfo.IsStub)
+ if (LibraryManager.IsVideoFile(args.Path) || videoInfo.IsStub)
{
var path = args.Path;
diff --git a/Emby.Server.Implementations/Library/Resolvers/Books/BookResolver.cs b/Emby.Server.Implementations/Library/Resolvers/Books/BookResolver.cs
index 1e2e0704c..0b93ebeb8 100644
--- a/Emby.Server.Implementations/Library/Resolvers/Books/BookResolver.cs
+++ b/Emby.Server.Implementations/Library/Resolvers/Books/BookResolver.cs
@@ -1,5 +1,4 @@
#pragma warning disable CS1591
-#pragma warning disable SA1600
using System;
using System.IO;
diff --git a/Emby.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs b/Emby.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs
index 6c7690055..cb67c8aa7 100644
--- a/Emby.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs
+++ b/Emby.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs
@@ -21,6 +21,28 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
/// </summary>
public class MovieResolver : BaseVideoResolver<Video>, IMultiItemResolver
{
+ private string[] _validCollectionTypes = new[]
+ {
+ CollectionType.Movies,
+ CollectionType.HomeVideos,
+ CollectionType.MusicVideos,
+ CollectionType.Movies,
+ CollectionType.Photos
+ };
+
+ private readonly IImageProcessor _imageProcessor;
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="MovieResolver"/> class.
+ /// </summary>
+ /// <param name="libraryManager">The library manager.</param>
+ /// <param name="imageProcessor">The image processor.</param>
+ public MovieResolver(ILibraryManager libraryManager, IImageProcessor imageProcessor)
+ : base(libraryManager)
+ {
+ _imageProcessor = imageProcessor;
+ }
+
/// <summary>
/// Gets the priority.
/// </summary>
@@ -144,7 +166,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
foreach (var video in resolverResult)
{
- var firstVideo = video.Files.First();
+ var firstVideo = video.Files[0];
var videoItem = new T
{
@@ -230,7 +252,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
// Owned items will be caught by the plain video resolver
if (args.Parent == null)
{
- //return FindMovie<Video>(args.Path, args.Parent, files, args.DirectoryService, collectionType);
+ // return FindMovie<Video>(args.Path, args.Parent, files, args.DirectoryService, collectionType);
return null;
}
@@ -275,7 +297,6 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
{
item = ResolveVideo<Movie>(args, true);
}
-
else if (string.Equals(collectionType, CollectionType.HomeVideos, StringComparison.OrdinalIgnoreCase) ||
string.Equals(collectionType, CollectionType.Photos, StringComparison.OrdinalIgnoreCase))
{
@@ -319,7 +340,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
{
if (item is Movie || item is MusicVideo)
{
- //we need to only look at the name of this actual item (not parents)
+ // We need to only look at the name of this actual item (not parents)
var justName = item.IsInMixedFolder ? Path.GetFileName(item.Path) : Path.GetFileName(item.ContainingFolderPath);
if (!string.IsNullOrEmpty(justName))
@@ -347,9 +368,8 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
}
/// <summary>
- /// Finds a movie based on a child file system entries
+ /// Finds a movie based on a child file system entries.
/// </summary>
- /// <typeparam name="T"></typeparam>
/// <returns>Movie.</returns>
private T FindMovie<T>(ItemResolveArgs args, string path, Folder parent, List<FileSystemMetadata> fileSystemEntries, IDirectoryService directoryService, string collectionType, bool parseName)
where T : Video, new()
@@ -377,6 +397,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
Set3DFormat(movie);
return movie;
}
+
if (IsBluRayDirectory(child.FullName, filename, directoryService))
{
var movie = new T
@@ -407,15 +428,15 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
}
// TODO: Allow GetMultiDiscMovie in here
- const bool supportsMultiVersion = true;
+ const bool SupportsMultiVersion = true;
- var result = ResolveVideos<T>(parent, fileSystemEntries, directoryService, supportsMultiVersion, collectionType, parseName) ??
+ var result = ResolveVideos<T>(parent, fileSystemEntries, directoryService, SupportsMultiVersion, collectionType, parseName) ??
new MultiItemResolverResult();
if (result.Items.Count == 1)
{
var videoPath = result.Items[0].Path;
- var hasPhotos = photos.Any(i => !PhotoResolver.IsOwnedByResolvedMedia(LibraryManager, libraryOptions, videoPath, i.Name));
+ var hasPhotos = photos.Any(i => !PhotoResolver.IsOwnedByResolvedMedia(LibraryManager, videoPath, i.Name));
if (!hasPhotos)
{
@@ -425,8 +446,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
return movie;
}
}
-
- if (result.Items.Count == 0 && multiDiscFolders.Count > 0)
+ else if (result.Items.Count == 0 && multiDiscFolders.Count > 0)
{
return GetMultiDiscMovie<T>(multiDiscFolders, directoryService);
}
@@ -437,7 +457,6 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
/// <summary>
/// Gets the multi disc movie.
/// </summary>
- /// <typeparam name="T"></typeparam>
/// <param name="multiDiscFolders">The folders.</param>
/// <param name="directoryService">The directory service.</param>
/// <returns>``0.</returns>
@@ -451,7 +470,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
var subFileEntries = directoryService.GetFileSystemEntries(i);
var subfolders = subFileEntries
- .Where(e => e.IsDirectory)
+ .Where(e => e.IsDirectory)
.ToList();
if (subfolders.Any(s => IsDvdDirectory(s.FullName, s.Name, directoryService)))
@@ -459,6 +478,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
videoTypes.Add(VideoType.Dvd);
return true;
}
+
if (subfolders.Any(s => IsBluRayDirectory(s.FullName, s.Name, directoryService)))
{
videoTypes.Add(VideoType.BluRay);
@@ -476,7 +496,6 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
}
return false;
-
}).OrderBy(i => i).ToList();
// If different video types were found, don't allow this
@@ -491,24 +510,24 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
}
var namingOptions = ((LibraryManager)LibraryManager).GetNamingOptions();
- var resolver = new StackResolver(namingOptions);
- var result = resolver.ResolveDirectories(folderPaths);
+ var result = new StackResolver(namingOptions).ResolveDirectories(folderPaths).ToList();
- if (result.Stacks.Count != 1)
+ if (result.Count != 1)
{
return null;
}
+ int additionalPartsLen = folderPaths.Count - 1;
+ var additionalParts = new string[additionalPartsLen];
+ folderPaths.CopyTo(1, additionalParts, 0, additionalPartsLen);
+
var returnVideo = new T
{
Path = folderPaths[0],
-
- AdditionalParts = folderPaths.Skip(1).ToArray(),
-
+ AdditionalParts = additionalParts,
VideoType = videoTypes[0],
-
- Name = result.Stacks[0].Name
+ Name = result[0].Name
};
SetIsoType(returnVideo);
@@ -516,15 +535,6 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
return returnVideo;
}
- private string[] ValidCollectionTypes = new[]
- {
- CollectionType.Movies,
- CollectionType.HomeVideos,
- CollectionType.MusicVideos,
- CollectionType.Movies,
- CollectionType.Photos
- };
-
private bool IsInvalid(Folder parent, string collectionType)
{
if (parent != null)
@@ -540,20 +550,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
return false;
}
- return !ValidCollectionTypes.Contains(collectionType, StringComparer.OrdinalIgnoreCase);
- }
-
- private IImageProcessor _imageProcessor;
-
- /// <summary>
- /// Initializes a new instance of the <see cref="MovieResolver"/> class.
- /// </summary>
- /// <param name="libraryManager">The library manager.</param>
- /// <param name="imageProcessor">The image processor.</param>
- public MovieResolver(ILibraryManager libraryManager, IImageProcessor imageProcessor)
- : base(libraryManager)
- {
- _imageProcessor = imageProcessor;
+ return !_validCollectionTypes.Contains(collectionType, StringComparer.OrdinalIgnoreCase);
}
}
}
diff --git a/Emby.Server.Implementations/Library/Resolvers/PhotoAlbumResolver.cs b/Emby.Server.Implementations/Library/Resolvers/PhotoAlbumResolver.cs
index 4536b0aaa..3ac837057 100644
--- a/Emby.Server.Implementations/Library/Resolvers/PhotoAlbumResolver.cs
+++ b/Emby.Server.Implementations/Library/Resolvers/PhotoAlbumResolver.cs
@@ -63,13 +63,12 @@ namespace Emby.Server.Implementations.Library.Resolvers
{
if (!file.IsDirectory && PhotoResolver.IsImageFile(file.FullName, _imageProcessor))
{
- var libraryOptions = args.GetLibraryOptions();
var filename = file.Name;
var ownedByMedia = false;
foreach (var siblingFile in files)
{
- if (PhotoResolver.IsOwnedByMedia(_libraryManager, libraryOptions, siblingFile.FullName, filename))
+ if (PhotoResolver.IsOwnedByMedia(_libraryManager, siblingFile.FullName, filename))
{
ownedByMedia = true;
break;
diff --git a/Emby.Server.Implementations/Library/Resolvers/PhotoResolver.cs b/Emby.Server.Implementations/Library/Resolvers/PhotoResolver.cs
index e1eb23652..bcfcee9c6 100644
--- a/Emby.Server.Implementations/Library/Resolvers/PhotoResolver.cs
+++ b/Emby.Server.Implementations/Library/Resolvers/PhotoResolver.cs
@@ -1,5 +1,4 @@
#pragma warning disable CS1591
-#pragma warning disable SA1600
using System;
using System.Collections.Generic;
@@ -8,7 +7,6 @@ using System.Linq;
using MediaBrowser.Controller.Drawing;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library;
-using MediaBrowser.Model.Configuration;
using MediaBrowser.Model.Entities;
namespace Emby.Server.Implementations.Library.Resolvers
@@ -57,11 +55,10 @@ namespace Emby.Server.Implementations.Library.Resolvers
// Make sure the image doesn't belong to a video file
var files = args.DirectoryService.GetFiles(Path.GetDirectoryName(args.Path));
- var libraryOptions = args.GetLibraryOptions();
foreach (var file in files)
{
- if (IsOwnedByMedia(_libraryManager, libraryOptions, file.FullName, filename))
+ if (IsOwnedByMedia(_libraryManager, file.FullName, filename))
{
return null;
}
@@ -78,17 +75,17 @@ namespace Emby.Server.Implementations.Library.Resolvers
return null;
}
- internal static bool IsOwnedByMedia(ILibraryManager libraryManager, LibraryOptions libraryOptions, string file, string imageFilename)
+ internal static bool IsOwnedByMedia(ILibraryManager libraryManager, string file, string imageFilename)
{
- if (libraryManager.IsVideoFile(file, libraryOptions))
+ if (libraryManager.IsVideoFile(file))
{
- return IsOwnedByResolvedMedia(libraryManager, libraryOptions, file, imageFilename);
+ return IsOwnedByResolvedMedia(libraryManager, file, imageFilename);
}
return false;
}
- internal static bool IsOwnedByResolvedMedia(ILibraryManager libraryManager, LibraryOptions libraryOptions, string file, string imageFilename)
+ internal static bool IsOwnedByResolvedMedia(ILibraryManager libraryManager, string file, string imageFilename)
=> imageFilename.StartsWith(Path.GetFileNameWithoutExtension(file), StringComparison.OrdinalIgnoreCase);
internal static bool IsImageFile(string path, IImageProcessor imageProcessor)
diff --git a/Emby.Server.Implementations/Library/Resolvers/PlaylistResolver.cs b/Emby.Server.Implementations/Library/Resolvers/PlaylistResolver.cs
index 5e672f221..41561916f 100644
--- a/Emby.Server.Implementations/Library/Resolvers/PlaylistResolver.cs
+++ b/Emby.Server.Implementations/Library/Resolvers/PlaylistResolver.cs
@@ -1,65 +1,70 @@
#pragma warning disable CS1591
-#pragma warning disable SA1600
using System;
using System.IO;
using System.Linq;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Playlists;
+using MediaBrowser.Controller.Resolvers;
+using MediaBrowser.LocalMetadata.Savers;
using MediaBrowser.Model.Entities;
namespace Emby.Server.Implementations.Library.Resolvers
{
+ /// <summary>
+ /// <see cref="IItemResolver"/> for <see cref="Playlist"/> library items.
+ /// </summary>
public class PlaylistResolver : FolderResolver<Playlist>
{
- private string[] SupportedCollectionTypes = new string[] {
-
+ private string[] _musicPlaylistCollectionTypes = new string[] {
string.Empty,
CollectionType.Music
};
- /// <summary>
- /// Resolves the specified args.
- /// </summary>
- /// <param name="args">The args.</param>
- /// <returns>BoxSet.</returns>
+ /// <inheritdoc/>
protected override Playlist Resolve(ItemResolveArgs args)
{
- // It's a boxset if all of the following conditions are met:
- // Is a Directory
- // Contains [playlist] in the path
if (args.IsDirectory)
{
- var filename = Path.GetFileName(args.Path);
-
- if (string.IsNullOrEmpty(filename))
+ // It's a boxset if the path is a directory with [playlist] in it's the name
+ // TODO: Should this use Path.GetDirectoryName() instead?
+ bool isBoxSet = Path.GetFileName(args.Path)
+ ?.Contains("[playlist]", StringComparison.OrdinalIgnoreCase)
+ ?? false;
+ if (isBoxSet)
{
- return null;
+ return new Playlist
+ {
+ Path = args.Path,
+ Name = Path.GetFileName(args.Path).Replace("[playlist]", string.Empty, StringComparison.OrdinalIgnoreCase).Trim()
+ };
}
- if (filename.IndexOf("[playlist]", StringComparison.OrdinalIgnoreCase) != -1)
+ // It's a directory-based playlist if the directory contains a playlist file
+ var filePaths = Directory.EnumerateFiles(args.Path);
+ if (filePaths.Any(f => f.EndsWith(PlaylistXmlSaver.DefaultPlaylistFilename, StringComparison.OrdinalIgnoreCase)))
{
return new Playlist
{
Path = args.Path,
- Name = Path.GetFileName(args.Path).Replace("[playlist]", string.Empty, StringComparison.OrdinalIgnoreCase).Trim()
+ Name = Path.GetFileName(args.Path)
};
}
}
- else
+
+ // Check if this is a music playlist file
+ // It should have the correct collection type and a supported file extension
+ else if (_musicPlaylistCollectionTypes.Contains(args.CollectionType ?? string.Empty, StringComparer.OrdinalIgnoreCase))
{
- if (SupportedCollectionTypes.Contains(args.CollectionType ?? string.Empty, StringComparer.OrdinalIgnoreCase))
+ var extension = Path.GetExtension(args.Path);
+ if (Playlist.SupportedExtensions.Contains(extension ?? string.Empty, StringComparer.OrdinalIgnoreCase))
{
- var extension = Path.GetExtension(args.Path);
- if (Playlist.SupportedExtensions.Contains(extension ?? string.Empty, StringComparer.OrdinalIgnoreCase))
+ return new Playlist
{
- return new Playlist
- {
- Path = args.Path,
- Name = Path.GetFileNameWithoutExtension(args.Path),
- IsInMixedFolder = true
- };
- }
+ Path = args.Path,
+ Name = Path.GetFileNameWithoutExtension(args.Path),
+ IsInMixedFolder = true
+ };
}
}
diff --git a/Emby.Server.Implementations/Library/Resolvers/SpecialFolderResolver.cs b/Emby.Server.Implementations/Library/Resolvers/SpecialFolderResolver.cs
index eca60b133..1030ed39d 100644
--- a/Emby.Server.Implementations/Library/Resolvers/SpecialFolderResolver.cs
+++ b/Emby.Server.Implementations/Library/Resolvers/SpecialFolderResolver.cs
@@ -1,5 +1,4 @@
#pragma warning disable CS1591
-#pragma warning disable SA1600
using System;
using System.IO;
diff --git a/Emby.Server.Implementations/Library/Resolvers/TV/SeasonResolver.cs b/Emby.Server.Implementations/Library/Resolvers/TV/SeasonResolver.cs
index 3b9e48d97..18145b7f1 100644
--- a/Emby.Server.Implementations/Library/Resolvers/TV/SeasonResolver.cs
+++ b/Emby.Server.Implementations/Library/Resolvers/TV/SeasonResolver.cs
@@ -9,17 +9,12 @@ using Microsoft.Extensions.Logging;
namespace Emby.Server.Implementations.Library.Resolvers.TV
{
/// <summary>
- /// Class SeasonResolver
+ /// Class SeasonResolver.
/// </summary>
public class SeasonResolver : FolderResolver<Season>
{
- /// <summary>
- /// The _config
- /// </summary>
private readonly IServerConfigurationManager _config;
-
private readonly ILibraryManager _libraryManager;
- private static readonly CultureInfo UsCulture = new CultureInfo("en-US");
private readonly ILocalizationManager _localization;
private readonly ILogger _logger;
@@ -30,7 +25,11 @@ namespace Emby.Server.Implementations.Library.Resolvers.TV
/// <param name="libraryManager">The library manager.</param>
/// <param name="localization">The localization</param>
/// <param name="logger">The logger</param>
- public SeasonResolver(IServerConfigurationManager config, ILibraryManager libraryManager, ILocalizationManager localization, ILogger logger)
+ public SeasonResolver(
+ IServerConfigurationManager config,
+ ILibraryManager libraryManager,
+ ILocalizationManager localization,
+ ILogger<SeasonResolver> logger)
{
_config = config;
_libraryManager = libraryManager;
@@ -45,14 +44,13 @@ namespace Emby.Server.Implementations.Library.Resolvers.TV
/// <returns>Season.</returns>
protected override Season Resolve(ItemResolveArgs args)
{
- if (args.Parent is Series && args.IsDirectory)
+ if (args.Parent is Series series && args.IsDirectory)
{
var namingOptions = ((LibraryManager)_libraryManager).GetNamingOptions();
- var series = ((Series)args.Parent);
var path = args.Path;
- var seasonParserResult = new SeasonPathParser().Parse(path, true, true);
+ var seasonParserResult = SeasonPathParser.Parse(path, true, true);
var season = new Season
{
@@ -74,7 +72,8 @@ namespace Emby.Server.Implementations.Library.Resolvers.TV
{
if (episodeInfo.EpisodeNumber.HasValue && episodeInfo.SeasonNumber.HasValue)
{
- _logger.LogDebug("Found folder underneath series with episode number: {0}. Season {1}. Episode {2}",
+ _logger.LogDebug(
+ "Found folder underneath series with episode number: {0}. Season {1}. Episode {2}",
path,
episodeInfo.SeasonNumber.Value,
episodeInfo.EpisodeNumber.Value);
@@ -90,7 +89,11 @@ namespace Emby.Server.Implementations.Library.Resolvers.TV
season.Name = seasonNumber == 0 ?
args.LibraryOptions.SeasonZeroDisplayName :
- string.Format(_localization.GetLocalizedString("NameSeasonNumber"), seasonNumber.ToString(UsCulture), args.GetLibraryOptions().PreferredMetadataLanguage);
+ string.Format(
+ CultureInfo.InvariantCulture,
+ _localization.GetLocalizedString("NameSeasonNumber"),
+ seasonNumber,
+ args.GetLibraryOptions().PreferredMetadataLanguage);
}
diff --git a/Emby.Server.Implementations/Library/Resolvers/TV/SeriesResolver.cs b/Emby.Server.Implementations/Library/Resolvers/TV/SeriesResolver.cs
index e39d85bc9..dd6bd8ee8 100644
--- a/Emby.Server.Implementations/Library/Resolvers/TV/SeriesResolver.cs
+++ b/Emby.Server.Implementations/Library/Resolvers/TV/SeriesResolver.cs
@@ -1,5 +1,4 @@
#pragma warning disable CS1591
-#pragma warning disable SA1600
using System;
using System.Collections.Generic;
@@ -9,7 +8,6 @@ using MediaBrowser.Controller.Entities.TV;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Providers;
using MediaBrowser.Controller.Resolvers;
-using MediaBrowser.Model.Configuration;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.IO;
using Microsoft.Extensions.Logging;
@@ -31,7 +29,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.TV
/// <param name="fileSystem">The file system.</param>
/// <param name="logger">The logger.</param>
/// <param name="libraryManager">The library manager.</param>
- public SeriesResolver(IFileSystem fileSystem, ILogger logger, ILibraryManager libraryManager)
+ public SeriesResolver(IFileSystem fileSystem, ILogger<SeriesResolver> logger, ILibraryManager libraryManager)
{
_fileSystem = fileSystem;
_logger = logger;
@@ -102,7 +100,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.TV
return null;
}
- if (IsSeriesFolder(args.Path, args.FileSystemChildren, args.DirectoryService, _fileSystem, _logger, _libraryManager, args.GetLibraryOptions(), false))
+ if (IsSeriesFolder(args.Path, args.FileSystemChildren, args.DirectoryService, _fileSystem, _logger, _libraryManager, false))
{
return new Series
{
@@ -123,24 +121,10 @@ namespace Emby.Server.Implementations.Library.Resolvers.TV
IFileSystem fileSystem,
ILogger logger,
ILibraryManager libraryManager,
- LibraryOptions libraryOptions,
bool isTvContentType)
{
foreach (var child in fileSystemChildren)
{
- //if ((attributes & FileAttributes.Hidden) == FileAttributes.Hidden)
- //{
- // //logger.LogDebug("Igoring series file or folder marked hidden: {0}", child.FullName);
- // continue;
- //}
-
- // Can't enforce this because files saved by Bitcasa are always marked System
- //if ((attributes & FileAttributes.System) == FileAttributes.System)
- //{
- // logger.LogDebug("Igoring series subfolder marked system: {0}", child.FullName);
- // continue;
- //}
-
if (child.IsDirectory)
{
if (IsSeasonFolder(child.FullName, isTvContentType, libraryManager))
@@ -152,7 +136,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.TV
else
{
string fullName = child.FullName;
- if (libraryManager.IsVideoFile(fullName, libraryOptions))
+ if (libraryManager.IsVideoFile(fullName))
{
if (isTvContentType)
{
@@ -203,7 +187,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.TV
/// <returns><c>true</c> if [is season folder] [the specified path]; otherwise, <c>false</c>.</returns>
private static bool IsSeasonFolder(string path, bool isTvContentType, ILibraryManager libraryManager)
{
- var seasonNumber = new SeasonPathParser().Parse(path, isTvContentType, isTvContentType).SeasonNumber;
+ var seasonNumber = SeasonPathParser.Parse(path, isTvContentType, isTvContentType).SeasonNumber;
return seasonNumber.HasValue;
}
diff --git a/Emby.Server.Implementations/Library/Resolvers/VideoResolver.cs b/Emby.Server.Implementations/Library/Resolvers/VideoResolver.cs
index 6404d6476..62268fce9 100644
--- a/Emby.Server.Implementations/Library/Resolvers/VideoResolver.cs
+++ b/Emby.Server.Implementations/Library/Resolvers/VideoResolver.cs
@@ -1,5 +1,4 @@
#pragma warning disable CS1591
-#pragma warning disable SA1600
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library;
diff --git a/Emby.Server.Implementations/Library/SearchEngine.cs b/Emby.Server.Implementations/Library/SearchEngine.cs
index 76ae14720..59a77607d 100644
--- a/Emby.Server.Implementations/Library/SearchEngine.cs
+++ b/Emby.Server.Implementations/Library/SearchEngine.cs
@@ -1,5 +1,4 @@
#pragma warning disable CS1591
-#pragma warning disable SA1600
using System;
using System.Collections.Generic;
@@ -18,16 +17,15 @@ namespace Emby.Server.Implementations.Library
{
public class SearchEngine : ISearchEngine
{
+ private readonly ILogger _logger;
private readonly ILibraryManager _libraryManager;
private readonly IUserManager _userManager;
- private readonly ILogger _logger;
- public SearchEngine(ILoggerFactory loggerFactory, ILibraryManager libraryManager, IUserManager userManager)
+ public SearchEngine(ILogger<SearchEngine> logger, ILibraryManager libraryManager, IUserManager userManager)
{
+ _logger = logger;
_libraryManager = libraryManager;
_userManager = userManager;
-
- _logger = loggerFactory.CreateLogger("SearchEngine");
}
public QueryResult<SearchHintInfo> GetSearchHints(SearchQuery query)
diff --git a/Emby.Server.Implementations/Library/UserDataManager.cs b/Emby.Server.Implementations/Library/UserDataManager.cs
index f1fb35d9a..a9772a078 100644
--- a/Emby.Server.Implementations/Library/UserDataManager.cs
+++ b/Emby.Server.Implementations/Library/UserDataManager.cs
@@ -1,5 +1,4 @@
#pragma warning disable CS1591
-#pragma warning disable SA1600
using System;
using System.Collections.Concurrent;
@@ -29,25 +28,24 @@ namespace Emby.Server.Implementations.Library
private readonly ILogger _logger;
private readonly IServerConfigurationManager _config;
-
- private Func<IUserManager> _userManager;
-
- public UserDataManager(ILoggerFactory loggerFactory, IServerConfigurationManager config, Func<IUserManager> userManager)
+ private readonly IUserManager _userManager;
+ private readonly IUserDataRepository _repository;
+
+ public UserDataManager(
+ ILogger<UserDataManager> logger,
+ IServerConfigurationManager config,
+ IUserManager userManager,
+ IUserDataRepository repository)
{
+ _logger = logger;
_config = config;
- _logger = loggerFactory.CreateLogger(GetType().Name);
_userManager = userManager;
+ _repository = repository;
}
- /// <summary>
- /// Gets or sets the repository.
- /// </summary>
- /// <value>The repository.</value>
- public IUserDataRepository Repository { get; set; }
-
public void SaveUserData(Guid userId, BaseItem item, UserItemData userData, UserDataSaveReason reason, CancellationToken cancellationToken)
{
- var user = _userManager().GetUserById(userId);
+ var user = _userManager.GetUserById(userId);
SaveUserData(user, item, userData, reason, cancellationToken);
}
@@ -72,7 +70,7 @@ namespace Emby.Server.Implementations.Library
foreach (var key in keys)
{
- Repository.SaveUserData(userId, key, userData, cancellationToken);
+ _repository.SaveUserData(userId, key, userData, cancellationToken);
}
var cacheKey = GetCacheKey(userId, item.Id);
@@ -97,9 +95,9 @@ namespace Emby.Server.Implementations.Library
/// <returns></returns>
public void SaveAllUserData(Guid userId, UserItemData[] userData, CancellationToken cancellationToken)
{
- var user = _userManager().GetUserById(userId);
+ var user = _userManager.GetUserById(userId);
- Repository.SaveAllUserData(user.InternalId, userData, cancellationToken);
+ _repository.SaveAllUserData(user.InternalId, userData, cancellationToken);
}
/// <summary>
@@ -109,14 +107,14 @@ namespace Emby.Server.Implementations.Library
/// <returns></returns>
public List<UserItemData> GetAllUserData(Guid userId)
{
- var user = _userManager().GetUserById(userId);
+ var user = _userManager.GetUserById(userId);
- return Repository.GetAllUserData(user.InternalId);
+ return _repository.GetAllUserData(user.InternalId);
}
public UserItemData GetUserData(Guid userId, Guid itemId, List<string> keys)
{
- var user = _userManager().GetUserById(userId);
+ var user = _userManager.GetUserById(userId);
return GetUserData(user, itemId, keys);
}
@@ -132,7 +130,7 @@ namespace Emby.Server.Implementations.Library
private UserItemData GetUserDataInternal(long internalUserId, List<string> keys)
{
- var userData = Repository.GetUserData(internalUserId, keys);
+ var userData = _repository.GetUserData(internalUserId, keys);
if (userData != null)
{
diff --git a/Emby.Server.Implementations/Library/UserManager.cs b/Emby.Server.Implementations/Library/UserManager.cs
index 656eeb145..d63bc6bda 100644
--- a/Emby.Server.Implementations/Library/UserManager.cs
+++ b/Emby.Server.Implementations/Library/UserManager.cs
@@ -1,5 +1,4 @@
#pragma warning disable CS1591
-#pragma warning disable SA1600
using System;
using System.Collections.Concurrent;
@@ -21,6 +20,7 @@ using MediaBrowser.Controller.Drawing;
using MediaBrowser.Controller.Dto;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library;
+using MediaBrowser.Controller.Net;
using MediaBrowser.Controller.Persistence;
using MediaBrowser.Controller.Plugins;
using MediaBrowser.Controller.Providers;
@@ -45,22 +45,14 @@ namespace Emby.Server.Implementations.Library
{
private readonly object _policySyncLock = new object();
private readonly object _configSyncLock = new object();
- /// <summary>
- /// The logger.
- /// </summary>
- private readonly ILogger _logger;
- /// <summary>
- /// Gets the active user repository.
- /// </summary>
- /// <value>The user repository.</value>
+ private readonly ILogger _logger;
private readonly IUserRepository _userRepository;
private readonly IXmlSerializer _xmlSerializer;
private readonly IJsonSerializer _jsonSerializer;
private readonly INetworkManager _networkManager;
-
- private readonly Func<IImageProcessor> _imageProcessorFactory;
- private readonly Func<IDtoService> _dtoServiceFactory;
+ private readonly IImageProcessor _imageProcessor;
+ private readonly Lazy<IDtoService> _dtoServiceFactory;
private readonly IServerApplicationHost _appHost;
private readonly IFileSystem _fileSystem;
private readonly ICryptoProvider _cryptoProvider;
@@ -75,13 +67,15 @@ namespace Emby.Server.Implementations.Library
private IPasswordResetProvider[] _passwordResetProviders;
private DefaultPasswordResetProvider _defaultPasswordResetProvider;
+ private IDtoService DtoService => _dtoServiceFactory.Value;
+
public UserManager(
ILogger<UserManager> logger,
IUserRepository userRepository,
IXmlSerializer xmlSerializer,
INetworkManager networkManager,
- Func<IImageProcessor> imageProcessorFactory,
- Func<IDtoService> dtoServiceFactory,
+ IImageProcessor imageProcessor,
+ Lazy<IDtoService> dtoServiceFactory,
IServerApplicationHost appHost,
IJsonSerializer jsonSerializer,
IFileSystem fileSystem,
@@ -91,7 +85,7 @@ namespace Emby.Server.Implementations.Library
_userRepository = userRepository;
_xmlSerializer = xmlSerializer;
_networkManager = networkManager;
- _imageProcessorFactory = imageProcessorFactory;
+ _imageProcessor = imageProcessor;
_dtoServiceFactory = dtoServiceFactory;
_appHost = appHost;
_jsonSerializer = jsonSerializer;
@@ -265,6 +259,7 @@ namespace Emby.Server.Implementations.Library
{
if (string.IsNullOrWhiteSpace(username))
{
+ _logger.LogInformation("Authentication request without username has been denied (IP: {IP}).", remoteEndPoint);
throw new ArgumentNullException(nameof(username));
}
@@ -291,10 +286,11 @@ namespace Emby.Server.Implementations.Library
&& authenticationProvider != null
&& !(authenticationProvider is DefaultAuthenticationProvider))
{
- // We should trust the user that the authprovider says, not what was typed
+ // Trust the username returned by the authentication provider
username = updatedUsername;
- // Search the database for the user again; the authprovider might have created it
+ // Search the database for the user again
+ // the authentication provider might have created it
user = Users
.FirstOrDefault(i => string.Equals(username, i.Name, StringComparison.OrdinalIgnoreCase));
@@ -319,26 +315,26 @@ namespace Emby.Server.Implementations.Library
if (user == null)
{
+ _logger.LogInformation("Authentication request for {UserName} has been denied (IP: {IP}).", username, remoteEndPoint);
throw new AuthenticationException("Invalid username or password entered.");
}
if (user.Policy.IsDisabled)
{
- throw new AuthenticationException(
- string.Format(
- CultureInfo.InvariantCulture,
- "The {0} account is currently disabled. Please consult with your administrator.",
- user.Name));
+ _logger.LogInformation("Authentication request for {UserName} has been denied because this account is currently disabled (IP: {IP}).", username, remoteEndPoint);
+ throw new SecurityException($"The {user.Name} account is currently disabled. Please consult with your administrator.");
}
if (!user.Policy.EnableRemoteAccess && !_networkManager.IsInLocalNetwork(remoteEndPoint))
{
- throw new AuthenticationException("Forbidden.");
+ _logger.LogInformation("Authentication request for {UserName} forbidden: remote access disabled and user not in local network (IP: {IP}).", username, remoteEndPoint);
+ throw new SecurityException("Forbidden.");
}
if (!user.IsParentalScheduleAllowed())
{
- throw new AuthenticationException("User is not allowed access at this time.");
+ _logger.LogInformation("Authentication request for {UserName} is not allowed at this time due parental restrictions (IP: {IP}).", username, remoteEndPoint);
+ throw new SecurityException("User is not allowed access at this time.");
}
// Update LastActivityDate and LastLoginDate, then save
@@ -351,14 +347,14 @@ namespace Emby.Server.Implementations.Library
}
ResetInvalidLoginAttemptCount(user);
+ _logger.LogInformation("Authentication request for {UserName} has succeeded.", user.Name);
}
else
{
IncrementInvalidLoginAttemptCount(user);
+ _logger.LogInformation("Authentication request for {UserName} has been denied (IP: {IP}).", user.Name, remoteEndPoint);
}
- _logger.LogInformation("Authentication request for {0} {1}.", user.Name, success ? "has succeeded" : "has been denied");
-
return success ? user : null;
}
@@ -600,7 +596,7 @@ namespace Emby.Server.Implementations.Library
try
{
- _dtoServiceFactory().AttachPrimaryImageAspectRatio(dto, user);
+ DtoService.AttachPrimaryImageAspectRatio(dto, user);
}
catch (Exception ex)
{
@@ -625,7 +621,7 @@ namespace Emby.Server.Implementations.Library
{
try
{
- return _imageProcessorFactory().GetImageCacheTag(item, image);
+ return _imageProcessor.GetImageCacheTag(item, image);
}
catch (Exception ex)
{
@@ -667,7 +663,7 @@ namespace Emby.Server.Implementations.Library
throw new ArgumentException("Invalid username", nameof(newName));
}
- if (user.Name.Equals(newName, StringComparison.OrdinalIgnoreCase))
+ if (user.Name.Equals(newName, StringComparison.Ordinal))
{
throw new ArgumentException("The new and old names must be different.");
}
@@ -805,17 +801,17 @@ namespace Emby.Server.Implementations.Library
// Delete user config dir
lock (_configSyncLock)
- lock (_policySyncLock)
- {
- try
- {
- Directory.Delete(user.ConfigurationDirectoryPath, true);
- }
- catch (IOException ex)
+ lock (_policySyncLock)
{
- _logger.LogError(ex, "Error deleting user config dir: {Path}", user.ConfigurationDirectoryPath);
+ try
+ {
+ Directory.Delete(user.ConfigurationDirectoryPath, true);
+ }
+ catch (IOException ex)
+ {
+ _logger.LogError(ex, "Error deleting user config dir: {Path}", user.ConfigurationDirectoryPath);
+ }
}
- }
_users.TryRemove(user.Id, out _);
diff --git a/Emby.Server.Implementations/Library/UserViewManager.cs b/Emby.Server.Implementations/Library/UserViewManager.cs
index 935deb71c..322819b05 100644
--- a/Emby.Server.Implementations/Library/UserViewManager.cs
+++ b/Emby.Server.Implementations/Library/UserViewManager.cs
@@ -1,5 +1,4 @@
#pragma warning disable CS1591
-#pragma warning disable SA1600
using System;
using System.Collections.Generic;
diff --git a/Emby.Server.Implementations/Library/Validators/ArtistsPostScanTask.cs b/Emby.Server.Implementations/Library/Validators/ArtistsPostScanTask.cs
index 61a07d0d6..2af8ff5cb 100644
--- a/Emby.Server.Implementations/Library/Validators/ArtistsPostScanTask.cs
+++ b/Emby.Server.Implementations/Library/Validators/ArtistsPostScanTask.cs
@@ -25,7 +25,10 @@ namespace Emby.Server.Implementations.Library.Validators
/// <param name="libraryManager">The library manager.</param>
/// <param name="logger">The logger.</param>
/// <param name="itemRepo">The item repository.</param>
- public ArtistsPostScanTask(ILibraryManager libraryManager, ILogger logger, IItemRepository itemRepo)
+ public ArtistsPostScanTask(
+ ILibraryManager libraryManager,
+ ILogger<ArtistsPostScanTask> logger,
+ IItemRepository itemRepo)
{
_libraryManager = libraryManager;
_logger = logger;
diff --git a/Emby.Server.Implementations/Library/Validators/GenresPostScanTask.cs b/Emby.Server.Implementations/Library/Validators/GenresPostScanTask.cs
index 06d1dd89d..251785dfd 100644
--- a/Emby.Server.Implementations/Library/Validators/GenresPostScanTask.cs
+++ b/Emby.Server.Implementations/Library/Validators/GenresPostScanTask.cs
@@ -25,7 +25,10 @@ namespace Emby.Server.Implementations.Library.Validators
/// <param name="libraryManager">The library manager.</param>
/// <param name="logger">The logger.</param>
/// <param name="itemRepo">The item repository.</param>
- public GenresPostScanTask(ILibraryManager libraryManager, ILogger logger, IItemRepository itemRepo)
+ public GenresPostScanTask(
+ ILibraryManager libraryManager,
+ ILogger<GenresPostScanTask> logger,
+ IItemRepository itemRepo)
{
_libraryManager = libraryManager;
_logger = logger;
diff --git a/Emby.Server.Implementations/Library/Validators/MusicGenresPostScanTask.cs b/Emby.Server.Implementations/Library/Validators/MusicGenresPostScanTask.cs
index 58549e9d7..9d8690116 100644
--- a/Emby.Server.Implementations/Library/Validators/MusicGenresPostScanTask.cs
+++ b/Emby.Server.Implementations/Library/Validators/MusicGenresPostScanTask.cs
@@ -25,7 +25,10 @@ namespace Emby.Server.Implementations.Library.Validators
/// <param name="libraryManager">The library manager.</param>
/// <param name="logger">The logger.</param>
/// <param name="itemRepo">The item repository.</param>
- public MusicGenresPostScanTask(ILibraryManager libraryManager, ILogger logger, IItemRepository itemRepo)
+ public MusicGenresPostScanTask(
+ ILibraryManager libraryManager,
+ ILogger<MusicGenresPostScanTask> logger,
+ IItemRepository itemRepo)
{
_libraryManager = libraryManager;
_logger = logger;
diff --git a/Emby.Server.Implementations/Library/Validators/StudiosPostScanTask.cs b/Emby.Server.Implementations/Library/Validators/StudiosPostScanTask.cs
index 00899c336..2f8f906b9 100644
--- a/Emby.Server.Implementations/Library/Validators/StudiosPostScanTask.cs
+++ b/Emby.Server.Implementations/Library/Validators/StudiosPostScanTask.cs
@@ -26,7 +26,10 @@ namespace Emby.Server.Implementations.Library.Validators
/// <param name="libraryManager">The library manager.</param>
/// <param name="logger">The logger.</param>
/// <param name="itemRepo">The item repository.</param>
- public StudiosPostScanTask(ILibraryManager libraryManager, ILogger logger, IItemRepository itemRepo)
+ public StudiosPostScanTask(
+ ILibraryManager libraryManager,
+ ILogger<StudiosPostScanTask> logger,
+ IItemRepository itemRepo)
{
_libraryManager = libraryManager;
_logger = logger;
diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/DirectRecorder.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/DirectRecorder.cs
index 84e8c31f9..2e13a3bb3 100644
--- a/Emby.Server.Implementations/LiveTv/EmbyTV/DirectRecorder.cs
+++ b/Emby.Server.Implementations/LiveTv/EmbyTV/DirectRecorder.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
using System;
using System.IO;
using System.Net.Http;
@@ -15,14 +17,12 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
{
private readonly ILogger _logger;
private readonly IHttpClient _httpClient;
- private readonly IFileSystem _fileSystem;
private readonly IStreamHelper _streamHelper;
- public DirectRecorder(ILogger logger, IHttpClient httpClient, IFileSystem fileSystem, IStreamHelper streamHelper)
+ public DirectRecorder(ILogger logger, IHttpClient httpClient, IStreamHelper streamHelper)
{
_logger = logger;
_httpClient = httpClient;
- _fileSystem = fileSystem;
_streamHelper = streamHelper;
}
@@ -45,7 +45,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
{
Directory.CreateDirectory(Path.GetDirectoryName(targetFile));
- using (var output = _fileSystem.GetFileStream(targetFile, FileOpenMode.Create, FileAccessMode.Write, FileShareMode.Read))
+ using (var output = new FileStream(targetFile, FileMode.Create, FileAccess.Write, FileShare.Read))
{
onStarted();
@@ -72,7 +72,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
UserAgent = "Emby/3.0",
// Shouldn't matter but may cause issues
- DecompressionMethod = CompressionMethod.None
+ DecompressionMethod = CompressionMethods.None
};
using (var response = await _httpClient.SendAsync(httpRequestOptions, HttpMethod.Get).ConfigureAwait(false))
@@ -81,7 +81,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
Directory.CreateDirectory(Path.GetDirectoryName(targetFile));
- using (var output = _fileSystem.GetFileStream(targetFile, FileOpenMode.Create, FileAccessMode.Write, FileShareMode.Read))
+ using (var output = new FileStream(targetFile, FileMode.Create, FileAccess.Write, FileShare.Read))
{
onStarted();
diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs
index f4d6cd4d3..33f4ca146 100644
--- a/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs
+++ b/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs
@@ -1,9 +1,9 @@
-#pragma warning disable SA1600
#pragma warning disable CS1591
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
+using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
@@ -26,11 +26,9 @@ using MediaBrowser.Controller.LiveTv;
using MediaBrowser.Controller.MediaEncoding;
using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Configuration;
-using MediaBrowser.Model.Diagnostics;
using MediaBrowser.Model.Dto;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Events;
-using MediaBrowser.Model.Extensions;
using MediaBrowser.Model.IO;
using MediaBrowser.Model.LiveTv;
using MediaBrowser.Model.MediaInfo;
@@ -63,7 +61,6 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
private readonly ILibraryManager _libraryManager;
private readonly IProviderManager _providerManager;
private readonly IMediaEncoder _mediaEncoder;
- private readonly IProcessFactory _processFactory;
private readonly IMediaSourceManager _mediaSourceManager;
private readonly IStreamHelper _streamHelper;
@@ -81,7 +78,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
IServerApplicationHost appHost,
IStreamHelper streamHelper,
IMediaSourceManager mediaSourceManager,
- ILogger logger,
+ ILogger<EmbyTV> logger,
IJsonSerializer jsonSerializer,
IHttpClient httpClient,
IServerConfigurationManager config,
@@ -90,8 +87,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
ILibraryManager libraryManager,
ILibraryMonitor libraryMonitor,
IProviderManager providerManager,
- IMediaEncoder mediaEncoder,
- IProcessFactory processFactory)
+ IMediaEncoder mediaEncoder)
{
Current = this;
@@ -104,7 +100,6 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
_libraryMonitor = libraryMonitor;
_providerManager = providerManager;
_mediaEncoder = mediaEncoder;
- _processFactory = processFactory;
_liveTvManager = (LiveTvManager)liveTvManager;
_jsonSerializer = jsonSerializer;
_mediaSourceManager = mediaSourceManager;
@@ -427,7 +422,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
{
foreach (NameValuePair mapping in mappings)
{
- if (StringHelper.EqualsIgnoreCase(mapping.Name, channelId))
+ if (string.Equals(mapping.Name, channelId, StringComparison.OrdinalIgnoreCase))
{
return mapping.Value;
}
@@ -1064,7 +1059,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
{
var stream = new MediaSourceInfo
{
- EncoderPath = _appHost.GetLocalApiUrl("127.0.0.1") + "/LiveTv/LiveRecordings/" + info.Id + "/stream",
+ EncoderPath = _appHost.GetLocalApiUrl("127.0.0.1", true) + "/LiveTv/LiveRecordings/" + info.Id + "/stream",
EncoderProtocol = MediaProtocol.Http,
Path = info.Path,
Protocol = MediaProtocol.File,
@@ -1664,10 +1659,10 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
{
if (mediaSource.RequiresLooping || !(mediaSource.Container ?? string.Empty).EndsWith("ts", StringComparison.OrdinalIgnoreCase) || (mediaSource.Protocol != MediaProtocol.File && mediaSource.Protocol != MediaProtocol.Http))
{
- return new EncodedRecorder(_logger, _fileSystem, _mediaEncoder, _config.ApplicationPaths, _jsonSerializer, _processFactory, _config);
+ return new EncodedRecorder(_logger, _mediaEncoder, _config.ApplicationPaths, _jsonSerializer, _config);
}
- return new DirectRecorder(_logger, _httpClient, _fileSystem, _streamHelper);
+ return new DirectRecorder(_logger, _httpClient, _streamHelper);
}
private void OnSuccessfulRecording(TimerInfo timer, string path)
@@ -1685,16 +1680,19 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
try
{
- var process = _processFactory.Create(new ProcessOptions
+ var process = new Process
{
- Arguments = GetPostProcessArguments(path, options.RecordingPostProcessorArguments),
- CreateNoWindow = true,
- EnableRaisingEvents = true,
- ErrorDialog = false,
- FileName = options.RecordingPostProcessor,
- IsHidden = true,
- UseShellExecute = false
- });
+ StartInfo = new ProcessStartInfo
+ {
+ Arguments = GetPostProcessArguments(path, options.RecordingPostProcessorArguments),
+ CreateNoWindow = true,
+ ErrorDialog = false,
+ FileName = options.RecordingPostProcessor,
+ WindowStyle = ProcessWindowStyle.Hidden,
+ UseShellExecute = false
+ },
+ EnableRaisingEvents = true
+ };
_logger.LogInformation("Running recording post processor {0} {1}", process.StartInfo.FileName, process.StartInfo.Arguments);
@@ -1714,11 +1712,9 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
private void Process_Exited(object sender, EventArgs e)
{
- using (var process = (IProcess)sender)
+ using (var process = (Process)sender)
{
_logger.LogInformation("Recording post-processing script completed with exit code {ExitCode}", process.ExitCode);
-
- process.Dispose();
}
}
@@ -1888,7 +1884,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
return;
}
- using (var stream = _fileSystem.GetFileStream(nfoPath, FileOpenMode.Create, FileAccessMode.Write, FileShareMode.Read))
+ using (var stream = new FileStream(nfoPath, FileMode.Create, FileAccess.Write, FileShare.Read))
{
var settings = new XmlWriterSettings
{
@@ -1952,7 +1948,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
return;
}
- using (var stream = _fileSystem.GetFileStream(nfoPath, FileOpenMode.Create, FileAccessMode.Write, FileShareMode.Read))
+ using (var stream = new FileStream(nfoPath, FileMode.Create, FileAccess.Write, FileShare.Read))
{
var settings = new XmlWriterSettings
{
diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs
index cc9c8e5d2..bc86cc59a 100644
--- a/Emby.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs
+++ b/Emby.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs
@@ -1,8 +1,10 @@
+#pragma warning disable CS1591
+
using System;
using System.Collections.Generic;
+using System.Diagnostics;
using System.Globalization;
using System.IO;
-using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
@@ -12,9 +14,7 @@ using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.MediaEncoding;
using MediaBrowser.Model.Configuration;
-using MediaBrowser.Model.Diagnostics;
using MediaBrowser.Model.Dto;
-using MediaBrowser.Model.Entities;
using MediaBrowser.Model.IO;
using MediaBrowser.Model.Serialization;
using Microsoft.Extensions.Logging;
@@ -24,33 +24,27 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
public class EncodedRecorder : IRecorder
{
private readonly ILogger _logger;
- private readonly IFileSystem _fileSystem;
private readonly IMediaEncoder _mediaEncoder;
private readonly IServerApplicationPaths _appPaths;
private bool _hasExited;
private Stream _logFileStream;
private string _targetPath;
- private IProcess _process;
- private readonly IProcessFactory _processFactory;
+ private Process _process;
private readonly IJsonSerializer _json;
private readonly TaskCompletionSource<bool> _taskCompletionSource = new TaskCompletionSource<bool>();
private readonly IServerConfigurationManager _config;
public EncodedRecorder(
ILogger logger,
- IFileSystem fileSystem,
IMediaEncoder mediaEncoder,
IServerApplicationPaths appPaths,
IJsonSerializer json,
- IProcessFactory processFactory,
IServerConfigurationManager config)
{
_logger = logger;
- _fileSystem = fileSystem;
_mediaEncoder = mediaEncoder;
_appPaths = appPaths;
_json = json;
- _processFactory = processFactory;
_config = config;
}
@@ -82,7 +76,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
_targetPath = targetFile;
Directory.CreateDirectory(Path.GetDirectoryName(targetFile));
- var process = _processFactory.Create(new ProcessOptions
+ var processStartInfo = new ProcessStartInfo
{
CreateNoWindow = true,
UseShellExecute = false,
@@ -93,35 +87,37 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
FileName = _mediaEncoder.EncoderPath,
Arguments = GetCommandLineArgs(mediaSource, inputFile, targetFile, duration),
- IsHidden = true,
- ErrorDialog = false,
- EnableRaisingEvents = true
- });
-
- _process = process;
+ WindowStyle = ProcessWindowStyle.Hidden,
+ ErrorDialog = false
+ };
- var commandLineLogMessage = process.StartInfo.FileName + " " + process.StartInfo.Arguments;
+ var commandLineLogMessage = processStartInfo.FileName + " " + processStartInfo.Arguments;
_logger.LogInformation(commandLineLogMessage);
var logFilePath = Path.Combine(_appPaths.LogDirectoryPath, "record-transcode-" + Guid.NewGuid() + ".txt");
Directory.CreateDirectory(Path.GetDirectoryName(logFilePath));
// FFMpeg writes debug/error info to stderr. This is useful when debugging so let's put it in the log directory.
- _logFileStream = _fileSystem.GetFileStream(logFilePath, FileOpenMode.Create, FileAccessMode.Write, FileShareMode.Read, true);
+ _logFileStream = new FileStream(logFilePath, FileMode.Create, FileAccess.Write, FileShare.Read, IODefaults.FileStreamBufferSize, true);
var commandLineLogMessageBytes = Encoding.UTF8.GetBytes(_json.SerializeToString(mediaSource) + Environment.NewLine + Environment.NewLine + commandLineLogMessage + Environment.NewLine + Environment.NewLine);
_logFileStream.Write(commandLineLogMessageBytes, 0, commandLineLogMessageBytes.Length);
- process.Exited += (sender, args) => OnFfMpegProcessExited(process, inputFile);
+ _process = new Process
+ {
+ StartInfo = processStartInfo,
+ EnableRaisingEvents = true
+ };
+ _process.Exited += (sender, args) => OnFfMpegProcessExited(_process, inputFile);
- process.Start();
+ _process.Start();
cancellationToken.Register(Stop);
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);
+ StartStreamingLog(_process.StandardError.BaseStream, _logFileStream);
_logger.LogInformation("ffmpeg recording process started for {0}", _targetPath);
@@ -295,30 +291,33 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
/// <summary>
/// Processes the exited.
/// </summary>
- private void OnFfMpegProcessExited(IProcess process, string inputFile)
+ private void OnFfMpegProcessExited(Process process, string inputFile)
{
- _hasExited = true;
+ using (process)
+ {
+ _hasExited = true;
- _logFileStream?.Dispose();
- _logFileStream = null;
+ _logFileStream?.Dispose();
+ _logFileStream = null;
- var exitCode = process.ExitCode;
+ var exitCode = process.ExitCode;
- _logger.LogInformation("FFMpeg recording exited with code {ExitCode} for {Path}", exitCode, _targetPath);
+ _logger.LogInformation("FFMpeg recording exited with code {ExitCode} for {Path}", exitCode, _targetPath);
- if (exitCode == 0)
- {
- _taskCompletionSource.TrySetResult(true);
- }
- else
- {
- _taskCompletionSource.TrySetException(
- new Exception(
- string.Format(
- CultureInfo.InvariantCulture,
- "Recording for {0} failed. Exit code {1}",
- _targetPath,
- exitCode)));
+ if (exitCode == 0)
+ {
+ _taskCompletionSource.TrySetResult(true);
+ }
+ else
+ {
+ _taskCompletionSource.TrySetException(
+ new Exception(
+ string.Format(
+ CultureInfo.InvariantCulture,
+ "Recording for {0} failed. Exit code {1}",
+ _targetPath,
+ exitCode)));
+ }
}
}
diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/EntryPoint.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/EntryPoint.cs
index 9c9ba09f5..69a9cb78a 100644
--- a/Emby.Server.Implementations/LiveTv/EmbyTV/EntryPoint.cs
+++ b/Emby.Server.Implementations/LiveTv/EmbyTV/EntryPoint.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
using System.Threading.Tasks;
using MediaBrowser.Controller.Plugins;
@@ -5,11 +7,13 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
{
public class EntryPoint : IServerEntryPoint
{
+ /// <inheritdoc />
public Task RunAsync()
{
return EmbyTV.Current.Start();
}
+ /// <inheritdoc />
public void Dispose()
{
}
diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/EpgChannelData.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/EpgChannelData.cs
index 498aa3c26..463d0ed0a 100644
--- a/Emby.Server.Implementations/LiveTv/EmbyTV/EpgChannelData.cs
+++ b/Emby.Server.Implementations/LiveTv/EmbyTV/EpgChannelData.cs
@@ -1,4 +1,3 @@
-#pragma warning disable SA1600
#pragma warning disable CS1591
using System;
diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/IRecorder.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/IRecorder.cs
index 6eced3050..4712724d6 100644
--- a/Emby.Server.Implementations/LiveTv/EmbyTV/IRecorder.cs
+++ b/Emby.Server.Implementations/LiveTv/EmbyTV/IRecorder.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
using System;
using System.Threading;
using System.Threading.Tasks;
diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/ItemDataProvider.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/ItemDataProvider.cs
index 9055a70a6..fc543dc55 100644
--- a/Emby.Server.Implementations/LiveTv/EmbyTV/ItemDataProvider.cs
+++ b/Emby.Server.Implementations/LiveTv/EmbyTV/ItemDataProvider.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
using System;
using System.Collections.Generic;
using System.IO;
diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/RecordingHelper.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/RecordingHelper.cs
index ded3c7607..0b0ff6cb3 100644
--- a/Emby.Server.Implementations/LiveTv/EmbyTV/RecordingHelper.cs
+++ b/Emby.Server.Implementations/LiveTv/EmbyTV/RecordingHelper.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
using System;
using System.Globalization;
using MediaBrowser.Controller.LiveTv;
@@ -21,7 +23,11 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
if (info.SeasonNumber.HasValue && info.EpisodeNumber.HasValue)
{
- name += string.Format(" S{0}E{1}", info.SeasonNumber.Value.ToString("00", CultureInfo.InvariantCulture), info.EpisodeNumber.Value.ToString("00", CultureInfo.InvariantCulture));
+ name += string.Format(
+ CultureInfo.InvariantCulture,
+ " S{0}E{1}",
+ info.SeasonNumber.Value.ToString("00", CultureInfo.InvariantCulture),
+ info.EpisodeNumber.Value.ToString("00", CultureInfo.InvariantCulture));
addHyphen = false;
}
else if (info.OriginalAirDate.HasValue)
@@ -32,7 +38,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
}
else
{
- name += " " + info.OriginalAirDate.Value.ToLocalTime().ToString("yyyy-MM-dd");
+ name += " " + info.OriginalAirDate.Value.ToLocalTime().ToString("yyyy-MM-dd", CultureInfo.InvariantCulture);
}
}
else
@@ -67,14 +73,15 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
{
date = date.ToLocalTime();
- return string.Format("{0}_{1}_{2}_{3}_{4}_{5}",
+ return string.Format(
+ CultureInfo.InvariantCulture,
+ "{0}_{1}_{2}_{3}_{4}_{5}",
date.Year.ToString("0000", CultureInfo.InvariantCulture),
date.Month.ToString("00", CultureInfo.InvariantCulture),
date.Day.ToString("00", CultureInfo.InvariantCulture),
date.Hour.ToString("00", CultureInfo.InvariantCulture),
date.Minute.ToString("00", CultureInfo.InvariantCulture),
- date.Second.ToString("00", CultureInfo.InvariantCulture)
- );
+ date.Second.ToString("00", CultureInfo.InvariantCulture));
}
}
}
diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/SeriesTimerManager.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/SeriesTimerManager.cs
index 520b44404..194e4606d 100644
--- a/Emby.Server.Implementations/LiveTv/EmbyTV/SeriesTimerManager.cs
+++ b/Emby.Server.Implementations/LiveTv/EmbyTV/SeriesTimerManager.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
using System;
using MediaBrowser.Controller.LiveTv;
using MediaBrowser.Model.Serialization;
@@ -12,6 +14,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
{
}
+ /// <inheritdoc />
public override void Add(SeriesTimerInfo item)
{
if (string.IsNullOrEmpty(item.Id))
diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/TimerManager.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/TimerManager.cs
index d09b281d4..7ebb043d8 100644
--- a/Emby.Server.Implementations/LiveTv/EmbyTV/TimerManager.cs
+++ b/Emby.Server.Implementations/LiveTv/EmbyTV/TimerManager.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
using System;
using System.Collections.Concurrent;
using System.Globalization;
diff --git a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs
index 1dd794da0..89b81fd96 100644
--- a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs
+++ b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
@@ -30,7 +32,11 @@ namespace Emby.Server.Implementations.LiveTv.Listings
private const string ApiUrl = "https://json.schedulesdirect.org/20141201";
- public SchedulesDirect(ILogger logger, IJsonSerializer jsonSerializer, IHttpClient httpClient, IApplicationHost appHost)
+ public SchedulesDirect(
+ ILogger<SchedulesDirect> logger,
+ IJsonSerializer jsonSerializer,
+ IHttpClient httpClient,
+ IApplicationHost appHost)
{
_logger = logger;
_jsonSerializer = jsonSerializer;
@@ -629,7 +635,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings
ListingsProviderInfo providerInfo)
{
// Schedules direct requires that the client support compression and will return a 400 response without it
- options.DecompressionMethod = CompressionMethod.Deflate;
+ options.DecompressionMethod = CompressionMethods.Deflate;
try
{
@@ -659,7 +665,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings
ListingsProviderInfo providerInfo)
{
// Schedules direct requires that the client support compression and will return a 400 response without it
- options.DecompressionMethod = CompressionMethod.Deflate;
+ options.DecompressionMethod = CompressionMethods.Deflate;
try
{
diff --git a/Emby.Server.Implementations/LiveTv/Listings/XmlTvListingsProvider.cs b/Emby.Server.Implementations/LiveTv/Listings/XmlTvListingsProvider.cs
index 1f38de2d8..07f8539c5 100644
--- a/Emby.Server.Implementations/LiveTv/Listings/XmlTvListingsProvider.cs
+++ b/Emby.Server.Implementations/LiveTv/Listings/XmlTvListingsProvider.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
using System;
using System.Collections.Generic;
using System.Globalization;
@@ -31,7 +33,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings
public XmlTvListingsProvider(
IServerConfigurationManager config,
IHttpClient httpClient,
- ILogger logger,
+ ILogger<XmlTvListingsProvider> logger,
IFileSystem fileSystem,
IZipClient zipClient)
{
@@ -81,7 +83,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings
{
CancellationToken = cancellationToken,
Url = path,
- DecompressionMethod = CompressionMethod.Gzip,
+ DecompressionMethod = CompressionMethods.Gzip,
},
HttpMethod.Get).ConfigureAwait(false))
using (var stream = res.Content)
@@ -91,12 +93,12 @@ namespace Emby.Server.Implementations.LiveTv.Listings
{
using (var gzStream = new GZipStream(stream, CompressionMode.Decompress))
{
- await gzStream.CopyToAsync(fileStream).ConfigureAwait(false);
+ await gzStream.CopyToAsync(fileStream, cancellationToken).ConfigureAwait(false);
}
}
else
{
- await stream.CopyToAsync(fileStream).ConfigureAwait(false);
+ await stream.CopyToAsync(fileStream, cancellationToken).ConfigureAwait(false);
}
}
diff --git a/Emby.Server.Implementations/LiveTv/LiveTvConfigurationFactory.cs b/Emby.Server.Implementations/LiveTv/LiveTvConfigurationFactory.cs
index f9b274acb..ba916af38 100644
--- a/Emby.Server.Implementations/LiveTv/LiveTvConfigurationFactory.cs
+++ b/Emby.Server.Implementations/LiveTv/LiveTvConfigurationFactory.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
using System.Collections.Generic;
using MediaBrowser.Common.Configuration;
using MediaBrowser.Model.LiveTv;
diff --git a/Emby.Server.Implementations/LiveTv/LiveTvDtoService.cs b/Emby.Server.Implementations/LiveTv/LiveTvDtoService.cs
index e584664c9..a59c1090e 100644
--- a/Emby.Server.Implementations/LiveTv/LiveTvDtoService.cs
+++ b/Emby.Server.Implementations/LiveTv/LiveTvDtoService.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
using System;
using System.Globalization;
using System.Linq;
@@ -20,9 +22,12 @@ namespace Emby.Server.Implementations.LiveTv
{
public class LiveTvDtoService
{
+ private const string InternalVersionNumber = "4";
+
+ private const string ServiceName = "Emby";
+
private readonly ILogger _logger;
private readonly IImageProcessor _imageProcessor;
-
private readonly IDtoService _dtoService;
private readonly IApplicationHost _appHost;
private readonly ILibraryManager _libraryManager;
@@ -30,13 +35,13 @@ namespace Emby.Server.Implementations.LiveTv
public LiveTvDtoService(
IDtoService dtoService,
IImageProcessor imageProcessor,
- ILoggerFactory loggerFactory,
+ ILogger<LiveTvDtoService> logger,
IApplicationHost appHost,
ILibraryManager libraryManager)
{
_dtoService = dtoService;
_imageProcessor = imageProcessor;
- _logger = loggerFactory.CreateLogger(nameof(LiveTvDtoService));
+ _logger = logger;
_appHost = appHost;
_libraryManager = libraryManager;
}
@@ -159,7 +164,6 @@ namespace Emby.Server.Implementations.LiveTv
Limit = 1,
ImageTypes = new ImageType[] { ImageType.Thumb },
DtoOptions = new DtoOptions(false)
-
}).FirstOrDefault();
if (librarySeries != null)
@@ -177,6 +181,7 @@ namespace Emby.Server.Implementations.LiveTv
_logger.LogError(ex, "Error");
}
}
+
image = librarySeries.GetImageInfo(ImageType.Backdrop, 0);
if (image != null)
{
@@ -197,13 +202,12 @@ namespace Emby.Server.Implementations.LiveTv
var program = _libraryManager.GetItemList(new InternalItemsQuery
{
- IncludeItemTypes = new string[] { typeof(LiveTvProgram).Name },
+ IncludeItemTypes = new string[] { nameof(LiveTvProgram) },
ExternalSeriesId = programSeriesId,
Limit = 1,
ImageTypes = new ImageType[] { ImageType.Primary },
DtoOptions = new DtoOptions(false),
Name = string.IsNullOrEmpty(programSeriesId) ? seriesName : null
-
}).FirstOrDefault();
if (program != null)
@@ -230,9 +234,10 @@ namespace Emby.Server.Implementations.LiveTv
try
{
dto.ParentBackdropImageTags = new string[]
- {
+ {
_imageProcessor.GetImageCacheTag(program, image)
- };
+ };
+
dto.ParentBackdropItemId = program.Id.ToString("N", CultureInfo.InvariantCulture);
}
catch (Exception ex)
@@ -253,7 +258,6 @@ namespace Emby.Server.Implementations.LiveTv
Limit = 1,
ImageTypes = new ImageType[] { ImageType.Thumb },
DtoOptions = new DtoOptions(false)
-
}).FirstOrDefault();
if (librarySeries != null)
@@ -271,6 +275,7 @@ namespace Emby.Server.Implementations.LiveTv
_logger.LogError(ex, "Error");
}
}
+
image = librarySeries.GetImageInfo(ImageType.Backdrop, 0);
if (image != null)
{
@@ -296,7 +301,6 @@ namespace Emby.Server.Implementations.LiveTv
Limit = 1,
ImageTypes = new ImageType[] { ImageType.Primary },
DtoOptions = new DtoOptions(false)
-
}).FirstOrDefault();
if (program == null)
@@ -309,7 +313,6 @@ namespace Emby.Server.Implementations.LiveTv
ImageTypes = new ImageType[] { ImageType.Primary },
DtoOptions = new DtoOptions(false),
Name = string.IsNullOrEmpty(programSeriesId) ? seriesName : null
-
}).FirstOrDefault();
}
@@ -394,8 +397,6 @@ namespace Emby.Server.Implementations.LiveTv
return null;
}
- private const string InternalVersionNumber = "4";
-
public Guid GetInternalChannelId(string serviceName, string externalId)
{
var name = serviceName + externalId + InternalVersionNumber;
@@ -403,7 +404,6 @@ namespace Emby.Server.Implementations.LiveTv
return _libraryManager.GetNewItemId(name.ToLowerInvariant(), typeof(LiveTvChannel));
}
- private const string ServiceName = "Emby";
public string GetInternalTimerId(string externalId)
{
var name = ServiceName + externalId + InternalVersionNumber;
diff --git a/Emby.Server.Implementations/LiveTv/LiveTvManager.cs b/Emby.Server.Implementations/LiveTv/LiveTvManager.cs
index e3f9df35a..1b10f2d27 100644
--- a/Emby.Server.Implementations/LiveTv/LiveTvManager.cs
+++ b/Emby.Server.Implementations/LiveTv/LiveTvManager.cs
@@ -1,4 +1,3 @@
-#pragma warning disable SA1600
#pragma warning disable CS1591
using System;
@@ -42,33 +41,32 @@ namespace Emby.Server.Implementations.LiveTv
/// </summary>
public class LiveTvManager : ILiveTvManager, IDisposable
{
+ private const string ExternalServiceTag = "ExternalServiceId";
+
+ private const string EtagKey = "ProgramEtag";
+
private readonly IServerConfigurationManager _config;
private readonly ILogger _logger;
private readonly IItemRepository _itemRepo;
private readonly IUserManager _userManager;
+ private readonly IDtoService _dtoService;
private readonly IUserDataManager _userDataManager;
private readonly ILibraryManager _libraryManager;
private readonly ITaskManager _taskManager;
- private readonly IJsonSerializer _jsonSerializer;
- private readonly Func<IChannelManager> _channelManager;
-
- private readonly IDtoService _dtoService;
private readonly ILocalizationManager _localization;
-
+ private readonly IJsonSerializer _jsonSerializer;
+ private readonly IFileSystem _fileSystem;
+ private readonly IChannelManager _channelManager;
private readonly LiveTvDtoService _tvDtoService;
private ILiveTvService[] _services = Array.Empty<ILiveTvService>();
-
private ITunerHost[] _tunerHosts = Array.Empty<ITunerHost>();
private IListingsProvider[] _listingProviders = Array.Empty<IListingsProvider>();
- private readonly IFileSystem _fileSystem;
public LiveTvManager(
- IServerApplicationHost appHost,
IServerConfigurationManager config,
- ILoggerFactory loggerFactory,
+ ILogger<LiveTvManager> logger,
IItemRepository itemRepo,
- IImageProcessor imageProcessor,
IUserDataManager userDataManager,
IDtoService dtoService,
IUserManager userManager,
@@ -77,10 +75,11 @@ namespace Emby.Server.Implementations.LiveTv
ILocalizationManager localization,
IJsonSerializer jsonSerializer,
IFileSystem fileSystem,
- Func<IChannelManager> channelManager)
+ IChannelManager channelManager,
+ LiveTvDtoService liveTvDtoService)
{
_config = config;
- _logger = loggerFactory.CreateLogger(nameof(LiveTvManager));
+ _logger = logger;
_itemRepo = itemRepo;
_userManager = userManager;
_libraryManager = libraryManager;
@@ -91,8 +90,7 @@ namespace Emby.Server.Implementations.LiveTv
_dtoService = dtoService;
_userDataManager = userDataManager;
_channelManager = channelManager;
-
- _tvDtoService = new LiveTvDtoService(dtoService, imageProcessor, loggerFactory, appHost, _libraryManager);
+ _tvDtoService = liveTvDtoService;
}
public event EventHandler<GenericEventArgs<TimerEventInfo>> SeriesTimerCancelled;
@@ -179,7 +177,6 @@ namespace Emby.Server.Implementations.LiveTv
{
Name = i.Name,
Id = i.Type
-
}).ToList();
}
@@ -262,6 +259,7 @@ namespace Emby.Server.Implementations.LiveTv
var endTime = DateTime.UtcNow;
_logger.LogInformation("Live stream opened after {0}ms", (endTime - startTime).TotalMilliseconds);
}
+
info.RequiresClosing = true;
var idPrefix = service.GetType().FullName.GetMD5().ToString("N", CultureInfo.InvariantCulture) + "_";
@@ -363,30 +361,37 @@ namespace Emby.Server.Implementations.LiveTv
{
stream.BitRate = null;
}
+
if (stream.Channels.HasValue && stream.Channels <= 0)
{
stream.Channels = null;
}
+
if (stream.AverageFrameRate.HasValue && stream.AverageFrameRate <= 0)
{
stream.AverageFrameRate = null;
}
+
if (stream.RealFrameRate.HasValue && stream.RealFrameRate <= 0)
{
stream.RealFrameRate = null;
}
+
if (stream.Width.HasValue && stream.Width <= 0)
{
stream.Width = null;
}
+
if (stream.Height.HasValue && stream.Height <= 0)
{
stream.Height = null;
}
+
if (stream.SampleRate.HasValue && stream.SampleRate <= 0)
{
stream.SampleRate = null;
}
+
if (stream.Level.HasValue && stream.Level <= 0)
{
stream.Level = null;
@@ -428,7 +433,6 @@ namespace Emby.Server.Implementations.LiveTv
}
}
- private const string ExternalServiceTag = "ExternalServiceId";
private LiveTvChannel GetChannel(ChannelInfo channelInfo, string serviceName, BaseItem parentFolder, CancellationToken cancellationToken)
{
var parentFolderId = parentFolder.Id;
@@ -457,6 +461,7 @@ namespace Emby.Server.Implementations.LiveTv
{
isNew = true;
}
+
item.Tags = channelInfo.Tags;
}
@@ -464,6 +469,7 @@ namespace Emby.Server.Implementations.LiveTv
{
isNew = true;
}
+
item.ParentId = parentFolderId;
item.ChannelType = channelInfo.ChannelType;
@@ -473,24 +479,28 @@ namespace Emby.Server.Implementations.LiveTv
{
forceUpdate = true;
}
+
item.SetProviderId(ExternalServiceTag, serviceName);
if (!string.Equals(channelInfo.Id, item.ExternalId, StringComparison.Ordinal))
{
forceUpdate = true;
}
+
item.ExternalId = channelInfo.Id;
if (!string.Equals(channelInfo.Number, item.Number, StringComparison.Ordinal))
{
forceUpdate = true;
}
+
item.Number = channelInfo.Number;
if (!string.Equals(channelInfo.Name, item.Name, StringComparison.Ordinal))
{
forceUpdate = true;
}
+
item.Name = channelInfo.Name;
if (!item.HasImage(ImageType.Primary))
@@ -519,8 +529,6 @@ namespace Emby.Server.Implementations.LiveTv
return item;
}
- private const string EtagKey = "ProgramEtag";
-
private Tuple<LiveTvProgram, bool, bool> GetProgram(ProgramInfo info, Dictionary<Guid, LiveTvProgram> allExistingPrograms, LiveTvChannel channel, ChannelType channelType, string serviceName, CancellationToken cancellationToken)
{
var id = _tvDtoService.GetInternalProgramId(info.Id);
@@ -2483,7 +2491,7 @@ namespace Emby.Server.Implementations.LiveTv
.OrderBy(i => i.SortName)
.ToList();
- folders.AddRange(_channelManager().GetChannelsInternal(new MediaBrowser.Model.Channels.ChannelQuery
+ folders.AddRange(_channelManager.GetChannelsInternal(new MediaBrowser.Model.Channels.ChannelQuery
{
UserId = user.Id,
IsRecordingsFolder = true,
diff --git a/Emby.Server.Implementations/LiveTv/LiveTvMediaSourceProvider.cs b/Emby.Server.Implementations/LiveTv/LiveTvMediaSourceProvider.cs
index 52d60c004..7f63991d0 100644
--- a/Emby.Server.Implementations/LiveTv/LiveTvMediaSourceProvider.cs
+++ b/Emby.Server.Implementations/LiveTv/LiveTvMediaSourceProvider.cs
@@ -1,52 +1,47 @@
+#pragma warning disable CS1591
+
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
-using MediaBrowser.Common.Configuration;
using MediaBrowser.Controller;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.LiveTv;
-using MediaBrowser.Controller.MediaEncoding;
using MediaBrowser.Model.Dto;
using MediaBrowser.Model.MediaInfo;
-using MediaBrowser.Model.Serialization;
using Microsoft.Extensions.Logging;
namespace Emby.Server.Implementations.LiveTv
{
public class LiveTvMediaSourceProvider : IMediaSourceProvider
{
+ // Do not use a pipe here because Roku http requests to the server will fail, without any explicit error message.
+ private const char StreamIdDelimeter = '_';
+ private const string StreamIdDelimeterString = "_";
+
private readonly ILiveTvManager _liveTvManager;
- private readonly IJsonSerializer _jsonSerializer;
private readonly ILogger _logger;
private readonly IMediaSourceManager _mediaSourceManager;
- private readonly IMediaEncoder _mediaEncoder;
private readonly IServerApplicationHost _appHost;
- private IApplicationPaths _appPaths;
- public LiveTvMediaSourceProvider(ILiveTvManager liveTvManager, IApplicationPaths appPaths, IJsonSerializer jsonSerializer, ILoggerFactory loggerFactory, IMediaSourceManager mediaSourceManager, IMediaEncoder mediaEncoder, IServerApplicationHost appHost)
+ public LiveTvMediaSourceProvider(ILiveTvManager liveTvManager, ILogger<LiveTvMediaSourceProvider> logger, IMediaSourceManager mediaSourceManager, IServerApplicationHost appHost)
{
_liveTvManager = liveTvManager;
- _jsonSerializer = jsonSerializer;
+ _logger = logger;
_mediaSourceManager = mediaSourceManager;
- _mediaEncoder = mediaEncoder;
_appHost = appHost;
- _logger = loggerFactory.CreateLogger(GetType().Name);
- _appPaths = appPaths;
}
public Task<IEnumerable<MediaSourceInfo>> GetMediaSources(BaseItem item, CancellationToken cancellationToken)
{
- var baseItem = (BaseItem)item;
-
- if (baseItem.SourceType == SourceType.LiveTV)
+ if (item.SourceType == SourceType.LiveTV)
{
var activeRecordingInfo = _liveTvManager.GetActiveRecordingInfo(item.Path);
- if (string.IsNullOrEmpty(baseItem.Path) || activeRecordingInfo != null)
+ if (string.IsNullOrEmpty(item.Path) || activeRecordingInfo != null)
{
return GetMediaSourcesInternal(item, activeRecordingInfo, cancellationToken);
}
@@ -55,10 +50,6 @@ namespace Emby.Server.Implementations.LiveTv
return Task.FromResult<IEnumerable<MediaSourceInfo>>(Array.Empty<MediaSourceInfo>());
}
- // Do not use a pipe here because Roku http requests to the server will fail, without any explicit error message.
- private const char StreamIdDelimeter = '_';
- private const string StreamIdDelimeterString = "_";
-
private async Task<IEnumerable<MediaSourceInfo>> GetMediaSourcesInternal(BaseItem item, ActiveRecordingInfo activeRecordingInfo, CancellationToken cancellationToken)
{
IEnumerable<MediaSourceInfo> sources;
@@ -91,7 +82,7 @@ namespace Emby.Server.Implementations.LiveTv
foreach (var source in list)
{
source.Type = MediaSourceType.Default;
- source.BufferMs = source.BufferMs ?? 1500;
+ source.BufferMs ??= 1500;
if (source.RequiresOpening || forceRequireOpening)
{
@@ -100,11 +91,14 @@ namespace Emby.Server.Implementations.LiveTv
if (source.RequiresOpening)
{
- var openKeys = new List<string>();
- openKeys.Add(item.GetType().Name);
- openKeys.Add(item.Id.ToString("N", CultureInfo.InvariantCulture));
- openKeys.Add(source.Id ?? string.Empty);
- source.OpenToken = string.Join(StreamIdDelimeterString, openKeys.ToArray());
+ var openKeys = new List<string>
+ {
+ item.GetType().Name,
+ item.Id.ToString("N", CultureInfo.InvariantCulture),
+ source.Id ?? string.Empty
+ };
+
+ source.OpenToken = string.Join(StreamIdDelimeterString, openKeys);
}
// Dummy this up so that direct play checks can still run
@@ -114,11 +108,12 @@ namespace Emby.Server.Implementations.LiveTv
}
}
- _logger.LogDebug("MediaSources: {0}", _jsonSerializer.SerializeToString(list));
+ _logger.LogDebug("MediaSources: {@MediaSources}", list);
return list;
}
+ /// <inheritdoc />
public async Task<ILiveStream> OpenMediaSource(string openToken, List<ILiveStream> currentLiveStreams, CancellationToken cancellationToken)
{
var keys = openToken.Split(new[] { StreamIdDelimeter }, 3);
diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/BaseTunerHost.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/BaseTunerHost.cs
index 715f600a1..80ee1ee33 100644
--- a/Emby.Server.Implementations/LiveTv/TunerHosts/BaseTunerHost.cs
+++ b/Emby.Server.Implementations/LiveTv/TunerHosts/BaseTunerHost.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs
index 06f27fa3e..25b2c674c 100644
--- a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs
+++ b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
using System;
using System.Collections.Generic;
using System.Globalization;
@@ -36,7 +38,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
public HdHomerunHost(
IServerConfigurationManager config,
- ILogger logger,
+ ILogger<HdHomerunHost> logger,
IJsonSerializer jsonSerializer,
IFileSystem fileSystem,
IHttpClient httpClient,
diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunManager.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunManager.cs
index 9702392b2..57c5b7500 100644
--- a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunManager.cs
+++ b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunManager.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
using System;
using System.Buffers;
using System.Collections.Generic;
diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunUdpStream.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunUdpStream.cs
index 649becbd3..d89a816b3 100644
--- a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunUdpStream.cs
+++ b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunUdpStream.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
using System;
using System.Collections.Generic;
using System.IO;
@@ -5,8 +7,8 @@ using System.Net;
using System.Net.Sockets;
using System.Threading;
using System.Threading.Tasks;
-using MediaBrowser.Common.Net;
using MediaBrowser.Common.Configuration;
+using MediaBrowser.Common.Net;
using MediaBrowser.Controller;
using MediaBrowser.Controller.Library;
using MediaBrowser.Model.Dto;
@@ -119,7 +121,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
//OpenedMediaSource.Path = tempFile;
//OpenedMediaSource.ReadAtNativeFramerate = true;
- MediaSource.Path = _appHost.GetLocalApiUrl("127.0.0.1") + "/LiveTv/LiveStreamFiles/" + UniqueId + "/stream.ts";
+ MediaSource.Path = _appHost.GetLocalApiUrl("127.0.0.1", true) + "/LiveTv/LiveStreamFiles/" + UniqueId + "/stream.ts";
MediaSource.Protocol = MediaProtocol.Http;
//OpenedMediaSource.SupportsDirectPlay = false;
//OpenedMediaSource.SupportsDirectStream = true;
diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/LiveStream.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/LiveStream.cs
index 1d55e7992..4e4f1d7f6 100644
--- a/Emby.Server.Implementations/LiveTv/TunerHosts/LiveStream.cs
+++ b/Emby.Server.Implementations/LiveTv/TunerHosts/LiveStream.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
using System;
using System.Collections.Generic;
using System.Globalization;
@@ -5,8 +7,8 @@ using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
-using MediaBrowser.Controller.Library;
using MediaBrowser.Common.Configuration;
+using MediaBrowser.Controller.Library;
using MediaBrowser.Model.Dto;
using MediaBrowser.Model.IO;
using MediaBrowser.Model.LiveTv;
@@ -96,7 +98,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
FileMode.Open,
FileAccess.Read,
FileShare.ReadWrite,
- StreamDefaults.DefaultFileStreamBufferSize,
+ IODefaults.FileStreamBufferSize,
allowAsyncFileRead ? FileOptions.SequentialScan | FileOptions.Asynchronous : FileOptions.SequentialScan);
public Task DeleteTempFiles()
@@ -199,7 +201,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
await StreamHelper.CopyToAsync(
inputStream,
stream,
- StreamDefaults.DefaultCopyToBufferSize,
+ IODefaults.CopyToBufferSize,
emptyReadLimit,
cancellationToken).ConfigureAwait(false);
}
diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs
index df054f1eb..f5dda79db 100644
--- a/Emby.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs
+++ b/Emby.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
using System;
using System.Collections.Generic;
using System.Globalization;
diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/M3uParser.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/M3uParser.cs
index 3d2267e75..59451fccd 100644
--- a/Emby.Server.Implementations/LiveTv/TunerHosts/M3uParser.cs
+++ b/Emby.Server.Implementations/LiveTv/TunerHosts/M3uParser.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
using System;
using System.Collections.Generic;
using System.Globalization;
@@ -10,7 +12,6 @@ using MediaBrowser.Common.Extensions;
using MediaBrowser.Common.Net;
using MediaBrowser.Controller;
using MediaBrowser.Controller.LiveTv;
-using MediaBrowser.Model.Extensions;
using Microsoft.Extensions.Logging;
namespace Emby.Server.Implementations.LiveTv.TunerHosts
diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/SharedHttpStream.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/SharedHttpStream.cs
index 0d94f4b32..0e600202a 100644
--- a/Emby.Server.Implementations/LiveTv/TunerHosts/SharedHttpStream.cs
+++ b/Emby.Server.Implementations/LiveTv/TunerHosts/SharedHttpStream.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
using System;
using System.Collections.Generic;
using System.IO;
@@ -57,7 +59,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
Url = url,
CancellationToken = CancellationToken.None,
BufferContent = false,
- DecompressionMethod = CompressionMethod.None
+ DecompressionMethod = CompressionMethods.None
};
foreach (var header in mediaSource.RequiredHttpHeaders)
@@ -104,7 +106,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
//OpenedMediaSource.Path = tempFile;
//OpenedMediaSource.ReadAtNativeFramerate = true;
- MediaSource.Path = _appHost.GetLocalApiUrl("127.0.0.1") + "/LiveTv/LiveStreamFiles/" + UniqueId + "/stream.ts";
+ MediaSource.Path = _appHost.GetLocalApiUrl("127.0.0.1", true) + "/LiveTv/LiveStreamFiles/" + UniqueId + "/stream.ts";
MediaSource.Protocol = MediaProtocol.Http;
//OpenedMediaSource.Path = TempFilePath;
@@ -127,12 +129,12 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
Logger.LogInformation("Beginning {0} stream to {1}", GetType().Name, TempFilePath);
using (response)
using (var stream = response.Content)
- using (var fileStream = FileSystem.GetFileStream(TempFilePath, FileOpenMode.Create, FileAccessMode.Write, FileShareMode.Read, FileOpenOptions.None))
+ using (var fileStream = new FileStream(TempFilePath, FileMode.Create, FileAccess.Write, FileShare.Read))
{
await StreamHelper.CopyToAsync(
stream,
fileStream,
- StreamDefaults.DefaultCopyToBufferSize,
+ IODefaults.CopyToBufferSize,
() => Resolve(openTaskCompletionSource),
cancellationToken).ConfigureAwait(false);
}
diff --git a/Emby.Server.Implementations/Localization/Core/af.json b/Emby.Server.Implementations/Localization/Core/af.json
index dcec26801..1363eaf85 100644
--- a/Emby.Server.Implementations/Localization/Core/af.json
+++ b/Emby.Server.Implementations/Localization/Core/af.json
@@ -41,7 +41,6 @@
"User": "Gebruiker",
"TvShows": "TV Programme",
"System": "Stelsel",
- "SubtitlesDownloadedForItem": "Ondertitels afgelaai vir {0}",
"SubtitleDownloadFailureFromForItem": "Ondertitels het misluk om af te laai van {0} vir {1}",
"StartupEmbyServerIsLoading": "Jellyfin Bediener is besig om te laai. Probeer weer in 'n kort tyd.",
"ServerNameNeedsToBeRestarted": "{0} moet herbegin word",
diff --git a/Emby.Server.Implementations/Localization/Core/ar.json b/Emby.Server.Implementations/Localization/Core/ar.json
index 4da3cdd3b..f313039a6 100644
--- a/Emby.Server.Implementations/Localization/Core/ar.json
+++ b/Emby.Server.Implementations/Localization/Core/ar.json
@@ -1,23 +1,23 @@
{
"Albums": "ألبومات",
"AppDeviceValues": "تطبيق: {0}, جهاز: {1}",
- "Application": "التطبيق",
- "Artists": "الفنان",
+ "Application": "تطبيق",
+ "Artists": "الفنانين",
"AuthenticationSucceededWithUserName": "{0} سجل الدخول بنجاح",
- "Books": "كتب",
+ "Books": "الكتب",
"CameraImageUploadedFrom": "صورة كاميرا جديدة تم رفعها من {0}",
"Channels": "القنوات",
- "ChapterNameValue": "الباب {0}",
+ "ChapterNameValue": "الفصل {0}",
"Collections": "مجموعات",
- "DeviceOfflineWithName": "تم قطع الاتصال بـ{0}",
+ "DeviceOfflineWithName": "قُطِع الاتصال بـ{0}",
"DeviceOnlineWithName": "{0} متصل",
"FailedLoginAttemptWithUserName": "عملية تسجيل الدخول فشلت من {0}",
- "Favorites": "التفضيلات",
+ "Favorites": "المفضلة",
"Folders": "المجلدات",
- "Genres": "أنواع الأفلام",
+ "Genres": "الأنواع",
"HeaderAlbumArtists": "فناني الألبومات",
"HeaderCameraUploads": "تحميلات الكاميرا",
- "HeaderContinueWatching": "استئناف المشاهدة",
+ "HeaderContinueWatching": "استئناف",
"HeaderFavoriteAlbums": "الألبومات المفضلة",
"HeaderFavoriteArtists": "الفنانون المفضلون",
"HeaderFavoriteEpisodes": "الحلقات المفضلة",
@@ -31,28 +31,28 @@
"ItemAddedWithName": "تم إضافة {0} للمكتبة",
"ItemRemovedWithName": "تم إزالة {0} من المكتبة",
"LabelIpAddressValue": "عنوان الآي بي: {0}",
- "LabelRunningTimeValue": "وقت التشغيل: {0}",
+ "LabelRunningTimeValue": "المدة: {0}",
"Latest": "الأحدث",
- "MessageApplicationUpdated": "لقد تم تحديث خادم أمبي",
+ "MessageApplicationUpdated": "لقد تم تحديث خادم Jellyfin",
"MessageApplicationUpdatedTo": "تم تحديث سيرفر Jellyfin الى {0}",
"MessageNamedServerConfigurationUpdatedWithValue": "تم تحديث إعدادات الخادم في قسم {0}",
"MessageServerConfigurationUpdated": "تم تحديث إعدادات الخادم",
- "MixedContent": "محتوى مخلوط",
+ "MixedContent": "محتوى مختلط",
"Movies": "الأفلام",
"Music": "الموسيقى",
"MusicVideos": "الفيديوهات الموسيقية",
"NameInstallFailed": "فشل التثبيت {0}",
"NameSeasonNumber": "الموسم {0}",
"NameSeasonUnknown": "الموسم غير معروف",
- "NewVersionIsAvailable": "نسخة حديثة من سيرفر Jellyfin متوفرة للتحميل .",
+ "NewVersionIsAvailable": "نسخة جديدة من سيرفر Jellyfin متوفرة للتحميل.",
"NotificationOptionApplicationUpdateAvailable": "يوجد تحديث للتطبيق",
"NotificationOptionApplicationUpdateInstalled": "تم تحديث التطبيق",
"NotificationOptionAudioPlayback": "بدأ تشغيل المقطع الصوتي",
"NotificationOptionAudioPlaybackStopped": "تم إيقاف تشغيل المقطع الصوتي",
- "NotificationOptionCameraImageUploaded": "تم رقع صورة الكاميرا",
+ "NotificationOptionCameraImageUploaded": "تم رفع صورة الكاميرا",
"NotificationOptionInstallationFailed": "فشل في التثبيت",
"NotificationOptionNewLibraryContent": "تم إضافة محتوى جديد",
- "NotificationOptionPluginError": "فشل في الملحق",
+ "NotificationOptionPluginError": "فشل في البرنامج المضاف",
"NotificationOptionPluginInstalled": "تم تثبيت الملحق",
"NotificationOptionPluginUninstalled": "تمت إزالة الملحق",
"NotificationOptionPluginUpdateInstalled": "تم تثبيت تحديثات الملحق",
@@ -75,8 +75,7 @@
"Songs": "الأغاني",
"StartupEmbyServerIsLoading": "سيرفر Jellyfin قيد التشغيل . الرجاء المحاولة بعد قليل.",
"SubtitleDownloadFailureForItem": "عملية إنزال الترجمة فشلت لـ{0}",
- "SubtitleDownloadFailureFromForItem": "الترجمات فشلت في التحميل من {0} لـ {1}",
- "SubtitlesDownloadedForItem": "تم تحميل الترجمات لـ {0}",
+ "SubtitleDownloadFailureFromForItem": "الترجمات فشلت في التحميل من {0} الى {1}",
"Sync": "مزامنة",
"System": "النظام",
"TvShows": "البرامج التلفزيونية",
@@ -88,10 +87,32 @@
"UserOfflineFromDevice": "تم قطع اتصال {0} من {1}",
"UserOnlineFromDevice": "{0} متصل عبر {1}",
"UserPasswordChangedWithName": "تم تغيير كلمة السر للمستخدم {0}",
- "UserPolicyUpdatedWithName": "سياسة المستخدمين تم تحديثها لـ {0}",
+ "UserPolicyUpdatedWithName": "تم تحديث سياسة المستخدم {0}",
"UserStartedPlayingItemWithValues": "قام {0} ببدء تشغيل {1} على {2}",
"UserStoppedPlayingItemWithValues": "قام {0} بإيقاف تشغيل {1} على {2}",
- "ValueHasBeenAddedToLibrary": "{0} تم اضافتها الى مكتبة الوسائط",
- "ValueSpecialEpisodeName": "مميز - {0}",
- "VersionNumber": "الإصدار رقم {0}"
+ "ValueHasBeenAddedToLibrary": "تمت اضافت {0} إلى مكتبة الوسائط",
+ "ValueSpecialEpisodeName": "خاص - {0}",
+ "VersionNumber": "النسخة {0}",
+ "TaskCleanCacheDescription": "يحذف ملفات ذاكرة التخزين المؤقت التي لم يعد النظام بحاجة إليها.",
+ "TaskCleanCache": "احذف مجلد ذاكرة التخزين المؤقت",
+ "TasksChannelsCategory": "قنوات الإنترنت",
+ "TasksLibraryCategory": "مكتبة",
+ "TasksMaintenanceCategory": "صيانة",
+ "TaskRefreshLibraryDescription": "يقوم بفصح مكتبة الوسائط الخاصة بك بحثًا عن ملفات جديدة وتحديث البيانات الوصفية.",
+ "TaskRefreshLibrary": "افحص مكتبة الوسائط",
+ "TaskRefreshChapterImagesDescription": "إنشاء صور مصغرة لمقاطع الفيديو ذات فصول.",
+ "TaskRefreshChapterImages": "استخراج صور الفصل",
+ "TasksApplicationCategory": "تطبيق",
+ "TaskDownloadMissingSubtitlesDescription": "ابحث في الإنترنت على الترجمات المفقودة إستنادا على الميتاداتا.",
+ "TaskDownloadMissingSubtitles": "تحميل الترجمات المفقودة",
+ "TaskRefreshChannelsDescription": "تحديث معلومات قنوات الإنترنت.",
+ "TaskRefreshChannels": "إعادة تحديث القنوات",
+ "TaskCleanTranscodeDescription": "حذف ملفات الترميز الأقدم من يوم واحد.",
+ "TaskCleanTranscode": "حذف سجلات الترميز",
+ "TaskUpdatePluginsDescription": "تحميل وتثبيت الإضافات التي تم تفعيل التحديث التلقائي لها.",
+ "TaskUpdatePlugins": "تحديث الإضافات",
+ "TaskRefreshPeopleDescription": "تحديث البيانات الوصفية للممثلين والمخرجين في مكتبة الوسائط الخاصة بك.",
+ "TaskRefreshPeople": "إعادة تحميل الأشخاص",
+ "TaskCleanLogsDescription": "حذف السجلات الأقدم من {0} يوم.",
+ "TaskCleanLogs": "حذف دليل السجل"
}
diff --git a/Emby.Server.Implementations/Localization/Core/bg-BG.json b/Emby.Server.Implementations/Localization/Core/bg-BG.json
index 46c10d912..3fc7c7dc0 100644
--- a/Emby.Server.Implementations/Localization/Core/bg-BG.json
+++ b/Emby.Server.Implementations/Localization/Core/bg-BG.json
@@ -1,8 +1,8 @@
{
"Albums": "Албуми",
- "AppDeviceValues": "Програма: {0}, устройство: {1}",
+ "AppDeviceValues": "Програма: {0}, Устройство: {1}",
"Application": "Програма",
- "Artists": "Изпълнители",
+ "Artists": "Артисти",
"AuthenticationSucceededWithUserName": "{0} се удостовери успешно",
"Books": "Книги",
"CameraImageUploadedFrom": "Нова снимка от камера беше качена от {0}",
@@ -16,7 +16,7 @@
"Folders": "Папки",
"Genres": "Жанрове",
"HeaderAlbumArtists": "Изпълнители на албуми",
- "HeaderCameraUploads": "",
+ "HeaderCameraUploads": "Качени от камера",
"HeaderContinueWatching": "Продължаване на гледането",
"HeaderFavoriteAlbums": "Любими албуми",
"HeaderFavoriteArtists": "Любими изпълнители",
@@ -25,26 +25,26 @@
"HeaderFavoriteSongs": "Любими песни",
"HeaderLiveTV": "Телевизия на живо",
"HeaderNextUp": "Следва",
- "HeaderRecordingGroups": "",
+ "HeaderRecordingGroups": "Запис групи",
"HomeVideos": "Домашни клипове",
"Inherit": "Наследяване",
"ItemAddedWithName": "{0} е добавено към библиотеката",
"ItemRemovedWithName": "{0} е премахнато от библиотеката",
"LabelIpAddressValue": "ИП адрес: {0}",
- "LabelRunningTimeValue": "",
+ "LabelRunningTimeValue": "Стартирано от: {0}",
"Latest": "Последни",
"MessageApplicationUpdated": "Сървърът е обновен",
- "MessageApplicationUpdatedTo": "",
- "MessageNamedServerConfigurationUpdatedWithValue": "",
- "MessageServerConfigurationUpdated": "",
+ "MessageApplicationUpdatedTo": "Сървърът е обновен до {0}",
+ "MessageNamedServerConfigurationUpdatedWithValue": "Секцията {0} от сървърната конфигурация се актуализира",
+ "MessageServerConfigurationUpdated": "Конфигурацията на сървъра се актуализира",
"MixedContent": "Смесено съдържание",
"Movies": "Филми",
"Music": "Музика",
"MusicVideos": "Музикални клипове",
- "NameInstallFailed": "",
+ "NameInstallFailed": "{0} не можа да се инсталира",
"NameSeasonNumber": "Сезон {0}",
"NameSeasonUnknown": "Неразпознат сезон",
- "NewVersionIsAvailable": "",
+ "NewVersionIsAvailable": "Нова версия на Jellyfin сървъра е достъпна за сваляне.",
"NotificationOptionApplicationUpdateAvailable": "Налично е обновление на програмата",
"NotificationOptionApplicationUpdateInstalled": "Обновлението на програмата е инсталирано",
"NotificationOptionAudioPlayback": "Възпроизвеждането на звук започна",
@@ -58,7 +58,7 @@
"NotificationOptionPluginUpdateInstalled": "Обновлението на приставката е инсталирано",
"NotificationOptionServerRestartRequired": "Нужно е повторно пускане на сървъра",
"NotificationOptionTaskFailed": "Грешка в планирана задача",
- "NotificationOptionUserLockedOut": "",
+ "NotificationOptionUserLockedOut": "Потребителя е заключен",
"NotificationOptionVideoPlayback": "Възпроизвеждането на видео започна",
"NotificationOptionVideoPlaybackStopped": "Възпроизвеждането на видео е спряно",
"Photos": "Снимки",
@@ -70,28 +70,49 @@
"ProviderValue": "Доставчик: {0}",
"ScheduledTaskFailedWithName": "{0} се провали",
"ScheduledTaskStartedWithName": "{0} започна",
- "ServerNameNeedsToBeRestarted": "",
+ "ServerNameNeedsToBeRestarted": "{0} е нужно да се рестартира",
"Shows": "Сериали",
"Songs": "Песни",
"StartupEmbyServerIsLoading": "Сървърът зарежда. Моля, опитайте отново след малко.",
"SubtitleDownloadFailureForItem": "Неуспешно изтегляне на субтитри за {0}",
- "SubtitleDownloadFailureFromForItem": "",
- "SubtitlesDownloadedForItem": "Изтеглени са субтитри за {0}",
+ "SubtitleDownloadFailureFromForItem": "Поднадписите за {1} от {0} не можаха да се изтеглят",
"Sync": "Синхронизиране",
"System": "Система",
"TvShows": "Телевизионни сериали",
"User": "Потребител",
"UserCreatedWithName": "Потребителят {0} е създаден",
"UserDeletedWithName": "Потребителят {0} е изтрит",
- "UserDownloadingItemWithValues": "",
- "UserLockedOutWithName": "",
+ "UserDownloadingItemWithValues": "{0} изтегля {1}",
+ "UserLockedOutWithName": "Потребител {0} се заключи",
"UserOfflineFromDevice": "{0} се разкачи от {1}",
"UserOnlineFromDevice": "{0} е на линия от {1}",
"UserPasswordChangedWithName": "Паролата на потребителя {0} е променена",
- "UserPolicyUpdatedWithName": "",
+ "UserPolicyUpdatedWithName": "Потребителската политика за {0} се актуализира",
"UserStartedPlayingItemWithValues": "{0} пусна {1}",
"UserStoppedPlayingItemWithValues": "{0} спря {1}",
- "ValueHasBeenAddedToLibrary": "",
+ "ValueHasBeenAddedToLibrary": "{0} беше добавен във Вашата библиотека",
"ValueSpecialEpisodeName": "Специални - {0}",
- "VersionNumber": "Версия {0}"
+ "VersionNumber": "Версия {0}",
+ "TaskDownloadMissingSubtitlesDescription": "Търси Интернет за липсващи поднадписи, на база конфигурацията за мета-данни.",
+ "TaskDownloadMissingSubtitles": "Изтегляне на липсващи поднадписи",
+ "TaskRefreshChannelsDescription": "Обновява информацията за интернет канала.",
+ "TaskRefreshChannels": "Обновяване на Канали",
+ "TaskCleanTranscodeDescription": "Изтрива прекодирани файлове по-стари от един ден.",
+ "TaskCleanTranscode": "Изчиства директорията за прекодиране",
+ "TaskUpdatePluginsDescription": "Изтегля и инсталира актуализации за добавките, които са настроени за автоматична актуализация.",
+ "TaskUpdatePlugins": "Актуализира добавките",
+ "TaskRefreshPeopleDescription": "Актуализира мета-данните за артистите и режисьорите за Вашата медийна библиотека.",
+ "TaskRefreshPeople": "Обновяване на участниците",
+ "TaskCleanLogsDescription": "Изтрива лог файлове по-стари от {0} дни.",
+ "TaskCleanLogs": "Изчисти директорията с логове",
+ "TaskRefreshLibraryDescription": "Сканира Вашата библиотека с медия за нови файлове и обновява мета-данните.",
+ "TaskRefreshLibrary": "Сканиране на библиотеката с медия",
+ "TaskRefreshChapterImagesDescription": "Създава иконки за видеа, които имат епизоди.",
+ "TaskRefreshChapterImages": "Извличане на изображения за епизода",
+ "TaskCleanCacheDescription": "Изтриване на ненужните от системата файлове.",
+ "TaskCleanCache": "Изчистване на Кеш-директорията",
+ "TasksChannelsCategory": "Интернет Канали",
+ "TasksApplicationCategory": "Приложение",
+ "TasksLibraryCategory": "Библиотека",
+ "TasksMaintenanceCategory": "Поддръжка"
}
diff --git a/Emby.Server.Implementations/Localization/Core/bn.json b/Emby.Server.Implementations/Localization/Core/bn.json
new file mode 100644
index 000000000..ef7792356
--- /dev/null
+++ b/Emby.Server.Implementations/Localization/Core/bn.json
@@ -0,0 +1,95 @@
+{
+ "DeviceOnlineWithName": "{0}-এর সাথে সংযুক্ত হয়েছে",
+ "DeviceOfflineWithName": "{0}-এর সাথে সংযোগ বিচ্ছিন্ন হয়েছে",
+ "Collections": "সংকলন",
+ "ChapterNameValue": "অধ্যায় {0}",
+ "Channels": "চ্যানেল",
+ "CameraImageUploadedFrom": "একটি নতুন ক্যামেরার চিত্র আপলোড করা হয়েছে {0} থেকে",
+ "Books": "বই",
+ "AuthenticationSucceededWithUserName": "{0} যাচাই সফল",
+ "Artists": "শিল্পী",
+ "Application": "অ্যাপ্লিকেশন",
+ "Albums": "অ্যালবামগুলো",
+ "HeaderFavoriteEpisodes": "প্রিব পর্বগুলো",
+ "HeaderFavoriteArtists": "প্রিয় শিল্পীরা",
+ "HeaderFavoriteAlbums": "প্রিয় এলবামগুলো",
+ "HeaderContinueWatching": "দেখতে থাকুন",
+ "HeaderCameraUploads": "ক্যামেরার আপলোডগুলো",
+ "HeaderAlbumArtists": "এলবামের শিল্পী",
+ "Genres": "ঘরানা",
+ "Folders": "ফোল্ডারগুলো",
+ "Favorites": "ফেভারিটগুলো",
+ "FailedLoginAttemptWithUserName": "{0} থেকে লগিন করতে ব্যর্থ",
+ "AppDeviceValues": "এপ: {0}, ডিভাইস: {0}",
+ "VersionNumber": "সংস্করণ {0}",
+ "ValueSpecialEpisodeName": "বিশেষ - {0}",
+ "ValueHasBeenAddedToLibrary": "আপনার লাইব্রেরিতে {0} যোগ করা হয়েছে",
+ "UserStoppedPlayingItemWithValues": "{2}তে {1} বাজানো শেষ করেছেন {0}",
+ "UserStartedPlayingItemWithValues": "{2}তে {1} বাজাচ্ছেন {0}",
+ "UserPolicyUpdatedWithName": "{0} এর জন্য ব্যবহার নীতি আপডেট করা হয়েছে",
+ "UserPasswordChangedWithName": "ব্যবহারকারী {0} এর পাসওয়ার্ড পরিবর্তিত হয়েছে",
+ "UserOnlineFromDevice": "{0}, {1} থেকে অনলাইন",
+ "UserOfflineFromDevice": "{0} {1} থেকে বিযুক্ত হয়ে গেছে",
+ "UserLockedOutWithName": "ব্যবহারকারী {0} ঢুকতে পারছে না",
+ "UserDownloadingItemWithValues": "{0}, {1} ডাউনলোড করছে",
+ "UserDeletedWithName": "ব্যবহারকারী {0}কে বাদ দেয়া হয়েছে",
+ "UserCreatedWithName": "ব্যবহারকারী {0} সৃষ্টি করা হয়েছে",
+ "User": "ব্যবহারকারী",
+ "TvShows": "টিভি শোগুলো",
+ "System": "সিস্টেম",
+ "Sync": "সিংক",
+ "SubtitleDownloadFailureFromForItem": "{2} থেকে {1} এর জন্য সাবটাইটেল ডাউনলোড ব্যর্থ",
+ "StartupEmbyServerIsLoading": "জেলিফিন সার্ভার লোড হচ্ছে। দয়া করে একটু পরে আবার চেষ্টা করুন।",
+ "Songs": "গানগুলো",
+ "Shows": "টিভি পর্ব",
+ "ServerNameNeedsToBeRestarted": "{0} রিস্টার্ট করা প্রয়োজন",
+ "ScheduledTaskStartedWithName": "{0} শুরু হয়েছে",
+ "ScheduledTaskFailedWithName": "{0} ব্যর্থ",
+ "ProviderValue": "প্রদানকারী: {0}",
+ "PluginUpdatedWithName": "{0} আপডেট করা হয়েছে",
+ "PluginUninstalledWithName": "{0} বাদ দেয়া হয়েছে",
+ "PluginInstalledWithName": "{0} ইন্সটল করা হয়েছে",
+ "Plugin": "প্লাগিন",
+ "Playlists": "প্লেলিস্ট",
+ "Photos": "ছবিগুলো",
+ "NotificationOptionVideoPlaybackStopped": "ভিডিও চলা বন্ধ",
+ "NotificationOptionVideoPlayback": "ভিডিও চলা শুরু হয়েছে",
+ "NotificationOptionUserLockedOut": "ব্যবহারকারী ঢুকতে পারছে না",
+ "NotificationOptionTaskFailed": "পরিকল্পিত কাজটি ব্যর্থ",
+ "NotificationOptionServerRestartRequired": "সার্ভার রিস্টার্ট বাধ্যতামূলক",
+ "NotificationOptionPluginUpdateInstalled": "প্লাগিন আপডেট ইন্সটল করা হয়েছে",
+ "NotificationOptionPluginUninstalled": "প্লাগিন বাদ দেয়া হয়েছে",
+ "NotificationOptionPluginInstalled": "প্লাগিন ইন্সটল করা হয়েছে",
+ "NotificationOptionPluginError": "প্লাগিন ব্যর্থ",
+ "NotificationOptionNewLibraryContent": "নতুন কন্টেন্ট যোগ করা হয়েছে",
+ "NotificationOptionInstallationFailed": "ইন্সটল ব্যর্থ",
+ "NotificationOptionCameraImageUploaded": "ক্যামেরার ছবি আপলোড হয়েছে",
+ "NotificationOptionAudioPlaybackStopped": "গান বাজা বন্ধ হয়েছে",
+ "NotificationOptionAudioPlayback": "গান বাজা শুরু হয়েছে",
+ "NotificationOptionApplicationUpdateInstalled": "এপ্লিকেশনের আপডেট ইনস্টল করা হয়েছে",
+ "NotificationOptionApplicationUpdateAvailable": "এপ্লিকেশনের আপডেট রয়েছে",
+ "NewVersionIsAvailable": "জেলিফিন সার্ভারের একটি নতুন ভার্শন ডাউনলোডের জন্য তৈরী",
+ "NameSeasonUnknown": "সিজন অজানা",
+ "NameSeasonNumber": "সিজন {0}",
+ "NameInstallFailed": "{0} ইন্সটল ব্যর্থ",
+ "MusicVideos": "গানের ভিডিও",
+ "Music": "গান",
+ "Movies": "সিনেমা",
+ "MixedContent": "মিশ্র কন্টেন্ট",
+ "MessageServerConfigurationUpdated": "সার্ভারের কনফিগারেশন হালনাগাদ করা হয়েছে",
+ "HeaderRecordingGroups": "রেকর্ডিং গ্রুপ",
+ "MessageNamedServerConfigurationUpdatedWithValue": "সার্ভারের {0} কনফিগারেসন অংশ আপডেট করা হয়েছে",
+ "MessageApplicationUpdatedTo": "জেলিফিন সার্ভার {0} তে হালনাগাদ করা হয়েছে",
+ "MessageApplicationUpdated": "জেলিফিন সার্ভার হালনাগাদ করা হয়েছে",
+ "Latest": "একদম নতুন",
+ "LabelRunningTimeValue": "চলার সময়: {0}",
+ "LabelIpAddressValue": "আইপি ঠিকানা: {0}",
+ "ItemRemovedWithName": "{0} লাইব্রেরি থেকে বাদ দেয়া হয়েছে",
+ "ItemAddedWithName": "{0} লাইব্রেরিতে যোগ করা হয়েছে",
+ "Inherit": "থেকে পাওয়া",
+ "HomeVideos": "বাসার ভিডিও",
+ "HeaderNextUp": "এরপরে আসছে",
+ "HeaderLiveTV": "লাইভ টিভি",
+ "HeaderFavoriteSongs": "প্রিয় গানগুলো",
+ "HeaderFavoriteShows": "প্রিয় শোগুলো"
+}
diff --git a/Emby.Server.Implementations/Localization/Core/ca.json b/Emby.Server.Implementations/Localization/Core/ca.json
index 9961b0984..7464ac1c0 100644
--- a/Emby.Server.Implementations/Localization/Core/ca.json
+++ b/Emby.Server.Implementations/Localization/Core/ca.json
@@ -5,17 +5,17 @@
"Artists": "Artistes",
"AuthenticationSucceededWithUserName": "{0} s'ha autenticat correctament",
"Books": "Llibres",
- "CameraImageUploadedFrom": "Una nova imatge de càmera ha sigut pujada des de {0}",
+ "CameraImageUploadedFrom": "Una nova imatge de la càmera ha estat pujada des de {0}",
"Channels": "Canals",
- "ChapterNameValue": "Episodi {0}",
+ "ChapterNameValue": "Capítol {0}",
"Collections": "Col·leccions",
"DeviceOfflineWithName": "{0} s'ha desconnectat",
"DeviceOnlineWithName": "{0} està connectat",
"FailedLoginAttemptWithUserName": "Intent de connexió fallit des de {0}",
"Favorites": "Preferits",
- "Folders": "Directoris",
+ "Folders": "Carpetes",
"Genres": "Gèneres",
- "HeaderAlbumArtists": "Artistes dels Àlbums",
+ "HeaderAlbumArtists": "Artistes del Àlbum",
"HeaderCameraUploads": "Pujades de Càmera",
"HeaderContinueWatching": "Continua Veient",
"HeaderFavoriteAlbums": "Àlbums Preferits",
@@ -76,7 +76,6 @@
"StartupEmbyServerIsLoading": "El Servidor d'Jellyfin est&agrave; carregant. Si et plau, prova de nou en breus.",
"SubtitleDownloadFailureForItem": "Subtitles failed to download for {0}",
"SubtitleDownloadFailureFromForItem": "Els subtítols no s'han pogut baixar de {0} per {1}",
- "SubtitlesDownloadedForItem": "Subtítols descarregats per a {0}",
"Sync": "Sincronitzar",
"System": "System",
"TvShows": "Espectacles de TV",
diff --git a/Emby.Server.Implementations/Localization/Core/cs.json b/Emby.Server.Implementations/Localization/Core/cs.json
index 86fbac380..992bb9df3 100644
--- a/Emby.Server.Implementations/Localization/Core/cs.json
+++ b/Emby.Server.Implementations/Localization/Core/cs.json
@@ -5,7 +5,7 @@
"Artists": "Umělci",
"AuthenticationSucceededWithUserName": "{0} úspěšně ověřen",
"Books": "Knihy",
- "CameraImageUploadedFrom": "Z {0} byla nahrána nová fotografie",
+ "CameraImageUploadedFrom": "Z {0} byla nahrána nová fotografie z fotoaparátu",
"Channels": "Kanály",
"ChapterNameValue": "Kapitola {0}",
"Collections": "Kolekce",
@@ -76,7 +76,6 @@
"StartupEmbyServerIsLoading": "Jellyfin Server je spouštěn. Zkuste to prosím v brzké době znovu.",
"SubtitleDownloadFailureForItem": "Stahování titulků selhalo pro {0}",
"SubtitleDownloadFailureFromForItem": "Stažení titulků pro {1} z {0} selhalo",
- "SubtitlesDownloadedForItem": "Staženy titulky pro {0}",
"Sync": "Synchronizace",
"System": "Systém",
"TvShows": "TV seriály",
@@ -93,5 +92,27 @@
"UserStoppedPlayingItemWithValues": "{0} zastavil přehrávání {1}",
"ValueHasBeenAddedToLibrary": "{0} byl přidán do vaší knihovny médií",
"ValueSpecialEpisodeName": "Speciál - {0}",
- "VersionNumber": "Verze {0}"
+ "VersionNumber": "Verze {0}",
+ "TaskDownloadMissingSubtitlesDescription": "Vyhledá na internetu chybějící titulky na základě nastavení metadat.",
+ "TaskDownloadMissingSubtitles": "Stáhnout chybějící titulky",
+ "TaskRefreshChannelsDescription": "Obnoví informace o internetových kanálech.",
+ "TaskRefreshChannels": "Obnovit kanály",
+ "TaskCleanTranscodeDescription": "Odstraní více než 1 den staré transkódované soubory.",
+ "TaskCleanTranscode": "Vyčistit adresář s transkódovaným obsahem",
+ "TaskUpdatePluginsDescription": "Stáhne a nainstaluje aktualizace zásuvných modulů, které mají nastavenou automatickou aktualizaci.",
+ "TaskUpdatePlugins": "Aktualizovat zásuvné moduly",
+ "TaskRefreshPeopleDescription": "Aktualizuje metadata umělců a režisérů ve Vaší knihovně médií.",
+ "TaskRefreshPeople": "Obnovit umělce",
+ "TaskCleanLogsDescription": "Odstraní soubory protokolu, které jsou starší více než {0} dní.",
+ "TaskCleanLogs": "Vyčistit adresář se souborem protokolu",
+ "TaskRefreshLibraryDescription": "Prohledá Vaši knihovnu médií zda neobsahuje nové soubory a obnoví metadatada.",
+ "TaskRefreshLibrary": "Prohledat knihovnu médií",
+ "TaskRefreshChapterImagesDescription": "Vytvoří náhledy videí, které obsahují kapitoly.",
+ "TaskRefreshChapterImages": "Extrahovat obrázky kapitol",
+ "TaskCleanCacheDescription": "Odstraní soubory mezipaměti, které systém již nebude potřebovat.",
+ "TaskCleanCache": "Vyčistit složku s mezipamětí",
+ "TasksChannelsCategory": "Internetové kanály",
+ "TasksApplicationCategory": "Aplikace",
+ "TasksLibraryCategory": "Knihovna",
+ "TasksMaintenanceCategory": "Údržba"
}
diff --git a/Emby.Server.Implementations/Localization/Core/da.json b/Emby.Server.Implementations/Localization/Core/da.json
index cc8b7dbd5..f5397b62c 100644
--- a/Emby.Server.Implementations/Localization/Core/da.json
+++ b/Emby.Server.Implementations/Localization/Core/da.json
@@ -1,9 +1,9 @@
{
- "Albums": "Album",
+ "Albums": "Albums",
"AppDeviceValues": "App: {0}, Enhed: {1}",
"Application": "Applikation",
"Artists": "Kunstnere",
- "AuthenticationSucceededWithUserName": "{0} bekræftet med succes",
+ "AuthenticationSucceededWithUserName": "{0} succesfuldt autentificeret",
"Books": "Bøger",
"CameraImageUploadedFrom": "Et nyt kamerabillede er blevet uploadet fra {0}",
"Channels": "Kanaler",
@@ -35,8 +35,8 @@
"Latest": "Seneste",
"MessageApplicationUpdated": "Jellyfin Server er blevet opdateret",
"MessageApplicationUpdatedTo": "Jellyfin Server er blevet opdateret til {0}",
- "MessageNamedServerConfigurationUpdatedWithValue": "Serverkonfigurationsafsnit {0} er blevet opdateret",
- "MessageServerConfigurationUpdated": "Serverkonfigurationen er blevet opdateret",
+ "MessageNamedServerConfigurationUpdatedWithValue": "Server konfiguration sektion {0} er blevet opdateret",
+ "MessageServerConfigurationUpdated": "Server konfigurationen er blevet opdateret",
"MixedContent": "Blandet indhold",
"Movies": "Film",
"Music": "Musik",
@@ -76,7 +76,6 @@
"StartupEmbyServerIsLoading": "Jellyfin Server er i gang med at starte op. Prøv venligst igen om lidt.",
"SubtitleDownloadFailureForItem": "Fejlet i download af undertekster for {0}",
"SubtitleDownloadFailureFromForItem": "Undertekster kunne ikke downloades fra {0} til {1}",
- "SubtitlesDownloadedForItem": "Undertekster downloadet for {0}",
"Sync": "Synk",
"System": "System",
"TvShows": "TV serier",
@@ -93,5 +92,27 @@
"UserStoppedPlayingItemWithValues": "{0} har afsluttet afspilning af {1} på {2}",
"ValueHasBeenAddedToLibrary": "{0} er blevet tilføjet til dit mediebibliotek",
"ValueSpecialEpisodeName": "Special - {0}",
- "VersionNumber": "Version {0}"
+ "VersionNumber": "Version {0}",
+ "TaskDownloadMissingSubtitlesDescription": "Søger på internettet efter manglende undertekster baseret på metadata konfiguration.",
+ "TaskDownloadMissingSubtitles": "Download manglende undertekster",
+ "TaskUpdatePluginsDescription": "Downloader og installere opdateringer for plugins som er konfigureret til at opdatere automatisk.",
+ "TaskUpdatePlugins": "Opdater Plugins",
+ "TaskCleanLogsDescription": "Sletter log filer som er mere end {0} dage gammle.",
+ "TaskCleanLogs": "Ryd Log Mappe",
+ "TaskRefreshLibraryDescription": "Scanner dit medie bibliotek for nye filer og opdaterer metadata.",
+ "TaskRefreshLibrary": "Scan Medie Bibliotek",
+ "TaskCleanCacheDescription": "Sletter cache filer som systemet ikke har brug for længere.",
+ "TaskCleanCache": "Ryd Cache Mappe",
+ "TasksChannelsCategory": "Internet Kanaler",
+ "TasksApplicationCategory": "Applikation",
+ "TasksLibraryCategory": "Bibliotek",
+ "TasksMaintenanceCategory": "Vedligeholdelse",
+ "TaskRefreshChapterImages": "Udtræk Kapitel billeder",
+ "TaskRefreshChapterImagesDescription": "Lav miniaturebilleder for videoer der har kapitler.",
+ "TaskRefreshChannelsDescription": "Genopfrisker internet kanal information.",
+ "TaskRefreshChannels": "Genopfrisk Kanaler",
+ "TaskCleanTranscodeDescription": "Fjern transcode filer som er mere end en dag gammel.",
+ "TaskCleanTranscode": "Rengør Transcode Mappen",
+ "TaskRefreshPeople": "Genopfrisk Personer",
+ "TaskRefreshPeopleDescription": "Opdatere metadata for skuespillere og instruktører i dit bibliotek."
}
diff --git a/Emby.Server.Implementations/Localization/Core/de.json b/Emby.Server.Implementations/Localization/Core/de.json
index 019736c47..82df43be1 100644
--- a/Emby.Server.Implementations/Localization/Core/de.json
+++ b/Emby.Server.Implementations/Localization/Core/de.json
@@ -3,13 +3,13 @@
"AppDeviceValues": "App: {0}, Gerät: {1}",
"Application": "Anwendung",
"Artists": "Interpreten",
- "AuthenticationSucceededWithUserName": "{0} hat sich erfolgreich angemeldet",
+ "AuthenticationSucceededWithUserName": "{0} hat sich erfolgreich authentifiziert",
"Books": "Bücher",
- "CameraImageUploadedFrom": "Ein neues Foto wurde hochgeladen von {0}",
+ "CameraImageUploadedFrom": "Ein neues Foto wurde von {0} hochgeladen",
"Channels": "Kanäle",
"ChapterNameValue": "Kapitel {0}",
"Collections": "Sammlungen",
- "DeviceOfflineWithName": "{0} wurde getrennt",
+ "DeviceOfflineWithName": "{0} hat die Verbindung getrennt",
"DeviceOnlineWithName": "{0} ist verbunden",
"FailedLoginAttemptWithUserName": "Fehlgeschlagener Anmeldeversuch von {0}",
"Favorites": "Favoriten",
@@ -17,7 +17,7 @@
"Genres": "Genres",
"HeaderAlbumArtists": "Album-Interpreten",
"HeaderCameraUploads": "Kamera-Uploads",
- "HeaderContinueWatching": "Weiterschauen",
+ "HeaderContinueWatching": "Fortsetzen",
"HeaderFavoriteAlbums": "Lieblingsalben",
"HeaderFavoriteArtists": "Lieblings-Interpreten",
"HeaderFavoriteEpisodes": "Lieblingsepisoden",
@@ -50,7 +50,7 @@
"NotificationOptionAudioPlayback": "Audiowiedergabe gestartet",
"NotificationOptionAudioPlaybackStopped": "Audiowiedergabe gestoppt",
"NotificationOptionCameraImageUploaded": "Foto hochgeladen",
- "NotificationOptionInstallationFailed": "Installationsfehler",
+ "NotificationOptionInstallationFailed": "Installation fehlgeschlagen",
"NotificationOptionNewLibraryContent": "Neuer Inhalt hinzugefügt",
"NotificationOptionPluginError": "Plugin-Fehler",
"NotificationOptionPluginInstalled": "Plugin installiert",
@@ -76,7 +76,6 @@
"StartupEmbyServerIsLoading": "Jellyfin-Server startet, bitte versuche es gleich noch einmal.",
"SubtitleDownloadFailureForItem": "Download der Untertitel fehlgeschlagen für {0}",
"SubtitleDownloadFailureFromForItem": "Untertitel von {0} für {1} konnten nicht heruntergeladen werden",
- "SubtitlesDownloadedForItem": "Untertitel heruntergeladen für {0}",
"Sync": "Synchronisation",
"System": "System",
"TvShows": "TV-Sendungen",
@@ -93,5 +92,27 @@
"UserStoppedPlayingItemWithValues": "{0} hat die Wiedergabe von {1} auf {2} beendet",
"ValueHasBeenAddedToLibrary": "{0} wurde deiner Bibliothek hinzugefügt",
"ValueSpecialEpisodeName": "Extra - {0}",
- "VersionNumber": "Version {0}"
+ "VersionNumber": "Version {0}",
+ "TaskDownloadMissingSubtitlesDescription": "Durchsucht das Internet nach fehlenden Untertiteln, basierend auf den Meta Einstellungen.",
+ "TaskDownloadMissingSubtitles": "Lade fehlende Untertitel herunter",
+ "TaskRefreshChannelsDescription": "Erneuere Internet Kanal Informationen.",
+ "TaskRefreshChannels": "Erneuere Kanäle",
+ "TaskCleanTranscodeDescription": "Löscht Transkodierdateien welche älter als ein Tag sind.",
+ "TaskCleanTranscode": "Lösche Transkodier Pfad",
+ "TaskUpdatePluginsDescription": "Lädt Updates für Plugins herunter, welche dazu eingestellt sind automatisch zu updaten und installiert sie.",
+ "TaskUpdatePlugins": "Update Plugins",
+ "TaskRefreshPeopleDescription": "Erneuert Metadaten für Schausteller und Regisseure in deinen Bibliotheken.",
+ "TaskRefreshPeople": "Erneuere Schausteller",
+ "TaskCleanLogsDescription": "Lösche Log Dateien die älter als {0} Tage sind.",
+ "TaskCleanLogs": "Lösche Log Pfad",
+ "TaskRefreshLibraryDescription": "Scanne alle Bibliotheken für hinzugefügte Datein und erneuere Metadaten.",
+ "TaskRefreshLibrary": "Scanne alle Bibliotheken",
+ "TaskRefreshChapterImagesDescription": "Kreiert Vorschaubilder für Videos welche Kapitel haben.",
+ "TaskRefreshChapterImages": "Extrahiert Kapitel-Bilder",
+ "TaskCleanCacheDescription": "Löscht Zwischenspeicherdatein die nicht länger von System gebraucht werden.",
+ "TaskCleanCache": "Leere Cache Pfad",
+ "TasksChannelsCategory": "Internet Kanäle",
+ "TasksApplicationCategory": "Anwendung",
+ "TasksLibraryCategory": "Bibliothek",
+ "TasksMaintenanceCategory": "Wartung"
}
diff --git a/Emby.Server.Implementations/Localization/Core/el.json b/Emby.Server.Implementations/Localization/Core/el.json
index 580b42330..0753ea39d 100644
--- a/Emby.Server.Implementations/Localization/Core/el.json
+++ b/Emby.Server.Implementations/Localization/Core/el.json
@@ -1,5 +1,5 @@
{
- "Albums": "Άλμπουμ",
+ "Albums": "Άλμπουμς",
"AppDeviceValues": "Εφαρμογή: {0}, Συσκευή: {1}",
"Application": "Εφαρμογή",
"Artists": "Καλλιτέχνες",
@@ -76,7 +76,6 @@
"StartupEmbyServerIsLoading": "Ο Jellyfin Server φορτώνει. Παρακαλώ δοκιμάστε σε λίγο.",
"SubtitleDownloadFailureForItem": "Οι υπότιτλοι απέτυχαν να κατέβουν για {0}",
"SubtitleDownloadFailureFromForItem": "Αποτυχίες μεταφόρτωσης υποτίτλων από {0} για {1}",
- "SubtitlesDownloadedForItem": "Οι υπότιτλοι κατέβηκαν για {0}",
"Sync": "Συγχρονισμός",
"System": "Σύστημα",
"TvShows": "Τηλεοπτικές Σειρές",
@@ -93,5 +92,27 @@
"UserStoppedPlayingItemWithValues": "{0} τελείωσε να παίζει {1} σε {2}",
"ValueHasBeenAddedToLibrary": "{0} προστέθηκαν στη βιβλιοθήκη πολυμέσων σας",
"ValueSpecialEpisodeName": "Σπέσιαλ - {0}",
- "VersionNumber": "Έκδοση {0}"
+ "VersionNumber": "Έκδοση {0}",
+ "TaskRefreshPeople": "Ανανέωση Ατόμων",
+ "TaskCleanLogsDescription": "Διαγράφει τα αρχεία καταγραφής που είναι άνω των {0} ημερών.",
+ "TaskCleanLogs": "Καθαρισμός Καταλόγου Καταγραφής",
+ "TaskRefreshLibraryDescription": "Σαρώνει την βιβλιοθήκη πολυμέσων σας για νέα αρχεία και αναζωογονεί τα μεταδεδομένα.",
+ "TaskRefreshLibrary": "Βιβλιοθήκη Σάρωσης Πολυμέσων",
+ "TaskRefreshChapterImagesDescription": "Δημιουργεί μικρογραφίες για βίντεο με κεφάλαια.",
+ "TaskRefreshChapterImages": "Εξαγωγή Εικόνων Κεφαλαίου",
+ "TaskCleanCacheDescription": "Τα διαγραμμένα αρχεία προσωρινής μνήμης που δεν χρειάζονται πλέον από το σύστημα.",
+ "TaskCleanCache": "Καθαρισμός Καταλόγου Προσωρινής Μνήμης",
+ "TasksChannelsCategory": "Κανάλια Διαδικτύου",
+ "TasksApplicationCategory": "Εφαρμογή",
+ "TasksLibraryCategory": "Βιβλιοθήκη",
+ "TasksMaintenanceCategory": "Συντήρηση",
+ "TaskDownloadMissingSubtitlesDescription": "Αναζητήσεις στο διαδίκτυο όπου λείπουν υπότιτλους με βάση τη διαμόρφωση μεταδεδομένων.",
+ "TaskDownloadMissingSubtitles": "Λήψη υπότιτλων που λείπουν",
+ "TaskRefreshChannelsDescription": "Ανανεώνει τις πληροφορίες καναλιού στο διαδικτύου.",
+ "TaskRefreshChannels": "Ανανέωση Καναλιών",
+ "TaskCleanTranscodeDescription": "Διαγράφει αρχείου διακωδικοποιητή περισσότερο από μία ημέρα.",
+ "TaskCleanTranscode": "Καθαρισμός Kαταλόγου Διακωδικοποιητή",
+ "TaskUpdatePluginsDescription": "Κατεβάζει και εγκαθιστά ενημερώσεις για τις προσθήκες που έχουν ρυθμιστεί για αυτόματη ενημέρωση.",
+ "TaskUpdatePlugins": "Ενημέρωση Προσθηκών",
+ "TaskRefreshPeopleDescription": "Ενημερώνει μεταδεδομένα για ηθοποιούς και σκηνοθέτες στην βιβλιοθήκη των πολυμέσων σας."
}
diff --git a/Emby.Server.Implementations/Localization/Core/en-GB.json b/Emby.Server.Implementations/Localization/Core/en-GB.json
index 67d4068cf..544c38cfa 100644
--- a/Emby.Server.Implementations/Localization/Core/en-GB.json
+++ b/Emby.Server.Implementations/Localization/Core/en-GB.json
@@ -76,7 +76,6 @@
"StartupEmbyServerIsLoading": "Jellyfin Server is loading. Please try again shortly.",
"SubtitleDownloadFailureForItem": "Subtitles failed to download for {0}",
"SubtitleDownloadFailureFromForItem": "Subtitles failed to download from {0} for {1}",
- "SubtitlesDownloadedForItem": "Subtitles downloaded for {0}",
"Sync": "Sync",
"System": "System",
"TvShows": "TV Shows",
@@ -93,5 +92,27 @@
"UserStoppedPlayingItemWithValues": "{0} has finished playing {1} on {2}",
"ValueHasBeenAddedToLibrary": "{0} has been added to your media library",
"ValueSpecialEpisodeName": "Special - {0}",
- "VersionNumber": "Version {0}"
+ "VersionNumber": "Version {0}",
+ "TaskDownloadMissingSubtitlesDescription": "Searches the internet for missing subtitles based on metadata configuration.",
+ "TaskDownloadMissingSubtitles": "Download missing subtitles",
+ "TaskRefreshChannelsDescription": "Refreshes internet channel information.",
+ "TaskRefreshChannels": "Refresh Channels",
+ "TaskCleanTranscodeDescription": "Deletes transcode files more than one day old.",
+ "TaskCleanTranscode": "Clean Transcode Directory",
+ "TaskUpdatePluginsDescription": "Downloads and installs updates for plugins that are configured to update automatically.",
+ "TaskUpdatePlugins": "Update Plugins",
+ "TaskRefreshPeopleDescription": "Updates metadata for actors and directors in your media library.",
+ "TaskRefreshPeople": "Refresh People",
+ "TaskCleanLogsDescription": "Deletes log files that are more than {0} days old.",
+ "TaskCleanLogs": "Clean Log Directory",
+ "TaskRefreshLibraryDescription": "Scans your media library for new files and refreshes metadata.",
+ "TaskRefreshLibrary": "Scan Media Library",
+ "TaskRefreshChapterImagesDescription": "Creates thumbnails for videos that have chapters.",
+ "TaskRefreshChapterImages": "Extract Chapter Images",
+ "TaskCleanCacheDescription": "Deletes cache files no longer needed by the system.",
+ "TaskCleanCache": "Clean Cache Directory",
+ "TasksChannelsCategory": "Internet Channels",
+ "TasksApplicationCategory": "Application",
+ "TasksLibraryCategory": "Library",
+ "TasksMaintenanceCategory": "Maintenance"
}
diff --git a/Emby.Server.Implementations/Localization/Core/en-US.json b/Emby.Server.Implementations/Localization/Core/en-US.json
index aa855ed21..97a843160 100644
--- a/Emby.Server.Implementations/Localization/Core/en-US.json
+++ b/Emby.Server.Implementations/Localization/Core/en-US.json
@@ -75,7 +75,6 @@
"Songs": "Songs",
"StartupEmbyServerIsLoading": "Jellyfin Server is loading. Please try again shortly.",
"SubtitleDownloadFailureFromForItem": "Subtitles failed to download from {0} for {1}",
- "SubtitlesDownloadedForItem": "Subtitles downloaded for {0}",
"Sync": "Sync",
"System": "System",
"TvShows": "TV Shows",
@@ -92,5 +91,27 @@
"UserStoppedPlayingItemWithValues": "{0} has finished playing {1} on {2}",
"ValueHasBeenAddedToLibrary": "{0} has been added to your media library",
"ValueSpecialEpisodeName": "Special - {0}",
- "VersionNumber": "Version {0}"
+ "VersionNumber": "Version {0}",
+ "TasksMaintenanceCategory": "Maintenance",
+ "TasksLibraryCategory": "Library",
+ "TasksApplicationCategory": "Application",
+ "TasksChannelsCategory": "Internet Channels",
+ "TaskCleanCache": "Clean Cache Directory",
+ "TaskCleanCacheDescription": "Deletes cache files no longer needed by the system.",
+ "TaskRefreshChapterImages": "Extract Chapter Images",
+ "TaskRefreshChapterImagesDescription": "Creates thumbnails for videos that have chapters.",
+ "TaskRefreshLibrary": "Scan Media Library",
+ "TaskRefreshLibraryDescription": "Scans your media library for new files and refreshes metadata.",
+ "TaskCleanLogs": "Clean Log Directory",
+ "TaskCleanLogsDescription": "Deletes log files that are more than {0} days old.",
+ "TaskRefreshPeople": "Refresh People",
+ "TaskRefreshPeopleDescription": "Updates metadata for actors and directors in your media library.",
+ "TaskUpdatePlugins": "Update Plugins",
+ "TaskUpdatePluginsDescription": "Downloads and installs updates for plugins that are configured to update automatically.",
+ "TaskCleanTranscode": "Clean Transcode Directory",
+ "TaskCleanTranscodeDescription": "Deletes transcode files more than one day old.",
+ "TaskRefreshChannels": "Refresh Channels",
+ "TaskRefreshChannelsDescription": "Refreshes internet channel information.",
+ "TaskDownloadMissingSubtitles": "Download missing subtitles",
+ "TaskDownloadMissingSubtitlesDescription": "Searches the internet for missing subtitles based on metadata configuration."
}
diff --git a/Emby.Server.Implementations/Localization/Core/es-AR.json b/Emby.Server.Implementations/Localization/Core/es-AR.json
index dc73ba6b3..1b6c6b5ae 100644
--- a/Emby.Server.Implementations/Localization/Core/es-AR.json
+++ b/Emby.Server.Implementations/Localization/Core/es-AR.json
@@ -11,20 +11,20 @@
"Collections": "Colecciones",
"DeviceOfflineWithName": "{0} se ha desconectado",
"DeviceOnlineWithName": "{0} está conectado",
- "FailedLoginAttemptWithUserName": "Error al intentar iniciar sesión desde {0}",
+ "FailedLoginAttemptWithUserName": "Error al intentar iniciar sesión de {0}",
"Favorites": "Favoritos",
"Folders": "Carpetas",
"Genres": "Géneros",
- "HeaderAlbumArtists": "Artistas de álbumes",
+ "HeaderAlbumArtists": "Artistas de álbum",
"HeaderCameraUploads": "Subidas de cámara",
- "HeaderContinueWatching": "Continuar viendo",
+ "HeaderContinueWatching": "Seguir viendo",
"HeaderFavoriteAlbums": "Álbumes favoritos",
"HeaderFavoriteArtists": "Artistas favoritos",
"HeaderFavoriteEpisodes": "Episodios favoritos",
"HeaderFavoriteShows": "Programas favoritos",
"HeaderFavoriteSongs": "Canciones favoritas",
"HeaderLiveTV": "TV en vivo",
- "HeaderNextUp": "Continuar Viendo",
+ "HeaderNextUp": "A Continuación",
"HeaderRecordingGroups": "Grupos de grabación",
"HomeVideos": "Videos caseros",
"Inherit": "Heredar",
@@ -35,48 +35,47 @@
"Latest": "Últimos",
"MessageApplicationUpdated": "El servidor Jellyfin fue actualizado",
"MessageApplicationUpdatedTo": "Se ha actualizado el servidor Jellyfin a la versión {0}",
- "MessageNamedServerConfigurationUpdatedWithValue": "Fue actualizada la sección {0} de la configuración del servidor",
- "MessageServerConfigurationUpdated": "Fue actualizada la configuración del servidor",
- "MixedContent": "Contenido mixto",
+ "MessageNamedServerConfigurationUpdatedWithValue": "Se ha actualizado la sección {0} de la configuración del servidor",
+ "MessageServerConfigurationUpdated": "Se ha actualizado la configuración del servidor",
+ "MixedContent": "Contenido mezclado",
"Movies": "Películas",
"Music": "Música",
"MusicVideos": "Videos musicales",
- "NameInstallFailed": "{0} error de instalación",
+ "NameInstallFailed": "{0} instalación fallida",
"NameSeasonNumber": "Temporada {0}",
"NameSeasonUnknown": "Temporada desconocida",
- "NewVersionIsAvailable": "Disponible una nueva versión de Jellyfin para descargar.",
+ "NewVersionIsAvailable": "Una nueva versión del Servidor Jellyfin está disponible para descargar.",
"NotificationOptionApplicationUpdateAvailable": "Actualización de la aplicación disponible",
"NotificationOptionApplicationUpdateInstalled": "Actualización de la aplicación instalada",
"NotificationOptionAudioPlayback": "Se inició la reproducción de audio",
"NotificationOptionAudioPlaybackStopped": "Se detuvo la reproducción de audio",
- "NotificationOptionCameraImageUploaded": "Imagen de la cámara cargada",
+ "NotificationOptionCameraImageUploaded": "Imagen de la cámara subida",
"NotificationOptionInstallationFailed": "Error de instalación",
"NotificationOptionNewLibraryContent": "Nuevo contenido añadido",
- "NotificationOptionPluginError": "Error en plugin",
- "NotificationOptionPluginInstalled": "Plugin instalado",
- "NotificationOptionPluginUninstalled": "Plugin desinstalado",
- "NotificationOptionPluginUpdateInstalled": "Actualización del complemento instalada",
- "NotificationOptionServerRestartRequired": "Se requiere reinicio del servidor",
- "NotificationOptionTaskFailed": "Error de tarea programada",
+ "NotificationOptionPluginError": "Falla de complemento",
+ "NotificationOptionPluginInstalled": "Complemento instalado",
+ "NotificationOptionPluginUninstalled": "Complemento desinstalado",
+ "NotificationOptionPluginUpdateInstalled": "Actualización de complemento instalada",
+ "NotificationOptionServerRestartRequired": "Se necesita reiniciar el Servidor",
+ "NotificationOptionTaskFailed": "Falla de tarea programada",
"NotificationOptionUserLockedOut": "Usuario bloqueado",
"NotificationOptionVideoPlayback": "Se inició la reproducción de video",
"NotificationOptionVideoPlaybackStopped": "Reproducción de video detenida",
"Photos": "Fotos",
"Playlists": "Listas de reproducción",
- "Plugin": "Plugin",
+ "Plugin": "Complemento",
"PluginInstalledWithName": "{0} fue instalado",
"PluginUninstalledWithName": "{0} fue desinstalado",
"PluginUpdatedWithName": "{0} fue actualizado",
"ProviderValue": "Proveedor: {0}",
"ScheduledTaskFailedWithName": "{0} falló",
- "ScheduledTaskStartedWithName": "{0} iniciada",
+ "ScheduledTaskStartedWithName": "{0} iniciado",
"ServerNameNeedsToBeRestarted": "{0} necesita ser reiniciado",
"Shows": "Series",
"Songs": "Canciones",
- "StartupEmbyServerIsLoading": "Jellyfin Server se está cargando. Vuelve a intentarlo en breve.",
+ "StartupEmbyServerIsLoading": "El servidor Jellyfin se está cargando. Vuelve a intentarlo en breve.",
"SubtitleDownloadFailureForItem": "Subtitles failed to download for {0}",
- "SubtitleDownloadFailureFromForItem": "Fallo de descarga de subtítulos desde {0} para {1}",
- "SubtitlesDownloadedForItem": "Descargar subtítulos para {0}",
+ "SubtitleDownloadFailureFromForItem": "Falló la descarga de subtitulos desde {0} para {1}",
"Sync": "Sincronizar",
"System": "Sistema",
"TvShows": "Series de TV",
@@ -88,10 +87,32 @@
"UserOfflineFromDevice": "{0} se ha desconectado de {1}",
"UserOnlineFromDevice": "{0} está en línea desde {1}",
"UserPasswordChangedWithName": "Se ha cambiado la contraseña para el usuario {0}",
- "UserPolicyUpdatedWithName": "Actualizada política de usuario para {0}",
+ "UserPolicyUpdatedWithName": "Las política de usuario ha sido actualizada para {0}",
"UserStartedPlayingItemWithValues": "{0} está reproduciendo {1} en {2}",
"UserStoppedPlayingItemWithValues": "{0} ha terminado de reproducir {1} en {2}",
"ValueHasBeenAddedToLibrary": "{0} ha sido añadido a tu biblioteca multimedia",
"ValueSpecialEpisodeName": "Especial - {0}",
- "VersionNumber": "Versión {0}"
+ "VersionNumber": "Versión {0}",
+ "TaskDownloadMissingSubtitlesDescription": "Busca en internet los subtítulos que falten basándose en la configuración de los metadatos.",
+ "TaskDownloadMissingSubtitles": "Descargar subtítulos extraviados",
+ "TaskRefreshChannelsDescription": "Actualizar información de canales de internet.",
+ "TaskRefreshChannels": "Actualizar canales",
+ "TaskCleanTranscodeDescription": "Eliminar archivos transcodificados con mas de un día de antigüedad.",
+ "TaskCleanTranscode": "Limpiar directorio de Transcodificado",
+ "TaskUpdatePluginsDescription": "Descargar e instalar actualizaciones para complementos que estén configurados en actualizar automáticamente.",
+ "TaskUpdatePlugins": "Actualizar complementos",
+ "TaskRefreshPeopleDescription": "Actualizar metadatos de actores y directores en su librería multimedia.",
+ "TaskRefreshPeople": "Actualizar personas",
+ "TaskCleanLogsDescription": "Eliminar archivos de registro que tengan mas de {0} días de antigüedad.",
+ "TaskCleanLogs": "Limpiar directorio de registros",
+ "TaskRefreshLibraryDescription": "Escanear su librería multimedia por nuevos archivos y refrescar metadatos.",
+ "TaskRefreshLibrary": "Escanear librería multimedia",
+ "TaskRefreshChapterImagesDescription": "Crear miniaturas de videos que tengan capítulos.",
+ "TaskRefreshChapterImages": "Extraer imágenes de capitulo",
+ "TaskCleanCacheDescription": "Eliminar archivos de cache que no se necesiten en el sistema.",
+ "TaskCleanCache": "Limpiar directorio Cache",
+ "TasksChannelsCategory": "Canales de Internet",
+ "TasksApplicationCategory": "Solicitud",
+ "TasksLibraryCategory": "Biblioteca",
+ "TasksMaintenanceCategory": "Mantenimiento"
}
diff --git a/Emby.Server.Implementations/Localization/Core/es-MX.json b/Emby.Server.Implementations/Localization/Core/es-MX.json
index 99fda7aa6..e0bbe90b3 100644
--- a/Emby.Server.Implementations/Localization/Core/es-MX.json
+++ b/Emby.Server.Implementations/Localization/Core/es-MX.json
@@ -76,7 +76,6 @@
"StartupEmbyServerIsLoading": "El servidor Jellyfin esta cargando. Por favor intente de nuevo dentro de poco.",
"SubtitleDownloadFailureForItem": "Falló la descarga de subtítulos para {0}",
"SubtitleDownloadFailureFromForItem": "Falló la descarga de subtitulos desde {0} para {1}",
- "SubtitlesDownloadedForItem": "Subtítulos descargados para {0}",
"Sync": "Sincronizar",
"System": "Sistema",
"TvShows": "Programas de TV",
@@ -93,5 +92,27 @@
"UserStoppedPlayingItemWithValues": "{0} ha terminado de reproducirse {1} en {2}",
"ValueHasBeenAddedToLibrary": "{0} se han añadido a su biblioteca de medios",
"ValueSpecialEpisodeName": "Especial - {0}",
- "VersionNumber": "Versión {0}"
+ "VersionNumber": "Versión {0}",
+ "TaskDownloadMissingSubtitlesDescription": "Buscar subtítulos de internet basado en configuración de metadatos.",
+ "TaskDownloadMissingSubtitles": "Descargar subtítulos perdidos",
+ "TaskRefreshChannelsDescription": "Refrescar información de canales de internet.",
+ "TaskRefreshChannels": "Actualizar canales",
+ "TaskCleanTranscodeDescription": "Eliminar archivos transcodificados que tengan mas de un día.",
+ "TaskCleanTranscode": "Limpiar directorio de transcodificado",
+ "TaskUpdatePluginsDescription": "Descargar y actualizar complementos que están configurados para actualizarse automáticamente.",
+ "TaskUpdatePlugins": "Actualizar complementos",
+ "TaskRefreshPeopleDescription": "Actualizar datos de actores y directores en su librería multimedia.",
+ "TaskRefreshPeople": "Refrescar persona",
+ "TaskCleanLogsDescription": "Eliminar archivos de registro con mas de {0} días.",
+ "TaskCleanLogs": "Directorio de logo limpio",
+ "TaskRefreshLibraryDescription": "Escanear su librería multimedia para nuevos archivos y refrescar metadatos.",
+ "TaskRefreshLibrary": "Escanear librería multimerdia",
+ "TaskRefreshChapterImagesDescription": "Crear miniaturas para videos con capítulos.",
+ "TaskRefreshChapterImages": "Extraer imágenes de capítulos",
+ "TaskCleanCacheDescription": "Eliminar archivos cache que ya no se necesiten por el sistema.",
+ "TaskCleanCache": "Limpiar directorio cache",
+ "TasksChannelsCategory": "Canales de Internet",
+ "TasksApplicationCategory": "Aplicación",
+ "TasksLibraryCategory": "Biblioteca",
+ "TasksMaintenanceCategory": "Mantenimiento"
}
diff --git a/Emby.Server.Implementations/Localization/Core/es.json b/Emby.Server.Implementations/Localization/Core/es.json
index 2dcc2c1c8..de1baada8 100644
--- a/Emby.Server.Implementations/Localization/Core/es.json
+++ b/Emby.Server.Implementations/Localization/Core/es.json
@@ -76,7 +76,6 @@
"StartupEmbyServerIsLoading": "Jellyfin Server se está cargando. Vuelve a intentarlo en breve.",
"SubtitleDownloadFailureForItem": "Error al descargar subtítulos para {0}",
"SubtitleDownloadFailureFromForItem": "Fallo de descarga de subtítulos desde {0} para {1}",
- "SubtitlesDownloadedForItem": "Descargar subtítulos para {0}",
"Sync": "Sincronizar",
"System": "Sistema",
"TvShows": "Programas de televisión",
@@ -93,5 +92,27 @@
"UserStoppedPlayingItemWithValues": "{0} ha terminado de reproducir {1} en {2}",
"ValueHasBeenAddedToLibrary": "{0} ha sido añadido a tu biblioteca multimedia",
"ValueSpecialEpisodeName": "Especial - {0}",
- "VersionNumber": "Versión {0}"
+ "VersionNumber": "Versión {0}",
+ "TasksMaintenanceCategory": "Mantenimiento",
+ "TasksLibraryCategory": "Librería",
+ "TasksApplicationCategory": "Aplicación",
+ "TasksChannelsCategory": "Canales de internet",
+ "TaskCleanCache": "Eliminar archivos temporales",
+ "TaskCleanCacheDescription": "Elimina los archivos temporales que ya no son necesarios para el servidor.",
+ "TaskRefreshChapterImages": "Extraer imágenes de los capítulos",
+ "TaskRefreshChapterImagesDescription": "Crea las miniaturas de los vídeos que tengan capítulos.",
+ "TaskRefreshLibrary": "Escanear la biblioteca",
+ "TaskRefreshLibraryDescription": "Añade los archivos que se hayan añadido a la biblioteca y actualiza las etiquetas de los ya presentes.",
+ "TaskCleanLogs": "Limpiar registros",
+ "TaskCleanLogsDescription": "Elimina los archivos de registro que tengan más de {0} días.",
+ "TaskRefreshPeople": "Actualizar personas",
+ "TaskRefreshPeopleDescription": "Actualiza las etiquetas de los intérpretes y directores presentes en tus bibliotecas.",
+ "TaskUpdatePlugins": "Actualizar extensiones",
+ "TaskUpdatePluginsDescription": "Actualiza las extensiones que están configuradas para actualizarse automáticamente.",
+ "TaskCleanTranscode": "Limpiar las transcodificaciones",
+ "TaskCleanTranscodeDescription": "Elimina los archivos temporales de transcodificación anteriores a un día de antigüedad.",
+ "TaskRefreshChannels": "Actualizar canales",
+ "TaskRefreshChannelsDescription": "Actualiza la información de los canales de internet.",
+ "TaskDownloadMissingSubtitles": "Descargar los subtítulos que faltan",
+ "TaskDownloadMissingSubtitlesDescription": "Busca en internet los subtítulos que falten en el contenido de tus bibliotecas, basándose en la configuración de los metadatos."
}
diff --git a/Emby.Server.Implementations/Localization/Core/es_DO.json b/Emby.Server.Implementations/Localization/Core/es_DO.json
new file mode 100644
index 000000000..0ef16542f
--- /dev/null
+++ b/Emby.Server.Implementations/Localization/Core/es_DO.json
@@ -0,0 +1,21 @@
+{
+ "Channels": "Canales",
+ "Books": "Libros",
+ "Albums": "Álbumes",
+ "Collections": "Colecciones",
+ "Artists": "Artistas",
+ "DeviceOnlineWithName": "{0} está conectado",
+ "DeviceOfflineWithName": "{0} se ha desconectado",
+ "ChapterNameValue": "Capítulo {0}",
+ "CameraImageUploadedFrom": "Se ha subido una nueva imagen de cámara desde {0}",
+ "AuthenticationSucceededWithUserName": "{0} autenticado con éxito",
+ "Application": "Aplicación",
+ "AppDeviceValues": "App: {0}, Dispositivo: {1}",
+ "HeaderContinueWatching": "Continuar Viendo",
+ "HeaderCameraUploads": "Subidas de Cámara",
+ "HeaderAlbumArtists": "Artistas del Álbum",
+ "Genres": "Géneros",
+ "Folders": "Carpetas",
+ "Favorites": "Favoritos",
+ "FailedLoginAttemptWithUserName": "Intento de inicio de sesión fallido de {0}"
+}
diff --git a/Emby.Server.Implementations/Localization/Core/fa.json b/Emby.Server.Implementations/Localization/Core/fa.json
index faa658ed5..500c29217 100644
--- a/Emby.Server.Implementations/Localization/Core/fa.json
+++ b/Emby.Server.Implementations/Localization/Core/fa.json
@@ -1,56 +1,56 @@
{
- "Albums": "آلبوم ها",
+ "Albums": "آلبوم‌ها",
"AppDeviceValues": "برنامه: {0} ، دستگاه: {1}",
"Application": "برنامه",
"Artists": "هنرمندان",
"AuthenticationSucceededWithUserName": "{0} با موفقیت تایید اعتبار شد",
- "Books": "کتاب ها",
- "CameraImageUploadedFrom": "یک عکس جدید از دوربین ارسال شده {0}",
- "Channels": "کانال ها",
- "ChapterNameValue": "فصل {0}",
- "Collections": "کلکسیون ها",
+ "Books": "کتاب‌ها",
+ "CameraImageUploadedFrom": "یک عکس جدید از دوربین ارسال شده است {0}",
+ "Channels": "کانال‌ها",
+ "ChapterNameValue": "قسمت {0}",
+ "Collections": "مجموعه‌ها",
"DeviceOfflineWithName": "ارتباط {0} قطع شد",
- "DeviceOnlineWithName": "{0} متصل شده",
+ "DeviceOnlineWithName": "{0} متصل شد",
"FailedLoginAttemptWithUserName": "تلاش برای ورود از {0} ناموفق بود",
- "Favorites": "مورد علاقه ها",
- "Folders": "پوشه ها",
+ "Favorites": "مورد علاقه‌ها",
+ "Folders": "پوشه‌ها",
"Genres": "ژانرها",
"HeaderAlbumArtists": "هنرمندان آلبوم",
"HeaderCameraUploads": "آپلودهای دوربین",
"HeaderContinueWatching": "ادامه تماشا",
- "HeaderFavoriteAlbums": "آلبوم های مورد علاقه",
+ "HeaderFavoriteAlbums": "آلبوم‌های مورد علاقه",
"HeaderFavoriteArtists": "هنرمندان مورد علاقه",
- "HeaderFavoriteEpisodes": "قسمت های مورد علاقه",
- "HeaderFavoriteShows": "سریال های مورد علاقه",
- "HeaderFavoriteSongs": "آهنگ های مورد علاقه",
- "HeaderLiveTV": "پخش زنده تلویزیون",
- "HeaderNextUp": "بعدی چیه",
- "HeaderRecordingGroups": "گروه های ضبط",
+ "HeaderFavoriteEpisodes": "قسمت‌های مورد علاقه",
+ "HeaderFavoriteShows": "سریال‌های مورد علاقه",
+ "HeaderFavoriteSongs": "آهنگ‌های مورد علاقه",
+ "HeaderLiveTV": "پخش زنده",
+ "HeaderNextUp": "قسمت بعدی",
+ "HeaderRecordingGroups": "گروه‌های ضبط",
"HomeVideos": "ویدیوهای خانگی",
"Inherit": "به ارث برده",
"ItemAddedWithName": "{0} به کتابخانه افزوده شد",
"ItemRemovedWithName": "{0} از کتابخانه حذف شد",
"LabelIpAddressValue": "آدرس آی پی: {0}",
"LabelRunningTimeValue": "زمان اجرا: {0}",
- "Latest": "آخرین",
+ "Latest": "جدیدترین‌ها",
"MessageApplicationUpdated": "سرور Jellyfin بروزرسانی شد",
- "MessageApplicationUpdatedTo": "سرور جلیفین آپدیت شده به نسخه {0}",
+ "MessageApplicationUpdatedTo": "سرور Jellyfin به نسخه {0} بروزرسانی شد",
"MessageNamedServerConfigurationUpdatedWithValue": "پکربندی بخش {0} سرور بروزرسانی شد",
"MessageServerConfigurationUpdated": "پیکربندی سرور بروزرسانی شد",
- "MixedContent": "محتوای درهم",
- "Movies": "فیلم های سینمایی",
+ "MixedContent": "محتوای مخلوط",
+ "Movies": "فیلم‌ها",
"Music": "موسیقی",
"MusicVideos": "موزیک ویدیوها",
- "NameInstallFailed": "{0} نصب با مشکل مواجه شده",
+ "NameInstallFailed": "{0} نصب با مشکل مواجه شد",
"NameSeasonNumber": "فصل {0}",
- "NameSeasonUnknown": "فصل های ناشناخته",
- "NewVersionIsAvailable": "یک نسخه جدید جلیفین برای بروزرسانی آماده میباشد.",
+ "NameSeasonUnknown": "فصل ناشناخته",
+ "NewVersionIsAvailable": "یک نسخه جدید Jellyfin برای بروزرسانی آماده می‌باشد.",
"NotificationOptionApplicationUpdateAvailable": "بروزرسانی برنامه موجود است",
"NotificationOptionApplicationUpdateInstalled": "بروزرسانی برنامه نصب شد",
"NotificationOptionAudioPlayback": "پخش صدا آغاز شد",
"NotificationOptionAudioPlaybackStopped": "پخش صدا متوقف شد",
"NotificationOptionCameraImageUploaded": "تصاویر دوربین آپلود شد",
- "NotificationOptionInstallationFailed": "شکست نصب",
+ "NotificationOptionInstallationFailed": "نصب شکست خورد",
"NotificationOptionNewLibraryContent": "محتوای جدید افزوده شد",
"NotificationOptionPluginError": "خرابی افزونه",
"NotificationOptionPluginInstalled": "افزونه نصب شد",
@@ -58,40 +58,61 @@
"NotificationOptionPluginUpdateInstalled": "بروزرسانی افزونه نصب شد",
"NotificationOptionServerRestartRequired": "شروع مجدد سرور نیاز است",
"NotificationOptionTaskFailed": "شکست وظیفه برنامه ریزی شده",
- "NotificationOptionUserLockedOut": "کاربر از سیستم خارج شد",
+ "NotificationOptionUserLockedOut": "کاربر قفل شد",
"NotificationOptionVideoPlayback": "پخش ویدیو آغاز شد",
"NotificationOptionVideoPlaybackStopped": "پخش ویدیو متوقف شد",
- "Photos": "عکس ها",
- "Playlists": "لیست های پخش",
+ "Photos": "عکس‌ها",
+ "Playlists": "لیست‌های پخش",
"Plugin": "افزونه",
"PluginInstalledWithName": "{0} نصب شد",
"PluginUninstalledWithName": "{0} حذف شد",
"PluginUpdatedWithName": "{0} آپدیت شد",
"ProviderValue": "ارائه دهنده: {0}",
- "ScheduledTaskFailedWithName": "{0} ناموفق بود",
+ "ScheduledTaskFailedWithName": "{0} شکست خورد",
"ScheduledTaskStartedWithName": "{0} شروع شد",
- "ServerNameNeedsToBeRestarted": "{0} احتیاج به راه اندازی مجدد",
- "Shows": "سریال ها",
- "Songs": "آهنگ ها",
+ "ServerNameNeedsToBeRestarted": "{0} نیاز به راه اندازی مجدد دارد",
+ "Shows": "سریال‌ها",
+ "Songs": "موسیقی‌ها",
"StartupEmbyServerIsLoading": "سرور Jellyfin در حال بارگیری است. لطفا کمی بعد دوباره تلاش کنید.",
"SubtitleDownloadFailureForItem": "دانلود زیرنویس برای {0} ناموفق بود",
- "SubtitleDownloadFailureFromForItem": "زیرنویس برای دانلود با مشکل مواجه شده از {0} برای {1}",
- "SubtitlesDownloadedForItem": "زیرنویس {0} دانلود شد",
- "Sync": "همگامسازی",
+ "SubtitleDownloadFailureFromForItem": "بارگیری زیرنویس برای {1} از {0} شکست خورد",
+ "Sync": "همگام‌سازی",
"System": "سیستم",
- "TvShows": "سریال های تلویزیونی",
+ "TvShows": "سریال‌های تلویزیونی",
"User": "کاربر",
"UserCreatedWithName": "کاربر {0} ایجاد شد",
"UserDeletedWithName": "کاربر {0} حذف شد",
- "UserDownloadingItemWithValues": "{0} در حال دانلود است {1}",
- "UserLockedOutWithName": "کاربر {0} از سیستم خارج شد",
+ "UserDownloadingItemWithValues": "{0} در حال بارگیری {1} می‌باشد",
+ "UserLockedOutWithName": "کاربر {0} قفل شده است",
"UserOfflineFromDevice": "ارتباط {0} از {1} قطع شد",
- "UserOnlineFromDevice": "{0}از {1} آنلاین میباشد",
- "UserPasswordChangedWithName": "رمز برای کاربر {0} تغییر یافت",
+ "UserOnlineFromDevice": "{0} از {1} آنلاین می‌باشد",
+ "UserPasswordChangedWithName": "گذرواژه برای کاربر {0} تغییر کرد",
"UserPolicyUpdatedWithName": "سیاست کاربری برای {0} بروزرسانی شد",
- "UserStartedPlayingItemWithValues": "{0} شروع به پخش {1} کرد",
- "UserStoppedPlayingItemWithValues": "{0} پخش {1} را متوقف کرد",
- "ValueHasBeenAddedToLibrary": "{0} اضافه شده به کتابخانه رسانه شما",
- "ValueSpecialEpisodeName": "ویژه- {0}",
- "VersionNumber": "نسخه {0}"
+ "UserStartedPlayingItemWithValues": "{0} در حال پخش {1} بر روی {2} است",
+ "UserStoppedPlayingItemWithValues": "{0} پخش {1} را بر روی {2} به پایان رساند",
+ "ValueHasBeenAddedToLibrary": "{0} به کتابخانه‌ی رسانه‌ی شما افزوده شد",
+ "ValueSpecialEpisodeName": "ویژه - {0}",
+ "VersionNumber": "نسخه {0}",
+ "TaskCleanTranscodeDescription": "فایل‌های کدگذاری که قدیمی‌تر از یک روز هستند را حذف می‌کند.",
+ "TaskCleanTranscode": "پاکسازی مسیر کد گذاری",
+ "TaskUpdatePluginsDescription": "دانلود و نصب به روز رسانی افزونه‌هایی که برای به روز رسانی خودکار پیکربندی شده‌اند.",
+ "TaskDownloadMissingSubtitlesDescription": "جستجوی زیرنویس‌های ناموجود در اینترنت بر اساس پیکربندی ابرداده‌ها.",
+ "TaskDownloadMissingSubtitles": "دانلود زیرنویس‌های ناموجود",
+ "TaskRefreshChannelsDescription": "اطلاعات کانال اینترنتی را تازه سازی می‌کند.",
+ "TaskRefreshChannels": "تازه سازی کانال‌ها",
+ "TaskUpdatePlugins": "به روز رسانی افزونه‌ها",
+ "TaskRefreshPeopleDescription": "ابرداده‌ها برای بازیگران و کارگردانان در کتابخانه رسانه شما به روزرسانی می شوند.",
+ "TaskRefreshPeople": "تازه سازی افراد",
+ "TaskCleanLogsDescription": "واقعه نگارهایی را که قدیمی تر {0} روز هستند را حذف می کند.",
+ "TaskCleanLogs": "پاکسازی مسیر واقعه نگار",
+ "TaskRefreshLibraryDescription": "کتابخانه رسانه شما را اسکن می‌کند و ابرداده‌ها را تازه سازی می‌کند.",
+ "TaskRefreshLibrary": "اسکن کتابخانه رسانه",
+ "TaskRefreshChapterImagesDescription": "عکس‌های کوچک برای ویدیوهایی که سکانس دارند ایجاد می‌کند.",
+ "TaskRefreshChapterImages": "استخراج عکس‌های سکانس",
+ "TaskCleanCacheDescription": "فایل‌های حافظه موقت که توسط سیستم دیگر مورد نیاز نیستند حذف می‌شوند.",
+ "TaskCleanCache": "پاکسازی مسیر حافظه موقت",
+ "TasksChannelsCategory": "کانال‌های داخلی",
+ "TasksApplicationCategory": "برنامه",
+ "TasksLibraryCategory": "کتابخانه",
+ "TasksMaintenanceCategory": "تعمیر"
}
diff --git a/Emby.Server.Implementations/Localization/Core/fi.json b/Emby.Server.Implementations/Localization/Core/fi.json
index 15aa0a8ee..f8d6e0e09 100644
--- a/Emby.Server.Implementations/Localization/Core/fi.json
+++ b/Emby.Server.Implementations/Localization/Core/fi.json
@@ -1,6 +1,6 @@
{
- "HeaderLiveTV": "Netti-TV",
- "NewVersionIsAvailable": "Uusi versio Jellyfin palvelimesta on ladattavissa",
+ "HeaderLiveTV": "Live-TV",
+ "NewVersionIsAvailable": "Uusi versio Jellyfin palvelimesta on ladattavissa.",
"NameSeasonUnknown": "Tuntematon Kausi",
"NameSeasonNumber": "Kausi {0}",
"NameInstallFailed": "{0} asennus epäonnistui",
@@ -8,41 +8,110 @@
"Music": "Musiikki",
"Movies": "Elokuvat",
"MixedContent": "Sekoitettu sisältö",
- "MessageServerConfigurationUpdated": "Palvelimen konfiguraatio on päivitetty",
- "MessageNamedServerConfigurationUpdatedWithValue": "Palvelimen konfiguraatio-osa {0} on päivitetty",
- "MessageApplicationUpdatedTo": "Jellyfin palvelin on päivitetty {0}",
+ "MessageServerConfigurationUpdated": "Palvelimen asetukset on päivitetty",
+ "MessageNamedServerConfigurationUpdatedWithValue": "Palvelimen asetusryhmä {0} on päivitetty",
+ "MessageApplicationUpdatedTo": "Jellyfin palvelin on päivitetty versioon {0}",
"MessageApplicationUpdated": "Jellyfin palvelin on päivitetty",
- "Latest": "Viimeisin",
- "LabelRunningTimeValue": "Kesto: {0}",
+ "Latest": "Uusimmat",
+ "LabelRunningTimeValue": "Toiston kesto: {0}",
"LabelIpAddressValue": "IP-osoite: {0}",
"ItemRemovedWithName": "{0} poistettiin kirjastosta",
"ItemAddedWithName": "{0} lisättiin kirjastoon",
- "Inherit": "Periä",
+ "Inherit": "Periytyä",
"HomeVideos": "Kotivideot",
- "HeaderRecordingGroups": "Äänitysryhmä",
+ "HeaderRecordingGroups": "Nauhoiteryhmät",
"HeaderNextUp": "Seuraavaksi",
"HeaderFavoriteSongs": "Lempikappaleet",
"HeaderFavoriteShows": "Lempisarjat",
"HeaderFavoriteEpisodes": "Lempijaksot",
- "HeaderCameraUploads": "Kamerasta Ladatut",
+ "HeaderCameraUploads": "Kamerasta Lähetetyt",
"HeaderFavoriteArtists": "Lempiartistit",
"HeaderFavoriteAlbums": "Lempialbumit",
- "HeaderContinueWatching": "Jatka Katsomista",
- "HeaderAlbumArtists": "Albumiartistit",
- "Genres": "Genret",
+ "HeaderContinueWatching": "Jatka katsomista",
+ "HeaderAlbumArtists": "Albumin esittäjä",
+ "Genres": "Tyylilajit",
"Folders": "Kansiot",
"Favorites": "Suosikit",
- "FailedLoginAttemptWithUserName": "Epäonnistunut kirjautumisyritys kohteesta {0}",
- "DeviceOnlineWithName": "{0} on yhdistynyt",
+ "FailedLoginAttemptWithUserName": "Kirjautuminen epäonnistui kohteesta {0}",
+ "DeviceOnlineWithName": "{0} on yhdistetty",
"DeviceOfflineWithName": "{0} on katkaissut yhteytensä",
"Collections": "Kokoelmat",
"ChapterNameValue": "Luku: {0}",
"Channels": "Kanavat",
- "CameraImageUploadedFrom": "Uusi kamerakuva on lähetetty kohteesta {0}",
+ "CameraImageUploadedFrom": "Uusi kamerakuva on ladattu {0}",
"Books": "Kirjat",
- "AuthenticationSucceededWithUserName": "{0} todennettu onnistuneesti",
+ "AuthenticationSucceededWithUserName": "{0} todennus onnistui",
"Artists": "Artistit",
"Application": "Sovellus",
"AppDeviceValues": "Sovellus: {0}, Laite: {1}",
- "Albums": "Albumit"
+ "Albums": "Albumit",
+ "User": "Käyttäjä",
+ "System": "Järjestelmä",
+ "ScheduledTaskFailedWithName": "{0} epäonnistui",
+ "PluginUpdatedWithName": "{0} päivitetty",
+ "PluginInstalledWithName": "{0} asennettu",
+ "Photos": "Kuvat",
+ "ScheduledTaskStartedWithName": "{0} aloitettu",
+ "PluginUninstalledWithName": "{0} poistettu",
+ "Playlists": "Soittolistat",
+ "VersionNumber": "Versio {0}",
+ "ValueSpecialEpisodeName": "Erikois - {0}",
+ "ValueHasBeenAddedToLibrary": "{0} lisättiin mediakirjastoon",
+ "UserStoppedPlayingItemWithValues": "{0} toistaminen valmistui {1} laitteella {2}",
+ "UserStartedPlayingItemWithValues": "{0} toistaa {1} laitteella {2}",
+ "UserPolicyUpdatedWithName": "Käyttöoikeudet päivitetty käyttäjälle {0}",
+ "UserPasswordChangedWithName": "Salasana vaihdettu käyttäjälle {0}",
+ "UserOnlineFromDevice": "{0} on paikalla osoitteesta {1}",
+ "UserOfflineFromDevice": "{0} yhteys katkaistu {1}",
+ "UserLockedOutWithName": "Käyttäjä {0} lukittu",
+ "UserDownloadingItemWithValues": "{0} lataa {1}",
+ "UserDeletedWithName": "Käyttäjä {0} poistettu",
+ "UserCreatedWithName": "Käyttäjä {0} luotu",
+ "TvShows": "TV-sarjat",
+ "Sync": "Synkronoi",
+ "SubtitleDownloadFailureFromForItem": "Tekstitysten lataus ({0} -> {1}) epäonnistui //this string would have to be generated for each provider and movie because of finnish cases, sorry",
+ "StartupEmbyServerIsLoading": "Jellyfin palvelin latautuu. Kokeile hetken kuluttua uudelleen.",
+ "Songs": "Kappaleet",
+ "Shows": "Sarjat",
+ "ServerNameNeedsToBeRestarted": "{0} täytyy käynnistää uudelleen",
+ "ProviderValue": "Tarjoaja: {0}",
+ "Plugin": "Liitännäinen",
+ "NotificationOptionVideoPlaybackStopped": "Videon toisto pysäytetty",
+ "NotificationOptionVideoPlayback": "Videota toistetaan",
+ "NotificationOptionUserLockedOut": "Käyttäjä kirjautui ulos",
+ "NotificationOptionTaskFailed": "Ajastettu tehtävä epäonnistui",
+ "NotificationOptionServerRestartRequired": "Palvelin pitää käynnistää uudelleen",
+ "NotificationOptionPluginUpdateInstalled": "Liitännäinen päivitetty",
+ "NotificationOptionPluginUninstalled": "Liitännäinen poistettu",
+ "NotificationOptionPluginInstalled": "Liitännäinen asennettu",
+ "NotificationOptionPluginError": "Ongelma liitännäisessä",
+ "NotificationOptionNewLibraryContent": "Uutta sisältöä lisätty",
+ "NotificationOptionInstallationFailed": "Asennus epäonnistui",
+ "NotificationOptionCameraImageUploaded": "Kameran kuva ladattu",
+ "NotificationOptionAudioPlaybackStopped": "Äänen toisto lopetettu",
+ "NotificationOptionAudioPlayback": "Toistetaan ääntä",
+ "NotificationOptionApplicationUpdateInstalled": "Sovelluspäivitys asennettu",
+ "NotificationOptionApplicationUpdateAvailable": "Ohjelmistopäivitys saatavilla",
+ "TasksMaintenanceCategory": "Ylläpito",
+ "TaskDownloadMissingSubtitlesDescription": "Etsii puuttuvia tekstityksiä videon metadatatietojen pohjalta.",
+ "TaskDownloadMissingSubtitles": "Lataa puuttuvat tekstitykset",
+ "TaskRefreshChannelsDescription": "Päivittää internet-kanavien tiedot.",
+ "TaskRefreshChannels": "Päivitä kanavat",
+ "TaskCleanTranscodeDescription": "Poistaa transkoodatut tiedostot jotka ovat yli päivän vanhoja.",
+ "TaskCleanTranscode": "Puhdista transkoodaushakemisto",
+ "TaskUpdatePluginsDescription": "Lataa ja asentaa päivitykset liitännäisille jotka on asetettu päivittymään automaattisesti.",
+ "TaskUpdatePlugins": "Päivitä liitännäiset",
+ "TaskRefreshPeopleDescription": "Päivittää näyttelijöiden ja ohjaajien mediatiedot kirjastossasi.",
+ "TaskRefreshPeople": "Päivitä henkilöt",
+ "TaskCleanLogsDescription": "Poistaa lokitiedostot jotka ovat yli {0} päivää vanhoja.",
+ "TaskCleanLogs": "Puhdista lokihakemisto",
+ "TaskRefreshLibraryDescription": "Skannaa mediakirjastosi uusien tiedostojen varalle, sekä virkistää metatiedot.",
+ "TaskRefreshLibrary": "Skannaa mediakirjasto",
+ "TaskRefreshChapterImagesDescription": "Luo pienoiskuvat videoille joissa on lukuja.",
+ "TaskRefreshChapterImages": "Eristä lukujen kuvat",
+ "TaskCleanCacheDescription": "Poistaa järjestelmälle tarpeettomat väliaikaistiedostot.",
+ "TaskCleanCache": "Tyhjennä välimuisti-hakemisto",
+ "TasksChannelsCategory": "Internet kanavat",
+ "TasksApplicationCategory": "Sovellus",
+ "TasksLibraryCategory": "Kirjasto"
}
diff --git a/Emby.Server.Implementations/Localization/Core/fil.json b/Emby.Server.Implementations/Localization/Core/fil.json
new file mode 100644
index 000000000..47daf2044
--- /dev/null
+++ b/Emby.Server.Implementations/Localization/Core/fil.json
@@ -0,0 +1,102 @@
+{
+ "VersionNumber": "Bersyon {0}",
+ "ValueSpecialEpisodeName": "Espesyal - {0}",
+ "ValueHasBeenAddedToLibrary": "Naidagdag na ang {0} sa iyong media library",
+ "UserStoppedPlayingItemWithValues": "Natapos ni {0} ang {1} sa {2}",
+ "UserStartedPlayingItemWithValues": "Si {0} ay nagplaplay ng {1} sa {2}",
+ "UserPolicyUpdatedWithName": "Ang user policy ay naiupdate para kay {0}",
+ "UserPasswordChangedWithName": "Napalitan na ang password ni {0}",
+ "UserOnlineFromDevice": "Si {0} ay nakakonekta galing sa {1}",
+ "UserOfflineFromDevice": "Si {0} ay nadiskonekta galing sa {1}",
+ "UserLockedOutWithName": "Si {0} ay nalock out",
+ "UserDownloadingItemWithValues": "Nagdadownload si {0} ng {1}",
+ "UserDeletedWithName": "Natanggal na is user {0}",
+ "UserCreatedWithName": "Nagawa na si user {0}",
+ "User": "User",
+ "TvShows": "Pelikula",
+ "System": "Sistema",
+ "Sync": "Pag-sync",
+ "SubtitleDownloadFailureFromForItem": "Hindi naidownload ang subtitles {0} para sa {1}",
+ "StartupEmbyServerIsLoading": "Nagloload ang Jellyfin Server. Sandaling maghintay.",
+ "Songs": "Kanta",
+ "Shows": "Pelikula",
+ "ServerNameNeedsToBeRestarted": "Kailangan irestart ang {0}",
+ "ScheduledTaskStartedWithName": "Nagsimula na ang {0}",
+ "ScheduledTaskFailedWithName": "Hindi gumana and {0}",
+ "ProviderValue": "Ang provider ay {0}",
+ "PluginUpdatedWithName": "Naiupdate na ang {0}",
+ "PluginUninstalledWithName": "Naiuninstall na ang {0}",
+ "PluginInstalledWithName": "Nainstall na ang {0}",
+ "Plugin": "Plugin",
+ "Playlists": "Playlists",
+ "Photos": "Larawan",
+ "NotificationOptionVideoPlaybackStopped": "Huminto na ang pelikula",
+ "NotificationOptionVideoPlayback": "Nagsimula na ang pelikula",
+ "NotificationOptionUserLockedOut": "Nakalock out ang user",
+ "NotificationOptionTaskFailed": "Hindi gumana ang scheduled task",
+ "NotificationOptionServerRestartRequired": "Kailangan irestart ang server",
+ "NotificationOptionPluginUpdateInstalled": "Naiupdate na ang plugin",
+ "NotificationOptionPluginUninstalled": "Naiuninstall na ang plugin",
+ "NotificationOptionPluginInstalled": "Nainstall na ang plugin",
+ "NotificationOptionPluginError": "Hindi gumagana ang plugin",
+ "NotificationOptionNewLibraryContent": "May bagong content na naidagdag",
+ "NotificationOptionInstallationFailed": "Hindi nainstall ng mabuti",
+ "NotificationOptionCameraImageUploaded": "Naiupload na ang picture",
+ "NotificationOptionAudioPlaybackStopped": "Huminto na ang patugtog",
+ "NotificationOptionAudioPlayback": "Nagsimula na ang patugtog",
+ "NotificationOptionApplicationUpdateInstalled": "Naiupdate na ang aplikasyon",
+ "NotificationOptionApplicationUpdateAvailable": "May bagong update ang aplikasyon",
+ "NewVersionIsAvailable": "May bagong version ng Jellyfin Server na pwede idownload.",
+ "NameSeasonUnknown": "Hindi alam ang season",
+ "NameSeasonNumber": "Season {0}",
+ "NameInstallFailed": "Hindi nainstall ang {0}",
+ "MusicVideos": "Music video",
+ "Music": "Kanta",
+ "Movies": "Pelikula",
+ "MixedContent": "Halo-halong content",
+ "MessageServerConfigurationUpdated": "Naiupdate na ang server configuration",
+ "MessageNamedServerConfigurationUpdatedWithValue": "Naiupdate na ang server configuration section {0}",
+ "MessageApplicationUpdatedTo": "Ang Jellyfin Server ay naiupdate to {0}",
+ "MessageApplicationUpdated": "Naiupdate na ang Jellyfin Server",
+ "Latest": "Pinakabago",
+ "LabelRunningTimeValue": "Oras: {0}",
+ "LabelIpAddressValue": "Ang IP Address ay {0}",
+ "ItemRemovedWithName": "Naitanggal ang {0} sa library",
+ "ItemAddedWithName": "Naidagdag ang {0} sa library",
+ "Inherit": "Manahin",
+ "HeaderRecordingGroups": "Pagtatalang Grupo",
+ "HeaderNextUp": "Susunod",
+ "HeaderLiveTV": "Live TV",
+ "HeaderFavoriteSongs": "Paboritong Kanta",
+ "HeaderFavoriteShows": "Paboritong Pelikula",
+ "HeaderFavoriteEpisodes": "Paboritong Episodes",
+ "HeaderFavoriteArtists": "Paboritong Artista",
+ "HeaderFavoriteAlbums": "Paboritong Albums",
+ "HeaderContinueWatching": "Ituloy Manood",
+ "HeaderCameraUploads": "Camera Uploads",
+ "HeaderAlbumArtists": "Artista ng Album",
+ "Genres": "Kategorya",
+ "Folders": "Folders",
+ "Favorites": "Paborito",
+ "FailedLoginAttemptWithUserName": "maling login galing {0}",
+ "DeviceOnlineWithName": "nakakonekta si {0}",
+ "DeviceOfflineWithName": "nadiskonekta si {0}",
+ "Collections": "Koleksyon",
+ "ChapterNameValue": "Kabanata {0}",
+ "Channels": "Channel",
+ "CameraImageUploadedFrom": "May bagong larawan na naupload galing {0}",
+ "Books": "Libro",
+ "AuthenticationSucceededWithUserName": "{0} na patunayan",
+ "Artists": "Artista",
+ "Application": "Aplikasyon",
+ "AppDeviceValues": "Aplikasyon: {0}, Aparato: {1}",
+ "Albums": "Albums",
+ "TaskRefreshLibrary": "Suriin ang nasa librerya",
+ "TaskRefreshChapterImagesDescription": "Gumawa ng larawan para sa mga pelikula na may kabanata",
+ "TaskRefreshChapterImages": "Kunin ang mga larawan ng kabanata",
+ "TaskCleanCacheDescription": "Tanggalin ang mga cache file na hindi na kailangan ng systema.",
+ "TasksChannelsCategory": "Palabas sa internet",
+ "TasksLibraryCategory": "Librerya",
+ "TasksMaintenanceCategory": "Pagpapanatili",
+ "HomeVideos": "Sariling pelikula"
+}
diff --git a/Emby.Server.Implementations/Localization/Core/fr-CA.json b/Emby.Server.Implementations/Localization/Core/fr-CA.json
index 4b4db39a8..2c9dae6a1 100644
--- a/Emby.Server.Implementations/Localization/Core/fr-CA.json
+++ b/Emby.Server.Implementations/Localization/Core/fr-CA.json
@@ -76,7 +76,6 @@
"StartupEmbyServerIsLoading": "Le serveur Jellyfin est en cours de chargement. Veuillez réessayer dans quelques instants.",
"SubtitleDownloadFailureForItem": "Subtitles failed to download for {0}",
"SubtitleDownloadFailureFromForItem": "Échec du téléchargement des sous-titres depuis {0} pour {1}",
- "SubtitlesDownloadedForItem": "Les sous-titres de {0} ont été téléchargés",
"Sync": "Synchroniser",
"System": "Système",
"TvShows": "Séries Télé",
@@ -93,5 +92,7 @@
"UserStoppedPlayingItemWithValues": "{0} vient d'arrêter la lecture de {1} sur {2}",
"ValueHasBeenAddedToLibrary": "{0} a été ajouté à votre médiathèque",
"ValueSpecialEpisodeName": "Spécial - {0}",
- "VersionNumber": "Version {0}"
+ "VersionNumber": "Version {0}",
+ "TasksLibraryCategory": "Bibliothèque",
+ "TasksMaintenanceCategory": "Entretien"
}
diff --git a/Emby.Server.Implementations/Localization/Core/fr.json b/Emby.Server.Implementations/Localization/Core/fr.json
index 6b25e3df0..150952d8b 100644
--- a/Emby.Server.Implementations/Localization/Core/fr.json
+++ b/Emby.Server.Implementations/Localization/Core/fr.json
@@ -3,15 +3,15 @@
"AppDeviceValues": "Application : {0}, Appareil : {1}",
"Application": "Application",
"Artists": "Artistes",
- "AuthenticationSucceededWithUserName": "{0} s'est authentifié avec succès",
+ "AuthenticationSucceededWithUserName": "{0} authentifié avec succès",
"Books": "Livres",
- "CameraImageUploadedFrom": "Une nouvelle photo a été chargée depuis {0}",
+ "CameraImageUploadedFrom": "Une nouvelle photographie a été chargée depuis {0}",
"Channels": "Chaînes",
"ChapterNameValue": "Chapitre {0}",
"Collections": "Collections",
"DeviceOfflineWithName": "{0} s'est déconnecté",
"DeviceOnlineWithName": "{0} est connecté",
- "FailedLoginAttemptWithUserName": "Échec d'une tentative de connexion de {0}",
+ "FailedLoginAttemptWithUserName": "Échec de connexion depuis {0}",
"Favorites": "Favoris",
"Folders": "Dossiers",
"Genres": "Genres",
@@ -34,7 +34,7 @@
"LabelRunningTimeValue": "Durée : {0}",
"Latest": "Derniers",
"MessageApplicationUpdated": "Le serveur Jellyfin a été mis à jour",
- "MessageApplicationUpdatedTo": "Le serveur Jellyfin a été mis à jour vers {0}",
+ "MessageApplicationUpdatedTo": "Le serveur Jellyfin a été mis à jour en version {0}",
"MessageNamedServerConfigurationUpdatedWithValue": "La configuration de la section {0} du serveur a été mise à jour",
"MessageServerConfigurationUpdated": "La configuration du serveur a été mise à jour",
"MixedContent": "Contenu mixte",
@@ -69,14 +69,13 @@
"PluginUpdatedWithName": "{0} a été mis à jour",
"ProviderValue": "Fournisseur : {0}",
"ScheduledTaskFailedWithName": "{0} a échoué",
- "ScheduledTaskStartedWithName": "{0} a commencé",
+ "ScheduledTaskStartedWithName": "{0} a démarré",
"ServerNameNeedsToBeRestarted": "{0} doit être redémarré",
"Shows": "Émissions",
"Songs": "Chansons",
"StartupEmbyServerIsLoading": "Le serveur Jellyfin est en cours de chargement. Veuillez réessayer dans quelques instants.",
"SubtitleDownloadFailureForItem": "Le téléchargement des sous-titres pour {0} a échoué.",
"SubtitleDownloadFailureFromForItem": "Échec du téléchargement des sous-titres depuis {0} pour {1}",
- "SubtitlesDownloadedForItem": "Les sous-titres de {0} ont été téléchargés",
"Sync": "Synchroniser",
"System": "Système",
"TvShows": "Séries Télé",
@@ -93,5 +92,27 @@
"UserStoppedPlayingItemWithValues": "{0} vient d'arrêter la lecture de {1} sur {2}",
"ValueHasBeenAddedToLibrary": "{0} a été ajouté à votre médiathèque",
"ValueSpecialEpisodeName": "Spécial - {0}",
- "VersionNumber": "Version {0}"
+ "VersionNumber": "Version {0}",
+ "TasksChannelsCategory": "Chaines en ligne",
+ "TaskDownloadMissingSubtitlesDescription": "Cherche les sous-titres manquant sur internet en se basant sur la configuration des métadonnées.",
+ "TaskDownloadMissingSubtitles": "Télécharger les sous-titres manquant",
+ "TaskRefreshChannelsDescription": "Rafraîchit les informations des chaines en ligne.",
+ "TaskRefreshChannels": "Rafraîchir les chaines",
+ "TaskCleanTranscodeDescription": "Supprime les fichiers transcodés de plus d'un jour.",
+ "TaskCleanTranscode": "Nettoyer les dossier des transcodages",
+ "TaskUpdatePluginsDescription": "Télécharge et installe les mises à jours des extensions configurés pour être mises à jour automatiquement.",
+ "TaskUpdatePlugins": "Mettre à jour les extensions",
+ "TaskRefreshPeopleDescription": "Met à jour les métadonnées pour les acteurs et réalisateurs dans votre bibliothèque.",
+ "TaskRefreshPeople": "Rafraîchir les acteurs",
+ "TaskCleanLogsDescription": "Supprime les journaux de plus de {0} jours.",
+ "TaskCleanLogs": "Nettoyer le répertoire des journaux",
+ "TaskRefreshLibraryDescription": "Scanne toute les bibliothèques pour trouver les nouveaux fichiers et rafraîchit les métadonnées.",
+ "TaskRefreshLibrary": "Scanner toute les Bibliothèques",
+ "TaskRefreshChapterImagesDescription": "Crée des images de miniature pour les vidéos ayant des chapitres.",
+ "TaskRefreshChapterImages": "Extraire les images de chapitre",
+ "TaskCleanCacheDescription": "Supprime les fichiers de cache dont le système n'a plus besoin.",
+ "TaskCleanCache": "Vider le répertoire cache",
+ "TasksApplicationCategory": "Application",
+ "TasksLibraryCategory": "Bibliothèque",
+ "TasksMaintenanceCategory": "Maintenance"
}
diff --git a/Emby.Server.Implementations/Localization/Core/gl.json b/Emby.Server.Implementations/Localization/Core/gl.json
new file mode 100644
index 000000000..94034962d
--- /dev/null
+++ b/Emby.Server.Implementations/Localization/Core/gl.json
@@ -0,0 +1,3 @@
+{
+ "Albums": "Álbumes"
+}
diff --git a/Emby.Server.Implementations/Localization/Core/gsw.json b/Emby.Server.Implementations/Localization/Core/gsw.json
index 69c157401..9611e33f5 100644
--- a/Emby.Server.Implementations/Localization/Core/gsw.json
+++ b/Emby.Server.Implementations/Localization/Core/gsw.json
@@ -76,7 +76,6 @@
"StartupEmbyServerIsLoading": "Jellyfin Server ladt. Bitte grad noeinisch probiere.",
"SubtitleDownloadFailureForItem": "Subtitles failed to download for {0}",
"SubtitleDownloadFailureFromForItem": "Ondertetle vo {0} för {1} hend ned chönne abeglade wärde",
- "SubtitlesDownloadedForItem": "Ondertetle abeglade för {0}",
"Sync": "Synchronisation",
"System": "System",
"TvShows": "Färnsehserie",
diff --git a/Emby.Server.Implementations/Localization/Core/he.json b/Emby.Server.Implementations/Localization/Core/he.json
index b08c8966e..8abe31d2a 100644
--- a/Emby.Server.Implementations/Localization/Core/he.json
+++ b/Emby.Server.Implementations/Localization/Core/he.json
@@ -1,34 +1,34 @@
{
"Albums": "אלבומים",
"AppDeviceValues": "יישום: {0}, מכשיר: {1}",
- "Application": "אפליקציה",
- "Artists": "אמנים",
- "AuthenticationSucceededWithUserName": "{0} זוהה בהצלחה",
+ "Application": "יישום",
+ "Artists": "אומנים",
+ "AuthenticationSucceededWithUserName": "{0} אומת בהצלחה",
"Books": "ספרים",
- "CameraImageUploadedFrom": "תמונה חדשה הועלתה מ{0}",
+ "CameraImageUploadedFrom": "תמונת מצלמה חדשה הועלתה מ {0}",
"Channels": "ערוצים",
"ChapterNameValue": "פרק {0}",
- "Collections": "קולקציות",
+ "Collections": "אוספים",
"DeviceOfflineWithName": "{0} התנתק",
"DeviceOnlineWithName": "{0} מחובר",
"FailedLoginAttemptWithUserName": "ניסיון כניסה שגוי מ{0}",
- "Favorites": "אהובים",
+ "Favorites": "מועדפים",
"Folders": "תיקיות",
"Genres": "ז'אנרים",
"HeaderAlbumArtists": "אמני האלבום",
"HeaderCameraUploads": "העלאות ממצלמה",
"HeaderContinueWatching": "המשך לצפות",
"HeaderFavoriteAlbums": "אלבומים שאהבתי",
- "HeaderFavoriteArtists": "אמנים שאהבתי",
- "HeaderFavoriteEpisodes": "פרקים אהובים",
- "HeaderFavoriteShows": "תוכניות אהובות",
- "HeaderFavoriteSongs": "שירים שאהבתי",
- "HeaderLiveTV": "טלוויזיה בשידור חי",
+ "HeaderFavoriteArtists": "אמנים מועדפים",
+ "HeaderFavoriteEpisodes": "פרקים מועדפים",
+ "HeaderFavoriteShows": "סדרות מועדפות",
+ "HeaderFavoriteSongs": "שירים מועדפים",
+ "HeaderLiveTV": "שידורים חיים",
"HeaderNextUp": "הבא",
"HeaderRecordingGroups": "קבוצות הקלטה",
"HomeVideos": "סרטונים בייתים",
"Inherit": "הורש",
- "ItemAddedWithName": "{0} was added to the library",
+ "ItemAddedWithName": "{0} הוסף לספרייה",
"ItemRemovedWithName": "{0} נמחק מהספרייה",
"LabelIpAddressValue": "Ip כתובת: {0}",
"LabelRunningTimeValue": "משך צפייה: {0}",
@@ -36,15 +36,15 @@
"MessageApplicationUpdated": "שרת הJellyfin עודכן",
"MessageApplicationUpdatedTo": "שרת הJellyfin עודכן לגרסא {0}",
"MessageNamedServerConfigurationUpdatedWithValue": "הגדרת השרת {0} שונתה",
- "MessageServerConfigurationUpdated": "Server configuration has been updated",
+ "MessageServerConfigurationUpdated": "תצורת השרת עודכנה",
"MixedContent": "תוכן מעורב",
"Movies": "סרטים",
"Music": "מוזיקה",
- "MusicVideos": "Music videos",
- "NameInstallFailed": "{0} installation failed",
- "NameSeasonNumber": "Season {0}",
- "NameSeasonUnknown": "Season Unknown",
- "NewVersionIsAvailable": "A new version of Jellyfin Server is available for download.",
+ "MusicVideos": "קליפים",
+ "NameInstallFailed": "התקנת {0} נכשלה",
+ "NameSeasonNumber": "עונה {0}",
+ "NameSeasonUnknown": "עונה לא ידועה",
+ "NewVersionIsAvailable": "גרסה חדשה של שרת Jellyfin זמינה להורדה.",
"NotificationOptionApplicationUpdateAvailable": "Application update available",
"NotificationOptionApplicationUpdateInstalled": "Application update installed",
"NotificationOptionAudioPlayback": "Audio playback started",
@@ -53,16 +53,16 @@
"NotificationOptionInstallationFailed": "התקנה נכשלה",
"NotificationOptionNewLibraryContent": "New content added",
"NotificationOptionPluginError": "Plugin failure",
- "NotificationOptionPluginInstalled": "Plugin installed",
- "NotificationOptionPluginUninstalled": "Plugin uninstalled",
- "NotificationOptionPluginUpdateInstalled": "Plugin update installed",
- "NotificationOptionServerRestartRequired": "Server restart required",
+ "NotificationOptionPluginInstalled": "התוסף הותקן",
+ "NotificationOptionPluginUninstalled": "התוסף הוסר",
+ "NotificationOptionPluginUpdateInstalled": "העדכון לתוסף הותקן",
+ "NotificationOptionServerRestartRequired": "יש לאתחל את השרת",
"NotificationOptionTaskFailed": "Scheduled task failure",
"NotificationOptionUserLockedOut": "User locked out",
"NotificationOptionVideoPlayback": "Video playback started",
"NotificationOptionVideoPlaybackStopped": "Video playback stopped",
"Photos": "תמונות",
- "Playlists": "רשימות ניגון",
+ "Playlists": "רשימות הפעלה",
"Plugin": "Plugin",
"PluginInstalledWithName": "{0} was installed",
"PluginUninstalledWithName": "{0} was uninstalled",
@@ -71,27 +71,33 @@
"ScheduledTaskFailedWithName": "{0} failed",
"ScheduledTaskStartedWithName": "{0} started",
"ServerNameNeedsToBeRestarted": "{0} needs to be restarted",
- "Shows": "Shows",
- "Songs": "Songs",
- "StartupEmbyServerIsLoading": "Jellyfin Server is loading. Please try again shortly.",
+ "Shows": "סדרות",
+ "Songs": "שירים",
+ "StartupEmbyServerIsLoading": "שרת Jellyfin בהליכי טעינה. אנא נסה שנית בעוד זמן קצר.",
"SubtitleDownloadFailureForItem": "Subtitles failed to download for {0}",
"SubtitleDownloadFailureFromForItem": "Subtitles failed to download from {0} for {1}",
- "SubtitlesDownloadedForItem": "Subtitles downloaded for {0}",
"Sync": "סנכרן",
"System": "System",
- "TvShows": "TV Shows",
+ "TvShows": "סדרות טלוויזיה",
"User": "User",
- "UserCreatedWithName": "User {0} has been created",
- "UserDeletedWithName": "User {0} has been deleted",
- "UserDownloadingItemWithValues": "{0} is downloading {1}",
+ "UserCreatedWithName": "המשתמש {0} נוצר",
+ "UserDeletedWithName": "המשתמש {0} הוסר",
+ "UserDownloadingItemWithValues": "{0} מוריד את {1}",
"UserLockedOutWithName": "User {0} has been locked out",
"UserOfflineFromDevice": "{0} has disconnected from {1}",
"UserOnlineFromDevice": "{0} is online from {1}",
"UserPasswordChangedWithName": "Password has been changed for user {0}",
"UserPolicyUpdatedWithName": "User policy has been updated for {0}",
- "UserStartedPlayingItemWithValues": "{0} is playing {1} on {2}",
- "UserStoppedPlayingItemWithValues": "{0} has finished playing {1} on {2}",
+ "UserStartedPlayingItemWithValues": "{0} מנגן את {1} על {2}",
+ "UserStoppedPlayingItemWithValues": "{0} סיים לנגן את {1} על {2}",
"ValueHasBeenAddedToLibrary": "{0} has been added to your media library",
"ValueSpecialEpisodeName": "מיוחד- {0}",
- "VersionNumber": "Version {0}"
+ "VersionNumber": "Version {0}",
+ "TaskRefreshLibrary": "סרוק ספריית מדיה",
+ "TaskRefreshChapterImages": "חלץ תמונות פרקים",
+ "TaskCleanCacheDescription": "מחק קבצי מטמון שלא בשימוש המערכת.",
+ "TaskCleanCache": "נקה תיקיית מטמון",
+ "TasksApplicationCategory": "יישום",
+ "TasksLibraryCategory": "ספרייה",
+ "TasksMaintenanceCategory": "תחזוקה"
}
diff --git a/Emby.Server.Implementations/Localization/Core/hr.json b/Emby.Server.Implementations/Localization/Core/hr.json
index f284b3cd9..6947178d7 100644
--- a/Emby.Server.Implementations/Localization/Core/hr.json
+++ b/Emby.Server.Implementations/Localization/Core/hr.json
@@ -76,7 +76,6 @@
"StartupEmbyServerIsLoading": "Jellyfin Server se učitava. Pokušajte ponovo kasnije.",
"SubtitleDownloadFailureForItem": "Titlovi prijevoda nisu preuzeti za {0}",
"SubtitleDownloadFailureFromForItem": "Subtitles failed to download from {0} for {1}",
- "SubtitlesDownloadedForItem": "Titlovi prijevoda preuzeti za {0}",
"Sync": "Sink.",
"System": "Sistem",
"TvShows": "TV Shows",
diff --git a/Emby.Server.Implementations/Localization/Core/hu.json b/Emby.Server.Implementations/Localization/Core/hu.json
index 3a6852321..c5c3844e3 100644
--- a/Emby.Server.Implementations/Localization/Core/hu.json
+++ b/Emby.Server.Implementations/Localization/Core/hu.json
@@ -1,23 +1,23 @@
{
"Albums": "Albumok",
"AppDeviceValues": "Program: {0}, Eszköz: {1}",
- "Application": "Program",
+ "Application": "Alkalmazás",
"Artists": "Előadók",
"AuthenticationSucceededWithUserName": "{0} sikeresen azonosítva",
"Books": "Könyvek",
"CameraImageUploadedFrom": "Új kamerakép került feltöltésre innen: {0}",
"Channels": "Csatornák",
- "ChapterNameValue": "Jelenet {0}",
+ "ChapterNameValue": "{0}. jelenet",
"Collections": "Gyűjtemények",
"DeviceOfflineWithName": "{0} kijelentkezett",
"DeviceOnlineWithName": "{0} belépett",
- "FailedLoginAttemptWithUserName": "Sikertelen bejelentkezési kísérlet {0}",
+ "FailedLoginAttemptWithUserName": "Sikertelen bejelentkezési kísérlet tőle: {0}",
"Favorites": "Kedvencek",
"Folders": "Könyvtárak",
"Genres": "Műfajok",
"HeaderAlbumArtists": "Album előadók",
"HeaderCameraUploads": "Kamera feltöltések",
- "HeaderContinueWatching": "Folyamatban lévő filmek",
+ "HeaderContinueWatching": "Megtekintés folytatása",
"HeaderFavoriteAlbums": "Kedvenc albumok",
"HeaderFavoriteArtists": "Kedvenc előadók",
"HeaderFavoriteEpisodes": "Kedvenc epizódok",
@@ -27,7 +27,7 @@
"HeaderNextUp": "Következik",
"HeaderRecordingGroups": "Felvételi csoportok",
"HomeVideos": "Házi videók",
- "Inherit": "Inherit",
+ "Inherit": "Örökölt",
"ItemAddedWithName": "{0} hozzáadva a könyvtárhoz",
"ItemRemovedWithName": "{0} eltávolítva a könyvtárból",
"LabelIpAddressValue": "IP cím: {0}",
@@ -42,7 +42,7 @@
"Music": "Zene",
"MusicVideos": "Zenei videók",
"NameInstallFailed": "{0} sikertelen telepítés",
- "NameSeasonNumber": "Évad {0}",
+ "NameSeasonNumber": "{0}. évad",
"NameSeasonUnknown": "Ismeretlen évad",
"NewVersionIsAvailable": "Letölthető a Jellyfin Szerver új verziója.",
"NotificationOptionApplicationUpdateAvailable": "Frissítés érhető el az alkalmazáshoz",
@@ -71,12 +71,11 @@
"ScheduledTaskFailedWithName": "{0} sikertelen",
"ScheduledTaskStartedWithName": "{0} elkezdve",
"ServerNameNeedsToBeRestarted": "{0}-t újra kell indítani",
- "Shows": "Műsorok",
+ "Shows": "Sorozatok",
"Songs": "Dalok",
- "StartupEmbyServerIsLoading": "A Jellyfin Szerver betöltődik. Kérlek próbáld újra később.",
+ "StartupEmbyServerIsLoading": "A Jellyfin Szerver betöltődik. Kérlek, próbáld újra hamarosan.",
"SubtitleDownloadFailureForItem": "Subtitles failed to download for {0}",
"SubtitleDownloadFailureFromForItem": "Nem sikerült a felirat letöltése innen: {0} ehhez: {1}",
- "SubtitlesDownloadedForItem": "Letöltött feliratok a következőhöz: {0}",
"Sync": "Szinkronizál",
"System": "Rendszer",
"TvShows": "TV műsorok",
@@ -86,12 +85,34 @@
"UserDownloadingItemWithValues": "{0} letölti {1}",
"UserLockedOutWithName": "{0} felhasználó zárolva van",
"UserOfflineFromDevice": "{0} kijelentkezett innen: {1}",
- "UserOnlineFromDevice": "{0} online itt: {1}",
+ "UserOnlineFromDevice": "{0} online innen: {1}",
"UserPasswordChangedWithName": "Jelszó megváltozott a következő felhasználó számára: {0}",
"UserPolicyUpdatedWithName": "A felhasználói házirend frissítve lett neki: {0}",
"UserStartedPlayingItemWithValues": "{0} elkezdte játszani a következőt: {1} itt: {2}",
- "UserStoppedPlayingItemWithValues": "{0} befejezte a következőt: {1} itt: {2}",
+ "UserStoppedPlayingItemWithValues": "{0} befejezte {1} lejátászását itt: {2}",
"ValueHasBeenAddedToLibrary": "{0} hozzáadva a médiatárhoz",
"ValueSpecialEpisodeName": "Special - {0}",
- "VersionNumber": "Verzió: {0}"
+ "VersionNumber": "Verzió: {0}",
+ "TaskCleanTranscode": "Átkódolási könyvtár ürítése",
+ "TaskUpdatePluginsDescription": "Letölti és telepíti a frissítéseket azokhoz a bővítményekhez, amelyeknél az automatikus frissítés engedélyezve van.",
+ "TaskUpdatePlugins": "Bővítmények frissítése",
+ "TaskRefreshPeopleDescription": "Frissíti a szereplők és a stábok metaadatait a könyvtáradban.",
+ "TaskRefreshPeople": "Személyek frissítése",
+ "TaskCleanLogsDescription": "Törli azokat a naplófájlokat, amelyek {0} napnál régebbiek.",
+ "TaskCleanLogs": "Naplózási könyvtár ürítése",
+ "TaskRefreshLibraryDescription": "Átvizsgálja a könyvtáraidat új fájlokért és frissíti a metaadatokat.",
+ "TaskRefreshLibrary": "Média könyvtár beolvasása",
+ "TaskRefreshChapterImagesDescription": "Miniatűröket generál olyan videókhoz, amely tartalmaz fejezeteket.",
+ "TaskRefreshChapterImages": "Fejezetek képeinek generálása",
+ "TaskCleanCacheDescription": "Törli azokat a gyorsítótárazott fájlokat, amikre a rendszernek már nincs szüksége.",
+ "TaskCleanCache": "Gyorsítótár könyvtárának ürítése",
+ "TasksChannelsCategory": "Internetes csatornák",
+ "TasksApplicationCategory": "Alkalmazás",
+ "TasksLibraryCategory": "Könyvtár",
+ "TasksMaintenanceCategory": "Karbantartás",
+ "TaskDownloadMissingSubtitlesDescription": "A metaadat konfiguráció alapján ellenőrzi és letölti a hiányzó feliratokat az internetről.",
+ "TaskDownloadMissingSubtitles": "Hiányzó feliratok letöltése",
+ "TaskRefreshChannelsDescription": "Frissíti az internetes csatornák adatait.",
+ "TaskRefreshChannels": "Csatornák frissítése",
+ "TaskCleanTranscodeDescription": "Törli az egy napnál régebbi átkódolási fájlokat."
}
diff --git a/Emby.Server.Implementations/Localization/Core/id.json b/Emby.Server.Implementations/Localization/Core/id.json
index 8d17ad38e..eabdb9138 100644
--- a/Emby.Server.Implementations/Localization/Core/id.json
+++ b/Emby.Server.Implementations/Localization/Core/id.json
@@ -6,7 +6,7 @@
"MessageApplicationUpdatedTo": "Jellyfin Server sudah diperbarui ke {0}",
"MessageApplicationUpdated": "Jellyfin Server sudah diperbarui",
"Latest": "Terbaru",
- "LabelIpAddressValue": "IP address: {0}",
+ "LabelIpAddressValue": "Alamat IP: {0}",
"ItemRemovedWithName": "{0} sudah dikeluarkan dari perpustakaan",
"ItemAddedWithName": "{0} sudah dimasukkan ke dalam perpustakaan",
"Inherit": "Warisan",
@@ -28,5 +28,68 @@
"Collections": "Koleksi",
"Books": "Buku",
"Artists": "Artis",
- "Application": "Aplikasi"
+ "Application": "Aplikasi",
+ "ChapterNameValue": "Bagian {0}",
+ "Channels": "Saluran",
+ "TvShows": "Seri TV",
+ "SubtitleDownloadFailureFromForItem": "Talop gagal diunduh dari {0} untuk {1}",
+ "StartupEmbyServerIsLoading": "Peladen Jellyfin sedang dimuat. Silakan coba kembali beberapa saat lagi.",
+ "Songs": "Lagu",
+ "Playlists": "Daftar putar",
+ "NotificationOptionPluginUninstalled": "Plugin dilepas",
+ "MusicVideos": "Video musik",
+ "VersionNumber": "Versi {0}",
+ "ValueSpecialEpisodeName": "Spesial - {0}",
+ "ValueHasBeenAddedToLibrary": "{0} telah ditambahkan ke pustaka media Anda",
+ "UserStoppedPlayingItemWithValues": "{0} telah selesai memutar {1} pada {2}",
+ "UserStartedPlayingItemWithValues": "{0} sedang memutar {1} pada {2}",
+ "UserPolicyUpdatedWithName": "Kebijakan pengguna telah diperbarui untuk {0}",
+ "UserPasswordChangedWithName": "Kata sandi telah diubah untuk pengguna {0}",
+ "UserOnlineFromDevice": "{0} sedang daring dari {1}",
+ "UserOfflineFromDevice": "{0} telah terputus dari {1}",
+ "UserLockedOutWithName": "Pengguna {0} telah dikunci",
+ "UserDownloadingItemWithValues": "{0} sedang mengunduh {1}",
+ "UserDeletedWithName": "Pengguna {0} telah dihapus",
+ "UserCreatedWithName": "Pengguna {0} telah dibuat",
+ "User": "Pengguna",
+ "System": "Sistem",
+ "Sync": "Sinkron",
+ "Shows": "Tayangan",
+ "ServerNameNeedsToBeRestarted": "{0} perlu dimuat ulang",
+ "ScheduledTaskStartedWithName": "{0} dimulai",
+ "ScheduledTaskFailedWithName": "{0} gagal",
+ "ProviderValue": "Penyedia: {0}",
+ "PluginUpdatedWithName": "{0} telah diperbarui",
+ "PluginInstalledWithName": "{0} telah dipasang",
+ "Plugin": "Plugin",
+ "Photos": "Foto",
+ "NotificationOptionUserLockedOut": "Pengguna terkunci",
+ "NotificationOptionTaskFailed": "Kegagalan tugas terjadwal",
+ "NotificationOptionServerRestartRequired": "Restart peladen dibutuhkan",
+ "NotificationOptionPluginUpdateInstalled": "Pembaruan plugin terpasang",
+ "NotificationOptionPluginInstalled": "Plugin terpasang",
+ "NotificationOptionPluginError": "Kegagalan plugin",
+ "NotificationOptionNewLibraryContent": "Konten baru ditambahkan",
+ "NotificationOptionInstallationFailed": "Kegagalan pemasangan",
+ "NotificationOptionCameraImageUploaded": "Gambar kamera terunggah",
+ "NotificationOptionApplicationUpdateInstalled": "Pembaruan aplikasi terpasang",
+ "NotificationOptionApplicationUpdateAvailable": "Pembaruan aplikasi tersedia",
+ "NewVersionIsAvailable": "Sebuah versi baru dari Peladen Jellyfin tersedia untuk diunduh.",
+ "NameSeasonUnknown": "Musim tak diketahui",
+ "NameSeasonNumber": "Musim {0}",
+ "NameInstallFailed": "{0} instalasi gagal",
+ "Music": "Musik",
+ "Movies": "Film",
+ "MessageServerConfigurationUpdated": "Konfigurasi peladen telah diperbarui",
+ "MessageNamedServerConfigurationUpdatedWithValue": "Konfigurasi peladen bagian {0} telah diperbarui",
+ "FailedLoginAttemptWithUserName": "Percobaan login gagal dari {0}",
+ "CameraImageUploadedFrom": "Sebuah gambar baru telah diunggah dari {0}",
+ "DeviceOfflineWithName": "{0} telah terputus",
+ "DeviceOnlineWithName": "{0} telah terhubung",
+ "NotificationOptionVideoPlaybackStopped": "Pemutaran video berhenti",
+ "NotificationOptionVideoPlayback": "Pemutaran video dimulai",
+ "NotificationOptionAudioPlaybackStopped": "Pemutaran audio berhenti",
+ "NotificationOptionAudioPlayback": "Pemutaran audio dimulai",
+ "MixedContent": "Konten campur",
+ "PluginUninstalledWithName": "{0} telah dihapus"
}
diff --git a/Emby.Server.Implementations/Localization/Core/is.json b/Emby.Server.Implementations/Localization/Core/is.json
index c3b5211b8..ef2a57e8e 100644
--- a/Emby.Server.Implementations/Localization/Core/is.json
+++ b/Emby.Server.Implementations/Localization/Core/is.json
@@ -3,7 +3,7 @@
"ItemRemovedWithName": "{0} var fjarlægt úr safninu",
"ItemAddedWithName": "{0} var bætt í safnið",
"Inherit": "Erfa",
- "HomeVideos": "Myndbönd að heiman",
+ "HomeVideos": "Heimamyndbönd",
"HeaderRecordingGroups": "Upptökuhópar",
"HeaderNextUp": "Næst á dagskrá",
"HeaderLiveTV": "Sjónvarp í beinni útsendingu",
@@ -36,10 +36,10 @@
"NotificationOptionVideoPlaybackStopped": "Myndbandafspilun stöðvuð",
"NotificationOptionVideoPlayback": "Myndbandafspilun hafin",
"NotificationOptionUserLockedOut": "Notandi læstur úti",
- "NotificationOptionServerRestartRequired": "Endurræsing miðlara nauðsynileg",
+ "NotificationOptionServerRestartRequired": "Endurræsing þjóns er nauðsynileg",
"NotificationOptionPluginUpdateInstalled": "Viðbótar uppfærsla uppsett",
"NotificationOptionPluginUninstalled": "Viðbót fjarlægð",
- "NotificationOptionPluginInstalled": "Viðbót settur upp",
+ "NotificationOptionPluginInstalled": "Viðbót sett upp",
"NotificationOptionPluginError": "Bilun í viðbót",
"NotificationOptionInstallationFailed": "Uppsetning tókst ekki",
"NotificationOptionCameraImageUploaded": "Myndavélarmynd hlaðið upp",
@@ -50,15 +50,15 @@
"NameSeasonUnknown": "Sería óþekkt",
"NameSeasonNumber": "Sería {0}",
"MixedContent": "Blandað efni",
- "MessageServerConfigurationUpdated": "Stillingar miðlarans hefur verið uppfærð",
- "MessageApplicationUpdatedTo": "Jellyfin Server hefur verið uppfærður í {0}",
- "MessageApplicationUpdated": "Jellyfin Server hefur verið uppfærður",
+ "MessageServerConfigurationUpdated": "Stillingar þjóns hafa verið uppfærðar",
+ "MessageApplicationUpdatedTo": "Jellyfin þjónn hefur verið uppfærður í {0}",
+ "MessageApplicationUpdated": "Jellyfin þjónn hefur verið uppfærður",
"Latest": "Nýjasta",
- "LabelRunningTimeValue": "Keyrslutími kerfis: {0}",
+ "LabelRunningTimeValue": "spilunartími: {0}",
"User": "Notandi",
"System": "Kerfi",
"NotificationOptionNewLibraryContent": "Nýju efni bætt við",
- "NewVersionIsAvailable": "Ný útgáfa af Jellyfin Server er fáanleg til niðurhals.",
+ "NewVersionIsAvailable": "Ný útgáfa af Jellyfin þjón er fáanleg til niðurhals.",
"NameInstallFailed": "{0} uppsetning mistókst",
"MusicVideos": "Tónlistarmyndbönd",
"Music": "Tónlist",
@@ -74,5 +74,22 @@
"PluginUpdatedWithName": "{0} var uppfært",
"PluginUninstalledWithName": "{0} var fjarlægt",
"PluginInstalledWithName": "{0} var sett upp",
- "NotificationOptionTaskFailed": "Tímasett verkefni mistókst"
+ "NotificationOptionTaskFailed": "Tímasett verkefni mistókst",
+ "StartupEmbyServerIsLoading": "Jellyfin netþjónnin er að hlaðast. Vinsamlega prufaðu aftur fljótlega.",
+ "VersionNumber": "Útgáfa {0}",
+ "ValueHasBeenAddedToLibrary": "{0} hefur verið bætt við í gagnasafnið þitt",
+ "UserStoppedPlayingItemWithValues": "{0} hefur lokið spilunar af {1} á {2}",
+ "UserStartedPlayingItemWithValues": "{0} er að spila {1} á {2}",
+ "UserPolicyUpdatedWithName": "Notandaregla hefur verið uppfærð fyrir notanda {0}",
+ "UserPasswordChangedWithName": "Lykilorði fyrir notandann {0} hefur verið breytt",
+ "UserOnlineFromDevice": "{0} hefur verið virkur síðan {1}",
+ "UserOfflineFromDevice": "{0} hefur aftengst frá {1}",
+ "UserLockedOutWithName": "Notanda {0} hefur verið hindraður aðgangur",
+ "UserDownloadingItemWithValues": "{0} Hleður niður {1}",
+ "SubtitleDownloadFailureFromForItem": "Tókst ekki að hala niður skjátextum frá {0} til {1}",
+ "ProviderValue": "Veitandi: {0}",
+ "MessageNamedServerConfigurationUpdatedWithValue": "Stilling {0} hefur verið uppfærð á netþjón",
+ "ValueSpecialEpisodeName": "Sérstakt - {0}",
+ "Shows": "Þættir",
+ "Playlists": "Spilunarlisti"
}
diff --git a/Emby.Server.Implementations/Localization/Core/it.json b/Emby.Server.Implementations/Localization/Core/it.json
index 8f91effb9..0758bbe9c 100644
--- a/Emby.Server.Implementations/Localization/Core/it.json
+++ b/Emby.Server.Implementations/Localization/Core/it.json
@@ -5,20 +5,20 @@
"Artists": "Artisti",
"AuthenticationSucceededWithUserName": "{0} autenticato con successo",
"Books": "Libri",
- "CameraImageUploadedFrom": "È stata caricata una nuova immagine della fotocamera {0}",
+ "CameraImageUploadedFrom": "È stata caricata una nuova immagine della fotocamera dal device {0}",
"Channels": "Canali",
"ChapterNameValue": "Capitolo {0}",
"Collections": "Collezioni",
- "DeviceOfflineWithName": "{0} ha disconnesso",
+ "DeviceOfflineWithName": "{0} si è disconnesso",
"DeviceOnlineWithName": "{0} è connesso",
"FailedLoginAttemptWithUserName": "Tentativo di accesso fallito da {0}",
"Favorites": "Preferiti",
"Folders": "Cartelle",
"Genres": "Generi",
- "HeaderAlbumArtists": "Artisti dell' Album",
+ "HeaderAlbumArtists": "Artisti degli Album",
"HeaderCameraUploads": "Caricamenti Fotocamera",
"HeaderContinueWatching": "Continua a guardare",
- "HeaderFavoriteAlbums": "Album preferiti",
+ "HeaderFavoriteAlbums": "Album Preferiti",
"HeaderFavoriteArtists": "Artisti Preferiti",
"HeaderFavoriteEpisodes": "Episodi Preferiti",
"HeaderFavoriteShows": "Serie TV Preferite",
@@ -76,7 +76,6 @@
"StartupEmbyServerIsLoading": "Jellyfin server si sta avviando. Per favore riprova più tardi.",
"SubtitleDownloadFailureForItem": "Impossibile scaricare i sottotitoli per {0}",
"SubtitleDownloadFailureFromForItem": "Impossibile scaricare i sottotitoli da {0} per {1}",
- "SubtitlesDownloadedForItem": "Sottotitoli scaricati per {0}",
"Sync": "Sincronizza",
"System": "Sistema",
"TvShows": "Serie TV",
@@ -93,5 +92,27 @@
"UserStoppedPlayingItemWithValues": "{0} ha interrotto la riproduzione di {1} su {2}",
"ValueHasBeenAddedToLibrary": "{0} è stato aggiunto alla tua libreria multimediale",
"ValueSpecialEpisodeName": "Speciale - {0}",
- "VersionNumber": "Versione {0}"
+ "VersionNumber": "Versione {0}",
+ "TaskRefreshChannelsDescription": "Aggiorna le informazioni dei canali Internet.",
+ "TaskDownloadMissingSubtitlesDescription": "Cerca su internet i sottotitoli mancanti basandosi sulle configurazioni dei metadati.",
+ "TaskDownloadMissingSubtitles": "Scarica i sottotitoli mancanti",
+ "TaskRefreshChannels": "Aggiorna i canali",
+ "TaskCleanTranscodeDescription": "Cancella i file di transcode più vecchi di un giorno.",
+ "TaskCleanTranscode": "Svuota la cartella del transcoding",
+ "TaskUpdatePluginsDescription": "Scarica e installa gli aggiornamenti per i plugin che sono stati configurati per essere aggiornati contemporaneamente.",
+ "TaskUpdatePlugins": "Aggiorna i Plugin",
+ "TaskRefreshPeopleDescription": "Aggiorna i metadati per gli attori e registi nella tua libreria multimediale.",
+ "TaskRefreshPeople": "Aggiorna persone",
+ "TaskCleanLogsDescription": "Rimuovi i file di log più vecchi di {0} giorni.",
+ "TaskCleanLogs": "Pulisci la cartella dei log",
+ "TaskRefreshLibraryDescription": "Analizza la tua libreria multimediale per nuovi file e rinnova i metadati.",
+ "TaskRefreshLibrary": "Analizza la libreria dei contenuti multimediali",
+ "TaskRefreshChapterImagesDescription": "Crea le thumbnail per i video che hanno capitoli.",
+ "TaskRefreshChapterImages": "Estrai immagini capitolo",
+ "TaskCleanCacheDescription": "Cancella i file di cache non più necessari al sistema.",
+ "TaskCleanCache": "Pulisci la directory della cache",
+ "TasksChannelsCategory": "Canali su Internet",
+ "TasksApplicationCategory": "Applicazione",
+ "TasksLibraryCategory": "Libreria",
+ "TasksMaintenanceCategory": "Manutenzione"
}
diff --git a/Emby.Server.Implementations/Localization/Core/ja.json b/Emby.Server.Implementations/Localization/Core/ja.json
index 4aa0637c5..a4d9f9ef6 100644
--- a/Emby.Server.Implementations/Localization/Core/ja.json
+++ b/Emby.Server.Implementations/Localization/Core/ja.json
@@ -75,7 +75,6 @@
"Songs": "曲",
"StartupEmbyServerIsLoading": "Jellyfin Server は現在読み込み中です。しばらくしてからもう一度お試しください。",
"SubtitleDownloadFailureFromForItem": "{0} から {1}の字幕のダウンロードに失敗しました",
- "SubtitlesDownloadedForItem": "{0} の字幕がダウンロードされました",
"Sync": "同期",
"System": "システム",
"TvShows": "テレビ番組",
@@ -92,5 +91,27 @@
"UserStoppedPlayingItemWithValues": "{0} は{2}で{1} の再生が終わりました",
"ValueHasBeenAddedToLibrary": "{0}はあなたのメディアライブラリに追加されました",
"ValueSpecialEpisodeName": "スペシャル - {0}",
- "VersionNumber": "バージョン {0}"
+ "VersionNumber": "バージョン {0}",
+ "TaskCleanLogsDescription": "{0} 日以上前のログを消去します。",
+ "TaskCleanLogs": "ログの掃除",
+ "TaskRefreshLibraryDescription": "メディアライブラリをスキャンして新しいファイルを探し、メタデータをリフレッシュします。",
+ "TaskRefreshLibrary": "メディアライブラリのスキャン",
+ "TaskCleanCacheDescription": "不要なキャッシュを消去します。",
+ "TaskCleanCache": "キャッシュの掃除",
+ "TasksChannelsCategory": "ネットチャンネル",
+ "TasksApplicationCategory": "アプリケーション",
+ "TasksLibraryCategory": "ライブラリ",
+ "TasksMaintenanceCategory": "メンテナンス",
+ "TaskRefreshChannelsDescription": "ネットチャンネルの情報をリフレッシュします。",
+ "TaskRefreshChannels": "チャンネルのリフレッシュ",
+ "TaskCleanTranscodeDescription": "1日以上経過したトランスコードファイルを削除します。",
+ "TaskCleanTranscode": "トランスコードディレクトリの削除",
+ "TaskUpdatePluginsDescription": "自動更新可能なプラグインのアップデートをダウンロードしてインストールします。",
+ "TaskUpdatePlugins": "プラグインの更新",
+ "TaskRefreshPeopleDescription": "メディアライブラリで俳優や監督のメタデータを更新します。",
+ "TaskRefreshPeople": "俳優や監督のデータの更新",
+ "TaskDownloadMissingSubtitlesDescription": "メタデータ構成に基づいて、欠落している字幕をインターネットで検索します。",
+ "TaskRefreshChapterImagesDescription": "チャプターのあるビデオのサムネイルを作成します。",
+ "TaskRefreshChapterImages": "チャプター画像を抽出する",
+ "TaskDownloadMissingSubtitles": "不足している字幕をダウンロードする"
}
diff --git a/Emby.Server.Implementations/Localization/Core/kk.json b/Emby.Server.Implementations/Localization/Core/kk.json
index cbee71155..5618ff4a8 100644
--- a/Emby.Server.Implementations/Localization/Core/kk.json
+++ b/Emby.Server.Implementations/Localization/Core/kk.json
@@ -76,7 +76,6 @@
"StartupEmbyServerIsLoading": "Jellyfin Server júktelýde. Áreketti kóp uzamaı qaıtalańyz.",
"SubtitleDownloadFailureForItem": "Субтитрлер {0} үшін жүктеліп алынуы сәтсіз",
"SubtitleDownloadFailureFromForItem": "{1} úshin sýbtıtrlerdi {0} kózinen júktep alý sátsiz",
- "SubtitlesDownloadedForItem": "{0} úshin sýbtıtrler júktelip alyndy",
"Sync": "Úndestirý",
"System": "Júıe",
"TvShows": "TD-kórsetimder",
diff --git a/Emby.Server.Implementations/Localization/Core/ko.json b/Emby.Server.Implementations/Localization/Core/ko.json
index 0320a0a1b..9e3ecd5a8 100644
--- a/Emby.Server.Implementations/Localization/Core/ko.json
+++ b/Emby.Server.Implementations/Localization/Core/ko.json
@@ -76,7 +76,6 @@
"StartupEmbyServerIsLoading": "Jellyfin 서버를 불러오고 있습니다. 잠시 후에 다시 시도하십시오.",
"SubtitleDownloadFailureForItem": "Subtitles failed to download for {0}",
"SubtitleDownloadFailureFromForItem": "{0}에서 {1} 자막 다운로드에 실패했습니다",
- "SubtitlesDownloadedForItem": "{0} 자막 다운로드 완료",
"Sync": "동기화",
"System": "시스템",
"TvShows": "TV 쇼",
@@ -93,5 +92,27 @@
"UserStoppedPlayingItemWithValues": "{2}에서 {0}이 {1} 재생을 마침",
"ValueHasBeenAddedToLibrary": "{0}가 미디어 라이브러리에 추가되었습니다",
"ValueSpecialEpisodeName": "스페셜 - {0}",
- "VersionNumber": "버전 {0}"
+ "VersionNumber": "버전 {0}",
+ "TasksApplicationCategory": "어플리케이션",
+ "TasksMaintenanceCategory": "유지 보수",
+ "TaskDownloadMissingSubtitlesDescription": "메타 데이터 기반으로 누락 된 자막이 있는지 인터넷을 검색합니다.",
+ "TaskDownloadMissingSubtitles": "누락 된 자막 다운로드",
+ "TaskRefreshChannelsDescription": "인터넷 채널 정보를 새로 고칩니다.",
+ "TaskRefreshChannels": "채널 새로고침",
+ "TaskCleanTranscodeDescription": "하루 이상 지난 트랜스 코드 파일을 삭제합니다.",
+ "TaskCleanTranscode": "트랜스코드 폴더 청소",
+ "TaskUpdatePluginsDescription": "자동으로 업데이트되도록 구성된 플러그인 업데이트를 다운로드하여 설치합니다.",
+ "TaskUpdatePlugins": "플러그인 업데이트",
+ "TaskRefreshPeopleDescription": "미디어 라이브러리에서 배우 및 감독의 메타 데이터를 업데이트합니다.",
+ "TaskRefreshPeople": "인물 새로고침",
+ "TaskCleanLogsDescription": "{0} 일이 지난 로그 파일을 삭제합니다.",
+ "TaskCleanLogs": "로그 폴더 청소",
+ "TaskRefreshLibraryDescription": "미디어 라이브러리에서 새 파일을 검색하고 메타 데이터를 새로 고칩니다.",
+ "TaskRefreshLibrary": "미디어 라이브러리 스캔",
+ "TaskRefreshChapterImagesDescription": "챕터가있는 비디오의 썸네일을 만듭니다.",
+ "TaskRefreshChapterImages": "챕터 이미지 추출",
+ "TaskCleanCacheDescription": "시스템에서 더 이상 필요하지 않은 캐시 파일을 삭제합니다.",
+ "TaskCleanCache": "캐시 폴더 청소",
+ "TasksChannelsCategory": "인터넷 채널",
+ "TasksLibraryCategory": "라이브러리"
}
diff --git a/Emby.Server.Implementations/Localization/Core/lt-LT.json b/Emby.Server.Implementations/Localization/Core/lt-LT.json
index e8e1b7740..01a740187 100644
--- a/Emby.Server.Implementations/Localization/Core/lt-LT.json
+++ b/Emby.Server.Implementations/Localization/Core/lt-LT.json
@@ -76,7 +76,6 @@
"StartupEmbyServerIsLoading": "Jellyfin Server kraunasi. Netrukus pabandykite dar kartą.",
"SubtitleDownloadFailureForItem": "Subtitles failed to download for {0}",
"SubtitleDownloadFailureFromForItem": "{1} subtitrai buvo nesėkmingai parsiųsti iš {0}",
- "SubtitlesDownloadedForItem": "{0} subtitrai parsiųsti",
"Sync": "Sinchronizuoti",
"System": "System",
"TvShows": "TV Serialai",
diff --git a/Emby.Server.Implementations/Localization/Core/lv.json b/Emby.Server.Implementations/Localization/Core/lv.json
new file mode 100644
index 000000000..dbcf17287
--- /dev/null
+++ b/Emby.Server.Implementations/Localization/Core/lv.json
@@ -0,0 +1,117 @@
+{
+ "ServerNameNeedsToBeRestarted": "{0} ir vajadzīgs restarts",
+ "NotificationOptionTaskFailed": "Plānota uzdevuma kļūme",
+ "HeaderRecordingGroups": "Ierakstu Grupas",
+ "UserPolicyUpdatedWithName": "Lietotāju politika atjaunota priekš {0}",
+ "SubtitleDownloadFailureFromForItem": "Subtitru lejupielāde no {0} priekš {1} neizdevās",
+ "NotificationOptionVideoPlaybackStopped": "Video atskaņošana apturēta",
+ "NotificationOptionVideoPlayback": "Video atskaņošana sākta",
+ "NotificationOptionInstallationFailed": "Instalācija neizdevās",
+ "AuthenticationSucceededWithUserName": "{0} veiksmīgi autentificējies",
+ "ValueSpecialEpisodeName": "Speciālais - {0}",
+ "ScheduledTaskStartedWithName": "{0} iesākts",
+ "ScheduledTaskFailedWithName": "{0} neizdevās",
+ "Photos": "Attēli",
+ "NotificationOptionUserLockedOut": "Lietotājs bloķēts",
+ "LabelRunningTimeValue": "Garums: {0}",
+ "Inherit": "Mantot",
+ "AppDeviceValues": "Lietotne:{0}, Ierīce:{1}",
+ "VersionNumber": "Versija {0}",
+ "ValueHasBeenAddedToLibrary": "{0} ir ticis pievienots tavai multvides bibliotēkai",
+ "UserStoppedPlayingItemWithValues": "{0} ir beidzis atskaņot {1} uz {2}",
+ "UserStartedPlayingItemWithValues": "{0} atskaņo {1} uz {2}",
+ "UserPasswordChangedWithName": "Parole nomainīta lietotājam {0}",
+ "UserOnlineFromDevice": "{0} ir tiešsaistē no {1}",
+ "UserOfflineFromDevice": "{0} ir atvienojies no {1}",
+ "UserLockedOutWithName": "Lietotājs {0} ir ticis bloķēts",
+ "UserDownloadingItemWithValues": "{0} lejupielādē {1}",
+ "UserDeletedWithName": "Lietotājs {0} ir izdzēsts",
+ "UserCreatedWithName": "Lietotājs {0} ir ticis izveidots",
+ "User": "Lietotājs",
+ "TvShows": "TV Raidījumi",
+ "Sync": "Sinhronizācija",
+ "System": "Sistēma",
+ "StartupEmbyServerIsLoading": "Jellyfin Serveris lādējas. Lūdzu mēģiniet vēlreiz pēc brīža.",
+ "Songs": "Dziesmas",
+ "Shows": "Raidījumi",
+ "PluginUpdatedWithName": "{0} tika atjaunots",
+ "PluginUninstalledWithName": "{0} tika noņemts",
+ "PluginInstalledWithName": "{0} tika uzstādīts",
+ "Plugin": "Paplašinājums",
+ "Playlists": "Atskaņošanas Saraksti",
+ "MixedContent": "Jaukts saturs",
+ "HomeVideos": "Mājas Video",
+ "HeaderNextUp": "Nākamais",
+ "ChapterNameValue": "Nodaļa {0}",
+ "Application": "Lietotne",
+ "NotificationOptionServerRestartRequired": "Vajadzīgs servera restarts",
+ "NotificationOptionPluginUpdateInstalled": "Paplašinājuma atjauninājums uzstādīts",
+ "NotificationOptionPluginUninstalled": "Paplašinājums noņemts",
+ "NotificationOptionPluginInstalled": "Paplašinājums uzstādīts",
+ "NotificationOptionPluginError": "Paplašinājuma kļūda",
+ "NotificationOptionNewLibraryContent": "Jauns saturs pievienots",
+ "NotificationOptionCameraImageUploaded": "Kameras attēls augšupielādēts",
+ "NotificationOptionAudioPlaybackStopped": "Audio atskaņošana apturēta",
+ "NotificationOptionAudioPlayback": "Audio atskaņošana sākta",
+ "NotificationOptionApplicationUpdateInstalled": "Lietotnes atjauninājums uzstādīts",
+ "NotificationOptionApplicationUpdateAvailable": "Lietotnes atjauninājums pieejams",
+ "NewVersionIsAvailable": "Lejupielādei ir pieejama jauna Jellyfin Server versija.",
+ "NameSeasonUnknown": "Nezināma Sezona",
+ "NameSeasonNumber": "Sezona {0}",
+ "NameInstallFailed": "{0} instalācija neizdevās",
+ "MusicVideos": "Mūzikas video",
+ "Music": "Mūzika",
+ "Movies": "Filmas",
+ "MessageServerConfigurationUpdated": "Servera konfigurācija ir tikusi atjaunota",
+ "MessageNamedServerConfigurationUpdatedWithValue": "Servera konfigurācijas sadaļa {0} ir tikusi atjaunota",
+ "MessageApplicationUpdatedTo": "Jellyfin Server ir ticis atjaunots uz {0}",
+ "MessageApplicationUpdated": "Jellyfin Server ir ticis atjaunots",
+ "Latest": "Jaunākais",
+ "LabelIpAddressValue": "IP adrese: {0}",
+ "ItemRemovedWithName": "{0} tika noņemts no bibliotēkas",
+ "ItemAddedWithName": "{0} tika pievienots bibliotēkai",
+ "HeaderLiveTV": "Tiešraides TV",
+ "HeaderContinueWatching": "Turpināt Skatīšanos",
+ "HeaderCameraUploads": "Kameras augšupielādes",
+ "HeaderAlbumArtists": "Albumu Izpildītāji",
+ "Genres": "Žanri",
+ "Folders": "Mapes",
+ "Favorites": "Favorīti",
+ "FailedLoginAttemptWithUserName": "Neizdevies pieslēgšanās mēģinājums no {0}",
+ "DeviceOnlineWithName": "{0} ir pievienojies",
+ "DeviceOfflineWithName": "{0} ir atvienojies",
+ "Collections": "Kolekcijas",
+ "Channels": "Kanāli",
+ "CameraImageUploadedFrom": "Jauns kameras attēls ir ticis augšupielādēts no {0}",
+ "Books": "Grāmatas",
+ "Artists": "Izpildītāji",
+ "Albums": "Albumi",
+ "ProviderValue": "Provider: {0}",
+ "HeaderFavoriteSongs": "Dziesmu Favorīti",
+ "HeaderFavoriteShows": "Raidījumu Favorīti",
+ "HeaderFavoriteEpisodes": "Episožu Favorīti",
+ "HeaderFavoriteArtists": "Izpildītāju Favorīti",
+ "HeaderFavoriteAlbums": "Albumu Favorīti",
+ "TaskCleanCacheDescription": "Nodzēš keša datnes, kas vairs nav sistēmai vajadzīgas.",
+ "TaskRefreshChapterImages": "Izvilkt Nodaļu Attēlus",
+ "TasksApplicationCategory": "Lietotne",
+ "TasksLibraryCategory": "Bibliotēka",
+ "TaskDownloadMissingSubtitlesDescription": "Internetā meklē trūkstošus subtitrus pēc metadatu uzstādījumiem.",
+ "TaskDownloadMissingSubtitles": "Lejupielādēt trūkstošus subtitrus",
+ "TaskRefreshChannelsDescription": "Atjauno interneta kanālu informāciju.",
+ "TaskRefreshChannels": "Atjaunot Kanālus",
+ "TaskCleanTranscodeDescription": "Izdzēš trans-kodēšanas datnes, kas ir vecākas par vienu dienu.",
+ "TaskCleanTranscode": "Iztīrīt Trans-kodēšanas Mapi",
+ "TaskUpdatePluginsDescription": "Lejupielādē un uzstāda atjauninājumus paplašinājumiem, kam ir uzstādīta automātiskā atjaunināšana.",
+ "TaskUpdatePlugins": "Atjaunot Paplašinājumus",
+ "TaskRefreshPeopleDescription": "Atjauno metadatus priekš aktieriem un direktoriem tavā mediju bibliotēkā.",
+ "TaskRefreshPeople": "Atjaunot Cilvēkus",
+ "TaskCleanLogsDescription": "Nodzēš log datnes, kas ir vairāk par {0} dienām vecas.",
+ "TaskCleanLogs": "Iztīrīt Logdatņu Mapi",
+ "TaskRefreshLibraryDescription": "Skenē tavas mediju bibliotēkas priekš jaunām datnēm un atjauno metadatus.",
+ "TaskRefreshLibrary": "Skanēt Mediju Bibliotēku",
+ "TaskRefreshChapterImagesDescription": "Izveido sīktēlus priekš video ar sadaļām.",
+ "TaskCleanCache": "Iztīrīt Kešošanas Mapi",
+ "TasksChannelsCategory": "Interneta Kanāli",
+ "TasksMaintenanceCategory": "Apkope"
+}
diff --git a/Emby.Server.Implementations/Localization/Core/mk.json b/Emby.Server.Implementations/Localization/Core/mk.json
new file mode 100644
index 000000000..8df137302
--- /dev/null
+++ b/Emby.Server.Implementations/Localization/Core/mk.json
@@ -0,0 +1,95 @@
+{
+ "ScheduledTaskFailedWithName": "{0} неуспешно",
+ "ProviderValue": "Провајдер: {0}",
+ "PluginUpdatedWithName": "{0} беше надоградено",
+ "PluginUninstalledWithName": "{0} беше успешно деинсталирано",
+ "PluginInstalledWithName": "{0} беше успешно инсталирано",
+ "Plugin": "Додатоци",
+ "Playlists": "Листи",
+ "Photos": "Слики",
+ "NotificationOptionVideoPlaybackStopped": "Видео стопирано",
+ "NotificationOptionVideoPlayback": "Видео пуштено",
+ "NotificationOptionUserLockedOut": "Корисникот е ослободен",
+ "NotificationOptionTaskFailed": "Закажани задачи неуспешно",
+ "NotificationOptionServerRestartRequired": "Задолжително рестартирање на серверот",
+ "NotificationOptionPluginUpdateInstalled": "Надоградба на Додаток успешна",
+ "NotificationOptionPluginUninstalled": "Додаток успешно деинсталиран",
+ "NotificationOptionPluginInstalled": "Додаток успешно инсталиран",
+ "NotificationOptionPluginError": "Грешка на додаток",
+ "NotificationOptionNewLibraryContent": "Додадена нова содржина",
+ "NotificationOptionInstallationFailed": "Неуспешна Инсталација",
+ "NotificationOptionCameraImageUploaded": "Слика од камера поставена",
+ "NotificationOptionAudioPlaybackStopped": "Аудио стопирано",
+ "NotificationOptionAudioPlayback": "Аудио стартувано",
+ "NotificationOptionApplicationUpdateInstalled": "Надоградбата на Апликацијата е иснталирана",
+ "NotificationOptionApplicationUpdateAvailable": "Возможна надоградба на Апликацијата",
+ "NewVersionIsAvailable": "Нова верзија од Jellyfin е возможна за спуштање.",
+ "NameSeasonUnknown": "Непозната Сезона",
+ "NameSeasonNumber": "Сезона {0}",
+ "NameInstallFailed": "{0} неуспешна инсталација",
+ "MusicVideos": "Музички видеа",
+ "Music": "Музика",
+ "Movies": "Филмови",
+ "MixedContent": "Мешана содржина",
+ "MessageServerConfigurationUpdated": "Серверската конфигурација беше надградена",
+ "MessageNamedServerConfigurationUpdatedWithValue": "Секцијата на конфигурација на сервер {0} беше надоградена",
+ "MessageApplicationUpdatedTo": "Jellyfin беше надограден до {0}",
+ "MessageApplicationUpdated": "Jellyfin Серверот беше надограден",
+ "Latest": "Последно",
+ "LabelRunningTimeValue": "Време на работа: {0}",
+ "LabelIpAddressValue": "ИП Адреса: {0}",
+ "ItemRemovedWithName": "{0} е избришано до Библиотеката",
+ "ItemAddedWithName": "{0} беше додадено во Библиотеката",
+ "Inherit": "Следно",
+ "HomeVideos": "Домашни Видеа",
+ "HeaderRecordingGroups": "Групи на снимање",
+ "HeaderNextUp": "Следно",
+ "HeaderLiveTV": "ТВ",
+ "HeaderFavoriteSongs": "Омилени Песни",
+ "HeaderFavoriteShows": "Омилени Серии",
+ "HeaderFavoriteEpisodes": "Омилени Епизоди",
+ "HeaderFavoriteArtists": "Омилени Изведувачи",
+ "HeaderFavoriteAlbums": "Омилени Албуми",
+ "HeaderContinueWatching": "Продолжи со гледање",
+ "HeaderCameraUploads": "Поставувања од камера",
+ "HeaderAlbumArtists": "Изведувачи од Албуми",
+ "Genres": "Жанрови",
+ "Folders": "Папки",
+ "Favorites": "Омилени",
+ "FailedLoginAttemptWithUserName": "Неуспешно поврзување од {0}",
+ "DeviceOnlineWithName": "{0} е приклучен",
+ "DeviceOfflineWithName": "{0} се исклучи",
+ "Collections": "Колекции",
+ "ChapterNameValue": "Дел {0}",
+ "Channels": "Канали",
+ "CameraImageUploadedFrom": "Нова слика од камера беше поставена од {0}",
+ "Books": "Книги",
+ "AuthenticationSucceededWithUserName": "{0} успешно поврзан",
+ "Artists": "Изведувач",
+ "Application": "Апликација",
+ "AppDeviceValues": "Аплиакција: {0}, Уред: {1}",
+ "Albums": "Албуми",
+ "VersionNumber": "Верзија {0}",
+ "ValueSpecialEpisodeName": "Специјално - {0}",
+ "ValueHasBeenAddedToLibrary": "{0} е додадено во твојата библиотека",
+ "UserStoppedPlayingItemWithValues": "{0} заврши со репродукција {1} во {2}",
+ "UserStartedPlayingItemWithValues": "{0} пушти {1} на {2}",
+ "UserPolicyUpdatedWithName": "Полисата на користење беше надоградена за {0}",
+ "UserPasswordChangedWithName": "Лозинката е сменета за корисникот {0}",
+ "UserOnlineFromDevice": "{0} е приклучен од {1}",
+ "UserOfflineFromDevice": "{0} е дисконектиран од {1}",
+ "UserLockedOutWithName": "Корисникот {0} е заклучен",
+ "UserDownloadingItemWithValues": "{0} се спушта {1}",
+ "UserDeletedWithName": "Корисникот {0} е избришан",
+ "UserCreatedWithName": "Корисникот {0} е креиран",
+ "User": "Корисник",
+ "TvShows": "ТВ Серии",
+ "System": "Систем",
+ "Sync": "Синхронизација",
+ "SubtitleDownloadFailureFromForItem": "Преводот неуспешно се спушти од {0} за {1}",
+ "StartupEmbyServerIsLoading": "Jellyfin Server се пушта. Ве молиме причекајте.",
+ "Songs": "Песни",
+ "Shows": "Серии",
+ "ServerNameNeedsToBeRestarted": "{0} треба да се рестартира",
+ "ScheduledTaskStartedWithName": "{0} започна"
+}
diff --git a/Emby.Server.Implementations/Localization/Core/mr.json b/Emby.Server.Implementations/Localization/Core/mr.json
new file mode 100644
index 000000000..50b6360d8
--- /dev/null
+++ b/Emby.Server.Implementations/Localization/Core/mr.json
@@ -0,0 +1,61 @@
+{
+ "Books": "पुस्तकं",
+ "Artists": "संगीतकार",
+ "Albums": "अल्बम",
+ "Playlists": "प्लेलिस्ट",
+ "HeaderAlbumArtists": "अल्बम संगीतकार",
+ "Folders": "फोल्डर",
+ "HeaderFavoriteEpisodes": "आवडते भाग",
+ "HeaderFavoriteSongs": "आवडती गाणी",
+ "Movies": "चित्रपट",
+ "HeaderFavoriteArtists": "आवडते संगीतकार",
+ "Shows": "कार्यक्रम",
+ "HeaderFavoriteAlbums": "आवडते अल्बम",
+ "Channels": "वाहिन्या",
+ "ValueSpecialEpisodeName": "विशेष - {0}",
+ "HeaderFavoriteShows": "आवडते कार्यक्रम",
+ "Favorites": "आवडीचे",
+ "HeaderNextUp": "यानंतर",
+ "Songs": "गाणी",
+ "HeaderLiveTV": "लाइव्ह टीव्ही",
+ "Genres": "जाँनरे",
+ "Photos": "चित्र",
+ "TaskDownloadMissingSubtitles": "नसलेले सबटायटल डाउनलोड करा",
+ "TaskCleanTranscodeDescription": "एक दिवसापेक्षा जुन्या ट्रान्सकोड फायली काढून टाका.",
+ "TaskCleanTranscode": "ट्रान्सकोड डिरेक्टरी साफ करून टाका",
+ "TaskUpdatePlugins": "प्लगइन अपडेट करा",
+ "TaskCleanLogs": "लॉग डिरेक्टरी साफ करून टाका",
+ "TaskCleanCache": "कॅश डिरेक्टरी साफ करून टाका",
+ "TasksChannelsCategory": "इंटरनेट वाहिन्या",
+ "TasksApplicationCategory": "अ‍ॅप्लिकेशन",
+ "TasksLibraryCategory": "संग्रहालय",
+ "VersionNumber": "आवृत्ती {0}",
+ "UserPasswordChangedWithName": "{0} या प्रयोक्त्याचे पासवर्ड बदलण्यात आले आहे",
+ "UserOnlineFromDevice": "{0} हे {1} येथून ऑनलाइन आहेत",
+ "UserDeletedWithName": "प्रयोक्ता {0} काढून टाकण्यात आले आहे",
+ "UserCreatedWithName": "प्रयोक्ता {0} बनवण्यात आले आहे",
+ "User": "प्रयोक्ता",
+ "TvShows": "टीव्ही कार्यक्रम",
+ "StartupEmbyServerIsLoading": "जेलिफिन सर्व्हर लोड होत आहे. कृपया थोड्या वेळात पुन्हा प्रयत्न करा.",
+ "Plugin": "प्लगइन",
+ "NotificationOptionCameraImageUploaded": "कॅमेरा चित्र अपलोड केले आहे",
+ "NotificationOptionApplicationUpdateInstalled": "अ‍ॅप्लिकेशन अपडेट इन्स्टॉल केले आहे",
+ "NotificationOptionApplicationUpdateAvailable": "अ‍ॅप्लिकेशन अपडेट उपलब्ध आहे",
+ "NewVersionIsAvailable": "जेलिफिन सर्व्हरची एक नवीन आवृत्ती डाउनलोड करण्यास उपलब्ध आहे.",
+ "NameSeasonUnknown": "अज्ञात सीझन",
+ "NameSeasonNumber": "सीझन {0}",
+ "MusicVideos": "संगीत व्हिडीयो",
+ "Music": "संगीत",
+ "MessageApplicationUpdatedTo": "जेलिफिन सर्व्हर अपडेट होऊन {0} आवृत्तीवर पोहोचला आहे",
+ "MessageApplicationUpdated": "जेलिफिन सर्व्हर अपडेट केला गेला आहे",
+ "Latest": "नवीनतम",
+ "LabelIpAddressValue": "आयपी पत्ता: {0}",
+ "ItemRemovedWithName": "{0} हे संग्रहालयातून काढून टाकण्यात आले",
+ "ItemAddedWithName": "{0} हे संग्रहालयात जोडले गेले",
+ "HomeVideos": "घरचे व्हिडीयो",
+ "HeaderRecordingGroups": "रेकॉर्डिंग गट",
+ "HeaderCameraUploads": "कॅमेरा अपलोड",
+ "CameraImageUploadedFrom": "एक नवीन कॅमेरा चित्र {0} येथून अपलोड केले आहे",
+ "Application": "अ‍ॅप्लिकेशन",
+ "AppDeviceValues": "अ‍ॅप: {0}, यंत्र: {1}"
+}
diff --git a/Emby.Server.Implementations/Localization/Core/ms.json b/Emby.Server.Implementations/Localization/Core/ms.json
index b91c98ca0..79d078d4a 100644
--- a/Emby.Server.Implementations/Localization/Core/ms.json
+++ b/Emby.Server.Implementations/Localization/Core/ms.json
@@ -1,9 +1,9 @@
{
"Albums": "Album-album",
- "AppDeviceValues": "App: {0}, Device: {1}",
+ "AppDeviceValues": "Apl: {0}, Peranti: {1}",
"Application": "Aplikasi",
- "Artists": "Artis-artis",
- "AuthenticationSucceededWithUserName": "{0} successfully authenticated",
+ "Artists": "Artis",
+ "AuthenticationSucceededWithUserName": "{0} berjaya disahkan",
"Books": "Buku-buku",
"CameraImageUploadedFrom": "A new camera image has been uploaded from {0}",
"Channels": "Saluran",
@@ -30,7 +30,7 @@
"Inherit": "Inherit",
"ItemAddedWithName": "{0} was added to the library",
"ItemRemovedWithName": "{0} was removed from the library",
- "LabelIpAddressValue": "Ip address: {0}",
+ "LabelIpAddressValue": "Alamat IP: {0}",
"LabelRunningTimeValue": "Running time: {0}",
"Latest": "Latest",
"MessageApplicationUpdated": "Jellyfin Server has been updated",
@@ -50,7 +50,7 @@
"NotificationOptionAudioPlayback": "Audio playback started",
"NotificationOptionAudioPlaybackStopped": "Audio playback stopped",
"NotificationOptionCameraImageUploaded": "Camera image uploaded",
- "NotificationOptionInstallationFailed": "Installation failure",
+ "NotificationOptionInstallationFailed": "Pemasangan gagal",
"NotificationOptionNewLibraryContent": "New content added",
"NotificationOptionPluginError": "Plugin failure",
"NotificationOptionPluginInstalled": "Plugin installed",
@@ -76,7 +76,6 @@
"StartupEmbyServerIsLoading": "Jellyfin Server is loading. Please try again shortly.",
"SubtitleDownloadFailureForItem": "Subtitles failed to download for {0}",
"SubtitleDownloadFailureFromForItem": "Subtitles failed to download from {0} for {1}",
- "SubtitlesDownloadedForItem": "Subtitles downloaded for {0}",
"Sync": "Sync",
"System": "Sistem",
"TvShows": "TV Shows",
diff --git a/Emby.Server.Implementations/Localization/Core/nb.json b/Emby.Server.Implementations/Localization/Core/nb.json
index 7d4b2bdac..50d0d083c 100644
--- a/Emby.Server.Implementations/Localization/Core/nb.json
+++ b/Emby.Server.Implementations/Localization/Core/nb.json
@@ -62,7 +62,7 @@
"NotificationOptionVideoPlayback": "Videoavspilling startet",
"NotificationOptionVideoPlaybackStopped": "Videoavspilling stoppet",
"Photos": "Bilder",
- "Playlists": "Spliielister",
+ "Playlists": "Spillelister",
"Plugin": "Plugin",
"PluginInstalledWithName": "{0} ble installert",
"PluginUninstalledWithName": "{0} ble avinstallert",
@@ -76,7 +76,6 @@
"StartupEmbyServerIsLoading": "Jellyfin Server laster. Prøv igjen snart.",
"SubtitleDownloadFailureForItem": "En feil oppstå under nedlasting av undertekster for {0}",
"SubtitleDownloadFailureFromForItem": "Kunne ikke laste ned undertekster fra {0} for {1}",
- "SubtitlesDownloadedForItem": "Undertekster lastet ned for {0}",
"Sync": "Synkroniser",
"System": "System",
"TvShows": "TV-serier",
@@ -93,5 +92,10 @@
"UserStoppedPlayingItemWithValues": "{0} har stoppet avspilling {1}",
"ValueHasBeenAddedToLibrary": "{0} har blitt lagt til i mediebiblioteket ditt",
"ValueSpecialEpisodeName": "Spesialepisode - {0}",
- "VersionNumber": "Versjon {0}"
+ "VersionNumber": "Versjon {0}",
+ "TasksChannelsCategory": "Internett kanaler",
+ "TasksApplicationCategory": "Applikasjon",
+ "TasksLibraryCategory": "Bibliotek",
+ "TasksMaintenanceCategory": "Vedlikehold",
+ "TaskCleanCache": "Tøm buffer katalog"
}
diff --git a/Emby.Server.Implementations/Localization/Core/nl.json b/Emby.Server.Implementations/Localization/Core/nl.json
index 4423b7f98..baa12e98e 100644
--- a/Emby.Server.Implementations/Localization/Core/nl.json
+++ b/Emby.Server.Implementations/Localization/Core/nl.json
@@ -1,15 +1,15 @@
{
"Albums": "Albums",
"AppDeviceValues": "App: {0}, Apparaat: {1}",
- "Application": "Applicatie",
+ "Application": "Programma",
"Artists": "Artiesten",
"AuthenticationSucceededWithUserName": "{0} is succesvol geverifieerd",
"Books": "Boeken",
- "CameraImageUploadedFrom": "Er is een nieuwe foto toegevoegd van {0}",
+ "CameraImageUploadedFrom": "Er is een nieuwe afbeelding toegevoegd via {0}",
"Channels": "Kanalen",
"ChapterNameValue": "Hoofdstuk {0}",
"Collections": "Verzamelingen",
- "DeviceOfflineWithName": "{0} heeft de verbinding verbroken",
+ "DeviceOfflineWithName": "Verbinding met {0} is verbroken",
"DeviceOnlineWithName": "{0} is verbonden",
"FailedLoginAttemptWithUserName": "Mislukte aanmeld poging van {0}",
"Favorites": "Favorieten",
@@ -76,7 +76,6 @@
"StartupEmbyServerIsLoading": "Jellyfin Server is aan het laden, probeer het later opnieuw.",
"SubtitleDownloadFailureForItem": "Downloaden van ondertiteling voor {0} is mislukt",
"SubtitleDownloadFailureFromForItem": "Ondertitels konden niet gedownload worden van {0} voor {1}",
- "SubtitlesDownloadedForItem": "Ondertiteling voor {0} is gedownload",
"Sync": "Synchronisatie",
"System": "Systeem",
"TvShows": "TV-series",
@@ -93,5 +92,27 @@
"UserStoppedPlayingItemWithValues": "{0} heeft afspelen van {1} gestopt op {2}",
"ValueHasBeenAddedToLibrary": "{0} is toegevoegd aan je mediabibliotheek",
"ValueSpecialEpisodeName": "Speciaal - {0}",
- "VersionNumber": "Versie {0}"
+ "VersionNumber": "Versie {0}",
+ "TaskDownloadMissingSubtitlesDescription": "Zoekt op het internet naar missende ondertitels gebaseerd op metadata configuratie.",
+ "TaskDownloadMissingSubtitles": "Download missende ondertitels",
+ "TaskRefreshChannelsDescription": "Vernieuwt informatie van internet kanalen.",
+ "TaskRefreshChannels": "Vernieuw Kanalen",
+ "TaskCleanTranscodeDescription": "Verwijder transcode bestanden ouder dan 1 dag.",
+ "TaskCleanLogs": "Log Folder Opschonen",
+ "TaskCleanTranscode": "Transcode Folder Opschonen",
+ "TaskUpdatePluginsDescription": "Download en installeert updates voor plugins waar automatisch updaten aan staat.",
+ "TaskUpdatePlugins": "Update Plugins",
+ "TaskRefreshPeopleDescription": "Update metadata for acteurs en regisseurs in de media bibliotheek.",
+ "TaskRefreshPeople": "Vernieuw Personen",
+ "TaskCleanLogsDescription": "Verwijdert log bestanden ouder dan {0} dagen.",
+ "TaskRefreshLibraryDescription": "Scant de media bibliotheek voor nieuwe bestanden en vernieuwt de metadata.",
+ "TaskRefreshLibrary": "Scan Media Bibliotheek",
+ "TaskRefreshChapterImagesDescription": "Maakt thumbnails aan voor videos met hoofdstukken.",
+ "TaskRefreshChapterImages": "Hoofdstukafbeeldingen Uitpakken",
+ "TaskCleanCacheDescription": "Verwijder gecachte bestanden die het systeem niet langer nodig heeft.",
+ "TaskCleanCache": "Cache Folder Opschonen",
+ "TasksChannelsCategory": "Internet Kanalen",
+ "TasksApplicationCategory": "Applicatie",
+ "TasksLibraryCategory": "Bibliotheek",
+ "TasksMaintenanceCategory": "Onderhoud"
}
diff --git a/Emby.Server.Implementations/Localization/Core/nn.json b/Emby.Server.Implementations/Localization/Core/nn.json
new file mode 100644
index 000000000..281cadac5
--- /dev/null
+++ b/Emby.Server.Implementations/Localization/Core/nn.json
@@ -0,0 +1,60 @@
+{
+ "MessageServerConfigurationUpdated": "Tenar konfigurasjonen har blitt oppdatert",
+ "MessageNamedServerConfigurationUpdatedWithValue": "Tenar konfigurasjon seksjon {0} har blitt oppdatert",
+ "MessageApplicationUpdatedTo": "Jellyfin Tenaren har blitt oppdatert til {0}",
+ "MessageApplicationUpdated": "Jellyfin Tenaren har blitt oppdatert",
+ "Latest": "Nyaste",
+ "LabelRunningTimeValue": "Speletid: {0}",
+ "LabelIpAddressValue": "IP adresse: {0}",
+ "ItemRemovedWithName": "{0} vart fjerna frå biblioteket",
+ "ItemAddedWithName": "{0} vart lagt til i biblioteket",
+ "Inherit": "Arv",
+ "HomeVideos": "Heime Videoar",
+ "HeaderRecordingGroups": "Innspelingsgrupper",
+ "HeaderNextUp": "Neste",
+ "HeaderLiveTV": "Direkte TV",
+ "HeaderFavoriteSongs": "Favoritt Songar",
+ "HeaderFavoriteShows": "Favoritt Seriar",
+ "HeaderFavoriteEpisodes": "Favoritt Episodar",
+ "HeaderFavoriteArtists": "Favoritt Artistar",
+ "HeaderFavoriteAlbums": "Favoritt Album",
+ "HeaderContinueWatching": "Fortsett å sjå",
+ "HeaderCameraUploads": "Kamera Opplastingar",
+ "HeaderAlbumArtists": "Album Artist",
+ "Genres": "Sjangrar",
+ "Folders": "Mapper",
+ "Favorites": "Favorittar",
+ "FailedLoginAttemptWithUserName": "Mislukka påloggingsforsøk frå {0}",
+ "DeviceOnlineWithName": "{0} er tilkopla",
+ "DeviceOfflineWithName": "{0} har kopla frå",
+ "Collections": "Samlingar",
+ "ChapterNameValue": "Kapittel {0}",
+ "Channels": "Kanalar",
+ "CameraImageUploadedFrom": "Eit nytt kamera bilete har blitt lasta opp frå {0}",
+ "Books": "Bøker",
+ "AuthenticationSucceededWithUserName": "{0} Har logga inn",
+ "Artists": "Artistar",
+ "Application": "Program",
+ "AppDeviceValues": "App: {0}, Einheit: {1}",
+ "Albums": "Album",
+ "NotificationOptionServerRestartRequired": "Tenaren krev omstart",
+ "NotificationOptionPluginUpdateInstalled": "Tilleggsprogram-oppdatering vart installert",
+ "NotificationOptionPluginUninstalled": "Tilleggsprogram avinstallert",
+ "NotificationOptionPluginInstalled": "Tilleggsprogram installert",
+ "NotificationOptionPluginError": "Tilleggsprogram feila",
+ "NotificationOptionNewLibraryContent": "Nytt innhald er lagt til",
+ "NotificationOptionInstallationFailed": "Installasjonen feila",
+ "NotificationOptionCameraImageUploaded": "Kamerabilde vart lasta opp",
+ "NotificationOptionAudioPlaybackStopped": "Lydavspilling stoppa",
+ "NotificationOptionAudioPlayback": "Lydavspilling påbyrja",
+ "NotificationOptionApplicationUpdateInstalled": "Applikasjonsoppdatering er installert",
+ "NotificationOptionApplicationUpdateAvailable": "Applikasjonsoppdatering er tilgjengeleg",
+ "NewVersionIsAvailable": "Ein ny versjon av Jellyfin serveren er tilgjengeleg for nedlasting.",
+ "NameSeasonUnknown": "Ukjend sesong",
+ "NameSeasonNumber": "Sesong {0}",
+ "NameInstallFailed": "{0} Installasjonen feila",
+ "MusicVideos": "Musikkvideoar",
+ "Music": "Musikk",
+ "Movies": "Filmar",
+ "MixedContent": "Blanda innhald"
+}
diff --git a/Emby.Server.Implementations/Localization/Core/pl.json b/Emby.Server.Implementations/Localization/Core/pl.json
index e72f1a262..bdc0d0169 100644
--- a/Emby.Server.Implementations/Localization/Core/pl.json
+++ b/Emby.Server.Implementations/Localization/Core/pl.json
@@ -76,7 +76,6 @@
"StartupEmbyServerIsLoading": "Trwa wczytywanie serwera Jellyfin. Spróbuj ponownie za chwilę.",
"SubtitleDownloadFailureForItem": "Pobieranie napisów dla {0} zakończone niepowodzeniem",
"SubtitleDownloadFailureFromForItem": "Nieudane pobieranie napisów z {0} dla {1}",
- "SubtitlesDownloadedForItem": "Pobrano napisy dla {0}",
"Sync": "Synchronizacja",
"System": "System",
"TvShows": "Seriale",
@@ -93,5 +92,27 @@
"UserStoppedPlayingItemWithValues": "{0} zakończył odtwarzanie {1} na {2}",
"ValueHasBeenAddedToLibrary": "{0} został dodany do biblioteki mediów",
"ValueSpecialEpisodeName": "Specjalne - {0}",
- "VersionNumber": "Wersja {0}"
+ "VersionNumber": "Wersja {0}",
+ "TaskDownloadMissingSubtitlesDescription": "Przeszukuje internet w poszukiwaniu brakujących napisów w oparciu o konfigurację metadanych.",
+ "TaskDownloadMissingSubtitles": "Pobierz brakujące napisy",
+ "TaskRefreshChannelsDescription": "Odświeża informacje o kanałach internetowych.",
+ "TaskRefreshChannels": "Odśwież kanały",
+ "TaskCleanTranscodeDescription": "Usuwa transkodowane pliki starsze niż 1 dzień.",
+ "TaskCleanTranscode": "Wyczyść folder transkodowania",
+ "TaskUpdatePluginsDescription": "Pobiera i instaluje aktualizacje dla pluginów które są skonfigurowane do automatycznej aktualizacji.",
+ "TaskUpdatePlugins": "Aktualizuj pluginy",
+ "TaskRefreshPeopleDescription": "Odświeża metadane o aktorów i reżyserów w Twojej bibliotece mediów.",
+ "TaskRefreshPeople": "Odśwież obsadę",
+ "TaskCleanLogsDescription": "Kasuje pliki logów starsze niż {0} dni.",
+ "TaskCleanLogs": "Wyczyść folder logów",
+ "TaskRefreshLibraryDescription": "Skanuje Twoją bibliotekę mediów dla nowych plików i odświeżenia metadanych.",
+ "TaskRefreshLibrary": "Skanuj bibliotekę mediów",
+ "TaskRefreshChapterImagesDescription": "Tworzy miniatury dla filmów posiadających rozdziały.",
+ "TaskRefreshChapterImages": "Wydobądź grafiki rozdziałów",
+ "TaskCleanCacheDescription": "Usuwa niepotrzebne i przestarzałe pliki cache.",
+ "TaskCleanCache": "Wyczyść folder Cache",
+ "TasksChannelsCategory": "Kanały internetowe",
+ "TasksApplicationCategory": "Aplikacja",
+ "TasksLibraryCategory": "Biblioteka",
+ "TasksMaintenanceCategory": "Konserwacja"
}
diff --git a/Emby.Server.Implementations/Localization/Core/pt-BR.json b/Emby.Server.Implementations/Localization/Core/pt-BR.json
index 41a389e3b..3a69b6d7a 100644
--- a/Emby.Server.Implementations/Localization/Core/pt-BR.json
+++ b/Emby.Server.Implementations/Localization/Core/pt-BR.json
@@ -76,7 +76,6 @@
"StartupEmbyServerIsLoading": "O Servidor Jellyfin está carregando. Por favor, tente novamente mais tarde.",
"SubtitleDownloadFailureForItem": "Download de legendas falhou para {0}",
"SubtitleDownloadFailureFromForItem": "Houve um problema ao baixar as legendas de {0} para {1}",
- "SubtitlesDownloadedForItem": "Legendas baixadas para {0}",
"Sync": "Sincronizar",
"System": "Sistema",
"TvShows": "Séries",
@@ -93,5 +92,27 @@
"UserStoppedPlayingItemWithValues": "{0} parou de reproduzir {1} em {2}",
"ValueHasBeenAddedToLibrary": "{0} foi adicionado à sua biblioteca de mídia",
"ValueSpecialEpisodeName": "Especial - {0}",
- "VersionNumber": "Versão {0}"
+ "VersionNumber": "Versão {0}",
+ "TaskDownloadMissingSubtitlesDescription": "Procurar na internet por legendas faltando baseado na configuração de metadados.",
+ "TaskDownloadMissingSubtitles": "Baixar legendas que estão faltando",
+ "TaskRefreshChannelsDescription": "Atualizar informação de canais da internet .",
+ "TaskRefreshChannels": "Atualizar Canais",
+ "TaskCleanTranscodeDescription": "Deletar arquivos de transcodificação com mais de um dia de criação.",
+ "TaskCleanTranscode": "Limpar pasta de transcodificação",
+ "TaskUpdatePluginsDescription": "Baixa e instala atualizações para plugins que estão configurados para atualizar automaticamente.",
+ "TaskUpdatePlugins": "Atualizar Plugins",
+ "TaskRefreshPeopleDescription": "Atualiza metadados para atores e diretores na sua biblioteca de mídia.",
+ "TaskRefreshPeople": "Atualizar pessoas",
+ "TaskCleanLogsDescription": "Deletar arquivos temporários com mais de {0} dias.",
+ "TaskCleanLogs": "Limpar pasta de logs",
+ "TaskRefreshLibraryDescription": "Escaneie a sua biblioteca de mídia para arquivos novos e atualize os metadados.",
+ "TaskRefreshLibrary": "Escanear a Biblioteca de Mídia",
+ "TaskRefreshChapterImagesDescription": "Criar miniaturas para vídeos que tem capítulos.",
+ "TaskRefreshChapterImages": "Extrair imagens dos capítulos",
+ "TaskCleanCacheDescription": "Deletar arquivos temporários que não são mais necessários para o sistema.",
+ "TaskCleanCache": "Limpar Arquivos Temporários",
+ "TasksChannelsCategory": "Canais da Internet",
+ "TasksApplicationCategory": "Aplicativo",
+ "TasksLibraryCategory": "Biblioteca",
+ "TasksMaintenanceCategory": "Manutenção"
}
diff --git a/Emby.Server.Implementations/Localization/Core/pt-PT.json b/Emby.Server.Implementations/Localization/Core/pt-PT.json
index b12d391c1..c1fb65743 100644
--- a/Emby.Server.Implementations/Localization/Core/pt-PT.json
+++ b/Emby.Server.Implementations/Localization/Core/pt-PT.json
@@ -26,7 +26,7 @@
"HeaderLiveTV": "TV em Direto",
"HeaderNextUp": "A Seguir",
"HeaderRecordingGroups": "Grupos de Gravação",
- "HomeVideos": "Home videos",
+ "HomeVideos": "Videos caseiros",
"Inherit": "Herdar",
"ItemAddedWithName": "{0} foi adicionado à biblioteca",
"ItemRemovedWithName": "{0} foi removido da biblioteca",
@@ -76,7 +76,6 @@
"StartupEmbyServerIsLoading": "O servidor Jellyfin está a iniciar. Tente novamente mais tarde.",
"SubtitleDownloadFailureForItem": "Subtitles failed to download for {0}",
"SubtitleDownloadFailureFromForItem": "Falha na transferência de legendas a partir de {0} para {1}",
- "SubtitlesDownloadedForItem": "Transferidas legendas para {0}",
"Sync": "Sincronização",
"System": "Sistema",
"TvShows": "Programas TV",
@@ -93,5 +92,27 @@
"UserStoppedPlayingItemWithValues": "{0} terminou a reprodução de {1} em {2}",
"ValueHasBeenAddedToLibrary": "{0} foi adicionado à sua biblioteca multimédia",
"ValueSpecialEpisodeName": "Especial - {0}",
- "VersionNumber": "Versão {0}"
+ "VersionNumber": "Versão {0}",
+ "TaskDownloadMissingSubtitlesDescription": "Procurar na internet por legendas em falta baseado na configuração de metadados.",
+ "TaskDownloadMissingSubtitles": "Fazer download de legendas em falta",
+ "TaskRefreshChannelsDescription": "Atualizar informação sobre canais da Internet.",
+ "TaskRefreshChannels": "Atualizar Canais",
+ "TaskCleanTranscodeDescription": "Apagar ficheiros de transcode com mais de um dia.",
+ "TaskCleanTranscode": "Limpar a Diretoria de Transcode",
+ "TaskUpdatePluginsDescription": "Faz o download e instala updates para os plugins que estão configurados para atualizar automaticamente.",
+ "TaskUpdatePlugins": "Atualizar Plugins",
+ "TaskRefreshPeopleDescription": "Atualizar metadados para atores e diretores na biblioteca.",
+ "TaskRefreshPeople": "Atualizar Pessoas",
+ "TaskCleanLogsDescription": "Apagar ficheiros de log que têm mais de {0} dias.",
+ "TaskCleanLogs": "Limpar a Diretoria de Logs",
+ "TaskRefreshLibraryDescription": "Scannear a biblioteca de música para novos ficheiros e atualizar os metadados.",
+ "TaskRefreshLibrary": "Scannear Biblioteca de Música",
+ "TaskRefreshChapterImagesDescription": "Criar thumbnails para os vídeos que têm capítulos.",
+ "TaskRefreshChapterImages": "Extrair Imagens dos Capítulos",
+ "TaskCleanCacheDescription": "Apagar ficheiros em cache que já não são necessários.",
+ "TaskCleanCache": "Limpar Cache",
+ "TasksChannelsCategory": "Canais da Internet",
+ "TasksApplicationCategory": "Aplicação",
+ "TasksLibraryCategory": "Biblioteca",
+ "TasksMaintenanceCategory": "Manutenção"
}
diff --git a/Emby.Server.Implementations/Localization/Core/pt.json b/Emby.Server.Implementations/Localization/Core/pt.json
index ef8d988c8..25c5b9053 100644
--- a/Emby.Server.Implementations/Localization/Core/pt.json
+++ b/Emby.Server.Implementations/Localization/Core/pt.json
@@ -1,5 +1,5 @@
{
- "HeaderLiveTV": "TV ao Vivo",
+ "HeaderLiveTV": "TV em Directo",
"Collections": "Colecções",
"Books": "Livros",
"Artists": "Artistas",
@@ -10,13 +10,13 @@
"HeaderFavoriteAlbums": "Álbuns Favoritos",
"HeaderFavoriteEpisodes": "Episódios Favoritos",
"HeaderFavoriteShows": "Séries Favoritas",
- "HeaderContinueWatching": "Continuar a Ver",
+ "HeaderContinueWatching": "Continuar a Assistir",
"HeaderAlbumArtists": "Artistas do Álbum",
"Genres": "Géneros",
- "Folders": "Pastas",
+ "Folders": "Directórios",
"Favorites": "Favoritos",
"Channels": "Canais",
- "UserDownloadingItemWithValues": "{0} está a transferir {1}",
+ "UserDownloadingItemWithValues": "{0} está a ser transferido {1}",
"VersionNumber": "Versão {0}",
"ValueHasBeenAddedToLibrary": "{0} foi adicionado à sua biblioteca multimédia",
"UserStoppedPlayingItemWithValues": "{0} terminou a reprodução de {1} em {2}",
@@ -24,36 +24,35 @@
"UserPolicyUpdatedWithName": "A política do utilizador {0} foi alterada",
"UserPasswordChangedWithName": "A palavra-passe do utilizador {0} foi alterada",
"UserOnlineFromDevice": "{0} ligou-se a partir de {1}",
- "UserOfflineFromDevice": "{0} desligou-se a partir de {1}",
- "UserLockedOutWithName": "Utilizador {0} bloqueado",
- "UserDeletedWithName": "Utilizador {0} removido",
- "UserCreatedWithName": "Utilizador {0} criado",
+ "UserOfflineFromDevice": "{0} desconectou-se a partir de {1}",
+ "UserLockedOutWithName": "O utilizador {0} foi bloqueado",
+ "UserDeletedWithName": "O utilizador {0} foi removido",
+ "UserCreatedWithName": "O utilizador {0} foi criado",
"User": "Utilizador",
- "TvShows": "Programas",
+ "TvShows": "Séries",
"System": "Sistema",
- "SubtitlesDownloadedForItem": "Legendas transferidas para {0}",
"SubtitleDownloadFailureFromForItem": "Falha na transferência de legendas de {0} para {1}",
"StartupEmbyServerIsLoading": "O servidor Jellyfin está a iniciar. Tente novamente dentro de momentos.",
"ServerNameNeedsToBeRestarted": "{0} necessita ser reiniciado",
"ScheduledTaskStartedWithName": "{0} iniciou",
"ScheduledTaskFailedWithName": "{0} falhou",
"ProviderValue": "Fornecedor: {0}",
- "PluginUpdatedWithName": "{0} foi actualizado",
+ "PluginUpdatedWithName": "{0} foi atualizado",
"PluginUninstalledWithName": "{0} foi desinstalado",
"PluginInstalledWithName": "{0} foi instalado",
- "Plugin": "Extensão",
+ "Plugin": "Plugin",
"NotificationOptionVideoPlaybackStopped": "Reprodução de vídeo parada",
"NotificationOptionVideoPlayback": "Reprodução de vídeo iniciada",
"NotificationOptionUserLockedOut": "Utilizador bloqueado",
"NotificationOptionTaskFailed": "Falha em tarefa agendada",
"NotificationOptionServerRestartRequired": "É necessário reiniciar o servidor",
- "NotificationOptionPluginUpdateInstalled": "Extensão actualizada",
- "NotificationOptionPluginUninstalled": "Extensão desinstalada",
- "NotificationOptionPluginInstalled": "Extensão instalada",
- "NotificationOptionPluginError": "Falha na extensão",
+ "NotificationOptionPluginUpdateInstalled": "Plugin actualizado",
+ "NotificationOptionPluginUninstalled": "Plugin desinstalado",
+ "NotificationOptionPluginInstalled": "Plugin instalado",
+ "NotificationOptionPluginError": "Falha no plugin",
"NotificationOptionNewLibraryContent": "Novo conteúdo adicionado",
"NotificationOptionInstallationFailed": "Falha de instalação",
- "NotificationOptionCameraImageUploaded": "Imagem da câmara enviada",
+ "NotificationOptionCameraImageUploaded": "Imagem de câmara enviada",
"NotificationOptionAudioPlaybackStopped": "Reprodução Parada",
"NotificationOptionAudioPlayback": "Reprodução Iniciada",
"NotificationOptionApplicationUpdateInstalled": "A actualização da aplicação foi instalada",
@@ -66,31 +65,43 @@
"Music": "Música",
"MixedContent": "Conteúdo Misto",
"MessageServerConfigurationUpdated": "A configuração do servidor foi actualizada",
- "MessageNamedServerConfigurationUpdatedWithValue": "Configurações do servidor na secção {0} foram atualizadas",
- "MessageApplicationUpdatedTo": "O servidor Jellyfin foi actualizado para a versão {0}",
+ "MessageNamedServerConfigurationUpdatedWithValue": "As configurações do servidor na secção {0} foram atualizadas",
+ "MessageApplicationUpdatedTo": "O servidor Jellyfin foi atualizado para a versão {0}",
"MessageApplicationUpdated": "O servidor Jellyfin foi actualizado",
"Latest": "Mais Recente",
"LabelRunningTimeValue": "Duração: {0}",
- "LabelIpAddressValue": "Endereço IP: {0}",
+ "LabelIpAddressValue": "Endereço de IP: {0}",
"ItemRemovedWithName": "{0} foi removido da biblioteca",
"ItemAddedWithName": "{0} foi adicionado à biblioteca",
"Inherit": "Herdar",
"HomeVideos": "Vídeos Caseiros",
"HeaderRecordingGroups": "Grupos de Gravação",
- "ValueSpecialEpisodeName": "Especial - {0}",
+ "ValueSpecialEpisodeName": "Episódio Especial - {0}",
"Sync": "Sincronização",
"Songs": "Músicas",
"Shows": "Séries",
"Playlists": "Listas de Reprodução",
"Photos": "Fotografias",
"Movies": "Filmes",
- "HeaderCameraUploads": "Envios a partir da câmara",
- "FailedLoginAttemptWithUserName": "Tentativa de ligação a partir de {0} falhou",
- "DeviceOnlineWithName": "{0} ligou-se",
- "DeviceOfflineWithName": "{0} desligou-se",
+ "HeaderCameraUploads": "Carregamentos a partir da câmara",
+ "FailedLoginAttemptWithUserName": "Tentativa de ligação falhada a partir de {0}",
+ "DeviceOnlineWithName": "{0} está connectado",
+ "DeviceOfflineWithName": "{0} desconectou-se",
"ChapterNameValue": "Capítulo {0}",
- "CameraImageUploadedFrom": "Uma nova imagem de câmara foi enviada a partir de {0}",
+ "CameraImageUploadedFrom": "Uma nova imagem da câmara foi enviada a partir de {0}",
"AuthenticationSucceededWithUserName": "{0} autenticado com sucesso",
"Application": "Aplicação",
- "AppDeviceValues": "Aplicação {0}, Dispositivo: {1}"
+ "AppDeviceValues": "Aplicação {0}, Dispositivo: {1}",
+ "TaskCleanCache": "Limpar Diretório de Cache",
+ "TasksApplicationCategory": "Aplicação",
+ "TasksLibraryCategory": "Biblioteca",
+ "TasksMaintenanceCategory": "Manutenção",
+ "TaskRefreshChannels": "Atualizar Canais",
+ "TaskUpdatePlugins": "Atualizar Plugins",
+ "TaskCleanLogsDescription": "Deletar arquivos de log que existe a mais de {0} dias.",
+ "TaskCleanLogs": "Limpar diretório de log",
+ "TaskRefreshLibrary": "Escanear biblioteca de mídias",
+ "TaskRefreshChapterImagesDescription": "Criar miniaturas para videos que tem capítulos.",
+ "TaskCleanCacheDescription": "Deletar arquivos de cache que não são mais usados pelo sistema.",
+ "TasksChannelsCategory": "Canais de Internet"
}
diff --git a/Emby.Server.Implementations/Localization/Core/ro.json b/Emby.Server.Implementations/Localization/Core/ro.json
index b871626f0..699dd26da 100644
--- a/Emby.Server.Implementations/Localization/Core/ro.json
+++ b/Emby.Server.Implementations/Localization/Core/ro.json
@@ -17,7 +17,6 @@
"TvShows": "Spectacole TV",
"System": "Sistem",
"Sync": "Sincronizare",
- "SubtitlesDownloadedForItem": "Subtitrari descarcate pentru {0}",
"SubtitleDownloadFailureFromForItem": "Subtitrările nu au putut fi descărcate de la {0} pentru {1}",
"StartupEmbyServerIsLoading": "Se încarcă serverul Jellyfin. Încercați din nou în scurt timp.",
"Songs": "Melodii",
@@ -65,8 +64,8 @@
"LabelIpAddressValue": "Adresa IP: {0}",
"ItemRemovedWithName": "{0} a fost eliminat din bibliotecă",
"ItemAddedWithName": "{0} a fost adăugat în bibliotecă",
- "Inherit": "moștenit",
- "HomeVideos": "Videoclipuri personale",
+ "Inherit": "Moștenit",
+ "HomeVideos": "Filme personale",
"HeaderRecordingGroups": "Grupuri de înregistrare",
"HeaderLiveTV": "TV în Direct",
"HeaderFavoriteSongs": "Melodii Favorite",
@@ -92,5 +91,27 @@
"Artists": "Artiști",
"Application": "Aplicație",
"AppDeviceValues": "Aplicație: {0}, Dispozitiv: {1}",
- "Albums": "Albume"
+ "Albums": "Albume",
+ "TaskDownloadMissingSubtitlesDescription": "Caută pe internet subtitrările lipsă pe baza configurației metadatelor.",
+ "TaskDownloadMissingSubtitles": "Descarcă subtitrările lipsă",
+ "TaskRefreshChannelsDescription": "Actualizează informațiile despre canalul de internet.",
+ "TaskRefreshChannels": "Actualizează canale",
+ "TaskCleanTranscodeDescription": "Șterge fișierele de transcodare mai vechi de o zi.",
+ "TaskCleanTranscode": "Curățați directorul de transcodare",
+ "TaskUpdatePluginsDescription": "Descarcă și instalează actualizări pentru pluginuri care sunt configurate să se actualizeze automat.",
+ "TaskUpdatePlugins": "Actualizați plugin-uri",
+ "TaskRefreshPeopleDescription": "Actualizează metadatele pentru actori și regizori din biblioteca media.",
+ "TaskRefreshPeople": "Actualizează oamenii",
+ "TaskCleanLogsDescription": "Șterge fișierele jurnal care au mai mult de {0} zile.",
+ "TaskCleanLogs": "Curățare director jurnal",
+ "TaskRefreshLibraryDescription": "Scanează biblioteca media pentru fișiere noi și reîmprospătează metadatele.",
+ "TaskRefreshLibrary": "Scanează Biblioteca Media",
+ "TaskRefreshChapterImagesDescription": "Creează miniaturi pentru videourile care au capitole.",
+ "TaskRefreshChapterImages": "Extrage Imaginile de Capitol",
+ "TaskCleanCacheDescription": "Șterge fișierele cache care nu mai sunt necesare sistemului.",
+ "TaskCleanCache": "Curățați directorul cache",
+ "TasksChannelsCategory": "Canale de pe Internet",
+ "TasksApplicationCategory": "Aplicație",
+ "TasksLibraryCategory": "Librărie",
+ "TasksMaintenanceCategory": "Mentenanță"
}
diff --git a/Emby.Server.Implementations/Localization/Core/ru.json b/Emby.Server.Implementations/Localization/Core/ru.json
index 7cf957a94..71ee6446c 100644
--- a/Emby.Server.Implementations/Localization/Core/ru.json
+++ b/Emby.Server.Implementations/Localization/Core/ru.json
@@ -9,8 +9,8 @@
"Channels": "Каналы",
"ChapterNameValue": "Сцена {0}",
"Collections": "Коллекции",
- "DeviceOfflineWithName": "{0} - подкл. разъ-но",
- "DeviceOnlineWithName": "{0} - подкл. уст-но",
+ "DeviceOfflineWithName": "{0} - отключено",
+ "DeviceOnlineWithName": "{0} - подключено",
"FailedLoginAttemptWithUserName": "{0} - попытка входа неудачна",
"Favorites": "Избранное",
"Folders": "Папки",
@@ -26,30 +26,30 @@
"HeaderLiveTV": "Эфир",
"HeaderNextUp": "Очередное",
"HeaderRecordingGroups": "Группы записей",
- "HomeVideos": "Дом. видео",
+ "HomeVideos": "Домашнее видео",
"Inherit": "Наследуемое",
"ItemAddedWithName": "{0} - добавлено в медиатеку",
"ItemRemovedWithName": "{0} - изъято из медиатеки",
"LabelIpAddressValue": "IP-адрес: {0}",
"LabelRunningTimeValue": "Длительность: {0}",
- "Latest": "Новейшее",
+ "Latest": "Последнее",
"MessageApplicationUpdated": "Jellyfin Server был обновлён",
"MessageApplicationUpdatedTo": "Jellyfin Server был обновлён до {0}",
- "MessageNamedServerConfigurationUpdatedWithValue": "Конфиг-ия сервера (раздел {0}) была обновлена",
- "MessageServerConfigurationUpdated": "Конфиг-ия сервера была обновлена",
+ "MessageNamedServerConfigurationUpdatedWithValue": "Конфигурация сервера (раздел {0}) была обновлена",
+ "MessageServerConfigurationUpdated": "Конфигурация сервера была обновлена",
"MixedContent": "Смешанное содержимое",
"Movies": "Кино",
"Music": "Музыка",
- "MusicVideos": "Муз. видео",
+ "MusicVideos": "Музыкальные клипы",
"NameInstallFailed": "Установка {0} неудачна",
"NameSeasonNumber": "Сезон {0}",
"NameSeasonUnknown": "Сезон неопознан",
"NewVersionIsAvailable": "Новая версия Jellyfin Server доступна для загрузки.",
"NotificationOptionApplicationUpdateAvailable": "Имеется обновление приложения",
"NotificationOptionApplicationUpdateInstalled": "Обновление приложения установлено",
- "NotificationOptionAudioPlayback": "Воспр-ие аудио зап-но",
- "NotificationOptionAudioPlaybackStopped": "Восп-ие аудио ост-но",
- "NotificationOptionCameraImageUploaded": "Произведена выкладка отснятого с камеры",
+ "NotificationOptionAudioPlayback": "Воспроизведение аудио запущено",
+ "NotificationOptionAudioPlaybackStopped": "Воспроизведение аудио остановлено",
+ "NotificationOptionCameraImageUploaded": "Изображения с камеры загружены",
"NotificationOptionInstallationFailed": "Сбой установки",
"NotificationOptionNewLibraryContent": "Новое содержание добавлено",
"NotificationOptionPluginError": "Сбой плагина",
@@ -59,8 +59,8 @@
"NotificationOptionServerRestartRequired": "Требуется перезапуск сервера",
"NotificationOptionTaskFailed": "Сбой назначенной задачи",
"NotificationOptionUserLockedOut": "Пользователь заблокирован",
- "NotificationOptionVideoPlayback": "Воспр-ие видео зап-но",
- "NotificationOptionVideoPlaybackStopped": "Восп-ие видео ост-но",
+ "NotificationOptionVideoPlayback": "Воспроизведение видео запущено",
+ "NotificationOptionVideoPlaybackStopped": "Воспроизведение видео остановлено",
"Photos": "Фото",
"Playlists": "Плей-листы",
"Plugin": "Плагин",
@@ -76,22 +76,43 @@
"StartupEmbyServerIsLoading": "Jellyfin Server загружается. Повторите попытку в ближайшее время.",
"SubtitleDownloadFailureForItem": "Субтитры к {0} не удалось загрузить",
"SubtitleDownloadFailureFromForItem": "Субтитры к {1} не удалось загрузить с {0}",
- "SubtitlesDownloadedForItem": "Субтитры к {0} загружены",
- "Sync": "Синхро",
+ "Sync": "Синхронизация",
"System": "Система",
"TvShows": "ТВ",
- "User": "Польз-ль",
+ "User": "Пользователь",
"UserCreatedWithName": "Пользователь {0} был создан",
"UserDeletedWithName": "Пользователь {0} был удалён",
"UserDownloadingItemWithValues": "{0} загружает {1}",
"UserLockedOutWithName": "Пользователь {0} был заблокирован",
- "UserOfflineFromDevice": "{0} - подкл. с {1} разъ-но",
- "UserOnlineFromDevice": "{0} - подкл. с {1} уст-но",
- "UserPasswordChangedWithName": "Пароль польз-ля {0} был изменён",
- "UserPolicyUpdatedWithName": "Польз-ие политики {0} были обновлены",
- "UserStartedPlayingItemWithValues": "{0} - воспр. «{1}» на {2}",
- "UserStoppedPlayingItemWithValues": "{0} - воспр. «{1}» ост-но на {2}",
+ "UserOfflineFromDevice": "{0} отключился с {1}",
+ "UserOnlineFromDevice": "{0} подключился с {1}",
+ "UserPasswordChangedWithName": "Пароль пользователя {0} был изменён",
+ "UserPolicyUpdatedWithName": "Политики пользователя {0} были обновлены",
+ "UserStartedPlayingItemWithValues": "{0} - воспроизведение «{1}» на {2}",
+ "UserStoppedPlayingItemWithValues": "{0} - воспроизведение остановлено «{1}» на {2}",
"ValueHasBeenAddedToLibrary": "{0} (добавлено в медиатеку)",
- "ValueSpecialEpisodeName": "Спецэпизод - {0}",
- "VersionNumber": "Версия {0}"
+ "ValueSpecialEpisodeName": "Специальный эпизод - {0}",
+ "VersionNumber": "Версия {0}",
+ "TaskDownloadMissingSubtitles": "Загрузка отсутствующих субтитров",
+ "TaskRefreshChannels": "Обновление каналов",
+ "TaskCleanTranscode": "Очистка каталога перекодировки",
+ "TaskUpdatePlugins": "Обновление плагинов",
+ "TaskRefreshPeople": "Обновление метаданных людей",
+ "TaskCleanLogs": "Очистка каталога журналов",
+ "TaskRefreshLibrary": "Сканирование медиатеки",
+ "TaskRefreshChapterImages": "Извлечение изображений сцен",
+ "TaskCleanCache": "Очистка каталога кеша",
+ "TasksChannelsCategory": "Интернет-каналы",
+ "TasksApplicationCategory": "Приложение",
+ "TasksLibraryCategory": "Медиатека",
+ "TasksMaintenanceCategory": "Обслуживание",
+ "TaskDownloadMissingSubtitlesDescription": "Выполняется поиск в Интернете отсутствующих субтитров на основе конфигурации метаданных.",
+ "TaskRefreshChannelsDescription": "Обновляются данные интернет-каналов.",
+ "TaskCleanTranscodeDescription": "Удаляются файлы перекодировки старше одного дня.",
+ "TaskUpdatePluginsDescription": "Загружаются и устанавливаются обновления для плагинов, у которых включено автоматическое обновление.",
+ "TaskRefreshPeopleDescription": "Обновляются метаданные актеров и режиссёров в медиатеке.",
+ "TaskCleanLogsDescription": "Удаляются файлы журнала, возраст которых превышает {0} дн(я/ей).",
+ "TaskRefreshLibraryDescription": "Сканируется медиатека на новые файлы и обновляются метаданные.",
+ "TaskRefreshChapterImagesDescription": "Создаются эскизы для видео, которые содержат сцены.",
+ "TaskCleanCacheDescription": "Удаляются файлы кэша, которые больше не нужны системе."
}
diff --git a/Emby.Server.Implementations/Localization/Core/sk.json b/Emby.Server.Implementations/Localization/Core/sk.json
index 9bac305a2..0ee652637 100644
--- a/Emby.Server.Implementations/Localization/Core/sk.json
+++ b/Emby.Server.Implementations/Localization/Core/sk.json
@@ -15,7 +15,7 @@
"Favorites": "Obľúbené",
"Folders": "Priečinky",
"Genres": "Žánre",
- "HeaderAlbumArtists": "Albumy umelcov",
+ "HeaderAlbumArtists": "Umelci albumu",
"HeaderCameraUploads": "Nahrané fotografie",
"HeaderContinueWatching": "Pokračovať v pozeraní",
"HeaderFavoriteAlbums": "Obľúbené albumy",
@@ -45,25 +45,25 @@
"NameSeasonNumber": "Séria {0}",
"NameSeasonUnknown": "Neznáma séria",
"NewVersionIsAvailable": "Nová verzia Jellyfin Serveru je dostupná na stiahnutie.",
- "NotificationOptionApplicationUpdateAvailable": "Aktualizácia aplikácie je dostupná",
- "NotificationOptionApplicationUpdateInstalled": "Aktualizácia aplikácie nainštalovaná",
- "NotificationOptionAudioPlayback": "Prehrávanie audia bolo spustené",
- "NotificationOptionAudioPlaybackStopped": "Prehrávanie audia bolo zastavené",
+ "NotificationOptionApplicationUpdateAvailable": "Je dostupná aktualizácia aplikácie",
+ "NotificationOptionApplicationUpdateInstalled": "Aktualizácia aplikácie bola nainštalovaná",
+ "NotificationOptionAudioPlayback": "Prehrávanie zvuku bolo spustené",
+ "NotificationOptionAudioPlaybackStopped": "Prehrávanie zvuku bolo zastavené",
"NotificationOptionCameraImageUploaded": "Obrázok z fotoaparátu bol nahraný",
"NotificationOptionInstallationFailed": "Chyba inštalácie",
- "NotificationOptionNewLibraryContent": "Nový obsah bol pridaný",
- "NotificationOptionPluginError": "Chyba rozšírenia",
- "NotificationOptionPluginInstalled": "Rozšírenie nainštalované",
- "NotificationOptionPluginUninstalled": "Rozšírenie odinštalované",
- "NotificationOptionPluginUpdateInstalled": "Aktualizácia rozšírenia nainštalovaná",
+ "NotificationOptionNewLibraryContent": "Bol pridaný nový obsah",
+ "NotificationOptionPluginError": "Chyba zásuvného modulu",
+ "NotificationOptionPluginInstalled": "Bol nainštalovaný zásuvný modul",
+ "NotificationOptionPluginUninstalled": "Zásuvný modul bol odinštalovaný",
+ "NotificationOptionPluginUpdateInstalled": "Bola nainštalovaná aktualizácia zásuvného modulu",
"NotificationOptionServerRestartRequired": "Vyžaduje sa reštart servera",
"NotificationOptionTaskFailed": "Naplánovaná úloha zlyhala",
"NotificationOptionUserLockedOut": "Používateľ je uzamknutý",
"NotificationOptionVideoPlayback": "Spustené prehrávanie videa",
"NotificationOptionVideoPlaybackStopped": "Zastavené prehrávanie videa",
"Photos": "Fotky",
- "Playlists": "Zoznamy skladieb",
- "Plugin": "Rozšírenie",
+ "Playlists": "Playlisty",
+ "Plugin": "Zásuvný modul",
"PluginInstalledWithName": "{0} bol nainštalovaný",
"PluginUninstalledWithName": "{0} bol odinštalovaný",
"PluginUpdatedWithName": "{0} bol aktualizovaný",
@@ -72,11 +72,10 @@
"ScheduledTaskStartedWithName": "{0} zahájených",
"ServerNameNeedsToBeRestarted": "{0} vyžaduje reštart",
"Shows": "Seriály",
- "Songs": "Skladby",
- "StartupEmbyServerIsLoading": "Jellyfin Server sa spúšťa. Skúste to prosím o chvíľu znova.",
+ "Songs": "Piesne",
+ "StartupEmbyServerIsLoading": "Jellyfin Server sa spúšťa. Prosím, skúste to o chvíľu znova.",
"SubtitleDownloadFailureForItem": "Sťahovanie titulkov pre {0} zlyhalo",
"SubtitleDownloadFailureFromForItem": "Sťahovanie titulkov z {0} pre {1} zlyhalo",
- "SubtitlesDownloadedForItem": "Titulky pre {0} stiahnuté",
"Sync": "Synchronizácia",
"System": "Systém",
"TvShows": "TV seriály",
@@ -87,11 +86,33 @@
"UserLockedOutWithName": "Používateľ {0} bol vymknutý",
"UserOfflineFromDevice": "{0} sa odpojil od {1}",
"UserOnlineFromDevice": "{0} je online z {1}",
- "UserPasswordChangedWithName": "Heslo používateľa {0} zmenené",
+ "UserPasswordChangedWithName": "Heslo používateľa {0} bolo zmenené",
"UserPolicyUpdatedWithName": "Používateľské zásady pre {0} boli aktualizované",
- "UserStartedPlayingItemWithValues": "{0} spustil prehrávanie {1}",
- "UserStoppedPlayingItemWithValues": "{0} zastavil prehrávanie {1}",
- "ValueHasBeenAddedToLibrary": "{0} bolo pridané do vašej knižnice médií",
+ "UserStartedPlayingItemWithValues": "{0} spustil prehrávanie {1} na {2}",
+ "UserStoppedPlayingItemWithValues": "{0} ukončil prehrávanie {1} na {2}",
+ "ValueHasBeenAddedToLibrary": "{0} bol pridané do vašej knižnice médií",
"ValueSpecialEpisodeName": "Špeciál - {0}",
- "VersionNumber": "Verzia {0}"
+ "VersionNumber": "Verzia {0}",
+ "TaskDownloadMissingSubtitlesDescription": "Vyhľadá na internete chýbajúce titulky podľa toho, ako sú nakonfigurované metadáta.",
+ "TaskDownloadMissingSubtitles": "Stiahnuť chýbajúce titulky",
+ "TaskRefreshChannelsDescription": "Obnoví informácie o internetových kanáloch.",
+ "TaskRefreshChannels": "Obnoviť kanály",
+ "TaskCleanTranscodeDescription": "Vymaže súbory transkódovania, ktoré sú staršie ako jeden deň.",
+ "TaskCleanTranscode": "Vyčistiť priečinok pre transkódovanie",
+ "TaskUpdatePluginsDescription": "Stiahne a nainštaluje aktualizácie pre zásuvné moduly, ktoré sú nastavené tak, aby sa aktualizovali automaticky.",
+ "TaskUpdatePlugins": "Aktualizovať zásuvné moduly",
+ "TaskRefreshPeopleDescription": "Aktualizuje metadáta pre hercov a režisérov vo vašej mediálnej knižnici.",
+ "TaskRefreshPeople": "Obnoviť osoby",
+ "TaskCleanLogsDescription": "Vymaže log súbory, ktoré su staršie ako {0} deň/dni/dní.",
+ "TaskCleanLogs": "Vyčistiť priečinok s logmi",
+ "TaskRefreshLibraryDescription": "Hľadá vo vašej mediálnej knižnici nové súbory a obnovuje metadáta.",
+ "TaskRefreshLibrary": "Prehľadávať knižnicu medií",
+ "TaskRefreshChapterImagesDescription": "Vytvorí náhľady pre videá, ktoré majú kapitoly.",
+ "TaskRefreshChapterImages": "Extrahovať obrázky kapitol",
+ "TaskCleanCacheDescription": "Vymaže cache súbory, ktoré nie sú už potrebné pre systém.",
+ "TaskCleanCache": "Vyčistiť Cache priečinok",
+ "TasksChannelsCategory": "Internetové kanály",
+ "TasksApplicationCategory": "Aplikácia",
+ "TasksLibraryCategory": "Knižnica",
+ "TasksMaintenanceCategory": "Údržba"
}
diff --git a/Emby.Server.Implementations/Localization/Core/sl-SI.json b/Emby.Server.Implementations/Localization/Core/sl-SI.json
index c141a40f6..b60dd33bd 100644
--- a/Emby.Server.Implementations/Localization/Core/sl-SI.json
+++ b/Emby.Server.Implementations/Localization/Core/sl-SI.json
@@ -12,7 +12,7 @@
"DeviceOfflineWithName": "{0} je prekinil povezavo",
"DeviceOnlineWithName": "{0} je povezan",
"FailedLoginAttemptWithUserName": "Neuspešen poskus prijave z {0}",
- "Favorites": "Priljubljeni",
+ "Favorites": "Priljubljeno",
"Folders": "Mape",
"Genres": "Zvrsti",
"HeaderAlbumArtists": "Izvajalci albuma",
@@ -40,7 +40,7 @@
"MixedContent": "Razne vsebine",
"Movies": "Filmi",
"Music": "Glasba",
- "MusicVideos": "Glasbeni posnetki",
+ "MusicVideos": "Glasbeni videi",
"NameInstallFailed": "{0} namestitev neuspešna",
"NameSeasonNumber": "Sezona {0}",
"NameSeasonUnknown": "Season neznana",
@@ -76,7 +76,6 @@
"StartupEmbyServerIsLoading": "Jellyfin Server se nalaga. Poskusi ponovno kasneje.",
"SubtitleDownloadFailureForItem": "Subtitles failed to download for {0}",
"SubtitleDownloadFailureFromForItem": "Neuspešen prenos podnapisov iz {0} za {1}",
- "SubtitlesDownloadedForItem": "Podnapisi preneseni za {0}",
"Sync": "Sinhroniziraj",
"System": "System",
"TvShows": "TV serije",
diff --git a/Emby.Server.Implementations/Localization/Core/sr.json b/Emby.Server.Implementations/Localization/Core/sr.json
index 0967ef424..5f3cbb1c8 100644
--- a/Emby.Server.Implementations/Localization/Core/sr.json
+++ b/Emby.Server.Implementations/Localization/Core/sr.json
@@ -1 +1,117 @@
-{}
+{
+ "UserPolicyUpdatedWithName": "Корисничке смернице ажуриране за {0}",
+ "NotificationOptionUserLockedOut": "Корисник закључан",
+ "VersionNumber": "Верзија {0}",
+ "ValueSpecialEpisodeName": "Специјал - {0}",
+ "ValueHasBeenAddedToLibrary": "{0} је додато у вашу медијску библиотеку",
+ "UserStoppedPlayingItemWithValues": "{0} заврши пуштање {1} на {2}",
+ "UserStartedPlayingItemWithValues": "{0} пушта {1} на {2}",
+ "UserPasswordChangedWithName": "Лозинка је промењена за корисника {0}",
+ "UserOnlineFromDevice": "{0} је на вези од {1}",
+ "UserOfflineFromDevice": "{0} се одвезао са {1}",
+ "UserLockedOutWithName": "Корисник {0} је закључан",
+ "UserDownloadingItemWithValues": "{0} преузима {1}",
+ "UserDeletedWithName": "Корисник {0} је обрисан",
+ "UserCreatedWithName": "Корисник {0} је направљен",
+ "User": "Корисник",
+ "TvShows": "ТВ серије",
+ "System": "Систем",
+ "Sync": "Усклади",
+ "SubtitleDownloadFailureFromForItem": "Неуспело преузимање титлова за {1} са {0}",
+ "StartupEmbyServerIsLoading": "Џелифин сервер се подиже. Покушајте поново убрзо.",
+ "Songs": "Песме",
+ "Shows": "Серије",
+ "ServerNameNeedsToBeRestarted": "{0} треба поново покренути",
+ "ScheduledTaskStartedWithName": "{0} покренуто",
+ "ScheduledTaskFailedWithName": "{0} неуспело",
+ "ProviderValue": "Пружалац: {0}",
+ "PluginUpdatedWithName": "{0} ажуриран",
+ "PluginUninstalledWithName": "{0} деинсталиран",
+ "PluginInstalledWithName": "{0} инсталиран",
+ "Plugin": "Прикључак",
+ "Playlists": "Листе",
+ "Photos": "Фотографије",
+ "NotificationOptionVideoPlaybackStopped": "Заустављено пуштање видеа",
+ "NotificationOptionVideoPlayback": "Покренуто пуштање видеа",
+ "NotificationOptionTaskFailed": "Неуспешан заказани посао",
+ "NotificationOptionServerRestartRequired": "Потребан је рестарт сервера",
+ "NotificationOptionPluginUpdateInstalled": "Ажурирање прикључка инсталирано",
+ "NotificationOptionPluginUninstalled": "Прикључак уклоњен",
+ "NotificationOptionPluginInstalled": "Прикључак инсталиран",
+ "NotificationOptionPluginError": "Грешка прикључка",
+ "NotificationOptionNewLibraryContent": "Додат нови садржај",
+ "NotificationOptionInstallationFailed": "Неуспела инсталација",
+ "NotificationOptionCameraImageUploaded": "Слика са камере послата",
+ "NotificationOptionAudioPlaybackStopped": "Заустављено пуштање звука",
+ "NotificationOptionAudioPlayback": "Покренуто пуштање звука",
+ "NotificationOptionApplicationUpdateInstalled": "Ажурирање инсталирано",
+ "NotificationOptionApplicationUpdateAvailable": "Доступно је ажурирање за апликацију",
+ "NewVersionIsAvailable": "Нова верзија Џелифин сервера је доступна за преузимање.",
+ "NameSeasonUnknown": "Непозната сезона",
+ "NameSeasonNumber": "Сезона {0}",
+ "NameInstallFailed": "Инсталација {0} није успела",
+ "MusicVideos": "Музички спотови",
+ "Music": "Музика",
+ "Movies": "Филмови",
+ "MixedContent": "Мешовит садржај",
+ "MessageServerConfigurationUpdated": "Серверска поставка је ажурирана",
+ "MessageNamedServerConfigurationUpdatedWithValue": "Одељак серверске поставке {0} је ажуриран",
+ "MessageApplicationUpdatedTo": "Џелифин сервер је ажуриран на {0}",
+ "MessageApplicationUpdated": "Џелифин сервер је ажуриран",
+ "Latest": "Последње",
+ "LabelRunningTimeValue": "Време рада: {0}",
+ "LabelIpAddressValue": "ИП адреса: {0}",
+ "ItemRemovedWithName": "{0} уклоњено из библиотеке",
+ "ItemAddedWithName": "{0} додато у библиотеку",
+ "Inherit": "Наследи",
+ "HomeVideos": "Кућни видео",
+ "HeaderRecordingGroups": "Групе снимања",
+ "HeaderNextUp": "Следеће горе",
+ "HeaderLiveTV": "ТВ уживо",
+ "HeaderFavoriteSongs": "Омиљене песме",
+ "HeaderFavoriteShows": "Омиљене серије",
+ "HeaderFavoriteEpisodes": "Омиљене епизоде",
+ "HeaderFavoriteArtists": "Омиљени извођачи",
+ "HeaderFavoriteAlbums": "Омиљени албуми",
+ "HeaderContinueWatching": "Настави гледање",
+ "HeaderCameraUploads": "Слања са камере",
+ "HeaderAlbumArtists": "Извођачи албума",
+ "Genres": "Жанрови",
+ "Folders": "Фасцикле",
+ "Favorites": "Омиљено",
+ "FailedLoginAttemptWithUserName": "Неуспела пријава са {0}",
+ "DeviceOnlineWithName": "{0} се повезао",
+ "DeviceOfflineWithName": "{0} је прекинуо везу",
+ "Collections": "Колекције",
+ "ChapterNameValue": "Поглавље {0}",
+ "Channels": "Канали",
+ "CameraImageUploadedFrom": "Нова фотографија је послата са {0}",
+ "Books": "Књиге",
+ "AuthenticationSucceededWithUserName": "{0} успешно проверено",
+ "Artists": "Извођач",
+ "Application": "Апликација",
+ "AppDeviceValues": "Апл: {0}, уређај: {1}",
+ "Albums": "Албуми",
+ "TaskDownloadMissingSubtitlesDescription": "Претражује интернет за недостајуће титлове на основу конфигурације метаподатака.",
+ "TaskDownloadMissingSubtitles": "Преузмите недостајуће титлове",
+ "TaskRefreshChannelsDescription": "Освежава информације о интернет каналу.",
+ "TaskRefreshChannels": "Освежи канале",
+ "TaskCleanTranscodeDescription": "Брише датотеке за кодирање старије од једног дана.",
+ "TaskCleanTranscode": "Очистите директоријум преноса",
+ "TaskUpdatePluginsDescription": "Преузима и инсталира исправке за додатке који су конфигурисани за аутоматско ажурирање.",
+ "TaskUpdatePlugins": "Ажурирајте додатке",
+ "TaskRefreshPeopleDescription": "Ажурира метаподатке за глумце и редитеље у вашој медијској библиотеци.",
+ "TaskRefreshPeople": "Освежите људе",
+ "TaskCleanLogsDescription": "Брише логове старије од {0} дана.",
+ "TaskCleanLogs": "Очистите директоријум логова",
+ "TaskRefreshLibraryDescription": "Скенира вашу медијску библиотеку за нове датотеке и освежава метаподатке.",
+ "TaskRefreshLibrary": "Скенирај Библиотеку Медија",
+ "TaskRefreshChapterImagesDescription": "Ствара сличице за видео записе који имају поглавља.",
+ "TaskRefreshChapterImages": "Издвоји слике из поглавља",
+ "TaskCleanCacheDescription": "Брише Кеш фајлове који више нису потребни систему.",
+ "TaskCleanCache": "Очистите Кеш Директоријум",
+ "TasksChannelsCategory": "Интернет канали",
+ "TasksApplicationCategory": "Апликација",
+ "TasksLibraryCategory": "Библиотека",
+ "TasksMaintenanceCategory": "Одржавање"
+}
diff --git a/Emby.Server.Implementations/Localization/Core/sv.json b/Emby.Server.Implementations/Localization/Core/sv.json
index 744b0e2d3..c8662b2ca 100644
--- a/Emby.Server.Implementations/Localization/Core/sv.json
+++ b/Emby.Server.Implementations/Localization/Core/sv.json
@@ -1,7 +1,7 @@
{
"Albums": "Album",
- "AppDeviceValues": "App: {0}, Enhet: {1}",
- "Application": "App",
+ "AppDeviceValues": "Applikation: {0}, Enhet: {1}",
+ "Application": "Applikation",
"Artists": "Artister",
"AuthenticationSucceededWithUserName": "{0} har autentiserats",
"Books": "Böcker",
@@ -9,22 +9,22 @@
"Channels": "Kanaler",
"ChapterNameValue": "Kapitel {0}",
"Collections": "Samlingar",
- "DeviceOfflineWithName": "{0} har tappat anslutningen",
+ "DeviceOfflineWithName": "{0} har kopplat från",
"DeviceOnlineWithName": "{0} är ansluten",
"FailedLoginAttemptWithUserName": "Misslyckat inloggningsförsök från {0}",
"Favorites": "Favoriter",
"Folders": "Mappar",
"Genres": "Genrer",
"HeaderAlbumArtists": "Albumartister",
- "HeaderCameraUploads": "Kamera Uppladdningar",
- "HeaderContinueWatching": "Fortsätt kolla på",
+ "HeaderCameraUploads": "Kamerauppladdningar",
+ "HeaderContinueWatching": "Fortsätt kolla",
"HeaderFavoriteAlbums": "Favoritalbum",
"HeaderFavoriteArtists": "Favoritartister",
"HeaderFavoriteEpisodes": "Favoritavsnitt",
"HeaderFavoriteShows": "Favoritserier",
"HeaderFavoriteSongs": "Favoritlåtar",
"HeaderLiveTV": "Live-TV",
- "HeaderNextUp": "Nästa på tur",
+ "HeaderNextUp": "Nästa",
"HeaderRecordingGroups": "Inspelningsgrupper",
"HomeVideos": "Hemvideor",
"Inherit": "Ärv",
@@ -34,9 +34,9 @@
"LabelRunningTimeValue": "Speltid: {0}",
"Latest": "Senaste",
"MessageApplicationUpdated": "Jellyfin Server har uppdaterats",
- "MessageApplicationUpdatedTo": "Jellyfin Server har uppgraderats till {0}",
+ "MessageApplicationUpdatedTo": "Jellyfin Server har uppdaterats till {0}",
"MessageNamedServerConfigurationUpdatedWithValue": "Serverinställningarna {0} har uppdaterats",
- "MessageServerConfigurationUpdated": "Server konfigurationen har uppdaterats",
+ "MessageServerConfigurationUpdated": "Serverkonfigurationen har uppdaterats",
"MixedContent": "Blandat innehåll",
"Movies": "Filmer",
"Music": "Musik",
@@ -44,13 +44,13 @@
"NameInstallFailed": "{0} installationen misslyckades",
"NameSeasonNumber": "Säsong {0}",
"NameSeasonUnknown": "Okänd säsong",
- "NewVersionIsAvailable": "En ny version av Jellyfin Server är klar för nedladdning.",
+ "NewVersionIsAvailable": "En ny version av Jellyfin Server är tillgänglig att hämta.",
"NotificationOptionApplicationUpdateAvailable": "Ny programversion tillgänglig",
"NotificationOptionApplicationUpdateInstalled": "Programuppdatering installerad",
"NotificationOptionAudioPlayback": "Ljuduppspelning har påbörjats",
- "NotificationOptionAudioPlaybackStopped": "Ljuduppspelning stoppad",
+ "NotificationOptionAudioPlaybackStopped": "Ljuduppspelning stoppades",
"NotificationOptionCameraImageUploaded": "Kamerabild har laddats upp",
- "NotificationOptionInstallationFailed": "Fel vid installation",
+ "NotificationOptionInstallationFailed": "Installationen misslyckades",
"NotificationOptionNewLibraryContent": "Nytt innehåll har lagts till",
"NotificationOptionPluginError": "Fel uppstod med tillägget",
"NotificationOptionPluginInstalled": "Tillägg har installerats",
@@ -60,7 +60,7 @@
"NotificationOptionTaskFailed": "Schemalagd aktivitet har misslyckats",
"NotificationOptionUserLockedOut": "Användare har låsts ut",
"NotificationOptionVideoPlayback": "Videouppspelning har påbörjats",
- "NotificationOptionVideoPlaybackStopped": "Videouppspelning stoppad",
+ "NotificationOptionVideoPlaybackStopped": "Videouppspelning stoppades",
"Photos": "Bilder",
"Playlists": "Spellistor",
"Plugin": "Tillägg",
@@ -69,14 +69,13 @@
"PluginUpdatedWithName": "{0} uppdaterades",
"ProviderValue": "Källa: {0}",
"ScheduledTaskFailedWithName": "{0} misslyckades",
- "ScheduledTaskStartedWithName": "{0} startad",
+ "ScheduledTaskStartedWithName": "{0} startades",
"ServerNameNeedsToBeRestarted": "{0} behöver startas om",
"Shows": "Serier",
"Songs": "Låtar",
- "StartupEmbyServerIsLoading": "Jellyfin server arbetar. Pröva igen inom kort.",
+ "StartupEmbyServerIsLoading": "Jellyfin Server arbetar. Pröva igen snart.",
"SubtitleDownloadFailureForItem": "Nerladdning av undertexter för {0} misslyckades",
- "SubtitleDownloadFailureFromForItem": "Undertexter misslyckades att ladda ner {0} för {1}",
- "SubtitlesDownloadedForItem": "Undertexter har laddats ner till {0}",
+ "SubtitleDownloadFailureFromForItem": "Undertexter kunde inte laddas ner från {0} för {1}",
"Sync": "Synk",
"System": "System",
"TvShows": "TV-serier",
@@ -89,9 +88,31 @@
"UserOnlineFromDevice": "{0} är uppkopplad från {1}",
"UserPasswordChangedWithName": "Lösenordet för {0} har ändrats",
"UserPolicyUpdatedWithName": "Användarpolicyn har uppdaterats för {0}",
- "UserStartedPlayingItemWithValues": "{0} har börjat spela upp {1}",
- "UserStoppedPlayingItemWithValues": "{0} har avslutat uppspelningen av {1}",
- "ValueHasBeenAddedToLibrary": "{0} har blivit tillagd till ditt mediabibliotek",
+ "UserStartedPlayingItemWithValues": "{0} spelar upp {1} på {2}",
+ "UserStoppedPlayingItemWithValues": "{0} har avslutat uppspelningen av {1} på {2}",
+ "ValueHasBeenAddedToLibrary": "{0} har lagts till i ditt mediebibliotek",
"ValueSpecialEpisodeName": "Specialavsnitt - {0}",
- "VersionNumber": "Version {0}"
+ "VersionNumber": "Version {0}",
+ "TaskDownloadMissingSubtitlesDescription": "Söker på internet efter saknade undertexter baserad på metadatas konfiguration.",
+ "TaskDownloadMissingSubtitles": "Ladda ned saknade undertexter",
+ "TaskRefreshChannelsDescription": "Uppdaterar information för internetkanaler.",
+ "TaskRefreshChannels": "Uppdatera kanaler",
+ "TaskCleanTranscodeDescription": "Raderar transkodningsfiler som är mer än en dag gamla.",
+ "TaskCleanTranscode": "Töm transkodningskatalog",
+ "TaskUpdatePluginsDescription": "Laddar ned och installerar uppdateringar till insticksprogram som är konfigurerade att uppdateras automatiskt.",
+ "TaskUpdatePlugins": "Uppdatera insticksprogram",
+ "TaskRefreshPeopleDescription": "Uppdaterar metadata för skådespelare och regissörer i ditt mediabibliotek.",
+ "TaskCleanLogsDescription": "Raderar loggfiler som är mer än {0} dagar gamla.",
+ "TaskCleanLogs": "Töm loggkatalog",
+ "TaskRefreshLibraryDescription": "Söker igenom ditt mediabibliotek efter nya filer och förnyar metadata.",
+ "TaskRefreshLibrary": "Genomsök mediabibliotek",
+ "TaskRefreshChapterImagesDescription": "Skapa miniatyrbilder för videor med kapitel.",
+ "TaskRefreshChapterImages": "Extrahera kapitelbilder",
+ "TaskCleanCacheDescription": "Radera cachade filer som systemet inte längre behöver.",
+ "TaskCleanCache": "Rensa cachekatalog",
+ "TasksChannelsCategory": "Internetkanaler",
+ "TasksApplicationCategory": "Applikation",
+ "TasksLibraryCategory": "Bibliotek",
+ "TasksMaintenanceCategory": "Underhåll",
+ "TaskRefreshPeople": "Uppdatera Personer"
}
diff --git a/Emby.Server.Implementations/Localization/Core/tr.json b/Emby.Server.Implementations/Localization/Core/tr.json
index eb1c2623f..3cf3482eb 100644
--- a/Emby.Server.Implementations/Localization/Core/tr.json
+++ b/Emby.Server.Implementations/Localization/Core/tr.json
@@ -34,8 +34,8 @@
"LabelRunningTimeValue": "Çalışma süresi: {0}",
"Latest": "En son",
"MessageApplicationUpdated": "Jellyfin Sunucusu güncellendi",
- "MessageApplicationUpdatedTo": "Jellyfin Sunucusu {0} olarak güncellendi",
- "MessageNamedServerConfigurationUpdatedWithValue": "Sunucu ayarları kısım {0} güncellendi",
+ "MessageApplicationUpdatedTo": "Jellyfin Sunucusu {0} sürümüne güncellendi",
+ "MessageNamedServerConfigurationUpdatedWithValue": "Sunucu ayar kısmı {0} güncellendi",
"MessageServerConfigurationUpdated": "Sunucu ayarları güncellendi",
"MixedContent": "Karışık içerik",
"Movies": "Filmler",
@@ -50,7 +50,7 @@
"NotificationOptionAudioPlayback": "Ses çalma başladı",
"NotificationOptionAudioPlaybackStopped": "Ses çalma durduruldu",
"NotificationOptionCameraImageUploaded": "Kamera fotoğrafı yüklendi",
- "NotificationOptionInstallationFailed": "Yükleme başarısız oldu",
+ "NotificationOptionInstallationFailed": "Kurulum hatası",
"NotificationOptionNewLibraryContent": "Yeni içerik eklendi",
"NotificationOptionPluginError": "Eklenti hatası",
"NotificationOptionPluginInstalled": "Eklenti yüklendi",
@@ -76,7 +76,6 @@
"StartupEmbyServerIsLoading": "Jellyfin Sunucusu yükleniyor. Lütfen kısa süre sonra tekrar deneyin.",
"SubtitleDownloadFailureForItem": "Subtitles failed to download for {0}",
"SubtitleDownloadFailureFromForItem": "{1} için alt yazılar {0} 'dan indirilemedi",
- "SubtitlesDownloadedForItem": "{0} için altyazılar indirildi",
"Sync": "Eşitle",
"System": "Sistem",
"TvShows": "Diziler",
@@ -93,5 +92,27 @@
"UserStoppedPlayingItemWithValues": "{0}, {2} cihazında {1} izlemeyi bitirdi",
"ValueHasBeenAddedToLibrary": "Medya kitaplığınıza {0} eklendi",
"ValueSpecialEpisodeName": "Özel - {0}",
- "VersionNumber": "Versiyon {0}"
+ "VersionNumber": "Versiyon {0}",
+ "TaskCleanCache": "Geçici dosya klasörünü temizle",
+ "TasksChannelsCategory": "İnternet kanalları",
+ "TasksApplicationCategory": "Uygulama",
+ "TasksLibraryCategory": "Kütüphane",
+ "TasksMaintenanceCategory": "Onarım",
+ "TaskRefreshPeopleDescription": "Medya kütüphanenizdeki videoların oyuncu ve yönetmen bilgilerini günceller.",
+ "TaskDownloadMissingSubtitlesDescription": "Metadata ayarlarını baz alarak eksik altyazıları internette arar.",
+ "TaskDownloadMissingSubtitles": "Eksik altyazıları indir",
+ "TaskRefreshChannelsDescription": "Internet kanal bilgilerini yenile.",
+ "TaskRefreshChannels": "Kanalları Yenile",
+ "TaskCleanTranscodeDescription": "Bir günü dolmuş dönüştürme bilgisi içeren dosyaları siler.",
+ "TaskCleanTranscode": "Dönüşüm Dizinini Temizle",
+ "TaskUpdatePluginsDescription": "Otomatik güncellenmeye ayarlanmış eklentilerin güncellemelerini indirir ve kurar.",
+ "TaskUpdatePlugins": "Eklentileri Güncelle",
+ "TaskRefreshPeople": "Kullanıcıları Yenile",
+ "TaskCleanLogsDescription": "{0} günden eski log dosyalarını siler.",
+ "TaskCleanLogs": "Log Dizinini Temizle",
+ "TaskRefreshLibraryDescription": "Medya kütüphanenize eklenen yeni dosyaları arar ve bilgileri yeniler.",
+ "TaskRefreshLibrary": "Medya Kütüphanesini Tara",
+ "TaskRefreshChapterImagesDescription": "Sahnelere ayrılmış videolar için küçük resimler oluştur.",
+ "TaskRefreshChapterImages": "Bölüm Resimlerini Çıkar",
+ "TaskCleanCacheDescription": "Sistem tarafından artık ihtiyaç duyulmayan önbellek dosyalarını siler."
}
diff --git a/Emby.Server.Implementations/Localization/Core/ur_PK.json b/Emby.Server.Implementations/Localization/Core/ur_PK.json
new file mode 100644
index 000000000..9a5874e29
--- /dev/null
+++ b/Emby.Server.Implementations/Localization/Core/ur_PK.json
@@ -0,0 +1,117 @@
+{
+ "HeaderFavoriteAlbums": "پسندیدہ البمز",
+ "HeaderNextUp": "اگلا",
+ "HeaderFavoriteArtists": "پسندیدہ فنکار",
+ "HeaderAlbumArtists": "البم کے فنکار",
+ "Movies": "فلمیں",
+ "HeaderFavoriteEpisodes": "پسندیدہ اقساط",
+ "Collections": "مجموعہ",
+ "Folders": "فولڈرز",
+ "HeaderLiveTV": "براہ راست ٹی وی",
+ "Channels": "چینل",
+ "HeaderContinueWatching": "دیکھنا جاری رکھیں",
+ "Playlists": "پلے لسٹس",
+ "ValueSpecialEpisodeName": "خاص - {0}",
+ "Shows": "شوز",
+ "Genres": "انواع",
+ "Artists": "فنکار",
+ "Sync": "مطابقت",
+ "Photos": "تصوریں",
+ "Albums": "البم",
+ "Favorites": "پسندیدہ",
+ "Songs": "گانے",
+ "Books": "کتابیں",
+ "HeaderFavoriteSongs": "پسندیدہ گانے",
+ "HeaderFavoriteShows": "پسندیدہ شوز",
+ "TaskDownloadMissingSubtitlesDescription": "میٹا ڈیٹا کی تشکیل پر مبنی ذیلی عنوانات کے غائب عنوانات انٹرنیٹ پے تلاش کرتا ہے۔",
+ "TaskDownloadMissingSubtitles": "غائب سب ٹائٹلز ڈاؤن لوڈ کریں",
+ "TaskRefreshChannelsDescription": "انٹرنیٹ چینل کی معلومات کو تازہ دم کرتا ہے۔",
+ "TaskRefreshChannels": "چینلز ریفریش کریں",
+ "TaskCleanTranscodeDescription": "ایک دن سے زیادہ پرانی ٹرانسکوڈ فائلوں کو حذف کرتا ہے۔",
+ "TaskCleanTranscode": "ٹرانس کوڈ ڈائرکٹری صاف کریں",
+ "TaskUpdatePluginsDescription": "پلگ انز کے لئے اپ ڈیٹس ڈاؤن لوڈ اور انسٹال کرتے ہیں جو خود بخود اپ ڈیٹ کرنے کیلئے تشکیل شدہ ہیں۔",
+ "TaskUpdatePlugins": "پلگ انز کو اپ ڈیٹ کریں",
+ "TaskRefreshPeopleDescription": "آپ کی میڈیا لائبریری میں اداکاروں اور ہدایت کاروں کے لئے میٹا ڈیٹا کی تازہ کاری۔",
+ "TaskRefreshPeople": "لوگوں کو تروتازہ کریں",
+ "TaskCleanLogsDescription": "لاگ فائلوں کو حذف کریں جو {0} دن سے زیادہ پرانی ہیں۔",
+ "TaskCleanLogs": "لاگ ڈائرکٹری کو صاف کریں",
+ "TaskRefreshLibraryDescription": "میڈیا لائبریری کو اسکین کرتا ھے ہر میٹا دیٹا کہ تازہ دم کرتا ھے.",
+ "TaskRefreshLibrary": "اسکین میڈیا لائبریری",
+ "TaskRefreshChapterImagesDescription": "بابوں والی ویڈیوز کے لئے تمبنیل بنایں۔",
+ "TaskRefreshChapterImages": "باب کی تصاویر نکالیں",
+ "TaskCleanCacheDescription": "فائلوں کو حذف کریں جنکی ضرورت نھیں ھے۔",
+ "TaskCleanCache": "کیش ڈائرکٹری کلیر کریں",
+ "TasksChannelsCategory": "انٹرنیٹ چینلز",
+ "TasksApplicationCategory": "پروگرام",
+ "TasksLibraryCategory": "لآیبریری",
+ "TasksMaintenanceCategory": "مرمت",
+ "VersionNumber": "ورژن {0}",
+ "ValueHasBeenAddedToLibrary": "{0} آپ کی میڈیا لائبریری میں شامل کر دیا گیا ہے",
+ "UserStoppedPlayingItemWithValues": "{0} نے {1} چلانا ختم کر دیا ھے {2} پے",
+ "UserStartedPlayingItemWithValues": "{0} چلا رہا ہے {1} {2} پے",
+ "UserPolicyUpdatedWithName": "صارف {0} کی پالیسی کیلئے تازہ کاری کی گئی ہے",
+ "UserPasswordChangedWithName": "صارف {0} کے لئے پاس ورڈ تبدیل کر دیا گیا ہے",
+ "UserOnlineFromDevice": "{0} آن لائن ہے {1} سے",
+ "UserOfflineFromDevice": "{0} سے منقطع ہوگیا ہے {1}",
+ "UserLockedOutWithName": "صارف {0} کو لاک آؤٹ کردیا گیا ہے",
+ "UserDownloadingItemWithValues": "{0} ڈاؤن لوڈ کر رھا ھے {1}",
+ "UserDeletedWithName": "صارف {0} کو ہٹا دیا گیا ہے",
+ "UserCreatedWithName": "صارف {0} تشکیل دیا گیا ہے",
+ "User": "صارف",
+ "TvShows": "ٹی وی کے پروگرام",
+ "System": "نظام",
+ "SubtitleDownloadFailureFromForItem": "ذیلی عنوانات {0} سے ڈاؤن لوڈ کرنے میں ناکام {1} کے لیے",
+ "StartupEmbyServerIsLoading": "جیلیفن سرور لوڈ ہورہا ہے۔ براہ کرم جلد ہی دوبارہ کوشش کریں۔",
+ "ServerNameNeedsToBeRestarted": "{0} دوبارہ چلانے کرنے کی ضرورت ہے",
+ "ScheduledTaskStartedWithName": "{0} شروع",
+ "ScheduledTaskFailedWithName": "{0} ناکام",
+ "ProviderValue": "فراہم کرنے والا: {0}",
+ "PluginUpdatedWithName": "{0} تازہ کاری کی گئی تھی",
+ "PluginUninstalledWithName": "[0} ہٹا دیا گیا تھا",
+ "PluginInstalledWithName": "{0} انسٹال کیا گیا تھا",
+ "Plugin": "پلگن",
+ "NotificationOptionVideoPlaybackStopped": "ویڈیو پلے بیک رک گیا",
+ "NotificationOptionVideoPlayback": "ویڈیو پلے بیک شروع ہوا",
+ "NotificationOptionUserLockedOut": "صارف کو لاک آؤٹ کیا گیا",
+ "NotificationOptionTaskFailed": "طے شدہ کام کی ناکامی",
+ "NotificationOptionServerRestartRequired": "سرور دوبارہ چلانے کرنے کی ضرورت ہے",
+ "NotificationOptionPluginUpdateInstalled": "پلگ ان اپ ڈیٹ انسٹال",
+ "NotificationOptionPluginUninstalled": "پلگ ان ہٹا دیا گیا",
+ "NotificationOptionPluginInstalled": "پلگ ان انسٹال ہوا",
+ "NotificationOptionPluginError": "پلگ ان کی ناکامی",
+ "NotificationOptionNewLibraryContent": "نیا مواد شامل کیا گیا",
+ "NotificationOptionInstallationFailed": "تنصیب کی ناکامی",
+ "NotificationOptionCameraImageUploaded": "کیمرے کی تصویر اپ لوڈ ہوگئی",
+ "NotificationOptionAudioPlaybackStopped": "آڈیو پلے بیک رک گیا",
+ "NotificationOptionAudioPlayback": "آڈیو پلے بیک شروع ہوا",
+ "NotificationOptionApplicationUpdateInstalled": "پروگرام اپ ڈیٹ انسٹال ہوچکا ھے",
+ "NotificationOptionApplicationUpdateAvailable": "پروگرام کی تازہ کاری دستیاب ہے",
+ "NewVersionIsAvailable": "جیلیفن سرور کا ایک نیا ورژن ڈاؤن لوڈ کے لئے دستیاب ہے۔",
+ "NameSeasonUnknown": "نامعلوم باب",
+ "NameSeasonNumber": "باب {0}",
+ "NameInstallFailed": "{0} تنصیب ناکام ہوگئی",
+ "MusicVideos": "موسیقی ویڈیو",
+ "Music": "موسیقی",
+ "MixedContent": "مخلوط مواد",
+ "MessageServerConfigurationUpdated": "سرور کو اپ ڈیٹ کر دیا گیا ہے",
+ "MessageNamedServerConfigurationUpdatedWithValue": "سرور ضمن {0} کو ترتیب دے دیا گیا ھے",
+ "MessageApplicationUpdatedTo": "جیلیفن سرور کو اپ ڈیٹ کیا ہے {0}",
+ "MessageApplicationUpdated": "جیلیفن سرور کو اپ ڈیٹ کر دیا گیا ہے",
+ "Latest": "تازہ ترین",
+ "LabelRunningTimeValue": "چلانے کی مدت",
+ "LabelIpAddressValue": "ای پی پتے {0}",
+ "ItemRemovedWithName": "لائبریری سے ہٹا دیا گیا ھے",
+ "ItemAddedWithName": "[0} لائبریری میں شامل کیا گیا ھے",
+ "Inherit": "وراثت میں",
+ "HomeVideos": "ہوم ویڈیو",
+ "HeaderRecordingGroups": "ریکارڈنگ گروپس",
+ "HeaderCameraUploads": "کیمرہ اپلوڈز",
+ "FailedLoginAttemptWithUserName": "لاگن کئ کوشش ناکام {0}",
+ "DeviceOnlineWithName": "{0} متصل ھو چکا ھے",
+ "DeviceOfflineWithName": "{0} منقطع ھو چکا ھے",
+ "ChapterNameValue": "باب",
+ "AuthenticationSucceededWithUserName": "{0} کامیابی کے ساتھ تصدیق ھوچکی ھے",
+ "CameraImageUploadedFrom": "ایک نئی کیمرہ تصویر اپ لوڈ کی گئی ہے {0}",
+ "Application": "پروگرام",
+ "AppDeviceValues": "پروگرام:{0}, آلہ:{1}"
+}
diff --git a/Emby.Server.Implementations/Localization/Core/zh-CN.json b/Emby.Server.Implementations/Localization/Core/zh-CN.json
index a4d53c57e..6b563a9b1 100644
--- a/Emby.Server.Implementations/Localization/Core/zh-CN.json
+++ b/Emby.Server.Implementations/Localization/Core/zh-CN.json
@@ -5,9 +5,9 @@
"Artists": "艺术家",
"AuthenticationSucceededWithUserName": "{0} 认证成功",
"Books": "书籍",
- "CameraImageUploadedFrom": "已从 {0} 上传了一张新的相片",
+ "CameraImageUploadedFrom": "新的相机图像已从 {0} 上传",
"Channels": "频道",
- "ChapterNameValue": "章节 {0}",
+ "ChapterNameValue": "第 {0} 集",
"Collections": "合集",
"DeviceOfflineWithName": "{0} 已断开",
"DeviceOnlineWithName": "{0} 已连接",
@@ -17,14 +17,14 @@
"Genres": "风格",
"HeaderAlbumArtists": "专辑作家",
"HeaderCameraUploads": "相机上传",
- "HeaderContinueWatching": "继续观看",
- "HeaderFavoriteAlbums": "最爱的专辑",
+ "HeaderContinueWatching": "继续观影",
+ "HeaderFavoriteAlbums": "收藏的专辑",
"HeaderFavoriteArtists": "最爱的艺术家",
"HeaderFavoriteEpisodes": "最爱的剧集",
"HeaderFavoriteShows": "最爱的节目",
"HeaderFavoriteSongs": "最爱的歌曲",
"HeaderLiveTV": "电视直播",
- "HeaderNextUp": "下一步",
+ "HeaderNextUp": "接下来",
"HeaderRecordingGroups": "录制组",
"HomeVideos": "家庭视频",
"Inherit": "继承",
@@ -76,10 +76,9 @@
"StartupEmbyServerIsLoading": "Jellyfin 服务器加载中。请稍后再试。",
"SubtitleDownloadFailureForItem": "为 {0} 下载字幕失败",
"SubtitleDownloadFailureFromForItem": "无法从 {0} 下载 {1} 的字幕",
- "SubtitlesDownloadedForItem": "已为 {0} 下载了字幕",
"Sync": "同步",
"System": "系统",
- "TvShows": "电视节目",
+ "TvShows": "电视剧",
"User": "用户",
"UserCreatedWithName": "用户 {0} 已创建",
"UserDeletedWithName": "用户 {0} 已删除",
@@ -89,9 +88,31 @@
"UserOnlineFromDevice": "{0} 在线,来自 {1}",
"UserPasswordChangedWithName": "已为用户 {0} 更改密码",
"UserPolicyUpdatedWithName": "用户协议已经被更新为 {0}",
- "UserStartedPlayingItemWithValues": "{0} 已开始播放 {1}",
- "UserStoppedPlayingItemWithValues": "{0} 已停止播放 {1}",
+ "UserStartedPlayingItemWithValues": "{0} 已在 {2} 上开始播放 {1}",
+ "UserStoppedPlayingItemWithValues": "{0} 已在 {2} 上停止播放 {1}",
"ValueHasBeenAddedToLibrary": "{0} 已添加至您的媒体库中",
"ValueSpecialEpisodeName": "特典 - {0}",
- "VersionNumber": "版本 {0}"
+ "VersionNumber": "版本 {0}",
+ "TaskUpdatePluginsDescription": "为已设置为自动更新的插件下载和安装更新。",
+ "TaskRefreshPeople": "刷新人员",
+ "TasksChannelsCategory": "互联网频道",
+ "TasksLibraryCategory": "媒体库",
+ "TaskDownloadMissingSubtitlesDescription": "根据元数据设置在互联网上搜索缺少的字幕。",
+ "TaskDownloadMissingSubtitles": "下载缺少的字幕",
+ "TaskRefreshChannelsDescription": "刷新互联网频道信息。",
+ "TaskRefreshChannels": "刷新频道",
+ "TaskCleanTranscodeDescription": "删除存在超过 1 天的转码文件。",
+ "TaskCleanTranscode": "清理转码目录",
+ "TaskUpdatePlugins": "更新插件",
+ "TaskRefreshPeopleDescription": "更新媒体库中演员和导演的元数据。",
+ "TaskCleanLogsDescription": "删除存在超过 {0} 天的的日志文件。",
+ "TaskCleanLogs": "清理日志目录",
+ "TaskRefreshLibraryDescription": "扫描你的媒体库以获取新文件并刷新元数据。",
+ "TaskRefreshLibrary": "扫描媒体库",
+ "TaskRefreshChapterImagesDescription": "为包含剧集的视频提取缩略图。",
+ "TaskRefreshChapterImages": "提取剧集图片",
+ "TaskCleanCacheDescription": "删除系统不再需要的缓存文件。",
+ "TaskCleanCache": "清理缓存目录",
+ "TasksApplicationCategory": "应用程序",
+ "TasksMaintenanceCategory": "维护"
}
diff --git a/Emby.Server.Implementations/Localization/Core/zh-HK.json b/Emby.Server.Implementations/Localization/Core/zh-HK.json
index 33fcb2d37..224748e61 100644
--- a/Emby.Server.Implementations/Localization/Core/zh-HK.json
+++ b/Emby.Server.Implementations/Localization/Core/zh-HK.json
@@ -1,97 +1,96 @@
{
- "Albums": "Albums",
- "AppDeviceValues": "App: {0}, Device: {1}",
- "Application": "Application",
+ "Albums": "專輯",
+ "AppDeviceValues": "軟體: {0}, 設備: {1}",
+ "Application": "應用程式",
"Artists": "藝人",
- "AuthenticationSucceededWithUserName": "{0} successfully authenticated",
- "Books": "Books",
- "CameraImageUploadedFrom": "A new camera image has been uploaded from {0}",
- "Channels": "Channels",
- "ChapterNameValue": "Chapter {0}",
- "Collections": "Collections",
- "DeviceOfflineWithName": "{0} has disconnected",
- "DeviceOnlineWithName": "{0} is connected",
- "FailedLoginAttemptWithUserName": "Failed login attempt from {0}",
- "Favorites": "Favorites",
- "Folders": "Folders",
- "Genres": "Genres",
- "HeaderAlbumArtists": "Album Artists",
- "HeaderCameraUploads": "Camera Uploads",
- "HeaderContinueWatching": "Continue Watching",
- "HeaderFavoriteAlbums": "Favorite Albums",
- "HeaderFavoriteArtists": "Favorite Artists",
- "HeaderFavoriteEpisodes": "Favorite Episodes",
- "HeaderFavoriteShows": "Favorite Shows",
- "HeaderFavoriteSongs": "Favorite Songs",
- "HeaderLiveTV": "Live TV",
- "HeaderNextUp": "Next Up",
- "HeaderRecordingGroups": "Recording Groups",
- "HomeVideos": "Home videos",
- "Inherit": "Inherit",
- "ItemAddedWithName": "{0} was added to the library",
- "ItemRemovedWithName": "{0} was removed from the library",
- "LabelIpAddressValue": "Ip address: {0}",
- "LabelRunningTimeValue": "Running time: {0}",
- "Latest": "Latest",
- "MessageApplicationUpdated": "Jellyfin Server has been updated",
- "MessageApplicationUpdatedTo": "Jellyfin Server has been updated to {0}",
- "MessageNamedServerConfigurationUpdatedWithValue": "Server configuration section {0} has been updated",
- "MessageServerConfigurationUpdated": "Server configuration has been updated",
+ "AuthenticationSucceededWithUserName": "{0} 授權成功",
+ "Books": "圖書",
+ "CameraImageUploadedFrom": "{0} 成功上傳一張新相片",
+ "Channels": "頻道",
+ "ChapterNameValue": "章節 {0}",
+ "Collections": "合輯",
+ "DeviceOfflineWithName": "{0} 已經斷開連結",
+ "DeviceOnlineWithName": "{0} 已經連接",
+ "FailedLoginAttemptWithUserName": "來自 {0} 的失敗登入嘗試",
+ "Favorites": "我的最愛",
+ "Folders": "檔案夾",
+ "Genres": "風格",
+ "HeaderAlbumArtists": "專輯藝術家",
+ "HeaderCameraUploads": "相機上載",
+ "HeaderContinueWatching": "繼續觀看",
+ "HeaderFavoriteAlbums": "最愛專輯",
+ "HeaderFavoriteArtists": "最愛藝術家",
+ "HeaderFavoriteEpisodes": "最愛的劇集",
+ "HeaderFavoriteShows": "最愛的節目",
+ "HeaderFavoriteSongs": "最愛的歌曲",
+ "HeaderLiveTV": "電視直播",
+ "HeaderNextUp": "接下來",
+ "HeaderRecordingGroups": "錄製組",
+ "HomeVideos": "家庭影片",
+ "Inherit": "繼承",
+ "ItemAddedWithName": "{0} 已添加至媒體庫",
+ "ItemRemovedWithName": "{0} 已從媒體庫移除",
+ "LabelIpAddressValue": "IP 地址: {0}",
+ "LabelRunningTimeValue": "運行時間: {0}",
+ "Latest": "最新",
+ "MessageApplicationUpdated": "Jellyfin Server 已更新",
+ "MessageApplicationUpdatedTo": "Jellyfin 伺服器已更新至 {0}",
+ "MessageNamedServerConfigurationUpdatedWithValue": "伺服器設定 {0} 部分已更新",
+ "MessageServerConfigurationUpdated": "伺服器設定已經更新",
"MixedContent": "Mixed content",
- "Movies": "Movies",
- "Music": "Music",
- "MusicVideos": "Music videos",
- "NameInstallFailed": "{0} installation failed",
- "NameSeasonNumber": "Season {0}",
- "NameSeasonUnknown": "Season Unknown",
- "NewVersionIsAvailable": "A new version of Jellyfin Server is available for download.",
- "NotificationOptionApplicationUpdateAvailable": "Application update available",
- "NotificationOptionApplicationUpdateInstalled": "Application update installed",
- "NotificationOptionAudioPlayback": "Audio playback started",
- "NotificationOptionAudioPlaybackStopped": "Audio playback stopped",
- "NotificationOptionCameraImageUploaded": "Camera image uploaded",
- "NotificationOptionInstallationFailed": "Installation failure",
- "NotificationOptionNewLibraryContent": "New content added",
- "NotificationOptionPluginError": "Plugin failure",
- "NotificationOptionPluginInstalled": "Plugin installed",
- "NotificationOptionPluginUninstalled": "Plugin uninstalled",
- "NotificationOptionPluginUpdateInstalled": "Plugin update installed",
- "NotificationOptionServerRestartRequired": "Server restart required",
- "NotificationOptionTaskFailed": "Scheduled task failure",
- "NotificationOptionUserLockedOut": "User locked out",
- "NotificationOptionVideoPlayback": "Video playback started",
- "NotificationOptionVideoPlaybackStopped": "Video playback stopped",
- "Photos": "Photos",
- "Playlists": "Playlists",
+ "Movies": "電影",
+ "Music": "音樂",
+ "MusicVideos": "音樂MV",
+ "NameInstallFailed": "{0} 安裝失敗",
+ "NameSeasonNumber": "第 {0} 季",
+ "NameSeasonUnknown": "未知季數",
+ "NewVersionIsAvailable": "新版本的 Jellyfin 伺服器可供下載。",
+ "NotificationOptionApplicationUpdateAvailable": "有可用的應用程式更新",
+ "NotificationOptionApplicationUpdateInstalled": "應用程式已更新",
+ "NotificationOptionAudioPlayback": "開始播放音頻",
+ "NotificationOptionAudioPlaybackStopped": "已停止播放音頻",
+ "NotificationOptionCameraImageUploaded": "相機相片已上傳",
+ "NotificationOptionInstallationFailed": "安裝失敗",
+ "NotificationOptionNewLibraryContent": "已添加新内容",
+ "NotificationOptionPluginError": "擴充元件錯誤",
+ "NotificationOptionPluginInstalled": "擴充元件已安裝",
+ "NotificationOptionPluginUninstalled": "擴充元件已移除",
+ "NotificationOptionPluginUpdateInstalled": "擴充元件更新已安裝",
+ "NotificationOptionServerRestartRequired": "伺服器需要重啓",
+ "NotificationOptionTaskFailed": "計劃任務失敗",
+ "NotificationOptionUserLockedOut": "用家已鎖定",
+ "NotificationOptionVideoPlayback": "開始播放視頻",
+ "NotificationOptionVideoPlaybackStopped": "已停止播放視頻",
+ "Photos": "相片",
+ "Playlists": "播放清單",
"Plugin": "Plugin",
- "PluginInstalledWithName": "{0} was installed",
- "PluginUninstalledWithName": "{0} was uninstalled",
- "PluginUpdatedWithName": "{0} was updated",
+ "PluginInstalledWithName": "已安裝 {0}",
+ "PluginUninstalledWithName": "已移除 {0}",
+ "PluginUpdatedWithName": "已更新 {0}",
"ProviderValue": "Provider: {0}",
- "ScheduledTaskFailedWithName": "{0} failed",
- "ScheduledTaskStartedWithName": "{0} started",
- "ServerNameNeedsToBeRestarted": "{0} needs to be restarted",
- "Shows": "Shows",
- "Songs": "Songs",
- "StartupEmbyServerIsLoading": "Jellyfin Server is loading. Please try again shortly.",
+ "ScheduledTaskFailedWithName": "{0} 任務失敗",
+ "ScheduledTaskStartedWithName": "{0} 任務開始",
+ "ServerNameNeedsToBeRestarted": "{0} 需要重啓",
+ "Shows": "節目",
+ "Songs": "歌曲",
+ "StartupEmbyServerIsLoading": "Jellyfin 伺服器載入中,請稍後再試。",
"SubtitleDownloadFailureForItem": "Subtitles failed to download for {0}",
- "SubtitleDownloadFailureFromForItem": "Subtitles failed to download from {0} for {1}",
- "SubtitlesDownloadedForItem": "Subtitles downloaded for {0}",
- "Sync": "Sync",
+ "SubtitleDownloadFailureFromForItem": "無法從 {0} 下載 {1} 的字幕",
+ "Sync": "同步",
"System": "System",
- "TvShows": "TV Shows",
+ "TvShows": "電視節目",
"User": "User",
- "UserCreatedWithName": "User {0} has been created",
- "UserDeletedWithName": "User {0} has been deleted",
- "UserDownloadingItemWithValues": "{0} is downloading {1}",
- "UserLockedOutWithName": "User {0} has been locked out",
- "UserOfflineFromDevice": "{0} has disconnected from {1}",
- "UserOnlineFromDevice": "{0} is online from {1}",
- "UserPasswordChangedWithName": "Password has been changed for user {0}",
- "UserPolicyUpdatedWithName": "User policy has been updated for {0}",
- "UserStartedPlayingItemWithValues": "{0} is playing {1} on {2}",
- "UserStoppedPlayingItemWithValues": "{0} has finished playing {1} on {2}",
- "ValueHasBeenAddedToLibrary": "{0} has been added to your media library",
- "ValueSpecialEpisodeName": "Special - {0}",
+ "UserCreatedWithName": "用家 {0} 已創建",
+ "UserDeletedWithName": "用家 {0} 已移除",
+ "UserDownloadingItemWithValues": "{0} 正在下載 {1}",
+ "UserLockedOutWithName": "用家 {0} 已被鎖定",
+ "UserOfflineFromDevice": "{0} 已從 {1} 斷開",
+ "UserOnlineFromDevice": "{0} 已連綫,來自 {1}",
+ "UserPasswordChangedWithName": "用家 {0} 的密碼已變更",
+ "UserPolicyUpdatedWithName": "用戶協議已被更新為 {0}",
+ "UserStartedPlayingItemWithValues": "{0} 正在 {2} 上播放 {1}",
+ "UserStoppedPlayingItemWithValues": "{0} 已在 {2} 上停止播放 {1}",
+ "ValueHasBeenAddedToLibrary": "{0} 已添加到你的媒體庫",
+ "ValueSpecialEpisodeName": "特典 - {0}",
"VersionNumber": "版本{0}"
}
diff --git a/Emby.Server.Implementations/Localization/Core/zh-TW.json b/Emby.Server.Implementations/Localization/Core/zh-TW.json
index 33bdbfb98..a22f66df9 100644
--- a/Emby.Server.Implementations/Localization/Core/zh-TW.json
+++ b/Emby.Server.Implementations/Localization/Core/zh-TW.json
@@ -20,7 +20,7 @@
"HeaderContinueWatching": "繼續觀賞",
"HeaderFavoriteAlbums": "最愛專輯",
"HeaderFavoriteArtists": "最愛演出者",
- "HeaderFavoriteEpisodes": "最愛級數",
+ "HeaderFavoriteEpisodes": "最愛影集",
"HeaderFavoriteShows": "最愛節目",
"HeaderFavoriteSongs": "最愛歌曲",
"HeaderLiveTV": "電視直播",
@@ -50,10 +50,10 @@
"NotificationOptionCameraImageUploaded": "相機相片已上傳",
"NotificationOptionInstallationFailed": "安裝失敗",
"NotificationOptionNewLibraryContent": "已新增新內容",
- "NotificationOptionPluginError": "外掛失敗",
- "NotificationOptionPluginInstalled": "外掛已安裝",
- "NotificationOptionPluginUninstalled": "外掛已移除",
- "NotificationOptionPluginUpdateInstalled": "已更新外掛",
+ "NotificationOptionPluginError": "插件安裝錯誤",
+ "NotificationOptionPluginInstalled": "插件已安裝",
+ "NotificationOptionPluginUninstalled": "插件已移除",
+ "NotificationOptionPluginUpdateInstalled": "插件已更新",
"NotificationOptionServerRestartRequired": "伺服器需要重新啟動",
"NotificationOptionTaskFailed": "排程任務失敗",
"NotificationOptionUserLockedOut": "使用者已鎖定",
@@ -61,7 +61,7 @@
"NotificationOptionVideoPlaybackStopped": "影片停止播放",
"Photos": "相片",
"Playlists": "播放清單",
- "Plugin": "外掛",
+ "Plugin": "插件",
"PluginInstalledWithName": "{0} 已安裝",
"PluginUninstalledWithName": "{0} 已移除",
"PluginUpdatedWithName": "{0} 已更新",
@@ -72,7 +72,6 @@
"Shows": "節目",
"Songs": "歌曲",
"StartupEmbyServerIsLoading": "Jellyfin Server正在啟動,請稍後再試一次。",
- "SubtitlesDownloadedForItem": "已為 {0} 下載字幕",
"Sync": "同步",
"System": "系統",
"TvShows": "電視節目",
@@ -92,5 +91,27 @@
"VersionNumber": "版本 {0}",
"HeaderRecordingGroups": "錄製組",
"Inherit": "繼承",
- "SubtitleDownloadFailureFromForItem": "無法為 {1} 從 {0} 下載字幕"
+ "SubtitleDownloadFailureFromForItem": "無法為 {1} 從 {0} 下載字幕",
+ "TaskDownloadMissingSubtitlesDescription": "在網路上透過描述資料搜尋遺失的字幕。",
+ "TaskDownloadMissingSubtitles": "下載遺失的字幕",
+ "TaskRefreshChannels": "重新整理頻道",
+ "TaskUpdatePlugins": "更新插件",
+ "TaskRefreshPeople": "重新整理人員",
+ "TaskCleanLogsDescription": "刪除超過{0}天的紀錄檔案。",
+ "TaskCleanLogs": "清空紀錄資料夾",
+ "TaskRefreshLibraryDescription": "掃描媒體庫內新的檔案並重新整理描述資料。",
+ "TaskRefreshLibrary": "掃描媒體庫",
+ "TaskRefreshChapterImages": "擷取章節圖片",
+ "TaskCleanCacheDescription": "刪除系統長時間不需要的快取。",
+ "TaskCleanCache": "清除快取資料夾",
+ "TasksLibraryCategory": "媒體庫",
+ "TaskRefreshChannelsDescription": "重新整理網絡頻道資料。",
+ "TaskCleanTranscodeDescription": "刪除超過一天的轉碼檔案。",
+ "TaskCleanTranscode": "清除轉碼資料夾",
+ "TaskUpdatePluginsDescription": "下載並安裝配置為自動更新的插件的更新。",
+ "TaskRefreshPeopleDescription": "更新媒體庫中演員和導演的中繼資料。",
+ "TaskRefreshChapterImagesDescription": "為有章節的視頻創建縮圖。",
+ "TasksChannelsCategory": "網絡頻道",
+ "TasksApplicationCategory": "應用程式",
+ "TasksMaintenanceCategory": "維修"
}
diff --git a/Emby.Server.Implementations/Localization/LocalizationManager.cs b/Emby.Server.Implementations/Localization/LocalizationManager.cs
index bda43e832..e2a634e1a 100644
--- a/Emby.Server.Implementations/Localization/LocalizationManager.cs
+++ b/Emby.Server.Implementations/Localization/LocalizationManager.cs
@@ -23,9 +23,6 @@ namespace Emby.Server.Implementations.Localization
private static readonly Assembly _assembly = typeof(LocalizationManager).Assembly;
private static readonly string[] _unratedValues = { "n/a", "unrated", "not rated" };
- /// <summary>
- /// The _configuration manager.
- /// </summary>
private readonly IServerConfigurationManager _configurationManager;
private readonly IJsonSerializer _jsonSerializer;
private readonly ILogger _logger;
diff --git a/Emby.Server.Implementations/MediaEncoder/EncodingManager.cs b/Emby.Server.Implementations/MediaEncoder/EncodingManager.cs
index 840aca7a6..677d68b4c 100644
--- a/Emby.Server.Implementations/MediaEncoder/EncodingManager.cs
+++ b/Emby.Server.Implementations/MediaEncoder/EncodingManager.cs
@@ -26,14 +26,20 @@ namespace Emby.Server.Implementations.MediaEncoder
private readonly IChapterManager _chapterManager;
private readonly ILibraryManager _libraryManager;
+ /// <summary>
+ /// The first chapter ticks.
+ /// </summary>
+ private static readonly long _firstChapterTicks = TimeSpan.FromSeconds(15).Ticks;
+
public EncodingManager(
+ ILogger<EncodingManager> logger,
IFileSystem fileSystem,
- ILoggerFactory loggerFactory,
IMediaEncoder encoder,
- IChapterManager chapterManager, ILibraryManager libraryManager)
+ IChapterManager chapterManager,
+ ILibraryManager libraryManager)
{
+ _logger = logger;
_fileSystem = fileSystem;
- _logger = loggerFactory.CreateLogger(nameof(EncodingManager));
_encoder = encoder;
_chapterManager = chapterManager;
_libraryManager = libraryManager;
@@ -97,12 +103,7 @@ namespace Emby.Server.Implementations.MediaEncoder
return video.DefaultVideoStreamIndex.HasValue;
}
- /// <summary>
- /// The first chapter ticks
- /// </summary>
- private static readonly long FirstChapterTicks = TimeSpan.FromSeconds(15).Ticks;
-
- public async Task<bool> RefreshChapterImages(Video video, IDirectoryService directoryService, List<ChapterInfo> chapters, bool extractImages, bool saveChapters, CancellationToken cancellationToken)
+ public async Task<bool> RefreshChapterImages(Video video, IDirectoryService directoryService, IReadOnlyList<ChapterInfo> chapters, bool extractImages, bool saveChapters, CancellationToken cancellationToken)
{
if (!IsEligibleForChapterImageExtraction(video))
{
@@ -135,7 +136,7 @@ namespace Emby.Server.Implementations.MediaEncoder
try
{
// Add some time for the first chapter to make sure we don't end up with a black image
- var time = chapter.StartPositionTicks == 0 ? TimeSpan.FromTicks(Math.Min(FirstChapterTicks, video.RunTimeTicks ?? 0)) : TimeSpan.FromTicks(chapter.StartPositionTicks);
+ var time = chapter.StartPositionTicks == 0 ? TimeSpan.FromTicks(Math.Min(_firstChapterTicks, video.RunTimeTicks ?? 0)) : TimeSpan.FromTicks(chapter.StartPositionTicks);
var protocol = MediaProtocol.File;
@@ -152,9 +153,9 @@ namespace Emby.Server.Implementations.MediaEncoder
{
_fileSystem.DeleteFile(tempFile);
}
- catch
+ catch (IOException ex)
{
-
+ _logger.LogError(ex, "Error deleting temporary chapter image encoding file {Path}", tempFile);
}
chapter.ImagePath = path;
@@ -184,7 +185,7 @@ namespace Emby.Server.Implementations.MediaEncoder
if (saveChapters && changesMade)
{
- _chapterManager.SaveChapters(video.Id.ToString(), chapters);
+ _chapterManager.SaveChapters(video.Id, chapters);
}
DeleteDeadImages(currentImages, chapters);
@@ -199,22 +200,21 @@ namespace Emby.Server.Implementations.MediaEncoder
return Path.Combine(GetChapterImagesPath(video), filename);
}
- private static List<string> GetSavedChapterImages(Video video, IDirectoryService directoryService)
+ private static IReadOnlyList<string> GetSavedChapterImages(Video video, IDirectoryService directoryService)
{
var path = GetChapterImagesPath(video);
if (!Directory.Exists(path))
{
- return new List<string>();
+ return Array.Empty<string>();
}
try
{
- return directoryService.GetFilePaths(path)
- .ToList();
+ return directoryService.GetFilePaths(path);
}
catch (IOException)
{
- return new List<string>();
+ return Array.Empty<string>();
}
}
@@ -227,7 +227,7 @@ namespace Emby.Server.Implementations.MediaEncoder
foreach (var image in deadImages)
{
- _logger.LogDebug("Deleting dead chapter image {path}", image);
+ _logger.LogDebug("Deleting dead chapter image {Path}", image);
try
{
@@ -235,7 +235,7 @@ namespace Emby.Server.Implementations.MediaEncoder
}
catch (IOException ex)
{
- _logger.LogError(ex, "Error deleting {path}.", image);
+ _logger.LogError(ex, "Error deleting {Path}.", image);
}
}
}
diff --git a/Emby.Server.Implementations/Net/SocketFactory.cs b/Emby.Server.Implementations/Net/SocketFactory.cs
index 0870db003..e42ff8496 100644
--- a/Emby.Server.Implementations/Net/SocketFactory.cs
+++ b/Emby.Server.Implementations/Net/SocketFactory.cs
@@ -1,5 +1,4 @@
using System;
-using System.IO;
using System.Net;
using System.Net.Sockets;
using MediaBrowser.Model.Net;
@@ -8,32 +7,6 @@ namespace Emby.Server.Implementations.Net
{
public class SocketFactory : ISocketFactory
{
- /// <summary>
- /// Creates a new UDP acceptSocket and binds it to the specified local port.
- /// </summary>
- /// <param name="localPort">An integer specifying the local port to bind the acceptSocket to.</param>
- public ISocket CreateUdpSocket(int localPort)
- {
- if (localPort < 0)
- {
- throw new ArgumentException("localPort cannot be less than zero.", nameof(localPort));
- }
-
- var retVal = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
-
- try
- {
- retVal.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true);
- return new UdpSocket(retVal, localPort, IPAddress.Any);
- }
- catch
- {
- retVal?.Dispose();
-
- throw;
- }
- }
-
public ISocket CreateUdpBroadcastSocket(int localPort)
{
if (localPort < 0)
@@ -156,8 +129,5 @@ namespace Emby.Server.Implementations.Net
throw;
}
}
-
- public Stream CreateNetworkStream(ISocket socket, bool ownsSocket)
- => new NetworkStream(((UdpSocket)socket).Socket, ownsSocket);
}
}
diff --git a/Emby.Server.Implementations/Net/UdpSocket.cs b/Emby.Server.Implementations/Net/UdpSocket.cs
index dde4a2a34..211ca6784 100644
--- a/Emby.Server.Implementations/Net/UdpSocket.cs
+++ b/Emby.Server.Implementations/Net/UdpSocket.cs
@@ -181,15 +181,6 @@ namespace Emby.Server.Implementations.Net
return taskCompletion.Task;
}
- public Task<SocketReceiveResult> ReceiveAsync(CancellationToken cancellationToken)
- {
- ThrowIfDisposed();
-
- var buffer = new byte[8192];
-
- return ReceiveAsync(buffer, 0, buffer.Length, cancellationToken);
- }
-
public Task SendToAsync(byte[] buffer, int offset, int size, IPEndPoint endPoint, CancellationToken cancellationToken)
{
ThrowIfDisposed();
diff --git a/Emby.Server.Implementations/Net/WebSocketConnectEventArgs.cs b/Emby.Server.Implementations/Net/WebSocketConnectEventArgs.cs
new file mode 100644
index 000000000..6880766f9
--- /dev/null
+++ b/Emby.Server.Implementations/Net/WebSocketConnectEventArgs.cs
@@ -0,0 +1,29 @@
+using System;
+using Microsoft.AspNetCore.Http;
+
+namespace Emby.Server.Implementations.Net
+{
+ public class WebSocketConnectEventArgs : EventArgs
+ {
+ /// <summary>
+ /// Gets or sets the URL.
+ /// </summary>
+ /// <value>The URL.</value>
+ public string Url { get; set; }
+ /// <summary>
+ /// Gets or sets the query string.
+ /// </summary>
+ /// <value>The query string.</value>
+ public IQueryCollection QueryString { get; set; }
+ /// <summary>
+ /// Gets or sets the web socket.
+ /// </summary>
+ /// <value>The web socket.</value>
+ public IWebSocket WebSocket { get; set; }
+ /// <summary>
+ /// Gets or sets the endpoint.
+ /// </summary>
+ /// <value>The endpoint.</value>
+ public string Endpoint { get; set; }
+ }
+}
diff --git a/Emby.Server.Implementations/Networking/NetworkManager.cs b/Emby.Server.Implementations/Networking/NetworkManager.cs
index 1d8d3cf39..b3e88b667 100644
--- a/Emby.Server.Implementations/Networking/NetworkManager.cs
+++ b/Emby.Server.Implementations/Networking/NetworkManager.cs
@@ -500,7 +500,7 @@ namespace Emby.Server.Implementations.Networking
{
if (ip.Address.Equals(address) && ip.IPv4Mask != null)
{
- return ip.IPv4Mask;
+ return ip.IPv4Mask;
}
}
}
diff --git a/Emby.Server.Implementations/Playlists/PlaylistImageProvider.cs b/Emby.Server.Implementations/Playlists/PlaylistImageProvider.cs
index 2dfe59088..bb56d9771 100644
--- a/Emby.Server.Implementations/Playlists/PlaylistImageProvider.cs
+++ b/Emby.Server.Implementations/Playlists/PlaylistImageProvider.cs
@@ -1,4 +1,3 @@
-using System;
using System.Collections.Generic;
using System.Linq;
using Emby.Server.Implementations.Images;
diff --git a/Emby.Server.Implementations/Playlists/PlaylistManager.cs b/Emby.Server.Implementations/Playlists/PlaylistManager.cs
index b26f4026c..9b1510ac9 100644
--- a/Emby.Server.Implementations/Playlists/PlaylistManager.cs
+++ b/Emby.Server.Implementations/Playlists/PlaylistManager.cs
@@ -8,12 +8,14 @@ using System.Threading.Tasks;
using MediaBrowser.Controller.Dto;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.Audio;
+using MediaBrowser.Controller.Extensions;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Playlists;
using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.IO;
using MediaBrowser.Model.Playlists;
+using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
using PlaylistsNET.Content;
using PlaylistsNET.Models;
@@ -28,21 +30,24 @@ namespace Emby.Server.Implementations.Playlists
private readonly ILogger _logger;
private readonly IUserManager _userManager;
private readonly IProviderManager _providerManager;
+ private readonly IConfiguration _appConfig;
public PlaylistManager(
ILibraryManager libraryManager,
IFileSystem fileSystem,
ILibraryMonitor iLibraryMonitor,
- ILoggerFactory loggerFactory,
+ ILogger<PlaylistManager> logger,
IUserManager userManager,
- IProviderManager providerManager)
+ IProviderManager providerManager,
+ IConfiguration appConfig)
{
_libraryManager = libraryManager;
_fileSystem = fileSystem;
_iLibraryMonitor = iLibraryMonitor;
- _logger = loggerFactory.CreateLogger(nameof(PlaylistManager));
+ _logger = logger;
_userManager = userManager;
_providerManager = providerManager;
+ _appConfig = appConfig;
}
public IEnumerable<Playlist> GetPlaylists(Guid userId)
@@ -177,7 +182,7 @@ namespace Emby.Server.Implementations.Playlists
return Playlist.GetPlaylistItems(playlistMediaType, items, user, options);
}
- public void AddToPlaylist(string playlistId, IEnumerable<Guid> itemIds, Guid userId)
+ public void AddToPlaylist(string playlistId, ICollection<Guid> itemIds, Guid userId)
{
var user = userId.Equals(Guid.Empty) ? null : _userManager.GetUserById(userId);
@@ -187,37 +192,59 @@ namespace Emby.Server.Implementations.Playlists
});
}
- private void AddToPlaylistInternal(string playlistId, IEnumerable<Guid> itemIds, User user, DtoOptions options)
+ private void AddToPlaylistInternal(string playlistId, ICollection<Guid> newItemIds, User user, DtoOptions options)
{
- var playlist = _libraryManager.GetItemById(playlistId) as Playlist;
+ // Retrieve the existing playlist
+ var playlist = _libraryManager.GetItemById(playlistId) as Playlist
+ ?? throw new ArgumentException("No Playlist exists with Id " + playlistId);
- if (playlist == null)
+ // Retrieve all the items to be added to the playlist
+ var newItems = GetPlaylistItems(newItemIds, playlist.MediaType, user, options)
+ .Where(i => i.SupportsAddingToPlaylist);
+
+ // Filter out duplicate items, if necessary
+ if (!_appConfig.DoPlaylistsAllowDuplicates())
{
- throw new ArgumentException("No Playlist exists with the supplied Id");
+ var existingIds = playlist.LinkedChildren.Select(c => c.ItemId).ToHashSet();
+ newItems = newItems
+ .Where(i => !existingIds.Contains(i.Id))
+ .Distinct();
}
- var list = new List<LinkedChild>();
-
- var items = GetPlaylistItems(itemIds, playlist.MediaType, user, options)
- .Where(i => i.SupportsAddingToPlaylist)
+ // Create a list of the new linked children to add to the playlist
+ var childrenToAdd = newItems
+ .Select(i => LinkedChild.Create(i))
.ToList();
- foreach (var item in items)
+ // Log duplicates that have been ignored, if any
+ int numDuplicates = newItemIds.Count - childrenToAdd.Count;
+ if (numDuplicates > 0)
{
- list.Add(LinkedChild.Create(item));
+ _logger.LogWarning("Ignored adding {DuplicateCount} duplicate items to playlist {PlaylistName}.", numDuplicates, playlist.Name);
}
- var newList = playlist.LinkedChildren.ToList();
- newList.AddRange(list);
- playlist.LinkedChildren = newList.ToArray();
+ // Do nothing else if there are no items to add to the playlist
+ if (childrenToAdd.Count == 0)
+ {
+ return;
+ }
+
+ // Create a new array with the updated playlist items
+ var newLinkedChildren = new LinkedChild[playlist.LinkedChildren.Length + childrenToAdd.Count];
+ playlist.LinkedChildren.CopyTo(newLinkedChildren, 0);
+ childrenToAdd.CopyTo(newLinkedChildren, playlist.LinkedChildren.Length);
+ // Update the playlist in the repository
+ playlist.LinkedChildren = newLinkedChildren;
playlist.UpdateToRepository(ItemUpdateType.MetadataEdit, CancellationToken.None);
+ // Update the playlist on disk
if (playlist.IsFile)
{
SavePlaylistFile(playlist);
}
+ // Refresh playlist metadata
_providerManager.QueueRefresh(
playlist.Id,
new MetadataRefreshOptions(new DirectoryService(_fileSystem))
diff --git a/Emby.Server.Implementations/ScheduledTasks/TaskManager.cs b/Emby.Server.Implementations/ScheduledTasks/TaskManager.cs
index ecf58dbc0..6ffa581a9 100644
--- a/Emby.Server.Implementations/ScheduledTasks/TaskManager.cs
+++ b/Emby.Server.Implementations/ScheduledTasks/TaskManager.cs
@@ -32,22 +32,8 @@ namespace Emby.Server.Implementations.ScheduledTasks
private readonly ConcurrentQueue<Tuple<Type, TaskOptions>> _taskQueue =
new ConcurrentQueue<Tuple<Type, TaskOptions>>();
- /// <summary>
- /// Gets or sets the json serializer.
- /// </summary>
- /// <value>The json serializer.</value>
private readonly IJsonSerializer _jsonSerializer;
-
- /// <summary>
- /// Gets or sets the application paths.
- /// </summary>
- /// <value>The application paths.</value>
private readonly IApplicationPaths _applicationPaths;
-
- /// <summary>
- /// Gets the logger.
- /// </summary>
- /// <value>The logger.</value>
private readonly ILogger _logger;
private readonly IFileSystem _fileSystem;
@@ -56,17 +42,17 @@ namespace Emby.Server.Implementations.ScheduledTasks
/// </summary>
/// <param name="applicationPaths">The application paths.</param>
/// <param name="jsonSerializer">The json serializer.</param>
- /// <param name="loggerFactory">The logger factory.</param>
+ /// <param name="logger">The logger.</param>
/// <param name="fileSystem">The filesystem manager.</param>
public TaskManager(
IApplicationPaths applicationPaths,
IJsonSerializer jsonSerializer,
- ILoggerFactory loggerFactory,
+ ILogger<TaskManager> logger,
IFileSystem fileSystem)
{
_applicationPaths = applicationPaths;
_jsonSerializer = jsonSerializer;
- _logger = loggerFactory.CreateLogger(nameof(TaskManager));
+ _logger = logger;
_fileSystem = fileSystem;
ScheduledTasks = Array.Empty<IScheduledTaskWorker>();
diff --git a/Emby.Server.Implementations/ScheduledTasks/Tasks/ChapterImagesTask.cs b/Emby.Server.Implementations/ScheduledTasks/Tasks/ChapterImagesTask.cs
index ecd526251..ea6a70615 100644
--- a/Emby.Server.Implementations/ScheduledTasks/Tasks/ChapterImagesTask.cs
+++ b/Emby.Server.Implementations/ScheduledTasks/Tasks/ChapterImagesTask.cs
@@ -15,6 +15,7 @@ using MediaBrowser.Model.Entities;
using MediaBrowser.Model.IO;
using MediaBrowser.Model.Tasks;
using Microsoft.Extensions.Logging;
+using MediaBrowser.Model.Globalization;
namespace Emby.Server.Implementations.ScheduledTasks
{
@@ -39,11 +40,19 @@ namespace Emby.Server.Implementations.ScheduledTasks
private readonly IEncodingManager _encodingManager;
private readonly IFileSystem _fileSystem;
+ private readonly ILocalizationManager _localization;
/// <summary>
/// Initializes a new instance of the <see cref="ChapterImagesTask" /> class.
/// </summary>
- public ChapterImagesTask(ILoggerFactory loggerFactory, ILibraryManager libraryManager, IItemRepository itemRepo, IApplicationPaths appPaths, IEncodingManager encodingManager, IFileSystem fileSystem)
+ public ChapterImagesTask(
+ ILoggerFactory loggerFactory,
+ ILibraryManager libraryManager,
+ IItemRepository itemRepo,
+ IApplicationPaths appPaths,
+ IEncodingManager encodingManager,
+ IFileSystem fileSystem,
+ ILocalizationManager localization)
{
_logger = loggerFactory.CreateLogger(GetType().Name);
_libraryManager = libraryManager;
@@ -51,6 +60,7 @@ namespace Emby.Server.Implementations.ScheduledTasks
_appPaths = appPaths;
_encodingManager = encodingManager;
_fileSystem = fileSystem;
+ _localization = localization;
}
/// <summary>
@@ -70,7 +80,7 @@ namespace Emby.Server.Implementations.ScheduledTasks
}
/// <summary>
- /// Returns the task to be executed
+ /// Returns the task to be executed.
/// </summary>
/// <param name="cancellationToken">The cancellation token.</param>
/// <param name="progress">The progress.</param>
@@ -89,7 +99,6 @@ namespace Emby.Server.Implementations.ScheduledTasks
SourceTypes = new SourceType[] { SourceType.Library },
HasChapterImages = false,
IsVirtualItem = false
-
})
.OfType<Video>()
.ToList();
@@ -160,11 +169,11 @@ namespace Emby.Server.Implementations.ScheduledTasks
}
}
- public string Name => "Chapter image extraction";
+ public string Name => _localization.GetLocalizedString("TaskRefreshChapterImages");
- public string Description => "Creates thumbnails for videos that have chapters.";
+ public string Description => _localization.GetLocalizedString("TaskRefreshChapterImagesDescription");
- public string Category => "Library";
+ public string Category => _localization.GetLocalizedString("TasksLibraryCategory");
public string Key => "RefreshChapterImages";
diff --git a/Emby.Server.Implementations/ScheduledTasks/Tasks/DeleteCacheFileTask.cs b/Emby.Server.Implementations/ScheduledTasks/Tasks/DeleteCacheFileTask.cs
index 6ec83b5c0..9df7c538b 100644
--- a/Emby.Server.Implementations/ScheduledTasks/Tasks/DeleteCacheFileTask.cs
+++ b/Emby.Server.Implementations/ScheduledTasks/Tasks/DeleteCacheFileTask.cs
@@ -8,6 +8,7 @@ using MediaBrowser.Common.Configuration;
using MediaBrowser.Model.IO;
using MediaBrowser.Model.Tasks;
using Microsoft.Extensions.Logging;
+using MediaBrowser.Model.Globalization;
namespace Emby.Server.Implementations.ScheduledTasks.Tasks
{
@@ -25,15 +26,21 @@ namespace Emby.Server.Implementations.ScheduledTasks.Tasks
private readonly ILogger _logger;
private readonly IFileSystem _fileSystem;
+ private readonly ILocalizationManager _localization;
/// <summary>
/// Initializes a new instance of the <see cref="DeleteCacheFileTask" /> class.
/// </summary>
- public DeleteCacheFileTask(IApplicationPaths appPaths, ILogger logger, IFileSystem fileSystem)
+ public DeleteCacheFileTask(
+ IApplicationPaths appPaths,
+ ILogger<DeleteCacheFileTask> logger,
+ IFileSystem fileSystem,
+ ILocalizationManager localization)
{
ApplicationPaths = appPaths;
_logger = logger;
_fileSystem = fileSystem;
+ _localization = localization;
}
/// <summary>
@@ -158,11 +165,11 @@ namespace Emby.Server.Implementations.ScheduledTasks.Tasks
}
}
- public string Name => "Cache file cleanup";
+ public string Name => _localization.GetLocalizedString("TaskCleanCache");
- public string Description => "Deletes cache files no longer needed by the system";
+ public string Description => _localization.GetLocalizedString("TaskCleanCacheDescription");
- public string Category => "Maintenance";
+ public string Category => _localization.GetLocalizedString("TasksMaintenanceCategory");
public string Key => "DeleteCacheFiles";
diff --git a/Emby.Server.Implementations/ScheduledTasks/Tasks/DeleteLogFileTask.cs b/Emby.Server.Implementations/ScheduledTasks/Tasks/DeleteLogFileTask.cs
index e468c301a..3140aa489 100644
--- a/Emby.Server.Implementations/ScheduledTasks/Tasks/DeleteLogFileTask.cs
+++ b/Emby.Server.Implementations/ScheduledTasks/Tasks/DeleteLogFileTask.cs
@@ -6,11 +6,12 @@ using System.Threading.Tasks;
using MediaBrowser.Common.Configuration;
using MediaBrowser.Model.IO;
using MediaBrowser.Model.Tasks;
+using MediaBrowser.Model.Globalization;
namespace Emby.Server.Implementations.ScheduledTasks.Tasks
{
/// <summary>
- /// Deletes old log files
+ /// Deletes old log files.
/// </summary>
public class DeleteLogFileTask : IScheduledTask, IConfigurableScheduledTask
{
@@ -21,32 +22,32 @@ namespace Emby.Server.Implementations.ScheduledTasks.Tasks
private IConfigurationManager ConfigurationManager { get; set; }
private readonly IFileSystem _fileSystem;
+ private readonly ILocalizationManager _localization;
/// <summary>
/// Initializes a new instance of the <see cref="DeleteLogFileTask" /> class.
/// </summary>
/// <param name="configurationManager">The configuration manager.</param>
- public DeleteLogFileTask(IConfigurationManager configurationManager, IFileSystem fileSystem)
+ public DeleteLogFileTask(IConfigurationManager configurationManager, IFileSystem fileSystem, ILocalizationManager localization)
{
ConfigurationManager = configurationManager;
_fileSystem = fileSystem;
+ _localization = localization;
}
/// <summary>
- /// Creates the triggers that define when the task will run
+ /// Creates the triggers that define when the task will run.
/// </summary>
/// <returns>IEnumerable{BaseTaskTrigger}.</returns>
public IEnumerable<TaskTriggerInfo> GetDefaultTriggers()
{
return new[] {
-
- // Every so often
new TaskTriggerInfo { Type = TaskTriggerInfo.TriggerInterval, IntervalTicks = TimeSpan.FromHours(24).Ticks}
};
}
/// <summary>
- /// Returns the task to be executed
+ /// Returns the task to be executed.
/// </summary>
/// <param name="cancellationToken">The cancellation token.</param>
/// <param name="progress">The progress.</param>
@@ -81,11 +82,11 @@ namespace Emby.Server.Implementations.ScheduledTasks.Tasks
return Task.CompletedTask;
}
- public string Name => "Log file cleanup";
+ public string Name => _localization.GetLocalizedString("TaskCleanLogs");
- public string Description => string.Format("Deletes log files that are more than {0} days old.", ConfigurationManager.CommonConfiguration.LogFileRetentionDays);
+ public string Description => string.Format(_localization.GetLocalizedString("TaskCleanLogsDescription"), ConfigurationManager.CommonConfiguration.LogFileRetentionDays);
- public string Category => "Maintenance";
+ public string Category => _localization.GetLocalizedString("TasksMaintenanceCategory");
public string Key => "CleanLogFiles";
diff --git a/Emby.Server.Implementations/ScheduledTasks/Tasks/DeleteTranscodeFileTask.cs b/Emby.Server.Implementations/ScheduledTasks/Tasks/DeleteTranscodeFileTask.cs
index f197734d4..1d133dcda 100644
--- a/Emby.Server.Implementations/ScheduledTasks/Tasks/DeleteTranscodeFileTask.cs
+++ b/Emby.Server.Implementations/ScheduledTasks/Tasks/DeleteTranscodeFileTask.cs
@@ -8,6 +8,7 @@ using MediaBrowser.Common.Configuration;
using MediaBrowser.Model.IO;
using MediaBrowser.Model.Tasks;
using Microsoft.Extensions.Logging;
+using MediaBrowser.Model.Globalization;
namespace Emby.Server.Implementations.ScheduledTasks.Tasks
{
@@ -19,15 +20,21 @@ namespace Emby.Server.Implementations.ScheduledTasks.Tasks
private readonly ILogger _logger;
private readonly IConfigurationManager _configurationManager;
private readonly IFileSystem _fileSystem;
+ private readonly ILocalizationManager _localization;
/// <summary>
/// Initializes a new instance of the <see cref="DeleteTranscodeFileTask" /> class.
/// </summary>
- public DeleteTranscodeFileTask(ILogger logger, IFileSystem fileSystem, IConfigurationManager configurationManager)
+ public DeleteTranscodeFileTask(
+ ILogger<DeleteTranscodeFileTask> logger,
+ IFileSystem fileSystem,
+ IConfigurationManager configurationManager,
+ ILocalizationManager localization)
{
_logger = logger;
_fileSystem = fileSystem;
_configurationManager = configurationManager;
+ _localization = localization;
}
/// <summary>
@@ -125,11 +132,11 @@ namespace Emby.Server.Implementations.ScheduledTasks.Tasks
}
}
- public string Name => "Transcode file cleanup";
+ public string Name => _localization.GetLocalizedString("TaskCleanTranscode");
- public string Description => "Deletes transcode files more than 24 hours old.";
+ public string Description => _localization.GetLocalizedString("TaskCleanTranscodeDescription");
- public string Category => "Maintenance";
+ public string Category => _localization.GetLocalizedString("TasksMaintenanceCategory");
public string Key => "DeleteTranscodeFiles";
diff --git a/Emby.Server.Implementations/ScheduledTasks/Tasks/PeopleValidationTask.cs b/Emby.Server.Implementations/ScheduledTasks/Tasks/PeopleValidationTask.cs
index d70799c47..63f867bf6 100644
--- a/Emby.Server.Implementations/ScheduledTasks/Tasks/PeopleValidationTask.cs
+++ b/Emby.Server.Implementations/ScheduledTasks/Tasks/PeopleValidationTask.cs
@@ -5,40 +5,42 @@ using System.Threading.Tasks;
using MediaBrowser.Controller;
using MediaBrowser.Controller.Library;
using MediaBrowser.Model.Tasks;
+using MediaBrowser.Model.Globalization;
namespace Emby.Server.Implementations.ScheduledTasks
{
/// <summary>
- /// Class PeopleValidationTask
+ /// Class PeopleValidationTask.
/// </summary>
public class PeopleValidationTask : IScheduledTask
{
/// <summary>
- /// The _library manager
+ /// The library manager.
/// </summary>
private readonly ILibraryManager _libraryManager;
private readonly IServerApplicationHost _appHost;
+ private readonly ILocalizationManager _localization;
/// <summary>
/// Initializes a new instance of the <see cref="PeopleValidationTask" /> class.
/// </summary>
/// <param name="libraryManager">The library manager.</param>
/// <param name="appHost">The server application host</param>
- public PeopleValidationTask(ILibraryManager libraryManager, IServerApplicationHost appHost)
+ public PeopleValidationTask(ILibraryManager libraryManager, IServerApplicationHost appHost, ILocalizationManager localization)
{
_libraryManager = libraryManager;
_appHost = appHost;
+ _localization = localization;
}
/// <summary>
- /// Creates the triggers that define when the task will run
+ /// Creates the triggers that define when the task will run.
/// </summary>
public IEnumerable<TaskTriggerInfo> GetDefaultTriggers()
{
return new[]
{
- // Every so often
new TaskTriggerInfo
{
Type = TaskTriggerInfo.TriggerInterval,
@@ -48,7 +50,7 @@ namespace Emby.Server.Implementations.ScheduledTasks
}
/// <summary>
- /// Returns the task to be executed
+ /// Returns the task to be executed.
/// </summary>
/// <param name="cancellationToken">The cancellation token.</param>
/// <param name="progress">The progress.</param>
@@ -58,11 +60,11 @@ namespace Emby.Server.Implementations.ScheduledTasks
return _libraryManager.ValidatePeople(cancellationToken, progress);
}
- public string Name => "Refresh people";
+ public string Name => _localization.GetLocalizedString("TaskRefreshPeople");
- public string Description => "Updates metadata for actors and directors in your media library.";
+ public string Description => _localization.GetLocalizedString("TaskRefreshPeopleDescription");
- public string Category => "Library";
+ public string Category => _localization.GetLocalizedString("TasksLibraryCategory");
public string Key => "RefreshPeople";
diff --git a/Emby.Server.Implementations/ScheduledTasks/Tasks/PluginUpdateTask.cs b/Emby.Server.Implementations/ScheduledTasks/Tasks/PluginUpdateTask.cs
index 909fffb1f..6a1afced7 100644
--- a/Emby.Server.Implementations/ScheduledTasks/Tasks/PluginUpdateTask.cs
+++ b/Emby.Server.Implementations/ScheduledTasks/Tasks/PluginUpdateTask.cs
@@ -8,6 +8,7 @@ using MediaBrowser.Common.Updates;
using MediaBrowser.Model.Net;
using MediaBrowser.Model.Tasks;
using Microsoft.Extensions.Logging;
+using MediaBrowser.Model.Globalization;
namespace Emby.Server.Implementations.ScheduledTasks
{
@@ -22,11 +23,13 @@ namespace Emby.Server.Implementations.ScheduledTasks
private readonly ILogger _logger;
private readonly IInstallationManager _installationManager;
+ private readonly ILocalizationManager _localization;
- public PluginUpdateTask(ILogger logger, IInstallationManager installationManager)
+ public PluginUpdateTask(ILogger<PluginUpdateTask> logger, IInstallationManager installationManager, ILocalizationManager localization)
{
_logger = logger;
_installationManager = installationManager;
+ _localization = localization;
}
/// <summary>
@@ -52,9 +55,8 @@ namespace Emby.Server.Implementations.ScheduledTasks
{
progress.Report(0);
- var packagesToInstall = await _installationManager.GetAvailablePluginUpdates(cancellationToken)
- .ToListAsync(cancellationToken)
- .ConfigureAwait(false);
+ var packageFetchTask = _installationManager.GetAvailablePluginUpdates(cancellationToken);
+ var packagesToInstall = (await packageFetchTask.ConfigureAwait(false)).ToList();
progress.Report(10);
@@ -96,13 +98,13 @@ namespace Emby.Server.Implementations.ScheduledTasks
}
/// <inheritdoc />
- public string Name => "Check for plugin updates";
+ public string Name => _localization.GetLocalizedString("TaskUpdatePlugins");
/// <inheritdoc />
- public string Description => "Downloads and installs updates for plugins that are configured to update automatically.";
+ public string Description => _localization.GetLocalizedString("TaskUpdatePluginsDescription");
/// <inheritdoc />
- public string Category => "Application";
+ public string Category => _localization.GetLocalizedString("TasksApplicationCategory");
/// <inheritdoc />
public string Key => "PluginUpdates";
diff --git a/Emby.Server.Implementations/ScheduledTasks/Tasks/RefreshMediaLibraryTask.cs b/Emby.Server.Implementations/ScheduledTasks/Tasks/RefreshMediaLibraryTask.cs
index 3e6d251c9..74cb01444 100644
--- a/Emby.Server.Implementations/ScheduledTasks/Tasks/RefreshMediaLibraryTask.cs
+++ b/Emby.Server.Implementations/ScheduledTasks/Tasks/RefreshMediaLibraryTask.cs
@@ -6,6 +6,7 @@ using Emby.Server.Implementations.Library;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Library;
using MediaBrowser.Model.Tasks;
+using MediaBrowser.Model.Globalization;
namespace Emby.Server.Implementations.ScheduledTasks
{
@@ -19,15 +20,17 @@ namespace Emby.Server.Implementations.ScheduledTasks
/// </summary>
private readonly ILibraryManager _libraryManager;
private readonly IServerConfigurationManager _config;
+ private readonly ILocalizationManager _localization;
/// <summary>
/// Initializes a new instance of the <see cref="RefreshMediaLibraryTask" /> class.
/// </summary>
/// <param name="libraryManager">The library manager.</param>
- public RefreshMediaLibraryTask(ILibraryManager libraryManager, IServerConfigurationManager config)
+ public RefreshMediaLibraryTask(ILibraryManager libraryManager, IServerConfigurationManager config, ILocalizationManager localization)
{
_libraryManager = libraryManager;
_config = config;
+ _localization = localization;
}
/// <summary>
@@ -38,7 +41,8 @@ namespace Emby.Server.Implementations.ScheduledTasks
{
yield return new TaskTriggerInfo
{
- Type = TaskTriggerInfo.TriggerInterval, IntervalTicks = TimeSpan.FromHours(12).Ticks
+ Type = TaskTriggerInfo.TriggerInterval,
+ IntervalTicks = TimeSpan.FromHours(12).Ticks
};
}
@@ -57,11 +61,11 @@ namespace Emby.Server.Implementations.ScheduledTasks
return ((LibraryManager)_libraryManager).ValidateMediaLibraryInternal(progress, cancellationToken);
}
- public string Name => "Scan media library";
+ public string Name => _localization.GetLocalizedString("TaskRefreshLibrary");
- public string Description => "Scans your media library for new files and refreshes metadata.";
+ public string Description => _localization.GetLocalizedString("TaskRefreshLibraryDescription");
- public string Category => "Library";
+ public string Category => _localization.GetLocalizedString("TasksLibraryCategory");
public string Key => "RefreshLibrary";
diff --git a/Emby.Server.Implementations/Security/AuthenticationRepository.cs b/Emby.Server.Implementations/Security/AuthenticationRepository.cs
index 1ef5c4b99..4e4029f06 100644
--- a/Emby.Server.Implementations/Security/AuthenticationRepository.cs
+++ b/Emby.Server.Implementations/Security/AuthenticationRepository.cs
@@ -15,8 +15,8 @@ namespace Emby.Server.Implementations.Security
{
public class AuthenticationRepository : BaseSqliteRepository, IAuthenticationRepository
{
- public AuthenticationRepository(ILoggerFactory loggerFactory, IServerConfigurationManager config)
- : base(loggerFactory.CreateLogger(nameof(AuthenticationRepository)))
+ public AuthenticationRepository(ILogger<AuthenticationRepository> logger, IServerConfigurationManager config)
+ : base(logger)
{
DbFilePath = Path.Combine(config.ApplicationPaths.DataPath, "authentication.db");
}
diff --git a/Emby.Server.Implementations/Serialization/JsonSerializer.cs b/Emby.Server.Implementations/Serialization/JsonSerializer.cs
index 36196ee36..bcc814daf 100644
--- a/Emby.Server.Implementations/Serialization/JsonSerializer.cs
+++ b/Emby.Server.Implementations/Serialization/JsonSerializer.cs
@@ -2,7 +2,6 @@ using System;
using System.Globalization;
using System.IO;
using System.Threading.Tasks;
-using MediaBrowser.Model.IO;
using MediaBrowser.Model.Serialization;
namespace Emby.Server.Implementations.Serialization
@@ -12,13 +11,15 @@ namespace Emby.Server.Implementations.Serialization
/// </summary>
public class JsonSerializer : IJsonSerializer
{
- private readonly IFileSystem _fileSystem;
-
- public JsonSerializer(
- IFileSystem fileSystem)
+ public JsonSerializer()
{
- _fileSystem = fileSystem;
- Configure();
+ ServiceStack.Text.JsConfig.DateHandler = ServiceStack.Text.DateHandler.ISO8601;
+ ServiceStack.Text.JsConfig.ExcludeTypeInfo = true;
+ ServiceStack.Text.JsConfig.IncludeNullValues = false;
+ ServiceStack.Text.JsConfig.AlwaysUseUtc = true;
+ ServiceStack.Text.JsConfig.AssumeUtc = true;
+
+ ServiceStack.Text.JsConfig<Guid>.SerializeFn = SerializeGuid;
}
/// <summary>
@@ -81,7 +82,7 @@ namespace Emby.Server.Implementations.Serialization
throw new ArgumentNullException(nameof(file));
}
- using (var stream = _fileSystem.GetFileStream(file, FileOpenMode.Create, FileAccessMode.Write, FileShareMode.Read))
+ using (var stream = new FileStream(file, FileMode.Create, FileAccess.Write, FileShare.Read))
{
SerializeToStream(obj, stream);
}
@@ -162,7 +163,6 @@ namespace Emby.Server.Implementations.Serialization
throw new ArgumentNullException(nameof(stream));
}
-
return ServiceStack.Text.JsonSerializer.DeserializeFromStreamAsync<T>(stream);
}
@@ -225,20 +225,6 @@ namespace Emby.Server.Implementations.Serialization
}
}
- /// <summary>
- /// Configures this instance.
- /// </summary>
- private void Configure()
- {
- ServiceStack.Text.JsConfig.DateHandler = ServiceStack.Text.DateHandler.ISO8601;
- ServiceStack.Text.JsConfig.ExcludeTypeInfo = true;
- ServiceStack.Text.JsConfig.IncludeNullValues = false;
- ServiceStack.Text.JsConfig.AlwaysUseUtc = true;
- ServiceStack.Text.JsConfig.AssumeUtc = true;
-
- ServiceStack.Text.JsConfig<Guid>.SerializeFn = SerializeGuid;
- }
-
private static string SerializeGuid(Guid guid)
{
if (guid.Equals(Guid.Empty))
diff --git a/Emby.Server.Implementations/ServerApplicationPaths.cs b/Emby.Server.Implementations/ServerApplicationPaths.cs
index 2f57c97a1..dfdd4200e 100644
--- a/Emby.Server.Implementations/ServerApplicationPaths.cs
+++ b/Emby.Server.Implementations/ServerApplicationPaths.cs
@@ -9,8 +9,6 @@ namespace Emby.Server.Implementations
/// </summary>
public class ServerApplicationPaths : BaseApplicationPaths, IServerApplicationPaths
{
- private string _internalMetadataPath;
-
/// <summary>
/// Initializes a new instance of the <see cref="ServerApplicationPaths" /> class.
/// </summary>
@@ -27,6 +25,7 @@ namespace Emby.Server.Implementations
cacheDirectoryPath,
webDirectoryPath)
{
+ InternalMetadataPath = DefaultInternalMetadataPath;
}
/// <summary>
@@ -98,12 +97,11 @@ namespace Emby.Server.Implementations
/// <value>The user configuration directory path.</value>
public string UserConfigurationDirectoryPath => Path.Combine(ConfigurationDirectoryPath, "users");
+ /// <inheritdoc/>
+ public string DefaultInternalMetadataPath => Path.Combine(ProgramDataPath, "metadata");
+
/// <inheritdoc />
- public string InternalMetadataPath
- {
- get => _internalMetadataPath ?? (_internalMetadataPath = Path.Combine(DataPath, "metadata"));
- set => _internalMetadataPath = value;
- }
+ public string InternalMetadataPath { get; set; }
/// <inheritdoc />
public string VirtualInternalMetadataPath { get; } = "%MetadataPath%";
diff --git a/Emby.Server.Implementations/Services/ServiceController.cs b/Emby.Server.Implementations/Services/ServiceController.cs
index d963f9043..e24a95dbb 100644
--- a/Emby.Server.Implementations/Services/ServiceController.cs
+++ b/Emby.Server.Implementations/Services/ServiceController.cs
@@ -3,14 +3,27 @@ using System.Collections.Generic;
using System.Threading.Tasks;
using Emby.Server.Implementations.HttpServer;
using MediaBrowser.Model.Services;
+using Microsoft.Extensions.Logging;
namespace Emby.Server.Implementations.Services
{
public delegate object ActionInvokerFn(object intance, object request);
+
public delegate void VoidActionInvokerFn(object intance, object request);
public class ServiceController
{
+ private readonly ILogger _logger;
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="ServiceController"/> class.
+ /// </summary>
+ /// <param name="logger">The <see cref="ServiceController"/> logger.</param>
+ public ServiceController(ILogger<ServiceController> logger)
+ {
+ _logger = logger;
+ }
+
public void Init(HttpListenerHost appHost, IEnumerable<Type> serviceTypes)
{
foreach (var serviceType in serviceTypes)
@@ -21,6 +34,13 @@ namespace Emby.Server.Implementations.Services
public void RegisterService(HttpListenerHost appHost, Type serviceType)
{
+ // Make sure the provided type implements IService
+ if (!typeof(IService).IsAssignableFrom(serviceType))
+ {
+ _logger.LogWarning("Tried to register a service that does not implement IService: {ServiceType}", serviceType);
+ return;
+ }
+
var processedReqs = new HashSet<Type>();
var actions = ServiceExecGeneral.Reset(serviceType);
diff --git a/Emby.Server.Implementations/Services/StringMapTypeDeserializer.cs b/Emby.Server.Implementations/Services/StringMapTypeDeserializer.cs
index 23e22afd5..56e23d549 100644
--- a/Emby.Server.Implementations/Services/StringMapTypeDeserializer.cs
+++ b/Emby.Server.Implementations/Services/StringMapTypeDeserializer.cs
@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Reflection;
+using MediaBrowser.Common.Extensions;
namespace Emby.Server.Implementations.Services
{
@@ -81,7 +82,7 @@ namespace Emby.Server.Implementations.Services
if (propertySerializerEntry.PropertyType == typeof(bool))
{
//InputExtensions.cs#530 MVC Checkbox helper emits extra hidden input field, generating 2 values, first is the real value
- propertyTextValue = LeftPart(propertyTextValue, ',');
+ propertyTextValue = StringExtensions.LeftPart(propertyTextValue, ',').ToString();
}
var value = propertySerializerEntry.PropertyParseStringFn(propertyTextValue);
@@ -95,19 +96,6 @@ namespace Emby.Server.Implementations.Services
return instance;
}
-
- public static string LeftPart(string strVal, char needle)
- {
- if (strVal == null)
- {
- return null;
- }
-
- var pos = strVal.IndexOf(needle);
- return pos == -1
- ? strVal
- : strVal.Substring(0, pos);
- }
}
internal static class TypeAccessor
diff --git a/Emby.Server.Implementations/Services/SwaggerService.cs b/Emby.Server.Implementations/Services/SwaggerService.cs
index c30f32af9..5177251c3 100644
--- a/Emby.Server.Implementations/Services/SwaggerService.cs
+++ b/Emby.Server.Implementations/Services/SwaggerService.cs
@@ -1,9 +1,9 @@
using System;
using System.Collections.Generic;
using System.Linq;
+using Emby.Server.Implementations.HttpServer;
using MediaBrowser.Controller.Net;
using MediaBrowser.Model.Services;
-using Emby.Server.Implementations.HttpServer;
namespace Emby.Server.Implementations.Services
{
@@ -241,7 +241,7 @@ namespace Emby.Server.Implementations.Services
responses = responses,
- security = new [] { apiKeySecurity }
+ security = new[] { apiKeySecurity }
};
}
diff --git a/Emby.Server.Implementations/Services/UrlExtensions.cs b/Emby.Server.Implementations/Services/UrlExtensions.cs
index 5d4407f3b..483c63ade 100644
--- a/Emby.Server.Implementations/Services/UrlExtensions.cs
+++ b/Emby.Server.Implementations/Services/UrlExtensions.cs
@@ -1,4 +1,5 @@
using System;
+using MediaBrowser.Common.Extensions;
namespace Emby.Server.Implementations.Services
{
@@ -13,25 +14,12 @@ namespace Emby.Server.Implementations.Services
public static string GetMethodName(this Type type)
{
var typeName = type.FullName != null // can be null, e.g. generic types
- ? LeftPart(type.FullName, "[[") // Generic Fullname
- .Replace(type.Namespace + ".", string.Empty) // Trim Namespaces
- .Replace("+", ".") // Convert nested into normal type
+ ? StringExtensions.LeftPart(type.FullName, "[[", StringComparison.Ordinal).ToString() // Generic Fullname
+ .Replace(type.Namespace + ".", string.Empty, StringComparison.Ordinal) // Trim Namespaces
+ .Replace("+", ".", StringComparison.Ordinal) // Convert nested into normal type
: type.Name;
return type.IsGenericParameter ? "'" + typeName : typeName;
}
-
- private static string LeftPart(string strVal, string needle)
- {
- if (strVal == null)
- {
- return null;
- }
-
- var pos = strVal.IndexOf(needle, StringComparison.OrdinalIgnoreCase);
- return pos == -1
- ? strVal
- : strVal.Substring(0, pos);
- }
}
}
diff --git a/Emby.Server.Implementations/Session/SessionManager.cs b/Emby.Server.Implementations/Session/SessionManager.cs
index 0d5df1dad..df98a35bc 100644
--- a/Emby.Server.Implementations/Session/SessionManager.cs
+++ b/Emby.Server.Implementations/Session/SessionManager.cs
@@ -30,17 +30,17 @@ using Microsoft.Extensions.Logging;
namespace Emby.Server.Implementations.Session
{
/// <summary>
- /// Class SessionManager
+ /// Class SessionManager.
/// </summary>
public class SessionManager : ISessionManager, IDisposable
{
/// <summary>
- /// The _user data repository
+ /// The user data repository.
/// </summary>
private readonly IUserDataManager _userDataManager;
/// <summary>
- /// The _logger
+ /// The logger.
/// </summary>
private readonly ILogger _logger;
@@ -57,36 +57,19 @@ namespace Emby.Server.Implementations.Session
private readonly IDeviceManager _deviceManager;
/// <summary>
- /// The _active connections
+ /// The active connections.
/// </summary>
private readonly ConcurrentDictionary<string, SessionInfo> _activeConnections =
new ConcurrentDictionary<string, SessionInfo>(StringComparer.OrdinalIgnoreCase);
- public event EventHandler<GenericEventArgs<AuthenticationRequest>> AuthenticationFailed;
-
- public event EventHandler<GenericEventArgs<AuthenticationResult>> AuthenticationSucceeded;
-
- /// <summary>
- /// Occurs when [playback start].
- /// </summary>
- public event EventHandler<PlaybackProgressEventArgs> PlaybackStart;
- /// <summary>
- /// Occurs when [playback progress].
- /// </summary>
- public event EventHandler<PlaybackProgressEventArgs> PlaybackProgress;
- /// <summary>
- /// Occurs when [playback stopped].
- /// </summary>
- public event EventHandler<PlaybackStopEventArgs> PlaybackStopped;
+ private Timer _idleTimer;
- public event EventHandler<SessionEventArgs> SessionStarted;
- public event EventHandler<SessionEventArgs> CapabilitiesChanged;
- public event EventHandler<SessionEventArgs> SessionEnded;
- public event EventHandler<SessionEventArgs> SessionActivity;
+ private DtoOptions _itemInfoDtoOptions;
+ private bool _disposed = false;
public SessionManager(
+ ILogger<SessionManager> logger,
IUserDataManager userDataManager,
- ILoggerFactory loggerFactory,
ILibraryManager libraryManager,
IUserManager userManager,
IMusicManager musicManager,
@@ -97,8 +80,8 @@ namespace Emby.Server.Implementations.Session
IDeviceManager deviceManager,
IMediaSourceManager mediaSourceManager)
{
+ _logger = logger;
_userDataManager = userDataManager;
- _logger = loggerFactory.CreateLogger(nameof(SessionManager));
_libraryManager = libraryManager;
_userManager = userManager;
_musicManager = musicManager;
@@ -108,9 +91,49 @@ namespace Emby.Server.Implementations.Session
_authRepo = authRepo;
_deviceManager = deviceManager;
_mediaSourceManager = mediaSourceManager;
+
_deviceManager.DeviceOptionsUpdated += OnDeviceManagerDeviceOptionsUpdated;
}
+ /// <inheritdoc />
+ public event EventHandler<GenericEventArgs<AuthenticationRequest>> AuthenticationFailed;
+
+ /// <inheritdoc />
+ public event EventHandler<GenericEventArgs<AuthenticationResult>> AuthenticationSucceeded;
+
+ /// <summary>
+ /// Occurs when playback has started.
+ /// </summary>
+ public event EventHandler<PlaybackProgressEventArgs> PlaybackStart;
+
+ /// <summary>
+ /// Occurs when playback has progressed.
+ /// </summary>
+ public event EventHandler<PlaybackProgressEventArgs> PlaybackProgress;
+
+ /// <summary>
+ /// Occurs when playback has stopped.
+ /// </summary>
+ public event EventHandler<PlaybackStopEventArgs> PlaybackStopped;
+
+ /// <inheritdoc />
+ public event EventHandler<SessionEventArgs> SessionStarted;
+
+ /// <inheritdoc />
+ public event EventHandler<SessionEventArgs> CapabilitiesChanged;
+
+ /// <inheritdoc />
+ public event EventHandler<SessionEventArgs> SessionEnded;
+
+ /// <inheritdoc />
+ public event EventHandler<SessionEventArgs> SessionActivity;
+
+ /// <summary>
+ /// Gets all connections.
+ /// </summary>
+ /// <value>All connections.</value>
+ public IEnumerable<SessionInfo> Sessions => _activeConnections.Values.OrderByDescending(c => c.LastActivityDate);
+
private void OnDeviceManagerDeviceOptionsUpdated(object sender, GenericEventArgs<Tuple<string, DeviceOptions>> e)
{
foreach (var session in Sessions)
@@ -130,14 +153,17 @@ namespace Emby.Server.Implementations.Session
}
}
- private bool _disposed = false;
-
+ /// <inheritdoc />
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
+ /// <summary>
+ /// Releases unmanaged and optionally managed resources.
+ /// </summary>
+ /// <param name="disposing"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param>
protected virtual void Dispose(bool disposing)
{
if (_disposed)
@@ -147,15 +173,17 @@ namespace Emby.Server.Implementations.Session
if (disposing)
{
- // TODO: dispose stuff
+ _idleTimer?.Dispose();
}
+ _idleTimer = null;
+
_deviceManager.DeviceOptionsUpdated -= OnDeviceManagerDeviceOptionsUpdated;
_disposed = true;
}
- public void CheckDisposed()
+ private void CheckDisposed()
{
if (_disposed)
{
@@ -163,12 +191,6 @@ namespace Emby.Server.Implementations.Session
}
}
- /// <summary>
- /// Gets all connections.
- /// </summary>
- /// <value>All connections.</value>
- public IEnumerable<SessionInfo> Sessions => _activeConnections.Values.OrderByDescending(c => c.LastActivityDate);
-
private void OnSessionStarted(SessionInfo info)
{
if (!string.IsNullOrEmpty(info.DeviceId))
@@ -199,13 +221,13 @@ namespace Emby.Server.Implementations.Session
new SessionEventArgs
{
SessionInfo = info
-
},
_logger);
info.Dispose();
}
+ /// <inheritdoc />
public void UpdateDeviceName(string sessionId, string deviceName)
{
var session = GetSession(sessionId);
@@ -225,7 +247,6 @@ namespace Emby.Server.Implementations.Session
/// <param name="remoteEndPoint">The remote end point.</param>
/// <param name="user">The user.</param>
/// <returns>SessionInfo.</returns>
- /// <exception cref="ArgumentNullException">user</exception>
public SessionInfo LogSessionActivity(
string appName,
string appVersion,
@@ -263,14 +284,7 @@ namespace Emby.Server.Implementations.Session
if ((activityDate - userLastActivityDate).TotalSeconds > 60)
{
- try
- {
- _userManager.UpdateUser(user);
- }
- catch (Exception ex)
- {
- _logger.LogError("Error updating user", ex);
- }
+ _userManager.UpdateUser(user);
}
}
@@ -287,18 +301,20 @@ namespace Emby.Server.Implementations.Session
return session;
}
+ /// <inheritdoc />
public void CloseIfNeeded(SessionInfo session)
{
if (!session.SessionControllers.Any(i => i.IsSessionActive))
{
var key = GetSessionKey(session.Client, session.DeviceId);
- _activeConnections.TryRemove(key, out var removed);
+ _activeConnections.TryRemove(key, out _);
OnSessionEnded(session);
}
}
+ /// <inheritdoc />
public void ReportSessionEnded(string sessionId)
{
CheckDisposed();
@@ -308,7 +324,7 @@ namespace Emby.Server.Implementations.Session
{
var key = GetSessionKey(session.Client, session.DeviceId);
- _activeConnections.TryRemove(key, out var removed);
+ _activeConnections.TryRemove(key, out _);
OnSessionEnded(session);
}
@@ -339,7 +355,7 @@ namespace Emby.Server.Implementations.Session
var runtimeTicks = libraryItem.RunTimeTicks;
MediaSourceInfo mediaSource = null;
- if (libraryItem is IHasMediaSources hasMediaSources)
+ if (libraryItem is IHasMediaSources)
{
mediaSource = await GetMediaSource(libraryItem, info.MediaSourceId, info.LiveStreamId).ConfigureAwait(false);
@@ -391,7 +407,6 @@ namespace Emby.Server.Implementations.Session
/// Removes the now playing item id.
/// </summary>
/// <param name="session">The session.</param>
- /// <exception cref="ArgumentNullException">item</exception>
private void RemoveNowPlayingItem(SessionInfo session)
{
session.NowPlayingItem = null;
@@ -404,9 +419,7 @@ namespace Emby.Server.Implementations.Session
}
private static string GetSessionKey(string appName, string deviceId)
- {
- return appName + deviceId;
- }
+ => appName + deviceId;
/// <summary>
/// Gets the connection.
@@ -426,6 +439,7 @@ namespace Emby.Server.Implementations.Session
{
throw new ArgumentNullException(nameof(deviceId));
}
+
var key = GetSessionKey(appName, deviceId);
CheckDisposed();
@@ -497,7 +511,7 @@ namespace Emby.Server.Implementations.Session
{
var users = new List<User>();
- if (!session.UserId.Equals(Guid.Empty))
+ if (session.UserId != Guid.Empty)
{
var user = _userManager.GetUserById(session.UserId);
@@ -516,8 +530,6 @@ namespace Emby.Server.Implementations.Session
return users;
}
- private Timer _idleTimer;
-
private void StartIdleCheckTimer()
{
if (_idleTimer == null)
@@ -593,11 +605,11 @@ namespace Emby.Server.Implementations.Session
}
/// <summary>
- /// Used to report that playback has started for an item
+ /// Used to report that playback has started for an item.
/// </summary>
/// <param name="info">The info.</param>
/// <returns>Task.</returns>
- /// <exception cref="ArgumentNullException">info</exception>
+ /// <exception cref="ArgumentNullException"><c>info</c> is <c>null</c>.</exception>
public async Task OnPlaybackStart(PlaybackStartInfo info)
{
CheckDisposed();
@@ -609,7 +621,7 @@ namespace Emby.Server.Implementations.Session
var session = GetSession(info.SessionId);
- var libraryItem = info.ItemId.Equals(Guid.Empty)
+ var libraryItem = info.ItemId == Guid.Empty
? null
: GetNowPlayingItem(session, info.ItemId);
@@ -647,7 +659,6 @@ namespace Emby.Server.Implementations.Session
ClientName = session.Client,
DeviceId = session.DeviceId,
Session = session
-
},
_logger);
@@ -678,13 +689,14 @@ namespace Emby.Server.Implementations.Session
_userDataManager.SaveUserData(user, item, data, UserDataSaveReason.PlaybackStart, CancellationToken.None);
}
+ /// <inheritdoc />
public Task OnPlaybackProgress(PlaybackProgressInfo info)
{
return OnPlaybackProgress(info, false);
}
/// <summary>
- /// Used to report playback progress for an item
+ /// Used to report playback progress for an item.
/// </summary>
/// <returns>Task.</returns>
public async Task OnPlaybackProgress(PlaybackProgressInfo info, bool isAutomated)
@@ -851,7 +863,7 @@ namespace Emby.Server.Implementations.Session
{
MediaSourceInfo mediaSource = null;
- if (libraryItem is IHasMediaSources hasMediaSources)
+ if (libraryItem is IHasMediaSources)
{
mediaSource = await GetMediaSource(libraryItem, info.MediaSourceId, info.LiveStreamId).ConfigureAwait(false);
}
@@ -923,7 +935,6 @@ namespace Emby.Server.Implementations.Session
ClientName = session.Client,
DeviceId = session.DeviceId,
Session = session
-
},
_logger);
}
@@ -961,13 +972,17 @@ namespace Emby.Server.Implementations.Session
/// <param name="sessionId">The session identifier.</param>
/// <param name="throwOnMissing">if set to <c>true</c> [throw on missing].</param>
/// <returns>SessionInfo.</returns>
- /// <exception cref="ResourceNotFoundException">sessionId</exception>
+ /// <exception cref="ResourceNotFoundException">
+ /// No session with an Id equal to <c>sessionId</c> was found
+ /// and <c>throwOnMissing</c> is <c>true</c>.
+ /// </exception>
private SessionInfo GetSession(string sessionId, bool throwOnMissing = true)
{
var session = Sessions.FirstOrDefault(i => string.Equals(i.Id, sessionId, StringComparison.Ordinal));
if (session == null && throwOnMissing)
{
- throw new ResourceNotFoundException(string.Format("Session {0} not found.", sessionId));
+ throw new ResourceNotFoundException(
+ string.Format(CultureInfo.InvariantCulture, "Session {0} not found.", sessionId));
}
return session;
@@ -980,12 +995,14 @@ namespace Emby.Server.Implementations.Session
if (session == null)
{
- throw new ResourceNotFoundException(string.Format("Session {0} not found.", sessionId));
+ throw new ResourceNotFoundException(
+ string.Format(CultureInfo.InvariantCulture, "Session {0} not found.", sessionId));
}
return session;
}
+ /// <inheritdoc />
public Task SendMessageCommand(string controllingSessionId, string sessionId, MessageCommand command, CancellationToken cancellationToken)
{
CheckDisposed();
@@ -1006,6 +1023,7 @@ namespace Emby.Server.Implementations.Session
return SendGeneralCommand(controllingSessionId, sessionId, generalCommand, cancellationToken);
}
+ /// <inheritdoc />
public Task SendGeneralCommand(string controllingSessionId, string sessionId, GeneralCommand command, CancellationToken cancellationToken)
{
CheckDisposed();
@@ -1050,6 +1068,7 @@ namespace Emby.Server.Implementations.Session
return Task.WhenAll(GetTasks());
}
+ /// <inheritdoc />
public async Task SendPlayCommand(string controllingSessionId, string sessionId, PlayRequest command, CancellationToken cancellationToken)
{
CheckDisposed();
@@ -1091,7 +1110,8 @@ namespace Emby.Server.Implementations.Session
{
if (items.Any(i => i.GetPlayAccess(user) != PlayAccess.Full))
{
- throw new ArgumentException(string.Format("{0} is not allowed to play media.", user.Name));
+ throw new ArgumentException(
+ string.Format(CultureInfo.InvariantCulture, "{0} is not allowed to play media.", user.Name));
}
}
@@ -1199,6 +1219,7 @@ namespace Emby.Server.Implementations.Session
return _musicManager.GetInstantMixFromItem(item, user, new DtoOptions(false) { EnableImages = false });
}
+ /// <inheritdoc />
public Task SendBrowseCommand(string controllingSessionId, string sessionId, BrowseRequest command, CancellationToken cancellationToken)
{
var generalCommand = new GeneralCommand
@@ -1215,6 +1236,7 @@ namespace Emby.Server.Implementations.Session
return SendGeneralCommand(controllingSessionId, sessionId, generalCommand, cancellationToken);
}
+ /// <inheritdoc />
public Task SendPlaystateCommand(string controllingSessionId, string sessionId, PlaystateRequest command, CancellationToken cancellationToken)
{
CheckDisposed();
@@ -1298,12 +1320,12 @@ namespace Emby.Server.Implementations.Session
var session = GetSession(sessionId);
- if (session.UserId.Equals(userId))
+ if (session.UserId == userId)
{
throw new ArgumentException("The requested user is already the primary user of the session.");
}
- if (session.AdditionalUsers.All(i => !i.UserId.Equals(userId)))
+ if (session.AdditionalUsers.All(i => i.UserId != userId))
{
var user = _userManager.GetUserById(userId);
@@ -1378,30 +1400,26 @@ namespace Emby.Server.Implementations.Session
user = _userManager.GetUserByName(request.Username);
}
- if (user != null)
+ if (enforcePassword)
{
- // TODO: Move this to userManager?
- if (!string.IsNullOrEmpty(request.DeviceId)
- && !_deviceManager.CanAccessDevice(user, request.DeviceId))
- {
- throw new SecurityException("User is not allowed access from this device.");
- }
+ user = await _userManager.AuthenticateUser(
+ request.Username,
+ request.Password,
+ request.PasswordSha1,
+ request.RemoteEndPoint,
+ true).ConfigureAwait(false);
}
if (user == null)
{
AuthenticationFailed?.Invoke(this, new GenericEventArgs<AuthenticationRequest>(request));
- throw new SecurityException("Invalid user or password entered.");
+ throw new AuthenticationException("Invalid username or password entered.");
}
- if (enforcePassword)
+ if (!string.IsNullOrEmpty(request.DeviceId)
+ && !_deviceManager.CanAccessDevice(user, request.DeviceId))
{
- user = await _userManager.AuthenticateUser(
- request.Username,
- request.Password,
- request.PasswordSha1,
- request.RemoteEndPoint,
- true).ConfigureAwait(false);
+ throw new SecurityException("User is not allowed access from this device.");
}
var token = GetAuthorizationToken(user, request.DeviceId, request.App, request.AppVersion, request.DeviceName);
@@ -1429,19 +1447,19 @@ namespace Emby.Server.Implementations.Session
private string GetAuthorizationToken(User user, string deviceId, string app, string appVersion, string deviceName)
{
- var existing = _authRepo.Get(new AuthenticationInfoQuery
- {
- DeviceId = deviceId,
- UserId = user.Id,
- Limit = 1
-
- }).Items.FirstOrDefault();
-
- var allExistingForDevice = _authRepo.Get(new AuthenticationInfoQuery
- {
- DeviceId = deviceId
+ var existing = _authRepo.Get(
+ new AuthenticationInfoQuery
+ {
+ DeviceId = deviceId,
+ UserId = user.Id,
+ Limit = 1
+ }).Items.FirstOrDefault();
- }).Items;
+ var allExistingForDevice = _authRepo.Get(
+ new AuthenticationInfoQuery
+ {
+ DeviceId = deviceId
+ }).Items;
foreach (var auth in allExistingForDevice)
{
@@ -1460,7 +1478,7 @@ namespace Emby.Server.Implementations.Session
if (existing != null)
{
- _logger.LogInformation("Reissuing access token: " + existing.AccessToken);
+ _logger.LogInformation("Reissuing access token: {Token}", existing.AccessToken);
return existing.AccessToken;
}
@@ -1485,6 +1503,7 @@ namespace Emby.Server.Implementations.Session
return newToken.AccessToken;
}
+ /// <inheritdoc />
public void Logout(string accessToken)
{
CheckDisposed();
@@ -1494,19 +1513,20 @@ namespace Emby.Server.Implementations.Session
throw new ArgumentNullException(nameof(accessToken));
}
- var existing = _authRepo.Get(new AuthenticationInfoQuery
- {
- Limit = 1,
- AccessToken = accessToken
-
- }).Items.FirstOrDefault();
+ var existing = _authRepo.Get(
+ new AuthenticationInfoQuery
+ {
+ Limit = 1,
+ AccessToken = accessToken
+ }).Items;
- if (existing != null)
+ if (existing.Count > 0)
{
- Logout(existing);
+ Logout(existing[0]);
}
}
+ /// <inheritdoc />
public void Logout(AuthenticationInfo existing)
{
CheckDisposed();
@@ -1532,6 +1552,7 @@ namespace Emby.Server.Implementations.Session
}
}
+ /// <inheritdoc />
public void RevokeUserTokens(Guid userId, string currentAccessToken)
{
CheckDisposed();
@@ -1550,6 +1571,7 @@ namespace Emby.Server.Implementations.Session
}
}
+ /// <inheritdoc />
public void RevokeToken(string token)
{
Logout(token);
@@ -1606,10 +1628,8 @@ namespace Emby.Server.Implementations.Session
_deviceManager.SaveCapabilities(deviceId, capabilities);
}
- private DtoOptions _itemInfoDtoOptions;
-
/// <summary>
- /// Converts a BaseItem to a BaseItemInfo
+ /// Converts a BaseItem to a BaseItemInfo.
/// </summary>
private BaseItemDto GetItemInfo(BaseItem item, MediaSourceInfo mediaSource)
{
@@ -1679,11 +1699,12 @@ namespace Emby.Server.Implementations.Session
}
catch (Exception ex)
{
- _logger.LogError("Error getting {0} image info", ex, type);
+ _logger.LogError(ex, "Error getting image information for {Type}", type);
return null;
}
}
+ /// <inheritdoc />
public void ReportNowViewingItem(string sessionId, string itemId)
{
if (string.IsNullOrEmpty(itemId))
@@ -1691,23 +1712,26 @@ namespace Emby.Server.Implementations.Session
throw new ArgumentNullException(nameof(itemId));
}
- //var item = _libraryManager.GetItemById(new Guid(itemId));
+ var item = _libraryManager.GetItemById(new Guid(itemId));
- //var info = GetItemInfo(item, null, null);
+ var info = GetItemInfo(item, null);
- //ReportNowViewingItem(sessionId, info);
+ ReportNowViewingItem(sessionId, info);
}
+ /// <inheritdoc />
public void ReportNowViewingItem(string sessionId, BaseItemDto item)
{
- //var session = GetSession(sessionId);
+ var session = GetSession(sessionId);
- //session.NowViewingItem = item;
+ session.NowViewingItem = item;
}
+ /// <inheritdoc />
public void ReportTranscodingInfo(string deviceId, TranscodingInfo info)
{
- var session = Sessions.FirstOrDefault(i => string.Equals(i.DeviceId, deviceId));
+ var session = Sessions.FirstOrDefault(i =>
+ string.Equals(i.DeviceId, deviceId, StringComparison.OrdinalIgnoreCase));
if (session != null)
{
@@ -1715,15 +1739,18 @@ namespace Emby.Server.Implementations.Session
}
}
+ /// <inheritdoc />
public void ClearTranscodingInfo(string deviceId)
{
ReportTranscodingInfo(deviceId, null);
}
+ /// <inheritdoc />
public SessionInfo GetSession(string deviceId, string client, string version)
{
- return Sessions.FirstOrDefault(i => string.Equals(i.DeviceId, deviceId) &&
- string.Equals(i.Client, client));
+ return Sessions.FirstOrDefault(i =>
+ string.Equals(i.DeviceId, deviceId, StringComparison.OrdinalIgnoreCase)
+ && string.Equals(i.Client, client, StringComparison.OrdinalIgnoreCase));
}
/// <inheritdoc />
@@ -1759,23 +1786,24 @@ namespace Emby.Server.Implementations.Session
return LogSessionActivity(appName, appVersion, deviceId, deviceName, remoteEndpoint, user);
}
+ /// <inheritdoc />
public SessionInfo GetSessionByAuthenticationToken(string token, string deviceId, string remoteEndpoint)
{
- var result = _authRepo.Get(new AuthenticationInfoQuery
+ var items = _authRepo.Get(new AuthenticationInfoQuery
{
- AccessToken = token
- });
-
- var info = result.Items.FirstOrDefault();
+ AccessToken = token,
+ Limit = 1
+ }).Items;
- if (info == null)
+ if (items.Count == 0)
{
return null;
}
- return GetSessionByAuthenticationToken(info, deviceId, remoteEndpoint, null);
+ return GetSessionByAuthenticationToken(items[0], deviceId, remoteEndpoint, null);
}
+ /// <inheritdoc />
public Task SendMessageToAdminSessions<T>(string name, T data, CancellationToken cancellationToken)
{
CheckDisposed();
@@ -1785,6 +1813,7 @@ namespace Emby.Server.Implementations.Session
return SendMessageToUserSessions(adminUserIds, name, data, cancellationToken);
}
+ /// <inheritdoc />
public Task SendMessageToUserSessions<T>(List<Guid> userIds, string name, Func<T> dataFn, CancellationToken cancellationToken)
{
CheckDisposed();
@@ -1796,11 +1825,10 @@ namespace Emby.Server.Implementations.Session
return Task.CompletedTask;
}
- var data = dataFn();
-
- return SendMessageToSessions(sessions, name, data, cancellationToken);
+ return SendMessageToSessions(sessions, name, dataFn(), cancellationToken);
}
+ /// <inheritdoc />
public Task SendMessageToUserSessions<T>(List<Guid> userIds, string name, T data, CancellationToken cancellationToken)
{
CheckDisposed();
@@ -1809,6 +1837,7 @@ namespace Emby.Server.Implementations.Session
return SendMessageToSessions(sessions, name, data, cancellationToken);
}
+ /// <inheritdoc />
public Task SendMessageToUserDeviceSessions<T>(string deviceId, string name, T data, CancellationToken cancellationToken)
{
CheckDisposed();
@@ -1817,22 +1846,5 @@ namespace Emby.Server.Implementations.Session
return SendMessageToSessions(sessions, name, data, cancellationToken);
}
-
- public Task SendMessageToUserDeviceAndAdminSessions<T>(string deviceId, string name, T data, CancellationToken cancellationToken)
- {
- CheckDisposed();
-
- var sessions = Sessions
- .Where(i => string.Equals(i.DeviceId, deviceId, StringComparison.OrdinalIgnoreCase) || IsAdminSession(i));
-
- return SendMessageToSessions(sessions, name, data, cancellationToken);
- }
-
- private bool IsAdminSession(SessionInfo s)
- {
- var user = _userManager.GetUserById(s.UserId);
-
- return user != null && user.Policy.IsAdministrator;
- }
}
}
diff --git a/Emby.Server.Implementations/SocketSharp/HttpPostedFile.cs b/Emby.Server.Implementations/SocketSharp/HttpPostedFile.cs
index 95b7912fb..7479d8104 100644
--- a/Emby.Server.Implementations/SocketSharp/HttpPostedFile.cs
+++ b/Emby.Server.Implementations/SocketSharp/HttpPostedFile.cs
@@ -1,11 +1,5 @@
using System;
-using System.Collections.Generic;
-using System.Globalization;
using System.IO;
-using System.Net;
-using System.Text;
-using System.Threading.Tasks;
-using MediaBrowser.Model.Services;
public sealed class HttpPostedFile : IDisposable
{
diff --git a/Emby.Server.Implementations/SocketSharp/WebSocketSharpListener.cs b/Emby.Server.Implementations/SocketSharp/WebSocketSharpListener.cs
new file mode 100644
index 000000000..b85750c9b
--- /dev/null
+++ b/Emby.Server.Implementations/SocketSharp/WebSocketSharpListener.cs
@@ -0,0 +1,135 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Net.WebSockets;
+using System.Threading;
+using System.Threading.Tasks;
+using Emby.Server.Implementations.HttpServer;
+using Emby.Server.Implementations.Net;
+using MediaBrowser.Model.Services;
+using Microsoft.AspNetCore.Http;
+using Microsoft.AspNetCore.Http.Extensions;
+using Microsoft.Extensions.Logging;
+using Microsoft.Net.Http.Headers;
+
+namespace Emby.Server.Implementations.SocketSharp
+{
+ public class WebSocketSharpListener : IHttpListener
+ {
+ private readonly ILogger _logger;
+
+ private CancellationTokenSource _disposeCancellationTokenSource = new CancellationTokenSource();
+ private CancellationToken _disposeCancellationToken;
+
+ public WebSocketSharpListener(ILogger<WebSocketSharpListener> logger)
+ {
+ _logger = logger;
+ _disposeCancellationToken = _disposeCancellationTokenSource.Token;
+ }
+
+ public Func<Exception, IRequest, bool, bool, Task> ErrorHandler { get; set; }
+
+ public Func<IHttpRequest, string, string, string, CancellationToken, Task> RequestHandler { get; set; }
+
+ public Action<WebSocketConnectEventArgs> WebSocketConnected { get; set; }
+
+ private static void LogRequest(ILogger logger, HttpRequest request)
+ {
+ var url = request.GetDisplayUrl();
+
+ logger.LogInformation("WS {Url}. UserAgent: {UserAgent}", url, request.Headers[HeaderNames.UserAgent].ToString());
+ }
+
+ public async Task ProcessWebSocketRequest(HttpContext ctx)
+ {
+ try
+ {
+ LogRequest(_logger, ctx.Request);
+ var endpoint = ctx.Connection.RemoteIpAddress.ToString();
+ var url = ctx.Request.GetDisplayUrl();
+
+ var webSocketContext = await ctx.WebSockets.AcceptWebSocketAsync(null).ConfigureAwait(false);
+ var socket = new SharpWebSocket(webSocketContext, _logger);
+
+ WebSocketConnected(new WebSocketConnectEventArgs
+ {
+ Url = url,
+ QueryString = ctx.Request.Query,
+ WebSocket = socket,
+ Endpoint = endpoint
+ });
+
+ WebSocketReceiveResult result;
+ var message = new List<byte>();
+
+ do
+ {
+ var buffer = WebSocket.CreateServerBuffer(4096);
+ result = await webSocketContext.ReceiveAsync(buffer, _disposeCancellationToken);
+ message.AddRange(buffer.Array.Take(result.Count));
+
+ if (result.EndOfMessage)
+ {
+ socket.OnReceiveBytes(message.ToArray());
+ message.Clear();
+ }
+ } while (socket.State == WebSocketState.Open && result.MessageType != WebSocketMessageType.Close);
+
+
+ if (webSocketContext.State == WebSocketState.Open)
+ {
+ await webSocketContext.CloseAsync(
+ result.CloseStatus ?? WebSocketCloseStatus.NormalClosure,
+ result.CloseStatusDescription,
+ _disposeCancellationToken).ConfigureAwait(false);
+ }
+
+ socket.Dispose();
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, "AcceptWebSocketAsync error");
+ if (!ctx.Response.HasStarted)
+ {
+ ctx.Response.StatusCode = 500;
+ }
+ }
+ }
+
+ public Task Stop()
+ {
+ _disposeCancellationTokenSource.Cancel();
+ return Task.CompletedTask;
+ }
+
+ /// <summary>
+ /// Releases the unmanaged resources and disposes of the managed resources used.
+ /// </summary>
+ public void Dispose()
+ {
+ Dispose(true);
+ GC.SuppressFinalize(this);
+ }
+
+ private bool _disposed;
+
+ /// <summary>
+ /// Releases the unmanaged resources and disposes of the managed resources used.
+ /// </summary>
+ /// <param name="disposing">Whether or not the managed resources should be disposed.</param>
+ protected virtual void Dispose(bool disposing)
+ {
+ if (_disposed)
+ {
+ return;
+ }
+
+ if (disposing)
+ {
+ Stop().GetAwaiter().GetResult();
+ }
+
+ _disposed = true;
+ }
+ }
+}
diff --git a/Emby.Server.Implementations/SocketSharp/WebSocketSharpRequest.cs b/Emby.Server.Implementations/SocketSharp/WebSocketSharpRequest.cs
index 1781df8b5..9c638f439 100644
--- a/Emby.Server.Implementations/SocketSharp/WebSocketSharpRequest.cs
+++ b/Emby.Server.Implementations/SocketSharp/WebSocketSharpRequest.cs
@@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Net.Mime;
+using MediaBrowser.Common.Extensions;
using MediaBrowser.Common.Net;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Extensions;
@@ -216,14 +217,14 @@ namespace Emby.Server.Implementations.SocketSharp
pi = pi.Slice(1);
}
- format = LeftPart(pi, '/');
+ format = pi.LeftPart('/');
if (format.Length > FormatMaxLength)
{
return null;
}
}
- format = LeftPart(format, '.');
+ format = format.LeftPart('.');
if (format.Contains("json", StringComparison.OrdinalIgnoreCase))
{
return "application/json";
@@ -235,16 +236,5 @@ namespace Emby.Server.Implementations.SocketSharp
return null;
}
-
- public static ReadOnlySpan<char> LeftPart(ReadOnlySpan<char> strVal, char needle)
- {
- if (strVal == null)
- {
- return null;
- }
-
- var pos = strVal.IndexOf(needle);
- return pos == -1 ? strVal : strVal.Slice(0, pos);
- }
}
}
diff --git a/Emby.Server.Implementations/Sorting/AlphanumComparator.cs b/Emby.Server.Implementations/Sorting/AlphanumComparator.cs
deleted file mode 100644
index 2e00c24d7..000000000
--- a/Emby.Server.Implementations/Sorting/AlphanumComparator.cs
+++ /dev/null
@@ -1,99 +0,0 @@
-using System.Collections.Generic;
-using System.Text;
-using MediaBrowser.Controller.Sorting;
-
-namespace Emby.Server.Implementations.Sorting
-{
- public class AlphanumComparator : IComparer<string>
- {
- public static int CompareValues(string s1, string s2)
- {
- if (s1 == null || s2 == null)
- {
- return 0;
- }
-
- int thisMarker = 0, thisNumericChunk = 0;
- int thatMarker = 0, thatNumericChunk = 0;
-
- while ((thisMarker < s1.Length) || (thatMarker < s2.Length))
- {
- if (thisMarker >= s1.Length)
- {
- return -1;
- }
- else if (thatMarker >= s2.Length)
- {
- return 1;
- }
- char thisCh = s1[thisMarker];
- char thatCh = s2[thatMarker];
-
- var thisChunk = new StringBuilder();
- var thatChunk = new StringBuilder();
-
- while ((thisMarker < s1.Length) && (thisChunk.Length == 0 || SortHelper.InChunk(thisCh, thisChunk[0])))
- {
- thisChunk.Append(thisCh);
- thisMarker++;
-
- if (thisMarker < s1.Length)
- {
- thisCh = s1[thisMarker];
- }
- }
-
- while ((thatMarker < s2.Length) && (thatChunk.Length == 0 || SortHelper.InChunk(thatCh, thatChunk[0])))
- {
- thatChunk.Append(thatCh);
- thatMarker++;
-
- if (thatMarker < s2.Length)
- {
- thatCh = s2[thatMarker];
- }
- }
-
- int result = 0;
- // If both chunks contain numeric characters, sort them numerically
- if (char.IsDigit(thisChunk[0]) && char.IsDigit(thatChunk[0]))
- {
- if (!int.TryParse(thisChunk.ToString(), out thisNumericChunk))
- {
- return 0;
- }
- if (!int.TryParse(thatChunk.ToString(), out thatNumericChunk))
- {
- return 0;
- }
-
- if (thisNumericChunk < thatNumericChunk)
- {
- result = -1;
- }
-
- if (thisNumericChunk > thatNumericChunk)
- {
- result = 1;
- }
- }
- else
- {
- result = thisChunk.ToString().CompareTo(thatChunk.ToString());
- }
-
- if (result != 0)
- {
- return result;
- }
- }
-
- return 0;
- }
-
- public int Compare(string x, string y)
- {
- return CompareValues(x, y);
- }
- }
-}
diff --git a/Emby.Server.Implementations/Udp/UdpServer.cs b/Emby.Server.Implementations/Udp/UdpServer.cs
index 185a282ac..c91d137a7 100644
--- a/Emby.Server.Implementations/Udp/UdpServer.cs
+++ b/Emby.Server.Implementations/Udp/UdpServer.cs
@@ -1,112 +1,44 @@
using System;
-using System.Collections.Generic;
-using System.Linq;
using System.Net;
+using System.Net.Sockets;
using System.Text;
+using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
using MediaBrowser.Controller;
using MediaBrowser.Model.ApiClient;
-using MediaBrowser.Model.Events;
-using MediaBrowser.Model.Net;
-using MediaBrowser.Model.Serialization;
using Microsoft.Extensions.Logging;
namespace Emby.Server.Implementations.Udp
{
/// <summary>
- /// Provides a Udp Server
+ /// Provides a Udp Server.
/// </summary>
- public class UdpServer : IDisposable
+ public sealed class UdpServer : IDisposable
{
/// <summary>
/// The _logger
/// </summary>
private readonly ILogger _logger;
+ private readonly IServerApplicationHost _appHost;
- private bool _isDisposed;
-
- private readonly List<Tuple<string, bool, Func<string, IPEndPoint, Encoding, CancellationToken, Task>>> _responders = new List<Tuple<string, bool, Func<string, IPEndPoint, Encoding, CancellationToken, Task>>>();
+ private Socket _udpSocket;
+ private IPEndPoint _endpoint;
+ private readonly byte[] _receiveBuffer = new byte[8192];
- private readonly IServerApplicationHost _appHost;
- private readonly IJsonSerializer _json;
+ private bool _disposed = false;
/// <summary>
/// Initializes a new instance of the <see cref="UdpServer" /> class.
/// </summary>
- public UdpServer(ILogger logger, IServerApplicationHost appHost, IJsonSerializer json, ISocketFactory socketFactory)
+ public UdpServer(ILogger logger, IServerApplicationHost appHost)
{
_logger = logger;
_appHost = appHost;
- _json = json;
- _socketFactory = socketFactory;
-
- AddMessageResponder("who is JellyfinServer?", true, RespondToV2Message);
- }
-
- private void AddMessageResponder(string message, bool isSubstring, Func<string, IPEndPoint, Encoding, CancellationToken, Task> responder)
- {
- _responders.Add(new Tuple<string, bool, Func<string, IPEndPoint, Encoding, CancellationToken, Task>>(message, isSubstring, responder));
- }
-
- /// <summary>
- /// Raises the <see cref="E:MessageReceived" /> event.
- /// </summary>
- private async void OnMessageReceived(GenericEventArgs<SocketReceiveResult> e)
- {
- var message = e.Argument;
-
- var encoding = Encoding.UTF8;
- var responder = GetResponder(message.Buffer, message.ReceivedBytes, encoding);
-
- if (responder == null)
- {
- encoding = Encoding.Unicode;
- responder = GetResponder(message.Buffer, message.ReceivedBytes, encoding);
- }
-
- if (responder != null)
- {
- var cancellationToken = CancellationToken.None;
-
- try
- {
- await responder.Item2.Item3(responder.Item1, message.RemoteEndPoint, encoding, cancellationToken).ConfigureAwait(false);
- }
- catch (OperationCanceledException)
- {
-
- }
- catch (Exception ex)
- {
- _logger.LogError(ex, "Error in OnMessageReceived");
- }
- }
}
- private Tuple<string, Tuple<string, bool, Func<string, IPEndPoint, Encoding, CancellationToken, Task>>> GetResponder(byte[] buffer, int bytesReceived, Encoding encoding)
+ private async Task RespondToV2Message(string messageText, EndPoint endpoint, CancellationToken cancellationToken)
{
- var text = encoding.GetString(buffer, 0, bytesReceived);
- var responder = _responders.FirstOrDefault(i =>
- {
- if (i.Item2)
- {
- return text.IndexOf(i.Item1, StringComparison.OrdinalIgnoreCase) != -1;
- }
- return string.Equals(i.Item1, text, StringComparison.OrdinalIgnoreCase);
- });
-
- if (responder == null)
- {
- return null;
- }
- return new Tuple<string, Tuple<string, bool, Func<string, IPEndPoint, Encoding, CancellationToken, Task>>>(text, responder);
- }
-
- private async Task RespondToV2Message(string messageText, IPEndPoint endpoint, Encoding encoding, CancellationToken cancellationToken)
- {
- var parts = messageText.Split('|');
-
var localUrl = await _appHost.GetLocalApiUrl(cancellationToken).ConfigureAwait(false);
if (!string.IsNullOrEmpty(localUrl))
@@ -118,8 +50,16 @@ namespace Emby.Server.Implementations.Udp
Name = _appHost.FriendlyName
};
- await SendAsync(encoding.GetBytes(_json.SerializeToString(response)), endpoint, cancellationToken).ConfigureAwait(false);
+ try
+ {
+ await _udpSocket.SendToAsync(JsonSerializer.SerializeToUtf8Bytes(response), SocketFlags.None, endpoint).ConfigureAwait(false);
+ }
+ catch (SocketException ex)
+ {
+ _logger.LogError(ex, "Error sending response message");
+ }
+ var parts = messageText.Split('|');
if (parts.Length > 1)
{
_appHost.EnableLoopback(parts[1]);
@@ -132,161 +72,59 @@ namespace Emby.Server.Implementations.Udp
}
/// <summary>
- /// The _udp client
- /// </summary>
- private ISocket _udpClient;
- private readonly ISocketFactory _socketFactory;
-
- /// <summary>
/// Starts the specified port.
/// </summary>
/// <param name="port">The port.</param>
- public void Start(int port)
+ /// <param name="cancellationToken"></param>
+ public void Start(int port, CancellationToken cancellationToken)
{
- _udpClient = _socketFactory.CreateUdpSocket(port);
+ _endpoint = new IPEndPoint(IPAddress.Any, port);
- Task.Run(() => BeginReceive());
- }
-
- private readonly byte[] _receiveBuffer = new byte[8192];
-
- private void BeginReceive()
- {
- if (_isDisposed)
- {
- return;
- }
+ _udpSocket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
+ _udpSocket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true);
+ _udpSocket.Bind(_endpoint);
- try
- {
- var result = _udpClient.BeginReceive(_receiveBuffer, 0, _receiveBuffer.Length, OnReceiveResult);
-
- if (result.CompletedSynchronously)
- {
- OnReceiveResult(result);
- }
- }
- catch (ObjectDisposedException)
- {
- //TODO Investigate and properly fix.
- }
- catch (Exception ex)
- {
- _logger.LogError(ex, "Error receiving udp message");
- }
+ _ = Task.Run(async () => await BeginReceiveAsync(cancellationToken).ConfigureAwait(false), cancellationToken).ConfigureAwait(false);
}
- private void OnReceiveResult(IAsyncResult result)
+ private async Task BeginReceiveAsync(CancellationToken cancellationToken)
{
- if (_isDisposed)
- {
- return;
- }
-
- try
- {
- var socketResult = _udpClient.EndReceive(result);
-
- OnMessageReceived(socketResult);
- }
- catch (ObjectDisposedException)
- {
- //TODO Investigate and properly fix.
- }
- catch (Exception ex)
+ while (!cancellationToken.IsCancellationRequested)
{
- _logger.LogError(ex, "Error receiving udp message");
- }
-
- BeginReceive();
- }
-
- /// <summary>
- /// Called when [message received].
- /// </summary>
- /// <param name="message">The message.</param>
- private void OnMessageReceived(SocketReceiveResult message)
- {
- if (_isDisposed)
- {
- return;
- }
-
- if (message.RemoteEndPoint.Port == 0)
- {
- return;
- }
-
- try
- {
- OnMessageReceived(new GenericEventArgs<SocketReceiveResult>
+ try
{
- Argument = message
- });
- }
- catch (Exception ex)
- {
- _logger.LogError(ex, "Error handling UDP message");
- }
- }
-
- /// <summary>
- /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
- /// </summary>
- public void Dispose()
- {
- Dispose(true);
- }
+ var result = await _udpSocket.ReceiveFromAsync(_receiveBuffer, SocketFlags.None, _endpoint).ConfigureAwait(false);
- /// <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)
- {
- _isDisposed = true;
+ cancellationToken.ThrowIfCancellationRequested();
- if (_udpClient != null)
+ var text = Encoding.UTF8.GetString(_receiveBuffer, 0, result.ReceivedBytes);
+ if (text.Contains("who is JellyfinServer?", StringComparison.OrdinalIgnoreCase))
+ {
+ await RespondToV2Message(text, result.RemoteEndPoint, cancellationToken).ConfigureAwait(false);
+ }
+ }
+ catch (SocketException ex)
{
- _udpClient.Dispose();
+ _logger.LogError(ex, "Failed to receive data drom socket");
+ }
+ catch (OperationCanceledException)
+ {
+ // Don't throw
}
}
}
- public async Task SendAsync(byte[] bytes, IPEndPoint remoteEndPoint, CancellationToken cancellationToken)
+ /// <inheritdoc />
+ public void Dispose()
{
- if (_isDisposed)
+ if (_disposed)
{
- throw new ObjectDisposedException(GetType().Name);
- }
-
- if (bytes == null)
- {
- throw new ArgumentNullException(nameof(bytes));
- }
-
- if (remoteEndPoint == null)
- {
- throw new ArgumentNullException(nameof(remoteEndPoint));
+ return;
}
- try
- {
- await _udpClient.SendToAsync(bytes, 0, bytes.Length, remoteEndPoint, cancellationToken).ConfigureAwait(false);
+ _udpSocket?.Dispose();
- _logger.LogInformation("Udp message sent to {remoteEndPoint}", remoteEndPoint);
- }
- catch (OperationCanceledException)
- {
-
- }
- catch (Exception ex)
- {
- _logger.LogError(ex, "Error sending message to {remoteEndPoint}", remoteEndPoint);
- }
+ GC.SuppressFinalize(this);
}
}
-
}
diff --git a/Emby.Server.Implementations/Updates/InstallationManager.cs b/Emby.Server.Implementations/Updates/InstallationManager.cs
index c897036eb..0b2309889 100644
--- a/Emby.Server.Implementations/Updates/InstallationManager.cs
+++ b/Emby.Server.Implementations/Updates/InstallationManager.cs
@@ -3,8 +3,10 @@ using System.Collections.Concurrent;
using System.Collections.Generic;
using System.IO;
using System.Linq;
+using System.Net;
using System.Net.Http;
using System.Runtime.CompilerServices;
+using System.Runtime.Serialization;
using System.Security.Cryptography;
using System.Threading;
using System.Threading.Tasks;
@@ -18,17 +20,23 @@ using MediaBrowser.Model.Events;
using MediaBrowser.Model.IO;
using MediaBrowser.Model.Serialization;
using MediaBrowser.Model.Updates;
+using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
namespace Emby.Server.Implementations.Updates
{
/// <summary>
- /// Manages all install, uninstall and update operations (both plugins and system).
+ /// Manages all install, uninstall, and update operations for the system and individual plugins.
/// </summary>
public class InstallationManager : IInstallationManager
{
/// <summary>
- /// The _logger.
+ /// The key for a setting that specifies a URL for the plugin repository JSON manifest.
+ /// </summary>
+ public const string PluginManifestUrlKey = "InstallationManager:PluginManifestUrl";
+
+ /// <summary>
+ /// The logger.
/// </summary>
private readonly ILogger _logger;
private readonly IApplicationPaths _appPaths;
@@ -44,6 +52,7 @@ namespace Emby.Server.Implementations.Updates
private readonly IApplicationHost _applicationHost;
private readonly IZipClient _zipClient;
+ private readonly IConfiguration _appConfig;
private readonly object _currentInstallationsLock = new object();
@@ -65,7 +74,8 @@ namespace Emby.Server.Implementations.Updates
IJsonSerializer jsonSerializer,
IServerConfigurationManager config,
IFileSystem fileSystem,
- IZipClient zipClient)
+ IZipClient zipClient,
+ IConfiguration appConfig)
{
if (logger == null)
{
@@ -83,6 +93,7 @@ namespace Emby.Server.Implementations.Updates
_config = config;
_fileSystem = fileSystem;
_zipClient = zipClient;
+ _appConfig = appConfig;
}
/// <inheritdoc />
@@ -101,10 +112,10 @@ namespace Emby.Server.Implementations.Updates
public event EventHandler<GenericEventArgs<IPlugin>> PluginUninstalled;
/// <inheritdoc />
- public event EventHandler<GenericEventArgs<(IPlugin, PackageVersionInfo)>> PluginUpdated;
+ public event EventHandler<GenericEventArgs<(IPlugin, VersionInfo)>> PluginUpdated;
/// <inheritdoc />
- public event EventHandler<GenericEventArgs<PackageVersionInfo>> PluginInstalled;
+ public event EventHandler<GenericEventArgs<VersionInfo>> PluginInstalled;
/// <inheritdoc />
public IEnumerable<InstallationInfo> CompletedInstallations => _completedInstallationsInternal;
@@ -112,19 +123,43 @@ namespace Emby.Server.Implementations.Updates
/// <inheritdoc />
public async Task<IReadOnlyList<PackageInfo>> GetAvailablePackages(CancellationToken cancellationToken = default)
{
- using (var response = await _httpClient.SendAsync(
- new HttpRequestOptions
+ var manifestUrl = _appConfig.GetValue<string>(PluginManifestUrlKey);
+
+ try
+ {
+ using (var response = await _httpClient.SendAsync(
+ new HttpRequestOptions
+ {
+ Url = manifestUrl,
+ CancellationToken = cancellationToken,
+ CacheMode = CacheMode.Unconditional,
+ CacheLength = TimeSpan.FromMinutes(3)
+ },
+ HttpMethod.Get).ConfigureAwait(false))
+ using (Stream stream = response.Content)
{
- Url = "https://repo.jellyfin.org/releases/plugin/manifest.json",
- CancellationToken = cancellationToken,
- CacheMode = CacheMode.Unconditional,
- CacheLength = TimeSpan.FromMinutes(3)
- },
- HttpMethod.Get).ConfigureAwait(false))
- using (Stream stream = response.Content)
+ try
+ {
+ return await _jsonSerializer.DeserializeFromStreamAsync<IReadOnlyList<PackageInfo>>(stream).ConfigureAwait(false);
+ }
+ catch (SerializationException ex)
+ {
+ const string LogTemplate =
+ "Failed to deserialize the plugin manifest retrieved from {PluginManifestUrl}. If you " +
+ "have specified a custom plugin repository manifest URL with --plugin-manifest-url or " +
+ PluginManifestUrlKey + ", please ensure that it is correct.";
+ _logger.LogError(ex, LogTemplate, manifestUrl);
+ throw;
+ }
+ }
+ }
+ catch (UriFormatException ex)
{
- return await _jsonSerializer.DeserializeFromStreamAsync<IReadOnlyList<PackageInfo>>(
- stream).ConfigureAwait(false);
+ const string LogTemplate =
+ "The URL configured for the plugin repository manifest URL is not valid: {PluginManifestUrl}. " +
+ "Please check the URL configured by --plugin-manifest-url or " + PluginManifestUrlKey;
+ _logger.LogError(ex, LogTemplate, manifestUrl);
+ throw;
}
}
@@ -148,60 +183,56 @@ namespace Emby.Server.Implementations.Updates
}
/// <inheritdoc />
- public IEnumerable<PackageVersionInfo> GetCompatibleVersions(
- IEnumerable<PackageVersionInfo> availableVersions,
- Version minVersion = null,
- PackageVersionClass classification = PackageVersionClass.Release)
+ public IEnumerable<VersionInfo> GetCompatibleVersions(
+ IEnumerable<VersionInfo> availableVersions,
+ Version minVersion = null)
{
var appVer = _applicationHost.ApplicationVersion;
availableVersions = availableVersions
- .Where(x => x.classification == classification
- && Version.Parse(x.requiredVersionStr) <= appVer);
+ .Where(x => Version.Parse(x.targetAbi) <= appVer);
if (minVersion != null)
{
- availableVersions = availableVersions.Where(x => x.Version >= minVersion);
+ availableVersions = availableVersions.Where(x => x.version >= minVersion);
}
- return availableVersions.OrderByDescending(x => x.Version);
+ return availableVersions.OrderByDescending(x => x.version);
}
/// <inheritdoc />
- public IEnumerable<PackageVersionInfo> GetCompatibleVersions(
+ public IEnumerable<VersionInfo> GetCompatibleVersions(
IEnumerable<PackageInfo> availablePackages,
string name = null,
Guid guid = default,
- Version minVersion = null,
- PackageVersionClass classification = PackageVersionClass.Release)
+ Version minVersion = null)
{
var package = FilterPackages(availablePackages, name, guid).FirstOrDefault();
- // Package not found.
+ // Package not found in repository
if (package == null)
{
- return Enumerable.Empty<PackageVersionInfo>();
+ return Enumerable.Empty<VersionInfo>();
}
return GetCompatibleVersions(
package.versions,
- minVersion,
- classification);
+ minVersion);
}
/// <inheritdoc />
- public async IAsyncEnumerable<PackageVersionInfo> GetAvailablePluginUpdates([EnumeratorCancellation] CancellationToken cancellationToken = default)
+ public async Task<IEnumerable<VersionInfo>> GetAvailablePluginUpdates(CancellationToken cancellationToken = default)
{
var catalog = await GetAvailablePackages(cancellationToken).ConfigureAwait(false);
+ return GetAvailablePluginUpdates(catalog);
+ }
- var systemUpdateLevel = _applicationHost.SystemUpdateLevel;
-
- // Figure out what needs to be installed
+ private IEnumerable<VersionInfo> GetAvailablePluginUpdates(IReadOnlyList<PackageInfo> pluginCatalog)
+ {
foreach (var plugin in _applicationHost.Plugins)
{
- var compatibleversions = GetCompatibleVersions(catalog, plugin.Name, plugin.Id, plugin.Version, systemUpdateLevel);
- var version = compatibleversions.FirstOrDefault(y => y.Version > plugin.Version);
- if (version != null
- && !CompletedInstallations.Any(x => string.Equals(x.AssemblyGuid, version.guid, StringComparison.OrdinalIgnoreCase)))
+ var compatibleversions = GetCompatibleVersions(pluginCatalog, plugin.Name, plugin.Id, plugin.Version);
+ var version = compatibleversions.FirstOrDefault(y => y.version > plugin.Version);
+ if (version != null && !CompletedInstallations.Any(x => string.Equals(x.Guid, version.guid, StringComparison.OrdinalIgnoreCase)))
{
yield return version;
}
@@ -209,7 +240,7 @@ namespace Emby.Server.Implementations.Updates
}
/// <inheritdoc />
- public async Task InstallPackage(PackageVersionInfo package, CancellationToken cancellationToken)
+ public async Task InstallPackage(VersionInfo package, CancellationToken cancellationToken)
{
if (package == null)
{
@@ -218,11 +249,9 @@ namespace Emby.Server.Implementations.Updates
var installationInfo = new InstallationInfo
{
- Id = Guid.NewGuid(),
+ Guid = package.guid,
Name = package.name,
- AssemblyGuid = package.guid,
- UpdateClass = package.classification,
- Version = package.versionStr
+ Version = package.version.ToString()
};
var innerCancellationTokenSource = new CancellationTokenSource();
@@ -240,7 +269,7 @@ namespace Emby.Server.Implementations.Updates
var installationEventArgs = new InstallationEventArgs
{
InstallationInfo = installationInfo,
- PackageVersionInfo = package
+ VersionInfo = package
};
PackageInstalling?.Invoke(this, installationEventArgs);
@@ -265,7 +294,7 @@ namespace Emby.Server.Implementations.Updates
_currentInstallations.Remove(tuple);
}
- _logger.LogInformation("Package installation cancelled: {0} {1}", package.name, package.versionStr);
+ _logger.LogInformation("Package installation cancelled: {0} {1}", package.name, package.version);
PackageInstallationCancelled?.Invoke(this, installationEventArgs);
@@ -301,7 +330,7 @@ namespace Emby.Server.Implementations.Updates
/// <param name="package">The package.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns><see cref="Task" />.</returns>
- private async Task InstallPackageInternal(PackageVersionInfo package, CancellationToken cancellationToken)
+ private async Task InstallPackageInternal(VersionInfo package, CancellationToken cancellationToken)
{
// Set last update time if we were installed before
IPlugin plugin = _applicationHost.Plugins.FirstOrDefault(p => string.Equals(p.Id.ToString(), package.guid, StringComparison.OrdinalIgnoreCase))
@@ -313,26 +342,26 @@ namespace Emby.Server.Implementations.Updates
// Do plugin-specific processing
if (plugin == null)
{
- _logger.LogInformation("New plugin installed: {0} {1} {2}", package.name, package.versionStr ?? string.Empty, package.classification);
+ _logger.LogInformation("New plugin installed: {0} {1} {2}", package.name, package.version);
- PluginInstalled?.Invoke(this, new GenericEventArgs<PackageVersionInfo>(package));
+ PluginInstalled?.Invoke(this, new GenericEventArgs<VersionInfo>(package));
}
else
{
- _logger.LogInformation("Plugin updated: {0} {1} {2}", package.name, package.versionStr ?? string.Empty, package.classification);
+ _logger.LogInformation("Plugin updated: {0} {1} {2}", package.name, package.version);
- PluginUpdated?.Invoke(this, new GenericEventArgs<(IPlugin, PackageVersionInfo)>((plugin, package)));
+ PluginUpdated?.Invoke(this, new GenericEventArgs<(IPlugin, VersionInfo)>((plugin, package)));
}
_applicationHost.NotifyPendingRestart();
}
- private async Task PerformPackageInstallation(PackageVersionInfo package, CancellationToken cancellationToken)
+ private async Task PerformPackageInstallation(VersionInfo package, CancellationToken cancellationToken)
{
- var extension = Path.GetExtension(package.targetFilename);
+ var extension = Path.GetExtension(package.filename);
if (!string.Equals(extension, ".zip", StringComparison.OrdinalIgnoreCase))
{
- _logger.LogError("Only zip packages are supported. {Filename} is not a zip archive.", package.targetFilename);
+ _logger.LogError("Only zip packages are supported. {Filename} is not a zip archive.", package.filename);
return;
}
@@ -379,7 +408,7 @@ namespace Emby.Server.Implementations.Updates
}
/// <summary>
- /// Uninstalls a plugin
+ /// Uninstalls a plugin.
/// </summary>
/// <param name="plugin">The plugin.</param>
public void UninstallPlugin(IPlugin plugin)
@@ -437,7 +466,7 @@ namespace Emby.Server.Implementations.Updates
{
lock (_currentInstallationsLock)
{
- var install = _currentInstallations.Find(x => x.info.Id == id);
+ var install = _currentInstallations.Find(x => x.info.Guid == id.ToString());
if (install == default((InstallationInfo, CancellationTokenSource)))
{
return false;
diff --git a/Emby.Server.Implementations/WebSockets/WebSocketManager.cs b/Emby.Server.Implementations/WebSockets/WebSocketManager.cs
new file mode 100644
index 000000000..31a7468fb
--- /dev/null
+++ b/Emby.Server.Implementations/WebSockets/WebSocketManager.cs
@@ -0,0 +1,102 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Net.WebSockets;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+using MediaBrowser.Model.Net;
+using MediaBrowser.Model.Serialization;
+using Microsoft.Extensions.Logging;
+using UtfUnknown;
+
+namespace Emby.Server.Implementations.WebSockets
+{
+ public class WebSocketManager
+ {
+ private readonly IWebSocketHandler[] _webSocketHandlers;
+ private readonly IJsonSerializer _jsonSerializer;
+ private readonly ILogger<WebSocketManager> _logger;
+ private const int BufferSize = 4096;
+
+ public WebSocketManager(IWebSocketHandler[] webSocketHandlers, IJsonSerializer jsonSerializer, ILogger<WebSocketManager> logger)
+ {
+ _webSocketHandlers = webSocketHandlers;
+ _jsonSerializer = jsonSerializer;
+ _logger = logger;
+ }
+
+ public async Task OnWebSocketConnected(WebSocket webSocket)
+ {
+ var taskCompletionSource = new TaskCompletionSource<bool>();
+ var cancellationToken = new CancellationTokenSource().Token;
+ WebSocketReceiveResult result;
+ var message = new List<byte>();
+
+ // Keep listening for incoming messages, otherwise the socket closes automatically
+ do
+ {
+ var buffer = WebSocket.CreateServerBuffer(BufferSize);
+ result = await webSocket.ReceiveAsync(buffer, cancellationToken).ConfigureAwait(false);
+ message.AddRange(buffer.Array.Take(result.Count));
+
+ if (result.EndOfMessage)
+ {
+ await ProcessMessage(message.ToArray(), taskCompletionSource).ConfigureAwait(false);
+ message.Clear();
+ }
+ } while (!taskCompletionSource.Task.IsCompleted &&
+ webSocket.State == WebSocketState.Open &&
+ result.MessageType != WebSocketMessageType.Close);
+
+ if (webSocket.State == WebSocketState.Open)
+ {
+ await webSocket.CloseAsync(
+ result.CloseStatus ?? WebSocketCloseStatus.NormalClosure,
+ result.CloseStatusDescription,
+ cancellationToken).ConfigureAwait(false);
+ }
+ }
+
+ private async Task ProcessMessage(byte[] messageBytes, TaskCompletionSource<bool> taskCompletionSource)
+ {
+ var charset = CharsetDetector.DetectFromBytes(messageBytes).Detected?.EncodingName;
+ var message = string.Equals(charset, "utf-8", StringComparison.OrdinalIgnoreCase)
+ ? Encoding.UTF8.GetString(messageBytes, 0, messageBytes.Length)
+ : Encoding.ASCII.GetString(messageBytes, 0, messageBytes.Length);
+
+ // All messages are expected to be valid JSON objects
+ if (!message.StartsWith("{", StringComparison.OrdinalIgnoreCase))
+ {
+ _logger.LogDebug("Received web socket message that is not a json structure: {Message}", message);
+ return;
+ }
+
+ try
+ {
+ var info = _jsonSerializer.DeserializeFromString<WebSocketMessage<object>>(message);
+
+ _logger.LogDebug("Websocket message received: {0}", info.MessageType);
+
+ var tasks = _webSocketHandlers.Select(handler => Task.Run(() =>
+ {
+ try
+ {
+ handler.ProcessMessage(info, taskCompletionSource).ConfigureAwait(false);
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, "{HandlerType} failed processing WebSocket message {MessageType}",
+ handler.GetType().Name, info.MessageType ?? string.Empty);
+ }
+ }));
+
+ await Task.WhenAll(tasks);
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, "Error processing web socket message");
+ }
+ }
+ }
+}
diff --git a/Jellyfin.Api/BaseJellyfinApiController.cs b/Jellyfin.Api/BaseJellyfinApiController.cs
index 1f4508e6c..a34f9eb62 100644
--- a/Jellyfin.Api/BaseJellyfinApiController.cs
+++ b/Jellyfin.Api/BaseJellyfinApiController.cs
@@ -1,3 +1,4 @@
+using System.Net.Mime;
using Microsoft.AspNetCore.Mvc;
namespace Jellyfin.Api
@@ -7,6 +8,7 @@ namespace Jellyfin.Api
/// </summary>
[ApiController]
[Route("[controller]")]
+ [Produces(MediaTypeNames.Application.Json)]
public class BaseJellyfinApiController : ControllerBase
{
}
diff --git a/Jellyfin.Api/Jellyfin.Api.csproj b/Jellyfin.Api/Jellyfin.Api.csproj
index 38cdb0998..a582a209c 100644
--- a/Jellyfin.Api/Jellyfin.Api.csproj
+++ b/Jellyfin.Api/Jellyfin.Api.csproj
@@ -1,5 +1,10 @@
<Project Sdk="Microsoft.NET.Sdk">
+ <!-- ProjectGuid is only included as a requirement for SonarQube analysis -->
+ <PropertyGroup>
+ <ProjectGuid>{DFBEFB4C-DA19-4143-98B7-27320C7F7163}</ProjectGuid>
+ </PropertyGroup>
+
<PropertyGroup>
<TargetFramework>netstandard2.1</TargetFramework>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
@@ -8,9 +13,9 @@
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Authentication" Version="2.2.0" />
- <PackageReference Include="Microsoft.AspNetCore.Authorization" Version="3.0.0" />
+ <PackageReference Include="Microsoft.AspNetCore.Authorization" Version="3.1.3" />
<PackageReference Include="Microsoft.AspNetCore.Mvc" Version="2.2.0" />
- <PackageReference Include="Swashbuckle.AspNetCore" Version="5.0.0-rc4" />
+ <PackageReference Include="Swashbuckle.AspNetCore" Version="5.0.0" />
</ItemGroup>
<ItemGroup>
diff --git a/Jellyfin.Data/DbContexts/Jellyfin.cs b/Jellyfin.Data/DbContexts/Jellyfin.cs
new file mode 100644
index 000000000..fd488ce7d
--- /dev/null
+++ b/Jellyfin.Data/DbContexts/Jellyfin.cs
@@ -0,0 +1,1140 @@
+//------------------------------------------------------------------------------
+// <auto-generated>
+// This code was generated from a template.
+//
+// Manual changes to this file may cause unexpected behavior in your application.
+// Manual changes to this file will be overwritten if the code is regenerated.
+//
+// Produced by Entity Framework Visual Editor
+// https://github.com/msawczyn/EFDesigner
+// </auto-generated>
+//------------------------------------------------------------------------------
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.ComponentModel.DataAnnotations.Schema;
+using Microsoft.EntityFrameworkCore;
+
+namespace Jellyfin.Data.DbContexts
+{
+ /// <inheritdoc/>
+ public partial class Jellyfin : DbContext
+ {
+ #region DbSets
+ public virtual Microsoft.EntityFrameworkCore.DbSet<global::Jellyfin.Data.Entities.Artwork> Artwork { get; set; }
+ public virtual Microsoft.EntityFrameworkCore.DbSet<global::Jellyfin.Data.Entities.Book> Books { get; set; }
+ public virtual Microsoft.EntityFrameworkCore.DbSet<global::Jellyfin.Data.Entities.BookMetadata> BookMetadata { get; set; }
+ public virtual Microsoft.EntityFrameworkCore.DbSet<global::Jellyfin.Data.Entities.Chapter> Chapters { get; set; }
+ public virtual Microsoft.EntityFrameworkCore.DbSet<global::Jellyfin.Data.Entities.Collection> Collections { get; set; }
+ public virtual Microsoft.EntityFrameworkCore.DbSet<global::Jellyfin.Data.Entities.CollectionItem> CollectionItems { get; set; }
+ public virtual Microsoft.EntityFrameworkCore.DbSet<global::Jellyfin.Data.Entities.Company> Companies { get; set; }
+ public virtual Microsoft.EntityFrameworkCore.DbSet<global::Jellyfin.Data.Entities.CompanyMetadata> CompanyMetadata { get; set; }
+ public virtual Microsoft.EntityFrameworkCore.DbSet<global::Jellyfin.Data.Entities.CustomItem> CustomItems { get; set; }
+ public virtual Microsoft.EntityFrameworkCore.DbSet<global::Jellyfin.Data.Entities.CustomItemMetadata> CustomItemMetadata { get; set; }
+ public virtual Microsoft.EntityFrameworkCore.DbSet<global::Jellyfin.Data.Entities.Episode> Episodes { get; set; }
+ public virtual Microsoft.EntityFrameworkCore.DbSet<global::Jellyfin.Data.Entities.EpisodeMetadata> EpisodeMetadata { get; set; }
+ public virtual Microsoft.EntityFrameworkCore.DbSet<global::Jellyfin.Data.Entities.Genre> Genres { get; set; }
+ public virtual Microsoft.EntityFrameworkCore.DbSet<global::Jellyfin.Data.Entities.Group> Groups { get; set; }
+ public virtual Microsoft.EntityFrameworkCore.DbSet<global::Jellyfin.Data.Entities.Library> Libraries { get; set; }
+ public virtual Microsoft.EntityFrameworkCore.DbSet<global::Jellyfin.Data.Entities.LibraryItem> LibraryItems { get; set; }
+ public virtual Microsoft.EntityFrameworkCore.DbSet<global::Jellyfin.Data.Entities.LibraryRoot> LibraryRoot { get; set; }
+ public virtual Microsoft.EntityFrameworkCore.DbSet<global::Jellyfin.Data.Entities.MediaFile> MediaFiles { get; set; }
+ public virtual Microsoft.EntityFrameworkCore.DbSet<global::Jellyfin.Data.Entities.MediaFileStream> MediaFileStream { get; set; }
+ public virtual Microsoft.EntityFrameworkCore.DbSet<global::Jellyfin.Data.Entities.Metadata> Metadata { get; set; }
+ public virtual Microsoft.EntityFrameworkCore.DbSet<global::Jellyfin.Data.Entities.MetadataProvider> MetadataProviders { get; set; }
+ public virtual Microsoft.EntityFrameworkCore.DbSet<global::Jellyfin.Data.Entities.MetadataProviderId> MetadataProviderIds { get; set; }
+ public virtual Microsoft.EntityFrameworkCore.DbSet<global::Jellyfin.Data.Entities.Movie> Movies { get; set; }
+ public virtual Microsoft.EntityFrameworkCore.DbSet<global::Jellyfin.Data.Entities.MovieMetadata> MovieMetadata { get; set; }
+ public virtual Microsoft.EntityFrameworkCore.DbSet<global::Jellyfin.Data.Entities.MusicAlbum> MusicAlbums { get; set; }
+ public virtual Microsoft.EntityFrameworkCore.DbSet<global::Jellyfin.Data.Entities.MusicAlbumMetadata> MusicAlbumMetadata { get; set; }
+ public virtual Microsoft.EntityFrameworkCore.DbSet<global::Jellyfin.Data.Entities.Permission> Permissions { get; set; }
+ public virtual Microsoft.EntityFrameworkCore.DbSet<global::Jellyfin.Data.Entities.Person> People { get; set; }
+ public virtual Microsoft.EntityFrameworkCore.DbSet<global::Jellyfin.Data.Entities.PersonRole> PersonRoles { get; set; }
+ public virtual Microsoft.EntityFrameworkCore.DbSet<global::Jellyfin.Data.Entities.Photo> Photo { get; set; }
+ public virtual Microsoft.EntityFrameworkCore.DbSet<global::Jellyfin.Data.Entities.PhotoMetadata> PhotoMetadata { get; set; }
+ public virtual Microsoft.EntityFrameworkCore.DbSet<global::Jellyfin.Data.Entities.Preference> Preferences { get; set; }
+ public virtual Microsoft.EntityFrameworkCore.DbSet<global::Jellyfin.Data.Entities.ProviderMapping> ProviderMappings { get; set; }
+ public virtual Microsoft.EntityFrameworkCore.DbSet<global::Jellyfin.Data.Entities.Rating> Ratings { get; set; }
+
+ /// <summary>
+ /// Repository for global::Jellyfin.Data.Entities.RatingSource - This is the entity to
+ /// store review ratings, not age ratings
+ /// </summary>
+ public virtual Microsoft.EntityFrameworkCore.DbSet<global::Jellyfin.Data.Entities.RatingSource> RatingSources { get; set; }
+ public virtual Microsoft.EntityFrameworkCore.DbSet<global::Jellyfin.Data.Entities.Release> Releases { get; set; }
+ public virtual Microsoft.EntityFrameworkCore.DbSet<global::Jellyfin.Data.Entities.Season> Seasons { get; set; }
+ public virtual Microsoft.EntityFrameworkCore.DbSet<global::Jellyfin.Data.Entities.SeasonMetadata> SeasonMetadata { get; set; }
+ public virtual Microsoft.EntityFrameworkCore.DbSet<global::Jellyfin.Data.Entities.Series> Series { get; set; }
+ public virtual Microsoft.EntityFrameworkCore.DbSet<global::Jellyfin.Data.Entities.SeriesMetadata> SeriesMetadata { get; set; }
+ public virtual Microsoft.EntityFrameworkCore.DbSet<global::Jellyfin.Data.Entities.Track> Tracks { get; set; }
+ public virtual Microsoft.EntityFrameworkCore.DbSet<global::Jellyfin.Data.Entities.TrackMetadata> TrackMetadata { get; set; }
+ public virtual Microsoft.EntityFrameworkCore.DbSet<global::Jellyfin.Data.Entities.User> Users { get; set; }
+ #endregion DbSets
+
+ /// <summary>
+ /// Default connection string
+ /// </summary>
+ public static string ConnectionString { get; set; } = @"Data Source=jellyfin.db";
+
+ /// <inheritdoc />
+ public Jellyfin(DbContextOptions<Jellyfin> options) : base(options)
+ {
+ }
+
+ partial void CustomInit(DbContextOptionsBuilder optionsBuilder);
+
+ /// <inheritdoc />
+ protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
+ {
+ CustomInit(optionsBuilder);
+ }
+
+ partial void OnModelCreatingImpl(ModelBuilder modelBuilder);
+ partial void OnModelCreatedImpl(ModelBuilder modelBuilder);
+
+ /// <inheritdoc />
+ protected override void OnModelCreating(ModelBuilder modelBuilder)
+ {
+ base.OnModelCreating(modelBuilder);
+ OnModelCreatingImpl(modelBuilder);
+
+ modelBuilder.HasDefaultSchema("jellyfin");
+
+ modelBuilder.Entity<global::Jellyfin.Data.Entities.Artwork>()
+ .ToTable("Artwork")
+ .HasKey(t => t.Id);
+ modelBuilder.Entity<global::Jellyfin.Data.Entities.Artwork>()
+ .Property(t => t.Id)
+ .IsRequired()
+ .HasField("_Id")
+ .UsePropertyAccessMode(PropertyAccessMode.Property)
+ .ValueGeneratedOnAdd();
+ modelBuilder.Entity<global::Jellyfin.Data.Entities.Artwork>()
+ .Property(t => t.Path)
+ .HasMaxLength(65535)
+ .IsRequired()
+ .HasField("_Path")
+ .UsePropertyAccessMode(PropertyAccessMode.Property);
+ modelBuilder.Entity<global::Jellyfin.Data.Entities.Artwork>()
+ .Property(t => t.Kind)
+ .IsRequired()
+ .HasField("_Kind")
+ .UsePropertyAccessMode(PropertyAccessMode.Property);
+ modelBuilder.Entity<global::Jellyfin.Data.Entities.Artwork>().HasIndex(t => t.Kind);
+ modelBuilder.Entity<global::Jellyfin.Data.Entities.Artwork>()
+ .Property(t => t.Timestamp)
+ .IsRequired()
+ .HasField("_Timestamp")
+ .UsePropertyAccessMode(PropertyAccessMode.Property)
+ .IsRowVersion();
+
+ modelBuilder.Entity<global::Jellyfin.Data.Entities.Book>()
+ .HasMany(x => x.BookMetadata)
+ .WithOne()
+ .HasForeignKey("BookMetadata_BookMetadata_Id")
+ .IsRequired();
+ modelBuilder.Entity<global::Jellyfin.Data.Entities.Book>()
+ .HasMany(x => x.Releases)
+ .WithOne()
+ .HasForeignKey("Release_Releases_Id")
+ .IsRequired();
+
+ modelBuilder.Entity<global::Jellyfin.Data.Entities.BookMetadata>()
+ .Property(t => t.ISBN)
+ .HasField("_ISBN")
+ .UsePropertyAccessMode(PropertyAccessMode.Property);
+ modelBuilder.Entity<global::Jellyfin.Data.Entities.BookMetadata>()
+ .HasMany(x => x.Publishers)
+ .WithOne()
+ .HasForeignKey("Company_Publishers_Id")
+ .IsRequired();
+
+ modelBuilder.Entity<global::Jellyfin.Data.Entities.Chapter>()
+ .ToTable("Chapter")
+ .HasKey(t => t.Id);
+ modelBuilder.Entity<global::Jellyfin.Data.Entities.Chapter>()
+ .Property(t => t.Id)
+ .IsRequired()
+ .HasField("_Id")
+ .UsePropertyAccessMode(PropertyAccessMode.Property)
+ .ValueGeneratedOnAdd();
+ modelBuilder.Entity<global::Jellyfin.Data.Entities.Chapter>()
+ .Property(t => t.Name)
+ .HasMaxLength(1024)
+ .HasField("_Name")
+ .UsePropertyAccessMode(PropertyAccessMode.Property);
+ modelBuilder.Entity<global::Jellyfin.Data.Entities.Chapter>()
+ .Property(t => t.Language)
+ .HasMaxLength(3)
+ .IsRequired()
+ .HasField("_Language")
+ .UsePropertyAccessMode(PropertyAccessMode.Property);
+ modelBuilder.Entity<global::Jellyfin.Data.Entities.Chapter>()
+ .Property(t => t.TimeStart)
+ .IsRequired()
+ .HasField("_TimeStart")
+ .UsePropertyAccessMode(PropertyAccessMode.Property);
+ modelBuilder.Entity<global::Jellyfin.Data.Entities.Chapter>()
+ .Property(t => t.TimeEnd)
+ .HasField("_TimeEnd")
+ .UsePropertyAccessMode(PropertyAccessMode.Property);
+ modelBuilder.Entity<global::Jellyfin.Data.Entities.Chapter>()
+ .Property(t => t.Timestamp)
+ .IsRequired()
+ .HasField("_Timestamp")
+ .UsePropertyAccessMode(PropertyAccessMode.Property)
+ .IsRowVersion();
+
+ modelBuilder.Entity<global::Jellyfin.Data.Entities.Collection>()
+ .ToTable("Collection")
+ .HasKey(t => t.Id);
+ modelBuilder.Entity<global::Jellyfin.Data.Entities.Collection>()
+ .Property(t => t.Id)
+ .IsRequired()
+ .HasField("_Id")
+ .UsePropertyAccessMode(PropertyAccessMode.Property)
+ .ValueGeneratedOnAdd();
+ modelBuilder.Entity<global::Jellyfin.Data.Entities.Collection>()
+ .Property(t => t.Name)
+ .HasMaxLength(1024)
+ .HasField("_Name")
+ .UsePropertyAccessMode(PropertyAccessMode.Property);
+ modelBuilder.Entity<global::Jellyfin.Data.Entities.Collection>()
+ .Property(t => t.Timestamp)
+ .IsRequired()
+ .HasField("_Timestamp")
+ .UsePropertyAccessMode(PropertyAccessMode.Property)
+ .IsRowVersion();
+ modelBuilder.Entity<global::Jellyfin.Data.Entities.Collection>()
+ .HasMany(x => x.CollectionItem)
+ .WithOne()
+ .HasForeignKey("CollectionItem_CollectionItem_Id")
+ .IsRequired();
+
+ modelBuilder.Entity<global::Jellyfin.Data.Entities.CollectionItem>()
+ .ToTable("CollectionItem")
+ .HasKey(t => t.Id);
+ modelBuilder.Entity<global::Jellyfin.Data.Entities.CollectionItem>()
+ .Property(t => t.Id)
+ .IsRequired()
+ .HasField("_Id")
+ .UsePropertyAccessMode(PropertyAccessMode.Property)
+ .ValueGeneratedOnAdd();
+ modelBuilder.Entity<global::Jellyfin.Data.Entities.CollectionItem>()
+ .Property(t => t.Timestamp)
+ .IsRequired()
+ .HasField("_Timestamp")
+ .UsePropertyAccessMode(PropertyAccessMode.Property)
+ .IsRowVersion();
+ modelBuilder.Entity<global::Jellyfin.Data.Entities.CollectionItem>()
+ .HasOne(x => x.LibraryItem)
+ .WithOne()
+ .HasForeignKey<global::Jellyfin.Data.Entities.CollectionItem>("LibraryItem_Id")
+ .IsRequired();
+ modelBuilder.Entity<global::Jellyfin.Data.Entities.CollectionItem>()
+ .HasOne(x => x.Next)
+ .WithOne()
+ .HasForeignKey<global::Jellyfin.Data.Entities.CollectionItem>("CollectionItem_Next_Id")
+ .IsRequired();
+ modelBuilder.Entity<global::Jellyfin.Data.Entities.CollectionItem>()
+ .HasOne(x => x.Previous)
+ .WithOne()
+ .HasForeignKey<global::Jellyfin.Data.Entities.CollectionItem>("CollectionItem_Previous_Id")
+ .IsRequired();
+
+ modelBuilder.Entity<global::Jellyfin.Data.Entities.Company>()
+ .ToTable("Company")
+ .HasKey(t => t.Id);
+ modelBuilder.Entity<global::Jellyfin.Data.Entities.Company>()
+ .Property(t => t.Id)
+ .IsRequired()
+ .HasField("_Id")
+ .UsePropertyAccessMode(PropertyAccessMode.Property)
+ .ValueGeneratedOnAdd();
+ modelBuilder.Entity<global::Jellyfin.Data.Entities.Company>()
+ .Property(t => t.Timestamp)
+ .IsRequired()
+ .HasField("_Timestamp")
+ .UsePropertyAccessMode(PropertyAccessMode.Property)
+ .IsRowVersion();
+ modelBuilder.Entity<global::Jellyfin.Data.Entities.Company>()
+ .HasMany(x => x.CompanyMetadata)
+ .WithOne()
+ .HasForeignKey("CompanyMetadata_CompanyMetadata_Id")
+ .IsRequired();
+ modelBuilder.Entity<global::Jellyfin.Data.Entities.Company>()
+ .HasOne(x => x.Parent)
+ .WithOne()
+ .HasForeignKey<global::Jellyfin.Data.Entities.Company>("Company_Parent_Id")
+ .IsRequired();
+
+ modelBuilder.Entity<global::Jellyfin.Data.Entities.CompanyMetadata>()
+ .Property(t => t.Description)
+ .HasMaxLength(65535)
+ .HasField("_Description")
+ .UsePropertyAccessMode(PropertyAccessMode.Property);
+ modelBuilder.Entity<global::Jellyfin.Data.Entities.CompanyMetadata>()
+ .Property(t => t.Headquarters)
+ .HasMaxLength(255)
+ .HasField("_Headquarters")
+ .UsePropertyAccessMode(PropertyAccessMode.Property);
+ modelBuilder.Entity<global::Jellyfin.Data.Entities.CompanyMetadata>()
+ .Property(t => t.Country)
+ .HasMaxLength(2)
+ .HasField("_Country")
+ .UsePropertyAccessMode(PropertyAccessMode.Property);
+ modelBuilder.Entity<global::Jellyfin.Data.Entities.CompanyMetadata>()
+ .Property(t => t.Homepage)
+ .HasMaxLength(1024)
+ .HasField("_Homepage")
+ .UsePropertyAccessMode(PropertyAccessMode.Property);
+
+ modelBuilder.Entity<global::Jellyfin.Data.Entities.CustomItem>()
+ .HasMany(x => x.CustomItemMetadata)
+ .WithOne()
+ .HasForeignKey("CustomItemMetadata_CustomItemMetadata_Id")
+ .IsRequired();
+ modelBuilder.Entity<global::Jellyfin.Data.Entities.CustomItem>()
+ .HasMany(x => x.Releases)
+ .WithOne()
+ .HasForeignKey("Release_Releases_Id")
+ .IsRequired();
+
+
+ modelBuilder.Entity<global::Jellyfin.Data.Entities.Episode>()
+ .Property(t => t.EpisodeNumber)
+ .HasField("_EpisodeNumber")
+ .UsePropertyAccessMode(PropertyAccessMode.Property);
+ modelBuilder.Entity<global::Jellyfin.Data.Entities.Episode>()
+ .HasMany(x => x.Releases)
+ .WithOne()
+ .HasForeignKey("Release_Releases_Id")
+ .IsRequired();
+ modelBuilder.Entity<global::Jellyfin.Data.Entities.Episode>()
+ .HasMany(x => x.EpisodeMetadata)
+ .WithOne()
+ .HasForeignKey("EpisodeMetadata_EpisodeMetadata_Id")
+ .IsRequired();
+
+ modelBuilder.Entity<global::Jellyfin.Data.Entities.EpisodeMetadata>()
+ .Property(t => t.Outline)
+ .HasMaxLength(1024)
+ .HasField("_Outline")
+ .UsePropertyAccessMode(PropertyAccessMode.Property);
+ modelBuilder.Entity<global::Jellyfin.Data.Entities.EpisodeMetadata>()
+ .Property(t => t.Plot)
+ .HasMaxLength(65535)
+ .HasField("_Plot")
+ .UsePropertyAccessMode(PropertyAccessMode.Property);
+ modelBuilder.Entity<global::Jellyfin.Data.Entities.EpisodeMetadata>()
+ .Property(t => t.Tagline)
+ .HasMaxLength(1024)
+ .HasField("_Tagline")
+ .UsePropertyAccessMode(PropertyAccessMode.Property);
+
+ modelBuilder.Entity<global::Jellyfin.Data.Entities.Genre>()
+ .ToTable("Genre")
+ .HasKey(t => t.Id);
+ modelBuilder.Entity<global::Jellyfin.Data.Entities.Genre>()
+ .Property(t => t.Id)
+ .IsRequired()
+ .HasField("_Id")
+ .UsePropertyAccessMode(PropertyAccessMode.Property)
+ .ValueGeneratedOnAdd();
+ modelBuilder.Entity<global::Jellyfin.Data.Entities.Genre>()
+ .Property(t => t.Name)
+ .HasMaxLength(255)
+ .IsRequired()
+ .HasField("_Name")
+ .UsePropertyAccessMode(PropertyAccessMode.Property);
+ modelBuilder.Entity<global::Jellyfin.Data.Entities.Genre>().HasIndex(t => t.Name)
+ .IsUnique();
+ modelBuilder.Entity<global::Jellyfin.Data.Entities.Genre>()
+ .Property(t => t.Timestamp)
+ .IsRequired()
+ .HasField("_Timestamp")
+ .UsePropertyAccessMode(PropertyAccessMode.Property)
+ .IsRowVersion();
+
+ modelBuilder.Entity<global::Jellyfin.Data.Entities.Group>()
+ .ToTable("Groups")
+ .HasKey(t => t.Id);
+ modelBuilder.Entity<global::Jellyfin.Data.Entities.Group>()
+ .Property(t => t.Id)
+ .IsRequired()
+ .ValueGeneratedOnAdd();
+ modelBuilder.Entity<global::Jellyfin.Data.Entities.Group>()
+ .Property(t => t.Name)
+ .HasMaxLength(255)
+ .IsRequired();
+ modelBuilder.Entity<global::Jellyfin.Data.Entities.Group>().Property<byte[]>("Timestamp").IsConcurrencyToken();
+ modelBuilder.Entity<global::Jellyfin.Data.Entities.Group>()
+ .HasMany(x => x.GroupPermissions)
+ .WithOne()
+ .HasForeignKey("Permission_GroupPermissions_Id")
+ .IsRequired();
+ modelBuilder.Entity<global::Jellyfin.Data.Entities.Group>()
+ .HasMany(x => x.ProviderMappings)
+ .WithOne()
+ .HasForeignKey("ProviderMapping_ProviderMappings_Id")
+ .IsRequired();
+ modelBuilder.Entity<global::Jellyfin.Data.Entities.Group>()
+ .HasMany(x => x.Preferences)
+ .WithOne()
+ .HasForeignKey("Preference_Preferences_Id")
+ .IsRequired();
+
+ modelBuilder.Entity<global::Jellyfin.Data.Entities.Library>()
+ .ToTable("Library")
+ .HasKey(t => t.Id);
+ modelBuilder.Entity<global::Jellyfin.Data.Entities.Library>()
+ .Property(t => t.Id)
+ .IsRequired()
+ .HasField("_Id")
+ .UsePropertyAccessMode(PropertyAccessMode.Property)
+ .ValueGeneratedOnAdd();
+ modelBuilder.Entity<global::Jellyfin.Data.Entities.Library>()
+ .Property(t => t.Name)
+ .HasMaxLength(1024)
+ .IsRequired()
+ .HasField("_Name")
+ .UsePropertyAccessMode(PropertyAccessMode.Property);
+ modelBuilder.Entity<global::Jellyfin.Data.Entities.Library>()
+ .Property(t => t.Timestamp)
+ .IsRequired()
+ .HasField("_Timestamp")
+ .UsePropertyAccessMode(PropertyAccessMode.Property)
+ .IsRowVersion();
+
+ modelBuilder.Entity<global::Jellyfin.Data.Entities.LibraryItem>()
+ .ToTable("LibraryItem")
+ .HasKey(t => t.Id);
+ modelBuilder.Entity<global::Jellyfin.Data.Entities.LibraryItem>()
+ .Property(t => t.Id)
+ .IsRequired()
+ .HasField("_Id")
+ .UsePropertyAccessMode(PropertyAccessMode.Property)
+ .ValueGeneratedOnAdd();
+ modelBuilder.Entity<global::Jellyfin.Data.Entities.LibraryItem>()
+ .Property(t => t.UrlId)
+ .IsRequired()
+ .HasField("_UrlId")
+ .UsePropertyAccessMode(PropertyAccessMode.Property);
+ modelBuilder.Entity<global::Jellyfin.Data.Entities.LibraryItem>().HasIndex(t => t.UrlId)
+ .IsUnique();
+ modelBuilder.Entity<global::Jellyfin.Data.Entities.LibraryItem>()
+ .Property(t => t.DateAdded)
+ .IsRequired()
+ .HasField("_DateAdded")
+ .UsePropertyAccessMode(PropertyAccessMode.Property);
+ modelBuilder.Entity<global::Jellyfin.Data.Entities.LibraryItem>()
+ .Property(t => t.Timestamp)
+ .IsRequired()
+ .HasField("_Timestamp")
+ .UsePropertyAccessMode(PropertyAccessMode.Property)
+ .IsRowVersion();
+ modelBuilder.Entity<global::Jellyfin.Data.Entities.LibraryItem>()
+ .HasOne(x => x.LibraryRoot)
+ .WithOne()
+ .HasForeignKey<global::Jellyfin.Data.Entities.LibraryItem>("LibraryRoot_Id")
+ .IsRequired();
+
+ modelBuilder.Entity<global::Jellyfin.Data.Entities.LibraryRoot>()
+ .ToTable("LibraryRoot")
+ .HasKey(t => t.Id);
+ modelBuilder.Entity<global::Jellyfin.Data.Entities.LibraryRoot>()
+ .Property(t => t.Id)
+ .IsRequired()
+ .HasField("_Id")
+ .UsePropertyAccessMode(PropertyAccessMode.Property)
+ .ValueGeneratedOnAdd();
+ modelBuilder.Entity<global::Jellyfin.Data.Entities.LibraryRoot>()
+ .Property(t => t.Path)
+ .HasMaxLength(65535)
+ .IsRequired()
+ .HasField("_Path")
+ .UsePropertyAccessMode(PropertyAccessMode.Property);
+ modelBuilder.Entity<global::Jellyfin.Data.Entities.LibraryRoot>()
+ .Property(t => t.NetworkPath)
+ .HasMaxLength(65535)
+ .HasField("_NetworkPath")
+ .UsePropertyAccessMode(PropertyAccessMode.Property);
+ modelBuilder.Entity<global::Jellyfin.Data.Entities.LibraryRoot>()
+ .Property(t => t.Timestamp)
+ .IsRequired()
+ .HasField("_Timestamp")
+ .UsePropertyAccessMode(PropertyAccessMode.Property)
+ .IsRowVersion();
+ modelBuilder.Entity<global::Jellyfin.Data.Entities.LibraryRoot>()
+ .HasOne(x => x.Library)
+ .WithOne()
+ .HasForeignKey<global::Jellyfin.Data.Entities.LibraryRoot>("Library_Id")
+ .IsRequired();
+
+ modelBuilder.Entity<global::Jellyfin.Data.Entities.MediaFile>()
+ .ToTable("MediaFile")
+ .HasKey(t => t.Id);
+ modelBuilder.Entity<global::Jellyfin.Data.Entities.MediaFile>()
+ .Property(t => t.Id)
+ .IsRequired()
+ .HasField("_Id")
+ .UsePropertyAccessMode(PropertyAccessMode.Property)
+ .ValueGeneratedOnAdd();
+ modelBuilder.Entity<global::Jellyfin.Data.Entities.MediaFile>()
+ .Property(t => t.Path)
+ .HasMaxLength(65535)
+ .IsRequired()
+ .HasField("_Path")
+ .UsePropertyAccessMode(PropertyAccessMode.Property);
+ modelBuilder.Entity<global::Jellyfin.Data.Entities.MediaFile>()
+ .Property(t => t.Kind)
+ .IsRequired()
+ .HasField("_Kind")
+ .UsePropertyAccessMode(PropertyAccessMode.Property);
+ modelBuilder.Entity<global::Jellyfin.Data.Entities.MediaFile>()
+ .Property(t => t.Timestamp)
+ .IsRequired()
+ .HasField("_Timestamp")
+ .UsePropertyAccessMode(PropertyAccessMode.Property)
+ .IsRowVersion();
+ modelBuilder.Entity<global::Jellyfin.Data.Entities.MediaFile>()
+ .HasMany(x => x.MediaFileStreams)
+ .WithOne()
+ .HasForeignKey("MediaFileStream_MediaFileStreams_Id")
+ .IsRequired();
+
+ modelBuilder.Entity<global::Jellyfin.Data.Entities.MediaFileStream>()
+ .ToTable("MediaFileStream")
+ .HasKey(t => t.Id);
+ modelBuilder.Entity<global::Jellyfin.Data.Entities.MediaFileStream>()
+ .Property(t => t.Id)
+ .IsRequired()
+ .HasField("_Id")
+ .UsePropertyAccessMode(PropertyAccessMode.Property)
+ .ValueGeneratedOnAdd();
+ modelBuilder.Entity<global::Jellyfin.Data.Entities.MediaFileStream>()
+ .Property(t => t.StreamNumber)
+ .IsRequired()
+ .HasField("_StreamNumber")
+ .UsePropertyAccessMode(PropertyAccessMode.Property);
+ modelBuilder.Entity<global::Jellyfin.Data.Entities.MediaFileStream>()
+ .Property(t => t.Timestamp)
+ .IsRequired()
+ .HasField("_Timestamp")
+ .UsePropertyAccessMode(PropertyAccessMode.Property)
+ .IsRowVersion();
+
+ modelBuilder.Entity<global::Jellyfin.Data.Entities.Metadata>()
+ .ToTable("Metadata")
+ .HasKey(t => t.Id);
+ modelBuilder.Entity<global::Jellyfin.Data.Entities.Metadata>()
+ .Property(t => t.Id)
+ .IsRequired()
+ .HasField("_Id")
+ .UsePropertyAccessMode(PropertyAccessMode.Property)
+ .ValueGeneratedOnAdd();
+ modelBuilder.Entity<global::Jellyfin.Data.Entities.Metadata>()
+ .Property(t => t.Title)
+ .HasMaxLength(1024)
+ .IsRequired()
+ .HasField("_Title")
+ .UsePropertyAccessMode(PropertyAccessMode.Property);
+ modelBuilder.Entity<global::Jellyfin.Data.Entities.Metadata>()
+ .Property(t => t.OriginalTitle)
+ .HasMaxLength(1024)
+ .HasField("_OriginalTitle")
+ .UsePropertyAccessMode(PropertyAccessMode.Property);
+ modelBuilder.Entity<global::Jellyfin.Data.Entities.Metadata>()
+ .Property(t => t.SortTitle)
+ .HasMaxLength(1024)
+ .HasField("_SortTitle")
+ .UsePropertyAccessMode(PropertyAccessMode.Property);
+ modelBuilder.Entity<global::Jellyfin.Data.Entities.Metadata>()
+ .Property(t => t.Language)
+ .HasMaxLength(3)
+ .IsRequired()
+ .HasField("_Language")
+ .UsePropertyAccessMode(PropertyAccessMode.Property);
+ modelBuilder.Entity<global::Jellyfin.Data.Entities.Metadata>()
+ .Property(t => t.ReleaseDate)
+ .HasField("_ReleaseDate")
+ .UsePropertyAccessMode(PropertyAccessMode.Property);
+ modelBuilder.Entity<global::Jellyfin.Data.Entities.Metadata>()
+ .Property(t => t.DateAdded)
+ .IsRequired()
+ .HasField("_DateAdded")
+ .UsePropertyAccessMode(PropertyAccessMode.Property);
+ modelBuilder.Entity<global::Jellyfin.Data.Entities.Metadata>()
+ .Property(t => t.DateModified)
+ .IsRequired()
+ .HasField("_DateModified")
+ .UsePropertyAccessMode(PropertyAccessMode.Property);
+ modelBuilder.Entity<global::Jellyfin.Data.Entities.Metadata>()
+ .Property(t => t.Timestamp)
+ .IsRequired()
+ .HasField("_Timestamp")
+ .UsePropertyAccessMode(PropertyAccessMode.Property)
+ .IsRowVersion();
+ modelBuilder.Entity<global::Jellyfin.Data.Entities.Metadata>()
+ .HasMany(x => x.PersonRoles)
+ .WithOne()
+ .HasForeignKey("PersonRole_PersonRoles_Id")
+ .IsRequired();
+ modelBuilder.Entity<global::Jellyfin.Data.Entities.Metadata>()
+ .HasMany(x => x.Genres)
+ .WithOne()
+ .HasForeignKey("Genre_Genres_Id")
+ .IsRequired();
+ modelBuilder.Entity<global::Jellyfin.Data.Entities.Metadata>()
+ .HasMany(x => x.Artwork)
+ .WithOne()
+ .HasForeignKey("Artwork_Artwork_Id")
+ .IsRequired();
+ modelBuilder.Entity<global::Jellyfin.Data.Entities.Metadata>()
+ .HasMany(x => x.Ratings)
+ .WithOne()
+ .HasForeignKey("Rating_Ratings_Id")
+ .IsRequired();
+ modelBuilder.Entity<global::Jellyfin.Data.Entities.Metadata>()
+ .HasMany(x => x.Sources)
+ .WithOne()
+ .HasForeignKey("MetadataProviderId_Sources_Id")
+ .IsRequired();
+
+ modelBuilder.Entity<global::Jellyfin.Data.Entities.MetadataProvider>()
+ .ToTable("MetadataProvider")
+ .HasKey(t => t.Id);
+ modelBuilder.Entity<global::Jellyfin.Data.Entities.MetadataProvider>()
+ .Property(t => t.Id)
+ .IsRequired()
+ .HasField("_Id")
+ .UsePropertyAccessMode(PropertyAccessMode.Property)
+ .ValueGeneratedOnAdd();
+ modelBuilder.Entity<global::Jellyfin.Data.Entities.MetadataProvider>()
+ .Property(t => t.Name)
+ .HasMaxLength(1024)
+ .IsRequired()
+ .HasField("_Name")
+ .UsePropertyAccessMode(PropertyAccessMode.Property);
+ modelBuilder.Entity<global::Jellyfin.Data.Entities.MetadataProvider>()
+ .Property(t => t.Timestamp)
+ .IsRequired()
+ .HasField("_Timestamp")
+ .UsePropertyAccessMode(PropertyAccessMode.Property)
+ .IsRowVersion();
+
+ modelBuilder.Entity<global::Jellyfin.Data.Entities.MetadataProviderId>()
+ .ToTable("MetadataProviderId")
+ .HasKey(t => t.Id);
+ modelBuilder.Entity<global::Jellyfin.Data.Entities.MetadataProviderId>()
+ .Property(t => t.Id)
+ .IsRequired()
+ .HasField("_Id")
+ .UsePropertyAccessMode(PropertyAccessMode.Property)
+ .ValueGeneratedOnAdd();
+ modelBuilder.Entity<global::Jellyfin.Data.Entities.MetadataProviderId>()
+ .Property(t => t.ProviderId)
+ .HasMaxLength(255)
+ .IsRequired()
+ .HasField("_ProviderId")
+ .UsePropertyAccessMode(PropertyAccessMode.Property);
+ modelBuilder.Entity<global::Jellyfin.Data.Entities.MetadataProviderId>()
+ .Property(t => t.Timestamp)
+ .IsRequired()
+ .HasField("_Timestamp")
+ .UsePropertyAccessMode(PropertyAccessMode.Property)
+ .IsRowVersion();
+ modelBuilder.Entity<global::Jellyfin.Data.Entities.MetadataProviderId>()
+ .HasOne(x => x.MetadataProvider)
+ .WithOne()
+ .HasForeignKey<global::Jellyfin.Data.Entities.MetadataProviderId>("MetadataProvider_Id")
+ .IsRequired();
+
+ modelBuilder.Entity<global::Jellyfin.Data.Entities.Movie>()
+ .HasMany(x => x.Releases)
+ .WithOne()
+ .HasForeignKey("Release_Releases_Id")
+ .IsRequired();
+ modelBuilder.Entity<global::Jellyfin.Data.Entities.Movie>()
+ .HasMany(x => x.MovieMetadata)
+ .WithOne()
+ .HasForeignKey("MovieMetadata_MovieMetadata_Id")
+ .IsRequired();
+
+ modelBuilder.Entity<global::Jellyfin.Data.Entities.MovieMetadata>()
+ .Property(t => t.Outline)
+ .HasMaxLength(1024)
+ .HasField("_Outline")
+ .UsePropertyAccessMode(PropertyAccessMode.Property);
+ modelBuilder.Entity<global::Jellyfin.Data.Entities.MovieMetadata>()
+ .Property(t => t.Plot)
+ .HasMaxLength(65535)
+ .HasField("_Plot")
+ .UsePropertyAccessMode(PropertyAccessMode.Property);
+ modelBuilder.Entity<global::Jellyfin.Data.Entities.MovieMetadata>()
+ .Property(t => t.Tagline)
+ .HasMaxLength(1024)
+ .HasField("_Tagline")
+ .UsePropertyAccessMode(PropertyAccessMode.Property);
+ modelBuilder.Entity<global::Jellyfin.Data.Entities.MovieMetadata>()
+ .Property(t => t.Country)
+ .HasMaxLength(2)
+ .HasField("_Country")
+ .UsePropertyAccessMode(PropertyAccessMode.Property);
+ modelBuilder.Entity<global::Jellyfin.Data.Entities.MovieMetadata>()
+ .HasMany(x => x.Studios)
+ .WithOne()
+ .HasForeignKey("Company_Studios_Id")
+ .IsRequired();
+
+ modelBuilder.Entity<global::Jellyfin.Data.Entities.MusicAlbum>()
+ .HasMany(x => x.MusicAlbumMetadata)
+ .WithOne()
+ .HasForeignKey("MusicAlbumMetadata_MusicAlbumMetadata_Id")
+ .IsRequired();
+ modelBuilder.Entity<global::Jellyfin.Data.Entities.MusicAlbum>()
+ .HasMany(x => x.Tracks)
+ .WithOne()
+ .HasForeignKey("Track_Tracks_Id")
+ .IsRequired();
+
+ modelBuilder.Entity<global::Jellyfin.Data.Entities.MusicAlbumMetadata>()
+ .Property(t => t.Barcode)
+ .HasMaxLength(255)
+ .HasField("_Barcode")
+ .UsePropertyAccessMode(PropertyAccessMode.Property);
+ modelBuilder.Entity<global::Jellyfin.Data.Entities.MusicAlbumMetadata>()
+ .Property(t => t.LabelNumber)
+ .HasMaxLength(255)
+ .HasField("_LabelNumber")
+ .UsePropertyAccessMode(PropertyAccessMode.Property);
+ modelBuilder.Entity<global::Jellyfin.Data.Entities.MusicAlbumMetadata>()
+ .Property(t => t.Country)
+ .HasMaxLength(2)
+ .HasField("_Country")
+ .UsePropertyAccessMode(PropertyAccessMode.Property);
+ modelBuilder.Entity<global::Jellyfin.Data.Entities.MusicAlbumMetadata>()
+ .HasMany(x => x.Labels)
+ .WithOne()
+ .HasForeignKey("Company_Labels_Id")
+ .IsRequired();
+
+ modelBuilder.Entity<global::Jellyfin.Data.Entities.Permission>()
+ .ToTable("Permissions")
+ .HasKey(t => t.Id);
+ modelBuilder.Entity<global::Jellyfin.Data.Entities.Permission>()
+ .Property(t => t.Id)
+ .IsRequired()
+ .ValueGeneratedOnAdd();
+ modelBuilder.Entity<global::Jellyfin.Data.Entities.Permission>()
+ .Property(t => t.Kind)
+ .IsRequired()
+ .HasField("_Kind")
+ .UsePropertyAccessMode(PropertyAccessMode.Property);
+ modelBuilder.Entity<global::Jellyfin.Data.Entities.Permission>()
+ .Property(t => t.Value)
+ .IsRequired();
+ modelBuilder.Entity<global::Jellyfin.Data.Entities.Permission>().Property<byte[]>("Timestamp").IsConcurrencyToken();
+
+ modelBuilder.Entity<global::Jellyfin.Data.Entities.Person>()
+ .ToTable("Person")
+ .HasKey(t => t.Id);
+ modelBuilder.Entity<global::Jellyfin.Data.Entities.Person>()
+ .Property(t => t.Id)
+ .IsRequired()
+ .HasField("_Id")
+ .UsePropertyAccessMode(PropertyAccessMode.Property)
+ .ValueGeneratedOnAdd();
+ modelBuilder.Entity<global::Jellyfin.Data.Entities.Person>()
+ .Property(t => t.UrlId)
+ .IsRequired()
+ .HasField("_UrlId")
+ .UsePropertyAccessMode(PropertyAccessMode.Property);
+ modelBuilder.Entity<global::Jellyfin.Data.Entities.Person>()
+ .Property(t => t.Name)
+ .HasMaxLength(1024)
+ .IsRequired()
+ .HasField("_Name")
+ .UsePropertyAccessMode(PropertyAccessMode.Property);
+ modelBuilder.Entity<global::Jellyfin.Data.Entities.Person>()
+ .Property(t => t.SourceId)
+ .HasMaxLength(255)
+ .HasField("_SourceId")
+ .UsePropertyAccessMode(PropertyAccessMode.Property);
+ modelBuilder.Entity<global::Jellyfin.Data.Entities.Person>()
+ .Property(t => t.DateAdded)
+ .IsRequired()
+ .HasField("_DateAdded")
+ .UsePropertyAccessMode(PropertyAccessMode.Property);
+ modelBuilder.Entity<global::Jellyfin.Data.Entities.Person>()
+ .Property(t => t.DateModified)
+ .IsRequired()
+ .HasField("_DateModified")
+ .UsePropertyAccessMode(PropertyAccessMode.Property);
+ modelBuilder.Entity<global::Jellyfin.Data.Entities.Person>()
+ .Property(t => t.Timestamp)
+ .IsRequired()
+ .HasField("_Timestamp")
+ .UsePropertyAccessMode(PropertyAccessMode.Property)
+ .IsRowVersion();
+ modelBuilder.Entity<global::Jellyfin.Data.Entities.Person>()
+ .HasMany(x => x.Sources)
+ .WithOne()
+ .HasForeignKey("MetadataProviderId_Sources_Id")
+ .IsRequired();
+
+ modelBuilder.Entity<global::Jellyfin.Data.Entities.PersonRole>()
+ .ToTable("PersonRole")
+ .HasKey(t => t.Id);
+ modelBuilder.Entity<global::Jellyfin.Data.Entities.PersonRole>()
+ .Property(t => t.Id)
+ .IsRequired()
+ .HasField("_Id")
+ .UsePropertyAccessMode(PropertyAccessMode.Property)
+ .ValueGeneratedOnAdd();
+ modelBuilder.Entity<global::Jellyfin.Data.Entities.PersonRole>()
+ .Property(t => t.Role)
+ .HasMaxLength(1024)
+ .HasField("_Role")
+ .UsePropertyAccessMode(PropertyAccessMode.Property);
+ modelBuilder.Entity<global::Jellyfin.Data.Entities.PersonRole>()
+ .Property(t => t.Type)
+ .IsRequired()
+ .HasField("_Type")
+ .UsePropertyAccessMode(PropertyAccessMode.Property);
+ modelBuilder.Entity<global::Jellyfin.Data.Entities.PersonRole>()
+ .Property(t => t.Timestamp)
+ .IsRequired()
+ .HasField("_Timestamp")
+ .UsePropertyAccessMode(PropertyAccessMode.Property)
+ .IsRowVersion();
+ modelBuilder.Entity<global::Jellyfin.Data.Entities.PersonRole>()
+ .HasOne(x => x.Person)
+ .WithOne()
+ .HasForeignKey<global::Jellyfin.Data.Entities.PersonRole>("Person_Id")
+ .IsRequired()
+ .OnDelete(DeleteBehavior.Cascade);
+ modelBuilder.Entity<global::Jellyfin.Data.Entities.PersonRole>()
+ .HasOne(x => x.Artwork)
+ .WithOne()
+ .HasForeignKey<global::Jellyfin.Data.Entities.PersonRole>("Artwork_Artwork_Id")
+ .IsRequired();
+ modelBuilder.Entity<global::Jellyfin.Data.Entities.PersonRole>()
+ .HasMany(x => x.Sources)
+ .WithOne()
+ .HasForeignKey("MetadataProviderId_Sources_Id")
+ .IsRequired();
+
+ modelBuilder.Entity<global::Jellyfin.Data.Entities.Photo>()
+ .HasMany(x => x.PhotoMetadata)
+ .WithOne()
+ .HasForeignKey("PhotoMetadata_PhotoMetadata_Id")
+ .IsRequired();
+ modelBuilder.Entity<global::Jellyfin.Data.Entities.Photo>()
+ .HasMany(x => x.Releases)
+ .WithOne()
+ .HasForeignKey("Release_Releases_Id")
+ .IsRequired();
+
+
+ modelBuilder.Entity<global::Jellyfin.Data.Entities.Preference>()
+ .ToTable("Preferences")
+ .HasKey(t => t.Id);
+ modelBuilder.Entity<global::Jellyfin.Data.Entities.Preference>()
+ .Property(t => t.Id)
+ .IsRequired()
+ .ValueGeneratedOnAdd();
+ modelBuilder.Entity<global::Jellyfin.Data.Entities.Preference>()
+ .Property(t => t.Kind)
+ .IsRequired();
+ modelBuilder.Entity<global::Jellyfin.Data.Entities.Preference>()
+ .Property(t => t.Value)
+ .HasMaxLength(65535)
+ .IsRequired();
+ modelBuilder.Entity<global::Jellyfin.Data.Entities.Preference>().Property<byte[]>("Timestamp").IsConcurrencyToken();
+
+ modelBuilder.Entity<global::Jellyfin.Data.Entities.ProviderMapping>()
+ .ToTable("ProviderMappings")
+ .HasKey(t => t.Id);
+ modelBuilder.Entity<global::Jellyfin.Data.Entities.ProviderMapping>()
+ .Property(t => t.Id)
+ .IsRequired()
+ .ValueGeneratedOnAdd();
+ modelBuilder.Entity<global::Jellyfin.Data.Entities.ProviderMapping>()
+ .Property(t => t.ProviderName)
+ .HasMaxLength(255)
+ .IsRequired();
+ modelBuilder.Entity<global::Jellyfin.Data.Entities.ProviderMapping>()
+ .Property(t => t.ProviderSecrets)
+ .HasMaxLength(65535)
+ .IsRequired();
+ modelBuilder.Entity<global::Jellyfin.Data.Entities.ProviderMapping>()
+ .Property(t => t.ProviderData)
+ .HasMaxLength(65535)
+ .IsRequired();
+ modelBuilder.Entity<global::Jellyfin.Data.Entities.ProviderMapping>().Property<byte[]>("Timestamp").IsConcurrencyToken();
+
+ modelBuilder.Entity<global::Jellyfin.Data.Entities.Rating>()
+ .ToTable("Rating")
+ .HasKey(t => t.Id);
+ modelBuilder.Entity<global::Jellyfin.Data.Entities.Rating>()
+ .Property(t => t.Id)
+ .IsRequired()
+ .HasField("_Id")
+ .UsePropertyAccessMode(PropertyAccessMode.Property)
+ .ValueGeneratedOnAdd();
+ modelBuilder.Entity<global::Jellyfin.Data.Entities.Rating>()
+ .Property(t => t.Value)
+ .IsRequired()
+ .HasField("_Value")
+ .UsePropertyAccessMode(PropertyAccessMode.Property);
+ modelBuilder.Entity<global::Jellyfin.Data.Entities.Rating>()
+ .Property(t => t.Votes)
+ .HasField("_Votes")
+ .UsePropertyAccessMode(PropertyAccessMode.Property);
+ modelBuilder.Entity<global::Jellyfin.Data.Entities.Rating>()
+ .Property(t => t.Timestamp)
+ .IsRequired()
+ .HasField("_Timestamp")
+ .UsePropertyAccessMode(PropertyAccessMode.Property)
+ .IsRowVersion();
+ modelBuilder.Entity<global::Jellyfin.Data.Entities.Rating>()
+ .HasOne(x => x.RatingType)
+ .WithOne()
+ .HasForeignKey<global::Jellyfin.Data.Entities.Rating>("RatingSource_RatingType_Id")
+ .IsRequired();
+
+ modelBuilder.Entity<global::Jellyfin.Data.Entities.RatingSource>()
+ .ToTable("RatingType")
+ .HasKey(t => t.Id);
+ modelBuilder.Entity<global::Jellyfin.Data.Entities.RatingSource>()
+ .Property(t => t.Id)
+ .IsRequired()
+ .HasField("_Id")
+ .UsePropertyAccessMode(PropertyAccessMode.Property)
+ .ValueGeneratedOnAdd();
+ modelBuilder.Entity<global::Jellyfin.Data.Entities.RatingSource>()
+ .Property(t => t.Name)
+ .HasMaxLength(1024)
+ .HasField("_Name")
+ .UsePropertyAccessMode(PropertyAccessMode.Property);
+ modelBuilder.Entity<global::Jellyfin.Data.Entities.RatingSource>()
+ .Property(t => t.MaximumValue)
+ .IsRequired()
+ .HasField("_MaximumValue")
+ .UsePropertyAccessMode(PropertyAccessMode.Property);
+ modelBuilder.Entity<global::Jellyfin.Data.Entities.RatingSource>()
+ .Property(t => t.MinimumValue)
+ .IsRequired()
+ .HasField("_MinimumValue")
+ .UsePropertyAccessMode(PropertyAccessMode.Property);
+ modelBuilder.Entity<global::Jellyfin.Data.Entities.RatingSource>()
+ .Property(t => t.Timestamp)
+ .IsRequired()
+ .HasField("_Timestamp")
+ .UsePropertyAccessMode(PropertyAccessMode.Property)
+ .IsRowVersion();
+ modelBuilder.Entity<global::Jellyfin.Data.Entities.RatingSource>()
+ .HasOne(x => x.Source)
+ .WithOne()
+ .HasForeignKey<global::Jellyfin.Data.Entities.RatingSource>("MetadataProviderId_Source_Id")
+ .IsRequired();
+
+ modelBuilder.Entity<global::Jellyfin.Data.Entities.Release>()
+ .ToTable("Release")
+ .HasKey(t => t.Id);
+ modelBuilder.Entity<global::Jellyfin.Data.Entities.Release>()
+ .Property(t => t.Id)
+ .IsRequired()
+ .HasField("_Id")
+ .UsePropertyAccessMode(PropertyAccessMode.Property)
+ .ValueGeneratedOnAdd();
+ modelBuilder.Entity<global::Jellyfin.Data.Entities.Release>()
+ .Property(t => t.Name)
+ .HasMaxLength(1024)
+ .IsRequired()
+ .HasField("_Name")
+ .UsePropertyAccessMode(PropertyAccessMode.Property);
+ modelBuilder.Entity<global::Jellyfin.Data.Entities.Release>()
+ .Property(t => t.Timestamp)
+ .IsRequired()
+ .HasField("_Timestamp")
+ .UsePropertyAccessMode(PropertyAccessMode.Property)
+ .IsRowVersion();
+ modelBuilder.Entity<global::Jellyfin.Data.Entities.Release>()
+ .HasMany(x => x.MediaFiles)
+ .WithOne()
+ .HasForeignKey("MediaFile_MediaFiles_Id")
+ .IsRequired();
+ modelBuilder.Entity<global::Jellyfin.Data.Entities.Release>()
+ .HasMany(x => x.Chapters)
+ .WithOne()
+ .HasForeignKey("Chapter_Chapters_Id")
+ .IsRequired();
+
+ modelBuilder.Entity<global::Jellyfin.Data.Entities.Season>()
+ .Property(t => t.SeasonNumber)
+ .HasField("_SeasonNumber")
+ .UsePropertyAccessMode(PropertyAccessMode.Property);
+ modelBuilder.Entity<global::Jellyfin.Data.Entities.Season>()
+ .HasMany(x => x.SeasonMetadata)
+ .WithOne()
+ .HasForeignKey("SeasonMetadata_SeasonMetadata_Id")
+ .IsRequired();
+ modelBuilder.Entity<global::Jellyfin.Data.Entities.Season>()
+ .HasMany(x => x.Episodes)
+ .WithOne()
+ .HasForeignKey("Episode_Episodes_Id")
+ .IsRequired();
+
+ modelBuilder.Entity<global::Jellyfin.Data.Entities.SeasonMetadata>()
+ .Property(t => t.Outline)
+ .HasMaxLength(1024)
+ .HasField("_Outline")
+ .UsePropertyAccessMode(PropertyAccessMode.Property);
+
+ modelBuilder.Entity<global::Jellyfin.Data.Entities.Series>()
+ .Property(t => t.AirsDayOfWeek)
+ .HasField("_AirsDayOfWeek")
+ .UsePropertyAccessMode(PropertyAccessMode.Property);
+ modelBuilder.Entity<global::Jellyfin.Data.Entities.Series>()
+ .Property(t => t.AirsTime)
+ .HasField("_AirsTime")
+ .UsePropertyAccessMode(PropertyAccessMode.Property);
+ modelBuilder.Entity<global::Jellyfin.Data.Entities.Series>()
+ .Property(t => t.FirstAired)
+ .HasField("_FirstAired")
+ .UsePropertyAccessMode(PropertyAccessMode.Property);
+ modelBuilder.Entity<global::Jellyfin.Data.Entities.Series>()
+ .HasMany(x => x.SeriesMetadata)
+ .WithOne()
+ .HasForeignKey("SeriesMetadata_SeriesMetadata_Id")
+ .IsRequired();
+ modelBuilder.Entity<global::Jellyfin.Data.Entities.Series>()
+ .HasMany(x => x.Seasons)
+ .WithOne()
+ .HasForeignKey("Season_Seasons_Id")
+ .IsRequired();
+
+ modelBuilder.Entity<global::Jellyfin.Data.Entities.SeriesMetadata>()
+ .Property(t => t.Outline)
+ .HasMaxLength(1024)
+ .HasField("_Outline")
+ .UsePropertyAccessMode(PropertyAccessMode.Property);
+ modelBuilder.Entity<global::Jellyfin.Data.Entities.SeriesMetadata>()
+ .Property(t => t.Plot)
+ .HasMaxLength(65535)
+ .HasField("_Plot")
+ .UsePropertyAccessMode(PropertyAccessMode.Property);
+ modelBuilder.Entity<global::Jellyfin.Data.Entities.SeriesMetadata>()
+ .Property(t => t.Tagline)
+ .HasMaxLength(1024)
+ .HasField("_Tagline")
+ .UsePropertyAccessMode(PropertyAccessMode.Property);
+ modelBuilder.Entity<global::Jellyfin.Data.Entities.SeriesMetadata>()
+ .Property(t => t.Country)
+ .HasMaxLength(2)
+ .HasField("_Country")
+ .UsePropertyAccessMode(PropertyAccessMode.Property);
+ modelBuilder.Entity<global::Jellyfin.Data.Entities.SeriesMetadata>()
+ .HasMany(x => x.Networks)
+ .WithOne()
+ .HasForeignKey("Company_Networks_Id")
+ .IsRequired();
+
+ modelBuilder.Entity<global::Jellyfin.Data.Entities.Track>()
+ .Property(t => t.TrackNumber)
+ .HasField("_TrackNumber")
+ .UsePropertyAccessMode(PropertyAccessMode.Property);
+ modelBuilder.Entity<global::Jellyfin.Data.Entities.Track>()
+ .HasMany(x => x.Releases)
+ .WithOne()
+ .HasForeignKey("Release_Releases_Id")
+ .IsRequired();
+ modelBuilder.Entity<global::Jellyfin.Data.Entities.Track>()
+ .HasMany(x => x.TrackMetadata)
+ .WithOne()
+ .HasForeignKey("TrackMetadata_TrackMetadata_Id")
+ .IsRequired();
+
+
+ modelBuilder.Entity<global::Jellyfin.Data.Entities.User>()
+ .ToTable("Users")
+ .HasKey(t => t.Id);
+ modelBuilder.Entity<global::Jellyfin.Data.Entities.User>()
+ .Property(t => t.Id)
+ .IsRequired()
+ .ValueGeneratedOnAdd();
+ modelBuilder.Entity<global::Jellyfin.Data.Entities.User>()
+ .Property(t => t.LastLoginTimestamp)
+ .IsRequired()
+ .IsRowVersion();
+ modelBuilder.Entity<global::Jellyfin.Data.Entities.User>()
+ .Property(t => t.Username)
+ .HasMaxLength(255)
+ .IsRequired();
+ modelBuilder.Entity<global::Jellyfin.Data.Entities.User>()
+ .Property(t => t.Password)
+ .HasMaxLength(65535);
+ modelBuilder.Entity<global::Jellyfin.Data.Entities.User>()
+ .Property(t => t.MustUpdatePassword)
+ .IsRequired();
+ modelBuilder.Entity<global::Jellyfin.Data.Entities.User>()
+ .Property(t => t.AudioLanguagePreference)
+ .HasMaxLength(255)
+ .IsRequired();
+ modelBuilder.Entity<global::Jellyfin.Data.Entities.User>()
+ .Property(t => t.AuthenticationProviderId)
+ .HasMaxLength(255)
+ .IsRequired();
+ modelBuilder.Entity<global::Jellyfin.Data.Entities.User>()
+ .Property(t => t.GroupedFolders)
+ .HasMaxLength(65535);
+ modelBuilder.Entity<global::Jellyfin.Data.Entities.User>()
+ .Property(t => t.InvalidLoginAttemptCount)
+ .IsRequired();
+ modelBuilder.Entity<global::Jellyfin.Data.Entities.User>()
+ .Property(t => t.LatestItemExcludes)
+ .HasMaxLength(65535);
+ modelBuilder.Entity<global::Jellyfin.Data.Entities.User>()
+ .Property(t => t.MyMediaExcludes)
+ .HasMaxLength(65535);
+ modelBuilder.Entity<global::Jellyfin.Data.Entities.User>()
+ .Property(t => t.OrderedViews)
+ .HasMaxLength(65535);
+ modelBuilder.Entity<global::Jellyfin.Data.Entities.User>()
+ .Property(t => t.SubtitleMode)
+ .HasMaxLength(255)
+ .IsRequired();
+ modelBuilder.Entity<global::Jellyfin.Data.Entities.User>()
+ .Property(t => t.PlayDefaultAudioTrack)
+ .IsRequired();
+ modelBuilder.Entity<global::Jellyfin.Data.Entities.User>()
+ .Property(t => t.SubtitleLanguagePrefernce)
+ .HasMaxLength(255);
+ modelBuilder.Entity<global::Jellyfin.Data.Entities.User>()
+ .HasMany(x => x.Groups)
+ .WithOne()
+ .HasForeignKey("Group_Groups_Id")
+ .IsRequired();
+ modelBuilder.Entity<global::Jellyfin.Data.Entities.User>()
+ .HasMany(x => x.Permissions)
+ .WithOne()
+ .HasForeignKey("Permission_Permissions_Id")
+ .IsRequired();
+ modelBuilder.Entity<global::Jellyfin.Data.Entities.User>()
+ .HasMany(x => x.ProviderMappings)
+ .WithOne()
+ .HasForeignKey("ProviderMapping_ProviderMappings_Id")
+ .IsRequired();
+ modelBuilder.Entity<global::Jellyfin.Data.Entities.User>()
+ .HasMany(x => x.Preferences)
+ .WithOne()
+ .HasForeignKey("Preference_Preferences_Id")
+ .IsRequired();
+
+ OnModelCreatedImpl(modelBuilder);
+ }
+ }
+}
diff --git a/Jellyfin.Data/Entities/Artwork.cs b/Jellyfin.Data/Entities/Artwork.cs
new file mode 100644
index 000000000..be13686dc
--- /dev/null
+++ b/Jellyfin.Data/Entities/Artwork.cs
@@ -0,0 +1,208 @@
+//------------------------------------------------------------------------------
+// <auto-generated>
+// This code was generated from a template.
+//
+// Manual changes to this file may cause unexpected behavior in your application.
+// Manual changes to this file will be overwritten if the code is regenerated.
+//
+// Produced by Entity Framework Visual Editor
+// https://github.com/msawczyn/EFDesigner
+// </auto-generated>
+//------------------------------------------------------------------------------
+
+using System;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.ComponentModel;
+using System.ComponentModel.DataAnnotations;
+using System.ComponentModel.DataAnnotations.Schema;
+using System.Linq;
+using System.Runtime.CompilerServices;
+
+namespace Jellyfin.Data.Entities
+{
+ public partial class Artwork
+ {
+ partial void Init();
+
+ /// <summary>
+ /// Default constructor. Protected due to required properties, but present because EF needs it.
+ /// </summary>
+ protected Artwork()
+ {
+ Init();
+ }
+
+ /// <summary>
+ /// Replaces default constructor, since it's protected. Caller assumes responsibility for setting all required values before saving.
+ /// </summary>
+ public static Artwork CreateArtworkUnsafe()
+ {
+ return new Artwork();
+ }
+
+ /// <summary>
+ /// Public constructor with required data
+ /// </summary>
+ /// <param name="path"></param>
+ /// <param name="kind"></param>
+ /// <param name="_metadata0"></param>
+ /// <param name="_personrole1"></param>
+ public Artwork(string path, global::Jellyfin.Data.Enums.ArtKind kind, global::Jellyfin.Data.Entities.Metadata _metadata0, global::Jellyfin.Data.Entities.PersonRole _personrole1)
+ {
+ if (string.IsNullOrEmpty(path)) throw new ArgumentNullException(nameof(path));
+ this.Path = path;
+
+ this.Kind = kind;
+
+ if (_metadata0 == null) throw new ArgumentNullException(nameof(_metadata0));
+ _metadata0.Artwork.Add(this);
+
+ if (_personrole1 == null) throw new ArgumentNullException(nameof(_personrole1));
+ _personrole1.Artwork = this;
+
+
+ Init();
+ }
+
+ /// <summary>
+ /// Static create function (for use in LINQ queries, etc.)
+ /// </summary>
+ /// <param name="path"></param>
+ /// <param name="kind"></param>
+ /// <param name="_metadata0"></param>
+ /// <param name="_personrole1"></param>
+ public static Artwork Create(string path, global::Jellyfin.Data.Enums.ArtKind kind, global::Jellyfin.Data.Entities.Metadata _metadata0, global::Jellyfin.Data.Entities.PersonRole _personrole1)
+ {
+ return new Artwork(path, kind, _metadata0, _personrole1);
+ }
+
+ /*************************************************************************
+ * Properties
+ *************************************************************************/
+
+ /// <summary>
+ /// Backing field for Id
+ /// </summary>
+ internal int _Id;
+ /// <summary>
+ /// When provided in a partial class, allows value of Id to be changed before setting.
+ /// </summary>
+ partial void SetId(int oldValue, ref int newValue);
+ /// <summary>
+ /// When provided in a partial class, allows value of Id to be changed before returning.
+ /// </summary>
+ partial void GetId(ref int result);
+
+ /// <summary>
+ /// Identity, Indexed, Required
+ /// </summary>
+ [Key]
+ [Required]
+ public int Id
+ {
+ get
+ {
+ int value = _Id;
+ GetId(ref value);
+ return (_Id = value);
+ }
+ protected set
+ {
+ int oldValue = _Id;
+ SetId(oldValue, ref value);
+ if (oldValue != value)
+ {
+ _Id = value;
+ }
+ }
+ }
+
+ /// <summary>
+ /// Backing field for Path
+ /// </summary>
+ protected string _Path;
+ /// <summary>
+ /// When provided in a partial class, allows value of Path to be changed before setting.
+ /// </summary>
+ partial void SetPath(string oldValue, ref string newValue);
+ /// <summary>
+ /// When provided in a partial class, allows value of Path to be changed before returning.
+ /// </summary>
+ partial void GetPath(ref string result);
+
+ /// <summary>
+ /// Required, Max length = 65535
+ /// </summary>
+ [Required]
+ [MaxLength(65535)]
+ [StringLength(65535)]
+ public string Path
+ {
+ get
+ {
+ string value = _Path;
+ GetPath(ref value);
+ return (_Path = value);
+ }
+ set
+ {
+ string oldValue = _Path;
+ SetPath(oldValue, ref value);
+ if (oldValue != value)
+ {
+ _Path = value;
+ }
+ }
+ }
+
+ /// <summary>
+ /// Backing field for Kind
+ /// </summary>
+ internal global::Jellyfin.Data.Enums.ArtKind _Kind;
+ /// <summary>
+ /// When provided in a partial class, allows value of Kind to be changed before setting.
+ /// </summary>
+ partial void SetKind(global::Jellyfin.Data.Enums.ArtKind oldValue, ref global::Jellyfin.Data.Enums.ArtKind newValue);
+ /// <summary>
+ /// When provided in a partial class, allows value of Kind to be changed before returning.
+ /// </summary>
+ partial void GetKind(ref global::Jellyfin.Data.Enums.ArtKind result);
+
+ /// <summary>
+ /// Indexed, Required
+ /// </summary>
+ [Required]
+ public global::Jellyfin.Data.Enums.ArtKind Kind
+ {
+ get
+ {
+ global::Jellyfin.Data.Enums.ArtKind value = _Kind;
+ GetKind(ref value);
+ return (_Kind = value);
+ }
+ set
+ {
+ global::Jellyfin.Data.Enums.ArtKind oldValue = _Kind;
+ SetKind(oldValue, ref value);
+ if (oldValue != value)
+ {
+ _Kind = value;
+ }
+ }
+ }
+
+ /// <summary>
+ /// Required
+ /// </summary>
+ [ConcurrencyCheck]
+ [Required]
+ public byte[] Timestamp { get; set; }
+
+ /*************************************************************************
+ * Navigation properties
+ *************************************************************************/
+
+ }
+}
+
diff --git a/Jellyfin.Data/Entities/Book.cs b/Jellyfin.Data/Entities/Book.cs
new file mode 100644
index 000000000..30c89ae5c
--- /dev/null
+++ b/Jellyfin.Data/Entities/Book.cs
@@ -0,0 +1,84 @@
+//------------------------------------------------------------------------------
+// <auto-generated>
+// This code was generated from a template.
+//
+// Manual changes to this file may cause unexpected behavior in your application.
+// Manual changes to this file will be overwritten if the code is regenerated.
+//
+// Produced by Entity Framework Visual Editor
+// https://github.com/msawczyn/EFDesigner
+// </auto-generated>
+//------------------------------------------------------------------------------
+
+using System;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.ComponentModel;
+using System.ComponentModel.DataAnnotations;
+using System.ComponentModel.DataAnnotations.Schema;
+using System.Linq;
+using System.Runtime.CompilerServices;
+
+namespace Jellyfin.Data.Entities
+{
+ public partial class Book: global::Jellyfin.Data.Entities.LibraryItem
+ {
+ partial void Init();
+
+ /// <summary>
+ /// Default constructor. Protected due to required properties, but present because EF needs it.
+ /// </summary>
+ protected Book(): base()
+ {
+ BookMetadata = new System.Collections.Generic.HashSet<global::Jellyfin.Data.Entities.BookMetadata>();
+ Releases = new System.Collections.Generic.HashSet<global::Jellyfin.Data.Entities.Release>();
+
+ Init();
+ }
+
+ /// <summary>
+ /// Replaces default constructor, since it's protected. Caller assumes responsibility for setting all required values before saving.
+ /// </summary>
+ public static Book CreateBookUnsafe()
+ {
+ return new Book();
+ }
+
+ /// <summary>
+ /// Public constructor with required data
+ /// </summary>
+ /// <param name="urlid">This is whats gets displayed in the Urls and API requests. This could also be a string.</param>
+ public Book(Guid urlid, DateTime dateadded)
+ {
+ this.UrlId = urlid;
+
+ this.BookMetadata = new System.Collections.Generic.HashSet<global::Jellyfin.Data.Entities.BookMetadata>();
+ this.Releases = new System.Collections.Generic.HashSet<global::Jellyfin.Data.Entities.Release>();
+
+ Init();
+ }
+
+ /// <summary>
+ /// Static create function (for use in LINQ queries, etc.)
+ /// </summary>
+ /// <param name="urlid">This is whats gets displayed in the Urls and API requests. This could also be a string.</param>
+ public static Book Create(Guid urlid, DateTime dateadded)
+ {
+ return new Book(urlid, dateadded);
+ }
+
+ /*************************************************************************
+ * Properties
+ *************************************************************************/
+
+ /*************************************************************************
+ * Navigation properties
+ *************************************************************************/
+
+ public virtual ICollection<global::Jellyfin.Data.Entities.BookMetadata> BookMetadata { get; protected set; }
+
+ public virtual ICollection<global::Jellyfin.Data.Entities.Release> Releases { get; protected set; }
+
+ }
+}
+
diff --git a/Jellyfin.Data/Entities/BookMetadata.cs b/Jellyfin.Data/Entities/BookMetadata.cs
new file mode 100644
index 000000000..3a28244d6
--- /dev/null
+++ b/Jellyfin.Data/Entities/BookMetadata.cs
@@ -0,0 +1,123 @@
+//------------------------------------------------------------------------------
+// <auto-generated>
+// This code was generated from a template.
+//
+// Manual changes to this file may cause unexpected behavior in your application.
+// Manual changes to this file will be overwritten if the code is regenerated.
+//
+// Produced by Entity Framework Visual Editor
+// https://github.com/msawczyn/EFDesigner
+// </auto-generated>
+//------------------------------------------------------------------------------
+
+using System;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.ComponentModel;
+using System.ComponentModel.DataAnnotations;
+using System.ComponentModel.DataAnnotations.Schema;
+using System.Linq;
+using System.Runtime.CompilerServices;
+
+namespace Jellyfin.Data.Entities
+{
+ public partial class BookMetadata: global::Jellyfin.Data.Entities.Metadata
+ {
+ partial void Init();
+
+ /// <summary>
+ /// Default constructor. Protected due to required properties, but present because EF needs it.
+ /// </summary>
+ protected BookMetadata(): base()
+ {
+ Publishers = new System.Collections.Generic.HashSet<global::Jellyfin.Data.Entities.Company>();
+
+ Init();
+ }
+
+ /// <summary>
+ /// Replaces default constructor, since it's protected. Caller assumes responsibility for setting all required values before saving.
+ /// </summary>
+ public static BookMetadata CreateBookMetadataUnsafe()
+ {
+ return new BookMetadata();
+ }
+
+ /// <summary>
+ /// Public constructor with required data
+ /// </summary>
+ /// <param name="title">The title or name of the object</param>
+ /// <param name="language">ISO-639-3 3-character language codes</param>
+ /// <param name="_book0"></param>
+ public BookMetadata(string title, string language, DateTime dateadded, DateTime datemodified, global::Jellyfin.Data.Entities.Book _book0)
+ {
+ if (string.IsNullOrEmpty(title)) throw new ArgumentNullException(nameof(title));
+ this.Title = title;
+
+ if (string.IsNullOrEmpty(language)) throw new ArgumentNullException(nameof(language));
+ this.Language = language;
+
+ if (_book0 == null) throw new ArgumentNullException(nameof(_book0));
+ _book0.BookMetadata.Add(this);
+
+ this.Publishers = new System.Collections.Generic.HashSet<global::Jellyfin.Data.Entities.Company>();
+
+ Init();
+ }
+
+ /// <summary>
+ /// Static create function (for use in LINQ queries, etc.)
+ /// </summary>
+ /// <param name="title">The title or name of the object</param>
+ /// <param name="language">ISO-639-3 3-character language codes</param>
+ /// <param name="_book0"></param>
+ public static BookMetadata Create(string title, string language, DateTime dateadded, DateTime datemodified, global::Jellyfin.Data.Entities.Book _book0)
+ {
+ return new BookMetadata(title, language, dateadded, datemodified, _book0);
+ }
+
+ /*************************************************************************
+ * Properties
+ *************************************************************************/
+
+ /// <summary>
+ /// Backing field for ISBN
+ /// </summary>
+ protected long? _ISBN;
+ /// <summary>
+ /// When provided in a partial class, allows value of ISBN to be changed before setting.
+ /// </summary>
+ partial void SetISBN(long? oldValue, ref long? newValue);
+ /// <summary>
+ /// When provided in a partial class, allows value of ISBN to be changed before returning.
+ /// </summary>
+ partial void GetISBN(ref long? result);
+
+ public long? ISBN
+ {
+ get
+ {
+ long? value = _ISBN;
+ GetISBN(ref value);
+ return (_ISBN = value);
+ }
+ set
+ {
+ long? oldValue = _ISBN;
+ SetISBN(oldValue, ref value);
+ if (oldValue != value)
+ {
+ _ISBN = value;
+ }
+ }
+ }
+
+ /*************************************************************************
+ * Navigation properties
+ *************************************************************************/
+
+ public virtual ICollection<global::Jellyfin.Data.Entities.Company> Publishers { get; protected set; }
+
+ }
+}
+
diff --git a/Jellyfin.Data/Entities/Chapter.cs b/Jellyfin.Data/Entities/Chapter.cs
new file mode 100644
index 000000000..21a5dd73e
--- /dev/null
+++ b/Jellyfin.Data/Entities/Chapter.cs
@@ -0,0 +1,274 @@
+//------------------------------------------------------------------------------
+// <auto-generated>
+// This code was generated from a template.
+//
+// Manual changes to this file may cause unexpected behavior in your application.
+// Manual changes to this file will be overwritten if the code is regenerated.
+//
+// Produced by Entity Framework Visual Editor
+// https://github.com/msawczyn/EFDesigner
+// </auto-generated>
+//------------------------------------------------------------------------------
+
+using System;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.ComponentModel;
+using System.ComponentModel.DataAnnotations;
+using System.ComponentModel.DataAnnotations.Schema;
+using System.Linq;
+using System.Runtime.CompilerServices;
+
+namespace Jellyfin.Data.Entities
+{
+ public partial class Chapter
+ {
+ partial void Init();
+
+ /// <summary>
+ /// Default constructor. Protected due to required properties, but present because EF needs it.
+ /// </summary>
+ protected Chapter()
+ {
+ Init();
+ }
+
+ /// <summary>
+ /// Replaces default constructor, since it's protected. Caller assumes responsibility for setting all required values before saving.
+ /// </summary>
+ public static Chapter CreateChapterUnsafe()
+ {
+ return new Chapter();
+ }
+
+ /// <summary>
+ /// Public constructor with required data
+ /// </summary>
+ /// <param name="language">ISO-639-3 3-character language codes</param>
+ /// <param name="timestart"></param>
+ /// <param name="_release0"></param>
+ public Chapter(string language, long timestart, global::Jellyfin.Data.Entities.Release _release0)
+ {
+ if (string.IsNullOrEmpty(language)) throw new ArgumentNullException(nameof(language));
+ this.Language = language;
+
+ this.TimeStart = timestart;
+
+ if (_release0 == null) throw new ArgumentNullException(nameof(_release0));
+ _release0.Chapters.Add(this);
+
+
+ Init();
+ }
+
+ /// <summary>
+ /// Static create function (for use in LINQ queries, etc.)
+ /// </summary>
+ /// <param name="language">ISO-639-3 3-character language codes</param>
+ /// <param name="timestart"></param>
+ /// <param name="_release0"></param>
+ public static Chapter Create(string language, long timestart, global::Jellyfin.Data.Entities.Release _release0)
+ {
+ return new Chapter(language, timestart, _release0);
+ }
+
+ /*************************************************************************
+ * Properties
+ *************************************************************************/
+
+ /// <summary>
+ /// Backing field for Id
+ /// </summary>
+ internal int _Id;
+ /// <summary>
+ /// When provided in a partial class, allows value of Id to be changed before setting.
+ /// </summary>
+ partial void SetId(int oldValue, ref int newValue);
+ /// <summary>
+ /// When provided in a partial class, allows value of Id to be changed before returning.
+ /// </summary>
+ partial void GetId(ref int result);
+
+ /// <summary>
+ /// Identity, Indexed, Required
+ /// </summary>
+ [Key]
+ [Required]
+ public int Id
+ {
+ get
+ {
+ int value = _Id;
+ GetId(ref value);
+ return (_Id = value);
+ }
+ protected set
+ {
+ int oldValue = _Id;
+ SetId(oldValue, ref value);
+ if (oldValue != value)
+ {
+ _Id = value;
+ }
+ }
+ }
+
+ /// <summary>
+ /// Backing field for Name
+ /// </summary>
+ protected string _Name;
+ /// <summary>
+ /// When provided in a partial class, allows value of Name to be changed before setting.
+ /// </summary>
+ partial void SetName(string oldValue, ref string newValue);
+ /// <summary>
+ /// When provided in a partial class, allows value of Name to be changed before returning.
+ /// </summary>
+ partial void GetName(ref string result);
+
+ /// <summary>
+ /// Max length = 1024
+ /// </summary>
+ [MaxLength(1024)]
+ [StringLength(1024)]
+ public string Name
+ {
+ get
+ {
+ string value = _Name;
+ GetName(ref value);
+ return (_Name = value);
+ }
+ set
+ {
+ string oldValue = _Name;
+ SetName(oldValue, ref value);
+ if (oldValue != value)
+ {
+ _Name = value;
+ }
+ }
+ }
+
+ /// <summary>
+ /// Backing field for Language
+ /// </summary>
+ protected string _Language;
+ /// <summary>
+ /// When provided in a partial class, allows value of Language to be changed before setting.
+ /// </summary>
+ partial void SetLanguage(string oldValue, ref string newValue);
+ /// <summary>
+ /// When provided in a partial class, allows value of Language to be changed before returning.
+ /// </summary>
+ partial void GetLanguage(ref string result);
+
+ /// <summary>
+ /// Required, Min length = 3, Max length = 3
+ /// ISO-639-3 3-character language codes
+ /// </summary>
+ [Required]
+ [MinLength(3)]
+ [MaxLength(3)]
+ [StringLength(3)]
+ public string Language
+ {
+ get
+ {
+ string value = _Language;
+ GetLanguage(ref value);
+ return (_Language = value);
+ }
+ set
+ {
+ string oldValue = _Language;
+ SetLanguage(oldValue, ref value);
+ if (oldValue != value)
+ {
+ _Language = value;
+ }
+ }
+ }
+
+ /// <summary>
+ /// Backing field for TimeStart
+ /// </summary>
+ protected long _TimeStart;
+ /// <summary>
+ /// When provided in a partial class, allows value of TimeStart to be changed before setting.
+ /// </summary>
+ partial void SetTimeStart(long oldValue, ref long newValue);
+ /// <summary>
+ /// When provided in a partial class, allows value of TimeStart to be changed before returning.
+ /// </summary>
+ partial void GetTimeStart(ref long result);
+
+ /// <summary>
+ /// Required
+ /// </summary>
+ [Required]
+ public long TimeStart
+ {
+ get
+ {
+ long value = _TimeStart;
+ GetTimeStart(ref value);
+ return (_TimeStart = value);
+ }
+ set
+ {
+ long oldValue = _TimeStart;
+ SetTimeStart(oldValue, ref value);
+ if (oldValue != value)
+ {
+ _TimeStart = value;
+ }
+ }
+ }
+
+ /// <summary>
+ /// Backing field for TimeEnd
+ /// </summary>
+ protected long? _TimeEnd;
+ /// <summary>
+ /// When provided in a partial class, allows value of TimeEnd to be changed before setting.
+ /// </summary>
+ partial void SetTimeEnd(long? oldValue, ref long? newValue);
+ /// <summary>
+ /// When provided in a partial class, allows value of TimeEnd to be changed before returning.
+ /// </summary>
+ partial void GetTimeEnd(ref long? result);
+
+ public long? TimeEnd
+ {
+ get
+ {
+ long? value = _TimeEnd;
+ GetTimeEnd(ref value);
+ return (_TimeEnd = value);
+ }
+ set
+ {
+ long? oldValue = _TimeEnd;
+ SetTimeEnd(oldValue, ref value);
+ if (oldValue != value)
+ {
+ _TimeEnd = value;
+ }
+ }
+ }
+
+ /// <summary>
+ /// Required
+ /// </summary>
+ [ConcurrencyCheck]
+ [Required]
+ public byte[] Timestamp { get; set; }
+
+ /*************************************************************************
+ * Navigation properties
+ *************************************************************************/
+
+ }
+}
+
diff --git a/Jellyfin.Data/Entities/Collection.cs b/Jellyfin.Data/Entities/Collection.cs
new file mode 100644
index 000000000..68979eb2f
--- /dev/null
+++ b/Jellyfin.Data/Entities/Collection.cs
@@ -0,0 +1,131 @@
+//------------------------------------------------------------------------------
+// <auto-generated>
+// This code was generated from a template.
+//
+// Manual changes to this file may cause unexpected behavior in your application.
+// Manual changes to this file will be overwritten if the code is regenerated.
+//
+// Produced by Entity Framework Visual Editor
+// https://github.com/msawczyn/EFDesigner
+// </auto-generated>
+//------------------------------------------------------------------------------
+
+using System;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.ComponentModel;
+using System.ComponentModel.DataAnnotations;
+using System.ComponentModel.DataAnnotations.Schema;
+using System.Linq;
+using System.Runtime.CompilerServices;
+
+namespace Jellyfin.Data.Entities
+{
+ public partial class Collection
+ {
+ partial void Init();
+
+ /// <summary>
+ /// Default constructor
+ /// </summary>
+ public Collection()
+ {
+ CollectionItem = new System.Collections.Generic.LinkedList<global::Jellyfin.Data.Entities.CollectionItem>();
+
+ Init();
+ }
+
+ /*************************************************************************
+ * Properties
+ *************************************************************************/
+
+ /// <summary>
+ /// Backing field for Id
+ /// </summary>
+ internal int _Id;
+ /// <summary>
+ /// When provided in a partial class, allows value of Id to be changed before setting.
+ /// </summary>
+ partial void SetId(int oldValue, ref int newValue);
+ /// <summary>
+ /// When provided in a partial class, allows value of Id to be changed before returning.
+ /// </summary>
+ partial void GetId(ref int result);
+
+ /// <summary>
+ /// Identity, Indexed, Required
+ /// </summary>
+ [Key]
+ [Required]
+ public int Id
+ {
+ get
+ {
+ int value = _Id;
+ GetId(ref value);
+ return (_Id = value);
+ }
+ protected set
+ {
+ int oldValue = _Id;
+ SetId(oldValue, ref value);
+ if (oldValue != value)
+ {
+ _Id = value;
+ }
+ }
+ }
+
+ /// <summary>
+ /// Backing field for Name
+ /// </summary>
+ protected string _Name;
+ /// <summary>
+ /// When provided in a partial class, allows value of Name to be changed before setting.
+ /// </summary>
+ partial void SetName(string oldValue, ref string newValue);
+ /// <summary>
+ /// When provided in a partial class, allows value of Name to be changed before returning.
+ /// </summary>
+ partial void GetName(ref string result);
+
+ /// <summary>
+ /// Max length = 1024
+ /// </summary>
+ [MaxLength(1024)]
+ [StringLength(1024)]
+ public string Name
+ {
+ get
+ {
+ string value = _Name;
+ GetName(ref value);
+ return (_Name = value);
+ }
+ set
+ {
+ string oldValue = _Name;
+ SetName(oldValue, ref value);
+ if (oldValue != value)
+ {
+ _Name = value;
+ }
+ }
+ }
+
+ /// <summary>
+ /// Required
+ /// </summary>
+ [ConcurrencyCheck]
+ [Required]
+ public byte[] Timestamp { get; set; }
+
+ /*************************************************************************
+ * Navigation properties
+ *************************************************************************/
+
+ public virtual ICollection<global::Jellyfin.Data.Entities.CollectionItem> CollectionItem { get; protected set; }
+
+ }
+}
+
diff --git a/Jellyfin.Data/Entities/CollectionItem.cs b/Jellyfin.Data/Entities/CollectionItem.cs
new file mode 100644
index 000000000..8e575e0a2
--- /dev/null
+++ b/Jellyfin.Data/Entities/CollectionItem.cs
@@ -0,0 +1,151 @@
+//------------------------------------------------------------------------------
+// <auto-generated>
+// This code was generated from a template.
+//
+// Manual changes to this file may cause unexpected behavior in your application.
+// Manual changes to this file will be overwritten if the code is regenerated.
+//
+// Produced by Entity Framework Visual Editor
+// https://github.com/msawczyn/EFDesigner
+// </auto-generated>
+//------------------------------------------------------------------------------
+
+using System;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.ComponentModel;
+using System.ComponentModel.DataAnnotations;
+using System.ComponentModel.DataAnnotations.Schema;
+using System.Linq;
+using System.Runtime.CompilerServices;
+
+namespace Jellyfin.Data.Entities
+{
+ public partial class CollectionItem
+ {
+ partial void Init();
+
+ /// <summary>
+ /// Default constructor. Protected due to required properties, but present because EF needs it.
+ /// </summary>
+ protected CollectionItem()
+ {
+ // NOTE: This class has one-to-one associations with CollectionItem.
+ // One-to-one associations are not validated in constructors since this causes a scenario where each one must be constructed before the other.
+
+ Init();
+ }
+
+ /// <summary>
+ /// Replaces default constructor, since it's protected. Caller assumes responsibility for setting all required values before saving.
+ /// </summary>
+ public static CollectionItem CreateCollectionItemUnsafe()
+ {
+ return new CollectionItem();
+ }
+
+ /// <summary>
+ /// Public constructor with required data
+ /// </summary>
+ /// <param name="_collection0"></param>
+ /// <param name="_collectionitem1"></param>
+ /// <param name="_collectionitem2"></param>
+ public CollectionItem(global::Jellyfin.Data.Entities.Collection _collection0, global::Jellyfin.Data.Entities.CollectionItem _collectionitem1, global::Jellyfin.Data.Entities.CollectionItem _collectionitem2)
+ {
+ // NOTE: This class has one-to-one associations with CollectionItem.
+ // One-to-one associations are not validated in constructors since this causes a scenario where each one must be constructed before the other.
+
+ if (_collection0 == null) throw new ArgumentNullException(nameof(_collection0));
+ _collection0.CollectionItem.Add(this);
+
+ if (_collectionitem1 == null) throw new ArgumentNullException(nameof(_collectionitem1));
+ _collectionitem1.Next = this;
+
+ if (_collectionitem2 == null) throw new ArgumentNullException(nameof(_collectionitem2));
+ _collectionitem2.Previous = this;
+
+
+ Init();
+ }
+
+ /// <summary>
+ /// Static create function (for use in LINQ queries, etc.)
+ /// </summary>
+ /// <param name="_collection0"></param>
+ /// <param name="_collectionitem1"></param>
+ /// <param name="_collectionitem2"></param>
+ public static CollectionItem Create(global::Jellyfin.Data.Entities.Collection _collection0, global::Jellyfin.Data.Entities.CollectionItem _collectionitem1, global::Jellyfin.Data.Entities.CollectionItem _collectionitem2)
+ {
+ return new CollectionItem(_collection0, _collectionitem1, _collectionitem2);
+ }
+
+ /*************************************************************************
+ * Properties
+ *************************************************************************/
+
+ /// <summary>
+ /// Backing field for Id
+ /// </summary>
+ internal int _Id;
+ /// <summary>
+ /// When provided in a partial class, allows value of Id to be changed before setting.
+ /// </summary>
+ partial void SetId(int oldValue, ref int newValue);
+ /// <summary>
+ /// When provided in a partial class, allows value of Id to be changed before returning.
+ /// </summary>
+ partial void GetId(ref int result);
+
+ /// <summary>
+ /// Identity, Indexed, Required
+ /// </summary>
+ [Key]
+ [Required]
+ public int Id
+ {
+ get
+ {
+ int value = _Id;
+ GetId(ref value);
+ return (_Id = value);
+ }
+ protected set
+ {
+ int oldValue = _Id;
+ SetId(oldValue, ref value);
+ if (oldValue != value)
+ {
+ _Id = value;
+ }
+ }
+ }
+
+ /// <summary>
+ /// Required
+ /// </summary>
+ [ConcurrencyCheck]
+ [Required]
+ public byte[] Timestamp { get; set; }
+
+ /*************************************************************************
+ * Navigation properties
+ *************************************************************************/
+
+ /// <summary>
+ /// Required
+ /// </summary>
+ public virtual global::Jellyfin.Data.Entities.LibraryItem LibraryItem { get; set; }
+
+ /// <remarks>
+ /// TODO check if this properly updated dependant and has the proper principal relationship
+ /// </remarks>
+ public virtual global::Jellyfin.Data.Entities.CollectionItem Next { get; set; }
+
+ /// <remarks>
+ /// TODO check if this properly updated dependant and has the proper principal relationship
+ /// </remarks>
+ public virtual global::Jellyfin.Data.Entities.CollectionItem Previous { get; set; }
+
+ }
+}
+
diff --git a/Jellyfin.Data/Entities/Company.cs b/Jellyfin.Data/Entities/Company.cs
new file mode 100644
index 000000000..444ae9c56
--- /dev/null
+++ b/Jellyfin.Data/Entities/Company.cs
@@ -0,0 +1,147 @@
+//------------------------------------------------------------------------------
+// <auto-generated>
+// This code was generated from a template.
+//
+// Manual changes to this file may cause unexpected behavior in your application.
+// Manual changes to this file will be overwritten if the code is regenerated.
+//
+// Produced by Entity Framework Visual Editor
+// https://github.com/msawczyn/EFDesigner
+// </auto-generated>
+//------------------------------------------------------------------------------
+
+using System;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.ComponentModel;
+using System.ComponentModel.DataAnnotations;
+using System.ComponentModel.DataAnnotations.Schema;
+using System.Linq;
+using System.Runtime.CompilerServices;
+
+namespace Jellyfin.Data.Entities
+{
+ public partial class Company
+ {
+ partial void Init();
+
+ /// <summary>
+ /// Default constructor. Protected due to required properties, but present because EF needs it.
+ /// </summary>
+ protected Company()
+ {
+ CompanyMetadata = new System.Collections.Generic.HashSet<global::Jellyfin.Data.Entities.CompanyMetadata>();
+
+ Init();
+ }
+
+ /// <summary>
+ /// Replaces default constructor, since it's protected. Caller assumes responsibility for setting all required values before saving.
+ /// </summary>
+ public static Company CreateCompanyUnsafe()
+ {
+ return new Company();
+ }
+
+ /// <summary>
+ /// Public constructor with required data
+ /// </summary>
+ /// <param name="_moviemetadata0"></param>
+ /// <param name="_seriesmetadata1"></param>
+ /// <param name="_musicalbummetadata2"></param>
+ /// <param name="_bookmetadata3"></param>
+ /// <param name="_company4"></param>
+ public Company(global::Jellyfin.Data.Entities.MovieMetadata _moviemetadata0, global::Jellyfin.Data.Entities.SeriesMetadata _seriesmetadata1, global::Jellyfin.Data.Entities.MusicAlbumMetadata _musicalbummetadata2, global::Jellyfin.Data.Entities.BookMetadata _bookmetadata3, global::Jellyfin.Data.Entities.Company _company4)
+ {
+ if (_moviemetadata0 == null) throw new ArgumentNullException(nameof(_moviemetadata0));
+ _moviemetadata0.Studios.Add(this);
+
+ if (_seriesmetadata1 == null) throw new ArgumentNullException(nameof(_seriesmetadata1));
+ _seriesmetadata1.Networks.Add(this);
+
+ if (_musicalbummetadata2 == null) throw new ArgumentNullException(nameof(_musicalbummetadata2));
+ _musicalbummetadata2.Labels.Add(this);
+
+ if (_bookmetadata3 == null) throw new ArgumentNullException(nameof(_bookmetadata3));
+ _bookmetadata3.Publishers.Add(this);
+
+ if (_company4 == null) throw new ArgumentNullException(nameof(_company4));
+ _company4.Parent = this;
+
+ this.CompanyMetadata = new System.Collections.Generic.HashSet<global::Jellyfin.Data.Entities.CompanyMetadata>();
+
+ Init();
+ }
+
+ /// <summary>
+ /// Static create function (for use in LINQ queries, etc.)
+ /// </summary>
+ /// <param name="_moviemetadata0"></param>
+ /// <param name="_seriesmetadata1"></param>
+ /// <param name="_musicalbummetadata2"></param>
+ /// <param name="_bookmetadata3"></param>
+ /// <param name="_company4"></param>
+ public static Company Create(global::Jellyfin.Data.Entities.MovieMetadata _moviemetadata0, global::Jellyfin.Data.Entities.SeriesMetadata _seriesmetadata1, global::Jellyfin.Data.Entities.MusicAlbumMetadata _musicalbummetadata2, global::Jellyfin.Data.Entities.BookMetadata _bookmetadata3, global::Jellyfin.Data.Entities.Company _company4)
+ {
+ return new Company(_moviemetadata0, _seriesmetadata1, _musicalbummetadata2, _bookmetadata3, _company4);
+ }
+
+ /*************************************************************************
+ * Properties
+ *************************************************************************/
+
+ /// <summary>
+ /// Backing field for Id
+ /// </summary>
+ internal int _Id;
+ /// <summary>
+ /// When provided in a partial class, allows value of Id to be changed before setting.
+ /// </summary>
+ partial void SetId(int oldValue, ref int newValue);
+ /// <summary>
+ /// When provided in a partial class, allows value of Id to be changed before returning.
+ /// </summary>
+ partial void GetId(ref int result);
+
+ /// <summary>
+ /// Identity, Indexed, Required
+ /// </summary>
+ [Key]
+ [Required]
+ public int Id
+ {
+ get
+ {
+ int value = _Id;
+ GetId(ref value);
+ return (_Id = value);
+ }
+ protected set
+ {
+ int oldValue = _Id;
+ SetId(oldValue, ref value);
+ if (oldValue != value)
+ {
+ _Id = value;
+ }
+ }
+ }
+
+ /// <summary>
+ /// Required
+ /// </summary>
+ [ConcurrencyCheck]
+ [Required]
+ public byte[] Timestamp { get; set; }
+
+ /*************************************************************************
+ * Navigation properties
+ *************************************************************************/
+
+ public virtual ICollection<global::Jellyfin.Data.Entities.CompanyMetadata> CompanyMetadata { get; protected set; }
+
+ public virtual global::Jellyfin.Data.Entities.Company Parent { get; set; }
+
+ }
+}
+
diff --git a/Jellyfin.Data/Entities/CompanyMetadata.cs b/Jellyfin.Data/Entities/CompanyMetadata.cs
new file mode 100644
index 000000000..6d636e884
--- /dev/null
+++ b/Jellyfin.Data/Entities/CompanyMetadata.cs
@@ -0,0 +1,234 @@
+//------------------------------------------------------------------------------
+// <auto-generated>
+// This code was generated from a template.
+//
+// Manual changes to this file may cause unexpected behavior in your application.
+// Manual changes to this file will be overwritten if the code is regenerated.
+//
+// Produced by Entity Framework Visual Editor
+// https://github.com/msawczyn/EFDesigner
+// </auto-generated>
+//------------------------------------------------------------------------------
+
+using System;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.ComponentModel;
+using System.ComponentModel.DataAnnotations;
+using System.ComponentModel.DataAnnotations.Schema;
+using System.Linq;
+using System.Runtime.CompilerServices;
+
+namespace Jellyfin.Data.Entities
+{
+ public partial class CompanyMetadata: global::Jellyfin.Data.Entities.Metadata
+ {
+ partial void Init();
+
+ /// <summary>
+ /// Default constructor. Protected due to required properties, but present because EF needs it.
+ /// </summary>
+ protected CompanyMetadata(): base()
+ {
+ Init();
+ }
+
+ /// <summary>
+ /// Replaces default constructor, since it's protected. Caller assumes responsibility for setting all required values before saving.
+ /// </summary>
+ public static CompanyMetadata CreateCompanyMetadataUnsafe()
+ {
+ return new CompanyMetadata();
+ }
+
+ /// <summary>
+ /// Public constructor with required data
+ /// </summary>
+ /// <param name="title">The title or name of the object</param>
+ /// <param name="language">ISO-639-3 3-character language codes</param>
+ /// <param name="_company0"></param>
+ public CompanyMetadata(string title, string language, DateTime dateadded, DateTime datemodified, global::Jellyfin.Data.Entities.Company _company0)
+ {
+ if (string.IsNullOrEmpty(title)) throw new ArgumentNullException(nameof(title));
+ this.Title = title;
+
+ if (string.IsNullOrEmpty(language)) throw new ArgumentNullException(nameof(language));
+ this.Language = language;
+
+ if (_company0 == null) throw new ArgumentNullException(nameof(_company0));
+ _company0.CompanyMetadata.Add(this);
+
+
+ Init();
+ }
+
+ /// <summary>
+ /// Static create function (for use in LINQ queries, etc.)
+ /// </summary>
+ /// <param name="title">The title or name of the object</param>
+ /// <param name="language">ISO-639-3 3-character language codes</param>
+ /// <param name="_company0"></param>
+ public static CompanyMetadata Create(string title, string language, DateTime dateadded, DateTime datemodified, global::Jellyfin.Data.Entities.Company _company0)
+ {
+ return new CompanyMetadata(title, language, dateadded, datemodified, _company0);
+ }
+
+ /*************************************************************************
+ * Properties
+ *************************************************************************/
+
+ /// <summary>
+ /// Backing field for Description
+ /// </summary>
+ protected string _Description;
+ /// <summary>
+ /// When provided in a partial class, allows value of Description to be changed before setting.
+ /// </summary>
+ partial void SetDescription(string oldValue, ref string newValue);
+ /// <summary>
+ /// When provided in a partial class, allows value of Description to be changed before returning.
+ /// </summary>
+ partial void GetDescription(ref string result);
+
+ /// <summary>
+ /// Max length = 65535
+ /// </summary>
+ [MaxLength(65535)]
+ [StringLength(65535)]
+ public string Description
+ {
+ get
+ {
+ string value = _Description;
+ GetDescription(ref value);
+ return (_Description = value);
+ }
+ set
+ {
+ string oldValue = _Description;
+ SetDescription(oldValue, ref value);
+ if (oldValue != value)
+ {
+ _Description = value;
+ }
+ }
+ }
+
+ /// <summary>
+ /// Backing field for Headquarters
+ /// </summary>
+ protected string _Headquarters;
+ /// <summary>
+ /// When provided in a partial class, allows value of Headquarters to be changed before setting.
+ /// </summary>
+ partial void SetHeadquarters(string oldValue, ref string newValue);
+ /// <summary>
+ /// When provided in a partial class, allows value of Headquarters to be changed before returning.
+ /// </summary>
+ partial void GetHeadquarters(ref string result);
+
+ /// <summary>
+ /// Max length = 255
+ /// </summary>
+ [MaxLength(255)]
+ [StringLength(255)]
+ public string Headquarters
+ {
+ get
+ {
+ string value = _Headquarters;
+ GetHeadquarters(ref value);
+ return (_Headquarters = value);
+ }
+ set
+ {
+ string oldValue = _Headquarters;
+ SetHeadquarters(oldValue, ref value);
+ if (oldValue != value)
+ {
+ _Headquarters = value;
+ }
+ }
+ }
+
+ /// <summary>
+ /// Backing field for Country
+ /// </summary>
+ protected string _Country;
+ /// <summary>
+ /// When provided in a partial class, allows value of Country to be changed before setting.
+ /// </summary>
+ partial void SetCountry(string oldValue, ref string newValue);
+ /// <summary>
+ /// When provided in a partial class, allows value of Country to be changed before returning.
+ /// </summary>
+ partial void GetCountry(ref string result);
+
+ /// <summary>
+ /// Max length = 2
+ /// </summary>
+ [MaxLength(2)]
+ [StringLength(2)]
+ public string Country
+ {
+ get
+ {
+ string value = _Country;
+ GetCountry(ref value);
+ return (_Country = value);
+ }
+ set
+ {
+ string oldValue = _Country;
+ SetCountry(oldValue, ref value);
+ if (oldValue != value)
+ {
+ _Country = value;
+ }
+ }
+ }
+
+ /// <summary>
+ /// Backing field for Homepage
+ /// </summary>
+ protected string _Homepage;
+ /// <summary>
+ /// When provided in a partial class, allows value of Homepage to be changed before setting.
+ /// </summary>
+ partial void SetHomepage(string oldValue, ref string newValue);
+ /// <summary>
+ /// When provided in a partial class, allows value of Homepage to be changed before returning.
+ /// </summary>
+ partial void GetHomepage(ref string result);
+
+ /// <summary>
+ /// Max length = 1024
+ /// </summary>
+ [MaxLength(1024)]
+ [StringLength(1024)]
+ public string Homepage
+ {
+ get
+ {
+ string value = _Homepage;
+ GetHomepage(ref value);
+ return (_Homepage = value);
+ }
+ set
+ {
+ string oldValue = _Homepage;
+ SetHomepage(oldValue, ref value);
+ if (oldValue != value)
+ {
+ _Homepage = value;
+ }
+ }
+ }
+
+ /*************************************************************************
+ * Navigation properties
+ *************************************************************************/
+
+ }
+}
+
diff --git a/Jellyfin.Data/Entities/CustomItem.cs b/Jellyfin.Data/Entities/CustomItem.cs
new file mode 100644
index 000000000..eb6d2752d
--- /dev/null
+++ b/Jellyfin.Data/Entities/CustomItem.cs
@@ -0,0 +1,84 @@
+//------------------------------------------------------------------------------
+// <auto-generated>
+// This code was generated from a template.
+//
+// Manual changes to this file may cause unexpected behavior in your application.
+// Manual changes to this file will be overwritten if the code is regenerated.
+//
+// Produced by Entity Framework Visual Editor
+// https://github.com/msawczyn/EFDesigner
+// </auto-generated>
+//------------------------------------------------------------------------------
+
+using System;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.ComponentModel;
+using System.ComponentModel.DataAnnotations;
+using System.ComponentModel.DataAnnotations.Schema;
+using System.Linq;
+using System.Runtime.CompilerServices;
+
+namespace Jellyfin.Data.Entities
+{
+ public partial class CustomItem: global::Jellyfin.Data.Entities.LibraryItem
+ {
+ partial void Init();
+
+ /// <summary>
+ /// Default constructor. Protected due to required properties, but present because EF needs it.
+ /// </summary>
+ protected CustomItem(): base()
+ {
+ CustomItemMetadata = new System.Collections.Generic.HashSet<global::Jellyfin.Data.Entities.CustomItemMetadata>();
+ Releases = new System.Collections.Generic.HashSet<global::Jellyfin.Data.Entities.Release>();
+
+ Init();
+ }
+
+ /// <summary>
+ /// Replaces default constructor, since it's protected. Caller assumes responsibility for setting all required values before saving.
+ /// </summary>
+ public static CustomItem CreateCustomItemUnsafe()
+ {
+ return new CustomItem();
+ }
+
+ /// <summary>
+ /// Public constructor with required data
+ /// </summary>
+ /// <param name="urlid">This is whats gets displayed in the Urls and API requests. This could also be a string.</param>
+ public CustomItem(Guid urlid, DateTime dateadded)
+ {
+ this.UrlId = urlid;
+
+ this.CustomItemMetadata = new System.Collections.Generic.HashSet<global::Jellyfin.Data.Entities.CustomItemMetadata>();
+ this.Releases = new System.Collections.Generic.HashSet<global::Jellyfin.Data.Entities.Release>();
+
+ Init();
+ }
+
+ /// <summary>
+ /// Static create function (for use in LINQ queries, etc.)
+ /// </summary>
+ /// <param name="urlid">This is whats gets displayed in the Urls and API requests. This could also be a string.</param>
+ public static CustomItem Create(Guid urlid, DateTime dateadded)
+ {
+ return new CustomItem(urlid, dateadded);
+ }
+
+ /*************************************************************************
+ * Properties
+ *************************************************************************/
+
+ /*************************************************************************
+ * Navigation properties
+ *************************************************************************/
+
+ public virtual ICollection<global::Jellyfin.Data.Entities.CustomItemMetadata> CustomItemMetadata { get; protected set; }
+
+ public virtual ICollection<global::Jellyfin.Data.Entities.Release> Releases { get; protected set; }
+
+ }
+}
+
diff --git a/Jellyfin.Data/Entities/CustomItemMetadata.cs b/Jellyfin.Data/Entities/CustomItemMetadata.cs
new file mode 100644
index 000000000..f2c15d3fe
--- /dev/null
+++ b/Jellyfin.Data/Entities/CustomItemMetadata.cs
@@ -0,0 +1,86 @@
+//------------------------------------------------------------------------------
+// <auto-generated>
+// This code was generated from a template.
+//
+// Manual changes to this file may cause unexpected behavior in your application.
+// Manual changes to this file will be overwritten if the code is regenerated.
+//
+// Produced by Entity Framework Visual Editor
+// https://github.com/msawczyn/EFDesigner
+// </auto-generated>
+//------------------------------------------------------------------------------
+
+using System;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.ComponentModel;
+using System.ComponentModel.DataAnnotations;
+using System.ComponentModel.DataAnnotations.Schema;
+using System.Linq;
+using System.Runtime.CompilerServices;
+
+namespace Jellyfin.Data.Entities
+{
+ public partial class CustomItemMetadata: global::Jellyfin.Data.Entities.Metadata
+ {
+ partial void Init();
+
+ /// <summary>
+ /// Default constructor. Protected due to required properties, but present because EF needs it.
+ /// </summary>
+ protected CustomItemMetadata(): base()
+ {
+ Init();
+ }
+
+ /// <summary>
+ /// Replaces default constructor, since it's protected. Caller assumes responsibility for setting all required values before saving.
+ /// </summary>
+ public static CustomItemMetadata CreateCustomItemMetadataUnsafe()
+ {
+ return new CustomItemMetadata();
+ }
+
+ /// <summary>
+ /// Public constructor with required data
+ /// </summary>
+ /// <param name="title">The title or name of the object</param>
+ /// <param name="language">ISO-639-3 3-character language codes</param>
+ /// <param name="_customitem0"></param>
+ public CustomItemMetadata(string title, string language, DateTime dateadded, DateTime datemodified, global::Jellyfin.Data.Entities.CustomItem _customitem0)
+ {
+ if (string.IsNullOrEmpty(title)) throw new ArgumentNullException(nameof(title));
+ this.Title = title;
+
+ if (string.IsNullOrEmpty(language)) throw new ArgumentNullException(nameof(language));
+ this.Language = language;
+
+ if (_customitem0 == null) throw new ArgumentNullException(nameof(_customitem0));
+ _customitem0.CustomItemMetadata.Add(this);
+
+
+ Init();
+ }
+
+ /// <summary>
+ /// Static create function (for use in LINQ queries, etc.)
+ /// </summary>
+ /// <param name="title">The title or name of the object</param>
+ /// <param name="language">ISO-639-3 3-character language codes</param>
+ /// <param name="_customitem0"></param>
+ public static CustomItemMetadata Create(string title, string language, DateTime dateadded, DateTime datemodified, global::Jellyfin.Data.Entities.CustomItem _customitem0)
+ {
+ return new CustomItemMetadata(title, language, dateadded, datemodified, _customitem0);
+ }
+
+ /*************************************************************************
+ * Properties
+ *************************************************************************/
+
+ /*************************************************************************
+ * Navigation properties
+ *************************************************************************/
+
+ }
+}
+
diff --git a/Jellyfin.Data/Entities/Episode.cs b/Jellyfin.Data/Entities/Episode.cs
new file mode 100644
index 000000000..3a23f0976
--- /dev/null
+++ b/Jellyfin.Data/Entities/Episode.cs
@@ -0,0 +1,127 @@
+//------------------------------------------------------------------------------
+// <auto-generated>
+// This code was generated from a template.
+//
+// Manual changes to this file may cause unexpected behavior in your application.
+// Manual changes to this file will be overwritten if the code is regenerated.
+//
+// Produced by Entity Framework Visual Editor
+// https://github.com/msawczyn/EFDesigner
+// </auto-generated>
+//------------------------------------------------------------------------------
+
+using System;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.ComponentModel;
+using System.ComponentModel.DataAnnotations;
+using System.ComponentModel.DataAnnotations.Schema;
+using System.Linq;
+using System.Runtime.CompilerServices;
+
+namespace Jellyfin.Data.Entities
+{
+ public partial class Episode: global::Jellyfin.Data.Entities.LibraryItem
+ {
+ partial void Init();
+
+ /// <summary>
+ /// Default constructor. Protected due to required properties, but present because EF needs it.
+ /// </summary>
+ protected Episode(): base()
+ {
+ // NOTE: This class has one-to-one associations with LibraryRoot, LibraryItem and CollectionItem.
+ // One-to-one associations are not validated in constructors since this causes a scenario where each one must be constructed before the other.
+
+ Releases = new System.Collections.Generic.HashSet<global::Jellyfin.Data.Entities.Release>();
+ EpisodeMetadata = new System.Collections.Generic.HashSet<global::Jellyfin.Data.Entities.EpisodeMetadata>();
+
+ Init();
+ }
+
+ /// <summary>
+ /// Replaces default constructor, since it's protected. Caller assumes responsibility for setting all required values before saving.
+ /// </summary>
+ public static Episode CreateEpisodeUnsafe()
+ {
+ return new Episode();
+ }
+
+ /// <summary>
+ /// Public constructor with required data
+ /// </summary>
+ /// <param name="urlid">This is whats gets displayed in the Urls and API requests. This could also be a string.</param>
+ /// <param name="_season0"></param>
+ public Episode(Guid urlid, DateTime dateadded, global::Jellyfin.Data.Entities.Season _season0)
+ {
+ // NOTE: This class has one-to-one associations with LibraryRoot, LibraryItem and CollectionItem.
+ // One-to-one associations are not validated in constructors since this causes a scenario where each one must be constructed before the other.
+
+ this.UrlId = urlid;
+
+ if (_season0 == null) throw new ArgumentNullException(nameof(_season0));
+ _season0.Episodes.Add(this);
+
+ this.Releases = new System.Collections.Generic.HashSet<global::Jellyfin.Data.Entities.Release>();
+ this.EpisodeMetadata = new System.Collections.Generic.HashSet<global::Jellyfin.Data.Entities.EpisodeMetadata>();
+
+ Init();
+ }
+
+ /// <summary>
+ /// Static create function (for use in LINQ queries, etc.)
+ /// </summary>
+ /// <param name="urlid">This is whats gets displayed in the Urls and API requests. This could also be a string.</param>
+ /// <param name="_season0"></param>
+ public static Episode Create(Guid urlid, DateTime dateadded, global::Jellyfin.Data.Entities.Season _season0)
+ {
+ return new Episode(urlid, dateadded, _season0);
+ }
+
+ /*************************************************************************
+ * Properties
+ *************************************************************************/
+
+ /// <summary>
+ /// Backing field for EpisodeNumber
+ /// </summary>
+ protected int? _EpisodeNumber;
+ /// <summary>
+ /// When provided in a partial class, allows value of EpisodeNumber to be changed before setting.
+ /// </summary>
+ partial void SetEpisodeNumber(int? oldValue, ref int? newValue);
+ /// <summary>
+ /// When provided in a partial class, allows value of EpisodeNumber to be changed before returning.
+ /// </summary>
+ partial void GetEpisodeNumber(ref int? result);
+
+ public int? EpisodeNumber
+ {
+ get
+ {
+ int? value = _EpisodeNumber;
+ GetEpisodeNumber(ref value);
+ return (_EpisodeNumber = value);
+ }
+ set
+ {
+ int? oldValue = _EpisodeNumber;
+ SetEpisodeNumber(oldValue, ref value);
+ if (oldValue != value)
+ {
+ _EpisodeNumber = value;
+ }
+ }
+ }
+
+ /*************************************************************************
+ * Navigation properties
+ *************************************************************************/
+
+ public virtual ICollection<global::Jellyfin.Data.Entities.Release> Releases { get; protected set; }
+
+ public virtual ICollection<global::Jellyfin.Data.Entities.EpisodeMetadata> EpisodeMetadata { get; protected set; }
+
+ }
+}
+
diff --git a/Jellyfin.Data/Entities/EpisodeMetadata.cs b/Jellyfin.Data/Entities/EpisodeMetadata.cs
new file mode 100644
index 000000000..963219140
--- /dev/null
+++ b/Jellyfin.Data/Entities/EpisodeMetadata.cs
@@ -0,0 +1,197 @@
+//------------------------------------------------------------------------------
+// <auto-generated>
+// This code was generated from a template.
+//
+// Manual changes to this file may cause unexpected behavior in your application.
+// Manual changes to this file will be overwritten if the code is regenerated.
+//
+// Produced by Entity Framework Visual Editor
+// https://github.com/msawczyn/EFDesigner
+// </auto-generated>
+//------------------------------------------------------------------------------
+
+using System;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.ComponentModel;
+using System.ComponentModel.DataAnnotations;
+using System.ComponentModel.DataAnnotations.Schema;
+using System.Linq;
+using System.Runtime.CompilerServices;
+
+namespace Jellyfin.Data.Entities
+{
+ public partial class EpisodeMetadata: global::Jellyfin.Data.Entities.Metadata
+ {
+ partial void Init();
+
+ /// <summary>
+ /// Default constructor. Protected due to required properties, but present because EF needs it.
+ /// </summary>
+ protected EpisodeMetadata(): base()
+ {
+ Init();
+ }
+
+ /// <summary>
+ /// Replaces default constructor, since it's protected. Caller assumes responsibility for setting all required values before saving.
+ /// </summary>
+ public static EpisodeMetadata CreateEpisodeMetadataUnsafe()
+ {
+ return new EpisodeMetadata();
+ }
+
+ /// <summary>
+ /// Public constructor with required data
+ /// </summary>
+ /// <param name="title">The title or name of the object</param>
+ /// <param name="language">ISO-639-3 3-character language codes</param>
+ /// <param name="_episode0"></param>
+ public EpisodeMetadata(string title, string language, DateTime dateadded, DateTime datemodified, global::Jellyfin.Data.Entities.Episode _episode0)
+ {
+ if (string.IsNullOrEmpty(title)) throw new ArgumentNullException(nameof(title));
+ this.Title = title;
+
+ if (string.IsNullOrEmpty(language)) throw new ArgumentNullException(nameof(language));
+ this.Language = language;
+
+ if (_episode0 == null) throw new ArgumentNullException(nameof(_episode0));
+ _episode0.EpisodeMetadata.Add(this);
+
+
+ Init();
+ }
+
+ /// <summary>
+ /// Static create function (for use in LINQ queries, etc.)
+ /// </summary>
+ /// <param name="title">The title or name of the object</param>
+ /// <param name="language">ISO-639-3 3-character language codes</param>
+ /// <param name="_episode0"></param>
+ public static EpisodeMetadata Create(string title, string language, DateTime dateadded, DateTime datemodified, global::Jellyfin.Data.Entities.Episode _episode0)
+ {
+ return new EpisodeMetadata(title, language, dateadded, datemodified, _episode0);
+ }
+
+ /*************************************************************************
+ * Properties
+ *************************************************************************/
+
+ /// <summary>
+ /// Backing field for Outline
+ /// </summary>
+ protected string _Outline;
+ /// <summary>
+ /// When provided in a partial class, allows value of Outline to be changed before setting.
+ /// </summary>
+ partial void SetOutline(string oldValue, ref string newValue);
+ /// <summary>
+ /// When provided in a partial class, allows value of Outline to be changed before returning.
+ /// </summary>
+ partial void GetOutline(ref string result);
+
+ /// <summary>
+ /// Max length = 1024
+ /// </summary>
+ [MaxLength(1024)]
+ [StringLength(1024)]
+ public string Outline
+ {
+ get
+ {
+ string value = _Outline;
+ GetOutline(ref value);
+ return (_Outline = value);
+ }
+ set
+ {
+ string oldValue = _Outline;
+ SetOutline(oldValue, ref value);
+ if (oldValue != value)
+ {
+ _Outline = value;
+ }
+ }
+ }
+
+ /// <summary>
+ /// Backing field for Plot
+ /// </summary>
+ protected string _Plot;
+ /// <summary>
+ /// When provided in a partial class, allows value of Plot to be changed before setting.
+ /// </summary>
+ partial void SetPlot(string oldValue, ref string newValue);
+ /// <summary>
+ /// When provided in a partial class, allows value of Plot to be changed before returning.
+ /// </summary>
+ partial void GetPlot(ref string result);
+
+ /// <summary>
+ /// Max length = 65535
+ /// </summary>
+ [MaxLength(65535)]
+ [StringLength(65535)]
+ public string Plot
+ {
+ get
+ {
+ string value = _Plot;
+ GetPlot(ref value);
+ return (_Plot = value);
+ }
+ set
+ {
+ string oldValue = _Plot;
+ SetPlot(oldValue, ref value);
+ if (oldValue != value)
+ {
+ _Plot = value;
+ }
+ }
+ }
+
+ /// <summary>
+ /// Backing field for Tagline
+ /// </summary>
+ protected string _Tagline;
+ /// <summary>
+ /// When provided in a partial class, allows value of Tagline to be changed before setting.
+ /// </summary>
+ partial void SetTagline(string oldValue, ref string newValue);
+ /// <summary>
+ /// When provided in a partial class, allows value of Tagline to be changed before returning.
+ /// </summary>
+ partial void GetTagline(ref string result);
+
+ /// <summary>
+ /// Max length = 1024
+ /// </summary>
+ [MaxLength(1024)]
+ [StringLength(1024)]
+ public string Tagline
+ {
+ get
+ {
+ string value = _Tagline;
+ GetTagline(ref value);
+ return (_Tagline = value);
+ }
+ set
+ {
+ string oldValue = _Tagline;
+ SetTagline(oldValue, ref value);
+ if (oldValue != value)
+ {
+ _Tagline = value;
+ }
+ }
+ }
+
+ /*************************************************************************
+ * Navigation properties
+ *************************************************************************/
+
+ }
+}
+
diff --git a/Jellyfin.Data/Entities/Genre.cs b/Jellyfin.Data/Entities/Genre.cs
new file mode 100644
index 000000000..982600553
--- /dev/null
+++ b/Jellyfin.Data/Entities/Genre.cs
@@ -0,0 +1,163 @@
+//------------------------------------------------------------------------------
+// <auto-generated>
+// This code was generated from a template.
+//
+// Manual changes to this file may cause unexpected behavior in your application.
+// Manual changes to this file will be overwritten if the code is regenerated.
+//
+// Produced by Entity Framework Visual Editor
+// https://github.com/msawczyn/EFDesigner
+// </auto-generated>
+//------------------------------------------------------------------------------
+
+using System;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.ComponentModel;
+using System.ComponentModel.DataAnnotations;
+using System.ComponentModel.DataAnnotations.Schema;
+using System.Linq;
+using System.Runtime.CompilerServices;
+
+namespace Jellyfin.Data.Entities
+{
+ public partial class Genre
+ {
+ partial void Init();
+
+ /// <summary>
+ /// Default constructor. Protected due to required properties, but present because EF needs it.
+ /// </summary>
+ protected Genre()
+ {
+ Init();
+ }
+
+ /// <summary>
+ /// Replaces default constructor, since it's protected. Caller assumes responsibility for setting all required values before saving.
+ /// </summary>
+ public static Genre CreateGenreUnsafe()
+ {
+ return new Genre();
+ }
+
+ /// <summary>
+ /// Public constructor with required data
+ /// </summary>
+ /// <param name="name"></param>
+ /// <param name="_metadata0"></param>
+ public Genre(string name, global::Jellyfin.Data.Entities.Metadata _metadata0)
+ {
+ if (string.IsNullOrEmpty(name)) throw new ArgumentNullException(nameof(name));
+ this.Name = name;
+
+ if (_metadata0 == null) throw new ArgumentNullException(nameof(_metadata0));
+ _metadata0.Genres.Add(this);
+
+
+ Init();
+ }
+
+ /// <summary>
+ /// Static create function (for use in LINQ queries, etc.)
+ /// </summary>
+ /// <param name="name"></param>
+ /// <param name="_metadata0"></param>
+ public static Genre Create(string name, global::Jellyfin.Data.Entities.Metadata _metadata0)
+ {
+ return new Genre(name, _metadata0);
+ }
+
+ /*************************************************************************
+ * Properties
+ *************************************************************************/
+
+ /// <summary>
+ /// Backing field for Id
+ /// </summary>
+ internal int _Id;
+ /// <summary>
+ /// When provided in a partial class, allows value of Id to be changed before setting.
+ /// </summary>
+ partial void SetId(int oldValue, ref int newValue);
+ /// <summary>
+ /// When provided in a partial class, allows value of Id to be changed before returning.
+ /// </summary>
+ partial void GetId(ref int result);
+
+ /// <summary>
+ /// Identity, Indexed, Required
+ /// </summary>
+ [Key]
+ [Required]
+ public int Id
+ {
+ get
+ {
+ int value = _Id;
+ GetId(ref value);
+ return (_Id = value);
+ }
+ protected set
+ {
+ int oldValue = _Id;
+ SetId(oldValue, ref value);
+ if (oldValue != value)
+ {
+ _Id = value;
+ }
+ }
+ }
+
+ /// <summary>
+ /// Backing field for Name
+ /// </summary>
+ internal string _Name;
+ /// <summary>
+ /// When provided in a partial class, allows value of Name to be changed before setting.
+ /// </summary>
+ partial void SetName(string oldValue, ref string newValue);
+ /// <summary>
+ /// When provided in a partial class, allows value of Name to be changed before returning.
+ /// </summary>
+ partial void GetName(ref string result);
+
+ /// <summary>
+ /// Indexed, Required, Max length = 255
+ /// </summary>
+ [Required]
+ [MaxLength(255)]
+ [StringLength(255)]
+ public string Name
+ {
+ get
+ {
+ string value = _Name;
+ GetName(ref value);
+ return (_Name = value);
+ }
+ set
+ {
+ string oldValue = _Name;
+ SetName(oldValue, ref value);
+ if (oldValue != value)
+ {
+ _Name = value;
+ }
+ }
+ }
+
+ /// <summary>
+ /// Required
+ /// </summary>
+ [ConcurrencyCheck]
+ [Required]
+ public byte[] Timestamp { get; set; }
+
+ /*************************************************************************
+ * Navigation properties
+ *************************************************************************/
+
+ }
+}
+
diff --git a/Jellyfin.Data/Entities/Group.cs b/Jellyfin.Data/Entities/Group.cs
new file mode 100644
index 000000000..ff19e9b01
--- /dev/null
+++ b/Jellyfin.Data/Entities/Group.cs
@@ -0,0 +1,115 @@
+//------------------------------------------------------------------------------
+// <auto-generated>
+// This code was generated from a template.
+//
+// Manual changes to this file may cause unexpected behavior in your application.
+// Manual changes to this file will be overwritten if the code is regenerated.
+//
+// Produced by Entity Framework Visual Editor
+// https://github.com/msawczyn/EFDesigner
+// </auto-generated>
+//------------------------------------------------------------------------------
+
+using System;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.ComponentModel;
+using System.ComponentModel.DataAnnotations;
+using System.ComponentModel.DataAnnotations.Schema;
+using System.Linq;
+using System.Runtime.CompilerServices;
+
+namespace Jellyfin.Data.Entities
+{
+ public partial class Group
+ {
+ partial void Init();
+
+ /// <summary>
+ /// Default constructor. Protected due to required properties, but present because EF needs it.
+ /// </summary>
+ protected Group()
+ {
+ GroupPermissions = new System.Collections.Generic.HashSet<global::Jellyfin.Data.Entities.Permission>();
+ ProviderMappings = new System.Collections.Generic.HashSet<global::Jellyfin.Data.Entities.ProviderMapping>();
+ Preferences = new System.Collections.Generic.HashSet<global::Jellyfin.Data.Entities.Preference>();
+
+ Init();
+ }
+
+ /// <summary>
+ /// Replaces default constructor, since it's protected. Caller assumes responsibility for setting all required values before saving.
+ /// </summary>
+ public static Group CreateGroupUnsafe()
+ {
+ return new Group();
+ }
+
+ /// <summary>
+ /// Public constructor with required data
+ /// </summary>
+ /// <param name="name"></param>
+ /// <param name="_user0"></param>
+ public Group(string name, global::Jellyfin.Data.Entities.User _user0)
+ {
+ if (string.IsNullOrEmpty(name)) throw new ArgumentNullException(nameof(name));
+ this.Name = name;
+
+ if (_user0 == null) throw new ArgumentNullException(nameof(_user0));
+ _user0.Groups.Add(this);
+
+ this.GroupPermissions = new System.Collections.Generic.HashSet<global::Jellyfin.Data.Entities.Permission>();
+ this.ProviderMappings = new System.Collections.Generic.HashSet<global::Jellyfin.Data.Entities.ProviderMapping>();
+ this.Preferences = new System.Collections.Generic.HashSet<global::Jellyfin.Data.Entities.Preference>();
+
+ Init();
+ }
+
+ /// <summary>
+ /// Static create function (for use in LINQ queries, etc.)
+ /// </summary>
+ /// <param name="name"></param>
+ /// <param name="_user0"></param>
+ public static Group Create(string name, global::Jellyfin.Data.Entities.User _user0)
+ {
+ return new Group(name, _user0);
+ }
+
+ /*************************************************************************
+ * Properties
+ *************************************************************************/
+
+ /// <summary>
+ /// Identity, Indexed, Required
+ /// </summary>
+ [Key]
+ [Required]
+ public int Id { get; protected set; }
+
+ /// <summary>
+ /// Required, Max length = 255
+ /// </summary>
+ [Required]
+ [MaxLength(255)]
+ [StringLength(255)]
+ public string Name { get; set; }
+
+ /// <summary>
+ /// Concurrency token
+ /// </summary>
+ [Timestamp]
+ public Byte[] Timestamp { get; set; }
+
+ /*************************************************************************
+ * Navigation properties
+ *************************************************************************/
+
+ public virtual ICollection<global::Jellyfin.Data.Entities.Permission> GroupPermissions { get; protected set; }
+
+ public virtual ICollection<global::Jellyfin.Data.Entities.ProviderMapping> ProviderMappings { get; protected set; }
+
+ public virtual ICollection<global::Jellyfin.Data.Entities.Preference> Preferences { get; protected set; }
+
+ }
+}
+
diff --git a/Jellyfin.Data/Entities/Library.cs b/Jellyfin.Data/Entities/Library.cs
new file mode 100644
index 000000000..19ca14294
--- /dev/null
+++ b/Jellyfin.Data/Entities/Library.cs
@@ -0,0 +1,158 @@
+//------------------------------------------------------------------------------
+// <auto-generated>
+// This code was generated from a template.
+//
+// Manual changes to this file may cause unexpected behavior in your application.
+// Manual changes to this file will be overwritten if the code is regenerated.
+//
+// Produced by Entity Framework Visual Editor
+// https://github.com/msawczyn/EFDesigner
+// </auto-generated>
+//------------------------------------------------------------------------------
+
+using System;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.ComponentModel;
+using System.ComponentModel.DataAnnotations;
+using System.ComponentModel.DataAnnotations.Schema;
+using System.Linq;
+using System.Runtime.CompilerServices;
+
+namespace Jellyfin.Data.Entities
+{
+ public partial class Library
+ {
+ partial void Init();
+
+ /// <summary>
+ /// Default constructor. Protected due to required properties, but present because EF needs it.
+ /// </summary>
+ protected Library()
+ {
+ Init();
+ }
+
+ /// <summary>
+ /// Replaces default constructor, since it's protected. Caller assumes responsibility for setting all required values before saving.
+ /// </summary>
+ public static Library CreateLibraryUnsafe()
+ {
+ return new Library();
+ }
+
+ /// <summary>
+ /// Public constructor with required data
+ /// </summary>
+ /// <param name="name"></param>
+ public Library(string name)
+ {
+ if (string.IsNullOrEmpty(name)) throw new ArgumentNullException(nameof(name));
+ this.Name = name;
+
+
+ Init();
+ }
+
+ /// <summary>
+ /// Static create function (for use in LINQ queries, etc.)
+ /// </summary>
+ /// <param name="name"></param>
+ public static Library Create(string name)
+ {
+ return new Library(name);
+ }
+
+ /*************************************************************************
+ * Properties
+ *************************************************************************/
+
+ /// <summary>
+ /// Backing field for Id
+ /// </summary>
+ internal int _Id;
+ /// <summary>
+ /// When provided in a partial class, allows value of Id to be changed before setting.
+ /// </summary>
+ partial void SetId(int oldValue, ref int newValue);
+ /// <summary>
+ /// When provided in a partial class, allows value of Id to be changed before returning.
+ /// </summary>
+ partial void GetId(ref int result);
+
+ /// <summary>
+ /// Identity, Indexed, Required
+ /// </summary>
+ [Key]
+ [Required]
+ public int Id
+ {
+ get
+ {
+ int value = _Id;
+ GetId(ref value);
+ return (_Id = value);
+ }
+ protected set
+ {
+ int oldValue = _Id;
+ SetId(oldValue, ref value);
+ if (oldValue != value)
+ {
+ _Id = value;
+ }
+ }
+ }
+
+ /// <summary>
+ /// Backing field for Name
+ /// </summary>
+ protected string _Name;
+ /// <summary>
+ /// When provided in a partial class, allows value of Name to be changed before setting.
+ /// </summary>
+ partial void SetName(string oldValue, ref string newValue);
+ /// <summary>
+ /// When provided in a partial class, allows value of Name to be changed before returning.
+ /// </summary>
+ partial void GetName(ref string result);
+
+ /// <summary>
+ /// Required, Max length = 1024
+ /// </summary>
+ [Required]
+ [MaxLength(1024)]
+ [StringLength(1024)]
+ public string Name
+ {
+ get
+ {
+ string value = _Name;
+ GetName(ref value);
+ return (_Name = value);
+ }
+ set
+ {
+ string oldValue = _Name;
+ SetName(oldValue, ref value);
+ if (oldValue != value)
+ {
+ _Name = value;
+ }
+ }
+ }
+
+ /// <summary>
+ /// Required
+ /// </summary>
+ [ConcurrencyCheck]
+ [Required]
+ public byte[] Timestamp { get; set; }
+
+ /*************************************************************************
+ * Navigation properties
+ *************************************************************************/
+
+ }
+}
+
diff --git a/Jellyfin.Data/Entities/LibraryItem.cs b/Jellyfin.Data/Entities/LibraryItem.cs
new file mode 100644
index 000000000..1987196d6
--- /dev/null
+++ b/Jellyfin.Data/Entities/LibraryItem.cs
@@ -0,0 +1,180 @@
+//------------------------------------------------------------------------------
+// <auto-generated>
+// This code was generated from a template.
+//
+// Manual changes to this file may cause unexpected behavior in your application.
+// Manual changes to this file will be overwritten if the code is regenerated.
+//
+// Produced by Entity Framework Visual Editor
+// https://github.com/msawczyn/EFDesigner
+// </auto-generated>
+//------------------------------------------------------------------------------
+
+using System;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.ComponentModel;
+using System.ComponentModel.DataAnnotations;
+using System.ComponentModel.DataAnnotations.Schema;
+using System.Linq;
+using System.Runtime.CompilerServices;
+
+namespace Jellyfin.Data.Entities
+{
+ public abstract partial class LibraryItem
+ {
+ partial void Init();
+
+ /// <summary>
+ /// Default constructor. Protected due to being abstract.
+ /// </summary>
+ protected LibraryItem()
+ {
+ Init();
+ }
+
+ /// <summary>
+ /// Public constructor with required data
+ /// </summary>
+ /// <param name="urlid">This is whats gets displayed in the Urls and API requests. This could also be a string.</param>
+ protected LibraryItem(Guid urlid, DateTime dateadded)
+ {
+ this.UrlId = urlid;
+
+
+ Init();
+ }
+
+ /*************************************************************************
+ * Properties
+ *************************************************************************/
+
+ /// <summary>
+ /// Backing field for Id
+ /// </summary>
+ internal int _Id;
+ /// <summary>
+ /// When provided in a partial class, allows value of Id to be changed before setting.
+ /// </summary>
+ partial void SetId(int oldValue, ref int newValue);
+ /// <summary>
+ /// When provided in a partial class, allows value of Id to be changed before returning.
+ /// </summary>
+ partial void GetId(ref int result);
+
+ /// <summary>
+ /// Identity, Indexed, Required
+ /// </summary>
+ [Key]
+ [Required]
+ public int Id
+ {
+ get
+ {
+ int value = _Id;
+ GetId(ref value);
+ return (_Id = value);
+ }
+ protected set
+ {
+ int oldValue = _Id;
+ SetId(oldValue, ref value);
+ if (oldValue != value)
+ {
+ _Id = value;
+ }
+ }
+ }
+
+ /// <summary>
+ /// Backing field for UrlId
+ /// </summary>
+ internal Guid _UrlId;
+ /// <summary>
+ /// When provided in a partial class, allows value of UrlId to be changed before setting.
+ /// </summary>
+ partial void SetUrlId(Guid oldValue, ref Guid newValue);
+ /// <summary>
+ /// When provided in a partial class, allows value of UrlId to be changed before returning.
+ /// </summary>
+ partial void GetUrlId(ref Guid result);
+
+ /// <summary>
+ /// Indexed, Required
+ /// This is whats gets displayed in the Urls and API requests. This could also be a string.
+ /// </summary>
+ [Required]
+ public Guid UrlId
+ {
+ get
+ {
+ Guid value = _UrlId;
+ GetUrlId(ref value);
+ return (_UrlId = value);
+ }
+ set
+ {
+ Guid oldValue = _UrlId;
+ SetUrlId(oldValue, ref value);
+ if (oldValue != value)
+ {
+ _UrlId = value;
+ }
+ }
+ }
+
+ /// <summary>
+ /// Backing field for DateAdded
+ /// </summary>
+ protected DateTime _DateAdded;
+ /// <summary>
+ /// When provided in a partial class, allows value of DateAdded to be changed before setting.
+ /// </summary>
+ partial void SetDateAdded(DateTime oldValue, ref DateTime newValue);
+ /// <summary>
+ /// When provided in a partial class, allows value of DateAdded to be changed before returning.
+ /// </summary>
+ partial void GetDateAdded(ref DateTime result);
+
+ /// <summary>
+ /// Required
+ /// </summary>
+ [Required]
+ public DateTime DateAdded
+ {
+ get
+ {
+ DateTime value = _DateAdded;
+ GetDateAdded(ref value);
+ return (_DateAdded = value);
+ }
+ internal set
+ {
+ DateTime oldValue = _DateAdded;
+ SetDateAdded(oldValue, ref value);
+ if (oldValue != value)
+ {
+ _DateAdded = value;
+ }
+ }
+ }
+
+ /// <summary>
+ /// Required
+ /// </summary>
+ [ConcurrencyCheck]
+ [Required]
+ public byte[] Timestamp { get; set; }
+
+ /*************************************************************************
+ * Navigation properties
+ *************************************************************************/
+
+ /// <summary>
+ /// Required
+ /// </summary>
+ public virtual global::Jellyfin.Data.Entities.LibraryRoot LibraryRoot { get; set; }
+
+ }
+}
+
diff --git a/Jellyfin.Data/Entities/LibraryRoot.cs b/Jellyfin.Data/Entities/LibraryRoot.cs
new file mode 100644
index 000000000..015fc4ea9
--- /dev/null
+++ b/Jellyfin.Data/Entities/LibraryRoot.cs
@@ -0,0 +1,202 @@
+//------------------------------------------------------------------------------
+// <auto-generated>
+// This code was generated from a template.
+//
+// Manual changes to this file may cause unexpected behavior in your application.
+// Manual changes to this file will be overwritten if the code is regenerated.
+//
+// Produced by Entity Framework Visual Editor
+// https://github.com/msawczyn/EFDesigner
+// </auto-generated>
+//------------------------------------------------------------------------------
+
+using System;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.ComponentModel;
+using System.ComponentModel.DataAnnotations;
+using System.ComponentModel.DataAnnotations.Schema;
+using System.Linq;
+using System.Runtime.CompilerServices;
+
+namespace Jellyfin.Data.Entities
+{
+ public partial class LibraryRoot
+ {
+ partial void Init();
+
+ /// <summary>
+ /// Default constructor. Protected due to required properties, but present because EF needs it.
+ /// </summary>
+ protected LibraryRoot()
+ {
+ Init();
+ }
+
+ /// <summary>
+ /// Replaces default constructor, since it's protected. Caller assumes responsibility for setting all required values before saving.
+ /// </summary>
+ public static LibraryRoot CreateLibraryRootUnsafe()
+ {
+ return new LibraryRoot();
+ }
+
+ /// <summary>
+ /// Public constructor with required data
+ /// </summary>
+ /// <param name="path">Absolute Path</param>
+ public LibraryRoot(string path)
+ {
+ if (string.IsNullOrEmpty(path)) throw new ArgumentNullException(nameof(path));
+ this.Path = path;
+
+
+ Init();
+ }
+
+ /// <summary>
+ /// Static create function (for use in LINQ queries, etc.)
+ /// </summary>
+ /// <param name="path">Absolute Path</param>
+ public static LibraryRoot Create(string path)
+ {
+ return new LibraryRoot(path);
+ }
+
+ /*************************************************************************
+ * Properties
+ *************************************************************************/
+
+ /// <summary>
+ /// Backing field for Id
+ /// </summary>
+ internal int _Id;
+ /// <summary>
+ /// When provided in a partial class, allows value of Id to be changed before setting.
+ /// </summary>
+ partial void SetId(int oldValue, ref int newValue);
+ /// <summary>
+ /// When provided in a partial class, allows value of Id to be changed before returning.
+ /// </summary>
+ partial void GetId(ref int result);
+
+ /// <summary>
+ /// Identity, Indexed, Required
+ /// </summary>
+ [Key]
+ [Required]
+ public int Id
+ {
+ get
+ {
+ int value = _Id;
+ GetId(ref value);
+ return (_Id = value);
+ }
+ protected set
+ {
+ int oldValue = _Id;
+ SetId(oldValue, ref value);
+ if (oldValue != value)
+ {
+ _Id = value;
+ }
+ }
+ }
+
+ /// <summary>
+ /// Backing field for Path
+ /// </summary>
+ protected string _Path;
+ /// <summary>
+ /// When provided in a partial class, allows value of Path to be changed before setting.
+ /// </summary>
+ partial void SetPath(string oldValue, ref string newValue);
+ /// <summary>
+ /// When provided in a partial class, allows value of Path to be changed before returning.
+ /// </summary>
+ partial void GetPath(ref string result);
+
+ /// <summary>
+ /// Required, Max length = 65535
+ /// Absolute Path
+ /// </summary>
+ [Required]
+ [MaxLength(65535)]
+ [StringLength(65535)]
+ public string Path
+ {
+ get
+ {
+ string value = _Path;
+ GetPath(ref value);
+ return (_Path = value);
+ }
+ set
+ {
+ string oldValue = _Path;
+ SetPath(oldValue, ref value);
+ if (oldValue != value)
+ {
+ _Path = value;
+ }
+ }
+ }
+
+ /// <summary>
+ /// Backing field for NetworkPath
+ /// </summary>
+ protected string _NetworkPath;
+ /// <summary>
+ /// When provided in a partial class, allows value of NetworkPath to be changed before setting.
+ /// </summary>
+ partial void SetNetworkPath(string oldValue, ref string newValue);
+ /// <summary>
+ /// When provided in a partial class, allows value of NetworkPath to be changed before returning.
+ /// </summary>
+ partial void GetNetworkPath(ref string result);
+
+ /// <summary>
+ /// Max length = 65535
+ /// Absolute network path, for example for transcoding sattelites.
+ /// </summary>
+ [MaxLength(65535)]
+ [StringLength(65535)]
+ public string NetworkPath
+ {
+ get
+ {
+ string value = _NetworkPath;
+ GetNetworkPath(ref value);
+ return (_NetworkPath = value);
+ }
+ set
+ {
+ string oldValue = _NetworkPath;
+ SetNetworkPath(oldValue, ref value);
+ if (oldValue != value)
+ {
+ _NetworkPath = value;
+ }
+ }
+ }
+
+ /// <summary>
+ /// Required
+ /// </summary>
+ [ConcurrencyCheck]
+ [Required]
+ public byte[] Timestamp { get; set; }
+
+ /*************************************************************************
+ * Navigation properties
+ *************************************************************************/
+
+ /// <summary>
+ /// Required
+ /// </summary>
+ public virtual global::Jellyfin.Data.Entities.Library Library { get; set; }
+
+ }
+}
+
diff --git a/Jellyfin.Data/Entities/MediaFile.cs b/Jellyfin.Data/Entities/MediaFile.cs
new file mode 100644
index 000000000..2a47a9632
--- /dev/null
+++ b/Jellyfin.Data/Entities/MediaFile.cs
@@ -0,0 +1,209 @@
+//------------------------------------------------------------------------------
+// <auto-generated>
+// This code was generated from a template.
+//
+// Manual changes to this file may cause unexpected behavior in your application.
+// Manual changes to this file will be overwritten if the code is regenerated.
+//
+// Produced by Entity Framework Visual Editor
+// https://github.com/msawczyn/EFDesigner
+// </auto-generated>
+//------------------------------------------------------------------------------
+
+using System;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.ComponentModel;
+using System.ComponentModel.DataAnnotations;
+using System.ComponentModel.DataAnnotations.Schema;
+using System.Linq;
+using System.Runtime.CompilerServices;
+
+namespace Jellyfin.Data.Entities
+{
+ public partial class MediaFile
+ {
+ partial void Init();
+
+ /// <summary>
+ /// Default constructor. Protected due to required properties, but present because EF needs it.
+ /// </summary>
+ protected MediaFile()
+ {
+ MediaFileStreams = new System.Collections.Generic.HashSet<global::Jellyfin.Data.Entities.MediaFileStream>();
+
+ Init();
+ }
+
+ /// <summary>
+ /// Replaces default constructor, since it's protected. Caller assumes responsibility for setting all required values before saving.
+ /// </summary>
+ public static MediaFile CreateMediaFileUnsafe()
+ {
+ return new MediaFile();
+ }
+
+ /// <summary>
+ /// Public constructor with required data
+ /// </summary>
+ /// <param name="path">Relative to the LibraryRoot</param>
+ /// <param name="kind"></param>
+ /// <param name="_release0"></param>
+ public MediaFile(string path, global::Jellyfin.Data.Enums.MediaFileKind kind, global::Jellyfin.Data.Entities.Release _release0)
+ {
+ if (string.IsNullOrEmpty(path)) throw new ArgumentNullException(nameof(path));
+ this.Path = path;
+
+ this.Kind = kind;
+
+ if (_release0 == null) throw new ArgumentNullException(nameof(_release0));
+ _release0.MediaFiles.Add(this);
+
+ this.MediaFileStreams = new System.Collections.Generic.HashSet<global::Jellyfin.Data.Entities.MediaFileStream>();
+
+ Init();
+ }
+
+ /// <summary>
+ /// Static create function (for use in LINQ queries, etc.)
+ /// </summary>
+ /// <param name="path">Relative to the LibraryRoot</param>
+ /// <param name="kind"></param>
+ /// <param name="_release0"></param>
+ public static MediaFile Create(string path, global::Jellyfin.Data.Enums.MediaFileKind kind, global::Jellyfin.Data.Entities.Release _release0)
+ {
+ return new MediaFile(path, kind, _release0);
+ }
+
+ /*************************************************************************
+ * Properties
+ *************************************************************************/
+
+ /// <summary>
+ /// Backing field for Id
+ /// </summary>
+ internal int _Id;
+ /// <summary>
+ /// When provided in a partial class, allows value of Id to be changed before setting.
+ /// </summary>
+ partial void SetId(int oldValue, ref int newValue);
+ /// <summary>
+ /// When provided in a partial class, allows value of Id to be changed before returning.
+ /// </summary>
+ partial void GetId(ref int result);
+
+ /// <summary>
+ /// Identity, Indexed, Required
+ /// </summary>
+ [Key]
+ [Required]
+ public int Id
+ {
+ get
+ {
+ int value = _Id;
+ GetId(ref value);
+ return (_Id = value);
+ }
+ protected set
+ {
+ int oldValue = _Id;
+ SetId(oldValue, ref value);
+ if (oldValue != value)
+ {
+ _Id = value;
+ }
+ }
+ }
+
+ /// <summary>
+ /// Backing field for Path
+ /// </summary>
+ protected string _Path;
+ /// <summary>
+ /// When provided in a partial class, allows value of Path to be changed before setting.
+ /// </summary>
+ partial void SetPath(string oldValue, ref string newValue);
+ /// <summary>
+ /// When provided in a partial class, allows value of Path to be changed before returning.
+ /// </summary>
+ partial void GetPath(ref string result);
+
+ /// <summary>
+ /// Required, Max length = 65535
+ /// Relative to the LibraryRoot
+ /// </summary>
+ [Required]
+ [MaxLength(65535)]
+ [StringLength(65535)]
+ public string Path
+ {
+ get
+ {
+ string value = _Path;
+ GetPath(ref value);
+ return (_Path = value);
+ }
+ set
+ {
+ string oldValue = _Path;
+ SetPath(oldValue, ref value);
+ if (oldValue != value)
+ {
+ _Path = value;
+ }
+ }
+ }
+
+ /// <summary>
+ /// Backing field for Kind
+ /// </summary>
+ protected global::Jellyfin.Data.Enums.MediaFileKind _Kind;
+ /// <summary>
+ /// When provided in a partial class, allows value of Kind to be changed before setting.
+ /// </summary>
+ partial void SetKind(global::Jellyfin.Data.Enums.MediaFileKind oldValue, ref global::Jellyfin.Data.Enums.MediaFileKind newValue);
+ /// <summary>
+ /// When provided in a partial class, allows value of Kind to be changed before returning.
+ /// </summary>
+ partial void GetKind(ref global::Jellyfin.Data.Enums.MediaFileKind result);
+
+ /// <summary>
+ /// Required
+ /// </summary>
+ [Required]
+ public global::Jellyfin.Data.Enums.MediaFileKind Kind
+ {
+ get
+ {
+ global::Jellyfin.Data.Enums.MediaFileKind value = _Kind;
+ GetKind(ref value);
+ return (_Kind = value);
+ }
+ set
+ {
+ global::Jellyfin.Data.Enums.MediaFileKind oldValue = _Kind;
+ SetKind(oldValue, ref value);
+ if (oldValue != value)
+ {
+ _Kind = value;
+ }
+ }
+ }
+
+ /// <summary>
+ /// Required
+ /// </summary>
+ [ConcurrencyCheck]
+ [Required]
+ public byte[] Timestamp { get; set; }
+
+ /*************************************************************************
+ * Navigation properties
+ *************************************************************************/
+
+ public virtual ICollection<global::Jellyfin.Data.Entities.MediaFileStream> MediaFileStreams { get; protected set; }
+
+ }
+}
+
diff --git a/Jellyfin.Data/Entities/MediaFileStream.cs b/Jellyfin.Data/Entities/MediaFileStream.cs
new file mode 100644
index 000000000..6593d3cf7
--- /dev/null
+++ b/Jellyfin.Data/Entities/MediaFileStream.cs
@@ -0,0 +1,160 @@
+//------------------------------------------------------------------------------
+// <auto-generated>
+// This code was generated from a template.
+//
+// Manual changes to this file may cause unexpected behavior in your application.
+// Manual changes to this file will be overwritten if the code is regenerated.
+//
+// Produced by Entity Framework Visual Editor
+// https://github.com/msawczyn/EFDesigner
+// </auto-generated>
+//------------------------------------------------------------------------------
+
+using System;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.ComponentModel;
+using System.ComponentModel.DataAnnotations;
+using System.ComponentModel.DataAnnotations.Schema;
+using System.Linq;
+using System.Runtime.CompilerServices;
+
+namespace Jellyfin.Data.Entities
+{
+ public partial class MediaFileStream
+ {
+ partial void Init();
+
+ /// <summary>
+ /// Default constructor. Protected due to required properties, but present because EF needs it.
+ /// </summary>
+ protected MediaFileStream()
+ {
+ Init();
+ }
+
+ /// <summary>
+ /// Replaces default constructor, since it's protected. Caller assumes responsibility for setting all required values before saving.
+ /// </summary>
+ public static MediaFileStream CreateMediaFileStreamUnsafe()
+ {
+ return new MediaFileStream();
+ }
+
+ /// <summary>
+ /// Public constructor with required data
+ /// </summary>
+ /// <param name="streamnumber"></param>
+ /// <param name="_mediafile0"></param>
+ public MediaFileStream(int streamnumber, global::Jellyfin.Data.Entities.MediaFile _mediafile0)
+ {
+ this.StreamNumber = streamnumber;
+
+ if (_mediafile0 == null) throw new ArgumentNullException(nameof(_mediafile0));
+ _mediafile0.MediaFileStreams.Add(this);
+
+
+ Init();
+ }
+
+ /// <summary>
+ /// Static create function (for use in LINQ queries, etc.)
+ /// </summary>
+ /// <param name="streamnumber"></param>
+ /// <param name="_mediafile0"></param>
+ public static MediaFileStream Create(int streamnumber, global::Jellyfin.Data.Entities.MediaFile _mediafile0)
+ {
+ return new MediaFileStream(streamnumber, _mediafile0);
+ }
+
+ /*************************************************************************
+ * Properties
+ *************************************************************************/
+
+ /// <summary>
+ /// Backing field for Id
+ /// </summary>
+ internal int _Id;
+ /// <summary>
+ /// When provided in a partial class, allows value of Id to be changed before setting.
+ /// </summary>
+ partial void SetId(int oldValue, ref int newValue);
+ /// <summary>
+ /// When provided in a partial class, allows value of Id to be changed before returning.
+ /// </summary>
+ partial void GetId(ref int result);
+
+ /// <summary>
+ /// Identity, Indexed, Required
+ /// </summary>
+ [Key]
+ [Required]
+ public int Id
+ {
+ get
+ {
+ int value = _Id;
+ GetId(ref value);
+ return (_Id = value);
+ }
+ protected set
+ {
+ int oldValue = _Id;
+ SetId(oldValue, ref value);
+ if (oldValue != value)
+ {
+ _Id = value;
+ }
+ }
+ }
+
+ /// <summary>
+ /// Backing field for StreamNumber
+ /// </summary>
+ protected int _StreamNumber;
+ /// <summary>
+ /// When provided in a partial class, allows value of StreamNumber to be changed before setting.
+ /// </summary>
+ partial void SetStreamNumber(int oldValue, ref int newValue);
+ /// <summary>
+ /// When provided in a partial class, allows value of StreamNumber to be changed before returning.
+ /// </summary>
+ partial void GetStreamNumber(ref int result);
+
+ /// <summary>
+ /// Required
+ /// </summary>
+ [Required]
+ public int StreamNumber
+ {
+ get
+ {
+ int value = _StreamNumber;
+ GetStreamNumber(ref value);
+ return (_StreamNumber = value);
+ }
+ set
+ {
+ int oldValue = _StreamNumber;
+ SetStreamNumber(oldValue, ref value);
+ if (oldValue != value)
+ {
+ _StreamNumber = value;
+ }
+ }
+ }
+
+ /// <summary>
+ /// Required
+ /// </summary>
+ [ConcurrencyCheck]
+ [Required]
+ public byte[] Timestamp { get; set; }
+
+ /*************************************************************************
+ * Navigation properties
+ *************************************************************************/
+
+ }
+}
+
diff --git a/Jellyfin.Data/Entities/Metadata.cs b/Jellyfin.Data/Entities/Metadata.cs
new file mode 100644
index 000000000..6057017e9
--- /dev/null
+++ b/Jellyfin.Data/Entities/Metadata.cs
@@ -0,0 +1,385 @@
+//------------------------------------------------------------------------------
+// <auto-generated>
+// This code was generated from a template.
+//
+// Manual changes to this file may cause unexpected behavior in your application.
+// Manual changes to this file will be overwritten if the code is regenerated.
+//
+// Produced by Entity Framework Visual Editor
+// https://github.com/msawczyn/EFDesigner
+// </auto-generated>
+//------------------------------------------------------------------------------
+
+using System;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.ComponentModel;
+using System.ComponentModel.DataAnnotations;
+using System.ComponentModel.DataAnnotations.Schema;
+using System.Linq;
+using System.Runtime.CompilerServices;
+
+namespace Jellyfin.Data.Entities
+{
+ public abstract partial class Metadata
+ {
+ partial void Init();
+
+ /// <summary>
+ /// Default constructor. Protected due to being abstract.
+ /// </summary>
+ protected Metadata()
+ {
+ PersonRoles = new System.Collections.Generic.HashSet<global::Jellyfin.Data.Entities.PersonRole>();
+ Genres = new System.Collections.Generic.HashSet<global::Jellyfin.Data.Entities.Genre>();
+ Artwork = new System.Collections.Generic.HashSet<global::Jellyfin.Data.Entities.Artwork>();
+ Ratings = new System.Collections.Generic.HashSet<global::Jellyfin.Data.Entities.Rating>();
+ Sources = new System.Collections.Generic.HashSet<global::Jellyfin.Data.Entities.MetadataProviderId>();
+
+ Init();
+ }
+
+ /// <summary>
+ /// Public constructor with required data
+ /// </summary>
+ /// <param name="title">The title or name of the object</param>
+ /// <param name="language">ISO-639-3 3-character language codes</param>
+ protected Metadata(string title, string language, DateTime dateadded, DateTime datemodified)
+ {
+ if (string.IsNullOrEmpty(title)) throw new ArgumentNullException(nameof(title));
+ this.Title = title;
+
+ if (string.IsNullOrEmpty(language)) throw new ArgumentNullException(nameof(language));
+ this.Language = language;
+
+ this.PersonRoles = new System.Collections.Generic.HashSet<global::Jellyfin.Data.Entities.PersonRole>();
+ this.Genres = new System.Collections.Generic.HashSet<global::Jellyfin.Data.Entities.Genre>();
+ this.Artwork = new System.Collections.Generic.HashSet<global::Jellyfin.Data.Entities.Artwork>();
+ this.Ratings = new System.Collections.Generic.HashSet<global::Jellyfin.Data.Entities.Rating>();
+ this.Sources = new System.Collections.Generic.HashSet<global::Jellyfin.Data.Entities.MetadataProviderId>();
+
+ Init();
+ }
+
+ /*************************************************************************
+ * Properties
+ *************************************************************************/
+
+ /// <summary>
+ /// Backing field for Id
+ /// </summary>
+ internal int _Id;
+ /// <summary>
+ /// When provided in a partial class, allows value of Id to be changed before setting.
+ /// </summary>
+ partial void SetId(int oldValue, ref int newValue);
+ /// <summary>
+ /// When provided in a partial class, allows value of Id to be changed before returning.
+ /// </summary>
+ partial void GetId(ref int result);
+
+ /// <summary>
+ /// Identity, Indexed, Required
+ /// </summary>
+ [Key]
+ [Required]
+ public int Id
+ {
+ get
+ {
+ int value = _Id;
+ GetId(ref value);
+ return (_Id = value);
+ }
+ protected set
+ {
+ int oldValue = _Id;
+ SetId(oldValue, ref value);
+ if (oldValue != value)
+ {
+ _Id = value;
+ }
+ }
+ }
+
+ /// <summary>
+ /// Backing field for Title
+ /// </summary>
+ protected string _Title;
+ /// <summary>
+ /// When provided in a partial class, allows value of Title to be changed before setting.
+ /// </summary>
+ partial void SetTitle(string oldValue, ref string newValue);
+ /// <summary>
+ /// When provided in a partial class, allows value of Title to be changed before returning.
+ /// </summary>
+ partial void GetTitle(ref string result);
+
+ /// <summary>
+ /// Required, Max length = 1024
+ /// The title or name of the object
+ /// </summary>
+ [Required]
+ [MaxLength(1024)]
+ [StringLength(1024)]
+ public string Title
+ {
+ get
+ {
+ string value = _Title;
+ GetTitle(ref value);
+ return (_Title = value);
+ }
+ set
+ {
+ string oldValue = _Title;
+ SetTitle(oldValue, ref value);
+ if (oldValue != value)
+ {
+ _Title = value;
+ }
+ }
+ }
+
+ /// <summary>
+ /// Backing field for OriginalTitle
+ /// </summary>
+ protected string _OriginalTitle;
+ /// <summary>
+ /// When provided in a partial class, allows value of OriginalTitle to be changed before setting.
+ /// </summary>
+ partial void SetOriginalTitle(string oldValue, ref string newValue);
+ /// <summary>
+ /// When provided in a partial class, allows value of OriginalTitle to be changed before returning.
+ /// </summary>
+ partial void GetOriginalTitle(ref string result);
+
+ /// <summary>
+ /// Max length = 1024
+ /// </summary>
+ [MaxLength(1024)]
+ [StringLength(1024)]
+ public string OriginalTitle
+ {
+ get
+ {
+ string value = _OriginalTitle;
+ GetOriginalTitle(ref value);
+ return (_OriginalTitle = value);
+ }
+ set
+ {
+ string oldValue = _OriginalTitle;
+ SetOriginalTitle(oldValue, ref value);
+ if (oldValue != value)
+ {
+ _OriginalTitle = value;
+ }
+ }
+ }
+
+ /// <summary>
+ /// Backing field for SortTitle
+ /// </summary>
+ protected string _SortTitle;
+ /// <summary>
+ /// When provided in a partial class, allows value of SortTitle to be changed before setting.
+ /// </summary>
+ partial void SetSortTitle(string oldValue, ref string newValue);
+ /// <summary>
+ /// When provided in a partial class, allows value of SortTitle to be changed before returning.
+ /// </summary>
+ partial void GetSortTitle(ref string result);
+
+ /// <summary>
+ /// Max length = 1024
+ /// </summary>
+ [MaxLength(1024)]
+ [StringLength(1024)]
+ public string SortTitle
+ {
+ get
+ {
+ string value = _SortTitle;
+ GetSortTitle(ref value);
+ return (_SortTitle = value);
+ }
+ set
+ {
+ string oldValue = _SortTitle;
+ SetSortTitle(oldValue, ref value);
+ if (oldValue != value)
+ {
+ _SortTitle = value;
+ }
+ }
+ }
+
+ /// <summary>
+ /// Backing field for Language
+ /// </summary>
+ protected string _Language;
+ /// <summary>
+ /// When provided in a partial class, allows value of Language to be changed before setting.
+ /// </summary>
+ partial void SetLanguage(string oldValue, ref string newValue);
+ /// <summary>
+ /// When provided in a partial class, allows value of Language to be changed before returning.
+ /// </summary>
+ partial void GetLanguage(ref string result);
+
+ /// <summary>
+ /// Required, Min length = 3, Max length = 3
+ /// ISO-639-3 3-character language codes
+ /// </summary>
+ [Required]
+ [MinLength(3)]
+ [MaxLength(3)]
+ [StringLength(3)]
+ public string Language
+ {
+ get
+ {
+ string value = _Language;
+ GetLanguage(ref value);
+ return (_Language = value);
+ }
+ set
+ {
+ string oldValue = _Language;
+ SetLanguage(oldValue, ref value);
+ if (oldValue != value)
+ {
+ _Language = value;
+ }
+ }
+ }
+
+ /// <summary>
+ /// Backing field for ReleaseDate
+ /// </summary>
+ protected DateTimeOffset? _ReleaseDate;
+ /// <summary>
+ /// When provided in a partial class, allows value of ReleaseDate to be changed before setting.
+ /// </summary>
+ partial void SetReleaseDate(DateTimeOffset? oldValue, ref DateTimeOffset? newValue);
+ /// <summary>
+ /// When provided in a partial class, allows value of ReleaseDate to be changed before returning.
+ /// </summary>
+ partial void GetReleaseDate(ref DateTimeOffset? result);
+
+ public DateTimeOffset? ReleaseDate
+ {
+ get
+ {
+ DateTimeOffset? value = _ReleaseDate;
+ GetReleaseDate(ref value);
+ return (_ReleaseDate = value);
+ }
+ set
+ {
+ DateTimeOffset? oldValue = _ReleaseDate;
+ SetReleaseDate(oldValue, ref value);
+ if (oldValue != value)
+ {
+ _ReleaseDate = value;
+ }
+ }
+ }
+
+ /// <summary>
+ /// Backing field for DateAdded
+ /// </summary>
+ protected DateTime _DateAdded;
+ /// <summary>
+ /// When provided in a partial class, allows value of DateAdded to be changed before setting.
+ /// </summary>
+ partial void SetDateAdded(DateTime oldValue, ref DateTime newValue);
+ /// <summary>
+ /// When provided in a partial class, allows value of DateAdded to be changed before returning.
+ /// </summary>
+ partial void GetDateAdded(ref DateTime result);
+
+ /// <summary>
+ /// Required
+ /// </summary>
+ [Required]
+ public DateTime DateAdded
+ {
+ get
+ {
+ DateTime value = _DateAdded;
+ GetDateAdded(ref value);
+ return (_DateAdded = value);
+ }
+ internal set
+ {
+ DateTime oldValue = _DateAdded;
+ SetDateAdded(oldValue, ref value);
+ if (oldValue != value)
+ {
+ _DateAdded = value;
+ }
+ }
+ }
+
+ /// <summary>
+ /// Backing field for DateModified
+ /// </summary>
+ protected DateTime _DateModified;
+ /// <summary>
+ /// When provided in a partial class, allows value of DateModified to be changed before setting.
+ /// </summary>
+ partial void SetDateModified(DateTime oldValue, ref DateTime newValue);
+ /// <summary>
+ /// When provided in a partial class, allows value of DateModified to be changed before returning.
+ /// </summary>
+ partial void GetDateModified(ref DateTime result);
+
+ /// <summary>
+ /// Required
+ /// </summary>
+ [Required]
+ public DateTime DateModified
+ {
+ get
+ {
+ DateTime value = _DateModified;
+ GetDateModified(ref value);
+ return (_DateModified = value);
+ }
+ internal set
+ {
+ DateTime oldValue = _DateModified;
+ SetDateModified(oldValue, ref value);
+ if (oldValue != value)
+ {
+ _DateModified = value;
+ }
+ }
+ }
+
+ /// <summary>
+ /// Required
+ /// </summary>
+ [ConcurrencyCheck]
+ [Required]
+ public byte[] Timestamp { get; set; }
+
+ /*************************************************************************
+ * Navigation properties
+ *************************************************************************/
+
+ public virtual ICollection<global::Jellyfin.Data.Entities.PersonRole> PersonRoles { get; protected set; }
+
+ public virtual ICollection<global::Jellyfin.Data.Entities.Genre> Genres { get; protected set; }
+
+ public virtual ICollection<global::Jellyfin.Data.Entities.Artwork> Artwork { get; protected set; }
+
+ public virtual ICollection<global::Jellyfin.Data.Entities.Rating> Ratings { get; protected set; }
+
+ public virtual ICollection<global::Jellyfin.Data.Entities.MetadataProviderId> Sources { get; protected set; }
+
+ }
+}
+
diff --git a/Jellyfin.Data/Entities/MetadataProvider.cs b/Jellyfin.Data/Entities/MetadataProvider.cs
new file mode 100644
index 000000000..3a8f5854e
--- /dev/null
+++ b/Jellyfin.Data/Entities/MetadataProvider.cs
@@ -0,0 +1,158 @@
+//------------------------------------------------------------------------------
+// <auto-generated>
+// This code was generated from a template.
+//
+// Manual changes to this file may cause unexpected behavior in your application.
+// Manual changes to this file will be overwritten if the code is regenerated.
+//
+// Produced by Entity Framework Visual Editor
+// https://github.com/msawczyn/EFDesigner
+// </auto-generated>
+//------------------------------------------------------------------------------
+
+using System;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.ComponentModel;
+using System.ComponentModel.DataAnnotations;
+using System.ComponentModel.DataAnnotations.Schema;
+using System.Linq;
+using System.Runtime.CompilerServices;
+
+namespace Jellyfin.Data.Entities
+{
+ public partial class MetadataProvider
+ {
+ partial void Init();
+
+ /// <summary>
+ /// Default constructor. Protected due to required properties, but present because EF needs it.
+ /// </summary>
+ protected MetadataProvider()
+ {
+ Init();
+ }
+
+ /// <summary>
+ /// Replaces default constructor, since it's protected. Caller assumes responsibility for setting all required values before saving.
+ /// </summary>
+ public static MetadataProvider CreateMetadataProviderUnsafe()
+ {
+ return new MetadataProvider();
+ }
+
+ /// <summary>
+ /// Public constructor with required data
+ /// </summary>
+ /// <param name="name"></param>
+ public MetadataProvider(string name)
+ {
+ if (string.IsNullOrEmpty(name)) throw new ArgumentNullException(nameof(name));
+ this.Name = name;
+
+
+ Init();
+ }
+
+ /// <summary>
+ /// Static create function (for use in LINQ queries, etc.)
+ /// </summary>
+ /// <param name="name"></param>
+ public static MetadataProvider Create(string name)
+ {
+ return new MetadataProvider(name);
+ }
+
+ /*************************************************************************
+ * Properties
+ *************************************************************************/
+
+ /// <summary>
+ /// Backing field for Id
+ /// </summary>
+ internal int _Id;
+ /// <summary>
+ /// When provided in a partial class, allows value of Id to be changed before setting.
+ /// </summary>
+ partial void SetId(int oldValue, ref int newValue);
+ /// <summary>
+ /// When provided in a partial class, allows value of Id to be changed before returning.
+ /// </summary>
+ partial void GetId(ref int result);
+
+ /// <summary>
+ /// Identity, Indexed, Required
+ /// </summary>
+ [Key]
+ [Required]
+ public int Id
+ {
+ get
+ {
+ int value = _Id;
+ GetId(ref value);
+ return (_Id = value);
+ }
+ protected set
+ {
+ int oldValue = _Id;
+ SetId(oldValue, ref value);
+ if (oldValue != value)
+ {
+ _Id = value;
+ }
+ }
+ }
+
+ /// <summary>
+ /// Backing field for Name
+ /// </summary>
+ protected string _Name;
+ /// <summary>
+ /// When provided in a partial class, allows value of Name to be changed before setting.
+ /// </summary>
+ partial void SetName(string oldValue, ref string newValue);
+ /// <summary>
+ /// When provided in a partial class, allows value of Name to be changed before returning.
+ /// </summary>
+ partial void GetName(ref string result);
+
+ /// <summary>
+ /// Required, Max length = 1024
+ /// </summary>
+ [Required]
+ [MaxLength(1024)]
+ [StringLength(1024)]
+ public string Name
+ {
+ get
+ {
+ string value = _Name;
+ GetName(ref value);
+ return (_Name = value);
+ }
+ set
+ {
+ string oldValue = _Name;
+ SetName(oldValue, ref value);
+ if (oldValue != value)
+ {
+ _Name = value;
+ }
+ }
+ }
+
+ /// <summary>
+ /// Required
+ /// </summary>
+ [ConcurrencyCheck]
+ [Required]
+ public byte[] Timestamp { get; set; }
+
+ /*************************************************************************
+ * Navigation properties
+ *************************************************************************/
+
+ }
+}
+
diff --git a/Jellyfin.Data/Entities/MetadataProviderId.cs b/Jellyfin.Data/Entities/MetadataProviderId.cs
new file mode 100644
index 000000000..87ff19e26
--- /dev/null
+++ b/Jellyfin.Data/Entities/MetadataProviderId.cs
@@ -0,0 +1,189 @@
+//------------------------------------------------------------------------------
+// <auto-generated>
+// This code was generated from a template.
+//
+// Manual changes to this file may cause unexpected behavior in your application.
+// Manual changes to this file will be overwritten if the code is regenerated.
+//
+// Produced by Entity Framework Visual Editor
+// https://github.com/msawczyn/EFDesigner
+// </auto-generated>
+//------------------------------------------------------------------------------
+
+using System;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.ComponentModel;
+using System.ComponentModel.DataAnnotations;
+using System.ComponentModel.DataAnnotations.Schema;
+using System.Linq;
+using System.Runtime.CompilerServices;
+
+namespace Jellyfin.Data.Entities
+{
+ public partial class MetadataProviderId
+ {
+ partial void Init();
+
+ /// <summary>
+ /// Default constructor. Protected due to required properties, but present because EF needs it.
+ /// </summary>
+ protected MetadataProviderId()
+ {
+ // NOTE: This class has one-to-one associations with MetadataProviderId.
+ // One-to-one associations are not validated in constructors since this causes a scenario where each one must be constructed before the other.
+
+ Init();
+ }
+
+ /// <summary>
+ /// Replaces default constructor, since it's protected. Caller assumes responsibility for setting all required values before saving.
+ /// </summary>
+ public static MetadataProviderId CreateMetadataProviderIdUnsafe()
+ {
+ return new MetadataProviderId();
+ }
+
+ /// <summary>
+ /// Public constructor with required data
+ /// </summary>
+ /// <param name="providerid"></param>
+ /// <param name="_metadata0"></param>
+ /// <param name="_person1"></param>
+ /// <param name="_personrole2"></param>
+ /// <param name="_ratingsource3"></param>
+ public MetadataProviderId(string providerid, global::Jellyfin.Data.Entities.Metadata _metadata0, global::Jellyfin.Data.Entities.Person _person1, global::Jellyfin.Data.Entities.PersonRole _personrole2, global::Jellyfin.Data.Entities.RatingSource _ratingsource3)
+ {
+ // NOTE: This class has one-to-one associations with MetadataProviderId.
+ // One-to-one associations are not validated in constructors since this causes a scenario where each one must be constructed before the other.
+
+ if (string.IsNullOrEmpty(providerid)) throw new ArgumentNullException(nameof(providerid));
+ this.ProviderId = providerid;
+
+ if (_metadata0 == null) throw new ArgumentNullException(nameof(_metadata0));
+ _metadata0.Sources.Add(this);
+
+ if (_person1 == null) throw new ArgumentNullException(nameof(_person1));
+ _person1.Sources.Add(this);
+
+ if (_personrole2 == null) throw new ArgumentNullException(nameof(_personrole2));
+ _personrole2.Sources.Add(this);
+
+ if (_ratingsource3 == null) throw new ArgumentNullException(nameof(_ratingsource3));
+ _ratingsource3.Source = this;
+
+
+ Init();
+ }
+
+ /// <summary>
+ /// Static create function (for use in LINQ queries, etc.)
+ /// </summary>
+ /// <param name="providerid"></param>
+ /// <param name="_metadata0"></param>
+ /// <param name="_person1"></param>
+ /// <param name="_personrole2"></param>
+ /// <param name="_ratingsource3"></param>
+ public static MetadataProviderId Create(string providerid, global::Jellyfin.Data.Entities.Metadata _metadata0, global::Jellyfin.Data.Entities.Person _person1, global::Jellyfin.Data.Entities.PersonRole _personrole2, global::Jellyfin.Data.Entities.RatingSource _ratingsource3)
+ {
+ return new MetadataProviderId(providerid, _metadata0, _person1, _personrole2, _ratingsource3);
+ }
+
+ /*************************************************************************
+ * Properties
+ *************************************************************************/
+
+ /// <summary>
+ /// Backing field for Id
+ /// </summary>
+ internal int _Id;
+ /// <summary>
+ /// When provided in a partial class, allows value of Id to be changed before setting.
+ /// </summary>
+ partial void SetId(int oldValue, ref int newValue);
+ /// <summary>
+ /// When provided in a partial class, allows value of Id to be changed before returning.
+ /// </summary>
+ partial void GetId(ref int result);
+
+ /// <summary>
+ /// Identity, Indexed, Required
+ /// </summary>
+ [Key]
+ [Required]
+ public int Id
+ {
+ get
+ {
+ int value = _Id;
+ GetId(ref value);
+ return (_Id = value);
+ }
+ protected set
+ {
+ int oldValue = _Id;
+ SetId(oldValue, ref value);
+ if (oldValue != value)
+ {
+ _Id = value;
+ }
+ }
+ }
+
+ /// <summary>
+ /// Backing field for ProviderId
+ /// </summary>
+ protected string _ProviderId;
+ /// <summary>
+ /// When provided in a partial class, allows value of ProviderId to be changed before setting.
+ /// </summary>
+ partial void SetProviderId(string oldValue, ref string newValue);
+ /// <summary>
+ /// When provided in a partial class, allows value of ProviderId to be changed before returning.
+ /// </summary>
+ partial void GetProviderId(ref string result);
+
+ /// <summary>
+ /// Required, Max length = 255
+ /// </summary>
+ [Required]
+ [MaxLength(255)]
+ [StringLength(255)]
+ public string ProviderId
+ {
+ get
+ {
+ string value = _ProviderId;
+ GetProviderId(ref value);
+ return (_ProviderId = value);
+ }
+ set
+ {
+ string oldValue = _ProviderId;
+ SetProviderId(oldValue, ref value);
+ if (oldValue != value)
+ {
+ _ProviderId = value;
+ }
+ }
+ }
+
+ /// <summary>
+ /// Required
+ /// </summary>
+ [ConcurrencyCheck]
+ [Required]
+ public byte[] Timestamp { get; set; }
+
+ /*************************************************************************
+ * Navigation properties
+ *************************************************************************/
+
+ /// <summary>
+ /// Required
+ /// </summary>
+ public virtual global::Jellyfin.Data.Entities.MetadataProvider MetadataProvider { get; set; }
+
+ }
+}
+
diff --git a/Jellyfin.Data/Entities/Movie.cs b/Jellyfin.Data/Entities/Movie.cs
new file mode 100644
index 000000000..dfcc05a94
--- /dev/null
+++ b/Jellyfin.Data/Entities/Movie.cs
@@ -0,0 +1,84 @@
+//------------------------------------------------------------------------------
+// <auto-generated>
+// This code was generated from a template.
+//
+// Manual changes to this file may cause unexpected behavior in your application.
+// Manual changes to this file will be overwritten if the code is regenerated.
+//
+// Produced by Entity Framework Visual Editor
+// https://github.com/msawczyn/EFDesigner
+// </auto-generated>
+//------------------------------------------------------------------------------
+
+using System;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.ComponentModel;
+using System.ComponentModel.DataAnnotations;
+using System.ComponentModel.DataAnnotations.Schema;
+using System.Linq;
+using System.Runtime.CompilerServices;
+
+namespace Jellyfin.Data.Entities
+{
+ public partial class Movie: global::Jellyfin.Data.Entities.LibraryItem
+ {
+ partial void Init();
+
+ /// <summary>
+ /// Default constructor. Protected due to required properties, but present because EF needs it.
+ /// </summary>
+ protected Movie(): base()
+ {
+ Releases = new System.Collections.Generic.HashSet<global::Jellyfin.Data.Entities.Release>();
+ MovieMetadata = new System.Collections.Generic.HashSet<global::Jellyfin.Data.Entities.MovieMetadata>();
+
+ Init();
+ }
+
+ /// <summary>
+ /// Replaces default constructor, since it's protected. Caller assumes responsibility for setting all required values before saving.
+ /// </summary>
+ public static Movie CreateMovieUnsafe()
+ {
+ return new Movie();
+ }
+
+ /// <summary>
+ /// Public constructor with required data
+ /// </summary>
+ /// <param name="urlid">This is whats gets displayed in the Urls and API requests. This could also be a string.</param>
+ public Movie(Guid urlid, DateTime dateadded)
+ {
+ this.UrlId = urlid;
+
+ this.Releases = new System.Collections.Generic.HashSet<global::Jellyfin.Data.Entities.Release>();
+ this.MovieMetadata = new System.Collections.Generic.HashSet<global::Jellyfin.Data.Entities.MovieMetadata>();
+
+ Init();
+ }
+
+ /// <summary>
+ /// Static create function (for use in LINQ queries, etc.)
+ /// </summary>
+ /// <param name="urlid">This is whats gets displayed in the Urls and API requests. This could also be a string.</param>
+ public static Movie Create(Guid urlid, DateTime dateadded)
+ {
+ return new Movie(urlid, dateadded);
+ }
+
+ /*************************************************************************
+ * Properties
+ *************************************************************************/
+
+ /*************************************************************************
+ * Navigation properties
+ *************************************************************************/
+
+ public virtual ICollection<global::Jellyfin.Data.Entities.Release> Releases { get; protected set; }
+
+ public virtual ICollection<global::Jellyfin.Data.Entities.MovieMetadata> MovieMetadata { get; protected set; }
+
+ }
+}
+
diff --git a/Jellyfin.Data/Entities/MovieMetadata.cs b/Jellyfin.Data/Entities/MovieMetadata.cs
new file mode 100644
index 000000000..bd847da8f
--- /dev/null
+++ b/Jellyfin.Data/Entities/MovieMetadata.cs
@@ -0,0 +1,239 @@
+//------------------------------------------------------------------------------
+// <auto-generated>
+// This code was generated from a template.
+//
+// Manual changes to this file may cause unexpected behavior in your application.
+// Manual changes to this file will be overwritten if the code is regenerated.
+//
+// Produced by Entity Framework Visual Editor
+// https://github.com/msawczyn/EFDesigner
+// </auto-generated>
+//------------------------------------------------------------------------------
+
+using System;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.ComponentModel;
+using System.ComponentModel.DataAnnotations;
+using System.ComponentModel.DataAnnotations.Schema;
+using System.Linq;
+using System.Runtime.CompilerServices;
+
+namespace Jellyfin.Data.Entities
+{
+ public partial class MovieMetadata: global::Jellyfin.Data.Entities.Metadata
+ {
+ partial void Init();
+
+ /// <summary>
+ /// Default constructor. Protected due to required properties, but present because EF needs it.
+ /// </summary>
+ protected MovieMetadata(): base()
+ {
+ Studios = new System.Collections.Generic.HashSet<global::Jellyfin.Data.Entities.Company>();
+
+ Init();
+ }
+
+ /// <summary>
+ /// Replaces default constructor, since it's protected. Caller assumes responsibility for setting all required values before saving.
+ /// </summary>
+ public static MovieMetadata CreateMovieMetadataUnsafe()
+ {
+ return new MovieMetadata();
+ }
+
+ /// <summary>
+ /// Public constructor with required data
+ /// </summary>
+ /// <param name="title">The title or name of the object</param>
+ /// <param name="language">ISO-639-3 3-character language codes</param>
+ /// <param name="_movie0"></param>
+ public MovieMetadata(string title, string language, DateTime dateadded, DateTime datemodified, global::Jellyfin.Data.Entities.Movie _movie0)
+ {
+ if (string.IsNullOrEmpty(title)) throw new ArgumentNullException(nameof(title));
+ this.Title = title;
+
+ if (string.IsNullOrEmpty(language)) throw new ArgumentNullException(nameof(language));
+ this.Language = language;
+
+ if (_movie0 == null) throw new ArgumentNullException(nameof(_movie0));
+ _movie0.MovieMetadata.Add(this);
+
+ this.Studios = new System.Collections.Generic.HashSet<global::Jellyfin.Data.Entities.Company>();
+
+ Init();
+ }
+
+ /// <summary>
+ /// Static create function (for use in LINQ queries, etc.)
+ /// </summary>
+ /// <param name="title">The title or name of the object</param>
+ /// <param name="language">ISO-639-3 3-character language codes</param>
+ /// <param name="_movie0"></param>
+ public static MovieMetadata Create(string title, string language, DateTime dateadded, DateTime datemodified, global::Jellyfin.Data.Entities.Movie _movie0)
+ {
+ return new MovieMetadata(title, language, dateadded, datemodified, _movie0);
+ }
+
+ /*************************************************************************
+ * Properties
+ *************************************************************************/
+
+ /// <summary>
+ /// Backing field for Outline
+ /// </summary>
+ protected string _Outline;
+ /// <summary>
+ /// When provided in a partial class, allows value of Outline to be changed before setting.
+ /// </summary>
+ partial void SetOutline(string oldValue, ref string newValue);
+ /// <summary>
+ /// When provided in a partial class, allows value of Outline to be changed before returning.
+ /// </summary>
+ partial void GetOutline(ref string result);
+
+ /// <summary>
+ /// Max length = 1024
+ /// </summary>
+ [MaxLength(1024)]
+ [StringLength(1024)]
+ public string Outline
+ {
+ get
+ {
+ string value = _Outline;
+ GetOutline(ref value);
+ return (_Outline = value);
+ }
+ set
+ {
+ string oldValue = _Outline;
+ SetOutline(oldValue, ref value);
+ if (oldValue != value)
+ {
+ _Outline = value;
+ }
+ }
+ }
+
+ /// <summary>
+ /// Backing field for Plot
+ /// </summary>
+ protected string _Plot;
+ /// <summary>
+ /// When provided in a partial class, allows value of Plot to be changed before setting.
+ /// </summary>
+ partial void SetPlot(string oldValue, ref string newValue);
+ /// <summary>
+ /// When provided in a partial class, allows value of Plot to be changed before returning.
+ /// </summary>
+ partial void GetPlot(ref string result);
+
+ /// <summary>
+ /// Max length = 65535
+ /// </summary>
+ [MaxLength(65535)]
+ [StringLength(65535)]
+ public string Plot
+ {
+ get
+ {
+ string value = _Plot;
+ GetPlot(ref value);
+ return (_Plot = value);
+ }
+ set
+ {
+ string oldValue = _Plot;
+ SetPlot(oldValue, ref value);
+ if (oldValue != value)
+ {
+ _Plot = value;
+ }
+ }
+ }
+
+ /// <summary>
+ /// Backing field for Tagline
+ /// </summary>
+ protected string _Tagline;
+ /// <summary>
+ /// When provided in a partial class, allows value of Tagline to be changed before setting.
+ /// </summary>
+ partial void SetTagline(string oldValue, ref string newValue);
+ /// <summary>
+ /// When provided in a partial class, allows value of Tagline to be changed before returning.
+ /// </summary>
+ partial void GetTagline(ref string result);
+
+ /// <summary>
+ /// Max length = 1024
+ /// </summary>
+ [MaxLength(1024)]
+ [StringLength(1024)]
+ public string Tagline
+ {
+ get
+ {
+ string value = _Tagline;
+ GetTagline(ref value);
+ return (_Tagline = value);
+ }
+ set
+ {
+ string oldValue = _Tagline;
+ SetTagline(oldValue, ref value);
+ if (oldValue != value)
+ {
+ _Tagline = value;
+ }
+ }
+ }
+
+ /// <summary>
+ /// Backing field for Country
+ /// </summary>
+ protected string _Country;
+ /// <summary>
+ /// When provided in a partial class, allows value of Country to be changed before setting.
+ /// </summary>
+ partial void SetCountry(string oldValue, ref string newValue);
+ /// <summary>
+ /// When provided in a partial class, allows value of Country to be changed before returning.
+ /// </summary>
+ partial void GetCountry(ref string result);
+
+ /// <summary>
+ /// Max length = 2
+ /// </summary>
+ [MaxLength(2)]
+ [StringLength(2)]
+ public string Country
+ {
+ get
+ {
+ string value = _Country;
+ GetCountry(ref value);
+ return (_Country = value);
+ }
+ set
+ {
+ string oldValue = _Country;
+ SetCountry(oldValue, ref value);
+ if (oldValue != value)
+ {
+ _Country = value;
+ }
+ }
+ }
+
+ /*************************************************************************
+ * Navigation properties
+ *************************************************************************/
+
+ public virtual ICollection<global::Jellyfin.Data.Entities.Company> Studios { get; protected set; }
+
+ }
+}
+
diff --git a/Jellyfin.Data/Entities/MusicAlbum.cs b/Jellyfin.Data/Entities/MusicAlbum.cs
new file mode 100644
index 000000000..417f2595b
--- /dev/null
+++ b/Jellyfin.Data/Entities/MusicAlbum.cs
@@ -0,0 +1,84 @@
+//------------------------------------------------------------------------------
+// <auto-generated>
+// This code was generated from a template.
+//
+// Manual changes to this file may cause unexpected behavior in your application.
+// Manual changes to this file will be overwritten if the code is regenerated.
+//
+// Produced by Entity Framework Visual Editor
+// https://github.com/msawczyn/EFDesigner
+// </auto-generated>
+//------------------------------------------------------------------------------
+
+using System;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.ComponentModel;
+using System.ComponentModel.DataAnnotations;
+using System.ComponentModel.DataAnnotations.Schema;
+using System.Linq;
+using System.Runtime.CompilerServices;
+
+namespace Jellyfin.Data.Entities
+{
+ public partial class MusicAlbum: global::Jellyfin.Data.Entities.LibraryItem
+ {
+ partial void Init();
+
+ /// <summary>
+ /// Default constructor. Protected due to required properties, but present because EF needs it.
+ /// </summary>
+ protected MusicAlbum(): base()
+ {
+ MusicAlbumMetadata = new System.Collections.Generic.HashSet<global::Jellyfin.Data.Entities.MusicAlbumMetadata>();
+ Tracks = new System.Collections.Generic.HashSet<global::Jellyfin.Data.Entities.Track>();
+
+ Init();
+ }
+
+ /// <summary>
+ /// Replaces default constructor, since it's protected. Caller assumes responsibility for setting all required values before saving.
+ /// </summary>
+ public static MusicAlbum CreateMusicAlbumUnsafe()
+ {
+ return new MusicAlbum();
+ }
+
+ /// <summary>
+ /// Public constructor with required data
+ /// </summary>
+ /// <param name="urlid">This is whats gets displayed in the Urls and API requests. This could also be a string.</param>
+ public MusicAlbum(Guid urlid, DateTime dateadded)
+ {
+ this.UrlId = urlid;
+
+ this.MusicAlbumMetadata = new System.Collections.Generic.HashSet<global::Jellyfin.Data.Entities.MusicAlbumMetadata>();
+ this.Tracks = new System.Collections.Generic.HashSet<global::Jellyfin.Data.Entities.Track>();
+
+ Init();
+ }
+
+ /// <summary>
+ /// Static create function (for use in LINQ queries, etc.)
+ /// </summary>
+ /// <param name="urlid">This is whats gets displayed in the Urls and API requests. This could also be a string.</param>
+ public static MusicAlbum Create(Guid urlid, DateTime dateadded)
+ {
+ return new MusicAlbum(urlid, dateadded);
+ }
+
+ /*************************************************************************
+ * Properties
+ *************************************************************************/
+
+ /*************************************************************************
+ * Navigation properties
+ *************************************************************************/
+
+ public virtual ICollection<global::Jellyfin.Data.Entities.MusicAlbumMetadata> MusicAlbumMetadata { get; protected set; }
+
+ public virtual ICollection<global::Jellyfin.Data.Entities.Track> Tracks { get; protected set; }
+
+ }
+}
+
diff --git a/Jellyfin.Data/Entities/MusicAlbumMetadata.cs b/Jellyfin.Data/Entities/MusicAlbumMetadata.cs
new file mode 100644
index 000000000..cd72ecba5
--- /dev/null
+++ b/Jellyfin.Data/Entities/MusicAlbumMetadata.cs
@@ -0,0 +1,202 @@
+//------------------------------------------------------------------------------
+// <auto-generated>
+// This code was generated from a template.
+//
+// Manual changes to this file may cause unexpected behavior in your application.
+// Manual changes to this file will be overwritten if the code is regenerated.
+//
+// Produced by Entity Framework Visual Editor
+// https://github.com/msawczyn/EFDesigner
+// </auto-generated>
+//------------------------------------------------------------------------------
+
+using System;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.ComponentModel;
+using System.ComponentModel.DataAnnotations;
+using System.ComponentModel.DataAnnotations.Schema;
+using System.Linq;
+using System.Runtime.CompilerServices;
+
+namespace Jellyfin.Data.Entities
+{
+ public partial class MusicAlbumMetadata: global::Jellyfin.Data.Entities.Metadata
+ {
+ partial void Init();
+
+ /// <summary>
+ /// Default constructor. Protected due to required properties, but present because EF needs it.
+ /// </summary>
+ protected MusicAlbumMetadata(): base()
+ {
+ Labels = new System.Collections.Generic.HashSet<global::Jellyfin.Data.Entities.Company>();
+
+ Init();
+ }
+
+ /// <summary>
+ /// Replaces default constructor, since it's protected. Caller assumes responsibility for setting all required values before saving.
+ /// </summary>
+ public static MusicAlbumMetadata CreateMusicAlbumMetadataUnsafe()
+ {
+ return new MusicAlbumMetadata();
+ }
+
+ /// <summary>
+ /// Public constructor with required data
+ /// </summary>
+ /// <param name="title">The title or name of the object</param>
+ /// <param name="language">ISO-639-3 3-character language codes</param>
+ /// <param name="_musicalbum0"></param>
+ public MusicAlbumMetadata(string title, string language, DateTime dateadded, DateTime datemodified, global::Jellyfin.Data.Entities.MusicAlbum _musicalbum0)
+ {
+ if (string.IsNullOrEmpty(title)) throw new ArgumentNullException(nameof(title));
+ this.Title = title;
+
+ if (string.IsNullOrEmpty(language)) throw new ArgumentNullException(nameof(language));
+ this.Language = language;
+
+ if (_musicalbum0 == null) throw new ArgumentNullException(nameof(_musicalbum0));
+ _musicalbum0.MusicAlbumMetadata.Add(this);
+
+ this.Labels = new System.Collections.Generic.HashSet<global::Jellyfin.Data.Entities.Company>();
+
+ Init();
+ }
+
+ /// <summary>
+ /// Static create function (for use in LINQ queries, etc.)
+ /// </summary>
+ /// <param name="title">The title or name of the object</param>
+ /// <param name="language">ISO-639-3 3-character language codes</param>
+ /// <param name="_musicalbum0"></param>
+ public static MusicAlbumMetadata Create(string title, string language, DateTime dateadded, DateTime datemodified, global::Jellyfin.Data.Entities.MusicAlbum _musicalbum0)
+ {
+ return new MusicAlbumMetadata(title, language, dateadded, datemodified, _musicalbum0);
+ }
+
+ /*************************************************************************
+ * Properties
+ *************************************************************************/
+
+ /// <summary>
+ /// Backing field for Barcode
+ /// </summary>
+ protected string _Barcode;
+ /// <summary>
+ /// When provided in a partial class, allows value of Barcode to be changed before setting.
+ /// </summary>
+ partial void SetBarcode(string oldValue, ref string newValue);
+ /// <summary>
+ /// When provided in a partial class, allows value of Barcode to be changed before returning.
+ /// </summary>
+ partial void GetBarcode(ref string result);
+
+ /// <summary>
+ /// Max length = 255
+ /// </summary>
+ [MaxLength(255)]
+ [StringLength(255)]
+ public string Barcode
+ {
+ get
+ {
+ string value = _Barcode;
+ GetBarcode(ref value);
+ return (_Barcode = value);
+ }
+ set
+ {
+ string oldValue = _Barcode;
+ SetBarcode(oldValue, ref value);
+ if (oldValue != value)
+ {
+ _Barcode = value;
+ }
+ }
+ }
+
+ /// <summary>
+ /// Backing field for LabelNumber
+ /// </summary>
+ protected string _LabelNumber;
+ /// <summary>
+ /// When provided in a partial class, allows value of LabelNumber to be changed before setting.
+ /// </summary>
+ partial void SetLabelNumber(string oldValue, ref string newValue);
+ /// <summary>
+ /// When provided in a partial class, allows value of LabelNumber to be changed before returning.
+ /// </summary>
+ partial void GetLabelNumber(ref string result);
+
+ /// <summary>
+ /// Max length = 255
+ /// </summary>
+ [MaxLength(255)]
+ [StringLength(255)]
+ public string LabelNumber
+ {
+ get
+ {
+ string value = _LabelNumber;
+ GetLabelNumber(ref value);
+ return (_LabelNumber = value);
+ }
+ set
+ {
+ string oldValue = _LabelNumber;
+ SetLabelNumber(oldValue, ref value);
+ if (oldValue != value)
+ {
+ _LabelNumber = value;
+ }
+ }
+ }
+
+ /// <summary>
+ /// Backing field for Country
+ /// </summary>
+ protected string _Country;
+ /// <summary>
+ /// When provided in a partial class, allows value of Country to be changed before setting.
+ /// </summary>
+ partial void SetCountry(string oldValue, ref string newValue);
+ /// <summary>
+ /// When provided in a partial class, allows value of Country to be changed before returning.
+ /// </summary>
+ partial void GetCountry(ref string result);
+
+ /// <summary>
+ /// Max length = 2
+ /// </summary>
+ [MaxLength(2)]
+ [StringLength(2)]
+ public string Country
+ {
+ get
+ {
+ string value = _Country;
+ GetCountry(ref value);
+ return (_Country = value);
+ }
+ set
+ {
+ string oldValue = _Country;
+ SetCountry(oldValue, ref value);
+ if (oldValue != value)
+ {
+ _Country = value;
+ }
+ }
+ }
+
+ /*************************************************************************
+ * Navigation properties
+ *************************************************************************/
+
+ public virtual ICollection<global::Jellyfin.Data.Entities.Company> Labels { get; protected set; }
+
+ }
+}
+
diff --git a/Jellyfin.Data/Entities/Permission.cs b/Jellyfin.Data/Entities/Permission.cs
new file mode 100644
index 000000000..a717fc83f
--- /dev/null
+++ b/Jellyfin.Data/Entities/Permission.cs
@@ -0,0 +1,152 @@
+//------------------------------------------------------------------------------
+// <auto-generated>
+// This code was generated from a template.
+//
+// Manual changes to this file may cause unexpected behavior in your application.
+// Manual changes to this file will be overwritten if the code is regenerated.
+//
+// Produced by Entity Framework Visual Editor
+// https://github.com/msawczyn/EFDesigner
+// </auto-generated>
+//------------------------------------------------------------------------------
+
+using System;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.ComponentModel;
+using System.ComponentModel.DataAnnotations;
+using System.ComponentModel.DataAnnotations.Schema;
+using System.Linq;
+using System.Runtime.CompilerServices;
+
+namespace Jellyfin.Data.Entities
+{
+ public partial class Permission
+ {
+ partial void Init();
+
+ /// <summary>
+ /// Default constructor. Protected due to required properties, but present because EF needs it.
+ /// </summary>
+ protected Permission()
+ {
+ Init();
+ }
+
+ /// <summary>
+ /// Replaces default constructor, since it's protected. Caller assumes responsibility for setting all required values before saving.
+ /// </summary>
+ public static Permission CreatePermissionUnsafe()
+ {
+ return new Permission();
+ }
+
+ /// <summary>
+ /// Public constructor with required data
+ /// </summary>
+ /// <param name="kind"></param>
+ /// <param name="value"></param>
+ /// <param name="_user0"></param>
+ /// <param name="_group1"></param>
+ public Permission(global::Jellyfin.Data.Enums.PermissionKind kind, bool value, global::Jellyfin.Data.Entities.User _user0, global::Jellyfin.Data.Entities.Group _group1)
+ {
+ this.Kind = kind;
+
+ this.Value = value;
+
+ if (_user0 == null) throw new ArgumentNullException(nameof(_user0));
+ _user0.Permissions.Add(this);
+
+ if (_group1 == null) throw new ArgumentNullException(nameof(_group1));
+ _group1.GroupPermissions.Add(this);
+
+
+ Init();
+ }
+
+ /// <summary>
+ /// Static create function (for use in LINQ queries, etc.)
+ /// </summary>
+ /// <param name="kind"></param>
+ /// <param name="value"></param>
+ /// <param name="_user0"></param>
+ /// <param name="_group1"></param>
+ public static Permission Create(global::Jellyfin.Data.Enums.PermissionKind kind, bool value, global::Jellyfin.Data.Entities.User _user0, global::Jellyfin.Data.Entities.Group _group1)
+ {
+ return new Permission(kind, value, _user0, _group1);
+ }
+
+ /*************************************************************************
+ * Properties
+ *************************************************************************/
+
+ /// <summary>
+ /// Identity, Indexed, Required
+ /// </summary>
+ [Key]
+ [Required]
+ public int Id { get; protected set; }
+
+ /// <summary>
+ /// Backing field for Kind
+ /// </summary>
+ protected global::Jellyfin.Data.Enums.PermissionKind _Kind;
+ /// <summary>
+ /// When provided in a partial class, allows value of Kind to be changed before setting.
+ /// </summary>
+ partial void SetKind(global::Jellyfin.Data.Enums.PermissionKind oldValue, ref global::Jellyfin.Data.Enums.PermissionKind newValue);
+ /// <summary>
+ /// When provided in a partial class, allows value of Kind to be changed before returning.
+ /// </summary>
+ partial void GetKind(ref global::Jellyfin.Data.Enums.PermissionKind result);
+
+ /// <summary>
+ /// Required
+ /// </summary>
+ [Required]
+ public global::Jellyfin.Data.Enums.PermissionKind Kind
+ {
+ get
+ {
+ global::Jellyfin.Data.Enums.PermissionKind value = _Kind;
+ GetKind(ref value);
+ return (_Kind = value);
+ }
+ set
+ {
+ global::Jellyfin.Data.Enums.PermissionKind oldValue = _Kind;
+ SetKind(oldValue, ref value);
+ if (oldValue != value)
+ {
+ _Kind = value;
+ OnPropertyChanged();
+ }
+ }
+ }
+
+ /// <summary>
+ /// Required
+ /// </summary>
+ [Required]
+ public bool Value { get; set; }
+
+ /// <summary>
+ /// Concurrency token
+ /// </summary>
+ [Timestamp]
+ public Byte[] Timestamp { get; set; }
+
+ /*************************************************************************
+ * Navigation properties
+ *************************************************************************/
+
+ public virtual event PropertyChangedEventHandler PropertyChanged;
+
+ protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
+ {
+ PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
+ }
+
+ }
+}
+
diff --git a/Jellyfin.Data/Entities/PermissionKind.cs b/Jellyfin.Data/Entities/PermissionKind.cs
new file mode 100644
index 000000000..971298674
--- /dev/null
+++ b/Jellyfin.Data/Entities/PermissionKind.cs
@@ -0,0 +1,40 @@
+//------------------------------------------------------------------------------
+// <auto-generated>
+// This code was generated from a template.
+//
+// Manual changes to this file may cause unexpected behavior in your application.
+// Manual changes to this file will be overwritten if the code is regenerated.
+//
+// Produced by Entity Framework Visual Editor
+// https://github.com/msawczyn/EFDesigner
+// </auto-generated>
+//------------------------------------------------------------------------------
+
+using System;
+
+namespace Jellyfin.Data.Enums
+{
+ public enum PermissionKind : Int32
+ {
+ IsAdministrator,
+ IsHidden,
+ IsDisabled,
+ BlockUnrateditems,
+ EnbleSharedDeviceControl,
+ EnableRemoteAccess,
+ EnableLiveTvManagement,
+ EnableLiveTvAccess,
+ EnableMediaPlayback,
+ EnableAudioPlaybackTranscoding,
+ EnableVideoPlaybackTranscoding,
+ EnableContentDeletion,
+ EnableContentDownloading,
+ EnableSyncTranscoding,
+ EnableMediaConversion,
+ EnableAllDevices,
+ EnableAllChannels,
+ EnableAllFolders,
+ EnablePublicSharing,
+ AccessSchedules
+ }
+}
diff --git a/Jellyfin.Data/Entities/Person.cs b/Jellyfin.Data/Entities/Person.cs
new file mode 100644
index 000000000..3437b9581
--- /dev/null
+++ b/Jellyfin.Data/Entities/Person.cs
@@ -0,0 +1,312 @@
+//------------------------------------------------------------------------------
+// <auto-generated>
+// This code was generated from a template.
+//
+// Manual changes to this file may cause unexpected behavior in your application.
+// Manual changes to this file will be overwritten if the code is regenerated.
+//
+// Produced by Entity Framework Visual Editor
+// https://github.com/msawczyn/EFDesigner
+// </auto-generated>
+//------------------------------------------------------------------------------
+
+using System;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.ComponentModel;
+using System.ComponentModel.DataAnnotations;
+using System.ComponentModel.DataAnnotations.Schema;
+using System.Linq;
+using System.Runtime.CompilerServices;
+
+namespace Jellyfin.Data.Entities
+{
+ public partial class Person
+ {
+ partial void Init();
+
+ /// <summary>
+ /// Default constructor. Protected due to required properties, but present because EF needs it.
+ /// </summary>
+ protected Person()
+ {
+ Sources = new System.Collections.Generic.HashSet<global::Jellyfin.Data.Entities.MetadataProviderId>();
+
+ Init();
+ }
+
+ /// <summary>
+ /// Replaces default constructor, since it's protected. Caller assumes responsibility for setting all required values before saving.
+ /// </summary>
+ public static Person CreatePersonUnsafe()
+ {
+ return new Person();
+ }
+
+ /// <summary>
+ /// Public constructor with required data
+ /// </summary>
+ /// <param name="urlid"></param>
+ /// <param name="name"></param>
+ public Person(Guid urlid, string name, DateTime dateadded, DateTime datemodified)
+ {
+ this.UrlId = urlid;
+
+ if (string.IsNullOrEmpty(name)) throw new ArgumentNullException(nameof(name));
+ this.Name = name;
+
+ this.Sources = new System.Collections.Generic.HashSet<global::Jellyfin.Data.Entities.MetadataProviderId>();
+
+ Init();
+ }
+
+ /// <summary>
+ /// Static create function (for use in LINQ queries, etc.)
+ /// </summary>
+ /// <param name="urlid"></param>
+ /// <param name="name"></param>
+ public static Person Create(Guid urlid, string name, DateTime dateadded, DateTime datemodified)
+ {
+ return new Person(urlid, name, dateadded, datemodified);
+ }
+
+ /*************************************************************************
+ * Properties
+ *************************************************************************/
+
+ /// <summary>
+ /// Backing field for Id
+ /// </summary>
+ internal int _Id;
+ /// <summary>
+ /// When provided in a partial class, allows value of Id to be changed before setting.
+ /// </summary>
+ partial void SetId(int oldValue, ref int newValue);
+ /// <summary>
+ /// When provided in a partial class, allows value of Id to be changed before returning.
+ /// </summary>
+ partial void GetId(ref int result);
+
+ /// <summary>
+ /// Identity, Indexed, Required
+ /// </summary>
+ [Key]
+ [Required]
+ public int Id
+ {
+ get
+ {
+ int value = _Id;
+ GetId(ref value);
+ return (_Id = value);
+ }
+ protected set
+ {
+ int oldValue = _Id;
+ SetId(oldValue, ref value);
+ if (oldValue != value)
+ {
+ _Id = value;
+ }
+ }
+ }
+
+ /// <summary>
+ /// Backing field for UrlId
+ /// </summary>
+ protected Guid _UrlId;
+ /// <summary>
+ /// When provided in a partial class, allows value of UrlId to be changed before setting.
+ /// </summary>
+ partial void SetUrlId(Guid oldValue, ref Guid newValue);
+ /// <summary>
+ /// When provided in a partial class, allows value of UrlId to be changed before returning.
+ /// </summary>
+ partial void GetUrlId(ref Guid result);
+
+ /// <summary>
+ /// Required
+ /// </summary>
+ [Required]
+ public Guid UrlId
+ {
+ get
+ {
+ Guid value = _UrlId;
+ GetUrlId(ref value);
+ return (_UrlId = value);
+ }
+ set
+ {
+ Guid oldValue = _UrlId;
+ SetUrlId(oldValue, ref value);
+ if (oldValue != value)
+ {
+ _UrlId = value;
+ }
+ }
+ }
+
+ /// <summary>
+ /// Backing field for Name
+ /// </summary>
+ protected string _Name;
+ /// <summary>
+ /// When provided in a partial class, allows value of Name to be changed before setting.
+ /// </summary>
+ partial void SetName(string oldValue, ref string newValue);
+ /// <summary>
+ /// When provided in a partial class, allows value of Name to be changed before returning.
+ /// </summary>
+ partial void GetName(ref string result);
+
+ /// <summary>
+ /// Required, Max length = 1024
+ /// </summary>
+ [Required]
+ [MaxLength(1024)]
+ [StringLength(1024)]
+ public string Name
+ {
+ get
+ {
+ string value = _Name;
+ GetName(ref value);
+ return (_Name = value);
+ }
+ set
+ {
+ string oldValue = _Name;
+ SetName(oldValue, ref value);
+ if (oldValue != value)
+ {
+ _Name = value;
+ }
+ }
+ }
+
+ /// <summary>
+ /// Backing field for SourceId
+ /// </summary>
+ protected string _SourceId;
+ /// <summary>
+ /// When provided in a partial class, allows value of SourceId to be changed before setting.
+ /// </summary>
+ partial void SetSourceId(string oldValue, ref string newValue);
+ /// <summary>
+ /// When provided in a partial class, allows value of SourceId to be changed before returning.
+ /// </summary>
+ partial void GetSourceId(ref string result);
+
+ /// <summary>
+ /// Max length = 255
+ /// </summary>
+ [MaxLength(255)]
+ [StringLength(255)]
+ public string SourceId
+ {
+ get
+ {
+ string value = _SourceId;
+ GetSourceId(ref value);
+ return (_SourceId = value);
+ }
+ set
+ {
+ string oldValue = _SourceId;
+ SetSourceId(oldValue, ref value);
+ if (oldValue != value)
+ {
+ _SourceId = value;
+ }
+ }
+ }
+
+ /// <summary>
+ /// Backing field for DateAdded
+ /// </summary>
+ protected DateTime _DateAdded;
+ /// <summary>
+ /// When provided in a partial class, allows value of DateAdded to be changed before setting.
+ /// </summary>
+ partial void SetDateAdded(DateTime oldValue, ref DateTime newValue);
+ /// <summary>
+ /// When provided in a partial class, allows value of DateAdded to be changed before returning.
+ /// </summary>
+ partial void GetDateAdded(ref DateTime result);
+
+ /// <summary>
+ /// Required
+ /// </summary>
+ [Required]
+ public DateTime DateAdded
+ {
+ get
+ {
+ DateTime value = _DateAdded;
+ GetDateAdded(ref value);
+ return (_DateAdded = value);
+ }
+ internal set
+ {
+ DateTime oldValue = _DateAdded;
+ SetDateAdded(oldValue, ref value);
+ if (oldValue != value)
+ {
+ _DateAdded = value;
+ }
+ }
+ }
+
+ /// <summary>
+ /// Backing field for DateModified
+ /// </summary>
+ protected DateTime _DateModified;
+ /// <summary>
+ /// When provided in a partial class, allows value of DateModified to be changed before setting.
+ /// </summary>
+ partial void SetDateModified(DateTime oldValue, ref DateTime newValue);
+ /// <summary>
+ /// When provided in a partial class, allows value of DateModified to be changed before returning.
+ /// </summary>
+ partial void GetDateModified(ref DateTime result);
+
+ /// <summary>
+ /// Required
+ /// </summary>
+ [Required]
+ public DateTime DateModified
+ {
+ get
+ {
+ DateTime value = _DateModified;
+ GetDateModified(ref value);
+ return (_DateModified = value);
+ }
+ internal set
+ {
+ DateTime oldValue = _DateModified;
+ SetDateModified(oldValue, ref value);
+ if (oldValue != value)
+ {
+ _DateModified = value;
+ }
+ }
+ }
+
+ /// <summary>
+ /// Required
+ /// </summary>
+ [ConcurrencyCheck]
+ [Required]
+ public byte[] Timestamp { get; set; }
+
+ /*************************************************************************
+ * Navigation properties
+ *************************************************************************/
+
+ public virtual ICollection<global::Jellyfin.Data.Entities.MetadataProviderId> Sources { get; protected set; }
+
+ }
+}
+
diff --git a/Jellyfin.Data/Entities/PersonRole.cs b/Jellyfin.Data/Entities/PersonRole.cs
new file mode 100644
index 000000000..d8e2dbc11
--- /dev/null
+++ b/Jellyfin.Data/Entities/PersonRole.cs
@@ -0,0 +1,215 @@
+//------------------------------------------------------------------------------
+// <auto-generated>
+// This code was generated from a template.
+//
+// Manual changes to this file may cause unexpected behavior in your application.
+// Manual changes to this file will be overwritten if the code is regenerated.
+//
+// Produced by Entity Framework Visual Editor
+// https://github.com/msawczyn/EFDesigner
+// </auto-generated>
+//------------------------------------------------------------------------------
+
+using System;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.ComponentModel;
+using System.ComponentModel.DataAnnotations;
+using System.ComponentModel.DataAnnotations.Schema;
+using System.Linq;
+using System.Runtime.CompilerServices;
+
+namespace Jellyfin.Data.Entities
+{
+ public partial class PersonRole
+ {
+ partial void Init();
+
+ /// <summary>
+ /// Default constructor. Protected due to required properties, but present because EF needs it.
+ /// </summary>
+ protected PersonRole()
+ {
+ // NOTE: This class has one-to-one associations with PersonRole.
+ // One-to-one associations are not validated in constructors since this causes a scenario where each one must be constructed before the other.
+
+ Sources = new System.Collections.Generic.HashSet<global::Jellyfin.Data.Entities.MetadataProviderId>();
+
+ Init();
+ }
+
+ /// <summary>
+ /// Replaces default constructor, since it's protected. Caller assumes responsibility for setting all required values before saving.
+ /// </summary>
+ public static PersonRole CreatePersonRoleUnsafe()
+ {
+ return new PersonRole();
+ }
+
+ /// <summary>
+ /// Public constructor with required data
+ /// </summary>
+ /// <param name="type"></param>
+ /// <param name="_metadata0"></param>
+ public PersonRole(global::Jellyfin.Data.Enums.PersonRoleType type, global::Jellyfin.Data.Entities.Metadata _metadata0)
+ {
+ // NOTE: This class has one-to-one associations with PersonRole.
+ // One-to-one associations are not validated in constructors since this causes a scenario where each one must be constructed before the other.
+
+ this.Type = type;
+
+ if (_metadata0 == null) throw new ArgumentNullException(nameof(_metadata0));
+ _metadata0.PersonRoles.Add(this);
+
+ this.Sources = new System.Collections.Generic.HashSet<global::Jellyfin.Data.Entities.MetadataProviderId>();
+
+ Init();
+ }
+
+ /// <summary>
+ /// Static create function (for use in LINQ queries, etc.)
+ /// </summary>
+ /// <param name="type"></param>
+ /// <param name="_metadata0"></param>
+ public static PersonRole Create(global::Jellyfin.Data.Enums.PersonRoleType type, global::Jellyfin.Data.Entities.Metadata _metadata0)
+ {
+ return new PersonRole(type, _metadata0);
+ }
+
+ /*************************************************************************
+ * Properties
+ *************************************************************************/
+
+ /// <summary>
+ /// Backing field for Id
+ /// </summary>
+ internal int _Id;
+ /// <summary>
+ /// When provided in a partial class, allows value of Id to be changed before setting.
+ /// </summary>
+ partial void SetId(int oldValue, ref int newValue);
+ /// <summary>
+ /// When provided in a partial class, allows value of Id to be changed before returning.
+ /// </summary>
+ partial void GetId(ref int result);
+
+ /// <summary>
+ /// Identity, Indexed, Required
+ /// </summary>
+ [Key]
+ [Required]
+ public int Id
+ {
+ get
+ {
+ int value = _Id;
+ GetId(ref value);
+ return (_Id = value);
+ }
+ protected set
+ {
+ int oldValue = _Id;
+ SetId(oldValue, ref value);
+ if (oldValue != value)
+ {
+ _Id = value;
+ }
+ }
+ }
+
+ /// <summary>
+ /// Backing field for Role
+ /// </summary>
+ protected string _Role;
+ /// <summary>
+ /// When provided in a partial class, allows value of Role to be changed before setting.
+ /// </summary>
+ partial void SetRole(string oldValue, ref string newValue);
+ /// <summary>
+ /// When provided in a partial class, allows value of Role to be changed before returning.
+ /// </summary>
+ partial void GetRole(ref string result);
+
+ /// <summary>
+ /// Max length = 1024
+ /// </summary>
+ [MaxLength(1024)]
+ [StringLength(1024)]
+ public string Role
+ {
+ get
+ {
+ string value = _Role;
+ GetRole(ref value);
+ return (_Role = value);
+ }
+ set
+ {
+ string oldValue = _Role;
+ SetRole(oldValue, ref value);
+ if (oldValue != value)
+ {
+ _Role = value;
+ }
+ }
+ }
+
+ /// <summary>
+ /// Backing field for Type
+ /// </summary>
+ protected global::Jellyfin.Data.Enums.PersonRoleType _Type;
+ /// <summary>
+ /// When provided in a partial class, allows value of Type to be changed before setting.
+ /// </summary>
+ partial void SetType(global::Jellyfin.Data.Enums.PersonRoleType oldValue, ref global::Jellyfin.Data.Enums.PersonRoleType newValue);
+ /// <summary>
+ /// When provided in a partial class, allows value of Type to be changed before returning.
+ /// </summary>
+ partial void GetType(ref global::Jellyfin.Data.Enums.PersonRoleType result);
+
+ /// <summary>
+ /// Required
+ /// </summary>
+ [Required]
+ public global::Jellyfin.Data.Enums.PersonRoleType Type
+ {
+ get
+ {
+ global::Jellyfin.Data.Enums.PersonRoleType value = _Type;
+ GetType(ref value);
+ return (_Type = value);
+ }
+ set
+ {
+ global::Jellyfin.Data.Enums.PersonRoleType oldValue = _Type;
+ SetType(oldValue, ref value);
+ if (oldValue != value)
+ {
+ _Type = value;
+ }
+ }
+ }
+
+ /// <summary>
+ /// Required
+ /// </summary>
+ [ConcurrencyCheck]
+ [Required]
+ public byte[] Timestamp { get; set; }
+
+ /*************************************************************************
+ * Navigation properties
+ *************************************************************************/
+
+ /// <summary>
+ /// Required
+ /// </summary>
+ public virtual global::Jellyfin.Data.Entities.Person Person { get; set; }
+
+ public virtual global::Jellyfin.Data.Entities.Artwork Artwork { get; set; }
+
+ public virtual ICollection<global::Jellyfin.Data.Entities.MetadataProviderId> Sources { get; protected set; }
+
+ }
+}
+
diff --git a/Jellyfin.Data/Entities/Photo.cs b/Jellyfin.Data/Entities/Photo.cs
new file mode 100644
index 000000000..16c97fef5
--- /dev/null
+++ b/Jellyfin.Data/Entities/Photo.cs
@@ -0,0 +1,84 @@
+//------------------------------------------------------------------------------
+// <auto-generated>
+// This code was generated from a template.
+//
+// Manual changes to this file may cause unexpected behavior in your application.
+// Manual changes to this file will be overwritten if the code is regenerated.
+//
+// Produced by Entity Framework Visual Editor
+// https://github.com/msawczyn/EFDesigner
+// </auto-generated>
+//------------------------------------------------------------------------------
+
+using System;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.ComponentModel;
+using System.ComponentModel.DataAnnotations;
+using System.ComponentModel.DataAnnotations.Schema;
+using System.Linq;
+using System.Runtime.CompilerServices;
+
+namespace Jellyfin.Data.Entities
+{
+ public partial class Photo: global::Jellyfin.Data.Entities.LibraryItem
+ {
+ partial void Init();
+
+ /// <summary>
+ /// Default constructor. Protected due to required properties, but present because EF needs it.
+ /// </summary>
+ protected Photo(): base()
+ {
+ PhotoMetadata = new System.Collections.Generic.HashSet<global::Jellyfin.Data.Entities.PhotoMetadata>();
+ Releases = new System.Collections.Generic.HashSet<global::Jellyfin.Data.Entities.Release>();
+
+ Init();
+ }
+
+ /// <summary>
+ /// Replaces default constructor, since it's protected. Caller assumes responsibility for setting all required values before saving.
+ /// </summary>
+ public static Photo CreatePhotoUnsafe()
+ {
+ return new Photo();
+ }
+
+ /// <summary>
+ /// Public constructor with required data
+ /// </summary>
+ /// <param name="urlid">This is whats gets displayed in the Urls and API requests. This could also be a string.</param>
+ public Photo(Guid urlid, DateTime dateadded)
+ {
+ this.UrlId = urlid;
+
+ this.PhotoMetadata = new System.Collections.Generic.HashSet<global::Jellyfin.Data.Entities.PhotoMetadata>();
+ this.Releases = new System.Collections.Generic.HashSet<global::Jellyfin.Data.Entities.Release>();
+
+ Init();
+ }
+
+ /// <summary>
+ /// Static create function (for use in LINQ queries, etc.)
+ /// </summary>
+ /// <param name="urlid">This is whats gets displayed in the Urls and API requests. This could also be a string.</param>
+ public static Photo Create(Guid urlid, DateTime dateadded)
+ {
+ return new Photo(urlid, dateadded);
+ }
+
+ /*************************************************************************
+ * Properties
+ *************************************************************************/
+
+ /*************************************************************************
+ * Navigation properties
+ *************************************************************************/
+
+ public virtual ICollection<global::Jellyfin.Data.Entities.PhotoMetadata> PhotoMetadata { get; protected set; }
+
+ public virtual ICollection<global::Jellyfin.Data.Entities.Release> Releases { get; protected set; }
+
+ }
+}
+
diff --git a/Jellyfin.Data/Entities/PhotoMetadata.cs b/Jellyfin.Data/Entities/PhotoMetadata.cs
new file mode 100644
index 000000000..9c47d022e
--- /dev/null
+++ b/Jellyfin.Data/Entities/PhotoMetadata.cs
@@ -0,0 +1,86 @@
+//------------------------------------------------------------------------------
+// <auto-generated>
+// This code was generated from a template.
+//
+// Manual changes to this file may cause unexpected behavior in your application.
+// Manual changes to this file will be overwritten if the code is regenerated.
+//
+// Produced by Entity Framework Visual Editor
+// https://github.com/msawczyn/EFDesigner
+// </auto-generated>
+//------------------------------------------------------------------------------
+
+using System;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.ComponentModel;
+using System.ComponentModel.DataAnnotations;
+using System.ComponentModel.DataAnnotations.Schema;
+using System.Linq;
+using System.Runtime.CompilerServices;
+
+namespace Jellyfin.Data.Entities
+{
+ public partial class PhotoMetadata: global::Jellyfin.Data.Entities.Metadata
+ {
+ partial void Init();
+
+ /// <summary>
+ /// Default constructor. Protected due to required properties, but present because EF needs it.
+ /// </summary>
+ protected PhotoMetadata(): base()
+ {
+ Init();
+ }
+
+ /// <summary>
+ /// Replaces default constructor, since it's protected. Caller assumes responsibility for setting all required values before saving.
+ /// </summary>
+ public static PhotoMetadata CreatePhotoMetadataUnsafe()
+ {
+ return new PhotoMetadata();
+ }
+
+ /// <summary>
+ /// Public constructor with required data
+ /// </summary>
+ /// <param name="title">The title or name of the object</param>
+ /// <param name="language">ISO-639-3 3-character language codes</param>
+ /// <param name="_photo0"></param>
+ public PhotoMetadata(string title, string language, DateTime dateadded, DateTime datemodified, global::Jellyfin.Data.Entities.Photo _photo0)
+ {
+ if (string.IsNullOrEmpty(title)) throw new ArgumentNullException(nameof(title));
+ this.Title = title;
+
+ if (string.IsNullOrEmpty(language)) throw new ArgumentNullException(nameof(language));
+ this.Language = language;
+
+ if (_photo0 == null) throw new ArgumentNullException(nameof(_photo0));
+ _photo0.PhotoMetadata.Add(this);
+
+
+ Init();
+ }
+
+ /// <summary>
+ /// Static create function (for use in LINQ queries, etc.)
+ /// </summary>
+ /// <param name="title">The title or name of the object</param>
+ /// <param name="language">ISO-639-3 3-character language codes</param>
+ /// <param name="_photo0"></param>
+ public static PhotoMetadata Create(string title, string language, DateTime dateadded, DateTime datemodified, global::Jellyfin.Data.Entities.Photo _photo0)
+ {
+ return new PhotoMetadata(title, language, dateadded, datemodified, _photo0);
+ }
+
+ /*************************************************************************
+ * Properties
+ *************************************************************************/
+
+ /*************************************************************************
+ * Navigation properties
+ *************************************************************************/
+
+ }
+}
+
diff --git a/Jellyfin.Data/Entities/Preference.cs b/Jellyfin.Data/Entities/Preference.cs
new file mode 100644
index 000000000..3d69ea2f3
--- /dev/null
+++ b/Jellyfin.Data/Entities/Preference.cs
@@ -0,0 +1,117 @@
+//------------------------------------------------------------------------------
+// <auto-generated>
+// This code was generated from a template.
+//
+// Manual changes to this file may cause unexpected behavior in your application.
+// Manual changes to this file will be overwritten if the code is regenerated.
+//
+// Produced by Entity Framework Visual Editor
+// https://github.com/msawczyn/EFDesigner
+// </auto-generated>
+//------------------------------------------------------------------------------
+
+using System;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.ComponentModel;
+using System.ComponentModel.DataAnnotations;
+using System.ComponentModel.DataAnnotations.Schema;
+using System.Linq;
+using System.Runtime.CompilerServices;
+
+namespace Jellyfin.Data.Entities
+{
+ public partial class Preference
+ {
+ partial void Init();
+
+ /// <summary>
+ /// Default constructor. Protected due to required properties, but present because EF needs it.
+ /// </summary>
+ protected Preference()
+ {
+ Init();
+ }
+
+ /// <summary>
+ /// Replaces default constructor, since it's protected. Caller assumes responsibility for setting all required values before saving.
+ /// </summary>
+ public static Preference CreatePreferenceUnsafe()
+ {
+ return new Preference();
+ }
+
+ /// <summary>
+ /// Public constructor with required data
+ /// </summary>
+ /// <param name="kind"></param>
+ /// <param name="value"></param>
+ /// <param name="_user0"></param>
+ /// <param name="_group1"></param>
+ public Preference(global::Jellyfin.Data.Enums.PreferenceKind kind, string value, global::Jellyfin.Data.Entities.User _user0, global::Jellyfin.Data.Entities.Group _group1)
+ {
+ this.Kind = kind;
+
+ if (string.IsNullOrEmpty(value)) throw new ArgumentNullException(nameof(value));
+ this.Value = value;
+
+ if (_user0 == null) throw new ArgumentNullException(nameof(_user0));
+ _user0.Preferences.Add(this);
+
+ if (_group1 == null) throw new ArgumentNullException(nameof(_group1));
+ _group1.Preferences.Add(this);
+
+
+ Init();
+ }
+
+ /// <summary>
+ /// Static create function (for use in LINQ queries, etc.)
+ /// </summary>
+ /// <param name="kind"></param>
+ /// <param name="value"></param>
+ /// <param name="_user0"></param>
+ /// <param name="_group1"></param>
+ public static Preference Create(global::Jellyfin.Data.Enums.PreferenceKind kind, string value, global::Jellyfin.Data.Entities.User _user0, global::Jellyfin.Data.Entities.Group _group1)
+ {
+ return new Preference(kind, value, _user0, _group1);
+ }
+
+ /*************************************************************************
+ * Properties
+ *************************************************************************/
+
+ /// <summary>
+ /// Identity, Indexed, Required
+ /// </summary>
+ [Key]
+ [Required]
+ public int Id { get; protected set; }
+
+ /// <summary>
+ /// Required
+ /// </summary>
+ [Required]
+ public global::Jellyfin.Data.Enums.PreferenceKind Kind { get; set; }
+
+ /// <summary>
+ /// Required, Max length = 65535
+ /// </summary>
+ [Required]
+ [MaxLength(65535)]
+ [StringLength(65535)]
+ public string Value { get; set; }
+
+ /// <summary>
+ /// Concurrency token
+ /// </summary>
+ [Timestamp]
+ public Byte[] Timestamp { get; set; }
+
+ /*************************************************************************
+ * Navigation properties
+ *************************************************************************/
+
+ }
+}
+
diff --git a/Jellyfin.Data/Entities/PreferenceKind.cs b/Jellyfin.Data/Entities/PreferenceKind.cs
new file mode 100644
index 000000000..e6673afb1
--- /dev/null
+++ b/Jellyfin.Data/Entities/PreferenceKind.cs
@@ -0,0 +1,27 @@
+//------------------------------------------------------------------------------
+// <auto-generated>
+// This code was generated from a template.
+//
+// Manual changes to this file may cause unexpected behavior in your application.
+// Manual changes to this file will be overwritten if the code is regenerated.
+//
+// Produced by Entity Framework Visual Editor
+// https://github.com/msawczyn/EFDesigner
+// </auto-generated>
+//------------------------------------------------------------------------------
+
+using System;
+
+namespace Jellyfin.Data.Enums
+{
+ public enum PreferenceKind : Int32
+ {
+ MaxParentalRating,
+ BlockedTags,
+ RemoteClientBitrateLimit,
+ EnabledDevices,
+ EnabledChannels,
+ EnabledFolders,
+ EnableContentDeletionFromFolders
+ }
+}
diff --git a/Jellyfin.Data/Entities/ProviderMapping.cs b/Jellyfin.Data/Entities/ProviderMapping.cs
new file mode 100644
index 000000000..e50a01489
--- /dev/null
+++ b/Jellyfin.Data/Entities/ProviderMapping.cs
@@ -0,0 +1,133 @@
+//------------------------------------------------------------------------------
+// <auto-generated>
+// This code was generated from a template.
+//
+// Manual changes to this file may cause unexpected behavior in your application.
+// Manual changes to this file will be overwritten if the code is regenerated.
+//
+// Produced by Entity Framework Visual Editor
+// https://github.com/msawczyn/EFDesigner
+// </auto-generated>
+//------------------------------------------------------------------------------
+
+using System;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.ComponentModel;
+using System.ComponentModel.DataAnnotations;
+using System.ComponentModel.DataAnnotations.Schema;
+using System.Linq;
+using System.Runtime.CompilerServices;
+
+namespace Jellyfin.Data.Entities
+{
+ public partial class ProviderMapping
+ {
+ partial void Init();
+
+ /// <summary>
+ /// Default constructor. Protected due to required properties, but present because EF needs it.
+ /// </summary>
+ protected ProviderMapping()
+ {
+ Init();
+ }
+
+ /// <summary>
+ /// Replaces default constructor, since it's protected. Caller assumes responsibility for setting all required values before saving.
+ /// </summary>
+ public static ProviderMapping CreateProviderMappingUnsafe()
+ {
+ return new ProviderMapping();
+ }
+
+ /// <summary>
+ /// Public constructor with required data
+ /// </summary>
+ /// <param name="providername"></param>
+ /// <param name="providersecrets"></param>
+ /// <param name="providerdata"></param>
+ /// <param name="_user0"></param>
+ /// <param name="_group1"></param>
+ public ProviderMapping(string providername, string providersecrets, string providerdata, global::Jellyfin.Data.Entities.User _user0, global::Jellyfin.Data.Entities.Group _group1)
+ {
+ if (string.IsNullOrEmpty(providername)) throw new ArgumentNullException(nameof(providername));
+ this.ProviderName = providername;
+
+ if (string.IsNullOrEmpty(providersecrets)) throw new ArgumentNullException(nameof(providersecrets));
+ this.ProviderSecrets = providersecrets;
+
+ if (string.IsNullOrEmpty(providerdata)) throw new ArgumentNullException(nameof(providerdata));
+ this.ProviderData = providerdata;
+
+ if (_user0 == null) throw new ArgumentNullException(nameof(_user0));
+ _user0.ProviderMappings.Add(this);
+
+ if (_group1 == null) throw new ArgumentNullException(nameof(_group1));
+ _group1.ProviderMappings.Add(this);
+
+
+ Init();
+ }
+
+ /// <summary>
+ /// Static create function (for use in LINQ queries, etc.)
+ /// </summary>
+ /// <param name="providername"></param>
+ /// <param name="providersecrets"></param>
+ /// <param name="providerdata"></param>
+ /// <param name="_user0"></param>
+ /// <param name="_group1"></param>
+ public static ProviderMapping Create(string providername, string providersecrets, string providerdata, global::Jellyfin.Data.Entities.User _user0, global::Jellyfin.Data.Entities.Group _group1)
+ {
+ return new ProviderMapping(providername, providersecrets, providerdata, _user0, _group1);
+ }
+
+ /*************************************************************************
+ * Properties
+ *************************************************************************/
+
+ /// <summary>
+ /// Identity, Indexed, Required
+ /// </summary>
+ [Key]
+ [Required]
+ public int Id { get; protected set; }
+
+ /// <summary>
+ /// Required, Max length = 255
+ /// </summary>
+ [Required]
+ [MaxLength(255)]
+ [StringLength(255)]
+ public string ProviderName { get; set; }
+
+ /// <summary>
+ /// Required, Max length = 65535
+ /// </summary>
+ [Required]
+ [MaxLength(65535)]
+ [StringLength(65535)]
+ public string ProviderSecrets { get; set; }
+
+ /// <summary>
+ /// Required, Max length = 65535
+ /// </summary>
+ [Required]
+ [MaxLength(65535)]
+ [StringLength(65535)]
+ public string ProviderData { get; set; }
+
+ /// <summary>
+ /// Concurrency token
+ /// </summary>
+ [Timestamp]
+ public Byte[] Timestamp { get; set; }
+
+ /*************************************************************************
+ * Navigation properties
+ *************************************************************************/
+
+ }
+}
+
diff --git a/Jellyfin.Data/Entities/Rating.cs b/Jellyfin.Data/Entities/Rating.cs
new file mode 100644
index 000000000..b1098a1d7
--- /dev/null
+++ b/Jellyfin.Data/Entities/Rating.cs
@@ -0,0 +1,197 @@
+//------------------------------------------------------------------------------
+// <auto-generated>
+// This code was generated from a template.
+//
+// Manual changes to this file may cause unexpected behavior in your application.
+// Manual changes to this file will be overwritten if the code is regenerated.
+//
+// Produced by Entity Framework Visual Editor
+// https://github.com/msawczyn/EFDesigner
+// </auto-generated>
+//------------------------------------------------------------------------------
+
+using System;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.ComponentModel;
+using System.ComponentModel.DataAnnotations;
+using System.ComponentModel.DataAnnotations.Schema;
+using System.Linq;
+using System.Runtime.CompilerServices;
+
+namespace Jellyfin.Data.Entities
+{
+ public partial class Rating
+ {
+ partial void Init();
+
+ /// <summary>
+ /// Default constructor. Protected due to required properties, but present because EF needs it.
+ /// </summary>
+ protected Rating()
+ {
+ Init();
+ }
+
+ /// <summary>
+ /// Replaces default constructor, since it's protected. Caller assumes responsibility for setting all required values before saving.
+ /// </summary>
+ public static Rating CreateRatingUnsafe()
+ {
+ return new Rating();
+ }
+
+ /// <summary>
+ /// Public constructor with required data
+ /// </summary>
+ /// <param name="value"></param>
+ /// <param name="_metadata0"></param>
+ public Rating(double value, global::Jellyfin.Data.Entities.Metadata _metadata0)
+ {
+ this.Value = value;
+
+ if (_metadata0 == null) throw new ArgumentNullException(nameof(_metadata0));
+ _metadata0.Ratings.Add(this);
+
+
+ Init();
+ }
+
+ /// <summary>
+ /// Static create function (for use in LINQ queries, etc.)
+ /// </summary>
+ /// <param name="value"></param>
+ /// <param name="_metadata0"></param>
+ public static Rating Create(double value, global::Jellyfin.Data.Entities.Metadata _metadata0)
+ {
+ return new Rating(value, _metadata0);
+ }
+
+ /*************************************************************************
+ * Properties
+ *************************************************************************/
+
+ /// <summary>
+ /// Backing field for Id
+ /// </summary>
+ internal int _Id;
+ /// <summary>
+ /// When provided in a partial class, allows value of Id to be changed before setting.
+ /// </summary>
+ partial void SetId(int oldValue, ref int newValue);
+ /// <summary>
+ /// When provided in a partial class, allows value of Id to be changed before returning.
+ /// </summary>
+ partial void GetId(ref int result);
+
+ /// <summary>
+ /// Identity, Indexed, Required
+ /// </summary>
+ [Key]
+ [Required]
+ public int Id
+ {
+ get
+ {
+ int value = _Id;
+ GetId(ref value);
+ return (_Id = value);
+ }
+ protected set
+ {
+ int oldValue = _Id;
+ SetId(oldValue, ref value);
+ if (oldValue != value)
+ {
+ _Id = value;
+ }
+ }
+ }
+
+ /// <summary>
+ /// Backing field for Value
+ /// </summary>
+ protected double _Value;
+ /// <summary>
+ /// When provided in a partial class, allows value of Value to be changed before setting.
+ /// </summary>
+ partial void SetValue(double oldValue, ref double newValue);
+ /// <summary>
+ /// When provided in a partial class, allows value of Value to be changed before returning.
+ /// </summary>
+ partial void GetValue(ref double result);
+
+ /// <summary>
+ /// Required
+ /// </summary>
+ [Required]
+ public double Value
+ {
+ get
+ {
+ double value = _Value;
+ GetValue(ref value);
+ return (_Value = value);
+ }
+ set
+ {
+ double oldValue = _Value;
+ SetValue(oldValue, ref value);
+ if (oldValue != value)
+ {
+ _Value = value;
+ }
+ }
+ }
+
+ /// <summary>
+ /// Backing field for Votes
+ /// </summary>
+ protected int? _Votes;
+ /// <summary>
+ /// When provided in a partial class, allows value of Votes to be changed before setting.
+ /// </summary>
+ partial void SetVotes(int? oldValue, ref int? newValue);
+ /// <summary>
+ /// When provided in a partial class, allows value of Votes to be changed before returning.
+ /// </summary>
+ partial void GetVotes(ref int? result);
+
+ public int? Votes
+ {
+ get
+ {
+ int? value = _Votes;
+ GetVotes(ref value);
+ return (_Votes = value);
+ }
+ set
+ {
+ int? oldValue = _Votes;
+ SetVotes(oldValue, ref value);
+ if (oldValue != value)
+ {
+ _Votes = value;
+ }
+ }
+ }
+
+ /// <summary>
+ /// Required
+ /// </summary>
+ [ConcurrencyCheck]
+ [Required]
+ public byte[] Timestamp { get; set; }
+
+ /*************************************************************************
+ * Navigation properties
+ *************************************************************************/
+
+ /// <summary>
+ /// If this is NULL it&apos;s the internal user rating.
+ /// </summary>
+ public virtual global::Jellyfin.Data.Entities.RatingSource RatingType { get; set; }
+
+ }
+}
+
diff --git a/Jellyfin.Data/Entities/RatingSource.cs b/Jellyfin.Data/Entities/RatingSource.cs
new file mode 100644
index 000000000..32d5634c2
--- /dev/null
+++ b/Jellyfin.Data/Entities/RatingSource.cs
@@ -0,0 +1,242 @@
+//------------------------------------------------------------------------------
+// <auto-generated>
+// This code was generated from a template.
+//
+// Manual changes to this file may cause unexpected behavior in your application.
+// Manual changes to this file will be overwritten if the code is regenerated.
+//
+// Produced by Entity Framework Visual Editor
+// https://github.com/msawczyn/EFDesigner
+// </auto-generated>
+//------------------------------------------------------------------------------
+
+using System;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.ComponentModel;
+using System.ComponentModel.DataAnnotations;
+using System.ComponentModel.DataAnnotations.Schema;
+using System.Linq;
+using System.Runtime.CompilerServices;
+
+namespace Jellyfin.Data.Entities
+{
+ /// <summary>
+ /// This is the entity to store review ratings, not age ratings
+ /// </summary>
+ public partial class RatingSource
+ {
+ partial void Init();
+
+ /// <summary>
+ /// Default constructor. Protected due to required properties, but present because EF needs it.
+ /// </summary>
+ protected RatingSource()
+ {
+ Init();
+ }
+
+ /// <summary>
+ /// Replaces default constructor, since it's protected. Caller assumes responsibility for setting all required values before saving.
+ /// </summary>
+ public static RatingSource CreateRatingSourceUnsafe()
+ {
+ return new RatingSource();
+ }
+
+ /// <summary>
+ /// Public constructor with required data
+ /// </summary>
+ /// <param name="maximumvalue"></param>
+ /// <param name="minimumvalue"></param>
+ /// <param name="_rating0"></param>
+ public RatingSource(double maximumvalue, double minimumvalue, global::Jellyfin.Data.Entities.Rating _rating0)
+ {
+ this.MaximumValue = maximumvalue;
+
+ this.MinimumValue = minimumvalue;
+
+ if (_rating0 == null) throw new ArgumentNullException(nameof(_rating0));
+ _rating0.RatingType = this;
+
+
+ Init();
+ }
+
+ /// <summary>
+ /// Static create function (for use in LINQ queries, etc.)
+ /// </summary>
+ /// <param name="maximumvalue"></param>
+ /// <param name="minimumvalue"></param>
+ /// <param name="_rating0"></param>
+ public static RatingSource Create(double maximumvalue, double minimumvalue, global::Jellyfin.Data.Entities.Rating _rating0)
+ {
+ return new RatingSource(maximumvalue, minimumvalue, _rating0);
+ }
+
+ /*************************************************************************
+ * Properties
+ *************************************************************************/
+
+ /// <summary>
+ /// Backing field for Id
+ /// </summary>
+ internal int _Id;
+ /// <summary>
+ /// When provided in a partial class, allows value of Id to be changed before setting.
+ /// </summary>
+ partial void SetId(int oldValue, ref int newValue);
+ /// <summary>
+ /// When provided in a partial class, allows value of Id to be changed before returning.
+ /// </summary>
+ partial void GetId(ref int result);
+
+ /// <summary>
+ /// Identity, Indexed, Required
+ /// </summary>
+ [Key]
+ [Required]
+ public int Id
+ {
+ get
+ {
+ int value = _Id;
+ GetId(ref value);
+ return (_Id = value);
+ }
+ protected set
+ {
+ int oldValue = _Id;
+ SetId(oldValue, ref value);
+ if (oldValue != value)
+ {
+ _Id = value;
+ }
+ }
+ }
+
+ /// <summary>
+ /// Backing field for Name
+ /// </summary>
+ protected string _Name;
+ /// <summary>
+ /// When provided in a partial class, allows value of Name to be changed before setting.
+ /// </summary>
+ partial void SetName(string oldValue, ref string newValue);
+ /// <summary>
+ /// When provided in a partial class, allows value of Name to be changed before returning.
+ /// </summary>
+ partial void GetName(ref string result);
+
+ /// <summary>
+ /// Max length = 1024
+ /// </summary>
+ [MaxLength(1024)]
+ [StringLength(1024)]
+ public string Name
+ {
+ get
+ {
+ string value = _Name;
+ GetName(ref value);
+ return (_Name = value);
+ }
+ set
+ {
+ string oldValue = _Name;
+ SetName(oldValue, ref value);
+ if (oldValue != value)
+ {
+ _Name = value;
+ }
+ }
+ }
+
+ /// <summary>
+ /// Backing field for MaximumValue
+ /// </summary>
+ protected double _MaximumValue;
+ /// <summary>
+ /// When provided in a partial class, allows value of MaximumValue to be changed before setting.
+ /// </summary>
+ partial void SetMaximumValue(double oldValue, ref double newValue);
+ /// <summary>
+ /// When provided in a partial class, allows value of MaximumValue to be changed before returning.
+ /// </summary>
+ partial void GetMaximumValue(ref double result);
+
+ /// <summary>
+ /// Required
+ /// </summary>
+ [Required]
+ public double MaximumValue
+ {
+ get
+ {
+ double value = _MaximumValue;
+ GetMaximumValue(ref value);
+ return (_MaximumValue = value);
+ }
+ set
+ {
+ double oldValue = _MaximumValue;
+ SetMaximumValue(oldValue, ref value);
+ if (oldValue != value)
+ {
+ _MaximumValue = value;
+ }
+ }
+ }
+
+ /// <summary>
+ /// Backing field for MinimumValue
+ /// </summary>
+ protected double _MinimumValue;
+ /// <summary>
+ /// When provided in a partial class, allows value of MinimumValue to be changed before setting.
+ /// </summary>
+ partial void SetMinimumValue(double oldValue, ref double newValue);
+ /// <summary>
+ /// When provided in a partial class, allows value of MinimumValue to be changed before returning.
+ /// </summary>
+ partial void GetMinimumValue(ref double result);
+
+ /// <summary>
+ /// Required
+ /// </summary>
+ [Required]
+ public double MinimumValue
+ {
+ get
+ {
+ double value = _MinimumValue;
+ GetMinimumValue(ref value);
+ return (_MinimumValue = value);
+ }
+ set
+ {
+ double oldValue = _MinimumValue;
+ SetMinimumValue(oldValue, ref value);
+ if (oldValue != value)
+ {
+ _MinimumValue = value;
+ }
+ }
+ }
+
+ /// <summary>
+ /// Required
+ /// </summary>
+ [ConcurrencyCheck]
+ [Required]
+ public byte[] Timestamp { get; set; }
+
+ /*************************************************************************
+ * Navigation properties
+ *************************************************************************/
+
+ public virtual global::Jellyfin.Data.Entities.MetadataProviderId Source { get; set; }
+
+ }
+}
+
diff --git a/Jellyfin.Data/Entities/Release.cs b/Jellyfin.Data/Entities/Release.cs
new file mode 100644
index 000000000..e02f70be8
--- /dev/null
+++ b/Jellyfin.Data/Entities/Release.cs
@@ -0,0 +1,197 @@
+//------------------------------------------------------------------------------
+// <auto-generated>
+// This code was generated from a template.
+//
+// Manual changes to this file may cause unexpected behavior in your application.
+// Manual changes to this file will be overwritten if the code is regenerated.
+//
+// Produced by Entity Framework Visual Editor
+// https://github.com/msawczyn/EFDesigner
+// </auto-generated>
+//------------------------------------------------------------------------------
+
+using System;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.ComponentModel;
+using System.ComponentModel.DataAnnotations;
+using System.ComponentModel.DataAnnotations.Schema;
+using System.Linq;
+using System.Runtime.CompilerServices;
+
+namespace Jellyfin.Data.Entities
+{
+ public partial class Release
+ {
+ partial void Init();
+
+ /// <summary>
+ /// Default constructor. Protected due to required properties, but present because EF needs it.
+ /// </summary>
+ protected Release()
+ {
+ MediaFiles = new System.Collections.Generic.HashSet<global::Jellyfin.Data.Entities.MediaFile>();
+ Chapters = new System.Collections.Generic.HashSet<global::Jellyfin.Data.Entities.Chapter>();
+
+ Init();
+ }
+
+ /// <summary>
+ /// Replaces default constructor, since it's protected. Caller assumes responsibility for setting all required values before saving.
+ /// </summary>
+ public static Release CreateReleaseUnsafe()
+ {
+ return new Release();
+ }
+
+ /// <summary>
+ /// Public constructor with required data
+ /// </summary>
+ /// <param name="name"></param>
+ /// <param name="_movie0"></param>
+ /// <param name="_episode1"></param>
+ /// <param name="_track2"></param>
+ /// <param name="_customitem3"></param>
+ /// <param name="_book4"></param>
+ /// <param name="_photo5"></param>
+ public Release(string name, global::Jellyfin.Data.Entities.Movie _movie0, global::Jellyfin.Data.Entities.Episode _episode1, global::Jellyfin.Data.Entities.Track _track2, global::Jellyfin.Data.Entities.CustomItem _customitem3, global::Jellyfin.Data.Entities.Book _book4, global::Jellyfin.Data.Entities.Photo _photo5)
+ {
+ if (string.IsNullOrEmpty(name)) throw new ArgumentNullException(nameof(name));
+ this.Name = name;
+
+ if (_movie0 == null) throw new ArgumentNullException(nameof(_movie0));
+ _movie0.Releases.Add(this);
+
+ if (_episode1 == null) throw new ArgumentNullException(nameof(_episode1));
+ _episode1.Releases.Add(this);
+
+ if (_track2 == null) throw new ArgumentNullException(nameof(_track2));
+ _track2.Releases.Add(this);
+
+ if (_customitem3 == null) throw new ArgumentNullException(nameof(_customitem3));
+ _customitem3.Releases.Add(this);
+
+ if (_book4 == null) throw new ArgumentNullException(nameof(_book4));
+ _book4.Releases.Add(this);
+
+ if (_photo5 == null) throw new ArgumentNullException(nameof(_photo5));
+ _photo5.Releases.Add(this);
+
+ this.MediaFiles = new System.Collections.Generic.HashSet<global::Jellyfin.Data.Entities.MediaFile>();
+ this.Chapters = new System.Collections.Generic.HashSet<global::Jellyfin.Data.Entities.Chapter>();
+
+ Init();
+ }
+
+ /// <summary>
+ /// Static create function (for use in LINQ queries, etc.)
+ /// </summary>
+ /// <param name="name"></param>
+ /// <param name="_movie0"></param>
+ /// <param name="_episode1"></param>
+ /// <param name="_track2"></param>
+ /// <param name="_customitem3"></param>
+ /// <param name="_book4"></param>
+ /// <param name="_photo5"></param>
+ public static Release Create(string name, global::Jellyfin.Data.Entities.Movie _movie0, global::Jellyfin.Data.Entities.Episode _episode1, global::Jellyfin.Data.Entities.Track _track2, global::Jellyfin.Data.Entities.CustomItem _customitem3, global::Jellyfin.Data.Entities.Book _book4, global::Jellyfin.Data.Entities.Photo _photo5)
+ {
+ return new Release(name, _movie0, _episode1, _track2, _customitem3, _book4, _photo5);
+ }
+
+ /*************************************************************************
+ * Properties
+ *************************************************************************/
+
+ /// <summary>
+ /// Backing field for Id
+ /// </summary>
+ internal int _Id;
+ /// <summary>
+ /// When provided in a partial class, allows value of Id to be changed before setting.
+ /// </summary>
+ partial void SetId(int oldValue, ref int newValue);
+ /// <summary>
+ /// When provided in a partial class, allows value of Id to be changed before returning.
+ /// </summary>
+ partial void GetId(ref int result);
+
+ /// <summary>
+ /// Identity, Indexed, Required
+ /// </summary>
+ [Key]
+ [Required]
+ public int Id
+ {
+ get
+ {
+ int value = _Id;
+ GetId(ref value);
+ return (_Id = value);
+ }
+ protected set
+ {
+ int oldValue = _Id;
+ SetId(oldValue, ref value);
+ if (oldValue != value)
+ {
+ _Id = value;
+ }
+ }
+ }
+
+ /// <summary>
+ /// Backing field for Name
+ /// </summary>
+ protected string _Name;
+ /// <summary>
+ /// When provided in a partial class, allows value of Name to be changed before setting.
+ /// </summary>
+ partial void SetName(string oldValue, ref string newValue);
+ /// <summary>
+ /// When provided in a partial class, allows value of Name to be changed before returning.
+ /// </summary>
+ partial void GetName(ref string result);
+
+ /// <summary>
+ /// Required, Max length = 1024
+ /// </summary>
+ [Required]
+ [MaxLength(1024)]
+ [StringLength(1024)]
+ public string Name
+ {
+ get
+ {
+ string value = _Name;
+ GetName(ref value);
+ return (_Name = value);
+ }
+ set
+ {
+ string oldValue = _Name;
+ SetName(oldValue, ref value);
+ if (oldValue != value)
+ {
+ _Name = value;
+ }
+ }
+ }
+
+ /// <summary>
+ /// Required
+ /// </summary>
+ [ConcurrencyCheck]
+ [Required]
+ public byte[] Timestamp { get; set; }
+
+ /*************************************************************************
+ * Navigation properties
+ *************************************************************************/
+
+ public virtual ICollection<global::Jellyfin.Data.Entities.MediaFile> MediaFiles { get; protected set; }
+
+ public virtual ICollection<global::Jellyfin.Data.Entities.Chapter> Chapters { get; protected set; }
+
+ }
+}
+
diff --git a/Jellyfin.Data/Entities/Season.cs b/Jellyfin.Data/Entities/Season.cs
new file mode 100644
index 000000000..fdfdf2409
--- /dev/null
+++ b/Jellyfin.Data/Entities/Season.cs
@@ -0,0 +1,127 @@
+//------------------------------------------------------------------------------
+// <auto-generated>
+// This code was generated from a template.
+//
+// Manual changes to this file may cause unexpected behavior in your application.
+// Manual changes to this file will be overwritten if the code is regenerated.
+//
+// Produced by Entity Framework Visual Editor
+// https://github.com/msawczyn/EFDesigner
+// </auto-generated>
+//------------------------------------------------------------------------------
+
+using System;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.ComponentModel;
+using System.ComponentModel.DataAnnotations;
+using System.ComponentModel.DataAnnotations.Schema;
+using System.Linq;
+using System.Runtime.CompilerServices;
+
+namespace Jellyfin.Data.Entities
+{
+ public partial class Season: global::Jellyfin.Data.Entities.LibraryItem
+ {
+ partial void Init();
+
+ /// <summary>
+ /// Default constructor. Protected due to required properties, but present because EF needs it.
+ /// </summary>
+ protected Season(): base()
+ {
+ // NOTE: This class has one-to-one associations with LibraryRoot, LibraryItem and CollectionItem.
+ // One-to-one associations are not validated in constructors since this causes a scenario where each one must be constructed before the other.
+
+ SeasonMetadata = new System.Collections.Generic.HashSet<global::Jellyfin.Data.Entities.SeasonMetadata>();
+ Episodes = new System.Collections.Generic.HashSet<global::Jellyfin.Data.Entities.Episode>();
+
+ Init();
+ }
+
+ /// <summary>
+ /// Replaces default constructor, since it's protected. Caller assumes responsibility for setting all required values before saving.
+ /// </summary>
+ public static Season CreateSeasonUnsafe()
+ {
+ return new Season();
+ }
+
+ /// <summary>
+ /// Public constructor with required data
+ /// </summary>
+ /// <param name="urlid">This is whats gets displayed in the Urls and API requests. This could also be a string.</param>
+ /// <param name="_series0"></param>
+ public Season(Guid urlid, DateTime dateadded, global::Jellyfin.Data.Entities.Series _series0)
+ {
+ // NOTE: This class has one-to-one associations with LibraryRoot, LibraryItem and CollectionItem.
+ // One-to-one associations are not validated in constructors since this causes a scenario where each one must be constructed before the other.
+
+ this.UrlId = urlid;
+
+ if (_series0 == null) throw new ArgumentNullException(nameof(_series0));
+ _series0.Seasons.Add(this);
+
+ this.SeasonMetadata = new System.Collections.Generic.HashSet<global::Jellyfin.Data.Entities.SeasonMetadata>();
+ this.Episodes = new System.Collections.Generic.HashSet<global::Jellyfin.Data.Entities.Episode>();
+
+ Init();
+ }
+
+ /// <summary>
+ /// Static create function (for use in LINQ queries, etc.)
+ /// </summary>
+ /// <param name="urlid">This is whats gets displayed in the Urls and API requests. This could also be a string.</param>
+ /// <param name="_series0"></param>
+ public static Season Create(Guid urlid, DateTime dateadded, global::Jellyfin.Data.Entities.Series _series0)
+ {
+ return new Season(urlid, dateadded, _series0);
+ }
+
+ /*************************************************************************
+ * Properties
+ *************************************************************************/
+
+ /// <summary>
+ /// Backing field for SeasonNumber
+ /// </summary>
+ protected int? _SeasonNumber;
+ /// <summary>
+ /// When provided in a partial class, allows value of SeasonNumber to be changed before setting.
+ /// </summary>
+ partial void SetSeasonNumber(int? oldValue, ref int? newValue);
+ /// <summary>
+ /// When provided in a partial class, allows value of SeasonNumber to be changed before returning.
+ /// </summary>
+ partial void GetSeasonNumber(ref int? result);
+
+ public int? SeasonNumber
+ {
+ get
+ {
+ int? value = _SeasonNumber;
+ GetSeasonNumber(ref value);
+ return (_SeasonNumber = value);
+ }
+ set
+ {
+ int? oldValue = _SeasonNumber;
+ SetSeasonNumber(oldValue, ref value);
+ if (oldValue != value)
+ {
+ _SeasonNumber = value;
+ }
+ }
+ }
+
+ /*************************************************************************
+ * Navigation properties
+ *************************************************************************/
+
+ public virtual ICollection<global::Jellyfin.Data.Entities.SeasonMetadata> SeasonMetadata { get; protected set; }
+
+ public virtual ICollection<global::Jellyfin.Data.Entities.Episode> Episodes { get; protected set; }
+
+ }
+}
+
diff --git a/Jellyfin.Data/Entities/SeasonMetadata.cs b/Jellyfin.Data/Entities/SeasonMetadata.cs
new file mode 100644
index 000000000..5939cbbca
--- /dev/null
+++ b/Jellyfin.Data/Entities/SeasonMetadata.cs
@@ -0,0 +1,123 @@
+//------------------------------------------------------------------------------
+// <auto-generated>
+// This code was generated from a template.
+//
+// Manual changes to this file may cause unexpected behavior in your application.
+// Manual changes to this file will be overwritten if the code is regenerated.
+//
+// Produced by Entity Framework Visual Editor
+// https://github.com/msawczyn/EFDesigner
+// </auto-generated>
+//------------------------------------------------------------------------------
+
+using System;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.ComponentModel;
+using System.ComponentModel.DataAnnotations;
+using System.ComponentModel.DataAnnotations.Schema;
+using System.Linq;
+using System.Runtime.CompilerServices;
+
+namespace Jellyfin.Data.Entities
+{
+ public partial class SeasonMetadata: global::Jellyfin.Data.Entities.Metadata
+ {
+ partial void Init();
+
+ /// <summary>
+ /// Default constructor. Protected due to required properties, but present because EF needs it.
+ /// </summary>
+ protected SeasonMetadata(): base()
+ {
+ Init();
+ }
+
+ /// <summary>
+ /// Replaces default constructor, since it's protected. Caller assumes responsibility for setting all required values before saving.
+ /// </summary>
+ public static SeasonMetadata CreateSeasonMetadataUnsafe()
+ {
+ return new SeasonMetadata();
+ }
+
+ /// <summary>
+ /// Public constructor with required data
+ /// </summary>
+ /// <param name="title">The title or name of the object</param>
+ /// <param name="language">ISO-639-3 3-character language codes</param>
+ /// <param name="_season0"></param>
+ public SeasonMetadata(string title, string language, DateTime dateadded, DateTime datemodified, global::Jellyfin.Data.Entities.Season _season0)
+ {
+ if (string.IsNullOrEmpty(title)) throw new ArgumentNullException(nameof(title));
+ this.Title = title;
+
+ if (string.IsNullOrEmpty(language)) throw new ArgumentNullException(nameof(language));
+ this.Language = language;
+
+ if (_season0 == null) throw new ArgumentNullException(nameof(_season0));
+ _season0.SeasonMetadata.Add(this);
+
+
+ Init();
+ }
+
+ /// <summary>
+ /// Static create function (for use in LINQ queries, etc.)
+ /// </summary>
+ /// <param name="title">The title or name of the object</param>
+ /// <param name="language">ISO-639-3 3-character language codes</param>
+ /// <param name="_season0"></param>
+ public static SeasonMetadata Create(string title, string language, DateTime dateadded, DateTime datemodified, global::Jellyfin.Data.Entities.Season _season0)
+ {
+ return new SeasonMetadata(title, language, dateadded, datemodified, _season0);
+ }
+
+ /*************************************************************************
+ * Properties
+ *************************************************************************/
+
+ /// <summary>
+ /// Backing field for Outline
+ /// </summary>
+ protected string _Outline;
+ /// <summary>
+ /// When provided in a partial class, allows value of Outline to be changed before setting.
+ /// </summary>
+ partial void SetOutline(string oldValue, ref string newValue);
+ /// <summary>
+ /// When provided in a partial class, allows value of Outline to be changed before returning.
+ /// </summary>
+ partial void GetOutline(ref string result);
+
+ /// <summary>
+ /// Max length = 1024
+ /// </summary>
+ [MaxLength(1024)]
+ [StringLength(1024)]
+ public string Outline
+ {
+ get
+ {
+ string value = _Outline;
+ GetOutline(ref value);
+ return (_Outline = value);
+ }
+ set
+ {
+ string oldValue = _Outline;
+ SetOutline(oldValue, ref value);
+ if (oldValue != value)
+ {
+ _Outline = value;
+ }
+ }
+ }
+
+ /*************************************************************************
+ * Navigation properties
+ *************************************************************************/
+
+ }
+}
+
diff --git a/Jellyfin.Data/Entities/Series.cs b/Jellyfin.Data/Entities/Series.cs
new file mode 100644
index 000000000..a57064824
--- /dev/null
+++ b/Jellyfin.Data/Entities/Series.cs
@@ -0,0 +1,183 @@
+//------------------------------------------------------------------------------
+// <auto-generated>
+// This code was generated from a template.
+//
+// Manual changes to this file may cause unexpected behavior in your application.
+// Manual changes to this file will be overwritten if the code is regenerated.
+//
+// Produced by Entity Framework Visual Editor
+// https://github.com/msawczyn/EFDesigner
+// </auto-generated>
+//------------------------------------------------------------------------------
+
+using System;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.ComponentModel;
+using System.ComponentModel.DataAnnotations;
+using System.ComponentModel.DataAnnotations.Schema;
+using System.Linq;
+using System.Runtime.CompilerServices;
+
+namespace Jellyfin.Data.Entities
+{
+ public partial class Series: global::Jellyfin.Data.Entities.LibraryItem
+ {
+ partial void Init();
+
+ /// <summary>
+ /// Default constructor. Protected due to required properties, but present because EF needs it.
+ /// </summary>
+ protected Series(): base()
+ {
+ SeriesMetadata = new System.Collections.Generic.HashSet<global::Jellyfin.Data.Entities.SeriesMetadata>();
+ Seasons = new System.Collections.Generic.HashSet<global::Jellyfin.Data.Entities.Season>();
+
+ Init();
+ }
+
+ /// <summary>
+ /// Replaces default constructor, since it's protected. Caller assumes responsibility for setting all required values before saving.
+ /// </summary>
+ public static Series CreateSeriesUnsafe()
+ {
+ return new Series();
+ }
+
+ /// <summary>
+ /// Public constructor with required data
+ /// </summary>
+ /// <param name="urlid">This is whats gets displayed in the Urls and API requests. This could also be a string.</param>
+ public Series(Guid urlid, DateTime dateadded)
+ {
+ this.UrlId = urlid;
+
+ this.SeriesMetadata = new System.Collections.Generic.HashSet<global::Jellyfin.Data.Entities.SeriesMetadata>();
+ this.Seasons = new System.Collections.Generic.HashSet<global::Jellyfin.Data.Entities.Season>();
+
+ Init();
+ }
+
+ /// <summary>
+ /// Static create function (for use in LINQ queries, etc.)
+ /// </summary>
+ /// <param name="urlid">This is whats gets displayed in the Urls and API requests. This could also be a string.</param>
+ public static Series Create(Guid urlid, DateTime dateadded)
+ {
+ return new Series(urlid, dateadded);
+ }
+
+ /*************************************************************************
+ * Properties
+ *************************************************************************/
+
+ /// <summary>
+ /// Backing field for AirsDayOfWeek
+ /// </summary>
+ protected global::Jellyfin.Data.Enums.Weekday? _AirsDayOfWeek;
+ /// <summary>
+ /// When provided in a partial class, allows value of AirsDayOfWeek to be changed before setting.
+ /// </summary>
+ partial void SetAirsDayOfWeek(global::Jellyfin.Data.Enums.Weekday? oldValue, ref global::Jellyfin.Data.Enums.Weekday? newValue);
+ /// <summary>
+ /// When provided in a partial class, allows value of AirsDayOfWeek to be changed before returning.
+ /// </summary>
+ partial void GetAirsDayOfWeek(ref global::Jellyfin.Data.Enums.Weekday? result);
+
+ public global::Jellyfin.Data.Enums.Weekday? AirsDayOfWeek
+ {
+ get
+ {
+ global::Jellyfin.Data.Enums.Weekday? value = _AirsDayOfWeek;
+ GetAirsDayOfWeek(ref value);
+ return (_AirsDayOfWeek = value);
+ }
+ set
+ {
+ global::Jellyfin.Data.Enums.Weekday? oldValue = _AirsDayOfWeek;
+ SetAirsDayOfWeek(oldValue, ref value);
+ if (oldValue != value)
+ {
+ _AirsDayOfWeek = value;
+ }
+ }
+ }
+
+ /// <summary>
+ /// Backing field for AirsTime
+ /// </summary>
+ protected DateTimeOffset? _AirsTime;
+ /// <summary>
+ /// When provided in a partial class, allows value of AirsTime to be changed before setting.
+ /// </summary>
+ partial void SetAirsTime(DateTimeOffset? oldValue, ref DateTimeOffset? newValue);
+ /// <summary>
+ /// When provided in a partial class, allows value of AirsTime to be changed before returning.
+ /// </summary>
+ partial void GetAirsTime(ref DateTimeOffset? result);
+
+ /// <summary>
+ /// The time the show airs, ignore the date portion
+ /// </summary>
+ public DateTimeOffset? AirsTime
+ {
+ get
+ {
+ DateTimeOffset? value = _AirsTime;
+ GetAirsTime(ref value);
+ return (_AirsTime = value);
+ }
+ set
+ {
+ DateTimeOffset? oldValue = _AirsTime;
+ SetAirsTime(oldValue, ref value);
+ if (oldValue != value)
+ {
+ _AirsTime = value;
+ }
+ }
+ }
+
+ /// <summary>
+ /// Backing field for FirstAired
+ /// </summary>
+ protected DateTimeOffset? _FirstAired;
+ /// <summary>
+ /// When provided in a partial class, allows value of FirstAired to be changed before setting.
+ /// </summary>
+ partial void SetFirstAired(DateTimeOffset? oldValue, ref DateTimeOffset? newValue);
+ /// <summary>
+ /// When provided in a partial class, allows value of FirstAired to be changed before returning.
+ /// </summary>
+ partial void GetFirstAired(ref DateTimeOffset? result);
+
+ public DateTimeOffset? FirstAired
+ {
+ get
+ {
+ DateTimeOffset? value = _FirstAired;
+ GetFirstAired(ref value);
+ return (_FirstAired = value);
+ }
+ set
+ {
+ DateTimeOffset? oldValue = _FirstAired;
+ SetFirstAired(oldValue, ref value);
+ if (oldValue != value)
+ {
+ _FirstAired = value;
+ }
+ }
+ }
+
+ /*************************************************************************
+ * Navigation properties
+ *************************************************************************/
+
+ public virtual ICollection<global::Jellyfin.Data.Entities.SeriesMetadata> SeriesMetadata { get; protected set; }
+
+ public virtual ICollection<global::Jellyfin.Data.Entities.Season> Seasons { get; protected set; }
+
+ }
+}
+
diff --git a/Jellyfin.Data/Entities/SeriesMetadata.cs b/Jellyfin.Data/Entities/SeriesMetadata.cs
new file mode 100644
index 000000000..9a91371df
--- /dev/null
+++ b/Jellyfin.Data/Entities/SeriesMetadata.cs
@@ -0,0 +1,239 @@
+//------------------------------------------------------------------------------
+// <auto-generated>
+// This code was generated from a template.
+//
+// Manual changes to this file may cause unexpected behavior in your application.
+// Manual changes to this file will be overwritten if the code is regenerated.
+//
+// Produced by Entity Framework Visual Editor
+// https://github.com/msawczyn/EFDesigner
+// </auto-generated>
+//------------------------------------------------------------------------------
+
+using System;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.ComponentModel;
+using System.ComponentModel.DataAnnotations;
+using System.ComponentModel.DataAnnotations.Schema;
+using System.Linq;
+using System.Runtime.CompilerServices;
+
+namespace Jellyfin.Data.Entities
+{
+ public partial class SeriesMetadata: global::Jellyfin.Data.Entities.Metadata
+ {
+ partial void Init();
+
+ /// <summary>
+ /// Default constructor. Protected due to required properties, but present because EF needs it.
+ /// </summary>
+ protected SeriesMetadata(): base()
+ {
+ Networks = new System.Collections.Generic.HashSet<global::Jellyfin.Data.Entities.Company>();
+
+ Init();
+ }
+
+ /// <summary>
+ /// Replaces default constructor, since it's protected. Caller assumes responsibility for setting all required values before saving.
+ /// </summary>
+ public static SeriesMetadata CreateSeriesMetadataUnsafe()
+ {
+ return new SeriesMetadata();
+ }
+
+ /// <summary>
+ /// Public constructor with required data
+ /// </summary>
+ /// <param name="title">The title or name of the object</param>
+ /// <param name="language">ISO-639-3 3-character language codes</param>
+ /// <param name="_series0"></param>
+ public SeriesMetadata(string title, string language, DateTime dateadded, DateTime datemodified, global::Jellyfin.Data.Entities.Series _series0)
+ {
+ if (string.IsNullOrEmpty(title)) throw new ArgumentNullException(nameof(title));
+ this.Title = title;
+
+ if (string.IsNullOrEmpty(language)) throw new ArgumentNullException(nameof(language));
+ this.Language = language;
+
+ if (_series0 == null) throw new ArgumentNullException(nameof(_series0));
+ _series0.SeriesMetadata.Add(this);
+
+ this.Networks = new System.Collections.Generic.HashSet<global::Jellyfin.Data.Entities.Company>();
+
+ Init();
+ }
+
+ /// <summary>
+ /// Static create function (for use in LINQ queries, etc.)
+ /// </summary>
+ /// <param name="title">The title or name of the object</param>
+ /// <param name="language">ISO-639-3 3-character language codes</param>
+ /// <param name="_series0"></param>
+ public static SeriesMetadata Create(string title, string language, DateTime dateadded, DateTime datemodified, global::Jellyfin.Data.Entities.Series _series0)
+ {
+ return new SeriesMetadata(title, language, dateadded, datemodified, _series0);
+ }
+
+ /*************************************************************************
+ * Properties
+ *************************************************************************/
+
+ /// <summary>
+ /// Backing field for Outline
+ /// </summary>
+ protected string _Outline;
+ /// <summary>
+ /// When provided in a partial class, allows value of Outline to be changed before setting.
+ /// </summary>
+ partial void SetOutline(string oldValue, ref string newValue);
+ /// <summary>
+ /// When provided in a partial class, allows value of Outline to be changed before returning.
+ /// </summary>
+ partial void GetOutline(ref string result);
+
+ /// <summary>
+ /// Max length = 1024
+ /// </summary>
+ [MaxLength(1024)]
+ [StringLength(1024)]
+ public string Outline
+ {
+ get
+ {
+ string value = _Outline;
+ GetOutline(ref value);
+ return (_Outline = value);
+ }
+ set
+ {
+ string oldValue = _Outline;
+ SetOutline(oldValue, ref value);
+ if (oldValue != value)
+ {
+ _Outline = value;
+ }
+ }
+ }
+
+ /// <summary>
+ /// Backing field for Plot
+ /// </summary>
+ protected string _Plot;
+ /// <summary>
+ /// When provided in a partial class, allows value of Plot to be changed before setting.
+ /// </summary>
+ partial void SetPlot(string oldValue, ref string newValue);
+ /// <summary>
+ /// When provided in a partial class, allows value of Plot to be changed before returning.
+ /// </summary>
+ partial void GetPlot(ref string result);
+
+ /// <summary>
+ /// Max length = 65535
+ /// </summary>
+ [MaxLength(65535)]
+ [StringLength(65535)]
+ public string Plot
+ {
+ get
+ {
+ string value = _Plot;
+ GetPlot(ref value);
+ return (_Plot = value);
+ }
+ set
+ {
+ string oldValue = _Plot;
+ SetPlot(oldValue, ref value);
+ if (oldValue != value)
+ {
+ _Plot = value;
+ }
+ }
+ }
+
+ /// <summary>
+ /// Backing field for Tagline
+ /// </summary>
+ protected string _Tagline;
+ /// <summary>
+ /// When provided in a partial class, allows value of Tagline to be changed before setting.
+ /// </summary>
+ partial void SetTagline(string oldValue, ref string newValue);
+ /// <summary>
+ /// When provided in a partial class, allows value of Tagline to be changed before returning.
+ /// </summary>
+ partial void GetTagline(ref string result);
+
+ /// <summary>
+ /// Max length = 1024
+ /// </summary>
+ [MaxLength(1024)]
+ [StringLength(1024)]
+ public string Tagline
+ {
+ get
+ {
+ string value = _Tagline;
+ GetTagline(ref value);
+ return (_Tagline = value);
+ }
+ set
+ {
+ string oldValue = _Tagline;
+ SetTagline(oldValue, ref value);
+ if (oldValue != value)
+ {
+ _Tagline = value;
+ }
+ }
+ }
+
+ /// <summary>
+ /// Backing field for Country
+ /// </summary>
+ protected string _Country;
+ /// <summary>
+ /// When provided in a partial class, allows value of Country to be changed before setting.
+ /// </summary>
+ partial void SetCountry(string oldValue, ref string newValue);
+ /// <summary>
+ /// When provided in a partial class, allows value of Country to be changed before returning.
+ /// </summary>
+ partial void GetCountry(ref string result);
+
+ /// <summary>
+ /// Max length = 2
+ /// </summary>
+ [MaxLength(2)]
+ [StringLength(2)]
+ public string Country
+ {
+ get
+ {
+ string value = _Country;
+ GetCountry(ref value);
+ return (_Country = value);
+ }
+ set
+ {
+ string oldValue = _Country;
+ SetCountry(oldValue, ref value);
+ if (oldValue != value)
+ {
+ _Country = value;
+ }
+ }
+ }
+
+ /*************************************************************************
+ * Navigation properties
+ *************************************************************************/
+
+ public virtual ICollection<global::Jellyfin.Data.Entities.Company> Networks { get; protected set; }
+
+ }
+}
+
diff --git a/Jellyfin.Data/Entities/Track.cs b/Jellyfin.Data/Entities/Track.cs
new file mode 100644
index 000000000..1d3ad372f
--- /dev/null
+++ b/Jellyfin.Data/Entities/Track.cs
@@ -0,0 +1,127 @@
+//------------------------------------------------------------------------------
+// <auto-generated>
+// This code was generated from a template.
+//
+// Manual changes to this file may cause unexpected behavior in your application.
+// Manual changes to this file will be overwritten if the code is regenerated.
+//
+// Produced by Entity Framework Visual Editor
+// https://github.com/msawczyn/EFDesigner
+// </auto-generated>
+//------------------------------------------------------------------------------
+
+using System;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.ComponentModel;
+using System.ComponentModel.DataAnnotations;
+using System.ComponentModel.DataAnnotations.Schema;
+using System.Linq;
+using System.Runtime.CompilerServices;
+
+namespace Jellyfin.Data.Entities
+{
+ public partial class Track: global::Jellyfin.Data.Entities.LibraryItem
+ {
+ partial void Init();
+
+ /// <summary>
+ /// Default constructor. Protected due to required properties, but present because EF needs it.
+ /// </summary>
+ protected Track(): base()
+ {
+ // NOTE: This class has one-to-one associations with LibraryRoot, LibraryItem and CollectionItem.
+ // One-to-one associations are not validated in constructors since this causes a scenario where each one must be constructed before the other.
+
+ Releases = new System.Collections.Generic.HashSet<global::Jellyfin.Data.Entities.Release>();
+ TrackMetadata = new System.Collections.Generic.HashSet<global::Jellyfin.Data.Entities.TrackMetadata>();
+
+ Init();
+ }
+
+ /// <summary>
+ /// Replaces default constructor, since it's protected. Caller assumes responsibility for setting all required values before saving.
+ /// </summary>
+ public static Track CreateTrackUnsafe()
+ {
+ return new Track();
+ }
+
+ /// <summary>
+ /// Public constructor with required data
+ /// </summary>
+ /// <param name="urlid">This is whats gets displayed in the Urls and API requests. This could also be a string.</param>
+ /// <param name="_musicalbum0"></param>
+ public Track(Guid urlid, DateTime dateadded, global::Jellyfin.Data.Entities.MusicAlbum _musicalbum0)
+ {
+ // NOTE: This class has one-to-one associations with LibraryRoot, LibraryItem and CollectionItem.
+ // One-to-one associations are not validated in constructors since this causes a scenario where each one must be constructed before the other.
+
+ this.UrlId = urlid;
+
+ if (_musicalbum0 == null) throw new ArgumentNullException(nameof(_musicalbum0));
+ _musicalbum0.Tracks.Add(this);
+
+ this.Releases = new System.Collections.Generic.HashSet<global::Jellyfin.Data.Entities.Release>();
+ this.TrackMetadata = new System.Collections.Generic.HashSet<global::Jellyfin.Data.Entities.TrackMetadata>();
+
+ Init();
+ }
+
+ /// <summary>
+ /// Static create function (for use in LINQ queries, etc.)
+ /// </summary>
+ /// <param name="urlid">This is whats gets displayed in the Urls and API requests. This could also be a string.</param>
+ /// <param name="_musicalbum0"></param>
+ public static Track Create(Guid urlid, DateTime dateadded, global::Jellyfin.Data.Entities.MusicAlbum _musicalbum0)
+ {
+ return new Track(urlid, dateadded, _musicalbum0);
+ }
+
+ /*************************************************************************
+ * Properties
+ *************************************************************************/
+
+ /// <summary>
+ /// Backing field for TrackNumber
+ /// </summary>
+ protected int? _TrackNumber;
+ /// <summary>
+ /// When provided in a partial class, allows value of TrackNumber to be changed before setting.
+ /// </summary>
+ partial void SetTrackNumber(int? oldValue, ref int? newValue);
+ /// <summary>
+ /// When provided in a partial class, allows value of TrackNumber to be changed before returning.
+ /// </summary>
+ partial void GetTrackNumber(ref int? result);
+
+ public int? TrackNumber
+ {
+ get
+ {
+ int? value = _TrackNumber;
+ GetTrackNumber(ref value);
+ return (_TrackNumber = value);
+ }
+ set
+ {
+ int? oldValue = _TrackNumber;
+ SetTrackNumber(oldValue, ref value);
+ if (oldValue != value)
+ {
+ _TrackNumber = value;
+ }
+ }
+ }
+
+ /*************************************************************************
+ * Navigation properties
+ *************************************************************************/
+
+ public virtual ICollection<global::Jellyfin.Data.Entities.Release> Releases { get; protected set; }
+
+ public virtual ICollection<global::Jellyfin.Data.Entities.TrackMetadata> TrackMetadata { get; protected set; }
+
+ }
+}
+
diff --git a/Jellyfin.Data/Entities/TrackMetadata.cs b/Jellyfin.Data/Entities/TrackMetadata.cs
new file mode 100644
index 000000000..f4c61459c
--- /dev/null
+++ b/Jellyfin.Data/Entities/TrackMetadata.cs
@@ -0,0 +1,86 @@
+//------------------------------------------------------------------------------
+// <auto-generated>
+// This code was generated from a template.
+//
+// Manual changes to this file may cause unexpected behavior in your application.
+// Manual changes to this file will be overwritten if the code is regenerated.
+//
+// Produced by Entity Framework Visual Editor
+// https://github.com/msawczyn/EFDesigner
+// </auto-generated>
+//------------------------------------------------------------------------------
+
+using System;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.ComponentModel;
+using System.ComponentModel.DataAnnotations;
+using System.ComponentModel.DataAnnotations.Schema;
+using System.Linq;
+using System.Runtime.CompilerServices;
+
+namespace Jellyfin.Data.Entities
+{
+ public partial class TrackMetadata: global::Jellyfin.Data.Entities.Metadata
+ {
+ partial void Init();
+
+ /// <summary>
+ /// Default constructor. Protected due to required properties, but present because EF needs it.
+ /// </summary>
+ protected TrackMetadata(): base()
+ {
+ Init();
+ }
+
+ /// <summary>
+ /// Replaces default constructor, since it's protected. Caller assumes responsibility for setting all required values before saving.
+ /// </summary>
+ public static TrackMetadata CreateTrackMetadataUnsafe()
+ {
+ return new TrackMetadata();
+ }
+
+ /// <summary>
+ /// Public constructor with required data
+ /// </summary>
+ /// <param name="title">The title or name of the object</param>
+ /// <param name="language">ISO-639-3 3-character language codes</param>
+ /// <param name="_track0"></param>
+ public TrackMetadata(string title, string language, DateTime dateadded, DateTime datemodified, global::Jellyfin.Data.Entities.Track _track0)
+ {
+ if (string.IsNullOrEmpty(title)) throw new ArgumentNullException(nameof(title));
+ this.Title = title;
+
+ if (string.IsNullOrEmpty(language)) throw new ArgumentNullException(nameof(language));
+ this.Language = language;
+
+ if (_track0 == null) throw new ArgumentNullException(nameof(_track0));
+ _track0.TrackMetadata.Add(this);
+
+
+ Init();
+ }
+
+ /// <summary>
+ /// Static create function (for use in LINQ queries, etc.)
+ /// </summary>
+ /// <param name="title">The title or name of the object</param>
+ /// <param name="language">ISO-639-3 3-character language codes</param>
+ /// <param name="_track0"></param>
+ public static TrackMetadata Create(string title, string language, DateTime dateadded, DateTime datemodified, global::Jellyfin.Data.Entities.Track _track0)
+ {
+ return new TrackMetadata(title, language, dateadded, datemodified, _track0);
+ }
+
+ /*************************************************************************
+ * Properties
+ *************************************************************************/
+
+ /*************************************************************************
+ * Navigation properties
+ *************************************************************************/
+
+ }
+}
+
diff --git a/Jellyfin.Data/Entities/User.cs b/Jellyfin.Data/Entities/User.cs
new file mode 100644
index 000000000..2ee3c8f4f
--- /dev/null
+++ b/Jellyfin.Data/Entities/User.cs
@@ -0,0 +1,242 @@
+//------------------------------------------------------------------------------
+// <auto-generated>
+// This code was generated from a template.
+//
+// Manual changes to this file may cause unexpected behavior in your application.
+// Manual changes to this file will be overwritten if the code is regenerated.
+//
+// Produced by Entity Framework Visual Editor
+// https://github.com/msawczyn/EFDesigner
+// </auto-generated>
+//------------------------------------------------------------------------------
+
+using System;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.ComponentModel;
+using System.ComponentModel.DataAnnotations;
+using System.ComponentModel.DataAnnotations.Schema;
+using System.Linq;
+using System.Runtime.CompilerServices;
+
+namespace Jellyfin.Data.Entities
+{
+ public partial class User
+ {
+ partial void Init();
+
+ /// <summary>
+ /// Default constructor. Protected due to required properties, but present because EF needs it.
+ /// </summary>
+ protected User()
+ {
+ Groups = new System.Collections.Generic.HashSet<global::Jellyfin.Data.Entities.Group>();
+ Permissions = new System.Collections.Generic.HashSet<global::Jellyfin.Data.Entities.Permission>();
+ ProviderMappings = new System.Collections.Generic.HashSet<global::Jellyfin.Data.Entities.ProviderMapping>();
+ Preferences = new System.Collections.Generic.HashSet<global::Jellyfin.Data.Entities.Preference>();
+
+ Init();
+ }
+
+ /// <summary>
+ /// Replaces default constructor, since it's protected. Caller assumes responsibility for setting all required values before saving.
+ /// </summary>
+ public static User CreateUserUnsafe()
+ {
+ return new User();
+ }
+
+ /// <summary>
+ /// Public constructor with required data
+ /// </summary>
+ /// <param name="username"></param>
+ /// <param name="mustupdatepassword"></param>
+ /// <param name="audiolanguagepreference"></param>
+ /// <param name="authenticationproviderid"></param>
+ /// <param name="invalidloginattemptcount"></param>
+ /// <param name="subtitlemode"></param>
+ /// <param name="playdefaultaudiotrack"></param>
+ public User(string username, bool mustupdatepassword, string audiolanguagepreference, string authenticationproviderid, int invalidloginattemptcount, string subtitlemode, bool playdefaultaudiotrack)
+ {
+ if (string.IsNullOrEmpty(username)) throw new ArgumentNullException(nameof(username));
+ this.Username = username;
+
+ this.MustUpdatePassword = mustupdatepassword;
+
+ if (string.IsNullOrEmpty(audiolanguagepreference)) throw new ArgumentNullException(nameof(audiolanguagepreference));
+ this.AudioLanguagePreference = audiolanguagepreference;
+
+ if (string.IsNullOrEmpty(authenticationproviderid)) throw new ArgumentNullException(nameof(authenticationproviderid));
+ this.AuthenticationProviderId = authenticationproviderid;
+
+ this.InvalidLoginAttemptCount = invalidloginattemptcount;
+
+ if (string.IsNullOrEmpty(subtitlemode)) throw new ArgumentNullException(nameof(subtitlemode));
+ this.SubtitleMode = subtitlemode;
+
+ this.PlayDefaultAudioTrack = playdefaultaudiotrack;
+
+ this.Groups = new System.Collections.Generic.HashSet<global::Jellyfin.Data.Entities.Group>();
+ this.Permissions = new System.Collections.Generic.HashSet<global::Jellyfin.Data.Entities.Permission>();
+ this.ProviderMappings = new System.Collections.Generic.HashSet<global::Jellyfin.Data.Entities.ProviderMapping>();
+ this.Preferences = new System.Collections.Generic.HashSet<global::Jellyfin.Data.Entities.Preference>();
+
+ Init();
+ }
+
+ /// <summary>
+ /// Static create function (for use in LINQ queries, etc.)
+ /// </summary>
+ /// <param name="username"></param>
+ /// <param name="mustupdatepassword"></param>
+ /// <param name="audiolanguagepreference"></param>
+ /// <param name="authenticationproviderid"></param>
+ /// <param name="invalidloginattemptcount"></param>
+ /// <param name="subtitlemode"></param>
+ /// <param name="playdefaultaudiotrack"></param>
+ public static User Create(string username, bool mustupdatepassword, string audiolanguagepreference, string authenticationproviderid, int invalidloginattemptcount, string subtitlemode, bool playdefaultaudiotrack)
+ {
+ return new User(username, mustupdatepassword, audiolanguagepreference, authenticationproviderid, invalidloginattemptcount, subtitlemode, playdefaultaudiotrack);
+ }
+
+ /*************************************************************************
+ * Properties
+ *************************************************************************/
+
+ /// <summary>
+ /// Identity, Indexed, Required
+ /// </summary>
+ [Key]
+ [Required]
+ public Guid Id { get; protected set; }
+
+ /// <summary>
+ /// Required
+ /// </summary>
+ [ConcurrencyCheck]
+ [Required]
+ public byte[] LastLoginTimestamp { get; set; }
+
+ /// <summary>
+ /// Required, Max length = 255
+ /// </summary>
+ [Required]
+ [MaxLength(255)]
+ [StringLength(255)]
+ public string Username { get; set; }
+
+ /// <summary>
+ /// Max length = 65535
+ /// </summary>
+ [MaxLength(65535)]
+ [StringLength(65535)]
+ public string Password { get; set; }
+
+ /// <summary>
+ /// Required
+ /// </summary>
+ [Required]
+ public bool MustUpdatePassword { get; set; }
+
+ /// <summary>
+ /// Required, Max length = 255
+ /// </summary>
+ [Required]
+ [MaxLength(255)]
+ [StringLength(255)]
+ public string AudioLanguagePreference { get; set; }
+
+ /// <summary>
+ /// Required, Max length = 255
+ /// </summary>
+ [Required]
+ [MaxLength(255)]
+ [StringLength(255)]
+ public string AuthenticationProviderId { get; set; }
+
+ /// <summary>
+ /// Max length = 65535
+ /// </summary>
+ [MaxLength(65535)]
+ [StringLength(65535)]
+ public string GroupedFolders { get; set; }
+
+ /// <summary>
+ /// Required
+ /// </summary>
+ [Required]
+ public int InvalidLoginAttemptCount { get; set; }
+
+ /// <summary>
+ /// Max length = 65535
+ /// </summary>
+ [MaxLength(65535)]
+ [StringLength(65535)]
+ public string LatestItemExcludes { get; set; }
+
+ public int? LoginAttemptsBeforeLockout { get; set; }
+
+ /// <summary>
+ /// Max length = 65535
+ /// </summary>
+ [MaxLength(65535)]
+ [StringLength(65535)]
+ public string MyMediaExcludes { get; set; }
+
+ /// <summary>
+ /// Max length = 65535
+ /// </summary>
+ [MaxLength(65535)]
+ [StringLength(65535)]
+ public string OrderedViews { get; set; }
+
+ /// <summary>
+ /// Required, Max length = 255
+ /// </summary>
+ [Required]
+ [MaxLength(255)]
+ [StringLength(255)]
+ public string SubtitleMode { get; set; }
+
+ /// <summary>
+ /// Required
+ /// </summary>
+ [Required]
+ public bool PlayDefaultAudioTrack { get; set; }
+
+ /// <summary>
+ /// Max length = 255
+ /// </summary>
+ [MaxLength(255)]
+ [StringLength(255)]
+ public string SubtitleLanguagePrefernce { get; set; }
+
+ public bool? DisplayMissingEpisodes { get; set; }
+
+ public bool? DisplayCollectionsView { get; set; }
+
+ public bool? HidePlayedInLatest { get; set; }
+
+ public bool? RememberAudioSelections { get; set; }
+
+ public bool? RememberSubtitleSelections { get; set; }
+
+ public bool? EnableNextEpisodeAutoPlay { get; set; }
+
+ public bool? EnableUserPreferenceAccess { get; set; }
+
+ /*************************************************************************
+ * Navigation properties
+ *************************************************************************/
+
+ public virtual ICollection<global::Jellyfin.Data.Entities.Group> Groups { get; protected set; }
+
+ public virtual ICollection<global::Jellyfin.Data.Entities.Permission> Permissions { get; protected set; }
+
+ public virtual ICollection<global::Jellyfin.Data.Entities.ProviderMapping> ProviderMappings { get; protected set; }
+
+ public virtual ICollection<global::Jellyfin.Data.Entities.Preference> Preferences { get; protected set; }
+
+ }
+}
+
diff --git a/Jellyfin.Data/Enums/ArtKind.cs b/Jellyfin.Data/Enums/ArtKind.cs
new file mode 100644
index 000000000..52e33048e
--- /dev/null
+++ b/Jellyfin.Data/Enums/ArtKind.cs
@@ -0,0 +1,25 @@
+//------------------------------------------------------------------------------
+// <auto-generated>
+// This code was generated from a template.
+//
+// Manual changes to this file may cause unexpected behavior in your application.
+// Manual changes to this file will be overwritten if the code is regenerated.
+//
+// Produced by Entity Framework Visual Editor
+// https://github.com/msawczyn/EFDesigner
+// </auto-generated>
+//------------------------------------------------------------------------------
+
+using System;
+
+namespace Jellyfin.Data.Enums
+{
+ public enum ArtKind : Int32
+ {
+ Other,
+ Poster,
+ Banner,
+ Thumbnail,
+ Logo
+ }
+}
diff --git a/Jellyfin.Data/Enums/MediaFileKind.cs b/Jellyfin.Data/Enums/MediaFileKind.cs
new file mode 100644
index 000000000..34d1b20f5
--- /dev/null
+++ b/Jellyfin.Data/Enums/MediaFileKind.cs
@@ -0,0 +1,25 @@
+//------------------------------------------------------------------------------
+// <auto-generated>
+// This code was generated from a template.
+//
+// Manual changes to this file may cause unexpected behavior in your application.
+// Manual changes to this file will be overwritten if the code is regenerated.
+//
+// Produced by Entity Framework Visual Editor
+// https://github.com/msawczyn/EFDesigner
+// </auto-generated>
+//------------------------------------------------------------------------------
+
+using System;
+
+namespace Jellyfin.Data.Enums
+{
+ public enum MediaFileKind : Int32
+ {
+ Main,
+ Sidecar,
+ AdditionalPart,
+ AlternativeFormat,
+ AdditionalStream
+ }
+}
diff --git a/Jellyfin.Data/Enums/PersonRoleType.cs b/Jellyfin.Data/Enums/PersonRoleType.cs
new file mode 100644
index 000000000..f5c8f43c5
--- /dev/null
+++ b/Jellyfin.Data/Enums/PersonRoleType.cs
@@ -0,0 +1,32 @@
+//------------------------------------------------------------------------------
+// <auto-generated>
+// This code was generated from a template.
+//
+// Manual changes to this file may cause unexpected behavior in your application.
+// Manual changes to this file will be overwritten if the code is regenerated.
+//
+// Produced by Entity Framework Visual Editor
+// https://github.com/msawczyn/EFDesigner
+// </auto-generated>
+//------------------------------------------------------------------------------
+
+using System;
+
+namespace Jellyfin.Data.Enums
+{
+ public enum PersonRoleType : Int32
+ {
+ Other,
+ Director,
+ Artist,
+ OriginalArtist,
+ Actor,
+ VoiceActor,
+ Producer,
+ Remixer,
+ Conductor,
+ Composer,
+ Author,
+ Editor
+ }
+}
diff --git a/Jellyfin.Data/Enums/Weekday.cs b/Jellyfin.Data/Enums/Weekday.cs
new file mode 100644
index 000000000..ce0c6e4ce
--- /dev/null
+++ b/Jellyfin.Data/Enums/Weekday.cs
@@ -0,0 +1,27 @@
+//------------------------------------------------------------------------------
+// <auto-generated>
+// This code was generated from a template.
+//
+// Manual changes to this file may cause unexpected behavior in your application.
+// Manual changes to this file will be overwritten if the code is regenerated.
+//
+// Produced by Entity Framework Visual Editor
+// https://github.com/msawczyn/EFDesigner
+// </auto-generated>
+//------------------------------------------------------------------------------
+
+using System;
+
+namespace Jellyfin.Data.Enums
+{
+ public enum Weekday : Int32
+ {
+ Sunday,
+ Monday,
+ Tuesday,
+ Wednesday,
+ Thursday,
+ Friday,
+ Saturday
+ }
+}
diff --git a/Jellyfin.Data/Jellyfin.Data.csproj b/Jellyfin.Data/Jellyfin.Data.csproj
new file mode 100644
index 000000000..73ea593b0
--- /dev/null
+++ b/Jellyfin.Data/Jellyfin.Data.csproj
@@ -0,0 +1,12 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+ <PropertyGroup>
+ <TargetFramework>netstandard2.0</TargetFramework>
+ </PropertyGroup>
+
+ <ItemGroup>
+ <PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="2.2.4" />
+ <PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="2.2.4" />
+ </ItemGroup>
+
+</Project>
diff --git a/Jellyfin.Data/Structs/.gitkeep b/Jellyfin.Data/Structs/.gitkeep
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/Jellyfin.Data/Structs/.gitkeep
diff --git a/Jellyfin.Drawing.Skia/Jellyfin.Drawing.Skia.csproj b/Jellyfin.Drawing.Skia/Jellyfin.Drawing.Skia.csproj
index febb1adab..a6e1f490a 100644
--- a/Jellyfin.Drawing.Skia/Jellyfin.Drawing.Skia.csproj
+++ b/Jellyfin.Drawing.Skia/Jellyfin.Drawing.Skia.csproj
@@ -1,10 +1,16 @@
<Project Sdk="Microsoft.NET.Sdk">
+ <!-- ProjectGuid is only included as a requirement for SonarQube analysis -->
+ <PropertyGroup>
+ <ProjectGuid>{154872D9-6C12-4007-96E3-8F70A58386CE}</ProjectGuid>
+ </PropertyGroup>
+
<PropertyGroup>
<TargetFramework>netstandard2.1</TargetFramework>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
+ <Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
@@ -12,9 +18,9 @@
</ItemGroup>
<ItemGroup>
- <PackageReference Include="SkiaSharp" Version="1.68.0" />
- <PackageReference Include="SkiaSharp.NativeAssets.Linux" Version="1.68.0" />
- <PackageReference Include="Jellyfin.SkiaSharp.NativeAssets.LinuxArm" Version="1.68.0" />
+ <PackageReference Include="SkiaSharp" Version="1.68.1" />
+ <PackageReference Include="SkiaSharp.NativeAssets.Linux" Version="1.68.1" />
+ <PackageReference Include="Jellyfin.SkiaSharp.NativeAssets.LinuxArm" Version="1.68.1" />
</ItemGroup>
<ItemGroup>
@@ -25,7 +31,7 @@
<!-- Code analysers-->
<ItemGroup Condition=" '$(Configuration)' == 'Debug' ">
- <PackageReference Include="Microsoft.CodeAnalysis.FxCopAnalyzers" Version="2.9.7" PrivateAssets="All" />
+ <PackageReference Include="Microsoft.CodeAnalysis.FxCopAnalyzers" Version="2.9.8" PrivateAssets="All" />
<PackageReference Include="SerilogAnalyzer" Version="0.15.0" PrivateAssets="All" />
<PackageReference Include="StyleCop.Analyzers" Version="1.1.118" PrivateAssets="All" />
<PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" PrivateAssets="All" />
diff --git a/Jellyfin.Drawing.Skia/PlayedIndicatorDrawer.cs b/Jellyfin.Drawing.Skia/PlayedIndicatorDrawer.cs
index 5084fd211..7eed5f4f7 100644
--- a/Jellyfin.Drawing.Skia/PlayedIndicatorDrawer.cs
+++ b/Jellyfin.Drawing.Skia/PlayedIndicatorDrawer.cs
@@ -26,7 +26,7 @@ namespace Jellyfin.Drawing.Skia
{
paint.Color = SKColor.Parse("#CC00A4DC");
paint.Style = SKPaintStyle.Fill;
- canvas.DrawCircle((float)x, OffsetFromTopRightCorner, 20, paint);
+ canvas.DrawCircle(x, OffsetFromTopRightCorner, 20, paint);
}
using (var paint = new SKPaint())
@@ -39,16 +39,13 @@ namespace Jellyfin.Drawing.Skia
// or:
// var emojiChar = 0x1F680;
- var text = "✔️";
- var emojiChar = StringUtilities.GetUnicodeCharacterCode(text, SKTextEncoding.Utf32);
+ const string Text = "✔️";
+ var emojiChar = StringUtilities.GetUnicodeCharacterCode(Text, SKTextEncoding.Utf32);
// ask the font manager for a font with that character
- var fontManager = SKFontManager.Default;
- var emojiTypeface = fontManager.MatchCharacter(emojiChar);
+ paint.Typeface = SKFontManager.Default.MatchCharacter(emojiChar);
- paint.Typeface = emojiTypeface;
-
- canvas.DrawText(text, (float)x - 20, OffsetFromTopRightCorner + 12, paint);
+ canvas.DrawText(Text, (float)x - 20, OffsetFromTopRightCorner + 12, paint);
}
}
}
diff --git a/Jellyfin.Drawing.Skia/SkiaCodecException.cs b/Jellyfin.Drawing.Skia/SkiaCodecException.cs
index 8158b846d..1d2db5515 100644
--- a/Jellyfin.Drawing.Skia/SkiaCodecException.cs
+++ b/Jellyfin.Drawing.Skia/SkiaCodecException.cs
@@ -1,4 +1,3 @@
-using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using SkiaSharp;
diff --git a/Jellyfin.Drawing.Skia/SkiaEncoder.cs b/Jellyfin.Drawing.Skia/SkiaEncoder.cs
index b080b3e6a..5c7462ee2 100644
--- a/Jellyfin.Drawing.Skia/SkiaEncoder.cs
+++ b/Jellyfin.Drawing.Skia/SkiaEncoder.cs
@@ -6,7 +6,6 @@ using MediaBrowser.Common.Configuration;
using MediaBrowser.Controller.Drawing;
using MediaBrowser.Controller.Extensions;
using MediaBrowser.Model.Drawing;
-using MediaBrowser.Model.Globalization;
using Microsoft.Extensions.Logging;
using SkiaSharp;
using static Jellyfin.Drawing.Skia.SkiaHelper;
@@ -18,27 +17,23 @@ namespace Jellyfin.Drawing.Skia
/// </summary>
public class SkiaEncoder : IImageEncoder
{
- private readonly ILogger _logger;
- private readonly IApplicationPaths _appPaths;
- private readonly ILocalizationManager _localizationManager;
-
private static readonly HashSet<string> _transparentImageTypes
= new HashSet<string>(StringComparer.OrdinalIgnoreCase) { ".png", ".gif", ".webp" };
+ private readonly ILogger _logger;
+ private readonly IApplicationPaths _appPaths;
+
/// <summary>
/// Initializes a new instance of the <see cref="SkiaEncoder"/> class.
/// </summary>
/// <param name="logger">The application logger.</param>
/// <param name="appPaths">The application paths.</param>
- /// <param name="localizationManager">The application localization manager.</param>
public SkiaEncoder(
ILogger<SkiaEncoder> logger,
- IApplicationPaths appPaths,
- ILocalizationManager localizationManager)
+ IApplicationPaths appPaths)
{
_logger = logger;
_appPaths = appPaths;
- _localizationManager = localizationManager;
}
/// <inheritdoc/>
@@ -83,12 +78,21 @@ namespace Jellyfin.Drawing.Skia
=> new HashSet<ImageFormat>() { ImageFormat.Webp, ImageFormat.Jpg, ImageFormat.Png };
/// <summary>
- /// Test to determine if the native lib is available.
+ /// Check if the native lib is available.
/// </summary>
- public static void TestSkia()
+ /// <returns>True if the native lib is available, otherwise false.</returns>
+ public static bool IsNativeLibAvailable()
{
- // test an operation that requires the native library
- SKPMColor.PreMultiply(SKColors.Black);
+ try
+ {
+ // test an operation that requires the native library
+ SKPMColor.PreMultiply(SKColors.Black);
+ return true;
+ }
+ catch (Exception)
+ {
+ return false;
+ }
}
private static bool IsTransparent(SKColor color)
@@ -210,11 +214,6 @@ namespace Jellyfin.Drawing.Skia
/// <exception cref="SkiaCodecException">The file at the specified path could not be used to generate a codec.</exception>
public ImageDimensions GetImageSize(string path)
{
- if (path == null)
- {
- throw new ArgumentNullException(nameof(path));
- }
-
if (!File.Exists(path))
{
throw new FileNotFoundException("File not found", path);
@@ -235,9 +234,12 @@ namespace Jellyfin.Drawing.Skia
private bool RequiresSpecialCharacterHack(string path)
{
- if (_localizationManager.HasUnicodeCategory(path, UnicodeCategory.OtherLetter))
+ for (int i = 0; i < path.Length; i++)
{
- return true;
+ if (char.GetUnicodeCategory(path[i]) == UnicodeCategory.OtherLetter)
+ {
+ return true;
+ }
}
if (HasDiacritics(path))
@@ -299,7 +301,7 @@ namespace Jellyfin.Drawing.Skia
/// <param name="orientation">The orientation of the image.</param>
/// <param name="origin">The detected origin of the image.</param>
/// <returns>The resulting bitmap of the image.</returns>
- internal SKBitmap Decode(string path, bool forceCleanBitmap, ImageOrientation? orientation, out SKEncodedOrigin origin)
+ internal SKBitmap? Decode(string path, bool forceCleanBitmap, ImageOrientation? orientation, out SKEncodedOrigin origin)
{
if (!File.Exists(path))
{
@@ -310,8 +312,7 @@ namespace Jellyfin.Drawing.Skia
if (requiresTransparencyHack || forceCleanBitmap)
{
- using (var stream = new SKFileStream(NormalizePath(path)))
- using (var codec = SKCodec.Create(stream))
+ using (var codec = SKCodec.Create(NormalizePath(path)))
{
if (codec == null)
{
@@ -351,12 +352,17 @@ namespace Jellyfin.Drawing.Skia
return resultBitmap;
}
- private SKBitmap GetBitmap(string path, bool cropWhitespace, bool forceAnalyzeBitmap, ImageOrientation? orientation, out SKEncodedOrigin origin)
+ private SKBitmap? GetBitmap(string path, bool cropWhitespace, bool forceAnalyzeBitmap, ImageOrientation? orientation, out SKEncodedOrigin origin)
{
if (cropWhitespace)
{
using (var bitmap = Decode(path, forceAnalyzeBitmap, orientation, out origin))
{
+ if (bitmap == null)
+ {
+ return null;
+ }
+
return CropWhiteSpace(bitmap);
}
}
@@ -364,13 +370,11 @@ namespace Jellyfin.Drawing.Skia
return Decode(path, forceAnalyzeBitmap, orientation, out origin);
}
- private SKBitmap GetBitmap(string path, bool cropWhitespace, bool autoOrient, ImageOrientation? orientation)
+ private SKBitmap? GetBitmap(string path, bool cropWhitespace, bool autoOrient, ImageOrientation? orientation)
{
- SKEncodedOrigin origin;
-
if (autoOrient)
{
- var bitmap = GetBitmap(path, cropWhitespace, true, orientation, out origin);
+ var bitmap = GetBitmap(path, cropWhitespace, true, orientation, out var origin);
if (bitmap != null && origin != SKEncodedOrigin.TopLeft)
{
@@ -383,7 +387,7 @@ namespace Jellyfin.Drawing.Skia
return bitmap;
}
- return GetBitmap(path, cropWhitespace, false, orientation, out origin);
+ return GetBitmap(path, cropWhitespace, false, orientation, out _);
}
private SKBitmap OrientImage(SKBitmap bitmap, SKEncodedOrigin origin)
@@ -520,14 +524,14 @@ namespace Jellyfin.Drawing.Skia
/// <inheritdoc/>
public string EncodeImage(string inputPath, DateTime dateModified, string outputPath, bool autoOrient, ImageOrientation? orientation, int quality, ImageProcessingOptions options, ImageFormat selectedOutputFormat)
{
- if (string.IsNullOrWhiteSpace(inputPath))
+ if (inputPath.Length == 0)
{
- throw new ArgumentNullException(nameof(inputPath));
+ throw new ArgumentException("String can't be empty.", nameof(inputPath));
}
- if (string.IsNullOrWhiteSpace(inputPath))
+ if (outputPath.Length == 0)
{
- throw new ArgumentNullException(nameof(outputPath));
+ throw new ArgumentException("String can't be empty.", nameof(outputPath));
}
var skiaOutputFormat = GetImageFormat(selectedOutputFormat);
@@ -541,7 +545,7 @@ namespace Jellyfin.Drawing.Skia
{
if (bitmap == null)
{
- throw new ArgumentOutOfRangeException($"Skia unable to read image {inputPath}");
+ throw new InvalidDataException($"Skia unable to read image {inputPath}");
}
var originalImageSize = new ImageDimensions(bitmap.Width, bitmap.Height);
diff --git a/Jellyfin.Drawing.Skia/StripCollageBuilder.cs b/Jellyfin.Drawing.Skia/StripCollageBuilder.cs
index 0735ef194..61bef90ec 100644
--- a/Jellyfin.Drawing.Skia/StripCollageBuilder.cs
+++ b/Jellyfin.Drawing.Skia/StripCollageBuilder.cs
@@ -120,13 +120,13 @@ namespace Jellyfin.Drawing.Skia
}
// resize to the same aspect as the original
- int iWidth = (int)Math.Abs(iHeight * currentBitmap.Width / currentBitmap.Height);
+ int iWidth = Math.Abs(iHeight * currentBitmap.Width / currentBitmap.Height);
using (var resizeBitmap = new SKBitmap(iWidth, iHeight, currentBitmap.ColorType, currentBitmap.AlphaType))
{
currentBitmap.ScalePixels(resizeBitmap, SKFilterQuality.High);
// crop image
- int ix = (int)Math.Abs((iWidth - iSlice) / 2);
+ int ix = Math.Abs((iWidth - iSlice) / 2);
using (var image = SKImage.FromBitmap(resizeBitmap))
using (var subset = image.Subset(SKRectI.Create(ix, 0, iSlice, iHeight)))
{
@@ -141,10 +141,10 @@ namespace Jellyfin.Drawing.Skia
return bitmap;
}
- private SKBitmap GetNextValidImage(string[] paths, int currentIndex, out int newIndex)
+ private SKBitmap? GetNextValidImage(string[] paths, int currentIndex, out int newIndex)
{
var imagesTested = new Dictionary<int, int>();
- SKBitmap bitmap = null;
+ SKBitmap? bitmap = null;
while (imagesTested.Count < paths.Length)
{
@@ -153,7 +153,7 @@ namespace Jellyfin.Drawing.Skia
currentIndex = 0;
}
- bitmap = _skiaEncoder.Decode(paths[currentIndex], false, null, out var origin);
+ bitmap = _skiaEncoder.Decode(paths[currentIndex], false, null, out _);
imagesTested[currentIndex] = 0;
diff --git a/Jellyfin.Drawing.Skia/UnplayedCountIndicator.cs b/Jellyfin.Drawing.Skia/UnplayedCountIndicator.cs
index a10fff9df..cf3dbde2c 100644
--- a/Jellyfin.Drawing.Skia/UnplayedCountIndicator.cs
+++ b/Jellyfin.Drawing.Skia/UnplayedCountIndicator.cs
@@ -32,7 +32,7 @@ namespace Jellyfin.Drawing.Skia
{
paint.Color = SKColor.Parse("#CC00A4DC");
paint.Style = SKPaintStyle.Fill;
- canvas.DrawCircle((float)x, OffsetFromTopRightCorner, 20, paint);
+ canvas.DrawCircle(x, OffsetFromTopRightCorner, 20, paint);
}
using (var paint = new SKPaint())
@@ -61,7 +61,7 @@ namespace Jellyfin.Drawing.Skia
paint.TextSize = 18;
}
- canvas.DrawText(text, (float)x, y, paint);
+ canvas.DrawText(text, x, y, paint);
}
}
}
diff --git a/Jellyfin.Server/CoreAppHost.cs b/Jellyfin.Server/CoreAppHost.cs
index 8b4b61e29..f678e714c 100644
--- a/Jellyfin.Server/CoreAppHost.cs
+++ b/Jellyfin.Server/CoreAppHost.cs
@@ -1,10 +1,13 @@
+using System;
using System.Collections.Generic;
using System.Reflection;
+using Emby.Drawing;
using Emby.Server.Implementations;
+using Jellyfin.Drawing.Skia;
using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Drawing;
using MediaBrowser.Model.IO;
-using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
namespace Jellyfin.Server
@@ -21,30 +24,40 @@ namespace Jellyfin.Server
/// <param name="loggerFactory">The <see cref="ILoggerFactory" /> to be used by the <see cref="CoreAppHost" />.</param>
/// <param name="options">The <see cref="StartupOptions" /> to be used by the <see cref="CoreAppHost" />.</param>
/// <param name="fileSystem">The <see cref="IFileSystem" /> to be used by the <see cref="CoreAppHost" />.</param>
- /// <param name="imageEncoder">The <see cref="IImageEncoder" /> to be used by the <see cref="CoreAppHost" />.</param>
/// <param name="networkManager">The <see cref="INetworkManager" /> to be used by the <see cref="CoreAppHost" />.</param>
- /// <param name="configuration">The <see cref="IConfiguration" /> to be used by the <see cref="CoreAppHost" />.</param>
public CoreAppHost(
ServerApplicationPaths applicationPaths,
ILoggerFactory loggerFactory,
StartupOptions options,
IFileSystem fileSystem,
- IImageEncoder imageEncoder,
- INetworkManager networkManager,
- IConfiguration configuration)
+ INetworkManager networkManager)
: base(
applicationPaths,
loggerFactory,
options,
fileSystem,
- imageEncoder,
- networkManager,
- configuration)
+ networkManager)
{
}
- /// <inheritdoc />
- public override bool CanSelfRestart => StartupOptions.RestartPath != null;
+ /// <inheritdoc/>
+ protected override void RegisterServices(IServiceCollection serviceCollection)
+ {
+ // Register an image encoder
+ bool useSkiaEncoder = SkiaEncoder.IsNativeLibAvailable();
+ Type imageEncoderType = useSkiaEncoder
+ ? typeof(SkiaEncoder)
+ : typeof(NullImageEncoder);
+ serviceCollection.AddSingleton(typeof(IImageEncoder), imageEncoderType);
+
+ // Log a warning if the Skia encoder could not be used
+ if (!useSkiaEncoder)
+ {
+ Logger.LogWarning($"Skia not available. Will fallback to {nameof(NullImageEncoder)}.");
+ }
+
+ base.RegisterServices(serviceCollection);
+ }
/// <inheritdoc />
protected override void RestartInternal() => Program.Restart();
diff --git a/Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs b/Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs
index dd4f9cd23..71ef9a69a 100644
--- a/Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs
+++ b/Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs
@@ -71,6 +71,11 @@ namespace Jellyfin.Server.Extensions
// Clear app parts to avoid other assemblies being picked up
.ConfigureApplicationPartManager(a => a.ApplicationParts.Clear())
.AddApplicationPart(typeof(StartupController).Assembly)
+ .AddJsonOptions(options =>
+ {
+ // Setting the naming policy to null leaves the property names as-is when serializing objects to JSON.
+ options.JsonSerializerOptions.PropertyNamingPolicy = null;
+ })
.AddControllersAsServices();
}
diff --git a/Jellyfin.Server/Jellyfin.Server.csproj b/Jellyfin.Server/Jellyfin.Server.csproj
index 166e22909..88114d999 100644
--- a/Jellyfin.Server/Jellyfin.Server.csproj
+++ b/Jellyfin.Server/Jellyfin.Server.csproj
@@ -1,14 +1,16 @@
<Project Sdk="Microsoft.NET.Sdk">
+ <!-- ProjectGuid is only included as a requirement for SonarQube analysis -->
+ <PropertyGroup>
+ <ProjectGuid>{07E39F42-A2C6-4B32-AF8C-725F957A73FF}</ProjectGuid>
+ </PropertyGroup>
+
<PropertyGroup>
<AssemblyName>jellyfin</AssemblyName>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp3.1</TargetFramework>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
- </PropertyGroup>
-
- <PropertyGroup>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<Nullable>enable</Nullable>
</PropertyGroup>
@@ -38,15 +40,18 @@
</PropertyGroup>
<ItemGroup>
- <PackageReference Include="CommandLineParser" Version="2.6.0" />
- <PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="3.1.0" />
- <PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="3.1.0" />
+ <PackageReference Include="CommandLineParser" Version="2.7.82" />
+ <PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="3.1.3" />
+ <PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="3.1.3" />
+ <PackageReference Include="prometheus-net" Version="3.5.0" />
+ <PackageReference Include="prometheus-net.AspNetCore" Version="3.5.0" />
<PackageReference Include="Serilog.AspNetCore" Version="3.2.0" />
+ <PackageReference Include="Serilog.Enrichers.Thread" Version="3.1.0" />
<PackageReference Include="Serilog.Settings.Configuration" Version="3.1.0" />
<PackageReference Include="Serilog.Sinks.Async" Version="1.4.0" />
<PackageReference Include="Serilog.Sinks.Console" Version="3.1.1" />
<PackageReference Include="Serilog.Sinks.File" Version="4.1.0" />
- <PackageReference Include="Serilog.Sinks.Graylog" Version="2.1.1" />
+ <PackageReference Include="Serilog.Sinks.Graylog" Version="2.1.2" />
<PackageReference Include="SQLitePCLRaw.bundle_e_sqlite3" Version="2.0.2" />
<PackageReference Include="SQLitePCLRaw.provider.sqlite3.netstandard11" Version="1.1.14" />
</ItemGroup>
diff --git a/Jellyfin.Server/Migrations/IMigrationRoutine.cs b/Jellyfin.Server/Migrations/IMigrationRoutine.cs
new file mode 100644
index 000000000..eab995d67
--- /dev/null
+++ b/Jellyfin.Server/Migrations/IMigrationRoutine.cs
@@ -0,0 +1,28 @@
+using System;
+using Microsoft.Extensions.Logging;
+
+namespace Jellyfin.Server.Migrations
+{
+ /// <summary>
+ /// Interface that describes a migration routine.
+ /// </summary>
+ internal interface IMigrationRoutine
+ {
+ /// <summary>
+ /// Gets the unique id for this migration. This should never be modified after the migration has been created.
+ /// </summary>
+ public Guid Id { get; }
+
+ /// <summary>
+ /// Gets the display name of the migration.
+ /// </summary>
+ public string Name { get; }
+
+ /// <summary>
+ /// Execute the migration routine.
+ /// </summary>
+ /// <param name="host">Host that hosts current version.</param>
+ /// <param name="logger">Host logger.</param>
+ public void Perform(CoreAppHost host, ILogger logger);
+ }
+}
diff --git a/Jellyfin.Server/Migrations/MigrationOptions.cs b/Jellyfin.Server/Migrations/MigrationOptions.cs
new file mode 100644
index 000000000..816dd9ee7
--- /dev/null
+++ b/Jellyfin.Server/Migrations/MigrationOptions.cs
@@ -0,0 +1,24 @@
+using System;
+using System.Collections.Generic;
+
+namespace Jellyfin.Server.Migrations
+{
+ /// <summary>
+ /// Configuration part that holds all migrations that were applied.
+ /// </summary>
+ public class MigrationOptions
+ {
+ /// <summary>
+ /// Initializes a new instance of the <see cref="MigrationOptions"/> class.
+ /// </summary>
+ public MigrationOptions()
+ {
+ Applied = new List<(Guid Id, string Name)>();
+ }
+
+ /// <summary>
+ /// Gets the list of applied migration routine names.
+ /// </summary>
+ public List<(Guid Id, string Name)> Applied { get; }
+ }
+}
diff --git a/Jellyfin.Server/Migrations/MigrationRunner.cs b/Jellyfin.Server/Migrations/MigrationRunner.cs
new file mode 100644
index 000000000..b5ea04dca
--- /dev/null
+++ b/Jellyfin.Server/Migrations/MigrationRunner.cs
@@ -0,0 +1,73 @@
+using System;
+using System.Linq;
+using MediaBrowser.Common.Configuration;
+using Microsoft.Extensions.Logging;
+
+namespace Jellyfin.Server.Migrations
+{
+ /// <summary>
+ /// The class that knows which migrations to apply and how to apply them.
+ /// </summary>
+ public sealed class MigrationRunner
+ {
+ /// <summary>
+ /// The list of known migrations, in order of applicability.
+ /// </summary>
+ internal static readonly IMigrationRoutine[] Migrations =
+ {
+ new Routines.DisableTranscodingThrottling(),
+ new Routines.CreateUserLoggingConfigFile()
+ };
+
+ /// <summary>
+ /// Run all needed migrations.
+ /// </summary>
+ /// <param name="host">CoreAppHost that hosts current version.</param>
+ /// <param name="loggerFactory">Factory for making the logger.</param>
+ public static void Run(CoreAppHost host, ILoggerFactory loggerFactory)
+ {
+ var logger = loggerFactory.CreateLogger<MigrationRunner>();
+ var migrationOptions = ((IConfigurationManager)host.ServerConfigurationManager).GetConfiguration<MigrationOptions>(MigrationsListStore.StoreKey);
+
+ if (!host.ServerConfigurationManager.Configuration.IsStartupWizardCompleted && migrationOptions.Applied.Count == 0)
+ {
+ // If startup wizard is not finished, this is a fresh install.
+ // Don't run any migrations, just mark all of them as applied.
+ logger.LogInformation("Marking all known migrations as applied because this is a fresh install");
+ migrationOptions.Applied.AddRange(Migrations.Select(m => (m.Id, m.Name)));
+ host.ServerConfigurationManager.SaveConfiguration(MigrationsListStore.StoreKey, migrationOptions);
+ return;
+ }
+
+ var appliedMigrationIds = migrationOptions.Applied.Select(m => m.Id).ToHashSet();
+
+ for (var i = 0; i < Migrations.Length; i++)
+ {
+ var migrationRoutine = Migrations[i];
+ if (appliedMigrationIds.Contains(migrationRoutine.Id))
+ {
+ logger.LogDebug("Skipping migration '{Name}' since it is already applied", migrationRoutine.Name);
+ continue;
+ }
+
+ logger.LogInformation("Applying migration '{Name}'", migrationRoutine.Name);
+
+ try
+ {
+ migrationRoutine.Perform(host, logger);
+ }
+ catch (Exception ex)
+ {
+ logger.LogError(ex, "Could not apply migration '{Name}'", migrationRoutine.Name);
+ throw;
+ }
+
+ // Mark the migration as completed
+ logger.LogInformation("Migration '{Name}' applied successfully", migrationRoutine.Name);
+ migrationOptions.Applied.Add((migrationRoutine.Id, migrationRoutine.Name));
+ host.ServerConfigurationManager.SaveConfiguration(MigrationsListStore.StoreKey, migrationOptions);
+ logger.LogDebug("Migration '{Name}' marked as applied in configuration.", migrationRoutine.Name);
+ }
+ }
+ }
+}
diff --git a/Jellyfin.Server/Migrations/MigrationsFactory.cs b/Jellyfin.Server/Migrations/MigrationsFactory.cs
new file mode 100644
index 000000000..23c1b1ee6
--- /dev/null
+++ b/Jellyfin.Server/Migrations/MigrationsFactory.cs
@@ -0,0 +1,20 @@
+using System.Collections.Generic;
+using MediaBrowser.Common.Configuration;
+
+namespace Jellyfin.Server.Migrations
+{
+ /// <summary>
+ /// A factory that can find a persistent file of the migration configuration, which lists all applied migrations.
+ /// </summary>
+ public class MigrationsFactory : IConfigurationFactory
+ {
+ /// <inheritdoc/>
+ public IEnumerable<ConfigurationStore> GetConfigurations()
+ {
+ return new[]
+ {
+ new MigrationsListStore()
+ };
+ }
+ }
+}
diff --git a/Jellyfin.Server/Migrations/MigrationsListStore.cs b/Jellyfin.Server/Migrations/MigrationsListStore.cs
new file mode 100644
index 000000000..7a1ca6671
--- /dev/null
+++ b/Jellyfin.Server/Migrations/MigrationsListStore.cs
@@ -0,0 +1,24 @@
+using MediaBrowser.Common.Configuration;
+
+namespace Jellyfin.Server.Migrations
+{
+ /// <summary>
+ /// A configuration that lists all the migration routines that were applied.
+ /// </summary>
+ public class MigrationsListStore : ConfigurationStore
+ {
+ /// <summary>
+ /// The name of the configuration in the storage.
+ /// </summary>
+ public static readonly string StoreKey = "migrations";
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="MigrationsListStore"/> class.
+ /// </summary>
+ public MigrationsListStore()
+ {
+ ConfigurationType = typeof(MigrationOptions);
+ Key = StoreKey;
+ }
+ }
+}
diff --git a/Jellyfin.Server/Migrations/Routines/CreateUserLoggingConfigFile.cs b/Jellyfin.Server/Migrations/Routines/CreateUserLoggingConfigFile.cs
new file mode 100644
index 000000000..3bc32c047
--- /dev/null
+++ b/Jellyfin.Server/Migrations/Routines/CreateUserLoggingConfigFile.cs
@@ -0,0 +1,73 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using MediaBrowser.Common.Configuration;
+using Microsoft.Extensions.Logging;
+using Newtonsoft.Json.Linq;
+
+namespace Jellyfin.Server.Migrations.Routines
+{
+ /// <summary>
+ /// Migration to initialize the user logging configuration file "logging.user.json".
+ /// If the deprecated logging.json file exists and has a custom config, it will be used as logging.user.json,
+ /// otherwise a blank file will be created.
+ /// </summary>
+ internal class CreateUserLoggingConfigFile : IMigrationRoutine
+ {
+ /// <summary>
+ /// File history for logging.json as existed during this migration creation. The contents for each has been minified.
+ /// </summary>
+ private readonly List<string> _defaultConfigHistory = new List<string>
+ {
+ // 9a6c27947353585391e211aa88b925f81e8cd7b9
+ @"{""Serilog"":{""MinimumLevel"":{""Default"":""Information"",""Override"":{""Microsoft"":""Warning"",""System"":""Warning""}},""WriteTo"":[{""Name"":""Console"",""Args"":{""outputTemplate"":""[{Timestamp:HH:mm:ss}] [{Level:u3}] [{ThreadId}] {SourceContext}: {Message:lj}{NewLine}{Exception}""}},{""Name"":""Async"",""Args"":{""configure"":[{""Name"":""File"",""Args"":{""path"":""%JELLYFIN_LOG_DIR%//log_.log"",""rollingInterval"":""Day"",""retainedFileCountLimit"":3,""rollOnFileSizeLimit"":true,""fileSizeLimitBytes"":100000000,""outputTemplate"":""[{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz}] [{Level:u3}] [{ThreadId}] {SourceContext}: {Message}{NewLine}{Exception}""}}]}}],""Enrich"":[""FromLogContext"",""WithThreadId""]}}",
+ // 71bdcd730705a714ee208eaad7290b7c68df3885
+ @"{""Serilog"":{""MinimumLevel"":""Information"",""WriteTo"":[{""Name"":""Console"",""Args"":{""outputTemplate"":""[{Timestamp:HH:mm:ss}] [{Level:u3}] [{ThreadId}] {SourceContext}: {Message:lj}{NewLine}{Exception}""}},{""Name"":""Async"",""Args"":{""configure"":[{""Name"":""File"",""Args"":{""path"":""%JELLYFIN_LOG_DIR%//log_.log"",""rollingInterval"":""Day"",""retainedFileCountLimit"":3,""rollOnFileSizeLimit"":true,""fileSizeLimitBytes"":100000000,""outputTemplate"":""[{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz}] [{Level:u3}] [{ThreadId}] {SourceContext}: {Message}{NewLine}{Exception}""}}]}}],""Enrich"":[""FromLogContext"",""WithThreadId""]}}",
+ // a44936f97f8afc2817d3491615a7cfe1e31c251c
+ @"{""Serilog"":{""MinimumLevel"":""Information"",""WriteTo"":[{""Name"":""Console"",""Args"":{""outputTemplate"":""[{Timestamp:HH:mm:ss}] [{Level:u3}] {Message:lj}{NewLine}{Exception}""}},{""Name"":""File"",""Args"":{""path"":""%JELLYFIN_LOG_DIR%//log_.log"",""rollingInterval"":""Day"",""outputTemplate"":""[{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz}] [{Level:u3}] {Message}{NewLine}{Exception}""}}]}}",
+ // 7af3754a11ad5a4284f107997fb5419a010ce6f3
+ @"{""Serilog"":{""MinimumLevel"":""Information"",""WriteTo"":[{""Name"":""Console"",""Args"":{""outputTemplate"":""[{Timestamp:HH:mm:ss}] [{Level:u3}] {Message:lj}{NewLine}{Exception}""}},{""Name"":""Async"",""Args"":{""configure"":[{""Name"":""File"",""Args"":{""path"":""%JELLYFIN_LOG_DIR%//log_.log"",""rollingInterval"":""Day"",""outputTemplate"":""[{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz}] [{Level:u3}] {Message}{NewLine}{Exception}""}}]}}]}}",
+ // 60691349a11f541958e0b2247c9abc13cb40c9fb
+ @"{""Serilog"":{""MinimumLevel"":""Information"",""WriteTo"":[{""Name"":""Console"",""Args"":{""outputTemplate"":""[{Timestamp:HH:mm:ss}] [{Level:u3}] {Message:lj}{NewLine}{Exception}""}},{""Name"":""Async"",""Args"":{""configure"":[{""Name"":""File"",""Args"":{""path"":""%JELLYFIN_LOG_DIR%//log_.log"",""rollingInterval"":""Day"",""retainedFileCountLimit"":3,""rollOnFileSizeLimit"":true,""fileSizeLimitBytes"":100000000,""outputTemplate"":""[{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz}] [{Level:u3}] {Message}{NewLine}{Exception}""}}]}}]}}",
+ // 65fe243afbcc4b596cf8726708c1965cd34b5f68
+ @"{""Serilog"":{""MinimumLevel"":""Information"",""WriteTo"":[{""Name"":""Console"",""Args"":{""outputTemplate"":""[{Timestamp:HH:mm:ss}] [{Level:u3}] {ThreadId} {SourceContext}: {Message:lj} {NewLine}{Exception}""}},{""Name"":""Async"",""Args"":{""configure"":[{""Name"":""File"",""Args"":{""path"":""%JELLYFIN_LOG_DIR%//log_.log"",""rollingInterval"":""Day"",""retainedFileCountLimit"":3,""rollOnFileSizeLimit"":true,""fileSizeLimitBytes"":100000000,""outputTemplate"":""[{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz}] [{Level:u3}] {ThreadId} {SourceContext}:{Message} {NewLine}{Exception}""}}]}}],""Enrich"":[""FromLogContext"",""WithThreadId""]}}",
+ // 96c9af590494aa8137d5a061aaf1e68feee60b67
+ @"{""Serilog"":{""MinimumLevel"":""Information"",""WriteTo"":[{""Name"":""Console"",""Args"":{""outputTemplate"":""[{Timestamp:HH:mm:ss}] [{Level:u3}] [{ThreadId}] {SourceContext}: {Message:lj}{NewLine}{Exception}""}},{""Name"":""Async"",""Args"":{""configure"":[{""Name"":""File"",""Args"":{""path"":""%JELLYFIN_LOG_DIR%//log_.log"",""rollingInterval"":""Day"",""retainedFileCountLimit"":3,""rollOnFileSizeLimit"":true,""fileSizeLimitBytes"":100000000,""outputTemplate"":""[{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz}] [{Level:u3}] [{ThreadId}] {SourceContext}:{Message}{NewLine}{Exception}""}}]}}],""Enrich"":[""FromLogContext"",""WithThreadId""]}}",
+ };
+
+ /// <inheritdoc/>
+ public Guid Id => Guid.Parse("{EF103419-8451-40D8-9F34-D1A8E93A1679}");
+
+ /// <inheritdoc/>
+ public string Name => "CreateLoggingConfigHeirarchy";
+
+ /// <inheritdoc/>
+ public void Perform(CoreAppHost host, ILogger logger)
+ {
+ var logDirectory = host.Resolve<IApplicationPaths>().ConfigurationDirectoryPath;
+ var existingConfigPath = Path.Combine(logDirectory, "logging.json");
+
+ // If the existing logging.json config file is unmodified, then 'reset' it by moving it to 'logging.old.json'
+ // NOTE: This config file has 'reloadOnChange: true', so this change will take effect immediately even though it has already been loaded
+ if (File.Exists(existingConfigPath) && ExistingConfigUnmodified(existingConfigPath))
+ {
+ File.Move(existingConfigPath, Path.Combine(logDirectory, "logging.old.json"));
+ }
+ }
+
+ /// <summary>
+ /// Check if the existing logging.json file has not been modified by the user by comparing it to all the
+ /// versions in our git history. Until now, the file has never been migrated after first creation so users
+ /// could have any version from the git history.
+ /// </summary>
+ /// <exception cref="IOException"><paramref name="oldConfigPath"/> does not exist or could not be read.</exception>
+ private bool ExistingConfigUnmodified(string oldConfigPath)
+ {
+ var existingConfigJson = JToken.Parse(File.ReadAllText(oldConfigPath));
+ return _defaultConfigHistory
+ .Select(historicalConfigText => JToken.Parse(historicalConfigText))
+ .Any(historicalConfigJson => JToken.DeepEquals(existingConfigJson, historicalConfigJson));
+ }
+ }
+}
diff --git a/Jellyfin.Server/Migrations/Routines/DisableTranscodingThrottling.cs b/Jellyfin.Server/Migrations/Routines/DisableTranscodingThrottling.cs
new file mode 100644
index 000000000..6f8e4a8ff
--- /dev/null
+++ b/Jellyfin.Server/Migrations/Routines/DisableTranscodingThrottling.cs
@@ -0,0 +1,33 @@
+using System;
+using MediaBrowser.Common.Configuration;
+using MediaBrowser.Model.Configuration;
+using Microsoft.Extensions.Logging;
+
+namespace Jellyfin.Server.Migrations.Routines
+{
+ /// <summary>
+ /// Disable transcode throttling for all installations since it is currently broken for certain video formats.
+ /// </summary>
+ internal class DisableTranscodingThrottling : IMigrationRoutine
+ {
+ /// <inheritdoc/>
+ public Guid Id => Guid.Parse("{4124C2CD-E939-4FFB-9BE9-9B311C413638}");
+
+ /// <inheritdoc/>
+ public string Name => "DisableTranscodingThrottling";
+
+ /// <inheritdoc/>
+ public void Perform(CoreAppHost host, ILogger logger)
+ {
+ // Set EnableThrottling to false since it wasn't used before and may introduce issues
+ var encoding = ((IConfigurationManager)host.ServerConfigurationManager).GetConfiguration<EncodingOptions>("encoding");
+ if (encoding.EnableThrottling)
+ {
+ logger.LogInformation("Disabling transcoding throttling during migration");
+ encoding.EnableThrottling = false;
+
+ host.ServerConfigurationManager.SaveConfiguration("encoding", encoding);
+ }
+ }
+ }
+}
diff --git a/Jellyfin.Server/Program.cs b/Jellyfin.Server/Program.cs
index 712990a1e..9635cc6ec 100644
--- a/Jellyfin.Server/Program.cs
+++ b/Jellyfin.Server/Program.cs
@@ -1,10 +1,8 @@
using System;
using System.Diagnostics;
-using System.Globalization;
using System.IO;
using System.Linq;
using System.Net;
-using System.Net.Security;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Text;
@@ -14,16 +12,20 @@ using System.Threading.Tasks;
using CommandLine;
using Emby.Drawing;
using Emby.Server.Implementations;
+using Emby.Server.Implementations.HttpServer;
using Emby.Server.Implementations.IO;
using Emby.Server.Implementations.Networking;
using Jellyfin.Drawing.Skia;
using MediaBrowser.Common.Configuration;
using MediaBrowser.Controller.Drawing;
-using MediaBrowser.Model.Globalization;
+using MediaBrowser.Controller.Extensions;
+using MediaBrowser.WebDashboard.Api;
using Microsoft.AspNetCore.Hosting;
+using Microsoft.AspNetCore.Server.Kestrel.Core;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
+using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
using Serilog;
@@ -38,6 +40,16 @@ namespace Jellyfin.Server
/// </summary>
public static class Program
{
+ /// <summary>
+ /// The name of logging configuration file containing application defaults.
+ /// </summary>
+ public static readonly string LoggingConfigFileDefault = "logging.default.json";
+
+ /// <summary>
+ /// The name of the logging configuration file containing the system-specific override settings.
+ /// </summary>
+ public static readonly string LoggingConfigFileSystem = "logging.json";
+
private static readonly CancellationTokenSource _tokenSource = new CancellationTokenSource();
private static readonly ILoggerFactory _loggerFactory = new SerilogLoggerFactory();
private static ILogger _logger = NullLogger.Instance;
@@ -102,10 +114,13 @@ namespace Jellyfin.Server
// $JELLYFIN_LOG_DIR needs to be set for the logger configuration manager
Environment.SetEnvironmentVariable("JELLYFIN_LOG_DIR", appPaths.LogDirectoryPath);
- IConfiguration appConfig = await CreateConfiguration(appPaths).ConfigureAwait(false);
+ await InitLoggingConfigFile(appPaths).ConfigureAwait(false);
- CreateLogger(appConfig, appPaths);
+ // Create an instance of the application configuration to use for application startup
+ IConfiguration startupConfig = CreateAppConfiguration(options, appPaths);
+ // Initialize logging framework
+ InitializeLoggingFramework(startupConfig, appPaths);
_logger = _loggerFactory.CreateLogger("Main");
// Log uncaught exceptions to the logging instead of std error
@@ -169,23 +184,37 @@ namespace Jellyfin.Server
_loggerFactory,
options,
new ManagedFileSystem(_loggerFactory.CreateLogger<ManagedFileSystem>(), appPaths),
- new NullImageEncoder(),
- new NetworkManager(_loggerFactory.CreateLogger<NetworkManager>()),
- appConfig);
+ new NetworkManager(_loggerFactory.CreateLogger<NetworkManager>()));
+
try
{
+ // If hosting the web client, validate the client content path
+ if (startupConfig.HostWebClient())
+ {
+ string webContentPath = DashboardService.GetDashboardUIPath(startupConfig, appHost.ServerConfigurationManager);
+ if (!Directory.Exists(webContentPath) || Directory.GetFiles(webContentPath).Length == 0)
+ {
+ throw new InvalidOperationException(
+ "The server is expected to host the web client, but the provided content directory is either " +
+ $"invalid or empty: {webContentPath}. If you do not want to host the web client with the " +
+ "server, you may set the '--nowebclient' command line flag, or set" +
+ $"'{MediaBrowser.Controller.Extensions.ConfigurationExtensions.HostWebClientKey}=false' in your config settings.");
+ }
+ }
+
ServiceCollection serviceCollection = new ServiceCollection();
- await appHost.InitAsync(serviceCollection).ConfigureAwait(false);
+ appHost.Init(serviceCollection);
- var host = CreateWebHostBuilder(appHost, serviceCollection).Build();
+ var webHost = CreateWebHostBuilder(appHost, serviceCollection, options, startupConfig, appPaths).Build();
- // A bit hacky to re-use service provider since ASP.NET doesn't allow a custom service collection.
- appHost.ServiceProvider = host.Services;
- appHost.FindParts();
+ // Re-use the web host service provider in the app host since ASP.NET doesn't allow a custom service collection.
+ appHost.ServiceProvider = webHost.Services;
+ await appHost.InitializeServices().ConfigureAwait(false);
+ Migrations.MigrationRunner.Run(appHost, _loggerFactory);
try
{
- await host.StartAsync().ConfigureAwait(false);
+ await webHost.StartAsync().ConfigureAwait(false);
}
catch
{
@@ -193,8 +222,6 @@ namespace Jellyfin.Server
throw;
}
- appHost.ImageProcessor.ImageEncoder = GetImageEncoder(appPaths, appHost.LocalizationManager);
-
await appHost.RunStartupTasksAsync().ConfigureAwait(false);
stopWatch.Stop();
@@ -223,30 +250,50 @@ namespace Jellyfin.Server
}
}
- private static IWebHostBuilder CreateWebHostBuilder(ApplicationHost appHost, IServiceCollection serviceCollection)
+ private static IWebHostBuilder CreateWebHostBuilder(
+ ApplicationHost appHost,
+ IServiceCollection serviceCollection,
+ StartupOptions commandLineOpts,
+ IConfiguration startupConfig,
+ IApplicationPaths appPaths)
{
return new WebHostBuilder()
- .UseKestrel(options =>
+ .UseKestrel((builderContext, options) =>
{
var addresses = appHost.ServerConfigurationManager
.Configuration
.LocalNetworkAddresses
.Select(appHost.NormalizeConfiguredLocalAddress)
.Where(i => i != null)
- .ToList();
- if (addresses.Any())
+ .ToHashSet();
+ if (addresses.Any() && !addresses.Contains(IPAddress.Any))
{
+ if (!addresses.Contains(IPAddress.Loopback))
+ {
+ // we must listen on loopback for LiveTV to function regardless of the settings
+ addresses.Add(IPAddress.Loopback);
+ }
+
foreach (var address in addresses)
{
- _logger.LogInformation("Kestrel listening on {ipaddr}", address);
+ _logger.LogInformation("Kestrel listening on {IpAddress}", address);
options.Listen(address, appHost.HttpPort);
if (appHost.EnableHttps && appHost.Certificate != null)
{
- options.Listen(
- address,
- appHost.HttpsPort,
- listenOptions => listenOptions.UseHttps(appHost.Certificate));
+ options.Listen(address, appHost.HttpsPort, listenOptions =>
+ {
+ listenOptions.UseHttps(appHost.Certificate);
+ listenOptions.Protocols = HttpProtocols.Http1AndHttp2;
+ });
+ }
+ else if (builderContext.HostingEnvironment.IsDevelopment())
+ {
+ options.Listen(address, appHost.HttpsPort, listenOptions =>
+ {
+ listenOptions.UseHttps();
+ listenOptions.Protocols = HttpProtocols.Http1AndHttp2;
+ });
}
}
}
@@ -257,13 +304,24 @@ namespace Jellyfin.Server
if (appHost.EnableHttps && appHost.Certificate != null)
{
- options.ListenAnyIP(
- appHost.HttpsPort,
- listenOptions => listenOptions.UseHttps(appHost.Certificate));
+ options.ListenAnyIP(appHost.HttpsPort, listenOptions =>
+ {
+ listenOptions.UseHttps(appHost.Certificate);
+ listenOptions.Protocols = HttpProtocols.Http1AndHttp2;
+ });
+ }
+ else if (builderContext.HostingEnvironment.IsDevelopment())
+ {
+ options.ListenAnyIP(appHost.HttpsPort, listenOptions =>
+ {
+ listenOptions.UseHttps();
+ listenOptions.Protocols = HttpProtocols.Http1AndHttp2;
+ });
}
}
})
- .UseContentRoot(appHost.ContentRoot)
+ .ConfigureAppConfiguration(config => config.ConfigureAppConfiguration(commandLineOpts, appPaths, startupConfig))
+ .UseSerilog()
.ConfigureServices(services =>
{
// Merge the external ServiceCollection into ASP.NET DI
@@ -386,9 +444,8 @@ namespace Jellyfin.Server
// webDir
// IF --webdir
// ELSE IF $JELLYFIN_WEB_DIR
- // ELSE use <bindir>/jellyfin-web
+ // ELSE <bindir>/jellyfin-web
var webDir = options.WebDir;
-
if (string.IsNullOrEmpty(webDir))
{
webDir = Environment.GetEnvironmentVariable("JELLYFIN_WEB_DIR");
@@ -435,39 +492,63 @@ namespace Jellyfin.Server
return new ServerApplicationPaths(dataDir, logDir, configDir, cacheDir, webDir);
}
- private static async Task<IConfiguration> CreateConfiguration(IApplicationPaths appPaths)
+ /// <summary>
+ /// Initialize the logging configuration file using the bundled resource file as a default if it doesn't exist
+ /// already.
+ /// </summary>
+ private static async Task InitLoggingConfigFile(IApplicationPaths appPaths)
{
+ // Do nothing if the config file already exists
+ string configPath = Path.Combine(appPaths.ConfigurationDirectoryPath, LoggingConfigFileDefault);
+ if (File.Exists(configPath))
+ {
+ return;
+ }
+
+ // Get a stream of the resource contents
+ // NOTE: The .csproj name is used instead of the assembly name in the resource path
const string ResourcePath = "Jellyfin.Server.Resources.Configuration.logging.json";
- string configPath = Path.Combine(appPaths.ConfigurationDirectoryPath, "logging.json");
+ await using Stream? resource = typeof(Program).Assembly.GetManifestResourceStream(ResourcePath)
+ ?? throw new InvalidOperationException($"Invalid resource path: '{ResourcePath}'");
- if (!File.Exists(configPath))
- {
- // For some reason the csproj name is used instead of the assembly name
- using (Stream? resource = typeof(Program).Assembly.GetManifestResourceStream(ResourcePath))
- {
- if (resource == null)
- {
- throw new InvalidOperationException(
- string.Format(
- CultureInfo.InvariantCulture,
- "Invalid resource path: '{0}'",
- ResourcePath));
- }
+ // Copy the resource contents to the expected file path for the config file
+ await using Stream dst = File.Open(configPath, FileMode.CreateNew);
+ await resource.CopyToAsync(dst).ConfigureAwait(false);
+ }
- using Stream dst = File.Open(configPath, FileMode.CreateNew);
- await resource.CopyToAsync(dst).ConfigureAwait(false);
- }
+ private static IConfiguration CreateAppConfiguration(StartupOptions commandLineOpts, IApplicationPaths appPaths)
+ {
+ return new ConfigurationBuilder()
+ .ConfigureAppConfiguration(commandLineOpts, appPaths)
+ .Build();
+ }
+
+ private static IConfigurationBuilder ConfigureAppConfiguration(
+ this IConfigurationBuilder config,
+ StartupOptions commandLineOpts,
+ IApplicationPaths appPaths,
+ IConfiguration? startupConfig = null)
+ {
+ // Use the swagger API page as the default redirect path if not hosting the web client
+ var inMemoryDefaultConfig = ConfigurationOptions.DefaultConfiguration;
+ if (startupConfig != null && !startupConfig.HostWebClient())
+ {
+ inMemoryDefaultConfig[HttpListenerHost.DefaultRedirectKey] = "swagger/index.html";
}
- return new ConfigurationBuilder()
+ return config
.SetBasePath(appPaths.ConfigurationDirectoryPath)
- .AddInMemoryCollection(ConfigurationOptions.Configuration)
- .AddJsonFile("logging.json", false, true)
+ .AddInMemoryCollection(inMemoryDefaultConfig)
+ .AddJsonFile(LoggingConfigFileDefault, optional: false, reloadOnChange: true)
+ .AddJsonFile(LoggingConfigFileSystem, optional: true, reloadOnChange: true)
.AddEnvironmentVariables("JELLYFIN_")
- .Build();
+ .AddInMemoryCollection(commandLineOpts.ConvertToConfig());
}
- private static void CreateLogger(IConfiguration configuration, IApplicationPaths appPaths)
+ /// <summary>
+ /// Initialize Serilog using configuration and fall back to defaults on failure.
+ /// </summary>
+ private static void InitializeLoggingFramework(IConfiguration configuration, IApplicationPaths appPaths)
{
try
{
@@ -475,45 +556,25 @@ namespace Jellyfin.Server
Serilog.Log.Logger = new LoggerConfiguration()
.ReadFrom.Configuration(configuration)
.Enrich.FromLogContext()
+ .Enrich.WithThreadId()
.CreateLogger();
}
catch (Exception ex)
{
Serilog.Log.Logger = new LoggerConfiguration()
- .WriteTo.Console(outputTemplate: "[{Timestamp:HH:mm:ss}] [{Level:u3}] {Message:lj}{NewLine}{Exception}")
+ .WriteTo.Console(outputTemplate: "[{Timestamp:HH:mm:ss}] [{Level:u3}] [{ThreadId}] {SourceContext}: {Message:lj}{NewLine}{Exception}")
.WriteTo.Async(x => x.File(
Path.Combine(appPaths.LogDirectoryPath, "log_.log"),
rollingInterval: RollingInterval.Day,
- outputTemplate: "[{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz}] [{Level:u3}] {Message}{NewLine}{Exception}"))
+ outputTemplate: "[{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz}] [{Level:u3}] [{ThreadId}] {SourceContext}: {Message}{NewLine}{Exception}"))
.Enrich.FromLogContext()
+ .Enrich.WithThreadId()
.CreateLogger();
Serilog.Log.Logger.Fatal(ex, "Failed to create/read logger configuration");
}
}
- private static IImageEncoder GetImageEncoder(
- IApplicationPaths appPaths,
- ILocalizationManager localizationManager)
- {
- try
- {
- // Test if the native lib is available
- SkiaEncoder.TestSkia();
-
- return new SkiaEncoder(
- _loggerFactory.CreateLogger<SkiaEncoder>(),
- appPaths,
- localizationManager);
- }
- catch (Exception ex)
- {
- _logger.LogWarning(ex, "Skia not available. Will fallback to NullIMageEncoder.");
- }
-
- return new NullImageEncoder();
- }
-
private static void StartNewInstance(StartupOptions options)
{
_logger.LogInformation("Starting new instance");
diff --git a/Jellyfin.Server/Properties/launchSettings.json b/Jellyfin.Server/Properties/launchSettings.json
new file mode 100644
index 000000000..b6e2bcf97
--- /dev/null
+++ b/Jellyfin.Server/Properties/launchSettings.json
@@ -0,0 +1,17 @@
+{
+ "profiles": {
+ "Jellyfin.Server": {
+ "commandName": "Project",
+ "environmentVariables": {
+ "ASPNETCORE_ENVIRONMENT": "Development"
+ }
+ },
+ "Jellyfin.Server (nowebclient)": {
+ "commandName": "Project",
+ "environmentVariables": {
+ "ASPNETCORE_ENVIRONMENT": "Development"
+ },
+ "commandLineArgs": "--nowebclient"
+ }
+ }
+}
diff --git a/Jellyfin.Server/Resources/Configuration/logging.json b/Jellyfin.Server/Resources/Configuration/logging.json
index e85ef05af..f64a85219 100644
--- a/Jellyfin.Server/Resources/Configuration/logging.json
+++ b/Jellyfin.Server/Resources/Configuration/logging.json
@@ -1,11 +1,17 @@
{
"Serilog": {
- "MinimumLevel": "Information",
+ "MinimumLevel": {
+ "Default": "Information",
+ "Override": {
+ "Microsoft": "Warning",
+ "System": "Warning"
+ }
+ },
"WriteTo": [
{
"Name": "Console",
"Args": {
- "outputTemplate": "[{Timestamp:HH:mm:ss}] [{Level:u3}] {Message:lj}{NewLine}{Exception}"
+ "outputTemplate": "[{Timestamp:HH:mm:ss}] [{Level:u3}] [{ThreadId}] {SourceContext}: {Message:lj}{NewLine}{Exception}"
}
},
{
@@ -20,12 +26,13 @@
"retainedFileCountLimit": 3,
"rollOnFileSizeLimit": true,
"fileSizeLimitBytes": 100000000,
- "outputTemplate": "[{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz}] [{Level:u3}] {Message}{NewLine}{Exception}"
+ "outputTemplate": "[{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz}] [{Level:u3}] [{ThreadId}] {SourceContext}: {Message}{NewLine}{Exception}"
}
}
]
}
}
- ]
+ ],
+ "Enrich": [ "FromLogContext", "WithThreadId" ]
}
}
diff --git a/Jellyfin.Server/Startup.cs b/Jellyfin.Server/Startup.cs
index 45f2b9d7c..5f9a5c161 100644
--- a/Jellyfin.Server/Startup.cs
+++ b/Jellyfin.Server/Startup.cs
@@ -5,6 +5,7 @@ using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
+using Prometheus;
namespace Jellyfin.Server
{
@@ -68,9 +69,19 @@ namespace Jellyfin.Server
app.UseJellyfinApiSwagger();
app.UseRouting();
app.UseAuthorization();
+ if (_serverConfigurationManager.Configuration.EnableMetrics)
+ {
+ // Must be registered after any middleware that could chagne HTTP response codes or the data will be bad
+ app.UseHttpMetrics();
+ }
+
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
+ if (_serverConfigurationManager.Configuration.EnableMetrics)
+ {
+ endpoints.MapMetrics(_serverConfigurationManager.Configuration.BaseUrl.TrimStart('/') + "/metrics");
+ }
});
app.Use(serverApplicationHost.ExecuteHttpHandlerAsync);
diff --git a/Jellyfin.Server/StartupOptions.cs b/Jellyfin.Server/StartupOptions.cs
index 1fb1c5af8..6e15d058f 100644
--- a/Jellyfin.Server/StartupOptions.cs
+++ b/Jellyfin.Server/StartupOptions.cs
@@ -1,5 +1,8 @@
+using System.Collections.Generic;
using CommandLine;
using Emby.Server.Implementations;
+using Emby.Server.Implementations.Updates;
+using MediaBrowser.Controller.Extensions;
namespace Jellyfin.Server
{
@@ -16,6 +19,12 @@ namespace Jellyfin.Server
public string? DataDir { get; set; }
/// <summary>
+ /// Gets or sets a value indicating whether the server should not host the web client.
+ /// </summary>
+ [Option("nowebclient", Required = false, HelpText = "Indicates that the web server should not host the web client.")]
+ public bool NoWebClient { get; set; }
+
+ /// <summary>
/// Gets or sets the path to the web directory.
/// </summary>
/// <value>The path to the web directory.</value>
@@ -66,5 +75,30 @@ namespace Jellyfin.Server
/// <inheritdoc />
[Option("restartargs", Required = false, HelpText = "Arguments for restart script.")]
public string? RestartArgs { get; set; }
+
+ /// <inheritdoc />
+ [Option("plugin-manifest-url", Required = false, HelpText = "A custom URL for the plugin repository JSON manifest")]
+ public string? PluginManifestUrl { get; set; }
+
+ /// <summary>
+ /// Gets the command line options as a dictionary that can be used in the .NET configuration system.
+ /// </summary>
+ /// <returns>The configuration dictionary.</returns>
+ public Dictionary<string, string> ConvertToConfig()
+ {
+ var config = new Dictionary<string, string>();
+
+ if (PluginManifestUrl != null)
+ {
+ config.Add(InstallationManager.PluginManifestUrlKey, PluginManifestUrl);
+ }
+
+ if (NoWebClient)
+ {
+ config.Add(ConfigurationExtensions.HostWebClientKey, bool.FalseString);
+ }
+
+ return config;
+ }
}
}
diff --git a/MediaBrowser.Api/ApiEntryPoint.cs b/MediaBrowser.Api/ApiEntryPoint.cs
index 1a3657c92..6691080bc 100644
--- a/MediaBrowser.Api/ApiEntryPoint.cs
+++ b/MediaBrowser.Api/ApiEntryPoint.cs
@@ -12,7 +12,6 @@ using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.MediaEncoding;
using MediaBrowser.Controller.Plugins;
using MediaBrowser.Controller.Session;
-using MediaBrowser.Model.Configuration;
using MediaBrowser.Model.IO;
using MediaBrowser.Model.Session;
using Microsoft.Extensions.Logging;
@@ -62,7 +61,7 @@ namespace MediaBrowser.Api
/// <param name="fileSystem">The file system.</param>
/// <param name="mediaSourceManager">The media source manager.</param>
public ApiEntryPoint(
- ILogger logger,
+ ILogger<ApiEntryPoint> logger,
ISessionManager sessionManager,
IServerConfigurationManager config,
IFileSystem fileSystem,
@@ -87,12 +86,9 @@ namespace MediaBrowser.Api
return Array.Empty<string>();
}
- if (removeEmpty)
- {
- return value.Split(new[] { separator }, StringSplitOptions.RemoveEmptyEntries);
- }
-
- return value.Split(separator);
+ return removeEmpty
+ ? value.Split(new[] { separator }, StringSplitOptions.RemoveEmptyEntries)
+ : value.Split(separator);
}
public SemaphoreSlim GetTranscodingLock(string outputPath)
@@ -259,7 +255,7 @@ namespace MediaBrowser.Api
public void ReportTranscodingProgress(TranscodingJob job, StreamState state, TimeSpan? transcodingPosition, float? framerate, double? percentComplete, long? bytesTranscoded, int? bitRate)
{
- var ticks = transcodingPosition.HasValue ? transcodingPosition.Value.Ticks : (long?)null;
+ var ticks = transcodingPosition?.Ticks;
if (job != null)
{
@@ -488,16 +484,9 @@ namespace MediaBrowser.Api
/// <returns>Task.</returns>
internal Task KillTranscodingJobs(string deviceId, string playSessionId, Func<string, bool> deleteFiles)
{
- return KillTranscodingJobs(j =>
- {
- if (!string.IsNullOrWhiteSpace(playSessionId))
- {
- return string.Equals(playSessionId, j.PlaySessionId, StringComparison.OrdinalIgnoreCase);
- }
-
- return string.Equals(deviceId, j.DeviceId, StringComparison.OrdinalIgnoreCase);
-
- }, deleteFiles);
+ return KillTranscodingJobs(j => string.IsNullOrWhiteSpace(playSessionId)
+ ? string.Equals(deviceId, j.DeviceId, StringComparison.OrdinalIgnoreCase)
+ : string.Equals(playSessionId, j.PlaySessionId, StringComparison.OrdinalIgnoreCase), deleteFiles);
}
/// <summary>
@@ -562,10 +551,7 @@ namespace MediaBrowser.Api
lock (job.ProcessLock)
{
- if (job.TranscodingThrottler != null)
- {
- job.TranscodingThrottler.Stop().GetAwaiter().GetResult();
- }
+ job.TranscodingThrottler?.Stop().GetAwaiter().GetResult();
var process = job.Process;
diff --git a/MediaBrowser.Api/BaseApiService.cs b/MediaBrowser.Api/BaseApiService.cs
index 2b994d279..1a1d86362 100644
--- a/MediaBrowser.Api/BaseApiService.cs
+++ b/MediaBrowser.Api/BaseApiService.cs
@@ -9,8 +9,8 @@ using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Net;
using MediaBrowser.Controller.Session;
using MediaBrowser.Model.Entities;
-using MediaBrowser.Model.Services;
using MediaBrowser.Model.Querying;
+using MediaBrowser.Model.Services;
using Microsoft.Extensions.Logging;
namespace MediaBrowser.Api
@@ -58,12 +58,9 @@ namespace MediaBrowser.Api
public static string[] SplitValue(string value, char delim)
{
- if (value == null)
- {
- return Array.Empty<string>();
- }
-
- return value.Split(new[] { delim }, StringSplitOptions.RemoveEmptyEntries);
+ return value == null
+ ? Array.Empty<string>()
+ : value.Split(new[] { delim }, StringSplitOptions.RemoveEmptyEntries);
}
public static Guid[] GetGuids(string value)
@@ -97,19 +94,10 @@ namespace MediaBrowser.Api
var authenticatedUser = auth.User;
// If they're going to update the record of another user, they must be an administrator
- if (!userId.Equals(auth.UserId))
+ if ((!userId.Equals(auth.UserId) && !authenticatedUser.Policy.IsAdministrator)
+ || (restrictUserPreferences && !authenticatedUser.Policy.EnableUserPreferenceAccess))
{
- if (!authenticatedUser.Policy.IsAdministrator)
- {
- throw new SecurityException("Unauthorized access.");
- }
- }
- else if (restrictUserPreferences)
- {
- if (!authenticatedUser.Policy.EnableUserPreferenceAccess)
- {
- throw new SecurityException("Unauthorized access.");
- }
+ throw new SecurityException("Unauthorized access.");
}
}
@@ -138,8 +126,8 @@ namespace MediaBrowser.Api
options.Fields = hasFields.GetItemFields();
}
- if (!options.ContainsField(Model.Querying.ItemFields.RecursiveItemCount)
- || !options.ContainsField(Model.Querying.ItemFields.ChildCount))
+ if (!options.ContainsField(ItemFields.RecursiveItemCount)
+ || !options.ContainsField(ItemFields.ChildCount))
{
var client = authContext.GetAuthorizationInfo(Request).Client ?? string.Empty;
if (client.IndexOf("kodi", StringComparison.OrdinalIgnoreCase) != -1 ||
@@ -150,7 +138,7 @@ namespace MediaBrowser.Api
int oldLen = options.Fields.Length;
var arr = new ItemFields[oldLen + 1];
options.Fields.CopyTo(arr, 0);
- arr[oldLen] = Model.Querying.ItemFields.RecursiveItemCount;
+ arr[oldLen] = ItemFields.RecursiveItemCount;
options.Fields = arr;
}
@@ -166,7 +154,7 @@ namespace MediaBrowser.Api
int oldLen = options.Fields.Length;
var arr = new ItemFields[oldLen + 1];
options.Fields.CopyTo(arr, 0);
- arr[oldLen] = Model.Querying.ItemFields.ChildCount;
+ arr[oldLen] = ItemFields.ChildCount;
options.Fields = arr;
}
}
@@ -282,27 +270,21 @@ namespace MediaBrowser.Api
}).OfType<T>().FirstOrDefault();
- if (result == null)
+ result ??= libraryManager.GetItemList(new InternalItemsQuery
{
- result = libraryManager.GetItemList(new InternalItemsQuery
- {
- Name = name.Replace(BaseItem.SlugChar, '/'),
- IncludeItemTypes = new[] { typeof(T).Name },
- DtoOptions = dtoOptions
+ Name = name.Replace(BaseItem.SlugChar, '/'),
+ IncludeItemTypes = new[] { typeof(T).Name },
+ DtoOptions = dtoOptions
- }).OfType<T>().FirstOrDefault();
- }
+ }).OfType<T>().FirstOrDefault();
- if (result == null)
+ result ??= libraryManager.GetItemList(new InternalItemsQuery
{
- result = libraryManager.GetItemList(new InternalItemsQuery
- {
- Name = name.Replace(BaseItem.SlugChar, '?'),
- IncludeItemTypes = new[] { typeof(T).Name },
- DtoOptions = dtoOptions
+ Name = name.Replace(BaseItem.SlugChar, '?'),
+ IncludeItemTypes = new[] { typeof(T).Name },
+ DtoOptions = dtoOptions
- }).OfType<T>().FirstOrDefault();
- }
+ }).OfType<T>().FirstOrDefault();
return result;
}
diff --git a/MediaBrowser.Api/ChannelService.cs b/MediaBrowser.Api/ChannelService.cs
index 92c32f2ad..fd9b8c396 100644
--- a/MediaBrowser.Api/ChannelService.cs
+++ b/MediaBrowser.Api/ChannelService.cs
@@ -4,8 +4,8 @@ using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using MediaBrowser.Api.UserLibrary;
-using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Channels;
+using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Net;
@@ -116,12 +116,9 @@ namespace MediaBrowser.Api
{
var val = Filters;
- if (string.IsNullOrEmpty(val))
- {
- return new ItemFilter[] { };
- }
-
- return val.Split(',').Select(v => (ItemFilter)Enum.Parse(typeof(ItemFilter), v, true));
+ return string.IsNullOrEmpty(val)
+ ? Array.Empty<ItemFilter>()
+ : val.Split(',').Select(v => Enum.Parse<ItemFilter>(v, true));
}
/// <summary>
@@ -173,14 +170,9 @@ namespace MediaBrowser.Api
/// <returns>IEnumerable{ItemFilter}.</returns>
public IEnumerable<ItemFilter> GetFilters()
{
- var val = Filters;
-
- if (string.IsNullOrEmpty(val))
- {
- return new ItemFilter[] { };
- }
-
- return val.Split(',').Select(v => (ItemFilter)Enum.Parse(typeof(ItemFilter), v, true));
+ return string.IsNullOrEmpty(Filters)
+ ? Array.Empty<ItemFilter>()
+ : Filters.Split(',').Select(v => Enum.Parse<ItemFilter>(v, true));
}
}
@@ -241,7 +233,7 @@ namespace MediaBrowser.Api
{
Limit = request.Limit,
StartIndex = request.StartIndex,
- ChannelIds = new Guid[] { new Guid(request.Id) },
+ ChannelIds = new[] { new Guid(request.Id) },
ParentId = string.IsNullOrWhiteSpace(request.FolderId) ? Guid.Empty : new Guid(request.FolderId),
OrderBy = request.GetOrderBy(),
DtoOptions = new Controller.Dto.DtoOptions
diff --git a/MediaBrowser.Api/Devices/DeviceService.cs b/MediaBrowser.Api/Devices/DeviceService.cs
index 8b63decd2..7004a2559 100644
--- a/MediaBrowser.Api/Devices/DeviceService.cs
+++ b/MediaBrowser.Api/Devices/DeviceService.cs
@@ -155,16 +155,14 @@ namespace MediaBrowser.Api.Devices
Id = id
});
}
- else
+
+ return _deviceManager.AcceptCameraUpload(deviceId, request.RequestStream, new LocalFileInfo
{
- return _deviceManager.AcceptCameraUpload(deviceId, request.RequestStream, new LocalFileInfo
- {
- MimeType = Request.ContentType,
- Album = album,
- Name = name,
- Id = id
- });
- }
+ MimeType = Request.ContentType,
+ Album = album,
+ Name = name,
+ Id = id
+ });
}
}
}
diff --git a/MediaBrowser.Api/EnvironmentService.cs b/MediaBrowser.Api/EnvironmentService.cs
index c6dbfb938..d199ce154 100644
--- a/MediaBrowser.Api/EnvironmentService.cs
+++ b/MediaBrowser.Api/EnvironmentService.cs
@@ -6,7 +6,6 @@ using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Net;
using MediaBrowser.Model.IO;
-using MediaBrowser.Model.Net;
using MediaBrowser.Model.Services;
using Microsoft.Extensions.Logging;
@@ -178,7 +177,7 @@ namespace MediaBrowser.Api
}
public object Get(GetDefaultDirectoryBrowser request) =>
- ToOptimizedResult(new DefaultDirectoryBrowserInfo {Path = null});
+ ToOptimizedResult(new DefaultDirectoryBrowserInfo { Path = null });
/// <summary>
/// Gets the specified request.
@@ -259,12 +258,7 @@ namespace MediaBrowser.Api
return false;
}
- if (!request.IncludeDirectories && isDirectory)
- {
- return false;
- }
-
- return true;
+ return request.IncludeDirectories || !isDirectory;
});
return entries.Select(f => new FileSystemEntryInfo
diff --git a/MediaBrowser.Api/FilterService.cs b/MediaBrowser.Api/FilterService.cs
index 25f23bcd1..5eb72cdb1 100644
--- a/MediaBrowser.Api/FilterService.cs
+++ b/MediaBrowser.Api/FilterService.cs
@@ -133,7 +133,7 @@ namespace MediaBrowser.Api
// Non recursive not yet supported for library folders
if ((request.Recursive ?? true) || parentItem is UserView || parentItem is ICollectionFolder)
{
- genreQuery.AncestorIds = parentItem == null ? Array.Empty<Guid>() : new Guid[] { parentItem.Id };
+ genreQuery.AncestorIds = parentItem == null ? Array.Empty<Guid>() : new[] { parentItem.Id };
}
else
{
@@ -231,7 +231,7 @@ namespace MediaBrowser.Api
EnableTotalRecordCount = false,
DtoOptions = new Controller.Dto.DtoOptions
{
- Fields = new ItemFields[] { ItemFields.Genres, ItemFields.Tags },
+ Fields = new[] { ItemFields.Genres, ItemFields.Tags },
EnableImages = false,
EnableUserData = false
}
diff --git a/MediaBrowser.Api/Images/ImageService.cs b/MediaBrowser.Api/Images/ImageService.cs
index e94c1321f..2e9b3e6cb 100644
--- a/MediaBrowser.Api/Images/ImageService.cs
+++ b/MediaBrowser.Api/Images/ImageService.cs
@@ -24,7 +24,7 @@ using Microsoft.Net.Http.Headers;
namespace MediaBrowser.Api.Images
{
/// <summary>
- /// Class GetItemImage
+ /// Class GetItemImage.
/// </summary>
[Route("/Items/{Id}/Images", "GET", Summary = "Gets information about an item's images")]
[Authenticated]
@@ -332,7 +332,8 @@ namespace MediaBrowser.Api.Images
var fileInfo = _fileSystem.GetFileInfo(info.Path);
length = fileInfo.Length;
- ImageDimensions size = _imageProcessor.GetImageDimensions(item, info, true);
+ ImageDimensions size = _imageProcessor.GetImageDimensions(item, info);
+ _libraryManager.UpdateImages(item);
width = size.Width;
height = size.Height;
@@ -558,21 +559,6 @@ namespace MediaBrowser.Api.Images
throw new ResourceNotFoundException(string.Format("{0} does not have an image of type {1}", displayText, request.Type));
}
- IImageEnhancer[] supportedImageEnhancers;
- if (_imageProcessor.ImageEnhancers.Count > 0)
- {
- if (item == null)
- {
- item = _libraryManager.GetItemById(itemId);
- }
-
- supportedImageEnhancers = request.EnableImageEnhancers ? _imageProcessor.GetSupportedEnhancers(item, request.Type).ToArray() : Array.Empty<IImageEnhancer>();
- }
- else
- {
- supportedImageEnhancers = Array.Empty<IImageEnhancer>();
- }
-
bool cropwhitespace;
if (request.CropWhitespace.HasValue)
{
@@ -598,33 +584,38 @@ namespace MediaBrowser.Api.Images
{"realTimeInfo.dlna.org", "DLNA.ORG_TLAG=*"}
};
- return GetImageResult(item,
+ return GetImageResult(
+ item,
itemId,
request,
imageInfo,
cropwhitespace,
outputFormats,
- supportedImageEnhancers,
cacheDuration,
responseHeaders,
isHeadRequest);
}
- private async Task<object> GetImageResult(BaseItem item,
+ private async Task<object> GetImageResult(
+ BaseItem item,
Guid itemId,
ImageRequest request,
ItemImageInfo image,
bool cropwhitespace,
IReadOnlyCollection<ImageFormat> supportedFormats,
- IReadOnlyCollection<IImageEnhancer> enhancers,
TimeSpan? cacheDuration,
IDictionary<string, string> headers,
bool isHeadRequest)
{
+ if (!image.IsLocalFile)
+ {
+ item ??= _libraryManager.GetItemById(itemId);
+ image = await _libraryManager.ConvertImageToLocal(item, image, request.Index ?? 0).ConfigureAwait(false);
+ }
+
var options = new ImageProcessingOptions
{
CropWhiteSpace = cropwhitespace,
- Enhancers = enhancers,
Height = request.Height,
ImageIndex = request.Index ?? 0,
Image = image,
@@ -656,7 +647,7 @@ namespace MediaBrowser.Api.Images
IsHeadRequest = isHeadRequest,
Path = imageResult.Item1,
- FileShare = FileShareMode.Read
+ FileShare = FileShare.Read
}).ConfigureAwait(false);
}
@@ -666,7 +657,7 @@ namespace MediaBrowser.Api.Images
if (!string.IsNullOrWhiteSpace(request.Format)
&& Enum.TryParse(request.Format, true, out ImageFormat format))
{
- return new ImageFormat[] { format };
+ return new[] { format };
}
return GetClientSupportedFormats();
@@ -759,24 +750,22 @@ namespace MediaBrowser.Api.Images
/// <returns>Task.</returns>
public async Task PostImage(BaseItem entity, Stream inputStream, ImageType imageType, string mimeType)
{
- using (var reader = new StreamReader(inputStream))
- {
- var text = await reader.ReadToEndAsync().ConfigureAwait(false);
+ using var reader = new StreamReader(inputStream);
+ var text = await reader.ReadToEndAsync().ConfigureAwait(false);
- var bytes = Convert.FromBase64String(text);
+ var bytes = Convert.FromBase64String(text);
- var memoryStream = new MemoryStream(bytes)
- {
- Position = 0
- };
+ var memoryStream = new MemoryStream(bytes)
+ {
+ Position = 0
+ };
- // Handle image/png; charset=utf-8
- mimeType = mimeType.Split(';').FirstOrDefault();
+ // Handle image/png; charset=utf-8
+ mimeType = mimeType.Split(';').FirstOrDefault();
- await _providerManager.SaveImage(entity, memoryStream, mimeType, imageType, null, CancellationToken.None).ConfigureAwait(false);
+ await _providerManager.SaveImage(entity, memoryStream, mimeType, imageType, null, CancellationToken.None).ConfigureAwait(false);
- entity.UpdateToRepository(ItemUpdateType.ImageUpdate, CancellationToken.None);
- }
+ entity.UpdateToRepository(ItemUpdateType.ImageUpdate, CancellationToken.None);
}
}
}
diff --git a/MediaBrowser.Api/Images/RemoteImageService.cs b/MediaBrowser.Api/Images/RemoteImageService.cs
index 5a37d3730..222bb34d3 100644
--- a/MediaBrowser.Api/Images/RemoteImageService.cs
+++ b/MediaBrowser.Api/Images/RemoteImageService.cs
@@ -261,29 +261,25 @@ namespace MediaBrowser.Api.Images
/// <returns>Task.</returns>
private async Task DownloadImage(string url, Guid urlHash, string pointerCachePath)
{
- using (var result = await _httpClient.GetResponse(new HttpRequestOptions
+ using var result = await _httpClient.GetResponse(new HttpRequestOptions
{
Url = url,
BufferContent = false
- }).ConfigureAwait(false))
- {
- var ext = result.ContentType.Split('/').Last();
-
- var fullCachePath = GetFullCachePath(urlHash + "." + ext);
+ }).ConfigureAwait(false);
+ var ext = result.ContentType.Split('/').Last();
- Directory.CreateDirectory(Path.GetDirectoryName(fullCachePath));
- using (var stream = result.Content)
- {
- using (var filestream = _fileSystem.GetFileStream(fullCachePath, FileOpenMode.Create, FileAccessMode.Write, FileShareMode.Read, true))
- {
- await stream.CopyToAsync(filestream).ConfigureAwait(false);
- }
- }
+ var fullCachePath = GetFullCachePath(urlHash + "." + ext);
- Directory.CreateDirectory(Path.GetDirectoryName(pointerCachePath));
- File.WriteAllText(pointerCachePath, fullCachePath);
+ Directory.CreateDirectory(Path.GetDirectoryName(fullCachePath));
+ using (var stream = result.Content)
+ {
+ using var filestream = new FileStream(fullCachePath, FileMode.Create, FileAccess.Write, FileShare.Read, IODefaults.FileStreamBufferSize, true);
+ await stream.CopyToAsync(filestream).ConfigureAwait(false);
}
+
+ Directory.CreateDirectory(Path.GetDirectoryName(pointerCachePath));
+ File.WriteAllText(pointerCachePath, fullCachePath);
}
/// <summary>
diff --git a/MediaBrowser.Api/ItemLookupService.cs b/MediaBrowser.Api/ItemLookupService.cs
index ea5a99892..0bbe7e1cf 100644
--- a/MediaBrowser.Api/ItemLookupService.cs
+++ b/MediaBrowser.Api/ItemLookupService.cs
@@ -305,9 +305,16 @@ namespace MediaBrowser.Api
Directory.CreateDirectory(Path.GetDirectoryName(fullCachePath));
using (var stream = result.Content)
- using (var filestream = _fileSystem.GetFileStream(fullCachePath, FileOpenMode.Create, FileAccessMode.Write, FileShareMode.Read, true))
{
- await stream.CopyToAsync(filestream).ConfigureAwait(false);
+ using var fileStream = new FileStream(
+ fullCachePath,
+ FileMode.Create,
+ FileAccess.Write,
+ FileShare.Read,
+ IODefaults.FileStreamBufferSize,
+ true);
+
+ await stream.CopyToAsync(fileStream).ConfigureAwait(false);
}
Directory.CreateDirectory(Path.GetDirectoryName(pointerCachePath));
diff --git a/MediaBrowser.Api/ItemUpdateService.cs b/MediaBrowser.Api/ItemUpdateService.cs
index 1847f7fde..2db6d717a 100644
--- a/MediaBrowser.Api/ItemUpdateService.cs
+++ b/MediaBrowser.Api/ItemUpdateService.cs
@@ -2,7 +2,6 @@ using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
-using System.Threading.Tasks;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.Audio;
@@ -264,8 +263,7 @@ namespace MediaBrowser.Api
item.Overview = request.Overview;
item.Genres = request.Genres;
- var episode = item as Episode;
- if (episode != null)
+ if (item is Episode episode)
{
episode.AirsAfterSeasonNumber = request.AirsAfterSeasonNumber;
episode.AirsBeforeEpisodeNumber = request.AirsBeforeEpisodeNumber;
@@ -303,14 +301,12 @@ namespace MediaBrowser.Api
item.PreferredMetadataCountryCode = request.PreferredMetadataCountryCode;
item.PreferredMetadataLanguage = request.PreferredMetadataLanguage;
- var hasDisplayOrder = item as IHasDisplayOrder;
- if (hasDisplayOrder != null)
+ if (item is IHasDisplayOrder hasDisplayOrder)
{
hasDisplayOrder.DisplayOrder = request.DisplayOrder;
}
- var hasAspectRatio = item as IHasAspectRatio;
- if (hasAspectRatio != null)
+ if (item is IHasAspectRatio hasAspectRatio)
{
hasAspectRatio.AspectRatio = request.AspectRatio;
}
@@ -338,16 +334,14 @@ namespace MediaBrowser.Api
item.ProviderIds = request.ProviderIds;
- var video = item as Video;
- if (video != null)
+ if (item is Video video)
{
video.Video3DFormat = request.Video3DFormat;
}
if (request.AlbumArtists != null)
{
- var hasAlbumArtists = item as IHasAlbumArtist;
- if (hasAlbumArtists != null)
+ if (item is IHasAlbumArtist hasAlbumArtists)
{
hasAlbumArtists.AlbumArtists = request
.AlbumArtists
@@ -358,8 +352,7 @@ namespace MediaBrowser.Api
if (request.ArtistItems != null)
{
- var hasArtists = item as IHasArtist;
- if (hasArtists != null)
+ if (item is IHasArtist hasArtists)
{
hasArtists.Artists = request
.ArtistItems
@@ -368,20 +361,17 @@ namespace MediaBrowser.Api
}
}
- var song = item as Audio;
- if (song != null)
+ if (item is Audio song)
{
song.Album = request.Album;
}
- var musicVideo = item as MusicVideo;
- if (musicVideo != null)
+ if (item is MusicVideo musicVideo)
{
musicVideo.Album = request.Album;
}
- var series = item as Series;
- if (series != null)
+ if (item is Series series)
{
series.Status = GetSeriesStatus(request);
@@ -401,7 +391,6 @@ namespace MediaBrowser.Api
}
return (SeriesStatus)Enum.Parse(typeof(SeriesStatus), item.Status, true);
-
}
}
}
diff --git a/MediaBrowser.Api/Library/LibraryService.cs b/MediaBrowser.Api/Library/LibraryService.cs
index b1ea3e262..a54640b2f 100644
--- a/MediaBrowser.Api/Library/LibraryService.cs
+++ b/MediaBrowser.Api/Library/LibraryService.cs
@@ -16,17 +16,13 @@ using MediaBrowser.Controller.Entities.Audio;
using MediaBrowser.Controller.Entities.Movies;
using MediaBrowser.Controller.Entities.TV;
using MediaBrowser.Controller.Library;
-using MediaBrowser.Controller.LiveTv;
using MediaBrowser.Controller.Net;
-using MediaBrowser.Controller.Persistence;
using MediaBrowser.Controller.Providers;
-using MediaBrowser.Controller.TV;
using MediaBrowser.Model.Activity;
using MediaBrowser.Model.Configuration;
using MediaBrowser.Model.Dto;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Globalization;
-using MediaBrowser.Model.IO;
using MediaBrowser.Model.Querying;
using MediaBrowser.Model.Services;
using Microsoft.Extensions.Logging;
@@ -352,28 +348,19 @@ namespace MediaBrowser.Api.Library
private string[] GetRepresentativeItemTypes(string contentType)
{
- switch (contentType)
+ return contentType switch
{
- case CollectionType.BoxSets:
- return new string[] { "BoxSet" };
- case CollectionType.Playlists:
- return new string[] { "Playlist" };
- case CollectionType.Movies:
- return new string[] { "Movie" };
- case CollectionType.TvShows:
- return new string[] { "Series", "Season", "Episode" };
- case CollectionType.Books:
- return new string[] { "Book" };
- case CollectionType.Music:
- return new string[] { "MusicAlbum", "MusicArtist", "Audio", "MusicVideo" };
- case CollectionType.HomeVideos:
- case CollectionType.Photos:
- return new string[] { "Video", "Photo" };
- case CollectionType.MusicVideos:
- return new string[] { "MusicVideo" };
- default:
- return new string[] { "Series", "Season", "Episode", "Movie" };
- }
+ CollectionType.BoxSets => new[] {"BoxSet"},
+ CollectionType.Playlists => new[] {"Playlist"},
+ CollectionType.Movies => new[] {"Movie"},
+ CollectionType.TvShows => new[] {"Series", "Season", "Episode"},
+ CollectionType.Books => new[] {"Book"},
+ CollectionType.Music => new[] {"MusicAlbum", "MusicArtist", "Audio", "MusicVideo"},
+ CollectionType.HomeVideos => new[] {"Video", "Photo"},
+ CollectionType.Photos => new[] {"Video", "Photo"},
+ CollectionType.MusicVideos => new[] {"MusicVideo"},
+ _ => new[] {"Series", "Season", "Episode", "Movie"}
+ };
}
private bool IsSaverEnabledByDefault(string name, string[] itemTypes, bool isNewLibrary)
@@ -401,54 +388,22 @@ namespace MediaBrowser.Api.Library
{
if (string.Equals(name, "TheMovieDb", StringComparison.OrdinalIgnoreCase))
{
- if (string.Equals(type, "Series", StringComparison.OrdinalIgnoreCase))
- {
- return true;
- }
- if (string.Equals(type, "Season", StringComparison.OrdinalIgnoreCase))
- {
- return false;
- }
- if (string.Equals(type, "Episode", StringComparison.OrdinalIgnoreCase))
- {
- return false;
- }
- if (string.Equals(type, "MusicVideo", StringComparison.OrdinalIgnoreCase))
- {
- return false;
- }
- return true;
- }
- else if (string.Equals(name, "TheTVDB", StringComparison.OrdinalIgnoreCase))
- {
- return true;
- }
- else if (string.Equals(name, "The Open Movie Database", StringComparison.OrdinalIgnoreCase))
- {
- return false;
- }
- else if (string.Equals(name, "TheAudioDB", StringComparison.OrdinalIgnoreCase))
- {
- return true;
- }
- else if (string.Equals(name, "MusicBrainz", StringComparison.OrdinalIgnoreCase))
- {
- return true;
+ return !(string.Equals(type, "Season", StringComparison.OrdinalIgnoreCase)
+ || string.Equals(type, "Episode", StringComparison.OrdinalIgnoreCase)
+ || string.Equals(type, "MusicVideo", StringComparison.OrdinalIgnoreCase));
}
- return false;
+ return string.Equals(name, "TheTVDB", StringComparison.OrdinalIgnoreCase)
+ || string.Equals(name, "TheAudioDB", StringComparison.OrdinalIgnoreCase)
+ || string.Equals(name, "MusicBrainz", StringComparison.OrdinalIgnoreCase);
}
var metadataOptions = ServerConfigurationManager.Configuration.MetadataOptions
.Where(i => string.Equals(i.ItemType, type, StringComparison.OrdinalIgnoreCase))
.ToArray();
- if (metadataOptions.Length == 0)
- {
- return true;
- }
-
- return metadataOptions.Any(i => !i.DisabledMetadataFetchers.Contains(name, StringComparer.OrdinalIgnoreCase));
+ return metadataOptions.Length == 0
+ || metadataOptions.Any(i => !i.DisabledMetadataFetchers.Contains(name, StringComparer.OrdinalIgnoreCase));
}
private bool IsImageFetcherEnabledByDefault(string name, string type, bool isNewLibrary)
@@ -457,50 +412,17 @@ namespace MediaBrowser.Api.Library
{
if (string.Equals(name, "TheMovieDb", StringComparison.OrdinalIgnoreCase))
{
- if (string.Equals(type, "Series", StringComparison.OrdinalIgnoreCase))
- {
- return false;
- }
- if (string.Equals(type, "Season", StringComparison.OrdinalIgnoreCase))
- {
- return false;
- }
- if (string.Equals(type, "Episode", StringComparison.OrdinalIgnoreCase))
- {
- return false;
- }
- if (string.Equals(type, "MusicVideo", StringComparison.OrdinalIgnoreCase))
- {
- return false;
- }
- return true;
- }
- else if (string.Equals(name, "TheTVDB", StringComparison.OrdinalIgnoreCase))
- {
- return true;
- }
- else if (string.Equals(name, "The Open Movie Database", StringComparison.OrdinalIgnoreCase))
- {
- return false;
- }
- else if (string.Equals(name, "TheAudioDB", StringComparison.OrdinalIgnoreCase))
- {
- return true;
- }
- else if (string.Equals(name, "Emby Designs", StringComparison.OrdinalIgnoreCase))
- {
- return true;
- }
- else if (string.Equals(name, "Screen Grabber", StringComparison.OrdinalIgnoreCase))
- {
- return true;
- }
- else if (string.Equals(name, "Image Extractor", StringComparison.OrdinalIgnoreCase))
- {
- return true;
+ return !string.Equals(type, "Series", StringComparison.OrdinalIgnoreCase)
+ && !string.Equals(type, "Season", StringComparison.OrdinalIgnoreCase)
+ && !string.Equals(type, "Episode", StringComparison.OrdinalIgnoreCase)
+ && !string.Equals(type, "MusicVideo", StringComparison.OrdinalIgnoreCase);
}
- return false;
+ return string.Equals(name, "TheTVDB", StringComparison.OrdinalIgnoreCase)
+ || string.Equals(name, "Screen Grabber", StringComparison.OrdinalIgnoreCase)
+ || string.Equals(name, "TheAudioDB", StringComparison.OrdinalIgnoreCase)
+ || string.Equals(name, "Emby Designs", StringComparison.OrdinalIgnoreCase)
+ || string.Equals(name, "Image Extractor", StringComparison.OrdinalIgnoreCase);
}
var metadataOptions = ServerConfigurationManager.Configuration.MetadataOptions
@@ -565,8 +487,7 @@ namespace MediaBrowser.Api.Library
foreach (var type in types)
{
- ImageOption[] defaultImageOptions = null;
- TypeOptions.DefaultImageOptions.TryGetValue(type, out defaultImageOptions);
+ TypeOptions.DefaultImageOptions.TryGetValue(type, out var defaultImageOptions);
typeOptions.Add(new LibraryTypeOptions
{
@@ -613,8 +534,6 @@ namespace MediaBrowser.Api.Library
public object Get(GetSimilarItems request)
{
- var user = !request.UserId.Equals(Guid.Empty) ? _userManager.GetUserById(request.UserId) : null;
-
var item = string.IsNullOrEmpty(request.Id) ?
(!request.UserId.Equals(Guid.Empty) ? _libraryManager.GetUserRootFolder() :
_libraryManager.RootFolder) : _libraryManager.GetItemById(request.Id);
@@ -672,7 +591,7 @@ namespace MediaBrowser.Api.Library
// ExcludeArtistIds
if (!string.IsNullOrEmpty(request.ExcludeArtistIds))
{
- query.ExcludeArtistIds = BaseApiService.GetGuids(request.ExcludeArtistIds);
+ query.ExcludeArtistIds = GetGuids(request.ExcludeArtistIds);
}
List<BaseItem> itemsResult;
@@ -693,7 +612,6 @@ namespace MediaBrowser.Api.Library
var result = new QueryResult<BaseItemDto>
{
Items = returnList,
-
TotalRecordCount = itemsResult.Count
};
@@ -819,7 +737,7 @@ namespace MediaBrowser.Api.Library
if (!string.IsNullOrWhiteSpace(filename))
{
// Kestrel doesn't support non-ASCII characters in headers
- if (Regex.IsMatch(filename, "[^[:ascii:]]"))
+ if (Regex.IsMatch(filename, @"[^\p{IsBasicLatin}]"))
{
// Manually encoding non-ASCII characters, following https://tools.ietf.org/html/rfc5987#section-3.2.2
headers[HeaderNames.ContentDisposition] = "attachment; filename*=UTF-8''" + WebUtility.UrlEncode(filename);
@@ -923,12 +841,10 @@ namespace MediaBrowser.Api.Library
private BaseItem TranslateParentItem(BaseItem item, User user)
{
- if (item.GetParent() is AggregateFolder)
- {
- return _libraryManager.GetUserRootFolder().GetChildren(user, true).FirstOrDefault(i => i.PhysicalLocations.Contains(item.Path));
- }
-
- return item;
+ return item.GetParent() is AggregateFolder
+ ? _libraryManager.GetUserRootFolder().GetChildren(user, true)
+ .FirstOrDefault(i => i.PhysicalLocations.Contains(item.Path))
+ : item;
}
/// <summary>
@@ -1090,7 +1006,7 @@ namespace MediaBrowser.Api.Library
var item = string.IsNullOrEmpty(request.Id)
? (!request.UserId.Equals(Guid.Empty)
? _libraryManager.GetUserRootFolder()
- : (Folder)_libraryManager.RootFolder)
+ : _libraryManager.RootFolder)
: _libraryManager.GetItemById(request.Id);
if (item == null)
@@ -1098,18 +1014,13 @@ namespace MediaBrowser.Api.Library
throw new ResourceNotFoundException("Item not found.");
}
- BaseItem[] themeItems = Array.Empty<BaseItem>();
+ IEnumerable<BaseItem> themeItems;
while (true)
{
- themeItems = item.GetThemeSongs().ToArray();
-
- if (themeItems.Length > 0)
- {
- break;
- }
+ themeItems = item.GetThemeSongs();
- if (!request.InheritFromParent)
+ if (themeItems.Any() || !request.InheritFromParent)
{
break;
}
@@ -1123,11 +1034,9 @@ namespace MediaBrowser.Api.Library
}
var dtoOptions = GetDtoOptions(_authContext, request);
-
- var dtos = themeItems
- .Select(i => _dtoService.GetBaseItemDto(i, dtoOptions, user, item));
-
- var items = dtos.ToArray();
+ var items = themeItems
+ .Select(i => _dtoService.GetBaseItemDto(i, dtoOptions, user, item))
+ .ToArray();
return new ThemeMediaResult
{
@@ -1144,9 +1053,7 @@ namespace MediaBrowser.Api.Library
/// <returns>System.Object.</returns>
public object Get(GetThemeVideos request)
{
- var result = GetThemeVideos(request);
-
- return ToOptimizedResult(result);
+ return ToOptimizedResult(GetThemeVideos(request));
}
public ThemeMediaResult GetThemeVideos(GetThemeVideos request)
@@ -1156,7 +1063,7 @@ namespace MediaBrowser.Api.Library
var item = string.IsNullOrEmpty(request.Id)
? (!request.UserId.Equals(Guid.Empty)
? _libraryManager.GetUserRootFolder()
- : (Folder)_libraryManager.RootFolder)
+ : _libraryManager.RootFolder)
: _libraryManager.GetItemById(request.Id);
if (item == null)
@@ -1164,18 +1071,13 @@ namespace MediaBrowser.Api.Library
throw new ResourceNotFoundException("Item not found.");
}
- BaseItem[] themeItems = Array.Empty<BaseItem>();
+ IEnumerable<BaseItem> themeItems;
while (true)
{
- themeItems = item.GetThemeVideos().ToArray();
-
- if (themeItems.Length > 0)
- {
- break;
- }
+ themeItems = item.GetThemeVideos();
- if (!request.InheritFromParent)
+ if (themeItems.Any() || !request.InheritFromParent)
{
break;
}
@@ -1190,10 +1092,9 @@ namespace MediaBrowser.Api.Library
var dtoOptions = GetDtoOptions(_authContext, request);
- var dtos = themeItems
- .Select(i => _dtoService.GetBaseItemDto(i, dtoOptions, user, item));
-
- var items = dtos.ToArray();
+ var items = themeItems
+ .Select(i => _dtoService.GetBaseItemDto(i, dtoOptions, user, item))
+ .ToArray();
return new ThemeMediaResult
{
diff --git a/MediaBrowser.Api/Library/LibraryStructureService.cs b/MediaBrowser.Api/Library/LibraryStructureService.cs
index c071b42f7..1e300814f 100644
--- a/MediaBrowser.Api/Library/LibraryStructureService.cs
+++ b/MediaBrowser.Api/Library/LibraryStructureService.cs
@@ -327,15 +327,11 @@ namespace MediaBrowser.Api.Library
try
{
- var mediaPath = request.PathInfo;
-
- if (mediaPath == null)
+ var mediaPath = request.PathInfo ?? new MediaPathInfo
{
- mediaPath = new MediaPathInfo
- {
- Path = request.Path
- };
- }
+ Path = request.Path
+ };
+
_libraryManager.AddMediaPath(request.Name, mediaPath);
}
finally
diff --git a/MediaBrowser.Api/LiveTv/LiveTvService.cs b/MediaBrowser.Api/LiveTv/LiveTvService.cs
index 4b4496139..5fe4c0cca 100644
--- a/MediaBrowser.Api/LiveTv/LiveTvService.cs
+++ b/MediaBrowser.Api/LiveTv/LiveTvService.cs
@@ -885,11 +885,10 @@ namespace MediaBrowser.Api.LiveTv
{
// SchedulesDirect requires a SHA1 hash of the user's password
// https://github.com/SchedulesDirect/JSON-Service/wiki/API-20141201#obtain-a-token
- using (SHA1 sha = SHA1.Create())
- {
- return Hex.Encode(
- sha.ComputeHash(Encoding.UTF8.GetBytes(str)));
- }
+ using SHA1 sha = SHA1.Create();
+
+ return Hex.Encode(
+ sha.ComputeHash(Encoding.UTF8.GetBytes(str)));
}
public void Delete(DeleteListingProvider request)
@@ -1050,8 +1049,7 @@ namespace MediaBrowser.Api.LiveTv
{
query.IsSeries = true;
- var series = _libraryManager.GetItemById(request.LibrarySeriesId) as Series;
- if (series != null)
+ if (_libraryManager.GetItemById(request.LibrarySeriesId) is Series series)
{
query.Name = series.Name;
}
diff --git a/MediaBrowser.Api/MediaBrowser.Api.csproj b/MediaBrowser.Api/MediaBrowser.Api.csproj
index 0d62cf8c5..d703bdb05 100644
--- a/MediaBrowser.Api/MediaBrowser.Api.csproj
+++ b/MediaBrowser.Api/MediaBrowser.Api.csproj
@@ -1,5 +1,10 @@
<Project Sdk="Microsoft.NET.Sdk">
+ <!-- ProjectGuid is only included as a requirement for SonarQube analysis -->
+ <PropertyGroup>
+ <ProjectGuid>{4FD51AC5-2C16-4308-A993-C3A84F3B4582}</ProjectGuid>
+ </PropertyGroup>
+
<ItemGroup>
<ProjectReference Include="..\MediaBrowser.Model\MediaBrowser.Model.csproj" />
<ProjectReference Include="..\MediaBrowser.Controller\MediaBrowser.Controller.csproj" />
diff --git a/MediaBrowser.Api/Movies/MoviesService.cs b/MediaBrowser.Api/Movies/MoviesService.cs
index 889ebc928..46da8b909 100644
--- a/MediaBrowser.Api/Movies/MoviesService.cs
+++ b/MediaBrowser.Api/Movies/MoviesService.cs
@@ -394,7 +394,7 @@ namespace MediaBrowser.Api.Movies
{
var people = _libraryManager.GetPeople(new InternalPeopleQuery
{
- PersonTypes = new string[]
+ PersonTypes = new[]
{
PersonType.Director
}
diff --git a/MediaBrowser.Api/PackageService.cs b/MediaBrowser.Api/PackageService.cs
index afc3e026a..444354a99 100644
--- a/MediaBrowser.Api/PackageService.cs
+++ b/MediaBrowser.Api/PackageService.cs
@@ -42,23 +42,6 @@ namespace MediaBrowser.Api
[Authenticated]
public class GetPackages : IReturn<PackageInfo[]>
{
- /// <summary>
- /// Gets or sets the name.
- /// </summary>
- /// <value>The name.</value>
- [ApiMember(Name = "PackageType", Description = "Optional package type filter (System/UserInstalled)", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
- public string PackageType { get; set; }
-
- [ApiMember(Name = "TargetSystems", Description = "Optional. Filter by target system type. Allows multiple, comma delimited.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)]
- public string TargetSystems { get; set; }
-
- [ApiMember(Name = "IsPremium", Description = "Optional. Filter by premium status", IsRequired = false, DataType = "boolean", ParameterType = "query", Verb = "GET")]
- public bool? IsPremium { get; set; }
-
- [ApiMember(Name = "IsAdult", Description = "Optional. Filter by package that contain adult content.", IsRequired = false, DataType = "boolean", ParameterType = "query", Verb = "GET")]
- public bool? IsAdult { get; set; }
-
- public bool? IsAppStoreEnabled { get; set; }
}
/// <summary>
@@ -88,13 +71,6 @@ namespace MediaBrowser.Api
/// <value>The version.</value>
[ApiMember(Name = "Version", Description = "Optional version. Defaults to latest version.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "POST")]
public string Version { get; set; }
-
- /// <summary>
- /// Gets or sets the update class.
- /// </summary>
- /// <value>The update class.</value>
- [ApiMember(Name = "UpdateClass", Description = "Optional update class (Dev, Beta, Release). Defaults to Release.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "POST")]
- public PackageVersionClass UpdateClass { get; set; }
}
/// <summary>
@@ -154,23 +130,6 @@ namespace MediaBrowser.Api
{
IEnumerable<PackageInfo> packages = await _installationManager.GetAvailablePackages().ConfigureAwait(false);
- if (!string.IsNullOrEmpty(request.TargetSystems))
- {
- var apps = request.TargetSystems.Split(',').Select(i => (PackageTargetSystem)Enum.Parse(typeof(PackageTargetSystem), i, true));
-
- packages = packages.Where(p => apps.Contains(p.targetSystem));
- }
-
- if (request.IsAdult.HasValue)
- {
- packages = packages.Where(p => p.adult == request.IsAdult.Value);
- }
-
- if (request.IsAppStoreEnabled.HasValue)
- {
- packages = packages.Where(p => p.enableInAppStore == request.IsAppStoreEnabled.Value);
- }
-
return ToOptimizedResult(packages.ToArray());
}
@@ -186,8 +145,7 @@ namespace MediaBrowser.Api
packages,
request.Name,
string.IsNullOrEmpty(request.AssemblyGuid) ? Guid.Empty : Guid.Parse(request.AssemblyGuid),
- string.IsNullOrEmpty(request.Version) ? null : Version.Parse(request.Version),
- request.UpdateClass).FirstOrDefault();
+ string.IsNullOrEmpty(request.Version) ? null : Version.Parse(request.Version)).FirstOrDefault();
if (package == null)
{
diff --git a/MediaBrowser.Api/Playback/BaseStreamingService.cs b/MediaBrowser.Api/Playback/BaseStreamingService.cs
index 1f7dc0d71..928ca1612 100644
--- a/MediaBrowser.Api/Playback/BaseStreamingService.cs
+++ b/MediaBrowser.Api/Playback/BaseStreamingService.cs
@@ -134,15 +134,12 @@ namespace MediaBrowser.Api.Playback
var data = $"{state.MediaPath}-{state.UserAgent}-{state.Request.DeviceId}-{state.Request.PlaySessionId}";
var filename = data.GetMD5().ToString("N", CultureInfo.InvariantCulture);
- var ext = outputFileExtension.ToLowerInvariant();
+ var ext = outputFileExtension?.ToLowerInvariant();
var folder = ServerConfigurationManager.GetTranscodePath();
- if (EnableOutputInSubFolder)
- {
- return Path.Combine(folder, filename, filename + ext);
- }
-
- return Path.Combine(folder, filename + ext);
+ return EnableOutputInSubFolder
+ ? Path.Combine(folder, filename, filename + ext)
+ : Path.Combine(folder, filename + ext);
}
protected virtual string GetDefaultEncoderPreset()
@@ -248,20 +245,14 @@ namespace MediaBrowser.Api.Playback
if (state.VideoRequest != null
&& string.Equals(state.OutputVideoCodec, "copy", StringComparison.OrdinalIgnoreCase))
{
- if (string.Equals(state.OutputAudioCodec, "copy", StringComparison.OrdinalIgnoreCase))
- {
- logFilePrefix = "ffmpeg-directstream";
- }
- else
- {
- logFilePrefix = "ffmpeg-remux";
- }
+ logFilePrefix = string.Equals(state.OutputAudioCodec, "copy", StringComparison.OrdinalIgnoreCase)
+ ? "ffmpeg-remux" : "ffmpeg-directstream";
}
var logFilePath = Path.Combine(ServerConfigurationManager.ApplicationPaths.LogDirectoryPath, logFilePrefix + "-" + Guid.NewGuid() + ".txt");
// FFMpeg writes debug/error info to stderr. This is useful when debugging so let's put it in the log directory.
- Stream logStream = FileSystem.GetFileStream(logFilePath, FileOpenMode.Create, FileAccessMode.Write, FileShareMode.Read, true);
+ Stream logStream = new FileStream(logFilePath, FileMode.Create, FileAccess.Write, FileShare.Read, IODefaults.FileStreamBufferSize, true);
var commandLineLogMessageBytes = Encoding.UTF8.GetBytes(Request.AbsoluteUri + Environment.NewLine + Environment.NewLine + JsonSerializer.SerializeToString(state.MediaSource) + Environment.NewLine + Environment.NewLine + commandLineLogMessage + Environment.NewLine + Environment.NewLine);
await logStream.WriteAsync(commandLineLogMessageBytes, 0, commandLineLogMessageBytes.Length, cancellationTokenSource.Token).ConfigureAwait(false);
@@ -327,15 +318,20 @@ namespace MediaBrowser.Api.Playback
private bool EnableThrottling(StreamState state)
{
+ var encodingOptions = ServerConfigurationManager.GetEncodingOptions();
+
+ // enable throttling when NOT using hardware acceleration
+ if (string.IsNullOrEmpty(encodingOptions.HardwareAccelerationType))
+ {
+ return state.InputProtocol == MediaProtocol.File &&
+ state.RunTimeTicks.HasValue &&
+ state.RunTimeTicks.Value >= TimeSpan.FromMinutes(5).Ticks &&
+ state.IsInputVideo &&
+ state.VideoType == VideoType.VideoFile &&
+ !string.Equals(state.OutputVideoCodec, "copy", StringComparison.OrdinalIgnoreCase);
+ }
+
return false;
- //// do not use throttling with hardware encoders
- //return state.InputProtocol == MediaProtocol.File &&
- // state.RunTimeTicks.HasValue &&
- // state.RunTimeTicks.Value >= TimeSpan.FromMinutes(5).Ticks &&
- // state.IsInputVideo &&
- // state.VideoType == VideoType.VideoFile &&
- // !string.Equals(state.OutputVideoCodec, "copy", StringComparison.OrdinalIgnoreCase) &&
- // string.Equals(GetVideoEncoder(state), "libx264", StringComparison.OrdinalIgnoreCase);
}
/// <summary>
@@ -385,195 +381,181 @@ namespace MediaBrowser.Api.Playback
continue;
}
- if (i == 0)
- {
- request.DeviceProfileId = val;
- }
- else if (i == 1)
- {
- request.DeviceId = val;
- }
- else if (i == 2)
- {
- request.MediaSourceId = val;
- }
- else if (i == 3)
- {
- request.Static = string.Equals("true", val, StringComparison.OrdinalIgnoreCase);
- }
- else if (i == 4)
- {
- if (videoRequest != null)
- {
- videoRequest.VideoCodec = val;
- }
- }
- else if (i == 5)
- {
- request.AudioCodec = val;
- }
- else if (i == 6)
- {
- if (videoRequest != null)
- {
- videoRequest.AudioStreamIndex = int.Parse(val, CultureInfo.InvariantCulture);
- }
- }
- else if (i == 7)
- {
- if (videoRequest != null)
- {
- videoRequest.SubtitleStreamIndex = int.Parse(val, CultureInfo.InvariantCulture);
- }
- }
- else if (i == 8)
- {
- if (videoRequest != null)
- {
- videoRequest.VideoBitRate = int.Parse(val, CultureInfo.InvariantCulture);
- }
- }
- else if (i == 9)
- {
- request.AudioBitRate = int.Parse(val, CultureInfo.InvariantCulture);
- }
- else if (i == 10)
- {
- request.MaxAudioChannels = int.Parse(val, CultureInfo.InvariantCulture);
- }
- else if (i == 11)
- {
- if (videoRequest != null)
- {
- videoRequest.MaxFramerate = float.Parse(val, CultureInfo.InvariantCulture);
- }
- }
- else if (i == 12)
- {
- if (videoRequest != null)
- {
- videoRequest.MaxWidth = int.Parse(val, CultureInfo.InvariantCulture);
- }
- }
- else if (i == 13)
- {
- if (videoRequest != null)
- {
- videoRequest.MaxHeight = int.Parse(val, CultureInfo.InvariantCulture);
- }
- }
- else if (i == 14)
- {
- request.StartTimeTicks = long.Parse(val, CultureInfo.InvariantCulture);
- }
- else if (i == 15)
- {
- if (videoRequest != null)
- {
- videoRequest.Level = val;
- }
- }
- else if (i == 16)
- {
- if (videoRequest != null)
- {
- videoRequest.MaxRefFrames = int.Parse(val, CultureInfo.InvariantCulture);
- }
- }
- else if (i == 17)
- {
- if (videoRequest != null)
- {
- videoRequest.MaxVideoBitDepth = int.Parse(val, CultureInfo.InvariantCulture);
- }
- }
- else if (i == 18)
- {
- if (videoRequest != null)
- {
- videoRequest.Profile = val;
- }
- }
- else if (i == 19)
- {
- // cabac no longer used
- }
- else if (i == 20)
- {
- request.PlaySessionId = val;
- }
- else if (i == 21)
- {
- // api_key
- }
- else if (i == 22)
- {
- request.LiveStreamId = val;
- }
- else if (i == 23)
- {
- // Duplicating ItemId because of MediaMonkey
- }
- else if (i == 24)
- {
- if (videoRequest != null)
- {
- videoRequest.CopyTimestamps = string.Equals("true", val, StringComparison.OrdinalIgnoreCase);
- }
- }
- else if (i == 25)
- {
- if (!string.IsNullOrWhiteSpace(val) && videoRequest != null)
- {
- if (Enum.TryParse(val, out SubtitleDeliveryMethod method))
+ switch (i)
+ {
+ case 0:
+ request.DeviceProfileId = val;
+ break;
+ case 1:
+ request.DeviceId = val;
+ break;
+ case 2:
+ request.MediaSourceId = val;
+ break;
+ case 3:
+ request.Static = string.Equals("true", val, StringComparison.OrdinalIgnoreCase);
+ break;
+ case 4:
+ if (videoRequest != null)
{
- videoRequest.SubtitleMethod = method;
+ videoRequest.VideoCodec = val;
}
- }
- }
- else if (i == 26)
- {
- request.TranscodingMaxAudioChannels = int.Parse(val, CultureInfo.InvariantCulture);
- }
- else if (i == 27)
- {
- if (videoRequest != null)
- {
- videoRequest.EnableSubtitlesInManifest = string.Equals("true", val, StringComparison.OrdinalIgnoreCase);
- }
- }
- else if (i == 28)
- {
- request.Tag = val;
- }
- else if (i == 29)
- {
- if (videoRequest != null)
- {
- videoRequest.RequireAvc = string.Equals("true", val, StringComparison.OrdinalIgnoreCase);
- }
- }
- else if (i == 30)
- {
- request.SubtitleCodec = val;
- }
- else if (i == 31)
- {
- if (videoRequest != null)
- {
- videoRequest.RequireNonAnamorphic = string.Equals("true", val, StringComparison.OrdinalIgnoreCase);
- }
- }
- else if (i == 32)
- {
- if (videoRequest != null)
- {
- videoRequest.DeInterlace = string.Equals("true", val, StringComparison.OrdinalIgnoreCase);
- }
- }
- else if (i == 33)
- {
- request.TranscodeReasons = val;
+
+ break;
+ case 5:
+ request.AudioCodec = val;
+ break;
+ case 6:
+ if (videoRequest != null)
+ {
+ videoRequest.AudioStreamIndex = int.Parse(val, CultureInfo.InvariantCulture);
+ }
+
+ break;
+ case 7:
+ if (videoRequest != null)
+ {
+ videoRequest.SubtitleStreamIndex = int.Parse(val, CultureInfo.InvariantCulture);
+ }
+
+ break;
+ case 8:
+ if (videoRequest != null)
+ {
+ videoRequest.VideoBitRate = int.Parse(val, CultureInfo.InvariantCulture);
+ }
+
+ break;
+ case 9:
+ request.AudioBitRate = int.Parse(val, CultureInfo.InvariantCulture);
+ break;
+ case 10:
+ request.MaxAudioChannels = int.Parse(val, CultureInfo.InvariantCulture);
+ break;
+ case 11:
+ if (videoRequest != null)
+ {
+ videoRequest.MaxFramerate = float.Parse(val, CultureInfo.InvariantCulture);
+ }
+
+ break;
+ case 12:
+ if (videoRequest != null)
+ {
+ videoRequest.MaxWidth = int.Parse(val, CultureInfo.InvariantCulture);
+ }
+
+ break;
+ case 13:
+ if (videoRequest != null)
+ {
+ videoRequest.MaxHeight = int.Parse(val, CultureInfo.InvariantCulture);
+ }
+
+ break;
+ case 14:
+ request.StartTimeTicks = long.Parse(val, CultureInfo.InvariantCulture);
+ break;
+ case 15:
+ if (videoRequest != null)
+ {
+ videoRequest.Level = val;
+ }
+
+ break;
+ case 16:
+ if (videoRequest != null)
+ {
+ videoRequest.MaxRefFrames = int.Parse(val, CultureInfo.InvariantCulture);
+ }
+
+ break;
+ case 17:
+ if (videoRequest != null)
+ {
+ videoRequest.MaxVideoBitDepth = int.Parse(val, CultureInfo.InvariantCulture);
+ }
+
+ break;
+ case 18:
+ if (videoRequest != null)
+ {
+ videoRequest.Profile = val;
+ }
+
+ break;
+ case 19:
+ // cabac no longer used
+ break;
+ case 20:
+ request.PlaySessionId = val;
+ break;
+ case 21:
+ // api_key
+ break;
+ case 22:
+ request.LiveStreamId = val;
+ break;
+ case 23:
+ // Duplicating ItemId because of MediaMonkey
+ break;
+ case 24:
+ if (videoRequest != null)
+ {
+ videoRequest.CopyTimestamps = string.Equals("true", val, StringComparison.OrdinalIgnoreCase);
+ }
+
+ break;
+ case 25:
+ if (!string.IsNullOrWhiteSpace(val) && videoRequest != null)
+ {
+ if (Enum.TryParse(val, out SubtitleDeliveryMethod method))
+ {
+ videoRequest.SubtitleMethod = method;
+ }
+ }
+
+ break;
+ case 26:
+ request.TranscodingMaxAudioChannels = int.Parse(val, CultureInfo.InvariantCulture);
+ break;
+ case 27:
+ if (videoRequest != null)
+ {
+ videoRequest.EnableSubtitlesInManifest = string.Equals("true", val, StringComparison.OrdinalIgnoreCase);
+ }
+
+ break;
+ case 28:
+ request.Tag = val;
+ break;
+ case 29:
+ if (videoRequest != null)
+ {
+ videoRequest.RequireAvc = string.Equals("true", val, StringComparison.OrdinalIgnoreCase);
+ }
+
+ break;
+ case 30:
+ request.SubtitleCodec = val;
+ break;
+ case 31:
+ if (videoRequest != null)
+ {
+ videoRequest.RequireNonAnamorphic = string.Equals("true", val, StringComparison.OrdinalIgnoreCase);
+ }
+
+ break;
+ case 32:
+ if (videoRequest != null)
+ {
+ videoRequest.DeInterlace = string.Equals("true", val, StringComparison.OrdinalIgnoreCase);
+ }
+
+ break;
+ case 33:
+ request.TranscodeReasons = val;
+ break;
}
}
}
@@ -626,14 +608,9 @@ namespace MediaBrowser.Api.Playback
throw new ArgumentException("Invalid timeseek header");
}
int index = value.IndexOf('-');
- if (index == -1)
- {
- value = value.Substring(Npt.Length);
- }
- else
- {
- value = value.Substring(Npt.Length, index - Npt.Length);
- }
+ value = index == -1
+ ? value.Substring(Npt.Length)
+ : value.Substring(Npt.Length, index - Npt.Length);
if (value.IndexOf(':') == -1)
{
@@ -852,21 +829,11 @@ namespace MediaBrowser.Api.Playback
{
state.DeviceProfile = DlnaManager.GetProfile(state.Request.DeviceProfileId);
}
- else
+ else if (!string.IsNullOrWhiteSpace(state.Request.DeviceId))
{
- if (!string.IsNullOrWhiteSpace(state.Request.DeviceId))
- {
- var caps = DeviceManager.GetCapabilities(state.Request.DeviceId);
+ var caps = DeviceManager.GetCapabilities(state.Request.DeviceId);
- if (caps != null)
- {
- state.DeviceProfile = caps.DeviceProfile;
- }
- else
- {
- state.DeviceProfile = DlnaManager.GetProfile(headers);
- }
- }
+ state.DeviceProfile = caps == null ? DlnaManager.GetProfile(headers) : caps.DeviceProfile;
}
var profile = state.DeviceProfile;
diff --git a/MediaBrowser.Api/Playback/Hls/BaseHlsService.cs b/MediaBrowser.Api/Playback/Hls/BaseHlsService.cs
index 5d0dc98dd..52962366c 100644
--- a/MediaBrowser.Api/Playback/Hls/BaseHlsService.cs
+++ b/MediaBrowser.Api/Playback/Hls/BaseHlsService.cs
@@ -140,7 +140,7 @@ namespace MediaBrowser.Api.Playback.Hls
if (isLive)
{
- job = job ?? ApiEntryPoint.Instance.OnTranscodeBeginRequest(playlist, TranscodingJobType);
+ job ??= ApiEntryPoint.Instance.OnTranscodeBeginRequest(playlist, TranscodingJobType);
if (job != null)
{
@@ -156,7 +156,7 @@ namespace MediaBrowser.Api.Playback.Hls
var playlistText = GetMasterPlaylistFileText(playlist, videoBitrate + audioBitrate, baselineStreamBitrate);
- job = job ?? ApiEntryPoint.Instance.OnTranscodeBeginRequest(playlist, TranscodingJobType);
+ job ??= ApiEntryPoint.Instance.OnTranscodeBeginRequest(playlist, TranscodingJobType);
if (job != null)
{
@@ -168,22 +168,19 @@ namespace MediaBrowser.Api.Playback.Hls
private string GetLivePlaylistText(string path, int segmentLength)
{
- using (var stream = FileSystem.GetFileStream(path, FileOpenMode.Open, FileAccessMode.Read, FileShareMode.ReadWrite))
- {
- using (var reader = new StreamReader(stream))
- {
- var text = reader.ReadToEnd();
+ using var stream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
+ using var reader = new StreamReader(stream);
- text = text.Replace("#EXTM3U", "#EXTM3U\n#EXT-X-PLAYLIST-TYPE:EVENT");
+ var text = reader.ReadToEnd();
- var newDuration = "#EXT-X-TARGETDURATION:" + segmentLength.ToString(CultureInfo.InvariantCulture);
+ text = text.Replace("#EXTM3U", "#EXTM3U\n#EXT-X-PLAYLIST-TYPE:EVENT");
- text = text.Replace("#EXT-X-TARGETDURATION:" + (segmentLength - 1).ToString(CultureInfo.InvariantCulture), newDuration, StringComparison.OrdinalIgnoreCase);
- //text = text.Replace("#EXT-X-TARGETDURATION:" + (segmentLength + 1).ToString(CultureInfo.InvariantCulture), newDuration, StringComparison.OrdinalIgnoreCase);
+ var newDuration = "#EXT-X-TARGETDURATION:" + segmentLength.ToString(CultureInfo.InvariantCulture);
- return text;
- }
- }
+ text = text.Replace("#EXT-X-TARGETDURATION:" + (segmentLength - 1).ToString(CultureInfo.InvariantCulture), newDuration, StringComparison.OrdinalIgnoreCase);
+ //text = text.Replace("#EXT-X-TARGETDURATION:" + (segmentLength + 1).ToString(CultureInfo.InvariantCulture), newDuration, StringComparison.OrdinalIgnoreCase);
+
+ return text;
}
private string GetMasterPlaylistFileText(string firstPlaylist, int bitrate, int baselineStreamBitrate)
@@ -211,30 +208,26 @@ namespace MediaBrowser.Api.Playback.Hls
{
try
{
- // Need to use FileShareMode.ReadWrite because we're reading the file at the same time it's being written
- using (var fileStream = GetPlaylistFileStream(playlist))
+ // 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);
+ var count = 0;
+
+ while (!reader.EndOfStream)
{
- using (var reader = new StreamReader(fileStream))
- {
- var count = 0;
+ var line = reader.ReadLine();
- while (!reader.EndOfStream)
+ if (line.IndexOf("#EXTINF:", StringComparison.OrdinalIgnoreCase) != -1)
+ {
+ count++;
+ if (count >= segmentCount)
{
- var line = reader.ReadLine();
-
- if (line.IndexOf("#EXTINF:", StringComparison.OrdinalIgnoreCase) != -1)
- {
- count++;
- if (count >= segmentCount)
- {
- Logger.LogDebug("Finished waiting for {0} segments in {1}", segmentCount, playlist);
- return;
- }
- }
+ Logger.LogDebug("Finished waiting for {0} segments in {1}", segmentCount, playlist);
+ return;
}
- await Task.Delay(100, cancellationToken).ConfigureAwait(false);
}
}
+ await Task.Delay(100, cancellationToken).ConfigureAwait(false);
}
catch (IOException)
{
@@ -247,17 +240,13 @@ namespace MediaBrowser.Api.Playback.Hls
protected Stream GetPlaylistFileStream(string path)
{
- var tmpPath = path + ".tmp";
- tmpPath = path;
-
- try
- {
- return FileSystem.GetFileStream(tmpPath, FileOpenMode.Open, FileAccessMode.Read, FileShareMode.ReadWrite, FileOpenOptions.SequentialScan);
- }
- catch (IOException)
- {
- return FileSystem.GetFileStream(path, FileOpenMode.Open, FileAccessMode.Read, FileShareMode.ReadWrite, FileOpenOptions.SequentialScan);
- }
+ return new FileStream(
+ path,
+ FileMode.Open,
+ FileAccess.Read,
+ FileShare.ReadWrite,
+ IODefaults.FileStreamBufferSize,
+ FileOptions.SequentialScan);
}
protected override string GetCommandLineArguments(string outputPath, EncodingOptions encodingOptions, StreamState state, bool isEncoding)
diff --git a/MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs b/MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs
index 0178f53af..7f74e85e9 100644
--- a/MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs
+++ b/MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs
@@ -16,7 +16,6 @@ using MediaBrowser.Controller.Net;
using MediaBrowser.Model.Configuration;
using MediaBrowser.Model.Dlna;
using MediaBrowser.Model.Entities;
-using MediaBrowser.Model.Extensions;
using MediaBrowser.Model.IO;
using MediaBrowser.Model.Serialization;
using MediaBrowser.Model.Services;
@@ -285,7 +284,7 @@ namespace MediaBrowser.Api.Playback.Hls
//}
Logger.LogDebug("returning {0} [general case]", segmentPath);
- job = job ?? ApiEntryPoint.Instance.OnTranscodeBeginRequest(playlistPath, TranscodingJobType);
+ job ??= ApiEntryPoint.Instance.OnTranscodeBeginRequest(playlistPath, TranscodingJobType);
return await GetSegmentResult(state, playlistPath, segmentPath, segmentExtension, requestedIndex, job, cancellationToken).ConfigureAwait(false);
}
@@ -439,8 +438,7 @@ namespace MediaBrowser.Api.Playback.Hls
{
var segmentId = "0";
- var segmentRequest = request as GetHlsVideoSegment;
- if (segmentRequest != null)
+ if (request is GetHlsVideoSegment segmentRequest)
{
segmentId = segmentRequest.SegmentId;
}
@@ -537,7 +535,7 @@ namespace MediaBrowser.Api.Playback.Hls
return ResultFactory.GetStaticFileResult(Request, new StaticFileResultOptions
{
Path = segmentPath,
- FileShare = FileShareMode.ReadWrite,
+ FileShare = FileShare.ReadWrite,
OnComplete = () =>
{
Logger.LogDebug("finished serving {0}", segmentPath);
@@ -691,8 +689,7 @@ namespace MediaBrowser.Api.Playback.Hls
return false;
}
- var request = state.Request as IMasterHlsRequest;
- if (request != null && !request.EnableAdaptiveBitrateStreaming)
+ if (state.Request is IMasterHlsRequest request && !request.EnableAdaptiveBitrateStreaming)
{
return false;
}
@@ -723,22 +720,203 @@ namespace MediaBrowser.Api.Playback.Hls
//return state.VideoRequest.VideoBitRate.HasValue;
}
+ /// <summary>
+ /// Get the H.26X level of the output video stream.
+ /// </summary>
+ /// <param name="state">StreamState of the current stream.</param>
+ /// <returns>H.26X level of the output video stream.</returns>
+ private int? GetOutputVideoCodecLevel(StreamState state)
+ {
+ string levelString;
+ if (string.Equals(state.OutputVideoCodec, "copy", StringComparison.OrdinalIgnoreCase)
+ && state.VideoStream.Level.HasValue)
+ {
+ levelString = state.VideoStream?.Level.ToString();
+ }
+ else
+ {
+ levelString = state.GetRequestedLevel(state.ActualOutputVideoCodec);
+ }
+
+ if (int.TryParse(levelString, NumberStyles.Integer, CultureInfo.InvariantCulture, out var parsedLevel))
+ {
+ return parsedLevel;
+ }
+
+ return null;
+ }
+
+ /// <summary>
+ /// Gets a formatted string of the output audio codec, for use in the CODECS field.
+ /// </summary>
+ /// <seealso cref="AppendPlaylistCodecsField(StringBuilder, StreamState)"/>
+ /// <seealso cref="GetPlaylistVideoCodecs(StreamState, string, int)"/>
+ /// <param name="state">StreamState of the current stream.</param>
+ /// <returns>Formatted audio codec string.</returns>
+ private string GetPlaylistAudioCodecs(StreamState state)
+ {
+
+ if (string.Equals(state.ActualOutputAudioCodec, "aac", StringComparison.OrdinalIgnoreCase))
+ {
+ string profile = state.GetRequestedProfiles("aac").FirstOrDefault();
+
+ return HlsCodecStringFactory.GetAACString(profile);
+ }
+ else if (string.Equals(state.ActualOutputAudioCodec, "mp3", StringComparison.OrdinalIgnoreCase))
+ {
+ return HlsCodecStringFactory.GetMP3String();
+ }
+ else if (string.Equals(state.ActualOutputAudioCodec, "ac3", StringComparison.OrdinalIgnoreCase))
+ {
+ return HlsCodecStringFactory.GetAC3String();
+ }
+ else if (string.Equals(state.ActualOutputAudioCodec, "eac3", StringComparison.OrdinalIgnoreCase))
+ {
+ return HlsCodecStringFactory.GetEAC3String();
+ }
+
+ return string.Empty;
+ }
+
+ /// <summary>
+ /// Gets a formatted string of the output video codec, for use in the CODECS field.
+ /// </summary>
+ /// <seealso cref="AppendPlaylistCodecsField(StringBuilder, StreamState)"/>
+ /// <seealso cref="GetPlaylistAudioCodecs(StreamState)"/>
+ /// <param name="state">StreamState of the current stream.</param>
+ /// <returns>Formatted video codec string.</returns>
+ private string GetPlaylistVideoCodecs(StreamState state, string codec, int level)
+ {
+ if (level == 0)
+ {
+ // This is 0 when there's no requested H.26X level in the device profile
+ // and the source is not encoded in H.26X
+ Logger.LogError("Got invalid H.26X level when building CODECS field for HLS master playlist");
+ return string.Empty;
+ }
+
+ if (string.Equals(codec, "h264", StringComparison.OrdinalIgnoreCase))
+ {
+ string profile = state.GetRequestedProfiles("h264").FirstOrDefault();
+
+ return HlsCodecStringFactory.GetH264String(profile, level);
+ }
+ else if (string.Equals(codec, "h265", StringComparison.OrdinalIgnoreCase)
+ || string.Equals(codec, "hevc", StringComparison.OrdinalIgnoreCase))
+ {
+ string profile = state.GetRequestedProfiles("h265").FirstOrDefault();
+
+ return HlsCodecStringFactory.GetH265String(profile, level);
+ }
+
+ return string.Empty;
+ }
+
+ /// <summary>
+ /// Appends a CODECS field containing formatted strings of
+ /// the active streams output video and audio codecs.
+ /// </summary>
+ /// <seealso cref="AppendPlaylist(StringBuilder, StreamState, string, int, string)"/>
+ /// <seealso cref="GetPlaylistVideoCodecs(StreamState, string, int)"/>
+ /// <seealso cref="GetPlaylistAudioCodecs(StreamState)"/>
+ /// <param name="builder">StringBuilder to append the field to.</param>
+ /// <param name="state">StreamState of the current stream.</param>
+ private void AppendPlaylistCodecsField(StringBuilder builder, StreamState state)
+ {
+ // Video
+ string videoCodecs = string.Empty;
+ int? videoCodecLevel = GetOutputVideoCodecLevel(state);
+ if (!string.IsNullOrEmpty(state.ActualOutputVideoCodec) && videoCodecLevel.HasValue)
+ {
+ videoCodecs = GetPlaylistVideoCodecs(state, state.ActualOutputVideoCodec, videoCodecLevel.Value);
+ }
+
+ // Audio
+ string audioCodecs = string.Empty;
+ if (!string.IsNullOrEmpty(state.ActualOutputAudioCodec))
+ {
+ audioCodecs = GetPlaylistAudioCodecs(state);
+ }
+
+ StringBuilder codecs = new StringBuilder();
+
+ codecs.Append(videoCodecs)
+ .Append(',')
+ .Append(audioCodecs);
+
+ if (codecs.Length > 1)
+ {
+ builder.Append(",CODECS=\"")
+ .Append(codecs)
+ .Append('"');
+ }
+ }
+
+ /// <summary>
+ /// Appends a FRAME-RATE field containing the framerate of the output stream.
+ /// </summary>
+ /// <seealso cref="AppendPlaylist(StringBuilder, StreamState, string, int, string)"/>
+ /// <param name="builder">StringBuilder to append the field to.</param>
+ /// <param name="state">StreamState of the current stream.</param>
+ private void AppendPlaylistFramerateField(StringBuilder builder, StreamState state)
+ {
+ double? framerate = null;
+ if (state.TargetFramerate.HasValue)
+ {
+ framerate = Math.Round(state.TargetFramerate.GetValueOrDefault(), 3);
+ }
+ else if (state.VideoStream.RealFrameRate.HasValue)
+ {
+ framerate = Math.Round(state.VideoStream.RealFrameRate.GetValueOrDefault(), 3);
+ }
+
+ if (framerate.HasValue)
+ {
+ builder.Append(",FRAME-RATE=\"")
+ .Append(framerate.Value)
+ .Append('"');
+ }
+ }
+
+ /// <summary>
+ /// Appends a RESOLUTION field containing the resolution of the output stream.
+ /// </summary>
+ /// <seealso cref="AppendPlaylist(StringBuilder, StreamState, string, int, string)"/>
+ /// <param name="builder">StringBuilder to append the field to.</param>
+ /// <param name="state">StreamState of the current stream.</param>
+ private void AppendPlaylistResolutionField(StringBuilder builder, StreamState state)
+ {
+ if (state.OutputWidth.HasValue && state.OutputHeight.HasValue)
+ {
+ builder.Append(",RESOLUTION=\"")
+ .Append(state.OutputWidth.GetValueOrDefault())
+ .Append('x')
+ .Append(state.OutputHeight.GetValueOrDefault())
+ .Append('"');
+ }
+ }
+
private void AppendPlaylist(StringBuilder builder, StreamState state, string url, int bitrate, string subtitleGroup)
{
- var header = "#EXT-X-STREAM-INF:BANDWIDTH=" + bitrate.ToString(CultureInfo.InvariantCulture) + ",AVERAGE-BANDWIDTH=" + bitrate.ToString(CultureInfo.InvariantCulture);
+ builder.Append("#EXT-X-STREAM-INF:BANDWIDTH=")
+ .Append(bitrate.ToString(CultureInfo.InvariantCulture))
+ .Append(",AVERAGE-BANDWIDTH=")
+ .Append(bitrate.ToString(CultureInfo.InvariantCulture));
- // tvos wants resolution, codecs, framerate
- //if (state.TargetFramerate.HasValue)
- //{
- // header += string.Format(",FRAME-RATE=\"{0}\"", state.TargetFramerate.Value.ToString(CultureInfo.InvariantCulture));
- //}
+ AppendPlaylistCodecsField(builder, state);
+
+ AppendPlaylistResolutionField(builder, state);
+
+ AppendPlaylistFramerateField(builder, state);
if (!string.IsNullOrWhiteSpace(subtitleGroup))
{
- header += string.Format(",SUBTITLES=\"{0}\"", subtitleGroup);
+ builder.Append(",SUBTITLES=\"")
+ .Append(subtitleGroup)
+ .Append('"');
}
- builder.AppendLine(header);
+ builder.Append(Environment.NewLine);
builder.AppendLine(url);
}
@@ -928,61 +1106,69 @@ namespace MediaBrowser.Api.Playback.Hls
}
else
{
+ var gopArg = string.Empty;
var keyFrameArg = string.Format(
CultureInfo.InvariantCulture,
" -force_key_frames:0 \"expr:gte(t,{0}+n_forced*{1})\"",
GetStartNumber(state) * state.SegmentLength,
state.SegmentLength);
- if (state.TargetFramerate.HasValue)
+
+ var framerate = state.VideoStream?.RealFrameRate;
+
+ if (framerate.HasValue)
{
// This is to make sure keyframe interval is limited to our segment,
// as forcing keyframes is not enough.
// Example: we encoded half of desired length, then codec detected
// scene cut and inserted a keyframe; next forced keyframe would
- // be created outside of segment, which breaks seeking.
- keyFrameArg += string.Format(
+ // be created outside of segment, which breaks seeking
+ // -sc_threshold 0 is used to prevent the hardware encoder from post processing to break the set keyframe
+ gopArg = string.Format(
CultureInfo.InvariantCulture,
- " -g {0} -keyint_min {0}",
- (int)(state.SegmentLength * state.TargetFramerate)
+ " -g {0} -keyint_min {0} -sc_threshold 0",
+ Math.Ceiling(state.SegmentLength * framerate.Value)
);
}
- var hasGraphicalSubs = state.SubtitleStream != null && !state.SubtitleStream.IsTextSubtitleStream && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode;
-
args += " " + EncodingHelper.GetVideoQualityParam(state, codec, encodingOptions, GetDefaultEncoderPreset());
- // Unable to force key frames to h264_qsv transcode
- if (string.Equals(codec, "h264_qsv", StringComparison.OrdinalIgnoreCase))
+ // Unable to force key frames using these hw encoders, set key frames by GOP
+ if (string.Equals(codec, "h264_qsv", StringComparison.OrdinalIgnoreCase)
+ || string.Equals(codec, "h264_nvenc", StringComparison.OrdinalIgnoreCase)
+ || string.Equals(codec, "h264_amf", StringComparison.OrdinalIgnoreCase))
{
- Logger.LogInformation("Bug Workaround: Disabling force_key_frames for h264_qsv");
- }
+ args += " " + gopArg;
+ }
else
{
- args += " " + keyFrameArg;
- }
+ args += " " + keyFrameArg + gopArg;
+ }
//args += " -mixed-refs 0 -refs 3 -x264opts b_pyramid=0:weightb=0:weightp=0";
+ var hasGraphicalSubs = state.SubtitleStream != null && !state.SubtitleStream.IsTextSubtitleStream && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode;
+
+ // This is for graphical subs
+ if (hasGraphicalSubs)
+ {
+ args += EncodingHelper.GetGraphicalSubtitleParam(state, encodingOptions, codec);
+ }
// Add resolution params, if specified
- if (!hasGraphicalSubs)
+ else
{
- args += EncodingHelper.GetOutputSizeParam(state, encodingOptions, codec, true);
+ args += EncodingHelper.GetOutputSizeParam(state, encodingOptions, codec);
}
- // This is for internal graphical subs
- if (hasGraphicalSubs)
+ // -start_at_zero is necessary to use with -ss when seeking,
+ // otherwise the target position cannot be determined.
+ if (!(state.SubtitleStream != null && state.SubtitleStream.IsExternal && !state.SubtitleStream.IsTextSubtitleStream))
{
- args += EncodingHelper.GetGraphicalSubtitleParam(state, encodingOptions, codec);
+ args += " -start_at_zero";
}
//args += " -flags -global_header";
}
- if (args.IndexOf("-copyts", StringComparison.OrdinalIgnoreCase) == -1)
- {
- args += " -copyts";
- }
-
if (!string.IsNullOrEmpty(state.OutputVideoSync))
{
args += " -vsync " + state.OutputVideoSync;
@@ -1008,6 +1194,7 @@ namespace MediaBrowser.Api.Playback.Hls
Logger.LogInformation("Current HLS implementation doesn't support non-keyframe breaks but one is requested, ignoring that request");
state.BaseRequest.BreakOnNonKeyFrames = false;
}
+
var inputModifier = EncodingHelper.GetInputModifier(state, encodingOptions);
// If isEncoding is true we're actually starting ffmpeg
@@ -1025,7 +1212,7 @@ namespace MediaBrowser.Api.Playback.Hls
}
return string.Format(
- "{0} {1} -map_metadata -1 -map_chapters -1 -threads {2} {3} {4} {5} -f hls -max_delay 5000000 -avoid_negative_ts disabled -start_at_zero -hls_time {6} -individual_header_trailer 0 -hls_segment_type {7} -start_number {8} -hls_segment_filename \"{9}\" -hls_playlist_type vod -hls_list_size 0 -y \"{10}\"",
+ "{0} {1} -map_metadata -1 -map_chapters -1 -threads {2} {3} {4} {5} -copyts -avoid_negative_ts disabled -f hls -max_delay 5000000 -hls_time {6} -individual_header_trailer 0 -hls_segment_type {7} -start_number {8} -hls_segment_filename \"{9}\" -hls_playlist_type vod -hls_list_size 0 -y \"{10}\"",
inputModifier,
EncodingHelper.GetInputArgument(state, encodingOptions),
threads,
diff --git a/MediaBrowser.Api/Playback/Hls/HlsCodecStringFactory.cs b/MediaBrowser.Api/Playback/Hls/HlsCodecStringFactory.cs
new file mode 100644
index 000000000..3bbb77a65
--- /dev/null
+++ b/MediaBrowser.Api/Playback/Hls/HlsCodecStringFactory.cs
@@ -0,0 +1,126 @@
+using System;
+using System.Text;
+
+
+namespace MediaBrowser.Api.Playback
+{
+ /// <summary>
+ /// Get various codec strings for use in HLS playlists.
+ /// </summary>
+ static class HlsCodecStringFactory
+ {
+
+ /// <summary>
+ /// Gets a MP3 codec string.
+ /// </summary>
+ /// <returns>MP3 codec string.</returns>
+ public static string GetMP3String()
+ {
+ return "mp4a.40.34";
+ }
+
+ /// <summary>
+ /// Gets an AAC codec string.
+ /// </summary>
+ /// <param name="profile">AAC profile.</param>
+ /// <returns>AAC codec string.</returns>
+ public static string GetAACString(string profile)
+ {
+ StringBuilder result = new StringBuilder("mp4a", 9);
+
+ if (string.Equals(profile, "HE", StringComparison.OrdinalIgnoreCase))
+ {
+ result.Append(".40.5");
+ }
+ else
+ {
+ // Default to LC if profile is invalid
+ result.Append(".40.2");
+ }
+
+ return result.ToString();
+ }
+
+ /// <summary>
+ /// Gets a H.264 codec string.
+ /// </summary>
+ /// <param name="profile">H.264 profile.</param>
+ /// <param name="level">H.264 level.</param>
+ /// <returns>H.264 string.</returns>
+ public static string GetH264String(string profile, int level)
+ {
+ StringBuilder result = new StringBuilder("avc1", 11);
+
+ if (string.Equals(profile, "high", StringComparison.OrdinalIgnoreCase))
+ {
+ result.Append(".6400");
+ }
+ else if (string.Equals(profile, "main", StringComparison.OrdinalIgnoreCase))
+ {
+ result.Append(".4D40");
+ }
+ else if (string.Equals(profile, "baseline", StringComparison.OrdinalIgnoreCase))
+ {
+ result.Append(".42E0");
+ }
+ else
+ {
+ // Default to constrained baseline if profile is invalid
+ result.Append(".4240");
+ }
+
+ string levelHex = level.ToString("X2");
+ result.Append(levelHex);
+
+ return result.ToString();
+ }
+
+ /// <summary>
+ /// Gets a H.265 codec string.
+ /// </summary>
+ /// <param name="profile">H.265 profile.</param>
+ /// <param name="level">H.265 level.</param>
+ /// <returns>H.265 string.</returns>
+ public static string GetH265String(string profile, int level)
+ {
+ // The h265 syntax is a bit of a mystery at the time this comment was written.
+ // This is what I've found through various sources:
+ // FORMAT: [codecTag].[profile].[constraint?].L[level * 30].[UNKNOWN]
+ StringBuilder result = new StringBuilder("hev1", 16);
+
+ if (string.Equals(profile, "main10", StringComparison.OrdinalIgnoreCase))
+ {
+ result.Append(".2.6");
+ }
+ else
+ {
+ // Default to main if profile is invalid
+ result.Append(".1.6");
+ }
+
+ result.Append(".L")
+ .Append(level * 3)
+ .Append(".B0");
+
+ return result.ToString();
+ }
+
+ /// <summary>
+ /// Gets an AC-3 codec string.
+ /// </summary>
+ /// <returns>AC-3 codec string.</returns>
+ public static string GetAC3String()
+ {
+ return "mp4a.a5";
+ }
+
+ /// <summary>
+ /// Gets an E-AC-3 codec string.
+ /// </summary>
+ /// <returns>E-AC-3 codec string.</returns>
+ public static string GetEAC3String()
+ {
+ return "mp4a.a6";
+ }
+ }
+}
diff --git a/MediaBrowser.Api/Playback/Hls/HlsSegmentService.cs b/MediaBrowser.Api/Playback/Hls/HlsSegmentService.cs
index bb12ab1f0..87ccde2e0 100644
--- a/MediaBrowser.Api/Playback/Hls/HlsSegmentService.cs
+++ b/MediaBrowser.Api/Playback/Hls/HlsSegmentService.cs
@@ -140,7 +140,7 @@ namespace MediaBrowser.Api.Playback.Hls
var file = request.SegmentId + Path.GetExtension(Request.PathInfo);
file = Path.Combine(ServerConfigurationManager.GetTranscodePath(), file);
- return ResultFactory.GetStaticFileResult(Request, file, FileShareMode.ReadWrite);
+ return ResultFactory.GetStaticFileResult(Request, file, FileShare.ReadWrite);
}
private Task<object> GetFileResult(string path, string playlistPath)
@@ -150,7 +150,7 @@ namespace MediaBrowser.Api.Playback.Hls
return ResultFactory.GetStaticFileResult(Request, new StaticFileResultOptions
{
Path = path,
- FileShare = FileShareMode.ReadWrite,
+ FileShare = FileShare.ReadWrite,
OnComplete = () =>
{
if (transcodingJob != null)
diff --git a/MediaBrowser.Api/Playback/MediaInfoService.cs b/MediaBrowser.Api/Playback/MediaInfoService.cs
index 4b9bb8010..db24eaca6 100644
--- a/MediaBrowser.Api/Playback/MediaInfoService.cs
+++ b/MediaBrowser.Api/Playback/MediaInfoService.cs
@@ -1,14 +1,12 @@
#pragma warning disable CS1591
#pragma warning disable SA1402
-#pragma warning disable SA1600
#pragma warning disable SA1649
using System;
using System.Buffers;
-using System.Collections.Generic;
using System.Globalization;
-using System.Text.Json;
using System.Linq;
+using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
using MediaBrowser.Common.Net;
@@ -23,7 +21,6 @@ using MediaBrowser.Model.Dlna;
using MediaBrowser.Model.Dto;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.MediaInfo;
-using MediaBrowser.Model.Serialization;
using MediaBrowser.Model.Services;
using MediaBrowser.Model.Session;
using Microsoft.Extensions.Logging;
@@ -237,7 +234,7 @@ namespace MediaBrowser.Api.Playback
OpenToken = mediaSource.OpenToken
}).ConfigureAwait(false);
- info.MediaSources = new MediaSourceInfo[] { openStreamResult.MediaSource };
+ info.MediaSources = new[] { openStreamResult.MediaSource };
}
}
@@ -264,41 +261,26 @@ namespace MediaBrowser.Api.Playback
return ToOptimizedResult(result);
}
- private T Clone<T>(T obj)
- {
- // Since we're going to be setting properties on MediaSourceInfos that come out of _mediaSourceManager, we should clone it
- // Should we move this directly into MediaSourceManager?
- var json = JsonSerializer.SerializeToUtf8Bytes(obj);
- return JsonSerializer.Deserialize<T>(json);
- }
-
private async Task<PlaybackInfoResponse> GetPlaybackInfo(Guid id, Guid userId, string[] supportedLiveMediaTypes, string mediaSourceId = null, string liveStreamId = null)
{
var user = _userManager.GetUserById(userId);
var item = _libraryManager.GetItemById(id);
var result = new PlaybackInfoResponse();
+ MediaSourceInfo[] mediaSources;
if (string.IsNullOrWhiteSpace(liveStreamId))
{
- IEnumerable<MediaSourceInfo> mediaSources;
- try
- {
- // TODO handle supportedLiveMediaTypes ?
- mediaSources = await _mediaSourceManager.GetPlaybackMediaSources(item, user, true, false, CancellationToken.None).ConfigureAwait(false);
- }
- catch (Exception ex)
- {
- mediaSources = new List<MediaSourceInfo>();
- Logger.LogError(ex, "Could not find media sources for item id {id}", id);
- // TODO PlaybackException ??
- //result.ErrorCode = ex.ErrorCode;
- }
- result.MediaSources = mediaSources.ToArray();
+ // TODO handle supportedLiveMediaTypes?
+ var mediaSourcesList = await _mediaSourceManager.GetPlaybackMediaSources(item, user, true, true, CancellationToken.None).ConfigureAwait(false);
- if (!string.IsNullOrWhiteSpace(mediaSourceId))
+ if (string.IsNullOrWhiteSpace(mediaSourceId))
{
- result.MediaSources = result.MediaSources
+ mediaSources = mediaSourcesList.ToArray();
+ }
+ else
+ {
+ mediaSources = mediaSourcesList
.Where(i => string.Equals(i.Id, mediaSourceId, StringComparison.OrdinalIgnoreCase))
.ToArray();
}
@@ -307,11 +289,13 @@ namespace MediaBrowser.Api.Playback
{
var mediaSource = await _mediaSourceManager.GetLiveStream(liveStreamId, CancellationToken.None).ConfigureAwait(false);
- result.MediaSources = new MediaSourceInfo[] { mediaSource };
+ mediaSources = new[] { mediaSource };
}
- if (result.MediaSources.Count == 0)
+ if (mediaSources.Length == 0)
{
+ result.MediaSources = Array.Empty<MediaSourceInfo>();
+
if (!result.ErrorCode.HasValue)
{
result.ErrorCode = PlaybackErrorCode.NoCompatibleStream;
@@ -319,7 +303,9 @@ namespace MediaBrowser.Api.Playback
}
else
{
- result.MediaSources = Clone(result.MediaSources);
+ // Since we're going to be setting properties on MediaSourceInfos that come out of _mediaSourceManager, we should clone it
+ // Should we move this directly into MediaSourceManager?
+ result.MediaSources = JsonSerializer.Deserialize<MediaSourceInfo[]>(JsonSerializer.SerializeToUtf8Bytes(mediaSources));
result.PlaySessionId = Guid.NewGuid().ToString("N", CultureInfo.InvariantCulture);
}
@@ -380,7 +366,7 @@ namespace MediaBrowser.Api.Playback
var options = new VideoOptions
{
- MediaSources = new MediaSourceInfo[] { mediaSource },
+ MediaSources = new[] { mediaSource },
Context = EncodingContext.Streaming,
DeviceId = auth.DeviceId,
ItemId = item.Id,
@@ -425,10 +411,12 @@ namespace MediaBrowser.Api.Playback
user.Policy.EnableAudioPlaybackTranscoding);
}
+ // Beginning of Playback Determination: Attempt DirectPlay first
if (mediaSource.SupportsDirectPlay)
{
- if (mediaSource.IsRemote && forceDirectPlayRemoteMediaSource)
+ if (mediaSource.IsRemote && user.Policy.ForceRemoteSourceTranscoding)
{
+ mediaSource.SupportsDirectPlay = false;
}
else
{
@@ -475,36 +463,43 @@ namespace MediaBrowser.Api.Playback
if (mediaSource.SupportsDirectStream)
{
- options.MaxBitrate = GetMaxBitrate(maxBitrate, user);
-
- if (item is Audio)
+ if (mediaSource.IsRemote && user.Policy.ForceRemoteSourceTranscoding)
{
- if (!user.Policy.EnableAudioPlaybackTranscoding)
- {
- options.ForceDirectStream = true;
- }
+ mediaSource.SupportsDirectStream = false;
}
- else if (item is Video)
+ else
{
- if (!user.Policy.EnableAudioPlaybackTranscoding && !user.Policy.EnableVideoPlaybackTranscoding && !user.Policy.EnablePlaybackRemuxing)
+ options.MaxBitrate = GetMaxBitrate(maxBitrate, user);
+
+ if (item is Audio)
{
- options.ForceDirectStream = true;
+ if (!user.Policy.EnableAudioPlaybackTranscoding)
+ {
+ options.ForceDirectStream = true;
+ }
+ }
+ else if (item is Video)
+ {
+ if (!user.Policy.EnableAudioPlaybackTranscoding && !user.Policy.EnableVideoPlaybackTranscoding && !user.Policy.EnablePlaybackRemuxing)
+ {
+ 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)
- : streamBuilder.BuildVideoItem(options);
+ // 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)
+ : streamBuilder.BuildVideoItem(options);
- if (streamInfo == null || !streamInfo.IsDirectStream)
- {
- mediaSource.SupportsDirectStream = false;
- }
+ if (streamInfo == null || !streamInfo.IsDirectStream)
+ {
+ mediaSource.SupportsDirectStream = false;
+ }
- if (streamInfo != null)
- {
- SetDeviceSpecificSubtitleInfo(streamInfo, mediaSource, auth.Token);
+ if (streamInfo != null)
+ {
+ SetDeviceSpecificSubtitleInfo(streamInfo, mediaSource, auth.Token);
+ }
}
}
@@ -517,18 +512,43 @@ namespace MediaBrowser.Api.Playback
? streamBuilder.BuildAudioItem(options)
: streamBuilder.BuildVideoItem(options);
- if (streamInfo != null)
+ if (mediaSource.IsRemote && user.Policy.ForceRemoteSourceTranscoding)
{
- streamInfo.PlaySessionId = playSessionId;
-
- if (streamInfo.PlayMethod == PlayMethod.Transcode)
+ if (streamInfo != null)
{
+ streamInfo.PlaySessionId = playSessionId;
streamInfo.StartPositionTicks = startTimeTicks;
mediaSource.TranscodingUrl = streamInfo.ToUrl("-", auth.Token).TrimStart('-');
+ mediaSource.TranscodingUrl += "&allowVideoStreamCopy=false";
+ mediaSource.TranscodingUrl += "&allowAudioStreamCopy=false";
+ mediaSource.TranscodingContainer = streamInfo.Container;
+ mediaSource.TranscodingSubProtocol = streamInfo.SubProtocol;
+
+ // Do this after the above so that StartPositionTicks is set
+ SetDeviceSpecificSubtitleInfo(streamInfo, mediaSource, auth.Token);
+ }
+ }
+ else
+ {
+ if (streamInfo != null)
+ {
+ streamInfo.PlaySessionId = playSessionId;
- if (!allowVideoStreamCopy)
+ if (streamInfo.PlayMethod == PlayMethod.Transcode)
{
- mediaSource.TranscodingUrl += "&allowVideoStreamCopy=false";
+ streamInfo.StartPositionTicks = startTimeTicks;
+ mediaSource.TranscodingUrl = streamInfo.ToUrl("-", auth.Token).TrimStart('-');
+
+ if (!allowVideoStreamCopy)
+ {
+ mediaSource.TranscodingUrl += "&allowVideoStreamCopy=false";
+ }
+ if (!allowAudioStreamCopy)
+ {
+ mediaSource.TranscodingUrl += "&allowAudioStreamCopy=false";
+ }
+ mediaSource.TranscodingContainer = streamInfo.Container;
+ mediaSource.TranscodingSubProtocol = streamInfo.SubProtocol;
}
if (!allowAudioStreamCopy)
@@ -538,10 +558,10 @@ namespace MediaBrowser.Api.Playback
mediaSource.TranscodingContainer = streamInfo.Container;
mediaSource.TranscodingSubProtocol = streamInfo.SubProtocol;
- }
- // Do this after the above so that StartPositionTicks is set
- SetDeviceSpecificSubtitleInfo(streamInfo, mediaSource, auth.Token);
+ // Do this after the above so that StartPositionTicks is set
+ SetDeviceSpecificSubtitleInfo(streamInfo, mediaSource, auth.Token);
+ }
}
}
@@ -559,7 +579,7 @@ namespace MediaBrowser.Api.Playback
private long? GetMaxBitrate(long? clientMaxBitrate, User user)
{
var maxBitrate = clientMaxBitrate;
- var remoteClientMaxBitrate = user == null ? 0 : user.Policy.RemoteClientBitrateLimit;
+ var remoteClientMaxBitrate = user?.Policy.RemoteClientBitrateLimit ?? 0;
if (remoteClientMaxBitrate <= 0)
{
@@ -638,17 +658,9 @@ namespace MediaBrowser.Api.Playback
};
}).ThenBy(i =>
{
- if (maxBitrate.HasValue)
+ if (maxBitrate.HasValue && i.Bitrate.HasValue)
{
- if (i.Bitrate.HasValue)
- {
- if (i.Bitrate.Value <= maxBitrate.Value)
- {
- return 0;
- }
-
- return 2;
- }
+ return i.Bitrate.Value <= maxBitrate.Value ? 0 : 2;
}
return 1;
diff --git a/MediaBrowser.Api/Playback/Progressive/BaseProgressiveStreamingService.cs b/MediaBrowser.Api/Playback/Progressive/BaseProgressiveStreamingService.cs
index ed30dbba6..ed68219c9 100644
--- a/MediaBrowser.Api/Playback/Progressive/BaseProgressiveStreamingService.cs
+++ b/MediaBrowser.Api/Playback/Progressive/BaseProgressiveStreamingService.cs
@@ -248,7 +248,7 @@ namespace MediaBrowser.Api.Playback.Progressive
// ContentType = contentType,
// IsHeadRequest = isHeadRequest,
// Path = outputPath,
- // FileShare = FileShareMode.ReadWrite,
+ // FileShare = FileShare.ReadWrite,
// OnComplete = () =>
// {
// if (transcodingJob != null)
diff --git a/MediaBrowser.Api/Playback/Progressive/ProgressiveStreamWriter.cs b/MediaBrowser.Api/Playback/Progressive/ProgressiveStreamWriter.cs
index 660912065..a53b848f9 100644
--- a/MediaBrowser.Api/Playback/Progressive/ProgressiveStreamWriter.cs
+++ b/MediaBrowser.Api/Playback/Progressive/ProgressiveStreamWriter.cs
@@ -21,8 +21,6 @@ namespace MediaBrowser.Api.Playback.Progressive
private readonly CancellationToken _cancellationToken;
private readonly Dictionary<string, string> _outputHeaders;
- const int StreamCopyToBufferSize = 81920;
-
private long _bytesWritten = 0;
public long StartPosition { get; set; }
public bool AllowEndOfFile = true;
@@ -52,14 +50,14 @@ namespace MediaBrowser.Api.Playback.Progressive
private Stream GetInputStream(bool allowAsyncFileRead)
{
- var fileOpenOptions = FileOpenOptions.SequentialScan;
+ var fileOptions = FileOptions.SequentialScan;
if (allowAsyncFileRead)
{
- fileOpenOptions |= FileOpenOptions.Asynchronous;
+ fileOptions |= FileOptions.Asynchronous;
}
- return _fileSystem.GetFileStream(_path, FileOpenMode.Open, FileAccessMode.Read, FileShareMode.ReadWrite, fileOpenOptions);
+ return new FileStream(_path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite, IODefaults.FileStreamBufferSize, fileOptions);
}
public async Task WriteToAsync(Stream outputStream, CancellationToken cancellationToken)
@@ -127,7 +125,7 @@ namespace MediaBrowser.Api.Playback.Progressive
private async Task<int> CopyToInternalAsyncWithSyncRead(Stream source, Stream destination, CancellationToken cancellationToken)
{
- var array = new byte[StreamCopyToBufferSize];
+ var array = new byte[IODefaults.CopyToBufferSize];
int bytesRead;
int totalBytesRead = 0;
@@ -154,7 +152,7 @@ namespace MediaBrowser.Api.Playback.Progressive
private async Task<int> CopyToInternalAsync(Stream source, Stream destination, CancellationToken cancellationToken)
{
- var array = new byte[StreamCopyToBufferSize];
+ var array = new byte[IODefaults.CopyToBufferSize];
int bytesRead;
int totalBytesRead = 0;
diff --git a/MediaBrowser.Api/Playback/StreamRequest.cs b/MediaBrowser.Api/Playback/StreamRequest.cs
index 7626cc378..9ba8eda91 100644
--- a/MediaBrowser.Api/Playback/StreamRequest.cs
+++ b/MediaBrowser.Api/Playback/StreamRequest.cs
@@ -1,4 +1,3 @@
-using System;
using MediaBrowser.Controller.MediaEncoding;
using MediaBrowser.Model.Services;
diff --git a/MediaBrowser.Api/Playback/StreamState.cs b/MediaBrowser.Api/Playback/StreamState.cs
index 7396b5c99..d5d2f58c0 100644
--- a/MediaBrowser.Api/Playback/StreamState.cs
+++ b/MediaBrowser.Api/Playback/StreamState.cs
@@ -103,7 +103,7 @@ namespace MediaBrowser.Api.Playback
_mediaSourceManager = mediaSourceManager;
}
- public override void ReportTranscodingProgress(TimeSpan? transcodingPosition, float framerate, double? percentComplete, long bytesTranscoded, int? bitRate)
+ public override void ReportTranscodingProgress(TimeSpan? transcodingPosition, float? framerate, double? percentComplete, long? bytesTranscoded, int? bitRate)
{
ApiEntryPoint.Instance.ReportTranscodingProgress(TranscodingJob, this, transcodingPosition, framerate, percentComplete, bytesTranscoded, bitRate);
}
diff --git a/MediaBrowser.Api/Playback/UniversalAudioService.cs b/MediaBrowser.Api/Playback/UniversalAudioService.cs
index cbf981dfe..cebd4b49a 100644
--- a/MediaBrowser.Api/Playback/UniversalAudioService.cs
+++ b/MediaBrowser.Api/Playback/UniversalAudioService.cs
@@ -167,7 +167,7 @@ namespace MediaBrowser.Api.Playback
AudioCodec = request.AudioCodec,
Protocol = request.TranscodingProtocol,
BreakOnNonKeyFrames = request.BreakOnNonKeyFrames,
- MaxAudioChannels = request.TranscodingAudioChannels.HasValue ? request.TranscodingAudioChannels.Value.ToString(CultureInfo.InvariantCulture) : null
+ MaxAudioChannels = request.TranscodingAudioChannels?.ToString(CultureInfo.InvariantCulture)
}
};
@@ -300,7 +300,7 @@ namespace MediaBrowser.Api.Playback
// hls segment container can only be mpegts or fmp4 per ffmpeg documentation
// TODO: remove this when we switch back to the segment muxer
- var supportedHLSContainers = new string[] { "mpegts", "fmp4" };
+ var supportedHLSContainers = new[] { "mpegts", "fmp4" };
var newRequest = new GetMasterHlsAudioPlaylist
{
diff --git a/MediaBrowser.Api/PluginService.cs b/MediaBrowser.Api/PluginService.cs
index 16d3268b9..7f74511ee 100644
--- a/MediaBrowser.Api/PluginService.cs
+++ b/MediaBrowser.Api/PluginService.cs
@@ -243,9 +243,7 @@ namespace MediaBrowser.Api
// https://code.google.com/p/servicestack/source/browse/trunk/Common/ServiceStack.Text/ServiceStack.Text/Controller/PathInfo.cs
var id = Guid.Parse(GetPathValue(1));
- var plugin = _appHost.Plugins.First(p => p.Id == id) as IHasPluginConfiguration;
-
- if (plugin == null)
+ if (!(_appHost.Plugins.First(p => p.Id == id) is IHasPluginConfiguration plugin))
{
throw new FileNotFoundException();
}
diff --git a/MediaBrowser.Api/ScheduledTasks/ScheduledTaskService.cs b/MediaBrowser.Api/ScheduledTasks/ScheduledTaskService.cs
index 2bd387229..e08a8482e 100644
--- a/MediaBrowser.Api/ScheduledTasks/ScheduledTaskService.cs
+++ b/MediaBrowser.Api/ScheduledTasks/ScheduledTaskService.cs
@@ -123,9 +123,7 @@ namespace MediaBrowser.Api.ScheduledTasks
{
var isHidden = false;
- var configurableTask = i.ScheduledTask as IConfigurableScheduledTask;
-
- if (configurableTask != null)
+ if (i.ScheduledTask is IConfigurableScheduledTask configurableTask)
{
isHidden = configurableTask.IsHidden;
}
@@ -142,9 +140,7 @@ namespace MediaBrowser.Api.ScheduledTasks
{
var isEnabled = true;
- var configurableTask = i.ScheduledTask as IConfigurableScheduledTask;
-
- if (configurableTask != null)
+ if (i.ScheduledTask is IConfigurableScheduledTask configurableTask)
{
isEnabled = configurableTask.IsEnabled;
}
diff --git a/MediaBrowser.Api/ScheduledTasks/ScheduledTasksWebSocketListener.cs b/MediaBrowser.Api/ScheduledTasks/ScheduledTasksWebSocketListener.cs
index d9530ffb7..14b9b3618 100644
--- a/MediaBrowser.Api/ScheduledTasks/ScheduledTasksWebSocketListener.cs
+++ b/MediaBrowser.Api/ScheduledTasks/ScheduledTasksWebSocketListener.cs
@@ -28,7 +28,7 @@ namespace MediaBrowser.Api.ScheduledTasks
/// <summary>
/// Initializes a new instance of the <see cref="ScheduledTasksWebSocketListener" /> class.
/// </summary>
- public ScheduledTasksWebSocketListener(ILogger logger, ITaskManager taskManager)
+ public ScheduledTasksWebSocketListener(ILogger<ScheduledTasksWebSocketListener> logger, ITaskManager taskManager)
: base(logger)
{
TaskManager = taskManager;
diff --git a/MediaBrowser.Api/SearchService.cs b/MediaBrowser.Api/SearchService.cs
index 0a3dc19dc..e9d339c6e 100644
--- a/MediaBrowser.Api/SearchService.cs
+++ b/MediaBrowser.Api/SearchService.cs
@@ -234,59 +234,48 @@ namespace MediaBrowser.Api
SetThumbImageInfo(result, item);
SetBackdropImageInfo(result, item);
- var program = item as LiveTvProgram;
- if (program != null)
+ switch (item)
{
- result.StartDate = program.StartDate;
- }
-
- var hasSeries = item as IHasSeries;
- if (hasSeries != null)
- {
- result.Series = hasSeries.SeriesName;
- }
-
- var series = item as Series;
- if (series != null)
- {
- if (series.Status.HasValue)
- {
- result.Status = series.Status.Value.ToString();
- }
- }
-
- var album = item as MusicAlbum;
-
- if (album != null)
- {
- result.Artists = album.Artists;
- result.AlbumArtist = album.AlbumArtist;
- }
-
- var song = item as Audio;
-
- if (song != null)
- {
- result.AlbumArtist = song.AlbumArtists.FirstOrDefault();
- result.Artists = song.Artists;
-
- album = song.AlbumEntity;
-
- if (album != null)
- {
- result.Album = album.Name;
- result.AlbumId = album.Id;
- }
- else
- {
- result.Album = song.Album;
- }
+ case IHasSeries hasSeries:
+ result.Series = hasSeries.SeriesName;
+ break;
+ case LiveTvProgram program:
+ result.StartDate = program.StartDate;
+ break;
+ case Series series:
+ if (series.Status.HasValue)
+ {
+ result.Status = series.Status.Value.ToString();
+ }
+
+ break;
+ case MusicAlbum album:
+ result.Artists = album.Artists;
+ result.AlbumArtist = album.AlbumArtist;
+ break;
+ case Audio song:
+ result.AlbumArtist = song.AlbumArtists.FirstOrDefault();
+ result.Artists = song.Artists;
+
+ MusicAlbum musicAlbum = song.AlbumEntity;
+
+ if (musicAlbum != null)
+ {
+ result.Album = musicAlbum.Name;
+ result.AlbumId = musicAlbum.Id;
+ }
+ else
+ {
+ result.Album = song.Album;
+ }
+
+ break;
}
if (!item.ChannelId.Equals(Guid.Empty))
{
var channel = _libraryManager.GetItemById(item.ChannelId);
- result.ChannelName = channel == null ? null : channel.Name;
+ result.ChannelName = channel?.Name;
}
return result;
@@ -296,12 +285,9 @@ namespace MediaBrowser.Api
{
var itemWithImage = item.HasImage(ImageType.Thumb) ? item : null;
- if (itemWithImage == null)
+ if (itemWithImage == null && item is Episode)
{
- if (item is Episode)
- {
- itemWithImage = GetParentWithImage<Series>(item, ImageType.Thumb);
- }
+ itemWithImage = GetParentWithImage<Series>(item, ImageType.Thumb);
}
if (itemWithImage == null)
@@ -323,12 +309,8 @@ namespace MediaBrowser.Api
private void SetBackdropImageInfo(SearchHint hint, BaseItem item)
{
- var itemWithImage = item.HasImage(ImageType.Backdrop) ? item : null;
-
- if (itemWithImage == null)
- {
- itemWithImage = GetParentWithImage<BaseItem>(item, ImageType.Backdrop);
- }
+ var itemWithImage = (item.HasImage(ImageType.Backdrop) ? item : null)
+ ?? GetParentWithImage<BaseItem>(item, ImageType.Backdrop);
if (itemWithImage != null)
{
diff --git a/MediaBrowser.Api/Sessions/ApiKeyService.cs b/MediaBrowser.Api/Sessions/ApiKeyService.cs
new file mode 100644
index 000000000..5102ce0a7
--- /dev/null
+++ b/MediaBrowser.Api/Sessions/ApiKeyService.cs
@@ -0,0 +1,85 @@
+using System;
+using System.Globalization;
+using MediaBrowser.Controller;
+using MediaBrowser.Controller.Configuration;
+using MediaBrowser.Controller.Net;
+using MediaBrowser.Controller.Security;
+using MediaBrowser.Controller.Session;
+using MediaBrowser.Model.Services;
+using Microsoft.Extensions.Logging;
+
+namespace MediaBrowser.Api.Sessions
+{
+ [Route("/Auth/Keys", "GET")]
+ [Authenticated(Roles = "Admin")]
+ public class GetKeys
+ {
+ }
+
+ [Route("/Auth/Keys/{Key}", "DELETE")]
+ [Authenticated(Roles = "Admin")]
+ public class RevokeKey
+ {
+ [ApiMember(Name = "Key", Description = "Authentication key", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "DELETE")]
+ public string Key { get; set; }
+ }
+
+ [Route("/Auth/Keys", "POST")]
+ [Authenticated(Roles = "Admin")]
+ public class CreateKey
+ {
+ [ApiMember(Name = "App", Description = "Name of the app using the authentication key", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "POST")]
+ public string App { get; set; }
+ }
+
+ public class ApiKeyService : BaseApiService
+ {
+ private readonly ISessionManager _sessionManager;
+
+ private readonly IAuthenticationRepository _authRepo;
+
+ private readonly IServerApplicationHost _appHost;
+
+ public ApiKeyService(
+ ILogger<ApiKeyService> logger,
+ IServerConfigurationManager serverConfigurationManager,
+ IHttpResultFactory httpResultFactory,
+ ISessionManager sessionManager,
+ IServerApplicationHost appHost,
+ IAuthenticationRepository authRepo)
+ : base(logger, serverConfigurationManager, httpResultFactory)
+ {
+ _sessionManager = sessionManager;
+ _authRepo = authRepo;
+ _appHost = appHost;
+ }
+
+ public void Delete(RevokeKey request)
+ {
+ _sessionManager.RevokeToken(request.Key);
+ }
+
+ public void Post(CreateKey request)
+ {
+ _authRepo.Create(new AuthenticationInfo
+ {
+ AppName = request.App,
+ AccessToken = Guid.NewGuid().ToString("N", CultureInfo.InvariantCulture),
+ DateCreated = DateTime.UtcNow,
+ DeviceId = _appHost.SystemId,
+ DeviceName = _appHost.FriendlyName,
+ AppVersion = _appHost.ApplicationVersionString
+ });
+ }
+
+ public object Get(GetKeys request)
+ {
+ var result = _authRepo.Get(new AuthenticationInfoQuery
+ {
+ HasUser = false
+ });
+
+ return result;
+ }
+ }
+}
diff --git a/MediaBrowser.Api/Session/SessionInfoWebSocketListener.cs b/MediaBrowser.Api/Sessions/SessionInfoWebSocketListener.cs
index cf7ddd631..0e74c9267 100644
--- a/MediaBrowser.Api/Session/SessionInfoWebSocketListener.cs
+++ b/MediaBrowser.Api/Sessions/SessionInfoWebSocketListener.cs
@@ -5,7 +5,7 @@ using MediaBrowser.Controller.Net;
using MediaBrowser.Controller.Session;
using Microsoft.Extensions.Logging;
-namespace MediaBrowser.Api.Session
+namespace MediaBrowser.Api.Sessions
{
/// <summary>
/// Class SessionInfoWebSocketListener
@@ -26,7 +26,7 @@ namespace MediaBrowser.Api.Session
/// <summary>
/// Initializes a new instance of the <see cref="SessionInfoWebSocketListener"/> class.
/// </summary>
- public SessionInfoWebSocketListener(ILogger logger, ISessionManager sessionManager)
+ public SessionInfoWebSocketListener(ILogger<SessionInfoWebSocketListener> logger, ISessionManager sessionManager)
: base(logger)
{
_sessionManager = sessionManager;
diff --git a/MediaBrowser.Api/Session/SessionsService.cs b/MediaBrowser.Api/Sessions/SessionService.cs
index 700861c55..020bb5042 100644
--- a/MediaBrowser.Api/Session/SessionsService.cs
+++ b/MediaBrowser.Api/Sessions/SessionService.cs
@@ -1,40 +1,37 @@
using System;
-using System.Globalization;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
-using MediaBrowser.Controller;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Devices;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Net;
-using MediaBrowser.Controller.Security;
using MediaBrowser.Controller.Session;
using MediaBrowser.Model.Dto;
using MediaBrowser.Model.Services;
using MediaBrowser.Model.Session;
using Microsoft.Extensions.Logging;
-namespace MediaBrowser.Api.Session
+namespace MediaBrowser.Api.Sessions
{
/// <summary>
- /// Class GetSessions
+ /// Class GetSessions.
/// </summary>
[Route("/Sessions", "GET", Summary = "Gets a list of sessions")]
[Authenticated]
public class GetSessions : IReturn<SessionInfo[]>
{
- [ApiMember(Name = "ControllableByUserId", Description = "Optional. Filter by sessions that a given user is allowed to remote control.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
+ [ApiMember(Name = "ControllableByUserId", Description = "Filter by sessions that a given user is allowed to remote control.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
public Guid ControllableByUserId { get; set; }
- [ApiMember(Name = "DeviceId", Description = "Optional. Filter by device id.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
+ [ApiMember(Name = "DeviceId", Description = "Filter by device Id.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
public string DeviceId { get; set; }
public int? ActiveWithinSeconds { get; set; }
}
/// <summary>
- /// Class DisplayContent
+ /// Class DisplayContent.
/// </summary>
[Route("/Sessions/{Id}/Viewing", "POST", Summary = "Instructs a session to browse to an item or view")]
[Authenticated]
@@ -182,7 +179,7 @@ namespace MediaBrowser.Api.Session
[ApiMember(Name = "Id", Description = "Session Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")]
public string Id { get; set; }
- [ApiMember(Name = "UserId", Description = "UserId Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")]
+ [ApiMember(Name = "UserId", Description = "User Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")]
public string UserId { get; set; }
}
@@ -230,15 +227,20 @@ namespace MediaBrowser.Api.Session
public string Id { get; set; }
}
- [Route("/Sessions/Logout", "POST", Summary = "Reports that a session has ended")]
+ [Route("/Sessions/Viewing", "POST", Summary = "Reports that a session is viewing an item")]
[Authenticated]
- public class ReportSessionEnded : IReturnVoid
+ public class ReportViewing : IReturnVoid
{
+ [ApiMember(Name = "SessionId", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "POST")]
+ public string SessionId { get; set; }
+
+ [ApiMember(Name = "ItemId", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "POST")]
+ public string ItemId { get; set; }
}
- [Route("/Auth/Keys", "GET")]
- [Authenticated(Roles = "Admin")]
- public class GetApiKeys
+ [Route("/Sessions/Logout", "POST", Summary = "Reports that a session has ended")]
+ [Authenticated]
+ public class ReportSessionEnded : IReturnVoid
{
}
@@ -254,48 +256,28 @@ namespace MediaBrowser.Api.Session
{
}
- [Route("/Auth/Keys/{Key}", "DELETE")]
- [Authenticated(Roles = "Admin")]
- public class RevokeKey
- {
- [ApiMember(Name = "Key", Description = "Auth Key", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "DELETE")]
- public string Key { get; set; }
- }
-
- [Route("/Auth/Keys", "POST")]
- [Authenticated(Roles = "Admin")]
- public class CreateKey
- {
- [ApiMember(Name = "App", Description = "App", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "POST")]
- public string App { get; set; }
- }
-
/// <summary>
/// Class SessionsService.
/// </summary>
- public class SessionsService : BaseApiService
+ public class SessionService : BaseApiService
{
/// <summary>
- /// The _session manager.
+ /// The session manager.
/// </summary>
private readonly ISessionManager _sessionManager;
private readonly IUserManager _userManager;
private readonly IAuthorizationContext _authContext;
- private readonly IAuthenticationRepository _authRepo;
private readonly IDeviceManager _deviceManager;
private readonly ISessionContext _sessionContext;
- private readonly IServerApplicationHost _appHost;
- public SessionsService(
- ILogger<SessionsService> logger,
+ public SessionService(
+ ILogger<SessionService> logger,
IServerConfigurationManager serverConfigurationManager,
IHttpResultFactory httpResultFactory,
ISessionManager sessionManager,
- IServerApplicationHost appHost,
IUserManager userManager,
IAuthorizationContext authContext,
- IAuthenticationRepository authRepo,
IDeviceManager deviceManager,
ISessionContext sessionContext)
: base(logger, serverConfigurationManager, httpResultFactory)
@@ -303,10 +285,8 @@ namespace MediaBrowser.Api.Session
_sessionManager = sessionManager;
_userManager = userManager;
_authContext = authContext;
- _authRepo = authRepo;
_deviceManager = deviceManager;
_sessionContext = sessionContext;
- _appHost = appHost;
}
public object Get(GetAuthProviders request)
@@ -319,25 +299,6 @@ namespace MediaBrowser.Api.Session
return _userManager.GetPasswordResetProviders();
}
- public void Delete(RevokeKey request)
- {
- _sessionManager.RevokeToken(request.Key);
-
- }
-
- public void Post(CreateKey request)
- {
- _authRepo.Create(new AuthenticationInfo
- {
- AppName = request.App,
- AccessToken = Guid.NewGuid().ToString("N", CultureInfo.InvariantCulture),
- DateCreated = DateTime.UtcNow,
- DeviceId = _appHost.SystemId,
- DeviceName = _appHost.FriendlyName,
- AppVersion = _appHost.ApplicationVersionString
- });
- }
-
public void Post(ReportSessionEnded request)
{
var auth = _authContext.GetAuthorizationInfo(Request);
@@ -345,16 +306,6 @@ namespace MediaBrowser.Api.Session
_sessionManager.Logout(auth.Token);
}
- public object Get(GetApiKeys request)
- {
- var result = _authRepo.Get(new AuthenticationInfoQuery
- {
- HasUser = false
- });
-
- return result;
- }
-
/// <summary>
/// Gets the specified request.
/// </summary>
@@ -438,14 +389,12 @@ namespace MediaBrowser.Api.Session
public Task Post(SendSystemCommand request)
{
var name = request.Command;
-
if (Enum.TryParse(name, true, out GeneralCommandType commandType))
{
name = commandType.ToString();
}
var currentSession = GetSession(_sessionContext);
-
var command = new GeneralCommand
{
Name = name,
@@ -518,16 +467,13 @@ namespace MediaBrowser.Api.Session
{
request.Id = GetSession(_sessionContext).Id;
}
+
_sessionManager.ReportCapabilities(request.Id, new ClientCapabilities
{
PlayableMediaTypes = SplitValue(request.PlayableMediaTypes, ','),
-
SupportedCommands = SplitValue(request.SupportedCommands, ','),
-
SupportsMediaControl = request.SupportsMediaControl,
-
SupportsSync = request.SupportsSync,
-
SupportsPersistentIdentifier = request.SupportsPersistentIdentifier
});
}
@@ -538,7 +484,15 @@ namespace MediaBrowser.Api.Session
{
request.Id = GetSession(_sessionContext).Id;
}
+
_sessionManager.ReportCapabilities(request.Id, request);
}
+
+ public void Post(ReportViewing request)
+ {
+ request.SessionId = GetSession(_sessionContext).Id;
+
+ _sessionManager.ReportNowViewingItem(request.SessionId, request.ItemId);
+ }
}
}
diff --git a/MediaBrowser.Api/Subtitles/SubtitleService.cs b/MediaBrowser.Api/Subtitles/SubtitleService.cs
index c4a7ae78e..f2968c6b5 100644
--- a/MediaBrowser.Api/Subtitles/SubtitleService.cs
+++ b/MediaBrowser.Api/Subtitles/SubtitleService.cs
@@ -230,17 +230,14 @@ namespace MediaBrowser.Api.Subtitles
if (string.Equals(request.Format, "vtt", StringComparison.OrdinalIgnoreCase) && request.AddVttTimeMap)
{
- using (var stream = await GetSubtitles(request).ConfigureAwait(false))
- {
- using (var reader = new StreamReader(stream))
- {
- var text = reader.ReadToEnd();
+ using var stream = await GetSubtitles(request).ConfigureAwait(false);
+ using var reader = new StreamReader(stream);
- text = text.Replace("WEBVTT", "WEBVTT\nX-TIMESTAMP-MAP=MPEGTS:900000,LOCAL:00:00:00.000");
+ var text = reader.ReadToEnd();
- return ResultFactory.GetResult(Request, text, MimeTypes.GetMimeType("file." + request.Format));
- }
- }
+ text = text.Replace("WEBVTT", "WEBVTT\nX-TIMESTAMP-MAP=MPEGTS:900000,LOCAL:00:00:00.000");
+
+ return ResultFactory.GetResult(Request, text, MimeTypes.GetMimeType("file." + request.Format));
}
return ResultFactory.GetResult(Request, await GetSubtitles(request).ConfigureAwait(false), MimeTypes.GetMimeType("file." + request.Format));
diff --git a/MediaBrowser.Api/System/ActivityLogWebSocketListener.cs b/MediaBrowser.Api/System/ActivityLogWebSocketListener.cs
index 60b190a0e..8e4860be4 100644
--- a/MediaBrowser.Api/System/ActivityLogWebSocketListener.cs
+++ b/MediaBrowser.Api/System/ActivityLogWebSocketListener.cs
@@ -23,7 +23,7 @@ namespace MediaBrowser.Api.System
/// </summary>
private readonly IActivityManager _activityManager;
- public ActivityLogWebSocketListener(ILogger logger, IActivityManager activityManager) : base(logger)
+ public ActivityLogWebSocketListener(ILogger<ActivityLogWebSocketListener> logger, IActivityManager activityManager) : base(logger)
{
_activityManager = activityManager;
_activityManager.EntryCreated += OnEntryCreated;
diff --git a/MediaBrowser.Api/System/SystemService.cs b/MediaBrowser.Api/System/SystemService.cs
index 3a56ba701..c57cc93d5 100644
--- a/MediaBrowser.Api/System/SystemService.cs
+++ b/MediaBrowser.Api/System/SystemService.cs
@@ -168,12 +168,9 @@ namespace MediaBrowser.Api.System
.First(i => string.Equals(i.Name, request.Name, StringComparison.OrdinalIgnoreCase));
// For older files, assume fully static
- if (file.LastWriteTimeUtc < DateTime.UtcNow.AddHours(-1))
- {
- return ResultFactory.GetStaticFileResult(Request, file.FullName, FileShareMode.Read);
- }
+ var fileShare = file.LastWriteTimeUtc < DateTime.UtcNow.AddHours(-1) ? FileShare.Read : FileShare.ReadWrite;
- return ResultFactory.GetStaticFileResult(Request, file.FullName, FileShareMode.ReadWrite);
+ return ResultFactory.GetStaticFileResult(Request, file.FullName, fileShare);
}
/// <summary>
diff --git a/MediaBrowser.Api/TranscodingJob.cs b/MediaBrowser.Api/TranscodingJob.cs
index 6d944d19e..8c24e3ce1 100644
--- a/MediaBrowser.Api/TranscodingJob.cs
+++ b/MediaBrowser.Api/TranscodingJob.cs
@@ -92,10 +92,7 @@ namespace MediaBrowser.Api
{
lock (_timerLock)
{
- if (KillTimer != null)
- {
- KillTimer.Change(Timeout.Infinite, Timeout.Infinite);
- }
+ KillTimer?.Change(Timeout.Infinite, Timeout.Infinite);
}
}
diff --git a/MediaBrowser.Api/TvShowsService.cs b/MediaBrowser.Api/TvShowsService.cs
index b843f7096..cd8e8dfbe 100644
--- a/MediaBrowser.Api/TvShowsService.cs
+++ b/MediaBrowser.Api/TvShowsService.cs
@@ -12,8 +12,8 @@ using MediaBrowser.Controller.Net;
using MediaBrowser.Controller.TV;
using MediaBrowser.Model.Dto;
using MediaBrowser.Model.Entities;
-using MediaBrowser.Model.Services;
using MediaBrowser.Model.Querying;
+using MediaBrowser.Model.Services;
using Microsoft.Extensions.Logging;
namespace MediaBrowser.Api
@@ -424,9 +424,7 @@ namespace MediaBrowser.Api
if (!string.IsNullOrWhiteSpace(request.SeasonId))
{
- var season = _libraryManager.GetItemById(new Guid(request.SeasonId)) as Season;
-
- if (season == null)
+ if (!(_libraryManager.GetItemById(new Guid(request.SeasonId)) is Season season))
{
throw new ResourceNotFoundException("No season exists with Id " + request.SeasonId);
}
@@ -444,14 +442,7 @@ namespace MediaBrowser.Api
var season = series.GetSeasons(user, dtoOptions).FirstOrDefault(i => i.IndexNumber == request.Season.Value);
- if (season == null)
- {
- episodes = new List<BaseItem>();
- }
- else
- {
- episodes = ((Season)season).GetEpisodes(user, dtoOptions);
- }
+ episodes = season == null ? new List<BaseItem>() : ((Season)season).GetEpisodes(user, dtoOptions);
}
else
{
diff --git a/MediaBrowser.Api/UserLibrary/ArtistsService.cs b/MediaBrowser.Api/UserLibrary/ArtistsService.cs
index adb0a440f..3d08d5437 100644
--- a/MediaBrowser.Api/UserLibrary/ArtistsService.cs
+++ b/MediaBrowser.Api/UserLibrary/ArtistsService.cs
@@ -126,12 +126,7 @@ namespace MediaBrowser.Api.UserLibrary
protected override QueryResult<(BaseItem, ItemCounts)> GetItems(GetItemsByName request, InternalItemsQuery query)
{
- if (request is GetAlbumArtists)
- {
- return LibraryManager.GetAlbumArtists(query);
- }
-
- return LibraryManager.GetArtists(query);
+ return request is GetAlbumArtists ? LibraryManager.GetAlbumArtists(query) : LibraryManager.GetArtists(query);
}
/// <summary>
diff --git a/MediaBrowser.Api/UserLibrary/BaseItemsByNameService.cs b/MediaBrowser.Api/UserLibrary/BaseItemsByNameService.cs
index 9fa222d32..c4a52d5f5 100644
--- a/MediaBrowser.Api/UserLibrary/BaseItemsByNameService.cs
+++ b/MediaBrowser.Api/UserLibrary/BaseItemsByNameService.cs
@@ -82,8 +82,7 @@ namespace MediaBrowser.Api.UserLibrary
{
var parent = GetParentItem(request);
- var collectionFolder = parent as IHasCollectionType;
- if (collectionFolder != null)
+ if (parent is IHasCollectionType collectionFolder)
{
return collectionFolder.CollectionType;
}
@@ -274,7 +273,7 @@ namespace MediaBrowser.Api.UserLibrary
DtoOptions = dtoOptions
};
- Func<BaseItem, bool> filter = i => FilterItem(request, i, excludeItemTypes, includeItemTypes, mediaTypes);
+ bool Filter(BaseItem i) => FilterItem(request, i, excludeItemTypes, includeItemTypes, mediaTypes);
if (parentItem.IsFolder)
{
@@ -284,18 +283,18 @@ namespace MediaBrowser.Api.UserLibrary
{
items = request.Recursive ?
folder.GetRecursiveChildren(user, query).ToList() :
- folder.GetChildren(user, true).Where(filter).ToList();
+ folder.GetChildren(user, true).Where(Filter).ToList();
}
else
{
items = request.Recursive ?
- folder.GetRecursiveChildren(filter) :
- folder.Children.Where(filter).ToList();
+ folder.GetRecursiveChildren(Filter) :
+ folder.Children.Where(Filter).ToList();
}
}
else
{
- items = new[] { parentItem }.Where(filter).ToList();
+ items = new[] { parentItem }.Where(Filter).ToList();
}
var extractedItems = GetAllItems(request, items);
@@ -346,30 +345,21 @@ namespace MediaBrowser.Api.UserLibrary
private bool FilterItem(GetItemsByName request, BaseItem f, string[] excludeItemTypes, string[] includeItemTypes, string[] mediaTypes)
{
// Exclude item types
- if (excludeItemTypes.Length > 0)
+ if (excludeItemTypes.Length > 0 && excludeItemTypes.Contains(f.GetType().Name, StringComparer.OrdinalIgnoreCase))
{
- if (excludeItemTypes.Contains(f.GetType().Name, StringComparer.OrdinalIgnoreCase))
- {
- return false;
- }
+ return false;
}
// Include item types
- if (includeItemTypes.Length > 0)
+ if (includeItemTypes.Length > 0 && !includeItemTypes.Contains(f.GetType().Name, StringComparer.OrdinalIgnoreCase))
{
- if (!includeItemTypes.Contains(f.GetType().Name, StringComparer.OrdinalIgnoreCase))
- {
- return false;
- }
+ return false;
}
// Include MediaTypes
- if (mediaTypes.Length > 0)
+ if (mediaTypes.Length > 0 && !mediaTypes.Contains(f.MediaType ?? string.Empty, StringComparer.OrdinalIgnoreCase))
{
- if (!mediaTypes.Contains(f.MediaType ?? string.Empty, StringComparer.OrdinalIgnoreCase))
- {
- return false;
- }
+ return false;
}
return true;
diff --git a/MediaBrowser.Api/UserLibrary/BaseItemsRequest.cs b/MediaBrowser.Api/UserLibrary/BaseItemsRequest.cs
index a26f59573..7561b5c89 100644
--- a/MediaBrowser.Api/UserLibrary/BaseItemsRequest.cs
+++ b/MediaBrowser.Api/UserLibrary/BaseItemsRequest.cs
@@ -396,12 +396,10 @@ namespace MediaBrowser.Api.UserLibrary
public VideoType[] GetVideoTypes()
{
- if (string.IsNullOrEmpty(VideoTypes))
- {
- return Array.Empty<VideoType>();
- }
-
- return VideoTypes.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries).Select(v => (VideoType)Enum.Parse(typeof(VideoType), v, true)).ToArray();
+ return string.IsNullOrEmpty(VideoTypes)
+ ? Array.Empty<VideoType>()
+ : VideoTypes.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries)
+ .Select(v => Enum.Parse<VideoType>(v, true)).ToArray();
}
/// <summary>
@@ -412,12 +410,10 @@ namespace MediaBrowser.Api.UserLibrary
{
var val = Filters;
- if (string.IsNullOrEmpty(val))
- {
- return new ItemFilter[] { };
- }
-
- return val.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries).Select(v => (ItemFilter)Enum.Parse(typeof(ItemFilter), v, true)).ToArray();
+ return string.IsNullOrEmpty(val)
+ ? Array.Empty<ItemFilter>()
+ : val.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries)
+ .Select(v => Enum.Parse<ItemFilter>(v, true)).ToArray();
}
/// <summary>
@@ -428,12 +424,9 @@ namespace MediaBrowser.Api.UserLibrary
{
var val = ImageTypes;
- if (string.IsNullOrEmpty(val))
- {
- return new ImageType[] { };
- }
-
- return val.Split(',').Select(v => (ImageType)Enum.Parse(typeof(ImageType), v, true)).ToArray();
+ return string.IsNullOrEmpty(val)
+ ? Array.Empty<ImageType>()
+ : val.Split(',').Select(v => Enum.Parse<ImageType>(v, true)).ToArray();
}
/// <summary>
@@ -469,7 +462,9 @@ namespace MediaBrowser.Api.UserLibrary
var sortOrderIndex = sortOrders.Length > i ? i : 0;
var sortOrderValue = sortOrders.Length > sortOrderIndex ? sortOrders[sortOrderIndex] : null;
- var sortOrder = string.Equals(sortOrderValue, "Descending", StringComparison.OrdinalIgnoreCase) ? MediaBrowser.Model.Entities.SortOrder.Descending : MediaBrowser.Model.Entities.SortOrder.Ascending;
+ var sortOrder = string.Equals(sortOrderValue, "Descending", StringComparison.OrdinalIgnoreCase)
+ ? MediaBrowser.Model.Entities.SortOrder.Descending
+ : MediaBrowser.Model.Entities.SortOrder.Ascending;
result[i] = new ValueTuple<string, SortOrder>(vals[i], sortOrder);
}
diff --git a/MediaBrowser.Api/UserLibrary/GenresService.cs b/MediaBrowser.Api/UserLibrary/GenresService.cs
index 13bb88ca8..1fa272a5f 100644
--- a/MediaBrowser.Api/UserLibrary/GenresService.cs
+++ b/MediaBrowser.Api/UserLibrary/GenresService.cs
@@ -5,7 +5,6 @@ using MediaBrowser.Controller.Dto;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Net;
-using MediaBrowser.Controller.Persistence;
using MediaBrowser.Model.Dto;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Querying;
diff --git a/MediaBrowser.Api/UserLibrary/ItemsService.cs b/MediaBrowser.Api/UserLibrary/ItemsService.cs
index c7b505171..c4d44042b 100644
--- a/MediaBrowser.Api/UserLibrary/ItemsService.cs
+++ b/MediaBrowser.Api/UserLibrary/ItemsService.cs
@@ -199,21 +199,22 @@ namespace MediaBrowser.Api.UserLibrary
item = _libraryManager.GetUserRootFolder();
}
- Folder folder = item as Folder;
- if (folder == null)
+ if (!(item is Folder folder))
{
folder = _libraryManager.GetUserRootFolder();
}
- var hasCollectionType = folder as IHasCollectionType;
- if (hasCollectionType != null
+ if (folder is IHasCollectionType hasCollectionType
&& string.Equals(hasCollectionType.CollectionType, CollectionType.Playlists, StringComparison.OrdinalIgnoreCase))
{
request.Recursive = true;
request.IncludeItemTypes = "Playlist";
}
- bool isInEnabledFolder = user.Policy.EnabledFolders.Any(i => new Guid(i) == item.Id);
+ bool isInEnabledFolder = user.Policy.EnabledFolders.Any(i => new Guid(i) == item.Id)
+ // Assume all folders inside an EnabledChannel are enabled
+ || user.Policy.EnabledChannels.Any(i => new Guid(i) == item.Id);
+
var collectionFolders = _libraryManager.GetCollectionFolders(item);
foreach (var collectionFolder in collectionFolders)
{
@@ -225,7 +226,7 @@ namespace MediaBrowser.Api.UserLibrary
}
}
- if (!(item is UserRootFolder) && !user.Policy.EnableAllFolders && !isInEnabledFolder)
+ if (!(item is UserRootFolder) && !user.Policy.EnableAllFolders && !isInEnabledFolder && !user.Policy.EnableAllChannels)
{
Logger.LogWarning("{UserName} is not permitted to access Library {ItemName}.", user.Name, item.Name);
return new QueryResult<BaseItem>
@@ -241,11 +242,11 @@ namespace MediaBrowser.Api.UserLibrary
return folder.GetItems(GetItemsQuery(request, dtoOptions, user));
}
- var itemsArray = folder.GetChildren(user, true).ToArray();
+ var itemsArray = folder.GetChildren(user, true);
return new QueryResult<BaseItem>
{
Items = itemsArray,
- TotalRecordCount = itemsArray.Length,
+ TotalRecordCount = itemsArray.Count,
StartIndex = 0
};
}
diff --git a/MediaBrowser.Api/UserLibrary/PersonsService.cs b/MediaBrowser.Api/UserLibrary/PersonsService.cs
index 853eada25..3204e5219 100644
--- a/MediaBrowser.Api/UserLibrary/PersonsService.cs
+++ b/MediaBrowser.Api/UserLibrary/PersonsService.cs
@@ -6,7 +6,6 @@ using MediaBrowser.Controller.Dto;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Net;
-using MediaBrowser.Controller.Persistence;
using MediaBrowser.Model.Dto;
using MediaBrowser.Model.Querying;
using MediaBrowser.Model.Services;
diff --git a/MediaBrowser.Api/UserLibrary/PlaystateService.cs b/MediaBrowser.Api/UserLibrary/PlaystateService.cs
index 9d1cf5d9e..d0faca163 100644
--- a/MediaBrowser.Api/UserLibrary/PlaystateService.cs
+++ b/MediaBrowser.Api/UserLibrary/PlaystateService.cs
@@ -103,10 +103,6 @@ namespace MediaBrowser.Api.UserLibrary
[ApiMember(Name = "MediaSourceId", Description = "The id of the MediaSource", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "POST")]
public string MediaSourceId { get; set; }
- /// <summary>
- /// Gets or sets a value indicating whether this <see cref="UpdateUserItemRating" /> is likes.
- /// </summary>
- /// <value><c>true</c> if likes; otherwise, <c>false</c>.</value>
[ApiMember(Name = "CanSeek", Description = "Indicates if the client can seek", IsRequired = false, DataType = "boolean", ParameterType = "query", Verb = "POST")]
public bool CanSeek { get; set; }
diff --git a/MediaBrowser.Api/UserLibrary/UserLibraryService.cs b/MediaBrowser.Api/UserLibrary/UserLibraryService.cs
index 2ec08f578..7fa750adb 100644
--- a/MediaBrowser.Api/UserLibrary/UserLibraryService.cs
+++ b/MediaBrowser.Api/UserLibrary/UserLibraryService.cs
@@ -361,7 +361,8 @@ namespace MediaBrowser.Api.UserLibrary
var dtoOptions = GetDtoOptions(_authContext, request);
- var dtos = item.GetDisplayExtras()
+ var dtos = item
+ .GetExtras(BaseItem.DisplayExtraTypes)
.Select(i => _dtoService.GetBaseItemDto(i, dtoOptions, user, item));
return dtos.ToArray();
diff --git a/MediaBrowser.Api/UserLibrary/YearsService.cs b/MediaBrowser.Api/UserLibrary/YearsService.cs
index 07b9aff1b..d023ee90a 100644
--- a/MediaBrowser.Api/UserLibrary/YearsService.cs
+++ b/MediaBrowser.Api/UserLibrary/YearsService.cs
@@ -6,7 +6,6 @@ using MediaBrowser.Controller.Dto;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Net;
-using MediaBrowser.Controller.Persistence;
using MediaBrowser.Model.Dto;
using MediaBrowser.Model.Services;
using Microsoft.Extensions.Logging;
diff --git a/MediaBrowser.Api/UserService.cs b/MediaBrowser.Api/UserService.cs
index e1b01b012..78fc6c694 100644
--- a/MediaBrowser.Api/UserService.cs
+++ b/MediaBrowser.Api/UserService.cs
@@ -240,7 +240,7 @@ namespace MediaBrowser.Api
public class UserService : BaseApiService
{
/// <summary>
- /// The _user manager
+ /// The user manager.
/// </summary>
private readonly IUserManager _userManager;
private readonly ISessionManager _sessionMananger;
@@ -281,7 +281,6 @@ namespace MediaBrowser.Api
{
IsHidden = false,
IsDisabled = false
-
}, true, true);
}
@@ -395,10 +394,11 @@ namespace MediaBrowser.Api
throw new MethodNotAllowedException("Hashed-only passwords are not valid for this API.");
}
+ // Password should always be null
return Post(new AuthenticateUserByName
{
Username = user.Name,
- Password = null, // This should always be null
+ Password = null,
Pw = request.Pw
});
}
@@ -426,7 +426,7 @@ namespace MediaBrowser.Api
catch (SecurityException e)
{
// rethrow adding IP address to message
- throw new SecurityException($"[{Request.RemoteIp}] {e.Message}");
+ throw new SecurityException($"[{Request.RemoteIp}] {e.Message}", e);
}
}
diff --git a/MediaBrowser.Api/VideosService.cs b/MediaBrowser.Api/VideosService.cs
index 46b6d5a94..b11fd48d3 100644
--- a/MediaBrowser.Api/VideosService.cs
+++ b/MediaBrowser.Api/VideosService.cs
@@ -139,17 +139,11 @@ namespace MediaBrowser.Api
.ToList();
var primaryVersion = videosWithVersions.FirstOrDefault();
-
if (primaryVersion == null)
{
primaryVersion = items.OrderBy(i =>
{
- if (i.Video3DFormat.HasValue)
- {
- return 1;
- }
-
- if (i.VideoType != Model.Entities.VideoType.VideoFile)
+ if (i.Video3DFormat.HasValue || i.VideoType != Model.Entities.VideoType.VideoFile)
{
return 1;
}
@@ -158,10 +152,7 @@ namespace MediaBrowser.Api
})
.ThenByDescending(i =>
{
- var stream = i.GetDefaultVideoStream();
-
- return stream == null || stream.Width == null ? 0 : stream.Width.Value;
-
+ return i.GetDefaultVideoStream()?.Width ?? 0;
}).First();
}
diff --git a/MediaBrowser.Common/Configuration/ConfigurationUpdateEventArgs.cs b/MediaBrowser.Common/Configuration/ConfigurationUpdateEventArgs.cs
index 828415c18..344aecf53 100644
--- a/MediaBrowser.Common/Configuration/ConfigurationUpdateEventArgs.cs
+++ b/MediaBrowser.Common/Configuration/ConfigurationUpdateEventArgs.cs
@@ -1,5 +1,4 @@
#pragma warning disable CS1591
-#pragma warning disable SA1600
using System;
diff --git a/MediaBrowser.Common/Configuration/EncodingConfigurationExtensions.cs b/MediaBrowser.Common/Configuration/EncodingConfigurationExtensions.cs
index ccf965898..89740ae08 100644
--- a/MediaBrowser.Common/Configuration/EncodingConfigurationExtensions.cs
+++ b/MediaBrowser.Common/Configuration/EncodingConfigurationExtensions.cs
@@ -1,3 +1,4 @@
+using System;
using System.IO;
using MediaBrowser.Model.Configuration;
@@ -17,18 +18,25 @@ namespace MediaBrowser.Common.Configuration
=> configurationManager.GetConfiguration<EncodingOptions>("encoding");
/// <summary>
- /// Retrieves the transcoding temp path from the encoding configuration.
+ /// Retrieves the transcoding temp path from the encoding configuration, falling back to a default if no path
+ /// is specified in configuration. If the directory does not exist, it will be created.
/// </summary>
- /// <param name="configurationManager">The Configuration manager.</param>
+ /// <param name="configurationManager">The configuration manager.</param>
/// <returns>The transcoding temp path.</returns>
+ /// <exception cref="UnauthorizedAccessException">If the directory does not exist, and the caller does not have the required permission to create it.</exception>
+ /// <exception cref="NotSupportedException">If there is a custom path transcoding path specified, but it is invalid.</exception>
+ /// <exception cref="IOException">If the directory does not exist, and it also could not be created.</exception>
public static string GetTranscodePath(this IConfigurationManager configurationManager)
{
+ // Get the configured path and fall back to a default
var transcodingTempPath = configurationManager.GetEncodingOptions().TranscodingTempPath;
if (string.IsNullOrEmpty(transcodingTempPath))
{
- return Path.Combine(configurationManager.CommonApplicationPaths.ProgramDataPath, "transcodes");
+ transcodingTempPath = Path.Combine(configurationManager.CommonApplicationPaths.ProgramDataPath, "transcodes");
}
+ // Make sure the directory exists
+ Directory.CreateDirectory(transcodingTempPath);
return transcodingTempPath;
}
}
diff --git a/MediaBrowser.Common/Configuration/IApplicationPaths.cs b/MediaBrowser.Common/Configuration/IApplicationPaths.cs
index 5bdea7d8b..870b90796 100644
--- a/MediaBrowser.Common/Configuration/IApplicationPaths.cs
+++ b/MediaBrowser.Common/Configuration/IApplicationPaths.cs
@@ -1,3 +1,5 @@
+using MediaBrowser.Model.Configuration;
+
namespace MediaBrowser.Common.Configuration
{
/// <summary>
@@ -12,9 +14,12 @@ namespace MediaBrowser.Common.Configuration
string ProgramDataPath { get; }
/// <summary>
- /// Gets the path to the web UI resources folder
+ /// Gets the path to the web UI resources folder.
/// </summary>
- /// <value>The web UI resources path.</value>
+ /// <remarks>
+ /// This value is not relevant if the server is configured to not host any static web content. Additionally,
+ /// the value for <see cref="ServerConfiguration.DashboardSourcePath"/> takes precedence over this one.
+ /// </remarks>
string WebPath { get; }
/// <summary>
diff --git a/MediaBrowser.Common/Configuration/IConfigurationFactory.cs b/MediaBrowser.Common/Configuration/IConfigurationFactory.cs
index 9b4ed772d..07ca2b58b 100644
--- a/MediaBrowser.Common/Configuration/IConfigurationFactory.cs
+++ b/MediaBrowser.Common/Configuration/IConfigurationFactory.cs
@@ -1,25 +1,47 @@
-#pragma warning disable CS1591
-#pragma warning disable SA1600
-
using System;
using System.Collections.Generic;
namespace MediaBrowser.Common.Configuration
{
+ /// <summary>
+ /// Provides an interface to retrieve a configuration store. Classes with this interface are scanned for at
+ /// application start to dynamically register configuration for various modules/plugins.
+ /// </summary>
public interface IConfigurationFactory
{
+ /// <summary>
+ /// Get the configuration store for this module.
+ /// </summary>
+ /// <returns>The configuration store.</returns>
IEnumerable<ConfigurationStore> GetConfigurations();
}
+ /// <summary>
+ /// Describes a single entry in the application configuration.
+ /// </summary>
public class ConfigurationStore
{
+ /// <summary>
+ /// Gets or sets the unique identifier for the configuration.
+ /// </summary>
public string Key { get; set; }
+ /// <summary>
+ /// Gets or sets the type used to store the data for this configuration entry.
+ /// </summary>
public Type ConfigurationType { get; set; }
}
+ /// <summary>
+ /// A configuration store that can be validated.
+ /// </summary>
public interface IValidatingConfiguration
{
+ /// <summary>
+ /// Validation method to be invoked before saving the configuration.
+ /// </summary>
+ /// <param name="oldConfig">The old configuration.</param>
+ /// <param name="newConfig">The new configuration.</param>
void Validate(object oldConfig, object newConfig);
}
}
diff --git a/MediaBrowser.Common/Configuration/IConfigurationManager.cs b/MediaBrowser.Common/Configuration/IConfigurationManager.cs
index 7773596af..caf2edd83 100644
--- a/MediaBrowser.Common/Configuration/IConfigurationManager.cs
+++ b/MediaBrowser.Common/Configuration/IConfigurationManager.cs
@@ -1,5 +1,4 @@
#pragma warning disable CS1591
-#pragma warning disable SA1600
using System;
using System.Collections.Generic;
diff --git a/MediaBrowser.Common/Cryptography/Extensions.cs b/MediaBrowser.Common/Cryptography/CryptoExtensions.cs
index 1e32a6d1a..157b0ed10 100644
--- a/MediaBrowser.Common/Cryptography/Extensions.cs
+++ b/MediaBrowser.Common/Cryptography/CryptoExtensions.cs
@@ -9,7 +9,7 @@ namespace MediaBrowser.Common.Cryptography
/// <summary>
/// Class containing extension methods for working with Jellyfin cryptography objects.
/// </summary>
- public static class Extensions
+ public static class CryptoExtensions
{
/// <summary>
/// Creates a new <see cref="PasswordHash" /> instance.
diff --git a/MediaBrowser.Common/Cryptography/PasswordHash.cs b/MediaBrowser.Common/Cryptography/PasswordHash.cs
index 3477c1c04..3e12536ec 100644
--- a/MediaBrowser.Common/Cryptography/PasswordHash.cs
+++ b/MediaBrowser.Common/Cryptography/PasswordHash.cs
@@ -1,5 +1,4 @@
#pragma warning disable CS1591
-#pragma warning disable SA1600
using System;
using System.Collections.Generic;
diff --git a/MediaBrowser.Common/Extensions/BaseExtensions.cs b/MediaBrowser.Common/Extensions/BaseExtensions.cs
index 08964420e..40020093b 100644
--- a/MediaBrowser.Common/Extensions/BaseExtensions.cs
+++ b/MediaBrowser.Common/Extensions/BaseExtensions.cs
@@ -1,3 +1,5 @@
+#nullable enable
+
using System;
using System.Security.Cryptography;
using System.Text;
diff --git a/MediaBrowser.Common/Extensions/CopyToExtensions.cs b/MediaBrowser.Common/Extensions/CopyToExtensions.cs
index 2ecbc6539..94bf7c740 100644
--- a/MediaBrowser.Common/Extensions/CopyToExtensions.cs
+++ b/MediaBrowser.Common/Extensions/CopyToExtensions.cs
@@ -1,3 +1,5 @@
+#nullable enable
+
using System.Collections.Generic;
namespace MediaBrowser.Common.Extensions
diff --git a/MediaBrowser.Common/Extensions/MethodNotAllowedException.cs b/MediaBrowser.Common/Extensions/MethodNotAllowedException.cs
index 48e758ee4..258bd6662 100644
--- a/MediaBrowser.Common/Extensions/MethodNotAllowedException.cs
+++ b/MediaBrowser.Common/Extensions/MethodNotAllowedException.cs
@@ -1,3 +1,5 @@
+#nullable enable
+
using System;
namespace MediaBrowser.Common.Extensions
diff --git a/MediaBrowser.Common/Extensions/ProcessExtensions.cs b/MediaBrowser.Common/Extensions/ProcessExtensions.cs
new file mode 100644
index 000000000..2f52ba196
--- /dev/null
+++ b/MediaBrowser.Common/Extensions/ProcessExtensions.cs
@@ -0,0 +1,82 @@
+#nullable enable
+
+using System;
+using System.Diagnostics;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace MediaBrowser.Common.Extensions
+{
+ /// <summary>
+ /// Extension methods for <see cref="Process"/>.
+ /// </summary>
+ public static class ProcessExtensions
+ {
+ /// <summary>
+ /// Asynchronously wait for the process to exit.
+ /// </summary>
+ /// <param name="process">The process to wait for.</param>
+ /// <param name="timeout">The duration to wait before cancelling waiting for the task.</param>
+ /// <returns>True if the task exited normally, false if the timeout elapsed before the process exited.</returns>
+ /// <exception cref="InvalidOperationException">If <see cref="Process.EnableRaisingEvents"/> is not set to true for the process.</exception>
+ public static async Task<bool> WaitForExitAsync(this Process process, TimeSpan timeout)
+ {
+ using (var cancelTokenSource = new CancellationTokenSource(timeout))
+ {
+ return await WaitForExitAsync(process, cancelTokenSource.Token).ConfigureAwait(false);
+ }
+ }
+
+ /// <summary>
+ /// Asynchronously wait for the process to exit.
+ /// </summary>
+ /// <param name="process">The process to wait for.</param>
+ /// <param name="cancelToken">A <see cref="CancellationToken"/> to observe while waiting for the process to exit.</param>
+ /// <returns>True if the task exited normally, false if cancelled before the process exited.</returns>
+ public static async Task<bool> WaitForExitAsync(this Process process, CancellationToken cancelToken)
+ {
+ if (!process.EnableRaisingEvents)
+ {
+ throw new InvalidOperationException("EnableRisingEvents must be enabled to async wait for a task to exit.");
+ }
+
+ // Add an event handler for the process exit event
+ var tcs = new TaskCompletionSource<bool>();
+ process.Exited += (sender, args) => tcs.TrySetResult(true);
+
+ // Return immediately if the process has already exited
+ if (process.HasExitedSafe())
+ {
+ return true;
+ }
+
+ // Register with the cancellation token then await
+ using (var cancelRegistration = cancelToken.Register(() => tcs.TrySetResult(process.HasExitedSafe())))
+ {
+ return await tcs.Task.ConfigureAwait(false);
+ }
+ }
+
+ /// <summary>
+ /// Gets a value indicating whether the associated process has been terminated using
+ /// <see cref="Process.HasExited"/>. This is safe to call even if there is no operating system process
+ /// associated with the <see cref="Process"/>.
+ /// </summary>
+ /// <param name="process">The process to check the exit status for.</param>
+ /// <returns>
+ /// True if the operating system process referenced by the <see cref="Process"/> component has
+ /// terminated, or if there is no associated operating system process; otherwise, false.
+ /// </returns>
+ private static bool HasExitedSafe(this Process process)
+ {
+ try
+ {
+ return process.HasExited;
+ }
+ catch (InvalidOperationException)
+ {
+ return true;
+ }
+ }
+ }
+}
diff --git a/MediaBrowser.Common/Extensions/RateLimitExceededException.cs b/MediaBrowser.Common/Extensions/RateLimitExceededException.cs
index 4e5d4e9ca..7c7bdaa92 100644
--- a/MediaBrowser.Common/Extensions/RateLimitExceededException.cs
+++ b/MediaBrowser.Common/Extensions/RateLimitExceededException.cs
@@ -1,5 +1,5 @@
+#nullable enable
#pragma warning disable CS1591
-#pragma warning disable SA1600
using System;
diff --git a/MediaBrowser.Common/Extensions/ResourceNotFoundException.cs b/MediaBrowser.Common/Extensions/ResourceNotFoundException.cs
index 22130c5a1..ebac9d8e6 100644
--- a/MediaBrowser.Common/Extensions/ResourceNotFoundException.cs
+++ b/MediaBrowser.Common/Extensions/ResourceNotFoundException.cs
@@ -1,3 +1,5 @@
+#nullable enable
+
using System;
namespace MediaBrowser.Common.Extensions
diff --git a/MediaBrowser.Common/Extensions/ShuffleExtensions.cs b/MediaBrowser.Common/Extensions/ShuffleExtensions.cs
index 5889d09c4..459bec110 100644
--- a/MediaBrowser.Common/Extensions/ShuffleExtensions.cs
+++ b/MediaBrowser.Common/Extensions/ShuffleExtensions.cs
@@ -1,3 +1,5 @@
+#nullable enable
+
using System;
using System.Collections.Generic;
@@ -17,11 +19,22 @@ namespace MediaBrowser.Common.Extensions
/// <typeparam name="T">The type.</typeparam>
public static void Shuffle<T>(this IList<T> list)
{
+ list.Shuffle(_rng);
+ }
+
+ /// <summary>
+ /// Shuffles the items in a list.
+ /// </summary>
+ /// <param name="list">The list that should get shuffled.</param>
+ /// <param name="rng">The random number generator to use.</param>
+ /// <typeparam name="T">The type.</typeparam>
+ public static void Shuffle<T>(this IList<T> list, Random rng)
+ {
int n = list.Count;
while (n > 1)
{
n--;
- int k = _rng.Next(n + 1);
+ int k = rng.Next(n + 1);
T value = list[k];
list[k] = list[n];
list[n] = value;
diff --git a/MediaBrowser.Common/Extensions/StringExtensions.cs b/MediaBrowser.Common/Extensions/StringExtensions.cs
new file mode 100644
index 000000000..764301741
--- /dev/null
+++ b/MediaBrowser.Common/Extensions/StringExtensions.cs
@@ -0,0 +1,37 @@
+#nullable enable
+
+using System;
+
+namespace MediaBrowser.Common.Extensions
+{
+ /// <summary>
+ /// Extensions methods to simplify string operations.
+ /// </summary>
+ public static class StringExtensions
+ {
+ /// <summary>
+ /// Returns the part on the left of the <c>needle</c>.
+ /// </summary>
+ /// <param name="haystack">The string to seek.</param>
+ /// <param name="needle">The needle to find.</param>
+ /// <returns>The part left of the <paramref name="needle" />.</returns>
+ public static ReadOnlySpan<char> LeftPart(this ReadOnlySpan<char> haystack, char needle)
+ {
+ var pos = haystack.IndexOf(needle);
+ return pos == -1 ? haystack : haystack[..pos];
+ }
+
+ /// <summary>
+ /// Returns the part on the left of the <c>needle</c>.
+ /// </summary>
+ /// <param name="haystack">The string to seek.</param>
+ /// <param name="needle">The needle to find.</param>
+ /// <param name="stringComparison">One of the enumeration values that specifies the rules for the search.</param>
+ /// <returns>The part left of the <c>needle</c>.</returns>
+ public static ReadOnlySpan<char> LeftPart(this ReadOnlySpan<char> haystack, ReadOnlySpan<char> needle, StringComparison stringComparison = default)
+ {
+ var pos = haystack.IndexOf(needle, stringComparison);
+ return pos == -1 ? haystack : haystack[..pos];
+ }
+ }
+}
diff --git a/MediaBrowser.Common/IApplicationHost.cs b/MediaBrowser.Common/IApplicationHost.cs
index 68a24aaba..e8d9282e4 100644
--- a/MediaBrowser.Common/IApplicationHost.cs
+++ b/MediaBrowser.Common/IApplicationHost.cs
@@ -2,7 +2,6 @@ using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using MediaBrowser.Common.Plugins;
-using MediaBrowser.Model.Updates;
using Microsoft.Extensions.DependencyInjection;
namespace MediaBrowser.Common
@@ -48,12 +47,6 @@ namespace MediaBrowser.Common
bool CanSelfRestart { get; }
/// <summary>
- /// Gets the version class of the system.
- /// </summary>
- /// <value><see cref="PackageVersionClass.Release" /> or <see cref="PackageVersionClass.Beta" />.</value>
- PackageVersionClass SystemUpdateLevel { get; }
-
- /// <summary>
/// Gets the application version.
/// </summary>
/// <value>The application version.</value>
@@ -121,11 +114,10 @@ namespace MediaBrowser.Common
void RemovePlugin(IPlugin plugin);
/// <summary>
- /// Inits this instance.
+ /// Initializes this instance.
/// </summary>
/// <param name="serviceCollection">The service collection.</param>
- /// <returns>A task.</returns>
- Task InitAsync(IServiceCollection serviceCollection);
+ void Init(IServiceCollection serviceCollection);
/// <summary>
/// Creates the instance.
diff --git a/MediaBrowser.Common/MediaBrowser.Common.csproj b/MediaBrowser.Common/MediaBrowser.Common.csproj
index 567fcdda1..69864106c 100644
--- a/MediaBrowser.Common/MediaBrowser.Common.csproj
+++ b/MediaBrowser.Common/MediaBrowser.Common.csproj
@@ -1,5 +1,10 @@
<Project Sdk="Microsoft.NET.Sdk">
+ <!-- ProjectGuid is only included as a requirement for SonarQube analysis -->
+ <PropertyGroup>
+ <ProjectGuid>{9142EEFA-7570-41E1-BFCC-468BB571AF2F}</ProjectGuid>
+ </PropertyGroup>
+
<PropertyGroup>
<Authors>Jellyfin Contributors</Authors>
<PackageId>Jellyfin.Common</PackageId>
@@ -12,7 +17,8 @@
</ItemGroup>
<ItemGroup>
- <PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="3.1.0" />
+ <PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="3.1.3" />
+ <PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="3.1.3" />
<PackageReference Include="Microsoft.Net.Http.Headers" Version="2.2.8" />
</ItemGroup>
@@ -29,7 +35,7 @@
<!-- Code analyzers-->
<ItemGroup Condition=" '$(Configuration)' == 'Debug' ">
- <!-- <PackageReference Include="Microsoft.CodeAnalysis.FxCopAnalyzers" Version="2.9.8" PrivateAssets="All" /> -->
+ <PackageReference Include="Microsoft.CodeAnalysis.FxCopAnalyzers" Version="2.9.8" PrivateAssets="All" />
<PackageReference Include="SerilogAnalyzer" Version="0.15.0" PrivateAssets="All" />
<!-- <PackageReference Include="StyleCop.Analyzers" Version="1.1.118" PrivateAssets="All" /> -->
<PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" PrivateAssets="All" />
diff --git a/MediaBrowser.Common/Net/CustomHeaderNames.cs b/MediaBrowser.Common/Net/CustomHeaderNames.cs
index 8cc48c55f..5ca9897eb 100644
--- a/MediaBrowser.Common/Net/CustomHeaderNames.cs
+++ b/MediaBrowser.Common/Net/CustomHeaderNames.cs
@@ -1,5 +1,4 @@
#pragma warning disable CS1591
-#pragma warning disable SA1600
namespace MediaBrowser.Common.Net
{
diff --git a/MediaBrowser.Common/Net/HttpRequestOptions.cs b/MediaBrowser.Common/Net/HttpRequestOptions.cs
index 8207a45f3..38274a80e 100644
--- a/MediaBrowser.Common/Net/HttpRequestOptions.cs
+++ b/MediaBrowser.Common/Net/HttpRequestOptions.cs
@@ -1,5 +1,4 @@
#pragma warning disable CS1591
-#pragma warning disable SA1600
using System;
using System.Collections.Generic;
@@ -21,7 +20,7 @@ namespace MediaBrowser.Common.Net
RequestHeaders = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
CacheMode = CacheMode.None;
- DecompressionMethod = CompressionMethod.Deflate;
+ DecompressionMethod = CompressionMethods.Deflate;
}
/// <summary>
@@ -30,7 +29,7 @@ namespace MediaBrowser.Common.Net
/// <value>The URL.</value>
public string Url { get; set; }
- public CompressionMethod DecompressionMethod { get; set; }
+ public CompressionMethods DecompressionMethod { get; set; }
/// <summary>
/// Gets or sets the accept header.
@@ -84,8 +83,6 @@ namespace MediaBrowser.Common.Net
public string RequestContent { get; set; }
- public byte[] RequestContentBytes { get; set; }
-
public bool BufferContent { get; set; }
public bool LogErrorResponseBody { get; set; }
@@ -113,7 +110,7 @@ namespace MediaBrowser.Common.Net
}
[Flags]
- public enum CompressionMethod
+ public enum CompressionMethods
{
None = 0b00000001,
Deflate = 0b00000010,
diff --git a/MediaBrowser.Common/Net/HttpResponseInfo.cs b/MediaBrowser.Common/Net/HttpResponseInfo.cs
index d711ad64a..d4fee6c78 100644
--- a/MediaBrowser.Common/Net/HttpResponseInfo.cs
+++ b/MediaBrowser.Common/Net/HttpResponseInfo.cs
@@ -8,10 +8,9 @@ namespace MediaBrowser.Common.Net
/// <summary>
/// Class HttpResponseInfo.
/// </summary>
- public class HttpResponseInfo : IDisposable
+ public sealed class HttpResponseInfo : IDisposable
{
#pragma warning disable CS1591
-#pragma warning disable SA1600
public HttpResponseInfo()
{
}
@@ -23,7 +22,6 @@ namespace MediaBrowser.Common.Net
}
#pragma warning restore CS1591
-#pragma warning restore SA1600
/// <summary>
/// Gets or sets the type of the content.
diff --git a/MediaBrowser.Common/Net/INetworkManager.cs b/MediaBrowser.Common/Net/INetworkManager.cs
index 6bd7dd1d6..3ba75abd8 100644
--- a/MediaBrowser.Common/Net/INetworkManager.cs
+++ b/MediaBrowser.Common/Net/INetworkManager.cs
@@ -1,5 +1,4 @@
#pragma warning disable CS1591
-#pragma warning disable SA1600
using System;
using System.Collections.Generic;
diff --git a/MediaBrowser.Common/Plugins/BasePlugin.cs b/MediaBrowser.Common/Plugins/BasePlugin.cs
index b24d10ff1..9e4a360c3 100644
--- a/MediaBrowser.Common/Plugins/BasePlugin.cs
+++ b/MediaBrowser.Common/Plugins/BasePlugin.cs
@@ -67,7 +67,7 @@ namespace MediaBrowser.Common.Plugins
}
/// <summary>
- /// Called when just before the plugin is uninstalled from the server.
+ /// Called just before the plugin is uninstalled from the server.
/// </summary>
public virtual void OnUninstalling()
{
@@ -101,7 +101,7 @@ namespace MediaBrowser.Common.Plugins
private readonly object _configurationSyncLock = new object();
/// <summary>
- /// The save lock.
+ /// The configuration save lock.
/// </summary>
private readonly object _configurationSaveLock = new object();
@@ -148,7 +148,7 @@ namespace MediaBrowser.Common.Plugins
protected string AssemblyFileName => Path.GetFileName(AssemblyFilePath);
/// <summary>
- /// Gets or sets the plugin's configuration.
+ /// Gets or sets the plugin configuration.
/// </summary>
/// <value>The configuration.</value>
public TConfigurationType Configuration
@@ -186,7 +186,7 @@ namespace MediaBrowser.Common.Plugins
public string ConfigurationFilePath => Path.Combine(ApplicationPaths.PluginConfigurationsPath, ConfigurationFileName);
/// <summary>
- /// Gets the plugin's configuration.
+ /// Gets the plugin configuration.
/// </summary>
/// <value>The configuration.</value>
BasePluginConfiguration IHasPluginConfiguration.Configuration => Configuration;
diff --git a/MediaBrowser.Common/Plugins/IPlugin.cs b/MediaBrowser.Common/Plugins/IPlugin.cs
index 001ca8be8..d34820961 100644
--- a/MediaBrowser.Common/Plugins/IPlugin.cs
+++ b/MediaBrowser.Common/Plugins/IPlugin.cs
@@ -1,5 +1,4 @@
#pragma warning disable CS1591
-#pragma warning disable SA1600
using System;
using MediaBrowser.Model.Plugins;
diff --git a/MediaBrowser.Common/Plugins/IPluginAssembly.cs b/MediaBrowser.Common/Plugins/IPluginAssembly.cs
index 388ac61ab..6df4fbb76 100644
--- a/MediaBrowser.Common/Plugins/IPluginAssembly.cs
+++ b/MediaBrowser.Common/Plugins/IPluginAssembly.cs
@@ -1,5 +1,4 @@
#pragma warning disable CS1591
-#pragma warning disable SA1600
using System;
diff --git a/MediaBrowser.Common/Progress/ActionableProgress.cs b/MediaBrowser.Common/Progress/ActionableProgress.cs
index 92141ba52..af69055aa 100644
--- a/MediaBrowser.Common/Progress/ActionableProgress.cs
+++ b/MediaBrowser.Common/Progress/ActionableProgress.cs
@@ -1,5 +1,4 @@
#pragma warning disable CS1591
-#pragma warning disable SA1600
using System;
diff --git a/MediaBrowser.Common/Providers/SubtitleConfigurationFactory.cs b/MediaBrowser.Common/Providers/SubtitleConfigurationFactory.cs
index a6422e2c8..0445397ad 100644
--- a/MediaBrowser.Common/Providers/SubtitleConfigurationFactory.cs
+++ b/MediaBrowser.Common/Providers/SubtitleConfigurationFactory.cs
@@ -1,5 +1,4 @@
#pragma warning disable CS1591
-#pragma warning disable SA1600
using System.Collections.Generic;
using MediaBrowser.Common.Configuration;
diff --git a/MediaBrowser.Common/System/OperatingSystem.cs b/MediaBrowser.Common/System/OperatingSystem.cs
index f23af4799..5f673d320 100644
--- a/MediaBrowser.Common/System/OperatingSystem.cs
+++ b/MediaBrowser.Common/System/OperatingSystem.cs
@@ -1,5 +1,4 @@
#pragma warning disable CS1591
-#pragma warning disable SA1600
using System;
using System.Runtime.InteropServices;
@@ -36,7 +35,7 @@ namespace MediaBrowser.Common.System
case OperatingSystemId.Linux: return "Linux";
case OperatingSystemId.Darwin: return "macOS";
case OperatingSystemId.Windows: return "Windows";
- default: throw new Exception($"Unknown OS {Id}");
+ default: throw new PlatformNotSupportedException($"Unknown OS {Id}");
}
}
}
@@ -54,20 +53,20 @@ namespace MediaBrowser.Common.System
default:
{
string osDescription = RuntimeInformation.OSDescription;
- if (osDescription.IndexOf("linux", StringComparison.OrdinalIgnoreCase) != -1)
+ if (osDescription.Contains("linux", StringComparison.OrdinalIgnoreCase))
{
return OperatingSystemId.Linux;
}
- else if (osDescription.IndexOf("darwin", StringComparison.OrdinalIgnoreCase) != -1)
+ else if (osDescription.Contains("darwin", StringComparison.OrdinalIgnoreCase))
{
return OperatingSystemId.Darwin;
}
- else if (osDescription.IndexOf("bsd", StringComparison.OrdinalIgnoreCase) != -1)
+ else if (osDescription.Contains("bsd", StringComparison.OrdinalIgnoreCase))
{
return OperatingSystemId.BSD;
}
- throw new Exception($"Can't resolve OS with description: '{osDescription}'");
+ throw new PlatformNotSupportedException($"Can't resolve OS with description: '{osDescription}'");
}
}
}
diff --git a/MediaBrowser.Common/Updates/IInstallationManager.cs b/MediaBrowser.Common/Updates/IInstallationManager.cs
index a09c1916c..950604432 100644
--- a/MediaBrowser.Common/Updates/IInstallationManager.cs
+++ b/MediaBrowser.Common/Updates/IInstallationManager.cs
@@ -1,5 +1,4 @@
#pragma warning disable CS1591
-#pragma warning disable SA1600
using System;
using System.Collections.Generic;
@@ -29,12 +28,12 @@ namespace MediaBrowser.Common.Updates
/// <summary>
/// Occurs when a plugin is updated.
/// </summary>
- event EventHandler<GenericEventArgs<(IPlugin, PackageVersionInfo)>> PluginUpdated;
+ event EventHandler<GenericEventArgs<(IPlugin, VersionInfo)>> PluginUpdated;
/// <summary>
/// Occurs when a plugin is installed.
/// </summary>
- event EventHandler<GenericEventArgs<PackageVersionInfo>> PluginInstalled;
+ event EventHandler<GenericEventArgs<VersionInfo>> PluginInstalled;
/// <summary>
/// Gets the completed installations.
@@ -65,12 +64,10 @@ namespace MediaBrowser.Common.Updates
/// </summary>
/// <param name="availableVersions">The available version of the plugin.</param>
/// <param name="minVersion">The minimum required version of the plugin.</param>
- /// <param name="classification">The classification of updates.</param>
/// <returns>All compatible versions ordered from newest to oldest.</returns>
- IEnumerable<PackageVersionInfo> GetCompatibleVersions(
- IEnumerable<PackageVersionInfo> availableVersions,
- Version minVersion = null,
- PackageVersionClass classification = PackageVersionClass.Release);
+ IEnumerable<VersionInfo> GetCompatibleVersions(
+ IEnumerable<VersionInfo> availableVersions,
+ Version minVersion = null);
/// <summary>
/// Returns all compatible versions ordered from newest to oldest.
@@ -79,21 +76,19 @@ namespace MediaBrowser.Common.Updates
/// <param name="name">The name.</param>
/// <param name="guid">The guid of the plugin.</param>
/// <param name="minVersion">The minimum required version of the plugin.</param>
- /// <param name="classification">The classification.</param>
/// <returns>All compatible versions ordered from newest to oldest.</returns>
- IEnumerable<PackageVersionInfo> GetCompatibleVersions(
+ IEnumerable<VersionInfo> GetCompatibleVersions(
IEnumerable<PackageInfo> availablePackages,
string name = null,
Guid guid = default,
- Version minVersion = null,
- PackageVersionClass classification = PackageVersionClass.Release);
+ Version minVersion = null);
/// <summary>
/// Returns the available plugin updates.
/// </summary>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>The available plugin updates.</returns>
- IAsyncEnumerable<PackageVersionInfo> GetAvailablePluginUpdates(CancellationToken cancellationToken = default);
+ Task<IEnumerable<VersionInfo>> GetAvailablePluginUpdates(CancellationToken cancellationToken = default);
/// <summary>
/// Installs the package.
@@ -101,7 +96,7 @@ namespace MediaBrowser.Common.Updates
/// <param name="package">The package.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns><see cref="Task" />.</returns>
- Task InstallPackage(PackageVersionInfo package, CancellationToken cancellationToken = default);
+ Task InstallPackage(VersionInfo package, CancellationToken cancellationToken = default);
/// <summary>
/// Uninstalls a plugin.
diff --git a/MediaBrowser.Common/Updates/InstallationEventArgs.cs b/MediaBrowser.Common/Updates/InstallationEventArgs.cs
index 8bbb231ce..11eb2ad34 100644
--- a/MediaBrowser.Common/Updates/InstallationEventArgs.cs
+++ b/MediaBrowser.Common/Updates/InstallationEventArgs.cs
@@ -1,5 +1,4 @@
#pragma warning disable CS1591
-#pragma warning disable SA1600
using MediaBrowser.Model.Updates;
@@ -9,6 +8,6 @@ namespace MediaBrowser.Common.Updates
{
public InstallationInfo InstallationInfo { get; set; }
- public PackageVersionInfo PackageVersionInfo { get; set; }
+ public VersionInfo VersionInfo { get; set; }
}
}
diff --git a/MediaBrowser.Common/Updates/InstallationFailedEventArgs.cs b/MediaBrowser.Common/Updates/InstallationFailedEventArgs.cs
index c8967f9db..46f10c84f 100644
--- a/MediaBrowser.Common/Updates/InstallationFailedEventArgs.cs
+++ b/MediaBrowser.Common/Updates/InstallationFailedEventArgs.cs
@@ -1,5 +1,4 @@
#pragma warning disable CS1591
-#pragma warning disable SA1600
using System;
diff --git a/MediaBrowser.Controller/Authentication/AuthenticationException.cs b/MediaBrowser.Controller/Authentication/AuthenticationException.cs
index 62eca3ea9..081f877f7 100644
--- a/MediaBrowser.Controller/Authentication/AuthenticationException.cs
+++ b/MediaBrowser.Controller/Authentication/AuthenticationException.cs
@@ -7,23 +7,29 @@ namespace MediaBrowser.Controller.Authentication
/// </summary>
public class AuthenticationException : Exception
{
- /// <inheritdoc />
+ /// <summary>
+ /// Initializes a new instance of the <see cref="AuthenticationException"/> class.
+ /// </summary>
public AuthenticationException() : base()
{
-
}
- /// <inheritdoc />
+ /// <summary>
+ /// Initializes a new instance of the <see cref="AuthenticationException"/> class.
+ /// </summary>
+ /// <param name="message">The message that describes the error.</param>
public AuthenticationException(string message) : base(message)
{
-
}
- /// <inheritdoc />
+ /// <summary>
+ /// Initializes a new instance of the <see cref="AuthenticationException"/> class.
+ /// </summary>
+ /// <param name="message">The message that describes the error.</param>
+ /// <param name="innerException">The exception that is the cause of the current exception, or a null reference if no inner exception is specified.</param>
public AuthenticationException(string message, Exception innerException)
: base(message, innerException)
{
-
}
}
}
diff --git a/MediaBrowser.Controller/Authentication/AuthenticationResult.cs b/MediaBrowser.Controller/Authentication/AuthenticationResult.cs
index 5248ea4c1..4249a9a66 100644
--- a/MediaBrowser.Controller/Authentication/AuthenticationResult.cs
+++ b/MediaBrowser.Controller/Authentication/AuthenticationResult.cs
@@ -1,5 +1,4 @@
#pragma warning disable CS1591
-#pragma warning disable SA1600
using MediaBrowser.Controller.Session;
using MediaBrowser.Model.Dto;
diff --git a/MediaBrowser.Controller/Channels/IChannel.cs b/MediaBrowser.Controller/Channels/IChannel.cs
index f8ed98a45..c44e20d1a 100644
--- a/MediaBrowser.Controller/Channels/IChannel.cs
+++ b/MediaBrowser.Controller/Channels/IChannel.cs
@@ -64,7 +64,7 @@ namespace MediaBrowser.Controller.Channels
/// </summary>
/// <param name="type">The type.</param>
/// <param name="cancellationToken">The cancellation token.</param>
- /// <returns>Task{DynamicImageInfo}.</returns>
+ /// <returns>Task{DynamicImageResponse}.</returns>
Task<DynamicImageResponse> GetChannelImage(ImageType type, CancellationToken cancellationToken);
/// <summary>
diff --git a/MediaBrowser.Controller/Chapters/IChapterManager.cs b/MediaBrowser.Controller/Chapters/IChapterManager.cs
index d061898a1..f82e5b41a 100644
--- a/MediaBrowser.Controller/Chapters/IChapterManager.cs
+++ b/MediaBrowser.Controller/Chapters/IChapterManager.cs
@@ -1,16 +1,17 @@
+using System;
using System.Collections.Generic;
using MediaBrowser.Model.Entities;
namespace MediaBrowser.Controller.Chapters
{
/// <summary>
- /// Interface IChapterManager
+ /// Interface IChapterManager.
/// </summary>
public interface IChapterManager
{
/// <summary>
/// Saves the chapters.
/// </summary>
- void SaveChapters(string itemId, List<ChapterInfo> chapters);
+ void SaveChapters(Guid itemId, IReadOnlyList<ChapterInfo> chapters);
}
}
diff --git a/MediaBrowser.Controller/Drawing/IImageProcessor.cs b/MediaBrowser.Controller/Drawing/IImageProcessor.cs
index a58a11bd1..36c746624 100644
--- a/MediaBrowser.Controller/Drawing/IImageProcessor.cs
+++ b/MediaBrowser.Controller/Drawing/IImageProcessor.cs
@@ -3,7 +3,6 @@ using System.Collections.Generic;
using System.IO;
using System.Threading.Tasks;
using MediaBrowser.Controller.Entities;
-using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Drawing;
using MediaBrowser.Model.Entities;
@@ -21,19 +20,11 @@ namespace MediaBrowser.Controller.Drawing
IReadOnlyCollection<string> SupportedInputFormats { get; }
/// <summary>
- /// Gets the image enhancers.
- /// </summary>
- /// <value>The image enhancers.</value>
- IReadOnlyCollection<IImageEnhancer> ImageEnhancers { get; set; }
-
- /// <summary>
/// Gets a value indicating whether [supports image collage creation].
/// </summary>
/// <value><c>true</c> if [supports image collage creation]; otherwise, <c>false</c>.</value>
bool SupportsImageCollageCreation { get; }
- IImageEncoder ImageEncoder { get; set; }
-
/// <summary>
/// Gets the dimensions of the image.
/// </summary>
@@ -50,23 +41,6 @@ namespace MediaBrowser.Controller.Drawing
ImageDimensions GetImageDimensions(BaseItem item, ItemImageInfo info);
/// <summary>
- /// Gets the dimensions of the image.
- /// </summary>
- /// <param name="item">The base item.</param>
- /// <param name="info">The information.</param>
- /// <param name="updateItem">Whether or not the item info should be updated.</param>
- /// <returns>ImageDimensions</returns>
- ImageDimensions GetImageDimensions(BaseItem item, ItemImageInfo info, bool updateItem);
-
- /// <summary>
- /// Gets the supported enhancers.
- /// </summary>
- /// <param name="item">The item.</param>
- /// <param name="imageType">Type of the image.</param>
- /// <returns>IEnumerable{IImageEnhancer}.</returns>
- IEnumerable<IImageEnhancer> GetSupportedEnhancers(BaseItem item, ImageType imageType);
-
- /// <summary>
/// Gets the image cache tag.
/// </summary>
/// <param name="item">The item.</param>
@@ -76,15 +50,6 @@ namespace MediaBrowser.Controller.Drawing
string GetImageCacheTag(BaseItem item, ChapterInfo info);
/// <summary>
- /// Gets the image cache tag.
- /// </summary>
- /// <param name="item">The item.</param>
- /// <param name="image">The image.</param>
- /// <param name="imageEnhancers">The image enhancers.</param>
- /// <returns>Guid.</returns>
- string GetImageCacheTag(BaseItem item, ItemImageInfo image, IReadOnlyCollection<IImageEnhancer> imageEnhancers);
-
- /// <summary>
/// Processes the image.
/// </summary>
/// <param name="options">The options.</param>
@@ -100,15 +65,6 @@ namespace MediaBrowser.Controller.Drawing
Task<(string path, string mimeType, DateTime dateModified)> ProcessImage(ImageProcessingOptions options);
/// <summary>
- /// Gets the enhanced image.
- /// </summary>
- /// <param name="item">The item.</param>
- /// <param name="imageType">Type of the image.</param>
- /// <param name="imageIndex">Index of the image.</param>
- /// <returns>Task{System.String}.</returns>
- Task<string> GetEnhancedImage(BaseItem item, ImageType imageType, int imageIndex);
-
- /// <summary>
/// Gets the supported image output formats.
/// </summary>
/// <returns><see cref="IReadOnlyCollection{ImageOutput}" />.</returns>
diff --git a/MediaBrowser.Controller/Drawing/ImageHelper.cs b/MediaBrowser.Controller/Drawing/ImageHelper.cs
index 432cf8042..d5a5f547e 100644
--- a/MediaBrowser.Controller/Drawing/ImageHelper.cs
+++ b/MediaBrowser.Controller/Drawing/ImageHelper.cs
@@ -19,8 +19,6 @@ namespace MediaBrowser.Controller.Drawing
return GetSizeEstimate(options);
}
- public static IImageProcessor ImageProcessor { get; set; }
-
private static ImageDimensions GetSizeEstimate(ImageProcessingOptions options)
{
if (options.Width.HasValue && options.Height.HasValue)
diff --git a/MediaBrowser.Controller/Drawing/ImageProcessingOptions.cs b/MediaBrowser.Controller/Drawing/ImageProcessingOptions.cs
index 29addf6e6..870e0278e 100644
--- a/MediaBrowser.Controller/Drawing/ImageProcessingOptions.cs
+++ b/MediaBrowser.Controller/Drawing/ImageProcessingOptions.cs
@@ -3,7 +3,6 @@ using System.Collections.Generic;
using System.IO;
using System.Linq;
using MediaBrowser.Controller.Entities;
-using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Drawing;
namespace MediaBrowser.Controller.Drawing
@@ -34,8 +33,6 @@ namespace MediaBrowser.Controller.Drawing
public int Quality { get; set; }
- public IReadOnlyCollection<IImageEnhancer> Enhancers { get; set; }
-
public IReadOnlyCollection<ImageFormat> SupportedOutputFormats { get; set; }
public bool AddPlayedIndicator { get; set; }
diff --git a/MediaBrowser.Controller/Entities/Audio/MusicArtist.cs b/MediaBrowser.Controller/Entities/Audio/MusicArtist.cs
index efe0d3cf7..5e3056ccb 100644
--- a/MediaBrowser.Controller/Entities/Audio/MusicArtist.cs
+++ b/MediaBrowser.Controller/Entities/Audio/MusicArtist.cs
@@ -198,6 +198,7 @@ namespace MediaBrowser.Controller.Entities.Audio
return true;
}
}
+
return base.RequiresRefresh();
}
diff --git a/MediaBrowser.Controller/Entities/BaseItem.cs b/MediaBrowser.Controller/Entities/BaseItem.cs
index 353c675cb..7ed8fa767 100644
--- a/MediaBrowser.Controller/Entities/BaseItem.cs
+++ b/MediaBrowser.Controller/Entities/BaseItem.cs
@@ -15,7 +15,6 @@ using MediaBrowser.Controller.Extensions;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Persistence;
using MediaBrowser.Controller.Providers;
-using MediaBrowser.Controller.Sorting;
using MediaBrowser.Model.Configuration;
using MediaBrowser.Model.Dto;
using MediaBrowser.Model.Entities;
@@ -33,7 +32,7 @@ namespace MediaBrowser.Controller.Entities
/// <summary>
/// Class BaseItem
/// </summary>
- public abstract class BaseItem : IHasProviderIds, IHasLookupInfo<ItemLookupInfo>
+ public abstract class BaseItem : IHasProviderIds, IHasLookupInfo<ItemLookupInfo>, IEquatable<BaseItem>
{
/// <summary>
/// The supported image extensions
@@ -178,6 +177,7 @@ namespace MediaBrowser.Controller.Entities
[JsonIgnore]
public int? TotalBitrate { get; set; }
+
[JsonIgnore]
public ExtraType? ExtraType { get; set; }
@@ -387,15 +387,12 @@ namespace MediaBrowser.Controller.Entities
while (thisMarker < s1.Length)
{
- if (thisMarker >= s1.Length)
- {
- break;
- }
char thisCh = s1[thisMarker];
var thisChunk = new StringBuilder();
+ bool isNumeric = char.IsDigit(thisCh);
- while ((thisMarker < s1.Length) && (thisChunk.Length == 0 || SortHelper.InChunk(thisCh, thisChunk[0])))
+ while (thisMarker < s1.Length && char.IsDigit(thisCh) == isNumeric)
{
thisChunk.Append(thisCh);
thisMarker++;
@@ -406,7 +403,6 @@ namespace MediaBrowser.Controller.Entities
}
}
- var isNumeric = thisChunk.Length > 0 && char.IsDigit(thisChunk[0]);
list.Add(new Tuple<StringBuilder, bool>(thisChunk, isNumeric));
}
@@ -553,7 +549,6 @@ namespace MediaBrowser.Controller.Entities
[JsonIgnore]
public DateTime DateModified { get; set; }
- [JsonIgnore]
public DateTime DateLastSaved { get; set; }
[JsonIgnore]
@@ -1330,8 +1325,9 @@ namespace MediaBrowser.Controller.Entities
}
// Use some hackery to get the extra type based on foldername
- Enum.TryParse(extraFolderName.Replace(" ", ""), true, out ExtraType extraType);
- item.ExtraType = extraType;
+ item.ExtraType = Enum.TryParse(extraFolderName.Replace(" ", string.Empty), true, out ExtraType extraType)
+ ? extraType
+ : Model.Entities.ExtraType.Unknown;
return item;
@@ -2194,13 +2190,9 @@ namespace MediaBrowser.Controller.Entities
/// <summary>
/// Do whatever refreshing is necessary when the filesystem pertaining to this item has changed.
/// </summary>
- /// <returns>Task.</returns>
public virtual void ChangedExternally()
{
- ProviderManager.QueueRefresh(Id, new MetadataRefreshOptions(new DirectoryService(FileSystem))
- {
-
- }, RefreshPriority.High);
+ ProviderManager.QueueRefresh(Id, new MetadataRefreshOptions(new DirectoryService(FileSystem)), RefreshPriority.High);
}
/// <summary>
@@ -2231,7 +2223,6 @@ namespace MediaBrowser.Controller.Entities
existingImage.Width = image.Width;
existingImage.Height = image.Height;
}
-
else
{
var current = ImageInfos;
@@ -2274,7 +2265,6 @@ namespace MediaBrowser.Controller.Entities
/// </summary>
/// <param name="type">The type.</param>
/// <param name="index">The index.</param>
- /// <returns>Task.</returns>
public void DeleteImage(ImageType type, int index)
{
var info = GetImageInfo(type, index);
@@ -2312,7 +2302,7 @@ namespace MediaBrowser.Controller.Entities
}
/// <summary>
- /// Validates that images within the item are still on the file system
+ /// Validates that images within the item are still on the filesystem.
/// </summary>
public bool ValidateImages(IDirectoryService directoryService)
{
@@ -2606,7 +2596,7 @@ namespace MediaBrowser.Controller.Entities
}
/// <summary>
- /// This is called before any metadata refresh and returns true or false indicating if changes were made
+ /// This is called before any metadata refresh and returns true if changes were made.
/// </summary>
public virtual bool BeforeMetadataRefresh(bool replaceAllMetdata)
{
@@ -2666,36 +2656,43 @@ namespace MediaBrowser.Controller.Entities
newOptions.ForceSave = true;
ownedItem.Genres = item.Genres;
}
+
if (!item.Studios.SequenceEqual(ownedItem.Studios, StringComparer.Ordinal))
{
newOptions.ForceSave = true;
ownedItem.Studios = item.Studios;
}
+
if (!item.ProductionLocations.SequenceEqual(ownedItem.ProductionLocations, StringComparer.Ordinal))
{
newOptions.ForceSave = true;
ownedItem.ProductionLocations = item.ProductionLocations;
}
+
if (item.CommunityRating != ownedItem.CommunityRating)
{
ownedItem.CommunityRating = item.CommunityRating;
newOptions.ForceSave = true;
}
+
if (item.CriticRating != ownedItem.CriticRating)
{
ownedItem.CriticRating = item.CriticRating;
newOptions.ForceSave = true;
}
+
if (!string.Equals(item.Overview, ownedItem.Overview, StringComparison.Ordinal))
{
ownedItem.Overview = item.Overview;
newOptions.ForceSave = true;
}
+
if (!string.Equals(item.OfficialRating, ownedItem.OfficialRating, StringComparison.Ordinal))
{
ownedItem.OfficialRating = item.OfficialRating;
newOptions.ForceSave = true;
}
+
if (!string.Equals(item.CustomRating, ownedItem.CustomRating, StringComparison.Ordinal))
{
ownedItem.CustomRating = item.CustomRating;
@@ -2743,7 +2740,7 @@ namespace MediaBrowser.Controller.Entities
{
var list = GetEtagValues(user);
- return string.Join("|", list.ToArray()).GetMD5().ToString("N", CultureInfo.InvariantCulture);
+ return string.Join("|", list).GetMD5().ToString("N", CultureInfo.InvariantCulture);
}
protected virtual List<string> GetEtagValues(User user)
@@ -2786,8 +2783,7 @@ namespace MediaBrowser.Controller.Entities
return true;
}
- var view = this as IHasCollectionType;
- if (view != null)
+ if (this is IHasCollectionType view)
{
if (string.Equals(view.CollectionType, CollectionType.LiveTv, StringComparison.OrdinalIgnoreCase))
{
@@ -2880,14 +2876,29 @@ namespace MediaBrowser.Controller.Entities
/// <value>The remote trailers.</value>
public IReadOnlyList<MediaUrl> RemoteTrailers { get; set; }
+ /// <summary>
+ /// Get all extras associated with this item, sorted by <see cref="SortName"/>.
+ /// </summary>
+ /// <returns>An enumerable containing the items.</returns>
public IEnumerable<BaseItem> GetExtras()
{
- return ExtraIds.Select(LibraryManager.GetItemById).Where(i => i != null).OrderBy(i => i.SortName);
+ return ExtraIds
+ .Select(LibraryManager.GetItemById)
+ .Where(i => i != null)
+ .OrderBy(i => i.SortName);
}
+ /// <summary>
+ /// Get all extras with specific types that are associated with this item.
+ /// </summary>
+ /// <param name="extraTypes">The types of extras to retrieve.</param>
+ /// <returns>An enumerable containing the extras.</returns>
public IEnumerable<BaseItem> GetExtras(IReadOnlyCollection<ExtraType> extraTypes)
{
- return ExtraIds.Select(LibraryManager.GetItemById).Where(i => i != null && extraTypes.Contains(i.ExtraType.Value));
+ return ExtraIds
+ .Select(LibraryManager.GetItemById)
+ .Where(i => i != null)
+ .Where(i => i.ExtraType.HasValue && extraTypes.Contains(i.ExtraType.Value));
}
public IEnumerable<BaseItem> GetTrailers()
@@ -2898,25 +2909,49 @@ namespace MediaBrowser.Controller.Entities
return Array.Empty<BaseItem>();
}
- public IEnumerable<BaseItem> GetDisplayExtras()
- {
- return GetExtras(DisplayExtraTypes);
- }
-
public virtual bool IsHD => Height >= 720;
+
public bool IsShortcut { get; set; }
+
public string ShortcutPath { get; set; }
+
public int Width { get; set; }
+
public int Height { get; set; }
+
public Guid[] ExtraIds { get; set; }
+
public virtual long GetRunTimeTicksForPlayState()
{
return RunTimeTicks ?? 0;
}
- // Possible types of extra videos
- public static readonly IReadOnlyCollection<ExtraType> DisplayExtraTypes = new[] { Model.Entities.ExtraType.BehindTheScenes, Model.Entities.ExtraType.Clip, Model.Entities.ExtraType.DeletedScene, Model.Entities.ExtraType.Interview, Model.Entities.ExtraType.Sample, Model.Entities.ExtraType.Scene };
+ /// <summary>
+ /// Extra types that should be counted and displayed as "Special Features" in the UI.
+ /// </summary>
+ public static readonly IReadOnlyCollection<ExtraType> DisplayExtraTypes = new HashSet<ExtraType>
+ {
+ Model.Entities.ExtraType.Unknown,
+ Model.Entities.ExtraType.BehindTheScenes,
+ Model.Entities.ExtraType.Clip,
+ Model.Entities.ExtraType.DeletedScene,
+ Model.Entities.ExtraType.Interview,
+ Model.Entities.ExtraType.Sample,
+ Model.Entities.ExtraType.Scene
+ };
public virtual bool SupportsExternalTransfer => false;
+
+ /// <inheritdoc />
+ public override bool Equals(object obj)
+ {
+ return obj is BaseItem baseItem && this.Equals(baseItem);
+ }
+
+ /// <inheritdoc />
+ public bool Equals(BaseItem item) => Object.Equals(Id, item?.Id);
+
+ /// <inheritdoc />
+ public override int GetHashCode() => HashCode.Combine(Id);
}
}
diff --git a/MediaBrowser.Controller/Entities/Book.cs b/MediaBrowser.Controller/Entities/Book.cs
index 44c35374d..dcad2554b 100644
--- a/MediaBrowser.Controller/Entities/Book.cs
+++ b/MediaBrowser.Controller/Entities/Book.cs
@@ -13,8 +13,10 @@ namespace MediaBrowser.Controller.Entities
[JsonIgnore]
public string SeriesPresentationUniqueKey { get; set; }
+
[JsonIgnore]
public string SeriesName { get; set; }
+
[JsonIgnore]
public Guid SeriesId { get; set; }
@@ -22,10 +24,12 @@ namespace MediaBrowser.Controller.Entities
{
return SeriesName;
}
+
public string FindSeriesName()
{
return SeriesName;
}
+
public string FindSeriesPresentationUniqueKey()
{
return SeriesPresentationUniqueKey;
diff --git a/MediaBrowser.Controller/Entities/Folder.cs b/MediaBrowser.Controller/Entities/Folder.cs
index 07fbe6035..a468e0c35 100644
--- a/MediaBrowser.Controller/Entities/Folder.cs
+++ b/MediaBrowser.Controller/Entities/Folder.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
using System;
using System.Collections.Generic;
using System.Globalization;
@@ -28,7 +30,6 @@ namespace MediaBrowser.Controller.Entities
/// </summary>
public class Folder : BaseItem
{
- public static IUserManager UserManager { get; set; }
public static IUserViewManager UserViewManager { get; set; }
/// <summary>
@@ -322,10 +323,10 @@ namespace MediaBrowser.Controller.Entities
ProviderManager.OnRefreshProgress(this, 5);
}
- //build a dictionary of the current children we have now by Id so we can compare quickly and easily
+ // Build a dictionary of the current children we have now by Id so we can compare quickly and easily
var currentChildren = GetActualChildrenDictionary();
- //create a list for our validated children
+ // Create a list for our validated children
var newItems = new List<BaseItem>();
cancellationToken.ThrowIfCancellationRequested();
@@ -391,7 +392,7 @@ namespace MediaBrowser.Controller.Entities
var folder = this;
innerProgress.RegisterAction(p =>
{
- double newPct = .80 * p + 10;
+ double newPct = 0.80 * p + 10;
progress.Report(newPct);
ProviderManager.OnRefreshProgress(folder, newPct);
});
@@ -421,7 +422,7 @@ namespace MediaBrowser.Controller.Entities
var folder = this;
innerProgress.RegisterAction(p =>
{
- double newPct = .10 * p + 90;
+ double newPct = 0.10 * p + 90;
progress.Report(newPct);
if (recursive)
{
@@ -620,7 +621,6 @@ namespace MediaBrowser.Controller.Entities
{
EnableImages = false
}
-
}).TotalRecordCount;
}
@@ -807,11 +807,45 @@ namespace MediaBrowser.Controller.Entities
return false;
}
+ private static BaseItem[] SortItemsByRequest(InternalItemsQuery query, IReadOnlyList<BaseItem> items)
+ {
+ var ids = query.ItemIds;
+ int size = items.Count;
+
+ // ids can potentially contain non-unique guids, but query result cannot,
+ // so we include only first occurrence of each guid
+ var positions = new Dictionary<Guid, int>(size);
+ int index = 0;
+ for (int i = 0; i < ids.Length; i++)
+ {
+ if (positions.TryAdd(ids[i], index))
+ {
+ index++;
+ }
+ }
+
+ var newItems = new BaseItem[size];
+ for (int i = 0; i < size; i++)
+ {
+ var item = items[i];
+ newItems[positions[item.Id]] = item;
+ }
+
+ return newItems;
+ }
+
public QueryResult<BaseItem> GetItems(InternalItemsQuery query)
{
if (query.ItemIds.Length > 0)
{
- return LibraryManager.GetItemsResult(query);
+ var result = LibraryManager.GetItemsResult(query);
+
+ if (query.OrderBy.Count == 0 && query.ItemIds.Length > 1)
+ {
+ result.Items = SortItemsByRequest(query, result.Items);
+ }
+
+ return result;
}
return GetItemsInternal(query);
@@ -823,7 +857,14 @@ namespace MediaBrowser.Controller.Entities
if (query.ItemIds.Length > 0)
{
- return LibraryManager.GetItemList(query);
+ var result = LibraryManager.GetItemList(query);
+
+ if (query.OrderBy.Count == 0 && query.ItemIds.Length > 1)
+ {
+ return SortItemsByRequest(query, result);
+ }
+
+ return result;
}
return GetItemsInternal(query).Items;
@@ -1672,7 +1713,6 @@ namespace MediaBrowser.Controller.Entities
{
EnableImages = false
}
-
});
double unplayedCount = unplayedQueryResult.TotalRecordCount;
diff --git a/MediaBrowser.Controller/Entities/IItemByName.cs b/MediaBrowser.Controller/Entities/IItemByName.cs
index 89b5dfee3..8ef5c8d96 100644
--- a/MediaBrowser.Controller/Entities/IItemByName.cs
+++ b/MediaBrowser.Controller/Entities/IItemByName.cs
@@ -3,7 +3,7 @@ using System.Collections.Generic;
namespace MediaBrowser.Controller.Entities
{
/// <summary>
- /// Marker interface
+ /// Marker interface.
/// </summary>
public interface IItemByName
{
diff --git a/MediaBrowser.Controller/Entities/InternalPeopleQuery.cs b/MediaBrowser.Controller/Entities/InternalPeopleQuery.cs
index 1613531b5..011975dd2 100644
--- a/MediaBrowser.Controller/Entities/InternalPeopleQuery.cs
+++ b/MediaBrowser.Controller/Entities/InternalPeopleQuery.cs
@@ -4,11 +4,21 @@ namespace MediaBrowser.Controller.Entities
{
public class InternalPeopleQuery
{
+ /// <summary>
+ /// Gets or sets the maximum number of items the query should return.
+ /// </summary>
+ public int Limit { get; set; }
+
public Guid ItemId { get; set; }
+
public string[] PersonTypes { get; set; }
+
public string[] ExcludePersonTypes { get; set; }
+
public int? MaxListOrder { get; set; }
+
public Guid AppearsInItemId { get; set; }
+
public string NameContains { get; set; }
public InternalPeopleQuery()
diff --git a/MediaBrowser.Controller/Entities/Person.cs b/MediaBrowser.Controller/Entities/Person.cs
index d9b4b2206..9e4f9d47e 100644
--- a/MediaBrowser.Controller/Entities/Person.cs
+++ b/MediaBrowser.Controller/Entities/Person.cs
@@ -3,7 +3,6 @@ using System.Collections.Generic;
using System.Text.Json.Serialization;
using MediaBrowser.Controller.Extensions;
using MediaBrowser.Controller.Providers;
-using MediaBrowser.Model.Entities;
using Microsoft.Extensions.Logging;
namespace MediaBrowser.Controller.Entities
@@ -135,57 +134,4 @@ namespace MediaBrowser.Controller.Entities
return hasChanges;
}
}
-
- /// <summary>
- /// This is the small Person stub that is attached to BaseItems
- /// </summary>
- public class PersonInfo : IHasProviderIds
- {
- public PersonInfo()
- {
- ProviderIds = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
- }
-
- public Guid ItemId { get; set; }
-
- /// <summary>
- /// Gets or sets the name.
- /// </summary>
- /// <value>The name.</value>
- public string Name { get; set; }
- /// <summary>
- /// Gets or sets the role.
- /// </summary>
- /// <value>The role.</value>
- public string Role { get; set; }
- /// <summary>
- /// Gets or sets the type.
- /// </summary>
- /// <value>The type.</value>
- public string Type { get; set; }
-
- /// <summary>
- /// Gets or sets the sort order - ascending
- /// </summary>
- /// <value>The sort order.</value>
- public int? SortOrder { get; set; }
-
- public string ImageUrl { get; set; }
-
- public Dictionary<string, string> ProviderIds { get; set; }
-
- /// <summary>
- /// Returns a <see cref="string" /> that represents this instance.
- /// </summary>
- /// <returns>A <see cref="string" /> that represents this instance.</returns>
- public override string ToString()
- {
- return Name;
- }
-
- public bool IsType(string type)
- {
- return string.Equals(Type, type, StringComparison.OrdinalIgnoreCase) || string.Equals(Role, type, StringComparison.OrdinalIgnoreCase);
- }
- }
}
diff --git a/MediaBrowser.Controller/Entities/PersonInfo.cs b/MediaBrowser.Controller/Entities/PersonInfo.cs
new file mode 100644
index 000000000..f3ec73b32
--- /dev/null
+++ b/MediaBrowser.Controller/Entities/PersonInfo.cs
@@ -0,0 +1,62 @@
+using System;
+using System.Collections.Generic;
+using MediaBrowser.Model.Entities;
+
+namespace MediaBrowser.Controller.Entities
+{
+ /// <summary>
+ /// This is a small Person stub that is attached to BaseItems.
+ /// </summary>
+ public sealed class PersonInfo : IHasProviderIds
+ {
+ public PersonInfo()
+ {
+ ProviderIds = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
+ }
+
+ public Guid ItemId { get; set; }
+
+ /// <summary>
+ /// Gets or sets the name.
+ /// </summary>
+ /// <value>The name.</value>
+ public string Name { get; set; }
+
+ /// <summary>
+ /// Gets or sets the role.
+ /// </summary>
+ /// <value>The role.</value>
+ public string Role { get; set; }
+
+ /// <summary>
+ /// Gets or sets the type.
+ /// </summary>
+ /// <value>The type.</value>
+ public string Type { get; set; }
+
+ /// <summary>
+ /// Gets or sets the ascending sort order.
+ /// </summary>
+ /// <value>The sort order.</value>
+ public int? SortOrder { get; set; }
+
+ public string ImageUrl { get; set; }
+
+ public Dictionary<string, string> ProviderIds { get; set; }
+
+ /// <summary>
+ /// Returns a <see cref="string" /> that represents this instance.
+ /// </summary>
+ /// <returns>A <see cref="string" /> that represents this instance.</returns>
+ public override string ToString()
+ {
+ return Name;
+ }
+
+ public bool IsType(string type)
+ {
+ return string.Equals(Type, type, StringComparison.OrdinalIgnoreCase)
+ || string.Equals(Role, type, StringComparison.OrdinalIgnoreCase);
+ }
+ }
+}
diff --git a/MediaBrowser.Controller/Entities/Photo.cs b/MediaBrowser.Controller/Entities/Photo.cs
index 86d62add9..5ebc9f16a 100644
--- a/MediaBrowser.Controller/Entities/Photo.cs
+++ b/MediaBrowser.Controller/Entities/Photo.cs
@@ -41,10 +41,10 @@ namespace MediaBrowser.Controller.Entities
public override double GetDefaultPrimaryImageAspectRatio()
{
// REVIEW: @bond
- if (Width.HasValue && Height.HasValue)
+ if (Width != 0 && Height != 0)
{
- double width = Width.Value;
- double height = Height.Value;
+ double width = Width;
+ double height = Height;
if (Orientation.HasValue)
{
@@ -67,8 +67,6 @@ namespace MediaBrowser.Controller.Entities
return base.GetDefaultPrimaryImageAspectRatio();
}
- public new int? Width { get; set; }
- public new int? Height { get; set; }
public string CameraMake { get; set; }
public string CameraModel { get; set; }
public string Software { get; set; }
diff --git a/MediaBrowser.Controller/Entities/UserRootFolder.cs b/MediaBrowser.Controller/Entities/UserRootFolder.cs
index 7fcf48a48..8a68f830c 100644
--- a/MediaBrowser.Controller/Entities/UserRootFolder.cs
+++ b/MediaBrowser.Controller/Entities/UserRootFolder.cs
@@ -60,14 +60,7 @@ namespace MediaBrowser.Controller.Entities
PresetViews = query.PresetViews
});
- var itemsArray = result;
- var totalCount = itemsArray.Length;
-
- return new QueryResult<BaseItem>
- {
- TotalRecordCount = totalCount,
- Items = itemsArray //TODO Fix The co-variant conversion between Folder[] and BaseItem[], this can generate runtime issues.
- };
+ return UserViewBuilder.SortAndPage(result, null, query, LibraryManager, true);
}
public override int GetChildCount(User user)
diff --git a/MediaBrowser.Controller/Entities/Video.cs b/MediaBrowser.Controller/Entities/Video.cs
index af4d227bc..c3ea7f347 100644
--- a/MediaBrowser.Controller/Entities/Video.cs
+++ b/MediaBrowser.Controller/Entities/Video.cs
@@ -7,7 +7,6 @@ using System.Threading;
using System.Threading.Tasks;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.LiveTv;
-using MediaBrowser.Controller.MediaEncoding;
using MediaBrowser.Controller.Persistence;
using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Dto;
diff --git a/MediaBrowser.Controller/Extensions/ConfigurationExtensions.cs b/MediaBrowser.Controller/Extensions/ConfigurationExtensions.cs
index 76c9b4b26..c0043c0ef 100644
--- a/MediaBrowser.Controller/Extensions/ConfigurationExtensions.cs
+++ b/MediaBrowser.Controller/Extensions/ConfigurationExtensions.cs
@@ -1,3 +1,4 @@
+using System;
using Microsoft.Extensions.Configuration;
namespace MediaBrowser.Controller.Extensions
@@ -8,29 +9,56 @@ namespace MediaBrowser.Controller.Extensions
public static class ConfigurationExtensions
{
/// <summary>
+ /// The key for a setting that indicates whether the application should host web client content.
+ /// </summary>
+ public const string HostWebClientKey = "hostwebclient";
+
+ /// <summary>
/// The key for the FFmpeg probe size option.
/// </summary>
public const string FfmpegProbeSizeKey = "FFmpeg:probesize";
/// <summary>
- /// The key for the FFmpeg analyse duration option.
+ /// The key for the FFmpeg analyze duration option.
/// </summary>
public const string FfmpegAnalyzeDurationKey = "FFmpeg:analyzeduration";
/// <summary>
- /// Retrieves the FFmpeg probe size from the <see cref="IConfiguration" />.
+ /// The key for a setting that indicates whether playlists should allow duplicate entries.
+ /// </summary>
+ public const string PlaylistsAllowDuplicatesKey = "playlists:allowDuplicates";
+
+ /// <summary>
+ /// Gets a value indicating whether the application should host static web content from the <see cref="IConfiguration"/>.
/// </summary>
- /// <param name="configuration">This configuration.</param>
+ /// <param name="configuration">The configuration to retrieve the value from.</param>
+ /// <returns>The parsed config value.</returns>
+ /// <exception cref="FormatException">The config value is not a valid bool string. See <see cref="bool.Parse(string)"/>.</exception>
+ public static bool HostWebClient(this IConfiguration configuration)
+ => configuration.GetValue<bool>(HostWebClientKey);
+
+ /// <summary>
+ /// Gets the FFmpeg probe size from the <see cref="IConfiguration" />.
+ /// </summary>
+ /// <param name="configuration">The configuration to read the setting from.</param>
/// <returns>The FFmpeg probe size option.</returns>
public static string GetFFmpegProbeSize(this IConfiguration configuration)
=> configuration[FfmpegProbeSizeKey];
/// <summary>
- /// Retrieves the FFmpeg analyse duration from the <see cref="IConfiguration" />.
+ /// Gets the FFmpeg analyze duration from the <see cref="IConfiguration" />.
/// </summary>
- /// <param name="configuration">This configuration.</param>
- /// <returns>The FFmpeg analyse duration option.</returns>
+ /// <param name="configuration">The configuration to read the setting from.</param>
+ /// <returns>The FFmpeg analyze duration option.</returns>
public static string GetFFmpegAnalyzeDuration(this IConfiguration configuration)
=> configuration[FfmpegAnalyzeDurationKey];
+
+ /// <summary>
+ /// Gets a value indicating whether playlists should allow duplicate entries from the <see cref="IConfiguration"/>.
+ /// </summary>
+ /// <param name="configuration">The configuration to read the setting from.</param>
+ /// <returns>True if playlists should allow duplicates, otherwise false.</returns>
+ public static bool DoPlaylistsAllowDuplicates(this IConfiguration configuration)
+ => configuration.GetValue<bool>(PlaylistsAllowDuplicatesKey);
}
}
diff --git a/MediaBrowser.Controller/IServerApplicationHost.cs b/MediaBrowser.Controller/IServerApplicationHost.cs
index 0790b6cd6..b35e539ab 100644
--- a/MediaBrowser.Controller/IServerApplicationHost.cs
+++ b/MediaBrowser.Controller/IServerApplicationHost.cs
@@ -65,23 +65,32 @@ namespace MediaBrowser.Controller
/// <summary>
/// Gets the local API URL.
/// </summary>
+ /// <param name="cancellationToken">Token to cancel the request if needed.</param>
+ /// <param name="forceHttp">Whether to force usage of plain HTTP protocol.</param>
/// <value>The local API URL.</value>
- Task<string> GetLocalApiUrl(CancellationToken cancellationToken);
+ Task<string> GetLocalApiUrl(CancellationToken cancellationToken, bool forceHttp = false);
/// <summary>
/// Gets the local API URL.
/// </summary>
/// <param name="hostname">The hostname.</param>
+ /// <param name="forceHttp">Whether to force usage of plain HTTP protocol.</param>
/// <returns>The local API URL.</returns>
- string GetLocalApiUrl(ReadOnlySpan<char> hostname);
+ string GetLocalApiUrl(ReadOnlySpan<char> hostname, bool forceHttp = false);
/// <summary>
/// Gets the local API URL.
/// </summary>
/// <param name="address">The IP address.</param>
+ /// <param name="forceHttp">Whether to force usage of plain HTTP protocol.</param>
/// <returns>The local API URL.</returns>
- string GetLocalApiUrl(IPAddress address);
+ string GetLocalApiUrl(IPAddress address, bool forceHttp = false);
+ /// <summary>
+ /// Open a URL in an external browser window.
+ /// </summary>
+ /// <param name="url">The URL to open.</param>
+ /// <exception cref="NotSupportedException"><see cref="CanLaunchWebBrowser"/> is false.</exception>
void LaunchUrl(string url);
void EnableLoopback(string appName);
diff --git a/MediaBrowser.Controller/IServerApplicationPaths.cs b/MediaBrowser.Controller/IServerApplicationPaths.cs
index 5d7c60910..c35a22ac7 100644
--- a/MediaBrowser.Controller/IServerApplicationPaths.cs
+++ b/MediaBrowser.Controller/IServerApplicationPaths.cs
@@ -71,7 +71,12 @@ namespace MediaBrowser.Controller
string UserConfigurationDirectoryPath { get; }
/// <summary>
- /// Gets the internal metadata path.
+ /// Gets the default internal metadata path.
+ /// </summary>
+ string DefaultInternalMetadataPath { get; }
+
+ /// <summary>
+ /// Gets the internal metadata path, either a custom path or the default.
/// </summary>
/// <value>The internal metadata path.</value>
string InternalMetadataPath { get; }
diff --git a/MediaBrowser.Controller/Library/ILibraryManager.cs b/MediaBrowser.Controller/Library/ILibraryManager.cs
index 511356aa4..2e1c97f67 100644
--- a/MediaBrowser.Controller/Library/ILibraryManager.cs
+++ b/MediaBrowser.Controller/Library/ILibraryManager.cs
@@ -157,7 +157,8 @@ namespace MediaBrowser.Controller.Library
/// <param name="introProviders">The intro providers.</param>
/// <param name="itemComparers">The item comparers.</param>
/// <param name="postscanTasks">The postscan tasks.</param>
- void AddParts(IEnumerable<IResolverIgnoreRule> rules,
+ void AddParts(
+ IEnumerable<IResolverIgnoreRule> rules,
IEnumerable<IItemResolver> resolvers,
IEnumerable<IIntroProvider> introProviders,
IEnumerable<IBaseItemComparer> itemComparers,
@@ -349,9 +350,6 @@ namespace MediaBrowser.Controller.Library
/// <returns><c>true</c> if [is audio file] [the specified path]; otherwise, <c>false</c>.</returns>
bool IsAudioFile(string path);
- bool IsAudioFile(string path, LibraryOptions libraryOptions);
- bool IsVideoFile(string path, LibraryOptions libraryOptions);
-
/// <summary>
/// Gets the season number from path.
/// </summary>
diff --git a/MediaBrowser.Controller/Library/IMediaSourceProvider.cs b/MediaBrowser.Controller/Library/IMediaSourceProvider.cs
index 9e74879fc..5bf4acebb 100644
--- a/MediaBrowser.Controller/Library/IMediaSourceProvider.cs
+++ b/MediaBrowser.Controller/Library/IMediaSourceProvider.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
diff --git a/MediaBrowser.Controller/Library/NameExtensions.cs b/MediaBrowser.Controller/Library/NameExtensions.cs
index 6b0b7e53a..24d0347e9 100644
--- a/MediaBrowser.Controller/Library/NameExtensions.cs
+++ b/MediaBrowser.Controller/Library/NameExtensions.cs
@@ -1,6 +1,6 @@
using System;
-using System.Linq;
using System.Collections.Generic;
+using System.Linq;
using MediaBrowser.Controller.Extensions;
namespace MediaBrowser.Controller.Library
diff --git a/MediaBrowser.Controller/Library/Profiler.cs b/MediaBrowser.Controller/Library/Profiler.cs
index 9fe175a7c..46a97d181 100644
--- a/MediaBrowser.Controller/Library/Profiler.cs
+++ b/MediaBrowser.Controller/Library/Profiler.cs
@@ -28,7 +28,7 @@ namespace MediaBrowser.Controller.Library
/// </summary>
/// <param name="name">The name.</param>
/// <param name="logger">The logger.</param>
- public Profiler(string name, ILogger logger)
+ public Profiler(string name, ILogger<Profiler> logger)
{
this._name = name;
diff --git a/MediaBrowser.Controller/MediaBrowser.Controller.csproj b/MediaBrowser.Controller/MediaBrowser.Controller.csproj
index 60c76ef7d..4e7d02737 100644
--- a/MediaBrowser.Controller/MediaBrowser.Controller.csproj
+++ b/MediaBrowser.Controller/MediaBrowser.Controller.csproj
@@ -1,5 +1,10 @@
<Project Sdk="Microsoft.NET.Sdk">
+ <!-- ProjectGuid is only included as a requirement for SonarQube analysis -->
+ <PropertyGroup>
+ <ProjectGuid>{17E1F4E6-8ABD-4FE5-9ECF-43D4B6087BA2}</ProjectGuid>
+ </PropertyGroup>
+
<PropertyGroup>
<Authors>Jellyfin Contributors</Authors>
<PackageId>Jellyfin.Controller</PackageId>
@@ -8,7 +13,8 @@
</PropertyGroup>
<ItemGroup>
- <PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="3.0.0" />
+ <PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="3.1.3" />
+ <PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="3.1.3" />
</ItemGroup>
<ItemGroup>
@@ -26,4 +32,16 @@
<GenerateDocumentationFile>true</GenerateDocumentationFile>
</PropertyGroup>
+ <!-- Code Analyzers-->
+ <ItemGroup Condition=" '$(Configuration)' == 'Debug' ">
+ <PackageReference Include="Microsoft.CodeAnalysis.FxCopAnalyzers" Version="2.9.8" PrivateAssets="All" />
+ <PackageReference Include="SerilogAnalyzer" Version="0.15.0" PrivateAssets="All" />
+ <PackageReference Include="StyleCop.Analyzers" Version="1.1.118" PrivateAssets="All" />
+ <PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" PrivateAssets="All" />
+ </ItemGroup>
+
+ <PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
+ <CodeAnalysisRuleSet>../jellyfin.ruleset</CodeAnalysisRuleSet>
+ </PropertyGroup>
+
</Project>
diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs
index 020f0553e..61a330675 100644
--- a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs
+++ b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs
@@ -78,8 +78,7 @@ namespace MediaBrowser.Controller.MediaEncoding
if (!string.IsNullOrEmpty(hwType)
&& encodingOptions.EnableHardwareEncoding
- && codecMap.ContainsKey(hwType)
- && CheckVaapi(state, hwType, encodingOptions))
+ && codecMap.ContainsKey(hwType))
{
var preferredEncoder = codecMap[hwType];
@@ -93,23 +92,6 @@ namespace MediaBrowser.Controller.MediaEncoding
return defaultEncoder;
}
- private bool CheckVaapi(EncodingJobInfo state, string hwType, EncodingOptions encodingOptions)
- {
- if (!string.Equals(hwType, "vaapi", StringComparison.OrdinalIgnoreCase))
- {
- // No vaapi requested, return OK.
- return true;
- }
-
- if (string.IsNullOrEmpty(encodingOptions.VaapiDevice))
- {
- // No device specified, return OK.
- return true;
- }
-
- return IsVaapiSupported(state);
- }
-
private bool IsVaapiSupported(EncodingJobInfo state)
{
var videoStream = state.VideoStream;
@@ -424,7 +406,13 @@ namespace MediaBrowser.Controller.MediaEncoding
if (string.Equals(codec, "aac", StringComparison.OrdinalIgnoreCase))
{
- return "aac -strict experimental";
+ // Use libfdk_aac for better audio quality if using custom build of FFmpeg which has fdk_aac support
+ if (_mediaEncoder.SupportsEncoder("libfdk_aac"))
+ {
+ return "libfdk_aac";
+ }
+
+ return "aac";
}
if (string.Equals(codec, "mp3", StringComparison.OrdinalIgnoreCase))
@@ -460,40 +448,37 @@ namespace MediaBrowser.Controller.MediaEncoding
if (state.IsVideoRequest
&& string.Equals(encodingOptions.HardwareAccelerationType, "vaapi", StringComparison.OrdinalIgnoreCase))
{
- var hasGraphicalSubs = state.SubtitleStream != null && !state.SubtitleStream.IsTextSubtitleStream && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode;
- var hwOutputFormat = "vaapi";
-
- if (hasGraphicalSubs)
- {
- hwOutputFormat = "yuv420p";
- }
-
- arg.Append("-hwaccel vaapi -hwaccel_output_format ")
- .Append(hwOutputFormat)
+ arg.Append("-hwaccel vaapi -hwaccel_output_format vaapi")
.Append(" -vaapi_device ")
.Append(encodingOptions.VaapiDevice)
.Append(' ');
}
- if (state.IsVideoRequest
+ if (state.IsVideoRequest
&& string.Equals(encodingOptions.HardwareAccelerationType, "qsv", StringComparison.OrdinalIgnoreCase))
{
var videoDecoder = GetHardwareAcceleratedVideoDecoder(state, encodingOptions);
var outputVideoCodec = GetVideoEncoder(state, encodingOptions);
- if (encodingOptions.EnableHardwareEncoding && outputVideoCodec.Contains("qsv", StringComparison.OrdinalIgnoreCase))
+ var hasTextSubs = state.SubtitleStream != null && state.SubtitleStream.IsTextSubtitleStream && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode;
+
+ if (!hasTextSubs)
{
- if (!string.IsNullOrEmpty(videoDecoder) && videoDecoder.Contains("qsv", StringComparison.OrdinalIgnoreCase))
+ // While using QSV encoder
+ if ((outputVideoCodec ?? string.Empty).IndexOf("qsv", StringComparison.OrdinalIgnoreCase) != -1)
{
- arg.Append("-hwaccel qsv ");
- }
- else
- {
- arg.Append("-init_hw_device qsv=hw -filter_hw_device hw ");
- }
+ // While using QSV decoder
+ if ((videoDecoder ?? string.Empty).IndexOf("qsv", StringComparison.OrdinalIgnoreCase) != -1)
+ {
+ arg.Append("-hwaccel qsv ");
+ }
+ // While using SW decoder
+ else
+ {
+ arg.Append("-init_hw_device qsv=hw -filter_hw_device hw ");
+ }
+ }
}
-
- arg.Append(videoDecoder + " ");
}
arg.Append("-i ")
@@ -503,17 +488,6 @@ namespace MediaBrowser.Controller.MediaEncoding
&& state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode
&& state.SubtitleStream.IsExternal && !state.SubtitleStream.IsTextSubtitleStream)
{
- if (state.VideoStream != null && state.VideoStream.Width.HasValue)
- {
- // This is hacky but not sure how to get the exact subtitle resolution
- int height = Convert.ToInt32(state.VideoStream.Width.Value / 16.0 * 9.0);
-
- arg.Append(" -canvas_size ")
- .Append(state.VideoStream.Width.Value.ToString(CultureInfo.InvariantCulture))
- .Append(':')
- .Append(height.ToString(CultureInfo.InvariantCulture));
- }
-
var subtitlePath = state.SubtitleStream.Path;
if (string.Equals(Path.GetExtension(subtitlePath), ".sub", StringComparison.OrdinalIgnoreCase))
@@ -653,7 +627,7 @@ namespace MediaBrowser.Controller.MediaEncoding
// _fileSystem.CreateDirectory(_fileSystem.GetDirectoryName(fallbackFontPath));
// using (var stream = _assemblyInfo.GetManifestResourceStream(GetType(), GetType().Namespace + ".DroidSansFallback.ttf"))
// {
- // using (var fileStream = _fileSystem.GetFileStream(fallbackFontPath, FileOpenMode.Create, FileAccessMode.Write, FileShareMode.Read))
+ // using (var fileStream = new FileStream(fallbackFontPath, FileMode.Create, FileAccess.Write, FileShare.Read))
// {
// stream.CopyTo(fileStream);
// }
@@ -1546,9 +1520,12 @@ namespace MediaBrowser.Controller.MediaEncoding
}
/// <summary>
- /// Gets the internal graphical subtitle param.
+ /// Gets the graphical subtitle param.
/// </summary>
- public string GetGraphicalSubtitleParam(EncodingJobInfo state, EncodingOptions options, string outputVideoCodec)
+ public string GetGraphicalSubtitleParam(
+ EncodingJobInfo state,
+ EncodingOptions options,
+ string outputVideoCodec)
{
var outputSizeParam = string.Empty;
@@ -1562,53 +1539,77 @@ namespace MediaBrowser.Controller.MediaEncoding
{
outputSizeParam = GetOutputSizeParam(state, options, outputVideoCodec).TrimEnd('"');
- if (string.Equals(outputVideoCodec, "h264_vaapi", StringComparison.OrdinalIgnoreCase))
+ var index = outputSizeParam.IndexOf("hwdownload", StringComparison.OrdinalIgnoreCase);
+ if (index != -1)
{
- var index = outputSizeParam.IndexOf("format", StringComparison.OrdinalIgnoreCase);
- if (index != -1)
- {
- outputSizeParam = "," + outputSizeParam.Substring(index);
- }
+ outputSizeParam = "," + outputSizeParam.Substring(index);
}
else
{
- var index = outputSizeParam.IndexOf("scale", StringComparison.OrdinalIgnoreCase);
+ index = outputSizeParam.IndexOf("format", StringComparison.OrdinalIgnoreCase);
if (index != -1)
{
outputSizeParam = "," + outputSizeParam.Substring(index);
}
- }
- }
-
- if (string.Equals(outputVideoCodec, "h264_vaapi", StringComparison.OrdinalIgnoreCase)
- && outputSizeParam.Length == 0)
- {
- outputSizeParam = ",format=nv12|vaapi,hwupload";
-
- // Add parameters to use VAAPI with burn-in subttiles (GH issue #642)
- if (state.SubtitleStream != null
- && state.SubtitleStream.IsTextSubtitleStream
- && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode) {
- outputSizeParam += ",hwmap=mode=read+write+direct";
+ else
+ {
+ index = outputSizeParam.IndexOf("yadif", StringComparison.OrdinalIgnoreCase);
+ if (index != -1)
+ {
+ outputSizeParam = "," + outputSizeParam.Substring(index);
+ }
+ else
+ {
+ index = outputSizeParam.IndexOf("scale", StringComparison.OrdinalIgnoreCase);
+ if (index != -1)
+ {
+ outputSizeParam = "," + outputSizeParam.Substring(index);
+ }
+ }
+ }
}
}
var videoSizeParam = string.Empty;
+ var videoDecoder = GetHardwareAcceleratedVideoDecoder(state, options);
// Setup subtitle scaling
if (state.VideoStream != null && state.VideoStream.Width.HasValue && state.VideoStream.Height.HasValue)
{
+ // force_original_aspect_ratio=decrease
+ // Enable decreasing output video width or height if necessary to keep the original aspect ratio
videoSizeParam = string.Format(
CultureInfo.InvariantCulture,
- "scale={0}:{1}",
+ "scale={0}:{1}:force_original_aspect_ratio=decrease",
state.VideoStream.Width.Value,
state.VideoStream.Height.Value);
- //For QSV, feed it into hardware encoder now
+ // For QSV, feed it into hardware encoder now
if (string.Equals(outputVideoCodec, "h264_qsv", StringComparison.OrdinalIgnoreCase))
{
videoSizeParam += ",hwupload=extra_hw_frames=64";
}
+
+ // For VAAPI and CUVID decoder
+ // these encoders cannot automatically adjust the size of graphical subtitles to fit the output video,
+ // thus needs to be manually adjusted.
+ if ((IsVaapiSupported(state) && string.Equals(options.HardwareAccelerationType, "vaapi", StringComparison.OrdinalIgnoreCase))
+ || (videoDecoder ?? string.Empty).IndexOf("cuvid", StringComparison.OrdinalIgnoreCase) != -1)
+ {
+ var videoStream = state.VideoStream;
+ var inputWidth = videoStream?.Width;
+ var inputHeight = videoStream?.Height;
+ var (width, height) = GetFixedOutputSize(inputWidth, inputHeight, request.Width, request.Height, request.MaxWidth, request.MaxHeight);
+
+ if (width.HasValue && height.HasValue)
+ {
+ videoSizeParam = string.Format(
+ CultureInfo.InvariantCulture,
+ "scale={0}:{1}:force_original_aspect_ratio=decrease",
+ width.Value,
+ height.Value);
+ }
+ }
}
var mapPrefix = state.SubtitleStream.IsExternal ?
@@ -1619,27 +1620,50 @@ namespace MediaBrowser.Controller.MediaEncoding
? 0
: state.SubtitleStream.Index;
- var videoDecoder = GetHardwareAcceleratedVideoDecoder(state, options);
-
// Setup default filtergraph utilizing FFMpeg overlay() and FFMpeg scale() (see the return of this function for index reference)
var retStr = " -filter_complex \"[{0}:{1}]{4}[sub];[0:{2}][sub]overlay{3}\"";
- if (string.Equals(outputVideoCodec, "h264_qsv", StringComparison.OrdinalIgnoreCase))
+ // When the input may or may not be hardware VAAPI decodable
+ if (string.Equals(outputVideoCodec, "h264_vaapi", StringComparison.OrdinalIgnoreCase))
+ {
+ /*
+ [base]: HW scaling video to OutputSize
+ [sub]: SW scaling subtitle to FixedOutputSize
+ [base][sub]: SW overlay
+ */
+ outputSizeParam = outputSizeParam.TrimStart(',');
+ retStr = " -filter_complex \"[{0}:{1}]{4}[sub];[0:{2}]{3},hwdownload[base];[base][sub]overlay,format=nv12,hwupload\"";
+ }
+
+ // If we're hardware VAAPI decoding and software encoding, download frames from the decoder first
+ else if (IsVaapiSupported(state) && string.Equals(options.HardwareAccelerationType, "vaapi", StringComparison.OrdinalIgnoreCase)
+ && string.Equals(outputVideoCodec, "libx264", StringComparison.OrdinalIgnoreCase))
+ {
+ /*
+ [base]: SW scaling video to OutputSize
+ [sub]: SW scaling subtitle to FixedOutputSize
+ [base][sub]: SW overlay
+ */
+ outputSizeParam = outputSizeParam.TrimStart(',');
+ retStr = " -filter_complex \"[{0}:{1}]{4}[sub];[0:{2}]{3}[base];[base][sub]overlay\"";
+ }
+
+ else if (string.Equals(outputVideoCodec, "h264_qsv", StringComparison.OrdinalIgnoreCase))
{
/*
QSV in FFMpeg can now setup hardware overlay for transcodes.
For software decoding and hardware encoding option, frames must be hwuploaded into hardware
- with fixed frame size.
+ with fixed frame size.
*/
if (!string.IsNullOrEmpty(videoDecoder) && videoDecoder.Contains("qsv", StringComparison.OrdinalIgnoreCase))
{
retStr = " -filter_complex \"[{0}:{1}]{4}[sub];[0:{2}][sub]overlay_qsv=x=(W-w)/2:y=(H-h)/2{3}\"";
- }
- else
+ }
+ else
{
retStr = " -filter_complex \"[{0}:{1}]{4}[sub];[0:{2}]hwupload=extra_hw_frames=64[v];[v][sub]overlay_qsv=x=(W-w)/2:y=(H-h)/2{3}\"";
}
- }
+ }
return string.Format(
CultureInfo.InvariantCulture,
@@ -1688,7 +1712,8 @@ namespace MediaBrowser.Controller.MediaEncoding
return (Convert.ToInt32(outputWidth), Convert.ToInt32(outputHeight));
}
- public List<string> GetScalingFilters(int? videoWidth,
+ public List<string> GetScalingFilters(EncodingJobInfo state,
+ int? videoWidth,
int? videoHeight,
Video3DFormat? threedFormat,
string videoDecoder,
@@ -1707,7 +1732,9 @@ namespace MediaBrowser.Controller.MediaEncoding
requestedMaxWidth,
requestedMaxHeight);
- if ((string.Equals(videoEncoder, "h264_vaapi", StringComparison.OrdinalIgnoreCase) || string.Equals(videoEncoder, "h264_qsv", StringComparison.OrdinalIgnoreCase))
+ var hasTextSubs = state.SubtitleStream != null && state.SubtitleStream.IsTextSubtitleStream && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode;
+
+ if (string.Equals(videoEncoder, "h264_vaapi", StringComparison.OrdinalIgnoreCase) || (string.Equals(videoEncoder, "h264_qsv", StringComparison.OrdinalIgnoreCase) && !hasTextSubs)
&& width.HasValue
&& height.HasValue)
{
@@ -1731,13 +1758,13 @@ namespace MediaBrowser.Controller.MediaEncoding
vaapi_or_qsv,
outputWidth,
outputHeight));
- }
- else
+ }
+ else
{
filters.Add(string.Format(CultureInfo.InvariantCulture, "scale_{0}=format=nv12", vaapi_or_qsv));
}
}
- else if ((videoDecoder ?? string.Empty).IndexOf("_cuvid", StringComparison.OrdinalIgnoreCase) != -1
+ else if ((videoDecoder ?? string.Empty).IndexOf("cuvid", StringComparison.OrdinalIgnoreCase) != -1
&& width.HasValue
&& height.HasValue)
{
@@ -1941,8 +1968,7 @@ namespace MediaBrowser.Controller.MediaEncoding
public string GetOutputSizeParam(
EncodingJobInfo state,
EncodingOptions options,
- string outputVideoCodec,
- bool allowTimeStampCopy = true)
+ string outputVideoCodec)
{
// http://sonnati.wordpress.com/2012/10/19/ffmpeg-the-swiss-army-knife-of-internet-streaming-part-vi/
@@ -1951,42 +1977,61 @@ namespace MediaBrowser.Controller.MediaEncoding
var videoStream = state.VideoStream;
var filters = new List<string>();
- // If we're hardware VAAPI decoding and software encoding, download frames from the decoder first
- var hwType = options.HardwareAccelerationType ?? string.Empty;
- if (string.Equals(hwType, "vaapi", StringComparison.OrdinalIgnoreCase) && !options.EnableHardwareEncoding )
- {
- filters.Add("hwdownload");
+ var videoDecoder = GetHardwareAcceleratedVideoDecoder(state, options);
+ var inputWidth = videoStream?.Width;
+ var inputHeight = videoStream?.Height;
+ var threeDFormat = state.MediaSource.Video3DFormat;
- // If transcoding from 10 bit, transform colour spaces too
- if (!string.IsNullOrEmpty(videoStream.PixelFormat)
- && videoStream.PixelFormat.IndexOf("p10", StringComparison.OrdinalIgnoreCase) != -1
- && string.Equals(outputVideoCodec, "libx264", StringComparison.OrdinalIgnoreCase))
- {
- filters.Add("format=p010le");
- filters.Add("format=nv12");
- }
- else
- {
- filters.Add("format=nv12");
- }
- }
+ var hasTextSubs = state.SubtitleStream != null && state.SubtitleStream.IsTextSubtitleStream && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode;
+ // When the input may or may not be hardware VAAPI decodable
if (string.Equals(outputVideoCodec, "h264_vaapi", StringComparison.OrdinalIgnoreCase))
{
filters.Add("format=nv12|vaapi");
filters.Add("hwupload");
}
- var videoDecoder = GetHardwareAcceleratedVideoDecoder(state, options);
-
- // If we are software decoding, and hardware encoding
- if (string.Equals(outputVideoCodec, "h264_qsv", StringComparison.OrdinalIgnoreCase)
- && (string.IsNullOrEmpty(videoDecoder) || !videoDecoder.Contains("qsv", StringComparison.OrdinalIgnoreCase)))
+ // When the input may or may not be hardware QSV decodable
+ else if (string.Equals(outputVideoCodec, "h264_qsv", StringComparison.OrdinalIgnoreCase))
{
- filters.Add("format=nv12|qsv");
- filters.Add("hwupload=extra_hw_frames=64");
+ if (!hasTextSubs)
+ {
+ filters.Add("format=nv12|qsv");
+ filters.Add("hwupload=extra_hw_frames=64");
+ }
}
+ // If we're hardware VAAPI decoding and software encoding, download frames from the decoder first
+ else if (IsVaapiSupported(state) && string.Equals(options.HardwareAccelerationType, "vaapi", StringComparison.OrdinalIgnoreCase)
+ && string.Equals(outputVideoCodec, "libx264", StringComparison.OrdinalIgnoreCase))
+ {
+ var codec = videoStream.Codec.ToLowerInvariant();
+ var isColorDepth10 = !string.IsNullOrEmpty(videoStream.Profile) && (videoStream.Profile.Contains("Main 10", StringComparison.OrdinalIgnoreCase)
+ || videoStream.Profile.Contains("High 10", StringComparison.OrdinalIgnoreCase));
+
+ // Assert 10-bit hardware VAAPI decodable
+ if (isColorDepth10 && (string.Equals(codec, "hevc", StringComparison.OrdinalIgnoreCase)
+ || string.Equals(codec, "h265", StringComparison.OrdinalIgnoreCase)
+ || string.Equals(codec, "vp9", StringComparison.OrdinalIgnoreCase)))
+ {
+ /*
+ Download data from GPU to CPU as p010le format.
+ Colorspace conversion is unnecessary here as libx264 will handle it.
+ If this step is missing, it will fail on AMD but not on intel.
+ */
+ filters.Add("hwdownload");
+ filters.Add("format=p010le");
+ }
+
+ // Assert 8-bit hardware VAAPI decodable
+ else if (!isColorDepth10)
+ {
+ filters.Add("hwdownload");
+ filters.Add("format=nv12");
+ }
+ }
+
+ // Add hardware deinterlace filter before scaling filter
if (state.DeInterlace("h264", true))
{
if (string.Equals(outputVideoCodec, "h264_vaapi", StringComparison.OrdinalIgnoreCase))
@@ -1995,17 +2040,23 @@ namespace MediaBrowser.Controller.MediaEncoding
}
else if (string.Equals(outputVideoCodec, "h264_qsv", StringComparison.OrdinalIgnoreCase))
{
- filters.Add(string.Format(CultureInfo.InvariantCulture, "deinterlace_qsv"));
+ if (!hasTextSubs)
+ {
+ filters.Add(string.Format(CultureInfo.InvariantCulture, "deinterlace_qsv"));
+ }
}
}
- if ((state.DeInterlace("h264", true) || state.DeInterlace("h265", true) || state.DeInterlace("hevc", true))
- && !string.Equals(outputVideoCodec, "h264_vaapi", StringComparison.OrdinalIgnoreCase))
+ // Add software deinterlace filter before scaling filter
+ if (((state.DeInterlace("h264", true) || state.DeInterlace("h265", true) || state.DeInterlace("hevc", true))
+ && !string.Equals(outputVideoCodec, "h264_vaapi", StringComparison.OrdinalIgnoreCase)
+ && !string.Equals(outputVideoCodec, "h264_qsv", StringComparison.OrdinalIgnoreCase))
+ || (hasTextSubs && state.DeInterlace("h264", true) && string.Equals(outputVideoCodec, "h264_qsv", StringComparison.OrdinalIgnoreCase)))
{
var inputFramerate = videoStream?.RealFrameRate;
// If it is already 60fps then it will create an output framerate that is much too high for roku and others to handle
- if (string.Equals(options.DeinterlaceMethod, "bobandweave", StringComparison.OrdinalIgnoreCase) && (inputFramerate ?? 60) <= 30)
+ if (string.Equals(options.DeinterlaceMethod, "yadif_bob", StringComparison.OrdinalIgnoreCase) && (inputFramerate ?? 60) <= 30)
{
filters.Add("yadif=1:-1:0");
}
@@ -2015,11 +2066,21 @@ namespace MediaBrowser.Controller.MediaEncoding
}
}
- var inputWidth = videoStream?.Width;
- var inputHeight = videoStream?.Height;
- var threeDFormat = state.MediaSource.Video3DFormat;
+ // Add scaling filter: scale_*=format=nv12 or scale_*=w=*:h=*:format=nv12 or scale=expr
+ filters.AddRange(GetScalingFilters(state, inputWidth, inputHeight, threeDFormat, videoDecoder, outputVideoCodec, request.Width, request.Height, request.MaxWidth, request.MaxHeight));
- filters.AddRange(GetScalingFilters(inputWidth, inputHeight, threeDFormat, videoDecoder, outputVideoCodec, request.Width, request.Height, request.MaxWidth, request.MaxHeight));
+ // Add parameters to use VAAPI with burn-in text subttiles (GH issue #642)
+ if (string.Equals(outputVideoCodec, "h264_vaapi", StringComparison.OrdinalIgnoreCase))
+ {
+ if (state.SubtitleStream != null
+ && state.SubtitleStream.IsTextSubtitleStream
+ && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode)
+ {
+ // Test passed on Intel and AMD gfx
+ filters.Add("hwmap=mode=read+write");
+ filters.Add("format=nv12");
+ }
+ }
var output = string.Empty;
@@ -2037,11 +2098,6 @@ namespace MediaBrowser.Controller.MediaEncoding
{
filters.Add("hwmap");
}
-
- if (allowTimeStampCopy)
- {
- output += " -copyts";
- }
}
if (filters.Count > 0)
@@ -2218,7 +2274,7 @@ namespace MediaBrowser.Controller.MediaEncoding
{
inputModifier += " " + videoDecoder;
- if ((videoDecoder ?? string.Empty).IndexOf("_cuvid", StringComparison.OrdinalIgnoreCase) != -1)
+ if ((videoDecoder ?? string.Empty).IndexOf("cuvid", StringComparison.OrdinalIgnoreCase) != -1)
{
var videoStream = state.VideoStream;
var inputWidth = videoStream?.Width;
@@ -2227,7 +2283,7 @@ namespace MediaBrowser.Controller.MediaEncoding
var (width, height) = GetFixedOutputSize(inputWidth, inputHeight, request.Width, request.Height, request.MaxWidth, request.MaxHeight);
- if ((videoDecoder ?? string.Empty).IndexOf("_cuvid", StringComparison.OrdinalIgnoreCase) != -1
+ if ((videoDecoder ?? string.Empty).IndexOf("cuvid", StringComparison.OrdinalIgnoreCase) != -1
&& width.HasValue
&& height.HasValue)
{
@@ -2491,7 +2547,7 @@ namespace MediaBrowser.Controller.MediaEncoding
encodingOptions.HardwareDecodingCodecs = Array.Empty<string>();
return null;
}
- return "-c:v h264_qsv ";
+ return "-c:v h264_qsv";
}
break;
case "hevc":
@@ -2499,19 +2555,19 @@ namespace MediaBrowser.Controller.MediaEncoding
if (_mediaEncoder.SupportsDecoder("hevc_qsv") && encodingOptions.HardwareDecodingCodecs.Contains("hevc", StringComparer.OrdinalIgnoreCase))
{
//return "-c:v hevc_qsv -load_plugin hevc_hw ";
- return "-c:v hevc_qsv ";
+ return "-c:v hevc_qsv";
}
break;
case "mpeg2video":
if (_mediaEncoder.SupportsDecoder("mpeg2_qsv") && encodingOptions.HardwareDecodingCodecs.Contains("mpeg2video", StringComparer.OrdinalIgnoreCase))
{
- return "-c:v mpeg2_qsv ";
+ return "-c:v mpeg2_qsv";
}
break;
case "vc1":
if (_mediaEncoder.SupportsDecoder("vc1_qsv") && encodingOptions.HardwareDecodingCodecs.Contains("vc1", StringComparer.OrdinalIgnoreCase))
{
- return "-c:v vc1_qsv ";
+ return "-c:v vc1_qsv";
}
break;
}
@@ -2525,32 +2581,38 @@ namespace MediaBrowser.Controller.MediaEncoding
case "h264":
if (_mediaEncoder.SupportsDecoder("h264_cuvid") && encodingOptions.HardwareDecodingCodecs.Contains("h264", StringComparer.OrdinalIgnoreCase))
{
- return "-c:v h264_cuvid ";
+ // cuvid decoder does not support 10-bit input
+ if ((videoStream.BitDepth ?? 8) > 8)
+ {
+ encodingOptions.HardwareDecodingCodecs = Array.Empty<string>();
+ return null;
+ }
+ return "-c:v h264_cuvid";
}
break;
case "hevc":
case "h265":
if (_mediaEncoder.SupportsDecoder("hevc_cuvid") && encodingOptions.HardwareDecodingCodecs.Contains("hevc", StringComparer.OrdinalIgnoreCase))
{
- return "-c:v hevc_cuvid ";
+ return "-c:v hevc_cuvid";
}
break;
case "mpeg2video":
if (_mediaEncoder.SupportsDecoder("mpeg2_cuvid") && encodingOptions.HardwareDecodingCodecs.Contains("mpeg2video", StringComparer.OrdinalIgnoreCase))
{
- return "-c:v mpeg2_cuvid ";
+ return "-c:v mpeg2_cuvid";
}
break;
case "vc1":
if (_mediaEncoder.SupportsDecoder("vc1_cuvid") && encodingOptions.HardwareDecodingCodecs.Contains("vc1", StringComparer.OrdinalIgnoreCase))
{
- return "-c:v vc1_cuvid ";
+ return "-c:v vc1_cuvid";
}
break;
case "mpeg4":
if (_mediaEncoder.SupportsDecoder("mpeg4_cuvid") && encodingOptions.HardwareDecodingCodecs.Contains("mpeg4", StringComparer.OrdinalIgnoreCase))
{
- return "-c:v mpeg4_cuvid ";
+ return "-c:v mpeg4_cuvid";
}
break;
}
@@ -2564,38 +2626,38 @@ namespace MediaBrowser.Controller.MediaEncoding
case "h264":
if (_mediaEncoder.SupportsDecoder("h264_mediacodec") && encodingOptions.HardwareDecodingCodecs.Contains("h264", StringComparer.OrdinalIgnoreCase))
{
- return "-c:v h264_mediacodec ";
+ return "-c:v h264_mediacodec";
}
break;
case "hevc":
case "h265":
if (_mediaEncoder.SupportsDecoder("hevc_mediacodec") && encodingOptions.HardwareDecodingCodecs.Contains("hevc", StringComparer.OrdinalIgnoreCase))
{
- return "-c:v hevc_mediacodec ";
+ return "-c:v hevc_mediacodec";
}
break;
case "mpeg2video":
if (_mediaEncoder.SupportsDecoder("mpeg2_mediacodec") && encodingOptions.HardwareDecodingCodecs.Contains("mpeg2video", StringComparer.OrdinalIgnoreCase))
{
- return "-c:v mpeg2_mediacodec ";
+ return "-c:v mpeg2_mediacodec";
}
break;
case "mpeg4":
if (_mediaEncoder.SupportsDecoder("mpeg4_mediacodec") && encodingOptions.HardwareDecodingCodecs.Contains("mpeg4", StringComparer.OrdinalIgnoreCase))
{
- return "-c:v mpeg4_mediacodec ";
+ return "-c:v mpeg4_mediacodec";
}
break;
case "vp8":
if (_mediaEncoder.SupportsDecoder("vp8_mediacodec") && encodingOptions.HardwareDecodingCodecs.Contains("vp8", StringComparer.OrdinalIgnoreCase))
{
- return "-c:v vp8_mediacodec ";
+ return "-c:v vp8_mediacodec";
}
break;
case "vp9":
if (_mediaEncoder.SupportsDecoder("vp9_mediacodec") && encodingOptions.HardwareDecodingCodecs.Contains("vp9", StringComparer.OrdinalIgnoreCase))
{
- return "-c:v vp9_mediacodec ";
+ return "-c:v vp9_mediacodec";
}
break;
}
@@ -2609,25 +2671,25 @@ namespace MediaBrowser.Controller.MediaEncoding
case "h264":
if (_mediaEncoder.SupportsDecoder("h264_mmal") && encodingOptions.HardwareDecodingCodecs.Contains("h264", StringComparer.OrdinalIgnoreCase))
{
- return "-c:v h264_mmal ";
+ return "-c:v h264_mmal";
}
break;
case "mpeg2video":
if (_mediaEncoder.SupportsDecoder("mpeg2_mmal") && encodingOptions.HardwareDecodingCodecs.Contains("mpeg2video", StringComparer.OrdinalIgnoreCase))
{
- return "-c:v mpeg2_mmal ";
+ return "-c:v mpeg2_mmal";
}
break;
case "mpeg4":
if (_mediaEncoder.SupportsDecoder("mpeg4_mmal") && encodingOptions.HardwareDecodingCodecs.Contains("mpeg4", StringComparer.OrdinalIgnoreCase))
{
- return "-c:v mpeg4_mmal ";
+ return "-c:v mpeg4_mmal";
}
break;
case "vc1":
if (_mediaEncoder.SupportsDecoder("vc1_mmal") && encodingOptions.HardwareDecodingCodecs.Contains("vc1", StringComparer.OrdinalIgnoreCase))
{
- return "-c:v vc1_mmal ";
+ return "-c:v vc1_mmal";
}
break;
}
@@ -2637,27 +2699,14 @@ namespace MediaBrowser.Controller.MediaEncoding
{
if (Environment.OSVersion.Platform == PlatformID.Win32NT)
{
- if(Environment.OSVersion.Version.Major > 6 || (Environment.OSVersion.Version.Major == 6 && Environment.OSVersion.Version.Minor > 1))
+ if (Environment.OSVersion.Version.Major > 6 || (Environment.OSVersion.Version.Major == 6 && Environment.OSVersion.Version.Minor > 1))
return "-hwaccel d3d11va";
else
return "-hwaccel dxva2";
}
-
- switch (videoStream.Codec.ToLowerInvariant())
+ else
{
- case "avc":
- case "h264":
- if (_mediaEncoder.SupportsDecoder("h264_amf") && encodingOptions.HardwareDecodingCodecs.Contains("h264", StringComparer.OrdinalIgnoreCase))
- {
- return "-c:v h264_amf";
- }
- break;
- case "mpeg2video":
- if (_mediaEncoder.SupportsDecoder("hevc_amf") && encodingOptions.HardwareDecodingCodecs.Contains("mpeg2video", StringComparer.OrdinalIgnoreCase))
- {
- return "-c:v mpeg2_mmal";
- }
- break;
+ return "-hwaccel vaapi";
}
}
}
@@ -2770,7 +2819,7 @@ namespace MediaBrowser.Controller.MediaEncoding
if (!state.RunTimeTicks.HasValue)
{
- args += " -flags -global_header -fflags +genpts";
+ args += " -fflags +genpts";
}
}
else
@@ -2785,14 +2834,27 @@ namespace MediaBrowser.Controller.MediaEncoding
var hasGraphicalSubs = state.SubtitleStream != null && !state.SubtitleStream.IsTextSubtitleStream && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode;
var hasCopyTs = false;
+
// Add resolution params, if specified
if (!hasGraphicalSubs)
{
var outputSizeParam = GetOutputSizeParam(state, encodingOptions, videoCodec);
+
args += outputSizeParam;
+
hasCopyTs = outputSizeParam.IndexOf("copyts", StringComparison.OrdinalIgnoreCase) != -1;
}
+ // This is for graphical subs
+ if (hasGraphicalSubs)
+ {
+ var graphicalSubtitleParam = GetGraphicalSubtitleParam(state, encodingOptions, videoCodec);
+
+ args += graphicalSubtitleParam;
+
+ hasCopyTs = graphicalSubtitleParam.IndexOf("copyts", StringComparison.OrdinalIgnoreCase) != -1;
+ }
+
if (state.RunTimeTicks.HasValue && state.BaseRequest.CopyTimestamps)
{
if (!hasCopyTs)
@@ -2800,13 +2862,12 @@ namespace MediaBrowser.Controller.MediaEncoding
args += " -copyts";
}
- args += " -avoid_negative_ts disabled -start_at_zero";
- }
+ args += " -avoid_negative_ts disabled";
- // This is for internal graphical subs
- if (hasGraphicalSubs)
- {
- args += GetGraphicalSubtitleParam(state, encodingOptions, videoCodec);
+ if (!(state.SubtitleStream != null && state.SubtitleStream.IsExternal && !state.SubtitleStream.IsTextSubtitleStream))
+ {
+ args += " -start_at_zero";
+ }
}
var qualityParam = GetVideoQualityParam(state, videoCodec, encodingOptions, defaultPreset);
@@ -2815,11 +2876,6 @@ namespace MediaBrowser.Controller.MediaEncoding
{
args += " " + qualityParam.Trim();
}
-
- if (!state.RunTimeTicks.HasValue)
- {
- args += " -flags -global_header";
- }
}
if (!string.IsNullOrEmpty(state.OutputVideoSync))
@@ -2917,6 +2973,5 @@ namespace MediaBrowser.Controller.MediaEncoding
string.Empty,
string.Empty).Trim();
}
-
}
}
diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingJobInfo.cs b/MediaBrowser.Controller/MediaEncoding/EncodingJobInfo.cs
index 34af3b156..1127a08de 100644
--- a/MediaBrowser.Controller/MediaEncoding/EncodingJobInfo.cs
+++ b/MediaBrowser.Controller/MediaEncoding/EncodingJobInfo.cs
@@ -9,8 +9,8 @@ using MediaBrowser.Model.Dto;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.IO;
using MediaBrowser.Model.MediaInfo;
-using MediaBrowser.Model.Session;
using MediaBrowser.Model.Net;
+using MediaBrowser.Model.Session;
namespace MediaBrowser.Controller.MediaEncoding
{
@@ -316,11 +316,7 @@ namespace MediaBrowser.Controller.MediaEncoding
{
if (VideoStream != null && VideoStream.Width.HasValue && VideoStream.Height.HasValue)
{
- var size = new ImageDimensions
- {
- Width = VideoStream.Width.Value,
- Height = VideoStream.Height.Value
- };
+ var size = new ImageDimensions(VideoStream.Width.Value, VideoStream.Height.Value);
var newSize = DrawingUtils.Resize(size,
BaseRequest.Width ?? 0,
@@ -346,11 +342,7 @@ namespace MediaBrowser.Controller.MediaEncoding
{
if (VideoStream != null && VideoStream.Width.HasValue && VideoStream.Height.HasValue)
{
- var size = new ImageDimensions
- {
- Width = VideoStream.Width.Value,
- Height = VideoStream.Height.Value
- };
+ var size = new ImageDimensions(VideoStream.Width.Value, VideoStream.Height.Value);
var newSize = DrawingUtils.Resize(size,
BaseRequest.Width ?? 0,
@@ -665,7 +657,7 @@ namespace MediaBrowser.Controller.MediaEncoding
}
public IProgress<double> Progress { get; set; }
- public virtual void ReportTranscodingProgress(TimeSpan? transcodingPosition, float framerate, double? percentComplete, long bytesTranscoded, int? bitRate)
+ public virtual void ReportTranscodingProgress(TimeSpan? transcodingPosition, float? framerate, double? percentComplete, long? bytesTranscoded, int? bitRate)
{
Progress.Report(percentComplete.Value);
}
diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingJobOptions.cs b/MediaBrowser.Controller/MediaEncoding/EncodingJobOptions.cs
index d64feb2f7..addc88174 100644
--- a/MediaBrowser.Controller/MediaEncoding/EncodingJobOptions.cs
+++ b/MediaBrowser.Controller/MediaEncoding/EncodingJobOptions.cs
@@ -1,6 +1,5 @@
using System;
using System.Collections.Generic;
-using System.Globalization;
using System.Linq;
using MediaBrowser.Model.Dlna;
using MediaBrowser.Model.Services;
diff --git a/MediaBrowser.Controller/MediaEncoding/IEncodingManager.cs b/MediaBrowser.Controller/MediaEncoding/IEncodingManager.cs
index e560999e8..15a2580af 100644
--- a/MediaBrowser.Controller/MediaEncoding/IEncodingManager.cs
+++ b/MediaBrowser.Controller/MediaEncoding/IEncodingManager.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
@@ -12,6 +14,6 @@ namespace MediaBrowser.Controller.MediaEncoding
/// <summary>
/// Refreshes the chapter images.
/// </summary>
- Task<bool> RefreshChapterImages(Video video, IDirectoryService directoryService, List<ChapterInfo> chapters, bool extractImages, bool saveChapters, CancellationToken cancellationToken);
+ Task<bool> RefreshChapterImages(Video video, IDirectoryService directoryService, IReadOnlyList<ChapterInfo> chapters, bool extractImages, bool saveChapters, CancellationToken cancellationToken);
}
}
diff --git a/MediaBrowser.Controller/MediaEncoding/JobLogger.cs b/MediaBrowser.Controller/MediaEncoding/JobLogger.cs
index ac989f6ba..c9f64c707 100644
--- a/MediaBrowser.Controller/MediaEncoding/JobLogger.cs
+++ b/MediaBrowser.Controller/MediaEncoding/JobLogger.cs
@@ -4,7 +4,6 @@ using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
-using MediaBrowser.Model.Extensions;
using Microsoft.Extensions.Logging;
namespace MediaBrowser.Controller.MediaEncoding
@@ -90,6 +89,15 @@ namespace MediaBrowser.Controller.MediaEncoding
framerate = val;
}
}
+ else if (part.StartsWith("fps=", StringComparison.OrdinalIgnoreCase))
+ {
+ var rate = part.Split(new[] { '=' }, 2)[^1];
+
+ if (float.TryParse(rate, NumberStyles.Any, _usCulture, out var val))
+ {
+ framerate = val;
+ }
+ }
else if (state.RunTimeTicks.HasValue &&
part.StartsWith("time=", StringComparison.OrdinalIgnoreCase))
{
@@ -146,7 +154,7 @@ namespace MediaBrowser.Controller.MediaEncoding
if (framerate.HasValue || percent.HasValue)
{
- state.ReportTranscodingProgress(transcodingPosition, 0, percent, 0, bitRate);
+ state.ReportTranscodingProgress(transcodingPosition, framerate, percent, bytesTranscoded, bitRate);
}
}
}
diff --git a/MediaBrowser.Controller/Net/BasePeriodicWebSocketListener.cs b/MediaBrowser.Controller/Net/BasePeriodicWebSocketListener.cs
index b193cbb55..1162bff13 100644
--- a/MediaBrowser.Controller/Net/BasePeriodicWebSocketListener.cs
+++ b/MediaBrowser.Controller/Net/BasePeriodicWebSocketListener.cs
@@ -22,7 +22,7 @@ namespace MediaBrowser.Controller.Net
/// <summary>
/// The _active connections
/// </summary>
- protected readonly List<Tuple<IWebSocketConnection, CancellationTokenSource, TStateType>> ActiveConnections =
+ private readonly List<Tuple<IWebSocketConnection, CancellationTokenSource, TStateType>> _activeConnections =
new List<Tuple<IWebSocketConnection, CancellationTokenSource, TStateType>>();
/// <summary>
@@ -98,9 +98,9 @@ namespace MediaBrowser.Controller.Net
InitialDelayMs = dueTimeMs
};
- lock (ActiveConnections)
+ lock (_activeConnections)
{
- ActiveConnections.Add(new Tuple<IWebSocketConnection, CancellationTokenSource, TStateType>(message.Connection, cancellationTokenSource, state));
+ _activeConnections.Add(new Tuple<IWebSocketConnection, CancellationTokenSource, TStateType>(message.Connection, cancellationTokenSource, state));
}
}
@@ -108,9 +108,9 @@ namespace MediaBrowser.Controller.Net
{
Tuple<IWebSocketConnection, CancellationTokenSource, TStateType>[] tuples;
- lock (ActiveConnections)
+ lock (_activeConnections)
{
- tuples = ActiveConnections
+ tuples = _activeConnections
.Where(c =>
{
if (c.Item1.State == WebSocketState.Open && !c.Item2.IsCancellationRequested)
@@ -177,9 +177,9 @@ namespace MediaBrowser.Controller.Net
/// <param name="message">The message.</param>
private void Stop(WebSocketMessageInfo message)
{
- lock (ActiveConnections)
+ lock (_activeConnections)
{
- var connection = ActiveConnections.FirstOrDefault(c => c.Item1 == message.Connection);
+ var connection = _activeConnections.FirstOrDefault(c => c.Item1 == message.Connection);
if (connection != null)
{
@@ -209,9 +209,9 @@ namespace MediaBrowser.Controller.Net
//TODO Investigate and properly fix.
}
- lock (ActiveConnections)
+ lock (_activeConnections)
{
- ActiveConnections.Remove(connection);
+ _activeConnections.Remove(connection);
}
}
@@ -223,9 +223,9 @@ namespace MediaBrowser.Controller.Net
{
if (dispose)
{
- lock (ActiveConnections)
+ lock (_activeConnections)
{
- foreach (var connection in ActiveConnections.ToArray())
+ foreach (var connection in _activeConnections.ToArray())
{
DisposeConnection(connection);
}
diff --git a/MediaBrowser.Controller/Net/IAuthService.cs b/MediaBrowser.Controller/Net/IAuthService.cs
index 4c9120e0c..9132404a0 100644
--- a/MediaBrowser.Controller/Net/IAuthService.cs
+++ b/MediaBrowser.Controller/Net/IAuthService.cs
@@ -1,3 +1,5 @@
+#nullable enable
+
using MediaBrowser.Controller.Entities;
using MediaBrowser.Model.Services;
using Microsoft.AspNetCore.Http;
@@ -7,6 +9,6 @@ namespace MediaBrowser.Controller.Net
public interface IAuthService
{
void Authenticate(IRequest request, IAuthenticationAttributes authAttribtues);
- User Authenticate(HttpRequest request, IAuthenticationAttributes authAttribtues);
+ User? Authenticate(HttpRequest request, IAuthenticationAttributes authAttribtues);
}
}
diff --git a/MediaBrowser.Controller/Net/IHttpResultFactory.cs b/MediaBrowser.Controller/Net/IHttpResultFactory.cs
index ff9ecf8af..25404fa78 100644
--- a/MediaBrowser.Controller/Net/IHttpResultFactory.cs
+++ b/MediaBrowser.Controller/Net/IHttpResultFactory.cs
@@ -2,8 +2,6 @@ using System;
using System.Collections.Generic;
using System.IO;
using System.Threading.Tasks;
-
-using MediaBrowser.Model.IO;
using MediaBrowser.Model.Services;
namespace MediaBrowser.Controller.Net
@@ -66,7 +64,7 @@ namespace MediaBrowser.Controller.Net
/// <param name="path">The path.</param>
/// <param name="fileShare">The file share.</param>
/// <returns>System.Object.</returns>
- Task<object> GetStaticFileResult(IRequest requestContext, string path, FileShareMode fileShare = FileShareMode.Read);
+ Task<object> GetStaticFileResult(IRequest requestContext, string path, FileShare fileShare = FileShare.Read);
/// <summary>
/// Gets the static file result.
diff --git a/MediaBrowser.Controller/Net/IHttpServer.cs b/MediaBrowser.Controller/Net/IHttpServer.cs
index 694e3954e..666ac1cfe 100644
--- a/MediaBrowser.Controller/Net/IHttpServer.cs
+++ b/MediaBrowser.Controller/Net/IHttpServer.cs
@@ -26,7 +26,7 @@ namespace MediaBrowser.Controller.Net
/// <summary>
/// Inits this instance.
/// </summary>
- void Init(IEnumerable<IService> services, IEnumerable<IWebSocketListener> listener, IEnumerable<string> urlPrefixes);
+ void Init(IEnumerable<Type> serviceTypes, IEnumerable<IWebSocketListener> listener, IEnumerable<string> urlPrefixes);
/// <summary>
/// If set, all requests will respond with this message
diff --git a/MediaBrowser.Controller/Net/SecurityException.cs b/MediaBrowser.Controller/Net/SecurityException.cs
index 3ccecf0eb..a5b94ea5e 100644
--- a/MediaBrowser.Controller/Net/SecurityException.cs
+++ b/MediaBrowser.Controller/Net/SecurityException.cs
@@ -2,20 +2,36 @@ using System;
namespace MediaBrowser.Controller.Net
{
+ /// <summary>
+ /// The exception that is thrown when a user is authenticated, but not authorized to access a requested resource.
+ /// </summary>
public class SecurityException : Exception
{
+ /// <summary>
+ /// Initializes a new instance of the <see cref="SecurityException"/> class.
+ /// </summary>
+ public SecurityException()
+ : base()
+ {
+ }
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="SecurityException"/> class.
+ /// </summary>
+ /// <param name="message">The message that describes the error.</param>
public SecurityException(string message)
: base(message)
{
-
}
- public SecurityExceptionType SecurityExceptionType { get; set; }
- }
-
- public enum SecurityExceptionType
- {
- Unauthenticated = 0,
- ParentalControl = 1
+ /// <summary>
+ /// Initializes a new instance of the <see cref="SecurityException"/> class.
+ /// </summary>
+ /// <param name="message">The message that describes the error</param>
+ /// <param name="innerException">The exception that is the cause of the current exception, or a null reference if no inner exception is specified.</param>
+ public SecurityException(string message, Exception innerException)
+ : base(message, innerException)
+ {
+ }
}
}
diff --git a/MediaBrowser.Controller/Net/StaticResultOptions.cs b/MediaBrowser.Controller/Net/StaticResultOptions.cs
index 7a179913a..071beaed1 100644
--- a/MediaBrowser.Controller/Net/StaticResultOptions.cs
+++ b/MediaBrowser.Controller/Net/StaticResultOptions.cs
@@ -3,8 +3,6 @@ using System.Collections.Generic;
using System.IO;
using System.Threading.Tasks;
-using MediaBrowser.Model.IO;
-
namespace MediaBrowser.Controller.Net
{
public class StaticResultOptions
@@ -24,12 +22,12 @@ namespace MediaBrowser.Controller.Net
public string Path { get; set; }
public long? ContentLength { get; set; }
- public FileShareMode FileShare { get; set; }
+ public FileShare FileShare { get; set; }
public StaticResultOptions()
{
ResponseHeaders = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
- FileShare = FileShareMode.Read;
+ FileShare = FileShare.Read;
}
}
diff --git a/MediaBrowser.Controller/Persistence/IDisplayPreferencesRepository.cs b/MediaBrowser.Controller/Persistence/IDisplayPreferencesRepository.cs
index 4424e044b..c2dcb66d7 100644
--- a/MediaBrowser.Controller/Persistence/IDisplayPreferencesRepository.cs
+++ b/MediaBrowser.Controller/Persistence/IDisplayPreferencesRepository.cs
@@ -6,30 +6,34 @@ using MediaBrowser.Model.Entities;
namespace MediaBrowser.Controller.Persistence
{
/// <summary>
- /// Interface IDisplayPreferencesRepository
+ /// Interface IDisplayPreferencesRepository.
/// </summary>
public interface IDisplayPreferencesRepository : IRepository
{
/// <summary>
- /// Saves display preferences for an item
+ /// Saves display preferences for an item.
/// </summary>
/// <param name="displayPreferences">The display preferences.</param>
/// <param name="userId">The user id.</param>
/// <param name="client">The client.</param>
/// <param name="cancellationToken">The cancellation token.</param>
- /// <returns>Task.</returns>
- void SaveDisplayPreferences(DisplayPreferences displayPreferences, string userId, string client,
- CancellationToken cancellationToken);
+ void SaveDisplayPreferences(
+ DisplayPreferences displayPreferences,
+ string userId,
+ string client,
+ CancellationToken cancellationToken);
/// <summary>
- /// Saves all display preferences for a user
+ /// Saves all display preferences for a user.
/// </summary>
/// <param name="displayPreferences">The display preferences.</param>
/// <param name="userId">The user id.</param>
/// <param name="cancellationToken">The cancellation token.</param>
- /// <returns>Task.</returns>
- void SaveAllDisplayPreferences(IEnumerable<DisplayPreferences> displayPreferences, Guid userId,
- CancellationToken cancellationToken);
+ void SaveAllDisplayPreferences(
+ IEnumerable<DisplayPreferences> displayPreferences,
+ Guid userId,
+ CancellationToken cancellationToken);
+
/// <summary>
/// Gets the display preferences.
/// </summary>
diff --git a/MediaBrowser.Controller/Persistence/IItemRepository.cs b/MediaBrowser.Controller/Persistence/IItemRepository.cs
index 5a5b7f58f..75fc43a04 100644
--- a/MediaBrowser.Controller/Persistence/IItemRepository.cs
+++ b/MediaBrowser.Controller/Persistence/IItemRepository.cs
@@ -24,8 +24,7 @@ namespace MediaBrowser.Controller.Persistence
/// Deletes the item.
/// </summary>
/// <param name="id">The identifier.</param>
- /// <param name="cancellationToken">The cancellation token.</param>
- void DeleteItem(Guid id, CancellationToken cancellationToken);
+ void DeleteItem(Guid id);
/// <summary>
/// Saves the items.
@@ -61,7 +60,7 @@ namespace MediaBrowser.Controller.Persistence
/// <summary>
/// Saves the chapters.
/// </summary>
- void SaveChapters(Guid id, List<ChapterInfo> chapters);
+ void SaveChapters(Guid id, IReadOnlyList<ChapterInfo> chapters);
/// <summary>
/// Gets the media streams.
@@ -169,4 +168,3 @@ namespace MediaBrowser.Controller.Persistence
List<string> GetAllArtistNames();
}
}
-
diff --git a/MediaBrowser.Controller/Persistence/MediaAttachmentQuery.cs b/MediaBrowser.Controller/Persistence/MediaAttachmentQuery.cs
index 91ab34aab..e3b2d4665 100644
--- a/MediaBrowser.Controller/Persistence/MediaAttachmentQuery.cs
+++ b/MediaBrowser.Controller/Persistence/MediaAttachmentQuery.cs
@@ -1,5 +1,4 @@
using System;
-using MediaBrowser.Model.Entities;
namespace MediaBrowser.Controller.Persistence
{
diff --git a/MediaBrowser.Controller/Playlists/IPlaylistManager.cs b/MediaBrowser.Controller/Playlists/IPlaylistManager.cs
index 5001f6842..544cd2643 100644
--- a/MediaBrowser.Controller/Playlists/IPlaylistManager.cs
+++ b/MediaBrowser.Controller/Playlists/IPlaylistManager.cs
@@ -29,7 +29,7 @@ namespace MediaBrowser.Controller.Playlists
/// <param name="itemIds">The item ids.</param>
/// <param name="userId">The user identifier.</param>
/// <returns>Task.</returns>
- void AddToPlaylist(string playlistId, IEnumerable<Guid> itemIds, Guid userId);
+ void AddToPlaylist(string playlistId, ICollection<Guid> itemIds, Guid userId);
/// <summary>
/// Removes from playlist.
diff --git a/MediaBrowser.Controller/Plugins/IServerEntryPoint.cs b/MediaBrowser.Controller/Plugins/IServerEntryPoint.cs
index e57929989..1e8654c4d 100644
--- a/MediaBrowser.Controller/Plugins/IServerEntryPoint.cs
+++ b/MediaBrowser.Controller/Plugins/IServerEntryPoint.cs
@@ -4,16 +4,22 @@ using System.Threading.Tasks;
namespace MediaBrowser.Controller.Plugins
{
/// <summary>
- /// Interface IServerEntryPoint
+ /// Represents an entry point for a module in the application. This interface is scanned for automatically and
+ /// provides a hook to initialize the module at application start.
+ /// The entry point can additionally be flagged as a pre-startup task by implementing the
+ /// <see cref="IRunBeforeStartup"/> interface.
/// </summary>
public interface IServerEntryPoint : IDisposable
{
/// <summary>
- /// Runs this instance.
+ /// Run the initialization for this module. This method is invoked at application start.
/// </summary>
Task RunAsync();
}
+ /// <summary>
+ /// Indicates that a <see cref="IServerEntryPoint"/> should be invoked as a pre-startup task.
+ /// </summary>
public interface IRunBeforeStartup
{
diff --git a/MediaBrowser.Controller/Providers/AlbumInfo.cs b/MediaBrowser.Controller/Providers/AlbumInfo.cs
index ac6b86c1d..dbda4843f 100644
--- a/MediaBrowser.Controller/Providers/AlbumInfo.cs
+++ b/MediaBrowser.Controller/Providers/AlbumInfo.cs
@@ -16,6 +16,7 @@ namespace MediaBrowser.Controller.Providers
/// </summary>
/// <value>The artist provider ids.</value>
public Dictionary<string, string> ArtistProviderIds { get; set; }
+
public List<SongInfo> SongInfos { get; set; }
public AlbumInfo()
diff --git a/MediaBrowser.Controller/Providers/BoxSetInfo.cs b/MediaBrowser.Controller/Providers/BoxSetInfo.cs
index 4cbe2d6ef..d23f2b9bf 100644
--- a/MediaBrowser.Controller/Providers/BoxSetInfo.cs
+++ b/MediaBrowser.Controller/Providers/BoxSetInfo.cs
@@ -2,6 +2,5 @@ namespace MediaBrowser.Controller.Providers
{
public class BoxSetInfo : ItemLookupInfo
{
-
}
}
diff --git a/MediaBrowser.Controller/Providers/DirectoryService.cs b/MediaBrowser.Controller/Providers/DirectoryService.cs
index 303b03a21..b7640c205 100644
--- a/MediaBrowser.Controller/Providers/DirectoryService.cs
+++ b/MediaBrowser.Controller/Providers/DirectoryService.cs
@@ -26,7 +26,6 @@ namespace MediaBrowser.Controller.Providers
{
entries = _fileSystem.GetFileSystemEntries(path).ToArray();
- //_cache.TryAdd(path, entries);
_cache[path] = entries;
}
@@ -56,7 +55,6 @@ namespace MediaBrowser.Controller.Providers
if (file != null && file.Exists)
{
- //_fileCache.TryAdd(path, file);
_fileCache[path] = file;
}
else
@@ -66,15 +64,12 @@ namespace MediaBrowser.Controller.Providers
}
return file;
- //return _fileSystem.GetFileInfo(path);
}
- public List<string> GetFilePaths(string path)
- {
- return GetFilePaths(path, false);
- }
+ public IReadOnlyList<string> GetFilePaths(string path)
+ => GetFilePaths(path, false);
- public List<string> GetFilePaths(string path, bool clearCache)
+ public IReadOnlyList<string> GetFilePaths(string path, bool clearCache)
{
if (clearCache || !_filePathCache.TryGetValue(path, out List<string> result))
{
diff --git a/MediaBrowser.Controller/Providers/DynamicImageInfo.cs b/MediaBrowser.Controller/Providers/DynamicImageInfo.cs
deleted file mode 100644
index 0791783c6..000000000
--- a/MediaBrowser.Controller/Providers/DynamicImageInfo.cs
+++ /dev/null
@@ -1,10 +0,0 @@
-using MediaBrowser.Model.Entities;
-
-namespace MediaBrowser.Controller.Providers
-{
- public class DynamicImageInfo
- {
- public string ImageId { get; set; }
- public ImageType Type { get; set; }
- }
-}
diff --git a/MediaBrowser.Controller/Providers/DynamicImageResponse.cs b/MediaBrowser.Controller/Providers/DynamicImageResponse.cs
index 11c7ccbe5..7c1371702 100644
--- a/MediaBrowser.Controller/Providers/DynamicImageResponse.cs
+++ b/MediaBrowser.Controller/Providers/DynamicImageResponse.cs
@@ -8,9 +8,13 @@ namespace MediaBrowser.Controller.Providers
public class DynamicImageResponse
{
public string Path { get; set; }
+
public MediaProtocol Protocol { get; set; }
+
public Stream Stream { get; set; }
+
public ImageFormat Format { get; set; }
+
public bool HasImage { get; set; }
public void SetFormatFromMimeType(string mimeType)
diff --git a/MediaBrowser.Controller/Providers/EpisodeInfo.cs b/MediaBrowser.Controller/Providers/EpisodeInfo.cs
index 6ecf4edc5..55c41ff82 100644
--- a/MediaBrowser.Controller/Providers/EpisodeInfo.cs
+++ b/MediaBrowser.Controller/Providers/EpisodeInfo.cs
@@ -10,6 +10,7 @@ namespace MediaBrowser.Controller.Providers
public int? IndexNumberEnd { get; set; }
public bool IsMissingEpisode { get; set; }
+
public string SeriesDisplayOrder { get; set; }
public EpisodeInfo()
diff --git a/MediaBrowser.Controller/Providers/ExtraInfo.cs b/MediaBrowser.Controller/Providers/ExtraInfo.cs
deleted file mode 100644
index 413ff2e78..000000000
--- a/MediaBrowser.Controller/Providers/ExtraInfo.cs
+++ /dev/null
@@ -1,15 +0,0 @@
-using MediaBrowser.Model.Entities;
-
-namespace MediaBrowser.Controller.Providers
-{
- public class ExtraInfo
- {
- public string Path { get; set; }
-
- public LocationType LocationType { get; set; }
-
- public bool IsDownloadable { get; set; }
-
- public ExtraType ExtraType { get; set; }
- }
-}
diff --git a/MediaBrowser.Controller/Providers/ExtraSource.cs b/MediaBrowser.Controller/Providers/ExtraSource.cs
deleted file mode 100644
index 46b246fbc..000000000
--- a/MediaBrowser.Controller/Providers/ExtraSource.cs
+++ /dev/null
@@ -1,9 +0,0 @@
-namespace MediaBrowser.Controller.Providers
-{
- public enum ExtraSource
- {
- Local = 1,
- Metadata = 2,
- Remote = 3
- }
-}
diff --git a/MediaBrowser.Controller/Providers/ICustomMetadataProvider.cs b/MediaBrowser.Controller/Providers/ICustomMetadataProvider.cs
index 2d5a0b364..6b4c9feb5 100644
--- a/MediaBrowser.Controller/Providers/ICustomMetadataProvider.cs
+++ b/MediaBrowser.Controller/Providers/ICustomMetadataProvider.cs
@@ -13,7 +13,7 @@ namespace MediaBrowser.Controller.Providers
where TItemType : BaseItem
{
/// <summary>
- /// Fetches the asynchronous.
+ /// Fetches the metadata asynchronously.
/// </summary>
/// <param name="item">The item.</param>
/// <param name="options">The options.</param>
diff --git a/MediaBrowser.Controller/Providers/IDirectoryService.cs b/MediaBrowser.Controller/Providers/IDirectoryService.cs
index 8059d2bd1..949a17740 100644
--- a/MediaBrowser.Controller/Providers/IDirectoryService.cs
+++ b/MediaBrowser.Controller/Providers/IDirectoryService.cs
@@ -6,10 +6,13 @@ namespace MediaBrowser.Controller.Providers
public interface IDirectoryService
{
FileSystemMetadata[] GetFileSystemEntries(string path);
+
List<FileSystemMetadata> GetFiles(string path);
+
FileSystemMetadata GetFile(string path);
- List<string> GetFilePaths(string path);
- List<string> GetFilePaths(string path, bool clearCache);
+ IReadOnlyList<string> GetFilePaths(string path);
+
+ IReadOnlyList<string> GetFilePaths(string path, bool clearCache);
}
}
diff --git a/MediaBrowser.Controller/Providers/IExtrasProvider.cs b/MediaBrowser.Controller/Providers/IExtrasProvider.cs
deleted file mode 100644
index fa31635cb..000000000
--- a/MediaBrowser.Controller/Providers/IExtrasProvider.cs
+++ /dev/null
@@ -1,20 +0,0 @@
-using MediaBrowser.Controller.Entities;
-
-namespace MediaBrowser.Controller.Providers
-{
- public interface IExtrasProvider
- {
- /// <summary>
- /// Gets the name.
- /// </summary>
- /// <value>The name.</value>
- string Name { get; }
-
- /// <summary>
- /// Supportses the specified item.
- /// </summary>
- /// <param name="item">The item.</param>
- /// <returns><c>true</c> if XXXX, <c>false</c> otherwise.</returns>
- bool Supports(BaseItem item);
- }
-}
diff --git a/MediaBrowser.Controller/Providers/IForcedProvider.cs b/MediaBrowser.Controller/Providers/IForcedProvider.cs
index 35fa29d94..5ae4a56ef 100644
--- a/MediaBrowser.Controller/Providers/IForcedProvider.cs
+++ b/MediaBrowser.Controller/Providers/IForcedProvider.cs
@@ -1,7 +1,7 @@
namespace MediaBrowser.Controller.Providers
{
/// <summary>
- /// This is a marker interface that will cause a provider to run even if IsLocked=true
+ /// This is a marker interface that will cause a provider to run even if an item is locked from changes.
/// </summary>
public interface IForcedProvider
{
diff --git a/MediaBrowser.Controller/Providers/IImageEnhancer.cs b/MediaBrowser.Controller/Providers/IImageEnhancer.cs
deleted file mode 100644
index c27c00ca2..000000000
--- a/MediaBrowser.Controller/Providers/IImageEnhancer.cs
+++ /dev/null
@@ -1,61 +0,0 @@
-using System.Threading.Tasks;
-using MediaBrowser.Controller.Entities;
-using MediaBrowser.Model.Drawing;
-using MediaBrowser.Model.Entities;
-
-namespace MediaBrowser.Controller.Providers
-{
- public interface IImageEnhancer
- {
- /// <summary>
- /// Return true only if the given image for the given item will be enhanced by this enhancer.
- /// </summary>
- /// <param name="item">The item.</param>
- /// <param name="imageType">Type of the image.</param>
- /// <returns><c>true</c> if this enhancer will enhance the supplied image for the supplied item, <c>false</c> otherwise</returns>
- bool Supports(BaseItem item, ImageType imageType);
-
- /// <summary>
- /// Gets the priority or order in which this enhancer should be run.
- /// </summary>
- /// <value>The priority.</value>
- MetadataProviderPriority Priority { get; }
-
- /// <summary>
- /// Return a key incorporating all configuration information related to this item
- /// </summary>
- /// <param name="item">The item.</param>
- /// <param name="imageType">Type of the image.</param>
- /// <returns>Cache key relating to the current state of this item and configuration</returns>
- string GetConfigurationCacheKey(BaseItem item, ImageType imageType);
-
- /// <summary>
- /// Gets the size of the enhanced image.
- /// </summary>
- /// <param name="item">The item.</param>
- /// <param name="imageType">Type of the image.</param>
- /// <param name="imageIndex">Index of the image.</param>
- /// <param name="originalImageSize">Size of the original image.</param>
- /// <returns>ImageSize.</returns>
- ImageDimensions GetEnhancedImageSize(BaseItem item, ImageType imageType, int imageIndex, ImageDimensions originalImageSize);
-
- EnhancedImageInfo GetEnhancedImageInfo(BaseItem item, string inputFile, ImageType imageType, int imageIndex);
-
- /// <summary>
- /// Enhances the image async.
- /// </summary>
- /// <param name="item">The item.</param>
- /// <param name="inputFile">The input file.</param>
- /// <param name="outputFile">The output file.</param>
- /// <param name="imageType">Type of the image.</param>
- /// <param name="imageIndex">Index of the image.</param>
- /// <returns>Task{Image}.</returns>
- /// <exception cref="System.ArgumentNullException"></exception>
- Task EnhanceImageAsync(BaseItem item, string inputFile, string outputFile, ImageType imageType, int imageIndex);
- }
-
- public class EnhancedImageInfo
- {
- public bool RequiresTransparency { get; set; }
- }
-}
diff --git a/MediaBrowser.Controller/Providers/IImageProvider.cs b/MediaBrowser.Controller/Providers/IImageProvider.cs
index 2df3d5ff8..29ab323f8 100644
--- a/MediaBrowser.Controller/Providers/IImageProvider.cs
+++ b/MediaBrowser.Controller/Providers/IImageProvider.cs
@@ -3,7 +3,7 @@ using MediaBrowser.Controller.Entities;
namespace MediaBrowser.Controller.Providers
{
/// <summary>
- /// Interface IImageProvider
+ /// Interface IImageProvider.
/// </summary>
public interface IImageProvider
{
@@ -14,10 +14,10 @@ namespace MediaBrowser.Controller.Providers
string Name { get; }
/// <summary>
- /// Supportses the specified item.
+ /// Supports the specified item.
/// </summary>
/// <param name="item">The item.</param>
- /// <returns><c>true</c> if XXXX, <c>false</c> otherwise</returns>
+ /// <returns><c>true</c> if the provider supports the item.</returns>
bool Supports(BaseItem item);
}
}
diff --git a/MediaBrowser.Controller/Providers/ILocalImageFileProvider.cs b/MediaBrowser.Controller/Providers/ILocalImageFileProvider.cs
deleted file mode 100644
index 72bc56390..000000000
--- a/MediaBrowser.Controller/Providers/ILocalImageFileProvider.cs
+++ /dev/null
@@ -1,10 +0,0 @@
-using System.Collections.Generic;
-using MediaBrowser.Controller.Entities;
-
-namespace MediaBrowser.Controller.Providers
-{
- public interface ILocalImageFileProvider : ILocalImageProvider
- {
- List<LocalImageInfo> GetImages(BaseItem item, IDirectoryService directoryService);
- }
-}
diff --git a/MediaBrowser.Controller/Providers/ILocalImageProvider.cs b/MediaBrowser.Controller/Providers/ILocalImageProvider.cs
index 09aaab3de..463c81376 100644
--- a/MediaBrowser.Controller/Providers/ILocalImageProvider.cs
+++ b/MediaBrowser.Controller/Providers/ILocalImageProvider.cs
@@ -1,9 +1,13 @@
+using System.Collections.Generic;
+using MediaBrowser.Controller.Entities;
+
namespace MediaBrowser.Controller.Providers
{
/// <summary>
- /// This is just a marker interface
+ /// This is just a marker interface.
/// </summary>
public interface ILocalImageProvider : IImageProvider
{
+ List<LocalImageInfo> GetImages(BaseItem item, IDirectoryService directoryService);
}
}
diff --git a/MediaBrowser.Controller/Providers/ILocalMetadataProvider.cs b/MediaBrowser.Controller/Providers/ILocalMetadataProvider.cs
index 2a2c379f6..44fb1b394 100644
--- a/MediaBrowser.Controller/Providers/ILocalMetadataProvider.cs
+++ b/MediaBrowser.Controller/Providers/ILocalMetadataProvider.cs
@@ -17,8 +17,9 @@ namespace MediaBrowser.Controller.Providers
/// <param name="info">The information.</param>
/// <param name="directoryService">The directory service.</param>
/// <param name="cancellationToken">The cancellation token.</param>
- /// <returns>Task{MetadataResult{`0}}.</returns>
- Task<MetadataResult<TItemType>> GetMetadata(ItemInfo info,
+ /// <returns>Task{MetadataResult{0}}.</returns>
+ Task<MetadataResult<TItemType>> GetMetadata(
+ ItemInfo info,
IDirectoryService directoryService,
CancellationToken cancellationToken);
}
diff --git a/MediaBrowser.Controller/Providers/IMetadataService.cs b/MediaBrowser.Controller/Providers/IMetadataService.cs
index 49f6a7830..21204e6d3 100644
--- a/MediaBrowser.Controller/Providers/IMetadataService.cs
+++ b/MediaBrowser.Controller/Providers/IMetadataService.cs
@@ -12,8 +12,9 @@ namespace MediaBrowser.Controller.Providers
/// Determines whether this instance can refresh the specified item.
/// </summary>
/// <param name="item">The item.</param>
- /// <returns><c>true</c> if this instance can refresh the specified item; otherwise, <c>false</c>.</returns>
+ /// <returns><c>true</c> if this instance can refresh the specified item.</returns>
bool CanRefresh(BaseItem item);
+
bool CanRefreshPrimary(Type type);
/// <summary>
diff --git a/MediaBrowser.Controller/Providers/IPreRefreshProvider.cs b/MediaBrowser.Controller/Providers/IPreRefreshProvider.cs
index 058010e1a..28da27ae7 100644
--- a/MediaBrowser.Controller/Providers/IPreRefreshProvider.cs
+++ b/MediaBrowser.Controller/Providers/IPreRefreshProvider.cs
@@ -2,6 +2,5 @@ namespace MediaBrowser.Controller.Providers
{
public interface IPreRefreshProvider : ICustomMetadataProvider
{
-
}
}
diff --git a/MediaBrowser.Controller/Providers/IProviderManager.cs b/MediaBrowser.Controller/Providers/IProviderManager.cs
index 925ace895..254b27460 100644
--- a/MediaBrowser.Controller/Providers/IProviderManager.cs
+++ b/MediaBrowser.Controller/Providers/IProviderManager.cs
@@ -14,7 +14,7 @@ using MediaBrowser.Model.Providers;
namespace MediaBrowser.Controller.Providers
{
/// <summary>
- /// Interface IProviderManager
+ /// Interface IProviderManager.
/// </summary>
public interface IProviderManager
{
@@ -159,13 +159,17 @@ namespace MediaBrowser.Controller.Providers
Dictionary<Guid, Guid> GetRefreshQueue();
void OnRefreshStart(BaseItem item);
+
void OnRefreshProgress(BaseItem item, double progress);
+
void OnRefreshComplete(BaseItem item);
double? GetRefreshProgress(Guid id);
event EventHandler<GenericEventArgs<BaseItem>> RefreshStarted;
+
event EventHandler<GenericEventArgs<BaseItem>> RefreshCompleted;
+
event EventHandler<GenericEventArgs<Tuple<BaseItem, double>>> RefreshProgress;
}
diff --git a/MediaBrowser.Controller/Providers/IRemoteImageProvider.cs b/MediaBrowser.Controller/Providers/IRemoteImageProvider.cs
index e56bba3e3..68a968f90 100644
--- a/MediaBrowser.Controller/Providers/IRemoteImageProvider.cs
+++ b/MediaBrowser.Controller/Providers/IRemoteImageProvider.cs
@@ -9,7 +9,7 @@ using MediaBrowser.Model.Providers;
namespace MediaBrowser.Controller.Providers
{
/// <summary>
- /// Interface IImageProvider
+ /// Interface IImageProvider.
/// </summary>
public interface IRemoteImageProvider : IImageProvider
{
diff --git a/MediaBrowser.Controller/Providers/ItemInfo.cs b/MediaBrowser.Controller/Providers/ItemInfo.cs
index f29a8aa70..d61153dfa 100644
--- a/MediaBrowser.Controller/Providers/ItemInfo.cs
+++ b/MediaBrowser.Controller/Providers/ItemInfo.cs
@@ -23,10 +23,15 @@ namespace MediaBrowser.Controller.Providers
}
public Type ItemType { get; set; }
+
public string Path { get; set; }
+
public string ContainingFolderPath { get; set; }
+
public VideoType VideoType { get; set; }
+
public bool IsInMixedFolder { get; set; }
+
public bool IsPlaceHolder { get; set; }
}
}
diff --git a/MediaBrowser.Controller/Providers/ItemLookupInfo.cs b/MediaBrowser.Controller/Providers/ItemLookupInfo.cs
index 0aaab9a94..4707b0c7f 100644
--- a/MediaBrowser.Controller/Providers/ItemLookupInfo.cs
+++ b/MediaBrowser.Controller/Providers/ItemLookupInfo.cs
@@ -11,29 +11,37 @@ namespace MediaBrowser.Controller.Providers
/// </summary>
/// <value>The name.</value>
public string Name { get; set; }
+
/// <summary>
/// Gets or sets the metadata language.
/// </summary>
/// <value>The metadata language.</value>
public string MetadataLanguage { get; set; }
+
/// <summary>
/// Gets or sets the metadata country code.
/// </summary>
/// <value>The metadata country code.</value>
public string MetadataCountryCode { get; set; }
+
/// <summary>
/// Gets or sets the provider ids.
/// </summary>
/// <value>The provider ids.</value>
public Dictionary<string, string> ProviderIds { get; set; }
+
/// <summary>
/// Gets or sets the year.
/// </summary>
/// <value>The year.</value>
public int? Year { get; set; }
+
public int? IndexNumber { get; set; }
+
public int? ParentIndexNumber { get; set; }
+
public DateTime? PremiereDate { get; set; }
+
public bool IsAutomated { get; set; }
public ItemLookupInfo()
diff --git a/MediaBrowser.Controller/Providers/LocalImageInfo.cs b/MediaBrowser.Controller/Providers/LocalImageInfo.cs
index 24cded79b..184281025 100644
--- a/MediaBrowser.Controller/Providers/LocalImageInfo.cs
+++ b/MediaBrowser.Controller/Providers/LocalImageInfo.cs
@@ -6,6 +6,7 @@ namespace MediaBrowser.Controller.Providers
public class LocalImageInfo
{
public FileSystemMetadata FileInfo { get; set; }
+
public ImageType Type { get; set; }
}
}
diff --git a/MediaBrowser.Controller/Providers/MetadataProviderPriority.cs b/MediaBrowser.Controller/Providers/MetadataProviderPriority.cs
deleted file mode 100644
index 0076bb972..000000000
--- a/MediaBrowser.Controller/Providers/MetadataProviderPriority.cs
+++ /dev/null
@@ -1,39 +0,0 @@
-namespace MediaBrowser.Controller.Providers
-{
- /// <summary>
- /// Determines when a provider should execute, relative to others
- /// </summary>
- public enum MetadataProviderPriority
- {
- // Run this provider at the beginning
- /// <summary>
- /// The first
- /// </summary>
- First = 1,
-
- // Run this provider after all first priority providers
- /// <summary>
- /// The second
- /// </summary>
- Second = 2,
-
- // Run this provider after all second priority providers
- /// <summary>
- /// The third
- /// </summary>
- Third = 3,
-
- /// <summary>
- /// The fourth
- /// </summary>
- Fourth = 4,
-
- Fifth = 5,
-
- // Run this provider last
- /// <summary>
- /// The last
- /// </summary>
- Last = 999
- }
-}
diff --git a/MediaBrowser.Controller/Providers/MetadataRefreshOptions.cs b/MediaBrowser.Controller/Providers/MetadataRefreshOptions.cs
index b3eb8cdd1..0a473b80c 100644
--- a/MediaBrowser.Controller/Providers/MetadataRefreshOptions.cs
+++ b/MediaBrowser.Controller/Providers/MetadataRefreshOptions.cs
@@ -13,11 +13,13 @@ namespace MediaBrowser.Controller.Providers
public bool ReplaceAllMetadata { get; set; }
public MetadataRefreshMode MetadataRefreshMode { get; set; }
+
public RemoteSearchResult SearchResult { get; set; }
public string[] RefreshPaths { get; set; }
public bool ForceSave { get; set; }
+
public bool EnableRemoteContentProbe { get; set; }
public MetadataRefreshOptions(IDirectoryService directoryService)
diff --git a/MediaBrowser.Controller/Providers/MetadataResult.cs b/MediaBrowser.Controller/Providers/MetadataResult.cs
index ebff81b7f..59adaedfa 100644
--- a/MediaBrowser.Controller/Providers/MetadataResult.cs
+++ b/MediaBrowser.Controller/Providers/MetadataResult.cs
@@ -8,6 +8,7 @@ namespace MediaBrowser.Controller.Providers
public class MetadataResult<T>
{
public List<LocalImageInfo> Images { get; set; }
+
public List<UserItemData> UserDataList { get; set; }
public MetadataResult()
@@ -19,10 +20,15 @@ namespace MediaBrowser.Controller.Providers
public List<PersonInfo> People { get; set; }
public bool HasMetadata { get; set; }
+
public T Item { get; set; }
+
public string ResultLanguage { get; set; }
+
public string Provider { get; set; }
+
public bool QueriedById { get; set; }
+
public void AddPerson(PersonInfo p)
{
if (People == null)
diff --git a/MediaBrowser.Controller/Providers/MovieInfo.cs b/MediaBrowser.Controller/Providers/MovieInfo.cs
index c9a766fe7..5b2c3ed03 100644
--- a/MediaBrowser.Controller/Providers/MovieInfo.cs
+++ b/MediaBrowser.Controller/Providers/MovieInfo.cs
@@ -2,6 +2,5 @@ namespace MediaBrowser.Controller.Providers
{
public class MovieInfo : ItemLookupInfo
{
-
}
}
diff --git a/MediaBrowser.Controller/Providers/PersonLookupInfo.cs b/MediaBrowser.Controller/Providers/PersonLookupInfo.cs
index 3fa6e50b7..a6218c039 100644
--- a/MediaBrowser.Controller/Providers/PersonLookupInfo.cs
+++ b/MediaBrowser.Controller/Providers/PersonLookupInfo.cs
@@ -2,6 +2,5 @@ namespace MediaBrowser.Controller.Providers
{
public class PersonLookupInfo : ItemLookupInfo
{
-
}
}
diff --git a/MediaBrowser.Controller/Providers/RemoteSearchQuery.cs b/MediaBrowser.Controller/Providers/RemoteSearchQuery.cs
index 078125673..a2ac6c9ae 100644
--- a/MediaBrowser.Controller/Providers/RemoteSearchQuery.cs
+++ b/MediaBrowser.Controller/Providers/RemoteSearchQuery.cs
@@ -10,14 +10,14 @@ namespace MediaBrowser.Controller.Providers
public Guid ItemId { get; set; }
/// <summary>
- /// If set will only search within the given provider
+ /// Will only search within the given provider when set.
/// </summary>
public string SearchProviderName { get; set; }
/// <summary>
- /// Gets or sets a value indicating whether [include disabled providers].
+ /// Gets or sets a value indicating whether disabled providers should be included.
/// </summary>
- /// <value><c>true</c> if [include disabled providers]; otherwise, <c>false</c>.</value>
+ /// <value><c>true</c> if disabled providers should be included.</value>
public bool IncludeDisabledProviders { get; set; }
}
}
diff --git a/MediaBrowser.Controller/Session/SessionInfo.cs b/MediaBrowser.Controller/Session/SessionInfo.cs
index 63ed43a83..2ba7c9fec 100644
--- a/MediaBrowser.Controller/Session/SessionInfo.cs
+++ b/MediaBrowser.Controller/Session/SessionInfo.cs
@@ -115,6 +115,8 @@ namespace MediaBrowser.Controller.Session
public BaseItem FullNowPlayingItem { get; set; }
+ public BaseItemDto NowViewingItem { get; set; }
+
/// <summary>
/// Gets or sets the device id.
/// </summary>
@@ -239,11 +241,6 @@ namespace MediaBrowser.Controller.Session
SessionControllers = controllers.ToArray();
}
- public bool ContainsUser(string userId)
- {
- return ContainsUser(new Guid(userId));
- }
-
public bool ContainsUser(Guid userId)
{
if (UserId.Equals(userId))
@@ -253,7 +250,7 @@ namespace MediaBrowser.Controller.Session
foreach (var additionalUser in AdditionalUsers)
{
- if (userId.Equals(userId))
+ if (additionalUser.UserId.Equals(userId))
{
return true;
}
@@ -308,7 +305,7 @@ namespace MediaBrowser.Controller.Session
var newPositionTicks = positionTicks + ProgressIncrement;
var item = progressInfo.Item;
- long? runtimeTicks = item == null ? null : item.RunTimeTicks;
+ long? runtimeTicks = item?.RunTimeTicks;
// Don't report beyond the runtime
if (runtimeTicks.HasValue && newPositionTicks >= runtimeTicks.Value)
diff --git a/MediaBrowser.Controller/Sorting/AlphanumComparator.cs b/MediaBrowser.Controller/Sorting/AlphanumComparator.cs
new file mode 100644
index 000000000..de7f72d1c
--- /dev/null
+++ b/MediaBrowser.Controller/Sorting/AlphanumComparator.cs
@@ -0,0 +1,135 @@
+#nullable enable
+
+using System;
+using System.Collections.Generic;
+
+namespace MediaBrowser.Controller.Sorting
+{
+ public class AlphanumComparator : IComparer<string?>
+ {
+ public static int CompareValues(string? s1, string? s2)
+ {
+ if (s1 == null && s2 == null)
+ {
+ return 0;
+ }
+ else if (s1 == null)
+ {
+ return -1;
+ }
+ else if (s2 == null)
+ {
+ return 1;
+ }
+
+ int len1 = s1.Length;
+ int len2 = s2.Length;
+
+ // Early return for empty strings
+ if (len1 == 0 && len2 == 0)
+ {
+ return 0;
+ }
+ else if (len1 == 0)
+ {
+ return -1;
+ }
+ else if (len2 == 0)
+ {
+ return 1;
+ }
+
+ int pos1 = 0;
+ int pos2 = 0;
+
+ do
+ {
+ int start1 = pos1;
+ int start2 = pos2;
+
+ bool isNum1 = char.IsDigit(s1[pos1++]);
+ bool isNum2 = char.IsDigit(s2[pos2++]);
+
+ while (pos1 < len1 && char.IsDigit(s1[pos1]) == isNum1)
+ {
+ pos1++;
+ }
+
+ while (pos2 < len2 && char.IsDigit(s2[pos2]) == isNum2)
+ {
+ pos2++;
+ }
+
+ var span1 = s1.AsSpan(start1, pos1 - start1);
+ var span2 = s2.AsSpan(start2, pos2 - start2);
+
+ if (isNum1 && isNum2)
+ {
+ // Trim leading zeros so we can compare the length
+ // of the strings to find the largest number
+ span1 = span1.TrimStart('0');
+ span2 = span2.TrimStart('0');
+ var span1Len = span1.Length;
+ var span2Len = span2.Length;
+ if (span1Len < span2Len)
+ {
+ return -1;
+ }
+ else if (span1Len > span2Len)
+ {
+ return 1;
+ }
+ else if (span1Len >= 20) // Number is probably too big for a ulong
+ {
+ // Trim all the first digits that are the same
+ int i = 0;
+ while (i < span1Len && span1[i] == span2[i])
+ {
+ i++;
+ }
+
+ // If there are no more digits it's the same number
+ if (i == span1Len)
+ {
+ continue;
+ }
+
+ // Only need to compare the most significant digit
+ span1 = span1.Slice(i, 1);
+ span2 = span2.Slice(i, 1);
+ }
+
+ if (!ulong.TryParse(span1, out var num1)
+ || !ulong.TryParse(span2, out var num2))
+ {
+ return 0;
+ }
+ else if (num1 < num2)
+ {
+ return -1;
+ }
+ else if (num1 > num2)
+ {
+ return 1;
+ }
+ }
+ else
+ {
+ int result = span1.CompareTo(span2, StringComparison.InvariantCulture);
+ if (result != 0)
+ {
+ return result;
+ }
+ }
+ } while (pos1 < len1 && pos2 < len2);
+
+ return len1 - len2;
+ }
+
+ /// <inheritdoc />
+ public int Compare(string x, string y)
+ {
+ return CompareValues(x, y);
+ }
+ }
+}
diff --git a/MediaBrowser.Controller/Sorting/SortExtensions.cs b/MediaBrowser.Controller/Sorting/SortExtensions.cs
index 111f4f17f..2a68f4678 100644
--- a/MediaBrowser.Controller/Sorting/SortExtensions.cs
+++ b/MediaBrowser.Controller/Sorting/SortExtensions.cs
@@ -1,143 +1,30 @@
using System;
using System.Collections.Generic;
using System.Linq;
-using System.Text;
namespace MediaBrowser.Controller.Sorting
{
public static class SortExtensions
{
+ private static readonly AlphanumComparator _comparer = new AlphanumComparator();
public static IEnumerable<T> OrderByString<T>(this IEnumerable<T> list, Func<T, string> getName)
{
- return list.OrderBy(getName, new AlphanumComparator());
+ return list.OrderBy(getName, _comparer);
}
public static IEnumerable<T> OrderByStringDescending<T>(this IEnumerable<T> list, Func<T, string> getName)
{
- return list.OrderByDescending(getName, new AlphanumComparator());
+ return list.OrderByDescending(getName, _comparer);
}
public static IOrderedEnumerable<T> ThenByString<T>(this IOrderedEnumerable<T> list, Func<T, string> getName)
{
- return list.ThenBy(getName, new AlphanumComparator());
+ return list.ThenBy(getName, _comparer);
}
public static IOrderedEnumerable<T> ThenByStringDescending<T>(this IOrderedEnumerable<T> list, Func<T, string> getName)
{
- return list.ThenByDescending(getName, new AlphanumComparator());
- }
-
- private class AlphanumComparator : IComparer<string>
- {
- private enum ChunkType { Alphanumeric, Numeric };
-
- private static bool InChunk(char ch, char otherCh)
- {
- var type = ChunkType.Alphanumeric;
-
- if (char.IsDigit(otherCh))
- {
- type = ChunkType.Numeric;
- }
-
- if ((type == ChunkType.Alphanumeric && char.IsDigit(ch))
- || (type == ChunkType.Numeric && !char.IsDigit(ch)))
- {
- return false;
- }
-
- return true;
- }
-
- public static int CompareValues(string s1, string s2)
- {
- if (s1 == null || s2 == null)
- {
- return 0;
- }
-
- int thisMarker = 0, thisNumericChunk = 0;
- int thatMarker = 0, thatNumericChunk = 0;
-
- while ((thisMarker < s1.Length) || (thatMarker < s2.Length))
- {
- if (thisMarker >= s1.Length)
- {
- return -1;
- }
- else if (thatMarker >= s2.Length)
- {
- return 1;
- }
- char thisCh = s1[thisMarker];
- char thatCh = s2[thatMarker];
-
- var thisChunk = new StringBuilder();
- var thatChunk = new StringBuilder();
-
- while ((thisMarker < s1.Length) && (thisChunk.Length == 0 || InChunk(thisCh, thisChunk[0])))
- {
- thisChunk.Append(thisCh);
- thisMarker++;
-
- if (thisMarker < s1.Length)
- {
- thisCh = s1[thisMarker];
- }
- }
-
- while ((thatMarker < s2.Length) && (thatChunk.Length == 0 || InChunk(thatCh, thatChunk[0])))
- {
- thatChunk.Append(thatCh);
- thatMarker++;
-
- if (thatMarker < s2.Length)
- {
- thatCh = s2[thatMarker];
- }
- }
-
- int result = 0;
- // If both chunks contain numeric characters, sort them numerically
- if (char.IsDigit(thisChunk[0]) && char.IsDigit(thatChunk[0]))
- {
- if (!int.TryParse(thisChunk.ToString(), out thisNumericChunk))
- {
- return 0;
- }
- if (!int.TryParse(thatChunk.ToString(), out thatNumericChunk))
- {
- return 0;
- }
-
- if (thisNumericChunk < thatNumericChunk)
- {
- result = -1;
- }
-
- if (thisNumericChunk > thatNumericChunk)
- {
- result = 1;
- }
- }
- else
- {
- result = thisChunk.ToString().CompareTo(thatChunk.ToString());
- }
-
- if (result != 0)
- {
- return result;
- }
- }
-
- return 0;
- }
-
- public int Compare(string x, string y)
- {
- return CompareValues(x, y);
- }
+ return list.ThenByDescending(getName, _comparer);
}
}
}
diff --git a/MediaBrowser.Controller/Sorting/SortHelper.cs b/MediaBrowser.Controller/Sorting/SortHelper.cs
deleted file mode 100644
index 05981d975..000000000
--- a/MediaBrowser.Controller/Sorting/SortHelper.cs
+++ /dev/null
@@ -1,25 +0,0 @@
-namespace MediaBrowser.Controller.Sorting
-{
- public static class SortHelper
- {
- private enum ChunkType { Alphanumeric, Numeric };
-
- public static bool InChunk(char ch, char otherCh)
- {
- var type = ChunkType.Alphanumeric;
-
- if (char.IsDigit(otherCh))
- {
- type = ChunkType.Numeric;
- }
-
- if ((type == ChunkType.Alphanumeric && char.IsDigit(ch))
- || (type == ChunkType.Numeric && !char.IsDigit(ch)))
- {
- return false;
- }
-
- return true;
- }
- }
-}
diff --git a/MediaBrowser.LocalMetadata/Images/CollectionFolderImageProvider.cs b/MediaBrowser.LocalMetadata/Images/CollectionFolderImageProvider.cs
index 206e761bb..3bab1243c 100644
--- a/MediaBrowser.LocalMetadata/Images/CollectionFolderImageProvider.cs
+++ b/MediaBrowser.LocalMetadata/Images/CollectionFolderImageProvider.cs
@@ -5,7 +5,7 @@ using MediaBrowser.Model.IO;
namespace MediaBrowser.LocalMetadata.Images
{
- public class CollectionFolderLocalImageProvider : ILocalImageFileProvider, IHasOrder
+ public class CollectionFolderLocalImageProvider : ILocalImageProvider, IHasOrder
{
private readonly IFileSystem _fileSystem;
diff --git a/MediaBrowser.LocalMetadata/Images/EpisodeLocalImageProvider.cs b/MediaBrowser.LocalMetadata/Images/EpisodeLocalImageProvider.cs
index 443f3fbb5..2f4cca5ff 100644
--- a/MediaBrowser.LocalMetadata/Images/EpisodeLocalImageProvider.cs
+++ b/MediaBrowser.LocalMetadata/Images/EpisodeLocalImageProvider.cs
@@ -10,7 +10,7 @@ using MediaBrowser.Model.IO;
namespace MediaBrowser.LocalMetadata.Images
{
- public class EpisodeLocalLocalImageProvider : ILocalImageFileProvider, IHasOrder
+ public class EpisodeLocalLocalImageProvider : ILocalImageProvider, IHasOrder
{
private readonly IFileSystem _fileSystem;
diff --git a/MediaBrowser.LocalMetadata/Images/InternalMetadataFolderImageProvider.cs b/MediaBrowser.LocalMetadata/Images/InternalMetadataFolderImageProvider.cs
index 25a8ad596..795933ce9 100644
--- a/MediaBrowser.LocalMetadata/Images/InternalMetadataFolderImageProvider.cs
+++ b/MediaBrowser.LocalMetadata/Images/InternalMetadataFolderImageProvider.cs
@@ -9,7 +9,7 @@ using Microsoft.Extensions.Logging;
namespace MediaBrowser.LocalMetadata.Images
{
- public class InternalMetadataFolderImageProvider : ILocalImageFileProvider, IHasOrder
+ public class InternalMetadataFolderImageProvider : ILocalImageProvider, IHasOrder
{
private readonly IServerConfigurationManager _config;
private readonly IFileSystem _fileSystem;
diff --git a/MediaBrowser.LocalMetadata/Images/LocalImageProvider.cs b/MediaBrowser.LocalMetadata/Images/LocalImageProvider.cs
index 7c330ad86..16807f5a4 100644
--- a/MediaBrowser.LocalMetadata/Images/LocalImageProvider.cs
+++ b/MediaBrowser.LocalMetadata/Images/LocalImageProvider.cs
@@ -13,7 +13,7 @@ using MediaBrowser.Model.IO;
namespace MediaBrowser.LocalMetadata.Images
{
- public class LocalImageProvider : ILocalImageFileProvider, IHasOrder
+ public class LocalImageProvider : ILocalImageProvider, IHasOrder
{
private readonly IFileSystem _fileSystem;
@@ -30,7 +30,7 @@ namespace MediaBrowser.LocalMetadata.Images
{
if (item.SupportsLocalMetadata)
{
- // Episode has it's own provider
+ // Episode has its own provider
if (item is Episode || item is Audio || item is Photo)
{
return false;
diff --git a/MediaBrowser.LocalMetadata/MediaBrowser.LocalMetadata.csproj b/MediaBrowser.LocalMetadata/MediaBrowser.LocalMetadata.csproj
index 71eb62693..24104d779 100644
--- a/MediaBrowser.LocalMetadata/MediaBrowser.LocalMetadata.csproj
+++ b/MediaBrowser.LocalMetadata/MediaBrowser.LocalMetadata.csproj
@@ -1,5 +1,10 @@
<Project Sdk="Microsoft.NET.Sdk">
+ <!-- ProjectGuid is only included as a requirement for SonarQube analysis -->
+ <PropertyGroup>
+ <ProjectGuid>{7EF9F3E0-697D-42F3-A08F-19DEB5F84392}</ProjectGuid>
+ </PropertyGroup>
+
<ItemGroup>
<ProjectReference Include="..\MediaBrowser.Controller\MediaBrowser.Controller.csproj" />
<ProjectReference Include="..\MediaBrowser.Model\MediaBrowser.Model.csproj" />
diff --git a/MediaBrowser.LocalMetadata/Parsers/BaseItemXmlParser.cs b/MediaBrowser.LocalMetadata/Parsers/BaseItemXmlParser.cs
index 59c8f4da5..d4b98182f 100644
--- a/MediaBrowser.LocalMetadata/Parsers/BaseItemXmlParser.cs
+++ b/MediaBrowser.LocalMetadata/Parsers/BaseItemXmlParser.cs
@@ -9,7 +9,6 @@ using System.Xml;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Entities;
-using MediaBrowser.Model.IO;
using Microsoft.Extensions.Logging;
namespace MediaBrowser.LocalMetadata.Parsers
diff --git a/MediaBrowser.LocalMetadata/Providers/BoxSetXmlProvider.cs b/MediaBrowser.LocalMetadata/Providers/BoxSetXmlProvider.cs
index 2e303efab..b2e3bc9e2 100644
--- a/MediaBrowser.LocalMetadata/Providers/BoxSetXmlProvider.cs
+++ b/MediaBrowser.LocalMetadata/Providers/BoxSetXmlProvider.cs
@@ -16,7 +16,7 @@ namespace MediaBrowser.LocalMetadata.Providers
private readonly ILogger _logger;
private readonly IProviderManager _providerManager;
- public BoxSetXmlProvider(IFileSystem fileSystem, ILogger logger, IProviderManager providerManager)
+ public BoxSetXmlProvider(IFileSystem fileSystem, ILogger<BoxSetXmlProvider> logger, IProviderManager providerManager)
: base(fileSystem)
{
_logger = logger;
diff --git a/MediaBrowser.LocalMetadata/Providers/PlaylistXmlProvider.cs b/MediaBrowser.LocalMetadata/Providers/PlaylistXmlProvider.cs
index d111ae9ba..df8107bad 100644
--- a/MediaBrowser.LocalMetadata/Providers/PlaylistXmlProvider.cs
+++ b/MediaBrowser.LocalMetadata/Providers/PlaylistXmlProvider.cs
@@ -13,7 +13,10 @@ namespace MediaBrowser.LocalMetadata.Providers
private readonly ILogger _logger;
private readonly IProviderManager _providerManager;
- public PlaylistXmlProvider(IFileSystem fileSystem, ILogger logger, IProviderManager providerManager)
+ public PlaylistXmlProvider(
+ IFileSystem fileSystem,
+ ILogger<PlaylistXmlProvider> logger,
+ IProviderManager providerManager)
: base(fileSystem)
{
_logger = logger;
diff --git a/MediaBrowser.LocalMetadata/Savers/BaseXmlSaver.cs b/MediaBrowser.LocalMetadata/Savers/BaseXmlSaver.cs
index bd727bcdf..ba1d850e3 100644
--- a/MediaBrowser.LocalMetadata/Savers/BaseXmlSaver.cs
+++ b/MediaBrowser.LocalMetadata/Savers/BaseXmlSaver.cs
@@ -1,5 +1,4 @@
using System;
-using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
@@ -93,7 +92,7 @@ namespace MediaBrowser.LocalMetadata.Savers
// On Windows, savint the file will fail if the file is hidden or readonly
FileSystem.SetAttributes(path, false, false);
- using (var filestream = FileSystem.GetFileStream(path, FileOpenMode.Create, FileAccessMode.Write, FileShareMode.Read))
+ using (var filestream = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.Read))
{
stream.CopyTo(filestream);
}
diff --git a/MediaBrowser.LocalMetadata/Savers/BoxSetXmlSaver.cs b/MediaBrowser.LocalMetadata/Savers/BoxSetXmlSaver.cs
index ea939e33b..1dc09bf18 100644
--- a/MediaBrowser.LocalMetadata/Savers/BoxSetXmlSaver.cs
+++ b/MediaBrowser.LocalMetadata/Savers/BoxSetXmlSaver.cs
@@ -30,7 +30,7 @@ namespace MediaBrowser.LocalMetadata.Savers
return Path.Combine(item.Path, "collection.xml");
}
- public BoxSetXmlSaver(IFileSystem fileSystem, IServerConfigurationManager configurationManager, ILibraryManager libraryManager, IUserManager userManager, IUserDataManager userDataManager, ILogger logger)
+ public BoxSetXmlSaver(IFileSystem fileSystem, IServerConfigurationManager configurationManager, ILibraryManager libraryManager, IUserManager userManager, IUserDataManager userDataManager, ILogger<BoxSetXmlSaver> logger)
: base(fileSystem, configurationManager, libraryManager, userManager, userDataManager, logger)
{
}
diff --git a/MediaBrowser.LocalMetadata/Savers/PlaylistXmlSaver.cs b/MediaBrowser.LocalMetadata/Savers/PlaylistXmlSaver.cs
index 35a431fa4..bbb0a3501 100644
--- a/MediaBrowser.LocalMetadata/Savers/PlaylistXmlSaver.cs
+++ b/MediaBrowser.LocalMetadata/Savers/PlaylistXmlSaver.cs
@@ -11,6 +11,11 @@ namespace MediaBrowser.LocalMetadata.Savers
{
public class PlaylistXmlSaver : BaseXmlSaver
{
+ /// <summary>
+ /// The default file name to use when creating a new playlist.
+ /// </summary>
+ public const string DefaultPlaylistFilename = "playlist.xml";
+
public override bool IsEnabledFor(BaseItem item, ItemUpdateType updateType)
{
if (!item.SupportsLocalMetadata)
@@ -45,10 +50,10 @@ namespace MediaBrowser.LocalMetadata.Savers
return Path.ChangeExtension(itemPath, ".xml");
}
- return Path.Combine(path, "playlist.xml");
+ return Path.Combine(path, DefaultPlaylistFilename);
}
- public PlaylistXmlSaver(IFileSystem fileSystem, IServerConfigurationManager configurationManager, ILibraryManager libraryManager, IUserManager userManager, IUserDataManager userDataManager, ILogger logger)
+ public PlaylistXmlSaver(IFileSystem fileSystem, IServerConfigurationManager configurationManager, ILibraryManager libraryManager, IUserManager userManager, IUserDataManager userDataManager, ILogger<PlaylistXmlSaver> logger)
: base(fileSystem, configurationManager, libraryManager, userManager, userDataManager, logger)
{
}
diff --git a/MediaBrowser.MediaEncoding/Attachments/AttachmentExtractor.cs b/MediaBrowser.MediaEncoding/Attachments/AttachmentExtractor.cs
index c530c9fd8..3f177a9fa 100644
--- a/MediaBrowser.MediaEncoding/Attachments/AttachmentExtractor.cs
+++ b/MediaBrowser.MediaEncoding/Attachments/AttachmentExtractor.cs
@@ -1,6 +1,6 @@
using System;
-using System.Diagnostics;
using System.Collections.Concurrent;
+using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
@@ -155,47 +155,44 @@ namespace MediaBrowser.MediaEncoding.Attachments
inputPath,
attachmentStreamIndex,
outputPath);
- var startInfo = new ProcessStartInfo
- {
- Arguments = processArgs,
- FileName = _mediaEncoder.EncoderPath,
- UseShellExecute = false,
- CreateNoWindow = true,
- WindowStyle = ProcessWindowStyle.Hidden,
- ErrorDialog = false
- };
- var process = new Process
- {
- StartInfo = startInfo
- };
- _logger.LogInformation("{File} {Arguments}", process.StartInfo.FileName, process.StartInfo.Arguments);
+ int exitCode;
- process.Start();
+ using (var process = new Process
+ {
+ StartInfo = new ProcessStartInfo
+ {
+ Arguments = processArgs,
+ FileName = _mediaEncoder.EncoderPath,
+ UseShellExecute = false,
+ CreateNoWindow = true,
+ WindowStyle = ProcessWindowStyle.Hidden,
+ ErrorDialog = false
+ },
+ EnableRaisingEvents = true
+ })
+ {
+ _logger.LogInformation("{File} {Arguments}", process.StartInfo.FileName, process.StartInfo.Arguments);
- var processTcs = new TaskCompletionSource<bool>();
- process.EnableRaisingEvents = true;
- process.Exited += (sender, args) => processTcs.TrySetResult(true);
- var unregister = cancellationToken.Register(() => processTcs.TrySetResult(process.HasExited));
- var ranToCompletion = await processTcs.Task.ConfigureAwait(false);
- unregister.Dispose();
+ process.Start();
- if (!ranToCompletion)
- {
- try
- {
- _logger.LogWarning("Killing ffmpeg attachment extraction process");
- process.Kill();
- }
- catch (Exception ex)
+ var ranToCompletion = await process.WaitForExitAsync(cancellationToken).ConfigureAwait(false);
+
+ if (!ranToCompletion)
{
- _logger.LogError(ex, "Error killing attachment extraction process");
+ try
+ {
+ _logger.LogWarning("Killing ffmpeg attachment extraction process");
+ process.Kill();
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, "Error killing attachment extraction process");
+ }
}
- }
- var exitCode = ranToCompletion ? process.ExitCode : -1;
-
- process.Dispose();
+ exitCode = ranToCompletion ? process.ExitCode : -1;
+ }
var failed = false;
diff --git a/MediaBrowser.MediaEncoding/BdInfo/BdInfoDirectoryInfo.cs b/MediaBrowser.MediaEncoding/BdInfo/BdInfoDirectoryInfo.cs
index 91c8b2792..e040286ab 100644
--- a/MediaBrowser.MediaEncoding/BdInfo/BdInfoDirectoryInfo.cs
+++ b/MediaBrowser.MediaEncoding/BdInfo/BdInfoDirectoryInfo.cs
@@ -1,16 +1,27 @@
using System;
-using System.Collections.Generic;
using System.Linq;
using BDInfo.IO;
using MediaBrowser.Model.IO;
namespace MediaBrowser.MediaEncoding.BdInfo
{
- class BdInfoDirectoryInfo : BDInfo.IO.IDirectoryInfo
+ class BdInfoDirectoryInfo : IDirectoryInfo
{
- IFileSystem _fileSystem = null;
+ private readonly IFileSystem _fileSystem = null;
- FileSystemMetadata _impl = null;
+ private readonly FileSystemMetadata _impl = null;
+
+ public BdInfoDirectoryInfo(IFileSystem fileSystem, string path)
+ {
+ _fileSystem = fileSystem;
+ _impl = _fileSystem.GetDirectoryInfo(path);
+ }
+
+ private BdInfoDirectoryInfo(IFileSystem fileSystem, FileSystemMetadata impl)
+ {
+ _fileSystem = fileSystem;
+ _impl = impl;
+ }
public string Name => _impl.Name;
@@ -25,22 +36,11 @@ namespace MediaBrowser.MediaEncoding.BdInfo
{
return new BdInfoDirectoryInfo(_fileSystem, parentFolder);
}
+
return null;
}
}
- public BdInfoDirectoryInfo(IFileSystem fileSystem, string path)
- {
- _fileSystem = fileSystem;
- _impl = _fileSystem.GetDirectoryInfo(path);
- }
-
- private BdInfoDirectoryInfo(IFileSystem fileSystem, FileSystemMetadata impl)
- {
- _fileSystem = fileSystem;
- _impl = impl;
- }
-
public IDirectoryInfo[] GetDirectories()
{
return Array.ConvertAll(_fileSystem.GetDirectories(_impl.FullName).ToArray(),
@@ -50,20 +50,20 @@ namespace MediaBrowser.MediaEncoding.BdInfo
public IFileInfo[] GetFiles()
{
return Array.ConvertAll(_fileSystem.GetFiles(_impl.FullName).ToArray(),
- x => new BdInfoFileInfo(_fileSystem, x));
+ x => new BdInfoFileInfo(x));
}
public IFileInfo[] GetFiles(string searchPattern)
{
return Array.ConvertAll(_fileSystem.GetFiles(_impl.FullName, new[] { searchPattern }, false, false).ToArray(),
- x => new BdInfoFileInfo(_fileSystem, x));
+ x => new BdInfoFileInfo(x));
}
public IFileInfo[] GetFiles(string searchPattern, System.IO.SearchOption searchOption)
{
return Array.ConvertAll(_fileSystem.GetFiles(_impl.FullName, new[] { searchPattern }, false,
searchOption.HasFlag(System.IO.SearchOption.AllDirectories)).ToArray(),
- x => new BdInfoFileInfo(_fileSystem, x));
+ x => new BdInfoFileInfo(x));
}
public static IDirectoryInfo FromFileSystemPath(Model.IO.IFileSystem fs, string path)
diff --git a/MediaBrowser.MediaEncoding/BdInfo/BdInfoFileInfo.cs b/MediaBrowser.MediaEncoding/BdInfo/BdInfoFileInfo.cs
index de9d7cb78..a6ff4f767 100644
--- a/MediaBrowser.MediaEncoding/BdInfo/BdInfoFileInfo.cs
+++ b/MediaBrowser.MediaEncoding/BdInfo/BdInfoFileInfo.cs
@@ -1,11 +1,10 @@
+using System.IO;
using MediaBrowser.Model.IO;
namespace MediaBrowser.MediaEncoding.BdInfo
{
class BdInfoFileInfo : BDInfo.IO.IFileInfo
{
- IFileSystem _fileSystem = null;
-
FileSystemMetadata _impl = null;
public string Name => _impl.Name;
@@ -18,18 +17,17 @@ namespace MediaBrowser.MediaEncoding.BdInfo
public bool IsDir => _impl.IsDirectory;
- public BdInfoFileInfo(IFileSystem fileSystem, FileSystemMetadata impl)
+ public BdInfoFileInfo(FileSystemMetadata impl)
{
- _fileSystem = fileSystem;
_impl = impl;
}
public System.IO.Stream OpenRead()
{
- return _fileSystem.GetFileStream(FullName,
- FileOpenMode.Open,
- FileAccessMode.Read,
- FileShareMode.Read);
+ return new FileStream(FullName,
+ FileMode.Open,
+ FileAccess.Read,
+ FileShare.Read);
}
public System.IO.StreamReader OpenText()
diff --git a/MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs b/MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs
index 1feca0ec9..6e036d24c 100644
--- a/MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs
+++ b/MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs
@@ -42,6 +42,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
"libvpx",
"libvpx-vp9",
"aac",
+ "libfdk_aac",
"libmp3lame",
"libopus",
"libvorbis",
@@ -55,7 +56,9 @@ namespace MediaBrowser.MediaEncoding.Encoder
"h264_vaapi",
"hevc_vaapi",
"h264_v4l2m2m",
- "ac3"
+ "ac3",
+ "h264_amf",
+ "hevc_amf"
};
// Try and use the individual library versions to determine a FFmpeg version
diff --git a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs
index e0f7b992c..1377502dd 100644
--- a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs
+++ b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs
@@ -13,7 +13,6 @@ using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.MediaEncoding;
using MediaBrowser.MediaEncoding.Probing;
using MediaBrowser.Model.Configuration;
-using MediaBrowser.Model.Diagnostics;
using MediaBrowser.Model.Dlna;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Globalization;
@@ -21,7 +20,7 @@ using MediaBrowser.Model.IO;
using MediaBrowser.Model.MediaInfo;
using MediaBrowser.Model.System;
using Microsoft.Extensions.Logging;
-using Microsoft.Extensions.Configuration;
+using System.Diagnostics;
namespace MediaBrowser.MediaEncoding.Encoder
{
@@ -38,10 +37,8 @@ namespace MediaBrowser.MediaEncoding.Encoder
private readonly ILogger _logger;
private readonly IServerConfigurationManager _configurationManager;
private readonly IFileSystem _fileSystem;
- private readonly IProcessFactory _processFactory;
private readonly ILocalizationManager _localization;
- private readonly Func<ISubtitleEncoder> _subtitleEncoder;
- private readonly IConfiguration _configuration;
+ private readonly Lazy<EncodingHelper> _encodingHelperFactory;
private readonly string _startupOptionFFmpegPath;
private readonly SemaphoreSlim _thumbnailResourcePool = new SemaphoreSlim(2, 2);
@@ -49,8 +46,6 @@ namespace MediaBrowser.MediaEncoding.Encoder
private readonly object _runningProcessesLock = new object();
private readonly List<ProcessWrapper> _runningProcesses = new List<ProcessWrapper>();
- private EncodingHelper _encodingHelper;
-
private string _ffmpegPath;
private string _ffprobePath;
@@ -58,26 +53,19 @@ namespace MediaBrowser.MediaEncoding.Encoder
ILogger<MediaEncoder> logger,
IServerConfigurationManager configurationManager,
IFileSystem fileSystem,
- IProcessFactory processFactory,
ILocalizationManager localization,
- Func<ISubtitleEncoder> subtitleEncoder,
- IConfiguration configuration,
+ Lazy<EncodingHelper> encodingHelperFactory,
string startupOptionsFFmpegPath)
{
_logger = logger;
_configurationManager = configurationManager;
_fileSystem = fileSystem;
- _processFactory = processFactory;
_localization = localization;
+ _encodingHelperFactory = encodingHelperFactory;
_startupOptionFFmpegPath = startupOptionsFFmpegPath;
- _subtitleEncoder = subtitleEncoder;
- _configuration = configuration;
}
- private EncodingHelper EncodingHelper
- => LazyInitializer.EnsureInitialized(
- ref _encodingHelper,
- () => new EncodingHelper(this, _fileSystem, _subtitleEncoder(), _configuration));
+ private EncodingHelper EncodingHelper => _encodingHelperFactory.Value;
/// <inheritdoc />
public string EncoderPath => _ffmpegPath;
@@ -125,7 +113,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
SetAvailableEncoders(validator.GetEncoders());
}
- _logger.LogInformation("FFmpeg: {0}: {1}", EncoderLocation, _ffmpegPath ?? string.Empty);
+ _logger.LogInformation("FFmpeg: {EncoderLocation}: {FfmpegPath}", EncoderLocation, _ffmpegPath ?? string.Empty);
}
/// <summary>
@@ -138,7 +126,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
{
string newPath;
- _logger.LogInformation("Attempting to update encoder path to {0}. pathType: {1}", path ?? string.Empty, pathType ?? string.Empty);
+ _logger.LogInformation("Attempting to update encoder path to {Path}. pathType: {PathType}", path ?? string.Empty, pathType ?? string.Empty);
if (!string.Equals(pathType, "custom", StringComparison.OrdinalIgnoreCase))
{
@@ -192,7 +180,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
if (!rc)
{
- _logger.LogWarning("FFmpeg: {0}: Failed version check: {1}", location, path);
+ _logger.LogWarning("FFmpeg: {Location}: Failed version check: {Path}", location, path);
}
// ToDo - Enable the ffmpeg validator. At the moment any version can be used.
@@ -203,18 +191,18 @@ namespace MediaBrowser.MediaEncoding.Encoder
}
else
{
- _logger.LogWarning("FFmpeg: {0}: File not found: {1}", location, path);
+ _logger.LogWarning("FFmpeg: {Location}: File not found: {Path}", location, path);
}
}
return rc;
}
- private string GetEncoderPathFromDirectory(string path, string filename)
+ private string GetEncoderPathFromDirectory(string path, string filename, bool recursive = false)
{
try
{
- var files = _fileSystem.GetFilePaths(path);
+ var files = _fileSystem.GetFilePaths(path, recursive);
var excludeExtensions = new[] { ".c" };
@@ -235,7 +223,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
/// <returns></returns>
private string ExistsOnSystemPath(string fileName)
{
- string inJellyfinPath = GetEncoderPathFromDirectory(System.AppContext.BaseDirectory, fileName);
+ var inJellyfinPath = GetEncoderPathFromDirectory(AppContext.BaseDirectory, fileName, recursive: true);
if (!string.IsNullOrEmpty(inJellyfinPath))
{
return inJellyfinPath;
@@ -362,30 +350,33 @@ namespace MediaBrowser.MediaEncoding.Encoder
: "{0} -i {1} -threads 0 -v warning -print_format json -show_streams -show_format";
args = string.Format(args, probeSizeArgument, inputPath).Trim();
- var process = _processFactory.Create(new ProcessOptions
+ var process = new Process
{
- CreateNoWindow = true,
- UseShellExecute = false,
+ StartInfo = new ProcessStartInfo
+ {
+ CreateNoWindow = true,
+ UseShellExecute = false,
- // Must consume both or ffmpeg may hang due to deadlocks. See comments below.
- RedirectStandardOutput = true,
+ // Must consume both or ffmpeg may hang due to deadlocks. See comments below.
+ RedirectStandardOutput = true,
- FileName = _ffprobePath,
- Arguments = args,
+ FileName = _ffprobePath,
+ Arguments = args,
- IsHidden = true,
- ErrorDialog = false,
+ WindowStyle = ProcessWindowStyle.Hidden,
+ ErrorDialog = false,
+ },
EnableRaisingEvents = true
- });
+ };
if (forceEnableLogging)
{
- _logger.LogInformation("{0} {1}", process.StartInfo.FileName, process.StartInfo.Arguments);
+ _logger.LogInformation("{ProcessFileName} {ProcessArgs}", process.StartInfo.FileName, process.StartInfo.Arguments);
}
else
{
- _logger.LogDebug("{0} {1}", process.StartInfo.FileName, process.StartInfo.Arguments);
+ _logger.LogDebug("{ProcessFileName} {ProcessArgs}", process.StartInfo.FileName, process.StartInfo.Arguments);
}
using (var processWrapper = new ProcessWrapper(process, this))
@@ -429,7 +420,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
}
}
- return new ProbeResultNormalizer(_logger, _fileSystem, _localization).GetMediaInfo(result, videoType, isAudio, primaryPath, protocol);
+ return new ProbeResultNormalizer(_logger, _localization).GetMediaInfo(result, videoType, isAudio, primaryPath, protocol);
}
}
@@ -478,7 +469,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
}
catch (Exception ex)
{
- _logger.LogError(ex, "I-frame image extraction failed, will attempt standard way. Input: {arguments}", inputArgument);
+ _logger.LogError(ex, "I-frame image extraction failed, will attempt standard way. Input: {Arguments}", inputArgument);
}
}
@@ -571,18 +562,21 @@ namespace MediaBrowser.MediaEncoding.Encoder
}
}
- var process = _processFactory.Create(new ProcessOptions
+ var process = new Process
{
- CreateNoWindow = true,
- UseShellExecute = false,
- FileName = _ffmpegPath,
- Arguments = args,
- IsHidden = true,
- ErrorDialog = false,
+ StartInfo = new ProcessStartInfo
+ {
+ CreateNoWindow = true,
+ UseShellExecute = false,
+ FileName = _ffmpegPath,
+ Arguments = args,
+ WindowStyle = ProcessWindowStyle.Hidden,
+ ErrorDialog = false,
+ },
EnableRaisingEvents = true
- });
+ };
- _logger.LogDebug("{0} {1}", process.StartInfo.FileName, process.StartInfo.Arguments);
+ _logger.LogDebug("{ProcessFileName} {ProcessArguments}", process.StartInfo.FileName, process.StartInfo.Arguments);
using (var processWrapper = new ProcessWrapper(process, this))
{
@@ -599,7 +593,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
timeoutMs = DefaultImageExtractionTimeout;
}
- ranToCompletion = await process.WaitForExitAsync(timeoutMs).ConfigureAwait(false);
+ ranToCompletion = await process.WaitForExitAsync(TimeSpan.FromMilliseconds(timeoutMs)).ConfigureAwait(false);
if (!ranToCompletion)
{
@@ -700,23 +694,27 @@ namespace MediaBrowser.MediaEncoding.Encoder
}
}
- var process = _processFactory.Create(new ProcessOptions
+ var processStartInfo = new ProcessStartInfo
{
CreateNoWindow = true,
UseShellExecute = false,
FileName = _ffmpegPath,
Arguments = args,
- IsHidden = true,
- ErrorDialog = false,
- EnableRaisingEvents = true
- });
+ WindowStyle = ProcessWindowStyle.Hidden,
+ ErrorDialog = false
+ };
- _logger.LogInformation(process.StartInfo.FileName + " " + process.StartInfo.Arguments);
+ _logger.LogInformation(processStartInfo.FileName + " " + processStartInfo.Arguments);
await _thumbnailResourcePool.WaitAsync(cancellationToken).ConfigureAwait(false);
bool ranToCompletion = false;
+ var process = new Process
+ {
+ StartInfo = processStartInfo,
+ EnableRaisingEvents = true
+ };
using (var processWrapper = new ProcessWrapper(process, this))
{
try
@@ -732,7 +730,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
while (isResponsive)
{
- if (await process.WaitForExitAsync(30000).ConfigureAwait(false))
+ if (await process.WaitForExitAsync(TimeSpan.FromSeconds(30)).ConfigureAwait(false))
{
ranToCompletion = true;
break;
@@ -849,14 +847,92 @@ namespace MediaBrowser.MediaEncoding.Encoder
}
}
+ /// <inheritdoc />
public Task ConvertImage(string inputPath, string outputPath)
{
throw new NotImplementedException();
}
+ /// <inheritdoc />
public IEnumerable<string> GetPrimaryPlaylistVobFiles(string path, IIsoMount isoMount, uint? titleNumber)
{
- throw new NotImplementedException();
+ // min size 300 mb
+ const long MinPlayableSize = 314572800;
+
+ var root = isoMount != null ? isoMount.MountedPath : path;
+
+ // Try to eliminate menus and intros by skipping all files at the front of the list that are less than the minimum size
+ // Once we reach a file that is at least the minimum, return all subsequent ones
+ var allVobs = _fileSystem.GetFiles(root, true)
+ .Where(file => string.Equals(file.Extension, ".vob", StringComparison.OrdinalIgnoreCase))
+ .OrderBy(i => i.FullName)
+ .ToList();
+
+ // If we didn't find any satisfying the min length, just take them all
+ if (allVobs.Count == 0)
+ {
+ _logger.LogWarning("No vobs found in dvd structure.");
+ return Enumerable.Empty<string>();
+ }
+
+ if (titleNumber.HasValue)
+ {
+ var prefix = string.Format(
+ CultureInfo.InvariantCulture,
+ titleNumber.Value >= 10 ? "VTS_{0}_" : "VTS_0{0}_",
+ titleNumber.Value);
+ var vobs = allVobs.Where(i => i.Name.StartsWith(prefix, StringComparison.OrdinalIgnoreCase)).ToList();
+
+ if (vobs.Count > 0)
+ {
+ var minSizeVobs = vobs
+ .SkipWhile(f => f.Length < MinPlayableSize)
+ .ToList();
+
+ return minSizeVobs.Count == 0 ? vobs.Select(i => i.FullName) : minSizeVobs.Select(i => i.FullName);
+ }
+
+ _logger.LogWarning("Could not determine vob file list for {Path} using DvdLib. Will scan using file sizes.", path);
+ }
+
+ var files = allVobs
+ .SkipWhile(f => f.Length < MinPlayableSize)
+ .ToList();
+
+ // If we didn't find any satisfying the min length, just take them all
+ if (files.Count == 0)
+ {
+ _logger.LogWarning("Vob size filter resulted in zero matches. Taking all vobs.");
+ files = allVobs;
+ }
+
+ // Assuming they're named "vts_05_01", take all files whose second part matches that of the first file
+ if (files.Count > 0)
+ {
+ var parts = _fileSystem.GetFileNameWithoutExtension(files[0]).Split('_');
+
+ if (parts.Length == 3)
+ {
+ var title = parts[1];
+
+ files = files.TakeWhile(f =>
+ {
+ var fileParts = _fileSystem.GetFileNameWithoutExtension(f).Split('_');
+
+ return fileParts.Length == 3 && string.Equals(title, fileParts[1], StringComparison.OrdinalIgnoreCase);
+
+ }).ToList();
+
+ // If this resulted in not getting any vobs, just take them all
+ if (files.Count == 0)
+ {
+ _logger.LogWarning("Vob filename filter resulted in zero matches. Taking all vobs.");
+ files = allVobs;
+ }
+ }
+ }
+
+ return files.Select(i => i.FullName);
}
public bool CanExtractSubtitles(string codec)
@@ -871,22 +947,22 @@ namespace MediaBrowser.MediaEncoding.Encoder
private bool _disposed = false;
- public ProcessWrapper(IProcess process, MediaEncoder mediaEncoder)
+ public ProcessWrapper(Process process, MediaEncoder mediaEncoder)
{
Process = process;
_mediaEncoder = mediaEncoder;
Process.Exited += OnProcessExited;
}
- public IProcess Process { get; }
+ public Process Process { get; }
public bool HasExited { get; private set; }
public int? ExitCode { get; private set; }
- void OnProcessExited(object sender, EventArgs e)
+ private void OnProcessExited(object sender, EventArgs e)
{
- var process = (IProcess)sender;
+ var process = (Process)sender;
HasExited = true;
@@ -901,7 +977,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
DisposeProcess(process);
}
- private void DisposeProcess(IProcess process)
+ private void DisposeProcess(Process process)
{
lock (_mediaEncoder._runningProcessesLock)
{
diff --git a/MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj b/MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj
index 783457bda..af8bee301 100644
--- a/MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj
+++ b/MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj
@@ -1,5 +1,10 @@
<Project Sdk="Microsoft.NET.Sdk">
+ <!-- ProjectGuid is only included as a requirement for SonarQube analysis -->
+ <PropertyGroup>
+ <ProjectGuid>{960295EE-4AF4-4440-A525-B4C295B01A61}</ProjectGuid>
+ </PropertyGroup>
+
<PropertyGroup>
<TargetFramework>netstandard2.1</TargetFramework>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
@@ -18,8 +23,8 @@
<ItemGroup>
<PackageReference Include="BDInfo" Version="0.7.6.1" />
- <PackageReference Include="System.Text.Encoding.CodePages" Version="4.6.0" />
- <PackageReference Include="UTF.Unknown" Version="2.2.0" />
+ <PackageReference Include="System.Text.Encoding.CodePages" Version="4.7.0" />
+ <PackageReference Include="UTF.Unknown" Version="2.3.0" />
</ItemGroup>
</Project>
diff --git a/MediaBrowser.MediaEncoding/Probing/MediaStreamInfo.cs b/MediaBrowser.MediaEncoding/Probing/MediaStreamInfo.cs
index 7fa7afa5b..0b2f1d231 100644
--- a/MediaBrowser.MediaEncoding/Probing/MediaStreamInfo.cs
+++ b/MediaBrowser.MediaEncoding/Probing/MediaStreamInfo.cs
@@ -225,7 +225,7 @@ namespace MediaBrowser.MediaEncoding.Probing
/// </summary>
/// <value>The start_pts.</value>
[JsonPropertyName("start_pts")]
- public int StartPts { get; set; }
+ public long StartPts { get; set; }
/// <summary>
/// Gets or sets the is_avc.
diff --git a/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs b/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs
index bd89c6cae..b24d97f4e 100644
--- a/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs
+++ b/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs
@@ -9,7 +9,6 @@ using MediaBrowser.Controller.Library;
using MediaBrowser.Model.Dto;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Globalization;
-using MediaBrowser.Model.IO;
using MediaBrowser.Model.MediaInfo;
using Microsoft.Extensions.Logging;
@@ -19,13 +18,11 @@ namespace MediaBrowser.MediaEncoding.Probing
{
private readonly CultureInfo _usCulture = new CultureInfo("en-US");
private readonly ILogger _logger;
- private readonly IFileSystem _fileSystem;
private readonly ILocalizationManager _localization;
- public ProbeResultNormalizer(ILogger logger, IFileSystem fileSystem, ILocalizationManager localization)
+ public ProbeResultNormalizer(ILogger logger, ILocalizationManager localization)
{
_logger = logger;
- _fileSystem = fileSystem;
_localization = localization;
}
@@ -40,7 +37,7 @@ namespace MediaBrowser.MediaEncoding.Probing
FFProbeHelpers.NormalizeFFProbeResult(data);
SetSize(data, info);
- var internalStreams = data.Streams ?? new MediaStreamInfo[] { };
+ var internalStreams = data.Streams ?? Array.Empty<MediaStreamInfo>();
info.MediaStreams = internalStreams.Select(s => GetMediaStream(isAudio, s, data.Format))
.Where(i => i != null)
@@ -112,6 +109,9 @@ namespace MediaBrowser.MediaEncoding.Probing
info.Name = title;
}
+ info.IndexNumber = FFProbeHelpers.GetDictionaryNumericValue(tags, "episode_sort");
+ info.ParentIndexNumber = FFProbeHelpers.GetDictionaryNumericValue(tags, "season_number");
+ info.ShowName = FFProbeHelpers.GetDictionaryValue(tags, "show_name");
info.ProductionYear = FFProbeHelpers.GetDictionaryNumericValue(tags, "date");
// Several different forms of retaildate
@@ -536,7 +536,7 @@ namespace MediaBrowser.MediaEncoding.Probing
if (!string.IsNullOrWhiteSpace(streamInfo.CodecTagString))
{
- attachment.CodecTag = streamInfo.CodecTagString;
+ attachment.CodecTag = streamInfo.CodecTagString;
}
if (streamInfo.Tags != null)
diff --git a/MediaBrowser.MediaEncoding/Subtitles/AssParser.cs b/MediaBrowser.MediaEncoding/Subtitles/AssParser.cs
index 605504418..293cf5ea5 100644
--- a/MediaBrowser.MediaEncoding/Subtitles/AssParser.cs
+++ b/MediaBrowser.MediaEncoding/Subtitles/AssParser.cs
@@ -5,7 +5,6 @@ using System.IO;
using System.Linq;
using System.Text.RegularExpressions;
using System.Threading;
-using MediaBrowser.Model.Extensions;
using MediaBrowser.Model.MediaInfo;
namespace MediaBrowser.MediaEncoding.Subtitles
diff --git a/MediaBrowser.MediaEncoding/Subtitles/SrtParser.cs b/MediaBrowser.MediaEncoding/Subtitles/SrtParser.cs
index 0606dbdb2..c98dd1502 100644
--- a/MediaBrowser.MediaEncoding/Subtitles/SrtParser.cs
+++ b/MediaBrowser.MediaEncoding/Subtitles/SrtParser.cs
@@ -4,7 +4,6 @@ using System.Globalization;
using System.IO;
using System.Text.RegularExpressions;
using System.Threading;
-using MediaBrowser.Model.Extensions;
using MediaBrowser.Model.MediaInfo;
using Microsoft.Extensions.Logging;
diff --git a/MediaBrowser.MediaEncoding/Subtitles/SsaParser.cs b/MediaBrowser.MediaEncoding/Subtitles/SsaParser.cs
index 0d696b906..b94d45165 100644
--- a/MediaBrowser.MediaEncoding/Subtitles/SsaParser.cs
+++ b/MediaBrowser.MediaEncoding/Subtitles/SsaParser.cs
@@ -3,7 +3,6 @@ using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Threading;
-using MediaBrowser.Model.Extensions;
using MediaBrowser.Model.MediaInfo;
namespace MediaBrowser.MediaEncoding.Subtitles
diff --git a/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs b/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs
index 99bb368b2..ba171295e 100644
--- a/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs
+++ b/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs
@@ -1,5 +1,6 @@
using System;
using System.Collections.Concurrent;
+using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
@@ -12,7 +13,6 @@ using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.MediaEncoding;
-using MediaBrowser.Model.Diagnostics;
using MediaBrowser.Model.Dto;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.IO;
@@ -31,7 +31,6 @@ namespace MediaBrowser.MediaEncoding.Subtitles
private readonly IMediaEncoder _mediaEncoder;
private readonly IHttpClient _httpClient;
private readonly IMediaSourceManager _mediaSourceManager;
- private readonly IProcessFactory _processFactory;
public SubtitleEncoder(
ILibraryManager libraryManager,
@@ -40,8 +39,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles
IFileSystem fileSystem,
IMediaEncoder mediaEncoder,
IHttpClient httpClient,
- IMediaSourceManager mediaSourceManager,
- IProcessFactory processFactory)
+ IMediaSourceManager mediaSourceManager)
{
_libraryManager = libraryManager;
_logger = logger;
@@ -50,7 +48,6 @@ namespace MediaBrowser.MediaEncoding.Subtitles
_mediaEncoder = mediaEncoder;
_httpClient = httpClient;
_mediaSourceManager = mediaSourceManager;
- _processFactory = processFactory;
}
private string SubtitleCachePath => Path.Combine(_appPaths.DataPath, "subtitles");
@@ -188,6 +185,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles
using (var stream = await GetStream(path, protocol, cancellationToken).ConfigureAwait(false))
{
var result = CharsetDetector.DetectFromStream(stream).Detected;
+ stream.Position = 0;
if (result != null)
{
@@ -428,49 +426,53 @@ namespace MediaBrowser.MediaEncoding.Subtitles
encodingParam = " -sub_charenc " + encodingParam;
}
- var process = _processFactory.Create(new ProcessOptions
- {
- CreateNoWindow = true,
- UseShellExecute = false,
- FileName = _mediaEncoder.EncoderPath,
- Arguments = string.Format("{0} -i \"{1}\" -c:s srt \"{2}\"", encodingParam, inputPath, outputPath),
- EnableRaisingEvents = true,
- IsHidden = true,
- ErrorDialog = false
- });
-
- _logger.LogInformation("{0} {1}", process.StartInfo.FileName, process.StartInfo.Arguments);
-
- try
- {
- process.Start();
- }
- catch (Exception ex)
- {
- _logger.LogError(ex, "Error starting ffmpeg");
+ int exitCode;
- throw;
- }
-
- var ranToCompletion = await process.WaitForExitAsync(300000).ConfigureAwait(false);
+ using (var process = new Process
+ {
+ StartInfo = new ProcessStartInfo
+ {
+ CreateNoWindow = true,
+ UseShellExecute = false,
+ FileName = _mediaEncoder.EncoderPath,
+ Arguments = string.Format("{0} -i \"{1}\" -c:s srt \"{2}\"", encodingParam, inputPath, outputPath),
+ WindowStyle = ProcessWindowStyle.Hidden,
+ ErrorDialog = false
+ },
+ EnableRaisingEvents = true
+ })
+ {
+ _logger.LogInformation("{0} {1}", process.StartInfo.FileName, process.StartInfo.Arguments);
- if (!ranToCompletion)
- {
try
{
- _logger.LogInformation("Killing ffmpeg subtitle conversion process");
-
- process.Kill();
+ process.Start();
}
catch (Exception ex)
{
- _logger.LogError(ex, "Error killing subtitle conversion process");
+ _logger.LogError(ex, "Error starting ffmpeg");
+
+ throw;
}
- }
- var exitCode = ranToCompletion ? process.ExitCode : -1;
+ var ranToCompletion = await process.WaitForExitAsync(TimeSpan.FromMinutes(5)).ConfigureAwait(false);
+
+ if (!ranToCompletion)
+ {
+ try
+ {
+ _logger.LogInformation("Killing ffmpeg subtitle conversion process");
+
+ process.Kill();
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, "Error killing subtitle conversion process");
+ }
+ }
- process.Dispose();
+ exitCode = ranToCompletion ? process.ExitCode : -1;
+ }
var failed = false;
@@ -577,49 +579,53 @@ namespace MediaBrowser.MediaEncoding.Subtitles
outputCodec,
outputPath);
- var process = _processFactory.Create(new ProcessOptions
- {
- CreateNoWindow = true,
- UseShellExecute = false,
- EnableRaisingEvents = true,
- FileName = _mediaEncoder.EncoderPath,
- Arguments = processArgs,
- IsHidden = true,
- ErrorDialog = false
- });
-
- _logger.LogInformation("{File} {Arguments}", process.StartInfo.FileName, process.StartInfo.Arguments);
+ int exitCode;
- try
- {
- process.Start();
- }
- catch (Exception ex)
- {
- _logger.LogError(ex, "Error starting ffmpeg");
-
- throw;
- }
-
- var ranToCompletion = await process.WaitForExitAsync(300000).ConfigureAwait(false);
+ using (var process = new Process
+ {
+ StartInfo = new ProcessStartInfo
+ {
+ CreateNoWindow = true,
+ UseShellExecute = false,
+ FileName = _mediaEncoder.EncoderPath,
+ Arguments = processArgs,
+ WindowStyle = ProcessWindowStyle.Hidden,
+ ErrorDialog = false
+ },
+ EnableRaisingEvents = true
+ })
+ {
+ _logger.LogInformation("{File} {Arguments}", process.StartInfo.FileName, process.StartInfo.Arguments);
- if (!ranToCompletion)
- {
try
{
- _logger.LogWarning("Killing ffmpeg subtitle extraction process");
-
- process.Kill();
+ process.Start();
}
catch (Exception ex)
{
- _logger.LogError(ex, "Error killing subtitle extraction process");
+ _logger.LogError(ex, "Error starting ffmpeg");
+
+ throw;
}
- }
- var exitCode = ranToCompletion ? process.ExitCode : -1;
+ var ranToCompletion = await process.WaitForExitAsync(TimeSpan.FromMinutes(5)).ConfigureAwait(false);
- process.Dispose();
+ if (!ranToCompletion)
+ {
+ try
+ {
+ _logger.LogWarning("Killing ffmpeg subtitle extraction process");
+
+ process.Kill();
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, "Error killing subtitle extraction process");
+ }
+ }
+
+ exitCode = ranToCompletion ? process.ExitCode : -1;
+ }
var failed = false;
@@ -691,7 +697,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles
if (!string.Equals(text, newText))
{
- using (var fileStream = _fileSystem.GetFileStream(file, FileOpenMode.Create, FileAccessMode.Write, FileShareMode.Read))
+ using (var fileStream = new FileStream(file, FileMode.Create, FileAccess.Write, FileShare.Read))
using (var writer = new StreamWriter(fileStream, encoding))
{
writer.Write(newText);
@@ -730,6 +736,14 @@ namespace MediaBrowser.MediaEncoding.Subtitles
{
var charset = CharsetDetector.DetectFromStream(stream).Detected?.EncodingName;
+ // UTF16 is automatically converted to UTF8 by FFmpeg, do not specify a character encoding
+ if ((path.EndsWith(".ass") || path.EndsWith(".ssa"))
+ && (string.Equals(charset, "utf-16le", StringComparison.OrdinalIgnoreCase)
+ || string.Equals(charset, "utf-16be", StringComparison.OrdinalIgnoreCase)))
+ {
+ charset = "";
+ }
+
_logger.LogDebug("charset {0} detected for {Path}", charset ?? "null", path);
return charset;
@@ -745,15 +759,17 @@ namespace MediaBrowser.MediaEncoding.Subtitles
{
Url = path,
CancellationToken = cancellationToken,
+
+ // Needed for seeking
BufferContent = true
};
return _httpClient.Get(opts);
- case MediaProtocol.File:
- return Task.FromResult<Stream>(File.OpenRead(path));
- default:
- throw new ArgumentOutOfRangeException(nameof(protocol));
+ case MediaProtocol.File:
+ return Task.FromResult<Stream>(File.OpenRead(path));
+ default:
+ throw new ArgumentOutOfRangeException(nameof(protocol));
}
}
}
diff --git a/MediaBrowser.MediaEncoding/Subtitles/TtmlWriter.cs b/MediaBrowser.MediaEncoding/Subtitles/TtmlWriter.cs
index 4f15bac49..7d3e18578 100644
--- a/MediaBrowser.MediaEncoding/Subtitles/TtmlWriter.cs
+++ b/MediaBrowser.MediaEncoding/Subtitles/TtmlWriter.cs
@@ -1,4 +1,3 @@
-using System;
using System.IO;
using System.Text;
using System.Text.RegularExpressions;
diff --git a/MediaBrowser.Model/Activity/ActivityLogEntry.cs b/MediaBrowser.Model/Activity/ActivityLogEntry.cs
index 186fd89ee..80f01b66e 100644
--- a/MediaBrowser.Model/Activity/ActivityLogEntry.cs
+++ b/MediaBrowser.Model/Activity/ActivityLogEntry.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
using System;
using Microsoft.Extensions.Logging;
diff --git a/MediaBrowser.Model/Activity/IActivityManager.cs b/MediaBrowser.Model/Activity/IActivityManager.cs
index 897d93d79..f336f5272 100644
--- a/MediaBrowser.Model/Activity/IActivityManager.cs
+++ b/MediaBrowser.Model/Activity/IActivityManager.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
using System;
using MediaBrowser.Model.Events;
using MediaBrowser.Model.Querying;
diff --git a/MediaBrowser.Model/Activity/IActivityRepository.cs b/MediaBrowser.Model/Activity/IActivityRepository.cs
index f0e3b902c..66144ec47 100644
--- a/MediaBrowser.Model/Activity/IActivityRepository.cs
+++ b/MediaBrowser.Model/Activity/IActivityRepository.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
using System;
using MediaBrowser.Model.Querying;
diff --git a/MediaBrowser.Model/ApiClient/ServerDiscoveryInfo.cs b/MediaBrowser.Model/ApiClient/ServerDiscoveryInfo.cs
index d1f3577f7..bb203f895 100644
--- a/MediaBrowser.Model/ApiClient/ServerDiscoveryInfo.cs
+++ b/MediaBrowser.Model/ApiClient/ServerDiscoveryInfo.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
namespace MediaBrowser.Model.ApiClient
{
public class ServerDiscoveryInfo
@@ -7,16 +9,19 @@ namespace MediaBrowser.Model.ApiClient
/// </summary>
/// <value>The address.</value>
public string Address { get; set; }
+
/// <summary>
/// Gets or sets the server identifier.
/// </summary>
/// <value>The server identifier.</value>
public string Id { get; set; }
+
/// <summary>
/// Gets or sets the name.
/// </summary>
/// <value>The name.</value>
public string Name { get; set; }
+
/// <summary>
/// Gets or sets the endpoint address.
/// </summary>
diff --git a/MediaBrowser.Model/Branding/BrandingOptions.cs b/MediaBrowser.Model/Branding/BrandingOptions.cs
index f83558df5..8ab268a64 100644
--- a/MediaBrowser.Model/Branding/BrandingOptions.cs
+++ b/MediaBrowser.Model/Branding/BrandingOptions.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
namespace MediaBrowser.Model.Branding
{
public class BrandingOptions
@@ -7,6 +9,7 @@ namespace MediaBrowser.Model.Branding
/// </summary>
/// <value>The login disclaimer.</value>
public string LoginDisclaimer { get; set; }
+
/// <summary>
/// Gets or sets the custom CSS.
/// </summary>
diff --git a/MediaBrowser.Model/Channels/ChannelFeatures.cs b/MediaBrowser.Model/Channels/ChannelFeatures.cs
index ee1d11bc0..c4e97ffe5 100644
--- a/MediaBrowser.Model/Channels/ChannelFeatures.cs
+++ b/MediaBrowser.Model/Channels/ChannelFeatures.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
using System;
namespace MediaBrowser.Model.Channels
diff --git a/MediaBrowser.Model/Channels/ChannelFolderType.cs b/MediaBrowser.Model/Channels/ChannelFolderType.cs
index 6039eb929..9ead74261 100644
--- a/MediaBrowser.Model/Channels/ChannelFolderType.cs
+++ b/MediaBrowser.Model/Channels/ChannelFolderType.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
namespace MediaBrowser.Model.Channels
{
public enum ChannelFolderType
diff --git a/MediaBrowser.Model/Channels/ChannelInfo.cs b/MediaBrowser.Model/Channels/ChannelInfo.cs
index 9b2d31bf3..bfb34db55 100644
--- a/MediaBrowser.Model/Channels/ChannelInfo.cs
+++ b/MediaBrowser.Model/Channels/ChannelInfo.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
namespace MediaBrowser.Model.Channels
{
public class ChannelInfo
diff --git a/MediaBrowser.Model/Channels/ChannelItemSortField.cs b/MediaBrowser.Model/Channels/ChannelItemSortField.cs
index af75e3edd..2c88e99af 100644
--- a/MediaBrowser.Model/Channels/ChannelItemSortField.cs
+++ b/MediaBrowser.Model/Channels/ChannelItemSortField.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
namespace MediaBrowser.Model.Channels
{
public enum ChannelItemSortField
diff --git a/MediaBrowser.Model/Channels/ChannelMediaContentType.cs b/MediaBrowser.Model/Channels/ChannelMediaContentType.cs
index fc7c21706..61afcbc56 100644
--- a/MediaBrowser.Model/Channels/ChannelMediaContentType.cs
+++ b/MediaBrowser.Model/Channels/ChannelMediaContentType.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
namespace MediaBrowser.Model.Channels
{
public enum ChannelMediaContentType
diff --git a/MediaBrowser.Model/Channels/ChannelMediaType.cs b/MediaBrowser.Model/Channels/ChannelMediaType.cs
index a3fa5cdf9..ba2c06d1b 100644
--- a/MediaBrowser.Model/Channels/ChannelMediaType.cs
+++ b/MediaBrowser.Model/Channels/ChannelMediaType.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
namespace MediaBrowser.Model.Channels
{
public enum ChannelMediaType
diff --git a/MediaBrowser.Model/Channels/ChannelQuery.cs b/MediaBrowser.Model/Channels/ChannelQuery.cs
index 32b368922..88fc94a6f 100644
--- a/MediaBrowser.Model/Channels/ChannelQuery.cs
+++ b/MediaBrowser.Model/Channels/ChannelQuery.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
using System;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Querying;
diff --git a/MediaBrowser.Model/Collections/CollectionCreationResult.cs b/MediaBrowser.Model/Collections/CollectionCreationResult.cs
index 2691f7970..2f1d903a5 100644
--- a/MediaBrowser.Model/Collections/CollectionCreationResult.cs
+++ b/MediaBrowser.Model/Collections/CollectionCreationResult.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
using System;
namespace MediaBrowser.Model.Collections
diff --git a/MediaBrowser.Model/Configuration/AccessSchedule.cs b/MediaBrowser.Model/Configuration/AccessSchedule.cs
index d34e27383..120c47dbc 100644
--- a/MediaBrowser.Model/Configuration/AccessSchedule.cs
+++ b/MediaBrowser.Model/Configuration/AccessSchedule.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
namespace MediaBrowser.Model.Configuration
{
public class AccessSchedule
@@ -7,11 +9,13 @@ namespace MediaBrowser.Model.Configuration
/// </summary>
/// <value>The day of week.</value>
public DynamicDayOfWeek DayOfWeek { get; set; }
+
/// <summary>
/// Gets or sets the start hour.
/// </summary>
/// <value>The start hour.</value>
public double StartHour { get; set; }
+
/// <summary>
/// Gets or sets the end hour.
/// </summary>
diff --git a/MediaBrowser.Model/Configuration/BaseApplicationConfiguration.cs b/MediaBrowser.Model/Configuration/BaseApplicationConfiguration.cs
index 6a1a0f090..cc2541f74 100644
--- a/MediaBrowser.Model/Configuration/BaseApplicationConfiguration.cs
+++ b/MediaBrowser.Model/Configuration/BaseApplicationConfiguration.cs
@@ -1,3 +1,6 @@
+using System;
+using System.Xml.Serialization;
+
namespace MediaBrowser.Model.Configuration
{
/// <summary>
@@ -26,6 +29,24 @@ namespace MediaBrowser.Model.Configuration
public string CachePath { get; set; }
/// <summary>
+ /// Last known version that was ran using the configuration.
+ /// </summary>
+ /// <value>The version from previous run.</value>
+ [XmlIgnore]
+ public Version PreviousVersion { get; set; }
+
+ /// <summary>
+ /// Stringified PreviousVersion to be stored/loaded,
+ /// because System.Version itself isn't xml-serializable
+ /// </summary>
+ /// <value>String value of PreviousVersion</value>
+ public string PreviousVersionStr
+ {
+ get => PreviousVersion?.ToString();
+ set => PreviousVersion = Version.Parse(value);
+ }
+
+ /// <summary>
/// Initializes a new instance of the <see cref="BaseApplicationConfiguration" /> class.
/// </summary>
public BaseApplicationConfiguration()
diff --git a/MediaBrowser.Model/Configuration/DynamicDayOfWeek.cs b/MediaBrowser.Model/Configuration/DynamicDayOfWeek.cs
index 73dda5a77..71b16cfba 100644
--- a/MediaBrowser.Model/Configuration/DynamicDayOfWeek.cs
+++ b/MediaBrowser.Model/Configuration/DynamicDayOfWeek.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
namespace MediaBrowser.Model.Configuration
{
public enum DynamicDayOfWeek
diff --git a/MediaBrowser.Model/Configuration/EncodingOptions.cs b/MediaBrowser.Model/Configuration/EncodingOptions.cs
index 9ae10d980..648568fd7 100644
--- a/MediaBrowser.Model/Configuration/EncodingOptions.cs
+++ b/MediaBrowser.Model/Configuration/EncodingOptions.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
namespace MediaBrowser.Model.Configuration
{
public class EncodingOptions
@@ -8,12 +10,14 @@ namespace MediaBrowser.Model.Configuration
public bool EnableThrottling { get; set; }
public int ThrottleDelaySeconds { get; set; }
public string HardwareAccelerationType { get; set; }
+
/// <summary>
- /// FFmpeg path as set by the user via the UI
+ /// FFmpeg path as set by the user via the UI.
/// </summary>
public string EncoderAppPath { get; set; }
+
/// <summary>
- /// The current FFmpeg path being used by the system and displayed on the transcode page
+ /// The current FFmpeg path being used by the system and displayed on the transcode page.
/// </summary>
public string EncoderAppPathDisplay { get; set; }
public string VaapiDevice { get; set; }
@@ -29,13 +33,14 @@ namespace MediaBrowser.Model.Configuration
public EncodingOptions()
{
DownMixAudioBoost = 2;
- EnableThrottling = true;
+ EnableThrottling = false;
ThrottleDelaySeconds = 180;
EncodingThreadCount = -1;
// This is a DRM device that is almost guaranteed to be there on every intel platform, plus it's the default one in ffmpeg if you don't specify anything
VaapiDevice = "/dev/dri/renderD128";
H264Crf = 23;
H265Crf = 28;
+ DeinterlaceMethod = "yadif";
EnableHardwareEncoding = true;
EnableSubtitleExtraction = true;
HardwareDecodingCodecs = new string[] { "h264", "vc1" };
diff --git a/MediaBrowser.Model/Configuration/ImageOption.cs b/MediaBrowser.Model/Configuration/ImageOption.cs
index 3b985bb1b..2b1268c74 100644
--- a/MediaBrowser.Model/Configuration/ImageOption.cs
+++ b/MediaBrowser.Model/Configuration/ImageOption.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
using MediaBrowser.Model.Entities;
namespace MediaBrowser.Model.Configuration
@@ -9,6 +11,7 @@ namespace MediaBrowser.Model.Configuration
/// </summary>
/// <value>The type.</value>
public ImageType Type { get; set; }
+
/// <summary>
/// Gets or sets the limit.
/// </summary>
diff --git a/MediaBrowser.Model/Configuration/ImageSavingConvention.cs b/MediaBrowser.Model/Configuration/ImageSavingConvention.cs
index 7206fa5fc..485a4d2f8 100644
--- a/MediaBrowser.Model/Configuration/ImageSavingConvention.cs
+++ b/MediaBrowser.Model/Configuration/ImageSavingConvention.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
namespace MediaBrowser.Model.Configuration
{
public enum ImageSavingConvention
diff --git a/MediaBrowser.Model/Configuration/LibraryOptions.cs b/MediaBrowser.Model/Configuration/LibraryOptions.cs
index ba33bee87..4342ccd8a 100644
--- a/MediaBrowser.Model/Configuration/LibraryOptions.cs
+++ b/MediaBrowser.Model/Configuration/LibraryOptions.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
using System;
using System.Collections.Generic;
using MediaBrowser.Model.Entities;
@@ -18,6 +20,7 @@ namespace MediaBrowser.Model.Configuration
public bool ImportMissingEpisodes { get; set; }
public bool EnableAutomaticSeriesGrouping { get; set; }
public bool EnableEmbeddedTitles { get; set; }
+ public bool EnableEmbeddedEpisodeInfos { get; set; }
public int AutomaticRefreshIntervalDays { get; set; }
diff --git a/MediaBrowser.Model/Configuration/MetadataConfiguration.cs b/MediaBrowser.Model/Configuration/MetadataConfiguration.cs
index 87e02d054..706831bdd 100644
--- a/MediaBrowser.Model/Configuration/MetadataConfiguration.cs
+++ b/MediaBrowser.Model/Configuration/MetadataConfiguration.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
namespace MediaBrowser.Model.Configuration
{
public class MetadataConfiguration
diff --git a/MediaBrowser.Model/Configuration/MetadataOptions.cs b/MediaBrowser.Model/Configuration/MetadataOptions.cs
index c095b8cdd..625054b9e 100644
--- a/MediaBrowser.Model/Configuration/MetadataOptions.cs
+++ b/MediaBrowser.Model/Configuration/MetadataOptions.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
using System;
namespace MediaBrowser.Model.Configuration
diff --git a/MediaBrowser.Model/Configuration/MetadataPlugin.cs b/MediaBrowser.Model/Configuration/MetadataPlugin.cs
index d6f863e55..c2b47eb9b 100644
--- a/MediaBrowser.Model/Configuration/MetadataPlugin.cs
+++ b/MediaBrowser.Model/Configuration/MetadataPlugin.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
namespace MediaBrowser.Model.Configuration
{
public class MetadataPlugin
diff --git a/MediaBrowser.Model/Configuration/MetadataPluginSummary.cs b/MediaBrowser.Model/Configuration/MetadataPluginSummary.cs
index 0bd20f837..53063810b 100644
--- a/MediaBrowser.Model/Configuration/MetadataPluginSummary.cs
+++ b/MediaBrowser.Model/Configuration/MetadataPluginSummary.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
using System;
using MediaBrowser.Model.Entities;
diff --git a/MediaBrowser.Model/Configuration/MetadataPluginType.cs b/MediaBrowser.Model/Configuration/MetadataPluginType.cs
index 985107ac9..bff12799f 100644
--- a/MediaBrowser.Model/Configuration/MetadataPluginType.cs
+++ b/MediaBrowser.Model/Configuration/MetadataPluginType.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
namespace MediaBrowser.Model.Configuration
{
/// <summary>
diff --git a/MediaBrowser.Model/Configuration/ServerConfiguration.cs b/MediaBrowser.Model/Configuration/ServerConfiguration.cs
index cf6d9c2f6..063ccd9b9 100644
--- a/MediaBrowser.Model/Configuration/ServerConfiguration.cs
+++ b/MediaBrowser.Model/Configuration/ServerConfiguration.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
using System;
using MediaBrowser.Model.Dto;
@@ -13,12 +15,16 @@ namespace MediaBrowser.Model.Configuration
private string _baseUrl;
/// <summary>
- /// Gets or sets a value indicating whether [enable u pn p].
+ /// Gets or sets a value indicating whether to enable automatic port forwarding.
/// </summary>
- /// <value><c>true</c> if [enable u pn p]; otherwise, <c>false</c>.</value>
public bool EnableUPnP { get; set; }
/// <summary>
+ /// Gets or sets a value indicating whether to enable prometheus metrics exporting.
+ /// </summary>
+ public bool EnableMetrics { get; set; }
+
+ /// <summary>
/// Gets or sets the public mapped port.
/// </summary>
/// <value>The public mapped port.</value>
@@ -146,9 +152,9 @@ namespace MediaBrowser.Model.Configuration
public bool EnableDashboardResponseCaching { get; set; }
/// <summary>
- /// Allows the dashboard to be served from a custom path.
+ /// Gets or sets a custom path to serve the dashboard from.
/// </summary>
- /// <value>The dashboard source path.</value>
+ /// <value>The dashboard source path, or null if the default path should be used.</value>
public string DashboardSourcePath { get; set; }
/// <summary>
@@ -159,10 +165,10 @@ namespace MediaBrowser.Model.Configuration
public MetadataOptions[] MetadataOptions { get; set; }
- public bool EnableAutomaticRestart { get; set; }
public bool SkipDeserializationForBasicTypes { get; set; }
public string ServerName { get; set; }
+
public string BaseUrl
{
get => _baseUrl;
@@ -235,7 +241,7 @@ namespace MediaBrowser.Model.Configuration
CodecsUsed = Array.Empty<string>();
PathSubstitutions = Array.Empty<PathSubstitution>();
IgnoreVirtualInterfaces = false;
- EnableSimpleArtistDetection = true;
+ EnableSimpleArtistDetection = false;
DisplaySpecialsWithinSeasons = true;
EnableExternalContentInSuggestions = true;
@@ -245,15 +251,15 @@ namespace MediaBrowser.Model.Configuration
PublicHttpsPort = DefaultHttpsPort;
HttpServerPortNumber = DefaultHttpPort;
HttpsPortNumber = DefaultHttpsPort;
- EnableHttps = true;
+ EnableMetrics = false;
+ EnableHttps = false;
EnableDashboardResponseCaching = true;
EnableCaseSensitiveItemIds = true;
- EnableAutomaticRestart = true;
AutoRunWebApp = true;
EnableRemoteAccess = true;
- EnableUPnP = true;
+ EnableUPnP = false;
MinResumePct = 5;
MaxResumePct = 90;
diff --git a/MediaBrowser.Model/Configuration/SubtitlePlaybackMode.cs b/MediaBrowser.Model/Configuration/SubtitlePlaybackMode.cs
index fc429934f..f0aa2b98c 100644
--- a/MediaBrowser.Model/Configuration/SubtitlePlaybackMode.cs
+++ b/MediaBrowser.Model/Configuration/SubtitlePlaybackMode.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
namespace MediaBrowser.Model.Configuration
{
public enum SubtitlePlaybackMode
diff --git a/MediaBrowser.Model/Configuration/UnratedItem.cs b/MediaBrowser.Model/Configuration/UnratedItem.cs
index 107b4e520..e1d1a363d 100644
--- a/MediaBrowser.Model/Configuration/UnratedItem.cs
+++ b/MediaBrowser.Model/Configuration/UnratedItem.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
namespace MediaBrowser.Model.Configuration
{
public enum UnratedItem
diff --git a/MediaBrowser.Model/Configuration/UserConfiguration.cs b/MediaBrowser.Model/Configuration/UserConfiguration.cs
index 689459eba..a475c9910 100644
--- a/MediaBrowser.Model/Configuration/UserConfiguration.cs
+++ b/MediaBrowser.Model/Configuration/UserConfiguration.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
using System;
namespace MediaBrowser.Model.Configuration
diff --git a/MediaBrowser.Model/Configuration/XbmcMetadataOptions.cs b/MediaBrowser.Model/Configuration/XbmcMetadataOptions.cs
index 19e6be166..d6c1295f4 100644
--- a/MediaBrowser.Model/Configuration/XbmcMetadataOptions.cs
+++ b/MediaBrowser.Model/Configuration/XbmcMetadataOptions.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
namespace MediaBrowser.Model.Configuration
{
public class XbmcMetadataOptions
diff --git a/MediaBrowser.Model/Cryptography/ICryptoProvider.cs b/MediaBrowser.Model/Cryptography/ICryptoProvider.cs
index 2d75c9b3e..656c04f46 100644
--- a/MediaBrowser.Model/Cryptography/ICryptoProvider.cs
+++ b/MediaBrowser.Model/Cryptography/ICryptoProvider.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
using System.Collections.Generic;
namespace MediaBrowser.Model.Cryptography
diff --git a/MediaBrowser.Model/Devices/ContentUploadHistory.cs b/MediaBrowser.Model/Devices/ContentUploadHistory.cs
index 5dd9bf2d0..c493760d5 100644
--- a/MediaBrowser.Model/Devices/ContentUploadHistory.cs
+++ b/MediaBrowser.Model/Devices/ContentUploadHistory.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
namespace MediaBrowser.Model.Devices
{
public class ContentUploadHistory
diff --git a/MediaBrowser.Model/Devices/DeviceInfo.cs b/MediaBrowser.Model/Devices/DeviceInfo.cs
index 214c49e5e..d2563d1d0 100644
--- a/MediaBrowser.Model/Devices/DeviceInfo.cs
+++ b/MediaBrowser.Model/Devices/DeviceInfo.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
using System;
using MediaBrowser.Model.Session;
@@ -5,6 +7,11 @@ namespace MediaBrowser.Model.Devices
{
public class DeviceInfo
{
+ public DeviceInfo()
+ {
+ Capabilities = new ClientCapabilities();
+ }
+
public string Name { get; set; }
/// <summary>
@@ -12,42 +19,43 @@ namespace MediaBrowser.Model.Devices
/// </summary>
/// <value>The identifier.</value>
public string Id { get; set; }
+
/// <summary>
/// Gets or sets the last name of the user.
/// </summary>
/// <value>The last name of the user.</value>
public string LastUserName { get; set; }
+
/// <summary>
/// Gets or sets the name of the application.
/// </summary>
/// <value>The name of the application.</value>
public string AppName { get; set; }
+
/// <summary>
/// Gets or sets the application version.
/// </summary>
/// <value>The application version.</value>
public string AppVersion { get; set; }
+
/// <summary>
/// Gets or sets the last user identifier.
/// </summary>
/// <value>The last user identifier.</value>
public Guid LastUserId { get; set; }
+
/// <summary>
/// Gets or sets the date last modified.
/// </summary>
/// <value>The date last modified.</value>
public DateTime DateLastActivity { get; set; }
+
/// <summary>
/// Gets or sets the capabilities.
/// </summary>
/// <value>The capabilities.</value>
public ClientCapabilities Capabilities { get; set; }
- public DeviceInfo()
- {
- Capabilities = new ClientCapabilities();
- }
-
public string IconUrl { get; set; }
}
}
diff --git a/MediaBrowser.Model/Devices/DeviceQuery.cs b/MediaBrowser.Model/Devices/DeviceQuery.cs
index 48d7ec142..64e366a56 100644
--- a/MediaBrowser.Model/Devices/DeviceQuery.cs
+++ b/MediaBrowser.Model/Devices/DeviceQuery.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
using System;
namespace MediaBrowser.Model.Devices
@@ -9,6 +11,7 @@ namespace MediaBrowser.Model.Devices
/// </summary>
/// <value><c>null</c> if [supports synchronize] contains no value, <c>true</c> if [supports synchronize]; otherwise, <c>false</c>.</value>
public bool? SupportsSync { get; set; }
+
/// <summary>
/// Gets or sets the user identifier.
/// </summary>
diff --git a/MediaBrowser.Model/Devices/DevicesOptions.cs b/MediaBrowser.Model/Devices/DevicesOptions.cs
index 5bbd33b73..02570650e 100644
--- a/MediaBrowser.Model/Devices/DevicesOptions.cs
+++ b/MediaBrowser.Model/Devices/DevicesOptions.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
using System;
namespace MediaBrowser.Model.Devices
diff --git a/MediaBrowser.Model/Devices/LocalFileInfo.cs b/MediaBrowser.Model/Devices/LocalFileInfo.cs
index cc5c9250b..63a8dc2aa 100644
--- a/MediaBrowser.Model/Devices/LocalFileInfo.cs
+++ b/MediaBrowser.Model/Devices/LocalFileInfo.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
namespace MediaBrowser.Model.Devices
{
public class LocalFileInfo
diff --git a/MediaBrowser.Model/Diagnostics/IProcess.cs b/MediaBrowser.Model/Diagnostics/IProcess.cs
deleted file mode 100644
index cade631c9..000000000
--- a/MediaBrowser.Model/Diagnostics/IProcess.cs
+++ /dev/null
@@ -1,21 +0,0 @@
-using System;
-using System.IO;
-using System.Threading.Tasks;
-
-namespace MediaBrowser.Model.Diagnostics
-{
- public interface IProcess : IDisposable
- {
- event EventHandler Exited;
-
- void Kill();
- bool WaitForExit(int timeMs);
- Task<bool> WaitForExitAsync(int timeMs);
- int ExitCode { get; }
- void Start();
- StreamWriter StandardInput { get; }
- StreamReader StandardError { get; }
- StreamReader StandardOutput { get; }
- ProcessOptions StartInfo { get; }
- }
-}
diff --git a/MediaBrowser.Model/Diagnostics/IProcessFactory.cs b/MediaBrowser.Model/Diagnostics/IProcessFactory.cs
deleted file mode 100644
index a11be8f4e..000000000
--- a/MediaBrowser.Model/Diagnostics/IProcessFactory.cs
+++ /dev/null
@@ -1,22 +0,0 @@
-namespace MediaBrowser.Model.Diagnostics
-{
- public interface IProcessFactory
- {
- IProcess Create(ProcessOptions options);
- }
-
- public class ProcessOptions
- {
- public string FileName { get; set; }
- public string Arguments { get; set; }
- public string WorkingDirectory { get; set; }
- public bool CreateNoWindow { get; set; }
- public bool UseShellExecute { get; set; }
- public bool EnableRaisingEvents { get; set; }
- public bool ErrorDialog { get; set; }
- public bool RedirectStandardError { get; set; }
- public bool RedirectStandardInput { get; set; }
- public bool RedirectStandardOutput { get; set; }
- public bool IsHidden { get; set; }
- }
-}
diff --git a/MediaBrowser.Model/Dlna/AudioOptions.cs b/MediaBrowser.Model/Dlna/AudioOptions.cs
index 6dfe8093e..40081b282 100644
--- a/MediaBrowser.Model/Dlna/AudioOptions.cs
+++ b/MediaBrowser.Model/Dlna/AudioOptions.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
using System;
using MediaBrowser.Model.Dto;
diff --git a/MediaBrowser.Model/Dlna/CodecProfile.cs b/MediaBrowser.Model/Dlna/CodecProfile.cs
index 9ea248908..756e500dd 100644
--- a/MediaBrowser.Model/Dlna/CodecProfile.cs
+++ b/MediaBrowser.Model/Dlna/CodecProfile.cs
@@ -1,3 +1,6 @@
+#pragma warning disable CS1591
+
+using System;
using System.Xml.Serialization;
using MediaBrowser.Model.Extensions;
@@ -20,8 +23,8 @@ namespace MediaBrowser.Model.Dlna
public CodecProfile()
{
- Conditions = new ProfileCondition[] { };
- ApplyConditions = new ProfileCondition[] { };
+ Conditions = Array.Empty<ProfileCondition>();
+ ApplyConditions = Array.Empty<ProfileCondition>();
}
public string[] GetCodecs()
diff --git a/MediaBrowser.Model/Dlna/CodecType.cs b/MediaBrowser.Model/Dlna/CodecType.cs
index d777be4c2..c9f090e4c 100644
--- a/MediaBrowser.Model/Dlna/CodecType.cs
+++ b/MediaBrowser.Model/Dlna/CodecType.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
namespace MediaBrowser.Model.Dlna
{
public enum CodecType
diff --git a/MediaBrowser.Model/Dlna/ConditionProcessor.cs b/MediaBrowser.Model/Dlna/ConditionProcessor.cs
index 3629d1547..7423efaf6 100644
--- a/MediaBrowser.Model/Dlna/ConditionProcessor.cs
+++ b/MediaBrowser.Model/Dlna/ConditionProcessor.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
using System;
using System.Globalization;
using MediaBrowser.Model.Extensions;
@@ -169,9 +171,9 @@ namespace MediaBrowser.Model.Dlna
return ListHelper.ContainsIgnoreCase(expected.Split('|'), currentValue);
}
case ProfileConditionType.Equals:
- return StringHelper.EqualsIgnoreCase(currentValue, expected);
+ return string.Equals(currentValue, expected, StringComparison.OrdinalIgnoreCase);
case ProfileConditionType.NotEquals:
- return !StringHelper.EqualsIgnoreCase(currentValue, expected);
+ return !string.Equals(currentValue, expected, StringComparison.OrdinalIgnoreCase);
default:
throw new InvalidOperationException("Unexpected ProfileConditionType: " + condition.Condition);
}
diff --git a/MediaBrowser.Model/Dlna/ContainerProfile.cs b/MediaBrowser.Model/Dlna/ContainerProfile.cs
index 073324c26..e6691c513 100644
--- a/MediaBrowser.Model/Dlna/ContainerProfile.cs
+++ b/MediaBrowser.Model/Dlna/ContainerProfile.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
using System;
using System.Xml.Serialization;
using MediaBrowser.Model.Extensions;
diff --git a/MediaBrowser.Model/Dlna/ContentFeatureBuilder.cs b/MediaBrowser.Model/Dlna/ContentFeatureBuilder.cs
index 2333fa7a0..a20f11503 100644
--- a/MediaBrowser.Model/Dlna/ContentFeatureBuilder.cs
+++ b/MediaBrowser.Model/Dlna/ContentFeatureBuilder.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
using System;
using System.Collections.Generic;
using System.Globalization;
diff --git a/MediaBrowser.Model/Dlna/DeviceIdentification.cs b/MediaBrowser.Model/Dlna/DeviceIdentification.cs
index 84573521a..f1699d930 100644
--- a/MediaBrowser.Model/Dlna/DeviceIdentification.cs
+++ b/MediaBrowser.Model/Dlna/DeviceIdentification.cs
@@ -1,3 +1,7 @@
+#pragma warning disable CS1591
+
+using System;
+
namespace MediaBrowser.Model.Dlna
{
public class DeviceIdentification
@@ -7,46 +11,55 @@ namespace MediaBrowser.Model.Dlna
/// </summary>
/// <value>The name of the friendly.</value>
public string FriendlyName { get; set; }
+
/// <summary>
/// Gets or sets the model number.
/// </summary>
/// <value>The model number.</value>
public string ModelNumber { get; set; }
+
/// <summary>
/// Gets or sets the serial number.
/// </summary>
/// <value>The serial number.</value>
public string SerialNumber { get; set; }
+
/// <summary>
/// Gets or sets the name of the model.
/// </summary>
/// <value>The name of the model.</value>
public string ModelName { get; set; }
+
/// <summary>
/// Gets or sets the model description.
/// </summary>
/// <value>The model description.</value>
public string ModelDescription { get; set; }
+
/// <summary>
/// Gets or sets the device description.
/// </summary>
/// <value>The device description.</value>
public string DeviceDescription { get; set; }
+
/// <summary>
/// Gets or sets the model URL.
/// </summary>
/// <value>The model URL.</value>
public string ModelUrl { get; set; }
+
/// <summary>
/// Gets or sets the manufacturer.
/// </summary>
/// <value>The manufacturer.</value>
public string Manufacturer { get; set; }
+
/// <summary>
/// Gets or sets the manufacturer URL.
/// </summary>
/// <value>The manufacturer URL.</value>
public string ManufacturerUrl { get; set; }
+
/// <summary>
/// Gets or sets the headers.
/// </summary>
@@ -55,7 +68,7 @@ namespace MediaBrowser.Model.Dlna
public DeviceIdentification()
{
- Headers = new HttpHeaderInfo[] { };
+ Headers = Array.Empty<HttpHeaderInfo>();
}
}
}
diff --git a/MediaBrowser.Model/Dlna/DeviceProfile.cs b/MediaBrowser.Model/Dlna/DeviceProfile.cs
index 8d8fe9eb5..0cefbbe01 100644
--- a/MediaBrowser.Model/Dlna/DeviceProfile.cs
+++ b/MediaBrowser.Model/Dlna/DeviceProfile.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
using System;
using System.Xml.Serialization;
using MediaBrowser.Model.Extensions;
@@ -122,7 +124,7 @@ namespace MediaBrowser.Model.Dlna
continue;
}
- if (!StringHelper.EqualsIgnoreCase(container, i.Container))
+ if (!string.Equals(container, i.Container, StringComparison.OrdinalIgnoreCase))
{
continue;
}
@@ -148,7 +150,7 @@ namespace MediaBrowser.Model.Dlna
continue;
}
- if (!StringHelper.EqualsIgnoreCase(container, i.Container))
+ if (!string.Equals(container, i.Container, StringComparison.OrdinalIgnoreCase))
{
continue;
}
@@ -158,7 +160,7 @@ namespace MediaBrowser.Model.Dlna
continue;
}
- if (!StringHelper.EqualsIgnoreCase(videoCodec, i.VideoCodec ?? string.Empty))
+ if (!string.Equals(videoCodec, i.VideoCodec ?? string.Empty, StringComparison.OrdinalIgnoreCase))
{
continue;
}
diff --git a/MediaBrowser.Model/Dlna/DeviceProfileInfo.cs b/MediaBrowser.Model/Dlna/DeviceProfileInfo.cs
index c78f0d9b2..347583965 100644
--- a/MediaBrowser.Model/Dlna/DeviceProfileInfo.cs
+++ b/MediaBrowser.Model/Dlna/DeviceProfileInfo.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
namespace MediaBrowser.Model.Dlna
{
public class DeviceProfileInfo
diff --git a/MediaBrowser.Model/Dlna/DeviceProfileType.cs b/MediaBrowser.Model/Dlna/DeviceProfileType.cs
index 2449fa434..46062abd0 100644
--- a/MediaBrowser.Model/Dlna/DeviceProfileType.cs
+++ b/MediaBrowser.Model/Dlna/DeviceProfileType.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
namespace MediaBrowser.Model.Dlna
{
public enum DeviceProfileType
diff --git a/MediaBrowser.Model/Dlna/DirectPlayProfile.cs b/MediaBrowser.Model/Dlna/DirectPlayProfile.cs
index 5a54847d7..b43f8633e 100644
--- a/MediaBrowser.Model/Dlna/DirectPlayProfile.cs
+++ b/MediaBrowser.Model/Dlna/DirectPlayProfile.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
using System.Xml.Serialization;
namespace MediaBrowser.Model.Dlna
@@ -23,12 +25,12 @@ namespace MediaBrowser.Model.Dlna
public bool SupportsVideoCodec(string codec)
{
- return ContainerProfile.ContainsContainer(VideoCodec, codec);
+ return Type == DlnaProfileType.Video && ContainerProfile.ContainsContainer(VideoCodec, codec);
}
public bool SupportsAudioCodec(string codec)
{
- return ContainerProfile.ContainsContainer(AudioCodec, codec);
+ return (Type == DlnaProfileType.Audio || Type == DlnaProfileType.Video) && ContainerProfile.ContainsContainer(AudioCodec, codec);
}
}
}
diff --git a/MediaBrowser.Model/Dlna/DlnaFlags.cs b/MediaBrowser.Model/Dlna/DlnaFlags.cs
index d076e73ec..02d9ea9c5 100644
--- a/MediaBrowser.Model/Dlna/DlnaFlags.cs
+++ b/MediaBrowser.Model/Dlna/DlnaFlags.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
using System;
namespace MediaBrowser.Model.Dlna
diff --git a/MediaBrowser.Model/Dlna/DlnaMaps.cs b/MediaBrowser.Model/Dlna/DlnaMaps.cs
index 880d05724..052b4b78b 100644
--- a/MediaBrowser.Model/Dlna/DlnaMaps.cs
+++ b/MediaBrowser.Model/Dlna/DlnaMaps.cs
@@ -1,6 +1,8 @@
+#pragma warning disable CS1591
+
namespace MediaBrowser.Model.Dlna
{
- public class DlnaMaps
+ public static class DlnaMaps
{
private static readonly string DefaultStreaming =
FlagsToString(DlnaFlags.StreamingTransferMode |
diff --git a/MediaBrowser.Model/Dlna/DlnaProfileType.cs b/MediaBrowser.Model/Dlna/DlnaProfileType.cs
index 6a23bbb04..e30ed0f3c 100644
--- a/MediaBrowser.Model/Dlna/DlnaProfileType.cs
+++ b/MediaBrowser.Model/Dlna/DlnaProfileType.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
namespace MediaBrowser.Model.Dlna
{
public enum DlnaProfileType
diff --git a/MediaBrowser.Model/Dlna/EncodingContext.cs b/MediaBrowser.Model/Dlna/EncodingContext.cs
index 7352bdd19..79ca6366d 100644
--- a/MediaBrowser.Model/Dlna/EncodingContext.cs
+++ b/MediaBrowser.Model/Dlna/EncodingContext.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
namespace MediaBrowser.Model.Dlna
{
public enum EncodingContext
diff --git a/MediaBrowser.Model/Dlna/HeaderMatchType.cs b/MediaBrowser.Model/Dlna/HeaderMatchType.cs
index b0a1438f6..2a9abb20e 100644
--- a/MediaBrowser.Model/Dlna/HeaderMatchType.cs
+++ b/MediaBrowser.Model/Dlna/HeaderMatchType.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
namespace MediaBrowser.Model.Dlna
{
public enum HeaderMatchType
diff --git a/MediaBrowser.Model/Dlna/HttpHeaderInfo.cs b/MediaBrowser.Model/Dlna/HttpHeaderInfo.cs
index d15727504..f23a24084 100644
--- a/MediaBrowser.Model/Dlna/HttpHeaderInfo.cs
+++ b/MediaBrowser.Model/Dlna/HttpHeaderInfo.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
using System.Xml.Serialization;
namespace MediaBrowser.Model.Dlna
diff --git a/MediaBrowser.Model/Dlna/IDeviceDiscovery.cs b/MediaBrowser.Model/Dlna/IDeviceDiscovery.cs
index 3de3fe761..76c9a4b04 100644
--- a/MediaBrowser.Model/Dlna/IDeviceDiscovery.cs
+++ b/MediaBrowser.Model/Dlna/IDeviceDiscovery.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
using System;
using MediaBrowser.Model.Events;
diff --git a/MediaBrowser.Model/Dlna/ITranscoderSupport.cs b/MediaBrowser.Model/Dlna/ITranscoderSupport.cs
index c0ff54c3f..7e35cc85b 100644
--- a/MediaBrowser.Model/Dlna/ITranscoderSupport.cs
+++ b/MediaBrowser.Model/Dlna/ITranscoderSupport.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
namespace MediaBrowser.Model.Dlna
{
public interface ITranscoderSupport
diff --git a/MediaBrowser.Model/Dlna/MediaFormatProfile.cs b/MediaBrowser.Model/Dlna/MediaFormatProfile.cs
index 7c56fc5f4..20e05b8a9 100644
--- a/MediaBrowser.Model/Dlna/MediaFormatProfile.cs
+++ b/MediaBrowser.Model/Dlna/MediaFormatProfile.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
namespace MediaBrowser.Model.Dlna
{
public enum MediaFormatProfile
diff --git a/MediaBrowser.Model/Dlna/MediaFormatProfileResolver.cs b/MediaBrowser.Model/Dlna/MediaFormatProfileResolver.cs
index 672784589..4cd318abb 100644
--- a/MediaBrowser.Model/Dlna/MediaFormatProfileResolver.cs
+++ b/MediaBrowser.Model/Dlna/MediaFormatProfileResolver.cs
@@ -1,7 +1,8 @@
+#pragma warning disable CS1591
+
using System;
using System.Collections.Generic;
using System.Linq;
-using MediaBrowser.Model.Extensions;
using MediaBrowser.Model.MediaInfo;
namespace MediaBrowser.Model.Dlna
@@ -17,53 +18,53 @@ namespace MediaBrowser.Model.Dlna
private MediaFormatProfile[] ResolveVideoFormatInternal(string container, string videoCodec, string audioCodec, int? width, int? height, TransportStreamTimestamp timestampType)
{
- if (StringHelper.EqualsIgnoreCase(container, "asf"))
+ if (string.Equals(container, "asf", StringComparison.OrdinalIgnoreCase))
{
MediaFormatProfile? val = ResolveVideoASFFormat(videoCodec, audioCodec, width, height);
return val.HasValue ? new MediaFormatProfile[] { val.Value } : new MediaFormatProfile[] { };
}
- if (StringHelper.EqualsIgnoreCase(container, "mp4"))
+ if (string.Equals(container, "mp4", StringComparison.OrdinalIgnoreCase))
{
MediaFormatProfile? val = ResolveVideoMP4Format(videoCodec, audioCodec, width, height);
return val.HasValue ? new MediaFormatProfile[] { val.Value } : new MediaFormatProfile[] { };
}
- if (StringHelper.EqualsIgnoreCase(container, "avi"))
+ if (string.Equals(container, "avi", StringComparison.OrdinalIgnoreCase))
return new MediaFormatProfile[] { MediaFormatProfile.AVI };
- if (StringHelper.EqualsIgnoreCase(container, "mkv"))
+ if (string.Equals(container, "mkv", StringComparison.OrdinalIgnoreCase))
return new MediaFormatProfile[] { MediaFormatProfile.MATROSKA };
- if (StringHelper.EqualsIgnoreCase(container, "mpeg2ps") ||
- StringHelper.EqualsIgnoreCase(container, "ts"))
+ if (string.Equals(container, "mpeg2ps", StringComparison.OrdinalIgnoreCase) ||
+ string.Equals(container, "ts", StringComparison.OrdinalIgnoreCase))
return new MediaFormatProfile[] { MediaFormatProfile.MPEG_PS_NTSC, MediaFormatProfile.MPEG_PS_PAL };
- if (StringHelper.EqualsIgnoreCase(container, "mpeg1video"))
+ if (string.Equals(container, "mpeg1video", StringComparison.OrdinalIgnoreCase))
return new MediaFormatProfile[] { MediaFormatProfile.MPEG1 };
- if (StringHelper.EqualsIgnoreCase(container, "mpeg2ts") ||
- StringHelper.EqualsIgnoreCase(container, "mpegts") ||
- StringHelper.EqualsIgnoreCase(container, "m2ts"))
+ if (string.Equals(container, "mpeg2ts", StringComparison.OrdinalIgnoreCase) ||
+ string.Equals(container, "mpegts", StringComparison.OrdinalIgnoreCase) ||
+ string.Equals(container, "m2ts", StringComparison.OrdinalIgnoreCase))
{
return ResolveVideoMPEG2TSFormat(videoCodec, audioCodec, width, height, timestampType);
}
- if (StringHelper.EqualsIgnoreCase(container, "flv"))
+ if (string.Equals(container, "flv", StringComparison.OrdinalIgnoreCase))
return new MediaFormatProfile[] { MediaFormatProfile.FLV };
- if (StringHelper.EqualsIgnoreCase(container, "wtv"))
+ if (string.Equals(container, "wtv", StringComparison.OrdinalIgnoreCase))
return new MediaFormatProfile[] { MediaFormatProfile.WTV };
- if (StringHelper.EqualsIgnoreCase(container, "3gp"))
+ if (string.Equals(container, "3gp", StringComparison.OrdinalIgnoreCase))
{
MediaFormatProfile? val = ResolveVideo3GPFormat(videoCodec, audioCodec);
return val.HasValue ? new MediaFormatProfile[] { val.Value } : new MediaFormatProfile[] { };
}
- if (StringHelper.EqualsIgnoreCase(container, "ogv") || StringHelper.EqualsIgnoreCase(container, "ogg"))
+ if (string.Equals(container, "ogv", StringComparison.OrdinalIgnoreCase) || string.Equals(container, "ogg", StringComparison.OrdinalIgnoreCase))
return new MediaFormatProfile[] { MediaFormatProfile.OGV };
return new MediaFormatProfile[] { };
@@ -89,7 +90,7 @@ namespace MediaBrowser.Model.Dlna
resolution = "H";
}
- if (StringHelper.EqualsIgnoreCase(videoCodec, "mpeg2video"))
+ if (string.Equals(videoCodec, "mpeg2video", StringComparison.OrdinalIgnoreCase))
{
var list = new List<MediaFormatProfile>();
@@ -97,18 +98,18 @@ namespace MediaBrowser.Model.Dlna
list.Add(ValueOf("MPEG_TS_SD_EU" + suffix));
list.Add(ValueOf("MPEG_TS_SD_KO" + suffix));
- if ((timestampType == TransportStreamTimestamp.Valid) && StringHelper.EqualsIgnoreCase(audioCodec, "aac"))
+ if ((timestampType == TransportStreamTimestamp.Valid) && string.Equals(audioCodec, "aac", StringComparison.OrdinalIgnoreCase))
{
list.Add(MediaFormatProfile.MPEG_TS_JP_T);
}
return list.ToArray();
}
- if (StringHelper.EqualsIgnoreCase(videoCodec, "h264"))
+ if (string.Equals(videoCodec, "h264", StringComparison.OrdinalIgnoreCase))
{
- if (StringHelper.EqualsIgnoreCase(audioCodec, "lpcm"))
+ if (string.Equals(audioCodec, "lpcm", StringComparison.OrdinalIgnoreCase))
return new MediaFormatProfile[] { MediaFormatProfile.AVC_TS_HD_50_LPCM_T };
- if (StringHelper.EqualsIgnoreCase(audioCodec, "dts"))
+ if (string.Equals(audioCodec, "dts", StringComparison.OrdinalIgnoreCase))
{
if (timestampType == TransportStreamTimestamp.None)
{
@@ -117,7 +118,7 @@ namespace MediaBrowser.Model.Dlna
return new MediaFormatProfile[] { MediaFormatProfile.AVC_TS_HD_DTS_T };
}
- if (StringHelper.EqualsIgnoreCase(audioCodec, "mp2"))
+ if (string.Equals(audioCodec, "mp2", StringComparison.OrdinalIgnoreCase))
{
if (timestampType == TransportStreamTimestamp.None)
{
@@ -127,19 +128,19 @@ namespace MediaBrowser.Model.Dlna
return new MediaFormatProfile[] { ValueOf(string.Format("AVC_TS_HP_{0}D_MPEG1_L2_T", resolution)) };
}
- if (StringHelper.EqualsIgnoreCase(audioCodec, "aac"))
+ if (string.Equals(audioCodec, "aac", StringComparison.OrdinalIgnoreCase))
return new MediaFormatProfile[] { ValueOf(string.Format("AVC_TS_MP_{0}D_AAC_MULT5{1}", resolution, suffix)) };
- if (StringHelper.EqualsIgnoreCase(audioCodec, "mp3"))
+ if (string.Equals(audioCodec, "mp3", StringComparison.OrdinalIgnoreCase))
return new MediaFormatProfile[] { ValueOf(string.Format("AVC_TS_MP_{0}D_MPEG1_L3{1}", resolution, suffix)) };
if (string.IsNullOrEmpty(audioCodec) ||
- StringHelper.EqualsIgnoreCase(audioCodec, "ac3"))
+ string.Equals(audioCodec, "ac3", StringComparison.OrdinalIgnoreCase))
return new MediaFormatProfile[] { ValueOf(string.Format("AVC_TS_MP_{0}D_AC3{1}", resolution, suffix)) };
}
- else if (StringHelper.EqualsIgnoreCase(videoCodec, "vc1"))
+ else if (string.Equals(videoCodec, "vc1", StringComparison.OrdinalIgnoreCase))
{
- if (string.IsNullOrEmpty(audioCodec) || StringHelper.EqualsIgnoreCase(audioCodec, "ac3"))
+ if (string.IsNullOrEmpty(audioCodec) || string.Equals(audioCodec, "ac3", StringComparison.OrdinalIgnoreCase))
{
if ((width.HasValue && width.Value > 720) || (height.HasValue && height.Value > 576))
{
@@ -147,23 +148,23 @@ namespace MediaBrowser.Model.Dlna
}
return new MediaFormatProfile[] { MediaFormatProfile.VC1_TS_AP_L1_AC3_ISO };
}
- if (StringHelper.EqualsIgnoreCase(audioCodec, "dts"))
+ if (string.Equals(audioCodec, "dts", StringComparison.OrdinalIgnoreCase))
{
- suffix = StringHelper.EqualsIgnoreCase(suffix, "_ISO") ? suffix : "_T";
+ suffix = string.Equals(suffix, "_ISO", StringComparison.OrdinalIgnoreCase) ? suffix : "_T";
return new MediaFormatProfile[] { ValueOf(string.Format("VC1_TS_HD_DTS{0}", suffix)) };
}
}
- else if (StringHelper.EqualsIgnoreCase(videoCodec, "mpeg4") || StringHelper.EqualsIgnoreCase(videoCodec, "msmpeg4"))
+ else if (string.Equals(videoCodec, "mpeg4", StringComparison.OrdinalIgnoreCase) || string.Equals(videoCodec, "msmpeg4", StringComparison.OrdinalIgnoreCase))
{
- if (StringHelper.EqualsIgnoreCase(audioCodec, "aac"))
+ if (string.Equals(audioCodec, "aac", StringComparison.OrdinalIgnoreCase))
return new MediaFormatProfile[] { ValueOf(string.Format("MPEG4_P2_TS_ASP_AAC{0}", suffix)) };
- if (StringHelper.EqualsIgnoreCase(audioCodec, "mp3"))
+ if (string.Equals(audioCodec, "mp3", StringComparison.OrdinalIgnoreCase))
return new MediaFormatProfile[] { ValueOf(string.Format("MPEG4_P2_TS_ASP_MPEG1_L3{0}", suffix)) };
- if (StringHelper.EqualsIgnoreCase(audioCodec, "mp2"))
+ if (string.Equals(audioCodec, "mp2", StringComparison.OrdinalIgnoreCase))
return new MediaFormatProfile[] { ValueOf(string.Format("MPEG4_P2_TS_ASP_MPEG2_L2{0}", suffix)) };
- if (StringHelper.EqualsIgnoreCase(audioCodec, "ac3"))
+ if (string.Equals(audioCodec, "ac3", StringComparison.OrdinalIgnoreCase))
return new MediaFormatProfile[] { ValueOf(string.Format("MPEG4_P2_TS_ASP_AC3{0}", suffix)) };
}
@@ -177,16 +178,16 @@ namespace MediaBrowser.Model.Dlna
private MediaFormatProfile? ResolveVideoMP4Format(string videoCodec, string audioCodec, int? width, int? height)
{
- if (StringHelper.EqualsIgnoreCase(videoCodec, "h264"))
+ if (string.Equals(videoCodec, "h264", StringComparison.OrdinalIgnoreCase))
{
- if (StringHelper.EqualsIgnoreCase(audioCodec, "lpcm"))
+ if (string.Equals(audioCodec, "lpcm", StringComparison.OrdinalIgnoreCase))
return MediaFormatProfile.AVC_MP4_LPCM;
if (string.IsNullOrEmpty(audioCodec) ||
- StringHelper.EqualsIgnoreCase(audioCodec, "ac3"))
+ string.Equals(audioCodec, "ac3", StringComparison.OrdinalIgnoreCase))
{
return MediaFormatProfile.AVC_MP4_MP_SD_AC3;
}
- if (StringHelper.EqualsIgnoreCase(audioCodec, "mp3"))
+ if (string.Equals(audioCodec, "mp3", StringComparison.OrdinalIgnoreCase))
{
return MediaFormatProfile.AVC_MP4_MP_SD_MPEG1_L3;
}
@@ -194,41 +195,41 @@ namespace MediaBrowser.Model.Dlna
{
if ((width.Value <= 720) && (height.Value <= 576))
{
- if (StringHelper.EqualsIgnoreCase(audioCodec, "aac"))
+ if (string.Equals(audioCodec, "aac", StringComparison.OrdinalIgnoreCase))
return MediaFormatProfile.AVC_MP4_MP_SD_AAC_MULT5;
}
else if ((width.Value <= 1280) && (height.Value <= 720))
{
- if (StringHelper.EqualsIgnoreCase(audioCodec, "aac"))
+ if (string.Equals(audioCodec, "aac", StringComparison.OrdinalIgnoreCase))
return MediaFormatProfile.AVC_MP4_MP_HD_720p_AAC;
}
else if ((width.Value <= 1920) && (height.Value <= 1080))
{
- if (StringHelper.EqualsIgnoreCase(audioCodec, "aac"))
+ if (string.Equals(audioCodec, "aac", StringComparison.OrdinalIgnoreCase))
{
return MediaFormatProfile.AVC_MP4_MP_HD_1080i_AAC;
}
}
}
}
- else if (StringHelper.EqualsIgnoreCase(videoCodec, "mpeg4") ||
- StringHelper.EqualsIgnoreCase(videoCodec, "msmpeg4"))
+ else if (string.Equals(videoCodec, "mpeg4", StringComparison.OrdinalIgnoreCase) ||
+ string.Equals(videoCodec, "msmpeg4", StringComparison.OrdinalIgnoreCase))
{
if (width.HasValue && height.HasValue && width.Value <= 720 && height.Value <= 576)
{
- if (string.IsNullOrEmpty(audioCodec) || StringHelper.EqualsIgnoreCase(audioCodec, "aac"))
+ if (string.IsNullOrEmpty(audioCodec) || string.Equals(audioCodec, "aac", StringComparison.OrdinalIgnoreCase))
return MediaFormatProfile.MPEG4_P2_MP4_ASP_AAC;
- if (StringHelper.EqualsIgnoreCase(audioCodec, "ac3") || StringHelper.EqualsIgnoreCase(audioCodec, "mp3"))
+ if (string.Equals(audioCodec, "ac3", StringComparison.OrdinalIgnoreCase) || string.Equals(audioCodec, "mp3", StringComparison.OrdinalIgnoreCase))
{
return MediaFormatProfile.MPEG4_P2_MP4_NDSD;
}
}
- else if (string.IsNullOrEmpty(audioCodec) || StringHelper.EqualsIgnoreCase(audioCodec, "aac"))
+ else if (string.IsNullOrEmpty(audioCodec) || string.Equals(audioCodec, "aac", StringComparison.OrdinalIgnoreCase))
{
return MediaFormatProfile.MPEG4_P2_MP4_SP_L6_AAC;
}
}
- else if (StringHelper.EqualsIgnoreCase(videoCodec, "h263") && StringHelper.EqualsIgnoreCase(audioCodec, "aac"))
+ else if (string.Equals(videoCodec, "h263", StringComparison.OrdinalIgnoreCase) && string.Equals(audioCodec, "aac", StringComparison.OrdinalIgnoreCase))
{
return MediaFormatProfile.MPEG4_H263_MP4_P0_L10_AAC;
}
@@ -238,20 +239,20 @@ namespace MediaBrowser.Model.Dlna
private MediaFormatProfile? ResolveVideo3GPFormat(string videoCodec, string audioCodec)
{
- if (StringHelper.EqualsIgnoreCase(videoCodec, "h264"))
+ if (string.Equals(videoCodec, "h264", StringComparison.OrdinalIgnoreCase))
{
- if (string.IsNullOrEmpty(audioCodec) || StringHelper.EqualsIgnoreCase(audioCodec, "aac"))
+ if (string.IsNullOrEmpty(audioCodec) || string.Equals(audioCodec, "aac", StringComparison.OrdinalIgnoreCase))
return MediaFormatProfile.AVC_3GPP_BL_QCIF15_AAC;
}
- else if (StringHelper.EqualsIgnoreCase(videoCodec, "mpeg4") ||
- StringHelper.EqualsIgnoreCase(videoCodec, "msmpeg4"))
+ else if (string.Equals(videoCodec, "mpeg4", StringComparison.OrdinalIgnoreCase) ||
+ string.Equals(videoCodec, "msmpeg4", StringComparison.OrdinalIgnoreCase))
{
- if (string.IsNullOrEmpty(audioCodec) || StringHelper.EqualsIgnoreCase(audioCodec, "wma"))
+ if (string.IsNullOrEmpty(audioCodec) || string.Equals(audioCodec, "wma", StringComparison.OrdinalIgnoreCase))
return MediaFormatProfile.MPEG4_P2_3GPP_SP_L0B_AAC;
- if (StringHelper.EqualsIgnoreCase(audioCodec, "amrnb"))
+ if (string.Equals(audioCodec, "amrnb", StringComparison.OrdinalIgnoreCase))
return MediaFormatProfile.MPEG4_P2_3GPP_SP_L0B_AMR;
}
- else if (StringHelper.EqualsIgnoreCase(videoCodec, "h263") && StringHelper.EqualsIgnoreCase(audioCodec, "amrnb"))
+ else if (string.Equals(videoCodec, "h263", StringComparison.OrdinalIgnoreCase) && string.Equals(audioCodec, "amrnb", StringComparison.OrdinalIgnoreCase))
{
return MediaFormatProfile.MPEG4_H263_3GPP_P0_L10_AMR;
}
@@ -261,15 +262,15 @@ namespace MediaBrowser.Model.Dlna
private MediaFormatProfile? ResolveVideoASFFormat(string videoCodec, string audioCodec, int? width, int? height)
{
- if (StringHelper.EqualsIgnoreCase(videoCodec, "wmv") &&
- (string.IsNullOrEmpty(audioCodec) || StringHelper.EqualsIgnoreCase(audioCodec, "wma") || StringHelper.EqualsIgnoreCase(videoCodec, "wmapro")))
+ if (string.Equals(videoCodec, "wmv", StringComparison.OrdinalIgnoreCase) &&
+ (string.IsNullOrEmpty(audioCodec) || string.Equals(audioCodec, "wma", StringComparison.OrdinalIgnoreCase) || string.Equals(videoCodec, "wmapro", StringComparison.OrdinalIgnoreCase)))
{
if (width.HasValue && height.HasValue)
{
if ((width.Value <= 720) && (height.Value <= 576))
{
- if (string.IsNullOrEmpty(audioCodec) || StringHelper.EqualsIgnoreCase(audioCodec, "wma"))
+ if (string.IsNullOrEmpty(audioCodec) || string.Equals(audioCodec, "wma", StringComparison.OrdinalIgnoreCase))
{
return MediaFormatProfile.WMVMED_FULL;
}
@@ -277,14 +278,14 @@ namespace MediaBrowser.Model.Dlna
}
}
- if (string.IsNullOrEmpty(audioCodec) || StringHelper.EqualsIgnoreCase(audioCodec, "wma"))
+ if (string.IsNullOrEmpty(audioCodec) || string.Equals(audioCodec, "wma", StringComparison.OrdinalIgnoreCase))
{
return MediaFormatProfile.WMVHIGH_FULL;
}
return MediaFormatProfile.WMVHIGH_PRO;
}
- if (StringHelper.EqualsIgnoreCase(videoCodec, "vc1"))
+ if (string.Equals(videoCodec, "vc1", StringComparison.OrdinalIgnoreCase))
{
if (width.HasValue && height.HasValue)
{
@@ -296,7 +297,7 @@ namespace MediaBrowser.Model.Dlna
return MediaFormatProfile.VC1_ASF_AP_L3_WMA;
}
}
- else if (StringHelper.EqualsIgnoreCase(videoCodec, "mpeg2video"))
+ else if (string.Equals(videoCodec, "mpeg2video", StringComparison.OrdinalIgnoreCase))
{
return MediaFormatProfile.DVR_MS;
}
@@ -306,27 +307,27 @@ namespace MediaBrowser.Model.Dlna
public MediaFormatProfile? ResolveAudioFormat(string container, int? bitrate, int? frequency, int? channels)
{
- if (StringHelper.EqualsIgnoreCase(container, "asf"))
+ if (string.Equals(container, "asf", StringComparison.OrdinalIgnoreCase))
return ResolveAudioASFFormat(bitrate);
- if (StringHelper.EqualsIgnoreCase(container, "mp3"))
+ if (string.Equals(container, "mp3", StringComparison.OrdinalIgnoreCase))
return MediaFormatProfile.MP3;
- if (StringHelper.EqualsIgnoreCase(container, "lpcm"))
+ if (string.Equals(container, "lpcm", StringComparison.OrdinalIgnoreCase))
return ResolveAudioLPCMFormat(frequency, channels);
- if (StringHelper.EqualsIgnoreCase(container, "mp4") ||
- StringHelper.EqualsIgnoreCase(container, "aac"))
+ if (string.Equals(container, "mp4", StringComparison.OrdinalIgnoreCase) ||
+ string.Equals(container, "aac", StringComparison.OrdinalIgnoreCase))
return ResolveAudioMP4Format(bitrate);
- if (StringHelper.EqualsIgnoreCase(container, "adts"))
+ if (string.Equals(container, "adts", StringComparison.OrdinalIgnoreCase))
return ResolveAudioADTSFormat(bitrate);
- if (StringHelper.EqualsIgnoreCase(container, "flac"))
+ if (string.Equals(container, "flac", StringComparison.OrdinalIgnoreCase))
return MediaFormatProfile.FLAC;
- if (StringHelper.EqualsIgnoreCase(container, "oga") ||
- StringHelper.EqualsIgnoreCase(container, "ogg"))
+ if (string.Equals(container, "oga", StringComparison.OrdinalIgnoreCase) ||
+ string.Equals(container, "ogg", StringComparison.OrdinalIgnoreCase))
return MediaFormatProfile.OGG;
return null;
@@ -388,17 +389,17 @@ namespace MediaBrowser.Model.Dlna
public MediaFormatProfile? ResolveImageFormat(string container, int? width, int? height)
{
- if (StringHelper.EqualsIgnoreCase(container, "jpeg") ||
- StringHelper.EqualsIgnoreCase(container, "jpg"))
+ if (string.Equals(container, "jpeg", StringComparison.OrdinalIgnoreCase) ||
+ string.Equals(container, "jpg", StringComparison.OrdinalIgnoreCase))
return ResolveImageJPGFormat(width, height);
- if (StringHelper.EqualsIgnoreCase(container, "png"))
+ if (string.Equals(container, "png", StringComparison.OrdinalIgnoreCase))
return ResolveImagePNGFormat(width, height);
- if (StringHelper.EqualsIgnoreCase(container, "gif"))
+ if (string.Equals(container, "gif", StringComparison.OrdinalIgnoreCase))
return MediaFormatProfile.GIF_LRG;
- if (StringHelper.EqualsIgnoreCase(container, "raw"))
+ if (string.Equals(container, "raw", StringComparison.OrdinalIgnoreCase))
return MediaFormatProfile.RAW;
return null;
diff --git a/MediaBrowser.Model/Dlna/PlaybackErrorCode.cs b/MediaBrowser.Model/Dlna/PlaybackErrorCode.cs
index 5080bc286..300fab5c5 100644
--- a/MediaBrowser.Model/Dlna/PlaybackErrorCode.cs
+++ b/MediaBrowser.Model/Dlna/PlaybackErrorCode.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
namespace MediaBrowser.Model.Dlna
{
public enum PlaybackErrorCode
diff --git a/MediaBrowser.Model/Dlna/ProfileCondition.cs b/MediaBrowser.Model/Dlna/ProfileCondition.cs
index b83566f6e..2021038d8 100644
--- a/MediaBrowser.Model/Dlna/ProfileCondition.cs
+++ b/MediaBrowser.Model/Dlna/ProfileCondition.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
using System.Xml.Serialization;
namespace MediaBrowser.Model.Dlna
diff --git a/MediaBrowser.Model/Dlna/ProfileConditionType.cs b/MediaBrowser.Model/Dlna/ProfileConditionType.cs
index 262841262..fbf133857 100644
--- a/MediaBrowser.Model/Dlna/ProfileConditionType.cs
+++ b/MediaBrowser.Model/Dlna/ProfileConditionType.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
namespace MediaBrowser.Model.Dlna
{
public enum ProfileConditionType
diff --git a/MediaBrowser.Model/Dlna/ProfileConditionValue.cs b/MediaBrowser.Model/Dlna/ProfileConditionValue.cs
index bae46bdcf..eb81fde75 100644
--- a/MediaBrowser.Model/Dlna/ProfileConditionValue.cs
+++ b/MediaBrowser.Model/Dlna/ProfileConditionValue.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
namespace MediaBrowser.Model.Dlna
{
public enum ProfileConditionValue
diff --git a/MediaBrowser.Model/Dlna/ResolutionConfiguration.cs b/MediaBrowser.Model/Dlna/ResolutionConfiguration.cs
index 6b1f85440..c26eeec77 100644
--- a/MediaBrowser.Model/Dlna/ResolutionConfiguration.cs
+++ b/MediaBrowser.Model/Dlna/ResolutionConfiguration.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
namespace MediaBrowser.Model.Dlna
{
public class ResolutionConfiguration
diff --git a/MediaBrowser.Model/Dlna/ResolutionNormalizer.cs b/MediaBrowser.Model/Dlna/ResolutionNormalizer.cs
index cf92633c3..8235b72d1 100644
--- a/MediaBrowser.Model/Dlna/ResolutionNormalizer.cs
+++ b/MediaBrowser.Model/Dlna/ResolutionNormalizer.cs
@@ -1,5 +1,6 @@
+#pragma warning disable CS1591
+
using System;
-using MediaBrowser.Model.Extensions;
namespace MediaBrowser.Model.Dlna
{
@@ -76,9 +77,9 @@ namespace MediaBrowser.Model.Dlna
private static double GetVideoBitrateScaleFactor(string codec)
{
- if (StringHelper.EqualsIgnoreCase(codec, "h265") ||
- StringHelper.EqualsIgnoreCase(codec, "hevc") ||
- StringHelper.EqualsIgnoreCase(codec, "vp9"))
+ if (string.Equals(codec, "h265", StringComparison.OrdinalIgnoreCase) ||
+ string.Equals(codec, "hevc", StringComparison.OrdinalIgnoreCase) ||
+ string.Equals(codec, "vp9", StringComparison.OrdinalIgnoreCase))
{
return .5;
}
diff --git a/MediaBrowser.Model/Dlna/ResolutionOptions.cs b/MediaBrowser.Model/Dlna/ResolutionOptions.cs
index 30c078b55..5ea0252cb 100644
--- a/MediaBrowser.Model/Dlna/ResolutionOptions.cs
+++ b/MediaBrowser.Model/Dlna/ResolutionOptions.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
namespace MediaBrowser.Model.Dlna
{
public class ResolutionOptions
diff --git a/MediaBrowser.Model/Dlna/ResponseProfile.cs b/MediaBrowser.Model/Dlna/ResponseProfile.cs
index 8c6b0806f..c264cb936 100644
--- a/MediaBrowser.Model/Dlna/ResponseProfile.cs
+++ b/MediaBrowser.Model/Dlna/ResponseProfile.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
using System.Xml.Serialization;
namespace MediaBrowser.Model.Dlna
diff --git a/MediaBrowser.Model/Dlna/SearchCriteria.cs b/MediaBrowser.Model/Dlna/SearchCriteria.cs
index 4f47c2821..394fb9af9 100644
--- a/MediaBrowser.Model/Dlna/SearchCriteria.cs
+++ b/MediaBrowser.Model/Dlna/SearchCriteria.cs
@@ -1,6 +1,7 @@
+#pragma warning disable CS1591
+
using System;
using System.Text.RegularExpressions;
-using MediaBrowser.Model.Extensions;
namespace MediaBrowser.Model.Dlna
{
@@ -48,22 +49,22 @@ namespace MediaBrowser.Model.Dlna
if (subFactors.Length == 3)
{
- if (StringHelper.EqualsIgnoreCase("upnp:class", subFactors[0]) &&
- (StringHelper.EqualsIgnoreCase("=", subFactors[1]) || StringHelper.EqualsIgnoreCase("derivedfrom", subFactors[1])))
+ if (string.Equals("upnp:class", subFactors[0], StringComparison.OrdinalIgnoreCase) &&
+ (string.Equals("=", subFactors[1]) || string.Equals("derivedfrom", subFactors[1], StringComparison.OrdinalIgnoreCase)))
{
- if (StringHelper.EqualsIgnoreCase("\"object.item.imageItem\"", subFactors[2]) || StringHelper.EqualsIgnoreCase("\"object.item.imageItem.photo\"", subFactors[2]))
+ if (string.Equals("\"object.item.imageItem\"", subFactors[2]) || string.Equals("\"object.item.imageItem.photo\"", subFactors[2], StringComparison.OrdinalIgnoreCase))
{
SearchType = SearchType.Image;
}
- else if (StringHelper.EqualsIgnoreCase("\"object.item.videoItem\"", subFactors[2]))
+ else if (string.Equals("\"object.item.videoItem\"", subFactors[2], StringComparison.OrdinalIgnoreCase))
{
SearchType = SearchType.Video;
}
- else if (StringHelper.EqualsIgnoreCase("\"object.container.playlistContainer\"", subFactors[2]))
+ else if (string.Equals("\"object.container.playlistContainer\"", subFactors[2], StringComparison.OrdinalIgnoreCase))
{
SearchType = SearchType.Playlist;
}
- else if (StringHelper.EqualsIgnoreCase("\"object.container.album.musicAlbum\"", subFactors[2]))
+ else if (string.Equals("\"object.container.album.musicAlbum\"", subFactors[2], StringComparison.OrdinalIgnoreCase))
{
SearchType = SearchType.MusicAlbum;
}
diff --git a/MediaBrowser.Model/Dlna/SearchType.cs b/MediaBrowser.Model/Dlna/SearchType.cs
index 05c59f5de..8bc7c5249 100644
--- a/MediaBrowser.Model/Dlna/SearchType.cs
+++ b/MediaBrowser.Model/Dlna/SearchType.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
namespace MediaBrowser.Model.Dlna
{
public enum SearchType
diff --git a/MediaBrowser.Model/Dlna/SortCriteria.cs b/MediaBrowser.Model/Dlna/SortCriteria.cs
index b5c1ac408..3f8985fdc 100644
--- a/MediaBrowser.Model/Dlna/SortCriteria.cs
+++ b/MediaBrowser.Model/Dlna/SortCriteria.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
using MediaBrowser.Model.Entities;
namespace MediaBrowser.Model.Dlna
diff --git a/MediaBrowser.Model/Dlna/StreamBuilder.cs b/MediaBrowser.Model/Dlna/StreamBuilder.cs
index 586e322e4..58755b171 100644
--- a/MediaBrowser.Model/Dlna/StreamBuilder.cs
+++ b/MediaBrowser.Model/Dlna/StreamBuilder.cs
@@ -1,10 +1,11 @@
+#pragma warning disable CS1591
+
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using MediaBrowser.Model.Dto;
using MediaBrowser.Model.Entities;
-using MediaBrowser.Model.Extensions;
using MediaBrowser.Model.MediaInfo;
using MediaBrowser.Model.Session;
using Microsoft.Extensions.Logging;
@@ -22,7 +23,7 @@ namespace MediaBrowser.Model.Dlna
_logger = logger;
}
- public StreamBuilder(ILogger logger)
+ public StreamBuilder(ILogger<StreamBuilder> logger)
: this(new FullTranscoderSupport(), logger)
{
}
@@ -35,7 +36,7 @@ namespace MediaBrowser.Model.Dlna
foreach (MediaSourceInfo i in options.MediaSources)
{
if (string.IsNullOrEmpty(options.MediaSourceId) ||
- StringHelper.EqualsIgnoreCase(i.Id, options.MediaSourceId))
+ string.Equals(i.Id, options.MediaSourceId, StringComparison.OrdinalIgnoreCase))
{
mediaSources.Add(i);
}
@@ -68,7 +69,7 @@ namespace MediaBrowser.Model.Dlna
foreach (MediaSourceInfo i in options.MediaSources)
{
if (string.IsNullOrEmpty(options.MediaSourceId) ||
- StringHelper.EqualsIgnoreCase(i.Id, options.MediaSourceId))
+ string.Equals(i.Id, options.MediaSourceId, StringComparison.OrdinalIgnoreCase))
{
mediaSources.Add(i);
}
@@ -582,7 +583,7 @@ namespace MediaBrowser.Model.Dlna
{
foreach (var profile in subtitleProfiles)
{
- if (profile.Method == SubtitleDeliveryMethod.External && StringHelper.EqualsIgnoreCase(profile.Format, stream.Codec))
+ if (profile.Method == SubtitleDeliveryMethod.External && string.Equals(profile.Format, stream.Codec, StringComparison.OrdinalIgnoreCase))
{
return stream.Index;
}
@@ -1198,7 +1199,7 @@ namespace MediaBrowser.Model.Dlna
continue;
}
- if (subtitleStream.IsTextSubtitleStream == MediaStream.IsTextFormat(profile.Format) && StringHelper.EqualsIgnoreCase(profile.Format, subtitleStream.Codec))
+ if (subtitleStream.IsTextSubtitleStream == MediaStream.IsTextFormat(profile.Format) && string.Equals(profile.Format, subtitleStream.Codec, StringComparison.OrdinalIgnoreCase))
{
return profile;
}
@@ -1292,7 +1293,7 @@ namespace MediaBrowser.Model.Dlna
if ((profile.Method == SubtitleDeliveryMethod.External && subtitleStream.IsTextSubtitleStream == MediaStream.IsTextFormat(profile.Format)) ||
(profile.Method == SubtitleDeliveryMethod.Hls && subtitleStream.IsTextSubtitleStream))
{
- bool requiresConversion = !StringHelper.EqualsIgnoreCase(subtitleStream.Codec, profile.Format);
+ bool requiresConversion = !string.Equals(subtitleStream.Codec, profile.Format, StringComparison.OrdinalIgnoreCase);
if (!requiresConversion)
{
diff --git a/MediaBrowser.Model/Dlna/StreamInfo.cs b/MediaBrowser.Model/Dlna/StreamInfo.cs
index 10efb9b38..c9fe679e1 100644
--- a/MediaBrowser.Model/Dlna/StreamInfo.cs
+++ b/MediaBrowser.Model/Dlna/StreamInfo.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
using System;
using System.Collections.Generic;
using System.Globalization;
@@ -5,7 +7,6 @@ using System.Linq;
using MediaBrowser.Model.Drawing;
using MediaBrowser.Model.Dto;
using MediaBrowser.Model.Entities;
-using MediaBrowser.Model.Extensions;
using MediaBrowser.Model.MediaInfo;
using MediaBrowser.Model.Session;
@@ -153,18 +154,18 @@ namespace MediaBrowser.Model.Dlna
}
// Try to keep the url clean by omitting defaults
- if (StringHelper.EqualsIgnoreCase(pair.Name, "StartTimeTicks") &&
- StringHelper.EqualsIgnoreCase(pair.Value, "0"))
+ if (string.Equals(pair.Name, "StartTimeTicks", StringComparison.OrdinalIgnoreCase) &&
+ string.Equals(pair.Value, "0", StringComparison.OrdinalIgnoreCase))
{
continue;
}
- if (StringHelper.EqualsIgnoreCase(pair.Name, "SubtitleStreamIndex") &&
- StringHelper.EqualsIgnoreCase(pair.Value, "-1"))
+ if (string.Equals(pair.Name, "SubtitleStreamIndex", StringComparison.OrdinalIgnoreCase) &&
+ string.Equals(pair.Value, "-1", StringComparison.OrdinalIgnoreCase))
{
continue;
}
- if (StringHelper.EqualsIgnoreCase(pair.Name, "Static") &&
- StringHelper.EqualsIgnoreCase(pair.Value, "false"))
+ if (string.Equals(pair.Name, "Static", StringComparison.OrdinalIgnoreCase) &&
+ string.Equals(pair.Value, "false", StringComparison.OrdinalIgnoreCase))
{
continue;
}
@@ -192,7 +193,7 @@ namespace MediaBrowser.Model.Dlna
if (MediaType == DlnaProfileType.Audio)
{
- if (StringHelper.EqualsIgnoreCase(SubProtocol, "hls"))
+ if (string.Equals(SubProtocol, "hls", StringComparison.OrdinalIgnoreCase))
{
return string.Format("{0}/audio/{1}/master.m3u8?{2}", baseUrl, ItemId, queryString);
}
@@ -200,7 +201,7 @@ namespace MediaBrowser.Model.Dlna
return string.Format("{0}/audio/{1}/stream{2}?{3}", baseUrl, ItemId, extension, queryString);
}
- if (StringHelper.EqualsIgnoreCase(SubProtocol, "hls"))
+ if (string.Equals(SubProtocol, "hls", StringComparison.OrdinalIgnoreCase))
{
return string.Format("{0}/videos/{1}/master.m3u8?{2}", baseUrl, ItemId, queryString);
}
@@ -237,7 +238,7 @@ namespace MediaBrowser.Model.Dlna
long startPositionTicks = item.StartPositionTicks;
- var isHls = StringHelper.EqualsIgnoreCase(item.SubProtocol, "hls");
+ var isHls = string.Equals(item.SubProtocol, "hls", StringComparison.OrdinalIgnoreCase);
if (isHls)
{
@@ -370,7 +371,7 @@ namespace MediaBrowser.Model.Dlna
var list = new List<SubtitleStreamInfo>();
// HLS will preserve timestamps so we can just grab the full subtitle stream
- long startPositionTicks = StringHelper.EqualsIgnoreCase(SubProtocol, "hls")
+ long startPositionTicks = string.Equals(SubProtocol, "hls", StringComparison.OrdinalIgnoreCase)
? 0
: (PlayMethod == PlayMethod.Transcode && !CopyTimestamps ? StartPositionTicks : 0);
@@ -435,7 +436,7 @@ namespace MediaBrowser.Model.Dlna
if (info.DeliveryMethod == SubtitleDeliveryMethod.External)
{
- if (MediaSource.Protocol == MediaProtocol.File || !StringHelper.EqualsIgnoreCase(stream.Codec, subtitleProfile.Format) || !stream.IsExternal)
+ if (MediaSource.Protocol == MediaProtocol.File || !string.Equals(stream.Codec, subtitleProfile.Format, StringComparison.OrdinalIgnoreCase) || !stream.IsExternal)
{
info.Url = string.Format("{0}/Videos/{1}/{2}/Subtitles/{3}/{4}/Stream.{5}",
baseUrl,
@@ -802,7 +803,7 @@ namespace MediaBrowser.Model.Dlna
foreach (string codec in AudioCodecs)
{
- if (StringHelper.EqualsIgnoreCase(codec, inputCodec))
+ if (string.Equals(codec, inputCodec, StringComparison.OrdinalIgnoreCase))
{
return string.IsNullOrEmpty(codec) ? new string[] { } : new[] { codec };
}
@@ -827,7 +828,7 @@ namespace MediaBrowser.Model.Dlna
foreach (string codec in VideoCodecs)
{
- if (StringHelper.EqualsIgnoreCase(codec, inputCodec))
+ if (string.Equals(codec, inputCodec, StringComparison.OrdinalIgnoreCase))
{
return string.IsNullOrEmpty(codec) ? new string[] { } : new[] { codec };
}
@@ -884,7 +885,7 @@ namespace MediaBrowser.Model.Dlna
{
get
{
- var defaultValue = StringHelper.EqualsIgnoreCase(Container, "m2ts")
+ var defaultValue = string.Equals(Container, "m2ts", StringComparison.OrdinalIgnoreCase)
? TransportStreamTimestamp.Valid
: TransportStreamTimestamp.None;
diff --git a/MediaBrowser.Model/Dlna/SubtitleDeliveryMethod.cs b/MediaBrowser.Model/Dlna/SubtitleDeliveryMethod.cs
index 925c1f9fc..7b0204590 100644
--- a/MediaBrowser.Model/Dlna/SubtitleDeliveryMethod.cs
+++ b/MediaBrowser.Model/Dlna/SubtitleDeliveryMethod.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
namespace MediaBrowser.Model.Dlna
{
public enum SubtitleDeliveryMethod
@@ -6,14 +8,17 @@ namespace MediaBrowser.Model.Dlna
/// The encode
/// </summary>
Encode = 0,
+
/// <summary>
/// The embed
/// </summary>
Embed = 1,
+
/// <summary>
/// The external
/// </summary>
External = 2,
+
/// <summary>
/// The HLS
/// </summary>
diff --git a/MediaBrowser.Model/Dlna/SubtitleProfile.cs b/MediaBrowser.Model/Dlna/SubtitleProfile.cs
index f950b6cb8..6a8f655ac 100644
--- a/MediaBrowser.Model/Dlna/SubtitleProfile.cs
+++ b/MediaBrowser.Model/Dlna/SubtitleProfile.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
using System.Xml.Serialization;
using MediaBrowser.Model.Extensions;
diff --git a/MediaBrowser.Model/Dlna/SubtitleStreamInfo.cs b/MediaBrowser.Model/Dlna/SubtitleStreamInfo.cs
index e81c26e69..02b3a198c 100644
--- a/MediaBrowser.Model/Dlna/SubtitleStreamInfo.cs
+++ b/MediaBrowser.Model/Dlna/SubtitleStreamInfo.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
namespace MediaBrowser.Model.Dlna
{
public class SubtitleStreamInfo
diff --git a/MediaBrowser.Model/Dlna/TranscodeSeekInfo.cs b/MediaBrowser.Model/Dlna/TranscodeSeekInfo.cs
index eac5d4b36..cc0c6069b 100644
--- a/MediaBrowser.Model/Dlna/TranscodeSeekInfo.cs
+++ b/MediaBrowser.Model/Dlna/TranscodeSeekInfo.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
namespace MediaBrowser.Model.Dlna
{
public enum TranscodeSeekInfo
diff --git a/MediaBrowser.Model/Dlna/TranscodingProfile.cs b/MediaBrowser.Model/Dlna/TranscodingProfile.cs
index dc2f0c90d..570ee7baa 100644
--- a/MediaBrowser.Model/Dlna/TranscodingProfile.cs
+++ b/MediaBrowser.Model/Dlna/TranscodingProfile.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
using System.Xml.Serialization;
namespace MediaBrowser.Model.Dlna
diff --git a/MediaBrowser.Model/Dlna/UpnpDeviceInfo.cs b/MediaBrowser.Model/Dlna/UpnpDeviceInfo.cs
index c443a8ad1..3dc1fca36 100644
--- a/MediaBrowser.Model/Dlna/UpnpDeviceInfo.cs
+++ b/MediaBrowser.Model/Dlna/UpnpDeviceInfo.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
using System;
using System.Collections.Generic;
using System.Net;
diff --git a/MediaBrowser.Model/Dlna/VideoOptions.cs b/MediaBrowser.Model/Dlna/VideoOptions.cs
index 9c4a38292..5b12fff1c 100644
--- a/MediaBrowser.Model/Dlna/VideoOptions.cs
+++ b/MediaBrowser.Model/Dlna/VideoOptions.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
namespace MediaBrowser.Model.Dlna
{
/// <summary>
diff --git a/MediaBrowser.Model/Dlna/XmlAttribute.cs b/MediaBrowser.Model/Dlna/XmlAttribute.cs
index aa64177a8..31603a754 100644
--- a/MediaBrowser.Model/Dlna/XmlAttribute.cs
+++ b/MediaBrowser.Model/Dlna/XmlAttribute.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
using System.Xml.Serialization;
namespace MediaBrowser.Model.Dlna
diff --git a/MediaBrowser.Model/Drawing/DrawingUtils.cs b/MediaBrowser.Model/Drawing/DrawingUtils.cs
index 9fe85512f..0be30b0ba 100644
--- a/MediaBrowser.Model/Drawing/DrawingUtils.cs
+++ b/MediaBrowser.Model/Drawing/DrawingUtils.cs
@@ -3,19 +3,19 @@ using System;
namespace MediaBrowser.Model.Drawing
{
/// <summary>
- /// Class DrawingUtils
+ /// Class DrawingUtils.
/// </summary>
public static class DrawingUtils
{
/// <summary>
- /// Resizes a set of dimensions
+ /// Resizes a set of dimensions.
/// </summary>
- /// <param name="size">The original size object</param>
- /// <param name="width">A new fixed width, if desired</param>
- /// <param name="height">A new fixed height, if desired</param>
- /// <param name="maxWidth">A max fixed width, if desired</param>
- /// <param name="maxHeight">A max fixed height, if desired</param>
- /// <returns>A new size object</returns>
+ /// <param name="size">The original size object.</param>
+ /// <param name="width">A new fixed width, if desired.</param>
+ /// <param name="height">A new fixed height, if desired.</param>
+ /// <param name="maxWidth">A max fixed width, if desired.</param>
+ /// <param name="maxHeight">A max fixed height, if desired.</param>
+ /// <returns>A new size object.</returns>
public static ImageDimensions Resize(ImageDimensions size,
int width,
int height,
diff --git a/MediaBrowser.Model/Drawing/ImageDimensions.cs b/MediaBrowser.Model/Drawing/ImageDimensions.cs
index e7805ac49..f84fe6830 100644
--- a/MediaBrowser.Model/Drawing/ImageDimensions.cs
+++ b/MediaBrowser.Model/Drawing/ImageDimensions.cs
@@ -1,36 +1,45 @@
+#pragma warning disable CS1591
+
+using System.Globalization;
+
namespace MediaBrowser.Model.Drawing
{
/// <summary>
- /// Struct ImageDimensions
+ /// Struct ImageDimensions.
/// </summary>
- public struct ImageDimensions
+ public readonly struct ImageDimensions
{
+ public ImageDimensions(int width, int height)
+ {
+ Width = width;
+ Height = height;
+ }
+
/// <summary>
- /// Gets or sets the height.
+ /// Gets the height.
/// </summary>
/// <value>The height.</value>
- public int Height { get; set; }
+ public int Height { get; }
/// <summary>
- /// Gets or sets the width.
+ /// Gets the width.
/// </summary>
/// <value>The width.</value>
- public int Width { get; set; }
+ public int Width { get; }
public bool Equals(ImageDimensions size)
{
return Width.Equals(size.Width) && Height.Equals(size.Height);
}
+ /// <inheritdoc />
public override string ToString()
{
- return string.Format("{0}-{1}", Width, Height);
- }
-
- public ImageDimensions(int width, int height)
- {
- Width = width;
- Height = height;
+ return string.Format(
+ CultureInfo.InvariantCulture,
+ "{0}-{1}",
+ Width,
+ Height);
}
}
}
diff --git a/MediaBrowser.Model/Drawing/ImageFormat.cs b/MediaBrowser.Model/Drawing/ImageFormat.cs
index 3639c1594..511c16a4e 100644
--- a/MediaBrowser.Model/Drawing/ImageFormat.cs
+++ b/MediaBrowser.Model/Drawing/ImageFormat.cs
@@ -1,28 +1,32 @@
namespace MediaBrowser.Model.Drawing
{
/// <summary>
- /// Enum ImageOutputFormat
+ /// Enum ImageOutputFormat.
/// </summary>
public enum ImageFormat
{
/// <summary>
- /// The BMP
+ /// The BMP.
/// </summary>
Bmp,
+
/// <summary>
- /// The GIF
+ /// The GIF.
/// </summary>
Gif,
+
/// <summary>
- /// The JPG
+ /// The JPG.
/// </summary>
Jpg,
+
/// <summary>
- /// The PNG
+ /// The PNG.
/// </summary>
Png,
+
/// <summary>
- /// The webp
+ /// The webp.
/// </summary>
Webp
}
diff --git a/MediaBrowser.Model/Drawing/ImageOrientation.cs b/MediaBrowser.Model/Drawing/ImageOrientation.cs
index 0fce8c3dc..5c78aea12 100644
--- a/MediaBrowser.Model/Drawing/ImageOrientation.cs
+++ b/MediaBrowser.Model/Drawing/ImageOrientation.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
namespace MediaBrowser.Model.Drawing
{
public enum ImageOrientation
diff --git a/MediaBrowser.Model/Dto/BaseItemDto.cs b/MediaBrowser.Model/Dto/BaseItemDto.cs
index 4da5508b4..607355d8d 100644
--- a/MediaBrowser.Model/Dto/BaseItemDto.cs
+++ b/MediaBrowser.Model/Dto/BaseItemDto.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
using System;
using System.Collections.Generic;
using MediaBrowser.Model.Drawing;
diff --git a/MediaBrowser.Model/Dto/BaseItemPerson.cs b/MediaBrowser.Model/Dto/BaseItemPerson.cs
index 270a4683a..5b7eefd70 100644
--- a/MediaBrowser.Model/Dto/BaseItemPerson.cs
+++ b/MediaBrowser.Model/Dto/BaseItemPerson.cs
@@ -3,7 +3,7 @@ using System.Text.Json.Serialization;
namespace MediaBrowser.Model.Dto
{
/// <summary>
- /// This is used by the api to get information about a Person within a BaseItem
+ /// This is used by the api to get information about a Person within a BaseItem.
/// </summary>
public class BaseItemPerson
{
diff --git a/MediaBrowser.Model/Dto/IHasServerId.cs b/MediaBrowser.Model/Dto/IHasServerId.cs
index 2cce5df62..8c9798c5c 100644
--- a/MediaBrowser.Model/Dto/IHasServerId.cs
+++ b/MediaBrowser.Model/Dto/IHasServerId.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
namespace MediaBrowser.Model.Dto
{
public interface IHasServerId
diff --git a/MediaBrowser.Model/Dto/IItemDto.cs b/MediaBrowser.Model/Dto/IItemDto.cs
index 0130adb6f..cb8808fb6 100644
--- a/MediaBrowser.Model/Dto/IItemDto.cs
+++ b/MediaBrowser.Model/Dto/IItemDto.cs
@@ -1,7 +1,7 @@
namespace MediaBrowser.Model.Dto
{
/// <summary>
- /// Interface IItemDto
+ /// Interface IItemDto.
/// </summary>
public interface IItemDto
{
diff --git a/MediaBrowser.Model/Dto/ImageByNameInfo.cs b/MediaBrowser.Model/Dto/ImageByNameInfo.cs
index 2bda8bf20..d2e43634d 100644
--- a/MediaBrowser.Model/Dto/ImageByNameInfo.cs
+++ b/MediaBrowser.Model/Dto/ImageByNameInfo.cs
@@ -1,3 +1,4 @@
+#pragma warning disable CS1591
namespace MediaBrowser.Model.Dto
{
@@ -8,21 +9,25 @@ namespace MediaBrowser.Model.Dto
/// </summary>
/// <value>The name.</value>
public string Name { get; set; }
+
/// <summary>
/// Gets or sets the theme.
/// </summary>
/// <value>The theme.</value>
public string Theme { get; set; }
+
/// <summary>
/// Gets or sets the context.
/// </summary>
/// <value>The context.</value>
public string Context { get; set; }
+
/// <summary>
/// Gets or sets the length of the file.
/// </summary>
/// <value>The length of the file.</value>
public long FileLength { get; set; }
+
/// <summary>
/// Gets or sets the format.
/// </summary>
diff --git a/MediaBrowser.Model/Dto/ImageInfo.cs b/MediaBrowser.Model/Dto/ImageInfo.cs
index 792eaff03..57942ac23 100644
--- a/MediaBrowser.Model/Dto/ImageInfo.cs
+++ b/MediaBrowser.Model/Dto/ImageInfo.cs
@@ -3,7 +3,7 @@ using MediaBrowser.Model.Entities;
namespace MediaBrowser.Model.Dto
{
/// <summary>
- /// Class ImageInfo
+ /// Class ImageInfo.
/// </summary>
public class ImageInfo
{
diff --git a/MediaBrowser.Model/Dto/ImageOptions.cs b/MediaBrowser.Model/Dto/ImageOptions.cs
index 1fd4a5383..4e672a007 100644
--- a/MediaBrowser.Model/Dto/ImageOptions.cs
+++ b/MediaBrowser.Model/Dto/ImageOptions.cs
@@ -4,7 +4,7 @@ using MediaBrowser.Model.Entities;
namespace MediaBrowser.Model.Dto
{
/// <summary>
- /// Class ImageOptions
+ /// Class ImageOptions.
/// </summary>
public class ImageOptions
{
diff --git a/MediaBrowser.Model/Dto/ItemCounts.cs b/MediaBrowser.Model/Dto/ItemCounts.cs
index ec5adab85..95f4a3d77 100644
--- a/MediaBrowser.Model/Dto/ItemCounts.cs
+++ b/MediaBrowser.Model/Dto/ItemCounts.cs
@@ -1,7 +1,7 @@
namespace MediaBrowser.Model.Dto
{
/// <summary>
- /// Class LibrarySummary
+ /// Class LibrarySummary.
/// </summary>
public class ItemCounts
{
@@ -10,48 +10,71 @@ namespace MediaBrowser.Model.Dto
/// </summary>
/// <value>The movie count.</value>
public int MovieCount { get; set; }
+
/// <summary>
/// Gets or sets the series count.
/// </summary>
/// <value>The series count.</value>
public int SeriesCount { get; set; }
+
/// <summary>
/// Gets or sets the episode count.
/// </summary>
/// <value>The episode count.</value>
public int EpisodeCount { get; set; }
+
+ /// <summary>
+ /// Gets or sets the artist count.
+ /// </summary>
+ /// <value>The artist count.</value>
public int ArtistCount { get; set; }
+
+ /// <summary>
+ /// Gets or sets the program count.
+ /// </summary>
+ /// <value>The program count.</value>
public int ProgramCount { get; set; }
+
/// <summary>
/// Gets or sets the trailer count.
/// </summary>
/// <value>The trailer count.</value>
public int TrailerCount { get; set; }
+
/// <summary>
/// Gets or sets the song count.
/// </summary>
/// <value>The song count.</value>
public int SongCount { get; set; }
+
/// <summary>
/// Gets or sets the album count.
/// </summary>
/// <value>The album count.</value>
public int AlbumCount { get; set; }
+
/// <summary>
/// Gets or sets the music video count.
/// </summary>
/// <value>The music video count.</value>
public int MusicVideoCount { get; set; }
+
/// <summary>
/// Gets or sets the box set count.
/// </summary>
/// <value>The box set count.</value>
public int BoxSetCount { get; set; }
+
/// <summary>
/// Gets or sets the book count.
/// </summary>
/// <value>The book count.</value>
public int BookCount { get; set; }
+
+ /// <summary>
+ /// Gets or sets the item count.
+ /// </summary>
+ /// <value>The item count.</value>
public int ItemCount { get; set; }
}
}
diff --git a/MediaBrowser.Model/Dto/ItemIndex.cs b/MediaBrowser.Model/Dto/ItemIndex.cs
index 21e14c73e..525576d61 100644
--- a/MediaBrowser.Model/Dto/ItemIndex.cs
+++ b/MediaBrowser.Model/Dto/ItemIndex.cs
@@ -1,7 +1,7 @@
namespace MediaBrowser.Model.Dto
{
/// <summary>
- /// Class ItemIndex
+ /// Class ItemIndex.
/// </summary>
public class ItemIndex
{
diff --git a/MediaBrowser.Model/Dto/MediaSourceInfo.cs b/MediaBrowser.Model/Dto/MediaSourceInfo.cs
index 5cb056566..29613adbf 100644
--- a/MediaBrowser.Model/Dto/MediaSourceInfo.cs
+++ b/MediaBrowser.Model/Dto/MediaSourceInfo.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
using System;
using System.Collections.Generic;
using System.Text.Json.Serialization;
@@ -76,6 +78,7 @@ namespace MediaBrowser.Model.Dto
{
Formats = Array.Empty<string>();
MediaStreams = new List<MediaStream>();
+ MediaAttachments = Array.Empty<MediaAttachment>();
RequiredHttpHeaders = new Dictionary<string, string>();
SupportsTranscoding = true;
SupportsDirectStream = true;
@@ -209,10 +212,7 @@ namespace MediaBrowser.Model.Dto
{
if (currentStream.Type == MediaStreamType.Audio && currentStream.IsDefault)
{
- if (currentStream.Index != stream.Index)
- {
- return true;
- }
+ return currentStream.Index != stream.Index;
}
}
diff --git a/MediaBrowser.Model/Dto/MediaSourceType.cs b/MediaBrowser.Model/Dto/MediaSourceType.cs
index b643cad9a..42314d519 100644
--- a/MediaBrowser.Model/Dto/MediaSourceType.cs
+++ b/MediaBrowser.Model/Dto/MediaSourceType.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
namespace MediaBrowser.Model.Dto
{
public enum MediaSourceType
diff --git a/MediaBrowser.Model/Dto/MetadataEditorInfo.cs b/MediaBrowser.Model/Dto/MetadataEditorInfo.cs
index 46bcb62f4..21d8a31f2 100644
--- a/MediaBrowser.Model/Dto/MetadataEditorInfo.cs
+++ b/MediaBrowser.Model/Dto/MetadataEditorInfo.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
using System;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Globalization;
diff --git a/MediaBrowser.Model/Dto/NameIdPair.cs b/MediaBrowser.Model/Dto/NameIdPair.cs
index ccd42f17f..1b4800863 100644
--- a/MediaBrowser.Model/Dto/NameIdPair.cs
+++ b/MediaBrowser.Model/Dto/NameIdPair.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
using System;
namespace MediaBrowser.Model.Dto
@@ -9,6 +11,7 @@ namespace MediaBrowser.Model.Dto
/// </summary>
/// <value>The name.</value>
public string Name { get; set; }
+
/// <summary>
/// Gets or sets the identifier.
/// </summary>
diff --git a/MediaBrowser.Model/Dto/NameValuePair.cs b/MediaBrowser.Model/Dto/NameValuePair.cs
index 624763211..74040c2cb 100644
--- a/MediaBrowser.Model/Dto/NameValuePair.cs
+++ b/MediaBrowser.Model/Dto/NameValuePair.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
namespace MediaBrowser.Model.Dto
{
public class NameValuePair
@@ -18,6 +20,7 @@ namespace MediaBrowser.Model.Dto
/// </summary>
/// <value>The name.</value>
public string Name { get; set; }
+
/// <summary>
/// Gets or sets the value.
/// </summary>
diff --git a/MediaBrowser.Model/Dto/RatingType.cs b/MediaBrowser.Model/Dto/RatingType.cs
index fc1f7ea0f..033776f9c 100644
--- a/MediaBrowser.Model/Dto/RatingType.cs
+++ b/MediaBrowser.Model/Dto/RatingType.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
namespace MediaBrowser.Model.Dto
{
public enum RatingType
diff --git a/MediaBrowser.Model/Dto/RecommendationDto.cs b/MediaBrowser.Model/Dto/RecommendationDto.cs
index acfb85e9b..bc97dd6f1 100644
--- a/MediaBrowser.Model/Dto/RecommendationDto.cs
+++ b/MediaBrowser.Model/Dto/RecommendationDto.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
using System;
using System.Collections.Generic;
diff --git a/MediaBrowser.Model/Dto/RecommendationType.cs b/MediaBrowser.Model/Dto/RecommendationType.cs
index 55a0a8091..384f20c32 100644
--- a/MediaBrowser.Model/Dto/RecommendationType.cs
+++ b/MediaBrowser.Model/Dto/RecommendationType.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
namespace MediaBrowser.Model.Dto
{
public enum RecommendationType
diff --git a/MediaBrowser.Model/Dto/UserDto.cs b/MediaBrowser.Model/Dto/UserDto.cs
index 13da018a6..d36706c38 100644
--- a/MediaBrowser.Model/Dto/UserDto.cs
+++ b/MediaBrowser.Model/Dto/UserDto.cs
@@ -5,7 +5,7 @@ using MediaBrowser.Model.Users;
namespace MediaBrowser.Model.Dto
{
/// <summary>
- /// Class UserDto
+ /// Class UserDto.
/// </summary>
public class UserDto : IItemDto, IHasServerId
{
@@ -58,6 +58,9 @@ namespace MediaBrowser.Model.Dto
/// <value><c>true</c> if this instance has configured easy password; otherwise, <c>false</c>.</value>
public bool HasConfiguredEasyPassword { get; set; }
+ /// <summary>
+ /// Gets or sets whether async login is enabled or not.
+ /// </summary>
public bool? EnableAutoLogin { get; set; }
/// <summary>
@@ -99,6 +102,7 @@ namespace MediaBrowser.Model.Dto
Policy = new UserPolicy();
}
+ /// <inheritdoc />
public override string ToString()
{
return Name ?? base.ToString();
diff --git a/MediaBrowser.Model/Dto/UserItemDataDto.cs b/MediaBrowser.Model/Dto/UserItemDataDto.cs
index fa512e94c..92f06c973 100644
--- a/MediaBrowser.Model/Dto/UserItemDataDto.cs
+++ b/MediaBrowser.Model/Dto/UserItemDataDto.cs
@@ -3,7 +3,7 @@ using System;
namespace MediaBrowser.Model.Dto
{
/// <summary>
- /// Class UserItemDataDto
+ /// Class UserItemDataDto.
/// </summary>
public class UserItemDataDto
{
diff --git a/MediaBrowser.Model/Entities/ChapterInfo.cs b/MediaBrowser.Model/Entities/ChapterInfo.cs
index dfd6fdf4a..bea7ec1db 100644
--- a/MediaBrowser.Model/Entities/ChapterInfo.cs
+++ b/MediaBrowser.Model/Entities/ChapterInfo.cs
@@ -1,9 +1,11 @@
+#pragma warning disable CS1591
+
using System;
namespace MediaBrowser.Model.Entities
{
/// <summary>
- /// Class ChapterInfo
+ /// Class ChapterInfo.
/// </summary>
public class ChapterInfo
{
@@ -24,6 +26,7 @@ namespace MediaBrowser.Model.Entities
/// </summary>
/// <value>The image path.</value>
public string ImagePath { get; set; }
+
public DateTime ImageDateModified { get; set; }
public string ImageTag { get; set; }
diff --git a/MediaBrowser.Model/Entities/CollectionType.cs b/MediaBrowser.Model/Entities/CollectionType.cs
index e26d1b8c3..354038712 100644
--- a/MediaBrowser.Model/Entities/CollectionType.cs
+++ b/MediaBrowser.Model/Entities/CollectionType.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
namespace MediaBrowser.Model.Entities
{
public static class CollectionType
diff --git a/MediaBrowser.Model/Entities/DisplayPreferences.cs b/MediaBrowser.Model/Entities/DisplayPreferences.cs
index f9b3ac7b3..2cd8bd306 100644
--- a/MediaBrowser.Model/Entities/DisplayPreferences.cs
+++ b/MediaBrowser.Model/Entities/DisplayPreferences.cs
@@ -3,12 +3,12 @@ using System.Collections.Generic;
namespace MediaBrowser.Model.Entities
{
/// <summary>
- /// Defines the display preferences for any item that supports them (usually Folders)
+ /// Defines the display preferences for any item that supports them (usually Folders).
/// </summary>
public class DisplayPreferences
{
/// <summary>
- /// The image scale
+ /// The image scale.
/// </summary>
private const double ImageScale = .9;
@@ -29,66 +29,79 @@ namespace MediaBrowser.Model.Entities
/// </summary>
/// <value>The user id.</value>
public string Id { get; set; }
+
/// <summary>
/// Gets or sets the type of the view.
/// </summary>
/// <value>The type of the view.</value>
public string ViewType { get; set; }
+
/// <summary>
/// Gets or sets the sort by.
/// </summary>
/// <value>The sort by.</value>
public string SortBy { get; set; }
+
/// <summary>
/// Gets or sets the index by.
/// </summary>
/// <value>The index by.</value>
public string IndexBy { get; set; }
+
/// <summary>
/// Gets or sets a value indicating whether [remember indexing].
/// </summary>
/// <value><c>true</c> if [remember indexing]; otherwise, <c>false</c>.</value>
public bool RememberIndexing { get; set; }
+
/// <summary>
/// Gets or sets the height of the primary image.
/// </summary>
/// <value>The height of the primary image.</value>
public int PrimaryImageHeight { get; set; }
+
/// <summary>
/// Gets or sets the width of the primary image.
/// </summary>
/// <value>The width of the primary image.</value>
public int PrimaryImageWidth { get; set; }
+
/// <summary>
/// Gets or sets the custom prefs.
/// </summary>
/// <value>The custom prefs.</value>
public Dictionary<string, string> CustomPrefs { get; set; }
+
/// <summary>
/// Gets or sets the scroll direction.
/// </summary>
/// <value>The scroll direction.</value>
public ScrollDirection ScrollDirection { get; set; }
+
/// <summary>
/// Gets or sets a value indicating whether to show backdrops on this item.
/// </summary>
/// <value><c>true</c> if showing backdrops; otherwise, <c>false</c>.</value>
public bool ShowBackdrop { get; set; }
+
/// <summary>
/// Gets or sets a value indicating whether [remember sorting].
/// </summary>
/// <value><c>true</c> if [remember sorting]; otherwise, <c>false</c>.</value>
public bool RememberSorting { get; set; }
+
/// <summary>
/// Gets or sets the sort order.
/// </summary>
/// <value>The sort order.</value>
public SortOrder SortOrder { get; set; }
+
/// <summary>
/// Gets or sets a value indicating whether [show sidebar].
/// </summary>
/// <value><c>true</c> if [show sidebar]; otherwise, <c>false</c>.</value>
public bool ShowSidebar { get; set; }
+
/// <summary>
/// Gets or sets the client
/// </summary>
diff --git a/MediaBrowser.Model/Entities/EmptyRequestResult.cs b/MediaBrowser.Model/Entities/EmptyRequestResult.cs
deleted file mode 100644
index 5d29218e3..000000000
--- a/MediaBrowser.Model/Entities/EmptyRequestResult.cs
+++ /dev/null
@@ -1,6 +0,0 @@
-namespace MediaBrowser.Model.Entities
-{
- public class EmptyRequestResult
- {
- }
-}
diff --git a/MediaBrowser.Model/Entities/ExtraType.cs b/MediaBrowser.Model/Entities/ExtraType.cs
index 97350b955..aca4bd282 100644
--- a/MediaBrowser.Model/Entities/ExtraType.cs
+++ b/MediaBrowser.Model/Entities/ExtraType.cs
@@ -1,7 +1,10 @@
+#pragma warning disable CS1591
+
namespace MediaBrowser.Model.Entities
{
public enum ExtraType
{
+ Unknown = 0,
Clip = 1,
Trailer = 2,
BehindTheScenes = 3,
diff --git a/MediaBrowser.Model/Entities/IHasProviderIds.cs b/MediaBrowser.Model/Entities/IHasProviderIds.cs
index 3b8d74cb4..c117efde9 100644
--- a/MediaBrowser.Model/Entities/IHasProviderIds.cs
+++ b/MediaBrowser.Model/Entities/IHasProviderIds.cs
@@ -3,7 +3,7 @@ using System.Collections.Generic;
namespace MediaBrowser.Model.Entities
{
/// <summary>
- /// Since BaseItem and DTOBaseItem both have ProviderIds, this interface helps avoid code repition by using extension methods
+ /// Since BaseItem and DTOBaseItem both have ProviderIds, this interface helps avoid code repition by using extension methods.
/// </summary>
public interface IHasProviderIds
{
diff --git a/MediaBrowser.Model/Entities/ImageType.cs b/MediaBrowser.Model/Entities/ImageType.cs
index ce3560e78..d89a4b3ad 100644
--- a/MediaBrowser.Model/Entities/ImageType.cs
+++ b/MediaBrowser.Model/Entities/ImageType.cs
@@ -1,56 +1,67 @@
namespace MediaBrowser.Model.Entities
{
/// <summary>
- /// Enum ImageType
+ /// Enum ImageType.
/// </summary>
public enum ImageType
{
/// <summary>
- /// The primary
+ /// The primary.
/// </summary>
Primary = 0,
+
/// <summary>
- /// The art
+ /// The art.
/// </summary>
Art = 1,
+
/// <summary>
- /// The backdrop
+ /// The backdrop.
/// </summary>
Backdrop = 2,
+
/// <summary>
- /// The banner
+ /// The banner.
/// </summary>
Banner = 3,
+
/// <summary>
- /// The logo
+ /// The logo.
/// </summary>
Logo = 4,
+
/// <summary>
- /// The thumb
+ /// The thumb.
/// </summary>
Thumb = 5,
+
/// <summary>
- /// The disc
+ /// The disc.
/// </summary>
Disc = 6,
+
/// <summary>
- /// The box
+ /// The box.
/// </summary>
Box = 7,
+
/// <summary>
- /// The screenshot
+ /// The screenshot.
/// </summary>
Screenshot = 8,
+
/// <summary>
- /// The menu
+ /// The menu.
/// </summary>
Menu = 9,
+
/// <summary>
- /// The chapter image
+ /// The chapter image.
/// </summary>
Chapter = 10,
+
/// <summary>
- /// The box rear
+ /// The box rear.
/// </summary>
BoxRear = 11
}
diff --git a/MediaBrowser.Model/Entities/IsoType.cs b/MediaBrowser.Model/Entities/IsoType.cs
index 8e4f0d63a..1aa29f31d 100644
--- a/MediaBrowser.Model/Entities/IsoType.cs
+++ b/MediaBrowser.Model/Entities/IsoType.cs
@@ -1,16 +1,17 @@
namespace MediaBrowser.Model.Entities
{
/// <summary>
- /// Enum IsoType
+ /// Enum IsoType.
/// </summary>
public enum IsoType
{
/// <summary>
- /// The DVD
+ /// The DVD.
/// </summary>
Dvd,
+
/// <summary>
- /// The blu ray
+ /// The blu ray.
/// </summary>
BluRay
}
diff --git a/MediaBrowser.Model/Entities/LibraryUpdateInfo.cs b/MediaBrowser.Model/Entities/LibraryUpdateInfo.cs
index b83df87e2..b98c00240 100644
--- a/MediaBrowser.Model/Entities/LibraryUpdateInfo.cs
+++ b/MediaBrowser.Model/Entities/LibraryUpdateInfo.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
using System;
namespace MediaBrowser.Model.Entities
diff --git a/MediaBrowser.Model/Entities/LocationType.cs b/MediaBrowser.Model/Entities/LocationType.cs
index 52c08b28b..879b31c5c 100644
--- a/MediaBrowser.Model/Entities/LocationType.cs
+++ b/MediaBrowser.Model/Entities/LocationType.cs
@@ -1,24 +1,27 @@
namespace MediaBrowser.Model.Entities
{
/// <summary>
- /// Enum LocationType
+ /// Enum LocationType.
/// </summary>
public enum LocationType
{
/// <summary>
- /// The file system
+ /// The file system.
/// </summary>
FileSystem = 0,
+
/// <summary>
- /// The remote
+ /// The remote.
/// </summary>
Remote = 1,
+
/// <summary>
- /// The virtual
+ /// The virtual.
/// </summary>
Virtual = 2,
+
/// <summary>
- /// The offline
+ /// The offline.
/// </summary>
Offline = 3
}
diff --git a/MediaBrowser.Model/Entities/MediaAttachment.cs b/MediaBrowser.Model/Entities/MediaAttachment.cs
index 8f8c3efd2..167be18c9 100644
--- a/MediaBrowser.Model/Entities/MediaAttachment.cs
+++ b/MediaBrowser.Model/Entities/MediaAttachment.cs
@@ -1,7 +1,7 @@
namespace MediaBrowser.Model.Entities
{
/// <summary>
- /// Class MediaAttachment
+ /// Class MediaAttachment.
/// </summary>
public class MediaAttachment
{
diff --git a/MediaBrowser.Model/Entities/MediaStream.cs b/MediaBrowser.Model/Entities/MediaStream.cs
index 5652962da..e7e8d7cec 100644
--- a/MediaBrowser.Model/Entities/MediaStream.cs
+++ b/MediaBrowser.Model/Entities/MediaStream.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
using System;
using System.Collections.Generic;
using System.Globalization;
@@ -67,9 +69,9 @@ namespace MediaBrowser.Model.Entities
}
}
- public string localizedUndefined { get; set; }
- public string localizedDefault { get; set; }
- public string localizedForced { get; set; }
+ public string localizedUndefined { get; set; }
+ public string localizedDefault { get; set; }
+ public string localizedForced { get; set; }
public string DisplayTitle
{
@@ -88,11 +90,11 @@ namespace MediaBrowser.Model.Entities
{
attributes.Add(StringHelper.FirstToUpper(Language));
}
- if (!string.IsNullOrEmpty(Codec) && !StringHelper.EqualsIgnoreCase(Codec, "dca"))
+ if (!string.IsNullOrEmpty(Codec) && !string.Equals(Codec, "dca", StringComparison.OrdinalIgnoreCase))
{
attributes.Add(AudioCodec.GetFriendlyName(Codec));
}
- else if (!string.IsNullOrEmpty(Profile) && !StringHelper.EqualsIgnoreCase(Profile, "lc"))
+ else if (!string.IsNullOrEmpty(Profile) && !string.Equals(Profile, "lc", StringComparison.OrdinalIgnoreCase))
{
attributes.Add(Profile);
}
@@ -394,8 +396,8 @@ namespace MediaBrowser.Model.Entities
return codec.IndexOf("pgs", StringComparison.OrdinalIgnoreCase) == -1 &&
codec.IndexOf("dvd", StringComparison.OrdinalIgnoreCase) == -1 &&
codec.IndexOf("dvbsub", StringComparison.OrdinalIgnoreCase) == -1 &&
- !StringHelper.EqualsIgnoreCase(codec, "sub") &&
- !StringHelper.EqualsIgnoreCase(codec, "dvb_subtitle");
+ !string.Equals(codec, "sub", StringComparison.OrdinalIgnoreCase) &&
+ !string.Equals(codec, "dvb_subtitle", StringComparison.OrdinalIgnoreCase);
}
public bool SupportsSubtitleConversionTo(string toCodec)
@@ -408,21 +410,21 @@ namespace MediaBrowser.Model.Entities
var fromCodec = Codec;
// Can't convert from this
- if (StringHelper.EqualsIgnoreCase(fromCodec, "ass"))
+ if (string.Equals(fromCodec, "ass", StringComparison.OrdinalIgnoreCase))
{
return false;
}
- if (StringHelper.EqualsIgnoreCase(fromCodec, "ssa"))
+ if (string.Equals(fromCodec, "ssa", StringComparison.OrdinalIgnoreCase))
{
return false;
}
// Can't convert to this
- if (StringHelper.EqualsIgnoreCase(toCodec, "ass"))
+ if (string.Equals(toCodec, "ass", StringComparison.OrdinalIgnoreCase))
{
return false;
}
- if (StringHelper.EqualsIgnoreCase(toCodec, "ssa"))
+ if (string.Equals(toCodec, "ssa", StringComparison.OrdinalIgnoreCase))
{
return false;
}
diff --git a/MediaBrowser.Model/Entities/MediaStreamType.cs b/MediaBrowser.Model/Entities/MediaStreamType.cs
index 4fc1e5372..e09aaf6d0 100644
--- a/MediaBrowser.Model/Entities/MediaStreamType.cs
+++ b/MediaBrowser.Model/Entities/MediaStreamType.cs
@@ -1,24 +1,27 @@
namespace MediaBrowser.Model.Entities
{
/// <summary>
- /// Enum MediaStreamType
+ /// Enum MediaStreamType.
/// </summary>
public enum MediaStreamType
{
/// <summary>
- /// The audio
+ /// The audio.
/// </summary>
Audio,
+
/// <summary>
- /// The video
+ /// The video.
/// </summary>
Video,
+
/// <summary>
- /// The subtitle
+ /// The subtitle.
/// </summary>
Subtitle,
+
/// <summary>
- /// The embedded image
+ /// The embedded image.
/// </summary>
EmbeddedImage
}
diff --git a/MediaBrowser.Model/Entities/MediaType.cs b/MediaBrowser.Model/Entities/MediaType.cs
index d8b02c9ea..dd2ae810b 100644
--- a/MediaBrowser.Model/Entities/MediaType.cs
+++ b/MediaBrowser.Model/Entities/MediaType.cs
@@ -1,27 +1,27 @@
namespace MediaBrowser.Model.Entities
{
/// <summary>
- /// Class MediaType
+ /// Class MediaType.
/// </summary>
public static class MediaType
{
/// <summary>
- /// The video
+ /// The video.
/// </summary>
public const string Video = "Video";
/// <summary>
- /// The audio
+ /// The audio.
/// </summary>
public const string Audio = "Audio";
/// <summary>
- /// The photo
+ /// The photo.
/// </summary>
public const string Photo = "Photo";
/// <summary>
- /// The book
+ /// The book.
/// </summary>
public const string Book = "Book";
}
diff --git a/MediaBrowser.Model/Entities/MediaUrl.cs b/MediaBrowser.Model/Entities/MediaUrl.cs
index 428c895b6..e44143755 100644
--- a/MediaBrowser.Model/Entities/MediaUrl.cs
+++ b/MediaBrowser.Model/Entities/MediaUrl.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
namespace MediaBrowser.Model.Entities
{
public class MediaUrl
diff --git a/MediaBrowser.Model/Entities/MetadataFields.cs b/MediaBrowser.Model/Entities/MetadataFields.cs
index a7947a933..d64d4f4da 100644
--- a/MediaBrowser.Model/Entities/MetadataFields.cs
+++ b/MediaBrowser.Model/Entities/MetadataFields.cs
@@ -1,44 +1,52 @@
namespace MediaBrowser.Model.Entities
{
/// <summary>
- /// Enum MetadataFields
+ /// Enum MetadataFields.
/// </summary>
public enum MetadataFields
{
/// <summary>
- /// The cast
+ /// The cast.
/// </summary>
Cast,
+
/// <summary>
- /// The genres
+ /// The genres.
/// </summary>
Genres,
+
/// <summary>
- /// The production locations
+ /// The production locations.
/// </summary>
ProductionLocations,
+
/// <summary>
- /// The studios
+ /// The studios.
/// </summary>
Studios,
+
/// <summary>
- /// The tags
+ /// The tags.
/// </summary>
Tags,
+
/// <summary>
- /// The name
+ /// The name.
/// </summary>
Name,
+
/// <summary>
- /// The overview
+ /// The overview.
/// </summary>
Overview,
+
/// <summary>
- /// The runtime
+ /// The runtime.
/// </summary>
Runtime,
+
/// <summary>
- /// The official rating
+ /// The official rating.
/// </summary>
OfficialRating
}
diff --git a/MediaBrowser.Model/Entities/MetadataProviders.cs b/MediaBrowser.Model/Entities/MetadataProviders.cs
index e9802cf46..1a44a1661 100644
--- a/MediaBrowser.Model/Entities/MetadataProviders.cs
+++ b/MediaBrowser.Model/Entities/MetadataProviders.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
namespace MediaBrowser.Model.Entities
{
/// <summary>
diff --git a/MediaBrowser.Model/Entities/PackageReviewInfo.cs b/MediaBrowser.Model/Entities/PackageReviewInfo.cs
index b73ba8dd0..a034de8ba 100644
--- a/MediaBrowser.Model/Entities/PackageReviewInfo.cs
+++ b/MediaBrowser.Model/Entities/PackageReviewInfo.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
using System;
namespace MediaBrowser.Model.Entities
diff --git a/MediaBrowser.Model/Entities/ParentalRating.cs b/MediaBrowser.Model/Entities/ParentalRating.cs
index a22e119fa..4b37bd64a 100644
--- a/MediaBrowser.Model/Entities/ParentalRating.cs
+++ b/MediaBrowser.Model/Entities/ParentalRating.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
namespace MediaBrowser.Model.Entities
{
/// <summary>
diff --git a/MediaBrowser.Model/Entities/PersonType.cs b/MediaBrowser.Model/Entities/PersonType.cs
index 72e3538fc..81db9c613 100644
--- a/MediaBrowser.Model/Entities/PersonType.cs
+++ b/MediaBrowser.Model/Entities/PersonType.cs
@@ -1,40 +1,47 @@
namespace MediaBrowser.Model.Entities
{
/// <summary>
- /// Struct PersonType
+ /// Struct PersonType.
/// </summary>
public class PersonType
{
/// <summary>
- /// The actor
+ /// The actor.
/// </summary>
public const string Actor = "Actor";
+
/// <summary>
- /// The director
+ /// The director.
/// </summary>
public const string Director = "Director";
+
/// <summary>
- /// The composer
+ /// The composer.
/// </summary>
public const string Composer = "Composer";
+
/// <summary>
- /// The writer
+ /// The writer.
/// </summary>
public const string Writer = "Writer";
+
/// <summary>
- /// The guest star
+ /// The guest star.
/// </summary>
public const string GuestStar = "GuestStar";
+
/// <summary>
- /// The producer
+ /// The producer.
/// </summary>
public const string Producer = "Producer";
+
/// <summary>
- /// The conductor
+ /// The conductor.
/// </summary>
public const string Conductor = "Conductor";
+
/// <summary>
- /// The lyricist
+ /// The lyricist.
/// </summary>
public const string Lyricist = "Lyricist";
}
diff --git a/MediaBrowser.Model/Entities/ProviderIdsExtensions.cs b/MediaBrowser.Model/Entities/ProviderIdsExtensions.cs
index a151bb3bb..922eb4ca7 100644
--- a/MediaBrowser.Model/Entities/ProviderIdsExtensions.cs
+++ b/MediaBrowser.Model/Entities/ProviderIdsExtensions.cs
@@ -4,7 +4,7 @@ using System.Collections.Generic;
namespace MediaBrowser.Model.Entities
{
/// <summary>
- /// Class ProviderIdsExtensions
+ /// Class ProviderIdsExtensions.
/// </summary>
public static class ProviderIdsExtensions
{
@@ -20,7 +20,7 @@ namespace MediaBrowser.Model.Entities
}
/// <summary>
- /// Gets a provider id
+ /// Gets a provider id.
/// </summary>
/// <param name="instance">The instance.</param>
/// <param name="provider">The provider.</param>
@@ -31,7 +31,7 @@ namespace MediaBrowser.Model.Entities
}
/// <summary>
- /// Gets a provider id
+ /// Gets a provider id.
/// </summary>
/// <param name="instance">The instance.</param>
/// <param name="name">The name.</param>
@@ -53,7 +53,7 @@ namespace MediaBrowser.Model.Entities
}
/// <summary>
- /// Sets a provider id
+ /// Sets a provider id.
/// </summary>
/// <param name="instance">The instance.</param>
/// <param name="name">The name.</param>
@@ -89,7 +89,7 @@ namespace MediaBrowser.Model.Entities
}
/// <summary>
- /// Sets a provider id
+ /// Sets a provider id.
/// </summary>
/// <param name="instance">The instance.</param>
/// <param name="provider">The provider.</param>
diff --git a/MediaBrowser.Model/Entities/ScrollDirection.cs b/MediaBrowser.Model/Entities/ScrollDirection.cs
index bc66364f7..a1de0edcb 100644
--- a/MediaBrowser.Model/Entities/ScrollDirection.cs
+++ b/MediaBrowser.Model/Entities/ScrollDirection.cs
@@ -1,16 +1,17 @@
namespace MediaBrowser.Model.Entities
{
/// <summary>
- /// Enum ScrollDirection
+ /// Enum ScrollDirection.
/// </summary>
public enum ScrollDirection
{
/// <summary>
- /// The horizontal
+ /// The horizontal.
/// </summary>
Horizontal,
+
/// <summary>
- /// The vertical
+ /// The vertical.
/// </summary>
Vertical
}
diff --git a/MediaBrowser.Model/Entities/SeriesStatus.cs b/MediaBrowser.Model/Entities/SeriesStatus.cs
index cab6a83e8..c77c4a8ad 100644
--- a/MediaBrowser.Model/Entities/SeriesStatus.cs
+++ b/MediaBrowser.Model/Entities/SeriesStatus.cs
@@ -1,16 +1,17 @@
namespace MediaBrowser.Model.Entities
{
/// <summary>
- /// Enum SeriesStatus
+ /// Enum SeriesStatus.
/// </summary>
public enum SeriesStatus
{
/// <summary>
- /// The continuing
+ /// The continuing.
/// </summary>
Continuing,
+
/// <summary>
- /// The ended
+ /// The ended.
/// </summary>
Ended
}
diff --git a/MediaBrowser.Model/Entities/SortOrder.cs b/MediaBrowser.Model/Entities/SortOrder.cs
index 558ebeac2..f3abc06f3 100644
--- a/MediaBrowser.Model/Entities/SortOrder.cs
+++ b/MediaBrowser.Model/Entities/SortOrder.cs
@@ -1,16 +1,17 @@
namespace MediaBrowser.Model.Entities
{
/// <summary>
- /// Enum SortOrder
+ /// Enum SortOrder.
/// </summary>
public enum SortOrder
{
/// <summary>
- /// The ascending
+ /// The ascending.
/// </summary>
Ascending,
+
/// <summary>
- /// The descending
+ /// The descending.
/// </summary>
Descending
}
diff --git a/MediaBrowser.Model/Entities/TrailerType.cs b/MediaBrowser.Model/Entities/TrailerType.cs
index 73be5d7ca..68d992b0d 100644
--- a/MediaBrowser.Model/Entities/TrailerType.cs
+++ b/MediaBrowser.Model/Entities/TrailerType.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
namespace MediaBrowser.Model.Entities
{
public enum TrailerType
diff --git a/MediaBrowser.Model/Entities/UserDataSaveReason.cs b/MediaBrowser.Model/Entities/UserDataSaveReason.cs
index bd7471682..20404e6f4 100644
--- a/MediaBrowser.Model/Entities/UserDataSaveReason.cs
+++ b/MediaBrowser.Model/Entities/UserDataSaveReason.cs
@@ -1,32 +1,37 @@
namespace MediaBrowser.Model.Entities
{
/// <summary>
- /// Enum UserDataSaveReason
+ /// Enum UserDataSaveReason.
/// </summary>
public enum UserDataSaveReason
{
/// <summary>
- /// The playback start
+ /// The playback start.
/// </summary>
PlaybackStart = 1,
+
/// <summary>
- /// The playback progress
+ /// The playback progress.
/// </summary>
PlaybackProgress = 2,
+
/// <summary>
- /// The playback finished
+ /// The playback finished.
/// </summary>
PlaybackFinished = 3,
+
/// <summary>
- /// The toggle played
+ /// The toggle played.
/// </summary>
TogglePlayed = 4,
+
/// <summary>
- /// The update user rating
+ /// The update user rating.
/// </summary>
UpdateUserRating = 5,
+
/// <summary>
- /// The import
+ /// The import.
/// </summary>
Import = 6
}
diff --git a/MediaBrowser.Model/Entities/Video3DFormat.cs b/MediaBrowser.Model/Entities/Video3DFormat.cs
index 89923ae52..a4f62e18b 100644
--- a/MediaBrowser.Model/Entities/Video3DFormat.cs
+++ b/MediaBrowser.Model/Entities/Video3DFormat.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
namespace MediaBrowser.Model.Entities
{
public enum Video3DFormat
diff --git a/MediaBrowser.Model/Entities/VideoType.cs b/MediaBrowser.Model/Entities/VideoType.cs
index 95d69fb7b..172796301 100644
--- a/MediaBrowser.Model/Entities/VideoType.cs
+++ b/MediaBrowser.Model/Entities/VideoType.cs
@@ -1,24 +1,27 @@
namespace MediaBrowser.Model.Entities
{
/// <summary>
- /// Enum VideoType
+ /// Enum VideoType.
/// </summary>
public enum VideoType
{
/// <summary>
- /// The video file
+ /// The video file.
/// </summary>
VideoFile,
+
/// <summary>
- /// The iso
+ /// The iso.
/// </summary>
Iso,
+
/// <summary>
- /// The DVD
+ /// The DVD.
/// </summary>
Dvd,
+
/// <summary>
- /// The blu ray
+ /// The blu ray.
/// </summary>
BluRay
}
diff --git a/MediaBrowser.Model/Entities/VirtualFolderInfo.cs b/MediaBrowser.Model/Entities/VirtualFolderInfo.cs
index 6bdbdb489..dd30c9c84 100644
--- a/MediaBrowser.Model/Entities/VirtualFolderInfo.cs
+++ b/MediaBrowser.Model/Entities/VirtualFolderInfo.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
using System;
using MediaBrowser.Model.Configuration;
diff --git a/MediaBrowser.Model/Events/GenericEventArgs.cs b/MediaBrowser.Model/Events/GenericEventArgs.cs
index fc8bc620f..1ef0b25c9 100644
--- a/MediaBrowser.Model/Events/GenericEventArgs.cs
+++ b/MediaBrowser.Model/Events/GenericEventArgs.cs
@@ -3,7 +3,7 @@ using System;
namespace MediaBrowser.Model.Events
{
/// <summary>
- /// Provides a generic EventArgs subclass that can hold any kind of object
+ /// Provides a generic EventArgs subclass that can hold any kind of object.
/// </summary>
/// <typeparam name="T"></typeparam>
public class GenericEventArgs<T> : EventArgs
diff --git a/MediaBrowser.Model/Extensions/ListHelper.cs b/MediaBrowser.Model/Extensions/ListHelper.cs
index b5bd07702..90ce6f2e5 100644
--- a/MediaBrowser.Model/Extensions/ListHelper.cs
+++ b/MediaBrowser.Model/Extensions/ListHelper.cs
@@ -1,7 +1,10 @@
+#pragma warning disable CS1591
+
using System;
namespace MediaBrowser.Model.Extensions
{
+ // TODO: @bond remove
public static class ListHelper
{
public static bool ContainsIgnoreCase(string[] list, string value)
diff --git a/MediaBrowser.Model/Extensions/StringHelper.cs b/MediaBrowser.Model/Extensions/StringHelper.cs
index 75ba12a17..f819a295c 100644
--- a/MediaBrowser.Model/Extensions/StringHelper.cs
+++ b/MediaBrowser.Model/Extensions/StringHelper.cs
@@ -1,57 +1,44 @@
-using System;
-using System.Text;
-
namespace MediaBrowser.Model.Extensions
{
/// <summary>
- /// Isolating these helpers allow this entire project to be easily converted to Java
+ /// Helper methods for manipulating strings.
/// </summary>
public static class StringHelper
{
/// <summary>
- /// Equalses the ignore case.
+ /// Returns the string with the first character as uppercase.
/// </summary>
- /// <param name="str1">The STR1.</param>
- /// <param name="str2">The STR2.</param>
- /// <returns><c>true</c> if XXXX, <c>false</c> otherwise.</returns>
- public static bool EqualsIgnoreCase(string str1, string str2)
+ /// <param name="str">The input string.</param>
+ /// <returns>The string with the first character as uppercase.</returns>
+ public static string FirstToUpper(string str)
{
- return string.Equals(str1, str2, StringComparison.OrdinalIgnoreCase);
- }
-
- /// <summary>
- /// Replaces the specified STR.
- /// </summary>
- /// <param name="str">The STR.</param>
- /// <param name="oldValue">The old value.</param>
- /// <param name="newValue">The new value.</param>
- /// <param name="comparison">The comparison.</param>
- /// <returns>System.String.</returns>
- public static string Replace(this string str, string oldValue, string newValue, StringComparison comparison)
- {
- var sb = new StringBuilder();
-
- var previousIndex = 0;
- var index = str.IndexOf(oldValue, comparison);
-
- while (index != -1)
+ if (string.IsNullOrEmpty(str))
{
- sb.Append(str.Substring(previousIndex, index - previousIndex));
- sb.Append(newValue);
- index += oldValue.Length;
-
- previousIndex = index;
- index = str.IndexOf(oldValue, index, comparison);
+ return string.Empty;
}
- sb.Append(str.Substring(previousIndex));
-
- return sb.ToString();
- }
+ if (char.IsUpper(str[0]))
+ {
+ return str;
+ }
- public static string FirstToUpper(this string str)
- {
- return string.IsNullOrEmpty(str) ? string.Empty : str.Substring(0, 1).ToUpperInvariant() + str.Substring(1);
+#if NETSTANDARD2_0
+ char[] a = str.ToCharArray();
+ a[0] = char.ToUpperInvariant(a[0]);
+ return new string(a);
+#else
+ return string.Create(
+ str.Length,
+ str,
+ (chars, buf) =>
+ {
+ chars[0] = char.ToUpperInvariant(buf[0]);
+ for (int i = 1; i < chars.Length; i++)
+ {
+ chars[i] = buf[i];
+ }
+ });
+#endif
}
}
}
diff --git a/MediaBrowser.Model/Globalization/CountryInfo.cs b/MediaBrowser.Model/Globalization/CountryInfo.cs
index 3ae59494f..72362f4f3 100644
--- a/MediaBrowser.Model/Globalization/CountryInfo.cs
+++ b/MediaBrowser.Model/Globalization/CountryInfo.cs
@@ -1,7 +1,7 @@
namespace MediaBrowser.Model.Globalization
{
/// <summary>
- /// Class CountryInfo
+ /// Class CountryInfo.
/// </summary>
public class CountryInfo
{
diff --git a/MediaBrowser.Model/Globalization/CultureDto.cs b/MediaBrowser.Model/Globalization/CultureDto.cs
index a213d4147..f415840b0 100644
--- a/MediaBrowser.Model/Globalization/CultureDto.cs
+++ b/MediaBrowser.Model/Globalization/CultureDto.cs
@@ -1,9 +1,11 @@
-using global::System;
+#pragma warning disable CS1591
+
+using System;
namespace MediaBrowser.Model.Globalization
{
/// <summary>
- /// Class CultureDto
+ /// Class CultureDto.
/// </summary>
public class CultureDto
{
diff --git a/MediaBrowser.Model/Globalization/ILocalizationManager.cs b/MediaBrowser.Model/Globalization/ILocalizationManager.cs
index 0b6cfe1b7..613bfca69 100644
--- a/MediaBrowser.Model/Globalization/ILocalizationManager.cs
+++ b/MediaBrowser.Model/Globalization/ILocalizationManager.cs
@@ -5,7 +5,7 @@ using MediaBrowser.Model.Entities;
namespace MediaBrowser.Model.Globalization
{
/// <summary>
- /// Interface ILocalizationManager
+ /// Interface ILocalizationManager.
/// </summary>
public interface ILocalizationManager
{
diff --git a/MediaBrowser.Model/Globalization/LocalizationOption.cs b/MediaBrowser.Model/Globalization/LocalizationOption.cs
index c4c9a8919..00caf5e11 100644
--- a/MediaBrowser.Model/Globalization/LocalizationOption.cs
+++ b/MediaBrowser.Model/Globalization/LocalizationOption.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
namespace MediaBrowser.Model.Globalization
{
public class LocalizationOption
diff --git a/MediaBrowser.Model/IO/FileSystemEntryType.cs b/MediaBrowser.Model/IO/FileSystemEntryType.cs
index a4ed2334d..7780d6fcc 100644
--- a/MediaBrowser.Model/IO/FileSystemEntryType.cs
+++ b/MediaBrowser.Model/IO/FileSystemEntryType.cs
@@ -1,24 +1,27 @@
namespace MediaBrowser.Model.IO
{
/// <summary>
- /// Enum FileSystemEntryType
+ /// Enum FileSystemEntryType.
/// </summary>
public enum FileSystemEntryType
{
/// <summary>
- /// The file
+ /// The file.
/// </summary>
File,
+
/// <summary>
- /// The directory
+ /// The directory.
/// </summary>
Directory,
+
/// <summary>
- /// The network computer
+ /// The network computer.
/// </summary>
NetworkComputer,
+
/// <summary>
- /// The network share
+ /// The network share.
/// </summary>
NetworkShare
}
diff --git a/MediaBrowser.Model/IO/FileSystemMetadata.cs b/MediaBrowser.Model/IO/FileSystemMetadata.cs
index 2a6d13959..4b9102392 100644
--- a/MediaBrowser.Model/IO/FileSystemMetadata.cs
+++ b/MediaBrowser.Model/IO/FileSystemMetadata.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
using System;
namespace MediaBrowser.Model.IO
@@ -9,26 +11,31 @@ namespace MediaBrowser.Model.IO
/// </summary>
/// <value><c>true</c> if exists; otherwise, <c>false</c>.</value>
public bool Exists { get; set; }
+
/// <summary>
/// Gets or sets the full name.
/// </summary>
/// <value>The full name.</value>
public string FullName { get; set; }
+
/// <summary>
/// Gets or sets the name.
/// </summary>
/// <value>The name.</value>
public string Name { get; set; }
+
/// <summary>
/// Gets or sets the extension.
/// </summary>
/// <value>The extension.</value>
public string Extension { get; set; }
+
/// <summary>
/// Gets or sets the length.
/// </summary>
/// <value>The length.</value>
public long Length { get; set; }
+
/// <summary>
/// Gets or sets the name of the directory.
/// </summary>
@@ -40,11 +47,13 @@ namespace MediaBrowser.Model.IO
/// </summary>
/// <value>The last write time UTC.</value>
public DateTime LastWriteTimeUtc { get; set; }
+
/// <summary>
/// Gets or sets the creation time UTC.
/// </summary>
/// <value>The creation time UTC.</value>
public DateTime CreationTimeUtc { get; set; }
+
/// <summary>
/// Gets a value indicating whether this instance is directory.
/// </summary>
diff --git a/MediaBrowser.Model/IO/IFileSystem.cs b/MediaBrowser.Model/IO/IFileSystem.cs
index ca99b28ca..53f23a8e0 100644
--- a/MediaBrowser.Model/IO/IFileSystem.cs
+++ b/MediaBrowser.Model/IO/IFileSystem.cs
@@ -1,11 +1,12 @@
+#pragma warning disable CS1591
+
using System;
using System.Collections.Generic;
-using System.IO;
namespace MediaBrowser.Model.IO
{
/// <summary>
- /// Interface IFileSystem
+ /// Interface IFileSystem.
/// </summary>
public interface IFileSystem
{
@@ -99,20 +100,6 @@ namespace MediaBrowser.Model.IO
DateTime GetLastWriteTimeUtc(string path);
/// <summary>
- /// Gets the file stream.
- /// </summary>
- /// <param name="path">The path.</param>
- /// <param name="mode">The mode.</param>
- /// <param name="access">The access.</param>
- /// <param name="share">The share.</param>
- /// <param name="isAsync">if set to <c>true</c> [is asynchronous].</param>
- /// <returns>FileStream.</returns>
- Stream GetFileStream(string path, FileOpenMode mode, FileAccessMode access, FileShareMode share, bool isAsync = false);
-
- Stream GetFileStream(string path, FileOpenMode mode, FileAccessMode access, FileShareMode share,
- FileOpenOptions fileOpenOptions);
-
- /// <summary>
/// Swaps the files.
/// </summary>
/// <param name="file1">The file1.</param>
@@ -218,128 +205,4 @@ namespace MediaBrowser.Model.IO
List<FileSystemMetadata> GetDrives();
void SetExecutable(string path);
}
-
- //TODO Investigate if can be replaced by the one from System.IO ?
- public enum FileOpenMode
- {
- //
- // Summary:
- // Specifies that the operating system should create a new file. This requires System.Security.Permissions.FileIOPermissionAccess.Write
- // permission. If the file already exists, an System.IO.IOException exception is
- // thrown.
- CreateNew = 1,
- //
- // Summary:
- // Specifies that the operating system should create a new file. If the file already
- // exists, it will be overwritten. This requires System.Security.Permissions.FileIOPermissionAccess.Write
- // permission. FileMode.Create is equivalent to requesting that if the file does
- // not exist, use System.IO.FileMode.CreateNew; otherwise, use System.IO.FileMode.Truncate.
- // If the file already exists but is a hidden file, an System.UnauthorizedAccessException
- // exception is thrown.
- Create = 2,
- //
- // Summary:
- // Specifies that the operating system should open an existing file. The ability
- // to open the file is dependent on the value specified by the System.IO.FileAccess
- // enumeration. A System.IO.FileNotFoundException exception is thrown if the file
- // does not exist.
- Open = 3,
- //
- // Summary:
- // Specifies that the operating system should open a file if it exists; otherwise,
- // a new file should be created. If the file is opened with FileAccess.Read, System.Security.Permissions.FileIOPermissionAccess.Read
- // permission is required. If the file access is FileAccess.Write, System.Security.Permissions.FileIOPermissionAccess.Write
- // permission is required. If the file is opened with FileAccess.ReadWrite, both
- // System.Security.Permissions.FileIOPermissionAccess.Read and System.Security.Permissions.FileIOPermissionAccess.Write
- // permissions are required.
- OpenOrCreate = 4
- }
-
- public enum FileAccessMode
- {
- //
- // Summary:
- // Read access to the file. Data can be read from the file. Combine with Write for
- // read/write access.
- Read = 1,
- //
- // Summary:
- // Write access to the file. Data can be written to the file. Combine with Read
- // for read/write access.
- Write = 2
- }
-
- public enum FileShareMode
- {
- //
- // Summary:
- // Declines sharing of the current file. Any request to open the file (by this process
- // or another process) will fail until the file is closed.
- None = 0,
- //
- // Summary:
- // Allows subsequent opening of the file for reading. If this flag is not specified,
- // any request to open the file for reading (by this process or another process)
- // will fail until the file is closed. However, even if this flag is specified,
- // additional permissions might still be needed to access the file.
- Read = 1,
- //
- // Summary:
- // Allows subsequent opening of the file for writing. If this flag is not specified,
- // any request to open the file for writing (by this process or another process)
- // will fail until the file is closed. However, even if this flag is specified,
- // additional permissions might still be needed to access the file.
- Write = 2,
- //
- // Summary:
- // Allows subsequent opening of the file for reading or writing. If this flag is
- // not specified, any request to open the file for reading or writing (by this process
- // or another process) will fail until the file is closed. However, even if this
- // flag is specified, additional permissions might still be needed to access the
- // file.
- ReadWrite = 3
- }
-
- //
- // Summary:
- // Represents advanced options for creating a System.IO.FileStream object.
- [Flags]
- public enum FileOpenOptions
- {
- //
- // Summary:
- // Indicates that the system should write through any intermediate cache and go
- // directly to disk.
- WriteThrough = int.MinValue,
- //
- // Summary:
- // Indicates that no additional options should be used when creating a System.IO.FileStream
- // object.
- None = 0,
- //
- // Summary:
- // Indicates that a file is encrypted and can be decrypted only by using the same
- // user account used for encryption.
- Encrypted = 16384,
- //
- // Summary:
- // Indicates that a file is automatically deleted when it is no longer in use.
- DeleteOnClose = 67108864,
- //
- // Summary:
- // Indicates that the file is to be accessed sequentially from beginning to end.
- // The system can use this as a hint to optimize file caching. If an application
- // moves the file pointer for random access, optimum caching may not occur; however,
- // correct operation is still guaranteed.
- SequentialScan = 134217728,
- //
- // Summary:
- // Indicates that the file is accessed randomly. The system can use this as a hint
- // to optimize file caching.
- RandomAccess = 268435456,
- //
- // Summary:
- // Indicates that a file can be used for asynchronous reading and writing.
- Asynchronous = 1073741824
- }
}
diff --git a/MediaBrowser.Model/IO/IIsoManager.cs b/MediaBrowser.Model/IO/IIsoManager.cs
index eb0cb4bfb..8b6af019d 100644
--- a/MediaBrowser.Model/IO/IIsoManager.cs
+++ b/MediaBrowser.Model/IO/IIsoManager.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
using System;
using System.Collections.Generic;
using System.IO;
diff --git a/MediaBrowser.Model/IO/IIsoMount.cs b/MediaBrowser.Model/IO/IIsoMount.cs
index 825c0c243..72ec673ee 100644
--- a/MediaBrowser.Model/IO/IIsoMount.cs
+++ b/MediaBrowser.Model/IO/IIsoMount.cs
@@ -3,7 +3,7 @@ using System;
namespace MediaBrowser.Model.IO
{
/// <summary>
- /// Interface IIsoMount
+ /// Interface IIsoMount.
/// </summary>
public interface IIsoMount : IDisposable
{
diff --git a/MediaBrowser.Model/IO/IIsoMounter.cs b/MediaBrowser.Model/IO/IIsoMounter.cs
index 766a9e4e6..83fdb5fd6 100644
--- a/MediaBrowser.Model/IO/IIsoMounter.cs
+++ b/MediaBrowser.Model/IO/IIsoMounter.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
using System;
using System.IO;
using System.Threading;
diff --git a/MediaBrowser.Model/IO/StreamDefaults.cs b/MediaBrowser.Model/IO/IODefaults.cs
index 4b55ce1f3..f392dbcce 100644
--- a/MediaBrowser.Model/IO/StreamDefaults.cs
+++ b/MediaBrowser.Model/IO/IODefaults.cs
@@ -1,18 +1,18 @@
namespace MediaBrowser.Model.IO
{
/// <summary>
- /// Class StreamDefaults.
+ /// Class IODefaults.
/// </summary>
- public static class StreamDefaults
+ public static class IODefaults
{
/// <summary>
/// The default copy to buffer size.
/// </summary>
- public const int DefaultCopyToBufferSize = 81920;
+ public const int CopyToBufferSize = 81920;
/// <summary>
/// The default file stream buffer size.
/// </summary>
- public const int DefaultFileStreamBufferSize = 4096;
+ public const int FileStreamBufferSize = 4096;
}
}
diff --git a/MediaBrowser.Model/IO/IShortcutHandler.cs b/MediaBrowser.Model/IO/IShortcutHandler.cs
index 2cc18274b..5c663aa0d 100644
--- a/MediaBrowser.Model/IO/IShortcutHandler.cs
+++ b/MediaBrowser.Model/IO/IShortcutHandler.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
namespace MediaBrowser.Model.IO
{
public interface IShortcutHandler
@@ -7,12 +9,14 @@ namespace MediaBrowser.Model.IO
/// </summary>
/// <value>The extension.</value>
string Extension { get; }
+
/// <summary>
/// Resolves the specified shortcut path.
/// </summary>
/// <param name="shortcutPath">The shortcut path.</param>
/// <returns>System.String.</returns>
string Resolve(string shortcutPath);
+
/// <summary>
/// Creates the specified shortcut path.
/// </summary>
diff --git a/MediaBrowser.Model/IO/IStreamHelper.cs b/MediaBrowser.Model/IO/IStreamHelper.cs
index 97d985df6..e348cd725 100644
--- a/MediaBrowser.Model/IO/IStreamHelper.cs
+++ b/MediaBrowser.Model/IO/IStreamHelper.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
using System;
using System.IO;
using System.Threading;
diff --git a/MediaBrowser.Model/IO/IZipClient.cs b/MediaBrowser.Model/IO/IZipClient.cs
index eaddd6df3..83e8a018d 100644
--- a/MediaBrowser.Model/IO/IZipClient.cs
+++ b/MediaBrowser.Model/IO/IZipClient.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
using System.IO;
namespace MediaBrowser.Model.IO
diff --git a/MediaBrowser.Model/Library/PlayAccess.cs b/MediaBrowser.Model/Library/PlayAccess.cs
index 2fd754f5e..a2f263ce5 100644
--- a/MediaBrowser.Model/Library/PlayAccess.cs
+++ b/MediaBrowser.Model/Library/PlayAccess.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
namespace MediaBrowser.Model.Library
{
public enum PlayAccess
diff --git a/MediaBrowser.Model/Library/UserViewQuery.cs b/MediaBrowser.Model/Library/UserViewQuery.cs
index c2e189603..a538efd25 100644
--- a/MediaBrowser.Model/Library/UserViewQuery.cs
+++ b/MediaBrowser.Model/Library/UserViewQuery.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
using System;
namespace MediaBrowser.Model.Library
diff --git a/MediaBrowser.Model/LiveTv/BaseTimerInfoDto.cs b/MediaBrowser.Model/LiveTv/BaseTimerInfoDto.cs
index 311b5b0c5..064ce6520 100644
--- a/MediaBrowser.Model/LiveTv/BaseTimerInfoDto.cs
+++ b/MediaBrowser.Model/LiveTv/BaseTimerInfoDto.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
using System;
using MediaBrowser.Model.Dto;
diff --git a/MediaBrowser.Model/LiveTv/ChannelType.cs b/MediaBrowser.Model/LiveTv/ChannelType.cs
index 8808aa6d2..b6974cb08 100644
--- a/MediaBrowser.Model/LiveTv/ChannelType.cs
+++ b/MediaBrowser.Model/LiveTv/ChannelType.cs
@@ -1,7 +1,7 @@
namespace MediaBrowser.Model.LiveTv
{
/// <summary>
- /// Enum ChannelType
+ /// Enum ChannelType.
/// </summary>
public enum ChannelType
{
@@ -11,7 +11,7 @@ namespace MediaBrowser.Model.LiveTv
TV,
/// <summary>
- /// The radio
+ /// The radio.
/// </summary>
Radio
}
diff --git a/MediaBrowser.Model/LiveTv/DayPattern.cs b/MediaBrowser.Model/LiveTv/DayPattern.cs
index 73b15507b..17efe3908 100644
--- a/MediaBrowser.Model/LiveTv/DayPattern.cs
+++ b/MediaBrowser.Model/LiveTv/DayPattern.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
namespace MediaBrowser.Model.LiveTv
{
public enum DayPattern
diff --git a/MediaBrowser.Model/LiveTv/GuideInfo.cs b/MediaBrowser.Model/LiveTv/GuideInfo.cs
index 1303c278e..a224d73b7 100644
--- a/MediaBrowser.Model/LiveTv/GuideInfo.cs
+++ b/MediaBrowser.Model/LiveTv/GuideInfo.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
using System;
namespace MediaBrowser.Model.LiveTv
diff --git a/MediaBrowser.Model/LiveTv/LiveTvChannelQuery.cs b/MediaBrowser.Model/LiveTv/LiveTvChannelQuery.cs
index eedf89db0..8154fbd0e 100644
--- a/MediaBrowser.Model/LiveTv/LiveTvChannelQuery.cs
+++ b/MediaBrowser.Model/LiveTv/LiveTvChannelQuery.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
using System;
using MediaBrowser.Model.Entities;
diff --git a/MediaBrowser.Model/LiveTv/LiveTvInfo.cs b/MediaBrowser.Model/LiveTv/LiveTvInfo.cs
index 60cb27331..85b77af24 100644
--- a/MediaBrowser.Model/LiveTv/LiveTvInfo.cs
+++ b/MediaBrowser.Model/LiveTv/LiveTvInfo.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
using System;
namespace MediaBrowser.Model.LiveTv
diff --git a/MediaBrowser.Model/LiveTv/LiveTvOptions.cs b/MediaBrowser.Model/LiveTv/LiveTvOptions.cs
index 36fe0c3d2..dc8e0f91b 100644
--- a/MediaBrowser.Model/LiveTv/LiveTvOptions.cs
+++ b/MediaBrowser.Model/LiveTv/LiveTvOptions.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
using System;
using MediaBrowser.Model.Dto;
diff --git a/MediaBrowser.Model/LiveTv/LiveTvServiceInfo.cs b/MediaBrowser.Model/LiveTv/LiveTvServiceInfo.cs
index 9ad13391a..09e900643 100644
--- a/MediaBrowser.Model/LiveTv/LiveTvServiceInfo.cs
+++ b/MediaBrowser.Model/LiveTv/LiveTvServiceInfo.cs
@@ -1,9 +1,11 @@
+#pragma warning disable CS1591
+
using System;
namespace MediaBrowser.Model.LiveTv
{
/// <summary>
- /// Class ServiceInfo
+ /// Class ServiceInfo.
/// </summary>
public class LiveTvServiceInfo
{
@@ -42,6 +44,7 @@ namespace MediaBrowser.Model.LiveTv
/// </summary>
/// <value><c>true</c> if this instance has update available; otherwise, <c>false</c>.</value>
public bool HasUpdateAvailable { get; set; }
+
/// <summary>
/// Gets or sets a value indicating whether this instance is visible.
/// </summary>
diff --git a/MediaBrowser.Model/LiveTv/LiveTvServiceStatus.cs b/MediaBrowser.Model/LiveTv/LiveTvServiceStatus.cs
index 7578f329a..72a0e2d7b 100644
--- a/MediaBrowser.Model/LiveTv/LiveTvServiceStatus.cs
+++ b/MediaBrowser.Model/LiveTv/LiveTvServiceStatus.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
namespace MediaBrowser.Model.LiveTv
{
public enum LiveTvServiceStatus
diff --git a/MediaBrowser.Model/LiveTv/LiveTvTunerStatus.cs b/MediaBrowser.Model/LiveTv/LiveTvTunerStatus.cs
index f7f521e43..80a646195 100644
--- a/MediaBrowser.Model/LiveTv/LiveTvTunerStatus.cs
+++ b/MediaBrowser.Model/LiveTv/LiveTvTunerStatus.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
namespace MediaBrowser.Model.LiveTv
{
public enum LiveTvTunerStatus
diff --git a/MediaBrowser.Model/LiveTv/ProgramAudio.cs b/MediaBrowser.Model/LiveTv/ProgramAudio.cs
index 158d67eb9..727d34695 100644
--- a/MediaBrowser.Model/LiveTv/ProgramAudio.cs
+++ b/MediaBrowser.Model/LiveTv/ProgramAudio.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
namespace MediaBrowser.Model.LiveTv
{
public enum ProgramAudio
diff --git a/MediaBrowser.Model/LiveTv/RecordingQuery.cs b/MediaBrowser.Model/LiveTv/RecordingQuery.cs
index f98d7fe86..c75092b79 100644
--- a/MediaBrowser.Model/LiveTv/RecordingQuery.cs
+++ b/MediaBrowser.Model/LiveTv/RecordingQuery.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
using System;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Querying;
diff --git a/MediaBrowser.Model/LiveTv/RecordingStatus.cs b/MediaBrowser.Model/LiveTv/RecordingStatus.cs
index d3270c4d3..b0ba42d43 100644
--- a/MediaBrowser.Model/LiveTv/RecordingStatus.cs
+++ b/MediaBrowser.Model/LiveTv/RecordingStatus.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
namespace MediaBrowser.Model.LiveTv
{
public enum RecordingStatus
diff --git a/MediaBrowser.Model/LiveTv/SeriesTimerInfoDto.cs b/MediaBrowser.Model/LiveTv/SeriesTimerInfoDto.cs
index 72c7a0c90..29f417489 100644
--- a/MediaBrowser.Model/LiveTv/SeriesTimerInfoDto.cs
+++ b/MediaBrowser.Model/LiveTv/SeriesTimerInfoDto.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
using System;
using System.Collections.Generic;
using MediaBrowser.Model.Entities;
@@ -12,7 +14,7 @@ namespace MediaBrowser.Model.LiveTv
public SeriesTimerInfoDto()
{
ImageTags = new Dictionary<ImageType, string>();
- Days = new DayOfWeek[] { };
+ Days = Array.Empty<DayOfWeek>();
Type = "SeriesTimer";
}
diff --git a/MediaBrowser.Model/LiveTv/SeriesTimerQuery.cs b/MediaBrowser.Model/LiveTv/SeriesTimerQuery.cs
index a15ba7a12..bb553a576 100644
--- a/MediaBrowser.Model/LiveTv/SeriesTimerQuery.cs
+++ b/MediaBrowser.Model/LiveTv/SeriesTimerQuery.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
using MediaBrowser.Model.Entities;
namespace MediaBrowser.Model.LiveTv
diff --git a/MediaBrowser.Model/LiveTv/TimerInfoDto.cs b/MediaBrowser.Model/LiveTv/TimerInfoDto.cs
index 208f731c5..a1fbc5177 100644
--- a/MediaBrowser.Model/LiveTv/TimerInfoDto.cs
+++ b/MediaBrowser.Model/LiveTv/TimerInfoDto.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
using MediaBrowser.Model.Dto;
namespace MediaBrowser.Model.LiveTv
diff --git a/MediaBrowser.Model/LiveTv/TimerQuery.cs b/MediaBrowser.Model/LiveTv/TimerQuery.cs
index 1478cc148..1ef6dd67e 100644
--- a/MediaBrowser.Model/LiveTv/TimerQuery.cs
+++ b/MediaBrowser.Model/LiveTv/TimerQuery.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
namespace MediaBrowser.Model.LiveTv
{
public class TimerQuery
diff --git a/MediaBrowser.Model/MediaBrowser.Model.csproj b/MediaBrowser.Model/MediaBrowser.Model.csproj
index 53cd08fbd..b41d0af1d 100644
--- a/MediaBrowser.Model/MediaBrowser.Model.csproj
+++ b/MediaBrowser.Model/MediaBrowser.Model.csproj
@@ -1,4 +1,9 @@
-<Project Sdk="Microsoft.NET.Sdk">
+<Project Sdk="Microsoft.NET.Sdk">
+
+ <!-- ProjectGuid is only included as a requirement for SonarQube analysis -->
+ <PropertyGroup>
+ <ProjectGuid>{7EEEB4BB-F3E8-48FC-B4C5-70F0FFF8329B}</ProjectGuid>
+ </PropertyGroup>
<PropertyGroup>
<Authors>Jellyfin Contributors</Authors>
@@ -8,20 +13,33 @@
</PropertyGroup>
<PropertyGroup>
- <TargetFramework>netstandard2.0</TargetFramework>
+ <TargetFrameworks>netstandard2.0;netstandard2.1</TargetFrameworks>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
+ <TreatWarningsAsErrors Condition=" '$(Configuration)' == 'Release' ">true</TreatWarningsAsErrors>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Http.Abstractions" Version="2.2.0" />
- <PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="3.0.0" />
+ <PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="3.1.3" />
<PackageReference Include="System.Globalization" Version="4.3.0" />
- <PackageReference Include="System.Text.Json" Version="4.6.0" />
+ <PackageReference Include="System.Text.Json" Version="4.7.1" />
</ItemGroup>
<ItemGroup>
<Compile Include="..\SharedVersion.cs" />
</ItemGroup>
+ <!-- Code Analyzers-->
+ <ItemGroup Condition=" '$(Configuration)' == 'Debug' ">
+ <PackageReference Include="Microsoft.CodeAnalysis.FxCopAnalyzers" Version="2.9.8" PrivateAssets="All" />
+ <PackageReference Include="SerilogAnalyzer" Version="0.15.0" PrivateAssets="All" />
+ <PackageReference Include="StyleCop.Analyzers" Version="1.1.118" PrivateAssets="All" />
+ <PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" PrivateAssets="All" />
+ </ItemGroup>
+
+ <PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
+ <CodeAnalysisRuleSet>../jellyfin.ruleset</CodeAnalysisRuleSet>
+ </PropertyGroup>
+
</Project>
diff --git a/MediaBrowser.Model/MediaInfo/AudioCodec.cs b/MediaBrowser.Model/MediaInfo/AudioCodec.cs
index 5ebdb99cb..dcb6fa270 100644
--- a/MediaBrowser.Model/MediaInfo/AudioCodec.cs
+++ b/MediaBrowser.Model/MediaInfo/AudioCodec.cs
@@ -1,6 +1,8 @@
+#pragma warning disable CS1591
+
namespace MediaBrowser.Model.MediaInfo
{
- public class AudioCodec
+ public static class AudioCodec
{
public const string AAC = "aac";
public const string MP3 = "mp3";
diff --git a/MediaBrowser.Model/MediaInfo/BlurayDiscInfo.cs b/MediaBrowser.Model/MediaInfo/BlurayDiscInfo.cs
index e728ecdfd..29ba10dbb 100644
--- a/MediaBrowser.Model/MediaInfo/BlurayDiscInfo.cs
+++ b/MediaBrowser.Model/MediaInfo/BlurayDiscInfo.cs
@@ -1,9 +1,11 @@
+#pragma warning disable CS1591
+
using MediaBrowser.Model.Entities;
namespace MediaBrowser.Model.MediaInfo
{
/// <summary>
- /// Represents the result of BDInfo output
+ /// Represents the result of BDInfo output.
/// </summary>
public class BlurayDiscInfo
{
diff --git a/MediaBrowser.Model/MediaInfo/Container.cs b/MediaBrowser.Model/MediaInfo/Container.cs
deleted file mode 100644
index f8d56702d..000000000
--- a/MediaBrowser.Model/MediaInfo/Container.cs
+++ /dev/null
@@ -1,8 +0,0 @@
-namespace MediaBrowser.Model.MediaInfo
-{
- public class Container
- {
- public const string MP4 = "mp4";
- public const string MKV = "mkv";
- }
-}
diff --git a/MediaBrowser.Model/MediaInfo/IBlurayExaminer.cs b/MediaBrowser.Model/MediaInfo/IBlurayExaminer.cs
index 27137ab26..5b7d1d03c 100644
--- a/MediaBrowser.Model/MediaInfo/IBlurayExaminer.cs
+++ b/MediaBrowser.Model/MediaInfo/IBlurayExaminer.cs
@@ -1,7 +1,7 @@
namespace MediaBrowser.Model.MediaInfo
{
/// <summary>
- /// Interface IBlurayExaminer
+ /// Interface IBlurayExaminer.
/// </summary>
public interface IBlurayExaminer
{
diff --git a/MediaBrowser.Model/MediaInfo/LiveStreamRequest.cs b/MediaBrowser.Model/MediaInfo/LiveStreamRequest.cs
index a5ae7c7a5..52348f802 100644
--- a/MediaBrowser.Model/MediaInfo/LiveStreamRequest.cs
+++ b/MediaBrowser.Model/MediaInfo/LiveStreamRequest.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
using System;
using MediaBrowser.Model.Dlna;
diff --git a/MediaBrowser.Model/MediaInfo/LiveStreamResponse.cs b/MediaBrowser.Model/MediaInfo/LiveStreamResponse.cs
index dd4b69469..45b8fcce9 100644
--- a/MediaBrowser.Model/MediaInfo/LiveStreamResponse.cs
+++ b/MediaBrowser.Model/MediaInfo/LiveStreamResponse.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
using MediaBrowser.Model.Dto;
namespace MediaBrowser.Model.MediaInfo
diff --git a/MediaBrowser.Model/MediaInfo/MediaInfo.cs b/MediaBrowser.Model/MediaInfo/MediaInfo.cs
index 9d45a2af1..ad174f15d 100644
--- a/MediaBrowser.Model/MediaInfo/MediaInfo.cs
+++ b/MediaBrowser.Model/MediaInfo/MediaInfo.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
using System;
using System.Collections.Generic;
using MediaBrowser.Model.Dto;
@@ -14,38 +16,45 @@ namespace MediaBrowser.Model.MediaInfo
/// </summary>
/// <value>The album.</value>
public string Album { get; set; }
+
/// <summary>
/// Gets or sets the artists.
/// </summary>
/// <value>The artists.</value>
public string[] Artists { get; set; }
+
/// <summary>
/// Gets or sets the album artists.
/// </summary>
/// <value>The album artists.</value>
public string[] AlbumArtists { get; set; }
+
/// <summary>
/// Gets or sets the studios.
/// </summary>
/// <value>The studios.</value>
public string[] Studios { get; set; }
public string[] Genres { get; set; }
+ public string ShowName { get; set; }
public int? IndexNumber { get; set; }
public int? ParentIndexNumber { get; set; }
public int? ProductionYear { get; set; }
public DateTime? PremiereDate { get; set; }
public BaseItemPerson[] People { get; set; }
public Dictionary<string, string> ProviderIds { get; set; }
+
/// <summary>
/// Gets or sets the official rating.
/// </summary>
/// <value>The official rating.</value>
public string OfficialRating { get; set; }
+
/// <summary>
/// Gets or sets the official rating description.
/// </summary>
/// <value>The official rating description.</value>
public string OfficialRatingDescription { get; set; }
+
/// <summary>
/// Gets or sets the overview.
/// </summary>
diff --git a/MediaBrowser.Model/MediaInfo/MediaProtocol.cs b/MediaBrowser.Model/MediaInfo/MediaProtocol.cs
index a993f6075..b9df01f27 100644
--- a/MediaBrowser.Model/MediaInfo/MediaProtocol.cs
+++ b/MediaBrowser.Model/MediaInfo/MediaProtocol.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
namespace MediaBrowser.Model.MediaInfo
{
public enum MediaProtocol
diff --git a/MediaBrowser.Model/MediaInfo/PlaybackInfoRequest.cs b/MediaBrowser.Model/MediaInfo/PlaybackInfoRequest.cs
index e5fad4e11..a2f163422 100644
--- a/MediaBrowser.Model/MediaInfo/PlaybackInfoRequest.cs
+++ b/MediaBrowser.Model/MediaInfo/PlaybackInfoRequest.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
using System;
using MediaBrowser.Model.Dlna;
diff --git a/MediaBrowser.Model/MediaInfo/SubtitleFormat.cs b/MediaBrowser.Model/MediaInfo/SubtitleFormat.cs
index 208e9bab9..2bd45695a 100644
--- a/MediaBrowser.Model/MediaInfo/SubtitleFormat.cs
+++ b/MediaBrowser.Model/MediaInfo/SubtitleFormat.cs
@@ -1,6 +1,8 @@
+#pragma warning disable CS1591
+
namespace MediaBrowser.Model.MediaInfo
{
- public class SubtitleFormat
+ public static class SubtitleFormat
{
public const string SRT = "srt";
public const string SSA = "ssa";
diff --git a/MediaBrowser.Model/MediaInfo/SubtitleTrackEvent.cs b/MediaBrowser.Model/MediaInfo/SubtitleTrackEvent.cs
index 4eb000e58..5b0ccb28a 100644
--- a/MediaBrowser.Model/MediaInfo/SubtitleTrackEvent.cs
+++ b/MediaBrowser.Model/MediaInfo/SubtitleTrackEvent.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
namespace MediaBrowser.Model.MediaInfo
{
public class SubtitleTrackEvent
diff --git a/MediaBrowser.Model/MediaInfo/SubtitleTrackInfo.cs b/MediaBrowser.Model/MediaInfo/SubtitleTrackInfo.cs
index c382b20c9..37f5c55da 100644
--- a/MediaBrowser.Model/MediaInfo/SubtitleTrackInfo.cs
+++ b/MediaBrowser.Model/MediaInfo/SubtitleTrackInfo.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
using System;
using System.Collections.Generic;
diff --git a/MediaBrowser.Model/MediaInfo/TransportStreamTimestamp.cs b/MediaBrowser.Model/MediaInfo/TransportStreamTimestamp.cs
index 46ce2302e..b7ee5747a 100644
--- a/MediaBrowser.Model/MediaInfo/TransportStreamTimestamp.cs
+++ b/MediaBrowser.Model/MediaInfo/TransportStreamTimestamp.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
namespace MediaBrowser.Model.MediaInfo
{
public enum TransportStreamTimestamp
diff --git a/MediaBrowser.Model/MediaInfo/VideoCodec.cs b/MediaBrowser.Model/MediaInfo/VideoCodec.cs
deleted file mode 100644
index a26ce1b70..000000000
--- a/MediaBrowser.Model/MediaInfo/VideoCodec.cs
+++ /dev/null
@@ -1,14 +0,0 @@
-namespace MediaBrowser.Model.MediaInfo
-{
- public class VideoCodec
- {
- public const string H263 = "h263";
- public const string H264 = "h264";
- public const string H265 = "h265";
- public const string MPEG4 = "mpeg4";
- public const string MPEG1 = "mpeg1video";
- public const string MPEG2 = "mpeg2video";
- public const string MSMPEG4 = "msmpeg4";
- public const string VC1 = "vc1";
- }
-}
diff --git a/MediaBrowser.Model/Net/EndPointInfo.cs b/MediaBrowser.Model/Net/EndPointInfo.cs
index b73799ea8..f5ac3d169 100644
--- a/MediaBrowser.Model/Net/EndPointInfo.cs
+++ b/MediaBrowser.Model/Net/EndPointInfo.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
namespace MediaBrowser.Model.Net
{
public class EndPointInfo
diff --git a/MediaBrowser.Model/Net/HttpException.cs b/MediaBrowser.Model/Net/HttpException.cs
index 16253ed6c..4b15e30f0 100644
--- a/MediaBrowser.Model/Net/HttpException.cs
+++ b/MediaBrowser.Model/Net/HttpException.cs
@@ -4,7 +4,7 @@ using System.Net;
namespace MediaBrowser.Model.Net
{
/// <summary>
- /// Class HttpException
+ /// Class HttpException.
/// </summary>
public class HttpException : Exception
{
diff --git a/MediaBrowser.Model/Net/ISocket.cs b/MediaBrowser.Model/Net/ISocket.cs
index f80de5524..2bfbfcb20 100644
--- a/MediaBrowser.Model/Net/ISocket.cs
+++ b/MediaBrowser.Model/Net/ISocket.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
using System;
using System.Net;
using System.Threading;
@@ -14,8 +16,6 @@ namespace MediaBrowser.Model.Net
Task<SocketReceiveResult> ReceiveAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken);
- int Receive(byte[] buffer, int offset, int count);
-
IAsyncResult BeginReceive(byte[] buffer, int offset, int count, AsyncCallback callback);
SocketReceiveResult EndReceive(IAsyncResult result);
diff --git a/MediaBrowser.Model/Net/ISocketFactory.cs b/MediaBrowser.Model/Net/ISocketFactory.cs
index 2f857f1af..363abefc1 100644
--- a/MediaBrowser.Model/Net/ISocketFactory.cs
+++ b/MediaBrowser.Model/Net/ISocketFactory.cs
@@ -1,4 +1,5 @@
-using System.IO;
+#pragma warning disable CS1591
+
using System.Net;
namespace MediaBrowser.Model.Net
@@ -8,13 +9,6 @@ namespace MediaBrowser.Model.Net
/// </summary>
public interface ISocketFactory
{
- /// <summary>
- /// Creates a new unicast socket using the specified local port number.
- /// </summary>
- /// <param name="localPort">The local port to bind to.</param>
- /// <returns>A <see cref="ISocket"/> implementation.</returns>
- ISocket CreateUdpSocket(int localPort);
-
ISocket CreateUdpBroadcastSocket(int localPort);
/// <summary>
@@ -30,7 +24,5 @@ namespace MediaBrowser.Model.Net
/// <param name="localPort">The local port to bind to.</param>
/// <returns>A <see cref="ISocket"/> implementation.</returns>
ISocket CreateUdpMulticastSocket(string ipAddress, int multicastTimeToLive, int localPort);
-
- Stream CreateNetworkStream(ISocket socket, bool ownsSocket);
}
}
diff --git a/MediaBrowser.Model/Net/MimeTypes.cs b/MediaBrowser.Model/Net/MimeTypes.cs
index de5e58d22..06efedb30 100644
--- a/MediaBrowser.Model/Net/MimeTypes.cs
+++ b/MediaBrowser.Model/Net/MimeTypes.cs
@@ -1,8 +1,9 @@
+#pragma warning disable CS1591
+
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
-using MediaBrowser.Model.Extensions;
namespace MediaBrowser.Model.Net
{
@@ -63,6 +64,7 @@ namespace MediaBrowser.Model.Net
{ ".m3u8", "application/x-mpegURL" },
{ ".mobi", "application/x-mobipocket-ebook" },
{ ".xml", "application/xml" },
+ { ".wasm", "application/wasm" },
// Type image
{ ".jpg", "image/jpeg" },
@@ -104,6 +106,7 @@ namespace MediaBrowser.Model.Net
{ ".3g2", "video/3gpp2" },
{ ".mpd", "video/vnd.mpeg.dash.mpd" },
{ ".ts", "video/mp2t" },
+ { ".mpegts", "video/mp2t" },
// Type audio
{ ".mp3", "audio/mpeg" },
@@ -121,6 +124,8 @@ namespace MediaBrowser.Model.Net
{ ".xsp", "audio/xsp" },
{ ".dsp", "audio/dsp" },
{ ".flac", "audio/flac" },
+ { ".ape", "audio/x-ape" },
+ { ".wv", "audio/x-wavpack" },
};
private static readonly Dictionary<string, string> _extensionLookup = CreateExtensionLookup();
@@ -165,20 +170,20 @@ namespace MediaBrowser.Model.Net
}
// Type text
- if (StringHelper.EqualsIgnoreCase(ext, ".html")
- || StringHelper.EqualsIgnoreCase(ext, ".htm"))
+ if (string.Equals(ext, ".html", StringComparison.OrdinalIgnoreCase)
+ || string.Equals(ext, ".htm", StringComparison.OrdinalIgnoreCase))
{
return "text/html; charset=UTF-8";
}
- if (StringHelper.EqualsIgnoreCase(ext, ".log")
- || StringHelper.EqualsIgnoreCase(ext, ".srt"))
+ if (string.Equals(ext, ".log", StringComparison.OrdinalIgnoreCase)
+ || string.Equals(ext, ".srt", StringComparison.OrdinalIgnoreCase))
{
return "text/plain";
}
// Misc
- if (StringHelper.EqualsIgnoreCase(ext, ".dll"))
+ if (string.Equals(ext, ".dll", StringComparison.OrdinalIgnoreCase))
{
return "application/octet-stream";
}
diff --git a/MediaBrowser.Model/Net/NetworkShare.cs b/MediaBrowser.Model/Net/NetworkShare.cs
index 1f61414fc..744c6ec14 100644
--- a/MediaBrowser.Model/Net/NetworkShare.cs
+++ b/MediaBrowser.Model/Net/NetworkShare.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
namespace MediaBrowser.Model.Net
{
public class NetworkShare
diff --git a/MediaBrowser.Model/Net/NetworkShareType.cs b/MediaBrowser.Model/Net/NetworkShareType.cs
index bf2d092a6..5d985f85d 100644
--- a/MediaBrowser.Model/Net/NetworkShareType.cs
+++ b/MediaBrowser.Model/Net/NetworkShareType.cs
@@ -1,28 +1,32 @@
namespace MediaBrowser.Model.Net
{
/// <summary>
- /// Enum NetworkShareType
+ /// Enum NetworkShareType.
/// </summary>
public enum NetworkShareType
{
/// <summary>
- /// Disk share
+ /// Disk share.
/// </summary>
Disk,
+
/// <summary>
- /// Printer share
+ /// Printer share.
/// </summary>
Printer,
+
/// <summary>
- /// Device share
+ /// Device share.
/// </summary>
Device,
+
/// <summary>
- /// IPC share
+ /// IPC share.
/// </summary>
Ipc,
+
/// <summary>
- /// Special share
+ /// Special share.
/// </summary>
Special
}
diff --git a/MediaBrowser.Model/Net/SocketReceiveResult.cs b/MediaBrowser.Model/Net/SocketReceiveResult.cs
index 3a4ad3738..141ae1608 100644
--- a/MediaBrowser.Model/Net/SocketReceiveResult.cs
+++ b/MediaBrowser.Model/Net/SocketReceiveResult.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
using System.Net;
namespace MediaBrowser.Model.Net
diff --git a/MediaBrowser.Model/Net/WebSocketMessage.cs b/MediaBrowser.Model/Net/WebSocketMessage.cs
index 308032f83..03f03e4cc 100644
--- a/MediaBrowser.Model/Net/WebSocketMessage.cs
+++ b/MediaBrowser.Model/Net/WebSocketMessage.cs
@@ -1,9 +1,12 @@
+
+#pragma warning disable CS1591
+
using System;
namespace MediaBrowser.Model.Net
{
/// <summary>
- /// Class WebSocketMessage
+ /// Class WebSocketMessage.
/// </summary>
/// <typeparam name="T"></typeparam>
public class WebSocketMessage<T>
diff --git a/MediaBrowser.Model/Notifications/NotificationLevel.cs b/MediaBrowser.Model/Notifications/NotificationLevel.cs
index 6a838b125..14fead3f0 100644
--- a/MediaBrowser.Model/Notifications/NotificationLevel.cs
+++ b/MediaBrowser.Model/Notifications/NotificationLevel.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
namespace MediaBrowser.Model.Notifications
{
public enum NotificationLevel
diff --git a/MediaBrowser.Model/Notifications/NotificationOption.cs b/MediaBrowser.Model/Notifications/NotificationOption.cs
index 51a07370f..4fb724515 100644
--- a/MediaBrowser.Model/Notifications/NotificationOption.cs
+++ b/MediaBrowser.Model/Notifications/NotificationOption.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
using System;
namespace MediaBrowser.Model.Notifications
diff --git a/MediaBrowser.Model/Notifications/NotificationOptions.cs b/MediaBrowser.Model/Notifications/NotificationOptions.cs
index f48b5ee7f..79a128e9b 100644
--- a/MediaBrowser.Model/Notifications/NotificationOptions.cs
+++ b/MediaBrowser.Model/Notifications/NotificationOptions.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
using System;
using MediaBrowser.Model.Extensions;
using MediaBrowser.Model.Users;
@@ -79,7 +81,7 @@ namespace MediaBrowser.Model.Notifications
{
foreach (NotificationOption i in Options)
{
- if (StringHelper.EqualsIgnoreCase(type, i.Type)) return i;
+ if (string.Equals(type, i.Type, StringComparison.OrdinalIgnoreCase)) return i;
}
return null;
}
diff --git a/MediaBrowser.Model/Notifications/NotificationRequest.cs b/MediaBrowser.Model/Notifications/NotificationRequest.cs
index 5a2634e73..ffcfab24f 100644
--- a/MediaBrowser.Model/Notifications/NotificationRequest.cs
+++ b/MediaBrowser.Model/Notifications/NotificationRequest.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
using System;
namespace MediaBrowser.Model.Notifications
diff --git a/MediaBrowser.Model/Notifications/NotificationType.cs b/MediaBrowser.Model/Notifications/NotificationType.cs
index 38b519a14..d58fbbc21 100644
--- a/MediaBrowser.Model/Notifications/NotificationType.cs
+++ b/MediaBrowser.Model/Notifications/NotificationType.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
namespace MediaBrowser.Model.Notifications
{
public enum NotificationType
diff --git a/MediaBrowser.Model/Notifications/NotificationTypeInfo.cs b/MediaBrowser.Model/Notifications/NotificationTypeInfo.cs
index ff957e644..bfa163b40 100644
--- a/MediaBrowser.Model/Notifications/NotificationTypeInfo.cs
+++ b/MediaBrowser.Model/Notifications/NotificationTypeInfo.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
namespace MediaBrowser.Model.Notifications
{
public class NotificationTypeInfo
diff --git a/MediaBrowser.Model/Notifications/SendToUserType.cs b/MediaBrowser.Model/Notifications/SendToUserType.cs
index 9f63d24bf..65fc4e1ab 100644
--- a/MediaBrowser.Model/Notifications/SendToUserType.cs
+++ b/MediaBrowser.Model/Notifications/SendToUserType.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
namespace MediaBrowser.Model.Notifications
{
public enum SendToUserType
diff --git a/MediaBrowser.Model/Playlists/PlaylistCreationRequest.cs b/MediaBrowser.Model/Playlists/PlaylistCreationRequest.cs
index 007965c0f..b7003c4c8 100644
--- a/MediaBrowser.Model/Playlists/PlaylistCreationRequest.cs
+++ b/MediaBrowser.Model/Playlists/PlaylistCreationRequest.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
using System;
namespace MediaBrowser.Model.Playlists
diff --git a/MediaBrowser.Model/Playlists/PlaylistCreationResult.cs b/MediaBrowser.Model/Playlists/PlaylistCreationResult.cs
index 301ae66a7..4f2067b98 100644
--- a/MediaBrowser.Model/Playlists/PlaylistCreationResult.cs
+++ b/MediaBrowser.Model/Playlists/PlaylistCreationResult.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
namespace MediaBrowser.Model.Playlists
{
public class PlaylistCreationResult
diff --git a/MediaBrowser.Model/Playlists/PlaylistItemQuery.cs b/MediaBrowser.Model/Playlists/PlaylistItemQuery.cs
index 1f03c14d3..324a38e70 100644
--- a/MediaBrowser.Model/Playlists/PlaylistItemQuery.cs
+++ b/MediaBrowser.Model/Playlists/PlaylistItemQuery.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
using MediaBrowser.Model.Querying;
namespace MediaBrowser.Model.Playlists
diff --git a/MediaBrowser.Model/Plugins/IHasWebPages.cs b/MediaBrowser.Model/Plugins/IHasWebPages.cs
index 5bda7e65e..765c2d373 100644
--- a/MediaBrowser.Model/Plugins/IHasWebPages.cs
+++ b/MediaBrowser.Model/Plugins/IHasWebPages.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
using System.Collections.Generic;
namespace MediaBrowser.Model.Plugins
diff --git a/MediaBrowser.Model/Plugins/PluginPageInfo.cs b/MediaBrowser.Model/Plugins/PluginPageInfo.cs
index 8ed2064b9..eb6a1527d 100644
--- a/MediaBrowser.Model/Plugins/PluginPageInfo.cs
+++ b/MediaBrowser.Model/Plugins/PluginPageInfo.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
namespace MediaBrowser.Model.Plugins
{
public class PluginPageInfo
diff --git a/MediaBrowser.Model/Providers/ExternalIdInfo.cs b/MediaBrowser.Model/Providers/ExternalIdInfo.cs
index bff84c553..2b481ad7e 100644
--- a/MediaBrowser.Model/Providers/ExternalIdInfo.cs
+++ b/MediaBrowser.Model/Providers/ExternalIdInfo.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
namespace MediaBrowser.Model.Providers
{
public class ExternalIdInfo
diff --git a/MediaBrowser.Model/Providers/ExternalUrl.cs b/MediaBrowser.Model/Providers/ExternalUrl.cs
index 69cead92a..d4f4fa840 100644
--- a/MediaBrowser.Model/Providers/ExternalUrl.cs
+++ b/MediaBrowser.Model/Providers/ExternalUrl.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
namespace MediaBrowser.Model.Providers
{
public class ExternalUrl
diff --git a/MediaBrowser.Model/Providers/ImageProviderInfo.cs b/MediaBrowser.Model/Providers/ImageProviderInfo.cs
index 1c4cff373..a22ec3c07 100644
--- a/MediaBrowser.Model/Providers/ImageProviderInfo.cs
+++ b/MediaBrowser.Model/Providers/ImageProviderInfo.cs
@@ -1,3 +1,6 @@
+#pragma warning disable CS1591
+
+using System;
using MediaBrowser.Model.Entities;
namespace MediaBrowser.Model.Providers
@@ -17,7 +20,7 @@ namespace MediaBrowser.Model.Providers
public ImageProviderInfo()
{
- SupportedImages = new ImageType[] { };
+ SupportedImages = Array.Empty<ImageType>();
}
}
}
diff --git a/MediaBrowser.Model/Providers/RemoteImageInfo.cs b/MediaBrowser.Model/Providers/RemoteImageInfo.cs
index aacd108ec..ee2b9d8fd 100644
--- a/MediaBrowser.Model/Providers/RemoteImageInfo.cs
+++ b/MediaBrowser.Model/Providers/RemoteImageInfo.cs
@@ -4,7 +4,7 @@ using MediaBrowser.Model.Entities;
namespace MediaBrowser.Model.Providers
{
/// <summary>
- /// Class RemoteImageInfo
+ /// Class RemoteImageInfo.
/// </summary>
public class RemoteImageInfo
{
@@ -21,7 +21,7 @@ namespace MediaBrowser.Model.Providers
public string Url { get; set; }
/// <summary>
- /// Gets a url used for previewing a smaller version
+ /// Gets a url used for previewing a smaller version.
/// </summary>
public string ThumbnailUrl { get; set; }
diff --git a/MediaBrowser.Model/Providers/RemoteImageQuery.cs b/MediaBrowser.Model/Providers/RemoteImageQuery.cs
index 7c9216ce7..2873c1003 100644
--- a/MediaBrowser.Model/Providers/RemoteImageQuery.cs
+++ b/MediaBrowser.Model/Providers/RemoteImageQuery.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
using MediaBrowser.Model.Entities;
namespace MediaBrowser.Model.Providers
diff --git a/MediaBrowser.Model/Providers/RemoteSearchResult.cs b/MediaBrowser.Model/Providers/RemoteSearchResult.cs
index 6e46b1556..161e04821 100644
--- a/MediaBrowser.Model/Providers/RemoteSearchResult.cs
+++ b/MediaBrowser.Model/Providers/RemoteSearchResult.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
using System;
using System.Collections.Generic;
using MediaBrowser.Model.Entities;
diff --git a/MediaBrowser.Model/Providers/RemoteSubtitleInfo.cs b/MediaBrowser.Model/Providers/RemoteSubtitleInfo.cs
index 861aabf72..06f29df3f 100644
--- a/MediaBrowser.Model/Providers/RemoteSubtitleInfo.cs
+++ b/MediaBrowser.Model/Providers/RemoteSubtitleInfo.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
using System;
namespace MediaBrowser.Model.Providers
diff --git a/MediaBrowser.Model/Providers/SubtitleOptions.cs b/MediaBrowser.Model/Providers/SubtitleOptions.cs
index fde816dd3..9e6049246 100644
--- a/MediaBrowser.Model/Providers/SubtitleOptions.cs
+++ b/MediaBrowser.Model/Providers/SubtitleOptions.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
using System;
namespace MediaBrowser.Model.Providers
diff --git a/MediaBrowser.Model/Providers/SubtitleProviderInfo.cs b/MediaBrowser.Model/Providers/SubtitleProviderInfo.cs
index 48a247818..fca93d176 100644
--- a/MediaBrowser.Model/Providers/SubtitleProviderInfo.cs
+++ b/MediaBrowser.Model/Providers/SubtitleProviderInfo.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
namespace MediaBrowser.Model.Providers
{
public class SubtitleProviderInfo
diff --git a/MediaBrowser.Model/Querying/AllThemeMediaResult.cs b/MediaBrowser.Model/Querying/AllThemeMediaResult.cs
index f843a33e6..a264c6178 100644
--- a/MediaBrowser.Model/Querying/AllThemeMediaResult.cs
+++ b/MediaBrowser.Model/Querying/AllThemeMediaResult.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
namespace MediaBrowser.Model.Querying
{
public class AllThemeMediaResult
diff --git a/MediaBrowser.Model/Querying/EpisodeQuery.cs b/MediaBrowser.Model/Querying/EpisodeQuery.cs
index 0c8ea7ed4..6fb4df676 100644
--- a/MediaBrowser.Model/Querying/EpisodeQuery.cs
+++ b/MediaBrowser.Model/Querying/EpisodeQuery.cs
@@ -1,3 +1,7 @@
+#pragma warning disable CS1591
+
+using System;
+
namespace MediaBrowser.Model.Querying
{
public class EpisodeQuery
@@ -7,46 +11,55 @@ namespace MediaBrowser.Model.Querying
/// </summary>
/// <value>The user identifier.</value>
public string UserId { get; set; }
+
/// <summary>
/// Gets or sets the season identifier.
/// </summary>
/// <value>The season identifier.</value>
public string SeasonId { get; set; }
+
/// <summary>
/// Gets or sets the series identifier.
/// </summary>
/// <value>The series identifier.</value>
public string SeriesId { get; set; }
+
/// <summary>
/// Gets or sets a value indicating whether this instance is missing.
/// </summary>
/// <value><c>null</c> if [is missing] contains no value, <c>true</c> if [is missing]; otherwise, <c>false</c>.</value>
public bool? IsMissing { get; set; }
+
/// <summary>
/// Gets or sets a value indicating whether this instance is virtual unaired.
/// </summary>
/// <value><c>null</c> if [is virtual unaired] contains no value, <c>true</c> if [is virtual unaired]; otherwise, <c>false</c>.</value>
public bool? IsVirtualUnaired { get; set; }
+
/// <summary>
/// Gets or sets the season number.
/// </summary>
/// <value>The season number.</value>
public int? SeasonNumber { get; set; }
+
/// <summary>
/// Gets or sets the fields.
/// </summary>
/// <value>The fields.</value>
public ItemFields[] Fields { get; set; }
+
/// <summary>
/// Gets or sets the start index.
/// </summary>
/// <value>The start index.</value>
public int? StartIndex { get; set; }
+
/// <summary>
/// Gets or sets the limit.
/// </summary>
/// <value>The limit.</value>
public int? Limit { get; set; }
+
/// <summary>
/// Gets or sets the start item identifier.
/// </summary>
@@ -55,7 +68,7 @@ namespace MediaBrowser.Model.Querying
public EpisodeQuery()
{
- Fields = new ItemFields[] { };
+ Fields = Array.Empty<ItemFields>();
}
}
}
diff --git a/MediaBrowser.Model/Querying/ItemCountsQuery.cs b/MediaBrowser.Model/Querying/ItemCountsQuery.cs
index 02b809fa8..f113cf380 100644
--- a/MediaBrowser.Model/Querying/ItemCountsQuery.cs
+++ b/MediaBrowser.Model/Querying/ItemCountsQuery.cs
@@ -1,7 +1,7 @@
namespace MediaBrowser.Model.Querying
{
/// <summary>
- /// Class ItemCountsQuery
+ /// Class ItemCountsQuery.
/// </summary>
public class ItemCountsQuery
{
diff --git a/MediaBrowser.Model/Querying/ItemFields.cs b/MediaBrowser.Model/Querying/ItemFields.cs
index af1aaf486..d7cc5ebbe 100644
--- a/MediaBrowser.Model/Querying/ItemFields.cs
+++ b/MediaBrowser.Model/Querying/ItemFields.cs
@@ -1,7 +1,9 @@
+#pragma warning disable CS1591
+
namespace MediaBrowser.Model.Querying
{
/// <summary>
- /// Used to control the data that gets attached to DtoBaseItems
+ /// Used to control the data that gets attached to DtoBaseItems.
/// </summary>
public enum ItemFields
{
diff --git a/MediaBrowser.Model/Querying/ItemFilter.cs b/MediaBrowser.Model/Querying/ItemFilter.cs
index b8dcfbdad..0ebb5185f 100644
--- a/MediaBrowser.Model/Querying/ItemFilter.cs
+++ b/MediaBrowser.Model/Querying/ItemFilter.cs
@@ -1,44 +1,52 @@
namespace MediaBrowser.Model.Querying
{
/// <summary>
- /// Enum ItemFilter
+ /// Enum ItemFilter.
/// </summary>
public enum ItemFilter
{
/// <summary>
- /// The item is a folder
+ /// The item is a folder.
/// </summary>
IsFolder = 1,
+
/// <summary>
- /// The item is not folder
+ /// The item is not folder.
/// </summary>
IsNotFolder = 2,
+
/// <summary>
- /// The item is unplayed
+ /// The item is unplayed.
/// </summary>
IsUnplayed = 3,
+
/// <summary>
- /// The item is played
+ /// The item is played.
/// </summary>
IsPlayed = 4,
+
/// <summary>
- /// The item is a favorite
+ /// The item is a favorite.
/// </summary>
IsFavorite = 5,
+
/// <summary>
- /// The item is resumable
+ /// The item is resumable.
/// </summary>
IsResumable = 7,
+
/// <summary>
- /// The likes
+ /// The likes.
/// </summary>
Likes = 8,
+
/// <summary>
- /// The dislikes
+ /// The dislikes.
/// </summary>
Dislikes = 9,
+
/// <summary>
- /// The is favorite or likes
+ /// The is favorite or likes.
/// </summary>
IsFavoriteOrLikes = 10
}
diff --git a/MediaBrowser.Model/Querying/ItemSortBy.cs b/MediaBrowser.Model/Querying/ItemSortBy.cs
index 6a71e3bb3..15b60ad84 100644
--- a/MediaBrowser.Model/Querying/ItemSortBy.cs
+++ b/MediaBrowser.Model/Querying/ItemSortBy.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
namespace MediaBrowser.Model.Querying
{
/// <summary>
diff --git a/MediaBrowser.Model/Querying/LatestItemsQuery.cs b/MediaBrowser.Model/Querying/LatestItemsQuery.cs
index 4a5818ac5..84e29e76a 100644
--- a/MediaBrowser.Model/Querying/LatestItemsQuery.cs
+++ b/MediaBrowser.Model/Querying/LatestItemsQuery.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
using System;
using MediaBrowser.Model.Entities;
diff --git a/MediaBrowser.Model/Querying/MovieRecommendationQuery.cs b/MediaBrowser.Model/Querying/MovieRecommendationQuery.cs
index 52c138355..93de0a8cd 100644
--- a/MediaBrowser.Model/Querying/MovieRecommendationQuery.cs
+++ b/MediaBrowser.Model/Querying/MovieRecommendationQuery.cs
@@ -1,3 +1,7 @@
+#pragma warning disable CS1591
+
+using System;
+
namespace MediaBrowser.Model.Querying
{
public class MovieRecommendationQuery
@@ -7,21 +11,25 @@ namespace MediaBrowser.Model.Querying
/// </summary>
/// <value>The user identifier.</value>
public string UserId { get; set; }
+
/// <summary>
/// Gets or sets the parent identifier.
/// </summary>
/// <value>The parent identifier.</value>
public string ParentId { get; set; }
+
/// <summary>
/// Gets or sets the item limit.
/// </summary>
/// <value>The item limit.</value>
public int ItemLimit { get; set; }
+
/// <summary>
/// Gets or sets the category limit.
/// </summary>
/// <value>The category limit.</value>
public int CategoryLimit { get; set; }
+
/// <summary>
/// Gets or sets the fields.
/// </summary>
@@ -32,7 +40,7 @@ namespace MediaBrowser.Model.Querying
{
ItemLimit = 10;
CategoryLimit = 6;
- Fields = new ItemFields[] { };
+ Fields = Array.Empty<ItemFields>();
}
}
}
diff --git a/MediaBrowser.Model/Querying/NextUpQuery.cs b/MediaBrowser.Model/Querying/NextUpQuery.cs
index ff146cede..1543aea16 100644
--- a/MediaBrowser.Model/Querying/NextUpQuery.cs
+++ b/MediaBrowser.Model/Querying/NextUpQuery.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
using System;
using MediaBrowser.Model.Entities;
@@ -40,16 +42,19 @@ namespace MediaBrowser.Model.Querying
/// </summary>
/// <value>The fields.</value>
public ItemFields[] Fields { get; set; }
+
/// <summary>
/// Gets or sets a value indicating whether [enable images].
/// </summary>
/// <value><c>null</c> if [enable images] contains no value, <c>true</c> if [enable images]; otherwise, <c>false</c>.</value>
public bool? EnableImages { get; set; }
+
/// <summary>
/// Gets or sets the image type limit.
/// </summary>
/// <value>The image type limit.</value>
public int? ImageTypeLimit { get; set; }
+
/// <summary>
/// Gets or sets the enable image types.
/// </summary>
@@ -60,7 +65,7 @@ namespace MediaBrowser.Model.Querying
public NextUpQuery()
{
- EnableImageTypes = new ImageType[] { };
+ EnableImageTypes = Array.Empty<ImageType>();
EnableTotalRecordCount = true;
}
}
diff --git a/MediaBrowser.Model/Querying/QueryFilters.cs b/MediaBrowser.Model/Querying/QueryFilters.cs
index 2f38299db..8d879c174 100644
--- a/MediaBrowser.Model/Querying/QueryFilters.cs
+++ b/MediaBrowser.Model/Querying/QueryFilters.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
using System;
using MediaBrowser.Model.Dto;
diff --git a/MediaBrowser.Model/Querying/QueryResult.cs b/MediaBrowser.Model/Querying/QueryResult.cs
index 221645afb..266f1c7e6 100644
--- a/MediaBrowser.Model/Querying/QueryResult.cs
+++ b/MediaBrowser.Model/Querying/QueryResult.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
using System;
using System.Collections.Generic;
@@ -27,5 +29,11 @@ namespace MediaBrowser.Model.Querying
{
Items = Array.Empty<T>();
}
+
+ public QueryResult(IReadOnlyList<T> items)
+ {
+ Items = items;
+ TotalRecordCount = items.Count;
+ }
}
}
diff --git a/MediaBrowser.Model/Querying/SessionQuery.cs b/MediaBrowser.Model/Querying/SessionQuery.cs
deleted file mode 100644
index 1fac9d639..000000000
--- a/MediaBrowser.Model/Querying/SessionQuery.cs
+++ /dev/null
@@ -1,13 +0,0 @@
-namespace MediaBrowser.Model.Querying
-{
- /// <summary>
- /// Class SessionQuery
- /// </summary>
- public class SessionQuery
- {
- /// <summary>
- /// Filter by sessions that are allowed to be controlled by a given user
- /// </summary>
- public string ControllableByUserId { get; set; }
- }
-}
diff --git a/MediaBrowser.Model/Querying/SimilarItemsQuery.cs b/MediaBrowser.Model/Querying/SimilarItemsQuery.cs
deleted file mode 100644
index 68f761bd4..000000000
--- a/MediaBrowser.Model/Querying/SimilarItemsQuery.cs
+++ /dev/null
@@ -1,29 +0,0 @@
-namespace MediaBrowser.Model.Querying
-{
- public class SimilarItemsQuery
- {
- /// <summary>
- /// The user to localize search results for
- /// </summary>
- /// <value>The user id.</value>
- public string UserId { get; set; }
-
- /// <summary>
- /// Gets or sets the id.
- /// </summary>
- /// <value>The id.</value>
- public string Id { get; set; }
-
- /// <summary>
- /// The maximum number of items to return
- /// </summary>
- /// <value>The limit.</value>
- public int? Limit { get; set; }
-
- /// <summary>
- /// Fields to return within the items, in addition to basic information
- /// </summary>
- /// <value>The fields.</value>
- public ItemFields[] Fields { get; set; }
- }
-}
diff --git a/MediaBrowser.Model/Querying/UpcomingEpisodesQuery.cs b/MediaBrowser.Model/Querying/UpcomingEpisodesQuery.cs
index 5eac2860d..123d0fad2 100644
--- a/MediaBrowser.Model/Querying/UpcomingEpisodesQuery.cs
+++ b/MediaBrowser.Model/Querying/UpcomingEpisodesQuery.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
using MediaBrowser.Model.Entities;
namespace MediaBrowser.Model.Querying
diff --git a/MediaBrowser.Model/Querying/UserQuery.cs b/MediaBrowser.Model/Querying/UserQuery.cs
deleted file mode 100644
index 55cef664e..000000000
--- a/MediaBrowser.Model/Querying/UserQuery.cs
+++ /dev/null
@@ -1,8 +0,0 @@
-namespace MediaBrowser.Model.Querying
-{
- public class UserQuery
- {
- public bool? IsHidden { get; set; }
- public bool? IsDisabled { get; set; }
- }
-}
diff --git a/MediaBrowser.Model/Search/SearchHint.cs b/MediaBrowser.Model/Search/SearchHint.cs
index 8f4824903..6e52314fa 100644
--- a/MediaBrowser.Model/Search/SearchHint.cs
+++ b/MediaBrowser.Model/Search/SearchHint.cs
@@ -1,10 +1,12 @@
+#pragma warning disable CS1591
+
using System;
using System.Collections.Generic;
namespace MediaBrowser.Model.Search
{
/// <summary>
- /// Class SearchHintResult
+ /// Class SearchHintResult.
/// </summary>
public class SearchHint
{
diff --git a/MediaBrowser.Model/Search/SearchHintResult.cs b/MediaBrowser.Model/Search/SearchHintResult.cs
index 069e7e025..3c4fbec9e 100644
--- a/MediaBrowser.Model/Search/SearchHintResult.cs
+++ b/MediaBrowser.Model/Search/SearchHintResult.cs
@@ -1,7 +1,7 @@
namespace MediaBrowser.Model.Search
{
/// <summary>
- /// Class SearchHintResult
+ /// Class SearchHintResult.
/// </summary>
public class SearchHintResult
{
diff --git a/MediaBrowser.Model/Search/SearchQuery.cs b/MediaBrowser.Model/Search/SearchQuery.cs
index 96a8cb00c..8a018312e 100644
--- a/MediaBrowser.Model/Search/SearchQuery.cs
+++ b/MediaBrowser.Model/Search/SearchQuery.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
using System;
namespace MediaBrowser.Model.Search
diff --git a/MediaBrowser.Model/Serialization/IJsonSerializer.cs b/MediaBrowser.Model/Serialization/IJsonSerializer.cs
index 18f51f652..6223bb559 100644
--- a/MediaBrowser.Model/Serialization/IJsonSerializer.cs
+++ b/MediaBrowser.Model/Serialization/IJsonSerializer.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
using System;
using System.IO;
using System.Threading.Tasks;
diff --git a/MediaBrowser.Model/Serialization/IXmlSerializer.cs b/MediaBrowser.Model/Serialization/IXmlSerializer.cs
index 902ebd4d1..1edd98fad 100644
--- a/MediaBrowser.Model/Serialization/IXmlSerializer.cs
+++ b/MediaBrowser.Model/Serialization/IXmlSerializer.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
using System;
using System.IO;
diff --git a/MediaBrowser.Model/Services/ApiMemberAttribute.cs b/MediaBrowser.Model/Services/ApiMemberAttribute.cs
index 8b155c8ab..8e50836f4 100644
--- a/MediaBrowser.Model/Services/ApiMemberAttribute.cs
+++ b/MediaBrowser.Model/Services/ApiMemberAttribute.cs
@@ -2,6 +2,9 @@ using System;
namespace MediaBrowser.Model.Services
{
+ /// <summary>
+ /// Identifies a single API endpoint.
+ /// </summary>
[AttributeUsage(AttributeTargets.Property, AllowMultiple = true, Inherited = true)]
public class ApiMemberAttribute : Attribute
{
diff --git a/MediaBrowser.Model/Services/IAsyncStreamWriter.cs b/MediaBrowser.Model/Services/IAsyncStreamWriter.cs
index f16a877e6..afbca78a2 100644
--- a/MediaBrowser.Model/Services/IAsyncStreamWriter.cs
+++ b/MediaBrowser.Model/Services/IAsyncStreamWriter.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
using System.IO;
using System.Threading;
using System.Threading.Tasks;
diff --git a/MediaBrowser.Model/Services/IHasHeaders.cs b/MediaBrowser.Model/Services/IHasHeaders.cs
index b2d413b70..313f34b41 100644
--- a/MediaBrowser.Model/Services/IHasHeaders.cs
+++ b/MediaBrowser.Model/Services/IHasHeaders.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
using System.Collections.Generic;
namespace MediaBrowser.Model.Services
diff --git a/MediaBrowser.Model/Services/IHasRequestFilter.cs b/MediaBrowser.Model/Services/IHasRequestFilter.cs
index 81a2dba69..3e49e9872 100644
--- a/MediaBrowser.Model/Services/IHasRequestFilter.cs
+++ b/MediaBrowser.Model/Services/IHasRequestFilter.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
using Microsoft.AspNetCore.Http;
namespace MediaBrowser.Model.Services
@@ -6,17 +8,17 @@ namespace MediaBrowser.Model.Services
{
/// <summary>
/// Order in which Request Filters are executed.
- /// &lt;0 Executed before global request filters
- /// &gt;0 Executed after global request filters
+ /// &lt;0 Executed before global request filters.
+ /// &gt;0 Executed after global request filters.
/// </summary>
int Priority { get; }
/// <summary>
/// The request filter is executed before the service.
/// </summary>
- /// <param name="req">The http request wrapper</param>
- /// <param name="res">The http response wrapper</param>
- /// <param name="requestDto">The request DTO</param>
+ /// <param name="req">The http request wrapper.</param>
+ /// <param name="res">The http response wrapper.</param>
+ /// <param name="requestDto">The request DTO.</param>
void RequestFilter(IRequest req, HttpResponse res, object requestDto);
}
}
diff --git a/MediaBrowser.Model/Services/IHttpRequest.cs b/MediaBrowser.Model/Services/IHttpRequest.cs
index daf91488f..4dccd2d68 100644
--- a/MediaBrowser.Model/Services/IHttpRequest.cs
+++ b/MediaBrowser.Model/Services/IHttpRequest.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
namespace MediaBrowser.Model.Services
{
public interface IHttpRequest : IRequest
diff --git a/MediaBrowser.Model/Services/IHttpResult.cs b/MediaBrowser.Model/Services/IHttpResult.cs
index bfa30f60d..b153f15ec 100644
--- a/MediaBrowser.Model/Services/IHttpResult.cs
+++ b/MediaBrowser.Model/Services/IHttpResult.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
using System.Net;
namespace MediaBrowser.Model.Services
diff --git a/MediaBrowser.Model/Services/IRequest.cs b/MediaBrowser.Model/Services/IRequest.cs
index 7a4152698..3f4edced6 100644
--- a/MediaBrowser.Model/Services/IRequest.cs
+++ b/MediaBrowser.Model/Services/IRequest.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
using System;
using System.Collections.Generic;
using System.IO;
diff --git a/MediaBrowser.Model/Services/IRequiresRequestStream.cs b/MediaBrowser.Model/Services/IRequiresRequestStream.cs
index 2f17c6a9a..622626edc 100644
--- a/MediaBrowser.Model/Services/IRequiresRequestStream.cs
+++ b/MediaBrowser.Model/Services/IRequiresRequestStream.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
using System.IO;
namespace MediaBrowser.Model.Services
diff --git a/MediaBrowser.Model/Services/IService.cs b/MediaBrowser.Model/Services/IService.cs
index 8f2e63e98..a26d39455 100644
--- a/MediaBrowser.Model/Services/IService.cs
+++ b/MediaBrowser.Model/Services/IService.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
namespace MediaBrowser.Model.Services
{
// marker interface
diff --git a/MediaBrowser.Model/Services/IStreamWriter.cs b/MediaBrowser.Model/Services/IStreamWriter.cs
index 9d65cff63..3ebfef66b 100644
--- a/MediaBrowser.Model/Services/IStreamWriter.cs
+++ b/MediaBrowser.Model/Services/IStreamWriter.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
using System.IO;
namespace MediaBrowser.Model.Services
diff --git a/MediaBrowser.Model/Services/QueryParamCollection.cs b/MediaBrowser.Model/Services/QueryParamCollection.cs
index 7708db00a..19e9e53e7 100644
--- a/MediaBrowser.Model/Services/QueryParamCollection.cs
+++ b/MediaBrowser.Model/Services/QueryParamCollection.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
using System;
using System.Collections.Generic;
using System.Linq;
diff --git a/MediaBrowser.Model/Services/RouteAttribute.cs b/MediaBrowser.Model/Services/RouteAttribute.cs
index f6316e2b1..197ba05e5 100644
--- a/MediaBrowser.Model/Services/RouteAttribute.cs
+++ b/MediaBrowser.Model/Services/RouteAttribute.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
using System;
namespace MediaBrowser.Model.Services
diff --git a/MediaBrowser.Model/Session/BrowseRequest.cs b/MediaBrowser.Model/Session/BrowseRequest.cs
index 65d58501b..f485d680e 100644
--- a/MediaBrowser.Model/Session/BrowseRequest.cs
+++ b/MediaBrowser.Model/Session/BrowseRequest.cs
@@ -1,12 +1,12 @@
namespace MediaBrowser.Model.Session
{
/// <summary>
- /// Class BrowseRequest
+ /// Class BrowseRequest.
/// </summary>
public class BrowseRequest
{
/// <summary>
- /// Artist, Genre, Studio, Person, or any kind of BaseItem
+ /// Artist, Genre, Studio, Person, or any kind of BaseItem.
/// </summary>
/// <value>The type of the item.</value>
public string ItemType { get; set; }
diff --git a/MediaBrowser.Model/Session/ClientCapabilities.cs b/MediaBrowser.Model/Session/ClientCapabilities.cs
index fa74efb1b..5da4998e8 100644
--- a/MediaBrowser.Model/Session/ClientCapabilities.cs
+++ b/MediaBrowser.Model/Session/ClientCapabilities.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
using System;
using MediaBrowser.Model.Dlna;
diff --git a/MediaBrowser.Model/Session/GeneralCommand.cs b/MediaBrowser.Model/Session/GeneralCommand.cs
index 74e58e678..980e1f88b 100644
--- a/MediaBrowser.Model/Session/GeneralCommand.cs
+++ b/MediaBrowser.Model/Session/GeneralCommand.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
using System;
using System.Collections.Generic;
diff --git a/MediaBrowser.Model/Session/GeneralCommandType.cs b/MediaBrowser.Model/Session/GeneralCommandType.cs
index 4bb0c5cc5..5a9042d5f 100644
--- a/MediaBrowser.Model/Session/GeneralCommandType.cs
+++ b/MediaBrowser.Model/Session/GeneralCommandType.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
namespace MediaBrowser.Model.Session
{
/// <summary>
diff --git a/MediaBrowser.Model/Session/MessageCommand.cs b/MediaBrowser.Model/Session/MessageCommand.cs
index 1e558ef07..473a7bccc 100644
--- a/MediaBrowser.Model/Session/MessageCommand.cs
+++ b/MediaBrowser.Model/Session/MessageCommand.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
namespace MediaBrowser.Model.Session
{
public class MessageCommand
diff --git a/MediaBrowser.Model/Session/PlayCommand.cs b/MediaBrowser.Model/Session/PlayCommand.cs
index b7a8f39ba..3ab049320 100644
--- a/MediaBrowser.Model/Session/PlayCommand.cs
+++ b/MediaBrowser.Model/Session/PlayCommand.cs
@@ -1,28 +1,32 @@
namespace MediaBrowser.Model.Session
{
/// <summary>
- /// Enum PlayCommand
+ /// Enum PlayCommand.
/// </summary>
public enum PlayCommand
{
/// <summary>
- /// The play now
+ /// The play now.
/// </summary>
PlayNow = 0,
+
/// <summary>
- /// The play next
+ /// The play next.
/// </summary>
PlayNext = 1,
+
/// <summary>
- /// The play last
+ /// The play last.
/// </summary>
PlayLast = 2,
+
/// <summary>
- /// The play instant mix
+ /// The play instant mix.
/// </summary>
PlayInstantMix = 3,
+
/// <summary>
- /// The play shuffle
+ /// The play shuffle.
/// </summary>
PlayShuffle = 4
}
diff --git a/MediaBrowser.Model/Session/PlayMethod.cs b/MediaBrowser.Model/Session/PlayMethod.cs
index 8daf8c953..806762784 100644
--- a/MediaBrowser.Model/Session/PlayMethod.cs
+++ b/MediaBrowser.Model/Session/PlayMethod.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
namespace MediaBrowser.Model.Session
{
public enum PlayMethod
diff --git a/MediaBrowser.Model/Session/PlayRequest.cs b/MediaBrowser.Model/Session/PlayRequest.cs
index 075ae7730..bdb2b2439 100644
--- a/MediaBrowser.Model/Session/PlayRequest.cs
+++ b/MediaBrowser.Model/Session/PlayRequest.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
using System;
using MediaBrowser.Model.Services;
diff --git a/MediaBrowser.Model/Session/PlaybackProgressInfo.cs b/MediaBrowser.Model/Session/PlaybackProgressInfo.cs
index c1d630671..5687ba84b 100644
--- a/MediaBrowser.Model/Session/PlaybackProgressInfo.cs
+++ b/MediaBrowser.Model/Session/PlaybackProgressInfo.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
using System;
using MediaBrowser.Model.Dto;
diff --git a/MediaBrowser.Model/Session/PlaybackStopInfo.cs b/MediaBrowser.Model/Session/PlaybackStopInfo.cs
index 8a85b1998..f8cfacc20 100644
--- a/MediaBrowser.Model/Session/PlaybackStopInfo.cs
+++ b/MediaBrowser.Model/Session/PlaybackStopInfo.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
using System;
using MediaBrowser.Model.Dto;
@@ -13,36 +15,43 @@ namespace MediaBrowser.Model.Session
/// </summary>
/// <value>The item.</value>
public BaseItemDto Item { get; set; }
+
/// <summary>
/// Gets or sets the item identifier.
/// </summary>
/// <value>The item identifier.</value>
public Guid ItemId { get; set; }
+
/// <summary>
/// Gets or sets the session id.
/// </summary>
/// <value>The session id.</value>
public string SessionId { get; set; }
+
/// <summary>
/// Gets or sets the media version identifier.
/// </summary>
/// <value>The media version identifier.</value>
public string MediaSourceId { get; set; }
+
/// <summary>
/// Gets or sets the position ticks.
/// </summary>
/// <value>The position ticks.</value>
public long? PositionTicks { get; set; }
+
/// <summary>
/// Gets or sets the live stream identifier.
/// </summary>
/// <value>The live stream identifier.</value>
public string LiveStreamId { get; set; }
+
/// <summary>
/// Gets or sets the play session identifier.
/// </summary>
/// <value>The play session identifier.</value>
public string PlaySessionId { get; set; }
+
/// <summary>
/// Gets or sets a value indicating whether this <see cref="PlaybackStopInfo"/> is failed.
/// </summary>
diff --git a/MediaBrowser.Model/Session/PlayerStateInfo.cs b/MediaBrowser.Model/Session/PlayerStateInfo.cs
index 7e54e16c8..0f9956873 100644
--- a/MediaBrowser.Model/Session/PlayerStateInfo.cs
+++ b/MediaBrowser.Model/Session/PlayerStateInfo.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
namespace MediaBrowser.Model.Session
{
public class PlayerStateInfo
diff --git a/MediaBrowser.Model/Session/PlaystateCommand.cs b/MediaBrowser.Model/Session/PlaystateCommand.cs
index 6eb3e53c2..3aa091f79 100644
--- a/MediaBrowser.Model/Session/PlaystateCommand.cs
+++ b/MediaBrowser.Model/Session/PlaystateCommand.cs
@@ -1,40 +1,49 @@
+#pragma warning disable CS1591
+
namespace MediaBrowser.Model.Session
{
/// <summary>
- /// Enum PlaystateCommand
+ /// Enum PlaystateCommand.
/// </summary>
public enum PlaystateCommand
{
/// <summary>
- /// The stop
+ /// The stop.
/// </summary>
Stop,
+
/// <summary>
- /// The pause
+ /// The pause.
/// </summary>
Pause,
+
/// <summary>
- /// The unpause
+ /// The unpause.
/// </summary>
Unpause,
+
/// <summary>
- /// The next track
+ /// The next track.
/// </summary>
NextTrack,
+
/// <summary>
- /// The previous track
+ /// The previous track.
/// </summary>
PreviousTrack,
+
/// <summary>
- /// The seek
+ /// The seek.
/// </summary>
Seek,
+
/// <summary>
- /// The rewind
+ /// The rewind.
/// </summary>
Rewind,
+
/// <summary>
- /// The fast forward
+ /// The fast forward.
/// </summary>
FastForward,
PlayPause
diff --git a/MediaBrowser.Model/Session/PlaystateRequest.cs b/MediaBrowser.Model/Session/PlaystateRequest.cs
index 08d3f0072..493a8063a 100644
--- a/MediaBrowser.Model/Session/PlaystateRequest.cs
+++ b/MediaBrowser.Model/Session/PlaystateRequest.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
namespace MediaBrowser.Model.Session
{
public class PlaystateRequest
diff --git a/MediaBrowser.Model/Session/TranscodingInfo.cs b/MediaBrowser.Model/Session/TranscodingInfo.cs
index 5161882fd..8f4e688f0 100644
--- a/MediaBrowser.Model/Session/TranscodingInfo.cs
+++ b/MediaBrowser.Model/Session/TranscodingInfo.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
namespace MediaBrowser.Model.Session
{
public class TranscodingInfo
diff --git a/MediaBrowser.Model/Session/UserDataChangeInfo.cs b/MediaBrowser.Model/Session/UserDataChangeInfo.cs
index ef0e2c89a..0872eb4b1 100644
--- a/MediaBrowser.Model/Session/UserDataChangeInfo.cs
+++ b/MediaBrowser.Model/Session/UserDataChangeInfo.cs
@@ -3,7 +3,7 @@ using MediaBrowser.Model.Dto;
namespace MediaBrowser.Model.Session
{
/// <summary>
- /// Class UserDataChangeInfo
+ /// Class UserDataChangeInfo.
/// </summary>
public class UserDataChangeInfo
{
diff --git a/MediaBrowser.Model/Sync/SyncCategory.cs b/MediaBrowser.Model/Sync/SyncCategory.cs
index 637c5ba74..215ac301e 100644
--- a/MediaBrowser.Model/Sync/SyncCategory.cs
+++ b/MediaBrowser.Model/Sync/SyncCategory.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
namespace MediaBrowser.Model.Sync
{
public enum SyncCategory
diff --git a/MediaBrowser.Model/Sync/SyncJob.cs b/MediaBrowser.Model/Sync/SyncJob.cs
index 7a1f76fe9..30bf27f38 100644
--- a/MediaBrowser.Model/Sync/SyncJob.cs
+++ b/MediaBrowser.Model/Sync/SyncJob.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
using System;
namespace MediaBrowser.Model.Sync
@@ -9,91 +11,109 @@ namespace MediaBrowser.Model.Sync
/// </summary>
/// <value>The identifier.</value>
public string Id { get; set; }
+
/// <summary>
/// Gets or sets the device identifier.
/// </summary>
/// <value>The device identifier.</value>
public string TargetId { get; set; }
+
/// <summary>
/// Gets or sets the name of the target.
/// </summary>
/// <value>The name of the target.</value>
public string TargetName { get; set; }
+
/// <summary>
/// Gets or sets the quality.
/// </summary>
/// <value>The quality.</value>
public string Quality { get; set; }
+
/// <summary>
/// Gets or sets the bitrate.
/// </summary>
/// <value>The bitrate.</value>
public int? Bitrate { get; set; }
+
/// <summary>
/// Gets or sets the profile.
/// </summary>
/// <value>The profile.</value>
public string Profile { get; set; }
+
/// <summary>
/// Gets or sets the category.
/// </summary>
/// <value>The category.</value>
public SyncCategory? Category { get; set; }
+
/// <summary>
/// Gets or sets the parent identifier.
/// </summary>
/// <value>The parent identifier.</value>
public string ParentId { get; set; }
+
/// <summary>
/// Gets or sets the current progress.
/// </summary>
/// <value>The current progress.</value>
public double? Progress { get; set; }
+
/// <summary>
/// Gets or sets the name.
/// </summary>
/// <value>The name.</value>
public string Name { get; set; }
+
/// <summary>
/// Gets or sets the status.
/// </summary>
/// <value>The status.</value>
public SyncJobStatus Status { get; set; }
+
/// <summary>
/// Gets or sets the user identifier.
/// </summary>
/// <value>The user identifier.</value>
public string UserId { get; set; }
+
/// <summary>
/// Gets or sets a value indicating whether [unwatched only].
/// </summary>
/// <value><c>true</c> if [unwatched only]; otherwise, <c>false</c>.</value>
public bool UnwatchedOnly { get; set; }
+
/// <summary>
/// Gets or sets a value indicating whether [synchronize new content].
/// </summary>
/// <value><c>true</c> if [synchronize new content]; otherwise, <c>false</c>.</value>
public bool SyncNewContent { get; set; }
+
/// <summary>
/// Gets or sets the item limit.
/// </summary>
/// <value>The item limit.</value>
public int? ItemLimit { get; set; }
+
/// <summary>
/// Gets or sets the requested item ids.
/// </summary>
/// <value>The requested item ids.</value>
public Guid[] RequestedItemIds { get; set; }
+
/// <summary>
/// Gets or sets the date created.
/// </summary>
/// <value>The date created.</value>
public DateTime DateCreated { get; set; }
+
/// <summary>
/// Gets or sets the date last modified.
/// </summary>
/// <value>The date last modified.</value>
public DateTime DateLastModified { get; set; }
+
/// <summary>
/// Gets or sets the item count.
/// </summary>
diff --git a/MediaBrowser.Model/Sync/SyncJobStatus.cs b/MediaBrowser.Model/Sync/SyncJobStatus.cs
index 4ea3d3fa5..226a47d4c 100644
--- a/MediaBrowser.Model/Sync/SyncJobStatus.cs
+++ b/MediaBrowser.Model/Sync/SyncJobStatus.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
namespace MediaBrowser.Model.Sync
{
public enum SyncJobStatus
diff --git a/MediaBrowser.Model/Sync/SyncTarget.cs b/MediaBrowser.Model/Sync/SyncTarget.cs
index a94bf9a25..20a0c8cc7 100644
--- a/MediaBrowser.Model/Sync/SyncTarget.cs
+++ b/MediaBrowser.Model/Sync/SyncTarget.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
namespace MediaBrowser.Model.Sync
{
public class SyncTarget
@@ -7,6 +9,7 @@ namespace MediaBrowser.Model.Sync
/// </summary>
/// <value>The name.</value>
public string Name { get; set; }
+
/// <summary>
/// Gets or sets the identifier.
/// </summary>
diff --git a/MediaBrowser.Model/System/LogFile.cs b/MediaBrowser.Model/System/LogFile.cs
index 913e8e1ea..a2b701664 100644
--- a/MediaBrowser.Model/System/LogFile.cs
+++ b/MediaBrowser.Model/System/LogFile.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
using System;
namespace MediaBrowser.Model.System
diff --git a/MediaBrowser.Model/System/OperatingSystemId.cs b/MediaBrowser.Model/System/OperatingSystemId.cs
index e81dd4213..2e417f6b5 100644
--- a/MediaBrowser.Model/System/OperatingSystemId.cs
+++ b/MediaBrowser.Model/System/OperatingSystemId.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
namespace MediaBrowser.Model.System
{
public enum OperatingSystemId
diff --git a/MediaBrowser.Model/System/PublicSystemInfo.cs b/MediaBrowser.Model/System/PublicSystemInfo.cs
index 23f6d378c..1775470b5 100644
--- a/MediaBrowser.Model/System/PublicSystemInfo.cs
+++ b/MediaBrowser.Model/System/PublicSystemInfo.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
namespace MediaBrowser.Model.System
{
public class PublicSystemInfo
diff --git a/MediaBrowser.Model/System/SystemInfo.cs b/MediaBrowser.Model/System/SystemInfo.cs
index 7014a5c87..3d543039e 100644
--- a/MediaBrowser.Model/System/SystemInfo.cs
+++ b/MediaBrowser.Model/System/SystemInfo.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
using System;
using System.Runtime.InteropServices;
using MediaBrowser.Model.Updates;
@@ -24,8 +26,6 @@ namespace MediaBrowser.Model.System
/// </summary>
public class SystemInfo : PublicSystemInfo
{
- public PackageVersionClass SystemUpdateLevel { get; set; }
-
/// <summary>
/// Gets or sets the display name of the operating system.
/// </summary>
diff --git a/MediaBrowser.Model/Tasks/IConfigurableScheduledTask.cs b/MediaBrowser.Model/Tasks/IConfigurableScheduledTask.cs
index 9c4b75c54..fbfaed22e 100644
--- a/MediaBrowser.Model/Tasks/IConfigurableScheduledTask.cs
+++ b/MediaBrowser.Model/Tasks/IConfigurableScheduledTask.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
namespace MediaBrowser.Model.Tasks
{
public interface IConfigurableScheduledTask
diff --git a/MediaBrowser.Model/Tasks/IScheduledTask.cs b/MediaBrowser.Model/Tasks/IScheduledTask.cs
index a615ebb07..ed160e176 100644
--- a/MediaBrowser.Model/Tasks/IScheduledTask.cs
+++ b/MediaBrowser.Model/Tasks/IScheduledTask.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
using System;
using System.Collections.Generic;
using System.Threading;
@@ -39,9 +41,9 @@ namespace MediaBrowser.Model.Tasks
Task Execute(CancellationToken cancellationToken, IProgress<double> progress);
/// <summary>
- /// Gets the default triggers.
+ /// Gets the default triggers that define when the task will run.
/// </summary>
- /// <returns>IEnumerable{BaseTaskTrigger}.</returns>
+ /// <returns>The default triggers that define when the task will run.</returns>
IEnumerable<TaskTriggerInfo> GetDefaultTriggers();
}
}
diff --git a/MediaBrowser.Model/Tasks/IScheduledTaskWorker.cs b/MediaBrowser.Model/Tasks/IScheduledTaskWorker.cs
index 61e3a65eb..4dd1bb5d0 100644
--- a/MediaBrowser.Model/Tasks/IScheduledTaskWorker.cs
+++ b/MediaBrowser.Model/Tasks/IScheduledTaskWorker.cs
@@ -4,7 +4,7 @@ using MediaBrowser.Model.Events;
namespace MediaBrowser.Model.Tasks
{
/// <summary>
- /// Interface IScheduledTaskWorker
+ /// Interface IScheduledTaskWorker.
/// </summary>
public interface IScheduledTaskWorker : IDisposable
{
diff --git a/MediaBrowser.Model/Tasks/ITaskManager.cs b/MediaBrowser.Model/Tasks/ITaskManager.cs
index 57b8d1af8..4a7f579ec 100644
--- a/MediaBrowser.Model/Tasks/ITaskManager.cs
+++ b/MediaBrowser.Model/Tasks/ITaskManager.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
diff --git a/MediaBrowser.Model/Tasks/ITaskTrigger.cs b/MediaBrowser.Model/Tasks/ITaskTrigger.cs
index c8433ed21..5c30d6c22 100644
--- a/MediaBrowser.Model/Tasks/ITaskTrigger.cs
+++ b/MediaBrowser.Model/Tasks/ITaskTrigger.cs
@@ -4,12 +4,12 @@ using Microsoft.Extensions.Logging;
namespace MediaBrowser.Model.Tasks
{
/// <summary>
- /// Interface ITaskTrigger
+ /// Interface ITaskTrigger.
/// </summary>
public interface ITaskTrigger
{
/// <summary>
- /// Fires when the trigger condition is satisfied and the task should run
+ /// Fires when the trigger condition is satisfied and the task should run.
/// </summary>
event EventHandler<EventArgs> Triggered;
@@ -19,12 +19,12 @@ namespace MediaBrowser.Model.Tasks
TaskOptions TaskOptions { get; set; }
/// <summary>
- /// Stars waiting for the trigger action
+ /// Stars waiting for the trigger action.
/// </summary>
void Start(TaskResult lastResult, ILogger logger, string taskName, bool isApplicationStartup);
/// <summary>
- /// Stops waiting for the trigger action
+ /// Stops waiting for the trigger action.
/// </summary>
void Stop();
}
diff --git a/MediaBrowser.Model/Tasks/ScheduledTaskHelpers.cs b/MediaBrowser.Model/Tasks/ScheduledTaskHelpers.cs
index e461e4a4b..ca0743cca 100644
--- a/MediaBrowser.Model/Tasks/ScheduledTaskHelpers.cs
+++ b/MediaBrowser.Model/Tasks/ScheduledTaskHelpers.cs
@@ -1,7 +1,7 @@
namespace MediaBrowser.Model.Tasks
{
/// <summary>
- /// Class ScheduledTaskHelpers
+ /// Class ScheduledTaskHelpers.
/// </summary>
public static class ScheduledTaskHelpers
{
diff --git a/MediaBrowser.Model/Tasks/TaskCompletionEventArgs.cs b/MediaBrowser.Model/Tasks/TaskCompletionEventArgs.cs
index 05eaff8da..cc6c2b62b 100644
--- a/MediaBrowser.Model/Tasks/TaskCompletionEventArgs.cs
+++ b/MediaBrowser.Model/Tasks/TaskCompletionEventArgs.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
using System;
namespace MediaBrowser.Model.Tasks
diff --git a/MediaBrowser.Model/Tasks/TaskCompletionStatus.cs b/MediaBrowser.Model/Tasks/TaskCompletionStatus.cs
index 4d7ff523d..5c8b5f223 100644
--- a/MediaBrowser.Model/Tasks/TaskCompletionStatus.cs
+++ b/MediaBrowser.Model/Tasks/TaskCompletionStatus.cs
@@ -1,27 +1,27 @@
namespace MediaBrowser.Model.Tasks
{
/// <summary>
- /// Enum TaskCompletionStatus
+ /// Enum TaskCompletionStatus.
/// </summary>
public enum TaskCompletionStatus
{
/// <summary>
- /// The completed
+ /// The completed.
/// </summary>
Completed,
/// <summary>
- /// The failed
+ /// The failed.
/// </summary>
Failed,
/// <summary>
- /// Manually cancelled by the user
+ /// Manually cancelled by the user.
/// </summary>
Cancelled,
/// <summary>
- /// Aborted due to a system failure or shutdown
+ /// Aborted due to a system failure or shutdown.
/// </summary>
Aborted
}
diff --git a/MediaBrowser.Model/Tasks/TaskInfo.cs b/MediaBrowser.Model/Tasks/TaskInfo.cs
index 8d80e68cf..5144c035a 100644
--- a/MediaBrowser.Model/Tasks/TaskInfo.cs
+++ b/MediaBrowser.Model/Tasks/TaskInfo.cs
@@ -1,7 +1,9 @@
+using System;
+
namespace MediaBrowser.Model.Tasks
{
/// <summary>
- /// Class TaskInfo
+ /// Class TaskInfo.
/// </summary>
public class TaskInfo
{
@@ -70,7 +72,7 @@ namespace MediaBrowser.Model.Tasks
/// </summary>
public TaskInfo()
{
- Triggers = new TaskTriggerInfo[] { };
+ Triggers = Array.Empty<TaskTriggerInfo>();
}
}
}
diff --git a/MediaBrowser.Model/Tasks/TaskOptions.cs b/MediaBrowser.Model/Tasks/TaskOptions.cs
index a9f03303a..3a221b878 100644
--- a/MediaBrowser.Model/Tasks/TaskOptions.cs
+++ b/MediaBrowser.Model/Tasks/TaskOptions.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
namespace MediaBrowser.Model.Tasks
{
public class TaskOptions
diff --git a/MediaBrowser.Model/Tasks/TaskResult.cs b/MediaBrowser.Model/Tasks/TaskResult.cs
index eede9069f..c6f92e7ed 100644
--- a/MediaBrowser.Model/Tasks/TaskResult.cs
+++ b/MediaBrowser.Model/Tasks/TaskResult.cs
@@ -3,7 +3,7 @@ using System;
namespace MediaBrowser.Model.Tasks
{
/// <summary>
- /// Class TaskExecutionInfo
+ /// Class TaskExecutionInfo.
/// </summary>
public class TaskResult
{
diff --git a/MediaBrowser.Model/Tasks/TaskState.cs b/MediaBrowser.Model/Tasks/TaskState.cs
index 91bc7f682..619dedb70 100644
--- a/MediaBrowser.Model/Tasks/TaskState.cs
+++ b/MediaBrowser.Model/Tasks/TaskState.cs
@@ -1,20 +1,22 @@
namespace MediaBrowser.Model.Tasks
{
/// <summary>
- /// Enum TaskState
+ /// Enum TaskState.
/// </summary>
public enum TaskState
{
/// <summary>
- /// The idle
+ /// The idle.
/// </summary>
Idle,
+
/// <summary>
- /// The cancelling
+ /// The cancelling.
/// </summary>
Cancelling,
+
/// <summary>
- /// The running
+ /// The running.
/// </summary>
Running
}
diff --git a/MediaBrowser.Model/Tasks/TaskTriggerInfo.cs b/MediaBrowser.Model/Tasks/TaskTriggerInfo.cs
index 714f11872..699e0ea3a 100644
--- a/MediaBrowser.Model/Tasks/TaskTriggerInfo.cs
+++ b/MediaBrowser.Model/Tasks/TaskTriggerInfo.cs
@@ -1,9 +1,11 @@
+#pragma warning disable CS1591
+
using System;
namespace MediaBrowser.Model.Tasks
{
/// <summary>
- /// Class TaskTriggerInfo
+ /// Class TaskTriggerInfo.
/// </summary>
public class TaskTriggerInfo
{
diff --git a/MediaBrowser.Model/Updates/CheckForUpdateResult.cs b/MediaBrowser.Model/Updates/CheckForUpdateResult.cs
deleted file mode 100644
index 4c66c6d49..000000000
--- a/MediaBrowser.Model/Updates/CheckForUpdateResult.cs
+++ /dev/null
@@ -1,29 +0,0 @@
-namespace MediaBrowser.Model.Updates
-{
- /// <summary>
- /// Class CheckForUpdateResult
- /// </summary>
- public class CheckForUpdateResult
- {
- /// <summary>
- /// Gets or sets a value indicating whether this instance is update available.
- /// </summary>
- /// <value><c>true</c> if this instance is update available; otherwise, <c>false</c>.</value>
- public bool IsUpdateAvailable { get; set; }
-
- /// <summary>
- /// Gets or sets the available version.
- /// </summary>
- /// <value>The available version.</value>
- public string AvailableVersion
- {
- get => Package != null ? Package.versionStr : "0.0.0.1";
- set { } // need this for the serializer
- }
-
- /// <summary>
- /// Get or sets package information for an available update
- /// </summary>
- public PackageVersionInfo Package { get; set; }
- }
-}
diff --git a/MediaBrowser.Model/Updates/InstallationInfo.cs b/MediaBrowser.Model/Updates/InstallationInfo.cs
index 7554e9fe2..e0d450d06 100644
--- a/MediaBrowser.Model/Updates/InstallationInfo.cs
+++ b/MediaBrowser.Model/Updates/InstallationInfo.cs
@@ -3,15 +3,15 @@ using System;
namespace MediaBrowser.Model.Updates
{
/// <summary>
- /// Class InstallationInfo
+ /// Class InstallationInfo.
/// </summary>
public class InstallationInfo
{
/// <summary>
- /// Gets or sets the id.
+ /// Gets or sets the guid.
/// </summary>
- /// <value>The id.</value>
- public Guid Id { get; set; }
+ /// <value>The guid.</value>
+ public string Guid { get; set; }
/// <summary>
/// Gets or sets the name.
@@ -20,21 +20,9 @@ namespace MediaBrowser.Model.Updates
public string Name { get; set; }
/// <summary>
- /// Gets or sets the assembly guid.
- /// </summary>
- /// <value>The guid of the assembly.</value>
- public string AssemblyGuid { get; set; }
-
- /// <summary>
/// Gets or sets the version.
/// </summary>
/// <value>The version.</value>
public string Version { get; set; }
-
- /// <summary>
- /// Gets or sets the update class.
- /// </summary>
- /// <value>The update class.</value>
- public PackageVersionClass UpdateClass { get; set; }
}
}
diff --git a/MediaBrowser.Model/Updates/PackageInfo.cs b/MediaBrowser.Model/Updates/PackageInfo.cs
index 5dd9c6591..f5aa8b6fa 100644
--- a/MediaBrowser.Model/Updates/PackageInfo.cs
+++ b/MediaBrowser.Model/Updates/PackageInfo.cs
@@ -4,77 +4,29 @@ using System.Collections.Generic;
namespace MediaBrowser.Model.Updates
{
/// <summary>
- /// Class PackageInfo
+ /// Class PackageInfo.
/// </summary>
public class PackageInfo
{
/// <summary>
- /// The internal id of this package.
- /// </summary>
- /// <value>The id.</value>
- public string id { get; set; }
-
- /// <summary>
/// Gets or sets the name.
/// </summary>
/// <value>The name.</value>
public string name { get; set; }
/// <summary>
- /// Gets or sets the short description.
+ /// Gets or sets a long description of the plugin containing features or helpful explanations.
/// </summary>
- /// <value>The short description.</value>
- public string shortDescription { get; set; }
+ /// <value>The description.</value>
+ public string description { get; set; }
/// <summary>
- /// Gets or sets the overview.
+ /// Gets or sets a short overview of what the plugin does.
/// </summary>
/// <value>The overview.</value>
public string overview { get; set; }
/// <summary>
- /// Gets or sets a value indicating whether this instance is premium.
- /// </summary>
- /// <value><c>true</c> if this instance is premium; otherwise, <c>false</c>.</value>
- public bool isPremium { get; set; }
-
- /// <summary>
- /// Gets or sets a value indicating whether this instance is adult only content.
- /// </summary>
- /// <value><c>true</c> if this instance is adult; otherwise, <c>false</c>.</value>
- public bool adult { get; set; }
-
- /// <summary>
- /// Gets or sets the rich desc URL.
- /// </summary>
- /// <value>The rich desc URL.</value>
- public string richDescUrl { get; set; }
-
- /// <summary>
- /// Gets or sets the thumb image.
- /// </summary>
- /// <value>The thumb image.</value>
- public string thumbImage { get; set; }
-
- /// <summary>
- /// Gets or sets the preview image.
- /// </summary>
- /// <value>The preview image.</value>
- public string previewImage { get; set; }
-
- /// <summary>
- /// Gets or sets the type.
- /// </summary>
- /// <value>The type.</value>
- public string type { get; set; }
-
- /// <summary>
- /// Gets or sets the target filename.
- /// </summary>
- /// <value>The target filename.</value>
- public string targetFilename { get; set; }
-
- /// <summary>
/// Gets or sets the owner.
/// </summary>
/// <value>The owner.</value>
@@ -87,90 +39,24 @@ namespace MediaBrowser.Model.Updates
public string category { get; set; }
/// <summary>
- /// Gets or sets the catalog tile color.
- /// </summary>
- /// <value>The owner.</value>
- public string tileColor { get; set; }
-
- /// <summary>
- /// Gets or sets the feature id of this package (if premium).
- /// </summary>
- /// <value>The feature id.</value>
- public string featureId { get; set; }
-
- /// <summary>
- /// Gets or sets the registration info for this package (if premium).
- /// </summary>
- /// <value>The registration info.</value>
- public string regInfo { get; set; }
-
- /// <summary>
- /// Gets or sets the price for this package (if premium).
- /// </summary>
- /// <value>The price.</value>
- public float price { get; set; }
-
- /// <summary>
- /// Gets or sets the target system for this plug-in (Server, MBTheater, MBClassic).
- /// </summary>
- /// <value>The target system.</value>
- public PackageTargetSystem targetSystem { get; set; }
-
- /// <summary>
- /// The guid of the assembly associated with this package (if a plug-in).
+ /// The guid of the assembly associated with this plugin.
/// This is used to identify the proper item for automatic updates.
/// </summary>
/// <value>The name.</value>
public string guid { get; set; }
/// <summary>
- /// Gets or sets the total number of ratings for this package.
- /// </summary>
- /// <value>The total ratings.</value>
- public int? totalRatings { get; set; }
-
- /// <summary>
- /// Gets or sets the average rating for this package .
- /// </summary>
- /// <value>The rating.</value>
- public float avgRating { get; set; }
-
- /// <summary>
- /// Gets or sets whether or not this package is registered.
- /// </summary>
- /// <value>True if registered.</value>
- public bool isRegistered { get; set; }
-
- /// <summary>
- /// Gets or sets the expiration date for this package.
- /// </summary>
- /// <value>Expiration Date.</value>
- public DateTime expDate { get; set; }
-
- /// <summary>
/// Gets or sets the versions.
/// </summary>
/// <value>The versions.</value>
- public IReadOnlyList<PackageVersionInfo> versions { get; set; }
-
- /// <summary>
- /// Gets or sets a value indicating whether [enable in application store].
- /// </summary>
- /// <value><c>true</c> if [enable in application store]; otherwise, <c>false</c>.</value>
- public bool enableInAppStore { get; set; }
-
- /// <summary>
- /// Gets or sets the installs.
- /// </summary>
- /// <value>The installs.</value>
- public int installs { get; set; }
+ public IReadOnlyList<VersionInfo> versions { get; set; }
/// <summary>
/// Initializes a new instance of the <see cref="PackageInfo"/> class.
/// </summary>
public PackageInfo()
{
- versions = new PackageVersionInfo[] { };
+ versions = Array.Empty<VersionInfo>();
}
}
}
diff --git a/MediaBrowser.Model/Updates/PackageTargetSystem.cs b/MediaBrowser.Model/Updates/PackageTargetSystem.cs
deleted file mode 100644
index a0646f959..000000000
--- a/MediaBrowser.Model/Updates/PackageTargetSystem.cs
+++ /dev/null
@@ -1,21 +0,0 @@
-namespace MediaBrowser.Model.Updates
-{
- /// <summary>
- /// Enum PackageType
- /// </summary>
- public enum PackageTargetSystem
- {
- /// <summary>
- /// Server
- /// </summary>
- Server,
- /// <summary>
- /// MB Theater
- /// </summary>
- MBTheater,
- /// <summary>
- /// MB Classic
- /// </summary>
- MBClassic
- }
-}
diff --git a/MediaBrowser.Model/Updates/PackageVersionClass.cs b/MediaBrowser.Model/Updates/PackageVersionClass.cs
deleted file mode 100644
index 52f08b73b..000000000
--- a/MediaBrowser.Model/Updates/PackageVersionClass.cs
+++ /dev/null
@@ -1,21 +0,0 @@
-namespace MediaBrowser.Model.Updates
-{
- /// <summary>
- /// Enum PackageVersionClass
- /// </summary>
- public enum PackageVersionClass
- {
- /// <summary>
- /// The release
- /// </summary>
- Release = 0,
- /// <summary>
- /// The beta
- /// </summary>
- Beta = 1,
- /// <summary>
- /// The dev
- /// </summary>
- Dev = 2
- }
-}
diff --git a/MediaBrowser.Model/Updates/PackageVersionInfo.cs b/MediaBrowser.Model/Updates/PackageVersionInfo.cs
deleted file mode 100644
index c0790317d..000000000
--- a/MediaBrowser.Model/Updates/PackageVersionInfo.cs
+++ /dev/null
@@ -1,94 +0,0 @@
-using System;
-using System.Text.Json.Serialization;
-
-namespace MediaBrowser.Model.Updates
-{
- /// <summary>
- /// Class PackageVersionInfo
- /// </summary>
- public class PackageVersionInfo
- {
- /// <summary>
- /// Gets or sets the name.
- /// </summary>
- /// <value>The name.</value>
- public string name { get; set; }
-
- /// <summary>
- /// Gets or sets the guid.
- /// </summary>
- /// <value>The guid.</value>
- public string guid { get; set; }
-
- /// <summary>
- /// Gets or sets the version STR.
- /// </summary>
- /// <value>The version STR.</value>
- public string versionStr { get; set; }
-
- /// <summary>
- /// The _version
- /// </summary>
- private Version _version;
-
- /// <summary>
- /// Gets or sets the version.
- /// Had to make this an interpreted property since Protobuf can't handle Version
- /// </summary>
- /// <value>The version.</value>
- [JsonIgnore]
- public Version Version
- {
- get
- {
- if (_version == null)
- {
- var ver = versionStr;
- _version = new Version(string.IsNullOrEmpty(ver) ? "0.0.0.1" : ver);
- }
-
- return _version;
- }
- }
-
- /// <summary>
- /// Gets or sets the classification.
- /// </summary>
- /// <value>The classification.</value>
- public PackageVersionClass classification { get; set; }
-
- /// <summary>
- /// Gets or sets the description.
- /// </summary>
- /// <value>The description.</value>
- public string description { get; set; }
-
- /// <summary>
- /// Gets or sets the required version STR.
- /// </summary>
- /// <value>The required version STR.</value>
- public string requiredVersionStr { get; set; }
-
- /// <summary>
- /// Gets or sets the source URL.
- /// </summary>
- /// <value>The source URL.</value>
- public string sourceUrl { get; set; }
-
- /// <summary>
- /// Gets or sets the source URL.
- /// </summary>
- /// <value>The source URL.</value>
- public string checksum { get; set; }
-
- /// <summary>
- /// Gets or sets the target filename.
- /// </summary>
- /// <value>The target filename.</value>
- public string targetFilename { get; set; }
-
- public string infoUrl { get; set; }
-
- public string runtimes { get; set; }
- }
-}
diff --git a/MediaBrowser.Model/Updates/VersionInfo.cs b/MediaBrowser.Model/Updates/VersionInfo.cs
new file mode 100644
index 000000000..fe5826ad2
--- /dev/null
+++ b/MediaBrowser.Model/Updates/VersionInfo.cs
@@ -0,0 +1,58 @@
+using System;
+
+namespace MediaBrowser.Model.Updates
+{
+ /// <summary>
+ /// Class PackageVersionInfo.
+ /// </summary>
+ public class VersionInfo
+ {
+ /// <summary>
+ /// Gets or sets the name.
+ /// </summary>
+ /// <value>The name.</value>
+ public string name { get; set; }
+
+ /// <summary>
+ /// Gets or sets the guid.
+ /// </summary>
+ /// <value>The guid.</value>
+ public string guid { get; set; }
+
+ /// <summary>
+ /// Gets or sets the version.
+ /// </summary>
+ /// <value>The version.</value>
+ public Version version { get; set; }
+
+ /// <summary>
+ /// Gets or sets the changelog for this version.
+ /// </summary>
+ /// <value>The changelog.</value>
+ public string changelog { get; set; }
+
+ /// <summary>
+ /// Gets or sets the ABI that this version was built against.
+ /// </summary>
+ /// <value>The target ABI version.</value>
+ public string targetAbi { get; set; }
+
+ /// <summary>
+ /// Gets or sets the source URL.
+ /// </summary>
+ /// <value>The source URL.</value>
+ public string sourceUrl { get; set; }
+
+ /// <summary>
+ /// Gets or sets a checksum for the binary.
+ /// </summary>
+ /// <value>The checksum.</value>
+ public string checksum { get; set; }
+
+ /// <summary>
+ /// Gets or sets the target filename for the downloaded binary.
+ /// </summary>
+ /// <value>The target filename.</value>
+ public string filename { get; set; }
+ }
+}
diff --git a/MediaBrowser.Model/Users/ForgotPasswordAction.cs b/MediaBrowser.Model/Users/ForgotPasswordAction.cs
index 2124126c1..f198476e3 100644
--- a/MediaBrowser.Model/Users/ForgotPasswordAction.cs
+++ b/MediaBrowser.Model/Users/ForgotPasswordAction.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
namespace MediaBrowser.Model.Users
{
public enum ForgotPasswordAction
diff --git a/MediaBrowser.Model/Users/ForgotPasswordResult.cs b/MediaBrowser.Model/Users/ForgotPasswordResult.cs
index 2f9b4cf48..368c642e8 100644
--- a/MediaBrowser.Model/Users/ForgotPasswordResult.cs
+++ b/MediaBrowser.Model/Users/ForgotPasswordResult.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
using System;
namespace MediaBrowser.Model.Users
@@ -9,11 +11,13 @@ namespace MediaBrowser.Model.Users
/// </summary>
/// <value>The action.</value>
public ForgotPasswordAction Action { get; set; }
+
/// <summary>
/// Gets or sets the pin file.
/// </summary>
/// <value>The pin file.</value>
public string PinFile { get; set; }
+
/// <summary>
/// Gets or sets the pin expiration date.
/// </summary>
diff --git a/MediaBrowser.Model/Users/PinRedeemResult.cs b/MediaBrowser.Model/Users/PinRedeemResult.cs
index 35663ba57..ab868cad4 100644
--- a/MediaBrowser.Model/Users/PinRedeemResult.cs
+++ b/MediaBrowser.Model/Users/PinRedeemResult.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
namespace MediaBrowser.Model.Users
{
public class PinRedeemResult
@@ -7,6 +9,7 @@ namespace MediaBrowser.Model.Users
/// </summary>
/// <value><c>true</c> if success; otherwise, <c>false</c>.</value>
public bool Success { get; set; }
+
/// <summary>
/// Gets or sets the users reset.
/// </summary>
diff --git a/MediaBrowser.Model/Users/UserAction.cs b/MediaBrowser.Model/Users/UserAction.cs
index 48b5bbef1..f6bb6451b 100644
--- a/MediaBrowser.Model/Users/UserAction.cs
+++ b/MediaBrowser.Model/Users/UserAction.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
using System;
namespace MediaBrowser.Model.Users
diff --git a/MediaBrowser.Model/Users/UserActionType.cs b/MediaBrowser.Model/Users/UserActionType.cs
index 5d843a738..dbb1513f2 100644
--- a/MediaBrowser.Model/Users/UserActionType.cs
+++ b/MediaBrowser.Model/Users/UserActionType.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
namespace MediaBrowser.Model.Users
{
public enum UserActionType
diff --git a/MediaBrowser.Model/Users/UserPolicy.cs b/MediaBrowser.Model/Users/UserPolicy.cs
index 9c3e1f980..ae2b3fd4e 100644
--- a/MediaBrowser.Model/Users/UserPolicy.cs
+++ b/MediaBrowser.Model/Users/UserPolicy.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
using System;
using MediaBrowser.Model.Configuration;
@@ -44,6 +46,7 @@ namespace MediaBrowser.Model.Users
public bool EnableAudioPlaybackTranscoding { get; set; }
public bool EnableVideoPlaybackTranscoding { get; set; }
public bool EnablePlaybackRemuxing { get; set; }
+ public bool ForceRemoteSourceTranscoding { get; set; }
public bool EnableContentDeletion { get; set; }
public string[] EnableContentDeletionFromFolders { get; set; }
@@ -80,7 +83,7 @@ namespace MediaBrowser.Model.Users
public UserPolicy()
{
IsHidden = true;
-
+
EnableContentDeletion = false;
EnableContentDeletionFromFolders = Array.Empty<string>();
@@ -91,7 +94,7 @@ namespace MediaBrowser.Model.Users
EnableAudioPlaybackTranscoding = true;
EnableVideoPlaybackTranscoding = true;
EnablePlaybackRemuxing = true;
-
+ ForceRemoteSourceTranscoding = false;
EnableLiveTvManagement = true;
EnableLiveTvAccess = true;
diff --git a/MediaBrowser.Providers/Books/AudioBookMetadataService.cs b/MediaBrowser.Providers/Books/AudioBookMetadataService.cs
index 309241bfa..8eaeeea08 100644
--- a/MediaBrowser.Providers/Books/AudioBookMetadataService.cs
+++ b/MediaBrowser.Providers/Books/AudioBookMetadataService.cs
@@ -13,7 +13,7 @@ namespace MediaBrowser.Providers.Books
{
public AudioBookMetadataService(
IServerConfigurationManager serverConfigurationManager,
- ILogger logger,
+ ILogger<AudioBookMetadataService> logger,
IProviderManager providerManager,
IFileSystem fileSystem,
ILibraryManager libraryManager)
diff --git a/MediaBrowser.Providers/Books/BookMetadataService.cs b/MediaBrowser.Providers/Books/BookMetadataService.cs
index 9d6a1ef59..340641711 100644
--- a/MediaBrowser.Providers/Books/BookMetadataService.cs
+++ b/MediaBrowser.Providers/Books/BookMetadataService.cs
@@ -13,7 +13,7 @@ namespace MediaBrowser.Providers.Books
{
public BookMetadataService(
IServerConfigurationManager serverConfigurationManager,
- ILogger logger,
+ ILogger<BookMetadataService> logger,
IProviderManager providerManager,
IFileSystem fileSystem,
ILibraryManager libraryManager)
diff --git a/MediaBrowser.Providers/BoxSets/BoxSetMetadataService.cs b/MediaBrowser.Providers/BoxSets/BoxSetMetadataService.cs
index 5bf01232c..3c9760ea7 100644
--- a/MediaBrowser.Providers/BoxSets/BoxSetMetadataService.cs
+++ b/MediaBrowser.Providers/BoxSets/BoxSetMetadataService.cs
@@ -16,7 +16,7 @@ namespace MediaBrowser.Providers.BoxSets
{
public BoxSetMetadataService(
IServerConfigurationManager serverConfigurationManager,
- ILogger logger,
+ ILogger<BoxSetMetadataService> logger,
IProviderManager providerManager,
IFileSystem fileSystem,
ILibraryManager libraryManager)
diff --git a/MediaBrowser.Providers/Channels/ChannelMetadataService.cs b/MediaBrowser.Providers/Channels/ChannelMetadataService.cs
index da41f208c..9afa82319 100644
--- a/MediaBrowser.Providers/Channels/ChannelMetadataService.cs
+++ b/MediaBrowser.Providers/Channels/ChannelMetadataService.cs
@@ -13,7 +13,7 @@ namespace MediaBrowser.Providers.Channels
{
public ChannelMetadataService(
IServerConfigurationManager serverConfigurationManager,
- ILogger logger,
+ ILogger<ChannelMetadataService> logger,
IProviderManager providerManager,
IFileSystem fileSystem,
ILibraryManager libraryManager)
diff --git a/MediaBrowser.Providers/Chapters/ChapterManager.cs b/MediaBrowser.Providers/Chapters/ChapterManager.cs
index 45e87f137..3cbfe7d4d 100644
--- a/MediaBrowser.Providers/Chapters/ChapterManager.cs
+++ b/MediaBrowser.Providers/Chapters/ChapterManager.cs
@@ -1,36 +1,26 @@
+#pragma warning disable CS1591
+
using System;
using System.Collections.Generic;
using MediaBrowser.Controller.Chapters;
-using MediaBrowser.Controller.Configuration;
-using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Persistence;
using MediaBrowser.Model.Entities;
-using Microsoft.Extensions.Logging;
namespace MediaBrowser.Providers.Chapters
{
public class ChapterManager : IChapterManager
{
- private readonly ILibraryManager _libraryManager;
- private readonly ILogger _logger;
- private readonly IServerConfigurationManager _config;
private readonly IItemRepository _itemRepo;
- public ChapterManager(
- ILibraryManager libraryManager,
- ILoggerFactory loggerFactory,
- IServerConfigurationManager config,
- IItemRepository itemRepo)
+ public ChapterManager(IItemRepository itemRepo)
{
- _libraryManager = libraryManager;
- _logger = loggerFactory.CreateLogger(nameof(ChapterManager));
- _config = config;
_itemRepo = itemRepo;
}
- public void SaveChapters(string itemId, List<ChapterInfo> chapters)
+ /// <inheritdoc />
+ public void SaveChapters(Guid itemId, IReadOnlyList<ChapterInfo> chapters)
{
- _itemRepo.SaveChapters(new Guid(itemId), chapters);
+ _itemRepo.SaveChapters(itemId, chapters);
}
}
}
diff --git a/MediaBrowser.Providers/Folders/CollectionFolderMetadataService.cs b/MediaBrowser.Providers/Folders/CollectionFolderMetadataService.cs
index dd1b4709d..921222543 100644
--- a/MediaBrowser.Providers/Folders/CollectionFolderMetadataService.cs
+++ b/MediaBrowser.Providers/Folders/CollectionFolderMetadataService.cs
@@ -14,7 +14,7 @@ namespace MediaBrowser.Providers.Folders
{
public CollectionFolderMetadataService(
IServerConfigurationManager serverConfigurationManager,
- ILogger logger,
+ ILogger<CollectionFolderMetadataService> logger,
IProviderManager providerManager,
IFileSystem fileSystem,
ILibraryManager libraryManager)
diff --git a/MediaBrowser.Providers/Folders/FolderMetadataService.cs b/MediaBrowser.Providers/Folders/FolderMetadataService.cs
index 8409e03fd..b6bd2515d 100644
--- a/MediaBrowser.Providers/Folders/FolderMetadataService.cs
+++ b/MediaBrowser.Providers/Folders/FolderMetadataService.cs
@@ -13,7 +13,7 @@ namespace MediaBrowser.Providers.Folders
{
public FolderMetadataService(
IServerConfigurationManager serverConfigurationManager,
- ILogger logger,
+ ILogger<FolderMetadataService> logger,
IProviderManager providerManager,
IFileSystem fileSystem,
ILibraryManager libraryManager)
diff --git a/MediaBrowser.Providers/Folders/UserViewMetadataService.cs b/MediaBrowser.Providers/Folders/UserViewMetadataService.cs
index 2ceb71afc..60ee81114 100644
--- a/MediaBrowser.Providers/Folders/UserViewMetadataService.cs
+++ b/MediaBrowser.Providers/Folders/UserViewMetadataService.cs
@@ -13,7 +13,7 @@ namespace MediaBrowser.Providers.Folders
{
public UserViewMetadataService(
IServerConfigurationManager serverConfigurationManager,
- ILogger logger,
+ ILogger<UserViewMetadataService> logger,
IProviderManager providerManager,
IFileSystem fileSystem,
ILibraryManager libraryManager)
diff --git a/MediaBrowser.Providers/Genres/GenreMetadataService.cs b/MediaBrowser.Providers/Genres/GenreMetadataService.cs
index 932eb368c..f3406c1ab 100644
--- a/MediaBrowser.Providers/Genres/GenreMetadataService.cs
+++ b/MediaBrowser.Providers/Genres/GenreMetadataService.cs
@@ -13,7 +13,7 @@ namespace MediaBrowser.Providers.Genres
{
public GenreMetadataService(
IServerConfigurationManager serverConfigurationManager,
- ILogger logger,
+ ILogger<GenreMetadataService> logger,
IProviderManager providerManager,
IFileSystem fileSystem,
ILibraryManager libraryManager)
diff --git a/MediaBrowser.Providers/LiveTv/ProgramMetadataService.cs b/MediaBrowser.Providers/LiveTv/ProgramMetadataService.cs
index 13dd97215..7dd49c71a 100644
--- a/MediaBrowser.Providers/LiveTv/ProgramMetadataService.cs
+++ b/MediaBrowser.Providers/LiveTv/ProgramMetadataService.cs
@@ -13,7 +13,7 @@ namespace MediaBrowser.Providers.LiveTv
{
public LiveTvMetadataService(
IServerConfigurationManager serverConfigurationManager,
- ILogger logger,
+ ILogger<LiveTvMetadataService> logger,
IProviderManager providerManager,
IFileSystem fileSystem,
ILibraryManager libraryManager)
diff --git a/MediaBrowser.Providers/Manager/ImageSaver.cs b/MediaBrowser.Providers/Manager/ImageSaver.cs
index ab906809f..3ab621ba4 100644
--- a/MediaBrowser.Providers/Manager/ImageSaver.cs
+++ b/MediaBrowser.Providers/Manager/ImageSaver.cs
@@ -244,9 +244,9 @@ namespace MediaBrowser.Providers.Manager
_fileSystem.SetAttributes(path, false, false);
- using (var fs = _fileSystem.GetFileStream(path, FileOpenMode.Create, FileAccessMode.Write, FileShareMode.Read, FileOpenOptions.Asynchronous))
+ using (var fs = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.Read, IODefaults.FileStreamBufferSize, FileOptions.Asynchronous))
{
- await source.CopyToAsync(fs, StreamDefaults.DefaultCopyToBufferSize, cancellationToken).ConfigureAwait(false);
+ await source.CopyToAsync(fs, IODefaults.CopyToBufferSize, cancellationToken).ConfigureAwait(false);
}
if (_config.Configuration.SaveMetadataHidden)
diff --git a/MediaBrowser.Providers/Manager/ItemImageProvider.cs b/MediaBrowser.Providers/Manager/ItemImageProvider.cs
index e9179815e..6ef0e44a2 100644
--- a/MediaBrowser.Providers/Manager/ItemImageProvider.cs
+++ b/MediaBrowser.Providers/Manager/ItemImageProvider.cs
@@ -5,7 +5,6 @@ using System.Linq;
using System.Net;
using System.Threading;
using System.Threading.Tasks;
-using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.Audio;
using MediaBrowser.Controller.Library;
@@ -25,14 +24,12 @@ namespace MediaBrowser.Providers.Manager
{
private readonly ILogger _logger;
private readonly IProviderManager _providerManager;
- private readonly IServerConfigurationManager _config;
private readonly IFileSystem _fileSystem;
- public ItemImageProvider(ILogger logger, IProviderManager providerManager, IServerConfigurationManager config, IFileSystem fileSystem)
+ public ItemImageProvider(ILogger logger, IProviderManager providerManager, IFileSystem fileSystem)
{
_logger = logger;
_providerManager = providerManager;
- _config = config;
_fileSystem = fileSystem;
}
@@ -42,7 +39,7 @@ namespace MediaBrowser.Providers.Manager
if (!(item is Photo))
{
- var images = providers.OfType<ILocalImageFileProvider>()
+ var images = providers.OfType<ILocalImageProvider>()
.SelectMany(i => i.GetImages(item, directoryService))
.ToList();
@@ -141,7 +138,7 @@ namespace MediaBrowser.Providers.Manager
{
var mimeType = MimeTypes.GetMimeType(response.Path);
- var stream = _fileSystem.GetFileStream(response.Path, FileOpenMode.Open, FileAccessMode.Read, FileShareMode.Read, true);
+ var stream = new FileStream(response.Path, FileMode.Open, FileAccess.Read, FileShare.Read, IODefaults.FileStreamBufferSize, true);
await _providerManager.SaveImage(item, stream, mimeType, imageType, null, cancellationToken).ConfigureAwait(false);
}
diff --git a/MediaBrowser.Providers/Manager/MetadataService.cs b/MediaBrowser.Providers/Manager/MetadataService.cs
index c3401f12b..c49aa407a 100644
--- a/MediaBrowser.Providers/Manager/MetadataService.cs
+++ b/MediaBrowser.Providers/Manager/MetadataService.cs
@@ -73,7 +73,7 @@ namespace MediaBrowser.Providers.Manager
}
}
- var itemImageProvider = new ItemImageProvider(Logger, ProviderManager, ServerConfigurationManager, FileSystem);
+ var itemImageProvider = new ItemImageProvider(Logger, ProviderManager, FileSystem);
var localImagesFailed = false;
var allImageProviders = ((ProviderManager)ProviderManager).GetImageProviders(item, refreshOptions).ToList();
@@ -606,7 +606,6 @@ namespace MediaBrowser.Providers.Manager
// Run custom refresh providers if they report a change or any remote providers change
return anyRemoteProvidersChanged || providersWithChanges.Contains(i);
-
}).ToList();
}
}
diff --git a/MediaBrowser.Providers/Manager/ProviderManager.cs b/MediaBrowser.Providers/Manager/ProviderManager.cs
index 631d063a5..cfff89767 100644
--- a/MediaBrowser.Providers/Manager/ProviderManager.cs
+++ b/MediaBrowser.Providers/Manager/ProviderManager.cs
@@ -1,5 +1,9 @@
+#pragma warning disable CS1591
+
using System;
+using System.Collections.Concurrent;
using System.Collections.Generic;
+using System.Globalization;
using System.IO;
using System.Linq;
using System.Threading;
@@ -32,60 +36,51 @@ namespace MediaBrowser.Providers.Manager
/// </summary>
public class ProviderManager : IProviderManager, IDisposable
{
- /// <summary>
- /// The _logger
- /// </summary>
private readonly ILogger _logger;
-
- /// <summary>
- /// The _HTTP client
- /// </summary>
private readonly IHttpClient _httpClient;
-
- /// <summary>
- /// The _directory watchers
- /// </summary>
private readonly ILibraryMonitor _libraryMonitor;
-
- /// <summary>
- /// Gets or sets the configuration manager.
- /// </summary>
- /// <value>The configuration manager.</value>
- private IServerConfigurationManager ConfigurationManager { get; set; }
+ private readonly IFileSystem _fileSystem;
+ private readonly IServerApplicationPaths _appPaths;
+ private readonly IJsonSerializer _json;
+ private readonly ILibraryManager _libraryManager;
+ private readonly ISubtitleManager _subtitleManager;
+ private readonly IServerConfigurationManager _configurationManager;
private IImageProvider[] ImageProviders { get; set; }
- private readonly IFileSystem _fileSystem;
-
private IMetadataService[] _metadataServices = { };
private IMetadataProvider[] _metadataProviders = { };
private IEnumerable<IMetadataSaver> _savers;
- private readonly IServerApplicationPaths _appPaths;
- private readonly IJsonSerializer _json;
private IExternalId[] _externalIds;
- private readonly Func<ILibraryManager> _libraryManagerFactory;
private CancellationTokenSource _disposeCancellationTokenSource = new CancellationTokenSource();
public event EventHandler<GenericEventArgs<BaseItem>> RefreshStarted;
public event EventHandler<GenericEventArgs<BaseItem>> RefreshCompleted;
public event EventHandler<GenericEventArgs<Tuple<BaseItem, double>>> RefreshProgress;
- private ISubtitleManager _subtitleManager;
-
/// <summary>
/// Initializes a new instance of the <see cref="ProviderManager" /> class.
/// </summary>
- public ProviderManager(IHttpClient httpClient, ISubtitleManager subtitleManager, IServerConfigurationManager configurationManager, ILibraryMonitor libraryMonitor, ILoggerFactory loggerFactory, IFileSystem fileSystem, IServerApplicationPaths appPaths, Func<ILibraryManager> libraryManagerFactory, IJsonSerializer json)
+ public ProviderManager(
+ IHttpClient httpClient,
+ ISubtitleManager subtitleManager,
+ IServerConfigurationManager configurationManager,
+ ILibraryMonitor libraryMonitor,
+ ILogger<ProviderManager> logger,
+ IFileSystem fileSystem,
+ IServerApplicationPaths appPaths,
+ ILibraryManager libraryManager,
+ IJsonSerializer json)
{
- _logger = loggerFactory.CreateLogger("ProviderManager");
+ _logger = logger;
_httpClient = httpClient;
- ConfigurationManager = configurationManager;
+ _configurationManager = configurationManager;
_libraryMonitor = libraryMonitor;
_fileSystem = fileSystem;
_appPaths = appPaths;
- _libraryManagerFactory = libraryManagerFactory;
+ _libraryManager = libraryManager;
_json = json;
_subtitleManager = subtitleManager;
}
@@ -172,7 +167,7 @@ namespace MediaBrowser.Providers.Manager
public Task SaveImage(BaseItem item, Stream source, string mimeType, ImageType type, int? imageIndex, CancellationToken cancellationToken)
{
- return new ImageSaver(ConfigurationManager, _libraryMonitor, _fileSystem, _logger).SaveImage(item, source, mimeType, type, imageIndex, cancellationToken);
+ return new ImageSaver(_configurationManager, _libraryMonitor, _fileSystem, _logger).SaveImage(item, source, mimeType, type, imageIndex, cancellationToken);
}
public Task SaveImage(BaseItem item, string source, string mimeType, ImageType type, int? imageIndex, bool? saveLocallyWithMedia, CancellationToken cancellationToken)
@@ -182,9 +177,9 @@ namespace MediaBrowser.Providers.Manager
throw new ArgumentNullException(nameof(source));
}
- var fileStream = _fileSystem.GetFileStream(source, FileOpenMode.Open, FileAccessMode.Read, FileShareMode.ReadWrite, true);
+ var fileStream = new FileStream(source, FileMode.Open, FileAccess.Read, FileShare.ReadWrite, IODefaults.FileStreamBufferSize, true);
- return new ImageSaver(ConfigurationManager, _libraryMonitor, _fileSystem, _logger).SaveImage(item, fileStream, mimeType, type, imageIndex, saveLocallyWithMedia, cancellationToken);
+ return new ImageSaver(_configurationManager, _libraryMonitor, _fileSystem, _logger).SaveImage(item, fileStream, mimeType, type, imageIndex, saveLocallyWithMedia, cancellationToken);
}
public async Task<IEnumerable<RemoteImageInfo>> GetAvailableRemoteImages(BaseItem item, RemoteImageQuery query, CancellationToken cancellationToken)
@@ -269,7 +264,7 @@ namespace MediaBrowser.Providers.Manager
public IEnumerable<IImageProvider> GetImageProviders(BaseItem item, ImageRefreshOptions refreshOptions)
{
- return GetImageProviders(item, _libraryManagerFactory().GetLibraryOptions(item), GetMetadataOptions(item), refreshOptions, false);
+ return GetImageProviders(item, _libraryManager.GetLibraryOptions(item), GetMetadataOptions(item), refreshOptions, false);
}
private IEnumerable<IImageProvider> GetImageProviders(BaseItem item, LibraryOptions libraryOptions, MetadataOptions options, ImageRefreshOptions refreshOptions, bool includeDisabled)
@@ -324,7 +319,7 @@ namespace MediaBrowser.Providers.Manager
private IEnumerable<IRemoteImageProvider> GetRemoteImageProviders(BaseItem item, bool includeDisabled)
{
var options = GetMetadataOptions(item);
- var libraryOptions = _libraryManagerFactory().GetLibraryOptions(item);
+ var libraryOptions = _libraryManager.GetLibraryOptions(item);
return GetImageProviders(item, libraryOptions, options,
new ImageRefreshOptions(
@@ -589,7 +584,7 @@ namespace MediaBrowser.Providers.Manager
{
var type = item.GetType().Name;
- return ConfigurationManager.Configuration.MetadataOptions
+ return _configurationManager.Configuration.MetadataOptions
.FirstOrDefault(i => string.Equals(i.ItemType, type, StringComparison.OrdinalIgnoreCase)) ??
new MetadataOptions();
}
@@ -619,7 +614,7 @@ namespace MediaBrowser.Providers.Manager
/// <returns>Task.</returns>
private void SaveMetadata(BaseItem item, ItemUpdateType updateType, IEnumerable<IMetadataSaver> savers)
{
- var libraryOptions = _libraryManagerFactory().GetLibraryOptions(item);
+ var libraryOptions = _libraryManager.GetLibraryOptions(item);
foreach (var saver in savers.Where(i => IsSaverEnabledForItem(i, item, libraryOptions, updateType, false)))
{
@@ -739,7 +734,7 @@ namespace MediaBrowser.Providers.Manager
if (!searchInfo.ItemId.Equals(Guid.Empty))
{
- referenceItem = _libraryManagerFactory().GetItemById(searchInfo.ItemId);
+ referenceItem = _libraryManager.GetItemById(searchInfo.ItemId);
}
return GetRemoteSearchResults<TItemType, TLookupType>(searchInfo, referenceItem, cancellationToken);
@@ -767,7 +762,7 @@ namespace MediaBrowser.Providers.Manager
}
else
{
- libraryOptions = _libraryManagerFactory().GetLibraryOptions(referenceItem);
+ libraryOptions = _libraryManager.GetLibraryOptions(referenceItem);
}
var options = GetMetadataOptions(referenceItem);
@@ -782,11 +777,11 @@ namespace MediaBrowser.Providers.Manager
if (string.IsNullOrWhiteSpace(searchInfo.SearchInfo.MetadataLanguage))
{
- searchInfo.SearchInfo.MetadataLanguage = ConfigurationManager.Configuration.PreferredMetadataLanguage;
+ searchInfo.SearchInfo.MetadataLanguage = _configurationManager.Configuration.PreferredMetadataLanguage;
}
if (string.IsNullOrWhiteSpace(searchInfo.SearchInfo.MetadataCountryCode))
{
- searchInfo.SearchInfo.MetadataCountryCode = ConfigurationManager.Configuration.MetadataCountryCode;
+ searchInfo.SearchInfo.MetadataCountryCode = _configurationManager.Configuration.MetadataCountryCode;
}
var resultList = new List<RemoteSearchResult>();
@@ -897,7 +892,10 @@ namespace MediaBrowser.Providers.Manager
return new ExternalUrl
{
Name = i.Name,
- Url = string.Format(i.UrlFormatString, value)
+ Url = string.Format(
+ CultureInfo.InvariantCulture,
+ i.UrlFormatString,
+ value)
};
}).Where(i => i != null).Concat(item.GetRelatedUrls());
@@ -911,11 +909,10 @@ namespace MediaBrowser.Providers.Manager
Name = i.Name,
Key = i.Key,
UrlFormatString = i.UrlFormatString
-
});
}
- private Dictionary<Guid, double> _activeRefreshes = new Dictionary<Guid, double>();
+ private ConcurrentDictionary<Guid, double> _activeRefreshes = new ConcurrentDictionary<Guid, double>();
public Dictionary<Guid, Guid> GetRefreshQueue()
{
@@ -927,66 +924,54 @@ namespace MediaBrowser.Providers.Manager
{
dict[item.Item1] = item.Item1;
}
+
return dict;
}
}
public void OnRefreshStart(BaseItem item)
{
- //_logger.LogInformation("OnRefreshStart {0}", item.Id.ToString("N", CultureInfo.InvariantCulture));
- var id = item.Id;
-
- lock (_activeRefreshes)
- {
- _activeRefreshes[id] = 0;
- }
-
+ _logger.LogInformation("OnRefreshStart {0}", item.Id.ToString("N", CultureInfo.InvariantCulture));
+ _activeRefreshes[item.Id] = 0;
RefreshStarted?.Invoke(this, new GenericEventArgs<BaseItem>(item));
}
public void OnRefreshComplete(BaseItem item)
{
- //_logger.LogInformation("OnRefreshComplete {0}", item.Id.ToString("N", CultureInfo.InvariantCulture));
- lock (_activeRefreshes)
- {
- _activeRefreshes.Remove(item.Id);
- }
+ _logger.LogInformation("OnRefreshComplete {0}", item.Id.ToString("N", CultureInfo.InvariantCulture));
+
+ _activeRefreshes.Remove(item.Id, out _);
RefreshCompleted?.Invoke(this, new GenericEventArgs<BaseItem>(item));
}
public double? GetRefreshProgress(Guid id)
{
- lock (_activeRefreshes)
+ if (_activeRefreshes.TryGetValue(id, out double value))
{
- if (_activeRefreshes.TryGetValue(id, out double value))
- {
- return value;
- }
-
- return null;
+ return value;
}
+
+ return null;
}
public void OnRefreshProgress(BaseItem item, double progress)
{
- //_logger.LogInformation("OnRefreshProgress {0} {1}", item.Id.ToString("N", CultureInfo.InvariantCulture), progress);
var id = item.Id;
-
- lock (_activeRefreshes)
- {
- if (_activeRefreshes.ContainsKey(id))
- {
- _activeRefreshes[id] = progress;
-
- RefreshProgress?.Invoke(this, new GenericEventArgs<Tuple<BaseItem, double>>(new Tuple<BaseItem, double>(item, progress)));
- }
- else
- {
- // TODO: Need to hunt down the conditions for this happening
- //throw new Exception(string.Format("Refresh for item {0} {1} is not in progress", item.GetType().Name, item.Id.ToString("N", CultureInfo.InvariantCulture)));
- }
- }
+ _logger.LogDebug("OnRefreshProgress {0} {1}", id.ToString("N", CultureInfo.InvariantCulture), progress);
+
+ // TODO: Need to hunt down the conditions for this happening
+ _activeRefreshes.AddOrUpdate(
+ id,
+ (_) => throw new Exception(
+ string.Format(
+ CultureInfo.InvariantCulture,
+ "Cannot update refresh progress of item '{0}' ({1}) because a refresh for this item is not running",
+ item.GetType().Name,
+ item.Id.ToString("N", CultureInfo.InvariantCulture))),
+ (_, __) => progress);
+
+ RefreshProgress?.Invoke(this, new GenericEventArgs<Tuple<BaseItem, double>>(new Tuple<BaseItem, double>(item, progress)));
}
private readonly SimplePriorityQueue<Tuple<Guid, MetadataRefreshOptions>> _refreshQueue =
@@ -1016,7 +1001,7 @@ namespace MediaBrowser.Providers.Manager
private async Task StartProcessingRefreshQueue()
{
- var libraryManager = _libraryManagerFactory();
+ var libraryManager = _libraryManager;
if (_disposed)
{
@@ -1040,10 +1025,9 @@ namespace MediaBrowser.Providers.Manager
// Try to throttle this a little bit.
await Task.Delay(100).ConfigureAwait(false);
- var artist = item as MusicArtist;
- var task = artist == null
- ? RefreshItem(item, refreshItem.Item2, cancellationToken)
- : RefreshArtist(artist, refreshItem.Item2, cancellationToken);
+ var task = item is MusicArtist artist
+ ? RefreshArtist(artist, refreshItem.Item2, cancellationToken)
+ : RefreshItem(item, refreshItem.Item2, cancellationToken);
await task.ConfigureAwait(false);
}
@@ -1095,7 +1079,7 @@ namespace MediaBrowser.Providers.Manager
private async Task RefreshArtist(MusicArtist item, MetadataRefreshOptions options, CancellationToken cancellationToken)
{
- var albums = _libraryManagerFactory()
+ var albums = _libraryManager
.GetItemList(new InternalItemsQuery
{
IncludeItemTypes = new[] { nameof(MusicAlbum) },
@@ -1125,8 +1109,7 @@ namespace MediaBrowser.Providers.Manager
}
}
- public Task RefreshFullItem(BaseItem item, MetadataRefreshOptions options,
- CancellationToken cancellationToken)
+ public Task RefreshFullItem(BaseItem item, MetadataRefreshOptions options, CancellationToken cancellationToken)
{
return RefreshItem(item, options, cancellationToken);
}
diff --git a/MediaBrowser.Providers/MediaBrowser.Providers.csproj b/MediaBrowser.Providers/MediaBrowser.Providers.csproj
index 8d373be28..1b3df63b6 100644
--- a/MediaBrowser.Providers/MediaBrowser.Providers.csproj
+++ b/MediaBrowser.Providers/MediaBrowser.Providers.csproj
@@ -1,5 +1,10 @@
<Project Sdk="Microsoft.NET.Sdk">
+ <!-- ProjectGuid is only included as a requirement for SonarQube analysis -->
+ <PropertyGroup>
+ <ProjectGuid>{442B5058-DCAF-4263-BB6A-F21E31120A1B}</ProjectGuid>
+ </PropertyGroup>
+
<ItemGroup>
<ProjectReference Include="..\MediaBrowser.Controller\MediaBrowser.Controller.csproj" />
<ProjectReference Include="..\MediaBrowser.Model\MediaBrowser.Model.csproj" />
@@ -11,11 +16,11 @@
</ItemGroup>
<ItemGroup>
- <PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="3.0.1" />
- <PackageReference Include="Microsoft.Extensions.Caching.Abstractions" Version="3.0.1" />
+ <PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="3.1.3" />
+ <PackageReference Include="Microsoft.Extensions.Caching.Abstractions" Version="3.1.3" />
<PackageReference Include="OptimizedPriorityQueue" Version="4.2.0" />
<PackageReference Include="PlaylistsNET" Version="1.0.4" />
- <PackageReference Include="TvDbSharper" Version="2.0.0" />
+ <PackageReference Include="TvDbSharper" Version="3.0.1" />
</ItemGroup>
<PropertyGroup>
@@ -24,4 +29,26 @@
<GenerateDocumentationFile>true</GenerateDocumentationFile>
</PropertyGroup>
+ <!-- Code Analyzers-->
+ <ItemGroup Condition=" '$(Configuration)' == 'Debug' ">
+ <PackageReference Include="Microsoft.CodeAnalysis.FxCopAnalyzers" Version="2.9.8" PrivateAssets="All" />
+ <PackageReference Include="SerilogAnalyzer" Version="0.15.0" PrivateAssets="All" />
+ <PackageReference Include="StyleCop.Analyzers" Version="1.1.118" PrivateAssets="All" />
+ <PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" PrivateAssets="All" />
+ </ItemGroup>
+
+ <PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
+ <CodeAnalysisRuleSet>../jellyfin.ruleset</CodeAnalysisRuleSet>
+ </PropertyGroup>
+
+ <ItemGroup>
+ <None Remove="Plugins\AudioDb\Configuration\config.html" />
+ <EmbeddedResource Include="Plugins\AudioDb\Configuration\config.html" />
+ </ItemGroup>
+
+ <ItemGroup>
+ <None Remove="Plugins\MusicBrainz\Configuration\config.html" />
+ <EmbeddedResource Include="Plugins\MusicBrainz\Configuration\config.html" />
+ </ItemGroup>
+
</Project>
diff --git a/MediaBrowser.Providers/MediaInfo/FFProbeProvider.cs b/MediaBrowser.Providers/MediaInfo/FFProbeProvider.cs
index 4e11fcbb2..6982568eb 100644
--- a/MediaBrowser.Providers/MediaInfo/FFProbeProvider.cs
+++ b/MediaBrowser.Providers/MediaInfo/FFProbeProvider.cs
@@ -46,7 +46,6 @@ namespace MediaBrowser.Providers.MediaInfo
private readonly IApplicationPaths _appPaths;
private readonly IJsonSerializer _json;
private readonly IEncodingManager _encodingManager;
- private readonly IFileSystem _fileSystem;
private readonly IServerConfigurationManager _config;
private readonly ISubtitleManager _subtitleManager;
private readonly IChapterManager _chapterManager;
@@ -121,7 +120,23 @@ namespace MediaBrowser.Providers.MediaInfo
}
private SubtitleResolver _subtitleResolver;
- public FFProbeProvider(ILogger logger, IMediaSourceManager mediaSourceManager, IChannelManager channelManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IItemRepository itemRepo, IBlurayExaminer blurayExaminer, ILocalizationManager localization, IApplicationPaths appPaths, IJsonSerializer json, IEncodingManager encodingManager, IFileSystem fileSystem, IServerConfigurationManager config, ISubtitleManager subtitleManager, IChapterManager chapterManager, ILibraryManager libraryManager)
+
+ public FFProbeProvider(
+ ILogger<FFProbeProvider> logger,
+ IMediaSourceManager mediaSourceManager,
+ IChannelManager channelManager,
+ IIsoManager isoManager,
+ IMediaEncoder mediaEncoder,
+ IItemRepository itemRepo,
+ IBlurayExaminer blurayExaminer,
+ ILocalizationManager localization,
+ IApplicationPaths appPaths,
+ IJsonSerializer json,
+ IEncodingManager encodingManager,
+ IServerConfigurationManager config,
+ ISubtitleManager subtitleManager,
+ IChapterManager chapterManager,
+ ILibraryManager libraryManager)
{
_logger = logger;
_isoManager = isoManager;
@@ -132,7 +147,6 @@ namespace MediaBrowser.Providers.MediaInfo
_appPaths = appPaths;
_json = json;
_encodingManager = encodingManager;
- _fileSystem = fileSystem;
_config = config;
_subtitleManager = subtitleManager;
_chapterManager = chapterManager;
@@ -140,7 +154,7 @@ namespace MediaBrowser.Providers.MediaInfo
_channelManager = channelManager;
_mediaSourceManager = mediaSourceManager;
- _subtitleResolver = new SubtitleResolver(BaseItem.LocalizationManager, fileSystem);
+ _subtitleResolver = new SubtitleResolver(BaseItem.LocalizationManager);
}
private readonly Task<ItemUpdateType> _cachedTask = Task.FromResult(ItemUpdateType.None);
@@ -177,7 +191,18 @@ namespace MediaBrowser.Providers.MediaInfo
FetchShortcutInfo(item);
}
- var prober = new FFProbeVideoInfo(_logger, _mediaSourceManager, _isoManager, _mediaEncoder, _itemRepo, _blurayExaminer, _localization, _appPaths, _json, _encodingManager, _fileSystem, _config, _subtitleManager, _chapterManager, _libraryManager);
+ var prober = new FFProbeVideoInfo(
+ _logger,
+ _mediaSourceManager,
+ _mediaEncoder,
+ _itemRepo,
+ _blurayExaminer,
+ _localization,
+ _encodingManager,
+ _config,
+ _subtitleManager,
+ _chapterManager,
+ _libraryManager);
return prober.ProbeVideo(item, options, cancellationToken);
}
diff --git a/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs b/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs
index 2b178d4d4..89496622f 100644
--- a/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs
+++ b/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
using System;
using System.Collections.Generic;
using System.Globalization;
@@ -25,7 +27,6 @@ using MediaBrowser.Model.Globalization;
using MediaBrowser.Model.IO;
using MediaBrowser.Model.MediaInfo;
using MediaBrowser.Model.Providers;
-using MediaBrowser.Model.Serialization;
using Microsoft.Extensions.Logging;
namespace MediaBrowser.Providers.MediaInfo
@@ -33,33 +34,38 @@ namespace MediaBrowser.Providers.MediaInfo
public class FFProbeVideoInfo
{
private readonly ILogger _logger;
- private readonly IIsoManager _isoManager;
private readonly IMediaEncoder _mediaEncoder;
private readonly IItemRepository _itemRepo;
private readonly IBlurayExaminer _blurayExaminer;
private readonly ILocalizationManager _localization;
- private readonly IApplicationPaths _appPaths;
- private readonly IJsonSerializer _json;
private readonly IEncodingManager _encodingManager;
- private readonly IFileSystem _fileSystem;
private readonly IServerConfigurationManager _config;
private readonly ISubtitleManager _subtitleManager;
private readonly IChapterManager _chapterManager;
private readonly ILibraryManager _libraryManager;
private readonly IMediaSourceManager _mediaSourceManager;
- public FFProbeVideoInfo(ILogger logger, IMediaSourceManager mediaSourceManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IItemRepository itemRepo, IBlurayExaminer blurayExaminer, ILocalizationManager localization, IApplicationPaths appPaths, IJsonSerializer json, IEncodingManager encodingManager, IFileSystem fileSystem, IServerConfigurationManager config, ISubtitleManager subtitleManager, IChapterManager chapterManager, ILibraryManager libraryManager)
+ private readonly long _dummyChapterDuration = TimeSpan.FromMinutes(5).Ticks;
+
+ public FFProbeVideoInfo(
+ ILogger logger,
+ IMediaSourceManager mediaSourceManager,
+ IMediaEncoder mediaEncoder,
+ IItemRepository itemRepo,
+ IBlurayExaminer blurayExaminer,
+ ILocalizationManager localization,
+ IEncodingManager encodingManager,
+ IServerConfigurationManager config,
+ ISubtitleManager subtitleManager,
+ IChapterManager chapterManager,
+ ILibraryManager libraryManager)
{
_logger = logger;
- _isoManager = isoManager;
_mediaEncoder = mediaEncoder;
_itemRepo = itemRepo;
_blurayExaminer = blurayExaminer;
_localization = localization;
- _appPaths = appPaths;
- _json = json;
_encodingManager = encodingManager;
- _fileSystem = fileSystem;
_config = config;
_subtitleManager = subtitleManager;
_chapterManager = chapterManager;
@@ -67,7 +73,8 @@ namespace MediaBrowser.Providers.MediaInfo
_mediaSourceManager = mediaSourceManager;
}
- public async Task<ItemUpdateType> ProbeVideo<T>(T item,
+ public async Task<ItemUpdateType> ProbeVideo<T>(
+ T item,
MetadataRefreshOptions options,
CancellationToken cancellationToken)
where T : Video
@@ -90,7 +97,6 @@ namespace MediaBrowser.Providers.MediaInfo
return ItemUpdateType.MetadataImport;
}
}
-
else if (item.VideoType == VideoType.BluRay)
{
var inputPath = item.Path;
@@ -121,7 +127,8 @@ namespace MediaBrowser.Providers.MediaInfo
return ItemUpdateType.MetadataImport;
}
- private Task<Model.MediaInfo.MediaInfo> GetMediaInfo(Video item,
+ private Task<Model.MediaInfo.MediaInfo> GetMediaInfo(
+ Video item,
string[] streamFileNames,
CancellationToken cancellationToken)
{
@@ -136,22 +143,24 @@ namespace MediaBrowser.Providers.MediaInfo
protocol = _mediaSourceManager.GetPathProtocol(path);
}
- return _mediaEncoder.GetMediaInfo(new MediaInfoRequest
- {
- PlayableStreamFileNames = streamFileNames,
- ExtractChapters = true,
- MediaType = DlnaProfileType.Video,
- MediaSource = new MediaSourceInfo
+ return _mediaEncoder.GetMediaInfo(
+ new MediaInfoRequest
{
- Path = path,
- Protocol = protocol,
- VideoType = item.VideoType
- }
-
- }, cancellationToken);
+ PlayableStreamFileNames = streamFileNames,
+ ExtractChapters = true,
+ MediaType = DlnaProfileType.Video,
+ MediaSource = new MediaSourceInfo
+ {
+ Path = path,
+ Protocol = protocol,
+ VideoType = item.VideoType
+ }
+ },
+ cancellationToken);
}
- protected async Task Fetch(Video video,
+ protected async Task Fetch(
+ Video video,
CancellationToken cancellationToken,
Model.MediaInfo.MediaInfo mediaInfo,
BlurayDiscInfo blurayInfo,
@@ -159,7 +168,7 @@ namespace MediaBrowser.Providers.MediaInfo
{
List<MediaStream> mediaStreams;
IReadOnlyList<MediaAttachment> mediaAttachments;
- List<ChapterInfo> chapters;
+ ChapterInfo[] chapters;
if (mediaInfo != null)
{
@@ -177,6 +186,7 @@ namespace MediaBrowser.Providers.MediaInfo
{
video.RunTimeTicks = mediaInfo.RunTimeTicks;
}
+
video.Size = mediaInfo.Size;
if (video.VideoType == VideoType.VideoFile)
@@ -189,19 +199,20 @@ namespace MediaBrowser.Providers.MediaInfo
{
video.Container = null;
}
+
video.Container = mediaInfo.Container;
- chapters = mediaInfo.Chapters == null ? new List<ChapterInfo>() : mediaInfo.Chapters.ToList();
+ chapters = mediaInfo.Chapters == null ? Array.Empty<ChapterInfo>() : mediaInfo.Chapters;
if (blurayInfo != null)
{
- FetchBdInfo(video, chapters, mediaStreams, blurayInfo);
+ FetchBdInfo(video, ref chapters, mediaStreams, blurayInfo);
}
}
else
{
mediaStreams = new List<MediaStream>();
mediaAttachments = Array.Empty<MediaAttachment>();
- chapters = new List<ChapterInfo>();
+ chapters = Array.Empty<ChapterInfo>();
}
await AddExternalSubtitles(video, mediaStreams, options, cancellationToken).ConfigureAwait(false);
@@ -231,9 +242,9 @@ namespace MediaBrowser.Providers.MediaInfo
if (options.MetadataRefreshMode == MetadataRefreshMode.FullRefresh ||
options.MetadataRefreshMode == MetadataRefreshMode.Default)
{
- if (chapters.Count == 0 && mediaStreams.Any(i => i.Type == MediaStreamType.Video))
+ if (chapters.Length == 0 && mediaStreams.Any(i => i.Type == MediaStreamType.Video))
{
- AddDummyChapters(video, chapters);
+ chapters = CreateDummyChapters(video);
}
NormalizeChapterNames(chapters);
@@ -246,28 +257,29 @@ namespace MediaBrowser.Providers.MediaInfo
await _encodingManager.RefreshChapterImages(video, options.DirectoryService, chapters, extractDuringScan, false, cancellationToken).ConfigureAwait(false);
- _chapterManager.SaveChapters(video.Id.ToString(), chapters);
+ _chapterManager.SaveChapters(video.Id, chapters);
}
}
- private void NormalizeChapterNames(List<ChapterInfo> chapters)
+ private void NormalizeChapterNames(ChapterInfo[] chapters)
{
- var index = 1;
-
- foreach (var chapter in chapters)
+ for (int i = 0; i < chapters.Length; i++)
{
+ string name = chapters[i].Name;
// Check if the name is empty and/or if the name is a time
// Some ripping programs do that.
- if (string.IsNullOrWhiteSpace(chapter.Name) ||
- TimeSpan.TryParse(chapter.Name, out var time))
+ if (string.IsNullOrWhiteSpace(name) ||
+ TimeSpan.TryParse(name, out _))
{
- chapter.Name = string.Format(_localization.GetLocalizedString("ChapterNameValue"), index.ToString(CultureInfo.InvariantCulture));
+ chapters[i].Name = string.Format(
+ CultureInfo.InvariantCulture,
+ _localization.GetLocalizedString("ChapterNameValue"),
+ (i + 1).ToString(CultureInfo.InvariantCulture));
}
- index++;
}
}
- private void FetchBdInfo(BaseItem item, List<ChapterInfo> chapters, List<MediaStream> mediaStreams, BlurayDiscInfo blurayInfo)
+ private void FetchBdInfo(BaseItem item, ref ChapterInfo[] chapters, List<MediaStream> mediaStreams, BlurayDiscInfo blurayInfo)
{
var video = (Video)item;
@@ -301,13 +313,15 @@ namespace MediaBrowser.Providers.MediaInfo
if (blurayInfo.Chapters != null)
{
- chapters.Clear();
-
- chapters.AddRange(blurayInfo.Chapters.Select(c => new ChapterInfo
+ double[] brChapter = blurayInfo.Chapters;
+ chapters = new ChapterInfo[brChapter.Length];
+ for (int i = 0; i < brChapter.Length; i++)
{
- StartPositionTicks = TimeSpan.FromSeconds(c).Ticks
-
- }));
+ chapters[i] = new ChapterInfo
+ {
+ StartPositionTicks = TimeSpan.FromSeconds(brChapter[i]).Ticks
+ };
+ }
}
videoStream = mediaStreams.FirstOrDefault(s => s.Type == MediaStreamType.Video);
@@ -477,12 +491,13 @@ namespace MediaBrowser.Providers.MediaInfo
/// <param name="options">The refreshOptions.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>Task.</returns>
- private async Task AddExternalSubtitles(Video video,
+ private async Task AddExternalSubtitles(
+ Video video,
List<MediaStream> currentStreams,
MetadataRefreshOptions options,
CancellationToken cancellationToken)
{
- var subtitleResolver = new SubtitleResolver(_localization, _fileSystem);
+ var subtitleResolver = new SubtitleResolver(_localization);
var startIndex = currentStreams.Count == 0 ? 0 : (currentStreams.Select(i => i.Index).Max() + 1);
var externalSubtitleStreams = subtitleResolver.GetExternalSubtitleStreams(video, startIndex, options.DirectoryService, false);
@@ -495,17 +510,17 @@ namespace MediaBrowser.Providers.MediaInfo
var libraryOptions = _libraryManager.GetLibraryOptions(video);
string[] subtitleDownloadLanguages;
- bool SkipIfEmbeddedSubtitlesPresent;
- bool SkipIfAudioTrackMatches;
- bool RequirePerfectMatch;
+ bool skipIfEmbeddedSubtitlesPresent;
+ bool skipIfAudioTrackMatches;
+ bool requirePerfectMatch;
bool enabled;
if (libraryOptions.SubtitleDownloadLanguages == null)
{
subtitleDownloadLanguages = subtitleOptions.DownloadLanguages;
- SkipIfEmbeddedSubtitlesPresent = subtitleOptions.SkipIfEmbeddedSubtitlesPresent;
- SkipIfAudioTrackMatches = subtitleOptions.SkipIfAudioTrackMatches;
- RequirePerfectMatch = subtitleOptions.RequirePerfectMatch;
+ skipIfEmbeddedSubtitlesPresent = subtitleOptions.SkipIfEmbeddedSubtitlesPresent;
+ skipIfAudioTrackMatches = subtitleOptions.SkipIfAudioTrackMatches;
+ requirePerfectMatch = subtitleOptions.RequirePerfectMatch;
enabled = (subtitleOptions.DownloadEpisodeSubtitles &&
video is Episode) ||
(subtitleOptions.DownloadMovieSubtitles &&
@@ -514,9 +529,9 @@ namespace MediaBrowser.Providers.MediaInfo
else
{
subtitleDownloadLanguages = libraryOptions.SubtitleDownloadLanguages;
- SkipIfEmbeddedSubtitlesPresent = libraryOptions.SkipSubtitlesIfEmbeddedSubtitlesPresent;
- SkipIfAudioTrackMatches = libraryOptions.SkipSubtitlesIfAudioTrackMatches;
- RequirePerfectMatch = libraryOptions.RequirePerfectSubtitleMatch;
+ skipIfEmbeddedSubtitlesPresent = libraryOptions.SkipSubtitlesIfEmbeddedSubtitlesPresent;
+ skipIfAudioTrackMatches = libraryOptions.SkipSubtitlesIfAudioTrackMatches;
+ requirePerfectMatch = libraryOptions.RequirePerfectSubtitleMatch;
enabled = true;
}
@@ -526,9 +541,9 @@ namespace MediaBrowser.Providers.MediaInfo
_subtitleManager)
.DownloadSubtitles(video,
currentStreams.Concat(externalSubtitleStreams).ToList(),
- SkipIfEmbeddedSubtitlesPresent,
- SkipIfAudioTrackMatches,
- RequirePerfectMatch,
+ skipIfEmbeddedSubtitlesPresent,
+ skipIfAudioTrackMatches,
+ requirePerfectMatch,
subtitleDownloadLanguages,
libraryOptions.DisabledSubtitleFetchers,
libraryOptions.SubtitleFetcherOrder,
@@ -547,49 +562,51 @@ namespace MediaBrowser.Providers.MediaInfo
}
/// <summary>
- /// The dummy chapter duration
- /// </summary>
- private readonly long _dummyChapterDuration = TimeSpan.FromMinutes(5).Ticks;
-
- /// <summary>
- /// Adds the dummy chapters.
+ /// Creates dummy chapters.
/// </summary>
/// <param name="video">The video.</param>
- /// <param name="chapters">The chapters.</param>
- private void AddDummyChapters(Video video, List<ChapterInfo> chapters)
+ /// <return>An array of dummy chapters.</returns>
+ private ChapterInfo[] CreateDummyChapters(Video video)
{
var runtime = video.RunTimeTicks ?? 0;
if (runtime < 0)
{
- throw new ArgumentException(string.Format("{0} has invalid runtime of {1}", video.Name, runtime));
+ throw new ArgumentException(
+ string.Format(
+ CultureInfo.InvariantCulture,
+ "{0} has invalid runtime of {1}",
+ video.Name,
+ runtime));
}
if (runtime < _dummyChapterDuration)
{
- return;
+ return Array.Empty<ChapterInfo>();
}
- long currentChapterTicks = 0;
- var index = 1;
-
// Limit to 100 chapters just in case there's some incorrect metadata here
- while (currentChapterTicks < runtime && index < 100)
+ int chapterCount = (int)Math.Min(runtime / _dummyChapterDuration, 100);
+ var chapters = new ChapterInfo[chapterCount];
+
+ long currentChapterTicks = 0;
+ for (int i = 0; i < chapterCount; i++)
{
- chapters.Add(new ChapterInfo
+ chapters[i] = new ChapterInfo
{
StartPositionTicks = currentChapterTicks
- });
+ };
- index++;
currentChapterTicks += _dummyChapterDuration;
}
+
+ return chapters;
}
private string[] FetchFromDvdLib(Video item)
{
var path = item.Path;
- var dvd = new Dvd(path, _fileSystem);
+ var dvd = new Dvd(path);
var primaryTitle = dvd.Titles.OrderByDescending(GetRuntime).FirstOrDefault();
diff --git a/MediaBrowser.Providers/MediaInfo/SubtitleResolver.cs b/MediaBrowser.Providers/MediaInfo/SubtitleResolver.cs
index 8195591e1..2bbe8a968 100644
--- a/MediaBrowser.Providers/MediaInfo/SubtitleResolver.cs
+++ b/MediaBrowser.Providers/MediaInfo/SubtitleResolver.cs
@@ -5,7 +5,6 @@ using System.Linq;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Entities;
-using MediaBrowser.Model.Extensions;
using MediaBrowser.Model.Globalization;
using MediaBrowser.Model.IO;
@@ -14,7 +13,6 @@ namespace MediaBrowser.Providers.MediaInfo
public class SubtitleResolver
{
private readonly ILocalizationManager _localization;
- private readonly IFileSystem _fileSystem;
private static readonly HashSet<string> SubtitleExtensions = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
{
@@ -27,16 +25,16 @@ namespace MediaBrowser.Providers.MediaInfo
".vtt"
};
- public SubtitleResolver(ILocalizationManager localization, IFileSystem fileSystem)
+ public SubtitleResolver(ILocalizationManager localization)
{
_localization = localization;
- _fileSystem = fileSystem;
}
- public List<MediaStream> GetExternalSubtitleStreams(Video video,
- int startIndex,
- IDirectoryService directoryService,
- bool clearCache)
+ public List<MediaStream> GetExternalSubtitleStreams(
+ Video video,
+ int startIndex,
+ IDirectoryService directoryService,
+ bool clearCache)
{
var streams = new List<MediaStream>();
diff --git a/MediaBrowser.Providers/MediaInfo/SubtitleScheduledTask.cs b/MediaBrowser.Providers/MediaInfo/SubtitleScheduledTask.cs
index 74f41f9df..2615f2dbb 100644
--- a/MediaBrowser.Providers/MediaInfo/SubtitleScheduledTask.cs
+++ b/MediaBrowser.Providers/MediaInfo/SubtitleScheduledTask.cs
@@ -14,6 +14,7 @@ using MediaBrowser.Model.Providers;
using MediaBrowser.Model.Serialization;
using MediaBrowser.Model.Tasks;
using Microsoft.Extensions.Logging;
+using MediaBrowser.Model.Globalization;
namespace MediaBrowser.Providers.MediaInfo
{
@@ -25,8 +26,16 @@ namespace MediaBrowser.Providers.MediaInfo
private readonly IMediaSourceManager _mediaSourceManager;
private readonly ILogger _logger;
private readonly IJsonSerializer _json;
-
- public SubtitleScheduledTask(ILibraryManager libraryManager, IJsonSerializer json, IServerConfigurationManager config, ISubtitleManager subtitleManager, ILogger logger, IMediaSourceManager mediaSourceManager)
+ private readonly ILocalizationManager _localization;
+
+ public SubtitleScheduledTask(
+ ILibraryManager libraryManager,
+ IJsonSerializer json,
+ IServerConfigurationManager config,
+ ISubtitleManager subtitleManager,
+ ILogger<SubtitleScheduledTask> logger,
+ IMediaSourceManager mediaSourceManager,
+ ILocalizationManager localization)
{
_libraryManager = libraryManager;
_config = config;
@@ -34,6 +43,7 @@ namespace MediaBrowser.Providers.MediaInfo
_logger = logger;
_mediaSourceManager = mediaSourceManager;
_json = json;
+ _localization = localization;
}
private SubtitleOptions GetOptions()
@@ -198,11 +208,11 @@ namespace MediaBrowser.Providers.MediaInfo
};
}
- public string Name => "Download missing subtitles";
+ public string Name => _localization.GetLocalizedString("TaskDownloadMissingSubtitles");
- public string Description => "Searches the internet for missing subtitles based on metadata configuration.";
+ public string Description => _localization.GetLocalizedString("TaskDownloadMissingSubtitlesDescription");
- public string Category => "Library";
+ public string Category => _localization.GetLocalizedString("TasksLibraryCategory");
public string Key => "DownloadSubtitles";
diff --git a/MediaBrowser.Providers/MediaInfo/VideoImageProvider.cs b/MediaBrowser.Providers/MediaInfo/VideoImageProvider.cs
index 95b915b3d..f40570040 100644
--- a/MediaBrowser.Providers/MediaInfo/VideoImageProvider.cs
+++ b/MediaBrowser.Providers/MediaInfo/VideoImageProvider.cs
@@ -20,7 +20,7 @@ namespace MediaBrowser.Providers.MediaInfo
private readonly ILogger _logger;
private readonly IFileSystem _fileSystem;
- public VideoImageProvider(IMediaEncoder mediaEncoder, ILogger logger, IFileSystem fileSystem)
+ public VideoImageProvider(IMediaEncoder mediaEncoder, ILogger<VideoImageProvider> logger, IFileSystem fileSystem)
{
_mediaEncoder = mediaEncoder;
_logger = logger;
diff --git a/MediaBrowser.Providers/Movies/MovieMetadataService.cs b/MediaBrowser.Providers/Movies/MovieMetadataService.cs
index c6cc5c7dc..1e2c325d9 100644
--- a/MediaBrowser.Providers/Movies/MovieMetadataService.cs
+++ b/MediaBrowser.Providers/Movies/MovieMetadataService.cs
@@ -13,7 +13,7 @@ namespace MediaBrowser.Providers.Movies
{
public MovieMetadataService(
IServerConfigurationManager serverConfigurationManager,
- ILogger logger,
+ ILogger<MovieMetadataService> logger,
IProviderManager providerManager,
IFileSystem fileSystem,
ILibraryManager libraryManager)
diff --git a/MediaBrowser.Providers/Movies/TrailerMetadataService.cs b/MediaBrowser.Providers/Movies/TrailerMetadataService.cs
index 53b556940..2e6f762b8 100644
--- a/MediaBrowser.Providers/Movies/TrailerMetadataService.cs
+++ b/MediaBrowser.Providers/Movies/TrailerMetadataService.cs
@@ -13,7 +13,7 @@ namespace MediaBrowser.Providers.Movies
{
public TrailerMetadataService(
IServerConfigurationManager serverConfigurationManager,
- ILogger logger,
+ ILogger<TrailerMetadataService> logger,
IProviderManager providerManager,
IFileSystem fileSystem,
ILibraryManager libraryManager)
diff --git a/MediaBrowser.Providers/Music/AlbumMetadataService.cs b/MediaBrowser.Providers/Music/AlbumMetadataService.cs
index 69133c1c1..ed6c01968 100644
--- a/MediaBrowser.Providers/Music/AlbumMetadataService.cs
+++ b/MediaBrowser.Providers/Music/AlbumMetadataService.cs
@@ -17,7 +17,7 @@ namespace MediaBrowser.Providers.Music
{
public AlbumMetadataService(
IServerConfigurationManager serverConfigurationManager,
- ILogger logger,
+ ILogger<AlbumMetadataService> logger,
IProviderManager providerManager,
IFileSystem fileSystem,
ILibraryManager libraryManager)
diff --git a/MediaBrowser.Providers/Music/ArtistMetadataService.cs b/MediaBrowser.Providers/Music/ArtistMetadataService.cs
index 1f099c60f..5a30260a5 100644
--- a/MediaBrowser.Providers/Music/ArtistMetadataService.cs
+++ b/MediaBrowser.Providers/Music/ArtistMetadataService.cs
@@ -15,7 +15,7 @@ namespace MediaBrowser.Providers.Music
{
public ArtistMetadataService(
IServerConfigurationManager serverConfigurationManager,
- ILogger logger,
+ ILogger<ArtistMetadataService> logger,
IProviderManager providerManager,
IFileSystem fileSystem,
ILibraryManager libraryManager)
@@ -31,10 +31,10 @@ namespace MediaBrowser.Providers.Music
{
return item.IsAccessedByName
? item.GetTaggedItems(new InternalItemsQuery
- {
- Recursive = true,
- IsFolder = false
- })
+ {
+ Recursive = true,
+ IsFolder = false
+ })
: item.GetRecursiveChildren(i => i is IHasArtist && !i.IsFolder);
}
diff --git a/MediaBrowser.Providers/Music/AudioMetadataService.cs b/MediaBrowser.Providers/Music/AudioMetadataService.cs
index 4d4739cef..e726fa1e2 100644
--- a/MediaBrowser.Providers/Music/AudioMetadataService.cs
+++ b/MediaBrowser.Providers/Music/AudioMetadataService.cs
@@ -13,7 +13,7 @@ namespace MediaBrowser.Providers.Music
{
public AudioMetadataService(
IServerConfigurationManager serverConfigurationManager,
- ILogger logger,
+ ILogger<AudioMetadataService> logger,
IProviderManager providerManager,
IFileSystem fileSystem,
ILibraryManager libraryManager)
diff --git a/MediaBrowser.Providers/Music/MusicExternalIds.cs b/MediaBrowser.Providers/Music/MusicExternalIds.cs
index 585c98af9..628b9a9a1 100644
--- a/MediaBrowser.Providers/Music/MusicExternalIds.cs
+++ b/MediaBrowser.Providers/Music/MusicExternalIds.cs
@@ -1,105 +1,9 @@
using MediaBrowser.Controller.Entities;
-using MediaBrowser.Controller.Entities.Audio;
using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Entities;
namespace MediaBrowser.Providers.Music
{
- public class MusicBrainzReleaseGroupExternalId : IExternalId
- {
- /// <inheritdoc />
- public string Name => "MusicBrainz Release Group";
-
- /// <inheritdoc />
- public string Key => MetadataProviders.MusicBrainzReleaseGroup.ToString();
-
- /// <inheritdoc />
- public string UrlFormatString => "https://musicbrainz.org/release-group/{0}";
-
- /// <inheritdoc />
- public bool Supports(IHasProviderIds item)
- => item is Audio || item is MusicAlbum;
- }
-
- public class MusicBrainzAlbumArtistExternalId : IExternalId
- {
- /// <inheritdoc />
- public string Name => "MusicBrainz Album Artist";
-
- /// <inheritdoc />
- public string Key => MetadataProviders.MusicBrainzAlbumArtist.ToString();
-
- /// <inheritdoc />
- public string UrlFormatString => "https://musicbrainz.org/artist/{0}";
-
- /// <inheritdoc />
- public bool Supports(IHasProviderIds item)
- => item is Audio;
- }
-
- public class MusicBrainzAlbumExternalId : IExternalId
- {
- /// <inheritdoc />
- public string Name => "MusicBrainz Album";
-
- /// <inheritdoc />
- public string Key => MetadataProviders.MusicBrainzAlbum.ToString();
-
- /// <inheritdoc />
- public string UrlFormatString => "https://musicbrainz.org/release/{0}";
-
- /// <inheritdoc />
- public bool Supports(IHasProviderIds item)
- => item is Audio || item is MusicAlbum;
- }
-
- public class MusicBrainzArtistExternalId : IExternalId
- {
- /// <inheritdoc />
- public string Name => "MusicBrainz";
-
- /// <inheritdoc />
- public string Key => MetadataProviders.MusicBrainzArtist.ToString();
-
- /// <inheritdoc />
- public string UrlFormatString => "https://musicbrainz.org/artist/{0}";
-
- /// <inheritdoc />
- public bool Supports(IHasProviderIds item) => item is MusicArtist;
- }
-
- public class MusicBrainzOtherArtistExternalId : IExternalId
- {
- /// <inheritdoc />
- public string Name => "MusicBrainz Artist";
-
- /// <inheritdoc />
-
- public string Key => MetadataProviders.MusicBrainzArtist.ToString();
-
- /// <inheritdoc />
- public string UrlFormatString => "https://musicbrainz.org/artist/{0}";
-
- /// <inheritdoc />
- public bool Supports(IHasProviderIds item)
- => item is Audio || item is MusicAlbum;
- }
-
- public class MusicBrainzTrackId : IExternalId
- {
- /// <inheritdoc />
- public string Name => "MusicBrainz Track";
-
- /// <inheritdoc />
- public string Key => MetadataProviders.MusicBrainzTrack.ToString();
-
- /// <inheritdoc />
- public string UrlFormatString => "https://musicbrainz.org/track/{0}";
-
- /// <inheritdoc />
- public bool Supports(IHasProviderIds item) => item is Audio;
- }
-
public class ImvdbId : IExternalId
{
/// <inheritdoc />
diff --git a/MediaBrowser.Providers/Music/MusicVideoMetadataService.cs b/MediaBrowser.Providers/Music/MusicVideoMetadataService.cs
index bbf0cd8db..d653e1063 100644
--- a/MediaBrowser.Providers/Music/MusicVideoMetadataService.cs
+++ b/MediaBrowser.Providers/Music/MusicVideoMetadataService.cs
@@ -13,7 +13,7 @@ namespace MediaBrowser.Providers.Music
{
public MusicVideoMetadataService(
IServerConfigurationManager serverConfigurationManager,
- ILogger logger,
+ ILogger<MusicVideoMetadataService> logger,
IProviderManager providerManager,
IFileSystem fileSystem,
ILibraryManager libraryManager)
diff --git a/MediaBrowser.Providers/MusicGenres/MusicGenreMetadataService.cs b/MediaBrowser.Providers/MusicGenres/MusicGenreMetadataService.cs
index d74e91ad6..bb47de40b 100644
--- a/MediaBrowser.Providers/MusicGenres/MusicGenreMetadataService.cs
+++ b/MediaBrowser.Providers/MusicGenres/MusicGenreMetadataService.cs
@@ -13,7 +13,7 @@ namespace MediaBrowser.Providers.MusicGenres
{
public MusicGenreMetadataService(
IServerConfigurationManager serverConfigurationManager,
- ILogger logger,
+ ILogger<MusicGenreMetadataService> logger,
IProviderManager providerManager,
IFileSystem fileSystem,
ILibraryManager libraryManager)
diff --git a/MediaBrowser.Providers/People/PersonMetadataService.cs b/MediaBrowser.Providers/People/PersonMetadataService.cs
index cdc3c77b7..804f3f3e3 100644
--- a/MediaBrowser.Providers/People/PersonMetadataService.cs
+++ b/MediaBrowser.Providers/People/PersonMetadataService.cs
@@ -13,7 +13,7 @@ namespace MediaBrowser.Providers.People
{
public PersonMetadataService(
IServerConfigurationManager serverConfigurationManager,
- ILogger logger,
+ ILogger<PersonMetadataService> logger,
IProviderManager providerManager,
IFileSystem fileSystem,
ILibraryManager libraryManager)
diff --git a/MediaBrowser.Providers/Photos/PhotoAlbumMetadataService.cs b/MediaBrowser.Providers/Photos/PhotoAlbumMetadataService.cs
index 845404dfb..af8f7a262 100644
--- a/MediaBrowser.Providers/Photos/PhotoAlbumMetadataService.cs
+++ b/MediaBrowser.Providers/Photos/PhotoAlbumMetadataService.cs
@@ -13,7 +13,7 @@ namespace MediaBrowser.Providers.Photos
{
public PhotoAlbumMetadataService(
IServerConfigurationManager serverConfigurationManager,
- ILogger logger,
+ ILogger<PhotoAlbumMetadataService> logger,
IProviderManager providerManager,
IFileSystem fileSystem,
ILibraryManager libraryManager)
diff --git a/MediaBrowser.Providers/Photos/PhotoMetadataService.cs b/MediaBrowser.Providers/Photos/PhotoMetadataService.cs
index 5d6ff8814..579b5a4d0 100644
--- a/MediaBrowser.Providers/Photos/PhotoMetadataService.cs
+++ b/MediaBrowser.Providers/Photos/PhotoMetadataService.cs
@@ -13,7 +13,7 @@ namespace MediaBrowser.Providers.Photos
{
public PhotoMetadataService(
IServerConfigurationManager serverConfigurationManager,
- ILogger logger,
+ ILogger<PhotoMetadataService> logger,
IProviderManager providerManager,
IFileSystem fileSystem,
ILibraryManager libraryManager)
diff --git a/MediaBrowser.Providers/Playlists/PlaylistItemsProvider.cs b/MediaBrowser.Providers/Playlists/PlaylistItemsProvider.cs
index dacb63f84..ae837c591 100644
--- a/MediaBrowser.Providers/Playlists/PlaylistItemsProvider.cs
+++ b/MediaBrowser.Providers/Playlists/PlaylistItemsProvider.cs
@@ -23,7 +23,7 @@ namespace MediaBrowser.Providers.Playlists
private ILogger _logger;
private IFileSystem _fileSystem;
- public PlaylistItemsProvider(IFileSystem fileSystem, ILogger logger)
+ public PlaylistItemsProvider(IFileSystem fileSystem, ILogger<PlaylistItemsProvider> logger)
{
_fileSystem = fileSystem;
_logger = logger;
diff --git a/MediaBrowser.Providers/Playlists/PlaylistMetadataService.cs b/MediaBrowser.Providers/Playlists/PlaylistMetadataService.cs
index 32bd6c282..a41362ea3 100644
--- a/MediaBrowser.Providers/Playlists/PlaylistMetadataService.cs
+++ b/MediaBrowser.Providers/Playlists/PlaylistMetadataService.cs
@@ -15,7 +15,7 @@ namespace MediaBrowser.Providers.Playlists
{
public PlaylistMetadataService(
IServerConfigurationManager serverConfigurationManager,
- ILogger logger,
+ ILogger<PlaylistMetadataService> logger,
IProviderManager providerManager,
IFileSystem fileSystem,
ILibraryManager libraryManager)
diff --git a/MediaBrowser.Providers/Music/AudioDbAlbumImageProvider.cs b/MediaBrowser.Providers/Plugins/AudioDb/AlbumImageProvider.cs
index 85a87630d..dee2d59f0 100644
--- a/MediaBrowser.Providers/Music/AudioDbAlbumImageProvider.cs
+++ b/MediaBrowser.Providers/Plugins/AudioDb/AlbumImageProvider.cs
@@ -10,7 +10,7 @@ using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Providers;
using MediaBrowser.Model.Serialization;
-namespace MediaBrowser.Providers.Music
+namespace MediaBrowser.Providers.Plugins.AudioDb
{
public class AudioDbAlbumImageProvider : IRemoteImageProvider, IHasOrder
{
@@ -102,6 +102,7 @@ namespace MediaBrowser.Providers.Music
}
/// <inheritdoc />
- public bool Supports(BaseItem item) => item is MusicAlbum;
+ public bool Supports(BaseItem item)
+ => Plugin.Instance.Configuration.Enable && item is MusicAlbum;
}
}
diff --git a/MediaBrowser.Providers/Music/AudioDbAlbumProvider.cs b/MediaBrowser.Providers/Plugins/AudioDb/AlbumProvider.cs
index e61d8792c..1a0e87871 100644
--- a/MediaBrowser.Providers/Music/AudioDbAlbumProvider.cs
+++ b/MediaBrowser.Providers/Plugins/AudioDb/AlbumProvider.cs
@@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
+using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using MediaBrowser.Common.Configuration;
@@ -15,8 +16,9 @@ using MediaBrowser.Model.Entities;
using MediaBrowser.Model.IO;
using MediaBrowser.Model.Providers;
using MediaBrowser.Model.Serialization;
+using MediaBrowser.Providers.Music;
-namespace MediaBrowser.Providers.Music
+namespace MediaBrowser.Providers.Plugins.AudioDb
{
public class AudioDbAlbumProvider : IRemoteMetadataProvider<MusicAlbum, AlbumInfo>, IHasOrder
{
@@ -53,6 +55,12 @@ namespace MediaBrowser.Providers.Music
{
var result = new MetadataResult<MusicAlbum>();
+ // TODO maybe remove when artist metadata can be disabled
+ if (!Plugin.Instance.Configuration.Enable)
+ {
+ return result;
+ }
+
var id = info.GetReleaseGroupId();
if (!string.IsNullOrWhiteSpace(id))
@@ -76,6 +84,11 @@ namespace MediaBrowser.Providers.Music
private void ProcessResult(MusicAlbum item, Album result, string preferredLanguage)
{
+ if (Plugin.Instance.Configuration.ReplaceAlbumName && !string.IsNullOrWhiteSpace(result.strAlbum))
+ {
+ item.Album = result.strAlbum;
+ }
+
if (!string.IsNullOrWhiteSpace(result.strArtist))
{
item.AlbumArtists = new string[] { result.strArtist };
@@ -164,11 +177,10 @@ namespace MediaBrowser.Providers.Music
{
Url = url,
CancellationToken = cancellationToken
-
},
- "GET").ConfigureAwait(false))
+ HttpMethod.Get).ConfigureAwait(false))
using (var response = httpResponse.Content)
- using (var xmlFileStream = _fileSystem.GetFileStream(path, FileOpenMode.Create, FileAccessMode.Write, FileShareMode.Read, true))
+ using (var xmlFileStream = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.Read, IODefaults.FileStreamBufferSize, true))
{
await response.CopyToAsync(xmlFileStream).ConfigureAwait(false);
}
diff --git a/MediaBrowser.Providers/Music/AudioDbArtistImageProvider.cs b/MediaBrowser.Providers/Plugins/AudioDb/ArtistImageProvider.cs
index b9315744f..18afd5dd5 100644
--- a/MediaBrowser.Providers/Music/AudioDbArtistImageProvider.cs
+++ b/MediaBrowser.Providers/Plugins/AudioDb/ArtistImageProvider.cs
@@ -10,7 +10,7 @@ using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Providers;
using MediaBrowser.Model.Serialization;
-namespace MediaBrowser.Providers.Music
+namespace MediaBrowser.Providers.Plugins.AudioDb
{
public class AudioDbArtistImageProvider : IRemoteImageProvider, IHasOrder
{
@@ -143,6 +143,7 @@ namespace MediaBrowser.Providers.Music
}
/// <inheritdoc />
- public bool Supports(BaseItem item) => item is MusicArtist;
+ public bool Supports(BaseItem item)
+ => Plugin.Instance.Configuration.Enable && item is MusicArtist;
}
}
diff --git a/MediaBrowser.Providers/Music/AudioDbArtistProvider.cs b/MediaBrowser.Providers/Plugins/AudioDb/ArtistProvider.cs
index 7e5893d49..df0f3df8f 100644
--- a/MediaBrowser.Providers/Music/AudioDbArtistProvider.cs
+++ b/MediaBrowser.Providers/Plugins/AudioDb/ArtistProvider.cs
@@ -2,6 +2,7 @@ using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
+using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using MediaBrowser.Common.Configuration;
@@ -14,8 +15,9 @@ using MediaBrowser.Model.Entities;
using MediaBrowser.Model.IO;
using MediaBrowser.Model.Providers;
using MediaBrowser.Model.Serialization;
+using MediaBrowser.Providers.Music;
-namespace MediaBrowser.Providers.Music
+namespace MediaBrowser.Providers.Plugins.AudioDb
{
public class AudioDbArtistProvider : IRemoteMetadataProvider<MusicArtist, ArtistInfo>, IHasOrder
{
@@ -54,6 +56,12 @@ namespace MediaBrowser.Providers.Music
{
var result = new MetadataResult<MusicArtist>();
+ // TODO maybe remove when artist metadata can be disabled
+ if (!Plugin.Instance.Configuration.Enable)
+ {
+ return result;
+ }
+
var id = info.GetMusicBrainzArtistId();
if (!string.IsNullOrWhiteSpace(id))
@@ -152,12 +160,12 @@ namespace MediaBrowser.Providers.Music
CancellationToken = cancellationToken,
BufferContent = true
},
- "GET").ConfigureAwait(false))
+ HttpMethod.Get).ConfigureAwait(false))
using (var response = httpResponse.Content)
{
Directory.CreateDirectory(Path.GetDirectoryName(path));
- using (var xmlFileStream = _fileSystem.GetFileStream(path, FileOpenMode.Create, FileAccessMode.Write, FileShareMode.Read, true))
+ using (var xmlFileStream = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.Read, IODefaults.FileStreamBufferSize, true))
{
await response.CopyToAsync(xmlFileStream).ConfigureAwait(false);
}
diff --git a/MediaBrowser.Providers/Plugins/AudioDb/Configuration/PluginConfiguration.cs b/MediaBrowser.Providers/Plugins/AudioDb/Configuration/PluginConfiguration.cs
new file mode 100644
index 000000000..ad3c7eb4b
--- /dev/null
+++ b/MediaBrowser.Providers/Plugins/AudioDb/Configuration/PluginConfiguration.cs
@@ -0,0 +1,11 @@
+using MediaBrowser.Model.Plugins;
+
+namespace MediaBrowser.Providers.Plugins.AudioDb
+{
+ public class PluginConfiguration : BasePluginConfiguration
+ {
+ public bool Enable { get; set; }
+
+ public bool ReplaceAlbumName { get; set; }
+ }
+}
diff --git a/MediaBrowser.Providers/Plugins/AudioDb/Configuration/config.html b/MediaBrowser.Providers/Plugins/AudioDb/Configuration/config.html
new file mode 100644
index 000000000..34494644d
--- /dev/null
+++ b/MediaBrowser.Providers/Plugins/AudioDb/Configuration/config.html
@@ -0,0 +1,57 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>AudioDB</title>
+</head>
+<body>
+ <div data-role="page" class="page type-interior pluginConfigurationPage configPage" data-require="emby-input,emby-button,emby-checkbox">
+ <div data-role="content">
+ <div class="content-primary">
+ <form class="configForm">
+ <label class="checkboxContainer">
+ <input is="emby-checkbox" type="checkbox" id="enable" />
+ <span>Enable this provider for metadata searches on artists and albums.</span>
+ </label>
+ <label class="checkboxContainer">
+ <input is="emby-checkbox" type="checkbox" id="replaceAlbumName" />
+ <span>When an album is found during a metadata search, replace the name with the value on the server.</span>
+ </label>
+ <br />
+ <div>
+ <button is="emby-button" type="submit" class="raised button-submit block"><span>Save</span></button>
+ </div>
+ </form>
+ </div>
+ </div>
+ <script type="text/javascript">
+ var PluginConfig = {
+ pluginId: "a629c0da-fac5-4c7e-931a-7174223f14c8"
+ };
+
+ $('.configPage').on('pageshow', function () {
+ Dashboard.showLoadingMsg();
+ ApiClient.getPluginConfiguration(PluginConfig.pluginId).then(function (config) {
+ $('#enable').checked(config.Enable);
+ $('#replaceAlbumName').checked(config.ReplaceAlbumName);
+
+ Dashboard.hideLoadingMsg();
+ });
+ });
+
+ $('.configForm').on('submit', function (e) {
+ Dashboard.showLoadingMsg();
+
+ var form = this;
+ ApiClient.getPluginConfiguration(PluginConfig.pluginId).then(function (config) {
+ config.Enable = $('#enable', form).checked();
+ config.ReplaceAlbumName = $('#replaceAlbumName', form).checked();
+
+ ApiClient.updatePluginConfiguration(PluginConfig.pluginId, config).then(Dashboard.processPluginConfigurationUpdateResult);
+ });
+
+ return false;
+ });
+ </script>
+ </div>
+</body>
+</html>
diff --git a/MediaBrowser.Providers/Music/AudioDbExternalIds.cs b/MediaBrowser.Providers/Plugins/AudioDb/ExternalIds.cs
index c866d12de..2d8cb431c 100644
--- a/MediaBrowser.Providers/Music/AudioDbExternalIds.cs
+++ b/MediaBrowser.Providers/Plugins/AudioDb/ExternalIds.cs
@@ -2,7 +2,7 @@ using MediaBrowser.Controller.Entities.Audio;
using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Entities;
-namespace MediaBrowser.Providers.Music
+namespace MediaBrowser.Providers.Plugins.AudioDb
{
public class AudioDbAlbumExternalId : IExternalId
{
@@ -16,8 +16,7 @@ namespace MediaBrowser.Providers.Music
public string UrlFormatString => "https://www.theaudiodb.com/album/{0}";
/// <inheritdoc />
- public bool Supports(IHasProviderIds item)
- => item is MusicAlbum;
+ public bool Supports(IHasProviderIds item) => item is MusicAlbum;
}
public class AudioDbOtherAlbumExternalId : IExternalId
@@ -62,7 +61,6 @@ namespace MediaBrowser.Providers.Music
public string UrlFormatString => "https://www.theaudiodb.com/artist/{0}";
/// <inheritdoc />
- public bool Supports(IHasProviderIds item)
- => item is Audio || item is MusicAlbum;
+ public bool Supports(IHasProviderIds item) => item is Audio || item is MusicAlbum;
}
}
diff --git a/MediaBrowser.Providers/Plugins/AudioDb/Plugin.cs b/MediaBrowser.Providers/Plugins/AudioDb/Plugin.cs
new file mode 100644
index 000000000..8532c4df3
--- /dev/null
+++ b/MediaBrowser.Providers/Plugins/AudioDb/Plugin.cs
@@ -0,0 +1,35 @@
+using System;
+using System.Collections.Generic;
+using MediaBrowser.Common.Configuration;
+using MediaBrowser.Common.Plugins;
+using MediaBrowser.Model.Plugins;
+using MediaBrowser.Model.Serialization;
+
+namespace MediaBrowser.Providers.Plugins.AudioDb
+{
+ public class Plugin : BasePlugin<PluginConfiguration>, IHasWebPages
+ {
+ public static Plugin Instance { get; private set; }
+
+ public override Guid Id => new Guid("a629c0da-fac5-4c7e-931a-7174223f14c8");
+
+ public override string Name => "AudioDB";
+
+ public override string Description => "Get artist and album metadata or images from AudioDB.";
+
+ public Plugin(IApplicationPaths applicationPaths, IXmlSerializer xmlSerializer)
+ : base(applicationPaths, xmlSerializer)
+ {
+ Instance = this;
+ }
+
+ public IEnumerable<PluginPageInfo> GetPages()
+ {
+ yield return new PluginPageInfo
+ {
+ Name = Name,
+ EmbeddedResourcePath = GetType().Namespace + ".Configuration.config.html"
+ };
+ }
+ }
+}
diff --git a/MediaBrowser.Providers/Music/MusicBrainzAlbumProvider.cs b/MediaBrowser.Providers/Plugins/MusicBrainz/AlbumProvider.cs
index 8e71b625e..31cdaf616 100644
--- a/MediaBrowser.Providers/Music/MusicBrainzAlbumProvider.cs
+++ b/MediaBrowser.Providers/Plugins/MusicBrainz/AlbumProvider.cs
@@ -5,6 +5,7 @@ using System.Globalization;
using System.IO;
using System.Linq;
using System.Net;
+using System.Net.Http;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
@@ -15,7 +16,7 @@ using MediaBrowser.Controller.Entities.Audio;
using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Providers;
-using Microsoft.Extensions.Configuration;
+using MediaBrowser.Providers.Plugins.MusicBrainz;
using Microsoft.Extensions.Logging;
namespace MediaBrowser.Providers.Music
@@ -28,7 +29,7 @@ namespace MediaBrowser.Providers.Music
/// Be prudent, use a value slightly above the minimun required.
/// https://musicbrainz.org/doc/XML_Web_Service/Rate_Limiting
/// </summary>
- private const long MusicBrainzQueryIntervalMs = 1050u;
+ private readonly long _musicBrainzQueryIntervalMs;
/// <summary>
/// For each single MB lookup/search, this is the maximum number of
@@ -50,14 +51,14 @@ namespace MediaBrowser.Providers.Music
public MusicBrainzAlbumProvider(
IHttpClient httpClient,
IApplicationHost appHost,
- ILogger logger,
- IConfiguration configuration)
+ ILogger<MusicBrainzAlbumProvider> logger)
{
_httpClient = httpClient;
_appHost = appHost;
_logger = logger;
- _musicBrainzBaseUrl = configuration["MusicBrainz:BaseUrl"];
+ _musicBrainzBaseUrl = Plugin.Instance.Configuration.Server;
+ _musicBrainzQueryIntervalMs = Plugin.Instance.Configuration.RateLimit;
// Use a stopwatch to ensure we don't exceed the MusicBrainz rate limit
_stopWatchMusicBrainz.Start();
@@ -74,6 +75,12 @@ namespace MediaBrowser.Providers.Music
/// <inheritdoc />
public async Task<IEnumerable<RemoteSearchResult>> GetSearchResults(AlbumInfo searchInfo, CancellationToken cancellationToken)
{
+ // TODO maybe remove when artist metadata can be disabled
+ if (!Plugin.Instance.Configuration.Enable)
+ {
+ return Enumerable.Empty<RemoteSearchResult>();
+ }
+
var releaseId = searchInfo.GetReleaseId();
var releaseGroupId = searchInfo.GetReleaseGroupId();
@@ -107,8 +114,8 @@ namespace MediaBrowser.Providers.Music
url = string.Format(
CultureInfo.InvariantCulture,
"/ws/2/release/?query=\"{0}\" AND artist:\"{1}\"",
- WebUtility.UrlEncode(queryName),
- WebUtility.UrlEncode(searchInfo.GetAlbumArtist()));
+ WebUtility.UrlEncode(queryName),
+ WebUtility.UrlEncode(searchInfo.GetAlbumArtist()));
}
}
@@ -170,7 +177,6 @@ namespace MediaBrowser.Providers.Music
}
return result;
-
});
}
}
@@ -187,6 +193,12 @@ namespace MediaBrowser.Providers.Music
Item = new MusicAlbum()
};
+ // TODO maybe remove when artist metadata can be disabled
+ if (!Plugin.Instance.Configuration.Enable)
+ {
+ return result;
+ }
+
// If we have a release group Id but not a release Id...
if (string.IsNullOrWhiteSpace(releaseId) && !string.IsNullOrWhiteSpace(releaseGroupId))
{
@@ -456,18 +468,6 @@ namespace MediaBrowser.Providers.Music
}
case "artist-credit":
{
- // TODO
-
- /*
- * <artist-credit>
-<name-credit>
-<artist id="e225cda5-882d-4b80-b8a3-b36d7175b1ea">
-<name>SARCASTIC+ZOOKEEPER</name>
-<sort-name>SARCASTIC+ZOOKEEPER</sort-name>
-</artist>
-</name-credit>
-</artist-credit>
- */
using (var subReader = reader.ReadSubtree())
{
var artist = ParseArtistCredit(subReader);
@@ -764,10 +764,10 @@ namespace MediaBrowser.Providers.Music
{
attempts++;
- if (_stopWatchMusicBrainz.ElapsedMilliseconds < MusicBrainzQueryIntervalMs)
+ if (_stopWatchMusicBrainz.ElapsedMilliseconds < _musicBrainzQueryIntervalMs)
{
// MusicBrainz is extremely adamant about limiting to one request per second
- var delayMs = MusicBrainzQueryIntervalMs - _stopWatchMusicBrainz.ElapsedMilliseconds;
+ var delayMs = _musicBrainzQueryIntervalMs - _stopWatchMusicBrainz.ElapsedMilliseconds;
await Task.Delay((int)delayMs, cancellationToken).ConfigureAwait(false);
}
@@ -776,9 +776,9 @@ namespace MediaBrowser.Providers.Music
_logger.LogDebug("GetMusicBrainzResponse: Time since previous request: {0} ms", _stopWatchMusicBrainz.ElapsedMilliseconds);
_stopWatchMusicBrainz.Restart();
- response = await _httpClient.SendAsync(options, "GET").ConfigureAwait(false);
+ response = await _httpClient.SendAsync(options, HttpMethod.Get).ConfigureAwait(false);
- // We retry a finite number of times, and only whilst MB is indcating 503 (throttling)
+ // We retry a finite number of times, and only whilst MB is indicating 503 (throttling)
}
while (attempts < MusicBrainzQueryAttempts && response.StatusCode == HttpStatusCode.ServiceUnavailable);
diff --git a/MediaBrowser.Providers/Music/MusicBrainzArtistProvider.cs b/MediaBrowser.Providers/Plugins/MusicBrainz/ArtistProvider.cs
index 5d675392c..260a3b6e7 100644
--- a/MediaBrowser.Providers/Music/MusicBrainzArtistProvider.cs
+++ b/MediaBrowser.Providers/Plugins/MusicBrainz/ArtistProvider.cs
@@ -14,6 +14,7 @@ using MediaBrowser.Controller.Extensions;
using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Providers;
+using MediaBrowser.Providers.Plugins.MusicBrainz;
namespace MediaBrowser.Providers.Music
{
@@ -22,6 +23,12 @@ namespace MediaBrowser.Providers.Music
/// <inheritdoc />
public async Task<IEnumerable<RemoteSearchResult>> GetSearchResults(ArtistInfo searchInfo, CancellationToken cancellationToken)
{
+ // TODO maybe remove when artist metadata can be disabled
+ if (!Plugin.Instance.Configuration.Enable)
+ {
+ return Enumerable.Empty<RemoteSearchResult>();
+ }
+
var musicBrainzId = searchInfo.GetMusicBrainzArtistId();
if (!string.IsNullOrWhiteSpace(musicBrainzId))
@@ -226,6 +233,12 @@ namespace MediaBrowser.Providers.Music
Item = new MusicArtist()
};
+ // TODO maybe remove when artist metadata can be disabled
+ if (!Plugin.Instance.Configuration.Enable)
+ {
+ return result;
+ }
+
var musicBrainzId = id.GetMusicBrainzArtistId();
if (string.IsNullOrWhiteSpace(musicBrainzId))
@@ -237,8 +250,12 @@ namespace MediaBrowser.Providers.Music
if (singleResult != null)
{
musicBrainzId = singleResult.GetProviderId(MetadataProviders.MusicBrainzArtist);
- //result.Item.Name = singleResult.Name;
result.Item.Overview = singleResult.Overview;
+
+ if (Plugin.Instance.Configuration.ReplaceArtistName)
+ {
+ result.Item.Name = singleResult.Name;
+ }
}
}
diff --git a/MediaBrowser.Providers/Plugins/MusicBrainz/Configuration/PluginConfiguration.cs b/MediaBrowser.Providers/Plugins/MusicBrainz/Configuration/PluginConfiguration.cs
new file mode 100644
index 000000000..5843b0c7d
--- /dev/null
+++ b/MediaBrowser.Providers/Plugins/MusicBrainz/Configuration/PluginConfiguration.cs
@@ -0,0 +1,48 @@
+using MediaBrowser.Model.Plugins;
+
+namespace MediaBrowser.Providers.Plugins.MusicBrainz
+{
+ public class PluginConfiguration : BasePluginConfiguration
+ {
+ private string _server = Plugin.DefaultServer;
+
+ private long _rateLimit = Plugin.DefaultRateLimit;
+
+ public string Server
+ {
+ get
+ {
+ return _server;
+ }
+
+ set
+ {
+ _server = value.TrimEnd('/');
+ }
+ }
+
+ public long RateLimit
+ {
+ get
+ {
+ return _rateLimit;
+ }
+
+ set
+ {
+ if (value < Plugin.DefaultRateLimit && _server == Plugin.DefaultServer)
+ {
+ _rateLimit = Plugin.DefaultRateLimit;
+ }
+ else
+ {
+ _rateLimit = value;
+ }
+ }
+ }
+
+ public bool Enable { get; set; }
+
+ public bool ReplaceArtistName { get; set; }
+ }
+}
diff --git a/MediaBrowser.Providers/Plugins/MusicBrainz/Configuration/config.html b/MediaBrowser.Providers/Plugins/MusicBrainz/Configuration/config.html
new file mode 100644
index 000000000..1f02461da
--- /dev/null
+++ b/MediaBrowser.Providers/Plugins/MusicBrainz/Configuration/config.html
@@ -0,0 +1,69 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>MusicBrainz</title>
+</head>
+<body>
+ <div data-role="page" class="page type-interior pluginConfigurationPage musicBrainzConfigPage" data-require="emby-input,emby-button,emby-checkbox">
+ <div data-role="content">
+ <div class="content-primary">
+ <form class="musicBrainzConfigForm">
+ <div class="inputContainer">
+ <input is="emby-input" type="text" id="server" required label="Server" />
+ <div class="fieldDescription">This can be a mirror of the official server or even a custom server.</div>
+ </div>
+ <div class="inputContainer">
+ <input is="emby-input" type="number" id="rateLimit" pattern="[0-9]*" required min="0" max="10000" label="Rate Limit" />
+ <div class="fieldDescription">Span of time between requests in milliseconds. The official server is limited to one request every two seconds.</div>
+ </div>
+ <label class="checkboxContainer">
+ <input is="emby-checkbox" type="checkbox" id="enable" />
+ <span>Enable this provider for metadata searches on artists and albums.</span>
+ </label>
+ <label class="checkboxContainer">
+ <input is="emby-checkbox" type="checkbox" id="replaceArtistName" />
+ <span>When an artist is found during a metadata search, replace the artist name with the value on the server.</span>
+ </label>
+ <br />
+ <div>
+ <button is="emby-button" type="submit" class="raised button-submit block"><span>Save</span></button>
+ </div>
+ </form>
+ </div>
+ </div>
+ <script type="text/javascript">
+ var MusicBrainzPluginConfig = {
+ uniquePluginId: "8c95c4d2-e50c-4fb0-a4f3-6c06ff0f9a1a"
+ };
+
+ $('.musicBrainzConfigPage').on('pageshow', function () {
+ Dashboard.showLoadingMsg();
+ ApiClient.getPluginConfiguration(MusicBrainzPluginConfig.uniquePluginId).then(function (config) {
+ $('#server').val(config.Server).change();
+ $('#rateLimit').val(config.RateLimit).change();
+ $('#enable').checked(config.Enable);
+ $('#replaceArtistName').checked(config.ReplaceArtistName);
+
+ Dashboard.hideLoadingMsg();
+ });
+ });
+
+ $('.musicBrainzConfigForm').on('submit', function (e) {
+ Dashboard.showLoadingMsg();
+
+ var form = this;
+ ApiClient.getPluginConfiguration(MusicBrainzPluginConfig.uniquePluginId).then(function (config) {
+ config.Server = $('#server', form).val();
+ config.RateLimit = $('#rateLimit', form).val();
+ config.Enable = $('#enable', form).checked();
+ config.ReplaceArtistName = $('#replaceArtistName', form).checked();
+
+ ApiClient.updatePluginConfiguration(MusicBrainzPluginConfig.uniquePluginId, config).then(Dashboard.processPluginConfigurationUpdateResult);
+ });
+
+ return false;
+ });
+ </script>
+ </div>
+</body>
+</html>
diff --git a/MediaBrowser.Providers/Plugins/MusicBrainz/ExternalIds.cs b/MediaBrowser.Providers/Plugins/MusicBrainz/ExternalIds.cs
new file mode 100644
index 000000000..03565a34c
--- /dev/null
+++ b/MediaBrowser.Providers/Plugins/MusicBrainz/ExternalIds.cs
@@ -0,0 +1,98 @@
+using MediaBrowser.Controller.Entities.Audio;
+using MediaBrowser.Controller.Providers;
+using MediaBrowser.Model.Entities;
+using MediaBrowser.Providers.Plugins.MusicBrainz;
+
+namespace MediaBrowser.Providers.Music
+{
+ public class MusicBrainzReleaseGroupExternalId : IExternalId
+ {
+ /// <inheritdoc />
+ public string Name => "MusicBrainz Release Group";
+
+ /// <inheritdoc />
+ public string Key => MetadataProviders.MusicBrainzReleaseGroup.ToString();
+
+ /// <inheritdoc />
+ public string UrlFormatString => Plugin.Instance.Configuration.Server + "/release-group/{0}";
+
+ /// <inheritdoc />
+ public bool Supports(IHasProviderIds item) => item is Audio || item is MusicAlbum;
+ }
+
+ public class MusicBrainzAlbumArtistExternalId : IExternalId
+ {
+ /// <inheritdoc />
+ public string Name => "MusicBrainz Album Artist";
+
+ /// <inheritdoc />
+ public string Key => MetadataProviders.MusicBrainzAlbumArtist.ToString();
+
+ /// <inheritdoc />
+ public string UrlFormatString => Plugin.Instance.Configuration.Server + "/artist/{0}";
+
+ /// <inheritdoc />
+ public bool Supports(IHasProviderIds item) => item is Audio;
+ }
+
+ public class MusicBrainzAlbumExternalId : IExternalId
+ {
+ /// <inheritdoc />
+ public string Name => "MusicBrainz Album";
+
+ /// <inheritdoc />
+ public string Key => MetadataProviders.MusicBrainzAlbum.ToString();
+
+ /// <inheritdoc />
+ public string UrlFormatString => Plugin.Instance.Configuration.Server + "/release/{0}";
+
+ /// <inheritdoc />
+ public bool Supports(IHasProviderIds item) => item is Audio || item is MusicAlbum;
+ }
+
+ public class MusicBrainzArtistExternalId : IExternalId
+ {
+ /// <inheritdoc />
+ public string Name => "MusicBrainz";
+
+ /// <inheritdoc />
+ public string Key => MetadataProviders.MusicBrainzArtist.ToString();
+
+ /// <inheritdoc />
+ public string UrlFormatString => Plugin.Instance.Configuration.Server + "/artist/{0}";
+
+ /// <inheritdoc />
+ public bool Supports(IHasProviderIds item) => item is MusicArtist;
+ }
+
+ public class MusicBrainzOtherArtistExternalId : IExternalId
+ {
+ /// <inheritdoc />
+ public string Name => "MusicBrainz Artist";
+
+ /// <inheritdoc />
+
+ public string Key => MetadataProviders.MusicBrainzArtist.ToString();
+
+ /// <inheritdoc />
+ public string UrlFormatString => Plugin.Instance.Configuration.Server + "/artist/{0}";
+
+ /// <inheritdoc />
+ public bool Supports(IHasProviderIds item) => item is Audio || item is MusicAlbum;
+ }
+
+ public class MusicBrainzTrackId : IExternalId
+ {
+ /// <inheritdoc />
+ public string Name => "MusicBrainz Track";
+
+ /// <inheritdoc />
+ public string Key => MetadataProviders.MusicBrainzTrack.ToString();
+
+ /// <inheritdoc />
+ public string UrlFormatString => Plugin.Instance.Configuration.Server + "/track/{0}";
+
+ /// <inheritdoc />
+ public bool Supports(IHasProviderIds item) => item is Audio;
+ }
+}
diff --git a/MediaBrowser.Providers/Plugins/MusicBrainz/Plugin.cs b/MediaBrowser.Providers/Plugins/MusicBrainz/Plugin.cs
new file mode 100644
index 000000000..8e1b3ea37
--- /dev/null
+++ b/MediaBrowser.Providers/Plugins/MusicBrainz/Plugin.cs
@@ -0,0 +1,39 @@
+using System;
+using System.Collections.Generic;
+using MediaBrowser.Common.Configuration;
+using MediaBrowser.Common.Plugins;
+using MediaBrowser.Model.Plugins;
+using MediaBrowser.Model.Serialization;
+
+namespace MediaBrowser.Providers.Plugins.MusicBrainz
+{
+ public class Plugin : BasePlugin<PluginConfiguration>, IHasWebPages
+ {
+ public static Plugin Instance { get; private set; }
+
+ public override Guid Id => new Guid("8c95c4d2-e50c-4fb0-a4f3-6c06ff0f9a1a");
+
+ public override string Name => "MusicBrainz";
+
+ public override string Description => "Get artist and album metadata from any MusicBrainz server.";
+
+ public const string DefaultServer = "https://musicbrainz.org";
+
+ public const long DefaultRateLimit = 2000u;
+
+ public Plugin(IApplicationPaths applicationPaths, IXmlSerializer xmlSerializer)
+ : base(applicationPaths, xmlSerializer)
+ {
+ Instance = this;
+ }
+
+ public IEnumerable<PluginPageInfo> GetPages()
+ {
+ yield return new PluginPageInfo
+ {
+ Name = Name,
+ EmbeddedResourcePath = GetType().Namespace + ".Configuration.config.html"
+ };
+ }
+ }
+}
diff --git a/MediaBrowser.Providers/TV/Omdb/OmdbEpisodeProvider.cs b/MediaBrowser.Providers/Plugins/Omdb/OmdbEpisodeProvider.cs
index dee3030af..37160dd2c 100644
--- a/MediaBrowser.Providers/TV/Omdb/OmdbEpisodeProvider.cs
+++ b/MediaBrowser.Providers/Plugins/Omdb/OmdbEpisodeProvider.cs
@@ -11,10 +11,9 @@ using MediaBrowser.Model.Entities;
using MediaBrowser.Model.IO;
using MediaBrowser.Model.Providers;
using MediaBrowser.Model.Serialization;
-using MediaBrowser.Providers.Omdb;
using Microsoft.Extensions.Logging;
-namespace MediaBrowser.Providers.TV.Omdb
+namespace MediaBrowser.Providers.Plugins.Omdb
{
public class OmdbEpisodeProvider :
IRemoteMetadataProvider<Episode, EpisodeInfo>,
diff --git a/MediaBrowser.Providers/Omdb/OmdbImageProvider.cs b/MediaBrowser.Providers/Plugins/Omdb/OmdbImageProvider.cs
index 1e0bcacf2..a450c2a6d 100644
--- a/MediaBrowser.Providers/Omdb/OmdbImageProvider.cs
+++ b/MediaBrowser.Providers/Plugins/Omdb/OmdbImageProvider.cs
@@ -13,7 +13,7 @@ using MediaBrowser.Model.IO;
using MediaBrowser.Model.Providers;
using MediaBrowser.Model.Serialization;
-namespace MediaBrowser.Providers.Omdb
+namespace MediaBrowser.Providers.Plugins.Omdb
{
public class OmdbImageProvider : IRemoteImageProvider, IHasOrder
{
diff --git a/MediaBrowser.Providers/Omdb/OmdbItemProvider.cs b/MediaBrowser.Providers/Plugins/Omdb/OmdbItemProvider.cs
index 44b9dcca1..3aadda5d0 100644
--- a/MediaBrowser.Providers/Omdb/OmdbItemProvider.cs
+++ b/MediaBrowser.Providers/Plugins/Omdb/OmdbItemProvider.cs
@@ -19,7 +19,7 @@ using MediaBrowser.Model.Providers;
using MediaBrowser.Model.Serialization;
using Microsoft.Extensions.Logging;
-namespace MediaBrowser.Providers.Omdb
+namespace MediaBrowser.Providers.Plugins.Omdb
{
public class OmdbItemProvider : IRemoteMetadataProvider<Series, SeriesInfo>,
IRemoteMetadataProvider<Movie, MovieInfo>, IRemoteMetadataProvider<Trailer, TrailerInfo>, IHasOrder
diff --git a/MediaBrowser.Providers/Omdb/OmdbProvider.cs b/MediaBrowser.Providers/Plugins/Omdb/OmdbProvider.cs
index f8b876580..fbdd293ed 100644
--- a/MediaBrowser.Providers/Omdb/OmdbProvider.cs
+++ b/MediaBrowser.Providers/Plugins/Omdb/OmdbProvider.cs
@@ -16,7 +16,7 @@ using MediaBrowser.Model.Entities;
using MediaBrowser.Model.IO;
using MediaBrowser.Model.Serialization;
-namespace MediaBrowser.Providers.Omdb
+namespace MediaBrowser.Providers.Plugins.Omdb
{
public class OmdbProvider
{
@@ -209,7 +209,7 @@ namespace MediaBrowser.Providers.Omdb
string resultString;
- using (var stream = _fileSystem.GetFileStream(path, FileOpenMode.Open, FileAccessMode.Read, FileShareMode.Read))
+ using (var stream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read))
{
using (var reader = new StreamReader(stream, new UTF8Encoding(false)))
{
@@ -228,7 +228,7 @@ namespace MediaBrowser.Providers.Omdb
string resultString;
- using (var stream = _fileSystem.GetFileStream(path, FileOpenMode.Open, FileAccessMode.Read, FileShareMode.Read))
+ using (var stream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read))
{
using (var reader = new StreamReader(stream, new UTF8Encoding(false)))
{
diff --git a/MediaBrowser.Providers/TV/TheTVDB/TvDbClientManager.cs b/MediaBrowser.Providers/Plugins/TheTvdb/TvdbClientManager.cs
index 4abe6a943..b73834155 100644
--- a/MediaBrowser.Providers/TV/TheTVDB/TvDbClientManager.cs
+++ b/MediaBrowser.Providers/Plugins/TheTvdb/TvdbClientManager.cs
@@ -10,9 +10,9 @@ using Microsoft.Extensions.Caching.Memory;
using TvDbSharper;
using TvDbSharper.Dto;
-namespace MediaBrowser.Providers.TV.TheTVDB
+namespace MediaBrowser.Providers.Plugins.TheTvdb
{
- public class TvDbClientManager
+ public class TvdbClientManager
{
private const string DefaultLanguage = "en";
@@ -21,7 +21,7 @@ namespace MediaBrowser.Providers.TV.TheTVDB
private readonly TvDbClient _tvDbClient;
private DateTime _tokenCreatedAt;
- public TvDbClientManager(IMemoryCache memoryCache)
+ public TvdbClientManager(IMemoryCache memoryCache)
{
_cache = memoryCache;
_tvDbClient = new TvDbClient();
@@ -60,21 +60,21 @@ namespace MediaBrowser.Providers.TV.TheTVDB
CancellationToken cancellationToken)
{
var cacheKey = GenerateKey("series", name, language);
- return TryGetValue(cacheKey, language,() => TvDbClient.Search.SearchSeriesByNameAsync(name, cancellationToken));
+ return TryGetValue(cacheKey, language, () => TvDbClient.Search.SearchSeriesByNameAsync(name, cancellationToken));
}
public Task<TvDbResponse<Series>> GetSeriesByIdAsync(int tvdbId, string language,
CancellationToken cancellationToken)
{
var cacheKey = GenerateKey("series", tvdbId, language);
- return TryGetValue(cacheKey, language,() => TvDbClient.Series.GetAsync(tvdbId, cancellationToken));
+ return TryGetValue(cacheKey, language, () => TvDbClient.Series.GetAsync(tvdbId, cancellationToken));
}
public Task<TvDbResponse<EpisodeRecord>> GetEpisodesAsync(int episodeTvdbId, string language,
CancellationToken cancellationToken)
{
var cacheKey = GenerateKey("episode", episodeTvdbId, language);
- return TryGetValue(cacheKey, language,() => TvDbClient.Episodes.GetAsync(episodeTvdbId, cancellationToken));
+ return TryGetValue(cacheKey, language, () => TvDbClient.Episodes.GetAsync(episodeTvdbId, cancellationToken));
}
public async Task<List<EpisodeRecord>> GetAllEpisodesAsync(int tvdbId, string language,
@@ -109,7 +109,7 @@ namespace MediaBrowser.Providers.TV.TheTVDB
CancellationToken cancellationToken)
{
var cacheKey = GenerateKey("series", imdbId, language);
- return TryGetValue(cacheKey, language,() => TvDbClient.Search.SearchSeriesByImdbIdAsync(imdbId, cancellationToken));
+ return TryGetValue(cacheKey, language, () => TvDbClient.Search.SearchSeriesByImdbIdAsync(imdbId, cancellationToken));
}
public Task<TvDbResponse<SeriesSearchResult[]>> GetSeriesByZap2ItIdAsync(
diff --git a/MediaBrowser.Providers/TV/TheTVDB/TvdbEpisodeImageProvider.cs b/MediaBrowser.Providers/Plugins/TheTvdb/TvdbEpisodeImageProvider.cs
index fc7f12b1a..6118a9c53 100644
--- a/MediaBrowser.Providers/TV/TheTVDB/TvdbEpisodeImageProvider.cs
+++ b/MediaBrowser.Providers/Plugins/TheTvdb/TvdbEpisodeImageProvider.cs
@@ -12,19 +12,19 @@ using Microsoft.Extensions.Logging;
using TvDbSharper;
using TvDbSharper.Dto;
-namespace MediaBrowser.Providers.TV.TheTVDB
+namespace MediaBrowser.Providers.Plugins.TheTvdb
{
public class TvdbEpisodeImageProvider : IRemoteImageProvider
{
private readonly IHttpClient _httpClient;
private readonly ILogger _logger;
- private readonly TvDbClientManager _tvDbClientManager;
+ private readonly TvdbClientManager _tvdbClientManager;
- public TvdbEpisodeImageProvider(IHttpClient httpClient, ILogger<TvdbEpisodeImageProvider> logger, TvDbClientManager tvDbClientManager)
+ public TvdbEpisodeImageProvider(IHttpClient httpClient, ILogger<TvdbEpisodeImageProvider> logger, TvdbClientManager tvdbClientManager)
{
_httpClient = httpClient;
_logger = logger;
- _tvDbClientManager = tvDbClientManager;
+ _tvdbClientManager = tvdbClientManager;
}
public string Name => "TheTVDB";
@@ -60,7 +60,7 @@ namespace MediaBrowser.Providers.TV.TheTVDB
SeriesProviderIds = series.ProviderIds,
SeriesDisplayOrder = series.DisplayOrder
};
- string episodeTvdbId = await _tvDbClientManager
+ string episodeTvdbId = await _tvdbClientManager
.GetEpisodeTvdbId(episodeInfo, language, cancellationToken).ConfigureAwait(false);
if (string.IsNullOrEmpty(episodeTvdbId))
{
@@ -73,7 +73,7 @@ namespace MediaBrowser.Providers.TV.TheTVDB
}
var episodeResult =
- await _tvDbClientManager
+ await _tvdbClientManager
.GetEpisodesAsync(Convert.ToInt32(episodeTvdbId), language, cancellationToken)
.ConfigureAwait(false);
diff --git a/MediaBrowser.Providers/TV/TheTVDB/TvdbEpisodeProvider.cs b/MediaBrowser.Providers/Plugins/TheTvdb/TvdbEpisodeProvider.cs
index 4269d3420..08c2a74d2 100644
--- a/MediaBrowser.Providers/TV/TheTVDB/TvdbEpisodeProvider.cs
+++ b/MediaBrowser.Providers/Plugins/TheTvdb/TvdbEpisodeProvider.cs
@@ -12,7 +12,7 @@ using Microsoft.Extensions.Logging;
using TvDbSharper;
using TvDbSharper.Dto;
-namespace MediaBrowser.Providers.TV.TheTVDB
+namespace MediaBrowser.Providers.Plugins.TheTvdb
{
/// <summary>
@@ -22,13 +22,13 @@ namespace MediaBrowser.Providers.TV.TheTVDB
{
private readonly IHttpClient _httpClient;
private readonly ILogger _logger;
- private readonly TvDbClientManager _tvDbClientManager;
+ private readonly TvdbClientManager _tvdbClientManager;
- public TvdbEpisodeProvider(IHttpClient httpClient, ILogger<TvdbEpisodeProvider> logger, TvDbClientManager tvDbClientManager)
+ public TvdbEpisodeProvider(IHttpClient httpClient, ILogger<TvdbEpisodeProvider> logger, TvdbClientManager tvdbClientManager)
{
_httpClient = httpClient;
_logger = logger;
- _tvDbClientManager = tvDbClientManager;
+ _tvdbClientManager = tvdbClientManager;
}
public async Task<IEnumerable<RemoteSearchResult>> GetSearchResults(EpisodeInfo searchInfo, CancellationToken cancellationToken)
@@ -99,7 +99,7 @@ namespace MediaBrowser.Providers.TV.TheTVDB
string episodeTvdbId = null;
try
{
- episodeTvdbId = await _tvDbClientManager
+ episodeTvdbId = await _tvdbClientManager
.GetEpisodeTvdbId(searchInfo, searchInfo.MetadataLanguage, cancellationToken)
.ConfigureAwait(false);
if (string.IsNullOrEmpty(episodeTvdbId))
@@ -109,7 +109,7 @@ namespace MediaBrowser.Providers.TV.TheTVDB
return result;
}
- var episodeResult = await _tvDbClientManager.GetEpisodesAsync(
+ var episodeResult = await _tvdbClientManager.GetEpisodesAsync(
Convert.ToInt32(episodeTvdbId), searchInfo.MetadataLanguage,
cancellationToken).ConfigureAwait(false);
@@ -201,7 +201,7 @@ namespace MediaBrowser.Providers.TV.TheTVDB
continue;
}
- var roles = new List<string> {currentActor.Substring(roleStartIndex + 1)};
+ var roles = new List<string> { currentActor.Substring(roleStartIndex + 1) };
// Fetch all roles
for (var j = i + 1; j < episode.GuestStars.Length; ++j)
diff --git a/MediaBrowser.Providers/People/TvdbPersonImageProvider.cs b/MediaBrowser.Providers/Plugins/TheTvdb/TvdbPersonImageProvider.cs
index 50476044b..c1cdc90e9 100644
--- a/MediaBrowser.Providers/People/TvdbPersonImageProvider.cs
+++ b/MediaBrowser.Providers/Plugins/TheTvdb/TvdbPersonImageProvider.cs
@@ -11,25 +11,24 @@ using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Providers;
-using MediaBrowser.Providers.TV.TheTVDB;
using Microsoft.Extensions.Logging;
using TvDbSharper;
-namespace MediaBrowser.Providers.People
+namespace MediaBrowser.Providers.Plugins.TheTvdb
{
public class TvdbPersonImageProvider : IRemoteImageProvider, IHasOrder
{
private readonly IHttpClient _httpClient;
private readonly ILogger _logger;
private readonly ILibraryManager _libraryManager;
- private readonly TvDbClientManager _tvDbClientManager;
+ private readonly TvdbClientManager _tvdbClientManager;
- public TvdbPersonImageProvider(ILibraryManager libraryManager, IHttpClient httpClient, ILogger<TvdbPersonImageProvider> logger, TvDbClientManager tvDbClientManager)
+ public TvdbPersonImageProvider(ILibraryManager libraryManager, IHttpClient httpClient, ILogger<TvdbPersonImageProvider> logger, TvdbClientManager tvdbClientManager)
{
_libraryManager = libraryManager;
_httpClient = httpClient;
_logger = logger;
- _tvDbClientManager = tvDbClientManager;
+ _tvdbClientManager = tvdbClientManager;
}
/// <inheritdoc />
@@ -78,7 +77,7 @@ namespace MediaBrowser.Providers.People
try
{
- var actorsResult = await _tvDbClientManager
+ var actorsResult = await _tvdbClientManager
.GetActorsAsync(tvdbId, series.GetPreferredMetadataLanguage(), cancellationToken)
.ConfigureAwait(false);
var actor = actorsResult.Data.FirstOrDefault(a =>
diff --git a/MediaBrowser.Providers/TV/TheTVDB/TvdbSeasonImageProvider.cs b/MediaBrowser.Providers/Plugins/TheTvdb/TvdbSeasonImageProvider.cs
index 94ca603f2..a5d183df7 100644
--- a/MediaBrowser.Providers/TV/TheTVDB/TvdbSeasonImageProvider.cs
+++ b/MediaBrowser.Providers/Plugins/TheTvdb/TvdbSeasonImageProvider.cs
@@ -14,19 +14,19 @@ using TvDbSharper;
using TvDbSharper.Dto;
using RatingType = MediaBrowser.Model.Dto.RatingType;
-namespace MediaBrowser.Providers.TV.TheTVDB
+namespace MediaBrowser.Providers.Plugins.TheTvdb
{
public class TvdbSeasonImageProvider : IRemoteImageProvider, IHasOrder
{
private readonly IHttpClient _httpClient;
private readonly ILogger _logger;
- private readonly TvDbClientManager _tvDbClientManager;
+ private readonly TvdbClientManager _tvdbClientManager;
- public TvdbSeasonImageProvider(IHttpClient httpClient, ILogger<TvdbSeasonImageProvider> logger, TvDbClientManager tvDbClientManager)
+ public TvdbSeasonImageProvider(IHttpClient httpClient, ILogger<TvdbSeasonImageProvider> logger, TvdbClientManager tvdbClientManager)
{
_httpClient = httpClient;
_logger = logger;
- _tvDbClientManager = tvDbClientManager;
+ _tvdbClientManager = tvdbClientManager;
}
public string Name => ProviderName;
@@ -73,7 +73,7 @@ namespace MediaBrowser.Providers.TV.TheTVDB
};
try
{
- var imageResults = await _tvDbClientManager
+ var imageResults = await _tvdbClientManager
.GetImagesAsync(tvdbId, imageQuery, language, cancellationToken).ConfigureAwait(false);
remoteImages.AddRange(GetImages(imageResults.Data, language));
}
@@ -89,7 +89,7 @@ namespace MediaBrowser.Providers.TV.TheTVDB
private IEnumerable<RemoteImageInfo> GetImages(Image[] images, string preferredLanguage)
{
var list = new List<RemoteImageInfo>();
- var languages = _tvDbClientManager.GetLanguagesAsync(CancellationToken.None).Result.Data;
+ var languages = _tvdbClientManager.GetLanguagesAsync(CancellationToken.None).Result.Data;
foreach (Image image in images)
{
var imageInfo = new RemoteImageInfo
diff --git a/MediaBrowser.Providers/TV/TheTVDB/TvdbSeriesImageProvider.cs b/MediaBrowser.Providers/Plugins/TheTvdb/TvdbSeriesImageProvider.cs
index 365f49fb7..1bad60756 100644
--- a/MediaBrowser.Providers/TV/TheTVDB/TvdbSeriesImageProvider.cs
+++ b/MediaBrowser.Providers/Plugins/TheTvdb/TvdbSeriesImageProvider.cs
@@ -14,19 +14,19 @@ using TvDbSharper.Dto;
using RatingType = MediaBrowser.Model.Dto.RatingType;
using Series = MediaBrowser.Controller.Entities.TV.Series;
-namespace MediaBrowser.Providers.TV.TheTVDB
+namespace MediaBrowser.Providers.Plugins.TheTvdb
{
public class TvdbSeriesImageProvider : IRemoteImageProvider, IHasOrder
{
private readonly IHttpClient _httpClient;
private readonly ILogger _logger;
- private readonly TvDbClientManager _tvDbClientManager;
+ private readonly TvdbClientManager _tvdbClientManager;
- public TvdbSeriesImageProvider(IHttpClient httpClient, ILogger<TvdbSeriesImageProvider> logger, TvDbClientManager tvDbClientManager)
+ public TvdbSeriesImageProvider(IHttpClient httpClient, ILogger<TvdbSeriesImageProvider> logger, TvdbClientManager tvdbClientManager)
{
_httpClient = httpClient;
_logger = logger;
- _tvDbClientManager = tvDbClientManager;
+ _tvdbClientManager = tvdbClientManager;
}
public string Name => ProviderName;
@@ -68,7 +68,7 @@ namespace MediaBrowser.Providers.TV.TheTVDB
try
{
var imageResults =
- await _tvDbClientManager.GetImagesAsync(tvdbId, imageQuery, language, cancellationToken)
+ await _tvdbClientManager.GetImagesAsync(tvdbId, imageQuery, language, cancellationToken)
.ConfigureAwait(false);
remoteImages.AddRange(GetImages(imageResults.Data, language));
@@ -85,7 +85,7 @@ namespace MediaBrowser.Providers.TV.TheTVDB
private IEnumerable<RemoteImageInfo> GetImages(Image[] images, string preferredLanguage)
{
var list = new List<RemoteImageInfo>();
- var languages = _tvDbClientManager.GetLanguagesAsync(CancellationToken.None).Result.Data;
+ var languages = _tvdbClientManager.GetLanguagesAsync(CancellationToken.None).Result.Data;
foreach (Image image in images)
{
diff --git a/MediaBrowser.Providers/TV/TheTVDB/TvdbSeriesProvider.cs b/MediaBrowser.Providers/Plugins/TheTvdb/TvdbSeriesProvider.cs
index 72ceadaf1..f6cd249f5 100644
--- a/MediaBrowser.Providers/TV/TheTVDB/TvdbSeriesProvider.cs
+++ b/MediaBrowser.Providers/Plugins/TheTvdb/TvdbSeriesProvider.cs
@@ -1,6 +1,5 @@
using System;
using System.Collections.Generic;
-using System.Globalization;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
@@ -18,7 +17,7 @@ using TvDbSharper;
using TvDbSharper.Dto;
using Series = MediaBrowser.Controller.Entities.TV.Series;
-namespace MediaBrowser.Providers.TV.TheTVDB
+namespace MediaBrowser.Providers.Plugins.TheTvdb
{
public class TvdbSeriesProvider : IRemoteMetadataProvider<Series, SeriesInfo>, IHasOrder
{
@@ -27,16 +26,16 @@ namespace MediaBrowser.Providers.TV.TheTVDB
private readonly ILogger _logger;
private readonly ILibraryManager _libraryManager;
private readonly ILocalizationManager _localizationManager;
- private readonly TvDbClientManager _tvDbClientManager;
+ private readonly TvdbClientManager _tvdbClientManager;
- public TvdbSeriesProvider(IHttpClient httpClient, ILogger<TvdbSeriesProvider> logger, ILibraryManager libraryManager, ILocalizationManager localizationManager, TvDbClientManager tvDbClientManager)
+ public TvdbSeriesProvider(IHttpClient httpClient, ILogger<TvdbSeriesProvider> logger, ILibraryManager libraryManager, ILocalizationManager localizationManager, TvdbClientManager tvdbClientManager)
{
_httpClient = httpClient;
_logger = logger;
_libraryManager = libraryManager;
_localizationManager = localizationManager;
Current = this;
- _tvDbClientManager = tvDbClientManager;
+ _tvdbClientManager = tvdbClientManager;
}
public async Task<IEnumerable<RemoteSearchResult>> GetSearchResults(SeriesInfo searchInfo, CancellationToken cancellationToken)
@@ -117,7 +116,7 @@ namespace MediaBrowser.Providers.TV.TheTVDB
try
{
var seriesResult =
- await _tvDbClientManager
+ await _tvdbClientManager
.GetSeriesByIdAsync(Convert.ToInt32(tvdbId), metadataLanguage, cancellationToken)
.ConfigureAwait(false);
MapSeriesToResult(result, seriesResult.Data, metadataLanguage);
@@ -134,7 +133,7 @@ namespace MediaBrowser.Providers.TV.TheTVDB
try
{
- var actorsResult = await _tvDbClientManager
+ var actorsResult = await _tvdbClientManager
.GetActorsAsync(Convert.ToInt32(tvdbId), metadataLanguage, cancellationToken).ConfigureAwait(false);
MapActorsToResult(result, actorsResult.Data);
}
@@ -153,12 +152,12 @@ namespace MediaBrowser.Providers.TV.TheTVDB
{
if (string.Equals(idType, MetadataProviders.Zap2It.ToString(), StringComparison.OrdinalIgnoreCase))
{
- result = await _tvDbClientManager.GetSeriesByZap2ItIdAsync(id, language, cancellationToken)
+ result = await _tvdbClientManager.GetSeriesByZap2ItIdAsync(id, language, cancellationToken)
.ConfigureAwait(false);
}
else
{
- result = await _tvDbClientManager.GetSeriesByImdbIdAsync(id, language, cancellationToken)
+ result = await _tvdbClientManager.GetSeriesByImdbIdAsync(id, language, cancellationToken)
.ConfigureAwait(false);
}
}
@@ -224,7 +223,7 @@ namespace MediaBrowser.Providers.TV.TheTVDB
TvDbResponse<SeriesSearchResult[]> result;
try
{
- result = await _tvDbClientManager.GetSeriesByNameAsync(comparableName, language, cancellationToken)
+ result = await _tvdbClientManager.GetSeriesByNameAsync(comparableName, language, cancellationToken)
.ConfigureAwait(false);
}
catch (TvDbServerException e)
@@ -253,7 +252,7 @@ namespace MediaBrowser.Providers.TV.TheTVDB
try
{
var seriesSesult =
- await _tvDbClientManager.GetSeriesByIdAsync(seriesSearchResult.Id, language, cancellationToken)
+ await _tvdbClientManager.GetSeriesByIdAsync(seriesSearchResult.Id, language, cancellationToken)
.ConfigureAwait(false);
remoteSearchResult.SetProviderId(MetadataProviders.Imdb, seriesSesult.Data.ImdbId);
remoteSearchResult.SetProviderId(MetadataProviders.Zap2It, seriesSesult.Data.Zap2itId);
@@ -345,7 +344,11 @@ namespace MediaBrowser.Providers.TV.TheTVDB
series.ProductionYear = date.Year;
}
- series.RunTimeTicks = TimeSpan.FromMinutes(Convert.ToDouble(tvdbSeries.Runtime)).Ticks;
+ if (!string.IsNullOrEmpty(tvdbSeries.Runtime) && double.TryParse(tvdbSeries.Runtime, out double runtime))
+ {
+ series.RunTimeTicks = TimeSpan.FromMinutes(runtime).Ticks;
+ }
+
foreach (var genre in tvdbSeries.Genre)
{
series.AddGenre(genre);
@@ -360,7 +363,7 @@ namespace MediaBrowser.Providers.TV.TheTVDB
{
try
{
- var episodeSummary = _tvDbClientManager
+ var episodeSummary = _tvdbClientManager
.GetSeriesEpisodeSummaryAsync(tvdbSeries.Id, metadataLanguage, CancellationToken.None).Result.Data;
var maxSeasonNumber = episodeSummary.AiredSeasons.Select(s => Convert.ToInt32(s)).Max();
var episodeQuery = new EpisodeQuery
@@ -368,7 +371,7 @@ namespace MediaBrowser.Providers.TV.TheTVDB
AiredSeason = maxSeasonNumber
};
var episodesPage =
- _tvDbClientManager.GetEpisodesPageAsync(tvdbSeries.Id, episodeQuery, metadataLanguage, CancellationToken.None).Result.Data;
+ _tvdbClientManager.GetEpisodesPageAsync(tvdbSeries.Id, episodeQuery, metadataLanguage, CancellationToken.None).Result.Data;
result.Item.EndDate = episodesPage.Select(e =>
{
DateTime.TryParse(e.FirstAired, out var firstAired);
diff --git a/MediaBrowser.Providers/TV/TheTVDB/TvdbUtils.cs b/MediaBrowser.Providers/Plugins/TheTvdb/TvdbUtils.cs
index dd5ebf270..79d879aa1 100644
--- a/MediaBrowser.Providers/TV/TheTVDB/TvdbUtils.cs
+++ b/MediaBrowser.Providers/Plugins/TheTvdb/TvdbUtils.cs
@@ -1,6 +1,7 @@
using System;
using MediaBrowser.Model.Entities;
-namespace MediaBrowser.Providers.TV.TheTVDB
+
+namespace MediaBrowser.Providers.Plugins.TheTvdb
{
public static class TvdbUtils
{
diff --git a/MediaBrowser.Providers/Studios/StudioMetadataService.cs b/MediaBrowser.Providers/Studios/StudioMetadataService.cs
index 6fa30c753..847e47f1b 100644
--- a/MediaBrowser.Providers/Studios/StudioMetadataService.cs
+++ b/MediaBrowser.Providers/Studios/StudioMetadataService.cs
@@ -13,7 +13,7 @@ namespace MediaBrowser.Providers.Studios
{
public StudioMetadataService(
IServerConfigurationManager serverConfigurationManager,
- ILogger logger,
+ ILogger<StudioMetadataService> logger,
IProviderManager providerManager,
IFileSystem fileSystem, ILibraryManager libraryManager)
: base(serverConfigurationManager, logger, providerManager, fileSystem, libraryManager)
diff --git a/MediaBrowser.Providers/Studios/StudiosImageProvider.cs b/MediaBrowser.Providers/Studios/StudiosImageProvider.cs
index ef412db5a..cbef27a09 100644
--- a/MediaBrowser.Providers/Studios/StudiosImageProvider.cs
+++ b/MediaBrowser.Providers/Studios/StudiosImageProvider.cs
@@ -78,7 +78,7 @@ namespace MediaBrowser.Providers.Studios
private RemoteImageInfo GetImage(BaseItem item, string filename, ImageType type, string remoteFilename)
{
- var list = GetAvailableImages(filename, _fileSystem);
+ var list = GetAvailableImages(filename);
var match = FindMatch(item, list);
@@ -179,9 +179,9 @@ namespace MediaBrowser.Providers.Studios
.Replace("/", string.Empty);
}
- public IEnumerable<string> GetAvailableImages(string file, IFileSystem fileSystem)
+ public IEnumerable<string> GetAvailableImages(string file)
{
- using (var fileStream = fileSystem.GetFileStream(file, FileOpenMode.Open, FileAccessMode.Read, FileShareMode.Read))
+ using (var fileStream = new FileStream(file, FileMode.Open, FileAccess.Read, FileShare.Read))
{
using (var reader = new StreamReader(fileStream))
{
diff --git a/MediaBrowser.Providers/Subtitles/SubtitleManager.cs b/MediaBrowser.Providers/Subtitles/SubtitleManager.cs
index 37d1230e2..127d29c04 100644
--- a/MediaBrowser.Providers/Subtitles/SubtitleManager.cs
+++ b/MediaBrowser.Providers/Subtitles/SubtitleManager.cs
@@ -19,28 +19,28 @@ using MediaBrowser.Model.Globalization;
using MediaBrowser.Model.IO;
using MediaBrowser.Model.Providers;
using Microsoft.Extensions.Logging;
-using static MediaBrowser.Model.IO.StreamDefaults;
+using static MediaBrowser.Model.IO.IODefaults;
namespace MediaBrowser.Providers.Subtitles
{
public class SubtitleManager : ISubtitleManager
{
- private ISubtitleProvider[] _subtitleProviders;
private readonly ILogger _logger;
private readonly IFileSystem _fileSystem;
private readonly ILibraryMonitor _monitor;
private readonly IMediaSourceManager _mediaSourceManager;
+ private readonly ILocalizationManager _localization;
- private ILocalizationManager _localization;
+ private ISubtitleProvider[] _subtitleProviders;
public SubtitleManager(
- ILoggerFactory loggerFactory,
+ ILogger<SubtitleManager> logger,
IFileSystem fileSystem,
ILibraryMonitor monitor,
IMediaSourceManager mediaSourceManager,
ILocalizationManager localizationManager)
{
- _logger = loggerFactory.CreateLogger(nameof(SubtitleManager));
+ _logger = logger;
_fileSystem = fileSystem;
_monitor = monitor;
_mediaSourceManager = mediaSourceManager;
@@ -210,7 +210,7 @@ namespace MediaBrowser.Providers.Subtitles
{
Directory.CreateDirectory(Path.GetDirectoryName(savePath));
- using (var fs = new FileStream(savePath, FileMode.Create, FileAccess.Write, FileShare.Read, DefaultFileStreamBufferSize, true))
+ using (var fs = new FileStream(savePath, FileMode.Create, FileAccess.Write, FileShare.Read, FileStreamBufferSize, true))
{
await stream.CopyToAsync(fs).ConfigureAwait(false);
}
diff --git a/MediaBrowser.Providers/TV/DummySeasonProvider.cs b/MediaBrowser.Providers/TV/DummySeasonProvider.cs
index 4a6676cb9..6a1e6df8f 100644
--- a/MediaBrowser.Providers/TV/DummySeasonProvider.cs
+++ b/MediaBrowser.Providers/TV/DummySeasonProvider.cs
@@ -3,7 +3,6 @@ using System.Globalization;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
-using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Entities.TV;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Providers;
diff --git a/MediaBrowser.Providers/TV/EpisodeMetadataService.cs b/MediaBrowser.Providers/TV/EpisodeMetadataService.cs
index 89615f406..758c47ba0 100644
--- a/MediaBrowser.Providers/TV/EpisodeMetadataService.cs
+++ b/MediaBrowser.Providers/TV/EpisodeMetadataService.cs
@@ -14,7 +14,7 @@ namespace MediaBrowser.Providers.TV
{
public EpisodeMetadataService(
IServerConfigurationManager serverConfigurationManager,
- ILogger logger,
+ ILogger<EpisodeMetadataService> logger,
IProviderManager providerManager,
IFileSystem fileSystem,
ILibraryManager libraryManager)
diff --git a/MediaBrowser.Providers/TV/MissingEpisodeProvider.cs b/MediaBrowser.Providers/TV/MissingEpisodeProvider.cs
index e72df50de..0721c4bb4 100644
--- a/MediaBrowser.Providers/TV/MissingEpisodeProvider.cs
+++ b/MediaBrowser.Providers/TV/MissingEpisodeProvider.cs
@@ -12,7 +12,7 @@ using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Globalization;
using MediaBrowser.Model.IO;
-using MediaBrowser.Providers.TV.TheTVDB;
+using MediaBrowser.Providers.Plugins.TheTvdb;
using Microsoft.Extensions.Logging;
namespace MediaBrowser.Providers.TV
@@ -26,7 +26,7 @@ namespace MediaBrowser.Providers.TV
private readonly ILibraryManager _libraryManager;
private readonly ILocalizationManager _localization;
private readonly IFileSystem _fileSystem;
- private readonly TvDbClientManager _tvDbClientManager;
+ private readonly TvdbClientManager _tvdbClientManager;
public MissingEpisodeProvider(
ILogger logger,
@@ -34,14 +34,14 @@ namespace MediaBrowser.Providers.TV
ILibraryManager libraryManager,
ILocalizationManager localization,
IFileSystem fileSystem,
- TvDbClientManager tvDbClientManager)
+ TvdbClientManager tvdbClientManager)
{
_logger = logger;
_config = config;
_libraryManager = libraryManager;
_localization = localization;
_fileSystem = fileSystem;
- _tvDbClientManager = tvDbClientManager;
+ _tvdbClientManager = tvdbClientManager;
}
public async Task<bool> Run(Series series, bool addNewItems, CancellationToken cancellationToken)
@@ -52,7 +52,7 @@ namespace MediaBrowser.Providers.TV
return false;
}
- var episodes = await _tvDbClientManager.GetAllEpisodesAsync(Convert.ToInt32(tvdbId), series.GetPreferredMetadataLanguage(), cancellationToken);
+ var episodes = await _tvdbClientManager.GetAllEpisodesAsync(Convert.ToInt32(tvdbId), series.GetPreferredMetadataLanguage(), cancellationToken);
var episodeLookup = episodes
.Select(i =>
diff --git a/MediaBrowser.Providers/TV/SeasonMetadataService.cs b/MediaBrowser.Providers/TV/SeasonMetadataService.cs
index 0672f886a..eb8032e0e 100644
--- a/MediaBrowser.Providers/TV/SeasonMetadataService.cs
+++ b/MediaBrowser.Providers/TV/SeasonMetadataService.cs
@@ -17,7 +17,7 @@ namespace MediaBrowser.Providers.TV
{
public SeasonMetadataService(
IServerConfigurationManager serverConfigurationManager,
- ILogger logger,
+ ILogger<SeasonMetadataService> logger,
IProviderManager providerManager,
IFileSystem fileSystem,
ILibraryManager libraryManager)
diff --git a/MediaBrowser.Providers/TV/SeriesMetadataService.cs b/MediaBrowser.Providers/TV/SeriesMetadataService.cs
index e9e633ce7..5e75a8125 100644
--- a/MediaBrowser.Providers/TV/SeriesMetadataService.cs
+++ b/MediaBrowser.Providers/TV/SeriesMetadataService.cs
@@ -9,7 +9,7 @@ using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Globalization;
using MediaBrowser.Model.IO;
using MediaBrowser.Providers.Manager;
-using MediaBrowser.Providers.TV.TheTVDB;
+using MediaBrowser.Providers.Plugins.TheTvdb;
using Microsoft.Extensions.Logging;
namespace MediaBrowser.Providers.TV
@@ -17,20 +17,20 @@ namespace MediaBrowser.Providers.TV
public class SeriesMetadataService : MetadataService<Series, SeriesInfo>
{
private readonly ILocalizationManager _localization;
- private readonly TvDbClientManager _tvDbClientManager;
+ private readonly TvdbClientManager _tvdbClientManager;
public SeriesMetadataService(
IServerConfigurationManager serverConfigurationManager,
- ILogger logger,
+ ILogger<SeriesMetadataService> logger,
IProviderManager providerManager,
IFileSystem fileSystem,
ILibraryManager libraryManager,
ILocalizationManager localization,
- TvDbClientManager tvDbClientManager)
+ TvdbClientManager tvdbClientManager)
: base(serverConfigurationManager, logger, providerManager, fileSystem, libraryManager)
{
_localization = localization;
- _tvDbClientManager = tvDbClientManager;
+ _tvdbClientManager = tvdbClientManager;
}
/// <inheritdoc />
@@ -47,7 +47,7 @@ namespace MediaBrowser.Providers.TV
LibraryManager,
_localization,
FileSystem,
- _tvDbClientManager);
+ _tvdbClientManager);
try
{
diff --git a/MediaBrowser.Providers/TV/TvExternalIds.cs b/MediaBrowser.Providers/TV/TvExternalIds.cs
index 646dae3e0..baf854285 100644
--- a/MediaBrowser.Providers/TV/TvExternalIds.cs
+++ b/MediaBrowser.Providers/TV/TvExternalIds.cs
@@ -1,7 +1,7 @@
using MediaBrowser.Controller.Entities.TV;
using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Entities;
-using MediaBrowser.Providers.TV.TheTVDB;
+using MediaBrowser.Providers.Plugins.TheTvdb;
namespace MediaBrowser.Providers.TV
{
diff --git a/MediaBrowser.Providers/Tmdb/BoxSets/TmdbBoxSetImageProvider.cs b/MediaBrowser.Providers/Tmdb/BoxSets/TmdbBoxSetImageProvider.cs
index 5db0edac2..0bdf2bce1 100644
--- a/MediaBrowser.Providers/Tmdb/BoxSets/TmdbBoxSetImageProvider.cs
+++ b/MediaBrowser.Providers/Tmdb/BoxSets/TmdbBoxSetImageProvider.cs
@@ -10,7 +10,6 @@ using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Dto;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Providers;
-using MediaBrowser.Providers.Movies;
using MediaBrowser.Providers.Tmdb.Models.Collections;
using MediaBrowser.Providers.Tmdb.Models.General;
using MediaBrowser.Providers.Tmdb.Movies;
diff --git a/MediaBrowser.Providers/Tmdb/BoxSets/TmdbBoxSetProvider.cs b/MediaBrowser.Providers/Tmdb/BoxSets/TmdbBoxSetProvider.cs
index a215177a9..dd3783ffb 100644
--- a/MediaBrowser.Providers/Tmdb/BoxSets/TmdbBoxSetProvider.cs
+++ b/MediaBrowser.Providers/Tmdb/BoxSets/TmdbBoxSetProvider.cs
@@ -37,7 +37,14 @@ namespace MediaBrowser.Providers.Tmdb.BoxSets
private readonly IHttpClient _httpClient;
private readonly ILibraryManager _libraryManager;
- public TmdbBoxSetProvider(ILogger logger, IJsonSerializer json, IServerConfigurationManager config, IFileSystem fileSystem, ILocalizationManager localization, IHttpClient httpClient, ILibraryManager libraryManager)
+ public TmdbBoxSetProvider(
+ ILogger<TmdbBoxSetProvider> logger,
+ IJsonSerializer json,
+ IServerConfigurationManager config,
+ IFileSystem fileSystem,
+ ILocalizationManager localization,
+ IHttpClient httpClient,
+ ILibraryManager libraryManager)
{
_logger = logger;
_json = json;
diff --git a/MediaBrowser.Providers/Tmdb/Models/General/Profile.cs b/MediaBrowser.Providers/Tmdb/Models/General/Profile.cs
index 73a049c73..f87d14850 100644
--- a/MediaBrowser.Providers/Tmdb/Models/General/Profile.cs
+++ b/MediaBrowser.Providers/Tmdb/Models/General/Profile.cs
@@ -2,10 +2,10 @@ namespace MediaBrowser.Providers.Tmdb.Models.General
{
public class Profile
{
- public string File_Path { get; set; }
- public int Width { get; set; }
- public int Height { get; set; }
- public object Iso_639_1 { get; set; }
- public double Aspect_Ratio { get; set; }
+ public string File_Path { get; set; }
+ public int Width { get; set; }
+ public int Height { get; set; }
+ public object Iso_639_1 { get; set; }
+ public double Aspect_Ratio { get; set; }
}
}
diff --git a/MediaBrowser.Providers/Tmdb/Models/Search/ExternalIdLookupResult.cs b/MediaBrowser.Providers/Tmdb/Models/Search/ExternalIdLookupResult.cs
index 6d9fe7081..d19f4e8cb 100644
--- a/MediaBrowser.Providers/Tmdb/Models/Search/ExternalIdLookupResult.cs
+++ b/MediaBrowser.Providers/Tmdb/Models/Search/ExternalIdLookupResult.cs
@@ -1,5 +1,4 @@
using System.Collections.Generic;
-using MediaBrowser.Providers.Movies;
namespace MediaBrowser.Providers.Tmdb.Models.Search
{
diff --git a/MediaBrowser.Providers/Tmdb/Movies/GenericTmdbMovieInfo.cs b/MediaBrowser.Providers/Tmdb/Movies/GenericTmdbMovieInfo.cs
index b7b447b68..ad42b564c 100644
--- a/MediaBrowser.Providers/Tmdb/Movies/GenericTmdbMovieInfo.cs
+++ b/MediaBrowser.Providers/Tmdb/Movies/GenericTmdbMovieInfo.cs
@@ -11,10 +11,8 @@ using MediaBrowser.Controller.Entities.Movies;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Entities;
-using MediaBrowser.Model.Extensions;
using MediaBrowser.Model.IO;
using MediaBrowser.Model.Serialization;
-using MediaBrowser.Providers.Movies;
using MediaBrowser.Providers.Tmdb.Models.Movies;
using Microsoft.Extensions.Logging;
diff --git a/MediaBrowser.Providers/Tmdb/Movies/TmdbImageProvider.cs b/MediaBrowser.Providers/Tmdb/Movies/TmdbImageProvider.cs
index cdb96e6ac..039a49728 100644
--- a/MediaBrowser.Providers/Tmdb/Movies/TmdbImageProvider.cs
+++ b/MediaBrowser.Providers/Tmdb/Movies/TmdbImageProvider.cs
@@ -13,7 +13,6 @@ using MediaBrowser.Model.Entities;
using MediaBrowser.Model.IO;
using MediaBrowser.Model.Providers;
using MediaBrowser.Model.Serialization;
-using MediaBrowser.Providers.Movies;
using MediaBrowser.Providers.Tmdb.Models.General;
using MediaBrowser.Providers.Tmdb.Models.Movies;
diff --git a/MediaBrowser.Providers/Tmdb/Movies/TmdbMovieProvider.cs b/MediaBrowser.Providers/Tmdb/Movies/TmdbMovieProvider.cs
index a1bea5847..e2fd5b9e3 100644
--- a/MediaBrowser.Providers/Tmdb/Movies/TmdbMovieProvider.cs
+++ b/MediaBrowser.Providers/Tmdb/Movies/TmdbMovieProvider.cs
@@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Net;
+using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using MediaBrowser.Common;
@@ -19,7 +20,6 @@ using MediaBrowser.Model.IO;
using MediaBrowser.Model.Net;
using MediaBrowser.Model.Providers;
using MediaBrowser.Model.Serialization;
-using MediaBrowser.Providers.Movies;
using MediaBrowser.Providers.Tmdb.Models.Movies;
using Microsoft.Extensions.Logging;
@@ -37,20 +37,25 @@ namespace MediaBrowser.Providers.Tmdb.Movies
private readonly IFileSystem _fileSystem;
private readonly IServerConfigurationManager _configurationManager;
private readonly ILogger _logger;
- private readonly ILocalizationManager _localization;
private readonly ILibraryManager _libraryManager;
private readonly IApplicationHost _appHost;
private readonly CultureInfo _usCulture = new CultureInfo("en-US");
- public TmdbMovieProvider(IJsonSerializer jsonSerializer, IHttpClient httpClient, IFileSystem fileSystem, IServerConfigurationManager configurationManager, ILogger logger, ILocalizationManager localization, ILibraryManager libraryManager, IApplicationHost appHost)
+ public TmdbMovieProvider(
+ IJsonSerializer jsonSerializer,
+ IHttpClient httpClient,
+ IFileSystem fileSystem,
+ IServerConfigurationManager configurationManager,
+ ILogger<TmdbMovieProvider> logger,
+ ILibraryManager libraryManager,
+ IApplicationHost appHost)
{
_jsonSerializer = jsonSerializer;
_httpClient = httpClient;
_fileSystem = fileSystem;
_configurationManager = configurationManager;
_logger = logger;
- _localization = localization;
_libraryManager = libraryManager;
_appHost = appHost;
Current = this;
@@ -402,15 +407,15 @@ namespace MediaBrowser.Providers.Tmdb.Movies
private static long _lastRequestTicks;
// The limit is 40 requests per 10 seconds
- private static int requestIntervalMs = 300;
+ private const int RequestIntervalMs = 300;
/// <summary>
/// Gets the movie db response.
/// </summary>
internal async Task<HttpResponseInfo> GetMovieDbResponse(HttpRequestOptions options)
{
- var delayTicks = (requestIntervalMs * 10000) - (DateTime.UtcNow.Ticks - _lastRequestTicks);
- var delayMs = Math.Min(delayTicks / 10000, requestIntervalMs);
+ var delayTicks = (RequestIntervalMs * 10000) - (DateTime.UtcNow.Ticks - _lastRequestTicks);
+ var delayMs = Math.Min(delayTicks / 10000, RequestIntervalMs);
if (delayMs > 0)
{
@@ -423,11 +428,13 @@ namespace MediaBrowser.Providers.Tmdb.Movies
options.BufferContent = true;
options.UserAgent = _appHost.ApplicationUserAgent;
- return await _httpClient.SendAsync(options, "GET").ConfigureAwait(false);
+ return await _httpClient.SendAsync(options, HttpMethod.Get).ConfigureAwait(false);
}
+ /// <inheritdoc />
public int Order => 1;
+ /// <inheritdoc />
public Task<HttpResponseInfo> GetImageResponse(string url, CancellationToken cancellationToken)
{
return _httpClient.GetResponse(new HttpRequestOptions
diff --git a/MediaBrowser.Providers/Tmdb/Music/TmdbMusicVideoProvider.cs b/MediaBrowser.Providers/Tmdb/Music/TmdbMusicVideoProvider.cs
index f3f8a92cf..81909fa38 100644
--- a/MediaBrowser.Providers/Tmdb/Music/TmdbMusicVideoProvider.cs
+++ b/MediaBrowser.Providers/Tmdb/Music/TmdbMusicVideoProvider.cs
@@ -6,7 +6,6 @@ using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Providers;
-using MediaBrowser.Providers.Movies;
using MediaBrowser.Providers.Tmdb.Movies;
namespace MediaBrowser.Providers.Tmdb.Music
diff --git a/MediaBrowser.Providers/Tmdb/People/TmdbPersonImageProvider.cs b/MediaBrowser.Providers/Tmdb/People/TmdbPersonImageProvider.cs
index 44ccbf453..e205d796a 100644
--- a/MediaBrowser.Providers/Tmdb/People/TmdbPersonImageProvider.cs
+++ b/MediaBrowser.Providers/Tmdb/People/TmdbPersonImageProvider.cs
@@ -10,7 +10,6 @@ using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Providers;
using MediaBrowser.Model.Serialization;
-using MediaBrowser.Providers.Movies;
using MediaBrowser.Providers.Tmdb.Models.General;
using MediaBrowser.Providers.Tmdb.Models.People;
using MediaBrowser.Providers.Tmdb.Movies;
diff --git a/MediaBrowser.Providers/Tmdb/People/TmdbPersonProvider.cs b/MediaBrowser.Providers/Tmdb/People/TmdbPersonProvider.cs
index 130403e4d..588001169 100644
--- a/MediaBrowser.Providers/Tmdb/People/TmdbPersonProvider.cs
+++ b/MediaBrowser.Providers/Tmdb/People/TmdbPersonProvider.cs
@@ -37,7 +37,12 @@ namespace MediaBrowser.Providers.Tmdb.People
private readonly IHttpClient _httpClient;
private readonly ILogger _logger;
- public TmdbPersonProvider(IFileSystem fileSystem, IServerConfigurationManager configurationManager, IJsonSerializer jsonSerializer, IHttpClient httpClient, ILogger logger)
+ public TmdbPersonProvider(
+ IFileSystem fileSystem,
+ IServerConfigurationManager configurationManager,
+ IJsonSerializer jsonSerializer,
+ IHttpClient httpClient,
+ ILogger<TmdbPersonProvider> logger)
{
_fileSystem = fileSystem;
_configurationManager = configurationManager;
@@ -234,7 +239,7 @@ namespace MediaBrowser.Providers.Tmdb.People
{
Directory.CreateDirectory(Path.GetDirectoryName(dataFilePath));
- using (var fs = _fileSystem.GetFileStream(dataFilePath, FileOpenMode.Create, FileAccessMode.Write, FileShareMode.Read, true))
+ using (var fs = new FileStream(dataFilePath, FileMode.Create, FileAccess.Write, FileShare.Read, IODefaults.FileStreamBufferSize, true))
{
await json.CopyToAsync(fs).ConfigureAwait(false);
}
diff --git a/MediaBrowser.Providers/Tmdb/TV/TmdbEpisodeImageProvider.cs b/MediaBrowser.Providers/Tmdb/TV/TmdbEpisodeImageProvider.cs
index 51e7891a1..558c8149e 100644
--- a/MediaBrowser.Providers/Tmdb/TV/TmdbEpisodeImageProvider.cs
+++ b/MediaBrowser.Providers/Tmdb/TV/TmdbEpisodeImageProvider.cs
@@ -13,7 +13,6 @@ using MediaBrowser.Model.Globalization;
using MediaBrowser.Model.IO;
using MediaBrowser.Model.Providers;
using MediaBrowser.Model.Serialization;
-using MediaBrowser.Providers.Movies;
using MediaBrowser.Providers.Tmdb.Models.General;
using MediaBrowser.Providers.Tmdb.Movies;
using Microsoft.Extensions.Logging;
diff --git a/MediaBrowser.Providers/Tmdb/TV/TmdbEpisodeProviderBase.cs b/MediaBrowser.Providers/Tmdb/TV/TmdbEpisodeProviderBase.cs
index 2003261c9..e87fe9332 100644
--- a/MediaBrowser.Providers/Tmdb/TV/TmdbEpisodeProviderBase.cs
+++ b/MediaBrowser.Providers/Tmdb/TV/TmdbEpisodeProviderBase.cs
@@ -8,7 +8,6 @@ using MediaBrowser.Controller.Configuration;
using MediaBrowser.Model.Globalization;
using MediaBrowser.Model.IO;
using MediaBrowser.Model.Serialization;
-using MediaBrowser.Providers.Movies;
using MediaBrowser.Providers.Tmdb.Models.TV;
using MediaBrowser.Providers.Tmdb.Movies;
using Microsoft.Extensions.Logging;
diff --git a/MediaBrowser.Providers/Tmdb/TV/TmdbSeasonImageProvider.cs b/MediaBrowser.Providers/Tmdb/TV/TmdbSeasonImageProvider.cs
index 24cc8c73b..698a43604 100644
--- a/MediaBrowser.Providers/Tmdb/TV/TmdbSeasonImageProvider.cs
+++ b/MediaBrowser.Providers/Tmdb/TV/TmdbSeasonImageProvider.cs
@@ -10,8 +10,6 @@ using MediaBrowser.Controller.Entities.TV;
using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Dto;
using MediaBrowser.Model.Entities;
-using MediaBrowser.Model.Globalization;
-using MediaBrowser.Model.IO;
using MediaBrowser.Model.Providers;
using MediaBrowser.Model.Serialization;
using MediaBrowser.Providers.Tmdb.Models.General;
diff --git a/MediaBrowser.Providers/Tmdb/TV/TmdbSeasonProvider.cs b/MediaBrowser.Providers/Tmdb/TV/TmdbSeasonProvider.cs
index fc0cde8b3..5ad331971 100644
--- a/MediaBrowser.Providers/Tmdb/TV/TmdbSeasonProvider.cs
+++ b/MediaBrowser.Providers/Tmdb/TV/TmdbSeasonProvider.cs
@@ -14,7 +14,6 @@ using MediaBrowser.Model.IO;
using MediaBrowser.Model.Net;
using MediaBrowser.Model.Providers;
using MediaBrowser.Model.Serialization;
-using MediaBrowser.Providers.Movies;
using MediaBrowser.Providers.Tmdb.Models.TV;
using MediaBrowser.Providers.Tmdb.Movies;
using Microsoft.Extensions.Logging;
diff --git a/MediaBrowser.Providers/Tmdb/TV/TmdbSeriesImageProvider.cs b/MediaBrowser.Providers/Tmdb/TV/TmdbSeriesImageProvider.cs
index 882ec7574..0460fe994 100644
--- a/MediaBrowser.Providers/Tmdb/TV/TmdbSeriesImageProvider.cs
+++ b/MediaBrowser.Providers/Tmdb/TV/TmdbSeriesImageProvider.cs
@@ -12,7 +12,6 @@ using MediaBrowser.Model.Entities;
using MediaBrowser.Model.IO;
using MediaBrowser.Model.Providers;
using MediaBrowser.Model.Serialization;
-using MediaBrowser.Providers.Movies;
using MediaBrowser.Providers.Tmdb.Models.General;
using MediaBrowser.Providers.Tmdb.Models.TV;
using MediaBrowser.Providers.Tmdb.Movies;
diff --git a/MediaBrowser.Providers/Tmdb/TV/TmdbSeriesProvider.cs b/MediaBrowser.Providers/Tmdb/TV/TmdbSeriesProvider.cs
index 304f34c25..7195dc42a 100644
--- a/MediaBrowser.Providers/Tmdb/TV/TmdbSeriesProvider.cs
+++ b/MediaBrowser.Providers/Tmdb/TV/TmdbSeriesProvider.cs
@@ -17,7 +17,6 @@ using MediaBrowser.Model.Globalization;
using MediaBrowser.Model.IO;
using MediaBrowser.Model.Providers;
using MediaBrowser.Model.Serialization;
-using MediaBrowser.Providers.Movies;
using MediaBrowser.Providers.Tmdb.Models.Search;
using MediaBrowser.Providers.Tmdb.Models.TV;
using MediaBrowser.Providers.Tmdb.Movies;
@@ -40,7 +39,14 @@ namespace MediaBrowser.Providers.Tmdb.TV
private readonly IHttpClient _httpClient;
private readonly ILibraryManager _libraryManager;
- public TmdbSeriesProvider(IJsonSerializer jsonSerializer, IFileSystem fileSystem, IServerConfigurationManager configurationManager, ILogger logger, ILocalizationManager localization, IHttpClient httpClient, ILibraryManager libraryManager)
+ public TmdbSeriesProvider(
+ IJsonSerializer jsonSerializer,
+ IFileSystem fileSystem,
+ IServerConfigurationManager configurationManager,
+ ILogger<TmdbSeriesProvider> logger,
+ ILocalizationManager localization,
+ IHttpClient httpClient,
+ ILibraryManager libraryManager)
{
_jsonSerializer = jsonSerializer;
_fileSystem = fileSystem;
diff --git a/MediaBrowser.Providers/Tmdb/Trailers/TmdbTrailerProvider.cs b/MediaBrowser.Providers/Tmdb/Trailers/TmdbTrailerProvider.cs
index b0dec0245..b15de0125 100644
--- a/MediaBrowser.Providers/Tmdb/Trailers/TmdbTrailerProvider.cs
+++ b/MediaBrowser.Providers/Tmdb/Trailers/TmdbTrailerProvider.cs
@@ -5,7 +5,6 @@ using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Providers;
-using MediaBrowser.Providers.Movies;
using MediaBrowser.Providers.Tmdb.Movies;
namespace MediaBrowser.Providers.Tmdb.Trailers
diff --git a/MediaBrowser.Providers/Users/UserMetadataService.cs b/MediaBrowser.Providers/Users/UserMetadataService.cs
index 9c2e27816..fb6c91b6c 100644
--- a/MediaBrowser.Providers/Users/UserMetadataService.cs
+++ b/MediaBrowser.Providers/Users/UserMetadataService.cs
@@ -13,7 +13,7 @@ namespace MediaBrowser.Providers.Users
{
public UserMetadataService(
IServerConfigurationManager serverConfigurationManager,
- ILogger logger,
+ ILogger<UserMetadataService> logger,
IProviderManager providerManager,
IFileSystem fileSystem,
ILibraryManager libraryManager)
diff --git a/MediaBrowser.Providers/Videos/VideoMetadataService.cs b/MediaBrowser.Providers/Videos/VideoMetadataService.cs
index 996af0368..21378ada0 100644
--- a/MediaBrowser.Providers/Videos/VideoMetadataService.cs
+++ b/MediaBrowser.Providers/Videos/VideoMetadataService.cs
@@ -13,7 +13,7 @@ namespace MediaBrowser.Providers.Videos
{
public VideoMetadataService(
IServerConfigurationManager serverConfigurationManager,
- ILogger logger,
+ ILogger<VideoMetadataService> logger,
IProviderManager providerManager,
IFileSystem fileSystem,
ILibraryManager libraryManager)
diff --git a/MediaBrowser.Providers/Years/YearMetadataService.cs b/MediaBrowser.Providers/Years/YearMetadataService.cs
index 414795e35..2a0fa19ea 100644
--- a/MediaBrowser.Providers/Years/YearMetadataService.cs
+++ b/MediaBrowser.Providers/Years/YearMetadataService.cs
@@ -13,7 +13,7 @@ namespace MediaBrowser.Providers.Years
{
public YearMetadataService(
IServerConfigurationManager serverConfigurationManager,
- ILogger logger,
+ ILogger<YearMetadataService> logger,
IProviderManager providerManager,
IFileSystem fileSystem,
ILibraryManager libraryManager)
diff --git a/MediaBrowser.WebDashboard/Api/ConfigurationPageInfo.cs b/MediaBrowser.WebDashboard/Api/ConfigurationPageInfo.cs
index b8f9e09b5..e49a4be8a 100644
--- a/MediaBrowser.WebDashboard/Api/ConfigurationPageInfo.cs
+++ b/MediaBrowser.WebDashboard/Api/ConfigurationPageInfo.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
using MediaBrowser.Common.Plugins;
using MediaBrowser.Controller.Plugins;
using MediaBrowser.Model.Plugins;
@@ -6,29 +8,6 @@ namespace MediaBrowser.WebDashboard.Api
{
public class ConfigurationPageInfo
{
- /// <summary>
- /// Gets the name.
- /// </summary>
- /// <value>The name.</value>
- public string Name { get; set; }
- public bool EnableInMainMenu { get; set; }
- public string MenuSection { get; set; }
- public string MenuIcon { get; set; }
-
- public string DisplayName { get; set; }
-
- /// <summary>
- /// Gets the type of the configuration page.
- /// </summary>
- /// <value>The type of the configuration page.</value>
- public ConfigurationPageType ConfigurationPageType { get; set; }
-
- /// <summary>
- /// Gets or sets the plugin id.
- /// </summary>
- /// <value>The plugin id.</value>
- public string PluginId { get; set; }
-
public ConfigurationPageInfo(IPluginConfigurationPage page)
{
Name = page.Name;
@@ -54,5 +33,31 @@ namespace MediaBrowser.WebDashboard.Api
// Don't use "N" because it needs to match Plugin.Id
PluginId = plugin.Id.ToString();
}
+
+ /// <summary>
+ /// Gets or sets the name.
+ /// </summary>
+ /// <value>The name.</value>
+ public string Name { get; set; }
+
+ public bool EnableInMainMenu { get; set; }
+
+ public string MenuSection { get; set; }
+
+ public string MenuIcon { get; set; }
+
+ public string DisplayName { get; set; }
+
+ /// <summary>
+ /// Gets or sets the type of the configuration page.
+ /// </summary>
+ /// <value>The type of the configuration page.</value>
+ public ConfigurationPageType ConfigurationPageType { get; set; }
+
+ /// <summary>
+ /// Gets or sets the plugin id.
+ /// </summary>
+ /// <value>The plugin id.</value>
+ public string PluginId { get; set; }
}
}
diff --git a/MediaBrowser.WebDashboard/Api/DashboardService.cs b/MediaBrowser.WebDashboard/Api/DashboardService.cs
index fadf32b28..133a35527 100644
--- a/MediaBrowser.WebDashboard/Api/DashboardService.cs
+++ b/MediaBrowser.WebDashboard/Api/DashboardService.cs
@@ -1,5 +1,10 @@
+#pragma warning disable CS1591
+#pragma warning disable SA1402
+#pragma warning disable SA1649
+
using System;
using System.Collections.Generic;
+using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
@@ -7,20 +12,20 @@ using MediaBrowser.Common.Extensions;
using MediaBrowser.Common.Plugins;
using MediaBrowser.Controller;
using MediaBrowser.Controller.Configuration;
+using MediaBrowser.Controller.Extensions;
using MediaBrowser.Controller.Net;
using MediaBrowser.Controller.Plugins;
-using MediaBrowser.Model.Globalization;
using MediaBrowser.Model.IO;
using MediaBrowser.Model.Net;
using MediaBrowser.Model.Plugins;
-using MediaBrowser.Model.Serialization;
using MediaBrowser.Model.Services;
+using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
namespace MediaBrowser.WebDashboard.Api
{
/// <summary>
- /// Class GetDashboardConfigurationPages
+ /// Class GetDashboardConfigurationPages.
/// </summary>
[Route("/web/ConfigurationPages", "GET")]
public class GetDashboardConfigurationPages : IReturn<List<ConfigurationPageInfo>>
@@ -30,11 +35,12 @@ namespace MediaBrowser.WebDashboard.Api
/// </summary>
/// <value>The type of the page.</value>
public ConfigurationPageType? PageType { get; set; }
+
public bool? EnableInMainMenu { get; set; }
}
/// <summary>
- /// Class GetDashboardConfigurationPage
+ /// Class GetDashboardConfigurationPage.
/// </summary>
[Route("/web/ConfigurationPage", "GET")]
public class GetDashboardConfigurationPage
@@ -58,7 +64,7 @@ namespace MediaBrowser.WebDashboard.Api
}
/// <summary>
- /// Class GetDashboardResource
+ /// Class GetDashboardResource.
/// </summary>
[Route("/web/{ResourceName*}", "GET", IsHidden = true)]
public class GetDashboardResource
@@ -68,6 +74,7 @@ namespace MediaBrowser.WebDashboard.Api
/// </summary>
/// <value>The name.</value>
public string ResourceName { get; set; }
+
/// <summary>
/// Gets or sets the V.
/// </summary>
@@ -81,7 +88,7 @@ namespace MediaBrowser.WebDashboard.Api
}
/// <summary>
- /// Class DashboardService
+ /// Class DashboardService.
/// </summary>
public class DashboardService : IService, IRequiresRequest
{
@@ -96,62 +103,74 @@ namespace MediaBrowser.WebDashboard.Api
/// </summary>
/// <value>The HTTP result factory.</value>
private readonly IHttpResultFactory _resultFactory;
-
- /// <summary>
- /// Gets or sets the request context.
- /// </summary>
- /// <value>The request context.</value>
- public IRequest Request { get; set; }
-
- /// <summary>
- /// The _app host
- /// </summary>
private readonly IServerApplicationHost _appHost;
-
- /// <summary>
- /// The _server configuration manager
- /// </summary>
+ private readonly IConfiguration _appConfig;
private readonly IServerConfigurationManager _serverConfigurationManager;
-
private readonly IFileSystem _fileSystem;
- private IResourceFileManager _resourceFileManager;
+ private readonly IResourceFileManager _resourceFileManager;
/// <summary>
/// Initializes a new instance of the <see cref="DashboardService" /> class.
/// </summary>
+ /// <param name="logger">The logger.</param>
+ /// <param name="appHost">The application host.</param>
+ /// <param name="appConfig">The application configuration.</param>
+ /// <param name="resourceFileManager">The resource file manager.</param>
+ /// <param name="serverConfigurationManager">The server configuration manager.</param>
+ /// <param name="fileSystem">The file system.</param>
+ /// <param name="resultFactory">The result factory.</param>
public DashboardService(
+ ILogger<DashboardService> logger,
IServerApplicationHost appHost,
+ IConfiguration appConfig,
IResourceFileManager resourceFileManager,
IServerConfigurationManager serverConfigurationManager,
IFileSystem fileSystem,
- ILogger logger,
IHttpResultFactory resultFactory)
{
+ _logger = logger;
_appHost = appHost;
+ _appConfig = appConfig;
+ _resourceFileManager = resourceFileManager;
_serverConfigurationManager = serverConfigurationManager;
_fileSystem = fileSystem;
- _logger = logger;
_resultFactory = resultFactory;
- _resourceFileManager = resourceFileManager;
}
/// <summary>
- /// Gets the path for the web interface.
+ /// Gets or sets the request context.
/// </summary>
- /// <value>The path for the web interface.</value>
- public string DashboardUIPath
+ /// <value>The request context.</value>
+ public IRequest Request { get; set; }
+
+ /// <summary>
+ /// Gets the path of the directory containing the static web interface content, or null if the server is not
+ /// hosting the web client.
+ /// </summary>
+ public string DashboardUIPath => GetDashboardUIPath(_appConfig, _serverConfigurationManager);
+
+ /// <summary>
+ /// Gets the path of the directory containing the static web interface content.
+ /// </summary>
+ /// <param name="appConfig">The app configuration.</param>
+ /// <param name="serverConfigManager">The server configuration manager.</param>
+ /// <returns>The directory path, or null if the server is not hosting the web client.</returns>
+ public static string GetDashboardUIPath(IConfiguration appConfig, IServerConfigurationManager serverConfigManager)
{
- get
+ if (!appConfig.HostWebClient())
{
- if (!string.IsNullOrEmpty(_serverConfigurationManager.Configuration.DashboardSourcePath))
- {
- return _serverConfigurationManager.Configuration.DashboardSourcePath;
- }
+ return null;
+ }
- return _serverConfigurationManager.ApplicationPaths.WebPath;
+ if (!string.IsNullOrEmpty(serverConfigManager.Configuration.DashboardSourcePath))
+ {
+ return serverConfigManager.Configuration.DashboardSourcePath;
}
+
+ return serverConfigManager.ApplicationPaths.WebPath;
}
+ [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "request", Justification = "Required for ServiceStack")]
public object Get(GetFavIcon request)
{
return Get(new GetDashboardResource
@@ -165,6 +184,7 @@ namespace MediaBrowser.WebDashboard.Api
/// </summary>
/// <param name="request">The request.</param>
/// <returns>System.Object.</returns>
+ [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "request", Justification = "Required for ServiceStack")]
public Task<object> Get(GetDashboardConfigurationPage request)
{
IPlugin plugin = null;
@@ -189,7 +209,7 @@ namespace MediaBrowser.WebDashboard.Api
stream = plugin.GetType().Assembly.GetManifestResourceStream(altPage.Item1.EmbeddedResourcePath);
isJs = string.Equals(Path.GetExtension(altPage.Item1.EmbeddedResourcePath), ".js", StringComparison.OrdinalIgnoreCase);
- isTemplate = altPage.Item1.EmbeddedResourcePath.EndsWith(".template.html");
+ isTemplate = altPage.Item1.EmbeddedResourcePath.EndsWith(".template.html", StringComparison.Ordinal);
}
}
@@ -205,7 +225,7 @@ namespace MediaBrowser.WebDashboard.Api
return _resultFactory.GetStaticResult(Request, plugin.Version.ToString().GetMD5(), null, null, MimeTypes.GetMimeType("page.html"), () => Task.FromResult(stream));
}
- return _resultFactory.GetStaticResult(Request, plugin.Version.ToString().GetMD5(), null, null, MimeTypes.GetMimeType("page.html"), () => GetPackageCreator(DashboardUIPath).ModifyHtml("dummy.html", stream, null, _appHost.ApplicationVersionString, null));
+ return _resultFactory.GetStaticResult(Request, plugin.Version.ToString().GetMD5(), null, null, MimeTypes.GetMimeType("page.html"), () => PackageCreator.ModifyHtml(false, stream, null, _appHost.ApplicationVersionString, null));
}
throw new ResourceNotFoundException();
@@ -237,7 +257,6 @@ namespace MediaBrowser.WebDashboard.Api
// Don't allow a failing plugin to fail them all
var configPages = pages.Select(p =>
{
-
try
{
return new ConfigurationPageInfo(p);
@@ -288,6 +307,7 @@ namespace MediaBrowser.WebDashboard.Api
return GetPluginPages(plugin).Select(i => new ConfigurationPageInfo(plugin, i.Item1));
}
+ [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "request", Justification = "Required for ServiceStack")]
public object Get(GetRobotsTxt request)
{
return Get(new GetDashboardResource
@@ -303,6 +323,11 @@ namespace MediaBrowser.WebDashboard.Api
/// <returns>System.Object.</returns>
public async Task<object> Get(GetDashboardResource request)
{
+ if (!_appConfig.HostWebClient() || DashboardUIPath == null)
+ {
+ throw new ResourceNotFoundException();
+ }
+
var path = request.ResourceName;
var contentType = MimeTypes.GetMimeType(path);
@@ -350,7 +375,7 @@ namespace MediaBrowser.WebDashboard.Api
return await _resultFactory.GetStaticResult(Request, cacheKey, null, cacheDuration, contentType, () => GetResourceStream(basePath, path, localizationCulture)).ConfigureAwait(false);
}
- return await _resultFactory.GetStaticFileResult(Request, _resourceFileManager.GetResourcePath(basePath, path));
+ return await _resultFactory.GetStaticFileResult(Request, _resourceFileManager.GetResourcePath(basePath, path)).ConfigureAwait(false);
}
private string GetLocalizationCulture()
@@ -374,6 +399,11 @@ namespace MediaBrowser.WebDashboard.Api
public async Task<object> Get(GetDashboardPackage request)
{
+ if (!_appConfig.HostWebClient() || DashboardUIPath == null)
+ {
+ throw new ResourceNotFoundException();
+ }
+
var mode = request.Mode;
var inputPath = string.IsNullOrWhiteSpace(mode) ?
@@ -392,9 +422,9 @@ namespace MediaBrowser.WebDashboard.Api
{
Directory.Delete(targetPath, true);
}
- catch (IOException)
+ catch (IOException ex)
{
-
+ _logger.LogError(ex, "Error deleting {Path}", targetPath);
}
CopyDirectory(inputPath, targetPath);
@@ -402,9 +432,9 @@ namespace MediaBrowser.WebDashboard.Api
var appVersion = _appHost.ApplicationVersionString;
- await DumpHtml(packageCreator, inputPath, targetPath, mode, appVersion);
+ await DumpHtml(packageCreator, inputPath, targetPath, mode, appVersion).ConfigureAwait(false);
- return "";
+ return string.Empty;
}
private async Task DumpHtml(PackageCreator packageCreator, string source, string destination, string mode, string appVersion)
@@ -425,9 +455,9 @@ namespace MediaBrowser.WebDashboard.Api
private async Task DumpFile(PackageCreator packageCreator, string resourceVirtualPath, string destinationFilePath, string mode, string appVersion)
{
using (var stream = await packageCreator.GetResource(resourceVirtualPath, mode, null, appVersion).ConfigureAwait(false))
- using (var fs = _fileSystem.GetFileStream(destinationFilePath, FileOpenMode.Create, FileAccessMode.Write, FileShareMode.Read))
+ using (var fs = new FileStream(destinationFilePath, FileMode.Create, FileAccess.Write, FileShare.Read))
{
- await stream.CopyToAsync(fs);
+ await stream.CopyToAsync(fs).ConfigureAwait(false);
}
}
@@ -435,14 +465,17 @@ namespace MediaBrowser.WebDashboard.Api
{
Directory.CreateDirectory(destination);
- //Now Create all of the directories
+ // Now Create all of the directories
foreach (var dirPath in _fileSystem.GetDirectories(source, true))
- Directory.CreateDirectory(dirPath.FullName.Replace(source, destination));
+ {
+ Directory.CreateDirectory(dirPath.FullName.Replace(source, destination, StringComparison.Ordinal));
+ }
- //Copy all the files & Replaces any files with the same name
+ // Copy all the files & Replaces any files with the same name
foreach (var newPath in _fileSystem.GetFiles(source, true))
- File.Copy(newPath.FullName, newPath.FullName.Replace(source, destination), true);
+ {
+ File.Copy(newPath.FullName, newPath.FullName.Replace(source, destination, StringComparison.Ordinal), true);
+ }
}
}
-
}
diff --git a/MediaBrowser.WebDashboard/Api/PackageCreator.cs b/MediaBrowser.WebDashboard/Api/PackageCreator.cs
index 133bf61e8..b7c15a840 100644
--- a/MediaBrowser.WebDashboard/Api/PackageCreator.cs
+++ b/MediaBrowser.WebDashboard/Api/PackageCreator.cs
@@ -1,4 +1,7 @@
+#pragma warning disable CS1591
+
using System;
+using System.Globalization;
using System.IO;
using System.Text;
using System.Threading.Tasks;
@@ -28,7 +31,8 @@ namespace MediaBrowser.WebDashboard.Api
if (resourceStream != null && IsCoreHtml(virtualPath))
{
- resourceStream = await ModifyHtml(virtualPath, resourceStream, mode, appVersion, localizationCulture).ConfigureAwait(false);
+ bool isMainIndexPage = string.Equals(virtualPath, "index.html", StringComparison.OrdinalIgnoreCase);
+ resourceStream = await ModifyHtml(isMainIndexPage, resourceStream, mode, appVersion, localizationCulture).ConfigureAwait(false);
}
return resourceStream;
@@ -45,18 +49,24 @@ namespace MediaBrowser.WebDashboard.Api
}
/// <summary>
- /// Modifies the HTML by adding common meta tags, css and js.
+ /// Modifies the source HTML stream by adding common meta tags, css and js.
/// </summary>
- /// <returns>Task{Stream}.</returns>
- public async Task<Stream> ModifyHtml(
- string path,
+ /// <param name="isMainIndexPage">True if the stream contains content for the main index page.</param>
+ /// <param name="sourceStream">The stream whose content should be modified.</param>
+ /// <param name="mode">The client mode ('cordova', 'android', etc).</param>
+ /// <param name="appVersion">The application version.</param>
+ /// <param name="localizationCulture">The localization culture.</param>
+ /// <returns>
+ /// A task that represents the async operation to read and modify the input stream.
+ /// The task result contains a stream containing the modified HTML content.
+ /// </returns>
+ public static async Task<Stream> ModifyHtml(
+ bool isMainIndexPage,
Stream sourceStream,
string mode,
string appVersion,
string localizationCulture)
{
- var isMainIndexPage = string.Equals(path, "index.html", StringComparison.OrdinalIgnoreCase);
-
string html;
using (var reader = new StreamReader(sourceStream, Encoding.UTF8))
{
@@ -67,30 +77,29 @@ namespace MediaBrowser.WebDashboard.Api
{
var lang = localizationCulture.Split('-')[0];
- html = html.Replace("<html", "<html data-culture=\"" + localizationCulture + "\" lang=\"" + lang + "\"");
+ html = html.Replace("<html", "<html data-culture=\"" + localizationCulture + "\" lang=\"" + lang + "\"", StringComparison.Ordinal);
}
if (isMainIndexPage)
{
- html = html.Replace("<head>", "<head>" + GetMetaTags(mode));
+ html = html.Replace("<head>", "<head>" + GetMetaTags(mode), StringComparison.Ordinal);
}
// Disable embedded scripts from plugins. We'll run them later once resources have loaded
if (html.IndexOf("<script", StringComparison.OrdinalIgnoreCase) != -1)
{
- html = html.Replace("<script", "<!--<script");
- html = html.Replace("</script>", "</script>-->");
+ html = html.Replace("<script", "<!--<script", StringComparison.Ordinal);
+ html = html.Replace("</script>", "</script>-->", StringComparison.Ordinal);
}
if (isMainIndexPage)
{
- html = html.Replace("</body>", GetCommonJavascript(mode, appVersion) + "</body>");
+ html = html.Replace("</body>", GetCommonJavascript(mode, appVersion) + "</body>", StringComparison.Ordinal);
}
var bytes = Encoding.UTF8.GetBytes(html);
return new MemoryStream(bytes);
-
}
/// <summary>
@@ -123,11 +132,11 @@ namespace MediaBrowser.WebDashboard.Api
builder.Append("<script>");
if (!string.IsNullOrWhiteSpace(mode))
{
- builder.AppendFormat("window.appMode='{0}';", mode);
+ builder.AppendFormat(CultureInfo.InvariantCulture, "window.appMode='{0}';", mode);
}
else
{
- builder.AppendFormat("window.dashboardVersion='{0}';", version);
+ builder.AppendFormat(CultureInfo.InvariantCulture, "window.dashboardVersion='{0}';", version);
}
builder.Append("</script>");
diff --git a/MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj b/MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj
index 1d256d689..bcaee50f2 100644
--- a/MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj
+++ b/MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj
@@ -1,5 +1,10 @@
<Project Sdk="Microsoft.NET.Sdk">
+ <!-- ProjectGuid is only included as a requirement for SonarQube analysis -->
+ <PropertyGroup>
+ <ProjectGuid>{5624B7B5-B5A7-41D8-9F10-CC5611109619}</ProjectGuid>
+ </PropertyGroup>
+
<ItemGroup>
<ProjectReference Include="..\MediaBrowser.Controller\MediaBrowser.Controller.csproj" />
<ProjectReference Include="..\MediaBrowser.Common\MediaBrowser.Common.csproj" />
@@ -19,6 +24,19 @@
<TargetFramework>netstandard2.1</TargetFramework>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
+ <TreatWarningsAsErrors>true</TreatWarningsAsErrors>
+ </PropertyGroup>
+
+ <!-- Code Analyzers-->
+ <ItemGroup Condition=" '$(Configuration)' == 'Debug' ">
+ <PackageReference Include="Microsoft.CodeAnalysis.FxCopAnalyzers" Version="2.9.8" PrivateAssets="All" />
+ <PackageReference Include="SerilogAnalyzer" Version="0.15.0" PrivateAssets="All" />
+ <PackageReference Include="StyleCop.Analyzers" Version="1.1.118" PrivateAssets="All" />
+ <PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" PrivateAssets="All" />
+ </ItemGroup>
+
+ <PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
+ <CodeAnalysisRuleSet>../jellyfin.ruleset</CodeAnalysisRuleSet>
</PropertyGroup>
</Project>
diff --git a/MediaBrowser.WebDashboard/ServerEntryPoint.cs b/MediaBrowser.WebDashboard/ServerEntryPoint.cs
index 18ed54a78..5c7e8b3c7 100644
--- a/MediaBrowser.WebDashboard/ServerEntryPoint.cs
+++ b/MediaBrowser.WebDashboard/ServerEntryPoint.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
@@ -6,24 +8,25 @@ using MediaBrowser.Controller.Plugins;
namespace MediaBrowser.WebDashboard
{
- public class ServerEntryPoint : IServerEntryPoint
+ public sealed class ServerEntryPoint : IServerEntryPoint
{
- /// <summary>
- /// Gets the list of plugin configuration pages
- /// </summary>
- /// <value>The configuration pages.</value>
- public List<IPluginConfigurationPage> PluginConfigurationPages { get; private set; }
-
private readonly IApplicationHost _appHost;
- public static ServerEntryPoint Instance { get; private set; }
-
public ServerEntryPoint(IApplicationHost appHost)
{
_appHost = appHost;
Instance = this;
}
+ public static ServerEntryPoint Instance { get; private set; }
+
+ /// <summary>
+ /// Gets the list of plugin configuration pages.
+ /// </summary>
+ /// <value>The configuration pages.</value>
+ public List<IPluginConfigurationPage> PluginConfigurationPages { get; private set; }
+
+ /// <inheritdoc />
public Task RunAsync()
{
PluginConfigurationPages = _appHost.GetExports<IPluginConfigurationPage>().ToList();
@@ -31,6 +34,7 @@ namespace MediaBrowser.WebDashboard
return Task.CompletedTask;
}
+ /// <inheritdoc />
public void Dispose()
{
}
diff --git a/MediaBrowser.XbmcMetadata/Configuration/NfoConfigurationExtension.cs b/MediaBrowser.XbmcMetadata/Configuration/NfoConfigurationExtension.cs
new file mode 100644
index 000000000..fe3bc3cd3
--- /dev/null
+++ b/MediaBrowser.XbmcMetadata/Configuration/NfoConfigurationExtension.cs
@@ -0,0 +1,15 @@
+#pragma warning disable CS1591
+
+using MediaBrowser.Common.Configuration;
+using MediaBrowser.Model.Configuration;
+
+namespace MediaBrowser.XbmcMetadata.Configuration
+{
+ public static class NfoConfigurationExtension
+ {
+ public static XbmcMetadataOptions GetNfoConfiguration(this IConfigurationManager manager)
+ {
+ return manager.GetConfiguration<XbmcMetadataOptions>("xbmcmetadata");
+ }
+ }
+}
diff --git a/MediaBrowser.XbmcMetadata/Configuration/NfoOptions.cs b/MediaBrowser.XbmcMetadata/Configuration/NfoConfigurationFactory.cs
index 60dcde4db..8325bfdbd 100644
--- a/MediaBrowser.XbmcMetadata/Configuration/NfoOptions.cs
+++ b/MediaBrowser.XbmcMetadata/Configuration/NfoConfigurationFactory.cs
@@ -1,10 +1,12 @@
+#pragma warning disable CS1591
+
using System.Collections.Generic;
using MediaBrowser.Common.Configuration;
using MediaBrowser.Model.Configuration;
namespace MediaBrowser.XbmcMetadata.Configuration
{
- public class ConfigurationFactory : IConfigurationFactory
+ public class NfoConfigurationFactory : IConfigurationFactory
{
/// <inheritdoc />
public IEnumerable<ConfigurationStore> GetConfigurations()
@@ -19,12 +21,4 @@ namespace MediaBrowser.XbmcMetadata.Configuration
};
}
}
-
- public static class ConfigurationExtension
- {
- public static XbmcMetadataOptions GetNfoConfiguration(this IConfigurationManager manager)
- {
- return manager.GetConfiguration<XbmcMetadataOptions>("xbmcmetadata");
- }
- }
}
diff --git a/MediaBrowser.XbmcMetadata/EntryPoint.cs b/MediaBrowser.XbmcMetadata/EntryPoint.cs
index fe4d50efa..571953b47 100644
--- a/MediaBrowser.XbmcMetadata/EntryPoint.cs
+++ b/MediaBrowser.XbmcMetadata/EntryPoint.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
using System;
using System.Threading.Tasks;
using MediaBrowser.Common.Configuration;
@@ -12,7 +14,7 @@ using Microsoft.Extensions.Logging;
namespace MediaBrowser.XbmcMetadata
{
- public class EntryPoint : IServerEntryPoint
+ public sealed class EntryPoint : IServerEntryPoint
{
private readonly IUserDataManager _userDataManager;
private readonly ILogger _logger;
@@ -21,7 +23,7 @@ namespace MediaBrowser.XbmcMetadata
public EntryPoint(
IUserDataManager userDataManager,
- ILogger logger,
+ ILogger<EntryPoint> logger,
IProviderManager providerManager,
IConfigurationManager config)
{
diff --git a/MediaBrowser.XbmcMetadata/MediaBrowser.XbmcMetadata.csproj b/MediaBrowser.XbmcMetadata/MediaBrowser.XbmcMetadata.csproj
index 0d62cf8c5..45fd9add9 100644
--- a/MediaBrowser.XbmcMetadata/MediaBrowser.XbmcMetadata.csproj
+++ b/MediaBrowser.XbmcMetadata/MediaBrowser.XbmcMetadata.csproj
@@ -1,5 +1,10 @@
<Project Sdk="Microsoft.NET.Sdk">
+ <!-- ProjectGuid is only included as a requirement for SonarQube analysis -->
+ <PropertyGroup>
+ <ProjectGuid>{23499896-B135-4527-8574-C26E926EA99E}</ProjectGuid>
+ </PropertyGroup>
+
<ItemGroup>
<ProjectReference Include="..\MediaBrowser.Model\MediaBrowser.Model.csproj" />
<ProjectReference Include="..\MediaBrowser.Controller\MediaBrowser.Controller.csproj" />
@@ -13,6 +18,19 @@
<TargetFramework>netstandard2.1</TargetFramework>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
+ <TreatWarningsAsErrors>true</TreatWarningsAsErrors>
+ </PropertyGroup>
+
+ <!-- Code Analyzers-->
+ <ItemGroup Condition=" '$(Configuration)' == 'Debug' ">
+ <PackageReference Include="Microsoft.CodeAnalysis.FxCopAnalyzers" Version="2.9.8" PrivateAssets="All" />
+ <PackageReference Include="SerilogAnalyzer" Version="0.15.0" PrivateAssets="All" />
+ <PackageReference Include="StyleCop.Analyzers" Version="1.1.118" PrivateAssets="All" />
+ <PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" PrivateAssets="All" />
+ </ItemGroup>
+
+ <PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
+ <CodeAnalysisRuleSet>../jellyfin.ruleset</CodeAnalysisRuleSet>
</PropertyGroup>
</Project>
diff --git a/MediaBrowser.XbmcMetadata/Parsers/BaseNfoParser.cs b/MediaBrowser.XbmcMetadata/Parsers/BaseNfoParser.cs
index b8d0e6560..5c8de80f1 100644
--- a/MediaBrowser.XbmcMetadata/Parsers/BaseNfoParser.cs
+++ b/MediaBrowser.XbmcMetadata/Parsers/BaseNfoParser.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
using System;
using System.Collections.Generic;
using System.Globalization;
@@ -12,7 +14,6 @@ using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.TV;
using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Entities;
-using MediaBrowser.Model.Extensions;
using MediaBrowser.XbmcMetadata.Configuration;
using MediaBrowser.XbmcMetadata.Savers;
using Microsoft.Extensions.Logging;
@@ -28,6 +29,9 @@ namespace MediaBrowser.XbmcMetadata.Parsers
/// <summary>
/// Initializes a new instance of the <see cref="BaseNfoParser{T}" /> class.
/// </summary>
+ /// <param name="logger">The logger.</param>
+ /// <param name="config">the configuration manager.</param>
+ /// <param name="providerManager">The provider manager.</param>
public BaseNfoParser(ILogger logger, IConfigurationManager config, IProviderManager providerManager)
{
Logger = logger;
@@ -49,13 +53,13 @@ namespace MediaBrowser.XbmcMetadata.Parsers
protected virtual string MovieDbParserSearchString => "themoviedb.org/movie/";
/// <summary>
- /// Fetches metadata for an item from one xml file
+ /// Fetches metadata for an item from one xml file.
/// </summary>
/// <param name="item">The item.</param>
/// <param name="metadataFile">The metadata file.</param>
/// <param name="cancellationToken">The cancellation token.</param>
- /// <exception cref="ArgumentNullException">
- /// </exception>
+ /// <exception cref="ArgumentNullException"><c>item</c> is <c>null</c>.</exception>
+ /// <exception cref="ArgumentException"><c>metadataFile</c> is <c>null</c> or empty.</exception>
public void Fetch(MetadataResult<T> item, string metadataFile, CancellationToken cancellationToken)
{
if (item == null)
@@ -81,7 +85,7 @@ namespace MediaBrowser.XbmcMetadata.Parsers
}
}
- //Additional Mappings
+ // Additional Mappings
_validProviderIds.Add("collectionnumber", "TmdbCollection");
_validProviderIds.Add("tmdbcolid", "TmdbCollection");
_validProviderIds.Add("imdb_id", "Imdb");
@@ -124,6 +128,7 @@ namespace MediaBrowser.XbmcMetadata.Parsers
}
}
}
+
return;
}
@@ -197,15 +202,14 @@ namespace MediaBrowser.XbmcMetadata.Parsers
}
catch (XmlException)
{
-
}
}
}
protected void ParseProviderLinks(T item, string xml)
{
- //Look for a match for the Regex pattern "tt" followed by 7 digits
- var m = Regex.Match(xml, @"tt([0-9]{7})", RegexOptions.IgnoreCase);
+ // Look for a match for the Regex pattern "tt" followed by 7 or 8 digits
+ var m = Regex.Match(xml, "tt([0-9]{7,8})", RegexOptions.IgnoreCase);
if (m.Success)
{
item.SetProviderId(MetadataProviders.Imdb, m.Value);
@@ -268,6 +272,7 @@ namespace MediaBrowser.XbmcMetadata.Parsers
Logger.LogWarning("Invalid Added value found: " + val);
}
}
+
break;
}
@@ -279,6 +284,7 @@ namespace MediaBrowser.XbmcMetadata.Parsers
{
item.OriginalTitle = val;
}
+
break;
}
@@ -310,6 +316,7 @@ namespace MediaBrowser.XbmcMetadata.Parsers
{
item.ForcedSortName = val;
}
+
break;
}
@@ -359,7 +366,6 @@ namespace MediaBrowser.XbmcMetadata.Parsers
}
return null;
-
}).Where(i => i.HasValue).Select(i => i.Value).ToArray();
}
@@ -374,6 +380,7 @@ namespace MediaBrowser.XbmcMetadata.Parsers
{
item.Tagline = val;
}
+
break;
}
@@ -388,6 +395,7 @@ namespace MediaBrowser.XbmcMetadata.Parsers
.Where(i => !string.IsNullOrWhiteSpace(i))
.ToArray();
}
+
break;
}
@@ -399,6 +407,7 @@ namespace MediaBrowser.XbmcMetadata.Parsers
{
item.OfficialRating = rating;
}
+
break;
}
@@ -410,6 +419,7 @@ namespace MediaBrowser.XbmcMetadata.Parsers
{
item.CustomRating = val;
}
+
break;
}
@@ -424,6 +434,7 @@ namespace MediaBrowser.XbmcMetadata.Parsers
item.RunTimeTicks = TimeSpan.FromMinutes(runtime).Ticks;
}
}
+
break;
}
@@ -436,6 +447,7 @@ namespace MediaBrowser.XbmcMetadata.Parsers
{
hasAspectRatio.AspectRatio = val;
}
+
break;
}
@@ -447,6 +459,7 @@ namespace MediaBrowser.XbmcMetadata.Parsers
{
item.IsLocked = string.Equals("true", val, StringComparison.OrdinalIgnoreCase);
}
+
break;
}
@@ -456,16 +469,9 @@ namespace MediaBrowser.XbmcMetadata.Parsers
if (!string.IsNullOrWhiteSpace(val))
{
- //var parts = val.Split('/')
- // .Select(i => i.Trim())
- // .Where(i => !string.IsNullOrWhiteSpace(i));
-
- //foreach (var p in parts)
- //{
- // item.AddStudio(p);
- //}
item.AddStudio(val);
}
+
break;
}
@@ -478,10 +484,13 @@ namespace MediaBrowser.XbmcMetadata.Parsers
{
continue;
}
+
itemResult.AddPerson(p);
}
+
break;
}
+
case "credits":
{
var val = reader.ReadElementContentAsString();
@@ -497,9 +506,11 @@ namespace MediaBrowser.XbmcMetadata.Parsers
{
continue;
}
+
itemResult.AddPerson(p);
}
}
+
break;
}
@@ -512,8 +523,10 @@ namespace MediaBrowser.XbmcMetadata.Parsers
{
continue;
}
+
itemResult.AddPerson(p);
}
+
break;
}
@@ -535,6 +548,7 @@ namespace MediaBrowser.XbmcMetadata.Parsers
{
reader.Read();
}
+
break;
}
@@ -548,6 +562,7 @@ namespace MediaBrowser.XbmcMetadata.Parsers
item.AddTrailerUrl(val);
}
+
break;
}
@@ -563,6 +578,7 @@ namespace MediaBrowser.XbmcMetadata.Parsers
hasDisplayOrder.DisplayOrder = val;
}
}
+
break;
}
@@ -583,7 +599,6 @@ namespace MediaBrowser.XbmcMetadata.Parsers
case "rating":
{
-
var rating = reader.ReadElementContentAsString();
if (!string.IsNullOrWhiteSpace(rating))
@@ -594,6 +609,7 @@ namespace MediaBrowser.XbmcMetadata.Parsers
item.CommunityRating = val;
}
}
+
break;
}
@@ -650,6 +666,7 @@ namespace MediaBrowser.XbmcMetadata.Parsers
item.AddGenre(p);
}
}
+
break;
}
@@ -661,6 +678,7 @@ namespace MediaBrowser.XbmcMetadata.Parsers
{
item.AddTag(val);
}
+
break;
}
@@ -677,6 +695,7 @@ namespace MediaBrowser.XbmcMetadata.Parsers
{
reader.Read();
}
+
break;
}
@@ -694,6 +713,7 @@ namespace MediaBrowser.XbmcMetadata.Parsers
{
reader.Skip();
}
+
break;
}
}
@@ -717,10 +737,12 @@ namespace MediaBrowser.XbmcMetadata.Parsers
reader.Read();
continue;
}
+
using (var subtree = reader.ReadSubtree())
{
FetchFromStreamDetailsNode(subtree, item);
}
+
break;
}
@@ -755,10 +777,12 @@ namespace MediaBrowser.XbmcMetadata.Parsers
reader.Read();
continue;
}
+
using (var subtree = reader.ReadSubtree())
{
FetchFromVideoNode(subtree, item);
}
+
break;
}
@@ -815,6 +839,7 @@ namespace MediaBrowser.XbmcMetadata.Parsers
video.Video3DFormat = Video3DFormat.MVC;
}
}
+
break;
}
@@ -864,8 +889,10 @@ namespace MediaBrowser.XbmcMetadata.Parsers
{
role = val;
}
+
break;
}
+
case "sortorder":
{
var val = reader.ReadElementContentAsString();
@@ -877,6 +904,7 @@ namespace MediaBrowser.XbmcMetadata.Parsers
sortOrder = intVal;
}
}
+
break;
}
@@ -910,7 +938,7 @@ namespace MediaBrowser.XbmcMetadata.Parsers
};
/// <summary>
- /// Used to split names of comma or pipe delimeted genres and people
+ /// Used to split names of comma or pipe delimeted genres and people.
/// </summary>
/// <param name="value">The value.</param>
/// <returns>IEnumerable{System.String}.</returns>
@@ -920,7 +948,9 @@ namespace MediaBrowser.XbmcMetadata.Parsers
// Only split by comma if there is no pipe in the string
// We have to be careful to not split names like Matthew, Jr.
- var separator = value.IndexOf('|') == -1 && value.IndexOf(';') == -1 ? new[] { ',' } : new[] { '|', ';' };
+ var separator = value.IndexOf('|', StringComparison.Ordinal) == -1 && value.IndexOf(';', StringComparison.Ordinal) == -1
+ ? new[] { ',' }
+ : new[] { '|', ';' };
value = value.Trim().Trim(separator);
diff --git a/MediaBrowser.XbmcMetadata/Parsers/EpisodeNfoParser.cs b/MediaBrowser.XbmcMetadata/Parsers/EpisodeNfoParser.cs
index 82ac6c548..9cc0344c1 100644
--- a/MediaBrowser.XbmcMetadata/Parsers/EpisodeNfoParser.cs
+++ b/MediaBrowser.XbmcMetadata/Parsers/EpisodeNfoParser.cs
@@ -11,8 +11,17 @@ using Microsoft.Extensions.Logging;
namespace MediaBrowser.XbmcMetadata.Parsers
{
+ /// <summary>
+ /// Nfo parser for episodes.
+ /// </summary>
public class EpisodeNfoParser : BaseNfoParser<Episode>
{
+ /// <summary>
+ /// Initializes a new instance of the <see cref="EpisodeNfoParser"/> class.
+ /// </summary>
+ /// <param name="logger">The logger.</param>
+ /// <param name="config">the configuration manager.</param>
+ /// <param name="providerManager">The provider manager.</param>
public EpisodeNfoParser(ILogger logger, IConfigurationManager config, IProviderManager providerManager)
: base(logger, config, providerManager)
{
@@ -63,7 +72,6 @@ namespace MediaBrowser.XbmcMetadata.Parsers
}
catch (XmlException)
{
-
}
}
}
@@ -86,6 +94,7 @@ namespace MediaBrowser.XbmcMetadata.Parsers
item.ParentIndexNumber = num;
}
}
+
break;
}
@@ -100,6 +109,7 @@ namespace MediaBrowser.XbmcMetadata.Parsers
item.IndexNumber = num;
}
}
+
break;
}
@@ -114,6 +124,7 @@ namespace MediaBrowser.XbmcMetadata.Parsers
item.IndexNumberEnd = num;
}
}
+
break;
}
@@ -197,7 +208,6 @@ namespace MediaBrowser.XbmcMetadata.Parsers
break;
}
-
default:
base.FetchDataFromXmlNode(reader, itemResult);
break;
diff --git a/MediaBrowser.XbmcMetadata/Parsers/MovieNfoParser.cs b/MediaBrowser.XbmcMetadata/Parsers/MovieNfoParser.cs
index 79d9111fe..c17212f31 100644
--- a/MediaBrowser.XbmcMetadata/Parsers/MovieNfoParser.cs
+++ b/MediaBrowser.XbmcMetadata/Parsers/MovieNfoParser.cs
@@ -11,8 +11,17 @@ using Microsoft.Extensions.Logging;
namespace MediaBrowser.XbmcMetadata.Parsers
{
+ /// <summary>
+ /// Nfo parser for movies.
+ /// </summary>
public class MovieNfoParser : BaseNfoParser<Video>
{
+ /// <summary>
+ /// Initializes a new instance of the <see cref="MovieNfoParser"/> class.
+ /// </summary>
+ /// <param name="logger">The logger.</param>
+ /// <param name="config">the configuration manager.</param>
+ /// <param name="providerManager">The provider manager.</param>
public MovieNfoParser(ILogger logger, IConfigurationManager config, IProviderManager providerManager)
: base(logger, config, providerManager)
{
@@ -50,6 +59,7 @@ namespace MediaBrowser.XbmcMetadata.Parsers
break;
}
+
case "set":
{
var movie = item as Movie;
@@ -65,7 +75,7 @@ namespace MediaBrowser.XbmcMetadata.Parsers
if (!string.IsNullOrWhiteSpace(val) && movie != null)
{
// TODO Handle this better later
- if (val.IndexOf('<') == -1)
+ if (val.IndexOf('<', StringComparison.Ordinal) == -1)
{
movie.CollectionName = val;
}
@@ -119,9 +129,6 @@ namespace MediaBrowser.XbmcMetadata.Parsers
private void ParseSetXml(string xml, Movie movie)
{
- //xml = xml.Substring(xml.IndexOf('<'));
- //xml = xml.Substring(0, xml.LastIndexOf('>'));
-
// These are not going to be valid xml so no sense in causing the provider to fail and spamming the log with exceptions
try
{
@@ -155,7 +162,6 @@ namespace MediaBrowser.XbmcMetadata.Parsers
}
catch (XmlException)
{
-
}
}
}
diff --git a/MediaBrowser.XbmcMetadata/Parsers/SeasonNfoParser.cs b/MediaBrowser.XbmcMetadata/Parsers/SeasonNfoParser.cs
index d6c06f982..d8cd88b9a 100644
--- a/MediaBrowser.XbmcMetadata/Parsers/SeasonNfoParser.cs
+++ b/MediaBrowser.XbmcMetadata/Parsers/SeasonNfoParser.cs
@@ -7,8 +7,17 @@ using Microsoft.Extensions.Logging;
namespace MediaBrowser.XbmcMetadata.Parsers
{
+ /// <summary>
+ /// Nfo parser for seasons.
+ /// </summary>
public class SeasonNfoParser : BaseNfoParser<Season>
{
+ /// <summary>
+ /// Initializes a new instance of the <see cref="SeasonNfoParser"/> class.
+ /// </summary>
+ /// <param name="logger">The logger.</param>
+ /// <param name="config">the configuration manager.</param>
+ /// <param name="providerManager">The provider manager.</param>
public SeasonNfoParser(ILogger logger, IConfigurationManager config, IProviderManager providerManager)
: base(logger, config, providerManager)
{
@@ -32,6 +41,7 @@ namespace MediaBrowser.XbmcMetadata.Parsers
item.IndexNumber = num;
}
}
+
break;
}
diff --git a/MediaBrowser.XbmcMetadata/Parsers/SeriesNfoParser.cs b/MediaBrowser.XbmcMetadata/Parsers/SeriesNfoParser.cs
index 278858b4a..0954ae206 100644
--- a/MediaBrowser.XbmcMetadata/Parsers/SeriesNfoParser.cs
+++ b/MediaBrowser.XbmcMetadata/Parsers/SeriesNfoParser.cs
@@ -9,8 +9,17 @@ using Microsoft.Extensions.Logging;
namespace MediaBrowser.XbmcMetadata.Parsers
{
+ /// <summary>
+ /// Nfo parser for series.
+ /// </summary>
public class SeriesNfoParser : BaseNfoParser<Series>
{
+ /// <summary>
+ /// Initializes a new instance of the <see cref="SeriesNfoParser"/> class.
+ /// </summary>
+ /// <param name="logger">The logger.</param>
+ /// <param name="config">the configuration manager.</param>
+ /// <param name="providerManager">The provider manager.</param>
public SeriesNfoParser(ILogger logger, IConfigurationManager config, IProviderManager providerManager)
: base(logger, config, providerManager)
{
@@ -39,20 +48,25 @@ namespace MediaBrowser.XbmcMetadata.Parsers
{
tvdbId = reader.ReadElementContentAsString();
}
+
if (!string.IsNullOrWhiteSpace(imdbId))
{
item.SetProviderId(MetadataProviders.Imdb, imdbId);
}
+
if (!string.IsNullOrWhiteSpace(tmdbId))
{
item.SetProviderId(MetadataProviders.Tmdb, tmdbId);
}
+
if (!string.IsNullOrWhiteSpace(tvdbId))
{
item.SetProviderId(MetadataProviders.Tvdb, tvdbId);
}
+
break;
}
+
case "airs_dayofweek":
{
item.AirDays = TVUtils.GetAirDays(reader.ReadElementContentAsString());
@@ -67,6 +81,7 @@ namespace MediaBrowser.XbmcMetadata.Parsers
{
item.AirTime = val;
}
+
break;
}
diff --git a/MediaBrowser.XbmcMetadata/Providers/AlbumNfoProvider.cs b/MediaBrowser.XbmcMetadata/Providers/AlbumNfoProvider.cs
index 3517bc32c..4b1ee4c9c 100644
--- a/MediaBrowser.XbmcMetadata/Providers/AlbumNfoProvider.cs
+++ b/MediaBrowser.XbmcMetadata/Providers/AlbumNfoProvider.cs
@@ -9,13 +9,27 @@ using Microsoft.Extensions.Logging;
namespace MediaBrowser.XbmcMetadata.Providers
{
+ /// <summary>
+ /// Nfo provider for albums.
+ /// </summary>
public class AlbumNfoProvider : BaseNfoProvider<MusicAlbum>
{
private readonly ILogger _logger;
private readonly IConfigurationManager _config;
private readonly IProviderManager _providerManager;
- public AlbumNfoProvider(IFileSystem fileSystem, ILogger logger, IConfigurationManager config, IProviderManager providerManager)
+ /// <summary>
+ /// Initializes a new instance of the <see cref="AlbumNfoProvider"/> class.
+ /// </summary>
+ /// <param name="logger">The logger.</param>
+ /// <param name="fileSystem">The file system.</param>
+ /// <param name="config">the configuration manager.</param>
+ /// <param name="providerManager">The provider manager.</param>
+ public AlbumNfoProvider(
+ ILogger<AlbumNfoProvider> logger,
+ IFileSystem fileSystem,
+ IConfigurationManager config,
+ IProviderManager providerManager)
: base(fileSystem)
{
_logger = logger;
diff --git a/MediaBrowser.XbmcMetadata/Providers/ArtistNfoProvider.cs b/MediaBrowser.XbmcMetadata/Providers/ArtistNfoProvider.cs
index 03d8dbc7e..3bbfa6e83 100644
--- a/MediaBrowser.XbmcMetadata/Providers/ArtistNfoProvider.cs
+++ b/MediaBrowser.XbmcMetadata/Providers/ArtistNfoProvider.cs
@@ -9,13 +9,27 @@ using Microsoft.Extensions.Logging;
namespace MediaBrowser.XbmcMetadata.Providers
{
+ /// <summary>
+ /// Nfo provider for artists.
+ /// </summary>
public class ArtistNfoProvider : BaseNfoProvider<MusicArtist>
{
private readonly ILogger _logger;
private readonly IConfigurationManager _config;
private readonly IProviderManager _providerManager;
- public ArtistNfoProvider(IFileSystem fileSystem, ILogger logger, IConfigurationManager config, IProviderManager providerManager)
+ /// <summary>
+ /// Initializes a new instance of the <see cref="ArtistNfoProvider"/> class.
+ /// </summary>
+ /// <param name="logger">The logger.</param>
+ /// <param name="fileSystem">The file system.</param>
+ /// <param name="config">the configuration manager.</param>
+ /// <param name="providerManager">The provider manager.</param>
+ public ArtistNfoProvider(
+ IFileSystem fileSystem,
+ ILogger<ArtistNfoProvider> logger,
+ IConfigurationManager config,
+ IProviderManager providerManager)
: base(fileSystem)
{
_logger = logger;
diff --git a/MediaBrowser.XbmcMetadata/Providers/BaseNfoProvider.cs b/MediaBrowser.XbmcMetadata/Providers/BaseNfoProvider.cs
index ff3368bb9..6ad6c18a5 100644
--- a/MediaBrowser.XbmcMetadata/Providers/BaseNfoProvider.cs
+++ b/MediaBrowser.XbmcMetadata/Providers/BaseNfoProvider.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
using System.IO;
using System.Threading;
using System.Threading.Tasks;
@@ -19,6 +21,9 @@ namespace MediaBrowser.XbmcMetadata.Providers
}
/// <inheritdoc />
+ public string Name => BaseNfoSaver.SaverName;
+
+ /// <inheritdoc />
public Task<MetadataResult<T>> GetMetadata(
ItemInfo info,
IDirectoryService directoryService,
@@ -55,12 +60,6 @@ namespace MediaBrowser.XbmcMetadata.Providers
}
/// <inheritdoc />
- protected abstract void Fetch(MetadataResult<T> result, string path, CancellationToken cancellationToken);
-
- /// <inheritdoc />
- protected abstract FileSystemMetadata GetXmlFile(ItemInfo info, IDirectoryService directoryService);
-
- /// <inheritdoc />
public bool HasChanged(BaseItem item, IDirectoryService directoryService)
{
var file = GetXmlFile(new ItemInfo(item), directoryService);
@@ -73,6 +72,8 @@ namespace MediaBrowser.XbmcMetadata.Providers
return file.Exists && _fileSystem.GetLastWriteTimeUtc(file) > item.DateLastSaved;
}
- public string Name => BaseNfoSaver.SaverName;
+ protected abstract void Fetch(MetadataResult<T> result, string path, CancellationToken cancellationToken);
+
+ protected abstract FileSystemMetadata GetXmlFile(ItemInfo info, IDirectoryService directoryService);
}
}
diff --git a/MediaBrowser.XbmcMetadata/Providers/BaseVideoNfoProvider.cs b/MediaBrowser.XbmcMetadata/Providers/BaseVideoNfoProvider.cs
index 7410b97e0..84c99664a 100644
--- a/MediaBrowser.XbmcMetadata/Providers/BaseVideoNfoProvider.cs
+++ b/MediaBrowser.XbmcMetadata/Providers/BaseVideoNfoProvider.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
using System.Linq;
using System.Threading;
using MediaBrowser.Common.Configuration;
@@ -10,14 +12,18 @@ using Microsoft.Extensions.Logging;
namespace MediaBrowser.XbmcMetadata.Providers
{
- public class BaseVideoNfoProvider<T> : BaseNfoProvider<T>
+ public abstract class BaseVideoNfoProvider<T> : BaseNfoProvider<T>
where T : Video, new()
{
private readonly ILogger _logger;
private readonly IConfigurationManager _config;
private readonly IProviderManager _providerManager;
- public BaseVideoNfoProvider(IFileSystem fileSystem, ILogger logger, IConfigurationManager config, IProviderManager providerManager)
+ public BaseVideoNfoProvider(
+ ILogger logger,
+ IFileSystem fileSystem,
+ IConfigurationManager config,
+ IProviderManager providerManager)
: base(fileSystem)
{
_logger = logger;
diff --git a/MediaBrowser.XbmcMetadata/Providers/EpisodeNfoProvider.cs b/MediaBrowser.XbmcMetadata/Providers/EpisodeNfoProvider.cs
index b2278ba4a..b2dc2e38e 100644
--- a/MediaBrowser.XbmcMetadata/Providers/EpisodeNfoProvider.cs
+++ b/MediaBrowser.XbmcMetadata/Providers/EpisodeNfoProvider.cs
@@ -9,13 +9,27 @@ using Microsoft.Extensions.Logging;
namespace MediaBrowser.XbmcMetadata.Providers
{
+ /// <summary>
+ /// Nfo provider for episodes.
+ /// </summary>
public class EpisodeNfoProvider : BaseNfoProvider<Episode>
{
private readonly ILogger _logger;
private readonly IConfigurationManager _config;
private readonly IProviderManager _providerManager;
- public EpisodeNfoProvider(IFileSystem fileSystem, ILogger logger, IConfigurationManager config, IProviderManager providerManager)
+ /// <summary>
+ /// Initializes a new instance of the <see cref="EpisodeNfoProvider"/> class.
+ /// </summary>
+ /// <param name="logger">The logger.</param>
+ /// <param name="fileSystem">The file system.</param>
+ /// <param name="config">the configuration manager.</param>
+ /// <param name="providerManager">The provider manager.</param>
+ public EpisodeNfoProvider(
+ ILogger<EpisodeNfoProvider> logger,
+ IFileSystem fileSystem,
+ IConfigurationManager config,
+ IProviderManager providerManager)
: base(fileSystem)
{
_logger = logger;
diff --git a/MediaBrowser.XbmcMetadata/Providers/MovieNfoProvider.cs b/MediaBrowser.XbmcMetadata/Providers/MovieNfoProvider.cs
index d21164c02..e3f6bada1 100644
--- a/MediaBrowser.XbmcMetadata/Providers/MovieNfoProvider.cs
+++ b/MediaBrowser.XbmcMetadata/Providers/MovieNfoProvider.cs
@@ -1,5 +1,4 @@
using MediaBrowser.Common.Configuration;
-using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.Movies;
using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.IO;
@@ -7,26 +6,24 @@ using Microsoft.Extensions.Logging;
namespace MediaBrowser.XbmcMetadata.Providers
{
+ /// <summary>
+ /// Nfo provider for movies.
+ /// </summary>
public class MovieNfoProvider : BaseVideoNfoProvider<Movie>
{
- public MovieNfoProvider(IFileSystem fileSystem, ILogger logger, IConfigurationManager config, IProviderManager providerManager)
- : base(fileSystem, logger, config, providerManager)
- {
- }
- }
-
- public class MusicVideoNfoProvider : BaseVideoNfoProvider<MusicVideo>
- {
- public MusicVideoNfoProvider(IFileSystem fileSystem, ILogger logger, IConfigurationManager config, IProviderManager providerManager)
- : base(fileSystem, logger, config, providerManager)
- {
- }
- }
-
- public class VideoNfoProvider : BaseVideoNfoProvider<Video>
- {
- public VideoNfoProvider(IFileSystem fileSystem, ILogger logger, IConfigurationManager config, IProviderManager providerManager)
- : base(fileSystem, logger, config, providerManager)
+ /// <summary>
+ /// Initializes a new instance of the <see cref="MovieNfoProvider"/> class.
+ /// </summary>
+ /// <param name="logger">The logger.</param>
+ /// <param name="fileSystem">The file system.</param>
+ /// <param name="config">the configuration manager.</param>
+ /// <param name="providerManager">The provider manager.</param>
+ public MovieNfoProvider(
+ ILogger<MovieNfoProvider> logger,
+ IFileSystem fileSystem,
+ IConfigurationManager config,
+ IProviderManager providerManager)
+ : base(logger, fileSystem, config, providerManager)
{
}
}
diff --git a/MediaBrowser.XbmcMetadata/Providers/MusicVideoNfoProvider.cs b/MediaBrowser.XbmcMetadata/Providers/MusicVideoNfoProvider.cs
new file mode 100644
index 000000000..b490a7120
--- /dev/null
+++ b/MediaBrowser.XbmcMetadata/Providers/MusicVideoNfoProvider.cs
@@ -0,0 +1,26 @@
+using MediaBrowser.Common.Configuration;
+using MediaBrowser.Controller.Entities;
+using MediaBrowser.Controller.Providers;
+using MediaBrowser.Model.IO;
+using Microsoft.Extensions.Logging;
+
+namespace MediaBrowser.XbmcMetadata.Providers
+{
+ /// <summary>
+ /// Nfo provider for music videos.
+ /// </summary>
+ public class MusicVideoNfoProvider : BaseVideoNfoProvider<MusicVideo>
+ {
+ /// <summary>
+ /// Initializes a new instance of the <see cref="MusicVideoNfoProvider"/> class.
+ /// </summary>
+ /// <param name="logger">The logger.</param>
+ /// <param name="fileSystem">The file system.</param>
+ /// <param name="config">the configuration manager.</param>
+ /// <param name="providerManager">The provider manager.</param>
+ public MusicVideoNfoProvider(ILogger<MusicVideoNfoProvider> logger, IFileSystem fileSystem, IConfigurationManager config, IProviderManager providerManager)
+ : base(logger, fileSystem, config, providerManager)
+ {
+ }
+ }
+}
diff --git a/MediaBrowser.XbmcMetadata/Providers/SeasonNfoProvider.cs b/MediaBrowser.XbmcMetadata/Providers/SeasonNfoProvider.cs
index 2cf542054..63ddd6025 100644
--- a/MediaBrowser.XbmcMetadata/Providers/SeasonNfoProvider.cs
+++ b/MediaBrowser.XbmcMetadata/Providers/SeasonNfoProvider.cs
@@ -9,13 +9,27 @@ using Microsoft.Extensions.Logging;
namespace MediaBrowser.XbmcMetadata.Providers
{
+ /// <summary>
+ /// Nfo provider for seasons.
+ /// </summary>
public class SeasonNfoProvider : BaseNfoProvider<Season>
{
private readonly ILogger _logger;
private readonly IConfigurationManager _config;
private readonly IProviderManager _providerManager;
- public SeasonNfoProvider(IFileSystem fileSystem, ILogger logger, IConfigurationManager config, IProviderManager providerManager)
+ /// <summary>
+ /// Initializes a new instance of the <see cref="SeasonNfoProvider"/> class.
+ /// </summary>
+ /// <param name="logger">The logger.</param>
+ /// <param name="fileSystem">The file system.</param>
+ /// <param name="config">the configuration manager.</param>
+ /// <param name="providerManager">The provider manager.</param>
+ public SeasonNfoProvider(
+ ILogger<SeasonNfoProvider> logger,
+ IFileSystem fileSystem,
+ IConfigurationManager config,
+ IProviderManager providerManager)
: base(fileSystem)
{
_logger = logger;
@@ -34,4 +48,3 @@ namespace MediaBrowser.XbmcMetadata.Providers
=> directoryService.GetFile(Path.Combine(info.Path, "season.nfo"));
}
}
-
diff --git a/MediaBrowser.XbmcMetadata/Providers/SeriesNfoProvider.cs b/MediaBrowser.XbmcMetadata/Providers/SeriesNfoProvider.cs
index 25c8e0faf..d65914542 100644
--- a/MediaBrowser.XbmcMetadata/Providers/SeriesNfoProvider.cs
+++ b/MediaBrowser.XbmcMetadata/Providers/SeriesNfoProvider.cs
@@ -9,13 +9,27 @@ using Microsoft.Extensions.Logging;
namespace MediaBrowser.XbmcMetadata.Providers
{
+ /// <summary>
+ /// Nfo provider for series.
+ /// </summary>
public class SeriesNfoProvider : BaseNfoProvider<Series>
{
private readonly ILogger _logger;
private readonly IConfigurationManager _config;
private readonly IProviderManager _providerManager;
- public SeriesNfoProvider(IFileSystem fileSystem, ILogger logger, IConfigurationManager config, IProviderManager providerManager)
+ /// <summary>
+ /// Initializes a new instance of the <see cref="SeriesNfoProvider"/> class.
+ /// </summary>
+ /// <param name="logger">The logger.</param>
+ /// <param name="fileSystem">The file system.</param>
+ /// <param name="config">the configuration manager.</param>
+ /// <param name="providerManager">The provider manager.</param>
+ public SeriesNfoProvider(
+ ILogger<SeriesNfoProvider> logger,
+ IFileSystem fileSystem,
+ IConfigurationManager config,
+ IProviderManager providerManager)
: base(fileSystem)
{
_logger = logger;
diff --git a/MediaBrowser.XbmcMetadata/Providers/VideoNfoProvider.cs b/MediaBrowser.XbmcMetadata/Providers/VideoNfoProvider.cs
new file mode 100644
index 000000000..f66ad30ca
--- /dev/null
+++ b/MediaBrowser.XbmcMetadata/Providers/VideoNfoProvider.cs
@@ -0,0 +1,26 @@
+using MediaBrowser.Common.Configuration;
+using MediaBrowser.Controller.Entities;
+using MediaBrowser.Controller.Providers;
+using MediaBrowser.Model.IO;
+using Microsoft.Extensions.Logging;
+
+namespace MediaBrowser.XbmcMetadata.Providers
+{
+ /// <summary>
+ /// Nfo provider for videos.
+ /// </summary>
+ public class VideoNfoProvider : BaseVideoNfoProvider<Video>
+ {
+ /// <summary>
+ /// Initializes a new instance of the <see cref="VideoNfoProvider"/> class.
+ /// </summary>
+ /// <param name="logger">The logger.</param>
+ /// <param name="fileSystem">The file system.</param>
+ /// <param name="config">the configuration manager.</param>
+ /// <param name="providerManager">The provider manager.</param>
+ public VideoNfoProvider(ILogger<VideoNfoProvider> logger, IFileSystem fileSystem, IConfigurationManager config, IProviderManager providerManager)
+ : base(logger, fileSystem, config, providerManager)
+ {
+ }
+ }
+}
diff --git a/MediaBrowser.XbmcMetadata/Savers/AlbumNfoSaver.cs b/MediaBrowser.XbmcMetadata/Savers/AlbumNfoSaver.cs
index 233b3cb89..c22f77dcd 100644
--- a/MediaBrowser.XbmcMetadata/Savers/AlbumNfoSaver.cs
+++ b/MediaBrowser.XbmcMetadata/Savers/AlbumNfoSaver.cs
@@ -13,15 +13,27 @@ using Microsoft.Extensions.Logging;
namespace MediaBrowser.XbmcMetadata.Savers
{
+ /// <summary>
+ /// Nfo saver for albums.
+ /// </summary>
public class AlbumNfoSaver : BaseNfoSaver
{
+ /// <summary>
+ /// Initializes a new instance of the <see cref="AlbumNfoSaver"/> class.
+ /// </summary>
+ /// <param name="fileSystem">The file system.</param>
+ /// <param name="configurationManager">the server configuration manager.</param>
+ /// <param name="libraryManager">The library manager.</param>
+ /// <param name="userManager">The user manager.</param>
+ /// <param name="userDataManager">The user data manager.</param>
+ /// <param name="logger">The logger.</param>
public AlbumNfoSaver(
IFileSystem fileSystem,
IServerConfigurationManager configurationManager,
ILibraryManager libraryManager,
IUserManager userManager,
IUserDataManager userDataManager,
- ILogger logger)
+ ILogger<AlbumNfoSaver> logger)
: base(fileSystem, configurationManager, libraryManager, userManager, userDataManager, logger)
{
}
@@ -74,7 +86,7 @@ namespace MediaBrowser.XbmcMetadata.Savers
if (track.RunTimeTicks.HasValue)
{
- var time = TimeSpan.FromTicks(track.RunTimeTicks.Value).ToString(@"mm\:ss");
+ var time = TimeSpan.FromTicks(track.RunTimeTicks.Value).ToString(@"mm\:ss", CultureInfo.InvariantCulture);
writer.WriteElementString("duration", time);
}
diff --git a/MediaBrowser.XbmcMetadata/Savers/ArtistNfoSaver.cs b/MediaBrowser.XbmcMetadata/Savers/ArtistNfoSaver.cs
index 04565ff7e..6365cdecb 100644
--- a/MediaBrowser.XbmcMetadata/Savers/ArtistNfoSaver.cs
+++ b/MediaBrowser.XbmcMetadata/Savers/ArtistNfoSaver.cs
@@ -12,9 +12,27 @@ using Microsoft.Extensions.Logging;
namespace MediaBrowser.XbmcMetadata.Savers
{
+ /// <summary>
+ /// Nfo saver for artsist.
+ /// </summary>
public class ArtistNfoSaver : BaseNfoSaver
{
- public ArtistNfoSaver(IFileSystem fileSystem, IServerConfigurationManager configurationManager, ILibraryManager libraryManager, IUserManager userManager, IUserDataManager userDataManager, ILogger logger)
+ /// <summary>
+ /// Initializes a new instance of the <see cref="ArtistNfoSaver"/> class.
+ /// </summary>
+ /// <param name="fileSystem">The file system.</param>
+ /// <param name="configurationManager">the server configuration manager.</param>
+ /// <param name="libraryManager">The library manager.</param>
+ /// <param name="userManager">The user manager.</param>
+ /// <param name="userDataManager">The user data manager.</param>
+ /// <param name="logger">The logger.</param>
+ public ArtistNfoSaver(
+ IFileSystem fileSystem,
+ IServerConfigurationManager configurationManager,
+ ILibraryManager libraryManager,
+ IUserManager userManager,
+ IUserDataManager userDataManager,
+ ILogger<ArtistNfoSaver> logger)
: base(fileSystem, configurationManager, libraryManager, userManager, userDataManager, logger)
{
}
@@ -40,7 +58,7 @@ namespace MediaBrowser.XbmcMetadata.Savers
{
var formatString = ConfigurationManager.GetNfoConfiguration().ReleaseDateFormat;
- writer.WriteElementString("disbanded", artist.EndDate.Value.ToLocalTime().ToString(formatString));
+ writer.WriteElementString("disbanded", artist.EndDate.Value.ToLocalTime().ToString(formatString, CultureInfo.InvariantCulture));
}
var albums = artist
diff --git a/MediaBrowser.XbmcMetadata/Savers/BaseNfoSaver.cs b/MediaBrowser.XbmcMetadata/Savers/BaseNfoSaver.cs
index 981c3a53a..90e8b4b99 100644
--- a/MediaBrowser.XbmcMetadata/Savers/BaseNfoSaver.cs
+++ b/MediaBrowser.XbmcMetadata/Savers/BaseNfoSaver.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
using System;
using System.Collections.Generic;
using System.Globalization;
@@ -28,6 +30,9 @@ namespace MediaBrowser.XbmcMetadata.Savers
public const string YouTubeWatchUrl = "https://www.youtube.com/watch?v=";
+ // filters control characters but allows only properly-formed surrogate sequences
+ private const string _invalidXMLCharsRegex = @"(?<![\uD800-\uDBFF])[\uDC00-\uDFFF]|[\uD800-\uDBFF](?![\uDC00-\uDFFF])|[\x00-\x08\x0B\x0C\x0E-\x1F\x7F-\x9F\uFEFF\uFFFE\uFFFF]";
+
private static readonly HashSet<string> _commonTags = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
{
"plot",
@@ -94,9 +99,6 @@ namespace MediaBrowser.XbmcMetadata.Savers
"countrycode"
};
- // filters control characters but allows only properly-formed surrogate sequences
- private const string _invalidXMLCharsRegex = @"(?<![\uD800-\uDBFF])[\uDC00-\uDFFF]|[\uD800-\uDBFF](?![\uDC00-\uDFFF])|[\x00-\x08\x0B\x0C\x0E-\x1F\x7F-\x9F\uFEFF\uFFFE\uFFFF]";
-
protected BaseNfoSaver(
IFileSystem fileSystem,
IServerConfigurationManager configurationManager,
@@ -247,7 +249,7 @@ namespace MediaBrowser.XbmcMetadata.Savers
if (baseItem != null)
{
- AddCommonNodes(baseItem, writer, LibraryManager, UserManager, UserDataManager, FileSystem, ConfigurationManager);
+ AddCommonNodes(baseItem, writer, LibraryManager, UserManager, UserDataManager, ConfigurationManager);
}
WriteCustomElements(item, writer);
@@ -265,11 +267,9 @@ namespace MediaBrowser.XbmcMetadata.Savers
}
catch (FileNotFoundException)
{
-
}
catch (IOException)
{
-
}
catch (XmlException ex)
{
@@ -364,8 +364,8 @@ namespace MediaBrowser.XbmcMetadata.Savers
writer.WriteElementString("samplingrate", stream.SampleRate.Value.ToString(CultureInfo.InvariantCulture));
}
- writer.WriteElementString("default", stream.IsDefault.ToString());
- writer.WriteElementString("forced", stream.IsForced.ToString());
+ writer.WriteElementString("default", stream.IsDefault.ToString(CultureInfo.InvariantCulture));
+ writer.WriteElementString("forced", stream.IsForced.ToString(CultureInfo.InvariantCulture));
if (stream.Type == MediaStreamType.Video)
{
@@ -384,7 +384,7 @@ namespace MediaBrowser.XbmcMetadata.Savers
if (item is Video video)
{
- //AddChapters(video, builder, itemRepository);
+ // AddChapters(video, builder, itemRepository);
if (video.Video3DFormat.HasValue)
{
@@ -420,21 +420,19 @@ namespace MediaBrowser.XbmcMetadata.Savers
/// <summary>
/// Adds the common nodes.
/// </summary>
- /// <returns>Task.</returns>
private void AddCommonNodes(
BaseItem item,
XmlWriter writer,
ILibraryManager libraryManager,
IUserManager userManager,
IUserDataManager userDataRepo,
- IFileSystem fileSystem,
IServerConfigurationManager config)
{
var writtenProviderIds = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
var overview = (item.Overview ?? string.Empty)
.StripHtml()
- .Replace("&quot;", "'");
+ .Replace("&quot;", "'", StringComparison.Ordinal);
var options = config.GetNfoConfiguration();
@@ -455,7 +453,7 @@ namespace MediaBrowser.XbmcMetadata.Savers
{
var outline = (item.Tagline ?? string.Empty)
.StripHtml()
- .Replace("&quot;", "'");
+ .Replace("&quot;", "'", StringComparison.Ordinal);
writer.WriteElementString("outline", outline);
}
@@ -476,7 +474,7 @@ namespace MediaBrowser.XbmcMetadata.Savers
writer.WriteElementString("lockedfields", string.Join("|", item.LockedFields));
}
- writer.WriteElementString("dateadded", item.DateCreated.ToLocalTime().ToString(DateAddedFormat));
+ writer.WriteElementString("dateadded", item.DateCreated.ToLocalTime().ToString(DateAddedFormat, CultureInfo.InvariantCulture));
writer.WriteElementString("title", item.Name ?? string.Empty);
@@ -590,6 +588,7 @@ namespace MediaBrowser.XbmcMetadata.Savers
{
writer.WriteElementString("language", item.PreferredMetadataLanguage);
}
+
if (!string.IsNullOrEmpty(item.PreferredMetadataCountryCode))
{
writer.WriteElementString("countrycode", item.PreferredMetadataCountryCode);
@@ -603,16 +602,16 @@ namespace MediaBrowser.XbmcMetadata.Savers
{
writer.WriteElementString(
"formed",
- item.PremiereDate.Value.ToLocalTime().ToString(formatString));
+ item.PremiereDate.Value.ToLocalTime().ToString(formatString, CultureInfo.InvariantCulture));
}
else
{
writer.WriteElementString(
"premiered",
- item.PremiereDate.Value.ToLocalTime().ToString(formatString));
+ item.PremiereDate.Value.ToLocalTime().ToString(formatString, CultureInfo.InvariantCulture));
writer.WriteElementString(
"releasedate",
- item.PremiereDate.Value.ToLocalTime().ToString(formatString));
+ item.PremiereDate.Value.ToLocalTime().ToString(formatString, CultureInfo.InvariantCulture));
}
}
@@ -624,7 +623,7 @@ namespace MediaBrowser.XbmcMetadata.Savers
writer.WriteElementString(
"enddate",
- item.EndDate.Value.ToLocalTime().ToString(formatString));
+ item.EndDate.Value.ToLocalTime().ToString(formatString, CultureInfo.InvariantCulture));
}
}
@@ -780,12 +779,12 @@ namespace MediaBrowser.XbmcMetadata.Savers
if (options.SaveImagePathsInNfo)
{
- AddImages(item, writer, libraryManager, config);
+ AddImages(item, writer, libraryManager);
}
AddUserData(item, writer, userManager, userDataRepo, options);
- AddActors(people, writer, libraryManager, fileSystem, config, options.SaveImagePathsInNfo);
+ AddActors(people, writer, libraryManager, options.SaveImagePathsInNfo);
if (item is BoxSet folder)
{
@@ -828,7 +827,7 @@ namespace MediaBrowser.XbmcMetadata.Savers
return url.Replace(YouTubeWatchUrl, "plugin://plugin.video.youtube/?action=play_video&videoid=", StringComparison.OrdinalIgnoreCase);
}
- private void AddImages(BaseItem item, XmlWriter writer, ILibraryManager libraryManager, IServerConfigurationManager config)
+ private void AddImages(BaseItem item, XmlWriter writer, ILibraryManager libraryManager)
{
writer.WriteStartElement("art");
@@ -836,12 +835,12 @@ namespace MediaBrowser.XbmcMetadata.Savers
if (image != null)
{
- writer.WriteElementString("poster", GetImagePathToSave(image, libraryManager, config));
+ writer.WriteElementString("poster", GetImagePathToSave(image, libraryManager));
}
foreach (var backdrop in item.GetImages(ImageType.Backdrop))
{
- writer.WriteElementString("fanart", GetImagePathToSave(backdrop, libraryManager, config));
+ writer.WriteElementString("fanart", GetImagePathToSave(backdrop, libraryManager));
}
writer.WriteEndElement();
@@ -893,7 +892,7 @@ namespace MediaBrowser.XbmcMetadata.Savers
{
writer.WriteElementString(
"lastplayed",
- userdata.LastPlayedDate.Value.ToLocalTime().ToString("yyyy-MM-dd HH:mm:ss").ToLowerInvariant());
+ userdata.LastPlayedDate.Value.ToLocalTime().ToString("yyyy-MM-dd HH:mm:ss", CultureInfo.InvariantCulture).ToLowerInvariant());
}
writer.WriteStartElement("resume");
@@ -911,7 +910,7 @@ namespace MediaBrowser.XbmcMetadata.Savers
writer.WriteEndElement();
}
- private void AddActors(List<PersonInfo> people, XmlWriter writer, ILibraryManager libraryManager, IFileSystem fileSystem, IServerConfigurationManager config, bool saveImagePath)
+ private void AddActors(List<PersonInfo> people, XmlWriter writer, ILibraryManager libraryManager, bool saveImagePath)
{
foreach (var person in people)
{
@@ -953,7 +952,7 @@ namespace MediaBrowser.XbmcMetadata.Savers
{
writer.WriteElementString(
"thumb",
- GetImagePathToSave(image, libraryManager, config));
+ GetImagePathToSave(image, libraryManager));
}
}
@@ -961,7 +960,7 @@ namespace MediaBrowser.XbmcMetadata.Savers
}
}
- private string GetImagePathToSave(ItemImageInfo image, ILibraryManager libraryManager, IServerConfigurationManager config)
+ private string GetImagePathToSave(ItemImageInfo image, ILibraryManager libraryManager)
{
if (!image.IsLocalFile)
{
diff --git a/MediaBrowser.XbmcMetadata/Savers/EpisodeNfoSaver.cs b/MediaBrowser.XbmcMetadata/Savers/EpisodeNfoSaver.cs
index 091c1957e..ac2fbb8d2 100644
--- a/MediaBrowser.XbmcMetadata/Savers/EpisodeNfoSaver.cs
+++ b/MediaBrowser.XbmcMetadata/Savers/EpisodeNfoSaver.cs
@@ -12,15 +12,33 @@ using Microsoft.Extensions.Logging;
namespace MediaBrowser.XbmcMetadata.Savers
{
+ /// <summary>
+ /// Nfo saver for episodes.
+ /// </summary>
public class EpisodeNfoSaver : BaseNfoSaver
{
- public EpisodeNfoSaver(IFileSystem fileSystem, IServerConfigurationManager configurationManager, ILibraryManager libraryManager, IUserManager userManager, IUserDataManager userDataManager, ILogger logger)
+ private readonly CultureInfo _usCulture = new CultureInfo("en-US");
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="EpisodeNfoSaver"/> class.
+ /// </summary>
+ /// <param name="fileSystem">The file system.</param>
+ /// <param name="configurationManager">the server configuration manager.</param>
+ /// <param name="libraryManager">The library manager.</param>
+ /// <param name="userManager">The user manager.</param>
+ /// <param name="userDataManager">The user data manager.</param>
+ /// <param name="logger">The logger.</param>
+ public EpisodeNfoSaver(
+ IFileSystem fileSystem,
+ IServerConfigurationManager configurationManager,
+ ILibraryManager libraryManager,
+ IUserManager userManager,
+ IUserDataManager userDataManager,
+ ILogger<EpisodeNfoSaver> logger)
: base(fileSystem, configurationManager, libraryManager, userManager, userDataManager, logger)
{
}
- private readonly CultureInfo _usCulture = new CultureInfo("en-US");
-
/// <inheritdoc />
protected override string GetLocalSavePath(BaseItem item)
=> Path.ChangeExtension(item.Path, ".nfo");
@@ -57,7 +75,7 @@ namespace MediaBrowser.XbmcMetadata.Savers
{
var formatString = ConfigurationManager.GetNfoConfiguration().ReleaseDateFormat;
- writer.WriteElementString("aired", episode.PremiereDate.Value.ToLocalTime().ToString(formatString));
+ writer.WriteElementString("aired", episode.PremiereDate.Value.ToLocalTime().ToString(formatString, CultureInfo.InvariantCulture));
}
if (!episode.ParentIndexNumber.HasValue || episode.ParentIndexNumber.Value == 0)
diff --git a/MediaBrowser.XbmcMetadata/Savers/MovieNfoSaver.cs b/MediaBrowser.XbmcMetadata/Savers/MovieNfoSaver.cs
index 08a752e33..eef989a5b 100644
--- a/MediaBrowser.XbmcMetadata/Savers/MovieNfoSaver.cs
+++ b/MediaBrowser.XbmcMetadata/Savers/MovieNfoSaver.cs
@@ -14,9 +14,27 @@ using Microsoft.Extensions.Logging;
namespace MediaBrowser.XbmcMetadata.Savers
{
+ /// <summary>
+ /// Nfo saver for movies.
+ /// </summary>
public class MovieNfoSaver : BaseNfoSaver
{
- public MovieNfoSaver(IFileSystem fileSystem, IServerConfigurationManager configurationManager, ILibraryManager libraryManager, IUserManager userManager, IUserDataManager userDataManager, ILogger logger)
+ /// <summary>
+ /// Initializes a new instance of the <see cref="MovieNfoSaver"/> class.
+ /// </summary>
+ /// <param name="fileSystem">The file system.</param>
+ /// <param name="configurationManager">the server configuration manager.</param>
+ /// <param name="libraryManager">The library manager.</param>
+ /// <param name="userManager">The user manager.</param>
+ /// <param name="userDataManager">The user data manager.</param>
+ /// <param name="logger">The logger.</param>
+ public MovieNfoSaver(
+ IFileSystem fileSystem,
+ IServerConfigurationManager configurationManager,
+ ILibraryManager libraryManager,
+ IUserManager userManager,
+ IUserDataManager userDataManager,
+ ILogger<MovieNfoSaver> logger)
: base(fileSystem, configurationManager, libraryManager, userManager, userDataManager, logger)
{
}
@@ -25,7 +43,7 @@ namespace MediaBrowser.XbmcMetadata.Savers
protected override string GetLocalSavePath(BaseItem item)
=> GetMovieSavePaths(new ItemInfo(item)).FirstOrDefault();
- public static IEnumerable<string> GetMovieSavePaths(ItemInfo item)
+ internal static IEnumerable<string> GetMovieSavePaths(ItemInfo item)
{
if (item.VideoType == VideoType.Dvd && !item.IsPlaceHolder)
{
@@ -42,13 +60,6 @@ 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"));
- //}
-
yield return Path.ChangeExtension(item.Path, ".nfo");
if (!item.IsInMixedFolder)
@@ -95,6 +106,7 @@ namespace MediaBrowser.XbmcMetadata.Savers
{
writer.WriteElementString("artist", artist);
}
+
if (!string.IsNullOrEmpty(musicVideo.Album))
{
writer.WriteElementString("album", musicVideo.Album);
diff --git a/MediaBrowser.XbmcMetadata/Savers/SeasonNfoSaver.cs b/MediaBrowser.XbmcMetadata/Savers/SeasonNfoSaver.cs
index 25695121d..925a230bd 100644
--- a/MediaBrowser.XbmcMetadata/Savers/SeasonNfoSaver.cs
+++ b/MediaBrowser.XbmcMetadata/Savers/SeasonNfoSaver.cs
@@ -11,15 +11,27 @@ using Microsoft.Extensions.Logging;
namespace MediaBrowser.XbmcMetadata.Savers
{
+ /// <summary>
+ /// Nfo saver for seasons.
+ /// </summary>
public class SeasonNfoSaver : BaseNfoSaver
{
+ /// <summary>
+ /// Initializes a new instance of the <see cref="SeasonNfoSaver"/> class.
+ /// </summary>
+ /// <param name="fileSystem">The file system.</param>
+ /// <param name="configurationManager">the server configuration manager.</param>
+ /// <param name="libraryManager">The library manager.</param>
+ /// <param name="userManager">The user manager.</param>
+ /// <param name="userDataManager">The user data manager.</param>
+ /// <param name="logger">The logger.</param>
public SeasonNfoSaver(
IFileSystem fileSystem,
IServerConfigurationManager configurationManager,
ILibraryManager libraryManager,
IUserManager userManager,
IUserDataManager userDataManager,
- ILogger logger)
+ ILogger<SeasonNfoSaver> logger)
: base(fileSystem, configurationManager, libraryManager, userManager, userDataManager, logger)
{
}
diff --git a/MediaBrowser.XbmcMetadata/Savers/SeriesNfoSaver.cs b/MediaBrowser.XbmcMetadata/Savers/SeriesNfoSaver.cs
index 8d7faece7..2a5d36d40 100644
--- a/MediaBrowser.XbmcMetadata/Savers/SeriesNfoSaver.cs
+++ b/MediaBrowser.XbmcMetadata/Savers/SeriesNfoSaver.cs
@@ -12,15 +12,27 @@ using Microsoft.Extensions.Logging;
namespace MediaBrowser.XbmcMetadata.Savers
{
+ /// <summary>
+ /// Nfo saver for series.
+ /// </summary>
public class SeriesNfoSaver : BaseNfoSaver
{
+ /// <summary>
+ /// Initializes a new instance of the <see cref="SeriesNfoSaver"/> class.
+ /// </summary>
+ /// <param name="fileSystem">The file system.</param>
+ /// <param name="configurationManager">the server configuration manager.</param>
+ /// <param name="libraryManager">The library manager.</param>
+ /// <param name="userManager">The user manager.</param>
+ /// <param name="userDataManager">The user data manager.</param>
+ /// <param name="logger">The logger.</param>
public SeriesNfoSaver(
IFileSystem fileSystem,
IServerConfigurationManager configurationManager,
ILibraryManager libraryManager,
IUserManager userManager,
IUserDataManager userDataManager,
- ILogger logger)
+ ILogger<SeriesNfoSaver> logger)
: base(fileSystem, configurationManager, libraryManager, userManager, userDataManager, logger)
{
}
@@ -56,7 +68,7 @@ namespace MediaBrowser.XbmcMetadata.Savers
: language;
writer.WriteStartElement("url");
- writer.WriteAttributeString("cache", string.Format("{0}.xml", tvdb));
+ writer.WriteAttributeString("cache", tvdb + ".xml");
writer.WriteString(
string.Format(
CultureInfo.InvariantCulture,
diff --git a/MediaBrowser.sln b/MediaBrowser.sln
index 416a434f4..a1dbe8047 100644
--- a/MediaBrowser.sln
+++ b/MediaBrowser.sln
@@ -58,6 +58,12 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Jellyfin.Naming.Tests", "te
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Jellyfin.Api.Tests", "tests\Jellyfin.Api.Tests\Jellyfin.Api.Tests.csproj", "{A2FD0A10-8F62-4F9D-B171-FFDF9F0AFA9D}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Jellyfin.Server.Implementations.Tests", "tests\Jellyfin.Server.Implementations.Tests\Jellyfin.Server.Implementations.Tests.csproj", "{2E3A1B4B-4225-4AAA-8B29-0181A84E7AEE}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Jellyfin.Controller.Tests", "tests\Jellyfin.Controller.Tests\Jellyfin.Controller.Tests.csproj", "{462584F7-5023-4019-9EAC-B98CA458C0A0}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Jellyfin.Data", "Jellyfin.Data\Jellyfin.Data.csproj", "{F03299F2-469F-40EF-A655-3766F97A5702}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -164,6 +170,18 @@ Global
{A2FD0A10-8F62-4F9D-B171-FFDF9F0AFA9D}.Debug|Any CPU.Build.0 = Debug|Any CPU
{A2FD0A10-8F62-4F9D-B171-FFDF9F0AFA9D}.Release|Any CPU.ActiveCfg = Release|Any CPU
{A2FD0A10-8F62-4F9D-B171-FFDF9F0AFA9D}.Release|Any CPU.Build.0 = Release|Any CPU
+ {2E3A1B4B-4225-4AAA-8B29-0181A84E7AEE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {2E3A1B4B-4225-4AAA-8B29-0181A84E7AEE}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {2E3A1B4B-4225-4AAA-8B29-0181A84E7AEE}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {2E3A1B4B-4225-4AAA-8B29-0181A84E7AEE}.Release|Any CPU.Build.0 = Release|Any CPU
+ {462584F7-5023-4019-9EAC-B98CA458C0A0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {462584F7-5023-4019-9EAC-B98CA458C0A0}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {462584F7-5023-4019-9EAC-B98CA458C0A0}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {462584F7-5023-4019-9EAC-B98CA458C0A0}.Release|Any CPU.Build.0 = Release|Any CPU
+ {F03299F2-469F-40EF-A655-3766F97A5702}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {F03299F2-469F-40EF-A655-3766F97A5702}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {F03299F2-469F-40EF-A655-3766F97A5702}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {F03299F2-469F-40EF-A655-3766F97A5702}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -194,5 +212,7 @@ Global
{28464062-0939-4AA7-9F7B-24DDDA61A7C0} = {FBBB5129-006E-4AD7-BAD5-8B7CA1D10ED6}
{3998657B-1CCC-49DD-A19F-275DC8495F57} = {FBBB5129-006E-4AD7-BAD5-8B7CA1D10ED6}
{A2FD0A10-8F62-4F9D-B171-FFDF9F0AFA9D} = {FBBB5129-006E-4AD7-BAD5-8B7CA1D10ED6}
+ {2E3A1B4B-4225-4AAA-8B29-0181A84E7AEE} = {FBBB5129-006E-4AD7-BAD5-8B7CA1D10ED6}
+ {462584F7-5023-4019-9EAC-B98CA458C0A0} = {FBBB5129-006E-4AD7-BAD5-8B7CA1D10ED6}
EndGlobalSection
EndGlobal
diff --git a/README.md b/README.md
index bbac4dd25..99a66e306 100644
--- a/README.md
+++ b/README.md
@@ -14,7 +14,7 @@
<img alt="Current Release" src="https://img.shields.io/github/release/jellyfin/jellyfin.svg"/>
</a>
<a href="https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/?utm_source=widget">
-<img src="https://translate.jellyfin.org/widgets/jellyfin/-/jellyfin-core/svg-badge.svg" alt="Translation Status"/>
+<img alt="Translation Status" src="https://translate.jellyfin.org/widgets/jellyfin/-/jellyfin-core/svg-badge.svg"/>
</a>
<a href="https://dev.azure.com/jellyfin-project/jellyfin/_build?definitionId=1">
<img alt="Azure Builds" src="https://dev.azure.com/jellyfin-project/jellyfin/_apis/build/status/Jellyfin%20CI"/>
@@ -38,6 +38,12 @@
<a href="https://www.reddit.com/r/jellyfin">
<img alt="Join our Subreddit" src="https://img.shields.io/badge/reddit-r%2Fjellyfin-%23FF5700.svg"/>
</a>
+<a href="https://github.com/jellyfin/jellyfin/releases.atom">
+<img alt="Release RSS Feed"" src="https://img.shields.io/badge/rss-releases-ffa500?logo=rss" />
+</a>
+<a href="https://github.com/jellyfin/jellyfin/commits/master.atom">
+<img alt="Master Commits RSS Feed"" src="https://img.shields.io/badge/rss-commits-ffa500?logo=rss" />
+</a>
</p>
---
@@ -47,7 +53,7 @@ Jellyfin is a Free Software Media System that puts you in control of managing an
For further details, please see [our documentation page](https://docs.jellyfin.org/). To receive the latest updates, get help with Jellyfin, and join the community, please visit [one of our communication channels](https://docs.jellyfin.org/general/getting-help.html). For more information about the project, please see our [about page](https://docs.jellyfin.org/general/about.html).
<strong>Want to get started?</strong><br/>
-Choose from <a href="https://docs.jellyfin.org/general/administration/installing.html">Prebuilt Packages</a> or <a href="https://docs.jellyfin.org/general/administration/building.html">Build from Source</a>, then see our <a href="https://docs.jellyfin.org/general/administration/quick-start.html">quick start guide</a>.<br/>
+Choose from <a href="https://docs.jellyfin.org/general/administration/installing.html">Prebuilt Packages</a> or <a href="https://docs.jellyfin.org/general/administration/building.html">Build from Source</a>, then see our <a href="https://docs.jellyfin.org/general/quick-start.html">quick start guide</a>.<br/>
<strong>Something not working right?</strong><br/>
Open an <a href="https://docs.jellyfin.org/general/contributing/issues.html">Issue</a> on GitHub.<br/>
@@ -63,3 +69,99 @@ Most of the translations can be found in the web client but we have several othe
<a href="https://translate.jellyfin.org/engage/jellyfin/?utm_source=widget">
<img src="https://translate.jellyfin.org/widgets/jellyfin/-/jellyfin-web/multi-auto.svg" alt="Detailed Translation Status"/>
</a>
+
+## Jellyfin Server
+
+This repository contains the code for Jellyfin's backend server. Note that this is only one of many projects under the Jellyfin GitHub [organization](https://github.com/jellyfin/) on GitHub. If you want to contribute, you can start by checking out our [documentation](https://jellyfin.org/docs/general/contributing/index.html) to see what to work on.
+
+## Server Development
+
+These instructions will help you get set up with a local development environment in order to contribute to this repository. Before you start, please be sure to completely read our [guidelines on development contributions](https://jellyfin.org/docs/general/contributing/development.html). Note that this project is supported on all major operating systems except FreeBSD, which is still incompatible.
+
+### Prerequisites
+
+Before the project can be built, you must first install the [.NET Core 3.1 SDK](https://dotnet.microsoft.com/download) on your system.
+
+Instructions to run this project from the command line are included here, but you will also need to install an IDE if you want to debug the server while it is running. Any IDE that supports .NET Core development will work, but two options are recent versions of [Visual Studio](https://visualstudio.microsoft.com/downloads/) (at least 2017) and [Visual Studio Code](https://code.visualstudio.com/Download).
+
+### Cloning the Repository
+
+After dependencies are installed you will need to clone a local copy of this repository. If you just want to run the server from source you can clone this repository directly, but if you are intending to contribute code changes to the project, you should [set up your own fork](https://jellyfin.org/docs/general/contributing/development.html#set-up-your-copy-of-the-repo) of the repository. The following example shows how you can clone the repository directly over HTTPS.
+
+```bash
+git clone https://github.com/jellyfin/jellyfin.git
+```
+
+### Installing the Web Client
+
+The server is configured to host the static files required for the [web client](https://github.com/jellyfin/jellyfin-web) in addition to serving the backend by default. Before you can run the server, you will need to get a copy of the web client since they are not included in this repository directly.
+
+Note that it is also possible to [host the web client separately](#hosting-the-web-client-separately) from the web server with some additional configuration, in which case you can skip this step.
+
+There are three options to get the files for the web client.
+
+1. Download one of the finished builds from the [Azure DevOps pipeline](https://dev.azure.com/jellyfin-project/jellyfin/_build?definitionId=11). You can download the build for a specific release by looking at the [branches tab](https://dev.azure.com/jellyfin-project/jellyfin/_build?definitionId=11&_a=summary&repositoryFilter=6&view=branches) of the pipelines page.
+2. Build them from source following the instructions on the [jellyfin-web repository](https://github.com/jellyfin/jellyfin-web)
+3. Get the pre-built files from an existing installation of the server. For example, with a Windows server installation the client files are located at `C:\Program Files\Jellyfin\Server\jellyfin-web`
+
+Once you have a copy of the built web client files, you need to copy them into a specific directory.
+
+> `<repository root>/Mediabrowser.WebDashboard/jellyfin-web`
+
+As part of the build process, this folder will be copied to the build output directory, where it can be accessed by the server.
+
+### Running The Server
+
+The following instructions will help you get the project up and running via the command line, or your preferred IDE.
+
+#### Running With Visual Studio
+
+To run the project with Visual Studio you can open the Solution (`.sln`) file and then press `F5` to run the server.
+
+#### Running With Visual Studio Code
+
+To run the project with Visual Studio Code you will first need to open the repository directory with Visual Studio Code using the `Open Folder...` option.
+
+Second, you need to [install the recommended extensions for the workspace](https://code.visualstudio.com/docs/editor/extension-gallery#_recommended-extensions). Note that extension recommendations are classified as either "Workspace Recommendations" or "Other Recommendations", but only the "Workspace Recommendations" are required.
+
+After the required extensions are installed, you can can run the server by pressing `F5`.
+
+#### Running From The Command Line
+
+To run the server from the command line you can use the `dotnet run` command. The example below shows how to do this if you have cloned the repository into a directory named `jellyfin` (the default directory name) and should work on all operating systems.
+
+```bash
+cd jellyfin # Move into the repository directory
+dotnet run --project Jellyfin.Server # Run the server startup project
+```
+
+A second option is to build the project and then run the resulting executable file directly. When running the executable directly you can easily add command line options. Add the `--help` flag to list details on all the supported command line options.
+
+1. Build the project
+
+ ```bash
+ dotnet build # Build the project
+ cd bin/Debug/netcoreapp3.1 # Change into the build output directory
+ ```
+
+2. Execute the build output. On Linux, Mac, etc. use `./jellyfin` and on Windows use `jellyfin.exe`.
+
+### Running The Tests
+
+This repository also includes unit tests that are used to validate functionality as part of a CI pipeline on Azure. There are several ways to run these tests.
+
+1. Run tests from the command line using `dotnet test`
+2. Run tests in Visual Studio using the [Test Explorer](https://docs.microsoft.com/en-us/visualstudio/test/run-unit-tests-with-test-explorer)
+3. Run individual tests in Visual Studio Code using the associated [CodeLens annotation](https://github.com/OmniSharp/omnisharp-vscode/wiki/How-to-run-and-debug-unit-tests)
+
+### Advanced Configuration
+
+The following sections describe some more advanced scenarios for running the server from source that build upon the standard instructions above.
+
+#### Hosting The Web Client Separately
+
+It is not necessary to host the frontend web client as part of the backend server. Hosting these two components separately may be useful for frontend developers who would prefer to host the client in a separate webpack development server for a tighter development loop. See the [jellyfin-web](https://github.com/jellyfin/jellyfin-web#getting-started) repo for instructions on how to do this.
+
+To instruct the server not to host the web content, there is a `nowebcontent` configuration flag that must be set. This can specified using the command line switch `--nowebcontent` or the environment variable `JELLYFIN_NOWEBCONTENT=true`.
+
+Since this is a common scenario, there is also a separate launch profile defined for Visual Studio called `Jellyfin.Server (nowebcontent)` that can be selected from the 'Start Debugging' dropdown in the main toolbar.
diff --git a/RSSDP/DeviceEventArgs.cs b/RSSDP/DeviceEventArgs.cs
index 3925ba248..05eb4a256 100644
--- a/RSSDP/DeviceEventArgs.cs
+++ b/RSSDP/DeviceEventArgs.cs
@@ -1,6 +1,4 @@
using System;
-using System.Collections.Generic;
-using System.Text;
namespace Rssdp
{
diff --git a/RSSDP/DeviceUnavailableEventArgs.cs b/RSSDP/DeviceUnavailableEventArgs.cs
index d90ddfb60..ef04904bd 100644
--- a/RSSDP/DeviceUnavailableEventArgs.cs
+++ b/RSSDP/DeviceUnavailableEventArgs.cs
@@ -1,7 +1,4 @@
using System;
-using System.Collections.Generic;
-using System.Text;
-using System.Threading.Tasks;
namespace Rssdp
{
diff --git a/RSSDP/DiscoveredSsdpDevice.cs b/RSSDP/DiscoveredSsdpDevice.cs
index f42e7c674..1244ce523 100644
--- a/RSSDP/DiscoveredSsdpDevice.cs
+++ b/RSSDP/DiscoveredSsdpDevice.cs
@@ -1,8 +1,4 @@
using System;
-using System.Collections.Generic;
-using System.Net.Http;
-using System.Text;
-using System.Threading.Tasks;
using System.Net.Http.Headers;
namespace Rssdp
diff --git a/RSSDP/DisposableManagedObjectBase.cs b/RSSDP/DisposableManagedObjectBase.cs
index 0f656fb46..39589f022 100644
--- a/RSSDP/DisposableManagedObjectBase.cs
+++ b/RSSDP/DisposableManagedObjectBase.cs
@@ -1,7 +1,6 @@
using System;
using System.Collections.Generic;
using System.Text;
-using System.Threading.Tasks;
namespace Rssdp.Infrastructure
{
@@ -73,7 +72,7 @@ namespace Rssdp.Infrastructure
/// <para>Sets the <see cref="IsDisposed"/> property to true. Does not explicitly throw an exception if called multiple times, but makes no promises about behaviour of derived classes.</para>
/// </remarks>
/// <seealso cref="IsDisposed"/>
- [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1063:ImplementIDisposableCorrectly", Justification="We do exactly as asked, but CA doesn't seem to like us also setting the IsDisposed property. Too bad, it's a good idea and shouldn't cause an exception or anything likely to interfer with the dispose process.")]
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1063:ImplementIDisposableCorrectly", Justification = "We do exactly as asked, but CA doesn't seem to like us also setting the IsDisposed property. Too bad, it's a good idea and shouldn't cause an exception or anything likely to interfer with the dispose process.")]
public void Dispose()
{
IsDisposed = true;
diff --git a/RSSDP/HttpParserBase.cs b/RSSDP/HttpParserBase.cs
index 76d816e7b..773a06cdb 100644
--- a/RSSDP/HttpParserBase.cs
+++ b/RSSDP/HttpParserBase.cs
@@ -2,8 +2,6 @@ using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
-using System.Text;
-using System.IO;
namespace Rssdp.Infrastructure
{
diff --git a/RSSDP/HttpRequestParser.cs b/RSSDP/HttpRequestParser.cs
index d4505b8ad..279ef883c 100644
--- a/RSSDP/HttpRequestParser.cs
+++ b/RSSDP/HttpRequestParser.cs
@@ -1,10 +1,6 @@
using System;
-using System.Collections.Generic;
using System.Linq;
-using System.Net;
using System.Net.Http;
-using System.Text;
-using System.Threading.Tasks;
namespace Rssdp.Infrastructure
{
diff --git a/RSSDP/HttpResponseParser.cs b/RSSDP/HttpResponseParser.cs
index a77c898ff..b96eaf625 100644
--- a/RSSDP/HttpResponseParser.cs
+++ b/RSSDP/HttpResponseParser.cs
@@ -1,10 +1,7 @@
using System;
-using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;
-using System.Text;
-using System.Threading.Tasks;
namespace Rssdp.Infrastructure
{
diff --git a/RSSDP/IEnumerableExtensions.cs b/RSSDP/IEnumerableExtensions.cs
index c96542dca..371454893 100644
--- a/RSSDP/IEnumerableExtensions.cs
+++ b/RSSDP/IEnumerableExtensions.cs
@@ -1,7 +1,6 @@
using System;
using System.Collections.Generic;
using System.Linq;
-using System.Text;
namespace Rssdp.Infrastructure
{
diff --git a/RSSDP/ISsdpDevicePublisher.cs b/RSSDP/ISsdpDevicePublisher.cs
index 7e914c109..96c15443d 100644
--- a/RSSDP/ISsdpDevicePublisher.cs
+++ b/RSSDP/ISsdpDevicePublisher.cs
@@ -1,4 +1,3 @@
-using System;
using System.Threading.Tasks;
namespace Rssdp.Infrastructure
diff --git a/RSSDP/RSSDP.csproj b/RSSDP/RSSDP.csproj
index 9753ae9b1..e3f3127b6 100644
--- a/RSSDP/RSSDP.csproj
+++ b/RSSDP/RSSDP.csproj
@@ -1,5 +1,10 @@
<Project Sdk="Microsoft.NET.Sdk">
+ <!-- ProjectGuid is only included as a requirement for SonarQube analysis -->
+ <PropertyGroup>
+ <ProjectGuid>{21002819-C39A-4D3E-BE83-2A276A77FB1F}</ProjectGuid>
+ </PropertyGroup>
+
<ItemGroup>
<ProjectReference Include="..\MediaBrowser.Model\MediaBrowser.Model.csproj" />
<ProjectReference Include="..\MediaBrowser.Common\MediaBrowser.Common.csproj" />
diff --git a/RSSDP/SsdpCommunicationsServer.cs b/RSSDP/SsdpCommunicationsServer.cs
index 0aa985a26..18097ef24 100644
--- a/RSSDP/SsdpCommunicationsServer.cs
+++ b/RSSDP/SsdpCommunicationsServer.cs
@@ -8,9 +8,9 @@ using System.Text;
using System.Threading;
using System.Threading.Tasks;
using MediaBrowser.Common.Net;
-using Microsoft.Extensions.Logging;
-using MediaBrowser.Model.Net;
using MediaBrowser.Controller.Configuration;
+using MediaBrowser.Model.Net;
+using Microsoft.Extensions.Logging;
namespace Rssdp.Infrastructure
{
diff --git a/RSSDP/SsdpConstants.cs b/RSSDP/SsdpConstants.cs
index 8372d1cb3..28a014fce 100644
--- a/RSSDP/SsdpConstants.cs
+++ b/RSSDP/SsdpConstants.cs
@@ -1,8 +1,3 @@
-using System;
-using System.Collections.Generic;
-using System.Text;
-using System.Threading.Tasks;
-
namespace Rssdp.Infrastructure
{
/// <summary>
diff --git a/RSSDP/SsdpEmbeddedDevice.cs b/RSSDP/SsdpEmbeddedDevice.cs
index 6f05518a9..4810703d7 100644
--- a/RSSDP/SsdpEmbeddedDevice.cs
+++ b/RSSDP/SsdpEmbeddedDevice.cs
@@ -1,7 +1,3 @@
-using System;
-using System.Collections.Generic;
-using System.Text;
-
namespace Rssdp
{
/// <summary>
diff --git a/SharedVersion.cs b/SharedVersion.cs
index d741f379d..6981c1ca9 100644
--- a/SharedVersion.cs
+++ b/SharedVersion.cs
@@ -1,4 +1,4 @@
using System.Reflection;
-[assembly: AssemblyVersion("10.5.0")]
-[assembly: AssemblyFileVersion("10.5.0")]
+[assembly: AssemblyVersion("10.6.0")]
+[assembly: AssemblyFileVersion("10.6.0")]
diff --git a/benches/Jellyfin.Common.Benches/Jellyfin.Common.Benches.csproj b/benches/Jellyfin.Common.Benches/Jellyfin.Common.Benches.csproj
index bea2e6f0f..47aeed05e 100644
--- a/benches/Jellyfin.Common.Benches/Jellyfin.Common.Benches.csproj
+++ b/benches/Jellyfin.Common.Benches/Jellyfin.Common.Benches.csproj
@@ -2,7 +2,7 @@
<PropertyGroup>
<OutputType>Exe</OutputType>
- <TargetFramework>netcoreapp3.0</TargetFramework>
+ <TargetFramework>netcoreapp3.1</TargetFramework>
</PropertyGroup>
<ItemGroup>
diff --git a/build b/build
index 95d5d5c49..c07a74de4 100755..120000
--- a/build
+++ b/build
@@ -1,197 +1 @@
-#!/usr/bin/env bash
-
-# build - build Jellyfin binaries or packages
-
-set -o errexit
-set -o pipefail
-
-# The list of possible package actions (except 'clean')
-declare -a actions=( 'build' 'package' 'sign' 'publish' )
-
-# The list of possible platforms, based on directories under 'deployment/'
-declare -a platforms=( $(
- find deployment/ -maxdepth 1 -mindepth 1 -type d -exec basename {} \; | sort
-) )
-
-# The list of standard dependencies required by all build scripts; individual
-# action scripts may specify their own dependencies
-declare -a dependencies=( 'tar' 'zip' )
-
-usage() {
- echo -e "build - build Jellyfin binaries or packages"
- echo -e ""
- echo -e "Usage:"
- echo -e " $ build --list-platforms"
- echo -e " $ build --list-actions <platform>"
- echo -e " $ build [-k/--keep-artifacts] [-b/--web-branch <web_branch>] <platform> <action>"
- echo -e ""
- echo -e "The 'keep-artifacts' option preserves build artifacts, e.g. Docker images for system package builds."
- echo -e "The web_branch defaults to the same branch name as the current main branch or can be 'local' to not touch the submodule branching."
- echo -e "To build all platforms, use 'all'."
- echo -e "To perform all build actions, use 'all'."
- echo -e "Build output files are collected at '../bin/<platform>'."
-}
-
-# Show usage on stderr with exit 1 on argless
-if [[ -z $1 ]]; then
- usage >&2
- exit 1
-fi
-# Show usage if -h or --help are specified in the args
-if [[ $@ =~ '-h' || $@ =~ '--help' ]]; then
- usage
- exit 0
-fi
-
-# List all available platforms then exit
-if [[ $1 == '--list-platforms' ]]; then
- echo -e "Available platforms:"
- for platform in ${platforms[@]}; do
- echo -e " ${platform}"
- done
- exit 0
-fi
-
-# List all available actions for a given platform then exit
-if [[ $1 == '--list-actions' ]]; then
- platform="$2"
- if [[ ! " ${platforms[@]} " =~ " ${platform} " ]]; then
- echo "ERROR: Platform ${platform} does not exist."
- exit 1
- fi
- echo -e "Available actions for platform ${platform}:"
- for action in ${actions[@]}; do
- if [[ -f deployment/${platform}/${action}.sh ]]; then
- echo -e " ${action}"
- fi
- done
- exit 0
-fi
-
-# Parse keep-artifacts option
-if [[ $1 == '-k' || $1 == '--keep-artifacts' ]]; then
- keep_artifacts="y"
- shift 1
-else
- keep_artifacts="n"
-fi
-
-# Parse branch option
-if [[ $1 == '-b' || $1 == '--web-branch' ]]; then
- web_branch="$2"
- shift 2
-else
- web_branch="$( git branch 2>/dev/null | sed -e '/^[^*]/d' -e 's/* \(.*\)/\1/' )"
-fi
-
-# Parse platform option
-if [[ -n $1 ]]; then
- cli_platform="$1"
- shift
-else
- echo "ERROR: A platform must be specified. Use 'all' to specify all platforms."
- exit 1
-fi
-if [[ ${cli_platform} == 'all' ]]; then
- declare -a platform=( ${platforms[@]} )
-else
- if [[ ! " ${platforms[@]} " =~ " ${cli_platform} " ]]; then
- echo "ERROR: Platform ${cli_platform} is invalid. Use the '--list-platforms' option to list available platforms."
- exit 1
- else
- declare -a platform=( "${cli_platform}" )
- fi
-fi
-
-# Parse action option
-if [[ -n $1 ]]; then
- cli_action="$1"
- shift
-else
- echo "ERROR: An action must be specified. Use 'all' to specify all actions."
- exit 1
-fi
-if [[ ${cli_action} == 'all' ]]; then
- declare -a action=( ${actions[@]} )
-else
- if [[ ! " ${actions[@]} " =~ " ${cli_action} " ]]; then
- echo "ERROR: Action ${cli_action} is invalid. Use the '--list-actions <platform>' option to list available actions."
- exit 1
- else
- declare -a action=( "${cli_action}" )
- fi
-fi
-
-# Verify required utilities are installed
-missing_deps=()
-for utility in ${dependencies[@]}; do
- if ! which ${utility} &>/dev/null; then
- missing_deps+=( ${utility} )
- fi
-done
-
-# Error if we're missing anything
-if [[ ${#missing_deps[@]} -gt 0 ]]; then
- echo -e "ERROR: This script requires the following missing utilities:"
- for utility in ${missing_deps[@]}; do
- echo -e " ${utility}"
- done
- exit 1
-fi
-
-# Parse platform-specific dependencies
-for target_platform in ${platform[@]}; do
- # Read platform-specific dependencies
- if [[ -f deployment/${target_platform}/dependencies.txt ]]; then
- platform_dependencies="$( grep -v '^#' deployment/${target_platform}/dependencies.txt )"
-
- # Verify required utilities are installed
- missing_deps=()
- for utility in ${platform_dependencies[@]}; do
- if ! which ${utility} &>/dev/null; then
- missing_deps+=( ${utility} )
- fi
- done
-
- # Error if we're missing anything
- if [[ ${#missing_deps[@]} -gt 0 ]]; then
- echo -e "ERROR: The ${target_platform} platform requires the following utilities:"
- for utility in ${missing_deps[@]}; do
- echo -e " ${utility}"
- done
- exit 1
- fi
- fi
-done
-
-# Execute each platform and action in order, if said action is enabled
-pushd deployment/
-for target_platform in ${platform[@]}; do
- echo -e "> Processing platform ${target_platform}"
- date_start=$( date +%s )
- pushd ${target_platform}
- cleanup() {
- echo -e ">> Processing action clean"
- if [[ -f clean.sh && -x clean.sh ]]; then
- ./clean.sh ${keep_artifacts}
- fi
- }
- trap cleanup EXIT INT
- for target_action in ${action[@]}; do
- echo -e ">> Processing action ${target_action}"
- if [[ -f ${target_action}.sh && -x ${target_action}.sh ]]; then
- ./${target_action}.sh web_branch=${web_branch}
- fi
- done
- if [[ -d pkg-dist/ ]]; then
- echo -e ">> Collecting build artifacts"
- target_dir="../../../bin/${target_platform}"
- mkdir -p ${target_dir}
- mv pkg-dist/* ${target_dir}/
- fi
- cleanup
- date_end=$( date +%s )
- echo -e "> Completed platform ${target_platform} in $( expr ${date_end} - ${date_start} ) seconds."
- popd
-done
-popd
+build.sh \ No newline at end of file
diff --git a/build.sh b/build.sh
new file mode 100755
index 000000000..1db02af98
--- /dev/null
+++ b/build.sh
@@ -0,0 +1,114 @@
+#!/usr/bin/env bash
+
+# build.sh - Build Jellyfin binary packages
+# Part of the Jellyfin Project
+
+set -o errexit
+set -o pipefail
+
+usage() {
+ echo -e "build.sh - Build Jellyfin binary packages"
+ echo -e "Usage:"
+ echo -e " $0 -t/--type <BUILD_TYPE> -p/--platform <PLATFORM> [-k/--keep-artifacts] [-l/--list-platforms]"
+ echo -e "Notes:"
+ echo -e " * BUILD_TYPE can be one of: [native, docker] and must be specified"
+ echo -e " * native: Build using the build script in the host OS"
+ echo -e " * docker: Build using the build script in a standardized Docker container"
+ echo -e " * PLATFORM can be any platform shown by -l/--list-platforms and must be specified"
+ echo -e " * If -k/--keep-artifacts is specified, transient artifacts (e.g. Docker containers) will be"
+ echo -e " retained after the build is finished; the source directory will still be cleaned"
+ echo -e " * If -l/--list-platforms is specified, all other arguments are ignored; the script will print"
+ echo -e " the list of supported platforms and exit"
+}
+
+list_platforms() {
+ declare -a platforms
+ platforms=(
+ $( find deployment -maxdepth 1 -mindepth 1 -name "build.*" | awk -F'.' '{ $1=""; printf $2; if ($3 != ""){ printf "." $3; }; if ($4 != ""){ printf "." $4; }; print ""; }' | sort )
+ )
+ echo -e "Valid platforms:"
+ echo
+ for platform in ${platforms[@]}; do
+ echo -e "* ${platform} : $( grep '^#=' deployment/build.${platform} | sed 's/^#= //' )"
+ done
+}
+
+do_build_native() {
+ if [[ ! -f $( which dpkg ) || $( dpkg --print-architecture | head -1 ) != "${PLATFORM##*.}" ]]; then
+ echo "Cross-building is not supported for native builds, use 'docker' builds on amd64 for cross-building."
+ exit 1
+ fi
+ export IS_DOCKER=NO
+ deployment/build.${PLATFORM}
+}
+
+do_build_docker() {
+ if [[ -f $( which dpkg ) && $( dpkg --print-architecture | head -1 ) != "amd64" ]]; then
+ echo "Docker-based builds only support amd64-based cross-building; use a 'native' build instead."
+ exit 1
+ fi
+ if [[ ! -f deployment/Dockerfile.${PLATFORM} ]]; then
+ echo "Missing Dockerfile for platform ${PLATFORM}"
+ exit 1
+ fi
+ if [[ ${KEEP_ARTIFACTS} == YES ]]; then
+ docker_args=""
+ else
+ docker_args="--rm"
+ fi
+
+ docker build . -t "jellyfin-builder.${PLATFORM}" -f deployment/Dockerfile.${PLATFORM}
+ mkdir -p ${ARTIFACT_DIR}
+ docker run $docker_args -v "${SOURCE_DIR}:/jellyfin" -v "${ARTIFACT_DIR}:/dist" "jellyfin-builder.${PLATFORM}"
+}
+
+while [[ $# -gt 0 ]]; do
+ key="$1"
+ case $key in
+ -t|--type)
+ BUILD_TYPE="$2"
+ shift # past argument
+ shift # past value
+ ;;
+ -p|--platform)
+ PLATFORM="$2"
+ shift # past argument
+ shift # past value
+ ;;
+ -k|--keep-artifacts)
+ KEEP_ARTIFACTS=YES
+ shift # past argument
+ ;;
+ -l|--list-platforms)
+ list_platforms
+ exit 0
+ ;;
+ -h|--help)
+ usage
+ exit 0
+ ;;
+ *) # unknown option
+ echo "Unknown option $1"
+ usage
+ exit 1
+ ;;
+ esac
+done
+
+if [[ -z ${BUILD_TYPE} || -z ${PLATFORM} ]]; then
+ usage
+ exit 1
+fi
+
+export SOURCE_DIR="$( pwd )"
+export ARTIFACT_DIR="${SOURCE_DIR}/../bin/${PLATFORM}"
+
+# Determine build type
+case ${BUILD_TYPE} in
+ native)
+ do_build_native
+ ;;
+ docker)
+ do_build_docker
+ ;;
+esac
diff --git a/build.yaml b/build.yaml
index 123f77fb8..9e590e5a0 100644
--- a/build.yaml
+++ b/build.yaml
@@ -1,18 +1,17 @@
---
# We just wrap `build` so this is really it
name: "jellyfin"
-version: "10.5.0"
+version: "10.6.0"
packages:
- - debian-package-x64
- - debian-package-armhf
- - debian-package-arm64
- - ubuntu-package-x64
- - ubuntu-package-armhf
- - ubuntu-package-arm64
- - fedora-package-x64
- - centos-package-x64
- - linux-x64
+ - debian.amd64
+ - debian.arm64
+ - debian.armhf
+ - ubuntu.amd64
+ - ubuntu.arm64
+ - ubuntu.armhf
+ - fedora.amd64
+ - centos.amd64
+ - linux.amd64
+ - windows.amd64
- macos
- portable
- - win-x64
- - win-x86
diff --git a/bump_version b/bump_version
index 106dd7a78..46b7f86e0 100755
--- a/bump_version
+++ b/bump_version
@@ -10,10 +10,6 @@ usage() {
echo -e ""
echo -e "Usage:"
echo -e " $ bump_version <new_version>"
- echo -e ""
- echo -e "The web_branch defaults to the same branch name as the current main branch."
- echo -e "This helps facilitate releases where both branches would be called release-X.Y.Z"
- echo -e "and would already be created before running this script."
}
if [[ -z $1 ]]; then
@@ -54,37 +50,26 @@ else
new_version_deb="${new_version}-1"
fi
-# Set the Dockerfile web version to the specified new_version
-old_version="$(
- grep "JELLYFIN_WEB_VERSION=" Dockerfile \
- | sed -E 's/ARG JELLYFIN_WEB_VERSION=v([0-9\.]+[-a-z0-9]*)/\1/'
-)"
-echo $old_version
-
-old_version_sed="$( sed 's/\./\\./g' <<<"${old_version}" )" # Escape the '.' chars
-sed -i "s/${old_version_sed}/${new_version}/g" Dockerfile*
+# Update the metapackage equivs file
+debian_equivs_file="debian/metapackage/jellyfin"
+sed -i "s/${old_version_sed}/${new_version}/g" ${debian_equivs_file}
# Write out a temporary Debian changelog with our new stuff appended and some templated formatting
-debian_changelog_file="deployment/debian-package-x64/pkg-src/changelog"
+debian_changelog_file="debian/changelog"
debian_changelog_temp="$( mktemp )"
# Create new temp file with our changelog
-echo -e "### DEBIAN PACKAGE CHANGELOG: Verify this file looks correct or edit accordingly, then delete this line, write, and exit.
-jellyfin (${new_version_deb}) unstable; urgency=medium
+echo -e "jellyfin (${new_version_deb}) unstable; urgency=medium
* New upstream version ${new_version}; release changelog at https://github.com/jellyfin/jellyfin/releases/tag/v${new_version}
-- Jellyfin Packaging Team <packaging@jellyfin.org> $( date --rfc-2822 )
" >> ${debian_changelog_temp}
cat ${debian_changelog_file} >> ${debian_changelog_temp}
-# Edit the file to verify
-$EDITOR ${debian_changelog_temp}
# Move into place
mv ${debian_changelog_temp} ${debian_changelog_file}
-# Clean up
-rm -f ${debian_changelog_temp}
# Write out a temporary Yum changelog with our new stuff prepended and some templated formatting
-fedora_spec_file="deployment/fedora-package-x64/pkg-src/jellyfin.spec"
+fedora_spec_file="fedora/jellyfin.spec"
fedora_changelog_temp="$( mktemp )"
fedora_spec_temp_dir="$( mktemp -d )"
fedora_spec_temp="${fedora_spec_temp_dir}/jellyfin.spec.tmp"
@@ -98,21 +83,18 @@ sed -i "s/${old_version_sed}/${new_version_sed}/g" xx00
# Remove the header from xx01
sed -i '/^%changelog/d' xx01
# Create new temp file with our changelog
-echo -e "### YUM SPEC CHANGELOG: Verify this file looks correct or edit accordingly, then delete this line, write, and exit.
-%changelog
+echo -e "%changelog
* $( LANG=C date '+%a %b %d %Y' ) Jellyfin Packaging Team <packaging@jellyfin.org>
- New upstream version ${new_version}; release changelog at https://github.com/jellyfin/jellyfin/releases/tag/v${new_version}" >> ${fedora_changelog_temp}
cat xx01 >> ${fedora_changelog_temp}
-# Edit the file to verify
-$EDITOR ${fedora_changelog_temp}
# Reassembble
cat xx00 ${fedora_changelog_temp} > ${fedora_spec_temp}
popd
# Move into place
mv ${fedora_spec_temp} ${fedora_spec_file}
# Clean up
-rm -rf ${fedora_changelog_temp} ${fedora_spec_temp_dir}
+rm -rf ${fedora_spec_temp_dir}
# Stage the changed files for commit
-git add ${shared_version_file} ${build_file} ${debian_changelog_file} ${fedora_spec_file} Dockerfile*
+git add ${shared_version_file} ${build_file} ${debian_equivs_file} ${debian_changelog_file} ${fedora_spec_file}
git status
diff --git a/deployment/debian-package-x64/pkg-src/bin/restart.sh b/debian/bin/restart.sh
index 9b64b6d72..9b64b6d72 100755
--- a/deployment/debian-package-x64/pkg-src/bin/restart.sh
+++ b/debian/bin/restart.sh
diff --git a/deployment/debian-package-x64/pkg-src/changelog b/debian/changelog
index 51c482237..35fb65957 100644
--- a/deployment/debian-package-x64/pkg-src/changelog
+++ b/debian/changelog
@@ -1,3 +1,9 @@
+jellyfin-server (10.6.0-1) unstable; urgency=medium
+
+ * Forthcoming stable release
+
+ -- Jellyfin Packaging Team <packaging@jellyfin.org> Mon, 23 Mar 2020 14:46:05 -0400
+
jellyfin (10.5.0-1) unstable; urgency=medium
* New upstream version 10.5.0; release changelog at https://github.com/jellyfin/jellyfin/releases/tag/v10.5.0
diff --git a/deployment/debian-package-x64/pkg-src/compat b/debian/compat
index 45a4fb75d..45a4fb75d 100644
--- a/deployment/debian-package-x64/pkg-src/compat
+++ b/debian/compat
diff --git a/deployment/debian-package-x64/pkg-src/conf/jellyfin b/debian/conf/jellyfin
index c6e595f15..64c98520c 100644
--- a/deployment/debian-package-x64/pkg-src/conf/jellyfin
+++ b/debian/conf/jellyfin
@@ -18,6 +18,9 @@ JELLYFIN_CONFIG_DIR="/etc/jellyfin"
JELLYFIN_LOG_DIR="/var/log/jellyfin"
JELLYFIN_CACHE_DIR="/var/cache/jellyfin"
+# web client path, installed by the jellyfin-web package
+JELLYFIN_WEB_OPT="--webdir=/usr/share/jellyfin/web"
+
# Restart script for in-app server control
JELLYFIN_RESTART_OPT="--restartpath=/usr/lib/jellyfin/restart.sh"
@@ -37,4 +40,4 @@ JELLYFIN_FFMPEG_OPT="--ffmpeg=/usr/lib/jellyfin-ffmpeg/ffmpeg"
# Application username
JELLYFIN_USER="jellyfin"
# Full application command
-JELLYFIN_ARGS="$JELLYFIN_RESTART_OPT $JELLYFIN_FFMPEG_OPT $JELLYFIN_SERVICE_OPT $JELLFIN_NOWEBAPP_OPT"
+JELLYFIN_ARGS="$JELLYFIN_WEB_OPT $JELLYFIN_RESTART_OPT $JELLYFIN_FFMPEG_OPT $JELLYFIN_SERVICE_OPT $JELLFIN_NOWEBAPP_OPT"
diff --git a/deployment/debian-package-x64/pkg-src/conf/jellyfin-sudoers b/debian/conf/jellyfin-sudoers
index b481ba4ad..b481ba4ad 100644
--- a/deployment/debian-package-x64/pkg-src/conf/jellyfin-sudoers
+++ b/debian/conf/jellyfin-sudoers
diff --git a/deployment/debian-package-x64/pkg-src/conf/jellyfin.service.conf b/debian/conf/jellyfin.service.conf
index 1b69dd74e..1b69dd74e 100644
--- a/deployment/debian-package-x64/pkg-src/conf/jellyfin.service.conf
+++ b/debian/conf/jellyfin.service.conf
diff --git a/deployment/debian-package-x64/pkg-src/conf/logging.json b/debian/conf/logging.json
index f32b2089e..f32b2089e 100644
--- a/deployment/debian-package-x64/pkg-src/conf/logging.json
+++ b/debian/conf/logging.json
diff --git a/deployment/debian-package-x64/pkg-src/control b/debian/control
index 13fd3ecab..896d8286b 100644
--- a/deployment/debian-package-x64/pkg-src/control
+++ b/debian/control
@@ -1,4 +1,4 @@
-Source: jellyfin
+Source: jellyfin-server
Section: misc
Priority: optional
Maintainer: Jellyfin Team <team@jellyfin.org>
@@ -8,24 +8,23 @@ Build-Depends: debhelper (>= 9),
libcurl4-openssl-dev,
libfontconfig1-dev,
libfreetype6-dev,
- libssl-dev,
- wget,
- npm | nodejs
+ libssl-dev
Standards-Version: 3.9.4
-Homepage: https://jellyfin.media/
+Homepage: https://jellyfin.org/
Vcs-Git: https://github.org/jellyfin/jellyfin.git
Vcs-Browser: https://github.org/jellyfin/jellyfin
-Package: jellyfin
+Package: jellyfin-server
Replaces: mediabrowser, emby, emby-server-beta, jellyfin-dev, emby-server
Breaks: mediabrowser, emby, emby-server-beta, jellyfin-dev, emby-server
Conflicts: mediabrowser, emby, emby-server-beta, jellyfin-dev, emby-server
Architecture: any
Depends: at,
libsqlite3-0,
- jellyfin-ffmpeg,
+ jellyfin-ffmpeg (>= 4.2.1-2),
libfontconfig1,
libfreetype6,
libssl1.1
-Description: Jellyfin is a home media server.
- It is built on top of other popular open source technologies such as Service Stack, jQuery, jQuery mobile, and Mono. It features a REST-based api with built-in documentation to facilitate client development. We also have client libraries for our api to enable rapid development.
+Recommends: jellyfin-web
+Description: Jellyfin is the Free Software Media System.
+ This package provides the Jellyfin server backend and API.
diff --git a/deployment/debian-package-x64/pkg-src/copyright b/debian/copyright
index 0d7a2a600..0d7a2a600 100644
--- a/deployment/debian-package-x64/pkg-src/copyright
+++ b/debian/copyright
diff --git a/deployment/debian-package-x64/pkg-src/gbp.conf b/debian/gbp.conf
index 60b3d2872..60b3d2872 100644
--- a/deployment/debian-package-x64/pkg-src/gbp.conf
+++ b/debian/gbp.conf
diff --git a/deployment/debian-package-x64/pkg-src/install b/debian/install
index 994322d14..994322d14 100644
--- a/deployment/debian-package-x64/pkg-src/install
+++ b/debian/install
diff --git a/deployment/debian-package-x64/pkg-src/jellyfin.init b/debian/jellyfin.init
index 7f5642bac..7f5642bac 100644
--- a/deployment/debian-package-x64/pkg-src/jellyfin.init
+++ b/debian/jellyfin.init
diff --git a/deployment/debian-package-x64/pkg-src/jellyfin.service b/debian/jellyfin.service
index 1305e238b..f1a8f4652 100644
--- a/deployment/debian-package-x64/pkg-src/jellyfin.service
+++ b/debian/jellyfin.service
@@ -6,7 +6,7 @@ After = network.target
Type = simple
EnvironmentFile = /etc/default/jellyfin
User = jellyfin
-ExecStart = /usr/bin/jellyfin ${JELLYFIN_RESTART_OPT} ${JELLYFIN_FFMPEG_OPT} ${JELLYFIN_SERVICE_OPT} ${JELLYFIN_NOWEBAPP_OPT}
+ExecStart = /usr/bin/jellyfin ${JELLYFIN_WEB_OPT} ${JELLYFIN_RESTART_OPT} ${JELLYFIN_FFMPEG_OPT} ${JELLYFIN_SERVICE_OPT} ${JELLYFIN_NOWEBAPP_OPT}
Restart = on-failure
TimeoutSec = 15
diff --git a/deployment/debian-package-x64/pkg-src/jellyfin.upstart b/debian/jellyfin.upstart
index ef5bc9bca..ef5bc9bca 100644
--- a/deployment/debian-package-x64/pkg-src/jellyfin.upstart
+++ b/debian/jellyfin.upstart
diff --git a/debian/metapackage/jellyfin b/debian/metapackage/jellyfin
new file mode 100644
index 000000000..9a41eafae
--- /dev/null
+++ b/debian/metapackage/jellyfin
@@ -0,0 +1,13 @@
+Source: jellyfin
+Section: misc
+Priority: optional
+Homepage: https://jellyfin.org
+Standards-Version: 3.9.2
+
+Package: jellyfin
+Version: 10.6.0
+Maintainer: Jellyfin Packaging Team <packaging@jellyfin.org>
+Depends: jellyfin-server, jellyfin-web
+Description: Provides the Jellyfin Free Software Media System
+ Provides the full Jellyfin experience, including both the server and web interface.
+
diff --git a/deployment/debian-package-x64/pkg-src/po/POTFILES.in b/debian/po/POTFILES.in
index cef83a340..cef83a340 100644
--- a/deployment/debian-package-x64/pkg-src/po/POTFILES.in
+++ b/debian/po/POTFILES.in
diff --git a/deployment/debian-package-x64/pkg-src/po/templates.pot b/debian/po/templates.pot
index 2cdcae417..2cdcae417 100644
--- a/deployment/debian-package-x64/pkg-src/po/templates.pot
+++ b/debian/po/templates.pot
diff --git a/deployment/debian-package-x64/pkg-src/postinst b/debian/postinst
index 860222e05..860222e05 100644
--- a/deployment/debian-package-x64/pkg-src/postinst
+++ b/debian/postinst
diff --git a/deployment/debian-package-x64/pkg-src/postrm b/debian/postrm
index 1d00a984e..1d00a984e 100644
--- a/deployment/debian-package-x64/pkg-src/postrm
+++ b/debian/postrm
diff --git a/deployment/debian-package-x64/pkg-src/preinst b/debian/preinst
index 2713fb9b8..2713fb9b8 100644
--- a/deployment/debian-package-x64/pkg-src/preinst
+++ b/debian/preinst
diff --git a/deployment/debian-package-x64/pkg-src/prerm b/debian/prerm
index e965cb7d7..e965cb7d7 100644
--- a/deployment/debian-package-x64/pkg-src/prerm
+++ b/debian/prerm
diff --git a/deployment/debian-package-x64/pkg-src/rules b/debian/rules
index c2d57dfb2..2a5d41a69 100755
--- a/deployment/debian-package-x64/pkg-src/rules
+++ b/debian/rules
@@ -2,8 +2,6 @@
CONFIG := Release
TERM := xterm
SHELL := /bin/bash
-WEB_TARGET := $(CURDIR)/MediaBrowser.WebDashboard/jellyfin-web
-WEB_VERSION := $(shell sed -n -e 's/^version: "\(.*\)"/\1/p' $(CURDIR)/build.yaml)
HOST_ARCH := $(shell arch)
BUILD_ARCH := ${DEB_HOST_MULTIARCH}
@@ -41,25 +39,12 @@ override_dh_auto_test:
override_dh_clistrip:
override_dh_auto_build:
- echo $(WEB_VERSION)
- # Clone down and build Web frontend
- mkdir -p $(WEB_TARGET)
- wget -O web-src.tgz https://github.com/jellyfin/jellyfin-web/archive/v$(WEB_VERSION).tar.gz || wget -O web-src.tgz https://github.com/jellyfin/jellyfin-web/archive/master.tar.gz
- mkdir -p $(CURDIR)/web
- tar -xzf web-src.tgz -C $(CURDIR)/web/ --strip 1
- cd $(CURDIR)/web/ && npm install yarn
- cd $(CURDIR)/web/ && node_modules/yarn/bin/yarn install
- mv $(CURDIR)/web/dist/* $(WEB_TARGET)/
- # Build the application
dotnet publish --configuration $(CONFIG) --output='$(CURDIR)/usr/lib/jellyfin/bin' --self-contained --runtime $(DOTNETRUNTIME) \
"-p:GenerateDocumentationFile=false;DebugSymbols=false;DebugType=none" Jellyfin.Server
override_dh_auto_clean:
dotnet clean -maxcpucount:1 --configuration $(CONFIG) Jellyfin.Server || true
- rm -f '$(CURDIR)/web-src.tgz'
rm -rf '$(CURDIR)/usr'
- rm -rf '$(CURDIR)/web'
- rm -rf '$(WEB_TARGET)'
# Force the service name to jellyfin even if we're building jellyfin-nightly
override_dh_installinit:
diff --git a/deployment/debian-package-x64/pkg-src/source.lintian-overrides b/debian/source.lintian-overrides
index aeb332f13..aeb332f13 100644
--- a/deployment/debian-package-x64/pkg-src/source.lintian-overrides
+++ b/debian/source.lintian-overrides
diff --git a/deployment/debian-package-x64/pkg-src/source/format b/debian/source/format
index d3827e75a..d3827e75a 100644
--- a/deployment/debian-package-x64/pkg-src/source/format
+++ b/debian/source/format
diff --git a/deployment/debian-package-x64/pkg-src/source/options b/debian/source/options
index 17b5373d5..17b5373d5 100644
--- a/deployment/debian-package-x64/pkg-src/source/options
+++ b/debian/source/options
diff --git a/deployment/Dockerfile.centos.amd64 b/deployment/Dockerfile.centos.amd64
new file mode 100644
index 000000000..39788cc0e
--- /dev/null
+++ b/deployment/Dockerfile.centos.amd64
@@ -0,0 +1,32 @@
+FROM centos:7
+# Docker build arguments
+ARG SOURCE_DIR=/jellyfin
+ARG ARTIFACT_DIR=/dist
+ARG SDK_VERSION=3.1
+# Docker run environment
+ENV SOURCE_DIR=/jellyfin
+ENV ARTIFACT_DIR=/dist
+ENV IS_DOCKER=YES
+
+# Prepare CentOS environment
+RUN yum update -y \
+ && yum install -y epel-release \
+ && yum install -y @buildsys-build rpmdevtools yum-plugins-core libcurl-devel fontconfig-devel freetype-devel openssl-devel glibc-devel libicu-devel git
+
+# Install DotNET SDK
+RUN rpm -Uvh https://packages.microsoft.com/config/rhel/7/packages-microsoft-prod.rpm \
+ && rpmdev-setuptree \
+ && yum install -y dotnet-sdk-${SDK_VERSION}
+
+# Create symlinks and directories
+RUN ln -sf ${SOURCE_DIR}/deployment/build.centos.amd64 /build.sh \
+ && mkdir -p ${SOURCE_DIR}/SPECS \
+ && ln -s ${SOURCE_DIR}/fedora/jellyfin.spec ${SOURCE_DIR}/SPECS/jellyfin.spec \
+ && mkdir -p ${SOURCE_DIR}/SOURCES \
+ && ln -s ${SOURCE_DIR}/fedora ${SOURCE_DIR}/SOURCES
+
+VOLUME ${SOURCE_DIR}/
+
+VOLUME ${ARTIFACT_DIR}/
+
+ENTRYPOINT ["/build.sh"]
diff --git a/deployment/win-x64/Dockerfile b/deployment/Dockerfile.debian.amd64
index 8a3374954..b5a038048 100644
--- a/deployment/win-x64/Dockerfile
+++ b/deployment/Dockerfile.debian.amd64
@@ -1,7 +1,6 @@
FROM debian:10
# Docker build arguments
ARG SOURCE_DIR=/jellyfin
-ARG PLATFORM_DIR=/jellyfin/deployment/win-x64
ARG ARTIFACT_DIR=/dist
ARG SDK_VERSION=3.1
# Docker run environment
@@ -9,10 +8,11 @@ ENV SOURCE_DIR=/jellyfin
ENV ARTIFACT_DIR=/dist
ENV DEB_BUILD_OPTIONS=noddebs
ENV ARCH=amd64
+ENV IS_DOCKER=YES
# Prepare Debian build environment
RUN apt-get update \
- && apt-get install -y apt-transport-https debhelper gnupg wget devscripts mmv libc6-dev libcurl4-openssl-dev libfontconfig1-dev libfreetype6-dev libssl-dev libssl1.1 liblttng-ust0 zip
+ && apt-get install -y apt-transport-https debhelper gnupg wget devscripts mmv libc6-dev libcurl4-openssl-dev libfontconfig1-dev libfreetype6-dev libssl-dev libssl1.1 liblttng-ust0
# Install dotnet repository
# https://dotnet.microsoft.com/download/linux-package-manager/debian9/sdk-current
@@ -21,17 +21,11 @@ RUN wget https://download.visualstudio.microsoft.com/download/pr/d731f991-8e68-4
&& tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \
&& ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet
-# Install yarn package manager
-RUN wget -q -O- https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add - \
- && echo "deb https://dl.yarnpkg.com/debian/ stable main" > /etc/apt/sources.list.d/yarn.list \
- && apt update \
- && apt install -y yarn
+# Link to build script
+RUN ln -sf ${SOURCE_DIR}/deployment/build.debian.amd64 /build.sh
-# Link to docker-build script
-RUN ln -sf ${PLATFORM_DIR}/docker-build.sh /docker-build.sh
+VOLUME ${SOURCE_DIR}/
VOLUME ${ARTIFACT_DIR}/
-COPY . ${SOURCE_DIR}/
-
-ENTRYPOINT ["/docker-build.sh"]
+ENTRYPOINT ["/build.sh"]
diff --git a/deployment/debian-package-arm64/Dockerfile.amd64 b/deployment/Dockerfile.debian.arm64
index b63e08b7d..cfe562df3 100644
--- a/deployment/debian-package-arm64/Dockerfile.amd64
+++ b/deployment/Dockerfile.debian.arm64
@@ -1,7 +1,6 @@
FROM debian:10
# Docker build arguments
ARG SOURCE_DIR=/jellyfin
-ARG PLATFORM_DIR=/jellyfin/deployment/debian-package-arm64
ARG ARTIFACT_DIR=/dist
ARG SDK_VERSION=3.1
# Docker run environment
@@ -9,10 +8,11 @@ ENV SOURCE_DIR=/jellyfin
ENV ARTIFACT_DIR=/dist
ENV DEB_BUILD_OPTIONS=noddebs
ENV ARCH=amd64
+ENV IS_DOCKER=YES
# Prepare Debian build environment
RUN apt-get update \
- && apt-get install -y apt-transport-https debhelper gnupg wget npm devscripts mmv
+ && apt-get install -y apt-transport-https debhelper gnupg wget devscripts mmv
# Install dotnet repository
# https://dotnet.microsoft.com/download/linux-package-manager/debian9/sdk-current
@@ -29,15 +29,11 @@ RUN dpkg --add-architecture arm64 \
&& cd cross-gcc-packages-amd64/cross-gcc-8-arm64 \
&& apt-get install -y gcc-8-source libstdc++-8-dev-arm64-cross binutils-aarch64-linux-gnu bison flex libtool gdb sharutils netbase libmpc-dev libmpfr-dev libgmp-dev systemtap-sdt-dev autogen expect chrpath zlib1g-dev zip libc6-dev:arm64 linux-libc-dev:arm64 libgcc1:arm64 libcurl4-openssl-dev:arm64 libfontconfig1-dev:arm64 libfreetype6-dev:arm64 libssl-dev:arm64 liblttng-ust0:arm64 libstdc++-8-dev:arm64
-# Link to docker-build script
-RUN ln -sf ${PLATFORM_DIR}/docker-build.sh /docker-build.sh
+# Link to build script
+RUN ln -sf ${SOURCE_DIR}/deployment/build.debian.arm64 /build.sh
-# Link to Debian source dir; mkdir needed or it fails, can't force dest
-RUN mkdir -p ${SOURCE_DIR} && ln -sf ${PLATFORM_DIR}/pkg-src ${SOURCE_DIR}/debian
+VOLUME ${SOURCE_DIR}/
VOLUME ${ARTIFACT_DIR}/
-COPY . ${SOURCE_DIR}/
-
-ENTRYPOINT ["/docker-build.sh"]
-#ENTRYPOINT ["/bin/bash"]
+ENTRYPOINT ["/build.sh"]
diff --git a/deployment/debian-package-armhf/Dockerfile.amd64 b/deployment/Dockerfile.debian.armhf
index 1b64b5314..ea8c8c8e6 100644
--- a/deployment/debian-package-armhf/Dockerfile.amd64
+++ b/deployment/Dockerfile.debian.armhf
@@ -1,7 +1,6 @@
FROM debian:10
# Docker build arguments
ARG SOURCE_DIR=/jellyfin
-ARG PLATFORM_DIR=/jellyfin/deployment/debian-package-armhf
ARG ARTIFACT_DIR=/dist
ARG SDK_VERSION=3.1
# Docker run environment
@@ -9,10 +8,11 @@ ENV SOURCE_DIR=/jellyfin
ENV ARTIFACT_DIR=/dist
ENV DEB_BUILD_OPTIONS=noddebs
ENV ARCH=amd64
+ENV IS_DOCKER=YES
# Prepare Debian build environment
RUN apt-get update \
- && apt-get install -y apt-transport-https debhelper gnupg wget npm devscripts mmv
+ && apt-get install -y apt-transport-https debhelper gnupg wget devscripts mmv
# Install dotnet repository
# https://dotnet.microsoft.com/download/linux-package-manager/debian9/sdk-current
@@ -29,14 +29,11 @@ RUN dpkg --add-architecture armhf \
&& cd cross-gcc-packages-amd64/cross-gcc-8-armhf \
&& apt-get install -y gcc-8-source libstdc++-8-dev-armhf-cross binutils-aarch64-linux-gnu bison flex libtool gdb sharutils netbase libmpc-dev libmpfr-dev libgmp-dev systemtap-sdt-dev autogen expect chrpath zlib1g-dev zip binutils-arm-linux-gnueabihf libc6-dev:armhf linux-libc-dev:armhf libgcc1:armhf libcurl4-openssl-dev:armhf libfontconfig1-dev:armhf libfreetype6-dev:armhf libssl-dev:armhf liblttng-ust0:armhf libstdc++-8-dev:armhf
-# Link to docker-build script
-RUN ln -sf ${PLATFORM_DIR}/docker-build.sh /docker-build.sh
+# Link to build script
+RUN ln -sf ${SOURCE_DIR}/deployment/build.debian.armhf /build.sh
-# Link to Debian source dir; mkdir needed or it fails, can't force dest
-RUN mkdir -p ${SOURCE_DIR} && ln -sf ${PLATFORM_DIR}/pkg-src ${SOURCE_DIR}/debian
+VOLUME ${SOURCE_DIR}/
VOLUME ${ARTIFACT_DIR}/
-COPY . ${SOURCE_DIR}/
-
-ENTRYPOINT ["/docker-build.sh"]
+ENTRYPOINT ["/build.sh"]
diff --git a/deployment/Dockerfile.fedora.amd64 b/deployment/Dockerfile.fedora.amd64
new file mode 100644
index 000000000..01b99deb6
--- /dev/null
+++ b/deployment/Dockerfile.fedora.amd64
@@ -0,0 +1,31 @@
+FROM fedora:31
+# Docker build arguments
+ARG SOURCE_DIR=/jellyfin
+ARG ARTIFACT_DIR=/dist
+ARG SDK_VERSION=3.1
+# Docker run environment
+ENV SOURCE_DIR=/jellyfin
+ENV ARTIFACT_DIR=/dist
+ENV IS_DOCKER=YES
+
+# Prepare Fedora environment
+RUN dnf update -y \
+ && dnf install -y @buildsys-build rpmdevtools git dnf-plugins-core libcurl-devel fontconfig-devel freetype-devel openssl-devel glibc-devel libicu-devel
+
+# Install DotNET SDK
+RUN rpm --import https://packages.microsoft.com/keys/microsoft.asc \
+ && curl -o /etc/yum.repos.d/microsoft-prod.repo https://packages.microsoft.com/config/fedora/$(rpm -E %fedora)/prod.repo \
+ && dnf install -y dotnet-sdk-${SDK_VERSION} dotnet-runtime-${SDK_VERSION}
+
+# Create symlinks and directories
+RUN ln -sf ${SOURCE_DIR}/deployment/build.fedora.amd64 /build.sh \
+ && mkdir -p ${SOURCE_DIR}/SPECS \
+ && ln -s ${SOURCE_DIR}/fedora/jellyfin.spec ${SOURCE_DIR}/SPECS/jellyfin.spec \
+ && mkdir -p ${SOURCE_DIR}/SOURCES \
+ && ln -s ${SOURCE_DIR}/fedora ${SOURCE_DIR}/SOURCES
+
+VOLUME ${SOURCE_DIR}/
+
+VOLUME ${ARTIFACT_DIR}/
+
+ENTRYPOINT ["/build.sh"]
diff --git a/deployment/macos/Dockerfile b/deployment/Dockerfile.linux.amd64
index b522df884..d8bec9214 100644
--- a/deployment/macos/Dockerfile
+++ b/deployment/Dockerfile.linux.amd64
@@ -1,7 +1,6 @@
FROM debian:10
# Docker build arguments
ARG SOURCE_DIR=/jellyfin
-ARG PLATFORM_DIR=/jellyfin/deployment/macos
ARG ARTIFACT_DIR=/dist
ARG SDK_VERSION=3.1
# Docker run environment
@@ -9,6 +8,7 @@ ENV SOURCE_DIR=/jellyfin
ENV ARTIFACT_DIR=/dist
ENV DEB_BUILD_OPTIONS=noddebs
ENV ARCH=amd64
+ENV IS_DOCKER=YES
# Prepare Debian build environment
RUN apt-get update \
@@ -21,17 +21,11 @@ RUN wget https://download.visualstudio.microsoft.com/download/pr/d731f991-8e68-4
&& tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \
&& ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet
-# Install yarn package manager
-RUN wget -q -O- https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add - \
- && echo "deb https://dl.yarnpkg.com/debian/ stable main" > /etc/apt/sources.list.d/yarn.list \
- && apt update \
- && apt install -y yarn
-
# Link to docker-build script
-RUN ln -sf ${PLATFORM_DIR}/docker-build.sh /docker-build.sh
+RUN ln -sf ${SOURCE_DIR}/deployment/build.linux.amd64 /build.sh
-VOLUME ${ARTIFACT_DIR}/
+VOLUME ${SOURCE_DIR}/
-COPY . ${SOURCE_DIR}/
+VOLUME ${ARTIFACT_DIR}/
-ENTRYPOINT ["/docker-build.sh"]
+ENTRYPOINT ["/build.sh"]
diff --git a/deployment/portable/Dockerfile b/deployment/Dockerfile.macos
index 965eb82b8..ba5da4019 100644
--- a/deployment/portable/Dockerfile
+++ b/deployment/Dockerfile.macos
@@ -1,7 +1,6 @@
FROM debian:10
# Docker build arguments
ARG SOURCE_DIR=/jellyfin
-ARG PLATFORM_DIR=/jellyfin/deployment/portable
ARG ARTIFACT_DIR=/dist
ARG SDK_VERSION=3.1
# Docker run environment
@@ -9,6 +8,7 @@ ENV SOURCE_DIR=/jellyfin
ENV ARTIFACT_DIR=/dist
ENV DEB_BUILD_OPTIONS=noddebs
ENV ARCH=amd64
+ENV IS_DOCKER=YES
# Prepare Debian build environment
RUN apt-get update \
@@ -21,17 +21,11 @@ RUN wget https://download.visualstudio.microsoft.com/download/pr/d731f991-8e68-4
&& tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \
&& ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet
-# Install yarn package manager
-RUN wget -q -O- https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add - \
- && echo "deb https://dl.yarnpkg.com/debian/ stable main" > /etc/apt/sources.list.d/yarn.list \
- && apt update \
- && apt install -y yarn
-
# Link to docker-build script
-RUN ln -sf ${PLATFORM_DIR}/docker-build.sh /docker-build.sh
+RUN ln -sf ${SOURCE_DIR}/deployment/build.macos /build.sh
-VOLUME ${ARTIFACT_DIR}/
+VOLUME ${SOURCE_DIR}/
-COPY . ${SOURCE_DIR}/
+VOLUME ${ARTIFACT_DIR}/
-ENTRYPOINT ["/docker-build.sh"]
+ENTRYPOINT ["/build.sh"]
diff --git a/deployment/linux-x64/Dockerfile b/deployment/Dockerfile.portable
index c47057546..2893e140d 100644
--- a/deployment/linux-x64/Dockerfile
+++ b/deployment/Dockerfile.portable
@@ -1,14 +1,13 @@
FROM debian:10
# Docker build arguments
ARG SOURCE_DIR=/jellyfin
-ARG PLATFORM_DIR=/jellyfin/deployment/linux-x64
ARG ARTIFACT_DIR=/dist
ARG SDK_VERSION=3.1
# Docker run environment
ENV SOURCE_DIR=/jellyfin
ENV ARTIFACT_DIR=/dist
ENV DEB_BUILD_OPTIONS=noddebs
-ENV ARCH=amd64
+ENV IS_DOCKER=YES
# Prepare Debian build environment
RUN apt-get update \
@@ -21,17 +20,11 @@ RUN wget https://download.visualstudio.microsoft.com/download/pr/d731f991-8e68-4
&& tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \
&& ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet
-# Install yarn package manager
-RUN wget -q -O- https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add - \
- && echo "deb https://dl.yarnpkg.com/debian/ stable main" > /etc/apt/sources.list.d/yarn.list \
- && apt update \
- && apt install -y yarn
-
# Link to docker-build script
-RUN ln -sf ${PLATFORM_DIR}/docker-build.sh /docker-build.sh
+RUN ln -sf ${SOURCE_DIR}/deployment/build.portable /build.sh
-VOLUME ${ARTIFACT_DIR}/
+VOLUME ${SOURCE_DIR}/
-COPY . ${SOURCE_DIR}/
+VOLUME ${ARTIFACT_DIR}/
-ENTRYPOINT ["/docker-build.sh"]
+ENTRYPOINT ["/build.sh"]
diff --git a/deployment/Dockerfile.ubuntu.amd64 b/deployment/Dockerfile.ubuntu.amd64
new file mode 100644
index 000000000..e61be4efc
--- /dev/null
+++ b/deployment/Dockerfile.ubuntu.amd64
@@ -0,0 +1,31 @@
+FROM ubuntu:bionic
+# Docker build arguments
+ARG SOURCE_DIR=/jellyfin
+ARG ARTIFACT_DIR=/dist
+ARG SDK_VERSION=3.1
+# Docker run environment
+ENV SOURCE_DIR=/jellyfin
+ENV ARTIFACT_DIR=/dist
+ENV DEB_BUILD_OPTIONS=noddebs
+ENV ARCH=amd64
+ENV IS_DOCKER=YES
+
+# Prepare Debian build environment
+RUN apt-get update \
+ && apt-get install -y apt-transport-https debhelper gnupg wget devscripts mmv libc6-dev libcurl4-openssl-dev libfontconfig1-dev libfreetype6-dev libssl-dev libssl1.1 liblttng-ust0
+
+# Install dotnet repository
+# https://dotnet.microsoft.com/download/linux-package-manager/debian9/sdk-current
+RUN wget https://download.visualstudio.microsoft.com/download/pr/d731f991-8e68-4c7c-8ea0-fad5605b077a/49497b5420eecbd905158d86d738af64/dotnet-sdk-3.1.100-linux-x64.tar.gz -O dotnet-sdk.tar.gz \
+ && mkdir -p dotnet-sdk \
+ && tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \
+ && ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet
+
+# Link to build script
+RUN ln -sf ${SOURCE_DIR}/deployment/build.ubuntu.amd64 /build.sh
+
+VOLUME ${SOURCE_DIR}/
+
+VOLUME ${ARTIFACT_DIR}/
+
+ENTRYPOINT ["/build.sh"]
diff --git a/deployment/ubuntu-package-arm64/Dockerfile.amd64 b/deployment/Dockerfile.ubuntu.arm64
index ac4f7404d..f91b91cd4 100644
--- a/deployment/ubuntu-package-arm64/Dockerfile.amd64
+++ b/deployment/Dockerfile.ubuntu.arm64
@@ -1,7 +1,6 @@
FROM ubuntu:bionic
# Docker build arguments
ARG SOURCE_DIR=/jellyfin
-ARG PLATFORM_DIR=/jellyfin/deployment/ubuntu-package-arm64
ARG ARTIFACT_DIR=/dist
ARG SDK_VERSION=3.1
# Docker run environment
@@ -9,6 +8,7 @@ ENV SOURCE_DIR=/jellyfin
ENV ARTIFACT_DIR=/dist
ENV DEB_BUILD_OPTIONS=noddebs
ENV ARCH=amd64
+ENV IS_DOCKER=YES
# Prepare Debian build environment
RUN apt-get update \
@@ -21,12 +21,6 @@ RUN wget https://download.visualstudio.microsoft.com/download/pr/d731f991-8e68-4
&& tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \
&& ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet
-# Install npm package manager
-RUN wget -q -O- https://deb.nodesource.com/gpgkey/nodesource.gpg.key | apt-key add - \
- && echo "deb https://deb.nodesource.com/node_8.x $(lsb_release -s -c) main" > /etc/apt/sources.list.d/npm.list \
- && apt update \
- && apt install -y nodejs
-
# Prepare the cross-toolchain
RUN rm /etc/apt/sources.list \
&& export CODENAME="$( lsb_release -c -s )" \
@@ -46,14 +40,11 @@ RUN rm /etc/apt/sources.list \
&& ln -fs /usr/share/zoneinfo/America/Toronto /etc/localtime \
&& apt-get install -y gcc-6-source libstdc++6-arm64-cross binutils-aarch64-linux-gnu bison flex libtool gdb sharutils netbase libcloog-isl-dev libmpc-dev libmpfr-dev libgmp-dev systemtap-sdt-dev autogen expect chrpath zlib1g-dev zip libc6-dev:arm64 linux-libc-dev:arm64 libgcc1:arm64 libcurl4-openssl-dev:arm64 libfontconfig1-dev:arm64 libfreetype6-dev:arm64 liblttng-ust0:arm64 libstdc++6:arm64 libssl-dev:arm64
-# Link to docker-build script
-RUN ln -sf ${PLATFORM_DIR}/docker-build.sh /docker-build.sh
+# Link to build script
+RUN ln -sf ${SOURCE_DIR}/deployment/build.ubuntu.arm64 /build.sh
-# Link to Debian source dir; mkdir needed or it fails, can't force dest
-RUN mkdir -p ${SOURCE_DIR} && ln -sf ${PLATFORM_DIR}/pkg-src ${SOURCE_DIR}/debian
+VOLUME ${SOURCE_DIR}/
VOLUME ${ARTIFACT_DIR}/
-COPY . ${SOURCE_DIR}/
-
-ENTRYPOINT ["/docker-build.sh"]
+ENTRYPOINT ["/build.sh"]
diff --git a/deployment/ubuntu-package-armhf/Dockerfile.amd64 b/deployment/Dockerfile.ubuntu.armhf
index 590eecab7..85414614c 100644
--- a/deployment/ubuntu-package-armhf/Dockerfile.amd64
+++ b/deployment/Dockerfile.ubuntu.armhf
@@ -1,7 +1,6 @@
FROM ubuntu:bionic
# Docker build arguments
ARG SOURCE_DIR=/jellyfin
-ARG PLATFORM_DIR=/jellyfin/deployment/ubuntu-package-armhf
ARG ARTIFACT_DIR=/dist
ARG SDK_VERSION=3.1
# Docker run environment
@@ -9,6 +8,7 @@ ENV SOURCE_DIR=/jellyfin
ENV ARTIFACT_DIR=/dist
ENV DEB_BUILD_OPTIONS=noddebs
ENV ARCH=amd64
+ENV IS_DOCKER=YES
# Prepare Debian build environment
RUN apt-get update \
@@ -21,12 +21,6 @@ RUN wget https://download.visualstudio.microsoft.com/download/pr/d731f991-8e68-4
&& tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \
&& ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet
-# Install npm package manager
-RUN wget -q -O- https://deb.nodesource.com/gpgkey/nodesource.gpg.key | apt-key add - \
- && echo "deb https://deb.nodesource.com/node_8.x $(lsb_release -s -c) main" > /etc/apt/sources.list.d/npm.list \
- && apt update \
- && apt install -y nodejs
-
# Prepare the cross-toolchain
RUN rm /etc/apt/sources.list \
&& export CODENAME="$( lsb_release -c -s )" \
@@ -46,14 +40,11 @@ RUN rm /etc/apt/sources.list \
&& ln -fs /usr/share/zoneinfo/America/Toronto /etc/localtime \
&& apt-get install -y gcc-6-source libstdc++6-armhf-cross binutils-arm-linux-gnueabihf bison flex libtool gdb sharutils netbase libcloog-isl-dev libmpc-dev libmpfr-dev libgmp-dev systemtap-sdt-dev autogen expect chrpath zlib1g-dev zip libc6-dev:armhf linux-libc-dev:armhf libgcc1:armhf libcurl4-openssl-dev:armhf libfontconfig1-dev:armhf libfreetype6-dev:armhf liblttng-ust0:armhf libstdc++6:armhf libssl-dev:armhf
-# Link to docker-build script
-RUN ln -sf ${PLATFORM_DIR}/docker-build.sh /docker-build.sh
+# Link to build script
+RUN ln -sf ${SOURCE_DIR}/deployment/build.debian.armhf /build.sh
-# Link to Debian source dir; mkdir needed or it fails, can't force dest
-RUN mkdir -p ${SOURCE_DIR} && ln -sf ${PLATFORM_DIR}/pkg-src ${SOURCE_DIR}/debian
+VOLUME ${SOURCE_DIR}/
VOLUME ${ARTIFACT_DIR}/
-COPY . ${SOURCE_DIR}/
-
-ENTRYPOINT ["/docker-build.sh"]
+ENTRYPOINT ["/build.sh"]
diff --git a/deployment/win-x86/Dockerfile b/deployment/Dockerfile.windows.amd64
index f8dc5be83..0397a023e 100644
--- a/deployment/win-x86/Dockerfile
+++ b/deployment/Dockerfile.windows.amd64
@@ -1,14 +1,13 @@
FROM debian:10
# Docker build arguments
ARG SOURCE_DIR=/jellyfin
-ARG PLATFORM_DIR=/jellyfin/deployment/win-x86
ARG ARTIFACT_DIR=/dist
ARG SDK_VERSION=3.1
# Docker run environment
ENV SOURCE_DIR=/jellyfin
ENV ARTIFACT_DIR=/dist
ENV DEB_BUILD_OPTIONS=noddebs
-ENV ARCH=amd64
+ENV IS_DOCKER=YES
# Prepare Debian build environment
RUN apt-get update \
@@ -21,17 +20,11 @@ RUN wget https://download.visualstudio.microsoft.com/download/pr/d731f991-8e68-4
&& tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \
&& ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet
-# Install yarn package manager
-RUN wget -q -O- https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add - \
- && echo "deb https://dl.yarnpkg.com/debian/ stable main" > /etc/apt/sources.list.d/yarn.list \
- && apt update \
- && apt install -y yarn
-
# Link to docker-build script
-RUN ln -sf ${PLATFORM_DIR}/docker-build.sh /docker-build.sh
+RUN ln -sf ${SOURCE_DIR}/deployment/build.windows.amd64 /build.sh
-VOLUME ${ARTIFACT_DIR}/
+VOLUME ${SOURCE_DIR}/
-COPY . ${SOURCE_DIR}/
+VOLUME ${ARTIFACT_DIR}/
-ENTRYPOINT ["/docker-build.sh"]
+ENTRYPOINT ["/build.sh"]
diff --git a/deployment/README.md b/deployment/README.md
deleted file mode 100644
index a805f59ca..000000000
--- a/deployment/README.md
+++ /dev/null
@@ -1,62 +0,0 @@
-# Jellyfin Packaging
-
-This directory contains the packaging configuration of Jellyfin for multiple platforms. The specification is below; all package platforms must follow the specification to be compatable with the central `build` script.
-
-## Package List
-
-### Operating System Packages
-
-* `debian-package-x64`: Package for Debian and Ubuntu amd64 systems.
-* `fedora-package-x64`: Package for Fedora, CentOS, and Red Hat Enterprise Linux amd64 systems.
-
-### Portable Builds (archives)
-
-* `linux-x64`: Portable binary archive for generic Linux amd64 systems.
-* `macos`: Portable binary archive for MacOS amd64 systems.
-* `win-x64`: Portable binary archive for Windows amd64 systems.
-* `win-x86`: Portable binary archive for Windows i386 systems.
-
-### Other Builds
-
-These builds are not necessarily run from the `build` script, but are present for other platforms.
-
-* `portable`: Compiled `.dll` for use with .NET Core runtime on any system.
-* `docker`: Docker manifests for auto-publishing.
-* `unraid`: unRaid Docker template; not built by `build` but imported into unRaid directly.
-* `windows`: Support files and scripts for Windows CI build.
-
-## Package Specification
-
-### Dependencies
-
-* If a platform requires additional build dependencies, the required binary names, i.e. to validate `which <binary>`, should be specified in a `dependencies.txt` file inside the platform directory.
-
-* Each dependency should be present on its own line.
-
-### Action Scripts
-
-* Actions are defined in BASH scripts with the name `<action>.sh` within the platform directory.
-
-* The list of valid actions are:
-
- 1. `build`: Builds a set of binaries.
- 2. `package`: Assembles the compiled binaries into a package.
- 3. `sign`: Performs signing actions on a package.
- 4. `publish`: Performs a publishing action for a package.
- 5. `clean`: Cleans up any artifacts from the previous actions.
-
-* All package actions are optional, however at least one should generate output files, and any that do should contain a `clean` action.
-
-* Actions are executed in the order specified above, and later actions may depend on former actions.
-
-* Actions except for `clean` should `set -o errexit` to terminate on failed actions.
-
-* The `clean` action should always `exit 0` even if no work is done or it fails.
-
-* The `clean` action can be passed a variable as argument 1, named `keep_artifacts`, containing either the value `y` or `n`. It is indended to handle situations when the user runs `build --keep-artifacts` and should be handled intelligently. Usually, this is used to preserve Docker images while still removing temporary directories.
-
-### Output Files
-
-* Upon completion of the defined actions, at least one output file must be created in the `<platform>/pkg-dist` directory.
-
-* Output files will be moved to the directory `jellyfin-build/<platform>` one directory above the repository root upon completion.
diff --git a/deployment/build.centos.amd64 b/deployment/build.centos.amd64
new file mode 100755
index 000000000..939bbc45a
--- /dev/null
+++ b/deployment/build.centos.amd64
@@ -0,0 +1,24 @@
+#!/bin/bash
+
+#= CentOS/RHEL 7+ amd64 .rpm
+
+set -o errexit
+set -o xtrace
+
+# Move to source directory
+pushd ${SOURCE_DIR}
+
+# Build RPM
+make -f fedora/Makefile srpm outdir=/root/rpmbuild/SRPMS
+rpmbuild --rebuild -bb /root/rpmbuild/SRPMS/jellyfin-*.src.rpm
+
+# Move the artifacts out
+mv /root/rpmbuild/RPMS/x86_64/jellyfin-*.rpm /root/rpmbuild/SRPMS/jellyfin-*.src.rpm ${ARTIFACT_DIR}/
+
+if [[ ${IS_DOCKER} == YES ]]; then
+ chown -Rc $(stat -c %u:%g ${ARTIFACT_DIR}) ${ARTIFACT_DIR}
+fi
+
+rm -f fedora/jellyfin*.tar.gz
+
+popd
diff --git a/deployment/build.debian.amd64 b/deployment/build.debian.amd64
new file mode 100755
index 000000000..f44c6a7d1
--- /dev/null
+++ b/deployment/build.debian.amd64
@@ -0,0 +1,28 @@
+#!/bin/bash
+
+#= Debian 10+ amd64 .deb
+
+set -o errexit
+set -o xtrace
+
+# Move to source directory
+pushd ${SOURCE_DIR}
+
+if [[ ${IS_DOCKER} == YES ]]; then
+ # Remove build-dep for dotnet-sdk-3.1, since it's installed manually
+ cp -a debian/control /tmp/control.orig
+ sed -i '/dotnet-sdk-3.1,/d' debian/control
+fi
+
+# Build DEB
+dpkg-buildpackage -us -uc --pre-clean --post-clean
+
+mkdir -p ${ARTIFACT_DIR}/
+mv ../jellyfin*.{deb,dsc,tar.gz,buildinfo,changes} ${ARTIFACT_DIR}/
+
+if [[ ${IS_DOCKER} == YES ]]; then
+ cp -a /tmp/control.orig debian/control
+ chown -Rc $(stat -c %u:%g ${ARTIFACT_DIR}) ${ARTIFACT_DIR}
+fi
+
+popd
diff --git a/deployment/build.debian.arm64 b/deployment/build.debian.arm64
new file mode 100755
index 000000000..0127671f3
--- /dev/null
+++ b/deployment/build.debian.arm64
@@ -0,0 +1,29 @@
+#!/bin/bash
+
+#= Debian 10+ arm64 .deb
+
+set -o errexit
+set -o xtrace
+
+# Move to source directory
+pushd ${SOURCE_DIR}
+
+if [[ ${IS_DOCKER} == YES ]]; then
+ # Remove build-dep for dotnet-sdk-3.1, since it's installed manually
+ cp -a debian/control /tmp/control.orig
+ sed -i '/dotnet-sdk-3.1,/d' debian/control
+fi
+
+# Build DEB
+export CONFIG_SITE=/etc/dpkg-cross/cross-config.${ARCH}
+dpkg-buildpackage -us -uc -a arm64 --pre-clean --post-clean
+
+mkdir -p ${ARTIFACT_DIR}/
+mv ../jellyfin*.{deb,dsc,tar.gz,buildinfo,changes} ${ARTIFACT_DIR}/
+
+if [[ ${IS_DOCKER} == YES ]]; then
+ cp -a /tmp/control.orig debian/control
+ chown -Rc $(stat -c %u:%g ${ARTIFACT_DIR}) ${ARTIFACT_DIR}
+fi
+
+popd
diff --git a/deployment/build.debian.armhf b/deployment/build.debian.armhf
new file mode 100755
index 000000000..02e3db4fc
--- /dev/null
+++ b/deployment/build.debian.armhf
@@ -0,0 +1,29 @@
+#!/bin/bash
+
+#= Debian 10+ arm64 .deb
+
+set -o errexit
+set -o xtrace
+
+# Move to source directory
+pushd ${SOURCE_DIR}
+
+if [[ ${IS_DOCKER} == YES ]]; then
+ # Remove build-dep for dotnet-sdk-3.1, since it's installed manually
+ cp -a debian/control /tmp/control.orig
+ sed -i '/dotnet-sdk-3.1,/d' debian/control
+fi
+
+# Build DEB
+export CONFIG_SITE=/etc/dpkg-cross/cross-config.${ARCH}
+dpkg-buildpackage -us -uc -a armhf --pre-clean --post-clean
+
+mkdir -p ${ARTIFACT_DIR}/
+mv ../jellyfin*.{deb,dsc,tar.gz,buildinfo,changes} ${ARTIFACT_DIR}/
+
+if [[ ${IS_DOCKER} == YES ]]; then
+ cp -a /tmp/control.orig debian/control
+ chown -Rc $(stat -c %u:%g ${ARTIFACT_DIR}) ${ARTIFACT_DIR}
+fi
+
+popd
diff --git a/deployment/build.fedora.amd64 b/deployment/build.fedora.amd64
new file mode 100755
index 000000000..8ac99decc
--- /dev/null
+++ b/deployment/build.fedora.amd64
@@ -0,0 +1,24 @@
+#!/bin/bash
+
+#= Fedora 29+ amd64 .rpm
+
+set -o errexit
+set -o xtrace
+
+# Move to source directory
+pushd ${SOURCE_DIR}
+
+# Build RPM
+make -f fedora/Makefile srpm outdir=/root/rpmbuild/SRPMS
+rpmbuild -rb /root/rpmbuild/SRPMS/jellyfin-*.src.rpm
+
+# Move the artifacts out
+mv /root/rpmbuild/RPMS/x86_64/jellyfin-*.rpm /root/rpmbuild/SRPMS/jellyfin-*.src.rpm ${ARTIFACT_DIR}/
+
+if [[ ${IS_DOCKER} == YES ]]; then
+ chown -Rc $(stat -c %u:%g ${ARTIFACT_DIR}) ${ARTIFACT_DIR}
+fi
+
+rm -f fedora/jellyfin*.tar.gz
+
+popd
diff --git a/deployment/build.linux.amd64 b/deployment/build.linux.amd64
new file mode 100755
index 000000000..0cbbd05cf
--- /dev/null
+++ b/deployment/build.linux.amd64
@@ -0,0 +1,27 @@
+#!/bin/bash
+
+#= Generic Linux amd64 .tar.gz
+
+set -o errexit
+set -o xtrace
+
+# Move to source directory
+pushd ${SOURCE_DIR}
+
+# Get version
+version="$( grep "version:" ./build.yaml | sed -E 's/version: "([0-9\.]+.*)"/\1/' )"
+
+# Build archives
+dotnet publish Jellyfin.Server --configuration Release --self-contained --runtime linux-x64 --output dist/jellyfin-server_${version}/ "-p:GenerateDocumentationFile=false;DebugSymbols=false;DebugType=none;UseAppHost=true"
+tar -czf jellyfin-server_${version}_linux-amd64.tar.gz -C dist jellyfin-server_${version}
+rm -rf dist/jellyfin-server_${version}
+
+# Move the artifacts out
+mkdir -p ${ARTIFACT_DIR}/
+mv jellyfin[-_]*.tar.gz ${ARTIFACT_DIR}/
+
+if [[ ${IS_DOCKER} == YES ]]; then
+ chown -Rc $(stat -c %u:%g ${ARTIFACT_DIR}) ${ARTIFACT_DIR}
+fi
+
+popd
diff --git a/deployment/build.macos b/deployment/build.macos
new file mode 100755
index 000000000..16be29eee
--- /dev/null
+++ b/deployment/build.macos
@@ -0,0 +1,27 @@
+#!/bin/bash
+
+#= MacOS 10.13+ .tar.gz
+
+set -o errexit
+set -o xtrace
+
+# Move to source directory
+pushd ${SOURCE_DIR}
+
+# Get version
+version="$( grep "version:" ./build.yaml | sed -E 's/version: "([0-9\.]+.*)"/\1/' )"
+
+# Build archives
+dotnet publish Jellyfin.Server --configuration Release --self-contained --runtime osx-x64 --output dist/jellyfin-server_${version}/ "-p:GenerateDocumentationFile=false;DebugSymbols=false;DebugType=none;UseAppHost=true"
+tar -czf jellyfin-server_${version}_macos-amd64.tar.gz -C dist jellyfin-server_${version}
+rm -rf dist/jellyfin-server_${version}
+
+# Move the artifacts out
+mkdir -p ${ARTIFACT_DIR}/
+mv jellyfin[-_]*.tar.gz ${ARTIFACT_DIR}/
+
+if [[ ${IS_DOCKER} == YES ]]; then
+ chown -Rc $(stat -c %u:%g ${ARTIFACT_DIR}) ${ARTIFACT_DIR}
+fi
+
+popd
diff --git a/deployment/build.portable b/deployment/build.portable
new file mode 100755
index 000000000..1e8a4ab62
--- /dev/null
+++ b/deployment/build.portable
@@ -0,0 +1,27 @@
+#!/bin/bash
+
+#= Portable .NET DLL .tar.gz
+
+set -o errexit
+set -o xtrace
+
+# Move to source directory
+pushd ${SOURCE_DIR}
+
+# Get version
+version="$( grep "version:" ./build.yaml | sed -E 's/version: "([0-9\.]+.*)"/\1/' )"
+
+# Build archives
+dotnet publish Jellyfin.Server --configuration Release --output dist/jellyfin-server_${version}/ "-p:GenerateDocumentationFile=false;DebugSymbols=false;DebugType=none;UseAppHost=true"
+tar -czf jellyfin-server_${version}_portable.tar.gz -C dist jellyfin-server_${version}
+rm -rf dist/jellyfin-server_${version}
+
+# Move the artifacts out
+mkdir -p ${ARTIFACT_DIR}/
+mv jellyfin[-_]*.tar.gz ${ARTIFACT_DIR}/
+
+if [[ ${IS_DOCKER} == YES ]]; then
+ chown -Rc $(stat -c %u:%g ${ARTIFACT_DIR}) ${ARTIFACT_DIR}
+fi
+
+popd
diff --git a/deployment/build.ubuntu.amd64 b/deployment/build.ubuntu.amd64
new file mode 100755
index 000000000..107ddbe02
--- /dev/null
+++ b/deployment/build.ubuntu.amd64
@@ -0,0 +1,28 @@
+#!/bin/bash
+
+#= Ubuntu 18.04+ amd64 .deb
+
+set -o errexit
+set -o xtrace
+
+# Move to source directory
+pushd ${SOURCE_DIR}
+
+if [[ ${IS_DOCKER} == YES ]]; then
+ # Remove build-dep for dotnet-sdk-3.1, since it's installed manually
+ cp -a debian/control /tmp/control.orig
+ sed -i '/dotnet-sdk-3.1,/d' debian/control
+fi
+
+# Build DEB
+dpkg-buildpackage -us -uc --pre-clean --post-clean
+
+mkdir -p ${ARTIFACT_DIR}/
+mv ../jellyfin*.{deb,dsc,tar.gz,buildinfo,changes} ${ARTIFACT_DIR}/
+
+if [[ ${IS_DOCKER} == YES ]]; then
+ cp -a /tmp/control.orig debian/control
+ chown -Rc $(stat -c %u:%g ${ARTIFACT_DIR}) ${ARTIFACT_DIR}
+fi
+
+popd
diff --git a/deployment/build.ubuntu.arm64 b/deployment/build.ubuntu.arm64
new file mode 100755
index 000000000..b13868f44
--- /dev/null
+++ b/deployment/build.ubuntu.arm64
@@ -0,0 +1,29 @@
+#!/bin/bash
+
+#= Ubuntu 18.04+ arm64 .deb
+
+set -o errexit
+set -o xtrace
+
+# Move to source directory
+pushd ${SOURCE_DIR}
+
+if [[ ${IS_DOCKER} == YES ]]; then
+ # Remove build-dep for dotnet-sdk-3.1, since it's installed manually
+ cp -a debian/control /tmp/control.orig
+ sed -i '/dotnet-sdk-3.1,/d' debian/control
+fi
+
+# Build DEB
+export CONFIG_SITE=/etc/dpkg-cross/cross-config.${ARCH}
+dpkg-buildpackage -us -uc -a arm64 --pre-clean --post-clean
+
+mkdir -p ${ARTIFACT_DIR}/
+mv ../jellyfin*.{deb,dsc,tar.gz,buildinfo,changes} ${ARTIFACT_DIR}/
+
+if [[ ${IS_DOCKER} == YES ]]; then
+ cp -a /tmp/control.orig debian/control
+ chown -Rc $(stat -c %u:%g ${ARTIFACT_DIR}) ${ARTIFACT_DIR}
+fi
+
+popd
diff --git a/deployment/build.ubuntu.armhf b/deployment/build.ubuntu.armhf
new file mode 100755
index 000000000..0b4dd308a
--- /dev/null
+++ b/deployment/build.ubuntu.armhf
@@ -0,0 +1,29 @@
+#!/bin/bash
+
+#= Ubuntu 18.04+ arm64 .deb
+
+set -o errexit
+set -o xtrace
+
+# Move to source directory
+pushd ${SOURCE_DIR}
+
+if [[ ${IS_DOCKER} == YES ]]; then
+ # Remove build-dep for dotnet-sdk-3.1, since it's installed manually
+ cp -a debian/control /tmp/control.orig
+ sed -i '/dotnet-sdk-3.1,/d' debian/control
+fi
+
+# Build DEB
+export CONFIG_SITE=/etc/dpkg-cross/cross-config.${ARCH}
+dpkg-buildpackage -us -uc -a armhf --pre-clean --post-clean
+
+mkdir -p ${ARTIFACT_DIR}/
+mv ../jellyfin*.{deb,dsc,tar.gz,buildinfo,changes} ${ARTIFACT_DIR}/
+
+if [[ ${IS_DOCKER} == YES ]]; then
+ cp -a /tmp/control.orig debian/control
+ chown -Rc $(stat -c %u:%g ${ARTIFACT_DIR}) ${ARTIFACT_DIR}
+fi
+
+popd
diff --git a/deployment/build.windows.amd64 b/deployment/build.windows.amd64
new file mode 100755
index 000000000..39bd41f99
--- /dev/null
+++ b/deployment/build.windows.amd64
@@ -0,0 +1,54 @@
+#!/bin/bash
+
+#= Windows 7+ amd64 (x64) .zip
+
+set -o errexit
+set -o xtrace
+
+# Version variables
+NSSM_VERSION="nssm-2.24-101-g897c7ad"
+NSSM_URL="http://files.evilt.win/nssm/${NSSM_VERSION}.zip"
+FFMPEG_VERSION="ffmpeg-4.2.1-win64-static"
+FFMPEG_URL="https://ffmpeg.zeranoe.com/builds/win64/static/${FFMPEG_VERSION}.zip"
+
+# Move to source directory
+pushd ${SOURCE_DIR}
+
+# Get version
+version="$( grep "version:" ./build.yaml | sed -E 's/version: "([0-9\.]+.*)"/\1/' )"
+
+output_dir="dist/jellyfin-server_${version}"
+
+# Build binary
+dotnet publish Jellyfin.Server --configuration Release --self-contained --runtime win-x64 --output ${output_dir}/ "-p:GenerateDocumentationFile=false;DebugSymbols=false;DebugType=none;UseAppHost=true"
+
+# Prepare addins
+addin_build_dir="$( mktemp -d )"
+wget ${NSSM_URL} -O ${addin_build_dir}/nssm.zip
+wget ${FFMPEG_URL} -O ${addin_build_dir}/ffmpeg.zip
+unzip ${addin_build_dir}/nssm.zip -d ${addin_build_dir}
+cp ${addin_build_dir}/${NSSM_VERSION}/win64/nssm.exe ${output_dir}/nssm.exe
+unzip ${addin_build_dir}/ffmpeg.zip -d ${addin_build_dir}
+cp ${addin_build_dir}/${FFMPEG_VERSION}/bin/ffmpeg.exe ${output_dir}/ffmpeg.exe
+cp ${addin_build_dir}/${FFMPEG_VERSION}/bin/ffprobe.exe ${output_dir}/ffprobe.exe
+rm -rf ${addin_build_dir}
+
+# Prepare scripts
+cp ${SOURCE_DIR}/windows/legacy/install-jellyfin.ps1 ${output_dir}/install-jellyfin.ps1
+cp ${SOURCE_DIR}/windows/legacy/install.bat ${output_dir}/install.bat
+
+# Create zip package
+pushd dist
+zip -qr jellyfin-server_${version}.portable.zip jellyfin-server_${version}
+popd
+rm -rf ${output_dir}
+
+# Move the artifacts out
+mkdir -p ${ARTIFACT_DIR}/
+mv dist/jellyfin[-_]*.zip ${ARTIFACT_DIR}/
+
+if [[ ${IS_DOCKER} == YES ]]; then
+ chown -Rc $(stat -c %u:%g ${ARTIFACT_DIR}) ${ARTIFACT_DIR}
+fi
+
+popd
diff --git a/deployment/centos-package-x64/Dockerfile b/deployment/centos-package-x64/Dockerfile
deleted file mode 100644
index 2b346f46a..000000000
--- a/deployment/centos-package-x64/Dockerfile
+++ /dev/null
@@ -1,39 +0,0 @@
-FROM centos:7
-# Docker build arguments
-ARG SOURCE_DIR=/jellyfin
-ARG PLATFORM_DIR=/jellyfin/deployment/centos-package-x64
-ARG ARTIFACT_DIR=/dist
-ARG SDK_VERSION=3.1
-# Docker run environment
-ENV SOURCE_DIR=/jellyfin
-ENV ARTIFACT_DIR=/dist
-
-# Prepare CentOS environment
-RUN yum update -y \
- && yum install -y epel-release
-
-# Install build dependencies
-RUN yum install -y @buildsys-build rpmdevtools yum-plugins-core libcurl-devel fontconfig-devel freetype-devel openssl-devel glibc-devel libicu-devel git
-
-# Install recent NodeJS and Yarn
-RUN curl -fSsLo /etc/yum.repos.d/yarn.repo https://dl.yarnpkg.com/rpm/yarn.repo \
- && rpm -i https://rpm.nodesource.com/pub_8.x/el/7/x86_64/nodesource-release-el7-1.noarch.rpm \
- && yum install -y yarn
-
-# Install DotNET SDK
-RUN rpm -Uvh https://packages.microsoft.com/config/rhel/7/packages-microsoft-prod.rpm \
- && rpmdev-setuptree \
- && yum install -y dotnet-sdk-${SDK_VERSION}
-
-# Create symlinks and directories
-RUN ln -sf ${PLATFORM_DIR}/docker-build.sh /docker-build.sh \
- && mkdir -p ${SOURCE_DIR}/SPECS \
- && ln -s ${PLATFORM_DIR}/pkg-src/jellyfin.spec ${SOURCE_DIR}/SPECS/jellyfin.spec \
- && mkdir -p ${SOURCE_DIR}/SOURCES \
- && ln -s ${PLATFORM_DIR}/pkg-src ${SOURCE_DIR}/SOURCES
-
-VOLUME ${ARTIFACT_DIR}/
-
-COPY . ${SOURCE_DIR}/
-
-ENTRYPOINT ["/docker-build.sh"]
diff --git a/deployment/centos-package-x64/clean.sh b/deployment/centos-package-x64/clean.sh
deleted file mode 100755
index 31455de0d..000000000
--- a/deployment/centos-package-x64/clean.sh
+++ /dev/null
@@ -1,32 +0,0 @@
-#!/usr/bin/env bash
-
-keep_artifacts="${1}"
-
-WORKDIR="$( pwd )"
-VERSION="$( grep -A1 '^Version:' ${WORKDIR}/pkg-src/jellyfin.spec | awk '{ print $NF }' )"
-
-package_temporary_dir="${WORKDIR}/pkg-dist-tmp"
-package_source_dir="${WORKDIR}/pkg-src"
-output_dir="${WORKDIR}/pkg-dist"
-current_user="$( whoami )"
-image_name="jellyfin-centos-build"
-
-rm -f "${package_source_dir}/jellyfin-${VERSION}.tar.gz" &>/dev/null \
- || sudo rm -f "${package_source_dir}/jellyfin-${VERSION}.tar.gz" &>/dev/null
-
-rm -rf "${package_temporary_dir}" &>/dev/null \
- || sudo rm -rf "${package_temporary_dir}" &>/dev/null
-
-rm -rf "${output_dir}" &>/dev/null \
- || sudo rm -rf "${output_dir}" &>/dev/null
-
-if [[ ${keep_artifacts} == 'n' ]]; then
- docker_sudo=""
- if [[ ! -z $(id -Gn | grep -q 'docker') ]] \
- && [[ ! ${EUID:-1000} -eq 0 ]] \
- && [[ ! ${USER} == "root" ]] \
- && [[ ! -z $( echo "${OSTYPE}" | grep -q "darwin" ) ]]; then
- docker_sudo=sudo
- fi
- ${docker_sudo} docker image rm ${image_name} --force
-fi
diff --git a/deployment/centos-package-x64/dependencies.txt b/deployment/centos-package-x64/dependencies.txt
deleted file mode 100644
index bdb967096..000000000
--- a/deployment/centos-package-x64/dependencies.txt
+++ /dev/null
@@ -1 +0,0 @@
-docker
diff --git a/deployment/centos-package-x64/docker-build.sh b/deployment/centos-package-x64/docker-build.sh
deleted file mode 100755
index 62dd144e5..000000000
--- a/deployment/centos-package-x64/docker-build.sh
+++ /dev/null
@@ -1,18 +0,0 @@
-#!/bin/bash
-
-# Builds the RPM inside the Docker container
-
-set -o errexit
-set -o xtrace
-
-# Move to source directory
-pushd ${SOURCE_DIR}
-
-# Build RPM
-make -f .copr/Makefile srpm outdir=/root/rpmbuild/SRPMS
-rpmbuild --rebuild -bb /root/rpmbuild/SRPMS/jellyfin-*.src.rpm
-
-# Move the artifacts out
-mkdir -p ${ARTIFACT_DIR}/rpm
-mv /root/rpmbuild/RPMS/x86_64/jellyfin-*.rpm /root/rpmbuild/SRPMS/jellyfin-*.src.rpm ${ARTIFACT_DIR}/rpm/
-chown -Rc $(stat -c %u:%g ${ARTIFACT_DIR}) ${ARTIFACT_DIR}
diff --git a/deployment/centos-package-x64/package.sh b/deployment/centos-package-x64/package.sh
deleted file mode 100755
index 1b983f49d..000000000
--- a/deployment/centos-package-x64/package.sh
+++ /dev/null
@@ -1,34 +0,0 @@
-#!/usr/bin/env bash
-
-args="${@}"
-declare -a docker_envvars
-for arg in ${args}; do
- docker_envvars+=("-e ${arg}")
-done
-
-WORKDIR="$( pwd )"
-
-package_temporary_dir="${WORKDIR}/pkg-dist-tmp"
-output_dir="${WORKDIR}/pkg-dist"
-current_user="$( whoami )"
-image_name="jellyfin-centos-build"
-
-# Determine if sudo should be used for Docker
-if [[ ! -z $(id -Gn | grep -q 'docker') ]] \
- && [[ ! ${EUID:-1000} -eq 0 ]] \
- && [[ ! ${USER} == "root" ]] \
- && [[ ! -z $( echo "${OSTYPE}" | grep -q "darwin" ) ]]; then
- docker_sudo="sudo"
-else
- docker_sudo=""
-fi
-
-# Prepare temporary package dir
-mkdir -p "${package_temporary_dir}"
-# Set up the build environment Docker image
-${docker_sudo} docker build ../.. -t "${image_name}" -f ./Dockerfile
-# Build the RPMs and copy out to ${package_temporary_dir}
-${docker_sudo} docker run --rm -v "${package_temporary_dir}:/dist" "${image_name}" ${docker_envvars}
-# Move the RPMs to the output directory
-mkdir -p "${output_dir}"
-mv "${package_temporary_dir}"/rpm/* "${output_dir}"
diff --git a/deployment/centos-package-x64/pkg-src b/deployment/centos-package-x64/pkg-src
deleted file mode 120000
index 3ff4d3cbf..000000000
--- a/deployment/centos-package-x64/pkg-src
+++ /dev/null
@@ -1 +0,0 @@
-../fedora-package-x64/pkg-src/ \ No newline at end of file
diff --git a/deployment/debian-package-arm64/Dockerfile.arm64 b/deployment/debian-package-arm64/Dockerfile.arm64
deleted file mode 100644
index 9ca486844..000000000
--- a/deployment/debian-package-arm64/Dockerfile.arm64
+++ /dev/null
@@ -1,34 +0,0 @@
-FROM debian:10
-# Docker build arguments
-ARG SOURCE_DIR=/jellyfin
-ARG PLATFORM_DIR=/jellyfin/deployment/debian-package-arm64
-ARG ARTIFACT_DIR=/dist
-ARG SDK_VERSION=3.1
-# Docker run environment
-ENV SOURCE_DIR=/jellyfin
-ENV ARTIFACT_DIR=/dist
-ENV DEB_BUILD_OPTIONS=noddebs
-ENV ARCH=arm64
-
-# Prepare Debian build environment
-RUN apt-get update \
- && apt-get install -y apt-transport-https debhelper gnupg wget npm devscripts mmv libc6-dev libcurl4-openssl-dev libfontconfig1-dev libfreetype6-dev libssl-dev liblttng-ust0
-
-# Install dotnet repository
-# https://dotnet.microsoft.com/download/linux-package-manager/debian9/sdk-current
-RUN wget https://download.visualstudio.microsoft.com/download/pr/5a4c8f96-1c73-401c-a6de-8e100403188a/0ce6ab39747e2508366d498f9c0a0669/dotnet-sdk-3.1.100-linux-arm64.tar.gz -O dotnet-sdk.tar.gz \
- && mkdir -p dotnet-sdk \
- && tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \
- && ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet
-
-# Link to docker-build script
-RUN ln -sf ${PLATFORM_DIR}/docker-build.sh /docker-build.sh
-
-# Link to Debian source dir; mkdir needed or it fails, can't force dest
-RUN mkdir -p ${SOURCE_DIR} && ln -sf ${PLATFORM_DIR}/pkg-src ${SOURCE_DIR}/debian
-
-VOLUME ${ARTIFACT_DIR}/
-
-COPY . ${SOURCE_DIR}/
-
-ENTRYPOINT ["/docker-build.sh"]
diff --git a/deployment/debian-package-arm64/clean.sh b/deployment/debian-package-arm64/clean.sh
deleted file mode 100755
index e7bfdf8b4..000000000
--- a/deployment/debian-package-arm64/clean.sh
+++ /dev/null
@@ -1,27 +0,0 @@
-#!/usr/bin/env bash
-
-keep_artifacts="${1}"
-
-WORKDIR="$( pwd )"
-
-package_temporary_dir="${WORKDIR}/pkg-dist-tmp"
-output_dir="${WORKDIR}/pkg-dist"
-current_user="$( whoami )"
-image_name="jellyfin-debian_arm64-build"
-
-rm -rf "${package_temporary_dir}" &>/dev/null \
- || sudo rm -rf "${package_temporary_dir}" &>/dev/null
-
-rm -rf "${output_dir}" &>/dev/null \
- || sudo rm -rf "${output_dir}" &>/dev/null
-
-if [[ ${keep_artifacts} == 'n' ]]; then
- docker_sudo=""
- if [[ ! -z $(id -Gn | grep -q 'docker') ]] \
- && [[ ! ${EUID:-1000} -eq 0 ]] \
- && [[ ! ${USER} == "root" ]] \
- && [[ ! -z $( echo "${OSTYPE}" | grep -q "darwin" ) ]]; then
- docker_sudo=sudo
- fi
- ${docker_sudo} docker image rm ${image_name} --force
-fi
diff --git a/deployment/debian-package-arm64/dependencies.txt b/deployment/debian-package-arm64/dependencies.txt
deleted file mode 100644
index bdb967096..000000000
--- a/deployment/debian-package-arm64/dependencies.txt
+++ /dev/null
@@ -1 +0,0 @@
-docker
diff --git a/deployment/debian-package-arm64/docker-build.sh b/deployment/debian-package-arm64/docker-build.sh
deleted file mode 100755
index 67ab6bd74..000000000
--- a/deployment/debian-package-arm64/docker-build.sh
+++ /dev/null
@@ -1,21 +0,0 @@
-#!/bin/bash
-
-# Builds the DEB inside the Docker container
-
-set -o errexit
-set -o xtrace
-
-# Move to source directory
-pushd ${SOURCE_DIR}
-
-# Remove build-dep for dotnet-sdk-3.1, since it's not a package in this image
-sed -i '/dotnet-sdk-3.1,/d' debian/control
-
-# Build DEB
-export CONFIG_SITE=/etc/dpkg-cross/cross-config.${ARCH}
-dpkg-buildpackage -us -uc -aarm64
-
-# Move the artifacts out
-mkdir -p ${ARTIFACT_DIR}/deb
-mv /jellyfin[-_]* ${ARTIFACT_DIR}/deb/
-chown -Rc $(stat -c %u:%g ${ARTIFACT_DIR}) ${ARTIFACT_DIR}
diff --git a/deployment/debian-package-arm64/package.sh b/deployment/debian-package-arm64/package.sh
deleted file mode 100755
index 209198218..000000000
--- a/deployment/debian-package-arm64/package.sh
+++ /dev/null
@@ -1,45 +0,0 @@
-#!/usr/bin/env bash
-
-args="${@}"
-declare -a docker_envvars
-for arg in ${args}; do
- docker_envvars+=("-e ${arg}")
-done
-
-ARCH="$( arch )"
-WORKDIR="$( pwd )"
-
-package_temporary_dir="${WORKDIR}/pkg-dist-tmp"
-output_dir="${WORKDIR}/pkg-dist"
-current_user="$( whoami )"
-image_name="jellyfin-debian_arm64-build"
-
-# Determine if sudo should be used for Docker
-if [[ ! -z $(id -Gn | grep -q 'docker') ]] \
- && [[ ! ${EUID:-1000} -eq 0 ]] \
- && [[ ! ${USER} == "root" ]] \
- && [[ ! -z $( echo "${OSTYPE}" | grep -q "darwin" ) ]]; then
- docker_sudo="sudo"
-else
- docker_sudo=""
-fi
-
-# Determine which Dockerfile to use
-case $ARCH in
- 'x86_64')
- DOCKERFILE="Dockerfile.amd64"
- ;;
- 'armv7l')
- DOCKERFILE="Dockerfile.arm64"
- ;;
-esac
-
-# Prepare temporary package dir
-mkdir -p "${package_temporary_dir}"
-# Set up the build environment Docker image
-${docker_sudo} docker build ../.. -t "${image_name}" -f ./${DOCKERFILE}
-# Build the DEBs and copy out to ${package_temporary_dir}
-${docker_sudo} docker run --rm -v "${package_temporary_dir}:/dist" "${image_name}" ${docker_envvars}
-# Move the DEBs to the output directory
-mkdir -p "${output_dir}"
-mv "${package_temporary_dir}"/deb/* "${output_dir}"
diff --git a/deployment/debian-package-arm64/pkg-src b/deployment/debian-package-arm64/pkg-src
deleted file mode 120000
index 4c695fea1..000000000
--- a/deployment/debian-package-arm64/pkg-src
+++ /dev/null
@@ -1 +0,0 @@
-../debian-package-x64/pkg-src \ No newline at end of file
diff --git a/deployment/debian-package-armhf/Dockerfile.armhf b/deployment/debian-package-armhf/Dockerfile.armhf
deleted file mode 100644
index dd398b5aa..000000000
--- a/deployment/debian-package-armhf/Dockerfile.armhf
+++ /dev/null
@@ -1,34 +0,0 @@
-FROM debian:10
-# Docker build arguments
-ARG SOURCE_DIR=/jellyfin
-ARG PLATFORM_DIR=/jellyfin/deployment/debian-package-armhf
-ARG ARTIFACT_DIR=/dist
-ARG SDK_VERSION=3.1
-# Docker run environment
-ENV SOURCE_DIR=/jellyfin
-ENV ARTIFACT_DIR=/dist
-ENV DEB_BUILD_OPTIONS=noddebs
-ENV ARCH=armhf
-
-# Prepare Debian build environment
-RUN apt-get update \
- && apt-get install -y apt-transport-https debhelper gnupg wget npm devscripts mmv libc6-dev libcurl4-openssl-dev libfontconfig1-dev libfreetype6-dev libssl-dev liblttng-ust0
-
-# Install dotnet repository
-# https://dotnet.microsoft.com/download/linux-package-manager/debian9/sdk-current
-RUN wget https://download.visualstudio.microsoft.com/download/pr/67766a96-eb8c-4cd2-bca4-ea63d2cc115c/7bf13840aa2ed88793b7315d5e0d74e6/dotnet-sdk-3.1.100-linux-arm.tar.gz -O dotnet-sdk.tar.gz \
- && mkdir -p dotnet-sdk \
- && tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \
- && ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet
-
-# Link to docker-build script
-RUN ln -sf ${PLATFORM_DIR}/docker-build.sh /docker-build.sh
-
-# Link to Debian source dir; mkdir needed or it fails, can't force dest
-RUN mkdir -p ${SOURCE_DIR} && ln -sf ${PLATFORM_DIR}/pkg-src ${SOURCE_DIR}/debian
-
-VOLUME ${ARTIFACT_DIR}/
-
-COPY . ${SOURCE_DIR}/
-
-ENTRYPOINT ["/docker-build.sh"]
diff --git a/deployment/debian-package-armhf/clean.sh b/deployment/debian-package-armhf/clean.sh
deleted file mode 100755
index 35a3d3e9a..000000000
--- a/deployment/debian-package-armhf/clean.sh
+++ /dev/null
@@ -1,27 +0,0 @@
-#!/usr/bin/env bash
-
-keep_artifacts="${1}"
-
-WORKDIR="$( pwd )"
-
-package_temporary_dir="${WORKDIR}/pkg-dist-tmp"
-output_dir="${WORKDIR}/pkg-dist"
-current_user="$( whoami )"
-image_name="jellyfin-debian_armhf-build"
-
-rm -rf "${package_temporary_dir}" &>/dev/null \
- || sudo rm -rf "${package_temporary_dir}" &>/dev/null
-
-rm -rf "${output_dir}" &>/dev/null \
- || sudo rm -rf "${output_dir}" &>/dev/null
-
-if [[ ${keep_artifacts} == 'n' ]]; then
- docker_sudo=""
- if [[ ! -z $(id -Gn | grep -q 'docker') ]] \
- && [[ ! ${EUID:-1000} -eq 0 ]] \
- && [[ ! ${USER} == "root" ]] \
- && [[ ! -z $( echo "${OSTYPE}" | grep -q "darwin" ) ]]; then
- docker_sudo=sudo
- fi
- ${docker_sudo} docker image rm ${image_name} --force
-fi
diff --git a/deployment/debian-package-armhf/dependencies.txt b/deployment/debian-package-armhf/dependencies.txt
deleted file mode 100644
index bdb967096..000000000
--- a/deployment/debian-package-armhf/dependencies.txt
+++ /dev/null
@@ -1 +0,0 @@
-docker
diff --git a/deployment/debian-package-armhf/docker-build.sh b/deployment/debian-package-armhf/docker-build.sh
deleted file mode 100755
index 1bd7fb291..000000000
--- a/deployment/debian-package-armhf/docker-build.sh
+++ /dev/null
@@ -1,21 +0,0 @@
-#!/bin/bash
-
-# Builds the DEB inside the Docker container
-
-set -o errexit
-set -o xtrace
-
-# Move to source directory
-pushd ${SOURCE_DIR}
-
-# Remove build-dep for dotnet-sdk-3.1, since it's not a package in this image
-sed -i '/dotnet-sdk-3.1,/d' debian/control
-
-# Build DEB
-export CONFIG_SITE=/etc/dpkg-cross/cross-config.${ARCH}
-dpkg-buildpackage -us -uc -aarmhf
-
-# Move the artifacts out
-mkdir -p ${ARTIFACT_DIR}/deb
-mv /jellyfin[-_]* ${ARTIFACT_DIR}/deb/
-chown -Rc $(stat -c %u:%g ${ARTIFACT_DIR}) ${ARTIFACT_DIR}
diff --git a/deployment/debian-package-armhf/package.sh b/deployment/debian-package-armhf/package.sh
deleted file mode 100755
index 4a27dd828..000000000
--- a/deployment/debian-package-armhf/package.sh
+++ /dev/null
@@ -1,45 +0,0 @@
-#!/usr/bin/env bash
-
-args="${@}"
-declare -a docker_envvars
-for arg in ${args}; do
- docker_envvars+=("-e ${arg}")
-done
-
-ARCH="$( arch )"
-WORKDIR="$( pwd )"
-
-package_temporary_dir="${WORKDIR}/pkg-dist-tmp"
-output_dir="${WORKDIR}/pkg-dist"
-current_user="$( whoami )"
-image_name="jellyfin-debian_armhf-build"
-
-# Determine if sudo should be used for Docker
-if [[ ! -z $(id -Gn | grep -q 'docker') ]] \
- && [[ ! ${EUID:-1000} -eq 0 ]] \
- && [[ ! ${USER} == "root" ]] \
- && [[ ! -z $( echo "${OSTYPE}" | grep -q "darwin" ) ]]; then
- docker_sudo="sudo"
-else
- docker_sudo=""
-fi
-
-# Determine which Dockerfile to use
-case $ARCH in
- 'x86_64')
- DOCKERFILE="Dockerfile.amd64"
- ;;
- 'armv7l')
- DOCKERFILE="Dockerfile.armhf"
- ;;
-esac
-
-# Prepare temporary package dir
-mkdir -p "${package_temporary_dir}"
-# Set up the build environment Docker image
-${docker_sudo} docker build ../.. -t "${image_name}" -f ./${DOCKERFILE}
-# Build the DEBs and copy out to ${package_temporary_dir}
-${docker_sudo} docker run --rm -v "${package_temporary_dir}:/dist" "${image_name}" ${docker_envvars}
-# Move the DEBs to the output directory
-mkdir -p "${output_dir}"
-mv "${package_temporary_dir}"/deb/* "${output_dir}"
diff --git a/deployment/debian-package-armhf/pkg-src b/deployment/debian-package-armhf/pkg-src
deleted file mode 120000
index 0bb6d5524..000000000
--- a/deployment/debian-package-armhf/pkg-src
+++ /dev/null
@@ -1 +0,0 @@
-../debian-package-x64/pkg-src/ \ No newline at end of file
diff --git a/deployment/debian-package-x64/Dockerfile b/deployment/debian-package-x64/Dockerfile
deleted file mode 100644
index e863d1edf..000000000
--- a/deployment/debian-package-x64/Dockerfile
+++ /dev/null
@@ -1,34 +0,0 @@
-FROM debian:10
-# Docker build arguments
-ARG SOURCE_DIR=/jellyfin
-ARG PLATFORM_DIR=/jellyfin/deployment/debian-package-x64
-ARG ARTIFACT_DIR=/dist
-ARG SDK_VERSION=3.1
-# Docker run environment
-ENV SOURCE_DIR=/jellyfin
-ENV ARTIFACT_DIR=/dist
-ENV DEB_BUILD_OPTIONS=noddebs
-ENV ARCH=amd64
-
-# Prepare Debian build environment
-RUN apt-get update \
- && apt-get install -y apt-transport-https debhelper gnupg wget npm devscripts mmv libc6-dev libcurl4-openssl-dev libfontconfig1-dev libfreetype6-dev libssl-dev libssl1.1 liblttng-ust0
-
-# Install dotnet repository
-# https://dotnet.microsoft.com/download/linux-package-manager/debian9/sdk-current
-RUN wget https://download.visualstudio.microsoft.com/download/pr/d731f991-8e68-4c7c-8ea0-fad5605b077a/49497b5420eecbd905158d86d738af64/dotnet-sdk-3.1.100-linux-x64.tar.gz -O dotnet-sdk.tar.gz \
- && mkdir -p dotnet-sdk \
- && tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \
- && ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet
-
-# Link to docker-build script
-RUN ln -sf ${PLATFORM_DIR}/docker-build.sh /docker-build.sh
-
-# Link to Debian source dir; mkdir needed or it fails, can't force dest
-RUN mkdir -p ${SOURCE_DIR} && ln -sf ${PLATFORM_DIR}/pkg-src ${SOURCE_DIR}/debian
-
-VOLUME ${ARTIFACT_DIR}/
-
-COPY . ${SOURCE_DIR}/
-
-ENTRYPOINT ["/docker-build.sh"]
diff --git a/deployment/debian-package-x64/clean.sh b/deployment/debian-package-x64/clean.sh
deleted file mode 100755
index 4e507bcb2..000000000
--- a/deployment/debian-package-x64/clean.sh
+++ /dev/null
@@ -1,27 +0,0 @@
-#!/usr/bin/env bash
-
-keep_artifacts="${1}"
-
-WORKDIR="$( pwd )"
-
-package_temporary_dir="${WORKDIR}/pkg-dist-tmp"
-output_dir="${WORKDIR}/pkg-dist"
-current_user="$( whoami )"
-image_name="jellyfin-debian-build"
-
-rm -rf "${package_temporary_dir}" &>/dev/null \
- || sudo rm -rf "${package_temporary_dir}" &>/dev/null
-
-rm -rf "${output_dir}" &>/dev/null \
- || sudo rm -rf "${output_dir}" &>/dev/null
-
-if [[ ${keep_artifacts} == 'n' ]]; then
- docker_sudo=""
- if [[ ! -z $(id -Gn | grep -q 'docker') ]] \
- && [[ ! ${EUID:-1000} -eq 0 ]] \
- && [[ ! ${USER} == "root" ]] \
- && [[ ! -z $( echo "${OSTYPE}" | grep -q "darwin" ) ]]; then
- docker_sudo=sudo
- fi
- ${docker_sudo} docker image rm ${image_name} --force
-fi
diff --git a/deployment/debian-package-x64/dependencies.txt b/deployment/debian-package-x64/dependencies.txt
deleted file mode 100644
index bdb967096..000000000
--- a/deployment/debian-package-x64/dependencies.txt
+++ /dev/null
@@ -1 +0,0 @@
-docker
diff --git a/deployment/debian-package-x64/docker-build.sh b/deployment/debian-package-x64/docker-build.sh
deleted file mode 100755
index 962a522eb..000000000
--- a/deployment/debian-package-x64/docker-build.sh
+++ /dev/null
@@ -1,20 +0,0 @@
-#!/bin/bash
-
-# Builds the DEB inside the Docker container
-
-set -o errexit
-set -o xtrace
-
-# Move to source directory
-pushd ${SOURCE_DIR}
-
-# Remove build-dep for dotnet-sdk-3.1, since it's not a package in this image
-sed -i '/dotnet-sdk-3.1,/d' debian/control
-
-# Build DEB
-dpkg-buildpackage -us -uc
-
-# Move the artifacts out
-mkdir -p ${ARTIFACT_DIR}/deb
-mv /jellyfin[-_]* ${ARTIFACT_DIR}/deb/
-chown -Rc $(stat -c %u:%g ${ARTIFACT_DIR}) ${ARTIFACT_DIR}
diff --git a/deployment/debian-package-x64/package.sh b/deployment/debian-package-x64/package.sh
deleted file mode 100755
index 5a416959a..000000000
--- a/deployment/debian-package-x64/package.sh
+++ /dev/null
@@ -1,34 +0,0 @@
-#!/usr/bin/env bash
-
-args="${@}"
-declare -a docker_envvars
-for arg in ${args}; do
- docker_envvars+=("-e ${arg}")
-done
-
-WORKDIR="$( pwd )"
-
-package_temporary_dir="${WORKDIR}/pkg-dist-tmp"
-output_dir="${WORKDIR}/pkg-dist"
-current_user="$( whoami )"
-image_name="jellyfin-debian-build"
-
-# Determine if sudo should be used for Docker
-if [[ ! -z $(id -Gn | grep -q 'docker') ]] \
- && [[ ! ${EUID:-1000} -eq 0 ]] \
- && [[ ! ${USER} == "root" ]] \
- && [[ ! -z $( echo "${OSTYPE}" | grep -q "darwin" ) ]]; then
- docker_sudo="sudo"
-else
- docker_sudo=""
-fi
-
-# Prepare temporary package dir
-mkdir -p "${package_temporary_dir}"
-# Set up the build environment Docker image
-${docker_sudo} docker build ../.. -t "${image_name}" -f ./Dockerfile
-# Build the DEBs and copy out to ${package_temporary_dir}
-${docker_sudo} docker run --rm -v "${package_temporary_dir}:/dist" "${image_name}" ${docker_envvars}
-# Move the DEBs to the output directory
-mkdir -p "${output_dir}"
-mv "${package_temporary_dir}"/deb/* "${output_dir}"
diff --git a/deployment/fedora-package-x64/Dockerfile b/deployment/fedora-package-x64/Dockerfile
deleted file mode 100644
index f5c3ab7a6..000000000
--- a/deployment/fedora-package-x64/Dockerfile
+++ /dev/null
@@ -1,33 +0,0 @@
-FROM fedora:29
-# Docker build arguments
-ARG SOURCE_DIR=/jellyfin
-ARG PLATFORM_DIR=/jellyfin/deployment/fedora-package-x64
-ARG ARTIFACT_DIR=/dist
-ARG SDK_VERSION=3.1
-# Docker run environment
-ENV SOURCE_DIR=/jellyfin
-ENV ARTIFACT_DIR=/dist
-
-# Prepare Fedora environment
-RUN dnf update -y
-
-# Install build dependencies
-RUN dnf install -y @buildsys-build rpmdevtools dnf-plugins-core libcurl-devel fontconfig-devel freetype-devel openssl-devel glibc-devel libicu-devel nodejs-yarn
-
-# Install DotNET SDK
-RUN dnf copr enable -y @dotnet-sig/dotnet \
- && rpmdev-setuptree \
- && dnf install -y dotnet-sdk-${SDK_VERSION} dotnet-runtime-${SDK_VERSION}
-
-# Create symlinks and directories
-RUN ln -sf ${PLATFORM_DIR}/docker-build.sh /docker-build.sh \
- && mkdir -p ${SOURCE_DIR}/SPECS \
- && ln -s ${PLATFORM_DIR}/pkg-src/jellyfin.spec ${SOURCE_DIR}/SPECS/jellyfin.spec \
- && mkdir -p ${SOURCE_DIR}/SOURCES \
- && ln -s ${PLATFORM_DIR}/pkg-src ${SOURCE_DIR}/SOURCES
-
-VOLUME ${ARTIFACT_DIR}/
-
-COPY . ${SOURCE_DIR}/
-
-ENTRYPOINT ["/docker-build.sh"]
diff --git a/deployment/fedora-package-x64/clean.sh b/deployment/fedora-package-x64/clean.sh
deleted file mode 100755
index 700c8f1bb..000000000
--- a/deployment/fedora-package-x64/clean.sh
+++ /dev/null
@@ -1,32 +0,0 @@
-#!/usr/bin/env bash
-
-keep_artifacts="${1}"
-
-WORKDIR="$( pwd )"
-VERSION="$( grep -A1 '^Version:' ${WORKDIR}/pkg-src/jellyfin.spec | awk '{ print $NF }' )"
-
-package_temporary_dir="${WORKDIR}/pkg-dist-tmp"
-package_source_dir="${WORKDIR}/pkg-src"
-output_dir="${WORKDIR}/pkg-dist"
-current_user="$( whoami )"
-image_name="jellyfin-fedora-build"
-
-rm -f "${package_source_dir}/jellyfin-${VERSION}.tar.gz" &>/dev/null \
- || sudo rm -f "${package_source_dir}/jellyfin-${VERSION}.tar.gz" &>/dev/null
-
-rm -rf "${package_temporary_dir}" &>/dev/null \
- || sudo rm -rf "${package_temporary_dir}" &>/dev/null
-
-rm -rf "${output_dir}" &>/dev/null \
- || sudo rm -rf "${output_dir}" &>/dev/null
-
-if [[ ${keep_artifacts} == 'n' ]]; then
- docker_sudo=""
- if [[ ! -z $(id -Gn | grep -q 'docker') ]] \
- && [[ ! ${EUID:-1000} -eq 0 ]] \
- && [[ ! ${USER} == "root" ]] \
- && [[ ! -z $( echo "${OSTYPE}" | grep -q "darwin" ) ]]; then
- docker_sudo=sudo
- fi
- ${docker_sudo} docker image rm ${image_name} --force
-fi
diff --git a/deployment/fedora-package-x64/dependencies.txt b/deployment/fedora-package-x64/dependencies.txt
deleted file mode 100644
index bdb967096..000000000
--- a/deployment/fedora-package-x64/dependencies.txt
+++ /dev/null
@@ -1 +0,0 @@
-docker
diff --git a/deployment/fedora-package-x64/docker-build.sh b/deployment/fedora-package-x64/docker-build.sh
deleted file mode 100755
index 740e8d35c..000000000
--- a/deployment/fedora-package-x64/docker-build.sh
+++ /dev/null
@@ -1,18 +0,0 @@
-#!/bin/bash
-
-# Builds the RPM inside the Docker container
-
-set -o errexit
-set -o xtrace
-
-# Move to source directory
-pushd ${SOURCE_DIR}
-
-# Build RPM
-make -f .copr/Makefile srpm outdir=/root/rpmbuild/SRPMS
-rpmbuild -rb /root/rpmbuild/SRPMS/jellyfin-*.src.rpm
-
-# Move the artifacts out
-mkdir -p ${ARTIFACT_DIR}/rpm
-mv /root/rpmbuild/RPMS/x86_64/jellyfin-*.rpm /root/rpmbuild/SRPMS/jellyfin-*.src.rpm ${ARTIFACT_DIR}/rpm/
-chown -Rc $(stat -c %u:%g ${ARTIFACT_DIR}) ${ARTIFACT_DIR}
diff --git a/deployment/fedora-package-x64/package.sh b/deployment/fedora-package-x64/package.sh
deleted file mode 100755
index ae6962dd1..000000000
--- a/deployment/fedora-package-x64/package.sh
+++ /dev/null
@@ -1,34 +0,0 @@
-#!/usr/bin/env bash
-
-args="${@}"
-declare -a docker_envvars
-for arg in ${args}; do
- docker_envvars+=("-e ${arg}")
-done
-
-WORKDIR="$( pwd )"
-
-package_temporary_dir="${WORKDIR}/pkg-dist-tmp"
-output_dir="${WORKDIR}/pkg-dist"
-current_user="$( whoami )"
-image_name="jellyfin-fedora-build"
-
-# Determine if sudo should be used for Docker
-if [[ ! -z $(id -Gn | grep -q 'docker') ]] \
- && [[ ! ${EUID:-1000} -eq 0 ]] \
- && [[ ! ${USER} == "root" ]] \
- && [[ ! -z $( echo "${OSTYPE}" | grep -q "darwin" ) ]]; then
- docker_sudo="sudo"
-else
- docker_sudo=""
-fi
-
-# Prepare temporary package dir
-mkdir -p "${package_temporary_dir}"
-# Set up the build environment Docker image
-${docker_sudo} docker build ../.. -t "${image_name}" -f ./Dockerfile
-# Build the RPMs and copy out to ${package_temporary_dir}
-${docker_sudo} docker run --rm -v "${package_temporary_dir}:/dist" "${image_name}" ${docker_envvars}
-# Move the RPMs to the output directory
-mkdir -p "${output_dir}"
-mv "${package_temporary_dir}"/rpm/* "${output_dir}"
diff --git a/deployment/linux-x64/clean.sh b/deployment/linux-x64/clean.sh
deleted file mode 100755
index c07501a7b..000000000
--- a/deployment/linux-x64/clean.sh
+++ /dev/null
@@ -1,27 +0,0 @@
-#!/usr/bin/env bash
-
-keep_artifacts="${1}"
-
-WORKDIR="$( pwd )"
-
-package_temporary_dir="${WORKDIR}/pkg-dist-tmp"
-output_dir="${WORKDIR}/pkg-dist"
-current_user="$( whoami )"
-image_name="jellyfin-linux-build"
-
-rm -rf "${package_temporary_dir}" &>/dev/null \
- || sudo rm -rf "${package_temporary_dir}" &>/dev/null
-
-rm -rf "${output_dir}" &>/dev/null \
- || sudo rm -rf "${output_dir}" &>/dev/null
-
-if [[ ${keep_artifacts} == 'n' ]]; then
- docker_sudo=""
- if [[ ! -z $(id -Gn | grep -q 'docker') ]] \
- && [[ ! ${EUID:-1000} -eq 0 ]] \
- && [[ ! ${USER} == "root" ]] \
- && [[ ! -z $( echo "${OSTYPE}" | grep -q "darwin" ) ]]; then
- docker_sudo=sudo
- fi
- ${docker_sudo} docker image rm ${image_name} --force
-fi
diff --git a/deployment/linux-x64/dependencies.txt b/deployment/linux-x64/dependencies.txt
deleted file mode 100644
index bdb967096..000000000
--- a/deployment/linux-x64/dependencies.txt
+++ /dev/null
@@ -1 +0,0 @@
-docker
diff --git a/deployment/linux-x64/docker-build.sh b/deployment/linux-x64/docker-build.sh
deleted file mode 100755
index 8860f943c..000000000
--- a/deployment/linux-x64/docker-build.sh
+++ /dev/null
@@ -1,36 +0,0 @@
-#!/bin/bash
-
-# Builds the TAR archive inside the Docker container
-
-set -o errexit
-set -o xtrace
-
-# Move to source directory
-pushd ${SOURCE_DIR}
-
-# Clone down and build Web frontend
-web_build_dir="$( mktemp -d )"
-web_target="${SOURCE_DIR}/MediaBrowser.WebDashboard/jellyfin-web"
-git clone https://github.com/jellyfin/jellyfin-web.git ${web_build_dir}/
-pushd ${web_build_dir}
-if [[ -n ${web_branch} ]]; then
- checkout -b origin/${web_branch}
-fi
-yarn install
-mkdir -p ${web_target}
-mv dist/* ${web_target}/
-popd
-rm -rf ${web_build_dir}
-
-# Get version
-version="$( grep "version:" ./build.yaml | sed -E 's/version: "([0-9\.]+.*)"/\1/' )"
-
-# Build archives
-dotnet publish --configuration Release --self-contained --runtime linux-x64 --output /dist/jellyfin_${version}/ "-p:GenerateDocumentationFile=false;DebugSymbols=false;DebugType=none;UseAppHost=true"
-tar -cvzf /jellyfin_${version}.portable.tar.gz -C /dist jellyfin_${version}
-rm -rf /dist/jellyfin_${version}
-
-# Move the artifacts out
-mkdir -p ${ARTIFACT_DIR}/
-mv /jellyfin[-_]*.tar.gz ${ARTIFACT_DIR}/
-chown -Rc $(stat -c %u:%g ${ARTIFACT_DIR}) ${ARTIFACT_DIR}
diff --git a/deployment/linux-x64/package.sh b/deployment/linux-x64/package.sh
deleted file mode 100755
index dfe8a9aa4..000000000
--- a/deployment/linux-x64/package.sh
+++ /dev/null
@@ -1,34 +0,0 @@
-#!/usr/bin/env bash
-
-args="${@}"
-declare -a docker_envvars
-for arg in ${args}; do
- docker_envvars+=("-e ${arg}")
-done
-
-WORKDIR="$( pwd )"
-
-package_temporary_dir="${WORKDIR}/pkg-dist-tmp"
-output_dir="${WORKDIR}/pkg-dist"
-current_user="$( whoami )"
-image_name="jellyfin-linux-build"
-
-# Determine if sudo should be used for Docker
-if [[ ! -z $(id -Gn | grep -q 'docker') ]] \
- && [[ ! ${EUID:-1000} -eq 0 ]] \
- && [[ ! ${USER} == "root" ]] \
- && [[ ! -z $( echo "${OSTYPE}" | grep -q "darwin" ) ]]; then
- docker_sudo="sudo"
-else
- docker_sudo=""
-fi
-
-# Prepare temporary package dir
-mkdir -p "${package_temporary_dir}"
-# Set up the build environment Docker image
-${docker_sudo} docker build ../.. -t "${image_name}" -f ./Dockerfile
-# Build the DEBs and copy out to ${package_temporary_dir}
-${docker_sudo} docker run --rm -v "${package_temporary_dir}:/dist" "${image_name}" ${docker_envvars}
-# Move the DEBs to the output directory
-mkdir -p "${output_dir}"
-mv "${package_temporary_dir}"/* "${output_dir}"
diff --git a/deployment/macos/clean.sh b/deployment/macos/clean.sh
deleted file mode 100755
index c07501a7b..000000000
--- a/deployment/macos/clean.sh
+++ /dev/null
@@ -1,27 +0,0 @@
-#!/usr/bin/env bash
-
-keep_artifacts="${1}"
-
-WORKDIR="$( pwd )"
-
-package_temporary_dir="${WORKDIR}/pkg-dist-tmp"
-output_dir="${WORKDIR}/pkg-dist"
-current_user="$( whoami )"
-image_name="jellyfin-linux-build"
-
-rm -rf "${package_temporary_dir}" &>/dev/null \
- || sudo rm -rf "${package_temporary_dir}" &>/dev/null
-
-rm -rf "${output_dir}" &>/dev/null \
- || sudo rm -rf "${output_dir}" &>/dev/null
-
-if [[ ${keep_artifacts} == 'n' ]]; then
- docker_sudo=""
- if [[ ! -z $(id -Gn | grep -q 'docker') ]] \
- && [[ ! ${EUID:-1000} -eq 0 ]] \
- && [[ ! ${USER} == "root" ]] \
- && [[ ! -z $( echo "${OSTYPE}" | grep -q "darwin" ) ]]; then
- docker_sudo=sudo
- fi
- ${docker_sudo} docker image rm ${image_name} --force
-fi
diff --git a/deployment/macos/dependencies.txt b/deployment/macos/dependencies.txt
deleted file mode 100644
index bdb967096..000000000
--- a/deployment/macos/dependencies.txt
+++ /dev/null
@@ -1 +0,0 @@
-docker
diff --git a/deployment/macos/docker-build.sh b/deployment/macos/docker-build.sh
deleted file mode 100755
index 1b4a554e6..000000000
--- a/deployment/macos/docker-build.sh
+++ /dev/null
@@ -1,36 +0,0 @@
-#!/bin/bash
-
-# Builds the TAR archive inside the Docker container
-
-set -o errexit
-set -o xtrace
-
-# Move to source directory
-pushd ${SOURCE_DIR}
-
-# Clone down and build Web frontend
-web_build_dir="$( mktemp -d )"
-web_target="${SOURCE_DIR}/MediaBrowser.WebDashboard/jellyfin-web"
-git clone https://github.com/jellyfin/jellyfin-web.git ${web_build_dir}/
-pushd ${web_build_dir}
-if [[ -n ${web_branch} ]]; then
- checkout -b origin/${web_branch}
-fi
-yarn install
-mkdir -p ${web_target}
-mv dist/* ${web_target}/
-popd
-rm -rf ${web_build_dir}
-
-# Get version
-version="$( grep "version:" ./build.yaml | sed -E 's/version: "([0-9\.]+.*)"/\1/' )"
-
-# Build archives
-dotnet publish --configuration Release --self-contained --runtime osx-x64 --output /dist/jellyfin_${version}/ "-p:GenerateDocumentationFile=false;DebugSymbols=false;DebugType=none;UseAppHost=true"
-tar -cvzf /jellyfin_${version}.portable.tar.gz -C /dist jellyfin_${version}
-rm -rf /dist/jellyfin_${version}
-
-# Move the artifacts out
-mkdir -p ${ARTIFACT_DIR}/
-mv /jellyfin[-_]*.tar.gz ${ARTIFACT_DIR}/
-chown -Rc $(stat -c %u:%g ${ARTIFACT_DIR}) ${ARTIFACT_DIR}
diff --git a/deployment/macos/package.sh b/deployment/macos/package.sh
deleted file mode 100755
index 464c0d382..000000000
--- a/deployment/macos/package.sh
+++ /dev/null
@@ -1,34 +0,0 @@
-#!/usr/bin/env bash
-
-args="${@}"
-declare -a docker_envvars
-for arg in ${args}; do
- docker_envvars+=("-e ${arg}")
-done
-
-WORKDIR="$( pwd )"
-
-package_temporary_dir="${WORKDIR}/pkg-dist-tmp"
-output_dir="${WORKDIR}/pkg-dist"
-current_user="$( whoami )"
-image_name="jellyfin-macos-build"
-
-# Determine if sudo should be used for Docker
-if [[ ! -z $(id -Gn | grep -q 'docker') ]] \
- && [[ ! ${EUID:-1000} -eq 0 ]] \
- && [[ ! ${USER} == "root" ]] \
- && [[ ! -z $( echo "${OSTYPE}" | grep -q "darwin" ) ]]; then
- docker_sudo="sudo"
-else
- docker_sudo=""
-fi
-
-# Prepare temporary package dir
-mkdir -p "${package_temporary_dir}"
-# Set up the build environment Docker image
-${docker_sudo} docker build ../.. -t "${image_name}" -f ./Dockerfile
-# Build the DEBs and copy out to ${package_temporary_dir}
-${docker_sudo} docker run --rm -v "${package_temporary_dir}:/dist" "${image_name}" ${docker_envvars}
-# Move the DEBs to the output directory
-mkdir -p "${output_dir}"
-mv "${package_temporary_dir}"/* "${output_dir}"
diff --git a/deployment/portable/clean.sh b/deployment/portable/clean.sh
deleted file mode 100755
index c07501a7b..000000000
--- a/deployment/portable/clean.sh
+++ /dev/null
@@ -1,27 +0,0 @@
-#!/usr/bin/env bash
-
-keep_artifacts="${1}"
-
-WORKDIR="$( pwd )"
-
-package_temporary_dir="${WORKDIR}/pkg-dist-tmp"
-output_dir="${WORKDIR}/pkg-dist"
-current_user="$( whoami )"
-image_name="jellyfin-linux-build"
-
-rm -rf "${package_temporary_dir}" &>/dev/null \
- || sudo rm -rf "${package_temporary_dir}" &>/dev/null
-
-rm -rf "${output_dir}" &>/dev/null \
- || sudo rm -rf "${output_dir}" &>/dev/null
-
-if [[ ${keep_artifacts} == 'n' ]]; then
- docker_sudo=""
- if [[ ! -z $(id -Gn | grep -q 'docker') ]] \
- && [[ ! ${EUID:-1000} -eq 0 ]] \
- && [[ ! ${USER} == "root" ]] \
- && [[ ! -z $( echo "${OSTYPE}" | grep -q "darwin" ) ]]; then
- docker_sudo=sudo
- fi
- ${docker_sudo} docker image rm ${image_name} --force
-fi
diff --git a/deployment/portable/dependencies.txt b/deployment/portable/dependencies.txt
deleted file mode 100644
index bdb967096..000000000
--- a/deployment/portable/dependencies.txt
+++ /dev/null
@@ -1 +0,0 @@
-docker
diff --git a/deployment/portable/docker-build.sh b/deployment/portable/docker-build.sh
deleted file mode 100755
index 0cc6e84f0..000000000
--- a/deployment/portable/docker-build.sh
+++ /dev/null
@@ -1,36 +0,0 @@
-#!/bin/bash
-
-# Builds the TAR archive inside the Docker container
-
-set -o errexit
-set -o xtrace
-
-# Move to source directory
-pushd ${SOURCE_DIR}
-
-# Clone down and build Web frontend
-web_build_dir="$( mktemp -d )"
-web_target="${SOURCE_DIR}/MediaBrowser.WebDashboard/jellyfin-web"
-git clone https://github.com/jellyfin/jellyfin-web.git ${web_build_dir}/
-pushd ${web_build_dir}
-if [[ -n ${web_branch} ]]; then
- checkout -b origin/${web_branch}
-fi
-yarn install
-mkdir -p ${web_target}
-mv dist/* ${web_target}/
-popd
-rm -rf ${web_build_dir}
-
-# Get version
-version="$( grep "version:" ./build.yaml | sed -E 's/version: "([0-9\.]+.*)"/\1/' )"
-
-# Build archives
-dotnet publish --configuration Release --output /dist/jellyfin_${version}/ "-p:GenerateDocumentationFile=false;DebugSymbols=false;DebugType=none"
-tar -cvzf /jellyfin_${version}.portable.tar.gz -C /dist jellyfin_${version}
-rm -rf /dist/jellyfin_${version}
-
-# Move the artifacts out
-mkdir -p ${ARTIFACT_DIR}/
-mv /jellyfin[-_]*.tar.gz ${ARTIFACT_DIR}/
-chown -Rc $(stat -c %u:%g ${ARTIFACT_DIR}) ${ARTIFACT_DIR}
diff --git a/deployment/portable/package.sh b/deployment/portable/package.sh
deleted file mode 100755
index 0ceb54dda..000000000
--- a/deployment/portable/package.sh
+++ /dev/null
@@ -1,34 +0,0 @@
-#!/usr/bin/env bash
-
-args="${@}"
-declare -a docker_envvars
-for arg in ${args}; do
- docker_envvars+=("-e ${arg}")
-done
-
-WORKDIR="$( pwd )"
-
-package_temporary_dir="${WORKDIR}/pkg-dist-tmp"
-output_dir="${WORKDIR}/pkg-dist"
-current_user="$( whoami )"
-image_name="jellyfin-portable-build"
-
-# Determine if sudo should be used for Docker
-if [[ ! -z $(id -Gn | grep -q 'docker') ]] \
- && [[ ! ${EUID:-1000} -eq 0 ]] \
- && [[ ! ${USER} == "root" ]] \
- && [[ ! -z $( echo "${OSTYPE}" | grep -q "darwin" ) ]]; then
- docker_sudo="sudo"
-else
- docker_sudo=""
-fi
-
-# Prepare temporary package dir
-mkdir -p "${package_temporary_dir}"
-# Set up the build environment Docker image
-${docker_sudo} docker build ../.. -t "${image_name}" -f ./Dockerfile
-# Build the DEBs and copy out to ${package_temporary_dir}
-${docker_sudo} docker run --rm -v "${package_temporary_dir}:/dist" "${image_name}" ${docker_envvars}
-# Move the DEBs to the output directory
-mkdir -p "${output_dir}"
-mv "${package_temporary_dir}"/* "${output_dir}"
diff --git a/deployment/ubuntu-package-arm64/Dockerfile.arm64 b/deployment/ubuntu-package-arm64/Dockerfile.arm64
deleted file mode 100644
index af7084459..000000000
--- a/deployment/ubuntu-package-arm64/Dockerfile.arm64
+++ /dev/null
@@ -1,40 +0,0 @@
-FROM ubuntu:bionic
-# Docker build arguments
-ARG SOURCE_DIR=/jellyfin
-ARG PLATFORM_DIR=/jellyfin/deployment/ubuntu-package-arm64
-ARG ARTIFACT_DIR=/dist
-ARG SDK_VERSION=3.1
-# Docker run environment
-ENV SOURCE_DIR=/jellyfin
-ENV ARTIFACT_DIR=/dist
-ENV DEB_BUILD_OPTIONS=noddebs
-ENV ARCH=arm64
-
-# Prepare Debian build environment
-RUN apt-get update \
- && apt-get install -y apt-transport-https debhelper gnupg wget devscripts mmv libc6-dev libcurl4-openssl-dev libfontconfig1-dev libfreetype6-dev liblttng-ust0 libssl-dev
-
-# Install dotnet repository
-# https://dotnet.microsoft.com/download/linux-package-manager/debian9/sdk-current
-RUN wget https://download.visualstudio.microsoft.com/download/pr/5a4c8f96-1c73-401c-a6de-8e100403188a/0ce6ab39747e2508366d498f9c0a0669/dotnet-sdk-3.1.100-linux-arm64.tar.gz -O dotnet-sdk.tar.gz \
- && mkdir -p dotnet-sdk \
- && tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \
- && ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet
-
-# Install npm package manager
-RUN wget -q -O- https://deb.nodesource.com/gpgkey/nodesource.gpg.key | apt-key add - \
- && echo "deb https://deb.nodesource.com/node_8.x $(lsb_release -s -c) main" > /etc/apt/sources.list.d/npm.list \
- && apt update \
- && apt install -y nodejs
-
-# Link to docker-build script
-RUN ln -sf ${PLATFORM_DIR}/docker-build.sh /docker-build.sh
-
-# Link to Debian source dir; mkdir needed or it fails, can't force dest
-RUN mkdir -p ${SOURCE_DIR} && ln -sf ${PLATFORM_DIR}/pkg-src ${SOURCE_DIR}/debian
-
-VOLUME ${ARTIFACT_DIR}/
-
-COPY . ${SOURCE_DIR}/
-
-ENTRYPOINT ["/docker-build.sh"]
diff --git a/deployment/ubuntu-package-arm64/clean.sh b/deployment/ubuntu-package-arm64/clean.sh
deleted file mode 100755
index 82d427f9e..000000000
--- a/deployment/ubuntu-package-arm64/clean.sh
+++ /dev/null
@@ -1,27 +0,0 @@
-#!/usr/bin/env bash
-
-keep_artifacts="${1}"
-
-WORKDIR="$( pwd )"
-
-package_temporary_dir="${WORKDIR}/pkg-dist-tmp"
-output_dir="${WORKDIR}/pkg-dist"
-current_user="$( whoami )"
-image_name="jellyfin-ubuntu-build"
-
-rm -rf "${package_temporary_dir}" &>/dev/null \
- || sudo rm -rf "${package_temporary_dir}" &>/dev/null
-
-rm -rf "${output_dir}" &>/dev/null \
- || sudo rm -rf "${output_dir}" &>/dev/null
-
-if [[ ${keep_artifacts} == 'n' ]]; then
- docker_sudo=""
- if [[ ! -z $(id -Gn | grep -q 'docker') ]] \
- && [[ ! ${EUID:-1000} -eq 0 ]] \
- && [[ ! ${USER} == "root" ]] \
- && [[ ! -z $( echo "${OSTYPE}" | grep -q "darwin" ) ]]; then
- docker_sudo=sudo
- fi
- ${docker_sudo} docker image rm ${image_name} --force
-fi
diff --git a/deployment/ubuntu-package-arm64/dependencies.txt b/deployment/ubuntu-package-arm64/dependencies.txt
deleted file mode 100644
index bdb967096..000000000
--- a/deployment/ubuntu-package-arm64/dependencies.txt
+++ /dev/null
@@ -1 +0,0 @@
-docker
diff --git a/deployment/ubuntu-package-arm64/docker-build.sh b/deployment/ubuntu-package-arm64/docker-build.sh
deleted file mode 100755
index 67ab6bd74..000000000
--- a/deployment/ubuntu-package-arm64/docker-build.sh
+++ /dev/null
@@ -1,21 +0,0 @@
-#!/bin/bash
-
-# Builds the DEB inside the Docker container
-
-set -o errexit
-set -o xtrace
-
-# Move to source directory
-pushd ${SOURCE_DIR}
-
-# Remove build-dep for dotnet-sdk-3.1, since it's not a package in this image
-sed -i '/dotnet-sdk-3.1,/d' debian/control
-
-# Build DEB
-export CONFIG_SITE=/etc/dpkg-cross/cross-config.${ARCH}
-dpkg-buildpackage -us -uc -aarm64
-
-# Move the artifacts out
-mkdir -p ${ARTIFACT_DIR}/deb
-mv /jellyfin[-_]* ${ARTIFACT_DIR}/deb/
-chown -Rc $(stat -c %u:%g ${ARTIFACT_DIR}) ${ARTIFACT_DIR}
diff --git a/deployment/ubuntu-package-arm64/package.sh b/deployment/ubuntu-package-arm64/package.sh
deleted file mode 100755
index d1140a727..000000000
--- a/deployment/ubuntu-package-arm64/package.sh
+++ /dev/null
@@ -1,45 +0,0 @@
-#!/usr/bin/env bash
-
-args="${@}"
-declare -a docker_envvars
-for arg in ${args}; do
- docker_envvars+=("-e ${arg}")
-done
-
-ARCH="$( arch )"
-WORKDIR="$( pwd )"
-
-package_temporary_dir="${WORKDIR}/pkg-dist-tmp"
-output_dir="${WORKDIR}/pkg-dist"
-current_user="$( whoami )"
-image_name="jellyfin-ubuntu_arm64-build"
-
-# Determine if sudo should be used for Docker
-if [[ ! -z $(id -Gn | grep -q 'docker') ]] \
- && [[ ! ${EUID:-1000} -eq 0 ]] \
- && [[ ! ${USER} == "root" ]] \
- && [[ ! -z $( echo "${OSTYPE}" | grep -q "darwin" ) ]]; then
- docker_sudo="sudo"
-else
- docker_sudo=""
-fi
-
-# Determine which Dockerfile to use
-case $ARCH in
- 'x86_64')
- DOCKERFILE="Dockerfile.amd64"
- ;;
- 'armv7l')
- DOCKERFILE="Dockerfile.arm64"
- ;;
-esac
-
-# Prepare temporary package dir
-mkdir -p "${package_temporary_dir}"
-# Set up the build environment Docker image
-${docker_sudo} docker build ../.. -t "${image_name}" -f ./${DOCKERFILE}
-# Build the DEBs and copy out to ${package_temporary_dir}
-${docker_sudo} docker run --rm -v "${package_temporary_dir}:/dist" "${image_name}" ${docker_envvars}
-# Move the DEBs to the output directory
-mkdir -p "${output_dir}"
-mv "${package_temporary_dir}"/deb/* "${output_dir}"
diff --git a/deployment/ubuntu-package-arm64/pkg-src b/deployment/ubuntu-package-arm64/pkg-src
deleted file mode 120000
index 4c695fea1..000000000
--- a/deployment/ubuntu-package-arm64/pkg-src
+++ /dev/null
@@ -1 +0,0 @@
-../debian-package-x64/pkg-src \ No newline at end of file
diff --git a/deployment/ubuntu-package-armhf/Dockerfile.armhf b/deployment/ubuntu-package-armhf/Dockerfile.armhf
deleted file mode 100644
index 06a8dace2..000000000
--- a/deployment/ubuntu-package-armhf/Dockerfile.armhf
+++ /dev/null
@@ -1,40 +0,0 @@
-FROM ubuntu:bionic
-# Docker build arguments
-ARG SOURCE_DIR=/jellyfin
-ARG PLATFORM_DIR=/jellyfin/deployment/ubuntu-package-armhf
-ARG ARTIFACT_DIR=/dist
-ARG SDK_VERSION=3.1
-# Docker run environment
-ENV SOURCE_DIR=/jellyfin
-ENV ARTIFACT_DIR=/dist
-ENV DEB_BUILD_OPTIONS=noddebs
-ENV ARCH=armhf
-
-# Prepare Debian build environment
-RUN apt-get update \
- && apt-get install -y apt-transport-https debhelper gnupg wget devscripts mmv libc6-dev libcurl4-openssl-dev libfontconfig1-dev libfreetype6-dev liblttng-ust0 libssl-dev
-
-# Install dotnet repository
-# https://dotnet.microsoft.com/download/linux-package-manager/debian9/sdk-current
-RUN wget https://download.visualstudio.microsoft.com/download/pr/67766a96-eb8c-4cd2-bca4-ea63d2cc115c/7bf13840aa2ed88793b7315d5e0d74e6/dotnet-sdk-3.1.100-linux-arm.tar.gz -O dotnet-sdk.tar.gz \
- && mkdir -p dotnet-sdk \
- && tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \
- && ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet
-
-# Install npm package manager
-RUN wget -q -O- https://deb.nodesource.com/gpgkey/nodesource.gpg.key | apt-key add - \
- && echo "deb https://deb.nodesource.com/node_8.x $(lsb_release -s -c) main" > /etc/apt/sources.list.d/npm.list \
- && apt update \
- && apt install -y nodejs
-
-# Link to docker-build script
-RUN ln -sf ${PLATFORM_DIR}/docker-build.sh /docker-build.sh
-
-# Link to Debian source dir; mkdir needed or it fails, can't force dest
-RUN mkdir -p ${SOURCE_DIR} && ln -sf ${PLATFORM_DIR}/pkg-src ${SOURCE_DIR}/debian
-
-VOLUME ${ARTIFACT_DIR}/
-
-COPY . ${SOURCE_DIR}/
-
-ENTRYPOINT ["/docker-build.sh"]
diff --git a/deployment/ubuntu-package-armhf/clean.sh b/deployment/ubuntu-package-armhf/clean.sh
deleted file mode 100755
index 82d427f9e..000000000
--- a/deployment/ubuntu-package-armhf/clean.sh
+++ /dev/null
@@ -1,27 +0,0 @@
-#!/usr/bin/env bash
-
-keep_artifacts="${1}"
-
-WORKDIR="$( pwd )"
-
-package_temporary_dir="${WORKDIR}/pkg-dist-tmp"
-output_dir="${WORKDIR}/pkg-dist"
-current_user="$( whoami )"
-image_name="jellyfin-ubuntu-build"
-
-rm -rf "${package_temporary_dir}" &>/dev/null \
- || sudo rm -rf "${package_temporary_dir}" &>/dev/null
-
-rm -rf "${output_dir}" &>/dev/null \
- || sudo rm -rf "${output_dir}" &>/dev/null
-
-if [[ ${keep_artifacts} == 'n' ]]; then
- docker_sudo=""
- if [[ ! -z $(id -Gn | grep -q 'docker') ]] \
- && [[ ! ${EUID:-1000} -eq 0 ]] \
- && [[ ! ${USER} == "root" ]] \
- && [[ ! -z $( echo "${OSTYPE}" | grep -q "darwin" ) ]]; then
- docker_sudo=sudo
- fi
- ${docker_sudo} docker image rm ${image_name} --force
-fi
diff --git a/deployment/ubuntu-package-armhf/dependencies.txt b/deployment/ubuntu-package-armhf/dependencies.txt
deleted file mode 100644
index bdb967096..000000000
--- a/deployment/ubuntu-package-armhf/dependencies.txt
+++ /dev/null
@@ -1 +0,0 @@
-docker
diff --git a/deployment/ubuntu-package-armhf/docker-build.sh b/deployment/ubuntu-package-armhf/docker-build.sh
deleted file mode 100755
index 1bd7fb291..000000000
--- a/deployment/ubuntu-package-armhf/docker-build.sh
+++ /dev/null
@@ -1,21 +0,0 @@
-#!/bin/bash
-
-# Builds the DEB inside the Docker container
-
-set -o errexit
-set -o xtrace
-
-# Move to source directory
-pushd ${SOURCE_DIR}
-
-# Remove build-dep for dotnet-sdk-3.1, since it's not a package in this image
-sed -i '/dotnet-sdk-3.1,/d' debian/control
-
-# Build DEB
-export CONFIG_SITE=/etc/dpkg-cross/cross-config.${ARCH}
-dpkg-buildpackage -us -uc -aarmhf
-
-# Move the artifacts out
-mkdir -p ${ARTIFACT_DIR}/deb
-mv /jellyfin[-_]* ${ARTIFACT_DIR}/deb/
-chown -Rc $(stat -c %u:%g ${ARTIFACT_DIR}) ${ARTIFACT_DIR}
diff --git a/deployment/ubuntu-package-armhf/package.sh b/deployment/ubuntu-package-armhf/package.sh
deleted file mode 100755
index 2ceb3e816..000000000
--- a/deployment/ubuntu-package-armhf/package.sh
+++ /dev/null
@@ -1,45 +0,0 @@
-#!/usr/bin/env bash
-
-args="${@}"
-declare -a docker_envvars
-for arg in ${args}; do
- docker_envvars+=("-e ${arg}")
-done
-
-ARCH="$( arch )"
-WORKDIR="$( pwd )"
-
-package_temporary_dir="${WORKDIR}/pkg-dist-tmp"
-output_dir="${WORKDIR}/pkg-dist"
-current_user="$( whoami )"
-image_name="jellyfin-ubuntu_armhf-build"
-
-# Determine if sudo should be used for Docker
-if [[ ! -z $(id -Gn | grep -q 'docker') ]] \
- && [[ ! ${EUID:-1000} -eq 0 ]] \
- && [[ ! ${USER} == "root" ]] \
- && [[ ! -z $( echo "${OSTYPE}" | grep -q "darwin" ) ]]; then
- docker_sudo="sudo"
-else
- docker_sudo=""
-fi
-
-# Determine which Dockerfile to use
-case $ARCH in
- 'x86_64')
- DOCKERFILE="Dockerfile.amd64"
- ;;
- 'armv7l')
- DOCKERFILE="Dockerfile.armhf"
- ;;
-esac
-
-# Prepare temporary package dir
-mkdir -p "${package_temporary_dir}"
-# Set up the build environment Docker image
-${docker_sudo} docker build ../.. -t "${image_name}" -f ./${DOCKERFILE}
-# Build the DEBs and copy out to ${package_temporary_dir}
-${docker_sudo} docker run --rm -v "${package_temporary_dir}:/dist" "${image_name}" ${docker_envvars}
-# Move the DEBs to the output directory
-mkdir -p "${output_dir}"
-mv "${package_temporary_dir}"/deb/* "${output_dir}"
diff --git a/deployment/ubuntu-package-armhf/pkg-src b/deployment/ubuntu-package-armhf/pkg-src
deleted file mode 120000
index 4c695fea1..000000000
--- a/deployment/ubuntu-package-armhf/pkg-src
+++ /dev/null
@@ -1 +0,0 @@
-../debian-package-x64/pkg-src \ No newline at end of file
diff --git a/deployment/ubuntu-package-x64/Dockerfile b/deployment/ubuntu-package-x64/Dockerfile
deleted file mode 100644
index 8237ced29..000000000
--- a/deployment/ubuntu-package-x64/Dockerfile
+++ /dev/null
@@ -1,36 +0,0 @@
-FROM ubuntu:bionic
-# Docker build arguments
-ARG SOURCE_DIR=/jellyfin
-ARG PLATFORM_DIR=/jellyfin/deployment/ubuntu-package-x64
-ARG ARTIFACT_DIR=/dist
-ARG SDK_VERSION=3.1
-# Docker run environment
-ENV SOURCE_DIR=/jellyfin
-ENV ARTIFACT_DIR=/dist
-ENV DEB_BUILD_OPTIONS=noddebs
-ENV ARCH=amd64
-
-# Prepare Ubuntu build environment
-RUN apt-get update \
- && apt-get install -y apt-transport-https debhelper gnupg wget devscripts mmv libc6-dev libcurl4-openssl-dev libfontconfig1-dev libfreetype6-dev libssl-dev liblttng-ust0 \
- && ln -sf ${PLATFORM_DIR}/docker-build.sh /docker-build.sh \
- && mkdir -p ${SOURCE_DIR} && ln -sf ${PLATFORM_DIR}/pkg-src ${SOURCE_DIR}/debian
-
-# Install dotnet repository
-# https://dotnet.microsoft.com/download/linux-package-manager/debian9/sdk-current
-RUN wget https://download.visualstudio.microsoft.com/download/pr/d731f991-8e68-4c7c-8ea0-fad5605b077a/49497b5420eecbd905158d86d738af64/dotnet-sdk-3.1.100-linux-x64.tar.gz -O dotnet-sdk.tar.gz \
- && mkdir -p dotnet-sdk \
- && tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \
- && ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet
-
-# Install npm package manager
-RUN wget -q -O- https://deb.nodesource.com/gpgkey/nodesource.gpg.key | apt-key add - \
- && echo "deb https://deb.nodesource.com/node_8.x $(lsb_release -s -c) main" > /etc/apt/sources.list.d/npm.list \
- && apt update \
- && apt install -y nodejs
-
-VOLUME ${ARTIFACT_DIR}/
-
-COPY . ${SOURCE_DIR}/
-
-ENTRYPOINT ["/docker-build.sh"]
diff --git a/deployment/ubuntu-package-x64/clean.sh b/deployment/ubuntu-package-x64/clean.sh
deleted file mode 100755
index 82d427f9e..000000000
--- a/deployment/ubuntu-package-x64/clean.sh
+++ /dev/null
@@ -1,27 +0,0 @@
-#!/usr/bin/env bash
-
-keep_artifacts="${1}"
-
-WORKDIR="$( pwd )"
-
-package_temporary_dir="${WORKDIR}/pkg-dist-tmp"
-output_dir="${WORKDIR}/pkg-dist"
-current_user="$( whoami )"
-image_name="jellyfin-ubuntu-build"
-
-rm -rf "${package_temporary_dir}" &>/dev/null \
- || sudo rm -rf "${package_temporary_dir}" &>/dev/null
-
-rm -rf "${output_dir}" &>/dev/null \
- || sudo rm -rf "${output_dir}" &>/dev/null
-
-if [[ ${keep_artifacts} == 'n' ]]; then
- docker_sudo=""
- if [[ ! -z $(id -Gn | grep -q 'docker') ]] \
- && [[ ! ${EUID:-1000} -eq 0 ]] \
- && [[ ! ${USER} == "root" ]] \
- && [[ ! -z $( echo "${OSTYPE}" | grep -q "darwin" ) ]]; then
- docker_sudo=sudo
- fi
- ${docker_sudo} docker image rm ${image_name} --force
-fi
diff --git a/deployment/ubuntu-package-x64/dependencies.txt b/deployment/ubuntu-package-x64/dependencies.txt
deleted file mode 100644
index bdb967096..000000000
--- a/deployment/ubuntu-package-x64/dependencies.txt
+++ /dev/null
@@ -1 +0,0 @@
-docker
diff --git a/deployment/ubuntu-package-x64/docker-build.sh b/deployment/ubuntu-package-x64/docker-build.sh
deleted file mode 100755
index 962a522eb..000000000
--- a/deployment/ubuntu-package-x64/docker-build.sh
+++ /dev/null
@@ -1,20 +0,0 @@
-#!/bin/bash
-
-# Builds the DEB inside the Docker container
-
-set -o errexit
-set -o xtrace
-
-# Move to source directory
-pushd ${SOURCE_DIR}
-
-# Remove build-dep for dotnet-sdk-3.1, since it's not a package in this image
-sed -i '/dotnet-sdk-3.1,/d' debian/control
-
-# Build DEB
-dpkg-buildpackage -us -uc
-
-# Move the artifacts out
-mkdir -p ${ARTIFACT_DIR}/deb
-mv /jellyfin[-_]* ${ARTIFACT_DIR}/deb/
-chown -Rc $(stat -c %u:%g ${ARTIFACT_DIR}) ${ARTIFACT_DIR}
diff --git a/deployment/ubuntu-package-x64/package.sh b/deployment/ubuntu-package-x64/package.sh
deleted file mode 100755
index 08c003778..000000000
--- a/deployment/ubuntu-package-x64/package.sh
+++ /dev/null
@@ -1,34 +0,0 @@
-#!/usr/bin/env bash
-
-args="${@}"
-declare -a docker_envvars
-for arg in ${args}; do
- docker_envvars+=("-e ${arg}")
-done
-
-WORKDIR="$( pwd )"
-
-package_temporary_dir="${WORKDIR}/pkg-dist-tmp"
-output_dir="${WORKDIR}/pkg-dist"
-current_user="$( whoami )"
-image_name="jellyfin-ubuntu-build"
-
-# Determine if sudo should be used for Docker
-if [[ ! -z $(id -Gn | grep -q 'docker') ]] \
- && [[ ! ${EUID:-1000} -eq 0 ]] \
- && [[ ! ${USER} == "root" ]] \
- && [[ ! -z $( echo "${OSTYPE}" | grep -q "darwin" ) ]]; then
- docker_sudo="sudo"
-else
- docker_sudo=""
-fi
-
-# Prepare temporary package dir
-mkdir -p "${package_temporary_dir}"
-# Set up the build environment Docker image
-${docker_sudo} docker build ../.. -t "${image_name}" -f ./Dockerfile
-# Build the DEBs and copy out to ${package_temporary_dir}
-${docker_sudo} docker run --rm -v "${package_temporary_dir}:/dist" "${image_name}" ${docker_envvars}
-# Move the DEBs to the output directory
-mkdir -p "${output_dir}"
-mv "${package_temporary_dir}"/deb/* "${output_dir}"
diff --git a/deployment/ubuntu-package-x64/pkg-src b/deployment/ubuntu-package-x64/pkg-src
deleted file mode 120000
index 0bb6d5524..000000000
--- a/deployment/ubuntu-package-x64/pkg-src
+++ /dev/null
@@ -1 +0,0 @@
-../debian-package-x64/pkg-src/ \ No newline at end of file
diff --git a/deployment/win-x64/clean.sh b/deployment/win-x64/clean.sh
deleted file mode 100755
index 6c183f337..000000000
--- a/deployment/win-x64/clean.sh
+++ /dev/null
@@ -1,27 +0,0 @@
-#!/usr/bin/env bash
-
-keep_artifacts="${1}"
-
-WORKDIR="$( pwd )"
-
-package_temporary_dir="${WORKDIR}/pkg-dist-tmp"
-output_dir="${WORKDIR}/pkg-dist"
-current_user="$( whoami )"
-image_name="jellyfin-windows-x64-build"
-
-rm -rf "${package_temporary_dir}" &>/dev/null \
- || sudo rm -rf "${package_temporary_dir}" &>/dev/null
-
-rm -rf "${output_dir}" &>/dev/null \
- || sudo rm -rf "${output_dir}" &>/dev/null
-
-if [[ ${keep_artifacts} == 'n' ]]; then
- docker_sudo=""
- if [[ ! -z $(id -Gn | grep -q 'docker') ]] \
- && [[ ! ${EUID:-1000} -eq 0 ]] \
- && [[ ! ${USER} == "root" ]] \
- && [[ ! -z $( echo "${OSTYPE}" | grep -q "darwin" ) ]]; then
- docker_sudo=sudo
- fi
- ${docker_sudo} docker image rm ${image_name} --force
-fi
diff --git a/deployment/win-x64/dependencies.txt b/deployment/win-x64/dependencies.txt
deleted file mode 100644
index bdb967096..000000000
--- a/deployment/win-x64/dependencies.txt
+++ /dev/null
@@ -1 +0,0 @@
-docker
diff --git a/deployment/win-x64/docker-build.sh b/deployment/win-x64/docker-build.sh
deleted file mode 100755
index 3f1ad78b5..000000000
--- a/deployment/win-x64/docker-build.sh
+++ /dev/null
@@ -1,61 +0,0 @@
-#!/bin/bash
-
-# Builds the ZIP archive inside the Docker container
-
-set -o errexit
-set -o xtrace
-
-# Version variables
-NSSM_VERSION="nssm-2.24-101-g897c7ad"
-NSSM_URL="http://files.evilt.win/nssm/${NSSM_VERSION}.zip"
-FFMPEG_VERSION="ffmpeg-4.0.2-win64-static"
-FFMPEG_URL="https://ffmpeg.zeranoe.com/builds/win64/static/${FFMPEG_VERSION}.zip"
-
-# Move to source directory
-pushd ${SOURCE_DIR}
-
-# Clone down and build Web frontend
-web_build_dir="$( mktemp -d )"
-web_target="${SOURCE_DIR}/MediaBrowser.WebDashboard/jellyfin-web"
-git clone https://github.com/jellyfin/jellyfin-web.git ${web_build_dir}/
-pushd ${web_build_dir}
-if [[ -n ${web_branch} ]]; then
- checkout -b origin/${web_branch}
-fi
-yarn install
-mkdir -p ${web_target}
-mv dist/* ${web_target}/
-popd
-rm -rf ${web_build_dir}
-
-# Get version
-version="$( grep "version:" ./build.yaml | sed -E 's/version: "([0-9\.]+.*)"/\1/' )"
-
-# Build binary
-dotnet publish --configuration Release --self-contained --runtime win-x64 --output /dist/jellyfin_${version}/ "-p:GenerateDocumentationFile=false;DebugSymbols=false;DebugType=none;UseAppHost=true"
-
-# Prepare addins
-addin_build_dir="$( mktemp -d )"
-wget ${NSSM_URL} -O ${addin_build_dir}/nssm.zip
-wget ${FFMPEG_URL} -O ${addin_build_dir}/ffmpeg.zip
-unzip ${addin_build_dir}/nssm.zip -d ${addin_build_dir}
-cp ${addin_build_dir}/${NSSM_VERSION}/win64/nssm.exe /dist/jellyfin_${version}/nssm.exe
-unzip ${addin_build_dir}/ffmpeg.zip -d ${addin_build_dir}
-cp ${addin_build_dir}/${FFMPEG_VERSION}/bin/ffmpeg.exe /dist/jellyfin_${version}/ffmpeg.exe
-cp ${addin_build_dir}/${FFMPEG_VERSION}/bin/ffprobe.exe /dist/jellyfin_${version}/ffprobe.exe
-rm -rf ${addin_build_dir}
-
-# Prepare scripts
-cp ${SOURCE_DIR}/deployment/windows/legacy/install-jellyfin.ps1 /dist/jellyfin_${version}/install-jellyfin.ps1
-cp ${SOURCE_DIR}/deployment/windows/legacy/install.bat /dist/jellyfin_${version}/install.bat
-
-# Create zip package
-pushd /dist
-zip -r /jellyfin_${version}.portable.zip jellyfin_${version}
-popd
-rm -rf /dist/jellyfin_${version}
-
-# Move the artifacts out
-mkdir -p ${ARTIFACT_DIR}/
-mv /jellyfin[-_]*.zip ${ARTIFACT_DIR}/
-chown -Rc $(stat -c %u:%g ${ARTIFACT_DIR}) ${ARTIFACT_DIR}
diff --git a/deployment/win-x64/package.sh b/deployment/win-x64/package.sh
deleted file mode 100755
index a8ab190fa..000000000
--- a/deployment/win-x64/package.sh
+++ /dev/null
@@ -1,34 +0,0 @@
-#!/usr/bin/env bash
-
-args="${@}"
-declare -a docker_envvars
-for arg in ${args}; do
- docker_envvars+=("-e ${arg}")
-done
-
-WORKDIR="$( pwd )"
-
-package_temporary_dir="${WORKDIR}/pkg-dist-tmp"
-output_dir="${WORKDIR}/pkg-dist"
-current_user="$( whoami )"
-image_name="jellyfin-windows-x64-build"
-
-# Determine if sudo should be used for Docker
-if [[ ! -z $(id -Gn | grep -q 'docker') ]] \
- && [[ ! ${EUID:-1000} -eq 0 ]] \
- && [[ ! ${USER} == "root" ]] \
- && [[ ! -z $( echo "${OSTYPE}" | grep -q "darwin" ) ]]; then
- docker_sudo="sudo"
-else
- docker_sudo=""
-fi
-
-# Prepare temporary package dir
-mkdir -p "${package_temporary_dir}"
-# Set up the build environment Docker image
-${docker_sudo} docker build ../.. -t "${image_name}" -f ./Dockerfile
-# Build the DEBs and copy out to ${package_temporary_dir}
-${docker_sudo} docker run --rm -v "${package_temporary_dir}:/dist" "${image_name}" ${docker_envvars}
-# Move the DEBs to the output directory
-mkdir -p "${output_dir}"
-mv "${package_temporary_dir}"/* "${output_dir}"
diff --git a/deployment/win-x86/clean.sh b/deployment/win-x86/clean.sh
deleted file mode 100755
index 8b78c5e4b..000000000
--- a/deployment/win-x86/clean.sh
+++ /dev/null
@@ -1,27 +0,0 @@
-#!/usr/bin/env bash
-
-keep_artifacts="${1}"
-
-WORKDIR="$( pwd )"
-
-package_temporary_dir="${WORKDIR}/pkg-dist-tmp"
-output_dir="${WORKDIR}/pkg-dist"
-current_user="$( whoami )"
-image_name="jellyfin-windows-x86-build"
-
-rm -rf "${package_temporary_dir}" &>/dev/null \
- || sudo rm -rf "${package_temporary_dir}" &>/dev/null
-
-rm -rf "${output_dir}" &>/dev/null \
- || sudo rm -rf "${output_dir}" &>/dev/null
-
-if [[ ${keep_artifacts} == 'n' ]]; then
- docker_sudo=""
- if [[ ! -z $(id -Gn | grep -q 'docker') ]] \
- && [[ ! ${EUID:-1000} -eq 0 ]] \
- && [[ ! ${USER} == "root" ]] \
- && [[ ! -z $( echo "${OSTYPE}" | grep -q "darwin" ) ]]; then
- docker_sudo=sudo
- fi
- ${docker_sudo} docker image rm ${image_name} --force
-fi
diff --git a/deployment/win-x86/dependencies.txt b/deployment/win-x86/dependencies.txt
deleted file mode 100644
index bdb967096..000000000
--- a/deployment/win-x86/dependencies.txt
+++ /dev/null
@@ -1 +0,0 @@
-docker
diff --git a/deployment/win-x86/docker-build.sh b/deployment/win-x86/docker-build.sh
deleted file mode 100755
index 7d79ba495..000000000
--- a/deployment/win-x86/docker-build.sh
+++ /dev/null
@@ -1,61 +0,0 @@
-#!/bin/bash
-
-# Builds the ZIP archive inside the Docker container
-
-set -o errexit
-set -o xtrace
-
-# Version variables
-NSSM_VERSION="nssm-2.24-101-g897c7ad"
-NSSM_URL="http://files.evilt.win/nssm/${NSSM_VERSION}.zip"
-FFMPEG_VERSION="ffmpeg-4.0.2-win32-static"
-FFMPEG_URL="https://ffmpeg.zeranoe.com/builds/win32/static/${FFMPEG_VERSION}.zip"
-
-# Move to source directory
-pushd ${SOURCE_DIR}
-
-# Clone down and build Web frontend
-web_build_dir="$( mktemp -d )"
-web_target="${SOURCE_DIR}/MediaBrowser.WebDashboard/jellyfin-web"
-git clone https://github.com/jellyfin/jellyfin-web.git ${web_build_dir}/
-pushd ${web_build_dir}
-if [[ -n ${web_branch} ]]; then
- checkout -b origin/${web_branch}
-fi
-yarn install
-mkdir -p ${web_target}
-mv dist/* ${web_target}/
-popd
-rm -rf ${web_build_dir}
-
-# Get version
-version="$( grep "version:" ./build.yaml | sed -E 's/version: "([0-9\.]+.*)"/\1/' )"
-
-# Build binary
-dotnet publish --configuration Release --self-contained --runtime win-x86 --output /dist/jellyfin_${version}/ "-p:GenerateDocumentationFile=false;DebugSymbols=false;DebugType=none;UseAppHost=true"
-
-# Prepare addins
-addin_build_dir="$( mktemp -d )"
-wget ${NSSM_URL} -O ${addin_build_dir}/nssm.zip
-wget ${FFMPEG_URL} -O ${addin_build_dir}/ffmpeg.zip
-unzip ${addin_build_dir}/nssm.zip -d ${addin_build_dir}
-cp ${addin_build_dir}/${NSSM_VERSION}/win64/nssm.exe /dist/jellyfin_${version}/nssm.exe
-unzip ${addin_build_dir}/ffmpeg.zip -d ${addin_build_dir}
-cp ${addin_build_dir}/${FFMPEG_VERSION}/bin/ffmpeg.exe /dist/jellyfin_${version}/ffmpeg.exe
-cp ${addin_build_dir}/${FFMPEG_VERSION}/bin/ffprobe.exe /dist/jellyfin_${version}/ffprobe.exe
-rm -rf ${addin_build_dir}
-
-# Prepare scripts
-cp ${SOURCE_DIR}/deployment/windows/legacy/install-jellyfin.ps1 /dist/jellyfin_${version}/install-jellyfin.ps1
-cp ${SOURCE_DIR}/deployment/windows/legacy/install.bat /dist/jellyfin_${version}/install.bat
-
-# Create zip package
-pushd /dist
-zip -r /jellyfin_${version}.portable.zip jellyfin_${version}
-popd
-rm -rf /dist/jellyfin_${version}
-
-# Move the artifacts out
-mkdir -p ${ARTIFACT_DIR}/
-mv /jellyfin[-_]*.zip ${ARTIFACT_DIR}/
-chown -Rc $(stat -c %u:%g ${ARTIFACT_DIR}) ${ARTIFACT_DIR}
diff --git a/deployment/win-x86/package.sh b/deployment/win-x86/package.sh
deleted file mode 100755
index 65e7e2928..000000000
--- a/deployment/win-x86/package.sh
+++ /dev/null
@@ -1,34 +0,0 @@
-#!/usr/bin/env bash
-
-args="${@}"
-declare -a docker_envvars
-for arg in ${args}; do
- docker_envvars+=("-e ${arg}")
-done
-
-WORKDIR="$( pwd )"
-
-package_temporary_dir="${WORKDIR}/pkg-dist-tmp"
-output_dir="${WORKDIR}/pkg-dist"
-current_user="$( whoami )"
-image_name="jellyfin-windows-x86-build"
-
-# Determine if sudo should be used for Docker
-if [[ ! -z $(id -Gn | grep -q 'docker') ]] \
- && [[ ! ${EUID:-1000} -eq 0 ]] \
- && [[ ! ${USER} == "root" ]] \
- && [[ ! -z $( echo "${OSTYPE}" | grep -q "darwin" ) ]]; then
- docker_sudo="sudo"
-else
- docker_sudo=""
-fi
-
-# Prepare temporary package dir
-mkdir -p "${package_temporary_dir}"
-# Set up the build environment Docker image
-${docker_sudo} docker build ../.. -t "${image_name}" -f ./Dockerfile
-# Build the DEBs and copy out to ${package_temporary_dir}
-${docker_sudo} docker run --rm -v "${package_temporary_dir}:/dist" "${image_name}" ${docker_envvars}
-# Move the DEBs to the output directory
-mkdir -p "${output_dir}"
-mv "${package_temporary_dir}"/* "${output_dir}"
diff --git a/deployment/fedora-package-x64/pkg-src/.gitignore b/fedora/.gitignore
index 6019b98c2..6019b98c2 100644
--- a/deployment/fedora-package-x64/pkg-src/.gitignore
+++ b/fedora/.gitignore
diff --git a/fedora/Makefile b/fedora/Makefile
new file mode 100644
index 000000000..97904ddd3
--- /dev/null
+++ b/fedora/Makefile
@@ -0,0 +1,26 @@
+VERSION := $(shell sed -ne '/^Version:/s/.* *//p' fedora/jellyfin.spec)
+
+srpm:
+ cd fedora/; \
+ SOURCE_DIR=.. \
+ WORKDIR="$${PWD}"; \
+ tar \
+ --transform "s,^\.,jellyfin-server-$(VERSION)," \
+ --exclude='.git*' \
+ --exclude='**/.git' \
+ --exclude='**/.hg' \
+ --exclude='**/.vs' \
+ --exclude='**/.vscode' \
+ --exclude='deployment' \
+ --exclude='**/bin' \
+ --exclude='**/obj' \
+ --exclude='**/.nuget' \
+ --exclude='*.deb' \
+ --exclude='*.rpm' \
+ --exclude='jellyfin-server-$(VERSION).tar.gz' \
+ -czf "jellyfin-server-$(VERSION).tar.gz" \
+ -C $${SOURCE_DIR} ./
+ cd fedora/; \
+ rpmbuild -bs jellyfin.spec \
+ --define "_sourcedir $$PWD/" \
+ --define "_srcrpmdir $(outdir)"
diff --git a/deployment/fedora-package-x64/pkg-src/README.md b/fedora/README.md
index 7ed6f7efc..7ed6f7efc 100644
--- a/deployment/fedora-package-x64/pkg-src/README.md
+++ b/fedora/README.md
diff --git a/deployment/fedora-package-x64/pkg-src/jellyfin-firewalld.xml b/fedora/jellyfin-firewalld.xml
index 538c5d65f..538c5d65f 100644
--- a/deployment/fedora-package-x64/pkg-src/jellyfin-firewalld.xml
+++ b/fedora/jellyfin-firewalld.xml
diff --git a/deployment/fedora-package-x64/pkg-src/jellyfin.env b/fedora/jellyfin.env
index de48f13af..bf64acd3f 100644
--- a/deployment/fedora-package-x64/pkg-src/jellyfin.env
+++ b/fedora/jellyfin.env
@@ -20,6 +20,9 @@ JELLYFIN_CONFIG_DIR="/etc/jellyfin"
JELLYFIN_LOG_DIR="/var/log/jellyfin"
JELLYFIN_CACHE_DIR="/var/cache/jellyfin"
+# web client path, installed by the jellyfin-web package
+JELLYFIN_WEB_OPT="--webdir=/usr/share/jellyfin-web"
+
# In-App service control
JELLYFIN_RESTART_OPT="--restartpath=/usr/libexec/jellyfin/restart.sh"
diff --git a/deployment/fedora-package-x64/pkg-src/jellyfin.override.conf b/fedora/jellyfin.override.conf
index 8652450bb..8652450bb 100644
--- a/deployment/fedora-package-x64/pkg-src/jellyfin.override.conf
+++ b/fedora/jellyfin.override.conf
diff --git a/deployment/fedora-package-x64/pkg-src/jellyfin.service b/fedora/jellyfin.service
index f3dc594b1..b092ebf2f 100644
--- a/deployment/fedora-package-x64/pkg-src/jellyfin.service
+++ b/fedora/jellyfin.service
@@ -5,7 +5,7 @@ Description=Jellyfin is a free software media system that puts you in control of
[Service]
EnvironmentFile=/etc/sysconfig/jellyfin
WorkingDirectory=/var/lib/jellyfin
-ExecStart=/usr/bin/jellyfin ${JELLYFIN_RESTART_OPT} ${JELLYFIN_FFMPEG_OPT} ${JELLYFIN_SERVICE_OPT} ${JELLYFIN_NOWEBAPP_OPT}
+ExecStart=/usr/bin/jellyfin ${JELLYFIN_WEB_OPT} ${JELLYFIN_RESTART_OPT} ${JELLYFIN_FFMPEG_OPT} ${JELLYFIN_SERVICE_OPT} ${JELLYFIN_NOWEBAPP_OPT}
TimeoutSec=15
Restart=on-failure
User=jellyfin
diff --git a/deployment/fedora-package-x64/pkg-src/jellyfin.spec b/fedora/jellyfin.spec
index 914f3d44a..9311864a6 100644
--- a/deployment/fedora-package-x64/pkg-src/jellyfin.spec
+++ b/fedora/jellyfin.spec
@@ -7,15 +7,13 @@
%endif
Name: jellyfin
-Version: 10.5.0
+Version: 10.6.0
Release: 1%{?dist}
-Summary: The Free Software Media Browser
-License: GPLv2
-URL: https://jellyfin.media
+Summary: The Free Software Media System
+License: GPLv3
+URL: https://jellyfin.org
# Jellyfin Server tarball created by `make -f .copr/Makefile srpm`, real URL ends with `v%{version}.tar.gz`
-Source0: https://github.com/%{name}/%{name}/archive/%{name}-%{version}.tar.gz
-# Jellyfin Webinterface downloaded by `make -f .copr/Makefile srpm`, real URL ends with `v%{version}.tar.gz`
-Source1: https://github.com/%{name}/%{name}-web/archive/%{name}-web-%{version}.tar.gz
+Source0: jellyfin-server-%{version}.tar.gz
Source11: jellyfin.service
Source12: jellyfin.env
Source13: jellyfin.sudoers
@@ -25,43 +23,30 @@ Source16: jellyfin-firewalld.xml
%{?systemd_requires}
BuildRequires: systemd
-Requires(pre): shadow-utils
BuildRequires: libcurl-devel, fontconfig-devel, freetype-devel, openssl-devel, glibc-devel, libicu-devel
-%if 0%{?fedora}
-BuildRequires: nodejs-yarn
-%else
-# Requirements not packaged in main repos
-# From https://rpm.nodesource.com/pub_8.x/el/7/x86_64/
-BuildRequires: nodejs >= 8 yarn
-%endif
-Requires: libcurl, fontconfig, freetype, openssl, glibc libicu
# Requirements not packaged in main repos
# COPR @dotnet-sig/dotnet or
# https://packages.microsoft.com/rhel/7/prod/
BuildRequires: dotnet-runtime-3.1, dotnet-sdk-3.1
-# RPMfusion free
-Requires: ffmpeg
-
+Requires: %{name}-server = %{version}-%{release}, %{name}-web >= 10.6, %{name}-web < 10.7
# Disable Automatic Dependency Processing
AutoReqProv: no
%description
Jellyfin is a free software media system that puts you in control of managing and streaming your media.
+%package server
+# RPMfusion free
+Summary: The Free Software Media System Server backend
+Requires(pre): shadow-utils
+Requires: ffmpeg
+Requires: libcurl, fontconfig, freetype, openssl, glibc libicu
+
+%description server
+The Jellyfin media server backend.
%prep
-%autosetup -n %{name}-%{version} -b 0 -b 1
-web_build_dir="$(mktemp -d)"
-web_target="$PWD/MediaBrowser.WebDashboard/jellyfin-web"
-pushd ../jellyfin-web-%{version} || pushd ../jellyfin-web-master
-%if 0%{?fedora}
-nodejs-yarn install
-%else
-yarn install
-%endif
-mkdir -p ${web_target}
-mv dist/* ${web_target}/
-popd
+%autosetup -n jellyfin-server-%{version} -b 0
%build
@@ -70,81 +55,76 @@ export DOTNET_CLI_TELEMETRY_OPTOUT=1
export DOTNET_SKIP_FIRST_TIME_EXPERIENCE=1
dotnet publish --configuration Release --output='%{buildroot}%{_libdir}/jellyfin' --self-contained --runtime %{dotnet_runtime} \
"-p:GenerateDocumentationFile=false;DebugSymbols=false;DebugType=none" Jellyfin.Server
-%{__install} -D -m 0644 LICENSE %{buildroot}%{_datadir}/licenses/%{name}/LICENSE
-%{__install} -D -m 0644 %{SOURCE15} %{buildroot}%{_sysconfdir}/systemd/system/%{name}.service.d/override.conf
-%{__install} -D -m 0644 Jellyfin.Server/Resources/Configuration/logging.json %{buildroot}%{_sysconfdir}/%{name}/logging.json
+%{__install} -D -m 0644 LICENSE %{buildroot}%{_datadir}/licenses/jellyfin/LICENSE
+%{__install} -D -m 0644 %{SOURCE15} %{buildroot}%{_sysconfdir}/systemd/system/jellyfin.service.d/override.conf
+%{__install} -D -m 0644 Jellyfin.Server/Resources/Configuration/logging.json %{buildroot}%{_sysconfdir}/jellyfin/logging.json
%{__mkdir} -p %{buildroot}%{_bindir}
tee %{buildroot}%{_bindir}/jellyfin << EOF
#!/bin/sh
-exec %{_libdir}/%{name}/%{name} \${@}
+exec %{_libdir}/jellyfin/jellyfin \${@}
EOF
%{__mkdir} -p %{buildroot}%{_sharedstatedir}/jellyfin
-%{__mkdir} -p %{buildroot}%{_sysconfdir}/%{name}
+%{__mkdir} -p %{buildroot}%{_sysconfdir}/jellyfin
%{__mkdir} -p %{buildroot}%{_var}/log/jellyfin
%{__mkdir} -p %{buildroot}%{_var}/cache/jellyfin
-%{__install} -D -m 0644 %{SOURCE11} %{buildroot}%{_unitdir}/%{name}.service
-%{__install} -D -m 0644 %{SOURCE12} %{buildroot}%{_sysconfdir}/sysconfig/%{name}
-%{__install} -D -m 0600 %{SOURCE13} %{buildroot}%{_sysconfdir}/sudoers.d/%{name}-sudoers
-%{__install} -D -m 0755 %{SOURCE14} %{buildroot}%{_libexecdir}/%{name}/restart.sh
-%{__install} -D -m 0644 %{SOURCE16} %{buildroot}%{_prefix}/lib/firewalld/services/%{name}.xml
+%{__install} -D -m 0644 %{SOURCE11} %{buildroot}%{_unitdir}/jellyfin.service
+%{__install} -D -m 0644 %{SOURCE12} %{buildroot}%{_sysconfdir}/sysconfig/jellyfin
+%{__install} -D -m 0600 %{SOURCE13} %{buildroot}%{_sysconfdir}/sudoers.d/jellyfin-sudoers
+%{__install} -D -m 0755 %{SOURCE14} %{buildroot}%{_libexecdir}/jellyfin/restart.sh
+%{__install} -D -m 0644 %{SOURCE16} %{buildroot}%{_prefix}/lib/firewalld/services/jellyfin.xml
-%files
-%{_libdir}/%{name}/jellyfin-web/*
-%attr(755,root,root) %{_bindir}/%{name}
-%{_libdir}/%{name}/*.json
-%{_libdir}/%{name}/*.dll
-%{_libdir}/%{name}/*.so
-%{_libdir}/%{name}/*.a
-%{_libdir}/%{name}/createdump
+%files server
+%attr(755,root,root) %{_bindir}/jellyfin
+%{_libdir}/jellyfin/*.json
+%{_libdir}/jellyfin/*.dll
+%{_libdir}/jellyfin/*.so
+%{_libdir}/jellyfin/*.a
+%{_libdir}/jellyfin/createdump
# Needs 755 else only root can run it since binary build by dotnet is 722
-%attr(755,root,root) %{_libdir}/%{name}/jellyfin
-%{_libdir}/%{name}/SOS_README.md
-%{_unitdir}/%{name}.service
-%{_libexecdir}/%{name}/restart.sh
-%{_prefix}/lib/firewalld/services/%{name}.xml
-%attr(755,jellyfin,jellyfin) %dir %{_sysconfdir}/%{name}
-%config %{_sysconfdir}/sysconfig/%{name}
-%config(noreplace) %attr(600,root,root) %{_sysconfdir}/sudoers.d/%{name}-sudoers
-%config(noreplace) %{_sysconfdir}/systemd/system/%{name}.service.d/override.conf
-%config(noreplace) %attr(644,jellyfin,jellyfin) %{_sysconfdir}/%{name}/logging.json
+%attr(755,root,root) %{_libdir}/jellyfin/jellyfin
+%{_libdir}/jellyfin/SOS_README.md
+%{_unitdir}/jellyfin.service
+%{_libexecdir}/jellyfin/restart.sh
+%{_prefix}/lib/firewalld/services/jellyfin.xml
+%attr(755,jellyfin,jellyfin) %dir %{_sysconfdir}/jellyfin
+%config %{_sysconfdir}/sysconfig/jellyfin
+%config(noreplace) %attr(600,root,root) %{_sysconfdir}/sudoers.d/jellyfin-sudoers
+%config(noreplace) %{_sysconfdir}/systemd/system/jellyfin.service.d/override.conf
+%config(noreplace) %attr(644,jellyfin,jellyfin) %{_sysconfdir}/jellyfin/logging.json
%attr(750,jellyfin,jellyfin) %dir %{_sharedstatedir}/jellyfin
%attr(-,jellyfin,jellyfin) %dir %{_var}/log/jellyfin
%attr(750,jellyfin,jellyfin) %dir %{_var}/cache/jellyfin
-%if 0%{?fedora}
-%license LICENSE
-%else
-%{_datadir}/licenses/%{name}/LICENSE
-%endif
+%{_datadir}/licenses/jellyfin/LICENSE
-%pre
+%pre server
getent group jellyfin >/dev/null || groupadd -r jellyfin
getent passwd jellyfin >/dev/null || \
useradd -r -g jellyfin -d %{_sharedstatedir}/jellyfin -s /sbin/nologin \
-c "Jellyfin default user" jellyfin
exit 0
-%post
+%post server
# Move existing configuration cache and logs to their new locations and symlink them.
if [ $1 -gt 1 ] ; then
service_state=$(systemctl is-active jellyfin.service)
if [ "${service_state}" = "active" ]; then
systemctl stop jellyfin.service
fi
- if [ ! -L %{_sharedstatedir}/%{name}/config ]; then
- mv %{_sharedstatedir}/%{name}/config/* %{_sysconfdir}/%{name}/
- rmdir %{_sharedstatedir}/%{name}/config
- ln -sf %{_sysconfdir}/%{name} %{_sharedstatedir}/%{name}/config
+ if [ ! -L %{_sharedstatedir}/jellyfin/config ]; then
+ mv %{_sharedstatedir}/jellyfin/config/* %{_sysconfdir}/jellyfin/
+ rmdir %{_sharedstatedir}/jellyfin/config
+ ln -sf %{_sysconfdir}/jellyfin %{_sharedstatedir}/jellyfin/config
fi
- if [ ! -L %{_sharedstatedir}/%{name}/logs ]; then
- mv %{_sharedstatedir}/%{name}/logs/* %{_var}/log/jellyfin
- rmdir %{_sharedstatedir}/%{name}/logs
- ln -sf %{_var}/log/jellyfin %{_sharedstatedir}/%{name}/logs
+ if [ ! -L %{_sharedstatedir}/jellyfin/logs ]; then
+ mv %{_sharedstatedir}/jellyfin/logs/* %{_var}/log/jellyfin
+ rmdir %{_sharedstatedir}/jellyfin/logs
+ ln -sf %{_var}/log/jellyfin %{_sharedstatedir}/jellyfin/logs
fi
- if [ ! -L %{_sharedstatedir}/%{name}/cache ]; then
- mv %{_sharedstatedir}/%{name}/cache/* %{_var}/cache/jellyfin
- rmdir %{_sharedstatedir}/%{name}/cache
- ln -sf %{_var}/cache/jellyfin %{_sharedstatedir}/%{name}/cache
+ if [ ! -L %{_sharedstatedir}/jellyfin/cache ]; then
+ mv %{_sharedstatedir}/jellyfin/cache/* %{_var}/cache/jellyfin
+ rmdir %{_sharedstatedir}/jellyfin/cache
+ ln -sf %{_var}/cache/jellyfin %{_sharedstatedir}/jellyfin/cache
fi
if [ "${service_state}" = "active" ]; then
systemctl start jellyfin.service
@@ -152,13 +132,15 @@ if [ $1 -gt 1 ] ; then
fi
%systemd_post jellyfin.service
-%preun
+%preun server
%systemd_preun jellyfin.service
-%postun
+%postun server
%systemd_postun_with_restart jellyfin.service
%changelog
+* Mon Mar 23 2020 Jellyfin Packaging Team <packaging@jellyfin.org>
+- Forthcoming stable release
* Fri Oct 11 2019 Jellyfin Packaging Team <packaging@jellyfin.org>
- New upstream version 10.5.0; release changelog at https://github.com/jellyfin/jellyfin/releases/tag/v10.5.0
* Sat Aug 31 2019 Jellyfin Packaging Team <packaging@jellyfin.org>
diff --git a/deployment/fedora-package-x64/pkg-src/jellyfin.sudoers b/fedora/jellyfin.sudoers
index dd245af4b..dd245af4b 100644
--- a/deployment/fedora-package-x64/pkg-src/jellyfin.sudoers
+++ b/fedora/jellyfin.sudoers
diff --git a/deployment/fedora-package-x64/pkg-src/restart.sh b/fedora/restart.sh
index 9b64b6d72..9e53efecd 100755
--- a/deployment/fedora-package-x64/pkg-src/restart.sh
+++ b/fedora/restart.sh
@@ -24,13 +24,13 @@ cmd="$( get_service_command )"
echo "Detected service control platform '$cmd'; using it to restart Jellyfin..."
case $cmd in
'systemctl')
- echo "sleep 2; /usr/bin/sudo $( which systemctl ) restart jellyfin" | at now
+ echo "sleep 2; /usr/bin/sudo $( which systemctl ) restart jellyfin" | at now
;;
'service')
- echo "sleep 2; /usr/bin/sudo $( which service ) jellyfin restart" | at now
+ echo "sleep 2; /usr/bin/sudo $( which service ) jellyfin restart" | at now
;;
'sysv')
- echo "sleep 2; /usr/bin/sudo /etc/init.d/jellyfin restart" | at now
+ echo "sleep 2; /usr/bin/sudo /etc/init.d/jellyfin restart" | at now
;;
esac
exit 0
diff --git a/jellyfin.ruleset b/jellyfin.ruleset
index 92b7a03fd..45ab725eb 100644
--- a/jellyfin.ruleset
+++ b/jellyfin.ruleset
@@ -5,6 +5,8 @@
<Rule Id="SA1202" Action="Info" />
<!-- disable warning SA1204: Static members must appear before non-static members -->
<Rule Id="SA1204" Action="Info" />
+ <!-- disable warning SA1404: Code analysis suppression should have justification -->
+ <Rule Id="SA1404" Action="Info" />
<!-- disable warning SA1009: Closing parenthesis should be followed by a space. -->
<Rule Id="SA1009" Action="None" />
@@ -26,6 +28,8 @@
<Rule Id="SA1512" Action="None" />
<!-- disable warning SA1515: Single-line comment should be preceded by blank line -->
<Rule Id="SA1515" Action="None" />
+ <!-- disable warning SA1600: Elements should be documented -->
+ <Rule Id="SA1600" Action="None" />
<!-- disable warning SA1633: The file header is missing or not located at the top of the file -->
<Rule Id="SA1633" Action="None" />
</Rules>
diff --git a/nuget.config b/nuget.config
new file mode 100644
index 000000000..326331f32
--- /dev/null
+++ b/nuget.config
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<configuration>
+ <packageSources>
+ <add key="NuGet official package source" value="https://api.nuget.org/v3/index.json" />
+ </packageSources>
+</configuration>
diff --git a/tests/Jellyfin.Api.Tests/Auth/CustomAuthenticationHandlerTests.cs b/tests/Jellyfin.Api.Tests/Auth/CustomAuthenticationHandlerTests.cs
new file mode 100644
index 000000000..3b3d03c8b
--- /dev/null
+++ b/tests/Jellyfin.Api.Tests/Auth/CustomAuthenticationHandlerTests.cs
@@ -0,0 +1,175 @@
+using System;
+using System.Linq;
+using System.Security.Claims;
+using System.Text.Encodings.Web;
+using System.Threading.Tasks;
+using AutoFixture;
+using AutoFixture.AutoMoq;
+using Jellyfin.Api.Auth;
+using Jellyfin.Api.Constants;
+using MediaBrowser.Controller.Entities;
+using MediaBrowser.Controller.Net;
+using Microsoft.AspNetCore.Authentication;
+using Microsoft.AspNetCore.Http;
+using Microsoft.Extensions.Logging;
+using Microsoft.Extensions.Logging.Abstractions;
+using Microsoft.Extensions.Options;
+using Moq;
+using Xunit;
+
+namespace Jellyfin.Api.Tests.Auth
+{
+ public class CustomAuthenticationHandlerTests
+ {
+ private readonly IFixture _fixture;
+
+ private readonly Mock<IAuthService> _jellyfinAuthServiceMock;
+ private readonly Mock<IOptionsMonitor<AuthenticationSchemeOptions>> _optionsMonitorMock;
+ private readonly Mock<ISystemClock> _clockMock;
+ private readonly Mock<IServiceProvider> _serviceProviderMock;
+ private readonly Mock<IAuthenticationService> _authenticationServiceMock;
+ private readonly UrlEncoder _urlEncoder;
+ private readonly HttpContext _context;
+
+ private readonly CustomAuthenticationHandler _sut;
+ private readonly AuthenticationScheme _scheme;
+
+ public CustomAuthenticationHandlerTests()
+ {
+ var fixtureCustomizations = new AutoMoqCustomization
+ {
+ ConfigureMembers = true
+ };
+
+ _fixture = new Fixture().Customize(fixtureCustomizations);
+ AllowFixtureCircularDependencies();
+
+ _jellyfinAuthServiceMock = _fixture.Freeze<Mock<IAuthService>>();
+ _optionsMonitorMock = _fixture.Freeze<Mock<IOptionsMonitor<AuthenticationSchemeOptions>>>();
+ _clockMock = _fixture.Freeze<Mock<ISystemClock>>();
+ _serviceProviderMock = _fixture.Freeze<Mock<IServiceProvider>>();
+ _authenticationServiceMock = _fixture.Freeze<Mock<IAuthenticationService>>();
+ _fixture.Register<ILoggerFactory>(() => new NullLoggerFactory());
+
+ _urlEncoder = UrlEncoder.Default;
+
+ _serviceProviderMock.Setup(s => s.GetService(typeof(IAuthenticationService)))
+ .Returns(_authenticationServiceMock.Object);
+
+ _optionsMonitorMock.Setup(o => o.Get(It.IsAny<string>()))
+ .Returns(new AuthenticationSchemeOptions
+ {
+ ForwardAuthenticate = null
+ });
+
+ _context = new DefaultHttpContext
+ {
+ RequestServices = _serviceProviderMock.Object
+ };
+
+ _scheme = new AuthenticationScheme(
+ _fixture.Create<string>(),
+ null,
+ typeof(CustomAuthenticationHandler));
+
+ _sut = _fixture.Create<CustomAuthenticationHandler>();
+ _sut.InitializeAsync(_scheme, _context).Wait();
+ }
+
+ [Fact]
+ public async Task HandleAuthenticateAsyncShouldFailWithNullUser()
+ {
+ _jellyfinAuthServiceMock.Setup(
+ a => a.Authenticate(
+ It.IsAny<HttpRequest>(),
+ It.IsAny<AuthenticatedAttribute>()))
+ .Returns((User?)null);
+
+ var authenticateResult = await _sut.AuthenticateAsync();
+
+ Assert.False(authenticateResult.Succeeded);
+ Assert.Equal("Invalid user", authenticateResult.Failure.Message);
+ }
+
+ [Fact]
+ public async Task HandleAuthenticateAsyncShouldFailOnSecurityException()
+ {
+ var errorMessage = _fixture.Create<string>();
+
+ _jellyfinAuthServiceMock.Setup(
+ a => a.Authenticate(
+ It.IsAny<HttpRequest>(),
+ It.IsAny<AuthenticatedAttribute>()))
+ .Throws(new SecurityException(errorMessage));
+
+ var authenticateResult = await _sut.AuthenticateAsync();
+
+ Assert.False(authenticateResult.Succeeded);
+ Assert.Equal(errorMessage, authenticateResult.Failure.Message);
+ }
+
+ [Fact]
+ public async Task HandleAuthenticateAsyncShouldSucceedWithUser()
+ {
+ SetupUser();
+ var authenticateResult = await _sut.AuthenticateAsync();
+
+ Assert.True(authenticateResult.Succeeded);
+ Assert.Null(authenticateResult.Failure);
+ }
+
+ [Fact]
+ public async Task HandleAuthenticateAsyncShouldAssignNameClaim()
+ {
+ var user = SetupUser();
+ var authenticateResult = await _sut.AuthenticateAsync();
+
+ Assert.True(authenticateResult.Principal.HasClaim(ClaimTypes.Name, user.Name));
+ }
+
+ [Theory]
+ [InlineData(true)]
+ [InlineData(false)]
+ public async Task HandleAuthenticateAsyncShouldAssignRoleClaim(bool isAdmin)
+ {
+ var user = SetupUser(isAdmin);
+ var authenticateResult = await _sut.AuthenticateAsync();
+
+ var expectedRole = user.Policy.IsAdministrator ? UserRoles.Administrator : UserRoles.User;
+ Assert.True(authenticateResult.Principal.HasClaim(ClaimTypes.Role, expectedRole));
+ }
+
+ [Fact]
+ public async Task HandleAuthenticateAsyncShouldAssignTicketCorrectScheme()
+ {
+ SetupUser();
+ var authenticatedResult = await _sut.AuthenticateAsync();
+
+ Assert.Equal(_scheme.Name, authenticatedResult.Ticket.AuthenticationScheme);
+ }
+
+ private User SetupUser(bool isAdmin = false)
+ {
+ var user = _fixture.Create<User>();
+ user.Policy.IsAdministrator = isAdmin;
+
+ _jellyfinAuthServiceMock.Setup(
+ a => a.Authenticate(
+ It.IsAny<HttpRequest>(),
+ It.IsAny<AuthenticatedAttribute>()))
+ .Returns(user);
+
+ return user;
+ }
+
+ private void AllowFixtureCircularDependencies()
+ {
+ // A circular dependency exists in the User entity around parent folders,
+ // this allows Autofixture to generate a User regardless, rather than throw
+ // an error.
+ _fixture.Behaviors.OfType<ThrowingRecursionBehavior>().ToList()
+ .ForEach(b => _fixture.Behaviors.Remove(b));
+ _fixture.Behaviors.Add(new OmitOnRecursionBehavior());
+ }
+ }
+}
diff --git a/tests/Jellyfin.Api.Tests/Auth/FirstTimeSetupOrElevatedPolicy/FirstTimeSetupOrElevatedHandlerTests.cs b/tests/Jellyfin.Api.Tests/Auth/FirstTimeSetupOrElevatedPolicy/FirstTimeSetupOrElevatedHandlerTests.cs
new file mode 100644
index 000000000..e40af703f
--- /dev/null
+++ b/tests/Jellyfin.Api.Tests/Auth/FirstTimeSetupOrElevatedPolicy/FirstTimeSetupOrElevatedHandlerTests.cs
@@ -0,0 +1,77 @@
+using System.Collections.Generic;
+using System.Security.Claims;
+using System.Threading.Tasks;
+using AutoFixture;
+using AutoFixture.AutoMoq;
+using Jellyfin.Api.Auth.FirstTimeSetupOrElevatedPolicy;
+using Jellyfin.Api.Constants;
+using MediaBrowser.Common.Configuration;
+using MediaBrowser.Model.Configuration;
+using Microsoft.AspNetCore.Authorization;
+using Moq;
+using Xunit;
+
+namespace Jellyfin.Api.Tests.Auth.FirstTimeSetupOrElevatedPolicy
+{
+ public class FirstTimeSetupOrElevatedHandlerTests
+ {
+ private readonly Mock<IConfigurationManager> _configurationManagerMock;
+ private readonly List<IAuthorizationRequirement> _requirements;
+ private readonly FirstTimeSetupOrElevatedHandler _sut;
+
+ public FirstTimeSetupOrElevatedHandlerTests()
+ {
+ var fixture = new Fixture().Customize(new AutoMoqCustomization());
+ _configurationManagerMock = fixture.Freeze<Mock<IConfigurationManager>>();
+ _requirements = new List<IAuthorizationRequirement> { new FirstTimeSetupOrElevatedRequirement() };
+
+ _sut = fixture.Create<FirstTimeSetupOrElevatedHandler>();
+ }
+
+ [Theory]
+ [InlineData(UserRoles.Administrator)]
+ [InlineData(UserRoles.Guest)]
+ [InlineData(UserRoles.User)]
+ public async Task ShouldSucceedIfStartupWizardIncomplete(string userRole)
+ {
+ SetupConfigurationManager(false);
+ var user = SetupUser(userRole);
+ var context = new AuthorizationHandlerContext(_requirements, user, null);
+
+ await _sut.HandleAsync(context);
+ Assert.True(context.HasSucceeded);
+ }
+
+ [Theory]
+ [InlineData(UserRoles.Administrator, true)]
+ [InlineData(UserRoles.Guest, false)]
+ [InlineData(UserRoles.User, false)]
+ public async Task ShouldRequireAdministratorIfStartupWizardComplete(string userRole, bool shouldSucceed)
+ {
+ SetupConfigurationManager(true);
+ var user = SetupUser(userRole);
+ var context = new AuthorizationHandlerContext(_requirements, user, null);
+
+ await _sut.HandleAsync(context);
+ Assert.Equal(shouldSucceed, context.HasSucceeded);
+ }
+
+ private static ClaimsPrincipal SetupUser(string role)
+ {
+ var claims = new[] { new Claim(ClaimTypes.Role, role) };
+ var identity = new ClaimsIdentity(claims);
+ return new ClaimsPrincipal(identity);
+ }
+
+ private void SetupConfigurationManager(bool startupWizardCompleted)
+ {
+ var commonConfiguration = new BaseApplicationConfiguration
+ {
+ IsStartupWizardCompleted = startupWizardCompleted
+ };
+
+ _configurationManagerMock.Setup(c => c.CommonConfiguration)
+ .Returns(commonConfiguration);
+ }
+ }
+}
diff --git a/tests/Jellyfin.Api.Tests/Auth/RequiresElevationPolicy/RequiresElevationHandlerTests.cs b/tests/Jellyfin.Api.Tests/Auth/RequiresElevationPolicy/RequiresElevationHandlerTests.cs
new file mode 100644
index 000000000..cd05a8328
--- /dev/null
+++ b/tests/Jellyfin.Api.Tests/Auth/RequiresElevationPolicy/RequiresElevationHandlerTests.cs
@@ -0,0 +1,38 @@
+using System.Collections.Generic;
+using System.Security.Claims;
+using System.Threading.Tasks;
+using Jellyfin.Api.Auth.RequiresElevationPolicy;
+using Jellyfin.Api.Constants;
+using Microsoft.AspNetCore.Authorization;
+using Xunit;
+
+namespace Jellyfin.Api.Tests.Auth.RequiresElevationPolicy
+{
+ public class RequiresElevationHandlerTests
+ {
+ private readonly RequiresElevationHandler _sut;
+
+ public RequiresElevationHandlerTests()
+ {
+ _sut = new RequiresElevationHandler();
+ }
+
+ [Theory]
+ [InlineData(UserRoles.Administrator, true)]
+ [InlineData(UserRoles.User, false)]
+ [InlineData(UserRoles.Guest, false)]
+ public async Task ShouldHandleRolesCorrectly(string role, bool shouldSucceed)
+ {
+ var requirements = new List<IAuthorizationRequirement> { new RequiresElevationRequirement() };
+
+ var claims = new[] { new Claim(ClaimTypes.Role, role) };
+ var identity = new ClaimsIdentity(claims);
+ var user = new ClaimsPrincipal(identity);
+
+ var context = new AuthorizationHandlerContext(requirements, user, null);
+
+ await _sut.HandleAsync(context);
+ Assert.Equal(shouldSucceed, context.HasSucceeded);
+ }
+ }
+}
diff --git a/tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj b/tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj
index e0deeeabb..fb76f34d0 100644
--- a/tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj
+++ b/tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj
@@ -1,20 +1,32 @@
<Project Sdk="Microsoft.NET.Sdk">
+ <!-- ProjectGuid is only included as a requirement for SonarQube analysis -->
+ <PropertyGroup>
+ <ProjectGuid>{A2FD0A10-8F62-4F9D-B171-FFDF9F0AFA9D}</ProjectGuid>
+ </PropertyGroup>
+
<PropertyGroup>
<TargetFramework>netcoreapp3.1</TargetFramework>
<IsPackable>false</IsPackable>
+ <TreatWarningsAsErrors>true</TreatWarningsAsErrors>
+ <Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
- <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.4.0" />
+ <PackageReference Include="AutoFixture" Version="4.11.0" />
+ <PackageReference Include="AutoFixture.AutoMoq" Version="4.11.0" />
+ <PackageReference Include="AutoFixture.Xunit2" Version="4.11.0" />
+ <PackageReference Include="Microsoft.Extensions.Options" Version="3.1.3" />
+ <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.5.0" />
<PackageReference Include="xunit" Version="2.4.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.1" />
- <PackageReference Include="coverlet.collector" Version="1.1.0" />
+ <PackageReference Include="coverlet.collector" Version="1.2.1" />
<PackageReference Include="Moq" Version="4.13.1" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="../../MediaBrowser.Api/MediaBrowser.Api.csproj" />
+ <ProjectReference Include="../../Jellyfin.Api/Jellyfin.Api.csproj" />
</ItemGroup>
</Project>
diff --git a/tests/Jellyfin.Common.Tests/Extensions/StringExtensionsTests.cs b/tests/Jellyfin.Common.Tests/Extensions/StringExtensionsTests.cs
new file mode 100644
index 000000000..8bf613f05
--- /dev/null
+++ b/tests/Jellyfin.Common.Tests/Extensions/StringExtensionsTests.cs
@@ -0,0 +1,43 @@
+using System;
+using MediaBrowser.Common.Extensions;
+using Xunit;
+
+namespace Jellyfin.Common.Tests.Extensions
+{
+ public class StringExtensionsTests
+ {
+ [Theory]
+ [InlineData("", 'q', "")]
+ [InlineData("Banana split", ' ', "Banana")]
+ [InlineData("Banana split", 'q', "Banana split")]
+ public void LeftPart_ValidArgsCharNeedle_Correct(string str, char needle, string expectedResult)
+ {
+ var result = str.AsSpan().LeftPart(needle).ToString();
+ Assert.Equal(expectedResult, result);
+ }
+
+ [Theory]
+ [InlineData("", "", "")]
+ [InlineData("", "q", "")]
+ [InlineData("Banana split", "", "")]
+ [InlineData("Banana split", " ", "Banana")]
+ [InlineData("Banana split test", " split", "Banana")]
+ public void LeftPart_ValidArgsWithoutStringComparison_Correct(string str, string needle, string expectedResult)
+ {
+ var result = str.AsSpan().LeftPart(needle).ToString();
+ Assert.Equal(expectedResult, result);
+ }
+
+ [Theory]
+ [InlineData("", "", StringComparison.Ordinal, "")]
+ [InlineData("Banana split", " ", StringComparison.Ordinal, "Banana")]
+ [InlineData("Banana split test", " split", StringComparison.Ordinal, "Banana")]
+ [InlineData("Banana split test", " Split", StringComparison.Ordinal, "Banana split test")]
+ [InlineData("Banana split test", " Splït", StringComparison.InvariantCultureIgnoreCase, "Banana split test")]
+ public void LeftPart_ValidArgs_Correct(string str, string needle, StringComparison stringComparison, string expectedResult)
+ {
+ var result = str.AsSpan().LeftPart(needle, stringComparison).ToString();
+ Assert.Equal(expectedResult, result);
+ }
+ }
+}
diff --git a/tests/Jellyfin.Common.Tests/Jellyfin.Common.Tests.csproj b/tests/Jellyfin.Common.Tests/Jellyfin.Common.Tests.csproj
index bc0114d1e..cd41c5604 100644
--- a/tests/Jellyfin.Common.Tests/Jellyfin.Common.Tests.csproj
+++ b/tests/Jellyfin.Common.Tests/Jellyfin.Common.Tests.csproj
@@ -1,15 +1,22 @@
<Project Sdk="Microsoft.NET.Sdk">
+ <!-- ProjectGuid is only included as a requirement for SonarQube analysis -->
+ <PropertyGroup>
+ <ProjectGuid>{DF194677-DFD3-42AF-9F75-D44D5A416478}</ProjectGuid>
+ </PropertyGroup>
+
<PropertyGroup>
<TargetFramework>netcoreapp3.1</TargetFramework>
<IsPackable>false</IsPackable>
+ <TreatWarningsAsErrors>true</TreatWarningsAsErrors>
+ <Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
- <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.4.0" />
+ <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.5.0" />
<PackageReference Include="xunit" Version="2.4.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.1" />
- <PackageReference Include="coverlet.collector" Version="1.1.0" />
+ <PackageReference Include="coverlet.collector" Version="1.2.1" />
</ItemGroup>
<ItemGroup>
diff --git a/tests/Jellyfin.Controller.Tests/AlphanumComparatorTests.cs b/tests/Jellyfin.Controller.Tests/AlphanumComparatorTests.cs
new file mode 100644
index 000000000..929bb92aa
--- /dev/null
+++ b/tests/Jellyfin.Controller.Tests/AlphanumComparatorTests.cs
@@ -0,0 +1,44 @@
+using System;
+using System.Linq;
+using MediaBrowser.Common.Extensions;
+using MediaBrowser.Controller.Sorting;
+using Xunit;
+
+namespace Jellyfin.Controller.Tests
+{
+ public class AlphanumComparatorTests
+ {
+ private readonly Random _rng = new Random(42);
+
+ // InlineData is pre-sorted
+ [Theory]
+ [InlineData(null, "", "1", "9", "10", "a", "z")]
+ [InlineData("50F", "100F", "SR9", "SR100")]
+ [InlineData("image-1.jpg", "image-02.jpg", "image-4.jpg", "image-9.jpg", "image-10.jpg", "image-11.jpg", "image-22.jpg")]
+ [InlineData("Hard drive 2GB", "Hard drive 20GB")]
+ [InlineData("b", "e", "è", "ě", "f", "g", "k")]
+ [InlineData("123456789", "123456789a", "abc", "abcd")]
+ [InlineData("12345678912345678912345678913234567891", "123456789123456789123456789132345678912")]
+ [InlineData("12345678912345678912345678913234567891", "12345678912345678912345678913234567891")]
+ [InlineData("12345678912345678912345678913234567891", "12345678912345678912345678913234567892")]
+ [InlineData("12345678912345678912345678913234567891a", "12345678912345678912345678913234567891a")]
+ [InlineData("12345678912345678912345678913234567891a", "12345678912345678912345678913234567891b")]
+ public void AlphanumComparatorTest(params string?[] strings)
+ {
+ var copy = (string?[])strings.Clone();
+ if (strings.Length == 2)
+ {
+ var tmp = copy[0];
+ copy[0] = copy[1];
+ copy[1] = tmp;
+ }
+ else
+ {
+ copy.Shuffle(_rng);
+ }
+
+ Array.Sort(copy, new AlphanumComparator());
+ Assert.True(strings.SequenceEqual(copy));
+ }
+ }
+}
diff --git a/tests/Jellyfin.Controller.Tests/Jellyfin.Controller.Tests.csproj b/tests/Jellyfin.Controller.Tests/Jellyfin.Controller.Tests.csproj
new file mode 100644
index 000000000..407fe2eda
--- /dev/null
+++ b/tests/Jellyfin.Controller.Tests/Jellyfin.Controller.Tests.csproj
@@ -0,0 +1,26 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+ <!-- ProjectGuid is only included as a requirement for SonarQube analysis -->
+ <PropertyGroup>
+ <ProjectGuid>{462584F7-5023-4019-9EAC-B98CA458C0A0}</ProjectGuid>
+ </PropertyGroup>
+
+ <PropertyGroup>
+ <TargetFramework>netcoreapp3.1</TargetFramework>
+ <IsPackable>false</IsPackable>
+ <TreatWarningsAsErrors>true</TreatWarningsAsErrors>
+ <Nullable>enable</Nullable>
+ </PropertyGroup>
+
+ <ItemGroup>
+ <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.5.0" />
+ <PackageReference Include="xunit" Version="2.4.1" />
+ <PackageReference Include="xunit.runner.visualstudio" Version="2.4.1" />
+ <PackageReference Include="coverlet.collector" Version="1.2.1" />
+ </ItemGroup>
+
+ <ItemGroup>
+ <ProjectReference Include="../../MediaBrowser.Controller/MediaBrowser.Controller.csproj" />
+ </ItemGroup>
+
+</Project>
diff --git a/tests/Jellyfin.MediaEncoding.Tests/EncoderValidatorTests.cs b/tests/Jellyfin.MediaEncoding.Tests/EncoderValidatorTests.cs
index a7848316e..e0f1f236c 100644
--- a/tests/Jellyfin.MediaEncoding.Tests/EncoderValidatorTests.cs
+++ b/tests/Jellyfin.MediaEncoding.Tests/EncoderValidatorTests.cs
@@ -9,15 +9,15 @@ namespace Jellyfin.MediaEncoding.Tests
{
public class EncoderValidatorTests
{
- private class GetFFmpegVersionTestData : IEnumerable<object[]>
+ private class GetFFmpegVersionTestData : IEnumerable<object?[]>
{
- public IEnumerator<object[]> GetEnumerator()
+ public IEnumerator<object?[]> GetEnumerator()
{
- yield return new object[] { EncoderValidatorTestsData.FFmpegV421Output, new Version(4, 2, 1) };
- yield return new object[] { EncoderValidatorTestsData.FFmpegV42Output, new Version(4, 2) };
- yield return new object[] { EncoderValidatorTestsData.FFmpegV414Output, new Version(4, 1, 4) };
- yield return new object[] { EncoderValidatorTestsData.FFmpegV404Output, new Version(4, 0, 4) };
- yield return new object[] { EncoderValidatorTestsData.FFmpegGitUnknownOutput, null };
+ yield return new object?[] { EncoderValidatorTestsData.FFmpegV421Output, new Version(4, 2, 1) };
+ yield return new object?[] { EncoderValidatorTestsData.FFmpegV42Output, new Version(4, 2) };
+ yield return new object?[] { EncoderValidatorTestsData.FFmpegV414Output, new Version(4, 1, 4) };
+ yield return new object?[] { EncoderValidatorTestsData.FFmpegV404Output, new Version(4, 0, 4) };
+ yield return new object?[] { EncoderValidatorTestsData.FFmpegGitUnknownOutput, null };
}
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
@@ -25,7 +25,7 @@ namespace Jellyfin.MediaEncoding.Tests
[Theory]
[ClassData(typeof(GetFFmpegVersionTestData))]
- public void GetFFmpegVersionTest(string versionOutput, Version version)
+ public void GetFFmpegVersionTest(string versionOutput, Version? version)
{
Assert.Equal(version, EncoderValidator.GetFFmpegVersion(versionOutput));
}
diff --git a/tests/Jellyfin.MediaEncoding.Tests/EncoderValidatorTestsData.cs b/tests/Jellyfin.MediaEncoding.Tests/EncoderValidatorTestsData.cs
index 12fde0770..c46c9578b 100644
--- a/tests/Jellyfin.MediaEncoding.Tests/EncoderValidatorTestsData.cs
+++ b/tests/Jellyfin.MediaEncoding.Tests/EncoderValidatorTestsData.cs
@@ -26,7 +26,7 @@ libswscale 5. 5.100 / 5. 5.100
libswresample 3. 5.100 / 3. 5.100
libpostproc 55. 5.100 / 55. 5.100";
- public const string FFmpegV414Output = @"ffmpeg version 4.1.4-1~deb10u1 Copyright (c) 2000-2019 the FFmpeg developers
+ public const string FFmpegV414Output = @"ffmpeg version 4.1.4-1~deb10u1 Copyright (c) 2000-2019 the FFmpeg developers
built with gcc 8 (Raspbian 8.3.0-6+rpi1)
configuration: --prefix=/usr --extra-version='1~deb10u1' --toolchain=hardened --libdir=/usr/lib/arm-linux-gnueabihf --incdir=/usr/include/arm-linux-gnueabihf --arch=arm --enable-gpl --disable-stripping --enable-avresample --disable-filter=resample --enable-avisynth --enable-gnutls --enable-ladspa --enable-libaom --enable-libass --enable-libbluray --enable-libbs2b --enable-libcaca --enable-libcdio --enable-libcodec2 --enable-libflite --enable-libfontconfig --enable-libfreetype --enable-libfribidi --enable-libgme --enable-libgsm --enable-libjack --enable-libmp3lame --enable-libmysofa --enable-libopenjpeg --enable-libopenmpt --enable-libopus --enable-libpulse --enable-librsvg --enable-librubberband --enable-libshine --enable-libsnappy --enable-libsoxr --enable-libspeex --enable-libssh --enable-libtheora --enable-libtwolame --enable-libvidstab --enable-libvorbis --enable-libvpx --enable-libwavpack --enable-libwebp --enable-libx265 --enable-libxml2 --enable-libxvid --enable-libzmq --enable-libzvbi --enable-lv2 --enable-omx --enable-openal --enable-opengl --enable-sdl2 --enable-libdc1394 --enable-libdrm --enable-libiec61883 --enable-chromaprint --enable-frei0r --enable-libx264 --enable-shared
libavutil 56. 22.100 / 56. 22.100
@@ -39,7 +39,7 @@ libswscale 5. 3.100 / 5. 3.100
libswresample 3. 3.100 / 3. 3.100
libpostproc 55. 3.100 / 55. 3.100";
- public const string FFmpegV404Output = @"ffmpeg version 4.0.4 Copyright (c) 2000-2019 the FFmpeg developers
+ public const string FFmpegV404Output = @"ffmpeg version 4.0.4 Copyright (c) 2000-2019 the FFmpeg developers
built with gcc 8 (Debian 8.3.0-6)
configuration: --toolchain=hardened --prefix=/usr --target-os=linux --enable-cross-compile --extra-cflags=--static --enable-gpl --enable-static --disable-doc --disable-ffplay --disable-shared --disable-libxcb --disable-sdl2 --disable-xlib --enable-libfontconfig --enable-fontconfig --enable-gmp --enable-gnutls --enable-libass --enable-libbluray --enable-libdrm --enable-libfreetype --enable-libfribidi --enable-libmp3lame --enable-libopus --enable-libtheora --enable-libvorbis --enable-libwebp --enable-libx264 --enable-libx265 --enable-libzvbi --enable-omx --enable-omx-rpi --enable-version3 --enable-vaapi --enable-vdpau --arch=amd64 --enable-nvenc --enable-nvdec
libavutil 56. 14.100 / 56. 14.100
@@ -51,7 +51,7 @@ libswscale 5. 1.100 / 5. 1.100
libswresample 3. 1.100 / 3. 1.100
libpostproc 55. 1.100 / 55. 1.100";
- public const string FFmpegGitUnknownOutput = @"ffmpeg version N-94303-g7cb4f8c962 Copyright (c) 2000-2019 the FFmpeg developers
+ public const string FFmpegGitUnknownOutput = @"ffmpeg version N-94303-g7cb4f8c962 Copyright (c) 2000-2019 the FFmpeg developers
built with gcc 9.1.1 (GCC) 20190716
configuration: --enable-gpl --enable-version3 --enable-sdl2 --enable-fontconfig --enable-gnutls --enable-iconv --enable-libass --enable-libdav1d --enable-libbluray --enable-libfreetype --enable-libmp3lame --enable-libopencore-amrnb --enable-libopencore-amrwb --enable-libopenjpeg --enable-libopus --enable-libshine --enable-libsnappy --enable-libsoxr --enable-libtheora --enable-libtwolame --enable-libvpx --enable-libwavpack --enable-libwebp --enable-libx264 --enable-libx265 --enable-libxml2 --enable-libzimg --enable-lzma --enable-zlib --enable-gmp --enable-libvidstab --enable-libvorbis --enable-libvo-amrwbenc --enable-libmysofa --enable-libspeex --enable-libxvid --enable-libaom --enable-libmfx --enable-amf --enable-ffnvcodec --enable-cuvid --enable-d3d11va --enable-nvenc --enable-nvdec --enable-dxva2 --enable-avisynth --enable-libopenmpt
libavutil 56. 30.100 / 56. 30.100
diff --git a/tests/Jellyfin.MediaEncoding.Tests/FFprobeParserTests.cs b/tests/Jellyfin.MediaEncoding.Tests/FFprobeParserTests.cs
new file mode 100644
index 000000000..2032f6cec
--- /dev/null
+++ b/tests/Jellyfin.MediaEncoding.Tests/FFprobeParserTests.cs
@@ -0,0 +1,22 @@
+using System.IO;
+using System.Text.Json;
+using System.Threading.Tasks;
+using MediaBrowser.MediaEncoding.Probing;
+using Xunit;
+
+namespace Jellyfin.MediaEncoding.Tests
+{
+ public class FFprobeParserTests
+ {
+ [Theory]
+ [InlineData("ffprobe1.json")]
+ public async Task Test(string fileName)
+ {
+ var path = Path.Join("Test Data", fileName);
+ using (var stream = File.OpenRead(path))
+ {
+ await JsonSerializer.DeserializeAsync<InternalMediaInfoResult>(stream).ConfigureAwait(false);
+ }
+ }
+ }
+}
diff --git a/tests/Jellyfin.MediaEncoding.Tests/Jellyfin.MediaEncoding.Tests.csproj b/tests/Jellyfin.MediaEncoding.Tests/Jellyfin.MediaEncoding.Tests.csproj
index 7f6b90533..276c50ca3 100644
--- a/tests/Jellyfin.MediaEncoding.Tests/Jellyfin.MediaEncoding.Tests.csproj
+++ b/tests/Jellyfin.MediaEncoding.Tests/Jellyfin.MediaEncoding.Tests.csproj
@@ -1,15 +1,28 @@
<Project Sdk="Microsoft.NET.Sdk">
+ <!-- ProjectGuid is only included as a requirement for SonarQube analysis -->
+ <PropertyGroup>
+ <ProjectGuid>{28464062-0939-4AA7-9F7B-24DDDA61A7C0}</ProjectGuid>
+ </PropertyGroup>
+
<PropertyGroup>
<TargetFramework>netcoreapp3.1</TargetFramework>
<IsPackable>false</IsPackable>
+ <TreatWarningsAsErrors>true</TreatWarningsAsErrors>
+ <Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
- <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.4.0" />
+ <None Include="Test Data\**\*.*">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </None>
+ </ItemGroup>
+
+ <ItemGroup>
+ <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.5.0" />
<PackageReference Include="xunit" Version="2.4.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.1" />
- <PackageReference Include="coverlet.collector" Version="1.1.0" />
+ <PackageReference Include="coverlet.collector" Version="1.2.1" />
</ItemGroup>
<ItemGroup>
diff --git a/tests/Jellyfin.MediaEncoding.Tests/Test Data/ffprobe1.json b/tests/Jellyfin.MediaEncoding.Tests/Test Data/ffprobe1.json
new file mode 100644
index 000000000..cdad5df50
--- /dev/null
+++ b/tests/Jellyfin.MediaEncoding.Tests/Test Data/ffprobe1.json
@@ -0,0 +1,105 @@
+{
+ "streams": [
+ {
+ "index": 0,
+ "codec_name": "h264",
+ "codec_long_name": "H.264 / AVC / MPEG-4 AVC / MPEG-4 part 10",
+ "profile": "Main",
+ "codec_type": "video",
+ "codec_time_base": "1/50",
+ "codec_tag_string": "[27][0][0][0]",
+ "codec_tag": "0x001b",
+ "width": 1920,
+ "height": 1080,
+ "coded_width": 1920,
+ "coded_height": 1080,
+ "has_b_frames": 0,
+ "sample_aspect_ratio": "0:1",
+ "display_aspect_ratio": "0:1",
+ "pix_fmt": "yuvj420p",
+ "level": 42,
+ "color_range": "pc",
+ "color_space": "bt709",
+ "color_transfer": "bt709",
+ "color_primaries": "bt709",
+ "chroma_location": "left",
+ "field_order": "progressive",
+ "refs": 1,
+ "is_avc": "false",
+ "nal_length_size": "0",
+ "id": "0x1",
+ "r_frame_rate": "25/1",
+ "avg_frame_rate": "25/1",
+ "time_base": "1/90000",
+ "start_pts": 8570867078,
+ "start_time": "95231.856422",
+ "duration_ts": 31694552,
+ "duration": "352.161689",
+ "bits_per_raw_sample": "8",
+ "disposition": {
+ "default": 0,
+ "dub": 0,
+ "original": 0,
+ "comment": 0,
+ "lyrics": 0,
+ "karaoke": 0,
+ "forced": 0,
+ "hearing_impaired": 0,
+ "visual_impaired": 0,
+ "clean_effects": 0,
+ "attached_pic": 0,
+ "timed_thumbnails": 0
+ }
+ },
+ {
+ "index": 1,
+ "codec_name": "aac",
+ "codec_long_name": "AAC (Advanced Audio Coding)",
+ "profile": "LC",
+ "codec_type": "audio",
+ "codec_time_base": "1/44100",
+ "codec_tag_string": "[15][0][0][0]",
+ "codec_tag": "0x000f",
+ "sample_fmt": "fltp",
+ "sample_rate": "44100",
+ "channels": 2,
+ "channel_layout": "stereo",
+ "bits_per_sample": 0,
+ "id": "0x2",
+ "r_frame_rate": "0/0",
+ "avg_frame_rate": "0/0",
+ "time_base": "1/90000",
+ "start_pts": 8570867697,
+ "start_time": "95231.863300",
+ "duration_ts": 31695687,
+ "duration": "352.174300",
+ "bit_rate": "98191",
+ "disposition": {
+ "default": 0,
+ "dub": 0,
+ "original": 0,
+ "comment": 0,
+ "lyrics": 0,
+ "karaoke": 0,
+ "forced": 0,
+ "hearing_impaired": 0,
+ "visual_impaired": 0,
+ "clean_effects": 0,
+ "attached_pic": 0,
+ "timed_thumbnails": 0
+ }
+ }
+ ],
+ "format": {
+ "filename": "TS Test record.ts",
+ "nb_streams": 2,
+ "nb_programs": 1,
+ "format_name": "mpegts",
+ "format_long_name": "MPEG-TS (MPEG-2 Transport Stream)",
+ "start_time": "95231.856422",
+ "duration": "352.181178",
+ "size": "179003772",
+ "bit_rate": "4066174",
+ "probe_score": 50
+ }
+}
diff --git a/tests/Jellyfin.Model.Tests/Extensions/StringHelperTests.cs b/tests/Jellyfin.Model.Tests/Extensions/StringHelperTests.cs
new file mode 100644
index 000000000..51633e157
--- /dev/null
+++ b/tests/Jellyfin.Model.Tests/Extensions/StringHelperTests.cs
@@ -0,0 +1,19 @@
+using System;
+using MediaBrowser.Model.Extensions;
+using Xunit;
+
+namespace Jellyfin.Model.Tests.Extensions
+{
+ public class StringHelperTests
+ {
+ [Theory]
+ [InlineData("", "")]
+ [InlineData("banana", "Banana")]
+ [InlineData("Banana", "Banana")]
+ [InlineData("ä", "Ä")]
+ public void StringHelper_ValidArgs_Success(string input, string expectedResult)
+ {
+ Assert.Equal(expectedResult, StringHelper.FirstToUpper(input));
+ }
+ }
+}
diff --git a/tests/Jellyfin.Model.Tests/Jellyfin.Model.Tests.csproj b/tests/Jellyfin.Model.Tests/Jellyfin.Model.Tests.csproj
new file mode 100644
index 000000000..f6c327498
--- /dev/null
+++ b/tests/Jellyfin.Model.Tests/Jellyfin.Model.Tests.csproj
@@ -0,0 +1,21 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+ <PropertyGroup>
+ <TargetFramework>netcoreapp3.1</TargetFramework>
+ <IsPackable>false</IsPackable>
+ <TreatWarningsAsErrors>true</TreatWarningsAsErrors>
+ <Nullable>enable</Nullable>
+ </PropertyGroup>
+
+ <ItemGroup>
+ <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.5.0" />
+ <PackageReference Include="xunit" Version="2.4.1" />
+ <PackageReference Include="xunit.runner.visualstudio" Version="2.4.1" />
+ <PackageReference Include="coverlet.collector" Version="1.2.1" />
+ </ItemGroup>
+
+ <ItemGroup>
+ <ProjectReference Include="../../MediaBrowser.Model/MediaBrowser.Model.csproj" />
+ </ItemGroup>
+
+</Project>
diff --git a/tests/Jellyfin.Naming.Tests/Jellyfin.Naming.Tests.csproj b/tests/Jellyfin.Naming.Tests/Jellyfin.Naming.Tests.csproj
index 79d2f2144..ac0c970c1 100644
--- a/tests/Jellyfin.Naming.Tests/Jellyfin.Naming.Tests.csproj
+++ b/tests/Jellyfin.Naming.Tests/Jellyfin.Naming.Tests.csproj
@@ -1,15 +1,21 @@
<Project Sdk="Microsoft.NET.Sdk">
+ <!-- ProjectGuid is only included as a requirement for SonarQube analysis -->
+ <PropertyGroup>
+ <ProjectGuid>{3998657B-1CCC-49DD-A19F-275DC8495F57}</ProjectGuid>
+ </PropertyGroup>
+
<PropertyGroup>
<TargetFramework>netcoreapp3.1</TargetFramework>
<IsPackable>false</IsPackable>
+ <Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
- <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.4.0" />
+ <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.5.0" />
<PackageReference Include="xunit" Version="2.4.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.1" />
- <PackageReference Include="coverlet.collector" Version="1.1.0" />
+ <PackageReference Include="coverlet.collector" Version="1.2.1" />
</ItemGroup>
<ItemGroup>
diff --git a/tests/Jellyfin.Naming.Tests/Music/MultiDiscAlbumTests.cs b/tests/Jellyfin.Naming.Tests/Music/MultiDiscAlbumTests.cs
index eb69d915c..c9a295a4c 100644
--- a/tests/Jellyfin.Naming.Tests/Music/MultiDiscAlbumTests.cs
+++ b/tests/Jellyfin.Naming.Tests/Music/MultiDiscAlbumTests.cs
@@ -6,52 +6,45 @@ namespace Jellyfin.Naming.Tests.Music
{
public class MultiDiscAlbumTests
{
- [Fact]
- public void TestMultiDiscAlbums()
+ private readonly NamingOptions _namingOptions = new NamingOptions();
+
+ [Theory]
+ [InlineData("", false)]
+ [InlineData("C:/", false)]
+ [InlineData("/home/", false)]
+ [InlineData(@"blah blah", false)]
+ [InlineData(@"D:/music/weezer/03 Pinkerton", false)]
+ [InlineData(@"D:/music/michael jackson/Bad (2012 Remaster)", false)]
+ [InlineData(@"cd1", true)]
+ [InlineData(@"disc18", true)]
+ [InlineData(@"disk10", true)]
+ [InlineData(@"vol7", true)]
+ [InlineData(@"volume1", true)]
+ [InlineData(@"cd 1", true)]
+ [InlineData(@"disc 1", true)]
+ [InlineData(@"disk 1", true)]
+ [InlineData(@"disk", false)]
+ [InlineData(@"disk ·", false)]
+ [InlineData(@"disk a", false)]
+ [InlineData(@"disk volume", false)]
+ [InlineData(@"disc disc", false)]
+ [InlineData(@"disk disc 6", false)]
+ [InlineData(@"cd - 1", true)]
+ [InlineData(@"disc- 1", true)]
+ [InlineData(@"disk - 1", true)]
+ [InlineData(@"Disc 01 (Hugo Wolf · 24 Lieder)", true)]
+ [InlineData(@"Disc 04 (Encores and Folk Songs)", true)]
+ [InlineData(@"Disc04 (Encores and Folk Songs)", true)]
+ [InlineData(@"Disc 04(Encores and Folk Songs)", true)]
+ [InlineData(@"Disc04(Encores and Folk Songs)", true)]
+ [InlineData(@"D:/Video/MBTestLibrary/VideoTest/music/.38 special/anth/Disc 2", true)]
+ [InlineData(@"[1985] Opportunities (Let's make lots of money) (1985)", false)]
+ [InlineData(@"Blah 04(Encores and Folk Songs)", false)]
+ public void AlbumParser_MultidiscPath_Identifies(string path, bool result)
{
- Assert.False(IsMultiDiscAlbumFolder(@"blah blah"));
- Assert.False(IsMultiDiscAlbumFolder(@"d:/music\weezer/03 Pinkerton"));
- Assert.False(IsMultiDiscAlbumFolder(@"d:/music/michael jackson/Bad (2012 Remaster)"));
+ var parser = new AlbumParser(_namingOptions);
- Assert.True(IsMultiDiscAlbumFolder(@"cd1"));
- Assert.True(IsMultiDiscAlbumFolder(@"disc1"));
- Assert.True(IsMultiDiscAlbumFolder(@"disk1"));
-
- // Add a space
- Assert.True(IsMultiDiscAlbumFolder(@"cd 1"));
- Assert.True(IsMultiDiscAlbumFolder(@"disc 1"));
- Assert.True(IsMultiDiscAlbumFolder(@"disk 1"));
-
- Assert.True(IsMultiDiscAlbumFolder(@"cd - 1"));
- Assert.True(IsMultiDiscAlbumFolder(@"disc- 1"));
- Assert.True(IsMultiDiscAlbumFolder(@"disk - 1"));
-
- Assert.True(IsMultiDiscAlbumFolder(@"Disc 01 (Hugo Wolf · 24 Lieder)"));
- Assert.True(IsMultiDiscAlbumFolder(@"Disc 04 (Encores and Folk Songs)"));
- Assert.True(IsMultiDiscAlbumFolder(@"Disc04 (Encores and Folk Songs)"));
- Assert.True(IsMultiDiscAlbumFolder(@"Disc 04(Encores and Folk Songs)"));
- Assert.True(IsMultiDiscAlbumFolder(@"Disc04(Encores and Folk Songs)"));
-
- Assert.True(IsMultiDiscAlbumFolder(@"D:/Video/MBTestLibrary/VideoTest/music/.38 special/anth/Disc 2"));
- }
-
- [Fact]
- public void TestMultiDiscAlbums1()
- {
- Assert.False(IsMultiDiscAlbumFolder(@"[1985] Oppurtunities (Let's make lots of money) (1985)"));
- }
-
- [Fact]
- public void TestMultiDiscAlbums2()
- {
- Assert.False(IsMultiDiscAlbumFolder(@"Blah 04(Encores and Folk Songs)"));
- }
-
- private bool IsMultiDiscAlbumFolder(string path)
- {
- var parser = new AlbumParser(new NamingOptions());
-
- return parser.ParseMultiPart(path).IsMultiPart;
+ Assert.Equal(result, parser.IsMultiPart(path));
}
}
}
diff --git a/tests/Jellyfin.Naming.Tests/Subtitles/SubtitleParserTests.cs b/tests/Jellyfin.Naming.Tests/Subtitles/SubtitleParserTests.cs
index e8f14cdc4..40d80607c 100644
--- a/tests/Jellyfin.Naming.Tests/Subtitles/SubtitleParserTests.cs
+++ b/tests/Jellyfin.Naming.Tests/Subtitles/SubtitleParserTests.cs
@@ -1,4 +1,5 @@
-using Emby.Naming.Common;
+using System;
+using Emby.Naming.Common;
using Emby.Naming.Subtitles;
using Xunit;
@@ -6,35 +7,40 @@ namespace Jellyfin.Naming.Tests.Subtitles
{
public class SubtitleParserTests
{
- private SubtitleParser GetParser()
+ private readonly NamingOptions _namingOptions = new NamingOptions();
+
+ [Theory]
+ [InlineData("The Skin I Live In (2011).srt", null, false, false)]
+ [InlineData("The Skin I Live In (2011).eng.srt", "eng", false, false)]
+ [InlineData("The Skin I Live In (2011).eng.default.srt", "eng", true, false)]
+ [InlineData("The Skin I Live In (2011).eng.forced.srt", "eng", false, true)]
+ [InlineData("The Skin I Live In (2011).eng.foreign.srt", "eng", false, true)]
+ [InlineData("The Skin I Live In (2011).eng.default.foreign.srt", "eng", true, true)]
+ [InlineData("The Skin I Live In (2011).default.foreign.eng.srt", "eng", true, true)]
+ public void SubtitleParser_ValidFileName_Parses(string input, string language, bool isDefault, bool isForced)
{
- var options = new NamingOptions();
+ var parser = new SubtitleParser(_namingOptions);
+
+ var result = parser.ParseFile(input);
- return new SubtitleParser(options);
+ Assert.Equal(language, result.Language, true);
+ Assert.Equal(isDefault, result.IsDefault);
+ Assert.Equal(isForced, result.IsForced);
}
- [Fact]
- public void TestSubtitles()
+ [Theory]
+ [InlineData("The Skin I Live In (2011).mp4")]
+ public void SubtitleParser_InvalidFileName_ReturnsNull(string input)
{
- Test("The Skin I Live In (2011).srt", null, false, false);
- Test("The Skin I Live In (2011).eng.srt", "eng", false, false);
- Test("The Skin I Live In (2011).eng.default.srt", "eng", true, false);
- Test("The Skin I Live In (2011).eng.forced.srt", "eng", false, true);
- Test("The Skin I Live In (2011).eng.foreign.srt", "eng", false, true);
- Test("The Skin I Live In (2011).eng.default.foreign.srt", "eng", true, true);
+ var parser = new SubtitleParser(_namingOptions);
- Test("The Skin I Live In (2011).default.foreign.eng.srt", "eng", true, true);
+ Assert.Null(parser.ParseFile(input));
}
- private void Test(string input, string language, bool isDefault, bool isForced)
+ [Fact]
+ public void SubtitleParser_EmptyFileName_ThrowsArgumentException()
{
- var parser = GetParser();
-
- var result = parser.ParseFile(input);
-
- Assert.Equal(language, result.Language, true);
- Assert.Equal(isDefault, result.IsDefault);
- Assert.Equal(isForced, result.IsForced);
+ Assert.Throws<ArgumentException>(() => new SubtitleParser(_namingOptions).ParseFile(string.Empty));
}
}
}
diff --git a/tests/Jellyfin.Naming.Tests/TV/AbsoluteEpisodeNumberTests.cs b/tests/Jellyfin.Naming.Tests/TV/AbsoluteEpisodeNumberTests.cs
index 9abbcc7bf..553d06681 100644
--- a/tests/Jellyfin.Naming.Tests/TV/AbsoluteEpisodeNumberTests.cs
+++ b/tests/Jellyfin.Naming.Tests/TV/AbsoluteEpisodeNumberTests.cs
@@ -6,56 +6,22 @@ namespace Jellyfin.Naming.Tests.TV
{
public class AbsoluteEpisodeNumberTests
{
- [Fact]
- public void TestAbsoluteEpisodeNumber1()
- {
- Assert.Equal(12, GetEpisodeNumberFromFile(@"The Simpsons/12.avi"));
- }
-
- [Fact]
- public void TestAbsoluteEpisodeNumber2()
- {
- Assert.Equal(12, GetEpisodeNumberFromFile(@"The Simpsons/The Simpsons 12.avi"));
- }
-
- [Fact]
- public void TestAbsoluteEpisodeNumber3()
- {
- Assert.Equal(82, GetEpisodeNumberFromFile(@"The Simpsons/The Simpsons 82.avi"));
- }
-
- [Fact]
- public void TestAbsoluteEpisodeNumber4()
- {
- Assert.Equal(112, GetEpisodeNumberFromFile(@"The Simpsons/The Simpsons 112.avi"));
- }
-
- [Fact]
- public void TestAbsoluteEpisodeNumber5()
- {
- Assert.Equal(2, GetEpisodeNumberFromFile(@"The Simpsons/Foo_ep_02.avi"));
- }
-
- [Fact]
- public void TestAbsoluteEpisodeNumber6()
- {
- Assert.Equal(889, GetEpisodeNumberFromFile(@"The Simpsons/The Simpsons 889.avi"));
- }
-
- [Fact]
- public void TestAbsoluteEpisodeNumber7()
- {
- Assert.Equal(101, GetEpisodeNumberFromFile(@"The Simpsons/The Simpsons 101.avi"));
- }
-
- private int? GetEpisodeNumberFromFile(string path)
+ [Theory]
+ [InlineData("The Simpsons/12.avi", 12)]
+ [InlineData("The Simpsons/The Simpsons 12.avi", 12)]
+ [InlineData("The Simpsons/The Simpsons 82.avi", 82)]
+ [InlineData("The Simpsons/The Simpsons 112.avi", 112)]
+ [InlineData("The Simpsons/Foo_ep_02.avi", 2)]
+ [InlineData("The Simpsons/The Simpsons 889.avi", 889)]
+ [InlineData("The Simpsons/The Simpsons 101.avi", 101)]
+ public void GetEpisodeNumberFromFileTest(string path, int episodeNumber)
{
var options = new NamingOptions();
var result = new EpisodeResolver(options)
.Resolve(path, false, null, null, true);
- return result.EpisodeNumber;
+ Assert.Equal(episodeNumber, result.EpisodeNumber);
}
}
}
diff --git a/tests/Jellyfin.Naming.Tests/TV/DailyEpisodeTests.cs b/tests/Jellyfin.Naming.Tests/TV/DailyEpisodeTests.cs
index 29daf8cc3..6ecffe80b 100644
--- a/tests/Jellyfin.Naming.Tests/TV/DailyEpisodeTests.cs
+++ b/tests/Jellyfin.Naming.Tests/TV/DailyEpisodeTests.cs
@@ -6,52 +6,17 @@ namespace Jellyfin.Naming.Tests.TV
{
public class DailyEpisodeTests
{
- [Fact]
- public void TestDailyEpisode1()
- {
- Test(@"/server/anything_1996.11.14.mp4", "anything", 1996, 11, 14);
- }
-
- [Fact]
- public void TestDailyEpisode2()
- {
- Test(@"/server/anything_1996-11-14.mp4", "anything", 1996, 11, 14);
- }
- // FIXME
- // [Fact]
- public void TestDailyEpisode3()
- {
- Test(@"/server/anything_14.11.1996.mp4", "anything", 1996, 11, 14);
- }
-
- // FIXME
- // [Fact]
- public void TestDailyEpisode4()
- {
- Test(@"/server/A Daily Show - (2015-01-15) - Episode Name - [720p].mkv", "A Daily Show", 2015, 01, 15);
- }
-
- [Fact]
- public void TestDailyEpisode5()
- {
- Test(@"/server/james.corden.2017.04.20.anne.hathaway.720p.hdtv.x264-crooks.mkv", "james.corden", 2017, 04, 20);
- }
-
- [Fact]
- public void TestDailyEpisode6()
- {
- Test(@"/server/ABC News 2018_03_24_19_00_00.mkv", "ABC News", 2018, 03, 24);
- }
-
- // FIXME
- // [Fact]
- public void TestDailyEpisode7()
- {
- Test(@"/server/Last Man Standing_KTLADT_2018_05_25_01_28_00.wtv", "Last Man Standing", 2018, 05, 25);
- }
- private void Test(string path, string seriesName, int? year, int? month, int? day)
+ [Theory]
+ [InlineData(@"/server/anything_1996.11.14.mp4", "anything", 1996, 11, 14)]
+ [InlineData(@"/server/anything_1996-11-14.mp4", "anything", 1996, 11, 14)]
+ [InlineData(@"/server/james.corden.2017.04.20.anne.hathaway.720p.hdtv.x264-crooks.mkv", "james.corden", 2017, 04, 20)]
+ [InlineData(@"/server/ABC News 2018_03_24_19_00_00.mkv", "ABC News", 2018, 03, 24)]
+ // TODO: [InlineData(@"/server/anything_14.11.1996.mp4", "anything", 1996, 11, 14)]
+ // TODO: [InlineData(@"/server/A Daily Show - (2015-01-15) - Episode Name - [720p].mkv", "A Daily Show", 2015, 01, 15)]
+ // TODO: [InlineData(@"/server/Last Man Standing_KTLADT_2018_05_25_01_28_00.wtv", "Last Man Standing", 2018, 05, 25)]
+ public void Test(string path, string seriesName, int? year, int? month, int? day)
{
var options = new NamingOptions();
diff --git a/tests/Jellyfin.Naming.Tests/TV/EpisodeNumberTests.cs b/tests/Jellyfin.Naming.Tests/TV/EpisodeNumberTests.cs
index 1ae637281..5e023bdb0 100644
--- a/tests/Jellyfin.Naming.Tests/TV/EpisodeNumberTests.cs
+++ b/tests/Jellyfin.Naming.Tests/TV/EpisodeNumberTests.cs
@@ -6,419 +6,78 @@ namespace Jellyfin.Naming.Tests.TV
{
public class EpisodeNumberTests
{
- [Fact]
- public void TestEpisodeNumber1()
- {
- Assert.Equal(03, GetEpisodeNumberFromFile(@"Season 02/S02E03 blah.avi"));
- }
-
- [Fact]
- public void TestEpisodeNumber40()
- {
- Assert.Equal(03, GetEpisodeNumberFromFile(@"Season 2/02x03 - 02x04 - 02x15 - Ep Name.mp4"));
- }
-
- [Fact]
- public void TestEpisodeNumber41()
- {
- Assert.Equal(02, GetEpisodeNumberFromFile(@"Season 1/01x02 blah.avi"));
- }
-
- [Fact]
- public void TestEpisodeNumber42()
- {
- Assert.Equal(02, GetEpisodeNumberFromFile(@"Season 1/S01x02 blah.avi"));
- }
-
- [Fact]
- public void TestEpisodeNumber43()
- {
- Assert.Equal(02, GetEpisodeNumberFromFile(@"Season 1/S01E02 blah.avi"));
- }
-
- [Fact]
- public void TestEpisodeNumber44()
- {
- Assert.Equal(03, GetEpisodeNumberFromFile(@"Season 2/Elementary - 02x03-04-15 - Ep Name.mp4"));
- }
-
- [Fact]
- public void TestEpisodeNumber45()
- {
- Assert.Equal(02, GetEpisodeNumberFromFile(@"Season 1/S01xE02 blah.avi"));
- }
-
- [Fact]
- public void TestEpisodeNumber46()
- {
- Assert.Equal(02, GetEpisodeNumberFromFile(@"Season 1/seriesname S01E02 blah.avi"));
- }
-
- [Fact]
- public void TestEpisodeNumber47()
- {
- Assert.Equal(36, GetEpisodeNumberFromFile(@"Season 2/[HorribleSubs] Hunter X Hunter - 136 [720p].mkv"));
- }
-
- [Fact]
- public void TestEpisodeNumber50()
- {
- // This convention is not currently supported, just adding in case we want to look at it in the future
- Assert.Equal(1, GetEpisodeNumberFromFile(@"2016/Season s2016e1.mp4"));
- }
-
- // FIXME
- // [Fact]
- public void TestEpisodeNumber51()
- {
- // This convention is not currently supported, just adding in case we want to look at it in the future
- Assert.Equal(1, GetEpisodeNumberFromFile(@"2016/Season 2016x1.mp4"));
- }
-
- [Fact]
- public void TestEpisodeNumber52()
- {
- Assert.Equal(16, GetEpisodeNumberFromFile(@"Season 2/Episode - 16.avi"));
- }
-
- [Fact]
- public void TestEpisodeNumber53()
- {
- // This is not supported. Expected to fail, although it would be a good one to add support for.
- Assert.Equal(16, GetEpisodeNumberFromFile(@"Season 2/Episode 16.avi"));
- }
-
- [Fact]
- public void TestEpisodeNumber54()
- {
- // This is not supported. Expected to fail, although it would be a good one to add support for.
- Assert.Equal(16, GetEpisodeNumberFromFile(@"Season 2/Episode 16 - Some Title.avi"));
- }
-
- // [Fact]
- public void TestEpisodeNumber55()
- {
- // This is not supported. Expected to fail, although it would be a good one to add support for.
- Assert.Equal(16, GetEpisodeNumberFromFile(@"Season 2/Season 3 Episode 16.avi"));
- }
-
- // [Fact]
- public void TestEpisodeNumber56()
- {
- // This is not supported. Expected to fail, although it would be a good one to add support for.
- Assert.Equal(16, GetEpisodeNumberFromFile(@"Season 2/Season 3 Episode 16 - Some Title.avi"));
- }
-
- [Fact]
- public void TestEpisodeNumber57()
- {
- Assert.Equal(16, GetEpisodeNumberFromFile(@"Season 2/16 Some Title.avi"));
- }
-
- [Fact]
- public void TestEpisodeNumber58()
- {
- Assert.Equal(16, GetEpisodeNumberFromFile(@"Season 2/16 - 12 Some Title.avi"));
- }
-
- [Fact]
- public void TestEpisodeNumber59()
- {
- Assert.Equal(7, GetEpisodeNumberFromFile(@"Season 2/7 - 12 Angry Men.avi"));
- }
-
- // FIXME
- // [Fact]
- public void TestEpisodeNumber60()
- {
- Assert.Equal(16, GetEpisodeNumberFromFile(@"Season 2/16 12 Some Title.avi"));
- }
-
- // FIXME
- // [Fact]
- public void TestEpisodeNumber61()
- {
- Assert.Equal(7, GetEpisodeNumberFromFile(@"Season 2/7 12 Angry Men.avi"));
- }
-
- // FIXME
- // [Fact]
- public void TestEpisodeNumber62()
- {
- // This is not supported. Expected to fail, although it would be a good one to add support for.
- Assert.Equal(3, GetEpisodeNumberFromFile(@"Season 4/Uchuu.Senkan.Yamato.2199.E03.avi"));
- }
-
- [Fact]
- public void TestEpisodeNumber63()
- {
- Assert.Equal(3, GetEpisodeNumberFromFile(@"Season 4/Uchuu.Senkan.Yamato.2199.S04E03.avi"));
- }
-
- [Fact]
- public void TestEpisodeNumber64()
- {
- Assert.Equal(368, GetEpisodeNumberFromFile(@"Running Man/Running Man S2017E368.mkv"));
- }
-
- // FIXME
- // [Fact]
- public void TestEpisodeNumber65()
- {
- // Not supported yet
- Assert.Equal(7, GetEpisodeNumberFromFile(@"/The.Legend.of.Condor.Heroes.2017.V2.web-dl.1080p.h264.aac-hdctv/The.Legend.of.Condor.Heroes.2017.E07.V2.web-dl.1080p.h264.aac-hdctv.mkv"));
- }
-
- [Fact]
- public void TestEpisodeNumber30()
- {
- Assert.Equal(03, GetEpisodeNumberFromFile(@"Season 2/02x03 - 02x04 - 02x15 - Ep Name.mp4"));
- }
-
- // FIXME
- // [Fact]
- public void TestEpisodeNumber31()
- {
- Assert.Equal(02, GetEpisodeNumberFromFile(@"Season 1/seriesname 01x02 blah.avi"));
- }
-
- [Fact]
- public void TestEpisodeNumber32()
- {
- Assert.Equal(9, GetEpisodeNumberFromFile(@"Season 25/The Simpsons.S25E09.Steal this episode.mp4"));
- }
-
- [Fact]
- public void TestEpisodeNumber33()
- {
- Assert.Equal(02, GetEpisodeNumberFromFile(@"Season 1/seriesname S01x02 blah.avi"));
- }
-
- [Fact]
- public void TestEpisodeNumber34()
- {
- Assert.Equal(03, GetEpisodeNumberFromFile(@"Season 2/Elementary - 02x03 - 02x04 - 02x15 - Ep Name.mp4"));
- }
-
- [Fact]
- public void TestEpisodeNumber35()
- {
- Assert.Equal(02, GetEpisodeNumberFromFile(@"Season 1/seriesname S01xE02 blah.avi"));
- }
-
- [Fact]
- public void TestEpisodeNumber36()
- {
- Assert.Equal(03, GetEpisodeNumberFromFile(@"Season 02/02x03 - x04 - x15 - Ep Name.mp4"));
- }
-
- [Fact]
- public void TestEpisodeNumber37()
- {
- Assert.Equal(03, GetEpisodeNumberFromFile(@"Season 02/Elementary - 02x03 - x04 - x15 - Ep Name.mp4"));
- }
-
- [Fact]
- public void TestEpisodeNumber38()
- {
- Assert.Equal(03, GetEpisodeNumberFromFile(@"Season 02/02x03x04x15 - Ep Name.mp4"));
- }
-
- [Fact]
- public void TestEpisodeNumber39()
- {
- Assert.Equal(03, GetEpisodeNumberFromFile(@"Season 02/Elementary - 02x03x04x15 - Ep Name.mp4"));
- }
-
- [Fact]
- public void TestEpisodeNumber20()
- {
- Assert.Equal(03, GetEpisodeNumberFromFile(@"Season 2/02x03-04-15 - Ep Name.mp4"));
- }
-
- [Fact]
- public void TestEpisodeNumber21()
- {
- Assert.Equal(03, GetEpisodeNumberFromFile(@"Season 02/02x03-E15 - Ep Name.mp4"));
- }
-
- [Fact]
- public void TestEpisodeNumber22()
- {
- Assert.Equal(03, GetEpisodeNumberFromFile(@"Season 02/Elementary - 02x03-E15 - Ep Name.mp4"));
- }
-
- [Fact]
- public void TestEpisodeNumber23()
- {
- Assert.Equal(23, GetEpisodeNumberFromFile(@"Season 1/Elementary - S01E23-E24-E26 - The Woman.mp4"));
- }
-
- [Fact]
- public void TestEpisodeNumber24()
- {
- Assert.Equal(23, GetEpisodeNumberFromFile(@"Season 2009/S2009E23-E24-E26 - The Woman.mp4"));
- }
-
- [Fact]
- public void TestEpisodeNumber25()
- {
- Assert.Equal(02, GetEpisodeNumberFromFile(@"Season 2009/2009x02 blah.avi"));
- }
-
- [Fact]
- public void TestEpisodeNumber26()
- {
- Assert.Equal(02, GetEpisodeNumberFromFile(@"Season 2009/S2009x02 blah.avi"));
- }
-
- [Fact]
- public void TestEpisodeNumber27()
- {
- Assert.Equal(02, GetEpisodeNumberFromFile(@"Season 2009/S2009E02 blah.avi"));
- }
-
- // FIXME
- // [Fact]
- public void TestEpisodeNumber28()
- {
- Assert.Equal(02, GetEpisodeNumberFromFile(@"Season 2009/seriesname 2009x02 blah.avi"));
- }
-
- [Fact]
- public void TestEpisodeNumber29()
- {
- Assert.Equal(03, GetEpisodeNumberFromFile(@"Season 2009/Elementary - 2009x03x04x15 - Ep Name.mp4"));
- }
-
- [Fact]
- public void TestEpisodeNumber11()
- {
- Assert.Equal(03, GetEpisodeNumberFromFile(@"Season 2009/2009x03x04x15 - Ep Name.mp4"));
- }
-
- [Fact]
- public void TestEpisodeNumber12()
- {
- Assert.Equal(03, GetEpisodeNumberFromFile(@"Season 2009/Elementary - 2009x03-E15 - Ep Name.mp4"));
- }
-
- [Fact]
- public void TestEpisodeNumber13()
- {
- Assert.Equal(02, GetEpisodeNumberFromFile(@"Season 2009/S2009xE02 blah.avi"));
- }
-
- [Fact]
- public void TestEpisodeNumber14()
- {
- Assert.Equal(23, GetEpisodeNumberFromFile(@"Season 2009/Elementary - S2009E23-E24-E26 - The Woman.mp4"));
- }
-
- [Fact]
- public void TestEpisodeNumber15()
- {
- Assert.Equal(02, GetEpisodeNumberFromFile(@"Season 2009/seriesname S2009xE02 blah.avi"));
- }
-
- [Fact]
- public void TestEpisodeNumber16()
- {
- Assert.Equal(03, GetEpisodeNumberFromFile(@"Season 2009/2009x03-E15 - Ep Name.mp4"));
- }
-
- [Fact]
- public void TestEpisodeNumber17()
- {
- Assert.Equal(02, GetEpisodeNumberFromFile(@"Season 2009/seriesname S2009E02 blah.avi"));
- }
-
- [Fact]
- public void TestEpisodeNumber18()
- {
- Assert.Equal(03, GetEpisodeNumberFromFile(@"Season 2009/2009x03 - 2009x04 - 2009x15 - Ep Name.mp4"));
- }
-
- [Fact]
- public void TestEpisodeNumber19()
- {
- Assert.Equal(03, GetEpisodeNumberFromFile(@"Season 2009/2009x03 - x04 - x15 - Ep Name.mp4"));
- }
-
- [Fact]
- public void TestEpisodeNumber2()
- {
- Assert.Equal(02, GetEpisodeNumberFromFile(@"Season 2009/seriesname S2009x02 blah.avi"));
- }
-
- [Fact]
- public void TestEpisodeNumber3()
- {
- Assert.Equal(03, GetEpisodeNumberFromFile(@"Season 2009/Elementary - 2009x03 - 2009x04 - 2009x15 - Ep Name.mp4"));
- }
-
- [Fact]
- public void TestEpisodeNumber4()
- {
- Assert.Equal(03, GetEpisodeNumberFromFile(@"Season 2009/Elementary - 2009x03-04-15 - Ep Name.mp4"));
- }
-
- [Fact]
- public void TestEpisodeNumber5()
- {
- Assert.Equal(03, GetEpisodeNumberFromFile(@"Season 2009/2009x03-04-15 - Ep Name.mp4"));
- }
-
- [Fact]
- public void TestEpisodeNumber6()
- {
- Assert.Equal(03, GetEpisodeNumberFromFile(@"Season 2009/Elementary - 2009x03 - x04 - x15 - Ep Name.mp4"));
- }
-
- [Fact]
- public void TestEpisodeNumber7()
- {
- Assert.Equal(02, GetEpisodeNumberFromFile(@"Season 1/02 - blah-02 a.avi"));
- }
-
- [Fact]
- public void TestEpisodeNumber8()
- {
- Assert.Equal(02, GetEpisodeNumberFromFile(@"Season 1/02 - blah.avi"));
- }
-
- [Fact]
- public void TestEpisodeNumber9()
- {
- Assert.Equal(02, GetEpisodeNumberFromFile(@"Season 2/02 - blah 14 blah.avi"));
- }
-
- [Fact]
- public void TestEpisodeNumber10()
- {
- Assert.Equal(02, GetEpisodeNumberFromFile(@"Season 2/02.avi"));
- }
-
- [Fact]
- public void TestEpisodeNumber48()
- {
- Assert.Equal(02, GetEpisodeNumberFromFile(@"Season 2/2. Infestation.avi"));
- }
-
- [Fact]
- public void TestEpisodeNumber49()
- {
- Assert.Equal(7, GetEpisodeNumberFromFile(@"The Wonder Years/The.Wonder.Years.S04.PDTV.x264-JCH/The Wonder Years s04e07 Christmas Party NTSC PDTV.avi"));
- }
-
- private int? GetEpisodeNumberFromFile(string path)
- {
- var options = new NamingOptions();
-
- var result = new EpisodePathParser(options)
+ private readonly NamingOptions _namingOptions = new NamingOptions();
+
+ [Theory]
+ [InlineData("Watchmen (2019)/Watchmen 1x03 [WEBDL-720p][EAC3 5.1][h264][-TBS] - She Was Killed by Space Junk.mkv", 3)]
+ [InlineData("The Daily Show/The Daily Show 25x22 - [WEBDL-720p][AAC 2.0][x264] Noah Baumbach-TBS.mkv", 22)]
+ [InlineData("Castle Rock 2x01 Que el rio siga su curso [WEB-DL HULU 1080p h264 Dual DD5.1 Subs].mkv", 1)]
+ [InlineData("After Life 1x06 Episodio 6 [WEB-DL NF 1080p h264 Dual DD 5.1 Sub].mkv", 6)]
+ [InlineData("Season 02/S02E03 blah.avi", 3)]
+ [InlineData("Season 2/02x03 - 02x04 - 02x15 - Ep Name.mp4", 3)]
+ [InlineData("Season 02/02x03 - x04 - x15 - Ep Name.mp4", 3)]
+ [InlineData("Season 1/01x02 blah.avi", 2)]
+ [InlineData("Season 1/S01x02 blah.avi", 2)]
+ [InlineData("Season 1/S01E02 blah.avi", 2)]
+ [InlineData("Season 2/Elementary - 02x03-04-15 - Ep Name.mp4", 3)]
+ [InlineData("Season 1/S01xE02 blah.avi", 2)]
+ [InlineData("Season 1/seriesname S01E02 blah.avi", 2)]
+ [InlineData("Season 2/Episode - 16.avi", 16)]
+ [InlineData("Season 2/Episode 16.avi", 16)]
+ [InlineData("Season 2/Episode 16 - Some Title.avi", 16)]
+ [InlineData("Season 2/16 Some Title.avi", 16)]
+ [InlineData("Season 2/16 - 12 Some Title.avi", 16)]
+ [InlineData("Season 2/7 - 12 Angry Men.avi", 7)]
+ [InlineData("Season 1/seriesname 01x02 blah.avi", 2)]
+ [InlineData("Season 25/The Simpsons.S25E09.Steal this episode.mp4", 9)]
+ [InlineData("Season 1/seriesname S01x02 blah.avi", 2)]
+ [InlineData("Season 2/Elementary - 02x03 - 02x04 - 02x15 - Ep Name.mp4", 3)]
+ [InlineData("Season 1/seriesname S01xE02 blah.avi", 2)]
+ [InlineData("Season 02/Elementary - 02x03 - x04 - x15 - Ep Name.mp4", 3)]
+ [InlineData("Season 02/Elementary - 02x03x04x15 - Ep Name.mp4", 3)]
+ [InlineData("Season 2/02x03-04-15 - Ep Name.mp4", 3)]
+ [InlineData("Season 02/02x03-E15 - Ep Name.mp4", 3)]
+ [InlineData("Season 02/Elementary - 02x03-E15 - Ep Name.mp4", 3)]
+ [InlineData("Season 1/Elementary - S01E23-E24-E26 - The Woman.mp4", 23)]
+ [InlineData("Season 2009/S2009E23-E24-E26 - The Woman.mp4", 23)]
+ [InlineData("Season 2009/2009x02 blah.avi", 2)]
+ [InlineData("Season 2009/S2009x02 blah.avi", 2)]
+ [InlineData("Season 2009/S2009E02 blah.avi", 2)]
+ [InlineData("Season 2009/seriesname 2009x02 blah.avi", 2)]
+ [InlineData("Season 2009/Elementary - 2009x03x04x15 - Ep Name.mp4", 3)]
+ [InlineData("Season 2009/2009x03x04x15 - Ep Name.mp4", 3)]
+ [InlineData("Season 2009/Elementary - 2009x03-E15 - Ep Name.mp4", 3)]
+ [InlineData("Season 2009/S2009xE02 blah.avi", 2)]
+ [InlineData("Season 2009/Elementary - S2009E23-E24-E26 - The Woman.mp4", 23)]
+ [InlineData("Season 2009/seriesname S2009xE02 blah.avi", 2)]
+ [InlineData("Season 2009/2009x03-E15 - Ep Name.mp4", 3)]
+ [InlineData("Season 2009/seriesname S2009E02 blah.avi", 2)]
+ [InlineData("Season 2009/2009x03 - 2009x04 - 2009x15 - Ep Name.mp4", 3)]
+ [InlineData("Season 2009/2009x03 - x04 - x15 - Ep Name.mp4", 3)]
+ [InlineData("Season 2009/seriesname S2009x02 blah.avi", 2)]
+ [InlineData("Season 2009/Elementary - 2009x03 - 2009x04 - 2009x15 - Ep Name.mp4", 3)]
+ [InlineData("Season 2009/Elementary - 2009x03-04-15 - Ep Name.mp4", 3)]
+ [InlineData("Season 2009/2009x03-04-15 - Ep Name.mp4", 3)]
+ [InlineData("Season 2009/Elementary - 2009x03 - x04 - x15 - Ep Name.mp4", 3)]
+ [InlineData("Season 1/02 - blah-02 a.avi", 2)]
+ [InlineData("Season 1/02 - blah.avi", 2)]
+ [InlineData("Season 2/02 - blah 14 blah.avi", 2)]
+ [InlineData("Season 2/02.avi", 2)]
+ [InlineData("Season 2/2. Infestation.avi", 2)]
+ [InlineData("The Wonder Years/The.Wonder.Years.S04.PDTV.x264-JCH/The Wonder Years s04e07 Christmas Party NTSC PDTV.avi", 7)]
+ [InlineData("Running Man/Running Man S2017E368.mkv", 368)]
+ // TODO: [InlineData("Season 2/16 12 Some Title.avi", 16)]
+ // TODO: [InlineData("/The.Legend.of.Condor.Heroes.2017.V2.web-dl.1080p.h264.aac-hdctv/The.Legend.of.Condor.Heroes.2017.E07.V2.web-dl.1080p.h264.aac-hdctv.mkv", 7)]
+ // TODO: [InlineData("Season 4/Uchuu.Senkan.Yamato.2199.E03.avi", 3)]
+ // TODO: [InlineData("Season 2/7 12 Angry Men.avi", 7)]
+ // TODO: [InlineData("Season 02/02x03x04x15 - Ep Name.mp4", 2)]
+ // TODO: [InlineData("Season 2/[HorribleSubs] Hunter X Hunter - 136 [720p].mkv", 136)]
+ public void GetEpisodeNumberFromFileTest(string path, int? expected)
+ {
+ var result = new EpisodePathParser(_namingOptions)
.Parse(path, false);
- return result.EpisodeNumber;
+ Assert.Equal(expected, result.EpisodeNumber);
}
}
}
diff --git a/tests/Jellyfin.Naming.Tests/TV/EpisodeNumberWithoutSeasonTests.cs b/tests/Jellyfin.Naming.Tests/TV/EpisodeNumberWithoutSeasonTests.cs
index 00aa9ee7c..0c7d9520e 100644
--- a/tests/Jellyfin.Naming.Tests/TV/EpisodeNumberWithoutSeasonTests.cs
+++ b/tests/Jellyfin.Naming.Tests/TV/EpisodeNumberWithoutSeasonTests.cs
@@ -6,122 +6,31 @@ namespace Jellyfin.Naming.Tests.TV
{
public class EpisodeNumberWithoutSeasonTests
{
- [Fact]
- public void TestEpisodeNumberWithoutSeason1()
- {
- Assert.Equal(8, GetEpisodeNumberFromFile(@"The Simpsons/The Simpsons.S25E08.Steal this episode.mp4"));
- }
-
- [Fact]
- public void TestEpisodeNumberWithoutSeason2()
- {
- Assert.Equal(2, GetEpisodeNumberFromFile(@"The Simpsons/The Simpsons - 02 - Ep Name.avi"));
- }
-
- [Fact]
- public void TestEpisodeNumberWithoutSeason3()
- {
- Assert.Equal(2, GetEpisodeNumberFromFile(@"The Simpsons/02.avi"));
- }
-
- [Fact]
- public void TestEpisodeNumberWithoutSeason4()
- {
- Assert.Equal(2, GetEpisodeNumberFromFile(@"The Simpsons/02 - Ep Name.avi"));
- }
-
- [Fact]
- public void TestEpisodeNumberWithoutSeason5()
- {
- Assert.Equal(2, GetEpisodeNumberFromFile(@"The Simpsons/02-Ep Name.avi"));
- }
- [Fact]
- public void TestEpisodeNumberWithoutSeason6()
- {
- Assert.Equal(2, GetEpisodeNumberFromFile(@"The Simpsons/02.EpName.avi"));
- }
-
- [Fact]
- public void TestEpisodeNumberWithoutSeason7()
- {
- Assert.Equal(2, GetEpisodeNumberFromFile(@"The Simpsons/The Simpsons - 02.avi"));
- }
-
- [Fact]
- public void TestEpisodeNumberWithoutSeason8()
- {
- Assert.Equal(2, GetEpisodeNumberFromFile(@"The Simpsons/The Simpsons - 02 Ep Name.avi"));
- }
-
- // FIXME
- // [Fact]
- public void TestEpisodeNumberWithoutSeason9()
- {
- Assert.Equal(2, GetEpisodeNumberFromFile(@"The Simpsons/The Simpsons 5 - 02 - Ep Name.avi"));
- }
-
- // FIXME
- // [Fact]
- public void TestEpisodeNumberWithoutSeason10()
- {
- Assert.Equal(2, GetEpisodeNumberFromFile(@"The Simpsons/The Simpsons 5 - 02 Ep Name.avi"));
- }
-
- // FIXME
- // [Fact]
- public void TestEpisodeNumberWithoutSeason11()
- {
- Assert.Equal(7, GetEpisodeNumberFromFile(@"Seinfeld/Seinfeld 0807 The Checks.avi"));
- Assert.Equal(8, GetSeasonNumberFromFile(@"Seinfeld/Seinfeld 0807 The Checks.avi"));
- }
-
- [Fact]
- public void TestEpisodeNumberWithoutSeason12()
- {
- Assert.Equal(7, GetEpisodeNumberFromFile(@"GJ Club (2013)/GJ Club - 07.mkv"));
- }
-
- // FIXME
- // [Fact]
- public void TestEpisodeNumberWithoutSeason13()
- {
- // This is not supported anymore after removing the episode number 365+ hack from EpisodePathParser
- Assert.Equal(13, GetEpisodeNumberFromFile(@"Case Closed (1996-2007)/Case Closed - 13.mkv"));
- }
-
- [Fact]
- public void TestEpisodeNumberWithoutSeason14()
- {
- Assert.Equal(3, GetSeasonNumberFromFile(@"Case Closed (1996-2007)/Case Closed - 317.mkv"));
- Assert.Equal(17, GetEpisodeNumberFromFile(@"Case Closed (1996-2007)/Case Closed - 317.mkv"));
- }
-
- [Fact]
- public void TestEpisodeNumberWithoutSeason15()
- {
- Assert.Equal(2017, GetSeasonNumberFromFile(@"Running Man/Running Man S2017E368.mkv"));
- }
-
- private int? GetEpisodeNumberFromFile(string path)
+ [Theory]
+ [InlineData(8, @"The Simpsons/The Simpsons.S25E08.Steal this episode.mp4")]
+ [InlineData(2, @"The Simpsons/The Simpsons - 02 - Ep Name.avi")]
+ [InlineData(2, @"The Simpsons/02.avi")]
+ [InlineData(2, @"The Simpsons/02 - Ep Name.avi")]
+ [InlineData(2, @"The Simpsons/02-Ep Name.avi")]
+ [InlineData(2, @"The Simpsons/02.EpName.avi")]
+ [InlineData(2, @"The Simpsons/The Simpsons - 02.avi")]
+ [InlineData(2, @"The Simpsons/The Simpsons - 02 Ep Name.avi")]
+ [InlineData(7, @"GJ Club (2013)/GJ Club - 07.mkv")]
+ [InlineData(17, @"Case Closed (1996-2007)/Case Closed - 317.mkv")]
+ // TODO: [InlineData(2, @"The Simpsons/The Simpsons 5 - 02 - Ep Name.avi")]
+ // TODO: [InlineData(2, @"The Simpsons/The Simpsons 5 - 02 Ep Name.avi")]
+ // TODO: [InlineData(7, @"Seinfeld/Seinfeld 0807 The Checks.avi")]
+ // This is not supported anymore after removing the episode number 365+ hack from EpisodePathParser
+ // TODO: [InlineData(13, @"Case Closed (1996-2007)/Case Closed - 13.mkv")]
+ public void GetEpisodeNumberFromFileTest(int episodeNumber, string path)
{
var options = new NamingOptions();
var result = new EpisodeResolver(options)
.Resolve(path, false);
- return result.EpisodeNumber;
+ Assert.Equal(episodeNumber, result.EpisodeNumber);
}
-
- private int? GetSeasonNumberFromFile(string path)
- {
- var options = new NamingOptions();
-
- var result = new EpisodeResolver(options)
- .Resolve(path, false);
-
- return result.SeasonNumber;
- }
-
}
}
diff --git a/tests/Jellyfin.Naming.Tests/TV/EpisodePathParserTest.cs b/tests/Jellyfin.Naming.Tests/TV/EpisodePathParserTest.cs
index da6e99310..4b5606715 100644
--- a/tests/Jellyfin.Naming.Tests/TV/EpisodePathParserTest.cs
+++ b/tests/Jellyfin.Naming.Tests/TV/EpisodePathParserTest.cs
@@ -15,7 +15,27 @@ namespace Jellyfin.Naming.Tests.TV
[InlineData("D:\\media\\Foo - S04E011", "Foo", 4, 11)]
[InlineData("D:\\media\\Foo\\Foo s01x01", "Foo", 1, 1)]
[InlineData("D:\\media\\Foo (2019)\\Season 4\\Foo (2019).S04E03", "Foo (2019)", 4, 3)]
+ [InlineData("/Season 2/Elementary - 02x03-04-15 - Ep Name.mp4", "Elementary", 2, 3)]
+ [InlineData("/Season 1/seriesname S01E02 blah.avi", "seriesname", 1, 2)]
+ [InlineData("/Running Man/Running Man S2017E368.mkv", "Running Man", 2017, 368)]
+ [InlineData("/Season 1/seriesname 01x02 blah.avi", "seriesname", 1, 2)]
+ [InlineData("/Season 25/The Simpsons.S25E09.Steal this episode.mp4", "The Simpsons", 25, 9)]
+ [InlineData("/Season 1/seriesname S01x02 blah.avi", "seriesname", 1, 2)]
+ [InlineData("/Season 2/Elementary - 02x03 - 02x04 - 02x15 - Ep Name.mp4", "Elementary", 2, 3)]
+ [InlineData("/Season 1/seriesname S01xE02 blah.avi", "seriesname", 1, 2)]
+ [InlineData("/Season 02/Elementary - 02x03 - x04 - x15 - Ep Name.mp4", "Elementary", 2, 3)]
+ [InlineData("/Season 02/Elementary - 02x03x04x15 - Ep Name.mp4", "Elementary", 2, 3)]
+ [InlineData("/Season 02/Elementary - 02x03-E15 - Ep Name.mp4", "Elementary", 2, 3)]
+ [InlineData("/Season 1/Elementary - S01E23-E24-E26 - The Woman.mp4", "Elementary", 1, 23)]
+ [InlineData("/The Wonder Years/The.Wonder.Years.S04.PDTV.x264-JCH/The Wonder Years s04e07 Christmas Party NTSC PDTV.avi", "The Wonder Years", 4, 7)]
+ // TODO: [InlineData("/Castle Rock 2x01 Que el rio siga su curso [WEB-DL HULU 1080p h264 Dual DD5.1 Subs].mkv", "Castle Rock", 2, 1)]
+ // TODO: [InlineData("/After Life 1x06 Episodio 6 [WEB-DL NF 1080p h264 Dual DD 5.1 Sub].mkv", "After Life", 1, 6)]
+ // TODO: [InlineData("/Season 4/Uchuu.Senkan.Yamato.2199.E03.avi", "Uchuu Senkan Yamoto 2199", 4, 3)]
+ // TODO: [InlineData("The Daily Show/The Daily Show 25x22 - [WEBDL-720p][AAC 2.0][x264] Noah Baumbach-TBS.mkv", "The Daily Show", 25, 22)]
+ // TODO: [InlineData("Watchmen (2019)/Watchmen 1x03 [WEBDL-720p][EAC3 5.1][h264][-TBS] - She Was Killed by Space Junk.mkv", "Watchmen (2019)", 1, 3)]
+ // TODO: [InlineData("/The.Legend.of.Condor.Heroes.2017.V2.web-dl.1080p.h264.aac-hdctv/The.Legend.of.Condor.Heroes.2017.E07.V2.web-dl.1080p.h264.aac-hdctv.mkv", "The Legend of Condor Heroes 2017", 1, 7)]
public void ParseEpisodesCorrectly(string path, string name, int season, int episode)
+
{
NamingOptions o = new NamingOptions();
EpisodePathParser p = new EpisodePathParser(o);
@@ -26,22 +46,5 @@ namespace Jellyfin.Naming.Tests.TV
Assert.Equal(season, res.SeasonNumber);
Assert.Equal(episode, res.EpisodeNumber);
}
-
- [Theory]
- [InlineData("/media/Foo/Foo 889", "Foo", 889)]
- [InlineData("/media/Foo/[Bar] Foo Baz - 11 [1080p]", "Foo Baz", 11)]
- [InlineData("D:\\media\\Foo\\Foo 889", "Foo", 889)]
- [InlineData("D:\\media\\Foo\\[Bar] Foo Baz - 11 [1080p]", "Foo Baz", 11)]
- public void ParseEpisodeWithoutSeason(string path, string name, int episode)
- {
- NamingOptions o = new NamingOptions();
- EpisodePathParser p = new EpisodePathParser(o);
- var res = p.Parse(path, true, fillExtendedInfo: true);
-
- Assert.True(res.Success);
- Assert.Equal(name, res.SeriesName);
- Assert.Null(res.SeasonNumber);
- Assert.Equal(episode, res.EpisodeNumber);
- }
}
}
diff --git a/tests/Jellyfin.Naming.Tests/TV/EpisodeWithoutSeasonTests.cs b/tests/Jellyfin.Naming.Tests/TV/EpisodeWithoutSeasonTests.cs
index c2851ccdb..364eb7ff8 100644
--- a/tests/Jellyfin.Naming.Tests/TV/EpisodeWithoutSeasonTests.cs
+++ b/tests/Jellyfin.Naming.Tests/TV/EpisodeWithoutSeasonTests.cs
@@ -6,42 +6,13 @@ namespace Jellyfin.Naming.Tests.TV
{
public class EpisodeWithoutSeasonTests
{
- // FIXME
- // [Fact]
- public void TestWithoutSeason1()
- {
- Test(@"/server/anything_ep02.mp4", "anything", null, 2);
- }
-
- // FIXME
- // [Fact]
- public void TestWithoutSeason2()
- {
- Test(@"/server/anything_ep_02.mp4", "anything", null, 2);
- }
-
- // FIXME
- // [Fact]
- public void TestWithoutSeason3()
- {
- Test(@"/server/anything_part.II.mp4", "anything", null, null);
- }
-
- // FIXME
- // [Fact]
- public void TestWithoutSeason4()
- {
- Test(@"/server/anything_pt.II.mp4", "anything", null, null);
- }
-
- // FIXME
- // [Fact]
- public void TestWithoutSeason5()
- {
- Test(@"/server/anything_pt_II.mp4", "anything", null, null);
- }
-
- private void Test(string path, string seriesName, int? seasonNumber, int? episodeNumber)
+ // TODO: [Theory]
+ // TODO: [InlineData(@"/server/anything_ep02.mp4", "anything", null, 2)]
+ // TODO: [InlineData(@"/server/anything_ep_02.mp4", "anything", null, 2)]
+ // TODO: [InlineData(@"/server/anything_part.II.mp4", "anything", null, null)]
+ // TODO: [InlineData(@"/server/anything_pt.II.mp4", "anything", null, null)]
+ // TODO: [InlineData(@"/server/anything_pt_II.mp4", "anything", null, null)]
+ public void Test(string path, string seriesName, int? seasonNumber, int? episodeNumber)
{
var options = new NamingOptions();
diff --git a/tests/Jellyfin.Naming.Tests/TV/MultiEpisodeTests.cs b/tests/Jellyfin.Naming.Tests/TV/MultiEpisodeTests.cs
index b15dd6b74..3513050b6 100644
--- a/tests/Jellyfin.Naming.Tests/TV/MultiEpisodeTests.cs
+++ b/tests/Jellyfin.Naming.Tests/TV/MultiEpisodeTests.cs
@@ -6,100 +6,75 @@ namespace Jellyfin.Naming.Tests.TV
{
public class MultiEpisodeTests
{
- [Fact]
- public void TestGetEndingEpisodeNumberFromFile()
- {
- Assert.Null(GetEndingEpisodeNumberFromFile(@"Season 1/4x01 – 20 Hours in America (1).mkv"));
-
- Assert.Null(GetEndingEpisodeNumberFromFile(@"Season 1/01x02 blah.avi"));
- Assert.Null(GetEndingEpisodeNumberFromFile(@"Season 1/S01x02 blah.avi"));
- Assert.Null(GetEndingEpisodeNumberFromFile(@"Season 1/S01E02 blah.avi"));
- Assert.Null(GetEndingEpisodeNumberFromFile(@"Season 1/S01xE02 blah.avi"));
- Assert.Null(GetEndingEpisodeNumberFromFile(@"Season 1/seriesname 01x02 blah.avi"));
- Assert.Null(GetEndingEpisodeNumberFromFile(@"Season 1/seriesname S01x02 blah.avi"));
- Assert.Null(GetEndingEpisodeNumberFromFile(@"Season 1/seriesname S01E02 blah.avi"));
- Assert.Null(GetEndingEpisodeNumberFromFile(@"Season 1/seriesname S01xE02 blah.avi"));
- Assert.Null(GetEndingEpisodeNumberFromFile(@"Season 2/02x03 - 04 Ep Name.mp4"));
- Assert.Null(GetEndingEpisodeNumberFromFile(@"Season 2/My show name 02x03 - 04 Ep Name.mp4"));
- Assert.Equal(15, GetEndingEpisodeNumberFromFile(@"Season 2/Elementary - 02x03 - 02x04 - 02x15 - Ep Name.mp4"));
- Assert.Equal(15, GetEndingEpisodeNumberFromFile(@"Season 2/02x03 - 02x04 - 02x15 - Ep Name.mp4"));
- Assert.Equal(15, GetEndingEpisodeNumberFromFile(@"Season 2/02x03-04-15 - Ep Name.mp4"));
- Assert.Equal(15, GetEndingEpisodeNumberFromFile(@"Season 2/Elementary - 02x03-04-15 - Ep Name.mp4"));
- Assert.Equal(15, GetEndingEpisodeNumberFromFile(@"Season 02/02x03-E15 - Ep Name.mp4"));
- Assert.Equal(15, GetEndingEpisodeNumberFromFile(@"Season 02/Elementary - 02x03-E15 - Ep Name.mp4"));
- Assert.Equal(15, GetEndingEpisodeNumberFromFile(@"Season 02/02x03 - x04 - x15 - Ep Name.mp4"));
- Assert.Equal(15, GetEndingEpisodeNumberFromFile(@"Season 02/Elementary - 02x03 - x04 - x15 - Ep Name.mp4"));
- Assert.Equal(15, GetEndingEpisodeNumberFromFile(@"Season 02/02x03x04x15 - Ep Name.mp4"));
- Assert.Equal(15, GetEndingEpisodeNumberFromFile(@"Season 02/Elementary - 02x03x04x15 - Ep Name.mp4"));
- Assert.Equal(26, GetEndingEpisodeNumberFromFile(@"Season 1/Elementary - S01E23-E24-E26 - The Woman.mp4"));
- Assert.Equal(26, GetEndingEpisodeNumberFromFile(@"Season 1/S01E23-E24-E26 - The Woman.mp4"));
-
-
- // Four Digits seasons
- Assert.Null(GetEndingEpisodeNumberFromFile(@"Season 2009/2009x02 blah.avi"));
- Assert.Null(GetEndingEpisodeNumberFromFile(@"Season 2009/S2009x02 blah.avi"));
- Assert.Null(GetEndingEpisodeNumberFromFile(@"Season 2009/S2009E02 blah.avi"));
- Assert.Null(GetEndingEpisodeNumberFromFile(@"Season 2009/S2009xE02 blah.avi"));
- Assert.Null(GetEndingEpisodeNumberFromFile(@"Season 2009/seriesname 2009x02 blah.avi"));
- Assert.Null(GetEndingEpisodeNumberFromFile(@"Season 2009/seriesname S2009x02 blah.avi"));
- Assert.Null(GetEndingEpisodeNumberFromFile(@"Season 2009/seriesname S2009E02 blah.avi"));
- Assert.Null(GetEndingEpisodeNumberFromFile(@"Season 2009/seriesname S2009xE02 blah.avi"));
- Assert.Equal(15, GetEndingEpisodeNumberFromFile(@"Season 2009/Elementary - 2009x03 - 2009x04 - 2009x15 - Ep Name.mp4"));
- Assert.Equal(15, GetEndingEpisodeNumberFromFile(@"Season 2009/2009x03 - 2009x04 - 2009x15 - Ep Name.mp4"));
- Assert.Equal(15, GetEndingEpisodeNumberFromFile(@"Season 2009/2009x03-04-15 - Ep Name.mp4"));
- Assert.Equal(15, GetEndingEpisodeNumberFromFile(@"Season 2009/Elementary - 2009x03-04-15 - Ep Name.mp4"));
- Assert.Equal(15, GetEndingEpisodeNumberFromFile(@"Season 2009/2009x03-E15 - Ep Name.mp4"));
- Assert.Equal(15, GetEndingEpisodeNumberFromFile(@"Season 2009/Elementary - 2009x03-E15 - Ep Name.mp4"));
- Assert.Equal(15, GetEndingEpisodeNumberFromFile(@"Season 2009/2009x03 - x04 - x15 - Ep Name.mp4"));
- Assert.Equal(15, GetEndingEpisodeNumberFromFile(@"Season 2009/Elementary - 2009x03 - x04 - x15 - Ep Name.mp4"));
- Assert.Equal(15, GetEndingEpisodeNumberFromFile(@"Season 2009/2009x03x04x15 - Ep Name.mp4"));
- Assert.Equal(15, GetEndingEpisodeNumberFromFile(@"Season 2009/Elementary - 2009x03x04x15 - Ep Name.mp4"));
- Assert.Equal(26, GetEndingEpisodeNumberFromFile(@"Season 2009/Elementary - S2009E23-E24-E26 - The Woman.mp4"));
- Assert.Equal(26, GetEndingEpisodeNumberFromFile(@"Season 2009/S2009E23-E24-E26 - The Woman.mp4"));
-
- // Without season number
- Assert.Null(GetEndingEpisodeNumberFromFile(@"Season 1/02 - blah.avi"));
- Assert.Null(GetEndingEpisodeNumberFromFile(@"Season 2/02 - blah 14 blah.avi"));
- Assert.Null(GetEndingEpisodeNumberFromFile(@"Season 1/02 - blah-02 a.avi"));
- Assert.Null(GetEndingEpisodeNumberFromFile(@"Season 2/02.avi"));
-
- Assert.Equal(3, GetEndingEpisodeNumberFromFile(@"Season 1/02-03 - blah.avi"));
- Assert.Equal(4, GetEndingEpisodeNumberFromFile(@"Season 2/02-04 - blah 14 blah.avi"));
- Assert.Equal(5, GetEndingEpisodeNumberFromFile(@"Season 1/02-05 - blah-02 a.avi"));
- Assert.Equal(4, GetEndingEpisodeNumberFromFile(@"Season 2/02-04.avi"));
- Assert.Null(GetEndingEpisodeNumberFromFile(@"Season 2/[HorribleSubs] Hunter X Hunter - 136 [720p].mkv"));
-
- // With format specification that must not be detected as ending episode number
- Assert.Null(GetEndingEpisodeNumberFromFile(@"Season 1/series-s09e14-1080p.mkv"));
- Assert.Null(GetEndingEpisodeNumberFromFile(@"Season 1/series-s09e14-720p.mkv"));
- Assert.Null(GetEndingEpisodeNumberFromFile(@"Season 1/series-s09e14-720i.mkv"));
- Assert.Equal(4, GetEndingEpisodeNumberFromFile(@"Season 1/MOONLIGHTING_s01e01-e04.mkv"));
- }
-
- [Fact]
- public void TestGetEndingEpisodeNumberFromFolder()
- {
- Assert.Equal(4, GetEndingEpisodeNumberFromFolder(@"Season 1/MOONLIGHTING_s01e01-e04"));
- }
-
- private int? GetEndingEpisodeNumberFromFolder(string path)
- {
- var options = new NamingOptions();
-
- var result = new EpisodePathParser(options)
- .Parse(path, true);
-
- return result.EndingEpsiodeNumber;
- }
-
- private int? GetEndingEpisodeNumberFromFile(string path)
+ [Theory]
+ [InlineData(@"Season 1/4x01 – 20 Hours in America (1).mkv", null)]
+ [InlineData(@"Season 1/01x02 blah.avi", null)]
+ [InlineData(@"Season 1/S01x02 blah.avi", null)]
+ [InlineData(@"Season 1/S01E02 blah.avi", null)]
+ [InlineData(@"Season 1/S01xE02 blah.avi", null)]
+ [InlineData(@"Season 1/seriesname 01x02 blah.avi", null)]
+ [InlineData(@"Season 1/seriesname S01x02 blah.avi", null)]
+ [InlineData(@"Season 1/seriesname S01E02 blah.avi", null)]
+ [InlineData(@"Season 1/seriesname S01xE02 blah.avi", null)]
+ [InlineData(@"Season 2/02x03 - 04 Ep Name.mp4", null)]
+ [InlineData(@"Season 2/My show name 02x03 - 04 Ep Name.mp4", null)]
+ [InlineData(@"Season 2/Elementary - 02x03 - 02x04 - 02x15 - Ep Name.mp4", 15)]
+ [InlineData(@"Season 2/02x03 - 02x04 - 02x15 - Ep Name.mp4", 15)]
+ [InlineData(@"Season 2/02x03-04-15 - Ep Name.mp4", 15)]
+ [InlineData(@"Season 2/Elementary - 02x03-04-15 - Ep Name.mp4", 15)]
+ [InlineData(@"Season 02/02x03-E15 - Ep Name.mp4", 15)]
+ [InlineData(@"Season 02/Elementary - 02x03-E15 - Ep Name.mp4", 15)]
+ [InlineData(@"Season 02/02x03 - x04 - x15 - Ep Name.mp4", 15)]
+ [InlineData(@"Season 02/Elementary - 02x03 - x04 - x15 - Ep Name.mp4", 15)]
+ [InlineData(@"Season 02/02x03x04x15 - Ep Name.mp4", 15)]
+ [InlineData(@"Season 02/Elementary - 02x03x04x15 - Ep Name.mp4", 15)]
+ [InlineData(@"Season 1/Elementary - S01E23-E24-E26 - The Woman.mp4", 26)]
+ [InlineData(@"Season 1/S01E23-E24-E26 - The Woman.mp4", 26)]
+ // Four Digits seasons
+ [InlineData(@"Season 2009/2009x02 blah.avi", null)]
+ [InlineData(@"Season 2009/S2009x02 blah.avi", null)]
+ [InlineData(@"Season 2009/S2009E02 blah.avi", null)]
+ [InlineData(@"Season 2009/S2009xE02 blah.avi", null)]
+ [InlineData(@"Season 2009/seriesname 2009x02 blah.avi", null)]
+ [InlineData(@"Season 2009/seriesname S2009x02 blah.avi", null)]
+ [InlineData(@"Season 2009/seriesname S2009E02 blah.avi", null)]
+ [InlineData(@"Season 2009/seriesname S2009xE02 blah.avi", null)]
+ [InlineData(@"Season 2009/Elementary - 2009x03 - 2009x04 - 2009x15 - Ep Name.mp4", 15)]
+ [InlineData(@"Season 2009/2009x03 - 2009x04 - 2009x15 - Ep Name.mp4", 15)]
+ [InlineData(@"Season 2009/2009x03-04-15 - Ep Name.mp4", 15)]
+ [InlineData(@"Season 2009/Elementary - 2009x03-04-15 - Ep Name.mp4", 15)]
+ [InlineData(@"Season 2009/2009x03-E15 - Ep Name.mp4", 15)]
+ [InlineData(@"Season 2009/Elementary - 2009x03-E15 - Ep Name.mp4", 15)]
+ [InlineData(@"Season 2009/2009x03 - x04 - x15 - Ep Name.mp4", 15)]
+ [InlineData(@"Season 2009/Elementary - 2009x03 - x04 - x15 - Ep Name.mp4", 15)]
+ [InlineData(@"Season 2009/2009x03x04x15 - Ep Name.mp4", 15)]
+ [InlineData(@"Season 2009/Elementary - 2009x03x04x15 - Ep Name.mp4", 15)]
+ [InlineData(@"Season 2009/Elementary - S2009E23-E24-E26 - The Woman.mp4", 26)]
+ [InlineData(@"Season 2009/S2009E23-E24-E26 - The Woman.mp4", 26)]
+ // Without season number
+ [InlineData(@"Season 1/02 - blah.avi", null)]
+ [InlineData(@"Season 2/02 - blah 14 blah.avi", null)]
+ [InlineData(@"Season 1/02 - blah-02 a.avi", null)]
+ [InlineData(@"Season 2/02.avi", null)]
+ [InlineData(@"Season 1/02-03 - blah.avi", 3)]
+ [InlineData(@"Season 2/02-04 - blah 14 blah.avi", 4)]
+ [InlineData(@"Season 1/02-05 - blah-02 a.avi", 5)]
+ [InlineData(@"Season 2/02-04.avi", 4)]
+ [InlineData(@"Season 2 /[HorribleSubs] Hunter X Hunter - 136[720p].mkv", null)]
+ // With format specification that must not be detected as ending episode number
+ [InlineData(@"Season 1/series-s09e14-1080p.mkv", null)]
+ [InlineData(@"Season 1/series-s09e14-720p.mkv", null)]
+ [InlineData(@"Season 1/series-s09e14-720i.mkv", null)]
+ [InlineData(@"Season 1/MOONLIGHTING_s01e01-e04.mkv", 4)]
+ [InlineData(@"Season 1/MOONLIGHTING_s01e01-e04", 4)]
+ public void TestGetEndingEpisodeNumberFromFile(string filename, int? endingEpisodeNumber)
{
var options = new NamingOptions();
var result = new EpisodePathParser(options)
- .Parse(path, false);
+ .Parse(filename, false);
- return result.EndingEpsiodeNumber;
+ Assert.Equal(result.EndingEpsiodeNumber, endingEpisodeNumber);
}
}
}
diff --git a/tests/Jellyfin.Naming.Tests/TV/SeasonFolderTests.cs b/tests/Jellyfin.Naming.Tests/TV/SeasonFolderTests.cs
index ffa8d3483..078f940b2 100644
--- a/tests/Jellyfin.Naming.Tests/TV/SeasonFolderTests.cs
+++ b/tests/Jellyfin.Naming.Tests/TV/SeasonFolderTests.cs
@@ -5,108 +5,27 @@ namespace Jellyfin.Naming.Tests.TV
{
public class SeasonFolderTests
{
- [Fact]
- public void TestGetSeasonNumberFromPath1()
- {
- Assert.Equal(1, GetSeasonNumberFromPath(@"/Drive/Season 1"));
- }
-
- [Fact]
- public void TestGetSeasonNumberFromPath2()
- {
- Assert.Equal(2, GetSeasonNumberFromPath(@"/Drive/Season 2"));
- }
-
- [Fact]
- public void TestGetSeasonNumberFromPath3()
- {
- Assert.Equal(2, GetSeasonNumberFromPath(@"/Drive/Season 02"));
- }
-
- [Fact]
- public void TestGetSeasonNumberFromPath4()
- {
- Assert.Equal(1, GetSeasonNumberFromPath(@"/Drive/Season 1"));
- }
-
- [Fact]
- public void TestGetSeasonNumberFromPath5()
- {
- Assert.Equal(2, GetSeasonNumberFromPath(@"/Drive/Seinfeld/S02"));
- }
-
- [Fact]
- public void TestGetSeasonNumberFromPath6()
- {
- Assert.Equal(2, GetSeasonNumberFromPath(@"/Drive/Seinfeld/2"));
- }
-
- [Fact]
- public void TestGetSeasonNumberFromPath7()
- {
- Assert.Equal(2009, GetSeasonNumberFromPath(@"/Drive/Season 2009"));
- }
-
- [Fact]
- public void TestGetSeasonNumberFromPath8()
- {
- Assert.Equal(1, GetSeasonNumberFromPath(@"/Drive/Season1"));
- }
-
- [Fact]
- public void TestGetSeasonNumberFromPath9()
- {
- Assert.Equal(4, GetSeasonNumberFromPath(@"The Wonder Years/The.Wonder.Years.S04.PDTV.x264-JCH"));
- }
-
- [Fact]
- public void TestGetSeasonNumberFromPath10()
- {
- Assert.Equal(7, GetSeasonNumberFromPath(@"/Drive/Season 7 (2016)"));
- }
-
- [Fact]
- public void TestGetSeasonNumberFromPath11()
- {
- Assert.Equal(7, GetSeasonNumberFromPath(@"/Drive/Staffel 7 (2016)"));
- }
-
- [Fact]
- public void TestGetSeasonNumberFromPath12()
- {
- Assert.Equal(7, GetSeasonNumberFromPath(@"/Drive/Stagione 7 (2016)"));
- }
-
- [Fact]
- public void TestGetSeasonNumberFromPath14()
- {
- Assert.Null(GetSeasonNumberFromPath(@"/Drive/Season (8)"));
- }
-
- [Fact]
- public void TestGetSeasonNumberFromPath13()
- {
- Assert.Equal(3, GetSeasonNumberFromPath(@"/Drive/3.Staffel"));
- }
-
- [Fact]
- public void TestGetSeasonNumberFromPath15()
- {
- Assert.Null(GetSeasonNumberFromPath(@"/Drive/s06e05"));
- }
-
- [Fact]
- public void TestGetSeasonNumberFromPath16()
- {
- Assert.Null(GetSeasonNumberFromPath(@"/Drive/The.Legend.of.Condor.Heroes.2017.V2.web-dl.1080p.h264.aac-hdctv"));
- }
-
- private int? GetSeasonNumberFromPath(string path)
- {
- var result = new SeasonPathParser()
- .Parse(path, true, true);
-
- return result.SeasonNumber;
+ [Theory]
+ [InlineData(@"/Drive/Season 1", 1)]
+ [InlineData(@"/Drive/Season 2", 2)]
+ [InlineData(@"/Drive/Season 02", 2)]
+ [InlineData(@"/Drive/Seinfeld/S02", 2)]
+ [InlineData(@"/Drive/Seinfeld/2", 2)]
+ [InlineData(@"/Drive/Season 2009", 2009)]
+ [InlineData(@"/Drive/Season1", 1)]
+ [InlineData(@"The Wonder Years/The.Wonder.Years.S04.PDTV.x264-JCH", 4)]
+ [InlineData(@"/Drive/Season 7 (2016)", 7)]
+ [InlineData(@"/Drive/Staffel 7 (2016)", 7)]
+ [InlineData(@"/Drive/Stagione 7 (2016)", 7)]
+ [InlineData(@"/Drive/Season (8)", null)]
+ [InlineData(@"/Drive/3.Staffel", 3)]
+ [InlineData(@"/Drive/s06e05", null)]
+ [InlineData(@"/Drive/The.Legend.of.Condor.Heroes.2017.V2.web-dl.1080p.h264.aac-hdctv", null)]
+ public void GetSeasonNumberFromPathTest(string path, int? seasonNumber)
+ {
+ var result = SeasonPathParser.Parse(path, true, true);
+
+ Assert.Equal(result.SeasonNumber, seasonNumber);
}
}
}
diff --git a/tests/Jellyfin.Naming.Tests/TV/SeasonNumberTests.cs b/tests/Jellyfin.Naming.Tests/TV/SeasonNumberTests.cs
index ba3c5ecac..9eaf897b9 100644
--- a/tests/Jellyfin.Naming.Tests/TV/SeasonNumberTests.cs
+++ b/tests/Jellyfin.Naming.Tests/TV/SeasonNumberTests.cs
@@ -6,300 +6,60 @@ namespace Jellyfin.Naming.Tests.TV
{
public class SeasonNumberTests
{
- private int? GetSeasonNumberFromEpisodeFile(string path)
- {
- var options = new NamingOptions();
-
- var result = new EpisodeResolver(options)
+ private readonly NamingOptions _namingOptions = new NamingOptions();
+
+ [Theory]
+ [InlineData("The Daily Show/The Daily Show 25x22 - [WEBDL-720p][AAC 2.0][x264] Noah Baumbach-TBS.mkv", 25)]
+ [InlineData("/Show/Season 02/S02E03 blah.avi", 2)]
+ [InlineData("Season 1/seriesname S01x02 blah.avi", 1)]
+ [InlineData("Season 1/S01x02 blah.avi", 1)]
+ [InlineData("Season 1/seriesname S01xE02 blah.avi", 1)]
+ [InlineData("Season 1/01x02 blah.avi", 1)]
+ [InlineData("Season 1/S01E02 blah.avi", 1)]
+ [InlineData("Season 1/S01xE02 blah.avi", 1)]
+ [InlineData("Season 1/seriesname 01x02 blah.avi", 1)]
+ [InlineData("Season 1/seriesname S01E02 blah.avi", 1)]
+ [InlineData("Season 2/Elementary - 02x03 - 02x04 - 02x15 - Ep Name.mp4", 2)]
+ [InlineData("Season 2/02x03 - 02x04 - 02x15 - Ep Name.mp4", 2)]
+ [InlineData("Season 2/02x03-04-15 - Ep Name.mp4", 2)]
+ [InlineData("Season 2/Elementary - 02x03-04-15 - Ep Name.mp4", 2)]
+ [InlineData("Season 02/02x03-E15 - Ep Name.mp4", 2)]
+ [InlineData("Season 02/Elementary - 02x03-E15 - Ep Name.mp4", 2)]
+ [InlineData("Season 02/02x03 - x04 - x15 - Ep Name.mp4", 2)]
+ [InlineData("Season 02/Elementary - 02x03 - x04 - x15 - Ep Name.mp4", 2)]
+ [InlineData("Season 02/02x03x04x15 - Ep Name.mp4", 2)]
+ [InlineData("Season 02/Elementary - 02x03x04x15 - Ep Name.mp4", 2)]
+ [InlineData("Season 1/Elementary - S01E23-E24-E26 - The Woman.mp4", 1)]
+ [InlineData("Season 1/S01E23-E24-E26 - The Woman.mp4", 1)]
+ [InlineData("Season 25/The Simpsons.S25E09.Steal this episode.mp4", 25)]
+ [InlineData("The Simpsons/The Simpsons.S25E09.Steal this episode.mp4", 25)]
+ [InlineData("2016/Season s2016e1.mp4", 2016)]
+ [InlineData("2016/Season 2016x1.mp4", 2016)]
+ [InlineData("Season 2009/2009x02 blah.avi", 2009)]
+ [InlineData("Season 2009/S2009x02 blah.avi", 2009)]
+ [InlineData("Season 2009/S2009E02 blah.avi", 2009)]
+ [InlineData("Season 2009/S2009xE02 blah.avi", 2009)]
+ [InlineData("Season 2009/seriesname 2009x02 blah.avi", 2009)]
+ [InlineData("Season 2009/seriesname S2009x02 blah.avi", 2009)]
+ [InlineData("Season 2009/seriesname S2009E02 blah.avi", 2009)]
+ [InlineData("Season 2009/Elementary - 2009x03 - 2009x04 - 2009x15 - Ep Name.mp4", 2009)]
+ [InlineData("Season 2009/2009x03 - 2009x04 - 2009x15 - Ep Name.mp4", 2009)]
+ [InlineData("Season 2009/2009x03-04-15 - Ep Name.mp4", 2009)]
+ [InlineData("Season 2009/Elementary - 2009x03 - x04 - x15 - Ep Name.mp4", 2009)]
+ [InlineData("Season 2009/2009x03x04x15 - Ep Name.mp4", 2009)]
+ [InlineData("Season 2009/Elementary - 2009x03x04x15 - Ep Name.mp4", 2009)]
+ [InlineData("Season 2009/Elementary - S2009E23-E24-E26 - The Woman.mp4", 2009)]
+ [InlineData("Season 2009/S2009E23-E24-E26 - The Woman.mp4", 2009)]
+ [InlineData("Series/1-12 - The Woman.mp4", 1)]
+ [InlineData(@"Running Man/Running Man S2017E368.mkv", 2017)]
+ [InlineData(@"Case Closed (1996-2007)/Case Closed - 317.mkv", 3)]
+ // TODO: [InlineData(@"Seinfeld/Seinfeld 0807 The Checks.avi", 8)]
+ public void GetSeasonNumberFromEpisodeFileTest(string path, int? expected)
+ {
+ var result = new EpisodeResolver(_namingOptions)
.Resolve(path, false);
- return result.SeasonNumber;
- }
-
- [Fact]
- public void TestSeasonNumber1()
- {
- Assert.Equal(2, GetSeasonNumberFromEpisodeFile(@"/Show/Season 02/S02E03 blah.avi"));
- }
-
- [Fact]
- public void TestSeasonNumber2()
- {
- Assert.Equal(1, GetSeasonNumberFromEpisodeFile(@"Season 1/seriesname S01x02 blah.avi"));
- }
-
- [Fact]
- public void TestSeasonNumber3()
- {
- Assert.Equal(1, GetSeasonNumberFromEpisodeFile(@"Season 1/S01x02 blah.avi"));
- }
-
- [Fact]
- public void TestSeasonNumber4()
- {
- Assert.Equal(1, GetSeasonNumberFromEpisodeFile(@"Season 1/seriesname S01xE02 blah.avi"));
- }
-
- [Fact]
- public void TestSeasonNumber5()
- {
- Assert.Equal(1, GetSeasonNumberFromEpisodeFile(@"Season 1/01x02 blah.avi"));
- }
-
- [Fact]
- public void TestSeasonNumber6()
- {
- Assert.Equal(1, GetSeasonNumberFromEpisodeFile(@"Season 1/S01E02 blah.avi"));
- }
-
- [Fact]
- public void TestSeasonNumber7()
- {
- Assert.Equal(1, GetSeasonNumberFromEpisodeFile(@"Season 1/S01xE02 blah.avi"));
- }
-
- // FIXME
- // [Fact]
- public void TestSeasonNumber8()
- {
- Assert.Equal(1, GetSeasonNumberFromEpisodeFile(@"Season 1/seriesname 01x02 blah.avi"));
- }
-
- [Fact]
- public void TestSeasonNumber9()
- {
- Assert.Equal(1, GetSeasonNumberFromEpisodeFile(@"Season 1/seriesname S01x02 blah.avi"));
- }
-
- [Fact]
- public void TestSeasonNumber10()
- {
- Assert.Equal(1, GetSeasonNumberFromEpisodeFile(@"Season 1/seriesname S01E02 blah.avi"));
- }
-
- [Fact]
- public void TestSeasonNumber11()
- {
- Assert.Equal(2, GetSeasonNumberFromEpisodeFile(@"Season 2/Elementary - 02x03 - 02x04 - 02x15 - Ep Name.mp4"));
- }
-
- [Fact]
- public void TestSeasonNumber12()
- {
- Assert.Equal(2, GetSeasonNumberFromEpisodeFile(@"Season 2/02x03 - 02x04 - 02x15 - Ep Name.mp4"));
- }
-
- [Fact]
- public void TestSeasonNumber13()
- {
- Assert.Equal(2, GetSeasonNumberFromEpisodeFile(@"Season 2/02x03-04-15 - Ep Name.mp4"));
- }
-
- [Fact]
- public void TestSeasonNumber14()
- {
- Assert.Equal(2, GetSeasonNumberFromEpisodeFile(@"Season 2/Elementary - 02x03-04-15 - Ep Name.mp4"));
- }
-
- [Fact]
- public void TestSeasonNumber15()
- {
- Assert.Equal(2, GetSeasonNumberFromEpisodeFile(@"Season 02/02x03-E15 - Ep Name.mp4"));
- }
-
- [Fact]
- public void TestSeasonNumber16()
- {
- Assert.Equal(2, GetSeasonNumberFromEpisodeFile(@"Season 02/Elementary - 02x03-E15 - Ep Name.mp4"));
- }
-
- [Fact]
- public void TestSeasonNumber17()
- {
- Assert.Equal(2, GetSeasonNumberFromEpisodeFile(@"Season 02/02x03 - x04 - x15 - Ep Name.mp4"));
- }
-
- [Fact]
- public void TestSeasonNumber18()
- {
- Assert.Equal(2, GetSeasonNumberFromEpisodeFile(@"Season 02/Elementary - 02x03 - x04 - x15 - Ep Name.mp4"));
- }
-
- [Fact]
- public void TestSeasonNumber19()
- {
- Assert.Equal(2, GetSeasonNumberFromEpisodeFile(@"Season 02/02x03x04x15 - Ep Name.mp4"));
- }
-
- [Fact]
- public void TestSeasonNumber20()
- {
- Assert.Equal(2, GetSeasonNumberFromEpisodeFile(@"Season 02/Elementary - 02x03x04x15 - Ep Name.mp4"));
- }
-
- [Fact]
- public void TestSeasonNumber21()
- {
- Assert.Equal(1, GetSeasonNumberFromEpisodeFile(@"Season 1/Elementary - S01E23-E24-E26 - The Woman.mp4"));
- }
-
- [Fact]
- public void TestSeasonNumber22()
- {
- Assert.Equal(1, GetSeasonNumberFromEpisodeFile(@"Season 1/S01E23-E24-E26 - The Woman.mp4"));
- }
-
- [Fact]
- public void TestSeasonNumber23()
- {
- Assert.Equal(25, GetSeasonNumberFromEpisodeFile(@"Season 25/The Simpsons.S25E09.Steal this episode.mp4"));
- }
-
- [Fact]
- public void TestSeasonNumber24()
- {
- Assert.Equal(25, GetSeasonNumberFromEpisodeFile(@"The Simpsons/The Simpsons.S25E09.Steal this episode.mp4"));
- }
-
- [Fact]
- public void TestSeasonNumber25()
- {
- Assert.Equal(2016, GetSeasonNumberFromEpisodeFile(@"2016/Season s2016e1.mp4"));
- }
-
- // FIXME
- // [Fact]
- public void TestSeasonNumber26()
- {
- // This convention is not currently supported, just adding in case we want to look at it in the future
- Assert.Equal(2016, GetSeasonNumberFromEpisodeFile(@"2016/Season 2016x1.mp4"));
- }
-
- [Fact]
- public void TestFourDigitSeasonNumber1()
- {
- Assert.Equal(2009, GetSeasonNumberFromEpisodeFile(@"Season 2009/2009x02 blah.avi"));
- }
-
- [Fact]
- public void TestFourDigitSeasonNumber2()
- {
- Assert.Equal(2009, GetSeasonNumberFromEpisodeFile(@"Season 2009/S2009x02 blah.avi"));
- }
-
- [Fact]
- public void TestFourDigitSeasonNumber3()
- {
- Assert.Equal(2009, GetSeasonNumberFromEpisodeFile(@"Season 2009/S2009E02 blah.avi"));
- }
-
- [Fact]
- public void TestFourDigitSeasonNumber4()
- {
- Assert.Equal(2009, GetSeasonNumberFromEpisodeFile(@"Season 2009/S2009xE02 blah.avi"));
- }
-
- // FIXME
- // [Fact]
- public void TestFourDigitSeasonNumber5()
- {
- Assert.Equal(2009, GetSeasonNumberFromEpisodeFile(@"Season 2009/seriesname 2009x02 blah.avi"));
- }
-
- [Fact]
- public void TestFourDigitSeasonNumber6()
- {
- Assert.Equal(2009, GetSeasonNumberFromEpisodeFile(@"Season 2009/seriesname S2009x02 blah.avi"));
- }
-
- [Fact]
- public void TestFourDigitSeasonNumber7()
- {
- Assert.Equal(2009, GetSeasonNumberFromEpisodeFile(@"Season 2009/seriesname S2009E02 blah.avi"));
- }
-
- [Fact]
- public void TestFourDigitSeasonNumber8()
- {
- Assert.Equal(2009, GetSeasonNumberFromEpisodeFile(@"Season 2009/Elementary - 2009x03 - 2009x04 - 2009x15 - Ep Name.mp4"));
- }
-
- [Fact]
- public void TestFourDigitSeasonNumber9()
- {
- Assert.Equal(2009, GetSeasonNumberFromEpisodeFile(@"Season 2009/2009x03 - 2009x04 - 2009x15 - Ep Name.mp4"));
- }
-
- [Fact]
- public void TestFourDigitSeasonNumber10()
- {
- Assert.Equal(2009, GetSeasonNumberFromEpisodeFile(@"Season 2009/2009x03-04-15 - Ep Name.mp4"));
- }
-
- [Fact]
- public void TestFourDigitSeasonNumber11()
- {
- Assert.Equal(2009, GetSeasonNumberFromEpisodeFile(@"Season 2009/Elementary - 2009x03 - x04 - x15 - Ep Name.mp4"));
- }
-
- [Fact]
- public void TestFourDigitSeasonNumber12()
- {
- Assert.Equal(2009, GetSeasonNumberFromEpisodeFile(@"Season 2009/2009x03x04x15 - Ep Name.mp4"));
- }
-
- [Fact]
- public void TestFourDigitSeasonNumber13()
- {
- Assert.Equal(2009, GetSeasonNumberFromEpisodeFile(@"Season 2009/Elementary - 2009x03x04x15 - Ep Name.mp4"));
- }
-
- [Fact]
- public void TestFourDigitSeasonNumber14()
- {
- Assert.Equal(2009, GetSeasonNumberFromEpisodeFile(@"Season 2009/Elementary - S2009E23-E24-E26 - The Woman.mp4"));
- }
-
- [Fact]
- public void TestFourDigitSeasonNumber15()
- {
- Assert.Equal(2009, GetSeasonNumberFromEpisodeFile(@"Season 2009/S2009E23-E24-E26 - The Woman.mp4"));
- }
-
- [Fact]
- public void TestFourDigitSeasonNumber16()
- {
- Assert.Equal(2009, GetSeasonNumberFromEpisodeFile(@"Season 2009/Elementary - 2009x03 - x04 - x15 - Ep Name.mp4"));
- }
-
- [Fact]
- public void TestFourDigitSeasonNumber17()
- {
- Assert.Equal(2009, GetSeasonNumberFromEpisodeFile(@"Season 2009/2009x03x04x15 - Ep Name.mp4"));
- }
-
- [Fact]
- public void TestFourDigitSeasonNumber18()
- {
- Assert.Equal(2009, GetSeasonNumberFromEpisodeFile(@"Season 2009/Elementary - 2009x03x04x15 - Ep Name.mp4"));
- }
-
- [Fact]
- public void TestFourDigitSeasonNumber19()
- {
- Assert.Equal(2009, GetSeasonNumberFromEpisodeFile(@"Season 2009/Elementary - S2009E23-E24-E26 - The Woman.mp4"));
- }
-
- [Fact]
- public void TestFourDigitSeasonNumber20()
- {
- Assert.Equal(2009, GetSeasonNumberFromEpisodeFile(@"Season 2009/S2009E23-E24-E26 - The Woman.mp4"));
- }
-
- [Fact]
- public void TestNoSeriesFolder()
- {
- Assert.Equal(1, GetSeasonNumberFromEpisodeFile(@"Series/1-12 - The Woman.mp4"));
+ Assert.Equal(expected, result.SeasonNumber);
}
}
}
diff --git a/tests/Jellyfin.Naming.Tests/TV/SimpleEpisodeTests.cs b/tests/Jellyfin.Naming.Tests/TV/SimpleEpisodeTests.cs
index c9323c218..de253ce37 100644
--- a/tests/Jellyfin.Naming.Tests/TV/SimpleEpisodeTests.cs
+++ b/tests/Jellyfin.Naming.Tests/TV/SimpleEpisodeTests.cs
@@ -6,81 +6,25 @@ namespace Jellyfin.Naming.Tests.TV
{
public class SimpleEpisodeTests
{
- [Fact]
- public void TestSimpleEpisodePath1()
- {
- Test(@"/server/anything_s01e02.mp4", "anything", 1, 2);
- }
-
- [Fact]
- public void TestSimpleEpisodePath2()
- {
- Test(@"/server/anything_s1e2.mp4", "anything", 1, 2);
- }
-
- [Fact]
- public void TestSimpleEpisodePath3()
- {
- Test(@"/server/anything_s01.e02.mp4", "anything", 1, 2);
- }
-
- [Fact]
- public void TestSimpleEpisodePath4()
- {
- Test(@"/server/anything_s01_e02.mp4", "anything", 1, 2);
- }
-
- [Fact]
- public void TestSimpleEpisodePath5()
- {
- Test(@"/server/anything_102.mp4", "anything", 1, 2);
- }
-
- [Fact]
- public void TestSimpleEpisodePath6()
- {
- Test(@"/server/anything_1x02.mp4", "anything", 1, 2);
- }
-
- // FIXME
- // [Fact]
- public void TestSimpleEpisodePath7()
- {
- Test(@"/server/The Walking Dead 4x01.mp4", "The Walking Dead", 4, 1);
- }
-
- [Fact]
- public void TestSimpleEpisodePath8()
- {
- Test(@"/server/the_simpsons-s02e01_18536.mp4", "the_simpsons", 2, 1);
- }
-
-
- [Fact]
- public void TestSimpleEpisodePath9()
- {
- Test(@"/server/Temp/S01E02 foo.mp4", string.Empty, 1, 2);
- }
-
- [Fact]
- public void TestSimpleEpisodePath10()
- {
- Test(@"Series/4-12 - The Woman.mp4", string.Empty, 4, 12);
- }
-
- [Fact]
- public void TestSimpleEpisodePath11()
- {
- Test(@"Series/4x12 - The Woman.mp4", string.Empty, 4, 12);
- }
-
- [Fact]
- public void TestSimpleEpisodePath12()
- {
- Test(@"Series/LA X, Pt. 1_s06e32.mp4", "LA X, Pt. 1", 6, 32);
- }
-
- private void Test(string path, string seriesName, int? seasonNumber, int? episodeNumber)
+ [Theory]
+ [InlineData("/server/anything_s01e02.mp4", "anything", 1, 2)]
+ [InlineData("/server/anything_s1e2.mp4", "anything", 1, 2)]
+ [InlineData("/server/anything_s01.e02.mp4", "anything", 1, 2)]
+ [InlineData("/server/anything_102.mp4", "anything", 1, 2)]
+ [InlineData("/server/anything_1x02.mp4", "anything", 1, 2)]
+ [InlineData("/server/The Walking Dead 4x01.mp4", "The Walking Dead", 4, 1)]
+ [InlineData("/server/the_simpsons-s02e01_18536.mp4", "the_simpsons", 2, 1)]
+ [InlineData("/server/Temp/S01E02 foo.mp4", "", 1, 2)]
+ [InlineData("Series/4-12 - The Woman.mp4", "", 4, 12)]
+ [InlineData("Series/4x12 - The Woman.mp4", "", 4, 12)]
+ [InlineData("Series/LA X, Pt. 1_s06e32.mp4", "LA X, Pt. 1", 6, 32)]
+ [InlineData("[Baz-Bar]Foo - [1080p][Multiple Subtitle]/[Baz-Bar] Foo - 05 [1080p][Multiple Subtitle].mkv", "Foo", null, 5)]
+ [InlineData(@"/Foo/The.Series.Name.S01E04.WEBRip.x264-Baz[Bar]/the.series.name.s01e04.webrip.x264-Baz[Bar].mkv", "The.Series.Name", 1, 4)]
+ [InlineData(@"Love.Death.and.Robots.S01.1080p.NF.WEB-DL.DDP5.1.x264-NTG/Love.Death.and.Robots.S01E01.Sonnies.Edge.1080p.NF.WEB-DL.DDP5.1.x264-NTG.mkv", "Love.Death.and.Robots", 1, 1)]
+ // TODO: [InlineData("[Baz-Bar]Foo - 01 - 12[1080p][Multiple Subtitle]/[Baz-Bar] Foo - 05 [1080p][Multiple Subtitle].mkv", "Foo", null, 5)]
+ // TODO: [InlineData("E:\\Anime\\Yahari Ore no Seishun Love Comedy wa Machigatteiru\\Yahari Ore no Seishun Love Comedy wa Machigatteiru. Zoku\\Oregairu Zoku 11 - Hayama Hayato Always Renconds to Everyone's Expectations..mkv", "Yahari Ore no Seishun Love Comedy wa Machigatteiru", null, 11)]
+ // TODO: [InlineData(@"/Library/Series/The Grand Tour (2016)/Season 1/S01E01 The Holy Trinity.mkv", "The Grand Tour", 1, 1)]
+ public void Test(string path, string seriesName, int? seasonNumber, int? episodeNumber)
{
var options = new NamingOptions();
diff --git a/tests/Jellyfin.Naming.Tests/Video/BaseVideoTest.cs b/tests/Jellyfin.Naming.Tests/Video/BaseVideoTest.cs
index b993e241c..0c2978aca 100644
--- a/tests/Jellyfin.Naming.Tests/Video/BaseVideoTest.cs
+++ b/tests/Jellyfin.Naming.Tests/Video/BaseVideoTest.cs
@@ -5,11 +5,9 @@ namespace Jellyfin.Naming.Tests.Video
{
public abstract class BaseVideoTest
{
- protected VideoResolver GetParser()
- {
- var options = new NamingOptions();
+ private readonly NamingOptions _namingOptions = new NamingOptions();
- return new VideoResolver(options);
- }
+ protected VideoResolver GetParser()
+ => new VideoResolver(_namingOptions);
}
}
diff --git a/tests/Jellyfin.Naming.Tests/Video/CleanDateTimeTests.cs b/tests/Jellyfin.Naming.Tests/Video/CleanDateTimeTests.cs
index bba73ad91..49cb2387b 100644
--- a/tests/Jellyfin.Naming.Tests/Video/CleanDateTimeTests.cs
+++ b/tests/Jellyfin.Naming.Tests/Video/CleanDateTimeTests.cs
@@ -1,143 +1,59 @@
using System.IO;
+using Emby.Naming.Common;
+using Emby.Naming.Video;
using Xunit;
namespace Jellyfin.Naming.Tests.Video
{
- public class CleanDateTimeTests : BaseVideoTest
+ public sealed class CleanDateTimeTests
{
- // FIXME
- // [Fact]
- public void TestCleanDateTime()
- {
- Test(@"The Wolf of Wall Street (2013).mkv", "The Wolf of Wall Street", 2013);
- Test(@"The Wolf of Wall Street 2 (2013).mkv", "The Wolf of Wall Street 2", 2013);
- Test(@"The Wolf of Wall Street - 2 (2013).mkv", "The Wolf of Wall Street - 2", 2013);
- Test(@"The Wolf of Wall Street 2001 (2013).mkv", "The Wolf of Wall Street 2001", 2013);
-
- Test(@"300 (2006).mkv", "300", 2006);
- Test(@"d:/movies/300 (2006).mkv", "300", 2006);
- Test(@"300 2 (2006).mkv", "300 2", 2006);
- Test(@"300 - 2 (2006).mkv", "300 - 2", 2006);
- Test(@"300 2001 (2006).mkv", "300 2001", 2006);
-
- Test(@"curse.of.chucky.2013.stv.unrated.multi.1080p.bluray.x264-rough", "curse.of.chucky", 2013);
- Test(@"curse.of.chucky.2013.stv.unrated.multi.2160p.bluray.x264-rough", "curse.of.chucky", 2013);
-
- Test(@"/server/Movies/300 (2007)/300 (2006).bluray.disc", "300", 2006);
- }
-
- // FIXME
- // [Fact]
- public void TestCleanDateTime1()
- {
- Test(@"Arrival.2016.2160p.Blu-Ray.HEVC.mkv", "Arrival", 2016);
- }
-
- // FIXME
- // [Fact]
- public void TestCleanDateTimeWithoutFileExtension()
- {
- Test(@"The Wolf of Wall Street (2013)", "The Wolf of Wall Street", 2013);
- Test(@"The Wolf of Wall Street 2 (2013)", "The Wolf of Wall Street 2", 2013);
- Test(@"The Wolf of Wall Street - 2 (2013)", "The Wolf of Wall Street - 2", 2013);
- Test(@"The Wolf of Wall Street 2001 (2013)", "The Wolf of Wall Street 2001", 2013);
-
- Test(@"300 (2006)", "300", 2006);
- Test(@"d:/movies/300 (2006)", "300", 2006);
- Test(@"300 2 (2006)", "300 2", 2006);
- Test(@"300 - 2 (2006)", "300 - 2", 2006);
- Test(@"300 2001 (2006)", "300 2001", 2006);
-
- Test(@"/server/Movies/300 (2007)/300 (2006)", "300", 2006);
- Test(@"/server/Movies/300 (2007)/300 (2006).mkv", "300", 2006);
- }
-
- [Fact]
- public void TestCleanDateTimeWithoutDate()
- {
- Test(@"American.Psycho.mkv", "American.Psycho.mkv", null);
- Test(@"American Psycho.mkv", "American Psycho.mkv", null);
- }
-
- [Fact]
- public void TestCleanDateTimeWithBracketedName()
- {
- Test(@"[rec].mkv", "[rec].mkv", null);
- }
-
- // FIXME
- // [Fact]
- public void TestCleanDateTimeWithoutExtension()
- {
- Test(@"St. Vincent (2014)", "St. Vincent", 2014);
- }
-
- // FIXME
- // [Fact]
- public void TestCleanDateTimeWithoutDate1()
- {
- Test("Super movie(2009).mp4", "Super movie", 2009);
- }
-
- // FIXME
- // [Fact]
- public void TestCleanDateTimeWithoutParenthesis()
- {
- Test("Drug War 2013.mp4", "Drug War", 2013);
- }
-
- // FIXME
- // [Fact]
- public void TestCleanDateTimeWithMultipleYears()
- {
- Test("My Movie (1997) - GreatestReleaseGroup 2019.mp4", "My Movie", 1997);
- }
-
- // FIXME
- // [Fact]
- public void TestCleanDateTimeWithYearAndResolution()
- {
- Test("First Man 2018 1080p.mkv", "First Man", 2018);
- }
-
- // FIXME
- // [Fact]
- public void TestCleanDateTimeWithYearAndResolution1()
- {
- Test("First Man (2018) 1080p.mkv", "First Man", 2018);
- }
-
- // FIXME
- // [Fact]
- public void TestCleanDateTimeWithSceneRelease()
- {
- Test("Maximum Ride - 2016 - WEBDL-1080p - x264 AC3.mkv", "Maximum Ride", 2016);
- }
-
- // FIXME
- // [Fact]
- public void TestYearInBrackets()
- {
- Test("Robin Hood [Multi-Subs] [2018].mkv", "Robin Hood", 2018);
- }
-
- private void Test(string input, string expectedName, int? expectedYear)
+ private readonly NamingOptions _namingOptions = new NamingOptions();
+
+ [Theory]
+ [InlineData(@"The Wolf of Wall Street (2013).mkv", "The Wolf of Wall Street", 2013)]
+ [InlineData(@"The Wolf of Wall Street 2 (2013).mkv", "The Wolf of Wall Street 2", 2013)]
+ [InlineData(@"The Wolf of Wall Street - 2 (2013).mkv", "The Wolf of Wall Street - 2", 2013)]
+ [InlineData(@"The Wolf of Wall Street 2001 (2013).mkv", "The Wolf of Wall Street 2001", 2013)]
+ [InlineData(@"300 (2006).mkv", "300", 2006)]
+ [InlineData(@"d:/movies/300 (2006).mkv", "300", 2006)]
+ [InlineData(@"300 2 (2006).mkv", "300 2", 2006)]
+ [InlineData(@"300 - 2 (2006).mkv", "300 - 2", 2006)]
+ [InlineData(@"300 2001 (2006).mkv", "300 2001", 2006)]
+ [InlineData(@"curse.of.chucky.2013.stv.unrated.multi.1080p.bluray.x264-rough", "curse.of.chucky", 2013)]
+ [InlineData(@"curse.of.chucky.2013.stv.unrated.multi.2160p.bluray.x264-rough", "curse.of.chucky", 2013)]
+ [InlineData(@"/server/Movies/300 (2007)/300 (2006).bluray.disc", "300", 2006)]
+ [InlineData(@"Arrival.2016.2160p.Blu-Ray.HEVC.mkv", "Arrival", 2016)]
+ [InlineData(@"The Wolf of Wall Street (2013)", "The Wolf of Wall Street", 2013)]
+ [InlineData(@"The Wolf of Wall Street 2 (2013)", "The Wolf of Wall Street 2", 2013)]
+ [InlineData(@"The Wolf of Wall Street - 2 (2013)", "The Wolf of Wall Street - 2", 2013)]
+ [InlineData(@"The Wolf of Wall Street 2001 (2013)", "The Wolf of Wall Street 2001", 2013)]
+ [InlineData(@"300 (2006)", "300", 2006)]
+ [InlineData(@"d:/movies/300 (2006)", "300", 2006)]
+ [InlineData(@"300 2 (2006)", "300 2", 2006)]
+ [InlineData(@"300 - 2 (2006)", "300 - 2", 2006)]
+ [InlineData(@"300 2001 (2006)", "300 2001", 2006)]
+ [InlineData(@"/server/Movies/300 (2007)/300 (2006)", "300", 2006)]
+ [InlineData(@"/server/Movies/300 (2007)/300 (2006).mkv", "300", 2006)]
+ [InlineData(@"American.Psycho.mkv", "American.Psycho.mkv", null)]
+ [InlineData(@"American Psycho.mkv", "American Psycho.mkv", null)]
+ [InlineData(@"[rec].mkv", "[rec].mkv", null)]
+ [InlineData(@"St. Vincent (2014)", "St. Vincent", 2014)]
+ [InlineData("Super movie(2009).mp4", "Super movie", 2009)]
+ [InlineData("Drug War 2013.mp4", "Drug War", 2013)]
+ [InlineData("My Movie (1997) - GreatestReleaseGroup 2019.mp4", "My Movie", 1997)]
+ [InlineData("First Man 2018 1080p.mkv", "First Man", 2018)]
+ [InlineData("First Man (2018) 1080p.mkv", "First Man", 2018)]
+ [InlineData("Maximum Ride - 2016 - WEBDL-1080p - x264 AC3.mkv", "Maximum Ride", 2016)]
+ // FIXME: [InlineData("Robin Hood [Multi-Subs] [2018].mkv", "Robin Hood", 2018)]
+ [InlineData(@"3.Days.to.Kill.2014.720p.BluRay.x264.YIFY.mkv", "3.Days.to.Kill", 2014)] // In this test case, running CleanDateTime first produces no date, so it will attempt to run CleanString first and then CleanDateTime again
+ public void CleanDateTimeTest(string input, string expectedName, int? expectedYear)
{
input = Path.GetFileName(input);
- var result = GetParser().CleanDateTime(input);
+ var result = new VideoResolver(_namingOptions).CleanDateTime(input);
Assert.Equal(expectedName, result.Name, true);
Assert.Equal(expectedYear, result.Year);
}
-
- // FIXME
- // [Fact]
- public void TestCleanDateAndStringsSequence()
- {
- // In this test case, running CleanDateTime first produces no date, so it will attempt to run CleanString first and then CleanDateTime again
-
- Test(@"3.Days.to.Kill.2014.720p.BluRay.x264.YIFY.mkv", "3.Days.to.Kill", 2014);
- }
}
}
diff --git a/tests/Jellyfin.Naming.Tests/Video/CleanStringTests.cs b/tests/Jellyfin.Naming.Tests/Video/CleanStringTests.cs
index cd90ac236..fde06c5a1 100644
--- a/tests/Jellyfin.Naming.Tests/Video/CleanStringTests.cs
+++ b/tests/Jellyfin.Naming.Tests/Video/CleanStringTests.cs
@@ -1,133 +1,45 @@
using System;
-using System.Globalization;
+using Emby.Naming.Common;
+using Emby.Naming.Video;
using Xunit;
namespace Jellyfin.Naming.Tests.Video
{
- public class CleanStringTests : BaseVideoTest
+ public sealed class CleanStringTests
{
- // FIXME
- // [Fact]
- public void TestCleanString()
- {
- Test("Super movie 480p.mp4", "Super movie");
- Test("Super movie 480p 2001.mp4", "Super movie");
- Test("Super movie [480p].mp4", "Super movie");
- Test("480 Super movie [tmdbid=12345].mp4", "480 Super movie");
- }
-
- // FIXME
- // [Fact]
- public void TestCleanString1()
- {
- Test("Super movie(2009).mp4", "Super movie(2009).mp4");
- }
-
- // FIXME
- // [Fact]
- public void TestCleanString2()
- {
- Test("Run lola run (lola rennt) (2009).mp4", "Run lola run (lola rennt) (2009).mp4");
- }
-
- // FIXME
- // [Fact]
- public void TestStringWithoutDate()
- {
- Test(@"American.Psycho.mkv", "American.Psycho.mkv");
- Test(@"American Psycho.mkv", "American Psycho.mkv");
- }
-
- // FIXME
- // [Fact]
- public void TestNameWithBrackets()
- {
- Test(@"[rec].mkv", "[rec].mkv");
- }
-
- // FIXME
- // [Fact]
- public void Test4k()
- {
- Test("Crouching.Tiger.Hidden.Dragon.4k.mkv", "Crouching.Tiger.Hidden.Dragon");
- }
-
- // FIXME
- // [Fact]
- public void TestUltraHd()
- {
- Test("Crouching.Tiger.Hidden.Dragon.UltraHD.mkv", "Crouching.Tiger.Hidden.Dragon");
- }
-
- // FIXME
- // [Fact]
- public void TestUHd()
- {
- Test("Crouching.Tiger.Hidden.Dragon.UHD.mkv", "Crouching.Tiger.Hidden.Dragon");
- }
-
- // FIXME
- // [Fact]
- public void TestHDR()
- {
- Test("Crouching.Tiger.Hidden.Dragon.HDR.mkv", "Crouching.Tiger.Hidden.Dragon");
- }
-
- // FIXME
- // [Fact]
- public void TestHDC()
- {
- Test("Crouching.Tiger.Hidden.Dragon.HDC.mkv", "Crouching.Tiger.Hidden.Dragon");
- }
-
- // FIXME
- // [Fact]
- public void TestHDC1()
- {
- Test("Crouching.Tiger.Hidden.Dragon-HDC.mkv", "Crouching.Tiger.Hidden.Dragon");
- }
-
- // FIXME
- // [Fact]
- public void TestBDrip()
- {
- Test("Crouching.Tiger.Hidden.Dragon.BDrip.mkv", "Crouching.Tiger.Hidden.Dragon");
- }
-
- // FIXME
- // [Fact]
- public void TestBDripHDC()
- {
- Test("Crouching.Tiger.Hidden.Dragon.BDrip-HDC.mkv", "Crouching.Tiger.Hidden.Dragon");
- }
-
- // FIXME
- // [Fact]
- public void TestMulti()
- {
- Test("Crouching.Tiger.Hidden.Dragon.4K.UltraHD.HDR.BDrip-HDC.mkv", "Crouching.Tiger.Hidden.Dragon");
- }
-
- // FIXME
- // [Fact]
- public void TestLeadingBraces()
- {
- // Not actually supported, just reported by a user
- Test("[0004] - After The Sunset.el.mkv", "After The Sunset");
- }
-
- // FIXME
- // [Fact]
- public void TestTrailingBraces()
- {
- Test("After The Sunset - [0004].mkv", "After The Sunset");
- }
-
- private void Test(string input, string expectedName)
- {
- var result = GetParser().CleanString(input).ToString();
-
- Assert.Equal(expectedName, result, true);
+ private readonly NamingOptions _namingOptions = new NamingOptions();
+
+ [Theory]
+ [InlineData("Super movie 480p.mp4", "Super movie")]
+ [InlineData("Super movie 480p 2001.mp4", "Super movie")]
+ [InlineData("Super movie [480p].mp4", "Super movie")]
+ [InlineData("480 Super movie [tmdbid=12345].mp4", "480 Super movie")]
+ [InlineData("Super movie(2009).mp4", "Super movie(2009).mp4")]
+ [InlineData("Run lola run (lola rennt) (2009).mp4", "Run lola run (lola rennt) (2009).mp4")]
+ [InlineData(@"American.Psycho.mkv", "American.Psycho.mkv")]
+ [InlineData(@"American Psycho.mkv", "American Psycho.mkv")]
+ [InlineData(@"[rec].mkv", "[rec].mkv")]
+ [InlineData("Crouching.Tiger.Hidden.Dragon.4k.mkv", "Crouching.Tiger.Hidden.Dragon")]
+ [InlineData("Crouching.Tiger.Hidden.Dragon.UltraHD.mkv", "Crouching.Tiger.Hidden.Dragon")]
+ [InlineData("Crouching.Tiger.Hidden.Dragon.UHD.mkv", "Crouching.Tiger.Hidden.Dragon")]
+ [InlineData("Crouching.Tiger.Hidden.Dragon.HDR.mkv", "Crouching.Tiger.Hidden.Dragon")]
+ [InlineData("Crouching.Tiger.Hidden.Dragon.HDC.mkv", "Crouching.Tiger.Hidden.Dragon")]
+ [InlineData("Crouching.Tiger.Hidden.Dragon-HDC.mkv", "Crouching.Tiger.Hidden.Dragon")]
+ [InlineData("Crouching.Tiger.Hidden.Dragon.BDrip.mkv", "Crouching.Tiger.Hidden.Dragon")]
+ [InlineData("Crouching.Tiger.Hidden.Dragon.BDrip-HDC.mkv", "Crouching.Tiger.Hidden.Dragon")]
+ [InlineData("Crouching.Tiger.Hidden.Dragon.4K.UltraHD.HDR.BDrip-HDC.mkv", "Crouching.Tiger.Hidden.Dragon")]
+ // FIXME: [InlineData("After The Sunset - [0004].mkv", "After The Sunset")]
+ public void CleanStringTest(string input, string expectedName)
+ {
+ if (new VideoResolver(_namingOptions).TryCleanString(input, out ReadOnlySpan<char> newName))
+ {
+ // TODO: compare spans when XUnit supports it
+ Assert.Equal(expectedName, newName.ToString());
+ }
+ else
+ {
+ Assert.Equal(expectedName, input);
+ }
}
}
}
diff --git a/tests/Jellyfin.Naming.Tests/Video/ExtraTests.cs b/tests/Jellyfin.Naming.Tests/Video/ExtraTests.cs
index 1646237a0..a64d17349 100644
--- a/tests/Jellyfin.Naming.Tests/Video/ExtraTests.cs
+++ b/tests/Jellyfin.Naming.Tests/Video/ExtraTests.cs
@@ -7,6 +7,8 @@ namespace Jellyfin.Naming.Tests.Video
{
public class ExtraTests : BaseVideoTest
{
+ private readonly NamingOptions _videoOptions = new NamingOptions();
+
// Requirements
// movie-deleted = ExtraType deletedscene
@@ -15,42 +17,64 @@ namespace Jellyfin.Naming.Tests.Video
[Fact]
public void TestKodiExtras()
{
- var videoOptions = new NamingOptions();
-
- Test("trailer.mp4", ExtraType.Trailer, videoOptions);
- Test("300-trailer.mp4", ExtraType.Trailer, videoOptions);
+ Test("trailer.mp4", ExtraType.Trailer, _videoOptions);
+ Test("300-trailer.mp4", ExtraType.Trailer, _videoOptions);
- Test("theme.mp3", ExtraType.ThemeSong, videoOptions);
+ Test("theme.mp3", ExtraType.ThemeSong, _videoOptions);
}
[Fact]
public void TestExpandedExtras()
{
- var videoOptions = new NamingOptions();
+ Test("trailer.mp4", ExtraType.Trailer, _videoOptions);
+ Test("trailer.mp3", null, _videoOptions);
+ Test("300-trailer.mp4", ExtraType.Trailer, _videoOptions);
- Test("trailer.mp4", ExtraType.Trailer, videoOptions);
- Test("trailer.mp3", null, videoOptions);
- Test("300-trailer.mp4", ExtraType.Trailer, videoOptions);
+ Test("theme.mp3", ExtraType.ThemeSong, _videoOptions);
+ Test("theme.mkv", null, _videoOptions);
- Test("theme.mp3", ExtraType.ThemeSong, videoOptions);
- Test("theme.mkv", null, videoOptions);
+ Test("300-scene.mp4", ExtraType.Scene, _videoOptions);
+ Test("300-scene2.mp4", ExtraType.Scene, _videoOptions);
+ Test("300-clip.mp4", ExtraType.Clip, _videoOptions);
+
+ Test("300-deleted.mp4", ExtraType.DeletedScene, _videoOptions);
+ Test("300-deletedscene.mp4", ExtraType.DeletedScene, _videoOptions);
+ Test("300-interview.mp4", ExtraType.Interview, _videoOptions);
+ Test("300-behindthescenes.mp4", ExtraType.BehindTheScenes, _videoOptions);
+ }
- Test("300-scene.mp4", ExtraType.Scene, videoOptions);
- Test("300-scene2.mp4", ExtraType.Scene, videoOptions);
- Test("300-clip.mp4", ExtraType.Clip, videoOptions);
+ [Theory]
+ [InlineData(ExtraType.BehindTheScenes, "behind the scenes" )]
+ [InlineData(ExtraType.DeletedScene, "deleted scenes" )]
+ [InlineData(ExtraType.Interview, "interviews" )]
+ [InlineData(ExtraType.Scene, "scenes" )]
+ [InlineData(ExtraType.Sample, "samples" )]
+ [InlineData(ExtraType.Clip, "shorts" )]
+ [InlineData(ExtraType.Clip, "featurettes" )]
+ [InlineData(ExtraType.Unknown, "extras" )]
+ public void TestDirectories(ExtraType type, string dirName)
+ {
+ Test(dirName + "/300.mp4", type, _videoOptions);
+ Test("300/" + dirName + "/something.mkv", type, _videoOptions);
+ Test("/data/something/Movies/300/" + dirName + "/whoknows.mp4", type, _videoOptions);
+ }
- Test("300-deleted.mp4", ExtraType.DeletedScene, videoOptions);
- Test("300-deletedscene.mp4", ExtraType.DeletedScene, videoOptions);
- Test("300-interview.mp4", ExtraType.Interview, videoOptions);
- Test("300-behindthescenes.mp4", ExtraType.BehindTheScenes, videoOptions);
+ [Theory]
+ [InlineData("gibberish")]
+ [InlineData("not a scene")]
+ [InlineData("The Big Short")]
+ public void TestNonExtraDirectories(string dirName)
+ {
+ Test(dirName + "/300.mp4", null, _videoOptions);
+ Test("300/" + dirName + "/something.mkv", null, _videoOptions);
+ Test("/data/something/Movies/300/" + dirName + "/whoknows.mp4", null, _videoOptions);
+ Test("/data/something/Movies/" + dirName + "/" + dirName + ".mp4", null, _videoOptions);
}
[Fact]
public void TestSample()
{
- var videoOptions = new NamingOptions();
-
- Test("300-sample.mp4", ExtraType.Sample, videoOptions);
+ Test("300-sample.mp4", ExtraType.Sample, _videoOptions);
}
private void Test(string input, ExtraType? expectedType, NamingOptions videoOptions)
diff --git a/tests/Jellyfin.Naming.Tests/Video/MultiVersionTests.cs b/tests/Jellyfin.Naming.Tests/Video/MultiVersionTests.cs
index b8674ec49..b8fbb2cb2 100644
--- a/tests/Jellyfin.Naming.Tests/Video/MultiVersionTests.cs
+++ b/tests/Jellyfin.Naming.Tests/Video/MultiVersionTests.cs
@@ -62,7 +62,6 @@ namespace Jellyfin.Naming.Tests.Video
[Fact]
public void TestMultiEdition3()
{
- // This is currently not supported and will fail, but we should try to figure it out
var files = new[]
{
@"/movies/The Phantom of the Opera (1925)/The Phantom of the Opera (1925) - 1925 version.mkv",
diff --git a/tests/Jellyfin.Naming.Tests/Video/StackTests.cs b/tests/Jellyfin.Naming.Tests/Video/StackTests.cs
index 5faef0e3d..3e0cbaf0c 100644
--- a/tests/Jellyfin.Naming.Tests/Video/StackTests.cs
+++ b/tests/Jellyfin.Naming.Tests/Video/StackTests.cs
@@ -1,4 +1,5 @@
-using Emby.Naming.Common;
+using System.Linq;
+using Emby.Naming.Common;
using Emby.Naming.Video;
using MediaBrowser.Model.IO;
using Xunit;
@@ -21,10 +22,10 @@ namespace Jellyfin.Naming.Tests.Video
var resolver = GetResolver();
- var result = resolver.ResolveFiles(files);
+ var result = resolver.ResolveFiles(files).ToList();
- Assert.Single(result.Stacks);
- TestStackInfo(result.Stacks[0], "Bad Boys (2006)", 4);
+ Assert.Single(result);
+ TestStackInfo(result[0], "Bad Boys (2006)", 4);
}
[Fact]
@@ -38,9 +39,9 @@ namespace Jellyfin.Naming.Tests.Video
var resolver = GetResolver();
- var result = resolver.ResolveFiles(files);
+ var result = resolver.ResolveFiles(files).ToList();
- Assert.Empty(result.Stacks);
+ Assert.Empty(result);
}
[Fact]
@@ -54,9 +55,9 @@ namespace Jellyfin.Naming.Tests.Video
var resolver = GetResolver();
- var result = resolver.ResolveFiles(files);
+ var result = resolver.ResolveFiles(files).ToList();
- Assert.Empty(result.Stacks);
+ Assert.Empty(result);
}
[Fact]
@@ -70,9 +71,9 @@ namespace Jellyfin.Naming.Tests.Video
var resolver = GetResolver();
- var result = resolver.ResolveFiles(files);
+ var result = resolver.ResolveFiles(files).ToList();
- Assert.Empty(result.Stacks);
+ Assert.Empty(result);
}
[Fact]
@@ -86,9 +87,9 @@ namespace Jellyfin.Naming.Tests.Video
var resolver = GetResolver();
- var result = resolver.ResolveFiles(files);
+ var result = resolver.ResolveFiles(files).ToList();
- Assert.Empty(result.Stacks);
+ Assert.Empty(result);
}
[Fact]
@@ -102,9 +103,8 @@ namespace Jellyfin.Naming.Tests.Video
var resolver = GetResolver();
- var result = resolver.ResolveFiles(files);
-
- Assert.Empty(result.Stacks);
+ var result = resolver.ResolveFiles(files).ToList();
+ Assert.Empty(result);
}
[Fact]
@@ -119,9 +119,9 @@ namespace Jellyfin.Naming.Tests.Video
var resolver = GetResolver();
- var result = resolver.ResolveFiles(files);
+ var result = resolver.ResolveFiles(files).ToList();
- Assert.Empty(result.Stacks);
+ Assert.Empty(result);
}
[Fact]
@@ -135,10 +135,10 @@ namespace Jellyfin.Naming.Tests.Video
var resolver = GetResolver();
- var result = resolver.ResolveFiles(files);
+ var result = resolver.ResolveFiles(files).ToList();
- Assert.Single(result.Stacks);
- TestStackInfo(result.Stacks[0], "300 2006", 2);
+ Assert.Single(result);
+ TestStackInfo(result[0], "300 2006", 2);
}
[Fact]
@@ -155,10 +155,10 @@ namespace Jellyfin.Naming.Tests.Video
var resolver = GetResolver();
- var result = resolver.ResolveFiles(files);
+ var result = resolver.ResolveFiles(files).ToList();
- Assert.Single(result.Stacks);
- TestStackInfo(result.Stacks[0], "Bad Boys (2006).stv.unrated.multi.1080p.bluray.x264-rough", 4);
+ Assert.Single(result);
+ TestStackInfo(result[0], "Bad Boys (2006).stv.unrated.multi.1080p.bluray.x264-rough", 4);
}
[Fact]
@@ -175,9 +175,9 @@ namespace Jellyfin.Naming.Tests.Video
var resolver = GetResolver();
- var result = resolver.ResolveFiles(files);
+ var result = resolver.ResolveFiles(files).ToList();
- Assert.Empty(result.Stacks);
+ Assert.Empty(result);
}
[Fact]
@@ -194,10 +194,10 @@ namespace Jellyfin.Naming.Tests.Video
var resolver = GetResolver();
- var result = resolver.ResolveFiles(files);
+ var result = resolver.ResolveFiles(files).ToList();
- Assert.Single(result.Stacks);
- TestStackInfo(result.Stacks[0], "300 (2006)", 4);
+ Assert.Single(result);
+ TestStackInfo(result[0], "300 (2006)", 4);
}
[Fact]
@@ -214,10 +214,10 @@ namespace Jellyfin.Naming.Tests.Video
var resolver = GetResolver();
- var result = resolver.ResolveFiles(files);
+ var result = resolver.ResolveFiles(files).ToList();
- Assert.Single(result.Stacks);
- TestStackInfo(result.Stacks[0], "Bad Boys (2006)", 3);
+ Assert.Single(result);
+ TestStackInfo(result[0], "Bad Boys (2006)", 3);
}
[Fact]
@@ -238,11 +238,11 @@ namespace Jellyfin.Naming.Tests.Video
var resolver = GetResolver();
- var result = resolver.ResolveFiles(files);
+ var result = resolver.ResolveFiles(files).ToList();
- Assert.Equal(2, result.Stacks.Count);
- TestStackInfo(result.Stacks[1], "Bad Boys (2006)", 4);
- TestStackInfo(result.Stacks[0], "300 (2006)", 3);
+ Assert.Equal(2, result.Count);
+ TestStackInfo(result[1], "Bad Boys (2006)", 4);
+ TestStackInfo(result[0], "300 (2006)", 3);
}
[Fact]
@@ -256,10 +256,10 @@ namespace Jellyfin.Naming.Tests.Video
var resolver = GetResolver();
- var result = resolver.ResolveDirectories(files);
+ var result = resolver.ResolveDirectories(files).ToList();
- Assert.Single(result.Stacks);
- TestStackInfo(result.Stacks[0], "blah blah", 2);
+ Assert.Single(result);
+ TestStackInfo(result[0], "blah blah", 2);
}
[Fact]
@@ -275,11 +275,11 @@ namespace Jellyfin.Naming.Tests.Video
var resolver = GetResolver();
- var result = resolver.ResolveFiles(files);
+ var result = resolver.ResolveFiles(files).ToList();
- Assert.Single(result.Stacks);
+ Assert.Single(result);
- TestStackInfo(result.Stacks[0], "300", 3);
+ TestStackInfo(result[0], "300", 3);
}
[Fact]
@@ -297,12 +297,12 @@ namespace Jellyfin.Naming.Tests.Video
var resolver = GetResolver();
- var result = resolver.ResolveFiles(files);
+ var result = resolver.ResolveFiles(files).ToList();
- Assert.Equal(2, result.Stacks.Count);
+ Assert.Equal(2, result.Count);
- TestStackInfo(result.Stacks[0], "300", 2);
- TestStackInfo(result.Stacks[1], "Avengers", 3);
+ TestStackInfo(result[0], "300", 2);
+ TestStackInfo(result[1], "Avengers", 3);
}
[Fact]
@@ -328,13 +328,13 @@ namespace Jellyfin.Naming.Tests.Video
var resolver = GetResolver();
- var result = resolver.ResolveFiles(files);
+ var result = resolver.ResolveFiles(files).ToList();
- Assert.Equal(3, result.Stacks.Count);
+ Assert.Equal(3, result.Count);
- TestStackInfo(result.Stacks[0], "300 (2006)", 4);
- TestStackInfo(result.Stacks[1], "300", 3);
- TestStackInfo(result.Stacks[2], "Bad Boys (2006)", 4);
+ TestStackInfo(result[0], "300 (2006)", 4);
+ TestStackInfo(result[1], "300", 3);
+ TestStackInfo(result[2], "Bad Boys (2006)", 4);
}
[Fact]
@@ -354,11 +354,11 @@ namespace Jellyfin.Naming.Tests.Video
var resolver = GetResolver();
- var result = resolver.ResolveFiles(files);
+ var result = resolver.ResolveFiles(files).ToList();
- Assert.Single(result.Stacks);
+ Assert.Single(result);
- TestStackInfo(result.Stacks[0], "300 (2006)", 4);
+ TestStackInfo(result[0], "300 (2006)", 4);
}
[Fact]
@@ -375,37 +375,11 @@ namespace Jellyfin.Naming.Tests.Video
var resolver = GetResolver();
- var result = resolver.Resolve(files);
-
- Assert.Equal(2, result.Stacks.Count);
- TestStackInfo(result.Stacks[0], "300 (2006)", 3);
- TestStackInfo(result.Stacks[1], "Bad Boys (2006)", 2);
- }
+ var result = resolver.Resolve(files).ToList();
- [Fact]
- public void TestDirectories2()
- {
- //TestDirectory(@"blah blah", false, @"blah blah");
- //TestDirectory(@"d:/music/weezer/03 Pinkerton", false, "03 Pinkerton");
- //TestDirectory(@"d:/music/michael jackson/Bad (2012 Remaster)", false, "Bad (2012 Remaster)");
-
- //TestDirectory(@"blah blah - cd1", true, "blah blah");
- //TestDirectory(@"blah blah - disc1", true, "blah blah");
- //TestDirectory(@"blah blah - disk1", true, "blah blah");
- //TestDirectory(@"blah blah - pt1", true, "blah blah");
- //TestDirectory(@"blah blah - part1", true, "blah blah");
- //TestDirectory(@"blah blah - dvd1", true, "blah blah");
-
- //// Add a space
- //TestDirectory(@"blah blah - cd 1", true, "blah blah");
- //TestDirectory(@"blah blah - disc 1", true, "blah blah");
- //TestDirectory(@"blah blah - disk 1", true, "blah blah");
- //TestDirectory(@"blah blah - pt 1", true, "blah blah");
- //TestDirectory(@"blah blah - part 1", true, "blah blah");
- //TestDirectory(@"blah blah - dvd 1", true, "blah blah");
-
- //// Not case sensitive
- //TestDirectory(@"blah blah - Disc1", true, "blah blah");
+ Assert.Equal(2, result.Count);
+ TestStackInfo(result[0], "300 (2006)", 3);
+ TestStackInfo(result[1], "Bad Boys (2006)", 2);
}
[Fact]
@@ -423,9 +397,9 @@ namespace Jellyfin.Naming.Tests.Video
var resolver = GetResolver();
- var result = resolver.ResolveFiles(files);
+ var result = resolver.ResolveFiles(files).ToList();
- Assert.Empty(result.Stacks);
+ Assert.Empty(result);
}
[Fact]
@@ -440,10 +414,10 @@ namespace Jellyfin.Naming.Tests.Video
var resolver = GetResolver();
- var result = resolver.ResolveFiles(files);
+ var result = resolver.ResolveFiles(files).ToList();
- Assert.Single(result.Stacks);
- Assert.Equal(2, result.Stacks[0].Files.Count);
+ Assert.Single(result);
+ Assert.Equal(2, result[0].Files.Count);
}
[Fact]
@@ -458,10 +432,10 @@ namespace Jellyfin.Naming.Tests.Video
var resolver = GetResolver();
- var result = resolver.ResolveDirectories(files);
+ var result = resolver.ResolveDirectories(files).ToList();
- Assert.Single(result.Stacks);
- Assert.Equal(2, result.Stacks[0].Files.Count);
+ Assert.Single(result);
+ Assert.Equal(2, result[0].Files.Count);
}
private void TestStackInfo(FileStack stack, string name, int fileCount)
diff --git a/tests/Jellyfin.Naming.Tests/Video/StubTests.cs b/tests/Jellyfin.Naming.Tests/Video/StubTests.cs
index 96fa8c5a5..8d5ced9a4 100644
--- a/tests/Jellyfin.Naming.Tests/Video/StubTests.cs
+++ b/tests/Jellyfin.Naming.Tests/Video/StubTests.cs
@@ -1,6 +1,4 @@
-using System;
-using System.Globalization;
-using Emby.Naming.Common;
+using Emby.Naming.Common;
using Emby.Naming.Video;
using Xunit;
@@ -38,17 +36,17 @@ namespace Jellyfin.Naming.Tests.Video
{
var options = new NamingOptions();
- var resultStubType = StubResolver.ResolveFile(path, options);
+ var isStubResult = StubResolver.TryResolveFile(path, options, out var stubTypeResult);
- Assert.Equal(isStub, resultStubType.IsStub);
+ Assert.Equal(isStub, isStubResult);
- if (stubType == null)
+ if (isStub)
{
- Assert.Null(resultStubType.StubType);
+ Assert.Equal(stubType, stubTypeResult);
}
else
{
- Assert.Equal(stubType, resultStubType.StubType, true);
+ Assert.Null(stubTypeResult);
}
}
}
diff --git a/tests/Jellyfin.Server.Implementations.Tests/HttpServer/ResponseFilterTests.cs b/tests/Jellyfin.Server.Implementations.Tests/HttpServer/ResponseFilterTests.cs
new file mode 100644
index 000000000..39bd94b59
--- /dev/null
+++ b/tests/Jellyfin.Server.Implementations.Tests/HttpServer/ResponseFilterTests.cs
@@ -0,0 +1,18 @@
+using Emby.Server.Implementations.HttpServer;
+using Xunit;
+
+namespace Jellyfin.Server.Implementations.Tests.HttpServer
+{
+ public class ResponseFilterTests
+ {
+ [Theory]
+ [InlineData(null, null)]
+ [InlineData("", "")]
+ [InlineData("This is a clean string.", "This is a clean string.")]
+ [InlineData("This isn't \n\ra clean string.", "This isn't a clean string.")]
+ public void RemoveControlCharacters_ValidArgs_Correct(string? input, string? result)
+ {
+ Assert.Equal(result, ResponseFilter.RemoveControlCharacters(input));
+ }
+ }
+}
diff --git a/tests/Jellyfin.Server.Implementations.Tests/IO/ManagedFileSystemTests.cs b/tests/Jellyfin.Server.Implementations.Tests/IO/ManagedFileSystemTests.cs
new file mode 100644
index 000000000..671c59b2e
--- /dev/null
+++ b/tests/Jellyfin.Server.Implementations.Tests/IO/ManagedFileSystemTests.cs
@@ -0,0 +1,42 @@
+using AutoFixture;
+using AutoFixture.AutoMoq;
+using Emby.Server.Implementations.IO;
+using MediaBrowser.Model.System;
+using Xunit;
+
+namespace Jellyfin.Server.Implementations.Tests.IO
+{
+ public class ManagedFileSystemTests
+ {
+ private readonly IFixture _fixture;
+ private readonly ManagedFileSystem _sut;
+
+ public ManagedFileSystemTests()
+ {
+ _fixture = new Fixture().Customize(new AutoMoqCustomization { ConfigureMembers = true });
+ _sut = _fixture.Create<ManagedFileSystem>();
+ }
+
+ [Theory]
+ [InlineData("/Volumes/Library/Sample/Music/Playlists/", "../Beethoven/Misc/Moonlight Sonata.mp3", "/Volumes/Library/Sample/Music/Beethoven/Misc/Moonlight Sonata.mp3")]
+ [InlineData("/Volumes/Library/Sample/Music/Playlists/", "../../Beethoven/Misc/Moonlight Sonata.mp3", "/Volumes/Library/Sample/Beethoven/Misc/Moonlight Sonata.mp3")]
+ [InlineData("/Volumes/Library/Sample/Music/Playlists/", "Beethoven/Misc/Moonlight Sonata.mp3", "/Volumes/Library/Sample/Music/Playlists/Beethoven/Misc/Moonlight Sonata.mp3")]
+ public void MakeAbsolutePathCorrectlyHandlesRelativeFilePaths(
+ string folderPath,
+ string filePath,
+ string expectedAbsolutePath)
+ {
+ var generatedPath = _sut.MakeAbsolutePath(folderPath, filePath);
+
+ if (MediaBrowser.Common.System.OperatingSystem.Id == OperatingSystemId.Windows)
+ {
+ var expectedWindowsPath = expectedAbsolutePath.Replace('/', '\\');
+ Assert.Equal(expectedWindowsPath, generatedPath.Split(':')[1]);
+ }
+ else
+ {
+ Assert.Equal(expectedAbsolutePath, generatedPath);
+ }
+ }
+ }
+}
diff --git a/tests/Jellyfin.Server.Implementations.Tests/Jellyfin.Server.Implementations.Tests.csproj b/tests/Jellyfin.Server.Implementations.Tests/Jellyfin.Server.Implementations.Tests.csproj
new file mode 100644
index 000000000..ba7ecb3d1
--- /dev/null
+++ b/tests/Jellyfin.Server.Implementations.Tests/Jellyfin.Server.Implementations.Tests.csproj
@@ -0,0 +1,29 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+ <!-- ProjectGuid is only included as a requirement for SonarQube analysis -->
+ <PropertyGroup>
+ <ProjectGuid>{2E3A1B4B-4225-4AAA-8B29-0181A84E7AEE}</ProjectGuid>
+ </PropertyGroup>
+
+ <PropertyGroup>
+ <TargetFramework>netcoreapp3.1</TargetFramework>
+ <IsPackable>false</IsPackable>
+ <TreatWarningsAsErrors>true</TreatWarningsAsErrors>
+ <Nullable>enable</Nullable>
+ <RootNamespace>Jellyfin.Server.Implementations.Tests</RootNamespace>
+ </PropertyGroup>
+
+ <ItemGroup>
+ <PackageReference Include="AutoFixture" Version="4.11.0" />
+ <PackageReference Include="AutoFixture.AutoMoq" Version="4.11.0" />
+ <PackageReference Include="Moq" Version="4.13.1" />
+ <PackageReference Include="xunit" Version="2.4.1" />
+ <PackageReference Include="xunit.runner.visualstudio" Version="2.4.1" />
+ <PackageReference Include="coverlet.collector" Version="1.2.1" />
+ </ItemGroup>
+
+ <ItemGroup>
+ <ProjectReference Include="..\..\Emby.Server.Implementations\Emby.Server.Implementations.csproj" />
+ </ItemGroup>
+
+</Project>
diff --git a/tests/Jellyfin.Server.Implementations.Tests/Library/PathExtensionsTests.cs b/tests/Jellyfin.Server.Implementations.Tests/Library/PathExtensionsTests.cs
new file mode 100644
index 000000000..c771f5f4a
--- /dev/null
+++ b/tests/Jellyfin.Server.Implementations.Tests/Library/PathExtensionsTests.cs
@@ -0,0 +1,27 @@
+using System;
+using Emby.Server.Implementations.Library;
+using Xunit;
+
+namespace Jellyfin.Server.Implementations.Tests.Library
+{
+ public class PathExtensionsTests
+ {
+ [Theory]
+ [InlineData("Superman: Red Son [imdbid=tt10985510]", "imdbid", "tt10985510")]
+ [InlineData("Superman: Red Son - tt10985510", "imdbid", "tt10985510")]
+ [InlineData("Superman: Red Son", "imdbid", null)]
+ public void GetAttributeValue_ValidArgs_Correct(string input, string attribute, string? expectedResult)
+ {
+ Assert.Equal(expectedResult, PathExtensions.GetAttributeValue(input, attribute));
+ }
+
+ [Theory]
+ [InlineData("", "")]
+ [InlineData("Superman: Red Son [imdbid=tt10985510]", "")]
+ [InlineData("", "imdbid")]
+ public void GetAttributeValue_EmptyString_ThrowsArgumentException(string input, string attribute)
+ {
+ Assert.Throws<ArgumentException>(() => PathExtensions.GetAttributeValue(input, attribute));
+ }
+ }
+}
diff --git a/tests/coverletArgs.runsettings b/tests/coverletArgs.runsettings
new file mode 100644
index 000000000..3113957e0
--- /dev/null
+++ b/tests/coverletArgs.runsettings
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="utf-8" ?>
+<RunSettings>
+ <DataCollectionRunSettings>
+ <DataCollectors>
+ <DataCollector friendlyName="XPlat code coverage">
+ <Configuration>
+ <Format>cobertura</Format>
+ <Exclude>[coverlet.*.tests?]*,[*]Coverlet.Core*,[*]Moq*</Exclude> <!-- [Assembly-Filter]Type-Filter -->
+ <ExcludeByAttribute>Obsolete,GeneratedCodeAttribute,CompilerGeneratedAttribute</ExcludeByAttribute>
+ <SingleHit>false</SingleHit>
+ <UseSourceLink>true</UseSourceLink>
+ <IncludeTestAssembly>false</IncludeTestAssembly>
+ </Configuration>
+ </DataCollector>
+ </DataCollectors>
+ </DataCollectionRunSettings>
+</RunSettings> \ No newline at end of file
diff --git a/deployment/windows/build-jellyfin.ps1 b/windows/build-jellyfin.ps1
index dde6eb8fc..c762137a7 100644
--- a/deployment/windows/build-jellyfin.ps1
+++ b/windows/build-jellyfin.ps1
@@ -15,6 +15,8 @@ param(
[ValidateSet('x64','x86', 'arm', 'arm64')][string]$Architecture = 'x64'
)
+$ProgressPreference = 'SilentlyContinue' # Speedup all downloads by hiding progress bars.
+
#PowershellCore and *nix check to make determine which temp dir to use.
if(($PSVersionTable.PSEdition -eq 'Core') -and (-not $IsWindows)){
$TempDir = mktemp -d
@@ -44,7 +46,8 @@ function Build-JellyFin {
function Install-FFMPEG {
param(
[string]$ResolvedInstallLocation,
- [string]$Architecture
+ [string]$Architecture,
+ [string]$FFMPEGVersionX86 = "ffmpeg-4.2.1-win32-shared"
)
Write-Verbose "Checking Architecture"
if($Architecture -notin @('x86','x64')){
@@ -55,7 +58,7 @@ function Install-FFMPEG {
Invoke-WebRequest -Uri https://repo.jellyfin.org/releases/server/windows/ffmpeg/jellyfin-ffmpeg.zip -UseBasicParsing -OutFile "$tempdir/ffmpeg.zip" | Write-Verbose
}else{
Write-Verbose "Downloading 32 bit FFMPEG"
- Invoke-WebRequest -Uri https://ffmpeg.zeranoe.com/builds/win32/shared/ffmpeg-4.0.2-win32-shared.zip -UseBasicParsing -OutFile "$tempdir/ffmpeg.zip" | Write-Verbose
+ Invoke-WebRequest -Uri https://ffmpeg.zeranoe.com/builds/win32/shared/$FFMPEGVersionX86.zip -UseBasicParsing -OutFile "$tempdir/ffmpeg.zip" | Write-Verbose
}
Expand-Archive "$tempdir/ffmpeg.zip" -DestinationPath "$tempdir/ffmpeg/" -Force | Write-Verbose
@@ -66,7 +69,7 @@ function Install-FFMPEG {
}
}else{
Write-Verbose "Copying Binaries to Jellyfin location"
- Get-ChildItem "$tempdir/ffmpeg/ffmpeg-4.0.2-win32-shared/bin" | ForEach-Object {
+ Get-ChildItem "$tempdir/ffmpeg/$FFMPEGVersionX86/bin" | ForEach-Object {
Copy-Item $_.FullName -Destination $installLocation | Write-Verbose
}
}
diff --git a/deployment/windows/dependencies.txt b/windows/dependencies.txt
index 16f77cce7..16f77cce7 100644
--- a/deployment/windows/dependencies.txt
+++ b/windows/dependencies.txt
diff --git a/deployment/windows/dialogs/confirmation.nsddef b/windows/dialogs/confirmation.nsddef
index 969ebacd6..969ebacd6 100644
--- a/deployment/windows/dialogs/confirmation.nsddef
+++ b/windows/dialogs/confirmation.nsddef
diff --git a/deployment/windows/dialogs/confirmation.nsdinc b/windows/dialogs/confirmation.nsdinc
index f00e9b43a..f00e9b43a 100644
--- a/deployment/windows/dialogs/confirmation.nsdinc
+++ b/windows/dialogs/confirmation.nsdinc
diff --git a/deployment/windows/dialogs/service-config.nsddef b/windows/dialogs/service-config.nsddef
index 3509ada24..3509ada24 100644
--- a/deployment/windows/dialogs/service-config.nsddef
+++ b/windows/dialogs/service-config.nsddef
diff --git a/deployment/windows/dialogs/service-config.nsdinc b/windows/dialogs/service-config.nsdinc
index 58c350f2e..58c350f2e 100644
--- a/deployment/windows/dialogs/service-config.nsdinc
+++ b/windows/dialogs/service-config.nsdinc
diff --git a/deployment/windows/dialogs/setuptype.nsddef b/windows/dialogs/setuptype.nsddef
index b55ceeaaa..b55ceeaaa 100644
--- a/deployment/windows/dialogs/setuptype.nsddef
+++ b/windows/dialogs/setuptype.nsddef
diff --git a/deployment/windows/dialogs/setuptype.nsdinc b/windows/dialogs/setuptype.nsdinc
index 8746ad2cc..8746ad2cc 100644
--- a/deployment/windows/dialogs/setuptype.nsdinc
+++ b/windows/dialogs/setuptype.nsdinc
diff --git a/deployment/windows/helpers/ShowError.nsh b/windows/helpers/ShowError.nsh
index 6e09b1e40..6e09b1e40 100644
--- a/deployment/windows/helpers/ShowError.nsh
+++ b/windows/helpers/ShowError.nsh
diff --git a/deployment/windows/helpers/StrSlash.nsh b/windows/helpers/StrSlash.nsh
index b8aa771aa..b8aa771aa 100644
--- a/deployment/windows/helpers/StrSlash.nsh
+++ b/windows/helpers/StrSlash.nsh
diff --git a/deployment/windows/jellyfin.nsi b/windows/jellyfin.nsi
index 5666d30f0..fada62d98 100644
--- a/deployment/windows/jellyfin.nsi
+++ b/windows/jellyfin.nsi
@@ -1,9 +1,9 @@
-; Shows a lot of debug information while compiling
-; This can be removed once stable.
-!verbose 4
-SetCompressor lzma
+!verbose 3
+SetCompressor /SOLID bzip2
ShowInstDetails show
ShowUninstDetails show
+Unicode True
+
;--------------------------------
!define SF_USELECTED 0 ; used to check selected options status, rest are inherited from Sections.nsh
@@ -73,7 +73,7 @@ ShowUninstDetails show
; TODO: Replace with nice Jellyfin Icons
!ifdef UXPATH
!define MUI_ICON "${UXPATH}\branding\NSIS\modern-install.ico" ; Installer Icon
- !define MUI_UNICON "${UXPATH}\branding\NSIS\modern-uninstall.ico" ; Uninstaller Icon
+ !define MUI_UNICON "${UXPATH}\branding\NSIS\modern-install.ico" ; Uninstaller Icon
!define MUI_HEADERIMAGE
!define MUI_HEADERIMAGE_BITMAP "${UXPATH}\branding\NSIS\installer-header.bmp"
diff --git a/deployment/windows/legacy/install-jellyfin.ps1 b/windows/legacy/install-jellyfin.ps1
index e909a0468..e909a0468 100644
--- a/deployment/windows/legacy/install-jellyfin.ps1
+++ b/windows/legacy/install-jellyfin.ps1
diff --git a/deployment/windows/legacy/install.bat b/windows/legacy/install.bat
index e21479a79..e21479a79 100644
--- a/deployment/windows/legacy/install.bat
+++ b/windows/legacy/install.bat